]> git.proxmox.com Git - mirror_qemu.git/blobdiff - hw/usb-ehci.c
framebuffer: drop use of cpu_physical_sync_dirty_bitmap()
[mirror_qemu.git] / hw / usb-ehci.c
index 9051571e41955882afcdc5e45c7da4a98f751fb2..7c926c0d47cfc4e0e32e3ab323a8c414360f260c 100644 (file)
@@ -20,9 +20,6 @@
  *
  * You should have received a copy of the GNU General Public License
  * along with this program; if not, see <http://www.gnu.org/licenses/>.
- *
- * TODO:
- *  o Downstream port handoff
  */
 
 #include "hw.h"
@@ -31,6 +28,7 @@
 #include "pci.h"
 #include "monitor.h"
 #include "trace.h"
+#include "dma.h"
 
 #define EHCI_DEBUG   0
 
 #define PORTSC_BEGIN         PORTSC
 #define PORTSC_END           (PORTSC + 4 * NB_PORTS)
 /*
- * Bits that are reserverd or are read-only are masked out of values
+ * Bits that are reserved or are read-only are masked out of values
  * written to us by software
  */
-#define PORTSC_RO_MASK       0x007021c5
+#define PORTSC_RO_MASK       0x007001c0
 #define PORTSC_RWC_MASK      0x0000002a
 #define PORTSC_WKOC_E        (1 << 22)    // Wake on Over Current Enable
 #define PORTSC_WKDS_E        (1 << 21)    // Wake on Disconnect Enable
 #define PORTSC_CONNECT       (1 << 0)     // Current Connect Status
 
 #define FRAME_TIMER_FREQ 1000
-#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
+#define FRAME_TIMER_NS   (1000000000 / FRAME_TIMER_FREQ)
 
 #define NB_MAXINTRATE    8        // Max rate at which controller issues ints
-#define NB_PORTS         4        // Number of downstream ports
+#define NB_PORTS         6        // Number of downstream ports
 #define BUFF_SIZE        5*4096   // Max bytes to transfer per transaction
 #define MAX_ITERATIONS   20       // Max number of QH before we break the loop
 #define MAX_QH           100      // Max allowable queue heads in a chain
