2010-12-03 18:17:28 +03:00
|
|
|
/*
|
|
|
|
* QEMU USB EHCI Emulation
|
|
|
|
*
|
|
|
|
* Copyright(c) 2008 Emutex Ltd. (address@hidden)
|
2012-08-28 18:21:12 +04:00
|
|
|
* Copyright(c) 2011-2012 Red Hat, Inc.
|
|
|
|
*
|
|
|
|
* Red Hat Authors:
|
|
|
|
* Gerd Hoffmann <kraxel@redhat.com>
|
|
|
|
* Hans de Goede <hdegoede@redhat.com>
|
2010-12-03 18:17:28 +03:00
|
|
|
*
|
|
|
|
* EHCI project was started by Mark Burkley, with contributions by
|
|
|
|
* Niels de Vos. David S. Ahern continued working on it. Kevin Wolf,
|
|
|
|
* Jan Kiszka and Vincent Palatin contributed bugfixes.
|
|
|
|
*
|
|
|
|
*
|
|
|
|
* This library is free software; you can redistribute it and/or
|
|
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
|
|
* License as published by the Free Software Foundation; either
|
|
|
|
* version 2 of the License, or(at your option) any later version.
|
|
|
|
*
|
|
|
|
* This library is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
|
|
* Lesser General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2012-03-07 17:55:18 +04:00
|
|
|
#include "hw/hw.h"
|
2010-12-03 18:17:28 +03:00
|
|
|
#include "qemu-timer.h"
|
2012-03-07 17:55:18 +04:00
|
|
|
#include "hw/usb.h"
|
|
|
|
#include "hw/pci.h"
|
2010-12-03 18:17:28 +03:00
|
|
|
#include "monitor.h"
|
2011-05-18 12:12:58 +04:00
|
|
|
#include "trace.h"
|
2011-07-13 19:36:46 +04:00
|
|
|
#include "dma.h"
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
#define EHCI_DEBUG 0
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
#if EHCI_DEBUG
|
2010-12-03 18:17:28 +03:00
|
|
|
#define DPRINTF printf
|
|
|
|
#else
|
|
|
|
#define DPRINTF(...)
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* internal processing - reset HC to try and recover */
|
|
|
|
#define USB_RET_PROCERR (-99)
|
|
|
|
|
|
|
|
#define MMIO_SIZE 0x1000
|
|
|
|
|
|
|
|
/* Capability Registers Base Address - section 2.2 */
|
|
|
|
#define CAPREGBASE 0x0000
|
|
|
|
#define CAPLENGTH CAPREGBASE + 0x0000 // 1-byte, 0x0001 reserved
|
|
|
|
#define HCIVERSION CAPREGBASE + 0x0002 // 2-bytes, i/f version #
|
|
|
|
#define HCSPARAMS CAPREGBASE + 0x0004 // 4-bytes, structural params
|
|
|
|
#define HCCPARAMS CAPREGBASE + 0x0008 // 4-bytes, capability params
|
|
|
|
#define EECP HCCPARAMS + 1
|
|
|
|
#define HCSPPORTROUTE1 CAPREGBASE + 0x000c
|
|
|
|
#define HCSPPORTROUTE2 CAPREGBASE + 0x0010
|
|
|
|
|
|
|
|
#define OPREGBASE 0x0020 // Operational Registers Base Address
|
|
|
|
|
|
|
|
#define USBCMD OPREGBASE + 0x0000
|
|
|
|
#define USBCMD_RUNSTOP (1 << 0) // run / Stop
|
|
|
|
#define USBCMD_HCRESET (1 << 1) // HC Reset
|
|
|
|
#define USBCMD_FLS (3 << 2) // Frame List Size
|
|
|
|
#define USBCMD_FLS_SH 2 // Frame List Size Shift
|
|
|
|
#define USBCMD_PSE (1 << 4) // Periodic Schedule Enable
|
|
|
|
#define USBCMD_ASE (1 << 5) // Asynch Schedule Enable
|
|
|
|
#define USBCMD_IAAD (1 << 6) // Int Asynch Advance Doorbell
|
|
|
|
#define USBCMD_LHCR (1 << 7) // Light Host Controller Reset
|
|
|
|
#define USBCMD_ASPMC (3 << 8) // Async Sched Park Mode Count
|
|
|
|
#define USBCMD_ASPME (1 << 11) // Async Sched Park Mode Enable
|
|
|
|
#define USBCMD_ITC (0x7f << 16) // Int Threshold Control
|
|
|
|
#define USBCMD_ITC_SH 16 // Int Threshold Control Shift
|
|
|
|
|
|
|
|
#define USBSTS OPREGBASE + 0x0004
|
|
|
|
#define USBSTS_RO_MASK 0x0000003f
|
|
|
|
#define USBSTS_INT (1 << 0) // USB Interrupt
|
|
|
|
#define USBSTS_ERRINT (1 << 1) // Error Interrupt
|
|
|
|
#define USBSTS_PCD (1 << 2) // Port Change Detect
|
|
|
|
#define USBSTS_FLR (1 << 3) // Frame List Rollover
|
|
|
|
#define USBSTS_HSE (1 << 4) // Host System Error
|
|
|
|
#define USBSTS_IAA (1 << 5) // Interrupt on Async Advance
|
|
|
|
#define USBSTS_HALT (1 << 12) // HC Halted
|
|
|
|
#define USBSTS_REC (1 << 13) // Reclamation
|
|
|
|
#define USBSTS_PSS (1 << 14) // Periodic Schedule Status
|
|
|
|
#define USBSTS_ASS (1 << 15) // Asynchronous Schedule Status
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Interrupt enable bits correspond to the interrupt active bits in USBSTS
|
|
|
|
* so no need to redefine here.
|
|
|
|
*/
|
|
|
|
#define USBINTR OPREGBASE + 0x0008
|
|
|
|
#define USBINTR_MASK 0x0000003f
|
|
|
|
|
|
|
|
#define FRINDEX OPREGBASE + 0x000c
|
|
|
|
#define CTRLDSSEGMENT OPREGBASE + 0x0010
|
|
|
|
#define PERIODICLISTBASE OPREGBASE + 0x0014
|
|
|
|
#define ASYNCLISTADDR OPREGBASE + 0x0018
|
|
|
|
#define ASYNCLISTADDR_MASK 0xffffffe0
|
|
|
|
|
|
|
|
#define CONFIGFLAG OPREGBASE + 0x0040
|
|
|
|
|
|
|
|
#define PORTSC (OPREGBASE + 0x0044)
|
|
|
|
#define PORTSC_BEGIN PORTSC
|
|
|
|
#define PORTSC_END (PORTSC + 4 * NB_PORTS)
|
|
|
|
/*
|
2011-06-21 14:12:35 +04:00
|
|
|
* Bits that are reserved or are read-only are masked out of values
|
2010-12-03 18:17:28 +03:00
|
|
|
* written to us by software
|
|
|
|
*/
|
2011-06-24 18:18:13 +04:00
|
|
|
#define PORTSC_RO_MASK 0x007001c0
|
2010-12-03 18:17:28 +03:00
|
|
|
#define PORTSC_RWC_MASK 0x0000002a
|
|
|
|
#define PORTSC_WKOC_E (1 << 22) // Wake on Over Current Enable
|
|
|
|
#define PORTSC_WKDS_E (1 << 21) // Wake on Disconnect Enable
|
|
|
|
#define PORTSC_WKCN_E (1 << 20) // Wake on Connect Enable
|
|
|
|
#define PORTSC_PTC (15 << 16) // Port Test Control
|
|
|
|
#define PORTSC_PTC_SH 16 // Port Test Control shift
|
|
|
|
#define PORTSC_PIC (3 << 14) // Port Indicator Control
|
|
|
|
#define PORTSC_PIC_SH 14 // Port Indicator Control Shift
|
|
|
|
#define PORTSC_POWNER (1 << 13) // Port Owner
|
|
|
|
#define PORTSC_PPOWER (1 << 12) // Port Power
|
|
|
|
#define PORTSC_LINESTAT (3 << 10) // Port Line Status
|
|
|
|
#define PORTSC_LINESTAT_SH 10 // Port Line Status Shift
|
|
|
|
#define PORTSC_PRESET (1 << 8) // Port Reset
|
|
|
|
#define PORTSC_SUSPEND (1 << 7) // Port Suspend
|
|
|
|
#define PORTSC_FPRES (1 << 6) // Force Port Resume
|
|
|
|
#define PORTSC_OCC (1 << 5) // Over Current Change
|
|
|
|
#define PORTSC_OCA (1 << 4) // Over Current Active
|
|
|
|
#define PORTSC_PEDC (1 << 3) // Port Enable/Disable Change
|
|
|
|
#define PORTSC_PED (1 << 2) // Port Enable/Disable
|
|
|
|
#define PORTSC_CSC (1 << 1) // Connect Status Change
|
|
|
|
#define PORTSC_CONNECT (1 << 0) // Current Connect Status
|
|
|
|
|
|
|
|
#define FRAME_TIMER_FREQ 1000
|
2011-05-31 14:23:13 +04:00
|
|
|
#define FRAME_TIMER_NS (1000000000 / FRAME_TIMER_FREQ)
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
#define NB_MAXINTRATE 8 // Max rate at which controller issues ints
|
2011-07-01 11:56:43 +04:00
|
|
|
#define NB_PORTS 6 // Number of downstream ports
|
2010-12-03 18:17:28 +03:00
|
|
|
#define BUFF_SIZE 5*4096 // Max bytes to transfer per transaction
|
|
|
|
#define MAX_QH 100 // Max allowable queue heads in a chain
|
|
|
|
|
|
|
|
/* Internal periodic / asynchronous schedule state machine states
|
|
|
|
*/
|
|
|
|
typedef enum {
|
|
|
|
EST_INACTIVE = 1000,
|
|
|
|
EST_ACTIVE,
|
|
|
|
EST_EXECUTING,
|
|
|
|
EST_SLEEPING,
|
|
|
|
/* The following states are internal to the state machine function
|
|
|
|
*/
|
|
|
|
EST_WAITLISTHEAD,
|
|
|
|
EST_FETCHENTRY,
|
|
|
|
EST_FETCHQH,
|
|
|
|
EST_FETCHITD,
|
2011-08-26 16:13:48 +04:00
|
|
|
EST_FETCHSITD,
|
2010-12-03 18:17:28 +03:00
|
|
|
EST_ADVANCEQUEUE,
|
|
|
|
EST_FETCHQTD,
|
|
|
|
EST_EXECUTE,
|
|
|
|
EST_WRITEBACK,
|
|
|
|
EST_HORIZONTALQH
|
|
|
|
} EHCI_STATES;
|
|
|
|
|
|
|
|
/* macros for accessing fields within next link pointer entry */
|
|
|
|
#define NLPTR_GET(x) ((x) & 0xffffffe0)
|
|
|
|
#define NLPTR_TYPE_GET(x) (((x) >> 1) & 3)
|
|
|
|
#define NLPTR_TBIT(x) ((x) & 1) // 1=invalid, 0=valid
|
|
|
|
|
|
|
|
/* link pointer types */
|
|
|
|
#define NLPTR_TYPE_ITD 0 // isoc xfer descriptor
|
|
|
|
#define NLPTR_TYPE_QH 1 // queue head
|
|
|
|
#define NLPTR_TYPE_STITD 2 // split xaction, isoc xfer descriptor
|
|
|
|
#define NLPTR_TYPE_FSTN 3 // frame span traversal node
|
|
|
|
|
|
|
|
|
|
|
|
/* EHCI spec version 1.0 Section 3.3
|
|
|
|
*/
|
|
|
|
typedef struct EHCIitd {
|
|
|
|
uint32_t next;
|
|
|
|
|
|
|
|
uint32_t transact[8];
|
|
|
|
#define ITD_XACT_ACTIVE (1 << 31)
|
|
|
|
#define ITD_XACT_DBERROR (1 << 30)
|
|
|
|
#define ITD_XACT_BABBLE (1 << 29)
|
|
|
|
#define ITD_XACT_XACTERR (1 << 28)
|
|
|
|
#define ITD_XACT_LENGTH_MASK 0x0fff0000
|
|
|
|
#define ITD_XACT_LENGTH_SH 16
|
|
|
|
#define ITD_XACT_IOC (1 << 15)
|
|
|
|
#define ITD_XACT_PGSEL_MASK 0x00007000
|
|
|
|
#define ITD_XACT_PGSEL_SH 12
|
|
|
|
#define ITD_XACT_OFFSET_MASK 0x00000fff
|
|
|
|
|
|
|
|
uint32_t bufptr[7];
|
|
|
|
#define ITD_BUFPTR_MASK 0xfffff000
|
|
|
|
#define ITD_BUFPTR_SH 12
|
|
|
|
#define ITD_BUFPTR_EP_MASK 0x00000f00
|
|
|
|
#define ITD_BUFPTR_EP_SH 8
|
|
|
|
#define ITD_BUFPTR_DEVADDR_MASK 0x0000007f
|
|
|
|
#define ITD_BUFPTR_DEVADDR_SH 0
|
|
|
|
#define ITD_BUFPTR_DIRECTION (1 << 11)
|
|
|
|
#define ITD_BUFPTR_MAXPKT_MASK 0x000007ff
|
|
|
|
#define ITD_BUFPTR_MAXPKT_SH 0
|
|
|
|
#define ITD_BUFPTR_MULT_MASK 0x00000003
|
2011-05-30 18:09:08 +04:00
|
|
|
#define ITD_BUFPTR_MULT_SH 0
|
2010-12-03 18:17:28 +03:00
|
|
|
} 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
|
2011-07-13 19:36:46 +04:00
|
|
|
#define QTD_BUFPTR_SH 12
|
2010-12-03 18:17:28 +03:00
|
|
|
} 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;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
typedef struct EHCIPacket EHCIPacket;
|
2011-05-19 12:49:03 +04:00
|
|
|
typedef struct EHCIQueue EHCIQueue;
|
|
|
|
typedef struct EHCIState EHCIState;
|
|
|
|
|
|
|
|
enum async_state {
|
|
|
|
EHCI_ASYNC_NONE = 0,
|
2012-09-03 13:01:13 +04:00
|
|
|
EHCI_ASYNC_INITIALIZED,
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCI_ASYNC_INFLIGHT,
|
|
|
|
EHCI_ASYNC_FINISHED,
|
|
|
|
};
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
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;
|
|
|
|
};
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
struct EHCIQueue {
|
|
|
|
EHCIState *ehci;
|
2011-05-19 19:56:19 +04:00
|
|
|
QTAILQ_ENTRY(EHCIQueue) next;
|
2011-05-31 14:23:13 +04:00
|
|
|
uint32_t seen;
|
|
|
|
uint64_t ts;
|
2012-05-11 11:05:15 +04:00
|
|
|
int async;
|
2011-05-19 12:49:03 +04:00
|
|
|
|
|
|
|
/* cached data from guest - needs to be flushed
|
|
|
|
* when guest removes an entry (doorbell, handshake sequence)
|
|
|
|
*/
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIqh qh; /* copy of current QH (being worked on) */
|
|
|
|
uint32_t qhaddr; /* address QH read from */
|
|
|
|
uint32_t qtdaddr; /* address QTD read from */
|
2012-05-10 14:18:45 +04:00
|
|
|
USBDevice *dev;
|
2012-05-09 19:06:36 +04:00
|
|
|
QTAILQ_HEAD(, EHCIPacket) packets;
|
2011-05-19 12:49:03 +04:00
|
|
|
};
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
typedef QTAILQ_HEAD(EHCIQueueHead, EHCIQueue) EHCIQueueHead;
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
struct EHCIState {
|
2010-12-03 18:17:28 +03:00
|
|
|
PCIDevice dev;
|
2011-05-19 12:49:03 +04:00
|
|
|
USBBus bus;
|
2010-12-03 18:17:28 +03:00
|
|
|
qemu_irq irq;
|
2011-08-08 17:09:23 +04:00
|
|
|
MemoryRegion mem;
|
2011-06-24 18:18:13 +04:00
|
|
|
int companion_count;
|
2011-05-30 18:24:29 +04:00
|
|
|
|
|
|
|
/* properties */
|
|
|
|
uint32_t maxframes;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/*
|
|
|
|
* 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];
|
|
|
|
};
|
|
|
|
};
|
2011-05-19 12:49:03 +04:00
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/*
|
|
|
|
* Internal states, shadow registers, etc
|
|
|
|
*/
|
|
|
|
QEMUTimer *frame_timer;
|
2012-05-11 13:19:11 +04:00
|
|
|
QEMUBH *async_bh;
|
2012-05-14 15:55:44 +04:00
|
|
|
uint32_t astate; /* Current state in asynchronous schedule */
|
|
|
|
uint32_t pstate; /* Current state in periodic schedule */
|
2010-12-03 18:17:28 +03:00
|
|
|
USBPort ports[NB_PORTS];
|
2011-06-24 18:18:13 +04:00
|
|
|
USBPort *companion_ports[NB_PORTS];
|
2010-12-03 18:17:28 +03:00
|
|
|
uint32_t usbsts_pending;
|
2012-07-11 13:06:05 +04:00
|
|
|
uint32_t usbsts_frindex;
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead aqueues;
|
|
|
|
EHCIQueueHead pqueues;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-14 15:55:44 +04:00
|
|
|
/* which address to look at next */
|
|
|
|
uint32_t a_fetch_addr;
|
|
|
|
uint32_t p_fetch_addr;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
USBPacket ipacket;
|
2011-07-13 19:36:46 +04:00
|
|
|
QEMUSGList isgl;
|
2011-05-19 12:49:03 +04:00
|
|
|
|
2011-05-31 14:23:13 +04:00
|
|
|
uint64_t last_run_ns;
|
2012-05-24 15:34:02 +04:00
|
|
|
uint32_t async_stepdown;
|
2011-05-19 12:49:03 +04:00
|
|
|
};
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
#define SET_LAST_RUN_CLOCK(s) \
|
2011-05-31 14:23:13 +04:00
|
|
|
(s)->last_run_ns = qemu_get_clock_ns(vm_clock);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* nifty macros from Arnon's EHCI version */
|
|
|
|
#define get_field(data, field) \
|
|
|
|
(((data) & field##_MASK) >> field##_SH)
|
|
|
|
|
|
|
|
#define set_field(data, newval, field) do { \
|
|
|
|
uint32_t val = *data; \
|
|
|
|
val &= ~ field##_MASK; \
|
|
|
|
val |= ((newval) << field##_SH) & field##_MASK; \
|
|
|
|
*data = val; \
|
|
|
|
} while(0)
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static const char *ehci_state_names[] = {
|
2011-11-18 13:48:47 +04:00
|
|
|
[EST_INACTIVE] = "INACTIVE",
|
|
|
|
[EST_ACTIVE] = "ACTIVE",
|
|
|
|
[EST_EXECUTING] = "EXECUTING",
|
|
|
|
[EST_SLEEPING] = "SLEEPING",
|
|
|
|
[EST_WAITLISTHEAD] = "WAITLISTHEAD",
|
|
|
|
[EST_FETCHENTRY] = "FETCH ENTRY",
|
|
|
|
[EST_FETCHQH] = "FETCH QH",
|
|
|
|
[EST_FETCHITD] = "FETCH ITD",
|
|
|
|
[EST_ADVANCEQUEUE] = "ADVANCEQUEUE",
|
|
|
|
[EST_FETCHQTD] = "FETCH QTD",
|
|
|
|
[EST_EXECUTE] = "EXECUTE",
|
|
|
|
[EST_WRITEBACK] = "WRITEBACK",
|
|
|
|
[EST_HORIZONTALQH] = "HORIZONTALQH",
|
2011-05-18 16:23:35 +04:00
|
|
|
};
|
|
|
|
|
|
|
|
static const char *ehci_mmio_names[] = {
|
2011-11-18 13:48:47 +04:00
|
|
|
[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",
|
2011-11-18 13:49:25 +04:00
|
|
|
[PORTSC_BEGIN + 16] = "PORTSC #4",
|
|
|
|
[PORTSC_BEGIN + 20] = "PORTSC #5",
|
2011-11-18 13:48:47 +04:00
|
|
|
[CONFIGFLAG] = "CONFIGFLAG",
|
2011-05-18 16:23:35 +04:00
|
|
|
};
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-08-30 17:18:24 +04:00
|
|
|
static int ehci_state_executing(EHCIQueue *q);
|
|
|
|
static int ehci_state_writeback(EHCIQueue *q);
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static const char *nr2str(const char **n, size_t len, uint32_t nr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-18 16:23:35 +04:00
|
|
|
if (nr < len && n[nr] != NULL) {
|
|
|
|
return n[nr];
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
2011-05-18 16:23:35 +04:00
|
|
|
return "unknown";
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
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)
|
|
|
|
{
|
|
|
|
return nr2str(ehci_mmio_names, ARRAY_SIZE(ehci_mmio_names), addr);
|
|
|
|
}
|
|
|
|
|
2011-05-18 12:12:58 +04:00
|
|
|
static void ehci_trace_usbsts(uint32_t mask, int state)
|
|
|
|
{
|
|
|
|
/* interrupts */
|
|
|
|
if (mask & USBSTS_INT) {
|
|
|
|
trace_usb_ehci_usbsts("INT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_ERRINT) {
|
|
|
|
trace_usb_ehci_usbsts("ERRINT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_PCD) {
|
|
|
|
trace_usb_ehci_usbsts("PCD", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_FLR) {
|
|
|
|
trace_usb_ehci_usbsts("FLR", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_HSE) {
|
|
|
|
trace_usb_ehci_usbsts("HSE", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_IAA) {
|
|
|
|
trace_usb_ehci_usbsts("IAA", state);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* status */
|
|
|
|
if (mask & USBSTS_HALT) {
|
|
|
|
trace_usb_ehci_usbsts("HALT", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_REC) {
|
|
|
|
trace_usb_ehci_usbsts("REC", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_PSS) {
|
|
|
|
trace_usb_ehci_usbsts("PSS", state);
|
|
|
|
}
|
|
|
|
if (mask & USBSTS_ASS) {
|
|
|
|
trace_usb_ehci_usbsts("ASS", state);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ehci_set_usbsts(EHCIState *s, int mask)
|
|
|
|
{
|
|
|
|
if ((s->usbsts & mask) == mask) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ehci_trace_usbsts(mask, 1);
|
|
|
|
s->usbsts |= mask;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline void ehci_clear_usbsts(EHCIState *s, int mask)
|
|
|
|
{
|
|
|
|
if ((s->usbsts & mask) == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ehci_trace_usbsts(mask, 0);
|
|
|
|
s->usbsts &= ~mask;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/* update irq line */
|
|
|
|
static inline void ehci_update_irq(EHCIState *s)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int level = 0;
|
|
|
|
|
|
|
|
if ((s->usbsts & USBINTR_MASK) & s->usbintr) {
|
|
|
|
level = 1;
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
trace_usb_ehci_irq(level, s->frindex, s->usbsts, s->usbintr);
|
2010-12-03 18:17:28 +03:00
|
|
|
qemu_set_irq(s->irq, level);
|
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/* flag interrupt condition */
|
|
|
|
static inline void ehci_raise_irq(EHCIState *s, int intr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-08-15 15:55:40 +04:00
|
|
|
if (intr & (USBSTS_PCD | USBSTS_FLR | USBSTS_HSE)) {
|
|
|
|
s->usbsts |= intr;
|
|
|
|
ehci_update_irq(s);
|
|
|
|
} else {
|
|
|
|
s->usbsts_pending |= intr;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
/*
|
|
|
|
* Commit pending interrupts (added via ehci_raise_irq),
|
|
|
|
* at the rate allowed by "Interrupt Threshold Control".
|
|
|
|
*/
|
|
|
|
static inline void ehci_commit_irq(EHCIState *s)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-07-11 13:06:05 +04:00
|
|
|
uint32_t itc;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
if (!s->usbsts_pending) {
|
|
|
|
return;
|
|
|
|
}
|
2012-07-11 13:06:05 +04:00
|
|
|
if (s->usbsts_frindex > s->frindex) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
itc = (s->usbcmd >> 16) & 0xff;
|
|
|
|
s->usbsts |= s->usbsts_pending;
|
2010-12-03 18:17:28 +03:00
|
|
|
s->usbsts_pending = 0;
|
2012-07-11 13:06:05 +04:00
|
|
|
s->usbsts_frindex = s->frindex + itc;
|
|
|
|
ehci_update_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-24 14:53:43 +04:00
|
|
|
static void ehci_update_halt(EHCIState *s)
|
|
|
|
{
|
|
|
|
if (s->usbcmd & USBCMD_RUNSTOP) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_HALT);
|
|
|
|
} else {
|
|
|
|
if (s->astate == EST_INACTIVE && s->pstate == EST_INACTIVE) {
|
|
|
|
ehci_set_usbsts(s, USBSTS_HALT);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static void ehci_set_state(EHCIState *s, int async, int state)
|
|
|
|
{
|
|
|
|
if (async) {
|
|
|
|
trace_usb_ehci_state("async", state2str(state));
|
|
|
|
s->astate = state;
|
2012-05-24 14:34:18 +04:00
|
|
|
if (s->astate == EST_INACTIVE) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_ASS);
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 14:34:18 +04:00
|
|
|
} else {
|
|
|
|
ehci_set_usbsts(s, USBSTS_ASS);
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
} else {
|
|
|
|
trace_usb_ehci_state("periodic", state2str(state));
|
|
|
|
s->pstate = state;
|
2012-05-24 14:34:18 +04:00
|
|
|
if (s->pstate == EST_INACTIVE) {
|
|
|
|
ehci_clear_usbsts(s, USBSTS_PSS);
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 14:34:18 +04:00
|
|
|
} else {
|
|
|
|
ehci_set_usbsts(s, USBSTS_PSS);
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_get_state(EHCIState *s, int async)
|
|
|
|
{
|
|
|
|
return async ? s->astate : s->pstate;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static void ehci_set_fetch_addr(EHCIState *s, int async, uint32_t addr)
|
|
|
|
{
|
|
|
|
if (async) {
|
|
|
|
s->a_fetch_addr = addr;
|
|
|
|
} else {
|
|
|
|
s->p_fetch_addr = addr;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_get_fetch_addr(EHCIState *s, int async)
|
|
|
|
{
|
|
|
|
return async ? s->a_fetch_addr : s->p_fetch_addr;
|
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
static void ehci_trace_qh(EHCIQueue *q, target_phys_addr_t addr, EHCIqh *qh)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2011-06-06 14:31:34 +04:00
|
|
|
/* need three here due to argument count limits */
|
|
|
|
trace_usb_ehci_qh_ptrs(q, addr, qh->next,
|
|
|
|
qh->current_qtd, qh->next_qtd, qh->altnext_qtd);
|
|
|
|
trace_usb_ehci_qh_fields(addr,
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_RL),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_MPLEN),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_EPS),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_EP),
|
|
|
|
get_field(qh->epchar, QH_EPCHAR_DEVADDR));
|
|
|
|
trace_usb_ehci_qh_bits(addr,
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_C),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_H),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_DTC),
|
|
|
|
(bool)(qh->epchar & QH_EPCHAR_I));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
static void ehci_trace_qtd(EHCIQueue *q, target_phys_addr_t addr, EHCIqtd *qtd)
|
2011-05-18 16:23:35 +04:00
|
|
|
{
|
2011-06-06 14:31:34 +04:00
|
|
|
/* need three here due to argument count limits */
|
|
|
|
trace_usb_ehci_qtd_ptrs(q, addr, qtd->next, qtd->altnext);
|
|
|
|
trace_usb_ehci_qtd_fields(addr,
|
|
|
|
get_field(qtd->token, QTD_TOKEN_TBYTES),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_CPAGE),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_CERR),
|
|
|
|
get_field(qtd->token, QTD_TOKEN_PID));
|
|
|
|
trace_usb_ehci_qtd_bits(addr,
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_IOC),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_ACTIVE),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_HALT),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_BABBLE),
|
|
|
|
(bool)(qtd->token & QTD_TOKEN_XACTERR));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_trace_itd(EHCIState *s, target_phys_addr_t addr, EHCIitd *itd)
|
|
|
|
{
|
2011-05-30 18:09:08 +04:00
|
|
|
trace_usb_ehci_itd(addr, itd->next,
|
|
|
|
get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT),
|
|
|
|
get_field(itd->bufptr[2], ITD_BUFPTR_MULT),
|
|
|
|
get_field(itd->bufptr[0], ITD_BUFPTR_EP),
|
|
|
|
get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR));
|
2011-05-18 16:23:35 +04:00
|
|
|
}
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
static void ehci_trace_sitd(EHCIState *s, target_phys_addr_t addr,
|
|
|
|
EHCIsitd *sitd)
|
|
|
|
{
|
|
|
|
trace_usb_ehci_sitd(addr, sitd->next,
|
|
|
|
(bool)(sitd->results & SITD_RESULTS_ACTIVE));
|
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static void ehci_trace_guest_bug(EHCIState *s, const char *message)
|
|
|
|
{
|
|
|
|
trace_usb_ehci_guest_bug(message);
|
|
|
|
fprintf(stderr, "ehci warning: %s\n", message);
|
|
|
|
}
|
|
|
|
|
2012-05-24 14:31:34 +04:00
|
|
|
static inline bool ehci_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return s->usbcmd & USBCMD_RUNSTOP;
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool ehci_async_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return ehci_enabled(s) && (s->usbcmd & USBCMD_ASE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static inline bool ehci_periodic_enabled(EHCIState *s)
|
|
|
|
{
|
|
|
|
return ehci_enabled(s) && (s->usbcmd & USBCMD_PSE);
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
/* packet management */
|
|
|
|
|
|
|
|
static EHCIPacket *ehci_alloc_packet(EHCIQueue *q)
|
|
|
|
{
|
|
|
|
EHCIPacket *p;
|
|
|
|
|
|
|
|
p = g_new0(EHCIPacket, 1);
|
|
|
|
p->queue = q;
|
|
|
|
usb_packet_init(&p->packet);
|
|
|
|
QTAILQ_INSERT_TAIL(&q->packets, p, next);
|
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "alloc");
|
|
|
|
return p;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_free_packet(EHCIPacket *p)
|
|
|
|
{
|
2012-08-30 17:18:24 +04:00
|
|
|
if (p->async == EHCI_ASYNC_FINISHED) {
|
|
|
|
int state = ehci_get_state(p->queue->ehci, p->queue->async);
|
|
|
|
/* This is a normal, but rare condition (cancel racing completion) */
|
|
|
|
fprintf(stderr, "EHCI: Warning packet completed but not processed\n");
|
|
|
|
ehci_state_executing(p->queue);
|
|
|
|
ehci_state_writeback(p->queue);
|
|
|
|
ehci_set_state(p->queue->ehci, p->queue->async, state);
|
|
|
|
/* state_writeback recurses into us with async == EHCI_ASYNC_NONE!! */
|
|
|
|
return;
|
|
|
|
}
|
2012-08-31 12:31:54 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "free");
|
2012-09-03 13:01:13 +04:00
|
|
|
if (p->async == EHCI_ASYNC_INITIALIZED) {
|
|
|
|
usb_packet_unmap(&p->packet, &p->sgl);
|
|
|
|
qemu_sglist_destroy(&p->sgl);
|
|
|
|
}
|
2012-08-31 12:31:54 +04:00
|
|
|
if (p->async == EHCI_ASYNC_INFLIGHT) {
|
|
|
|
usb_cancel_packet(&p->packet);
|
|
|
|
usb_packet_unmap(&p->packet, &p->sgl);
|
|
|
|
qemu_sglist_destroy(&p->sgl);
|
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
QTAILQ_REMOVE(&p->queue->packets, p, next);
|
|
|
|
usb_packet_cleanup(&p->packet);
|
|
|
|
g_free(p);
|
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
/* queue management */
|
|
|
|
|
2012-05-11 10:56:49 +04:00
|
|
|
static EHCIQueue *ehci_alloc_queue(EHCIState *ehci, uint32_t addr, int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q;
|
|
|
|
|
2011-08-21 07:09:37 +04:00
|
|
|
q = g_malloc0(sizeof(*q));
|
2011-05-19 19:56:19 +04:00
|
|
|
q->ehci = ehci;
|
2012-05-11 10:56:49 +04:00
|
|
|
q->qhaddr = addr;
|
2012-05-11 11:05:15 +04:00
|
|
|
q->async = async;
|
2012-05-09 19:06:36 +04:00
|
|
|
QTAILQ_INIT(&q->packets);
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_INSERT_HEAD(head, q, next);
|
2011-05-19 19:56:19 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "alloc");
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static int ehci_cancel_queue(EHCIQueue *q)
|
2012-08-21 15:58:40 +04:00
|
|
|
{
|
|
|
|
EHCIPacket *p;
|
2012-08-31 12:44:21 +04:00
|
|
|
int packets = 0;
|
2012-08-21 15:58:40 +04:00
|
|
|
|
|
|
|
p = QTAILQ_FIRST(&q->packets);
|
|
|
|
if (p == NULL) {
|
2012-08-31 12:44:21 +04:00
|
|
|
return 0;
|
2012-08-21 15:58:40 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
trace_usb_ehci_queue_action(q, "cancel");
|
|
|
|
do {
|
|
|
|
ehci_free_packet(p);
|
2012-08-31 12:44:21 +04:00
|
|
|
packets++;
|
2012-08-21 15:58:40 +04:00
|
|
|
} while ((p = QTAILQ_FIRST(&q->packets)) != NULL);
|
2012-08-31 12:44:21 +04:00
|
|
|
return packets;
|
2012-08-21 15:58:40 +04:00
|
|
|
}
|
|
|
|
|
2012-08-31 12:44:21 +04:00
|
|
|
static int ehci_reset_queue(EHCIQueue *q)
|
2012-08-29 12:37:37 +04:00
|
|
|
{
|
2012-08-31 12:44:21 +04:00
|
|
|
int packets;
|
|
|
|
|
2012-08-29 12:37:37 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "reset");
|
2012-08-31 12:44:21 +04:00
|
|
|
packets = ehci_cancel_queue(q);
|
2012-08-29 12:37:37 +04:00
|
|
|
q->dev = NULL;
|
|
|
|
q->qtdaddr = 0;
|
2012-08-31 12:44:21 +04:00
|
|
|
return packets;
|
2012-08-29 12:37:37 +04:00
|
|
|
}
|
|
|
|
|
2012-09-03 12:22:16 +04:00
|
|
|
static void ehci_free_queue(EHCIQueue *q, const char *warn)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-05-11 11:05:15 +04:00
|
|
|
EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues;
|
2012-09-03 12:22:16 +04:00
|
|
|
int cancelled;
|
2012-05-09 19:06:36 +04:00
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
trace_usb_ehci_queue_action(q, "free");
|
2012-09-03 12:22:16 +04:00
|
|
|
cancelled = ehci_cancel_queue(q);
|
|
|
|
if (warn && cancelled > 0) {
|
|
|
|
ehci_trace_guest_bug(q->ehci, warn);
|
|
|
|
}
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_REMOVE(head, q, next);
|
2011-08-21 07:09:37 +04:00
|
|
|
g_free(q);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static EHCIQueue *ehci_find_queue_by_qh(EHCIState *ehci, uint32_t addr,
|
|
|
|
int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH(q, head, next) {
|
2011-05-19 19:56:19 +04:00
|
|
|
if (addr == q->qhaddr) {
|
|
|
|
return q;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2012-08-29 12:12:52 +04:00
|
|
|
static void ehci_queues_rip_unused(EHCIState *ehci, int async, int flush)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2012-09-03 12:22:16 +04:00
|
|
|
const char *warn = (async && !flush) ? "guest unlinked busy QH" : NULL;
|
2012-05-24 15:34:02 +04:00
|
|
|
uint64_t maxage = FRAME_TIMER_NS * ehci->maxframes * 4;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2011-05-19 19:56:19 +04:00
|
|
|
if (q->seen) {
|
|
|
|
q->seen = 0;
|
2011-05-31 14:23:13 +04:00
|
|
|
q->ts = ehci->last_run_ns;
|
2011-05-19 19:56:19 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-08-29 12:12:52 +04:00
|
|
|
if (!flush && ehci->last_run_ns < q->ts + maxage) {
|
2011-05-19 19:56:19 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, warn);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static void ehci_queues_rip_device(EHCIState *ehci, USBDevice *dev, int async)
|
2011-05-23 19:37:12 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2011-05-23 19:37:12 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2012-05-10 14:18:45 +04:00
|
|
|
if (q->dev != dev) {
|
2011-05-23 19:37:12 +04:00
|
|
|
continue;
|
|
|
|
}
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, NULL);
|
2011-05-23 19:37:12 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
static void ehci_queues_rip_all(EHCIState *ehci, int async)
|
2011-05-19 19:56:19 +04:00
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
EHCIQueueHead *head = async ? &ehci->aqueues : &ehci->pqueues;
|
2012-09-03 12:22:16 +04:00
|
|
|
const char *warn = async ? "guest stopped busy async schedule" : NULL;
|
2011-05-19 19:56:19 +04:00
|
|
|
EHCIQueue *q, *tmp;
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_FOREACH_SAFE(q, head, next, tmp) {
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_free_queue(q, warn);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Attach or detach a device on root hub */
|
|
|
|
|
|
|
|
static void ehci_attach(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t *portsc = &s->portsc[port->index];
|
2012-06-08 15:00:44 +04:00
|
|
|
const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-08 15:00:44 +04:00
|
|
|
trace_usb_ehci_port_attach(port->index, owner, port->dev->product_desc);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
if (*portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->dev = port->dev;
|
|
|
|
companion->ops->attach(companion);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc |= PORTSC_CONNECT;
|
|
|
|
*portsc |= PORTSC_CSC;
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(s, USBSTS_PCD);
|
|
|
|
ehci_commit_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_detach(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t *portsc = &s->portsc[port->index];
|
2012-06-08 15:00:44 +04:00
|
|
|
const char *owner = (*portsc & PORTSC_POWNER) ? "comp" : "ehci";
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-08 15:00:44 +04:00
|
|
|
trace_usb_ehci_port_detach(port->index, owner);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
if (*portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->detach(companion);
|
|
|
|
companion->dev = NULL;
|
2012-01-13 17:28:56 +04:00
|
|
|
/*
|
|
|
|
* EHCI spec 4.2.2: "When a disconnect occurs... On the event,
|
|
|
|
* the port ownership is returned immediately to the EHCI controller."
|
|
|
|
*/
|
|
|
|
*portsc &= ~PORTSC_POWNER;
|
2011-06-24 18:18:13 +04:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_device(s, port->dev, 0);
|
|
|
|
ehci_queues_rip_device(s, port->dev, 1);
|
2011-06-24 14:31:11 +04:00
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
*portsc &= ~(PORTSC_CONNECT|PORTSC_PED);
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc |= PORTSC_CSC;
|
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(s, USBSTS_PCD);
|
|
|
|
ehci_commit_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-06-24 14:31:11 +04:00
|
|
|
static void ehci_child_detach(USBPort *port, USBDevice *child)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
2011-06-24 18:18:13 +04:00
|
|
|
uint32_t portsc = s->portsc[port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->child_detach(companion, child);
|
|
|
|
return;
|
|
|
|
}
|
2011-06-24 14:31:11 +04:00
|
|
|
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_device(s, child, 0);
|
|
|
|
ehci_queues_rip_device(s, child, 1);
|
2011-06-24 14:31:11 +04:00
|
|
|
}
|
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
static void ehci_wakeup(USBPort *port)
|
|
|
|
{
|
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t portsc = s->portsc[port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
if (companion->ops->wakeup) {
|
|
|
|
companion->ops->wakeup(companion);
|
|
|
|
}
|
2012-07-06 18:53:39 +04:00
|
|
|
return;
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
2012-07-06 18:53:39 +04:00
|
|
|
|
|
|
|
qemu_bh_schedule(s->async_bh);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
static int ehci_register_companion(USBBus *bus, USBPort *ports[],
|
|
|
|
uint32_t portcount, uint32_t firstport)
|
|
|
|
{
|
|
|
|
EHCIState *s = container_of(bus, EHCIState, bus);
|
|
|
|
uint32_t i;
|
|
|
|
|
|
|
|
if (firstport + portcount > NB_PORTS) {
|
|
|
|
qerror_report(QERR_INVALID_PARAMETER_VALUE, "firstport",
|
|
|
|
"firstport on masterbus");
|
|
|
|
error_printf_unless_qmp(
|
|
|
|
"firstport value of %u makes companion take ports %u - %u, which "
|
|
|
|
"is outside of the valid range of 0 - %u\n", firstport, firstport,
|
|
|
|
firstport + portcount - 1, NB_PORTS - 1);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < portcount; i++) {
|
|
|
|
if (s->companion_ports[firstport + i]) {
|
|
|
|
qerror_report(QERR_INVALID_PARAMETER_VALUE, "masterbus",
|
|
|
|
"an USB masterbus");
|
|
|
|
error_printf_unless_qmp(
|
|
|
|
"port %u on masterbus %s already has a companion assigned\n",
|
|
|
|
firstport + i, bus->qbus.name);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < portcount; i++) {
|
|
|
|
s->companion_ports[firstport + i] = ports[i];
|
|
|
|
s->ports[firstport + i].speedmask |=
|
|
|
|
USB_SPEED_MASK_LOW | USB_SPEED_MASK_FULL;
|
|
|
|
/* Ensure devs attached before the initial reset go to the companion */
|
|
|
|
s->portsc[firstport + i] = PORTSC_POWNER;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->companion_count++;
|
|
|
|
s->mmio[0x05] = (s->companion_count << 4) | portcount;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-01-10 20:46:15 +04:00
|
|
|
static USBDevice *ehci_find_device(EHCIState *ehci, uint8_t addr)
|
|
|
|
{
|
|
|
|
USBDevice *dev;
|
|
|
|
USBPort *port;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < NB_PORTS; i++) {
|
|
|
|
port = &ehci->ports[i];
|
|
|
|
if (!(ehci->portsc[i] & PORTSC_PED)) {
|
|
|
|
DPRINTF("Port %d not enabled\n", i);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
dev = usb_find_device(port, addr);
|
|
|
|
if (dev != NULL) {
|
|
|
|
return dev;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* 4.1 host controller initialization */
|
|
|
|
static void ehci_reset(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *s = opaque;
|
|
|
|
int i;
|
2011-06-24 18:18:13 +04:00
|
|
|
USBDevice *devs[NB_PORTS];
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-18 12:12:58 +04:00
|
|
|
trace_usb_ehci_reset();
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
/*
|
|
|
|
* Do the detach before touching portsc, so that it correctly gets send to
|
|
|
|
* us or to our companion based on PORTSC_POWNER before the reset.
|
|
|
|
*/
|
|
|
|
for(i = 0; i < NB_PORTS; i++) {
|
|
|
|
devs[i] = s->ports[i].dev;
|
2011-09-01 15:56:37 +04:00
|
|
|
if (devs[i] && devs[i]->attached) {
|
|
|
|
usb_detach(&s->ports[i]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
memset(&s->mmio[OPREGBASE], 0x00, MMIO_SIZE - OPREGBASE);
|
|
|
|
|
|
|
|
s->usbcmd = NB_MAXINTRATE << USBCMD_ITC_SH;
|
|
|
|
s->usbsts = USBSTS_HALT;
|
2012-07-11 13:06:05 +04:00
|
|
|
s->usbsts_pending = 0;
|
|
|
|
s->usbsts_frindex = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
s->astate = EST_INACTIVE;
|
|
|
|
s->pstate = EST_INACTIVE;
|
|
|
|
|
|
|
|
for(i = 0; i < NB_PORTS; i++) {
|
2011-06-24 18:18:13 +04:00
|
|
|
if (s->companion_ports[i]) {
|
|
|
|
s->portsc[i] = PORTSC_POWNER | PORTSC_PPOWER;
|
|
|
|
} else {
|
|
|
|
s->portsc[i] = PORTSC_PPOWER;
|
|
|
|
}
|
2011-09-01 15:56:37 +04:00
|
|
|
if (devs[i] && devs[i]->attached) {
|
|
|
|
usb_attach(&s->ports[i]);
|
2012-01-06 18:23:10 +04:00
|
|
|
usb_device_reset(devs[i]);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
2012-03-03 00:27:10 +04:00
|
|
|
ehci_queues_rip_all(s, 0);
|
|
|
|
ehci_queues_rip_all(s, 1);
|
2012-02-23 17:24:00 +04:00
|
|
|
qemu_del_timer(s->frame_timer);
|
2012-05-11 13:19:11 +04:00
|
|
|
qemu_bh_cancel(s->async_bh);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t ehci_mem_readb(void *ptr, target_phys_addr_t addr)
|
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
|
|
|
uint32_t val;
|
|
|
|
|
|
|
|
val = s->mmio[addr];
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t ehci_mem_readw(void *ptr, target_phys_addr_t addr)
|
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
|
|
|
uint32_t val;
|
|
|
|
|
|
|
|
val = s->mmio[addr] | (s->mmio[addr+1] << 8);
|
|
|
|
|
|
|
|
return val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t ehci_mem_readl(void *ptr, target_phys_addr_t addr)
|
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
|
|
|
uint32_t val;
|
|
|
|
|
|
|
|
val = s->mmio[addr] | (s->mmio[addr+1] << 8) |
|
|
|
|
(s->mmio[addr+2] << 16) | (s->mmio[addr+3] << 24);
|
|
|
|
|
2011-05-18 12:12:58 +04:00
|
|
|
trace_usb_ehci_mmio_readl(addr, addr2str(addr), val);
|
2010-12-03 18:17:28 +03:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2011-06-24 18:18:13 +04:00
|
|
|
static void handle_port_owner_write(EHCIState *s, int port, uint32_t owner)
|
|
|
|
{
|
|
|
|
USBDevice *dev = s->ports[port].dev;
|
|
|
|
uint32_t *portsc = &s->portsc[port];
|
|
|
|
uint32_t orig;
|
|
|
|
|
|
|
|
if (s->companion_ports[port] == NULL)
|
|
|
|
return;
|
|
|
|
|
|
|
|
owner = owner & PORTSC_POWNER;
|
|
|
|
orig = *portsc & PORTSC_POWNER;
|
|
|
|
|
|
|
|
if (!(owner ^ orig)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
|
|
|
usb_detach(&s->ports[port]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
*portsc &= ~PORTSC_POWNER;
|
|
|
|
*portsc |= owner;
|
|
|
|
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
|
|
|
usb_attach(&s->ports[port]);
|
2011-06-24 18:18:13 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
static void handle_port_status_write(EHCIState *s, int port, uint32_t val)
|
|
|
|
{
|
|
|
|
uint32_t *portsc = &s->portsc[port];
|
|
|
|
USBDevice *dev = s->ports[port].dev;
|
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
/* Clear rwc bits */
|
|
|
|
*portsc &= ~(val & PORTSC_RWC_MASK);
|
|
|
|
/* The guest may clear, but not set the PED bit */
|
|
|
|
*portsc &= val | ~PORTSC_PED;
|
2011-06-24 18:18:13 +04:00
|
|
|
/* POWNER is masked out by RO_MASK as it is RO when we've no companion */
|
|
|
|
handle_port_owner_write(s, port, val);
|
|
|
|
/* And finally apply RO_MASK */
|
2010-12-03 18:17:28 +03:00
|
|
|
val &= PORTSC_RO_MASK;
|
|
|
|
|
|
|
|
if ((val & PORTSC_PRESET) && !(*portsc & PORTSC_PRESET)) {
|
2011-05-19 10:46:53 +04:00
|
|
|
trace_usb_ehci_port_reset(port, 1);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!(val & PORTSC_PRESET) &&(*portsc & PORTSC_PRESET)) {
|
2011-05-19 10:46:53 +04:00
|
|
|
trace_usb_ehci_port_reset(port, 0);
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached) {
|
2012-01-06 18:23:10 +04:00
|
|
|
usb_port_reset(&s->ports[port]);
|
2010-12-03 18:17:28 +03:00
|
|
|
*portsc &= ~PORTSC_CSC;
|
|
|
|
}
|
|
|
|
|
2011-06-24 16:36:13 +04:00
|
|
|
/*
|
|
|
|
* Table 2.16 Set the enable bit(and enable bit change) to indicate
|
2010-12-03 18:17:28 +03:00
|
|
|
* to SW that this port has a high speed device attached
|
|
|
|
*/
|
2011-09-01 15:56:37 +04:00
|
|
|
if (dev && dev->attached && (dev->speedmask & USB_SPEED_MASK_HIGH)) {
|
2011-06-24 16:36:13 +04:00
|
|
|
val |= PORTSC_PED;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
*portsc &= ~PORTSC_RO_MASK;
|
|
|
|
*portsc |= val;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_mem_writel(void *ptr, target_phys_addr_t addr, uint32_t val)
|
|
|
|
{
|
|
|
|
EHCIState *s = ptr;
|
2011-05-19 10:55:09 +04:00
|
|
|
uint32_t *mmio = (uint32_t *)(&s->mmio[addr]);
|
|
|
|
uint32_t old = *mmio;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
2011-05-18 12:12:58 +04:00
|
|
|
|
2011-05-19 10:55:09 +04:00
|
|
|
trace_usb_ehci_mmio_writel(addr, addr2str(addr), val);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* 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);
|
2011-05-19 10:55:09 +04:00
|
|
|
trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
|
2010-12-03 18:17:28 +03:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (addr < OPREGBASE) {
|
|
|
|
fprintf(stderr, "usb-ehci: write attempt to read-only register"
|
|
|
|
TARGET_FMT_plx "\n", addr);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* Do any register specific pre-write processing here. */
|
|
|
|
switch(addr) {
|
|
|
|
case USBCMD:
|
2012-05-24 14:04:50 +04:00
|
|
|
if (val & USBCMD_HCRESET) {
|
|
|
|
ehci_reset(s);
|
|
|
|
val = s->usbcmd;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-08-15 19:08:54 +04:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2012-08-30 11:55:19 +04:00
|
|
|
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);
|
2012-08-31 14:41:43 +04:00
|
|
|
trace_usb_ehci_doorbell_ring();
|
2012-08-30 11:55:19 +04:00
|
|
|
}
|
|
|
|
|
2012-05-24 14:53:43 +04:00
|
|
|
if (((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & val) !=
|
|
|
|
((USBCMD_RUNSTOP | USBCMD_PSE | USBCMD_ASE) & s->usbcmd)) {
|
2012-05-24 15:34:02 +04:00
|
|
|
if (s->pstate == EST_INACTIVE) {
|
2012-05-24 14:53:43 +04:00
|
|
|
SET_LAST_RUN_CLOCK(s);
|
|
|
|
}
|
2012-08-15 19:08:54 +04:00
|
|
|
s->usbcmd = val; /* Set usbcmd for ehci_update_halt() */
|
2012-05-24 14:53:43 +04:00
|
|
|
ehci_update_halt(s);
|
2012-05-24 15:34:02 +04:00
|
|
|
s->async_stepdown = 0;
|
|
|
|
qemu_mod_timer(s->frame_timer, qemu_get_clock_ns(vm_clock));
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case USBSTS:
|
2012-05-09 09:12:04 +04:00
|
|
|
val &= USBSTS_RO_MASK; // bits 6 through 31 are RO
|
|
|
|
ehci_clear_usbsts(s, val); // bits 0 through 5 are R/WC
|
2011-05-18 12:12:58 +04:00
|
|
|
val = s->usbsts;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_update_irq(s);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case USBINTR:
|
|
|
|
val &= USBINTR_MASK;
|
|
|
|
break;
|
|
|
|
|
2012-04-03 16:21:47 +04:00
|
|
|
case FRINDEX:
|
|
|
|
val &= 0x00003ff8; /* frindex is 14bits and always a multiple of 8 */
|
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
case CONFIGFLAG:
|
|
|
|
val &= 0x1;
|
|
|
|
if (val) {
|
|
|
|
for(i = 0; i < NB_PORTS; i++)
|
2011-06-24 18:18:13 +04:00
|
|
|
handle_port_owner_write(s, i, 0);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case PERIODICLISTBASE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (ehci_periodic_enabled(s)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr,
|
|
|
|
"ehci: PERIODIC list base register set while periodic schedule\n"
|
|
|
|
" is enabled and HC is enabled\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ASYNCLISTADDR:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (ehci_async_enabled(s)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr,
|
|
|
|
"ehci: ASYNC list address register set while async schedule\n"
|
|
|
|
" is enabled and HC is enabled\n");
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-19 10:55:09 +04:00
|
|
|
*mmio = val;
|
|
|
|
trace_usb_ehci_mmio_change(addr, addr2str(addr), *mmio, old);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TODO : Put in common header file, duplication from usb-ohci.c
|
|
|
|
|
|
|
|
/* Get an array of dwords from main memory */
|
2011-10-31 10:06:57 +04:00
|
|
|
static inline int get_dwords(EHCIState *ehci, uint32_t addr,
|
|
|
|
uint32_t *buf, int num)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
2011-11-04 05:03:37 +04:00
|
|
|
pci_dma_read(&ehci->dev, addr, buf, sizeof(*buf));
|
2010-12-03 18:17:28 +03:00
|
|
|
*buf = le32_to_cpu(*buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Put an array of dwords in to main memory */
|
2011-10-31 10:06:57 +04:00
|
|
|
static inline int put_dwords(EHCIState *ehci, uint32_t addr,
|
|
|
|
uint32_t *buf, int num)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for(i = 0; i < num; i++, buf++, addr += sizeof(*buf)) {
|
|
|
|
uint32_t tmp = cpu_to_le32(*buf);
|
2011-11-04 05:03:37 +04:00
|
|
|
pci_dma_write(&ehci->dev, addr, &tmp, sizeof(tmp));
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-06-19 18:23:32 +04:00
|
|
|
/*
|
|
|
|
* Write the qh back to guest physical memory. This step isn't
|
|
|
|
* in the EHCI spec but we need to do it since we don't share
|
|
|
|
* physical memory with our guest VM.
|
|
|
|
*
|
|
|
|
* The first three dwords are read-only for the EHCI, so skip them
|
|
|
|
* when writing back the qh.
|
|
|
|
*/
|
|
|
|
static void ehci_flush_qh(EHCIQueue *q)
|
|
|
|
{
|
|
|
|
uint32_t *qh = (uint32_t *) &q->qh;
|
|
|
|
uint32_t dwords = sizeof(EHCIqh) >> 2;
|
|
|
|
uint32_t addr = NLPTR_GET(q->qhaddr);
|
|
|
|
|
|
|
|
put_dwords(q->ehci, addr + 3 * sizeof(uint32_t), qh + 3, dwords - 3);
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
// 4.10.2
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static int ehci_qh_do_overlay(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
|
|
|
int dtoggle;
|
|
|
|
int ping;
|
|
|
|
int eps;
|
|
|
|
int reload;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
// remember values in fields to preserve in qh after overlay
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
dtoggle = q->qh.token & QTD_TOKEN_DTOGGLE;
|
|
|
|
ping = q->qh.token & QTD_TOKEN_PING;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
q->qh.current_qtd = p->qtdaddr;
|
|
|
|
q->qh.next_qtd = p->qtd.next;
|
|
|
|
q->qh.altnext_qtd = p->qtd.altnext;
|
|
|
|
q->qh.token = p->qtd.token;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
eps = get_field(q->qh.epchar, QH_EPCHAR_EPS);
|
2010-12-03 18:17:28 +03:00
|
|
|
if (eps == EHCI_QH_EPS_HIGH) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token &= ~QTD_TOKEN_PING;
|
|
|
|
q->qh.token |= ping;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
reload = get_field(q->qh.epchar, QH_EPCHAR_RL);
|
|
|
|
set_field(&q->qh.altnext_qtd, reload, QH_ALTNEXT_NAKCNT);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
for (i = 0; i < 5; i++) {
|
2012-05-09 19:06:36 +04:00
|
|
|
q->qh.bufptr[i] = p->qtd.bufptr[i];
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (!(q->qh.epchar & QH_EPCHAR_DTC)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
// preserve QH DT bit
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token &= ~QTD_TOKEN_DTOGGLE;
|
|
|
|
q->qh.token |= dtoggle;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.bufptr[1] &= ~BUFPTR_CPROGMASK_MASK;
|
|
|
|
q->qh.bufptr[2] &= ~BUFPTR_FRAMETAG_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-06-19 18:23:32 +04:00
|
|
|
ehci_flush_qh(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
static int ehci_init_transfer(EHCIPacket *p)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-07-13 19:36:46 +04:00
|
|
|
uint32_t cpage, offset, bytes, plen;
|
2011-10-31 10:06:57 +04:00
|
|
|
dma_addr_t page;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
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);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
while (bytes > 0) {
|
|
|
|
if (cpage > 4) {
|
|
|
|
fprintf(stderr, "cpage out of range (%d)\n", cpage);
|
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
page = p->qtd.bufptr[cpage] & QTD_BUFPTR_MASK;
|
2011-07-13 19:36:46 +04:00
|
|
|
page += offset;
|
|
|
|
plen = bytes;
|
|
|
|
if (plen > 4096 - offset) {
|
|
|
|
plen = 4096 - offset;
|
|
|
|
offset = 0;
|
|
|
|
cpage++;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
qemu_sglist_add(&p->sgl, page, plen);
|
2011-07-13 19:36:46 +04:00
|
|
|
bytes -= plen;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
static void ehci_finish_transfer(EHCIQueue *q, int status)
|
|
|
|
{
|
|
|
|
uint32_t cpage, offset;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
if (status > 0) {
|
|
|
|
/* update cpage & offset */
|
|
|
|
cpage = get_field(q->qh.token, QTD_TOKEN_CPAGE);
|
|
|
|
offset = q->qh.bufptr[0] & ~QTD_BUFPTR_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
offset += status;
|
|
|
|
cpage += offset >> QTD_BUFPTR_SH;
|
|
|
|
offset &= ~QTD_BUFPTR_MASK;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
set_field(&q->qh.token, cpage, QTD_TOKEN_CPAGE);
|
|
|
|
q->qh.bufptr[0] &= QTD_BUFPTR_MASK;
|
|
|
|
q->qh.bufptr[0] |= offset;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-06-21 13:52:28 +04:00
|
|
|
static void ehci_async_complete_packet(USBPort *port, USBPacket *packet)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p;
|
2011-06-24 18:18:13 +04:00
|
|
|
EHCIState *s = port->opaque;
|
|
|
|
uint32_t portsc = s->portsc[port->index];
|
|
|
|
|
|
|
|
if (portsc & PORTSC_POWNER) {
|
|
|
|
USBPort *companion = s->companion_ports[port->index];
|
|
|
|
companion->ops->complete(companion, packet);
|
|
|
|
return;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
p = container_of(packet, EHCIPacket, packet);
|
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "wakeup");
|
|
|
|
assert(p->async == EHCI_ASYNC_INFLIGHT);
|
|
|
|
p->async = EHCI_ASYNC_FINISHED;
|
|
|
|
p->usb_status = packet->result;
|
2012-05-11 13:31:56 +04:00
|
|
|
|
|
|
|
if (p->queue->async) {
|
|
|
|
qemu_bh_schedule(p->queue->ehci->async_bh);
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static void ehci_execute_complete(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
|
|
|
|
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
2012-09-03 13:01:13 +04:00
|
|
|
assert(p->async == EHCI_ASYNC_INITIALIZED ||
|
|
|
|
p->async == EHCI_ASYNC_FINISHED);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
DPRINTF("execute_complete: qhaddr 0x%x, next %x, qtdaddr 0x%x, status %d\n",
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qhaddr, q->qh.next, q->qtdaddr, q->usb_status);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->usb_status < 0) {
|
|
|
|
switch (p->usb_status) {
|
2012-03-03 00:27:20 +04:00
|
|
|
case USB_RET_IOERROR:
|
2010-12-03 18:17:28 +03:00
|
|
|
case USB_RET_NODEV:
|
2011-05-23 11:50:27 +04:00
|
|
|
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_XACTERR);
|
2012-03-03 00:27:15 +04:00
|
|
|
set_field(&q->qh.token, 0, QTD_TOKEN_CERR);
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
case USB_RET_STALL:
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token |= QTD_TOKEN_HALT;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
case USB_RET_NAK:
|
usb-ehci: Fix and simplify nakcnt handling
The nakcnt code in ehci_execute_complete() marked transactions as finished
when a packet completed with a result of USB_RET_NAK, but USB_RET_NAK
means that the device cannot receive / send data at that time and that
the transaction should be retried later, which is also what the usb-uhci
and usb-ohci code does.
Note that there already was some special code in place to handle this
for interrupt endpoints in the form of doing a return from
ehci_execute_complete() when reload == 0, but that for bulk transactions
this was not handled correctly (where as for example the usb-ccid device does
return USB_RET_NAK for bulk packets).
Besides that the code in ehci_execute_complete() decrement nakcnt by 1
on a packet result of USB_RET_NAK, but
-since the transaction got marked as finished,
nakcnt would never be decremented again
-there is no code checking for nakcnt becoming 0
-there is no use in re-trying the transaction within the same usb frame /
usb-ehci frame-timer call, since the status of emulated devices won't change
as long as the usb-ehci frame-timer is running
So we should simply set the nakcnt to 0 when we get a USB_RET_NAK, thus
claiming that we've tried reload times (or as many times as possible if
reload is 0).
Besides the code in ehci_execute_complete() handling USB_RET_NAK there
was also code handling it in ehci_state_executing(), which calls
ehci_execute_complete(), and then does its own handling on top of the handling
in ehci_execute_complete(), this code would decrement nakcnt *again* (if not
already 0), or restore the reload value (which was never changed) on success.
Since the double decrement was wrong to begin with, and is no longer needed
now that we set nakcnt directly to 0 on USB_RET_NAK, and the restore of reload
is not needed either, this patch simply removes all nakcnt handling from
ehci_state_executing().
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:17 +04:00
|
|
|
set_field(&q->qh.altnext_qtd, 0, QH_ALTNEXT_NAKCNT);
|
|
|
|
return; /* We're not done yet with this transaction */
|
2010-12-03 18:17:28 +03:00
|
|
|
case USB_RET_BABBLE:
|
2011-05-23 11:50:27 +04:00
|
|
|
q->qh.token |= (QTD_TOKEN_HALT | QTD_TOKEN_BABBLE);
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(q->ehci, USBSTS_ERRINT);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
default:
|
2011-05-19 12:49:03 +04:00
|
|
|
/* should not be triggerable */
|
2012-05-09 19:06:36 +04:00
|
|
|
fprintf(stderr, "USB invalid response %d\n", p->usb_status);
|
2011-05-19 12:49:03 +04:00
|
|
|
assert(0);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// TODO check 4.12 for splits
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->tbytes && p->pid == USB_TOKEN_IN) {
|
|
|
|
p->tbytes -= p->usb_status;
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
2012-05-09 19:06:36 +04:00
|
|
|
p->tbytes = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
DPRINTF("updating tbytes to %d\n", p->tbytes);
|
|
|
|
set_field(&q->qh.token, p->tbytes, QTD_TOKEN_TBYTES);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
ehci_finish_transfer(q, p->usb_status);
|
2012-06-27 08:50:42 +04:00
|
|
|
usb_packet_unmap(&p->packet, &p->sgl);
|
2012-05-09 19:06:36 +04:00
|
|
|
qemu_sglist_destroy(&p->sgl);
|
2012-09-03 13:01:13 +04:00
|
|
|
p->async = EHCI_ASYNC_NONE;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qh.token ^= QTD_TOKEN_DTOGGLE;
|
|
|
|
q->qh.token &= ~QTD_TOKEN_ACTIVE;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
usb-ehci: Fix and simplify nakcnt handling
The nakcnt code in ehci_execute_complete() marked transactions as finished
when a packet completed with a result of USB_RET_NAK, but USB_RET_NAK
means that the device cannot receive / send data at that time and that
the transaction should be retried later, which is also what the usb-uhci
and usb-ohci code does.
Note that there already was some special code in place to handle this
for interrupt endpoints in the form of doing a return from
ehci_execute_complete() when reload == 0, but that for bulk transactions
this was not handled correctly (where as for example the usb-ccid device does
return USB_RET_NAK for bulk packets).
Besides that the code in ehci_execute_complete() decrement nakcnt by 1
on a packet result of USB_RET_NAK, but
-since the transaction got marked as finished,
nakcnt would never be decremented again
-there is no code checking for nakcnt becoming 0
-there is no use in re-trying the transaction within the same usb frame /
usb-ehci frame-timer call, since the status of emulated devices won't change
as long as the usb-ehci frame-timer is running
So we should simply set the nakcnt to 0 when we get a USB_RET_NAK, thus
claiming that we've tried reload times (or as many times as possible if
reload is 0).
Besides the code in ehci_execute_complete() handling USB_RET_NAK there
was also code handling it in ehci_state_executing(), which calls
ehci_execute_complete(), and then does its own handling on top of the handling
in ehci_execute_complete(), this code would decrement nakcnt *again* (if not
already 0), or restore the reload value (which was never changed) on success.
Since the double decrement was wrong to begin with, and is no longer needed
now that we set nakcnt directly to 0 on USB_RET_NAK, and the restore of reload
is not needed either, this patch simply removes all nakcnt handling from
ehci_state_executing().
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:17 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_IOC) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(q->ehci, USBSTS_INT);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 4.10.3
|
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
static int ehci_execute(EHCIPacket *p, const char *action)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2010-12-03 18:17:28 +03:00
|
|
|
int ret;
|
|
|
|
int endp;
|
|
|
|
|
2012-09-03 13:01:13 +04:00
|
|
|
assert(p->async == EHCI_ASYNC_NONE ||
|
|
|
|
p->async == EHCI_ASYNC_INITIALIZED);
|
|
|
|
|
2012-05-10 14:10:47 +04:00
|
|
|
if (!(p->qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
fprintf(stderr, "Attempting to execute inactive qtd\n");
|
2010-12-03 18:17:28 +03:00
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
|
|
|
|
2012-05-10 14:10:47 +04:00
|
|
|
p->tbytes = (p->qtd.token & QTD_TOKEN_TBYTES_MASK) >> QTD_TOKEN_TBYTES_SH;
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->tbytes > BUFF_SIZE) {
|
2012-09-03 12:22:16 +04:00
|
|
|
ehci_trace_guest_bug(p->queue->ehci,
|
|
|
|
"guest requested more bytes than allowed");
|
2010-12-03 18:17:28 +03:00
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
|
|
|
|
2012-05-10 14:10:47 +04:00
|
|
|
p->pid = (p->qtd.token & QTD_TOKEN_PID_MASK) >> QTD_TOKEN_PID_SH;
|
2012-05-09 19:06:36 +04:00
|
|
|
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;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-10 14:10:47 +04:00
|
|
|
endp = get_field(p->queue->qh.epchar, QH_EPCHAR_EP);
|
2012-05-10 14:18:45 +04:00
|
|
|
ep = usb_ep_get(p->queue->dev, p->pid, endp);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-09-03 13:01:13 +04:00
|
|
|
if (p->async == EHCI_ASYNC_NONE) {
|
|
|
|
if (ehci_init_transfer(p) != 0) {
|
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
usb_packet_setup(&p->packet, p->pid, ep, p->qtdaddr);
|
|
|
|
usb_packet_map(&p->packet, &p->sgl);
|
|
|
|
p->async = EHCI_ASYNC_INITIALIZED;
|
|
|
|
}
|
2011-07-13 19:36:46 +04:00
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, action);
|
2012-05-10 14:18:45 +04:00
|
|
|
ret = usb_handle_packet(p->queue->dev, &p->packet);
|
2012-01-10 20:46:15 +04:00
|
|
|
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);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
if (ret > BUFF_SIZE) {
|
|
|
|
fprintf(stderr, "ret from usb_handle_packet > BUFF_SIZE\n");
|
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 4.7.2
|
|
|
|
*/
|
|
|
|
|
|
|
|
static int ehci_process_itd(EHCIState *ehci,
|
2012-08-23 15:30:13 +04:00
|
|
|
EHCIitd *itd,
|
|
|
|
uint32_t addr)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
USBDevice *dev;
|
2012-01-12 16:23:01 +04:00
|
|
|
USBEndpoint *ep;
|
2010-12-03 18:17:28 +03:00
|
|
|
int ret;
|
2012-01-10 20:46:15 +04:00
|
|
|
uint32_t i, len, pid, dir, devaddr, endp;
|
2011-05-30 18:09:08 +04:00
|
|
|
uint32_t pg, off, ptr1, ptr2, max, mult;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
dir =(itd->bufptr[1] & ITD_BUFPTR_DIRECTION);
|
2011-05-30 18:09:08 +04:00
|
|
|
devaddr = get_field(itd->bufptr[0], ITD_BUFPTR_DEVADDR);
|
2010-12-03 18:17:28 +03:00
|
|
|
endp = get_field(itd->bufptr[0], ITD_BUFPTR_EP);
|
2011-05-30 18:09:08 +04:00
|
|
|
max = get_field(itd->bufptr[1], ITD_BUFPTR_MAXPKT);
|
|
|
|
mult = get_field(itd->bufptr[2], ITD_BUFPTR_MULT);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
for(i = 0; i < 8; i++) {
|
|
|
|
if (itd->transact[i] & ITD_XACT_ACTIVE) {
|
2011-05-30 18:09:08 +04:00
|
|
|
pg = get_field(itd->transact[i], ITD_XACT_PGSEL);
|
|
|
|
off = itd->transact[i] & ITD_XACT_OFFSET_MASK;
|
|
|
|
ptr1 = (itd->bufptr[pg] & ITD_BUFPTR_MASK);
|
|
|
|
ptr2 = (itd->bufptr[pg+1] & ITD_BUFPTR_MASK);
|
|
|
|
len = get_field(itd->transact[i], ITD_XACT_LENGTH);
|
|
|
|
|
|
|
|
if (len > max * mult) {
|
|
|
|
len = max * mult;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
if (len > BUFF_SIZE) {
|
|
|
|
return USB_RET_PROCERR;
|
|
|
|
}
|
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
pci_dma_sglist_init(&ehci->isgl, &ehci->dev, 2);
|
2011-05-30 18:09:08 +04:00
|
|
|
if (off + len > 4096) {
|
|
|
|
/* transfer crosses page border */
|
2011-07-13 19:36:46 +04:00
|
|
|
uint32_t len2 = off + len - 4096;
|
|
|
|
uint32_t len1 = len - len2;
|
|
|
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len1);
|
|
|
|
qemu_sglist_add(&ehci->isgl, ptr2, len2);
|
2011-05-30 18:09:08 +04:00
|
|
|
} else {
|
2011-07-13 19:36:46 +04:00
|
|
|
qemu_sglist_add(&ehci->isgl, ptr1 + off, len);
|
2011-05-30 18:09:08 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-07-13 19:36:46 +04:00
|
|
|
pid = dir ? USB_TOKEN_IN : USB_TOKEN_OUT;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-01-12 16:23:01 +04:00
|
|
|
dev = ehci_find_device(ehci, devaddr);
|
|
|
|
ep = usb_ep_get(dev, pid, endp);
|
2012-08-28 13:50:26 +04:00
|
|
|
if (ep && ep->type == USB_ENDPOINT_XFER_ISOC) {
|
2012-08-23 15:30:13 +04:00
|
|
|
usb_packet_setup(&ehci->ipacket, pid, ep, addr);
|
2012-02-27 14:23:08 +04:00
|
|
|
usb_packet_map(&ehci->ipacket, &ehci->isgl);
|
|
|
|
ret = usb_handle_packet(dev, &ehci->ipacket);
|
|
|
|
assert(ret != USB_RET_ASYNC);
|
2012-06-27 08:50:42 +04:00
|
|
|
usb_packet_unmap(&ehci->ipacket, &ehci->isgl);
|
2012-02-27 14:23:08 +04:00
|
|
|
} else {
|
|
|
|
DPRINTF("ISOCH: attempt to addess non-iso endpoint\n");
|
|
|
|
ret = USB_RET_NAK;
|
|
|
|
}
|
2011-07-13 19:36:46 +04:00
|
|
|
qemu_sglist_destroy(&ehci->isgl);
|
|
|
|
|
2012-03-03 00:27:18 +04:00
|
|
|
if (ret < 0) {
|
2012-02-26 19:14:48 +04:00
|
|
|
switch (ret) {
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Unexpected iso usb result: %d\n", ret);
|
|
|
|
/* Fall through */
|
2012-03-03 00:27:20 +04:00
|
|
|
case USB_RET_IOERROR:
|
2012-02-26 19:14:48 +04:00
|
|
|
case USB_RET_NODEV:
|
|
|
|
/* 3.3.2: XACTERR is only allowed on IN transactions */
|
|
|
|
if (dir) {
|
|
|
|
itd->transact[i] |= ITD_XACT_XACTERR;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_ERRINT);
|
2012-02-26 19:14:48 +04:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
case USB_RET_BABBLE:
|
|
|
|
itd->transact[i] |= ITD_XACT_BABBLE;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_ERRINT);
|
2012-02-26 19:14:48 +04:00
|
|
|
break;
|
2012-03-03 00:27:18 +04:00
|
|
|
case USB_RET_NAK:
|
|
|
|
/* no data for us, so do a zero-length transfer */
|
|
|
|
ret = 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);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
2012-02-26 19:14:48 +04:00
|
|
|
if (itd->transact[i] & ITD_XACT_IOC) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_INT);
|
2012-02-26 19:14:48 +04:00
|
|
|
}
|
2011-05-30 18:09:08 +04:00
|
|
|
itd->transact[i] &= ~ITD_XACT_ACTIVE;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-05-10 16:13:41 +04:00
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* This state is the entry point for asynchronous schedule
|
|
|
|
* processing. Entry here consitutes a EHCI start event state (4.8.5)
|
|
|
|
*/
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_waitlisthead(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIqh qh;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i = 0;
|
|
|
|
int again = 0;
|
|
|
|
uint32_t entry = ehci->asynclistaddr;
|
|
|
|
|
|
|
|
/* set reclamation flag at start event (4.8.6) */
|
|
|
|
if (async) {
|
2011-05-18 12:12:58 +04:00
|
|
|
ehci_set_usbsts(ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-08-29 12:12:52 +04:00
|
|
|
ehci_queues_rip_unused(ehci, async, 0);
|
2011-05-19 19:56:19 +04:00
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Find the head of the list (4.9.1.1) */
|
|
|
|
for(i = 0; i < MAX_QH; i++) {
|
2011-10-31 10:06:57 +04:00
|
|
|
get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &qh,
|
|
|
|
sizeof(EHCIqh) >> 2);
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_trace_qh(NULL, NLPTR_GET(entry), &qh);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (qh.epchar & QH_EPCHAR_H) {
|
2010-12-03 18:17:28 +03:00
|
|
|
if (async) {
|
|
|
|
entry |= (NLPTR_TYPE_QH << 1);
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async, entry);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
entry = qh.next;
|
2010-12-03 18:17:28 +03:00
|
|
|
if (entry == ehci->asynclistaddr) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* no head found for list. */
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* This state is the entry point for periodic schedule processing as
|
|
|
|
* well as being a continuation state for async processing.
|
|
|
|
*/
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_fetchentry(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int again = 0;
|
2011-05-19 12:49:03 +04:00
|
|
|
uint32_t entry = ehci_get_fetch_addr(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-03-03 00:27:09 +04:00
|
|
|
if (NLPTR_TBIT(entry)) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* section 4.8, only QH in async schedule */
|
|
|
|
if (async && (NLPTR_TYPE_GET(entry) != NLPTR_TYPE_QH)) {
|
|
|
|
fprintf(stderr, "non queue head request in async schedule\n");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (NLPTR_TYPE_GET(entry)) {
|
|
|
|
case NLPTR_TYPE_QH:
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case NLPTR_TYPE_ITD:
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHITD);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
case NLPTR_TYPE_STITD:
|
|
|
|
ehci_set_state(ehci, async, EST_FETCHSITD);
|
|
|
|
again = 1;
|
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
default:
|
2011-08-26 16:13:48 +04:00
|
|
|
/* TODO: handle FSTN type */
|
2010-12-03 18:17:28 +03:00
|
|
|
fprintf(stderr, "FETCHENTRY: entry at %X is of type %d "
|
|
|
|
"which is not supported yet\n", entry, NLPTR_TYPE_GET(entry));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p;
|
2012-08-29 12:37:37 +04:00
|
|
|
uint32_t entry, devaddr, endp;
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIQueue *q;
|
2012-08-29 12:37:37 +04:00
|
|
|
EHCIqh qh;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
2012-03-03 00:27:10 +04:00
|
|
|
q = ehci_find_queue_by_qh(ehci, entry, async);
|
2011-05-19 19:56:19 +04:00
|
|
|
if (NULL == q) {
|
2012-05-11 10:56:49 +04:00
|
|
|
q = ehci_alloc_queue(ehci, entry, async);
|
2011-05-19 19:56:19 +04:00
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
p = QTAILQ_FIRST(&q->packets);
|
2011-05-19 19:56:19 +04:00
|
|
|
|
2012-05-11 10:56:49 +04:00
|
|
|
q->seen++;
|
2011-05-19 19:56:19 +04:00
|
|
|
if (q->seen > 1) {
|
|
|
|
/* we are going in circles -- stop processing */
|
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
|
|
|
q = NULL;
|
|
|
|
goto out;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
get_dwords(ehci, NLPTR_GET(q->qhaddr),
|
2012-08-29 12:37:37 +04:00
|
|
|
(uint32_t *) &qh, sizeof(EHCIqh) >> 2);
|
|
|
|
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.
|
|
|
|
*/
|
|
|
|
devaddr = get_field(qh.epchar, QH_EPCHAR_DEVADDR);
|
|
|
|
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)) ||
|
|
|
|
(memcmp(&qh.current_qtd, &q->qh.current_qtd,
|
|
|
|
9 * sizeof(uint32_t)) != 0) ||
|
|
|
|
(q->dev != NULL && q->dev->addr != devaddr)) {
|
2012-08-31 12:44:21 +04:00
|
|
|
if (ehci_reset_queue(q) > 0) {
|
|
|
|
ehci_trace_guest_bug(ehci, "guest updated active QH");
|
|
|
|
}
|
2012-08-29 12:37:37 +04:00
|
|
|
p = NULL;
|
|
|
|
}
|
|
|
|
q->qh = qh;
|
|
|
|
|
2012-05-10 14:18:45 +04:00
|
|
|
if (q->dev == NULL) {
|
|
|
|
q->dev = ehci_find_device(q->ehci, devaddr);
|
|
|
|
}
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p && p->async == EHCI_ASYNC_FINISHED) {
|
2011-05-19 19:56:19 +04:00
|
|
|
/* I/O finished -- continue processing queue */
|
2012-05-10 16:12:38 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "complete");
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_set_state(ehci, async, EST_EXECUTING);
|
|
|
|
goto out;
|
|
|
|
}
|
2011-05-19 12:49:03 +04:00
|
|
|
|
|
|
|
if (async && (q->qh.epchar & QH_EPCHAR_H)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/* EHCI spec version 1.0 Section 4.8.3 & 4.10.1 */
|
|
|
|
if (ehci->usbsts & USBSTS_REC) {
|
2011-05-18 12:12:58 +04:00
|
|
|
ehci_clear_usbsts(ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
|
|
|
DPRINTF("FETCHQH: QH 0x%08x. H-bit set, reclamation status reset"
|
2011-05-19 12:49:03 +04:00
|
|
|
" - done processing\n", q->qhaddr);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2011-05-19 12:49:03 +04:00
|
|
|
q = NULL;
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#if EHCI_DEBUG
|
2011-05-19 12:49:03 +04:00
|
|
|
if (q->qhaddr != q->qh.next) {
|
2010-12-03 18:17:28 +03:00
|
|
|
DPRINTF("FETCHQH: QH 0x%08x (h %x halt %x active %x) next 0x%08x\n",
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qhaddr,
|
|
|
|
q->qh.epchar & QH_EPCHAR_H,
|
|
|
|
q->qh.token & QTD_TOKEN_HALT,
|
|
|
|
q->qh.token & QTD_TOKEN_ACTIVE,
|
|
|
|
q->qh.next);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_HALT) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-03-03 00:27:09 +04:00
|
|
|
} else if ((q->qh.token & QTD_TOKEN_ACTIVE) &&
|
|
|
|
(NLPTR_TBIT(q->qh.current_qtd) == 0)) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qtdaddr = q->qh.current_qtd;
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
} else {
|
|
|
|
/* EHCI spec version 1.0 Section 4.10.2 */
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ADVANCEQUEUE);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
out:
|
2011-05-19 12:49:03 +04:00
|
|
|
return q;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
static int ehci_state_fetchitd(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
uint32_t entry;
|
2010-12-03 18:17:28 +03:00
|
|
|
EHCIitd itd;
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
assert(!async);
|
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
get_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
|
2010-12-03 18:17:28 +03:00
|
|
|
sizeof(EHCIitd) >> 2);
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_trace_itd(ehci, entry, &itd);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-08-23 15:30:13 +04:00
|
|
|
if (ehci_process_itd(ehci, &itd, entry) != 0) {
|
2010-12-03 18:17:28 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
put_dwords(ehci, NLPTR_GET(entry), (uint32_t *) &itd,
|
|
|
|
sizeof(EHCIitd) >> 2);
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async, itd.next);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
static int ehci_state_fetchsitd(EHCIState *ehci, int async)
|
|
|
|
{
|
|
|
|
uint32_t entry;
|
|
|
|
EHCIsitd sitd;
|
|
|
|
|
|
|
|
assert(!async);
|
|
|
|
entry = ehci_get_fetch_addr(ehci, async);
|
|
|
|
|
2011-10-31 10:06:57 +04:00
|
|
|
get_dwords(ehci, NLPTR_GET(entry), (uint32_t *)&sitd,
|
2011-08-26 16:13:48 +04:00
|
|
|
sizeof(EHCIsitd) >> 2);
|
|
|
|
ehci_trace_sitd(ehci, entry, &sitd);
|
|
|
|
|
|
|
|
if (!(sitd.results & SITD_RESULTS_ACTIVE)) {
|
|
|
|
/* siTD is not active, nothing to do */;
|
|
|
|
} else {
|
|
|
|
/* TODO: split transfers are not implemented */
|
|
|
|
fprintf(stderr, "WARNING: Skipping active siTD\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
ehci_set_fetch_addr(ehci, async, sitd.next);
|
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
/* Section 4.10.2 - paragraph 3 */
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_advqueue(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
#if 0
|
|
|
|
/* TO-DO: 4.10.2 - paragraph 2
|
|
|
|
* if I-bit is set to 1 and QH is not active
|
|
|
|
* go to horizontal QH
|
|
|
|
*/
|
|
|
|
if (I-bit set) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/*
|
|
|
|
* want data and alt-next qTD is valid
|
|
|
|
*/
|
2011-05-19 12:49:03 +04:00
|
|
|
if (((q->qh.token & QTD_TOKEN_TBYTES_MASK) != 0) &&
|
|
|
|
(NLPTR_TBIT(q->qh.altnext_qtd) == 0)) {
|
|
|
|
q->qtdaddr = q->qh.altnext_qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* next qTD is valid
|
|
|
|
*/
|
2012-03-03 00:27:09 +04:00
|
|
|
} else if (NLPTR_TBIT(q->qh.next_qtd) == 0) {
|
2011-05-19 12:49:03 +04:00
|
|
|
q->qtdaddr = q->qh.next_qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHQTD);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* no valid qTD, try next QH
|
|
|
|
*/
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Section 4.10.2 - paragraph 4 */
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_fetchqtd(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIqtd qtd;
|
|
|
|
EHCIPacket *p;
|
2010-12-03 18:17:28 +03:00
|
|
|
int again = 0;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
get_dwords(q->ehci, NLPTR_GET(q->qtdaddr), (uint32_t *) &qtd,
|
2011-10-31 10:06:57 +04:00
|
|
|
sizeof(EHCIqtd) >> 2);
|
2012-05-09 19:06:36 +04:00
|
|
|
ehci_trace_qtd(q, NLPTR_GET(q->qtdaddr), &qtd);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
p = QTAILQ_FIRST(&q->packets);
|
|
|
|
if (p != NULL) {
|
2012-08-21 16:03:09 +04:00
|
|
|
if (p->qtdaddr != q->qtdaddr ||
|
|
|
|
(!NLPTR_TBIT(p->qtd.next) && (p->qtd.next != qtd.next)) ||
|
|
|
|
(!NLPTR_TBIT(p->qtd.altnext) && (p->qtd.altnext != qtd.altnext)) ||
|
|
|
|
p->qtd.bufptr[0] != qtd.bufptr[0]) {
|
|
|
|
ehci_cancel_queue(q);
|
2012-08-31 12:44:21 +04:00
|
|
|
ehci_trace_guest_bug(q->ehci, "guest updated active QH or qTD");
|
2012-08-21 16:03:09 +04:00
|
|
|
p = NULL;
|
|
|
|
} else {
|
|
|
|
p->qtd = qtd;
|
|
|
|
ehci_qh_do_overlay(q);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
if (p != NULL) {
|
|
|
|
/* transfer canceled by guest (clear active) */
|
|
|
|
ehci_cancel_queue(q);
|
|
|
|
p = NULL;
|
|
|
|
}
|
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
|
|
|
again = 1;
|
|
|
|
} else if (p != NULL) {
|
2012-08-30 13:20:51 +04:00
|
|
|
switch (p->async) {
|
|
|
|
case EHCI_ASYNC_NONE:
|
2012-09-03 13:01:13 +04:00
|
|
|
/* Should never happen packet should at least be initialized */
|
|
|
|
assert(0);
|
|
|
|
break;
|
|
|
|
case EHCI_ASYNC_INITIALIZED:
|
2012-08-30 13:20:51 +04:00
|
|
|
/* Previously nacked packet (likely interrupt ep) */
|
2012-09-03 13:01:13 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
|
|
|
|
break;
|
2012-08-30 13:20:51 +04:00
|
|
|
case EHCI_ASYNC_INFLIGHT:
|
2012-09-03 13:01:13 +04:00
|
|
|
/* Unfinished async handled packet, go horizontal */
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2012-08-30 13:20:51 +04:00
|
|
|
break;
|
|
|
|
case EHCI_ASYNC_FINISHED:
|
2012-09-03 14:17:48 +04:00
|
|
|
/*
|
|
|
|
* We get here when advqueue moves to a packet which is already
|
|
|
|
* finished, which can happen with packets queued up by fill_queue
|
|
|
|
*/
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
|
2012-08-30 13:20:51 +04:00
|
|
|
break;
|
2012-05-10 16:12:38 +04:00
|
|
|
}
|
|
|
|
again = 1;
|
2012-08-21 16:03:09 +04:00
|
|
|
} else {
|
2012-05-09 19:06:36 +04:00
|
|
|
p = ehci_alloc_packet(q);
|
|
|
|
p->qtdaddr = q->qtdaddr;
|
|
|
|
p->qtd = qtd;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTE);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_horizqh(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
|
|
|
int again = 0;
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
if (ehci_get_fetch_addr(q->ehci, q->async) != q->qh.next) {
|
|
|
|
ehci_set_fetch_addr(q->ehci, q->async, q->qh.next);
|
|
|
|
ehci_set_state(q->ehci, q->async, EST_FETCHENTRY);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-09-03 13:35:58 +04:00
|
|
|
static int ehci_fill_queue(EHCIPacket *p)
|
2012-05-10 16:12:38 +04:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
ehci_trace_qtd(q, NLPTR_GET(qtdaddr), &qtd);
|
|
|
|
if (!(qtd.token & QTD_TOKEN_ACTIVE)) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
p = ehci_alloc_packet(q);
|
|
|
|
p->qtdaddr = qtdaddr;
|
|
|
|
p->qtd = qtd;
|
|
|
|
p->usb_status = ehci_execute(p, "queue");
|
2012-09-03 13:35:58 +04:00
|
|
|
if (p->usb_status == USB_RET_PROCERR) {
|
|
|
|
break;
|
|
|
|
}
|
2012-08-14 18:13:02 +04:00
|
|
|
assert(p->usb_status == USB_RET_ASYNC);
|
2012-05-10 16:12:38 +04:00
|
|
|
p->async = EHCI_ASYNC_INFLIGHT;
|
|
|
|
}
|
2012-09-03 13:35:58 +04:00
|
|
|
return p->usb_status;
|
2012-05-10 16:12:38 +04:00
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_execute(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
int again = 0;
|
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
if (ehci_qh_do_overlay(q) != 0) {
|
2010-12-03 18:17:28 +03:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
if (!q->async) {
|
2011-05-19 12:49:03 +04:00
|
|
|
int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
|
2010-12-03 18:17:28 +03:00
|
|
|
if (!transactCtr) {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
|
|
|
goto out;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
if (q->async) {
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_usbsts(q->ehci, USBSTS_REC);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-10 16:12:38 +04:00
|
|
|
p->usb_status = ehci_execute(p, "process");
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->usb_status == USB_RET_PROCERR) {
|
2010-12-03 18:17:28 +03:00
|
|
|
again = -1;
|
|
|
|
goto out;
|
|
|
|
}
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->usb_status == USB_RET_ASYNC) {
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_flush_qh(q);
|
2012-05-10 16:12:38 +04:00
|
|
|
trace_usb_ehci_packet_action(p->queue, p, "async");
|
2012-05-09 19:06:36 +04:00
|
|
|
p->async = EHCI_ASYNC_INFLIGHT;
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2012-09-03 13:35:58 +04:00
|
|
|
again = (ehci_fill_queue(p) == USB_RET_PROCERR) ? -1 : 1;
|
2011-05-19 19:56:19 +04:00
|
|
|
goto out;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_EXECUTING);
|
2011-05-19 19:56:19 +04:00
|
|
|
again = 1;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
out:
|
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_executing(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_execute_complete(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
// 4.10.3
|
2012-05-11 11:05:15 +04:00
|
|
|
if (!q->async) {
|
2011-05-19 12:49:03 +04:00
|
|
|
int transactCtr = get_field(q->qh.epcap, QH_EPCAP_MULT);
|
2010-12-03 18:17:28 +03:00
|
|
|
transactCtr--;
|
2011-05-19 12:49:03 +04:00
|
|
|
set_field(&q->qh.epcap, transactCtr, QH_EPCAP_MULT);
|
2010-12-03 18:17:28 +03:00
|
|
|
// 4.10.3, bottom of page 82, should exit this state when transaction
|
|
|
|
// counter decrements to 0
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 4.10.5 */
|
2012-05-09 19:06:36 +04:00
|
|
|
if (p->usb_status == USB_RET_NAK) {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2010-12-03 18:17:28 +03:00
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_WRITEBACK);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2011-05-19 19:56:19 +04:00
|
|
|
ehci_flush_qh(q);
|
2012-08-17 13:39:17 +04:00
|
|
|
return 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static int ehci_state_writeback(EHCIQueue *q)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2012-05-09 19:06:36 +04:00
|
|
|
EHCIPacket *p = QTAILQ_FIRST(&q->packets);
|
2012-06-19 15:53:28 +04:00
|
|
|
uint32_t *qtd, addr;
|
2010-12-03 18:17:28 +03:00
|
|
|
int again = 0;
|
|
|
|
|
|
|
|
/* Write back the QTD from the QH area */
|
2012-05-09 19:06:36 +04:00
|
|
|
assert(p != NULL);
|
|
|
|
assert(p->qtdaddr == q->qtdaddr);
|
|
|
|
|
|
|
|
ehci_trace_qtd(q, NLPTR_GET(p->qtdaddr), (EHCIqtd *) &q->qh.next_qtd);
|
2012-06-19 15:53:28 +04:00
|
|
|
qtd = (uint32_t *) &q->qh.next_qtd;
|
|
|
|
addr = NLPTR_GET(p->qtdaddr);
|
|
|
|
put_dwords(q->ehci, addr + 2 * sizeof(uint32_t), qtd + 2, 2);
|
2012-05-09 19:06:36 +04:00
|
|
|
ehci_free_packet(p);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-23 11:50:27 +04:00
|
|
|
/*
|
|
|
|
* EHCI specs say go horizontal here.
|
|
|
|
*
|
|
|
|
* We can also advance the queue here for performance reasons. We
|
|
|
|
* need to take care to only take that shortcut in case we've
|
|
|
|
* processed the qtd just written back without errors, i.e. halt
|
|
|
|
* bit is clear.
|
2010-12-03 18:17:28 +03:00
|
|
|
*/
|
2011-05-23 11:50:27 +04:00
|
|
|
if (q->qh.token & QTD_TOKEN_HALT) {
|
usb: Halt ep queue en cancel pending packets on a packet error
For controllers which queue up more then 1 packet at a time, we must halt the
ep queue, and inside the controller code cancel all pending packets on an
error.
There are multiple reasons for this:
1) Guests expect the controllers to halt ep queues on error, so that they
get the opportunity to cancel transfers which the scheduled after the failing
one, before processing continues
2) Not cancelling queued up packets after a failed transfer also messes up
the controller state machine, in the case of EHCI causing the following
assert to trigger: "assert(p->qtdaddr == q->qtdaddr)" at hcd-ehci.c:2075
3) For bulk endpoints with pipelining enabled (redirection to a real USB
device), we must cancel all the transfers after this a failed one so that:
a) If they've completed already, they are not processed further causing more
stalls to be reported, originating from the same failed transfer
b) If still in flight, they are cancelled before the guest does
a clear stall, otherwise the guest and device can loose sync!
Note this patch only touches the ehci and uhci controller changes, since AFAIK
no other controllers actually queue up multiple transfer. If I'm wrong on this
other controllers need to be updated too!
Also note that this patch was heavily tested with the ehci code, where I had
a reproducer for a device causing a transfer to fail. The uhci code is not
tested with actually failing transfers and could do with a thorough review!
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-08-17 17:24:49 +04:00
|
|
|
/*
|
|
|
|
* We should not do any further processing on a halted queue!
|
|
|
|
* This is esp. important for bulk endpoints with pipelining enabled
|
|
|
|
* (redirection to a real USB device), where we must cancel all the
|
|
|
|
* transfers after this one so that:
|
|
|
|
* 1) If they've completed already, they are not processed further
|
|
|
|
* causing more stalls, originating from the same failed transfer
|
|
|
|
* 2) If still in flight, they are cancelled before the guest does
|
|
|
|
* a clear stall, otherwise the guest and device can loose sync!
|
|
|
|
*/
|
|
|
|
while ((p = QTAILQ_FIRST(&q->packets)) != NULL) {
|
|
|
|
ehci_free_packet(p);
|
|
|
|
}
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_HORIZONTALQH);
|
2011-05-23 11:50:27 +04:00
|
|
|
again = 1;
|
|
|
|
} else {
|
2012-05-11 11:05:15 +04:00
|
|
|
ehci_set_state(q->ehci, q->async, EST_ADVANCEQUEUE);
|
2010-12-03 18:17:28 +03:00
|
|
|
again = 1;
|
2011-05-23 11:50:27 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
return again;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* This is the state machine that is common to both async and periodic
|
|
|
|
*/
|
|
|
|
|
2012-05-11 11:05:15 +04:00
|
|
|
static void ehci_advance_state(EHCIState *ehci, int async)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-05-19 12:49:03 +04:00
|
|
|
EHCIQueue *q = NULL;
|
2010-12-03 18:17:28 +03:00
|
|
|
int again;
|
|
|
|
|
|
|
|
do {
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_WAITLISTHEAD:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_waitlisthead(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHENTRY:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_fetchentry(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHQH:
|
2011-05-19 12:49:03 +04:00
|
|
|
q = ehci_state_fetchqh(ehci, async);
|
2012-05-11 11:05:15 +04:00
|
|
|
if (q != NULL) {
|
|
|
|
assert(q->async == async);
|
|
|
|
again = 1;
|
|
|
|
} else {
|
|
|
|
again = 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHITD:
|
2011-05-18 16:23:35 +04:00
|
|
|
again = ehci_state_fetchitd(ehci, async);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
2011-08-26 16:13:48 +04:00
|
|
|
case EST_FETCHSITD:
|
|
|
|
again = ehci_state_fetchsitd(ehci, async);
|
|
|
|
break;
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_ADVANCEQUEUE:
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_advqueue(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_FETCHQTD:
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_fetchqtd(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_HORIZONTALQH:
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_horizqh(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_EXECUTE:
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_execute(q);
|
2012-05-24 15:34:02 +04:00
|
|
|
if (async) {
|
|
|
|
ehci->async_stepdown = 0;
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_EXECUTING:
|
2011-05-19 19:56:19 +04:00
|
|
|
assert(q != NULL);
|
2012-05-24 15:34:02 +04:00
|
|
|
if (async) {
|
|
|
|
ehci->async_stepdown = 0;
|
|
|
|
}
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_executing(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_WRITEBACK:
|
2011-11-09 15:20:20 +04:00
|
|
|
assert(q != NULL);
|
2012-05-11 11:05:15 +04:00
|
|
|
again = ehci_state_writeback(q);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
fprintf(stderr, "Bad state!\n");
|
|
|
|
again = -1;
|
2011-05-19 19:56:19 +04:00
|
|
|
assert(0);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (again < 0) {
|
|
|
|
fprintf(stderr, "processing error - resetting ehci HC\n");
|
|
|
|
ehci_reset(ehci);
|
|
|
|
again = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (again);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_advance_async_state(EHCIState *ehci)
|
|
|
|
{
|
2012-03-03 00:27:10 +04:00
|
|
|
const int async = 1;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_INACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!ehci_async_enabled(ehci)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
// No break, fall through to ACTIVE
|
|
|
|
|
|
|
|
case EST_ACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!ehci_async_enabled(ehci)) {
|
2012-03-03 00:27:13 +04:00
|
|
|
ehci_queues_rip_all(ehci, async);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_INACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
/* make sure guest has acknowledged the doorbell interrupt */
|
2010-12-03 18:17:28 +03:00
|
|
|
/* TO-DO: is this really needed? */
|
|
|
|
if (ehci->usbsts & USBSTS_IAA) {
|
|
|
|
DPRINTF("IAA status bit still set.\n");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check that address register has been set */
|
|
|
|
if (ehci->asynclistaddr == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_WAITLISTHEAD);
|
|
|
|
ehci_advance_state(ehci, async);
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
|
|
|
|
/* If the doorbell is set, the guest wants to make a change to the
|
|
|
|
* schedule. The host controller needs to release cached data.
|
|
|
|
* (section 4.8.2)
|
|
|
|
*/
|
|
|
|
if (ehci->usbcmd & USBCMD_IAAD) {
|
|
|
|
/* Remove all unseen qhs from the async qhs queue */
|
2012-08-29 12:12:52 +04:00
|
|
|
ehci_queues_rip_unused(ehci, async, 1);
|
2012-08-31 14:41:43 +04:00
|
|
|
trace_usb_ehci_doorbell_ack();
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
ehci->usbcmd &= ~USBCMD_IAAD;
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_IAA);
|
usb-ehci: Drop cached qhs when the doorbell gets rung
The purpose of the IAAD bit / the doorbell is to make the ehci controller
forget about cached qhs, this is mainly used when cancelling transactions,
the qh is unlinked from the async schedule and then the doorbell gets rung,
once the doorbell is acked by the controller the hcd knows that the qh is
no longer in use and that it can do something else with the memory, such
as re-use it for a new qh! But we keep our struct representing this qh around
for circa 250 ms. This allows for a (mightily large) race window where the
following could happen:
-hcd submits a qh at address 0xdeadbeef
-our ehci code sees the qh, sends a request to a usb-device, gets a result
of USB_RET_ASYNC, sets the async_state of the qh to EHCI_ASYNC_INFLIGHT
-hcd unlinks the qh at address 0xdeadbeef
-hcd rings the doorbell, wait for us to ack it
-hcd re-uses the qh at address 0xdeadbeef
-our ehci code sees the qh, looks in the async_queue, sees there already is
a qh at address 0xdeadbeef there with async_state of EHCI_ASYNC_INFLIGHT,
does nothing
-the *original* (which the hcd thinks it has cancelled) transaction finishes
-our ehci code sees the qh on yet another pass through the async list,
looks in the async_queue, sees there already is a qh at address 0xdeadbeef
there with async_state of EHCI_ASYNC_COMPLETED, and finished the transaction
with the results of the *original* transaction.
Not good (tm), this patch fixes this race by removing all qhs which have not
been seen during the last cycle through the async list immidiately when the
doorbell is rung.
Note this patch does not fix any actually observed problem, but upon
reading of the EHCI spec it became apparent to me that the above race could
happen and the usb-ehci behavior from before this patch is not good.
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
2012-03-03 00:27:12 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* this should only be due to a developer mistake */
|
|
|
|
fprintf(stderr, "ehci: Bad asynchronous state %d. "
|
|
|
|
"Resetting to active\n", ehci->astate);
|
2011-05-19 12:49:03 +04:00
|
|
|
assert(0);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ehci_advance_periodic_state(EHCIState *ehci)
|
|
|
|
{
|
|
|
|
uint32_t entry;
|
|
|
|
uint32_t list;
|
2012-03-03 00:27:10 +04:00
|
|
|
const int async = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
// 4.6
|
|
|
|
|
2011-05-18 16:23:35 +04:00
|
|
|
switch(ehci_get_state(ehci, async)) {
|
2010-12-03 18:17:28 +03:00
|
|
|
case EST_INACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!(ehci->frindex & 7) && ehci_periodic_enabled(ehci)) {
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_ACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
// No break, fall through to ACTIVE
|
|
|
|
} else
|
|
|
|
break;
|
|
|
|
|
|
|
|
case EST_ACTIVE:
|
2012-05-24 14:31:34 +04:00
|
|
|
if (!(ehci->frindex & 7) && !ehci_periodic_enabled(ehci)) {
|
2012-03-03 00:27:13 +04:00
|
|
|
ehci_queues_rip_all(ehci, async);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_INACTIVE);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
list = ehci->periodiclistbase & 0xfffff000;
|
|
|
|
/* check that register has been set */
|
|
|
|
if (list == 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
list |= ((ehci->frindex & 0x1ff8) >> 1);
|
|
|
|
|
2011-11-04 05:03:37 +04:00
|
|
|
pci_dma_read(&ehci->dev, list, &entry, sizeof entry);
|
2010-12-03 18:17:28 +03:00
|
|
|
entry = le32_to_cpu(entry);
|
|
|
|
|
|
|
|
DPRINTF("PERIODIC state adv fr=%d. [%08X] -> %08X\n",
|
|
|
|
ehci->frindex / 8, list, entry);
|
2011-05-19 12:49:03 +04:00
|
|
|
ehci_set_fetch_addr(ehci, async,entry);
|
2011-05-18 16:23:35 +04:00
|
|
|
ehci_set_state(ehci, async, EST_FETCHENTRY);
|
|
|
|
ehci_advance_state(ehci, async);
|
2012-08-29 12:12:52 +04:00
|
|
|
ehci_queues_rip_unused(ehci, async, 0);
|
2010-12-03 18:17:28 +03:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
/* this should only be due to a developer mistake */
|
|
|
|
fprintf(stderr, "ehci: Bad periodic state %d. "
|
|
|
|
"Resetting to active\n", ehci->pstate);
|
2011-05-19 12:49:03 +04:00
|
|
|
assert(0);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-24 15:28:32 +04:00
|
|
|
static void ehci_update_frindex(EHCIState *ehci, int frames)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
|
|
|
|
if (!ehci_enabled(ehci)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < frames; i++) {
|
|
|
|
ehci->frindex += 8;
|
|
|
|
|
|
|
|
if (ehci->frindex == 0x00002000) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_FLR);
|
2012-05-24 15:28:32 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (ehci->frindex == 0x00004000) {
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_raise_irq(ehci, USBSTS_FLR);
|
2012-05-24 15:28:32 +04:00
|
|
|
ehci->frindex = 0;
|
2012-07-11 13:06:05 +04:00
|
|
|
if (ehci->usbsts_frindex > 0x00004000) {
|
|
|
|
ehci->usbsts_frindex -= 0x00004000;
|
|
|
|
} else {
|
|
|
|
ehci->usbsts_frindex = 0;
|
|
|
|
}
|
2012-05-24 15:28:32 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-12-03 18:17:28 +03:00
|
|
|
static void ehci_frame_timer(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *ehci = opaque;
|
2012-07-11 13:06:05 +04:00
|
|
|
int need_timer = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
int64_t expire_time, t_now;
|
2011-05-31 14:23:13 +04:00
|
|
|
uint64_t ns_elapsed;
|
2012-05-25 10:13:55 +04:00
|
|
|
int frames, skipped_frames;
|
2010-12-03 18:17:28 +03:00
|
|
|
int i;
|
|
|
|
|
|
|
|
t_now = qemu_get_clock_ns(vm_clock);
|
2011-05-31 14:23:13 +04:00
|
|
|
ns_elapsed = t_now - ehci->last_run_ns;
|
|
|
|
frames = ns_elapsed / FRAME_TIMER_NS;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-24 15:34:02 +04:00
|
|
|
if (ehci_periodic_enabled(ehci) || ehci->pstate != EST_INACTIVE) {
|
2012-07-11 13:06:05 +04:00
|
|
|
need_timer++;
|
2012-07-11 13:23:17 +04:00
|
|
|
ehci->async_stepdown = 0;
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-05-25 10:13:55 +04:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2012-05-24 15:34:02 +04:00
|
|
|
for (i = 0; i < frames; i++) {
|
|
|
|
ehci_update_frindex(ehci, 1);
|
2012-05-25 10:13:55 +04:00
|
|
|
ehci_advance_periodic_state(ehci);
|
2012-05-24 15:34:02 +04:00
|
|
|
ehci->last_run_ns += FRAME_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;
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Async is not inside loop since it executes everything it can once
|
|
|
|
* called
|
|
|
|
*/
|
2012-05-24 15:34:02 +04:00
|
|
|
if (ehci_async_enabled(ehci) || ehci->astate != EST_INACTIVE) {
|
2012-07-11 13:06:05 +04:00
|
|
|
need_timer++;
|
2012-07-11 13:23:17 +04:00
|
|
|
ehci_advance_async_state(ehci);
|
2012-05-24 15:34:02 +04:00
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
ehci_commit_irq(ehci);
|
|
|
|
if (ehci->usbsts_pending) {
|
|
|
|
need_timer++;
|
|
|
|
ehci->async_stepdown = 0;
|
2012-05-24 14:53:43 +04:00
|
|
|
}
|
2012-07-10 20:00:50 +04:00
|
|
|
|
2012-07-11 13:06:05 +04:00
|
|
|
if (need_timer) {
|
2012-07-11 13:23:17 +04:00
|
|
|
expire_time = t_now + (get_ticks_per_sec()
|
|
|
|
* (ehci->async_stepdown+1) / FRAME_TIMER_FREQ);
|
2012-07-11 13:06:05 +04:00
|
|
|
qemu_mod_timer(ehci->frame_timer, expire_time);
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
|
|
|
|
2012-05-11 13:19:11 +04:00
|
|
|
static void ehci_async_bh(void *opaque)
|
|
|
|
{
|
|
|
|
EHCIState *ehci = opaque;
|
|
|
|
ehci_advance_async_state(ehci);
|
|
|
|
}
|
2010-12-03 18:17:28 +03:00
|
|
|
|
2011-08-08 17:09:23 +04:00
|
|
|
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 },
|
|
|
|
},
|
|
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
2010-12-03 18:17:28 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
static int usb_ehci_initfn(PCIDevice *dev);
|
|
|
|
|
|
|
|
static USBPortOps ehci_port_ops = {
|
|
|
|
.attach = ehci_attach,
|
|
|
|
.detach = ehci_detach,
|
2011-06-24 14:31:11 +04:00
|
|
|
.child_detach = ehci_child_detach,
|
2011-06-24 18:18:13 +04:00
|
|
|
.wakeup = ehci_wakeup,
|
2010-12-03 18:17:28 +03:00
|
|
|
.complete = ehci_async_complete_packet,
|
|
|
|
};
|
|
|
|
|
2011-05-23 19:37:12 +04:00
|
|
|
static USBBusOps ehci_bus_ops = {
|
2011-06-24 18:18:13 +04:00
|
|
|
.register_companion = ehci_register_companion,
|
2011-05-23 19:37:12 +04:00
|
|
|
};
|
|
|
|
|
2012-05-14 15:55:44 +04:00
|
|
|
static int usb_ehci_post_load(void *opaque, int version_id)
|
|
|
|
{
|
|
|
|
EHCIState *s = opaque;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for (i = 0; i < NB_PORTS; i++) {
|
|
|
|
USBPort *companion = s->companion_ports[i];
|
|
|
|
if (companion == NULL) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (s->portsc[i] & PORTSC_POWNER) {
|
|
|
|
companion->dev = s->ports[i].dev;
|
|
|
|
} else {
|
|
|
|
companion->dev = NULL;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2011-07-08 12:48:46 +04:00
|
|
|
static const VMStateDescription vmstate_ehci = {
|
2012-05-14 15:55:44 +04:00
|
|
|
.name = "ehci",
|
2012-08-15 15:55:40 +04:00
|
|
|
.version_id = 2,
|
|
|
|
.minimum_version_id = 1,
|
2012-05-14 15:55:44 +04:00
|
|
|
.post_load = usb_ehci_post_load,
|
|
|
|
.fields = (VMStateField[]) {
|
|
|
|
VMSTATE_PCI_DEVICE(dev, EHCIState),
|
|
|
|
/* mmio registers */
|
|
|
|
VMSTATE_UINT32(usbcmd, EHCIState),
|
|
|
|
VMSTATE_UINT32(usbsts, EHCIState),
|
2012-08-15 15:55:40 +04:00
|
|
|
VMSTATE_UINT32_V(usbsts_pending, EHCIState, 2),
|
|
|
|
VMSTATE_UINT32_V(usbsts_frindex, EHCIState, 2),
|
2012-05-14 15:55:44 +04:00
|
|
|
VMSTATE_UINT32(usbintr, EHCIState),
|
|
|
|
VMSTATE_UINT32(frindex, EHCIState),
|
|
|
|
VMSTATE_UINT32(ctrldssegment, EHCIState),
|
|
|
|
VMSTATE_UINT32(periodiclistbase, EHCIState),
|
|
|
|
VMSTATE_UINT32(asynclistaddr, EHCIState),
|
|
|
|
VMSTATE_UINT32(configflag, EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[0], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[1], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[2], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[3], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[4], EHCIState),
|
|
|
|
VMSTATE_UINT32(portsc[5], EHCIState),
|
|
|
|
/* frame timer */
|
|
|
|
VMSTATE_TIMER(frame_timer, EHCIState),
|
|
|
|
VMSTATE_UINT64(last_run_ns, EHCIState),
|
|
|
|
VMSTATE_UINT32(async_stepdown, EHCIState),
|
|
|
|
/* schedule state */
|
|
|
|
VMSTATE_UINT32(astate, EHCIState),
|
|
|
|
VMSTATE_UINT32(pstate, EHCIState),
|
|
|
|
VMSTATE_UINT32(a_fetch_addr, EHCIState),
|
|
|
|
VMSTATE_UINT32(p_fetch_addr, EHCIState),
|
|
|
|
VMSTATE_END_OF_LIST()
|
|
|
|
}
|
2011-07-08 12:48:46 +04:00
|
|
|
};
|
|
|
|
|
2011-07-01 13:51:02 +04:00
|
|
|
static Property ehci_properties[] = {
|
|
|
|
DEFINE_PROP_UINT32("maxframes", EHCIState, maxframes, 128),
|
|
|
|
DEFINE_PROP_END_OF_LIST(),
|
|
|
|
};
|
|
|
|
|
2011-12-04 22:22:06 +04:00
|
|
|
static void ehci_class_init(ObjectClass *klass, void *data)
|
|
|
|
{
|
2011-12-08 07:34:16 +04:00
|
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
2011-12-04 22:22:06 +04:00
|
|
|
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;
|
2011-12-08 07:34:16 +04:00
|
|
|
dc->vmsd = &vmstate_ehci;
|
|
|
|
dc->props = ehci_properties;
|
2011-12-04 22:22:06 +04:00
|
|
|
}
|
|
|
|
|
2011-12-08 07:34:16 +04:00
|
|
|
static TypeInfo ehci_info = {
|
|
|
|
.name = "usb-ehci",
|
|
|
|
.parent = TYPE_PCI_DEVICE,
|
|
|
|
.instance_size = sizeof(EHCIState),
|
|
|
|
.class_init = ehci_class_init,
|
2011-12-07 05:32:44 +04:00
|
|
|
};
|
|
|
|
|
2011-12-04 22:22:06 +04:00
|
|
|
static void ich9_ehci_class_init(ObjectClass *klass, void *data)
|
|
|
|
{
|
2011-12-08 07:34:16 +04:00
|
|
|
DeviceClass *dc = DEVICE_CLASS(klass);
|
2011-12-04 22:22:06 +04:00
|
|
|
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;
|
2011-12-08 07:34:16 +04:00
|
|
|
dc->vmsd = &vmstate_ehci;
|
|
|
|
dc->props = ehci_properties;
|
2011-12-04 22:22:06 +04:00
|
|
|
}
|
|
|
|
|
2011-12-08 07:34:16 +04:00
|
|
|
static TypeInfo ich9_ehci_info = {
|
|
|
|
.name = "ich9-usb-ehci1",
|
|
|
|
.parent = TYPE_PCI_DEVICE,
|
|
|
|
.instance_size = sizeof(EHCIState),
|
|
|
|
.class_init = ich9_ehci_class_init,
|
2010-12-03 18:17:28 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2011-09-11 14:40:23 +04:00
|
|
|
pci_set_byte(&pci_conf[PCI_INTERRUPT_PIN], 4); /* interrupt pin D */
|
2010-12-03 18:17:28 +03:00
|
|
|
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
|
|
|
|
|
2011-06-02 05:18:48 +04:00
|
|
|
pci_set_byte(&pci_conf[USB_SBRN], USB_RELEASE_2); // release number (2.1.4)
|
2010-12-03 18:17:28 +03:00
|
|
|
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];
|
|
|
|
|
2011-05-23 19:37:12 +04:00
|
|
|
usb_bus_new(&s->bus, &ehci_bus_ops, &s->dev.qdev);
|
2010-12-03 18:17:28 +03:00
|
|
|
for(i = 0; i < NB_PORTS; i++) {
|
|
|
|
usb_register_port(&s->bus, &s->ports[i], s, i, &ehci_port_ops,
|
|
|
|
USB_SPEED_MASK_HIGH);
|
|
|
|
s->ports[i].dev = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->frame_timer = qemu_new_timer_ns(vm_clock, ehci_frame_timer, s);
|
2012-05-11 13:19:11 +04:00
|
|
|
s->async_bh = qemu_bh_new(ehci_async_bh, s);
|
2012-03-03 00:27:10 +04:00
|
|
|
QTAILQ_INIT(&s->aqueues);
|
|
|
|
QTAILQ_INIT(&s->pqueues);
|
2012-07-06 14:09:33 +04:00
|
|
|
usb_packet_init(&s->ipacket);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
qemu_register_reset(ehci_reset, s);
|
|
|
|
|
2011-08-08 17:09:23 +04:00
|
|
|
memory_region_init_io(&s->mem, &ehci_mem_ops, s, "ehci", MMIO_SIZE);
|
2011-08-08 17:09:31 +04:00
|
|
|
pci_register_bar(&s->dev, 0, PCI_BASE_ADDRESS_SPACE_MEMORY, &s->mem);
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-02-09 18:20:55 +04:00
|
|
|
static void ehci_register_types(void)
|
2010-12-03 18:17:28 +03:00
|
|
|
{
|
2011-12-08 07:34:16 +04:00
|
|
|
type_register_static(&ehci_info);
|
|
|
|
type_register_static(&ich9_ehci_info);
|
2010-12-03 18:17:28 +03:00
|
|
|
}
|
2012-02-09 18:20:55 +04:00
|
|
|
|
|
|
|
type_init(ehci_register_types)
|
2010-12-03 18:17:28 +03:00
|
|
|
|
|
|
|
/*
|
|
|
|
* vim: expandtab ts=4
|
|
|
|
*/
|