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