* [Qemu-devel] [kvm-devel] [PATCH] USB 2.0 EHCI emulation
@ 2008-01-30 10:03 ` Arnon Gilboa
2008-02-28 19:46 ` [kvm-devel] [Qemu-devel] " Gerb Stralko
0 siblings, 1 reply; 5+ messages in thread
From: Arnon Gilboa @ 2008-01-30 10:03 UTC (permalink / raw)
To: qemu-devel, kvm-devel
[-- Attachment #1: Type: text/plain, Size: 2136 bytes --]
Hi,
Attached is a repost of the preliminary patch implementing USB 2.0 EHCI
emulation.
http://lists.gnu.org/archive/html/qemu-devel/2008-01/msg00144.html
Paul's comments regarding the timing resolution and accuracy problems is
more
than right. It is relevant for any device emulation which requires
hi-res timers.
However, from my experince, guests like WinXP can easily handle these
timing issues. Use this patch and try it by yourself. I believe that
with a few
more bits of code in usb-ehci.c, Linux and other guests will work with
the ehci
emulation as well.
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-30 10:57:54.000005000 +0200
+++ qemu-merge/Makefile.target 2008-01-30 11:02:35.472390081 +0200
@@ -434,7 +434,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] 5+ messages in thread