diff mbox

[18/18] usb: add ehci adapter

Message ID 1305575782-31766-19-git-send-email-kraxel@redhat.com
State New
Headers show

Commit Message

Gerd Hoffmann May 16, 2011, 7:56 p.m. UTC
This patch finally merges the EHCI host adapter aka USB 2.0 support.

Based on git://git.kiszka.org/qemu.git ehci

Changes:
  - Adapt to recent changes in the usb subsystem.
  - Don't create device automagically, use -device instead.
  - Add quickstart text file, see docs/usb2.txt.
  - A bunch of codestyle fixups.
  - Add authors+contributers list.
  - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
    wakeup rate anyway.
  - A few bug fixes.

Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 Makefile.objs           |    1 +
 default-configs/pci.mak |    1 +
 docs/usb2.txt           |   38 +
 hw/pci_ids.h            |    1 +
 hw/usb-ehci.c           | 2057 +++++++++++++++++++++++++++++++++++++++++++++++
 hw/usb-ehci.h           |    8 +
 6 files changed, 2106 insertions(+), 0 deletions(-)
 create mode 100644 docs/usb2.txt
 create mode 100644 hw/usb-ehci.c
 create mode 100644 hw/usb-ehci.h

Comments

David S. Ahern May 17, 2011, 2:47 a.m. UTC | #1
On 05/16/11 13:56, Gerd Hoffmann wrote:
> This patch finally merges the EHCI host adapter aka USB 2.0 support.
> 
> Based on git://git.kiszka.org/qemu.git ehci
> 
> Changes:
>   - Adapt to recent changes in the usb subsystem.
>   - Don't create device automagically, use -device instead.
>   - Add quickstart text file, see docs/usb2.txt.
>   - A bunch of codestyle fixups.
>   - Add authors+contributers list.
>   - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>     wakeup rate anyway.
>   - A few bug fixes.
> 
> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>

As someone who spent a significant amount of time working on the EHCI
code last year I am absolutely not ok with this. The entire contribution
history for EHCI lost - and for no reason. The inclusion of EHCI into
qemu can be done in such a way as to maintain the history.

David