@@ -151,6 +149,7 @@ typedef enum {
     EST_FETCHENTRY,
     EST_FETCHQH,
     EST_FETCHITD,
+    EST_FETCHSITD,
     EST_ADVANCEQUEUE,
     EST_FETCHQTD,
     EST_EXECUTE,
@@ -198,6 +197,7 @@ typedef struct EHCIitd {
 #define ITD_BUFPTR_MAXPKT_MASK   0x000007ff
 #define ITD_BUFPTR_MAXPKT_SH     0
 #define ITD_BUFPTR_MULT_MASK     0x00000003
+#define ITD_BUFPTR_MULT_SH       0
 } EHCIitd;
 
 /*  EHCI spec version 1.0 Section 3.4
@@ -271,6 +271,7 @@ typedef struct EHCIqtd {
 
     uint32_t bufptr[5];               // Standard buffer pointer
 #define QTD_BUFPTR_MASK               0xfffff000
+#define QTD_BUFPTR_SH                 12
 } EHCIqtd;
 
 /*  EHCI spec version 1.0 Section 3.6
@@ -347,7 +348,8 @@ struct EHCIQueue {
     EHCIState *ehci;
     QTAILQ_ENTRY(EHCIQueue) next;
     bool async_schedule;
-    uint32_t seen, ts;
+    uint32_t seen;
+    uint64_t ts;
 
     /* cached data from guest - needs to be flushed
      * when guest removes an entry (doorbell, handshake sequence)
@@ -358,7 +360,7 @@ struct EHCIQueue {
     uint32_t qtdaddr;      // address QTD read from
 
     USBPacket packet;
-    uint8_t buffer[BUFF_SIZE];
+    QEMUSGList sgl;
     int pid;
     uint32_t tbytes;
     enum async_state async;
@@ -369,9 +371,13 @@ struct EHCIState {
     PCIDevice dev;
     USBBus bus;
     qemu_irq irq;
-    target_phys_addr_t mem_base;
-    int mem;
-    int num_ports;
+    MemoryRegion mem;
+    int companion_count;
+
+    /* properties */
+    uint32_t freq;
+    uint32_t maxframes;
+
     /*
      *  EHCI spec version 1.0 Section 2.3
      *  Host Controller Operational Registers
@@ -402,6 +408,7 @@ struct EHCIState {
     int astate;                        // Current state in asynchronous schedule
     int pstate;                        // Current state in periodic schedule
     USBPort ports[NB_PORTS];
+    USBPort *companion_ports[NB_PORTS];
     uint32_t usbsts_pending;
     QTAILQ_HEAD(, EHCIQueue) queues;
 
@@ -409,15 +416,14 @@ struct EHCIState {
     uint32_t p_fetch_addr;   // which address to look at next
 
     USBPacket ipacket;
-    uint8_t ibuffer[BUFF_SIZE];
+    QEMUSGList isgl;
     int isoch_pause;
 
-    uint32_t last_run_usec;
-    uint32_t frame_end_usec;
+    uint64_t last_run_ns;
 };
 
 #define SET_LAST_RUN_CLOCK(s) \
-    (s)->last_run_usec = qemu_get_clock_ns(vm_clock) / 1000;
+    (s)->last_run_ns = qemu_get_clock_ns(vm_clock);
 
 /* nifty macros from Arnon's EHCI version  */
 #define get_field(data, field) \
@@ -431,37 +437,39 @@ struct EHCIState {
     } while(0)
 
 static const char *ehci_state_names[] = {
-    [ EST_INACTIVE ]     = "INACTIVE",
-    [ EST_ACTIVE ]       = "ACTIVE",
-    [ EST_EXECUTING ]    = "EXECUTING",
-    [ EST_SLEEPING ]     = "SLEEPING",
-    [ EST_WAITLISTHEAD ] = "WAITLISTHEAD",
-    [ EST_FETCHENTRY ]   = "FETCH ENTRY",
-    [ EST_FETCHQH ]      = "FETCH QH",
-    [ EST_FETCHITD ]     = "FETCH ITD",
-    [ EST_ADVANCEQUEUE ] = "ADVANCEQUEUE",
-    [ EST_FETCHQTD ]     = "FETCH QTD",
-    [ EST_EXECUTE ]      = "EXECUTE",
-    [ EST_WRITEBACK ]    = "WRITEBACK",
-    [ EST_HORIZONTALQH ] = "HORIZONTALQH",
+    [EST_INACTIVE]     = "INACTIVE",
+    [EST_ACTIVE]       = "ACTIVE",
+    [EST_EXECUTING]    = "EXECUTING",
+    [EST_SLEEPING]     = "SLEEPING",
+    [EST_WAITLISTHEAD] = "WAITLISTHEAD",
+    [EST_FETCHENTRY]   = "FETCH ENTRY",
+    [EST_FETCHQH]      = "FETCH QH",
+    [EST_FETCHITD]     = "FETCH ITD",
+    [EST_ADVANCEQUEUE] = "ADVANCEQUEUE",
+    [EST_FETCHQTD]     = "FETCH QTD",
+    [EST_EXECUTE]      = "EXECUTE",
+    [EST_WRITEBACK]    = "WRITEBACK",
+    [EST_HORIZONTALQH] = "HORIZONTALQH",
 };
 
 static const char *ehci_mmio_names[] = {
-    [ CAPLENGTH ]        = "CAPLENGTH",
-    [ HCIVERSION ]       = "HCIVERSION",
-    [ HCSPARAMS ]        = "HCSPARAMS",
-    [ HCCPARAMS ]        = "HCCPARAMS",
-    [ USBCMD ]           = "USBCMD",
-    [ USBSTS ]           = "USBSTS",
-    [ USBINTR ]          = "USBINTR",
-    [ FRINDEX ]          = "FRINDEX",
-    [ PERIODICLISTBASE ] = "P-LIST BASE",
-    [ ASYNCLISTADDR ]    = "A-LIST ADDR",
-    [ PORTSC_BEGIN ]     = "PORTSC #0",
-    [ PORTSC_BEGIN + 4]  = "PORTSC #1",
-    [ PORTSC_BEGIN + 8]  = "PORTSC #2",
-    [ PORTSC_BEGIN + 12] = "PORTSC #3",
-    [ CONFIGFLAG ]       = "CONFIGFLAG",
+    [CAPLENGTH]         = "CAPLENGTH",
+    [HCIVERSION]        = "HCIVERSION",
+    [HCSPARAMS]         = "HCSPARAMS",
+    [HCCPARAMS]         = "HCCPARAMS",
+    [USBCMD]            = "USBCMD",
+    [USBSTS]            = "USBSTS",
+    [USBINTR]           = "USBINTR",
+    [FRINDEX]           = "FRINDEX",
+    [PERIODICLISTBASE]  = "P-LIST BASE",
+    [ASYNCLISTADDR]     = "A-LIST ADDR",
+    [PORTSC_BEGIN]      = "PORTSC #0",
+    [PORTSC_BEGIN + 4]  = "PORTSC #1",
+    [PORTSC_BEGIN + 8]  = "PORTSC #2",
+    [PORTSC_BEGIN + 12] = "PORTSC #3",
+    [PORTSC_BEGIN + 16] = "PORTSC #4",
+    [PORTSC_BEGIN + 20] = "PORTSC #5",
+    [CONFIGFLAG]        = "CONFIGFLAG",
 };
 
 static const char *nr2str(const char **n, size_t len, uint32_t nr)
@@ -599,36 +607,53 @@ static int ehci_get_fetch_addr(EHCIState *s, int async)
 
 static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh)
 {
-    trace_usb_ehci_qh(q, addr, qh->next,
-                      qh->current_qtd, qh->next_qtd, qh->altnext_qtd,
-                      get_field(qh->epchar, QH_EPCHAR_RL),
-                      get_field(qh->epchar, QH_EPCHAR_MPLEN),
-                      get_field(qh->epchar, QH_EPCHAR_EPS),
-                      get_field(qh->epchar, QH_EPCHAR_EP),
-                      get_field(qh->epchar, QH_EPCHAR_DEVADDR),
-                      (bool)(qh->epchar & QH_EPCHAR_C),
-                      (bool)(qh->epchar & QH_EPCHAR_H),
-                      (bool)(qh->epchar & QH_EPCHAR_DTC),
-                      (bool)(qh->epchar & QH_EPCHAR_I));
+    /* need three here due to argument count limits */
+    trace_usb_ehci_qh_ptrs(q, addr, qh->next,
+                           qh->current_qtd, qh->next_qtd, qh->altnext_qtd);
+    trace_usb_ehci_qh_fields(addr,
+                             get_field(qh->epchar, QH_EPCHAR_RL),
+                             get_field(qh->epchar, QH_EPCHAR_MPLEN),
+                             get_field(qh->epchar, QH_EPCHAR_EPS),
+                             get_field(qh->epchar, QH_EPCHAR_EP),
+                             get_field(qh->epchar, QH_EPCHAR_DEVADDR));
+    trace_usb_ehci_qh_bits(addr,
+                           (bool)(qh->epchar & QH_EPCHAR_C),
+                           (bool)(qh->epchar & QH_EPCHAR_H),
+                           (bool)(qh->epchar & QH_EPCHAR_DTC),
+                           (bool)(qh->epchar & QH_EPCHAR_I));
 }
 
 static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd)
 {
-    trace_usb_ehci_qtd(q, addr, qtd->next, qtd->altnext,
-                       get_field(qtd->token, QTD_TOKEN_TBYTES),
-                       get_field(qtd->token, QTD_TOKEN_CPAGE),
-                       get_field(qtd->token, QTD_TOKEN_CERR),
-                       get_field(qtd->token, QTD_TOKEN_PID),
-                       (bool)(qtd->token & QTD_TOKEN_IOC),
-                       (bool)(qtd->token & QTD_TOKEN_ACTIVE),
-                       (bool)(qtd->token & QTD_TOKEN_HALT),
-                       (bool)(qtd->token & QTD_TOKEN_BABBLE),
-                       (bool)(qtd->token & QTD_TOKEN_XACTERR));
+    /* need three here due to argument count limits */
+    trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext);
+    trace_usb_ehci_qtd_fields(addr,
+                              get_field(qtd->token, QTD_TOKEN_TBYTES),
+                              get_field(qtd->token, QTD_TOKEN_CPAGE),
+                              get_field(qtd->token, QTD_TOKEN_CERR),
+                              get_field(qtd->token, QTD_TOKEN_PID));
+    trace_usb_ehci_qtd_bits(addr,
+                            (bool)(qtd->token & QTD_TOKEN_IOC),
+                            (bool)(qtd->token & QTD_TOKEN_ACTIVE),
+                            (bool)(qtd->token & QTD_TOKEN_HALT),
+                            (bool)(qtd->token & QTD_TOKEN_BABBLE),
+                            (bool)(qtd->token & QTD_TOKEN_XACTERR));
 }
 
 static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd)
 {
-    trace_usb_ehci_itd(addr, itd->next);
+    trace_usb_ehci_itd(addr, itd->next,
+                       get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT),
+                       get_field(itd->bufptr[2], ITD_BUFPTR_MULT),
+                       get_field(itd->bufptr[0], ITD_BUFPTR_EP),
+                       get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR));
+}
+
+static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr,
+                            EHCIsitd *sitd)
+{
+    trace_usb_ehci_sitd(addr, sitd->next,
+                        (bool)(sitd->results & SITD_RESULTS_ACTIVE));
 }
 
 /* queue management */
