diff options
Diffstat (limited to 'src/VBox/Devices/USB/DevEHCI.cpp')
-rw-r--r-- | src/VBox/Devices/USB/DevEHCI.cpp | 5233 |
1 files changed, 5233 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..ad5fde70 --- /dev/null +++ b/src/VBox/Devices/USB/DevEHCI.cpp @@ -0,0 +1,5233 @@ +/* $Id: DevEHCI.cpp $ */ +/** @file + * DevEHCI - Enhanced Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2006-2022 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. + * + * @returns nothing. + * @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. + * + * @returns VBox status code. + * @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 */ + |