diff options
Diffstat (limited to 'hw/usb/hcd-ehci.c')
-rw-r--r-- | hw/usb/hcd-ehci.c | 1551 |
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 */ |