@@ -637,7 +662,7 @@ static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, int async)
 {
     EHCIQueue *q;
 
-    q = qemu_mallocz(sizeof(*q));
+    q = g_malloc0(sizeof(*q));
     q->ehci = ehci;
     q->async_schedule = async;
     QTAILQ_INSERT_HEAD(&ehci->queues, q, next);
@@ -652,7 +677,7 @@ static void ehci_free_queue(EHCIQueue *q)
         usb_cancel_packet(&q->packet);
     }
     QTAILQ_REMOVE(&q->ehci->queues, q, next);
-    qemu_free(q);
+    g_free(q);
 }
 
 static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr)
@@ -674,10 +699,10 @@ static void ehci_queues_rip_unused(EHCIState *ehci)
     QTAILQ_FOREACH_SAFE(q, &ehci->queues, next, tmp) {
         if (q->seen) {
             q->seen = 0;
-            q->ts = ehci->last_run_usec;
+            q->ts = ehci->last_run_ns;
             continue;
         }
-        if (ehci->last_run_usec < q->ts + 250000) {
+        if (ehci->last_run_ns < q->ts + 250000000) {
             /* allow 0.25 sec idle */
             continue;
         }
@@ -715,17 +740,17 @@ static void ehci_attach(USBPort *port)
 
     trace_usb_ehci_port_attach(port->index, port->dev->product_desc);
 
+    if (*portsc & PORTSC_POWNER) {
+        USBPort *companion = s->companion_ports[port->index];
+        companion->dev = port->dev;
+        companion->ops->attach(companion);
+        return;
+    }
+
     *portsc |= PORTSC_CONNECT;
     *portsc |= PORTSC_CSC;
 
-    /*
-     *  If a high speed device is attached then we own this port(indicated
-     *  by zero in the PORTSC_POWNER bit field) so set the status bit
-     *  and set an interrupt if enabled.
-     */
-    if ( !(*portsc & PORTSC_POWNER)) {
-        ehci_set_interrupt(s, USBSTS_PCD);
-    }
+    ehci_set_interrupt(s, USBSTS_PCD);
 }
 
 static void ehci_detach(USBPort *port)
@@ -735,27 +760,110 @@ static void ehci_detach(USBPort *port)
 
     trace_usb_ehci_port_detach(port->index);
 
-    *portsc &= ~PORTSC_CONNECT;
+    if (*portsc & PORTSC_POWNER) {
+        USBPort *companion = s->companion_ports[port->index];
+        companion->ops->detach(companion);
+        companion->dev = NULL;
+        return;
+    }
+
+    ehci_queues_rip_device(s, port->dev);
+
+    *portsc &= ~(PORTSC_CONNECT|PORTSC_PED);
     *portsc |= PORTSC_CSC;
 
-    /*
-     *  If a high speed device is attached then we own this port(indicated
-     *  by zero in the PORTSC_POWNER bit field) so set the status bit
-     *  and set an interrupt if enabled.
-     */
-    if ( !(*portsc & PORTSC_POWNER)) {
-        ehci_set_interrupt(s, USBSTS_PCD);
+    ehci_set_interrupt(s, USBSTS_PCD);
+}
+
+static void ehci_child_detach(USBPort *port, USBDevice *child)
+{
+    EHCIState *s = port->opaque;
+    uint32_t portsc = s->portsc[port->index];
+
+    if (portsc & PORTSC_POWNER) {
+        USBPort *companion = s->companion_ports[port->index];
+        companion->ops->child_detach(companion, child);
+        companion->dev = NULL;
+        return;
+    }
+
+    ehci_queues_rip_device(s, child);
+}
+
+static void ehci_wakeup(USBPort *port)
+{
+    EHCIState *s = port->opaque;
+    uint32_t portsc = s->portsc[port->index];
+
+    if (portsc & PORTSC_POWNER) {
+        USBPort *companion = s->companion_ports[port->index];
+        if (companion->ops->wakeup) {
+            companion->ops->wakeup(companion);
+        }
     }
 }
 
+static int ehci_register_companion(USBBus *bus, USBPort *ports[],
+                                   uint32_t portcount, uint32_t firstport)
+{
+    EHCIState *s = container_of(bus, EHCIState, bus);
+    uint32_t i;
+
+    if (firstport + portcount > NB_PORTS) {
+        qerror_report(QERR_INVALID_PARAMETER_VALUE, "firstport",
+                      "firstport on masterbus");
+        error_printf_unless_qmp(
+            "firstport value of %u makes companion take ports %u - %u, which "
+            "is outside of the valid range of 0 - %u\n", firstport, firstport,
+            firstport + portcount - 1, NB_PORTS - 1);
+        return -1;
+    }
+
+    for (i = 0; i < portcount; i++) {
+        if (s->companion_ports[firstport + i]) {
+            qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus",
+                          "an USB masterbus");
+            error_printf_unless_qmp(
+                "port %u on masterbus %s already has a companion assigned\n",
+                firstport + i, bus->qbus.name);
+            return -1;
+        }
+    }
+
+    for (i = 0; i < portcount; i++) {
+        s->companion_ports[firstport + i] = ports[i];
+        s->ports[firstport + i].speedmask |=
+            USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL;
+        /* Ensure devs attached before the initial reset go to the companion */
+        s->portsc[firstport + i] = PORTSC_POWNER;
+    }
+
+    s->companion_count++;
+    s->mmio[0x05] = (s->companion_count << 4) | portcount;
+
+    return 0;
+}
+
 /* 4.1 host controller initialization */
 static void ehci_reset(void *opaque)
 {
     EHCIState *s = opaque;
     int i;
+    USBDevice *devs[NB_PORTS];
 
     trace_usb_ehci_reset();
 
+    /*
+     * Do the detach before touching portsc, so that it correctly gets send to
+     * us or to our companion based on PORTSC_POWNER before the reset.
+     */
+    for(i = 0; i < NB_PORTS; i++) {
+        devs[i] = s->ports[i].dev;
+        if (devs[i] && devs[i]->attached) {
+            usb_detach(&s->ports[i]);
+        }
+    }
+
     memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
 
     s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
@@ -767,10 +875,14 @@ static void ehci_reset(void *opaque)
     s->attach_poll_counter = 0;
 
     for(i = 0; i < NB_PORTS; i++) {
-        s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
-
-        if (s->ports[i].dev) {
-            usb_attach(&s->ports[i], s->ports[i].dev);
+        if (s->companion_ports[i]) {
+            s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
+        } else {
+            s->portsc[i] = PORTSC_PPOWER;
+        }
+        if (devs[i] && devs[i]->attached) {
+            usb_attach(&s->ports[i]);
+            usb_send_msg(devs[i], USB_MSG_RESET);
         }
     }
     ehci_queues_rip_all(s);
@@ -820,43 +932,66 @@ static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val)
     exit(1);
 }
 