> ---
>  Makefile.objs           |    1 +
>  default-configs/pci.mak |    1 +
>  docs/usb2.txt           |   38 +
>  hw/pci_ids.h            |    1 +
>  hw/usb-ehci.c           | 2057 +++++++++++++++++++++++++++++++++++++++++++++++
>  hw/usb-ehci.h           |    8 +
>  6 files changed, 2106 insertions(+), 0 deletions(-)
>  create mode 100644 docs/usb2.txt
>  create mode 100644 hw/usb-ehci.c
>  create mode 100644 hw/usb-ehci.h
> 
> diff --git a/Makefile.objs b/Makefile.objs
> index 4478c61..90838f6 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -193,6 +193,7 @@ hw-obj-$(CONFIG_PCSPK) += pcspk.o
>  hw-obj-$(CONFIG_PCKBD) += pckbd.o
>  hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o
>  hw-obj-$(CONFIG_USB_OHCI) += usb-ohci.o
> +hw-obj-$(CONFIG_USB_EHCI) += usb-ehci.o
>  hw-obj-$(CONFIG_FDC) += fdc.o
>  hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o
>  hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o
> diff --git a/default-configs/pci.mak b/default-configs/pci.mak
> index 0471efb..22bd350 100644
> --- a/default-configs/pci.mak
> +++ b/default-configs/pci.mak
> @@ -3,6 +3,7 @@ CONFIG_VIRTIO_PCI=y
>  CONFIG_VIRTIO=y
>  CONFIG_USB_UHCI=y
>  CONFIG_USB_OHCI=y
> +CONFIG_USB_EHCI=y
>  CONFIG_NE2000_PCI=y
>  CONFIG_EEPRO100_PCI=y
>  CONFIG_PCNET_PCI=y
> diff --git a/docs/usb2.txt b/docs/usb2.txt
> new file mode 100644
> index 0000000..b283c13
> --- /dev/null
> +++ b/docs/usb2.txt
> @@ -0,0 +1,38 @@
> +
> +USB 2.0 Quick Start
> +===================
> +
> +The QEMU EHCI Adapter does *not* support companion controllers.  That
> +implies there are two completely separate USB busses: One USB 1.1 bus
> +driven by the UHCI controller and one USB 2.0 bus driven by the EHCI
> +controller.  Devices must be attached to the correct controller
> +manually.
> +
> +The '-usb' switch will make qemu create the UHCI controller as part of
> +the PIIX3 chipset.  The USB 1.1 bus will carry the name "usb.0".
> +
> +You can use the standard -device switch to add a EHCI controller to
> +your virtual machine.  It is strongly recommended to specify an ID for
> +the controller so the USB 2.0 bus gets a individual name, for example
> +'-device usb-ehci,id=ehci".  This will give you a USB 2.0 bus named
> +"ehci.0".
> +
> +I strongly recomment to also use -device to attach usb devices because
> +you can specify the bus they should be attached to this way.  Here is
> +a complete example:
> +
> +    qemu -M pc ${otheroptions}                           \
> +        -drive if=none,id=usbstick,file=/path/to/image   \
> +        -usb                                             \
> +        -device usb-ehci,id=ehci                         \
> +        -device usb-tablet,bus=usb.0                     \
> +        -device usb-storage,bus=ehci.0,drive=usbstick
> +
> +This attaches a usb tablet to the UHCI adapter and a usb mass storage
> +device to the EHCI adapter.
> +
> +enjoy,
> +  Gerd
> +
> +--
> +Gerd Hoffmann <kraxel@redhat.com>
> diff --git a/hw/pci_ids.h b/hw/pci_ids.h
> index ea3418c..d9457ed 100644
> --- a/hw/pci_ids.h
> +++ b/hw/pci_ids.h
> @@ -100,6 +100,7 @@
>  #define PCI_VENDOR_ID_INTEL              0x8086
>  #define PCI_DEVICE_ID_INTEL_82441        0x1237
>  #define PCI_DEVICE_ID_INTEL_82801AA_5    0x2415
> +#define PCI_DEVICE_ID_INTEL_82801D       0x24CD
>  #define PCI_DEVICE_ID_INTEL_ESB_9        0x25ab
>  #define PCI_DEVICE_ID_INTEL_82371SB_0    0x7000
>  #define PCI_DEVICE_ID_INTEL_82371SB_1    0x7010
> diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
> new file mode 100644
> index 0000000..fbd818f
> --- /dev/null
> +++ b/hw/usb-ehci.c
> @@ -0,0 +1,2057 @@
> +/*
> + * QEMU USB EHCI Emulation
> + *
> + * Copyright(c) 2008  Emutex Ltd. (address@hidden)
> + *
> + * Authors & Contributors:
> + *    Mark Burkley
> + *    Niels de Vos
> + *    David S. Ahern
> + *    Kevin Wolf
> + *    Jan Kiszka
> + *    Vincent Palatin
> + *    Gerd Hoffmann
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or(at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, write to the Free Software
> + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
> + *
> + * TODO:
> + *  o Downstream port handoff
> + */
> +
> +#include "hw.h"
> +#include "qemu-timer.h"
> +#include "usb.h"
> +#include "pci.h"
> +#include "usb-ehci.h"
> +#include "monitor.h"
> +
> +#define EHCI_DEBUG   0
> +#define STATE_DEBUG  0       /* state transitions  */
> +
> +#if EHCI_DEBUG || STATE_DEBUG
> +#define DPRINTF printf
> +#else
> +#define DPRINTF(...)
> +#endif
> +
> +#if STATE_DEBUG
> +#define DPRINTF_ST DPRINTF
> +#else
> +#define DPRINTF_ST(...)
> +#endif
> +
> +/* internal processing - reset HC to try and recover */
> +#define USB_RET_PROCERR   (-99)
> +
> +#define MMIO_SIZE        0x1000
> +
> +/* Capability Registers Base Address - section 2.2 */
> +#define CAPREGBASE       0x0000
> +#define CAPLENGTH        CAPREGBASE + 0x0000  // 1-byte, 0x0001 reserved
> +#define HCIVERSION       CAPREGBASE + 0x0002  // 2-bytes, i/f version #
> +#define HCSPARAMS        CAPREGBASE + 0x0004  // 4-bytes, structural params
> +#define HCCPARAMS        CAPREGBASE + 0x0008  // 4-bytes, capability params
> +#define EECP             HCCPARAMS + 1
> +#define HCSPPORTROUTE1   CAPREGBASE + 0x000c
> +#define HCSPPORTROUTE2   CAPREGBASE + 0x0010
> +
> +#define OPREGBASE        0x0020        // Operational Registers Base Address
> +
> +#define USBCMD           OPREGBASE + 0x0000
> +#define USBCMD_RUNSTOP   (1 << 0)      // run / Stop
> +#define USBCMD_HCRESET   (1 << 1)      // HC Reset
> +#define USBCMD_FLS       (3 << 2)      // Frame List Size
> +#define USBCMD_FLS_SH    2             // Frame List Size Shift
> +#define USBCMD_PSE       (1 << 4)      // Periodic Schedule Enable
> +#define USBCMD_ASE       (1 << 5)      // Asynch Schedule Enable
> +#define USBCMD_IAAD      (1 << 6)      // Int Asynch Advance Doorbell
> +#define USBCMD_LHCR      (1 << 7)      // Light Host Controller Reset
> +#define USBCMD_ASPMC     (3 << 8)      // Async Sched Park Mode Count
> +#define USBCMD_ASPME     (1 << 11)     // Async Sched Park Mode Enable
> +#define USBCMD_ITC       (0x7f << 16)  // Int Threshold Control
> +#define USBCMD_ITC_SH    16            // Int Threshold Control Shift
> +
> +#define USBSTS           OPREGBASE + 0x0004
> +#define USBSTS_RO_MASK   0x0000003f
> +#define USBSTS_INT       (1 << 0)      // USB Interrupt
> +#define USBSTS_ERRINT    (1 << 1)      // Error Interrupt
> +#define USBSTS_PCD       (1 << 2)      // Port Change Detect
> +#define USBSTS_FLR       (1 << 3)      // Frame List Rollover
> +#define USBSTS_HSE       (1 << 4)      // Host System Error
> +#define USBSTS_IAA       (1 << 5)      // Interrupt on Async Advance
> +#define USBSTS_HALT      (1 << 12)     // HC Halted
> +#define USBSTS_REC       (1 << 13)     // Reclamation
> +#define USBSTS_PSS       (1 << 14)     // Periodic Schedule Status
> +#define USBSTS_ASS       (1 << 15)     // Asynchronous Schedule Status
> +
> +/*
> + *  Interrupt enable bits correspond to the interrupt active bits in USBSTS
> + *  so no need to redefine here.
> + */
> +#define USBINTR              OPREGBASE + 0x0008
> +#define USBINTR_MASK         0x0000003f
> +
> +#define FRINDEX              OPREGBASE + 0x000c
> +#define CTRLDSSEGMENT        OPREGBASE + 0x0010
> +#define PERIODICLISTBASE     OPREGBASE + 0x0014
> +#define ASYNCLISTADDR        OPREGBASE + 0x0018
> +#define ASYNCLISTADDR_MASK   0xffffffe0
> +
> +#define CONFIGFLAG           OPREGBASE + 0x0040
> +
> +#define PORTSC               (OPREGBASE + 0x0044)
> +#define PORTSC_BEGIN         PORTSC
> +#define PORTSC_END           (PORTSC + 4 * NB_PORTS)
> +/*
> + * Bits that are reserverd or are read-only are masked out of values
> + * written to us by software
> + */
> +#define PORTSC_RO_MASK       0x007021c5
> +#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_WKCN_E        (1 << 20)    // Wake on Connect Enable
> +#define PORTSC_PTC           (15 << 16)   // Port Test Control
> +#define PORTSC_PTC_SH        16           // Port Test Control shift
> +#define PORTSC_PIC           (3 << 14)    // Port Indicator Control
> +#define PORTSC_PIC_SH        14           // Port Indicator Control Shift
> +#define PORTSC_POWNER        (1 << 13)    // Port Owner
> +#define PORTSC_PPOWER        (1 << 12)    // Port Power
> +#define PORTSC_LINESTAT      (3 << 10)    // Port Line Status
> +#define PORTSC_LINESTAT_SH   10           // Port Line Status Shift
> +#define PORTSC_PRESET        (1 << 8)     // Port Reset
> +#define PORTSC_SUSPEND       (1 << 7)     // Port Suspend
> +#define PORTSC_FPRES         (1 << 6)     // Force Port Resume
> +#define PORTSC_OCC           (1 << 5)     // Over Current Change
> +#define PORTSC_OCA           (1 << 4)     // Over Current Active
> +#define PORTSC_PEDC          (1 << 3)     // Port Enable/Disable Change
> +#define PORTSC_PED           (1 << 2)     // Port Enable/Disable
> +#define PORTSC_CSC           (1 << 1)     // Connect Status Change
> +#define PORTSC_CONNECT       (1 << 0)     // Current Connect Status
> +
> +#define FRAME_TIMER_FREQ 1000
> +#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
> +
> +#define NB_MAXINTRATE    8        // Max rate at which controller issues ints
> +#define NB_PORTS         4        // 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
> +
> +/*  Internal periodic / asynchronous schedule state machine states
> + */
> +typedef enum {
> +    EST_INACTIVE = 1000,
> +    EST_ACTIVE,
> +    EST_EXECUTING,
> +    EST_SLEEPING,
> +    /*  The following states are internal to the state machine function
> +    */
> +    EST_WAITLISTHEAD,
> +    EST_FETCHENTRY,
> +    EST_FETCHQH,
> +    EST_FETCHITD,
> +    EST_ADVANCEQUEUE,
> +    EST_FETCHQTD,
> +    EST_EXECUTE,
> +    EST_WRITEBACK,
> +    EST_HORIZONTALQH
> +} EHCI_STATES;
> +
> +/* macros for accessing fields within next link pointer entry */
> +#define NLPTR_GET(x)             ((x) & 0xffffffe0)
> +#define NLPTR_TYPE_GET(x)        (((x) >> 1) & 3)
> +#define NLPTR_TBIT(x)            ((x) & 1)  // 1=invalid, 0=valid
> +
> +/* link pointer types */
> +#define NLPTR_TYPE_ITD           0     // isoc xfer descriptor
> +#define NLPTR_TYPE_QH            1     // queue head
> +#define NLPTR_TYPE_STITD         2     // split xaction, isoc xfer descriptor
> +#define NLPTR_TYPE_FSTN          3     // frame span traversal node
> +
> +
> +/*  EHCI spec version 1.0 Section 3.3
> + */
> +typedef struct EHCIitd {
> +    uint32_t next;
> +
> +    uint32_t transact[8];
> +#define ITD_XACT_ACTIVE          (1 << 31)
> +#define ITD_XACT_DBERROR         (1 << 30)
> +#define ITD_XACT_BABBLE          (1 << 29)
> +#define ITD_XACT_XACTERR         (1 << 28)
> +#define ITD_XACT_LENGTH_MASK     0x0fff0000
> +#define ITD_XACT_LENGTH_SH       16
> +#define ITD_XACT_IOC             (1 << 15)
> +#define ITD_XACT_PGSEL_MASK      0x00007000
> +#define ITD_XACT_PGSEL_SH        12
> +#define ITD_XACT_OFFSET_MASK     0x00000fff
> +
> +    uint32_t bufptr[7];
> +#define ITD_BUFPTR_MASK          0xfffff000
> +#define ITD_BUFPTR_SH            12
> +#define ITD_BUFPTR_EP_MASK       0x00000f00
> +#define ITD_BUFPTR_EP_SH         8
> +#define ITD_BUFPTR_DEVADDR_MASK  0x0000007f
> +#define ITD_BUFPTR_DEVADDR_SH    0
> +#define ITD_BUFPTR_DIRECTION     (1 << 11)
> +#define ITD_BUFPTR_MAXPKT_MASK   0x000007ff
> +#define ITD_BUFPTR_MAXPKT_SH     0
> +#define ITD_BUFPTR_MULT_MASK     0x00000003
> +} EHCIitd;
> +
> +/*  EHCI spec version 1.0 Section 3.4
> + */
> +typedef struct EHCIsitd {
> +    uint32_t next;                  // Standard next link pointer
> +    uint32_t epchar;
> +#define SITD_EPCHAR_IO              (1 << 31)
> +#define SITD_EPCHAR_PORTNUM_MASK    0x7f000000
> +#define SITD_EPCHAR_PORTNUM_SH      24
> +#define SITD_EPCHAR_HUBADD_MASK     0x007f0000
> +#define SITD_EPCHAR_HUBADDR_SH      16
> +#define SITD_EPCHAR_EPNUM_MASK      0x00000f00
> +#define SITD_EPCHAR_EPNUM_SH        8
> +#define SITD_EPCHAR_DEVADDR_MASK    0x0000007f
> +
> +    uint32_t uframe;
> +#define SITD_UFRAME_CMASK_MASK      0x0000ff00
> +#define SITD_UFRAME_CMASK_SH        8
> +#define SITD_UFRAME_SMASK_MASK      0x000000ff
> +
> +    uint32_t results;
> +#define SITD_RESULTS_IOC              (1 << 31)
> +#define SITD_RESULTS_PGSEL            (1 << 30)
> +#define SITD_RESULTS_TBYTES_MASK      0x03ff0000
> +#define SITD_RESULTS_TYBYTES_SH       16
> +#define SITD_RESULTS_CPROGMASK_MASK   0x0000ff00
> +#define SITD_RESULTS_CPROGMASK_SH     8
> +#define SITD_RESULTS_ACTIVE           (1 << 7)
> +#define SITD_RESULTS_ERR              (1 << 6)
> +#define SITD_RESULTS_DBERR            (1 << 5)
> +#define SITD_RESULTS_BABBLE           (1 << 4)
> +#define SITD_RESULTS_XACTERR          (1 << 3)
> +#define SITD_RESULTS_MISSEDUF         (1 << 2)
> +#define SITD_RESULTS_SPLITXSTATE      (1 << 1)
> +
> +    uint32_t bufptr[2];
> +#define SITD_BUFPTR_MASK              0xfffff000
> +#define SITD_BUFPTR_CURROFF_MASK      0x00000fff
> +#define SITD_BUFPTR_TPOS_MASK         0x00000018
> +#define SITD_BUFPTR_TPOS_SH           3
> +#define SITD_BUFPTR_TCNT_MASK         0x00000007
> +
> +    uint32_t backptr;                 // Standard next link pointer
> +} EHCIsitd;
> +
> +/*  EHCI spec version 1.0 Section 3.5
> + */
> +typedef struct EHCIqtd {
> +    uint32_t next;                    // Standard next link pointer
> +    uint32_t altnext;                 // Standard next link pointer
> +    uint32_t token;
> +#define QTD_TOKEN_DTOGGLE             (1 << 31)
> +#define QTD_TOKEN_TBYTES_MASK         0x7fff0000
> +#define QTD_TOKEN_TBYTES_SH           16
> +#define QTD_TOKEN_IOC                 (1 << 15)
> +#define QTD_TOKEN_CPAGE_MASK          0x00007000
> +#define QTD_TOKEN_CPAGE_SH            12
> +#define QTD_TOKEN_CERR_MASK           0x00000c00
> +#define QTD_TOKEN_CERR_SH             10
> +#define QTD_TOKEN_PID_MASK            0x00000300
> +#define QTD_TOKEN_PID_SH              8
> +#define QTD_TOKEN_ACTIVE              (1 << 7)
> +#define QTD_TOKEN_HALT                (1 << 6)
> +#define QTD_TOKEN_DBERR               (1 << 5)
> +#define QTD_TOKEN_BABBLE              (1 << 4)
> +#define QTD_TOKEN_XACTERR             (1 << 3)
> +#define QTD_TOKEN_MISSEDUF            (1 << 2)
> +#define QTD_TOKEN_SPLITXSTATE         (1 << 1)
> +#define QTD_TOKEN_PING                (1 << 0)
> +
> +    uint32_t bufptr[5];               // Standard buffer pointer
> +#define QTD_BUFPTR_MASK               0xfffff000
> +} EHCIqtd;
> +
> +/*  EHCI spec version 1.0 Section 3.6
> + */
> +typedef struct EHCIqh {
> +    uint32_t next;                    // Standard next link pointer
> +
> +    /* endpoint characteristics */
> +    uint32_t epchar;
> +#define QH_EPCHAR_RL_MASK             0xf0000000
> +#define QH_EPCHAR_RL_SH               28
> +#define QH_EPCHAR_C                   (1 << 27)
> +#define QH_EPCHAR_MPLEN_MASK          0x07FF0000
> +#define QH_EPCHAR_MPLEN_SH            16
> +#define QH_EPCHAR_H                   (1 << 15)
> +#define QH_EPCHAR_DTC                 (1 << 14)
> +#define QH_EPCHAR_EPS_MASK            0x00003000
> +#define QH_EPCHAR_EPS_SH              12
> +#define EHCI_QH_EPS_FULL              0
> +#define EHCI_QH_EPS_LOW               1
> +#define EHCI_QH_EPS_HIGH              2
> +#define EHCI_QH_EPS_RESERVED          3
> +
> +#define QH_EPCHAR_EP_MASK             0x00000f00
> +#define QH_EPCHAR_EP_SH               8
> +#define QH_EPCHAR_I                   (1 << 7)
> +#define QH_EPCHAR_DEVADDR_MASK        0x0000007f
> +#define QH_EPCHAR_DEVADDR_SH          0
> +
> +    /* endpoint capabilities */
> +    uint32_t epcap;
> +#define QH_EPCAP_MULT_MASK            0xc0000000
> +#define QH_EPCAP_MULT_SH              30
> +#define QH_EPCAP_PORTNUM_MASK         0x3f800000
> +#define QH_EPCAP_PORTNUM_SH           23
> +#define QH_EPCAP_HUBADDR_MASK         0x007f0000
> +#define QH_EPCAP_HUBADDR_SH           16
> +#define QH_EPCAP_CMASK_MASK           0x0000ff00
> +#define QH_EPCAP_CMASK_SH             8
> +#define QH_EPCAP_SMASK_MASK           0x000000ff
> +#define QH_EPCAP_SMASK_SH             0
> +
> +    uint32_t current_qtd;             // Standard next link pointer
> +    uint32_t next_qtd;                // Standard next link pointer
> +    uint32_t altnext_qtd;
> +#define QH_ALTNEXT_NAKCNT_MASK        0x0000001e
> +#define QH_ALTNEXT_NAKCNT_SH          1
> +
> +    uint32_t token;                   // Same as QTD token
> +    uint32_t bufptr[5];               // Standard buffer pointer
> +#define BUFPTR_CPROGMASK_MASK         0x000000ff
> +#define BUFPTR_FRAMETAG_MASK          0x0000001f
> +#define BUFPTR_SBYTES_MASK            0x00000fe0
> +#define BUFPTR_SBYTES_SH              5
> +} EHCIqh;
> +
> +/*  EHCI spec version 1.0 Section 3.7
> + */
> +typedef struct EHCIfstn {
> +    uint32_t next;                    // Standard next link pointer
> +    uint32_t backptr;                 // Standard next link pointer
> +} EHCIfstn;
> +
> +typedef struct {
> +    PCIDevice dev;
> +    qemu_irq irq;
> +    target_phys_addr_t mem_base;
> +    int mem;
> +    int num_ports;
> +    /*
> +     *  EHCI spec version 1.0 Section 2.3
> +     *  Host Controller Operational Registers
> +     */
> +    union {
> +        uint8_t mmio[MMIO_SIZE];
> +        struct {
> +            uint8_t cap[OPREGBASE];
> +            uint32_t usbcmd;
> +            uint32_t usbsts;
> +            uint32_t usbintr;
> +            uint32_t frindex;
> +            uint32_t ctrldssegment;
> +            uint32_t periodiclistbase;
> +            uint32_t asynclistaddr;
> +            uint32_t notused[9];
> +            uint32_t configflag;
> +            uint32_t portsc[NB_PORTS];
> +        };
> +    };
> +    /*
> +     *  Internal states, shadow registers, etc
> +     */
> +    uint32_t sofv;
> +    QEMUTimer *frame_timer;
> +    int attach_poll_counter;
> +    int astate;                        // Current state in asynchronous schedule
> +    int pstate;                        // Current state in periodic schedule
> +    USBPort ports[NB_PORTS];
> +    uint8_t buffer[BUFF_SIZE];
> +    uint32_t usbsts_pending;
> +
> +    /* cached data from guest - needs to be flushed
> +     * when guest removes an entry (doorbell, handshake sequence)
> +     */
> +    EHCIqh qh;             // copy of current QH (being worked on)
> +    uint32_t qhaddr;       // address QH read from
> +
> +    EHCIqtd qtd;           // copy of current QTD (being worked on)
> +    uint32_t qtdaddr;      // address QTD read from
> +
> +    uint32_t itdaddr;      // current ITD
> +
> +    uint32_t fetch_addr;   // which address to look at next
> +
> +    USBBus bus;
> +    USBPacket usb_packet;
> +    int async_complete;
> +    uint32_t tbytes;
> +    int pid;
> +    int exec_status;
> +    int isoch_pause;
> +    uint32_t last_run_usec;
> +    uint32_t frame_end_usec;
> +} EHCIState;
> +
> +#define SET_LAST_RUN_CLOCK(s) \
> +    (s)->last_run_usec = qemu_get_clock_ns(vm_clock) / 1000;
> +
> +/* nifty macros from Arnon's EHCI version  */
> +#define get_field(data, field) \
> +    (((data) & field##_MASK) >> field##_SH)
> +
> +#define set_field(data, newval, field) do { \
> +    uint32_t val = *data; \
> +    val &= ~ field##_MASK; \
> +    val |= ((newval) << field##_SH) & field##_MASK; \
> +    *data = val; \
> +    } while(0)
> +
> +
> +#if EHCI_DEBUG
> +static const char *addr2str(unsigned addr)
> +{
> +    const char *r            = "   unknown";
> +    const char *n[] = {
> +        [ CAPLENGTH ]        = " CAPLENGTH",
> +        [ HCIVERSION ]       = "HCIVERSION",
> +        [ HCSPARAMS ]        = " HCSPARAMS",
> +        [ HCCPARAMS ]        = " HCCPARAMS",
> +        [ USBCMD ]           = "   COMMAND",
> +        [ USBSTS ]           = "    STATUS",
> +        [ USBINTR ]          = " INTERRUPT",
> +        [ FRINDEX ]          = " FRAME IDX",
> +        [ PERIODICLISTBASE ] = "P-LIST BASE",
> +        [ ASYNCLISTADDR ]    = "A-LIST ADDR",
> +        [ PORTSC_BEGIN ...
> +          PORTSC_END ]       = "PORT STATUS",
> +        [ CONFIGFLAG ]       = "CONFIG FLAG",
> +    };
> +
> +    if (addr < ARRAY_SIZE(n) && n[addr] != NULL) {
> +        return n[addr];
> +    } else {
> +        return r;
> +    }
> +}
> +#endif
> +
> +
> +static inline void ehci_set_interrupt(EHCIState *s, int intr)
> +{
> +    int level = 0;
> +
> +    // TODO honour interrupt threshold requests
> +
> +    s->usbsts |= intr;
> +
> +    if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
> +        level = 1;
> +    }
> +
> +    qemu_set_irq(s->irq, level);
> +}
> +
> +static inline void ehci_record_interrupt(EHCIState *s, int intr)
> +{
> +    s->usbsts_pending |= intr;
> +}
> +
> +static inline void ehci_commit_interrupt(EHCIState *s)
> +{
> +    if (!s->usbsts_pending) {
> +        return;
> +    }
> +    ehci_set_interrupt(s, s->usbsts_pending);
> +    s->usbsts_pending = 0;
> +}
> +
> +/* Attach or detach a device on root hub */
> +
> +static void ehci_attach(USBPort *port)
> +{
> +    EHCIState *s = port->opaque;
> +    uint32_t *portsc = &s->portsc[port->index];
> +
> +    DPRINTF("ehci_attach invoked for index %d, portsc 0x%x, desc %s\n",
> +           port->index, *portsc, port->dev->product_desc);
> +
> +    *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);
> +    }
> +}
> +
> +static void ehci_detach(USBPort *port)
> +{
> +    EHCIState *s = port->opaque;
> +    uint32_t *portsc = &s->portsc[port->index];
> +
> +    DPRINTF("ehci_attach invoked for index %d, portsc 0x%x\n",
> +           port->index, *portsc);
> +
> +    *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);
> +    }
> +}
> +
> +/* 4.1 host controller initialization */
> +static void ehci_reset(void *opaque)
> +{
> +    EHCIState *s = opaque;
> +    uint8_t *pci_conf;
> +    int i;
> +
> +    pci_conf = s->dev.config;
> +
> +    memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
> +
> +    s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
> +    s->usbsts = USBSTS_HALT;
> +
> +    s->astate = EST_INACTIVE;
> +    s->pstate = EST_INACTIVE;
> +    s->async_complete = 0;
> +    s->isoch_pause = -1;
> +    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);
> +        }
> +    }
> +}
> +
> +static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
> +{
> +    EHCIState *s = ptr;
> +    uint32_t val;
> +
> +    val = s->mmio[addr];
> +
> +    return val;
> +}
> +
> +static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
> +{
> +    EHCIState *s = ptr;
> +    uint32_t val;
> +
> +    val = s->mmio[addr] | (s->mmio[addr+1] << 8);
> +
> +    return val;
> +}
> +
> +static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
> +{
> +    EHCIState *s = ptr;
> +    uint32_t val;
> +
> +    val = s->mmio[addr] | (s->mmio[addr+1] << 8) |
> +          (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24);
> +
> +    return val;
> +}
> +
> +static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val)
> +{
> +    fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n");
> +    exit(1);
> +}
> +
> +static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val)
> +{
> +    fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n");
> +    exit(1);
> +}
> +
> +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;
> +
> +    DPRINTF("port_status_write: "
> +            "PORTSC (port %d) curr %08X new %08X rw-clear %08X rw %08X\n",
> +            port, *portsc, val, (val & PORTSC_RWC_MASK), val & PORTSC_RO_MASK);
> +
> +    rwc = val & PORTSC_RWC_MASK;
> +    val &= PORTSC_RO_MASK;
> +
> +    // handle_read_write_clear(&val, portsc, PORTSC_PEDC | PORTSC_CSC);
> +
> +    *portsc &= ~rwc;
> +
> +    if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
> +        DPRINTF("port_status_write: USBTRAN Port %d reset begin\n", port);
> +    }
> +
> +    if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
> +        DPRINTF("port_status_write: USBTRAN Port %d reset done\n", port);
> +        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) {
> +            DPRINTF("port_status_write: "
> +                    "Device was connected before reset, clearing CSC bit\n");
> +            *portsc &= ~PORTSC_CSC;
> +        }
> +
> +        /*  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;
> +    }
> +
> +    *portsc &= ~PORTSC_RO_MASK;
> +    *portsc |= val;
> +    DPRINTF("port_status_write: Port %d status set to 0x%08x\n", port, *portsc);
> +}
> +
> +static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
> +{
> +    EHCIState *s = ptr;
> +    int i;
> +#if EHCI_DEBUG
> +    const char *str;
> +#endif
> +
> +    /* Only aligned reads are allowed on OHCI */
> +    if (addr & 3) {
> +        fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x"
> +                TARGET_FMT_plx "\n", addr);
> +        return;
> +    }
> +
> +    if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) {
> +        handle_port_status_write(s, (addr-PORTSC)/4, val);
> +        return;
> +    }
> +
> +    if (addr < OPREGBASE) {
> +        fprintf(stderr, "usb-ehci: write attempt to read-only register"
> +                TARGET_FMT_plx "\n", addr);
> +        return;
> +    }
> +
> +
> +    /* Do any register specific pre-write processing here.  */
> +#if EHCI_DEBUG
> +    str = addr2str((unsigned) addr);
> +#endif
> +    switch(addr) {
> +    case USBCMD:
> +        DPRINTF("ehci_mem_writel: USBCMD val=0x%08X, current cmd=0x%08X\n",
> +                val, s->usbcmd);
> +
> +        if ((val & USBCMD_RUNSTOP) && !(s->usbcmd & USBCMD_RUNSTOP)) {
> +            DPRINTF("ehci_mem_writel: %s run, clear halt\n", str);
> +            qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock));
> +            SET_LAST_RUN_CLOCK(s);
> +            s->usbsts &= ~USBSTS_HALT;
> +        }
> +
> +        if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) {
> +            DPRINTF("                         ** STOP **\n");
> +            qemu_del_timer(s->frame_timer);
> +            // TODO - should finish out some stuff before setting halt
> +            s->usbsts |= USBSTS_HALT;
> +        }
> +
> +        if (val & USBCMD_HCRESET) {
> +            DPRINTF("ehci_mem_writel: %s run, resetting\n", str);
> +            ehci_reset(s);
> +            val &= ~USBCMD_HCRESET;
> +        }
> +
> +        /* not supporting dynamic frame list size at the moment */
> +        if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
> +            fprintf(stderr, "attempt to set frame list size -- value %d\n",
> +                    val & USBCMD_FLS);
> +            val &= ~USBCMD_FLS;
> +        }
> +#if EHCI_DEBUG
> +        if ((val & USBCMD_PSE) && !(s->usbcmd & USBCMD_PSE)) {
> +            DPRINTF("periodic scheduling enabled\n");
> +        }
> +        if (!(val & USBCMD_PSE) && (s->usbcmd & USBCMD_PSE)) {
> +            DPRINTF("periodic scheduling disabled\n");
> +        }
> +        if ((val & USBCMD_ASE) && !(s->usbcmd & USBCMD_ASE)) {
> +            DPRINTF("asynchronous scheduling enabled\n");
> +        }
> +        if (!(val & USBCMD_ASE) && (s->usbcmd & USBCMD_ASE)) {
> +            DPRINTF("asynchronous scheduling disabled\n");
> +        }
> +        if ((val & USBCMD_IAAD) && !(s->usbcmd & USBCMD_IAAD)) {
> +            DPRINTF("doorbell request received\n");
> +        }
> +        if ((val & USBCMD_LHCR) && !(s->usbcmd & USBCMD_LHCR)) {
> +            DPRINTF("light host controller reset received\n");
> +        }
> +        if ((val & USBCMD_ITC) != (s->usbcmd & USBCMD_ITC)) {
> +            DPRINTF("interrupt threshold control set to %x\n",
> +                    (val & USBCMD_ITC)>>USBCMD_ITC_SH);
> +        }
> +#endif
> +        break;
> +
> +
> +    case USBSTS:
> +        val &= USBSTS_RO_MASK;              // bits 6 thru 31 are RO
> +        DPRINTF("ehci_mem_writel: %s RWC set to 0x%08X\n", str, val);
> +
> +        val = (s->usbsts &= ~val);         // bits 0 thru 5 are R/WC
> +
> +        DPRINTF("ehci_mem_writel: %s updating interrupt condition\n", str);
> +        ehci_set_interrupt(s, 0);
> +        break;
> +
> +
> +    case USBINTR:
> +        val &= USBINTR_MASK;
> +        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
> +        break;
> +
> +    case FRINDEX:
> +        s->sofv = val >> 3;
> +        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
> +        break;
> +
> +    case CONFIGFLAG:
> +        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
> +        val &= 0x1;
> +        if (val) {
> +            for(i = 0; i < NB_PORTS; i++)
> +                s->portsc[i] &= ~PORTSC_POWNER;
> +        }
> +        break;
> +
> +    case PERIODICLISTBASE:
> +        if ((s->usbcmd & USBCMD_PSE) && (s->usbcmd & USBCMD_RUNSTOP)) {
> +            fprintf(stderr,
> +              "ehci: PERIODIC list base register set while periodic schedule\n"
> +              "      is enabled and HC is enabled\n");
> +        }
> +        DPRINTF("ehci_mem_writel: P-LIST BASE set to 0x%08X\n", val);
> +        break;
> +
> +    case ASYNCLISTADDR:
> +        if ((s->usbcmd & USBCMD_ASE) && (s->usbcmd & USBCMD_RUNSTOP)) {
> +            fprintf(stderr,
> +              "ehci: ASYNC list address register set while async schedule\n"
> +              "      is enabled and HC is enabled\n");
> +        }
> +        DPRINTF("ehci_mem_writel: A-LIST ADDR set to 0x%08X\n", val);
> +        break;
> +    }
> +
> +    *(uint32_t *)(&s->mmio[addr]) = 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)
> +{
> +    int i;
> +
> +    for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
> +        cpu_physical_memory_rw(addr,(uint8_t *)buf, sizeof(*buf), 0);
> +        *buf = le32_to_cpu(*buf);
> +    }
> +
> +    return 1;
> +}
> +
> +/* Put an array of dwords in to main memory */
> +static inline int put_dwords(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);
> +    }
> +
> +    return 1;
> +}
> +
> +// 4.10.2
> +
> +static int ehci_qh_do_overlay(EHCIState *ehci, EHCIqh *qh, EHCIqtd *qtd)
> +{
> +    int i;
> +    int dtoggle;
> +    int ping;
> +    int eps;
> +    int reload;
> +
> +    // remember values in fields to preserve in qh after overlay
> +
> +    dtoggle = qh->token & QTD_TOKEN_DTOGGLE;
> +    ping    = qh->token & QTD_TOKEN_PING;
> +
> +    DPRINTF("setting qh.current from %08X to 0x%08X\n", qh->current_qtd,
> +            ehci->qtdaddr);
> +    qh->current_qtd = ehci->qtdaddr;
> +    qh->next_qtd    = qtd->next;
> +    qh->altnext_qtd = qtd->altnext;
> +    qh->token       = qtd->token;
> +
> +
> +    eps = get_field(qh->epchar, QH_EPCHAR_EPS);
> +    if (eps == EHCI_QH_EPS_HIGH) {
> +        qh->token &= ~QTD_TOKEN_PING;
> +        qh->token |= ping;
> +    }
> +
> +    reload = get_field(qh->epchar, QH_EPCHAR_RL);
> +    set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
> +
> +    for (i = 0; i < 5; i++) {
> +        qh->bufptr[i] = qtd->bufptr[i];
> +    }
> +
> +    if (!(qh->epchar & QH_EPCHAR_DTC)) {
> +        // preserve QH DT bit
> +        qh->token &= ~QTD_TOKEN_DTOGGLE;
> +        qh->token |= dtoggle;
> +    }
> +
> +    qh->bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
> +    qh->bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
> +
> +    put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
> +
> +    return 0;
> +}
> +
> +static int ehci_buffer_rw(uint8_t *buffer, EHCIqh *qh, int bytes, int rw)
> +{
> +    int bufpos = 0;
> +    int cpage, offset;
> +    uint32_t head;
> +    uint32_t tail;
> +
> +
> +    if (!bytes) {
> +        return 0;
> +    }
> +
> +    cpage = get_field(qh->token, QTD_TOKEN_CPAGE);
> +    if (cpage > 4) {
> +        fprintf(stderr, "cpage out of range (%d)\n", cpage);
> +        return USB_RET_PROCERR;
> +    }
> +
> +    offset = qh->bufptr[0] & ~QTD_BUFPTR_MASK;
> +    DPRINTF("ehci_buffer_rw: %sing %d bytes %08x cpage %d offset %d\n",
> +           rw ? "writ" : "read", bytes, qh->bufptr[0], cpage, offset);
> +
> +    do {
> +        /* start and end of this page */
> +        head = 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;
> +        }
> +
> +        DPRINTF("DATA %s cpage:%d head:%08X tail:%08X target:%08X\n",
> +                rw ? "WRITE" : "READ ", cpage, head, tail, bufpos);
> +
> +        cpu_physical_memory_rw(head, &buffer[bufpos], tail - head, rw);
> +
> +        bufpos += (tail - head);
> +        bytes -= (tail - head);
> +
> +        if (bytes > 0) {
> +            cpage++;
> +            offset = 0;
> +        }
> +    } while (bytes > 0);
> +
> +    /* save cpage */
> +    set_field(&qh->token, cpage, QTD_TOKEN_CPAGE);
> +
> +    /* save offset into cpage */
> +    offset = tail - head;
> +    qh->bufptr[0] &= ~QTD_BUFPTR_MASK;
> +    qh->bufptr[0] |= offset;
> +
> +    return 0;
> +}
> +
> +static void ehci_async_complete_packet(USBDevice *dev, USBPacket *packet)
> +{
> +    EHCIState *ehci = container_of(packet, EHCIState, usb_packet);
> +
> +    DPRINTF("Async packet complete\n");
> +    ehci->async_complete = 1;
> +    ehci->exec_status = packet->len;
> +}
> +
> +static int ehci_execute_complete(EHCIState *ehci, EHCIqh *qh, int ret)
> +{
> +    int c_err, reload;
> +
> +    if (ret == USB_RET_ASYNC && !ehci->async_complete) {
> +        DPRINTF("not done yet\n");
> +        return ret;
> +    }
> +
> +    ehci->async_complete = 0;
> +
> +    DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
> +            ehci->qhaddr, qh->next, ehci->qtdaddr, ret);
> +
> +    if (ret < 0) {
> +err:
> +        /* TO-DO: put this is in a function that can be invoked below as well */
> +        c_err = get_field(qh->token, QTD_TOKEN_CERR);
> +        c_err--;
> +        set_field(&qh->token, c_err, QTD_TOKEN_CERR);
> +
> +        switch(ret) {
> +        case USB_RET_NODEV:
> +            fprintf(stderr, "USB no device\n");
> +            break;
> +        case USB_RET_STALL:
> +            fprintf(stderr, "USB stall\n");
> +            qh->token |= QTD_TOKEN_HALT;
> +            break;
> +        case USB_RET_NAK:
> +            /* 4.10.3 */
> +            reload = get_field(qh->epchar, QH_EPCHAR_RL);
> +            if ((ehci->pid == USB_TOKEN_IN) && reload) {
> +                int nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
> +                nakcnt--;
> +                set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
> +            } else if (!reload) {
> +                return USB_RET_NAK;
> +            }
> +            break;
> +        case USB_RET_BABBLE:
> +            fprintf(stderr, "USB babble TODO\n");
> +            qh->token |= QTD_TOKEN_BABBLE;
> +            break;
> +        default:
> +            fprintf(stderr, "USB invalid response %d to handle\n", ret);
> +            /* TO-DO: transaction error */
> +            ret = USB_RET_PROCERR;
> +            break;
> +        }
> +    } else {
> +        // DPRINTF("Short packet condition\n");
> +        // TODO check 4.12 for splits
> +
> +        if ((ret > ehci->tbytes) && (ehci->pid == USB_TOKEN_IN)) {
> +            ret = USB_RET_BABBLE;
> +            goto err;
> +        }
> +
> +        if (ehci->tbytes && ehci->pid == USB_TOKEN_IN) {
> +            if (ehci_buffer_rw(ehci->buffer, qh, ret, 1) != 0) {
> +                return USB_RET_PROCERR;
> +            }
> +            ehci->tbytes -= ret;
> +        } else {
> +            ehci->tbytes = 0;
> +        }
> +
> +        DPRINTF("updating tbytes to %d\n", ehci->tbytes);
> +        set_field(&qh->token, ehci->tbytes, QTD_TOKEN_TBYTES);
> +    }
> +
> +    qh->token ^= QTD_TOKEN_DTOGGLE;
> +    qh->token &= ~QTD_TOKEN_ACTIVE;
> +
> +    if ((ret >= 0) && (qh->token & QTD_TOKEN_IOC)) {
> +        ehci_record_interrupt(ehci, USBSTS_INT);
> +    }
> +
> +    return ret;
> +}
> +
> +// 4.10.3
> +
> +static int ehci_execute(EHCIState *ehci, EHCIqh *qh)
> +{
> +    USBPort *port;
> +    USBDevice *dev;
> +    int ret;
> +    int i;
> +    int endp;
> +    int devadr;
> +
> +    if ( !(qh->token & QTD_TOKEN_ACTIVE)) {
> +        fprintf(stderr, "Attempting to execute inactive QH\n");
> +        return USB_RET_PROCERR;
> +    }
> +
> +    ehci->tbytes = (qh->token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
> +    if (ehci->tbytes > BUFF_SIZE) {
> +        fprintf(stderr, "Request for more bytes than allowed\n");
> +        return USB_RET_PROCERR;
> +    }
> +
> +    ehci->pid = (qh->token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
> +    switch(ehci->pid) {
> +        case 0: ehci->pid = USB_TOKEN_OUT; break;
> +        case 1: ehci->pid = USB_TOKEN_IN; break;
> +        case 2: ehci->pid = USB_TOKEN_SETUP; break;
> +        default: fprintf(stderr, "bad token\n"); break;
> +    }
> +
> +    if ((ehci->tbytes && ehci->pid != USB_TOKEN_IN) &&
> +        (ehci_buffer_rw(ehci->buffer, qh, ehci->tbytes, 0) != 0)) {
> +        return USB_RET_PROCERR;
> +    }
> +
> +    endp = get_field(qh->epchar, QH_EPCHAR_EP);
> +    devadr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
> +
> +    ret = USB_RET_NODEV;
> +
> +    // TO-DO: associating device with ehci port
> +    for(i = 0; i < NB_PORTS; i++) {
> +        port = &ehci->ports[i];
> +        dev = port->dev;
> +
> +        // TODO sometime we will also need to check if we are the port owner
> +
> +        if (!(ehci->portsc[i] &(PORTSC_CONNECT))) {
> +            DPRINTF("Port %d, no exec, not connected(%08X)\n",
> +                    i, ehci->portsc[i]);
> +            continue;
> +        }
> +
> +        ehci->usb_packet.pid = ehci->pid;
> +        ehci->usb_packet.devaddr = devadr;
> +        ehci->usb_packet.devep = endp;
> +        ehci->usb_packet.data = ehci->buffer;
> +        ehci->usb_packet.len = ehci->tbytes;
> +
> +        ret = usb_handle_packet(dev, &ehci->usb_packet);
> +
> +        DPRINTF("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n",
> +                ehci->qhaddr, qh->next, ehci->qtdaddr, ehci->pid,
> +                ehci->usb_packet.len, ehci->tbytes, endp, ret);
> +
> +        if (ret != USB_RET_NODEV) {
> +            break;
> +        }
> +    }
> +
> +    if (ret > BUFF_SIZE) {
> +        fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
> +        return USB_RET_PROCERR;
> +    }
> +
> +    if (ret == USB_RET_ASYNC) {
> +        ehci->async_complete = 0;
> +    }
> +
> +    return ret;
> +}
> +
> +/*  4.7.2
> + */
> +
> +static int ehci_process_itd(EHCIState *ehci,
> +                            EHCIitd *itd)
> +{
> +    USBPort *port;
> +    USBDevice *dev;
> +    int ret;
> +    int i, j;
> +    int ptr;
> +    int pid;
> +    int pg;
> +    int len;
> +    int dir;
> +    int devadr;
> +    int endp;
> +    int maxpkt;
> +
> +    dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
> +    devadr = 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);
> +
> +    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);
> +
> +            if (len > BUFF_SIZE) {
> +                return USB_RET_PROCERR;
> +            }
> +
> +            DPRINTF("ISOCH: buffer %08X len %d\n", ptr, len);
> +
> +            if (!dir) {
> +                cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 0);
> +                pid = USB_TOKEN_OUT;
> +            } else
> +                pid = USB_TOKEN_IN;
> +
> +            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->usb_packet.pid = ehci->pid;
> +                ehci->usb_packet.devaddr = devadr;
> +                ehci->usb_packet.devep = endp;
> +                ehci->usb_packet.data = ehci->buffer;
> +                ehci->usb_packet.len = len;
> +
> +                DPRINTF("calling usb_handle_packet\n");
> +                ret = usb_handle_packet(dev, &ehci->usb_packet);
> +
> +                if (ret != USB_RET_NODEV) {
> +                    break;
> +                }
> +            }
> +
> +            /*  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.
> +             */
> +
> +            if (ret == USB_RET_NAK) {
> +                if (ehci->isoch_pause > 0) {
> +                    DPRINTF("ISOCH: received a NAK but paused so returning\n");
> +                    ehci->isoch_pause--;
> +                    return 0;
> +                } else if (ehci->isoch_pause == -1) {
> +                    DPRINTF("ISOCH: recv NAK & isoch pause inactive, setting\n");
> +                    // Pause frindex for up to 50 msec waiting for data from
> +                    // remote
> +                    ehci->isoch_pause = 50;
> +                    return 0;
> +                } else {
> +                    DPRINTF("ISOCH: isoch pause timeout! return 0\n");
> +                    ret = 0;
> +                }
> +            } else {
> +                DPRINTF("ISOCH: received ACK, clearing pause\n");
> +                ehci->isoch_pause = -1;
> +            }
> +
> +            if (ret >= 0) {
> +                itd->transact[i] &= ~ITD_XACT_ACTIVE;
> +
> +                if (itd->transact[i] & ITD_XACT_IOC) {
> +                    ehci_record_interrupt(ehci, USBSTS_INT);
> +                }
> +            }
> +
> +            if (ret >= 0 && dir) {
> +                cpu_physical_memory_rw(ptr, &ehci->buffer[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);
> +                }
> +            }
> +        }
> +    }
> +    return 0;
> +}
> +
> +/*  This state is the entry point for asynchronous schedule
> + *  processing.  Entry here consitutes a EHCI start event state (4.8.5)
> + */
> +static int ehci_state_waitlisthead(EHCIState *ehci,  int async, int *state)
> +{
> +    EHCIqh *qh = &ehci->qh;
> +    int i = 0;
> +    int again = 0;
> +    uint32_t entry = ehci->asynclistaddr;
> +
> +    /* set reclamation flag at start event (4.8.6) */
> +    if (async) {
> +        ehci->usbsts |= USBSTS_REC;
> +    }
> +
> +    /*  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);
> +
> +        if (qh->epchar & QH_EPCHAR_H) {
> +            DPRINTF_ST("WAITLISTHEAD: QH %08X is the HEAD of the list\n",
> +                       entry);
> +            if (async) {
> +                entry |= (NLPTR_TYPE_QH << 1);
> +            }
> +
> +            ehci->fetch_addr = entry;
> +            *state = EST_FETCHENTRY;
> +            again = 1;
> +            goto out;
> +        }
> +
> +        DPRINTF_ST("WAITLISTHEAD: QH %08X is NOT the HEAD of the list\n",
> +                   entry);
> +        entry = qh->next;
> +        if (entry == ehci->asynclistaddr) {
> +            DPRINTF("WAITLISTHEAD: reached beginning of QH list\n");
> +            break;
> +        }
> +    }
> +
> +    /* no head found for list. */
> +
> +    *state = EST_ACTIVE;
> +
> +out:
> +    return again;
> +}
> +
> +
> +/*  This state is the entry point for periodic schedule processing as
> + *  well as being a continuation state for async processing.
> + */
> +static int ehci_state_fetchentry(EHCIState *ehci, int async, int *state)
> +{
> +    int again = 0;
> +    uint32_t entry = ehci->fetch_addr;
> +
> +#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);
> +        *state = EST_ACTIVE;
> +        goto out;
> +    }
> +
> +    /* section 4.8, only QH in async schedule */
> +    if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
> +        fprintf(stderr, "non queue head request in async schedule\n");
> +        return -1;
> +    }
> +
> +    switch (NLPTR_TYPE_GET(entry)) {
> +    case NLPTR_TYPE_QH:
> +        DPRINTF_ST("FETCHENTRY: entry %X is a Queue Head\n", entry);
> +        *state = EST_FETCHQH;
> +        ehci->qhaddr = entry;
> +        again = 1;
> +        break;
> +
> +    case NLPTR_TYPE_ITD:
> +        DPRINTF_ST("FETCHENTRY: entry %X is an ITD\n", entry);
> +        *state = EST_FETCHITD;
> +        ehci->itdaddr = entry;
> +        again = 1;
> +        break;
> +
> +    default:
> +        // TODO: handle siTD and FSTN types
> +        fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
> +                "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
> +        return -1;
> +    }
> +
> +out:
> +    return again;
> +}
> +
> +static int ehci_state_fetchqh(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIqh *qh = &ehci->qh;
> +    int reload;
> +    int again = 0;
> +
> +    get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
> +
> +    if (async && (qh->epchar & QH_EPCHAR_H)) {
> +
> +        /*  EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
> +        if (ehci->usbsts & USBSTS_REC) {
> +            ehci->usbsts &= ~USBSTS_REC;
> +        } else {
> +            DPRINTF("FETCHQH:  QH 0x%08x. H-bit set, reclamation status reset"
> +                       " - done processing\n", ehci->qhaddr);
> +            *state = EST_ACTIVE;
> +            goto out;
> +        }
> +    }
> +
> +#if EHCI_DEBUG
> +    if (ehci->qhaddr != qh->next) {
> +    DPRINTF("FETCHQH:  QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
> +               ehci->qhaddr,
> +               qh->epchar & QH_EPCHAR_H,
> +               qh->token & QTD_TOKEN_HALT,
> +               qh->token & QTD_TOKEN_ACTIVE,
> +               qh->next);
> +    }
> +#endif
> +
> +    reload = get_field(qh->epchar, QH_EPCHAR_RL);
> +    if (reload) {
> +        DPRINTF_ST("FETCHQH: reloading nakcnt to %d\n", reload);
> +        set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
> +    }
> +
> +    if (qh->token & QTD_TOKEN_HALT) {
> +        DPRINTF_ST("FETCHQH: QH Halted, go horizontal\n");
> +        *state = EST_HORIZONTALQH;
> +        again = 1;
> +
> +    } else if ((qh->token & QTD_TOKEN_ACTIVE) && (qh->current_qtd > 0x1000)) {
> +        DPRINTF_ST("FETCHQH: Active, !Halt, execute - fetch qTD\n");
> +        ehci->qtdaddr = qh->current_qtd;
> +        *state = EST_FETCHQTD;
> +        again = 1;
> +
> +    } else {
> +        /*  EHCI spec version 1.0 Section 4.10.2 */
> +        DPRINTF_ST("FETCHQH: !Active, !Halt, advance queue\n");
> +        *state = EST_ADVANCEQUEUE;
> +        again = 1;
> +    }
> +
> +out:
> +    return again;
> +}
> +
> +static int ehci_state_fetchitd(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIitd itd;
> +
> +    get_dwords(NLPTR_GET(ehci->itdaddr),(uint32_t *) &itd,
> +               sizeof(EHCIitd) >> 2);
> +    DPRINTF_ST("FETCHITD: Fetched ITD at address %08X " "(next is %08X)\n",
> +               ehci->itdaddr, itd.next);
> +
> +    if (ehci_process_itd(ehci, &itd) != 0) {
> +        return -1;
> +    }
> +
> +    put_dwords(NLPTR_GET(ehci->itdaddr), (uint32_t *) &itd,
> +                sizeof(EHCIitd) >> 2);
> +    ehci->fetch_addr = itd.next;
> +    *state = EST_FETCHENTRY;
> +
> +    return 1;
> +}
> +
> +/* Section 4.10.2 - paragraph 3 */
> +static int ehci_state_advqueue(EHCIState *ehci, int async, int *state)
> +{
> +#if 0
> +    /* TO-DO: 4.10.2 - paragraph 2
> +     * if I-bit is set to 1 and QH is not active
> +     * go to horizontal QH
> +     */
> +    if (I-bit set) {
> +        *state = EST_HORIZONTALQH;
> +        goto out;
> +    }
> +#endif
> +
> +    /*
> +     * want data and alt-next qTD is valid
> +     */
> +    if (((ehci->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
> +        (ehci->qh.altnext_qtd > 0x1000) &&
> +        (NLPTR_TBIT(ehci->qh.altnext_qtd) == 0)) {
> +        DPRINTF_ST("ADVQUEUE: goto alt next qTD. "
> +                   "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
> +                   ehci->qh.current_qtd, ehci->qh.altnext_qtd,
> +                   ehci->qh.next_qtd, ehci->qh.next);
> +        ehci->qtdaddr = ehci->qh.altnext_qtd;
> +        *state = EST_FETCHQTD;
> +
> +    /*
> +     *  next qTD is valid
> +     */
> +    } else if ((ehci->qh.next_qtd > 0x1000) &&
> +               (NLPTR_TBIT(ehci->qh.next_qtd) == 0)) {
> +        DPRINTF_ST("ADVQUEUE: next qTD. "
> +                   "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
> +                   ehci->qh.current_qtd, ehci->qh.altnext_qtd,
> +                   ehci->qh.next_qtd, ehci->qh.next);
> +        ehci->qtdaddr = ehci->qh.next_qtd;
> +        *state = EST_FETCHQTD;
> +
> +    /*
> +     *  no valid qTD, try next QH
> +     */
> +    } else {
> +        DPRINTF_ST("ADVQUEUE: go to horizontal QH\n");
> +        *state = EST_HORIZONTALQH;
> +    }
> +
> +    return 1;
> +}
> +
> +/* Section 4.10.2 - paragraph 4 */
> +static int ehci_state_fetchqtd(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIqtd *qtd = &ehci->qtd;
> +    int again = 0;
> +
> +    get_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) qtd, sizeof(EHCIqtd) >> 2);
> +
> +    if (qtd->token & QTD_TOKEN_ACTIVE) {
> +        *state = EST_EXECUTE;
> +        again = 1;
> +    } else {
> +        *state = EST_HORIZONTALQH;
> +        again = 1;
> +    }
> +
> +    return again;
> +}
> +
> +static int ehci_state_horizqh(EHCIState *ehci, int async, int *state)
> +{
> +    int again = 0;
> +
> +    if (ehci->fetch_addr != ehci->qh.next) {
> +        ehci->fetch_addr = ehci->qh.next;
> +        *state = EST_FETCHENTRY;
> +        again = 1;
> +    } else {
> +        *state = EST_ACTIVE;
> +    }
> +
> +    return again;
> +}
> +
> +static int ehci_state_execute(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIqh *qh = &ehci->qh;
> +    EHCIqtd *qtd = &ehci->qtd;
> +    int again = 0;
> +    int reload, nakcnt;
> +    int smask;
> +
> +    if (async) {
> +        DPRINTF_ST(">>>>> ASYNC STATE MACHINE execute QH 0x%08x, QTD 0x%08x\n",
> +                  ehci->qhaddr, ehci->qtdaddr);
> +    } else {
> +        DPRINTF_ST(">>>>> PERIODIC STATE MACHINE execute\n");
> +    }
> +
> +    if (ehci_qh_do_overlay(ehci, qh, qtd) != 0) {
> +        return -1;
> +    }
> +
> +    smask = get_field(qh->epcap, QH_EPCAP_SMASK);
> +
> +    if (!smask) {
> +        reload = get_field(qh->epchar, QH_EPCHAR_RL);
> +        nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
> +        if (reload && !nakcnt) {
> +            DPRINTF_ST("EXECUTE: RL != 0 but NakCnt == 0 -- no execute\n");
> +            *state = EST_HORIZONTALQH;
> +            again = 1;
> +            goto out;
> +        }
> +    }
> +
> +    // TODO verify enough time remains in the uframe as in 4.4.1.1
> +    // TODO write back ptr to async list when done or out of time
> +    // TODO Windows does not seem to ever set the MULT field
> +
> +    if (!async) {
> +        int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
> +        if (!transactCtr) {
> +            DPRINTF("ZERO transactctr for int qh, go HORIZ\n");
> +            *state = EST_HORIZONTALQH;
> +            again = 1;
> +            goto out;
> +        }
> +    }
> +
> +    if (async) {
> +        ehci->usbsts |= USBSTS_REC;
> +    }
> +
> +    ehci->exec_status = ehci_execute(ehci, qh);
> +    if (ehci->exec_status == USB_RET_PROCERR) {
> +        again = -1;
> +        goto out;
> +    }
> +    *state = EST_EXECUTING;
> +
> +    if (ehci->exec_status != USB_RET_ASYNC) {
> +        again = 1;
> +    }
> +
> +out:
> +    return again;
> +}
> +
> +static int ehci_state_executing(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIqh *qh = &ehci->qh;
> +    int again = 0;
> +    int reload, nakcnt;
> +
> +    ehci->exec_status = ehci_execute_complete(ehci, qh, ehci->exec_status);
> +    if (ehci->exec_status == USB_RET_ASYNC) {
> +        goto out;
> +    }
> +    if (ehci->exec_status == USB_RET_PROCERR) {
> +        again = -1;
> +        goto out;
> +    }
> +
> +    // 4.10.3
> +    if (!async) {
> +        int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
> +        transactCtr--;
> +        set_field(&qh->epcap, transactCtr, QH_EPCAP_MULT);
> +        // 4.10.3, bottom of page 82, should exit this state when transaction
> +        // counter decrements to 0
> +    }
> +
> +
> +    reload = get_field(qh->epchar, QH_EPCHAR_RL);
> +    if (reload) {
> +        nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
> +        if (ehci->exec_status == USB_RET_NAK) {
> +            if (nakcnt) {
> +                nakcnt--;
> +            }
> +            DPRINTF_ST("EXECUTING: Nak occured and RL != 0, dec NakCnt to %d\n",
> +                    nakcnt);
> +        } else {
> +            nakcnt = reload;
> +            DPRINTF_ST("EXECUTING: Nak didn't occur, reloading to %d\n",
> +                       nakcnt);
> +        }
> +        set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
> +    }
> +
> +    /*
> +     *  Write the qh back to guest physical memory.  This step isn't
> +     *  in the EHCI spec but we need to do it since we don't share
> +     *  physical memory with our guest VM.
> +     */
> +
> +    DPRINTF("EXECUTING: write QH to VM memory: qhaddr 0x%x, next 0x%x\n",
> +              ehci->qhaddr, qh->next);
> +    put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
> +
> +    /* 4.10.5 */
> +    if ((ehci->exec_status == USB_RET_NAK) || (qh->token & QTD_TOKEN_ACTIVE)) {
> +        *state = EST_HORIZONTALQH;
> +    } else {
> +        *state = EST_WRITEBACK;
> +    }
> +
> +    again = 1;
> +
> +out:
> +    return again;
> +}
> +
> +
> +static int ehci_state_writeback(EHCIState *ehci, int async, int *state)
> +{
> +    EHCIqh *qh = &ehci->qh;
> +    int again = 0;
> +
> +    /*  Write back the QTD from the QH area */
> +    DPRINTF_ST("WRITEBACK: write QTD to VM memory\n");
> +    put_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) &qh->next_qtd,
> +                sizeof(EHCIqtd) >> 2);
> +
> +    /* TODO confirm next state.  For now, keep going if async
> +     * but stop after one qtd if periodic
> +     */
> +    //if (async) {
> +        *state = EST_ADVANCEQUEUE;
> +        again = 1;
> +    //} else {
> +    //    *state = EST_ACTIVE;
> +    //}
> +    return again;
> +}
> +
> +/*
> + * This is the state machine that is common to both async and periodic
> + */
> +
> +static int ehci_advance_state(EHCIState *ehci,
> +                              int async,
> +                              int state)
> +{
> +    int again;
> +    int iter = 0;
> +
> +    do {
> +        if (state == EST_FETCHQH) {
> +            iter++;
> +            /* if we are roaming a lot of QH without executing a qTD
> +             * something is wrong with the linked list. TO-DO: why is
> +             * this hack needed?
> +             */
> +            if (iter > MAX_ITERATIONS) {
> +                DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n");
> +                state = EST_ACTIVE;
> +                break;
> +            }
> +        }
> +        switch(state) {
> +        case EST_WAITLISTHEAD:
> +            again = ehci_state_waitlisthead(ehci, async, &state);
> +            break;
> +
> +        case EST_FETCHENTRY:
> +            again = ehci_state_fetchentry(ehci, async, &state);
> +            break;
> +
> +        case EST_FETCHQH:
> +            again = ehci_state_fetchqh(ehci, async, &state);
> +            break;
> +
> +        case EST_FETCHITD:
> +            again = ehci_state_fetchitd(ehci, async, &state);
> +            break;
> +
> +        case EST_ADVANCEQUEUE:
> +            again = ehci_state_advqueue(ehci, async, &state);
> +            break;
> +
> +        case EST_FETCHQTD:
> +            again = ehci_state_fetchqtd(ehci, async, &state);
> +            break;
> +
> +        case EST_HORIZONTALQH:
> +            again = ehci_state_horizqh(ehci, async, &state);
> +            break;
> +
> +        case EST_EXECUTE:
> +            iter = 0;
> +            again = ehci_state_execute(ehci, async, &state);
> +            break;
> +
> +        case EST_EXECUTING:
> +            again = ehci_state_executing(ehci, async, &state);
> +            break;
> +
> +        case EST_WRITEBACK:
> +            again = ehci_state_writeback(ehci, async, &state);
> +            break;
> +
> +        default:
> +            fprintf(stderr, "Bad state!\n");
> +            again = -1;
> +            break;
> +        }
> +
> +        if (again < 0) {
> +            fprintf(stderr, "processing error - resetting ehci HC\n");
> +            ehci_reset(ehci);
> +            again = 0;
> +        }
> +    }
> +    while (again);
> +
> +    ehci_commit_interrupt(ehci);
> +    return state;
> +}
> +
> +static void ehci_advance_async_state(EHCIState *ehci)
> +{
> +    EHCIqh qh;
> +    int state = ehci->astate;
> +
> +    switch(state) {
> +    case EST_INACTIVE:
> +        if (!(ehci->usbcmd & USBCMD_ASE)) {
> +            break;
> +        }
> +        ehci->usbsts |= USBSTS_ASS;
> +        ehci->astate = EST_ACTIVE;
> +        // No break, fall through to ACTIVE
> +
> +    case EST_ACTIVE:
> +        if ( !(ehci->usbcmd & USBCMD_ASE)) {
> +            ehci->usbsts &= ~USBSTS_ASS;
> +            ehci->astate = EST_INACTIVE;
> +            break;
> +        }
> +
> +        /* If the doorbell is set, the guest wants to make a change to the
> +         * schedule. The host controller needs to release cached data.
> +         * (section 4.8.2)
> +         */
> +        if (ehci->usbcmd & USBCMD_IAAD) {
> +            DPRINTF("ASYNC: doorbell request acknowledged\n");
> +            ehci->usbcmd &= ~USBCMD_IAAD;
> +            ehci_set_interrupt(ehci, USBSTS_IAA);
> +            break;
> +        }
> +
> +        /* make sure guest has acknowledged */
> +        /* TO-DO: is this really needed? */
> +        if (ehci->usbsts & USBSTS_IAA) {
> +            DPRINTF("IAA status bit still set.\n");
> +            break;
> +        }
> +
> +        DPRINTF_ST("ASYNC: waiting for listhead, starting at %08x\n",
> +                ehci->asynclistaddr);
> +        /* check that address register has been set */
> +        if (ehci->asynclistaddr == 0) {
> +            break;
> +        }
> +
> +        state = EST_WAITLISTHEAD;
> +        /* fall through */
> +
> +    case EST_FETCHENTRY:
> +        /* fall through */
> +
> +    case EST_EXECUTING:
> +        get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) &qh,
> +                   sizeof(EHCIqh) >> 2);
> +        ehci->astate = ehci_advance_state(ehci, 1, state);
> +        break;
> +
> +    default:
> +        /* this should only be due to a developer mistake */
> +        fprintf(stderr, "ehci: Bad asynchronous state %d. "
> +                "Resetting to active\n", ehci->astate);
> +        ehci->astate = EST_ACTIVE;
> +    }
> +}
> +
> +static void ehci_advance_periodic_state(EHCIState *ehci)
> +{
> +    uint32_t entry;
> +    uint32_t list;
> +
> +    // 4.6
> +
> +    switch(ehci->pstate) {
> +    case EST_INACTIVE:
> +        if ( !(ehci->frindex & 7) && (ehci->usbcmd & USBCMD_PSE)) {
> +            DPRINTF("PERIODIC going active\n");
> +            ehci->usbsts |= USBSTS_PSS;
> +            ehci->pstate = EST_ACTIVE;
> +            // No break, fall through to ACTIVE
> +        } else
> +            break;
> +
> +    case EST_ACTIVE:
> +        if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) {
> +            DPRINTF("PERIODIC going inactive\n");
> +            ehci->usbsts &= ~USBSTS_PSS;
> +            ehci->pstate = EST_INACTIVE;
> +            break;
> +        }
> +
> +        list = ehci->periodiclistbase & 0xfffff000;
> +        /* check that register has been set */
> +        if (list == 0) {
> +            break;
> +        }
> +        list |= ((ehci->frindex & 0x1ff8) >> 1);
> +
> +        cpu_physical_memory_rw(list, (uint8_t *) &entry, sizeof entry, 0);
> +        entry = le32_to_cpu(entry);
> +
> +        DPRINTF("PERIODIC state adv fr=%d.  [%08X] -> %08X\n",
> +                ehci->frindex / 8, list, entry);
> +        ehci->fetch_addr = entry;
> +        ehci->pstate = ehci_advance_state(ehci, 0, EST_FETCHENTRY);
> +        break;
> +
> +    case EST_EXECUTING:
> +        DPRINTF("PERIODIC state adv for executing\n");
> +        ehci->pstate = ehci_advance_state(ehci, 0, EST_EXECUTING);
> +        break;
> +
> +    default:
> +        /* this should only be due to a developer mistake */
> +        fprintf(stderr, "ehci: Bad periodic state %d. "
> +                "Resetting to active\n", ehci->pstate);
> +        ehci->pstate = EST_ACTIVE;
> +    }
> +}
> +
> +static void ehci_frame_timer(void *opaque)
> +{
> +    EHCIState *ehci = opaque;
> +    int64_t expire_time, t_now;
> +    int usec_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++;
> +    }
> +
> +    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;
> +
> +    for (i = 0; i < frames; i++) {
> +        if ( !(ehci->usbsts & USBSTS_HALT)) {
> +            if (ehci->isoch_pause <= 0) {
> +                ehci->frindex += 8;
> +            }
> +
> +            if (ehci->frindex > 0x00001fff) {
> +                ehci->frindex = 0;
> +                ehci_set_interrupt(ehci, USBSTS_FLR);
> +            }
> +
> +            ehci->sofv = (ehci->frindex - 1) >> 3;
> +            ehci->sofv &= 0x000003ff;
> +        }
> +
> +        if (frames - i > 10) {
> +            skipped_frames++;
> +        } else {
> +            // TODO could this cause periodic frames to get skipped if async
> +            // active?
> +            if (ehci->astate != EST_EXECUTING) {
> +                ehci_advance_periodic_state(ehci);
> +            }
> +        }
> +
> +        ehci->last_run_usec += FRAME_TIMER_USEC;
> +    }
> +
> +#if 0
> +    if (skipped_frames) {
> +        DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames);
> +    }
> +#endif
> +
> +    /*  Async is not inside loop since it executes everything it can once
> +     *  called
> +     */
> +    if (ehci->pstate != EST_EXECUTING) {
> +        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 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 int usb_ehci_initfn(PCIDevice *dev);
> +
> +static USBPortOps ehci_port_ops = {
> +    .attach = ehci_attach,
> +    .detach = ehci_detach,
> +    .complete = ehci_async_complete_packet,
> +};
> +
> +static PCIDeviceInfo ehci_info = {
> +    .qdev.name    = "usb-ehci",
> +    .qdev.size    = sizeof(EHCIState),
> +    .init         = usb_ehci_initfn,
> +};
> +
> +static int usb_ehci_initfn(PCIDevice *dev)
> +{
> +    EHCIState *s = DO_UPCAST(EHCIState, dev, 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_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[0x61], 0x20);  // frame length adjustment (2.1.5)
> +    pci_set_word(&pci_conf[0x62], 0x00);  // port wake up capability (2.1.6)
> +
> +    pci_conf[0x64] = 0x00;
> +    pci_conf[0x65] = 0x00;
> +    pci_conf[0x66] = 0x00;
> +    pci_conf[0x67] = 0x00;
> +    pci_conf[0x68] = 0x01;
> +    pci_conf[0x69] = 0x00;
> +    pci_conf[0x6a] = 0x00;
> +    pci_conf[0x6b] = 0x00;  // USBLEGSUP
> +    pci_conf[0x6c] = 0x00;
> +    pci_conf[0x6d] = 0x00;
> +    pci_conf[0x6e] = 0x00;
> +    pci_conf[0x6f] = 0xc0;  // USBLEFCTLSTS
> +
> +    // 2.2 host controller interface version
> +    s->mmio[0x00] = (uint8_t) OPREGBASE;
> +    s->mmio[0x01] = 0x00;
> +    s->mmio[0x02] = 0x00;
> +    s->mmio[0x03] = 0x01;        // HC version
> +    s->mmio[0x04] = NB_PORTS;    // Number of downstream ports
> +    s->mmio[0x05] = 0x00;        // No companion ports at present
> +    s->mmio[0x06] = 0x00;
> +    s->mmio[0x07] = 0x00;
> +    s->mmio[0x08] = 0x80;        // We can cache whole frame, not 64-bit capable
> +    s->mmio[0x09] = 0x68;        // EECP
> +    s->mmio[0x0a] = 0x00;
> +    s->mmio[0x0b] = 0x00;
> +
> +    s->irq = s->dev.irq[3];
> +
> +    // TODO - port registration is going to need an overhaul since ports
> +    // can be low, full or high speed and are not tied to UHCI or EHCI.
> +    // This works for now since we register last so are top of the free
> +    // list but really all ports need to be owned by EHCI and it should
> +    // hand off to companion controllers if device is full or low speed.
> +
> +    DPRINTF("ehci_init : registering USB ports with no device attached\n");
> +
> +    // TODO come up with a better port allocation scheme
> +    // added ehci->bus, need to find ehci->DeviceState
> +    usb_bus_new(&s->bus, &s->dev.qdev);
> +    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;
> +    }
> +
> +    s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
> +
> +    qemu_register_reset(ehci_reset, s);
> +
> +    s->mem = cpu_register_io_memory(ehci_readfn, ehci_writefn, s,
> +                                    DEVICE_LITTLE_ENDIAN);
> +
> +    DPRINTF("ehci_init: registering MMIO size %d\n", MMIO_SIZE);
> +    pci_register_bar(&s->dev, 0, MMIO_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY,
> +                                                            ehci_map);
> +
> +    fprintf(stderr, "*** EHCI support is under development ***\n");
> +
> +    return 0;
> +}
> +
> +static void ehci_register(void)
> +{
> +    pci_qdev_register(&ehci_info);
> +}
> +device_init(ehci_register);
> +
> +void usb_ehci_init(PCIBus *bus, int devfn)
> +{
> +    pci_create_simple(bus, devfn, "usb-ehci");
> +}
> +
> +/*
> + * vim: expandtab ts=4
> + */
> diff --git a/hw/usb-ehci.h b/hw/usb-ehci.h
> new file mode 100644
> index 0000000..ba087a0
> --- /dev/null
> +++ b/hw/usb-ehci.h
> @@ -0,0 +1,8 @@
> +#ifndef QEMU_USB_EHCI_H
> +#define QEMU_USB_EHCI_H
> +
> +#include "qemu-common.h"
> +
> +void usb_ehci_init(PCIBus *bus, int devfn);
> +
> +#endif
Gerd Hoffmann May 17, 2011, 7:20 a.m. UTC | #2
> On 05/16/11 13:56, Gerd Hoffmann wrote:
>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>
>> Based on git://git.kiszka.org/qemu.git ehci
>>
>> Changes:
>>    - Adapt to recent changes in the usb subsystem.
>>    - Don't create device automagically, use -device instead.
>>    - Add quickstart text file, see docs/usb2.txt.
>>    - A bunch of codestyle fixups.
>>    - Add authors+contributers list.
>>    - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>      wakeup rate anyway.
>>    - A few bug fixes.
>>
>> Signed-off-by: Gerd Hoffmann<kraxel@redhat.com>
>
> As someone who spent a significant amount of time working on the EHCI
> code last year I am absolutely not ok with this. The entire contribution
> history for EHCI lost - and for no reason.

