summaryrefslogtreecommitdiffstats
path: root/hw/usb/hcd-ehci.c
diff options
context:
space:
mode:
Diffstat (limited to 'hw/usb/hcd-ehci.c')
-rw-r--r--hw/usb/hcd-ehci.c1551
1 files changed, 735 insertions, 816 deletions
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c
index 104c21d315..320b7e7239 100644
--- a/hw/usb/hcd-ehci.c
+++ b/hw/usb/hcd-ehci.c
@@ -2,6 +2,11 @@
* QEMU USB EHCI Emulation
*
* Copyright(c) 2008 Emutex Ltd. (address@hidden)
+ * Copyright(c) 2011-2012 Red Hat, Inc.
+ *
+ * Red Hat Authors:
+ * Gerd Hoffmann <kraxel@redhat.com>
+ * Hans de Goede <hdegoede@redhat.com>
*
* EHCI project was started by Mark Burkley, with contributions by
* Niels de Vos. David S. Ahern continued working on it. Kevin Wolf,
@@ -22,40 +27,18 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
-#include "hw/hw.h"
-#include "qemu-timer.h"
-#include "hw/usb.h"
-#include "hw/pci.h"
-#include "monitor.h"
-#include "trace.h"
-#include "dma.h"
-
-#define EHCI_DEBUG 0
-
-#if EHCI_DEBUG
-#define DPRINTF printf
-#else
-#define DPRINTF(...)
-#endif
-
-/* internal processing - reset HC to try and recover */
-#define USB_RET_PROCERR (-99)
-
-#define MMIO_SIZE 0x1000
+#include "hw/usb/hcd-ehci.h"
/* Capability Registers Base Address - section 2.2 */
-#define CAPREGBASE 0x0000
-#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved
-#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version #
-#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params
-#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params
+#define CAPLENGTH 0x0000 /* 1-byte, 0x0001 reserved */
+#define HCIVERSION 0x0002 /* 2-bytes, i/f version # */
+#define HCSPARAMS 0x0004 /* 4-bytes, structural params */
+#define HCCPARAMS 0x0008 /* 4-bytes, capability params */
#define EECP HCCPARAMS + 1
-#define HCSPPORTROUTE1 CAPREGBASE + 0x000c
-#define HCSPPORTROUTE2 CAPREGBASE + 0x0010
+#define HCSPPORTROUTE1 0x000c
+#define HCSPPORTROUTE2 0x0010
-#define OPREGBASE 0x0020 // Operational Registers Base Address
-
-#define USBCMD OPREGBASE + 0x0000
+#define USBCMD 0x0000
#define USBCMD_RUNSTOP (1 << 0) // run / Stop
#define USBCMD_HCRESET (1 << 1) // HC Reset
#define USBCMD_FLS (3 << 2) // Frame List Size
@@ -69,7 +52,7 @@
#define USBCMD_ITC (0x7f << 16) // Int Threshold Control
#define USBCMD_ITC_SH 16 // Int Threshold Control Shift
-#define USBSTS OPREGBASE + 0x0004
+#define USBSTS 0x0004
#define USBSTS_RO_MASK 0x0000003f
#define USBSTS_INT (1 << 0) // USB Interrupt
#define USBSTS_ERRINT (1 << 1) // Error Interrupt
@@ -86,20 +69,17 @@
* Interrupt enable bits correspond to the interrupt active bits in USBSTS
* so no need to redefine here.
*/
-#define USBINTR OPREGBASE + 0x0008
+#define USBINTR 0x0008
#define USBINTR_MASK 0x0000003f
-#define FRINDEX OPREGBASE + 0x000c
-#define CTRLDSSEGMENT OPREGBASE + 0x0010
-#define PERIODICLISTBASE OPREGBASE + 0x0014
-#define ASYNCLISTADDR OPREGBASE + 0x0018
+#define FRINDEX 0x000c
+#define CTRLDSSEGMENT 0x0010
+#define PERIODICLISTBASE 0x0014
+#define ASYNCLISTADDR 0x0018
#define ASYNCLISTADDR_MASK 0xffffffe0
-#define CONFIGFLAG OPREGBASE + 0x0040
+#define CONFIGFLAG 0x0040
-#define PORTSC (OPREGBASE + 0x0044)
-#define PORTSC_BEGIN PORTSC
-#define PORTSC_END (PORTSC + 4 * NB_PORTS)
/*
* Bits that are reserved or are read-only are masked out of values
* written to us by software
@@ -129,11 +109,13 @@
#define FRAME_TIMER_FREQ 1000
#define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ)
+#define UFRAME_TIMER_NS (FRAME_TIMER_NS / 8)
#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
-#define NB_PORTS 6 // Number of downstream ports
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
#define MAX_QH 100 // Max allowable queue heads in a chain
+#define MIN_UFR_PER_TICK 24 /* Min frames to process when catching up */
+#define PERIODIC_ACTIVE 512 /* Micro-frames */
/* Internal periodic / asynchronous schedule state machine states
*/
@@ -167,274 +149,6 @@ typedef enum {
#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor
#define NLPTR_TYPE_FSTN 3 // frame span traversal node
-
-/* EHCI spec version 1.0 Section 3.3
- */
-typedef struct EHCIitd {
- uint32_t next;
-
- uint32_t transact[8];
-#define ITD_XACT_ACTIVE (1 << 31)
-#define ITD_XACT_DBERROR (1 << 30)
-#define ITD_XACT_BABBLE (1 << 29)
-#define ITD_XACT_XACTERR (1 << 28)
-#define ITD_XACT_LENGTH_MASK 0x0fff0000
-#define ITD_XACT_LENGTH_SH 16
-#define ITD_XACT_IOC (1 << 15)
-#define ITD_XACT_PGSEL_MASK 0x00007000
-#define ITD_XACT_PGSEL_SH 12
-#define ITD_XACT_OFFSET_MASK 0x00000fff
-
- uint32_t bufptr[7];
-#define ITD_BUFPTR_MASK 0xfffff000
-#define ITD_BUFPTR_SH 12
-#define ITD_BUFPTR_EP_MASK 0x00000f00
-#define ITD_BUFPTR_EP_SH 8
-#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f
-#define ITD_BUFPTR_DEVADDR_SH 0
-#define ITD_BUFPTR_DIRECTION (1 << 11)
-#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff
-#define ITD_BUFPTR_MAXPKT_SH 0
-#define ITD_BUFPTR_MULT_MASK 0x00000003
-#define ITD_BUFPTR_MULT_SH 0
-} EHCIitd;
-
-/* EHCI spec version 1.0 Section 3.4
- */
-typedef struct EHCIsitd {
- uint32_t next; // Standard next link pointer
- uint32_t epchar;
-#define SITD_EPCHAR_IO (1 << 31)
-#define SITD_EPCHAR_PORTNUM_MASK 0x7f000000
-#define SITD_EPCHAR_PORTNUM_SH 24
-#define SITD_EPCHAR_HUBADD_MASK 0x007f0000
-#define SITD_EPCHAR_HUBADDR_SH 16
-#define SITD_EPCHAR_EPNUM_MASK 0x00000f00
-#define SITD_EPCHAR_EPNUM_SH 8
-#define SITD_EPCHAR_DEVADDR_MASK 0x0000007f
-
- uint32_t uframe;
-#define SITD_UFRAME_CMASK_MASK 0x0000ff00
-#define SITD_UFRAME_CMASK_SH 8
-#define SITD_UFRAME_SMASK_MASK 0x000000ff
-
- uint32_t results;
-#define SITD_RESULTS_IOC (1 << 31)
-#define SITD_RESULTS_PGSEL (1 << 30)
-#define SITD_RESULTS_TBYTES_MASK 0x03ff0000
-#define SITD_RESULTS_TYBYTES_SH 16
-#define SITD_RESULTS_CPROGMASK_MASK 0x0000ff00
-#define SITD_RESULTS_CPROGMASK_SH 8
-#define SITD_RESULTS_ACTIVE (1 << 7)
-#define SITD_RESULTS_ERR (1 << 6)
-#define SITD_RESULTS_DBERR (1 << 5)
-#define SITD_RESULTS_BABBLE (1 << 4)
-#define SITD_RESULTS_XACTERR (1 << 3)
-#define SITD_RESULTS_MISSEDUF (1 << 2)
-#define SITD_RESULTS_SPLITXSTATE (1 << 1)
-
- uint32_t bufptr[2];
-#define SITD_BUFPTR_MASK 0xfffff000
-#define SITD_BUFPTR_CURROFF_MASK 0x00000fff
-#define SITD_BUFPTR_TPOS_MASK 0x00000018
-#define SITD_BUFPTR_TPOS_SH 3
-#define SITD_BUFPTR_TCNT_MASK 0x00000007
-
- uint32_t backptr; // Standard next link pointer
-} EHCIsitd;
-
-/* EHCI spec version 1.0 Section 3.5
- */
-typedef struct EHCIqtd {
- uint32_t next; // Standard next link pointer
- uint32_t altnext; // Standard next link pointer
- uint32_t token;
-#define QTD_TOKEN_DTOGGLE (1 << 31)
-#define QTD_TOKEN_TBYTES_MASK 0x7fff0000
-#define QTD_TOKEN_TBYTES_SH 16
-#define QTD_TOKEN_IOC (1 << 15)
-#define QTD_TOKEN_CPAGE_MASK 0x00007000
-#define QTD_TOKEN_CPAGE_SH 12
-#define QTD_TOKEN_CERR_MASK 0x00000c00
-#define QTD_TOKEN_CERR_SH 10
-#define QTD_TOKEN_PID_MASK 0x00000300
-#define QTD_TOKEN_PID_SH 8
-#define QTD_TOKEN_ACTIVE (1 << 7)
-#define QTD_TOKEN_HALT (1 << 6)
-#define QTD_TOKEN_DBERR (1 << 5)
-#define QTD_TOKEN_BABBLE (1 << 4)
-#define QTD_TOKEN_XACTERR (1 << 3)
-#define QTD_TOKEN_MISSEDUF (1 << 2)
-#define QTD_TOKEN_SPLITXSTATE (1 << 1)
-#define QTD_TOKEN_PING (1 << 0)
-
- uint32_t bufptr[5]; // Standard buffer pointer
-#define QTD_BUFPTR_MASK 0xfffff000
-#define QTD_BUFPTR_SH 12
-} EHCIqtd;
-
-/* EHCI spec version 1.0 Section 3.6
- */
-typedef struct EHCIqh {
- uint32_t next; // Standard next link pointer
-
- /* endpoint characteristics */
- uint32_t epchar;
-#define QH_EPCHAR_RL_MASK 0xf0000000
-#define QH_EPCHAR_RL_SH 28
-#define QH_EPCHAR_C (1 << 27)
-#define QH_EPCHAR_MPLEN_MASK 0x07FF0000
-#define QH_EPCHAR_MPLEN_SH 16
-#define QH_EPCHAR_H (1 << 15)
-#define QH_EPCHAR_DTC (1 << 14)
-#define QH_EPCHAR_EPS_MASK 0x00003000
-#define QH_EPCHAR_EPS_SH 12
-#define EHCI_QH_EPS_FULL 0
-#define EHCI_QH_EPS_LOW 1
-#define EHCI_QH_EPS_HIGH 2
-#define EHCI_QH_EPS_RESERVED 3
-
-#define QH_EPCHAR_EP_MASK 0x00000f00
-#define QH_EPCHAR_EP_SH 8
-#define QH_EPCHAR_I (1 << 7)
-#define QH_EPCHAR_DEVADDR_MASK 0x0000007f
-#define QH_EPCHAR_DEVADDR_SH 0
-
- /* endpoint capabilities */
- uint32_t epcap;
-#define QH_EPCAP_MULT_MASK 0xc0000000
-#define QH_EPCAP_MULT_SH 30
-#define QH_EPCAP_PORTNUM_MASK 0x3f800000
-#define QH_EPCAP_PORTNUM_SH 23
-#define QH_EPCAP_HUBADDR_MASK 0x007f0000
-#define QH_EPCAP_HUBADDR_SH 16
-#define QH_EPCAP_CMASK_MASK 0x0000ff00
-#define QH_EPCAP_CMASK_SH 8
-#define QH_EPCAP_SMASK_MASK 0x000000ff
-#define QH_EPCAP_SMASK_SH 0
-
- uint32_t current_qtd; // Standard next link pointer
- uint32_t next_qtd; // Standard next link pointer
- uint32_t altnext_qtd;
-#define QH_ALTNEXT_NAKCNT_MASK 0x0000001e
-#define QH_ALTNEXT_NAKCNT_SH 1
-
- uint32_t token; // Same as QTD token
- uint32_t bufptr[5]; // Standard buffer pointer
-#define BUFPTR_CPROGMASK_MASK 0x000000ff
-#define BUFPTR_FRAMETAG_MASK 0x0000001f
-#define BUFPTR_SBYTES_MASK 0x00000fe0
-#define BUFPTR_SBYTES_SH 5
-} EHCIqh;
-
-/* EHCI spec version 1.0 Section 3.7
- */
-typedef struct EHCIfstn {
- uint32_t next; // Standard next link pointer
- uint32_t backptr; // Standard next link pointer
-} EHCIfstn;
-
-typedef struct EHCIPacket EHCIPacket;
-typedef struct EHCIQueue EHCIQueue;
-typedef struct EHCIState EHCIState;
-
-enum async_state {
- EHCI_ASYNC_NONE = 0,
- EHCI_ASYNC_INFLIGHT,
- EHCI_ASYNC_FINISHED,
-};
-
-struct EHCIPacket {
- EHCIQueue *queue;
- QTAILQ_ENTRY(EHCIPacket) next;
-
- EHCIqtd qtd; /* copy of current QTD (being worked on) */
- uint32_t qtdaddr; /* address QTD read from */
-
- USBPacket packet;
- QEMUSGList sgl;
- int pid;
- uint32_t tbytes;
- enum async_state async;
- int usb_status;
-};
-
-struct EHCIQueue {
- EHCIState *ehci;
- QTAILQ_ENTRY(EHCIQueue) next;
- uint32_t seen;
- uint64_t ts;
- int async;
- int revalidate;
-
- /* cached data from guest - needs to be flushed
- * when guest removes an entry (doorbell, handshake sequence)
- */
- EHCIqh qh; /* copy of current QH (being worked on) */
- uint32_t qhaddr; /* address QH read from */
- uint32_t qtdaddr; /* address QTD read from */
- USBDevice *dev;
- QTAILQ_HEAD(, EHCIPacket) packets;
-};
-
-typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead;
-
-struct EHCIState {
- PCIDevice dev;
- USBBus bus;
- qemu_irq irq;
- MemoryRegion mem;
- int companion_count;
-
- /* properties */
- uint32_t maxframes;
-
- /*
- * EHCI spec version 1.0 Section 2.3
- * Host Controller Operational Registers
- */
- union {
- uint8_t mmio[MMIO_SIZE];
- struct {
- uint8_t cap[OPREGBASE];
- uint32_t usbcmd;
- uint32_t usbsts;
- uint32_t usbintr;
- uint32_t frindex;
- uint32_t ctrldssegment;
- uint32_t periodiclistbase;
- uint32_t asynclistaddr;
- uint32_t notused[9];
- uint32_t configflag;
- uint32_t portsc[NB_PORTS];
- };
- };
-
- /*
- * Internal states, shadow registers, etc
- */
- QEMUTimer *frame_timer;
- QEMUBH *async_bh;
- uint32_t astate; /* Current state in asynchronous schedule */
- uint32_t pstate; /* Current state in periodic schedule */
- USBPort ports[NB_PORTS];
- USBPort *companion_ports[NB_PORTS];
- uint32_t usbsts_pending;
- uint32_t usbsts_frindex;
- EHCIQueueHead aqueues;
- EHCIQueueHead pqueues;
-
- /* which address to look at next */
- uint32_t a_fetch_addr;
- uint32_t p_fetch_addr;
-
- USBPacket ipacket;
- QEMUSGList isgl;
-
- uint64_t last_run_ns;
- uint32_t async_stepdown;
-};
-
#define SET_LAST_RUN_CLOCK(s) \
(s)->last_run_ns = qemu_get_clock_ns(vm_clock);
@@ -466,25 +180,21 @@ static const char *ehci_state_names[] = {
};
static const char *ehci_mmio_names[] = {
- [CAPLENGTH] = "CAPLENGTH",
- [HCIVERSION] = "HCIVERSION",
- [HCSPARAMS] = "HCSPARAMS",
- [HCCPARAMS] = "HCCPARAMS",
[USBCMD] = "USBCMD",
[USBSTS] = "USBSTS",
[USBINTR] = "USBINTR",
[FRINDEX] = "FRINDEX",
[PERIODICLISTBASE] = "P-LIST BASE",
[ASYNCLISTADDR] = "A-LIST ADDR",
- [PORTSC_BEGIN] = "PORTSC #0",
- [PORTSC_BEGIN + 4] = "PORTSC #1",
- [PORTSC_BEGIN + 8] = "PORTSC #2",
- [PORTSC_BEGIN + 12] = "PORTSC #3",
- [PORTSC_BEGIN + 16] = "PORTSC #4",
- [PORTSC_BEGIN + 20] = "PORTSC #5",
[CONFIGFLAG] = "CONFIGFLAG",
};
+static int ehci_state_executing(EHCIQueue *q);
+static int ehci_state_writeback(EHCIQueue *q);
+static int ehci_state_advqueue(EHCIQueue *q);
+static int ehci_fill_queue(EHCIPacket *p);
+static void ehci_free_packet(EHCIPacket *p);
+
static const char *nr2str(const char **n, size_t len, uint32_t nr)
{
if (nr < len && n[nr] != NULL) {
@@ -499,7 +209,7 @@ static const char *state2str(uint32_t state)
return nr2str(ehci_state_names, ARRAY_SIZE(ehci_state_names), state);
}
-static const char *addr2str(target_phys_addr_t addr)
+static const char *addr2str(hwaddr addr)
{
return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
}
@@ -575,7 +285,12 @@ static inline void ehci_update_irq(EHCIState *s)
/* flag interrupt condition */
static inline void ehci_raise_irq(EHCIState *s, int intr)
{
- s->usbsts_pending |= intr;
+ if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) {
+ s->usbsts |= intr;
+ ehci_update_irq(s);
+ } else {
+ s->usbsts_pending |= intr;
+ }
}
/*
@@ -653,7 +368,7 @@ static int ehci_get_fetch_addr(EHCIState *s, int async)
return async ? s->a_fetch_addr : s->p_fetch_addr;
}
-static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh)
+static void ehci_trace_qh(EHCIQueue *q, hwaddr addr, EHCIqh *qh)
{
/* need three here due to argument count limits */
trace_usb_ehci_qh_ptrs(q, addr, qh->next,
@@ -671,7 +386,7 @@ static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh)
(bool)(qh->epchar & QH_EPCHAR_I));
}
-static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd)
+static void ehci_trace_qtd(EHCIQueue *q, hwaddr addr, EHCIqtd *qtd)
{
/* need three here due to argument count limits */
trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext);
@@ -688,7 +403,7 @@ static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd)
(bool)(qtd->token & QTD_TOKEN_XACTERR));
}
-static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd)
+static void ehci_trace_itd(EHCIState *s, hwaddr addr, EHCIitd *itd)
{
trace_usb_ehci_itd(addr, itd->next,
get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT),
@@ -697,13 +412,19 @@ static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd)
get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR));
}
-static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr,
+static void ehci_trace_sitd(EHCIState *s, hwaddr addr,
EHCIsitd *sitd)
{
trace_usb_ehci_sitd(addr, sitd->next,
(bool)(sitd->results & SITD_RESULTS_ACTIVE));
}
+static void ehci_trace_guest_bug(EHCIState *s, const char *message)
+{
+ trace_usb_ehci_guest_bug(message);
+ fprintf(stderr, "ehci warning: %s\n", message);
+}
+
static inline bool ehci_enabled(EHCIState *s)
{
return s->usbcmd & USBCMD_RUNSTOP;
@@ -719,6 +440,136 @@ static inline bool ehci_periodic_enabled(EHCIState *s)
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
}
+/* Get an array of dwords from main memory */
+static inline int get_dwords(EHCIState *ehci, uint32_t addr,
+ uint32_t *buf, int num)
+{
+ int i;
+
+ if (!ehci->dma) {
+ ehci_raise_irq(ehci, USBSTS_HSE);
+ ehci->usbcmd &= ~USBCMD_RUNSTOP;
+ trace_usb_ehci_dma_error();
+ return -1;
+ }
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ dma_memory_read(ehci->dma, addr, buf, sizeof(*buf));
+ *buf = le32_to_cpu(*buf);
+ }
+
+ return num;
+}
+
+/* Put an array of dwords in to main memory */
+static inline int put_dwords(EHCIState *ehci, uint32_t addr,
+ uint32_t *buf, int num)
+{
+ int i;
+
+ if (!ehci->dma) {
+ ehci_raise_irq(ehci, USBSTS_HSE);
+ ehci->usbcmd &= ~USBCMD_RUNSTOP;
+ trace_usb_ehci_dma_error();
+ return -1;
+ }
+
+ for (i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
+ uint32_t tmp = cpu_to_le32(*buf);
+ dma_memory_write(ehci->dma, addr, &tmp, sizeof(tmp));
+ }
+
+ return num;
+}
+
+static int ehci_get_pid(EHCIqtd *qtd)
+{
+ switch (get_field(qtd->token, QTD_TOKEN_PID)) {
+ case 0:
+ return USB_TOKEN_OUT;
+ case 1:
+ return USB_TOKEN_IN;
+ case 2:
+ return USB_TOKEN_SETUP;
+ default:
+ fprintf(stderr, "bad token\n");
+ return 0;
+ }
+}
+
+static bool ehci_verify_qh(EHCIQueue *q, EHCIqh *qh)
+{
+ uint32_t devaddr = get_field(qh->epchar, QH_EPCHAR_DEVADDR);
+ uint32_t endp = get_field(qh->epchar, QH_EPCHAR_EP);
+ if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) ||
+ (endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) ||
+ (qh->current_qtd != q->qh.current_qtd) ||
+ (q->async && qh->next_qtd != q->qh.next_qtd) ||
+ (memcmp(&qh->altnext_qtd, &q->qh.altnext_qtd,
+ 7 * sizeof(uint32_t)) != 0) ||
+ (q->dev != NULL && q->dev->addr != devaddr)) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static bool ehci_verify_qtd(EHCIPacket *p, EHCIqtd *qtd)
+{
+ if (p->qtdaddr != p->queue->qtdaddr ||
+ (p->queue->async && !NLPTR_TBIT(p->qtd.next) &&
+ (p->qtd.next != qtd->next)) ||
+ (!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd->altnext)) ||
+ p->qtd.token != qtd->token ||
+ p->qtd.bufptr[0] != qtd->bufptr[0]) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+static bool ehci_verify_pid(EHCIQueue *q, EHCIqtd *qtd)
+{
+ int ep = get_field(q->qh.epchar, QH_EPCHAR_EP);
+ int pid = ehci_get_pid(qtd);
+
+ /* Note the pid changing is normal for ep 0 (the control ep) */
+ if (q->last_pid && ep != 0 && pid != q->last_pid) {
+ return false;
+ } else {
+ return true;
+ }
+}
+
+/* Finish executing and writeback a packet outside of the regular
+ fetchqh -> fetchqtd -> execute -> writeback cycle */
+static void ehci_writeback_async_complete_packet(EHCIPacket *p)
+{
+ EHCIQueue *q = p->queue;
+ EHCIqtd qtd;
+ EHCIqh qh;
+ int state;
+
+ /* Verify the qh + qtd, like we do when going through fetchqh & fetchqtd */
+ get_dwords(q->ehci, NLPTR_GET(q->qhaddr),
+ (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
+ get_dwords(q->ehci, NLPTR_GET(q->qtdaddr),
+ (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
+ if (!ehci_verify_qh(q, &qh) || !ehci_verify_qtd(p, &qtd)) {
+ p->async = EHCI_ASYNC_INITIALIZED;
+ ehci_free_packet(p);
+ return;
+ }
+
+ state = ehci_get_state(q->ehci, q->async);
+ ehci_state_executing(q);
+ ehci_state_writeback(q); /* Frees the packet! */
+ if (!(q->qh.token & QTD_TOKEN_HALT)) {
+ ehci_state_advqueue(q);
+ }
+ ehci_set_state(q->ehci, q->async, state);
+}
+
/* packet management */
static EHCIPacket *ehci_alloc_packet(EHCIQueue *q)
@@ -735,9 +586,19 @@ static EHCIPacket *ehci_alloc_packet(EHCIQueue *q)
static void ehci_free_packet(EHCIPacket *p)
{
+ if (p->async == EHCI_ASYNC_FINISHED) {
+ ehci_writeback_async_complete_packet(p);
+ return;
+ }
trace_usb_ehci_packet_action(p->queue, p, "free");
+ if (p->async == EHCI_ASYNC_INITIALIZED) {
+ usb_packet_unmap(&p->packet, &p->sgl);
+ qemu_sglist_destroy(&p->sgl);
+ }
if (p->async == EHCI_ASYNC_INFLIGHT) {
usb_cancel_packet(&p->packet);
+ usb_packet_unmap(&p->packet, &p->sgl);
+ qemu_sglist_destroy(&p->sgl);
}
QTAILQ_REMOVE(&p->queue->packets, p, next);
usb_packet_cleanup(&p->packet);
@@ -761,14 +622,59 @@ static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
return q;
}
-static void ehci_free_queue(EHCIQueue *q)
+static void ehci_queue_stopped(EHCIQueue *q)
+{
+ int endp = get_field(q->qh.epchar, QH_EPCHAR_EP);
+
+ if (!q->last_pid || !q->dev) {
+ return;
+ }
+
+ usb_device_ep_stopped(q->dev, usb_ep_get(q->dev, q->last_pid, endp));
+}
+
+static int ehci_cancel_queue(EHCIQueue *q)
{
- EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
EHCIPacket *p;
+ int packets = 0;
- trace_usb_ehci_queue_action(q, "free");
- while ((p = QTAILQ_FIRST(&q->packets)) != NULL) {
+ p = QTAILQ_FIRST(&q->packets);
+ if (p == NULL) {
+ goto leave;
+ }
+
+ trace_usb_ehci_queue_action(q, "cancel");
+ do {
ehci_free_packet(p);
+ packets++;
+ } while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
+
+leave:
+ ehci_queue_stopped(q);
+ return packets;
+}
+
+static int ehci_reset_queue(EHCIQueue *q)
+{
+ int packets;
+
+ trace_usb_ehci_queue_action(q, "reset");
+ packets = ehci_cancel_queue(q);
+ q->dev = NULL;
+ q->qtdaddr = 0;
+ q->last_pid = 0;
+ return packets;
+}
+
+static void ehci_free_queue(EHCIQueue *q, const char *warn)
+{
+ EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
+ int cancelled;
+
+ trace_usb_ehci_queue_action(q, "free");
+ cancelled = ehci_cancel_queue(q);
+ if (warn && cancelled > 0) {
+ ehci_trace_guest_bug(q->ehci, warn);
}
QTAILQ_REMOVE(head, q, next);
g_free(q);
@@ -788,20 +694,10 @@ static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
return NULL;
}
-static void ehci_queues_tag_unused_async(EHCIState *ehci)
-{
- EHCIQueue *q;
-
- QTAILQ_FOREACH(q, &ehci->aqueues, next) {
- if (!q->seen) {
- q->revalidate = 1;
- }
- }
-}
-
static void ehci_queues_rip_unused(EHCIState *ehci, int async)
{
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ const char *warn = async ? "guest unlinked busy QH" : NULL;
uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4;
EHCIQueue *q, *tmp;
@@ -814,7 +710,19 @@ static void ehci_queues_rip_unused(EHCIState *ehci, int async)
if (ehci->last_run_ns < q->ts + maxage) {
continue;
}
- ehci_free_queue(q);
+ ehci_free_queue(q, warn);
+ }
+}
+
+static void ehci_queues_rip_unseen(EHCIState *ehci, int async)
+{
+ EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ EHCIQueue *q, *tmp;
+
+ QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
+ if (!q->seen) {
+ ehci_free_queue(q, NULL);
+ }
}
}
@@ -827,17 +735,18 @@ static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async)
if (q->dev != dev) {
continue;
}
- ehci_free_queue(q);
+ ehci_free_queue(q, NULL);
}
}
static void ehci_queues_rip_all(EHCIState *ehci, int async)
{
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
+ const char *warn = async ? "guest stopped busy async schedule" : NULL;
EHCIQueue *q, *tmp;
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
- ehci_free_queue(q);
+ ehci_free_queue(q, warn);
}
}
@@ -862,7 +771,6 @@ static void ehci_attach(USBPort *port)
*portsc |= PORTSC_CSC;
ehci_raise_irq(s, USBSTS_PCD);
- ehci_commit_irq(s);
}
static void ehci_detach(USBPort *port)
@@ -892,7 +800,6 @@ static void ehci_detach(USBPort *port)
*portsc |= PORTSC_CSC;
ehci_raise_irq(s, USBSTS_PCD);
- ehci_commit_irq(s);
}
static void ehci_child_detach(USBPort *port, USBDevice *child)
@@ -962,11 +869,24 @@ static int ehci_register_companion(USBBus *bus, USBPort *ports[],
}
s->companion_count++;
- s->mmio[0x05] = (s->companion_count << 4) | portcount;
+ s->caps[0x05] = (s->companion_count << 4) | portcount;
return 0;
}
+static void ehci_wakeup_endpoint(USBBus *bus, USBEndpoint *ep)
+{
+ EHCIState *s = container_of(bus, EHCIState, bus);
+ uint32_t portsc = s->portsc[ep->dev->port->index];
+
+ if (portsc & PORTSC_POWNER) {
+ return;
+ }
+
+ s->periodic_sched_active = PERIODIC_ACTIVE;
+ qemu_bh_schedule(s->async_bh);
+}
+
static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
{
USBDevice *dev;
@@ -1007,7 +927,8 @@ static void ehci_reset(void *opaque)
}
}
- memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
+ memset(&s->opreg, 0x00, sizeof(s->opreg));
+ memset(&s->portsc, 0x00, sizeof(s->portsc));
s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
s->usbsts = USBSTS_HALT;
@@ -1034,50 +955,43 @@ static void ehci_reset(void *opaque)
qemu_bh_cancel(s->async_bh);
}
-static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
+static uint64_t ehci_caps_read(void *ptr, hwaddr addr,
+ unsigned size)
{
EHCIState *s = ptr;
- uint32_t val;
-
- val = s->mmio[addr];
-
- return val;
+ return s->caps[addr];
}
-static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
+static uint64_t ehci_opreg_read(void *ptr, hwaddr addr,
+ unsigned size)
{
EHCIState *s = ptr;
uint32_t val;
- val = s->mmio[addr] | (s->mmio[addr+1] << 8);
+ switch (addr) {
+ case FRINDEX:
+ /* Round down to mult of 8, else it can go backwards on migration */
+ val = s->frindex & ~7;
+ break;
+ default:
+ val = s->opreg[addr >> 2];
+ }
+ trace_usb_ehci_opreg_read(addr + s->opregbase, addr2str(addr), val);
return val;
}
-static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
+static uint64_t ehci_port_read(void *ptr, hwaddr addr,
+ unsigned size)
{
EHCIState *s = ptr;
uint32_t val;
- val = s->mmio[addr] | (s->mmio[addr+1] << 8) |
- (s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24);
-
- trace_usb_ehci_mmio_readl(addr, addr2str(addr), val);
+ val = s->portsc[addr >> 2];
+ trace_usb_ehci_portsc_read(addr + PORTSC_BEGIN, addr >> 2, val);
return val;
}
-static void ehci_mem_writeb(void *ptr, target_phys_addr_t addr, uint32_t val)
-{
- fprintf(stderr, "EHCI doesn't handle byte writes to MMIO\n");
- exit(1);
-}
-
-static void ehci_mem_writew(void *ptr, target_phys_addr_t addr, uint32_t val)
-{
- fprintf(stderr, "EHCI doesn't handle 16-bit writes to MMIO\n");
- exit(1);
-}
-
static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
{
USBDevice *dev = s->ports[port].dev;
@@ -1106,11 +1020,17 @@ static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
}
}
-static void handle_port_status_write(EHCIState *s, int port, uint32_t val)
+static void ehci_port_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
{
+ EHCIState *s = ptr;
+ int port = addr >> 2;
uint32_t *portsc = &s->portsc[port];
+ uint32_t old = *portsc;
USBDevice *dev = s->ports[port].dev;
+ trace_usb_ehci_portsc_write(addr + PORTSC_BEGIN, addr >> 2, val);
+
/* Clear rwc bits */
*portsc &= ~(val & PORTSC_RWC_MASK);
/* The guest may clear, but not set the PED bit */
@@ -1142,39 +1062,20 @@ static void handle_port_status_write(EHCIState *s, int port, uint32_t val)
*portsc &= ~PORTSC_RO_MASK;
*portsc |= val;
+ trace_usb_ehci_portsc_change(addr + PORTSC_BEGIN, addr >> 2, *portsc, old);
}
-static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
+static void ehci_opreg_write(void *ptr, hwaddr addr,
+ uint64_t val, unsigned size)
{
EHCIState *s = ptr;
- uint32_t *mmio = (uint32_t *)(&s->mmio[addr]);
+ uint32_t *mmio = s->opreg + (addr >> 2);
uint32_t old = *mmio;
int i;
- trace_usb_ehci_mmio_writel(addr, addr2str(addr), val);
-
- /* Only aligned reads are allowed on OHCI */
- if (addr & 3) {
- fprintf(stderr, "usb-ehci: Mis-aligned write to addr 0x"
- TARGET_FMT_plx "\n", addr);
- return;
- }
-
- if (addr >= PORTSC && addr < PORTSC + 4 * NB_PORTS) {
- handle_port_status_write(s, (addr-PORTSC)/4, val);
- trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
- return;
- }
-
- if (addr < OPREGBASE) {
- fprintf(stderr, "usb-ehci: write attempt to read-only register"
- TARGET_FMT_plx "\n", addr);
- return;
- }
+ trace_usb_ehci_opreg_write(addr + s->opregbase, addr2str(addr), val);
-
- /* Do any register specific pre-write processing here. */
- switch(addr) {
+ switch (addr) {
case USBCMD:
if (val & USBCMD_HCRESET) {
ehci_reset(s);
@@ -1182,21 +1083,32 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
break;
}
+ /* not supporting dynamic frame list size at the moment */
+ if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
+ fprintf(stderr, "attempt to set frame list size -- value %d\n",
+ (int)val & USBCMD_FLS);
+ val &= ~USBCMD_FLS;
+ }
+
+ if (val & USBCMD_IAAD) {
+ /*
+ * Process IAAD immediately, otherwise the Linux IAAD watchdog may
+ * trigger and re-use a qh without us seeing the unlink.
+ */
+ s->async_stepdown = 0;
+ qemu_bh_schedule(s->async_bh);
+ trace_usb_ehci_doorbell_ring();
+ }
+
if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) {
if (s->pstate == EST_INACTIVE) {
SET_LAST_RUN_CLOCK(s);
}
+ s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
ehci_update_halt(s);
s->async_stepdown = 0;
- qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock));
- }
-
- /* not supporting dynamic frame list size at the moment */
- if ((val & USBCMD_FLS) && !(s->usbcmd & USBCMD_FLS)) {
- fprintf(stderr, "attempt to set frame list size -- value %d\n",
- val & USBCMD_FLS);
- val &= ~USBCMD_FLS;
+ qemu_bh_schedule(s->async_bh);
}
break;
@@ -1209,10 +1121,14 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
case USBINTR:
val &= USBINTR_MASK;
+ if (ehci_enabled(s) && (USBSTS_FLR & val)) {
+ qemu_bh_schedule(s->async_bh);
+ }
break;
case FRINDEX:
- val &= 0x00003ff8; /* frindex is 14bits and always a multiple of 8 */
+ val &= 0x00003fff; /* frindex is 14bits */
+ s->usbsts_frindex = val;
break;
case CONFIGFLAG:
@@ -1241,38 +1157,8 @@ static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
}
*mmio = val;
- trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
-}
-
-
-// TODO : Put in common header file, duplication from usb-ohci.c
-
-/* Get an array of dwords from main memory */
-static inline int get_dwords(EHCIState *ehci, uint32_t addr,
- uint32_t *buf, int num)
-{
- int i;
-
- for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
- pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf));
- *buf = le32_to_cpu(*buf);
- }
-
- return 1;
-}
-
-/* Put an array of dwords in to main memory */
-static inline int put_dwords(EHCIState *ehci, uint32_t addr,
- uint32_t *buf, int num)
-{
- int i;
-
- for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
- uint32_t tmp = cpu_to_le32(*buf);
- pci_dma_write(&ehci->dev, addr, &tmp, sizeof(tmp));
- }
-
- return 1;
+ trace_usb_ehci_opreg_change(addr + s->opregbase, addr2str(addr),
+ *mmio, old);
}
/*
@@ -1352,12 +1238,12 @@ static int ehci_init_transfer(EHCIPacket *p)
cpage = get_field(p->qtd.token, QTD_TOKEN_CPAGE);
bytes = get_field(p->qtd.token, QTD_TOKEN_TBYTES);
offset = p->qtd.bufptr[0] & ~QTD_BUFPTR_MASK;
- pci_dma_sglist_init(&p->sgl, &p->queue->ehci->dev, 5);
+ qemu_sglist_init(&p->sgl, 5, p->queue->ehci->dma);
while (bytes > 0) {
if (cpage > 4) {
fprintf(stderr, "cpage out of range (%d)\n", cpage);
- return USB_RET_PROCERR;
+ return -1;
}
page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
@@ -1375,16 +1261,16 @@ static int ehci_init_transfer(EHCIPacket *p)
return 0;
}
-static void ehci_finish_transfer(EHCIQueue *q, int status)
+static void ehci_finish_transfer(EHCIQueue *q, int len)
{
uint32_t cpage, offset;
- if (status > 0) {
+ if (len > 0) {
/* update cpage & offset */
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
- offset += status;
+ offset += len;
cpage += offset >> QTD_BUFPTR_SH;
offset &= ~QTD_BUFPTR_MASK;
@@ -1407,153 +1293,168 @@ static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
}
p = container_of(packet, EHCIPacket, packet);
- trace_usb_ehci_packet_action(p->queue, p, "wakeup");
assert(p->async == EHCI_ASYNC_INFLIGHT);
+
+ if (packet->status == USB_RET_REMOVE_FROM_QUEUE) {
+ trace_usb_ehci_packet_action(p->queue, p, "remove");
+ ehci_free_packet(p);
+ return;
+ }
+
+ trace_usb_ehci_packet_action(p->queue, p, "wakeup");
p->async = EHCI_ASYNC_FINISHED;
- p->usb_status = packet->result;
- if (p->queue->async) {
- qemu_bh_schedule(p->queue->ehci->async_bh);
+ if (!p->queue->async) {
+ s->periodic_sched_active = PERIODIC_ACTIVE;
}
+ qemu_bh_schedule(s->async_bh);
}
static void ehci_execute_complete(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
+ uint32_t tbytes;
assert(p != NULL);
assert(p->qtdaddr == q->qtdaddr);
- assert(p->async != EHCI_ASYNC_INFLIGHT);
- p->async = EHCI_ASYNC_NONE;
+ assert(p->async == EHCI_ASYNC_INITIALIZED ||
+ p->async == EHCI_ASYNC_FINISHED);
- DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
- q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status);
+ DPRINTF("execute_complete: qhaddr 0x%x, next 0x%x, qtdaddr 0x%x, "
+ "status %d, actual_length %d\n",
+ q->qhaddr, q->qh.next, q->qtdaddr,
+ p->packet.status, p->packet.actual_length);
- if (p->usb_status < 0) {
- switch (p->usb_status) {
- case USB_RET_IOERROR:
- case USB_RET_NODEV:
- q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
- set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
- ehci_raise_irq(q->ehci, USBSTS_ERRINT);
- break;
- case USB_RET_STALL:
- q->qh.token |= QTD_TOKEN_HALT;
- ehci_raise_irq(q->ehci, USBSTS_ERRINT);
- break;
- case USB_RET_NAK:
- set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
- return; /* We're not done yet with this transaction */
- case USB_RET_BABBLE:
- q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
- ehci_raise_irq(q->ehci, USBSTS_ERRINT);
- break;
- default:
- /* should not be triggerable */
- fprintf(stderr, "USB invalid response %d\n", p->usb_status);
- assert(0);
- break;
- }
- } else if ((p->usb_status > p->tbytes) && (p->pid == USB_TOKEN_IN)) {
- p->usb_status = USB_RET_BABBLE;
+ switch (p->packet.status) {
+ case USB_RET_SUCCESS:
+ break;
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
+ set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
+ ehci_raise_irq(q->ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_STALL:
+ q->qh.token |= QTD_TOKEN_HALT;
+ ehci_raise_irq(q->ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_NAK:
+ set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
+ return; /* We're not done yet with this transaction */
+ case USB_RET_BABBLE:
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
- } else {
- // TODO check 4.12 for splits
+ break;
+ default:
+ /* should not be triggerable */
+ fprintf(stderr, "USB invalid response %d\n", p->packet.status);
+ assert(0);
+ break;
+ }
- if (p->tbytes && p->pid == USB_TOKEN_IN) {
- p->tbytes -= p->usb_status;
- } else {
- p->tbytes = 0;
+ /* TODO check 4.12 for splits */
+ tbytes = get_field(q->qh.token, QTD_TOKEN_TBYTES);
+ if (tbytes && p->pid == USB_TOKEN_IN) {
+ tbytes -= p->packet.actual_length;
+ if (tbytes) {
+ /* 4.15.1.2 must raise int on a short input packet */
+ ehci_raise_irq(q->ehci, USBSTS_INT);
+ if (q->async) {
+ q->ehci->int_req_by_async = true;
+ }
}
-
- DPRINTF("updating tbytes to %d\n", p->tbytes);
- set_field(&q->qh.token, p->tbytes, QTD_TOKEN_TBYTES);
+ } else {
+ tbytes = 0;
}
- ehci_finish_transfer(q, p->usb_status);
+ DPRINTF("updating tbytes to %d\n", tbytes);
+ set_field(&q->qh.token, tbytes, QTD_TOKEN_TBYTES);
+
+ ehci_finish_transfer(q, p->packet.actual_length);
usb_packet_unmap(&p->packet, &p->sgl);
qemu_sglist_destroy(&p->sgl);
+ p->async = EHCI_ASYNC_NONE;
q->qh.token ^= QTD_TOKEN_DTOGGLE;
q->qh.token &= ~QTD_TOKEN_ACTIVE;
if (q->qh.token & QTD_TOKEN_IOC) {
ehci_raise_irq(q->ehci, USBSTS_INT);
+ if (q->async) {
+ q->ehci->int_req_by_async = true;
+ }
}
}
-// 4.10.3
-
+/* 4.10.3 returns "again" */
static int ehci_execute(EHCIPacket *p, const char *action)
{
USBEndpoint *ep;
- int ret;
int endp;
+ bool spd;
+
+ assert(p->async == EHCI_ASYNC_NONE ||
+ p->async == EHCI_ASYNC_INITIALIZED);
if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) {
fprintf(stderr, "Attempting to execute inactive qtd\n");
- return USB_RET_PROCERR;
- }
-
- p->tbytes = (p->qtd.token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
- if (p->tbytes > BUFF_SIZE) {
- fprintf(stderr, "Request for more bytes than allowed\n");
- return USB_RET_PROCERR;
+ return -1;
}
- p->pid = (p->qtd.token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
- switch (p->pid) {
- case 0:
- p->pid = USB_TOKEN_OUT;
- break;
- case 1:
- p->pid = USB_TOKEN_IN;
- break;
- case 2:
- p->pid = USB_TOKEN_SETUP;
- break;
- default:
- fprintf(stderr, "bad token\n");
- break;
+ if (get_field(p->qtd.token, QTD_TOKEN_TBYTES) > BUFF_SIZE) {
+ ehci_trace_guest_bug(p->queue->ehci,
+ "guest requested more bytes than allowed");
+ return -1;
}
- if (ehci_init_transfer(p) != 0) {
- return USB_RET_PROCERR;
+ if (!ehci_verify_pid(p->queue, &p->qtd)) {
+ ehci_queue_stopped(p->queue); /* Mark the ep in the prev dir stopped */
}
-
+ p->pid = ehci_get_pid(&p->qtd);
+ p->queue->last_pid = p->pid;
endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
ep = usb_ep_get(p->queue->dev, p->pid, endp);
- usb_packet_setup(&p->packet, p->pid, ep);
- usb_packet_map(&p->packet, &p->sgl);
+ if (p->async == EHCI_ASYNC_NONE) {
+ if (ehci_init_transfer(p) != 0) {
+ return -1;
+ }
+
+ spd = (p->pid == USB_TOKEN_IN && NLPTR_TBIT(p->qtd.altnext) == 0);
+ usb_packet_setup(&p->packet, p->pid, ep, p->qtdaddr, spd,
+ (p->qtd.token & QTD_TOKEN_IOC) != 0);
+ usb_packet_map(&p->packet, &p->sgl);
+ p->async = EHCI_ASYNC_INITIALIZED;
+ }
trace_usb_ehci_packet_action(p->queue, p, action);
- ret = usb_handle_packet(p->queue->dev, &p->packet);
- DPRINTF("submit: qh %x next %x qtd %x pid %x len %zd "
- "(total %d) endp %x ret %d\n",
- q->qhaddr, q->qh.next, q->qtdaddr, q->pid,
- q->packet.iov.size, q->tbytes, endp, ret);
+ usb_handle_packet(p->queue->dev, &p->packet);
+ DPRINTF("submit: qh 0x%x next 0x%x qtd 0x%x pid 0x%x len %zd endp 0x%x "
+ "status %d actual_length %d\n", p->queue->qhaddr, p->qtd.next,
+ p->qtdaddr, p->pid, p->packet.iov.size, endp, p->packet.status,
+ p->packet.actual_length);
- if (ret > BUFF_SIZE) {
+ if (p->packet.actual_length > BUFF_SIZE) {
fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
- return USB_RET_PROCERR;
+ return -1;
}
- return ret;
+ return 1;
}
/* 4.7.2
*/
static int ehci_process_itd(EHCIState *ehci,
- EHCIitd *itd)
+ EHCIitd *itd,
+ uint32_t addr)
{
USBDevice *dev;
USBEndpoint *ep;
- int ret;
uint32_t i, len, pid, dir, devaddr, endp;
uint32_t pg, off, ptr1, ptr2, max, mult;
+ ehci->periodic_sched_active = PERIODIC_ACTIVE;
+
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
@@ -1573,10 +1474,10 @@ static int ehci_process_itd(EHCIState *ehci,
}
if (len > BUFF_SIZE) {
- return USB_RET_PROCERR;
+ return -1;
}
- pci_dma_sglist_init(&ehci->isgl, &ehci->dev, 2);
+ qemu_sglist_init(&ehci->isgl, 2, ehci->dma);
if (off + len > 4096) {
/* transfer crosses page border */
uint32_t len2 = off + len - 4096;
@@ -1591,49 +1492,49 @@ static int ehci_process_itd(EHCIState *ehci,
dev = ehci_find_device(ehci, devaddr);
ep = usb_ep_get(dev, pid, endp);
- if (ep->type == USB_ENDPOINT_XFER_ISOC) {
- usb_packet_setup(&ehci->ipacket, pid, ep);
+ if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) {
+ usb_packet_setup(&ehci->ipacket, pid, ep, addr, false,
+ (itd->transact[i] & ITD_XACT_IOC) != 0);
usb_packet_map(&ehci->ipacket, &ehci->isgl);
- ret = usb_handle_packet(dev, &ehci->ipacket);
- assert(ret != USB_RET_ASYNC);
+ usb_handle_packet(dev, &ehci->ipacket);
usb_packet_unmap(&ehci->ipacket, &ehci->isgl);
} else {
DPRINTF("ISOCH: attempt to addess non-iso endpoint\n");
- ret = USB_RET_NAK;
+ ehci->ipacket.status = USB_RET_NAK;
+ ehci->ipacket.actual_length = 0;
}
qemu_sglist_destroy(&ehci->isgl);
- if (ret < 0) {
- switch (ret) {
- default:
- fprintf(stderr, "Unexpected iso usb result: %d\n", ret);
- /* Fall through */
- case USB_RET_IOERROR:
- case USB_RET_NODEV:
- /* 3.3.2: XACTERR is only allowed on IN transactions */
- if (dir) {
- itd->transact[i] |= ITD_XACT_XACTERR;
- ehci_raise_irq(ehci, USBSTS_ERRINT);
- }
- break;
- case USB_RET_BABBLE:
- itd->transact[i] |= ITD_XACT_BABBLE;
+ switch (ehci->ipacket.status) {
+ case USB_RET_SUCCESS:
+ break;
+ default:
+ fprintf(stderr, "Unexpected iso usb result: %d\n",
+ ehci->ipacket.status);
+ /* Fall through */
+ case USB_RET_IOERROR:
+ case USB_RET_NODEV:
+ /* 3.3.2: XACTERR is only allowed on IN transactions */
+ if (dir) {
+ itd->transact[i] |= ITD_XACT_XACTERR;
ehci_raise_irq(ehci, USBSTS_ERRINT);
- break;
- case USB_RET_NAK:
- /* no data for us, so do a zero-length transfer */
- ret = 0;
- break;
}
+ break;
+ case USB_RET_BABBLE:
+ itd->transact[i] |= ITD_XACT_BABBLE;
+ ehci_raise_irq(ehci, USBSTS_ERRINT);
+ break;
+ case USB_RET_NAK:
+ /* no data for us, so do a zero-length transfer */
+ ehci->ipacket.actual_length = 0;
+ break;
}
- if (ret >= 0) {
- if (!dir) {
- /* OUT */
- set_field(&itd->transact[i], len - ret, ITD_XACT_LENGTH);
- } else {
- /* IN */
- set_field(&itd->transact[i], ret, ITD_XACT_LENGTH);
- }
+ if (!dir) {
+ set_field(&itd->transact[i], len - ehci->ipacket.actual_length,
+ ITD_XACT_LENGTH); /* OUT */
+ } else {
+ set_field(&itd->transact[i], ehci->ipacket.actual_length,
+ ITD_XACT_LENGTH); /* IN */
}
if (itd->transact[i] & ITD_XACT_IOC) {
ehci_raise_irq(ehci, USBSTS_INT);
@@ -1664,8 +1565,10 @@ static int ehci_state_waitlisthead(EHCIState *ehci, int async)
/* Find the head of the list (4.9.1.1) */
for(i = 0; i < MAX_QH; i++) {
- get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
- sizeof(EHCIqh) >> 2);
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
+ sizeof(EHCIqh) >> 2) < 0) {
+ return 0;
+ }
ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
if (qh.epchar & QH_EPCHAR_H) {
@@ -1742,8 +1645,7 @@ out:
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
{
- EHCIPacket *p;
- uint32_t entry, devaddr;
+ uint32_t entry;
EHCIQueue *q;
EHCIqh qh;
@@ -1752,7 +1654,6 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
if (NULL == q) {
q = ehci_alloc_queue(ehci, entry, async);
}
- p = QTAILQ_FIRST(&q->packets);
q->seen++;
if (q->seen > 1) {
@@ -1762,44 +1663,32 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
goto out;
}
- get_dwords(ehci, NLPTR_GET(q->qhaddr),
- (uint32_t *) &qh, sizeof(EHCIqh) >> 2);
- if (q->revalidate && (q->qh.epchar != qh.epchar ||
- q->qh.epcap != qh.epcap ||
- q->qh.current_qtd != qh.current_qtd)) {
- ehci_free_queue(q);
- q = ehci_alloc_queue(ehci, entry, async);
- q->seen++;
- p = NULL;
+ if (get_dwords(ehci, NLPTR_GET(q->qhaddr),
+ (uint32_t *) &qh, sizeof(EHCIqh) >> 2) < 0) {
+ q = NULL;
+ goto out;
}
- q->qh = qh;
- q->revalidate = 0;
- ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh);
-
- devaddr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR);
- if (q->dev != NULL && q->dev->addr != devaddr) {
- if (!QTAILQ_EMPTY(&q->packets)) {
- /* should not happen (guest bug) */
- while ((p = QTAILQ_FIRST(&q->packets)) != NULL) {
- ehci_free_packet(p);
- }
+ ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh);
+
+ /*
+ * The overlay area of the qh should never be changed by the guest,
+ * except when idle, in which case the reset is a nop.
+ */
+ if (!ehci_verify_qh(q, &qh)) {
+ if (ehci_reset_queue(q) > 0) {
+ ehci_trace_guest_bug(ehci, "guest updated active QH");
}
- q->dev = NULL;
- }
- if (q->dev == NULL) {
- q->dev = ehci_find_device(q->ehci, devaddr);
}
+ q->qh = qh;
- if (p && p->async == EHCI_ASYNC_INFLIGHT) {
- /* I/O still in progress -- skip queue */
- ehci_set_state(ehci, async, EST_HORIZONTALQH);
- goto out;
+ q->transact_ctr = get_field(q->qh.epcap, QH_EPCAP_MULT);
+ if (q->transact_ctr == 0) { /* Guest bug in some versions of windows */
+ q->transact_ctr = 4;
}
- if (p && p->async == EHCI_ASYNC_FINISHED) {
- /* I/O finished -- continue processing queue */
- trace_usb_ehci_packet_action(p->queue, p, "complete");
- ehci_set_state(ehci, async, EST_EXECUTING);
- goto out;
+
+ if (q->dev == NULL) {
+ q->dev = ehci_find_device(q->ehci,
+ get_field(q->qh.epchar, QH_EPCHAR_DEVADDR));
}
if (async && (q->qh.epchar & QH_EPCHAR_H)) {
@@ -1852,11 +1741,13 @@ static int ehci_state_fetchitd(EHCIState *ehci, int async)
assert(!async);
entry = ehci_get_fetch_addr(ehci, async);
- get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
- sizeof(EHCIitd) >> 2);
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
+ sizeof(EHCIitd) >> 2) < 0) {
+ return -1;
+ }
ehci_trace_itd(ehci, entry, &itd);
- if (ehci_process_itd(ehci, &itd) != 0) {
+ if (ehci_process_itd(ehci, &itd, entry) != 0) {
return -1;
}
@@ -1876,8 +1767,10 @@ static int ehci_state_fetchsitd(EHCIState *ehci, int async)
assert(!async);
entry = ehci_get_fetch_addr(ehci, async);
- get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
- sizeof(EHCIsitd) >> 2);
+ if (get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
+ sizeof(EHCIsitd) >> 2) < 0) {
+ return 0;
+ }
ehci_trace_sitd(ehci, entry, &sitd);
if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
@@ -1936,36 +1829,53 @@ static int ehci_state_fetchqtd(EHCIQueue *q)
{
EHCIqtd qtd;
EHCIPacket *p;
- int again = 0;
+ int again = 1;
- get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
- sizeof(EHCIqtd) >> 2);
+ if (get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
+ sizeof(EHCIqtd) >> 2) < 0) {
+ return 0;
+ }
ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
p = QTAILQ_FIRST(&q->packets);
- while (p != NULL && p->qtdaddr != q->qtdaddr) {
- /* should not happen (guest bug) */
- ehci_free_packet(p);
- p = QTAILQ_FIRST(&q->packets);
- }
if (p != NULL) {
- ehci_qh_do_overlay(q);
- ehci_flush_qh(q);
- if (p->async == EHCI_ASYNC_INFLIGHT) {
- ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ if (!ehci_verify_qtd(p, &qtd)) {
+ ehci_cancel_queue(q);
+ if (qtd.token & QTD_TOKEN_ACTIVE) {
+ ehci_trace_guest_bug(q->ehci, "guest updated active qTD");
+ }
+ p = NULL;
} else {
+ p->qtd = qtd;
+ ehci_qh_do_overlay(q);
+ }
+ }
+
+ if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ } else if (p != NULL) {
+ switch (p->async) {
+ case EHCI_ASYNC_NONE:
+ case EHCI_ASYNC_INITIALIZED:
+ /* Not yet executed (MULT), or previously nacked (int) packet */
+ ehci_set_state(q->ehci, q->async, EST_EXECUTE);
+ break;
+ case EHCI_ASYNC_INFLIGHT:
+ /* Check if the guest has added new tds to the queue */
+ again = ehci_fill_queue(QTAILQ_LAST(&q->packets, pkts_head));
+ /* Unfinished async handled packet, go horizontal */
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ break;
+ case EHCI_ASYNC_FINISHED:
+ /* Complete executing of the packet */
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
+ break;
}
- again = 1;
- } else if (qtd.token & QTD_TOKEN_ACTIVE) {
+ } else {
p = ehci_alloc_packet(q);
p->qtdaddr = q->qtdaddr;
p->qtd = qtd;
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
- again = 1;
- } else {
- ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
- again = 1;
}
return again;
@@ -1986,33 +1896,52 @@ static int ehci_state_horizqh(EHCIQueue *q)
return again;
}
-static void ehci_fill_queue(EHCIPacket *p)
+/* Returns "again" */
+static int ehci_fill_queue(EHCIPacket *p)
{
+ USBEndpoint *ep = p->packet.ep;
EHCIQueue *q = p->queue;
EHCIqtd qtd = p->qtd;
uint32_t qtdaddr;
for (;;) {
- if (NLPTR_TBIT(qtd.altnext) == 0) {
- break;
- }
if (NLPTR_TBIT(qtd.next) != 0) {
break;
}
qtdaddr = qtd.next;
- get_dwords(q->ehci, NLPTR_GET(qtdaddr),
- (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2);
+ /*
+ * Detect circular td lists, Windows creates these, counting on the
+ * active bit going low after execution to make the queue stop.
+ */
+ QTAILQ_FOREACH(p, &q->packets, next) {
+ if (p->qtdaddr == qtdaddr) {
+ goto leave;
+ }
+ }
+ if (get_dwords(q->ehci, NLPTR_GET(qtdaddr),
+ (uint32_t *) &qtd, sizeof(EHCIqtd) >> 2) < 0) {
+ return -1;
+ }
ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
break;
}
+ if (!ehci_verify_pid(q, &qtd)) {
+ ehci_trace_guest_bug(q->ehci, "guest queued token with wrong pid");
+ break;
+ }
p = ehci_alloc_packet(q);
p->qtdaddr = qtdaddr;
p->qtd = qtd;
- p->usb_status = ehci_execute(p, "queue");
- assert(p->usb_status == USB_RET_ASYNC);
+ if (ehci_execute(p, "queue") == -1) {
+ return -1;
+ }
+ assert(p->packet.status == USB_RET_ASYNC);
p->async = EHCI_ASYNC_INFLIGHT;
}
+leave:
+ usb_device_flush_ep_queue(ep->dev, ep);
+ return 1;
}
static int ehci_state_execute(EHCIQueue *q)
@@ -2029,33 +1958,32 @@ static int ehci_state_execute(EHCIQueue *q)
// TODO verify enough time remains in the uframe as in 4.4.1.1
// TODO write back ptr to async list when done or out of time
- // TODO Windows does not seem to ever set the MULT field
- if (!q->async) {
- int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
- if (!transactCtr) {
- ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
- again = 1;
- goto out;
- }
+ /* 4.10.3, bottom of page 82, go horizontal on transaction counter == 0 */
+ if (!q->async && q->transact_ctr == 0) {
+ ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
+ again = 1;
+ goto out;
}
if (q->async) {
ehci_set_usbsts(q->ehci, USBSTS_REC);
}
- p->usb_status = ehci_execute(p, "process");
- if (p->usb_status == USB_RET_PROCERR) {
- again = -1;
+ again = ehci_execute(p, "process");
+ if (again == -1) {
goto out;
}
- if (p->usb_status == USB_RET_ASYNC) {
+ if (p->packet.status == USB_RET_ASYNC) {
ehci_flush_qh(q);
trace_usb_ehci_packet_action(p->queue, p, "async");
p->async = EHCI_ASYNC_INFLIGHT;
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
- again = 1;
- ehci_fill_queue(p);
+ if (q->async) {
+ again = ehci_fill_queue(p);
+ } else {
+ again = 1;
+ }
goto out;
}
@@ -2069,41 +1997,26 @@ out:
static int ehci_state_executing(EHCIQueue *q)
{
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
- int again = 0;
assert(p != NULL);
assert(p->qtdaddr == q->qtdaddr);
ehci_execute_complete(q);
- if (p->usb_status == USB_RET_ASYNC) {
- goto out;
- }
- if (p->usb_status == USB_RET_PROCERR) {
- again = -1;
- goto out;
- }
- // 4.10.3
- if (!q->async) {
- int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
- transactCtr--;
- set_field(&q->qh.epcap, transactCtr, QH_EPCAP_MULT);
- // 4.10.3, bottom of page 82, should exit this state when transaction
- // counter decrements to 0
+ /* 4.10.3 */
+ if (!q->async && q->transact_ctr > 0) {
+ q->transact_ctr--;
}
/* 4.10.5 */
- if (p->usb_status == USB_RET_NAK) {
+ if (p->packet.status == USB_RET_NAK) {
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
} else {
ehci_set_state(q->ehci, q->async, EST_WRITEBACK);
}
- again = 1;
-
-out:
ehci_flush_qh(q);
- return again;
+ return 1;
}
@@ -2208,6 +2121,9 @@ static void ehci_advance_state(EHCIState *ehci, int async)
case EST_WRITEBACK:
assert(q != NULL);
again = ehci_state_writeback(q);
+ if (!async) {
+ ehci->periodic_sched_active = PERIODIC_ACTIVE;
+ }
break;
default:
@@ -2266,8 +2182,8 @@ static void ehci_advance_async_state(EHCIState *ehci)
*/
if (ehci->usbcmd & USBCMD_IAAD) {
/* Remove all unseen qhs from the async qhs queue */
- ehci_queues_tag_unused_async(ehci);
- DPRINTF("ASYNC: doorbell request acknowledged\n");
+ ehci_queues_rip_unseen(ehci, async);
+ trace_usb_ehci_doorbell_ack();
ehci->usbcmd &= ~USBCMD_IAAD;
ehci_raise_irq(ehci, USBSTS_IAA);
}
@@ -2311,8 +2227,9 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
}
list |= ((ehci->frindex & 0x1ff8) >> 1);
- pci_dma_read(&ehci->dev, list, &entry, sizeof entry);
- entry = le32_to_cpu(entry);
+ if (get_dwords(ehci, list, &entry, 1) < 0) {
+ break;
+ }
DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
ehci->frindex / 8, list, entry);
@@ -2330,16 +2247,16 @@ static void ehci_advance_periodic_state(EHCIState *ehci)
}
}
-static void ehci_update_frindex(EHCIState *ehci, int frames)
+static void ehci_update_frindex(EHCIState *ehci, int uframes)
{
int i;
- if (!ehci_enabled(ehci)) {
+ if (!ehci_enabled(ehci) && ehci->pstate == EST_INACTIVE) {
return;
}
- for (i = 0; i < frames; i++) {
- ehci->frindex += 8;
+ for (i = 0; i < uframes; i++) {
+ ehci->frindex++;
if (ehci->frindex == 0x00002000) {
ehci_raise_irq(ehci, USBSTS_FLR);
@@ -2348,7 +2265,7 @@ static void ehci_update_frindex(EHCIState *ehci, int frames)
if (ehci->frindex == 0x00004000) {
ehci_raise_irq(ehci, USBSTS_FLR);
ehci->frindex = 0;
- if (ehci->usbsts_frindex > 0x00004000) {
+ if (ehci->usbsts_frindex >= 0x00004000) {
ehci->usbsts_frindex -= 0x00004000;
} else {
ehci->usbsts_frindex = 0;
@@ -2363,36 +2280,57 @@ static void ehci_frame_timer(void *opaque)
int need_timer = 0;
int64_t expire_time, t_now;
uint64_t ns_elapsed;
- int frames, skipped_frames;
+ int uframes, skipped_uframes;
int i;
t_now = qemu_get_clock_ns(vm_clock);
ns_elapsed = t_now - ehci->last_run_ns;
- frames = ns_elapsed / FRAME_TIMER_NS;
+ uframes = ns_elapsed / UFRAME_TIMER_NS;
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
need_timer++;
- ehci->async_stepdown = 0;
- if (frames > ehci->maxframes) {
- skipped_frames = frames - ehci->maxframes;
- ehci_update_frindex(ehci, skipped_frames);
- ehci->last_run_ns += FRAME_TIMER_NS * skipped_frames;
- frames -= skipped_frames;
- DPRINTF("WARNING - EHCI skipped %d frames\n", skipped_frames);
+ if (uframes > (ehci->maxframes * 8)) {
+ skipped_uframes = uframes - (ehci->maxframes * 8);
+ ehci_update_frindex(ehci, skipped_uframes);
+ ehci->last_run_ns += UFRAME_TIMER_NS * skipped_uframes;
+ uframes -= skipped_uframes;
+ DPRINTF("WARNING - EHCI skipped %d uframes\n", skipped_uframes);
}
- for (i = 0; i < frames; i++) {
+ for (i = 0; i < uframes; i++) {
+ /*
+ * If we're running behind schedule, we should not catch up
+ * too fast, as that will make some guests unhappy:
+ * 1) We must process a minimum of MIN_UFR_PER_TICK frames,
+ * otherwise we will never catch up
+ * 2) Process frames until the guest has requested an irq (IOC)
+ */
+ if (i >= MIN_UFR_PER_TICK) {
+ ehci_commit_irq(ehci);
+ if ((ehci->usbsts & USBINTR_MASK) & ehci->usbintr) {
+ break;
+ }
+ }
+ if (ehci->periodic_sched_active) {
+ ehci->periodic_sched_active--;
+ }
ehci_update_frindex(ehci, 1);
- ehci_advance_periodic_state(ehci);
- ehci->last_run_ns += FRAME_TIMER_NS;
+ if ((ehci->frindex & 7) == 0) {
+ ehci_advance_periodic_state(ehci);
+ }
+ ehci->last_run_ns += UFRAME_TIMER_NS;
}
} else {
- if (ehci->async_stepdown < ehci->maxframes / 2) {
- ehci->async_stepdown++;
- }
- ehci_update_frindex(ehci, frames);
- ehci->last_run_ns += FRAME_TIMER_NS * frames;
+ ehci->periodic_sched_active = 0;
+ ehci_update_frindex(ehci, uframes);
+ ehci->last_run_ns += UFRAME_TIMER_NS * uframes;
+ }
+
+ if (ehci->periodic_sched_active) {
+ ehci->async_stepdown = 0;
+ } else if (ehci->async_stepdown < ehci->maxframes / 2) {
+ ehci->async_stepdown++;
}
/* Async is not inside loop since it executes everything it can once
@@ -2409,28 +2347,48 @@ static void ehci_frame_timer(void *opaque)
ehci->async_stepdown = 0;
}
+ if (ehci_enabled(ehci) && (ehci->usbintr & USBSTS_FLR)) {
+ need_timer++;
+ }
+
if (need_timer) {
- expire_time = t_now + (get_ticks_per_sec()
+ /* If we've raised int, we speed up the timer, so that we quickly
+ * notice any new packets queued up in response */
+ if (ehci->int_req_by_async && (ehci->usbsts & USBSTS_INT)) {
+ expire_time = t_now + get_ticks_per_sec() / (FRAME_TIMER_FREQ * 4);
+ ehci->int_req_by_async = false;
+ } else {
+ expire_time = t_now + (get_ticks_per_sec()
* (ehci->async_stepdown+1) / FRAME_TIMER_FREQ);
+ }
qemu_mod_timer(ehci->frame_timer, expire_time);
}
}
-static void ehci_async_bh(void *opaque)
-{
- EHCIState *ehci = opaque;
- ehci_advance_async_state(ehci);
-}
+static const MemoryRegionOps ehci_mmio_caps_ops = {
+ .read = ehci_caps_read,
+ .valid.min_access_size = 1,
+ .valid.max_access_size = 4,
+ .impl.min_access_size = 1,
+ .impl.max_access_size = 1,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
-static const MemoryRegionOps ehci_mem_ops = {
- .old_mmio = {
- .read = { ehci_mem_readb, ehci_mem_readw, ehci_mem_readl },
- .write = { ehci_mem_writeb, ehci_mem_writew, ehci_mem_writel },
- },
+static const MemoryRegionOps ehci_mmio_opreg_ops = {
+ .read = ehci_opreg_read,
+ .write = ehci_opreg_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
.endianness = DEVICE_LITTLE_ENDIAN,
};
-static int usb_ehci_initfn(PCIDevice *dev);
+static const MemoryRegionOps ehci_mmio_port_ops = {
+ .read = ehci_port_read,
+ .write = ehci_port_write,
+ .valid.min_access_size = 4,
+ .valid.max_access_size = 4,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
static USBPortOps ehci_port_ops = {
.attach = ehci_attach,
@@ -2442,8 +2400,20 @@ static USBPortOps ehci_port_ops = {
static USBBusOps ehci_bus_ops = {
.register_companion = ehci_register_companion,
+ .wakeup_endpoint = ehci_wakeup_endpoint,
};
+static void usb_ehci_pre_save(void *opaque)
+{
+ EHCIState *ehci = opaque;
+ uint32_t new_frindex;
+
+ /* Round down frindex to a multiple of 8 for migration compatibility */
+ new_frindex = ehci->frindex & ~7;
+ ehci->last_run_ns -= (ehci->frindex - new_frindex) * UFRAME_TIMER_NS;
+ ehci->frindex = new_frindex;
+}
+
static int usb_ehci_post_load(void *opaque, int version_id)
{
EHCIState *s = opaque;
@@ -2464,15 +2434,44 @@ static int usb_ehci_post_load(void *opaque, int version_id)
return 0;
}
-static const VMStateDescription vmstate_ehci = {
- .name = "ehci",
- .version_id = 1,
+static void usb_ehci_vm_state_change(void *opaque, int running, RunState state)
+{
+ EHCIState *ehci = opaque;
+
+ /*
+ * We don't migrate the EHCIQueue-s, instead we rebuild them for the
+ * schedule in guest memory. We must do the rebuilt ASAP, so that
+ * USB-devices which have async handled packages have a packet in the
+ * ep queue to match the completion with.
+ */
+ if (state == RUN_STATE_RUNNING) {
+ ehci_advance_async_state(ehci);
+ }
+
+ /*
+ * The schedule rebuilt from guest memory could cause the migration dest
+ * to miss a QH unlink, and fail to cancel packets, since the unlinked QH
+ * will never have existed on the destination. Therefor we must flush the
+ * async schedule on savevm to catch any not yet noticed unlinks.
+ */
+ if (state == RUN_STATE_SAVE_VM) {
+ ehci_advance_async_state(ehci);
+ ehci_queues_rip_unseen(ehci, 1);
+ }
+}
+
+const VMStateDescription vmstate_ehci = {
+ .name = "ehci-core",
+ .version_id = 2,
+ .minimum_version_id = 1,
+ .pre_save = usb_ehci_pre_save,
.post_load = usb_ehci_post_load,
.fields = (VMStateField[]) {
- VMSTATE_PCI_DEVICE(dev, EHCIState),
/* mmio registers */
VMSTATE_UINT32(usbcmd, EHCIState),
VMSTATE_UINT32(usbsts, EHCIState),
+ VMSTATE_UINT32_V(usbsts_pending, EHCIState, 2),
+ VMSTATE_UINT32_V(usbsts_frindex, EHCIState, 2),
VMSTATE_UINT32(usbintr, EHCIState),
VMSTATE_UINT32(frindex, EHCIState),
VMSTATE_UINT32(ctrldssegment, EHCIState),
@@ -2498,105 +2497,24 @@ static const VMStateDescription vmstate_ehci = {
}
};
-static Property ehci_properties[] = {
- DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128),
- DEFINE_PROP_END_OF_LIST(),
-};
-
-static void ehci_class_init(ObjectClass *klass, void *data)
+void usb_ehci_initfn(EHCIState *s, DeviceState *dev)
{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_ehci_initfn;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82801D; /* ich4 */
- k->revision = 0x10;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_ehci;
- dc->props = ehci_properties;
-}
-
-static TypeInfo ehci_info = {
- .name = "usb-ehci",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(EHCIState),
- .class_init = ehci_class_init,
-};
-
-static void ich9_ehci_class_init(ObjectClass *klass, void *data)
-{
- DeviceClass *dc = DEVICE_CLASS(klass);
- PCIDeviceClass *k = PCI_DEVICE_CLASS(klass);
-
- k->init = usb_ehci_initfn;
- k->vendor_id = PCI_VENDOR_ID_INTEL;
- k->device_id = PCI_DEVICE_ID_INTEL_82801I_EHCI1;
- k->revision = 0x03;
- k->class_id = PCI_CLASS_SERIAL_USB;
- dc->vmsd = &vmstate_ehci;
- dc->props = ehci_properties;
-}
-
-static TypeInfo ich9_ehci_info = {
- .name = "ich9-usb-ehci1",
- .parent = TYPE_PCI_DEVICE,
- .instance_size = sizeof(EHCIState),
- .class_init = ich9_ehci_class_init,
-};
-
-static int usb_ehci_initfn(PCIDevice *dev)
-{
- EHCIState *s = DO_UPCAST(EHCIState, dev, dev);
- uint8_t *pci_conf = s->dev.config;
int i;
- pci_set_byte(&pci_conf[PCI_CLASS_PROG], 0x20);
-
- /* capabilities pointer */
- pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x00);
- //pci_set_byte(&pci_conf[PCI_CAPABILITY_LIST], 0x50);
-
- pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */
- pci_set_byte(&pci_conf[PCI_MIN_GNT], 0);
- pci_set_byte(&pci_conf[PCI_MAX_LAT], 0);
-
- // pci_conf[0x50] = 0x01; // power management caps
-
- pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4)
- pci_set_byte(&pci_conf[0x61], 0x20); // frame length adjustment (2.1.5)
- pci_set_word(&pci_conf[0x62], 0x00); // port wake up capability (2.1.6)
-
- pci_conf[0x64] = 0x00;
- pci_conf[0x65] = 0x00;
- pci_conf[0x66] = 0x00;
- pci_conf[0x67] = 0x00;
- pci_conf[0x68] = 0x01;
- pci_conf[0x69] = 0x00;
- pci_conf[0x6a] = 0x00;
- pci_conf[0x6b] = 0x00; // USBLEGSUP
- pci_conf[0x6c] = 0x00;
- pci_conf[0x6d] = 0x00;
- pci_conf[0x6e] = 0x00;
- pci_conf[0x6f] = 0xc0; // USBLEFCTLSTS
-
- // 2.2 host controller interface version
- s->mmio[0x00] = (uint8_t) OPREGBASE;
- s->mmio[0x01] = 0x00;
- s->mmio[0x02] = 0x00;
- s->mmio[0x03] = 0x01; // HC version
- s->mmio[0x04] = NB_PORTS; // Number of downstream ports
- s->mmio[0x05] = 0x00; // No companion ports at present
- s->mmio[0x06] = 0x00;
- s->mmio[0x07] = 0x00;
- s->mmio[0x08] = 0x80; // We can cache whole frame, not 64-bit capable
- s->mmio[0x09] = 0x68; // EECP
- s->mmio[0x0a] = 0x00;
- s->mmio[0x0b] = 0x00;
-
- s->irq = s->dev.irq[3];
-
- usb_bus_new(&s->bus, &ehci_bus_ops, &s->dev.qdev);
+ /* 2.2 host controller interface version */
+ s->caps[0x00] = (uint8_t)(s->opregbase - s->capsbase);
+ s->caps[0x01] = 0x00;
+ s->caps[0x02] = 0x00;
+ s->caps[0x03] = 0x01; /* HC version */
+ s->caps[0x04] = NB_PORTS; /* Number of downstream ports */
+ s->caps[0x05] = 0x00; /* No companion ports at present */
+ s->caps[0x06] = 0x00;
+ s->caps[0x07] = 0x00;
+ s->caps[0x08] = 0x80; /* We can cache whole frame, no 64-bit */
+ s->caps[0x0a] = 0x00;
+ s->caps[0x0b] = 0x00;
+
+ usb_bus_new(&s->bus, &ehci_bus_ops, dev);
for(i = 0; i < NB_PORTS; i++) {
usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
USB_SPEED_MASK_HIGH);
@@ -2604,27 +2522,28 @@ static int usb_ehci_initfn(PCIDevice *dev)
}
s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
- s->async_bh = qemu_bh_new(ehci_async_bh, s);
+ s->async_bh = qemu_bh_new(ehci_frame_timer, s);
QTAILQ_INIT(&s->aqueues);
QTAILQ_INIT(&s->pqueues);
usb_packet_init(&s->ipacket);
qemu_register_reset(ehci_reset, s);
+ qemu_add_vm_change_state_handler(usb_ehci_vm_state_change, s);
- memory_region_init_io(&s->mem, &ehci_mem_ops, s, "ehci", MMIO_SIZE);
- pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
-
- return 0;
-}
+ memory_region_init(&s->mem, "ehci", MMIO_SIZE);
+ memory_region_init_io(&s->mem_caps, &ehci_mmio_caps_ops, s,
+ "capabilities", CAPA_SIZE);
+ memory_region_init_io(&s->mem_opreg, &ehci_mmio_opreg_ops, s,
+ "operational", PORTSC_BEGIN);
+ memory_region_init_io(&s->mem_ports, &ehci_mmio_port_ops, s,
+ "ports", PORTSC_END - PORTSC_BEGIN);
-static void ehci_register_types(void)
-{
- type_register_static(&ehci_info);
- type_register_static(&ich9_ehci_info);
+ memory_region_add_subregion(&s->mem, s->capsbase, &s->mem_caps);
+ memory_region_add_subregion(&s->mem, s->opregbase, &s->mem_opreg);
+ memory_region_add_subregion(&s->mem, s->opregbase + PORTSC_BEGIN,
+ &s->mem_ports);
}
-type_init(ehci_register_types)
-
/*
* vim: expandtab ts=4
*/