+static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
+{
+    USBDevice *dev = s->ports[port].dev;
+    uint32_t *portsc = &s->portsc[port];
+    uint32_t orig;
+
+    if (s->companion_ports[port] == NULL)
+        return;
+
+    owner = owner & PORTSC_POWNER;
+    orig  = *portsc & PORTSC_POWNER;
+
+    if (!(owner ^ orig)) {
+        return;
+    }
+
+    if (dev && dev->attached) {
+        usb_detach(&s->ports[port]);
+    }
+
+    *portsc &= ~PORTSC_POWNER;
+    *portsc |= owner;
+
+    if (dev && dev->attached) {
+        usb_attach(&s->ports[port]);
+    }
+}
+
 static void handle_port_status_write(EHCIState *s, int port, uint32_t val)
 {
     uint32_t *portsc = &s->portsc[port];
-    int rwc;
     USBDevice *dev = s->ports[port].dev;
 
-    rwc = val & PORTSC_RWC_MASK;
+    /* Clear rwc bits */
+    *portsc &= ~(val & PORTSC_RWC_MASK);
+    /* The guest may clear, but not set the PED bit */
+    *portsc &= val | ~PORTSC_PED;
+    /* POWNER is masked out by RO_MASK as it is RO when we've no companion */
+    handle_port_owner_write(s, port, val);
+    /* And finally apply RO_MASK */
     val &= PORTSC_RO_MASK;
 
-    // handle_read_write_clear(&val, portsc, PORTSC_PEDC | PORTSC_CSC);
-
-    *portsc &= ~rwc;
-
     if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
         trace_usb_ehci_port_reset(port, 1);
     }
 
     if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
         trace_usb_ehci_port_reset(port, 0);
-        usb_attach(&s->ports[port], dev);
-
-        // TODO how to handle reset of ports with no device
-        if (dev) {
-            usb_send_msg(dev, USB_MSG_RESET);
-        }
-
-        if (s->ports[port].dev) {
+        if (dev && dev->attached) {
+            usb_reset(&s->ports[port]);
             *portsc &= ~PORTSC_CSC;
         }
 
-        /*  Table 2.16 Set the enable bit(and enable bit change) to indicate
+        /*
+         *  Table 2.16 Set the enable bit(and enable bit change) to indicate
          *  to SW that this port has a high speed device attached
-         *
-         *  TODO - when to disable?
          */
-        val |= PORTSC_PED;
-        val |= PORTSC_PEDC;
+        if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
+            val |= PORTSC_PED;
+        }
     }
 
     *portsc &= ~PORTSC_RO_MASK;
@@ -939,7 +1074,7 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
         val &= 0x1;
         if (val) {
             for(i = 0; i < NB_PORTS; i++)
-                s->portsc[i] &= ~PORTSC_POWNER;
+                handle_port_owner_write(s, i, 0);
         }
         break;
 
@@ -968,12 +1103,13 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
 // TODO : Put in common header file, duplication from usb-ohci.c
 
 /* Get an array of dwords from main memory */
-static inline int get_dwords(uint32_t addr, uint32_t *buf, int num)
+static inline int get_dwords(EHCIState *ehci, uint32_t addr,
+                             uint32_t *buf, int num)
 {
     int i;
 
     for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
-        cpu_physical_memory_rw(addr,(uint8_t *)buf, sizeof(*buf), 0);
+        pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf));
         *buf = le32_to_cpu(*buf);
     }
 
@@ -981,13 +1117,14 @@ static inline int get_dwords(uint32_t addr, uint32_t *buf, int num)
 }
 
 /* Put an array of dwords in to main memory */
-static inline int put_dwords(uint32_t addr, uint32_t *buf, int num)
+static inline int put_dwords(EHCIState *ehci, uint32_t addr,
+                             uint32_t *buf, int num)
 {
     int i;
 
     for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
         uint32_t tmp = cpu_to_le32(*buf);
-        cpu_physical_memory_rw(addr,(uint8_t *)&tmp, sizeof(tmp), 1);
+        pci_dma_write(&ehci->dev, addr, &tmp, sizeof(tmp));
     }
 
     return 1;
@@ -1036,73 +1173,81 @@ static int ehci_qh_do_overlay(EHCIQueue *q)
     q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
     q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
 
-    put_dwords(NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
+    put_dwords(q->ehci, NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh,
+               sizeof(EHCIqh) >> 2);
 
     return 0;
 }
 