There is a reason.  I've tried to keep the history, but it was a big 
mess with conflicts and build errors due to ehci being out-of-tree for a 
loooooong time.

> The inclusion of EHCI into
> qemu can be done in such a way as to maintain the history.

Prove it.  Give me a git tree with ehci history I can use as replacement 
for patch 18 and I'll pull it in.

cheers,
   Gerd
David S. Ahern May 17, 2011, 12:42 p.m. UTC | #3
On 05/17/11 01:20, Gerd Hoffmann wrote:
>> On 05/16/11 13:56, Gerd Hoffmann wrote:
>>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>>
>>> Based on git://git.kiszka.org/qemu.git ehci
>>>
>>> Changes:
>>>    - Adapt to recent changes in the usb subsystem.
>>>    - Don't create device automagically, use -device instead.
>>>    - Add quickstart text file, see docs/usb2.txt.
>>>    - A bunch of codestyle fixups.
>>>    - Add authors+contributers list.
>>>    - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>>      wakeup rate anyway.
>>>    - A few bug fixes.
>>>
>>> Signed-off-by: Gerd Hoffmann<kraxel@redhat.com>
>>
>> As someone who spent a significant amount of time working on the EHCI
>> code last year I am absolutely not ok with this. The entire contribution
>> history for EHCI lost - and for no reason.
> 
> There is a reason.  I've tried to keep the history, but it was a big
> mess with conflicts and build errors due to ehci being out-of-tree for a
> loooooong time.

