qemu-devel.nongnu.org archive mirror
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH] USB 2.0 EHCI emulation
@ 2008-01-07 17:41 Arnon Gilboa
  2008-01-08  1:30 ` Paul Brook
  0 siblings, 1 reply; 6+ messages in thread
From: Arnon Gilboa @ 2008-01-07 17:41 UTC (permalink / raw)
  To: qemu-devel; +Cc: KVM, Roni Luxenberg

[-- Attachment #1: Type: text/plain, Size: 1627 bytes --]

Hi,

Attached is a preliminary patch implementing USB 2.0 EHCI emulation.

The current status is as following:
-The patch was tested on WinXP guest with both qemu-head & kvm.
-It was tried on FC7 guest as well, but in this case there are still
some control transfers problems.
-Tested on USB 2.0 DoK, USB 1.1 multi-function DoK, USB 2.0 HP DeskJet
multi-function printer/scanner.
-Performance is almost similar to UHCI emulation. There are still many
possible performance improvements.
-Asynchronous schedule is supported, which means Bulk and Control
transfers are working.
-Periodic schedule is currently NOT supported, so Interrupt and
Isochronous transfers are NOT working (no webcam or audio devices
support).
-Currently async transfers are serialized for simplicity, so at each
moment only a single pending transfer is allowed. This is similar to the
UHCI & OHCI emulations.
-The host kernel was configured with dynamic tick & hi-res timers, to
allow the desired timer resolution. USB 2.0 microframe is 125usec.
-In usb-linux.c, usb_host_handle_data() was rewritten for EHCI with the
option of async i/o. See the defines USE_ASYNCIO & USE_EHCI.
-There are possible end-of-micro-frame drifts when
usb_host_handle_control()'s synchronous ioctls take too long.

TODOs:
-Support periodic schedule
-Support linux guest
-Parallelize async transfers - support several pending QHs
-Implement async control transfers, prevent microframe drifts
-Move EHCIState state machine members into ehci_qh_state_machine()
 (async_qh_addr; async_qh; usb_packet; usb_buf)

Waiting for your comments,
Arnon

[-- Attachment #2: usb-ehci.patch --]
[-- Type: application/octet-stream, Size: 52159 bytes --]

diff -u -d -p -P -r qemu-head/hw/pci.h qemu-merge/hw/pci.h
--- qemu-head/hw/pci.h	2008-01-07 15:27:02.000000000 +0200
+++ qemu-merge/hw/pci.h	2008-01-07 14:54:51.000009000 +0200
@@ -112,6 +112,9 @@ void usb_uhci_piix4_init(PCIBus *bus, in
 /* usb-ohci.c */
 void usb_ohci_init_pci(struct PCIBus *bus, int num_ports, int devfn);
 
+/* usb-ehci.c */
+void usb_ehci_init(PCIBus *bus, int devfn);
+
 /* eepro100.c */
 
 void pci_i82551_init(PCIBus *bus, NICInfo *nd, int devfn);
diff -u -d -p -P -r qemu-head/hw/usb-ehci.c qemu-merge/hw/usb-ehci.c
--- qemu-head/hw/usb-ehci.c	1970-01-01 02:00:00.000000000 +0200
+++ qemu-merge/hw/usb-ehci.c	2008-01-07 18:19:47.050569214 +0200
@@ -0,0 +1,1339 @@
+/*
+ * USB EHCI controller emulation
+ *
+ * Copyright (C) 2007 Qumranet
+ *
+ * Author:
+ *
+ *  Arnon Gilboa <arnong@qumranet.com>
+ *
+ * This work is licensed under the GNU LGPL license, version 2.
+ */
+#include <stddef.h>
+#include "hw.h"
+#include "usb.h"
+#include "pci.h"
+#include "qemu-timer.h"
+
+//#define DEBUG_EHCI
+//#define DEBUG_PACKET
+
+#ifdef DEBUG_EHCI
+#define dprintf printf
+#else
+#define dprintf(...)
+#endif
+
+/* microframes (125usec) in second */
+#define MICROFRAME_TIMER_FREQ 8000
+#define ASYNC_SLEEP_TIMER_FREQ 100000
+
+#define NB_PORTS 4
+
+#define PAGE_SIZE 4096
+#define MAX_TX_SIZE 4*PAGE_SIZE
+
+typedef struct ehci_qh {
+    uint32_t next;
+    uint32_t info1;
+    uint32_t info2;
+    uint32_t current;
+    uint32_t qtd_next;
+    uint32_t alt_next;
+    uint32_t token;
+    uint32_t buf[5];
+} ehci_qh;
+
+typedef struct ehci_qtd {
+    uint32_t next;
+    uint32_t alt_next;
+    uint32_t token;
+    uint32_t buf[5];
+} ehci_qtd;
+
+typedef struct ehci_itd {
+    uint32_t next;
+    uint32_t transaction[8];
+    uint32_t bufp[7];
+} ehci_itd;
+
+typedef struct ehci_sitd {
+    uint32_t next;
+    uint32_t fullspeed_ep;
+    uint32_t uframe;
+    uint32_t results;
+    uint32_t buf[2];
+    uint32_t backpointer;
+} ehci_sitd;
+
+typedef struct ehci_fstn {
+    uint32_t next;
+    uint32_t prev;
+} ehci_fstn;
+
+typedef struct EHCIPort {
+    USBPort port;
+    uint32_t ctrl;
+} EHCIPort;
+
+/* FIXME: support variable num_ports, given in usb_ehci_init() */
+typedef struct EHCIState {
+    PCIDevice dev;
+    target_phys_addr_t mem_base;
+    int mem;
+
+    uint32_t command;
+    uint32_t status;
+    uint32_t intr_enable;
+    uint32_t frame_index;
+    uint32_t segment;
+    uint32_t frame_list;
+    uint32_t async_next;
+    uint32_t configured_flag;
+
+    EHCIPort ports[NB_PORTS];
+    QEMUTimer *frame_timer;
+    QEMUTimer *async_timer;
+
+    int async_state;
+    int nak_state;
+
+    uint32_t async_qh_addr;
+    ehci_qh async_qh;
+    USBPacket usb_packet;
+    int pending_qh;
+    uint8_t usb_buf[MAX_TX_SIZE];
+} EHCIState;
+
+static const uint8_t ehci_pci_config[] = {
+    0x31, 0x11,             /* Vendor ID */
+    0x62, 0x15,             /* Device ID */
+    0x00, 0x00,             /* Command */
+    0x00, 0x02,             /* Status - Capabilities Disabled */
+    0x30,                   /* REVID */ 
+    0x20, 0x03, 0x0c,       /* Class Code - USB 2.0 Host Controller */
+    0x00,                   /* CacheLine Size */
+    0x00,                   /* Latency Timer */
+    0x80,                   /* Header Type - Multi-Function Device */
+    0x00,                   /* BIST */
+    0x00, 0x00, 0x00, 0x00, /* BAR0 */
+    0x00, 0x00, 0x00, 0x00, /* BAR1 */
+    0x00, 0x00, 0x00, 0x00, /* BAR2 */
+    0x00, 0x00, 0x00, 0x00, /* BAR3 */
+    0x00, 0x00, 0x00, 0x00, /* BAR4 */
+    0x00, 0x00, 0x00, 0x00, /* BAR5 */
+    0x00, 0x00, 0x00, 0x00, /* Cardbus CIS Pointer */
+    0x31, 0x11,             /* Subsystem Vendor ID */
+    0x62, 0x15,             /* Subsystem Device ID */
+    0x00, 0x00, 0x00, 0x00, /* Expansion ROM Base Address */
+    0x00,                   /* Capabilities Pointer - Disabled */
+    0x00, 0x00, 0x00,       /* Reserved */
+    0x00, 0x00, 0x00, 0x00, /* Reserved */
+    0x00,                   /* Interrupt Line */
+    0x01,                   /* Interrupt Pin - INTA# */
+    0x02,                   /* Minimum Grant */
+    0x10,                   /* Maximum Latency */
+    0x00,                   /* TRDY Timeout */
+    0x80,                   /* Retry Timeout */
+    0x00, 0x00              /* Reserved */
+};
+
+static const uint8_t ehci_specific_pci_config[] = {
+    0x20,                   /* Serial Bus Spec - Release 2.0 */
+    0x20,                   /* Frame Length Adjustment Register */
+    0x1f, 0x00              /* Port Wake Capability Register - 4 Ports */
+};
+
+static const uint8_t ehci_power_mgmt_config[] = {
+    0x01,                   /* Capability Identifier */
+    0x00,                   /* Next Item Pointer */
+    0x02, 0xff,             /* Power Management Capabilities */
+    0x00, 0x1f,             /* Power Management Control/Status */
+    0x00,                   /* Bridge Support Extensions */
+    0x00                    /* Data */
+};
+
+#define EHCI_CMD_RS                 (1 << 0)
+#define EHCI_CMD_HCRESET            (1 << 1)
+#define EHCI_CMD_FLS_SHIFT          2
+#define EHCI_CMD_FLS_MASK           (3 << EHCI_CMD_FLS_SHIFT)
+#define EHCI_CMD_PSE                (1 << 4)
+#define EHCI_CMD_ASE                (1 << 5)
+#define EHCI_CMD_IAAD               (1 << 6)
+#define EHCI_CMD_LHCR               (1 << 7)
+#define EHCI_CMD_ITC_SHIFT          16
+#define EHCI_CMD_ITC_MASK           (0xff << EHCI_CMD_ITC_SHIFT)
+
+#define EHCI_STS_USBINT             (1 << 0)
+#define EHCI_STS_USBERRINT          (1 << 1)
+#define EHCI_STS_PCD                (1 << 2)
+#define EHCI_STS_FLR                (1 << 3)
+#define EHCI_STS_HSE                (1 << 4)
+#define EHCI_STS_IAA                (1 << 5)
+#define EHCI_STS_HCH                (1 << 12)
+#define EHCI_STS_RECL               (1 << 13)
+#define EHCI_STS_PSSTAT             (1 << 14)
+#define EHCI_STS_ASS                (1 << 15)
+
+#define EHCI_INTR_USBINTE           (1 << 0)
+#define EHCI_INTR_USBERRINTE        (1 << 1)
+#define EHCI_INTR_PCIE              (1 << 2)
+#define EHCI_INTR_FLRE              (1 << 3)
+#define EHCI_INTR_HSEE              (1 << 4)
+#define EHCI_INTR_IAAE              (1 << 5)
+
+#define EHCI_PRT_ECCS               (1 << 0)
+#define EHCI_PRT_ECSC               (1 << 1)
+#define EHCI_PRT_PED                (1 << 2)
+#define EHCI_PRT_PEDC               (1 << 3)
+#define EHCI_PRT_OCA                (1 << 4)
+#define EHCI_PRT_OCC                (1 << 5)
+#define EHCI_PRT_FPR                (1 << 6)
+#define EHCI_PRT_SUSP               (1 << 7)
+#define EHCI_PRT_PR                 (1 << 8)
+#define EHCI_PRT_LS_SHIFT           10
+#define EHCI_PRT_LS_MASK            (3 << EHCI_PRT_LS_SHIFT)
+#define EHCI_PRT_PP                 (1 << 12)
+#define EHCI_PRT_PO                 (1 << 13)
+#define EHCI_PRT_PIC_SHIFT          14
+#define EHCI_PRT_PIC_MASK           (3 << EHCI_PRT_PIC_SHIFT)
+#define EHCI_PRT_PTC_SHIFT          16
+#define EHCI_PRT_PTC_MASK           (0xf << EHCI_PRT_PTC_SHIFT)
+#define EHCI_PRT_WKCNNT_E           (1 << 20)
+#define EHCI_PRT_WKDSCNNT_E         (1 << 21)
+#define EHCI_PRT_WKOC_E             (1 << 22)
+
+#define EHCI_COMMAND_MASK           0x00ff007f
+#define EHCI_STATUS_MASK            0x0000003f
+#define EHCI_INTR_MASK              0x0000003f
+#define EHCI_FRAME_INDEX_MASK       0x00003fff
+#define EHCI_FRAME_LIST_MASK        0xfffff000
+#define EHCI_ASYNC_NEXT_MASK        0xffffffe0
+#define EHCI_CONFIGURED_FLAG_MASK   0x00000001
+#define EHCI_PORT_MASK              0x00eff1c4
+#define EHCI_PORT_WC_MASK           0x0000002a
+
+/* Data Structures and Bitmasks - EHCI spec ch3 */
+#define EHCI_LINK_T                 1
+#define EHCI_LINK_TYP_SHIFT         1
+#define EHCI_LINK_TYP_MASK          (3 << EHCI_LINK_TYP_SHIFT)
+#define EHCI_LINK_Z                 0x18
+#define EHCI_LINK_PTR               0xffffffe0
+
+#define EHCI_LINK_TYP_ITD           0
+#define EHCI_LINK_TYP_QH            1
+#define EHCI_LINK_TYP_SITD          2
+#define EHCI_LINK_TYP_FSTN          3
+
+/* FIXME: J/K-state values are inconsistent between EHCI specs */
+#define EHCI_PORT_LS_SE0            0
+#define EHCI_PORT_LS_KSTATE         1
+#define EHCI_PORT_LS_JSTATE         2
+
+/* QH bitmasks */
+#define EHCI_QH_DEV_ADDR            0x7f
+#define EHCI_QH_I                   (1 << 7)
+#define EHCI_QH_ENDPT_SHIFT         8
+#define EHCI_QH_ENDPT_MASK          (0xf << EHCI_QH_ENDPT_SHIFT)
+#define EHCI_QH_EPS_SHIFT           12
+#define EHCI_QH_EPS_MASK            (3 << EHCI_QH_EPS_SHIFT)
+#define EHCI_QH_DTC                 (1 << 14)
+#define EHCI_QH_H                   (1 << 15)
+#define EHCI_QH_MAX_PKT_LEN_SHIFT   16
+#define EHCI_QH_MAX_PKT_LEN_MASK    (0x7ff << EHCI_QH_MAX_PKT_LEN_SHIFT)
+#define EHCI_QH_RL_SHIFT            28
+#define EHCI_QH_RL_MASK             (0xf << EHCI_QH_RL_SHIFT)
+#define EHCI_QH_S_MASK              0xff
+
+#define EHCI_QH_EPS_FULL            0
+#define EHCI_QH_EPS_LOW             1
+#define EHCI_QH_EPS_HIGH            2
+#define EHCI_QH_EPS_RESERVED        3
+
+/* QH & QTD bitmasks */
+#define EHCI_QH_T                   1
+#define EHCI_QH_NAK_CNT_SHIFT       1
+#define EHCI_QH_NAK_CNT_MASK        (0xf << EHCI_QH_NAK_CNT_SHIFT)
+#define EHCI_QH_STATUS_SHIFT        0
+#define EHCI_QH_STATUS_MASK         0xff
+#define EHCI_QH_PID_CODE_SHIFT      8
+#define EHCI_QH_PID_CODE_MASK       (3 << EHCI_QH_PID_CODE_SHIFT)
+#define EHCI_QH_CERR_SHIFT          10
+#define EHCI_QH_CERR_MASK           (3 << EHCI_QH_CERR_SHIFT)
+#define EHCI_QH_C_PAGE_SHIFT        12
+#define EHCI_QH_C_PAGE_MASK         (7 << EHCI_QH_C_PAGE_SHIFT)
+#define EHCI_QH_IOC                 (1 << 15)
+#define EHCI_QH_TOTAL_BYTES_SHIFT   16
+#define EHCI_QH_TOTAL_BYTES_MASK    (0x7fff << EHCI_QH_TOTAL_BYTES_SHIFT)
+#define EHCI_QH_DT                  (1 << 31)
+#define EHCI_QH_CURR_OFFSET_SHIFT   0
+#define EHCI_QH_CURR_OFFSET_MASK    (0xfff << EHCI_QH_CURR_OFFSET_SHIFT)
+#define EHCI_QH_BUFFER_POINTER      0xfffff000
+#define EHCI_QH_C_PROG_MASK         0xff
+#define EHCI_QH_FRAME_TAG           0x1f
+#define EHCI_QTD_WRITE_BACK \
+    (EHCI_QH_STATUS_MASK | EHCI_QH_CERR_MASK | EHCI_QH_TOTAL_BYTES_MASK)
+
+#define EHCI_QH_STS_PING            (1 << 0)
+#define EHCI_QH_STS_STS             (1 << 1)
+#define EHCI_QH_STS_MMF             (1 << 2)
+#define EHCI_QH_STS_XACT            (1 << 3)
+#define EHCI_QH_STS_BABBLE          (1 << 4)
+#define EHCI_QH_STS_DBE             (1 << 5)
+#define EHCI_QH_STS_HALT            (1 << 6)
+#define EHCI_QH_STS_ACTIVE          (1 << 7)
+
+#define ACTIVE_QH(qh)   (qh->token & EHCI_QH_STS_ACTIVE)
+#define HALTED_QH(qh)   (qh->token & EHCI_QH_STS_HALT)
+
+#define EHCI_QH_PID_OUT             0
+#define EHCI_QH_PID_IN              1
+#define EHCI_QH_PID_SETUP           2
+#define EHCI_QH_PID_RESERVED        3
+
+#define EHCI_BM(val, field) \
+  (((val) & EHCI_##field##_MASK) >> EHCI_##field##_SHIFT)
+
+#define EHCI_SET_BM(val, field, newval) do { \
+    val &= ~EHCI_##field##_MASK; \
+    val |= ((newval) << EHCI_##field##_SHIFT) & EHCI_##field##_MASK; \
+    } while(0)
+
+#define BIT(val, bitmask) (((val) & (bitmask)) ? 1 : 0)
+
+/* Async schedule states */
+#define EHCI_ASYNC_STATE_NOT_ACTIVE     1
+#define EHCI_ASYNC_STATE_ACTIVE         2
+#define EHCI_ASYNC_STATE_SLEEPING       3
+
+/* Nak counter reload states */
+#define EHCI_NAK_STATE_WAIT_FOR_HEAD    1
+#define EHCI_NAK_STATE_DO_RELOAD        2
+#define EHCI_NAK_STATE_WAIT_FOR_START   3
+
+/* Queue head traversal states */
+#define EHCI_QH_STATE_FETCH_QH          1
+#define EHCI_QH_STATE_ADVANCE_QUEUE     2
+#define EHCI_QH_STATE_EXEC_TRANSACTION  3
+#define EHCI_QH_STATE_WRITE_BACK_QTD    4
+#define EHCI_QH_STATE_FOLLOW_QHLP       5
+#define EHCI_QH_STATE_DONE              6
+
+static void ehci_update_irq(EHCIState *ehci)
+{
+    int level;
+
+    /* FIXME: check the STS & INTR bit-by bit? */
+    if (ehci->status & ehci->intr_enable & EHCI_INTR_MASK) {
+        level = 1;
+    } else {
+        level = 0;
+    }
+
+    qemu_set_irq(ehci->dev.irq[0], level);
+}
+
+static inline void ehci_set_irq(EHCIState *ehci, uint32_t intr)
+{
+    ehci->status |= intr;
+    ehci_update_irq(ehci);
+}
+
+static void ehci_attach(USBPort *p, USBDevice *dev)
+{
+    EHCIState *ehci = p->opaque;
+    EHCIPort *port = &ehci->ports[p->index];
+    uint32_t old_state = port->ctrl;
+
+    if (dev) {
+        if (port->port.dev) {
+            usb_attach(p, NULL);
+        }
+
+        /* set connect status */
+        if (!(port->ctrl & EHCI_PRT_ECCS)) {
+            port->ctrl |= (EHCI_PRT_ECCS | EHCI_PRT_ECSC);
+        }
+
+        /* FIXME: update speed */
+        dprintf("ehci_attach: dev_speed=%d\n", dev->speed);
+        if (dev->speed == USB_SPEED_LOW)
+            EHCI_SET_BM(port->ctrl, PRT_LS, EHCI_PORT_LS_KSTATE);
+        else
+            EHCI_SET_BM(port->ctrl, PRT_LS, EHCI_PORT_LS_JSTATE);
+        
+        port->port.dev = dev;
+
+        /* FIXME: notify of remote-wakeup */
+
+        /* send the attach message */
+        usb_send_msg(dev, USB_MSG_ATTACH);
+        dprintf("ehci_attach: Attached port %d\n", p->index);
+    } else {
+        /* set connect status */
+        if (port->ctrl & EHCI_PRT_ECCS) {
+            port->ctrl &= ~EHCI_PRT_ECCS;
+            port->ctrl |= EHCI_PRT_ECSC;
+        }
+
+        dev = port->port.dev;
+        if (dev) {
+            /* send the detach message */
+            usb_send_msg(dev, USB_MSG_DETACH);
+        }
+        port->port.dev = NULL;
+        dprintf("ehci_attach: Detached port %d\n", p->index);
+    }
+
+    /* set port change detect only if port owner is zero */
+    if (!(port->ctrl & EHCI_PRT_PO) && (old_state != port->ctrl))
+        ehci_set_irq(ehci, EHCI_STS_PCD);
+}
+
+static void ehci_reset(void *opaque)
+{
+    EHCIState *ehci = opaque;
+    EHCIPort *port;
+    int i;
+#ifdef DEBUG_EHCI
+    uint8_t *pci_conf = ehci->dev.config;
+#endif
+
+    /* Set HCHalted bit to one */
+    ehci->status = EHCI_STS_HCH;
+    ehci->intr_enable = 0;
+    ehci->frame_index = 0;
+    ehci->segment = 0;
+    ehci->frame_list = 0;
+    ehci->async_next = 0;
+    ehci->configured_flag = 0;
+
+    for(i = 0; i < NB_PORTS; i++) {
+        port = &ehci->ports[i];
+        port->ctrl = EHCI_PRT_PO;
+        if (port->port.dev) {
+            ehci_attach(&port->port, port->port.dev);
+        }
+    }
+
+    ehci->async_state = EHCI_ASYNC_STATE_NOT_ACTIVE;
+    ehci->nak_state = EHCI_NAK_STATE_WAIT_FOR_HEAD;
+    ehci->pending_qh = 0;
+
+    /* Set HCReset bit to zero only when reset process is complete */
+    ehci->command = 0x80000;
+
+    dprintf("ehci_reset: [PCI Conf] CMD=0x%.4x CLS=0x%.2x LT=0x%.2x BAR=0x%.8x "
+            "FLADJ=0x%.2x\n", *(uint16_t *)(pci_conf + 0x04), pci_conf[0x0c],
+            pci_conf[0x0d], *(uint32_t *)(pci_conf + 0x10), pci_conf[0x61]);
+}
+
+static uint32_t ehci_mem_read(void *ptr, target_phys_addr_t addr)
+{
+    uint32_t ret = 0;
+    EHCIState *ehci = ptr;
+    addr -= ehci->mem_base;
+    int portnum;
+
+    if (addr >= 0x50 && addr < 0x50 + NB_PORTS * 4) {
+        portnum = (addr - 0x50) >> 2;
+        ret = ehci->ports[portnum].ctrl;
+        dprintf("ehci_read: port[%d] 0x%.8x\n", portnum, ret);
+        return ret;
+    }
+
+    switch (addr) {
+    case 0x00:
+        /* caplength & hciversion - read-only */
+        ret = 0x95000c;
+        dprintf("ehci_read: caplength/hciversion=0x%.8x\n", ret);
+        break;
+    case 0x04:
+        /* hcsparams - read-only */
+        ret = 0x2214;
+        dprintf("ehci_read: hcsparams=0x%.8x\n", ret);
+        break;
+    case 0x08:
+        /* hccparams - read-only */
+        ret = 0x12;
+        dprintf("ehci_read: hcsparams=0x%.8x\n", ret);
+        break;
+    case 0x0c:
+        ret = ehci->command;
+        dprintf("ehci_read: command=0x%.8x\n", ret);
+        break;
+    case 0x10:
+        ret = ehci->status;
+        dprintf("ehci_read: status=0x%.8x\n", ret);
+        break;
+    case 0x14:
+        ret = ehci->intr_enable;
+        dprintf("ehci_read: intr=0x%.8x\n", ret);
+        break;
+    case 0x18:
+        ret = ehci->frame_index;
+        dprintf("ehci_read: index=0x%.8x\n", ret);
+        break;
+    case 0x1c:
+        ret = ehci->segment;
+        dprintf("ehci_read: segment=0x%.8x\n", ret);
+        break;
+    case 0x20:
+        ret = ehci->frame_list;
+        dprintf("ehci_read: list=0x%.8x\n", ret);
+        break;
+    case 0x24:
+        ret = ehci->async_next;
+        dprintf("ehci_read: async=0x%.8x\n", ret);
+        break;
+    case 0x4c:
+        ret = ehci->configured_flag;
+        dprintf("ehci_read: cfg=0x%.8x\n", ret);
+        break;
+    default:
+        dprintf("ehci_read: Bad offset 0x%.8x\n", (int)addr);
+    }
+
+    return ret;
+}
+
+static void ehci_mem_write(void *ptr, target_phys_addr_t addr, uint32_t val)
+{
+    EHCIState *ehci = ptr;
+    addr -= ehci->mem_base;
+    EHCIPort *port;
+    USBDevice *dev;
+    uint32_t old_val;
+    int i, portnum;
+
+    if (addr >= 0x50 && addr < 0x50 + NB_PORTS * 4) {
+        portnum = (addr - 0x50) >> 2;
+        port = &ehci->ports[portnum];
+        old_val = port->ctrl;
+
+        port->ctrl &= ~EHCI_PORT_MASK;
+        port->ctrl |= (val & EHCI_PORT_MASK);
+
+        /* prevent enabling port but let disabling it */
+        if ((val & EHCI_PRT_PED) && !(old_val & EHCI_PRT_PED)) {
+            port->ctrl &= ~EHCI_PRT_PED;
+        }
+
+        /* if configured_flag is 0, make sure port owner is 1 */ 
+        if (!ehci->configured_flag && !(val & EHCI_PRT_PO)) {
+            port->ctrl |= EHCI_PRT_PO;
+        }
+
+        /* handle bits cleared by write */
+        port->ctrl &= ~(val & EHCI_PORT_WC_MASK);
+
+        dprintf("ehci_write: port[%d]=0x%.8x(0x%.8x) val=0x%.8x"
+                "(w=0x%.6x wc=0x%.2x) pr=%d(%d) status-hch=%d\n",
+                portnum, port->ctrl, old_val, val, val & EHCI_PORT_MASK,
+                val & EHCI_PORT_WC_MASK, BIT(val, EHCI_PRT_PR),
+                BIT(old_val, EHCI_PRT_PR), BIT(ehci->status, EHCI_STS_HCH));
+
+        if ((val & EHCI_PRT_PR) && !(old_val & EHCI_PRT_PR)) {
+            /* send reset on the USB bus */
+            for(i = 0; i < NB_PORTS; i++) {
+                port = &ehci->ports[i];
+                dev = port->port.dev;
+                if (dev) {
+                    usb_send_msg(dev, USB_MSG_RESET);
+                    /* enable only high-speed ports, only after reset */
+                    if ( (dev->speed == USB_SPEED_HIGH) &&
+                         !(port->ctrl & EHCI_PRT_PED)) {
+                        port->ctrl |= (EHCI_PRT_PED | EHCI_PRT_PEDC);
+                    } else if ((dev->speed != USB_SPEED_HIGH) &&
+                               (port->ctrl & EHCI_PRT_PED)) {
+                        port->ctrl &= ~EHCI_PRT_PED;
+                        port->ctrl |= EHCI_PRT_PEDC;
+                    }
+                }
+            }
+            /* set Port Reset bit to zero when reset is complete */
+            ehci->ports[portnum].ctrl &= ~EHCI_PRT_PR;
+        }
+
+        return;
+    }
+
+    switch (addr) {
+    case 0x0c:
+        dprintf("ehci_write: command=0x%.8x(0x%.8x) rs=%d(%d) hcreset=%d(%d) "
+                "status-hch=%d\n", val, ehci->command, BIT(val, EHCI_CMD_RS),
+                BIT(ehci->command, EHCI_CMD_RS), BIT(val, EHCI_CMD_HCRESET), 
+                BIT(ehci->command, EHCI_CMD_HCRESET),
+                BIT(ehci->status, EHCI_STS_HCH));
+
+        old_val = ehci->command;
+        ehci->command = val & EHCI_COMMAND_MASK;
+
+        /* Run/Stop */
+        if ((val & EHCI_CMD_RS) && !(old_val & EHCI_CMD_RS)) {
+            /* start frame processing */
+            qemu_mod_timer(ehci->frame_timer, qemu_get_clock(vm_clock));
+            ehci->status &= ~EHCI_STS_HCH;
+        } else if (!(val & EHCI_CMD_RS)) {
+            /* FIXME: complete active transactions */
+            ehci->status |= EHCI_STS_HCH;
+        }
+
+        /* HC Reset - without usb reset on downstream ports */
+        if (val & EHCI_CMD_HCRESET) {
+            ehci_reset(ehci);
+        }
+
+        /* update periodic and async schedule status */
+        if (val & EHCI_CMD_PSE) {
+            ehci->status |= EHCI_STS_PSSTAT;
+        } else {
+            ehci->status &= ~EHCI_STS_PSSTAT;
+        }
+
+        if (val & EHCI_CMD_ASE) {
+            ehci->status |= EHCI_STS_ASS;
+        } else {
+            ehci->status &= ~EHCI_STS_ASS;
+        }
+        break;
+    case 0x10:
+        old_val = ehci->status;
+        /* software sets a bit to 0 in this register by writing 1 to it */
+        ehci->status &= ~(val & EHCI_STATUS_MASK);
+        dprintf("ehci_write: status=0x%.8x(0x%.8x) val=0x%.8x\n", ehci->status,
+                old_val, val);
+        ehci_update_irq(ehci);
+        break;
+    case 0x14:
+        dprintf("ehci_write: intr=0x%.8x(0x%.8x)\n", val, ehci->intr_enable);
+        ehci->intr_enable = val & EHCI_INTR_MASK;
+        break;
+    case 0x18:
+        dprintf("ehci_write: index=0x%.8x(0x%.8x)\n", val, ehci->frame_index);
+        ehci->frame_index = val & EHCI_FRAME_INDEX_MASK;
+        break;
+    case 0x1c:
+        dprintf("ehci_write: segment=0x%.8x(0x%.8x)\n", val, ehci->segment);
+        ehci->segment = val;
+        break;
+    case 0x20:
+        dprintf("ehci_write: list=0x%.8x(0x%.8x)\n", val, ehci->frame_list);
+        ehci->frame_list = val & EHCI_FRAME_LIST_MASK;
+        break;
+    case 0x24:
+        dprintf("ehci_write: async=0x%.8x(0x%.8x)\n", val, ehci->async_next);
+        ehci->async_next = val & EHCI_ASYNC_NEXT_MASK;
+        break;
+    case 0x4c:
+        dprintf("ehci_write: cfg=0x%.8x(0x%.8x)\n", val, ehci->configured_flag);
+        old_val = ehci->configured_flag;
+        ehci->configured_flag = val & EHCI_CONFIGURED_FLAG_MASK;
+        if (ehci->configured_flag != old_val) {
+            for(i = 0; i < NB_PORTS; i++) {
+                port = &ehci->ports[i];
+                if (ehci->configured_flag) {
+                    port->ctrl &= ~EHCI_PRT_PO;
+                } else {
+                    port->ctrl |= EHCI_PRT_PO;
+                }
+            }
+        }
+        break;
+    default:
+        dprintf("ehci_write: Bad offset 0x%.8x val=0x%.8x\n", (int)addr, val);
+    }
+}
+
+static CPUReadMemoryFunc *ehci_readfn[3]={
+    ehci_mem_read,
+    ehci_mem_read,
+    ehci_mem_read
+};
+
+static CPUWriteMemoryFunc *ehci_writefn[3]={
+    ehci_mem_write,
+    ehci_mem_write,
+    ehci_mem_write
+};
+
+/* FIXME: do something smart here except logging */ 
+static int ehci_handle_periodic_schedule(EHCIState *ehci,
+                                         uint32_t frame_index_mask)
+{
+    uint32_t frame_addr, link;
+    int typ;
+
+    frame_addr = ehci->frame_list |
+        ((ehci->frame_index & frame_index_mask) >> 1);
+
+    dprintf("handle_periodic: list=0x%.8x index=0x%.3x addr=0x%.8x\n",
+            ehci->frame_list, ehci->frame_index, frame_addr);
+
+    cpu_physical_memory_read(frame_addr, (uint8_t *)&link, 4);
+    le32_to_cpus(&link);
+    typ = EHCI_BM(link, LINK_TYP);
+
+    dprintf("handle_periodic: link=0x%.8x ptr=0x%.8x z=%d typ=%d t=%d\n", link,
+            link & EHCI_LINK_PTR, link & EHCI_LINK_Z, typ, link & EHCI_LINK_T);
+
+    if (link & EHCI_LINK_T) {
+        dprintf("handle_periodic: T-Bit=1\n");
+        return 1;
+    }
+
+    return 0;
+}
+
+static int ehci_fetch_qh(EHCIState *ehci)
+{
+    ehci_qh *qh = &ehci->async_qh;
+    cpu_physical_memory_read(ehci->async_qh_addr, (uint8_t *)qh, sizeof(*qh));
+
+    /* QH H-bit=1  */
+    if (qh->info1 & EHCI_QH_H) {
+        /* update nak state */
+        if (ehci->nak_state == EHCI_NAK_STATE_WAIT_FOR_HEAD) {
+            ehci->nak_state = EHCI_NAK_STATE_DO_RELOAD;
+        } else if (ehci->nak_state == EHCI_NAK_STATE_DO_RELOAD) {
+            ehci->nak_state = EHCI_NAK_STATE_WAIT_FOR_START;
+        }
+
+        /* if reclamation==0, it's an empty list */ 
+        if (!(ehci->status & EHCI_STS_RECL)) {
+            dprintf("handle_async: empty list\n");
+            ehci->async_state = EHCI_ASYNC_STATE_SLEEPING;
+            /* set 10usec retry timer */
+            qemu_mod_timer(ehci->async_timer, qemu_get_clock(vm_clock) +
+                           (ticks_per_sec / ASYNC_SLEEP_TIMER_FREQ));
+            return 1;
+        } else {
+            /* set reclamation=0 */
+            ehci->status &= ~EHCI_STS_RECL;
+        }
+    }
+
+    return 0;
+}
+
+static int ehci_advance_queue(EHCIState *ehci)
+{
+    ehci_qh *qh = &ehci->async_qh;
+    uint32_t curr_qtd, old_token;
+    ehci_qtd qtd;
+    int rl, eps, i;
+    
+    if ((qh->token & EHCI_QH_TOTAL_BYTES_MASK) && !(qh->alt_next & EHCI_QH_T)) {
+        curr_qtd = qh->alt_next & EHCI_LINK_PTR;
+    } else if (!(qh->qtd_next & EHCI_QH_T)){
+        curr_qtd = qh->qtd_next & EHCI_LINK_PTR;
+    } else {
+        return 1;
+    }
+
+    cpu_physical_memory_read(curr_qtd, (uint8_t *)&qtd, sizeof(qtd));
+    if (qtd.token & EHCI_QH_STS_ACTIVE) {
+        qh->current = curr_qtd;
+    } else {
+        return 1;
+    }
+
+    /* perform overlay */
+    qh->qtd_next = qtd.next;
+    qh->alt_next = qtd.alt_next;
+
+    rl = EHCI_BM(qh->info1, QH_RL);
+    EHCI_SET_BM(qh->alt_next, QH_NAK_CNT, rl);
+
+    old_token = qh->token;
+    qh->token = qtd.token;
+
+    if (qh->info1 & EHCI_QH_DTC) {
+        if (qtd.token & EHCI_QH_DT) {
+            qh->token |= EHCI_QH_DT;
+        } else {
+            qh->token &= ~EHCI_QH_DT;
+        }
+    }
+
+    eps = EHCI_BM(qh->info1, QH_EPS);
+    if (eps == EHCI_QH_EPS_HIGH) {
+        if (old_token & EHCI_QH_STS_PING) {
+            qh->token |= EHCI_QH_STS_PING;
+        } else {
+            qh->token &= ~EHCI_QH_STS_PING;
+        }
+    }
+
+    for (i = 0; i < 5; i++) {
+        qh->buf[i] = qtd.buf[i];
+    }
+
+    qh->buf[1] &= ~EHCI_QH_C_PROG_MASK;
+    qh->buf[2] &= ~EHCI_QH_FRAME_TAG;
+
+    cpu_physical_memory_write(ehci->async_qh_addr, (uint8_t *)qh, sizeof(*qh));
+    return 0;
+}
+
+static void ehci_async_complete_packet(USBPacket *packet, void *opaque);
+
+static int ehci_exec_transaction(EHCIState *ehci)
+{
+    ehci_qh *qh = &ehci->async_qh;
+    USBDevice *dev;
+    const char *pidstr;
+    uint32_t curr_offset, buf_addr;
+    int dir, pid, rl = 0;
+    int max_len, total_bytes;
+    int len, len_done, len_todo;
+    int devaddr, devep;
+    int c_page, c_err, nak_cnt;
+    int dev_ret, ret, i;
+    int inc_page;
+
+    /* async transfer */
+    /* FIXME: handle interrupt transfers as well*/
+    if (!(qh->info2 & EHCI_QH_S_MASK)) {
+        /* reload nak counter if required */
+        if (ehci->nak_state == EHCI_NAK_STATE_DO_RELOAD) {
+            rl = EHCI_BM(qh->info1, QH_RL);
+            if (rl) {
+                EHCI_SET_BM(qh->alt_next, QH_NAK_CNT, rl);
+            }
+        }
+
+        if (rl && !EHCI_BM(qh->alt_next, QH_NAK_CNT)) {
+            return 1;
+        }
+    }
+
+    /* transaction is executed, so set reclamation=1 */
+    ehci->status |= EHCI_STS_RECL;
+
+    dir = EHCI_BM(qh->token, QH_PID_CODE);
+    switch (dir) {
+    case EHCI_QH_PID_OUT:
+        pid = USB_TOKEN_OUT;
+        pidstr = "OUT";
+        break;
+    case EHCI_QH_PID_IN:
+        pid = USB_TOKEN_IN;
+        pidstr = "IN";
+        break;
+    case EHCI_QH_PID_SETUP:
+        pid = USB_TOKEN_SETUP;
+        pidstr = "SETUP";
+        break;
+    default:
+        dprintf("exec_transaction: Bad direction %d\n", dir);
+        return 1;
+    }
+
+    devaddr = qh->info1 & EHCI_QH_DEV_ADDR;
+    devep = EHCI_BM(qh->info1, QH_ENDPT);
+
+    /* FIXME: handle zero length transactions */
+    c_page = EHCI_BM(qh->token, QH_C_PAGE);
+    if (c_page > 4) {
+        dprintf("exec_transaction: Bad c_page %d\n", c_page);
+        return 1;
+    }
+
+    curr_offset = EHCI_BM(qh->buf[0], QH_CURR_OFFSET); 
+    buf_addr = (qh->buf[c_page] & EHCI_QH_BUFFER_POINTER) | curr_offset;
+
+    max_len = PAGE_SIZE - curr_offset + (4 - c_page) * PAGE_SIZE;
+    if (max_len > MAX_TX_SIZE) {
+        max_len = MAX_TX_SIZE;
+    }
+
+    total_bytes = EHCI_BM(qh->token, QH_TOTAL_BYTES);
+    if (total_bytes < max_len) {
+        len = total_bytes;
+    } else {
+        len = max_len;
+    }
+
+    /* if pending async call, jump to async return */
+    if (ehci->pending_qh)
+        goto async_return;
+
+    if (dir != EHCI_QH_PID_IN) {
+        if (curr_offset + len < PAGE_SIZE) {
+            cpu_physical_memory_read(buf_addr, ehci->usb_buf, len);
+        } else {
+            /* handle transaction that spans a page boundary */
+            len_done = PAGE_SIZE - curr_offset;
+            cpu_physical_memory_read(buf_addr, ehci->usb_buf, len_done);
+            inc_page = 1;
+
+            while (len_done < len) {
+                /* update current page */
+                c_page++;
+                EHCI_SET_BM(qh->token, QH_C_PAGE, c_page);
+                /* zero offset for the second part */
+                buf_addr = qh->buf[c_page] & EHCI_QH_BUFFER_POINTER;
+
+                if (len - len_done < PAGE_SIZE) {
+                    len_todo = len - len_done;
+                    inc_page = 0;
+                } else {
+                    len_todo = PAGE_SIZE;
+                }
+                cpu_physical_memory_read(buf_addr, ehci->usb_buf + len_done,
+                                         len_todo);
+                len_done += len_todo;
+            }
+
+            /* handle the case of exact page completion */
+            if (inc_page && (c_page < 4)) {
+                c_page++;
+                EHCI_SET_BM(qh->token, QH_C_PAGE, c_page);
+            }
+        }
+    }
+
+#ifdef DEBUG_PACKET
+    dprintf("exec_transaction: frame=%d pid=%s devaddr=%d devep=%d len=%d "
+            "max_len=%d total_bytes=%d\n", ehci->frame_index, pidstr, devaddr,
+            devep, len, max_len, total_bytes);
+
+    if (dir != EHCI_QH_PID_IN) {
+        dprintf("exec_transaction: c_page=%d buf_addr=0x%.8x usb_buf=", c_page,
+                buf_addr);
+        for(i = 0; i < len; i++) {
+            dprintf(" %.2x", ehci->usb_buf[i]);
+        }
+        dprintf("\n");
+    }
+#endif
+
+    ehci->usb_packet.pid = pid;
+    ehci->usb_packet.devaddr = devaddr; 
+    ehci->usb_packet.devep = devep;
+    ehci->usb_packet.data = ehci->usb_buf;
+    ehci->usb_packet.len = len;
+    ehci->usb_packet.complete_cb = ehci_async_complete_packet;
+    ehci->usb_packet.complete_opaque = ehci;
+
+    dev_ret = USB_RET_NODEV;
+    for (i = 0; (i < NB_PORTS) && (dev_ret == USB_RET_NODEV); i++) {
+        if (ehci->ports[i].ctrl & EHCI_PRT_PED) {
+            dev =  ehci->ports[i].port.dev;
+            dev_ret = dev->handle_packet(dev, &ehci->usb_packet);
+        }
+    }
+
+#ifdef DEBUG_PACKET
+    dprintf("exec_transaction: dev_ret=%d\n", dev_ret);
+#endif
+
+    if (dev_ret == USB_RET_ASYNC) {
+        ehci->pending_qh = 1;
+        return 2;
+    }
+
+async_return:
+    if (ehci->pending_qh) {
+        dev_ret = ehci->pending_qh;
+    }
+
+    if (dev_ret >= 0) {
+        if (dir == EHCI_QH_PID_IN) {
+            if (dev_ret <= len) {
+                len = dev_ret;
+            } else {
+                dev_ret = USB_RET_BABBLE;
+            }
+        }
+
+        /* decrease actual length from total bytes to transfer */
+        total_bytes -= len;
+        EHCI_SET_BM(qh->token, QH_TOTAL_BYTES, total_bytes);
+
+        /* handle interrupt on completion */
+        if (BIT(qh->token, EHCI_QH_IOC) && !total_bytes) {
+            ehci->status |= EHCI_STS_USBINT;          
+        }
+
+        /* update data toggle bit */
+        if (qh->token & EHCI_QH_DT) {
+            qh->token &= ~EHCI_QH_DT;
+        } else {
+            qh->token |= EHCI_QH_DT;
+        }
+
+        if (dir == EHCI_QH_PID_IN) {
+            /* FIXME: merge code with the cpu_physical_memory_reads */
+            if (curr_offset + len < PAGE_SIZE) {
+                cpu_physical_memory_write(buf_addr, ehci->usb_buf, len);
+            } else {
+                /* handle transaction that spans a page boundary */
+                len_done = PAGE_SIZE - curr_offset;
+                cpu_physical_memory_write(buf_addr, ehci->usb_buf, len_done);
+                inc_page = 1;
+
+                while (len_done < len) {
+                    /* update current page */
+                    c_page++;
+                    EHCI_SET_BM(qh->token, QH_C_PAGE, c_page);
+                    /* zero offset for the second part */
+                    buf_addr = qh->buf[c_page] & EHCI_QH_BUFFER_POINTER;
+
+                    if (len - len_done < PAGE_SIZE) {
+                        len_todo = len - len_done;
+                        inc_page = 0;
+                    } else {
+                        len_todo = PAGE_SIZE;
+                    }
+                    cpu_physical_memory_write(buf_addr, ehci->usb_buf + len_done,
+                                             len_todo);
+                    len_done += len_todo;
+                }
+
+                /* handle the case of exact page completion */
+                if (inc_page && (c_page < 4)) {
+                    c_page++;
+                    EHCI_SET_BM(qh->token, QH_C_PAGE, c_page);
+                }
+            }
+#ifdef DEBUG_PACKET
+            dprintf("exec_transaction: usb_buf=");
+            for (i = 0; i < len; i++)
+                dprintf(" %.2x", ehci->usb_buf[i]);
+            dprintf("\n");
+#endif
+        }
+
+        /* update current offset */
+        curr_offset = (curr_offset + len) % PAGE_SIZE;
+        EHCI_SET_BM(qh->buf[0], QH_CURR_OFFSET, curr_offset);
+    } 
+
+    if (dev_ret >= 0) {
+        /* FIXME: handle successful transaction */
+        ret = 0;
+
+        /* set the active bit according to event */
+        if (!total_bytes ||
+            ((dir == EHCI_QH_PID_IN) && (len < max_len))) {
+            EHCI_SET_BM(qh->token, QH_STATUS, 0);
+        } else {
+            EHCI_SET_BM(qh->token, QH_STATUS, EHCI_QH_STS_ACTIVE);
+        }
+    } else {
+        /* FIXME: return the same value for all errors? same handling? */
+        ret = 1;
+
+        /* decrement error counter */
+        c_err = EHCI_BM(qh->token, QH_CERR);
+        c_err--;
+        EHCI_SET_BM(qh->token, QH_CERR, c_err);
+
+        /* active bit is set to 0 for all the errors */
+        switch (dev_ret) {
+        case USB_RET_NODEV:
+            dprintf("exec_transaction: NODEV\n");
+            EHCI_SET_BM(qh->token, QH_STATUS, EHCI_QH_STS_XACT);
+            break;
+        case USB_RET_NAK:
+            if ((dir == EHCI_QH_PID_IN) && (EHCI_BM(qh->info1, QH_RL))) {
+                /* decrement nak counter */
+                nak_cnt = EHCI_BM(qh->alt_next, QH_NAK_CNT);
+                nak_cnt--;
+                EHCI_SET_BM(qh->alt_next, QH_NAK_CNT, nak_cnt);
+            }
+            dprintf("exec_transaction: got NAK\n");
+            break;
+        case USB_RET_STALL:
+            dprintf("exec_transaction: got STALL\n");
+            EHCI_SET_BM(qh->token, QH_STATUS, EHCI_QH_STS_HALT);
+            break;
+        case USB_RET_BABBLE:
+            dprintf("exec_transaction: got BABBLE\n");
+            EHCI_SET_BM(qh->token, QH_STATUS, EHCI_QH_STS_BABBLE |
+                        EHCI_QH_STS_HALT);
+            break;
+        default:
+            dprintf("exec_transaction: Bad device response %d\n", dev_ret);
+            EHCI_SET_BM(qh->token, QH_STATUS, EHCI_QH_STS_XACT);
+            break;
+        }
+
+        /* if error counter is 0, set halted bit to 1 */
+        if (c_err == 0) {
+            dprintf("exec_transaction: c_err = 0\n");
+            qh->token |= EHCI_QH_STS_HALT;
+        }
+    }
+  
+    cpu_physical_memory_write(ehci->async_qh_addr, (uint8_t *)qh, sizeof(*qh));
+    return ret;
+}
+
+static int ehci_write_back_qtd(EHCIState *ehci)
+{
+    ehci_qh *qh = &ehci->async_qh;
+    uint32_t qtd_token;
+
+    cpu_physical_memory_read(qh->current + offsetof(ehci_qtd, token),
+                             (uint8_t *)&qtd_token, sizeof(qtd_token));
+    qtd_token &= ~EHCI_QTD_WRITE_BACK;
+    qtd_token |= (qh->token & EHCI_QTD_WRITE_BACK);
+    cpu_physical_memory_write(qh->current + offsetof(ehci_qtd, token),
+                              (uint8_t *)&qtd_token, sizeof(qtd_token));
+    return 0;
+}
+
+static void ehci_async_complete_packet(USBPacket *packet, void *opaque)
+{
+    EHCIState *ehci = opaque;
+    ehci_qh *qh = &ehci->async_qh;
+
+    /* FIXME: keep packet len elsewhere */
+    ehci->pending_qh = packet->len;
+    if (!ehci_exec_transaction(ehci)) {
+        if (!ACTIVE_QH(qh)) {
+            ehci_write_back_qtd(ehci);
+        }
+        ehci->async_qh_addr = qh->next & EHCI_LINK_PTR;
+    }
+   
+    ehci->pending_qh = 0;
+}
+
+static int ehci_qh_state_machine(EHCIState *ehci)
+{
+    ehci_qh *qh = &ehci->async_qh;
+    int state = EHCI_QH_STATE_FETCH_QH;
+    int ret = 0;
+
+    while (state != EHCI_QH_STATE_DONE) {
+        switch (state) {
+        case EHCI_QH_STATE_FETCH_QH:
+            ret = ehci_fetch_qh(ehci);
+            if (ret) {
+                state = EHCI_QH_STATE_DONE;
+            } else if (!HALTED_QH(qh)) {
+                if (ACTIVE_QH(qh)) {
+                    state = EHCI_QH_STATE_EXEC_TRANSACTION;
+                } else {
+                    state = EHCI_QH_STATE_ADVANCE_QUEUE;
+                } 
+            } else {
+                state = EHCI_QH_STATE_FOLLOW_QHLP;
+            }
+            break;
+        case EHCI_QH_STATE_ADVANCE_QUEUE:
+            ret = ehci_advance_queue(ehci);
+            if (!ret && ACTIVE_QH(qh)) {
+                state = EHCI_QH_STATE_EXEC_TRANSACTION;
+            } else {
+                state = EHCI_QH_STATE_FOLLOW_QHLP;
+            }
+            break;
+        case EHCI_QH_STATE_EXEC_TRANSACTION:
+            ret = ehci_exec_transaction(ehci);
+            if (ret) {
+                state = EHCI_QH_STATE_DONE;
+            } else if (ACTIVE_QH(qh)) {
+                state = EHCI_QH_STATE_FOLLOW_QHLP;
+            } else {
+                state = EHCI_QH_STATE_WRITE_BACK_QTD;
+            }    
+            break;
+        case EHCI_QH_STATE_WRITE_BACK_QTD:
+            ehci_write_back_qtd(ehci);
+            state = EHCI_QH_STATE_FOLLOW_QHLP;
+            break;
+        case EHCI_QH_STATE_FOLLOW_QHLP:
+            ehci->async_qh_addr = qh->next & EHCI_LINK_PTR;
+            state = EHCI_QH_STATE_DONE;
+            break;
+        case EHCI_QH_STATE_DONE:
+            break;
+        default:
+            dprintf("xxx: unknown state %x\n", state);
+        }
+    }
+
+    return ret;
+}
+
+/* FIXME: detect & exit on EOF1,2 */
+static int ehci_handle_async_schedule(EHCIState *ehci)
+{
+    int ret;
+
+    ehci->async_state = EHCI_ASYNC_STATE_ACTIVE;
+
+    /* on async schedule start, set reclamation=1 */
+    ehci->status |= EHCI_STS_RECL;
+
+    /* on start event, set nak_state to wait for QH with H-bit=1 */
+    ehci->nak_state = EHCI_NAK_STATE_WAIT_FOR_HEAD;
+
+    ehci->async_qh_addr = ehci->async_next & EHCI_LINK_PTR;
+
+    /* FIXME: re-enter qh-state-machine while (ehci->command & EHCI_CMD_ASE)
+     *        and microframe is not over yet... */
+    ret = ehci_qh_state_machine(ehci);
+
+    /* retain last accessed QH's QH.next for next visit */
+    if (ret != 2) {
+        ehci->async_next = ehci->async_qh.next & EHCI_LINK_PTR;
+    }
+
+    /* FIXME: set async advance interrupt */
+    if (ehci->command & EHCI_CMD_IAAD) {
+        ehci->status |= EHCI_STS_IAA;
+        ehci->command &= ~EHCI_CMD_IAAD; 
+    }
+
+    return 0;
+}
+
+static void ehci_async_timer(void *opaque)
+{
+    EHCIState *ehci = opaque;
+    ehci->async_state = EHCI_ASYNC_STATE_ACTIVE;
+
+    /* when async timer expires, set reclamation=1 */
+    ehci->status |= EHCI_STS_RECL;
+
+    /* on start event, set nak_state to wait for QH with H-bit=1 */
+    ehci->nak_state = EHCI_NAK_STATE_WAIT_FOR_HEAD;
+}
+
+static void ehci_frame_timer(void *opaque)
+{
+    EHCIState *ehci = opaque;
+    uint32_t frame_index_mask, rollover_mask;
+    uint32_t old_index;
+    int64_t expire_time;
+    int fls, itc;
+
+    /* FIXME: use EOF1,2 instead as end of microframe?  */
+    if (ehci->async_state == EHCI_ASYNC_STATE_SLEEPING) {
+        qemu_del_timer(ehci->async_timer);
+    }
+    ehci->async_state = EHCI_ASYNC_STATE_NOT_ACTIVE;
+
+    if (ehci->command & EHCI_CMD_RS) {
+        /* prepare the timer for the next frame */
+        expire_time = qemu_get_clock(vm_clock) +
+            (ticks_per_sec / MICROFRAME_TIMER_FREQ);
+        qemu_mod_timer(ehci->frame_timer, expire_time);
+    } else {
+        qemu_del_timer(ehci->frame_timer);
+        /* set hchalted bit in status */
+        ehci->status |= EHCI_STS_HCH;
+        return;
+    }
+
+    if (ehci->frame_index == 0) {
+        dprintf("ehci_frame_timer: index=0x%.4x command=0x%.8x status=0x%.8x "
+                "rs=%d hch=%d psstat=%d ass=%d\n", ehci->frame_index,
+                ehci->command, ehci->status, BIT(ehci->command, EHCI_CMD_RS),
+                BIT(ehci->status, EHCI_STS_HCH),
+                BIT(ehci->status, EHCI_STS_PSSTAT),
+                BIT(ehci->status, EHCI_STS_ASS));
+    }
+
+    /* FIXME: Complete the previous frame  */
+    old_index = ehci->frame_index;
+    ehci->frame_index = (ehci->frame_index + 1) & EHCI_FRAME_INDEX_MASK;
+
+    fls = EHCI_BM(ehci->command, CMD_FLS);
+    switch (fls) {
+    case 0:
+        frame_index_mask = 0x1ff8;
+        rollover_mask = 0x2000;
+        break;
+    case 1:
+        frame_index_mask = 0xff8;
+        rollover_mask = 0x1000;
+        break;
+    case 2:
+        frame_index_mask = 0x7f8;
+        rollover_mask = 0x800;
+        break;
+    default:
+        dprintf("ehci_frame_timer: Bad FLS %x\n", fls);
+        return;
+    }
+
+    /* frame list rollover - not delayed to the next interrupt threshold */
+    if ((ehci->frame_index & rollover_mask) != (old_index & rollover_mask)) {
+        dprintf("ehci_frame_timer: index rollover %d->%d mask 0x%.4x 0x%.4x\n",
+                old_index & frame_index_mask, ehci->frame_index &
+                frame_index_mask, frame_index_mask, rollover_mask);
+        ehci_set_irq(ehci, EHCI_STS_FLR);
+    }
+
+    /* Handle Periodic Frame List */
+    if (ehci->status & EHCI_STS_PSSTAT) {
+        ehci_handle_periodic_schedule(ehci, frame_index_mask);
+    }
+
+    /* Handle Async List */
+    if (ehci->status & EHCI_STS_ASS) {
+        /* FIXME: handle more than one pending qh */
+        if (ehci->pending_qh == 0) {
+            ehci_handle_async_schedule(ehci);
+        }
+    }
+
+    /* interrupt threshold control */
+    itc = EHCI_BM(ehci->command, CMD_ITC);
+    if (itc && !(ehci->frame_index % itc)) {
+        ehci_update_irq(ehci);
+    }
+}
+
+static void ehci_map(PCIDevice *pci_dev, int region_num,
+                    uint32_t addr, uint32_t size, int type)
+{
+    EHCIState *ehci = (EHCIState *)pci_dev;
+    ehci->mem_base = addr;
+    cpu_register_physical_memory(addr, size, ehci->mem);
+}
+
+void usb_ehci_init(PCIBus *bus, int devfn)
+{
+    EHCIState *ehci;
+    int i;
+
+    ehci = (EHCIState *)pci_register_device(bus, "USB-EHCI", sizeof(EHCIState),
+                                            devfn, NULL, NULL);
+
+    if (ehci == NULL) {
+        dprintf("usb_ehci_init: Failed to register PCI device\n");
+        return;
+    }
+
+    /* Reset PCI configuration */
+    memcpy(ehci->dev.config, ehci_pci_config, sizeof(ehci_pci_config));
+    memcpy(ehci->dev.config + 0x60, ehci_specific_pci_config,
+           sizeof(ehci_specific_pci_config));
+    memcpy(ehci->dev.config + 0xdc, ehci_power_mgmt_config,
+           sizeof(ehci_power_mgmt_config));
+
+    ehci->mem = cpu_register_io_memory(0, ehci_readfn, ehci_writefn, ehci);
+
+    for(i = 0; i < NB_PORTS; i++) {
+        qemu_register_usb_port(&ehci->ports[i].port, ehci, i, ehci_attach);
+    }
+
+    ehci->frame_timer = qemu_new_timer(vm_clock, ehci_frame_timer, ehci);
+    ehci->async_timer = qemu_new_timer(vm_clock, ehci_async_timer, ehci);
+
+    /* Reset capability and operational registers */
+    qemu_register_reset(ehci_reset, ehci);
+    ehci_reset(ehci);
+
+    pci_register_io_region(&ehci->dev, 0, 0x400, PCI_ADDRESS_SPACE_MEM,
+                           ehci_map);
+}
+
diff -u -d -p -P -r qemu-head/Makefile.target qemu-merge/Makefile.target
--- qemu-head/Makefile.target	2008-01-07 15:28:23.000005000 +0200
+++ qemu-merge/Makefile.target	2008-01-07 17:59:47.353824381 +0200
@@ -425,7 +425,7 @@ endif
 VL_OBJS+= lsi53c895a.o
 
 # USB layer
-VL_OBJS+= usb-ohci.o
+VL_OBJS+= usb-ohci.o usb-ehci.o
 
 # EEPROM emulation
 VL_OBJS += eeprom93xx.o
diff -u -d -p -P -r qemu-head/usb-linux.c qemu-merge/usb-linux.c
--- qemu-head/usb-linux.c	2008-01-07 14:03:49.000000000 +0200
+++ qemu-merge/usb-linux.c	2008-01-07 17:49:25.000000000 +0200
@@ -52,7 +52,23 @@ static int usb_host_find_device(int *pbu
 
 //#define DEBUG
 //#define DEBUG_ISOCH
-//#define USE_ASYNCIO
+
+#ifdef DEBUG
+#define dprintf printf
+#else
+#define dprintf(...)
+#endif
+
+/* Defines that async i/o is used instead of blocking ioctls */
+#define USE_ASYNCIO
+
+/* Defines that only EHCI emulation is used - no UHCI/OHCI */
+//#define USE_EHCI
+
+/* Bulk transfers async i/o is currently supported only by EHCI emulation */
+#if defined(USE_EHCI) && defined(USE_ASYNCIO)
+#define USE_EHCI_BULK_ASYNCIO
+#endif
 
 #define USBDEVFS_PATH "/proc/bus/usb"
 #define PRODUCT_NAME_SZ 32
@@ -155,10 +171,8 @@ static int usb_host_update_interfaces(US
 
     i += dev_descr_len;
     while (i < dev->descr_len) {
-#ifdef DEBUG
-        printf("i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len,
-               dev->descr[i], dev->descr[i+1]);
-#endif
+        dprintf("i is %d, descr_len is %d, dl %d, dt %d\n", i, dev->descr_len,
+                dev->descr[i], dev->descr[i+1]);
         if (dev->descr[i+1] != USB_DT_CONFIG) {
             i += dev->descr[i];
             continue;
@@ -208,11 +222,8 @@ static int usb_host_update_interfaces(US
         }
     }
 
-#ifdef DEBUG
-    printf("usb_host: %d interfaces claimed for configuration %d\n",
-           nb_interfaces, configuration);
-#endif
-
+    dprintf("usb_host: %d interfaces claimed for configuration %d\n",
+            nb_interfaces, configuration);
     return 1;
 }
 
@@ -262,10 +273,8 @@ static int usb_host_handle_control(USBDe
         ret = ioctl(s->fd, USBDEVFS_SETINTERFACE, &si);
         usb_linux_update_endp_table(s);
     } else if (request == (DeviceOutRequest | USB_REQ_SET_CONFIGURATION)) {
-#ifdef DEBUG
-        printf("usb_host_handle_control: SET_CONFIGURATION request - "
-               "config %d\n", value & 0xff);
-#endif
+        dprintf("usb_host_handle_control: SET_CONFIGURATION request - "
+                "config %d\n", value & 0xff);
         if (s->configuration != (value & 0xff)) {
             s->configuration = (value & 0xff);
             intf_update_required = 1;
@@ -292,9 +301,7 @@ static int usb_host_handle_control(USBDe
         }
     } else {
         if (intf_update_required) {
-#ifdef DEBUG
-            printf("usb_host_handle_control: updating interfaces\n");
-#endif
+            dprintf("usb_host_handle_control: updating interfaces\n");
             usb_host_update_interfaces(s, value & 0xff);
         }
         return ret;
@@ -306,9 +313,9 @@ static int usb_host_handle_isoch(USBDevi
 static int usb_host_handle_data(USBDevice *dev, USBPacket *p)
 {
     USBHostDevice *s = (USBHostDevice *)dev;
-    struct usbdevfs_bulktransfer bt;
-    int ret;
+    struct usbdevfs_urb *urb, *purb = NULL;
     uint8_t devep = p->devep;
+    int ret;
 
     if (s->endp_table[p->devep - 1].type == USBDEVFS_URB_TYPE_ISO) {
         return usb_host_handle_isoch(dev, p);
@@ -318,25 +325,54 @@ static int usb_host_handle_data(USBDevic
        config descriptor */
     if (p->pid == USB_TOKEN_IN)
         devep |= 0x80;
-    bt.ep = devep;
-    bt.len = p->len;
-    bt.timeout = 50;
-    bt.data = p->data;
-    ret = ioctl(s->fd, USBDEVFS_BULK, &bt);
-    if (ret < 0) {
-        switch(errno) {
-        case ETIMEDOUT:
-            return USB_RET_NAK;
-        case EPIPE:
-        default:
-#ifdef DEBUG
-            printf("handle_data: errno=%d\n", errno);
+
+    urb = qemu_mallocz(sizeof(struct usbdevfs_urb));
+    if (!urb) {
+        printf("usb_host_handle_data: malloc failed\n");
+        return 0;
+    }
+
+    memset(urb, 0, sizeof(struct usbdevfs_urb));
+    urb->type = USBDEVFS_URB_TYPE_BULK;
+    urb->endpoint = devep;
+    urb->buffer = p->data;
+    urb->buffer_length = p->len;
+#ifdef USE_EHCI_BULK_ASYNCIO
+    urb->signr = SIG_ISOCOMPLETE;
+    urb->usercontext = s;
 #endif
-            return USB_RET_STALL;
+
+    ret = ioctl(s->fd, USBDEVFS_SUBMITURB, urb);
+    if (ret == 0) {
+        if (!add_pending_urb(urb)) {
+            printf("usb_host_handle_data: add_pending_urb failed %p\n", urb);
         }
     } else {
-        return ret;
+        printf("usb_host_handle_data: SUBMITURB ioctl=%d errno=%d\n", ret,
+               errno);
+        qemu_free(urb);
+        return 0;
     }
+
+#ifdef USE_EHCI_BULK_ASYNCIO
+    s->packet = p;
+    return USB_RET_ASYNC;
+#else
+    /* FIXME: NDELAY */
+    ret = ioctl(s->fd, USBDEVFS_REAPURB, &purb);
+    if (ret == 0) {
+        if (del_pending_urb(purb)) {
+            ret = purb->actual_length;
+            qemu_free(purb);
+        } else {
+            printf("usb_host_handle_data: del_pending_urb failed %p\n", purb);
+        }
+    } else {
+        printf("usb_host_handle_data: REAPURB ioctl=%d errno=%d\n", ret, errno);
+        ret = 0;
+    }
+    return ret;
+#endif
 }
 
 #ifdef USE_ASYNCIO
@@ -344,18 +380,25 @@ static void urb_completion_pipe_read(voi
 {
     USBHostDevice *s = opaque;
     USBPacket *p = s->packet;
-    PendingURB *pending_urb = NULL;
-    struct usbdevfs_urb *purb = NULL;
-    int len, ret;
+    PendingURB *pending_urb, *urb_eater;
+    struct usbdevfs_urb *purb, *urb;
+    int urb_size = sizeof(pending_urb);
+    int ret;
 
-    len = read(s->pipe_fds[0], &pending_urb, sizeof(pending_urb));
-    if (len != sizeof(pending_urb)) {
-        printf("urb_completion: error reading pending_urb, len=%d\n", len);
+    if (urb_size == read(s->pipe_fds[0], &pending_urb, urb_size)) {
+        /* if there are any other pending urbs in the pipe - eat them up */
+        while (urb_size == read(s->pipe_fds[0], &urb_eater, urb_size)) {
+            del_pending_urb(urb_eater->urb);
+            s->urbs_ready++;
+        }
+    } else {
+        printf("urb_completion: error reading pending_urb\n");
         return;
     }
 
     /* FIXME: handle pending_urb->status */
-    del_pending_urb(pending_urb->urb);
+    urb = pending_urb->urb;
+    del_pending_urb(urb);
 
     if (!p) {
         s->urbs_ready++;
@@ -369,12 +412,10 @@ static void urb_completion_pipe_read(voi
         return;
     }
 
-#ifdef DEBUG_ISOCH
-    if (purb == pending_urb->urb) {
-        printf("urb_completion: urb mismatch reaped=%p pending=%p\n",
-               purb, urb);
+    if (purb != urb) {
+        dprintf("urb_completion: urb mismatch reaped=%p pending=%p\n",
+                purb, urb);
     }
-#endif
 
     p->len = purb->actual_length;
     usb_packet_complete(p);
@@ -608,9 +649,6 @@ USBDevice *usb_host_device_open(const ch
     if (!dev)
         goto fail;
 
-#ifdef DEBUG_ISOCH
-    printf("usb_host_device_open %s\n", devname);
-#endif
     if (usb_host_find_device(&bus_num, &addr,
                              product_name, sizeof(product_name),
                              devname) < 0)
@@ -654,10 +692,7 @@ USBDevice *usb_host_device_open(const ch
         goto fail;
     }
 
-#ifdef DEBUG
-    printf("host USB device %d.%d grabbed\n", bus_num, addr);
-#endif
-
+    dprintf("host USB device %d.%d grabbed\n", bus_num, addr);
     ret = usb_linux_update_endp_table(dev);
     if (ret)
         goto fail;

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2008-01-08 17:14 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2008-01-07 17:41 [Qemu-devel] [PATCH] USB 2.0 EHCI emulation Arnon Gilboa
2008-01-08  1:30 ` Paul Brook
2008-01-08  8:31   ` Arnon Gilboa
2008-01-08  8:43   ` Dor Laor
2008-01-08 15:31     ` Paul Brook
2008-01-08 17:14       ` Avi Kivity

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).