-static int ehci_buffer_rw(EHCIQueue *q, int bytes, int rw)
+static int ehci_init_transfer(EHCIQueue *q)
 {
-    int bufpos = 0;
-    int cpage, offset;
-    uint32_t head;
-    uint32_t tail;
-
-
-    if (!bytes) {
-        return 0;
-    }
-
-    cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
-    if (cpage > 4) {
-        fprintf(stderr, "cpage out of range (%d)\n", cpage);
-        return USB_RET_PROCERR;
-    }
+    uint32_t cpage, offset, bytes, plen;
+    dma_addr_t page;
 
+    cpage  = get_field(q->qh.token, QTD_TOKEN_CPAGE);
+    bytes  = get_field(q->qh.token, QTD_TOKEN_TBYTES);
     offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
+    pci_dma_sglist_init(&q->sgl, &q->ehci->dev, 5);
 
-    do {
-        /* start and end of this page */
-        head = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
-        tail = head + ~QTD_BUFPTR_MASK + 1;
-        /* add offset into page */
-        head |= offset;
-
-        if (bytes <= (tail - head)) {
-            tail = head + bytes;
+    while (bytes > 0) {
+        if (cpage > 4) {
+            fprintf(stderr, "cpage out of range (%d)\n", cpage);
+            return USB_RET_PROCERR;
         }
 
-        trace_usb_ehci_data(rw, cpage, offset, head, tail-head, bufpos);
-        cpu_physical_memory_rw(head, q->buffer + bufpos, tail - head, rw);
-
-        bufpos += (tail - head);
-        offset += (tail - head);
-        bytes -= (tail - head);
-
-        if (bytes > 0) {
-            cpage++;
+        page  = q->qh.bufptr[cpage] & QTD_BUFPTR_MASK;
+        page += offset;
+        plen  = bytes;
+        if (plen > 4096 - offset) {
+            plen = 4096 - offset;
             offset = 0;
+            cpage++;
         }
-    } while (bytes > 0);
 
-    /* save cpage */
-    set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
+        qemu_sglist_add(&q->sgl, page, plen);
+        bytes -= plen;
+    }
+    return 0;
+}
 
-    /* save offset into cpage */
-    q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
-    q->qh.bufptr[0] |= offset;
+static void ehci_finish_transfer(EHCIQueue *q, int status)
+{
+    uint32_t cpage, offset;
 
-    return 0;
+    qemu_sglist_destroy(&q->sgl);
+
+    if (status > 0) {
+        /* update cpage & offset */
+        cpage  = get_field(q->qh.token, QTD_TOKEN_CPAGE);
+        offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
+
+        offset += status;
+        cpage  += offset >> QTD_BUFPTR_SH;
+        offset &= ~QTD_BUFPTR_MASK;
+
+        set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
+        q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
+        q->qh.bufptr[0] |= offset;
+    }
 }
 
-static void ehci_async_complete_packet(USBDevice *dev, USBPacket *packet)
+static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
 {
-    EHCIQueue *q = container_of(packet, EHCIQueue, packet);
+    EHCIQueue *q;
+    EHCIState *s = port->opaque;
+    uint32_t portsc = s->portsc[port->index];
+
+    if (portsc & PORTSC_POWNER) {
+        USBPort *companion = s->companion_ports[port->index];
+        companion->ops->complete(companion, packet);
+        return;
+    }
 
+    q = container_of(packet, EHCIQueue, packet);
     trace_usb_ehci_queue_action(q, "wakeup");
     assert(q->async == EHCI_ASYNC_INFLIGHT);
     q->async = EHCI_ASYNC_FINISHED;
-    q->usb_status = packet->len;
+    q->usb_status = packet->result;
 }
 
 static void ehci_execute_complete(EHCIQueue *q)
@@ -1162,10 +1307,6 @@ err:
         }
 
         if (q->tbytes && q->pid == USB_TOKEN_IN) {
-            if (ehci_buffer_rw(q, q->usb_status, 1) != 0) {
-                q->usb_status = USB_RET_PROCERR;
-                return;
-            }
             q->tbytes -= q->usb_status;
         } else {
             q->tbytes = 0;
@@ -1174,6 +1315,8 @@ err:
         DPRINTF("updating tbytes to %d\n", q->tbytes);
         set_field(&q->qh.token, q->tbytes, QTD_TOKEN_TBYTES);
     }
+    ehci_finish_transfer(q, q->usb_status);
+    usb_packet_unmap(&q->packet);
 
     q->qh.token ^= QTD_TOKEN_DTOGGLE;
     q->qh.token &= ~QTD_TOKEN_ACTIVE;
@@ -1213,8 +1356,7 @@ static int ehci_execute(EHCIQueue *q)
         default: fprintf(stderr, "bad token\n"); break;
     }
 