Not true. Back in March it took me less 2-1/2 hours to see a request for
a 0.14 version, update my git repo, merge master onto ehci, fix merges,
test and send out:

http://comments.gmane.org/gmane.comp.emulators.kvm.devel/68898

Again, most of the changes are to the EHCI code. The rest are
sprinklings to add the adapter and adhere to USB API.

> 
>> The inclusion of EHCI into
>> qemu can be done in such a way as to maintain the history.
> 
> Prove it.  Give me a git tree with ehci history I can use as replacement
> for patch 18 and I'll pull it in.

Jan's tree that was started for the EHCI development. That's my repo
locally is based on. You chose to not work with it or even get a pull
request to pull in the patches. Instead you opted to pick up the code
and plop into your repo.

David

> 
> cheers,
>   Gerd
Gerd Hoffmann May 17, 2011, 1:46 p.m. UTC | #4
Hi,

>>> As someone who spent a significant amount of time working on the EHCI
>>> code last year I am absolutely not ok with this. The entire contribution
>>> history for EHCI lost - and for no reason.
>>
>> There is a reason.  I've tried to keep the history, but it was a big
>> mess with conflicts and build errors due to ehci being out-of-tree for a
>> loooooong time.
>
> Not true. Back in March it took me less 2-1/2 hours to see a request for
> a 0.14 version, update my git repo, merge master onto ehci, fix merges,
> test and send out:
>
> http://comments.gmane.org/gmane.comp.emulators.kvm.devel/68898
>
> Again, most of the changes are to the EHCI code. The rest are
> sprinklings to add the adapter and adhere to USB API.

And it totally ignores the usb changes which went into 0.14 to prepare 
the qemu usb subsystem for higher speeds.  There is no such thing like a 
v1 or v2 usb bus.

You've mailed out a single monster patch instead of a patch series. 
That is the problem with merges:  You can trivially get a full diff 
against master, but you can't easily get a nicely review-able patch series.

I've tried to rebase the ehci tree.  First, to fix the patch series 
issue.  Second to adapt patches to qemu changes, to make the patch 
series bisect-able.  Also to drop obsolete bits.  That didn't work out 
as described.  You didn't even try.

I'm not impressed.

cheers,
   Gerd
David S. Ahern May 17, 2011, 1:52 p.m. UTC | #5
On 05/17/11 06:42, David Ahern wrote:
> 
> 
> On 05/17/11 01:20, Gerd Hoffmann wrote:
>>> On 05/16/11 13:56, Gerd Hoffmann wrote:
>>>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>>>
>>>> Based on git://git.kiszka.org/qemu.git ehci
>>>>
>>>> Changes:
>>>>    - Adapt to recent changes in the usb subsystem.
>>>>    - Don't create device automagically, use -device instead.
>>>>    - Add quickstart text file, see docs/usb2.txt.
>>>>    - A bunch of codestyle fixups.
>>>>    - Add authors+contributers list.
>>>>    - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>>>      wakeup rate anyway.
>>>>    - A few bug fixes.
>>>>
>>>> Signed-off-by: Gerd Hoffmann<kraxel@redhat.com>
>>>
>>> As someone who spent a significant amount of time working on the EHCI
>>> code last year I am absolutely not ok with this. The entire contribution
>>> history for EHCI lost - and for no reason.
>>
>> There is a reason.  I've tried to keep the history, but it was a big
>> mess with conflicts and build errors due to ehci being out-of-tree for a
>> loooooong time.
> 
> Not true. Back in March it took me less 2-1/2 hours to see a request for
> a 0.14 version, update my git repo, merge master onto ehci, fix merges,
> test and send out:
> 
> http://comments.gmane.org/gmane.comp.emulators.kvm.devel/68898
> 
> Again, most of the changes are to the EHCI code. The rest are
> sprinklings to add the adapter and adhere to USB API.
> 
>>
>>> The inclusion of EHCI into
>>> qemu can be done in such a way as to maintain the history.
>>
>> Prove it.  Give me a git tree with ehci history I can use as replacement
>> for patch 18 and I'll pull it in.
> 
> Jan's tree that was started for the EHCI development. That's my repo
> locally is based on. You chose to not work with it or even get a pull
> request to pull in the patches. Instead you opted to pick up the code
> and plop into your repo.
> 
> David

Let me turn this around. Your contributions to the EHCI code:
1. coding standards,
2. internal qemu API
3. nuking code based on 8kHz.

All of those could have been done in Jan's tree and from there have a
Jan do a pull request to the maintainers. It is not hard.

(And by the way, where are the focused patches for each, especially the
last one - nuking the 8kHz code?  We know that it worked on linux and
that printers, scanners and storage devices worked ok (mostly). Changes
made in a vacuum despite prior patches and interest from qemu users -
some of whom are using the EHCI code.)

David



> 
>>
>> cheers,
>>   Gerd
> 
>
David S. Ahern May 17, 2011, 2:19 p.m. UTC | #6
On 05/17/11 07:46, Gerd Hoffmann wrote:
> 
>   Hi,
> 
>>>> As someone who spent a significant amount of time working on the EHCI
>>>> code last year I am absolutely not ok with this. The entire
>>>> contribution
>>>> history for EHCI lost - and for no reason.
>>>
>>> There is a reason.  I've tried to keep the history, but it was a big
>>> mess with conflicts and build errors due to ehci being out-of-tree for a
>>> loooooong time.
>>
>> Not true. Back in March it took me less 2-1/2 hours to see a request for
>> a 0.14 version, update my git repo, merge master onto ehci, fix merges,
>> test and send out:
>>
>> http://comments.gmane.org/gmane.comp.emulators.kvm.devel/68898
>>
>> Again, most of the changes are to the EHCI code. The rest are
>> sprinklings to add the adapter and adhere to USB API.
> 
> And it totally ignores the usb changes which went into 0.14 to prepare
> the qemu usb subsystem for higher speeds.  There is no such thing like a
> v1 or v2 usb bus.
> 
> You've mailed out a single monster patch instead of a patch series. That
> is the problem with merges:  You can trivially get a full diff against
> master, but you can't easily get a nicely review-able patch series.

Come on Gerd. That was not an attempt to get it included. Someone asked
for a patch and I took the existing tree, merged with latest and through
out the patch. The v1/v2 is not in Jan's tree. That's a hack I have
locally to have mixed devices. What is shows is that is not a big deal
to move to latest code from Jan's last update in November.

> 
> I've tried to rebase the ehci tree.  First, to fix the patch series
> issue.  Second to adapt patches to qemu changes, to make the patch
> series bisect-able.  Also to drop obsolete bits.  That didn't work out
> as described.  You didn't even try.

Right, you did not even try to work with Jan's tree. There is no reason
the EHCI code could not have been brought in to qemu that way.


David


> 
> I'm not impressed.
> 
> cheers,
>   Gerd
>
Gerd Hoffmann May 17, 2011, 2:36 p.m. UTC | #7
Hi,

> Right, you did not even try to work with Jan's tree. There is no reason
> the EHCI code could not have been brought in to qemu that way.

The usual way to review & merge code is to send a patch series against 
recent master to qemu-devel, optionally with a git tree to pull from.
So I tried to do that for ehci by merging & rebasing Jan's tree.  Didn't 
work out as described multiple times.

Using Jan's tree as-is isn't an option IMHO.  The patches didn't got a 
serious review @ qemu-devel.  IIRC the tree was more meant as "collect 
bits here so they doesn't get lost" not as "this stuff is ready for merge".

cheers,
   Gerd
Gerd Hoffmann May 17, 2011, 3:02 p.m. UTC | #8
Hi,

