summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/USB/DevEHCI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/USB/DevEHCI.cpp')
-rw-r--r--src/VBox/Devices/USB/DevEHCI.cpp5231
1 files changed, 5231 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevEHCI.cpp b/src/VBox/Devices/USB/DevEHCI.cpp
new file mode 100644
index 00000000..ade53208
--- /dev/null
+++ b/src/VBox/Devices/USB/DevEHCI.cpp
@@ -0,0 +1,5231 @@
+/* $Id: DevEHCI.cpp $ */
+/** @file
+ * DevEHCI - Enhanced Host Controller Interface for USB.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program 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
+ * 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 <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_dev_ehci EHCI - Enhanced Host Controller Interface Emulation.
+ *
+ * This component implements an EHCI USB controller. It is split roughly
+ * into two main parts, the first part implements the register level
+ * specification of USB EHCI and the second part maintains the root hub (which
+ * is an integrated component of the device).
+ *
+ * The EHCI registers are used for the usual stuff like enabling and disabling
+ * interrupts. Since the USB time is divided in to 1ms frames and various
+ * interrupts may need to be triggered at frame boundary time, a timer-based
+ * approach was taken.
+ *
+ * Note that all processing is currently done on a frame boundary and
+ * no attempt is made to emulate events with micro-frame granularity.
+ *
+ * The actual USB transfers are stored in main memory (along with endpoint and
+ * transfer descriptors). The ED's for all the control and bulk endpoints are
+ * found by consulting the HcAsyncListAddr register (ASYNCLISTADDR).
+ * Interrupt and isochronous ED's are found by looking at the HcPeriodicListBase
+ * (PERIODICLISTBASE) register.
+ *
+ * At the start of every frame (in function ehciR3StartOfFrame) we traverse all
+ * enabled ED lists and queue up as many transfers as possible. No attention
+ * is paid to control/bulk service ratios or bandwidth requirements since our
+ * USB could conceivably contain a dozen high speed busses and this would
+ * artificially limit the performance.
+ *
+ * Once we have a transfer ready to go (in the appropriate ehciServiceXxx function)
+ * we allocate an URB on the stack, fill in all the relevant fields and submit
+ * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual
+ * USB core code coordinates everything else from this point onwards.
+ *
+ * When the URB has been successfully handed to the lower level driver, our
+ * prepare callback gets called and we can remove the TD from the ED transfer
+ * list. This stops us queueing it twice while it completes.
+ * bird: no, we don't remove it because that confuses the guest! (=> crashes)
+ *
+ * Completed URBs are reaped at the end of every frame (in function
+ * ehciR3FrameBoundaryTimer). Our completion routine makes use of the ED and TD
+ * fields in the URB to store the physical addresses of the descriptors so
+ * that they may be modified in the roothub callbacks. Our completion
+ * routine (ehciRhXferCompleteXxx) carries out a number of tasks:
+ * -# Retires the TD associated with the transfer, setting the
+ * relevant error code etc.
+ * -# Updates done-queue interrupt timer and potentially causes
+ * a writeback of the done-queue.
+ * -# If the transfer was device-to-host, we copy the data into
+ * the host memory.
+ *
+ * As for error handling EHCI allows for 3 retries before failing a transfer,
+ * an error count is stored in each transfer descriptor. A halt flag is also
+ * stored in the transfer descriptor. That allows for ED's to be disabled
+ * without stopping the bus and de-queuing them.
+ *
+ * When the bus is started and stopped, we call VUSBIDevPowerOn/Off() on our
+ * roothub to indicate it's powering up and powering down. Whenever we power
+ * down, the USB core makes sure to synchronously complete all outstanding
+ * requests so that the EHCI is never seen in an inconsistent state by the
+ * guest OS (Transfers are not meant to be unlinked until they've actually
+ * completed, but we can't do that unless we work synchronously, so we just
+ * have to fake it).
+ * bird: we do work synchronously now, anything causes guest crashes.
+ *
+ * The number of ports is configurable. The architectural maximum is 15, but
+ * some guests (e.g. OS/2) crash if they see more than 12 or so ports. Saved
+ * states always include the data for all 15 ports but HCSPARAMS determines
+ * the actual number visible to the guest.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_EHCI
+#include <VBox/pci.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/param.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+#ifdef IN_RING3
+# include <iprt/thread.h>
+# include <iprt/mem.h>
+# include <iprt/uuid.h>
+#endif
+#include <VBox/vusb.h>
+#ifdef VBOX_IN_EXTPACK_R3
+# include <VBox/version.h>
+#elif defined(VBOX_IN_EXTPACK)
+# include <VBox/sup.h>
+#endif
+#ifndef VBOX_IN_EXTPACK
+# include "VBoxDD.h"
+#endif
+
+
+/** The saved state version. */
+#define EHCI_SAVED_STATE_VERSION 7
+/** The saved state version before the EOF timers were removed. */
+#define EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL 6 /* Introduced in 5.2. */
+/** The saved state with support of 8 ports. */
+#define EHCI_SAVED_STATE_VERSION_8PORTS 5 /* Introduced in 3.1 or so. */
+
+/** Number of Downstream Ports on the root hub; 15 is the maximum
+ * the EHCI specification provides for. */
+#define EHCI_NDP_MAX 15
+
+/** The default Number of Downstream Ports reported to guests. */
+#define EHCI_NDP_DEFAULT 12
+
+/* Macro to query the number of currently configured ports. */
+#define EHCI_NDP_CFG(pehci) ((pehci)->hcs_params & EHCI_HCS_PARAMS_NDP_MASK)
+/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */
+#define EHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1)
+
+/** Size of the capability part of the MMIO page. */
+#define EHCI_CAPS_REG_SIZE 0x20
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * Host controller Transfer Descriptor data.
+ */
+typedef struct VUSBURBHCITDINT
+{
+ /** Type of TD. */
+ uint32_t TdType;
+ /** The address of the TD. */
+ RTGCPHYS TdAddr;
+ /** A copy of the TD. */
+ uint32_t TdCopy[16];
+} VUSBURBHCITDINT;
+
+/**
+ * The host controller data associated with each URB.
+ */
+typedef struct VUSBURBHCIINT
+{
+ /** The endpoint descriptor address. */
+ RTGCPHYS EdAddr;
+ /** Number of Tds in the array. */
+ uint32_t cTds;
+ /** When this URB was created.
+ * (Used for isochronous frames and for logging.) */
+ uint32_t u32FrameNo;
+ /** Flag indicating that the TDs have been unlinked. */
+ bool fUnlinked;
+} VUSBURBHCIINT;
+#endif
+
+/**
+ * An EHCI root hub port, shared.
+ */
+typedef struct EHCIHUBPORT
+{
+ /** The port register. */
+ uint32_t fReg;
+} EHCIHUBPORT;
+/** Pointer to a shared EHCI root hub port. */
+typedef EHCIHUBPORT *PEHCIHUBPORT;
+
+/**
+ * An EHCI root hub port, ring-3.
+ */
+typedef struct EHCIHUBPORTR3
+{
+ /** Flag whether there is a device attached to the port. */
+ bool fAttached;
+} EHCIHUBPORTR3;
+/** Pointer to a ring-3 EHCI root hub port. */
+typedef EHCIHUBPORTR3 *PEHCIHUBPORTR3;
+
+
+/**
+ * The EHCI root hub, shared.
+ */
+typedef struct EHCIROOTHUB
+{
+ /** Per-port state. */
+ EHCIHUBPORT aPorts[EHCI_NDP_MAX];
+ /** Unused, only needed for saved state compatibility. */
+ uint32_t unused;
+} EHCIROOTHUB;
+/** Pointer to the EHCI root hub. */
+typedef EHCIROOTHUB *PEHCIROOTHUB;
+
+
+/**
+ * The EHCI root hub, ring-3 edition.
+ *
+ * @implements PDMIBASE
+ * @implements VUSBIROOTHUBPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct EHCIROOTHUBR3
+{
+ /** Pointer to the base interface of the VUSB RootHub. */
+ R3PTRTYPE(PPDMIBASE) pIBase;
+ /** Pointer to the connector interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn;
+ /** The base interface exposed to the roothub driver. */
+ PDMIBASE IBase;
+ /** The roothub port interface exposed to the roothub driver. */
+ VUSBIROOTHUBPORT IRhPort;
+
+ /** The LED. */
+ PDMLED Led;
+ /** The LED ports. */
+ PDMILEDPORTS ILeds;
+ /** Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+
+ EHCIHUBPORTR3 aPorts[EHCI_NDP_MAX];
+} EHCIROOTHUBR3;
+/** Pointer to the ring-3 EHCI root hub state. */
+typedef EHCIROOTHUBR3 *PEHCIROOTHUBR3;
+
+
+/**
+ * EHCI device data, shared.
+ */
+typedef struct EHCI
+{
+ /** Async scheduler sleeping; triggered by empty list detection */
+ bool fAsyncTraversalTimerActive;
+
+ bool afAlignment0[7];
+
+ /** Start of current frame. */
+ uint64_t SofTime;
+ /** Root hub device */
+ EHCIROOTHUB RootHub;
+
+ /** @name Host Controller Capability Registers (R/O)
+ * @{ */
+ /** CAPLENGTH: base + cap_length = operational register start */
+ uint32_t cap_length;
+ /** HCIVERSION: host controller interface version */
+ uint32_t hci_version;
+ /** HCSPARAMS: Structural parameters */
+ uint32_t hcs_params;
+ /** HCCPARAMS: Capability parameters */
+ uint32_t hcc_params;
+ /** @} */
+
+ /** @name Host Controller Operational Registers (R/W)
+ * @{ */
+ /** USB command register */
+ uint32_t cmd;
+ /** USB status register */
+ uint32_t intr_status;
+ /** USB interrupt enable register */
+ uint32_t intr;
+ /** Frame index register; actually it's micro-frame number */
+ uint32_t frame_idx;
+ /** Control Data Structure Segment Register */
+ uint32_t ds_segment;
+ /** Periodic Frame List Base Address Register */
+ uint32_t periodic_list_base;
+ /** Current Asynchronous List Address Register */
+ uint32_t async_list_base;
+ /** Configure Flag Register */
+ uint32_t config;
+ /** @} */
+
+ /** @name Control partition (registers)
+ * @{ */
+ /** Interrupt interval; see interrupt threshold in the command register */
+ uint32_t uIrqInterval;
+ /** @} */
+
+ /** @name Frame counter partition (registers)
+ * @{ */
+ /** HcFmNumber.
+ * @remark The register size is 16-bit, but for debugging and performance
+ * reasons we maintain a 32-bit counter. */
+ uint32_t HcFmNumber;
+ /** Number of micro-frames per timer call */
+ uint32_t uFramesPerTimerCall;
+ /** @} */
+
+ /** Flag whether the framer thread should processing frames. */
+ volatile bool fBusStarted;
+
+ bool afAlignment1[3]; /**< Align CsIrq correctly. */
+
+ /* The following members are not part of saved state. */
+
+ /** Critical section synchronising interrupt handling. */
+ PDMCRITSECT CsIrq;
+
+ /** The MMIO region. */
+ IOMMMIOHANDLE hMmio;
+} EHCI;
+AssertCompileMemberAlignment(EHCI, CsIrq, 8);
+/** Pointer to shared EHCI device data. */
+typedef struct EHCI *PEHCI;
+
+
+/**
+ * EHCI device data, ring-3 edition.
+ */
+typedef struct EHCIR3
+{
+ /** Root hub device. */
+ EHCIROOTHUBR3 RootHub;
+
+ /** The number of virtual time ticks per frame. */
+ uint64_t cTicksPerFrame;
+ /** The number of virtual time ticks per USB bus tick. */
+ uint64_t cTicksPerUsbTick;
+
+ /** Pointer to the device instance. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** Number of in-flight TDs. */
+ unsigned cInFlight;
+ unsigned Alignment2; /**< Align aInFlight on a 8 byte boundary. */
+ /** Array of in-flight TDs. */
+ struct ehci_td_in_flight
+ {
+ /** Address of the transport descriptor. */
+ RTGCPHYS GCPhysTD;
+ /** Pointer to the URB. */
+ R3PTRTYPE(PVUSBURB) pUrb;
+ } aInFlight[257];
+
+ /** Detected canceled isochronous URBs. */
+ STAMCOUNTER StatCanceledIsocUrbs;
+ /** Detected canceled general URBs. */
+ STAMCOUNTER StatCanceledGenUrbs;
+ /** Dropped URBs (endpoint halted, or URB canceled). */
+ STAMCOUNTER StatDroppedUrbs;
+
+ /* The following members are not part of saved state. */
+
+ /** VM timer frequency used for frame timer calculations. */
+ uint64_t u64TimerHz;
+ /** Number of USB work cycles with no transfers. */
+ uint32_t cIdleCycles;
+ /** Current frame timer rate (default 1000). */
+ uint32_t uFrameRate;
+ /** Idle detection flag; must be cleared at start of frame */
+ bool fIdle;
+ bool afAlignment4[3];
+
+ /** Default frequency of the frame timer. */
+ uint32_t uFrameRateDefault;
+ /** How long to wait until the next frame. */
+ uint64_t nsWait;
+ /** The framer thread. */
+ R3PTRTYPE(PPDMTHREAD) hThreadFrame;
+ /** Event semaphore to interact with the framer thread. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrame;
+ /** Event semaphore to release the thread waiting for the framer thread to stop. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrameStopped;
+ /** Critical section to synchronize the framer and URB completion handler. */
+ RTCRITSECT CritSect;
+} EHCIR3;
+/** Pointer to ring-3 EHCI device data. */
+typedef struct EHCIR3 *PEHCIR3;
+
+
+/**
+ * EHCI device data, ring-0 edition.
+ */
+typedef struct EHCIR0
+{
+ uint32_t uUnused;
+} EHCIR0;
+/** Pointer to ring-0 EHCI device data. */
+typedef struct EHCIR0 *PEHCIR0;
+
+
+/**
+ * EHCI device data, raw-mode edition.
+ */
+typedef struct EHCIRC
+{
+ uint32_t uUnused;
+} EHCIRC;
+/** Pointer to raw-mode EHCI device data. */
+typedef struct EHCIRC *PEHCIRC;
+
+
+/** @typedef EHCICC
+ * The EHCI device data for the current context. */
+typedef CTX_SUFF(EHCI) EHCICC;
+/** @typedef PEHCICC
+ * Pointer to the EHCI device for the current context. */
+typedef CTX_SUFF(PEHCI) PEHCICC;
+
+
+/** @@name EHCI Transfer Descriptor Types
+ * @{ */
+/** Isochronous Transfer Descriptor */
+#define EHCI_DESCRIPTOR_ITD 0
+/** Queue Head */
+#define EHCI_DESCRIPTOR_QH 1
+/** Split Transaction Isochronous Transfer Descriptor */
+#define EHCI_DESCRIPTOR_SITD 2
+/** Frame Span Traversal Node */
+#define EHCI_DESCRIPTOR_FSTN 3
+/** @} */
+
+/** @@name EHCI Transfer service type
+ * @{ */
+typedef enum
+{
+ EHCI_SERVICE_PERIODIC = 0,
+ EHCI_SERVICE_ASYNC = 1
+} EHCI_SERVICE_TYPE;
+/** @} */
+
+/** @@name EHCI Frame List Element Pointer
+ * @{ */
+#define EHCI_FRAME_LIST_NEXTPTR_SHIFT 5
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t FrameAddr : 27;
+} EHCI_FRAME_LIST_PTR;
+AssertCompileSize(EHCI_FRAME_LIST_PTR, 4);
+/** @} */
+
+/** @@name EHCI Isochronous Transfer Descriptor (iTD)
+ * @{ */
+#define EHCI_TD_PTR_SHIFT 5
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t Pointer : 27;
+} EHCI_TD_PTR;
+AssertCompileSize(EHCI_TD_PTR, 4);
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t PG : 3;
+ uint32_t IOC : 1;
+ uint32_t Length : 12;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Active : 1;
+} EHCI_ITD_TRANSACTION;
+AssertCompileSize(EHCI_ITD_TRANSACTION, 4);
+
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t Reserved1 : 1;
+ uint32_t EndPt : 4;
+ uint32_t Ignore1 : 20;
+ uint32_t MaxPacket : 11;
+ uint32_t DirectionIn : 1;
+ uint32_t Ignore2 : 20;
+ uint32_t Multi : 2;
+ uint32_t Reserved10 : 10;
+ uint32_t Ignore3 : 20;
+} EHCI_ITD_MISC;
+AssertCompileSize(EHCI_ITD_MISC, 12);
+
+#define EHCI_BUFFER_PTR_SHIFT 12
+
+typedef struct
+{
+ uint32_t Reserved : 12;
+ uint32_t Pointer : 20; /* 4k aligned */
+} EHCI_BUFFER_PTR;
+AssertCompileSize(EHCI_BUFFER_PTR, 4);
+
+#define EHCI_NUM_ITD_TRANSACTIONS 8
+#define EHCI_NUM_ITD_PAGES 7
+
+typedef struct
+{
+ EHCI_TD_PTR Next;
+ EHCI_ITD_TRANSACTION Transaction[EHCI_NUM_ITD_TRANSACTIONS];
+ union
+ {
+ EHCI_ITD_MISC Misc;
+ EHCI_BUFFER_PTR Buffer[EHCI_NUM_ITD_PAGES];
+ } Buffer;
+} EHCI_ITD, *PEHCI_ITD;
+typedef const EHCI_ITD *PEHCI_CITD;
+AssertCompileSize(EHCI_ITD, 0x40);
+/** @} */
+
+/* ITD with extra padding to add 8th 'Buffer' entry. The PG member of
+ * EHCI_ITD_TRANSACTION can contain values in the 0-7 range, but only values
+ * 0-6 are valid. The extra padding is added to avoid cluttering the code
+ * with range checks; ehciR3ReadItd() initializes the pad with a safe value.
+ * The EHCI 1.0 specification explicitly says using PG value of 7 yields
+ * undefined behavior.
+ */
+typedef struct
+{
+ EHCI_ITD itd;
+ EHCI_BUFFER_PTR pad;
+} EHCI_ITD_PAD, *PEHCI_ITD_PAD;
+AssertCompileSize(EHCI_ITD_PAD, 0x44);
+
+/** @name Split Transaction Isochronous Transfer Descriptor (siTD)
+ * @{ */
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t Reserved : 1;
+ uint32_t EndPt : 4;
+ uint32_t Reserved2 : 4;
+ uint32_t HubAddress : 7;
+ uint32_t Reserved3 : 1;
+ uint32_t Port : 7;
+ uint32_t DirectionIn : 1;
+} EHCI_SITD_ADDR;
+AssertCompileSize(EHCI_SITD_ADDR, 4);
+
+typedef struct
+{
+ uint32_t SMask : 8;
+ uint32_t CMask : 8;
+ uint32_t Reserved : 16;
+} EHCI_SITD_SCHEDCTRL;
+AssertCompileSize(EHCI_SITD_SCHEDCTRL, 4);
+
+typedef struct
+{
+ /* 8 Status flags */
+ uint32_t Reserved : 1;
+ uint32_t SplitXState : 1;
+ uint32_t MisseduFrame : 1;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Error : 1;
+ uint32_t Active : 1;
+ uint32_t CPMask : 8;
+ uint32_t Length : 10;
+ uint32_t Reserved4 : 4;
+ uint32_t PageSelect : 1;
+ uint32_t IOC : 1;
+} EHCI_SITD_TRANSFER;
+AssertCompileSize(EHCI_SITD_TRANSFER, 4);
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_SITD_BUFFER0;
+AssertCompileSize(EHCI_SITD_BUFFER0, 4);
+
+typedef struct
+{
+ uint32_t TCount : 3;
+ uint32_t TPosition : 2;
+ uint32_t Reserved : 7;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_SITD_BUFFER1;
+AssertCompileSize(EHCI_SITD_BUFFER1, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_SITD_BACKPTR;
+AssertCompileSize(EHCI_SITD_BACKPTR, 4);
+
+typedef struct
+{
+ EHCI_TD_PTR NextSITD;
+ EHCI_SITD_ADDR Address;
+ EHCI_SITD_SCHEDCTRL ScheduleCtrl;
+ EHCI_SITD_TRANSFER Transfer;
+ EHCI_SITD_BUFFER0 Buffer0;
+ EHCI_SITD_BUFFER1 Buffer1;
+ EHCI_SITD_BACKPTR BackPtr;
+} EHCI_SITD, *PEHCI_SITD;
+typedef const EHCI_SITD *PEHCI_CSITD;
+AssertCompileSize(EHCI_SITD, 0x1C);
+/** @} */
+
+
+/** @name Queue Element Transfer Descriptor (qTD)
+ * @{ */
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_QTD_NEXTPTR;
+AssertCompileSize(EHCI_QTD_NEXTPTR, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_QTD_ALTNEXTPTR;
+AssertCompileSize(EHCI_QTD_ALTNEXTPTR, 4);
+
+#define EHCI_QTD_PID_OUT 0
+#define EHCI_QTD_PID_IN 1
+#define EHCI_QTD_PID_SETUP 2
+
+typedef struct
+{
+ /* 8 Status flags */
+ uint32_t PingState : 1;
+ uint32_t SplitXState : 1;
+ uint32_t MisseduFrame : 1;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Halted : 1;
+ uint32_t Active : 1;
+ uint32_t PID : 2;
+ uint32_t ErrorCount : 2;
+ uint32_t CurrentPage : 3;
+ uint32_t IOC : 1;
+ uint32_t Length : 15;
+ uint32_t DataToggle : 1;
+} EHCI_QTD_TOKEN;
+AssertCompileSize(EHCI_QTD_TOKEN, 4);
+
+#define EHCI_QTD_HAS_ERROR(pQtdToken) (*((uint32_t *)pQtdToken) & 0x7F)
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t Reserved : 20;
+ uint32_t Ignore[4];
+} EHCI_QTD_OFFSET;
+AssertCompileSize(EHCI_QTD_OFFSET, 20);
+
+typedef struct
+{
+ EHCI_QTD_NEXTPTR Next;
+ EHCI_QTD_ALTNEXTPTR AltNext;
+ union
+ {
+ EHCI_QTD_TOKEN Bits;
+ uint32_t u32;
+ } Token;
+ union
+ {
+ EHCI_QTD_OFFSET Offset;
+ EHCI_BUFFER_PTR Buffer[5];
+ } Buffer;
+} EHCI_QTD, *PEHCI_QTD;
+typedef const EHCI_QTD *PEHCI_CQTD;
+AssertCompileSize(EHCI_QTD, 0x20);
+/** @} */
+
+
+/** @name Queue Head Descriptor (QHD)
+ * @{ */
+
+#define EHCI_QHD_EPT_SPEED_FULL 0 /**< 12 Mbps */
+#define EHCI_QHD_EPT_SPEED_LOW 1 /**< 1.5 Mbps */
+#define EHCI_QHD_EPT_SPEED_HIGH 2 /**< 480 Mbps */
+#define EHCI_QHD_EPT_SPEED_RESERVED 3
+
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t InActiveNext : 1;
+ uint32_t EndPt : 4;
+ uint32_t EndPtSpeed : 2;
+ uint32_t DataToggle : 1;
+ uint32_t HeadReclamation : 1;
+ uint32_t MaxLength : 11;
+ uint32_t ControlEPFlag : 1;
+ uint32_t NakCountReload : 4;
+} EHCI_QHD_EPCHARS;
+AssertCompileSize(EHCI_QHD_EPCHARS, 4);
+
+typedef struct
+{
+ uint32_t SMask : 8;
+ uint32_t CMask : 8;
+ uint32_t HubAddress : 7;
+ uint32_t Port : 7;
+ uint32_t Mult : 2;
+} EHCI_QHD_EPCAPS;
+AssertCompileSize(EHCI_QHD_EPCAPS, 4);
+
+typedef struct
+{
+ uint32_t Reserved : 5;
+ uint32_t Pointer : 27;
+} EHCI_QHD_CURRPTR;
+AssertCompileSize(EHCI_QHD_CURRPTR, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t NakCnt : 4;
+ uint32_t Pointer : 27;
+} EHCI_QHD_ALTNEXT;
+AssertCompileSize(EHCI_QHD_ALTNEXT, 4);
+
+typedef struct
+{
+ uint32_t CProgMask : 8;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_QHD_BUFFER1;
+AssertCompileSize(EHCI_QHD_BUFFER1, 4);
+
+typedef struct
+{
+ uint32_t FrameTag : 5;
+ uint32_t SBytes : 7;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_QHD_BUFFER2;
+AssertCompileSize(EHCI_QHD_BUFFER2, 4);
+
+typedef struct
+{
+ EHCI_TD_PTR Next;
+ EHCI_QHD_EPCHARS Characteristics;
+ EHCI_QHD_EPCAPS Caps;
+ EHCI_QHD_CURRPTR CurrQTD;
+ union
+ {
+ EHCI_QTD OrgQTD;
+ struct
+ {
+ uint32_t Identical1[2];
+ EHCI_QHD_ALTNEXT AltNextQTD;
+ uint32_t Identical2;
+ EHCI_QHD_BUFFER1 Buffer1;
+ EHCI_QHD_BUFFER2 Buffer2;
+ uint32_t Identical3[2];
+ } Status;
+ } Overlay;
+} EHCI_QHD, *PEHCI_QHD;
+typedef const EHCI_QHD *PEHCI_CQHD;
+AssertCompileSize(EHCI_QHD, 0x30);
+/** @} */
+
+/** @name Periodic Frame Span Traversal Node (FSTN)
+ * @{ */
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t Ptr : 27;
+} EHCI_FSTN_PTR;
+AssertCompileSize(EHCI_FSTN_PTR, 4);
+
+typedef struct
+{
+ EHCI_FSTN_PTR NormalPtr;
+ EHCI_FSTN_PTR BackPtr;
+} EHCI_FSTN, *PEHCI_FSTN;
+typedef const EHCI_FSTN *PEHCI_CFSTN;
+AssertCompileSize(EHCI_FSTN, 8);
+
+/** @} */
+
+
+/**
+ * EHCI register operator.
+ */
+typedef struct EHCIOPREG
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t u32Value);
+} EHCIOPREG;
+
+
+/* EHCI Local stuff */
+#define EHCI_HCS_PARAMS_PORT_ROUTING_RULES RT_BIT(7)
+#define EHCI_HCS_PARAMS_PORT_POWER_CONTROL RT_BIT(4)
+#define EHCI_HCS_PARAMS_NDP_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2) | RT_BIT(3))
+
+/* controller may cache an isochronous data structure for an entire frame */
+#define EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING RT_BIT(7)
+#define EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING RT_BIT(2)
+#define EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST RT_BIT(1)
+#define EHCI_HCC_PARAMS_64BITS_ADDRESSING RT_BIT(0)
+
+/** @name Interrupt Enable Register bits (USBINTR)
+ * @{ */
+#define EHCI_INTR_ENABLE_THRESHOLD RT_BIT(0)
+#define EHCI_INTR_ENABLE_ERROR RT_BIT(1)
+#define EHCI_INTR_ENABLE_PORT_CHANGE RT_BIT(2)
+#define EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER RT_BIT(3)
+#define EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR RT_BIT(4)
+#define EHCI_INTR_ENABLE_ASYNC_ADVANCE RT_BIT(5)
+#define EHCI_INTR_ENABLE_MASK (EHCI_INTR_ENABLE_ASYNC_ADVANCE|EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR|EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER|EHCI_INTR_ENABLE_PORT_CHANGE|EHCI_INTR_ENABLE_ERROR|EHCI_INTR_ENABLE_THRESHOLD)
+/** @} */
+
+/** @name Configure Flag Register (CONFIGFLAG)
+ * @{ */
+#define EHCI_CONFIGFLAG_ROUTING RT_BIT(0)
+#define EHCI_CONFIGFLAG_MASK EHCI_CONFIGFLAG_ROUTING
+/** @} */
+
+/** @name Status Register (USBSTS)
+ * @{ */
+#define EHCI_STATUS_ASYNC_SCHED RT_BIT(15) /* RO */
+#define EHCI_STATUS_PERIOD_SCHED RT_BIT(14) /* RO */
+#define EHCI_STATUS_RECLAMATION RT_BIT(13) /* RO */
+#define EHCI_STATUS_HCHALTED RT_BIT(12) /* RO */
+#define EHCI_STATUS_INT_ON_ASYNC_ADV RT_BIT(5)
+#define EHCI_STATUS_HOST_SYSTEM_ERROR RT_BIT(4)
+#define EHCI_STATUS_FRAME_LIST_ROLLOVER RT_BIT(3)
+#define EHCI_STATUS_PORT_CHANGE_DETECT RT_BIT(2)
+#define EHCI_STATUS_ERROR_INT RT_BIT(1)
+#define EHCI_STATUS_THRESHOLD_INT RT_BIT(0)
+#define EHCI_STATUS_INTERRUPT_MASK (EHCI_STATUS_THRESHOLD_INT|EHCI_STATUS_ERROR_INT|EHCI_STATUS_PORT_CHANGE_DETECT|EHCI_STATUS_FRAME_LIST_ROLLOVER|EHCI_STATUS_HOST_SYSTEM_ERROR|EHCI_STATUS_INT_ON_ASYNC_ADV)
+/** @} */
+
+#define EHCI_PERIODIC_LIST_MASK UINT32_C(0xFFFFF000) /**< 4kb aligned */
+#define EHCI_ASYNC_LIST_MASK UINT32_C(0xFFFFFFE0) /**< 32-byte aligned */
+
+
+/** @name Port Status and Control Register bits (PORTSC)
+ * @{ */
+#define EHCI_PORT_CURRENT_CONNECT RT_BIT(0) /**< RO */
+#define EHCI_PORT_CONNECT_CHANGE RT_BIT(1)
+#define EHCI_PORT_PORT_ENABLED RT_BIT(2)
+#define EHCI_PORT_PORT_CHANGE RT_BIT(3)
+#define EHCI_PORT_OVER_CURRENT_ACTIVE RT_BIT(4) /**< RO */
+#define EHCI_PORT_OVER_CURRENT_CHANGE RT_BIT(5)
+#define EHCI_PORT_FORCE_PORT_RESUME RT_BIT(6)
+#define EHCI_PORT_SUSPEND RT_BIT(7)
+#define EHCI_PORT_RESET RT_BIT(8)
+#define EHCI_PORT_LINE_STATUS_MASK (RT_BIT(10) | RT_BIT(11)) /**< RO */
+#define EHCI_PORT_LINE_STATUS_SHIFT 10
+#define EHCI_PORT_POWER RT_BIT(12)
+#define EHCI_PORT_OWNER RT_BIT(13)
+#define EHCI_PORT_INDICATOR (RT_BIT(14) | RT_BIT(15))
+#define EHCI_PORT_TEST_CONTROL_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19))
+#define EHCI_PORT_TEST_CONTROL_SHIFT 16
+#define EHCI_PORT_WAKE_ON_CONNECT_ENABLE RT_BIT(20)
+#define EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE RT_BIT(21)
+#define EHCI_PORT_WAKE_OVER_CURRENT_ENABLE RT_BIT(22)
+#define EHCI_PORT_RESERVED (RT_BIT(9)|RT_BIT(23)|RT_BIT(24)|RT_BIT(25)|RT_BIT(26)|RT_BIT(27)|RT_BIT(28)|RT_BIT(29)|RT_BIT(30)|RT_BIT(31))
+
+#define EHCI_PORT_WAKE_MASK (EHCI_PORT_WAKE_ON_CONNECT_ENABLE|EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE|EHCI_PORT_WAKE_OVER_CURRENT_ENABLE)
+#define EHCI_PORT_CHANGE_MASK (EHCI_PORT_CONNECT_CHANGE|EHCI_PORT_PORT_CHANGE|EHCI_PORT_OVER_CURRENT_CHANGE)
+/** @} */
+
+/** @name Command Register bits (USBCMD)
+ * @{ */
+#define EHCI_CMD_RUN RT_BIT(0)
+#define EHCI_CMD_RESET RT_BIT(1)
+#define EHCI_CMD_FRAME_LIST_SIZE_MASK (RT_BIT(2) | RT_BIT(3))
+#define EHCI_CMD_FRAME_LIST_SIZE_SHIFT 2
+#define EHCI_CMD_PERIODIC_SCHED_ENABLE RT_BIT(4)
+#define EHCI_CMD_ASYNC_SCHED_ENABLE RT_BIT(5)
+#define EHCI_CMD_INT_ON_ADVANCE_DOORBELL RT_BIT(6)
+#define EHCI_CMD_SOFT_RESET RT_BIT(7) /**< optional */
+#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK (RT_BIT(8) | RT_BIT(9)) /**< optional */
+#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT 8
+#define EHCI_CMD_RESERVED RT_BIT(10)
+#define EHCI_CMD_ASYNC_SCHED_PARK_ENABLE RT_BIT(11) /**< optional */
+#define EHCI_CMD_RESERVED2 (RT_BIT(12) | RT_BIT(13) | RT_BIT(14) | RT_BIT(15))
+#define EHCI_CMD_INTERRUPT_THRESHOLD_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19) | RT_BIT(20) | RT_BIT(21) | RT_BIT(22) | RT_BIT(23))
+#define EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT 16
+#define EHCI_CMD_MASK (EHCI_CMD_INTERRUPT_THRESHOLD_MASK|EHCI_CMD_ASYNC_SCHED_PARK_ENABLE|EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK|EHCI_CMD_SOFT_RESET|EHCI_CMD_INT_ON_ADVANCE_DOORBELL|EHCI_CMD_ASYNC_SCHED_ENABLE|EHCI_CMD_PERIODIC_SCHED_ENABLE|EHCI_CMD_FRAME_LIST_SIZE_MASK|EHCI_CMD_RESET|EHCI_CMD_RUN)
+
+#define EHCI_DEFAULT_PERIODIC_LIST_SIZE 1024
+#define EHCI_DEFAULT_PERIODIC_LIST_MASK 0x3ff
+
+#define EHCI_FRINDEX_UFRAME_COUNT_MASK 0x7
+#define EHCI_FRINDEX_FRAME_INDEX_MASK EHCI_DEFAULT_PERIODIC_LIST_MASK
+#define EHCI_FRINDEX_FRAME_INDEX_SHIFT 3
+
+/** @} */
+
+/* Local EHCI definitions */
+#define EHCI_USB_RESET 0x00
+#define EHCI_USB_RESUME 0x40
+#define EHCI_USB_OPERATIONAL 0x80
+#define EHCI_USB_SUSPEND 0xc0
+
+#define EHCI_HARDWARE_TIMER_FREQ 8000 /**< 8000 hz = every 125 usec */
+#define EHCI_DEFAULT_TIMER_FREQ 1000
+#define EHCI_UFRAMES_PER_FRAME 8
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(VBOX_IN_EXTPACK_R0) && defined(RT_OS_SOLARIS)
+/* Dependency information for the native solaris loader. */
+extern "C" { char _depends_on[] = "vboxdrv VMMR0.r0"; }
+#endif
+
+#if defined(LOG_ENABLED) && defined(IN_RING3)
+static bool g_fLogControlEPs = false;
+static bool g_fLogInterruptEPs = false;
+#endif
+
+#ifdef IN_RING3
+/**
+ * SSM descriptor table for the EHCI structure.
+ */
+static SSMFIELD const g_aEhciFields[] =
+{
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY( EHCI, fBusStarted),
+ SSMFIELD_ENTRY_TERM()
+};
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+/* Update host controller state to reflect a device attach */
+static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp);
+
+static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd);
+static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate);
+#endif /* IN_RING3 */
+RT_C_DECLS_END
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ehciUpdateInterruptLocked(PPDMDEVINS pDevIns, PEHCI pThis, const char *msg)
+{
+ int level = 0;
+
+ if (pThis->intr_status & pThis->intr)
+ level = 1;
+
+ PDMDevHlpPCISetIrq(pDevIns, 0, level);
+ if (level)
+ {
+ uint32_t val = pThis->intr_status & pThis->intr;
+
+ Log2Func(("Fired off interrupt %#010x - INT=%d ERR=%d PCD=%d FLR=%d HSE=%d IAA=%d - %s\n",
+ val,
+ !!(val & EHCI_STATUS_THRESHOLD_INT),
+ !!(val & EHCI_STATUS_ERROR_INT),
+ !!(val & EHCI_STATUS_PORT_CHANGE_DETECT),
+ !!(val & EHCI_STATUS_FRAME_LIST_ROLLOVER),
+ !!(val & EHCI_STATUS_HOST_SYSTEM_ERROR),
+ !!(val & EHCI_STATUS_INT_ON_ASYNC_ADV),
+ msg));
+ RT_NOREF(val, msg);
+
+ /* host controller must clear the EHCI_CMD_INT_ON_ADVANCE_DOORBELL bit after setting it in the status register */
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ ASMAtomicAndU32(&pThis->cmd, ~EHCI_CMD_INT_ON_ADVANCE_DOORBELL);
+
+ }
+ else
+ Log2Func(("cleared interrupt\n"));
+}
+
+/**
+ * Set an interrupt, use the wrapper ehciSetInterrupt.
+ */
+DECLINLINE(int) ehciSetInterruptInt(PPDMDEVINS pDevIns, PEHCI pThis, int rcBusy, uint32_t intr, const char *msg)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if ( (pThis->intr_status & intr) != intr )
+ {
+ ASMAtomicOrU32(&pThis->intr_status, intr);
+ ehciUpdateInterruptLocked(pDevIns, pThis, msg);
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return rc;
+}
+
+/**
+ * Set an interrupt wrapper macro for logging purposes.
+ */
+#define ehciSetInterrupt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr) \
+ ehciSetInterruptInt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr, #a_fIntr)
+#define ehciR3SetInterrupt(a_pDevIns, a_pEhci, a_fIntr) \
+ ehciSetInterruptInt(a_pDevIns, a_pEhci, VERR_IGNORED, a_fIntr, #a_fIntr)
+
+#ifdef IN_RING3
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ehciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThisCC->RootHub.IRhPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->RootHub.ILeds);
+ return NULL;
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ehciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.ILeds);
+ if (iLUN == 0)
+ {
+ *ppLed = &pThisCC->RootHub.Led;
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * Get the number of avialable ports in the hub.
+ *
+ * @returns The number of ports available.
+ * @param pInterface Pointer to this structure.
+ * @param pAvailable Bitmap indicating the available ports. Set bit == available port.
+ */
+static DECLCALLBACK(unsigned) ehciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ memset(pAvailable, 0, sizeof(*pAvailable));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ unsigned cPorts = 0;
+ for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (!pThisCC->RootHub.aPorts[iPort].fAttached)
+ {
+ cPorts++;
+ ASMBitSet(pAvailable, iPort + 1);
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return cPorts;
+}
+
+
+/**
+ * Gets the supported USB versions.
+ *
+ * @returns The mask of supported USB versions.
+ * @param pInterface Pointer to this structure.
+ */
+static DECLCALLBACK(uint32_t) ehciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ return VUSB_STDVER_20;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */
+static DECLCALLBACK(int) ehciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(!pThisCC->RootHub.aPorts[uPort].fAttached);
+ Assert(enmSpeed == VUSB_SPEED_HIGH); RT_NOREF(enmSpeed); /* Only HS devices should end up here! */
+
+ /*
+ * Attach it.
+ */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_OWNER); /* not attached to a companion controller */
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ pThisCC->RootHub.aPorts[uPort].fAttached = true;
+ ehciR3PortPower(pThis, pThisCC, uPort, 1 /* power on */);
+
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device is being detached from a port in the roothub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param uPort The port number assigned to the device.
+ */
+static DECLCALLBACK(void) ehciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(pThisCC->RootHub.aPorts[uPort].fAttached);
+
+ /*
+ * Detach it.
+ */
+ pThisCC->RootHub.aPorts[uPort].fAttached = false;
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_CURRENT_CONNECT);
+ if (pThis->RootHub.aPorts[uPort].fReg & EHCI_PORT_PORT_ENABLED)
+ {
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_PORT_ENABLED);
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE | EHCI_PORT_PORT_CHANGE);
+ }
+ else
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE);
+
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+}
+
+
+/**
+ * One of the roothub devices has completed its reset operation.
+ *
+ * Currently, we don't think anything is required to be done here
+ * so it's just a stub for forcing async resetting of the devices
+ * during a root hub reset.
+ *
+ * @param pDev The root hub device.
+ * @param uPort The port number of the device on the roothub being resetted.
+ * @param rc The result of the operation.
+ * @param pvUser Pointer to the controller.
+ */
+static DECLCALLBACK(void) ehciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser)
+{
+ LogRel(("EHCI: root hub reset completed with %Rrc\n", rc));
+ RT_NOREF(pDev, uPort, rc, pvUser);
+}
+
+
+/**
+ * Does a software or hardware reset of the controller.
+ *
+ * This is called in response to setting HcCommandStatus.HCR, hardware reset,
+ * and device construction.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared EHCI instance data.
+ * @param pThisCC The ring-3 EHCI instance data.
+ * @param fNewMode The new mode of operation. This is UsbSuspend if it's a
+ * software reset, and UsbReset if it's a hardware reset / cold boot.
+ * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub.
+ * This is really a just a hack for the non-working linux device reset.
+ * Linux has this feature called 'logical disconnect' if device reset fails
+ * which prevents us from doing resets when the guest asks for it - the guest
+ * will get confused when the device seems to be reconnected everytime it tries
+ * to reset it. But if we're at hardware reset time, we can allow a device to
+ * be 'reconnected' without upsetting the guest.
+ *
+ * @remark This hasn't got anything to do with software setting the mode to UsbReset.
+ */
+static void ehciR3DoReset(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux)
+{
+ LogFunc(("%s reset%s\n", fNewMode == EHCI_USB_RESET ? "hardware" : "software",
+ fResetOnLinux ? " (reset on linux)" : ""));
+
+ /*
+ * Cancel all outstanding URBs.
+ *
+ * We can't, and won't, deal with URBs until we're moved out of the
+ * suspend/reset state. Also, a real HC isn't going to send anything
+ * any more when a reset has been signaled.
+ *
+ * This must be done on the framer thread to avoid race conditions.
+ */
+ pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn);
+
+ /*
+ * Reset the hardware registers.
+ */
+ /** @todo other differences between hardware reset and VM reset? */
+
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING)
+ pThis->cmd = 0x80000 | EHCI_CMD_ASYNC_SCHED_PARK_ENABLE | (3 << EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT);
+ else
+ pThis->cmd = 0x80000;
+
+ pThis->intr_status = EHCI_STATUS_HCHALTED;
+ pThis->intr = 0;
+ pThis->frame_idx = 0;
+ pThis->ds_segment = 0;
+ pThis->periodic_list_base = 0; /* undefined */
+ pThis->async_list_base = 0; /* undefined */
+ pThis->config = 0;
+ pThis->uIrqInterval = (pThis->intr_status & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT;
+
+ /* We have to update interrupts as the IRQ may need to be cleared. */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CsIrq, rcLock);
+
+ ehciUpdateInterruptLocked(pDevIns, pThis, "ehciR3DoReset");
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+
+ if (fNewMode == EHCI_USB_RESET)
+ {
+ /* Only a hardware reset reinits the port registers */
+ for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++)
+ {
+ if (pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL)
+ pThis->RootHub.aPorts[i].fReg = EHCI_PORT_OWNER;
+ else
+ pThis->RootHub.aPorts[i].fReg = EHCI_PORT_POWER | EHCI_PORT_OWNER;
+ }
+ }
+/** @todo Shouldn't we stop the SOF timer at this point? */
+
+ /*
+ * If this is a hardware reset, we will initialize the root hub too.
+ * Software resets doesn't do this according to the specs.
+ * (It's not possible to have device connected at the time of the
+ * device construction, so nothing to worry about there.)
+ */
+ if (fNewMode == EHCI_USB_RESET)
+ {
+ pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux);
+
+ /*
+ * Reattach the devices.
+ */
+ for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++)
+ {
+ bool fAttached = pThisCC->RootHub.aPorts[i].fAttached;
+ pThisCC->RootHub.aPorts[i].fAttached = false;
+
+ if (fAttached)
+ ehciR3RhAttach(&pThisCC->RootHub.IRhPort, i+1, VUSB_SPEED_HIGH);
+ }
+ }
+}
+
+/**
+ * Reset the root hub.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this structure.
+ * @param fResetOnLinux This is used to indicate whether we're at VM reset time and
+ * can do real resets or if we're at any other time where that
+ * isn't such a good idea.
+ * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion!
+ * @thread EMT
+ */
+static DECLCALLBACK(int) ehciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFunc(("fResetOnLinux=%d\n", fResetOnLinux));
+
+ /* Soft reset first */
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */);
+
+ /*
+ * We're pretending to _reattach_ the devices without resetting them.
+ * Except, during VM reset where we use the opportunity to do a proper
+ * reset before the guest comes along and expects things.
+ *
+ * However, it's very very likely that we're not doing the right thing
+ * here when end up here on request from the guest (USB Reset state).
+ * The docs talks about root hub resetting, however what exact behaviour
+ * in terms of root hub status and changed bits, and HC interrupts aren't
+ * stated clearly. IF we get trouble and see the guest doing "USB Resets"
+ * we will have to look into this. For the time being we stick with simple.
+ */
+ for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (pThisCC->RootHub.aPorts[iPort].fAttached)
+ {
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ if (fResetOnLinux)
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux,
+ ehciR3RhResetDoneOneDev, pThis, pVM);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads physical memory.
+ */
+DECLINLINE(void) ehciPhysRead(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Reads physical memory - metadata.
+ */
+DECLINLINE(void) ehciPhysReadMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ehciPhysWrite(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ehciPhysWriteMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Read an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ehciGetDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, uint32_t *pau32s, int c32s)
+{
+ ehciPhysReadMeta(pDevIns, Addr, pau32s, c32s * sizeof(uint32_t));
+# ifndef RT_LITTLE_ENDIAN
+ for(int i = 0; i < c32s; i++)
+ pau32s[i] = RT_H2LE_U32(pau32s[i]);
+# endif
+}
+
+
+/**
+ * Write an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ehciPutDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, const uint32_t *pau32s, int cu32s)
+{
+# ifdef RT_LITTLE_ENDIAN
+ ehciPhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2);
+# else
+ for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s))
+ {
+ uint32_t u32Tmp = RT_H2LE_U32(*pau32s);
+ ehciPhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp));
+ }
+# endif
+}
+
+
+DECLINLINE(void) ehciR3ReadFrameListPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_FRAME_LIST_PTR *pFrameList)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pFrameList, sizeof(*pFrameList) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadTDPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_TD_PTR *pTD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pTD, sizeof(*pTD) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD_PAD pPItd)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pPItd, sizeof(EHCI_ITD) >> 2);
+ pPItd->pad.Pointer = 0xFFFFF; /* Direct accesses at the last page under 4GB (ROM). */
+}
+
+DECLINLINE(void) ehciR3ReadSitd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_SITD pSitd)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pSitd, sizeof(*pSitd) >> 2);
+}
+
+DECLINLINE(void) ehciR3WriteItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD pItd)
+{
+ /** @todo might need to be careful about write order in async io thread */
+ /*
+ * Only write to the fields the controller is allowed to write to,
+ * namely the eight double words coming after the next link pointer.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_ITD, Transaction[0]);
+ uint32_t offDWordsWrite = offWrite / sizeof(uint32_t);
+ Assert(!(offWrite % sizeof(uint32_t)));
+
+ ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pItd + offDWordsWrite, (sizeof(*pItd) >> 2) - offDWordsWrite);
+}
+
+DECLINLINE(void) ehciR3ReadQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQHD, sizeof(*pQHD) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQTD, sizeof(*pQTD) >> 2);
+}
+
+DECLINLINE(void) ehciR3WriteQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD)
+{
+ /** @todo might need to be careful about write order in async io thread */
+ /*
+ * Only write to the fields the controller is allowed to write to,
+ * namely the two double words coming after the alternate next QTD pointer.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_QTD, Token.u32);
+ uint32_t offDWordsWrite = offWrite / sizeof(uint32_t);
+ Assert(!(offWrite % sizeof(uint32_t)));
+
+ ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pQTD + offDWordsWrite, (sizeof(*pQTD) >> 2) - offDWordsWrite);
+}
+
+
+/**
+ * Updates the QHD in guest memory only updating portions of the QHD the controller
+ * is allowed to write to.
+ *
+ * @param pDevIns The device instance.
+ * @param GCPhys Physical guest address of the QHD.
+ * @param pQHD The QHD to update the guest memory with.
+ */
+DECLINLINE(void) ehciR3UpdateQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD)
+{
+ /*
+ * Only update members starting from the current QTD pointer, everything
+ * before is readonly for the controller and the guest might have updated it
+ * behind our backs already.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_QHD, CurrQTD);
+ ehciPhysWriteMeta(pDevIns, GCPhys + offWrite, (uint8_t *)pQHD + offWrite, sizeof(EHCI_QHD) - offWrite);
+}
+
+#ifdef LOG_ENABLED
+
+# if 0 /* unused */
+/**
+ * Dumps a TD queue. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpTdQueue(PEHCI pThis, RTGCPHYS GCPhysHead, const char *pszMsg)
+{
+ RT_NOREF(pThis, GCPhysHead, pszMsg);
+ AssertFailed();
+}
+# endif /* unused */
+
+/**
+ * Dumps an SITD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpSITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RT_NOREF(pDevIns, GCPhysHead, fList);
+ AssertFailed();
+}
+
+/**
+ * Dumps an FSTN list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpFSTN(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RT_NOREF(pDevIns, GCPhysHead, fList);
+ AssertFailed();
+}
+
+#ifdef LOG_ENABLED
+static const char *ehciPID2Str(uint32_t PID)
+{
+ switch(PID)
+ {
+ case EHCI_QTD_PID_OUT:
+ return "OUT";
+ case EHCI_QTD_PID_IN:
+ return "IN";
+ case EHCI_QTD_PID_SETUP:
+ return "SETUP";
+ default:
+ return "Invalid PID!";
+ }
+}
+#endif
+
+DECLINLINE(void) ehciR3DumpSingleQTD(RTGCPHYS GCPhys, PEHCI_QTD pQtd, const char *pszPrefix)
+{
+ if (pQtd->Token.Bits.Active)
+ {
+ Log2((" QTD%s: %RGp={", pszPrefix, GCPhys));
+ Log2((" Length=%x IOC=%d DT=%d CErr=%d C_Page=%d Status=%x PID=%s}\n", pQtd->Token.Bits.Length, pQtd->Token.Bits.IOC, pQtd->Token.Bits.DataToggle, pQtd->Token.Bits.ErrorCount, pQtd->Token.Bits.CurrentPage, pQtd->Token.u32 & 0xff, ehciPID2Str(pQtd->Token.Bits.PID)));
+ Log2((" QTD: %RGp={", GCPhys));
+ Log2((" Buf0=%x Offset=%x Buf1=%x Buf2=%x Buf3=%x Buf4=%x}\n", pQtd->Buffer.Buffer[0].Pointer, pQtd->Buffer.Offset.Offset, pQtd->Buffer.Buffer[1].Pointer, pQtd->Buffer.Buffer[2].Pointer, pQtd->Buffer.Buffer[3].Pointer, pQtd->Buffer.Buffer[4].Pointer));
+ Log2((" QTD: %RGp={", GCPhys));
+ Log2((" Next=%RGp T=%d AltNext=%RGp AltT=%d\n", (RTGCPHYS)pQtd->Next.Pointer << EHCI_TD_PTR_SHIFT, pQtd->Next.Terminate, (RTGCPHYS)pQtd->AltNext.Pointer << EHCI_TD_PTR_SHIFT, pQtd->AltNext.Terminate));
+ }
+ else
+ Log2((" QTD%s: %RGp={Not Active}\n", pszPrefix, GCPhys));
+}
+
+/**
+ * Dumps a QTD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ for (;;)
+ {
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD */
+ ehciR3ReadQTD(pDevIns, GCPhys, &qtd);
+ ehciR3DumpSingleQTD(GCPhys, &qtd, "");
+
+ if (!fList || qtd.Next.Terminate || !qtd.Next.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)qtd.Next.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* detect if list item is self-cycled. */
+
+ GCPhys = qtd.Next.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (GCPhys == GCPhysHead)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+
+ /* alternative pointers */
+ GCPhys = GCPhysHead;
+ iterations = 0;
+
+ for (;;)
+ {
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD */
+ ehciR3ReadQTD(pDevIns, GCPhys, &qtd);
+ if (GCPhys != GCPhysHead)
+ ehciR3DumpSingleQTD(GCPhys, &qtd, "-A");
+
+ if (!fList || qtd.AltNext.Terminate || !qtd.AltNext.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* detect if list item is self-cycled. */
+
+ GCPhys = qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (GCPhys == GCPhysHead)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+}
+
+/**
+ * Dumps a QHD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpQH(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ EHCI_QHD qhd;
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ Log2((" QH: %RGp={", GCPhys));
+
+ /* Read the whole QHD */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+
+ Log2(("HorzLnk=%RGp Typ=%u T=%u Addr=%x EndPt=%x Speed=%x MaxSize=%x NAK=%d C=%d RH=%d I=%d}\n",
+ ((RTGCPHYS)qhd.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Next.Type, qhd.Next.Terminate,
+ qhd.Characteristics.DeviceAddress, qhd.Characteristics.EndPt, qhd.Characteristics.EndPtSpeed,
+ qhd.Characteristics.MaxLength, qhd.Characteristics.NakCountReload, qhd.Characteristics.ControlEPFlag,
+ qhd.Characteristics.HeadReclamation, qhd.Characteristics.InActiveNext));
+ Log2((" Caps: Port=%x Hub=%x Multi=%x CMask=%x SMask=%x\n", qhd.Caps.Port, qhd.Caps.HubAddress,
+ qhd.Caps.Mult, qhd.Caps.CMask, qhd.Caps.SMask));
+ Log2((" CurrPtr=%RGp Next=%RGp T=%d AltNext=%RGp T=%d\n",
+ ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT),
+ ((RTGCPHYS)qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.Next.Terminate,
+ ((RTGCPHYS)qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.AltNext.Terminate));
+ ehciR3DumpSingleQTD(qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qhd.Overlay.OrgQTD, "");
+ ehciR3DumpQTD(pDevIns, qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT, true);
+
+ Assert(qhd.Next.Pointer || qhd.Next.Terminate);
+ if ( !fList
+ || qhd.Next.Terminate
+ || !qhd.Next.Pointer)
+ return;
+
+ for (;;)
+ {
+ /* Read the next pointer */
+ EHCI_TD_PTR ptr;
+ ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr);
+
+ AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type));
+ Assert(ptr.Pointer || ptr.Terminate);
+ if ( ptr.Terminate
+ || !ptr.Pointer
+ || ptr.Type != EHCI_DESCRIPTOR_QH)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* Looping on itself. Bad guest! */
+
+ GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT;
+ if (GCPhys == GCPhysHead)
+ break; /* break the loop */
+
+ ehciR3DumpQH(pDevIns, GCPhys, false);
+
+ /* And again, if we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 64)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+}
+
+/**
+ * Dumps an ITD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ for (;;)
+ {
+ Log2((" ITD: %RGp={", GCPhys));
+
+ /* Read the whole ITD */
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+ ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd);
+
+ Log2(("Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi));
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ Log2(("T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC,
+ pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer << EHCI_BUFFER_PTR_SHIFT));
+ }
+ }
+ Assert(pItd->Next.Pointer || pItd->Next.Terminate);
+ if (!fList || pItd->Next.Terminate || !pItd->Next.Pointer)
+ break;
+
+ /* And again, if we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+
+ /* next */
+ GCPhys = pItd->Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+}
+
+/**
+ * Dumps a periodic list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpPeriodicList(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, const char *pszMsg, bool fTDs)
+{
+ RT_NOREF(fTDs);
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ if (pszMsg)
+ Log2(("%s:", pszMsg));
+
+ for (;;)
+ {
+ EHCI_FRAME_LIST_PTR FramePtr;
+
+ /* ED */
+ Log2((" %RGp={", GCPhys));
+ if (!GCPhys)
+ {
+ Log2(("END}\n"));
+ return;
+ }
+
+ /* Frame list pointer */
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+ if (FramePtr.Terminate)
+ {
+ Log2(("[Terminate]}\n"));
+ }
+ else
+ {
+ RTGCPHYS GCPhys1 = (RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT;
+ switch (FramePtr.Type)
+ {
+ case EHCI_DESCRIPTOR_ITD:
+ Log2(("[ITD]}\n"));
+ ehciR3DumpITD(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_SITD:
+ Log2(("[SITD]}\n"));
+ ehciR3DumpSITD(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_QH:
+ Log2(("[QH]}\n"));
+ ehciR3DumpQH(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_FSTN:
+ Log2(("[FSTN]}\n"));
+ ehciR3DumpFSTN(pDevIns, GCPhys1, false);
+ break;
+ }
+ }
+
+ /* Same old. If we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+
+ /* next */
+ GCPhys = GCPhys + sizeof(FramePtr);
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+
+DECLINLINE(int) ehciR3InFlightFindFree(PEHCICC pThisCC, const int iStart)
+{
+ unsigned i = iStart;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight)) {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ i++;
+ }
+ i = iStart;
+ while (i-- > 0) {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+ * Record an in-flight TD.
+ *
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @param pUrb The URB.
+ */
+static void ehciR3InFlightAdd(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD, PVUSBURB pUrb)
+{
+ int i = ehciR3InFlightFindFree(pThisCC, (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight));
+ if (i >= 0)
+ {
+#ifdef LOG_ENABLED
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+#endif
+ pThisCC->aInFlight[i].GCPhysTD = GCPhysTD;
+ pThisCC->aInFlight[i].pUrb = pUrb;
+ pThisCC->cInFlight++;
+ return;
+ }
+ AssertMsgFailed(("Out of space cInFlight=%d!\n", pThisCC->cInFlight));
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Record in-flight TDs for an URB.
+ *
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static void ehciR3InFlightAddUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ ehciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb);
+}
+
+
+/**
+ * Finds a in-flight TD.
+ *
+ * @returns Index of the record.
+ * @returns -1 if not found.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @remark This has to be fast.
+ */
+static int ehciR3InFlightFind(PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ unsigned cLeft = pThisCC->cInFlight;
+ unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight);
+ const int iLast = i;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight)) {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ i++;
+ }
+ i = iLast;
+ while (i-- > 0) {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ }
+ return -1;
+}
+
+
+/**
+ * Checks if a TD is in-flight.
+ *
+ * @returns true if in flight, false if not.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ehciR3IsTdInFlight(PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ return ehciR3InFlightFind(pThisCC, GCPhysTD) >= 0;
+}
+
+
+/**
+ * Removes a in-flight TD.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ehciR3InFlightRemove(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ int i = ehciR3InFlightFind(pThisCC, GCPhysTD);
+ if (i >= 0)
+ {
+#ifdef LOG_ENABLED
+ const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo;
+#else
+ const int cFramesInFlight = 0;
+#endif
+ Log2Func(("reaping TD=%RGp %d frames (%#010x-%#010x)\n",
+ GCPhysTD, cFramesInFlight, pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber));
+ pThisCC->aInFlight[i].GCPhysTD = 0;
+ pThisCC->aInFlight[i].pUrb = NULL;
+ pThisCC->cInFlight--;
+ return cFramesInFlight;
+ }
+ AssertMsgFailed(("TD %RGp is not in flight\n", GCPhysTD));
+ RT_NOREF(pThis);
+ return -1;
+}
+
+
+/**
+ * Removes all TDs associated with a URB from the in-flight tracking.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static int ehciR3InFlightRemoveUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ int cFramesInFlight = ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr);
+ if (pUrb->pHci->cTds > 1)
+ {
+ for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++)
+ if (ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0)
+ cFramesInFlight = -1;
+ }
+ return cFramesInFlight;
+}
+
+
+/**
+ * Checks that the transport descriptors associated with the URB
+ * hasn't been changed in any way indicating that they may have been canceled.
+ *
+ * This rountine also updates the TD copies contained within the URB.
+ *
+ * @returns true if the URB has been canceled, otherwise false.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb The URB in question.
+ * @param pItd The ITD pointer.
+ */
+static bool ehciR3ItdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_ITD pItd)
+{
+ RT_NOREF(pThisCC);
+ Assert(pItd);
+ if (!pUrb)
+ return true;
+
+ PEHCI_ITD pItdCopy = (PEHCI_ITD)pUrb->paTds[0].TdCopy;
+
+ /* Check transactions */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++)
+ {
+ if ( pItd->Transaction[i].Length != pItdCopy->Transaction[i].Length
+ || pItd->Transaction[i].Offset != pItdCopy->Transaction[i].Offset
+ || pItd->Transaction[i].PG != pItdCopy->Transaction[i].PG
+ || pItd->Transaction[i].Active != pItdCopy->Transaction[i].Active)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+ }
+
+ /* Check misc characteristics */
+ if ( pItd->Buffer.Misc.DeviceAddress != pItdCopy->Buffer.Misc.DeviceAddress
+ || pItd->Buffer.Misc.DirectionIn != pItdCopy->Buffer.Misc.DirectionIn
+ || pItd->Buffer.Misc.EndPt != pItdCopy->Buffer.Misc.EndPt)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled (misc): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+
+ /* Check buffer pointers */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Buffer.Buffer); i++)
+ {
+ if (pItd->Buffer.Buffer[i].Pointer != pItdCopy->Buffer.Buffer[i].Pointer)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Checks that the transport descriptors associated with the URB
+ * hasn't been changed in any way indicating that they may have been canceled.
+ *
+ * This rountine also updates the TD copies contained within the URB.
+ *
+ * @returns true if the URB has been canceled, otherwise false.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb The URB in question.
+ * @param pQhd The QHD pointer
+ * @param pQtd The QTD pointer
+ */
+static bool ehciR3QhdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_QHD pQhd, PEHCI_QTD pQtd)
+{
+ RT_NOREF(pQhd, pThisCC);
+ Assert(pQhd && pQtd);
+ if ( !pUrb
+ || !ehciR3IsTdInFlight(pThisCC, pUrb->paTds[0].TdAddr))
+ return true;
+
+ PEHCI_QTD pQtdCopy = (PEHCI_QTD)pUrb->paTds[0].TdCopy;
+
+ if ( pQtd->Token.Bits.Length != pQtdCopy->Token.Bits.Length
+ || pQtd->Token.Bits.Active != pQtdCopy->Token.Bits.Active
+ || pQtd->Token.Bits.DataToggle != pQtdCopy->Token.Bits.DataToggle
+ || pQtd->Token.Bits.CurrentPage != pQtdCopy->Token.Bits.CurrentPage
+ || pQtd->Token.Bits.PID != pQtdCopy->Token.Bits.PID
+ || pQtd->Buffer.Offset.Offset != pQtdCopy->Buffer.Offset.Offset)
+ {
+ Log(("%s: ehciQtdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs);
+ return true;
+ }
+
+
+ /* Check buffer pointers */
+ for (unsigned i = 0; i < RT_ELEMENTS(pQtd->Buffer.Buffer); i++)
+ {
+ if (pQtd->Buffer.Buffer[i].Pointer != pQtdCopy->Buffer.Buffer[i].Pointer)
+ {
+ Log(("%s: ehciQtdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Set the ITD status bits acorresponding to the VUSB status code.
+ *
+ * @param enmStatus The VUSB status code.
+ * @param pItdStatus ITD status pointer
+ */
+static void ehciR3VUsbStatus2ItdStatus(VUSBSTATUS enmStatus, EHCI_ITD_TRANSACTION *pItdStatus)
+{
+ switch (enmStatus)
+ {
+ case VUSBSTATUS_OK:
+ pItdStatus->TransactError = 0;
+ pItdStatus->DataBufError = 0;
+ break; /* make sure error bits are cleared */
+ case VUSBSTATUS_STALL:
+ case VUSBSTATUS_DNR:
+ case VUSBSTATUS_CRC:
+ pItdStatus->TransactError = 1;
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ case VUSBSTATUS_DATA_OVERRUN:
+ pItdStatus->DataBufError = 1;
+ break;
+ case VUSBSTATUS_NOT_ACCESSED:
+ Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n"));
+ break; /* can't signal this other than setting the length to 0 */
+ default:
+ Log(("pUrb->enmStatus=%#x!!!\n", enmStatus));
+ break;;
+ }
+}
+
+/**
+ * Set the QTD status bits acorresponding to the VUSB status code.
+ *
+ * @param enmStatus The VUSB status code.
+ * @param pQtdStatus QTD status pointer
+ */
+static void ehciR3VUsbStatus2QtdStatus(VUSBSTATUS enmStatus, EHCI_QTD_TOKEN *pQtdStatus)
+{
+ /** @todo CERR */
+ switch (enmStatus)
+ {
+ case VUSBSTATUS_OK:
+ break; /* nothing to do */
+ case VUSBSTATUS_STALL:
+ pQtdStatus->Halted = 1;
+ pQtdStatus->Active = 0;
+ break; /* not an error! */
+ case VUSBSTATUS_DNR:
+ case VUSBSTATUS_CRC:
+ pQtdStatus->TransactError = 1;
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ case VUSBSTATUS_DATA_OVERRUN:
+ pQtdStatus->DataBufError = 1;
+ break;
+ case VUSBSTATUS_NOT_ACCESSED:
+ Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n"));
+ break; /* can't signal this */
+ default:
+ Log(("pUrb->enmStatus=%#x!!!\n", enmStatus));
+ break;;
+ }
+}
+
+
+/**
+ * Heuristic to determine the transfer type
+ *
+ * @returns transfer type
+ * @param pQhd Queue head pointer
+ */
+static VUSBXFERTYPE ehciR3QueryTransferType(PEHCI_QHD pQhd)
+{
+ /* If it's EP0, we know what it is. */
+ if (!pQhd->Characteristics.EndPt)
+ return VUSBXFERTYPE_CTRL;
+
+ /* Non-zero SMask implies interrupt transfer. */
+ if (pQhd->Caps.SMask)
+ return VUSBXFERTYPE_INTR;
+
+ /* For non-HS EPs, control endpoints are clearly marked. */
+ if ( pQhd->Characteristics.ControlEPFlag
+ && pQhd->Characteristics.EndPtSpeed != EHCI_QHD_EPT_SPEED_HIGH)
+ return VUSBXFERTYPE_CTRL;
+
+ /* If we still don't know, it's guesswork from now on. */
+
+ /* 64 likely indicates an interrupt transfer (see @bugref{8314})*/
+ if (pQhd->Characteristics.MaxLength == 64)
+ return VUSBXFERTYPE_INTR;
+
+ /* At this point we hope it's a bulk transfer with max packet size of 512. */
+ Assert(pQhd->Characteristics.MaxLength == 512);
+ return VUSBXFERTYPE_BULK;
+}
+
+/**
+ * Worker for ehciR3RhXferCompletion that handles the completion of
+ * a URB made up of isochronous TDs.
+ *
+ * In general, all URBs should have status OK.
+ */
+static void ehciR3RhXferCompleteITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ /* Read the whole ITD */
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+ ehciR3ReadItd(pDevIns, pUrb->paTds[0].TdAddr, &PaddedItd);
+
+ /*
+ * Check that the URB hasn't been canceled and then try unlink the TDs.
+ *
+ * We drop the URB if the ED is marked halted/skip ASSUMING that this
+ * means the HCD has canceled the URB.
+ *
+ * If we succeed here (i.e. not dropping the URB), the TdCopy members will
+ * be updated but not yet written. We will delay the writing till we're done
+ * with the data copying, buffer pointer advancing and error handling.
+ */
+ bool fHasBeenCanceled = false;
+ int cFmAge = ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+ if ( cFmAge < 0
+ || (fHasBeenCanceled = ehciR3ItdHasUrbBeenCanceled(pThisCC, pUrb, pItd))
+ )
+ {
+ Log(("%s: ehciR3RhXferCompleteITD: DROPPED {ITD=%RGp cTds=%d TD0=%RGp age %d} because:%s%s!!!\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge,
+ cFmAge < 0 ? " td not-in-flight" : "",
+ fHasBeenCanceled ? " td canceled" : ""));
+ NOREF(fHasBeenCanceled);
+ STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs);
+ return;
+ }
+
+ bool fIOC = false, fError = false;
+
+ /*
+ * Copy the data back (if IN operation) and update the TDs.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ {
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]);
+ if (pItd->Transaction[i].IOC)
+ fIOC = true;
+
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && ( pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_OK
+ || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_OVERRUN))
+ {
+ Assert(pItd->Transaction[i].Active);
+
+ if (pItd->Transaction[i].Active)
+ {
+ const unsigned pg = pItd->Transaction[i].PG;
+ const unsigned cb = pUrb->aIsocPkts[i].cb;
+ pItd->Transaction[i].Length = cb; /* Set the actual size. */
+ /* Copy data. */
+ if (cb)
+ {
+ uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i].off];
+
+ RTGCPHYS GCPhysBuf = (RTGCPHYS)pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ GCPhysBuf += pItd->Transaction[i].Offset;
+
+ /* If the transfer would cross page boundary, use the next sequential PG pointer
+ * for the second part (section 4.7.1).
+ */
+ if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE)
+ {
+ unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset;
+ unsigned cb2 = cb - cb1;
+
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb1);
+ if ((pg + 1) >= EHCI_NUM_ITD_PAGES)
+ LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on completion.\n", pg + 1, pUrb->paTds[0].TdAddr));
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb + cb1, cb2);
+ }
+ else
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb);
+
+ Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n"
+ "%.*Rhxd\n",
+ i, pUrb->aIsocPkts[i].off, cb, pb, pb - &pUrb->abData[0], cb, pb));
+ }
+ }
+ }
+ pItd->Transaction[i].Active = 0; /* transfer is now officially finished */
+ } /* for */
+ }
+ else
+ {
+ LogFunc(("Taking untested code path at line %d...\n", __LINE__));
+ /*
+ * Most status codes only apply to the individual packets.
+ *
+ * If we get a URB level error code of this kind, we'll distribute
+ * it to all the packages unless some other status is available for
+ * a package. This is a bit fuzzy, and we will get rid of this code
+ * before long!
+ */
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]);
+ if (pItd->Transaction[i].IOC)
+ fIOC = true;
+
+ pItd->Transaction[i].Active = 0; /* transfer is now officially finished */
+ }
+ }
+ fError = true;
+ }
+
+ /*
+ * Write back the modified TD.
+ */
+
+ Log(("%s: ehciR3RhXferCompleteITD: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp "
+ "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr,
+ pUrb->pHci->EdAddr,
+ pItd->Buffer.Buffer[pItd->Transaction[0].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[0].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[1].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[1].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[2].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[2].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[3].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[3].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[4].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[4].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[5].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[5].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[6].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[6].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[7].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[7].Length
+ ));
+ ehciR3WriteItd(pDevIns, pUrb->paTds[0].TdAddr, pItd);
+
+ /*
+ * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction.
+ * Both error and completion interrupts may be signaled at the same time (see Table 2.10).
+ */
+ if (fError)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT);
+ if (fIOC)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT);
+}
+
+
+/**
+ * Worker for ehciR3RhXferCompletion that handles the completion of
+ * a URB made up of queue heads/descriptors
+ */
+static void ehciR3RhXferCompleteQH(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ EHCI_QHD qhd;
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD & QTD */
+ ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd);
+ AssertMsg(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT),
+ ("Out of order completion %RGp != %RGp Endpoint=%#x\n", pUrb->paTds[0].TdAddr,
+ ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT), pUrb->EndPt));
+ ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd);
+
+ /*
+ * Check that the URB hasn't been canceled and then try unlink the TDs.
+ *
+ * We drop the URB if the ED is marked halted/skip ASSUMING that this
+ * means the HCD has canceled the URB.
+ *
+ * If we succeed here (i.e. not dropping the URB), the TdCopy members will
+ * be updated but not yet written. We will delay the writing till we're done
+ * with the data copying, buffer pointer advancing and error handling.
+ */
+ bool fHasBeenCanceled = false;
+ if ((fHasBeenCanceled = ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd)))
+ {
+ Log(("%s: ehciRhXferCompletionQH: DROPPED {qTD=%RGp cTds=%d TD0=%RGp} because:%s%s!!!\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr,
+ (pUrb->paTds[0].TdAddr != ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)) ? " curptr changed" : "",
+ fHasBeenCanceled ? " td canceled" : ""));
+ NOREF(fHasBeenCanceled);
+ STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs);
+
+ ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+ qtd.Token.Bits.Active = 0;
+ ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd);
+ return;
+ }
+ ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+
+ /* Update the status/error bits */
+ ehciR3VUsbStatus2QtdStatus(pUrb->enmStatus, &qtd.Token.Bits);
+
+ /*
+ * Write back IN buffers.
+ */
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && pUrb->cbData
+ && ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ )
+ )
+ {
+ unsigned curOffset = 0;
+ unsigned cbLeft = pUrb->cbData;
+
+ for (unsigned i=qtd.Token.Bits.CurrentPage;i<RT_ELEMENTS(qtd.Buffer.Buffer);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+ unsigned cbCurTransfer;
+
+ GCPhysBuf = qtd.Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ if (i == 0)
+ GCPhysBuf += qtd.Buffer.Offset.Offset;
+
+ cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK);
+ cbCurTransfer = RT_MIN(cbCurTransfer, cbLeft);
+
+ Log3Func(("packet data for page %d:\n"
+ "%.*Rhxd\n",
+ i,
+ cbCurTransfer, &pUrb->abData[curOffset]));
+
+ ehciPhysWrite(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer);
+ curOffset += cbCurTransfer;
+ cbLeft -= cbCurTransfer;
+
+ if (cbLeft == 0)
+ break;
+ Assert(cbLeft < qtd.Token.Bits.Length);
+ }
+ }
+
+ if ( pUrb->cbData
+ && ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ )
+ )
+ {
+ /* 3.5.3:
+ * This field specifies the total number of bytes to be moved
+ * with this transfer descriptor. This field is decremented by the number of bytes actually
+ * moved during the transaction, only on the successful completion of the transaction
+ */
+ Assert(qtd.Token.Bits.Length >= pUrb->cbData);
+ qtd.Token.Bits.Length -= pUrb->cbData;
+
+ /* Data was moved; toggle data toggle bit */
+ qtd.Token.Bits.DataToggle ^= 1;
+ }
+
+#ifdef LOG_ENABLED
+ ehciR3DumpSingleQTD(pUrb->paTds[0].TdAddr, &qtd, "");
+#endif
+ qtd.Token.Bits.Active = 0; /* transfer is now officially finished */
+
+ /*
+ * Write back the modified TD.
+ */
+ Log(("%s: ehciR3RhXferCompleteQH: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr,
+ pUrb->pHci->EdAddr));
+
+ ehciR3WriteQTD(pDevIns, pUrb->paTds[0].TdAddr, &qtd);
+
+ ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd);
+
+ /*
+ * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction.
+ * Both error and completion interrupts may be signaled at the same time (see Table 2.10).
+ */
+ if (EHCI_QTD_HAS_ERROR(&qtd.Token.Bits))
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT);
+
+ bool fIOC = false;
+ if (qtd.Token.Bits.IOC) {
+ fIOC = true;
+ Log2Func(("Interrupting, IOC set\n"));
+ } else if (qtd.Token.Bits.Length && (qtd.Token.Bits.PID == EHCI_QTD_PID_IN)) {
+ fIOC = true; /* See 4.10.8 */
+ Log2Func(("Interrupting, short IN packet\n"));
+ }
+ if (fIOC)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT);
+}
+
+
+/**
+ * Transfer completion callback routine.
+ *
+ * VUSB will call this when a transfer have been completed
+ * in a one or another way.
+ *
+ * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(void) ehciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ LogFlow(("%s: ehciR3RhXferCompletion: EdAddr=%RGp cTds=%d TdAddr0=%RGp\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr));
+ LogFlow(("%s: ehciR3RhXferCompletion: cbData=%x status=%x\n", pUrb->pszDesc, pUrb->cbData, pUrb->enmStatus));
+
+ Assert(pUrb->pHci->cTds == 1);
+
+ RTCritSectEnter(&pThisCC->CritSect);
+ pThisCC->fIdle = false; /* Mark as active */
+
+ switch (pUrb->paTds[0].TdType)
+ {
+ case EHCI_DESCRIPTOR_QH:
+ ehciR3RhXferCompleteQH(pDevIns, pThis, pThisCC, pUrb);
+ break;
+
+ case EHCI_DESCRIPTOR_ITD:
+ ehciR3RhXferCompleteITD(pDevIns, pThis, pThisCC, pUrb);
+ break;
+
+ case EHCI_DESCRIPTOR_SITD:
+ case EHCI_DESCRIPTOR_FSTN:
+ AssertFailed();
+ break;
+ }
+
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+ RTCritSectLeave(&pThisCC->CritSect);
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Worker for ehciR3RhXferError that handles the error case of
+ * a URB made up of queue heads/descriptors
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be retried.
+ * @param pDevIns The device instance.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb Pointer to the URB in question.
+ */
+static bool ehciR3RhXferErrorQH(PPDMDEVINS pDevIns, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ EHCI_QHD qhd;
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD & QTD */
+ ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd);
+ Assert(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT));
+ ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd);
+
+ /*
+ * Check if the TDs still are valid.
+ * This will make sure the TdCopy is up to date.
+ */
+ /** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */
+ if (ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd))
+ {
+ Log(("%s: ehciR3RhXferError: TdAddr0=%RGp canceled!\n", pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ return true;
+ }
+ return true;
+}
+
+/**
+ * Handle transfer errors.
+ *
+ * VUSB calls this when a transfer attempt failed. This function will respond
+ * indicating whether to retry or complete the URB with failure.
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be retried.
+ * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(bool) ehciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ bool fRetire = false;
+
+ RTCritSectEnter(&pThisCC->CritSect);
+ /*
+ * Don't retry on stall.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_STALL)
+ {
+ Log2(("%s: ehciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc));
+ fRetire = true;
+ }
+ else
+ {
+ switch (pUrb->paTds[0].TdType)
+ {
+ case EHCI_DESCRIPTOR_QH:
+ {
+ fRetire = ehciR3RhXferErrorQH(pDevIns, pThisCC, pUrb);
+ break;
+ }
+
+ /*
+ * Isochronous URBs can't be retried.
+ */
+ case EHCI_DESCRIPTOR_ITD:
+ case EHCI_DESCRIPTOR_SITD:
+ case EHCI_DESCRIPTOR_FSTN:
+ default:
+ fRetire = true;
+ break;
+ }
+ }
+
+ RTCritSectLeave(&pThisCC->CritSect);
+ return fRetire;
+}
+
+/**
+ * A worker for ehciR3ServiceQTD which submits the specified TD.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ */
+static bool ehciR3SubmitQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysQHD,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQTD, PEHCI_QTD pQtd, const unsigned iFrame)
+{
+ /*
+ * Determine the endpoint direction.
+ */
+ VUSBDIRECTION enmDir;
+ switch(pQtd->Token.Bits.PID)
+ {
+ case EHCI_QTD_PID_OUT:
+ enmDir = VUSBDIRECTION_OUT;
+ break;
+ case EHCI_QTD_PID_IN:
+ enmDir = VUSBDIRECTION_IN;
+ break;
+ case EHCI_QTD_PID_SETUP:
+ enmDir = VUSBDIRECTION_SETUP;
+ break;
+ default:
+ return false;
+ }
+
+ VUSBXFERTYPE enmType;
+
+ enmType = ehciR3QueryTransferType(pQhd);
+
+ pThisCC->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize the URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pQhd->Characteristics.DeviceAddress, VUSB_DEVICE_PORT_INVALID,
+ enmType, enmDir, pQtd->Token.Bits.Length, 1, NULL);
+ if (!pUrb)
+ /* retry later... */
+ return false;
+
+ pUrb->EndPt = pQhd->Characteristics.EndPt;
+ pUrb->fShortNotOk = (enmDir != VUSBDIRECTION_IN); /** @todo ??? */
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->cTds = 1;
+ pUrb->pHci->EdAddr = GCPhysQHD;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = iFrame;
+ pUrb->paTds[0].TdAddr = GCPhysQTD;
+ pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_QH;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pQtd));
+ memcpy(pUrb->paTds[0].TdCopy, pQtd, sizeof(*pQtd));
+#if 0 /* color the data */
+ memset(pUrb->abData, 0xfe, cbTotal);
+#endif
+
+ /* copy the data */
+ if ( pQtd->Token.Bits.Length
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ unsigned curOffset = 0;
+ unsigned cbTransfer = pQtd->Token.Bits.Length;
+
+ for (unsigned i=pQtd->Token.Bits.CurrentPage;i<RT_ELEMENTS(pQtd->Buffer.Buffer);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+ unsigned cbCurTransfer;
+
+ GCPhysBuf = pQtd->Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ if (i == 0)
+ GCPhysBuf += pQtd->Buffer.Offset.Offset;
+
+ cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK);
+ cbCurTransfer = RT_MIN(cbCurTransfer, cbTransfer);
+
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer);
+
+ Log3Func(("packet data:\n"
+ "%.*Rhxd\n",
+ cbCurTransfer, &pUrb->abData[curOffset]));
+
+ curOffset += cbCurTransfer;
+ cbTransfer -= cbCurTransfer;
+
+ if (cbTransfer == 0)
+ break;
+ Assert(cbTransfer < pQtd->Token.Bits.Length);
+ }
+ }
+
+ /*
+ * Submit the URB.
+ */
+ ehciR3InFlightAddUrb(pThis, pThisCC, pUrb);
+ Log(("%s: ehciSubmitQtd: QtdAddr=%RGp GCPhysQHD=%RGp cbData=%#x\n",
+ pUrb->pszDesc, GCPhysQTD, GCPhysQHD, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSect);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ RTCritSectEnter(&pThisCC->CritSect);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ LogFunc(("failed GCPhysQtd=%RGp GCPhysQHD=%RGp pUrb=%p!!\n",
+ GCPhysQTD, GCPhysQHD, pUrb));
+ ehciR3InFlightRemove(pThis, pThisCC, GCPhysQTD);
+
+ /* Also mark the QH as halted and inactive and write back the changes. */
+ pQhd->Overlay.OrgQTD.Token.Bits.Active = 0;
+ pQhd->Overlay.OrgQTD.Token.Bits.Halted = 1;
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+ return false;
+}
+
+/**
+ * A worker for ehciR3ServiceITD which submits the specified TD.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ */
+static bool ehciR3SubmitITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_ITD pItd, RTGCPHYS ITdAddr, const unsigned iFrame)
+{
+ /*
+ * Determine the endpoint direction.
+ */
+ VUSBDIRECTION enmDir;
+ if(pItd->Buffer.Misc.DirectionIn)
+ enmDir = VUSBDIRECTION_IN;
+ else
+ enmDir = VUSBDIRECTION_OUT;
+
+ /*
+ * Extract the packet sizes and calc the total URB size.
+ */
+ struct
+ {
+ uint16_t cb;
+ } aPkts[EHCI_NUM_ITD_TRANSACTIONS];
+
+ unsigned cPackets = 0;
+ uint32_t cbTotal = 0;
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ aPkts[cPackets].cb = pItd->Transaction[i].Length;
+ cbTotal += pItd->Transaction[i].Length;
+ cPackets++;
+ }
+ }
+ Assert(cbTotal <= 24576);
+
+ pThisCC->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize the URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pItd->Buffer.Misc.DeviceAddress, VUSB_DEVICE_PORT_INVALID,
+ VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL);
+ if (!pUrb)
+ /* retry later... */
+ return false;
+
+ pUrb->EndPt = pItd->Buffer.Misc.EndPt;
+ pUrb->fShortNotOk = false;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->cTds = 1;
+ pUrb->pHci->EdAddr = ITdAddr;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = iFrame;
+ pUrb->paTds[0].TdAddr = ITdAddr;
+ pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_ITD;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pItd));
+ memcpy(pUrb->paTds[0].TdCopy, pItd, sizeof(*pItd));
+#if 0 /* color the data */
+ memset(pUrb->abData, 0xfe, cbTotal);
+#endif
+
+ /* copy the data */
+ if ( cbTotal
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ unsigned curOffset = 0;
+
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+
+ if (pItd->Transaction[i].Active)
+ {
+ const unsigned pg = pItd->Transaction[i].PG;
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ GCPhysBuf += pItd->Transaction[i].Offset;
+
+ /* If the transfer would cross page boundary, use the next sequential PG pointer
+ * for the second part (section 4.7.1).
+ */
+ if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE)
+ {
+ unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset;
+ unsigned cb2 = pItd->Transaction[i].Length - cb1;
+
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cb1);
+ if ((pg + 1) >= EHCI_NUM_ITD_PAGES)
+ LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on submit.\n", pg + 1, pUrb->paTds[0].TdAddr));
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset + cb1], cb2);
+ }
+ else
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], pItd->Transaction[i].Length);
+
+ curOffset += pItd->Transaction[i].Length;
+ }
+ }
+ }
+
+ /* setup the packets */
+ pUrb->cIsocPkts = cPackets;
+ unsigned off = 0;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
+ pUrb->aIsocPkts[i].off = off;
+ off += pUrb->aIsocPkts[i].cb = aPkts[i].cb;
+ }
+ Assert(off == cbTotal);
+
+ /*
+ * Submit the URB.
+ */
+ ehciR3InFlightAddUrb(pThis, pThisCC, pUrb);
+ Log(("%s: ehciR3SubmitITD: cbData=%#x cIsocPkts=%d TdAddr=%RGp (%#x)\n",
+ pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, ITdAddr, iFrame));
+ RTCritSectLeave(&pThisCC->CritSect);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ RTCritSectEnter(&pThisCC->CritSect);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ LogFunc(("failed pUrb=%p cbData=%#x cTds=%d ITdAddr0=%RGp - rc=%Rrc\n",
+ pUrb, cbTotal, 1, ITdAddr, rc));
+ ehciR3InFlightRemove(pThis, pThisCC, ITdAddr);
+ return false;
+}
+
+
+/**
+ * Services an ITD list (only for high-speed isochronous endpoints; all others use queues)
+ *
+ * An ITD can contain up to 8 transactions, which are all processed within a single frame.
+ * Note that FRINDEX includes the micro-frame number, but only bits [12:3] are used as an
+ * index into the periodic frame list (see 4.7.1).
+ */
+static void ehciR3ServiceITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(enmServiceType);
+ bool fAnyActive = false;
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+
+ if (ehciR3IsTdInFlight(pThisCC, GCPhys))
+ return;
+
+ /* Read the whole ITD */
+ ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd);
+
+ Log2((" ITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", GCPhys, pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi));
+
+ /* Some basic checks */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ fAnyActive = true;
+ if (pItd->Transaction[i].PG >= EHCI_NUM_ITD_PAGES)
+ {
+ /* Using out of range PG value (7) yields undefined behavior. We will attempt
+ * the last page below 4GB (which is ROM, not writable).
+ */
+ LogRelMax(10, ("EHCI: Illegal page value %d in iTD at %RGp.\n", pItd->Transaction[i].PG, (RTGCPHYS)GCPhys));
+ }
+
+ Log2((" T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC,
+ pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer));
+ }
+ }
+ /* We can't service one transaction every 125 usec, so we'll handle all 8 of them at once. */
+ if (fAnyActive)
+ ehciR3SubmitITD(pDevIns, pThis, pThisCC, pItd, GCPhys, iFrame);
+ else
+ Log2((" ITD not active, skipping.\n"));
+}
+
+/**
+ * Services an SITD list
+ */
+static void ehciR3ServiceSITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(pThis, pThisCC, enmServiceType, iFrame);
+
+ /* Read the whole SITD */
+ EHCI_SITD sitd;
+ ehciR3ReadSitd(pDevIns, GCPhys, &sitd);
+
+ Log2((" SITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x}\n", GCPhys, sitd.Address.DeviceAddress, sitd.Address.EndPt, (sitd.Address.DirectionIn) ? "in" : "out", sitd.Transfer.Length));
+
+ if (sitd.Transfer.Active)
+ AssertMsgFailed(("SITD lists not implemented; active SITD should never occur!\n"));
+ else
+ Log2((" SITD not active, skipping.\n"));
+}
+
+/**
+ * Copies the currently active QTD to the QH overlay area
+ */
+static void ehciR3QHSetupOverlay(PPDMDEVINS pDevIns, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd, RTGCPHYS GCPhysQTD)
+{
+ bool fDataToggle = pQhd->Overlay.OrgQTD.Token.Bits.DataToggle;
+
+ Assert(pQtd->Token.Bits.Active);
+
+ Log2Func(("current pointer %RGp old %RGp\n", GCPhysQTD, ((RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)));
+ pQhd->CurrQTD.Pointer = GCPhysQTD >> EHCI_TD_PTR_SHIFT;
+ pQhd->CurrQTD.Reserved = 0;
+ pQhd->Overlay.OrgQTD = *pQtd;
+ /* All fields except those below are copied from the QTD; see 4.10.2 */
+ if (pQhd->Characteristics.DataToggle)
+ pQhd->Overlay.OrgQTD.Token.Bits.DataToggle = fDataToggle; /* Preserve data toggle bit in the queue head */
+
+ pQhd->Overlay.Status.Buffer1.CProgMask = 0;
+ pQhd->Overlay.Status.Buffer2.FrameTag = 0;
+ pQhd->Overlay.Status.AltNextQTD.NakCnt = pQhd->Characteristics.NakCountReload;
+ /* Note: ping state not changed if it's a high-speed device */
+
+ /* Save the current QTD to the overlay area */
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+}
+
+/**
+ * Updates the currently active QTD to the QH overlay area
+ */
+static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd)
+{
+ Assert(!pQtd->Token.Bits.Active);
+ pQhd->Overlay.OrgQTD = *pQtd;
+ if (!pQhd->Overlay.OrgQTD.Next.Terminate)
+ {
+ EHCI_QTD qtdNext;
+ RTGCPHYS GCPhysNextQTD = pQhd->Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (ehciR3IsTdInFlight(pThisCC, GCPhysNextQTD))
+ {
+ /* Read the whole QTD */
+ ehciR3ReadQTD(pDevIns, GCPhysNextQTD, &qtdNext);
+ if (qtdNext.Token.Bits.Active)
+ {
+ ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtdNext, GCPhysNextQTD);
+ return;
+ }
+ else
+ {
+ /* Td has been cancelled! */
+ LogFunc(("in-flight qTD %RGp has been cancelled! (active=%d T=%d)\n", GCPhysNextQTD, qtdNext.Token.Bits.Active, pQhd->Overlay.OrgQTD.Next.Terminate));
+ /** @todo we don't properly cancel the URB; it will remain active on the host.... */
+ ehciR3InFlightRemove(pThis, pThisCC, GCPhysNextQTD);
+ }
+ }
+ }
+ /* Save the current QTD to the overlay area. */
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+}
+
+/**
+ * Services a QTD list
+ */
+static RTGCPHYS ehciR3ServiceQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD,
+ RTGCPHYS GCPhysQTD, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ EHCI_QTD qtd;
+
+ /* Read the whole QTD */
+ ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd);
+
+ if (qtd.Token.Bits.Active)
+ {
+ if (!ehciR3IsTdInFlight(pThisCC, GCPhysQTD))
+ {
+ /* Don't queue more than one non-bulk transfer at a time */
+ if ( ehciR3QueryTransferType(pQhd) != VUSBXFERTYPE_BULK
+ && pQhd->Overlay.OrgQTD.Token.Bits.Active)
+ return 0;
+
+ Log2((" Length=%x IOC=%d DT=%d PID=%s}\n", qtd.Token.Bits.Length, qtd.Token.Bits.IOC, qtd.Token.Bits.DataToggle, ehciPID2Str(qtd.Token.Bits.PID)));
+ if ( !pQhd->Overlay.OrgQTD.Token.Bits.Active
+ || GCPhysQTD == (RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)
+ ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtd, GCPhysQTD);
+ else
+ Log2Func(("transfer %RGp in progress -> don't update the overlay\n", (RTGCPHYS)(pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)));
+
+ ehciR3SubmitQTD(pDevIns, pThis, pThisCC, GCPhysQHD, pQhd, GCPhysQTD, &qtd, iFrame);
+
+ /* Set the Reclamation bit in USBSTS (4.10.3) */
+ if (enmServiceType == EHCI_SERVICE_ASYNC)
+ {
+ Log2Func(("activity detected, set EHCI_STATUS_RECLAMATION\n"));
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION);
+ }
+
+ /* Reread the whole QTD; it might have been completed already and therefore changed */
+ ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd);
+ }
+ /* Table 4-10: any transfer with zero size: queue only one */
+ if (qtd.Token.Bits.Length == 0)
+ {
+ LogFunc(("queue only one: transfer with zero size\n"));
+ return 0;
+ }
+
+ /* We can't queue more than one TD if we can't decide here and now which TD we should take next */
+ if ( qtd.Token.Bits.Active /* only check if this urb is in-flight */
+ && qtd.Token.Bits.PID == EHCI_QTD_PID_IN
+ && !qtd.AltNext.Terminate
+ && !qtd.Next.Terminate
+ && qtd.Next.Pointer != qtd.AltNext.Pointer)
+ {
+ Log2Func(("Can't decide which pointer to take next; don't queue more than one!\n"));
+ return 0;
+ }
+ }
+ else
+ {
+ Log2((" Not active}\n"));
+ return 0;
+ }
+
+ /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */
+ if ( !qtd.Token.Bits.Active /* only check if no urbs are in-flight */
+ && qtd.Token.Bits.PID == EHCI_QTD_PID_IN /* short packets only apply to incoming tds */
+ && !qtd.AltNext.Terminate
+ && qtd.Token.Bits.Length)
+ {
+ Assert(qtd.AltNext.Pointer);
+ Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT)));
+ return qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ Assert(qtd.Next.Pointer || qtd.Next.Terminate);
+ if (qtd.Next.Terminate || !qtd.Next.Pointer)
+ return 0;
+ return qtd.Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+}
+
+/**
+ * Services a QHD list
+ */
+static bool ehciR3ServiceQHD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ EHCI_QHD qhd;
+
+ Log2Func(("%RGp={", GCPhys));
+
+ /* Read the whole QHD */ /** @todo reading too much */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+
+ /* Only interrupt qHDs should be linked from the periodic list; the S-mask field description
+ * in table 3-20 clearly says a zero S-mask on the periodic list yields undefined results. In reality,
+ * the Windows HCD links dummy qHDs at the start of the interrupt queue and these have an empty S-mask.
+ * If we're servicing the periodic list, check the S-mask first; that takes care of the dummy qHDs.
+ */
+ if (enmServiceType == EHCI_SERVICE_PERIODIC)
+ {
+ // If iFrame was a micro-frame number, we should check the S-mask against it. But
+ // we're processing all micro-frames at once, so we'll look at any qHD with non-zero S-mask
+ if (qhd.Caps.SMask == 0) {
+ Log2Func(("periodic qHD not scheduled for current frame -> next\n"));
+ return true;
+ }
+ else
+ Log2Func(("periodic qHD scheduled for current frame, processing\n"));
+ }
+ else
+ {
+ Assert(enmServiceType == EHCI_SERVICE_ASYNC);
+ /* Empty schedule detection (4.10.1), for async schedule only */
+ if (qhd.Characteristics.HeadReclamation) /* H-bit set but not an interrupt qHD */
+ {
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ {
+ Log2Func(("clear EHCI_STATUS_RECLAMATION\n"));
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_RECLAMATION);
+ }
+ else
+ {
+ Log2Func(("empty schedule -> bail out\n"));
+ pThis->fAsyncTraversalTimerActive = true;
+ return false; /** stop traversing the list */
+ }
+ }
+ }
+
+ /* no active qTD here or in the next queue element -> skip to next horizontal pointer (Figure 4.14 & 4.10.2) */
+ if ( !qhd.Overlay.OrgQTD.Token.Bits.Active
+ && qhd.Characteristics.InActiveNext)
+ {
+ Log2Func(("skip to next pointer (active)\n"));
+ return true;
+ }
+ /* we are ignoring the Inactivate on Next Transaction bit; only applies to periodic lists & low or full speed devices (table 3.9) */
+
+ /** We are not allowed to handle multiple TDs unless async park is enabled (and only for high-speed devices), but we can cheat a bit. */
+ unsigned PMCount = 1;
+ if ( (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ && qhd.Characteristics.EndPtSpeed == EHCI_QHD_EPT_SPEED_HIGH
+ && enmServiceType == EHCI_SERVICE_ASYNC)
+ {
+ PMCount = (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT;
+ Log2Func(("PM Count=%d\n", PMCount));
+
+ /* We will attempt to queue a bit more if we're allowed to queue more than one TD. */
+ if (PMCount != 1)
+ PMCount = 16;
+ }
+
+ /* Queue as many transfer descriptors as possible */
+ RTGCPHYS GCPhysQTD;
+ if (qhd.Overlay.OrgQTD.Token.Bits.Active)
+ {
+ Assert(ehciR3IsTdInFlight(pThisCC, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT));
+ GCPhysQTD = qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */
+ if ( !qhd.Overlay.OrgQTD.AltNext.Terminate
+ && qhd.Overlay.OrgQTD.Token.Bits.Length)
+ {
+ Assert(qhd.Overlay.OrgQTD.AltNext.Pointer);
+ Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT)));
+ GCPhysQTD = qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ Assert(qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Next.Terminate || qhd.Overlay.OrgQTD.Token.Bits.Halted);
+ if (qhd.Overlay.OrgQTD.Next.Terminate || !qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Token.Bits.Halted)
+ GCPhysQTD = 0;
+ else
+ GCPhysQTD = qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ }
+
+ while (GCPhysQTD && PMCount--)
+ {
+ GCPhysQTD = ehciR3ServiceQTD(pDevIns, pThis, pThisCC, &qhd, GCPhys, GCPhysQTD, enmServiceType, iFrame);
+
+ /* Reread the whole QHD; urb submit can call us right back which causes QH changes */ /** @todo reading too much */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+ }
+ return true;
+}
+
+/**
+ * Services a FSTN list
+ */
+static void ehciR3ServiceFSTN(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(pDevIns, pThis, pThisCC, GCPhys, enmServiceType, iFrame);
+ AssertMsgFailed(("FSTN lists not implemented; should never occur!\n"));
+}
+
+/**
+ * Services the async list.
+ *
+ * The async list has complex URB assembling, but that's taken
+ * care of at VUSB level (unlike the other transfer types).
+ */
+static void ehciR3ServiceAsyncList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame)
+{
+ RTGCPHYS GCPhysHead = pThis->async_list_base;
+ RTGCPHYS GCPhys = GCPhysHead;
+ EHCI_TD_PTR ptr;
+ unsigned cIterations = 0;
+
+ Assert(!(pThis->async_list_base & 0x1f));
+ Assert(pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE);
+ Assert(pThis->cmd & EHCI_CMD_RUN);
+
+ Log2Func(("%RGp\n", GCPhysHead));
+#ifdef LOG_ENABLED
+ ehciR3DumpQH(pDevIns, GCPhysHead, true);
+#endif
+
+ /* Signal the async advance doorbell interrupt (if required) */
+ if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL))
+// && !pThis->cInFlight)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_INT_ON_ASYNC_ADV);
+
+ /* Process the list of qHDs */
+ for (;;)
+ {
+ /* Process the qHD */
+ if (!ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_ASYNC, iFrame))
+ break;
+
+ /* Read the next pointer */
+ RTGCPHYS GCPhysLast = GCPhys;
+ ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr);
+
+ /* Detect obvious loops. */
+ if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT))
+ break;
+
+ /* Technically a zero address could be valid, but that's extremely unlikely! */
+ Assert(ptr.Pointer || ptr.Terminate);
+ if (ptr.Terminate || !ptr.Pointer)
+ break;
+
+ /* Not clear what we should do if this *is* something other than a qHD. */
+ AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type));
+ if (ptr.Type != EHCI_DESCRIPTOR_QH)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ AssertMsgBreak(++cIterations < 128, ("Too many iterations, exiting\n"));
+
+ /* next */
+ GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT;
+ Assert(!(GCPhys & 0x1f));
+ if ( GCPhys == GCPhysHead
+ || GCPhys == GCPhysLast)
+ break; /* break the loop */
+ }
+
+#ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ehciR3DumpQH(pDevIns, GCPhysHead, true);
+#endif
+}
+
+
+/**
+ * Services the periodic list.
+ *
+ * On the interrupt portion of the periodic list we must reassemble URBs from multiple
+ * TDs using heuristics derived from USB tracing done in the guests and guest source
+ * code (when available).
+ */
+static void ehciR3ServicePeriodicList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame)
+{
+ Assert(pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE);
+
+#ifdef LOG_ENABLED
+ RTGCPHYS pFramePtrHead = 0;
+ if (g_fLogInterruptEPs)
+ {
+ RTGCPHYS pFramePtr = pThis->periodic_list_base + iFrame * sizeof(EHCI_FRAME_LIST_PTR);
+
+ pFramePtrHead = pFramePtr;
+
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x before", iFrame);
+ ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true);
+ }
+#endif
+
+ /*
+ * Iterate the periodic list.
+ */
+ EHCI_FRAME_LIST_PTR FramePtr;
+ RTGCPHYS GCPhys = pThis->periodic_list_base + iFrame * sizeof(FramePtr);
+ unsigned iterations = 0;
+
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+ while (!FramePtr.Terminate && (pThis->cmd & EHCI_CMD_RUN))
+ {
+ GCPhys = FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT;
+ /* Process the descriptor based on its type. Note that on the periodic
+ * list, HCDs may (and do) mix iTDs and qHDs more or less freely.
+ */
+ switch(FramePtr.Type)
+ {
+ case EHCI_DESCRIPTOR_ITD:
+ ehciR3ServiceITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_SITD:
+ ehciR3ServiceSITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_QH:
+ ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_FSTN:
+ ehciR3ServiceFSTN(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ }
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 2048)
+ {
+ AssertMsgFailed(("ehciR3ServicePeriodicList: Too many iterations, exiting\n"));
+ break;
+ }
+ /* Read the next link */
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+
+ /* Detect obvious loops. */
+ if (GCPhys == ((RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT))
+ break;
+ }
+
+#ifdef LOG_ENABLED
+ if (g_fLogInterruptEPs)
+ {
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iFrame);
+ ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true);
+ }
+#endif
+}
+
+
+/**
+ * Calculate frame timer variables given a frame rate (1,000 Hz is the full speed).
+ */
+static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate)
+{
+ Assert(u32FrameRate <= EHCI_HARDWARE_TIMER_FREQ);
+
+ pThis->uFramesPerTimerCall = EHCI_HARDWARE_TIMER_FREQ / u32FrameRate;
+ pThisCC->nsWait = RT_NS_1SEC / u32FrameRate;
+ pThisCC->cTicksPerFrame = pThisCC->u64TimerHz / u32FrameRate;
+ if (!pThisCC->cTicksPerFrame)
+ pThisCC->cTicksPerFrame = 1;
+ pThisCC->uFrameRate = u32FrameRate;
+}
+
+/**
+ * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame.
+ */
+static void ehciR3StartOfFrame(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC)
+{
+ uint32_t uNewFrameRate = pThisCC->uFrameRate;
+#ifdef LOG_ENABLED
+ const uint32_t status_old = pThis->intr_status;
+#endif
+
+ pThis->SofTime += pThisCC->cTicksPerFrame;
+ unsigned iFrame = (pThis->frame_idx >> EHCI_FRINDEX_FRAME_INDEX_SHIFT) & EHCI_FRINDEX_FRAME_INDEX_MASK;
+
+ if (pThis->uIrqInterval < pThis->uFramesPerTimerCall)
+ pThis->uIrqInterval = 0;
+ else
+ pThis->uIrqInterval -= pThis->uFramesPerTimerCall;
+
+ /* Empty async list detection halted the async schedule */
+ if (pThis->fAsyncTraversalTimerActive)
+ {
+ /* Table 4.7 in 4.8.4.1 */
+ Log2Func(("setting STATUS_RECLAMATION after empty list detection\n"));
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION);
+ pThis->fAsyncTraversalTimerActive = false;
+ }
+
+ /*
+ * Periodic EPs (Isochronous & Interrupt)
+ */
+ if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ {
+ int num_frames = RT_MAX(1, pThis->uFramesPerTimerCall >> EHCI_FRINDEX_FRAME_INDEX_SHIFT);
+ Assert(num_frames > 0 && num_frames < 1024);
+
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_PERIOD_SCHED);
+
+ if (pThis->cmd & EHCI_CMD_RUN)
+ {
+ /* If we're running the frame timer at a reduced rate, we still need to process
+ * all frames. Otherwise we risk completely missing newly scheduled periodic transfers.
+ */
+ for (int i = 0; i < num_frames; ++i)
+ ehciR3ServicePeriodicList(pDevIns, pThis, pThisCC, (iFrame + i) & EHCI_FRINDEX_FRAME_INDEX_MASK);
+ }
+ }
+ else
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED);
+
+ /*
+ * Async EPs (Control and Bulk)
+ */
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ {
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_ASYNC_SCHED);
+ if (pThis->cmd & EHCI_CMD_RUN)
+ ehciR3ServiceAsyncList(pDevIns, pThis, pThisCC, iFrame);
+ }
+ else
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED);
+
+ /*
+ * ASSUME the guest won't be very upset if a TD is completed, retired and handed
+ * back immediately. The idea is to be able to retire the data and/or status stages
+ * of a control transfer together with the setup stage, thus saving a frame. This
+ * behaviour is should be perfectly ok, since the setup (and maybe data) stages
+ * have already taken at least one frame to complete.
+ *
+ * But, when implementing the first synchronous virtual USB devices, we'll have to
+ * verify that the guest doesn't choke when having a TD returned in the same frame
+ * as it was submitted.
+ */
+
+#ifdef LOG_ENABLED
+ if (pThis->intr_status ^ status_old)
+ {
+ uint32_t val = pThis->intr_status;
+ uint32_t chg = val ^ status_old; NOREF(chg);
+ Log2Func(("HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", val & 1,
+ chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1,
+ chg & (3<<16)? "*" : "", (val >> 16) & 3));
+ }
+#endif
+
+ /*
+ * Adjust the frame timer interval based on idle detection.
+ */
+ if (pThisCC->fIdle)
+ {
+ pThisCC->cIdleCycles++;
+
+ /*
+ * Set the new frame rate based on how long we've been idle.
+ * Don't remain more than 2 seconds in each frame rate (except for lowest one).
+ */
+ /** @todo Experiment with these values. */
+ if (pThisCC->cIdleCycles == 2 * pThisCC->uFrameRate)
+ {
+ if (pThisCC->uFrameRate > 500)
+ uNewFrameRate = pThisCC->uFrameRate - 500;
+ else
+ uNewFrameRate = 50; /* Absolute minimum is 50 Hertz, i.e 20ms interval. */
+
+ pThisCC->cIdleCycles = 1;
+ }
+ }
+ else
+ {
+ if (pThisCC->cIdleCycles)
+ {
+ pThisCC->cIdleCycles = 0;
+ uNewFrameRate = pThisCC->uFrameRateDefault;
+ }
+ }
+ if (uNewFrameRate != pThisCC->uFrameRate)
+ ehciR3CalcTimerIntervals(pThis, pThisCC, uNewFrameRate);
+}
+
+/**
+ * Updates the HcFmNumber and frame_index values. HcFmNumber contains the current USB
+ * frame number, frame_idx is the current micro-frame. In other words,
+ *
+ * HcFmNumber == frame_idx << EHCI_FRAME_INDEX_SHIFT
+ */
+static void ehciR3BumpFrameNumber(PPDMDEVINS pDevIns, PEHCI pThis)
+{
+ pThis->HcFmNumber = pThis->frame_idx;
+
+ const uint32_t u32OldFmNumber = pThis->HcFmNumber;
+
+ pThis->HcFmNumber += pThis->uFramesPerTimerCall;
+
+ if ((u32OldFmNumber ^ pThis->HcFmNumber) & ~EHCI_FRINDEX_FRAME_INDEX_MASK)
+ {
+ Log2Func(("rollover!\n"));
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_FRAME_LIST_ROLLOVER);
+ }
+
+ pThis->frame_idx = pThis->HcFmNumber;
+
+}
+
+/**
+ * @callback_method_impl{PFNPDMTHREADDEV, EHCI Frame Thread}
+ */
+static DECLCALLBACK(int) ehciR3ThreadFrame(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ int rc = VINF_SUCCESS;
+ while ( !ASMAtomicReadBool(&pThis->fBusStarted)
+ && pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ /* Make sure the SCHED status bits are clear. */
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED);
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED);
+
+ /* Signal the waiter that we are stopped now. */
+ rc = RTSemEventMultiSignal(pThisCC->hSemEventFrameStopped);
+ AssertRC(rc);
+
+ rc = RTSemEventMultiWait(pThisCC->hSemEventFrame, RT_INDEFINITE_WAIT);
+ RTSemEventMultiReset(pThisCC->hSemEventFrame);
+ }
+
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+
+ uint64_t const tsNanoStart = RTTimeNanoTS();
+
+ RTCritSectEnter(&pThisCC->CritSect);
+
+ /* Reset idle detection flag */
+ pThisCC->fIdle = true;
+
+ /* Frame boundary, so do EOF stuff here */
+ ehciR3StartOfFrame(pDevIns, pThis, pThisCC);
+
+ /* Start the next frame */
+ ehciR3BumpFrameNumber(pDevIns, pThis);
+
+ RTCritSectLeave(&pThisCC->CritSect);
+
+ /* Wait for the next round. */
+ uint64_t nsWait = (RTTimeNanoTS() + pThisCC->nsWait) - tsNanoStart;
+
+ rc = RTSemEventMultiWaitEx(pThisCC->hSemEventFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE,
+ nsWait);
+ AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ RTSemEventMultiReset(pThisCC->hSemEventFrame);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unblock the framer thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) ehciR3ThreadFrameWakeup(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ RT_NOREF(pThread);
+ return RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Start sending SOF tokens across the USB bus, lists are processed in next frame.
+ */
+static void ehciR3BusStart(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC)
+{
+ pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn);
+ ehciR3BumpFrameNumber(pDevIns, pThis);
+
+ LogFunc(("Bus started\n"));
+
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_HCHALTED);
+ pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns) - pThisCC->cTicksPerFrame;
+ bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, true);
+ if (!fBusActive)
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Stop sending SOF tokens on the bus
+ */
+static void ehciR3BusStop(PEHCI pThis, PEHCICC pThisCC)
+{
+ LogFunc(("\n"));
+ bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, false);
+ if (fBusActive)
+ {
+ int rc = RTSemEventMultiReset(pThisCC->hSemEventFrameStopped);
+ AssertRC(rc);
+
+ /* Signal the frame thread to stop. */
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+
+ /* Wait for signal from the thread that it stopped. */
+ rc = RTSemEventMultiWait(pThisCC->hSemEventFrameStopped, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn);
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_HCHALTED);
+}
+
+/**
+ * Power a port up or down
+ */
+static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp)
+{
+ bool fOldPPS = !!(pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_POWER);
+ if (fPowerUp)
+ {
+ Log2Func(("port %d UP\n", iPort));
+ /* power up */
+ if (pThisCC->RootHub.aPorts[iPort].fAttached)
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT);
+ if (pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT)
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_POWER);
+ if (pThisCC->RootHub.aPorts[iPort].fAttached && !fOldPPS)
+ VUSBIRhDevPowerOn(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort));
+ }
+ else
+ {
+ Log2(("Func port %d DOWN\n", iPort));
+ /* power down */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_POWER|EHCI_PORT_CURRENT_CONNECT));
+ if (pThisCC->RootHub.aPorts[iPort].fAttached && fOldPPS)
+ VUSBIRhDevPowerOff(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort));
+ }
+}
+
+/**
+ * Completion callback for the VUSBIDevReset() operation.
+ * @thread EMT.
+ */
+static void ehciR3PortResetDone(PEHCI pThis, PEHCICC pThisCC, uint32_t uPort, int rc)
+{
+ Log2Func(("rc=%Rrc\n", rc));
+ Assert(uPort >= 1);
+ unsigned iPort = uPort - 1;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Successful reset.
+ */
+ Log2Func(("Reset completed.\n"));
+ /* Note: XP relies on us clearing EHCI_PORT_CONNECT_CHANGE */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND | EHCI_PORT_CONNECT_CHANGE));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_PORT_ENABLED);
+ }
+ else
+ {
+ /* desperate measures. */
+ if ( pThisCC->RootHub.aPorts[iPort].fAttached
+ && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED)
+ {
+ /*
+ * Damn, something weird happend during reset. We'll pretend the user did an
+ * incredible fast reconnect or something. (prolly not gonna work)
+ */
+ Log2Func(("The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ }
+ else
+ {
+ /*
+ * The device has / will be disconnected.
+ */
+ Log2Func(("Disconnected (rc=%Rrc)!!!\n", rc));
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CONNECT_CHANGE);
+ }
+ }
+}
+
+/**
+ * Sets a flag in a port status register but only set it if a device is
+ * connected, if not set ConnectStatusChange flag to force HCD to reevaluate
+ * connect status.
+ *
+ * @returns true if device was connected and the flag was cleared.
+ */
+static bool ehciR3RhPortSetIfConnected(PEHCIROOTHUB pRh, int iPort, uint32_t fValue)
+{
+ /*
+ * Writing a 0 has no effect
+ */
+ if (fValue == 0)
+ return false;
+
+ /*
+ * The port might be still/already disconnected.
+ */
+ if (!(pRh->aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT))
+ return false;
+
+ bool fRc = !(pRh->aPorts[iPort].fReg & fValue);
+
+ /* set the bit */
+ ASMAtomicOrU32(&pRh->aPorts[iPort].fReg, fValue);
+
+ return fRc;
+}
+
+#endif /* IN_RING3 */
+
+
+/**
+ * Read the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcCommand_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ /* Signal the async advance doorbell interrupt (if required)
+ * XP polls the command register to see when it can queue up more TDs.
+ */
+ if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL))
+// && !pThis->cInFlight)
+ {
+ int rc = ehciSetInterrupt(pDevIns, pThis, VINF_IOM_R3_MMIO_READ, EHCI_STATUS_INT_ON_ASYNC_ADV);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ *pu32Value = pThis->cmd;
+ RT_NOREF(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcCommand_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+#endif
+ RT_NOREF(pDevIns, iReg);
+
+#ifdef LOG_ENABLED
+ Log(("HcCommand_w old=%x new=%x\n", pThis->cmd, val));
+ if (val & EHCI_CMD_RUN)
+ Log((" CMD_RUN\n"));
+ if (val & EHCI_CMD_RESET)
+ Log((" CMD_RESET\n"));
+ if (val & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ Log((" CMD_PERIODIC_SCHED_ENABLE\n"));
+ if (val & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ Log((" CMD_ASYNC_SCHED_ENABLE\n"));
+ if (val & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)
+ Log((" CMD_INT_ON_ADVANCE_DOORBELL\n"));
+ if (val & EHCI_CMD_SOFT_RESET)
+ Log((" CMD_SOFT_RESET\n"));
+ if (val & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ Log((" CMD_ASYNC_SCHED_PARK_ENABLE\n"));
+
+ Log((" CMD_FRAME_LIST_SIZE %d\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT));
+ Log((" CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (val & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT));
+ Log((" CMD_INTERRUPT_THRESHOLD %d\n", (val & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT));
+#endif
+
+ Assert(!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST)); /* hardcoded assumptions about list size */
+ if (!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST))
+ {
+ if (val & EHCI_CMD_FRAME_LIST_SIZE_MASK)
+ Log(("Trying to change the frame list size to %d even though it's hardcoded at 1024 elements!!\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT));
+
+ val &= ~EHCI_CMD_FRAME_LIST_SIZE_MASK; /* 00 = 1024 */
+ }
+ if (val & ~EHCI_CMD_MASK)
+ Log(("Unknown bits %#x are set!!!\n", val & ~0x0003000f));
+
+ uint32_t old_cmd = pThis->cmd;
+#ifdef IN_RING3
+ pThis->cmd = val;
+#endif
+
+ if (val & EHCI_CMD_RESET)
+ {
+#ifdef IN_RING3
+ LogRel(("EHCI: Hardware reset\n"));
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else if (val & EHCI_CMD_SOFT_RESET)
+ {
+#ifdef IN_RING3
+ LogRel(("EHCI: Software reset\n"));
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else
+ {
+ /* see what changed and take action on that. */
+ uint32_t old_state = old_cmd & EHCI_CMD_RUN;
+ uint32_t new_state = val & EHCI_CMD_RUN;
+
+ if (old_state != new_state)
+ {
+#ifdef IN_RING3
+ switch (new_state)
+ {
+ case EHCI_CMD_RUN:
+ LogRel(("EHCI: USB Operational\n"));
+ ehciR3BusStart(pDevIns, pThis, pThisCC);
+ break;
+ case 0:
+ ehciR3BusStop(pThis, pThisCC);
+ LogRel(("EHCI: USB Suspended\n"));
+ break;
+ }
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ }
+#ifndef IN_RING3
+ pThis->cmd = val;
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcStatus_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+#ifdef LOG_ENABLED
+ Log(("HcStatus_r current value %x\n", pThis->intr_status));
+ if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED)
+ Log((" STATUS_ASYNC_SCHED\n"));
+ if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED)
+ Log((" STATUS_PERIOD_SCHED\n"));
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ Log((" STATUS_RECLAMATION\n"));
+ if (pThis->intr_status & EHCI_STATUS_HCHALTED)
+ Log((" STATUS_HCHALTED\n"));
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ Log((" STATUS_INT_ON_ASYNC_ADV\n"));
+ if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ Log((" STATUS_HOST_SYSTEM_ERROR\n"));
+ if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ Log((" STATUS_FRAME_LIST_ROLLOVER\n"));
+ if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT)
+ Log((" STATUS_PORT_CHANGE_DETECT\n"));
+ if (pThis->intr_status & EHCI_STATUS_ERROR_INT)
+ Log((" STATUS_ERROR_INT\n"));
+ if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT)
+ Log((" STATUS_THRESHOLD_INT\n"));
+#endif
+ *pu32Value = pThis->intr_status;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcStatus_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+#ifdef LOG_ENABLED
+ Log(("HcStatus_w current value %x; new %x\n", pThis->intr_status, val));
+ if (val & EHCI_STATUS_ASYNC_SCHED)
+ Log((" STATUS_ASYNC_SCHED\n"));
+ if (val & EHCI_STATUS_PERIOD_SCHED)
+ Log((" STATUS_PERIOD_SCHED\n"));
+ if (val & EHCI_STATUS_RECLAMATION)
+ Log((" STATUS_RECLAMATION\n"));
+ if (val & EHCI_STATUS_HCHALTED)
+ Log((" STATUS_HCHALTED\n"));
+ if (val & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ Log((" STATUS_INT_ON_ASYNC_ADV\n"));
+ if (val & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ Log((" STATUS_HOST_SYSTEM_ERROR\n"));
+ if (val & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ Log((" STATUS_FRAME_LIST_ROLLOVER\n"));
+ if (val & EHCI_STATUS_PORT_CHANGE_DETECT)
+ Log((" STATUS_PORT_CHANGE_DETECT\n"));
+ if (val & EHCI_STATUS_ERROR_INT)
+ Log((" STATUS_ERROR_INT\n"));
+ if (val & EHCI_STATUS_THRESHOLD_INT)
+ Log((" STATUS_THRESHOLD_INT\n"));
+#endif
+ if ( (val & ~EHCI_STATUS_INTERRUPT_MASK)
+ && val != 0xffffffff /* ignore clear-all-like requests from xp. */)
+ Log(("Unknown bits %#x are set!!!\n", val & ~EHCI_STATUS_INTERRUPT_MASK));
+
+ /* Some bits are read-only */
+ val &= EHCI_STATUS_INTERRUPT_MASK;
+
+ /* "The Host Controller Driver may clear specific bits in this
+ * register by writing '1' to bit positions to be cleared"
+ */
+ ASMAtomicAndU32(&pThis->intr_status, ~val);
+ ehciUpdateInterruptLocked(pDevIns, pThis, "HcStatus_w");
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the USBINTR register of the host controller.
+ */
+static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ *pu32Value = pThis->intr;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBINTR register of the host controller.
+ */
+static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+#ifdef LOG_ENABLED
+ Log(("HcInterruptEnable_w -> new value %x\n", val));
+ if (val & EHCI_INTR_ENABLE_THRESHOLD)
+ Log((" INTR_ENABLE_THRESHOLD\n"));
+ if (val & EHCI_INTR_ENABLE_ERROR)
+ Log((" INTR_ENABLE_ERROR\n"));
+ if (val & EHCI_INTR_ENABLE_PORT_CHANGE)
+ Log((" INTR_ENABLE_PORT_CHANGE\n"));
+ if (val & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER)
+ Log((" INTR_ENABLE_FRAME_LIST_ROLLOVER\n"));
+ if (val & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR)
+ Log((" INTR_ENABLE_HOST_SYSTEM_ERROR\n"));
+ if (val & EHCI_INTR_ENABLE_ASYNC_ADVANCE)
+ Log((" INTR_ENABLE_ASYNC_ADVANCE\n"));
+ if (val & ~EHCI_INTR_ENABLE_MASK)
+ Log((" Illegal bits set %x!!\n", val & ~EHCI_INTR_ENABLE_MASK));
+#endif
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ pThis->intr = val & EHCI_INTR_ENABLE_MASK;
+ ehciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w");
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ }
+ return rc;
+}
+
+/**
+ * Read the FRINDEX register of the host controller.
+ */
+static VBOXSTRICTRC HcFrameIndex_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current frame %x\n", pThis->frame_idx));
+ *pu32Value = pThis->frame_idx;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the FRINDEX register of the host controller.
+ */
+static VBOXSTRICTRC HcFrameIndex_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("pThis->frame_idx new index=%x\n", val));
+ if (!(pThis->intr_status & EHCI_STATUS_HCHALTED))
+ Log(("->>Updating the frame index while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->frame_idx, val);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the CTRLDSSEGMENT register of the host controller.
+ */
+static VBOXSTRICTRC HcControlDSSeg_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING)
+ *pu32Value = pThis->ds_segment;
+ else
+ *pu32Value = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the CTRLDSSEGMENT register of the host controller.
+ */
+static VBOXSTRICTRC HcControlDSSeg_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new base %x\n", val));
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING)
+ ASMAtomicXchgU32(&pThis->ds_segment, val);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the PERIODICLISTBASE register of the host controller.
+ */
+static VBOXSTRICTRC HcPeriodicListBase_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current base %x\n", pThis->periodic_list_base));
+ *pu32Value = pThis->periodic_list_base;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the PERIODICLISTBASE register of the host controller.
+ */
+static VBOXSTRICTRC HcPeriodicListBase_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new base %x\n", val));
+ if (val & ~EHCI_PERIODIC_LIST_MASK)
+ Log(("->> Base not aligned on a 4kb boundary!!!!\n"));
+ if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED)
+ && (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE))
+ Log(("->>Updating the periodic list base while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->periodic_list_base, val & EHCI_PERIODIC_LIST_MASK);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the ASYNCLISTADDR register of the host controller.
+ */
+static VBOXSTRICTRC HcAsyncListAddr_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current base %x\n", pThis->async_list_base));
+ *pu32Value = pThis->async_list_base;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the ASYNCLISTADDR register of the host controller.
+ */
+static VBOXSTRICTRC HcAsyncListAddr_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new address %x\n", val));
+ if (val & ~EHCI_ASYNC_LIST_MASK)
+ Log(("->> Base not aligned on a 32-byte boundary!!!!\n"));
+ if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED)
+ && (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE))
+ Log(("->>Updating the asynchronous list address while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->async_list_base, val & EHCI_ASYNC_LIST_MASK);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the CONFIGFLAG register of the host controller.
+ */
+static VBOXSTRICTRC HcConfigFlag_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current config=%x\n", pThis->config));
+ *pu32Value = pThis->config;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the CONFIGFLAG register of the host controller.
+ */
+static VBOXSTRICTRC HcConfigFlag_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new configuration routing %x\n", val & EHCI_CONFIGFLAG_ROUTING));
+ pThis->config = val & EHCI_CONFIGFLAG_MASK;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the PORTSC register of a port
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ const unsigned i = iReg - 1;
+ PEHCIHUBPORT p = &pThis->RootHub.aPorts[i];
+ RT_NOREF(pDevIns);
+
+ Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL));
+
+ if (p->fReg & EHCI_PORT_RESET)
+ {
+#ifdef IN_RING3
+ Log2Func(("port %u: Impatient guest!\n", i));
+ RTThreadYield();
+#else
+ Log2Func(("yield -> VINF_IOM_R3_MMIO_READ\n"));
+ return VINF_IOM_R3_MMIO_READ;
+#endif
+ }
+
+ *pu32Value = p->fReg;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the PORTSC register of a port
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ const unsigned i = iReg - 1;
+ PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[i];
+
+ if ( pPort->fReg == val
+ && !(val & EHCI_PORT_CHANGE_MASK))
+ return VINF_SUCCESS;
+
+#ifdef IN_RING3
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+
+ LogFunc(("port %d: old=%x new=%x\n", i, pPort->fReg, val));
+ Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL));
+ Assert(pPort->fReg & EHCI_PORT_POWER);
+
+ if (val & EHCI_PORT_RESERVED)
+ Log(("Invalid bits set %x!!!\n", val & EHCI_PORT_RESERVED));
+
+ /* Write to clear any of the change bits: EHCI_PORT_CONNECT_CHANGE, EHCI_PORT_PORT_CHANGE and EHCI_PORT_OVER_CURRENT_CHANGE */
+ if (val & EHCI_PORT_CHANGE_MASK)
+ {
+ ASMAtomicAndU32(&pPort->fReg, ~(val & EHCI_PORT_CHANGE_MASK));
+ /* XP seems to need this after device detach */
+ if (!(pPort->fReg & EHCI_PORT_CURRENT_CONNECT))
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_CONNECT_CHANGE);
+ }
+
+ /* Writing the Port Enable/Disable bit as 1 has no effect; software cannot enable
+ * the port that way. Writing the bit as zero does disable the port, but does not
+ * set the corresponding 'changed' bit or trigger an interrupt.
+ */
+ if (!(val & EHCI_PORT_PORT_ENABLED) && (pPort->fReg & EHCI_PORT_PORT_ENABLED))
+ {
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_PORT_ENABLED);
+ LogFunc(("port %u: DISABLE\n", i));
+ }
+
+ if (val & EHCI_PORT_SUSPEND)
+ LogFunc(("port %u: SUSPEND - not implemented correctly!!!\n", i));
+
+ if (val & EHCI_PORT_RESET)
+ {
+ Log2Func(("Reset port\n"));
+ if ( ehciR3RhPortSetIfConnected(&pThis->RootHub, i, val & EHCI_PORT_RESET) )
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, NULL /* sync */, pThis, pVM);
+ ehciR3PortResetDone(pThis, pThisCC, EHCI_PORT_2_VUSB_PORT(i), VINF_SUCCESS);
+ }
+ else if (pPort->fReg & EHCI_PORT_RESET)
+ {
+ /* the guest is getting impatient. */
+ Log2Func(("port %u: Impatient guest!\n", i));
+ RTThreadYield();
+ }
+ }
+
+ /* EHCI_PORT_POWER ignored as we don't support this in HCS_PARAMS */
+ /* EHCI_PORT_INDICATOR ignored as we don't support this in HCS_PARAMS */
+ /* EHCI_PORT_TEST_CONTROL_MASK ignored */
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_WAKE_MASK);
+ ASMAtomicOrU32(&pPort->fReg, (val & EHCI_PORT_WAKE_MASK));
+ return VINF_SUCCESS;
+
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+/**
+ * Register descriptor table
+ */
+static const EHCIOPREG g_aOpRegs[] =
+{
+ {"HcCommand" , HcCommand_r, HcCommand_w},
+ {"HcStatus", HcStatus_r, HcStatus_w},
+ {"HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w},
+ {"HcFrameIndex", HcFrameIndex_r, HcFrameIndex_w},
+ {"HcControlDSSeg", HcControlDSSeg_r, HcControlDSSeg_w},
+ {"HcPeriodicListBase", HcPeriodicListBase_r, HcPeriodicListBase_w},
+ {"HcAsyncListAddr", HcAsyncListAddr_r, HcAsyncListAddr_w}
+};
+
+/**
+ * Register descriptor table 2
+ * (Starting at offset 0x40)
+ */
+static const EHCIOPREG g_aOpRegs2[] =
+{
+ {"HcConfigFlag", HcConfigFlag_r, HcConfigFlag_w},
+
+ /* The number of port status register depends on the definition
+ * of EHCI_NDP_MAX macro
+ */
+ {"HcPortStatusCtrl[0]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[1]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[2]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[3]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[4]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[5]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[6]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[7]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[8]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[9]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[10]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[11]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[12]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[13]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[14]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+};
+
+/* Quick way to determine how many op regs are valid. Since at least one port must
+ * be configured (and no more than 15), there will be between 2 and 16 registers.
+ */
+#define NUM_OP_REGS2(pehci) (1 + EHCI_NDP_CFG(pehci))
+
+AssertCompile(RT_ELEMENTS(g_aOpRegs2) > 1);
+AssertCompile(RT_ELEMENTS(g_aOpRegs2) <= 16);
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. }
+ *
+ * @note We only accept 32-bit reads that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ehciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ RT_NOREF(pvUser);
+
+ Log2Func(("%RGp size=%d\n", off, cb));
+
+ if (off < EHCI_CAPS_REG_SIZE)
+ {
+ switch (off)
+ {
+ case 0x0: /* CAPLENGTH */
+ /* read CAPLENGTH + HCIVERSION in one go */
+ if (cb == 4)
+ {
+ *(uint32_t *)pv = (pThis->hci_version << 16) | pThis->cap_length;
+ return VINF_SUCCESS;
+ }
+
+ AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint8_t *)pv = pThis->cap_length;
+ break;
+
+ case 0x2: /* HCIVERSION */
+ AssertReturn(cb == 2, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint16_t *)pv = pThis->hci_version;
+ break;
+
+ case 0x4: /* HCSPARAMS (structural) */
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = pThis->hcs_params;
+ break;
+
+ case 0x8: /* HCCPARAMS (caps) */
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = pThis->hcc_params;
+ break;
+
+ case 0x9: /* one byte HCIPARAMS read (XP; EHCI extended capability offset) */
+ AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint8_t *)pv = (uint8_t)(pThis->hcc_params >> 8);
+ break;
+
+ case 0xC: /* HCSP-PORTROUTE (60 bits) */
+ case 0x10:
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = 0;
+ break;
+
+ default:
+ LogFunc(("Trying to read register %#x!!!\n", off));
+ return VINF_IOM_MMIO_UNUSED_FF;
+ }
+ Log2Func(("%RGp size=%d -> val=%x\n", off, cb, *(uint32_t *)pv));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access.
+ */
+ if (cb != sizeof(uint32_t))
+ {
+ Log2Func(("Bad read size!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_IOM_MMIO_UNUSED_FF; /* No idea what really would happen... */
+ }
+ if (off & 0x3)
+ {
+ Log2Func(("Unaligned read!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_IOM_MMIO_UNUSED_FF;
+ }
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ uint32_t iReg = (off - pThis->cap_length) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv);
+ Log2Func(("%RGp size=%d -> val=%x (rc=%d)\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ }
+ else if (iReg >= 0x10) /* 0x40 */
+ {
+ iReg -= 0x10;
+ if (iReg < NUM_OP_REGS2(pThis))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs2[iReg];
+ rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv);
+ Log2Func(("%RGp size=%d -> val=%x (rc=%d)*\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ }
+ else
+ {
+ LogFunc(("Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis)));
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+ }
+ else
+ {
+ LogFunc(("Trying to read register %u/%u (2)!!!\n", iReg, RT_ELEMENTS(g_aOpRegs)));
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. }
+ *
+ * @note We only accept 32-bit writes that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ehciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ RT_NOREF(pvUser);
+
+ Log2Func(("%RGp %x size=%d\n", off, *(uint32_t *)pv, cb));
+
+ if (off < EHCI_CAPS_REG_SIZE)
+ {
+ /* These are read-only */
+ LogFunc(("Trying to write to register %#x!!!\n", off));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access.
+ */
+ if (cb != sizeof(uint32_t))
+ {
+ Log2Func(("Bad write size!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_SUCCESS;
+ }
+ if (off & 0x3)
+ {
+ Log2Func(("Unaligned write!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ uint32_t iReg = (off - pThis->cap_length) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv);
+ }
+ else if (iReg >= 0x10) /* 0x40 */
+ {
+ iReg -= 0x10;
+ if (iReg < NUM_OP_REGS2(pThis))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs2[iReg];
+ rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv);
+ }
+ else
+ {
+ LogFunc(("Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis)));
+ rc = VINF_SUCCESS; /* ignore the invalid write */
+ }
+ }
+ else
+ {
+ LogFunc(("Trying to write to register %u/%u!!! (2)\n", iReg, RT_ELEMENTS(g_aOpRegs)));
+ rc = VINF_SUCCESS; /* ignore the invalid write */
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ehciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("\n"));
+ return pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ehciLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ LogFlowFunc(("\n"));
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ if (uVersion == EHCI_SAVED_STATE_VERSION)
+ {
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL)
+ {
+ static SSMFIELD const g_aEhciFieldsPreTimerRemoval[] =
+ {
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFieldsPreTimerRemoval[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(EHCI_NDP_CFG(pThis) <= EHCI_NDP_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+ else if (uVersion == EHCI_SAVED_STATE_VERSION_8PORTS)
+ {
+ static SSMFIELD const s_aEhciFields8Ports[] =
+ {
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &s_aEhciFields8Ports[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(EHCI_NDP_CFG(pThis) == 8, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+ else
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /*
+ * The EOF timer changed from one to two in version 4 of the saved state,
+ * then was dropped entirely in version 7.
+ *
+ * Note! Looks like someone remove the code that dealt with versions 1 thru 4,
+ * without adjust the above comment.
+ */
+ if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL)
+ {
+ bool fActive1 = false;
+ pHlp->pfnTimerSkipLoad(pSSM, &fActive1);
+ bool fActive2 = false;
+ pHlp->pfnTimerSkipLoad(pSSM, &fActive2);
+ bool fNoSync = false;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fNoSync);
+ if ( RT_SUCCESS(rc)
+ && (fActive1 || fActive2))
+ pThis->fBusStarted = true;
+ else
+ pThis->fBusStarted = false;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, Dumps EHCI control registers.}
+ */
+static DECLCALLBACK(void) ehciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ unsigned uPort;
+
+ /* Command register */
+ pHlp->pfnPrintf(pHlp, "USBCMD: %x\n", pThis->cmd);
+ if (pThis->cmd & EHCI_CMD_RUN)
+ pHlp->pfnPrintf(pHlp, " CMD_RUN\n");
+ if (pThis->cmd & EHCI_CMD_RESET)
+ pHlp->pfnPrintf(pHlp, " CMD_RESET\n");
+ if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_PERIODIC_SCHED_ENABLE\n");
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_ENABLE\n");
+ if (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)
+ pHlp->pfnPrintf(pHlp, " CMD_INT_ON_ADVANCE_DOORBELL\n");
+ if (pThis->cmd & EHCI_CMD_SOFT_RESET)
+ pHlp->pfnPrintf(pHlp, " CMD_SOFT_RESET\n");
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_ENABLE\n");
+
+ pHlp->pfnPrintf(pHlp, " CMD_FRAME_LIST_SIZE %d\n", (pThis->cmd & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT);
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT);
+ pHlp->pfnPrintf(pHlp, " CMD_INTERRUPT_THRESHOLD %d\n", (pThis->cmd & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT);
+
+ /* Status register */
+ pHlp->pfnPrintf(pHlp, "USBSTS: %x\n", pThis->intr_status);
+ if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED)
+ pHlp->pfnPrintf(pHlp, " STATUS_ASYNC_SCHED\n");
+ if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED)
+ pHlp->pfnPrintf(pHlp, " STATUS_PERIOD_SCHED\n");
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ pHlp->pfnPrintf(pHlp, " STATUS_RECLAMATION\n");
+ if (pThis->intr_status & EHCI_STATUS_HCHALTED)
+ pHlp->pfnPrintf(pHlp, " STATUS_HCHALTED\n");
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ pHlp->pfnPrintf(pHlp, " STATUS_INT_ON_ASYNC_ADV\n");
+ if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ pHlp->pfnPrintf(pHlp, " STATUS_HOST_SYSTEM_ERROR\n");
+ if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ pHlp->pfnPrintf(pHlp, " STATUS_FRAME_LIST_ROLLOVER\n");
+ if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT)
+ pHlp->pfnPrintf(pHlp, " STATUS_PORT_CHANGE_DETECT\n");
+ if (pThis->intr_status & EHCI_STATUS_ERROR_INT)
+ pHlp->pfnPrintf(pHlp, " STATUS_ERROR_INT\n");
+ if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT)
+ pHlp->pfnPrintf(pHlp, " STATUS_THRESHOLD_INT\n");
+
+ /* Interrupt enable register */
+ pHlp->pfnPrintf(pHlp, "USBINTR: %x\n", pThis->intr);
+ if (pThis->intr & EHCI_INTR_ENABLE_THRESHOLD)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_THRESHOLD\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_ERROR)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ERROR\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_PORT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_PORT_CHANGE\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_FRAME_LIST_ROLLOVER\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_HOST_SYSTEM_ERROR\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_ASYNC_ADVANCE)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ASYNC_ADVANCE\n");
+ if (pThis->intr & ~EHCI_INTR_ENABLE_MASK)
+ pHlp->pfnPrintf(pHlp, " Illegal bits set %x!!\n", pThis->intr & ~EHCI_INTR_ENABLE_MASK);
+
+ /* Frame index register */
+ pHlp->pfnPrintf(pHlp, "FRINDEX: %x\n", pThis->frame_idx);
+
+ /* Control data structure segment */
+ pHlp->pfnPrintf(pHlp, "CTRLDSSEGMENT: %RX32\n", pThis->ds_segment);
+
+ /* Periodic frame list base address register */
+ pHlp->pfnPrintf(pHlp, "PERIODICLISTBASE: %RX32\n", pThis->periodic_list_base);
+
+ /* Current asynchronous list address register */
+ pHlp->pfnPrintf(pHlp, "ASYNCLISTADDR: %RX32\n", pThis->async_list_base);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (uPort = 0; uPort < EHCI_NDP_CFG(pThis); ++uPort)
+ {
+ PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort];
+ pHlp->pfnPrintf(pHlp, "PORTSC for port %u:\n", uPort);
+ if (pPort->fReg & EHCI_PORT_CURRENT_CONNECT)
+ pHlp->pfnPrintf(pHlp, " PORT_CURRENT_CONNECT\n");
+ if (pPort->fReg & EHCI_PORT_CONNECT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_CONNECT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_PORT_ENABLED)
+ pHlp->pfnPrintf(pHlp, " PORT_PORT_ENABLED\n");
+ if (pPort->fReg & EHCI_PORT_PORT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_PORT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_OVER_CURRENT_ACTIVE)
+ pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_ACTIVE\n");
+ if (pPort->fReg & EHCI_PORT_OVER_CURRENT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_FORCE_PORT_RESUME)
+ pHlp->pfnPrintf(pHlp, " PORT_FORCE_PORT_RESUME\n");
+ if (pPort->fReg & EHCI_PORT_SUSPEND)
+ pHlp->pfnPrintf(pHlp, " PORT_SUSPEND\n");
+ if (pPort->fReg & EHCI_PORT_RESET)
+ pHlp->pfnPrintf(pHlp, " PORT_RESET\n");
+ pHlp->pfnPrintf(pHlp, " LINE_STATUS: ");
+ switch ((pPort->fReg & EHCI_PORT_LINE_STATUS_MASK) >> EHCI_PORT_LINE_STATUS_SHIFT)
+ {
+ case 0:
+ pHlp->pfnPrintf(pHlp, " SE0 (0), not low-speed\n");
+ break;
+ case 1:
+ pHlp->pfnPrintf(pHlp, " K-state (1), low-speed device\n");
+ break;
+ case 2:
+ pHlp->pfnPrintf(pHlp, " J-state (2), not low-speed\n");
+ break;
+ default:
+ case 3:
+ pHlp->pfnPrintf(pHlp, " Undefined (3)\n");
+ break;
+ }
+ if (pPort->fReg & EHCI_PORT_POWER)
+ pHlp->pfnPrintf(pHlp, " PORT_POWER\n");
+ if (pPort->fReg & EHCI_PORT_OWNER)
+ pHlp->pfnPrintf(pHlp, " PORT_OWNER (1 = owned by companion HC)\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_ON_CONNECT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_CONNECT_ENABLE\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_DISCONNECT_ENABLE\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_OVER_CURRENT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_OVER_CURRENT_ENABLE\n");
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) ehciR3Reset(PPDMDEVINS pDevIns)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ /*
+ * There is no distinction between cold boot, warm reboot and software reboots,
+ * all of these are treated as cold boots. We are also doing the initialization
+ * job of a BIOS or SMM driver.
+ *
+ * Important: Don't confuse UsbReset with hardware reset. Hardware reset is
+ * just one way of getting into the UsbReset state.
+ */
+ ehciR3BusStop(pThis, pThisCC);
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */);
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ehciR3Resume(PPDMDEVINS pDevIns)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ /* Restart the frame thread if the timer is active. */
+ if (pThis->fBusStarted)
+ {
+ LogFlowFunc(("Bus was active, restart frame thread\n"));
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) ehciR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ if (pThisCC->hSemEventFrame != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThisCC->hSemEventFrame);
+ pThisCC->hSemEventFrame = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (pThisCC->hSemEventFrameStopped != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThisCC->hSemEventFrameStopped);
+ pThisCC->hSemEventFrameStopped = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (RTCritSectIsInitialized(&pThisCC->CritSect))
+ RTCritSectDelete(&pThisCC->CritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CsIrq);
+
+ /*
+ * Tear down the per endpoint in-flight tracking...
+ */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct,EHCI constructor}
+ */
+static DECLCALLBACK(int) ehciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ LogFlowFunc(("\n"));
+
+ /*
+ * Read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "DefaultFrameRateKHz|Ports", "");
+
+ /* Frame rate option. */
+ int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "DefaultFrameRateKHz", &pThisCC->uFrameRateDefault, EHCI_DEFAULT_TIMER_FREQ / 1000);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("EHCI configuration error: failed to read DefaultFrameRateKHz as integer"));
+
+ if ( pThisCC->uFrameRateDefault > EHCI_HARDWARE_TIMER_FREQ / 1000
+ || pThisCC->uFrameRateDefault == 0)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("EHCI configuration error: DefaultFrameRateKHz must be in range [%u,%u]"),
+ 1, EHCI_HARDWARE_TIMER_FREQ / 1000);
+
+ /* Convert to Hertz. */
+ pThisCC->uFrameRateDefault *= 1000;
+
+ /* Number of ports option. */
+ uint32_t cPorts;
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, EHCI_NDP_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("EHCI configuration error: failed to read Ports as integer"));
+
+ if (cPorts == 0 || cPorts > EHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("EHCI configuration error: Ports must be in range [%u,%u]"),
+ 1, EHCI_NDP_MAX);
+
+ /*
+ * Init instance data.
+ */
+ pThisCC->pDevIns = pDevIns;
+
+ /* Intel 82801FB/FBM USB2 controller */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, 0x8086);
+ PDMPciDevSetDeviceId(pPciDev, 0x265C);
+ PDMPciDevSetClassProg(pPciDev, 0x20); /* EHCI */
+ PDMPciDevSetClassSub(pPciDev, 0x03);
+ PDMPciDevSetClassBase(pPciDev, 0x0c);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+#endif
+ PDMPciDevSetByte(pPciDev, 0x60, 0x20); /* serial bus release number register; 0x20 = USB 2.0 */
+ /** @todo USBLEGSUP & USBLEGCTLSTS? Legacy interface for the BIOS (0xEECP+0 & 0xEECP+4) */
+
+ pThisCC->RootHub.IBase.pfnQueryInterface = ehciR3RhQueryInterface;
+ pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ehciR3RhGetAvailablePorts;
+ pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ehciR3RhGetUSBVersions;
+ pThisCC->RootHub.IRhPort.pfnAttach = ehciR3RhAttach;
+ pThisCC->RootHub.IRhPort.pfnDetach = ehciR3RhDetach;
+ pThisCC->RootHub.IRhPort.pfnReset = ehciR3RhReset;
+ pThisCC->RootHub.IRhPort.pfnXferCompletion = ehciR3RhXferCompletion;
+ pThisCC->RootHub.IRhPort.pfnXferError = ehciR3RhXferError;
+
+ /* USB LED */
+ pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC;
+ pThisCC->RootHub.ILeds.pfnQueryStatusLed = ehciR3RhQueryStatusLed;
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x80;
+ MsiReg.iMsiNextOffset = 0x00;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ PDMPciDevSetCapabilityList(pPciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0 /*iPciRegion*/, 4096 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM,
+ ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED
+ | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE,
+ "USB EHCI", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+ /* Initialize capability registers */
+ pThis->cap_length = EHCI_CAPS_REG_SIZE;
+ pThis->hci_version = 0x100;
+ /* 31:24 Reserved
+ * 23:20 Debug Port Number
+ * 19:17 Reserved
+ * 16 Port indicators (P_INDICATOR) enabled/disabled
+ * 15:12 Number of companion controllers (N_CC)
+ * 11:8 Number of ports per companion controller (N_PCC)
+ * 7 Port routing controls enabled/disabled
+ * 6:5 Reserved
+ * 4 Port power control enabled/disabled -> disabled to simplify matters!
+ * 3:0 N_PORTS; number of ports
+ */
+ /* Currently only number of ports specified */
+ pThis->hcs_params = cPorts;
+
+ /* 31:16 Reserved
+ * 15:8 EHCI extended capabilities pointer (EECP) (0x40 or greater)
+ * 7:4 Isochronous scheduling threshold
+ * 3 Reserved
+ * 2 Asynchronous schedule park capability (allow several TDs to be handled per async queue head)
+ * 1 Programmable frame list flag (0=1024 frames fixed)
+ * 0 64 bits addressability
+ */
+ pThis->hcc_params = EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING | EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING;
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, EHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, ehciR3SaveExec, NULL,
+ NULL, ehciLoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #0.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->RootHub.IBase, &pThisCC->RootHub.pIBase, "RootHub");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n"));
+ return rc;
+ }
+ pThisCC->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIROOTHUBCONNECTOR);
+ AssertMsgReturn(pThisCC->RootHub.pIRhConn,
+ ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->RootHub.IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThisCC->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else
+ AssertLogRelMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc), rc);
+
+ /* Set URB parameters. */
+ rc = VUSBIRhSetUrbParams(pThisCC->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to set URB parameters"));
+
+ /*
+ * Calculate the timer intervals.
+ * This ASSUMES that the VM timer doesn't change frequency during the run.
+ */
+ pThisCC->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns);
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+ LogFunc(("cTicksPerFrame=%RU64 cTicksPerUsbTick=%RU64\n", pThisCC->cTicksPerFrame, pThisCC->cTicksPerUsbTick));
+
+ pThis->fBusStarted = false;
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "EHCI#%uIrq", iInstance);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("EHCI: Failed to create critical section"));
+
+ rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrame);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrameStopped);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectInit(&pThisCC->CritSect);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create critical section"));
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->hThreadFrame, pThisCC, ehciR3ThreadFrame,
+ ehciR3ThreadFrameWakeup, 0, RTTHREADTYPE_IO, "EhciFramer");
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create worker thread"));
+
+ /*
+ * Do a hardware reset.
+ */
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, false /* don't reset devices */);
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled).");
+#endif
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ehci", "EHCI control registers.", ehciR3InfoRegs);
+
+#ifdef DEBUG_sandervl
+// g_fLogInterruptEPs = true;
+ g_fLogControlEPs = true;
+#endif
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ehciRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+const PDMDEVREG g_DeviceEHCI =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "usb-ehci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(EHCI),
+ /* .cbInstanceCC = */ sizeof(EHCICC),
+ /* .cbInstanceRC = */ sizeof(EHCIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "EHCI USB controller.\n",
+#if defined(IN_RING3)
+# ifdef VBOX_IN_EXTPACK
+ /* .pszRCMod = */ "VBoxEhciRC.rc",
+ /* .pszR0Mod = */ "VBoxEhciR0.r0",
+# else
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+# endif
+ /* .pfnConstruct = */ ehciR3Construct,
+ /* .pfnDestruct = */ ehciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ehciR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ ehciR3Resume,
+ /* .pfnAttach = */ NULL,
+ /* .pfnDetach = */ NULL,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ehciRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ehciRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#ifdef VBOX_IN_EXTPACK
+extern "C" const PDMDEVREG g_DeviceXHCI;
+
+# ifdef VBOX_IN_EXTPACK_R3
+
+/**
+ * @callback_method_impl{FNPDMVBOXDEVICESREGISTER}
+ */
+extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version)
+{
+ AssertLogRelMsgReturn(u32Version >= VBOX_VERSION,
+ ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION),
+ VERR_EXTPACK_VBOX_VERSION_MISMATCH);
+ AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION,
+ ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION),
+ VERR_VERSION_MISMATCH);
+
+ int rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceEHCI);
+
+ /* EHCI and xHCI devices live in the same module. */
+ extern const PDMDEVREG g_DeviceXHCI;
+ if (RT_SUCCESS(rc))
+ rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceXHCI);
+
+ return rc;
+}
+
+# else
+
+/** Pointer to the ring-0 device registrations for VBoxEhciR0/RC. */
+static PCPDMDEVREGR0 g_apDevRegs[] =
+{
+ &g_DeviceEHCI,
+ &g_DeviceXHCI,
+};
+
+/** Module device registration record for VBoxEhciR0/RC. */
+static PDMDEVMODREGR0 g_ModDevReg =
+{
+ /* .u32Version = */ PDM_DEVMODREGR0_VERSION,
+ /* .cDevRegs = */ RT_ELEMENTS(g_apDevRegs),
+ /* .papDevRegs = */ &g_apDevRegs[0],
+ /* .hMod = */ NULL,
+ /* .ListEntry = */ { NULL, NULL },
+};
+
+
+DECLEXPORT(int) ModuleInit(void *hMod)
+{
+ LogFlow(("VBoxEhciRZ/ModuleInit: %p\n", hMod));
+ return PDMR0DeviceRegisterModule(hMod, &g_ModDevReg);
+}
+
+
+DECLEXPORT(void) ModuleTerm(void *hMod)
+{
+ LogFlow(("VBoxEhciRZ/ModuleTerm: %p\n", hMod));
+ PDMR0DeviceDeregisterModule(hMod, &g_ModDevReg);
+}
+
+# endif
+#endif /* VBOX_IN_EXTPACK */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+