-    if ((q->tbytes && q->pid != USB_TOKEN_IN) &&
-        (ehci_buffer_rw(q, q->tbytes, 0) != 0)) {
+    if (ehci_init_transfer(q) != 0) {
         return USB_RET_PROCERR;
     }
 
@@ -1223,30 +1365,26 @@ static int ehci_execute(EHCIQueue *q)
 
     ret = USB_RET_NODEV;
 
+    usb_packet_setup(&q->packet, q->pid, devadr, endp);
+    usb_packet_map(&q->packet, &q->sgl);
+
     // TO-DO: associating device with ehci port
     for(i = 0; i < NB_PORTS; i++) {
         port = &q->ehci->ports[i];
         dev = port->dev;
 
-        // TODO sometime we will also need to check if we are the port owner
-
         if (!(q->ehci->portsc[i] &(PORTSC_CONNECT))) {
             DPRINTF("Port %d, no exec, not connected(%08X)\n",
                     i, q->ehci->portsc[i]);
             continue;
         }
 
-        q->packet.pid = q->pid;
-        q->packet.devaddr = devadr;
-        q->packet.devep = endp;
-        q->packet.data = q->buffer;
-        q->packet.len = q->tbytes;
-
         ret = usb_handle_packet(dev, &q->packet);
 
-        DPRINTF("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n",
+        DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd "
+                "(total %d) endp %x ret %d\n",
                 q->qhaddr, q->qh.next, q->qtdaddr, q->pid,
-                q->packet.len, q->tbytes, endp, ret);
+                q->packet.iov.size, q->tbytes, endp, ret);
 
         if (ret != USB_RET_NODEV) {
             break;
@@ -1270,63 +1408,56 @@ static int ehci_process_itd(EHCIState *ehci,
     USBPort *port;
     USBDevice *dev;
     int ret;
-    int i, j;
-    int ptr;
-    int pid;
-    int pg;
-    int len;
-    int dir;
-    int devadr;
-    int endp;
+    uint32_t i, j, len, pid, dir, devaddr, endp;
+    uint32_t pg, off, ptr1, ptr2, max, mult;
 
     dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
-    devadr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
+    devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
     endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
-    /* maxpkt = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT); */
+    max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT);
+    mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT);
 
     for(i = 0; i < 8; i++) {
         if (itd->transact[i] & ITD_XACT_ACTIVE) {
-            DPRINTF("ISOCHRONOUS active for frame %d, interval %d\n",
-                    ehci->frindex >> 3, i);
-
-            pg = get_field(itd->transact[i], ITD_XACT_PGSEL);
-            ptr = (itd->bufptr[pg] & ITD_BUFPTR_MASK) |
-                (itd->transact[i] & ITD_XACT_OFFSET_MASK);
-            len = get_field(itd->transact[i], ITD_XACT_LENGTH);
+            pg   = get_field(itd->transact[i], ITD_XACT_PGSEL);
+            off  = itd->transact[i] & ITD_XACT_OFFSET_MASK;
+            ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
+            ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK);
+            len  = get_field(itd->transact[i], ITD_XACT_LENGTH);
+
+            if (len > max * mult) {
+                len = max * mult;
+            }
 
             if (len > BUFF_SIZE) {
                 return USB_RET_PROCERR;
             }
 
-            DPRINTF("ISOCH: buffer %08X len %d\n", ptr, len);
+            pci_dma_sglist_init(&ehci->isgl, &ehci->dev, 2);
+            if (off + len > 4096) {
+                /* transfer crosses page border */
+                uint32_t len2 = off + len - 4096;
+                uint32_t len1 = len - len2;
+                qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
+                qemu_sglist_add(&ehci->isgl, ptr2, len2);
+            } else {
+                qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
+            }
 
-            if (!dir) {
-                cpu_physical_memory_rw(ptr, &ehci->ibuffer[0], len, 0);
-                pid = USB_TOKEN_OUT;
-            } else
-                pid = USB_TOKEN_IN;
+            pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
 
-            ret = USB_RET_NODEV;
+            usb_packet_setup(&ehci->ipacket, pid, devaddr, endp);
+            usb_packet_map(&ehci->ipacket, &ehci->isgl);
 
+            ret = USB_RET_NODEV;
             for (j = 0; j < NB_PORTS; j++) {
                 port = &ehci->ports[j];
                 dev = port->dev;
 
-                // TODO sometime we will also need to check if we are the port owner
-
                 if (!(ehci->portsc[j] &(PORTSC_CONNECT))) {
-                    DPRINTF("Port %d, no exec, not connected(%08X)\n",
-                            j, ehci->portsc[j]);
                     continue;
                 }
 
-                ehci->ipacket.pid = pid;
-                ehci->ipacket.devaddr = devadr;
-                ehci->ipacket.devep = endp;
-                ehci->ipacket.data = ehci->ibuffer;
-                ehci->ipacket.len = len;
-
-                DPRINTF("calling usb_handle_packet\n");
                 ret = usb_handle_packet(dev, &ehci->ipacket);
 
                 if (ret != USB_RET_NODEV) {
@@ -1334,6 +1465,10 @@ static int ehci_process_itd(EHCIState *ehci,
                 }
             }
 
+            usb_packet_unmap(&ehci->ipacket);
+            qemu_sglist_destroy(&ehci->isgl);
+
+#if 0
             /*  In isoch, there is no facility to indicate a NAK so let's
              *  instead just complete a zero-byte transaction.  Setting
              *  DBERR seems too draconian.
@@ -1358,24 +1493,26 @@ static int ehci_process_itd(EHCIState *ehci,
                 DPRINTF("ISOCH: received ACK, clearing pause\n");
                 ehci->isoch_pause = -1;
             }
+#else
+            if (ret == USB_RET_NAK) {
+                ret = 0;
+            }
+#endif
 
             if (ret >= 0) {
-                itd->transact[i] &= ~ITD_XACT_ACTIVE;
+                if (!dir) {
+                    /* OUT */
+                    set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
+                } else {
+                    /* IN */
+                    set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
+                }
 
                 if (itd->transact[i] & ITD_XACT_IOC) {
                     ehci_record_interrupt(ehci, USBSTS_INT);
                 }
             }
-
-            if (ret >= 0 && dir) {
-                cpu_physical_memory_rw(ptr, &ehci->ibuffer[0], len, 1);
-
-                if (ret != len) {
-                    DPRINTF("ISOCH IN expected %d, got %d\n",
-                            len, ret);
-                    set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
-                }
-            }
+            itd->transact[i] &= ~ITD_XACT_ACTIVE;
         }
     }
     return 0;
@@ -1400,7 +1537,8 @@ static int ehci_state_waitlisthead(EHCIState *ehci,  int async)
 
     /*  Find the head of the list (4.9.1.1) */
     for(i = 0; i < MAX_QH; i++) {
-        get_dwords(NLPTR_GET(entry), (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+        get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
+                   sizeof(EHCIqh) >> 2);
         ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
 
         if (qh.epchar & QH_EPCHAR_H) {
@@ -1437,17 +1575,6 @@ static int ehci_state_fetchentry(EHCIState *ehci, int async)
     int again = 0;
     uint32_t entry = ehci_get_fetch_addr(ehci, async);
 
-#if EHCI_DEBUG == 0
-    if (qemu_get_clock_ns(vm_clock) / 1000 >= ehci->frame_end_usec) {
-        if (async) {
-            DPRINTF("FETCHENTRY: FRAME timer elapsed, exit state machine\n");
-            goto out;
-        } else {
-            DPRINTF("FETCHENTRY: WARNING "
-                    "- frame timer elapsed during periodic\n");
-        }
-    }
-#endif
     if (entry < 0x1000) {
         DPRINTF("fetchentry: entry invalid (0x%08x)\n", entry);
         ehci_set_state(ehci, async, EST_ACTIVE);
@@ -1471,8 +1598,13 @@ static int ehci_state_fetchentry(EHCIState *ehci, int async)
         again = 1;
         break;
 
+    case NLPTR_TYPE_STITD:
+        ehci_set_state(ehci, async, EST_FETCHSITD);
+        again = 1;
+        break;
+
     default:
-        // TODO: handle siTD and FSTN types
+        /* TODO: handle FSTN type */
         fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
                 "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
         return -1;
@@ -1503,7 +1635,8 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
         goto out;
     }
 
-    get_dwords(NLPTR_GET(q->qhaddr), (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
+    get_dwords(ehci, NLPTR_GET(q->qhaddr),
+               (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2);
     ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh);
 
     if (q->async == EHCI_ASYNC_INFLIGHT) {
@@ -1572,7 +1705,7 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
     assert(!async);
     entry = ehci_get_fetch_addr(ehci, async);
 
-    get_dwords(NLPTR_GET(entry),(uint32_t *) &itd,
+    get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
                sizeof(EHCIitd) >> 2);
     ehci_trace_itd(ehci, entry, &itd);
 
@@ -1580,14 +1713,38 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
         return -1;
     }
 
-    put_dwords(NLPTR_GET(entry), (uint32_t *) &itd,
-                sizeof(EHCIitd) >> 2);
+    put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
+               sizeof(EHCIitd) >> 2);
     ehci_set_fetch_addr(ehci, async, itd.next);
     ehci_set_state(ehci, async, EST_FETCHENTRY);
 
     return 1;
 }
 
+static int ehci_state_fetchsitd(EHCIState *ehci, int async)
+{
+    uint32_t entry;
+    EHCIsitd sitd;
+
+    assert(!async);
+    entry = ehci_get_fetch_addr(ehci, async);
+
+    get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
+               sizeof(EHCIsitd) >> 2);
+    ehci_trace_sitd(ehci, entry, &sitd);
+
+    if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
+        /* siTD is not active, nothing to do */;
+    } else {
+        /* TODO: split transfers are not implemented */
+        fprintf(stderr, "WARNING: Skipping active siTD\n");
+    }
+
+    ehci_set_fetch_addr(ehci, async, sitd.next);
+    ehci_set_state(ehci, async, EST_FETCHENTRY);
+    return 1;
+}
+
 /* Section 4.10.2 - paragraph 3 */
 static int ehci_state_advqueue(EHCIQueue *q, int async)
 {
@@ -1634,7 +1791,8 @@ static int ehci_state_fetchqtd(EHCIQueue *q, int async)
 {
     int again = 0;
 
-    get_dwords(NLPTR_GET(q->qtdaddr),(uint32_t *) &q->qtd, sizeof(EHCIqtd) >> 2);
+    get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &q->qtd,
+               sizeof(EHCIqtd) >> 2);
     ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &q->qtd);
 
     if (q->qtd.token & QTD_TOKEN_ACTIVE) {
@@ -1677,7 +1835,7 @@ static void ehci_flush_qh(EHCIQueue *q)
     uint32_t dwords = sizeof(EHCIqh) >> 2;
     uint32_t addr = NLPTR_GET(q->qhaddr);
 
-    put_dwords(addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
+    put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
 }
 
 static int ehci_state_execute(EHCIQueue *q, int async)
@@ -1797,8 +1955,8 @@ static int ehci_state_writeback(EHCIQueue *q, int async)
 
     /*  Write back the QTD from the QH area */
     ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), (EHCIqtd*) &q->qh.next_qtd);