> (And by the way, where are the focused patches for each, especially the
> last one - nuking the 8kHz code?

It's squashed in, like everything else.

> We know that it worked on linux and
> that printers, scanners and storage devices worked ok (mostly).

8 kHz is insane.

I looked closely while trying to make 8 kHz a runtime option instead of 
a compile time option, then decided to drop it altogether as it is 
totally pointless.  qemu simply can't handle that wakeup rate.  It maxed 
out at ~3 kHz wakeups in my tests.  And it burns tons of CPU time.

I also don't see what it would buy us.  We can wakeup with 1 kHz rate 
(maybe even lower), then emulate 8 (or more) microframes each time.

Throughput issues (guess this is the reason to try 8kHz wakeups) need to 
be addressed by modeling data pipes in the usb system instead of playing 
ping-pong between EHCI and USB device emulation for each single usb 
packet, at 8 kHz.

I see the ehci merge just as very first step.  USB 2.0 (and 3.0) support 
in qemu still has a loooooooong way to go.

cheers,
   Gerd
David S. Ahern May 17, 2011, 5 p.m. UTC | #9
On 05/17/11 01:20, Gerd Hoffmann wrote:
>> On 05/16/11 13:56, Gerd Hoffmann wrote:
>>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>>
>>> Based on git://git.kiszka.org/qemu.git ehci
>>>
>>> Changes:
>>>    - Adapt to recent changes in the usb subsystem.
>>>    - Don't create device automagically, use -device instead.
>>>    - Add quickstart text file, see docs/usb2.txt.
>>>    - A bunch of codestyle fixups.
>>>    - Add authors+contributers list.
>>>    - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>>      wakeup rate anyway.
>>>    - A few bug fixes.
>>>
>>> Signed-off-by: Gerd Hoffmann<kraxel@redhat.com>
>>
>> As someone who spent a significant amount of time working on the EHCI
>> code last year I am absolutely not ok with this. The entire contribution
>> history for EHCI lost - and for no reason.
> 
> There is a reason.  I've tried to keep the history, but it was a big
> mess with conflicts and build errors due to ehci being out-of-tree for a
> loooooong time.
> 
>> The inclusion of EHCI into
>> qemu can be done in such a way as to maintain the history.
> 
> Prove it.  Give me a git tree with ehci history I can use as replacement
> for patch 18 and I'll pull it in.

To come back to this one: Back in December when you got a hankering to
to be the USB maintainer Jan could have created a pull request that you
sucked into your repo. I am not a git guru, but I believe the syntax is
something like:

git request-pull master git://git.kiszka.org/qemu.git


At this point you clearly have no motivation or intention to save the
history. The least you can do is summarize it with a better commit
message. One that references the original code and the efforts by Mark
Burkley. I believe this is the original code:

http://lists.gnu.org/archive/html/qemu-devel/2008-10/msg00764.html

Followed by contributions by various folks and my efforts to make the
code usable - validated by the fact that for a linux host USB storage
devices worked (mostly) as well as printers and scanners. There are
about 16 commits to usb-ehci; surely that is not too cumbersome to read
the commit logs and properly summarize.

David


> 
> cheers,
>   Gerd
>
David S. Ahern May 17, 2011, 5:05 p.m. UTC | #10
On 05/17/11 09:02, Gerd Hoffmann wrote:
> 
>   Hi,
> 
>> (And by the way, where are the focused patches for each, especially the
>> last one - nuking the 8kHz code?
> 
> It's squashed in, like everything else.
> 
>> We know that it worked on linux and
>> that printers, scanners and storage devices worked ok (mostly).
> 
> 8 kHz is insane.
> 
> I looked closely while trying to make 8 kHz a runtime option instead of
> a compile time option, then decided to drop it altogether as it is
> totally pointless.  qemu simply can't handle that wakeup rate.  It maxed
> out at ~3 kHz wakeups in my tests.  And it burns tons of CPU time.

Our mileage varies. While CPU usage was high (30%'ish) when the device
was in use, the controller was stopped when devices are not accessed.
All in all my laptop was billowing smoke while using it.

> 
> I also don't see what it would buy us.  We can wakeup with 1 kHz rate
> (maybe even lower), then emulate 8 (or more) microframes each time.

How much time have you spent looking at isochronous devices (web cams,
audio streaming, iphones)? Your positive that the 8k code will not be
needed for it? My point is that you prematurely nuked code that is
controlled by a define which allows you to try both paths.

David

> 
> Throughput issues (guess this is the reason to try 8kHz wakeups) need to
> be addressed by modeling data pipes in the usb system instead of playing
> ping-pong between EHCI and USB device emulation for each single usb
> packet, at 8 kHz.


> 
> I see the ehci merge just as very first step.  USB 2.0 (and 3.0) support
> in qemu still has a loooooooong way to go.
> 
> cheers,
>   Gerd
>
Erik Rull May 17, 2011, 5:15 p.m. UTC | #11
David Ahern wrote:
> Come on Gerd. That was not an attempt to get it included. Someone asked
> for a patch and I took the existing tree, merged with latest and through
> out the patch. The v1/v2 is not in Jan's tree. That's a hack I have
> locally to have mixed devices. What is shows is that is not a big deal
> to move to latest code from Jan's last update in November.

I asked for the patch and it works great for 0.14.0 :-) Except some 
parameter stuff that is still in the old command style, but it works.

I'm looking forward for a default USB 2.0 implementation without patches by 
not excluding other community members "already working" work.

Best regards,

Erik
Hans de Goede May 17, 2011, 5:33 p.m. UTC | #12
Hi,

On 05/17/2011 05:02 PM, Gerd Hoffmann wrote:
> Hi,
>
>> (And by the way, where are the focused patches for each, especially the
>> last one - nuking the 8kHz code?
>
> It's squashed in, like everything else.
>
>> We know that it worked on linux and
>> that printers, scanners and storage devices worked ok (mostly).
>
> 8 kHz is insane.
>
> I looked closely while trying to make 8 kHz a runtime option instead of a compile time option, then decided to drop it altogether as it is totally pointless. qemu simply can't handle that wakeup rate. It maxed out at ~3 kHz wakeups in my tests. And it burns tons of CPU time.
>
> I also don't see what it would buy us. We can wakeup with 1 kHz rate (maybe even lower), then emulate 8 (or more) microframes each time.
>

Agreed, I had a hack in my local tree (which I dropped when preparing things for Gerd to pull) which lowered the wakeup
rate for UHCI to 200 and then process 5 frames at a time, this worked fine, including redirection of real usb 1 devices,
including usb 1 webcam and audio devices. I would actually like to discuss making the wakeup rate for
the UHCI controller lower, maybe together with some mechanism for (emulated) input devices to force a wake-up
sooner, so as to not add 4 ms of input latency.

Regards,

Hans
Hans de Goede May 17, 2011, 5:36 p.m. UTC | #13
Hi,

On 05/17/2011 07:05 PM, David Ahern wrote:
>
>
> On 05/17/11 09:02, Gerd Hoffmann wrote:
>>
>>    Hi,
>>
>>> (And by the way, where are the focused patches for each, especially the
>>> last one - nuking the 8kHz code?
>>
>> It's squashed in, like everything else.
>>
>>> We know that it worked on linux and
>>> that printers, scanners and storage devices worked ok (mostly).
>>
>> 8 kHz is insane.
>>
>> I looked closely while trying to make 8 kHz a runtime option instead of
>> a compile time option, then decided to drop it altogether as it is
>> totally pointless.  qemu simply can't handle that wakeup rate.  It maxed
>> out at ~3 kHz wakeups in my tests.  And it burns tons of CPU time.
>
> Our mileage varies. While CPU usage was high (30%'ish) when the device
> was in use, the controller was stopped when devices are not accessed.
> All in all my laptop was billowing smoke while using it.
>
>>
>> I also don't see what it would buy us.  We can wakeup with 1 kHz rate
>> (maybe even lower), then emulate 8 (or more) microframes each time.
>
> How much time have you spent looking at isochronous devices (web cams,
> audio streaming, iphones)? Your positive that the 8k code will not be
> needed for it?

Very likely it won't be needed both the guest OS, as well as any
emulated hardware (or redirection code) will be doing some amount of
buffering. Normal bufferering for usb video devices is 32 iso packets
per urb and then 2-4 queued urbs, so that means that the OS does not
even ask to be bothered with iso transfers till 32 of them have completed,
or in other words 4 ms have passed in the usb 2 case with an endpoint
with the lowest possible interval.

Regards,

Hans
David S. Ahern May 17, 2011, 5:43 p.m. UTC | #14
On 05/17/11 11:36, Hans de Goede wrote:
> Very likely it won't be needed both the guest OS, as well as any
> emulated hardware (or redirection code) will be doing some amount of
> buffering. Normal bufferering for usb video devices is 32 iso packets
> per urb and then 2-4 queued urbs, so that means that the OS does not
> even ask to be bothered with iso transfers till 32 of them have completed,
> or in other words 4 ms have passed in the usb 2 case with an endpoint
> with the lowest possible interval.

I got audio streaming working nicely through UHCI in early 2010. The
qemu patch is:
http://www.mail-archive.com/kvm@vger.kernel.org/msg28185.html

I noticed a lot of timing issues related to when the UHCI frame timer
ran and saw the pending requests from the guest. If they lose sync (ie.,
the guest is not appending them before the qemu code passes that frame),
you get a 1+ second delay as the UHCI code in qemu cycled through the
frames coming back to the frame holding the request from the guest OS.
One of the key changes for the audio streaming to work was setting the
expire_time  immediately and not at the end of the frame_timer function:

 static void uhci_frame_timer(void *opaque)
 {
     UHCIState *s = opaque;
-    int64_t expire_time;
+
+    /* prepare the timer for the next frame */
+    s->expire_time += (get_ticks_per_sec() / FRAME_TIMER_FREQ);

David

> 
> Regards,
> 
> Hans
Hans de Goede May 17, 2011, 6:44 p.m. UTC | #15
Hi,

On 05/17/2011 07:43 PM, David Ahern wrote:
> On 05/17/11 11:36, Hans de Goede wrote:
>> Very likely it won't be needed both the guest OS, as well as any
>> emulated hardware (or redirection code) will be doing some amount of
>> buffering. Normal bufferering for usb video devices is 32 iso packets
>> per urb and then 2-4 queued urbs, so that means that the OS does not
>> even ask to be bothered with iso transfers till 32 of them have completed,
>> or in other words 4 ms have passed in the usb 2 case with an endpoint
>> with the lowest possible interval.
>
> I got audio streaming working nicely through UHCI in early 2010. The
> qemu patch is:
> http://www.mail-archive.com/kvm@vger.kernel.org/msg28185.html
>
> I noticed a lot of timing issues related to when the UHCI frame timer
> ran and saw the pending requests from the guest.

Given the nature of isoc data streams, tricks like you did can only
go so far. If you look at the patches which I did for isochroneous
data buffering, which are in kraxel's latest usb pull request
(tested with usb 1 webcams and audio devices), you see that they add a
small fifo between the real device and the emulated uhci controller,
this allows to handle iso request synchronously by simply
taking the next packet from the buffer for input streams, or storing it
into the buffer for output streams.

With these patches in place I can significantly lower the wakeup rate
of the UHCI emulation (and handle multiple frames at once on wakeup
without any ill effects and a nice drop in CPU load.

Note that once every while a packet will get lost since normally a usb
device is synced to the usb frame timer, but the real and emulated frame timer
are not synced, so eventually a packet will get lost.

Regards,

Hans
David S. Ahern May 17, 2011, 7:10 p.m. UTC | #16
On 05/17/11 12:44, Hans de Goede wrote:
> Hi,
> 
> On 05/17/2011 07:43 PM, David Ahern wrote:
>> On 05/17/11 11:36, Hans de Goede wrote:
>>> Very likely it won't be needed both the guest OS, as well as any
>>> emulated hardware (or redirection code) will be doing some amount of
>>> buffering. Normal bufferering for usb video devices is 32 iso packets
>>> per urb and then 2-4 queued urbs, so that means that the OS does not
>>> even ask to be bothered with iso transfers till 32 of them have
>>> completed,
>>> or in other words 4 ms have passed in the usb 2 case with an endpoint
>>> with the lowest possible interval.
>>
>> I got audio streaming working nicely through UHCI in early 2010. The
>> qemu patch is:
>> http://www.mail-archive.com/kvm@vger.kernel.org/msg28185.html
>>
>> I noticed a lot of timing issues related to when the UHCI frame timer
>> ran and saw the pending requests from the guest.
> 
> Given the nature of isoc data streams, tricks like you did can only
> go so far. If you look at the patches which I did for isochroneous
> data buffering, which are in kraxel's latest usb pull request
> (tested with usb 1 webcams and audio devices), you see that they add a
> small fifo between the real device and the emulated uhci controller,
> this allows to handle iso request synchronously by simply
> taking the next packet from the buffer for input streams, or storing it
> into the buffer for output streams.

interesting.

> 
> With these patches in place I can significantly lower the wakeup rate
> of the UHCI emulation (and handle multiple frames at once on wakeup
> without any ill effects and a nice drop in CPU load.

that's good.

> 
> Note that once every while a packet will get lost since normally a usb
> device is synced to the usb frame timer, but the real and emulated frame
> timer
> are not synced, so eventually a packet will get lost.

I noticed that as well. The guest uhci driver (linux at least) guesses
where to put the initial request (10 frames out as I recall). From there
the requests need to flow - responses come into the guest, requests are
appended to the stream.

One missed packet in the guest causes the driver to "start over" -- ie,
it's no longer a stream, but an initial request placed 10 frames out.
That causes an ugly jitter, and if the qemu controller happens to be
past the '10 frames out' guess by the OS you get a 1+ sec gap of silence.

David

> 
> Regards,
> 
> Hans
David S. Ahern May 17, 2011, 7:18 p.m. UTC | #17
On 05/17/11 13:10, David Ahern wrote:
>> Note that once every while a packet will get lost since normally a usb
>> device is synced to the usb frame timer, but the real and emulated frame
>> timer
>> are not synced, so eventually a packet will get lost.
> 
> I noticed that as well. The guest uhci driver (linux at least) guesses
> where to put the initial request (10 frames out as I recall). From there
> the requests need to flow - responses come into the guest, requests are
> appended to the stream.
> 
> One missed packet in the guest causes the driver to "start over" -- ie,
> it's no longer a stream, but an initial request placed 10 frames out.
> That causes an ugly jitter, and if the qemu controller happens to be
> past the '10 frames out' guess by the OS you get a 1+ sec gap of silence.

Oh yea, as I mentioned in the URL modifying the guest driver to have
more outstanding URBs (at least 4 URBs with 5 packets per URB) was a key
aspect of the solution as well -- keeping the guest OS and controller
from losing sync.

David

> 
> David
> 
>>
>> Regards,
>>
>> Hans
> 
>
Blue Swirl May 17, 2011, 8:39 p.m. UTC | #18
On Tue, May 17, 2011 at 5:47 AM, David Ahern <daahern@cisco.com> wrote:
>
>
> On 05/16/11 13:56, Gerd Hoffmann wrote:
>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>
>> Based on git://git.kiszka.org/qemu.git ehci
>>
>> Changes:
>>   - Adapt to recent changes in the usb subsystem.
>>   - Don't create device automagically, use -device instead.
>>   - Add quickstart text file, see docs/usb2.txt.
>>   - A bunch of codestyle fixups.
>>   - Add authors+contributers list.
>>   - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>     wakeup rate anyway.
>>   - A few bug fixes.
>>
>> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
>
> As someone who spent a significant amount of time working on the EHCI
> code last year I am absolutely not ok with this. The entire contribution
> history for EHCI lost - and for no reason. The inclusion of EHCI into
> qemu can be done in such a way as to maintain the history.

The development history (implement a feature, fix bugs, implement
another, fix bugs etc) is not interesting and pulling it would make
bisection and other bugfixing more difficult. The patches should not
add known broken features and fix them next.

Instead, patches should add one simple, bug free feature at a time.
This also makes it possible for some of them to be committed before
others, while others may get rejected and may need major rework. The
patches for EHCI are just like other patches.
David S. Ahern May 17, 2011, 8:51 p.m. UTC | #19
On 05/17/11 14:39, Blue Swirl wrote:
> The development history (implement a feature, fix bugs, implement
> another, fix bugs etc) is not interesting and pulling it would make
> bisection and other bugfixing more difficult. The patches should not
> add known broken features and fix them next.
> 
> Instead, patches should add one simple, bug free feature at a time.
> This also makes it possible for some of them to be committed before
> others, while others may get rejected and may need major rework. The
> patches for EHCI are just like other patches.

Jan's tree is bisectable. After the initial import of the EHCI code
there was one mass diff which mainly went after cleanups and from there
small, targeted patches.

David
Hans de Goede May 18, 2011, 8:25 a.m. UTC | #20
Hi,

On 05/17/2011 09:18 PM, David Ahern wrote:
>
>
> On 05/17/11 13:10, David Ahern wrote:
>>> Note that once every while a packet will get lost since normally a usb
>>> device is synced to the usb frame timer, but the real and emulated frame
>>> timer
>>> are not synced, so eventually a packet will get lost.
>>
>> I noticed that as well. The guest uhci driver (linux at least) guesses
>> where to put the initial request (10 frames out as I recall). From there
>> the requests need to flow - responses come into the guest, requests are
>> appended to the stream.
>>
>> One missed packet in the guest causes the driver to "start over" -- ie,
>> it's no longer a stream, but an initial request placed 10 frames out.
>> That causes an ugly jitter, and if the qemu controller happens to be
>> past the '10 frames out' guess by the OS you get a 1+ sec gap of silence.
>
> Oh yea, as I mentioned in the URL modifying the guest driver to have
> more outstanding URBs (at least 4 URBs with 5 packets per URB) was a key
> aspect of the solution as well -- keeping the guest OS and controller
> from losing sync.

With the fifo now in place this should no longer be necessary. I don't know
if you know, but I've been working on qemu usb stuff for a while now,
mostly focusing on usb redirection over the network. I've been distracted
by some other work for the last 2 months, but I hope to return to working
on this full time again now. Currently my work is in bad need of syncing
with the latest qemu. Once that is done I'll likely put out a call
for some early testers. Any feedback would be very welcome. My isoc
buffering fixes for usb-redirection on the host itself were mostly
done to get a feel for all this, and because if I could not get this
to work locally, it certainly would not work over the network.

Regards,

Hans
Andreas Färber May 21, 2011, 10:34 a.m. UTC | #21
Am 17.05.2011 um 22:39 schrieb Blue Swirl:

> On Tue, May 17, 2011 at 5:47 AM, David Ahern <daahern@cisco.com>  
> wrote:
>> On 05/16/11 13:56, Gerd Hoffmann wrote:
>>> This patch finally merges the EHCI host adapter aka USB 2.0 support.
>>>
>>> Based on git://git.kiszka.org/qemu.git ehci
>>>
>>> Changes:
>>>   - Adapt to recent changes in the usb subsystem.
>>>   - Don't create device automagically, use -device instead.
>>>   - Add quickstart text file, see docs/usb2.txt.
>>>   - A bunch of codestyle fixups.
>>>   - Add authors+contributers list.
>>>   - Zap EHCI_NOMICROFRAMES, qemu can't handle a 8 kHz
>>>     wakeup rate anyway.
>>>   - A few bug fixes.
>>>
>>> Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
>>
>> As someone who spent a significant amount of time working on the EHCI
>> code last year I am absolutely not ok with this. The entire  
>> contribution
>> history for EHCI lost - and for no reason. The inclusion of EHCI into
>> qemu can be done in such a way as to maintain the history.
>
> The development history (implement a feature, fix bugs, implement
> another, fix bugs etc) is not interesting and pulling it would make
> bisection and other bugfixing more difficult. The patches should not
> add known broken features and fix them next.
>
> Instead, patches should add one simple, bug free feature at a time.
> This also makes it possible for some of them to be committed before
> others, while others may get rejected and may need major rework. The
> patches for EHCI are just like other patches.

Well, if David and Jan contributed to it, just as for other patches it  
would've been good practice to at least Cc: them. That's a way to  
carry a contribution history even if patches are reworked.

Reminds me of Jonathan Corbet's FOSDEM keynote about kernel  
contributions: In the end it's not just about fixing a problem; if you  
liberally recycle other people's patches and commit them as your own,  
you risk losing contributors.

Andreas
diff mbox

Patch

diff --git a/Makefile.objs b/Makefile.objs
index 4478c61..90838f6 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -193,6 +193,7 @@  hw-obj-$(CONFIG_PCSPK) += pcspk.o
 hw-obj-$(CONFIG_PCKBD) += pckbd.o
 hw-obj-$(CONFIG_USB_UHCI) += usb-uhci.o
 hw-obj-$(CONFIG_USB_OHCI) += usb-ohci.o
+hw-obj-$(CONFIG_USB_EHCI) += usb-ehci.o
 hw-obj-$(CONFIG_FDC) += fdc.o
 hw-obj-$(CONFIG_ACPI) += acpi.o acpi_piix4.o
 hw-obj-$(CONFIG_APM) += pm_smbus.o apm.o
diff --git a/default-configs/pci.mak b/default-configs/pci.mak
index 0471efb..22bd350 100644
--- a/default-configs/pci.mak
+++ b/default-configs/pci.mak
@@ -3,6 +3,7 @@  CONFIG_VIRTIO_PCI=y
 CONFIG_VIRTIO=y
 CONFIG_USB_UHCI=y
 CONFIG_USB_OHCI=y
+CONFIG_USB_EHCI=y
 CONFIG_NE2000_PCI=y
 CONFIG_EEPRO100_PCI=y
 CONFIG_PCNET_PCI=y
diff --git a/docs/usb2.txt b/docs/usb2.txt
new file mode 100644
index 0000000..b283c13
--- /dev/null
+++ b/docs/usb2.txt
@@ -0,0 +1,38 @@ 
+
+USB 2.0 Quick Start
+===================
+
+The QEMU EHCI Adapter does *not* support companion controllers.  That
+implies there are two completely separate USB busses: One USB 1.1 bus
+driven by the UHCI controller and one USB 2.0 bus driven by the EHCI
+controller.  Devices must be attached to the correct controller
+manually.
+
+The '-usb' switch will make qemu create the UHCI controller as part of
+the PIIX3 chipset.  The USB 1.1 bus will carry the name "usb.0".
+
+You can use the standard -device switch to add a EHCI controller to
+your virtual machine.  It is strongly recommended to specify an ID for
+the controller so the USB 2.0 bus gets a individual name, for example
+'-device usb-ehci,id=ehci".  This will give you a USB 2.0 bus named
+"ehci.0".
+
+I strongly recomment to also use -device to attach usb devices because
+you can specify the bus they should be attached to this way.  Here is
+a complete example:
+
+    qemu -M pc ${otheroptions}                           \
+        -drive if=none,id=usbstick,file=/path/to/image   \
+        -usb                                             \
+        -device usb-ehci,id=ehci                         \
+        -device usb-tablet,bus=usb.0                     \
+        -device usb-storage,bus=ehci.0,drive=usbstick
+
+This attaches a usb tablet to the UHCI adapter and a usb mass storage
+device to the EHCI adapter.
+
+enjoy,
+  Gerd
+
+--
+Gerd Hoffmann <kraxel@redhat.com>
diff --git a/hw/pci_ids.h b/hw/pci_ids.h
index ea3418c..d9457ed 100644
--- a/hw/pci_ids.h
+++ b/hw/pci_ids.h
@@ -100,6 +100,7 @@ 
 #define PCI_VENDOR_ID_INTEL              0x8086
 #define PCI_DEVICE_ID_INTEL_82441        0x1237
 #define PCI_DEVICE_ID_INTEL_82801AA_5    0x2415
+#define PCI_DEVICE_ID_INTEL_82801D       0x24CD
 #define PCI_DEVICE_ID_INTEL_ESB_9        0x25ab
 #define PCI_DEVICE_ID_INTEL_82371SB_0    0x7000
 #define PCI_DEVICE_ID_INTEL_82371SB_1    0x7010
diff --git a/hw/usb-ehci.c b/hw/usb-ehci.c
new file mode 100644
index 0000000..fbd818f
--- /dev/null
+++ b/hw/usb-ehci.c
@@ -0,0 +1,2057 @@ 
+/*
+ * QEMU USB EHCI Emulation
+ *
+ * Copyright(c) 2008  Emutex Ltd. (address@hidden)
+ *
+ * Authors & Contributors:
+ *    Mark Burkley
+ *    Niels de Vos
+ *    David S. Ahern
+ *    Kevin Wolf
+ *    Jan Kiszka
+ *    Vincent Palatin
+ *    Gerd Hoffmann
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or(at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ * TODO:
+ *  o Downstream port handoff
+ */
+
+#include "hw.h"
+#include "qemu-timer.h"
+#include "usb.h"
+#include "pci.h"
+#include "usb-ehci.h"
+#include "monitor.h"
+
+#define EHCI_DEBUG   0
+#define STATE_DEBUG  0       /* state transitions  */
+
+#if EHCI_DEBUG || STATE_DEBUG
+#define DPRINTF printf
+#else
+#define DPRINTF(...)
+#endif
+
+#if STATE_DEBUG
+#define DPRINTF_ST DPRINTF
+#else
+#define DPRINTF_ST(...)
+#endif
+
+/* internal processing - reset HC to try and recover */
+#define USB_RET_PROCERR   (-99)
+
+#define MMIO_SIZE        0x1000
+
+/* Capability Registers Base Address - section 2.2 */
+#define CAPREGBASE       0x0000
+#define CAPLENGTH        CAPREGBASE + 0x0000  // 1-byte, 0x0001 reserved
+#define HCIVERSION       CAPREGBASE + 0x0002  // 2-bytes, i/f version #
+#define HCSPARAMS        CAPREGBASE + 0x0004  // 4-bytes, structural params
+#define HCCPARAMS        CAPREGBASE + 0x0008  // 4-bytes, capability params
+#define EECP             HCCPARAMS + 1
+#define HCSPPORTROUTE1   CAPREGBASE + 0x000c
+#define HCSPPORTROUTE2   CAPREGBASE + 0x0010
+
+#define OPREGBASE        0x0020        // Operational Registers Base Address
+
+#define USBCMD           OPREGBASE + 0x0000
+#define USBCMD_RUNSTOP   (1 << 0)      // run / Stop
+#define USBCMD_HCRESET   (1 << 1)      // HC Reset
+#define USBCMD_FLS       (3 << 2)      // Frame List Size
+#define USBCMD_FLS_SH    2             // Frame List Size Shift
+#define USBCMD_PSE       (1 << 4)      // Periodic Schedule Enable
+#define USBCMD_ASE       (1 << 5)      // Asynch Schedule Enable
+#define USBCMD_IAAD      (1 << 6)      // Int Asynch Advance Doorbell
+#define USBCMD_LHCR      (1 << 7)      // Light Host Controller Reset
+#define USBCMD_ASPMC     (3 << 8)      // Async Sched Park Mode Count
+#define USBCMD_ASPME     (1 << 11)     // Async Sched Park Mode Enable
+#define USBCMD_ITC       (0x7f << 16)  // Int Threshold Control
+#define USBCMD_ITC_SH    16            // Int Threshold Control Shift
+
+#define USBSTS           OPREGBASE + 0x0004
+#define USBSTS_RO_MASK   0x0000003f
+#define USBSTS_INT       (1 << 0)      // USB Interrupt
+#define USBSTS_ERRINT    (1 << 1)      // Error Interrupt
+#define USBSTS_PCD       (1 << 2)      // Port Change Detect
+#define USBSTS_FLR       (1 << 3)      // Frame List Rollover
+#define USBSTS_HSE       (1 << 4)      // Host System Error
+#define USBSTS_IAA       (1 << 5)      // Interrupt on Async Advance
+#define USBSTS_HALT      (1 << 12)     // HC Halted
+#define USBSTS_REC       (1 << 13)     // Reclamation
+#define USBSTS_PSS       (1 << 14)     // Periodic Schedule Status
+#define USBSTS_ASS       (1 << 15)     // Asynchronous Schedule Status
+
+/*
+ *  Interrupt enable bits correspond to the interrupt active bits in USBSTS
+ *  so no need to redefine here.
+ */
+#define USBINTR              OPREGBASE + 0x0008
+#define USBINTR_MASK         0x0000003f
+
+#define FRINDEX              OPREGBASE + 0x000c
+#define CTRLDSSEGMENT        OPREGBASE + 0x0010
+#define PERIODICLISTBASE     OPREGBASE + 0x0014
+#define ASYNCLISTADDR        OPREGBASE + 0x0018
+#define ASYNCLISTADDR_MASK   0xffffffe0
+
+#define CONFIGFLAG           OPREGBASE + 0x0040
+
+#define PORTSC               (OPREGBASE + 0x0044)
+#define PORTSC_BEGIN         PORTSC
+#define PORTSC_END           (PORTSC + 4 * NB_PORTS)
+/*
+ * Bits that are reserverd or are read-only are masked out of values
+ * written to us by software
+ */
+#define PORTSC_RO_MASK       0x007021c5
+#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_WKCN_E        (1 << 20)    // Wake on Connect Enable
+#define PORTSC_PTC           (15 << 16)   // Port Test Control
+#define PORTSC_PTC_SH        16           // Port Test Control shift
+#define PORTSC_PIC           (3 << 14)    // Port Indicator Control
+#define PORTSC_PIC_SH        14           // Port Indicator Control Shift
+#define PORTSC_POWNER        (1 << 13)    // Port Owner
+#define PORTSC_PPOWER        (1 << 12)    // Port Power
+#define PORTSC_LINESTAT      (3 << 10)    // Port Line Status
+#define PORTSC_LINESTAT_SH   10           // Port Line Status Shift
+#define PORTSC_PRESET        (1 << 8)     // Port Reset
+#define PORTSC_SUSPEND       (1 << 7)     // Port Suspend
+#define PORTSC_FPRES         (1 << 6)     // Force Port Resume
+#define PORTSC_OCC           (1 << 5)     // Over Current Change
+#define PORTSC_OCA           (1 << 4)     // Over Current Active
+#define PORTSC_PEDC          (1 << 3)     // Port Enable/Disable Change
+#define PORTSC_PED           (1 << 2)     // Port Enable/Disable
+#define PORTSC_CSC           (1 << 1)     // Connect Status Change
+#define PORTSC_CONNECT       (1 << 0)     // Current Connect Status
+
+#define FRAME_TIMER_FREQ 1000
+#define FRAME_TIMER_USEC (1000000 / FRAME_TIMER_FREQ)
+
+#define NB_MAXINTRATE    8        // Max rate at which controller issues ints
+#define NB_PORTS         4        // 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
+
+/*  Internal periodic / asynchronous schedule state machine states
+ */
+typedef enum {
+    EST_INACTIVE = 1000,
+    EST_ACTIVE,
+    EST_EXECUTING,
+    EST_SLEEPING,
+    /*  The following states are internal to the state machine function
+    */
+    EST_WAITLISTHEAD,
+    EST_FETCHENTRY,
+    EST_FETCHQH,
+    EST_FETCHITD,
+    EST_ADVANCEQUEUE,
+    EST_FETCHQTD,
+    EST_EXECUTE,
+    EST_WRITEBACK,
+    EST_HORIZONTALQH
+} EHCI_STATES;
+
+/* macros for accessing fields within next link pointer entry */
+#define NLPTR_GET(x)             ((x) & 0xffffffe0)
+#define NLPTR_TYPE_GET(x)        (((x) >> 1) & 3)
+#define NLPTR_TBIT(x)            ((x) & 1)  // 1=invalid, 0=valid
+
+/* link pointer types */
+#define NLPTR_TYPE_ITD           0     // isoc xfer descriptor
+#define NLPTR_TYPE_QH            1     // queue head
+#define NLPTR_TYPE_STITD         2     // split xaction, isoc xfer descriptor
+#define NLPTR_TYPE_FSTN          3     // frame span traversal node
+
+
+/*  EHCI spec version 1.0 Section 3.3
+ */
+typedef struct EHCIitd {
+    uint32_t next;
+
+    uint32_t transact[8];
+#define ITD_XACT_ACTIVE          (1 << 31)
+#define ITD_XACT_DBERROR         (1 << 30)
+#define ITD_XACT_BABBLE          (1 << 29)
+#define ITD_XACT_XACTERR         (1 << 28)
+#define ITD_XACT_LENGTH_MASK     0x0fff0000
+#define ITD_XACT_LENGTH_SH       16
+#define ITD_XACT_IOC             (1 << 15)
+#define ITD_XACT_PGSEL_MASK      0x00007000
+#define ITD_XACT_PGSEL_SH        12
+#define ITD_XACT_OFFSET_MASK     0x00000fff
+
+    uint32_t bufptr[7];
+#define ITD_BUFPTR_MASK          0xfffff000
+#define ITD_BUFPTR_SH            12
+#define ITD_BUFPTR_EP_MASK       0x00000f00
+#define ITD_BUFPTR_EP_SH         8
+#define ITD_BUFPTR_DEVADDR_MASK  0x0000007f
+#define ITD_BUFPTR_DEVADDR_SH    0
+#define ITD_BUFPTR_DIRECTION     (1 << 11)
+#define ITD_BUFPTR_MAXPKT_MASK   0x000007ff
+#define ITD_BUFPTR_MAXPKT_SH     0
+#define ITD_BUFPTR_MULT_MASK     0x00000003
+} EHCIitd;
+
+/*  EHCI spec version 1.0 Section 3.4
+ */
+typedef struct EHCIsitd {
+    uint32_t next;                  // Standard next link pointer
+    uint32_t epchar;
+#define SITD_EPCHAR_IO              (1 << 31)
+#define SITD_EPCHAR_PORTNUM_MASK    0x7f000000
+#define SITD_EPCHAR_PORTNUM_SH      24
+#define SITD_EPCHAR_HUBADD_MASK     0x007f0000
+#define SITD_EPCHAR_HUBADDR_SH      16
+#define SITD_EPCHAR_EPNUM_MASK      0x00000f00
+#define SITD_EPCHAR_EPNUM_SH        8
+#define SITD_EPCHAR_DEVADDR_MASK    0x0000007f
+
+    uint32_t uframe;
+#define SITD_UFRAME_CMASK_MASK      0x0000ff00
+#define SITD_UFRAME_CMASK_SH        8
+#define SITD_UFRAME_SMASK_MASK      0x000000ff
+
+    uint32_t results;
+#define SITD_RESULTS_IOC              (1 << 31)
+#define SITD_RESULTS_PGSEL            (1 << 30)
+#define SITD_RESULTS_TBYTES_MASK      0x03ff0000
+#define SITD_RESULTS_TYBYTES_SH       16
+#define SITD_RESULTS_CPROGMASK_MASK   0x0000ff00
+#define SITD_RESULTS_CPROGMASK_SH     8
+#define SITD_RESULTS_ACTIVE           (1 << 7)
+#define SITD_RESULTS_ERR              (1 << 6)
+#define SITD_RESULTS_DBERR            (1 << 5)
+#define SITD_RESULTS_BABBLE           (1 << 4)
+#define SITD_RESULTS_XACTERR          (1 << 3)
+#define SITD_RESULTS_MISSEDUF         (1 << 2)
+#define SITD_RESULTS_SPLITXSTATE      (1 << 1)
+
+    uint32_t bufptr[2];
+#define SITD_BUFPTR_MASK              0xfffff000
+#define SITD_BUFPTR_CURROFF_MASK      0x00000fff
+#define SITD_BUFPTR_TPOS_MASK         0x00000018
+#define SITD_BUFPTR_TPOS_SH           3
+#define SITD_BUFPTR_TCNT_MASK         0x00000007
+
+    uint32_t backptr;                 // Standard next link pointer
+} EHCIsitd;
+
+/*  EHCI spec version 1.0 Section 3.5
+ */
+typedef struct EHCIqtd {
+    uint32_t next;                    // Standard next link pointer
+    uint32_t altnext;                 // Standard next link pointer
+    uint32_t token;
+#define QTD_TOKEN_DTOGGLE             (1 << 31)
+#define QTD_TOKEN_TBYTES_MASK         0x7fff0000
+#define QTD_TOKEN_TBYTES_SH           16
+#define QTD_TOKEN_IOC                 (1 << 15)
+#define QTD_TOKEN_CPAGE_MASK          0x00007000
+#define QTD_TOKEN_CPAGE_SH            12
+#define QTD_TOKEN_CERR_MASK           0x00000c00
+#define QTD_TOKEN_CERR_SH             10
+#define QTD_TOKEN_PID_MASK            0x00000300
+#define QTD_TOKEN_PID_SH              8
+#define QTD_TOKEN_ACTIVE              (1 << 7)
+#define QTD_TOKEN_HALT                (1 << 6)
+#define QTD_TOKEN_DBERR               (1 << 5)
+#define QTD_TOKEN_BABBLE              (1 << 4)
+#define QTD_TOKEN_XACTERR             (1 << 3)
+#define QTD_TOKEN_MISSEDUF            (1 << 2)
+#define QTD_TOKEN_SPLITXSTATE         (1 << 1)
+#define QTD_TOKEN_PING                (1 << 0)
+
+    uint32_t bufptr[5];               // Standard buffer pointer
+#define QTD_BUFPTR_MASK               0xfffff000
+} EHCIqtd;
+
+/*  EHCI spec version 1.0 Section 3.6
+ */
+typedef struct EHCIqh {
+    uint32_t next;                    // Standard next link pointer
+
+    /* endpoint characteristics */
+    uint32_t epchar;
+#define QH_EPCHAR_RL_MASK             0xf0000000
+#define QH_EPCHAR_RL_SH               28
+#define QH_EPCHAR_C                   (1 << 27)
+#define QH_EPCHAR_MPLEN_MASK          0x07FF0000
+#define QH_EPCHAR_MPLEN_SH            16
+#define QH_EPCHAR_H                   (1 << 15)
+#define QH_EPCHAR_DTC                 (1 << 14)
+#define QH_EPCHAR_EPS_MASK            0x00003000
+#define QH_EPCHAR_EPS_SH              12
+#define EHCI_QH_EPS_FULL              0
+#define EHCI_QH_EPS_LOW               1
+#define EHCI_QH_EPS_HIGH              2
+#define EHCI_QH_EPS_RESERVED          3
+
+#define QH_EPCHAR_EP_MASK             0x00000f00
+#define QH_EPCHAR_EP_SH               8
+#define QH_EPCHAR_I                   (1 << 7)
+#define QH_EPCHAR_DEVADDR_MASK        0x0000007f
+#define QH_EPCHAR_DEVADDR_SH          0
+
+    /* endpoint capabilities */
+    uint32_t epcap;
+#define QH_EPCAP_MULT_MASK            0xc0000000
+#define QH_EPCAP_MULT_SH              30
+#define QH_EPCAP_PORTNUM_MASK         0x3f800000
+#define QH_EPCAP_PORTNUM_SH           23
+#define QH_EPCAP_HUBADDR_MASK         0x007f0000
+#define QH_EPCAP_HUBADDR_SH           16
+#define QH_EPCAP_CMASK_MASK           0x0000ff00
+#define QH_EPCAP_CMASK_SH             8
+#define QH_EPCAP_SMASK_MASK           0x000000ff
+#define QH_EPCAP_SMASK_SH             0
+
+    uint32_t current_qtd;             // Standard next link pointer
+    uint32_t next_qtd;                // Standard next link pointer
+    uint32_t altnext_qtd;
+#define QH_ALTNEXT_NAKCNT_MASK        0x0000001e
+#define QH_ALTNEXT_NAKCNT_SH          1
+
+    uint32_t token;                   // Same as QTD token
+    uint32_t bufptr[5];               // Standard buffer pointer
+#define BUFPTR_CPROGMASK_MASK         0x000000ff
+#define BUFPTR_FRAMETAG_MASK          0x0000001f
+#define BUFPTR_SBYTES_MASK            0x00000fe0
+#define BUFPTR_SBYTES_SH              5
+} EHCIqh;
+
+/*  EHCI spec version 1.0 Section 3.7
+ */
+typedef struct EHCIfstn {
+    uint32_t next;                    // Standard next link pointer
+    uint32_t backptr;                 // Standard next link pointer
+} EHCIfstn;
+
+typedef struct {
+    PCIDevice dev;
+    qemu_irq irq;
+    target_phys_addr_t mem_base;
+    int mem;
+    int num_ports;
+    /*
+     *  EHCI spec version 1.0 Section 2.3
+     *  Host Controller Operational Registers
+     */
+    union {
+        uint8_t mmio[MMIO_SIZE];
+        struct {
+            uint8_t cap[OPREGBASE];
+            uint32_t usbcmd;
+            uint32_t usbsts;
+            uint32_t usbintr;
+            uint32_t frindex;
+            uint32_t ctrldssegment;
+            uint32_t periodiclistbase;
+            uint32_t asynclistaddr;
+            uint32_t notused[9];
+            uint32_t configflag;
+            uint32_t portsc[NB_PORTS];
+        };
+    };
+    /*
+     *  Internal states, shadow registers, etc
+     */
+    uint32_t sofv;
+    QEMUTimer *frame_timer;
+    int attach_poll_counter;
+    int astate;                        // Current state in asynchronous schedule
+    int pstate;                        // Current state in periodic schedule
+    USBPort ports[NB_PORTS];
+    uint8_t buffer[BUFF_SIZE];
+    uint32_t usbsts_pending;
+
+    /* cached data from guest - needs to be flushed
+     * when guest removes an entry (doorbell, handshake sequence)
+     */
+    EHCIqh qh;             // copy of current QH (being worked on)
+    uint32_t qhaddr;       // address QH read from
+
+    EHCIqtd qtd;           // copy of current QTD (being worked on)
+    uint32_t qtdaddr;      // address QTD read from
+
+    uint32_t itdaddr;      // current ITD
+
+    uint32_t fetch_addr;   // which address to look at next
+
+    USBBus bus;
+    USBPacket usb_packet;
+    int async_complete;
+    uint32_t tbytes;
+    int pid;
+    int exec_status;
+    int isoch_pause;
+    uint32_t last_run_usec;
+    uint32_t frame_end_usec;
+} EHCIState;
+
+#define SET_LAST_RUN_CLOCK(s) \
+    (s)->last_run_usec = qemu_get_clock_ns(vm_clock) / 1000;
+
+/* nifty macros from Arnon's EHCI version  */
+#define get_field(data, field) \
+    (((data) & field##_MASK) >> field##_SH)
+
+#define set_field(data, newval, field) do { \
+    uint32_t val = *data; \
+    val &= ~ field##_MASK; \
+    val |= ((newval) << field##_SH) & field##_MASK; \
+    *data = val; \
+    } while(0)
+
+
+#if EHCI_DEBUG
+static const char *addr2str(unsigned addr)
+{
+    const char *r            = "   unknown";
+    const char *n[] = {
+        [ CAPLENGTH ]        = " CAPLENGTH",
+        [ HCIVERSION ]       = "HCIVERSION",
+        [ HCSPARAMS ]        = " HCSPARAMS",
+        [ HCCPARAMS ]        = " HCCPARAMS",
+        [ USBCMD ]           = "   COMMAND",
+        [ USBSTS ]           = "    STATUS",
+        [ USBINTR ]          = " INTERRUPT",
+        [ FRINDEX ]          = " FRAME IDX",
+        [ PERIODICLISTBASE ] = "P-LIST BASE",
+        [ ASYNCLISTADDR ]    = "A-LIST ADDR",
+        [ PORTSC_BEGIN ...
+          PORTSC_END ]       = "PORT STATUS",
+        [ CONFIGFLAG ]       = "CONFIG FLAG",
+    };
+
+    if (addr < ARRAY_SIZE(n) && n[addr] != NULL) {
+        return n[addr];
+    } else {
+        return r;
+    }
+}
+#endif
+
+
+static inline void ehci_set_interrupt(EHCIState *s, int intr)
+{
+    int level = 0;
+
+    // TODO honour interrupt threshold requests
+
+    s->usbsts |= intr;
+
+    if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
+        level = 1;
+    }
+
+    qemu_set_irq(s->irq, level);
+}
+
+static inline void ehci_record_interrupt(EHCIState *s, int intr)
+{
+    s->usbsts_pending |= intr;
+}
+
+static inline void ehci_commit_interrupt(EHCIState *s)
+{
+    if (!s->usbsts_pending) {
+        return;
+    }
+    ehci_set_interrupt(s, s->usbsts_pending);
+    s->usbsts_pending = 0;
+}
+
+/* Attach or detach a device on root hub */
+
+static void ehci_attach(USBPort *port)
+{
+    EHCIState *s = port->opaque;
+    uint32_t *portsc = &s->portsc[port->index];
+
+    DPRINTF("ehci_attach invoked for index %d, portsc 0x%x, desc %s\n",
+           port->index, *portsc, port->dev->product_desc);
+
+    *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);
+    }
+}
+
+static void ehci_detach(USBPort *port)
+{
+    EHCIState *s = port->opaque;
+    uint32_t *portsc = &s->portsc[port->index];
+
+    DPRINTF("ehci_attach invoked for index %d, portsc 0x%x\n",
+           port->index, *portsc);
+
+    *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);
+    }
+}
+
+/* 4.1 host controller initialization */
+static void ehci_reset(void *opaque)
+{
+    EHCIState *s = opaque;
+    uint8_t *pci_conf;
+    int i;
+
+    pci_conf = s->dev.config;
+
+    memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
+
+    s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
+    s->usbsts = USBSTS_HALT;
+
+    s->astate = EST_INACTIVE;
+    s->pstate = EST_INACTIVE;
+    s->async_complete = 0;
+    s->isoch_pause = -1;
+    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);
+        }
+    }
+}
+
+static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
+{
+    EHCIState *s = ptr;
+    uint32_t val;
+
+    val = s->mmio[addr];
+
+    return val;
+}
+
+static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
+{
+    EHCIState *s = ptr;
+    uint32_t val;
+
+    val = s->mmio[addr] | (s->mmio[addr+1] << 8);
+
+    return val;
+}
+
+static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
+{
+    EHCIState *s = ptr;
+    uint32_t val;
+
+    val = s->mmio[addr] | (s->mmio[addr+1] << 8) |
+          (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24);
+
+    return val;
+}
+
+static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+    fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n");
+    exit(1);
+}
+
+static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+    fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n");
+    exit(1);
+}
+
+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;
+
+    DPRINTF("port_status_write: "
+            "PORTSC (port %d) curr %08X new %08X rw-clear %08X rw %08X\n",
+            port, *portsc, val, (val & PORTSC_RWC_MASK), val & PORTSC_RO_MASK);
+
+    rwc = val & PORTSC_RWC_MASK;
+    val &= PORTSC_RO_MASK;
+
+    // handle_read_write_clear(&val, portsc, PORTSC_PEDC | PORTSC_CSC);
+
+    *portsc &= ~rwc;
+
+    if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
+        DPRINTF("port_status_write: USBTRAN Port %d reset begin\n", port);
+    }
+
+    if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
+        DPRINTF("port_status_write: USBTRAN Port %d reset done\n", port);
+        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) {
+            DPRINTF("port_status_write: "
+                    "Device was connected before reset, clearing CSC bit\n");
+            *portsc &= ~PORTSC_CSC;
+        }
+
+        /*  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;
+    }
+
+    *portsc &= ~PORTSC_RO_MASK;
+    *portsc |= val;
+    DPRINTF("port_status_write: Port %d status set to 0x%08x\n", port, *portsc);
+}
+
+static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+    EHCIState *s = ptr;
+    int i;
+#if EHCI_DEBUG
+    const char *str;
+#endif
+
+    /* Only aligned reads are allowed on OHCI */
+    if (addr & 3) {
+        fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x"
+                TARGET_FMT_plx "\n", addr);
+        return;
+    }
+
+    if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) {
+        handle_port_status_write(s, (addr-PORTSC)/4, val);
+        return;
+    }
+
+    if (addr < OPREGBASE) {
+        fprintf(stderr, "usb-ehci: write attempt to read-only register"
+                TARGET_FMT_plx "\n", addr);
+        return;
+    }
+
+
+    /* Do any register specific pre-write processing here.  */
+#if EHCI_DEBUG
+    str = addr2str((unsigned) addr);
+#endif
+    switch(addr) {
+    case USBCMD:
+        DPRINTF("ehci_mem_writel: USBCMD val=0x%08X, current cmd=0x%08X\n",
+                val, s->usbcmd);
+
+        if ((val & USBCMD_RUNSTOP) && !(s->usbcmd & USBCMD_RUNSTOP)) {
+            DPRINTF("ehci_mem_writel: %s run, clear halt\n", str);
+            qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock));
+            SET_LAST_RUN_CLOCK(s);
+            s->usbsts &= ~USBSTS_HALT;
+        }
+
+        if (!(val & USBCMD_RUNSTOP) && (s->usbcmd & USBCMD_RUNSTOP)) {
+            DPRINTF("                         ** STOP **\n");
+            qemu_del_timer(s->frame_timer);
+            // TODO - should finish out some stuff before setting halt
+            s->usbsts |= USBSTS_HALT;
+        }
+
+        if (val & USBCMD_HCRESET) {
+            DPRINTF("ehci_mem_writel: %s run, resetting\n", str);
+            ehci_reset(s);
+            val &= ~USBCMD_HCRESET;
+        }
+
+        /* not supporting dynamic frame list size at the moment */
+        if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
+            fprintf(stderr, "attempt to set frame list size -- value %d\n",
+                    val & USBCMD_FLS);
+            val &= ~USBCMD_FLS;
+        }
+#if EHCI_DEBUG
+        if ((val & USBCMD_PSE) && !(s->usbcmd & USBCMD_PSE)) {
+            DPRINTF("periodic scheduling enabled\n");
+        }
+        if (!(val & USBCMD_PSE) && (s->usbcmd & USBCMD_PSE)) {
+            DPRINTF("periodic scheduling disabled\n");
+        }
+        if ((val & USBCMD_ASE) && !(s->usbcmd & USBCMD_ASE)) {
+            DPRINTF("asynchronous scheduling enabled\n");
+        }
+        if (!(val & USBCMD_ASE) && (s->usbcmd & USBCMD_ASE)) {
+            DPRINTF("asynchronous scheduling disabled\n");
+        }
+        if ((val & USBCMD_IAAD) && !(s->usbcmd & USBCMD_IAAD)) {
+            DPRINTF("doorbell request received\n");
+        }
+        if ((val & USBCMD_LHCR) && !(s->usbcmd & USBCMD_LHCR)) {
+            DPRINTF("light host controller reset received\n");
+        }
+        if ((val & USBCMD_ITC) != (s->usbcmd & USBCMD_ITC)) {
+            DPRINTF("interrupt threshold control set to %x\n",
+                    (val & USBCMD_ITC)>>USBCMD_ITC_SH);
+        }
+#endif
+        break;
+
+
+    case USBSTS:
+        val &= USBSTS_RO_MASK;              // bits 6 thru 31 are RO
+        DPRINTF("ehci_mem_writel: %s RWC set to 0x%08X\n", str, val);
+
+        val = (s->usbsts &= ~val);         // bits 0 thru 5 are R/WC
+
+        DPRINTF("ehci_mem_writel: %s updating interrupt condition\n", str);
+        ehci_set_interrupt(s, 0);
+        break;
+
+
+    case USBINTR:
+        val &= USBINTR_MASK;
+        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+        break;
+
+    case FRINDEX:
+        s->sofv = val >> 3;
+        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+        break;
+
+    case CONFIGFLAG:
+        DPRINTF("ehci_mem_writel: %s set to 0x%08X\n", str, val);
+        val &= 0x1;
+        if (val) {
+            for(i = 0; i < NB_PORTS; i++)
+                s->portsc[i] &= ~PORTSC_POWNER;
+        }
+        break;
+
+    case PERIODICLISTBASE:
+        if ((s->usbcmd & USBCMD_PSE) && (s->usbcmd & USBCMD_RUNSTOP)) {
+            fprintf(stderr,
+              "ehci: PERIODIC list base register set while periodic schedule\n"
+              "      is enabled and HC is enabled\n");
+        }
+        DPRINTF("ehci_mem_writel: P-LIST BASE set to 0x%08X\n", val);
+        break;
+
+    case ASYNCLISTADDR:
+        if ((s->usbcmd & USBCMD_ASE) && (s->usbcmd & USBCMD_RUNSTOP)) {
+            fprintf(stderr,
+              "ehci: ASYNC list address register set while async schedule\n"
+              "      is enabled and HC is enabled\n");
+        }
+        DPRINTF("ehci_mem_writel: A-LIST ADDR set to 0x%08X\n", val);
+        break;
+    }
+
+    *(uint32_t *)(&s->mmio[addr]) = 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)
+{
+    int i;
+
+    for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+        cpu_physical_memory_rw(addr,(uint8_t *)buf, sizeof(*buf), 0);
+        *buf = le32_to_cpu(*buf);
+    }
+
+    return 1;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(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);
+    }
+
+    return 1;
+}
+
+// 4.10.2
+
+static int ehci_qh_do_overlay(EHCIState *ehci, EHCIqh *qh, EHCIqtd *qtd)
+{
+    int i;
+    int dtoggle;
+    int ping;
+    int eps;
+    int reload;
+
+    // remember values in fields to preserve in qh after overlay
+
+    dtoggle = qh->token & QTD_TOKEN_DTOGGLE;
+    ping    = qh->token & QTD_TOKEN_PING;
+
+    DPRINTF("setting qh.current from %08X to 0x%08X\n", qh->current_qtd,
+            ehci->qtdaddr);
+    qh->current_qtd = ehci->qtdaddr;
+    qh->next_qtd    = qtd->next;
+    qh->altnext_qtd = qtd->altnext;
+    qh->token       = qtd->token;
+
+
+    eps = get_field(qh->epchar, QH_EPCHAR_EPS);
+    if (eps == EHCI_QH_EPS_HIGH) {
+        qh->token &= ~QTD_TOKEN_PING;
+        qh->token |= ping;
+    }
+
+    reload = get_field(qh->epchar, QH_EPCHAR_RL);
+    set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
+
+    for (i = 0; i < 5; i++) {
+        qh->bufptr[i] = qtd->bufptr[i];
+    }
+
+    if (!(qh->epchar & QH_EPCHAR_DTC)) {
+        // preserve QH DT bit
+        qh->token &= ~QTD_TOKEN_DTOGGLE;
+        qh->token |= dtoggle;
+    }
+
+    qh->bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
+    qh->bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
+
+    put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+    return 0;
+}
+
+static int ehci_buffer_rw(uint8_t *buffer, EHCIqh *qh, int bytes, int rw)
+{
+    int bufpos = 0;
+    int cpage, offset;
+    uint32_t head;
+    uint32_t tail;
+
+
+    if (!bytes) {
+        return 0;
+    }
+
+    cpage = get_field(qh->token, QTD_TOKEN_CPAGE);
+    if (cpage > 4) {
+        fprintf(stderr, "cpage out of range (%d)\n", cpage);
+        return USB_RET_PROCERR;
+    }
+
+    offset = qh->bufptr[0] & ~QTD_BUFPTR_MASK;
+    DPRINTF("ehci_buffer_rw: %sing %d bytes %08x cpage %d offset %d\n",
+           rw ? "writ" : "read", bytes, qh->bufptr[0], cpage, offset);
+
+    do {
+        /* start and end of this page */
+        head = 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;
+        }
+
+        DPRINTF("DATA %s cpage:%d head:%08X tail:%08X target:%08X\n",
+                rw ? "WRITE" : "READ ", cpage, head, tail, bufpos);
+
+        cpu_physical_memory_rw(head, &buffer[bufpos], tail - head, rw);
+
+        bufpos += (tail - head);
+        bytes -= (tail - head);
+
+        if (bytes > 0) {
+            cpage++;
+            offset = 0;
+        }
+    } while (bytes > 0);
+
+    /* save cpage */
+    set_field(&qh->token, cpage, QTD_TOKEN_CPAGE);
+
+    /* save offset into cpage */
+    offset = tail - head;
+    qh->bufptr[0] &= ~QTD_BUFPTR_MASK;
+    qh->bufptr[0] |= offset;
+
+    return 0;
+}
+
+static void ehci_async_complete_packet(USBDevice *dev, USBPacket *packet)
+{
+    EHCIState *ehci = container_of(packet, EHCIState, usb_packet);
+
+    DPRINTF("Async packet complete\n");
+    ehci->async_complete = 1;
+    ehci->exec_status = packet->len;
+}
+
+static int ehci_execute_complete(EHCIState *ehci, EHCIqh *qh, int ret)
+{
+    int c_err, reload;
+
+    if (ret == USB_RET_ASYNC && !ehci->async_complete) {
+        DPRINTF("not done yet\n");
+        return ret;
+    }
+
+    ehci->async_complete = 0;
+
+    DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
+            ehci->qhaddr, qh->next, ehci->qtdaddr, ret);
+
+    if (ret < 0) {
+err:
+        /* TO-DO: put this is in a function that can be invoked below as well */
+        c_err = get_field(qh->token, QTD_TOKEN_CERR);
+        c_err--;
+        set_field(&qh->token, c_err, QTD_TOKEN_CERR);
+
+        switch(ret) {
+        case USB_RET_NODEV:
+            fprintf(stderr, "USB no device\n");
+            break;
+        case USB_RET_STALL:
+            fprintf(stderr, "USB stall\n");
+            qh->token |= QTD_TOKEN_HALT;
+            break;
+        case USB_RET_NAK:
+            /* 4.10.3 */
+            reload = get_field(qh->epchar, QH_EPCHAR_RL);
+            if ((ehci->pid == USB_TOKEN_IN) && reload) {
+                int nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+                nakcnt--;
+                set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
+            } else if (!reload) {
+                return USB_RET_NAK;
+            }
+            break;
+        case USB_RET_BABBLE:
+            fprintf(stderr, "USB babble TODO\n");
+            qh->token |= QTD_TOKEN_BABBLE;
+            break;
+        default:
+            fprintf(stderr, "USB invalid response %d to handle\n", ret);
+            /* TO-DO: transaction error */
+            ret = USB_RET_PROCERR;
+            break;
+        }
+    } else {
+        // DPRINTF("Short packet condition\n");
+        // TODO check 4.12 for splits
+
+        if ((ret > ehci->tbytes) && (ehci->pid == USB_TOKEN_IN)) {
+            ret = USB_RET_BABBLE;
+            goto err;
+        }
+
+        if (ehci->tbytes && ehci->pid == USB_TOKEN_IN) {
+            if (ehci_buffer_rw(ehci->buffer, qh, ret, 1) != 0) {
+                return USB_RET_PROCERR;
+            }
+            ehci->tbytes -= ret;
+        } else {
+            ehci->tbytes = 0;
+        }
+
+        DPRINTF("updating tbytes to %d\n", ehci->tbytes);
+        set_field(&qh->token, ehci->tbytes, QTD_TOKEN_TBYTES);
+    }
+
+    qh->token ^= QTD_TOKEN_DTOGGLE;
+    qh->token &= ~QTD_TOKEN_ACTIVE;
+
+    if ((ret >= 0) && (qh->token & QTD_TOKEN_IOC)) {
+        ehci_record_interrupt(ehci, USBSTS_INT);
+    }
+
+    return ret;
+}
+
+// 4.10.3
+
+static int ehci_execute(EHCIState *ehci, EHCIqh *qh)
+{
+    USBPort *port;
+    USBDevice *dev;
+    int ret;
+    int i;
+    int endp;
+    int devadr;
+
+    if ( !(qh->token & QTD_TOKEN_ACTIVE)) {
+        fprintf(stderr, "Attempting to execute inactive QH\n");
+        return USB_RET_PROCERR;
+    }
+
+    ehci->tbytes = (qh->token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
+    if (ehci->tbytes > BUFF_SIZE) {
+        fprintf(stderr, "Request for more bytes than allowed\n");
+        return USB_RET_PROCERR;
+    }
+
+    ehci->pid = (qh->token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
+    switch(ehci->pid) {
+        case 0: ehci->pid = USB_TOKEN_OUT; break;
+        case 1: ehci->pid = USB_TOKEN_IN; break;
+        case 2: ehci->pid = USB_TOKEN_SETUP; break;
+        default: fprintf(stderr, "bad token\n"); break;
+    }
+
+    if ((ehci->tbytes && ehci->pid != USB_TOKEN_IN) &&
+        (ehci_buffer_rw(ehci->buffer, qh, ehci->tbytes, 0) != 0)) {
+        return USB_RET_PROCERR;
+    }
+
+    endp = get_field(qh->epchar, QH_EPCHAR_EP);
+    devadr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
+
+    ret = USB_RET_NODEV;
+
+    // TO-DO: associating device with ehci port
+    for(i = 0; i < NB_PORTS; i++) {
+        port = &ehci->ports[i];
+        dev = port->dev;
+
+        // TODO sometime we will also need to check if we are the port owner
+
+        if (!(ehci->portsc[i] &(PORTSC_CONNECT))) {
+            DPRINTF("Port %d, no exec, not connected(%08X)\n",
+                    i, ehci->portsc[i]);
+            continue;
+        }
+
+        ehci->usb_packet.pid = ehci->pid;
+        ehci->usb_packet.devaddr = devadr;
+        ehci->usb_packet.devep = endp;
+        ehci->usb_packet.data = ehci->buffer;
+        ehci->usb_packet.len = ehci->tbytes;
+
+        ret = usb_handle_packet(dev, &ehci->usb_packet);
+
+        DPRINTF("submit: qh %x next %x qtd %x pid %x len %d (total %d) endp %x ret %d\n",
+                ehci->qhaddr, qh->next, ehci->qtdaddr, ehci->pid,
+                ehci->usb_packet.len, ehci->tbytes, endp, ret);
+
+        if (ret != USB_RET_NODEV) {
+            break;
+        }
+    }
+
+    if (ret > BUFF_SIZE) {
+        fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
+        return USB_RET_PROCERR;
+    }
+
+    if (ret == USB_RET_ASYNC) {
+        ehci->async_complete = 0;
+    }
+
+    return ret;
+}
+
+/*  4.7.2
+ */
+
+static int ehci_process_itd(EHCIState *ehci,
+                            EHCIitd *itd)
+{
+    USBPort *port;
+    USBDevice *dev;
+    int ret;
+    int i, j;
+    int ptr;
+    int pid;
+    int pg;
+    int len;
+    int dir;
+    int devadr;
+    int endp;
+    int maxpkt;
+
+    dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
+    devadr = 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);
+
+    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);
+
+            if (len > BUFF_SIZE) {
+                return USB_RET_PROCERR;
+            }
+
+            DPRINTF("ISOCH: buffer %08X len %d\n", ptr, len);
+
+            if (!dir) {
+                cpu_physical_memory_rw(ptr, &ehci->buffer[0], len, 0);
+                pid = USB_TOKEN_OUT;
+            } else
+                pid = USB_TOKEN_IN;
+
+            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->usb_packet.pid = ehci->pid;
+                ehci->usb_packet.devaddr = devadr;
+                ehci->usb_packet.devep = endp;
+                ehci->usb_packet.data = ehci->buffer;
+                ehci->usb_packet.len = len;
+
+                DPRINTF("calling usb_handle_packet\n");
+                ret = usb_handle_packet(dev, &ehci->usb_packet);
+
+                if (ret != USB_RET_NODEV) {
+                    break;
+                }
+            }
+
+            /*  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.
+             */
+
+            if (ret == USB_RET_NAK) {
+                if (ehci->isoch_pause > 0) {
+                    DPRINTF("ISOCH: received a NAK but paused so returning\n");
+                    ehci->isoch_pause--;
+                    return 0;
+                } else if (ehci->isoch_pause == -1) {
+                    DPRINTF("ISOCH: recv NAK & isoch pause inactive, setting\n");
+                    // Pause frindex for up to 50 msec waiting for data from
+                    // remote
+                    ehci->isoch_pause = 50;
+                    return 0;
+                } else {
+                    DPRINTF("ISOCH: isoch pause timeout! return 0\n");
+                    ret = 0;
+                }
+            } else {
+                DPRINTF("ISOCH: received ACK, clearing pause\n");
+                ehci->isoch_pause = -1;
+            }
+
+            if (ret >= 0) {
+                itd->transact[i] &= ~ITD_XACT_ACTIVE;
+
+                if (itd->transact[i] & ITD_XACT_IOC) {
+                    ehci_record_interrupt(ehci, USBSTS_INT);
+                }
+            }
+
+            if (ret >= 0 && dir) {
+                cpu_physical_memory_rw(ptr, &ehci->buffer[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);
+                }
+            }
+        }
+    }
+    return 0;
+}
+
+/*  This state is the entry point for asynchronous schedule
+ *  processing.  Entry here consitutes a EHCI start event state (4.8.5)
+ */
+static int ehci_state_waitlisthead(EHCIState *ehci,  int async, int *state)
+{
+    EHCIqh *qh = &ehci->qh;
+    int i = 0;
+    int again = 0;
+    uint32_t entry = ehci->asynclistaddr;
+
+    /* set reclamation flag at start event (4.8.6) */
+    if (async) {
+        ehci->usbsts |= USBSTS_REC;
+    }
+
+    /*  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);
+
+        if (qh->epchar & QH_EPCHAR_H) {
+            DPRINTF_ST("WAITLISTHEAD: QH %08X is the HEAD of the list\n",
+                       entry);
+            if (async) {
+                entry |= (NLPTR_TYPE_QH << 1);
+            }
+
+            ehci->fetch_addr = entry;
+            *state = EST_FETCHENTRY;
+            again = 1;
+            goto out;
+        }
+
+        DPRINTF_ST("WAITLISTHEAD: QH %08X is NOT the HEAD of the list\n",
+                   entry);
+        entry = qh->next;
+        if (entry == ehci->asynclistaddr) {
+            DPRINTF("WAITLISTHEAD: reached beginning of QH list\n");
+            break;
+        }
+    }
+
+    /* no head found for list. */
+
+    *state = EST_ACTIVE;
+
+out:
+    return again;
+}
+
+
+/*  This state is the entry point for periodic schedule processing as
+ *  well as being a continuation state for async processing.
+ */
+static int ehci_state_fetchentry(EHCIState *ehci, int async, int *state)
+{
+    int again = 0;
+    uint32_t entry = ehci->fetch_addr;
+
+#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);
+        *state = EST_ACTIVE;
+        goto out;
+    }
+
+    /* section 4.8, only QH in async schedule */
+    if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
+        fprintf(stderr, "non queue head request in async schedule\n");
+        return -1;
+    }
+
+    switch (NLPTR_TYPE_GET(entry)) {
+    case NLPTR_TYPE_QH:
+        DPRINTF_ST("FETCHENTRY: entry %X is a Queue Head\n", entry);
+        *state = EST_FETCHQH;
+        ehci->qhaddr = entry;
+        again = 1;
+        break;
+
+    case NLPTR_TYPE_ITD:
+        DPRINTF_ST("FETCHENTRY: entry %X is an ITD\n", entry);
+        *state = EST_FETCHITD;
+        ehci->itdaddr = entry;
+        again = 1;
+        break;
+
+    default:
+        // TODO: handle siTD and FSTN types
+        fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
+                "which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
+        return -1;
+    }
+
+out:
+    return again;
+}
+
+static int ehci_state_fetchqh(EHCIState *ehci, int async, int *state)
+{
+    EHCIqh *qh = &ehci->qh;
+    int reload;
+    int again = 0;
+
+    get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+    if (async && (qh->epchar & QH_EPCHAR_H)) {
+
+        /*  EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
+        if (ehci->usbsts & USBSTS_REC) {
+            ehci->usbsts &= ~USBSTS_REC;
+        } else {
+            DPRINTF("FETCHQH:  QH 0x%08x. H-bit set, reclamation status reset"
+                       " - done processing\n", ehci->qhaddr);
+            *state = EST_ACTIVE;
+            goto out;
+        }
+    }
+
+#if EHCI_DEBUG
+    if (ehci->qhaddr != qh->next) {
+    DPRINTF("FETCHQH:  QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
+               ehci->qhaddr,
+               qh->epchar & QH_EPCHAR_H,
+               qh->token & QTD_TOKEN_HALT,
+               qh->token & QTD_TOKEN_ACTIVE,
+               qh->next);
+    }
+#endif
+
+    reload = get_field(qh->epchar, QH_EPCHAR_RL);
+    if (reload) {
+        DPRINTF_ST("FETCHQH: reloading nakcnt to %d\n", reload);
+        set_field(&qh->altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
+    }
+
+    if (qh->token & QTD_TOKEN_HALT) {
+        DPRINTF_ST("FETCHQH: QH Halted, go horizontal\n");
+        *state = EST_HORIZONTALQH;
+        again = 1;
+
+    } else if ((qh->token & QTD_TOKEN_ACTIVE) && (qh->current_qtd > 0x1000)) {
+        DPRINTF_ST("FETCHQH: Active, !Halt, execute - fetch qTD\n");
+        ehci->qtdaddr = qh->current_qtd;
+        *state = EST_FETCHQTD;
+        again = 1;
+
+    } else {
+        /*  EHCI spec version 1.0 Section 4.10.2 */
+        DPRINTF_ST("FETCHQH: !Active, !Halt, advance queue\n");
+        *state = EST_ADVANCEQUEUE;
+        again = 1;
+    }
+
+out:
+    return again;
+}
+
+static int ehci_state_fetchitd(EHCIState *ehci, int async, int *state)
+{
+    EHCIitd itd;
+
+    get_dwords(NLPTR_GET(ehci->itdaddr),(uint32_t *) &itd,
+               sizeof(EHCIitd) >> 2);
+    DPRINTF_ST("FETCHITD: Fetched ITD at address %08X " "(next is %08X)\n",
+               ehci->itdaddr, itd.next);
+
+    if (ehci_process_itd(ehci, &itd) != 0) {
+        return -1;
+    }
+
+    put_dwords(NLPTR_GET(ehci->itdaddr), (uint32_t *) &itd,
+                sizeof(EHCIitd) >> 2);
+    ehci->fetch_addr = itd.next;
+    *state = EST_FETCHENTRY;
+
+    return 1;
+}
+
+/* Section 4.10.2 - paragraph 3 */
+static int ehci_state_advqueue(EHCIState *ehci, int async, int *state)
+{
+#if 0
+    /* TO-DO: 4.10.2 - paragraph 2
+     * if I-bit is set to 1 and QH is not active
+     * go to horizontal QH
+     */
+    if (I-bit set) {
+        *state = EST_HORIZONTALQH;
+        goto out;
+    }
+#endif
+
+    /*
+     * want data and alt-next qTD is valid
+     */
+    if (((ehci->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
+        (ehci->qh.altnext_qtd > 0x1000) &&
+        (NLPTR_TBIT(ehci->qh.altnext_qtd) == 0)) {
+        DPRINTF_ST("ADVQUEUE: goto alt next qTD. "
+                   "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
+                   ehci->qh.current_qtd, ehci->qh.altnext_qtd,
+                   ehci->qh.next_qtd, ehci->qh.next);
+        ehci->qtdaddr = ehci->qh.altnext_qtd;
+        *state = EST_FETCHQTD;
+
+    /*
+     *  next qTD is valid
+     */
+    } else if ((ehci->qh.next_qtd > 0x1000) &&
+               (NLPTR_TBIT(ehci->qh.next_qtd) == 0)) {
+        DPRINTF_ST("ADVQUEUE: next qTD. "
+                   "curr 0x%08x next 0x%08x alt 0x%08x (next qh %x)\n",
+                   ehci->qh.current_qtd, ehci->qh.altnext_qtd,
+                   ehci->qh.next_qtd, ehci->qh.next);
+        ehci->qtdaddr = ehci->qh.next_qtd;
+        *state = EST_FETCHQTD;
+
+    /*
+     *  no valid qTD, try next QH
+     */
+    } else {
+        DPRINTF_ST("ADVQUEUE: go to horizontal QH\n");
+        *state = EST_HORIZONTALQH;
+    }
+
+    return 1;
+}
+
+/* Section 4.10.2 - paragraph 4 */
+static int ehci_state_fetchqtd(EHCIState *ehci, int async, int *state)
+{
+    EHCIqtd *qtd = &ehci->qtd;
+    int again = 0;
+
+    get_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) qtd, sizeof(EHCIqtd) >> 2);
+
+    if (qtd->token & QTD_TOKEN_ACTIVE) {
+        *state = EST_EXECUTE;
+        again = 1;
+    } else {
+        *state = EST_HORIZONTALQH;
+        again = 1;
+    }
+
+    return again;
+}
+
+static int ehci_state_horizqh(EHCIState *ehci, int async, int *state)
+{
+    int again = 0;
+
+    if (ehci->fetch_addr != ehci->qh.next) {
+        ehci->fetch_addr = ehci->qh.next;
+        *state = EST_FETCHENTRY;
+        again = 1;
+    } else {
+        *state = EST_ACTIVE;
+    }
+
+    return again;
+}
+
+static int ehci_state_execute(EHCIState *ehci, int async, int *state)
+{
+    EHCIqh *qh = &ehci->qh;
+    EHCIqtd *qtd = &ehci->qtd;
+    int again = 0;
+    int reload, nakcnt;
+    int smask;
+
+    if (async) {
+        DPRINTF_ST(">>>>> ASYNC STATE MACHINE execute QH 0x%08x, QTD 0x%08x\n",
+                  ehci->qhaddr, ehci->qtdaddr);
+    } else {
+        DPRINTF_ST(">>>>> PERIODIC STATE MACHINE execute\n");
+    }
+
+    if (ehci_qh_do_overlay(ehci, qh, qtd) != 0) {
+        return -1;
+    }
+
+    smask = get_field(qh->epcap, QH_EPCAP_SMASK);
+
+    if (!smask) {
+        reload = get_field(qh->epchar, QH_EPCHAR_RL);
+        nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+        if (reload && !nakcnt) {
+            DPRINTF_ST("EXECUTE: RL != 0 but NakCnt == 0 -- no execute\n");
+            *state = EST_HORIZONTALQH;
+            again = 1;
+            goto out;
+        }
+    }
+
+    // TODO verify enough time remains in the uframe as in 4.4.1.1
+    // TODO write back ptr to async list when done or out of time
+    // TODO Windows does not seem to ever set the MULT field
+
+    if (!async) {
+        int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
+        if (!transactCtr) {
+            DPRINTF("ZERO transactctr for int qh, go HORIZ\n");
+            *state = EST_HORIZONTALQH;
+            again = 1;
+            goto out;
+        }
+    }
+
+    if (async) {
+        ehci->usbsts |= USBSTS_REC;
+    }
+
+    ehci->exec_status = ehci_execute(ehci, qh);
+    if (ehci->exec_status == USB_RET_PROCERR) {
+        again = -1;
+        goto out;
+    }
+    *state = EST_EXECUTING;
+
+    if (ehci->exec_status != USB_RET_ASYNC) {
+        again = 1;
+    }
+
+out:
+    return again;
+}
+
+static int ehci_state_executing(EHCIState *ehci, int async, int *state)
+{
+    EHCIqh *qh = &ehci->qh;
+    int again = 0;
+    int reload, nakcnt;
+
+    ehci->exec_status = ehci_execute_complete(ehci, qh, ehci->exec_status);
+    if (ehci->exec_status == USB_RET_ASYNC) {
+        goto out;
+    }
+    if (ehci->exec_status == USB_RET_PROCERR) {
+        again = -1;
+        goto out;
+    }
+
+    // 4.10.3
+    if (!async) {
+        int transactCtr = get_field(qh->epcap, QH_EPCAP_MULT);
+        transactCtr--;
+        set_field(&qh->epcap, transactCtr, QH_EPCAP_MULT);
+        // 4.10.3, bottom of page 82, should exit this state when transaction
+        // counter decrements to 0
+    }
+
+
+    reload = get_field(qh->epchar, QH_EPCHAR_RL);
+    if (reload) {
+        nakcnt = get_field(qh->altnext_qtd, QH_ALTNEXT_NAKCNT);
+        if (ehci->exec_status == USB_RET_NAK) {
+            if (nakcnt) {
+                nakcnt--;
+            }
+            DPRINTF_ST("EXECUTING: Nak occured and RL != 0, dec NakCnt to %d\n",
+                    nakcnt);
+        } else {
+            nakcnt = reload;
+            DPRINTF_ST("EXECUTING: Nak didn't occur, reloading to %d\n",
+                       nakcnt);
+        }
+        set_field(&qh->altnext_qtd, nakcnt, QH_ALTNEXT_NAKCNT);
+    }
+
+    /*
+     *  Write the qh back to guest physical memory.  This step isn't
+     *  in the EHCI spec but we need to do it since we don't share
+     *  physical memory with our guest VM.
+     */
+
+    DPRINTF("EXECUTING: write QH to VM memory: qhaddr 0x%x, next 0x%x\n",
+              ehci->qhaddr, qh->next);
+    put_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) qh, sizeof(EHCIqh) >> 2);
+
+    /* 4.10.5 */
+    if ((ehci->exec_status == USB_RET_NAK) || (qh->token & QTD_TOKEN_ACTIVE)) {
+        *state = EST_HORIZONTALQH;
+    } else {
+        *state = EST_WRITEBACK;
+    }
+
+    again = 1;
+
+out:
+    return again;
+}
+
+
+static int ehci_state_writeback(EHCIState *ehci, int async, int *state)
+{
+    EHCIqh *qh = &ehci->qh;
+    int again = 0;
+
+    /*  Write back the QTD from the QH area */
+    DPRINTF_ST("WRITEBACK: write QTD to VM memory\n");
+    put_dwords(NLPTR_GET(ehci->qtdaddr),(uint32_t *) &qh->next_qtd,
+                sizeof(EHCIqtd) >> 2);
+
+    /* TODO confirm next state.  For now, keep going if async
+     * but stop after one qtd if periodic
+     */
+    //if (async) {
+        *state = EST_ADVANCEQUEUE;
+        again = 1;
+    //} else {
+    //    *state = EST_ACTIVE;
+    //}
+    return again;
+}
+
+/*
+ * This is the state machine that is common to both async and periodic
+ */
+
+static int ehci_advance_state(EHCIState *ehci,
+                              int async,
+                              int state)
+{
+    int again;
+    int iter = 0;
+
+    do {
+        if (state == EST_FETCHQH) {
+            iter++;
+            /* if we are roaming a lot of QH without executing a qTD
+             * something is wrong with the linked list. TO-DO: why is
+             * this hack needed?
+             */
+            if (iter > MAX_ITERATIONS) {
+                DPRINTF("\n*** advance_state: bailing on MAX ITERATIONS***\n");
+                state = EST_ACTIVE;
+                break;
+            }
+        }
+        switch(state) {
+        case EST_WAITLISTHEAD:
+            again = ehci_state_waitlisthead(ehci, async, &state);
+            break;
+
+        case EST_FETCHENTRY:
+            again = ehci_state_fetchentry(ehci, async, &state);
+            break;
+
+        case EST_FETCHQH:
+            again = ehci_state_fetchqh(ehci, async, &state);
+            break;
+
+        case EST_FETCHITD:
+            again = ehci_state_fetchitd(ehci, async, &state);
+            break;
+
+        case EST_ADVANCEQUEUE:
+            again = ehci_state_advqueue(ehci, async, &state);
+            break;
+
+        case EST_FETCHQTD:
+            again = ehci_state_fetchqtd(ehci, async, &state);
+            break;
+
+        case EST_HORIZONTALQH:
+            again = ehci_state_horizqh(ehci, async, &state);
+            break;
+
+        case EST_EXECUTE:
+            iter = 0;
+            again = ehci_state_execute(ehci, async, &state);
+            break;
+
+        case EST_EXECUTING:
+            again = ehci_state_executing(ehci, async, &state);
+            break;
+
+        case EST_WRITEBACK:
+            again = ehci_state_writeback(ehci, async, &state);
+            break;
+
+        default:
+            fprintf(stderr, "Bad state!\n");
+            again = -1;
+            break;
+        }
+
+        if (again < 0) {
+            fprintf(stderr, "processing error - resetting ehci HC\n");
+            ehci_reset(ehci);
+            again = 0;
+        }
+    }
+    while (again);
+
+    ehci_commit_interrupt(ehci);
+    return state;
+}
+
+static void ehci_advance_async_state(EHCIState *ehci)
+{
+    EHCIqh qh;
+    int state = ehci->astate;
+
+    switch(state) {
+    case EST_INACTIVE:
+        if (!(ehci->usbcmd & USBCMD_ASE)) {
+            break;
+        }
+        ehci->usbsts |= USBSTS_ASS;
+        ehci->astate = EST_ACTIVE;
+        // No break, fall through to ACTIVE
+
+    case EST_ACTIVE:
+        if ( !(ehci->usbcmd & USBCMD_ASE)) {
+            ehci->usbsts &= ~USBSTS_ASS;
+            ehci->astate = EST_INACTIVE;
+            break;
+        }
+
+        /* If the doorbell is set, the guest wants to make a change to the
+         * schedule. The host controller needs to release cached data.
+         * (section 4.8.2)
+         */
+        if (ehci->usbcmd & USBCMD_IAAD) {
+            DPRINTF("ASYNC: doorbell request acknowledged\n");
+            ehci->usbcmd &= ~USBCMD_IAAD;
+            ehci_set_interrupt(ehci, USBSTS_IAA);
+            break;
+        }
+
+        /* make sure guest has acknowledged */
+        /* TO-DO: is this really needed? */
+        if (ehci->usbsts & USBSTS_IAA) {
+            DPRINTF("IAA status bit still set.\n");
+            break;
+        }
+
+        DPRINTF_ST("ASYNC: waiting for listhead, starting at %08x\n",
+                ehci->asynclistaddr);
+        /* check that address register has been set */
+        if (ehci->asynclistaddr == 0) {
+            break;
+        }
+
+        state = EST_WAITLISTHEAD;
+        /* fall through */
+
+    case EST_FETCHENTRY:
+        /* fall through */
+
+    case EST_EXECUTING:
+        get_dwords(NLPTR_GET(ehci->qhaddr), (uint32_t *) &qh,
+                   sizeof(EHCIqh) >> 2);
+        ehci->astate = ehci_advance_state(ehci, 1, state);
+        break;
+
+    default:
+        /* this should only be due to a developer mistake */
+        fprintf(stderr, "ehci: Bad asynchronous state %d. "
+                "Resetting to active\n", ehci->astate);
+        ehci->astate = EST_ACTIVE;
+    }
+}
+
+static void ehci_advance_periodic_state(EHCIState *ehci)
+{
+    uint32_t entry;
+    uint32_t list;
+
+    // 4.6
+
+    switch(ehci->pstate) {
+    case EST_INACTIVE:
+        if ( !(ehci->frindex & 7) && (ehci->usbcmd & USBCMD_PSE)) {
+            DPRINTF("PERIODIC going active\n");
+            ehci->usbsts |= USBSTS_PSS;
+            ehci->pstate = EST_ACTIVE;
+            // No break, fall through to ACTIVE
+        } else
+            break;
+
+    case EST_ACTIVE:
+        if ( !(ehci->frindex & 7) && !(ehci->usbcmd & USBCMD_PSE)) {
+            DPRINTF("PERIODIC going inactive\n");
+            ehci->usbsts &= ~USBSTS_PSS;
+            ehci->pstate = EST_INACTIVE;
+            break;
+        }
+
+        list = ehci->periodiclistbase & 0xfffff000;
+        /* check that register has been set */
+        if (list == 0) {
+            break;
+        }
+        list |= ((ehci->frindex & 0x1ff8) >> 1);
+
+        cpu_physical_memory_rw(list, (uint8_t *) &entry, sizeof entry, 0);
+        entry = le32_to_cpu(entry);
+
+        DPRINTF("PERIODIC state adv fr=%d.  [%08X] -> %08X\n",
+                ehci->frindex / 8, list, entry);
+        ehci->fetch_addr = entry;
+        ehci->pstate = ehci_advance_state(ehci, 0, EST_FETCHENTRY);
+        break;
+
+    case EST_EXECUTING:
+        DPRINTF("PERIODIC state adv for executing\n");
+        ehci->pstate = ehci_advance_state(ehci, 0, EST_EXECUTING);
+        break;
+
+    default:
+        /* this should only be due to a developer mistake */
+        fprintf(stderr, "ehci: Bad periodic state %d. "
+                "Resetting to active\n", ehci->pstate);
+        ehci->pstate = EST_ACTIVE;
+    }
+}
+
+static void ehci_frame_timer(void *opaque)
+{
+    EHCIState *ehci = opaque;
+    int64_t expire_time, t_now;
+    int usec_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++;
+    }
+
+    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;
+
+    for (i = 0; i < frames; i++) {
+        if ( !(ehci->usbsts & USBSTS_HALT)) {
+            if (ehci->isoch_pause <= 0) {
+                ehci->frindex += 8;
+            }
+
+            if (ehci->frindex > 0x00001fff) {
+                ehci->frindex = 0;
+                ehci_set_interrupt(ehci, USBSTS_FLR);
+            }
+
+            ehci->sofv = (ehci->frindex - 1) >> 3;
+            ehci->sofv &= 0x000003ff;
+        }
+
+        if (frames - i > 10) {
+            skipped_frames++;
+        } else {
+            // TODO could this cause periodic frames to get skipped if async
+            // active?
+            if (ehci->astate != EST_EXECUTING) {
+                ehci_advance_periodic_state(ehci);
+            }
+        }
+
+        ehci->last_run_usec += FRAME_TIMER_USEC;
+    }
+
+#if 0
+    if (skipped_frames) {
+        DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames);
+    }
+#endif
+
+    /*  Async is not inside loop since it executes everything it can once
+     *  called
+     */
+    if (ehci->pstate != EST_EXECUTING) {
+        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 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 int usb_ehci_initfn(PCIDevice *dev);
+
+static USBPortOps ehci_port_ops = {
+    .attach = ehci_attach,
+    .detach = ehci_detach,
+    .complete = ehci_async_complete_packet,
+};
+
+static PCIDeviceInfo ehci_info = {
+    .qdev.name    = "usb-ehci",
+    .qdev.size    = sizeof(EHCIState),
+    .init         = usb_ehci_initfn,
+};
+
+static int usb_ehci_initfn(PCIDevice *dev)
+{
+    EHCIState *s = DO_UPCAST(EHCIState, dev, 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_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[0x61], 0x20);  // frame length adjustment (2.1.5)
+    pci_set_word(&pci_conf[0x62], 0x00);  // port wake up capability (2.1.6)
+
+    pci_conf[0x64] = 0x00;
+    pci_conf[0x65] = 0x00;
+    pci_conf[0x66] = 0x00;
+    pci_conf[0x67] = 0x00;
+    pci_conf[0x68] = 0x01;
+    pci_conf[0x69] = 0x00;
+    pci_conf[0x6a] = 0x00;
+    pci_conf[0x6b] = 0x00;  // USBLEGSUP
+    pci_conf[0x6c] = 0x00;
+    pci_conf[0x6d] = 0x00;
+    pci_conf[0x6e] = 0x00;
+    pci_conf[0x6f] = 0xc0;  // USBLEFCTLSTS
+
+    // 2.2 host controller interface version
+    s->mmio[0x00] = (uint8_t) OPREGBASE;
+    s->mmio[0x01] = 0x00;
+    s->mmio[0x02] = 0x00;
+    s->mmio[0x03] = 0x01;        // HC version
+    s->mmio[0x04] = NB_PORTS;    // Number of downstream ports
+    s->mmio[0x05] = 0x00;        // No companion ports at present
+    s->mmio[0x06] = 0x00;
+    s->mmio[0x07] = 0x00;
+    s->mmio[0x08] = 0x80;        // We can cache whole frame, not 64-bit capable
+    s->mmio[0x09] = 0x68;        // EECP
+    s->mmio[0x0a] = 0x00;
+    s->mmio[0x0b] = 0x00;
+
+    s->irq = s->dev.irq[3];
+
+    // TODO - port registration is going to need an overhaul since ports
+    // can be low, full or high speed and are not tied to UHCI or EHCI.
+    // This works for now since we register last so are top of the free
+    // list but really all ports need to be owned by EHCI and it should
+    // hand off to companion controllers if device is full or low speed.
+
+    DPRINTF("ehci_init : registering USB ports with no device attached\n");
+
+    // TODO come up with a better port allocation scheme
+    // added ehci->bus, need to find ehci->DeviceState
+    usb_bus_new(&s->bus, &s->dev.qdev);
+    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;
+    }
+
+    s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
+
+    qemu_register_reset(ehci_reset, s);
+
+    s->mem = cpu_register_io_memory(ehci_readfn, ehci_writefn, s,
+                                    DEVICE_LITTLE_ENDIAN);
+
+    DPRINTF("ehci_init: registering MMIO size %d\n", MMIO_SIZE);
+    pci_register_bar(&s->dev, 0, MMIO_SIZE, PCI_BASE_ADDRESS_SPACE_MEMORY,
+                                                            ehci_map);
+
+    fprintf(stderr, "*** EHCI support is under development ***\n");
+
+    return 0;
+}
+
+static void ehci_register(void)
+{
+    pci_qdev_register(&ehci_info);
+}
+device_init(ehci_register);
+
+void usb_ehci_init(PCIBus *bus, int devfn)
+{
+    pci_create_simple(bus, devfn, "usb-ehci");
+}
+
+/*
+ * vim: expandtab ts=4
+ */
diff --git a/hw/usb-ehci.h b/hw/usb-ehci.h
new file mode 100644
index 0000000..ba087a0
--- /dev/null
+++ b/hw/usb-ehci.h
@@ -0,0 +1,8 @@ 
+#ifndef QEMU_USB_EHCI_H
+#define QEMU_USB_EHCI_H
+
+#include "qemu-common.h"
+
+void usb_ehci_init(PCIBus *bus, int devfn);
+
+#endif