-    put_dwords(NLPTR_GET(q->qtdaddr),(uint32_t *) &q->qh.next_qtd,
-                sizeof(EHCIqtd) >> 2);
+    put_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &q->qh.next_qtd,
+               sizeof(EHCIqtd) >> 2);
 
     /*
      * EHCI specs say go horizontal here.
@@ -1863,6 +2021,10 @@ static void ehci_advance_state(EHCIState *ehci,
             again = ehci_state_fetchitd(ehci, async);
             break;
 
+        case EST_FETCHSITD:
+            again = ehci_state_fetchsitd(ehci, async);
+            break;
+
         case EST_ADVANCEQUEUE:
             again = ehci_state_advqueue(q, async);
             break;
@@ -1886,6 +2048,7 @@ static void ehci_advance_state(EHCIState *ehci,
             break;
 
         case EST_WRITEBACK:
+            assert(q != NULL);
             again = ehci_state_writeback(q, async);
             break;
 
@@ -1952,12 +2115,6 @@ static void ehci_advance_async_state(EHCIState *ehci)
         }
 
         ehci_set_state(ehci, async, EST_WAITLISTHEAD);
-        /* fall through */
-
-    case EST_FETCHENTRY:
-        /* fall through */
-
-    case EST_EXECUTING:
         ehci_advance_state(ehci, async);
         break;
 
@@ -2000,7 +2157,7 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
         }
         list |= ((ehci->frindex & 0x1ff8) >> 1);
 
-        cpu_physical_memory_rw(list, (uint8_t *) &entry, sizeof entry, 0);
+        pci_dma_read(&ehci->dev, list, &entry, sizeof entry);
         entry = le32_to_cpu(entry);
 
         DPRINTF("PERIODIC state adv fr=%d.  [%08X] -> %08X\n",
@@ -2010,11 +2167,6 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
         ehci_advance_state(ehci, async);
         break;
 
-    case EST_EXECUTING:
-        DPRINTF("PERIODIC state adv for executing\n");
-        ehci_advance_state(ehci, async);
-        break;
-
     default:
         /* this should only be due to a developer mistake */
         fprintf(stderr, "ehci: Bad periodic state %d. "
@@ -2027,23 +2179,16 @@ static void ehci_frame_timer(void *opaque)
 {
     EHCIState *ehci = opaque;
     int64_t expire_time, t_now;
-    int usec_elapsed;
+    uint64_t ns_elapsed;
     int frames;
-    int usec_now;
     int i;
     int skipped_frames = 0;
 
-
     t_now = qemu_get_clock_ns(vm_clock);
-    expire_time = t_now + (get_ticks_per_sec() / FRAME_TIMER_FREQ);
-    if (expire_time == t_now) {
-        expire_time++;
-    }
+    expire_time = t_now + (get_ticks_per_sec() / ehci->freq);
 
-    usec_now = t_now / 1000;
-    usec_elapsed = usec_now - ehci->last_run_usec;
-    frames = usec_elapsed / FRAME_TIMER_USEC;
-    ehci->frame_end_usec = usec_now + FRAME_TIMER_USEC - 10;
+    ns_elapsed = t_now - ehci->last_run_ns;
+    frames = ns_elapsed / FRAME_TIMER_NS;
 
     for (i = 0; i < frames; i++) {
         if ( !(ehci->usbsts & USBSTS_HALT)) {
@@ -2060,17 +2205,13 @@ static void ehci_frame_timer(void *opaque)
             ehci->sofv &= 0x000003ff;
         }
 
-        if (frames - i > 10) {
+        if (frames - i > ehci->maxframes) {
             skipped_frames++;
         } else {
-            // TODO could this cause periodic frames to get skipped if async
-            // active?
-            if (ehci_get_state(ehci, 1) != EST_EXECUTING) {
-                ehci_advance_periodic_state(ehci);
-            }
+            ehci_advance_periodic_state(ehci);
         }
 
-        ehci->last_run_usec += FRAME_TIMER_USEC;
+        ehci->last_run_ns += FRAME_TIMER_NS;
     }
 
 #if 0
@@ -2082,59 +2223,69 @@ static void ehci_frame_timer(void *opaque)
     /*  Async is not inside loop since it executes everything it can once
      *  called
      */
-    if (ehci_get_state(ehci, 0) != EST_EXECUTING) {
-        ehci_advance_async_state(ehci);
-    }
+    ehci_advance_async_state(ehci);
 
     qemu_mod_timer(ehci->frame_timer, expire_time);
 }
 
-static CPUReadMemoryFunc *ehci_readfn[3]={
-    ehci_mem_readb,
-    ehci_mem_readw,
-    ehci_mem_readl
-};
 
-static CPUWriteMemoryFunc *ehci_writefn[3]={
-    ehci_mem_writeb,
-    ehci_mem_writew,
-    ehci_mem_writel
+static const MemoryRegionOps ehci_mem_ops = {
+    .old_mmio = {
+        .read = { ehci_mem_readb, ehci_mem_readw, ehci_mem_readl },
+        .write = { ehci_mem_writeb, ehci_mem_writew, ehci_mem_writel },
+    },
+    .endianness = DEVICE_LITTLE_ENDIAN,
 };
 
-static void ehci_map(PCIDevice *pci_dev, int region_num,
-                     pcibus_t addr, pcibus_t size, int type)
-{
-    EHCIState *s =(EHCIState *)pci_dev;
-
-    DPRINTF("ehci_map: region %d, addr %08" PRIx64 ", size %" PRId64 ", s->mem %08X\n",
-            region_num, addr, size, s->mem);
-    s->mem_base = addr;
-    cpu_register_physical_memory(addr, size, s->mem);
-}
-
-static void ehci_device_destroy(USBBus *bus, USBDevice *dev)
-{
-    EHCIState *s = container_of(bus, EHCIState, bus);
-
-    ehci_queues_rip_device(s, dev);
-}
-
 static int usb_ehci_initfn(PCIDevice *dev);
 
 static USBPortOps ehci_port_ops = {
     .attach = ehci_attach,
     .detach = ehci_detach,
+    .child_detach = ehci_child_detach,
+    .wakeup = ehci_wakeup,
     .complete = ehci_async_complete_packet,
 };
 
 static USBBusOps ehci_bus_ops = {
-    .device_destroy = ehci_device_destroy,
+    .register_companion = ehci_register_companion,
 };
 
-static PCIDeviceInfo ehci_info = {
-    .qdev.name    = "usb-ehci",
-    .qdev.size    = sizeof(EHCIState),
-    .init         = usb_ehci_initfn,
+static const VMStateDescription vmstate_ehci = {
+    .name = "ehci",
+    .unmigratable = 1,
+};
+
+static Property ehci_properties[] = {
+    DEFINE_PROP_UINT32("freq",      EHCIState, freq, FRAME_TIMER_FREQ),
+    DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static PCIDeviceInfo ehci_info[] = {
+    {
+        .qdev.name    = "usb-ehci",
+        .qdev.size    = sizeof(EHCIState),
+        .qdev.vmsd    = &vmstate_ehci,
+        .init         = usb_ehci_initfn,
+        .vendor_id    = PCI_VENDOR_ID_INTEL,
+        .device_id    = PCI_DEVICE_ID_INTEL_82801D, /* ich4 */
+        .revision     = 0x10,
+        .class_id     = PCI_CLASS_SERIAL_USB,
+        .qdev.props   = ehci_properties,
+    },{
+        .qdev.name    = "ich9-usb-ehci1",
+        .qdev.size    = sizeof(EHCIState),
+        .qdev.vmsd    = &vmstate_ehci,
+        .init         = usb_ehci_initfn,
+        .vendor_id    = PCI_VENDOR_ID_INTEL,
+        .device_id    = PCI_DEVICE_ID_INTEL_82801I_EHCI1,
+        .revision     = 0x03,
+        .class_id     = PCI_CLASS_SERIAL_USB,
+        .qdev.props   = ehci_properties,
+    },{
+        /* end of list */
+    }
 };
 
 static int usb_ehci_initfn(PCIDevice *dev)
@@ -2143,24 +2294,19 @@ static int usb_ehci_initfn(PCIDevice *dev)
     uint8_t *pci_conf = s->dev.config;
     int i;
 
-    pci_config_set_vendor_id(pci_conf, PCI_VENDOR_ID_INTEL);
-    pci_config_set_device_id(pci_conf, PCI_DEVICE_ID_INTEL_82801D);
-    pci_set_byte(&pci_conf[PCI_REVISION_ID], 0x10);
     pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20);
-    pci_config_set_class(pci_conf, PCI_CLASS_SERIAL_USB);
-    pci_set_byte(&pci_conf[PCI_HEADER_TYPE], PCI_HEADER_TYPE_NORMAL);
 
     /* capabilities pointer */
     pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00);
     //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50);
 
-    pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); // interrupt pin 3
+    pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */
     pci_set_byte(&pci_conf[PCI_MIN_GNT], 0);
     pci_set_byte(&pci_conf[PCI_MAX_LAT], 0);
 
     // pci_conf[0x50] = 0x01; // power management caps
 
-    pci_set_byte(&pci_conf[0x60], 0x20);  // spec release number (2.1.4)
+    pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4)
     pci_set_byte(&pci_conf[0x61], 0x20);  // frame length adjustment (2.1.5)
     pci_set_word(&pci_conf[0x62], 0x00);  // port wake up capability (2.1.6)
 
@@ -2197,7 +2343,6 @@ static int usb_ehci_initfn(PCIDevice *dev)
     for(i = 0; i < NB_PORTS; i++) {
         usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
                           USB_SPEED_MASK_HIGH);
-        usb_port_location(&s->ports[i], NULL, i+1);
         s->ports[i].dev = 0;
     }
 
@@ -2206,11 +2351,8 @@ static int usb_ehci_initfn(PCIDevice *dev)
 
     qemu_register_reset(ehci_reset, s);
 
-    s->mem = cpu_register_io_memory(ehci_readfn, ehci_writefn, s,
-                                    DEVICE_LITTLE_ENDIAN);
-
-    pci_register_bar(&s->dev, 0, MMIO_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY,
-                                                            ehci_map);
+    memory_region_init_io(&s->mem, &ehci_mem_ops, s, "ehci", MMIO_SIZE);
+    pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
 
     fprintf(stderr, "*** EHCI support is under development ***\n");
 
@@ -2219,7 +2361,7 @@ static int usb_ehci_initfn(PCIDevice *dev)
 
 static void ehci_register(void)
 {
-    pci_qdev_register(&ehci_info);
+    pci_qdev_register_many(ehci_info);
 }
 device_init(ehci_register);