diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 16:49:04 +0000 |
commit | 16f504a9dca3fe3b70568f67b7d41241ae485288 (patch) | |
tree | c60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/USB | |
parent | Initial commit. (diff) | |
download | virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip |
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/USB')
38 files changed, 40930 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevEHCI.cpp b/src/VBox/Devices/USB/DevEHCI.cpp new file mode 100644 index 00000000..ad5fde70 --- /dev/null +++ b/src/VBox/Devices/USB/DevEHCI.cpp @@ -0,0 +1,5233 @@ +/* $Id: DevEHCI.cpp $ */ +/** @file + * DevEHCI - Enhanced Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_ehci EHCI - Enhanced Host Controller Interface Emulation. + * + * This component implements an EHCI USB controller. It is split roughly + * into two main parts, the first part implements the register level + * specification of USB EHCI and the second part maintains the root hub (which + * is an integrated component of the device). + * + * The EHCI registers are used for the usual stuff like enabling and disabling + * interrupts. Since the USB time is divided in to 1ms frames and various + * interrupts may need to be triggered at frame boundary time, a timer-based + * approach was taken. + * + * Note that all processing is currently done on a frame boundary and + * no attempt is made to emulate events with micro-frame granularity. + * + * The actual USB transfers are stored in main memory (along with endpoint and + * transfer descriptors). The ED's for all the control and bulk endpoints are + * found by consulting the HcAsyncListAddr register (ASYNCLISTADDR). + * Interrupt and isochronous ED's are found by looking at the HcPeriodicListBase + * (PERIODICLISTBASE) register. + * + * At the start of every frame (in function ehciR3StartOfFrame) we traverse all + * enabled ED lists and queue up as many transfers as possible. No attention + * is paid to control/bulk service ratios or bandwidth requirements since our + * USB could conceivably contain a dozen high speed busses and this would + * artificially limit the performance. + * + * Once we have a transfer ready to go (in the appropriate ehciServiceXxx function) + * we allocate an URB on the stack, fill in all the relevant fields and submit + * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual + * USB core code coordinates everything else from this point onwards. + * + * When the URB has been successfully handed to the lower level driver, our + * prepare callback gets called and we can remove the TD from the ED transfer + * list. This stops us queueing it twice while it completes. + * bird: no, we don't remove it because that confuses the guest! (=> crashes) + * + * Completed URBs are reaped at the end of every frame (in function + * ehciR3FrameBoundaryTimer). Our completion routine makes use of the ED and TD + * fields in the URB to store the physical addresses of the descriptors so + * that they may be modified in the roothub callbacks. Our completion + * routine (ehciRhXferCompleteXxx) carries out a number of tasks: + * -# Retires the TD associated with the transfer, setting the + * relevant error code etc. + * -# Updates done-queue interrupt timer and potentially causes + * a writeback of the done-queue. + * -# If the transfer was device-to-host, we copy the data into + * the host memory. + * + * As for error handling EHCI allows for 3 retries before failing a transfer, + * an error count is stored in each transfer descriptor. A halt flag is also + * stored in the transfer descriptor. That allows for ED's to be disabled + * without stopping the bus and de-queuing them. + * + * When the bus is started and stopped, we call VUSBIDevPowerOn/Off() on our + * roothub to indicate it's powering up and powering down. Whenever we power + * down, the USB core makes sure to synchronously complete all outstanding + * requests so that the EHCI is never seen in an inconsistent state by the + * guest OS (Transfers are not meant to be unlinked until they've actually + * completed, but we can't do that unless we work synchronously, so we just + * have to fake it). + * bird: we do work synchronously now, anything causes guest crashes. + * + * The number of ports is configurable. The architectural maximum is 15, but + * some guests (e.g. OS/2) crash if they see more than 12 or so ports. Saved + * states always include the data for all 15 ports but HCSPARAMS determines + * the actual number visible to the guest. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_EHCI +#include <VBox/pci.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/mm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/param.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/critsect.h> +#ifdef IN_RING3 +# include <iprt/thread.h> +# include <iprt/mem.h> +# include <iprt/uuid.h> +#endif +#include <VBox/vusb.h> +#ifdef VBOX_IN_EXTPACK_R3 +# include <VBox/version.h> +#elif defined(VBOX_IN_EXTPACK) +# include <VBox/sup.h> +#endif +#ifndef VBOX_IN_EXTPACK +# include "VBoxDD.h" +#endif + + +/** The saved state version. */ +#define EHCI_SAVED_STATE_VERSION 7 +/** The saved state version before the EOF timers were removed. */ +#define EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL 6 /* Introduced in 5.2. */ +/** The saved state with support of 8 ports. */ +#define EHCI_SAVED_STATE_VERSION_8PORTS 5 /* Introduced in 3.1 or so. */ + +/** Number of Downstream Ports on the root hub; 15 is the maximum + * the EHCI specification provides for. */ +#define EHCI_NDP_MAX 15 + +/** The default Number of Downstream Ports reported to guests. */ +#define EHCI_NDP_DEFAULT 12 + +/* Macro to query the number of currently configured ports. */ +#define EHCI_NDP_CFG(pehci) ((pehci)->hcs_params & EHCI_HCS_PARAMS_NDP_MASK) +/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */ +#define EHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1) + +/** Size of the capability part of the MMIO page. */ +#define EHCI_CAPS_REG_SIZE 0x20 + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * Host controller Transfer Descriptor data. + */ +typedef struct VUSBURBHCITDINT +{ + /** Type of TD. */ + uint32_t TdType; + /** The address of the TD. */ + RTGCPHYS TdAddr; + /** A copy of the TD. */ + uint32_t TdCopy[16]; +} VUSBURBHCITDINT; + +/** + * The host controller data associated with each URB. + */ +typedef struct VUSBURBHCIINT +{ + /** The endpoint descriptor address. */ + RTGCPHYS EdAddr; + /** Number of Tds in the array. */ + uint32_t cTds; + /** When this URB was created. + * (Used for isochronous frames and for logging.) */ + uint32_t u32FrameNo; + /** Flag indicating that the TDs have been unlinked. */ + bool fUnlinked; +} VUSBURBHCIINT; +#endif + +/** + * An EHCI root hub port, shared. + */ +typedef struct EHCIHUBPORT +{ + /** The port register. */ + uint32_t fReg; +} EHCIHUBPORT; +/** Pointer to a shared EHCI root hub port. */ +typedef EHCIHUBPORT *PEHCIHUBPORT; + +/** + * An EHCI root hub port, ring-3. + */ +typedef struct EHCIHUBPORTR3 +{ + /** Flag whether there is a device attached to the port. */ + bool fAttached; +} EHCIHUBPORTR3; +/** Pointer to a ring-3 EHCI root hub port. */ +typedef EHCIHUBPORTR3 *PEHCIHUBPORTR3; + + +/** + * The EHCI root hub, shared. + */ +typedef struct EHCIROOTHUB +{ + /** Per-port state. */ + EHCIHUBPORT aPorts[EHCI_NDP_MAX]; + /** Unused, only needed for saved state compatibility. */ + uint32_t unused; +} EHCIROOTHUB; +/** Pointer to the EHCI root hub. */ +typedef EHCIROOTHUB *PEHCIROOTHUB; + + +/** + * The EHCI root hub, ring-3 edition. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + * @implements PDMILEDPORTS + */ +typedef struct EHCIROOTHUBR3 +{ + /** Pointer to the base interface of the VUSB RootHub. */ + R3PTRTYPE(PPDMIBASE) pIBase; + /** Pointer to the connector interface of the VUSB RootHub. */ + R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn; + /** The base interface exposed to the roothub driver. */ + PDMIBASE IBase; + /** The roothub port interface exposed to the roothub driver. */ + VUSBIROOTHUBPORT IRhPort; + + /** The LED. */ + PDMLED Led; + /** The LED ports. */ + PDMILEDPORTS ILeds; + /** Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + EHCIHUBPORTR3 aPorts[EHCI_NDP_MAX]; +} EHCIROOTHUBR3; +/** Pointer to the ring-3 EHCI root hub state. */ +typedef EHCIROOTHUBR3 *PEHCIROOTHUBR3; + + +/** + * EHCI device data, shared. + */ +typedef struct EHCI +{ + /** Async scheduler sleeping; triggered by empty list detection */ + bool fAsyncTraversalTimerActive; + + bool afAlignment0[7]; + + /** Start of current frame. */ + uint64_t SofTime; + /** Root hub device */ + EHCIROOTHUB RootHub; + + /** @name Host Controller Capability Registers (R/O) + * @{ */ + /** CAPLENGTH: base + cap_length = operational register start */ + uint32_t cap_length; + /** HCIVERSION: host controller interface version */ + uint32_t hci_version; + /** HCSPARAMS: Structural parameters */ + uint32_t hcs_params; + /** HCCPARAMS: Capability parameters */ + uint32_t hcc_params; + /** @} */ + + /** @name Host Controller Operational Registers (R/W) + * @{ */ + /** USB command register */ + uint32_t cmd; + /** USB status register */ + uint32_t intr_status; + /** USB interrupt enable register */ + uint32_t intr; + /** Frame index register; actually it's micro-frame number */ + uint32_t frame_idx; + /** Control Data Structure Segment Register */ + uint32_t ds_segment; + /** Periodic Frame List Base Address Register */ + uint32_t periodic_list_base; + /** Current Asynchronous List Address Register */ + uint32_t async_list_base; + /** Configure Flag Register */ + uint32_t config; + /** @} */ + + /** @name Control partition (registers) + * @{ */ + /** Interrupt interval; see interrupt threshold in the command register */ + uint32_t uIrqInterval; + /** @} */ + + /** @name Frame counter partition (registers) + * @{ */ + /** HcFmNumber. + * @remark The register size is 16-bit, but for debugging and performance + * reasons we maintain a 32-bit counter. */ + uint32_t HcFmNumber; + /** Number of micro-frames per timer call */ + uint32_t uFramesPerTimerCall; + /** @} */ + + /** Flag whether the framer thread should processing frames. */ + volatile bool fBusStarted; + + bool afAlignment1[3]; /**< Align CsIrq correctly. */ + + /* The following members are not part of saved state. */ + + /** Critical section synchronising interrupt handling. */ + PDMCRITSECT CsIrq; + + /** The MMIO region. */ + IOMMMIOHANDLE hMmio; +} EHCI; +AssertCompileMemberAlignment(EHCI, CsIrq, 8); +/** Pointer to shared EHCI device data. */ +typedef struct EHCI *PEHCI; + + +/** + * EHCI device data, ring-3 edition. + */ +typedef struct EHCIR3 +{ + /** Root hub device. */ + EHCIROOTHUBR3 RootHub; + + /** The number of virtual time ticks per frame. */ + uint64_t cTicksPerFrame; + /** The number of virtual time ticks per USB bus tick. */ + uint64_t cTicksPerUsbTick; + + /** Pointer to the device instance. */ + PPDMDEVINSR3 pDevIns; + + /** Number of in-flight TDs. */ + unsigned cInFlight; + unsigned Alignment2; /**< Align aInFlight on a 8 byte boundary. */ + /** Array of in-flight TDs. */ + struct ehci_td_in_flight + { + /** Address of the transport descriptor. */ + RTGCPHYS GCPhysTD; + /** Pointer to the URB. */ + R3PTRTYPE(PVUSBURB) pUrb; + } aInFlight[257]; + + /** Detected canceled isochronous URBs. */ + STAMCOUNTER StatCanceledIsocUrbs; + /** Detected canceled general URBs. */ + STAMCOUNTER StatCanceledGenUrbs; + /** Dropped URBs (endpoint halted, or URB canceled). */ + STAMCOUNTER StatDroppedUrbs; + + /* The following members are not part of saved state. */ + + /** VM timer frequency used for frame timer calculations. */ + uint64_t u64TimerHz; + /** Number of USB work cycles with no transfers. */ + uint32_t cIdleCycles; + /** Current frame timer rate (default 1000). */ + uint32_t uFrameRate; + /** Idle detection flag; must be cleared at start of frame */ + bool fIdle; + bool afAlignment4[3]; + + /** Default frequency of the frame timer. */ + uint32_t uFrameRateDefault; + /** How long to wait until the next frame. */ + uint64_t nsWait; + /** The framer thread. */ + R3PTRTYPE(PPDMTHREAD) hThreadFrame; + /** Event semaphore to interact with the framer thread. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrame; + /** Event semaphore to release the thread waiting for the framer thread to stop. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrameStopped; + /** Critical section to synchronize the framer and URB completion handler. */ + RTCRITSECT CritSect; +} EHCIR3; +/** Pointer to ring-3 EHCI device data. */ +typedef struct EHCIR3 *PEHCIR3; + + +/** + * EHCI device data, ring-0 edition. + */ +typedef struct EHCIR0 +{ + uint32_t uUnused; +} EHCIR0; +/** Pointer to ring-0 EHCI device data. */ +typedef struct EHCIR0 *PEHCIR0; + + +/** + * EHCI device data, raw-mode edition. + */ +typedef struct EHCIRC +{ + uint32_t uUnused; +} EHCIRC; +/** Pointer to raw-mode EHCI device data. */ +typedef struct EHCIRC *PEHCIRC; + + +/** @typedef EHCICC + * The EHCI device data for the current context. */ +typedef CTX_SUFF(EHCI) EHCICC; +/** @typedef PEHCICC + * Pointer to the EHCI device for the current context. */ +typedef CTX_SUFF(PEHCI) PEHCICC; + + +/** @@name EHCI Transfer Descriptor Types + * @{ */ +/** Isochronous Transfer Descriptor */ +#define EHCI_DESCRIPTOR_ITD 0 +/** Queue Head */ +#define EHCI_DESCRIPTOR_QH 1 +/** Split Transaction Isochronous Transfer Descriptor */ +#define EHCI_DESCRIPTOR_SITD 2 +/** Frame Span Traversal Node */ +#define EHCI_DESCRIPTOR_FSTN 3 +/** @} */ + +/** @@name EHCI Transfer service type + * @{ */ +typedef enum +{ + EHCI_SERVICE_PERIODIC = 0, + EHCI_SERVICE_ASYNC = 1 +} EHCI_SERVICE_TYPE; +/** @} */ + +/** @@name EHCI Frame List Element Pointer + * @{ */ +#define EHCI_FRAME_LIST_NEXTPTR_SHIFT 5 + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Type : 2; + uint32_t Reserved : 2; + uint32_t FrameAddr : 27; +} EHCI_FRAME_LIST_PTR; +AssertCompileSize(EHCI_FRAME_LIST_PTR, 4); +/** @} */ + +/** @@name EHCI Isochronous Transfer Descriptor (iTD) + * @{ */ +#define EHCI_TD_PTR_SHIFT 5 + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Type : 2; + uint32_t Reserved : 2; + uint32_t Pointer : 27; +} EHCI_TD_PTR; +AssertCompileSize(EHCI_TD_PTR, 4); + +typedef struct +{ + uint32_t Offset : 12; + uint32_t PG : 3; + uint32_t IOC : 1; + uint32_t Length : 12; + uint32_t TransactError : 1; + uint32_t Babble : 1; + uint32_t DataBufError : 1; + uint32_t Active : 1; +} EHCI_ITD_TRANSACTION; +AssertCompileSize(EHCI_ITD_TRANSACTION, 4); + +typedef struct +{ + uint32_t DeviceAddress : 7; + uint32_t Reserved1 : 1; + uint32_t EndPt : 4; + uint32_t Ignore1 : 20; + uint32_t MaxPacket : 11; + uint32_t DirectionIn : 1; + uint32_t Ignore2 : 20; + uint32_t Multi : 2; + uint32_t Reserved10 : 10; + uint32_t Ignore3 : 20; +} EHCI_ITD_MISC; +AssertCompileSize(EHCI_ITD_MISC, 12); + +#define EHCI_BUFFER_PTR_SHIFT 12 + +typedef struct +{ + uint32_t Reserved : 12; + uint32_t Pointer : 20; /* 4k aligned */ +} EHCI_BUFFER_PTR; +AssertCompileSize(EHCI_BUFFER_PTR, 4); + +#define EHCI_NUM_ITD_TRANSACTIONS 8 +#define EHCI_NUM_ITD_PAGES 7 + +typedef struct +{ + EHCI_TD_PTR Next; + EHCI_ITD_TRANSACTION Transaction[EHCI_NUM_ITD_TRANSACTIONS]; + union + { + EHCI_ITD_MISC Misc; + EHCI_BUFFER_PTR Buffer[EHCI_NUM_ITD_PAGES]; + } Buffer; +} EHCI_ITD, *PEHCI_ITD; +typedef const EHCI_ITD *PEHCI_CITD; +AssertCompileSize(EHCI_ITD, 0x40); +/** @} */ + +/* ITD with extra padding to add 8th 'Buffer' entry. The PG member of + * EHCI_ITD_TRANSACTION can contain values in the 0-7 range, but only values + * 0-6 are valid. The extra padding is added to avoid cluttering the code + * with range checks; ehciR3ReadItd() initializes the pad with a safe value. + * The EHCI 1.0 specification explicitly says using PG value of 7 yields + * undefined behavior. + */ +typedef struct +{ + EHCI_ITD itd; + EHCI_BUFFER_PTR pad; +} EHCI_ITD_PAD, *PEHCI_ITD_PAD; +AssertCompileSize(EHCI_ITD_PAD, 0x44); + +/** @name Split Transaction Isochronous Transfer Descriptor (siTD) + * @{ */ +typedef struct +{ + uint32_t DeviceAddress : 7; + uint32_t Reserved : 1; + uint32_t EndPt : 4; + uint32_t Reserved2 : 4; + uint32_t HubAddress : 7; + uint32_t Reserved3 : 1; + uint32_t Port : 7; + uint32_t DirectionIn : 1; +} EHCI_SITD_ADDR; +AssertCompileSize(EHCI_SITD_ADDR, 4); + +typedef struct +{ + uint32_t SMask : 8; + uint32_t CMask : 8; + uint32_t Reserved : 16; +} EHCI_SITD_SCHEDCTRL; +AssertCompileSize(EHCI_SITD_SCHEDCTRL, 4); + +typedef struct +{ + /* 8 Status flags */ + uint32_t Reserved : 1; + uint32_t SplitXState : 1; + uint32_t MisseduFrame : 1; + uint32_t TransactError : 1; + uint32_t Babble : 1; + uint32_t DataBufError : 1; + uint32_t Error : 1; + uint32_t Active : 1; + uint32_t CPMask : 8; + uint32_t Length : 10; + uint32_t Reserved4 : 4; + uint32_t PageSelect : 1; + uint32_t IOC : 1; +} EHCI_SITD_TRANSFER; +AssertCompileSize(EHCI_SITD_TRANSFER, 4); + +typedef struct +{ + uint32_t Offset : 12; + uint32_t Pointer : 20; /**< 4k aligned */ +} EHCI_SITD_BUFFER0; +AssertCompileSize(EHCI_SITD_BUFFER0, 4); + +typedef struct +{ + uint32_t TCount : 3; + uint32_t TPosition : 2; + uint32_t Reserved : 7; + uint32_t Pointer : 20; /**< 4k aligned */ +} EHCI_SITD_BUFFER1; +AssertCompileSize(EHCI_SITD_BUFFER1, 4); + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Reserved : 4; + uint32_t Pointer : 27; +} EHCI_SITD_BACKPTR; +AssertCompileSize(EHCI_SITD_BACKPTR, 4); + +typedef struct +{ + EHCI_TD_PTR NextSITD; + EHCI_SITD_ADDR Address; + EHCI_SITD_SCHEDCTRL ScheduleCtrl; + EHCI_SITD_TRANSFER Transfer; + EHCI_SITD_BUFFER0 Buffer0; + EHCI_SITD_BUFFER1 Buffer1; + EHCI_SITD_BACKPTR BackPtr; +} EHCI_SITD, *PEHCI_SITD; +typedef const EHCI_SITD *PEHCI_CSITD; +AssertCompileSize(EHCI_SITD, 0x1C); +/** @} */ + + +/** @name Queue Element Transfer Descriptor (qTD) + * @{ */ +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Reserved : 4; + uint32_t Pointer : 27; +} EHCI_QTD_NEXTPTR; +AssertCompileSize(EHCI_QTD_NEXTPTR, 4); + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Reserved : 4; + uint32_t Pointer : 27; +} EHCI_QTD_ALTNEXTPTR; +AssertCompileSize(EHCI_QTD_ALTNEXTPTR, 4); + +#define EHCI_QTD_PID_OUT 0 +#define EHCI_QTD_PID_IN 1 +#define EHCI_QTD_PID_SETUP 2 + +typedef struct +{ + /* 8 Status flags */ + uint32_t PingState : 1; + uint32_t SplitXState : 1; + uint32_t MisseduFrame : 1; + uint32_t TransactError : 1; + uint32_t Babble : 1; + uint32_t DataBufError : 1; + uint32_t Halted : 1; + uint32_t Active : 1; + uint32_t PID : 2; + uint32_t ErrorCount : 2; + uint32_t CurrentPage : 3; + uint32_t IOC : 1; + uint32_t Length : 15; + uint32_t DataToggle : 1; +} EHCI_QTD_TOKEN; +AssertCompileSize(EHCI_QTD_TOKEN, 4); + +#define EHCI_QTD_HAS_ERROR(pQtdToken) (*((uint32_t *)pQtdToken) & 0x7F) + +typedef struct +{ + uint32_t Offset : 12; + uint32_t Reserved : 20; + uint32_t Ignore[4]; +} EHCI_QTD_OFFSET; +AssertCompileSize(EHCI_QTD_OFFSET, 20); + +typedef struct +{ + EHCI_QTD_NEXTPTR Next; + EHCI_QTD_ALTNEXTPTR AltNext; + union + { + EHCI_QTD_TOKEN Bits; + uint32_t u32; + } Token; + union + { + EHCI_QTD_OFFSET Offset; + EHCI_BUFFER_PTR Buffer[5]; + } Buffer; +} EHCI_QTD, *PEHCI_QTD; +typedef const EHCI_QTD *PEHCI_CQTD; +AssertCompileSize(EHCI_QTD, 0x20); +/** @} */ + + +/** @name Queue Head Descriptor (QHD) + * @{ */ + +#define EHCI_QHD_EPT_SPEED_FULL 0 /**< 12 Mbps */ +#define EHCI_QHD_EPT_SPEED_LOW 1 /**< 1.5 Mbps */ +#define EHCI_QHD_EPT_SPEED_HIGH 2 /**< 480 Mbps */ +#define EHCI_QHD_EPT_SPEED_RESERVED 3 + +typedef struct +{ + uint32_t DeviceAddress : 7; + uint32_t InActiveNext : 1; + uint32_t EndPt : 4; + uint32_t EndPtSpeed : 2; + uint32_t DataToggle : 1; + uint32_t HeadReclamation : 1; + uint32_t MaxLength : 11; + uint32_t ControlEPFlag : 1; + uint32_t NakCountReload : 4; +} EHCI_QHD_EPCHARS; +AssertCompileSize(EHCI_QHD_EPCHARS, 4); + +typedef struct +{ + uint32_t SMask : 8; + uint32_t CMask : 8; + uint32_t HubAddress : 7; + uint32_t Port : 7; + uint32_t Mult : 2; +} EHCI_QHD_EPCAPS; +AssertCompileSize(EHCI_QHD_EPCAPS, 4); + +typedef struct +{ + uint32_t Reserved : 5; + uint32_t Pointer : 27; +} EHCI_QHD_CURRPTR; +AssertCompileSize(EHCI_QHD_CURRPTR, 4); + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t NakCnt : 4; + uint32_t Pointer : 27; +} EHCI_QHD_ALTNEXT; +AssertCompileSize(EHCI_QHD_ALTNEXT, 4); + +typedef struct +{ + uint32_t CProgMask : 8; + uint32_t Reserved : 4; + uint32_t Pointer : 20; /**< 4k aligned */ +} EHCI_QHD_BUFFER1; +AssertCompileSize(EHCI_QHD_BUFFER1, 4); + +typedef struct +{ + uint32_t FrameTag : 5; + uint32_t SBytes : 7; + uint32_t Pointer : 20; /**< 4k aligned */ +} EHCI_QHD_BUFFER2; +AssertCompileSize(EHCI_QHD_BUFFER2, 4); + +typedef struct +{ + EHCI_TD_PTR Next; + EHCI_QHD_EPCHARS Characteristics; + EHCI_QHD_EPCAPS Caps; + EHCI_QHD_CURRPTR CurrQTD; + union + { + EHCI_QTD OrgQTD; + struct + { + uint32_t Identical1[2]; + EHCI_QHD_ALTNEXT AltNextQTD; + uint32_t Identical2; + EHCI_QHD_BUFFER1 Buffer1; + EHCI_QHD_BUFFER2 Buffer2; + uint32_t Identical3[2]; + } Status; + } Overlay; +} EHCI_QHD, *PEHCI_QHD; +typedef const EHCI_QHD *PEHCI_CQHD; +AssertCompileSize(EHCI_QHD, 0x30); +/** @} */ + +/** @name Periodic Frame Span Traversal Node (FSTN) + * @{ */ + +typedef struct +{ + uint32_t Terminate : 1; + uint32_t Type : 2; + uint32_t Reserved : 2; + uint32_t Ptr : 27; +} EHCI_FSTN_PTR; +AssertCompileSize(EHCI_FSTN_PTR, 4); + +typedef struct +{ + EHCI_FSTN_PTR NormalPtr; + EHCI_FSTN_PTR BackPtr; +} EHCI_FSTN, *PEHCI_FSTN; +typedef const EHCI_FSTN *PEHCI_CFSTN; +AssertCompileSize(EHCI_FSTN, 8); + +/** @} */ + + +/** + * EHCI register operator. + */ +typedef struct EHCIOPREG +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t u32Value); +} EHCIOPREG; + + +/* EHCI Local stuff */ +#define EHCI_HCS_PARAMS_PORT_ROUTING_RULES RT_BIT(7) +#define EHCI_HCS_PARAMS_PORT_POWER_CONTROL RT_BIT(4) +#define EHCI_HCS_PARAMS_NDP_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2) | RT_BIT(3)) + +/* controller may cache an isochronous data structure for an entire frame */ +#define EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING RT_BIT(7) +#define EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING RT_BIT(2) +#define EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST RT_BIT(1) +#define EHCI_HCC_PARAMS_64BITS_ADDRESSING RT_BIT(0) + +/** @name Interrupt Enable Register bits (USBINTR) + * @{ */ +#define EHCI_INTR_ENABLE_THRESHOLD RT_BIT(0) +#define EHCI_INTR_ENABLE_ERROR RT_BIT(1) +#define EHCI_INTR_ENABLE_PORT_CHANGE RT_BIT(2) +#define EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER RT_BIT(3) +#define EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR RT_BIT(4) +#define EHCI_INTR_ENABLE_ASYNC_ADVANCE RT_BIT(5) +#define EHCI_INTR_ENABLE_MASK (EHCI_INTR_ENABLE_ASYNC_ADVANCE|EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR|EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER|EHCI_INTR_ENABLE_PORT_CHANGE|EHCI_INTR_ENABLE_ERROR|EHCI_INTR_ENABLE_THRESHOLD) +/** @} */ + +/** @name Configure Flag Register (CONFIGFLAG) + * @{ */ +#define EHCI_CONFIGFLAG_ROUTING RT_BIT(0) +#define EHCI_CONFIGFLAG_MASK EHCI_CONFIGFLAG_ROUTING +/** @} */ + +/** @name Status Register (USBSTS) + * @{ */ +#define EHCI_STATUS_ASYNC_SCHED RT_BIT(15) /* RO */ +#define EHCI_STATUS_PERIOD_SCHED RT_BIT(14) /* RO */ +#define EHCI_STATUS_RECLAMATION RT_BIT(13) /* RO */ +#define EHCI_STATUS_HCHALTED RT_BIT(12) /* RO */ +#define EHCI_STATUS_INT_ON_ASYNC_ADV RT_BIT(5) +#define EHCI_STATUS_HOST_SYSTEM_ERROR RT_BIT(4) +#define EHCI_STATUS_FRAME_LIST_ROLLOVER RT_BIT(3) +#define EHCI_STATUS_PORT_CHANGE_DETECT RT_BIT(2) +#define EHCI_STATUS_ERROR_INT RT_BIT(1) +#define EHCI_STATUS_THRESHOLD_INT RT_BIT(0) +#define EHCI_STATUS_INTERRUPT_MASK (EHCI_STATUS_THRESHOLD_INT|EHCI_STATUS_ERROR_INT|EHCI_STATUS_PORT_CHANGE_DETECT|EHCI_STATUS_FRAME_LIST_ROLLOVER|EHCI_STATUS_HOST_SYSTEM_ERROR|EHCI_STATUS_INT_ON_ASYNC_ADV) +/** @} */ + +#define EHCI_PERIODIC_LIST_MASK UINT32_C(0xFFFFF000) /**< 4kb aligned */ +#define EHCI_ASYNC_LIST_MASK UINT32_C(0xFFFFFFE0) /**< 32-byte aligned */ + + +/** @name Port Status and Control Register bits (PORTSC) + * @{ */ +#define EHCI_PORT_CURRENT_CONNECT RT_BIT(0) /**< RO */ +#define EHCI_PORT_CONNECT_CHANGE RT_BIT(1) +#define EHCI_PORT_PORT_ENABLED RT_BIT(2) +#define EHCI_PORT_PORT_CHANGE RT_BIT(3) +#define EHCI_PORT_OVER_CURRENT_ACTIVE RT_BIT(4) /**< RO */ +#define EHCI_PORT_OVER_CURRENT_CHANGE RT_BIT(5) +#define EHCI_PORT_FORCE_PORT_RESUME RT_BIT(6) +#define EHCI_PORT_SUSPEND RT_BIT(7) +#define EHCI_PORT_RESET RT_BIT(8) +#define EHCI_PORT_LINE_STATUS_MASK (RT_BIT(10) | RT_BIT(11)) /**< RO */ +#define EHCI_PORT_LINE_STATUS_SHIFT 10 +#define EHCI_PORT_POWER RT_BIT(12) +#define EHCI_PORT_OWNER RT_BIT(13) +#define EHCI_PORT_INDICATOR (RT_BIT(14) | RT_BIT(15)) +#define EHCI_PORT_TEST_CONTROL_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19)) +#define EHCI_PORT_TEST_CONTROL_SHIFT 16 +#define EHCI_PORT_WAKE_ON_CONNECT_ENABLE RT_BIT(20) +#define EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE RT_BIT(21) +#define EHCI_PORT_WAKE_OVER_CURRENT_ENABLE RT_BIT(22) +#define EHCI_PORT_RESERVED (RT_BIT(9)|RT_BIT(23)|RT_BIT(24)|RT_BIT(25)|RT_BIT(26)|RT_BIT(27)|RT_BIT(28)|RT_BIT(29)|RT_BIT(30)|RT_BIT(31)) + +#define EHCI_PORT_WAKE_MASK (EHCI_PORT_WAKE_ON_CONNECT_ENABLE|EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE|EHCI_PORT_WAKE_OVER_CURRENT_ENABLE) +#define EHCI_PORT_CHANGE_MASK (EHCI_PORT_CONNECT_CHANGE|EHCI_PORT_PORT_CHANGE|EHCI_PORT_OVER_CURRENT_CHANGE) +/** @} */ + +/** @name Command Register bits (USBCMD) + * @{ */ +#define EHCI_CMD_RUN RT_BIT(0) +#define EHCI_CMD_RESET RT_BIT(1) +#define EHCI_CMD_FRAME_LIST_SIZE_MASK (RT_BIT(2) | RT_BIT(3)) +#define EHCI_CMD_FRAME_LIST_SIZE_SHIFT 2 +#define EHCI_CMD_PERIODIC_SCHED_ENABLE RT_BIT(4) +#define EHCI_CMD_ASYNC_SCHED_ENABLE RT_BIT(5) +#define EHCI_CMD_INT_ON_ADVANCE_DOORBELL RT_BIT(6) +#define EHCI_CMD_SOFT_RESET RT_BIT(7) /**< optional */ +#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK (RT_BIT(8) | RT_BIT(9)) /**< optional */ +#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT 8 +#define EHCI_CMD_RESERVED RT_BIT(10) +#define EHCI_CMD_ASYNC_SCHED_PARK_ENABLE RT_BIT(11) /**< optional */ +#define EHCI_CMD_RESERVED2 (RT_BIT(12) | RT_BIT(13) | RT_BIT(14) | RT_BIT(15)) +#define EHCI_CMD_INTERRUPT_THRESHOLD_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19) | RT_BIT(20) | RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT 16 +#define EHCI_CMD_MASK (EHCI_CMD_INTERRUPT_THRESHOLD_MASK|EHCI_CMD_ASYNC_SCHED_PARK_ENABLE|EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK|EHCI_CMD_SOFT_RESET|EHCI_CMD_INT_ON_ADVANCE_DOORBELL|EHCI_CMD_ASYNC_SCHED_ENABLE|EHCI_CMD_PERIODIC_SCHED_ENABLE|EHCI_CMD_FRAME_LIST_SIZE_MASK|EHCI_CMD_RESET|EHCI_CMD_RUN) + +#define EHCI_DEFAULT_PERIODIC_LIST_SIZE 1024 +#define EHCI_DEFAULT_PERIODIC_LIST_MASK 0x3ff + +#define EHCI_FRINDEX_UFRAME_COUNT_MASK 0x7 +#define EHCI_FRINDEX_FRAME_INDEX_MASK EHCI_DEFAULT_PERIODIC_LIST_MASK +#define EHCI_FRINDEX_FRAME_INDEX_SHIFT 3 + +/** @} */ + +/* Local EHCI definitions */ +#define EHCI_USB_RESET 0x00 +#define EHCI_USB_RESUME 0x40 +#define EHCI_USB_OPERATIONAL 0x80 +#define EHCI_USB_SUSPEND 0xc0 + +#define EHCI_HARDWARE_TIMER_FREQ 8000 /**< 8000 hz = every 125 usec */ +#define EHCI_DEFAULT_TIMER_FREQ 1000 +#define EHCI_UFRAMES_PER_FRAME 8 + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(VBOX_IN_EXTPACK_R0) && defined(RT_OS_SOLARIS) +/* Dependency information for the native solaris loader. */ +extern "C" { char _depends_on[] = "vboxdrv VMMR0.r0"; } +#endif + +#if defined(LOG_ENABLED) && defined(IN_RING3) +static bool g_fLogControlEPs = false; +static bool g_fLogInterruptEPs = false; +#endif + +#ifdef IN_RING3 +/** + * SSM descriptor table for the EHCI structure. + */ +static SSMFIELD const g_aEhciFields[] = +{ + SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive), + SSMFIELD_ENTRY( EHCI, SofTime), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg), + SSMFIELD_ENTRY( EHCI, cap_length), + SSMFIELD_ENTRY( EHCI, hci_version), + SSMFIELD_ENTRY( EHCI, hcs_params), + SSMFIELD_ENTRY( EHCI, hcc_params), + SSMFIELD_ENTRY( EHCI, cmd), + SSMFIELD_ENTRY( EHCI, intr_status), + SSMFIELD_ENTRY( EHCI, intr), + SSMFIELD_ENTRY( EHCI, frame_idx), + SSMFIELD_ENTRY( EHCI, ds_segment), + SSMFIELD_ENTRY( EHCI, periodic_list_base), + SSMFIELD_ENTRY( EHCI, async_list_base), + SSMFIELD_ENTRY( EHCI, config), + SSMFIELD_ENTRY( EHCI, uIrqInterval), + SSMFIELD_ENTRY( EHCI, HcFmNumber), + SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall), + SSMFIELD_ENTRY( EHCI, fBusStarted), + SSMFIELD_ENTRY_TERM() +}; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +#ifdef IN_RING3 +/* Update host controller state to reflect a device attach */ +static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp); + +static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd); +static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate); +#endif /* IN_RING3 */ +RT_C_DECLS_END + +/** + * Update PCI IRQ levels + */ +static void ehciUpdateInterruptLocked(PPDMDEVINS pDevIns, PEHCI pThis, const char *msg) +{ + int level = 0; + + if (pThis->intr_status & pThis->intr) + level = 1; + + PDMDevHlpPCISetIrq(pDevIns, 0, level); + if (level) + { + uint32_t val = pThis->intr_status & pThis->intr; + + Log2Func(("Fired off interrupt %#010x - INT=%d ERR=%d PCD=%d FLR=%d HSE=%d IAA=%d - %s\n", + val, + !!(val & EHCI_STATUS_THRESHOLD_INT), + !!(val & EHCI_STATUS_ERROR_INT), + !!(val & EHCI_STATUS_PORT_CHANGE_DETECT), + !!(val & EHCI_STATUS_FRAME_LIST_ROLLOVER), + !!(val & EHCI_STATUS_HOST_SYSTEM_ERROR), + !!(val & EHCI_STATUS_INT_ON_ASYNC_ADV), + msg)); + RT_NOREF(val, msg); + + /* host controller must clear the EHCI_CMD_INT_ON_ADVANCE_DOORBELL bit after setting it in the status register */ + if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV) + ASMAtomicAndU32(&pThis->cmd, ~EHCI_CMD_INT_ON_ADVANCE_DOORBELL); + + } + else + Log2Func(("cleared interrupt\n")); +} + +/** + * Set an interrupt, use the wrapper ehciSetInterrupt. + */ +DECLINLINE(int) ehciSetInterruptInt(PPDMDEVINS pDevIns, PEHCI pThis, int rcBusy, uint32_t intr, const char *msg) +{ + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, rcBusy); + if (rc != VINF_SUCCESS) + return rc; + + if ( (pThis->intr_status & intr) != intr ) + { + ASMAtomicOrU32(&pThis->intr_status, intr); + ehciUpdateInterruptLocked(pDevIns, pThis, msg); + } + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return rc; +} + +/** + * Set an interrupt wrapper macro for logging purposes. + */ +#define ehciSetInterrupt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr) \ + ehciSetInterruptInt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr, #a_fIntr) +#define ehciR3SetInterrupt(a_pDevIns, a_pEhci, a_fIntr) \ + ehciSetInterruptInt(a_pDevIns, a_pEhci, VERR_IGNORED, a_fIntr, #a_fIntr) + +#ifdef IN_RING3 + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) ehciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThisCC->RootHub.IRhPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->RootHub.ILeds); + return NULL; +} + +/** + * Gets the pointer to the status LED of a unit. + * + * @returns VBox status code. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param iLUN The unit which status LED we desire. + * @param ppLed Where to store the LED pointer. + */ +static DECLCALLBACK(int) ehciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.ILeds); + if (iLUN == 0) + { + *ppLed = &pThisCC->RootHub.Led; + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** + * Get the number of avialable ports in the hub. + * + * @returns The number of ports available. + * @param pInterface Pointer to this structure. + * @param pAvailable Bitmap indicating the available ports. Set bit == available port. + */ +static DECLCALLBACK(unsigned) ehciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + + memset(pAvailable, 0, sizeof(*pAvailable)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + unsigned cPorts = 0; + for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++) + { + if (!pThisCC->RootHub.aPorts[iPort].fAttached) + { + cPorts++; + ASMBitSet(pAvailable, iPort + 1); + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return cPorts; +} + + +/** + * Gets the supported USB versions. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) ehciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_20; +} + + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) ehciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + LogFlowFunc(("uPort=%u\n", uPort)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis)); + uPort--; + Assert(!pThisCC->RootHub.aPorts[uPort].fAttached); + Assert(enmSpeed == VUSB_SPEED_HIGH); RT_NOREF(enmSpeed); /* Only HS devices should end up here! */ + + /* + * Attach it. + */ + ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_OWNER); /* not attached to a companion controller */ + ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE); + pThisCC->RootHub.aPorts[uPort].fAttached = true; + ehciR3PortPower(pThis, pThisCC, uPort, 1 /* power on */); + + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * A device is being detached from a port in the roothub. + * + * @param pInterface Pointer to this structure. + * @param uPort The port number assigned to the device. + */ +static DECLCALLBACK(void) ehciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + LogFlowFunc(("uPort=%u\n", uPort)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis)); + uPort--; + Assert(pThisCC->RootHub.aPorts[uPort].fAttached); + + /* + * Detach it. + */ + pThisCC->RootHub.aPorts[uPort].fAttached = false; + ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_CURRENT_CONNECT); + if (pThis->RootHub.aPorts[uPort].fReg & EHCI_PORT_PORT_ENABLED) + { + ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_PORT_ENABLED); + ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE | EHCI_PORT_PORT_CHANGE); + } + else + ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE); + + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); +} + + +/** + * One of the roothub devices has completed its reset operation. + * + * Currently, we don't think anything is required to be done here + * so it's just a stub for forcing async resetting of the devices + * during a root hub reset. + * + * @param pDev The root hub device. + * @param uPort The port number of the device on the roothub being resetted. + * @param rc The result of the operation. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) ehciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + LogRel(("EHCI: root hub reset completed with %Rrc\n", rc)); + RT_NOREF(pDev, uPort, rc, pvUser); +} + + +/** + * Does a software or hardware reset of the controller. + * + * This is called in response to setting HcCommandStatus.HCR, hardware reset, + * and device construction. + * + * @param pDevIns The device instance. + * @param pThis The shared EHCI instance data. + * @param pThisCC The ring-3 EHCI instance data. + * @param fNewMode The new mode of operation. This is UsbSuspend if it's a + * software reset, and UsbReset if it's a hardware reset / cold boot. + * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub. + * This is really a just a hack for the non-working linux device reset. + * Linux has this feature called 'logical disconnect' if device reset fails + * which prevents us from doing resets when the guest asks for it - the guest + * will get confused when the device seems to be reconnected everytime it tries + * to reset it. But if we're at hardware reset time, we can allow a device to + * be 'reconnected' without upsetting the guest. + * + * @remark This hasn't got anything to do with software setting the mode to UsbReset. + */ +static void ehciR3DoReset(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux) +{ + LogFunc(("%s reset%s\n", fNewMode == EHCI_USB_RESET ? "hardware" : "software", + fResetOnLinux ? " (reset on linux)" : "")); + + /* + * Cancel all outstanding URBs. + * + * We can't, and won't, deal with URBs until we're moved out of the + * suspend/reset state. Also, a real HC isn't going to send anything + * any more when a reset has been signaled. + * + * This must be done on the framer thread to avoid race conditions. + */ + pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn); + + /* + * Reset the hardware registers. + */ + /** @todo other differences between hardware reset and VM reset? */ + + if (pThis->hcc_params & EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING) + pThis->cmd = 0x80000 | EHCI_CMD_ASYNC_SCHED_PARK_ENABLE | (3 << EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT); + else + pThis->cmd = 0x80000; + + pThis->intr_status = EHCI_STATUS_HCHALTED; + pThis->intr = 0; + pThis->frame_idx = 0; + pThis->ds_segment = 0; + pThis->periodic_list_base = 0; /* undefined */ + pThis->async_list_base = 0; /* undefined */ + pThis->config = 0; + pThis->uIrqInterval = (pThis->intr_status & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT; + + /* We have to update interrupts as the IRQ may need to be cleared. */ + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CsIrq, rcLock); + + ehciUpdateInterruptLocked(pDevIns, pThis, "ehciR3DoReset"); + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + + ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault); + + if (fNewMode == EHCI_USB_RESET) + { + /* Only a hardware reset reinits the port registers */ + for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++) + { + if (pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL) + pThis->RootHub.aPorts[i].fReg = EHCI_PORT_OWNER; + else + pThis->RootHub.aPorts[i].fReg = EHCI_PORT_POWER | EHCI_PORT_OWNER; + } + } +/** @todo Shouldn't we stop the SOF timer at this point? */ + + /* + * If this is a hardware reset, we will initialize the root hub too. + * Software resets doesn't do this according to the specs. + * (It's not possible to have device connected at the time of the + * device construction, so nothing to worry about there.) + */ + if (fNewMode == EHCI_USB_RESET) + { + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux); + + /* + * Reattach the devices. + */ + for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++) + { + bool fAttached = pThisCC->RootHub.aPorts[i].fAttached; + pThisCC->RootHub.aPorts[i].fAttached = false; + + if (fAttached) + ehciR3RhAttach(&pThisCC->RootHub.IRhPort, i+1, VUSB_SPEED_HIGH); + } + } +} + +/** + * Reset the root hub. + * + * @returns VBox status code. + * @param pInterface Pointer to this structure. + * @param fResetOnLinux This is used to indicate whether we're at VM reset time and + * can do real resets or if we're at any other time where that + * isn't such a good idea. + * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion! + * @thread EMT + */ +static DECLCALLBACK(int) ehciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + LogFunc(("fResetOnLinux=%d\n", fResetOnLinux)); + + /* Soft reset first */ + ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */); + + /* + * We're pretending to _reattach_ the devices without resetting them. + * Except, during VM reset where we use the opportunity to do a proper + * reset before the guest comes along and expects things. + * + * However, it's very very likely that we're not doing the right thing + * here when end up here on request from the guest (USB Reset state). + * The docs talks about root hub resetting, however what exact behaviour + * in terms of root hub status and changed bits, and HC interrupts aren't + * stated clearly. IF we get trouble and see the guest doing "USB Resets" + * we will have to look into this. For the time being we stick with simple. + */ + for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++) + { + if (pThisCC->RootHub.aPorts[iPort].fAttached) + { + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE); + if (fResetOnLinux) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux, + ehciR3RhResetDoneOneDev, pThis, pVM); + } + } + } + + return VINF_SUCCESS; +} + + +/** + * Reads physical memory. + */ +DECLINLINE(void) ehciPhysRead(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf) +{ + PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf); +} + + +/** + * Reads physical memory - metadata. + */ +DECLINLINE(void) ehciPhysReadMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf) +{ + PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf); +} + + +/** + * Writes physical memory. + */ +DECLINLINE(void) ehciPhysWrite(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf) +{ + PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf); +} + + +/** + * Writes physical memory. + */ +DECLINLINE(void) ehciPhysWriteMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf) +{ + PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf); +} + + +/** + * Read an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ehciGetDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, uint32_t *pau32s, int c32s) +{ + ehciPhysReadMeta(pDevIns, Addr, pau32s, c32s * sizeof(uint32_t)); +# ifndef RT_LITTLE_ENDIAN + for(int i = 0; i < c32s; i++) + pau32s[i] = RT_H2LE_U32(pau32s[i]); +# endif +} + + +/** + * Write an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ehciPutDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, const uint32_t *pau32s, int cu32s) +{ +# ifdef RT_LITTLE_ENDIAN + ehciPhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2); +# else + for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s)) + { + uint32_t u32Tmp = RT_H2LE_U32(*pau32s); + ehciPhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp)); + } +# endif +} + + +DECLINLINE(void) ehciR3ReadFrameListPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_FRAME_LIST_PTR *pFrameList) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pFrameList, sizeof(*pFrameList) >> 2); +} + +DECLINLINE(void) ehciR3ReadTDPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_TD_PTR *pTD) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pTD, sizeof(*pTD) >> 2); +} + +DECLINLINE(void) ehciR3ReadItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD_PAD pPItd) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pPItd, sizeof(EHCI_ITD) >> 2); + pPItd->pad.Pointer = 0xFFFFF; /* Direct accesses at the last page under 4GB (ROM). */ +} + +DECLINLINE(void) ehciR3ReadSitd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_SITD pSitd) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pSitd, sizeof(*pSitd) >> 2); +} + +DECLINLINE(void) ehciR3WriteItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD pItd) +{ + /** @todo might need to be careful about write order in async io thread */ + /* + * Only write to the fields the controller is allowed to write to, + * namely the eight double words coming after the next link pointer. + */ + uint32_t offWrite = RT_OFFSETOF(EHCI_ITD, Transaction[0]); + uint32_t offDWordsWrite = offWrite / sizeof(uint32_t); + Assert(!(offWrite % sizeof(uint32_t))); + + ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pItd + offDWordsWrite, (sizeof(*pItd) >> 2) - offDWordsWrite); +} + +DECLINLINE(void) ehciR3ReadQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQHD, sizeof(*pQHD) >> 2); +} + +DECLINLINE(void) ehciR3ReadQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD) +{ + ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQTD, sizeof(*pQTD) >> 2); +} + +DECLINLINE(void) ehciR3WriteQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD) +{ + /** @todo might need to be careful about write order in async io thread */ + /* + * Only write to the fields the controller is allowed to write to, + * namely the two double words coming after the alternate next QTD pointer. + */ + uint32_t offWrite = RT_OFFSETOF(EHCI_QTD, Token.u32); + uint32_t offDWordsWrite = offWrite / sizeof(uint32_t); + Assert(!(offWrite % sizeof(uint32_t))); + + ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pQTD + offDWordsWrite, (sizeof(*pQTD) >> 2) - offDWordsWrite); +} + + +/** + * Updates the QHD in guest memory only updating portions of the QHD the controller + * is allowed to write to. + * + * @returns nothing. + * @param pDevIns The device instance. + * @param GCPhys Physical guest address of the QHD. + * @param pQHD The QHD to update the guest memory with. + */ +DECLINLINE(void) ehciR3UpdateQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD) +{ + /* + * Only update members starting from the current QTD pointer, everything + * before is readonly for the controller and the guest might have updated it + * behind our backs already. + */ + uint32_t offWrite = RT_OFFSETOF(EHCI_QHD, CurrQTD); + ehciPhysWriteMeta(pDevIns, GCPhys + offWrite, (uint8_t *)pQHD + offWrite, sizeof(EHCI_QHD) - offWrite); +} + +#ifdef LOG_ENABLED + +# if 0 /* unused */ +/** + * Dumps a TD queue. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpTdQueue(PEHCI pThis, RTGCPHYS GCPhysHead, const char *pszMsg) +{ + RT_NOREF(pThis, GCPhysHead, pszMsg); + AssertFailed(); +} +# endif /* unused */ + +/** + * Dumps an SITD list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpSITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList) +{ + RT_NOREF(pDevIns, GCPhysHead, fList); + AssertFailed(); +} + +/** + * Dumps an FSTN list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpFSTN(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList) +{ + RT_NOREF(pDevIns, GCPhysHead, fList); + AssertFailed(); +} + +#ifdef LOG_ENABLED +static const char *ehciPID2Str(uint32_t PID) +{ + switch(PID) + { + case EHCI_QTD_PID_OUT: + return "OUT"; + case EHCI_QTD_PID_IN: + return "IN"; + case EHCI_QTD_PID_SETUP: + return "SETUP"; + default: + return "Invalid PID!"; + } +} +#endif + +DECLINLINE(void) ehciR3DumpSingleQTD(RTGCPHYS GCPhys, PEHCI_QTD pQtd, const char *pszPrefix) +{ + if (pQtd->Token.Bits.Active) + { + Log2((" QTD%s: %RGp={", pszPrefix, GCPhys)); + Log2((" Length=%x IOC=%d DT=%d CErr=%d C_Page=%d Status=%x PID=%s}\n", pQtd->Token.Bits.Length, pQtd->Token.Bits.IOC, pQtd->Token.Bits.DataToggle, pQtd->Token.Bits.ErrorCount, pQtd->Token.Bits.CurrentPage, pQtd->Token.u32 & 0xff, ehciPID2Str(pQtd->Token.Bits.PID))); + Log2((" QTD: %RGp={", GCPhys)); + Log2((" Buf0=%x Offset=%x Buf1=%x Buf2=%x Buf3=%x Buf4=%x}\n", pQtd->Buffer.Buffer[0].Pointer, pQtd->Buffer.Offset.Offset, pQtd->Buffer.Buffer[1].Pointer, pQtd->Buffer.Buffer[2].Pointer, pQtd->Buffer.Buffer[3].Pointer, pQtd->Buffer.Buffer[4].Pointer)); + Log2((" QTD: %RGp={", GCPhys)); + Log2((" Next=%RGp T=%d AltNext=%RGp AltT=%d\n", (RTGCPHYS)pQtd->Next.Pointer << EHCI_TD_PTR_SHIFT, pQtd->Next.Terminate, (RTGCPHYS)pQtd->AltNext.Pointer << EHCI_TD_PTR_SHIFT, pQtd->AltNext.Terminate)); + } + else + Log2((" QTD%s: %RGp={Not Active}\n", pszPrefix, GCPhys)); +} + +/** + * Dumps a QTD list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList) +{ + RTGCPHYS GCPhys = GCPhysHead; + unsigned iterations = 0; + + for (;;) + { + EHCI_QTD qtd; + + /* Read the whole QHD */ + ehciR3ReadQTD(pDevIns, GCPhys, &qtd); + ehciR3DumpSingleQTD(GCPhys, &qtd, ""); + + if (!fList || qtd.Next.Terminate || !qtd.Next.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active) + break; + + /* next */ + if (GCPhys == ((RTGCPHYS)qtd.Next.Pointer << EHCI_TD_PTR_SHIFT)) + break; /* detect if list item is self-cycled. */ + + GCPhys = qtd.Next.Pointer << EHCI_TD_PTR_SHIFT; + + if (GCPhys == GCPhysHead) + break; + + /* If we ran too many iterations, the list must be looping in on itself. + * On a real controller loops wouldn't be fatal, as it will eventually + * run out of time in the micro-frame. + */ + if (++iterations == 128) + { + LogFunc(("Too many iterations, exiting!\n")); + break; + } + } + + /* alternative pointers */ + GCPhys = GCPhysHead; + iterations = 0; + + for (;;) + { + EHCI_QTD qtd; + + /* Read the whole QHD */ + ehciR3ReadQTD(pDevIns, GCPhys, &qtd); + if (GCPhys != GCPhysHead) + ehciR3DumpSingleQTD(GCPhys, &qtd, "-A"); + + if (!fList || qtd.AltNext.Terminate || !qtd.AltNext.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active) + break; + + /* next */ + if (GCPhys == ((RTGCPHYS)qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT)) + break; /* detect if list item is self-cycled. */ + + GCPhys = qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT; + + if (GCPhys == GCPhysHead) + break; + + /* If we ran too many iterations, the list must be looping in on itself. + * On a real controller loops wouldn't be fatal, as it will eventually + * run out of time in the micro-frame. + */ + if (++iterations == 128) + { + LogFunc(("Too many iterations, exiting!\n")); + break; + } + } +} + +/** + * Dumps a QHD list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpQH(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList) +{ + EHCI_QHD qhd; + RTGCPHYS GCPhys = GCPhysHead; + unsigned iterations = 0; + + Log2((" QH: %RGp={", GCPhys)); + + /* Read the whole QHD */ + ehciR3ReadQHD(pDevIns, GCPhys, &qhd); + + Log2(("HorzLnk=%RGp Typ=%u T=%u Addr=%x EndPt=%x Speed=%x MaxSize=%x NAK=%d C=%d RH=%d I=%d}\n", + ((RTGCPHYS)qhd.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Next.Type, qhd.Next.Terminate, + qhd.Characteristics.DeviceAddress, qhd.Characteristics.EndPt, qhd.Characteristics.EndPtSpeed, + qhd.Characteristics.MaxLength, qhd.Characteristics.NakCountReload, qhd.Characteristics.ControlEPFlag, + qhd.Characteristics.HeadReclamation, qhd.Characteristics.InActiveNext)); + Log2((" Caps: Port=%x Hub=%x Multi=%x CMask=%x SMask=%x\n", qhd.Caps.Port, qhd.Caps.HubAddress, + qhd.Caps.Mult, qhd.Caps.CMask, qhd.Caps.SMask)); + Log2((" CurrPtr=%RGp Next=%RGp T=%d AltNext=%RGp T=%d\n", + ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT), + ((RTGCPHYS)qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.Next.Terminate, + ((RTGCPHYS)qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.AltNext.Terminate)); + ehciR3DumpSingleQTD(qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qhd.Overlay.OrgQTD, ""); + ehciR3DumpQTD(pDevIns, qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT, true); + + Assert(qhd.Next.Pointer || qhd.Next.Terminate); + if ( !fList + || qhd.Next.Terminate + || !qhd.Next.Pointer) + return; + + for (;;) + { + /* Read the next pointer */ + EHCI_TD_PTR ptr; + ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr); + + AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type)); + Assert(ptr.Pointer || ptr.Terminate); + if ( ptr.Terminate + || !ptr.Pointer + || ptr.Type != EHCI_DESCRIPTOR_QH) + break; + + /* next */ + if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT)) + break; /* Looping on itself. Bad guest! */ + + GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT; + if (GCPhys == GCPhysHead) + break; /* break the loop */ + + ehciR3DumpQH(pDevIns, GCPhys, false); + + /* And again, if we ran too many iterations, the list must be looping on itself. + * Just quit. + */ + if (++iterations == 64) + { + LogFunc(("Too many iterations, exiting!\n")); + break; + } + } +} + +/** + * Dumps an ITD list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList) +{ + RTGCPHYS GCPhys = GCPhysHead; + unsigned iterations = 0; + + for (;;) + { + Log2((" ITD: %RGp={", GCPhys)); + + /* Read the whole ITD */ + EHCI_ITD_PAD PaddedItd; + PEHCI_ITD pItd = &PaddedItd.itd; + ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd); + + Log2(("Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi)); + for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++) + { + if (pItd->Transaction[i].Active) + { + Log2(("T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC, + pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer << EHCI_BUFFER_PTR_SHIFT)); + } + } + Assert(pItd->Next.Pointer || pItd->Next.Terminate); + if (!fList || pItd->Next.Terminate || !pItd->Next.Pointer) + break; + + /* And again, if we ran too many iterations, the list must be looping on itself. + * Just quit. + */ + if (++iterations == 128) + { + LogFunc(("Too many iterations, exiting!\n")); + break; + } + + /* next */ + GCPhys = pItd->Next.Pointer << EHCI_TD_PTR_SHIFT; + } +} + +/** + * Dumps a periodic list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ehciR3DumpPeriodicList(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, const char *pszMsg, bool fTDs) +{ + RT_NOREF(fTDs); + RTGCPHYS GCPhys = GCPhysHead; + unsigned iterations = 0; + + if (pszMsg) + Log2(("%s:", pszMsg)); + + for (;;) + { + EHCI_FRAME_LIST_PTR FramePtr; + + /* ED */ + Log2((" %RGp={", GCPhys)); + if (!GCPhys) + { + Log2(("END}\n")); + return; + } + + /* Frame list pointer */ + ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr); + if (FramePtr.Terminate) + { + Log2(("[Terminate]}\n")); + } + else + { + RTGCPHYS GCPhys1 = (RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT; + switch (FramePtr.Type) + { + case EHCI_DESCRIPTOR_ITD: + Log2(("[ITD]}\n")); + ehciR3DumpITD(pDevIns, GCPhys1, false); + break; + case EHCI_DESCRIPTOR_SITD: + Log2(("[SITD]}\n")); + ehciR3DumpSITD(pDevIns, GCPhys1, false); + break; + case EHCI_DESCRIPTOR_QH: + Log2(("[QH]}\n")); + ehciR3DumpQH(pDevIns, GCPhys1, false); + break; + case EHCI_DESCRIPTOR_FSTN: + Log2(("[FSTN]}\n")); + ehciR3DumpFSTN(pDevIns, GCPhys1, false); + break; + } + } + + /* Same old. If we ran too many iterations, the list must be looping on itself. + * Just quit. + */ + if (++iterations == 128) + { + LogFunc(("Too many iterations, exiting!\n")); + break; + } + + /* next */ + GCPhys = GCPhys + sizeof(FramePtr); + } +} + +#endif /* LOG_ENABLED */ + + +DECLINLINE(int) ehciR3InFlightFindFree(PEHCICC pThisCC, const int iStart) +{ + unsigned i = iStart; + while (i < RT_ELEMENTS(pThisCC->aInFlight)) { + if (pThisCC->aInFlight[i].pUrb == NULL) + return i; + i++; + } + i = iStart; + while (i-- > 0) { + if (pThisCC->aInFlight[i].pUrb == NULL) + return i; + } + return -1; +} + + +/** + * Record an in-flight TD. + * + * @param pThis EHCI instance data, shared edition. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @param pUrb The URB. + */ +static void ehciR3InFlightAdd(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD, PVUSBURB pUrb) +{ + int i = ehciR3InFlightFindFree(pThisCC, (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight)); + if (i >= 0) + { +#ifdef LOG_ENABLED + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; +#endif + pThisCC->aInFlight[i].GCPhysTD = GCPhysTD; + pThisCC->aInFlight[i].pUrb = pUrb; + pThisCC->cInFlight++; + return; + } + AssertMsgFailed(("Out of space cInFlight=%d!\n", pThisCC->cInFlight)); + RT_NOREF(pThis); +} + + +/** + * Record in-flight TDs for an URB. + * + * @param pThis EHCI instance data, shared edition. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static void ehciR3InFlightAddUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb) +{ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + ehciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb); +} + + +/** + * Finds a in-flight TD. + * + * @returns Index of the record. + * @returns -1 if not found. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @remark This has to be fast. + */ +static int ehciR3InFlightFind(PEHCICC pThisCC, RTGCPHYS GCPhysTD) +{ + unsigned cLeft = pThisCC->cInFlight; + unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight); + const int iLast = i; + while (i < RT_ELEMENTS(pThisCC->aInFlight)) { + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) + return i; + if (pThisCC->aInFlight[i].pUrb) + if (cLeft-- <= 1) + return -1; + i++; + } + i = iLast; + while (i-- > 0) { + if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb) + return i; + if (pThisCC->aInFlight[i].pUrb) + if (cLeft-- <= 1) + return -1; + } + return -1; +} + + +/** + * Checks if a TD is in-flight. + * + * @returns true if in flight, false if not. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static bool ehciR3IsTdInFlight(PEHCICC pThisCC, RTGCPHYS GCPhysTD) +{ + return ehciR3InFlightFind(pThisCC, GCPhysTD) >= 0; +} + + +/** + * Removes a in-flight TD. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis EHCI instance data, shared edition. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static int ehciR3InFlightRemove(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD) +{ + int i = ehciR3InFlightFind(pThisCC, GCPhysTD); + if (i >= 0) + { +#ifdef LOG_ENABLED + const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo; +#else + const int cFramesInFlight = 0; +#endif + Log2Func(("reaping TD=%RGp %d frames (%#010x-%#010x)\n", + GCPhysTD, cFramesInFlight, pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber)); + pThisCC->aInFlight[i].GCPhysTD = 0; + pThisCC->aInFlight[i].pUrb = NULL; + pThisCC->cInFlight--; + return cFramesInFlight; + } + AssertMsgFailed(("TD %RGp is not in flight\n", GCPhysTD)); + RT_NOREF(pThis); + return -1; +} + + +/** + * Removes all TDs associated with a URB from the in-flight tracking. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis EHCI instance data, shared edition. + * @param pThisCC EHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static int ehciR3InFlightRemoveUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb) +{ + int cFramesInFlight = ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr); + if (pUrb->pHci->cTds > 1) + { + for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++) + if (ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0) + cFramesInFlight = -1; + } + return cFramesInFlight; +} + + +/** + * Checks that the transport descriptors associated with the URB + * hasn't been changed in any way indicating that they may have been canceled. + * + * This rountine also updates the TD copies contained within the URB. + * + * @returns true if the URB has been canceled, otherwise false. + * @param pThisCC EHCI instance data, ring-3 edition. (For stats.) + * @param pUrb The URB in question. + * @param pItd The ITD pointer. + */ +static bool ehciR3ItdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_ITD pItd) +{ + RT_NOREF(pThisCC); + Assert(pItd); + if (!pUrb) + return true; + + PEHCI_ITD pItdCopy = (PEHCI_ITD)pUrb->paTds[0].TdCopy; + + /* Check transactions */ + for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++) + { + if ( pItd->Transaction[i].Length != pItdCopy->Transaction[i].Length + || pItd->Transaction[i].Offset != pItdCopy->Transaction[i].Offset + || pItd->Transaction[i].PG != pItdCopy->Transaction[i].PG + || pItd->Transaction[i].Active != pItdCopy->Transaction[i].Active) + { + Log(("%s: ehciR3ItdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0])); + STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs); + return true; + } + } + + /* Check misc characteristics */ + if ( pItd->Buffer.Misc.DeviceAddress != pItdCopy->Buffer.Misc.DeviceAddress + || pItd->Buffer.Misc.DirectionIn != pItdCopy->Buffer.Misc.DirectionIn + || pItd->Buffer.Misc.EndPt != pItdCopy->Buffer.Misc.EndPt) + { + Log(("%s: ehciR3ItdHasUrbBeenCanceled (misc): TdAddr=%RGp canceled! [iso]\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0])); + STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs); + return true; + } + + /* Check buffer pointers */ + for (unsigned i = 0; i < RT_ELEMENTS(pItd->Buffer.Buffer); i++) + { + if (pItd->Buffer.Buffer[i].Pointer != pItdCopy->Buffer.Buffer[i].Pointer) + { + Log(("%s: ehciR3ItdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0])); + STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs); + return true; + } + } + return false; +} + +/** + * Checks that the transport descriptors associated with the URB + * hasn't been changed in any way indicating that they may have been canceled. + * + * This rountine also updates the TD copies contained within the URB. + * + * @returns true if the URB has been canceled, otherwise false. + * @param pThisCC EHCI instance data, ring-3 edition. (For stats.) + * @param pUrb The URB in question. + * @param pQhd The QHD pointer + * @param pQtd The QTD pointer + */ +static bool ehciR3QhdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_QHD pQhd, PEHCI_QTD pQtd) +{ + RT_NOREF(pQhd, pThisCC); + Assert(pQhd && pQtd); + if ( !pUrb + || !ehciR3IsTdInFlight(pThisCC, pUrb->paTds[0].TdAddr)) + return true; + + PEHCI_QTD pQtdCopy = (PEHCI_QTD)pUrb->paTds[0].TdCopy; + + if ( pQtd->Token.Bits.Length != pQtdCopy->Token.Bits.Length + || pQtd->Token.Bits.Active != pQtdCopy->Token.Bits.Active + || pQtd->Token.Bits.DataToggle != pQtdCopy->Token.Bits.DataToggle + || pQtd->Token.Bits.CurrentPage != pQtdCopy->Token.Bits.CurrentPage + || pQtd->Token.Bits.PID != pQtdCopy->Token.Bits.PID + || pQtd->Buffer.Offset.Offset != pQtdCopy->Buffer.Offset.Offset) + { + Log(("%s: ehciQtdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0])); + STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs); + return true; + } + + + /* Check buffer pointers */ + for (unsigned i = 0; i < RT_ELEMENTS(pQtd->Buffer.Buffer); i++) + { + if (pQtd->Buffer.Buffer[i].Pointer != pQtdCopy->Buffer.Buffer[i].Pointer) + { + Log(("%s: ehciQtdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0])); + STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs); + return true; + } + } + + return false; +} + +/** + * Set the ITD status bits acorresponding to the VUSB status code. + * + * @param enmStatus The VUSB status code. + * @param pItdStatus ITD status pointer + */ +static void ehciR3VUsbStatus2ItdStatus(VUSBSTATUS enmStatus, EHCI_ITD_TRANSACTION *pItdStatus) +{ + switch (enmStatus) + { + case VUSBSTATUS_OK: + pItdStatus->TransactError = 0; + pItdStatus->DataBufError = 0; + break; /* make sure error bits are cleared */ + case VUSBSTATUS_STALL: + case VUSBSTATUS_DNR: + case VUSBSTATUS_CRC: + pItdStatus->TransactError = 1; + break; + case VUSBSTATUS_DATA_UNDERRUN: + case VUSBSTATUS_DATA_OVERRUN: + pItdStatus->DataBufError = 1; + break; + case VUSBSTATUS_NOT_ACCESSED: + Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n")); + break; /* can't signal this other than setting the length to 0 */ + default: + Log(("pUrb->enmStatus=%#x!!!\n", enmStatus)); + break;; + } +} + +/** + * Set the QTD status bits acorresponding to the VUSB status code. + * + * @param enmStatus The VUSB status code. + * @param pQtdStatus QTD status pointer + */ +static void ehciR3VUsbStatus2QtdStatus(VUSBSTATUS enmStatus, EHCI_QTD_TOKEN *pQtdStatus) +{ + /** @todo CERR */ + switch (enmStatus) + { + case VUSBSTATUS_OK: + break; /* nothing to do */ + case VUSBSTATUS_STALL: + pQtdStatus->Halted = 1; + pQtdStatus->Active = 0; + break; /* not an error! */ + case VUSBSTATUS_DNR: + case VUSBSTATUS_CRC: + pQtdStatus->TransactError = 1; + break; + case VUSBSTATUS_DATA_UNDERRUN: + case VUSBSTATUS_DATA_OVERRUN: + pQtdStatus->DataBufError = 1; + break; + case VUSBSTATUS_NOT_ACCESSED: + Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n")); + break; /* can't signal this */ + default: + Log(("pUrb->enmStatus=%#x!!!\n", enmStatus)); + break;; + } +} + + +/** + * Heuristic to determine the transfer type + * + * @returns transfer type + * @param pQhd Queue head pointer + */ +static VUSBXFERTYPE ehciR3QueryTransferType(PEHCI_QHD pQhd) +{ + /* If it's EP0, we know what it is. */ + if (!pQhd->Characteristics.EndPt) + return VUSBXFERTYPE_CTRL; + + /* Non-zero SMask implies interrupt transfer. */ + if (pQhd->Caps.SMask) + return VUSBXFERTYPE_INTR; + + /* For non-HS EPs, control endpoints are clearly marked. */ + if ( pQhd->Characteristics.ControlEPFlag + && pQhd->Characteristics.EndPtSpeed != EHCI_QHD_EPT_SPEED_HIGH) + return VUSBXFERTYPE_CTRL; + + /* If we still don't know, it's guesswork from now on. */ + + /* 64 likely indicates an interrupt transfer (see @bugref{8314})*/ + if (pQhd->Characteristics.MaxLength == 64) + return VUSBXFERTYPE_INTR; + + /* At this point we hope it's a bulk transfer with max packet size of 512. */ + Assert(pQhd->Characteristics.MaxLength == 512); + return VUSBXFERTYPE_BULK; +} + +/** + * Worker for ehciR3RhXferCompletion that handles the completion of + * a URB made up of isochronous TDs. + * + * In general, all URBs should have status OK. + */ +static void ehciR3RhXferCompleteITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb) +{ + /* Read the whole ITD */ + EHCI_ITD_PAD PaddedItd; + PEHCI_ITD pItd = &PaddedItd.itd; + ehciR3ReadItd(pDevIns, pUrb->paTds[0].TdAddr, &PaddedItd); + + /* + * Check that the URB hasn't been canceled and then try unlink the TDs. + * + * We drop the URB if the ED is marked halted/skip ASSUMING that this + * means the HCD has canceled the URB. + * + * If we succeed here (i.e. not dropping the URB), the TdCopy members will + * be updated but not yet written. We will delay the writing till we're done + * with the data copying, buffer pointer advancing and error handling. + */ + bool fHasBeenCanceled = false; + int cFmAge = ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb); + if ( cFmAge < 0 + || (fHasBeenCanceled = ehciR3ItdHasUrbBeenCanceled(pThisCC, pUrb, pItd)) + ) + { + Log(("%s: ehciR3RhXferCompleteITD: DROPPED {ITD=%RGp cTds=%d TD0=%RGp age %d} because:%s%s!!!\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge, + cFmAge < 0 ? " td not-in-flight" : "", + fHasBeenCanceled ? " td canceled" : "")); + NOREF(fHasBeenCanceled); + STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs); + return; + } + + bool fIOC = false, fError = false; + + /* + * Copy the data back (if IN operation) and update the TDs. + */ + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]); + if (pItd->Transaction[i].IOC) + fIOC = true; + + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_OK + || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_OVERRUN)) + { + Assert(pItd->Transaction[i].Active); + + if (pItd->Transaction[i].Active) + { + const unsigned pg = pItd->Transaction[i].PG; + const unsigned cb = pUrb->aIsocPkts[i].cb; + pItd->Transaction[i].Length = cb; /* Set the actual size. */ + /* Copy data. */ + if (cb) + { + uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i].off]; + + RTGCPHYS GCPhysBuf = (RTGCPHYS)pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT; + GCPhysBuf += pItd->Transaction[i].Offset; + + /* If the transfer would cross page boundary, use the next sequential PG pointer + * for the second part (section 4.7.1). + */ + if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE) + { + unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset; + unsigned cb2 = cb - cb1; + + ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb1); + if ((pg + 1) >= EHCI_NUM_ITD_PAGES) + LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on completion.\n", pg + 1, pUrb->paTds[0].TdAddr)); + + GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT; + ehciPhysWrite(pDevIns, GCPhysBuf, pb + cb1, cb2); + } + else + ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb); + + Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n" + "%.*Rhxd\n", + i, pUrb->aIsocPkts[i].off, cb, pb, pb - &pUrb->abData[0], cb, pb)); + } + } + } + pItd->Transaction[i].Active = 0; /* transfer is now officially finished */ + } /* for */ + } + else + { + LogFunc(("Taking untested code path at line %d...\n", __LINE__)); + /* + * Most status codes only apply to the individual packets. + * + * If we get a URB level error code of this kind, we'll distribute + * it to all the packages unless some other status is available for + * a package. This is a bit fuzzy, and we will get rid of this code + * before long! + */ + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + if (pItd->Transaction[i].Active) + { + ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]); + if (pItd->Transaction[i].IOC) + fIOC = true; + + pItd->Transaction[i].Active = 0; /* transfer is now officially finished */ + } + } + fError = true; + } + + /* + * Write back the modified TD. + */ + + Log(("%s: ehciR3RhXferCompleteITD: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp " + "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr, + pUrb->pHci->EdAddr, + pItd->Buffer.Buffer[pItd->Transaction[0].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[0].Length, + pItd->Buffer.Buffer[pItd->Transaction[1].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[1].Length, + pItd->Buffer.Buffer[pItd->Transaction[2].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[2].Length, + pItd->Buffer.Buffer[pItd->Transaction[3].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[3].Length, + pItd->Buffer.Buffer[pItd->Transaction[4].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[4].Length, + pItd->Buffer.Buffer[pItd->Transaction[5].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[5].Length, + pItd->Buffer.Buffer[pItd->Transaction[6].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[6].Length, + pItd->Buffer.Buffer[pItd->Transaction[7].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[7].Length + )); + ehciR3WriteItd(pDevIns, pUrb->paTds[0].TdAddr, pItd); + + /* + * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction. + * Both error and completion interrupts may be signaled at the same time (see Table 2.10). + */ + if (fError) + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT); + if (fIOC) + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT); +} + + +/** + * Worker for ehciR3RhXferCompletion that handles the completion of + * a URB made up of queue heads/descriptors + */ +static void ehciR3RhXferCompleteQH(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb) +{ + EHCI_QHD qhd; + EHCI_QTD qtd; + + /* Read the whole QHD & QTD */ + ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd); + AssertMsg(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT), + ("Out of order completion %RGp != %RGp Endpoint=%#x\n", pUrb->paTds[0].TdAddr, + ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT), pUrb->EndPt)); + ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd); + + /* + * Check that the URB hasn't been canceled and then try unlink the TDs. + * + * We drop the URB if the ED is marked halted/skip ASSUMING that this + * means the HCD has canceled the URB. + * + * If we succeed here (i.e. not dropping the URB), the TdCopy members will + * be updated but not yet written. We will delay the writing till we're done + * with the data copying, buffer pointer advancing and error handling. + */ + bool fHasBeenCanceled = false; + if ((fHasBeenCanceled = ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd))) + { + Log(("%s: ehciRhXferCompletionQH: DROPPED {qTD=%RGp cTds=%d TD0=%RGp} because:%s%s!!!\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, + (pUrb->paTds[0].TdAddr != ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)) ? " curptr changed" : "", + fHasBeenCanceled ? " td canceled" : "")); + NOREF(fHasBeenCanceled); + STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs); + + ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb); + qtd.Token.Bits.Active = 0; + ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd); + return; + } + ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb); + + /* Update the status/error bits */ + ehciR3VUsbStatus2QtdStatus(pUrb->enmStatus, &qtd.Token.Bits); + + /* + * Write back IN buffers. + */ + if ( pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->cbData + && ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN + ) + ) + { + unsigned curOffset = 0; + unsigned cbLeft = pUrb->cbData; + + for (unsigned i=qtd.Token.Bits.CurrentPage;i<RT_ELEMENTS(qtd.Buffer.Buffer);i++) + { + RTGCPHYS GCPhysBuf; + unsigned cbCurTransfer; + + GCPhysBuf = qtd.Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT; + if (i == 0) + GCPhysBuf += qtd.Buffer.Offset.Offset; + + cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK); + cbCurTransfer = RT_MIN(cbCurTransfer, cbLeft); + + Log3Func(("packet data for page %d:\n" + "%.*Rhxd\n", + i, + cbCurTransfer, &pUrb->abData[curOffset])); + + ehciPhysWrite(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer); + curOffset += cbCurTransfer; + cbLeft -= cbCurTransfer; + + if (cbLeft == 0) + break; + Assert(cbLeft < qtd.Token.Bits.Length); + } + } + + if ( pUrb->cbData + && ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN + ) + ) + { + /* 3.5.3: + * This field specifies the total number of bytes to be moved + * with this transfer descriptor. This field is decremented by the number of bytes actually + * moved during the transaction, only on the successful completion of the transaction + */ + Assert(qtd.Token.Bits.Length >= pUrb->cbData); + qtd.Token.Bits.Length -= pUrb->cbData; + + /* Data was moved; toggle data toggle bit */ + qtd.Token.Bits.DataToggle ^= 1; + } + +#ifdef LOG_ENABLED + ehciR3DumpSingleQTD(pUrb->paTds[0].TdAddr, &qtd, ""); +#endif + qtd.Token.Bits.Active = 0; /* transfer is now officially finished */ + + /* + * Write back the modified TD. + */ + Log(("%s: ehciR3RhXferCompleteQH: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp\n", + pUrb->pszDesc, pUrb->paTds[0].TdAddr, + pUrb->pHci->EdAddr)); + + ehciR3WriteQTD(pDevIns, pUrb->paTds[0].TdAddr, &qtd); + + ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd); + + /* + * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction. + * Both error and completion interrupts may be signaled at the same time (see Table 2.10). + */ + if (EHCI_QTD_HAS_ERROR(&qtd.Token.Bits)) + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT); + + bool fIOC = false; + if (qtd.Token.Bits.IOC) { + fIOC = true; + Log2Func(("Interrupting, IOC set\n")); + } else if (qtd.Token.Bits.Length && (qtd.Token.Bits.PID == EHCI_QTD_PID_IN)) { + fIOC = true; /* See 4.10.8 */ + Log2Func(("Interrupting, short IN packet\n")); + } + if (fIOC) + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT); +} + + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) ehciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + + LogFlow(("%s: ehciR3RhXferCompletion: EdAddr=%RGp cTds=%d TdAddr0=%RGp\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr)); + LogFlow(("%s: ehciR3RhXferCompletion: cbData=%x status=%x\n", pUrb->pszDesc, pUrb->cbData, pUrb->enmStatus)); + + Assert(pUrb->pHci->cTds == 1); + + RTCritSectEnter(&pThisCC->CritSect); + pThisCC->fIdle = false; /* Mark as active */ + + switch (pUrb->paTds[0].TdType) + { + case EHCI_DESCRIPTOR_QH: + ehciR3RhXferCompleteQH(pDevIns, pThis, pThisCC, pUrb); + break; + + case EHCI_DESCRIPTOR_ITD: + ehciR3RhXferCompleteITD(pDevIns, pThis, pThisCC, pUrb); + break; + + case EHCI_DESCRIPTOR_SITD: + case EHCI_DESCRIPTOR_FSTN: + AssertFailed(); + break; + } + + ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault); + RTCritSectLeave(&pThisCC->CritSect); + RTSemEventMultiSignal(pThisCC->hSemEventFrame); +} + +/** + * Worker for ehciR3RhXferError that handles the error case of + * a URB made up of queue heads/descriptors + * + * @returns true if the URB should be retired. + * @returns false if the URB should be retried. + * @param pDevIns The device instance. + * @param pThisCC EHCI instance data, ring-3 edition. (For stats.) + * @param pUrb Pointer to the URB in question. + */ +static bool ehciR3RhXferErrorQH(PPDMDEVINS pDevIns, PEHCICC pThisCC, PVUSBURB pUrb) +{ + EHCI_QHD qhd; + EHCI_QTD qtd; + + /* Read the whole QHD & QTD */ + ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd); + Assert(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)); + ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd); + + /* + * Check if the TDs still are valid. + * This will make sure the TdCopy is up to date. + */ + /** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */ + if (ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd)) + { + Log(("%s: ehciR3RhXferError: TdAddr0=%RGp canceled!\n", pUrb->pszDesc, pUrb->paTds[0].TdAddr)); + return true; + } + return true; +} + +/** + * Handle transfer errors. + * + * VUSB calls this when a transfer attempt failed. This function will respond + * indicating whether to retry or complete the URB with failure. + * + * @returns true if the URB should be retired. + * @returns false if the URB should be retried. + * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) ehciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort); + PPDMDEVINS pDevIns = pThisCC->pDevIns; + bool fRetire = false; + + RTCritSectEnter(&pThisCC->CritSect); + /* + * Don't retry on stall. + */ + if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + Log2(("%s: ehciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + fRetire = true; + } + else + { + switch (pUrb->paTds[0].TdType) + { + case EHCI_DESCRIPTOR_QH: + { + fRetire = ehciR3RhXferErrorQH(pDevIns, pThisCC, pUrb); + break; + } + + /* + * Isochronous URBs can't be retried. + */ + case EHCI_DESCRIPTOR_ITD: + case EHCI_DESCRIPTOR_SITD: + case EHCI_DESCRIPTOR_FSTN: + default: + fRetire = true; + break; + } + } + + RTCritSectLeave(&pThisCC->CritSect); + return fRetire; +} + +/** + * A worker for ehciR3ServiceQTD which submits the specified TD. + * + * @returns true on success. + * @returns false on failure to submit. + */ +static bool ehciR3SubmitQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysQHD, + PEHCI_QHD pQhd, RTGCPHYS GCPhysQTD, PEHCI_QTD pQtd, const unsigned iFrame) +{ + /* + * Determine the endpoint direction. + */ + VUSBDIRECTION enmDir; + switch(pQtd->Token.Bits.PID) + { + case EHCI_QTD_PID_OUT: + enmDir = VUSBDIRECTION_OUT; + break; + case EHCI_QTD_PID_IN: + enmDir = VUSBDIRECTION_IN; + break; + case EHCI_QTD_PID_SETUP: + enmDir = VUSBDIRECTION_SETUP; + break; + default: + return false; + } + + VUSBXFERTYPE enmType; + + enmType = ehciR3QueryTransferType(pQhd); + + pThisCC->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize the URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pQhd->Characteristics.DeviceAddress, VUSB_DEVICE_PORT_INVALID, + enmType, enmDir, pQtd->Token.Bits.Length, 1, NULL); + if (!pUrb) + /* retry later... */ + return false; + + pUrb->EndPt = pQhd->Characteristics.EndPt; + pUrb->fShortNotOk = (enmDir != VUSBDIRECTION_IN); /** @todo ??? */ + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->cTds = 1; + pUrb->pHci->EdAddr = GCPhysQHD; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = iFrame; + pUrb->paTds[0].TdAddr = GCPhysQTD; + pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_QH; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pQtd)); + memcpy(pUrb->paTds[0].TdCopy, pQtd, sizeof(*pQtd)); +#if 0 /* color the data */ + memset(pUrb->abData, 0xfe, cbTotal); +#endif + + /* copy the data */ + if ( pQtd->Token.Bits.Length + && enmDir != VUSBDIRECTION_IN) + { + unsigned curOffset = 0; + unsigned cbTransfer = pQtd->Token.Bits.Length; + + for (unsigned i=pQtd->Token.Bits.CurrentPage;i<RT_ELEMENTS(pQtd->Buffer.Buffer);i++) + { + RTGCPHYS GCPhysBuf; + unsigned cbCurTransfer; + + GCPhysBuf = pQtd->Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT; + if (i == 0) + GCPhysBuf += pQtd->Buffer.Offset.Offset; + + cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK); + cbCurTransfer = RT_MIN(cbCurTransfer, cbTransfer); + + ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer); + + Log3Func(("packet data:\n" + "%.*Rhxd\n", + cbCurTransfer, &pUrb->abData[curOffset])); + + curOffset += cbCurTransfer; + cbTransfer -= cbCurTransfer; + + if (cbTransfer == 0) + break; + Assert(cbTransfer < pQtd->Token.Bits.Length); + } + } + + /* + * Submit the URB. + */ + ehciR3InFlightAddUrb(pThis, pThisCC, pUrb); + Log(("%s: ehciSubmitQtd: QtdAddr=%RGp GCPhysQHD=%RGp cbData=%#x\n", + pUrb->pszDesc, GCPhysQTD, GCPhysQHD, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSect); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + RTCritSectEnter(&pThisCC->CritSect); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + LogFunc(("failed GCPhysQtd=%RGp GCPhysQHD=%RGp pUrb=%p!!\n", + GCPhysQTD, GCPhysQHD, pUrb)); + ehciR3InFlightRemove(pThis, pThisCC, GCPhysQTD); + + /* Also mark the QH as halted and inactive and write back the changes. */ + pQhd->Overlay.OrgQTD.Token.Bits.Active = 0; + pQhd->Overlay.OrgQTD.Token.Bits.Halted = 1; + ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd); + return false; +} + +/** + * A worker for ehciR3ServiceITD which submits the specified TD. + * + * @returns true on success. + * @returns false on failure to submit. + */ +static bool ehciR3SubmitITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + PEHCI_ITD pItd, RTGCPHYS ITdAddr, const unsigned iFrame) +{ + /* + * Determine the endpoint direction. + */ + VUSBDIRECTION enmDir; + if(pItd->Buffer.Misc.DirectionIn) + enmDir = VUSBDIRECTION_IN; + else + enmDir = VUSBDIRECTION_OUT; + + /* + * Extract the packet sizes and calc the total URB size. + */ + struct + { + uint16_t cb; + } aPkts[EHCI_NUM_ITD_TRANSACTIONS]; + + unsigned cPackets = 0; + uint32_t cbTotal = 0; + for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++) + { + if (pItd->Transaction[i].Active) + { + aPkts[cPackets].cb = pItd->Transaction[i].Length; + cbTotal += pItd->Transaction[i].Length; + cPackets++; + } + } + Assert(cbTotal <= 24576); + + pThisCC->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize the URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pItd->Buffer.Misc.DeviceAddress, VUSB_DEVICE_PORT_INVALID, + VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL); + if (!pUrb) + /* retry later... */ + return false; + + pUrb->EndPt = pItd->Buffer.Misc.EndPt; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->cTds = 1; + pUrb->pHci->EdAddr = ITdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = iFrame; + pUrb->paTds[0].TdAddr = ITdAddr; + pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_ITD; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pItd)); + memcpy(pUrb->paTds[0].TdCopy, pItd, sizeof(*pItd)); +#if 0 /* color the data */ + memset(pUrb->abData, 0xfe, cbTotal); +#endif + + /* copy the data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN) + { + unsigned curOffset = 0; + + for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++) + { + RTGCPHYS GCPhysBuf; + + if (pItd->Transaction[i].Active) + { + const unsigned pg = pItd->Transaction[i].PG; + + GCPhysBuf = pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT; + GCPhysBuf += pItd->Transaction[i].Offset; + + /* If the transfer would cross page boundary, use the next sequential PG pointer + * for the second part (section 4.7.1). + */ + if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE) + { + unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset; + unsigned cb2 = pItd->Transaction[i].Length - cb1; + + ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cb1); + if ((pg + 1) >= EHCI_NUM_ITD_PAGES) + LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on submit.\n", pg + 1, pUrb->paTds[0].TdAddr)); + + GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT; + ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset + cb1], cb2); + } + else + ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], pItd->Transaction[i].Length); + + curOffset += pItd->Transaction[i].Length; + } + } + } + + /* setup the packets */ + pUrb->cIsocPkts = cPackets; + unsigned off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED; + pUrb->aIsocPkts[i].off = off; + off += pUrb->aIsocPkts[i].cb = aPkts[i].cb; + } + Assert(off == cbTotal); + + /* + * Submit the URB. + */ + ehciR3InFlightAddUrb(pThis, pThisCC, pUrb); + Log(("%s: ehciR3SubmitITD: cbData=%#x cIsocPkts=%d TdAddr=%RGp (%#x)\n", + pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, ITdAddr, iFrame)); + RTCritSectLeave(&pThisCC->CritSect); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + RTCritSectEnter(&pThisCC->CritSect); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + LogFunc(("failed pUrb=%p cbData=%#x cTds=%d ITdAddr0=%RGp - rc=%Rrc\n", + pUrb, cbTotal, 1, ITdAddr, rc)); + ehciR3InFlightRemove(pThis, pThisCC, ITdAddr); + return false; +} + + +/** + * Services an ITD list (only for high-speed isochronous endpoints; all others use queues) + * + * An ITD can contain up to 8 transactions, which are all processed within a single frame. + * Note that FRINDEX includes the micro-frame number, but only bits [12:3] are used as an + * index into the periodic frame list (see 4.7.1). + */ +static void ehciR3ServiceITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame) +{ + RT_NOREF(enmServiceType); + bool fAnyActive = false; + EHCI_ITD_PAD PaddedItd; + PEHCI_ITD pItd = &PaddedItd.itd; + + if (ehciR3IsTdInFlight(pThisCC, GCPhys)) + return; + + /* Read the whole ITD */ + ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd); + + Log2((" ITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", GCPhys, pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi)); + + /* Some basic checks */ + for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++) + { + if (pItd->Transaction[i].Active) + { + fAnyActive = true; + if (pItd->Transaction[i].PG >= EHCI_NUM_ITD_PAGES) + { + /* Using out of range PG value (7) yields undefined behavior. We will attempt + * the last page below 4GB (which is ROM, not writable). + */ + LogRelMax(10, ("EHCI: Illegal page value %d in iTD at %RGp.\n", pItd->Transaction[i].PG, (RTGCPHYS)GCPhys)); + } + + Log2((" T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC, + pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer)); + } + } + /* We can't service one transaction every 125 usec, so we'll handle all 8 of them at once. */ + if (fAnyActive) + ehciR3SubmitITD(pDevIns, pThis, pThisCC, pItd, GCPhys, iFrame); + else + Log2((" ITD not active, skipping.\n")); +} + +/** + * Services an SITD list + */ +static void ehciR3ServiceSITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame) +{ + RT_NOREF(pThis, pThisCC, enmServiceType, iFrame); + + /* Read the whole SITD */ + EHCI_SITD sitd; + ehciR3ReadSitd(pDevIns, GCPhys, &sitd); + + Log2((" SITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x}\n", GCPhys, sitd.Address.DeviceAddress, sitd.Address.EndPt, (sitd.Address.DirectionIn) ? "in" : "out", sitd.Transfer.Length)); + + if (sitd.Transfer.Active) + AssertMsgFailed(("SITD lists not implemented; active SITD should never occur!\n")); + else + Log2((" SITD not active, skipping.\n")); +} + +/** + * Copies the currently active QTD to the QH overlay area + */ +static void ehciR3QHSetupOverlay(PPDMDEVINS pDevIns, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd, RTGCPHYS GCPhysQTD) +{ + bool fDataToggle = pQhd->Overlay.OrgQTD.Token.Bits.DataToggle; + + Assert(pQtd->Token.Bits.Active); + + Log2Func(("current pointer %RGp old %RGp\n", GCPhysQTD, ((RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT))); + pQhd->CurrQTD.Pointer = GCPhysQTD >> EHCI_TD_PTR_SHIFT; + pQhd->CurrQTD.Reserved = 0; + pQhd->Overlay.OrgQTD = *pQtd; + /* All fields except those below are copied from the QTD; see 4.10.2 */ + if (pQhd->Characteristics.DataToggle) + pQhd->Overlay.OrgQTD.Token.Bits.DataToggle = fDataToggle; /* Preserve data toggle bit in the queue head */ + + pQhd->Overlay.Status.Buffer1.CProgMask = 0; + pQhd->Overlay.Status.Buffer2.FrameTag = 0; + pQhd->Overlay.Status.AltNextQTD.NakCnt = pQhd->Characteristics.NakCountReload; + /* Note: ping state not changed if it's a high-speed device */ + + /* Save the current QTD to the overlay area */ + ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd); +} + +/** + * Updates the currently active QTD to the QH overlay area + */ +static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd) +{ + Assert(!pQtd->Token.Bits.Active); + pQhd->Overlay.OrgQTD = *pQtd; + if (!pQhd->Overlay.OrgQTD.Next.Terminate) + { + EHCI_QTD qtdNext; + RTGCPHYS GCPhysNextQTD = pQhd->Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT; + + if (ehciR3IsTdInFlight(pThisCC, GCPhysNextQTD)) + { + /* Read the whole QTD */ + ehciR3ReadQTD(pDevIns, GCPhysNextQTD, &qtdNext); + if (qtdNext.Token.Bits.Active) + { + ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtdNext, GCPhysNextQTD); + return; + } + else + { + /* Td has been cancelled! */ + LogFunc(("in-flight qTD %RGp has been cancelled! (active=%d T=%d)\n", GCPhysNextQTD, qtdNext.Token.Bits.Active, pQhd->Overlay.OrgQTD.Next.Terminate)); + /** @todo we don't properly cancel the URB; it will remain active on the host.... */ + ehciR3InFlightRemove(pThis, pThisCC, GCPhysNextQTD); + } + } + } + /* Save the current QTD to the overlay area. */ + ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd); +} + +/** + * Services a QTD list + */ +static RTGCPHYS ehciR3ServiceQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, + RTGCPHYS GCPhysQTD, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame) +{ + EHCI_QTD qtd; + + /* Read the whole QTD */ + ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd); + + if (qtd.Token.Bits.Active) + { + if (!ehciR3IsTdInFlight(pThisCC, GCPhysQTD)) + { + /* Don't queue more than one non-bulk transfer at a time */ + if ( ehciR3QueryTransferType(pQhd) != VUSBXFERTYPE_BULK + && pQhd->Overlay.OrgQTD.Token.Bits.Active) + return 0; + + Log2((" Length=%x IOC=%d DT=%d PID=%s}\n", qtd.Token.Bits.Length, qtd.Token.Bits.IOC, qtd.Token.Bits.DataToggle, ehciPID2Str(qtd.Token.Bits.PID))); + if ( !pQhd->Overlay.OrgQTD.Token.Bits.Active + || GCPhysQTD == (RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT) + ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtd, GCPhysQTD); + else + Log2Func(("transfer %RGp in progress -> don't update the overlay\n", (RTGCPHYS)(pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT))); + + ehciR3SubmitQTD(pDevIns, pThis, pThisCC, GCPhysQHD, pQhd, GCPhysQTD, &qtd, iFrame); + + /* Set the Reclamation bit in USBSTS (4.10.3) */ + if (enmServiceType == EHCI_SERVICE_ASYNC) + { + Log2Func(("activity detected, set EHCI_STATUS_RECLAMATION\n")); + ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION); + } + + /* Reread the whole QTD; it might have been completed already and therefore changed */ + ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd); + } + /* Table 4-10: any transfer with zero size: queue only one */ + if (qtd.Token.Bits.Length == 0) + { + LogFunc(("queue only one: transfer with zero size\n")); + return 0; + } + + /* We can't queue more than one TD if we can't decide here and now which TD we should take next */ + if ( qtd.Token.Bits.Active /* only check if this urb is in-flight */ + && qtd.Token.Bits.PID == EHCI_QTD_PID_IN + && !qtd.AltNext.Terminate + && !qtd.Next.Terminate + && qtd.Next.Pointer != qtd.AltNext.Pointer) + { + Log2Func(("Can't decide which pointer to take next; don't queue more than one!\n")); + return 0; + } + } + else + { + Log2((" Not active}\n")); + return 0; + } + + /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */ + if ( !qtd.Token.Bits.Active /* only check if no urbs are in-flight */ + && qtd.Token.Bits.PID == EHCI_QTD_PID_IN /* short packets only apply to incoming tds */ + && !qtd.AltNext.Terminate + && qtd.Token.Bits.Length) + { + Assert(qtd.AltNext.Pointer); + Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT))); + return qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT; + } + else + { + Assert(qtd.Next.Pointer || qtd.Next.Terminate); + if (qtd.Next.Terminate || !qtd.Next.Pointer) + return 0; + return qtd.Next.Pointer << EHCI_TD_PTR_SHIFT; + } +} + +/** + * Services a QHD list + */ +static bool ehciR3ServiceQHD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame) +{ + EHCI_QHD qhd; + + Log2Func(("%RGp={", GCPhys)); + + /* Read the whole QHD */ /** @todo reading too much */ + ehciR3ReadQHD(pDevIns, GCPhys, &qhd); + + /* Only interrupt qHDs should be linked from the periodic list; the S-mask field description + * in table 3-20 clearly says a zero S-mask on the periodic list yields undefined results. In reality, + * the Windows HCD links dummy qHDs at the start of the interrupt queue and these have an empty S-mask. + * If we're servicing the periodic list, check the S-mask first; that takes care of the dummy qHDs. + */ + if (enmServiceType == EHCI_SERVICE_PERIODIC) + { + // If iFrame was a micro-frame number, we should check the S-mask against it. But + // we're processing all micro-frames at once, so we'll look at any qHD with non-zero S-mask + if (qhd.Caps.SMask == 0) { + Log2Func(("periodic qHD not scheduled for current frame -> next\n")); + return true; + } + else + Log2Func(("periodic qHD scheduled for current frame, processing\n")); + } + else + { + Assert(enmServiceType == EHCI_SERVICE_ASYNC); + /* Empty schedule detection (4.10.1), for async schedule only */ + if (qhd.Characteristics.HeadReclamation) /* H-bit set but not an interrupt qHD */ + { + if (pThis->intr_status & EHCI_STATUS_RECLAMATION) + { + Log2Func(("clear EHCI_STATUS_RECLAMATION\n")); + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_RECLAMATION); + } + else + { + Log2Func(("empty schedule -> bail out\n")); + pThis->fAsyncTraversalTimerActive = true; + return false; /** stop traversing the list */ + } + } + } + + /* no active qTD here or in the next queue element -> skip to next horizontal pointer (Figure 4.14 & 4.10.2) */ + if ( !qhd.Overlay.OrgQTD.Token.Bits.Active + && qhd.Characteristics.InActiveNext) + { + Log2Func(("skip to next pointer (active)\n")); + return true; + } + /* we are ignoring the Inactivate on Next Transaction bit; only applies to periodic lists & low or full speed devices (table 3.9) */ + + /** We are not allowed to handle multiple TDs unless async park is enabled (and only for high-speed devices), but we can cheat a bit. */ + unsigned PMCount = 1; + if ( (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE) + && qhd.Characteristics.EndPtSpeed == EHCI_QHD_EPT_SPEED_HIGH + && enmServiceType == EHCI_SERVICE_ASYNC) + { + PMCount = (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT; + Log2Func(("PM Count=%d\n", PMCount)); + + /* We will attempt to queue a bit more if we're allowed to queue more than one TD. */ + if (PMCount != 1) + PMCount = 16; + } + + /* Queue as many transfer descriptors as possible */ + RTGCPHYS GCPhysQTD; + if (qhd.Overlay.OrgQTD.Token.Bits.Active) + { + Assert(ehciR3IsTdInFlight(pThisCC, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)); + GCPhysQTD = qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT; + } + else + { + /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */ + if ( !qhd.Overlay.OrgQTD.AltNext.Terminate + && qhd.Overlay.OrgQTD.Token.Bits.Length) + { + Assert(qhd.Overlay.OrgQTD.AltNext.Pointer); + Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT))); + GCPhysQTD = qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT; + } + else + { + Assert(qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Next.Terminate || qhd.Overlay.OrgQTD.Token.Bits.Halted); + if (qhd.Overlay.OrgQTD.Next.Terminate || !qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Token.Bits.Halted) + GCPhysQTD = 0; + else + GCPhysQTD = qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT; + } + } + + while (GCPhysQTD && PMCount--) + { + GCPhysQTD = ehciR3ServiceQTD(pDevIns, pThis, pThisCC, &qhd, GCPhys, GCPhysQTD, enmServiceType, iFrame); + + /* Reread the whole QHD; urb submit can call us right back which causes QH changes */ /** @todo reading too much */ + ehciR3ReadQHD(pDevIns, GCPhys, &qhd); + } + return true; +} + +/** + * Services a FSTN list + */ +static void ehciR3ServiceFSTN(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, + RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame) +{ + RT_NOREF(pDevIns, pThis, pThisCC, GCPhys, enmServiceType, iFrame); + AssertMsgFailed(("FSTN lists not implemented; should never occur!\n")); +} + +/** + * Services the async list. + * + * The async list has complex URB assembling, but that's taken + * care of at VUSB level (unlike the other transfer types). + */ +static void ehciR3ServiceAsyncList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame) +{ + RTGCPHYS GCPhysHead = pThis->async_list_base; + RTGCPHYS GCPhys = GCPhysHead; + EHCI_TD_PTR ptr; + unsigned cIterations = 0; + + Assert(!(pThis->async_list_base & 0x1f)); + Assert(pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE); + Assert(pThis->cmd & EHCI_CMD_RUN); + + Log2Func(("%RGp\n", GCPhysHead)); +#ifdef LOG_ENABLED + ehciR3DumpQH(pDevIns, GCPhysHead, true); +#endif + + /* Signal the async advance doorbell interrupt (if required) */ + if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)) +// && !pThis->cInFlight) + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_INT_ON_ASYNC_ADV); + + /* Process the list of qHDs */ + for (;;) + { + /* Process the qHD */ + if (!ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_ASYNC, iFrame)) + break; + + /* Read the next pointer */ + RTGCPHYS GCPhysLast = GCPhys; + ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr); + + /* Detect obvious loops. */ + if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT)) + break; + + /* Technically a zero address could be valid, but that's extremely unlikely! */ + Assert(ptr.Pointer || ptr.Terminate); + if (ptr.Terminate || !ptr.Pointer) + break; + + /* Not clear what we should do if this *is* something other than a qHD. */ + AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type)); + if (ptr.Type != EHCI_DESCRIPTOR_QH) + break; + + /* If we ran too many iterations, the list must be looping in on itself. + * On a real controller loops wouldn't be fatal, as it will eventually + * run out of time in the micro-frame. + */ + AssertMsgBreak(++cIterations < 128, ("Too many iterations, exiting\n")); + + /* next */ + GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT; + Assert(!(GCPhys & 0x1f)); + if ( GCPhys == GCPhysHead + || GCPhys == GCPhysLast) + break; /* break the loop */ + } + +#ifdef LOG_ENABLED + if (g_fLogControlEPs) + ehciR3DumpQH(pDevIns, GCPhysHead, true); +#endif +} + + +/** + * Services the periodic list. + * + * On the interrupt portion of the periodic list we must reassemble URBs from multiple + * TDs using heuristics derived from USB tracing done in the guests and guest source + * code (when available). + */ +static void ehciR3ServicePeriodicList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame) +{ + Assert(pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE); + +#ifdef LOG_ENABLED + RTGCPHYS pFramePtrHead = 0; + if (g_fLogInterruptEPs) + { + RTGCPHYS pFramePtr = pThis->periodic_list_base + iFrame * sizeof(EHCI_FRAME_LIST_PTR); + + pFramePtrHead = pFramePtr; + + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x before", iFrame); + ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true); + } +#endif + + /* + * Iterate the periodic list. + */ + EHCI_FRAME_LIST_PTR FramePtr; + RTGCPHYS GCPhys = pThis->periodic_list_base + iFrame * sizeof(FramePtr); + unsigned iterations = 0; + + ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr); + while (!FramePtr.Terminate && (pThis->cmd & EHCI_CMD_RUN)) + { + GCPhys = FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT; + /* Process the descriptor based on its type. Note that on the periodic + * list, HCDs may (and do) mix iTDs and qHDs more or less freely. + */ + switch(FramePtr.Type) + { + case EHCI_DESCRIPTOR_ITD: + ehciR3ServiceITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame); + break; + case EHCI_DESCRIPTOR_SITD: + ehciR3ServiceSITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame); + break; + case EHCI_DESCRIPTOR_QH: + ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame); + break; + case EHCI_DESCRIPTOR_FSTN: + ehciR3ServiceFSTN(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame); + break; + } + + /* If we ran too many iterations, the list must be looping in on itself. + * On a real controller loops wouldn't be fatal, as it will eventually + * run out of time in the micro-frame. + */ + if (++iterations == 2048) + { + AssertMsgFailed(("ehciR3ServicePeriodicList: Too many iterations, exiting\n")); + break; + } + /* Read the next link */ + ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr); + + /* Detect obvious loops. */ + if (GCPhys == ((RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT)) + break; + } + +#ifdef LOG_ENABLED + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iFrame); + ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true); + } +#endif +} + + +/** + * Calculate frame timer variables given a frame rate (1,000 Hz is the full speed). + */ +static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate) +{ + Assert(u32FrameRate <= EHCI_HARDWARE_TIMER_FREQ); + + pThis->uFramesPerTimerCall = EHCI_HARDWARE_TIMER_FREQ / u32FrameRate; + pThisCC->nsWait = RT_NS_1SEC / u32FrameRate; + pThisCC->cTicksPerFrame = pThisCC->u64TimerHz / u32FrameRate; + if (!pThisCC->cTicksPerFrame) + pThisCC->cTicksPerFrame = 1; + pThisCC->uFrameRate = u32FrameRate; +} + +/** + * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame. + */ +static void ehciR3StartOfFrame(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC) +{ + uint32_t uNewFrameRate = pThisCC->uFrameRate; +#ifdef LOG_ENABLED + const uint32_t status_old = pThis->intr_status; +#endif + + pThis->SofTime += pThisCC->cTicksPerFrame; + unsigned iFrame = (pThis->frame_idx >> EHCI_FRINDEX_FRAME_INDEX_SHIFT) & EHCI_FRINDEX_FRAME_INDEX_MASK; + + if (pThis->uIrqInterval < pThis->uFramesPerTimerCall) + pThis->uIrqInterval = 0; + else + pThis->uIrqInterval -= pThis->uFramesPerTimerCall; + + /* Empty async list detection halted the async schedule */ + if (pThis->fAsyncTraversalTimerActive) + { + /* Table 4.7 in 4.8.4.1 */ + Log2Func(("setting STATUS_RECLAMATION after empty list detection\n")); + ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION); + pThis->fAsyncTraversalTimerActive = false; + } + + /* + * Periodic EPs (Isochronous & Interrupt) + */ + if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE) + { + int num_frames = RT_MAX(1, pThis->uFramesPerTimerCall >> EHCI_FRINDEX_FRAME_INDEX_SHIFT); + Assert(num_frames > 0 && num_frames < 1024); + + ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_PERIOD_SCHED); + + if (pThis->cmd & EHCI_CMD_RUN) + { + /* If we're running the frame timer at a reduced rate, we still need to process + * all frames. Otherwise we risk completely missing newly scheduled periodic transfers. + */ + for (int i = 0; i < num_frames; ++i) + ehciR3ServicePeriodicList(pDevIns, pThis, pThisCC, (iFrame + i) & EHCI_FRINDEX_FRAME_INDEX_MASK); + } + } + else + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED); + + /* + * Async EPs (Control and Bulk) + */ + if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE) + { + ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_ASYNC_SCHED); + if (pThis->cmd & EHCI_CMD_RUN) + ehciR3ServiceAsyncList(pDevIns, pThis, pThisCC, iFrame); + } + else + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED); + + /* + * ASSUME the guest won't be very upset if a TD is completed, retired and handed + * back immediately. The idea is to be able to retire the data and/or status stages + * of a control transfer together with the setup stage, thus saving a frame. This + * behaviour is should be perfectly ok, since the setup (and maybe data) stages + * have already taken at least one frame to complete. + * + * But, when implementing the first synchronous virtual USB devices, we'll have to + * verify that the guest doesn't choke when having a TD returned in the same frame + * as it was submitted. + */ + +#ifdef LOG_ENABLED + if (pThis->intr_status ^ status_old) + { + uint32_t val = pThis->intr_status; + uint32_t chg = val ^ status_old; NOREF(chg); + Log2Func(("HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", val & 1, + chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & (3<<16)? "*" : "", (val >> 16) & 3)); + } +#endif + + /* + * Adjust the frame timer interval based on idle detection. + */ + if (pThisCC->fIdle) + { + pThisCC->cIdleCycles++; + + /* + * Set the new frame rate based on how long we've been idle. + * Don't remain more than 2 seconds in each frame rate (except for lowest one). + */ + /** @todo Experiment with these values. */ + if (pThisCC->cIdleCycles == 2 * pThisCC->uFrameRate) + { + if (pThisCC->uFrameRate > 500) + uNewFrameRate = pThisCC->uFrameRate - 500; + else + uNewFrameRate = 50; /* Absolute minimum is 50 Hertz, i.e 20ms interval. */ + + pThisCC->cIdleCycles = 1; + } + } + else + { + if (pThisCC->cIdleCycles) + { + pThisCC->cIdleCycles = 0; + uNewFrameRate = pThisCC->uFrameRateDefault; + } + } + if (uNewFrameRate != pThisCC->uFrameRate) + ehciR3CalcTimerIntervals(pThis, pThisCC, uNewFrameRate); +} + +/** + * Updates the HcFmNumber and frame_index values. HcFmNumber contains the current USB + * frame number, frame_idx is the current micro-frame. In other words, + * + * HcFmNumber == frame_idx << EHCI_FRAME_INDEX_SHIFT + */ +static void ehciR3BumpFrameNumber(PPDMDEVINS pDevIns, PEHCI pThis) +{ + pThis->HcFmNumber = pThis->frame_idx; + + const uint32_t u32OldFmNumber = pThis->HcFmNumber; + + pThis->HcFmNumber += pThis->uFramesPerTimerCall; + + if ((u32OldFmNumber ^ pThis->HcFmNumber) & ~EHCI_FRINDEX_FRAME_INDEX_MASK) + { + Log2Func(("rollover!\n")); + ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_FRAME_LIST_ROLLOVER); + } + + pThis->frame_idx = pThis->HcFmNumber; + +} + +/** + * @callback_method_impl{PFNPDMTHREADDEV, EHCI Frame Thread} + */ +static DECLCALLBACK(int) ehciR3ThreadFrame(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + int rc = VINF_SUCCESS; + while ( !ASMAtomicReadBool(&pThis->fBusStarted) + && pThread->enmState == PDMTHREADSTATE_RUNNING) + { + /* Make sure the SCHED status bits are clear. */ + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED); + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED); + + /* Signal the waiter that we are stopped now. */ + rc = RTSemEventMultiSignal(pThisCC->hSemEventFrameStopped); + AssertRC(rc); + + rc = RTSemEventMultiWait(pThisCC->hSemEventFrame, RT_INDEFINITE_WAIT); + RTSemEventMultiReset(pThisCC->hSemEventFrame); + } + + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + + uint64_t const tsNanoStart = RTTimeNanoTS(); + + RTCritSectEnter(&pThisCC->CritSect); + + /* Reset idle detection flag */ + pThisCC->fIdle = true; + + /* Frame boundary, so do EOF stuff here */ + ehciR3StartOfFrame(pDevIns, pThis, pThisCC); + + /* Start the next frame */ + ehciR3BumpFrameNumber(pDevIns, pThis); + + RTCritSectLeave(&pThisCC->CritSect); + + /* Wait for the next round. */ + uint64_t nsWait = (RTTimeNanoTS() + pThisCC->nsWait) - tsNanoStart; + + rc = RTSemEventMultiWaitEx(pThisCC->hSemEventFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE, + nsWait); + AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc)); + RTSemEventMultiReset(pThisCC->hSemEventFrame); + } + + return VINF_SUCCESS; +} + +/** + * Unblock the framer thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThread The send thread. + */ +static DECLCALLBACK(int) ehciR3ThreadFrameWakeup(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + RT_NOREF(pThread); + return RTSemEventMultiSignal(pThisCC->hSemEventFrame); +} + +/** + * Start sending SOF tokens across the USB bus, lists are processed in next frame. + */ +static void ehciR3BusStart(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC) +{ + pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn); + ehciR3BumpFrameNumber(pDevIns, pThis); + + LogFunc(("Bus started\n")); + + ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_HCHALTED); + pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns) - pThisCC->cTicksPerFrame; + bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, true); + if (!fBusActive) + RTSemEventMultiSignal(pThisCC->hSemEventFrame); +} + +/** + * Stop sending SOF tokens on the bus + */ +static void ehciR3BusStop(PEHCI pThis, PEHCICC pThisCC) +{ + LogFunc(("\n")); + bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, false); + if (fBusActive) + { + int rc = RTSemEventMultiReset(pThisCC->hSemEventFrameStopped); + AssertRC(rc); + + /* Signal the frame thread to stop. */ + RTSemEventMultiSignal(pThisCC->hSemEventFrame); + + /* Wait for signal from the thread that it stopped. */ + rc = RTSemEventMultiWait(pThisCC->hSemEventFrameStopped, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn); + ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_HCHALTED); +} + +/** + * Power a port up or down + */ +static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp) +{ + bool fOldPPS = !!(pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_POWER); + if (fPowerUp) + { + Log2Func(("port %d UP\n", iPort)); + /* power up */ + if (pThisCC->RootHub.aPorts[iPort].fAttached) + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT); + if (pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT) + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_POWER); + if (pThisCC->RootHub.aPorts[iPort].fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort)); + } + else + { + Log2(("Func port %d DOWN\n", iPort)); + /* power down */ + ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_POWER|EHCI_PORT_CURRENT_CONNECT)); + if (pThisCC->RootHub.aPorts[iPort].fAttached && fOldPPS) + VUSBIRhDevPowerOff(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort)); + } +} + +/** + * Completion callback for the VUSBIDevReset() operation. + * @thread EMT. + */ +static void ehciR3PortResetDone(PEHCI pThis, PEHCICC pThisCC, uint32_t uPort, int rc) +{ + Log2Func(("rc=%Rrc\n", rc)); + Assert(uPort >= 1); + unsigned iPort = uPort - 1; + + if (RT_SUCCESS(rc)) + { + /* + * Successful reset. + */ + Log2Func(("Reset completed.\n")); + /* Note: XP relies on us clearing EHCI_PORT_CONNECT_CHANGE */ + ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND | EHCI_PORT_CONNECT_CHANGE)); + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_PORT_ENABLED); + } + else + { + /* desperate measures. */ + if ( pThisCC->RootHub.aPorts[iPort].fAttached + && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED) + { + /* + * Damn, something weird happend during reset. We'll pretend the user did an + * incredible fast reconnect or something. (prolly not gonna work) + */ + Log2Func(("The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc)); + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE); + } + else + { + /* + * The device has / will be disconnected. + */ + Log2Func(("Disconnected (rc=%Rrc)!!!\n", rc)); + ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND)); + ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CONNECT_CHANGE); + } + } +} + +/** + * Sets a flag in a port status register but only set it if a device is + * connected, if not set ConnectStatusChange flag to force HCD to reevaluate + * connect status. + * + * @returns true if device was connected and the flag was cleared. + */ +static bool ehciR3RhPortSetIfConnected(PEHCIROOTHUB pRh, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * The port might be still/already disconnected. + */ + if (!(pRh->aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT)) + return false; + + bool fRc = !(pRh->aPorts[iPort].fReg & fValue); + + /* set the bit */ + ASMAtomicOrU32(&pRh->aPorts[iPort].fReg, fValue); + + return fRc; +} + +#endif /* IN_RING3 */ + + +/** + * Read the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcCommand_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + /* Signal the async advance doorbell interrupt (if required) + * XP polls the command register to see when it can queue up more TDs. + */ + if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)) +// && !pThis->cInFlight) + { + int rc = ehciSetInterrupt(pDevIns, pThis, VINF_IOM_R3_MMIO_READ, EHCI_STATUS_INT_ON_ASYNC_ADV); + if (rc != VINF_SUCCESS) + return rc; + } + + *pu32Value = pThis->cmd; + RT_NOREF(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcCommand_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); +#endif + RT_NOREF(pDevIns, iReg); + +#ifdef LOG_ENABLED + Log(("HcCommand_w old=%x new=%x\n", pThis->cmd, val)); + if (val & EHCI_CMD_RUN) + Log((" CMD_RUN\n")); + if (val & EHCI_CMD_RESET) + Log((" CMD_RESET\n")); + if (val & EHCI_CMD_PERIODIC_SCHED_ENABLE) + Log((" CMD_PERIODIC_SCHED_ENABLE\n")); + if (val & EHCI_CMD_ASYNC_SCHED_ENABLE) + Log((" CMD_ASYNC_SCHED_ENABLE\n")); + if (val & EHCI_CMD_INT_ON_ADVANCE_DOORBELL) + Log((" CMD_INT_ON_ADVANCE_DOORBELL\n")); + if (val & EHCI_CMD_SOFT_RESET) + Log((" CMD_SOFT_RESET\n")); + if (val & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE) + Log((" CMD_ASYNC_SCHED_PARK_ENABLE\n")); + + Log((" CMD_FRAME_LIST_SIZE %d\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT)); + Log((" CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (val & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT)); + Log((" CMD_INTERRUPT_THRESHOLD %d\n", (val & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT)); +#endif + + Assert(!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST)); /* hardcoded assumptions about list size */ + if (!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST)) + { + if (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) + Log(("Trying to change the frame list size to %d even though it's hardcoded at 1024 elements!!\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT)); + + val &= ~EHCI_CMD_FRAME_LIST_SIZE_MASK; /* 00 = 1024 */ + } + if (val & ~EHCI_CMD_MASK) + Log(("Unknown bits %#x are set!!!\n", val & ~0x0003000f)); + + uint32_t old_cmd = pThis->cmd; +#ifdef IN_RING3 + pThis->cmd = val; +#endif + + if (val & EHCI_CMD_RESET) + { +#ifdef IN_RING3 + LogRel(("EHCI: Hardware reset\n")); + ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else if (val & EHCI_CMD_SOFT_RESET) + { +#ifdef IN_RING3 + LogRel(("EHCI: Software reset\n")); + ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else + { + /* see what changed and take action on that. */ + uint32_t old_state = old_cmd & EHCI_CMD_RUN; + uint32_t new_state = val & EHCI_CMD_RUN; + + if (old_state != new_state) + { +#ifdef IN_RING3 + switch (new_state) + { + case EHCI_CMD_RUN: + LogRel(("EHCI: USB Operational\n")); + ehciR3BusStart(pDevIns, pThis, pThisCC); + break; + case 0: + ehciR3BusStop(pThis, pThisCC); + LogRel(("EHCI: USB Suspended\n")); + break; + } +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + } +#ifndef IN_RING3 + pThis->cmd = val; +#endif + return VINF_SUCCESS; +} + +/** + * Read the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcStatus_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); +#ifdef LOG_ENABLED + Log(("HcStatus_r current value %x\n", pThis->intr_status)); + if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED) + Log((" STATUS_ASYNC_SCHED\n")); + if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED) + Log((" STATUS_PERIOD_SCHED\n")); + if (pThis->intr_status & EHCI_STATUS_RECLAMATION) + Log((" STATUS_RECLAMATION\n")); + if (pThis->intr_status & EHCI_STATUS_HCHALTED) + Log((" STATUS_HCHALTED\n")); + if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV) + Log((" STATUS_INT_ON_ASYNC_ADV\n")); + if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR) + Log((" STATUS_HOST_SYSTEM_ERROR\n")); + if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER) + Log((" STATUS_FRAME_LIST_ROLLOVER\n")); + if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT) + Log((" STATUS_PORT_CHANGE_DETECT\n")); + if (pThis->intr_status & EHCI_STATUS_ERROR_INT) + Log((" STATUS_ERROR_INT\n")); + if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT) + Log((" STATUS_THRESHOLD_INT\n")); +#endif + *pu32Value = pThis->intr_status; + return VINF_SUCCESS; +} + +/** + * Write to the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcStatus_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + +#ifdef LOG_ENABLED + Log(("HcStatus_w current value %x; new %x\n", pThis->intr_status, val)); + if (val & EHCI_STATUS_ASYNC_SCHED) + Log((" STATUS_ASYNC_SCHED\n")); + if (val & EHCI_STATUS_PERIOD_SCHED) + Log((" STATUS_PERIOD_SCHED\n")); + if (val & EHCI_STATUS_RECLAMATION) + Log((" STATUS_RECLAMATION\n")); + if (val & EHCI_STATUS_HCHALTED) + Log((" STATUS_HCHALTED\n")); + if (val & EHCI_STATUS_INT_ON_ASYNC_ADV) + Log((" STATUS_INT_ON_ASYNC_ADV\n")); + if (val & EHCI_STATUS_HOST_SYSTEM_ERROR) + Log((" STATUS_HOST_SYSTEM_ERROR\n")); + if (val & EHCI_STATUS_FRAME_LIST_ROLLOVER) + Log((" STATUS_FRAME_LIST_ROLLOVER\n")); + if (val & EHCI_STATUS_PORT_CHANGE_DETECT) + Log((" STATUS_PORT_CHANGE_DETECT\n")); + if (val & EHCI_STATUS_ERROR_INT) + Log((" STATUS_ERROR_INT\n")); + if (val & EHCI_STATUS_THRESHOLD_INT) + Log((" STATUS_THRESHOLD_INT\n")); +#endif + if ( (val & ~EHCI_STATUS_INTERRUPT_MASK) + && val != 0xffffffff /* ignore clear-all-like requests from xp. */) + Log(("Unknown bits %#x are set!!!\n", val & ~EHCI_STATUS_INTERRUPT_MASK)); + + /* Some bits are read-only */ + val &= EHCI_STATUS_INTERRUPT_MASK; + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + ASMAtomicAndU32(&pThis->intr_status, ~val); + ehciUpdateInterruptLocked(pDevIns, pThis, "HcStatus_w"); + + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the USBINTR register of the host controller. + */ +static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + *pu32Value = pThis->intr; + return VINF_SUCCESS; +} + +/** + * Write to the USBINTR register of the host controller. + */ +static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); +#ifdef LOG_ENABLED + Log(("HcInterruptEnable_w -> new value %x\n", val)); + if (val & EHCI_INTR_ENABLE_THRESHOLD) + Log((" INTR_ENABLE_THRESHOLD\n")); + if (val & EHCI_INTR_ENABLE_ERROR) + Log((" INTR_ENABLE_ERROR\n")); + if (val & EHCI_INTR_ENABLE_PORT_CHANGE) + Log((" INTR_ENABLE_PORT_CHANGE\n")); + if (val & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER) + Log((" INTR_ENABLE_FRAME_LIST_ROLLOVER\n")); + if (val & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR) + Log((" INTR_ENABLE_HOST_SYSTEM_ERROR\n")); + if (val & EHCI_INTR_ENABLE_ASYNC_ADVANCE) + Log((" INTR_ENABLE_ASYNC_ADVANCE\n")); + if (val & ~EHCI_INTR_ENABLE_MASK) + Log((" Illegal bits set %x!!\n", val & ~EHCI_INTR_ENABLE_MASK)); +#endif + VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc == VINF_SUCCESS) + { + pThis->intr = val & EHCI_INTR_ENABLE_MASK; + ehciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + } + return rc; +} + +/** + * Read the FRINDEX register of the host controller. + */ +static VBOXSTRICTRC HcFrameIndex_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2Func(("current frame %x\n", pThis->frame_idx)); + *pu32Value = pThis->frame_idx; + return VINF_SUCCESS; +} + +/** + * Write to the FRINDEX register of the host controller. + */ +static VBOXSTRICTRC HcFrameIndex_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + LogFunc(("pThis->frame_idx new index=%x\n", val)); + if (!(pThis->intr_status & EHCI_STATUS_HCHALTED)) + Log(("->>Updating the frame index while the controller is running!!!\n")); + + ASMAtomicXchgU32(&pThis->frame_idx, val); + return VINF_SUCCESS; +} + +/** + * Read the CTRLDSSEGMENT register of the host controller. + */ +static VBOXSTRICTRC HcControlDSSeg_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING) + *pu32Value = pThis->ds_segment; + else + *pu32Value = 0; + + return VINF_SUCCESS; +} + +/** + * Write to the CTRLDSSEGMENT register of the host controller. + */ +static VBOXSTRICTRC HcControlDSSeg_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + LogFunc(("new base %x\n", val)); + if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING) + ASMAtomicXchgU32(&pThis->ds_segment, val); + + return VINF_SUCCESS; +} + +/** + * Read the PERIODICLISTBASE register of the host controller. + */ +static VBOXSTRICTRC HcPeriodicListBase_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2Func(("current base %x\n", pThis->periodic_list_base)); + *pu32Value = pThis->periodic_list_base; + return VINF_SUCCESS; +} + +/** + * Write to the PERIODICLISTBASE register of the host controller. + */ +static VBOXSTRICTRC HcPeriodicListBase_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + LogFunc(("new base %x\n", val)); + if (val & ~EHCI_PERIODIC_LIST_MASK) + Log(("->> Base not aligned on a 4kb boundary!!!!\n")); + if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED) + && (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE)) + Log(("->>Updating the periodic list base while the controller is running!!!\n")); + + ASMAtomicXchgU32(&pThis->periodic_list_base, val & EHCI_PERIODIC_LIST_MASK); + return VINF_SUCCESS; +} + +/** + * Read the ASYNCLISTADDR register of the host controller. + */ +static VBOXSTRICTRC HcAsyncListAddr_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2Func(("current base %x\n", pThis->async_list_base)); + *pu32Value = pThis->async_list_base; + return VINF_SUCCESS; +} + +/** + * Write to the ASYNCLISTADDR register of the host controller. + */ +static VBOXSTRICTRC HcAsyncListAddr_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + LogFunc(("new address %x\n", val)); + if (val & ~EHCI_ASYNC_LIST_MASK) + Log(("->> Base not aligned on a 32-byte boundary!!!!\n")); + if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED) + && (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE)) + Log(("->>Updating the asynchronous list address while the controller is running!!!\n")); + + ASMAtomicXchgU32(&pThis->async_list_base, val & EHCI_ASYNC_LIST_MASK); + return VINF_SUCCESS; +} + +/** + * Read the CONFIGFLAG register of the host controller. + */ +static VBOXSTRICTRC HcConfigFlag_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2Func(("current config=%x\n", pThis->config)); + *pu32Value = pThis->config; + return VINF_SUCCESS; +} + +/** + * Write to the CONFIGFLAG register of the host controller. + */ +static VBOXSTRICTRC HcConfigFlag_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + LogFunc(("new configuration routing %x\n", val & EHCI_CONFIGFLAG_ROUTING)); + pThis->config = val & EHCI_CONFIGFLAG_MASK; + return VINF_SUCCESS; +} + +/** + * Read the PORTSC register of a port + */ +static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + const unsigned i = iReg - 1; + PEHCIHUBPORT p = &pThis->RootHub.aPorts[i]; + RT_NOREF(pDevIns); + + Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL)); + + if (p->fReg & EHCI_PORT_RESET) + { +#ifdef IN_RING3 + Log2Func(("port %u: Impatient guest!\n", i)); + RTThreadYield(); +#else + Log2Func(("yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + + *pu32Value = p->fReg; + return VINF_SUCCESS; +} + +/** + * Write to the PORTSC register of a port + */ +static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val) +{ + const unsigned i = iReg - 1; + PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[i]; + + if ( pPort->fReg == val + && !(val & EHCI_PORT_CHANGE_MASK)) + return VINF_SUCCESS; + +#ifdef IN_RING3 + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + + LogFunc(("port %d: old=%x new=%x\n", i, pPort->fReg, val)); + Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL)); + Assert(pPort->fReg & EHCI_PORT_POWER); + + if (val & EHCI_PORT_RESERVED) + Log(("Invalid bits set %x!!!\n", val & EHCI_PORT_RESERVED)); + + /* Write to clear any of the change bits: EHCI_PORT_CONNECT_CHANGE, EHCI_PORT_PORT_CHANGE and EHCI_PORT_OVER_CURRENT_CHANGE */ + if (val & EHCI_PORT_CHANGE_MASK) + { + ASMAtomicAndU32(&pPort->fReg, ~(val & EHCI_PORT_CHANGE_MASK)); + /* XP seems to need this after device detach */ + if (!(pPort->fReg & EHCI_PORT_CURRENT_CONNECT)) + ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_CONNECT_CHANGE); + } + + /* Writing the Port Enable/Disable bit as 1 has no effect; software cannot enable + * the port that way. Writing the bit as zero does disable the port, but does not + * set the corresponding 'changed' bit or trigger an interrupt. + */ + if (!(val & EHCI_PORT_PORT_ENABLED) && (pPort->fReg & EHCI_PORT_PORT_ENABLED)) + { + ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_PORT_ENABLED); + LogFunc(("port %u: DISABLE\n", i)); + } + + if (val & EHCI_PORT_SUSPEND) + LogFunc(("port %u: SUSPEND - not implemented correctly!!!\n", i)); + + if (val & EHCI_PORT_RESET) + { + Log2Func(("Reset port\n")); + if ( ehciR3RhPortSetIfConnected(&pThis->RootHub, i, val & EHCI_PORT_RESET) ) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, NULL /* sync */, pThis, pVM); + ehciR3PortResetDone(pThis, pThisCC, EHCI_PORT_2_VUSB_PORT(i), VINF_SUCCESS); + } + else if (pPort->fReg & EHCI_PORT_RESET) + { + /* the guest is getting impatient. */ + Log2Func(("port %u: Impatient guest!\n", i)); + RTThreadYield(); + } + } + + /* EHCI_PORT_POWER ignored as we don't support this in HCS_PARAMS */ + /* EHCI_PORT_INDICATOR ignored as we don't support this in HCS_PARAMS */ + /* EHCI_PORT_TEST_CONTROL_MASK ignored */ + ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_WAKE_MASK); + ASMAtomicOrU32(&pPort->fReg, (val & EHCI_PORT_WAKE_MASK)); + return VINF_SUCCESS; + +#else /* !IN_RING3 */ + RT_NOREF(pDevIns); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Register descriptor table + */ +static const EHCIOPREG g_aOpRegs[] = +{ + {"HcCommand" , HcCommand_r, HcCommand_w}, + {"HcStatus", HcStatus_r, HcStatus_w}, + {"HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w}, + {"HcFrameIndex", HcFrameIndex_r, HcFrameIndex_w}, + {"HcControlDSSeg", HcControlDSSeg_r, HcControlDSSeg_w}, + {"HcPeriodicListBase", HcPeriodicListBase_r, HcPeriodicListBase_w}, + {"HcAsyncListAddr", HcAsyncListAddr_r, HcAsyncListAddr_w} +}; + +/** + * Register descriptor table 2 + * (Starting at offset 0x40) + */ +static const EHCIOPREG g_aOpRegs2[] = +{ + {"HcConfigFlag", HcConfigFlag_r, HcConfigFlag_w}, + + /* The number of port status register depends on the definition + * of EHCI_NDP_MAX macro + */ + {"HcPortStatusCtrl[0]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[1]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[2]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[3]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[4]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[5]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[6]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[7]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[8]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[9]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[10]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[11]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[12]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[13]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, + {"HcPortStatusCtrl[14]", HcPortStatusCtrl_r, HcPortStatusCtrl_w}, +}; + +/* Quick way to determine how many op regs are valid. Since at least one port must + * be configured (and no more than 15), there will be between 2 and 16 registers. + */ +#define NUM_OP_REGS2(pehci) (1 + EHCI_NDP_CFG(pehci)) + +AssertCompile(RT_ELEMENTS(g_aOpRegs2) > 1); +AssertCompile(RT_ELEMENTS(g_aOpRegs2) <= 16); + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. } + * + * @note We only accept 32-bit reads that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) ehciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + RT_NOREF(pvUser); + + Log2Func(("%RGp size=%d\n", off, cb)); + + if (off < EHCI_CAPS_REG_SIZE) + { + switch (off) + { + case 0x0: /* CAPLENGTH */ + /* read CAPLENGTH + HCIVERSION in one go */ + if (cb == 4) + { + *(uint32_t *)pv = (pThis->hci_version << 16) | pThis->cap_length; + return VINF_SUCCESS; + } + + AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF); + *(uint8_t *)pv = pThis->cap_length; + break; + + case 0x2: /* HCIVERSION */ + AssertReturn(cb == 2, VINF_IOM_MMIO_UNUSED_FF); + *(uint16_t *)pv = pThis->hci_version; + break; + + case 0x4: /* HCSPARAMS (structural) */ + AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF); + *(uint32_t *)pv = pThis->hcs_params; + break; + + case 0x8: /* HCCPARAMS (caps) */ + AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF); + *(uint32_t *)pv = pThis->hcc_params; + break; + + case 0x9: /* one byte HCIPARAMS read (XP; EHCI extended capability offset) */ + AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF); + *(uint8_t *)pv = (uint8_t)(pThis->hcc_params >> 8); + break; + + case 0xC: /* HCSP-PORTROUTE (60 bits) */ + case 0x10: + AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF); + *(uint32_t *)pv = 0; + break; + + default: + LogFunc(("Trying to read register %#x!!!\n", off)); + return VINF_IOM_MMIO_UNUSED_FF; + } + Log2Func(("%RGp size=%d -> val=%x\n", off, cb, *(uint32_t *)pv)); + return VINF_SUCCESS; + } + + /* + * Validate the access. + */ + if (cb != sizeof(uint32_t)) + { + Log2Func(("Bad read size!!! off=%RGp cb=%d\n", off, cb)); + return VINF_IOM_MMIO_UNUSED_FF; /* No idea what really would happen... */ + } + if (off & 0x3) + { + Log2Func(("Unaligned read!!! off=%RGp cb=%d\n", off, cb)); + return VINF_IOM_MMIO_UNUSED_FF; + } + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + uint32_t iReg = (off - pThis->cap_length) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const EHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv); + Log2Func(("%RGp size=%d -> val=%x (rc=%d)\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); + } + else if (iReg >= 0x10) /* 0x40 */ + { + iReg -= 0x10; + if (iReg < NUM_OP_REGS2(pThis)) + { + const EHCIOPREG *pReg = &g_aOpRegs2[iReg]; + rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv); + Log2Func(("%RGp size=%d -> val=%x (rc=%d)*\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc))); + } + else + { + LogFunc(("Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis))); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + } + else + { + LogFunc(("Trying to read register %u/%u (2)!!!\n", iReg, RT_ELEMENTS(g_aOpRegs))); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. } + * + * @note We only accept 32-bit writes that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) ehciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + RT_NOREF(pvUser); + + Log2Func(("%RGp %x size=%d\n", off, *(uint32_t *)pv, cb)); + + if (off < EHCI_CAPS_REG_SIZE) + { + /* These are read-only */ + LogFunc(("Trying to write to register %#x!!!\n", off)); + return VINF_SUCCESS; + } + + /* + * Validate the access. + */ + if (cb != sizeof(uint32_t)) + { + Log2Func(("Bad write size!!! off=%RGp cb=%d\n", off, cb)); + return VINF_SUCCESS; + } + if (off & 0x3) + { + Log2Func(("Unaligned write!!! off=%RGp cb=%d\n", off, cb)); + return VINF_SUCCESS; + } + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + uint32_t iReg = (off - pThis->cap_length) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const EHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv); + } + else if (iReg >= 0x10) /* 0x40 */ + { + iReg -= 0x10; + if (iReg < NUM_OP_REGS2(pThis)) + { + const EHCIOPREG *pReg = &g_aOpRegs2[iReg]; + rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv); + } + else + { + LogFunc(("Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis))); + rc = VINF_SUCCESS; /* ignore the invalid write */ + } + } + else + { + LogFunc(("Trying to write to register %u/%u!!! (2)\n", iReg, RT_ELEMENTS(g_aOpRegs))); + rc = VINF_SUCCESS; /* ignore the invalid write */ + } + return rc; +} + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) ehciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + LogFlowFunc(("\n")); + return pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL); +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) ehciLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + LogFlowFunc(("\n")); + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + if (uVersion == EHCI_SAVED_STATE_VERSION) + { + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL); + if (RT_FAILURE(rc)) + return rc; + } + else if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL) + { + static SSMFIELD const g_aEhciFieldsPreTimerRemoval[] = + { + SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive), + SSMFIELD_ENTRY( EHCI, SofTime), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg), + SSMFIELD_ENTRY( EHCI, cap_length), + SSMFIELD_ENTRY( EHCI, hci_version), + SSMFIELD_ENTRY( EHCI, hcs_params), + SSMFIELD_ENTRY( EHCI, hcc_params), + SSMFIELD_ENTRY( EHCI, cmd), + SSMFIELD_ENTRY( EHCI, intr_status), + SSMFIELD_ENTRY( EHCI, intr), + SSMFIELD_ENTRY( EHCI, frame_idx), + SSMFIELD_ENTRY( EHCI, ds_segment), + SSMFIELD_ENTRY( EHCI, periodic_list_base), + SSMFIELD_ENTRY( EHCI, async_list_base), + SSMFIELD_ENTRY( EHCI, config), + SSMFIELD_ENTRY( EHCI, uIrqInterval), + SSMFIELD_ENTRY( EHCI, HcFmNumber), + SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall), + SSMFIELD_ENTRY_TERM() + }; + + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFieldsPreTimerRemoval[0], NULL); + if (RT_FAILURE(rc)) + return rc; + AssertReturn(EHCI_NDP_CFG(pThis) <= EHCI_NDP_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + } + else if (uVersion == EHCI_SAVED_STATE_VERSION_8PORTS) + { + static SSMFIELD const s_aEhciFields8Ports[] = + { + SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive), + SSMFIELD_ENTRY( EHCI, SofTime), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.unused), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( EHCI, cap_length), + SSMFIELD_ENTRY( EHCI, hci_version), + SSMFIELD_ENTRY( EHCI, hcs_params), + SSMFIELD_ENTRY( EHCI, hcc_params), + SSMFIELD_ENTRY( EHCI, cmd), + SSMFIELD_ENTRY( EHCI, intr_status), + SSMFIELD_ENTRY( EHCI, intr), + SSMFIELD_ENTRY( EHCI, frame_idx), + SSMFIELD_ENTRY( EHCI, ds_segment), + SSMFIELD_ENTRY( EHCI, periodic_list_base), + SSMFIELD_ENTRY( EHCI, async_list_base), + SSMFIELD_ENTRY( EHCI, config), + SSMFIELD_ENTRY( EHCI, uIrqInterval), + SSMFIELD_ENTRY( EHCI, HcFmNumber), + SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall), + SSMFIELD_ENTRY_TERM() + }; + + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &s_aEhciFields8Ports[0], NULL); + if (RT_FAILURE(rc)) + return rc; + AssertReturn(EHCI_NDP_CFG(pThis) == 8, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + } + else + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + /* + * The EOF timer changed from one to two in version 4 of the saved state, + * then was dropped entirely in version 7. + * + * Note! Looks like someone remove the code that dealt with versions 1 thru 4, + * without adjust the above comment. + */ + if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL) + { + bool fActive1 = false; + pHlp->pfnTimerSkipLoad(pSSM, &fActive1); + bool fActive2 = false; + pHlp->pfnTimerSkipLoad(pSSM, &fActive2); + bool fNoSync = false; + rc = pHlp->pfnSSMGetBool(pSSM, &fNoSync); + if ( RT_SUCCESS(rc) + && (fActive1 || fActive2)) + pThis->fBusStarted = true; + else + pThis->fBusStarted = false; + } + return rc; +} + + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, Dumps EHCI control registers.} + */ +static DECLCALLBACK(void) ehciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + unsigned uPort; + + /* Command register */ + pHlp->pfnPrintf(pHlp, "USBCMD: %x\n", pThis->cmd); + if (pThis->cmd & EHCI_CMD_RUN) + pHlp->pfnPrintf(pHlp, " CMD_RUN\n"); + if (pThis->cmd & EHCI_CMD_RESET) + pHlp->pfnPrintf(pHlp, " CMD_RESET\n"); + if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE) + pHlp->pfnPrintf(pHlp, " CMD_PERIODIC_SCHED_ENABLE\n"); + if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE) + pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_ENABLE\n"); + if (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL) + pHlp->pfnPrintf(pHlp, " CMD_INT_ON_ADVANCE_DOORBELL\n"); + if (pThis->cmd & EHCI_CMD_SOFT_RESET) + pHlp->pfnPrintf(pHlp, " CMD_SOFT_RESET\n"); + if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE) + pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_ENABLE\n"); + + pHlp->pfnPrintf(pHlp, " CMD_FRAME_LIST_SIZE %d\n", (pThis->cmd & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT); + pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT); + pHlp->pfnPrintf(pHlp, " CMD_INTERRUPT_THRESHOLD %d\n", (pThis->cmd & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT); + + /* Status register */ + pHlp->pfnPrintf(pHlp, "USBSTS: %x\n", pThis->intr_status); + if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED) + pHlp->pfnPrintf(pHlp, " STATUS_ASYNC_SCHED\n"); + if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED) + pHlp->pfnPrintf(pHlp, " STATUS_PERIOD_SCHED\n"); + if (pThis->intr_status & EHCI_STATUS_RECLAMATION) + pHlp->pfnPrintf(pHlp, " STATUS_RECLAMATION\n"); + if (pThis->intr_status & EHCI_STATUS_HCHALTED) + pHlp->pfnPrintf(pHlp, " STATUS_HCHALTED\n"); + if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV) + pHlp->pfnPrintf(pHlp, " STATUS_INT_ON_ASYNC_ADV\n"); + if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR) + pHlp->pfnPrintf(pHlp, " STATUS_HOST_SYSTEM_ERROR\n"); + if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER) + pHlp->pfnPrintf(pHlp, " STATUS_FRAME_LIST_ROLLOVER\n"); + if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT) + pHlp->pfnPrintf(pHlp, " STATUS_PORT_CHANGE_DETECT\n"); + if (pThis->intr_status & EHCI_STATUS_ERROR_INT) + pHlp->pfnPrintf(pHlp, " STATUS_ERROR_INT\n"); + if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT) + pHlp->pfnPrintf(pHlp, " STATUS_THRESHOLD_INT\n"); + + /* Interrupt enable register */ + pHlp->pfnPrintf(pHlp, "USBINTR: %x\n", pThis->intr); + if (pThis->intr & EHCI_INTR_ENABLE_THRESHOLD) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_THRESHOLD\n"); + if (pThis->intr & EHCI_INTR_ENABLE_ERROR) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ERROR\n"); + if (pThis->intr & EHCI_INTR_ENABLE_PORT_CHANGE) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_PORT_CHANGE\n"); + if (pThis->intr & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_FRAME_LIST_ROLLOVER\n"); + if (pThis->intr & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_HOST_SYSTEM_ERROR\n"); + if (pThis->intr & EHCI_INTR_ENABLE_ASYNC_ADVANCE) + pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ASYNC_ADVANCE\n"); + if (pThis->intr & ~EHCI_INTR_ENABLE_MASK) + pHlp->pfnPrintf(pHlp, " Illegal bits set %x!!\n", pThis->intr & ~EHCI_INTR_ENABLE_MASK); + + /* Frame index register */ + pHlp->pfnPrintf(pHlp, "FRINDEX: %x\n", pThis->frame_idx); + + /* Control data structure segment */ + pHlp->pfnPrintf(pHlp, "CTRLDSSEGMENT: %RX32\n", pThis->ds_segment); + + /* Periodic frame list base address register */ + pHlp->pfnPrintf(pHlp, "PERIODICLISTBASE: %RX32\n", pThis->periodic_list_base); + + /* Current asynchronous list address register */ + pHlp->pfnPrintf(pHlp, "ASYNCLISTADDR: %RX32\n", pThis->async_list_base); + + pHlp->pfnPrintf(pHlp, "\n"); + + for (uPort = 0; uPort < EHCI_NDP_CFG(pThis); ++uPort) + { + PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort]; + pHlp->pfnPrintf(pHlp, "PORTSC for port %u:\n", uPort); + if (pPort->fReg & EHCI_PORT_CURRENT_CONNECT) + pHlp->pfnPrintf(pHlp, " PORT_CURRENT_CONNECT\n"); + if (pPort->fReg & EHCI_PORT_CONNECT_CHANGE) + pHlp->pfnPrintf(pHlp, " PORT_CONNECT_CHANGE\n"); + if (pPort->fReg & EHCI_PORT_PORT_ENABLED) + pHlp->pfnPrintf(pHlp, " PORT_PORT_ENABLED\n"); + if (pPort->fReg & EHCI_PORT_PORT_CHANGE) + pHlp->pfnPrintf(pHlp, " PORT_PORT_CHANGE\n"); + if (pPort->fReg & EHCI_PORT_OVER_CURRENT_ACTIVE) + pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_ACTIVE\n"); + if (pPort->fReg & EHCI_PORT_OVER_CURRENT_CHANGE) + pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_CHANGE\n"); + if (pPort->fReg & EHCI_PORT_FORCE_PORT_RESUME) + pHlp->pfnPrintf(pHlp, " PORT_FORCE_PORT_RESUME\n"); + if (pPort->fReg & EHCI_PORT_SUSPEND) + pHlp->pfnPrintf(pHlp, " PORT_SUSPEND\n"); + if (pPort->fReg & EHCI_PORT_RESET) + pHlp->pfnPrintf(pHlp, " PORT_RESET\n"); + pHlp->pfnPrintf(pHlp, " LINE_STATUS: "); + switch ((pPort->fReg & EHCI_PORT_LINE_STATUS_MASK) >> EHCI_PORT_LINE_STATUS_SHIFT) + { + case 0: + pHlp->pfnPrintf(pHlp, " SE0 (0), not low-speed\n"); + break; + case 1: + pHlp->pfnPrintf(pHlp, " K-state (1), low-speed device\n"); + break; + case 2: + pHlp->pfnPrintf(pHlp, " J-state (2), not low-speed\n"); + break; + default: + case 3: + pHlp->pfnPrintf(pHlp, " Undefined (3)\n"); + break; + } + if (pPort->fReg & EHCI_PORT_POWER) + pHlp->pfnPrintf(pHlp, " PORT_POWER\n"); + if (pPort->fReg & EHCI_PORT_OWNER) + pHlp->pfnPrintf(pHlp, " PORT_OWNER (1 = owned by companion HC)\n"); + if (pPort->fReg & EHCI_PORT_WAKE_ON_CONNECT_ENABLE) + pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_CONNECT_ENABLE\n"); + if (pPort->fReg & EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE) + pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_DISCONNECT_ENABLE\n"); + if (pPort->fReg & EHCI_PORT_WAKE_OVER_CURRENT_ENABLE) + pHlp->pfnPrintf(pHlp, " PORT_WAKE_OVER_CURRENT_ENABLE\n"); + } +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) ehciR3Reset(PPDMDEVINS pDevIns) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + LogFlowFunc(("\n")); + + /* + * There is no distinction between cold boot, warm reboot and software reboots, + * all of these are treated as cold boots. We are also doing the initialization + * job of a BIOS or SMM driver. + * + * Important: Don't confuse UsbReset with hardware reset. Hardware reset is + * just one way of getting into the UsbReset state. + */ + ehciR3BusStop(pThis, pThisCC); + ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */); +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ehciR3Resume(PPDMDEVINS pDevIns) +{ + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + LogFlowFunc(("\n")); + + /* Restart the frame thread if the timer is active. */ + if (pThis->fBusStarted) + { + LogFlowFunc(("Bus was active, restart frame thread\n")); + RTSemEventMultiSignal(pThisCC->hSemEventFrame); + } +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) ehciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + LogFlowFunc(("\n")); + + if (pThisCC->hSemEventFrame != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(pThisCC->hSemEventFrame); + pThisCC->hSemEventFrame = NIL_RTSEMEVENTMULTI; + } + + if (pThisCC->hSemEventFrameStopped != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(pThisCC->hSemEventFrameStopped); + pThisCC->hSemEventFrameStopped = NIL_RTSEMEVENTMULTI; + } + + if (RTCritSectIsInitialized(&pThisCC->CritSect)) + RTCritSectDelete(&pThisCC->CritSect); + PDMDevHlpCritSectDelete(pDevIns, &pThis->CsIrq); + + /* + * Tear down the per endpoint in-flight tracking... + */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct,EHCI constructor} + */ +static DECLCALLBACK(int) ehciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + LogFlowFunc(("\n")); + + /* + * Read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "DefaultFrameRateKHz|Ports", ""); + + /* Frame rate option. */ + int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "DefaultFrameRateKHz", &pThisCC->uFrameRateDefault, EHCI_DEFAULT_TIMER_FREQ / 1000); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("EHCI configuration error: failed to read DefaultFrameRateKHz as integer")); + + if ( pThisCC->uFrameRateDefault > EHCI_HARDWARE_TIMER_FREQ / 1000 + || pThisCC->uFrameRateDefault == 0) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("EHCI configuration error: DefaultFrameRateKHz must be in range [%u,%u]"), + 1, EHCI_HARDWARE_TIMER_FREQ / 1000); + + /* Convert to Hertz. */ + pThisCC->uFrameRateDefault *= 1000; + + /* Number of ports option. */ + uint32_t cPorts; + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, EHCI_NDP_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("EHCI configuration error: failed to read Ports as integer")); + + if (cPorts == 0 || cPorts > EHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("EHCI configuration error: Ports must be in range [%u,%u]"), + 1, EHCI_NDP_MAX); + + /* + * Init instance data. + */ + pThisCC->pDevIns = pDevIns; + + /* Intel 82801FB/FBM USB2 controller */ + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); + + PDMPciDevSetVendorId(pPciDev, 0x8086); + PDMPciDevSetDeviceId(pPciDev, 0x265C); + PDMPciDevSetClassProg(pPciDev, 0x20); /* EHCI */ + PDMPciDevSetClassSub(pPciDev, 0x03); + PDMPciDevSetClassBase(pPciDev, 0x0c); + PDMPciDevSetInterruptPin(pPciDev, 0x01); +#ifdef VBOX_WITH_MSI_DEVICES + PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST); + PDMPciDevSetCapabilityList(pPciDev, 0x80); +#endif + PDMPciDevSetByte(pPciDev, 0x60, 0x20); /* serial bus release number register; 0x20 = USB 2.0 */ + /** @todo USBLEGSUP & USBLEGCTLSTS? Legacy interface for the BIOS (0xEECP+0 & 0xEECP+4) */ + + pThisCC->RootHub.IBase.pfnQueryInterface = ehciR3RhQueryInterface; + pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ehciR3RhGetAvailablePorts; + pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ehciR3RhGetUSBVersions; + pThisCC->RootHub.IRhPort.pfnAttach = ehciR3RhAttach; + pThisCC->RootHub.IRhPort.pfnDetach = ehciR3RhDetach; + pThisCC->RootHub.IRhPort.pfnReset = ehciR3RhReset; + pThisCC->RootHub.IRhPort.pfnXferCompletion = ehciR3RhXferCompletion; + pThisCC->RootHub.IRhPort.pfnXferError = ehciR3RhXferError; + + /* USB LED */ + pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC; + pThisCC->RootHub.ILeds.pfnQueryStatusLed = ehciR3RhQueryStatusLed; + + /* + * Register PCI device and I/O region. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x80; + MsiReg.iMsiNextOffset = 0x00; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + PDMPciDevSetCapabilityList(pPciDev, 0x0); + /* That's OK, we can work without MSI */ + } +#endif + + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0 /*iPciRegion*/, 4096 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM, + ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE, + "USB EHCI", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* Initialize capability registers */ + pThis->cap_length = EHCI_CAPS_REG_SIZE; + pThis->hci_version = 0x100; + /* 31:24 Reserved + * 23:20 Debug Port Number + * 19:17 Reserved + * 16 Port indicators (P_INDICATOR) enabled/disabled + * 15:12 Number of companion controllers (N_CC) + * 11:8 Number of ports per companion controller (N_PCC) + * 7 Port routing controls enabled/disabled + * 6:5 Reserved + * 4 Port power control enabled/disabled -> disabled to simplify matters! + * 3:0 N_PORTS; number of ports + */ + /* Currently only number of ports specified */ + pThis->hcs_params = cPorts; + + /* 31:16 Reserved + * 15:8 EHCI extended capabilities pointer (EECP) (0x40 or greater) + * 7:4 Isochronous scheduling threshold + * 3 Reserved + * 2 Asynchronous schedule park capability (allow several TDs to be handled per async queue head) + * 1 Programmable frame list flag (0=1024 frames fixed) + * 0 64 bits addressability + */ + pThis->hcc_params = EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING | EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING; + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, EHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + NULL, ehciR3SaveExec, NULL, + NULL, ehciLoadExec, NULL); + if (RT_FAILURE(rc)) + return rc; + + /* + * Attach to the VBox USB RootHub Driver on LUN #0. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->RootHub.IBase, &pThisCC->RootHub.pIBase, "RootHub"); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n")); + return rc; + } + pThisCC->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIROOTHUBCONNECTOR); + AssertMsgReturn(pThisCC->RootHub.pIRhConn, + ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), + VERR_PDM_MISSING_INTERFACE); + + /* + * Attach status driver (optional). + */ + PPDMIBASE pBase; + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->RootHub.IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThisCC->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else + AssertLogRelMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc), rc); + + /* Set URB parameters. */ + rc = VUSBIRhSetUrbParams(pThisCC->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT)); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to set URB parameters")); + + /* + * Calculate the timer intervals. + * This ASSUMES that the VM timer doesn't change frequency during the run. + */ + pThisCC->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns); + ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault); + LogFunc(("cTicksPerFrame=%RU64 cTicksPerUsbTick=%RU64\n", pThisCC->cTicksPerFrame, pThisCC->cTicksPerUsbTick)); + + pThis->fBusStarted = false; + + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "EHCI#%uIrq", iInstance); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("EHCI: Failed to create critical section")); + + rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrame); + AssertRCReturn(rc, rc); + + rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrameStopped); + AssertRCReturn(rc, rc); + + rc = RTCritSectInit(&pThisCC->CritSect); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create critical section")); + + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->hThreadFrame, pThisCC, ehciR3ThreadFrame, + ehciR3ThreadFrameWakeup, 0, RTTHREADTYPE_IO, "EhciFramer"); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create worker thread")); + + /* + * Do a hardware reset. + */ + ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, false /* don't reset devices */); + +#ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled)."); +#endif + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "ehci", "EHCI control registers.", ehciR3InfoRegs); + +#ifdef DEBUG_sandervl +// g_fLogInterruptEPs = true; + g_fLogControlEPs = true; +#endif + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) ehciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +const PDMDEVREG g_DeviceEHCI = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "usb-ehci", + /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE, + /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB, + /* .cMaxInstances = */ ~0U, + /* .uSharedVersion = */ 42, + /* .cbInstanceShared = */ sizeof(EHCI), + /* .cbInstanceCC = */ sizeof(EHCICC), + /* .cbInstanceRC = */ sizeof(EHCIRC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "EHCI USB controller.\n", +#if defined(IN_RING3) +# ifdef VBOX_IN_EXTPACK + /* .pszRCMod = */ "VBoxEhciRC.rc", + /* .pszR0Mod = */ "VBoxEhciR0.r0", +# else + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", +# endif + /* .pfnConstruct = */ ehciR3Construct, + /* .pfnDestruct = */ ehciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ ehciR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ ehciR3Resume, + /* .pfnAttach = */ NULL, + /* .pfnDetach = */ NULL, + /* .pfnQueryInterface = */ NULL, + /* .pfnInitComplete = */ NULL, + /* .pfnPowerOff = */ NULL, + /* .pfnSoftReset = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RING0) + /* .pfnEarlyConstruct = */ NULL, + /* .pfnConstruct = */ ehciRZConstruct, + /* .pfnDestruct = */ NULL, + /* .pfnFinalDestruct = */ NULL, + /* .pfnRequest = */ NULL, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#elif defined(IN_RC) + /* .pfnConstruct = */ ehciRZConstruct, + /* .pfnReserved0 = */ NULL, + /* .pfnReserved1 = */ NULL, + /* .pfnReserved2 = */ NULL, + /* .pfnReserved3 = */ NULL, + /* .pfnReserved4 = */ NULL, + /* .pfnReserved5 = */ NULL, + /* .pfnReserved6 = */ NULL, + /* .pfnReserved7 = */ NULL, +#else +# error "Not in IN_RING3, IN_RING0 or IN_RC!" +#endif + /* .u32VersionEnd = */ PDM_DEVREG_VERSION +}; + +#ifdef VBOX_IN_EXTPACK +extern "C" const PDMDEVREG g_DeviceXHCI; + +# ifdef VBOX_IN_EXTPACK_R3 + +/** + * @callback_method_impl{FNPDMVBOXDEVICESREGISTER} + */ +extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version) +{ + AssertLogRelMsgReturn(u32Version >= VBOX_VERSION, + ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION), + VERR_EXTPACK_VBOX_VERSION_MISMATCH); + AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION, + ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION), + VERR_VERSION_MISMATCH); + + int rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceEHCI); + + /* EHCI and xHCI devices live in the same module. */ + extern const PDMDEVREG g_DeviceXHCI; + if (RT_SUCCESS(rc)) + rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceXHCI); + + return rc; +} + +# else + +/** Pointer to the ring-0 device registrations for VBoxEhciR0/RC. */ +static PCPDMDEVREGR0 g_apDevRegs[] = +{ + &g_DeviceEHCI, + &g_DeviceXHCI, +}; + +/** Module device registration record for VBoxEhciR0/RC. */ +static PDMDEVMODREGR0 g_ModDevReg = +{ + /* .u32Version = */ PDM_DEVMODREGR0_VERSION, + /* .cDevRegs = */ RT_ELEMENTS(g_apDevRegs), + /* .papDevRegs = */ &g_apDevRegs[0], + /* .hMod = */ NULL, + /* .ListEntry = */ { NULL, NULL }, +}; + + +DECLEXPORT(int) ModuleInit(void *hMod) +{ + LogFlow(("VBoxEhciRZ/ModuleInit: %p\n", hMod)); + return PDMR0DeviceRegisterModule(hMod, &g_ModDevReg); +} + + +DECLEXPORT(void) ModuleTerm(void *hMod) +{ + LogFlow(("VBoxEhciRZ/ModuleTerm: %p\n", hMod)); + PDMR0DeviceDeregisterModule(hMod, &g_ModDevReg); +} + +# endif +#endif /* VBOX_IN_EXTPACK */ + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ + diff --git a/src/VBox/Devices/USB/DevOHCI.cpp b/src/VBox/Devices/USB/DevOHCI.cpp new file mode 100644 index 00000000..aa4e2357 --- /dev/null +++ b/src/VBox/Devices/USB/DevOHCI.cpp @@ -0,0 +1,6087 @@ +/* $Id: DevOHCI.cpp $ */ +/** @file + * DevOHCI - Open Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_ohci OHCI - Open Host Controller Interface Emulation. + * + * This component implements an OHCI USB controller. It is split roughly in + * to two main parts, the first part implements the register level + * specification of USB OHCI and the second part maintains the root hub (which + * is an integrated component of the device). + * + * The OHCI 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. Whenever the bus is enabled ohci->eof_timer will be set. + * + * 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 HcControlHeadED and HcBulkHeadED registers + * respectively. Interrupt ED's are different, they are found by looking + * in the HCCA (another communication area in main memory). + * + * At the start of every frame (in function ohci_sof) 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 so this would + * artificially limit the performance. + * + * Once we have a transfer ready to go (in function ohciR3ServiceTd) 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 (vusb.c) 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 + * ohci_frame_boundary). 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 (ohciR3RhXferCompletion) 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 in to + * the host memory. + * + * As for error handling OHCI 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 OHCI 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. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_OHCI +#include <VBox/pci.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/mm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/AssertGuest.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/asm-math.h> +#include <iprt/semaphore.h> +#include <iprt/critsect.h> +#include <iprt/param.h> +#ifdef IN_RING3 +# include <iprt/alloca.h> +# include <iprt/mem.h> +# include <iprt/thread.h> +# include <iprt/uuid.h> +#endif +#include <VBox/vusb.h> +#include "VBoxDD.h" + + +#define VBOX_WITH_OHCI_PHYS_READ_CACHE +//#define VBOX_WITH_OHCI_PHYS_READ_STATS + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The current saved state version. */ +#define OHCI_SAVED_STATE_VERSION OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER +/** The current saved state version. + * @since 6.1.0beta3/rc1 */ +#define OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER 6 +/** The current saved with the start-of-frame timer. + * @since 4.3.x */ +#define OHCI_SAVED_STATE_VERSION_EOF_TIMER 5 +/** The saved state with support of up to 8 ports. + * @since 3.1 or so */ +#define OHCI_SAVED_STATE_VERSION_8PORTS 4 + + +/** Maximum supported number of Downstream Ports on the root hub. 15 ports + * is the maximum defined by the OHCI spec. Must match the number of status + * register words to the 'opreg' array. + */ +#define OHCI_NDP_MAX 15 + +/** Default NDP, chosen to be compatible with everything. */ +#define OHCI_NDP_DEFAULT 12 + +/* Macro to query the number of currently configured ports. */ +#define OHCI_NDP_CFG(pohci) ((pohci)->RootHub.desc_a & OHCI_RHA_NDP) +/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */ +#define OHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1) + +/** Pointer to OHCI device data. */ +typedef struct OHCI *POHCI; +/** Read-only pointer to the OHCI device data. */ +typedef struct OHCI const *PCOHCI; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * Host controller transfer descriptor data. + */ +typedef struct VUSBURBHCITDINT +{ + /** Type of TD. */ + uint32_t TdType; + /** The address of the */ + RTGCPHYS32 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. */ + RTGCPHYS32 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 OHCI root hub port. + */ +typedef struct OHCIHUBPORT +{ + /** The port register. */ + uint32_t fReg; + /** Flag whether there is a device attached to the port. */ + bool fAttached; + bool afPadding[3]; +} OHCIHUBPORT; +/** Pointer to an OHCI hub port. */ +typedef OHCIHUBPORT *POHCIHUBPORT; + +/** + * The OHCI root hub, shared. + */ +typedef struct OHCIROOTHUB +{ + uint32_t status; + uint32_t desc_a; + uint32_t desc_b; +#if HC_ARCH_BITS == 64 + uint32_t Alignment0; /**< Align aPorts on a 8 byte boundary. */ +#endif + OHCIHUBPORT aPorts[OHCI_NDP_MAX]; +} OHCIROOTHUB; +/** Pointer to the OHCI root hub. */ +typedef OHCIROOTHUB *POHCIROOTHUB; + + +/** + * The OHCI root hub, ring-3 data. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + * @implements PDMILEDPORTS + */ +typedef struct OHCIROOTHUBR3 +{ + /** 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; + + OHCIHUBPORT aPorts[OHCI_NDP_MAX]; + R3PTRTYPE(POHCI) pOhci; +} OHCIROOTHUBR3; +/** Pointer to the OHCI ring-3 root hub data. */ +typedef OHCIROOTHUBR3 *POHCIROOTHUBR3; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +typedef struct OHCIPAGECACHE +{ + /** Last read physical page address. */ + RTGCPHYS GCPhysReadCacheAddr; + /** Copy of last read physical page. */ + uint8_t abPhysReadCache[GUEST_PAGE_SIZE]; +} OHCIPAGECACHE; +typedef OHCIPAGECACHE *POHCIPAGECACHE; +#endif + +/** + * OHCI device data, shared. + */ +typedef struct OHCI +{ + /** Start of current frame. */ + uint64_t SofTime; + /** done queue interrupt counter */ + uint32_t dqic : 3; + /** frame number overflow. */ + uint32_t fno : 1; + + /** Align roothub structure on a 8-byte boundary. */ + uint32_t u32Alignment0; + /** Root hub device, shared data. */ + OHCIROOTHUB RootHub; + + /* OHCI registers */ + + /** @name Control partition + * @{ */ + /** HcControl. */ + uint32_t ctl; + /** HcCommandStatus. */ + uint32_t status; + /** HcInterruptStatus. */ + uint32_t intr_status; + /** HcInterruptEnabled. */ + uint32_t intr; + /** @} */ + + /** @name Memory pointer partition + * @{ */ + /** HcHCCA. */ + uint32_t hcca; + /** HcPeriodCurrentEd. */ + uint32_t per_cur; + /** HcControlCurrentED. */ + uint32_t ctrl_cur; + /** HcControlHeadED. */ + uint32_t ctrl_head; + /** HcBlockCurrendED. */ + uint32_t bulk_cur; + /** HcBlockHeadED. */ + uint32_t bulk_head; + /** HcDoneHead. */ + uint32_t done; + /** @} */ + + /** @name Frame counter partition + * @{ */ + /** HcFmInterval.FSMPS - FSLargestDataPacket */ + uint32_t fsmps : 15; + /** HcFmInterval.FIT - FrameItervalToggle */ + uint32_t fit : 1; + /** HcFmInterval.FI - FrameInterval */ + uint32_t fi : 14; + /** HcFmRemaining.FRT - toggle bit. */ + uint32_t frt : 1; + /** HcFmNumber. + * @remark The register size is 16-bit, but for debugging and performance + * reasons we maintain a 32-bit counter. */ + uint32_t HcFmNumber; + /** HcPeriodicStart */ + uint32_t pstart; + /** @} */ + + /** This member and all the following are not part of saved state. */ + uint64_t SavedStateEnd; + + /** The number of virtual time ticks per frame. */ + uint64_t cTicksPerFrame; + /** The number of virtual time ticks per USB bus tick. */ + uint64_t cTicksPerUsbTick; + + /** Detected canceled isochronous URBs. */ + STAMCOUNTER StatCanceledIsocUrbs; + /** Detected canceled general URBs. */ + STAMCOUNTER StatCanceledGenUrbs; + /** Dropped URBs (endpoint halted, or URB canceled). */ + STAMCOUNTER StatDroppedUrbs; + + /** VM timer frequency used for frame timer calculations. */ + uint64_t u64TimerHz; + /** Idle detection flag; must be cleared at start of frame */ + bool fIdle; + /** A flag indicating that the bulk list may have in-flight URBs. */ + bool fBulkNeedsCleaning; + + bool afAlignment3[2]; + uint32_t Alignment4; /**< Align size on a 8 byte boundary. */ + + /** Critical section synchronising interrupt handling. */ + PDMCRITSECT CsIrq; + + /** The MMIO region handle. */ + IOMMMIOHANDLE hMmio; +} OHCI; + + +/** + * OHCI device data, ring-3. + */ +typedef struct OHCIR3 +{ + /** The root hub, ring-3 portion. */ + OHCIROOTHUBR3 RootHub; + /** Pointer to the device instance - R3 ptr. */ + PPDMDEVINSR3 pDevInsR3; + + /** Number of in-flight TDs. */ + unsigned cInFlight; + unsigned Alignment0; /**< Align aInFlight on a 8 byte boundary. */ + /** Array of in-flight TDs. */ + struct ohci_td_in_flight + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + /** Flag indicating an inactive (not-linked) URB. */ + bool fInactive; + /** Pointer to the URB. */ + R3PTRTYPE(PVUSBURB) pUrb; + } aInFlight[257]; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment1; +#endif + + /** Number of in-done-queue TDs. */ + unsigned cInDoneQueue; + /** Array of in-done-queue TDs. */ + struct ohci_td_in_done_queue + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + } aInDoneQueue[64]; + /** When the tail of the done queue was added. + * Used to calculate the age of the done queue. */ + uint32_t u32FmDoneQueueTail; +#if R3_ARCH_BITS == 32 + /** Align pLoad, the stats and the struct size correctly. */ + uint32_t Alignment2; +#endif + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /** Last read physical page for caching ED reads in the framer thread. */ + OHCIPAGECACHE CacheED; + /** Last read physical page for caching TD reads in the framer thread. */ + OHCIPAGECACHE CacheTD; +#endif + + /** Critical section to synchronize the framer and URB completion handler. */ + RTCRITSECT CritSect; + + /** The restored periodic frame rate. */ + uint32_t uRestoredPeriodicFrameRate; +} OHCIR3; +/** Pointer to ring-3 OHCI state. */ +typedef OHCIR3 *POHCIR3; + +/** + * OHCI device data, ring-0. + */ +typedef struct OHCIR0 +{ + uint32_t uUnused; +} OHCIR0; +/** Pointer to ring-0 OHCI state. */ +typedef OHCIR0 *POHCIR0; + + +/** + * OHCI device data, raw-mode. + */ +typedef struct OHCIRC +{ + uint32_t uUnused; +} OHCIRC; +/** Pointer to raw-mode OHCI state. */ +typedef OHCIRC *POHCIRC; + + +/** @typedef OHCICC + * The instance data for the current context. */ +typedef CTX_SUFF(OHCI) OHCICC; +/** @typedef POHCICC + * Pointer to the instance data for the current context. */ +typedef CTX_SUFF(POHCI) POHCICC; + + +/** Standard OHCI bus speed */ +#define OHCI_DEFAULT_TIMER_FREQ 1000 + +/** Host Controller Communications Area + * @{ */ +#define OHCI_HCCA_NUM_INTR 32 +#define OHCI_HCCA_OFS (OHCI_HCCA_NUM_INTR * sizeof(uint32_t)) +typedef struct OCHIHCCA +{ + uint16_t frame; + uint16_t pad; + uint32_t done; +} OCHIHCCA; +AssertCompileSize(OCHIHCCA, 8); +/** @} */ + +/** @name OHCI Endpoint Descriptor + * @{ */ + +#define ED_PTR_MASK (~(uint32_t)0xf) +#define ED_HWINFO_MPS 0x07ff0000 +#define ED_HWINFO_ISO RT_BIT(15) +#define ED_HWINFO_SKIP RT_BIT(14) +#define ED_HWINFO_LOWSPEED RT_BIT(13) +#define ED_HWINFO_IN RT_BIT(12) +#define ED_HWINFO_OUT RT_BIT(11) +#define ED_HWINFO_DIR (RT_BIT(11) | RT_BIT(12)) +#define ED_HWINFO_ENDPOINT 0x780 /* 4 bits */ +#define ED_HWINFO_ENDPOINT_SHIFT 7 +#define ED_HWINFO_FUNCTION 0x7f /* 7 bits */ +#define ED_HEAD_CARRY RT_BIT(1) +#define ED_HEAD_HALTED RT_BIT(0) + +/** + * OHCI Endpoint Descriptor. + */ +typedef struct OHCIED +{ + /** Flags and stuff. */ + uint32_t hwinfo; + /** TailP - TD Queue Tail pointer. Bits 0-3 ignored / preserved. */ + uint32_t TailP; + /** HeadP - TD Queue head pointer. Bit 0 - Halted, Bit 1 - toggleCarry. Bit 2&3 - 0. */ + uint32_t HeadP; + /** NextED - Next Endpoint Descriptor. Bits 0-3 ignored / preserved. */ + uint32_t NextED; +} OHCIED, *POHCIED; +typedef const OHCIED *PCOHCIED; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name Completion Codes + * @{ */ +#define OHCI_CC_NO_ERROR (UINT32_C(0x00) << 28) +#define OHCI_CC_CRC (UINT32_C(0x01) << 28) +#define OHCI_CC_STALL (UINT32_C(0x04) << 28) +#define OHCI_CC_DEVICE_NOT_RESPONDING (UINT32_C(0x05) << 28) +#define OHCI_CC_DNR OHCI_CC_DEVICE_NOT_RESPONDING +#define OHCI_CC_PID_CHECK_FAILURE (UINT32_C(0x06) << 28) +#define OHCI_CC_UNEXPECTED_PID (UINT32_C(0x07) << 28) +#define OHCI_CC_DATA_OVERRUN (UINT32_C(0x08) << 28) +#define OHCI_CC_DATA_UNDERRUN (UINT32_C(0x09) << 28) +/* 0x0a..0x0b - reserved */ +#define OHCI_CC_BUFFER_OVERRUN (UINT32_C(0x0c) << 28) +#define OHCI_CC_BUFFER_UNDERRUN (UINT32_C(0x0d) << 28) +#define OHCI_CC_NOT_ACCESSED_0 (UINT32_C(0x0e) << 28) +#define OHCI_CC_NOT_ACCESSED_1 (UINT32_C(0x0f) << 28) +/** @} */ + + +/** @name OHCI General transfer descriptor + * @{ */ + +/** Error count (EC) shift. */ +#define TD_ERRORS_SHIFT 26 +/** Error count max. (One greater than what the EC field can hold.) */ +#define TD_ERRORS_MAX 4 + +/** CC - Condition code mask. */ +#define TD_HWINFO_CC (UINT32_C(0xf0000000)) +#define TD_HWINFO_CC_SHIFT 28 +/** EC - Error count. */ +#define TD_HWINFO_ERRORS (RT_BIT(26) | RT_BIT(27)) +/** T - Data toggle. */ +#define TD_HWINFO_TOGGLE (RT_BIT(24) | RT_BIT(25)) +#define TD_HWINFO_TOGGLE_HI (RT_BIT(25)) +#define TD_HWINFO_TOGGLE_LO (RT_BIT(24)) +/** DI - Delay interrupt. */ +#define TD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define TD_HWINFO_IN (RT_BIT(20)) +#define TD_HWINFO_OUT (RT_BIT(19)) +/** DP - Direction / PID. */ +#define TD_HWINFO_DIR (RT_BIT(19) | RT_BIT(20)) +/** R - Buffer rounding. */ +#define TD_HWINFO_ROUNDING (RT_BIT(18)) +/** Bits that are reserved / unknown. */ +#define TD_HWINFO_UNKNOWN_MASK (UINT32_C(0x0003ffff)) + +/** SETUP - to endpoint. */ +#define OHCI_TD_DIR_SETUP 0x0 +/** OUT - to endpoint. */ +#define OHCI_TD_DIR_OUT 0x1 +/** IN - from endpoint. */ +#define OHCI_TD_DIR_IN 0x2 +/** Reserved. */ +#define OHCI_TD_DIR_RESERVED 0x3 + +/** + * OHCI general transfer descriptor + */ +typedef struct OHCITD +{ + uint32_t hwinfo; + /** CBP - Current Buffer Pointer. (32-bit physical address) */ + uint32_t cbp; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t be; +} OHCITD, *POHCITD; +typedef const OHCITD *PCOHCITD; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name OHCI isochronous transfer descriptor. + * @{ */ +/** SF - Start frame number. */ +#define ITD_HWINFO_SF 0xffff +/** DI - Delay interrupt. (TD_HWINFO_DI) */ +#define ITD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define ITD_HWINFO_DI_SHIFT 21 +/** FC - Frame count. */ +#define ITD_HWINFO_FC (RT_BIT(24) | RT_BIT(25) | RT_BIT(26)) +#define ITD_HWINFO_FC_SHIFT 24 +/** CC - Condition code mask. (=TD_HWINFO_CC) */ +#define ITD_HWINFO_CC UINT32_C(0xf0000000) +#define ITD_HWINFO_CC_SHIFT 28 +/** The buffer page 0 mask (lower 12 bits are ignored). */ +#define ITD_BP0_MASK UINT32_C(0xfffff000) + +#define ITD_NUM_PSW 8 +/** OFFSET - offset of the package into the buffer page. + * (Only valid when CC set to Not Accessed.) + * + * Note that the top bit of the OFFSET field is overlapping with the + * first bit in the CC field. This is ok because both 0xf and 0xe are + * defined as "Not Accessed". + */ +#define ITD_PSW_OFFSET 0x1fff +/** SIZE field mask for IN bound transfers. + * (Only valid when CC isn't Not Accessed.)*/ +#define ITD_PSW_SIZE 0x07ff +/** CC field mask. + * USed to indicate the format of SIZE (Not Accessed -> OFFSET). */ +#define ITD_PSW_CC 0xf000 +#define ITD_PSW_CC_SHIFT 12 + +/** + * OHCI isochronous transfer descriptor. + */ +typedef struct OHCIITD +{ + uint32_t HwInfo; + /** BP0 - Buffer Page 0. The lower 12 bits are ignored. */ + uint32_t BP0; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t BE; + /** (OffsetN/)PSWN - package status word array (0..7). + * The format varies depending on whether the package has been completed or not. */ + uint16_t aPSW[ITD_NUM_PSW]; +} OHCIITD, *POHCIITD; +typedef const OHCIITD *PCOHCIITD; +/** @} */ +AssertCompileSize(OHCIITD, 32); + +/** + * OHCI register operator. + */ +typedef struct OHCIOPREG +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value); +} OHCIOPREG; + + +/* OHCI Local stuff */ +#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) /* Control/Bulk Service Ratio. */ +#define OHCI_CTL_PLE (1<<2) /* Periodic List Enable. */ +#define OHCI_CTL_IE (1<<3) /* Isochronous Enable. */ +#define OHCI_CTL_CLE (1<<4) /* Control List Enable. */ +#define OHCI_CTL_BLE (1<<5) /* Bulk List Enable. */ +#define OHCI_CTL_HCFS ((1<<6)|(1<<7)) /* Host Controller Functional State. */ +#define OHCI_USB_RESET 0x00 +#define OHCI_USB_RESUME 0x40 +#define OHCI_USB_OPERATIONAL 0x80 +#define OHCI_USB_SUSPEND 0xc0 +#define OHCI_CTL_IR (1<<8) /* Interrupt Routing (host/SMI). */ +#define OHCI_CTL_RWC (1<<9) /* Remote Wakeup Connected. */ +#define OHCI_CTL_RWE (1<<10) /* Remote Wakeup Enabled. */ + +#define OHCI_STATUS_HCR (1<<0) /* Host Controller Reset. */ +#define OHCI_STATUS_CLF (1<<1) /* Control List Filled. */ +#define OHCI_STATUS_BLF (1<<2) /* Bulk List Filled. */ +#define OHCI_STATUS_OCR (1<<3) /* Ownership Change Request. */ +#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) /* Scheduling Overrun Count. */ + +/** @name Interrupt Status and Enabled/Disabled Flags + * @{ */ +/** SO - Scheduling overrun. */ +#define OHCI_INTR_SCHEDULING_OVERRUN RT_BIT(0) +/** WDH - HcDoneHead writeback. */ +#define OHCI_INTR_WRITE_DONE_HEAD RT_BIT(1) +/** SF - Start of frame. */ +#define OHCI_INTR_START_OF_FRAME RT_BIT(2) +/** RD - Resume detect. */ +#define OHCI_INTR_RESUME_DETECT RT_BIT(3) +/** UE - Unrecoverable error. */ +#define OHCI_INTR_UNRECOVERABLE_ERROR RT_BIT(4) +/** FNO - Frame number overflow. */ +#define OHCI_INTR_FRAMENUMBER_OVERFLOW RT_BIT(5) +/** RHSC- Root hub status change. */ +#define OHCI_INTR_ROOT_HUB_STATUS_CHANGE RT_BIT(6) +/** OC - Ownership change. */ +#define OHCI_INTR_OWNERSHIP_CHANGE RT_BIT(30) +/** MIE - Master interrupt enable. */ +#define OHCI_INTR_MASTER_INTERRUPT_ENABLED RT_BIT(31) +/** @} */ + +#define OHCI_HCCA_SIZE 0x100 +#define OHCI_HCCA_MASK UINT32_C(0xffffff00) + +#define OHCI_FMI_FI UINT32_C(0x00003fff) /* Frame Interval. */ +#define OHCI_FMI_FSMPS UINT32_C(0x7fff0000) /* Full-Speed Max Packet Size. */ +#define OHCI_FMI_FSMPS_SHIFT 16 +#define OHCI_FMI_FIT UINT32_C(0x80000000) /* Frame Interval Toggle. */ +#define OHCI_FMI_FIT_SHIFT 31 + +#define OHCI_FR_FRT RT_BIT_32(31) /* Frame Remaining Toggle */ + +#define OHCI_LS_THRESH 0x628 /* Low-Speed Threshold. */ + +#define OHCI_RHA_NDP (0xff) /* Number of Downstream Ports. */ +#define OHCI_RHA_PSM RT_BIT_32(8) /* Power Switching Mode. */ +#define OHCI_RHA_NPS RT_BIT_32(9) /* No Power Switching. */ +#define OHCI_RHA_DT RT_BIT_32(10) /* Device Type. */ +#define OHCI_RHA_OCPM RT_BIT_32(11) /* Over-Current Protection Mode. */ +#define OHCI_RHA_NOCP RT_BIT_32(12) /* No Over-Current Protection. */ +#define OHCI_RHA_POTPGP UINT32_C(0xff000000) /* Power On To Power Good Time. */ + +#define OHCI_RHS_LPS RT_BIT_32(0) /* Local Power Status. */ +#define OHCI_RHS_OCI RT_BIT_32(1) /* Over-Current Indicator. */ +#define OHCI_RHS_DRWE RT_BIT_32(15) /* Device Remote Wakeup Enable. */ +#define OHCI_RHS_LPSC RT_BIT_32(16) /* Local Power Status Change. */ +#define OHCI_RHS_OCIC RT_BIT_32(17) /* Over-Current Indicator Change. */ +#define OHCI_RHS_CRWE RT_BIT_32(31) /* Clear Remote Wakeup Enable. */ + +/** @name HcRhPortStatus[n] - RH Port Status register (read). + * @{ */ +/** CCS - CurrentConnectionStatus - 0 = no device, 1 = device. */ +#define OHCI_PORT_CCS RT_BIT(0) +/** ClearPortEnable (when writing CCS). */ +#define OHCI_PORT_CLRPE OHCI_PORT_CCS +/** PES - PortEnableStatus. */ +#define OHCI_PORT_PES RT_BIT(1) +/** PSS - PortSuspendStatus */ +#define OHCI_PORT_PSS RT_BIT(2) +/** POCI- PortOverCurrentIndicator. */ +#define OHCI_PORT_POCI RT_BIT(3) +/** ClearSuspendStatus (when writing POCI). */ +#define OHCI_PORT_CLRSS OHCI_PORT_POCI +/** PRS - PortResetStatus */ +#define OHCI_PORT_PRS RT_BIT(4) +/** PPS - PortPowerStatus */ +#define OHCI_PORT_PPS RT_BIT(8) +/** LSDA - LowSpeedDeviceAttached */ +#define OHCI_PORT_LSDA RT_BIT(9) +/** ClearPortPower (when writing LSDA). */ +#define OHCI_PORT_CLRPP OHCI_PORT_LSDA +/** CSC - ConnectStatusChange */ +#define OHCI_PORT_CSC RT_BIT(16) +/** PESC - PortEnableStatusChange */ +#define OHCI_PORT_PESC RT_BIT(17) +/** PSSC - PortSuspendStatusChange */ +#define OHCI_PORT_PSSC RT_BIT(18) +/** OCIC - OverCurrentIndicatorChange */ +#define OHCI_PORT_OCIC RT_BIT(19) +/** PRSC - PortResetStatusChange */ +#define OHCI_PORT_PRSC RT_BIT(20) +/** The mask of RW1C bits. */ +#define OHCI_PORT_CLEAR_CHANGE_MASK (OHCI_PORT_CSC | OHCI_PORT_PESC | OHCI_PORT_PSSC | OHCI_PORT_OCIC | OHCI_PORT_PRSC) +/** @} */ + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS +/* + * Explain + */ +typedef struct OHCIDESCREADSTATS +{ + uint32_t cReads; + uint32_t cPageChange; + uint32_t cMinReadsPerPage; + uint32_t cMaxReadsPerPage; + + uint32_t cReadsLastPage; + uint32_t u32LastPageAddr; +} OHCIDESCREADSTATS; +typedef OHCIDESCREADSTATS *POHCIDESCREADSTATS; + +typedef struct OHCIPHYSREADSTATS +{ + OHCIDESCREADSTATS ed; + OHCIDESCREADSTATS td; + OHCIDESCREADSTATS all; + + uint32_t cCrossReads; + uint32_t cCacheReads; + uint32_t cPageReads; +} OHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS *POHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS const *PCOHCIPHYSREADSTATS; +#endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(VBOX_WITH_OHCI_PHYS_READ_STATS) && defined(IN_RING3) +static OHCIPHYSREADSTATS g_PhysReadState; +#endif + +#if defined(LOG_ENABLED) && defined(IN_RING3) +static bool g_fLogBulkEPs = false; +static bool g_fLogControlEPs = false; +static bool g_fLogInterruptEPs = false; +#endif +#ifdef IN_RING3 +/** + * SSM descriptor table for the OHCI structure. + */ +static SSMFIELD const g_aOhciFields[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[8].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[9].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[10].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[11].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[12].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[13].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[14].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the older 8-port OHCI structure. + */ +static SSMFIELD const g_aOhciFields8Ports[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +#ifdef IN_RING3 +/* Update host controller state to reflect a device attach */ +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp); +static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pOhci, POHCICC pThisCC, bool fHardware); +static void ohciR3BusStop(POHCICC pThisCC); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache); +#endif + +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); + +static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t GCPhysTD); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) +static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD); +# endif +#endif /* IN_RING3 */ +RT_C_DECLS_END + + +/** + * Update PCI IRQ levels + */ +static void ohciUpdateInterruptLocked(PPDMDEVINS pDevIns, POHCI ohci, const char *msg) +{ + int level = 0; + + if ( (ohci->intr & OHCI_INTR_MASTER_INTERRUPT_ENABLED) + && (ohci->intr_status & ohci->intr) + && !(ohci->ctl & OHCI_CTL_IR)) + level = 1; + + PDMDevHlpPCISetIrq(pDevIns, 0, level); + if (level) + { + uint32_t val = ohci->intr_status & ohci->intr; + Log2(("ohci: Fired off interrupt %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d - %s\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, msg)); NOREF(val); NOREF(msg); + } +} + +#ifdef IN_RING3 + +/** + * Set an interrupt, use the wrapper ohciSetInterrupt. + */ +DECLINLINE(int) ohciR3SetInterruptInt(PPDMDEVINS pDevIns, POHCI ohci, int rcBusy, uint32_t intr, const char *msg) +{ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ohci->CsIrq, rcBusy); + if (rc != VINF_SUCCESS) + return rc; + + if ( (ohci->intr_status & intr) != intr ) + { + ohci->intr_status |= intr; + ohciUpdateInterruptLocked(pDevIns, ohci, msg); + } + + PDMDevHlpCritSectLeave(pDevIns, &ohci->CsIrq); + return rc; +} + +/** + * Set an interrupt wrapper macro for logging purposes. + */ +# define ohciR3SetInterrupt(a_pDevIns, a_pOhci, a_fIntr) \ + ohciR3SetInterruptInt(a_pDevIns, a_pOhci, VERR_IGNORED, a_fIntr, #a_fIntr) + + +/** + * Sets the HC in the unrecoverable error state and raises the appropriate interrupt. + * + * @returns nothing. + * @param pDevIns The device instance. + * @param pThis The OHCI instance. + * @param iCode Diagnostic code. + */ +DECLINLINE(void) ohciR3RaiseUnrecoverableError(PPDMDEVINS pDevIns, POHCI pThis, int iCode) +{ + LogRelMax(10, ("OHCI#%d: Raising unrecoverable error (%d)\n", pDevIns->iInstance, iCode)); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_UNRECOVERABLE_ERROR); +} + + +/* Carry out a hardware remote wakeup */ +static void ohciR3RemoteWakeup(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + if ((pThis->ctl & OHCI_CTL_HCFS) != OHCI_USB_SUSPEND) + return; + if (!(pThis->RootHub.status & OHCI_RHS_DRWE)) + return; + ohciR3BusResume(pDevIns, pThis, pThisCC, true /* hardware */); +} + + +/** + * Query interface method for the roothub LUN. + */ +static DECLCALLBACK(void *) ohciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, 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) ohciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, RootHub.ILeds); + if (iLUN == 0) + { + *ppLed = &pThisCC->RootHub.Led; + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** Converts a OHCI.roothub.IRhPort pointer to a OHCICC one. */ +#define VUSBIROOTHUBPORT_2_OHCI(a_pInterface) RT_FROM_MEMBER(a_pInterface, OHCICC, RootHub.IRhPort) + +/** + * Get the number of available 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) ohciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + unsigned cPorts = 0; + + memset(pAvailable, 0, sizeof(*pAvailable)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + + for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) + if (!pThis->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) ohciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_11; +} + + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3RhAttach: 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 <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(!pThis->RootHub.aPorts[uPort].fAttached); + /* Only LS/FS devices should end up here. */ + Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL); + + /* + * Attach it. + */ + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + if (enmSpeed == VUSB_SPEED_LOW) + pThis->RootHub.aPorts[uPort].fReg |= OHCI_PORT_LSDA; + pThis->RootHub.aPorts[uPort].fAttached = true; + ohciR3RhPortPower(&pThisCC->RootHub, uPort, 1 /* power on */); + + ohciR3RemoteWakeup(pDevIns, pThis, pThisCC); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + 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) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3RhDetach: 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 <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(pThis->RootHub.aPorts[uPort].fAttached); + + /* + * Detach it. + */ + pThis->RootHub.aPorts[uPort].fAttached = false; + if (pThis->RootHub.aPorts[uPort].fReg & OHCI_PORT_PES) + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC | OHCI_PORT_PESC; + else + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC; + + ohciR3RemoteWakeup(pDevIns, pThis, pThisCC); + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + 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 of the device completing the reset. + * @param rc The result of the operation. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + LogRel(("OHCI: root hub reset completed with %Rrc\n", rc)); + RT_NOREF(pDev, uPort, rc, pvUser); +} + + +/** + * 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) ohciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + Log(("ohci: root hub reset%s\n", fResetOnLinux ? " (reset on linux)" : "")); + + pThis->RootHub.status = 0; + pThis->RootHub.desc_a = OHCI_RHA_NPS | OHCI_NDP_CFG(pThis); /* Preserve NDP value. */ + pThis->RootHub.desc_b = 0x0; /* Impl. specific */ + + /* + * We're pending to _reattach_ the device without resetting them. + * Except, during VM reset where we use the opportunity to do a proper + * reset before the guest comes along and expect things. + * + * However, it's very very likely that we're not doing the right thing + * here if coming 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 < OHCI_NDP_CFG(pThis); iPort++) + { + if (pThis->RootHub.aPorts[iPort].fAttached) + { + pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS; + if (fResetOnLinux) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux, + ohciR3RhResetDoneOneDev, pThis, pVM); + } + } + else + pThis->RootHub.aPorts[iPort].fReg = 0; + } + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * 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 ohci instance data. + * @param pThisCC The ohci instance data, current context. + * @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 ohciR3DoReset(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux) +{ + Log(("ohci: %s reset%s\n", fNewMode == OHCI_USB_RESET ? "hardware" : "software", + fResetOnLinux ? " (reset on linux)" : "")); + + /* Clear list enable bits first, so that any processing currently in progress terminates quickly. */ + pThis->ctl &= ~(OHCI_CTL_BLE | OHCI_CTL_CLE | OHCI_CTL_PLE); + + /* Stop the bus in any case, disabling walking the lists. */ + ohciR3BusStop(pThisCC); + + /* + * 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. + */ + pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn); + Assert(pThisCC->cInFlight == 0); + + /* + * Reset the hardware registers. + */ + if (fNewMode == OHCI_USB_RESET) + pThis->ctl = OHCI_CTL_RWC; /* We're the firmware, set RemoteWakeupConnected. */ + else + pThis->ctl &= OHCI_CTL_IR | OHCI_CTL_RWC; /* IR and RWC are preserved on software reset. */ + + /* Clear the HCFS bits first to make setting the new state work. */ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= fNewMode; + pThis->status = 0; + pThis->intr_status = 0; + pThis->intr = 0; + PDMDevHlpPCISetIrq(pDevIns, 0, 0); + + pThis->hcca = 0; + pThis->per_cur = 0; + pThis->ctrl_head = pThis->ctrl_cur = 0; + pThis->bulk_head = pThis->bulk_cur = 0; + pThis->done = 0; + + pThis->fsmps = 0x2778; /* To-Be-Defined, use the value linux sets...*/ + pThis->fit = 0; + pThis->fi = 11999; /* (12MHz ticks, one frame is 1ms) */ + pThis->frt = 0; + pThis->HcFmNumber = 0; + pThis->pstart = 0; + + pThis->dqic = 0x7; + pThis->fno = 0; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +#endif + + /* + * 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 == OHCI_USB_RESET) + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux); +} + + +/** + * Reads physical memory. + */ +DECLINLINE(void) ohciR3PhysRead(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Reads physical memory - metadata. + */ +DECLINLINE(void) ohciR3PhysReadMeta(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Writes physical memory. + */ +DECLINLINE(void) ohciR3PhysWrite(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Writes physical memory - metadata. + */ +DECLINLINE(void) ohciR3PhysWriteMeta(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf); +} + +/** + * Read an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ohciR3GetDWords(PPDMDEVINS pDevIns, uint32_t Addr, uint32_t *pau32s, int c32s) +{ + ohciR3PhysReadMeta(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) ohciR3PutDWords(PPDMDEVINS pDevIns, uint32_t Addr, const uint32_t *pau32s, int cu32s) +{ +# ifdef RT_LITTLE_ENDIAN + ohciR3PhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2); +# else + for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s)) + { + uint32_t u32Tmp = RT_H2LE_U32(*pau32s); + ohciR3PhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp)); + } +# endif +} + + + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + +static void descReadStatsReset(POHCIDESCREADSTATS p) +{ + p->cReads = 0; + p->cPageChange = 0; + p->cMinReadsPerPage = UINT32_MAX; + p->cMaxReadsPerPage = 0; + + p->cReadsLastPage = 0; + p->u32LastPageAddr = 0; +} + +static void physReadStatsReset(POHCIPHYSREADSTATS p) +{ + descReadStatsReset(&p->ed); + descReadStatsReset(&p->td); + descReadStatsReset(&p->all); + + p->cCrossReads = 0; + p->cCacheReads = 0; + p->cPageReads = 0; +} + +static void physReadStatsUpdateDesc(POHCIDESCREADSTATS p, uint32_t u32Addr) +{ + const uint32_t u32PageAddr = u32Addr & ~UINT32_C(0xFFF); + + ++p->cReads; + + if (p->u32LastPageAddr == 0) + { + /* First call. */ + ++p->cReadsLastPage; + p->u32LastPageAddr = u32PageAddr; + } + else if (u32PageAddr != p->u32LastPageAddr) + { + /* New page. */ + ++p->cPageChange; + + p->cMinReadsPerPage = RT_MIN(p->cMinReadsPerPage, p->cReadsLastPage); + p->cMaxReadsPerPage = RT_MAX(p->cMaxReadsPerPage, p->cReadsLastPage);; + + p->cReadsLastPage = 1; + p->u32LastPageAddr = u32PageAddr; + } + else + { + /* Read on the same page. */ + ++p->cReadsLastPage; + } +} + +static void physReadStatsPrint(POHCIPHYSREADSTATS p) +{ + p->ed.cMinReadsPerPage = RT_MIN(p->ed.cMinReadsPerPage, p->ed.cReadsLastPage); + p->ed.cMaxReadsPerPage = RT_MAX(p->ed.cMaxReadsPerPage, p->ed.cReadsLastPage);; + + p->td.cMinReadsPerPage = RT_MIN(p->td.cMinReadsPerPage, p->td.cReadsLastPage); + p->td.cMaxReadsPerPage = RT_MAX(p->td.cMaxReadsPerPage, p->td.cReadsLastPage);; + + p->all.cMinReadsPerPage = RT_MIN(p->all.cMinReadsPerPage, p->all.cReadsLastPage); + p->all.cMaxReadsPerPage = RT_MAX(p->all.cMaxReadsPerPage, p->all.cReadsLastPage);; + + LogRel(("PHYSREAD:\n" + " ED: %d, %d, %d/%d\n" + " TD: %d, %d, %d/%d\n" + " ALL: %d, %d, %d/%d\n" + " C: %d, %d, %d\n" + "", + p->ed.cReads, p->ed.cPageChange, p->ed.cMinReadsPerPage, p->ed.cMaxReadsPerPage, + p->td.cReads, p->td.cPageChange, p->td.cMinReadsPerPage, p->td.cMaxReadsPerPage, + p->all.cReads, p->all.cPageChange, p->all.cMinReadsPerPage, p->all.cMaxReadsPerPage, + p->cCrossReads, p->cCacheReads, p->cPageReads + )); + + physReadStatsReset(p); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + +static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache) +{ + pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS; +} + +static void ohciR3PhysReadCacheRead(PPDMDEVINS pDevIns, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS PageAddr = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + + if (PageAddr == ((GCPhys + cbBuf) & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK)) + { + if (PageAddr != pPageCache->GCPhysReadCacheAddr) + { + PDMDevHlpPCIPhysRead(pDevIns, PageAddr, pPageCache->abPhysReadCache, sizeof(pPageCache->abPhysReadCache)); + pPageCache->GCPhysReadCacheAddr = PageAddr; +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cPageReads; +# endif + } + + memcpy(pvBuf, &pPageCache->abPhysReadCache[GCPhys & GUEST_PAGE_OFFSET_MASK], cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCacheReads; +# endif + } + else + { + PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvBuf, cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCrossReads; +# endif + } +} + + +/** + * Updates the data in the given page cache if the given guest physical address is currently contained + * in the cache. + * + * @returns nothing. + * @param pPageCache The page cache to update. + * @param GCPhys The guest physical address needing the update. + * @param pvBuf Pointer to the buffer to update the page cache with. + * @param cbBuf Number of bytes to update. + */ +static void ohciR3PhysCacheUpdate(POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, const void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS GCPhysPage = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + + if (GCPhysPage == pPageCache->GCPhysReadCacheAddr) + { + uint32_t offPage = GCPhys & GUEST_PAGE_OFFSET_MASK; + memcpy(&pPageCache->abPhysReadCache[offPage], pvBuf, RT_MIN(GUEST_PAGE_SIZE - offPage, cbBuf)); + } +} + +/** + * Update any cached ED data with the given endpoint descriptor at the given address. + * + * @returns nothing. + * @param pThisCC The OHCI instance data for the current context. + * @param EdAddr Endpoint descriptor address. + * @param pEd The endpoint descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheEdUpdate(POHCICC pThisCC, RTGCPHYS32 EdAddr, PCOHCIED pEd) +{ + ohciR3PhysCacheUpdate(&pThisCC->CacheED, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, sizeof(uint32_t)); +} + + +/** + * Update any cached TD data with the given transfer descriptor at the given address. + * + * @returns nothing. + * @param pThisCC The OHCI instance data, current context. + * @param TdAddr Transfer descriptor address. + * @param pTd The transfer descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheTdUpdate(POHCICC pThisCC, RTGCPHYS32 TdAddr, PCOHCITD pTd) +{ + ohciR3PhysCacheUpdate(&pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd)); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */ + +/** + * Reads an OHCIED. + */ +DECLINLINE(void) ohciR3ReadEd(PPDMDEVINS pDevIns, uint32_t EdAddr, POHCIED pEd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.ed, EdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, EdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheED, EdAddr, pEd, sizeof(*pEd)); +#else + ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2); +#endif +} + +/** + * Reads an OHCITD. + */ +DECLINLINE(void) ohciR3ReadTd(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.td, TdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, TdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd)); +#else + ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#endif +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + uint32_t hichg; + hichg = pTd->hwinfo; + Log3(("ohciR3ReadTd(,%#010x,): R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x UNK=%#x\n", + TdAddr, + (pTd->hwinfo >> 18) & 1, + (pTd->hwinfo >> 19) & 3, + (pTd->hwinfo >> 21) & 7, + (pTd->hwinfo >> 24) & 3, + (pTd->hwinfo >> 26) & 3, + (pTd->hwinfo >> 28) &15, + pTd->cbp, + pTd->NextTD, + pTd->be, + pTd->hwinfo & TD_HWINFO_UNKNOWN_MASK)); +# if 0 + if (LogIs3Enabled()) + { + /* + * usbohci.sys (32-bit XP) allocates 0x80 bytes per TD: + * 0x00-0x0f is the OHCI TD. + * 0x10-0x1f for isochronous TDs + * 0x20 is the physical address of this TD. + * 0x24 is initialized with 0x64745948, probably a magic. + * 0x28 is some kind of flags. the first bit begin the allocated / not allocated indicator. + * 0x30 is a pointer to something. endpoint? interface? device? + * 0x38 is initialized to 0xdeadface. but is changed into a pointer or something. + * 0x40 looks like a pointer. + * The rest is unknown and initialized with zeros. + */ + uint8_t abXpTd[0x80]; + ohciR3PhysRead(pDevIns, TdAddr, abXpTd, sizeof(abXpTd)); + Log3(("WinXpTd: alloc=%d PhysSelf=%RX32 s2=%RX32 magic=%RX32 s4=%RX32 s5=%RX32\n" + "%.*Rhxd\n", + abXpTd[28] & RT_BIT(0), + *((uint32_t *)&abXpTd[0x20]), *((uint32_t *)&abXpTd[0x30]), + *((uint32_t *)&abXpTd[0x24]), *((uint32_t *)&abXpTd[0x38]), + *((uint32_t *)&abXpTd[0x40]), + sizeof(abXpTd), &abXpTd[0])); + } +# endif + } +# endif +} + +/** + * Reads an OHCIITD. + */ +DECLINLINE(void) ohciR3ReadITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, POHCIITD pITd) +{ + ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + Log3(("ohciR3ReadITd(,%#010x,): SF=%#06x (%#RX32) DI=%#x FC=%d CC=%#x BP0=%#010x NextTD=%#010x BE=%#010x\n", + ITdAddr, + pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (pITd->HwInfo >> 21) & 7, + (pITd->HwInfo >> 24) & 7, + (pITd->HwInfo >> 28) &15, + pITd->BP0, + pITd->NextTD, + pITd->BE)); + Log3(("psw0=%x:%03x psw1=%x:%03x psw2=%x:%03x psw3=%x:%03x psw4=%x:%03x psw5=%x:%03x psw6=%x:%03x psw7=%x:%03x\n", + pITd->aPSW[0] >> 12, pITd->aPSW[0] & 0xfff, + pITd->aPSW[1] >> 12, pITd->aPSW[1] & 0xfff, + pITd->aPSW[2] >> 12, pITd->aPSW[2] & 0xfff, + pITd->aPSW[3] >> 12, pITd->aPSW[3] & 0xfff, + pITd->aPSW[4] >> 12, pITd->aPSW[4] & 0xfff, + pITd->aPSW[5] >> 12, pITd->aPSW[5] & 0xfff, + pITd->aPSW[6] >> 12, pITd->aPSW[6] & 0xfff, + pITd->aPSW[7] >> 12, pITd->aPSW[7] & 0xfff)); + } +# else + RT_NOREF(pThis); +# endif +} + + +/** + * Writes an OHCIED. + */ +DECLINLINE(void) ohciR3WriteEd(PPDMDEVINS pDevIns, uint32_t EdAddr, PCOHCIED pEd) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIED EdOld; + uint32_t hichg; + + ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)&EdOld, sizeof(EdOld) >> 2); + hichg = EdOld.hwinfo ^ pEd->hwinfo; + Log3(("ohciR3WriteEd(,%#010x,): %sFA=%#x %sEN=%#x %sD=%#x %sS=%d %sK=%d %sF=%d %sMPS=%#x %sTailP=%#010x %sHeadP=%#010x %sH=%d %sC=%d %sNextED=%#010x\n", + EdAddr, + (hichg >> 0) & 0x7f ? "*" : "", (pEd->hwinfo >> 0) & 0x7f, + (hichg >> 7) & 0xf ? "*" : "", (pEd->hwinfo >> 7) & 0xf, + (hichg >> 11) & 3 ? "*" : "", (pEd->hwinfo >> 11) & 3, + (hichg >> 13) & 1 ? "*" : "", (pEd->hwinfo >> 13) & 1, + (hichg >> 14) & 1 ? "*" : "", (pEd->hwinfo >> 14) & 1, + (hichg >> 15) & 1 ? "*" : "", (pEd->hwinfo >> 15) & 1, + (hichg >> 24) &0x3ff ? "*" : "", (pEd->hwinfo >> 16) &0x3ff, + EdOld.TailP != pEd->TailP ? "*" : "", pEd->TailP, + (EdOld.HeadP & ~3) != (pEd->HeadP & ~3) ? "*" : "", pEd->HeadP & ~3, + (EdOld.HeadP ^ pEd->HeadP) & 1 ? "*" : "", pEd->HeadP & 1, + (EdOld.HeadP ^ pEd->HeadP) & 2 ? "*" : "", (pEd->HeadP >> 1) & 1, + EdOld.NextED != pEd->NextED ? "*" : "", pEd->NextED)); + } +# endif + + ohciR3PutDWords(pDevIns, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, 1); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3CacheEdUpdate(pThisCC, EdAddr, pEd); +#endif +} + + +/** + * Writes an OHCITD. + */ +DECLINLINE(void) ohciR3WriteTd(PPDMDEVINS pDevIns, uint32_t TdAddr, PCOHCITD pTd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCITD TdOld; + ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)&TdOld, sizeof(TdOld) >> 2); + uint32_t hichg = TdOld.hwinfo ^ pTd->hwinfo; + Log3(("ohciR3WriteTd(,%#010x,): %sR=%d %sDP=%d %sDI=%#x %sT=%d %sEC=%d %sCC=%#x %sCBP=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + TdAddr, + (hichg >> 18) & 1 ? "*" : "", (pTd->hwinfo >> 18) & 1, + (hichg >> 19) & 3 ? "*" : "", (pTd->hwinfo >> 19) & 3, + (hichg >> 21) & 7 ? "*" : "", (pTd->hwinfo >> 21) & 7, + (hichg >> 24) & 3 ? "*" : "", (pTd->hwinfo >> 24) & 3, + (hichg >> 26) & 3 ? "*" : "", (pTd->hwinfo >> 26) & 3, + (hichg >> 28) &15 ? "*" : "", (pTd->hwinfo >> 28) &15, + TdOld.cbp != pTd->cbp ? "*" : "", pTd->cbp, + TdOld.NextTD != pTd->NextTD ? "*" : "", pTd->NextTD, + TdOld.be != pTd->be ? "*" : "", pTd->be, + pszLogMsg)); + } +# else + RT_NOREF(pszLogMsg); +# endif + ohciR3PutDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + ohciR3CacheTdUpdate(pThisCC, TdAddr, pTd); +#endif +} + +/** + * Writes an OHCIITD. + */ +DECLINLINE(void) ohciR3WriteITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, PCOHCIITD pITd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIITD ITdOld; + ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)&ITdOld, sizeof(ITdOld) / sizeof(uint32_t)); + uint32_t HIChg = ITdOld.HwInfo ^ pITd->HwInfo; + Log3(("ohciR3WriteITd(,%#010x,): %sSF=%#x (now=%#RX32) %sDI=%#x %sFC=%d %sCC=%#x %sBP0=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + ITdAddr, + (HIChg & 0xffff) & 1 ? "*" : "", pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (HIChg >> 21) & 7 ? "*" : "", (pITd->HwInfo >> 21) & 7, + (HIChg >> 24) & 7 ? "*" : "", (pITd->HwInfo >> 24) & 7, + (HIChg >> 28) &15 ? "*" : "", (pITd->HwInfo >> 28) &15, + ITdOld.BP0 != pITd->BP0 ? "*" : "", pITd->BP0, + ITdOld.NextTD != pITd->NextTD ? "*" : "", pITd->NextTD, + ITdOld.BE != pITd->BE ? "*" : "", pITd->BE, + pszLogMsg)); + Log3(("psw0=%s%x:%s%03x psw1=%s%x:%s%03x psw2=%s%x:%s%03x psw3=%s%x:%s%03x psw4=%s%x:%s%03x psw5=%s%x:%s%03x psw6=%s%x:%s%03x psw7=%s%x:%s%03x\n", + (ITdOld.aPSW[0] >> 12) != (pITd->aPSW[0] >> 12) ? "*" : "", pITd->aPSW[0] >> 12, (ITdOld.aPSW[0] & 0xfff) != (pITd->aPSW[0] & 0xfff) ? "*" : "", pITd->aPSW[0] & 0xfff, + (ITdOld.aPSW[1] >> 12) != (pITd->aPSW[1] >> 12) ? "*" : "", pITd->aPSW[1] >> 12, (ITdOld.aPSW[1] & 0xfff) != (pITd->aPSW[1] & 0xfff) ? "*" : "", pITd->aPSW[1] & 0xfff, + (ITdOld.aPSW[2] >> 12) != (pITd->aPSW[2] >> 12) ? "*" : "", pITd->aPSW[2] >> 12, (ITdOld.aPSW[2] & 0xfff) != (pITd->aPSW[2] & 0xfff) ? "*" : "", pITd->aPSW[2] & 0xfff, + (ITdOld.aPSW[3] >> 12) != (pITd->aPSW[3] >> 12) ? "*" : "", pITd->aPSW[3] >> 12, (ITdOld.aPSW[3] & 0xfff) != (pITd->aPSW[3] & 0xfff) ? "*" : "", pITd->aPSW[3] & 0xfff, + (ITdOld.aPSW[4] >> 12) != (pITd->aPSW[4] >> 12) ? "*" : "", pITd->aPSW[4] >> 12, (ITdOld.aPSW[4] & 0xfff) != (pITd->aPSW[4] & 0xfff) ? "*" : "", pITd->aPSW[4] & 0xfff, + (ITdOld.aPSW[5] >> 12) != (pITd->aPSW[5] >> 12) ? "*" : "", pITd->aPSW[5] >> 12, (ITdOld.aPSW[5] & 0xfff) != (pITd->aPSW[5] & 0xfff) ? "*" : "", pITd->aPSW[5] & 0xfff, + (ITdOld.aPSW[6] >> 12) != (pITd->aPSW[6] >> 12) ? "*" : "", pITd->aPSW[6] >> 12, (ITdOld.aPSW[6] & 0xfff) != (pITd->aPSW[6] & 0xfff) ? "*" : "", pITd->aPSW[6] & 0xfff, + (ITdOld.aPSW[7] >> 12) != (pITd->aPSW[7] >> 12) ? "*" : "", pITd->aPSW[7] >> 12, (ITdOld.aPSW[7] & 0xfff) != (pITd->aPSW[7] & 0xfff) ? "*" : "", pITd->aPSW[7] & 0xfff)); + } +# else + RT_NOREF(pThis, pszLogMsg); +# endif + ohciR3PutDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +} + + +# ifdef LOG_ENABLED + +/** + * Core TD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + uint32_t GCPhys = GCPhysHead; + int cIterations = 128; + for (;;) + { + OHCITD Td; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&Td, sizeof(Td) >> 2); + if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else + Log4((" -> ")); + GCPhys = Td.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + if (!--cIterations) + break; + } +} + +/** + * Dumps a TD queue. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueue(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg) +{ + if (pszMsg) + Log4(("%s: ", pszMsg)); + ohciR3DumpTdQueueCore(pDevIns, pThisCC, GCPhysHead, 0, true); + Log4(("\n")); +} + +/** + * Core ITD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpITdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + RT_NOREF(fFull); + uint32_t GCPhys = GCPhysHead; + int cIterations = 100; + for (;;) + { + OHCIITD ITd; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&ITd, sizeof(ITd) / sizeof(uint32_t)); + /*if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else*/ + Log4((" -> ")); + GCPhys = ITd.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + if (!--cIterations) + break; + } +} + +/** + * Dumps a ED list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpEdList(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg, bool fTDs) +{ + RT_NOREF(fTDs); + uint32_t GCPhys = GCPhysHead; + if (pszMsg) + Log4(("%s:", pszMsg)); + for (;;) + { + OHCIED Ed; + + /* ED */ + Log4((" %#010x={", GCPhys)); + if (!GCPhys) + { + Log4(("END}\n")); + return; + } + + /* TDs */ + ohciR3ReadEd(pDevIns, GCPhys, &Ed); + if (Ed.hwinfo & ED_HWINFO_ISO) + Log4(("[I]")); + if ((Ed.HeadP & ED_HEAD_HALTED) || (Ed.hwinfo & ED_HWINFO_SKIP)) + { + if ((Ed.HeadP & ED_HEAD_HALTED) && (Ed.hwinfo & ED_HWINFO_SKIP)) + Log4(("SH}")); + else if (Ed.hwinfo & ED_HWINFO_SKIP) + Log4(("S-}")); + else + Log4(("-H}")); + } + else + { + if (Ed.hwinfo & ED_HWINFO_ISO) + ohciR3DumpITdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + else + ohciR3DumpTdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + Log4(("}")); + } + + /* next */ + GCPhys = Ed.NextED & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + } + /* not reached */ +} + +# endif /* LOG_ENABLED */ + + +DECLINLINE(int) ohciR3InFlightFindFree(POHCICC 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 OHCI instance data, shared edition. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @param pUrb The URB. + */ +static void ohciR3InFlightAdd(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD, PVUSBURB pUrb) +{ + int i = ohciR3InFlightFindFree(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 OHCI instance data, shared edition. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static void ohciR3InFlightAddUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb) +{ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + ohciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb); +} + + +/** + * Finds a in-flight TD. + * + * @returns Index of the record. + * @returns -1 if not found. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + * @remark This has to be fast. + */ +static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t 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 OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3IsTdInFlight(POHCICC pThisCC, uint32_t GCPhysTD) +{ + return ohciR3InFlightFind(pThisCC, GCPhysTD) >= 0; +} + +/** + * Returns a URB associated with an in-flight TD, if any. + * + * @returns pointer to URB if TD is in flight. + * @returns NULL if not in flight. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static PVUSBURB ohciR3TdInFlightUrb(POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i; + + i = ohciR3InFlightFind(pThisCC, GCPhysTD); + if ( i >= 0 ) + return pThisCC->aInFlight[i].pUrb; + return NULL; +} + +/** + * 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 OHCI instance data, shared edition (for logging). + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InFlightRemove(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i = ohciR3InFlightFind(pThisCC, GCPhysTD); + if (i >= 0) + { +# ifdef LOG_ENABLED + const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo; +# else + const int cFramesInFlight = 0; RT_NOREF(pThis); +# endif + Log2(("ohciR3InFlightRemove: reaping TD=%#010x %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 %#010x is not in flight\n", GCPhysTD)); + 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 OHCI instance data, shared edition (for logging). + * @param pThisCC OHCI instance data, ring-3 edition. + * @param pUrb The URB. + */ +static int ohciR3InFlightRemoveUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb) +{ + int cFramesInFlight = ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr); + if (pUrb->pHci->cTds > 1) + { + for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++) + if (ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0) + cFramesInFlight = -1; + } + return cFramesInFlight; +} + + +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + +/** + * Empties the in-done-queue. + * @param pThisCC OHCI instance data, ring-3 edition. + */ +static void ohciR3InDoneQueueZap(POHCICC pThisCC) +{ + pThisCC->cInDoneQueue = 0; +} + +/** + * Finds a TD in the in-done-queue. + * @returns >= 0 on success. + * @returns -1 if not found. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD) +{ + unsigned i = pThisCC->cInDoneQueue; + while (i-- > 0) + if (pThisCC->aInDoneQueue[i].GCPhysTD == GCPhysTD) + return i; + return -1; +} + +/** + * Checks that the specified TD is not in the done queue. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3InDoneQueueCheck(POHCICC pThisCC, uint32_t GCPhysTD) +{ + int i = ohciR3InDoneQueueFind(pThisCC, GCPhysTD); +# if 0 + /* This condition has been observed with the USB tablet emulation or with + * a real USB mouse and an SMP XP guest. I am also not sure if this is + * really a problem for us. The assertion checks that the guest doesn't + * re-submit a TD which is still in the done queue. It seems to me that + * this should only be a problem if we either keep track of TDs in the done + * queue somewhere else as well (in which case we should also free those + * references in time, and I can't see any code doing that) or if we + * manipulate TDs in the done queue in some way that might fail if they are + * re-submitted (can't see anything like that either). + */ + AssertMsg(i < 0, ("TD %#010x (i=%d)\n", GCPhysTD, i)); +# endif + return i < 0; +} + + +# if defined(VBOX_STRICT) && defined(LOG_ENABLED) +/** + * Adds a TD to the in-done-queue tracking, checking that it's not there already. + * @param pThisCC OHCI instance data, ring-3 edition. + * @param GCPhysTD Physical address of the TD. + */ +static void ohciR3InDoneQueueAdd(POHCICC pThisCC, uint32_t GCPhysTD) +{ + Assert(pThisCC->cInDoneQueue + 1 <= RT_ELEMENTS(pThisCC->aInDoneQueue)); + if (ohciR3InDoneQueueCheck(pThisCC, GCPhysTD)) + pThisCC->aInDoneQueue[pThisCC->cInDoneQueue++].GCPhysTD = GCPhysTD; +} +# endif /* VBOX_STRICT */ +# endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */ + + +/** + * OHCI Transport Buffer - represents a OHCI Transport Descriptor (TD). + * A TD may be split over max 2 pages. + */ +typedef struct OHCIBUF +{ + /** Pages involved. */ + struct OHCIBUFVEC + { + /** The 32-bit physical address of this part. */ + uint32_t Addr; + /** The length. */ + uint32_t cb; + } aVecs[2]; + /** Number of valid entries in aVecs. */ + uint32_t cVecs; + /** The total length. */ + uint32_t cbTotal; +} OHCIBUF, *POHCIBUF; + + +/** + * Sets up a OHCI transport buffer. + * + * @param pBuf OHCI buffer. + * @param cbp Current buffer pointer. 32-bit physical address. + * @param be Last byte in buffer (BufferEnd). 32-bit physical address. + */ +static void ohciR3BufInit(POHCIBUF pBuf, uint32_t cbp, uint32_t be) +{ + if (!cbp || !be) + { + pBuf->cVecs = 0; + pBuf->cbTotal = 0; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=0 EMPTY\n", cbp, be)); + } + else if ((cbp & ~0xfff) == (be & ~0xfff) && (cbp <= be)) + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = (be - cbp) + 1; + pBuf->cVecs = 1; + pBuf->cbTotal = pBuf->aVecs[0].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u\n", cbp, be, pBuf->cbTotal)); + } + else + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = 0x1000 - (cbp & 0xfff); + pBuf->aVecs[1].Addr = be & ~0xfff; + pBuf->aVecs[1].cb = (be & 0xfff) + 1; + pBuf->cVecs = 2; + pBuf->cbTotal = pBuf->aVecs[0].cb + pBuf->aVecs[1].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u PAGE FLIP\n", cbp, be, pBuf->cbTotal)); + } +} + +/** + * Updates a OHCI transport buffer. + * + * This is called upon completion to adjust the sector lengths if + * the total length has changed. (received less then we had space for + * or a partial transfer.) + * + * @param pBuf The buffer to update. cbTotal contains the new total on input. + * While the aVecs[*].cb members is updated upon return. + */ +static void ohciR3BufUpdate(POHCIBUF pBuf) +{ + for (uint32_t i = 0, cbCur = 0; i < pBuf->cVecs; i++) + { + if (cbCur + pBuf->aVecs[i].cb > pBuf->cbTotal) + { + pBuf->aVecs[i].cb = pBuf->cbTotal - cbCur; + pBuf->cVecs = i + 1; + return; + } + cbCur += pBuf->aVecs[i].cb; + } +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkIsochronousTdInList(PPDMDEVINS pDevIns, POHCI pThis, uint32_t TdAddr, POHCIITD pITd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciUnlinkIsocTdInList: Unlinking non-head ITD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cIterations = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cIterations-- > 0) + { + OHCIITD ITd; + ohciR3ReadITd(pDevIns, pThis, CurTdAddr, &ITd); + if ((ITd.NextTD & ED_PTR_MASK) == TdAddr) + { + ITd.NextTD = (pITd->NextTD & ED_PTR_MASK) | (ITd.NextTD & ~ED_PTR_MASK); + ohciR3WriteITd(pDevIns, pThis, CurTdAddr, &ITd, "ohciUnlinkIsocTdInList"); + pITd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = ITd.NextTD & ED_PTR_MASK; + } + + Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); + return false; +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkGeneralTdInList(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciR3UnlinkGeneralTdInList: Unlinking non-head TD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cIterations = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cIterations-- > 0) + { + OHCITD Td; + ohciR3ReadTd(pDevIns, CurTdAddr, &Td); + if ((Td.NextTD & ED_PTR_MASK) == TdAddr) + { + Td.NextTD = (pTd->NextTD & ED_PTR_MASK) | (Td.NextTD & ~ED_PTR_MASK); + ohciR3WriteTd(pDevIns, CurTdAddr, &Td, "ohciR3UnlinkGeneralTdInList"); + pTd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = Td.NextTD & ED_PTR_MASK; + } + + Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations)); + return false; +} + + +/** + * Unlinks the TDs that makes up the URB from the ED. + * + * @returns success indicator. true if successfully unlinked. + * @returns false if the TD was not found in the list. + */ +static bool ohciR3UnlinkTds(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, POHCIED pEd) +{ + /* + * Don't unlink more than once. + */ + if (pUrb->pHci->fUnlinked) + return true; + pUrb->pHci->fUnlinked = true; + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((ITdAddr & ED_PTR_MASK) == ITdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == ITdAddr) + { + pEd->HeadP = (pITd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pITd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * It's probably somewhere in the list, not a unlikely situation with + * the current isochronous code. + */ + if (!ohciR3UnlinkIsochronousTdInList(pDevIns, pThis, ITdAddr, pITd, pEd)) + return false; + } + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /** @todo r=bird: Messing with the toggle flag in prepare is probably not correct + * when we encounter a STALL error, 4.3.1.3.7.2: ''If an endpoint returns a STALL + * PID, the Host Controller retires the General TD with the ConditionCode set + * to STALL and halts the endpoint. The CurrentBufferPointer, ErrorCount, and + * dataToggle fields retain the values that they had at the start of the + * transaction.'' */ + + /* update toggle and set data toggle carry */ + pTd->hwinfo &= ~TD_HWINFO_TOGGLE; + if ( pTd->hwinfo & TD_HWINFO_TOGGLE_HI ) + { + if ( !!(pTd->hwinfo & TD_HWINFO_TOGGLE_LO) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pTd->hwinfo |= TD_HWINFO_TOGGLE_LO; + else + pTd->hwinfo &= ~TD_HWINFO_TOGGLE_LO; + } + else + { + if ( !!(pEd->HeadP & ED_HEAD_CARRY) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pEd->HeadP |= ED_HEAD_CARRY; + else + pEd->HeadP &= ~ED_HEAD_CARRY; + } + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((TdAddr & ED_PTR_MASK) == TdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == TdAddr) + { + pEd->HeadP = (pTd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pTd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * The TD is probably somewhere in the list. + * + * This shouldn't ever happen unless there was a failure! Even on failure, + * we can screw up the HCD state by picking out a TD from within the list + * like this! If this turns out to be a problem, we have to find a better + * solution. For now we'll hope the HCD handles it... + */ + if (!ohciR3UnlinkGeneralTdInList(pDevIns, TdAddr, pTd, pEd)) + return false; + } + + /* + * Only unlink the first TD on error. + * See comment in ohciR3RhXferCompleteGeneralURB(). + */ + if (pUrb->enmStatus != VUSBSTATUS_OK) + break; + } + } + + return true; +} + + +/** + * 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 pDevIns The device instance. + * @param pThis The OHCI instance. + * @param pUrb The URB in question. + * @param pEd The ED pointer (optional). + */ +static bool ohciR3HasUrbBeenCanceled(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, PCOHCIED pEd) +{ + if (!pUrb) + return true; + + /* + * Make sure we've got an endpoint descriptor so we can + * check for tail TDs. + */ + OHCIED Ed; + if (!pEd) + { + ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + pEd = &Ed; + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCIITD ITd; + uint32_t au32[8]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + ohciR3ReadITd(pDevIns, pThis, pUrb->paTds[iTd].TdAddr, &u.ITd); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* bp0 */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + || u.au32[4] != pUrb->paTds[iTd].TdCopy[4] /* psw0&1 */ + || u.au32[5] != pUrb->paTds[iTd].TdCopy[5] /* psw2&3 */ + || u.au32[6] != pUrb->paTds[iTd].TdCopy[6] /* psw4&5 */ + || u.au32[7] != pUrb->paTds[iTd].TdCopy[7] /* psw6&7 */ + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.ITd), &u.ITd, sizeof(u.ITd), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCITD Td; + uint32_t au32[4]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + ohciR3ReadTd(pDevIns, pUrb->paTds[iTd].TdAddr, &u.Td); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* cbp */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.Td), &u.Td, sizeof(u.Td), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + return false; +} + + +/** + * Returns the OHCI_CC_* corresponding to the VUSB status code. + * + * @returns OHCI_CC_* value. + * @param enmStatus The VUSB status code. + */ +static uint32_t ohciR3VUsbStatus2OhciStatus(VUSBSTATUS enmStatus) +{ + switch (enmStatus) + { + case VUSBSTATUS_OK: return OHCI_CC_NO_ERROR; + case VUSBSTATUS_STALL: return OHCI_CC_STALL; + case VUSBSTATUS_CRC: return OHCI_CC_CRC; + case VUSBSTATUS_DATA_UNDERRUN: return OHCI_CC_DATA_UNDERRUN; + case VUSBSTATUS_DATA_OVERRUN: return OHCI_CC_DATA_OVERRUN; + case VUSBSTATUS_DNR: return OHCI_CC_DNR; + case VUSBSTATUS_NOT_ACCESSED: return OHCI_CC_NOT_ACCESSED_1; + default: + Log(("pUrb->enmStatus=%#x!!!\n", enmStatus)); + return OHCI_CC_DNR; + } +} + + +/** + * Lock the given OHCI controller instance. + * + * @returns nothing. + * @param pThisCC The OHCI controller instance to lock, ring-3 edition. + */ +DECLINLINE(void) ohciR3Lock(POHCICC pThisCC) +{ + RTCritSectEnter(&pThisCC->CritSect); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Clear all caches here to avoid reading stale data from previous lock holders. */ + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif +} + + +/** + * Unlocks the given OHCI controller instance. + * + * @returns nothing. + * @param pThisCC The OHCI controller instance to unlock, ring-3 edition. + */ +DECLINLINE(void) ohciR3Unlock(POHCICC pThisCC) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* + * Clear all caches here to avoid leaving stale data behind (paranoia^2, + * already done in ohciR3Lock). + */ + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED); + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + + RTCritSectLeave(&pThisCC->CritSect); +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of isochronous TDs. + * + * In general, all URBs should have status OK. + */ +static void ohciR3RhXferCompleteIsochronousURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb + /*, POHCIED pEd , int cFmAge*/) +{ + /* + * Copy the data back (if IN operation) and update the TDs. + */ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + unsigned R = (pUrb->pHci->u32FrameNo & ITD_HWINFO_SF) - (pITd->HwInfo & ITD_HWINFO_SF); + if (R >= 8) + R = 0; /* submitted ahead of time. */ + + /* + * Only one case of TD level condition code is document, so + * just set NO_ERROR here to reduce number duplicate code. + */ + pITd->HwInfo &= ~TD_HWINFO_CC; + AssertCompile(OHCI_CC_NO_ERROR == 0); + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + /* + * Update the frames and copy back the data. + * We assume that we don't get incorrect lengths here. + */ + for (unsigned i = 0; i < cFrames; i++) + { + if ( i < R + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_NOT_ACCESSED) + { + /* It should already be NotAccessed. */ + pITd->aPSW[i] |= 0xe000; /* (Don't touch the 12th bit.) */ + continue; + } + + /* Update the PSW (save the offset first in case of a IN). */ + uint32_t off = pITd->aPSW[i] & ITD_PSW_OFFSET; + pITd->aPSW[i] = ohciR3VUsbStatus2OhciStatus(pUrb->aIsocPkts[i - R].enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_OK + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_OVERRUN)) + { + /* Set the size. */ + const unsigned cb = pUrb->aIsocPkts[i - R].cb; + pITd->aPSW[i] |= cb & ITD_PSW_SIZE; + /* Copy data. */ + if (cb) + { + uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i - R].off]; + if (off + cb > 0x1000) + { + if (off < 0x1000) + { + /* both */ + const unsigned cb0 = 0x1000 - off; + ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb0); + ohciR3PhysWrite(pDevIns, pITd->BE & ITD_BP0_MASK, pb + cb0, cb - cb0); + } + else /* only in the 2nd page */ + ohciR3PhysWrite(pDevIns, (pITd->BE & ITD_BP0_MASK) + (off & ITD_BP0_MASK), pb, cb); + } + else /* only in the 1st page */ + ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb); + Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n" + "%.*Rhxd\n", + i + R, off, cb, pb, pb - &pUrb->abData[0], cb, pb)); + //off += cb; + } + } + } + + /* + * If the last package ended with a NotAccessed status, set ITD CC + * to DataOverrun to indicate scheduling overrun. + */ + if (pUrb->aIsocPkts[pUrb->cIsocPkts - 1].enmStatus == VUSBSTATUS_NOT_ACCESSED) + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + } + else + { + Log(("DevOHCI: Taking untested code path at line %d...\n", __LINE__)); + /* + * Most status codes only applies 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! + */ + //if (pUrb->enmStatus != VUSBSTATUS_DATA_OVERRUN) + { + const unsigned uCC = ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + for (unsigned i = 0; i < cFrames; i++) + pITd->aPSW[i] = uCC; + } + //else + // pITd->HwInfo |= ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus); + } + + /* + * Update the done queue interrupt timer. + */ + uint32_t DoneInt = (pITd->HwInfo & ITD_HWINFO_DI) >> ITD_HWINFO_DI_SHIFT; + if ((pITd->HwInfo & TD_HWINFO_CC) != OHCI_CC_NO_ERROR) + DoneInt = 0; /* It's cleared on error. */ + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThisCC, ITdAddr); +# endif +# endif + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + Log(("%s: ohciR3RhXferCompleteIsochronousURB: ITdAddr=%#010x EdAddr=%#010x SF=%#x (%#x) CC=%#x FC=%d " + "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x R=%d\n", + pUrb->pszDesc, ITdAddr, + pUrb->pHci->EdAddr, + pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber, + (pITd->HwInfo & ITD_HWINFO_CC) >> ITD_HWINFO_CC_SHIFT, + (pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT, + pITd->aPSW[0] >> ITD_PSW_CC_SHIFT, pITd->aPSW[0] & ITD_PSW_SIZE, + pITd->aPSW[1] >> ITD_PSW_CC_SHIFT, pITd->aPSW[1] & ITD_PSW_SIZE, + pITd->aPSW[2] >> ITD_PSW_CC_SHIFT, pITd->aPSW[2] & ITD_PSW_SIZE, + pITd->aPSW[3] >> ITD_PSW_CC_SHIFT, pITd->aPSW[3] & ITD_PSW_SIZE, + pITd->aPSW[4] >> ITD_PSW_CC_SHIFT, pITd->aPSW[4] & ITD_PSW_SIZE, + pITd->aPSW[5] >> ITD_PSW_CC_SHIFT, pITd->aPSW[5] & ITD_PSW_SIZE, + pITd->aPSW[6] >> ITD_PSW_CC_SHIFT, pITd->aPSW[6] & ITD_PSW_SIZE, + pITd->aPSW[7] >> ITD_PSW_CC_SHIFT, pITd->aPSW[7] & ITD_PSW_SIZE, + R)); + ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "retired"); + } + RT_NOREF(pThisCC); +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of general TDs. + */ +static void ohciR3RhXferCompleteGeneralURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb, + POHCIED pEd, int cFmAge) +{ + RT_NOREF(cFmAge); + + /* + * Copy the data back (if IN operation) and update the TDs. + */ + unsigned cbLeft = pUrb->cbData; + uint8_t *pb = &pUrb->abData[0]; + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Setup a ohci transfer buffer and calc the new cbp value. + */ + OHCIBUF Buf; + ohciR3BufInit(&Buf, pTd->cbp, pTd->be); + uint32_t NewCbp; + if (cbLeft >= Buf.cbTotal) + NewCbp = 0; + else + { + /* (len may have changed for short transfers) */ + Buf.cbTotal = cbLeft; + ohciR3BufUpdate(&Buf); + Assert(Buf.cVecs >= 1); + NewCbp = Buf.aVecs[Buf.cVecs-1].Addr + Buf.aVecs[Buf.cVecs-1].cb; + } + + /* + * Write back IN buffers. + */ + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) + && Buf.cbTotal > 0) + { + Assert(Buf.cVecs > 0); + + /* Be paranoid */ + if ( Buf.aVecs[0].cb > cbLeft + || ( Buf.cVecs > 1 + && Buf.aVecs[1].cb > (cbLeft - Buf.aVecs[0].cb))) + { + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 1); + return; + } + + ohciR3PhysWrite(pDevIns, Buf.aVecs[0].Addr, pb, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysWrite(pDevIns, Buf.aVecs[1].Addr, pb + Buf.aVecs[0].cb, Buf.aVecs[1].cb); + } + + /* advance the data buffer. */ + cbLeft -= Buf.cbTotal; + pb += Buf.cbTotal; + + /* + * Set writeback field. + */ + /* zero out writeback fields for retirement */ + pTd->hwinfo &= ~TD_HWINFO_CC; + /* always update the CurrentBufferPointer; essential for underrun/overrun errors */ + pTd->cbp = NewCbp; + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + + /* update done queue interrupt timer */ + uint32_t DoneInt = (pTd->hwinfo & TD_HWINFO_DI) >> 21; + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + Log(("%s: ohciR3RhXferCompleteGeneralURB: ED=%#010x TD=%#010x Age=%d enmStatus=%d cbTotal=%#x NewCbp=%#010RX32 dqic=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus, Buf.cbTotal, NewCbp, pThis->dqic)); + } + else + { + Log(("%s: ohciR3RhXferCompleteGeneralURB: HALTED ED=%#010x TD=%#010x (age %d) pUrb->enmStatus=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus)); + pEd->HeadP |= ED_HEAD_HALTED; + pThis->dqic = 0; /* "If the Transfer Descriptor is being retired with an error, + * then the Done Queue Interrupt Counter is cleared as if the + * InterruptDelay field were zero." + */ + switch (pUrb->enmStatus) + { + case VUSBSTATUS_STALL: + pTd->hwinfo |= OHCI_CC_STALL; + break; + case VUSBSTATUS_CRC: + pTd->hwinfo |= OHCI_CC_CRC; + break; + case VUSBSTATUS_DATA_UNDERRUN: + pTd->hwinfo |= OHCI_CC_DATA_UNDERRUN; + break; + case VUSBSTATUS_DATA_OVERRUN: + pTd->hwinfo |= OHCI_CC_DATA_OVERRUN; + break; + default: /* what the hell */ + Log(("pUrb->enmStatus=%#x!!!\n", pUrb->enmStatus)); + RT_FALL_THRU(); + case VUSBSTATUS_DNR: + pTd->hwinfo |= OHCI_CC_DNR; + break; + } + } + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThisCC, TdAddr); +# endif +# endif + pTd->NextTD = pThis->done; + pThis->done = TdAddr; + + ohciR3WriteTd(pDevIns, TdAddr, pTd, "retired"); + + /* + * If we've halted the endpoint, we stop here. + * ohciR3UnlinkTds() will make sure we've only unliked the first TD. + * + * The reason for this is that while we can have more than one TD in a URB, real + * OHCI hardware will only deal with one TD at the time and it's therefore incorrect + * to retire TDs after the endpoint has been halted. Win2k will crash or enter infinite + * kernel loop if we don't behave correctly. (See @bugref{1646}.) + */ + if (pEd->HeadP & ED_HEAD_HALTED) + break; + } + RT_NOREF(pThisCC); +} + + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + LogFlow(("%s: ohciR3RhXferCompletion: EdAddr=%#010RX32 cTds=%d TdAddr0=%#010RX32\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr)); + + ohciR3Lock(pThisCC); + + int cFmAge = ohciR3InFlightRemoveUrb(pThis, pThisCC, pUrb); + + /* Do nothing requiring memory access if the HC encountered an unrecoverable error. */ + if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)) + { + pThis->fIdle = false; /* Mark as active */ + + /* get the current end point descriptor. */ + OHCIED Ed; + ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + + /* + * 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. + */ + if (pUrb->enmStatus == VUSBSTATUS_UNDO) + { + /* Leave the TD alone - the HCD doesn't want us talking to the device. */ + Log(("%s: ohciR3RhXferCompletion: CANCELED {ED=%#010x cTds=%d TD0=%#010x age %d}\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge)); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThisCC); + return; + } + bool fHasBeenCanceled = false; + if ( (Ed.HeadP & ED_HEAD_HALTED) + || (Ed.hwinfo & ED_HWINFO_SKIP) + || cFmAge < 0 + || (fHasBeenCanceled = ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, &Ed)) + || !ohciR3UnlinkTds(pDevIns, pThis, pUrb, &Ed) + ) + { + Log(("%s: ohciR3RhXferCompletion: DROPPED {ED=%#010x cTds=%d TD0=%#010x age %d} because:%s%s%s%s%s!!!\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge, + (Ed.HeadP & ED_HEAD_HALTED) ? " ep halted" : "", + (Ed.hwinfo & ED_HWINFO_SKIP) ? " ep skip" : "", + (Ed.HeadP & ED_PTR_MASK) != pUrb->paTds[0].TdAddr ? " ep head-changed" : "", + cFmAge < 0 ? " td not-in-flight" : "", + fHasBeenCanceled ? " td canceled" : "")); + NOREF(fHasBeenCanceled); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThisCC); + return; + } + + /* + * Complete the TD updating and write the back. + * When appropriate also copy data back to the guest memory. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + ohciR3RhXferCompleteIsochronousURB(pDevIns, pThis, pThisCC, pUrb /*, &Ed , cFmAge*/); + else + ohciR3RhXferCompleteGeneralURB(pDevIns, pThis, pThisCC, pUrb, &Ed, cFmAge); + + /* finally write back the endpoint descriptor. */ + ohciR3WriteEd(pDevIns, pUrb->pHci->EdAddr, &Ed); + } + + ohciR3Unlock(pThisCC); +} + + +/** + * 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 OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + /* + * Isochronous URBs can't be retried. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + return true; + + /* + * Don't retry on stall. + */ + if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + Log2(("%s: ohciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + return true; + } + + ohciR3Lock(pThisCC); + bool fRetire = false; + /* + * Check if the TDs still are valid. + * This will make sure the TdCopy is up to date. + */ + const uint32_t TdAddr = pUrb->paTds[0].TdAddr; +/** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */ + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, NULL)) + { + Log(("%s: ohciR3RhXferError: TdAddr0=%#x canceled!\n", pUrb->pszDesc, TdAddr)); + fRetire = true; + } + else + { + /* + * Get and update the error counter. + */ + POHCITD pTd = (POHCITD)&pUrb->paTds[0].TdCopy[0]; + unsigned cErrs = (pTd->hwinfo & TD_HWINFO_ERRORS) >> TD_ERRORS_SHIFT; + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + cErrs++; + pTd->hwinfo |= (cErrs % TD_ERRORS_MAX) << TD_ERRORS_SHIFT; + ohciR3WriteTd(pDevIns, TdAddr, pTd, "ohciR3RhXferError"); + + if (cErrs >= TD_ERRORS_MAX - 1) + { + Log2(("%s: ohciR3RhXferError: too many errors, giving up!\n", pUrb->pszDesc)); + fRetire = true; + } + else + Log2(("%s: ohciR3RhXferError: cErrs=%d: retrying...\n", pUrb->pszDesc, cErrs)); + } + + ohciR3Unlock(pThisCC); + return fRetire; +} + + +/** + * Service a general transport descriptor. + */ +static bool ohciR3ServiceTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TD and setup the buffer data. + */ + OHCITD Td; + ohciR3ReadTd(pDevIns, TdAddr, &Td); + OHCIBUF Buf; + ohciR3BufInit(&Buf, Td.cbp, Td.be); + + *pNextTdAddr = Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + switch (Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + case 0: enmDir = VUSBDIRECTION_SETUP; break; + default: + Log(("ohciR3ServiceTd: Invalid direction!!!! Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Td.hwinfo, pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 2); + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + enmType, enmDir, Buf.cbTotal, 1, NULL); + if (!pUrb) + return false; /* retry later... */ + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = !(Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->cTds = 1; + pUrb->paTds[0].TdAddr = TdAddr; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(Td)); + memcpy(pUrb->paTds[0].TdCopy, &Td, sizeof(Td)); + + /* copy data if out bound transfer. */ + pUrb->cbData = Buf.cbTotal; + if ( Buf.cbTotal + && Buf.cVecs > 0 + && enmDir != VUSBDIRECTION_IN) + { + /* Be paranoid. */ + if ( Buf.aVecs[0].cb > pUrb->cbData + || ( Buf.cVecs > 1 + && Buf.aVecs[1].cb > (pUrb->cbData - Buf.aVecs[0].cb))) + { + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 3); + VUSBIRhFreeUrb(pThisCC->RootHub.pIRhConn, pUrb); + return false; + } + + ohciR3PhysRead(pDevIns, Buf.aVecs[0].Addr, pUrb->abData, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysRead(pDevIns, Buf.aVecs[1].Addr, &pUrb->abData[Buf.aVecs[0].cb], Buf.aVecs[1].cb); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAdd(pThis, pThisCC, TdAddr, pUrb); + Log(("%s: ohciR3ServiceTd: submitting TdAddr=%#010x EdAddr=%#010x cbData=%#x\n", + pUrb->pszDesc, TdAddr, EdAddr, pUrb->cbData)); + + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTd: failed submitting TdAddr=%#010x EdAddr=%#010x pUrb=%p!!\n", + TdAddr, EdAddr, pUrb)); + ohciR3InFlightRemove(pThis, pThisCC, TdAddr); + return false; +} + + +/** + * Service a the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * Read the TD, after first checking if it's already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThisCC, TdAddr); +# endif + return ohciR3ServiceTd(pDevIns, pThis, pThisCC, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * Service one or more general transport descriptors (bulk or interrupt). + */ +static bool ohciR3ServiceTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, + uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TDs involved in this URB. + */ + struct OHCITDENTRY + { + /** The TD. */ + OHCITD Td; + /** The associated OHCI buffer tracker. */ + OHCIBUF Buf; + /** The TD address. */ + uint32_t TdAddr; + /** Pointer to the next element in the chain (stack). */ + struct OHCITDENTRY *pNext; + } Head; + + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + + /* read the head */ + ohciR3ReadTd(pDevIns, TdAddr, &Head.Td); + ohciR3BufInit(&Head.Buf, Head.Td.cbp, Head.Td.be); + Head.TdAddr = TdAddr; + Head.pNext = NULL; + + /* combine with more TDs. */ + struct OHCITDENTRY *pTail = &Head; + unsigned cbTotal = pTail->Buf.cbTotal; + unsigned cTds = 1; + while ( (pTail->Buf.cbTotal == 0x1000 || pTail->Buf.cbTotal == 0x2000) + && !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING) /* This isn't right for *BSD, but let's not . */ + && (pTail->Td.NextTD & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && cTds < 128) + { + struct OHCITDENTRY *pCur = (struct OHCITDENTRY *)alloca(sizeof(*pCur)); + + pCur->pNext = NULL; + pCur->TdAddr = pTail->Td.NextTD & ED_PTR_MASK; + ohciR3ReadTd(pDevIns, pCur->TdAddr, &pCur->Td); + ohciR3BufInit(&pCur->Buf, pCur->Td.cbp, pCur->Td.be); + + /* Don't combine if the direction doesn't match up. There can't actually be + * a mismatch for bulk/interrupt EPs unless the guest is buggy. + */ + if ( (pCur->Td.hwinfo & (TD_HWINFO_DIR)) + != (Head.Td.hwinfo & (TD_HWINFO_DIR))) + break; + + pTail->pNext = pCur; + pTail = pCur; + cbTotal += pCur->Buf.cbTotal; + cTds++; + } + + /* calc next TD address */ + *pNextTdAddr = pTail->Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: WARNING! Ed.hwdinfo=%#x bulk or interrupt EP shouldn't rely on the TD for direction...\n", pEd->hwinfo)); + switch (Head.Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: Invalid direction!!!! Head.Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Head.Td.hwinfo, pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 4); + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + enmType, enmDir, cbTotal, cTds, "ohciR3ServiceTdMultiple"); + if (!pUrb) + /* retry later... */ + return false; + Assert(pUrb->cbData == cbTotal); + + pUrb->enmType = enmType; + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->enmDir = enmDir; + pUrb->fShortNotOk = !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->cTds = cTds; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + + /* Copy data and TD information. */ + unsigned iTd = 0; + uint8_t *pb = &pUrb->abData[0]; + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + { + /* data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN + && pCur->Buf.cVecs > 0) + { + ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[0].Addr, pb, pCur->Buf.aVecs[0].cb); + if (pCur->Buf.cVecs > 1) + ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[1].Addr, pb + pCur->Buf.aVecs[0].cb, pCur->Buf.aVecs[1].cb); + } + pb += pCur->Buf.cbTotal; + + /* TD info */ + pUrb->paTds[iTd].TdAddr = pCur->TdAddr; + AssertCompile(sizeof(pUrb->paTds[iTd].TdCopy) >= sizeof(pCur->Td)); + memcpy(pUrb->paTds[iTd].TdCopy, &pCur->Td, sizeof(pCur->Td)); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAddUrb(pThis, pThisCC, pUrb); + Log(("%s: ohciR3ServiceTdMultiple: submitting cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x\n", + pUrb->pszDesc, pUrb->cbData, EdAddr, cTds, TdAddr)); + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTdMultiple: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, cTds, TdAddr, rc)); + /* NB: We cannot call ohciR3InFlightRemoveUrb() because the URB is already gone! */ + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + ohciR3InFlightRemove(pThis, pThisCC, pCur->TdAddr); + return false; +} + + +/** + * Service the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType, + PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * First, check that it's not already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThisCC, TdAddr); +# endif + return ohciR3ServiceTdMultiple(pDevIns, pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which unlinks a ITD + * that belongs to the past. + */ +static bool ohciR3ServiceIsochronousTdUnlink(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIITD pITd, uint32_t ITdAddr, + uint32_t ITdAddrPrev, PVUSBURB pUrb, POHCIED pEd, uint32_t EdAddr) +{ + LogFlow(("%s%sohciR3ServiceIsochronousTdUnlink: Unlinking ITD: ITdAddr=%#010x EdAddr=%#010x ITdAddrPrev=%#010x\n", + pUrb ? pUrb->pszDesc : "", pUrb ? ": " : "", ITdAddr, EdAddr, ITdAddrPrev)); + + /* + * Do the unlinking. + */ + const uint32_t ITdAddrNext = pITd->NextTD & ED_PTR_MASK; + if (ITdAddrPrev) + { + /* Get validate the previous TD */ + int iInFlightPrev = ohciR3InFlightFind(pThisCC, ITdAddrPrev); + AssertMsgReturn(iInFlightPrev >= 0, ("ITdAddr=%#RX32\n", ITdAddrPrev), false); + PVUSBURB pUrbPrev = pThisCC->aInFlight[iInFlightPrev].pUrb; + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrbPrev, pEd)) /* ensures the copy is correct. */ + return false; + + /* Update the copy and write it back. */ + POHCIITD pITdPrev = ((POHCIITD)pUrbPrev->paTds[0].TdCopy); + pITdPrev->NextTD = (pITdPrev->NextTD & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteITd(pDevIns, pThis, ITdAddrPrev, pITdPrev, "ohciR3ServiceIsochronousEndpoint"); + } + else + { + /* It's the head node. update the copy from the caller and write it back. */ + pEd->HeadP = (pEd->HeadP & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteEd(pDevIns, EdAddr, pEd); + } + + /* + * If it's in flight, just mark the URB as unlinked (there is only one ITD per URB atm). + * Otherwise, retire it to the done queue with an error and cause a done line interrupt (?). + */ + if (pUrb) + { + pUrb->pHci->fUnlinked = true; + if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, pEd)) /* ensures the copy is correct (paranoia). */ + return false; + + POHCIITD pITdCopy = ((POHCIITD)pUrb->paTds[0].TdCopy); + pITd->NextTD = pITdCopy->NextTD &= ~ED_PTR_MASK; + } + else + { + pITd->HwInfo &= ~ITD_HWINFO_CC; + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + pThis->dqic = 0; + } + + ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "ohciR3ServiceIsochronousTdUnlink"); + return true; +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which submits the specified TD. + * + * @returns true on success. + * @returns false on failure to submit. + * @param pDevIns The device instance. + * @param pThis The OHCI controller instance data, shared edition. + * @param pThisCC The OHCI controller instance data, ring-3 edition. + * @param pITd The transfer descriptor to service. + * @param ITdAddr The address of the transfer descriptor in gues memory. + * @param R The start packet (frame) relative to the start of frame in HwInfo. + * @param pEd The OHCI endpoint descriptor. + * @param EdAddr The endpoint descriptor address in guest memory. + */ +static bool ohciR3ServiceIsochronousTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, + POHCIITD pITd, uint32_t ITdAddr, const unsigned R, PCOHCIED pEd, uint32_t EdAddr) +{ + /* + * Determine the endpoint direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceIsochronousTd: Invalid direction!!!! Ed.hwdinfo=%#x\n", pEd->hwinfo)); + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 5); + return false; + } + + /* + * Extract the packet sizes and calc the total URB size. + */ + struct + { + uint16_t cb; + uint16_t off; + } aPkts[ITD_NUM_PSW]; + + /* first entry (R) */ + uint32_t cbTotal = 0; + if (((uint32_t)pITd->aPSW[R] >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + { + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, R, pITd->aPSW[R] >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + pThis->intr_status |= OHCI_INTR_UNRECOVERABLE_ERROR; + return false; + } + uint16_t offPrev = aPkts[0].off = (pITd->aPSW[R] & ITD_PSW_OFFSET); + + /* R+1..cFrames */ + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + for (unsigned iR = R + 1; iR < cFrames; iR++) + { + const uint16_t PSW = pITd->aPSW[iR]; + const uint16_t off = aPkts[iR - R].off = (PSW & ITD_PSW_OFFSET); + cbTotal += aPkts[iR - R - 1].cb = off - offPrev; + if (off < offPrev) + { + Log(("ITdAddr=%RX32 PSW%d.offset=%#x < offPrev=%#x!\n", ITdAddr, iR, off, offPrev)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 6); + return false; + } + if (((uint32_t)PSW >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + { + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, iR, PSW >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 7); + return false; + } + offPrev = off; + } + + /* calc offEnd and figure out the size of the last packet. */ + const uint32_t offEnd = (pITd->BE & 0xfff) + + (((pITd->BE & ITD_BP0_MASK) != (pITd->BP0 & ITD_BP0_MASK)) << 12) + + 1 /* BE is inclusive */; + if (offEnd < offPrev) + { + Log(("ITdAddr=%RX32 offEnd=%#x < offPrev=%#x!\n", ITdAddr, offEnd, offPrev)); /* => Unrecoverable Error*/ + ohciR3RaiseUnrecoverableError(pDevIns, pThis, 8); + return false; + } + cbTotal += aPkts[cFrames - 1 - R].cb = offEnd - offPrev; + Assert(cbTotal <= 0x2000); + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID, + VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL); + if (!pUrb) + /* retry later... */ + return false; + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->cTds = 1; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + pUrb->paTds[0].TdAddr = ITdAddr; + 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) + { + const uint32_t off0 = pITd->aPSW[R] & ITD_PSW_OFFSET; + if (off0 < 0x1000) + { + if (offEnd > 0x1000) + { + /* both pages. */ + const unsigned cb0 = 0x1000 - off0; + ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, &pUrb->abData[0], cb0); + ohciR3PhysRead(pDevIns, pITd->BE & ITD_BP0_MASK, &pUrb->abData[cb0], offEnd & 0xfff); + } + else /* a portion of the 1st page. */ + ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, pUrb->abData, offEnd - off0); + } + else /* a portion of the 2nd page. */ + ohciR3PhysRead(pDevIns, (pITd->BE & UINT32_C(0xfffff000)) + (off0 & 0xfff), pUrb->abData, cbTotal); + } + + /* setup the packets */ + pUrb->cIsocPkts = cFrames - R; + 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. + */ + ohciR3InFlightAdd(pThis, pThisCC, ITdAddr, pUrb); + Log(("%s: ohciR3ServiceIsochronousTd: submitting cbData=%#x cIsocPkts=%d EdAddr=%#010x TdAddr=%#010x SF=%#x (%#x)\n", + pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, EdAddr, ITdAddr, pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber)); + ohciR3Unlock(pThisCC); + int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led); + ohciR3Lock(pThisCC); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceIsochronousTd: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d ITdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, 1, ITdAddr, rc)); + ohciR3InFlightRemove(pThis, pThisCC, ITdAddr); + return false; +} + + +/** + * Service an isochronous endpoint. + */ +static void ohciR3ServiceIsochronousEndpoint(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIED pEd, uint32_t EdAddr) +{ + /* + * We currently process this as if the guest follows the interrupt end point chaining + * hierarchy described in the documenation. This means that for an isochronous endpoint + * with a 1 ms interval we expect to find in-flight TDs at the head of the list. We will + * skip over all in-flight TDs which timeframe has been exceed. Those which aren't in + * flight but which are too late will be retired (possibly out of order, but, we don't + * care right now). + * + * When we reach a TD which still has a buffer which is due for take off, we will + * stop iterating TDs. If it's in-flight, there isn't anything to be done. Otherwise + * we will push it onto the runway for immediate take off. In this process we + * might have to complete buffers which didn't make it on time, something which + * complicates the kind of status info we need to keep around for the TD. + * + * Note: We're currently not making any attempt at reassembling ITDs into URBs. + * However, this will become necessary because of EMT scheduling and guest + * like linux using one TD for each frame (simple but inefficient for us). + */ + OHCIITD ITd; + uint32_t ITdAddr = pEd->HeadP & ED_PTR_MASK; + uint32_t ITdAddrPrev = 0; + uint32_t u32NextFrame = UINT32_MAX; + const uint16_t u16CurFrame = pThis->HcFmNumber; + for (;;) + { + /* check for end-of-chain. */ + if ( ITdAddr == (pEd->TailP & ED_PTR_MASK) + || !ITdAddr) + break; + + /* + * If isochronous endpoints are around, don't slow down the timer. Getting the timing right + * is difficult enough as it is. + */ + pThis->fIdle = false; + + /* + * Read the current ITD and check what we're supposed to do about it. + */ + ohciR3ReadITd(pDevIns, pThis, ITdAddr, &ITd); + const uint32_t ITdAddrNext = ITd.NextTD & ED_PTR_MASK; + const int16_t R = u16CurFrame - (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF); /* 4.3.2.3 */ + const int16_t cFrames = ((ITd.HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + + if (R < cFrames) + { + /* + * It's inside the current or a future launch window. + * + * We will try maximize the TD in flight here to deal with EMT scheduling + * issues and similar stuff which will screw up the time. So, we will only + * stop submitting TD when we reach a gap (in time) or end of the list. + */ + if ( R < 0 /* (a future frame) */ + && (uint16_t)u32NextFrame != (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF)) + break; + if (ohciR3InFlightFind(pThisCC, ITdAddr) < 0) + if (!ohciR3ServiceIsochronousTd(pDevIns, pThis, pThisCC, &ITd, ITdAddr, R < 0 ? 0 : R, pEd, EdAddr)) + break; + + ITdAddrPrev = ITdAddr; + } + else + { +# if 1 + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * Don't remove in-flight TDs before they complete. + * Windows will, upon the completion of another ITD it seems, check for if + * any other TDs has been unlinked. If we unlink them before they really + * complete all the packet status codes will be NotAccessed and Windows + * will fail the URB with status USBD_STATUS_ISOCH_REQUEST_FAILED. + * + * I don't know if unlinking TDs out of order could cause similar problems, + * time will show. + */ + int iInFlight = ohciR3InFlightFind(pThisCC, ITdAddr); + if (iInFlight >= 0) + ITdAddrPrev = ITdAddr; + else if (!ohciR3ServiceIsochronousTdUnlink(pDevIns, pThis, pThisCC, &ITd, ITdAddr, ITdAddrPrev, NULL, pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# else /* BAD IDEA: */ + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * If it's in flight we will try unlink it from the list prematurely to + * help the guest to move on and shorten the list we have to walk. We currently + * are successful with the first URB but then it goes too slowly... + */ + int iInFlight = ohciR3InFlightFind(pThis, ITdAddr); + if (!ohciR3ServiceIsochronousTdUnlink(pThis, &ITd, ITdAddr, ITdAddrPrev, + iInFlight < 0 ? NULL : pThis->aInFlight[iInFlight].pUrb, + pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# endif + } + + /* advance to the next ITD */ + ITdAddr = ITdAddrNext; + u32NextFrame = (ITd.HwInfo & ITD_HWINFO_SF) + cFrames; + } +} + + +/** + * Checks if a endpoints has TDs queued and is ready to have them processed. + * + * @returns true if it's ok to process TDs. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdReady(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED) + && !(pEd->hwinfo & ED_HWINFO_SKIP); +} + + +/** + * Checks if an endpoint has TDs queued (not necessarily ready to have them processed). + * + * @returns true if endpoint may have TDs queued. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdPresent(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED); +} + + +/** + * Services the bulk list. + * + * On the bulk 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 ohciR3ServiceBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3ServiceBulkList: bulk_cur=%#010x before listprocessing!!! HCD have positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* + * ", HC will start processing the Bulk list and will set BF [BulkListFilled] to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = false; + pThis->bulk_cur = 0; + + uint32_t EdAddr = pThis->bulk_head; + uint32_t cIterations = 256; + while (EdAddr + && (pThis->ctl & OHCI_CTL_BLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { + pThis->status |= OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = true; + +# if 1 + /* + + * After we figured out that all the TDs submitted for dealing with MSD + * read/write data really makes up on single URB, and that we must + * reassemble these TDs into an URB before submitting it, there is no + * longer any need for servicing anything other than the head *URB* + * on a bulk endpoint. + */ + ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_BULK, &Ed, EdAddr, "Bulk"); +# else + /* + * This alternative code was used before we started reassembling URBs from + * multiple TDs. We keep it handy for debugging. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (!ohciR3IsTdInFlight(pThis, TdAddr)) + { + do + { + if (!ohciR3ServiceTdMultiple(pThis, VUSBXFERTYPE_BULK, &Ed, EdAddr, TdAddr, &TdAddr, "Bulk")) + { + LogFlow(("ohciR3ServiceBulkList: ohciR3ServiceTdMultiple -> false\n")); + break; + } + if ( (TdAddr & ED_PTR_MASK) == (Ed.TailP & ED_PTR_MASK) + || !TdAddr /* paranoia */) + { + LogFlow(("ohciR3ServiceBulkList: TdAddr=%#010RX32 Ed.TailP=%#010RX32\n", TdAddr, Ed.TailP)); + break; + } + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); + } +# endif + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + LogFlow(("ohciR3ServiceBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk after ", true); +# endif +} + + +/** + * Abort outstanding transfers on the bulk list. + * + * If the guest disabled bulk list processing, we must abort any outstanding transfers + * (that is, cancel in-flight URBs associated with the list). This is required because + * there may be outstanding read URBs that will never get a response from the device + * and would block further communication. + */ +static void ohciR3UndoBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3UndoBulkList: bulk_cur=%#010x before list processing!!! HCD has positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* This flag follows OHCI_STATUS_BLF, but BLF doesn't change when list processing is disabled. */ + pThis->fBulkNeedsCleaning = false; + + uint32_t EdAddr = pThis->bulk_head; + uint32_t cIterations = 256; + while (EdAddr + && (cIterations-- > 0)) + { + OHCIED Ed; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdPresent(&Ed)) + { + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThisCC, TdAddr)) + { + LogFlow(("ohciR3UndoBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 UNDO\n", EdAddr, Ed.TailP)); + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } +} + + +/** + * Services the control list. + * + * The control list has complex URB assembling, but that's taken + * care of at VUSB level (unlike the other transfer types). + */ +static void ohciR3ServiceCtrlList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl before", true); + if (pThis->ctrl_cur) + Log(("ohciR3ServiceCtrlList: ctrl_cur=%010x before list processing!!! HCD have positioned us!!!\n", pThis->ctrl_cur)); +# endif + + /* + * ", HC will start processing the list and will set ControlListFilled to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_CLF; + pThis->ctrl_cur = 0; + + uint32_t EdAddr = pThis->ctrl_head; + uint32_t cIterations = 256; + while ( EdAddr + && (pThis->ctl & OHCI_CTL_CLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { +# if 1 + /* + * Control TDs depends on order and stage. Only one can be in-flight + * at any given time. OTOH, some stages are completed immediately, + * so we process the list until we've got a head which is in-flight + * or reach the end of the list. + */ + do + { + if ( !ohciR3ServiceHeadTd(pDevIns, pThis, pThisCC, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control") + || ohciR3IsTdInFlight(pThisCC, Ed.HeadP & ED_PTR_MASK)) + { + pThis->status |= OHCI_STATUS_CLF; + break; + } + ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); +# else + /* Simplistic, for debugging. */ + ohciR3ServiceHeadTd(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control"); + pThis->status |= OHCI_STATUS_CLF; +# endif + } + + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl after ", 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 ohciR3ServicePeriodicList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + /* + * Read the list head from the HCCA. + */ + const unsigned iList = pThis->HcFmNumber % OHCI_HCCA_NUM_INTR; + uint32_t EdAddr; + ohciR3GetDWords(pDevIns, pThis->hcca + iList * sizeof(EdAddr), &EdAddr, 1); + +# ifdef LOG_ENABLED + const uint32_t EdAddrHead = EdAddr; + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x before", iList); + ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true); + } +# endif + + /* + * Iterate the endpoint list. + */ + unsigned cIterations = 128; + while (EdAddr + && (pThis->ctl & OHCI_CTL_PLE) + && (cIterations-- > 0)) + { + OHCIED Ed; + + /* Bail if previous processing ended up in the unrecoverable error state. */ + if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR) + break; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + if (ohciR3IsEdReady(&Ed)) + { + /* + * "There is no separate head pointer of isochronous transfers. The first + * isochronous Endpoint Descriptor simply links to the last interrupt + * Endpoint Descriptor." + */ + if (!(Ed.hwinfo & ED_HWINFO_ISO)) + { + /* + * Presently we will only process the head URB on an interrupt endpoint. + */ + ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_INTR, &Ed, EdAddr, "Periodic"); + } + else if (pThis->ctl & OHCI_CTL_IE) + { + /* + * Presently only the head ITD. + */ + ohciR3ServiceIsochronousEndpoint(pDevIns, pThis, pThisCC, &Ed, EdAddr); + } + else + break; + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + Log3(("ohciR3ServicePeriodicList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr); + if (pUrb) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iList); + ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true); + } +# endif +} + + +/** + * Update the HCCA. + * + * @param pDevIns The device instance. + * @param pThis The OHCI controller instance data, shared edition. + * @param pThisCC The OHCI controller instance data, ring-3 edition. + */ +static void ohciR3UpdateHCCA(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + OCHIHCCA hcca; + ohciR3PhysRead(pDevIns, pThis->hcca + OHCI_HCCA_OFS, &hcca, sizeof(hcca)); + + hcca.frame = RT_H2LE_U16((uint16_t)pThis->HcFmNumber); + hcca.pad = 0; + + bool fWriteDoneHeadInterrupt = false; + if ( pThis->dqic == 0 + && (pThis->intr_status & OHCI_INTR_WRITE_DONE_HEAD) == 0) + { + uint32_t done = pThis->done; + + if (pThis->intr_status & ~( OHCI_INTR_MASTER_INTERRUPT_ENABLED | OHCI_INTR_OWNERSHIP_CHANGE + | OHCI_INTR_WRITE_DONE_HEAD) ) + done |= 0x1; + + hcca.done = RT_H2LE_U32(done); + pThis->done = 0; + pThis->dqic = 0x7; + + Log(("ohci: Writeback Done (%#010x) on frame %#x (age %#x)\n", hcca.done, + pThis->HcFmNumber, pThis->HcFmNumber - pThisCC->u32FmDoneQueueTail)); +# ifdef LOG_ENABLED + ohciR3DumpTdQueue(pDevIns, pThisCC, hcca.done & ED_PTR_MASK, "DoneQueue"); +# endif + Assert(RT_OFFSETOF(OCHIHCCA, done) == 4); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueZap(pThisCC); +# endif + fWriteDoneHeadInterrupt = true; + } + + Log3(("ohci: Updating HCCA on frame %#x\n", pThis->HcFmNumber)); + ohciR3PhysWriteMeta(pDevIns, pThis->hcca + OHCI_HCCA_OFS, (uint8_t *)&hcca, sizeof(hcca)); + if (fWriteDoneHeadInterrupt) + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_WRITE_DONE_HEAD); + RT_NOREF(pThisCC); +} + + +/** + * Go over the in-flight URB list and cancel any URBs that are no longer in use. + * This occurs when the host removes EDs or TDs from the lists and we don't notice + * the sKip bit. Such URBs must be promptly canceled, otherwise there is a risk + * they might "steal" data destined for another URB. + */ +static void ohciR3CancelOrphanedURBs(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + unsigned i, cLeft; + int j; + uint32_t EdAddr; + PVUSBURB pUrb; + + /* If the HCCA is not currently valid, or there are no in-flight URBs, + * there's nothing to do. + */ + if (!fValidHCCA || !pThisCC->cInFlight) + return; + + /* Initially mark all in-flight URBs as inactive. */ + for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++) + { + if (pThisCC->aInFlight[i].pUrb) + { + pThisCC->aInFlight[i].fInactive = true; + cLeft--; + } + } + Assert(cLeft == 0); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Get hcca data to minimize calls to ohciR3GetDWords/PDMDevHlpPCIPhysRead. */ + uint32_t au32HCCA[OHCI_HCCA_NUM_INTR]; + ohciR3GetDWords(pDevIns, pThis->hcca, au32HCCA, OHCI_HCCA_NUM_INTR); +# endif + + /* Go over all bulk/control/interrupt endpoint lists; any URB found in these lists + * is marked as active again. + */ + for (i = 0; i < OHCI_HCCA_NUM_INTR + 2; i++) + { + switch (i) + { + case OHCI_HCCA_NUM_INTR: + EdAddr = pThis->bulk_head; + break; + case OHCI_HCCA_NUM_INTR + 1: + EdAddr = pThis->ctrl_head; + break; + default: +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + EdAddr = au32HCCA[i]; +# else + ohciR3GetDWords(pDevIns, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1); +# endif + break; + } + + unsigned cIterED = 128; + while ( EdAddr + && (cIterED-- > 0)) + { + OHCIED Ed; + OHCITD Td; + + ohciR3ReadEd(pDevIns, EdAddr, &Ed); + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + uint32_t TailP = Ed.TailP & ED_PTR_MASK; + unsigned cIterTD = 0; + if ( !(Ed.hwinfo & ED_HWINFO_SKIP) + && (TdAddr != TailP)) + { +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD); +# endif + do + { + ohciR3ReadTd(pDevIns, TdAddr, &Td); + j = ohciR3InFlightFind(pThisCC, TdAddr); + if (j > -1) + pThisCC->aInFlight[j].fInactive = false; + TdAddr = Td.NextTD & ED_PTR_MASK; + /* See #8125. + * Sometimes the ED is changed by the guest between ohciR3ReadEd above and here. + * Then the code reads TD pointed by the new TailP, which is not allowed. + * Luckily Windows guests have Td.NextTD = 0 in the tail TD. + * Also having a real TD at 0 is very unlikely. + * So do not continue. + */ + if (TdAddr == 0) + break; + /* Failsafe for temporarily looped lists. */ + if (++cIterTD == 128) + break; + } while (TdAddr != (Ed.TailP & ED_PTR_MASK)); + } + /* Trivial loop detection. */ + if (EdAddr == (Ed.NextED & ED_PTR_MASK)) + break; + /* Proceed to the next endpoint. */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + } + + /* In-flight URBs still marked as inactive are not used anymore and need + * to be canceled. + */ + for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++) + { + if (pThisCC->aInFlight[i].pUrb) + { + cLeft--; + pUrb = pThisCC->aInFlight[i].pUrb; + if ( pThisCC->aInFlight[i].fInactive + && pUrb->enmState == VUSBURBSTATE_IN_FLIGHT + && pUrb->enmType != VUSBXFERTYPE_CTRL) + pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb); + } + } + Assert(cLeft == 0); +} + + +/** + * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame. + */ +static void ohciR3StartOfFrame(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ +# ifdef LOG_ENABLED + const uint32_t status_old = pThis->status; +# endif + + /* + * Update HcFmRemaining.FRT and update start of frame time. + */ + pThis->frt = pThis->fit; + pThis->SofTime += pThis->cTicksPerFrame; + + /* + * Check that the HCCA address isn't bogus. Linux 2.4.x is known to start + * the bus with a hcca of 0 to work around problem with a specific controller. + */ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + +# if 1 + /* + * Update the HCCA. + * Should be done after SOF but before HC read first ED in this frame. + */ + if (fValidHCCA) + ohciR3UpdateHCCA(pDevIns, pThis, pThisCC); +# endif + + /* "After writing to HCCA, HC will set SF in HcInterruptStatus" - guest isn't executing, so ignore the order! */ + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_START_OF_FRAME); + + if (pThis->fno) + { + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_FRAMENUMBER_OVERFLOW); + pThis->fno = 0; + } + + /* If the HCCA address is invalid, we're quitting here to avoid doing something which cannot be reported to the HCD. */ + if (!fValidHCCA) + { + Log(("ohciR3StartOfFrame: skipping hcca part because hcca=%RX32 (our 'valid' range: %RX32-%RX32)\n", + pThis->hcca, ~OHCI_HCCA_MASK, OHCI_HCCA_MASK)); + return; + } + + /* + * Periodic EPs. + */ + if (pThis->ctl & OHCI_CTL_PLE) + ohciR3ServicePeriodicList(pDevIns, pThis, pThisCC); + + /* + * Control EPs. + */ + if ( (pThis->ctl & OHCI_CTL_CLE) + && (pThis->status & OHCI_STATUS_CLF) ) + ohciR3ServiceCtrlList(pDevIns, pThis, pThisCC); + + /* + * Bulk EPs. + */ + if ( (pThis->ctl & OHCI_CTL_BLE) + && (pThis->status & OHCI_STATUS_BLF)) + ohciR3ServiceBulkList(pDevIns, pThis, pThisCC); + else if ((pThis->status & OHCI_STATUS_BLF) + && pThis->fBulkNeedsCleaning) + ohciR3UndoBulkList(pDevIns, pThis, pThisCC); /* If list disabled but not empty, abort endpoints. */ + +# if 0 + /* + * Update the HCCA after processing the lists and everything. A bit experimental. + * + * 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. + */ + ohciR3UpdateHCCA(pThis); +# endif + +# ifdef LOG_ENABLED + if (pThis->status ^ status_old) + { + uint32_t val = pThis->status; + uint32_t chg = val ^ status_old; NOREF(chg); + Log2(("ohciR3StartOfFrame: 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 +} + + +/** + * Updates the HcFmNumber and FNO registers. + */ +static void ohciR3BumpFrameNumber(POHCI pThis) +{ + const uint16_t u16OldFmNumber = pThis->HcFmNumber++; + if ((u16OldFmNumber ^ pThis->HcFmNumber) & RT_BIT(15)) + pThis->fno = 1; +} + + +/** + * Callback for periodic frame processing. + */ +static DECLCALLBACK(bool) ohciR3StartFrame(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameNo) +{ + RT_NOREF(u32FrameNo); + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + ohciR3Lock(pThisCC); + + /* Reset idle detection flag */ + pThis->fIdle = true; + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsReset(&g_PhysReadState); +# endif + + if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)) + { + /* Frame boundary, so do EOF stuff here. */ + ohciR3BumpFrameNumber(pThis); + if ( (pThis->dqic != 0x7) && (pThis->dqic != 0)) + pThis->dqic--; + + /* Clean up any URBs that have been removed. */ + ohciR3CancelOrphanedURBs(pDevIns, pThis, pThisCC); + + /* Start the next frame. */ + ohciR3StartOfFrame(pDevIns, pThis, pThisCC); + } + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsPrint(&g_PhysReadState); +# endif + + ohciR3Unlock(pThisCC); + return pThis->fIdle; +} + + +/** + * @interface_method_impl{VUSBIROOTHUBPORT,pfnFrameRateChanged} + */ +static DECLCALLBACK(void) ohciR3FrameRateChanged(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameRate) +{ + POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PPDMDEVINS pDevIns = pThisCC->pDevInsR3; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + Assert(u32FrameRate <= OHCI_DEFAULT_TIMER_FREQ); + + pThis->cTicksPerFrame = pThis->u64TimerHz / u32FrameRate; + if (!pThis->cTicksPerFrame) + pThis->cTicksPerFrame = 1; + pThis->cTicksPerUsbTick = pThis->u64TimerHz >= VUSB_BUS_HZ ? pThis->u64TimerHz / VUSB_BUS_HZ : 1; +} + + +/** + * Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static void ohciR3BusStart(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC) +{ + pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn); + pThis->dqic = 0x7; + + Log(("ohci: Bus started\n")); + + pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns); + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ); + AssertRC(rc); +} + + +/** + * Stop sending SOF tokens on the bus + */ +static void ohciR3BusStop(POHCICC pThisCC) +{ + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, 0); + AssertRC(rc); + pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn); +} + + +/** + * Move in to resume state + */ +static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, bool fHardware) +{ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= OHCI_USB_RESUME; + + LogFunc(("fHardware=%RTbool RWE=%s\n", + fHardware, (pThis->ctl & OHCI_CTL_RWE) ? "on" : "off")); + + if (fHardware && (pThis->ctl & OHCI_CTL_RWE)) + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_RESUME_DETECT); + + ohciR3BusStart(pDevIns, pThis, pThisCC); +} + + +/* Power a port up or down */ +static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp) +{ + POHCIHUBPORT pPort = &pRh->aPorts[iPort]; + bool fOldPPS = !!(pPort->fReg & OHCI_PORT_PPS); + + LogFlowFunc(("iPort=%u fPowerUp=%RTbool\n", iPort, fPowerUp)); + + if (fPowerUp) + { + /* power up */ + if (pPort->fAttached) + pPort->fReg |= OHCI_PORT_CCS; + if (pPort->fReg & OHCI_PORT_CCS) + pPort->fReg |= OHCI_PORT_PPS; + if (pPort->fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); + } + else + { + /* power down */ + pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS); + if (pPort->fAttached && fOldPPS) + VUSBIRhDevPowerOff(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort)); + } +} + +#endif /* IN_RING3 */ + +/** + * Read the HcRevision register. + */ +static VBOXSTRICTRC HcRevision_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + Log2(("HcRevision_r() -> 0x10\n")); + *pu32Value = 0x10; /* OHCI revision 1.0, no emulation. */ + return VINF_SUCCESS; +} + +/** + * Write to the HcRevision register. + */ +static VBOXSTRICTRC HcRevision_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF(pDevIns, pThis, iReg, u32Value); + Log2(("HcRevision_w(%#010x) - denied\n", u32Value)); + ASSERT_GUEST_MSG_FAILED(("Invalid operation!!! u32Value=%#010x\n", u32Value)); + return VINF_SUCCESS; +} + +/** + * Read the HcControl register. + */ +static VBOXSTRICTRC HcControl_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t ctl = pThis->ctl; + Log2(("HcControl_r -> %#010x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1)); + *pu32Value = ctl; + return VINF_SUCCESS; +} + +/** + * Write the HcControl register. + */ +static VBOXSTRICTRC HcControl_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + + /* log it. */ + uint32_t chg = pThis->ctl ^ val; NOREF(chg); + Log2(("HcControl_w(%#010x) => %sCBSR=%d %sPLE=%d %sIE=%d %sCLE=%d %sBLE=%d %sHCFS=%#x %sIR=%d %sRWC=%d %sRWE=%d\n", + val, + chg & 3 ? "*" : "", val & 3, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (val >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (val >> 5) & 1, + chg & (3 << 6)? "*" : "", (val >> 6) & 3, + chg & RT_BIT(8) ? "*" : "", (val >> 8) & 1, + chg & RT_BIT(9) ? "*" : "", (val >> 9) & 1, + chg & RT_BIT(10) ? "*" : "", (val >> 10) & 1)); + if (val & ~0x07ff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x07ff)); + + /* see what changed and take action on that. */ + uint32_t old_state = pThis->ctl & OHCI_CTL_HCFS; + uint32_t new_state = val & OHCI_CTL_HCFS; + +#ifdef IN_RING3 + pThis->ctl = val; + if (new_state != old_state) + { + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + switch (new_state) + { + case OHCI_USB_OPERATIONAL: + LogRel(("OHCI: USB Operational\n")); + ohciR3BusStart(pDevIns, pThis, pThisCC); + break; + case OHCI_USB_SUSPEND: + ohciR3BusStop(pThisCC); + LogRel(("OHCI: USB Suspended\n")); + break; + case OHCI_USB_RESUME: + LogRel(("OHCI: USB Resume\n")); + ohciR3BusResume(pDevIns, pThis, pThisCC, false /* not hardware */); + break; + case OHCI_USB_RESET: + { + LogRel(("OHCI: USB Reset\n")); + ohciR3BusStop(pThisCC); + /** @todo This should probably do a real reset, but we don't implement + * that correctly in the roothub reset callback yet. check it's + * comments and argument for more details. */ + pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, false /* don't do a real reset */); + break; + } + } + } +#else /* !IN_RING3 */ + RT_NOREF(pDevIns); + if ( new_state != old_state ) + { + Log2(("HcControl_w: state changed -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->ctl = val; +#endif /* !IN_RING3 */ + + return VINF_SUCCESS; +} + +/** + * Read the HcCommandStatus register. + */ +static VBOXSTRICTRC HcCommandStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t status = pThis->status; + Log2(("HcCommandStatus_r() -> %#010x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3)); + *pu32Value = status; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcCommandStatus register. + */ +static VBOXSTRICTRC HcCommandStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + + /* log */ + uint32_t chg = pThis->status ^ val; NOREF(chg); + Log2(("HcCommandStatus_w(%#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)? "!!!":"", (pThis->status >> 16) & 3)); + if (val & ~0x0003000f) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x0003000f)); + + /* SOC is read-only */ + val = (val & ~OHCI_STATUS_SOC); + +#ifdef IN_RING3 + /* "bits written as '0' remain unchanged in the register" */ + pThis->status |= val; + if (pThis->status & OHCI_STATUS_HCR) + { + LogRel(("OHCI: Software reset\n")); + ohciR3DoReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, POHCICC), OHCI_USB_SUSPEND, false /* N/A */); + } +#else + if ((pThis->status | val) & OHCI_STATUS_HCR) + { + LogFlow(("HcCommandStatus_w: reset -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->status |= val; +#endif + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptStatus register. + */ +static VBOXSTRICTRC HcInterruptStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr_status; + Log2(("HcInterruptStatus_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcInterruptStatus register. + */ +static VBOXSTRICTRC HcInterruptStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + + uint32_t res = pThis->intr_status & ~val; + uint32_t chg = pThis->intr_status ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptStatus_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30)? "*" : "", (res >> 30) & 1)); + if ( (val & ~0xc000007f) + && val != 0xffffffff /* ignore clear-all-like requests from xp. */) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xc000007f)); + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + pThis->intr_status &= ~val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptStatus_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptEnable register + */ +static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr; + Log2(("HcInterruptEnable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptEnable register. + */ +static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + uint32_t res = pThis->intr | val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptEnable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + if (val & ~0xc000007f) + Log2(("Uknown bits %#x are set!!!\n", val & ~0xc000007f)); + + pThis->intr |= val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Reads the HcInterruptDisable register. + */ +static VBOXSTRICTRC HcInterruptDisable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#if 1 /** @todo r=bird: "On read, the current value of the HcInterruptEnable register is returned." */ + uint32_t val = pThis->intr; +#else /* old code. */ + uint32_t val = ~pThis->intr; +#endif + Log2(("HcInterruptDisable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptDisable register. + */ +static VBOXSTRICTRC HcInterruptDisable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + uint32_t res = pThis->intr & ~val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptDisable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + /* Don't bitch about invalid bits here since it makes sense to disable + * interrupts you don't know about. */ + + pThis->intr &= ~val; + ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptDisable_w"); + PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcHCCA register (Host Controller Communications Area physical address). + */ +static VBOXSTRICTRC HcHCCA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcHCCA_r() -> %#010x\n", pThis->hcca)); + *pu32Value = pThis->hcca; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcHCCA register (Host Controller Communications Area physical address). + */ +static VBOXSTRICTRC HcHCCA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t Value) +{ + Log2(("HcHCCA_w(%#010x) - old=%#010x new=%#010x\n", Value, pThis->hcca, Value & OHCI_HCCA_MASK)); + pThis->hcca = Value & OHCI_HCCA_MASK; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodCurrentED register. + */ +static VBOXSTRICTRC HcPeriodCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcPeriodCurrentED_r() -> %#010x\n", pThis->per_cur)); + *pu32Value = pThis->per_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodCurrentED register. + */ +static VBOXSTRICTRC HcPeriodCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log(("HcPeriodCurrentED_w(%#010x) - old=%#010x new=%#010x (This is a read only register, only the linux guys don't respect that!)\n", + val, pThis->per_cur, val & ~7)); + //AssertMsgFailed(("HCD (Host Controller Driver) should not write to HcPeriodCurrentED! val=%#010x (old=%#010x)\n", val, pThis->per_cur)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->per_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlHeadED register. + */ +static VBOXSTRICTRC HcControlHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlHeadED_r() -> %#010x\n", pThis->ctrl_head)); + *pu32Value = pThis->ctrl_head; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlHeadED register. + */ +static VBOXSTRICTRC HcControlHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_head = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlCurrentED register. + */ +static VBOXSTRICTRC HcControlCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlCurrentED_r() -> %#010x\n", pThis->ctrl_cur)); + *pu32Value = pThis->ctrl_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlCurrentED register. + */ +static VBOXSTRICTRC HcControlCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_CLE), ("Illegal write! HcControl.ControlListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkHeadED register. + */ +static VBOXSTRICTRC HcBulkHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkHeadED_r() -> %#010x\n", pThis->bulk_head)); + *pu32Value = pThis->bulk_head; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkHeadED register. + */ +static VBOXSTRICTRC HcBulkHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_head = val & ~7; /** @todo The ATI OHCI controller on my machine enforces 16-byte address alignment. */ + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkCurrentED register. + */ +static VBOXSTRICTRC HcBulkCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkCurrentED_r() -> %#010x\n", pThis->bulk_cur)); + *pu32Value = pThis->bulk_cur; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkCurrentED register. + */ +static VBOXSTRICTRC HcBulkCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_BLE), ("Illegal write! HcControl.BulkListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_cur = val & ~7; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + + +/** + * Read the HcDoneHead register. + */ +static VBOXSTRICTRC HcDoneHead_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcDoneHead_r() -> 0x%#08x\n", pThis->done)); + *pu32Value = pThis->done; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcDoneHead register. + */ +static VBOXSTRICTRC HcDoneHead_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcDoneHead_w(0x%#08x) - denied!!!\n", val)); + /*AssertMsgFailed(("Illegal operation!!! val=%#010x\n", val)); - OS/2 does this */ + return VINF_SUCCESS; +} + + +/** + * Read the HcFmInterval (Fm=Frame) register. + */ +static VBOXSTRICTRC HcFmInterval_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = (pThis->fit << 31) | (pThis->fsmps << 16) | (pThis->fi); + Log2(("HcFmInterval_r() -> 0x%#08x - FI=%d FSMPS=%d FIT=%d\n", + val, val & 0x3fff, (val >> 16) & 0x7fff, val >> 31)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcFmInterval (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmInterval_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + + /* log */ + uint32_t chg = val ^ ((pThis->fit << 31) | (pThis->fsmps << 16) | pThis->fi); NOREF(chg); + Log2(("HcFmInterval_w(%#010x) => %sFI=%d %sFSMPS=%d %sFIT=%d\n", + val, + chg & 0x00003fff ? "*" : "", val & 0x3fff, + chg & 0x7fff0000 ? "*" : "", (val >> 16) & 0x7fff, + chg >> 31 ? "*" : "", (val >> 31) & 1)); + if (pThis->fi != (val & OHCI_FMI_FI)) + { + Log(("ohci: FrameInterval: %#010x -> %#010x\n", pThis->fi, val & OHCI_FMI_FI)); + AssertMsg(pThis->fit != ((val >> OHCI_FMI_FIT_SHIFT) & 1), ("HCD didn't toggle the FIT bit!!!\n")); + } + + /* update */ + pThis->fi = val & OHCI_FMI_FI; + pThis->fit = (val & OHCI_FMI_FIT) >> OHCI_FMI_FIT_SHIFT; + pThis->fsmps = (val & OHCI_FMI_FSMPS) >> OHCI_FMI_FSMPS_SHIFT; + return VINF_SUCCESS; +} + +/** + * Read the HcFmRemaining (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmRemaining_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(iReg); + uint32_t Value = pThis->frt << 31; + if ((pThis->ctl & OHCI_CTL_HCFS) == OHCI_USB_OPERATIONAL) + { + /* + * Being in USB operational state guarantees SofTime was set already. + */ + uint64_t tks = PDMDevHlpTMTimeVirtGet(pDevIns) - pThis->SofTime; + if (tks < pThis->cTicksPerFrame) /* avoid muldiv if possible */ + { + uint16_t fr; + tks = ASMMultU64ByU32DivByU32(1, tks, pThis->cTicksPerUsbTick); + fr = (uint16_t)(pThis->fi - tks); + Value |= fr; + } + } + + Log2(("HcFmRemaining_r() -> %#010x - FR=%d FRT=%d\n", Value, Value & 0x3fff, Value >> 31)); + *pu32Value = Value; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmRemaining (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmRemaining_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcFmRemaining_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcFmNumber (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmNumber_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t val = (uint16_t)pThis->HcFmNumber; + Log2(("HcFmNumber_r() -> %#010x - FN=%#x(%d) (32-bit=%#x(%d))\n", val, val, val, pThis->HcFmNumber, pThis->HcFmNumber)); + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmNumber (Fm = Frame) register. + */ +static VBOXSTRICTRC HcFmNumber_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcFmNumber_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static VBOXSTRICTRC HcPeriodicStart_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + Log2(("HcPeriodicStart_r() -> %#010x - PS=%d\n", pThis->pstart, pThis->pstart & 0x3fff)); + *pu32Value = pThis->pstart; + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static VBOXSTRICTRC HcPeriodicStart_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + Log2(("HcPeriodicStart_w(%#010x) => PS=%d\n", val, val & 0x3fff)); + if (val & ~0x3fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x3fff)); + pThis->pstart = val; /** @todo r=bird: should we support setting the other bits? */ + return VINF_SUCCESS; +} + +/** + * Read the HcLSThreshold register. + */ +static VBOXSTRICTRC HcLSThreshold_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + Log2(("HcLSThreshold_r() -> %#010x\n", OHCI_LS_THRESH)); + *pu32Value = OHCI_LS_THRESH; + return VINF_SUCCESS; +} + +/** + * Write to the HcLSThreshold register. + * + * Docs are inconsistent here: + * + * "Neither the Host Controller nor the Host Controller Driver are allowed to change this value." + * + * "This value is calculated by HCD with the consideration of transmission and setup overhead." + * + * The register is marked "R/W" the HCD column. + * + */ +static VBOXSTRICTRC HcLSThreshold_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, pThis, iReg, val); + Log2(("HcLSThreshold_w(%#010x) => LST=0x%03x(%d)\n", val, val & 0x0fff, val & 0x0fff)); + AssertMsg(val == OHCI_LS_THRESH, + ("HCD tried to write bad LS threshold: 0x%x (see function header)\n", val)); + /** @todo the HCD can change this. */ + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorA register. + */ +static VBOXSTRICTRC HcRhDescriptorA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + uint32_t val = pThis->RootHub.desc_a; +#if 0 /* annoying */ + Log2(("HcRhDescriptorA_r() -> %#010x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTGT=%#x\n", + val, val & 0xff, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, + (val >> 12) & 1, (val >> 24) & 0xff)); +#endif + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorA register. + */ +static VBOXSTRICTRC HcRhDescriptorA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + uint32_t chg = val ^ pThis->RootHub.desc_a; NOREF(chg); + Log2(("HcRhDescriptorA_w(%#010x) => %sNDP=%d %sPSM=%d %sNPS=%d %sDT=%d %sOCPM=%d %sNOCP=%d %sPOTGT=%#x - %sPowerSwitching Set%sPower\n", + val, + chg & 0xff ?"!!!": "", val & 0xff, + (chg >> 8) & 1 ? "*" : "", (val >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (val >> 9) & 1, + (chg >> 10) & 1 ?"!!!": "", 0, + (chg >> 11) & 1 ? "*" : "", (val >> 11) & 1, + (chg >> 12) & 1 ? "*" : "", (val >> 12) & 1, + (chg >> 24)&0xff? "*" : "", (val >> 24) & 0xff, + val & OHCI_RHA_NPS ? "No" : "", + val & OHCI_RHA_PSM ? "Port" : "Global")); + if (val & ~0xff001fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xff001fff)); + + + if ((val & (OHCI_RHA_NDP | OHCI_RHA_DT)) != OHCI_NDP_CFG(pThis)) + { + Log(("ohci: invalid write to NDP or DT in roothub descriptor A!!! val=0x%.8x\n", val)); + val &= ~(OHCI_RHA_NDP | OHCI_RHA_DT); + val |= OHCI_NDP_CFG(pThis); + } + + pThis->RootHub.desc_a = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorB register. + */ +static VBOXSTRICTRC HcRhDescriptorB_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.desc_b; + Log2(("HcRhDescriptorB_r() -> %#010x - DR=0x%04x PPCM=0x%04x\n", + val, val & 0xffff, val >> 16)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorB register. + */ +static VBOXSTRICTRC HcRhDescriptorB_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + uint32_t chg = pThis->RootHub.desc_b ^ val; NOREF(chg); + Log2(("HcRhDescriptorB_w(%#010x) => %sDR=0x%04x %sPPCM=0x%04x\n", + val, + chg & 0xffff ? "!!!" : "", val & 0xffff, + chg >> 16 ? "!!!" : "", val >> 16)); + + if ( pThis->RootHub.desc_b != val ) + Log(("ohci: unsupported write to root descriptor B!!! 0x%.8x -> 0x%.8x\n", pThis->RootHub.desc_b, val)); + pThis->RootHub.desc_b = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhStatus (Rh = Root Hub) register. + */ +static VBOXSTRICTRC HcRhStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.status; + if (val & (OHCI_RHS_LPSC | OHCI_RHS_OCIC)) + Log2(("HcRhStatus_r() -> %#010x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhStatus (Rh = Root Hub) register. + */ +static VBOXSTRICTRC HcRhStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + + /* log */ + uint32_t old = pThis->RootHub.status; + uint32_t chg; + if (val & ~0x80038003) + Log2(("HcRhStatus_w: Unknown bits %#x are set!!!\n", val & ~0x80038003)); + if ( (val & OHCI_RHS_LPSC) && (val & OHCI_RHS_LPS) ) + Log2(("HcRhStatus_w: Warning both CGP and SGP are set! (Clear/Set Global Power)\n")); + if ( (val & OHCI_RHS_DRWE) && (val & OHCI_RHS_CRWE) ) + Log2(("HcRhStatus_w: Warning both CRWE and SRWE are set! (Clear/Set Remote Wakeup Enable)\n")); + + + /* write 1 to clear OCIC */ + if ( val & OHCI_RHS_OCIC ) + pThis->RootHub.status &= ~OHCI_RHS_OCIC; + + /* SetGlobalPower */ + if ( val & OHCI_RHS_LPSC ) + { + unsigned i; + Log2(("ohci: global power up\n")); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + } + + /* ClearGlobalPower */ + if ( val & OHCI_RHS_LPS ) + { + unsigned i; + Log2(("ohci: global power down\n")); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); + } + + if ( val & OHCI_RHS_DRWE ) + pThis->RootHub.status |= OHCI_RHS_DRWE; + + if ( val & OHCI_RHS_CRWE ) + pThis->RootHub.status &= ~OHCI_RHS_DRWE; + + chg = pThis->RootHub.status ^ old; + Log2(("HcRhStatus_w(%#010x) => %sCGP=%d %sOCI=%d %sSRWE=%d %sSGP=%d %sOCIC=%d %sCRWE=%d\n", + val, + chg & 1 ? "*" : "", val & 1, + (chg >> 1) & 1 ?"!!!": "", (val >> 1) & 1, + (chg >> 15) & 1 ? "*" : "", (val >> 15) & 1, + (chg >> 16) & 1 ? "*" : "", (val >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (val >> 17) & 1, + (chg >> 31) & 1 ? "*" : "", (val >> 31) & 1)); + RT_NOREF(pDevIns, iReg); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns, pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Read the HcRhPortStatus register of a port. + */ +static VBOXSTRICTRC HcRhPortStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + const unsigned i = iReg - 21; + uint32_t val = pThis->RootHub.aPorts[i].fReg | OHCI_PORT_PPS; /* PortPowerStatus: see todo on power in _w function. */ + if (val & OHCI_PORT_PRS) + { +#ifdef IN_RING3 + RTThreadYield(); +#else + Log2(("HcRhPortStatus_r: yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + if (val & (OHCI_PORT_PRS | OHCI_PORT_CLEAR_CHANGE_MASK)) + Log2(("HcRhPortStatus_r(): port %u: -> %#010x - CCS=%d PES=%d PSS=%d POCI=%d RRS=%d PPS=%d LSDA=%d CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1)); + *pu32Value = val; + RT_NOREF(pDevIns); + return VINF_SUCCESS; +} + +#ifdef IN_RING3 +/** + * Completion callback for the vusb_dev_reset() operation. + * @thread EMT. + */ +static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + RT_NOREF(pDev); + + Assert(uPort >= 1); + PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser; + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + POHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort - 1]; + + if (RT_SUCCESS(rc)) + { + /* + * Successful reset. + */ + Log2(("ohciR3PortResetDone: Reset completed.\n")); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC); + pPort->fReg |= OHCI_PORT_PES | OHCI_PORT_PRSC; + } + else + { + /* desperate measures. */ + if ( pPort->fAttached + && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED) + { + /* + * Damn, something weird happened during reset. We'll pretend the user did an + * incredible fast reconnect or something. (probably not gonna work) + */ + Log2(("ohciR3PortResetDone: The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc)); + pPort->fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + } + else + { + /* + * The device have / will be disconnected. + */ + Log2(("ohciR3PortResetDone: Disconnected (rc=%Rrc)!!!\n", rc)); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC | OHCI_PORT_PRSC); + pPort->fReg |= OHCI_PORT_CSC; + } + } + + /* Raise roothub status change interrupt. */ + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_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 ohciR3RhPortSetIfConnected(PPDMDEVINS pDevIns, POHCI pThis, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * If CurrentConnectStatus is cleared we set ConnectStatusChange. + */ + if (!(pThis->RootHub.aPorts[iPort].fReg & OHCI_PORT_CCS)) + { + pThis->RootHub.aPorts[iPort].fReg |= OHCI_PORT_CSC; + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + return false; + } + + bool fRc = !(pThis->RootHub.aPorts[iPort].fReg & fValue); + + /* set the bit */ + pThis->RootHub.aPorts[iPort].fReg |= fValue; + + return fRc; +} +#endif /* IN_RING3 */ + +/** + * Write to the HcRhPortStatus register of a port. + */ +static VBOXSTRICTRC HcRhPortStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + const unsigned i = iReg - 21; + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + POHCIHUBPORT p = &pThis->RootHub.aPorts[i]; + uint32_t old_state = p->fReg; + +# ifdef LOG_ENABLED + /* + * Log it. + */ + static const char *apszCmdNames[32] = + { + "ClearPortEnable", "SetPortEnable", "SetPortSuspend", "!!!ClearSuspendStatus", + "SetPortReset", "!!!5", "!!!6", "!!!7", + "SetPortPower", "ClearPortPower", "!!!10", "!!!11", + "!!!12", "!!!13", "!!!14", "!!!15", + "ClearCSC", "ClearPESC", "ClearPSSC", "ClearOCIC", + "ClearPRSC", "!!!21", "!!!22", "!!!23", + "!!!24", "!!!25", "!!!26", "!!!27", + "!!!28", "!!!29", "!!!30", "!!!31" + }; + Log2(("HcRhPortStatus_w(%#010x): port %u:", val, i)); + for (unsigned j = 0; j < RT_ELEMENTS(apszCmdNames); j++) + if (val & (1 << j)) + Log2((" %s", apszCmdNames[j])); + Log2(("\n")); +# endif + + /* Write to clear any of the change bits: CSC, PESC, PSSC, OCIC and PRSC */ + if (val & OHCI_PORT_CLEAR_CHANGE_MASK) + p->fReg &= ~(val & OHCI_PORT_CLEAR_CHANGE_MASK); + + if (val & OHCI_PORT_CLRPE) + { + p->fReg &= ~OHCI_PORT_PES; + Log2(("HcRhPortStatus_w(): port %u: DISABLE\n", i)); + } + + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PES)) + Log2(("HcRhPortStatus_w(): port %u: ENABLE\n", i)); + + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PSS)) + Log2(("HcRhPortStatus_w(): port %u: SUSPEND - not implemented correctly!!!\n", i)); + + if (val & OHCI_PORT_PRS) + { + if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PRS)) + { + PVM pVM = PDMDevHlpGetVM(pDevIns); + p->fReg &= ~OHCI_PORT_PRSC; + VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, + ohciR3PortResetDone, pDevIns, pVM); + } + else if (p->fReg & OHCI_PORT_PRS) + { + /* the guest is getting impatient. */ + Log2(("HcRhPortStatus_w(): port %u: Impatient guest!\n", i)); + RTThreadYield(); + } + } + + if (!(pThis->RootHub.desc_a & OHCI_RHA_NPS)) + { + /** @todo To implement per-device power-switching + * we need to check PortPowerControlMask to make + * sure it isn't gang powered + */ + if (val & OHCI_PORT_CLRPP) + ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */); + if (val & OHCI_PORT_PPS) + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + } + + /** @todo r=frank: ClearSuspendStatus. Timing? */ + if (val & OHCI_PORT_CLRSS) + { + ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */); + pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS; + pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC; + ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + } + + if (p->fReg != old_state) + { + uint32_t res = p->fReg; + uint32_t chg = res ^ old_state; NOREF(chg); + Log2(("HcRhPortStatus_w(%#010x): port %u: => %sCCS=%d %sPES=%d %sPSS=%d %sPOCI=%d %sRRS=%d %sPPS=%d %sLSDA=%d %sCSC=%d %sPESC=%d %sPSSC=%d %sOCIC=%d %sPRSC=%d\n", + val, i, + chg & 1 ? "*" : "", res & 1, + (chg >> 1) & 1 ? "*" : "", (res >> 1) & 1, + (chg >> 2) & 1 ? "*" : "", (res >> 2) & 1, + (chg >> 3) & 1 ? "*" : "", (res >> 3) & 1, + (chg >> 4) & 1 ? "*" : "", (res >> 4) & 1, + (chg >> 8) & 1 ? "*" : "", (res >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (res >> 9) & 1, + (chg >> 16) & 1 ? "*" : "", (res >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (res >> 17) & 1, + (chg >> 18) & 1 ? "*" : "", (res >> 18) & 1, + (chg >> 19) & 1 ? "*" : "", (res >> 19) & 1, + (chg >> 20) & 1 ? "*" : "", (res >> 20) & 1)); + } + RT_NOREF(pDevIns); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns, pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Register descriptor table + */ +static const OHCIOPREG g_aOpRegs[] = +{ + { "HcRevision", HcRevision_r, HcRevision_w }, /* 0 */ + { "HcControl", HcControl_r, HcControl_w }, /* 1 */ + { "HcCommandStatus", HcCommandStatus_r, HcCommandStatus_w }, /* 2 */ + { "HcInterruptStatus", HcInterruptStatus_r, HcInterruptStatus_w }, /* 3 */ + { "HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w }, /* 4 */ + { "HcInterruptDisable", HcInterruptDisable_r, HcInterruptDisable_w }, /* 5 */ + { "HcHCCA", HcHCCA_r, HcHCCA_w }, /* 6 */ + { "HcPeriodCurrentED", HcPeriodCurrentED_r, HcPeriodCurrentED_w }, /* 7 */ + { "HcControlHeadED", HcControlHeadED_r, HcControlHeadED_w }, /* 8 */ + { "HcControlCurrentED", HcControlCurrentED_r, HcControlCurrentED_w }, /* 9 */ + { "HcBulkHeadED", HcBulkHeadED_r, HcBulkHeadED_w }, /* 10 */ + { "HcBulkCurrentED", HcBulkCurrentED_r, HcBulkCurrentED_w }, /* 11 */ + { "HcDoneHead", HcDoneHead_r, HcDoneHead_w }, /* 12 */ + { "HcFmInterval", HcFmInterval_r, HcFmInterval_w }, /* 13 */ + { "HcFmRemaining", HcFmRemaining_r, HcFmRemaining_w }, /* 14 */ + { "HcFmNumber", HcFmNumber_r, HcFmNumber_w }, /* 15 */ + { "HcPeriodicStart", HcPeriodicStart_r, HcPeriodicStart_w }, /* 16 */ + { "HcLSThreshold", HcLSThreshold_r, HcLSThreshold_w }, /* 17 */ + { "HcRhDescriptorA", HcRhDescriptorA_r, HcRhDescriptorA_w }, /* 18 */ + { "HcRhDescriptorB", HcRhDescriptorB_r, HcRhDescriptorB_w }, /* 19 */ + { "HcRhStatus", HcRhStatus_r, HcRhStatus_w }, /* 20 */ + + /* The number of port status register depends on the definition + * of OHCI_NDP_MAX macro + */ + { "HcRhPortStatus[0]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 21 */ + { "HcRhPortStatus[1]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 22 */ + { "HcRhPortStatus[2]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 23 */ + { "HcRhPortStatus[3]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 24 */ + { "HcRhPortStatus[4]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 25 */ + { "HcRhPortStatus[5]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 26 */ + { "HcRhPortStatus[6]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 27 */ + { "HcRhPortStatus[7]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 28 */ + { "HcRhPortStatus[8]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 29 */ + { "HcRhPortStatus[9]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 30 */ + { "HcRhPortStatus[10]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 31 */ + { "HcRhPortStatus[11]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 32 */ + { "HcRhPortStatus[12]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 33 */ + { "HcRhPortStatus[13]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 34 */ + { "HcRhPortStatus[14]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 35 */ +}; + +/* 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 22 and 36 registers. + */ +#define NUM_OP_REGS(pohci) (21 + OHCI_NDP_CFG(pohci)) + +AssertCompile(RT_ELEMENTS(g_aOpRegs) > 21); +AssertCompile(RT_ELEMENTS(g_aOpRegs) <= 36); + +/** + * @callback_method_impl{FNIOMMMIONEWREAD} + */ +static DECLCALLBACK(VBOXSTRICTRC) ohciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + RT_NOREF(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_READ_DWORD works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + const uint32_t iReg = off >> 2; + if (iReg < NUM_OP_REGS(pThis)) + rc = g_aOpRegs[iReg].pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv); + else + { + Log(("ohci: Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE} + */ +static DECLCALLBACK(VBOXSTRICTRC) ohciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + RT_NOREF(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_WRITE_DWORD_ZEROED works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rc; + const uint32_t iReg = off >> 2; + if (iReg < NUM_OP_REGS(pThis)) + rc = g_aOpRegs[iReg].pfnWrite(pDevIns, pThis, iReg, *(uint32_t const *)pv); + else + { + Log(("ohci: Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_SUCCESS; + } + return rc; +} + +#ifdef IN_RING3 + +/** + * Saves the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to save the state to. + */ +static DECLCALLBACK(int) ohciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlow(("ohciR3SaveExec:\n")); + + int rc = pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + AssertRCReturn(rc, rc); + + /* Save the periodic frame rate so we can we can tell if the bus was started or not when restoring. */ + return pDevIns->pHlpR3->pfnSSMPutU32(pSSM, VUSBIRhGetPeriodicFrameRate(pThisCC->RootHub.pIRhConn)); +} + + +/** + * Loads the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to the saved state. + * @param uVersion The data unit version number. + * @param uPass The data pass. + */ +static DECLCALLBACK(int) ohciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + LogFlow(("ohciR3LoadExec:\n")); + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + if (uVersion >= OHCI_SAVED_STATE_VERSION_EOF_TIMER) + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + else if (uVersion == OHCI_SAVED_STATE_VERSION_8PORTS) + rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields8Ports[0], NULL); + else + AssertMsgFailedReturn(("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + AssertRCReturn(rc, rc); + + /* + * Get the frame rate / started indicator. + * + * For older versions there is a timer saved here. We'll skip it and deduce + * the periodic frame rate from the host controller functional state. + */ + if (uVersion > OHCI_SAVED_STATE_VERSION_EOF_TIMER) + { + rc = pHlp->pfnSSMGetU32(pSSM, &pThisCC->uRestoredPeriodicFrameRate); + AssertRCReturn(rc, rc); + } + else + { + rc = pHlp->pfnSSMSkipToEndOfUnit(pSSM); + AssertRCReturn(rc, rc); + + uint32_t fHcfs = pThis->ctl & OHCI_CTL_HCFS; + switch (fHcfs) + { + case OHCI_USB_OPERATIONAL: + case OHCI_USB_RESUME: + pThisCC->uRestoredPeriodicFrameRate = OHCI_DEFAULT_TIMER_FREQ; + break; + default: + pThisCC->uRestoredPeriodicFrameRate = 0; + break; + } + } + + /** @todo could we restore the frame rate here instead of in ohciR3Resume? */ + return VINF_SUCCESS; +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Reset(PPDMDEVINS pDevIns) +{ + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlow(("ohciR3Reset:\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. + */ + ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, true /* reset devices */); +} + + +/** + * Resume notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Resume(PPDMDEVINS pDevIns) +{ + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + LogFlowFunc(("\n")); + + /* Restart the frame thread if it was active when the loaded state was saved. */ + uint32_t uRestoredPeriodicFR = pThisCC->uRestoredPeriodicFrameRate; + pThisCC->uRestoredPeriodicFrameRate = 0; + if (uRestoredPeriodicFR) + { + LogFlowFunc(("Bus was active, enable periodic frame processing (rate: %u)\n", uRestoredPeriodicFR)); + int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, uRestoredPeriodicFR); + AssertRC(rc); + } +} + + +/** + * Info handler, device version. Dumps OHCI control registers. + * + * @param pDevIns Device instance which registered the info. + * @param pHlp Callback functions for doing output. + * @param pszArgs Argument string. Optional and specific to the handler. + */ +static DECLCALLBACK(void) ohciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + uint32_t val, ctl, status; + + /* Control register */ + ctl = pThis->ctl; + pHlp->pfnPrintf(pHlp, "HcControl: %08x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1); + + /* Command status register */ + status = pThis->status; + pHlp->pfnPrintf(pHlp, "HcCommandStatus: %08x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3); + + /* Interrupt status register */ + val = pThis->intr_status; + pHlp->pfnPrintf(pHlp, "HcInterruptStatus: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1); + + /* Interrupt enable register */ + val = pThis->intr; + pHlp->pfnPrintf(pHlp, "HcInterruptEnable: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1); + + /* HCCA address register */ + pHlp->pfnPrintf(pHlp, "HcHCCA: %08x\n", pThis->hcca); + + /* Current periodic ED register */ + pHlp->pfnPrintf(pHlp, "HcPeriodCurrentED: %08x\n", pThis->per_cur); + + /* Control ED registers */ + pHlp->pfnPrintf(pHlp, "HcControlHeadED: %08x\n", pThis->ctrl_head); + pHlp->pfnPrintf(pHlp, "HcControlCurrentED: %08x\n", pThis->ctrl_cur); + + /* Bulk ED registers */ + pHlp->pfnPrintf(pHlp, "HcBulkHeadED: %08x\n", pThis->bulk_head); + pHlp->pfnPrintf(pHlp, "HcBulkCurrentED: %08x\n", pThis->bulk_cur); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Root hub descriptor A */ + val = pThis->RootHub.desc_a; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorA: %08x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTPGT=%d\n", + val, (uint8_t)val, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, (val >> 12) & 1, (uint8_t)(val >> 24)); + + /* Root hub descriptor B */ + val = pThis->RootHub.desc_b; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorB: %08x - DR=%#04x PPCM=%#04x\n", val, (uint16_t)val, (uint16_t)(val >> 16)); + + /* Root hub status register */ + val = pThis->RootHub.status; + pHlp->pfnPrintf(pHlp, "HcRhStatus: %08x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1); + + /* Port status registers */ + for (unsigned i = 0; i < OHCI_NDP_CFG(pThis); ++i) + { + val = pThis->RootHub.aPorts[i].fReg; + pHlp->pfnPrintf(pHlp, "HcRhPortStatus%02d: CCS=%d PES =%d PSS =%d POCI=%d PRS =%d PPS=%d LSDA=%d\n" + " %08x - CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val & 1, (val >> 1) & 1, (val >> 2) & 1,(val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + val, (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1); + } +} + + +/** + * Destruct a device instance. + * + * Most VM resources are freed by the VM. This callback is provided so that any non-VM + * resources can be freed correctly. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(int) ohciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC); + + 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,OHCI constructor} + */ +static DECLCALLBACK(int) ohciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + POHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCIR3); + + /* + * Init instance data. + */ + pThisCC->pDevInsR3 = pDevIns; + + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev); + + PDMPciDevSetVendorId(pPciDev, 0x106b); + PDMPciDevSetDeviceId(pPciDev, 0x003f); + PDMPciDevSetClassProg(pPciDev, 0x10); /* OHCI */ + 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 + + pThisCC->RootHub.pOhci = pThis; + pThisCC->RootHub.IBase.pfnQueryInterface = ohciR3RhQueryInterface; + pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ohciR3RhGetAvailablePorts; + pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ohciR3RhGetUSBVersions; + pThisCC->RootHub.IRhPort.pfnAttach = ohciR3RhAttach; + pThisCC->RootHub.IRhPort.pfnDetach = ohciR3RhDetach; + pThisCC->RootHub.IRhPort.pfnReset = ohciR3RhReset; + pThisCC->RootHub.IRhPort.pfnXferCompletion = ohciR3RhXferCompletion; + pThisCC->RootHub.IRhPort.pfnXferError = ohciR3RhXferError; + pThisCC->RootHub.IRhPort.pfnStartFrame = ohciR3StartFrame; + pThisCC->RootHub.IRhPort.pfnFrameRateChanged = ohciR3FrameRateChanged; + + /* USB LED */ + pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC; + pThisCC->RootHub.ILeds.pfnQueryStatusLed = ohciR3RhQueryStatusLed; + + + /* + * Read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Ports", ""); + + /* Number of ports option. */ + uint32_t cPorts; + int rc = pDevIns->pHlpR3->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, OHCI_NDP_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, N_("OHCI configuration error: failed to read Ports as integer")); + if (cPorts == 0 || cPorts > OHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("OHCI configuration error: Ports must be in range [%u,%u]"), + 1, OHCI_NDP_MAX); + + /* Store the configured NDP; it will be used everywhere else from now on. */ + pThis->RootHub.desc_a = cPorts; + + /* + * 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, 4096, PCI_ADDRESS_SPACE_MEM, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE, "USB OHCI", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + NULL, ohciR3SaveExec, NULL, + NULL, ohciR3LoadExec, NULL); + AssertRCReturn(rc, 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 if (rc != VERR_PDM_NO_ATTACHED_DRIVER) + { + AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc)); + return 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_("OHCI: Failed to set URB parameters")); + + /* + * Take down the virtual clock frequence for use in ohciR3FrameRateChanged(). + * (Used to be a timer, thus the name.) + */ + pThis->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns); + + /* + * Critical sections: explain + */ + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "OHCI#%uIrq", iInstance); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section")); + + rc = RTCritSectInit(&pThisCC->CritSect); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section")); + + /* + * Do a hardware reset. + */ + ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, false /* don't reset devices */); + +# ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled)."); +# endif + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "ohci", "OHCI control registers.", ohciR3InfoRegs); + +# if 0/*def DEBUG_bird*/ +// g_fLogInterruptEPs = true; + g_fLogControlEPs = true; + g_fLogBulkEPs = true; +# endif + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) ohciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +const PDMDEVREG g_DeviceOHCI = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "usb-ohci", + /* .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(OHCI), + /* .cbInstanceCC = */ sizeof(OHCICC), + /* .cbInstanceRC = */ 0, + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "OHCI USB controller.\n", +#if defined(IN_RING3) + /* .pszRCMod = */ "VBoxDDRC.rc", + /* .pszR0Mod = */ "VBoxDDR0.r0", + /* .pfnConstruct = */ ohciR3Construct, + /* .pfnDestruct = */ ohciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ ohciR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ ohciR3Resume, + /* .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 = */ ohciRZConstruct, + /* .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 = */ ohciRZConstruct, + /* .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 +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/Devices/USB/DevXHCI.cpp b/src/VBox/Devices/USB/DevXHCI.cpp new file mode 100644 index 00000000..ede6413c --- /dev/null +++ b/src/VBox/Devices/USB/DevXHCI.cpp @@ -0,0 +1,8295 @@ +/* $Id: DevXHCI.cpp $ */ +/** @file + * DevXHCI - eXtensible Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2012-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +/** @page pg_dev_xhci xHCI - eXtensible Host Controller Interface Emulation. + * + * This component implements an xHCI USB controller. + * + * The xHCI device is significantly different from the EHCI and OHCI + * controllers in that it is not timer driven. A worker thread is responsible + * for transferring data between xHCI and VUSB. + * + * Since there can be dozens or even hundreds of USB devices, and because USB + * transfers must share the same bus, only one worker thread is created (per + * host controller). + * + * + * The xHCI operational model is heavily based around a producer/consumer + * model utilizing rings -- Command, Event, and Transfer rings. The Event ring + * is only written by the xHC and is read-only for the HCD (Host Controller + * Driver). The Command/Transfer rings are only written by the HCD and are + * read-only for the xHC. + * + * The rings contain TRBs (Transfer Request Blocks). The TRBs represent not + * only data transfers but also commands and status information. Each type of + * ring only produces/consumes specific TRB types. + * + * When processing a ring, the xHC simply keeps advancing an internal pointer. + * For the Command/Transfer rings, the HCD uses Link TRBs to manage the ring + * storage in a fairly arbitrary manner. Since the HCD cannot write to the + * Event ring, the Event Ring Segment Table (ERST) is used to manage the ring + * storage instead. + * + * The Cycle bit is used to manage the ring buffer full/empty condition. The + * Producer and Consumer both have their own Cycle State (PCS/CCS). The Cycle + * bit of each TRB determines who owns it. The consumer only processes TRBs + * whose Cycle bit matches the CCS. HCD software typically toggles the Cycle + * bit on each pass through the ring. The Link TRB can be used to toggle the + * CCS accordingly. + * + * Multiple Transfer TRBs can be chained together (via the Chain bit) into a + * single Transfer Descriptor (TD). This provides a convenient capability for + * the HCD to turn a URB into a single TD regardless of how the URB is laid + * out in physical memory. If a transfer encounters an error or is terminated + * by a short packet, the entire TD (i.e. chain of TRBs) is retired. + * + * Note that the xHC detects and handles short packets on its own. Backends + * are always asked not to consider a short packet to be an error condition. + * + * Command and Event TRBs cannot be chained, thus an ED (Event Descriptor) + * or a Command Descriptor (CD) always consists of a single TRB. + * + * There is one Command ring per xHC, one Event ring per interrupter (one or + * more), and a potentially very large number of Transfer rings. There is a + * 1:1 mapping between Transfer Rings and USB pipes, hence each USB device + * uses 1-31 Transfer rings (at least one for the default control endpoint, + * up to 31 if all IN/OUT endpoints are used). USB 3.0 devices may also use + * up to 64K streams per endpoint, each with its Transfer ring, massively + * increasing the potential number of Transfer rings in use. + * + * When building a Transfer ring, it's possible to queue up a large number + * of TDs and as soon as the oldest ones are retired, queue up new TDs. The + * Transfer ring might thus never be empty. + * + * For tracking ring buffer position, the TRDP and TREP fields in an endpoint + * context are used. The TRDP is the 'TR Dequeue Pointer', i.e. the position + * of the next TRB to be completed. This field is visible by the HCD when the + * endpoint isn't running. It reflects TRBs completely processed by the xHC + * and hence no longer owned by the xHC. + * + * The TREP field is the 'TR Enqueue Pointer' and tracks the position of the + * next TRB to start processing (submit). This is purely internal to the + * xHC. The TREP can potentially get far ahead of the TRDP, but only in the + * part of the ring owned by the xHC (i.e. with matching DCS bit). + * + * Unlike most other xHCI data structures, transfer TRBs may describe memory + * buffers with no alignment restrictions (both starting position and size). + * In addition, there is no relationship between TRB boundaries and USB + * packet boundaries. + * + * + * Typically an event would be generated via the IOC bit (Interrupt On + * Completion) when the last TRB of a TD is completed. However, multiple IOC + * bits may be set per TD. This may be required when a TD equal or larger + * than 16MB is used, since transfer events utilize a 24-bit length field. + * + * There is also the option of using Transfer Event TRBs to report TRB + * completion. Transfer Event TRBs may be freely intermixed with transfer + * TRBs. Note that an event TRB will produce an event reporting the size of + * data transferred since the last event TRB or since the beginning of a TD. + * The xHC submits URBs such that they either comprise the entire TD or end + * at a Transfer Event TRB, thus there is no need to track the EDTLA + * separately. + * + * Transfer errors always generate events, irrespective of IOC settings. The + * xHC has always the option to generate events at implementation-specific + * points so that the HCD does not fall too far behind. + * + * Control transfers use special TDs. A Setup Stage TD consists of only a + * single Setup Stage TRB (there's no Chain bit). The optional Data Stage + * TD consists of a Data Stage TRB chained to zero or more Normal TRBs + * and/or Event Data TRBs. The Status Stage TD then consists of a Status + * Stage TRB optionally chained to an Event Data TRB. The HCD is responsible + * for building the TDs correctly. + * + * For isochronous transfers, only the first TRB of a TD is actually an + * isochronous TRB. If the TD is chained, it will contain Normal TRBs (and + * possibly Event Data TRBs). + * + * + * Isochronous transfers require multiple TDs/URBs to be in flight at a + * time. This complicates dealing with non-data TRBs (such as link or event + * data TRBs). These TRBs cannot be completed while a previous TRB is still + * in flight. They are completed either: a) when submitting URBs and there + * are no in-flight URBs, or b) just prior to completing an URB. + * + * This approach works because URBs must be completed strictly in-order. The + * TRDP and TREP determine whether there are in-flight TRBs (TREP equals + * TRDP if and only if there are no in-flight TRBs). + * + * When submitting TRBs and there is in-flight traffic, non-data TRBs must + * be examined and skipped over. Link TRBs need to be taken into account. + * + * Unfortunately, certain HCDs (looking at you, Microsoft!) violate the xHCI + * specification and make assumptions about how far ahead of the TRDP the + * xHC can get. We have to artificially limit the number of in-flight TDs + * for this reason. + * + * Non-isochronous TRBs do not require this treatment for correct function + * but are likely to benefit performance-wise from the pipelining. + * + * With high-speed and faster transfers, there is an added complication for + * endpoints with more than one transfer per frame, i.e. short intervals. At + * least some host USB stacks require URBs to cover an entire frame, which + * means we may have to glue together several TDs into a single URB. + * + * + * A buggy or malicious guest can create a transfer or command ring that + * loops in on itself (in the simplest case using a sequence of one or more + * link TRBs where the last TRB points to the beginning of the sequence). + * Such a loop would effectively hang the processing thread. Since we cannot + * easily detect a generic loop, and because even non-looped TRB/command + * rings might contain extremely large number of items, we limit the number + * of entries that we are willing to process at once. If the limit is + * crossed, the xHC reports a host controller error and shuts itself down + * until it's reset. + * + * Note that for TRB lists, both URB submission and completion must protect + * against loops because the lists in guest memory are not guaranteed to stay + * unchanged between submitting and completing URBs. + * + * The event ring is not susceptible to loops because the xHC is the producer, + * not consumer. The event ring can run out of space but that is not a fatal + * problem. + * + * + * The interrupt logic uses an internal IPE (Interrupt Pending Enable) bit + * which controls whether the register-visible IP (Interrupt Pending) bit + * can be set. The IPE bit is set when a non-blocking event (BEI bit clear) + * is enqueued. The IPE bit is cleared when the event ring is initialized or + * transitions to empty (i.e. ERDP == EREP). When IPE transtitions to set, + * it will set IP unless the EHB (Event Handler Busy) bit is set or IMODC + * (Interrupt Moderation Counter) is non-zero. When IMODC counts down to + * zero, it sets the IP bit if IPE is set and EHB is not. Setting the IP bit + * triggers interrupt delivery. Note that clearing the IPE bit does not + * change the IP bit state. + * + * Interrupt delivery depends on whether MSI/MSI-X is in use or not. With MSI, + * an interrupter's IP (Interrupt Pending) bit is cleared as soon as the MSI + * message is written; with classic PCI interrupt delivery, the HCD must clear + * the IP bit. However, the EHB (Event Handler Busy) bit is always set, which + * causes further interrupts to be blocked on the interrupter until the HCD + * processes pending events and clears the EHB bit. + * + * Note that clearing the EHB bit may immediately trigger an interrupt if + * additional event TRBs were queued up while the HCD was processing previous + * ones. + * + * + * Each enabled USB device has a corresponding slot ID, a doorbell, as well as + * a device context which can be accessed through the DCBAA (Device Context + * Base Address Array). Valid slot IDs are in the 1-255 range; the first entry + * (i.e. index 0) in the DCBAA may optionally point to the Scratchpad Buffer + * Array, while doorbell 0 is associated with the Command Ring. + * + * While 255 valid slot IDs is an xHCI architectural limit, existing xHC + * implementations usually set a considerably lower limit, such as 32. See + * the XHCI_NDS constant. + * + * It would be tempting to use the DCBAA to determine which slots are free. + * Unfortunately the xHC is not allowed to access DCBAA entries which map to + * disabled slots (see section 6.1). A parallel aSlotState array is hence used + * to internally track the slot state and find available slots. Once a slot + * is enabled, the slot context entry in the DCBAA is used to track the + * slot state. + * + * + * Unlike OHCI/UHCI/EHCI, the xHC much more closely tracks USB device state. + * HCDs are not allowed to issue SET_ADDRESS requests at all and must use + * the Address Device xHCI command instead. + * + * HCDs can use SET_CONFIGURATION and SET_INTERFACE requests normally, but + * must inform the xHC of the changes via Configure Endpoint and Evaluate + * Context commands. Similarly there are Reset Endpoint and Stop Endpoint + * commands to manage endpoint state. + * + * A corollary of the above is that unlike OHCI/UHCI/EHCI, with xHCI there + * are very clear rules and a straightforward protocol for managing + * ownership of structures in physical memory. During normal operation, the + * xHC owns all device context memory and the HCD must explicitly ask the xHC + * to relinquish the ownership. + * + * The xHCI architecture offers an interesting feature in that it reserves + * opaque fields for xHCI use in certain data structures (slot and endpoint + * contexts) and gives the xHC an option to request scratchpad buffers that + * a HCD must provide. The xHC may use the opaque storage and/or scratchpad + * buffers for saving internal state. + * + * For implementation reasons, the xHCI device creates two root hubs on the + * VUSB level; one for USB2 devices (USB 1.x and 2.0), one for USB3. The + * behavior of USB2 vs. USB3 ports is different, and a device can only be + * attached to either one or the other hub. However, there is a single array + * of ports to avoid overly complicating the code, given that port numbering + * is linear and encompasses both USB2 and USB3 ports. + * + * + * The default emulated device is an Intel 7-Series xHC aka Panther Point. + * This was Intel's first xHC and is widely supported. It is also possible + * to select an Intel 8-Series xHC aka Lynx Point; this is only useful for + * debugging and requires the 'other' set of Windows 7 drivers. + * + * For Windows XP guest support, it is possible to emulate a Renesas + * (formerly NEC) uPD720201 xHC. It would be possible to emulate the earlier + * NEC chips but those a) only support xHCI 0.96, and b) their drivers + * require a reboot during installation. Renesas' drivers also support + * Windows Vista and 7. + * + * + * NB: Endpoints are addressed differently in xHCI and USB. In USB, + * endpoint addresses are 8-bit values with the low four bits identifying + * the endpoint number and the high bit indicating the direction (0=OUT, + * 1=IN); see e.g. 9.3.4 in USB 2.0 spec. In xHCI, endpoint addresses are + * used as DCIs (Device Context Index) and for that reason, they're + * compressed into 5 bits where the lowest bit(!) indicates direction (again + * 1=IN) and bits 1-4 designate the endpoint number. Endpoint 0 is somewhat + * special and uses DCI 1. See 4.8.1 in xHCI spec. + * + * + * NB: A variable named iPort is a zero-based index into the port array. + * On the other hand, a variable named uPort is a one-based port number! + * The implementation (obviously) uses zero-based indexing, but USB ports + * are numbered starting with 1. The same is true of xHCI slot numbering. + * The macros IDX_TO_ID() and ID_TO_IDX(a) should be used to convert between + * the two numbering conventions to make the intent clear. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_XHCI +#include <VBox/pci.h> +#include <VBox/msi.h> +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#ifdef IN_RING3 +# include <iprt/uuid.h> +# include <iprt/critsect.h> +#endif +#include <VBox/vusb.h> +#ifdef VBOX_IN_EXTPACK_R3 +# include <VBox/version.h> +#endif +#ifndef VBOX_IN_EXTPACK +# include "VBoxDD.h" +#endif + + +/********************************************************************************************************************************* +* (Most of the) Defined Constants, Macros and Structures * +*********************************************************************************************************************************/ + +/* Optional error injection support via DBGF. */ +//#define XHCI_ERROR_INJECTION + +/** The saved state version. */ +#define XHCI_SAVED_STATE_VERSION 1 + + +/** Convert a zero-based index to a 1-based ID. */ +#define IDX_TO_ID(a) (a + 1) +/** Convert a 1-based ID to a zero-based index. */ +#define ID_TO_IDX(a) (a - 1) + +/** PCI device related constants. */ +#define XHCI_PCI_MSI_CAP_OFS 0x80 + +/** Number of LUNs/root hubs. One each for USB2/USB3. */ +#define XHCI_NUM_LUNS 2 + +/** @name The following two constants were determined experimentally. + * They determine the maximum number of TDs allowed to be in flight. + * NB: For isochronous TDs, the number *must* be limited because + * Windows 8+ violates the xHCI specification and does not keep + * the transfer rings consistent. + * @{ + */ +//#define XHCI_MAX_ISOC_IN_FLIGHT 3 /* Scarlett needs 3; was 12 */ +#define XHCI_MAX_ISOC_IN_FLIGHT 12 +#define XHCI_MAX_BULK_IN_FLIGHT 8 +/** @} */ + +/** @name Implementation limit on the number of TRBs and commands + * the xHC is willing to process at once. A larger number is taken + * to indicate a broken or malicious guest, and causes a HC error. + * @{ + */ +#define XHCI_MAX_NUM_CMDS 128 +#define XHCI_MAX_NUM_TRBS 1024 +/** @} */ + +/** Implementation TD size limit. Prevents EDTLA wrap-around. */ +#define XHCI_MAX_TD_SIZE (16 * _1M - 1) + +/** Special value to prevent further queuing. */ +#define XHCI_NO_QUEUING_IN_FLIGHT (XHCI_MAX_BULK_IN_FLIGHT * 2) + +/* Structural Parameters #1 (HCSPARAMS1) values. */ + +/** Maximum allowed Number of Downstream Ports on the root hub. Careful + * when changing -- other structures may need adjusting! + */ +#define XHCI_NDP_MAX 32 + +/** Default number of USB 2.0 ports. + * + * @note AppleUSBXHCI does not handle more than 15 ports. At least OS X + * 10.8.2 crashes if we report more than 15 ports! Hence the default + * is 8 USB2 + 6 USB3 ports for a total of 14 so that OS X is happy. + */ +#define XHCI_NDP_20_DEFAULT 8 + +/** Default number of USB 3.0 ports. */ +#define XHCI_NDP_30_DEFAULT 6 + +/** Number of interrupters. */ +#define XHCI_NINTR 8 + +/** Mask for interrupter indexing. */ +#define XHCI_INTR_MASK (XHCI_NINTR - 1) + +/* The following is only true if XHCI_NINTR is a (non-zero) power of two. */ +AssertCompile((XHCI_NINTR & XHCI_INTR_MASK) == 0); + +/** Number of Device Slots. Determines the number of doorbell + * registers and device slots, among other things. */ +#define XHCI_NDS 32 + +/* Enforce xHCI architectural limits on HCSPARAMS1. */ +AssertCompile(XHCI_NDP_MAX < 255 && XHCI_NINTR < 1024 && XHCI_NDS < 255); +AssertCompile(XHCI_NDP_20_DEFAULT + XHCI_NDP_30_DEFAULT <= XHCI_NDP_MAX); +AssertCompile(XHCI_NDP_MAX <= XHCI_NDS); + +/* Structural Parameters #2 (HCSPARAMS2) values. */ + +/** Isochronous Scheduling Threshold. */ +#define XHCI_IST (RT_BIT(3) | 1) /* One frame. */ + +/** Max number of Event Ring Segment Table entries as a power of two. */ +#define XHCI_ERSTMAX_LOG2 5 +/** Max number of Event Ring Segment Table entries. */ +#define XHCI_ERSTMAX RT_BIT(XHCI_ERSTMAX_LOG2) + +/* Enforce xHCI architectural limits on HCSPARAMS2. */ +AssertCompile(XHCI_ERSTMAX_LOG2 < 16); + + +/** Size of the xHCI memory-mapped I/O region. */ +#define XHCI_MMIO_SIZE _64K + +/** Size of the capability part of the MMIO region. */ +#define XHCI_CAPS_REG_SIZE 0x80 + +/** Offset of the port registers in operational register space. */ +#define XHCI_PORT_REG_OFFSET 0x400 + +/** Offset of xHCI extended capabilities in MMIO region. */ +#define XHCI_XECP_OFFSET 0x1000 + +/** Offset of the run-time registers in MMIO region. */ +#define XHCI_RTREG_OFFSET 0x2000 + +/** Offset of the doorbell registers in MMIO region. */ +#define XHCI_DOORBELL_OFFSET 0x3000 + +/** Size of the extended capability area. */ +#define XHCI_EXT_CAP_SIZE 1024 + +/* Make sure we can identify MMIO register accesses properly. */ +AssertCompile(XHCI_DOORBELL_OFFSET > XHCI_RTREG_OFFSET); +AssertCompile(XHCI_XECP_OFFSET > XHCI_PORT_REG_OFFSET + XHCI_CAPS_REG_SIZE); +AssertCompile(XHCI_RTREG_OFFSET > XHCI_XECP_OFFSET + XHCI_EXT_CAP_SIZE); + + +/** Maximum size of a single extended capability. */ +#define MAX_XCAP_SIZE 256 + +/** @name xHCI Extended capability types. + * @{ */ +#define XHCI_XCP_USB_LEGACY 1 /**< USB legacy support. */ +#define XHCI_XCP_PROTOCOL 2 /**< Protocols supported by ports. */ +#define XHCI_XCP_EXT_PM 3 /**< Extended power management (non-PCI). */ +#define XHCI_XCP_IOVIRT 4 /**< Hardware xHCI virtualization support. */ +#define XHCI_XCP_MSI 5 /**< Message interrupts (non-PCI). */ +#define XHCI_XCP_LOCAL_MEM 6 /**< Local memory (for debug support). */ +#define XHCI_XCP_USB_DEBUG 10 /**< USB debug capability. */ +#define XHCI_XCP_EXT_MSI 17 /**< MSI-X (non-PCI). */ +/** @} */ + + +/* xHCI Register Bits. */ + + +/** @name Capability Parameters (HCCPARAMS) bits + * @{ */ +#define XHCI_HCC_AC64 RT_BIT(0) /**< RO */ +#define XHCI_HCC_BNC RT_BIT(1) /**< RO */ +#define XHCI_HCC_CSZ RT_BIT(2) /**< RO */ +#define XHCI_HCC_PPC RT_BIT(3) /**< RO */ +#define XHCI_HCC_PIND RT_BIT(4) /**< RO */ +#define XHCI_HCC_LHRC RT_BIT(5) /**< RO */ +#define XHCI_HCC_LTC RT_BIT(6) /**< RO */ +#define XHCI_HCC_NSS RT_BIT(7) /**< RO */ +#define XHCI_HCC_MAXPSA_MASK (RT_BIT(12)|RT_BIT(13)|RT_BIT(14)| RT_BIT(15)) /**< RO */ +#define XHCI_HCC_MAXPSA_SHIFT 12 +#define XHCI_HCC_XECP_MASK 0xFFFF0000 /**< RO */ +#define XHCI_HCC_XECP_SHIFT 16 +/** @} */ + + +/** @name Command Register (USBCMD) bits + * @{ */ +#define XHCI_CMD_RS RT_BIT(0) /**< RW - Run/Stop */ +#define XHCI_CMD_HCRST RT_BIT(1) /**< RW - Host Controller Reset */ +#define XHCI_CMD_INTE RT_BIT(2) /**< RW - Interrupter Enable */ +#define XHCI_CMD_HSEE RT_BIT(3) /**< RW - Host System Error Enable */ +#define XHCI_CMD_LCRST RT_BIT(7) /**< RW - Light HC Reset */ +#define XHCI_CMD_CSS RT_BIT(8) /**< RW - Controller Save State */ +#define XHCI_CMD_CRS RT_BIT(9) /**< RW - Controller Restore State */ +#define XHCI_CMD_EWE RT_BIT(10) /**< RW - Enable Wrap Event */ +#define XHCI_CMD_EU3S RT_BIT(11) /**< RW - Enable U3 MFINDEX Stop */ + +#define XHCI_CMD_MASK ( XHCI_CMD_RS | XHCI_CMD_HCRST | XHCI_CMD_INTE | XHCI_CMD_HSEE | XHCI_CMD_LCRST \ + | XHCI_CMD_CSS | XHCI_CMD_CRS | XHCI_CMD_EWE | XHCI_CMD_EU3S) +/** @} */ + + +/** @name Status Register (USBSTS) bits + * @{ */ +#define XHCI_STATUS_HCH RT_BIT(0) /**< RO - HC Halted */ +#define XHCI_STATUS_HSE RT_BIT(2) /**< RW1C - Host System Error */ +#define XHCI_STATUS_EINT RT_BIT(3) /**< RW1C - Event Interrupt */ +#define XHCI_STATUS_PCD RT_BIT(4) /**< RW1C - Port Change Detect */ +#define XHCI_STATUS_SSS RT_BIT(8) /**< RO - Save State Status */ +#define XHCI_STATUS_RSS RT_BIT(9) /**< RO - Resture State Status */ +#define XHCI_STATUS_SRE RT_BIT(10) /**< RW1C - Save/Restore Error */ +#define XHCI_STATUS_CNR RT_BIT(11) /**< RO - Controller Not Ready */ +#define XHCI_STATUS_HCE RT_BIT(12) /**< RO - Host Controller Error */ + +#define XHCI_STATUS_WRMASK (XHCI_STATUS_HSE | XHCI_STATUS_EINT | XHCI_STATUS_PCD | XHCI_STATUS_SRE) +/** @} */ + + +/** @name Default xHCI speed definitions (7.2.2.1.1) + * @{ */ +#define XHCI_SPD_FULL 1 +#define XHCI_SPD_LOW 2 +#define XHCI_SPD_HIGH 3 +#define XHCI_SPD_SUPER 4 +/** @} */ + +/** @name Port Status and Control Register bits (PORTSCUSB2/PORTSCUSB3) + * @{ */ +#define XHCI_PORT_CCS RT_BIT(0) /**< ROS - Current Connection Status */ +#define XHCI_PORT_PED RT_BIT(1) /**< RW1S - Port Enabled/Disabled */ +#define XHCI_PORT_OCA RT_BIT(3) /**< RO - Over-current Active */ +#define XHCI_PORT_PR RT_BIT(4) /**< RW1S - Port Reset */ +#define XHCI_PORT_PLS_MASK (RT_BIT(5) | RT_BIT(6) | RT_BIT(7) | RT_BIT(8)) /**< RWS */ +#define XHCI_PORT_PLS_SHIFT 5 +#define XHCI_PORT_PP RT_BIT(9) /**< RWS - Port Power */ +#define XHCI_PORT_SPD_MASK (RT_BIT(10) | RT_BIT(11) | RT_BIT(12) | RT_BIT(13)) /**< ROS */ +#define XHCI_PORT_SPD_SHIFT 10 +#define XHCI_PORT_LWS RT_BIT(16) /**< RW - Link State Write Strobe */ +#define XHCI_PORT_CSC RT_BIT(17) /**< RW1CS - Connect Status Change */ +#define XHCI_PORT_PEC RT_BIT(18) /**< RW1CS - Port Enabled/Disabled Change */ +#define XHCI_PORT_WRC RT_BIT(19) /**< RW1CS - Warm Port Reset Change */ +#define XHCI_PORT_OCC RT_BIT(20) /**< RW1CS - Over-current Change */ +#define XHCI_PORT_PRC RT_BIT(21) /**< RW1CS - Port Reset Change */ +#define XHCI_PORT_PLC RT_BIT(22) /**< RW1CS - Port Link State Change */ +#define XHCI_PORT_CEC RT_BIT(23) /**< RW1CS - Port Config Error Change */ +#define XHCI_PORT_CAS RT_BIT(24) /**< RO - Cold Attach Status */ +#define XHCI_PORT_WCE RT_BIT(25) /**< RWS - Wake on Connect Enable */ +#define XHCI_PORT_WDE RT_BIT(26) /**< RWS - Wake on Disconnect Enable */ +#define XHCI_PORT_WOE RT_BIT(27) /**< RWS - Wake on Over-current Enable */ +#define XHCI_PORT_DR RT_BIT(30) /**< RO - Device (Not) Removable */ +#define XHCI_PORT_WPR RT_BIT(31) /**< RW1S - Warm Port Reset */ + +#define XHCI_PORT_RESERVED (RT_BIT(2) | RT_BIT(14) | RT_BIT(15) | RT_BIT(28) | RT_BIT(29)) + +#define XHCI_PORT_WAKE_MASK (XHCI_PORT_WCE|XHCI_PORT_WDE|XHCI_PORT_WOE) +#define XHCI_PORT_CHANGE_MASK (XHCI_PORT_CSC|XHCI_PORT_PEC|XHCI_PORT_WRC|XHCI_PORT_OCC|XHCI_PORT_PRC|XHCI_PORT_PLC|XHCI_PORT_CEC) +#define XHCI_PORT_CTL_RW_MASK (XHCI_PORT_PP|XHCI_PORT_LWS) +#define XHCI_PORT_CTL_W1_MASK (XHCI_PORT_PED|XHCI_PORT_PR|XHCI_PORT_WPR) +#define XHCI_PORT_RO_MASK (XHCI_PORT_CCS|XHCI_PORT_OCA|XHCI_PORT_SPD_MASK|XHCI_PORT_CAS|XHCI_PORT_DR) +/** @} */ + +/** @name Port Link State values + * @{ */ +#define XHCI_PLS_U0 0 /**< U0 State. */ +#define XHCI_PLS_U1 1 /**< U1 State. */ +#define XHCI_PLS_U2 2 /**< U2 State. */ +#define XHCI_PLS_U3 3 /**< U3 State (Suspended). */ +#define XHCI_PLS_DISABLED 4 /**< Disabled. */ +#define XHCI_PLS_RXDETECT 5 /**< RxDetect. */ +#define XHCI_PLS_INACTIVE 6 /**< Inactive. */ +#define XHCI_PLS_POLLING 7 /**< Polling. */ +#define XHCI_PLS_RECOVERY 8 /**< Recovery. */ +#define XHCI_PLS_HOTRST 9 /**< Hot Reset. */ +#define XHCI_PLS_CMPLMODE 10 /**< Compliance Mode. */ +#define XHCI_PLS_TSTMODE 11 /**< Test Mode. */ +/* Values 12-14 are reserved. */ +#define XHCI_PLS_RESUME 15 /**< Resume. */ +/** @} */ + + +/** @name Command Ring Control Register (CRCR) bits + * @{ */ +#define XHCI_CRCR_RCS RT_BIT(0) /**< RW - Ring Cycle State */ +#define XHCI_CRCR_CS RT_BIT(1) /**< RW1S - Command Stop */ +#define XHCI_CRCR_CA RT_BIT(2) /**< RW1S - Command Abort */ +#define XHCI_CRCR_CRR RT_BIT(3) /**< RO - Command Ring Running */ + +#define XHCI_CRCR_RD_MASK UINT64_C(0xFFFFFFFFFFFFFFF8) /* Mask off bits always read as zero. */ +#define XHCI_CRCR_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) +#define XHCI_CRCR_UPD_MASK (XHCI_CRCR_ADDR_MASK | XHCI_CRCR_RCS) +/** @} */ + + +/** @name Interrupter Management Register (IMAN) bits + * @{ */ +#define XHCI_IMAN_IP RT_BIT(0) /**< RW1C - Interrupt Pending */ +#define XHCI_IMAN_IE RT_BIT(1) /**< RW - Interrupt Enable */ + +#define XHCI_IMAN_VALID_MASK (XHCI_IMAN_IP | XHCI_IMAN_IE) +/** @} */ + + +/** @name Interrupter Moderation Register (IMOD) bits + * @{ */ +#define XHCI_IMOD_IMODC_MASK 0xFFFF0000 /**< RW */ +#define XHCI_IMOD_IMODC_SHIFT 16 +#define XHCI_IMOD_IMODI_MASK 0x0000FFFF /**< RW */ +/** @} */ + + +/** @name Event Ring Segment Table Size Register (ERSTSZ) bits + * @{ */ +#define XHCI_ERSTSZ_MASK 0x0000FFFF /**< RW */ +/** @} */ + +/** @name Event Ring Segment Table Base Address Register (ERSTBA) bits + * @{ */ +#define XHCI_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) +/** @} */ + +/** For reasons that are not obvious, NEC/Renesas xHCs only require 16-bit + * alignment for the ERST base. This is not in line with the xHCI spec + * (which requires 64-bit alignment) but is clearly documented by NEC. + */ +#define NEC_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) + +/** Firmware revision reported in NEC/Renesas mode. Value chosen based on + * OS X driver check (OS X supports these chips since they're commonly + * found in ExpressCards). + */ +#define NEC_FW_REV 0x3028 + +/** @name Event Ring Deqeue Pointer Register (ERDP) bits + * @{ */ +#define XHCI_ERDP_DESI_MASK 0x00000007 /**< RW - Dequeue ERST Segment Index */ +#define XHCI_ERDP_EHB RT_BIT(3) /**< RW1C - Event Handler Busy */ +#define XHCI_ERDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) /**< RW - ERDP address mask */ +/** @} */ + +/** @name Device Context Base Address Array (DCBAA) definitions + * @{ */ +#define XHCI_DCBAA_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) /**< Applies to DCBAAP and its entries. */ +/** @} */ + +/** @name Doorbell Register bits + * @{ */ +#define XHCI_DB_TGT_MASK 0x000000FF /**< DB Target mask. */ +#define XHCI_DB_STRMID_SHIFT 16 /**< DB Stream ID shift. */ +#define XHCI_DB_STRMID_MASK 0xFFFF0000 /**< DB Stream ID mask. */ +/** @} */ + +/** Address mask for device/endpoint/input contexts. */ +#define XHCI_CTX_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) + +/** @name TRB Completion Codes + * @{ */ +#define XHCI_TCC_INVALID 0 /**< CC field not updated. */ +#define XHCI_TCC_SUCCESS 1 /**< Successful TRB completion. */ +#define XHCI_TCC_DATA_BUF_ERR 2 /**< Overrun/underrun. */ +#define XHCI_TCC_BABBLE 3 /**< Babble detected. */ +#define XHCI_TCC_USB_XACT_ERR 4 /**< USB transaction error. */ +#define XHCI_TCC_TRB_ERR 5 /**< TRB error detected. */ +#define XHCI_TCC_STALL 6 /**< USB Stall detected. */ +#define XHCI_TCC_RSRC_ERR 7 /**< Inadequate xHC resources. */ +#define XHCI_TCC_BWIDTH_ERR 8 /**< Unable to allocate bandwidth. */ +#define XHCI_TCC_NO_SLOTS 9 /**< MaxSlots (NDS) exceeded. */ +#define XHCI_TCC_INV_STRM_TYP 10 /**< Invalid stream context type. */ +#define XHCI_TCC_SLOT_NOT_ENB 11 /**< Slot not enabled. */ +#define XHCI_TCC_EP_NOT_ENB 12 /**< Endpoint not enabled. */ +#define XHCI_TCC_SHORT_PKT 13 /**< Short packet detected. */ +#define XHCI_TCC_RING_UNDERRUN 14 /**< Transfer ring underrun. */ +#define XHCI_TCC_RING_OVERRUN 15 /**< Transfer ring overrun. */ +#define XHCI_TCC_VF_RING_FULL 16 /**< VF event ring full. */ +#define XHCI_TCC_PARM_ERR 17 /**< Invalid context parameter. */ +#define XHCI_TCC_BWIDTH_OVER 18 /**< Isoc bandwidth overrun. */ +#define XHCI_TCC_CTX_STATE_ERR 19 /**< Transition from illegal context state. */ +#define XHCI_TCC_NO_PING 20 /**< No ping response in time. */ +#define XHCI_TCC_EVT_RING_FULL 21 /**< Event Ring full. */ +#define XHCI_TCC_DEVICE_COMPAT 22 /**< Incompatible device detected. */ +#define XHCI_TCC_MISS_SVC 23 /**< Missed isoc service. */ +#define XHCI_TCC_CMDR_STOPPED 24 /**< Command ring stopped. */ +#define XHCI_TCC_CMD_ABORTED 25 /**< Command aborted. */ +#define XHCI_TCC_STOPPED 26 /**< Endpoint stopped. */ +#define XHCI_TCC_STP_INV_LEN 27 /**< EP stopped, invalid transfer length. */ + /* 28 Reserved. */ +#define XHCI_TCC_MAX_EXIT_LAT 29 /**< Max exit latency too large. */ + /* 30 Reserved. */ +#define XHCI_TCC_ISOC_OVERRUN 31 /**< Isochronous buffer overrun. */ +#define XHCI_TCC_EVT_LOST 32 /**< Event lost due to overrun. */ +#define XHCI_TCC_ERR_OTHER 33 /**< Implementation specific error. */ +#define XHCI_TCC_INV_STRM_ID 34 /**< Invalid stream ID. */ +#define XHCI_TCC_SEC_BWIDTH_ERR 35 /**< Secondary bandwidth error. */ +#define XHCI_TCC_SPLIT_ERR 36 /**< Split transaction error. */ +/** @} */ + +#if defined(IN_RING3) && defined(LOG_ENABLED) +/** Human-readable completion code descriptions for debugging. */ +static const char * const g_apszCmplCodes[] = { + "CC field not updated", "Successful TRB completion", "Overrun/underrun", "Babble detected", /* 0-3 */ + "USB transaction error", "TRB error detected", "USB Stall detected", "Inadequate xHC resources", /* 4-7 */ + "Unable to allocate bandwidth", "MaxSlots (NDS) exceeded", "Invalid stream context type", "Slot not enabled", /* 8-11 */ + "Endpoint not enabled", "Short packet detected", "Transfer ring underrun", "Transfer ring overrun", /* 12-15 */ + "VF event ring full", "Invalid context param", "Isoc bandwidth overrun", "Transition from illegal ctx state", /* 16-19 */ + "No ping response in time", "Event Ring full", "Incompatible device detected", "Missed isoc service", /* 20-23 */ + "Command ring stopped", "Command aborted", "Endpoint stopped", "EP stopped, invalid transfer length", /* 24-27 */ + "Reserved", "Max exit latency too large", "Reserved", "Isochronous buffer overrun", /* 28-31 */ + "Event lost due to overrun", "Implementation specific error", "Invalid stream ID", "Secondary bandwidth error", /* 32-35 */ + "Split transaction error" /* 36 */ +}; +#endif + + +/* TRBs marked as 'TRB' are only valid in the transfer ring. TRBs marked + * as 'Command' are only valid in the command ring. TRBs marked as 'Event' + * are the only ones generated in the event ring. The Link TRB is valid + * in both the transfer and command rings. + */ + +/** @name TRB Types + * @{ */ +#define XHCI_TRB_INVALID 0 /**< Reserved/unused TRB type. */ +#define XHCI_TRB_NORMAL 1 /**< Normal TRB. */ +#define XHCI_TRB_SETUP_STG 2 /**< Setup Stage TRB. */ +#define XHCI_TRB_DATA_STG 3 /**< Data Stage TRB. */ +#define XHCI_TRB_STATUS_STG 4 /**< Status Stage TRB. */ +#define XHCI_TRB_ISOCH 5 /**< Isochronous TRB. */ +#define XHCI_TRB_LINK 6 /**< Link. */ +#define XHCI_TRB_EVT_DATA 7 /**< Event Data TRB. */ +#define XHCI_TRB_NOOP_XFER 8 /**< No-op transfer TRB. */ +#define XHCI_TRB_ENB_SLOT 9 /**< Enable Slot Command. */ +#define XHCI_TRB_DIS_SLOT 10 /**< Disable Slot Command. */ +#define XHCI_TRB_ADDR_DEV 11 /**< Address Device Command. */ +#define XHCI_TRB_CFG_EP 12 /**< Configure Endpoint Command. */ +#define XHCI_TRB_EVAL_CTX 13 /**< Evaluate Context Command. */ +#define XHCI_TRB_RESET_EP 14 /**< Reset Endpoint Command. */ +#define XHCI_TRB_STOP_EP 15 /**< Stop Endpoint Command. */ +#define XHCI_TRB_SET_DEQ_PTR 16 /**< Set TR Dequeue Pointer Command. */ +#define XHCI_TRB_RESET_DEV 17 /**< Reset Device Command. */ +#define XHCI_TRB_FORCE_EVT 18 /**< Force Event Command. */ +#define XHCI_TRB_NEG_BWIDTH 19 /**< Negotiate Bandwidth Command. */ +#define XHCI_TRB_SET_LTV 20 /**< Set Latency Tolerate Value Command. */ +#define XHCI_TRB_GET_PORT_BW 21 /**< Get Port Bandwidth Command. */ +#define XHCI_TRB_FORCE_HDR 22 /**< Force Header Command. */ +#define XHCI_TRB_NOOP_CMD 23 /**< No-op Command. */ + /* 24-31 Reserved. */ +#define XHCI_TRB_XFER 32 /**< Transfer Event. */ +#define XHCI_TRB_CMD_CMPL 33 /**< Command Completion Event. */ +#define XHCI_TRB_PORT_SC 34 /**< Port Status Change Event. */ +#define XHCI_TRB_BW_REQ 35 /**< Bandwidth Request Event. */ +#define XHCI_TRB_DBELL 36 /**< Doorbell Event. */ +#define XHCI_TRB_HC_EVT 37 /**< Host Controller Event. */ +#define XHCI_TRB_DEV_NOTIFY 38 /**< Device Notification Event. */ +#define XHCI_TRB_MFIDX_WRAP 39 /**< MFINDEX Wrap Event. */ + /* 40-47 Reserved. */ +#define NEC_TRB_CMD_CMPL 48 /**< Command Completion Event, NEC specific. */ +#define NEC_TRB_GET_FW_VER 49 /**< Get Firmware Version Command, NEC specific. */ +#define NEC_TRB_AUTHENTICATE 50 /**< Authenticate Command, NEC specific. */ +/** @} */ + +#if defined(IN_RING3) && defined(LOG_ENABLED) +/** Human-readable TRB names for debugging. */ +static const char * const g_apszTrbNames[] = { + "Reserved/unused TRB!!", "Normal TRB", "Setup Stage TRB", "Data Stage TRB", /* 0-3 */ + "Status Stage TRB", "Isochronous TRB", "Link", "Event Data TRB", /* 4-7 */ + "No-op transfer TRB", "Enable Slot", "Disable Slot", "Address Device", /* 8-11 */ + "Configure Endpoint", "Evaluate Context", "Reset Endpoint", "Stop Endpoint", /* 12-15 */ + "Set TR Dequeue Pointer", "Reset Device", "Force Event", "Negotiate Bandwidth", /* 16-19 */ + "Set Latency Tolerate Value", "Get Port Bandwidth", "Force Header", "No-op", /* 20-23 */ + "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 24-31 */ + "Transfer", "Command Completion", "Port Status Change", "BW Request", /* 32-35 */ + "Doorbell", "Host Controller", "Device Notification", "MFINDEX Wrap", /* 36-39 */ + "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 40-47 */ + "NEC FW Version Completion", "NEC Get FW Version", "NEC Authenticate" /* 48-50 */ +}; +#endif + +/** Generic TRB template. */ +typedef struct sXHCI_TRB_G { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_G; +AssertCompile(sizeof(XHCI_TRB_G) == 0x10); + +/** Generic transfer TRB template. */ +typedef struct sXHCI_TRB_GX { + uint32_t resvd0; + uint32_t resvd1; + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t resvd2 : 5; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd3 : 3; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_GX; +AssertCompile(sizeof(XHCI_TRB_GX) == 0x10); + + +/* -= Transfer TRB types =- */ + + +/** Normal Transfer TRB. */ +typedef struct sXHCI_TRB_NORM { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd0 : 2; + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd1 : 16; +} XHCI_TRB_NORM; +AssertCompile(sizeof(XHCI_TRB_NORM) == 0x10); + +/** Control Transfer - Setup Stage TRB. */ +typedef struct sXHCI_TRB_CTSP { + uint8_t bmRequestType; /**< See the USB spec. */ + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; + uint32_t xfr_len : 17; /**< Transfer length (8). */ + uint32_t resvd0 : 5; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 4; + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd2 : 2; + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t trt : 2; /**< Transfer Type. */ + uint32_t resvd3 : 14; +} XHCI_TRB_CTSP; +AssertCompile(sizeof(XHCI_TRB_CTSP) == 0x10); + +/** Control Transfer - Data Stage TRB. */ +typedef struct sXHCI_TRB_CTDT { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t resvd0 : 3; + uint32_t type : 6; /**< TRB Type. */ + uint32_t dir : 1; /**< Direction (1=IN). */ + uint32_t resvd1 : 15; +} XHCI_TRB_CTDT; +AssertCompile(sizeof(XHCI_TRB_CTDT) == 0x10); + +/** Control Transfer - Status Stage TRB. */ +typedef struct sXHCI_TRB_CTSS { + uint64_t resvd0; + uint32_t resvd1 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t resvd2 : 2; + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t resvd3 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t dir : 1; /**< Direction (1=IN). */ + uint32_t resvd4 : 15; +} XHCI_TRB_CTSS; +AssertCompile(sizeof(XHCI_TRB_CTSS) == 0x10); + +/** Isochronous Transfer TRB. */ +typedef struct sXHCI_TRB_ISOC { + uint64_t data_ptr; /**< Pointer or data. */ + uint32_t xfr_len : 17; /**< Transfer length. */ + uint32_t td_size : 5; /**< Remaining packets. */ + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t isp : 1; /**< Interrupt on Short Packet. */ + uint32_t ns : 1; /**< No Snoop. */ + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t idt : 1; /**< Immediate Data. */ + uint32_t tbc : 2; /**< Transfer Burst Count. */ + uint32_t bei : 1; /**< Block Event Interrupt. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t tlbpc : 4; /**< Transfer Last Burst Packet Count. */ + uint32_t frm_id : 11; /**< Frame ID. */ + uint32_t sia : 1; /**< Start Isoch ASAP. */ +} XHCI_TRB_ISOC; +AssertCompile(sizeof(XHCI_TRB_ISOC) == 0x10); + +/* Number of bits in the frame ID. */ +#define XHCI_FRAME_ID_BITS 11 + +/** No Op Transfer TRB. */ +typedef struct sXHCI_TRB_NOPT { + uint64_t resvd0; + uint32_t resvd1 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next TRB. */ + uint32_t resvd2 : 2; + uint32_t ch : 1; /**< Chain bit. */ + uint32_t ioc : 1; /**< Interrupt On Completion. */ + uint32_t resvd3 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_NOPT; +AssertCompile(sizeof(XHCI_TRB_NOPT) == 0x10); + + +/* -= Event TRB types =- */ + + +/** Transfer Event TRB. */ +typedef struct sXHCI_TRB_TE { + uint64_t trb_ptr; /**< TRB pointer. */ + uint32_t xfr_len : 24; /**< Transfer length. */ + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd0 : 1; + uint32_t ed : 1; /**< Event Data flag. */ + uint32_t resvd1 : 7; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd2 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_TE; +AssertCompile(sizeof(XHCI_TRB_TE) == 0x10); + +/** Command Completion Event TRB. */ +typedef struct sXHCI_TRB_CCE { + uint64_t trb_ptr; /**< Command TRB pointer. */ + uint32_t resvd0 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t vf_id : 8; /**< Virtual Function ID. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_CCE; +AssertCompile(sizeof(XHCI_TRB_CCE) == 0x10); + +/** Port Staus Change Event TRB. */ +typedef struct sXHCI_TRB_PSCE { + uint32_t resvd0 : 24; + uint32_t port_id : 8; /**< Port ID. */ + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_PSCE; +AssertCompile(sizeof(XHCI_TRB_PSCE) == 0x10); + +/** Bandwidth Request Event TRB. */ +typedef struct sXHCI_TRB_BRE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_BRE; +AssertCompile(sizeof(XHCI_TRB_BRE) == 0x10); + +/** Doorbell Event TRB. */ +typedef struct sXHCI_TRB_DBE { + uint32_t reason : 5; /**< DB Reason/target. */ + uint32_t resvd0 : 27; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t vf_id : 8; /**< Virtual Function ID. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DBE; +AssertCompile(sizeof(XHCI_TRB_DBE) == 0x10); + +/** Host Controller Event TRB. */ +typedef struct sXHCI_TRB_HCE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_HCE; +AssertCompile(sizeof(XHCI_TRB_HCE) == 0x10); + +/** Device Notification Event TRB. */ +typedef struct sXHCI_TRB_DNE { + uint32_t resvd0 : 4; + uint32_t dn_type : 4; /**< Device Notification Type. */ + uint32_t dnd_lo : 5; /**< Device Notification Data Lo. */ + uint32_t dnd_hi; /**< Device Notification Data Hi. */ + uint32_t resvd1 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd2 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DNE; +AssertCompile(sizeof(XHCI_TRB_DNE) == 0x10); + +/** MFINDEX Wrap Event TRB. */ +typedef struct sXHCI_TRB_MWE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2 : 24; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_MWE; +AssertCompile(sizeof(XHCI_TRB_MWE) == 0x10); + +/** NEC Specific Command Completion Event TRB. */ +typedef struct sXHCI_TRB_NCE { + uint64_t trb_ptr; /**< Command TRB pointer. */ + uint32_t word1 : 16; /**< First result word. */ + uint32_t resvd0 : 8; + uint32_t cc : 8; /**< Completion Code. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t word2 : 16; /**< Second result word. */ +} XHCI_TRB_NCE; +AssertCompile(sizeof(XHCI_TRB_NCE) == 0x10); + + + +/* -= Command TRB types =- */ + + +/** No Op Command TRB. */ +typedef struct sXHCI_TRB_NOPC { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_NOPC; +AssertCompile(sizeof(XHCI_TRB_NOPC) == 0x10); + +/** Enable Slot Command TRB. */ +typedef struct sXHCI_TRB_ESL { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 16; +} XHCI_TRB_ESL; +AssertCompile(sizeof(XHCI_TRB_ESL) == 0x10); + +/** Disable Slot Command TRB. */ +typedef struct sXHCI_TRB_DSL { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_DSL; +AssertCompile(sizeof(XHCI_TRB_DSL) == 0x10); + +/** Address Device Command TRB. */ +typedef struct sXHCI_TRB_ADR { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 8; + uint32_t bsr : 1; /**< Block Set Address Request. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_ADR; +AssertCompile(sizeof(XHCI_TRB_ADR) == 0x10); + +/** Configure Endpoint Command TRB. */ +typedef struct sXHCI_TRB_CFG { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 8; + uint32_t dc : 1; /**< Deconfigure. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_CFG; +AssertCompile(sizeof(XHCI_TRB_CFG) == 0x10); + +/** Evaluate Context Command TRB. */ +typedef struct sXHCI_TRB_EVC { + uint64_t ctx_ptr; /**< Input Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_EVC; +AssertCompile(sizeof(XHCI_TRB_EVC) == 0x10); + +/** Reset Endpoint Command TRB. */ +typedef struct sXHCI_TRB_RSE { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 8; + uint32_t tsp : 1; /**< Transfer State Preserve. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd4 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_RSE; +AssertCompile(sizeof(XHCI_TRB_RSE) == 0x10); + +/** Stop Endpoint Command TRB. */ +typedef struct sXHCI_TRB_STP { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd4 : 2; + uint32_t sp : 1; /**< Suspend. */ + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_STP; +AssertCompile(sizeof(XHCI_TRB_STP) == 0x10); + +/** Set TR Dequeue Pointer Command TRB. */ +typedef struct sXHCI_TRB_STDP { +#if 0 + uint64_t dcs : 1; /**< Dequeue Cycle State. */ + uint64_t sct : 3; /**< Stream Context Type. */ + uint64_t tr_dqp : 60; /**< New TR Dequeue Pointer (63:4). */ +#else + uint64_t tr_dqp; +#endif + uint16_t resvd0; + uint16_t strm_id; /**< Stream ID. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t ep_id : 5; /**< Endpoint ID. */ + uint32_t resvd2 : 3; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_STDP; +AssertCompile(sizeof(XHCI_TRB_STDP) == 0x10); + +/** Reset Device Command TRB. */ +typedef struct sXHCI_TRB_RSD { + uint32_t resvd0; + uint32_t resvd1; + uint32_t resvd2; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd3 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd4 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_RSD; +AssertCompile(sizeof(XHCI_TRB_RSD) == 0x10); + +/** Get Port Bandwidth Command TRB. */ +typedef struct sXHCI_TRB_GPBW { + uint64_t pbctx_ptr; /**< Port Bandwidth Context pointer. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t spd : 4; /**< Dev Speed. */ + uint32_t resvd2 : 4; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_GPBW; +AssertCompile(sizeof(XHCI_TRB_GPBW) == 0x10); + +/** Force Header Command TRB. */ +typedef struct sXHCI_TRB_FHD { + uint32_t pkt_typ : 5; /**< Packet Type. */ + uint32_t hdr_lo : 27; /**< Header Info Lo. */ + uint32_t hdr_mid; /**< Header Info Mid. */ + uint32_t hdr_hi; /**< Header Info Hi. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd0 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd1 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_FHD; +AssertCompile(sizeof(XHCI_TRB_FHD) == 0x10); + +/** NEC Specific Authenticate Command TRB. */ +typedef struct sXHCI_TRB_NAC { + uint64_t cookie; /**< Cookie to munge. */ + uint32_t resvd0; + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t resvd1 : 9; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd2 : 8; + uint32_t slot_id : 8; /**< Slot ID. */ +} XHCI_TRB_NAC; +AssertCompile(sizeof(XHCI_TRB_NAC) == 0x10); + + +/* -= Other TRB types =- */ + + +/** Link TRB. */ +typedef struct sXHCI_TRB_LNK { + uint64_t rseg_ptr; /**< Ring Segment Pointer. */ + uint32_t resvd0 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t toggle : 1; /**< Toggle Cycle flag. */ + uint32_t resvd1 : 2; + uint32_t chain : 1; /**< Chain flag. */ + uint32_t ioc : 1; /**< Interrupt On Completion flag. */ + uint32_t resvd2 : 4; + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 16; +} XHCI_TRB_LNK; +AssertCompile(sizeof(XHCI_TRB_LNK) == 0x10); + +/** Event Data TRB. */ +typedef struct sXHCI_TRB_EVTD { + uint64_t evt_data; /**< Event Data. */ + uint32_t resvd0 : 22; + uint32_t int_tgt : 10; /**< Interrupter target. */ + uint32_t cycle : 1; /**< Cycle bit. */ + uint32_t ent : 1; /**< Evaluate Next Target flag. */ + uint32_t resvd1 : 2; + uint32_t chain : 1; /**< Chain flag. */ + uint32_t ioc : 1; /**< Interrupt On Completion flag. */ + uint32_t resvd2 : 3; + uint32_t bei : 1; /**< Block Event Interrupt flag. */ + uint32_t type : 6; /**< TRB Type. */ + uint32_t resvd3 : 16; +} XHCI_TRB_EVTD; +AssertCompile(sizeof(XHCI_TRB_EVTD) == 0x10); + + +/* -= Union TRB types for the three rings =- */ + + +typedef union sXHCI_XFER_TRB { + XHCI_TRB_NORM norm; + XHCI_TRB_CTSP setup; + XHCI_TRB_CTDT data; + XHCI_TRB_CTSS status; + XHCI_TRB_ISOC isoc; + XHCI_TRB_EVTD evtd; + XHCI_TRB_NOPT nop; + XHCI_TRB_LNK link; + XHCI_TRB_GX gen; +} XHCI_XFER_TRB; +AssertCompile(sizeof(XHCI_XFER_TRB) == 0x10); + +typedef union sXHCI_COMMAND_TRB { + XHCI_TRB_ESL esl; + XHCI_TRB_DSL dsl; + XHCI_TRB_ADR adr; + XHCI_TRB_CFG cfg; + XHCI_TRB_EVC evc; + XHCI_TRB_RSE rse; + XHCI_TRB_STP stp; + XHCI_TRB_STDP stdp; + XHCI_TRB_RSD rsd; + XHCI_TRB_GPBW gpbw; + XHCI_TRB_FHD fhd; + XHCI_TRB_NAC nac; + XHCI_TRB_NOPC nopc; + XHCI_TRB_LNK link; + XHCI_TRB_G gen; +} XHCI_COMMAND_TRB; +AssertCompile(sizeof(XHCI_COMMAND_TRB) == 0x10); + +typedef union sXHCI_EVENT_TRB { + XHCI_TRB_TE te; + XHCI_TRB_CCE cce; + XHCI_TRB_PSCE psce; + XHCI_TRB_BRE bre; + XHCI_TRB_DBE dbe; + XHCI_TRB_HCE hce; + XHCI_TRB_DNE dne; + XHCI_TRB_MWE mwe; + XHCI_TRB_NCE nce; + XHCI_TRB_G gen; +} XHCI_EVENT_TRB; +AssertCompile(sizeof(XHCI_EVENT_TRB) == 0x10); + + + +/* -=-=-= Contexts =-=-=- */ + +/** Slot Context. */ +typedef struct sXHCI_SLOT_CTX { + uint32_t route_str : 20; /**< Route String. */ + uint32_t speed : 4; /**< Device speed. */ + uint32_t resvd0 : 1; + uint32_t mtt : 1; /**< Multi-TT flag. */ + uint32_t hub : 1; /**< Hub flag. */ + uint32_t ctx_ent : 5; /**< Context entries. */ + uint32_t max_lat : 16; /**< Max exit latency in usec. */ + uint32_t rh_port : 8; /**< Root hub port number (1-based). */ + uint32_t n_ports : 8; /**< No. of ports for hubs. */ + uint32_t tt_slot : 8; /**< TT hub slot ID. */ + uint32_t tt_port : 8; /**< TT port number. */ + uint32_t ttt : 2; /**< TT Think Time. */ + uint32_t resvd1 : 4; + uint32_t intr_tgt : 10; /**< Interrupter Target. */ + uint32_t dev_addr : 8; /**< Device Address. */ + uint32_t resvd2 : 19; + uint32_t slot_state : 5; /**< Slot State. */ + uint32_t opaque[4]; /**< For xHC (i.e. our own) use. */ +} XHCI_SLOT_CTX; +AssertCompile(sizeof(XHCI_SLOT_CTX) == 0x20); + +/** @name Slot Context states + * @{ */ +#define XHCI_SLTST_ENDIS 0 /**< Enabled/Disabled. */ +#define XHCI_SLTST_DEFAULT 1 /**< Default. */ +#define XHCI_SLTST_ADDRESSED 2 /**< Addressed. */ +#define XHCI_SLTST_CONFIGURED 3 /**< Configured. */ +/** @} */ + +#ifdef IN_RING3 +/** Human-readable slot state descriptions for debugging. */ +static const char * const g_apszSltStates[] = { + "Enabled/Disabled", "Default", "Addressed", "Configured" /* 0-3 */ +}; +#endif + +/** Endpoint Context. */ +typedef struct sXHCI_EP_CTX { + uint32_t ep_state : 3; /**< Endpoint state. */ + uint32_t resvd0 : 5; + uint32_t mult : 2; /**< SS isoc burst count. */ + uint32_t maxps : 5; /**< Max Primary Streams. */ + uint32_t lsa : 1; /**< Linear Stream Array. */ + uint32_t interval : 8; /**< USB request interval. */ + uint32_t resvd1 : 8; + uint32_t resvd2 : 1; + uint32_t c_err : 2; /**< Error count. */ + uint32_t ep_type : 3; /**< Endpoint type. */ + uint32_t resvd3 : 1; + uint32_t hid : 1; /**< Host Initiate Disable. */ + uint32_t max_brs_sz : 8; /**< Max Burst Size. */ + uint32_t max_pkt_sz : 16; /**< Max Packet Size. */ + uint64_t trdp; /**< TR Dequeue Pointer. */ + uint32_t avg_trb_len : 16; /**< Average TRB Length. */ + uint32_t max_esit : 16; /**< Max EP Service Interval Time Payload. */ + /**< The rest for xHC (i.e. our own) use. */ + uint32_t last_frm : 16; /**< Last isochronous frame used (opaque). */ + uint32_t ifc : 8; /**< isoch in-flight TD count (opaque). */ + uint32_t last_cc : 8; /**< Last TRB completion code (opaque). */ + uint64_t trep; /**< TR Enqueue Pointer (opaque). */ +} XHCI_EP_CTX; +AssertCompile(sizeof(XHCI_EP_CTX) == 0x20); + +/** @name Endpoint Context states + * @{ */ +#define XHCI_EPST_DISABLED 0 /**< Disabled. */ +#define XHCI_EPST_RUNNING 1 /**< Running. */ +#define XHCI_EPST_HALTED 2 /**< Halted. */ +#define XHCI_EPST_STOPPED 3 /**< Not running/stopped. */ +#define XHCI_EPST_ERROR 4 /**< Not running/error. */ +/** @} */ + +/** @name Endpoint Type values + * @{ */ +#define XHCI_EPTYPE_INVALID 0 /**< Not valid. */ +#define XHCI_EPTYPE_ISOCH_OUT 1 /**< Isochronous Out. */ +#define XHCI_EPTYPE_BULK_OUT 2 /**< Bulk Out. */ +#define XHCI_EPTYPE_INTR_OUT 3 /**< Interrupt Out. */ +#define XHCI_EPTYPE_CONTROL 4 /**< Control Bidi. */ +#define XHCI_EPTYPE_ISOCH_IN 5 /**< Isochronous In. */ +#define XHCI_EPTYPE_BULK_IN 6 /**< Bulk In. */ +#define XHCI_EPTYPE_INTR_IN 7 /**< Interrupt In. */ +/** @} */ + +/* Pick out transfer type from endpoint. */ +#define XHCI_EP_XTYPE(a) (a & 3) + +/* Endpoint transfer types. */ +#define XHCI_XFTYPE_CONTROL 0 +#define XHCI_XFTYPE_ISOCH XHCI_EPTYPE_ISOCH_OUT +#define XHCI_XFTYPE_BULK XHCI_EPTYPE_BULK_OUT +#define XHCI_XFTYPE_INTR XHCI_EPTYPE_INTR_OUT + +/* Transfer Ring Dequeue Pointer address mask. */ +#define XHCI_TRDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) +#define XHCI_TRDP_DCS_MASK RT_BIT(0) /* Dequeue Cycle State bit. */ + + +#ifdef IN_RING3 + +/* Human-readable endpoint state descriptions for debugging. */ +static const char * const g_apszEpStates[] = { + "Disabled", "Running", "Halted", "Stopped", "Error" /* 0-4 */ +}; + +/* Human-readable endpoint type descriptions for debugging. */ +static const char * const g_apszEpTypes[] = { + "Not Valid", "Isoch Out", "Bulk Out", "Interrupt Out", /* 0-3 */ + "Control", "Isoch In", "Bulk In", "Interrupt In" /* 4-7 */ +}; + +#endif /* IN_RING3 */ + +/* Input Control Context. */ +typedef struct sXHCI_INPC_CTX { + uint32_t drop_flags; /* Drop Context flags (2-31). */ + uint32_t add_flags; /* Add Context flags (0-31). */ + uint32_t resvd[6]; +} XHCI_INPC_CTX; +AssertCompile(sizeof(XHCI_INPC_CTX) == 0x20); + +/* Make sure all contexts are the same size. */ +AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_SLOT_CTX)); +AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_INPC_CTX)); + +/* -= Event Ring Segment Table =- */ + +/** Event Ring Segment Table Entry. */ +typedef struct sXHCI_ERSTE { + uint64_t addr; + uint16_t size; + uint16_t resvd0; + uint32_t resvd1; +} XHCI_ERSTE; +AssertCompile(sizeof(XHCI_ERSTE) == 0x10); + + +/* -=-= Internal data structures not defined by xHCI =-=- */ + + +/** Device slot entry -- either slot context or endpoint context. */ +typedef union sXHCI_DS_ENTRY { + XHCI_SLOT_CTX sc; /**< Slot context. */ + XHCI_EP_CTX ep; /**< Endpoint context. */ +} XHCI_DS_ENTRY; + +/** Full device context (slot context + 31 endpoint contexts). */ +typedef struct sXHCI_DEV_CTX { + XHCI_DS_ENTRY entry[32]; +} XHCI_DEV_CTX; +AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_EP_CTX)); +AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_SLOT_CTX)); + +/** Pointer to the xHCI device state. */ +typedef struct XHCI *PXHCI; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * The xHCI controller data associated with each URB. + */ +typedef struct VUSBURBHCIINT +{ + /** The slot index. */ + uint8_t uSlotID; + /** Number of Tds in the array. */ + uint32_t cTRB; +} VUSBURBHCIINT; +#endif + +/** + * An xHCI root hub port, shared. + */ +typedef struct XHCIHUBPORT +{ + /** PORTSC: Port status/control register (R/W). */ + uint32_t portsc; + /** PORTPM: Power management status/control register (R/W). */ + uint32_t portpm; + /** PORTLI: USB3 port link information (R/O). */ + uint32_t portli; +} XHCIHUBPORT; +/** Pointer to a shared xHCI root hub port. */ +typedef XHCIHUBPORT *PXHCIHUBPORT; + +/** + * An xHCI root hub port, ring-3. + */ +typedef struct XHCIHUBPORTR3 +{ + /** Flag whether there is a device attached to the port. */ + bool fAttached; +} XHCIHUBPORTR3; +/** Pointer to a ring-3 xHCI root hub port. */ +typedef XHCIHUBPORTR3 *PXHCIHUBPORTR3; + +/** + * The xHCI root hub, ring-3 only. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + */ +typedef struct XHCIROOTHUBR3 +{ + /** Pointer to the parent xHC. */ + R3PTRTYPE(struct XHCIR3 *) pXhciR3; + /** 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 for this hub. */ + PDMLED Led; + + /** Number of actually implemented ports. */ + uint8_t cPortsImpl; + /** Index of first port for this hub. */ + uint8_t uPortBase; + + uint16_t Alignment0; /**< Force alignment. */ +#if HC_ARCH_BITS == 64 + uint32_t Alignment1; +#endif +} XHCIROOTHUBR3; +/** Pointer to a xHCI root hub (ring-3 only). */ +typedef XHCIROOTHUBR3 *PXHCIROOTHUBR3; + +/** + * An xHCI interrupter. + */ +typedef struct sXHCIINTRPTR +{ + /* Registers defined by xHCI. */ + /** IMAN: Interrupt Management Register (R/W). */ + uint32_t iman; + /** IMOD: Interrupt Moderation Register (R/W). */ + uint32_t imod; + /** ERSTSZ: Event Ring Segment Table Size (R/W). */ + uint32_t erstsz; + /* Reserved/padding. */ + uint32_t reserved; + /** ERSTBA: Event Ring Segment Table Base Address (R/W). */ + uint64_t erstba; + /** ERDP: Event Ring Dequeue Pointer (R/W). */ + uint64_t erdp; + /* Interrupter lock. */ + PDMCRITSECT lock; + /* Internal xHCI non-register state. */ + /** Internal Event Ring enqueue pointer. */ + uint64_t erep; + /** Internal ERDP re-write counter. */ + uint32_t erdp_rewrites; + /** This interrupter's index (for logging). */ + uint32_t index; + /** Internal index into Event Ring Segment Table. */ + uint16_t erst_idx; + /** Internal index into Event Ring Segment. */ + uint16_t trb_count; + /** Internal Event Ring Producer Cycle State. */ + bool evtr_pcs; + /** Internal Interrupt Pending Enable flag. */ + bool ipe; +} XHCIINTRPTR, *PXHCIINTRPTR; + +/** + * xHCI device state. + * @implements PDMILEDPORTS + */ +typedef struct XHCI +{ + /** MFINDEX wraparound timer. */ + TMTIMERHANDLE hWrapTimer; + +#ifdef XHCI_ERROR_INJECTION + bool fDropIntrHw; + bool fDropIntrIpe; + bool fDropUrb; + uint8_t Alignment00[1]; +#else + uint32_t Alignment00; /**< Force alignment. */ +#endif + + /** Flag indicating a sleeping worker thread. */ + volatile bool fWrkThreadSleeping; + volatile bool afPadding[3]; + + /** The event semaphore the worker thread waits on. */ + SUPSEMEVENT hEvtProcess; + + /** Bitmap for finished tasks (R3 -> Guest). */ + volatile uint32_t u32TasksFinished; + /** Bitmap for finished queued tasks (R3 -> Guest). */ + volatile uint32_t u32QueuedTasksFinished; + /** Bitmap for new queued tasks (Guest -> R3). */ + volatile uint32_t u32TasksNew; + + /** Copy of XHCIR3::RootHub2::cPortsImpl. */ + uint8_t cUsb2Ports; + /** Copy of XHCIR3::RootHub3::cPortsImpl. */ + uint8_t cUsb3Ports; + /** Sum of cUsb2Ports and cUsb3Ports. */ + uint8_t cTotalPorts; + /** Explicit padding. */ + uint8_t bPadding; + + /** Start of current frame. */ + uint64_t SofTime; + /** State of the individual ports. */ + XHCIHUBPORT aPorts[XHCI_NDP_MAX]; + /** Interrupters array. */ + XHCIINTRPTR aInterrupters[XHCI_NINTR]; + + /** @name Host Controller Capability Registers + * @{ */ + /** CAPLENGTH: base + CAPLENGTH = operational register start (R/O). */ + uint32_t cap_length; + /** HCIVERSION: host controller interface version (R/O). */ + uint32_t hci_version; + /** HCSPARAMS: Structural parameters 1 (R/O). */ + uint32_t hcs_params1; + /** HCSPARAMS: Structural parameters 2 (R/O). */ + uint32_t hcs_params2; + /** HCSPARAMS: Structural parameters 3 (R/O). */ + uint32_t hcs_params3; + /** HCCPARAMS: Capability parameters (R/O). */ + uint32_t hcc_params; + /** DBOFF: Doorbell offset (R/O). */ + uint32_t dbell_off; + /** RTSOFF: Run-time register space offset (R/O). */ + uint32_t rts_off; + /** @} */ + + /** @name Host Controller Operational Registers + * @{ */ + /** USB command register - USBCMD (R/W). */ + uint32_t cmd; + /** USB status register - USBSTS (R/W).*/ + uint32_t status; + /** Device Control Notification register - DNCTRL (R/W). */ + uint32_t dnctrl; + /** Configure Register (R/W). */ + uint32_t config; + /** Command Ring Control Register - CRCR (R/W). */ + uint64_t crcr; + /** Device Context Base Address Array Pointer (R/W). */ + uint64_t dcbaap; + /** @} */ + + /** Extended Capabilities storage. */ + uint8_t abExtCap[XHCI_EXT_CAP_SIZE]; + /** Size of valid extended capabilities. */ + uint32_t cbExtCap; + + uint32_t Alignment1; /**< Align cmdr_dqp. */ + + /** @name Internal xHCI non-register state + * @{ */ + /** Internal Command Ring dequeue pointer. */ + uint64_t cmdr_dqp; + /** Internal Command Ring Consumer Cycle State. */ + bool cmdr_ccs; + uint8_t aAlignment2[7]; /**< Force alignment. */ + /** Internal Device Slot states. */ + uint8_t aSlotState[XHCI_NDS]; + /** Internal doorbell states. Each bit corresponds to an endpoint. */ + uint32_t aBellsRung[XHCI_NDS]; + /** @} */ + + /** @name Model specific configuration + * @{ */ + /** ERST address mask. */ + uint64_t erst_addr_mask; + /** @} */ + + /** The MMIO region. */ + IOMMMIOHANDLE hMmio; + + /** Detected isochronous URBs completed with error. */ + STAMCOUNTER StatErrorIsocUrbs; + /** Detected isochronous packets (not URBs!) with error. */ + STAMCOUNTER StatErrorIsocPkts; + + /** Event TRBs written to event ring(s). */ + STAMCOUNTER StatEventsWritten; + /** Event TRBs not written to event ring(s) due to HC being stopped. */ + STAMCOUNTER StatEventsDropped; + /** Requests to set the IP bit. */ + STAMCOUNTER StatIntrsPending; + /** Actual interrupt deliveries. */ + STAMCOUNTER StatIntrsSet; + /** Interrupts not raised because they were disabled. */ + STAMCOUNTER StatIntrsNotSet; + /** A pending interrupt was cleared. */ + STAMCOUNTER StatIntrsCleared; + /** Number of TRBs that formed a single control URB. */ + STAMCOUNTER StatTRBsPerCtlUrb; + /** Number of TRBs that formed a single data (bulk/interrupt) URB. */ + STAMCOUNTER StatTRBsPerDtaUrb; + /** Number of TRBs that formed a single isochronous URB. */ + STAMCOUNTER StatTRBsPerIsoUrb; + /** Size of a control URB in bytes. */ + STAMCOUNTER StatUrbSizeCtrl; + /** Size of a data URB in bytes. */ + STAMCOUNTER StatUrbSizeData; + /** Size of an isochronous URB in bytes. */ + STAMCOUNTER StatUrbSizeIsoc; + +#ifdef VBOX_WITH_STATISTICS + /** @name Register access counters. + * @{ */ + STAMCOUNTER StatRdCaps; + STAMCOUNTER StatRdCmdRingCtlHi; + STAMCOUNTER StatRdCmdRingCtlLo; + STAMCOUNTER StatRdConfig; + STAMCOUNTER StatRdDevCtxBaapHi; + STAMCOUNTER StatRdDevCtxBaapLo; + STAMCOUNTER StatRdDevNotifyCtrl; + STAMCOUNTER StatRdDoorBell; + STAMCOUNTER StatRdEvtRingDeqPtrHi; + STAMCOUNTER StatRdEvtRingDeqPtrLo; + STAMCOUNTER StatRdEvtRsTblBaseHi; + STAMCOUNTER StatRdEvtRsTblBaseLo; + STAMCOUNTER StatRdEvtRstblSize; + STAMCOUNTER StatRdEvtRsvd; + STAMCOUNTER StatRdIntrMgmt; + STAMCOUNTER StatRdIntrMod; + STAMCOUNTER StatRdMfIndex; + STAMCOUNTER StatRdPageSize; + STAMCOUNTER StatRdPortLinkInfo; + STAMCOUNTER StatRdPortPowerMgmt; + STAMCOUNTER StatRdPortRsvd; + STAMCOUNTER StatRdPortStatusCtrl; + STAMCOUNTER StatRdUsbCmd; + STAMCOUNTER StatRdUsbSts; + STAMCOUNTER StatRdUnknown; + + STAMCOUNTER StatWrCmdRingCtlHi; + STAMCOUNTER StatWrCmdRingCtlLo; + STAMCOUNTER StatWrConfig; + STAMCOUNTER StatWrDevCtxBaapHi; + STAMCOUNTER StatWrDevCtxBaapLo; + STAMCOUNTER StatWrDevNotifyCtrl; + STAMCOUNTER StatWrDoorBell0; + STAMCOUNTER StatWrDoorBellN; + STAMCOUNTER StatWrEvtRingDeqPtrHi; + STAMCOUNTER StatWrEvtRingDeqPtrLo; + STAMCOUNTER StatWrEvtRsTblBaseHi; + STAMCOUNTER StatWrEvtRsTblBaseLo; + STAMCOUNTER StatWrEvtRstblSize; + STAMCOUNTER StatWrIntrMgmt; + STAMCOUNTER StatWrIntrMod; + STAMCOUNTER StatWrPortPowerMgmt; + STAMCOUNTER StatWrPortStatusCtrl; + STAMCOUNTER StatWrUsbCmd; + STAMCOUNTER StatWrUsbSts; + STAMCOUNTER StatWrUnknown; + /** @} */ +#endif +} XHCI; + +/** + * xHCI device state, ring-3 edition. + * @implements PDMILEDPORTS + */ +typedef struct XHCIR3 +{ + /** The async worker thread. */ + R3PTRTYPE(PPDMTHREAD) pWorkerThread; + /** The device instance. + * @note This is only so interface functions can get their bearings. */ + PPDMDEVINSR3 pDevIns; + + /** Status LUN: The base interface. */ + PDMIBASE IBase; + /** Status LUN: Leds interface. */ + PDMILEDPORTS ILeds; + /** Status LUN: Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + /** USB 2.0 Root hub device. */ + XHCIROOTHUBR3 RootHub2; + /** USB 3.0 Root hub device. */ + XHCIROOTHUBR3 RootHub3; + + /** State of the individual ports. */ + XHCIHUBPORTR3 aPorts[XHCI_NDP_MAX]; + + /** Critsect to synchronize worker and I/O completion threads. */ + RTCRITSECT CritSectThrd; +} XHCIR3; +/** Pointer to ring-3 xHCI device state. */ +typedef XHCIR3 *PXHCIR3; + +/** + * xHCI device data, ring-0 edition. + */ +typedef struct XHCIR0 +{ + uint32_t uUnused; +} XHCIR0; +/** Pointer to ring-0 xHCI device data. */ +typedef struct XHCIR0 *PXHCIR0; + + +/** + * xHCI device data, raw-mode edition. + */ +typedef struct XHCIRC +{ + uint32_t uUnused; +} XHCIRC; +/** Pointer to raw-mode xHCI device data. */ +typedef struct XHCIRC *PXHCIRC; + + +/** @typedef XHCICC + * The xHCI device data for the current context. */ +typedef CTX_SUFF(XHCI) XHCICC; +/** @typedef PXHCICC + * Pointer to the xHCI device for the current context. */ +typedef CTX_SUFF(PXHCI) PXHCICC; + + +/* -=-= Local implementation details =-=- */ + +typedef enum sXHCI_JOB { + XHCI_JOB_PROCESS_CMDRING, /**< Process the command ring. */ + XHCI_JOB_DOORBELL, /**< A doorbell (other than DB0) was rung. */ + XHCI_JOB_XFER_DONE, /**< Transfer completed, look for more work. */ + XHCI_JOB_MAX +} XHCI_JOB; + +/* -=-=- Local xHCI definitions -=-=- */ + +/** @name USB states. + * @{ */ +#define XHCI_USB_RESET 0x00 +#define XHCI_USB_RESUME 0x40 +#define XHCI_USB_OPERATIONAL 0x80 +#define XHCI_USB_SUSPEND 0xc0 +/** @} */ + +/* Primary interrupter (for readability). */ +#define XHCI_PRIMARY_INTERRUPTER 0 + +/** @name Device Slot states. + * @{ */ +#define XHCI_DEVSLOT_EMPTY 0 +#define XHCI_DEVSLOT_ENABLED 1 +#define XHCI_DEVSLOT_DEFAULT 2 +#define XHCI_DEVSLOT_ADDRESSED 3 +#define XHCI_DEVSLOT_CONFIGURED 4 +/** @} */ + +/** Get the pointer to a root hub corresponding to given port index. */ +#define GET_PORT_PRH(a_pThisCC, a_uPort) \ + ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl ? &(a_pThisCC)->RootHub3 : &(a_pThisCC)->RootHub2) +#define GET_VUSB_PORT_FROM_XHCI_PORT(a_pRh, a_iPort) \ + (((a_iPort) - (a_pRh)->uPortBase) + 1) +#define GET_XHCI_PORT_FROM_VUSB_PORT(a_pRh, a_uPort) \ + ((a_pRh)->uPortBase + (a_uPort) - 1) + +/** Check if port corresponding to index is USB3, using shared data. */ +#define IS_USB3_PORT_IDX_SHR(a_pThis, a_uPort) ((a_uPort) >= (a_pThis)->cUsb2Ports) + +/** Check if port corresponding to index is USB3, using ring-3 data. */ +#define IS_USB3_PORT_IDX_R3(a_pThisCC, a_uPort) ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl) + +/** Query the number of configured USB2 ports. */ +#define XHCI_NDP_USB2(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub2.cPortsImpl) + +/** Query the number of configured USB3 ports. */ +#define XHCI_NDP_USB3(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub3.cPortsImpl) + +/** Query the total number of configured ports. */ +#define XHCI_NDP_CFG(a_pThis) ((unsigned)RT_MIN((a_pThis)->cTotalPorts, XHCI_NDP_MAX)) + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +#ifdef IN_RING3 + +/** Build a Protocol extended capability. */ +static uint32_t xhciR3BuildProtocolCaps(uint8_t *pbCap, uint32_t cbMax, int cPorts, int nPortOfs, int ver) +{ + uint32_t *pu32Cap = (uint32_t *)pbCap; + unsigned cPsi; + + Assert(nPortOfs + cPorts < 255); + Assert(ver == 2 || ver == 3); + + cPsi = 0; /* Currently only implied port speed IDs. */ + + /* Make sure there's enough room. */ + if (cPsi * 4 + 16 > cbMax) + return 0; + + /* Header - includes (USB) specification version. */ + *pu32Cap++ = (ver << 24) | (0 << 16) | XHCI_XCP_PROTOCOL; + /* Specification - 'USB ' */ + *pu32Cap++ = 0x20425355; + /* Port offsets and counts. 1-based! */ + *pu32Cap++ = (cPsi << 28) | (cPorts << 8) | (nPortOfs + 1); + /* Reserved dword. */ + *pu32Cap++ = 0; + + return (uint8_t *)pu32Cap - pbCap; +} + + +/** Add an extended capability and link it into the chain. */ +static int xhciR3AddExtCap(PXHCI pThis, const uint8_t *pCap, uint32_t cbCap, uint32_t *puPrevOfs) +{ + Assert(*puPrevOfs <= pThis->cbExtCap); + Assert(!(cbCap & 3)); + + /* Check that the extended capability is sane. */ + if (cbCap == 0) + return VERR_BUFFER_UNDERFLOW; + if (pThis->cbExtCap + cbCap > XHCI_EXT_CAP_SIZE) + return VERR_BUFFER_OVERFLOW; + if (cbCap > 255 * 4) /* Size must fit into 8-bit dword count. */ + return VERR_BUFFER_OVERFLOW; + + /* Copy over the capability data and update offsets. */ + memcpy(pThis->abExtCap + pThis->cbExtCap, pCap, cbCap); + pThis->abExtCap[*puPrevOfs + 1] = cbCap >> 2; + pThis->abExtCap[pThis->cbExtCap + 1] = 0; + *puPrevOfs = pThis->cbExtCap; + pThis->cbExtCap += cbCap; + return VINF_SUCCESS; +} + +/** Build the xHCI Extended Capabilities region. */ +static int xhciR3BuildExtCaps(PXHCI pThis, PXHCICC pThisCC) +{ + int rc; + uint8_t abXcp[MAX_XCAP_SIZE]; + uint32_t cbXcp; + uint32_t uPrevOfs = 0; + + Assert(XHCI_NDP_USB2(pThisCC)); + Assert(XHCI_NDP_USB3(pThisCC)); + + /* Most of the extended capabilities are optional or not relevant for PCI + * implementations. However, the Supported Protocol caps are required. + */ + cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB2(pThisCC), 0, 2); + rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs); + AssertReturn(RT_SUCCESS(rc), rc); + + cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB3(pThisCC), XHCI_NDP_USB2(pThisCC), 3); + rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs); + AssertReturn(RT_SUCCESS(rc), rc); + + return VINF_SUCCESS; +} + + +/** + * Select an unused device address. Note that this may fail in the unlikely + * case where all possible addresses are exhausted. + */ +static uint8_t xhciR3SelectNewAddress(PXHCI pThis, uint8_t uSlotID) +{ + RT_NOREF(pThis, uSlotID); + + /* + * Since there is a 1:1 mapping between USB devices and device slots, we + * should be able to assign a USB address which equals slot ID to any USB + * device. However, the address selection algorithm could be completely + * different (it is not defined by the xHCI spec). + */ + return uSlotID; +} + + +/** + * Read the address of a device context for a slot from the DCBAA. + * + * @returns Given slot's device context base address. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to get the context address of. + */ +static uint64_t xhciR3FetchDevCtxAddr(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID) +{ + uint64_t uCtxAddr; + RTGCPHYS GCPhysDCBAAE; + + Assert(uSlotID > 0); + Assert(uSlotID < XHCI_NDS); + + /* Fetch the address of the output slot context from the DCBAA. */ + GCPhysDCBAAE = pThis->dcbaap + uSlotID * sizeof(uint64_t); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysDCBAAE, &uCtxAddr, sizeof(uCtxAddr)); + LogFlowFunc(("Slot ID %u, device context @ %RGp\n", uSlotID, uCtxAddr)); + Assert(uCtxAddr); + + return uCtxAddr & XHCI_CTX_ADDR_MASK; +} + + +/** + * Fetch a device's slot or endpoint context from memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Device Context Index. + * @param pCtx Pointer to storage for the context. + */ +static int xhciR3FetchDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, void *pCtx) +{ + RTGCPHYS GCPhysCtx; + + GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + LogFlowFunc(("Reading device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI)); + GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCtx, pCtx, sizeof(XHCI_SLOT_CTX)); + return VINF_SUCCESS; +} + + +/** + * Fetch a device's slot and endpoint contexts from guest memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Endpoint Device Context Index. + * @param pSlot Pointer to storage for the slot context. + * @param pEp Pointer to storage for the endpoint context. + */ +static int xhciR3FetchCtxAndEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_SLOT_CTX *pSlot, XHCI_EP_CTX *pEp) +{ + AssertPtr(pSlot); + AssertPtr(pEp); + Assert(uDCI); /* Can't be 0 -- that's the device context. */ + + /* Load the slot context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, pSlot); + /// @todo sanity check the slot context here? + Assert(pSlot->ctx_ent >= uDCI); + + /* Load the endpoint context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, uDCI, pEp); + /// @todo sanity check the endpoint context here? + + return VINF_SUCCESS; +} + + +/** + * Update an endpoint context in guest memory. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID Slot ID to access. + * @param uDCI Endpoint Device Context Index. + * @param pEp Pointer to storage of the endpoint context. + */ +static int xhciR3WriteBackEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_EP_CTX *pEp) +{ + RTGCPHYS GCPhysCtx; + + AssertPtr(pEp); + Assert(uDCI); /* Can't be 0 -- that's the device context. */ + + /// @todo sanity check the endpoint context here? + /* Find the physical address. */ + GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + LogFlowFunc(("Writing device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI)); + GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX); + /* Write the updated context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysCtx, pEp, sizeof(XHCI_EP_CTX)); + + return VINF_SUCCESS; +} + + +/** + * Modify an endpoint context such that it enters the running state. + * + * @param pEpCtx Pointer to the endpoint context. + */ +static void xhciR3EnableEP(XHCI_EP_CTX *pEpCtx) +{ + LogFlow(("Enabling EP, TRDP @ %RGp, DCS=%u\n", pEpCtx->trdp & XHCI_TRDP_ADDR_MASK, pEpCtx->trdp & XHCI_TRDP_DCS_MASK)); + pEpCtx->ep_state = XHCI_EPST_RUNNING; + pEpCtx->trep = pEpCtx->trdp; +} + +#endif /* IN_RING3 */ + +#define MFIND_PERIOD_NS (UINT64_C(2048) * 1000000) + +/** + * Set up the MFINDEX wrap timer. + */ +static void xhciSetWrapTimer(PPDMDEVINS pDevIns, PXHCI pThis) +{ + uint64_t u64Now; + uint64_t u64LastWrap; + uint64_t u64Expire; + int rc; + + /* Try to avoid drift. */ + u64Now = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer); +// u64LastWrap = u64Now - (u64Now % (0x3FFF * 125000)); + u64LastWrap = u64Now / MFIND_PERIOD_NS * MFIND_PERIOD_NS; + /* The MFINDEX counter wraps around every 2048 milliseconds. */ + u64Expire = u64LastWrap + (uint64_t)2048 * 1000000; + rc = PDMDevHlpTimerSet(pDevIns, pThis->hWrapTimer, u64Expire); + AssertRC(rc); +} + +/** + * Determine whether MSI/MSI-X is enabled for this PCI device. + * + * This influences interrupt handling in xHCI. NB: There should be a PCIDevXxx + * function for this. + */ +static bool xhciIsMSIEnabled(PPDMPCIDEV pDevIns) +{ + uint16_t uMsgCtl; + + uMsgCtl = PDMPciDevGetWord(pDevIns, XHCI_PCI_MSI_CAP_OFS + VBOX_MSI_CAP_MESSAGE_CONTROL); + return !!(uMsgCtl & VBOX_PCI_MSI_FLAGS_ENABLE); +} + +/** + * Get the worker thread going -- there's something to do. + */ +static void xhciKickWorker(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_JOB enmJob, uint32_t uWorkDesc) +{ + RT_NOREF(enmJob, uWorkDesc); + + /* Tell the worker thread there's something to do. */ + if (ASMAtomicReadBool(&pThis->fWrkThreadSleeping)) + { + LogFlowFunc(("Signal event semaphore\n")); + int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess); + AssertRC(rc); + } +} + +/** + * Fetch the current ERST entry from guest memory. + */ +static void xhciFetchErstEntry(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + RTGCPHYS GCPhysErste; + XHCI_ERSTE entry; + + Assert(ip->erst_idx < ip->erstsz); + GCPhysErste = ip->erstba + ip->erst_idx * sizeof(XHCI_ERSTE); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry)); + + /* + * 6.5 claims values in 16-4096 range are valid, but does not say what + * happens for values outside of that range... + */ + Assert((pThis->status & XHCI_STATUS_HCH) || (entry.size >= 16 && entry.size <= 4096)); + + /* Cache the entry data internally. */ + ip->erep = entry.addr & pThis->erst_addr_mask; + ip->trb_count = entry.size; + Log(("Fetched ERST Entry at %RGp: %u entries at %RGp\n", GCPhysErste, ip->trb_count, ip->erep)); +} + +/** + * Set the interrupter's IP and EHB bits and trigger an interrupt if required. + * + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param ip Pointer to the interrupter structure. + * + */ +static void xhciSetIntr(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + Assert(pThis && ip); + LogFlowFunc(("old IP: %u\n", !!(ip->iman & XHCI_IMAN_IP))); + + if (!(ip->iman & XHCI_IMAN_IP)) + { + /// @todo assert that we own the interrupter lock + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_EINT); + ASMAtomicOrU64(&ip->erdp, XHCI_ERDP_EHB); + ASMAtomicOrU32(&ip->iman, XHCI_IMAN_IP); + if ((ip->iman & XHCI_IMAN_IE) && (pThis->cmd & XHCI_CMD_INTE)) + { +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropIntrHw) + { + pThis->fDropIntrHw = false; + ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP); + } + else +#endif + { + Log2(("Triggering interrupt on interrupter %u\n", ip->index)); + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + STAM_COUNTER_INC(&pThis->StatIntrsSet); + } + } + else + { + Log2(("Not triggering interrupt on interrupter %u (interrupts disabled)\n", ip->index)); + STAM_COUNTER_INC(&pThis->StatIntrsNotSet); + } + + /* If MSI/MSI-X is in use, the IP bit is immediately cleared again. */ + if (xhciIsMSIEnabled(pDevIns->apPciDevs[0])) + ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP); + } +} + +#ifdef IN_RING3 + +/** + * Set the interrupter's IPE bit. If this causes a 0->1 transition, an + * interrupt may be triggered. + * + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param ip Pointer to the interrupter structure. + */ +static void xhciR3SetIntrPending(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip) +{ + uint16_t imodc = (ip->imod >> XHCI_IMOD_IMODC_SHIFT) & XHCI_IMOD_IMODC_MASK; + + Assert(pThis && ip); + LogFlowFunc(("old IPE: %u, IMODC: %u, EREP: %RGp, EHB: %u\n", ip->ipe, imodc, (RTGCPHYS)ip->erep, !!(ip->erdp & XHCI_ERDP_EHB))); + STAM_COUNTER_INC(&pThis->StatIntrsPending); + + if (!ip->ipe) + { +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropIntrIpe) + { + pThis->fDropIntrIpe = false; + } + else +#endif + { + ip->ipe = true; + if (!(ip->erdp & XHCI_ERDP_EHB) && (imodc == 0)) + xhciSetIntr(pDevIns, pThis, ip); + } + } +} + + +/** + * Check if there is space available for writing at least two events on the + * event ring. See 4.9.4 for the state machine (right hand side of diagram). + * If there's only room for one event, the Event Ring Full TRB will need to + * be written out, hence the ring is considered full. + * + * @returns True if space is available, false otherwise. + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param pIntr Pointer to the interrupter structure. + */ +static bool xhciR3IsEvtRingFull(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr) +{ + uint64_t next_ptr; + uint64_t erdp = pIntr->erdp & XHCI_ERDP_ADDR_MASK; + + if (pIntr->trb_count > 1) + { + /* Check the current segment. */ + next_ptr = pIntr->erep + sizeof(XHCI_EVENT_TRB); + } + else + { + uint16_t erst_idx; + XHCI_ERSTE entry; + RTGCPHYS GCPhysErste; + + /* Need to check the next segment. */ + erst_idx = pIntr->erst_idx + 1; + if (erst_idx == pIntr->erstsz) + erst_idx = 0; + GCPhysErste = pIntr->erstba + erst_idx * sizeof(XHCI_ERSTE); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry)); + next_ptr = entry.addr & pThis->erst_addr_mask; + } + + /// @todo We'll have to remember somewhere that the ring is full + return erdp == next_ptr; +} + +/** + * Write an event to the given Event Ring. This implements a good chunk of + * the event ring state machine in section 4.9.4 of the xHCI spec. + * + * @returns VBox status code. Error if event could not be enqueued. + * @param pDevIns The PDM device instance. + * @param pThis Pointer to the xHCI state. + * @param pEvent Pointer to the Event TRB to be enqueued. + * @param iIntr Index of the interrupter to write to. + * @param fBlockInt Set if interrupt should be blocked (BEI bit). + */ +static int xhciR3WriteEvent(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_EVENT_TRB *pEvent, unsigned iIntr, bool fBlockInt) +{ + PXHCIINTRPTR pIntr; + int rc = VINF_SUCCESS; + + LogFlowFunc(("Interrupter: %u\n", iIntr)); + + /* If the HC isn't running, events can not be generated. However, + * especially port change events can be triggered at any time. We just + * drop them here -- it's often not an error condition. + */ + if (pThis->cmd & XHCI_CMD_RS) + { + STAM_COUNTER_INC(&pThis->StatEventsWritten); + Assert(iIntr < XHCI_NINTR); /* Supplied by guest, potentially invalid. */ + pIntr = &pThis->aInterrupters[iIntr & XHCI_INTR_MASK]; + + /* + * If the interrupter/event ring isn't in a sane state, just + * give up and report Host Controller Error (HCE). + */ + // pIntr->erst_idx + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pIntr->lock, VERR_IGNORED); /* R3 only, no rcBusy. */ + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pIntr->lock, rcLock); /* eventually, most call chains ignore the status. */ + + if (xhciR3IsEvtRingFull(pDevIns, pThis, pIntr)) + { + LogRel(("xHCI: Event ring full!\n")); + } + + /* Set the TRB's Cycle bit as appropriate. */ + pEvent->gen.cycle = pIntr->evtr_pcs; + + /* Write out the TRB and advance the EREP. */ + /// @todo This either has to be atomic from the guest's POV or the cycle bit needs to be toggled last!! + PDMDevHlpPCIPhysWriteMeta(pDevIns, pIntr->erep, pEvent, sizeof(*pEvent)); + pIntr->erep += sizeof(*pEvent); + --pIntr->trb_count; + + /* Advance to the next ERST entry if necessary. */ + if (pIntr->trb_count == 0) + { + ++pIntr->erst_idx; + /* If necessary, roll over back to the beginning. */ + if (pIntr->erst_idx == pIntr->erstsz) + { + pIntr->erst_idx = 0; + pIntr->evtr_pcs = !pIntr->evtr_pcs; + } + xhciFetchErstEntry(pDevIns, pThis, pIntr); + } + + /* Set the IPE bit unless interrupts are blocked. */ + if (!fBlockInt) + xhciR3SetIntrPending(pDevIns, pThis, pIntr); + + PDMDevHlpCritSectLeave(pDevIns, &pIntr->lock); + } + else + { + STAM_COUNTER_INC(&pThis->StatEventsDropped); + Log(("Event dropped because HC is not running.\n")); + } + + return rc; +} + + +/** + * Post a port change TRB to an Event Ring. + */ +static int xhciR3GenPortChgEvent(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uPort) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Port ID: %u\n", uPort)); + + /* + * Devices can be "physically" attached/detached regardless of whether + * the HC is running or not, but the port status change events can only + * be generated when R/S is set; xhciR3WriteEvent() takes care of that. + */ + RT_ZERO(ed); + ed.psce.cc = XHCI_TCC_SUCCESS; + ed.psce.port_id = uPort; + ed.psce.type = XHCI_TRB_PORT_SC; + return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); +} + + +/** + * Post a command completion TRB to an Event Ring. + */ +static int xhciR3PostCmdCompletion(PPDMDEVINS pDevIns, PXHCI pThis, unsigned cc, unsigned uSlotID) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Cmd @ %RGp, Completion Code: %u (%s), Slot ID: %u\n", (RTGCPHYS)pThis->cmdr_dqp, cc, + cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!", uSlotID)); + + /* The Command Ring dequeue pointer still holds the address of the current + * command TRB. It is written to the completion event TRB as the command + * TRB pointer. + */ + RT_ZERO(ed); + ed.cce.trb_ptr = pThis->cmdr_dqp; + ed.cce.cc = cc; + ed.cce.type = XHCI_TRB_CMD_CMPL; + ed.cce.slot_id = uSlotID; + return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); +} + + +/** + * Post a transfer event TRB to an Event Ring. + */ +static int xhciR3PostXferEvent(PPDMDEVINS pDevIns, PXHCI pThis, unsigned uIntTgt, unsigned uXferLen, unsigned cc, + unsigned uSlotID, unsigned uEpDCI, uint64_t uEvtData, bool fBlockInt, bool fEvent) +{ + XHCI_EVENT_TRB ed; /* Event Descriptor */ + LogFlowFunc(("Xfer @ %RGp, Completion Code: %u (%s), Slot ID=%u DCI=%u Target=%u EvtData=%RX64 XfrLen=%u BEI=%u ED=%u\n", + (RTGCPHYS)pThis->cmdr_dqp, cc, cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!", + uSlotID, uEpDCI, uIntTgt, uEvtData, uXferLen, fBlockInt, fEvent)); + + /* A transfer event may be either generated by TRB completion (in case + * fEvent=false) or by a special transfer event TRB (fEvent=true). In + * either case, interrupts may be suppressed. + */ + RT_ZERO(ed); + ed.te.trb_ptr = uEvtData; + ed.te.xfr_len = uXferLen; + ed.te.cc = cc; + ed.te.ed = fEvent; + ed.te.type = XHCI_TRB_XFER; + ed.te.ep_id = uEpDCI; + ed.te.slot_id = uSlotID; + return xhciR3WriteEvent(pDevIns, pThis, &ed, uIntTgt, fBlockInt); /* Sets the cycle bit, too. */ +} + + +static int xhciR3FindRhDevBySlot(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, PXHCIROOTHUBR3 *ppRh, uint32_t *puPort) +{ + XHCI_SLOT_CTX slot_ctx; + PXHCIROOTHUBR3 pRh; + unsigned iPort; + int rc; + + /// @todo Do any of these need to be release assertions? + Assert(uSlotID <= RT_ELEMENTS(pThis->aSlotState)); + Assert(pThis->aSlotState[ID_TO_IDX(uSlotID)] > XHCI_DEVSLOT_EMPTY); + + /* Load the slot context. */ + xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, &slot_ctx); + + /* The port ID is stored in the slot context. */ + iPort = ID_TO_IDX(slot_ctx.rh_port); + if (iPort < XHCI_NDP_CFG(pThis)) + { + /* Find the corresponding root hub. */ + pRh = GET_PORT_PRH(pThisCC, iPort); + Assert(pRh); + + /* And the device; if the device was ripped out fAttached will be false. */ + if (pThisCC->aPorts[iPort].fAttached) + { + /* Provide the information the caller asked for. */ + if (ppRh) + *ppRh = pRh; + if (puPort) + *puPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort); + rc = VINF_SUCCESS; + } + else + { + LogFunc(("No device attached (port index %u)!\n", iPort)); + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + } + } + else + { + LogFunc(("Port out of range (index %u)!\n", iPort)); + rc = VERR_INVALID_PARAMETER; + } + return rc; +} + + +static void xhciR3EndlessTrbError(PPDMDEVINS pDevIns, PXHCI pThis) +{ + /* Clear the R/S bit and indicate controller error. */ + ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE); + + /* Ensure that XHCI_STATUS_HCH gets set by the worker thread. */ + xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0); + + LogRelMax(10, ("xHCI: Attempted to process too many TRBs, stopping xHC!\n")); +} + +/** + * TRB walker callback prototype. + * + * @returns true if walking should continue. + * @returns false if walking should be terminated. + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param pXferTRB Pointer to the transfer TRB to handle. + * @param GCPhysXfrTRB Physical address of the TRB. + * @param pvContext User-defined walk context. + * @remarks We don't need to use DECLCALLBACKPTR here, since all users are in + * the same source file, but having the functions marked with + * DECLCALLBACK helps readability. + */ +typedef DECLCALLBACKPTR(bool, PFNTRBWALKCB,(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, + RTGCPHYS GCPhysXfrTRB, void *pvContext)); + + +/** + * Walk a chain of TRBs which comprise a single TD. + * + * This is something we need to do potentially more than once when submitting a + * URB and then often again when completing the URB. Note that the walker does + * not update the endpoint state (TRDP/TREP/DCS) so that it can be re-run + * multiple times. + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uTRP Initial TR pointer and DCS. + * @param pfnCbk Callback routine. + * @param pvContext User-defined walk context. + * @param pTREP Pointer to storage for final TR Enqueue Pointer/DCS. + */ +static int xhciR3WalkXferTrbChain(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uTRP, + PFNTRBWALKCB pfnCbk, void *pvContext, uint64_t *pTREP) +{ + RTGCPHYS GCPhysXfrTRB; + uint64_t uTREP; + XHCI_XFER_TRB XferTRB; + bool fContinue = true; + bool dcs; + int rc = VINF_SUCCESS; + unsigned cTrbs = 0; + + AssertPtr(pvContext); + AssertPtr(pTREP); + Assert(uTRP); + + /* Find the transfer TRB address and the DCS. */ + GCPhysXfrTRB = uTRP & XHCI_TRDP_ADDR_MASK; + dcs = !!(uTRP & XHCI_TRDP_DCS_MASK); /* MSC upgrades bool to signed something when comparing with a uint8_t:1. */ + LogFlowFunc(("Walking Transfer Ring, TREP:%RGp DCS=%u\n", GCPhysXfrTRB, dcs)); + + do { + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB)); + + if ((bool)XferTRB.gen.cycle == dcs) + { + Log2(("Walking TRB@%RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, XferTRB.gen.type, + XferTRB.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[XferTRB.gen.type] : "WHAT?!!", + XferTRB.gen.xfr_len, XferTRB.gen.ent, XferTRB.gen.isp, XferTRB.gen.ns, XferTRB.gen.ch, XferTRB.gen.ioc, XferTRB.gen.idt)); + + /* DCS matches, the TRB is ours to process. */ + switch (XferTRB.gen.type) { + case XHCI_TRB_LINK: + Log2(("Link intra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", XferTRB.link.rseg_ptr, XferTRB.link.ioc, XferTRB.link.toggle, XferTRB.link.chain)); + Assert(XferTRB.link.chain); + /* Do not update the actual TRDP/TREP and DCS yet, just the temporary images. */ + GCPhysXfrTRB = XferTRB.link.rseg_ptr & XHCI_TRDP_ADDR_MASK; + if (XferTRB.link.toggle) + dcs = !dcs; + Assert(!XferTRB.link.ioc); /// @todo Needs to be reported. + break; + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + case XHCI_TRB_EVT_DATA: + fContinue = pfnCbk(pDevIns, pThis, &XferTRB, GCPhysXfrTRB, pvContext); + GCPhysXfrTRB += sizeof(XferTRB); + break; + default: + /* NB: No-op TRBs are not allowed within TDs (4.11.7). */ + Log(("Bad TRB type %u found within TD!!\n", XferTRB.gen.type)); + fContinue = false; + /// @todo Stop EP etc.? + } + } + else + { + /* We don't have a complete TD. Interesting times. */ + Log2(("DCS mismatch, no more TRBs available.\n")); + fContinue = false; + rc = VERR_TRY_AGAIN; + } + + /* Kill the xHC if the TRB list has no end in sight. */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* Inform caller of the new TR Enqueue Pointer/DCS (not necessarily changed). */ + Assert(!(GCPhysXfrTRB & ~XHCI_TRDP_ADDR_MASK)); + uTREP = GCPhysXfrTRB | (unsigned)dcs; + Log2(("Final TRP after walk: %RGp\n", uTREP)); + *pTREP = uTREP; + + return rc; +} + + +/** Context for probing TD size. */ +typedef struct { + uint32_t uXferLen; + uint32_t cTRB; + uint32_t uXfrLenLastED; + uint32_t cTRBLastED; +} XHCI_CTX_XFER_PROBE; + + +/** Context for submitting 'out' TDs. */ +typedef struct { + PVUSBURB pUrb; + uint32_t uXferPos; + unsigned cTRB; +} XHCI_CTX_XFER_SUBMIT; + + +/** Context for completing TDs. */ +typedef struct { + PVUSBURB pUrb; + uint32_t uXferPos; + uint32_t uXferLeft; + unsigned cTRB; + uint32_t uEDTLA : 24; + uint32_t uLastCC : 8; + uint8_t uSlotID; + uint8_t uEpDCI; + bool fMaxCount; +} XHCI_CTX_XFER_COMPLETE; + + +/** Context for building isochronous URBs. */ +typedef struct { + PVUSBURB pUrb; + unsigned iPkt; + uint32_t offCur; + uint64_t uInitTREP; + bool fSubmitFailed; +} XHCI_CTX_ISOCH; + + +/** + * @callback_method_impl{PFNTRBWALKCB, + * Probe a TD and figure out how big it is so that a URB can be allocated to back it.} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsProbe(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + RT_NOREF(pDevIns, pThis, GCPhysXfrTRB); + XHCI_CTX_XFER_PROBE *pCtx = (XHCI_CTX_XFER_PROBE *)pvContext; + + pCtx->cTRB++; + + /* Only consider TRBs which transfer data. */ + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + pCtx->uXferLen += pXferTRB->norm.xfr_len; + if (RT_UNLIKELY(pCtx->uXferLen > XHCI_MAX_TD_SIZE)) + { + /* NB: We let the TD size get a bit past the max so that we don't lose anything, + * but the EDTLA will wrap around. + */ + LogRelMax(10, ("xHCI: TD size (%u) too big, not continuing!\n", pCtx->uXferLen)); + return false; + } + break; + case XHCI_TRB_EVT_DATA: + /* Remember where the last seen Event Data TRB was. */ + pCtx->cTRBLastED = pCtx->cTRB; + pCtx->uXfrLenLastED = pCtx->uXferLen; + break; + default: /* Could be a link TRB, too. */ + break; + } + + return pXferTRB->gen.ch; +} + + +/** + * @callback_method_impl{PFNTRBWALKCB, + * Copy data from a TD (TRB chain) into the corresponding TD. OUT direction only.} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsSubmit(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + RT_NOREF(pThis, GCPhysXfrTRB); + XHCI_CTX_XFER_SUBMIT *pCtx = (XHCI_CTX_XFER_SUBMIT *)pvContext; + uint32_t uXferLen = pXferTRB->norm.xfr_len; + + + /* Only consider TRBs which transfer data. */ + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: + case XHCI_TRB_STATUS_STG: + /* NB: Transfer length may be zero! */ + /// @todo explain/verify abuse of various TRB types here (data stage mapped to normal etc.). + if (uXferLen) + { + /* Sanity check for broken guests (TRBs may have changed since probing). */ + if (pCtx->uXferPos + uXferLen <= pCtx->pUrb->cbData) + { + /* Data might be immediate or elsewhere in memory. */ + if (pXferTRB->norm.idt) + { + /* If an immediate data TRB claims there's more than 8 bytes, we have a problem. */ + if (uXferLen > 8) + { + LogRelMax(10, ("xHCI: Immediate data TRB length %u bytes, ignoring!\n", uXferLen)); + return false; /* Stop walking the chain immediately. */ + } + + Assert(uXferLen >= 1 && uXferLen <= 8); + Log2(("Copying %u bytes to URB offset %u (immediate data)\n", uXferLen, pCtx->uXferPos)); + memcpy(pCtx->pUrb->abData + pCtx->uXferPos, pXferTRB, uXferLen); + } + else + { + PDMDevHlpPCIPhysReadUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen); + Log2(("Copying %u bytes to URB offset %u (from %RGp)\n", uXferLen, pCtx->uXferPos, pXferTRB->norm.data_ptr)); + } + pCtx->uXferPos += uXferLen; + } + else + { + LogRelMax(10, ("xHCI: Attempted to submit too much data, ignoring!\n")); + return false; /* Stop walking the chain immediately. */ + } + + } + break; + default: /* Could be an event or status stage TRB, too. */ + break; + } + pCtx->cTRB++; + + /// @todo Maybe have to make certain that the number of probed TRBs matches? Potentially + /// by the time TRBs get submitted, there might be more of them available if the TD was + /// initially not fully written by HCD. + + return pXferTRB->gen.ch; +} + + +/** + * Perform URB completion processing. + * + * Figure out how much data was really transferred, post events if required, and + * for IN transfers, copy data from the URB. + * + * @callback_method_impl{PFNTRBWALKCB} + */ +static DECLCALLBACK(bool) +xhciR3WalkDataTRBsComplete(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext) +{ + XHCI_CTX_XFER_COMPLETE *pCtx = (XHCI_CTX_XFER_COMPLETE *)pvContext; + int rc; + unsigned uXferLen; + unsigned uResidue; + uint8_t cc; + bool fKeepGoing = true; + + switch (pXferTRB->gen.type) + { + case XHCI_TRB_NORMAL: + case XHCI_TRB_ISOCH: + case XHCI_TRB_SETUP_STG: + case XHCI_TRB_DATA_STG: /// @todo document abuse; esp. check BEI bit + case XHCI_TRB_STATUS_STG: + /* Assume successful transfer. */ + uXferLen = pXferTRB->norm.xfr_len; + cc = XHCI_TCC_SUCCESS; + + /* If there was a short packet, handle it accordingly. */ + if (pCtx->uXferLeft < uXferLen) + { + /* The completion code is set regardless of IOC/ISP. It may be + * reported later via an Event Data TRB (4.10.1.1) + */ + uXferLen = pCtx->uXferLeft; + cc = XHCI_TCC_SHORT_PKT; + } + + if (pCtx->pUrb->enmDir == VUSBDIRECTION_IN) + { + Assert(!pXferTRB->norm.idt); + + /* NB: Transfer length may be zero! */ + if (uXferLen) + { + if (uXferLen <= pCtx->uXferLeft) + { + Log2(("Writing %u bytes to %RGp from URB offset %u (TRB@%RGp)\n", uXferLen, pXferTRB->norm.data_ptr, pCtx->uXferPos, GCPhysXfrTRB)); + PDMDevHlpPCIPhysWriteUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen); + } + else + { + LogRelMax(10, ("xHCI: Attempted to read too much data, ignoring!\n")); + } + } + } + + /* Update position within TD. */ + pCtx->uXferLeft -= uXferLen; + pCtx->uXferPos += uXferLen; + Log2(("Current uXferLeft=%u, uXferPos=%u (length was %u)\n", pCtx->uXferLeft, pCtx->uXferPos, uXferLen)); + + /* Keep track of the EDTLA and last completion status. */ + pCtx->uEDTLA += uXferLen; /* May wrap around! */ + pCtx->uLastCC = cc; + + /* Report events as required. */ + uResidue = pXferTRB->norm.xfr_len - uXferLen; + if (pXferTRB->norm.ioc || (pXferTRB->norm.isp && uResidue)) + { + rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->norm.int_tgt, uResidue, cc, + pCtx->uSlotID, pCtx->uEpDCI, GCPhysXfrTRB, pXferTRB->norm.bei, false); + } + break; + case XHCI_TRB_EVT_DATA: + if (pXferTRB->evtd.ioc) + { + rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->evtd.int_tgt, pCtx->uEDTLA, pCtx->uLastCC, + pCtx->uSlotID, pCtx->uEpDCI, pXferTRB->evtd.evt_data, pXferTRB->evtd.bei, true); + } + /* Clear the EDTLA. */ + pCtx->uEDTLA = 0; + break; + default: + AssertMsgFailed(("%#x\n", pXferTRB->gen.type)); + break; + } + + pCtx->cTRB--; + /* For TD fragments, enforce the maximum count, but only as long as the transfer + * is successful. In case of error we have to complete the entire TD! */ + if (!pCtx->cTRB && pCtx->fMaxCount && pCtx->uLastCC == XHCI_TCC_SUCCESS) + { + Log2(("Stopping at the end of TD Fragment.\n")); + fKeepGoing = false; + } + + /* NB: We currently do not enforce that the number of TRBs can't change between + * submission and completion. If we do, we'll have to store it somewhere for + * isochronous URBs. + */ + return pXferTRB->gen.ch && fKeepGoing; +} + +/** + * Process (consume) non-data TRBs on a transfer ring. This function + * completes TRBs which do not have any URB associated with them. Only + * used with running endpoints. Usable regardless of whether there are + * in-flight TRBs or not. Returns the next TRB and its address to the + * caller. May modify the endpoint context! + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state. + * @param uSlotID The slot corresponding to this USB device. + * @param uEpDCI The DCI of this endpoint. + * @param pEpCtx Endpoint context. May be modified. + * @param pXfer Storage for returning the next TRB to caller. + * @param pGCPhys Storage for returning the physical address of TRB. + */ +static int xhciR3ConsumeNonXferTRBs(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uEpDCI, + XHCI_EP_CTX *pEpCtx, XHCI_XFER_TRB *pXfer, RTGCPHYS *pGCPhys) +{ + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB = 0; + bool dcs; + bool fInFlight; + bool fContinue = true; + int rc; + unsigned cTrbs = 0; + + LogFlowFunc(("Slot ID: %u, EP DCI %u\n", uSlotID, uEpDCI)); + Assert(uSlotID > 0); + Assert(uSlotID <= XHCI_NDS); + + Assert(pEpCtx->ep_state == XHCI_EPST_RUNNING); + do + { + /* Find the transfer TRB address. */ + GCPhysXfrTRB = pEpCtx->trdp & XHCI_TRDP_ADDR_MASK; + dcs = !!(pEpCtx->trdp & XHCI_TRDP_DCS_MASK); + + /* Determine whether there are any in-flight TRBs or not. This affects TREP + * processing -- when nothing is in flight, we have to move both TREP and TRDP; + * otherwise only the TRDP must be updated. + */ + fInFlight = pEpCtx->trep != pEpCtx->trdp; + LogFlowFunc(("Skipping non-data TRBs, TREP:%RGp, TRDP:%RGp, in-flight: %RTbool\n", pEpCtx->trep, pEpCtx->trdp, fInFlight)); + + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer)); + + /* Make sure the Cycle State matches. */ + if ((bool)xfer.gen.cycle == dcs) + { + Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type, + xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!", + xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt)); + + switch (xfer.gen.type) { + case XHCI_TRB_LINK: + Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain)); + Assert(!xfer.link.chain); + /* Set new TRDP but leave DCS bit alone... */ + pEpCtx->trdp = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (pEpCtx->trdp & XHCI_TRDP_DCS_MASK); + /* ...and flip the DCS bit if required. Then update the TREP. */ + if (xfer.link.toggle) + pEpCtx->trdp = (pEpCtx->trdp & ~XHCI_TRDP_DCS_MASK) | (pEpCtx->trdp ^ XHCI_TRDP_DCS_MASK); + if (!fInFlight) + pEpCtx->trep = pEpCtx->trdp; + if (xfer.link.ioc) + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.link.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI, + GCPhysXfrTRB, false, false); + break; + case XHCI_TRB_NOOP_XFER: + Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent)); + /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */ + Assert(!xfer.link.chain); + /* Update enqueue/dequeue pointers. */ + pEpCtx->trdp += sizeof(XHCI_XFER_TRB); + if (!fInFlight) + pEpCtx->trep += sizeof(XHCI_XFER_TRB); + if (xfer.nop.ioc) + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.nop.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI, + GCPhysXfrTRB, false, false); + break; + default: + fContinue = false; + break; + } + } + else + { + LogFunc(("Transfer Ring empty\n")); + fContinue = false; + } + + /* Kill the xHC if the TRB list has no end in sight. */ + /* NB: The limit here could perhaps be much lower because a sequence of Link + * and No-op TRBs with no real work to be done would be highly suspect. + */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* The caller will need the next TRB. Hand it over. */ + Assert(GCPhysXfrTRB); + *pGCPhys = GCPhysXfrTRB; + *pXfer = xfer; + LogFlowFunc(("Final TREP:%RGp, TRDP:%RGp GCPhysXfrTRB:%RGp\n", pEpCtx->trep, pEpCtx->trdp, GCPhysXfrTRB)); + + return VINF_SUCCESS; +} + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) xhciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX ep_ctx; + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB; + int rc; + unsigned uResidue = 0; + uint8_t uSlotID = pUrb->pHci->uSlotID; + uint8_t cc = XHCI_TCC_SUCCESS; + uint8_t uEpDCI; + + /* Check for URBs completed synchronously as part of xHCI command execution. + * The URB will have zero cTRB as it's not associated with a TD. + */ + if (!pUrb->pHci->cTRB) + { + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus)); + LogFlow(("%s: xhciR3RhXferCompletion: Completing xHCI-generated request\n", pUrb->pszDesc)); + return; + } + + /* If the xHC isn't running, just drop the URB right here. */ + if (pThis->status & XHCI_STATUS_HCH) + { + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus)); + LogFlow(("%s: xhciR3RhXferCompletion: xHC halted, skipping URB completion\n", pUrb->pszDesc)); + return; + } + +#ifdef XHCI_ERROR_INJECTION + if (pThis->fDropUrb) + { + LogFlow(("%s: xhciR3RhXferCompletion: Error injection, dropping URB!\n", pUrb->pszDesc)); + pThis->fDropUrb = false; + return; + } +#endif + + RTCritSectEnter(&pThisCC->CritSectThrd); + + /* Convert USB endpoint address to xHCI format. */ + if (pUrb->EndPt) + uEpDCI = pUrb->EndPt * 2 + (pUrb->enmDir == VUSBDIRECTION_IN ? 1 : 0); + else + uEpDCI = 1; /* EP 0 */ + + LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d\n", + pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB)); + LogFlow(("%s: xhciR3RhXferCompletion: EP DCI=%u, cbData=%u status=%u\n", pUrb->pszDesc, uEpDCI, pUrb->cbData, pUrb->enmStatus)); + + /* Load the slot/endpoint contexts from guest memory. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uEpDCI, &slot_ctx, &ep_ctx); + + /* If the EP is disabled, we don't own it so we can't complete the URB. + * Leave this EP alone and drop the URB. + */ + if (ep_ctx.ep_state != XHCI_EPST_RUNNING) + { + Log(("EP DCI %u not running (state %u), skipping URB completion\n", uEpDCI, ep_ctx.ep_state)); + RTCritSectLeave(&pThisCC->CritSectThrd); + return; + } + + /* Now complete any non-transfer TRBs that might be on the transfer ring before + * the TRB(s) corresponding to this URB. Preloads the TRB as a side effect. + * Endpoint state now must be written back in case it was modified! + */ + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB); + + /* Deal with failures which halt the EP first. */ + if (RT_UNLIKELY(pUrb->enmStatus != VUSBSTATUS_OK)) + { + switch(pUrb->enmStatus) + { + case VUSBSTATUS_STALL: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_STALL; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + case VUSBSTATUS_DNR: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_USB_XACT_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + case VUSBSTATUS_CRC: /// @todo Separate status for canceling?! + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_USB_XACT_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + + /* NB: The TRDP is *not* advanced and TREP is reset. */ + ep_ctx.trep = ep_ctx.trdp; + break; + case VUSBSTATUS_DATA_OVERRUN: + case VUSBSTATUS_DATA_UNDERRUN: + /* Halt the endpoint and inform the HCD. + * NB: The TRDP is NOT advanced in case of error. + */ + ep_ctx.ep_state = XHCI_EPST_HALTED; + cc = XHCI_TCC_DATA_BUF_ERR; + rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc, + uSlotID, uEpDCI, GCPhysXfrTRB, false, false); + break; + default: + AssertMsgFailed(("Unexpected URB status %u\n", pUrb->enmStatus)); + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + STAM_COUNTER_INC(&pThis->StatErrorIsocUrbs); + } + else if (xfer.gen.type == XHCI_TRB_NORMAL) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + + ctxComplete.pUrb = pUrb; + ctxComplete.uXferPos = 0; + ctxComplete.uXferLeft = pUrb->cbData; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT; + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + + if (ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT) + ep_ctx.ifc -= XHCI_NO_QUEUING_IN_FLIGHT; /* TD fragment done, allow further queuing. */ + else + ep_ctx.ifc--; /* TD done, decrement in-flight counter. */ + } + else if (xfer.gen.type == XHCI_TRB_ISOCH) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + unsigned iPkt; + + ctxComplete.pUrb = pUrb; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + + for (iPkt = 0; iPkt < pUrb->cIsocPkts; ++iPkt) { + ctxComplete.uXferPos = pUrb->aIsocPkts[iPkt].off; + ctxComplete.uXferLeft = pUrb->aIsocPkts[iPkt].cb; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uEDTLA = 0; // Zero at TD start. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = false; + if (pUrb->aIsocPkts[iPkt].enmStatus != VUSBSTATUS_OK) + STAM_COUNTER_INC(&pThis->StatErrorIsocPkts); + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB); + } + ep_ctx.ifc--; /* TD done, decrement in-flight counter. */ + } + else if (xfer.gen.type == XHCI_TRB_SETUP_STG || xfer.gen.type == XHCI_TRB_DATA_STG || xfer.gen.type == XHCI_TRB_STATUS_STG) + { + XHCI_CTX_XFER_COMPLETE ctxComplete; + uint64_t uTRDP; + + ctxComplete.pUrb = pUrb; + ctxComplete.uXferPos = 0; + ctxComplete.uXferLeft = pUrb->cbData; + ctxComplete.cTRB = pUrb->pHci->cTRB; + ctxComplete.uSlotID = uSlotID; + ctxComplete.uEpDCI = uEpDCI; + ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD. + ctxComplete.uLastCC = cc; + ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT; + xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP); + ep_ctx.last_cc = ctxComplete.uLastCC; + ep_ctx.trdp = uTRDP; + } + else + { + AssertMsgFailed(("Unexpected TRB type %u\n", xfer.gen.type)); + Log2(("TRB @ %RGp, type %u unexpected!\n", GCPhysXfrTRB, xfer.gen.type)); + /* Advance the TRDP anyway so that the endpoint isn't completely stuck. */ + ep_ctx.trdp += sizeof(XHCI_XFER_TRB); + } + + /* Update the endpoint state. */ + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx); + + RTCritSectLeave(&pThisCC->CritSectThrd); + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + /* Completion callback usually runs on a separate thread. Let the worker do more. */ + Log2(("Ring bell for slot %u, DCI %u\n", uSlotID, uEpDCI)); + ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], 1 << uEpDCI); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0); + } +} + + +/** + * 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 re-tried. + * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) xhciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PXHCI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PXHCI); + bool fRetire = true; + + /* If the xHC isn't running, get out of here immediately. */ + if (pThis->status & XHCI_STATUS_HCH) + { + Log(("xHC halted, skipping URB error handling\n")); + return fRetire; + } + + RTCritSectEnter(&pThisCC->CritSectThrd); + + Assert(pUrb->pHci->cTRB); /* xHCI-generated URBs should not fail! */ + if (!pUrb->pHci->cTRB) + { + Log(("%s: Failing xHCI-generated request!\n", pUrb->pszDesc)); + } + else if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + /* Don't retry on stall. */ + Log2(("%s: xhciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_CRC) { + /* Don't retry on CRC errors either. These indicate canceled URBs, among others. */ + Log2(("%s: xhciR3RhXferError: CRC, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DNR) { + /* Don't retry on DNR errors. These indicate the device vanished. */ + Log2(("%s: xhciR3RhXferError: DNR, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) { + /* Don't retry on OVERRUN errors. These indicate a fatal error. */ + Log2(("%s: xhciR3RhXferError: OVERRUN, giving up.\n", pUrb->pszDesc)); + } else if (pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) { + /* Don't retry on UNDERRUN errors. These indicate a fatal error. */ + Log2(("%s: xhciR3RhXferError: UNDERRUN, giving up.\n", pUrb->pszDesc)); + } else { + /// @todo + AssertMsgFailed(("%#x\n", pUrb->enmStatus)); + } + + RTCritSectLeave(&pThisCC->CritSectThrd); + return fRetire; +} + + +/** + * Queue a TD composed of normal TRBs, event data TRBs, and suchlike. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical gues address of the TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + */ +static int xhciR3QueueDataTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI) +{ + RT_NOREF(GCPhysTRB); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + bool fFragOnly = false; + int rc; + VUSBXFERTYPE enmType; + VUSBDIRECTION enmDir; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + if (RT_SUCCESS(rc)) + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + else + { + LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP)); + if (rc == VERR_TRY_AGAIN && pTrb->gen.type == XHCI_TRB_NORMAL && ctxProbe.cTRBLastED) + { + /* The TD is incomplete, but we have at least one TD fragment. We can create a URB for + * what we have but we can't safely queue any more because if any error occurs, the + * TD needs to fail as a whole. + * OS X Mavericks and Yosemite tend to trigger this case when reading from USB 3.0 + * MSDs (transfers up to 1MB). + */ + fFragOnly = true; + + /* Because we currently do not maintain the EDTLA across URBs, we have to only submit + * TD fragments up to where we last saw an Event Data TRB. If there was no Event Data + * TRB, we'll just try waiting a bit longer for the TD to be complete or an Event Data + * TRB to show up. The guest is extremely likely to do one or the other, since otherwise + * it has no way to tell when the transfer completed. + */ + ctxProbe.cTRB = ctxProbe.cTRBLastED; + ctxProbe.uXferLen = ctxProbe.uXfrLenLastED; + } + else + return rc; + } + + /* Determine the transfer kind based on endpoint type. */ + switch (pEpCtx->ep_type) + { + case XHCI_EPTYPE_BULK_IN: + case XHCI_EPTYPE_BULK_OUT: + enmType = VUSBXFERTYPE_BULK; + break; + case XHCI_EPTYPE_INTR_IN: + case XHCI_EPTYPE_INTR_OUT: + enmType = VUSBXFERTYPE_INTR; + break; + case XHCI_EPTYPE_CONTROL: + enmType = VUSBXFERTYPE_CTRL; + break; + case XHCI_EPTYPE_ISOCH_IN: + case XHCI_EPTYPE_ISOCH_OUT: + default: + enmType = VUSBXFERTYPE_INVALID; + AssertMsgFailed(("%#x\n", pEpCtx->ep_type)); + } + + /* Determine the direction based on endpoint type. */ + switch (pEpCtx->ep_type) + { + case XHCI_EPTYPE_BULK_IN: + case XHCI_EPTYPE_INTR_IN: + enmDir = VUSBDIRECTION_IN; + break; + case XHCI_EPTYPE_BULK_OUT: + case XHCI_EPTYPE_INTR_OUT: + enmDir = VUSBDIRECTION_OUT; + break; + default: + enmDir = VUSBDIRECTION_INVALID; + AssertMsgFailed(("%#x\n", pEpCtx->ep_type)); + } + + /* Allocate and initialize a URB. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, enmType, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB, NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerDtaUrb, ctxProbe.cTRB); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + + /// @todo Cross check that the EP type corresponds to direction. Probably + //should check when configuring device? + pUrb->pHci->uSlotID = uSlotID; + + /* For OUT transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = 0; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + ctxProbe.cTRB = ctxSubmit.cTRB; + } + + /* If only completing a fragment, remember the TRB count and increase + * the in-flight count past the limit so we won't queue any more. + */ + pUrb->pHci->cTRB = ctxProbe.cTRB; + if (fFragOnly) + /* Bit of a hack -- prevent further queuing. */ + pEpCtx->ifc += XHCI_NO_QUEUING_IN_FLIGHT; + else + /* Increment the in-flight counter before queuing more. */ + pEpCtx->ifc++; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* + * Submit the URB. + */ + STAM_COUNTER_ADD(&pThis->StatUrbSizeData, pUrb->cbData); + Log(("%s: xhciR3QueueDataTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + /// @todo Mark the EP as halted and inactive and write back the changes. + + return VERR_OUT_OF_RESOURCES; +} + + +/** + * Queue an isochronous TD composed of isochronous and normal TRBs, event + * data TRBs, and suchlike. This TD may either correspond to a single URB or + * form one packet of an isochronous URB. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical guest address of the TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + * @param pCtxIso Additional isochronous URB context. + */ +static int xhciR3QueueIsochTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI, + XHCI_CTX_ISOCH *pCtxIso) +{ + RT_NOREF(GCPhysTRB, pTrb); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + PVUSBURB pUrb; + unsigned cIsoPackets; + uint32_t cbPktMax; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + /* For isochronous transfers, there's a bit of extra work to do. The interval + * is key and determines whether the TD will directly correspond to a URB or + * if it will only form part of a larger URB. In any case, one TD equals one + * 'packet' of an isochronous URB. + */ + switch (pEpCtx->interval) + { + case 0: /* Every 2^0 * 125us, i.e. 8 per frame. */ + cIsoPackets = 8; + break; + case 1: /* Every 2^1 * 125us, i.e. 4 per frame. */ + cIsoPackets = 4; + break; + case 2: /* Every 2^2 * 125us, i.e. 2 per frame. */ + cIsoPackets = 2; + break; + case 3: /* Every 2^3 * 125us, i.e. 1 per frame. */ + default:/* Or any larger interval (every n frames).*/ + cIsoPackets = 1; + break; + } + + /* We do not know exactly how much data might be transferred until we + * look at all TDs/packets that constitute the URB. However, we do know + * the maximum possible size even without probing any TDs at all. + * The actual size is expected to be the same or at most slightly smaller, + * hence it makes sense to allocate the URB right away and copy data into + * it as we go, rather than doing complicated probing first. + * The Max Endpoint Service Interval Time (ESIT) Payload defines the + * maximum number of bytes that can be transferred per interval (4.14.2). + * Unfortunately Apple was lazy and their driver leaves the Max ESIT + * Payload as zero, so we have to do the math ourselves. + */ + + /* Calculate the maximum transfer size per (micro)frame. */ + /// @todo This ought to be stored within the URB somewhere. + cbPktMax = pEpCtx->max_pkt_sz * (pEpCtx->max_brs_sz + 1) * (pEpCtx->mult + 1); + if (!pCtxIso->pUrb) + { + uint32_t cbUrbMax = cIsoPackets * cbPktMax; + + /* Validate endpoint type. */ + AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN || pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_OUT, + ("%#x\n", pEpCtx->ep_type)); + + /* Allocate and initialize a new URB. */ + pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_ISOC, + (pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT, + cbUrbMax, ctxProbe.cTRB, NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerIsoUrb, ctxProbe.cTRB); + + LogFlowFunc(("Allocated URB with %u packets, %u bytes total (ESIT payload %u)\n", cIsoPackets, cbUrbMax, cbPktMax)); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->cIsocPkts = cIsoPackets; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = ctxProbe.cTRB; + + /* If TRB says so or if there are multiple packets per interval, don't even + * bother with frame counting and schedule everything ASAP. + */ + if (pTrb->isoc.sia || cIsoPackets != 1) + pUrb->uStartFrameDelta = 0; + else + { + uint16_t uFrameDelta; + uint32_t uPort; + + /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after + * writing back the EP state so that the completion callback can operate. + */ + if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, NULL, &uPort))) + { + + uFrameDelta = pRh->pIRhConn->pfnUpdateIsocFrameDelta(pRh->pIRhConn, uPort, uEpDCI / 2, + uEpDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT, + pTrb->isoc.frm_id, XHCI_FRAME_ID_BITS); + pUrb->uStartFrameDelta = uFrameDelta; + Log(("%s: Isoch frame delta set to %u\n", pUrb->pszDesc, uFrameDelta)); + } + else + { + Log(("%s: Failed to find device for slot! Setting frame delta to zero.\n", pUrb->pszDesc)); + pUrb->uStartFrameDelta = 0; + } + } + + Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u FrmID=%u Isoch URB created\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData, pTrb->isoc.frm_id)); + + /* Set up the context for later use. */ + pCtxIso->pUrb = pUrb; + /* Save the current TREP in case we need to rewind. */ + pCtxIso->uInitTREP = pEpCtx->trep; + } + else + { + Assert(cIsoPackets > 1); + /* Grab the URB we initialized earlier. */ + pUrb = pCtxIso->pUrb; + } + + /* Set up the packet corresponding to this TD. */ + pUrb->aIsocPkts[pCtxIso->iPkt].cb = RT_MIN(ctxProbe.uXferLen, cbPktMax); + pUrb->aIsocPkts[pCtxIso->iPkt].off = pCtxIso->offCur; + pUrb->aIsocPkts[pCtxIso->iPkt].enmStatus = VUSBSTATUS_NOT_ACCESSED; + + /* For OUT transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = pCtxIso->offCur; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + } + + /* Done preparing this packet. */ + Assert(pCtxIso->iPkt < 8); + pCtxIso->iPkt++; + pCtxIso->offCur += ctxProbe.uXferLen; + Assert(pCtxIso->offCur <= pUrb->cbData); + + /* Increment the in-flight counter before queuing more. */ + if (pCtxIso->iPkt == pUrb->cIsocPkts) + pEpCtx->ifc++; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* If the URB is complete, submit it. */ + if (pCtxIso->iPkt == pUrb->cIsocPkts) + { + /* Change cbData to reflect how much data should be transferred. This can differ + * from how much data was allocated for the URB. + */ + pUrb->cbData = pCtxIso->offCur; + STAM_COUNTER_ADD(&pThis->StatUrbSizeIsoc, pUrb->cbData); + Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u Isoch URB being submitted\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + pCtxIso->fSubmitFailed = true; + /// @todo Mark the EP as halted and inactive and write back the changes. + return VERR_OUT_OF_RESOURCES; + } + /* Clear the isochronous URB context. */ + RT_ZERO(*pCtxIso); + } + + return VINF_SUCCESS; +} + + +/** + * Queue a control TD composed of setup/data/status stage TRBs, event data + * TRBs, and suchlike. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param pRh Root hub for the device. + * @param GCPhysTRB Physical guest address of th TRB. + * @param pTrb Pointer to the contents of the first TRB. + * @param pEpCtx Pointer to the cached EP context. + * @param uSlotID ID of the associated slot context. + * @param uAddr The device address. + * @param uEpDCI The DCI(!) of the endpoint. + */ +static int xhciR3QueueControlTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB, + XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI) +{ + RT_NOREF(GCPhysTRB); + XHCI_CTX_XFER_PROBE ctxProbe; + XHCI_CTX_XFER_SUBMIT ctxSubmit; + uint64_t uTREP; + int rc; + VUSBDIRECTION enmDir; + + /* Discover how big this TD is. */ + RT_ZERO(ctxProbe); + rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP); + if (RT_SUCCESS(rc)) + LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP)); + else + { + LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP)); + return rc; + } + + /* Determine the transfer direction. */ + switch (pTrb->gen.type) + { + case XHCI_TRB_SETUP_STG: + enmDir = VUSBDIRECTION_SETUP; + /* For setup TRBs, there is always 8 bytes of immediate data. */ + Assert(sizeof(VUSBSETUP) == 8); + Assert(ctxProbe.uXferLen == 8); + Log2(("bmRequestType:%02X bRequest:%02X wValue:%04X wIndex:%04X wLength:%04X\n", pTrb->setup.bmRequestType, + pTrb->setup.bRequest, pTrb->setup.wValue, pTrb->setup.wIndex, pTrb->setup.wLength)); + break; + case XHCI_TRB_STATUS_STG: + enmDir = pTrb->status.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + break; + case XHCI_TRB_DATA_STG: + enmDir = pTrb->data.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + break; + default: + AssertMsgFailed(("%#x\n", pTrb->gen.type)); /* Can't happen unless caller messed up. */ + return VERR_INTERNAL_ERROR; + } + + /* Allocate and initialize a URB. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_CTRL, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB, + NULL); + if (!pUrb) + return VERR_OUT_OF_RESOURCES; /// @todo handle error! + + STAM_COUNTER_ADD(&pThis->StatTRBsPerCtlUrb, ctxProbe.cTRB); + + /* See 4.5.1 about xHCI vs. USB endpoint addressing. */ + Assert(uEpDCI); + + /* This had better be a control endpoint. */ + AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_CONTROL, ("%#x\n", pEpCtx->ep_type)); + + pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */ + pUrb->fShortNotOk = false; /* We detect short packets ourselves. */ + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + + /* For OUT/SETUP transfers, copy the TD data into the URB. */ + if (pUrb->enmDir == VUSBDIRECTION_OUT || pUrb->enmDir == VUSBDIRECTION_SETUP) + { + ctxSubmit.pUrb = pUrb; + ctxSubmit.uXferPos = 0; + ctxSubmit.cTRB = 0; + xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP); + Assert(ctxProbe.cTRB == ctxSubmit.cTRB); + ctxProbe.cTRB = ctxSubmit.cTRB; + } + + pUrb->pHci->cTRB = ctxProbe.cTRB; + + /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */ + pEpCtx->trep = uTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx); + + /* + * Submit the URB. + */ + STAM_COUNTER_ADD(&pThis->StatUrbSizeCtrl, pUrb->cbData); + Log(("%s: xhciR3QueueControlTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n", + pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources, + * or the user just ripped out the device. + */ + /// @todo Mark the EP as halted and inactive and write back the changes. + + return VERR_OUT_OF_RESOURCES; +} + + +/** + * Process a device context (transfer data). + * + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot/doorbell which had been rung. + * @param uDBVal Value written to the doorbell. + */ +static int xhciR3ProcessDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint32_t uDBVal) +{ + uint8_t uDBTarget = uDBVal & XHCI_DB_TGT_MASK; + XHCI_CTX_ISOCH ctxIsoch = {0}; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX ep_ctx; + XHCI_XFER_TRB xfer; + RTGCPHYS GCPhysXfrTRB; + PXHCIROOTHUBR3 pRh; + bool dcs; + bool fContinue = true; + int rc; + unsigned cTrbs = 0; + + LogFlowFunc(("Slot ID: %u, DB target %u, DB stream ID %u\n", uSlotID, uDBTarget, (uDBVal & XHCI_DB_STRMID_MASK) >> XHCI_DB_STRMID_SHIFT)); + Assert(uSlotID > 0); + Assert(uSlotID <= XHCI_NDS); + /// @todo report errors for bogus DB targets + Assert(uDBTarget > 0); + Assert(uDBTarget < 32); + + /// @todo Check for aborts and the like? + + /* Load the slot and endpoint contexts. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx); + /// @todo sanity check the context in here? + + /* Select the root hub corresponding to the port. */ + pRh = GET_PORT_PRH(pThisCC, ID_TO_IDX(slot_ctx.rh_port)); + + /* Stopped endpoints automatically transition to running state. */ + if (RT_UNLIKELY(ep_ctx.ep_state == XHCI_EPST_STOPPED)) + { + Log(("EP DCI %u stopped -> running\n", uDBTarget)); + ep_ctx.ep_state = XHCI_EPST_RUNNING; + /* Update EP right here. Theoretically could be postponed, but we + * must ensure that the EP does get written back even if there is + * no other work to do. + */ + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + } + + /* If the EP isn't running, get outta here. */ + if (RT_UNLIKELY(ep_ctx.ep_state != XHCI_EPST_RUNNING)) + { + Log2(("EP DCI %u not running (state %u), bail!\n", uDBTarget, ep_ctx.ep_state)); + return VINF_SUCCESS; + } + + /* Get any non-transfer TRBs out of the way. */ + xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx, &xfer, &GCPhysXfrTRB); + /// @todo This is inefficient. + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + + do + { + /* Fetch the contexts again and find the TRB address at enqueue point. */ + xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx); + GCPhysXfrTRB = ep_ctx.trep & XHCI_TRDP_ADDR_MASK; + dcs = !!(ep_ctx.trep & XHCI_TRDP_DCS_MASK); + LogFlowFunc(("Processing Transfer Ring, TREP: %RGp\n", GCPhysXfrTRB)); + + /* Fetch the transfer TRB. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer)); + + /* Make sure the Cycle State matches. */ + if ((bool)xfer.gen.cycle == dcs) + { + Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type, + xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!", + xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt)); + + /* If there is an "in-flight" TRDP, check if we need to wait until the transfer completes. */ + if ((ep_ctx.trdp & XHCI_TRDP_ADDR_MASK) != GCPhysXfrTRB) + { + switch (xfer.gen.type) { + case XHCI_TRB_ISOCH: + if (ep_ctx.ifc >= XHCI_MAX_ISOC_IN_FLIGHT) + { + Log(("%u isoch URBs in flight, backing off\n", ep_ctx.ifc)); + fContinue = false; + break; + } + RT_FALL_THRU(); + case XHCI_TRB_LINK: + Log2(("TRB OK, continuing @ %RX64\n", GCPhysXfrTRB)); + break; + case XHCI_TRB_NORMAL: + if (XHCI_EP_XTYPE(ep_ctx.ep_type) != XHCI_XFTYPE_BULK) + { + Log2(("Normal TRB not bulk, not continuing @ %RX64\n", GCPhysXfrTRB)); + fContinue = false; + break; + } + if (ep_ctx.ifc >= XHCI_MAX_BULK_IN_FLIGHT) + { + Log(("%u normal URBs in flight, backing off\n", ep_ctx.ifc)); + fContinue = false; + break; + } + Log2(("Bulk TRB OK, continuing @ %RX64\n", GCPhysXfrTRB)); + break; + case XHCI_TRB_EVT_DATA: + case XHCI_TRB_NOOP_XFER: + Log2(("TRB not OK, not continuing @ %RX64\n", GCPhysXfrTRB)); + fContinue = false; + break; + default: + Log2(("Some other TRB (type %u), not continuing @ %RX64\n", xfer.gen.type, GCPhysXfrTRB)); + fContinue = false; + break; + } + } + if (!fContinue) + break; + + switch (xfer.gen.type) { + case XHCI_TRB_NORMAL: + Log(("Normal TRB: Ptr=%RGp IOC=%u CH=%u\n", xfer.norm.data_ptr, xfer.norm.ioc, xfer.norm.ch)); + rc = xhciR3QueueDataTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_SETUP_STG: + Log(("Setup stage TRB: IOC=%u IDT=%u\n", xfer.setup.ioc, xfer.setup.idt)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_DATA_STG: + Log(("Data stage TRB: Ptr=%RGp IOC=%u CH=%u DIR=%u\n", xfer.data.data_ptr, xfer.data.ioc, xfer.data.ch, xfer.data.dir)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_STATUS_STG: + Log(("Status stage TRB: IOC=%u CH=%u DIR=%u\n", xfer.status.ioc, xfer.status.ch, xfer.status.dir)); + rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget); + break; + case XHCI_TRB_ISOCH: + Log(("Isoch TRB: Ptr=%RGp IOC=%u CH=%u TLBPC=%u TBC=%u SIA=%u FrmID=%u\n", xfer.isoc.data_ptr, xfer.isoc.ioc, xfer.isoc.ch, xfer.isoc.tlbpc, xfer.isoc.tbc, xfer.isoc.sia, xfer.isoc.frm_id)); + rc = xhciR3QueueIsochTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID, + slot_ctx.dev_addr, uDBTarget, &ctxIsoch); + break; + case XHCI_TRB_LINK: + Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain)); + Assert(!xfer.link.chain); + /* Set new TREP but leave DCS bit alone... */ + ep_ctx.trep = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (ep_ctx.trep & XHCI_TRDP_DCS_MASK); + /* ...and flip the DCS bit if required. Then update the TREP. */ + if (xfer.link.toggle) + ep_ctx.trep = (ep_ctx.trep & ~XHCI_TRDP_DCS_MASK) | (ep_ctx.trep ^ XHCI_TRDP_DCS_MASK); + rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + break; + case XHCI_TRB_NOOP_XFER: + Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent)); + /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */ + Assert(!xfer.link.chain); + /* Update enqueue pointer (TRB was not yet completed). */ + ep_ctx.trep += sizeof(XHCI_XFER_TRB); + rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + break; + default: + Log(("Unsupported TRB!!\n")); + rc = VERR_NOT_SUPPORTED; + break; + } + /* If queuing failed, stop right here. */ + if (RT_FAILURE(rc)) + fContinue = false; + } + else + { + LogFunc(("Transfer Ring empty\n")); + fContinue = false; + + /* If an isochronous ring is empty, this is an overrun/underrun. At this point + * the ring will no longer be scheduled (until the doorbell is rung again) + * but it remains in the Running state. This error is only reported if someone + * rang the doorbell and there are no TDs available or in-flight. + */ + if ( (ep_ctx.trep == ep_ctx.trdp) /* Nothing in-flight? */ + && (ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN || ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_OUT)) + { + /* There is no TRB associated with this error; the slot context + * determines the interrupter. + */ + Log(("Isochronous ring %s, TRDP:%RGp\n", ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? "overrun" : "underrun", ep_ctx.trdp & XHCI_TRDP_ADDR_MASK)); + rc = xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0, + ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? XHCI_TCC_RING_OVERRUN : XHCI_TCC_RING_UNDERRUN, + uSlotID, uDBTarget, 0, false, false); + } + + } + + /* Kill the xHC if the TRB list has no end in sight. */ + if (++cTrbs > XHCI_MAX_NUM_TRBS) + { + /* Stop the xHC with an error. */ + xhciR3EndlessTrbError(pDevIns, pThis); + + /* Get out of the loop. */ + fContinue = false; + rc = VERR_NOT_SUPPORTED; /* No good error code really... */ + } + } while (fContinue); + + /* It can unfortunately happen that for endpoints with more than one + * transfer per USB frame, there won't be a complete multi-packet URB ready + * when we go looking for it. If that happens, we'll "rewind" the TREP and + * try again later. Since the URB construction is done under a lock, this + * is safe as we won't be accessing the endpoint concurrently. + */ + if (ctxIsoch.pUrb) + { + Log(("Unfinished ISOC URB (%u packets out of %u)!\n", ctxIsoch.iPkt, ctxIsoch.pUrb->cIsocPkts)); + /* If submitting failed, the URB is already freed. */ + if (!ctxIsoch.fSubmitFailed) + VUSBIRhFreeUrb(pRh->pIRhConn, ctxIsoch.pUrb); + ep_ctx.trep = ctxIsoch.uInitTREP; + xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx); + } + return VINF_SUCCESS; +} + + +/** + * A worker routine for Address Device command. Builds a URB containing + * a SET_ADDRESS requests and (synchronously) submits it to VUSB, then + * follows up with a status stage URB. + * + * @returns true on success. + * @returns false on failure to submit. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot ID to assign address to. + * @param uDevAddr New device address. + * @param iPort The xHCI root hub port index. + */ +static bool xhciR3IssueSetAddress(PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDevAddr, unsigned iPort) +{ + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + Assert(uSlotID); + LogFlowFunc(("Slot %u port idx %u: new address is %u\n", uSlotID, iPort, uDevAddr)); + + /* For USB3 devices, force the port number. This simulates the fact that USB3 uses directed (unicast) traffic. */ + if (!IS_USB3_PORT_IDX_R3(pThisCC, iPort)) + iPort = VUSB_DEVICE_PORT_INVALID; + else + iPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort); + + /* Allocate and initialize a URB. NB: Zero cTds indicates a URB not submitted by guest. */ + PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_SETUP, + sizeof(VUSBSETUP), 0 /* cTds */, NULL); + if (!pUrb) + return false; + + pUrb->EndPt = 0; + pUrb->fShortNotOk = true; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = 0; + + /* Build the request. */ + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + pSetup->bmRequestType = VUSB_DIR_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_TO_DEVICE; + pSetup->bRequest = VUSB_REQ_SET_ADDRESS; + pSetup->wValue = uDevAddr; + pSetup->wIndex = 0; + pSetup->wLength = 0; + + /* NB: We assume the address assignment is a synchronous operation. */ + + /* Submit the setup URB. */ + Log(("%s: xhciSetAddress setup: cbData=%u\n", pUrb->pszDesc, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + Log(("xhciSetAddress: setup stage failed pUrb=%p!!\n", pUrb)); + return false; + } + + /* To complete the SET_ADDRESS request, the status stage must succeed. */ + pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_IN, 0 /* cbData */, 0 /* cTds */, + NULL); + if (!pUrb) + return false; + + pUrb->EndPt = 0; + pUrb->fShortNotOk = true; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->uSlotID = uSlotID; + pUrb->pHci->cTRB = 0; + + /* Submit the setup URB. */ + Log(("%s: xhciSetAddress status: cbData=%u\n", pUrb->pszDesc, pUrb->cbData)); + RTCritSectLeave(&pThisCC->CritSectThrd); + rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led); + RTCritSectEnter(&pThisCC->CritSectThrd); + if (RT_FAILURE(rc)) + { + Log(("xhciSetAddress: status stage failed pUrb=%p!!\n", pUrb)); + return false; + } + + Log(("xhciSetAddress: set address succeeded\n")); + return true; +} + + +/** + * Address a device. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID to assign address to. + * @param fBSR Block Set address Request flag. + */ +static unsigned xhciR3AddressDevice(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint64_t uInpCtxAddr, + uint8_t uSlotID, bool fBSR) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX inp_slot_ctx; /* Input Slot Context (ICI=1). */ + XHCI_EP_CTX ep_ctx; /* Endpoint Context (ICI=2+). */ + XHCI_SLOT_CTX out_slot_ctx; /* Output Slot Context. */ + uint8_t dev_addr; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(GCPhysInpCtx); + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /// @todo Check for valid context (6.2.2.1, 6.2.3.1) + + /* See 4.6.5 */ + do { + /* Parameter validation depends on whether the BSR flag is set or not. */ + if (fBSR) + { + /* Check that the output slot context state is in Enabled state. */ + if (out_slot_ctx.slot_state >= XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state (%u) wrong (BSR)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + dev_addr = 0; + } + else + { + /* Check that the output slot context state is in Enabled or Default state. */ + if (out_slot_ctx.slot_state > XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state (%u) wrong (no-BSR)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + dev_addr = xhciR3SelectNewAddress(pThis, uSlotID); + } + + /* Fetch the input control context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + Assert(icc.add_flags == (RT_BIT(0) | RT_BIT(1))); /* Should have been already checked. */ + Assert(!icc.drop_flags); + + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the input slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &inp_slot_ctx, sizeof(inp_slot_ctx)); + + /* If BSR isn't set, issue the actual SET_ADDRESS request. */ + if (!fBSR) { + unsigned iPort; + + /* We have to dig out the port number/index to determine which virtual root hub to use. */ + iPort = ID_TO_IDX(inp_slot_ctx.rh_port); + if (iPort >= XHCI_NDP_CFG(pThis)) + { + Log(("Port out of range (index %u)!\n", iPort)); + cc = XHCI_TCC_USB_XACT_ERR; + break; + } + if (!xhciR3IssueSetAddress(pThisCC, uSlotID, dev_addr, iPort)) + { + Log(("SET_ADDRESS failed!\n")); + cc = XHCI_TCC_USB_XACT_ERR; + break; + } + } + + /* Copy the slot context with appropriate modifications. */ + out_slot_ctx = inp_slot_ctx; + if (fBSR) + out_slot_ctx.slot_state = XHCI_SLTST_DEFAULT; + else + out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED; + out_slot_ctx.dev_addr = dev_addr; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* Point at the EP0 contexts. */ + GCPhysInpSlot += sizeof(inp_slot_ctx); + GCPhysOutSlot += sizeof(out_slot_ctx); + + /* Copy EP0 context with appropriate modifications. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &ep_ctx, sizeof(ep_ctx)); + xhciR3EnableEP(&ep_ctx); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &ep_ctx, sizeof(ep_ctx)); + } while (0); + + return cc; +} + + +/** + * Reset a halted endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to reset. + * @param fTSP The Transfer State Preserve flag. + */ +static unsigned xhciR3ResetEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, bool fTSP) +{ + RT_NOREF(fTSP); + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.8 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Halted. */ + if (endp_ctx.ep_state != XHCI_EPST_HALTED) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Transition EP state. */ + endp_ctx.ep_state = XHCI_EPST_STOPPED; + + /// @todo What can we do with the TSP flag? + /// @todo Anything to do WRT enabling the corresponding doorbell register? + + /* Write back the updated endpoint context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + } while (0); + + return cc; +} + + +/** + * Stop a running endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis The xHCI device state, shared edition. + * @param pThisCC The xHCI device state, ring-3 edition. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to stop. + * @param fTSP The Suspend flag. + */ +static unsigned xhciR3StopEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDCI, bool fTSP) +{ + RT_NOREF(fTSP); + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.9 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* The doorbell could be ringing; stop it if so. */ + if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << uDCI)) + { + Log(("Unring bell for slot ID %u, DCI %u\n", uSlotID, uDCI)); + ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDCI)); + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Running. */ + if (endp_ctx.ep_state != XHCI_EPST_RUNNING) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Transition EP state. */ + endp_ctx.ep_state = XHCI_EPST_STOPPED; + + /* Write back the updated endpoint context *now*, before actually canceling anyhing. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /// @todo What can we do with the SP flag? + + PXHCIROOTHUBR3 pRh; + uint32_t uPort; + + /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after + * writing back the EP state so that the completion callback can operate. + */ + if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, &pRh, &uPort))) + { + /* Temporarily give up the lock so that the completion callbacks can run. */ + RTCritSectLeave(&pThisCC->CritSectThrd); + Log(("Aborting DCI %u -> ep=%u d=%u\n", uDCI, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT)); + pRh->pIRhConn->pfnAbortEp(pRh->pIRhConn, uPort, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT); + RTCritSectEnter(&pThisCC->CritSectThrd); + } + + /// @todo The completion callbacks should do more work for canceled URBs. + /* Once the completion callbacks had a chance to run, we have to adjust + * the endpoint state. + * NB: The guest may just ring the doorbell to continue and not execute + * 'Set TRDP' after stopping the endpoint. + */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + bool fXferWasInProgress = endp_ctx.trep != endp_ctx.trdp; + + /* Reset the TREP, but the EDTLA should be left alone. */ + endp_ctx.trep = endp_ctx.trdp; + + if (fXferWasInProgress) + { + /* Fetch the transfer TRB to see the length. */ + RTGCPHYS GCPhysXfrTRB = endp_ctx.trdp & XHCI_TRDP_ADDR_MASK; + XHCI_XFER_TRB XferTRB; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB)); + + xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, XferTRB.gen.xfr_len, XHCI_TCC_STOPPED, uSlotID, uDCI, + GCPhysXfrTRB, false, false); + } + else + { + /* We need to generate a Force Stopped Event or FSE. Note that FSEs were optional + * in xHCI 0.96 but aren't in 1.0. + */ + xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0, XHCI_TCC_STP_INV_LEN, uSlotID, uDCI, + endp_ctx.trdp & XHCI_TRDP_ADDR_MASK, false, false); + } + + /* Write back the updated endpoint context again. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + } while (0); + + return cc; +} + + +/** + * Set a new TR Dequeue Pointer for an endpoint. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + * @param uDCI DCI of the endpoint to reset. + * @param uTRDP The TRDP including DCS/ flag. + */ +static unsigned xhciR3SetTRDP(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, uint64_t uTRDP) +{ + RTGCPHYS GCPhysSlot; + RTGCPHYS GCPhysEndp; + XHCI_SLOT_CTX slot_ctx; + XHCI_EP_CTX endp_ctx; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the addresses of the contexts. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.10 */ + do { + /* Check that the slot context state is Default, Addressed, or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the endpoint context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + + /* Check that the endpoint context state is Stopped or Error. */ + if (endp_ctx.ep_state != XHCI_EPST_STOPPED && endp_ctx.ep_state != XHCI_EPST_ERROR) + { + Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Update the TRDP/TREP and DCS. */ + endp_ctx.trdp = uTRDP; + endp_ctx.trep = uTRDP; + + /* Also clear the in-flight counter! */ + endp_ctx.ifc = 0; + + /// @todo Handle streams + + /* Write back the updated endpoint context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx)); + } while (0); + + return cc; +} + + +/** + * Prepare for a device reset. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uSlotID Slot ID to work with. + */ +static unsigned xhciR3ResetDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID) +{ + RTGCPHYS GCPhysSlot; + XHCI_SLOT_CTX slot_ctx; + XHCI_DEV_CTX dc; + unsigned num_ctx; + unsigned i; + unsigned cc = XHCI_TCC_SUCCESS; + + Assert(uSlotID); + + /* Determine the address of the slot/device context. */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + + /* Fetch the slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx)); + + /* See 4.6.11. */ + do { + /* Check that the slot context state is Addressed or Configured. */ + if (slot_ctx.slot_state < XHCI_SLTST_ADDRESSED) + { + Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Read the entire Device Context. */ + num_ctx = slot_ctx.ctx_ent + 1; /* Slot context plus EPs. */ + Assert(num_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX)); + + /// @todo Abort any outstanding transfers! + + /* Set slot state to Default and reset the USB device address. */ + dc.entry[0].sc.slot_state = XHCI_SLTST_DEFAULT; + dc.entry[0].sc.dev_addr = 0; + + /* Disable all endpoints except for EP 0 (aka DCI 1). */ + for (i = 2; i < num_ctx; ++i) + dc.entry[i].ep.ep_state = XHCI_EPST_DISABLED; + + /* Write back the updated device context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX)); + } while (0); + + return cc; +} + + +/** + * Configure a device (even though the relevant command is called 'Configure + * Endpoint'. This includes adding/dropping endpoint contexts as directed by + * the input control context bits. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID associated with the context. + * @param fDC Deconfigure flag set (input context unused). + */ +static unsigned xhciR3ConfigureDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID, bool fDC) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + RTGCPHYS GCPhysOutEndp; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */ + XHCI_EP_CTX out_endp_ctx; /* Endpoint Context (DCI=1). */ + unsigned cc = XHCI_TCC_SUCCESS; + uint32_t uAddFlags; + uint32_t uDropFlags; + unsigned num_inp_ctx; + unsigned num_out_ctx; + XHCI_DEV_CTX dc_inp; + XHCI_DEV_CTX dc_out; + unsigned uDCI; + + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + Assert(GCPhysOutSlot); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* See 4.6.6 */ + do { + /* Check that the output slot context state is Addressed, or Configured. */ + if (out_slot_ctx.slot_state < XHCI_SLTST_ADDRESSED) + { + Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Check for deconfiguration request. */ + if (fDC) { + if (out_slot_ctx.slot_state == XHCI_SLTST_CONFIGURED) { + /* Disable all enabled endpoints. */ + uDropFlags = 0xFFFFFFFC; /** @todo r=bird: Why do you set uDropFlags and uAddFlags in a code path that doesn't use + * them? This is a _very_ difficult function to get the hang of the way it's written. + * Stuff like this looks like there's a control flow flaw (as to the do-break-while-false + * loop which doesn't do any clean up or logging at the end and seems only sever the very + * dubious purpose of making sure ther's only one return statement). The insistance on + * C-style variable declarations (top of function), makes checking state harder, which is + * why it's discouraged. */ + uAddFlags = 0; + + /* Start with EP1. */ + GCPhysOutEndp = GCPhysOutSlot + sizeof(XHCI_SLOT_CTX) + sizeof(XHCI_EP_CTX); + + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx)); + out_endp_ctx.ep_state = XHCI_EPST_DISABLED; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx)); + GCPhysOutEndp += sizeof(XHCI_EP_CTX); /* Point to the next EP context. */ + + /* Finally update the output slot context. */ + out_slot_ctx.ctx_ent = 1; /* Only EP0 left. */ + out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED; + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + LogFlow(("Setting Output Slot State to Addressed, Context Entries = %u\n", out_slot_ctx.ctx_ent)); + } + else + /* NB: Attempts to deconfigure a slot in Addressed state are ignored. */ + Log(("Ignoring attempt to deconfigure slot in Addressed state!\n")); + break; + } + + /* Fetch the input control context. */ + Assert(GCPhysInpCtx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + Assert(icc.add_flags || icc.drop_flags); /* Make sure there's something to do. */ + + uAddFlags = icc.add_flags; + uDropFlags = icc.drop_flags; + LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags)); + + /* If and only if any 'add context' flag is set, fetch the corresponding + * input device context. + */ + if (uAddFlags) { + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the input Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_inp_ctx = ASMBitLastSetU32(uAddFlags); + Assert(num_inp_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY)); + + /// @todo Check that the highest set add flag isn't beyond input slot Context Entries + + /// @todo Check input slot context according to 6.2.2.2 + /// @todo Check input EP contexts according to 6.2.3.2 + } +/** @todo r=bird: Looks like MSC is right that dc_inp can be used uninitalized. + * + * However, this function is so hard to read I'm leaving the exorcism of it to + * the author and just zeroing it in the mean time. + * + */ + else + RT_ZERO(dc_inp); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' or 'drop' bit set. + */ + num_out_ctx = ASMBitLastSetU32(uAddFlags | uDropFlags); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + + /* Drop contexts as directed by flags. */ + for (uDCI = 2; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uDropFlags)) + continue; + + Log2(("Dropping EP DCI %u\n", uDCI)); + dc_out.entry[uDCI].ep.ep_state = XHCI_EPST_DISABLED; + /// @todo Do we need to bother tracking resources/bandwidth? + } + + /* Now add contexts as directed by flags. */ + for (uDCI = 2; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uAddFlags)) + continue; + + Assert(!fDC); + /* Copy over EP context, set to running. */ + Log2(("Adding EP DCI %u\n", uDCI)); + dc_out.entry[uDCI].ep = dc_inp.entry[uDCI].ep; + xhciR3EnableEP(&dc_out.entry[uDCI].ep); + /// @todo Do we need to bother tracking resources/bandwidth? + } + + /* Finally update the device context. */ + if (fDC || dc_inp.entry[0].sc.ctx_ent == 1) + { + dc_out.entry[0].sc.slot_state = XHCI_SLTST_ADDRESSED; + dc_out.entry[0].sc.ctx_ent = 1; + LogFlow(("Setting Output Slot State to Addressed\n")); + } + else + { + uint32_t uKillFlags = uDropFlags & ~uAddFlags; /* Endpoints going away. */ + + /* At least one EP enabled. Update Context Entries and state. */ + Assert(dc_inp.entry[0].sc.ctx_ent); + dc_out.entry[0].sc.slot_state = XHCI_SLTST_CONFIGURED; + if (ID_TO_IDX(ASMBitLastSetU32(uAddFlags)) > dc_out.entry[0].sc.ctx_ent) + { + /* Adding new endpoints. */ + dc_out.entry[0].sc.ctx_ent = ID_TO_IDX(ASMBitLastSetU32(uAddFlags)); + } + else if (ID_TO_IDX(ASMBitLastSetU32(uKillFlags)) == dc_out.entry[0].sc.ctx_ent) + { + /* Removing the last endpoint, find the last non-disabled one. */ + unsigned num_ctx_ent; + + Assert(dc_out.entry[0].sc.ctx_ent + 1u == num_out_ctx); + for (num_ctx_ent = dc_out.entry[0].sc.ctx_ent; num_ctx_ent > 1; --num_ctx_ent) + if (dc_out.entry[num_ctx_ent].ep.ep_state != XHCI_EPST_DISABLED) + break; + dc_out.entry[0].sc.ctx_ent = num_ctx_ent; /* Last valid index to be precise. */ + } + LogFlow(("Setting Output Slot State to Configured, Context Entries = %u\n", dc_out.entry[0].sc.ctx_ent)); + } + + /* If there were no errors, write back the updated output context. */ + LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot)); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + } while (0); + + return cc; +} + + +/** + * Evaluate an input context. This involves modifying device and endpoint + * contexts as directed by the input control context add bits. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uInpCtxAddr Address of the input context. + * @param uSlotID Slot ID associated with the context. + */ +static unsigned xhciR3EvalContext(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID) +{ + RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK; + RTGCPHYS GCPhysInpSlot; + RTGCPHYS GCPhysOutSlot; + XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */ + XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */ + unsigned cc = XHCI_TCC_SUCCESS; + uint32_t uAddFlags; + uint32_t uDropFlags; + unsigned num_inp_ctx; + unsigned num_out_ctx; + XHCI_DEV_CTX dc_inp; + XHCI_DEV_CTX dc_out; + unsigned uDCI; + + Assert(GCPhysInpCtx); + Assert(uSlotID); + LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx)); + + /* Determine the address of the output slot context. */ + GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + Assert(GCPhysOutSlot); + + /* Fetch the output slot context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx)); + + /* See 4.6.7 */ + do { + /* Check that the output slot context state is Default, Addressed, or Configured. */ + if (out_slot_ctx.slot_state < XHCI_SLTST_DEFAULT) + { + Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state)); + cc = XHCI_TCC_CTX_STATE_ERR; + break; + } + + /* Fetch the input control context. */ + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc)); + uAddFlags = icc.add_flags; + uDropFlags = icc.drop_flags; + LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags)); + + /* Drop flags "shall be cleared to 0" but also "do not apply" (4.6.7). Log & ignore. */ + if (uDropFlags) + Log(("Drop flags set (%X) for evaluating context!\n", uDropFlags)); + + /* If no add flags are set, nothing will be done but an error is not reported + * according to the logic flow in 4.6.7. + */ + if (!uAddFlags) + { + Log(("Warning: no add flags set for evaluating context!\n")); + break; + } + + /* Calculate the address of the input slot context (ICI=1/DCI=0). */ + GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_inp_ctx = ASMBitLastSetU32(uAddFlags); + Assert(num_inp_ctx); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY)); + + /* Read the output Slot Context plus all Endpoint Contexts up to and + * including the one with the highest 'add' bit set. + */ + num_out_ctx = ASMBitLastSetU32(uAddFlags); + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + + /// @todo Check input slot context according to 6.2.2.3 + /// @todo Check input EP contexts according to 6.2.3.3 + /// @todo Check that the highest set add flag isn't beyond input slot Context Entries + + /* Evaluate endpoint contexts as directed by add flags. */ + /// @todo 6.2.3.3 suggests only the A1 bit matters? Anything besides A0/A1 is ignored?? + for (uDCI = 1; uDCI < 32; ++uDCI) + { + if (!((1 << uDCI) & uAddFlags)) + continue; + + /* Evaluate Max Packet Size. */ + LogFunc(("DCI %u: Max Packet Size: %u -> %u\n", uDCI, dc_out.entry[uDCI].ep.max_pkt_sz, dc_inp.entry[uDCI].ep.max_pkt_sz)); + dc_out.entry[uDCI].ep.max_pkt_sz = dc_inp.entry[uDCI].ep.max_pkt_sz; + } + + /* Finally update the device context if directed to do so (A0 flag set). */ + if (uAddFlags & RT_BIT(0)) + { + /* 6.2.2.3 - evaluate Interrupter Target and Max Exit Latency. */ + Log(("Interrupter Target: %u -> %u\n", dc_out.entry[0].sc.intr_tgt, dc_inp.entry[0].sc.intr_tgt)); + Log(("Max Exit Latency : %u -> %u\n", dc_out.entry[0].sc.max_lat, dc_inp.entry[0].sc.max_lat)); + + /// @todo Non-zero Max Exit Latency (see 4.6.7) + dc_out.entry[0].sc.intr_tgt = dc_inp.entry[0].sc.intr_tgt; + dc_out.entry[0].sc.max_lat = dc_inp.entry[0].sc.max_lat; + } + + /* If there were no errors, write back the updated output context. */ + LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot)); + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY)); + } while (0); + + return cc; +} + + +/** + * Query available port bandwidth. + * + * @returns TRB completion code. + * @param pDevIns The device instance. + * @param pThis Pointer to the xHCI state. + * @param uDevSpd Speed of not yet attached devices. + * @param uHubSlotID Hub Slot ID to query (unsupported). + * @param uBwCtx Bandwidth context physical address. + */ +static unsigned xhciR3GetPortBandwidth(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uDevSpd, uint8_t uHubSlotID, uint64_t uBwCtx) +{ + RT_NOREF(uHubSlotID); + RTGCPHYS GCPhysBwCtx; + unsigned cc = XHCI_TCC_SUCCESS; + unsigned ctx_size; + unsigned iPort; + uint8_t bw_ctx[RT_ALIGN_32(XHCI_NDP_MAX + 1, 4)] = {0}; + uint8_t dev_spd; + uint8_t avail_bw; + + Assert(!uHubSlotID); + Assert(uBwCtx); + + /* See 4.6.15. */ + + /* Hubs are not supported because guests will never see them. The + * reported values are more or less dummy because we have no real + * information about the bandwidth available on the host. The reported + * values are optimistic, as if each port had its own separate Bus + * Instance aka BI. + */ + + GCPhysBwCtx = uBwCtx & XHCI_CTX_ADDR_MASK; + + /* Number of ports + 1, rounded up to DWORDs. */ + ctx_size = RT_ALIGN_32(XHCI_NDP_CFG(pThis) + 1, 4); + LogFlowFunc(("BW Context at %RGp, size %u\n", GCPhysBwCtx, ctx_size)); + Assert(ctx_size <= sizeof(bw_ctx)); + + /* Go over all the ports. */ + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + /* Get the device speed from the port... */ + dev_spd = (pThis->aPorts[iPort].portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + /* ...and if nothing is attached, use the provided default. */ + if (!dev_spd) + dev_spd = uDevSpd; + + /* For USB3 ports, report 90% available for SS devices (see 6.2.6). */ + if (IS_USB3_PORT_IDX_SHR(pThis, iPort)) + avail_bw = dev_spd == XHCI_SPD_SUPER ? 90 : 0; + else + /* For USB2 ports, report 80% available for HS and 90% for FS/LS. */ + switch (dev_spd) + { + case XHCI_SPD_HIGH: + avail_bw = 80; + break; + case XHCI_SPD_FULL: + case XHCI_SPD_LOW: + avail_bw = 90; + break; + default: + avail_bw = 0; + } + + /* The first entry in the context is reserved. */ + bw_ctx[iPort + 1] = avail_bw; + } + + /* Write back the bandwidth context. */ + PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysBwCtx, &bw_ctx, ctx_size); + + return cc; +} + +#define NEC_MAGIC ('x' | ('H' << 8) | ('C' << 16) | ('I' << 24)) + +/** + * Take a 64-bit input, shake well, produce 32-bit token. This mechanism + * prevents NEC/Renesas drivers from running on 3rd party hardware. Mirrors + * code found in vendor's drivers. + */ +static uint32_t xhciR3NecAuthenticate(uint64_t cookie) +{ + uint32_t cookie_lo = RT_LODWORD(cookie); + uint32_t cookie_hi = RT_HIDWORD(cookie); + uint32_t shift_cnt; + uint32_t token; + + shift_cnt = (cookie_hi >> 8) & 31; + token = ASMRotateRightU32(cookie_lo - NEC_MAGIC, shift_cnt); + shift_cnt = cookie_hi & 31; + token += ASMRotateLeftU32(cookie_lo + NEC_MAGIC, shift_cnt); + shift_cnt = (cookie_lo >> 16) & 31; + token -= ASMRotateLeftU32(cookie_hi ^ NEC_MAGIC, shift_cnt); + + return ~token; +} + +/** + * Process a single command TRB and post completion information. + */ +static int xhciR3ExecuteCommand(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, XHCI_COMMAND_TRB *pCmd) +{ + XHCI_EVENT_TRB ed; + uint32_t token; + unsigned slot; + unsigned cc; + int rc = VINF_SUCCESS; + LogFlowFunc(("Executing command %u (%s) @ %RGp\n", pCmd->gen.type, + pCmd->gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[pCmd->gen.type] : "WHAT?!!", + (RTGCPHYS)pThis->cmdr_dqp)); + + switch (pCmd->gen.type) + { + case XHCI_TRB_NOOP_CMD: + /* No-op, slot ID is always zero. */ + rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_LINK: + /* Link; set the dequeue pointer. CH bit is ignored. */ + Log(("Link: Ptr=%RGp IOC=%u TC=%u\n", pCmd->link.rseg_ptr, pCmd->link.ioc, pCmd->link.toggle)); + if (pCmd->link.ioc) /* Command completion event is optional! */ + rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0); + /* Update the dequeue pointer and flip DCS if required. */ + pThis->cmdr_dqp = pCmd->link.rseg_ptr & XHCI_TRDP_ADDR_MASK; + pThis->cmdr_ccs = pThis->cmdr_ccs ^ pCmd->link.toggle; + break; + + case XHCI_TRB_ENB_SLOT: + /* Look for an empty device slot. */ + for (slot = 0; slot < RT_ELEMENTS(pThis->aSlotState); ++slot) + { + if (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY) + { + /* Found a slot - transition to enabled state. */ + pThis->aSlotState[slot] = XHCI_DEVSLOT_ENABLED; + break; + } + } + Log(("Enable Slot: found slot ID %u\n", IDX_TO_ID(slot))); + + /* Post command completion event. */ + if (slot == RT_ELEMENTS(pThis->aSlotState)) + xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_NO_SLOTS, 0); + else + xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, IDX_TO_ID(slot)); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_DIS_SLOT: + /* Disable the given device slot. */ + Log(("Disable Slot: slot ID %u\n", pCmd->dsl.slot_id)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->dsl.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + { + /// @todo set slot state of assoc. context to disabled + pThis->aSlotState[slot] = XHCI_DEVSLOT_EMPTY; + } + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->dsl.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_ADDR_DEV: + /* Address a device. */ + Log(("Address Device: slot ID %u, BSR=%u\n", pCmd->adr.slot_id, pCmd->adr.bsr)); + slot = ID_TO_IDX(pCmd->cfg.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3AddressDevice(pDevIns, pThis, pThisCC, pCmd->adr.ctx_ptr, pCmd->adr.slot_id, pCmd->adr.bsr); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->adr.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_CFG_EP: + /* Configure endpoint. */ + Log(("Configure endpoint: slot ID %u, DC=%u, Ctx @ %RGp\n", pCmd->cfg.slot_id, pCmd->cfg.dc, pCmd->cfg.ctx_ptr)); + slot = ID_TO_IDX(pCmd->cfg.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ConfigureDevice(pDevIns, pThis, pCmd->cfg.ctx_ptr, pCmd->cfg.slot_id, pCmd->cfg.dc); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->cfg.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_EVAL_CTX: + /* Evaluate context. */ + Log(("Evaluate context: slot ID %u, Ctx @ %RGp\n", pCmd->evc.slot_id, pCmd->evc.ctx_ptr)); + slot = ID_TO_IDX(pCmd->evc.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3EvalContext(pDevIns, pThis, pCmd->evc.ctx_ptr, pCmd->evc.slot_id); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->evc.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_RESET_EP: + /* Reset the given endpoint. */ + Log(("Reset Endpoint: slot ID %u, EP ID %u, TSP=%u\n", pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->rse.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ResetEndpoint(pDevIns, pThis, pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_STOP_EP: + /* Stop the given endpoint. */ + Log(("Stop Endpoint: slot ID %u, EP ID %u, SP=%u\n", pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->stp.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3StopEndpoint(pDevIns, pThis, pThisCC, pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_SET_DEQ_PTR: + /* Set TR Dequeue Pointer. */ + Log(("Set TRDP: slot ID %u, EP ID %u, TRDP=%RX64\n", pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->stdp.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3SetTRDP(pDevIns, pThis, pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stdp.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_RESET_DEV: + /* Reset a device. */ + Log(("Reset Device: slot ID %u\n", pCmd->rsd.slot_id)); + cc = XHCI_TCC_SUCCESS; + slot = ID_TO_IDX(pCmd->rsd.slot_id); + if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)) + cc = XHCI_TCC_SLOT_NOT_ENB; + else + cc = xhciR3ResetDevice(pDevIns, pThis, pCmd->rsd.slot_id); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->rsd.slot_id); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case XHCI_TRB_GET_PORT_BW: + /* Get port bandwidth. */ + Log(("Get Port Bandwidth: Dev Speed %u, Hub Slot ID %u, Context=%RX64\n", pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr)); + cc = XHCI_TCC_SUCCESS; + if (pCmd->gpbw.slot_id) + cc = XHCI_TCC_PARM_ERR; /* Potential undefined behavior, see 4.6.15. */ + else + cc = xhciR3GetPortBandwidth(pDevIns, pThis, pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr); + xhciR3PostCmdCompletion(pDevIns, pThis, cc, 0); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case NEC_TRB_GET_FW_VER: + /* Get NEC firmware version. */ + Log(("Get NEC firmware version\n")); + cc = XHCI_TCC_SUCCESS; + + RT_ZERO(ed); + ed.nce.word1 = NEC_FW_REV; + ed.nce.trb_ptr = pThis->cmdr_dqp; + ed.nce.cc = cc; + ed.nce.type = NEC_TRB_CMD_CMPL; + + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + case NEC_TRB_AUTHENTICATE: + /* NEC authentication. */ + Log(("NEC authentication, cookie %RX64\n", pCmd->nac.cookie)); + cc = XHCI_TCC_SUCCESS; + + token = xhciR3NecAuthenticate(pCmd->nac.cookie); + RT_ZERO(ed); + ed.nce.word1 = RT_LOWORD(token); + ed.nce.word2 = RT_HIWORD(token); + ed.nce.trb_ptr = pThis->cmdr_dqp; + ed.nce.cc = cc; + ed.nce.type = NEC_TRB_CMD_CMPL; + + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + + default: + Log(("Unsupported command!\n")); + pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB); + break; + } + + return rc; +} + + +/** + * Stop the Command Ring. + */ +static int xhciR3StopCommandRing(PPDMDEVINS pDevIns, PXHCI pThis) +{ + LogFlowFunc(("Command Ring stopping\n")); + + Assert(pThis->crcr & (XHCI_CRCR_CA | XHCI_CRCR_CS)); + Assert(pThis->crcr & XHCI_CRCR_CRR); + ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS)); + return xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_CMDR_STOPPED, 0); +} + + +/** + * Process the xHCI command ring. + */ +static int xhciR3ProcessCommandRing(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + RTGCPHYS GCPhysCmdTRB; + XHCI_COMMAND_TRB cmd; /* Command Descriptor */ + unsigned cCmds; + + Assert(pThis->crcr & XHCI_CRCR_CRR); + LogFlowFunc(("Processing commands...\n")); + + for (cCmds = 0;; cCmds++) + { + /* First check if the xHC is running at all. */ + if (!(pThis->cmd & XHCI_CMD_RS)) + { + /* Note that this will call xhciR3PostCmdCompletion() which will + * end up doing nothing because R/S is clear. + */ + xhciR3StopCommandRing(pDevIns, pThis); + break; + } + + /* Check if Command Ring was stopped in the meantime. */ + if (pThis->crcr & (XHCI_CRCR_CS | XHCI_CRCR_CA)) + { + /* NB: We currently do not abort commands. If we did, we would + * abort the currently running command and complete it with + * the XHCI_TCC_CMD_ABORTED status. + */ + xhciR3StopCommandRing(pDevIns, pThis); + break; + } + + /* Fetch the command TRB. */ + GCPhysCmdTRB = pThis->cmdr_dqp; + PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCmdTRB, &cmd, sizeof(cmd)); + + /* Make sure the Cycle State matches. */ + if ((bool)cmd.gen.cycle == pThis->cmdr_ccs) + xhciR3ExecuteCommand(pDevIns, pThis, pThisCC, &cmd); + else + { + Log(("Command Ring empty\n")); + break; + } + + /* Check if we're being fed suspiciously many commands. */ + if (cCmds > XHCI_MAX_NUM_CMDS) + { + /* Clear the R/S bit and any command ring running bits. + * Note that the caller (xhciR3WorkerLoop) will set XHCI_STATUS_HCH. + */ + ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS); + ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS)); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE); + LogRelMax(10, ("xHCI: Attempted to execute too many commands, stopping xHC!\n")); + break; + } + } + return VINF_SUCCESS; +} + + +/** + * The xHCI asynchronous worker thread. + * + * @returns VBox status code. + * @param pDevIns The xHCI device instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) xhciR3WorkerLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + int rc; + + LogFlow(("xHCI entering worker thread loop.\n")); + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + uint32_t u32Tasks = 0; + uint8_t uSlotID; + + ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, true); + u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0); + if (!u32Tasks) + { + Assert(ASMAtomicReadBool(&pThis->fWrkThreadSleeping)); + rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hEvtProcess, RT_INDEFINITE_WAIT); + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + LogFlowFunc(("Woken up with rc=%Rrc\n", rc)); + u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0); + } + + RTCritSectEnter(&pThisCC->CritSectThrd); + + if (pThis->crcr & XHCI_CRCR_CRR) + xhciR3ProcessCommandRing(pDevIns, pThis, pThisCC); + + /* Run down the list of doorbells that are ringing. */ + for (uSlotID = 1; uSlotID < XHCI_NDS; ++uSlotID) + { + if (pThis->aSlotState[ID_TO_IDX(uSlotID)] >= XHCI_DEVSLOT_ENABLED) + { + while (pThis->aBellsRung[ID_TO_IDX(uSlotID)]) + { + uint8_t bit; + uint32_t uDBVal = 0; + + for (bit = 0; bit < 32; ++bit) + if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << bit)) + { + uDBVal = bit; + break; + } + + Log2(("Stop ringing bell for slot %u, DCI %u\n", uSlotID, uDBVal)); + ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDBVal)); + xhciR3ProcessDevCtx(pDevIns, pThis, pThisCC, uSlotID, uDBVal); + } + } + } + + /* If the R/S bit is no longer set, halt the xHC. */ + if (!(pThis->cmd & XHCI_CMD_RS)) + { + Log(("R/S clear, halting the xHC.\n")); + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH); + } + + RTCritSectLeave(&pThisCC->CritSectThrd); + + ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, false); + } /* While running */ + + LogFlow(("xHCI worker thread exiting.\n")); + return VINF_SUCCESS; +} + + +/** + * Unblock the worker thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDevIns The xHCI device instance. + * @param pThread The worker thread. + */ +static DECLCALLBACK(int) xhciR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread) +{ + NOREF(pThread); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess); +} + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) xhciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pRh->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pRh->IRhPort); + return NULL; +} + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) xhciR3QueryStatusInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PXHCIR3 pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->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) xhciR3QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + PXHCICC pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, ILeds); + + if (iLUN < XHCI_NUM_LUNS) + { + *ppLed = iLUN ? &pThisCC->RootHub3.Led : &pThisCC->RootHub2.Led; + Assert((*ppLed)->u32Magic == PDMLED_MAGIC); + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** + * Get the number of ports available 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) xhciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + unsigned iPort; + unsigned cPorts = 0; + LogFlow(("xhciR3RhGetAvailablePorts\n")); + + memset(pAvailable, 0, sizeof(*pAvailable)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock); + + for (iPort = pRh->uPortBase; iPort < (unsigned)pRh->uPortBase + pRh->cPortsImpl; iPort++) + { + Assert(iPort < XHCI_NDP_CFG(PDMDEVINS_2_DATA(pDevIns, PXHCI))); + if (!pThisCC->aPorts[iPort].fAttached) + { + cPorts++; + ASMBitSet(pAvailable, IDX_TO_ID(iPort - pRh->uPortBase)); + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return cPorts; +} + + +/** + * Get the supported USB versions for USB2 hubs. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions2(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_11 | VUSB_STDVER_20; +} + + +/** + * Get the supported USB versions for USB2 hubs. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions3(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_30; +} + + +/** + * Start sending SOF tokens across the USB bus, lists are processed in the + * next frame. + */ +static void xhciR3BusStart(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + unsigned iPort; + + pThisCC->RootHub2.pIRhConn->pfnPowerOn(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnPowerOn(pThisCC->RootHub3.pIRhConn); +// xhciR3BumpFrameNumber(pThis); + + Log(("xHCI: Bus started\n")); + + Assert(pThis->status & XHCI_STATUS_HCH); + ASMAtomicAndU32(&pThis->status, ~XHCI_STATUS_HCH); + + /* HCH gates PSCEG (4.19.2). When clearing HCH, re-evaluate port changes. */ + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + if (pThis->aPorts[iPort].portsc & XHCI_PORT_CHANGE_MASK) + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + } + + /// @todo record the starting time? +// pThis->SofTime = TMTimerGet(pThis->CTX_SUFF(pEndOfFrameTimer)) - pThis->cTicksPerFrame; +} + +/** + * Stop sending SOF tokens on the bus and processing the data. + */ +static void xhciR3BusStop(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC) +{ + LogFlow(("xhciR3BusStop\n")); + + /* Stop the controller and Command Ring. */ + pThis->cmd &= ~XHCI_CMD_RS; + pThis->crcr |= XHCI_CRCR_CS; + + /* Power off the root hubs. */ + pThisCC->RootHub2.pIRhConn->pfnPowerOff(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnPowerOff(pThisCC->RootHub3.pIRhConn); + + /* The worker thread will halt the HC (set HCH) when done. */ + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); +} + + +/** + * Power a port up or down + */ +static void xhciR3PortPower(PXHCI pThis, PXHCICC pThisCC, unsigned iPort, bool fPowerUp) +{ + PXHCIHUBPORT pPort = &pThis->aPorts[iPort]; + PXHCIHUBPORTR3 pPortR3 = &pThisCC->aPorts[iPort]; + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + bool fOldPPS = !!(pPort->portsc & XHCI_PORT_PP); + LogFlow(("xhciR3PortPower (port %u) %s\n", IDX_TO_ID(iPort), fPowerUp ? "UP" : "DOWN")); + + if (fPowerUp) + { + /* Power up a port. */ + if (pPortR3->fAttached) + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS); + if (pPort->portsc & XHCI_PORT_CCS) + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_PP); + if (pPortR3->fAttached && !fOldPPS) + VUSBIRhDevPowerOn(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort)); + } + else + { + /* Power down. */ + ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_PP | XHCI_PORT_CCS)); + if (pPortR3->fAttached && fOldPPS) + VUSBIRhDevPowerOff(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort)); + } +} + + +/** + * Port reset done callback. + * + * @returns nothing. + * @param pDevIns The device instance data. + * @param iPort The XHCI port index of the port being resetted. + */ +static void xhciR3PortResetDone(PPDMDEVINS pDevIns, unsigned iPort) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + Log2(("xhciR3PortResetDone\n")); + + AssertReturnVoid(iPort < XHCI_NDP_CFG(pThis)); + + /* + * Successful reset. + */ + Log2(("xhciR3PortResetDone: Reset completed.\n")); + + uint32_t fChangeMask = XHCI_PORT_PED | XHCI_PORT_PRC; + /* For USB2 ports, transition the link state. */ + if (!IS_USB3_PORT_IDX_SHR(pThis, iPort)) + { + pThis->aPorts[iPort].portsc &= ~XHCI_PORT_PLS_MASK; + pThis->aPorts[iPort].portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT; + } + else + { + if (pThis->aPorts[iPort].portsc & XHCI_PORT_WPR) + fChangeMask |= XHCI_PORT_WRC; + } + + ASMAtomicAndU32(&pThis->aPorts[iPort].portsc, ~(XHCI_PORT_PR | XHCI_PORT_WPR)); + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fChangeMask); + /// @todo Set USBSTS.PCD and manage PSCEG correctly! + /// @todo just guessing?! +// ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CSC | XHCI_PORT_PLC); + + /// @todo Is this the right place? + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); +} + + +/** + * Sets a flag in a port status register, but only 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 xhciR3RhPortSetIfConnected(PXHCI pThis, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * The port might be still/already disconnected. + */ + if (!(pThis->aPorts[iPort].portsc & XHCI_PORT_CCS)) + return false; + + bool fRc = !(pThis->aPorts[iPort].portsc & fValue); + + /* Set the bit. */ + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fValue); + + return fRc; +} + + +/** Translate VUSB speed enum to xHCI definition. */ +static unsigned xhciR3UsbSpd2XhciSpd(VUSBSPEED enmSpeed) +{ + unsigned uSpd; + + switch (enmSpeed) + { + default: AssertMsgFailed(("%d\n", enmSpeed)); + RT_FALL_THRU(); + case VUSB_SPEED_LOW: uSpd = XHCI_SPD_LOW; break; + case VUSB_SPEED_FULL: uSpd = XHCI_SPD_FULL; break; + case VUSB_SPEED_HIGH: uSpd = XHCI_SPD_HIGH; break; + case VUSB_SPEED_SUPER: uSpd = XHCI_SPD_SUPER; break; + } + return uSpd; +} + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */ +static DECLCALLBACK(int) xhciR3RhAttach(PVUSBIROOTHUBPORT pInterface, unsigned uPort, VUSBSPEED enmSpeed) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCIHUBPORT pPort; + unsigned iPort; + LogFlow(("xhciR3RhAttach: uPort=%u (iPort=%u)\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase)); + + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + AssertRCReturn(rcLock, rcLock); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= pRh->cPortsImpl); + iPort = ID_TO_IDX(uPort) + pRh->uPortBase; + Assert(iPort < XHCI_NDP_CFG(pThis)); + pPort = &pThis->aPorts[iPort]; + Assert(!pThisCC->aPorts[iPort].fAttached); + Assert(enmSpeed != VUSB_SPEED_UNKNOWN); + + /* + * Attach it. + */ + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS | XHCI_PORT_CSC); + pThisCC->aPorts[iPort].fAttached = true; + xhciR3PortPower(pThis, pThisCC, iPort, 1 /* power on */); + + /* USB3 ports automatically transition to Enabled state. */ + if (IS_USB3_PORT_IDX_R3(pThisCC, iPort)) + { + Assert(enmSpeed == VUSB_SPEED_SUPER); + pPort->portsc |= XHCI_PORT_PED; + pPort->portsc &= ~XHCI_PORT_PLS_MASK; + pPort->portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT; + pPort->portsc &= ~XHCI_PORT_SPD_MASK; + pPort->portsc |= XHCI_SPD_SUPER << XHCI_PORT_SPD_SHIFT; + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), + false, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + } + else + { + Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL || enmSpeed == VUSB_SPEED_HIGH); + pPort->portsc &= ~XHCI_PORT_SPD_MASK; + pPort->portsc |= xhciR3UsbSpd2XhciSpd(enmSpeed) << XHCI_PORT_SPD_SHIFT; + } + + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * A device is being detached from a port in the root hub. + * + * @param pInterface Pointer to this structure. + * @param uPort The 1-based port number assigned to the device. + */ +static DECLCALLBACK(void) xhciR3RhDetach(PVUSBIROOTHUBPORT pInterface, unsigned uPort) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCIHUBPORT pPort; + unsigned iPort; + LogFlow(("xhciR3RhDetach: uPort=%u iPort=%u\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase)); + 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 <= pRh->cPortsImpl); + iPort = ID_TO_IDX(uPort) + pRh->uPortBase; + Assert(iPort < XHCI_NDP_CFG(pThis)); + pPort = &pThis->aPorts[iPort]; + Assert(pThisCC->aPorts[iPort].fAttached); + + /* + * Detach it. + */ + pThisCC->aPorts[iPort].fAttached = false; + ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_CCS | XHCI_PORT_SPD_MASK | XHCI_PORT_PLS_MASK)); + ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CSC); + /* Link state goes to RxDetect. */ + ASMAtomicOrU32(&pPort->portsc, XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT); + /* Disconnect clears the port enable bit. */ + if (pPort->portsc & XHCI_PORT_PED) + ASMAtomicAndU32(&pPort->portsc, ~XHCI_PORT_PED); + + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); +} + + +/** + * One of the root hub 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 rc The result of the operation. + * @param uPort The port number of the device on the roothub being resetted. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) xhciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser) +{ + LogRel(("xHCI: Root hub-attached device 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 pThis The shared XHCI instance data + * @param pThisCC The ring-3 XHCI 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 fTrueReset 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 has nothing to do with software setting the + * mode to UsbReset. + */ +static void xhciR3DoReset(PXHCI pThis, PXHCICC pThisCC, uint32_t fNewMode, bool fTrueReset) +{ + LogFunc(("%s reset%s\n", fNewMode == XHCI_USB_RESET ? "Hardware" : "Software", + fTrueReset ? " (really reset devices)" : "")); + + /* + * 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. + */ + pThisCC->RootHub2.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub2.pIRhConn); + pThisCC->RootHub3.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub3.pIRhConn); + + /* + * Reset the hardware registers. + */ + /** @todo other differences between hardware reset and VM reset? */ + + pThis->cmd = 0; + pThis->status = XHCI_STATUS_HCH; + pThis->dnctrl = 0; + pThis->crcr = 0; + pThis->dcbaap = 0; + pThis->config = 0; + + /* + * Reset the internal state. + */ + pThis->cmdr_dqp = 0; + pThis->cmdr_ccs = 0; + + RT_ZERO(pThis->aSlotState); + RT_ZERO(pThis->aBellsRung); + + /* Zap everything but the lock. */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + pThis->aInterrupters[i].iman = 0; + pThis->aInterrupters[i].imod = 0; + pThis->aInterrupters[i].erstsz = 0; + pThis->aInterrupters[i].erstba = 0; + pThis->aInterrupters[i].erdp = 0; + pThis->aInterrupters[i].erep = 0; + pThis->aInterrupters[i].erst_idx = 0; + pThis->aInterrupters[i].trb_count = 0; + pThis->aInterrupters[i].evtr_pcs = false; + pThis->aInterrupters[i].ipe = false; + } + + if (fNewMode == XHCI_USB_RESET) + { + /* Only a hardware reset reinits the port registers. */ + for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++) + { + /* Need to preserve the speed of attached devices. */ + pThis->aPorts[i].portsc &= XHCI_PORT_SPD_MASK; + pThis->aPorts[i].portsc |= XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT; + /* If Port Power Control is not supported, ports are always powered on. */ + if (!(pThis->hcc_params & XHCI_HCC_PPC)) + pThis->aPorts[i].portsc |= XHCI_PORT_PP; + } + } + + /* + * 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 a device connected at the time of the + * device construction, so nothing to worry about there.) + */ + if (fNewMode == XHCI_USB_RESET) + { + pThisCC->RootHub2.pIRhConn->pfnReset(pThisCC->RootHub2.pIRhConn, fTrueReset); + pThisCC->RootHub3.pIRhConn->pfnReset(pThisCC->RootHub3.pIRhConn, fTrueReset); + + /* + * Reattach the devices. + */ + for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++) + { + bool fAttached = pThisCC->aPorts[i].fAttached; + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, i); + pThisCC->aPorts[i].fAttached = false; + + if (fAttached) + { + VUSBSPEED enmSpeed = VUSBIRhDevGetSpeed(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i)); + xhciR3RhAttach(&pRh->IRhPort, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i), enmSpeed); + } + } + } +} + +/** + * Reset the root hub. + * + * @returns VBox status code. + * @param pInterface Pointer to this structure. + * @param fTrueReset 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) xhciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fTrueReset) +{ + PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort); + PXHCICC pThisCC = pRh->pXhciR3; + PPDMDEVINS pDevIns = pThisCC->pDevIns; + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + Log(("xhciR3RhReset fTrueReset=%d\n", fTrueReset)); + int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED); + AssertRCReturn(rcLock, rcLock); + + /* Soft reset first */ + xhciR3DoReset(pThis, pThisCC, XHCI_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 talk 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 = pRh->uPortBase; iPort < XHCI_NDP_CFG(pThis); iPort++) + { + if (pThisCC->aPorts[iPort].fAttached) + { + ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CCS | XHCI_PORT_CSC); + if (fTrueReset) + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), fTrueReset, + xhciR3RhResetDoneOneDev, pDevIns, PDMDevHlpGetVM(pDevIns)); + } + } + + PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3); + return VINF_SUCCESS; +} + +#endif /* IN_RING3 */ + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Operational Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcUsbcmd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdUsbCmd); + *pu32Value = pThis->cmd; + return VINF_SUCCESS; +} + +/** + * Write to the USBCMD register of the host controller. + */ +static VBOXSTRICTRC HcUsbcmd_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); +#endif + RT_NOREF(iReg); + STAM_COUNTER_INC(&pThis->StatWrUsbCmd); +#ifdef LOG_ENABLED + Log(("HcUsbcmd_w old=%x new=%x\n", pThis->cmd, val)); + if (val & XHCI_CMD_RS) + Log((" XHCI_CMD_RS\n")); + if (val & XHCI_CMD_HCRST) + Log((" XHCI_CMD_HCRST\n")); + if (val & XHCI_CMD_INTE ) + Log((" XHCI_CMD_INTE\n")); + if (val & XHCI_CMD_HSEE) + Log((" XHCI_CMD_HSEE\n")); + if (val & XHCI_CMD_LCRST) + Log((" XHCI_CMD_LCRST\n")); + if (val & XHCI_CMD_CSS) + Log((" XHCI_CMD_CSS\n")); + if (val & XHCI_CMD_CRS) + Log((" XHCI_CMD_CRS\n")); + if (val & XHCI_CMD_EWE) + Log((" XHCI_CMD_EWE\n")); + if (val & XHCI_CMD_EU3S) + Log((" XHCI_CMD_EU3S\n")); +#endif + + if (val & ~XHCI_CMD_MASK) + Log(("Unknown USBCMD bits %#x are set!\n", val & ~XHCI_CMD_MASK)); + + uint32_t old_cmd = pThis->cmd; +#ifdef IN_RING3 + pThis->cmd = val; +#endif + + if (val & XHCI_CMD_HCRST) + { +#ifdef IN_RING3 + LogRel(("xHCI: Hardware reset\n")); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else if (val & XHCI_CMD_LCRST) + { +#ifdef IN_RING3 + LogRel(("xHCI: Software reset\n")); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_SUSPEND, false /* N/A */); +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + else if (pThis->status & XHCI_STATUS_HCE) + { + /* If HCE is set, don't restart the controller. Only a reset + * will clear the HCE bit. + */ + Log(("xHCI: HCE bit set, ignoring USBCMD register changes!\n")); + pThis->cmd = old_cmd; + return VINF_SUCCESS; + } + else + { + /* See what changed and take action on that. First the R/S bit. */ + uint32_t old_state = old_cmd & XHCI_CMD_RS; + uint32_t new_state = val & XHCI_CMD_RS; + + if (old_state != new_state) + { +#ifdef IN_RING3 + switch (new_state) + { + case XHCI_CMD_RS: + LogRel(("xHCI: USB Operational\n")); + xhciR3BusStart(pDevIns, pThis, pThisCC); + break; + case 0: + xhciR3BusStop(pDevIns, pThis, pThisCC); + LogRel(("xHCI: USB Suspended\n")); + break; + } +#else + return VINF_IOM_R3_MMIO_WRITE; +#endif + } + + /* Check EWE (Enable MFINDEX Wraparound Event) changes. */ + old_state = old_cmd & XHCI_CMD_EWE; + new_state = val & XHCI_CMD_EWE; + + if (old_state != new_state) + { + switch (new_state) + { + case XHCI_CMD_EWE: + Log(("xHCI: MFINDEX Wrap timer started\n")); + xhciSetWrapTimer(pDevIns, pThis); + break; + case 0: + PDMDevHlpTimerStop(pDevIns, pThis->hWrapTimer); + Log(("xHCI: MFINDEX Wrap timer stopped\n")); + break; + } + } + + /* INTE transitions need to twiddle interrupts. */ + old_state = old_cmd & XHCI_CMD_INTE; + new_state = val & XHCI_CMD_INTE; + if (old_state != new_state) + { + switch (new_state) + { + case XHCI_CMD_INTE: + /* Check whether the event interrupt bit is set and trigger an interrupt. */ + if (pThis->status & XHCI_STATUS_EINT) + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + break; + case 0: + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW); + break; + } + } + + /* We currently do nothing for state save/restore. If we did, the CSS/CRS command bits + * would set the SSS/RSS status bits until the operation is done. The CSS/CRS bits are + * never read as one. + */ + /// @todo 4.9.4 describes internal state that needs to be saved/restored: + /// ERSTE, ERST Count, EREP, and TRB Count + /// Command Ring Dequeue Pointer? + if (val & XHCI_CMD_CSS) + { + Log(("xHCI: Save State requested\n")); + val &= ~XHCI_CMD_CSS; + } + + if (val & XHCI_CMD_CRS) + { + Log(("xHCI: Restore State requested\n")); + val &= ~XHCI_CMD_CRS; + } + } +#ifndef IN_RING3 + pThis->cmd = val; +#endif + return VINF_SUCCESS; +} + +#ifdef LOG_ENABLED +static void HcUsbstsLogBits(uint32_t val) +{ + if (val & XHCI_STATUS_HCH) + Log((" XHCI_STATUS_HCH (HC Halted)\n")); + if (val & XHCI_STATUS_HSE) + Log((" XHCI_STATUS_HSE (Host System Error)\n")); + if (val & XHCI_STATUS_EINT) + Log((" XHCI_STATUS_EINT (Event Interrupt)\n")); + if (val & XHCI_STATUS_PCD) + Log((" XHCI_STATUS_PCD (Port Change Detect)\n")); + if (val & XHCI_STATUS_SSS) + Log((" XHCI_STATUS_SSS (Save State Status)\n")); + if (val & XHCI_STATUS_RSS) + Log((" XHCI_STATUS_RSS (Restore State Status)\n")); + if (val & XHCI_STATUS_SRE) + Log((" XHCI_STATUS_SRE (Save/Restore Error)\n")); + if (val & XHCI_STATUS_CNR) + Log((" XHCI_STATUS_CNR (Controller Not Ready)\n")); + if (val & XHCI_STATUS_HCE) + Log((" XHCI_STATUS_HCE (Host Controller Error)\n")); +} +#endif + +/** + * Read the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcUsbsts_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#ifdef LOG_ENABLED + Log(("HcUsbsts_r current value %x\n", pThis->status)); + HcUsbstsLogBits(pThis->status); +#endif + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdUsbSts); + + *pu32Value = pThis->status; + return VINF_SUCCESS; +} + +/** + * Write to the USBSTS register of the host controller. + */ +static VBOXSTRICTRC HcUsbsts_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef LOG_ENABLED + Log(("HcUsbsts_w current value %x; new %x\n", pThis->status, val)); + HcUsbstsLogBits(val); +#endif + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrUsbSts); + + if ( (val & ~XHCI_STATUS_WRMASK) + && val != 0xffffffff /* Ignore clear-all-like requests. */) + Log(("Unknown USBSTS bits %#x are set!\n", val & ~XHCI_STATUS_WRMASK)); + + /* Most bits are read-only. */ + val &= XHCI_STATUS_WRMASK; + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + ASMAtomicAndU32(&pThis->status, ~val); + + return VINF_SUCCESS; +} + +/** + * Read the PAGESIZE register of the host controller. + */ +static VBOXSTRICTRC HcPagesize_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iReg); + STAM_COUNTER_INC(&pThis->StatRdPageSize); + *pu32Value = 1; /* 2^(bit n + 12) -> 4K page size only. */ + return VINF_SUCCESS; +} + +/** + * Read the DNCTRL (Device Notification Control) register. + */ +static VBOXSTRICTRC HcDevNotifyCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevNotifyCtrl); + *pu32Value = pThis->dnctrl; + return VINF_SUCCESS; +} + +/** + * Write the DNCTRL (Device Notification Control) register. + */ +static VBOXSTRICTRC HcDevNotifyCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevNotifyCtrl); + pThis->dnctrl = val; + return VINF_SUCCESS; +} + +/** + * Read the low dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlLo); + *pu32Value = (uint32_t)(pThis->crcr & XHCI_CRCR_RD_MASK); + return VINF_SUCCESS; +} + +/** + * Write the low dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(iReg); + STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlLo); + /* NB: A dword write to the low half clears the high half. */ + + /* Sticky Abort/Stop bits - update register and kick the worker thread. */ + if (val & (XHCI_CRCR_CA | XHCI_CRCR_CS)) + { + pThis->crcr |= val & (XHCI_CRCR_CA | XHCI_CRCR_CS); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); + } + + /* + * If the command ring is not running, the internal dequeue pointer + * and the cycle state is updated. Otherwise the update is ignored. + */ + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + pThis->crcr = (pThis->crcr & ~XHCI_CRCR_UPD_MASK) | (val & XHCI_CRCR_UPD_MASK); + /// @todo cmdr_dqp: atomic? volatile? + pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK; + pThis->cmdr_ccs = pThis->crcr & XHCI_CRCR_RCS; + } + + return VINF_SUCCESS; +} + +/** + * Read the high dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlHi); + *pu32Value = pThis->crcr >> 32; + return VINF_SUCCESS; +} + +/** + * Write the high dword of CRCR (Command Ring Control) register. + */ +static VBOXSTRICTRC HcCmdRingCtlHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlHi); + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + pThis->crcr = ((uint64_t)val << 32) | (uint32_t)pThis->crcr; + pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK; + } + return VINF_SUCCESS; +} + +/** + * Read the low dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapLo); + *pu32Value = (uint32_t)pThis->dcbaap; + return VINF_SUCCESS; +} + +/** + * Write the low dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapLo); + /* NB: A dword write to the low half clears the high half. */ + /// @todo Should this mask off the reserved bits? + pThis->dcbaap = val; + return VINF_SUCCESS; +} + +/** + * Read the high dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapHi); + *pu32Value = pThis->dcbaap >> 32; + return VINF_SUCCESS; +} + +/** + * Write the high dword of the DCBAAP register. + */ +static VBOXSTRICTRC HcDevCtxBAAPHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapHi); + pThis->dcbaap = ((uint64_t)val << 32) | (uint32_t)pThis->dcbaap; + return VINF_SUCCESS; +} + +/** + * Read the CONFIG register. + */ +static VBOXSTRICTRC HcConfig_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatRdConfig); + *pu32Value = pThis->config; + return VINF_SUCCESS; +} + +/** + * Write the CONFIG register. + */ +static VBOXSTRICTRC HcConfig_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF(pDevIns, iReg); + STAM_COUNTER_INC(&pThis->StatWrConfig); + /// @todo side effects? + pThis->config = val; + return VINF_SUCCESS; +} + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Port Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the PORTSC register. + */ +static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortStatusCtrl); + + Assert(!(pThis->hcc_params & XHCI_HCC_PPC)); + + if (p->portsc & XHCI_PORT_PR) + { +/// @todo Probably not needed? +#ifdef IN_RING3 + Log2(("HcPortStatusCtrl_r(): port %u: Impatient guest!\n", IDX_TO_ID(iPort))); + RTThreadYield(); +#else + Log2(("HcPortStatusCtrl_r: yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + + /* The WPR bit is always read as zero. */ + *pu32Value = p->portsc & ~XHCI_PORT_WPR; + return VINF_SUCCESS; +} + +/** + * Write the PORTSC register. + */ +static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; +#ifdef IN_RING3 + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); +#endif + STAM_COUNTER_INC(&pThis->StatWrPortStatusCtrl); + + /* If no register change results, we're done. */ + if ( p->portsc == val + && !(val & XHCI_PORT_CHANGE_MASK)) + return VINF_SUCCESS; + + /* If port state is not changing (status bits are being cleared etc.), we can do it in any context. + * This case occurs when the R/W control bits are not changing and the W1C bits are not being set. + */ + if ( (p->portsc & XHCI_PORT_CTL_RW_MASK) == (val & XHCI_PORT_CTL_RW_MASK) + && !(val & XHCI_PORT_CTL_W1_MASK)) + { + Log(("HcPortStatusCtrl_w port %u (status only): old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val)); + + if (val & XHCI_PORT_RESERVED) + Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED)); + + /* A write to clear any of the change notification bits. */ + if (val & XHCI_PORT_CHANGE_MASK) + p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK); + + /* Update the wake mask. */ + p->portsc &= ~XHCI_PORT_WAKE_MASK; + p->portsc |= val & XHCI_PORT_WAKE_MASK; + + /* There may still be differences between 'portsc' and 'val' in + * the R/O bits; that does not count as a register change and is fine. + * The RW1x control bits are not considered either since those only matter + * if set in 'val'. Since the LWS bit was not set, the PLS bits should not + * be compared. The port change bits may differ as well since the guest + * could be clearing only some or none of them. + */ + AssertMsg(!(val & XHCI_PORT_CTL_W1_MASK), ("val=%X\n", val)); + AssertMsg(!(val & XHCI_PORT_LWS), ("val=%X\n", val)); + AssertMsg((val & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)) == (p->portsc & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)), ("val=%X vs. portsc=%X\n", val, p->portsc)); + return VINF_SUCCESS; + } + + /* Actual USB port state changes need to be done in R3. */ +#ifdef IN_RING3 + Log(("HcPortStatusCtrl_w port %u: old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val)); + Assert(!(pThis->hcc_params & XHCI_HCC_PPC)); + Assert(p->portsc & XHCI_PORT_PP); + + if (val & XHCI_PORT_RESERVED) + Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED)); + + /* A write to clear any of the change notification bits. */ + if (val & XHCI_PORT_CHANGE_MASK) + p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK); + + /* Writing the Port Enable/Disable bit as 1 disables a port; it cannot be + * enabled that way. Writing the bit as zero does does nothing. + */ + if ((val & XHCI_PORT_PED) && (p->portsc & XHCI_PORT_PED)) + { + p->portsc &= ~XHCI_PORT_PED; + Log(("HcPortStatusCtrl_w(): port %u: DISABLE\n", IDX_TO_ID(iPort))); + } + + if (!(val & XHCI_PORT_PP) && (p->portsc & XHCI_PORT_PP)) + { + p->portsc &= ~XHCI_PORT_PP; + Log(("HcPortStatusCtrl_w(): port %u: POWER OFF\n", IDX_TO_ID(iPort))); + } + + /* Warm Port Reset - USB3 only; see 4.19.5.1. */ + if ((val & XHCI_PORT_WPR) && IS_USB3_PORT_IDX_SHR(pThis, iPort)) + { + Log(("HcPortStatusCtrl_w(): port %u: WARM RESET\n", IDX_TO_ID(iPort))); + if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR | XHCI_PORT_WPR)) + { + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + xhciR3PortResetDone(pDevIns, iPort); + } + } + + if (val & XHCI_PORT_PR) + { + Log(("HcPortStatusCtrl_w(): port %u: RESET\n", IDX_TO_ID(iPort))); + if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR)) + { + PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort); + + VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns)); + xhciR3PortResetDone(pDevIns, iPort); + } + else if (p->portsc & XHCI_PORT_PR) + { + /* the guest is getting impatient. */ + Log2(("HcPortStatusCtrl_w(): port %u: Impatient guest!\n", IDX_TO_ID(iPort))); + RTThreadYield(); + } + } + + /// @todo Do some sanity checking on the new link state? + /* Update the link state if requested. */ + if (val & XHCI_PORT_LWS) + { + unsigned old_pls; + unsigned new_pls; + + old_pls = (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + new_pls = (val & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT; + + p->portsc &= ~XHCI_PORT_PLS_MASK; + p->portsc |= new_pls << XHCI_PORT_PLS_SHIFT; + Log2(("HcPortStatusCtrl_w(): port %u: Updating link state from %u to %u\n", IDX_TO_ID(iPort), old_pls, new_pls)); + /* U3->U0 (USB3) and Resume->U0 transitions set the PLC flag. See 4.15.2.2 */ + if (new_pls == XHCI_PLS_U0) + if (old_pls == XHCI_PLS_U3 || old_pls == XHCI_PLS_RESUME) + { + p->portsc |= XHCI_PORT_PLC; + xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort)); + } + } + + /// @todo which other bits can we safely ignore? + + /* Update the wake mask. */ + p->portsc &= ~XHCI_PORT_WAKE_MASK; + p->portsc |= val & XHCI_PORT_WAKE_MASK; + + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF(pDevIns); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + + +/** + * Read the PORTPMSC register. + */ +static VBOXSTRICTRC HcPortPowerMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortPowerMgmt); + + *pu32Value = p->portpm; + return VINF_SUCCESS; +} + + +/** + * Write the PORTPMSC register. + */ +static VBOXSTRICTRC HcPortPowerMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatWrPortPowerMgmt); + + /// @todo anything to do here? + p->portpm = val; + return VINF_SUCCESS; +} + + +/** + * Read the PORTLI register. + */ +static VBOXSTRICTRC HcPortLinkInfo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + PXHCIHUBPORT p = &pThis->aPorts[iPort]; + RT_NOREF(pDevIns); + STAM_COUNTER_INC(&pThis->StatRdPortLinkInfo); + + /* The link information is R/O; we probably can't get it at all. If we + * do maintain it for USB3 ports, we also have to reset it (5.4.10). + */ + *pu32Value = p->portli; + return VINF_SUCCESS; +} + +/** + * Read the reserved register. Linux likes to do this. + */ +static VBOXSTRICTRC HcPortRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, iPort); + STAM_COUNTER_INC(&pThis->StatRdPortRsvd); + *pu32Value = 0; + return VINF_SUCCESS; +} + + + +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ +/* xHCI Interrupter Register access routines */ +/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */ + + + +/** + * Read the IMAN register. + */ +static VBOXSTRICTRC HcIntrMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdIntrMgmt); + + *pu32Value = ip->iman; + return VINF_SUCCESS; +} + +/** + * Write the IMAN register. + */ +static VBOXSTRICTRC HcIntrMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + uint32_t uNew = val & XHCI_IMAN_VALID_MASK; + STAM_COUNTER_INC(&pThis->StatWrIntrMgmt); + + if (val & ~XHCI_IMAN_VALID_MASK) + Log(("Reserved bits set %x!\n", val & ~XHCI_IMAN_VALID_MASK)); + + /* If the Interrupt Pending (IP) bit is set, writing one clears it. + * Note that when MSIs are enabled, the bit auto-clears almost immediately. + */ + if (val & ip->iman & XHCI_IMAN_IP) + { + Log2(("clearing interrupt on interrupter %u\n", ip->index)); + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW); + STAM_COUNTER_INC(&pThis->StatIntrsCleared); + uNew &= ~XHCI_IMAN_IP; + } + else + { + /* Preserve the current IP bit. */ + uNew = (uNew & ~XHCI_IMAN_IP) | (ip->iman & XHCI_IMAN_IP); + } + + /* Trigger an interrupt if the IP bit is set and IE transitions from 0 to 1. */ + if ( (uNew & XHCI_IMAN_IE) + && !(ip->iman & XHCI_IMAN_IE) + && (ip->iman & XHCI_IMAN_IP) + && (pThis->cmd & XHCI_CMD_INTE)) + PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH); + + ip->iman = uNew; + return VINF_SUCCESS; +} + +/** + * Read the IMOD register. + */ +static VBOXSTRICTRC HcIntrMod_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdIntrMod); + + *pu32Value = ip->imod; + return VINF_SUCCESS; +} + +/** + * Write the IMOD register. + */ +static VBOXSTRICTRC HcIntrMod_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatWrIntrMod); + + /// @todo Does writing a zero to IMODC/IMODI potentially trigger + /// an interrupt? + ip->imod = val; + return VINF_SUCCESS; +} + +/** + * Read the ERSTSZ register. + */ +static VBOXSTRICTRC HcEvtRSTblSize_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRstblSize); + + *pu32Value = ip->erstsz; + return VINF_SUCCESS; +} + +/** + * Write the ERSTSZ register. + */ +static VBOXSTRICTRC HcEvtRSTblSize_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatWrEvtRstblSize); + + if (val & ~XHCI_ERSTSZ_MASK) + Log(("Reserved bits set %x!\n", val & ~XHCI_ERSTSZ_MASK)); + if (val > XHCI_ERSTMAX) + Log(("ERSTSZ (%u) > ERSTMAX (%u)!\n", val, XHCI_ERSTMAX)); + + /* Enforce the maximum size. */ + ip->erstsz = RT_MIN(val, XHCI_ERSTMAX); + + if (!ip->index && !ip->erstsz) /* Windows 8 does this temporarily. Thanks guys... */ + Log(("ERSTSZ is zero for primary interrupter: undefined behavior!\n")); + + return VINF_SUCCESS; +} + +/** + * Read the reserved register. Linux likes to do this. + */ +static VBOXSTRICTRC HcEvtRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis, ip); + STAM_COUNTER_INC(&pThis->StatRdEvtRsvd); + *pu32Value = 0; + return VINF_SUCCESS; +} + +/** + * Read the low dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseLo); + + *pu32Value = (uint32_t)ip->erstba; + return VINF_SUCCESS; +} + + +/** + * Write the low dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseLo); + + if (val & ~pThis->erst_addr_mask) + Log(("Reserved bits set %x!\n", val & ~pThis->erst_addr_mask)); + + /* NB: A dword write to the low half clears the high half. */ + ip->erstba = val & pThis->erst_addr_mask; + + /* Initialize the internal event ring state. */ + ip->evtr_pcs = 1; + ip->erst_idx = 0; + ip->ipe = false; + + /* Fetch the first ERST entry now. Not later! That "sets the Event Ring + * State Machine:EREP Advancement to the Start state" + */ + xhciFetchErstEntry(pDevIns, pThis, ip); + + return VINF_SUCCESS; +} + +/** + * Read the high dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseHi); + + *pu32Value = (uint32_t)(ip->erstba >> 32); + return VINF_SUCCESS; +} + +/** + * Write the high dword of the ERSTBA register. + */ +static VBOXSTRICTRC HcEvtRSTblBaseHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseHi); + + /* Update the high dword while preserving the low one. */ + ip->erstba = ((uint64_t)val << 32) | (uint32_t)ip->erstba; + + /* We shouldn't be doing this when AC64 is set. But High Sierra + * ignores that because it "knows" the xHC handles 64-bit addressing, + * so we're going to assume that OSes are not going to write junk into + * ERSTBAH when they don't see AC64 set. + */ + xhciFetchErstEntry(pDevIns, pThis, ip); + + return VINF_SUCCESS; +} + + +/** + * Read the low dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrLo); + + /* Lock to avoid incomplete update being seen. */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_READ); + if (rc != VINF_SUCCESS) + return rc; + + *pu32Value = (uint32_t)ip->erdp; + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + +/** + * Write the low dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + uint64_t old_erdp; + uint64_t new_erdp; + STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrLo); + + /* NB: A dword write to the low half clears the high half. + * The high dword should be ignored when AC64=0, but High Sierra + * does not care what we report. Therefore a write to the low dword + * handles all the control bits and a write to the high dword still + * updates the ERDP address. On a 64-bit host, there must be a + * back-to-back low dword + high dword access. We are going to boldly + * assume that the guest will not place the event ring across the 4G + * boundary (i.e. storing the bottom part in the firmware ROM). + */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + old_erdp = ip->erdp & XHCI_ERDP_ADDR_MASK; /* Remember old ERDP address. */ + new_erdp = ip->erdp & XHCI_ERDP_EHB; /* Preserve EHB */ + + /* If the Event Handler Busy (EHB) bit is set, writing a one clears it. */ + if (val & ip->erdp & XHCI_ERDP_EHB) + { + Log2(("clearing EHB on interrupter %p\n", ip)); + new_erdp &= ~XHCI_ERDP_EHB; + } + /// @todo Check if this might inadvertently set EHB! + + new_erdp |= val & ~XHCI_ERDP_EHB; + ip->erdp = new_erdp; + + /* Check if the ERDP changed. See workaround below. */ + if (old_erdp != (new_erdp & XHCI_ERDP_ADDR_MASK)) + ip->erdp_rewrites = 0; + else + ++ip->erdp_rewrites; + + LogFlowFunc(("ERDP: %RGp, EREP: %RGp\n", (RTGCPHYS)(ip->erdp & XHCI_ERDP_ADDR_MASK), (RTGCPHYS)ip->erep)); + + if ((ip->erdp & XHCI_ERDP_ADDR_MASK) == ip->erep) + { + Log2(("Event Ring empty, clearing IPE\n")); + ip->ipe = false; + } + else if (ip->ipe && (val & XHCI_ERDP_EHB)) + { + /* EHB is being cleared but the ring isn't empty and IPE is still set. */ + if (RT_UNLIKELY(old_erdp == (new_erdp & XHCI_ERDP_ADDR_MASK) && ip->erdp_rewrites > 2)) + { + /* If guest does not advance the ERDP, do not trigger an interrupt + * again. Workaround for buggy xHCI initialization in Linux 4.6 which + * enables interrupts before setting up internal driver state. That + * leads to the guest IRQ handler not actually handling events and + * infinitely re-triggering interrupts. However, only do this if the + * guest has already written the same ERDP value a few times. The Intel + * xHCI driver always writes the same ERDP twice and we must still + * re-trigger interrupts in that case. + * See @bugref{8546}. + */ + Log2(("Event Ring not empty, ERDP not advanced, not re-triggering interrupt!\n")); + ip->ipe = false; + } + else + { + Log2(("Event Ring not empty, re-triggering interrupt\n")); + xhciSetIntr(pDevIns, pThis, ip); + } + } + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + +/** + * Read the high dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value) +{ + RT_NOREF(pDevIns, pThis); + STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrHi); + + *pu32Value = (uint32_t)(ip->erdp >> 32); + return VINF_SUCCESS; +} + +/** + * Write the high dword of the ERDP register. + */ +static VBOXSTRICTRC HcEvtRingDeqPtrHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val) +{ + RT_NOREF(pThis); + STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrHi); + + /* See HcEvtRingDeqPtrLo_w for semantics. */ + int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + /* Update the high dword while preserving the low one. */ + ip->erdp = ((uint64_t)val << 32) | (uint32_t)ip->erdp; + + PDMDevHlpCritSectLeave(pDevIns, &ip->lock); + + return VINF_SUCCESS; +} + + +/** + * xHCI register access routines. + */ +typedef struct +{ + const char *pszName; + VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t u32Value); +} XHCIREGACC; + +/** + * xHCI interrupter register access routines. + */ +typedef struct +{ + const char *pszName; + VBOXSTRICTRC (*pfnIntrRead )(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t *pu32Value); + VBOXSTRICTRC (*pfnIntrWrite)(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t u32Value); +} XHCIINTRREGACC; + +/** + * Operational registers descriptor table. + */ +static const XHCIREGACC g_aOpRegs[] = +{ + {"USBCMD" , HcUsbcmd_r, HcUsbcmd_w }, + {"USBSTS", HcUsbsts_r, HcUsbsts_w }, + {"PAGESIZE", HcPagesize_r, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"DNCTRL", HcDevNotifyCtrl_r, HcDevNotifyCtrl_w }, + {"CRCRL", HcCmdRingCtlLo_r, HcCmdRingCtlLo_w }, + {"CRCRH", HcCmdRingCtlHi_r, HcCmdRingCtlHi_w }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"Unused", NULL, NULL }, + {"DCBAAPL", HcDevCtxBAAPLo_r, HcDevCtxBAAPLo_w }, + {"DCBAAPH", HcDevCtxBAAPHi_r, HcDevCtxBAAPHi_w }, + {"CONFIG", HcConfig_r, HcConfig_w } +}; + + +/** + * Port registers descriptor table (for a single port). The number of ports + * and their associated registers depends on the NDP value. + */ +static const XHCIREGACC g_aPortRegs[] = +{ + /* + */ + {"PORTSC", HcPortStatusCtrl_r, HcPortStatusCtrl_w }, + {"PORTPMSC", HcPortPowerMgmt_r, HcPortPowerMgmt_w }, + {"PORTLI", HcPortLinkInfo_r, NULL }, + {"Reserved", HcPortRsvd_r, NULL } +}; +AssertCompile(RT_ELEMENTS(g_aPortRegs) * sizeof(uint32_t) == 0x10); + + +/** + * Interrupter runtime registers descriptor table (for a single interrupter). + * The number of interrupters depends on the XHCI_NINTR value. + */ +static const XHCIINTRREGACC g_aIntrRegs[] = +{ + {"IMAN", HcIntrMgmt_r, HcIntrMgmt_w }, + {"IMOD", HcIntrMod_r, HcIntrMod_w }, + {"ERSTSZ", HcEvtRSTblSize_r, HcEvtRSTblSize_w }, + {"Reserved", HcEvtRsvd_r, NULL }, + {"ERSTBAL", HcEvtRSTblBaseLo_r, HcEvtRSTblBaseLo_w }, + {"ERSTBAH", HcEvtRSTblBaseHi_r, HcEvtRSTblBaseHi_w }, + {"ERDPL", HcEvtRingDeqPtrLo_r, HcEvtRingDeqPtrLo_w }, + {"ERDPH", HcEvtRingDeqPtrHi_r, HcEvtRingDeqPtrHi_w } +}; +AssertCompile(RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t) == 0x20); + + +/** + * Read the MFINDEX register. + */ +static int HcMfIndex_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t *pu32Value) +{ + uint64_t uNanoTime; + uint64_t uMfTime; + STAM_COUNTER_INC(&pThis->StatRdMfIndex); + + /* MFINDEX increments once per micro-frame, i.e. 8 times per millisecond + * or every 125us. The resolution is only 14 bits, meaning that MFINDEX + * wraps around after it reaches 0x3FFF (16383) or every 2048 milliseconds. + */ + /// @todo MFINDEX should only be running when R/S is set. May not matter. + uNanoTime = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer); + uMfTime = uNanoTime / 125000; + + *pu32Value = uMfTime & 0x3FFF; + Log2(("MFINDEX read: %u\n", *pu32Value)); + return VINF_SUCCESS; +} + +/** + * @callback_method_impl{FNIOMMMIONEWREAD, Read a MMIO register.} + * + * @note We only accept 32-bit writes that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) xhciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + const uint32_t offReg = (uint32_t)off; + uint32_t * const pu32 = (uint32_t *)pv; + uint32_t iReg; + RT_NOREF(pvUser); + + Log2(("xhciRead %RGp (offset %04X) size=%d\n", off, offReg, cb)); + + if (offReg < XHCI_CAPS_REG_SIZE) + { + switch (offReg) + { + case 0x0: /* CAPLENGTH + HCIVERSION */ + *pu32 = (pThis->hci_version << 16) | pThis->cap_length; + break; + + case 0x4: /* HCSPARAMS1 (structural) */ + Log2(("HCSPARAMS1 read\n")); + *pu32 = pThis->hcs_params1; + break; + + case 0x8: /* HCSPARAMS2 (structural) */ + Log2(("HCSPARAMS2 read\n")); + *pu32 = pThis->hcs_params2; + break; + + case 0xC: /* HCSPARAMS3 (structural) */ + Log2(("HCSPARAMS3 read\n")); + *pu32 = pThis->hcs_params3; + break; + + case 0x10: /* HCCPARAMS1 (caps) */ + Log2(("HCCPARAMS1 read\n")); + *pu32 = pThis->hcc_params; + break; + + case 0x14: /* DBOFF (doorbell offset) */ + Log2(("DBOFF read\n")); + *pu32 = pThis->dbell_off; + break; + + case 0x18: /* RTSOFF (run-time register offset) */ + Log2(("RTSOFF read\n")); + *pu32 = pThis->rts_off; + break; + + case 0x1C: /* HCCPARAMS2 (caps) */ + Log2(("HCCPARAMS2 read\n")); + *pu32 = 0; /* xHCI 1.1 only */ + break; + + default: + Log(("xHCI: Trying to read unknown capability register %u!\n", offReg)); + STAM_COUNTER_INC(&pThis->StatRdUnknown); + return VINF_IOM_MMIO_UNUSED_FF; + } + STAM_COUNTER_INC(&pThis->StatRdCaps); + Log2(("xhciRead %RGp size=%d -> val=%x\n", off, cb, *pu32)); + return VINF_SUCCESS; + } + + /* + * Validate the access (in case of IOM bugs or incorrect MMIO registration). + */ + AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb), + VINF_IOM_MMIO_UNUSED_FF /* No idea what really would happen... */); + /** r=bird: If you don't have an idea what would happen for non-dword reads, + * then the flags passed to IOM when creating the MMIO region are doubtful, right? */ + AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_IOM_MMIO_UNUSED_FF); + + /* + * Validate the register and call the read operator. + */ + VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF; + if (offReg >= XHCI_DOORBELL_OFFSET) + { + /* The doorbell registers are effectively write-only and return 0 when read. */ + iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2; + if (iReg < XHCI_NDS) + { + STAM_COUNTER_INC(&pThis->StatRdDoorBell); + *pu32 = 0; + rcStrict = VINF_SUCCESS; + Log2(("xhciRead: DBellReg (DB %u) %RGp size=%d -> val=%x (rc=%d)\n", iReg, off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (offReg >= XHCI_RTREG_OFFSET) + { + /* Run-time registers. */ + Assert(offReg < XHCI_DOORBELL_OFFSET); + /* The MFINDEX register would be interrupter -1... */ + if (offReg < XHCI_RTREG_OFFSET + RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) + { + if (offReg == XHCI_RTREG_OFFSET) + rcStrict = HcMfIndex_r(pDevIns, pThis, pu32); + else + { + /* The silly Linux xHCI driver reads the reserved registers. */ + STAM_COUNTER_INC(&pThis->StatRdUnknown); + *pu32 = 0; + rcStrict = VINF_SUCCESS; + } + } + else + { + Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0); + const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1; + + if (iIntr < XHCI_NINTR) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1); + const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg]; + if (pReg->pfnIntrRead) + { + PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr]; + rcStrict = pReg->pfnIntrRead(pDevIns, pThis, pIntr, pu32); + Log2(("xhciRead: IntrReg (intr %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + else if (offReg >= XHCI_XECP_OFFSET) + { + /* Extended Capability registers. */ + Assert(offReg < XHCI_RTREG_OFFSET); + uint32_t offXcp = offReg - XHCI_XECP_OFFSET; + + if (offXcp + cb <= RT_MIN(pThis->cbExtCap, sizeof(pThis->abExtCap))) /* can't trust cbExtCap in ring-0. */ + { + *pu32 = *(uint32_t *)&pThis->abExtCap[offXcp]; + rcStrict = VINF_SUCCESS; + } + Log2(("xhciRead: ExtCapReg %RGp size=%d -> val=%x (rc=%d)\n", off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + else + { + /* Operational registers (incl. port registers). */ + Assert(offReg < XHCI_XECP_OFFSET); + iReg = (offReg - XHCI_CAPS_REG_SIZE) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const XHCIREGACC *pReg = &g_aOpRegs[iReg]; + if (pReg->pfnRead) + { + rcStrict = pReg->pfnRead(pDevIns, pThis, iReg, pu32); + Log2(("xhciRead: OpReg %RGp (%s) size=%d -> val=%x (rc=%d)\n", off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2)) + { + iReg -= (XHCI_PORT_REG_OFFSET >> 2); + const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs); + if (iPort < XHCI_NDP_CFG(pThis)) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1); + Assert(iReg < RT_ELEMENTS(g_aPortRegs)); + const XHCIREGACC *pReg = &g_aPortRegs[iReg]; + if (pReg->pfnRead) + { + rcStrict = pReg->pfnRead(pDevIns, pThis, iPort, pu32); + Log2(("xhciRead: PortReg (port %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + + if (rcStrict != VINF_IOM_MMIO_UNUSED_FF) + { /* likely */ } + else + { + STAM_COUNTER_INC(&pThis->StatRdUnknown); + Log(("xHCI: Trying to read unimplemented register at offset %04X!\n", offReg)); + } + + return rcStrict; +} + + +/** + * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register.} + * + * @note We only accept 32-bit writes that are 32-bit aligned. + */ +static DECLCALLBACK(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + const uint32_t offReg = (uint32_t)off; + uint32_t * const pu32 = (uint32_t *)pv; + uint32_t iReg; + RT_NOREF(pvUser); + + Log2(("xhciWrite %RGp (offset %04X) %x size=%d\n", off, offReg, *(uint32_t *)pv, cb)); + + if (offReg < XHCI_CAPS_REG_SIZE) + { + /* These are read-only */ + Log(("xHCI: Trying to write to register %u!\n", offReg)); + STAM_COUNTER_INC(&pThis->StatWrUnknown); + return VINF_SUCCESS; + } + + /* + * Validate the access (in case of IOM bug or incorrect MMIO registration). + */ + AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS); + AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS); + + /* + * Validate the register and call the write operator. + */ + VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF; + if (offReg >= XHCI_DOORBELL_OFFSET) + { + /* Let's spring into action... as long as the xHC is running. */ + iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2; + if ((pThis->cmd & XHCI_CMD_RS) && iReg < XHCI_NDS) + { + if (iReg == 0) + { + /* DB0 aka Command Ring. */ + STAM_COUNTER_INC(&pThis->StatWrDoorBell0); + if (*pu32 == 0) + { + /* Set the Command Ring state to Running if not already set. */ + if (!(pThis->crcr & XHCI_CRCR_CRR)) + { + Log(("Command ring entered Running state\n")); + ASMAtomicOrU64(&pThis->crcr, XHCI_CRCR_CRR); + } + xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0); + } + else + Log2(("Ignoring DB0 write with value %X!\n", *pu32)); + } + else + { + /* Device context doorbell. Do basic parameter checking to avoid + * waking up the worker thread needlessly. + */ + STAM_COUNTER_INC(&pThis->StatWrDoorBellN); + uint8_t uDBTarget = *pu32 & XHCI_DB_TGT_MASK; + Assert(uDBTarget < 32); /// @todo Report an error? Or just ignore? + if (uDBTarget < 32) + { + Log2(("Ring bell for slot %u, DCI %u\n", iReg, uDBTarget)); + ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(iReg)], 1 << uDBTarget); + xhciKickWorker(pDevIns, pThis, XHCI_JOB_DOORBELL, *pu32); + } + else + Log2(("Ignoring DB%u write with bad target %u!\n", iReg, uDBTarget)); + } + rcStrict = VINF_SUCCESS; + Log2(("xhciWrite: DBellReg (DB %u) %RGp size=%d <- val=%x (rc=%d)\n", iReg, off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (offReg >= XHCI_RTREG_OFFSET) + { + /* Run-time registers. */ + Assert(offReg < XHCI_DOORBELL_OFFSET); + /* NB: The MFINDEX register is R/O. */ + if (offReg >= XHCI_RTREG_OFFSET + (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t))) + { + Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0); + const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1; + + if (iIntr < XHCI_NINTR) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1); + const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg]; + if (pReg->pfnIntrWrite) + { + PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr]; + rcStrict = pReg->pfnIntrWrite(pDevIns, pThis, pIntr, *pu32); + Log2(("xhciWrite: IntrReg (intr %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + else + { + /* Operational registers (incl. port registers). */ + Assert(offReg < XHCI_RTREG_OFFSET); + iReg = (offReg - pThis->cap_length) >> 2; + if (iReg < RT_ELEMENTS(g_aOpRegs)) + { + const XHCIREGACC *pReg = &g_aOpRegs[iReg]; + if (pReg->pfnWrite) + { + rcStrict = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv); + Log2(("xhciWrite: OpReg %RGp (%s) size=%d <- val=%x (rc=%d)\n", off, pReg->pszName, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict))); + } + } + else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2)) + { + iReg -= (XHCI_PORT_REG_OFFSET >> 2); + const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs); + if (iPort < XHCI_NDP_CFG(pThis)) + { + iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1); + Assert(iReg < RT_ELEMENTS(g_aPortRegs)); + const XHCIREGACC *pReg = &g_aPortRegs[iReg]; + if (pReg->pfnWrite) + { + rcStrict = pReg->pfnWrite(pDevIns, pThis, iPort, *pu32); + Log2(("xhciWrite: PortReg (port %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict))); + } + } + } + } + + if (rcStrict != VINF_IOM_MMIO_UNUSED_FF) + { /* likely */ } + else + { + /* Ignore writes to unimplemented or read-only registers. */ + STAM_COUNTER_INC(&pThis->StatWrUnknown); + Log(("xHCI: Trying to write unimplemented or R/O register at offset %04X!\n", offReg)); + rcStrict = VINF_SUCCESS; + } + + return rcStrict; +} + + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNTMTIMERDEV, + * Provides periodic MFINDEX wrap events. See 4.14.2.} + */ +static DECLCALLBACK(void) xhciR3WrapTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PXHCI pThis = (PXHCI)pvUser; + XHCI_EVENT_TRB ed; + LogFlow(("xhciR3WrapTimer:\n")); + RT_NOREF(hTimer); + + /* + * Post the MFINDEX Wrap event and rearm the timer. Only called + * when the EWE bit is set in command register. + */ + RT_ZERO(ed); + ed.mwe.cc = XHCI_TCC_SUCCESS; + ed.mwe.type = XHCI_TRB_MFIDX_WRAP; + xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false); + + xhciSetWrapTimer(pDevIns, pThis); +} + + +/** + * @callback_method_impl{FNSSMDEVSAVEEXEC} + */ +static DECLCALLBACK(int) xhciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t iPort; + uint32_t iSlot; + uint32_t iIntr; + + LogFlow(("xhciR3SaveExec: \n")); + + /* Save HC operational registers. */ + pHlp->pfnSSMPutU32(pSSM, pThis->cmd); + pHlp->pfnSSMPutU32(pSSM, pThis->status); + pHlp->pfnSSMPutU32(pSSM, pThis->dnctrl); + pHlp->pfnSSMPutU64(pSSM, pThis->crcr); + pHlp->pfnSSMPutU64(pSSM, pThis->dcbaap); + pHlp->pfnSSMPutU32(pSSM, pThis->config); + + /* Save HC non-register state. */ + pHlp->pfnSSMPutU64(pSSM, pThis->cmdr_dqp); + pHlp->pfnSSMPutBool(pSSM, pThis->cmdr_ccs); + + /* Save per-slot state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NDS); + for (iSlot = 0; iSlot < XHCI_NDS; ++iSlot) + { + pHlp->pfnSSMPutU8 (pSSM, pThis->aSlotState[iSlot]); + pHlp->pfnSSMPutU32(pSSM, pThis->aBellsRung[iSlot]); + } + + /* Save root hub (port) state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NDP_CFG(pThis)); + for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort) + { + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portsc); + pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portpm); + } + + /* Save interrupter state. */ + pHlp->pfnSSMPutU32(pSSM, XHCI_NINTR); + for (iIntr = 0; iIntr < XHCI_NINTR; ++iIntr) + { + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].iman); + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].imod); + pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].erstsz); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erstba); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erdp); + pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erep); + pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].erst_idx); + pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].trb_count); + pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].evtr_pcs); + pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].ipe); + } + + /* Terminator marker. */ + pHlp->pfnSSMPutU32(pSSM, UINT32_MAX); + + /* If not continuing after save, force HC into non-running state to avoid trouble later. */ + if (pHlp->pfnSSMHandleGetAfter(pSSM) != SSMAFTER_CONTINUE) + pThis->cmd &= ~XHCI_CMD_RS; + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDEVLOADEXEC} + */ +static DECLCALLBACK(int) xhciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + int rc; + uint32_t cPorts; + uint32_t iPort; + uint32_t cSlots; + uint32_t iSlot; + uint32_t cIntrs; + uint32_t iIntr; + uint64_t u64Dummy; + uint32_t u32Dummy; + uint16_t u16Dummy; + uint8_t u8Dummy; + bool fDummy; + + LogFlow(("xhciR3LoadExec:\n")); + + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + if (uVersion != XHCI_SAVED_STATE_VERSION) + return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION; + + /* Load HC operational registers. */ + pHlp->pfnSSMGetU32(pSSM, &pThis->cmd); + pHlp->pfnSSMGetU32(pSSM, &pThis->status); + pHlp->pfnSSMGetU32(pSSM, &pThis->dnctrl); + pHlp->pfnSSMGetU64(pSSM, &pThis->crcr); + pHlp->pfnSSMGetU64(pSSM, &pThis->dcbaap); + pHlp->pfnSSMGetU32(pSSM, &pThis->config); + + /* Load HC non-register state. */ + pHlp->pfnSSMGetU64(pSSM, &pThis->cmdr_dqp); + pHlp->pfnSSMGetBool(pSSM, &pThis->cmdr_ccs); + + /* Load per-slot state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cSlots); + AssertRCReturn(rc, rc); + if (cSlots > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + for (iSlot = 0; iSlot < cSlots; ++iSlot) + { + /* Load only as many slots as we have; discard any extras. */ + if (iSlot < XHCI_NDS) + { + pHlp->pfnSSMGetU8 (pSSM, &pThis->aSlotState[iSlot]); + pHlp->pfnSSMGetU32(pSSM, &pThis->aBellsRung[iSlot]); + } + else + { + pHlp->pfnSSMGetU8 (pSSM, &u8Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + } + } + + /* Load root hub (port) state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cPorts); + AssertRCReturn(rc, rc); + if (cPorts > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + + for (iPort = 0; iPort < cPorts; ++iPort) + { + /* Load only as many ports as we have; discard any extras. */ + if (iPort < XHCI_NDP_CFG(pThis)) + { + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portsc); + pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portpm); + } + else + { + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + } + } + + /* Load interrupter state. */ + rc = pHlp->pfnSSMGetU32(pSSM, &cIntrs); + AssertRCReturn(rc, rc); + if (cIntrs > 256) /* Sanity check. */ + return VERR_SSM_INVALID_STATE; + for (iIntr = 0; iIntr < cIntrs; ++iIntr) + { + /* Load only as many interrupters as we have; discard any extras. */ + if (iIntr < XHCI_NINTR) + { + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].iman); + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].imod); + pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].erstsz); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erstba); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erdp); + pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erep); + pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].erst_idx); + pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].trb_count); + pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].evtr_pcs); + pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].ipe); + } + else + { + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU64(pSSM, &u64Dummy); + pHlp->pfnSSMGetU16(pSSM, &u16Dummy); + pHlp->pfnSSMGetU16(pSSM, &u16Dummy); + pHlp->pfnSSMGetBool(pSSM, &fDummy); + pHlp->pfnSSMGetBool(pSSM, &fDummy); + } + } + + /* Terminator marker. */ + rc = pHlp->pfnSSMGetU32(pSSM, &u32Dummy); + AssertRCReturn(rc, rc); + AssertReturn(u32Dummy == UINT32_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED); + + return rc; +} + + +/* -=-=-=-=- DBGF -=-=-=-=- */ + +/** + * @callback_method_impl{FNDBGFHANDLERDEV, Dumps xHCI state.} + */ +static DECLCALLBACK(void) xhciR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + RTGCPHYS GPAddr; + bool fVerbose = false; + unsigned i, j; + uint64_t u64Val; + + /* Parse arguments. */ + if (pszArgs) + fVerbose = strstr(pszArgs, "verbose") != NULL; + +#ifdef XHCI_ERROR_INJECTION + if (pszArgs && strstr(pszArgs, "dropintrhw")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (external)!\n"); + pThis->fDropIntrHw = true; + return; + } + + if (pszArgs && strstr(pszArgs, "dropintrint")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (internal)!\n"); + pThis->fDropIntrIpe = true; + return; + } + + if (pszArgs && strstr(pszArgs, "dropurb")) + { + pHlp->pfnPrintf(pHlp, "Dropping the next URB!\n"); + pThis->fDropUrb = true; + return; + } +#endif + + /* Show basic information. */ + pHlp->pfnPrintf(pHlp, + "%s#%d: PCI MMIO=%RGp IRQ=%u MSI=%s R0=%RTbool RC=%RTbool\n", + pDevIns->pReg->szName, + pDevIns->iInstance, + PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio), + PCIDevGetInterruptLine(pDevIns->apPciDevs[0]), +#ifdef VBOX_WITH_MSI_DEVICES + xhciIsMSIEnabled(pDevIns->apPciDevs[0]) ? "on" : "off", +#else + "none", +#endif + pDevIns->fR0Enabled, pDevIns->fRCEnabled); + + /* Command register. */ + pHlp->pfnPrintf(pHlp, "USBCMD: %X:", pThis->cmd); + if (pThis->cmd & XHCI_CMD_EU3S) pHlp->pfnPrintf(pHlp, " EU3S" ); + if (pThis->cmd & XHCI_CMD_EWE) pHlp->pfnPrintf(pHlp, " EWE" ); + if (pThis->cmd & XHCI_CMD_CRS) pHlp->pfnPrintf(pHlp, " CRS" ); + if (pThis->cmd & XHCI_CMD_CSS) pHlp->pfnPrintf(pHlp, " CSS" ); + if (pThis->cmd & XHCI_CMD_LCRST) pHlp->pfnPrintf(pHlp, " LCRST" ); + if (pThis->cmd & XHCI_CMD_HSEE) pHlp->pfnPrintf(pHlp, " HSEE" ); + if (pThis->cmd & XHCI_CMD_INTE) pHlp->pfnPrintf(pHlp, " INTE" ); + if (pThis->cmd & XHCI_CMD_HCRST) pHlp->pfnPrintf(pHlp, " HCRST" ); + if (pThis->cmd & XHCI_CMD_RS) pHlp->pfnPrintf(pHlp, " RS" ); + pHlp->pfnPrintf(pHlp, "\n"); + + /* Status register. */ + pHlp->pfnPrintf(pHlp, "USBSTS: %X:", pThis->status); + if (pThis->status & XHCI_STATUS_HCH) pHlp->pfnPrintf(pHlp, " HCH" ); + if (pThis->status & XHCI_STATUS_HSE) pHlp->pfnPrintf(pHlp, " HSE" ); + if (pThis->status & XHCI_STATUS_EINT) pHlp->pfnPrintf(pHlp, " EINT" ); + if (pThis->status & XHCI_STATUS_PCD) pHlp->pfnPrintf(pHlp, " PCD" ); + if (pThis->status & XHCI_STATUS_SSS) pHlp->pfnPrintf(pHlp, " SSS" ); + if (pThis->status & XHCI_STATUS_RSS) pHlp->pfnPrintf(pHlp, " RSS" ); + if (pThis->status & XHCI_STATUS_SRE) pHlp->pfnPrintf(pHlp, " SRE" ); + if (pThis->status & XHCI_STATUS_CNR) pHlp->pfnPrintf(pHlp, " CNR" ); + if (pThis->status & XHCI_STATUS_HCE) pHlp->pfnPrintf(pHlp, " HCE" ); + pHlp->pfnPrintf(pHlp, "\n"); + + /* Device Notification Control and Configure registers. */ + pHlp->pfnPrintf(pHlp, "DNCTRL: %X CONFIG: %X (%u slots)\n", pThis->dnctrl, pThis->config, pThis->config); + + /* Device Context Base Address Array. */ + GPAddr = pThis->dcbaap & XHCI_DCBAA_ADDR_MASK; + pHlp->pfnPrintf(pHlp, "DCBAA ptr: %RGp\n", GPAddr); + /* The DCBAA must be valid in 'run' state. */ + if (fVerbose && (pThis->cmd & XHCI_CMD_RS)) + { + PDMDevHlpPCIPhysRead(pDevIns, GPAddr, &u64Val, sizeof(u64Val)); + pHlp->pfnPrintf(pHlp, " Scratchpad buffer: %RX64\n", u64Val); + } + + /* Command Ring Control Register. */ + pHlp->pfnPrintf(pHlp, "CRCR: %X:", pThis->crcr & ~XHCI_CRCR_ADDR_MASK); + if (pThis->crcr & XHCI_CRCR_RCS) pHlp->pfnPrintf(pHlp, " RCS"); + if (pThis->crcr & XHCI_CRCR_CS) pHlp->pfnPrintf(pHlp, " CS" ); + if (pThis->crcr & XHCI_CRCR_CA) pHlp->pfnPrintf(pHlp, " CA" ); + if (pThis->crcr & XHCI_CRCR_CRR) pHlp->pfnPrintf(pHlp, " CRR"); + pHlp->pfnPrintf(pHlp, "\n"); + GPAddr = pThis->crcr & XHCI_CRCR_ADDR_MASK; + pHlp->pfnPrintf(pHlp, "CRCR ptr : %RGp\n", GPAddr); + + /* Interrupters. */ + if (fVerbose) + { + for (i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + if (pThis->aInterrupters[i].erstsz) + { + XHCIINTRPTR *ir = &pThis->aInterrupters[i]; + + pHlp->pfnPrintf(pHlp, "Interrupter %d (IPE=%u)\n", i, ir->ipe); + + /* The Interrupt Management Register. */ + pHlp->pfnPrintf(pHlp, " IMAN : %X:", ir->iman); + if (ir->iman & XHCI_IMAN_IP) pHlp->pfnPrintf(pHlp, " IP"); + if (ir->iman & XHCI_IMAN_IE) pHlp->pfnPrintf(pHlp, " IE"); + pHlp->pfnPrintf(pHlp, "\n"); + + /* The Interrupt Moderation Register. */ + pHlp->pfnPrintf(pHlp, " IMOD : %X:", ir->imod); + pHlp->pfnPrintf(pHlp, " IMODI=%u", ir->imod & XHCI_IMOD_IMODI_MASK); + pHlp->pfnPrintf(pHlp, " IMODC=%u", (ir->imod & XHCI_IMOD_IMODC_MASK) >> XHCI_IMOD_IMODC_SHIFT); + pHlp->pfnPrintf(pHlp, "\n"); + + pHlp->pfnPrintf(pHlp, " ERSTSZ: %X\n", ir->erstsz); + pHlp->pfnPrintf(pHlp, " ERSTBA: %RGp\n", (RTGCPHYS)ir->erstba); + + pHlp->pfnPrintf(pHlp, " ERDP : %RGp:", (RTGCPHYS)ir->erdp); + pHlp->pfnPrintf(pHlp, " EHB=%u", !!(ir->erdp & XHCI_ERDP_EHB)); + pHlp->pfnPrintf(pHlp, " DESI=%u", ir->erdp & XHCI_ERDP_DESI_MASK); + pHlp->pfnPrintf(pHlp, " ptr=%RGp", ir->erdp & XHCI_ERDP_ADDR_MASK); + pHlp->pfnPrintf(pHlp, "\n"); + + pHlp->pfnPrintf(pHlp, " EREP : %RGp", ir->erep); + pHlp->pfnPrintf(pHlp, " Free TRBs in seg=%u", ir->trb_count); + pHlp->pfnPrintf(pHlp, "\n"); + } + } + } + + /* Port control/status. */ + for (i = 0; i < XHCI_NDP_CFG(pThis); ++i) + { + PXHCIHUBPORT p = &pThis->aPorts[i]; + + pHlp->pfnPrintf(pHlp, "Port %02u (USB%c): ", IDX_TO_ID(i), IS_USB3_PORT_IDX_SHR(pThis, i) ? '3' : '2'); + + /* Port Status register. */ + pHlp->pfnPrintf(pHlp, "PORTSC: %8X:", p->portsc); + if (p->portsc & XHCI_PORT_CCS) pHlp->pfnPrintf(pHlp, " CCS" ); + if (p->portsc & XHCI_PORT_PED) pHlp->pfnPrintf(pHlp, " PED" ); + if (p->portsc & XHCI_PORT_OCA) pHlp->pfnPrintf(pHlp, " OCA" ); + if (p->portsc & XHCI_PORT_PR ) pHlp->pfnPrintf(pHlp, " PR" ); + pHlp->pfnPrintf(pHlp, " PLS=%u", (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT); + if (p->portsc & XHCI_PORT_PP ) pHlp->pfnPrintf(pHlp, " PP" ); + pHlp->pfnPrintf(pHlp, " SPD=%u", (p->portsc & XHCI_PORT_SPD_MASK) >> XHCI_PORT_SPD_SHIFT); + if (p->portsc & XHCI_PORT_LWS) pHlp->pfnPrintf(pHlp, " LWS" ); + if (p->portsc & XHCI_PORT_CSC) pHlp->pfnPrintf(pHlp, " CSC" ); + if (p->portsc & XHCI_PORT_PEC) pHlp->pfnPrintf(pHlp, " PEC" ); + if (p->portsc & XHCI_PORT_WRC) pHlp->pfnPrintf(pHlp, " WRC" ); + if (p->portsc & XHCI_PORT_OCC) pHlp->pfnPrintf(pHlp, " OCC" ); + if (p->portsc & XHCI_PORT_PRC) pHlp->pfnPrintf(pHlp, " PRC" ); + if (p->portsc & XHCI_PORT_PLC) pHlp->pfnPrintf(pHlp, " PLC" ); + if (p->portsc & XHCI_PORT_CEC) pHlp->pfnPrintf(pHlp, " CEC" ); + if (p->portsc & XHCI_PORT_CAS) pHlp->pfnPrintf(pHlp, " CAS" ); + if (p->portsc & XHCI_PORT_WCE) pHlp->pfnPrintf(pHlp, " WCE" ); + if (p->portsc & XHCI_PORT_WDE) pHlp->pfnPrintf(pHlp, " WDE" ); + if (p->portsc & XHCI_PORT_WOE) pHlp->pfnPrintf(pHlp, " WOE" ); + if (p->portsc & XHCI_PORT_DR ) pHlp->pfnPrintf(pHlp, " DR" ); + if (p->portsc & XHCI_PORT_WPR) pHlp->pfnPrintf(pHlp, " WPR" ); + pHlp->pfnPrintf(pHlp, "\n"); + } + + /* Device contexts. */ + if (fVerbose && (pThis->cmd & XHCI_CMD_RS)) + { + for (i = 0; i < XHCI_NDS; ++i) + { + if (pThis->aSlotState[i] > XHCI_DEVSLOT_EMPTY) + { + RTGCPHYS GCPhysSlot; + XHCI_DEV_CTX ctxDevice; + XHCI_SLOT_CTX ctxSlot; + const char *pcszDesc; + uint8_t uSlotID = IDX_TO_ID(i); + + /* Find the slot address/ */ + GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID); + pHlp->pfnPrintf(pHlp, "Slot %d (device context @ %RGp)\n", uSlotID, GCPhysSlot); + if (!GCPhysSlot) + { + pHlp->pfnPrintf(pHlp, "Bad context address, skipping!\n"); + continue; + } + + /* Just read in the whole lot and sort in which contexts are valid later. */ + PDMDevHlpPCIPhysRead(pDevIns, GCPhysSlot, &ctxDevice, sizeof(ctxDevice)); + + ctxSlot = ctxDevice.entry[0].sc; + pcszDesc = ctxSlot.slot_state < RT_ELEMENTS(g_apszSltStates) ? g_apszSltStates[ctxSlot.slot_state] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Speed:%u Entries:%u RhPort:%u", ctxSlot.speed, ctxSlot.ctx_ent, ctxSlot.rh_port); + pHlp->pfnPrintf(pHlp, " Address:%u State:%s \n", ctxSlot.dev_addr, pcszDesc); + + /* Endpoint contexts. */ + for (j = 1; j <= ctxSlot.ctx_ent; ++j) + { + XHCI_EP_CTX ctxEP = ctxDevice.entry[j].ep; + + /* Skip disabled endpoints -- they may be unused and do not + * contain valid data in any case. + */ + if (ctxEP.ep_state == XHCI_EPST_DISABLED) + continue; + + pcszDesc = ctxEP.ep_state < RT_ELEMENTS(g_apszEpStates) ? g_apszEpStates[ctxEP.ep_state] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Endpoint DCI %u State:%s", j, pcszDesc); + pcszDesc = ctxEP.ep_type < RT_ELEMENTS(g_apszEpTypes) ? g_apszEpTypes[ctxEP.ep_type] : "BAD!!!"; + pHlp->pfnPrintf(pHlp, " Type:%s\n",pcszDesc); + + pHlp->pfnPrintf(pHlp, " Mult:%u MaxPStreams:%u LSA:%u Interval:%u\n", + ctxEP.mult, ctxEP.maxps, ctxEP.lsa, ctxEP.interval); + pHlp->pfnPrintf(pHlp, " CErr:%u HID:%u MaxPS:%u MaxBS:%u", + ctxEP.c_err, ctxEP.hid, ctxEP.max_pkt_sz, ctxEP.max_brs_sz); + pHlp->pfnPrintf(pHlp, " AvgTRBLen:%u MaxESIT:%u", + ctxEP.avg_trb_len, ctxEP.max_esit); + pHlp->pfnPrintf(pHlp, " LastFrm:%u IFC:%u LastCC:%u\n", + ctxEP.last_frm, ctxEP.ifc, ctxEP.last_cc); + pHlp->pfnPrintf(pHlp, " TRDP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trdp & XHCI_TRDP_ADDR_MASK), + ctxEP.trdp & XHCI_TRDP_DCS_MASK); + pHlp->pfnPrintf(pHlp, " TREP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trep & XHCI_TRDP_ADDR_MASK), + ctxEP.trep & XHCI_TRDP_DCS_MASK); + } + } + } + } +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnReset} + */ +static DECLCALLBACK(void) xhciR3Reset(PPDMDEVINS pDevIns) +{ + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + LogFlow(("xhciR3Reset:\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. + */ + + /* Set the HC Halted bit now to prevent completion callbacks from running + *(there is really no point when resetting). + */ + ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH); + + xhciR3BusStop(pDevIns, pThis, pThisCC); + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */); +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnDestruct} + */ +static DECLCALLBACK(int) xhciR3Destruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + LogFlow(("xhciR3Destruct:\n")); + + /* + * Destroy interrupter locks. + */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->aInterrupters[i].lock)) + PDMDevHlpCritSectDelete(pDevIns, &pThis->aInterrupters[i].lock); + } + + /* + * Clean up the worker thread and associated machinery. + */ + if (pThis->hEvtProcess != NIL_SUPSEMEVENT) + { + PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEvtProcess); + pThis->hEvtProcess = NIL_SUPSEMEVENT; + } + if (RTCritSectIsInitialized(&pThisCC->CritSectThrd)) + RTCritSectDelete(&pThisCC->CritSectThrd); + + return VINF_SUCCESS; +} + + +/** + * Worker for xhciR3Construct that registers a LUN (USB root hub). + */ +static int xhciR3RegisterHub(PPDMDEVINS pDevIns, PXHCIROOTHUBR3 pRh, int iLun, const char *pszDesc) +{ + int rc = PDMDevHlpDriverAttach(pDevIns, iLun, &pRh->IBase, &pRh->pIBase, pszDesc); + AssertMsgRCReturn(rc, ("Configuration error: Failed to attach root hub driver to LUN #%d! (%Rrc)\n", iLun, rc), rc); + + pRh->pIRhConn = PDMIBASE_QUERY_INTERFACE(pRh->pIBase, VUSBIROOTHUBCONNECTOR); + AssertMsgReturn(pRh->pIRhConn, + ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), + VERR_PDM_MISSING_INTERFACE); + + /* Set URB parameters. */ + rc = VUSBIRhSetUrbParams(pRh->pIRhConn, sizeof(VUSBURBHCIINT), 0); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to set URB parameters")); + + return rc; +} + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct,XHCI + * constructor} + */ +static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC); + PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3; + uint32_t cUsb2Ports; + uint32_t cUsb3Ports; + int rc; + LogFlow(("xhciR3Construct:\n")); + RT_NOREF(iInstance); + + /* + * Initialize data so the destructor runs smoothly. + */ + pThis->hEvtProcess = NIL_SUPSEMEVENT; + + /* + * Validate and read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "USB2Ports|USB3Ports|ChipType", ""); + + /* Number of USB2 ports option. */ + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB2Ports", &cUsb2Ports, XHCI_NDP_20_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("xHCI configuration error: failed to read USB2Ports as integer")); + + if (cUsb2Ports == 0 || cUsb2Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB2Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Number of USB3 ports option. */ + rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB3Ports", &cUsb3Ports, XHCI_NDP_30_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("xHCI configuration error: failed to read USB3Ports as integer")); + + if (cUsb3Ports == 0 || cUsb3Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB3Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Check that the total number of ports is within limits.*/ + if (cUsb2Ports + cUsb3Ports > XHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("xHCI configuration error: USB2Ports + USB3Ports must be in range [%u,%u]"), + 1, XHCI_NDP_MAX); + + /* Determine the model. */ + char szChipType[16]; + rc = pHlp->pfnCFGMQueryStringDef(pCfg, "ChipType", &szChipType[0], sizeof(szChipType), "PantherPoint"); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, + N_("xHCI configuration error: Querying \"ChipType\" as string failed")); + + /* + * The default model is Panther Point (8086:1E31), Intel's first and most widely + * supported xHCI implementation. For debugging, the Lynx Point (8086:8C31) model + * can be selected. These two models work with the 7 Series and 8 Series Intel xHCI + * drivers for Windows 7, respectively. There is no functional difference. + * For Windows XP support, it's also possible to present a Renesas uPD720201 xHC; + * this is an evolution of the original NEC xHCI chip. + */ + bool fChipLynxPoint = false; + bool fChipRenesas = false; + if (!strcmp(szChipType, "PantherPoint")) + fChipLynxPoint = false; + else if (!strcmp(szChipType, "LynxPoint")) + fChipLynxPoint = true; + else if (!strcmp(szChipType, "uPD720201")) + fChipRenesas = true; + else + return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS, + N_("xHCI configuration error: The \"ChipType\" value \"%s\" is unsupported"), szChipType); + + LogFunc(("cUsb2Ports=%u cUsb3Ports=%u szChipType=%s (%d,%d) fR0Enabled=%d fRCEnabled=%d\n", cUsb2Ports, cUsb3Ports, + szChipType, fChipLynxPoint, fChipRenesas, pDevIns->fR0Enabled, pDevIns->fRCEnabled)); + + /* Set up interrupter locks. */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i) + { + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aInterrupters[i].lock, RT_SRC_POS, "xHCIIntr#%u", i); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("xHCI: Failed to create critical section for interrupter %u"), i); + pThis->aInterrupters[i].index = i; /* Stash away index, mostly for logging/debugging. */ + } + + + /* + * Init instance data. + */ + pThisCC->pDevIns = pDevIns; + + PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0]; + if (fChipRenesas) + { + pThis->erst_addr_mask = NEC_ERST_ADDR_MASK; + PCIDevSetVendorId(pPciDev, 0x1912); + PCIDevSetDeviceId(pPciDev, 0x0014); + PCIDevSetByte(pPciDev, VBOX_PCI_REVISION_ID, 0x02); + } + else + { + pThis->erst_addr_mask = XHCI_ERST_ADDR_MASK; + PCIDevSetVendorId(pPciDev, 0x8086); + if (fChipLynxPoint) + PCIDevSetDeviceId(pPciDev, 0x8C31); /* Lynx Point / 8 Series */ + else + PCIDevSetDeviceId(pPciDev, 0x1E31); /* Panther Point / 7 Series */ + } + + PCIDevSetClassProg(pPciDev, 0x30); /* xHCI */ + PCIDevSetClassSub(pPciDev, 0x03); /* USB 3.0 */ + PCIDevSetClassBase(pPciDev, 0x0C); + PCIDevSetInterruptPin(pPciDev, 0x01); +#ifdef VBOX_WITH_MSI_DEVICES + PCIDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST); + PCIDevSetCapabilityList(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) */ + + pThis->cTotalPorts = (uint8_t)(cUsb2Ports + cUsb3Ports); + + /* Set up the USB2 root hub interface. */ + pThis->cUsb2Ports = (uint8_t)cUsb2Ports; + pThisCC->RootHub2.pXhciR3 = pThisCC; + pThisCC->RootHub2.cPortsImpl = cUsb2Ports; + pThisCC->RootHub2.uPortBase = 0; + pThisCC->RootHub2.IBase.pfnQueryInterface = xhciR3RhQueryInterface; + pThisCC->RootHub2.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts; + pThisCC->RootHub2.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions2; + pThisCC->RootHub2.IRhPort.pfnAttach = xhciR3RhAttach; + pThisCC->RootHub2.IRhPort.pfnDetach = xhciR3RhDetach; + pThisCC->RootHub2.IRhPort.pfnReset = xhciR3RhReset; + pThisCC->RootHub2.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion; + pThisCC->RootHub2.IRhPort.pfnXferError = xhciR3RhXferError; + + /* Now the USB3 root hub interface. */ + pThis->cUsb3Ports = (uint8_t)cUsb3Ports; + pThisCC->RootHub3.pXhciR3 = pThisCC; + pThisCC->RootHub3.cPortsImpl = cUsb3Ports; + pThisCC->RootHub3.uPortBase = XHCI_NDP_USB2(pThisCC); + pThisCC->RootHub3.IBase.pfnQueryInterface = xhciR3RhQueryInterface; + pThisCC->RootHub3.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts; + pThisCC->RootHub3.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions3; + pThisCC->RootHub3.IRhPort.pfnAttach = xhciR3RhAttach; + pThisCC->RootHub3.IRhPort.pfnDetach = xhciR3RhDetach; + pThisCC->RootHub3.IRhPort.pfnReset = xhciR3RhReset; + pThisCC->RootHub3.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion; + pThisCC->RootHub3.IRhPort.pfnXferError = xhciR3RhXferError; + + /* USB LED */ + pThisCC->RootHub2.Led.u32Magic = PDMLED_MAGIC; + pThisCC->RootHub3.Led.u32Magic = PDMLED_MAGIC; + pThisCC->IBase.pfnQueryInterface = xhciR3QueryStatusInterface; + pThisCC->ILeds.pfnQueryStatusLed = xhciR3QueryStatusLed; + + /* Initialize the capability registers */ + pThis->cap_length = XHCI_CAPS_REG_SIZE; + pThis->hci_version = 0x100; /* Version 1.0 */ + pThis->hcs_params1 = (XHCI_NDP_CFG(pThis) << 24) | (XHCI_NINTR << 8) | XHCI_NDS; + pThis->hcs_params2 = (XHCI_ERSTMAX_LOG2 << 4) | XHCI_IST; + pThis->hcs_params3 = (4 << 16) | 1; /* Matches Intel 7 Series xHCI. */ + /* Note: The Intel 7 Series xHCI does not have port power control (XHCI_HCC_PPC). */ + pThis->hcc_params = ((XHCI_XECP_OFFSET >> 2) << XHCI_HCC_XECP_SHIFT); /// @todo other fields + pThis->dbell_off = XHCI_DOORBELL_OFFSET; + pThis->rts_off = XHCI_RTREG_OFFSET; + + /* + * Set up extended capabilities. + */ + rc = xhciR3BuildExtCaps(pThis, pThisCC); + AssertRCReturn(rc, rc); + + /* + * Register PCI device and I/O region. + */ + rc = PDMDevHlpPCIRegister(pDevIns, pPciDev); + AssertRCReturn(rc, rc); + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = XHCI_PCI_MSI_CAP_OFS; + MsiReg.iMsiNextOffset = 0x00; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE (rc)) + { + PCIDevSetCapabilityList(pPciDev, 0x0); + /* That's OK, we can work without MSI */ + } +#endif + + rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, XHCI_MMIO_SIZE, PCI_ADDRESS_SPACE_MEM, + xhciMmioWrite, xhciMmioRead, NULL, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + /*| IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE*/, + "USB xHCI", &pThis->hMmio); + AssertRCReturn(rc, rc); + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, XHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + NULL, xhciR3SaveExec, NULL, + NULL, xhciR3LoadExec, NULL); + AssertRCReturn(rc, rc); + + /* + * Attach to the VBox USB RootHub Driver on LUN #0 (USB3 root hub). + * NB: USB3 must come first so that emulated devices which support both USB2 + * and USB3 are attached to the USB3 hub. + */ + rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub3, 0, "RootHubUSB3"); + AssertRCReturn(rc, rc); + + /* + * Attach to the VBox USB RootHub Driver on LUN #1 (USB2 root hub). + */ + rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub2, 1, "RootHubUSB2"); + AssertRCReturn(rc, rc); + + /* + * Attach the status LED (optional). + */ + PPDMIBASE pBase; + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else if (rc != VERR_PDM_NO_ATTACHED_DRIVER) + { + AssertMsgFailed(("xHCI: Failed to attach to status driver. rc=%Rrc\n", rc)); + return PDMDEV_SET_ERROR(pDevIns, rc, N_("xHCI cannot attach to status driver")); + } + + /* + * Create the MFINDEX wrap event timer. + */ + rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, xhciR3WrapTimer, pThis, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "xHCI MFINDEX Wrap", &pThis->hWrapTimer); + AssertRCReturn(rc, rc); + + /* + * Set up the worker thread. + */ + rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hEvtProcess); + AssertLogRelRCReturn(rc, rc); + + rc = RTCritSectInit(&pThisCC->CritSectThrd); + AssertLogRelRCReturn(rc, rc); + + rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->pWorkerThread, pThis, xhciR3WorkerLoop, xhciR3WorkerWakeUp, + 0, RTTHREADTYPE_IO, "xHCI"); + AssertLogRelRCReturn(rc, rc); + + /* + * Do a hardware reset. + */ + xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, false /* don't reset devices */); + +# ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocUrbs, STAMTYPE_COUNTER, "IsocUrbsErr", STAMUNIT_OCCURENCES, "Isoch URBs completed w/error."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocPkts, STAMTYPE_COUNTER, "IsocPktsErr", STAMUNIT_OCCURENCES, "Isoch packets completed w/error."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsWritten, STAMTYPE_COUNTER, "EventsWritten", STAMUNIT_OCCURENCES, "Event TRBs delivered."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsDropped, STAMTYPE_COUNTER, "EventsDropped", STAMUNIT_OCCURENCES, "Event TRBs dropped (HC stopped)."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsPending, STAMTYPE_COUNTER, "IntrsPending", STAMUNIT_OCCURENCES, "Requests to set the IP bit."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsSet, STAMTYPE_COUNTER, "IntrsSet", STAMUNIT_OCCURENCES, "Actual interrupts delivered."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsNotSet, STAMTYPE_COUNTER, "IntrsNotSet", STAMUNIT_OCCURENCES, "Interrupts not delivered/disabled."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsCleared, STAMTYPE_COUNTER, "IntrsCleared", STAMUNIT_OCCURENCES, "Interrupts cleared by guest."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerCtlUrb, STAMTYPE_COUNTER, "UrbTrbsCtl", STAMUNIT_COUNT, "TRBs per one control URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerDtaUrb, STAMTYPE_COUNTER, "UrbTrbsDta", STAMUNIT_COUNT, "TRBs per one data (bulk/intr) URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerIsoUrb, STAMTYPE_COUNTER, "UrbTrbsIso", STAMUNIT_COUNT, "TRBs per one isochronous URB."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeCtrl, STAMTYPE_COUNTER, "UrbSizeCtl", STAMUNIT_COUNT, "Size of a control URB in bytes."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeData, STAMTYPE_COUNTER, "UrbSizeDta", STAMUNIT_COUNT, "Size of a data (bulk/intr) URB in bytes."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeIsoc, STAMTYPE_COUNTER, "UrbSizeIso", STAMUNIT_COUNT, "Size of an isochronous URB in bytes."); + + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCaps, STAMTYPE_COUNTER, "Regs/RdCaps", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdConfig, STAMTYPE_COUNTER, "Regs/RdConfig", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/RdDevNotifyCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDoorBell, STAMTYPE_COUNTER, "Regs/RdDoorBell", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRstblSize, STAMTYPE_COUNTER, "Regs/RdEvtRstblSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsvd, STAMTYPE_COUNTER, "Regs/RdEvtRsvd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMgmt, STAMTYPE_COUNTER, "Regs/RdIntrMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMod, STAMTYPE_COUNTER, "Regs/RdIntrMod", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdMfIndex, STAMTYPE_COUNTER, "Regs/RdMfIndex", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPageSize, STAMTYPE_COUNTER, "Regs/RdPageSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortLinkInfo, STAMTYPE_COUNTER, "Regs/RdPortLinkInfo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortPowerMgmt, STAMTYPE_COUNTER, "Regs/RdPortPowerMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortRsvd, STAMTYPE_COUNTER, "Regs/RdPortRsvd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortStatusCtrl, STAMTYPE_COUNTER, "Regs/RdPortStatusCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbCmd, STAMTYPE_COUNTER, "Regs/RdUsbCmd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbSts, STAMTYPE_COUNTER, "Regs/RdUsbSts", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUnknown, STAMTYPE_COUNTER, "Regs/RdUnknown", STAMUNIT_COUNT, ""); + + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrConfig, STAMTYPE_COUNTER, "Regs/WrConfig", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/WrDevNotifyCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBell0, STAMTYPE_COUNTER, "Regs/WrDoorBell0", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBellN, STAMTYPE_COUNTER, "Regs/WrDoorBellN", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseHi", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseLo", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRstblSize, STAMTYPE_COUNTER, "Regs/WrEvtRstblSize", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMgmt, STAMTYPE_COUNTER, "Regs/WrIntrMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMod, STAMTYPE_COUNTER, "Regs/WrIntrMod", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortPowerMgmt, STAMTYPE_COUNTER, "Regs/WrPortPowerMgmt", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortStatusCtrl, STAMTYPE_COUNTER, "Regs/WrPortStatusCtrl", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbCmd, STAMTYPE_COUNTER, "Regs/WrUsbCmd", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbSts, STAMTYPE_COUNTER, "Regs/WrUsbSts", STAMUNIT_COUNT, ""); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUnknown, STAMTYPE_COUNTER, "Regs/WrUnknown", STAMUNIT_COUNT, ""); +# endif /* VBOX_WITH_STATISTICS */ + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "xhci", "xHCI registers.", xhciR3Info); + + return VINF_SUCCESS; +} + +#else /* !IN_RING3 */ + +/** + * @callback_method_impl{PDMDEVREGR0,pfnConstruct} + */ +static DECLCALLBACK(int) xhciRZConstruct(PPDMDEVINS pDevIns) +{ + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI); + + int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, xhciMmioWrite, xhciMmioRead, NULL /*pvUser*/); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + +#endif /* !IN_RING3 */ + +/* Without this, g_DeviceXHCI won't be visible outside this module! */ +extern "C" const PDMDEVREG g_DeviceXHCI; + +const PDMDEVREG g_DeviceXHCI = +{ + /* .u32version = */ PDM_DEVREG_VERSION, + /* .uReserved0 = */ 0, + /* .szName = */ "usb-xhci", + /* .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(XHCI), + /* .cbInstanceCC = */ sizeof(XHCICC), + /* .cbInstanceRC = */ sizeof(XHCIRC), + /* .cMaxPciDevices = */ 1, + /* .cMaxMsixVectors = */ 0, + /* .pszDescription = */ "xHCI 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 = */ xhciR3Construct, + /* .pfnDestruct = */ xhciR3Destruct, + /* .pfnRelocate = */ NULL, + /* .pfnMemSetup = */ NULL, + /* .pfnPowerOn = */ NULL, + /* .pfnReset = */ xhciR3Reset, + /* .pfnSuspend = */ NULL, + /* .pfnResume = */ NULL, + /* .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 = */ xhciRZConstruct, + /* .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 = */ xhciRZConstruct, + /* .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 +}; + +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/Devices/USB/DrvVUSBRootHub.cpp b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp new file mode 100644 index 00000000..53b8811c --- /dev/null +++ b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp @@ -0,0 +1,1880 @@ +/* $Id: DrvVUSBRootHub.cpp $ */ +/** @file + * Virtual USB - Root Hub Driver. + */ + +/* + * Copyright (C) 2005-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/** @page pg_dev_vusb VUSB - Virtual USB + * + * @todo read thru this and correct typos. Merge with old docs. + * + * + * The Virtual USB component glues USB devices and host controllers together. + * The VUSB takes the form of a PDM driver which is attached to the HCI. USB + * devices are created by, attached to, and managed by the VUSB roothub. The + * VUSB also exposes an interface which is used by Main to attach and detach + * proxied USB devices. + * + * + * @section sec_dev_vusb_urb The Life of an URB + * + * The URB is created when the HCI calls the roothub (VUSB) method pfnNewUrb. + * VUSB has a pool of URBs, if no free URBs are available a new one is + * allocated. The returned URB starts life in the ALLOCATED state and all + * fields are initialized with sensible defaults. + * + * The HCI then copies any request data into the URB if it's an host2dev + * transfer. It then submits the URB by calling the pfnSubmitUrb roothub + * method. + * + * pfnSubmitUrb will start by checking if it knows the device address, and if + * it doesn't the URB is completed with a device-not-ready error. When the + * device address is known to it, action is taken based on the kind of + * transfer it is. There are four kinds of transfers: 1. control, 2. bulk, + * 3. interrupt, and 4. isochronous. In either case something eventually ends + * up being submitted to the device. + * + * + * If an URB fails submitting, may or may not be completed. This depends on + * heuristics in some cases and on the kind of failure in others. If + * pfnSubmitUrb returns a failure, the HCI should retry submitting it at a + * later time. If pfnSubmitUrb returns success the URB is submitted, and it + * can even been completed. + * + * The URB is in the IN_FLIGHT state from the time it's successfully submitted + * and till it's reaped or cancelled. + * + * When an URB transfer or in some case submit failure occurs, the pfnXferError + * callback of the HCI is consulted about what to do. If pfnXferError indicates + * that the URB should be retried, pfnSubmitUrb will fail. If it indicates that + * it should fail, the URB will be completed. + * + * Completing an URB means that the URB status is set and the HCI + * pfnXferCompletion callback is invoked with the URB. The HCI is the supposed + * to report the transfer status to the guest OS. After completion the URB + * is freed and returned to the pool, unless it was cancelled. If it was + * cancelled it will have to await reaping before it's actually freed. + * + * + * @subsection subsec_dev_vusb_urb_ctrl Control + * + * The control transfer is the most complex one, from VUSB's point of view, + * with its three stages and being bi-directional. A control transfer starts + * with a SETUP packet containing the request description and two basic + * parameters. It is followed by zero or more DATA packets which either picks + * up incoming data (dev2host) or supplies the request data (host2dev). This + * can then be followed by a STATUS packet which gets the status of the whole + * transfer. + * + * What makes the control transfer complicated is that for a host2dev request + * the URB is assembled from the SETUP and DATA stage, and for a dev2host + * request the returned data must be kept around for the DATA stage. For both + * transfer directions the status of the transfer has to be kept around for + * the STATUS stage. + * + * To complicate matters further, VUSB must intercept and in some cases emulate + * some of the standard requests in order to keep the virtual device state + * correct and provide the correct virtualization of a device. + * + * @subsection subsec_dev_vusb_urb_bulk Bulk and Interrupt + * + * The bulk and interrupt transfer types are relativly simple compared to the + * control transfer. VUSB is not inspecting the request content or anything, + * but passes it down the device. + * + * @subsection subsec_dev_vusb_urb_isoc Isochronous + * + * This kind of transfers hasn't yet been implemented. + * + */ + + +/** @page pg_dev_vusb_old VUSB - Virtual USB Core + * + * The virtual USB core is controlled by the roothub and the underlying HCI + * emulator, it is responsible for device addressing, managing configurations, + * interfaces and endpoints, assembling and splitting multi-part control + * messages and in general acts as a middle layer between the USB device + * emulation code and USB HCI emulation code. + * + * All USB devices are represented by a struct vusb_dev. This structure + * contains things like the device state, device address, all the configuration + * descriptors, the currently selected configuration and a mapping between + * endpoint addresses and endpoint descriptors. + * + * Each vusb_dev also has a pointer to a vusb_dev_ops structure which serves as + * the virtual method table and includes a virtual constructor and destructor. + * After a vusb_dev is created it may be attached to a hub device such as a + * roothub (using vusbHubAttach). Although each hub structure has cPorts + * and cDevices fields, it is the responsibility of the hub device to allocate + * a free port for the new device. + * + * Devices can chose one of two interfaces for dealing with requests, the + * synchronous interface or the asynchronous interface. The synchronous + * interface is much simpler and ought to be used for devices which are + * unlikely to sleep for long periods in order to serve requests. The + * asynchronous interface on the other hand is more difficult to use but is + * useful for the USB proxy or if one were to write a mass storage device + * emulator. Currently the synchronous interface only supports control and bulk + * endpoints and is no longer used by anything. + * + * In order to use the asynchronous interface, the queue_urb, cancel_urb and + * pfnUrbReap fields must be set in the devices vusb_dev_ops structure. The + * queue_urb method is used to submit a request to a device without blocking, + * it returns 1 if successful and 0 on any kind of failure. A successfully + * queued URB is completed when the pfnUrbReap method returns it. Each function + * address is reference counted so that pfnUrbReap will only be called if there + * are URBs outstanding. For a roothub to reap an URB from any one of it's + * devices, the vusbRhReapAsyncUrbs() function is used. + * + * There are four types of messages an URB may contain: + * -# Control - represents a single packet of a multi-packet control + * transfer, these are only really used by the host controller to + * submit the parts to the usb core. + * -# Message - the usb core assembles multiple control transfers in + * to single message transfers. In this case the data buffer + * contains the setup packet in little endian followed by the full + * buffer. In the case of an host-to-device control message, the + * message packet is created when the STATUS transfer is seen. In + * the case of device-to-host messages, the message packet is + * created after the SETUP transfer is seen. Also, certain control + * requests never go the real device and get handled synchronously. + * -# Bulk - Currently the only endpoint type that does error checking + * and endpoint halting. + * -# Interrupt - The only non-periodic type supported. + * + * Hubs are special cases of devices, they have a number of downstream ports + * that other devices can be attached to and removed from. + * + * After a device has been attached (vusbHubAttach): + * -# The hub attach method is called, which sends a hub status + * change message to the OS. + * -# The OS resets the device, and it appears on the default + * address with it's config 0 selected (a pseudo-config that + * contains only 1 interface with 1 endpoint - the default + * message pipe). + * -# The OS assigns the device a new address and selects an + * appropriate config. + * -# The device is ready. + * + * After a device has been detached (vusbDevDetach): + * -# All pending URBs are cancelled. + * -# The devices address is unassigned. + * -# The hub detach method is called which signals the OS + * of the status change. + * -# The OS unlinks the ED's for that device. + * + * A device can also request detachment from within its own methods by + * calling vusbDevUnplugged(). + * + * Roothubs are responsible for driving the whole system, they are special + * cases of hubs and as such implement attach and detach methods, each one + * is described by a struct vusb_roothub. Once a roothub has submitted an + * URB to the USB core, a number of callbacks to the roothub are required + * for when the URB completes, since the roothub typically wants to inform + * the OS when transfers are completed. + * + * There are four callbacks to be concerned with: + * -# prepare - This is called after the URB is successfully queued. + * -# completion - This is called after the URB completed. + * -# error - This is called if the URB errored, some systems have + * automatic resubmission of failed requests, so this callback + * should keep track of the error count and return 1 if the count + * is above the number of allowed resubmissions. + * -# halt_ep - This is called after errors on bulk pipes in order + * to halt the pipe. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/uuid.h> +#include "VUSBInternal.h" +#include "VBoxDD.h" + + +#define VUSB_ROOTHUB_SAVED_STATE_VERSION 1 + + +/** + * Data used for reattaching devices on a state load. + */ +typedef struct VUSBROOTHUBLOAD +{ + /** Timer used once after state load to inform the guest about new devices. + * We do this to be sure the guest get any disconnect / reconnect on the + * same port. */ + TMTIMERHANDLE hTimer; + /** Number of detached devices. */ + unsigned cDevs; + /** Array of devices which were detached. */ + PVUSBDEV apDevs[VUSB_DEVICES_MAX]; +} VUSBROOTHUBLOAD; + + +/** + * Returns the attached VUSB device for the given port or NULL if none is attached. + * + * @returns Pointer to the VUSB device or NULL if not found. + * @param pThis The VUSB roothub device instance. + * @param uPort The port to get the device for. + * @param pszWho Caller of this method. + * + * @note The reference count of the VUSB device structure is retained to prevent it from going away. + */ +static PVUSBDEV vusbR3RhGetVUsbDevByPortRetain(PVUSBROOTHUB pThis, uint32_t uPort, const char *pszWho) +{ + PVUSBDEV pDev = NULL; + + AssertReturn(uPort < RT_ELEMENTS(pThis->apDevByPort), NULL); + + RTCritSectEnter(&pThis->CritSectDevices); + + pDev = pThis->apDevByPort[uPort]; + if (RT_LIKELY(pDev)) + vusbDevRetain(pDev, pszWho); + + RTCritSectLeave(&pThis->CritSectDevices); + + return pDev; +} + + +/** + * Returns the attached VUSB device for the given port or NULL if none is attached. + * + * @returns Pointer to the VUSB device or NULL if not found. + * @param pThis The VUSB roothub device instance. + * @param u8Address The address to get the device for. + * @param pszWho Caller of this method. + * + * @note The reference count of the VUSB device structure is retained to prevent it from going away. + */ +static PVUSBDEV vusbR3RhGetVUsbDevByAddrRetain(PVUSBROOTHUB pThis, uint8_t u8Address, const char *pszWho) +{ + PVUSBDEV pDev = NULL; + + AssertReturn(u8Address < RT_ELEMENTS(pThis->apDevByAddr), NULL); + + RTCritSectEnter(&pThis->CritSectDevices); + + pDev = pThis->apDevByAddr[u8Address]; + if (RT_LIKELY(pDev)) + vusbDevRetain(pDev, pszWho); + + RTCritSectLeave(&pThis->CritSectDevices); + + return pDev; +} + + +/** + * Returns a human readable string fromthe given USB speed enum. + * + * @returns Human readable string. + * @param enmSpeed The speed to stringify. + */ +static const char *vusbGetSpeedString(VUSBSPEED enmSpeed) +{ + const char *pszSpeed = NULL; + + switch (enmSpeed) + { + case VUSB_SPEED_LOW: + pszSpeed = "Low"; + break; + case VUSB_SPEED_FULL: + pszSpeed = "Full"; + break; + case VUSB_SPEED_HIGH: + pszSpeed = "High"; + break; + case VUSB_SPEED_VARIABLE: + pszSpeed = "Variable"; + break; + case VUSB_SPEED_SUPER: + pszSpeed = "Super"; + break; + case VUSB_SPEED_SUPERPLUS: + pszSpeed = "SuperPlus"; + break; + default: + pszSpeed = "Unknown"; + break; + } + return pszSpeed; +} + + +/** + * Attaches a device to a specific hub. + * + * This function is called by the vusb_add_device() and vusbRhAttachDevice(). + * + * @returns VBox status code. + * @param pThis The roothub to attach it to. + * @param pDev The device to attach. + * @thread EMT + */ +static int vusbHubAttach(PVUSBROOTHUB pThis, PVUSBDEV pDev) +{ + LogFlow(("vusbHubAttach: pThis=%p[%s] pDev=%p[%s]\n", pThis, pThis->pszName, pDev, pDev->pUsbIns->pszName)); + + /* + * Assign a port. + */ + int iPort = ASMBitFirstSet(&pThis->Bitmap, sizeof(pThis->Bitmap) * 8); + if (iPort < 0) + { + LogRel(("VUSB: No ports available!\n")); + return VERR_VUSB_NO_PORTS; + } + ASMBitClear(&pThis->Bitmap, iPort); + pThis->cDevices++; + pDev->i16Port = iPort; + + /* Call the device attach helper, so it can initialize its state. */ + int rc = vusbDevAttach(pDev, pThis); + if (RT_SUCCESS(rc)) + { + RTCritSectEnter(&pThis->CritSectDevices); + Assert(!pThis->apDevByPort[iPort]); + pThis->apDevByPort[iPort] = pDev; + RTCritSectLeave(&pThis->CritSectDevices); + + /* + * Call the HCI attach routine and let it have its say before the device is + * linked into the device list of this hub. + */ + VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice); + rc = pThis->pIRhPort->pfnAttach(pThis->pIRhPort, iPort, enmSpeed); + if (RT_SUCCESS(rc)) + { + LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName, + iPort, pThis->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed))); + return VINF_SUCCESS; + } + + /* Remove from the port in case of failure. */ + RTCritSectEnter(&pThis->CritSectDevices); + Assert(!pThis->apDevByPort[iPort]); + pThis->apDevByPort[iPort] = NULL; + RTCritSectLeave(&pThis->CritSectDevices); + + vusbDevDetach(pDev); + } + + ASMBitSet(&pThis->Bitmap, iPort); + pThis->cDevices--; + pDev->i16Port = -1; + LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc)); + + return rc; +} + + +/** + * Detaches the given device from the given roothub. + * + * @returns VBox status code. + * @param pThis The roothub to detach the device from. + * @param pDev The device to detach. + */ +static int vusbHubDetach(PVUSBROOTHUB pThis, PVUSBDEV pDev) +{ + Assert(pDev->i16Port != -1); + + /* + * Detach the device and mark the port as available. + */ + unsigned uPort = pDev->i16Port; + pDev->i16Port = -1; + pThis->pIRhPort->pfnDetach(pThis->pIRhPort, uPort); + ASMBitSet(&pThis->Bitmap, uPort); + pThis->cDevices--; + + /* Check that it's attached and remove it. */ + RTCritSectEnter(&pThis->CritSectDevices); + Assert(pThis->apDevByPort[uPort] == pDev); + pThis->apDevByPort[uPort] = NULL; + + if (pDev->u8Address != VUSB_INVALID_ADDRESS) + { + Assert(pThis->apDevByAddr[pDev->u8Address] == pDev); + pThis->apDevByAddr[pDev->u8Address] = NULL; + + pDev->u8Address = VUSB_INVALID_ADDRESS; + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + } + RTCritSectLeave(&pThis->CritSectDevices); + + /* Cancel all in-flight URBs from this device. */ + vusbDevCancelAllUrbs(pDev, true); + + /* Free resources. */ + vusbDevDetach(pDev); + return VINF_SUCCESS; +} + + + +/* -=-=-=-=-=- PDMUSBHUBREG methods -=-=-=-=-=- */ + +/** @interface_method_impl{PDMUSBHUBREG,pfnAttachDevice} */ +static DECLCALLBACK(int) vusbPDMHubAttachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, const char *pszCaptureFilename, uint32_t *piPort) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + + /* + * Allocate a new VUSB device and initialize it. + */ + PVUSBDEV pDev = (PVUSBDEV)RTMemAllocZ(sizeof(*pDev)); + AssertReturn(pDev, VERR_NO_MEMORY); + int rc = vusbDevInit(pDev, pUsbIns, pszCaptureFilename); + if (RT_SUCCESS(rc)) + { + pUsbIns->pvVUsbDev2 = pDev; + rc = vusbHubAttach(pThis, pDev); + if (RT_SUCCESS(rc)) + { + *piPort = UINT32_MAX; /// @todo implement piPort + return rc; + } + + RTMemFree(pDev->paIfStates); + pUsbIns->pvVUsbDev2 = NULL; + } + vusbDevRelease(pDev, "vusbPDMHubAttachDevice"); + return rc; +} + + +/** @interface_method_impl{PDMUSBHUBREG,pfnDetachDevice} */ +static DECLCALLBACK(int) vusbPDMHubDetachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, uint32_t iPort) +{ + RT_NOREF(iPort); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PVUSBDEV pDev = (PVUSBDEV)pUsbIns->pvVUsbDev2; + Assert(pDev); + + LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, pDev->i16Port, pThis->pszName)); + + /* + * Deal with pending async reset. + * (anything but reset) + */ + vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET); + vusbHubDetach(pThis, pDev); + vusbDevRelease(pDev, "vusbPDMHubDetachDevice"); + return VINF_SUCCESS; +} + +/** + * The hub registration structure. + */ +static const PDMUSBHUBREG g_vusbHubReg = +{ + PDM_USBHUBREG_VERSION, + vusbPDMHubAttachDevice, + vusbPDMHubDetachDevice, + PDM_USBHUBREG_VERSION +}; + + +/* -=-=-=-=-=- VUSBIROOTHUBCONNECTOR methods -=-=-=-=-=- */ + + +/** + * Callback for freeing an URB. + * @param pUrb The URB to free. + */ +static DECLCALLBACK(void) vusbRhFreeUrb(PVUSBURB pUrb) +{ + /* + * Assert sanity. + */ + vusbUrbAssert(pUrb); + PVUSBROOTHUB pRh = (PVUSBROOTHUB)pUrb->pVUsb->pvFreeCtx; + Assert(pRh); + + Assert(pUrb->enmState != VUSBURBSTATE_FREE); + +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbRhFreeUrb", true); +#endif + + /* + * Free the URB description (logging builds only). + */ + if (pUrb->pszDesc) + { + RTStrFree(pUrb->pszDesc); + pUrb->pszDesc = NULL; + } + + /* The URB comes from the roothub if there is no device (invalid address). */ + if (pUrb->pVUsb->pDev) + { + PVUSBDEV pDev = pUrb->pVUsb->pDev; + + vusbUrbPoolFree(&pUrb->pVUsb->pDev->UrbPool, pUrb); + vusbDevRelease(pDev, "vusbRhFreeUrb"); + } + else + vusbUrbPoolFree(&pRh->UrbPool, pUrb); +} + + +/** + * Worker routine for vusbRhConnNewUrb(). + */ +static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) +{ + RT_NOREF(pszTag); + PVUSBURBPOOL pUrbPool = &pRh->UrbPool; + + if (RT_UNLIKELY(cbData > (32 * _1M))) + { + LogFunc(("Bad URB size (%u)!\n", cbData)); + return NULL; + } + + PVUSBDEV pDev; + if (uPort == VUSB_DEVICE_PORT_INVALID) + pDev = vusbR3RhGetVUsbDevByAddrRetain(pRh, DstAddress, "vusbRhNewUrb"); + else + pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhNewUrb"); + + if (pDev) + pUrbPool = &pDev->UrbPool; + + PVUSBURB pUrb = vusbUrbPoolAlloc(pUrbPool, enmType, enmDir, cbData, + pRh->cbHci, pRh->cbHciTd, cTds); + if (RT_LIKELY(pUrb)) + { + pUrb->pVUsb->pvFreeCtx = pRh; + pUrb->pVUsb->pfnFree = vusbRhFreeUrb; + pUrb->DstAddress = DstAddress; + pUrb->pVUsb->pDev = pDev; + +#ifdef LOG_ENABLED + const char *pszType = NULL; + + switch(pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: + pszType = "ctrl"; + break; + case VUSBXFERTYPE_INTR: + pszType = "intr"; + break; + case VUSBXFERTYPE_BULK: + pszType = "bulk"; + break; + case VUSBXFERTYPE_ISOC: + pszType = "isoc"; + break; + default: + pszType = "invld"; + break; + } + + pRh->iSerial = (pRh->iSerial + 1) % 10000; + RTStrAPrintf(&pUrb->pszDesc, "URB %p %s%c%04d (%s)", pUrb, pszType, + (pUrb->enmDir == VUSBDIRECTION_IN) ? '<' : ((pUrb->enmDir == VUSBDIRECTION_SETUP) ? 's' : '>'), + pRh->iSerial, pszTag ? pszTag : "<none>"); + + vusbUrbTrace(pUrb, "vusbRhNewUrb", false); +#endif + } + + return pUrb; +} + + +/** + * Calculate frame timer variables given a frame rate. + */ +static void vusbRhR3CalcTimerIntervals(PVUSBROOTHUB pThis, uint32_t u32FrameRate) +{ + pThis->nsWait = RT_NS_1SEC / u32FrameRate; + pThis->uFrameRate = u32FrameRate; + /* Inform the HCD about the new frame rate. */ + pThis->pIRhPort->pfnFrameRateChanged(pThis->pIRhPort, u32FrameRate); +} + + +/** + * Calculates the new frame rate based on the idle detection and number of idle + * cycles. + * + * @returns nothing. + * @param pThis The roothub instance data. + * @param fIdle Flag whether the last frame didn't produce any activity. + */ +static void vusbRhR3FrameRateCalcNew(PVUSBROOTHUB pThis, bool fIdle) +{ + uint32_t uNewFrameRate = pThis->uFrameRate; + + /* + * Adjust the frame timer interval based on idle detection. + */ + if (fIdle) + { + pThis->cIdleCycles++; + /* Set the new frame rate based on how long we've been idle. Tunable. */ + switch (pThis->cIdleCycles) + { + case 4: uNewFrameRate = 500; break; /* 2ms interval */ + case 16:uNewFrameRate = 125; break; /* 8ms interval */ + case 24:uNewFrameRate = 50; break; /* 20ms interval */ + default: break; + } + /* Avoid overflow. */ + if (pThis->cIdleCycles > 60000) + pThis->cIdleCycles = 20000; + } + else + { + if (pThis->cIdleCycles) + { + pThis->cIdleCycles = 0; + uNewFrameRate = pThis->uFrameRateDefault; + } + } + + if ( uNewFrameRate != pThis->uFrameRate + && uNewFrameRate) + { + LogFlow(("Frame rate changed from %u to %u\n", pThis->uFrameRate, uNewFrameRate)); + vusbRhR3CalcTimerIntervals(pThis, uNewFrameRate); + } +} + + +/** + * The core frame processing routine keeping track of the elapsed time and calling into + * the device emulation above us to do the work. + * + * @returns Relative timespan when to process the next frame. + * @param pThis The roothub instance data. + * @param fCallback Flag whether this method is called from the URB completion callback or + * from the worker thread (only used for statistics). + */ +DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback) +{ + uint64_t tsNext = 0; + uint64_t tsNanoStart = RTTimeNanoTS(); + + /* Don't do anything if we are not supposed to process anything (EHCI and XHCI). */ + if (!pThis->uFrameRateDefault) + return 0; + + if (ASMAtomicXchgBool(&pThis->fFrameProcessing, true)) + return pThis->nsWait; + + if ( tsNanoStart > pThis->tsFrameProcessed + && tsNanoStart - pThis->tsFrameProcessed >= 750 * RT_NS_1US) + { + LogFlowFunc(("Starting new frame at ts %llu\n", tsNanoStart)); + + bool fIdle = pThis->pIRhPort->pfnStartFrame(pThis->pIRhPort, 0 /* u32FrameNo */); + vusbRhR3FrameRateCalcNew(pThis, fIdle); + + uint64_t tsNow = RTTimeNanoTS(); + tsNext = (tsNanoStart + pThis->nsWait) > tsNow ? (tsNanoStart + pThis->nsWait) - tsNow : 0; + pThis->tsFrameProcessed = tsNanoStart; + LogFlowFunc(("Current frame took %llu nano seconds to process, next frame in %llu ns\n", tsNow - tsNanoStart, tsNext)); + if (fCallback) + STAM_COUNTER_INC(&pThis->StatFramesProcessedClbk); + else + STAM_COUNTER_INC(&pThis->StatFramesProcessedThread); + } + else + { + tsNext = (pThis->tsFrameProcessed + pThis->nsWait) > tsNanoStart ? (pThis->tsFrameProcessed + pThis->nsWait) - tsNanoStart : 0; + LogFlowFunc(("Next frame is too far away in the future, waiting... (tsNanoStart=%llu tsFrameProcessed=%llu)\n", + tsNanoStart, pThis->tsFrameProcessed)); + } + + ASMAtomicXchgBool(&pThis->fFrameProcessing, false); + LogFlowFunc(("returns %llu\n", tsNext)); + return tsNext; +} + + +/** + * Worker for processing frames periodically. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pThread The PDM thread structure for the thread this worker runs on. + */ +static DECLCALLBACK(int) vusbRhR3PeriodFrameWorker(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pDrvIns); + int rc = VINF_SUCCESS; + PVUSBROOTHUB pThis = (PVUSBROOTHUB)pThread->pvUser; + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + while ( !ASMAtomicReadU32(&pThis->uFrameRateDefault) + && pThread->enmState == PDMTHREADSTATE_RUNNING) + { + /* Signal the waiter that we are stopped now. */ + rc = RTSemEventMultiSignal(pThis->hSemEventPeriodFrameStopped); + AssertRC(rc); + + rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrame, RT_INDEFINITE_WAIT); + RTSemEventMultiReset(pThis->hSemEventPeriodFrame); + + /* + * Notify the device above about the frame rate changed if we are supposed to + * process frames. + */ + uint32_t uFrameRate = ASMAtomicReadU32(&pThis->uFrameRateDefault); + if (uFrameRate) + vusbRhR3CalcTimerIntervals(pThis, uFrameRate); + } + + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + + uint64_t tsNext = vusbRhR3ProcessFrame(pThis, false /* fCallback */); + + if (tsNext >= 250 * RT_NS_1US) + { + rc = RTSemEventMultiWaitEx(pThis->hSemEventPeriodFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE, + tsNext); + AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc)); + RTSemEventMultiReset(pThis->hSemEventPeriodFrame); + } + } + + return VINF_SUCCESS; +} + + +/** + * Unblock the periodic frame thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pThread The send thread. + */ +static DECLCALLBACK(int) vusbRhR3PeriodFrameWorkerWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pThread); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + return RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetUrbParams} */ +static DECLCALLBACK(int) vusbRhSetUrbParams(PVUSBIROOTHUBCONNECTOR pInterface, size_t cbHci, size_t cbHciTd) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + pRh->cbHci = cbHci; + pRh->cbHciTd = cbHciTd; + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReset} */ +static DECLCALLBACK(int) vusbR3RhReset(PVUSBIROOTHUBCONNECTOR pInterface, bool fResetOnLinux) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOn} */ +static DECLCALLBACK(int) vusbR3RhPowerOn(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + LogFlow(("vusR3bRhPowerOn: pRh=%p\n", pRh)); + + Assert( pRh->enmState != VUSB_DEVICE_STATE_DETACHED + && pRh->enmState != VUSB_DEVICE_STATE_RESET); + + if (pRh->enmState == VUSB_DEVICE_STATE_ATTACHED) + pRh->enmState = VUSB_DEVICE_STATE_POWERED; + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOff} */ +static DECLCALLBACK(int) vusbR3RhPowerOff(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + LogFlow(("vusbR3RhDevPowerOff: pThis=%p\n", pThis)); + + Assert( pThis->enmState != VUSB_DEVICE_STATE_DETACHED + && pThis->enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Cancel all URBs and reap them. + */ + VUSBIRhCancelAllUrbs(&pThis->IRhConnector); + for (uint32_t uPort = 0; uPort < RT_ELEMENTS(pThis->apDevByPort); uPort++) + VUSBIRhReapAsyncUrbs(&pThis->IRhConnector, uPort, 0); + + pThis->enmState = VUSB_DEVICE_STATE_ATTACHED; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnNewUrb} */ +static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + return vusbRhNewUrb(pRh, DstAddress, uPort, enmType, enmDir, cbData, cTds, pszTag); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnFreeUrb} */ +static DECLCALLBACK(int) vusbRhConnFreeUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb) +{ + RT_NOREF(pInterface); + pUrb->pVUsb->pfnFree(pUrb); + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSubmitUrb} */ +static DECLCALLBACK(int) vusbRhSubmitUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb, PPDMLED pLed) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + STAM_PROFILE_START(&pRh->StatSubmitUrb, a); + +#ifdef VBOX_WITH_STATISTICS + /* + * Total and per-type submit statistics. + */ + Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsSubmitted); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsSubmitted); + + STAM_COUNTER_ADD(&pRh->Total.StatReqBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqBytes, pUrb->cbData); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatReqReadBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqReadBytes, pUrb->cbData); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatReqWriteBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqWriteBytes, pUrb->cbData); + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + STAM_COUNTER_ADD(&pRh->StatIsocReqPkts, pUrb->cIsocPkts); + if (pUrb->enmDir == VUSBDIRECTION_IN) + STAM_COUNTER_ADD(&pRh->StatIsocReqReadPkts, pUrb->cIsocPkts); + else + STAM_COUNTER_ADD(&pRh->StatIsocReqWritePkts, pUrb->cIsocPkts); + } +#endif + + /* If there is a sniffer on the roothub record the URB there. */ + if (pRh->hSniffer != VUSBSNIFFER_NIL) + { + int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB submit event on the root hub failed with %Rrc\n", rc)); + } + + /* + * The device was resolved when we allocated the URB. + * Submit it to the device if we found it, if not fail with device-not-ready. + */ + int rc; + if ( pUrb->pVUsb->pDev + && pUrb->pVUsb->pDev->pUsbIns) + { + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + pLed->Asserted.s.fReading = pLed->Actual.s.fReading = 1; + rc = vusbUrbSubmit(pUrb); + pLed->Actual.s.fReading = 0; + break; + case VUSBDIRECTION_OUT: + pLed->Asserted.s.fWriting = pLed->Actual.s.fWriting = 1; + rc = vusbUrbSubmit(pUrb); + pLed->Actual.s.fWriting = 0; + break; + default: + rc = vusbUrbSubmit(pUrb); + break; + } + + if (RT_FAILURE(rc)) + { + LogFlow(("vusbRhSubmitUrb: freeing pUrb=%p\n", pUrb)); + pUrb->pVUsb->pfnFree(pUrb); + } + } + else + { + Log(("vusb: pRh=%p: SUBMIT: Address %i not found!!!\n", pRh, pUrb->DstAddress)); + + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRhEx(pRh, pUrb); + rc = VINF_SUCCESS; + } + + STAM_PROFILE_STOP(&pRh->StatSubmitUrb, a); + return rc; +} + + +static DECLCALLBACK(int) vusbRhReapAsyncUrbsWorker(PVUSBDEV pDev, RTMSINTERVAL cMillies) +{ + if (!cMillies) + vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, 0); + else + { + uint64_t u64Start = RTTimeMilliTS(); + do + { + vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, RT_MIN(cMillies >> 8, 10)); + } while ( !RTListIsEmpty(&pDev->LstAsyncUrbs) + && RTTimeMilliTS() - u64Start < cMillies); + } + + return VINF_SUCCESS; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReapAsyncUrbs} */ +static DECLCALLBACK(void) vusbRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, RTMSINTERVAL cMillies) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); NOREF(pRh); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhReapAsyncUrbs"); + + if (!pDev) + return; + + if (!RTListIsEmpty(&pDev->LstAsyncUrbs)) + { + STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies); + AssertRC(rc); + STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a); + } + + vusbDevRelease(pDev, "vusbRhReapAsyncUrbs"); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelUrbsEp} */ +static DECLCALLBACK(int) vusbRhCancelUrbsEp(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + AssertReturn(pRh, VERR_INVALID_PARAMETER); + AssertReturn(pUrb, VERR_INVALID_PARAMETER); + + /// @todo This method of URB canceling may not work on non-Linux hosts. + /* + * Cancel and reap the URB(s) on an endpoint. + */ + LogFlow(("vusbRhCancelUrbsEp: pRh=%p pUrb=%p\n", pRh, pUrb)); + + vusbUrbCancelAsync(pUrb, CANCELMODE_UNDO); + + /* The reaper thread will take care of completing the URB. */ + + return VINF_SUCCESS; +} + +/** + * Worker doing the actual cancelling of all outstanding URBs on the device I/O thread. + * + * @returns VBox status code. + * @param pDev USB device instance data. + */ +static DECLCALLBACK(int) vusbRhCancelAllUrbsWorker(PVUSBDEV pDev) +{ + /* + * Cancel the URBS. + * + * Not using th CritAsyncUrbs critical section here is safe + * as the I/O thread is the only thread accessing this struture at the + * moment. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + /* Call the worker directly. */ + vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL); + } + + return VINF_SUCCESS; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelAllUrbs} */ +static DECLCALLBACK(void) vusbRhCancelAllUrbs(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + RTCritSectEnter(&pThis->CritSectDevices); + for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev) + vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev); + } + RTCritSectLeave(&pThis->CritSectDevices); +} + +/** + * Worker doing the actual cancelling of all outstanding per-EP URBs on the + * device I/O thread. + * + * @returns VBox status code. + * @param pDev USB device instance data. + * @param EndPt Endpoint number. + * @param enmDir Endpoint direction. + */ +static DECLCALLBACK(int) vusbRhAbortEpWorker(PVUSBDEV pDev, int EndPt, VUSBDIRECTION enmDir) +{ + /* + * Iterate the URBs, find ones corresponding to given EP, and cancel them. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + + Assert(pUrb->pVUsb->pDev == pDev); + + /* For the default control EP, direction does not matter. */ + if (pUrb->EndPt == EndPt && (pUrb->enmDir == enmDir || !EndPt)) + { + LogFlow(("%s: vusbRhAbortEpWorker: CANCELING URB\n", pUrb->pszDesc)); + int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_UNDO); + AssertRC(rc); + } + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAbortEp} */ +static DECLCALLBACK(int) vusbRhAbortEp(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhAbortEp"); + + if (pDev->pHub != pRh) + AssertFailedReturn(VERR_INVALID_PARAMETER); + + vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhAbortEpWorker, 3, pDev, EndPt, enmDir); + vusbDevRelease(pDev, "vusbRhAbortEp"); + + /* The reaper thread will take care of completing the URB. */ + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetPeriodicFrameProcessing} */ +static DECLCALLBACK(int) vusbRhSetFrameProcessing(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uFrameRate) +{ + int rc = VINF_SUCCESS; + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + /* Create the frame thread lazily. */ + if ( !pThis->hThreadPeriodFrame + && uFrameRate) + { + ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + pThis->uFrameRate = uFrameRate; + vusbRhR3CalcTimerIntervals(pThis, uFrameRate); + + rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrame); + AssertRCReturn(rc, rc); + + rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrameStopped); + AssertRCReturn(rc, rc); + + rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->hThreadPeriodFrame, pThis, vusbRhR3PeriodFrameWorker, + vusbRhR3PeriodFrameWorkerWakeup, 0, RTTHREADTYPE_IO, "VUsbPeriodFrm"); + AssertRCReturn(rc, rc); + + VMSTATE enmState = PDMDrvHlpVMState(pThis->pDrvIns); + if ( enmState == VMSTATE_RUNNING + || enmState == VMSTATE_RUNNING_LS) + { + rc = PDMDrvHlpThreadResume(pThis->pDrvIns, pThis->hThreadPeriodFrame); + AssertRCReturn(rc, rc); + } + } + else if ( pThis->hThreadPeriodFrame + && !uFrameRate) + { + /* Stop processing. */ + uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + if (uFrameRateOld) + { + rc = RTSemEventMultiReset(pThis->hSemEventPeriodFrameStopped); + AssertRC(rc); + + /* Signal the frame thread to stop. */ + RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); + + /* Wait for signal from the thread that it stopped. */ + rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrameStopped, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + } + else if ( pThis->hThreadPeriodFrame + && uFrameRate) + { + /* Just switch to the new frame rate and let the periodic frame thread pick it up. */ + uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + + /* Signal the frame thread to continue if it was stopped. */ + if (!uFrameRateOld) + RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); + } + + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnGetPeriodicFrameRate} */ +static DECLCALLBACK(uint32_t) vusbRhGetPeriodicFrameRate(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + return pThis->uFrameRate; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnUpdateIsocFrameDelta} */ +static DECLCALLBACK(uint32_t) vusbRhUpdateIsocFrameDelta(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, + int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + AssertReturn(pRh, 0); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhUpdateIsocFrameDelta"); AssertPtr(pDev); + PVUSBPIPE pPipe = &pDev->aPipes[EndPt]; + uint32_t *puLastFrame; + int32_t uFrameDelta; + uint32_t uMaxVal = 1 << uBits; + + puLastFrame = enmDir == VUSBDIRECTION_IN ? &pPipe->uLastFrameIn : &pPipe->uLastFrameOut; + uFrameDelta = uNewFrameID - *puLastFrame; + *puLastFrame = uNewFrameID; + /* Take care of wrap-around. */ + if (uFrameDelta < 0) + uFrameDelta += uMaxVal; + + vusbDevRelease(pDev, "vusbRhUpdateIsocFrameDelta"); + return (uint16_t)uFrameDelta; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevReset} */ +static DECLCALLBACK(int) vusbR3RhDevReset(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevReset"); + AssertPtrReturn(pDev, VERR_VUSB_DEVICE_NOT_ATTACHED); + + int rc = VUSBIDevReset(&pDev->IDevice, fResetOnLinux, pfnDone, pvUser, pVM); + vusbDevRelease(pDev, "vusbR3RhDevReset"); + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOn} */ +static DECLCALLBACK(int) vusbR3RhDevPowerOn(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOn"); + AssertPtr(pDev); + + int rc = VUSBIDevPowerOn(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevPowerOn"); + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOff} */ +static DECLCALLBACK(int) vusbR3RhDevPowerOff(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOff"); + AssertPtr(pDev); + + int rc = VUSBIDevPowerOff(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevPowerOff"); + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetState} */ +static DECLCALLBACK(VUSBDEVICESTATE) vusbR3RhDevGetState(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetState"); + AssertPtr(pDev); + + VUSBDEVICESTATE enmState = VUSBIDevGetState(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevGetState"); + return enmState; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevIsSavedStateSupported} */ +static DECLCALLBACK(bool) vusbR3RhDevIsSavedStateSupported(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevIsSavedStateSupported"); + AssertPtr(pDev); + + bool fSavedStateSupported = VUSBIDevIsSavedStateSupported(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevIsSavedStateSupported"); + return fSavedStateSupported; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetSpeed} */ +static DECLCALLBACK(VUSBSPEED) vusbR3RhDevGetSpeed(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetSpeed"); + AssertPtr(pDev); + + VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice); + vusbDevRelease(pDev, "vusbR3RhDevGetSpeed"); + return enmSpeed; +} + + +/** + * @callback_method_impl{FNSSMDRVSAVEPREP, All URBs needs to be canceled.} + */ +static DECLCALLBACK(int) vusbR3RhSavePrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + LogFlow(("vusbR3RhSavePrep:\n")); + RT_NOREF(pSSM); + + /* + * Detach all proxied devices. + */ + RTCritSectEnter(&pThis->CritSectDevices); + + /** @todo we a) can't tell which are proxied, and b) this won't work well when continuing after saving! */ + for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev) + { + if (!VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + { + int rc = vusbHubDetach(pThis, pDev); + AssertRC(rc); + + /* + * Save the device pointers here so we can reattach them afterwards. + * This will work fine even if the save fails since the Done handler is + * called unconditionally if the Prep handler was called. + */ + pThis->apDevByPort[i] = pDev; + } + } + } + + RTCritSectLeave(&pThis->CritSectDevices); + + /* + * Kill old load data which might be hanging around. + */ + if (pThis->pLoad) + { + PDMDrvHlpTimerDestroy(pDrvIns, pThis->pLoad->hTimer); + pThis->pLoad->hTimer = NIL_TMTIMERHANDLE; + PDMDrvHlpMMHeapFree(pDrvIns, pThis->pLoad); + pThis->pLoad = NULL; + } + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDRVSAVEDONE} + */ +static DECLCALLBACK(int) vusbR3RhSaveDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PVUSBDEV aPortsOld[VUSB_DEVICES_MAX]; + unsigned i; + LogFlow(("vusbR3RhSaveDone:\n")); + RT_NOREF(pSSM); + + /* Save the current data. */ + memcpy(aPortsOld, pThis->apDevByPort, sizeof(aPortsOld)); + AssertCompile(sizeof(aPortsOld) == sizeof(pThis->apDevByPort)); + + /* + * NULL the dev pointers. + */ + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + if (pThis->apDevByPort[i] && !VUSBIDevIsSavedStateSupported(&pThis->apDevByPort[i]->IDevice)) + pThis->apDevByPort[i] = NULL; + + /* + * Attach the devices. + */ + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = aPortsOld[i]; + if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + vusbHubAttach(pThis, pDev); + } + + return VINF_SUCCESS; +} + + +/** + * @callback_method_impl{FNSSMDRVLOADPREP, This must detach the devices + * currently attached and save them for reconnect after the state load has been + * completed.} + */ +static DECLCALLBACK(int) vusbR3RhLoadPrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + int rc = VINF_SUCCESS; + LogFlow(("vusbR3RhLoadPrep:\n")); + RT_NOREF(pSSM); + + if (!pThis->pLoad) + { + VUSBROOTHUBLOAD Load; + unsigned i; + + /// @todo This is all bogus. + /* + * Detach all devices which are present in this session. Save them in the load + * structure so we can reattach them after restoring the guest. + */ + Load.hTimer = NIL_TMTIMERHANDLE; + Load.cDevs = 0; + for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++) + { + PVUSBDEV pDev = pThis->apDevByPort[i]; + if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice)) + { + Load.apDevs[Load.cDevs++] = pDev; + vusbHubDetach(pThis, pDev); + Assert(!pThis->apDevByPort[i]); + } + } + + /* + * Any devices to reattach? If so, duplicate the Load struct. + */ + if (Load.cDevs) + { + pThis->pLoad = (PVUSBROOTHUBLOAD)RTMemAllocZ(sizeof(Load)); + if (!pThis->pLoad) + return VERR_NO_MEMORY; + *pThis->pLoad = Load; + } + } + /* else: we ASSUME no device can be attached or detached in the time + * between a state load and the pLoad stuff processing. */ + return rc; +} + + +/** + * Reattaches devices after a saved state load. + */ +static DECLCALLBACK(void) vusbR3RhLoadReattachDevices(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PVUSBROOTHUBLOAD pLoad = pThis->pLoad; + LogFlow(("vusbR3RhLoadReattachDevices:\n")); + Assert(hTimer == pLoad->hTimer); RT_NOREF(pvUser); + + /* + * Reattach devices. + */ + for (unsigned i = 0; i < pLoad->cDevs; i++) + vusbHubAttach(pThis, pLoad->apDevs[i]); + + /* + * Cleanup. + */ + PDMDrvHlpTimerDestroy(pDrvIns, hTimer); + pLoad->hTimer = NIL_TMTIMERHANDLE; + RTMemFree(pLoad); + pThis->pLoad = NULL; +} + + +/** + * @callback_method_impl{FNSSMDRVLOADDONE} + */ +static DECLCALLBACK(int) vusbR3RhLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + LogFlow(("vusbR3RhLoadDone:\n")); + RT_NOREF(pSSM); + + /* + * Start a timer if we've got devices to reattach + */ + if (pThis->pLoad) + { + int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, vusbR3RhLoadReattachDevices, NULL, + TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0, + "VUSB reattach on load", &pThis->pLoad->hTimer); + if (RT_SUCCESS(rc)) + rc = PDMDrvHlpTimerSetMillies(pDrvIns, pThis->pLoad->hTimer, 250); + return rc; + } + + return VINF_SUCCESS; +} + + +/* -=-=-=-=-=- PDM Base interface methods -=-=-=-=-=- */ + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) vusbRhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBCONNECTOR, &pRh->IRhConnector); + return NULL; +} + + +/* -=-=-=-=-=- PDM Driver methods -=-=-=-=-=- */ + + +/** + * Destruct a driver instance. + * + * Most VM resources are freed by the VM. This callback is provided so that any non-VM + * resources can be freed correctly. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) vusbRhDestruct(PPDMDRVINS pDrvIns) +{ + PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + vusbUrbPoolDestroy(&pRh->UrbPool); + if (pRh->pszName) + { + RTStrFree(pRh->pszName); + pRh->pszName = NULL; + } + if (pRh->hSniffer != VUSBSNIFFER_NIL) + VUSBSnifferDestroy(pRh->hSniffer); + + if (pRh->hSemEventPeriodFrame) + RTSemEventMultiDestroy(pRh->hSemEventPeriodFrame); + + if (pRh->hSemEventPeriodFrameStopped) + RTSemEventMultiDestroy(pRh->hSemEventPeriodFrameStopped); + + RTCritSectDelete(&pRh->CritSectDevices); +} + + +/** + * Construct a root hub driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) vusbRhConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3; + + LogFlow(("vusbRhConstruct: Instance %d\n", pDrvIns->iInstance)); + + /* + * Validate configuration. + */ + PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "CaptureFilename", ""); + + /* + * Check that there are no drivers below us. + */ + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Initialize the critical sections. + */ + int rc = RTCritSectInit(&pThis->CritSectDevices); + if (RT_FAILURE(rc)) + return rc; + + char *pszCaptureFilename = NULL; + rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "CaptureFilename", &pszCaptureFilename); + if ( RT_FAILURE(rc) + && rc != VERR_CFGM_VALUE_NOT_FOUND) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: Failed to query value of \"CaptureFilename\"")); + + /* + * Initialize the data members. + */ + pDrvIns->IBase.pfnQueryInterface = vusbRhQueryInterface; + /* the usb device */ + pThis->enmState = VUSB_DEVICE_STATE_ATTACHED; + //pThis->hub.cPorts - later + pThis->cDevices = 0; + RTStrAPrintf(&pThis->pszName, "RootHub#%d", pDrvIns->iInstance); + /* misc */ + pThis->pDrvIns = pDrvIns; + /* the connector */ + pThis->IRhConnector.pfnSetUrbParams = vusbRhSetUrbParams; + pThis->IRhConnector.pfnReset = vusbR3RhReset; + pThis->IRhConnector.pfnPowerOn = vusbR3RhPowerOn; + pThis->IRhConnector.pfnPowerOff = vusbR3RhPowerOff; + pThis->IRhConnector.pfnNewUrb = vusbRhConnNewUrb; + pThis->IRhConnector.pfnFreeUrb = vusbRhConnFreeUrb; + pThis->IRhConnector.pfnSubmitUrb = vusbRhSubmitUrb; + pThis->IRhConnector.pfnReapAsyncUrbs = vusbRhReapAsyncUrbs; + pThis->IRhConnector.pfnCancelUrbsEp = vusbRhCancelUrbsEp; + pThis->IRhConnector.pfnCancelAllUrbs = vusbRhCancelAllUrbs; + pThis->IRhConnector.pfnAbortEp = vusbRhAbortEp; + pThis->IRhConnector.pfnSetPeriodicFrameProcessing = vusbRhSetFrameProcessing; + pThis->IRhConnector.pfnGetPeriodicFrameRate = vusbRhGetPeriodicFrameRate; + pThis->IRhConnector.pfnUpdateIsocFrameDelta = vusbRhUpdateIsocFrameDelta; + pThis->IRhConnector.pfnDevReset = vusbR3RhDevReset; + pThis->IRhConnector.pfnDevPowerOn = vusbR3RhDevPowerOn; + pThis->IRhConnector.pfnDevPowerOff = vusbR3RhDevPowerOff; + pThis->IRhConnector.pfnDevGetState = vusbR3RhDevGetState; + pThis->IRhConnector.pfnDevIsSavedStateSupported = vusbR3RhDevIsSavedStateSupported; + pThis->IRhConnector.pfnDevGetSpeed = vusbR3RhDevGetSpeed; + pThis->hSniffer = VUSBSNIFFER_NIL; + pThis->cbHci = 0; + pThis->cbHciTd = 0; + pThis->fFrameProcessing = false; +#ifdef LOG_ENABLED + pThis->iSerial = 0; +#endif + /* + * Resolve interface(s). + */ + pThis->pIRhPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, VUSBIROOTHUBPORT); + AssertMsgReturn(pThis->pIRhPort, ("Configuration error: the device/driver above us doesn't expose any VUSBIROOTHUBPORT interface!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + + /* + * Get number of ports and the availability bitmap. + * ASSUME that the number of ports reported now at creation time is the max number. + */ + pThis->cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap); + Log(("vusbRhConstruct: cPorts=%d\n", pThis->cPorts)); + + /* + * Get the USB version of the attached HC. + * ASSUME that version 2.0 implies high-speed. + */ + pThis->fHcVersions = pThis->pIRhPort->pfnGetUSBVersions(pThis->pIRhPort); + Log(("vusbRhConstruct: fHcVersions=%u\n", pThis->fHcVersions)); + + rc = vusbUrbPoolInit(&pThis->UrbPool); + if (RT_FAILURE(rc)) + return rc; + + if (pszCaptureFilename) + { + rc = VUSBSnifferCreate(&pThis->hSniffer, 0, pszCaptureFilename, NULL, NULL); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("VUSBSniffer cannot open '%s' for writing. The directory must exist and it must be writable for the current user"), + pszCaptureFilename); + + PDMDrvHlpMMHeapFree(pDrvIns, pszCaptureFilename); + } + + /* + * Register ourselves as a USB hub. + * The current implementation uses the VUSBIRHCONFIG interface for communication. + */ + PCPDMUSBHUBHLP pHlpUsb; /* not used currently */ + rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->cPorts, &g_vusbHubReg, &pHlpUsb); + if (RT_FAILURE(rc)) + return rc; + + /* + * Register the saved state data unit for attaching devices. + */ + rc = PDMDrvHlpSSMRegisterEx(pDrvIns, VUSB_ROOTHUB_SAVED_STATE_VERSION, 0, + NULL, NULL, NULL, + vusbR3RhSavePrep, NULL, vusbR3RhSaveDone, + vusbR3RhLoadPrep, NULL, vusbR3RhLoadDone); + AssertRCReturn(rc, rc); + + /* + * Statistics. (It requires a 30" monitor or extremely tiny fonts to edit this "table".) + */ +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs submitted.", "/VUSB/%d/UrbsSubmitted", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsSubmitted/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsSubmitted/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsSubmitted/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsSubmitted/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs cancelled. (included in failed)", "/VUSB/%d/UrbsCancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsCancelled/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsCancelled/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsCancelled/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsCancelled/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs failing.", "/VUSB/%d/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsFailed/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsFailed/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsFailed/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsFailed/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested transfer.", "/VUSB/%d/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested read transfer.", "/VUSB/%d/ReqReadBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqReadBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqReadBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqReadBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqReadBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested write transfer.", "/VUSB/%d/ReqWriteBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqWriteBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqWriteBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqWriteBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqWriteBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total transfer.", "/VUSB/%d/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total read transfer.", "/VUSB/%d/ActReadBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActReadBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActReadBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActReadBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActReadBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total write transfer.", "/VUSB/%d/ActWriteBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActWriteBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActWriteBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActWriteBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActWriteBytes/Isoc", pDrvIns->iInstance); + + /* bulk */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Bulk/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Bulk/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Bulk/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Bulk/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Bulk/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ReqBytes/Write", pDrvIns->iInstance); + + /* control */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Ctrl/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Ctrl/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Ctrl/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Ctrl/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Ctrl/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ReqBytes/Write", pDrvIns->iInstance); + + /* interrupt */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Intr/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Intr/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Intr/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Intr/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Intr/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ReqBytes/Write", pDrvIns->iInstance); + + /* isochronous */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Isoc/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Isoc/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Isoc/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Isoc/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Isoc/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ReqBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of isochronous packets returning data.", "/VUSB/%d/Isoc/ActPkts", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ActPkts/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ActPkts/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Requested number of isochronous packets.", "/VUSB/%d/Isoc/ReqPkts", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ReqPkts/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ReqPkts/Write", pDrvIns->iInstance); + + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aStatIsocDetails); i++) + { + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Pkts, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok0", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun0", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataOverrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataOverrun", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].NotAccessed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/NotAccessed", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Misc, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Misc", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Bytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, ".", "/VUSB/%d/Isoc/%d/Bytes", pDrvIns->iInstance, i); + } + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReapAsyncUrbs, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling the vusbRhReapAsyncUrbs body (omitting calls when nothing is in-flight).", + "/VUSB/%d/ReapAsyncUrbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatSubmitUrb, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling the vusbRhSubmitUrb body.", + "/VUSB/%d/SubmitUrb", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedThread, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the dedicated thread", + "/VUSB/%d/FramesProcessedThread", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedClbk, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the URB completion callback", + "/VUSB/%d/FramesProcessedClbk", pDrvIns->iInstance); +#endif + PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->UrbPool.cUrbsInPool, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs in the pool.", + "/VUSB/%d/cUrbsInPool", pDrvIns->iInstance); + + return VINF_SUCCESS; +} + + +/** + * VUSB Root Hub driver registration record. + */ +const PDMDRVREG g_DrvVUSBRootHub = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "VUSBRootHub", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "VUSB Root Hub Driver.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_USB, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(VUSBROOTHUB), + /* pfnConstruct */ + vusbRhConstruct, + /* pfnDestruct */ + vusbRhDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/Makefile.kup b/src/VBox/Devices/USB/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/Makefile.kup diff --git a/src/VBox/Devices/USB/USBProxyDevice-stub.cpp b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp new file mode 100644 index 00000000..9f8dd353 --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp @@ -0,0 +1,61 @@ +/* $Id: USBProxyDevice-stub.cpp $ */ +/** @file + * USB device proxy - Stub. + */ + +/* + * Copyright (C) 2008-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/vmm/pdm.h> + +#include "USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Stub USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + "host", + 0, /* cbBackend */ + NULL, /* Open */ + NULL, /* Init */ + NULL, /* Close */ + NULL, /* Reset */ + NULL, /* SetConfig */ + NULL, /* ClaimInterface */ + NULL, /* ReleaseInterface */ + NULL, /* SetInterface */ + NULL, /* ClearHaltedEp */ + NULL, /* UrbQueue */ + NULL, /* UrbCancel */ + NULL, /* UrbReap */ + 0 +}; + diff --git a/src/VBox/Devices/USB/USBProxyDevice.cpp b/src/VBox/Devices/USB/USBProxyDevice.cpp new file mode 100644 index 00000000..66c1452b --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice.cpp @@ -0,0 +1,1328 @@ +/* $Id: USBProxyDevice.cpp $ */ +/** @file + * USBProxy - USB device proxy. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <VBox/usb.h> +#include <VBox/usbfilter.h> +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include "USBProxyDevice.h" +#include "VUSBInternal.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** A dummy name used early during the construction phase to avoid log crashes. */ +static char g_szDummyName[] = "proxy xxxx:yyyy"; + +/** + * Array of supported proxy backends. + */ +static PCUSBPROXYBACK g_aUsbProxies[] = +{ + &g_USBProxyDeviceHost, + &g_USBProxyDeviceVRDP, + &g_USBProxyDeviceUsbIp +}; + +/* Synchronously obtain a standard USB descriptor for a device, used in order + * to grab configuration descriptors when we first add the device + */ +static void *GetStdDescSync(PUSBPROXYDEV pProxyDev, uint8_t iDescType, uint8_t iIdx, uint16_t LangId, uint16_t cbHint) +{ +#define GET_DESC_RETRIES 6 + int cRetries = 0; + uint16_t cbInitialHint = cbHint; + + LogFlow(("GetStdDescSync: pProxyDev=%s, iDescType=%d, iIdx=%d, LangId=%04X, cbHint=%u\n", pProxyDev->pUsbIns->pszName, iDescType, iIdx, LangId, cbHint)); + for (;;) + { + /* + * Setup a MSG URB, queue and reap it. + */ + int rc = VINF_SUCCESS; + VUSBURB Urb; + AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K); + RT_ZERO(Urb); + Urb.u32Magic = VUSBURB_MAGIC; + Urb.enmState = VUSBURBSTATE_IN_FLIGHT; + Urb.pszDesc = (char*)"URB sync"; + Urb.DstAddress = 0; + Urb.EndPt = 0; + Urb.enmType = VUSBXFERTYPE_MSG; + Urb.enmDir = VUSBDIRECTION_IN; + Urb.fShortNotOk = false; + Urb.enmStatus = VUSBSTATUS_INVALID; + cbHint = RT_MIN(cbHint, sizeof(Urb.abData) - sizeof(VUSBSETUP)); + Urb.cbData = cbHint + sizeof(VUSBSETUP); + + PVUSBSETUP pSetup = (PVUSBSETUP)Urb.abData; + pSetup->bmRequestType = VUSB_DIR_TO_HOST | VUSB_REQ_STANDARD | VUSB_TO_DEVICE; + pSetup->bRequest = VUSB_REQ_GET_DESCRIPTOR; + pSetup->wValue = (iDescType << 8) | iIdx; + pSetup->wIndex = LangId; + pSetup->wLength = cbHint; + + uint8_t *pbDesc = (uint8_t *)(pSetup + 1); + uint32_t cbDesc = 0; + PVUSBURB pUrbReaped = NULL; + + rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, &Urb); + if (RT_FAILURE(rc)) + { + Log(("GetStdDescSync: pfnUrbQueue failed, rc=%d\n", rc)); + goto err; + } + + /* Don't wait forever, it's just a simple request that should + return immediately. Since we're executing in the EMT thread + it's important not to get stuck here. (Some of the builtin + iMac devices may refuse to respond for instance.) */ + pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 5000 /* ms */); + if (!pUrbReaped) + { + Log(("GetStdDescSync: pfnUrbReap returned NULL, cancel and re-reap\n")); + rc = pProxyDev->pOps->pfnUrbCancel(pProxyDev, &Urb); + AssertRC(rc); + /** @todo This breaks the comment above... */ + pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT); + } + if (pUrbReaped != &Urb) + { + Log(("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped)); + goto err; + } + + if (Urb.enmStatus != VUSBSTATUS_OK) + { + Log(("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus)); + goto err; + } + + /* + * Check the length, config descriptors have total_length field + */ + if (iDescType == VUSB_DT_CONFIG) + { + if (Urb.cbData < sizeof(VUSBSETUP) + 4) + { + Log(("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData)); + goto err; + } + cbDesc = RT_LE2H_U16(((uint16_t *)pbDesc)[1]); + } + else + { + if (Urb.cbData < sizeof(VUSBSETUP) + 1) + { + Log(("GetStdDescSync: Urb.cbData=%#x (min 1)\n", Urb.cbData)); + goto err; + } + cbDesc = ((uint8_t *)pbDesc)[0]; + } + + Log(("GetStdDescSync: got Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint)); + + if ( Urb.cbData == cbHint + sizeof(VUSBSETUP) + && cbDesc > Urb.cbData - sizeof(VUSBSETUP)) + { + cbHint = cbDesc; + Log(("GetStdDescSync: Part descriptor, Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint)); + + if (cbHint > sizeof(Urb.abData)) + { + Log(("GetStdDescSync: cbHint=%u, Urb.abData=%u, retrying immediately\n", cbHint, sizeof(Urb.abData))); + /* Not an error, go again without incrementing retry count or delaying. */ + continue; + } + + goto err; + } + + if (cbDesc > Urb.cbData - sizeof(VUSBSETUP)) + { + Log(("GetStdDescSync: Descriptor length too short, cbDesc=%u, Urb.cbData=%u\n", cbDesc, Urb.cbData)); + goto err; + } + + if ( cbInitialHint != cbHint + && ( cbDesc != cbHint + || Urb.cbData < cbInitialHint) ) + { + Log(("GetStdDescSync: Descriptor length incorrect, cbDesc=%u, Urb.cbData=%u, cbHint=%u\n", cbDesc, Urb.cbData, cbHint)); + goto err; + } + +#ifdef LOG_ENABLED + vusbUrbTrace(&Urb, "GetStdDescSync", true); +#endif + + /* + * Fine, we got everything return a heap duplicate of the descriptor. + */ + return RTMemDup(pbDesc, cbDesc); + +err: + cRetries++; + if (cRetries < GET_DESC_RETRIES) + { + Log(("GetStdDescSync: Retrying %u/%u\n", cRetries, GET_DESC_RETRIES)); + RTThreadSleep(100); + continue; + } + else + { + Log(("GetStdDescSync: Retries exceeded %u/%u. Giving up.\n", cRetries, GET_DESC_RETRIES)); + break; + } + } + + return NULL; +} + +/** + * Frees a descriptor returned by GetStdDescSync(). + */ +static void free_desc(void *pvDesc) +{ + RTMemFree(pvDesc); +} + +/** + * Get and a device descriptor and byteswap it appropriately. + */ +static bool usbProxyGetDeviceDesc(PUSBPROXYDEV pProxyDev, PVUSBDESCDEVICE pOut) +{ + /* + * Get the descriptor from the device. + */ + PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN); + if (!pIn) + { + Log(("usbProxyGetDeviceDesc: pProxyDev=%s: GetStdDescSync failed\n", pProxyDev->pUsbIns->pszName)); + return false; + } + if (pIn->bLength < VUSB_DT_DEVICE_MIN_LEN) + { + Log(("usb-proxy: pProxyDev=%s: Corrupted device descriptor. bLength=%d\n", pProxyDev->pUsbIns->pszName, pIn->bLength)); + return false; + } + + /* + * Convert it. + */ + pOut->bLength = VUSB_DT_DEVICE_MIN_LEN; + pOut->bDescriptorType = VUSB_DT_DEVICE; + pOut->bcdUSB = RT_LE2H_U16(pIn->bcdUSB); + pOut->bDeviceClass = pIn->bDeviceClass; + pOut->bDeviceSubClass = pIn->bDeviceSubClass; + pOut->bDeviceProtocol = pIn->bDeviceProtocol; + pOut->bMaxPacketSize0 = pIn->bMaxPacketSize0; + pOut->idVendor = RT_LE2H_U16(pIn->idVendor); + pOut->idProduct = RT_LE2H_U16(pIn->idProduct); + pOut->bcdDevice = RT_LE2H_U16(pIn->bcdDevice); + pOut->iManufacturer = pIn->iManufacturer; + pOut->iProduct = pIn->iProduct; + pOut->iSerialNumber = pIn->iSerialNumber; + pOut->bNumConfigurations = pIn->bNumConfigurations; + + free_desc(pIn); + return true; +} + +/** + * Count the numbers and types of each kind of descriptor that we need to + * copy out of the config descriptor + */ +struct desc_counts +{ + size_t num_ed, num_id, num_if; + /** bitmap (128 bits) */ + uint32_t idmap[4]; +}; + +static int count_descriptors(struct desc_counts *cnt, uint8_t *buf, size_t len) +{ + PVUSBDESCCONFIG cfg; + uint8_t *tmp, *end; + uint32_t i, x; + + memset(cnt, 0, sizeof(*cnt)); + + end = buf + len; + + cfg = (PVUSBDESCCONFIG)buf; + if ( cfg->bLength < VUSB_DT_CONFIG_MIN_LEN ) + return 0; + if ( cfg->bLength > len ) + return 0; + + for (tmp = buf + cfg->bLength; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + uint8_t type; + uint32_t ifnum; + PVUSBDESCINTERFACE id; + PVUSBDESCENDPOINT ed; + + type = *(tmp + 1); + + switch ( type ) { + case VUSB_DT_INTERFACE: + id = (PVUSBDESCINTERFACE)tmp; + if ( id->bLength < VUSB_DT_INTERFACE_MIN_LEN ) + return 0; + cnt->num_id++; + ifnum = id->bInterfaceNumber; + cnt->idmap[ifnum >> 6] |= (1 << (ifnum & 0x1f)); + break; + case VUSB_DT_ENDPOINT: + ed = (PVUSBDESCENDPOINT)tmp; + if ( ed->bLength < VUSB_DT_ENDPOINT_MIN_LEN ) + return 0; + cnt->num_ed++; + break; + default: + break; + } + } + + /* count interfaces */ + for(i=0; i < RT_ELEMENTS(cnt->idmap); i++) + for(x=1; x; x<<=1) + if ( cnt->idmap[i] & x ) + cnt->num_if++; + + return 1; +} + +/* Given the pointer to a configuration/interface/endpoint descriptor, find any following + * non-standard (vendor or class) descriptors. + */ +static const void *collect_stray_bits(uint8_t *this_desc, uint8_t *end, uint16_t *cbExtra) +{ + uint8_t *tmp, *buf; + uint8_t type; + + Assert(*(this_desc + 1) == VUSB_DT_INTERFACE || *(this_desc + 1) == VUSB_DT_ENDPOINT || *(this_desc + 1) == VUSB_DT_CONFIG); + buf = this_desc; + + /* Skip the current configuration/interface/endpoint descriptor. */ + buf += *(uint8_t *)buf; + + /* Loop until we find another descriptor we understand. */ + for (tmp = buf; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + type = *(tmp + 1); + if (type == VUSB_DT_INTERFACE || type == VUSB_DT_ENDPOINT) + break; + } + *cbExtra = tmp - buf; + if (*cbExtra) + return buf; + else + return NULL; +} + +/* Setup a vusb_interface structure given some preallocated structures + * to use, (we counted them already) + */ +static int copy_interface(PVUSBINTERFACE pIf, uint8_t ifnum, + PVUSBDESCINTERFACEEX *id, PVUSBDESCENDPOINTEX *ed, + uint8_t *buf, size_t len) +{ + PVUSBDESCINTERFACEEX cur_if = NULL; + uint32_t altmap[4] = {0,}; + uint8_t *tmp, *end = buf + len; + uint8_t alt; + int state; + size_t num_ep = 0; + + buf += *(uint8_t *)buf; + + pIf->cSettings = 0; + pIf->paSettings = NULL; + + for (tmp = buf, state = 0; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + uint8_t type; + PVUSBDESCINTERFACE ifd; + PVUSBDESCENDPOINT epd; + PVUSBDESCENDPOINTEX cur_ep; + + type = tmp[1]; + + switch ( type ) { + case VUSB_DT_INTERFACE: + state = 0; + ifd = (PVUSBDESCINTERFACE)tmp; + + /* Ignoring this interface */ + if ( ifd->bInterfaceNumber != ifnum ) + break; + + /* Check we didn't see this alternate setting already + * because that will break stuff + */ + alt = ifd->bAlternateSetting; + if ( altmap[alt >> 6] & (1 << (alt & 0x1f)) ) + return 0; + altmap[alt >> 6] |= (1 << (alt & 0x1f)); + + cur_if = *id; + (*id)++; + if ( pIf->cSettings == 0 ) + pIf->paSettings = cur_if; + + memcpy(cur_if, ifd, sizeof(cur_if->Core)); + + /* Point to additional interface descriptor bytes, if any. */ + AssertCompile(sizeof(cur_if->Core) == VUSB_DT_INTERFACE_MIN_LEN); + if (cur_if->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN > 0) + cur_if->pvMore = tmp + VUSB_DT_INTERFACE_MIN_LEN; + else + cur_if->pvMore = NULL; + + cur_if->pvClass = collect_stray_bits(tmp, end, &cur_if->cbClass); + + pIf->cSettings++; + + state = 1; + num_ep = 0; + break; + case VUSB_DT_ENDPOINT: + if ( state == 0 ) + break; + + epd = (PVUSBDESCENDPOINT)tmp; + + cur_ep = *ed; + (*ed)++; + + if ( num_ep == 0 ) + cur_if->paEndpoints = cur_ep; + + if ( num_ep > cur_if->Core.bNumEndpoints ) + return 0; + + memcpy(cur_ep, epd, sizeof(cur_ep->Core)); + + /* Point to additional endpoint descriptor bytes, if any. */ + AssertCompile(sizeof(cur_ep->Core) == VUSB_DT_ENDPOINT_MIN_LEN); + if (cur_ep->Core.bLength - VUSB_DT_ENDPOINT_MIN_LEN > 0) + cur_ep->pvMore = tmp + VUSB_DT_ENDPOINT_MIN_LEN; + else + cur_ep->pvMore = NULL; + + cur_ep->pvClass = collect_stray_bits(tmp, end, &cur_ep->cbClass); + + cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize); + + num_ep++; + break; + default: + /* Skip unknown descriptors. */ + break; + } + } + + return 1; +} + +/** + * Copy all of a devices config descriptors, this is needed so that the USB + * core layer knows all about how to map the different functions on to the + * virtual USB bus. + */ +static bool copy_config(PUSBPROXYDEV pProxyDev, uint8_t idx, PVUSBDESCCONFIGEX out) +{ + PVUSBDESCCONFIG cfg; + PVUSBINTERFACE pIf; + PVUSBDESCINTERFACEEX ifd; + PVUSBDESCENDPOINTEX epd; + struct desc_counts cnt; + void *descs; + size_t tot_len; + size_t cbIface; + uint32_t i, x; + uint8_t *tmp, *end; + + descs = GetStdDescSync(pProxyDev, VUSB_DT_CONFIG, idx, 0, VUSB_DT_CONFIG_MIN_LEN); + if ( descs == NULL ) { + Log(("copy_config: GetStdDescSync failed\n")); + return false; + } + + cfg = (PVUSBDESCCONFIG)descs; + tot_len = RT_LE2H_U16(cfg->wTotalLength); + + if ( !count_descriptors(&cnt, (uint8_t *)descs, tot_len) ) { + Log(("copy_config: count_descriptors failed\n")); + goto err; + } + + if ( cfg->bNumInterfaces != cnt.num_if ) + Log(("usb-proxy: config%u: bNumInterfaces %u != %u\n", + idx, cfg->bNumInterfaces, cnt.num_if)); + + Log(("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n", + idx, tot_len, cnt.num_id, cnt.num_ed, cnt.num_if)); + + cbIface = cnt.num_if * sizeof(VUSBINTERFACE) + + cnt.num_id * sizeof(VUSBDESCINTERFACEEX) + + cnt.num_ed * sizeof(VUSBDESCENDPOINTEX); + out->paIfs = (PCVUSBINTERFACE)RTMemAllocZ(cbIface); + if ( out->paIfs == NULL ) { + free_desc(descs); + return false; + } + + /* Stash a pointer to the raw config descriptor; we may need bits of it later. */ + out->pvOriginal = descs; + + pIf = (PVUSBINTERFACE)out->paIfs; + ifd = (PVUSBDESCINTERFACEEX)&pIf[cnt.num_if]; + epd = (PVUSBDESCENDPOINTEX)&ifd[cnt.num_id]; + + out->Core.bLength = cfg->bLength; + out->Core.bDescriptorType = cfg->bDescriptorType; + out->Core.wTotalLength = 0; /* Auto Calculated */ + out->Core.bNumInterfaces = (uint8_t)cnt.num_if; + out->Core.bConfigurationValue = cfg->bConfigurationValue; + out->Core.iConfiguration = cfg->iConfiguration; + out->Core.bmAttributes = cfg->bmAttributes; + out->Core.MaxPower = cfg->MaxPower; + + tmp = (uint8_t *)out->pvOriginal; + end = tmp + tot_len; + + /* Point to additional configuration descriptor bytes, if any. */ + AssertCompile(sizeof(out->Core) == VUSB_DT_CONFIG_MIN_LEN); + if (out->Core.bLength - VUSB_DT_CONFIG_MIN_LEN > 0) + out->pvMore = tmp + VUSB_DT_CONFIG_MIN_LEN; + else + out->pvMore = NULL; + + /* Typically there might be an interface association descriptor here. */ + out->pvClass = collect_stray_bits(tmp, end, &out->cbClass); + + for(i=0; i < 4; i++) + for(x=0; x < 32; x++) + if ( cnt.idmap[i] & (1 << x) ) + if ( !copy_interface(pIf++, (i << 6) | x, &ifd, &epd, (uint8_t *)out->pvOriginal, tot_len) ) { + Log(("copy_interface(%d,,) failed\n", pIf - 1)); + goto err; + } + + return true; +err: + Log(("usb-proxy: config%u: Corrupted configuration descriptor\n", idx)); + free_desc(descs); + return false; +} + + +/** + * Edit out masked interface descriptors. + * + * @param pProxyDev The proxy device + */ +static void usbProxyDevEditOutMaskedIfs(PUSBPROXYDEV pProxyDev) +{ + unsigned cRemoved = 0; + + PVUSBDESCCONFIGEX paCfgs = pProxyDev->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pProxyDev->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + if ( paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber < 32 + && ((1 << paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber) & pProxyDev->fMaskedIfs)) + { + Log(("usb-proxy: removing interface #%d (iIf=%d iAlt=%d) on config #%d (iCfg=%d)\n", + paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber, iIf, iAlt, paCfgs[iCfg].Core.bConfigurationValue, iCfg)); + cRemoved++; + + paCfgs[iCfg].Core.bNumInterfaces--; + unsigned cToCopy = paCfgs[iCfg].Core.bNumInterfaces - iIf; + if (cToCopy) + memmove(&paIfs[iIf], &paIfs[iIf + 1], sizeof(paIfs[0]) * cToCopy); + memset(&paIfs[iIf + cToCopy], '\0', sizeof(paIfs[0])); + break; + } + } + + Log(("usb-proxy: edited out %d interface(s).\n", cRemoved)); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbReset} + * + * USB Device Proxy: Call OS specific code to reset the device. + */ +static DECLCALLBACK(int) usbProxyDevReset(PPDMUSBINS pUsbIns, bool fResetOnLinux) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + + if (pProxyDev->fMaskedIfs) + { + Log(("usbProxyDevReset: pProxyDev=%s - ignoring reset request fMaskedIfs=%#x\n", pUsbIns->pszName, pProxyDev->fMaskedIfs)); + return VINF_SUCCESS; + } + LogFlow(("usbProxyDevReset: pProxyDev=%s\n", pUsbIns->pszName)); + return pProxyDev->pOps->pfnReset(pProxyDev, fResetOnLinux); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache} + */ +static DECLCALLBACK(PCPDMUSBDESCCACHE) usbProxyDevGetDescriptorCache(PPDMUSBINS pUsbIns) +{ + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + return &pThis->DescCache; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration} + * + * USB Device Proxy: Release claimed interfaces, tell the OS+device about the config change, claim the new interfaces. + */ +static DECLCALLBACK(int) usbProxyDevSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue, + const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevSetConfiguration: pProxyDev=%s iActiveCfg=%d bConfigurationValue=%d\n", + pUsbIns->pszName, pProxyDev->iActiveCfg, bConfigurationValue)); + + /* + * Release the current config. + */ + if (pvOldCfgDesc) + { + PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)pvOldCfgDesc; + PCVUSBINTERFACESTATE pOldIfState = (PCVUSBINTERFACESTATE)pvOldIfState; + for (unsigned i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++) + if (pOldIfState[i].pCurIfDesc) + pProxyDev->pOps->pfnReleaseInterface(pProxyDev, pOldIfState[i].pCurIfDesc->Core.bInterfaceNumber); + } + + /* + * Do the actual SET_CONFIGURE. + * The mess here is because most backends will already have selected a + * configuration and there are a bunch of devices which will freak out + * if we do SET_CONFIGURE twice with the same value. (PalmOne, TrekStor USB-StickGO, ..) + * + * After open and reset the backend should use the members iActiveCfg and cIgnoreSetConfigs + * to indicate the new configuration state and what to do on the next SET_CONFIGURATION call. + */ + if ( pProxyDev->iActiveCfg != bConfigurationValue + || ( bConfigurationValue == 0 + && pProxyDev->iActiveCfg != -1 /* this test doesn't make sense, we know it's 0 */ + && pProxyDev->cIgnoreSetConfigs >= 2) + || !pProxyDev->cIgnoreSetConfigs) + { + pProxyDev->cIgnoreSetConfigs = 0; + int rc = pProxyDev->pOps->pfnSetConfig(pProxyDev, bConfigurationValue); + if (RT_FAILURE(rc)) + { + pProxyDev->iActiveCfg = -1; + return rc; + } + pProxyDev->iActiveCfg = bConfigurationValue; + } + else if (pProxyDev->cIgnoreSetConfigs > 0) + pProxyDev->cIgnoreSetConfigs--; + + /* + * Claim the interfaces. + */ + PCVUSBDESCCONFIGEX pNewCfgDesc = (PCVUSBDESCCONFIGEX)pvNewCfgDesc; + Assert(pNewCfgDesc->Core.bConfigurationValue == bConfigurationValue); + for (unsigned iIf = 0; iIf < pNewCfgDesc->Core.bNumInterfaces; iIf++) + { + PCVUSBINTERFACE pIf = &pNewCfgDesc->paIfs[iIf]; + for (uint32_t iAlt = 0; iAlt < pIf->cSettings; iAlt++) + { + if (pIf->paSettings[iAlt].Core.bAlternateSetting != 0) + continue; + pProxyDev->pOps->pfnClaimInterface(pProxyDev, pIf->paSettings[iAlt].Core.bInterfaceNumber); + /* ignore failures - the backend deals with that and does the necessary logging. */ + break; + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface} + * + * USB Device Proxy: Call OS specific code to select alternate interface settings. + */ +static DECLCALLBACK(int) usbProxyDevSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevSetInterface: pProxyDev=%s bInterfaceNumber=%d bAlternateSetting=%d\n", + pUsbIns->pszName, bInterfaceNumber, bAlternateSetting)); + + return pProxyDev->pOps->pfnSetInterface(pProxyDev, bInterfaceNumber, bAlternateSetting); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint} + * + * USB Device Proxy: Call OS specific code to clear the endpoint. + */ +static DECLCALLBACK(int) usbProxyDevClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevClearHaltedEndpoint: pProxyDev=%s uEndpoint=%u\n", + pUsbIns->pszName, uEndpoint)); + + return pProxyDev->pOps->pfnClearHaltedEndpoint(pProxyDev, uEndpoint); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbQueue} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevUrbQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, pUrb); + if (RT_FAILURE(rc)) + return pProxyDev->fDetached + ? VERR_VUSB_DEVICE_NOT_ATTACHED + : VERR_VUSB_FAILED_TO_QUEUE_URB; + return rc; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbCancel} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + return pProxyDev->pOps->pfnUrbCancel(pProxyDev, pUrb); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbReap} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(PVUSBURB) usbProxyDevUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + PVUSBURB pUrb = pProxyDev->pOps->pfnUrbReap(pProxyDev, cMillies); + if ( pUrb + && pUrb->enmState == VUSBURBSTATE_CANCELLED + && pUrb->enmStatus == VUSBSTATUS_OK) + pUrb->enmStatus = VUSBSTATUS_DNR; + return pUrb; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnWakeup} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevWakeup(PPDMUSBINS pUsbIns) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + + return pProxyDev->pOps->pfnWakeup(pProxyDev); +} + + +/** @interface_method_impl{PDMUSBREG,pfnDestruct} */ +static DECLCALLBACK(void) usbProxyDestruct(PPDMUSBINS pUsbIns) +{ + PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns); + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + Log(("usbProxyDestruct: destroying pProxyDev=%s\n", pUsbIns->pszName)); + + /* close it. */ + if (pThis->fOpened) + { + pThis->pOps->pfnClose(pThis); + pThis->fOpened = false; + } + + /* free the config descriptors. */ + if (pThis->paCfgDescs) + { + for (unsigned i = 0; i < pThis->DevDesc.bNumConfigurations; i++) + { + RTMemFree((void *)pThis->paCfgDescs[i].paIfs); + RTMemFree((void *)pThis->paCfgDescs[i].pvOriginal); + } + RTMemFree(pThis->paCfgDescs); + pThis->paCfgDescs = NULL; + } + + /* free dev */ + if (&g_szDummyName[0] != pUsbIns->pszName) + RTStrFree(pUsbIns->pszName); + pUsbIns->pszName = NULL; + + if (pThis->pvInstanceDataR3) + RTMemFree(pThis->pvInstanceDataR3); +} + + +/** + * Helper function used by usbProxyConstruct when + * reading a filter from CFG. + * + * @returns VBox status code. + * @param pFilter The filter. + * @param enmFieldIdx The filter field indext. + * @param pHlp The USB helper callback table. + * @param pNode The CFGM node. + * @param pszExact The exact value name. + * @param pszExpr The expression value name. + */ +static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, + PCPDMUSBHLP pHlp, PCFGMNODE pNode, + const char *pszExact, const char *pszExpr) +{ + char szTmp[256]; + + /* try exact first */ + uint16_t u16; + int rc = pHlp->pfnCFGMQueryU16(pNode, pszExact, &u16); + if (RT_SUCCESS(rc)) + { + rc = USBFilterSetNumExact(pFilter, enmFieldIdx, u16, true); + AssertRCReturn(rc, rc); + + /* make sure only the exact attribute is present. */ + rc = pHlp->pfnCFGMQueryString(pNode, pszExpr, szTmp, sizeof(szTmp)); + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: Both %s and %s are present!\n", szTmp, pszExact, pszExpr)); + return VERR_INVALID_PARAMETER; + } + return VINF_SUCCESS; + } + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc)); + return rc; + } + + /* expression? */ + rc = pHlp->pfnCFGMQueryString(pNode, pszExpr, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(rc)) + { + rc = USBFilterSetNumExpression(pFilter, enmFieldIdx, szTmp, true); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; + } + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExpr, rc)); + return rc; + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{PDMUSBREG,pfnConstruct} */ +static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal) +{ + PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns); + RT_NOREF(iInstance); + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + PCPDMUSBHLP pHlp = pUsbIns->pHlpR3; + + LogFlow(("usbProxyConstruct: pUsbIns=%p iInstance=%d\n", pUsbIns, iInstance)); + + /* + * Initialize the instance data. + */ + pThis->pUsbIns = pUsbIns; + pThis->pUsbIns->pszName = g_szDummyName; + pThis->iActiveCfg = -1; + pThis->fMaskedIfs = 0; + pThis->fOpened = false; + pThis->fInited = false; + + /* + * Read the basic configuration. + */ + char szAddress[1024]; + int rc = pHlp->pfnCFGMQueryString(pCfg, "Address", szAddress, sizeof(szAddress)); + AssertRCReturn(rc, rc); + + char szBackend[64]; + rc = pHlp->pfnCFGMQueryString(pCfg, "Backend", szBackend, sizeof(szBackend)); + AssertRCReturn(rc, rc); + + /* + * Select backend and open the device. + */ + rc = VERR_NOT_FOUND; + for (unsigned i = 0; i < RT_ELEMENTS(g_aUsbProxies); i++) + { + if (!RTStrICmp(szBackend, g_aUsbProxies[i]->pszName)) + { + pThis->pOps = g_aUsbProxies[i]; + rc = VINF_SUCCESS; + break; + } + } + if (RT_FAILURE(rc)) + return PDMUSB_SET_ERROR(pUsbIns, rc, N_("USBProxy: Failed to find backend")); + + pThis->pvInstanceDataR3 = RTMemAllocZ(pThis->pOps->cbBackend); + if (!pThis->pvInstanceDataR3) + return PDMUSB_SET_ERROR(pUsbIns, VERR_NO_MEMORY, N_("USBProxy: can't allocate memory for host backend")); + + rc = pThis->pOps->pfnOpen(pThis, szAddress); + if (RT_FAILURE(rc)) + { + LogRel(("usbProxyConstruct: Failed to open '%s', rc=%Rrc\n", szAddress, rc)); + return rc; + } + pThis->fOpened = true; + + /* + * Get the device descriptor and format the device name (for logging). + */ + if (!usbProxyGetDeviceDesc(pThis, &pThis->DevDesc)) + { + Log(("usbProxyConstruct: usbProxyGetDeviceDesc failed\n")); + return VERR_READ_ERROR; + } + + RTStrAPrintf(&pUsbIns->pszName, "%p[proxy %04x:%04x]", pThis, pThis->DevDesc.idVendor, pThis->DevDesc.idProduct); /** @todo append the user comment */ + AssertReturn(pUsbIns->pszName, VERR_NO_MEMORY); + + /* + * Get config descriptors. + */ + size_t cbConfigs = pThis->DevDesc.bNumConfigurations * sizeof(pThis->paCfgDescs[0]); + pThis->paCfgDescs = (PVUSBDESCCONFIGEX)RTMemAllocZ(cbConfigs); + AssertReturn(pThis->paCfgDescs, VERR_NO_MEMORY); + + unsigned i; + for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++) + if (!copy_config(pThis, i, (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i])) + break; + if (i < pThis->DevDesc.bNumConfigurations) + { + Log(("usbProxyConstruct: copy_config failed, i=%d\n", i)); + return VERR_READ_ERROR; + } + + /* + * Pickup best matching global configuration for this device. + * The global configuration is organized like this: + * + * GlobalConfig/Whatever/ + * |- idVendor = 300 + * |- idProduct = 300 + * - Config/ + * + * The first level contains filter attributes which we stuff into a USBFILTER + * structure and match against the device info that's available. The highest + * ranked match is will be used. If nothing is found, the values will be + * queried from the GlobalConfig node (simplifies code and might actually + * be useful). + */ + PCFGMNODE pCfgGlobalDev = pCfgGlobal; + PCFGMNODE pCur = pHlp->pfnCFGMGetFirstChild(pCfgGlobal); + if (pCur) + { + /* + * Create a device filter from the device configuration + * descriptor ++. No strings currently. + */ + USBFILTER Device; + USBFilterInit(&Device, USBFILTERTYPE_CAPTURE); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_VENDOR_ID, pThis->DevDesc.idVendor, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_PRODUCT_ID, pThis->DevDesc.idProduct, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_REV, pThis->DevDesc.bcdDevice, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_CLASS, pThis->DevDesc.bDeviceClass, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_SUB_CLASS, pThis->DevDesc.bDeviceSubClass, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_PROTOCOL, pThis->DevDesc.bDeviceProtocol, true); AssertRC(rc); + /** @todo manufacturer, product and serial strings */ + + int iBestMatchRate = -1; + PCFGMNODE pBestMatch = NULL; + for (pCur = pHlp->pfnCFGMGetFirstChild(pCfgGlobal); pCur; pCur = pHlp->pfnCFGMGetNextChild(pCur)) + { + /* + * Construct a filter from the attributes in the node. + */ + USBFILTER Filter; + USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE); + + /* numeric */ + if ( RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_VENDOR_ID, pHlp, pCur, "idVendor", "idVendorExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pHlp, pCur, "idProduct", "idProcutExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pHlp, pCur, "bcdDevice", "bcdDeviceExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pHlp, pCur, "bDeviceClass", "bDeviceClassExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pHlp, pCur, "bDeviceSubClass", "bDeviceSubClassExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pHlp, pCur, "bDeviceProtocol", "bDeviceProtocolExpr"))) + continue; /* skip it */ + + /* strings */ + /** @todo manufacturer, product and serial strings */ + + /* ignore unknown config values, but not without bitching. */ + if (!pHlp->pfnCFGMAreValuesValid(pCur, + "idVendor\0idVendorExpr\0" + "idProduct\0idProductExpr\0" + "bcdDevice\0bcdDeviceExpr\0" + "bDeviceClass\0bDeviceClassExpr\0" + "bDeviceSubClass\0bDeviceSubClassExpr\0" + "bDeviceProtocol\0bDeviceProtocolExpr\0")) + LogRel(("usbProxyConstruct: Unknown value(s) in config filter (ignored)!\n")); + + /* + * Try match it and on match see if it has is a higher rate hit + * than the previous match. Quit if its a 100% match. + */ + int iRate = USBFilterMatchRated(&Filter, &Device); + if (iRate > iBestMatchRate) + { + pBestMatch = pCur; + iBestMatchRate = iRate; + if (iRate >= 100) + break; + } + } + if (pBestMatch) + pCfgGlobalDev = pHlp->pfnCFGMGetChild(pBestMatch, "Config"); + if (pCfgGlobalDev) + pCfgGlobalDev = pCfgGlobal; + } + + /* + * Query the rest of the configuration using the global as fallback. + */ + rc = pHlp->pfnCFGMQueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = pHlp->pfnCFGMQueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + pThis->fMaskedIfs = 0; + else + AssertRCReturn(rc, rc); + + bool fForce11Device; + rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11Device", &fForce11Device); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fForce11Device = false; + else + AssertRCReturn(rc, rc); + + bool fForce11PacketSize; + rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fForce11PacketSize = false; + else + AssertRCReturn(rc, rc); + + bool fEditAudioSyncEp; + rc = pHlp->pfnCFGMQueryBool(pCfg, "EditAudioSyncEp", &fEditAudioSyncEp); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditAudioSyncEp", &fEditAudioSyncEp); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fEditAudioSyncEp = true; /* NB: On by default! */ + else + AssertRCReturn(rc, rc); + + bool fEditRemoteWake; + rc = pHlp->pfnCFGMQueryBool(pCfg, "EditRemoteWake", &fEditRemoteWake); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditRemoteWake", &fEditRemoteWake); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fEditRemoteWake = true; /* NB: On by default! */ + else + AssertRCReturn(rc, rc); + + /* + * If we're masking interfaces, edit the descriptors. + */ + bool fEdited = pThis->fMaskedIfs != 0; + if (pThis->fMaskedIfs) + usbProxyDevEditOutMaskedIfs(pThis); + + /* + * Do 2.0 -> 1.1 device edits if requested to do so. + */ + if ( fForce11PacketSize + && pThis->DevDesc.bcdUSB >= 0x0200) + { + PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + { + /* + * USB 1.1 defines the max for control, interrupt and bulk to be 64 bytes. + * While isochronous has a max of 1023 bytes. + */ + PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints; + if (!paEps) + continue; + + for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++) + { + const uint16_t cbMax = (paEps[iEp].Core.bmAttributes & 3) == 1 /* isoc */ + ? 1023 + : 64; + if (paEps[iEp].Core.wMaxPacketSize > cbMax) + { + Log(("usb-proxy: pProxyDev=%s correcting wMaxPacketSize from %#x to %#x (mainly for vista)\n", + pUsbIns->pszName, paEps[iEp].Core.wMaxPacketSize, cbMax)); + paEps[iEp].Core.wMaxPacketSize = cbMax; + fEdited = true; + } + } + } + } + } + + if ( fForce11Device + && pThis->DevDesc.bcdUSB == 0x0200) + { + /* + * Discourages windows from helping you find a 2.0 port. + */ + Log(("usb-proxy: %s correcting USB version 2.0 to 1.1 (to avoid Windows warning)\n", pUsbIns->pszName)); + pThis->DevDesc.bcdUSB = 0x110; + fEdited = true; + } + + + /* + * Turn asynchronous audio endpoints into synchronous ones, see @bugref{8769} + */ + if (fEditAudioSyncEp) + { + PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + { + /* If not an audio class interface, skip. */ + if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceClass != 1) + continue; + + /* If not a streaming interface, skip. */ + if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceSubClass != 2) + continue; + + PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints; + if (!paEps) + continue; + + for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++) + { + /* isoch/asynch/data*/ + if ((paEps[iEp].Core.bmAttributes == 5) && (paEps[iEp].Core.bLength == 9)) + { + uint8_t *pbExtra = (uint8_t *)paEps[iEp].pvMore; /* unconst*/ + if (pbExtra[1] == 0) + continue; /* If bSynchAddress is zero, leave the descriptor alone. */ + + Log(("usb-proxy: pProxyDev=%s async audio with bmAttr=%02X [%02X, %02X] on EP %02X\n", + pUsbIns->pszName, paEps[iEp].Core.bmAttributes, pbExtra[0], pbExtra[1], paEps[iEp].Core.bEndpointAddress)); + paEps[iEp].Core.bmAttributes = 0xD; /* isoch/synch/data*/ + pbExtra[1] = 0; /* Clear bSynchAddress. */ + fEdited = true; + LogRel(("VUSB: Modified '%s' async audio endpoint 0x%02x\n", pUsbIns->pszName, paEps[iEp].Core.bEndpointAddress)); + } + } + } + } + } + + /* + * Disable remote wakeup capability, see @bugref{9839}. This is done on + * a device/configuration level, no need to dig too deep through the descriptors. + * On most backends, we can't perform a real selective suspend, and more importantly + * can't receive a remote wake notification. If a guest suspends the device and waits + * for a remote wake, the device is effectively dead. + */ + if (fEditRemoteWake) + { + PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++) + { + Log(("usb-proxy: pProxyDev=%s configuration %d with bmAttr=%02X\n", + pUsbIns->pszName, paCfgs[iCfg].Core.bmAttributes, iCfg)); + if (paCfgs[iCfg].Core.bmAttributes & RT_BIT(5)) + { + paCfgs[iCfg].Core.bmAttributes = paCfgs[iCfg].Core.bmAttributes & ~RT_BIT(5); /* Remote wakeup. */ + fEdited = true; + LogRel(("VUSB: Disabled '%s' remote wakeup for configuration %d\n", pUsbIns->pszName, iCfg)); + } + } + } + + /* + * Init the PDM/VUSB descriptor cache. + */ + pThis->DescCache.pDevice = &pThis->DevDesc; + pThis->DescCache.paConfigs = pThis->paCfgDescs; + pThis->DescCache.paLanguages = NULL; + pThis->DescCache.cLanguages = 0; + pThis->DescCache.fUseCachedDescriptors = fEdited; + pThis->DescCache.fUseCachedStringsDescriptors = false; + + /* + * Call the backend if it wishes to do some more initializing + * after we've read the config and descriptors. + */ + if (pThis->pOps->pfnInit) + { + rc = pThis->pOps->pfnInit(pThis); + if (RT_FAILURE(rc)) + return rc; + } + pThis->fInited = true; + + /* + * We're good! + */ + Log(("usb-proxy: created pProxyDev=%s address '%s' fMaskedIfs=%#x (rc=%Rrc)\n", + pUsbIns->pszName, szAddress, pThis->fMaskedIfs, rc)); + return VINF_SUCCESS; +} + + +/** + * The USB proxy device registration record. + */ +const PDMUSBREG g_UsbDevProxy = +{ + /* u32Version */ + PDM_USBREG_VERSION, + /* szName */ + "USBProxy", + /* pszDescription */ + "USB Proxy Device.", + /* fFlags */ + 0, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(USBPROXYDEV), + /* pfnConstruct */ + usbProxyConstruct, + /* pfnDestruct */ + usbProxyDestruct, + /* pfnVMInitComplete */ + NULL, + /* pfnVMPowerOn */ + NULL, + /* pfnVMReset */ + NULL, + /* pfnVMSuspend */ + NULL, + /* pfnVMResume */ + NULL, + /* pfnVMPowerOff */ + NULL, + /* pfnHotPlugged */ + NULL, + /* pfnHotUnplugged */ + NULL, + /* pfnDriverAttach */ + NULL, + /* pfnDriverDetach */ + NULL, + /* pfnQueryInterface */ + NULL, + /* pfnUsbReset */ + usbProxyDevReset, + /* pfnUsbGetDescriptorCache */ + usbProxyDevGetDescriptorCache, + /* pfnUsbSetConfiguration */ + usbProxyDevSetConfiguration, + /* pfnUsbSetInterface */ + usbProxyDevSetInterface, + /* pfnUsbClearHaltedEndpoint */ + usbProxyDevClearHaltedEndpoint, + /* pfnUrbNew */ + NULL, + /* pfnUrbQueue */ + usbProxyDevUrbQueue, + /* pfnUrbCancel */ + usbProxyDevUrbCancel, + /* pfnUrbReap */ + usbProxyDevUrbReap, + /* pfnWakeup */ + usbProxyDevWakeup, + + /* u32TheEnd */ + PDM_USBREG_VERSION +}; + diff --git a/src/VBox/Devices/USB/USBProxyDevice.h b/src/VBox/Devices/USB/USBProxyDevice.h new file mode 100644 index 00000000..79687bff --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice.h @@ -0,0 +1,285 @@ +/* $Id: USBProxyDevice.h $ */ +/** @file + * USBPROXY - USB proxy header + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_USB_USBProxyDevice_h +#define VBOX_INCLUDED_SRC_USB_USBProxyDevice_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/vusb.h> + +RT_C_DECLS_BEGIN + + +/** Pointer to a USB proxy device. */ +typedef struct USBPROXYDEV *PUSBPROXYDEV; + +/** + * USB Proxy Device Backend + */ +typedef struct USBPROXYBACK +{ + /** Name of the backend. */ + const char *pszName; + /** Size of the backend specific data. */ + size_t cbBackend; + + /** + * Opens the USB device specfied by pszAddress. + * + * This method will initialize backend private data. If the backend has + * already selected a configuration for the device, this must be indicated + * in USBPROXYDEV::iActiveCfg. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pszAddress Host specific USB device address. + */ + DECLR3CALLBACKMEMBER(int, pfnOpen, (PUSBPROXYDEV pProxyDev, const char *pszAddress)); + + /** + * Optional callback for initializing the device after the configuration + * has been established. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUSBPROXYDEV pProxyDev)); + + /** + * Closes handle to the host USB device. + * + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(void, pfnClose, (PUSBPROXYDEV pProxyDev)); + + /** + * Reset a device. + * + * The backend must update iActualCfg and fIgnoreEqualSetConfig. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param fResetOnLinux It's safe to do reset on linux, we can deal with devices + * being logically reconnected. + */ + DECLR3CALLBACKMEMBER(int, pfnReset, (PUSBPROXYDEV pProxyDev, bool fResetOnLinux)); + + /** + * Sets the given configuration of the device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iCfg The configuration to set. + */ + DECLR3CALLBACKMEMBER(int, pfnSetConfig, (PUSBPROXYDEV pProxyDev, int iCfg)); + + /** + * Claim an interface for use by the prox device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface number to claim. + */ + DECLR3CALLBACKMEMBER(int, pfnClaimInterface, (PUSBPROXYDEV pProxyDev, int iIf)); + + /** + * Releases an interface which was claimed before. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface number to release. + */ + DECLR3CALLBACKMEMBER(int, pfnReleaseInterface, (PUSBPROXYDEV pProxyDev, int iIf)); + + /** + * Sets the given alternate interface for the device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface to use. + * @param iSetting The alternate setting to use. + */ + DECLR3CALLBACKMEMBER(int, pfnSetInterface, (PUSBPROXYDEV pProxyDev, int iIf, int iSetting)); + + /** + * Clears the given halted endpoint. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iEp The endpoint to clear. + */ + DECLR3CALLBACKMEMBER(int, pfnClearHaltedEndpoint, (PUSBPROXYDEV pDev, unsigned int iEp)); + + /** + * Queue a new URB. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pUrb The URB to queue. + */ + DECLR3CALLBACKMEMBER(int, pfnUrbQueue, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)); + + /** + * Cancel an in-flight URB. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pUrb The URB to cancel. + */ + DECLR3CALLBACKMEMBER(int, pfnUrbCancel, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)); + + /** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The USB Proxy Device instance. + * @param cMillies Number of milliseconds to wait. Use 0 to not + * wait at all. + */ + DECLR3CALLBACKMEMBER(PVUSBURB, pfnUrbReap, (PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)); + + /** + * Kicks the thread waiting in pfnUrbReap to make it return. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(int, pfnWakeup, (PUSBPROXYDEV pProxyDev)); + + /** Dummy entry for making sure we've got all members initialized. */ + uint32_t uDummy; +} USBPROXYBACK; +/** Pointer to a USB Proxy Device Backend. */ +typedef USBPROXYBACK *PUSBPROXYBACK; +/** Pointer to a const USB Proxy Device Backend. */ +typedef const USBPROXYBACK *PCUSBPROXYBACK; + +/** The Host backend. */ +extern const USBPROXYBACK g_USBProxyDeviceHost; +/** The remote desktop backend. */ +extern const USBPROXYBACK g_USBProxyDeviceVRDP; +/** The USB/IP backend. */ +extern const USBPROXYBACK g_USBProxyDeviceUsbIp; + +#ifdef RDESKTOP +typedef struct VUSBDEV +{ + char* pszName; +} VUSBDEV, *PVUSBDEV; +#endif + +/** + * USB Proxy device. + */ +typedef struct USBPROXYDEV +{ + /** The device descriptor. */ + VUSBDESCDEVICE DevDesc; + /** The configuration descriptor array. */ + PVUSBDESCCONFIGEX paCfgDescs; +#ifndef RDESKTOP + /** The descriptor cache. + * Contains &DevDesc and paConfigDescs. */ + PDMUSBDESCCACHE DescCache; + /** Pointer to the PDM USB device instance. */ + PPDMUSBINS pUsbIns; +#endif + + /** Pointer to the backend. */ + PCUSBPROXYBACK pOps; + /** The currently active configuration. + * It's -1 if no configuration is active. This is set to -1 before open and reset, + * the backend will change it if open or reset implies SET_CONFIGURATION. */ + int iActiveCfg; + /** Ignore one or two SET_CONFIGURATION operation. + * See usbProxyDevSetCfg for details. */ + int cIgnoreSetConfigs; + /** Mask of the interfaces that the guest shall not see. */ + uint32_t fMaskedIfs; + /** Whether we've opened the device or not. + * For dealing with failed construction (the destruct method is always called). */ + bool fOpened; + /** Whether we've called pfnInit or not. + * For dealing with failed construction (the destruct method is always called). */ + bool fInited; + /** Whether the device has been detached. + * This is hack for making PDMUSBREG::pfnUsbQueue return the right status code. */ + bool fDetached; + /** Backend specific data, the size is stored in pOps::cbBackend. */ + void *pvInstanceDataR3; + +#ifdef RDESKTOP + /** @name VRDP client (rdesktop) related members. + * @{ */ + /** The vrdp device ID. */ + uint32_t idVrdp; + /** The VUSB device structure - must be the first structure member. */ + VUSBDEV Dev; + /** The next device in rdesktop-vrdp's linked list. */ + PUSBPROXYDEV pNext; + /** The previous device in rdesktop-vrdp's linked list. */ + PUSBPROXYDEV pPrev; + /** Linked list of in-flight URBs */ + PVUSBURB pUrbs; + /** @} */ +#endif +} USBPROXYDEV; + +/** @def USBPROXYDEV_2_DATA + * Converts a USB proxy Device, pointer to a pointer to the backend specific instance data. + */ +#define USBPROXYDEV_2_DATA(a_pProxyDev, a_Type) ( (a_Type)(a_pProxyDev)->pvInstanceDataR3 ) + + +DECLINLINE(const char *) usbProxyGetName(PUSBPROXYDEV pProxyDev) +{ +#ifndef RDESKTOP + return pProxyDev->pUsbIns->pszName; +#else + return pProxyDev->Dev.pszName; +#endif +} + +#ifdef RDESKTOP +DECLINLINE(PUSBPROXYDEV) usbProxyFromVusbDev(PVUSBDEV pDev) +{ + return RT_FROM_MEMBER(pDev, USBPROXYDEV, Dev); +} +#endif + +#ifdef RT_OS_LINUX +RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev); +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_USB_USBProxyDevice_h */ + diff --git a/src/VBox/Devices/USB/VUSBDevice.cpp b/src/VBox/Devices/USB/VUSBDevice.cpp new file mode 100644 index 00000000..50f93d3a --- /dev/null +++ b/src/VBox/Devices/USB/VUSBDevice.cpp @@ -0,0 +1,1884 @@ +/* $Id: VUSBDevice.cpp $ */ +/** @file + * Virtual USB - Device. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include "VUSBInternal.h" + +#include "VUSBSniffer.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Argument package of vusbDevResetThread(). + */ +typedef struct vusb_reset_args +{ + /** Pointer to the device which is being reset. */ + PVUSBDEV pDev; + /** The reset return code. */ + int rc; + /** Pointer to the completion callback. */ + PFNVUSBRESETDONE pfnDone; + /** User argument to pfnDone. */ + void *pvUser; +} VUSBRESETARGS, *PVUSBRESETARGS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Default message pipe. */ +const VUSBDESCENDPOINTEX g_Endpoint0 = +{ + { + /* .bLength = */ VUSB_DT_ENDPOINT_MIN_LEN, + /* .bDescriptorType = */ VUSB_DT_ENDPOINT, + /* .bEndpointAddress = */ 0, + /* .bmAttributes = */ 0, + /* .wMaxPacketSize = */ 64, + /* .bInterval = */ 0 + }, + NULL +}; + +/** Default configuration. */ +const VUSBDESCCONFIGEX g_Config0 = +{ + { + /* .bLength = */ VUSB_DT_CONFIG_MIN_LEN, + /* .bDescriptorType = */ VUSB_DT_CONFIG, + /* .WTotalLength = */ 0, /* (auto-calculated) */ + /* .bNumInterfaces = */ 0, + /* .bConfigurationValue =*/ 0, + /* .iConfiguration = */ 0, + /* .bmAttributes = */ 0x80, + /* .MaxPower = */ 14 + }, + NULL, + NULL +}; + + + +static PCVUSBDESCCONFIGEX vusbDevFindCfgDesc(PVUSBDEV pDev, int iCfg) +{ + if (iCfg == 0) + return &g_Config0; + + for (unsigned i = 0; i < pDev->pDescCache->pDevice->bNumConfigurations; i++) + if (pDev->pDescCache->paConfigs[i].Core.bConfigurationValue == iCfg) + return &pDev->pDescCache->paConfigs[i]; + return NULL; +} + +static PVUSBINTERFACESTATE vusbDevFindIfState(PVUSBDEV pDev, int iIf) +{ + for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++) + if (pDev->paIfStates[i].pIf->paSettings[0].Core.bInterfaceNumber == iIf) + return &pDev->paIfStates[i]; + return NULL; +} + +static PCVUSBDESCINTERFACEEX vusbDevFindAltIfDesc(PCVUSBINTERFACESTATE pIfState, int iAlt) +{ + for (uint32_t i = 0; i < pIfState->pIf->cSettings; i++) + if (pIfState->pIf->paSettings[i].Core.bAlternateSetting == iAlt) + return &pIfState->pIf->paSettings[i]; + return NULL; +} + +void vusbDevMapEndpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc) +{ + uint8_t i8Addr = pEndPtDesc->Core.bEndpointAddress & 0xF; + PVUSBPIPE pPipe = &pDev->aPipes[i8Addr]; + LogFlow(("vusbDevMapEndpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n", + pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes, + pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3])); + + if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0) + { + Log(("vusb: map message pipe on address %u\n", i8Addr)); + pPipe->in = pEndPtDesc; + pPipe->out = pEndPtDesc; + } + else if (pEndPtDesc->Core.bEndpointAddress & 0x80) + { + Log(("vusb: map input pipe on address %u\n", i8Addr)); + pPipe->in = pEndPtDesc; + } + else + { + Log(("vusb: map output pipe on address %u\n", i8Addr)); + pPipe->out = pEndPtDesc; + } + + if (pPipe->pCtrl) + { + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + } +} + +static void unmap_endpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc) +{ + uint8_t EndPt = pEndPtDesc->Core.bEndpointAddress & 0xF; + PVUSBPIPE pPipe = &pDev->aPipes[EndPt]; + LogFlow(("unmap_endpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n", + pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes, + pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3])); + + if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0) + { + Log(("vusb: unmap MSG pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->in = NULL; + pPipe->out = NULL; + } + else if (pEndPtDesc->Core.bEndpointAddress & 0x80) + { + Log(("vusb: unmap IN pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->in = NULL; + } + else + { + Log(("vusb: unmap OUT pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->out = NULL; + } + + if (pPipe->pCtrl) + { + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + } +} + +static void map_interface(PVUSBDEV pDev, PCVUSBDESCINTERFACEEX pIfDesc) +{ + LogFlow(("map_interface: pDev=%p[%s] pIfDesc=%p:{.iInterface=%d, .bAlternateSetting=%d}\n", + pDev, pDev->pUsbIns->pszName, pIfDesc, pIfDesc->Core.iInterface, pIfDesc->Core.bAlternateSetting)); + + for (unsigned i = 0; i < pIfDesc->Core.bNumEndpoints; i++) + { + if ((pIfDesc->paEndpoints[i].Core.bEndpointAddress & 0xF) == VUSB_PIPE_DEFAULT) + Log(("vusb: Endpoint 0x%x on interface %u.%u tried to override the default message pipe!!!\n", + pIfDesc->paEndpoints[i].Core.bEndpointAddress, pIfDesc->Core.bInterfaceNumber, pIfDesc->Core.bAlternateSetting)); + else + vusbDevMapEndpoint(pDev, &pIfDesc->paEndpoints[i]); + } +} + + +/** + * Worker that resets the pipe data on select config and detach. + * + * This leaves the critical section unmolested + * + * @param pPipe The pipe which data should be reset. + */ +static void vusbDevResetPipeData(PVUSBPIPE pPipe) +{ + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + + RT_ZERO(pPipe->in); + RT_ZERO(pPipe->out); + pPipe->async = 0; +} + + +bool vusbDevDoSelectConfig(PVUSBDEV pDev, PCVUSBDESCCONFIGEX pCfgDesc) +{ + LogFlow(("vusbDevDoSelectConfig: pDev=%p[%s] pCfgDesc=%p:{.iConfiguration=%d}\n", + pDev, pDev->pUsbIns->pszName, pCfgDesc, pCfgDesc->Core.iConfiguration)); + + /* + * Clean up all pipes and interfaces. + */ + unsigned i; + for (i = 0; i < VUSB_PIPE_MAX; i++) + if (i != VUSB_PIPE_DEFAULT) + vusbDevResetPipeData(&pDev->aPipes[i]); + memset(pDev->paIfStates, 0, pCfgDesc->Core.bNumInterfaces * sizeof(pDev->paIfStates[0])); + + /* + * Map in the default setting for every interface. + */ + for (i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf; + struct vusb_interface_state *pIfState; + + pIf = &pCfgDesc->paIfs[i]; + pIfState = &pDev->paIfStates[i]; + pIfState->pIf = pIf; + + /* + * Find the 0 setting, if it is not present we just use + * the lowest numbered one. + */ + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + if ( !pIfState->pCurIfDesc + || pIf->paSettings[j].Core.bAlternateSetting < pIfState->pCurIfDesc->Core.bAlternateSetting) + pIfState->pCurIfDesc = &pIf->paSettings[j]; + if (pIfState->pCurIfDesc->Core.bAlternateSetting == 0) + break; + } + + if (pIfState->pCurIfDesc) + map_interface(pDev, pIfState->pCurIfDesc); + } + + pDev->pCurCfgDesc = pCfgDesc; + + if (pCfgDesc->Core.bmAttributes & 0x40) + pDev->u16Status |= (1 << VUSB_DEV_SELF_POWERED); + else + pDev->u16Status &= ~(1 << VUSB_DEV_SELF_POWERED); + + return true; +} + +/** + * Standard device request: SET_CONFIGURATION + * @returns success indicator. + */ +static bool vusbDevStdReqSetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + unsigned iCfg = pSetup->wValue & 0xff; + + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: SET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DEFAULT) + { + LogFlow(("vusbDevStdReqSetConfig: %s: default dev state !!?\n", pDev->pUsbIns->pszName)); + return false; + } + + PCVUSBDESCCONFIGEX pNewCfgDesc = vusbDevFindCfgDesc(pDev, iCfg); + if (!pNewCfgDesc) + { + Log(("vusb: error: %s: config %i not found !!!\n", pDev->pUsbIns->pszName, iCfg)); + return false; + } + + if (iCfg == 0) + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS); + else + vusbDevSetState(pDev, VUSB_DEVICE_STATE_CONFIGURED); + if (pDev->pUsbIns->pReg->pfnUsbSetConfiguration) + { + RTCritSectEnter(&pDev->pHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5, + pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue, + pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc); + RTCritSectLeave(&pDev->pHub->CritSectDevices); + if (RT_FAILURE(rc)) + { + Log(("vusb: error: %s: failed to set config %i (%Rrc) !!!\n", pDev->pUsbIns->pszName, iCfg, rc)); + return false; + } + } + Log(("vusb: %p[%s]: SET_CONFIGURATION: Selected config %u\n", pDev, pDev->pUsbIns->pszName, iCfg)); + return vusbDevDoSelectConfig(pDev, pNewCfgDesc); +} + + +/** + * Standard device request: GET_CONFIGURATION + * @returns success indicator. + */ +static bool vusbDevStdReqGetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: GET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if ( enmState != VUSB_DEVICE_STATE_CONFIGURED + && enmState != VUSB_DEVICE_STATE_ADDRESS) + { + LogFlow(("vusbDevStdReqGetConfig: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + if (*pcbBuf < 1) + { + LogFlow(("vusbDevStdReqGetConfig: %s: no space for data!\n", pDev->pUsbIns->pszName)); + return true; + } + + uint8_t iCfg; + if (enmState == VUSB_DEVICE_STATE_ADDRESS) + iCfg = 0; + else + iCfg = pDev->pCurCfgDesc->Core.bConfigurationValue; + + *pbBuf = iCfg; + *pcbBuf = 1; + LogFlow(("vusbDevStdReqGetConfig: %s: returns iCfg=%d\n", pDev->pUsbIns->pszName, iCfg)); + return true; +} + +/** + * Standard device request: GET_INTERFACE + * @returns success indicator. + */ +static bool vusbDevStdReqGetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE) + { + Log(("vusb: error: %s: GET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState != VUSB_DEVICE_STATE_CONFIGURED) + { + LogFlow(("vusbDevStdReqGetInterface: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + if (*pcbBuf < 1) + { + LogFlow(("vusbDevStdReqGetInterface: %s: no space for data!\n", pDev->pUsbIns->pszName)); + return true; + } + + for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBDESCINTERFACEEX pIfDesc = pDev->paIfStates[i].pCurIfDesc; + if ( pIfDesc + && pSetup->wIndex == pIfDesc->Core.bInterfaceNumber) + { + *pbBuf = pIfDesc->Core.bAlternateSetting; + *pcbBuf = 1; + Log(("vusb: %s: GET_INTERFACE: %u.%u\n", pDev->pUsbIns->pszName, pIfDesc->Core.bInterfaceNumber, *pbBuf)); + return true; + } + } + + Log(("vusb: error: %s: GET_INTERFACE - unknown iface %u !!!\n", pDev->pUsbIns->pszName, pSetup->wIndex)); + return false; +} + +/** + * Standard device request: SET_INTERFACE + * @returns success indicator. + */ +static bool vusbDevStdReqSetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE) + { + Log(("vusb: error: %s: SET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState != VUSB_DEVICE_STATE_CONFIGURED) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + /* + * Find the interface. + */ + uint8_t iIf = pSetup->wIndex; + PVUSBINTERFACESTATE pIfState = vusbDevFindIfState(pDev, iIf); + if (!pIfState) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find interface %u !!!\n", pDev->pUsbIns->pszName, iIf)); + return false; + } + uint8_t iAlt = pSetup->wValue; + PCVUSBDESCINTERFACEEX pIfDesc = vusbDevFindAltIfDesc(pIfState, iAlt); + if (!pIfDesc) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u !!!\n", pDev->pUsbIns->pszName, iIf, iAlt)); + return false; + } + + if (pDev->pUsbIns->pReg->pfnUsbSetInterface) + { + RTCritSectEnter(&pDev->pHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt); + RTCritSectLeave(&pDev->pHub->CritSectDevices); + if (RT_FAILURE(rc)) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u (%Rrc)\n", pDev->pUsbIns->pszName, iIf, iAlt, rc)); + return false; + } + } + + for (unsigned i = 0; i < pIfState->pCurIfDesc->Core.bNumEndpoints; i++) + unmap_endpoint(pDev, &pIfState->pCurIfDesc->paEndpoints[i]); + + Log(("vusb: SET_INTERFACE: Selected %u.%u\n", iIf, iAlt)); + + map_interface(pDev, pIfDesc); + pIfState->pCurIfDesc = pIfDesc; + + return true; +} + +/** + * Standard device request: SET_ADDRESS + * @returns success indicator. + */ +static bool vusbDevStdReqSetAddress(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: SET_ADDRESS - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if ( enmState != VUSB_DEVICE_STATE_DEFAULT + && enmState != VUSB_DEVICE_STATE_ADDRESS) + { + LogFlow(("vusbDevStdReqSetAddress: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + pDev->u8NewAddress = pSetup->wValue; + return true; +} + +/** + * Standard device request: CLEAR_FEATURE + * @returns success indicator. + * + * @remark This is only called for VUSB_TO_ENDPOINT && ep == 0 && wValue == ENDPOINT_HALT. + * All other cases of CLEAR_FEATURE is handled in the normal async/sync manner. + */ +static bool vusbDevStdReqClearFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(pbBuf, pcbBuf); + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + Log(("vusb: ClearFeature: dev(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_INTERFACE: + Log(("vusb: ClearFeature: iface(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_ENDPOINT: + Log(("vusb: ClearFeature: ep(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + if ( !EndPt /* Default control pipe only */ + && pSetup->wValue == 0 /* ENDPOINT_HALT */ + && pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint) + { + RTCritSectEnter(&pDev->pHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint, + 2, pDev->pUsbIns, pSetup->wIndex); + RTCritSectLeave(&pDev->pHub->CritSectDevices); + return RT_SUCCESS(rc); + } + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + break; + } + + AssertMsgFailed(("Invalid safe check !!!\n")); + return false; +} + +/** + * Standard device request: SET_FEATURE + * @returns success indicator. + */ +static bool vusbDevStdReqSetFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(pDev, EndPt, pbBuf, pcbBuf); + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + Log(("vusb: SetFeature: dev(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_INTERFACE: + Log(("vusb: SetFeature: if(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_ENDPOINT: + Log(("vusb: SetFeature: ep(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + return false; + } + AssertMsgFailed(("This stuff is bogus\n")); + return false; +} + +static bool vusbDevStdReqGetStatus(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if (*pcbBuf != 2) + { + LogFlow(("vusbDevStdReqGetStatus: %s: buffer is too small! (%d)\n", pDev->pUsbIns->pszName, *pcbBuf)); + return false; + } + + uint16_t u16Status; + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + u16Status = pDev->u16Status; + LogFlow(("vusbDevStdReqGetStatus: %s: device status %#x (%d)\n", pDev->pUsbIns->pszName, u16Status, u16Status)); + break; + case VUSB_TO_INTERFACE: + u16Status = 0; + LogFlow(("vusbDevStdReqGetStatus: %s: bogus interface status request!!\n", pDev->pUsbIns->pszName)); + break; + case VUSB_TO_ENDPOINT: + u16Status = 0; + LogFlow(("vusbDevStdReqGetStatus: %s: bogus endpoint status request!!\n", pDev->pUsbIns->pszName)); + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + return false; + } + + *(uint16_t *)pbBuf = u16Status; + return true; +} + + +/** + * Finds a cached string. + * + * @returns Pointer to the cached string if found. NULL if not. + * @param paLanguages The languages to search. + * @param cLanguages The number of languages in the table. + * @param idLang The language ID. + * @param iString The string index. + */ +static PCPDMUSBDESCCACHESTRING FindCachedString(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages, + uint16_t idLang, uint8_t iString) +{ + /** @todo binary lookups! */ + unsigned iCurLang = cLanguages; + while (iCurLang-- > 0) + if (paLanguages[iCurLang].idLang == idLang) + { + PCPDMUSBDESCCACHESTRING paStrings = paLanguages[iCurLang].paStrings; + unsigned iCurStr = paLanguages[iCurLang].cStrings; + while (iCurStr-- > 0) + if (paStrings[iCurStr].idx == iString) + return &paStrings[iCurStr]; + break; + } + return NULL; +} + + +/** Macro for copying descriptor data. */ +#define COPY_DATA(pbDst, cbLeft, pvSrc, cbSrc) \ + do { \ + uint32_t cbSrc_ = cbSrc; \ + uint32_t cbCopy = RT_MIN(cbLeft, cbSrc_); \ + if (cbCopy) \ + memcpy(pbBuf, pvSrc, cbCopy); \ + cbLeft -= cbCopy; \ + if (!cbLeft) \ + return; \ + pbBuf += cbCopy; \ + } while (0) + +/** + * Internal function for reading the language IDs. + */ +static void ReadCachedStringDesc(PCPDMUSBDESCCACHESTRING pString, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + RTUTF16 wsz[128]; /* 128-1 => bLength=0xff */ + PRTUTF16 pwsz = wsz; + size_t cwc; + int rc = RTStrToUtf16Ex(pString->psz, RT_ELEMENTS(wsz) - 1, &pwsz, RT_ELEMENTS(wsz), &cwc); + if (RT_FAILURE(rc)) + { + AssertRC(rc); + wsz[0] = 'e'; + wsz[1] = 'r'; + wsz[2] = 'r'; + cwc = 3; + } + + VUSBDESCSTRING StringDesc; + StringDesc.bLength = (uint8_t)(sizeof(StringDesc) + cwc * sizeof(RTUTF16)); + StringDesc.bDescriptorType = VUSB_DT_STRING; + COPY_DATA(pbBuf, cbLeft, &StringDesc, sizeof(StringDesc)); + COPY_DATA(pbBuf, cbLeft, wsz, (uint32_t)cwc * sizeof(RTUTF16)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + + +/** + * Internal function for reading the language IDs. + */ +static void ReadCachedLangIdDesc(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages, + uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + VUSBDESCLANGID LangIdDesc; + size_t cbDesc = sizeof(LangIdDesc) + cLanguages * sizeof(paLanguages[0].idLang); + LangIdDesc.bLength = (uint8_t)RT_MIN(0xff, cbDesc); + LangIdDesc.bDescriptorType = VUSB_DT_STRING; + COPY_DATA(pbBuf, cbLeft, &LangIdDesc, sizeof(LangIdDesc)); + + unsigned iLanguage = cLanguages; + while (iLanguage-- > 0) + COPY_DATA(pbBuf, cbLeft, &paLanguages[iLanguage].idLang, sizeof(paLanguages[iLanguage].idLang)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + + +/** + * Internal function which performs a descriptor read on the cached descriptors. + */ +static void ReadCachedConfigDesc(PCVUSBDESCCONFIGEX pCfgDesc, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + /* + * Make a copy of the config descriptor and calculate the wTotalLength field. + */ + VUSBDESCCONFIG CfgDesc; + memcpy(&CfgDesc, pCfgDesc, VUSB_DT_CONFIG_MIN_LEN); + uint32_t cbTotal = 0; + cbTotal += pCfgDesc->Core.bLength; + cbTotal += pCfgDesc->cbClass; + for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i]; + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + cbTotal += pIf->paSettings[j].cbIAD; + cbTotal += pIf->paSettings[j].Core.bLength; + cbTotal += pIf->paSettings[j].cbClass; + for (unsigned k = 0; k < pIf->paSettings[j].Core.bNumEndpoints; k++) + { + cbTotal += pIf->paSettings[j].paEndpoints[k].Core.bLength; + cbTotal += pIf->paSettings[j].paEndpoints[k].cbSsepc; + cbTotal += pIf->paSettings[j].paEndpoints[k].cbClass; + } + } + } + CfgDesc.wTotalLength = RT_H2LE_U16(cbTotal); + + /* + * Copy the config descriptor + */ + COPY_DATA(pbBuf, cbLeft, &CfgDesc, VUSB_DT_CONFIG_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvMore, pCfgDesc->Core.bLength - VUSB_DT_CONFIG_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvClass, pCfgDesc->cbClass); + + /* + * Copy out all the interfaces for this configuration + */ + for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i]; + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + PCVUSBDESCINTERFACEEX pIfDesc = &pIf->paSettings[j]; + + COPY_DATA(pbBuf, cbLeft, pIfDesc->pIAD, pIfDesc->cbIAD); + COPY_DATA(pbBuf, cbLeft, pIfDesc, VUSB_DT_INTERFACE_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->pvMore, pIfDesc->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->pvClass, pIfDesc->cbClass); + + /* + * Copy out all the endpoints for this interface + */ + for (unsigned k = 0; k < pIfDesc->Core.bNumEndpoints; k++) + { + VUSBDESCENDPOINT EndPtDesc; + memcpy(&EndPtDesc, &pIfDesc->paEndpoints[k], VUSB_DT_ENDPOINT_MIN_LEN); + EndPtDesc.wMaxPacketSize = RT_H2LE_U16(EndPtDesc.wMaxPacketSize); + + COPY_DATA(pbBuf, cbLeft, &EndPtDesc, VUSB_DT_ENDPOINT_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvMore, EndPtDesc.bLength - VUSB_DT_ENDPOINT_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvSsepc, pIfDesc->paEndpoints[k].cbSsepc); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvClass, pIfDesc->paEndpoints[k].cbClass); + } + } + } + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + +/** + * Internal function which performs a descriptor read on the cached descriptors. + */ +static void ReadCachedDeviceDesc(PCVUSBDESCDEVICE pDevDesc, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + /* + * Duplicate the device description and update some fields we keep in cpu type. + */ + Assert(sizeof(VUSBDESCDEVICE) == 18); + VUSBDESCDEVICE DevDesc = *pDevDesc; + DevDesc.bcdUSB = RT_H2LE_U16(DevDesc.bcdUSB); + DevDesc.idVendor = RT_H2LE_U16(DevDesc.idVendor); + DevDesc.idProduct = RT_H2LE_U16(DevDesc.idProduct); + DevDesc.bcdDevice = RT_H2LE_U16(DevDesc.bcdDevice); + + COPY_DATA(pbBuf, cbLeft, &DevDesc, sizeof(DevDesc)); + COPY_DATA(pbBuf, cbLeft, pDevDesc + 1, pDevDesc->bLength - sizeof(DevDesc)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + +#undef COPY_DATA + +/** + * Checks whether a descriptor read can be satisfied by reading from the + * descriptor cache or has to be passed to the device. + * If we have descriptors cached, it is generally safe to satisfy descriptor reads + * from the cache. As usual, there is broken USB software and hardware out there + * and guests might try to read a nonexistent desciptor (out of range index for + * string or configuration descriptor) and rely on it not failing. + * Since we cannot very well guess if such invalid requests should really succeed, + * and what exactly should happen if they do, we pass such requests to the device. + * If the descriptor was cached because it was edited, and the guest bypasses the + * edited cache by reading a descriptor with an invalid index, it is probably + * best to smash the USB device with a large hammer. + * + * See @bugref{10016}. + * + * @returns false if request must be passed to device. + */ +bool vusbDevIsDescriptorInCache(PVUSBDEV pDev, PCVUSBSETUP pSetup) +{ + unsigned int iIndex = (pSetup->wValue & 0xff); + Assert(pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR); + + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE) + { + if (pDev->pDescCache->fUseCachedDescriptors) + { + switch (pSetup->wValue >> 8) + { + case VUSB_DT_DEVICE: + if (iIndex == 0) + return true; + + LogRelMax(10, ("VUSB: %s: Warning: Reading device descriptor with non-zero index %u (wLength=%u), passing request to device\n", + pDev->pUsbIns->pszName, iIndex, pSetup->wLength)); + break; + + case VUSB_DT_CONFIG: + if (iIndex < pDev->pDescCache->pDevice->bNumConfigurations) + return true; + + LogRelMax(10, ("VUSB: %s: Warning: Reading configuration descriptor invalid index %u (bNumConfigurations=%u, wLength=%u), passing request to device\n", + pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations, pSetup->wLength)); + break; + + case VUSB_DT_STRING: + if (pDev->pDescCache->fUseCachedStringsDescriptors) + { + if (pSetup->wIndex == 0) /* Language IDs. */ + return true; + + if (FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, + pSetup->wIndex, iIndex)) + return true; + } + break; + + default: + break; + } + Log(("VUSB: %s: Descriptor not cached: type=%u descidx=%u lang=%u len=%u, passing request to device\n", + pDev->pUsbIns->pszName, pSetup->wValue >> 8, iIndex, pSetup->wIndex, pSetup->wLength)); + } + } + return false; +} + + +/** + * Standard device request: GET_DESCRIPTOR + * @returns success indicator. + */ +static bool vusbDevStdReqGetDescriptor(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE) + { + switch (pSetup->wValue >> 8) + { + case VUSB_DT_DEVICE: + ReadCachedDeviceDesc(pDev->pDescCache->pDevice, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of device descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + + case VUSB_DT_CONFIG: + { + unsigned int iIndex = (pSetup->wValue & 0xff); + if (iIndex >= pDev->pDescCache->pDevice->bNumConfigurations) + { + LogFlow(("vusbDevStdReqGetDescriptor: %s: iIndex=%u >= bNumConfigurations=%d !!!\n", + pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations)); + return false; + } + ReadCachedConfigDesc(&pDev->pDescCache->paConfigs[iIndex], pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of config descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + } + + case VUSB_DT_STRING: + { + if (pSetup->wIndex == 0) + { + ReadCachedLangIdDesc(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of language ID (string) descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + } + PCPDMUSBDESCCACHESTRING pString; + pString = FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, + pSetup->wIndex, pSetup->wValue & 0xff); + if (pString) + { + ReadCachedStringDesc(pString, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of string descriptors \"%s\"\n", + pDev->pUsbIns->pszName, *pcbBuf, pString->psz)); + return true; + } + break; + } + + default: + break; + } + } + Log(("vusb: %s: warning: unknown descriptor: type=%u descidx=%u lang=%u len=%u!!!\n", + pDev->pUsbIns->pszName, pSetup->wValue >> 8, pSetup->wValue & 0xff, pSetup->wIndex, pSetup->wLength)); + return false; +} + + +/** + * Service the standard USB requests. + * + * Devices may call this from controlmsg() if you want vusb core to handle your standard + * request, it's not necessary - you could handle them manually + * + * @param pDev The device. + * @param EndPoint The endpoint. + * @param pSetup Pointer to the setup request structure. + * @param pvBuf Buffer? + * @param pcbBuf ? + */ +bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPoint, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf) +{ + static bool (* const s_apfnStdReq[VUSB_REQ_MAX])(PVUSBDEV, int, PVUSBSETUP, uint8_t *, uint32_t *) = + { + vusbDevStdReqGetStatus, + vusbDevStdReqClearFeature, + NULL, + vusbDevStdReqSetFeature, + NULL, + vusbDevStdReqSetAddress, + vusbDevStdReqGetDescriptor, + NULL, + vusbDevStdReqGetConfig, + vusbDevStdReqSetConfig, + vusbDevStdReqGetInterface, + vusbDevStdReqSetInterface, + NULL /* for iso */ + }; + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: standard control message ignored, the device is resetting\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Do the request if it's one we want to deal with. + */ + if ( pSetup->bRequest >= VUSB_REQ_MAX + || !s_apfnStdReq[pSetup->bRequest]) + { + Log(("vusb: warning: standard req not implemented: message %u: val=%u idx=%u len=%u !!!\n", + pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + return false; + } + + return s_apfnStdReq[pSetup->bRequest](pDev, EndPoint, pSetup, (uint8_t *)pvBuf, pcbBuf); +} + + +/** + * Sets the address of a device. + * + * Called by status_completion() and vusbDevResetWorker(). + */ +void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address) +{ + LogFlow(("vusbDevSetAddress: pDev=%p[%s]/%i u8Address=%#x\n", + pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + VUSBDEV_ASSERT_VALID_STATE(enmState); + if ( enmState == VUSB_DEVICE_STATE_ATTACHED + || enmState == VUSB_DEVICE_STATE_DETACHED) + { + LogFlow(("vusbDevSetAddress: %s: fails because %d < POWERED\n", pDev->pUsbIns->pszName, pDev->enmState)); + return; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: set address ignored, the device is resetting\n", pDev->pUsbIns->pszName)); + return; + } + + /* + * Ok, get on with it. + */ + if (pDev->u8Address == u8Address) + return; + + /** @todo The following logic belongs to the roothub and should actually be in that file. */ + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + AssertPtrReturnVoid(pRh); + + RTCritSectEnter(&pRh->CritSectDevices); + + /* Remove the device from the current address. */ + if (pDev->u8Address != VUSB_INVALID_ADDRESS) + { + Assert(pRh->apDevByAddr[pDev->u8Address] == pDev); + pRh->apDevByAddr[pDev->u8Address] = NULL; + } + + if (u8Address == VUSB_DEFAULT_ADDRESS) + { + PVUSBDEV pDevDef = pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS]; + + if (pDevDef) + { + pDevDef->u8Address = VUSB_INVALID_ADDRESS; + pDevDef->u8NewAddress = VUSB_INVALID_ADDRESS; + vusbDevSetStateCmp(pDevDef, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT); + Log(("2 DEFAULT ADDRS\n")); + } + + pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS] = pDev; + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); + } + else + { + Assert(!pRh->apDevByAddr[u8Address]); + pRh->apDevByAddr[u8Address] = pDev; + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS); + } + + pDev->u8Address = u8Address; + RTCritSectLeave(&pRh->CritSectDevices); + + Log(("vusb: %p[%s]/%i: Assigned address %u\n", + pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address)); +} + + +static DECLCALLBACK(int) vusbDevCancelAllUrbsWorker(PVUSBDEV pDev, bool fDetaching) +{ + /* + * Iterate the URBs and cancel them. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + + Assert(pUrb->pVUsb->pDev == pDev); + + LogFlow(("%s: vusbDevCancelAllUrbs: CANCELING URB\n", pUrb->pszDesc)); + int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL); + AssertRC(rc); + } + + /* + * Reap any URBs which became ripe during cancel now. + */ + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + unsigned cReaped; + do + { + cReaped = 0; + pVUsbUrb = RTListGetFirst(&pDev->LstAsyncUrbs, VUSBURBVUSBINT, NdLst); + while (pVUsbUrb) + { + PVUSBURBVUSB pNext = RTListGetNext(&pDev->LstAsyncUrbs, pVUsbUrb, VUSBURBVUSBINT, NdLst); + PVUSBURB pUrb = pVUsbUrb->pUrb; + Assert(pUrb->pVUsb->pDev == pDev); + + PVUSBURB pRipe = NULL; + if (pUrb->enmState == VUSBURBSTATE_REAPED) + pRipe = pUrb; + else if (pUrb->enmState == VUSBURBSTATE_CANCELLED) +#ifdef RT_OS_WINDOWS /** @todo Windows doesn't do cancelling, thus this kludge to prevent really bad + * things from happening if we leave a pending URB behinds. */ + pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 1500 : 0 /*ms*/); +#else + pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 10 : 0 /*ms*/); +#endif + else + AssertMsgFailed(("pUrb=%p enmState=%d\n", pUrb, pUrb->enmState)); + if (pRipe) + { + if ( pNext + && pRipe == pNext->pUrb) + pNext = RTListGetNext(&pDev->LstAsyncUrbs, pNext, VUSBURBVUSBINT, NdLst); + vusbUrbRipe(pRipe); + cReaped++; + } + + pVUsbUrb = pNext; + } + } while (cReaped > 0); + + /* + * If we're detaching, we'll have to orphan any leftover URBs. + */ + if (fDetaching) + { + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + Assert(pUrb->pVUsb->pDev == pDev); + + AssertMsgFailed(("%s: Leaking left over URB! state=%d pDev=%p[%s]\n", + pUrb->pszDesc, pUrb->enmState, pDev, pDev->pUsbIns->pszName)); + vusbUrbUnlink(pUrb); + /* Unlink isn't enough, because boundary timer and detaching will try to reap it. + * It was tested with MSD & iphone attachment to vSMP guest, if + * it breaks anything, please add comment here, why we should unlink only. + */ + pUrb->pVUsb->pfnFree(pUrb); + } + } + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + return VINF_SUCCESS; +} + +/** + * Cancels and completes (with CRC failure) all async URBs pending + * on a device. This is typically done as part of a reset and + * before detaching a device. + * + * @returns nothing. + * @param pDev The VUSB device instance. + * @param fDetaching If set, we will unconditionally unlink (and leak) + * any URBs which isn't reaped. + */ +DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching) +{ + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevCancelAllUrbsWorker, 2, pDev, fDetaching); + AssertRC(rc); +} + + +static DECLCALLBACK(int) vusbDevUrbIoThread(RTTHREAD hThread, void *pvUser) +{ + PVUSBDEV pDev = (PVUSBDEV)pvUser; + + /* Notify the starter that we are up and running. */ + RTThreadUserSignal(hThread); + + LogFlowFunc(("Entering work loop\n")); + + while (!ASMAtomicReadBool(&pDev->fTerminate)) + { + if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET) + vusbUrbDoReapAsyncDev(pDev, RT_INDEFINITE_WAIT); + + /* Process any URBs waiting to be cancelled first. */ + int rc = RTReqQueueProcess(pDev->hReqQueueSync, 0); /* Don't wait if there is nothing to do. */ + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc); + } + + return VINF_SUCCESS; +} + +int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev) +{ + ASMAtomicXchgBool(&pDev->fWokenUp, true); + return pDev->pUsbIns->pReg->pfnWakeup(pDev->pUsbIns); +} + +/** + * Create the URB I/O thread. + * + * @returns VBox status code. + * @param pDev The VUSB device. + */ +int vusbDevUrbIoThreadCreate(PVUSBDEV pDev) +{ + int rc = VINF_SUCCESS; + + ASMAtomicXchgBool(&pDev->fTerminate, false); + rc = RTThreadCreateF(&pDev->hUrbIoThread, vusbDevUrbIoThread, pDev, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "USBDevIo-%d", pDev->i16Port); + if (RT_SUCCESS(rc)) + { + /* Wait for it to become active. */ + rc = RTThreadUserWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT); + } + + return rc; +} + +/** + * Destro the URB I/O thread. + * + * @returns VBox status code. + * @param pDev The VUSB device. + */ +int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev) +{ + int rc = VINF_SUCCESS; + int rcThread = VINF_SUCCESS; + + ASMAtomicXchgBool(&pDev->fTerminate, true); + vusbDevUrbIoThreadWakeup(pDev); + + rc = RTThreadWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + + pDev->hUrbIoThread = NIL_RTTHREAD; + + return rc; +} + + +/** + * Attaches a device to the given hub. + * + * @returns VBox status code. + * @param pDev The device to attach. + * @param pHub The roothub to attach to. + */ +int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub) +{ + AssertMsg(pDev->enmState == VUSB_DEVICE_STATE_DETACHED, ("enmState=%d\n", pDev->enmState)); + + pDev->pHub = pHub; + pDev->enmState = VUSB_DEVICE_STATE_ATTACHED; + + /* noone else ever messes with the default pipe while we are attached */ + vusbDevMapEndpoint(pDev, &g_Endpoint0); + vusbDevDoSelectConfig(pDev, &g_Config0); + + /* Create I/O thread and attach to the hub. */ + int rc = vusbDevUrbIoThreadCreate(pDev); + if (RT_FAILURE(rc)) + { + pDev->pHub = NULL; + pDev->enmState = VUSB_DEVICE_STATE_DETACHED; + } + + return rc; +} + + +/** + * Detaches a device from the hub it's attached to. + * + * @returns VBox status code. + * @param pDev The device to detach. + * + * @remark This can be called in any state but reset. + */ +int vusbDevDetach(PVUSBDEV pDev) +{ + LogFlow(("vusbDevDetach: pDev=%p[%s] enmState=%#x\n", pDev, pDev->pUsbIns->pszName, pDev->enmState)); + VUSBDEV_ASSERT_VALID_STATE(pDev->enmState); + Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Destroy I/O thread and request queue last because they might still be used + * when cancelling URBs. + */ + vusbDevUrbIoThreadDestroy(pDev); + + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DETACHED); + pDev->pHub = NULL; + + /* Remove the configuration */ + pDev->pCurCfgDesc = NULL; + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + vusbDevResetPipeData(&pDev->aPipes[i]); + return VINF_SUCCESS; +} + + +/** + * Destroys a device, detaching it from the hub if necessary. + * + * @param pDev The device. + * @thread any. + */ +void vusbDevDestroy(PVUSBDEV pDev) +{ + LogFlow(("vusbDevDestroy: pDev=%p[%s] enmState=%d\n", pDev, pDev->pUsbIns->pszName, pDev->enmState)); + + RTMemFree(pDev->paIfStates); + + PDMUsbHlpTimerDestroy(pDev->pUsbIns, pDev->hResetTimer); + pDev->hResetTimer = NIL_TMTIMERHANDLE; + + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + { + Assert(pDev->aPipes[i].pCtrl == NULL); + RTCritSectDelete(&pDev->aPipes[i].CritSectCtrl); + } + + if (pDev->hSniffer != VUSBSNIFFER_NIL) + VUSBSnifferDestroy(pDev->hSniffer); + + vusbUrbPoolDestroy(&pDev->UrbPool); + + int rc = RTReqQueueDestroy(pDev->hReqQueueSync); + AssertRC(rc); + pDev->hReqQueueSync = NIL_RTREQQUEUE; + + RTCritSectDelete(&pDev->CritSectAsyncUrbs); + /* Not using vusbDevSetState() deliberately here because it would assert on the state. */ + pDev->enmState = VUSB_DEVICE_STATE_DESTROYED; + pDev->pUsbIns->pvVUsbDev2 = NULL; + RTMemFree(pDev); +} + + +/* -=-=-=-=-=- VUSBIDEVICE methods -=-=-=-=-=- */ + + +/** + * The actual reset has been done, do completion on EMT. + * + * There are several things we have to do now, like set default + * config and address, and cleanup the state of control pipes. + * + * It's possible that the device has a delayed destroy request + * pending when we get here. This can happen for async resetting. + * We deal with it here, since we're now executing on the EMT + * thread and the destruction will be properly serialized now. + * + * @param pDev The device that is being reset. + * @param rc The vusbDevResetWorker return code. + * @param pfnDone The done callback specified by the caller of vusbDevReset(). + * @param pvUser The user argument for the callback. + */ +static void vusbDevResetDone(PVUSBDEV pDev, int rc, PFNVUSBRESETDONE pfnDone, void *pvUser) +{ + VUSBDEV_ASSERT_VALID_STATE(pDev->enmState); + Assert(pDev->enmState == VUSB_DEVICE_STATE_RESET); + + /* + * Do control pipe cleanup regardless of state and result. + */ + for (unsigned i = 0; i < VUSB_PIPE_MAX; i++) + if (pDev->aPipes[i].pCtrl) + vusbMsgResetExtraData(pDev->aPipes[i].pCtrl); + + /* + * Switch to the default state. + */ + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); + pDev->u16Status = 0; + vusbDevDoSelectConfig(pDev, &g_Config0); + vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS); + if (pfnDone) + pfnDone(&pDev->IDevice, pDev->i16Port, rc, pvUser); +} + + +/** + * @callback_method_impl{FNTMTIMERUSB, + * Timer callback for doing reset completion.} + */ +static DECLCALLBACK(void) vusbDevResetDoneTimer(PPDMUSBINS pUsbIns, TMTIMERHANDLE hTimer, void *pvUser) +{ + PVUSBDEV pDev = (PVUSBDEV)pvUser; + PVUSBRESETARGS pArgs = (PVUSBRESETARGS)pDev->pvArgs; + Assert(pDev->pUsbIns == pUsbIns); + RT_NOREF(pUsbIns, hTimer); + + AssertPtr(pArgs); + + /* + * Reset-done processing and cleanup. + */ + pDev->pvArgs = NULL; + vusbDevResetDone(pDev, pArgs->rc, pArgs->pfnDone, pArgs->pvUser); + RTMemFree(pArgs); +} + + +/** + * Perform the actual reset. + * + * @thread EMT or a VUSB reset thread. + */ +static DECLCALLBACK(int) vusbDevResetWorker(PVUSBDEV pDev, bool fResetOnLinux, bool fUseTimer, PVUSBRESETARGS pArgs) +{ + uint64_t const uTimerDeadline = !fUseTimer ? 0 + : PDMUsbHlpTimerGet(pDev->pUsbIns, pDev->hResetTimer) + + PDMUsbHlpTimerFromMilli(pDev->pUsbIns, pDev->hResetTimer, 10); + + int rc = VINF_SUCCESS; + if (pDev->pUsbIns->pReg->pfnUsbReset) + rc = pDev->pUsbIns->pReg->pfnUsbReset(pDev->pUsbIns, fResetOnLinux); + + if (pArgs) + { + pArgs->rc = rc; + rc = VINF_SUCCESS; + } + + if (fUseTimer) + { + /* + * We use a timer to communicate the result back to EMT. + * This avoids suspend + poweroff issues, and it should give + * us more accurate scheduling than making this thread sleep. + */ + int rc2 = PDMUsbHlpTimerSet(pDev->pUsbIns, pDev->hResetTimer, uTimerDeadline); + AssertReleaseRC(rc2); + } + + LogFlow(("vusbDevResetWorker: %s: returns %Rrc\n", pDev->pUsbIns->pszName, rc)); + return rc; +} + + +/** + * Resets a device. + * + * Since a device reset shall take at least 10ms from the guest point of view, + * it must be performed asynchronously. We create a thread which performs this + * operation and ensures it will take at least 10ms. + * + * At times - like init - a synchronous reset is required, this can be done + * by passing NULL for pfnDone. + * + * While the device is being reset it is in the VUSB_DEVICE_STATE_RESET state. + * On completion it will be in the VUSB_DEVICE_STATE_DEFAULT state if successful, + * or in the VUSB_DEVICE_STATE_DETACHED state if the rest failed. + * + * @returns VBox status code. + * + * @param pDevice Pointer to the VUSB device interface. + * @param fResetOnLinux Whether it's safe to reset the device(s) on a linux + * host system. See discussion of logical reconnects elsewhere. + * @param pfnDone Pointer to the completion routine. If NULL a synchronous + * reset is preformed not respecting the 10ms. + * @param pvUser Opaque user data to pass to the done callback. + * @param pVM Pointer to the VM handle for performing the done function + * on the EMT thread. + * @thread EMT + */ +static DECLCALLBACK(int) vusbIDeviceReset(PVUSBIDEVICE pDevice, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + RT_NOREF(pVM); + PVUSBDEV pDev = (PVUSBDEV)pDevice; + Assert(!pfnDone || pVM); + LogFlow(("vusb: reset: [%s]/%i\n", pDev->pUsbIns->pszName, pDev->i16Port)); + + /* + * Only one reset operation at a time. + */ + const VUSBDEVICESTATE enmStateOld = vusbDevSetState(pDev, VUSB_DEVICE_STATE_RESET); + if (enmStateOld == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: reset request is ignored, the device is already resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + /* + * First, cancel all async URBs. + */ + vusbDevCancelAllUrbs(pDev, false); + + /* Async or sync? */ + if (pfnDone) + { + /* + * Async fashion. + */ + PVUSBRESETARGS pArgs = (PVUSBRESETARGS)RTMemTmpAlloc(sizeof(*pArgs)); + if (pArgs) + { + pArgs->pDev = pDev; + pArgs->pfnDone = pfnDone; + pArgs->pvUser = pvUser; + pArgs->rc = VINF_SUCCESS; + AssertPtrNull(pDev->pvArgs); + pDev->pvArgs = pArgs; + int rc = vusbDevIoThreadExec(pDev, 0 /* fFlags */, (PFNRT)vusbDevResetWorker, 4, pDev, fResetOnLinux, true, pArgs); + if (RT_SUCCESS(rc)) + return rc; + + RTMemTmpFree(pArgs); + } + /* fall back to sync on failure */ + } + + /* + * Sync fashion. + */ + int rc = vusbDevResetWorker(pDev, fResetOnLinux, false, NULL); + vusbDevResetDone(pDev, rc, pfnDone, pvUser); + return rc; +} + + +/** + * Powers on the device. + * + * @returns VBox status code. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(int) vusbIDevicePowerOn(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + LogFlow(("vusbDevPowerOn: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DETACHED) + { + Log(("vusb: warning: attempt to power on detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power on ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + /* + * Do the job. + */ + if (enmState == VUSB_DEVICE_STATE_ATTACHED) + vusbDevSetState(pDev, VUSB_DEVICE_STATE_POWERED); + + return VINF_SUCCESS; +} + + +/** + * Powers off the device. + * + * @returns VBox status code. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(int) vusbIDevicePowerOff(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + LogFlow(("vusbDevPowerOff: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DETACHED) + { + Log(("vusb: warning: attempt to power off detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ATTACHED); + return VINF_SUCCESS; +} + + +/** + * Get the state of the device. + * + * @returns Device state. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(VUSBDEVICESTATE) vusbIDeviceGetState(PVUSBIDEVICE pInterface) +{ + return vusbDevGetState((PVUSBDEV)pInterface); +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnIsSavedStateSupported} + */ +static DECLCALLBACK(bool) vusbIDeviceIsSavedStateSupported(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + bool fSavedStateSupported = RT_BOOL(pDev->pUsbIns->pReg->fFlags & PDM_USBREG_SAVED_STATE_SUPPORTED); + + LogFlowFunc(("pInterface=%p\n", pInterface)); + + LogFlowFunc(("returns %RTbool\n", fSavedStateSupported)); + return fSavedStateSupported; +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnGetState} + */ +static DECLCALLBACK(VUSBSPEED) vusbIDeviceGetSpeed(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + VUSBSPEED enmSpeed = pDev->pUsbIns->enmSpeed; + + LogFlowFunc(("pInterface=%p, returns %u\n", pInterface, enmSpeed)); + return enmSpeed; +} + + +/** + * The maximum number of interfaces the device can have in all of it's configuration. + * + * @returns Number of interfaces. + * @param pDev The device. + */ +size_t vusbDevMaxInterfaces(PVUSBDEV pDev) +{ + uint8_t cMax = 0; + unsigned i = pDev->pDescCache->pDevice->bNumConfigurations; + while (i-- > 0) + { + if (pDev->pDescCache->paConfigs[i].Core.bNumInterfaces > cMax) + cMax = pDev->pDescCache->paConfigs[i].Core.bNumInterfaces; + } + + return cMax; +} + + +/** + * Executes a given function on the I/O thread. + * + * @returns IPRT status code. + * @param pDev The USB device instance data. + * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_* + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param Args The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args) +{ + int rc = VINF_SUCCESS; + PRTREQ hReq = NULL; + + Assert(pDev->hUrbIoThread != NIL_RTTHREAD); + if (RT_LIKELY(pDev->hUrbIoThread != NIL_RTTHREAD)) + { + uint32_t fReqFlags = RTREQFLAGS_IPRT_STATUS; + + if (!(fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + fReqFlags |= RTREQFLAGS_NO_WAIT; + + rc = RTReqQueueCallV(pDev->hReqQueueSync, &hReq, 0 /* cMillies */, fReqFlags, pfnFunction, cArgs, Args); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); + + /* In case we are called on the I/O thread just process the request. */ + if ( pDev->hUrbIoThread == RTThreadSelf() + && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + { + int rc2 = RTReqQueueProcess(pDev->hReqQueueSync, 0); + Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT); NOREF(rc2); + } + else + vusbDevUrbIoThreadWakeup(pDev); + + if ( rc == VERR_TIMEOUT + && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + { + rc = RTReqWait(hReq, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + RTReqRelease(hReq); + } + else + rc = VERR_INVALID_STATE; + + return rc; +} + + +/** + * Executes a given function on the I/O thread. + * + * @returns IPRT status code. + * @param pDev The USB device instance data. + * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_* + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param ... The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...) +{ + int rc = VINF_SUCCESS; + va_list va; + + va_start(va, cArgs); + rc = vusbDevIoThreadExecV(pDev, fFlags, pfnFunction, cArgs, va); + va_end(va); + return rc; +} + + +/** + * Executes a given function synchronously on the I/O thread waiting for it to complete. + * + * @returns IPRT status code. + * @param pDev The USB device instance data + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param ... The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...) +{ + int rc = VINF_SUCCESS; + va_list va; + + va_start(va, cArgs); + rc = vusbDevIoThreadExecV(pDev, VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC, pfnFunction, cArgs, va); + va_end(va); + return rc; +} + + +/** + * Initialize a new VUSB device. + * + * @returns VBox status code. + * @param pDev The VUSB device to initialize. + * @param pUsbIns Pointer to the PDM USB Device instance. + * @param pszCaptureFilename Optional fileame to capture the traffic to. + */ +int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename) +{ + /* + * Initialize the device data members. + * (All that are Non-Zero at least.) + */ + Assert(!pDev->IDevice.pfnReset); + Assert(!pDev->IDevice.pfnPowerOn); + Assert(!pDev->IDevice.pfnPowerOff); + Assert(!pDev->IDevice.pfnGetState); + Assert(!pDev->IDevice.pfnIsSavedStateSupported); + + pDev->IDevice.pfnReset = vusbIDeviceReset; + pDev->IDevice.pfnPowerOn = vusbIDevicePowerOn; + pDev->IDevice.pfnPowerOff = vusbIDevicePowerOff; + pDev->IDevice.pfnGetState = vusbIDeviceGetState; + pDev->IDevice.pfnIsSavedStateSupported = vusbIDeviceIsSavedStateSupported; + pDev->IDevice.pfnGetSpeed = vusbIDeviceGetSpeed; + pDev->pUsbIns = pUsbIns; + pDev->pHub = NULL; + pDev->enmState = VUSB_DEVICE_STATE_DETACHED; + pDev->cRefs = 1; + pDev->u8Address = VUSB_INVALID_ADDRESS; + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + pDev->i16Port = -1; + pDev->u16Status = 0; + pDev->pDescCache = NULL; + pDev->pCurCfgDesc = NULL; + pDev->paIfStates = NULL; + RTListInit(&pDev->LstAsyncUrbs); + memset(&pDev->aPipes[0], 0, sizeof(pDev->aPipes)); + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + { + int rc = RTCritSectInit(&pDev->aPipes[i].CritSectCtrl); + AssertRCReturn(rc, rc); + } + pDev->hResetTimer = NIL_TMTIMERHANDLE; + pDev->hSniffer = VUSBSNIFFER_NIL; + + int rc = RTCritSectInit(&pDev->CritSectAsyncUrbs); + AssertRCReturn(rc, rc); + + /* Create the URB pool. */ + rc = vusbUrbPoolInit(&pDev->UrbPool); + AssertRCReturn(rc, rc); + + /* Setup request queue executing synchronous tasks on the I/O thread. */ + rc = RTReqQueueCreate(&pDev->hReqQueueSync); + AssertRCReturn(rc, rc); + + /* + * Create the reset timer. Make sure the name is unique as we're generic code. + */ + static uint32_t volatile s_iSeq; + char szDesc[32]; + RTStrPrintf(szDesc, sizeof(szDesc), "VUSB Reset #%u", ASMAtomicIncU32(&s_iSeq)); + rc = PDMUsbHlpTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/, + szDesc, &pDev->hResetTimer); + AssertRCReturn(rc, rc); + + if (pszCaptureFilename) + { + rc = VUSBSnifferCreate(&pDev->hSniffer, 0, pszCaptureFilename, NULL, NULL); + AssertRCReturn(rc, rc); + } + + /* + * Get the descriptor cache from the device. (shall cannot fail) + */ + pDev->pDescCache = pUsbIns->pReg->pfnUsbGetDescriptorCache(pUsbIns); + AssertPtr(pDev->pDescCache); +#ifdef VBOX_STRICT + if (pDev->pDescCache->fUseCachedStringsDescriptors) + { + int32_t iPrevId = -1; + for (unsigned iLang = 0; iLang < pDev->pDescCache->cLanguages; iLang++) + { + Assert((int32_t)pDev->pDescCache->paLanguages[iLang].idLang > iPrevId); + iPrevId = pDev->pDescCache->paLanguages[iLang].idLang; + + int32_t idxPrevStr = -1; + PCPDMUSBDESCCACHESTRING paStrings = pDev->pDescCache->paLanguages[iLang].paStrings; + unsigned cStrings = pDev->pDescCache->paLanguages[iLang].cStrings; + for (unsigned iStr = 0; iStr < cStrings; iStr++) + { + Assert((int32_t)paStrings[iStr].idx > idxPrevStr); + idxPrevStr = paStrings[iStr].idx; + size_t cch = strlen(paStrings[iStr].psz); + Assert(cch <= 127); + } + } + } +#endif + + /* + * Allocate memory for the interface states. + */ + size_t cbIface = vusbDevMaxInterfaces(pDev) * sizeof(*pDev->paIfStates); + pDev->paIfStates = (PVUSBINTERFACESTATE)RTMemAllocZ(cbIface); + AssertMsgReturn(pDev->paIfStates, ("RTMemAllocZ(%d) failed\n", cbIface), VERR_NO_MEMORY); + + return VINF_SUCCESS; +} + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/VUSBInternal.h b/src/VBox/Devices/USB/VUSBInternal.h new file mode 100644 index 00000000..d4ac13b7 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBInternal.h @@ -0,0 +1,742 @@ +/* $Id: VUSBInternal.h $ */ +/** @file + * Virtual USB - Internal header. + * + * This subsystem implements USB devices in a host controller independent + * way. All the host controller code has to do is use VUSBROOTHUB for its + * root hub implementation and any emulated USB device may be plugged into + * the virtual bus. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBInternal_h +#define VBOX_INCLUDED_SRC_USB_VUSBInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/vusb.h> +#include <VBox/vmm/stam.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/vmm/pdmusb.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/req.h> +#include <iprt/asm.h> +#include <iprt/list.h> + +#include "VUSBSniffer.h" + +RT_C_DECLS_BEGIN + + +/** @defgroup grp_vusb_int VUSB Internals. + * @ingroup grp_vusb + * @internal + * @{ + */ + +/** @defgroup grp_vusb_int_dev Internal Device Operations, Structures and Constants. + * @{ + */ + +/** Pointer to a Virtual USB device (core). */ +typedef struct VUSBDEV *PVUSBDEV; +/** Pointer to a VUSB root hub. */ +typedef struct VUSBROOTHUB *PVUSBROOTHUB; + + +/** Number of the default control endpoint */ +#define VUSB_PIPE_DEFAULT 0 + +/** @name Device addresses + * @{ */ +#define VUSB_DEFAULT_ADDRESS 0 +#define VUSB_INVALID_ADDRESS UINT8_C(0xff) +/** @} */ + +/** @name Feature bits (1<<FEATURE for the u16Status bit) + * @{ */ +#define VUSB_DEV_SELF_POWERED 0 +#define VUSB_DEV_REMOTE_WAKEUP 1 +#define VUSB_EP_HALT 0 +/** @} */ + +/** Maximum number of endpoint addresses */ +#define VUSB_PIPE_MAX 16 + +/** + * The VUSB URB data. + */ +typedef struct VUSBURBVUSBINT +{ + /** Node for one of the lists the URB can be in. */ + RTLISTNODE NdLst; + /** Pointer to the URB this structure is part of. */ + PVUSBURB pUrb; + /** Pointer to the original for control messages. */ + PVUSBURB pCtrlUrb; + /** Pointer to the VUSB device. + * This may be NULL if the destination address is invalid. */ + PVUSBDEV pDev; + /** Specific to the pfnFree function. */ + void *pvFreeCtx; + /** + * Callback which will free the URB once it's reaped and completed. + * @param pUrb The URB. + */ + DECLCALLBACKMEMBER(void, pfnFree,(PVUSBURB pUrb)); + /** Submit timestamp. (logging only) */ + uint64_t u64SubmitTS; +} VUSBURBVUSBINT; + +/** + * Control-pipe stages. + */ +typedef enum CTLSTAGE +{ + /** the control pipe is in the setup stage. */ + CTLSTAGE_SETUP = 0, + /** the control pipe is in the data stage. */ + CTLSTAGE_DATA, + /** the control pipe is in the status stage. */ + CTLSTAGE_STATUS +} CTLSTAGE; + +/** + * Extra data for a control pipe. + * + * This is state information needed for the special multi-stage + * transfers performed on this kind of pipes. + */ +typedef struct vusb_ctrl_extra +{ + /** Current pipe stage. */ + CTLSTAGE enmStage; + /** Success indicator. */ + bool fOk; + /** Set if the message URB has been submitted. */ + bool fSubmitted; + /** Pointer to the SETUP. + * This is a pointer to Urb->abData[0]. */ + PVUSBSETUP pMsg; + /** Current DATA pointer. + * This starts at pMsg + 1 and is incremented at we read/write data. */ + uint8_t *pbCur; + /** The amount of data left to read on IN operations. + * On OUT operations this is not used. */ + uint32_t cbLeft; + /** The amount of data we can house. + * This starts at the default 8KB, and this structure will be reallocated to + * accommodate any larger request (unlikely). */ + uint32_t cbMax; + /** VUSB internal data for the extra URB. */ + VUSBURBVUSBINT VUsbExtra; + /** The message URB. */ + VUSBURB Urb; +} VUSBCTRLEXTRA, *PVUSBCTRLEXTRA; + +void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra); +void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra); + +/** + * A VUSB pipe + */ +typedef struct vusb_pipe +{ + PCVUSBDESCENDPOINTEX in; + PCVUSBDESCENDPOINTEX out; + /** Pointer to the extra state data required to run a control pipe. */ + PVUSBCTRLEXTRA pCtrl; + /** Critical section serializing access to the extra state data for a control pipe. */ + RTCRITSECT CritSectCtrl; + /** Count of active async transfers. */ + volatile uint32_t async; + /** Last scheduled frame - only valid for isochronous IN endpoints. */ + uint32_t uLastFrameIn; + /** Last scheduled frame - only valid for isochronous OUT endpoints. */ + uint32_t uLastFrameOut; +} VUSBPIPE; +/** Pointer to a VUSB pipe structure. */ +typedef VUSBPIPE *PVUSBPIPE; + + +/** + * Interface state and possible settings. + */ +typedef struct vusb_interface_state +{ + /** Pointer to the interface descriptor of the currently selected (active) + * interface. */ + PCVUSBDESCINTERFACEEX pCurIfDesc; + /** Pointer to the interface settings. */ + PCVUSBINTERFACE pIf; +} VUSBINTERFACESTATE; +/** Pointer to interface state. */ +typedef VUSBINTERFACESTATE *PVUSBINTERFACESTATE; +/** Pointer to const interface state. */ +typedef const VUSBINTERFACESTATE *PCVUSBINTERFACESTATE; + + +/** + * VUSB URB pool. + */ +typedef struct VUSBURBPOOL +{ + /** Critical section protecting the pool. */ + RTCRITSECT CritSectPool; + /** Chain of free URBs by type. (Singly linked) */ + RTLISTANCHOR aLstFreeUrbs[VUSBXFERTYPE_ELEMENTS]; + /** The number of URBs in the pool. */ + volatile uint32_t cUrbsInPool; + /** Align the size to a 8 byte boundary. */ + uint32_t Alignment0; +} VUSBURBPOOL; +/** Pointer to a VUSB URB pool. */ +typedef VUSBURBPOOL *PVUSBURBPOOL; + +AssertCompileSizeAlignment(VUSBURBPOOL, 8); + +/** + * A Virtual USB device (core). + * + * @implements VUSBIDEVICE + */ +typedef struct VUSBDEV +{ + /** The device interface exposed to the HCI. */ + VUSBIDEVICE IDevice; + /** Pointer to the PDM USB device instance. */ + PPDMUSBINS pUsbIns; + /** Pointer to the roothub this device is attached to. */ + PVUSBROOTHUB pHub; + /** The device state. */ + VUSBDEVICESTATE volatile enmState; + /** Reference counter to protect the device structure from going away. */ + uint32_t volatile cRefs; + + /** The device address. */ + uint8_t u8Address; + /** The new device address. */ + uint8_t u8NewAddress; + /** The port. */ + int16_t i16Port; + /** Device status. (VUSB_DEV_SELF_POWERED or not.) */ + uint16_t u16Status; + + /** Pointer to the descriptor cache. + * (Provided by the device thru the pfnGetDescriptorCache method.) */ + PCPDMUSBDESCCACHE pDescCache; + /** Current configuration. */ + PCVUSBDESCCONFIGEX pCurCfgDesc; + + /** Current interface state (including alternate interface setting) - maximum + * valid index is config->bNumInterfaces + */ + PVUSBINTERFACESTATE paIfStates; + + /** Pipe/direction -> endpoint descriptor mapping */ + VUSBPIPE aPipes[VUSB_PIPE_MAX]; + /** Critical section protecting the active URB list. */ + RTCRITSECT CritSectAsyncUrbs; + /** List of active async URBs. */ + RTLISTANCHOR LstAsyncUrbs; + + /** Dumper state. */ + union VUSBDEVURBDUMPERSTATE + { + /** The current scsi command. */ + uint8_t u8ScsiCmd; + } Urb; + + /** The reset timer handle. */ + TMTIMERHANDLE hResetTimer; + /** Reset handler arguments. */ + void *pvArgs; + /** URB submit and reap thread. */ + RTTHREAD hUrbIoThread; + /** Request queue for executing tasks on the I/O thread which should be done + * synchronous and without any other thread accessing the USB device. */ + RTREQQUEUE hReqQueueSync; + /** Sniffer instance for this device if configured. */ + VUSBSNIFFER hSniffer; + /** Flag whether the URB I/O thread should terminate. */ + bool volatile fTerminate; + /** Flag whether the I/O thread was woken up. */ + bool volatile fWokenUp; +#if HC_ARCH_BITS == 32 + /** Align the size to a 8 byte boundary. */ + bool afAlignment0[2]; +#endif + /** The pool of free URBs for faster allocation. */ + VUSBURBPOOL UrbPool; +} VUSBDEV; +AssertCompileSizeAlignment(VUSBDEV, 8); + + +int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename); +void vusbDevDestroy(PVUSBDEV pDev); +bool vusbDevDoSelectConfig(PVUSBDEV dev, PCVUSBDESCCONFIGEX pCfg); +void vusbDevMapEndpoint(PVUSBDEV dev, PCVUSBDESCENDPOINTEX ep); +int vusbDevDetach(PVUSBDEV pDev); +int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub); +DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev); +size_t vusbDevMaxInterfaces(PVUSBDEV dev); + +void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address); +bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf); + + +/** @} */ + + +/** @defgroup grp_vusb_int_hub Internal Hub Operations, Structures and Constants. + * @{ + */ + + +/** @} */ + + +/** @defgroup grp_vusb_int_roothub Internal Root Hub Operations, Structures and Constants. + * @{ + */ + +/** + * Per transfer type statistics. + */ +typedef struct VUSBROOTHUBTYPESTATS +{ + STAMCOUNTER StatUrbsSubmitted; + STAMCOUNTER StatUrbsFailed; + STAMCOUNTER StatUrbsCancelled; + + STAMCOUNTER StatReqBytes; + STAMCOUNTER StatReqReadBytes; + STAMCOUNTER StatReqWriteBytes; + + STAMCOUNTER StatActBytes; + STAMCOUNTER StatActReadBytes; + STAMCOUNTER StatActWriteBytes; +} VUSBROOTHUBTYPESTATS, *PVUSBROOTHUBTYPESTATS; + + + +/** Pointer to a VUSBROOTHUBLOAD struct. */ +typedef struct VUSBROOTHUBLOAD *PVUSBROOTHUBLOAD; + +/** + * The instance data of a root hub driver. + * + * This extends the generic VUSB hub. + * + * @implements VUSBIROOTHUBCONNECTOR + */ +typedef struct VUSBROOTHUB +{ + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** Pointer to the root hub port interface we're attached to. */ + PVUSBIROOTHUBPORT pIRhPort; + /** Connector interface exposed upwards. */ + VUSBIROOTHUBCONNECTOR IRhConnector; + + /** Critical section protecting the device arrays. */ + RTCRITSECT CritSectDevices; + /** Array of pointers to USB devices indexed by the port the device is on. */ + PVUSBDEV apDevByPort[VUSB_DEVICES_MAX]; + /** Array of pointers to USB devices indexed by the address assigned. */ + PVUSBDEV apDevByAddr[VUSB_DEVICES_MAX]; + /** Structure after a saved state load to re-attach devices. */ + PVUSBROOTHUBLOAD pLoad; + + /** Roothub device state. */ + VUSBDEVICESTATE enmState; + /** Number of ports this roothub offers. */ + uint16_t cPorts; + /** Number of devices attached to this roothub currently. */ + uint16_t cDevices; + /** Name of the roothub. Used for logging. */ + char *pszName; + /** URB pool for URBs from the roothub. */ + VUSBURBPOOL UrbPool; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment0; +#endif + + /** Availability Bitmap. */ + VUSBPORTBITMAP Bitmap; + + /** Sniffer instance for the root hub. */ + VUSBSNIFFER hSniffer; + /** Version of the attached Host Controller. */ + uint32_t fHcVersions; + /** Size of the HCI specific data for each URB. */ + size_t cbHci; + /** Size of the HCI specific TD. */ + size_t cbHciTd; + + /** The periodic frame processing thread. */ + R3PTRTYPE(PPDMTHREAD) hThreadPeriodFrame; + /** Event semaphore to interact with the periodic frame processing thread. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrame; + /** Event semaphore to release the thread waiting for the periodic frame processing thread to stop. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrameStopped; + /** Current default frame rate for periodic frame processing thread. */ + volatile uint32_t uFrameRateDefault; + /** Current frame rate (can be lower than the default frame rate if there is no activity). */ + uint32_t uFrameRate; + /** How long to wait until the next frame. */ + uint64_t nsWait; + /** Timestamp when the last frame was processed. */ + uint64_t tsFrameProcessed; + /** Number of USB work cycles with no transfers. */ + uint32_t cIdleCycles; + + /** Flag whether a frame is currently being processed. */ + volatile bool fFrameProcessing; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment1; +#endif + +#ifdef LOG_ENABLED + /** A serial number for URBs submitted on the roothub instance. + * Only logging builds. */ + uint32_t iSerial; + /** Alignment */ + uint32_t Alignment2; +#endif +#ifdef VBOX_WITH_STATISTICS + VUSBROOTHUBTYPESTATS Total; + VUSBROOTHUBTYPESTATS aTypes[VUSBXFERTYPE_MSG]; + STAMCOUNTER StatIsocReqPkts; + STAMCOUNTER StatIsocReqReadPkts; + STAMCOUNTER StatIsocReqWritePkts; + STAMCOUNTER StatIsocActPkts; + STAMCOUNTER StatIsocActReadPkts; + STAMCOUNTER StatIsocActWritePkts; + struct + { + STAMCOUNTER Pkts; + STAMCOUNTER Ok; + STAMCOUNTER Ok0; + STAMCOUNTER DataUnderrun; + STAMCOUNTER DataUnderrun0; + STAMCOUNTER DataOverrun; + STAMCOUNTER NotAccessed; + STAMCOUNTER Misc; + STAMCOUNTER Bytes; + } aStatIsocDetails[8]; + + STAMPROFILE StatReapAsyncUrbs; + STAMPROFILE StatSubmitUrb; + STAMCOUNTER StatFramesProcessedClbk; + STAMCOUNTER StatFramesProcessedThread; +#endif +} VUSBROOTHUB; +AssertCompileMemberAlignment(VUSBROOTHUB, IRhConnector, 8); +AssertCompileMemberAlignment(VUSBROOTHUB, Bitmap, 8); +AssertCompileMemberAlignment(VUSBROOTHUB, CritSectDevices, 8); +#ifdef VBOX_WITH_STATISTICS +AssertCompileMemberAlignment(VUSBROOTHUB, Total, 8); +#endif + +/** Converts a pointer to VUSBROOTHUB::IRhConnector to a PVUSBROOTHUB. */ +#define VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface) (PVUSBROOTHUB)( (uintptr_t)(pInterface) - RT_UOFFSETOF(VUSBROOTHUB, IRhConnector) ) + +/** + * URB cancellation modes + */ +typedef enum CANCELMODE +{ + /** complete the URB with an error (CRC). */ + CANCELMODE_FAIL = 0, + /** do not change the URB contents. */ + CANCELMODE_UNDO +} CANCELMODE; + +/** @} */ + + + +/** @defgroup grp_vusb_int_urb Internal URB Operations, Structures and Constants. + * @{ */ +int vusbUrbSubmit(PVUSBURB pUrb); +void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies); +void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies); +void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode); +void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode); +void vusbUrbRipe(PVUSBURB pUrb); +void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb); +int vusbUrbSubmitHardError(PVUSBURB pUrb); +int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb); +int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev); +int vusbDevUrbIoThreadCreate(PVUSBDEV pDev); +int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev); +DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching); +DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args); +DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...); +DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...); +DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode); + +DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback); + +int vusbUrbQueueAsyncRh(PVUSBURB pUrb); + +bool vusbDevIsDescriptorInCache(PVUSBDEV pDev, PCVUSBSETUP pSetup); + +/** + * Initializes the given URB pool. + * + * @returns VBox status code. + * @param pUrbPool The URB pool to initialize. + */ +DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool); + +/** + * Destroy a given URB pool freeing all ressources. + * + * @returns nothing. + * @param pUrbPool The URB pool to destroy. + */ +DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool); + +/** + * Allocate a new URB from the given URB pool. + * + * @returns Pointer to the new URB or NULL if out of memory. + * @param pUrbPool The URB pool to allocate from. + * @param enmType Type of the URB. + * @param enmDir The direction of the URB. + * @param cbData The number of bytes to allocate for the data buffer. + * @param cbHci Size of the data private to the HCI for each URB when allocated. + * @param cbHciTd Size of one transfer descriptor. + * @param cTds Number of transfer descriptors. + */ +DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, size_t cbData, + size_t cbHci, size_t cbHciTd, unsigned cTds); + +/** + * Frees a given URB. + * + * @returns nothing. + * @param pUrbPool The URB pool the URB was allocated from. + * @param pUrb The URB to free. + */ +DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb); + +#ifdef LOG_ENABLED +/** + * Logs an URB in the debug log. + * + * @returns nothing. + * @param pUrb The URB to log. + * @param pszMsg Additional message to log. + * @param fComplete Flag whther the URB is completing. + */ +DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete); + +/** + * Return the USB direction as a string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir); + +/** + * Return the URB type as string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType); + +/** + * Return the URB status as string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus); +#endif + +DECLINLINE(void) vusbUrbUnlink(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + RTListNodeRemove(&pUrb->pVUsb->NdLst); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); +} + + +DECLINLINE(int) vusbUrbErrorRh(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED); + + return vusbUrbErrorRhEx(pRh, pUrb); +} + + +DECLINLINE(void) vusbUrbCompletionRh(PVUSBURB pUrb) +{ + PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); + AssertPtrReturnVoid(pRh); + + vusbUrbCompletionRhEx(pRh, pUrb); +} + + +/** @def vusbUrbAssert + * Asserts that a URB is valid. + */ +#ifdef VBOX_STRICT +# define vusbUrbAssert(pUrb) do { \ + AssertPtr((pUrb)); \ + AssertMsg((pUrb)->u32Magic == VUSBURB_MAGIC, ("%#x", (pUrb)->u32Magic)); \ + AssertMsg((pUrb)->enmState > VUSBURBSTATE_INVALID && (pUrb)->enmState < VUSBURBSTATE_END, \ + ("%d\n", (pUrb)->enmState)); \ + } while (0) +#else +# define vusbUrbAssert(pUrb) do {} while (0) +#endif + +/** + * @def VUSBDEV_ASSERT_VALID_STATE + * Asserts that the give device state is valid. + */ +#define VUSBDEV_ASSERT_VALID_STATE(enmState) \ + AssertMsg((enmState) > VUSB_DEVICE_STATE_INVALID && (enmState) < VUSB_DEVICE_STATE_DESTROYED, ("enmState=%#x\n", enmState)); + +/** Executes a function synchronously. */ +#define VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC RT_BIT_32(0) + +/** @} */ + + +/** + * Gets the roothub of a device. + * + * @returns Pointer to the roothub instance the device is attached to. + * @returns NULL if not attached to any hub. + * @param pDev Pointer to the device in question. + */ +DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev) +{ + if (!pDev->pHub) + return NULL; + return pDev->pHub; +} + + +/** + * Returns the state of the USB device. + * + * @returns State of the USB device. + * @param pDev Pointer to the device. + */ +DECLINLINE(VUSBDEVICESTATE) vusbDevGetState(PVUSBDEV pDev) +{ + VUSBDEVICESTATE enmState = (VUSBDEVICESTATE)ASMAtomicReadU32((volatile uint32_t *)&pDev->enmState); + VUSBDEV_ASSERT_VALID_STATE(enmState); + return enmState; +} + + +/** + * Sets the given state for the USB device. + * + * @returns The old state of the device. + * @param pDev Pointer to the device. + * @param enmState The new state to set. + */ +DECLINLINE(VUSBDEVICESTATE) vusbDevSetState(PVUSBDEV pDev, VUSBDEVICESTATE enmState) +{ + VUSBDEV_ASSERT_VALID_STATE(enmState); + VUSBDEVICESTATE enmStateOld = (VUSBDEVICESTATE)ASMAtomicXchgU32((volatile uint32_t *)&pDev->enmState, enmState); + VUSBDEV_ASSERT_VALID_STATE(enmStateOld); + return enmStateOld; +} + + +/** + * Compare and exchange the states for the given USB device. + * + * @returns true if the state was changed. + * @returns false if the state wasn't changed. + * @param pDev Pointer to the device. + * @param enmStateNew The new state to set. + * @param enmStateOld The old state to compare with. + */ +DECLINLINE(bool) vusbDevSetStateCmp(PVUSBDEV pDev, VUSBDEVICESTATE enmStateNew, VUSBDEVICESTATE enmStateOld) +{ + VUSBDEV_ASSERT_VALID_STATE(enmStateNew); + VUSBDEV_ASSERT_VALID_STATE(enmStateOld); + return ASMAtomicCmpXchgU32((volatile uint32_t *)&pDev->enmState, enmStateNew, enmStateOld); +} + +/** + * Retains the given VUSB device pointer. + * + * @returns New reference count. + * @param pThis The VUSB device pointer. + * @param pszWho Caller of the retaining. + */ +DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis, const char *pszWho) +{ + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + +/** + * Releases the given VUSB device pointer. + * + * @returns New reference count. + * @retval 0 if no onw is holding a reference anymore causing the device to be destroyed. + * @param pThis The VUSB device pointer. + * @param pszWho Caller of the retaining. + */ +DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis, const char *pszWho) +{ + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + vusbDevDestroy(pThis); + return cRefs; +} + +/** Strings for the CTLSTAGE enum values. */ +extern const char * const g_apszCtlStates[4]; + +/** @} */ +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBInternal_h */ + diff --git a/src/VBox/Devices/USB/VUSBSniffer.cpp b/src/VBox/Devices/USB/VUSBSniffer.cpp new file mode 100644 index 00000000..96282294 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSniffer.cpp @@ -0,0 +1,247 @@ +/* $Id: VUSBSniffer.cpp $ */ +/** @file + * Virtual USB - Sniffer facility. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/file.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERINT +{ + /** The file handle to dump to. */ + RTFILE hFile; + /** Fast Mutex protecting the state against concurrent access. */ + RTSEMFASTMUTEX hMtx; + /** File stream. */ + VUSBSNIFFERSTRM Strm; + /** Pointer to the used format. */ + PCVUSBSNIFFERFMT pFmt; + /** Format specific state - variable in size. */ + uint8_t abFmt[1]; +} VUSBSNIFFERINT; +/** Pointer to the internal VUSB sniffer state. */ +typedef VUSBSNIFFERINT *PVUSBSNIFFERINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +static PCVUSBSNIFFERFMT s_aVUsbSnifferFmts[] = +{ + &g_VUsbSnifferFmtPcapNg, + &g_VUsbSnifferFmtUsbMon, + &g_VUsbSnifferFmtVmx, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** @interface_method_impl{VUSBSNIFFERSTRM,pfnWrite} */ +static DECLCALLBACK(int) vusbSnifferStrmWrite(PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf) +{ + PVUSBSNIFFERINT pThis = RT_FROM_MEMBER(pStrm, VUSBSNIFFERINT, Strm); + + return RTFileWrite(pThis->hFile, pvBuf, cbBuf, NULL); +} + +/** + * Returns a supporting format writer taken from the given format name. + * + * @returns Pointer to the format structure or NULL if none was found. + * @param pszFmt The format to use. + */ +static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromString(const char *pszFmt) +{ + for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++) + { + if (!RTStrICmp(pszFmt, s_aVUsbSnifferFmts[i]->szName)) + return s_aVUsbSnifferFmts[i]; + } + + return NULL; +} + +/** + * Returns a supporting format writer taken from the file suffix. + * + * @returns Pointer to the format structure or NULL if none was found. + * @param pszFilename The file name to take the suffix from. + */ +static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromFilename(const char *pszFilename) +{ + const char *pszFileExt = RTPathSuffix(pszFilename); + if (!pszFileExt) + return NULL; + + pszFileExt++; /* Skip the dot. */ + + for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++) + { + unsigned idxFileExt = 0; + + while (s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt]) + { + if (!RTStrICmp(pszFileExt, s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt])) + return s_aVUsbSnifferFmts[i]; + + idxFileExt++; + } + } + + return NULL; +} + + +DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags, + const char *pszCaptureFilename, const char *pszFmt, + const char *pszDesc) +{ + RT_NOREF(pszDesc); + int rc = VINF_SUCCESS; + PVUSBSNIFFERINT pThis = NULL; + PCVUSBSNIFFERFMT pFmt = NULL; + + if (pszFmt) + pFmt = vusbSnifferGetFmtFromString(pszFmt); + else + pFmt = vusbSnifferGetFmtFromFilename(pszCaptureFilename); + + if (!pFmt) + return VERR_NOT_FOUND; + + pThis = (PVUSBSNIFFERINT)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBSNIFFERINT, abFmt[pFmt->cbFmt])); + if (pThis) + { + pThis->hFile = NIL_RTFILE; + pThis->hMtx = NIL_RTSEMFASTMUTEX; + pThis->pFmt = pFmt; + pThis->Strm.pfnWrite = vusbSnifferStrmWrite; + + rc = RTSemFastMutexCreate(&pThis->hMtx); + if (RT_SUCCESS(rc)) + { + uint32_t fFileFlags = RTFILE_O_DENY_NONE | RTFILE_O_WRITE | RTFILE_O_READ; + if (fFlags & VUSBSNIFFER_F_NO_REPLACE) + fFileFlags |= RTFILE_O_CREATE; + else + fFileFlags |= RTFILE_O_CREATE_REPLACE; + + rc = RTFileOpen(&pThis->hFile, pszCaptureFilename, fFileFlags); + if (RT_SUCCESS(rc)) + { + rc = pThis->pFmt->pfnInit((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], &pThis->Strm); + if (RT_SUCCESS(rc)) + { + *phSniffer = pThis; + return VINF_SUCCESS; + } + + RTFileClose(pThis->hFile); + pThis->hFile = NIL_RTFILE; + RTFileDelete(pszCaptureFilename); + } + RTSemFastMutexDestroy(pThis->hMtx); + pThis->hMtx = NIL_RTSEMFASTMUTEX; + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Destroys the given VUSB sniffer instance. + * + * @returns nothing. + * @param hSniffer The sniffer instance to destroy. + */ +DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer) +{ + PVUSBSNIFFERINT pThis = hSniffer; + + int rc = RTSemFastMutexRequest(pThis->hMtx); + AssertRC(rc); + + pThis->pFmt->pfnDestroy((PVUSBSNIFFERFMTINT)&pThis->abFmt[0]); + + if (pThis->hFile != NIL_RTFILE) + RTFileClose(pThis->hFile); + + RTSemFastMutexRelease(pThis->hMtx); + RTSemFastMutexDestroy(pThis->hMtx); + RTMemFree(pThis); +} + +/** + * Records an VUSB event. + * + * @returns VBox status code. + * @param hSniffer The sniffer instance. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ +DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + int rc = VINF_SUCCESS; + PVUSBSNIFFERINT pThis = hSniffer; + + /* Write the packet to the capture file. */ + rc = RTSemFastMutexRequest(pThis->hMtx); + if (RT_SUCCESS(rc)) + { + rc = pThis->pFmt->pfnRecordEvent((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], pUrb, enmEvent); + RTSemFastMutexRelease(pThis->hMtx); + } + + return rc; +} + diff --git a/src/VBox/Devices/USB/VUSBSniffer.h b/src/VBox/Devices/USB/VUSBSniffer.h new file mode 100644 index 00000000..30d21301 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSniffer.h @@ -0,0 +1,111 @@ +/* $Id: VUSBSniffer.h $ */ +/** @file + * Virtual USB - Sniffer facility. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBSniffer_h +#define VBOX_INCLUDED_SRC_USB_VUSBSniffer_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/vusb.h> + +RT_C_DECLS_BEGIN + +/** Opaque VUSB sniffer handle. */ +typedef struct VUSBSNIFFERINT *VUSBSNIFFER; +/** Pointer to a VUSB sniffer handle. */ +typedef VUSBSNIFFER *PVUSBSNIFFER; + +/** NIL sniffer instance handle. */ +#define VUSBSNIFFER_NIL ((VUSBSNIFFER)0) + +/** + * VUSB Sniffer event types. + */ +typedef enum VUSBSNIFFEREVENT +{ + /** Invalid event. */ + VUSBSNIFFEREVENT_INVALID = 0, + /** URB submit event. */ + VUSBSNIFFEREVENT_SUBMIT, + /** URB complete event. */ + VUSBSNIFFEREVENT_COMPLETE, + /** URB submit failed event. */ + VUSBSNIFFEREVENT_ERROR_SUBMIT, + /** URB completed with error event. */ + VUSBSNIFFEREVENT_ERROR_COMPLETE, + /** 32bit hack. */ + VUSBSNIFFEREVENT_32BIT_HACK = 0x7fffffff +} VUSBSNIFFEREVENT; + +/** VUSB Sniffer creation flags. + * @{ */ +/** Default flags. */ +#define VUSBSNIFFER_F_DEFAULT 0 +/** Don't overwrite any existing capture file. */ +#define VUSBSNIFFER_F_NO_REPLACE RT_BIT_32(0) +/** @} */ + +/** + * Create a new VUSB sniffer instance dumping to the given capture file. + * + * @returns VBox status code. + * @param phSniffer Where to store the handle to the sniffer instance on success. + * @param fFlags Flags, combination of VUSBSNIFFER_F_* + * @param pszCaptureFilename The filename to use for capturing the sniffed data. + * @param pszFmt The format of the dump, NULL to select one based on the filename + * extension. + * @param pszDesc Optional description for the dump. + */ +DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags, + const char *pszCaptureFilename, const char *pszFmt, + const char *pszDesc); + +/** + * Destroys the given VUSB sniffer instance. + * + * @returns nothing. + * @param hSniffer The sniffer instance to destroy. + */ +DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer); + +/** + * Records an VUSB event. + * + * @returns VBox status code. + * @param hSniffer The sniffer instance. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ +DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent); + + +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSniffer_h */ + diff --git a/src/VBox/Devices/USB/VUSBSnifferInternal.h b/src/VBox/Devices/USB/VUSBSnifferInternal.h new file mode 100644 index 00000000..feed9a1d --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferInternal.h @@ -0,0 +1,119 @@ +/* $Id: VUSBSnifferInternal.h $ */ +/** @file + * Virtual USB Sniffer facility - Internal header. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h +#define VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> + +#include "VUSBSniffer.h" + +RT_C_DECLS_BEGIN + +/** Pointer to a stream operations structure. */ +typedef struct VUSBSNIFFERSTRM *PVUSBSNIFFERSTRM; +/** Pointer to the internal format specific state. */ +typedef struct VUSBSNIFFERFMTINT *PVUSBSNIFFERFMTINT; + +/** + * Stream operations structure. + */ +typedef struct VUSBSNIFFERSTRM +{ + /** + * Write the given buffer to the underlying stream. + * + * @returns VBox status code. + * @param pStrm The pointer to the interface containing this method. + * @param pvBuf The buffer to write. + * @param cbBuf How many bytes to write. + */ + DECLR3CALLBACKMEMBER(int, pfnWrite, (PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf)); + +} VUSBSNIFFERSTRM; + + +/** + * VUSB Sniffer format backend. + */ +typedef struct VUSBSNIFFERFMT +{ + /** Name of the format. */ + char szName[16]; + /** Description of the format. */ + const char *pszDesc; + /** Supported file extensions - terminated with a NULL entry. */ + const char **papszFileExts; + /** Size of the format specific state structure in bytes. */ + size_t cbFmt; + + /** + * Initializes the format. + * + * @returns VBox status code. + * @param pThis Pointer to the unitialized format specific state. + * @param pStrm The stream to use for writing. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm)); + + /** + * Destroys the format instance. + * + * @returns nothing. + * @param pThis Pointer to the format specific state. + */ + DECLR3CALLBACKMEMBER(void, pfnDestroy, (PVUSBSNIFFERFMTINT pThis)); + + /** + * Records the given VUSB event. + * + * @returns VBox status code. + * @param pThis Pointer to the format specific state. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ + DECLR3CALLBACKMEMBER(int, pfnRecordEvent, (PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)); + +} VUSBSNIFFERFMT; +/** Pointer to a VUSB Sniffer format backend. */ +typedef VUSBSNIFFERFMT *PVUSBSNIFFERFMT; +/** Pointer to a const VUSB Sniffer format backend. */ +typedef const VUSBSNIFFERFMT *PCVUSBSNIFFERFMT; + +/** PCAP-Ng format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg; +/** VMware VMX log format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx; +/** Linux UsbMon log format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon; + +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h */ diff --git a/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp new file mode 100644 index 00000000..7f9877cb --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp @@ -0,0 +1,741 @@ +/* $Id: VUSBSnifferPcapNg.cpp $ */ +/** @file + * Virtual USB Sniffer facility - PCAP-NG format writer. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/buildconfig.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** DumpFile Section Header Block type. */ +#define DUMPFILE_SHB_BLOCK_TYPE UINT32_C(0x0a0d0d0a) +/** The byte order magic value. */ +#define DUMPFILE_SHB_BYTE_ORDER_MAGIC UINT32_C(0x1a2b3c4d) +/** Current major version. */ +#define DUMPFILE_SHB_VERSION_MAJOR UINT16_C(1) +/** Current minor version. */ +#define DUMPFILE_SHB_VERSION_MINOR UINT16_C(0) + +/** Block type for the interface descriptor block. */ +#define DUMPFILE_IDB_BLOCK_TYPE UINT32_C(0x00000001) +/** USB link type. */ +#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX UINT16_C(189) +#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED UINT16_C(220) + +/** Block type for an enhanced packet block. */ +#define DUMPFILE_EPB_BLOCK_TYPE UINT32_C(0x00000006) + +/** USB packet event types. */ +#define DUMPFILE_USB_EVENT_TYPE_SUBMIT ('S') +#define DUMPFILE_USB_EVENT_TYPE_COMPLETE ('C') +#define DUMPFILE_USB_EVENT_TYPE_ERROR ('E') + +#define DUMPFILE_OPTION_CODE_END UINT16_C(0) +#define DUMPFILE_OPTION_CODE_COMMENT UINT16_C(1) + +#define DUMPFILE_OPTION_CODE_HARDWARE UINT16_C(2) +#define DUMPFILE_OPTION_CODE_OS UINT16_C(3) +#define DUMPFILE_OPTION_CODE_USERAPP UINT16_C(4) + +#define DUMPFILE_IDB_OPTION_TS_RESOLUTION UINT16_C(9) + + +/********************************************************************************************************************************* +* DumpFile format structures * +*********************************************************************************************************************************/ + +/** + * DumpFile Block header. + */ +typedef struct DumpFileBlockHdr +{ + /** Block type. */ + uint32_t u32BlockType; + /** Block total length. */ + uint32_t u32BlockTotalLength; +} DumpFileBlockHdr; +/** Pointer to a block header. */ +typedef DumpFileBlockHdr *PDumpFileBlockHdr; + +/** + * DumpFile Option header. + */ +typedef struct DumpFileOptionHdr +{ + /** Option code. */ + uint16_t u16OptionCode; + /** Block total length. */ + uint16_t u16OptionLength; +} DumpFileOptionHdr; +/** Pointer to a option header. */ +typedef DumpFileOptionHdr *PDumpFileOptionHdr; + +/** + * DumpFile Section Header Block. + */ +typedef struct DumpFileShb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Byte order magic. */ + uint32_t u32ByteOrderMagic; + /** Major version. */ + uint16_t u16VersionMajor; + /** Minor version. */ + uint16_t u16VersionMinor; + /** Section length. */ + uint64_t u64SectionLength; +} DumpFileShb; +/** Pointer to a Section Header Block. */ +typedef DumpFileShb *PDumpFileShb; + +/** + * DumpFile Interface description block. + */ +typedef struct DumpFileIdb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Link type. */ + uint16_t u16LinkType; + /** Reserved. */ + uint16_t u16Reserved; + /** Maximum number of bytes dumped from each packet. */ + uint32_t u32SnapLen; +} DumpFileIdb; +/** Pointer to an Interface description block. */ +typedef DumpFileIdb *PDumpFileIdb; + +/** + * DumpFile Enhanced packet block. + */ +typedef struct DumpFileEpb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Interface ID. */ + uint32_t u32InterfaceId; + /** Timestamp (high). */ + uint32_t u32TimestampHigh; + /** Timestamp (low). */ + uint32_t u32TimestampLow; + /** Captured packet length. */ + uint32_t u32CapturedLen; + /** Original packet length. */ + uint32_t u32PacketLen; +} DumpFileEpb; +/** Pointer to an Enhanced packet block. */ +typedef DumpFileEpb *PDumpFileEpb; + +/** + * USB setup URB data. + */ +typedef struct DumpFileUsbSetup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} DumpFileUsbSetup; +typedef DumpFileUsbSetup *PDumpFileUsbSetup; + +/** + * USB Isochronous data. + */ +typedef struct DumpFileIsoRec +{ + int32_t i32ErrorCount; + int32_t i32NumDesc; +} DumpFileIsoRec; +typedef DumpFileIsoRec *PDumpFileIsoRec; + +/** + * USB packet header (Linux mmapped variant). + */ +typedef struct DumpFileUsbHeaderLnxMmapped +{ + /** Packet Id. */ + uint64_t u64Id; + /** Event type. */ + uint8_t u8EventType; + /** Transfer type. */ + uint8_t u8TransferType; + /** Endpoint number. */ + uint8_t u8EndpointNumber; + /** Device address. */ + uint8_t u8DeviceAddress; + /** Bus id. */ + uint16_t u16BusId; + /** Setup flag != 0 if the URB setup header is not present. */ + uint8_t u8SetupFlag; + /** Data present flag != 0 if the URB data is not present. */ + uint8_t u8DataFlag; + /** Timestamp (second part). */ + uint64_t u64TimestampSec; + /** Timestamp (us part). */ + uint32_t u32TimestampUSec; + /** Status. */ + int32_t i32Status; + /** URB length. */ + uint32_t u32UrbLength; + /** Recorded data length. */ + uint32_t u32DataLength; + /** Union of data for different URB types. */ + union + { + DumpFileUsbSetup UsbSetup; + DumpFileIsoRec IsoRec; + } u; + int32_t i32Interval; + int32_t i32StartFrame; + /** Copy of transfer flags. */ + uint32_t u32XferFlags; + /** Number of isochronous descriptors. */ + uint32_t u32NumDesc; +} DumpFileUsbHeaderLnxMmapped; +/** Pointer to a USB packet header. */ +typedef DumpFileUsbHeaderLnxMmapped *PDumpFileUsbHeaderLnxMmapped; + +AssertCompileSize(DumpFileUsbHeaderLnxMmapped, 64); + +/** + * USB packet isochronous descriptor. + */ +typedef struct DumpFileUsbIsoDesc +{ + int32_t i32Status; + uint32_t u32Offset; + uint32_t u32Len; + uint8_t au8Padding[4]; +} DumpFileUsbIsoDesc; +typedef DumpFileUsbIsoDesc *PDumpFileUsbIsoDesc; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; + /** Current size of the block being written. */ + uint32_t cbBlockCur; + /** Maximum size allocated for the block. */ + uint32_t cbBlockMax; + /** Current block header. */ + PDumpFileBlockHdr pBlockHdr; + /** Pointer to the block data which will be written on commit. */ + uint8_t *pbBlockData; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "pcap", + "pcapng", + NULL +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Allocates additional space for the block. + * + * @returns Pointer to the new unused space or NULL if out of memory. + * @param pThis The VUSB sniffer instance. + * @param cbAdditional The additional memory requested. + */ +static void *vusbSnifferBlockAllocSpace(PVUSBSNIFFERFMTINT pThis, uint32_t cbAdditional) +{ + /* Fast path where we have enough memory allocated. */ + if (pThis->cbBlockCur + cbAdditional <= pThis->cbBlockMax) + { + void *pv = pThis->pbBlockData + pThis->cbBlockCur; + pThis->cbBlockCur += cbAdditional; + return pv; + } + + /* Allocate additional memory. */ + uint32_t cbNew = pThis->cbBlockCur + cbAdditional; + uint8_t *pbDataNew = (uint8_t *)RTMemRealloc(pThis->pbBlockData, cbNew); + if (pbDataNew) + { + pThis->pbBlockData = pbDataNew; + pThis->pBlockHdr = (PDumpFileBlockHdr)pbDataNew; + + void *pv = pThis->pbBlockData + pThis->cbBlockCur; + pThis->cbBlockCur = cbNew; + pThis->cbBlockMax = cbNew; + return pv; + } + + return NULL; +} + +/** + * Adds new data to the current block. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param pvData The data to add. + * @param cbData Amount of data to add. + */ +static int vusbSnifferBlockAddData(PVUSBSNIFFERFMTINT pThis, const void *pvData, uint32_t cbData) +{ + int rc = VINF_SUCCESS; + + Assert(pThis->cbBlockCur); + AssertPtr(pThis->pBlockHdr); + + void *pv = vusbSnifferBlockAllocSpace(pThis, cbData); + if (pv) + memcpy(pv, pvData, cbData); + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Aligns the current block data to a 32bit boundary. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + */ +static int vusbSnifferBlockAlign(PVUSBSNIFFERFMTINT pThis) +{ + int rc = VINF_SUCCESS; + + Assert(pThis->cbBlockCur); + + /* Pad to 32bits. */ + uint8_t abPad[3] = { 0 }; + uint32_t cbPad = RT_ALIGN_32(pThis->cbBlockCur, 4) - pThis->cbBlockCur; + + Assert(cbPad <= 3); + if (cbPad) + rc = vusbSnifferBlockAddData(pThis, abPad, cbPad); + + return rc; +} + +/** + * Commits the current block to the capture file. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + */ +static int vusbSnifferBlockCommit(PVUSBSNIFFERFMTINT pThis) +{ + int rc = VINF_SUCCESS; + + AssertPtr(pThis->pBlockHdr); + + rc = vusbSnifferBlockAlign(pThis); + if (RT_SUCCESS(rc)) + { + /* Update the block total length field. */ + uint32_t *pcbTotalLength = (uint32_t *)vusbSnifferBlockAllocSpace(pThis, 4); + if (pcbTotalLength) + { + *pcbTotalLength = pThis->cbBlockCur; + pThis->pBlockHdr->u32BlockTotalLength = pThis->cbBlockCur; + + /* Write the data. */ + rc = pThis->pStrm->pfnWrite(pThis->pStrm, pThis->pbBlockData, pThis->cbBlockCur); + pThis->cbBlockCur = 0; + pThis->pBlockHdr = NULL; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Starts a new block for capturing. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param pBlockHdr Pointer to the block header for the new block. + * @param cbData Amount of data added with this block. + */ +static int vusbSnifferBlockNew(PVUSBSNIFFERFMTINT pThis, PDumpFileBlockHdr pBlockHdr, uint32_t cbData) +{ + int rc = VINF_SUCCESS; + + /* Validate we don't get called while another block is active. */ + Assert(!pThis->cbBlockCur); + Assert(!pThis->pBlockHdr); + pThis->pBlockHdr = (PDumpFileBlockHdr)vusbSnifferBlockAllocSpace(pThis, cbData); + if (pThis->pBlockHdr) + memcpy(pThis->pBlockHdr, pBlockHdr, cbData); + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Add a new option to the current block. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param u16OptionCode The option code identifying the type of option. + * @param pvOption Raw data for the option. + * @param cbOption Size of the optiob data. + */ +static int vusbSnifferAddOption(PVUSBSNIFFERFMTINT pThis, uint16_t u16OptionCode, const void *pvOption, size_t cbOption) +{ + AssertStmt((uint16_t)cbOption == cbOption, cbOption = UINT16_MAX); + DumpFileOptionHdr OptHdr; + OptHdr.u16OptionCode = u16OptionCode; + OptHdr.u16OptionLength = (uint16_t)cbOption; + int rc = vusbSnifferBlockAddData(pThis, &OptHdr, sizeof(OptHdr)); + if ( RT_SUCCESS(rc) + && u16OptionCode != DUMPFILE_OPTION_CODE_END + && cbOption != 0) + { + rc = vusbSnifferBlockAddData(pThis, pvOption, (uint16_t)cbOption); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockAlign(pThis); + } + + return rc; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtPcapNgInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + pThis->cbBlockCur = 0; + pThis->cbBlockMax = 0; + pThis->pbBlockData = NULL; + + /* Write header and link type blocks. */ + DumpFileShb Shb; + + Shb.Hdr.u32BlockType = DUMPFILE_SHB_BLOCK_TYPE; + Shb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */ + Shb.u32ByteOrderMagic = DUMPFILE_SHB_BYTE_ORDER_MAGIC; + Shb.u16VersionMajor = DUMPFILE_SHB_VERSION_MAJOR; + Shb.u16VersionMinor = DUMPFILE_SHB_VERSION_MINOR; + Shb.u64SectionLength = UINT64_C(0xffffffffffffffff); /* -1 */ + + /* Write the blocks. */ + int rc = vusbSnifferBlockNew(pThis, &Shb.Hdr, sizeof(Shb)); + if (RT_SUCCESS(rc)) + { + const char *pszOpt = RTBldCfgTargetDotArch(); + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_HARDWARE, pszOpt, strlen(pszOpt) + 1); + } + + if (RT_SUCCESS(rc)) + { + char szTmp[512]; + size_t cbTmp = sizeof(szTmp); + + RT_ZERO(szTmp); + + /* Build the OS code. */ + rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, cbTmp); + if (RT_SUCCESS(rc)) + { + size_t cb = strlen(szTmp); + + szTmp[cb] = ' '; + rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, &szTmp[cb + 1], cbTmp - (cb + 1)); + if (RT_SUCCESS(rc)) + { + cb = strlen(szTmp); + szTmp[cb] = ' '; + rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, &szTmp[cb + 1], cbTmp - (cb + 1)); + } + } + + if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_OS, szTmp, strlen(szTmp) + 1); + else + rc = VINF_SUCCESS; /* Skip OS code if building the string failed. */ + } + + if (RT_SUCCESS(rc)) + { + /** @todo Add product info. */ + } + + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + + /* Write Interface descriptor block. */ + if (RT_SUCCESS(rc)) + { + DumpFileIdb Idb; + + Idb.Hdr.u32BlockType = DUMPFILE_IDB_BLOCK_TYPE; + Idb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */ + Idb.u16LinkType = DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED; + Idb.u16Reserved = 0; + Idb.u32SnapLen = UINT32_C(0xffffffff); + + rc = vusbSnifferBlockNew(pThis, &Idb.Hdr, sizeof(Idb)); + if (RT_SUCCESS(rc)) + { + uint8_t u8TsResolution = 9; /* Nano second resolution. */ + /* Add timestamp resolution option. */ + rc = vusbSnifferAddOption(pThis, DUMPFILE_IDB_OPTION_TS_RESOLUTION, + &u8TsResolution, sizeof(u8TsResolution)); + } + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + } + + if ( RT_FAILURE(rc) + && pThis->pbBlockData) + RTMemFree(pThis->pbBlockData); + + return rc; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtPcapNgDestroy(PVUSBSNIFFERFMTINT pThis) +{ + if (pThis->pbBlockData) + RTMemFree(pThis->pbBlockData); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtPcapNgRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + DumpFileEpb Epb; + DumpFileUsbHeaderLnxMmapped UsbHdr; + uint32_t cbCapturedLength = sizeof(UsbHdr); + uint8_t *pbData = NULL; + + RTTIMESPEC TimeNow; + RTTimeNow(&TimeNow); + uint64_t u64TimestampEvent = RTTimeSpecGetNano(&TimeNow); + + /* Start with the enhanced packet block. */ + Epb.Hdr.u32BlockType = DUMPFILE_EPB_BLOCK_TYPE; + Epb.Hdr.u32BlockTotalLength = 0; + Epb.u32InterfaceId = 0; + Epb.u32TimestampHigh = (u64TimestampEvent >> 32) & UINT32_C(0xffffffff); + Epb.u32TimestampLow = u64TimestampEvent & UINT32_C(0xffffffff); + + UsbHdr.u64Id = (uintptr_t)pUrb; /** @todo check whether the pointer is a good ID. */ + uint32_t cbUrbLength; + switch (enmEvent) + { + case VUSBSNIFFEREVENT_SUBMIT: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_SUBMIT; + cbUrbLength = pUrb->cbData; + break; + case VUSBSNIFFEREVENT_COMPLETE: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_COMPLETE; + cbUrbLength = pUrb->cbData; + break; + case VUSBSNIFFEREVENT_ERROR_SUBMIT: + case VUSBSNIFFEREVENT_ERROR_COMPLETE: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_ERROR; + cbUrbLength = 0; + break; + default: + AssertMsgFailed(("Invalid event type %d\n", enmEvent)); + cbUrbLength = 0; + } + uint32_t cbDataLength = cbUrbLength; + pbData = &pUrb->abData[0]; + + uint32_t cIsocPkts = 0; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_ISOC: + { + int32_t cErrors = 0; + + UsbHdr.u8TransferType = 0; + cIsocPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < cIsocPkts; i++) + if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK + && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED) + cErrors++; + + UsbHdr.u.IsoRec.i32ErrorCount = cErrors; + UsbHdr.u.IsoRec.i32NumDesc = pUrb->cIsocPkts; + cbCapturedLength += cIsocPkts * sizeof(DumpFileUsbIsoDesc); + break; + } + case VUSBXFERTYPE_BULK: + UsbHdr.u8TransferType = 3; + break; + case VUSBXFERTYPE_INTR: + UsbHdr.u8TransferType = 1; + break; + case VUSBXFERTYPE_CTRL: + case VUSBXFERTYPE_MSG: + UsbHdr.u8TransferType = 2; + break; + default: + AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType)); + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + if (enmEvent == VUSBSNIFFEREVENT_SUBMIT) + cbDataLength = 0; + } + else if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE + || pUrb->enmType == VUSBXFERTYPE_CTRL + || pUrb->enmType == VUSBXFERTYPE_MSG) + cbDataLength = 0; + } + else if ( pUrb->enmDir == VUSBDIRECTION_SETUP + && cbDataLength >= sizeof(VUSBSETUP)) + cbDataLength -= sizeof(VUSBSETUP); + + Epb.u32CapturedLen = cbCapturedLength + cbDataLength; + Epb.u32PacketLen = cbCapturedLength + cbUrbLength; + + UsbHdr.u8EndpointNumber = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00); + UsbHdr.u8DeviceAddress = pUrb->DstAddress; + UsbHdr.u16BusId = 0; + UsbHdr.u8DataFlag = cbDataLength ? 0 : 1; + UsbHdr.u64TimestampSec = u64TimestampEvent / RT_NS_1SEC_64; + UsbHdr.u32TimestampUSec = u64TimestampEvent / RT_NS_1US_64 - UsbHdr.u64TimestampSec * RT_US_1SEC; + UsbHdr.i32Status = pUrb->enmStatus; + UsbHdr.u32UrbLength = cbUrbLength; + UsbHdr.u32DataLength = cbDataLength + cIsocPkts * sizeof(DumpFileUsbIsoDesc); + UsbHdr.i32Interval = 0; + UsbHdr.i32StartFrame = 0; + UsbHdr.u32XferFlags = 0; + UsbHdr.u32NumDesc = cIsocPkts; + + if ( (pUrb->enmType == VUSBXFERTYPE_MSG || pUrb->enmType == VUSBXFERTYPE_CTRL) + && enmEvent == VUSBSNIFFEREVENT_SUBMIT) + { + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + + UsbHdr.u.UsbSetup.bmRequestType = pSetup->bmRequestType; + UsbHdr.u.UsbSetup.bRequest = pSetup->bRequest; + UsbHdr.u.UsbSetup.wValue = pSetup->wValue; + UsbHdr.u.UsbSetup.wIndex = pSetup->wIndex; + UsbHdr.u.UsbSetup.wLength = pSetup->wLength; + UsbHdr.u8SetupFlag = 0; + } + else + UsbHdr.u8SetupFlag = '-'; /* Follow usbmon source here. */ + + /* Write the packet to the capture file. */ + int rc = vusbSnifferBlockNew(pThis, &Epb.Hdr, sizeof(Epb)); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockAddData(pThis, &UsbHdr, sizeof(UsbHdr)); + + /* Add Isochronous descriptors now. */ + for (unsigned i = 0; i < cIsocPkts && RT_SUCCESS(rc); i++) + { + DumpFileUsbIsoDesc IsoDesc; + IsoDesc.i32Status = pUrb->aIsocPkts[i].enmStatus; + IsoDesc.u32Offset = pUrb->aIsocPkts[i].off; + IsoDesc.u32Len = pUrb->aIsocPkts[i].cb; + rc = vusbSnifferBlockAddData(pThis, &IsoDesc, sizeof(IsoDesc)); + } + + /* Record data. */ + if ( RT_SUCCESS(rc) + && cbDataLength) + rc = vusbSnifferBlockAddData(pThis, pbData, cbDataLength); + + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg = +{ + /** szName */ + "PCAPNG", + /** pszDesc */ + "PCAP-NG format writer compatible with WireShark", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtPcapNgInit, + /** pfnDestroy */ + vusbSnifferFmtPcapNgDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtPcapNgRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp new file mode 100644 index 00000000..ef09fb3b --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp @@ -0,0 +1,249 @@ +/* $Id: VUSBSnifferUsbMon.cpp $ */ +/** @file + * Virtual USB Sniffer facility - Linux usbmon ASCII format. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/buildconfig.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "mon", + "usbmon", + NULL +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtUsbMonInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtUsbMonDestroy(PVUSBSNIFFERFMTINT pThis) +{ + RT_NOREF(pThis); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtUsbMonRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + char aszLineBuf[512]; + char chEvtType = 'X'; + char chDir = 'X'; + char chEptType = 'X'; + + switch (enmEvent) + { + case VUSBSNIFFEREVENT_SUBMIT: + chEvtType = 'S'; + break; + case VUSBSNIFFEREVENT_COMPLETE: + chEvtType = 'C'; + break; + case VUSBSNIFFEREVENT_ERROR_SUBMIT: + case VUSBSNIFFEREVENT_ERROR_COMPLETE: + chEvtType = 'E'; + break; + default: + AssertMsgFailed(("Invalid event type %d\n", enmEvent)); + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_ISOC: + chEptType = 'Z'; + break; + case VUSBXFERTYPE_BULK: + chEptType = 'B'; + break; + case VUSBXFERTYPE_INTR: + chEptType = 'I'; + break; + case VUSBXFERTYPE_CTRL: + case VUSBXFERTYPE_MSG: + chEptType = 'C'; + break; + default: + AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType)); + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + chDir = 'i'; + else if (pUrb->enmDir == VUSBDIRECTION_OUT) + chDir = 'o'; + else if (pUrb->enmDir == VUSBDIRECTION_SETUP) + chDir = 'o'; + + RT_ZERO(aszLineBuf); + + /* Assemble the static part. */ + size_t cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%p %llu %c %c%c:%u:%u:%u ", + pUrb, RTTimeNanoTS() / RT_NS_1US, chEvtType, chEptType, chDir, + 0, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00)); + int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + if (RT_SUCCESS(rc)) + { + /* Log the setup packet for control requests, the status otherwise. */ + if ( (pUrb->enmType == VUSBXFERTYPE_CTRL || pUrb->enmType == VUSBXFERTYPE_MSG) + && enmEvent == VUSBSNIFFEREVENT_SUBMIT) + { + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "s %02x %02x %04x %04x %04x ", + pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, + pSetup->wIndex, pSetup->wLength); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + else + { + bool fLogAdditionalStatus = pUrb->enmType == VUSBXFERTYPE_ISOC + || pUrb->enmType == VUSBXFERTYPE_INTR; + + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d%s", pUrb->enmStatus, + fLogAdditionalStatus ? "" : " "); + + /* There are additional fields to log for isochronous and interrupt URBs. */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + if (enmEvent == VUSBSNIFFEREVENT_COMPLETE) + { + uint32_t u32ErrorCount = 0; + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK + && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED) + u32ErrorCount++; + + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u:%u ", + 1 /* Interval */, 0 /* Frame number */, u32ErrorCount); + } + else + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u ", + 1 /* Interval */, 0 /* Frame number */); + } + else if (pUrb->enmType == VUSBXFERTYPE_INTR) + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u ", + 1 /* Interval */); + + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + + /* Log the packet descriptors for isochronous URBs. */ + if ( RT_SUCCESS(rc) + && pUrb->enmType == VUSBXFERTYPE_ISOC) + { + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%u ", pUrb->cIsocPkts); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + for (unsigned i = 0; i < pUrb->cIsocPkts && RT_SUCCESS(rc); i++) + { + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d:%u:%u ", + pUrb->aIsocPkts[i].enmStatus, pUrb->aIsocPkts[i].off, + pUrb->aIsocPkts[i].cb); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + } + + if (RT_SUCCESS(rc)) + { + /* Print data length */ + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d n\n", pUrb->cbData); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + + /** @todo Dump the data */ + } + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon = +{ + /** szName */ + "USBMON", + /** pszDesc */ + "UsbMon format writer compatible with vusb-analyzer: http://vusb-analyzer.sourceforge.net", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtUsbMonInit, + /** pfnDestroy */ + vusbSnifferFmtUsbMonDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtUsbMonRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBSnifferVmx.cpp b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp new file mode 100644 index 00000000..b69548a4 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp @@ -0,0 +1,212 @@ +/* $Id: VUSBSnifferVmx.cpp $ */ +/** @file + * Virtual USB Sniffer facility - VMX USBIO format. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/buildconfig.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "vmx", + "vmware", + "usbio", + NULL +}; + + +/** + * Month strings. + */ +static const char *s_apszMonths[] = +{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +static int vusbSnifferFmtVmxLogData(PVUSBSNIFFERFMTINT pThis, PRTTIME pTime, uint8_t *pbBuf, size_t cbBuf) +{ + int rc; + char szLineBuf[256]; + size_t off = 0; + + do + { + size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf), + "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %03zx: %16.*Rhxs\n", + s_apszMonths[pTime->u8Month - 1], pTime->u8MonthDay, + pTime->u8Hour, pTime->u8Minute, pTime->u8Second, 3, pTime->u32Nanosecond, + off, RT_MIN(cbBuf - off, 16), pbBuf); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch); + off += RT_MIN(cbBuf, 16); + pbBuf += RT_MIN(cbBuf, 16); + } while (RT_SUCCESS(rc) && off < cbBuf); + + return rc; +} + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtVmxInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtVmxDestroy(PVUSBSNIFFERFMTINT pThis) +{ + NOREF(pThis); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtVmxRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + RTTIMESPEC TimeNow; + RTTIME Time; + char szLineBuf[256]; + const char *pszEvt = enmEvent == VUSBSNIFFEREVENT_SUBMIT ? "Down" : "Up"; + uint8_t cIsocPkts = pUrb->enmType == VUSBXFERTYPE_ISOC ? pUrb->cIsocPkts : 0; + + if (pUrb->enmType == VUSBXFERTYPE_MSG) + return VINF_SUCCESS; + + RT_ZERO(szLineBuf); + + RTTimeNow(&TimeNow); + RTTimeExplode(&Time, &TimeNow); + + size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf), + "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %s dev=%u endpt=%x datalen=%u numPackets=%u status=%u 0\n", + s_apszMonths[Time.u8Month - 1], Time.u8MonthDay, Time.u8Hour, Time.u8Minute, Time.u8Second, 3, Time.u32Nanosecond, + pszEvt, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00), + pUrb->cbData, cIsocPkts, pUrb->enmStatus); + int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch); + if (RT_SUCCESS(rc)) + { + /* Log the data in the appropriate stage. */ + if ( pUrb->enmType == VUSBXFERTYPE_CTRL + || pUrb->enmType == VUSBXFERTYPE_MSG) + { + if (enmEvent == VUSBSNIFFEREVENT_SUBMIT) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP)); + else if (enmEvent == VUSBSNIFFEREVENT_COMPLETE) + { + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP)); + if ( RT_SUCCESS(rc) + && pUrb->cbData > sizeof(VUSBSETUP)) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[sizeof(VUSBSETUP)], pUrb->cbData - sizeof(VUSBSETUP)); + } + } + else + { + if ( enmEvent == VUSBSNIFFEREVENT_SUBMIT + && pUrb->enmDir == VUSBDIRECTION_OUT) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData); + else if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE + && pUrb->enmDir == VUSBDIRECTION_IN) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData); + } + } + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx = +{ + /** szName */ + "VMX", + /** pszDesc */ + "VMX log format writer supported by vusb-analyzer: http://vusb-analyzer.sourceforge.net", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtVmxInit, + /** pfnDestroy */ + vusbSnifferFmtVmxDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtVmxRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp new file mode 100644 index 00000000..3c14f756 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrb.cpp @@ -0,0 +1,1501 @@ +/* $Id: VUSBUrb.cpp $ */ +/** @file + * Virtual USB - URBs. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/env.h> +#include "VUSBInternal.h" + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Strings for the CTLSTAGE enum values. */ +const char * const g_apszCtlStates[4] = +{ + "SETUP", + "DATA", + "STATUS", + "N/A" +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Complete a SETUP stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgSetupCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgSetupCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage %s->DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe, g_apszCtlStates[pExtra->enmStage])); NOREF(pSetup); + pExtra->enmStage = CTLSTAGE_DATA; + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a DATA stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgDataCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgDataCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe)); NOREF(pSetup); + + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a STATUS stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgStatusCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (pExtra->fOk) + { + /* + * vusbDevStdReqSetAddress requests are deferred. + */ + if (pDev->u8NewAddress != VUSB_INVALID_ADDRESS) + { + vusbDevSetAddress(pDev, pDev->u8NewAddress); + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + } + + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=OK stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_OK; + } + else + { + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_STALL; + } + + /* + * Done with this message sequence. + */ + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; +} + +/** + * This is a worker function for vusbMsgCompletion and + * vusbMsgSubmitSynchronously used to complete the original URB. + * + * @param pUrb The URB originating from the HCI. + */ +static void vusbCtrlCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbCtrlCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + vusbMsgSetupCompletion(pUrb); + break; + case CTLSTAGE_DATA: + vusbMsgDataCompletion(pUrb); + break; + case CTLSTAGE_STATUS: + vusbMsgStatusCompletion(pUrb); + break; + } +} + +/** + * Called from vusbUrbCompletionRh when it encounters a + * message type URB. + * + * @param pUrb The URB within the control pipe extra state data. + */ +static void vusbMsgCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + +#ifdef LOG_ENABLED + LogFlow(("%s: vusbMsgCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + vusbUrbTrace(pUrb, "vusbMsgCompletion", true); +#endif + Assert(&pExtra->Urb == pUrb); + + + if (pUrb->enmStatus == VUSBSTATUS_OK) + pExtra->fOk = true; + else + pExtra->fOk = false; + pExtra->cbLeft = pUrb->cbData - sizeof(VUSBSETUP); + + /* + * Complete the original URB. + */ + PVUSBURB pCtrlUrb = pUrb->pVUsb->pCtrlUrb; + pCtrlUrb->enmState = VUSBURBSTATE_REAPED; + vusbCtrlCompletion(pCtrlUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + Assert( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED); + if (pUrb->enmState != VUSBURBSTATE_CANCELLED) + { + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } + RTCritSectLeave(&pPipe->CritSectCtrl); + + /* Complete the original control URB on the root hub now. */ + vusbUrbCompletionRh(pCtrlUrb); +} + +/** + * Deal with URB errors, talking thru the RH to the HCI. + * + * @returns true if it could be retried. + * @returns false if it should be completed with failure. + * @param pRh The roothub the URB originated from. + * @param pUrb The URB in question. + */ +int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh)); + RT_NOREF(pDev); + return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb); +} + +/** + * Does URB completion on roothub level. + * + * @param pRh The roothub the URB originated from. + * @param pUrb The URB to complete. + */ +void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbCompletionRh: type=%s status=%s\n", + pUrb->pszDesc, vusbUrbTypeName(pUrb->enmType), vusbUrbStatusName(pUrb->enmStatus))); + AssertMsg( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED, ("%d\n", pUrb->enmState)); + + if ( pUrb->pVUsb->pDev + && pUrb->pVUsb->pDev->hSniffer) + { + int rc = VUSBSnifferRecordEvent(pUrb->pVUsb->pDev->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event failed with %Rrc\n", rc)); + } + + /* If there is a sniffer on the roothub record the completed URB there too. */ + if (pRh->hSniffer != VUSBSNIFFER_NIL) + { + int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event on the root hub failed with %Rrc\n", rc)); + } + +#ifdef VBOX_WITH_STATISTICS + /* + * Total and per-type submit statistics. + */ + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes)); + + if ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) + { + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + const unsigned cb = pUrb->aIsocPkts[i].cb; + if (cb) + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aStatIsocDetails[i].Bytes, cb); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, cb); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, cb); + } + STAM_COUNTER_INC(&pRh->StatIsocActPkts); + STAM_COUNTER_INC(&pRh->StatIsocActReadPkts); + } + STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Pkts); + switch (pUrb->aIsocPkts[i].enmStatus) + { + case VUSBSTATUS_OK: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0); + break; + case VUSBSTATUS_DATA_UNDERRUN: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0); + break; + case VUSBSTATUS_DATA_OVERRUN: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataOverrun); break; + case VUSBSTATUS_NOT_ACCESSED: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].NotAccessed); break; + default: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Misc); break; + } + } + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActBytes, pUrb->cbData); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActReadBytes, pUrb->cbData); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActWriteBytes, pUrb->cbData); + } + } + } + else + { + /* (Note. this also counts the cancelled packets) */ + STAM_COUNTER_INC(&pRh->Total.StatUrbsFailed); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsFailed); + } + } +#endif /* VBOX_WITH_STATISTICS */ + + /* + * Msg transfers are special virtual transfers associated with + * vusb, not the roothub + */ + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + vusbMsgCompletion(pUrb); + return; + case VUSBXFERTYPE_ISOC: + /* Don't bother with error callback for isochronous URBs. */ + break; + +#if 1 /** @todo r=bird: OHCI say ''If the Transfer Descriptor is being + * retired because of an error, the Host Controller must update + * the Halt bit of the Endpoint Descriptor.'' + * + * So, I'll subject all transfertypes to the same halt stuff now. It could + * just happen to fix the logitech disconnect trap in win2k. + */ + default: +#endif + case VUSBXFERTYPE_BULK: + if (pUrb->enmStatus != VUSBSTATUS_OK) + vusbUrbErrorRhEx(pRh, pUrb); + break; + } +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbCompletionRh", true); +#endif + + pRh->pIRhPort->pfnXferCompletion(pRh->pIRhPort, pUrb); + if (pUrb->enmState == VUSBURBSTATE_REAPED) + { + LogFlow(("%s: vusbUrbCompletionRh: Freeing URB\n", pUrb->pszDesc)); + pUrb->pVUsb->pfnFree(pUrb); + } + + vusbRhR3ProcessFrame(pRh, true /* fCallback */); +} + + +/** + * Certain control requests must not ever be forwarded to the device because + * they are required by the vusb core in order to maintain the vusb internal + * data structures. + */ +DECLINLINE(bool) vusbUrbIsRequestSafe(PCVUSBSETUP pSetup, PVUSBURB pUrb) +{ + if ((pSetup->bmRequestType & VUSB_REQ_MASK) != VUSB_REQ_STANDARD) + return true; + + switch (pSetup->bRequest) + { + case VUSB_REQ_CLEAR_FEATURE: + return pUrb->EndPt != 0 /* not default control pipe */ + || pSetup->wValue != 0 /* not ENDPOINT_HALT */ + || !pUrb->pVUsb->pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint; /* not special need for backend */ + case VUSB_REQ_SET_ADDRESS: + case VUSB_REQ_SET_CONFIGURATION: + case VUSB_REQ_GET_CONFIGURATION: + case VUSB_REQ_SET_INTERFACE: + case VUSB_REQ_GET_INTERFACE: + return false; + + /* + * If the device wishes it, we'll use the cached device and + * configuration descriptors. (We return false when we want to use the + * cache. Yeah, it's a bit weird to read.) + */ + case VUSB_REQ_GET_DESCRIPTOR: + return !vusbDevIsDescriptorInCache(pUrb->pVUsb->pDev, pSetup); + + default: + return true; + } +} + + +/** + * Queues an URB for asynchronous transfer. + * A list of asynchronous URBs is kept by the roothub. + * + * @returns VBox status code (from pfnUrbQueue). + * @param pUrb The URB. + */ +int vusbUrbQueueAsyncRh(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbQueueAsyncRh", false); +#endif + + /* Immediately return in case of error. + * XXX There is still a race: The Rh might vanish after this point! */ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + if (!pRh) + { + Log(("vusbUrbQueueAsyncRh returning VERR_OBJECT_DESTROYED\n")); + return VERR_OBJECT_DESTROYED; + } + + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb); + if (RT_FAILURE(rc)) + { + LogFlow(("%s: vusbUrbQueueAsyncRh: returns %Rrc (queue_urb)\n", pUrb->pszDesc, rc)); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + return rc; + } + + ASMAtomicIncU32(&pDev->aPipes[pUrb->EndPt].async); + + /* Queue the Urb on the roothub */ + RTListAppend(&pDev->LstAsyncUrbs, &pUrb->pVUsb->NdLst); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + + return VINF_SUCCESS; +} + + +/** + * Send a control message *synchronously*. + * @return + */ +static void vusbMsgSubmitSynchronously(PVUSBURB pUrb, bool fSafeRequest) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + Assert(pDev); + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + LogFlow(("%s: vusbMsgSubmitSynchronously: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "")); + + uint8_t *pbData = (uint8_t *)pExtra->pMsg + sizeof(*pSetup); + uint32_t cbData = pSetup->wLength; + bool fOk = false; + if (!fSafeRequest) + fOk = vusbDevStandardRequest(pDev, pUrb->EndPt, pSetup, pbData, &cbData); + else + AssertMsgFailed(("oops\n")); + + pUrb->enmState = VUSBURBSTATE_REAPED; + if (fOk) + { + pSetup->wLength = cbData; + pUrb->enmStatus = VUSBSTATUS_OK; + pExtra->fOk = true; + } + else + { + pUrb->enmStatus = VUSBSTATUS_STALL; + pExtra->fOk = false; + } + pExtra->cbLeft = cbData; /* used by IN only */ + + vusbCtrlCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +} + +/** + * Callback for dealing with device reset. + */ +void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + pExtra->enmStage = CTLSTAGE_SETUP; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } +} + + +/** + * Callback to free a cancelled message URB. + * + * This is yet another place we're we have to performance acrobatics to + * deal with cancelled URBs. sigh. + * + * The deal here is that we never free message URBs since they are integrated + * into the message pipe state. But since cancel can leave URBs unreaped and in + * a state which require them not to be freed, we'll have to do two things. + * First, if a new message URB is processed we'll have to get a new message + * pipe state. Second, we cannot just free the damn state structure because + * that might lead to heap corruption since it might still be in-flight. + * + * The URB embedded into the message pipe control structure will start in an + * ALLOCATED state. When submitted it will be go to the IN-FLIGHT state. When + * reaped it will go from REAPED to ALLOCATED. When completed in the CANCELLED + * state it will remain in that state (as does normal URBs). + * + * If a new message urb comes up while it's in the CANCELLED state, we will + * orphan it and it will be freed here in vusbMsgFreeUrb. We indicate this + * by setting pVUsb->pvFreeCtx to NULL. + * + * If we have to free the message state structure because of device destruction, + * configuration changes, or similar, we will orphan the message pipe state in + * the same way by setting pVUsb->pvFreeCtx to NULL and let this function free it. + * + * @param pUrb + */ +static DECLCALLBACK(void) vusbMsgFreeUrb(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + PVUSBCTRLEXTRA pExtra = (PVUSBCTRLEXTRA)((uint8_t *)pUrb - RT_UOFFSETOF(VUSBCTRLEXTRA, Urb)); + if ( pUrb->enmState == VUSBURBSTATE_CANCELLED + && !pUrb->pVUsb->pvFreeCtx) + { + LogFlow(("vusbMsgFreeUrb: Freeing orphan: %p (pUrb=%p)\n", pExtra, pUrb)); + RTMemFree(pExtra); + } + else + { + Assert(pUrb->pVUsb->pvFreeCtx == &pExtra->Urb); + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } +} + +/** + * Frees the extra state data associated with a message pipe. + * + * @param pExtra The data. + */ +void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.u32Magic = 0; + pExtra->Urb.enmState = VUSBURBSTATE_FREE; + if (pExtra->Urb.pszDesc) + RTStrFree(pExtra->Urb.pszDesc); + RTMemFree(pExtra); + } + else + pExtra->Urb.pVUsb->pvFreeCtx = NULL; /* see vusbMsgFreeUrb */ +} + +/** + * Allocates the extra state data required for a control pipe. + * + * @returns Pointer to the allocated and initialized state data. + * @returns NULL on out of memory condition. + * @param pUrb A URB we can copy default data from. + */ +static PVUSBCTRLEXTRA vusbMsgAllocExtraData(PVUSBURB pUrb) +{ +/** @todo reuse these? */ + PVUSBCTRLEXTRA pExtra; + /* The initial allocation tries to balance wasted memory versus the need to re-allocate + * the message data. Experience shows that an 8K initial allocation in practice never needs + * to be expanded but almost certainly wastes 4K or more memory. + */ + const size_t cbMax = _2K + sizeof(VUSBSETUP); + pExtra = (PVUSBCTRLEXTRA)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbMax])); + if (pExtra) + { + pExtra->enmStage = CTLSTAGE_SETUP; + //pExtra->fOk = false; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->pbCur = (uint8_t *)(pExtra->pMsg + 1); + //pExtra->cbLeft = 0; + pExtra->cbMax = cbMax; + + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.u32Magic = VUSBURB_MAGIC; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +#ifdef LOG_ENABLED + RTStrAPrintf(&pExtra->Urb.pszDesc, "URB %p msg->%p", &pExtra->Urb, pUrb); +#endif + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + //pExtra->Urb.pVUsb->pCtrlUrb = NULL; + //pExtra->Urb.pVUsb->pNext = NULL; + //pExtra->Urb.pVUsb->ppPrev = NULL; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->Urb.pVUsb->pDev = pUrb->pVUsb->pDev; + pExtra->Urb.pVUsb->pfnFree = vusbMsgFreeUrb; + pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; + //pExtra->Urb.Hci = {0}; + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.DstAddress = pUrb->DstAddress; + pExtra->Urb.EndPt = pUrb->EndPt; + pExtra->Urb.enmType = VUSBXFERTYPE_MSG; + pExtra->Urb.enmDir = VUSBDIRECTION_INVALID; + //pExtra->Urb.fShortNotOk = false; + pExtra->Urb.enmStatus = VUSBSTATUS_INVALID; + //pExtra->Urb.cbData = 0; + vusbUrbAssert(&pExtra->Urb); + } + return pExtra; +} + +/** + * Sets up the message. + * + * The message is associated with the pipe, in what's currently called + * control pipe extra state data (pointed to by pPipe->pCtrl). If this + * is a OUT message, we will no go on collecting data URB. If it's a + * IN message, we'll send it and then queue any incoming data for the + * URBs collecting it. + * + * @returns Success indicator. + */ +static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf) +{ + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf; + + /* + * Validate length. + */ + if (cbBuf < sizeof(VUSBSETUP)) + { + LogFlow(("vusbMsgSetup: pPipe=%p cbBuf=%u < %u (failure) !!!\n", + pPipe, cbBuf, sizeof(VUSBSETUP))); + return false; + } + + /* + * Check if we've got an cancelled message URB. Allocate a new one in that case. + */ + if (pExtra->Urb.enmState == VUSBURBSTATE_CANCELLED) + { + void *pvNew = RTMemDup(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax])); + if (!pvNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%zu\n", RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax]))); + return false; + } + pExtra->Urb.pVUsb->pvFreeCtx = NULL; + LogFlow(("vusbMsgSetup: Replacing canceled pExtra=%p with %p.\n", pExtra, pvNew)); + pPipe->pCtrl = pExtra = (PVUSBCTRLEXTRA)pvNew; + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } + + /* + * Check that we've got sufficient space in the message URB. + */ + if (pExtra->cbMax < cbBuf + pSetupIn->wLength) + { + uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength, 64); + PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])); + if (!pNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n", + cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]))); + return false; + } + if (pExtra != pNew) + { + LogFunc(("Reallocated %u -> %u\n", pExtra->cbMax, cbReq)); + pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData; + pExtra = pNew; + pPipe->pCtrl = pExtra; + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; + } + + pExtra->cbMax = cbReq; + } + Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED); + + /* + * Copy the setup data and prepare for data. + */ + PVUSBSETUP pSetup = pExtra->pMsg; + pExtra->fSubmitted = false; + pExtra->Urb.enmState = VUSBURBSTATE_IN_FLIGHT; + pExtra->pbCur = (uint8_t *)(pSetup + 1); + pSetup->bmRequestType = pSetupIn->bmRequestType; + pSetup->bRequest = pSetupIn->bRequest; + pSetup->wValue = RT_LE2H_U16(pSetupIn->wValue); + pSetup->wIndex = RT_LE2H_U16(pSetupIn->wIndex); + pSetup->wLength = RT_LE2H_U16(pSetupIn->wLength); + + LogFlow(("vusbMsgSetup(%p,,%d): bmRequestType=%#04x bRequest=%#04x wValue=%#06x wIndex=%#06x wLength=0x%.4x\n", + pPipe, cbBuf, pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + return true; +} + +/** + * Build the message URB from the given control URB and accompanying message + * pipe state which we grab from the device for the URB. + * + * @param pUrb The URB to submit. + * @param pSetup The setup packet for the message transfer. + * @param pExtra Pointer to the additional state requred for a control transfer. + * @param pPipe The message pipe state. + */ +static void vusbMsgDoTransfer(PVUSBURB pUrb, PVUSBSETUP pSetup, PVUSBCTRLEXTRA pExtra, PVUSBPIPE pPipe) +{ + RT_NOREF(pPipe); + + /* + * Mark this transfer as sent (cleared at setup time). + */ + Assert(!pExtra->fSubmitted); + pExtra->fSubmitted = true; + + /* + * Do we have to do this synchronously? + */ + bool fSafeRequest = vusbUrbIsRequestSafe(pSetup, pUrb); + if (!fSafeRequest) + { + vusbMsgSubmitSynchronously(pUrb, fSafeRequest); + return; + } + + /* + * Do it asynchronously. + */ + LogFlow(("%s: vusbMsgDoTransfer: ep=%d pMsgUrb=%p pPipe=%p stage=%s\n", + pUrb->pszDesc, pUrb->EndPt, &pExtra->Urb, pPipe, g_apszCtlStates[pExtra->enmStage])); + Assert(pExtra->Urb.enmType == VUSBXFERTYPE_MSG); + Assert(pExtra->Urb.EndPt == pUrb->EndPt); + pExtra->Urb.enmDir = (pSetup->bmRequestType & VUSB_DIR_TO_HOST) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + pExtra->Urb.cbData = pSetup->wLength + sizeof(*pSetup); + pExtra->Urb.pVUsb->pCtrlUrb = pUrb; + int rc = vusbUrbQueueAsyncRh(&pExtra->Urb); + if (RT_FAILURE(rc)) + { + /* + * If we fail submitting it, will not retry but fail immediately. + * + * This keeps things simple. The host OS will have retried if + * it's a proxied device, and if it's a virtual one it really means + * it if it's failing a control message. + */ + LogFlow(("%s: vusbMsgDoTransfer: failed submitting urb! failing it with %s (rc=%Rrc)!!!\n", + pUrb->pszDesc, rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? "DNR" : "CRC", rc)); + pExtra->Urb.enmStatus = rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? VUSBSTATUS_DNR : VUSBSTATUS_CRC; + pExtra->Urb.enmState = VUSBURBSTATE_REAPED; + vusbMsgCompletion(&pExtra->Urb); + } +} + +/** + * Fails a URB request with a pipe STALL error. + * + * @returns VINF_SUCCESS indicating that we've completed the URB. + * @param pUrb The URB in question. + */ +static int vusbMsgStall(PVUSBURB pUrb) +{ + PVUSBPIPE pPipe = &pUrb->pVUsb->pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbMsgStall: pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_STALL; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + +/** + * Submit a control message. + * + * Here we implement the USB defined traffic that occurs in message pipes + * (aka control endpoints). We want to provide a single function for device + * drivers so that they don't all have to reimplement the usb logic for + * themselves. This means we need to keep a little bit of state information + * because control transfers occur over multiple bus transactions. We may + * also need to buffer data over multiple data stages. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitCtrl(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbSubmitCtrl", false); +#endif + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (!pExtra && !(pExtra = pPipe->pCtrl = vusbMsgAllocExtraData(pUrb))) + { + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_VUSB_NO_URB_MEMORY; + } + PVUSBSETUP pSetup = pExtra->pMsg; + + if (pPipe->async) + { + AssertMsgFailed(("%u\n", pPipe->async)); + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_GENERAL_FAILURE; + } + + /* + * A setup packet always resets the transaction and the + * end of data transmission is signified by change in + * data direction. + */ + if (pUrb->enmDir == VUSBDIRECTION_SETUP) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_SETUP; + } + else if ( pExtra->enmStage == CTLSTAGE_DATA + /* (the STATUS stage direction goes the other way) */ + && !!(pSetup->bmRequestType & VUSB_DIR_TO_HOST) != (pUrb->enmDir == VUSBDIRECTION_IN)) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->STATUS\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_STATUS; + } + + /* + * Act according to the current message stage. + */ + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + /* + * When stall handshake is returned, all subsequent packets + * must generate stall until a setup packet arrives. + */ + if (pUrb->enmDir != VUSBDIRECTION_SETUP) + { + Log(("%s: vusbUrbSubmitCtrl: Stall at setup stage (dir=%#x)!!\n", pUrb->pszDesc, pUrb->enmDir)); + vusbMsgStall(pUrb); + break; + } + + /* Store setup details, return DNR if corrupt */ + if (!vusbMsgSetup(pPipe, pUrb->abData, pUrb->cbData)) + { + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + break; + } + if (pPipe->pCtrl != pExtra) + { + pExtra = pPipe->pCtrl; + pSetup = pExtra->pMsg; + } + + /* pre-buffer our output if it's device-to-host */ + if (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + else if (pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: need data\n", pUrb->pszDesc)); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgSetupCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + /* + * If there is no DATA stage, we must send it now since there are + * no requirement of a STATUS stage. + */ + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + + case CTLSTAGE_DATA: + { + /* + * If a data stage exceeds the target buffer indicated in + * setup return stall, if data stage returns stall there + * will be no status stage. + */ + uint8_t *pbData = (uint8_t *)(pExtra->pMsg + 1); + if ((uintptr_t)&pExtra->pbCur[pUrb->cbData] > (uintptr_t)&pbData[pSetup->wLength]) + { + /* In the device -> host direction, the device never returns more data than + what was requested (wLength). So, we can just cap cbData. */ + ssize_t const cbLeft = &pbData[pSetup->wLength] - pExtra->pbCur; + if (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + { + LogFlow(("%s: vusbUrbSubmitCtrl: Adjusting DATA request: %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft)); + pUrb->cbData = cbLeft >= 0 ? (uint32_t)cbLeft : 0; + } + /* In the host -> direction it's undefined what happens if the host provides + more data than what wLength inidicated. However, in 2007, iPhone detection + via iTunes would issue wLength=0 but provide a data URB which we needed to + pass on to the device anyway, so we'll just quietly adjust wLength if it's + zero and get on with the work. + + What confuses me (bird) here, though, is that we've already sent the SETUP + URB to the device when we received it, and all we end up doing is an + unnecessary memcpy and completing the URB, but never actually sending the + data to the device. So, I guess this stuff is still a little iffy. + + Note! We currently won't be doing any resizing, as we've disabled resizing + in general. + P.S. We used to have a very strange (pUrb->cbData % pSetup->wLength) == 0 + thing too that joined the pUrb->cbData adjusting above. */ + else if ( pSetup->wLength == 0 + && pUrb->cbData <= pExtra->cbMax) + { + Log(("%s: vusbUrbSubmitCtrl: pAdjusting wLength: %u -> %u (iPhone hack)\n", + pUrb->pszDesc, pSetup->wLength, pUrb->cbData)); + pSetup->wLength = pUrb->cbData; + Assert(cbLeft >= (ssize_t)pUrb->cbData); + } + else + { + Log(("%s: vusbUrbSubmitCtrl: Stall at data stage!! wLength=%u cbData=%d cbMax=%d cbLeft=%dz\n", + pUrb->pszDesc, pSetup->wLength, pUrb->cbData, pExtra->cbMax, cbLeft)); + vusbMsgStall(pUrb); + break; + } + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + /* put data received from the device. */ + const uint32_t cbRead = RT_MIN(pUrb->cbData, pExtra->cbLeft); + memcpy(pUrb->abData, pExtra->pbCur, cbRead); + + /* advance */ + pExtra->pbCur += cbRead; + if (pUrb->cbData == cbRead) + pExtra->cbLeft -= pUrb->cbData; + else + { + /* adjust the pUrb->cbData to reflect the number of bytes containing actual data. */ + LogFlow(("%s: vusbUrbSubmitCtrl: adjusting last DATA pUrb->cbData, %d -> %d\n", + pUrb->pszDesc, pUrb->cbData, pExtra->cbLeft)); + pUrb->cbData = cbRead; + pExtra->cbLeft = 0; + } + } + else + { + /* get data for sending when completed. */ + AssertStmt((ssize_t)pUrb->cbData <= pExtra->cbMax - (pExtra->pbCur - pbData), /* paranoia: checked above */ + pUrb->cbData = pExtra->cbMax - (uint32_t)RT_MIN(pExtra->pbCur - pbData, pExtra->cbMax)); + memcpy(pExtra->pbCur, pUrb->abData, pUrb->cbData); + + /* advance */ + pExtra->pbCur += pUrb->cbData; + + /* + * If we've got the necessary data, we'll send it now since there are + * no requirement of a STATUS stage. + */ + if ( !pExtra->fSubmitted + && pExtra->pbCur - pbData >= pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=DATA - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + break; + } + } + + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgDataCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + break; + } + + case CTLSTAGE_STATUS: + if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + || pExtra->fSubmitted) + { + Assert(pExtra->fSubmitted); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgStatusCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=STATUS - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + } + + RTCritSectLeave(&pPipe->CritSectCtrl); + return VINF_SUCCESS; +} + + +/** + * Submit a interrupt URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitInterrupt(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitInterrupt: (sync)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit a bulk URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitBulk(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitBulk: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit an isochronous URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitIsochronous(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitIsochronous: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Fail a URB with a 'hard-error' sort of error. + * + * @return VINF_SUCCESS (the Urb status indicates the error). + * @param pUrb The URB. + */ +int vusbUrbSubmitHardError(PVUSBURB pUrb) +{ + /* FIXME: Find out the correct return code from the spec */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + + +/** + * Submit a URB. + */ +int vusbUrbSubmit(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + Assert(pUrb->enmState == VUSBURBSTATE_ALLOCATED); + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = NULL; + Assert(pDev); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + pUrb->enmStatus = VUSBSTATUS_DNR; + /* This will postpone the TDs until we're done with the resetting. */ + return VERR_VUSB_DEVICE_IS_RESETTING; + } + +#ifdef LOG_ENABLED + /* stamp it */ + pUrb->pVUsb->u64SubmitTS = RTTimeNanoTS(); +#endif + + /** @todo Check max packet size here too? */ + + /* + * Validate the pipe. + */ + if (pUrb->EndPt >= VUSB_PIPE_MAX) + { + Log(("%s: pDev=%p[%s]: SUBMIT: ep %i >= %i!!!\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pUrb->EndPt, VUSB_PIPE_MAX)); + return vusbUrbSubmitHardError(pUrb); + } + PCVUSBDESCENDPOINTEX pEndPtDesc; + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].in; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + case VUSBDIRECTION_SETUP: + case VUSBDIRECTION_OUT: + default: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].out; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + } + if (!pEndPtDesc) + { + Log(("%s: pDev=%p[%s]: SUBMIT: no endpoint!!! dir=%s e=%i\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbDirName(pUrb->enmDir), pUrb->EndPt)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * Check for correct transfer types. + * Our type codes are the same - what a coincidence. + */ + if ((pEndPtDesc->Core.bmAttributes & 0x3) != pUrb->enmType) + { + /* Bulk and interrupt transfers are identical on the bus level (the only difference + * is in how they are scheduled by the HCD/HC) and need an exemption. + * Atheros AR9271 is a known offender; its configuration descriptors include + * interrupt endpoints, but drivers (Win7/8, Linux kernel pre-3.05) treat them + * as bulk endpoints. + */ + if ( (pUrb->enmType == VUSBXFERTYPE_BULK && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_INTR) + || (pUrb->enmType == VUSBXFERTYPE_INTR && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_BULK)) + { + Log2(("%s: pDev=%p[%s]: SUBMIT: mixing bulk/interrupt transfers on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + } + else + { + Log(("%s: pDev=%p[%s]: SUBMIT: %s transfer requested for %#x endpoint on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbTypeName(pUrb->enmType), pEndPtDesc->Core.bmAttributes, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + return vusbUrbSubmitHardError(pUrb); + } + } + + /* + * If there's a URB in the read-ahead buffer, use it. + */ + int rc; + + if (pDev->hSniffer) + { + rc = VUSBSnifferRecordEvent(pDev->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB submit event failed with %Rrc\n", rc)); + } + + /* + * Take action based on type. + */ + pUrb->enmState = VUSBURBSTATE_IN_FLIGHT; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: + rc = vusbUrbSubmitCtrl(pUrb); + break; + case VUSBXFERTYPE_BULK: + rc = vusbUrbSubmitBulk(pUrb); + break; + case VUSBXFERTYPE_INTR: + rc = vusbUrbSubmitInterrupt(pUrb); + break; + case VUSBXFERTYPE_ISOC: + rc = vusbUrbSubmitIsochronous(pUrb); + break; + default: + AssertMsgFailed(("Unexpected pUrb type %d\n", pUrb->enmType)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * The device was detached, so we fail everything. + * (We should really detach and destroy the device, but we'll have to wait till Main reacts.) + */ + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + rc = vusbUrbSubmitHardError(pUrb); + /* + * We don't increment error count if async URBs are in flight, in + * this case we just assume we need to throttle back, this also + * makes sure we don't halt bulk endpoints at the wrong time. + */ + else if ( RT_FAILURE(rc) + && !ASMAtomicReadU32(&pDev->aPipes[pUrb->EndPt].async) + /* && pUrb->enmType == VUSBXFERTYPE_BULK ?? */ + && !vusbUrbErrorRh(pUrb)) + { + /* don't retry it anymore. */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; + } + + return rc; +} + + +/** + * Reap in-flight URBs. + * + * @param pUrbLst Pointer to the head of the URB list. + * @param cMillies Number of milliseconds to block in each reap operation. + * Use 0 to not block at all. + */ +void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies) +{ + PVUSBURBVUSB pVUsbUrb = RTListGetFirst(pUrbLst, VUSBURBVUSBINT, NdLst); + while (pVUsbUrb) + { + vusbUrbAssert(pVUsbUrb->pUrb); + PVUSBURBVUSB pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, VUSBURBVUSBINT, NdLst); + PVUSBDEV pDev = pVUsbUrb->pDev; + + /* Don't touch resetting devices - paranoid safety precaution. */ + if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET) + { + /* + * Reap most URBs pending on a single device. + */ + PVUSBURB pRipe; + + /** + * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other + * and leaked URBs (shouldn't be affected by leaked URBs). + */ + Assert(pDev->pUsbIns); + while ( pDev->pUsbIns + && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL)) + { + vusbUrbAssert(pRipe); + if (pVUsbUrbNext && pRipe == pVUsbUrbNext->pUrb) + pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrbNext, VUSBURBVUSBINT, NdLst); + vusbUrbRipe(pRipe); + } + } + + /* next */ + pVUsbUrb = pVUsbUrbNext; + } +} + +/** + * Reap URBs on a per device level. + * + * @returns nothing. + * @param pDev The device instance to reap URBs for. + * @param cMillies Number of milliseconds to block in each reap operation. + * Use 0 to not block at all. + */ +void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies) +{ + Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Reap most URBs pending on a single device. + */ + PVUSBURB pRipe; + + /** + * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other + * and leaked URBs (shouldn't be affected by leaked URBs). + */ + + if (ASMAtomicXchgBool(&pDev->fWokenUp, false)) + return; + + Assert(pDev->pUsbIns); + while ( pDev->pUsbIns + && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL)) + { + vusbUrbAssert(pRipe); + vusbUrbRipe(pRipe); + if (ASMAtomicXchgBool(&pDev->fWokenUp, false)) + break; + } +} + +/** + * Completes the URB. + */ +static void vusbUrbCompletion(PVUSBURB pUrb) +{ + Assert(pUrb->pVUsb->pDev->aPipes); + ASMAtomicDecU32(&pUrb->pVUsb->pDev->aPipes[pUrb->EndPt].async); + + if (pUrb->enmState == VUSBURBSTATE_REAPED) + vusbUrbUnlink(pUrb); + + vusbUrbCompletionRh(pUrb); +} + +/** + * The worker for vusbUrbCancel() which is executed on the I/O thread. + * + * @returns IPRT status code. + * @param pUrb The URB to cancel. + * @param enmMode The way the URB should be canceled. + */ +DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode) +{ + vusbUrbAssert(pUrb); +#ifdef VBOX_WITH_STATISTICS + PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); +#endif + if (pUrb->enmState == VUSBURBSTATE_IN_FLIGHT) + { + LogFlow(("%s: vusbUrbCancel: Canceling in-flight\n", pUrb->pszDesc)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled); + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));}); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled); + } + + pUrb->enmState = VUSBURBSTATE_CANCELLED; + PPDMUSBINS pUsbIns = pUrb->pVUsb->pDev->pUsbIns; + pUsbIns->pReg->pfnUrbCancel(pUsbIns, pUrb); + Assert(pUrb->enmState == VUSBURBSTATE_CANCELLED || pUrb->enmState == VUSBURBSTATE_REAPED); + + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletion(pUrb); + } + else if (pUrb->enmState == VUSBURBSTATE_REAPED) + { + LogFlow(("%s: vusbUrbCancel: Canceling reaped urb\n", pUrb->pszDesc)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled); + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));}); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled); + } + + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletion(pUrb); + } + else + { + AssertMsg(pUrb->enmState == VUSBURBSTATE_CANCELLED, ("Invalid state %d, pUrb=%p\n", pUrb->enmState, pUrb)); + switch (enmMode) + { + default: + AssertMsgFailed(("Invalid cancel mode\n")); + RT_FALL_THRU(); + case CANCELMODE_FAIL: + pUrb->enmStatus = VUSBSTATUS_CRC; + break; + case CANCELMODE_UNDO: + pUrb->enmStatus = VUSBSTATUS_UNDO; + break; + + } + } + return VINF_SUCCESS; +} + +/** + * Cancels an URB with CRC failure. + * + * Cancelling an URB is a tricky thing. The USBProxy backend can not + * all cancel it and we must keep the URB around until it's ripe and + * can be reaped the normal way. However, we must complete the URB + * now, before leaving this function. This is not nice. sigh. + * + * This function will cancel the URB if it's in-flight and complete + * it. The device will in its pfnCancel method be given the chance to + * say that the URB doesn't need reaping and should be unlinked. + * + * An URB which is in the cancel state after pfnCancel will remain in that + * state and in the async list until its reaped. When it's finally reaped + * it will be unlinked and freed without doing any completion. + * + * There are different modes of canceling an URB. When devices are being + * disconnected etc., they will be completed with an error (CRC). However, + * when the HC needs to temporarily halt communication with a device, the + * URB/TD must be left alone if possible. + * + * @param pUrb The URB to cancel. + * @param mode The way the URB should be canceled. + */ +void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode) +{ + int rc = vusbDevIoThreadExecSync(pUrb->pVUsb->pDev, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode); + AssertRC(rc); +} + + +/** + * Async version of vusbUrbCancel() - doesn't wait for the cancelling to be complete. + */ +void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode) +{ + /* Don't try to cancel the URB when completion is in progress at the moment. */ + if (!ASMAtomicXchgBool(&pUrb->fCompleting, true)) + { + int rc = vusbDevIoThreadExec(pUrb->pVUsb->pDev, 0 /* fFlags */, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode); + AssertRC(rc); + } +} + + +/** + * Deals with a ripe URB (i.e. after reaping it). + * + * If an URB is in the reaped or in-flight state, we'll + * complete it. If it's cancelled, we'll simply free it. + * Any other states should never get here. + * + * @param pUrb The URB. + */ +void vusbUrbRipe(PVUSBURB pUrb) +{ + if ( pUrb->enmState == VUSBURBSTATE_IN_FLIGHT + || pUrb->enmState == VUSBURBSTATE_REAPED) + { + pUrb->enmState = VUSBURBSTATE_REAPED; + if (!ASMAtomicXchgBool(&pUrb->fCompleting, true)) + vusbUrbCompletion(pUrb); + } + else if (pUrb->enmState == VUSBURBSTATE_CANCELLED) + { + vusbUrbUnlink(pUrb); + LogFlow(("%s: vusbUrbRipe: Freeing cancelled URB\n", pUrb->pszDesc)); + pUrb->pVUsb->pfnFree(pUrb); + } + else + AssertMsgFailed(("Invalid URB state %d; %s\n", pUrb->enmState, pUrb->pszDesc)); +} + + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/VUSBUrbPool.cpp b/src/VBox/Devices/USB/VUSBUrbPool.cpp new file mode 100644 index 00000000..3353c86d --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrbPool.cpp @@ -0,0 +1,245 @@ +/* $Id: VUSBUrbPool.cpp $ */ +/** @file + * Virtual USB - URB pool. + */ + +/* + * Copyright (C) 2016-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/critsect.h> + +#include "VUSBInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Maximum age for one URB. */ +#define VUSBURB_AGE_MAX 10 + +/** Convert from an URB to the URB header. */ +#define VUSBURBPOOL_URB_2_URBHDR(a_pUrb) RT_FROM_MEMBER(a_pUrb, VUSBURBHDR, Urb); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * URB header not visible to the caller allocating an URB + * and only for internal tracking. + */ +typedef struct VUSBURBHDR +{ + /** List node for keeping the URB in the free list. */ + RTLISTNODE NdFree; + /** Size of the data allocated for the URB (Only the variable part including the + * HCI and TDs). */ + size_t cbAllocated; + /** Age of the URB waiting on the list, if it is waiting for too long without being used + * again it will be freed. */ + uint32_t cAge; +#if HC_ARCH_BITS == 64 + uint32_t u32Alignment0; +#endif + /** The embedded URB. */ + VUSBURB Urb; +} VUSBURBHDR; +/** Pointer to a URB header. */ +typedef VUSBURBHDR *PVUSBURBHDR; + +AssertCompileSizeAlignment(VUSBURBHDR, 8); + + + +DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool) +{ + int rc = RTCritSectInit(&pUrbPool->CritSectPool); + if (RT_SUCCESS(rc)) + { + pUrbPool->cUrbsInPool = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++) + RTListInit(&pUrbPool->aLstFreeUrbs[i]); + } + + return rc; +} + + +DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool) +{ + RTCritSectEnter(&pUrbPool->CritSectPool); + for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++) + { + PVUSBURBHDR pHdr, pHdrNext; + RTListForEachSafe(&pUrbPool->aLstFreeUrbs[i], pHdr, pHdrNext, VUSBURBHDR, NdFree) + { + RTListNodeRemove(&pHdr->NdFree); + + pHdr->cbAllocated = 0; + pHdr->Urb.u32Magic = 0; + pHdr->Urb.enmState = VUSBURBSTATE_INVALID; + RTMemFree(pHdr); + } + } + RTCritSectLeave(&pUrbPool->CritSectPool); + RTCritSectDelete(&pUrbPool->CritSectPool); +} + + +DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, size_t cbData, size_t cbHci, + size_t cbHciTd, unsigned cTds) +{ + Assert((uint32_t)cbData == cbData); + Assert((uint32_t)cbHci == cbHci); + + /* + * Reuse or allocate a new URB. + */ + /** @todo The allocations should be done by the device, at least as an option, since the devices + * frequently wish to associate their own stuff with the in-flight URB or need special buffering + * (isochronous on Darwin for instance). */ + /* Get the required amount of additional memory to allocate the whole state. */ + size_t cbMem = cbData + sizeof(VUSBURBVUSBINT) + cbHci + cTds * cbHciTd; + + AssertReturn((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs), NULL); + + RTCritSectEnter(&pUrbPool->CritSectPool); + PVUSBURBHDR pHdr = NULL; + PVUSBURBHDR pIt, pItNext; + RTListForEachSafe(&pUrbPool->aLstFreeUrbs[enmType], pIt, pItNext, VUSBURBHDR, NdFree) + { + if (pIt->cbAllocated >= cbMem) + { + RTListNodeRemove(&pIt->NdFree); + Assert(pIt->Urb.u32Magic == VUSBURB_MAGIC); + Assert(pIt->Urb.enmState == VUSBURBSTATE_FREE); + /* + * If the allocation is far too big we increase the age counter too + * so we don't waste memory for a lot of small transfers + */ + if (pIt->cbAllocated >= 2 * cbMem) + pIt->cAge++; + else + pIt->cAge = 0; + pHdr = pIt; + break; + } + else + { + /* Increase age and free if it reached a threshold. */ + pIt->cAge++; + if (pIt->cAge == VUSBURB_AGE_MAX) + { + RTListNodeRemove(&pIt->NdFree); + ASMAtomicDecU32(&pUrbPool->cUrbsInPool); + RTMemFree(pIt); + } + } + } + + if (!pHdr) + { + /* allocate a new one. */ + size_t cbDataAllocated = cbMem <= _4K ? RT_ALIGN_32(cbMem, _1K) + : cbMem <= _32K ? RT_ALIGN_32(cbMem, _4K) + : RT_ALIGN_32(cbMem, 16*_1K); + + pHdr = (PVUSBURBHDR)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBURBHDR, Urb.abData[cbDataAllocated])); + if (RT_UNLIKELY(!pHdr)) + { + RTCritSectLeave(&pUrbPool->CritSectPool); + AssertLogRelFailedReturn(NULL); + } + + pHdr->cbAllocated = cbDataAllocated; + pHdr->cAge = 0; + ASMAtomicIncU32(&pUrbPool->cUrbsInPool); + } + RTCritSectLeave(&pUrbPool->CritSectPool); + + Assert(pHdr->cbAllocated >= cbMem); + + /* + * (Re)init the URB + */ + uint32_t offAlloc = (uint32_t)cbData; + PVUSBURB pUrb = &pHdr->Urb; + pUrb->u32Magic = VUSBURB_MAGIC; + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + pUrb->pszDesc = NULL; + pUrb->pVUsb = (PVUSBURBVUSB)&pUrb->abData[offAlloc]; + offAlloc += sizeof(VUSBURBVUSBINT); + pUrb->pVUsb->pUrb = pUrb; + pUrb->pVUsb->pvFreeCtx = NULL; + pUrb->pVUsb->pfnFree = NULL; + pUrb->pVUsb->pCtrlUrb = NULL; + pUrb->pVUsb->u64SubmitTS = 0; + pUrb->Dev.pvPrivate = NULL; + pUrb->Dev.pNext = NULL; + pUrb->EndPt = UINT8_MAX; + pUrb->enmType = enmType; + pUrb->enmDir = enmDir; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_INVALID; + pUrb->cbData = (uint32_t)cbData; + pUrb->pHci = cbHci ? (PVUSBURBHCI)&pUrb->abData[offAlloc] : NULL; + offAlloc += (uint32_t)cbHci; + pUrb->paTds = (cbHciTd && cTds) ? (PVUSBURBHCITD)&pUrb->abData[offAlloc] : NULL; + + return pUrb; +} + + +DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb) +{ + PVUSBURBHDR pHdr = VUSBURBPOOL_URB_2_URBHDR(pUrb); + + /* URBs which aged too much because they are too big are freed. */ + if (pHdr->cAge == VUSBURB_AGE_MAX) + { + ASMAtomicDecU32(&pUrbPool->cUrbsInPool); + RTMemFree(pHdr); + } + else + { + /* Put it into the list of free URBs. */ + VUSBXFERTYPE enmType = pUrb->enmType; + AssertReturnVoid((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs)); + RTCritSectEnter(&pUrbPool->CritSectPool); + pUrb->enmState = VUSBURBSTATE_FREE; + RTListAppend(&pUrbPool->aLstFreeUrbs[enmType], &pHdr->NdFree); + RTCritSectLeave(&pUrbPool->CritSectPool); + } +} + diff --git a/src/VBox/Devices/USB/VUSBUrbTrace.cpp b/src/VBox/Devices/USB/VUSBUrbTrace.cpp new file mode 100644 index 00000000..8bf9a27b --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrbTrace.cpp @@ -0,0 +1,831 @@ +/* $Id: VUSBUrbTrace.cpp $ */ +/** @file + * Virtual USB - URBs. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <iprt/errcore.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/env.h> +#include "VUSBInternal.h" + + + +#ifdef LOG_ENABLED +DECLINLINE(const char *) GetScsiErrCd(uint8_t ScsiErr) +{ + switch (ScsiErr) + { + case 0: return "?"; + } + return "?"; +} + +DECLINLINE(const char *) GetScsiKCQ(uint8_t Key, uint8_t ASC, uint8_t ASCQ) +{ + switch (Key) + { + case 0: + switch (RT_MAKE_U16(ASC, ASCQ)) + { + case RT_MAKE_U16(0x00, 0x00): return "No error"; + } + break; + + case 1: + return "Soft Error"; + + case 2: + return "Not Ready"; + + case 3: + return "Medium Error"; + + case 4: + return "Hard Error"; + + case 5: + return "Illegal Request"; + + case 6: + return "Unit Attention"; + + case 7: + return "Write Protected"; + + case 0xb: + return "Aborted Command"; + } + return "?"; +} + +DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus) +{ + /** Strings for the URB statuses. */ + static const char * const s_apszNames[] = + { + "OK", + "STALL", + "ERR_DNR", + "ERR_CRC", + "DATA_UNDERRUN", + "DATA_OVERRUN", + "NOT_ACCESSED", + "7", "8", "9", "10", "11", "12", "13", "14", "15" + }; + + return enmStatus < (int)RT_ELEMENTS(s_apszNames) + ? s_apszNames[enmStatus] + : enmStatus == VUSBSTATUS_INVALID + ? "INVALID" + : "??"; +} + +DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir) +{ + /** Strings for the URB directions. */ + static const char * const s_apszNames[] = + { + "setup", + "in", + "out" + }; + + return enmDir < (int)RT_ELEMENTS(s_apszNames) + ? s_apszNames[enmDir] + : "??"; +} + +DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType) +{ + /** Strings for the URB types. */ + static const char * const s_apszName[] = + { + "control-part", + "isochronous", + "bulk", + "interrupt", + "control" + }; + + return enmType < (int)RT_ELEMENTS(s_apszName) + ? s_apszName[enmType] + : "??"; +} + +/** + * Logs an URB. + * + * Note that pUrb->pVUsb->pDev and pUrb->pVUsb->pDev->pUsbIns can all be NULL. + */ +DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete) +{ + PVUSBDEV pDev = pUrb->pVUsb ? pUrb->pVUsb->pDev : NULL; /* Can be NULL when called from usbProxyConstruct and friends. */ + PVUSBPIPE pPipe = pDev ? &pDev->aPipes[pUrb->EndPt] : NULL; + const uint8_t *pbData = pUrb->abData; + uint32_t cbData = pUrb->cbData; + PCVUSBSETUP pSetup = NULL; + bool fDescriptors = false; + static size_t s_cchMaxMsg = 10; + size_t cchMsg = strlen(pszMsg); + if (cchMsg > s_cchMaxMsg) + s_cchMaxMsg = cchMsg; + + Log(("%s: %*s: pDev=%p[%s] rc=%s a=%i e=%u d=%s t=%s cb=%#x(%d) ts=%RU64 (%RU64 ns ago) %s\n", + pUrb->pszDesc, s_cchMaxMsg, pszMsg, + pDev, + pUrb->pVUsb && pUrb->pVUsb->pDev && pUrb->pVUsb->pDev->pUsbIns ? pUrb->pVUsb->pDev->pUsbIns->pszName : "", + vusbUrbStatusName(pUrb->enmStatus), + pDev ? pDev->u8Address : -1, + pUrb->EndPt, + vusbUrbDirName(pUrb->enmDir), + vusbUrbTypeName(pUrb->enmType), + pUrb->cbData, + pUrb->cbData, + pUrb->pVUsb ? pUrb->pVUsb->u64SubmitTS : 0, + pUrb->pVUsb ? RTTimeNanoTS() - pUrb->pVUsb->u64SubmitTS : 0, + pUrb->fShortNotOk ? "ShortNotOk" : "ShortOk")); + +#ifndef DEBUG_bird + if ( pUrb->enmType == VUSBXFERTYPE_CTRL + && pUrb->enmStatus == VUSBSTATUS_OK) + return; +#endif + + if ( pUrb->enmType == VUSBXFERTYPE_MSG + || ( pUrb->enmDir == VUSBDIRECTION_SETUP + && pUrb->enmType == VUSBXFERTYPE_CTRL + && cbData)) + { + static const char * const s_apszReqDirs[] = {"host2dev", "dev2host"}; + static const char * const s_apszReqTypes[] = {"std", "class", "vendor", "reserved"}; + static const char * const s_apszReqRecipients[] = {"dev", "if", "endpoint", "other"}; + static const char * const s_apszRequests[] = + { + "GET_STATUS", "CLEAR_FEATURE", "2?", "SET_FEATURE", + "4?", "SET_ADDRESS", "GET_DESCRIPTOR", "SET_DESCRIPTOR", + "GET_CONFIGURATION", "SET_CONFIGURATION", "GET_INTERFACE", "SET_INTERFACE", + "SYNCH_FRAME" + }; + pSetup = (PVUSBSETUP)pUrb->abData; + pbData += sizeof(*pSetup); + cbData -= sizeof(*pSetup); + + Log(("%s: %*s: CTRL: bmRequestType=0x%.2x (%s %s %s) bRequest=0x%.2x (%s) wValue=0x%.4x wIndex=0x%.4x wLength=0x%.4x\n", + pUrb->pszDesc, s_cchMaxMsg, pszMsg, + pSetup->bmRequestType, s_apszReqDirs[pSetup->bmRequestType >> 7], s_apszReqTypes[(pSetup->bmRequestType >> 5) & 0x3], + (unsigned)(pSetup->bmRequestType & 0xf) < RT_ELEMENTS(s_apszReqRecipients) ? s_apszReqRecipients[pSetup->bmRequestType & 0xf] : "??", + pSetup->bRequest, pSetup->bRequest < RT_ELEMENTS(s_apszRequests) ? s_apszRequests[pSetup->bRequest] : "??", + pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + + if ( pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR + && fComplete + && pUrb->enmStatus == VUSBSTATUS_OK + && ((pSetup->bmRequestType >> 5) & 0x3) < 2 /* vendor */) + fDescriptors = true; + } + else if ( fComplete + && pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->enmType == VUSBXFERTYPE_CTRL + && pUrb->enmStatus == VUSBSTATUS_OK + && pPipe->pCtrl + && pPipe->pCtrl->enmStage == CTLSTAGE_DATA + && cbData > 0) + { + pSetup = pPipe->pCtrl->pMsg; + if (pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR) + { + /* HID report (0x22) and physical (0x23) descriptors do not use standard format + * with descriptor length/type at the front. Don't try to dump them, we'll only + * misinterpret them. + */ + if ( ((pSetup->bmRequestType >> 5) & 0x3) == 1 /* class */ + && ((RT_HIBYTE(pSetup->wValue) == 0x22) || (RT_HIBYTE(pSetup->wValue) == 0x23))) + { + fDescriptors = false; + } + } + else + fDescriptors = true; + } + + /* + * Dump descriptors. + */ + if (fDescriptors) + { + const uint8_t *pb = pbData; + const uint8_t *pbEnd = pbData + cbData; + while (pb + 1 < pbEnd) + { + const unsigned cbLeft = pbEnd - pb; + const unsigned cbLength = *pb; + unsigned cb = cbLength; + uint8_t bDescriptorType = pb[1]; + + /* length out of bounds? */ + if (cbLength > cbLeft) + { + cb = cbLeft; + if (cbLength != 0xff) /* ignore this */ + Log(("URB: %*s: DESC: warning descriptor length goes beyond the end of the URB! cbLength=%d cbLeft=%d\n", + s_cchMaxMsg, pszMsg, cbLength, cbLeft)); + } + + if (cb >= 2) + { + Log(("URB: %*s: DESC: %04x: %25s = %#04x (%d)\n" + "URB: %*s: %04x: %25s = %#04x (", + s_cchMaxMsg, pszMsg, pb - pbData, "bLength", cbLength, cbLength, + s_cchMaxMsg, pszMsg, pb - pbData + 1, "bDescriptorType", bDescriptorType)); + + #pragma pack(1) + #define BYTE_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \ + Log(("URB: %*s: %04x: %25s = %#04x\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)])) + #define BYTE_FIELD_START(strct, memb) do { \ + if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \ + { \ + Log(("URB: %*s: %04x: %25s = %#04x", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)])) + #define BYTE_FIELD_END(strct, memb) \ + Log(("\n")); \ + } } while (0) + #define WORD_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \ + Log(("URB: %*s: %04x: %25s = %#06x\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)])) + #define BCD_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \ + Log(("URB: %*s: %04x: %25s = %#06x (%02x.%02x)\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)], \ + pb[RT_OFFSETOF(strct, memb) + 1], pb[RT_OFFSETOF(strct, memb)])) + #define SIZE_CHECK(strct) \ + if (cb > sizeof(strct)) \ + Log(("URB: %*s: %04x: WARNING %d extra byte(s) %.*Rhxs\n", s_cchMaxMsg, pszMsg, \ + pb + sizeof(strct) - pbData, cb - sizeof(strct), cb - sizeof(strct), pb + sizeof(strct))); \ + else if (cb < sizeof(strct)) \ + Log(("URB: %*s: %04x: WARNING %d missing byte(s)! Expected size %d.\n", s_cchMaxMsg, pszMsg, \ + pb + cb - pbData, sizeof(strct) - cb, sizeof(strct))) + + /* on type */ + switch (bDescriptorType) + { + case VUSB_DT_DEVICE: + { + struct dev_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } *pDesc = (struct dev_desc *)pb; NOREF(pDesc); + Log(("DEV)\n")); + BCD_FIELD( struct dev_desc, bcdUSB); + BYTE_FIELD(struct dev_desc, bDeviceClass); + BYTE_FIELD(struct dev_desc, bDeviceSubClass); + BYTE_FIELD(struct dev_desc, bDeviceProtocol); + BYTE_FIELD(struct dev_desc, bMaxPacketSize0); + WORD_FIELD(struct dev_desc, idVendor); + WORD_FIELD(struct dev_desc, idProduct); + BCD_FIELD( struct dev_desc, bcdDevice); + BYTE_FIELD(struct dev_desc, iManufacturer); + BYTE_FIELD(struct dev_desc, iProduct); + BYTE_FIELD(struct dev_desc, iSerialNumber); + BYTE_FIELD(struct dev_desc, bNumConfigurations); + SIZE_CHECK(struct dev_desc); + break; + } + + case VUSB_DT_CONFIG: + { + struct cfg_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t MaxPower; + } *pDesc = (struct cfg_desc *)pb; NOREF(pDesc); + Log(("CFG)\n")); + WORD_FIELD(struct cfg_desc, wTotalLength); + BYTE_FIELD(struct cfg_desc, bNumInterfaces); + BYTE_FIELD(struct cfg_desc, bConfigurationValue); + BYTE_FIELD(struct cfg_desc, iConfiguration); + BYTE_FIELD_START(struct cfg_desc, bmAttributes); + static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; + static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" }; + static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" }; + Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)], + s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)])); + BYTE_FIELD_END(struct cfg_desc, bmAttributes); + BYTE_FIELD(struct cfg_desc, MaxPower); + SIZE_CHECK(struct cfg_desc); + break; + } + + case VUSB_DT_STRING: + if (!pSetup->wIndex) + { + /* langid array */ + uint16_t *pu16 = (uint16_t *)pb + 1; + Log(("LANGIDs)\n")); + while ((uintptr_t)pu16 + 2 - (uintptr_t)pb <= cb) + { + Log(("URB: %*s: %04x: wLANGID[%#x] = %#06x\n", + s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, pu16 - (uint16_t *)pb, *pu16)); + pu16++; + } + if (cb & 1) + Log(("URB: %*s: %04x: WARNING descriptor size is odd! extra byte: %02\n", + s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, *(uint8_t *)pu16)); + } + else + { + /** a string. */ + Log(("STRING)\n")); + if (cb > 2) + Log(("URB: %*s: %04x: Length=%d String=%.*ls\n", + s_cchMaxMsg, pszMsg, pb - pbData, cb - 2, cb / 2 - 1, pb + 2)); + else + Log(("URB: %*s: %04x: Length=0\n", s_cchMaxMsg, pszMsg, pb - pbData)); + } + break; + + case VUSB_DT_INTERFACE: + { + struct if_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } *pDesc = (struct if_desc *)pb; NOREF(pDesc); + Log(("IF)\n")); + BYTE_FIELD(struct if_desc, bInterfaceNumber); + BYTE_FIELD(struct if_desc, bAlternateSetting); + BYTE_FIELD(struct if_desc, bNumEndpoints); + BYTE_FIELD(struct if_desc, bInterfaceClass); + BYTE_FIELD(struct if_desc, bInterfaceSubClass); + BYTE_FIELD(struct if_desc, bInterfaceProtocol); + BYTE_FIELD(struct if_desc, iInterface); + SIZE_CHECK(struct if_desc); + break; + } + + case VUSB_DT_ENDPOINT: + { + struct ep_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + } *pDesc = (struct ep_desc *)pb; NOREF(pDesc); + Log(("EP)\n")); + BYTE_FIELD(struct ep_desc, bEndpointAddress); + BYTE_FIELD(struct ep_desc, bmAttributes); + WORD_FIELD(struct ep_desc, wMaxPacketSize); + BYTE_FIELD(struct ep_desc, bInterval); + SIZE_CHECK(struct ep_desc); + break; + } + + case VUSB_DT_DEVICE_QUALIFIER: + { + struct dq_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + uint8_t bReserved; + } *pDQDesc = (struct dq_desc *)pb; NOREF(pDQDesc); + Log(("DEVQ)\n")); + BCD_FIELD( struct dq_desc, bcdUSB); + BYTE_FIELD(struct dq_desc, bDeviceClass); + BYTE_FIELD(struct dq_desc, bDeviceSubClass); + BYTE_FIELD(struct dq_desc, bDeviceProtocol); + BYTE_FIELD(struct dq_desc, bMaxPacketSize0); + BYTE_FIELD(struct dq_desc, bNumConfigurations); + BYTE_FIELD(struct dq_desc, bReserved); + SIZE_CHECK(struct dq_desc); + break; + } + + case VUSB_DT_OTHER_SPEED_CFG: + { + struct oth_cfg_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t MaxPower; + } *pDesc = (struct oth_cfg_desc *)pb; NOREF(pDesc); + Log(("OCFG)\n")); + WORD_FIELD(struct oth_cfg_desc, wTotalLength); + BYTE_FIELD(struct oth_cfg_desc, bNumInterfaces); + BYTE_FIELD(struct oth_cfg_desc, bConfigurationValue); + BYTE_FIELD(struct oth_cfg_desc, iConfiguration); + BYTE_FIELD_START(struct oth_cfg_desc, bmAttributes); + static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; + static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" }; + static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" }; + Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)], + s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)])); + BYTE_FIELD_END(struct oth_cfg_desc, bmAttributes); + BYTE_FIELD(struct oth_cfg_desc, MaxPower); + SIZE_CHECK(struct oth_cfg_desc); + break; + } + + case 0x21: + { + struct hid_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHid; + uint8_t bCountry; + uint8_t bNumDescriptors; + uint8_t bReportType; + uint16_t wReportLength; + } *pDesc = (struct hid_desc *)pb; NOREF(pDesc); + Log(("EP)\n")); + BCD_FIELD( struct hid_desc, bcdHid); + BYTE_FIELD(struct hid_desc, bCountry); + BYTE_FIELD(struct hid_desc, bNumDescriptors); + BYTE_FIELD(struct hid_desc, bReportType); + WORD_FIELD(struct hid_desc, wReportLength); + SIZE_CHECK(struct hid_desc); + break; + } + + case 0xff: + Log(("UNKNOWN-ignore)\n")); + break; + + default: + Log(("UNKNOWN)!!!\n")); + break; + } + + #undef BYTE_FIELD + #undef WORD_FIELD + #undef BCD_FIELD + #undef SIZE_CHECK + #pragma pack() + } + else + { + Log(("URB: %*s: DESC: %04x: bLength=%d bDescriptorType=%d - invalid length\n", + s_cchMaxMsg, pszMsg, pb - pbData, cb, bDescriptorType)); + break; + } + + /* next */ + pb += cb; + } + } + + /* + * SCSI + */ + if ( pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_OUT + && pUrb->cbData >= 12 + && !memcmp(pUrb->abData, "USBC", 4)) + { + const struct usbc + { + uint32_t Signature; + uint32_t Tag; + uint32_t DataTransferLength; + uint8_t Flags; + uint8_t Lun; + uint8_t Length; + uint8_t CDB[13]; + } *pUsbC = (struct usbc *)pUrb->abData; + Log(("URB: %*s: SCSI: Tag=%#x DataTransferLength=%#x Flags=%#x Lun=%#x Length=%#x CDB=%.*Rhxs\n", + s_cchMaxMsg, pszMsg, pUsbC->Tag, pUsbC->DataTransferLength, pUsbC->Flags, pUsbC->Lun, + pUsbC->Length, pUsbC->Length, pUsbC->CDB)); + const uint8_t *pb = &pUsbC->CDB[0]; + switch (pb[0]) + { + case 0x00: /* test unit read */ + Log(("URB: %*s: SCSI: TEST_UNIT_READY LUN=%d Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[5])); + break; + case 0x03: /* Request Sense command */ + Log(("URB: %*s: SCSI: REQUEST_SENSE LUN=%d AlcLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[4], pb[5])); + break; + case 0x12: /* Inquiry command. */ + Log(("URB: %*s: SCSI: INQUIRY EVPD=%d LUN=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] & 1, pb[1] >> 5, pb[2], pb[4], pb[5])); + break; + case 0x1a: /* Mode Sense(6) command */ + Log(("URB: %*s: SCSI: MODE_SENSE6 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f, pb[4], pb[5])); + break; + case 0x5a: + Log(("URB: %*s: SCSI: MODE_SENSE10 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f, + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0x25: /* Read Capacity(6) command. */ + Log(("URB: %*s: SCSI: READ_CAPACITY\n", + s_cchMaxMsg, pszMsg)); + break; + case 0x28: /* Read(10) command. */ + Log(("URB: %*s: SCSI: READ10 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0xa8: /* Read(12) command. */ + Log(("URB: %*s: SCSI: READ12 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]), + pb[11])); + break; + case 0x3e: /* Read Long command. */ + Log(("URB: %*s: SCSI: READ LONG RelAdr=%d Correct=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(1)), pb[1] >> 5, + RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]), + pb[11])); + break; + case 0x2a: /* Write(10) command. */ + Log(("URB: %*s: SCSI: WRITE10 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(2)), !!(pb[1] & RT_BIT(3)), + !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0xaa: /* Write(12) command. */ + Log(("URB: %*s: SCSI: WRITE12 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), + !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]), + pb[11])); + break; + case 0x3f: /* Write Long command. */ + Log(("URB: %*s: SCSI: WRITE LONG RelAdr=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, pb[1] >> 5, + RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]), + pb[11])); + break; + case 0x35: /* Synchronize Cache(10) command. */ + Log(("URB: %*s: SCSI: SYNCHRONIZE_CACHE10\n", + s_cchMaxMsg, pszMsg)); + break; + case 0xa0: /* Report LUNs command. */ + Log(("URB: %*s: SCSI: REPORT_LUNS\n", + s_cchMaxMsg, pszMsg)); + break; + default: + Log(("URB: %*s: SCSI: cmd=%#x\n", + s_cchMaxMsg, pszMsg, pb[0])); + break; + } + if (pDev) + pDev->Urb.u8ScsiCmd = pb[0]; + } + else if ( fComplete + && pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->cbData >= 12 + && !memcmp(pUrb->abData, "USBS", 4)) + { + const struct usbs + { + uint32_t Signature; + uint32_t Tag; + uint32_t DataResidue; + uint8_t Status; + uint8_t CDB[3]; + } *pUsbS = (struct usbs *)pUrb->abData; + static const char * const s_apszStatuses[] = { "PASSED", "FAILED", "PHASE ERROR", "RESERVED" }; + Log(("URB: %*s: SCSI: Tag=%#x DataResidue=%#RX32 Status=%#RX8 %s\n", + s_cchMaxMsg, pszMsg, pUsbS->Tag, pUsbS->DataResidue, pUsbS->Status, + s_apszStatuses[pUsbS->Status < RT_ELEMENTS(s_apszStatuses) ? pUsbS->Status : RT_ELEMENTS(s_apszStatuses) - 1])); + if (pDev) + pDev->Urb.u8ScsiCmd = 0xff; + } + else if ( fComplete + && pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_IN + && pDev + && pDev->Urb.u8ScsiCmd != 0xff) + { + const uint8_t *pb = pUrb->abData; + switch (pDev->Urb.u8ScsiCmd) + { + case 0x03: /* REQUEST_SENSE */ + Log(("URB: %*s: SCSI: RESPONSE: REQUEST_SENSE (%s)\n", + s_cchMaxMsg, pszMsg, pb[0] & 7 ? "scsi compliant" : "not scsi compliant")); + Log(("URB: %*s: SCSI: ErrCd=%#RX8 (%s) Seg=%#RX8 Filemark=%d EOM=%d ILI=%d\n", + s_cchMaxMsg, pszMsg, pb[0] & 0x7f, GetScsiErrCd(pb[0] & 0x7f), pb[1], + pb[2] >> 7, !!(pb[2] & RT_BIT(6)), !!(pb[2] & RT_BIT(5)))); + Log(("URB: %*s: SCSI: SenseKey=%#x ASC=%#RX8 ASCQ=%#RX8 : %s\n", + s_cchMaxMsg, pszMsg, pb[2] & 0xf, pb[12], pb[13], + GetScsiKCQ(pb[2] & 0xf, pb[12], pb[13]))); + /** @todo more later */ + break; + + case 0x12: /* INQUIRY. */ + { + unsigned cb = pb[4] + 5; + Log(("URB: %*s: SCSI: RESPONSE: INQUIRY\n" + "URB: %*s: SCSI: PeripheralQualifier=%d PeripheralType=%#RX8 RMB=%d DevTypeMod=%#RX8\n", + s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg, + pb[0] >> 5, pb[0] & 0x1f, pb[1] >> 7, pb[1] & 0x7f)); + Log(("URB: %*s: SCSI: ISOVer=%d ECMAVer=%d ANSIVer=%d\n", + s_cchMaxMsg, pszMsg, pb[2] >> 6, (pb[2] >> 3) & 7, pb[2] & 7)); + Log(("URB: %*s: SCSI: AENC=%d TrmlOP=%d RespDataFmt=%d (%s) AddLen=%d\n", + s_cchMaxMsg, pszMsg, pb[3] >> 7, (pb[3] >> 6) & 1, + pb[3] & 0xf, pb[3] & 0xf ? "legacy" : "scsi", pb[4])); + if (cb < 8) + break; + Log(("URB: %*s: SCSI: RelAdr=%d WBus32=%d WBus16=%d Sync=%d Linked=%d CmdQue=%d SftRe=%d\n", + s_cchMaxMsg, pszMsg, pb[7] >> 7, !!(pb[7] >> 6), !!(pb[7] >> 5), !!(pb[7] >> 4), + !!(pb[7] >> 3), !!(pb[7] >> 1), pb[7] & 1)); + if (cb < 16) + break; + Log(("URB: %*s: SCSI: VendorId=%.8s\n", s_cchMaxMsg, pszMsg, &pb[8])); + if (cb < 32) + break; + Log(("URB: %*s: SCSI: ProductId=%.16s\n", s_cchMaxMsg, pszMsg, &pb[16])); + if (cb < 36) + break; + Log(("URB: %*s: SCSI: ProdRevLvl=%.4s\n", s_cchMaxMsg, pszMsg, &pb[32])); + if (cb > 36) + Log(("URB: %*s: SCSI: VendorSpecific=%.*s\n", + s_cchMaxMsg, pszMsg, RT_MIN(cb - 36, 20), &pb[36])); + if (cb > 96) + Log(("URB: %*s: SCSI: VendorParam=%.*Rhxs\n", + s_cchMaxMsg, pszMsg, cb - 96, &pb[96])); + break; + } + + case 0x25: /* Read Capacity(6) command. */ + Log(("URB: %*s: SCSI: RESPONSE: READ_CAPACITY\n" + "URB: %*s: SCSI: LBA=%#RX32 BlockLen=%#RX32\n", + s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg, + RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]), + RT_MAKE_U32_FROM_U8(pb[7], pb[6], pb[5], pb[4]))); + break; + } + + pDev->Urb.u8ScsiCmd = 0xff; + } + + /* + * The Quickcam control pipe. + */ + if ( pSetup + && ((pSetup->bmRequestType >> 5) & 0x3) >= 2 /* vendor */ + && (fComplete || !(pSetup->bmRequestType >> 7)) + && pDev + && pDev->pDescCache + && pDev->pDescCache->pDevice + && pDev->pDescCache->pDevice->idVendor == 0x046d + && ( pDev->pDescCache->pDevice->idProduct == 0x8f6 + || pDev->pDescCache->pDevice->idProduct == 0x8f5 + || pDev->pDescCache->pDevice->idProduct == 0x8f0) + ) + { + pbData = (const uint8_t *)(pSetup + 1); + cbData = pUrb->cbData - sizeof(*pSetup); + + if ( pSetup->bRequest == 0x04 + && pSetup->wIndex == 0 + && (cbData == 1 || cbData == 2)) + { + /* the value */ + unsigned uVal = pbData[0]; + if (cbData > 1) + uVal |= (unsigned)pbData[1] << 8; + + const char *pszReg = NULL; + switch (pSetup->wValue) + { + case 0: pszReg = "i2c init"; break; + case 0x0423: pszReg = "STV_REG23"; break; + case 0x0509: pszReg = "RED something"; break; + case 0x050a: pszReg = "GREEN something"; break; + case 0x050b: pszReg = "BLUE something"; break; + case 0x143f: pszReg = "COMMIT? INIT DONE?"; break; + case 0x1440: pszReg = "STV_ISO_ENABLE"; break; + case 0x1442: pszReg = uVal & (RT_BIT(7)|RT_BIT(5)) ? "BUTTON PRESSED" : "BUTTON" ; break; + case 0x1443: pszReg = "STV_SCAN_RATE"; break; + case 0x1445: pszReg = "LED?"; break; + case 0x1500: pszReg = "STV_REG00"; break; + case 0x1501: pszReg = "STV_REG01"; break; + case 0x1502: pszReg = "STV_REG02"; break; + case 0x1503: pszReg = "STV_REG03"; break; + case 0x1504: pszReg = "STV_REG04"; break; + case 0x15c1: pszReg = "STV_ISO_SIZE"; break; + case 0x15c3: pszReg = "STV_Y_CTRL"; break; + case 0x1680: pszReg = "STV_X_CTRL"; break; + case 0xe00a: pszReg = "ProductId"; break; + default: pszReg = "[no clue]"; break; + } + if (pszReg) + Log(("URB: %*s: QUICKCAM: %s %#x (%d) %s '%s' (%#x)\n", + s_cchMaxMsg, pszMsg, + (pSetup->bmRequestType >> 7) ? "read" : "write", uVal, uVal, (pSetup->bmRequestType >> 7) ? "from" : "to", + pszReg, pSetup->wValue)); + } + else if (cbData) + Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: %.*Rhxs\n", s_cchMaxMsg, pszMsg, + pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex, cbData, pbData)); + else + Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: (no data)\n", s_cchMaxMsg, pszMsg, + pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex)); + } + +#if 1 + if ( cbData /** @todo Fix RTStrFormatV to communicate .* so formatter doesn't apply defaults when cbData=0. */ + && (fComplete + ? pUrb->enmDir != VUSBDIRECTION_OUT + : pUrb->enmDir == VUSBDIRECTION_OUT)) + Log3(("%16.*Rhxd\n", cbData, pbData)); +#endif + if (pUrb->enmType == VUSBXFERTYPE_MSG && pUrb->pVUsb && pUrb->pVUsb->pCtrlUrb) + vusbUrbTrace(pUrb->pVUsb->pCtrlUrb, "NESTED MSG", fComplete); +} +#endif /* LOG_ENABLED */ + diff --git a/src/VBox/Devices/USB/darwin/Makefile.kup b/src/VBox/Devices/USB/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/darwin/Makefile.kup diff --git a/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp new file mode 100644 index 00000000..92929904 --- /dev/null +++ b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp @@ -0,0 +1,2014 @@ +/* $Id: USBProxyDevice-darwin.cpp $ */ +/** @file + * USB device proxy - the Darwin backend. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#define __STDC_LIMIT_MACROS +#define __STDC_CONSTANT_MACROS + +#include <mach/mach.h> +#include <Carbon/Carbon.h> +#include <IOKit/IOKitLib.h> +#include <mach/mach_error.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> +#ifndef __MAC_10_10 /* Quick hack: The following two masks appeared in 10.10. */ +# define kUSBReEnumerateReleaseDeviceMask RT_BIT_32(29) +# define kUSBReEnumerateCaptureDeviceMask RT_BIT_32(30) +#endif + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/string.h> +#include <iprt/time.h> + +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** An experiment... */ +//#define USE_LOW_LATENCY_API 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Forward declaration of the Darwin interface structure. */ +typedef struct USBPROXYIFOSX *PUSBPROXYIFOSX; + + +/** + * A low latency isochronous buffer. + * + * These are allocated in chunks on an interface level, see USBPROXYISOCBUFCOL. + */ +typedef struct USBPROXYISOCBUF +{ + /** Whether this buffer is in use or not. */ + bool volatile fUsed; + /** Pointer to the buffer. */ + void *pvBuf; + /** Pointer to an array of 8 frames. */ + IOUSBLowLatencyIsocFrame *paFrames; +} USBPROXYISOCBUF, *PUSBPROXYISOCBUF; + + +/** + * Isochronous buffer collection (associated with an interface). + * + * These are allocated in decent sized chunks and there isn't supposed + * to be too many of these per interface. + */ +typedef struct USBPROXYISOCBUFCOL +{ + /** Write or Read buffers? */ + USBLowLatencyBufferType enmType; + /** The next buffer collection on this interface. */ + struct USBPROXYISOCBUFCOL *pNext; + /** The buffer. */ + void *pvBuffer; + /** The frame. */ + void *pvFrames; + /** The buffers. + * The number of buffers here is decided by pvFrame begin allocated in + * GUEST_PAGE_SIZE chunks. The size of IOUSBLowLatencyIsocFrame is 16 bytes + * and we require 8 of those per buffer. GUEST_PAGE_SIZE / (16 * 8) = 32. + * @remarks Don't allocate too many as it may temporarily halt the system if + * some pool is low / exhausted. (Contiguous memory woes on mach.) + */ + USBPROXYISOCBUF aBuffers[/*32*/ 4]; +} USBPROXYISOCBUFCOL, *PUSBPROXYISOCBUFCOL; + +AssertCompileSize(IOUSBLowLatencyIsocFrame, 16); + +/** + * Per-urb data for the Darwin usb proxy backend. + * + * This is required to track in-flight and landed URBs + * since we take down the URBs in a different thread (perhaps). + */ +typedef struct USBPROXYURBOSX +{ + /** Pointer to the next Darwin URB. */ + struct USBPROXYURBOSX *pNext; + /** Pointer to the previous Darwin URB. */ + struct USBPROXYURBOSX *pPrev; + /** The millisecond timestamp when this URB was submitted. */ + uint64_t u64SubmitTS; + /** Pointer to the VUSB URB. + * This is set to NULL if canceled. */ + PVUSBURB pVUsbUrb; + /** Pointer to the Darwin device. */ + struct USBPROXYDEVOSX *pDevOsX; + /** The transfer type. */ + VUSBXFERTYPE enmType; + /** Union with data depending on transfer type. */ + union + { + /** The control message. */ + IOUSBDevRequest ControlMsg; + /** The Isochronous Data. */ + struct + { +#ifdef USE_LOW_LATENCY_API + /** The low latency isochronous buffer. */ + PUSBPROXYISOCBUF pBuf; + /** Array of frames parallel to the one in VUSBURB. (Same as pBuf->paFrames.) */ + IOUSBLowLatencyIsocFrame *aFrames; +#else + /** Array of frames parallel to the one in VUSBURB. */ + IOUSBIsocFrame aFrames[8]; +#endif + } Isoc; + } u; +} USBPROXYURBOSX, *PUSBPROXYURBOSX; + +/** + * Per-pipe data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYPIPEOSX +{ + /** The endpoint number. */ + uint8_t u8Endpoint; + /** The IOKit pipe reference. */ + uint8_t u8PipeRef; + /** The pipe Transfer type type. */ + uint8_t u8TransferType; + /** The pipe direction. */ + uint8_t u8Direction; + /** The endpoint interval. (interrupt) */ + uint8_t u8Interval; + /** Full-speed device indicator (isochronous pipes only). */ + bool fIsFullSpeed; + /** The max packet size. */ + uint16_t u16MaxPacketSize; + /** The next frame number (isochronous pipes only). */ + uint64_t u64NextFrameNo; +} USBPROXYPIPEOSX, *PUSBPROXYPIPEOSX, **PPUSBPROXYPIPEOSX; + +typedef struct RUNLOOPREFLIST +{ + RTLISTNODE List; + CFRunLoopRef RunLoopRef; +} RUNLOOPREFLIST, *PRUNLOOPREFLIST; +typedef RUNLOOPREFLIST **PPRUNLOOPREFLIST; + +/** + * Per-interface data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYIFOSX +{ + /** Pointer to the next interface. */ + struct USBPROXYIFOSX *pNext; + /** The interface number. */ + uint8_t u8Interface; + /** The current alternative interface setting. + * This is used to skip unnecessary SetAltInterface calls. */ + uint8_t u8AltSetting; + /** The interface class. (not really used) */ + uint8_t u8Class; + /** The interface protocol. (not really used) */ + uint8_t u8Protocol; + /** The number of pipes. */ + uint8_t cPipes; + /** Array containing all the pipes. (Currently unsorted.) */ + USBPROXYPIPEOSX aPipes[kUSBMaxPipes]; + /** The IOUSBDeviceInterface. */ + IOUSBInterfaceInterface245 **ppIfI; + /** The run loop source for the async operations on the interface level. */ + CFRunLoopSourceRef RunLoopSrcRef; + /** List of isochronous buffer collections. + * These are allocated on demand by the URB queuing routine and then recycled until the interface is destroyed. */ + RTLISTANCHOR HeadOfRunLoopLst; + PUSBPROXYISOCBUFCOL pIsocBufCols; +} USBPROXYIFOSX, *PUSBPROXYIFOSX, **PPUSBPROXYIFOSX; +/** Pointer to a pointer to an darwin interface. */ +typedef USBPROXYIFOSX **PPUSBPROXYIFOSX; + +/** + * Per-device Data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYDEVOSX +{ + /** The USB Device IOService object. */ + io_object_t USBDevice; + /** The IOUSBDeviceInterface. */ + IOUSBDeviceInterface245 **ppDevI; + /** The run loop source for the async operations on the device level + * (i.e. the default control pipe stuff). */ + CFRunLoopSourceRef RunLoopSrcRef; + /** we want to add and remove RunLoopSourceRefs to run loop's of + * every EMT thread participated in USB processing. */ + RTLISTANCHOR HeadOfRunLoopLst; + /** Pointer to the proxy device instance. */ + PUSBPROXYDEV pProxyDev; + + /** Pointer to the first interface. */ + PUSBPROXYIFOSX pIfHead; + /** Pointer to the last interface. */ + PUSBPROXYIFOSX pIfTail; + + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** The list of free Darwin URBs. Singly linked. */ + PUSBPROXYURBOSX pFreeHead; + /** The list of landed Darwin URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBOSX pTaxingHead; + /** The tail of the landed Darwin URBs. */ + PUSBPROXYURBOSX pTaxingTail; + /** Last reaper runloop reference, there can be only one runloop at a time. */ + CFRunLoopRef hRunLoopReapingLast; + /** Runloop source for waking up the reaper thread. */ + CFRunLoopSourceRef hRunLoopSrcWakeRef; + /** List of threads used for reaping which can be woken up. */ + RTLISTANCHOR HeadOfRunLoopWakeLst; + /** Runloop reference of the thread reaping. */ + volatile CFRunLoopRef hRunLoopReaping; + /** Flag whether the reaping thread is about the be waked. */ + volatile bool fReapingThreadWake; +} USBPROXYDEVOSX, *PUSBPROXYDEVOSX; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTONCE g_usbProxyDarwinOnce = RTONCE_INITIALIZER; +/** The runloop mode we use. + * Since it's difficult to remove this, we leak it to prevent crashes. + * @bugref{4407} */ +static CFStringRef g_pRunLoopMode = NULL; +/** The IO Master Port. + * Not worth cleaning up. */ +static mach_port_t g_MasterPort = MACH_PORT_NULL; + + +/** + * Init once callback that sets up g_MasterPort and g_pRunLoopMode. + * + * @returns IPRT status code. + * + * @param pvUser1 NULL, ignored. + */ +static DECLCALLBACK(int32_t) usbProxyDarwinInitOnce(void *pvUser1) +{ + RT_NOREF(pvUser1); + + int rc; + kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort); + if (krc == KERN_SUCCESS) + { + g_pRunLoopMode = CFStringCreateWithCString(kCFAllocatorDefault, "VBoxUsbProxyMode", kCFStringEncodingUTF8); + if (g_pRunLoopMode) + return VINF_SUCCESS; + rc = VERR_INTERNAL_ERROR_5; + } + else + rc = RTErrConvertFromDarwin(krc); + return rc; +} + +/** + * Kicks the reaper thread if it sleeps currently to respond to state changes + * or to pick up completed URBs. + * + * @returns nothing. + * @param pDevOsX The darwin device instance data. + */ +static void usbProxyDarwinReaperKick(PUSBPROXYDEVOSX pDevOsX) +{ + CFRunLoopRef hRunLoopWake = (CFRunLoopRef)ASMAtomicReadPtr((void * volatile *)&pDevOsX->hRunLoopReaping); + if (hRunLoopWake) + { + LogFlowFunc(("Waking runloop %p\n", hRunLoopWake)); + CFRunLoopSourceSignal(pDevOsX->hRunLoopSrcWakeRef); + CFRunLoopWakeUp(hRunLoopWake); + } +} + +/** + * Adds Source ref to current run loop and adds it the list of runloops. + */ +static int usbProxyDarwinAddRunLoopRef(PRTLISTANCHOR pListHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pListHead, VERR_INVALID_PARAMETER); + AssertReturn(CFRunLoopSourceIsValid(SourceRef), VERR_INVALID_PARAMETER); + + if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), SourceRef, g_pRunLoopMode)) + return VINF_SUCCESS; + + /* Add to the list */ + PRUNLOOPREFLIST pListNode = (PRUNLOOPREFLIST)RTMemAllocZ(sizeof(RUNLOOPREFLIST)); + if (!pListNode) + return VERR_NO_MEMORY; + + pListNode->RunLoopRef = CFRunLoopGetCurrent(); + + CFRetain(pListNode->RunLoopRef); + CFRetain(SourceRef); /* We want to be aware of releasing */ + + CFRunLoopAddSource(pListNode->RunLoopRef, SourceRef, g_pRunLoopMode); + + RTListInit(&pListNode->List); + + RTListAppend((PRTLISTNODE)pListHead, &pListNode->List); + + return VINF_SUCCESS; +} + + +/* + * Removes all source reference from mode of run loop's we've registered them. + * + */ +static int usbProxyDarwinRemoveSourceRefFromAllRunLoops(PRTLISTANCHOR pHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pHead, VERR_INVALID_PARAMETER); + + while (!RTListIsEmpty(pHead)) + { + PRUNLOOPREFLIST pNode = RTListGetFirst(pHead, RUNLOOPREFLIST, List); + /* XXX: Should Release Reference? */ + Assert(CFGetRetainCount(pNode->RunLoopRef)); + + CFRunLoopRemoveSource(pNode->RunLoopRef, SourceRef, g_pRunLoopMode); + CFRelease(SourceRef); + CFRelease(pNode->RunLoopRef); + + RTListNodeRemove(&pNode->List); + + RTMemFree(pNode); + } + + return VINF_SUCCESS; +} + + +/** + * Allocates a Darwin URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * + * @param pDevOsX The darwin proxy device. + */ +static PUSBPROXYURBOSX usbProxyDarwinUrbAlloc(PUSBPROXYDEVOSX pDevOsX) +{ + PUSBPROXYURBOSX pUrbOsX; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Try remove a Darwin URB from the free list, if none there allocate a new one. + */ + pUrbOsX = pDevOsX->pFreeHead; + if (pUrbOsX) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTCritSectLeave(&pDevOsX->CritSect); + } + else + { + RTCritSectLeave(&pDevOsX->CritSect); + pUrbOsX = (PUSBPROXYURBOSX)RTMemAlloc(sizeof(*pUrbOsX)); + if (!pUrbOsX) + return NULL; + } + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + return pUrbOsX; +} + + +#ifdef USE_LOW_LATENCY_API +/** + * Allocates an low latency isochronous buffer. + * + * @returns VBox status code. + * @param pUrbOsX The OsX URB to allocate it for. + * @param pIf The interface to allocated it from. + */ +static int usbProxyDarwinUrbAllocIsocBuf(PUSBPROXYURBOSX pUrbOsX, PUSBPROXYIFOSX pIf) +{ + USBLowLatencyBufferType enmLLType = pUrbOsX->pVUsbUrb->enmDir == VUSBDIRECTION_IN + ? kUSBLowLatencyWriteBuffer : kUSBLowLatencyReadBuffer; + + /* + * Walk the buffer collection list and look for an unused one. + */ + pUrbOsX->u.Isoc.pBuf = NULL; + for (PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; pCur; pCur = pCur->pNext) + if (pCur->enmType == enmLLType) + for (unsigned i = 0; i < RT_ELEMENTS(pCur->aBuffers); i++) + if (!pCur->aBuffers[i].fUsed) + { + pCur->aBuffers[i].fUsed = true; + pUrbOsX->u.Isoc.pBuf = &pCur->aBuffers[i]; + AssertPtr(pUrbOsX->u.Isoc.pBuf); + AssertPtr(pUrbOsX->u.Isoc.pBuf->pvBuf); + pUrbOsX->u.Isoc.aFrames = pCur->aBuffers[i].paFrames; + AssertPtr(pUrbOsX->u.Isoc.aFrames); + return VINF_SUCCESS; + } + + /* + * Didn't find an empty one, create a new buffer collection and take the first buffer. + */ + PUSBPROXYISOCBUFCOL pNew = (PUSBPROXYISOCBUFCOL)RTMemAllocZ(sizeof(*pNew)); + AssertReturn(pNew, VERR_NO_MEMORY); + + IOReturn irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvBuffer, 8192 * RT_ELEMENTS(pNew->aBuffers), enmLLType); + if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvBuffer)) + { + AssertPtr(pNew->pvBuffer); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + /** @todo GUEST_PAGE_SIZE or HOST_PAGE_SIZE or just 4K? */ + irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvFrames, GUEST_PAGE_SIZE, kUSBLowLatencyFrameListBuffer); + if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvFrames)) + { + AssertPtr(pNew->pvFrames); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + for (unsigned i = 0; i < RT_ELEMENTS(pNew->aBuffers); i++) + { + //pNew->aBuffers[i].fUsed = false; + pNew->aBuffers[i].paFrames = &((IOUSBLowLatencyIsocFrame *)pNew->pvFrames)[i * 8]; + pNew->aBuffers[i].pvBuf = (uint8_t *)pNew->pvBuffer + i * 8192; + } + + pNew->aBuffers[0].fUsed = true; + pUrbOsX->u.Isoc.aFrames = pNew->aBuffers[0].paFrames; + pUrbOsX->u.Isoc.pBuf = &pNew->aBuffers[0]; + + pNew->enmType = enmLLType; + pNew->pNext = pIf->pIsocBufCols; + pIf->pIsocBufCols = pNew; + +#if 0 /* doesn't help :-/ */ + /* + * If this is the first time we're here, try mess with the policy? + */ + if (!pNew->pNext) + for (unsigned iPipe = 0; iPipe < pIf->cPipes; iPipe++) + if (pIf->aPipes[iPipe].u8TransferType == kUSBIsoc) + { + irc = (*pIf->ppIfI)->SetPipePolicy(pIf->ppIfI, pIf->aPipes[iPipe].u8PipeRef, + pIf->aPipes[iPipe].u16MaxPacketSize, pIf->aPipes[iPipe].u8Interval); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } +#endif + + return VINF_SUCCESS; + } + + /* bail out */ + (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pNew->pvBuffer); + } + AssertMsgFailed(("%#x\n", irc)); + RTMemFree(pNew); + + return RTErrConvertFromDarwin(irc); +} +#endif /* USE_LOW_LATENCY_API */ + + +/** + * Frees a Darwin URB request structure. + * + * @param pDevOsX The darwin proxy device. + * @param pUrbOsX The Darwin URB to free. + */ +static void usbProxyDarwinUrbFree(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYURBOSX pUrbOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + +#ifdef USE_LOW_LATENCY_API + /* + * Free low latency stuff. + */ + if ( pUrbOsX->enmType == VUSBXFERTYPE_ISOC + && pUrbOsX->u.Isoc.pBuf) + { + pUrbOsX->u.Isoc.pBuf->fUsed = false; + pUrbOsX->u.Isoc.pBuf = NULL; + } +#endif + + /* + * Link it into the free list. + */ + pUrbOsX->pPrev = NULL; + pUrbOsX->pNext = pDevOsX->pFreeHead; + pDevOsX->pFreeHead = pUrbOsX; + + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = NULL; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + RTCritSectLeave(&pDevOsX->CritSect); +} + +/** + * Translate the IOKit status code to a VUSB status. + * + * @returns VUSB URB status code. + * @param irc IOKit status code. + */ +static VUSBSTATUS vusbProxyDarwinStatusToVUsbStatus(IOReturn irc) +{ + switch (irc) + { + /* IOKit OHCI VUSB */ + case kIOReturnSuccess: /* 0 */ return VUSBSTATUS_OK; + case kIOUSBCRCErr: /* 1 */ return VUSBSTATUS_CRC; + //case kIOUSBBitstufErr: /* 2 */ return VUSBSTATUS_; + //case kIOUSBDataToggleErr: /* 3 */ return VUSBSTATUS_; + case kIOUSBPipeStalled: /* 4 */ return VUSBSTATUS_STALL; + case kIOReturnNotResponding: /* 5 */ return VUSBSTATUS_DNR; + //case kIOUSBPIDCheckErr: /* 6 */ return VUSBSTATUS_; + //case kIOUSBWrongPIDErr: /* 7 */ return VUSBSTATUS_; + case kIOReturnOverrun: /* 8 */ return VUSBSTATUS_DATA_OVERRUN; + case kIOReturnUnderrun: /* 9 */ return VUSBSTATUS_DATA_UNDERRUN; + //case kIOUSBReserved1Err: /* 10 */ return VUSBSTATUS_; + //case kIOUSBReserved2Err: /* 11 */ return VUSBSTATUS_; + //case kIOUSBBufferOverrunErr: /* 12 */ return VUSBSTATUS_; + //case kIOUSBBufferUnderrunErr: /* 13 */ return VUSBSTATUS_; + case kIOUSBNotSent1Err: /* 14 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + case kIOUSBNotSent2Err: /* 15 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + + /* Other errors */ + case kIOUSBTransactionTimeout: return VUSBSTATUS_DNR; + //case kIOReturnAborted: return VUSBSTATUS_CRC; - see on SET_INTERFACE... + + default: + Log(("vusbProxyDarwinStatusToVUsbStatus: irc=%#x!!\n", irc)); + return VUSBSTATUS_STALL; + } +} + + +/** + * Completion callback for an async URB transfer. + * + * @param pvUrbOsX The Darwin URB. + * @param irc The status of the operation. + * @param Size Possible the transfer size. + */ +static void usbProxyDarwinUrbAsyncComplete(void *pvUrbOsX, IOReturn irc, void *Size) +{ + PUSBPROXYURBOSX pUrbOsX = (PUSBPROXYURBOSX)pvUrbOsX; + PUSBPROXYDEVOSX pDevOsX = pUrbOsX->pDevOsX; + const uint32_t cb = (uintptr_t)Size; + + /* + * Do status updates. + */ + PVUSBURB pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + Assert(pUrb->u32Magic == VUSBURB_MAGIC); + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { +#ifdef USE_LOW_LATENCY_API + /* copy the data. */ + //if (pUrb->enmDir == VUSBDIRECTION_IN) + memcpy(pUrb->abData, pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->cbData); +#endif + Log3(("AsyncComplete isoc - raw data (%d bytes):\n" + "%16.*Rhxd\n", pUrb->cbData, pUrb->cbData, pUrb->abData)); + uint32_t off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { +#ifdef USE_LOW_LATENCY_API + Log2((" %d{%d/%d-%x-%RX64}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus, + RT_MAKE_U64(pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo, pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi) )); +#else + Log2((" %d{%d/%d-%x}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus)); +#endif + pUrb->aIsocPkts[i].enmStatus = vusbProxyDarwinStatusToVUsbStatus(pUrbOsX->u.Isoc.aFrames[i].frStatus); + pUrb->aIsocPkts[i].cb = pUrbOsX->u.Isoc.aFrames[i].frActCount; + off += pUrbOsX->u.Isoc.aFrames[i].frActCount; + } + Log2(("\n")); +#if 0 /** @todo revisit this, wasn't working previously. */ + for (int i = (int)pUrb->cIsocPkts - 1; i >= 0; i--) + Assert( !pUrbOsX->u.Isoc.aFrames[i].frActCount + && !pUrbOsX->u.Isoc.aFrames[i].frReqCount + && !pUrbOsX->u.Isoc.aFrames[i].frStatus); +#endif + pUrb->cbData = off; /* 'Size' seems to be pointing at an error code or something... */ + pUrb->enmStatus = VUSBSTATUS_OK; /* Don't use 'irc'. OHCI expects OK unless it's a really bad error. */ + } + else + { + pUrb->cbData = cb; + pUrb->enmStatus = vusbProxyDarwinStatusToVUsbStatus(irc); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + pUrb->cbData += sizeof(VUSBSETUP); + } + } + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Link it into the taxing list. + */ + pUrbOsX->pNext = NULL; + pUrbOsX->pPrev = pDevOsX->pTaxingTail; + if (pDevOsX->pTaxingTail) + pDevOsX->pTaxingTail->pNext = pUrbOsX; + else + pDevOsX->pTaxingHead = pUrbOsX; + pDevOsX->pTaxingTail = pUrbOsX; + + RTCritSectLeave(&pDevOsX->CritSect); + + LogFlow(("%s: usbProxyDarwinUrbAsyncComplete: cb=%d EndPt=%#x irc=%#x (%d)\n", + pUrb->pszDesc, cb, pUrb ? pUrb->EndPt : 0xff, irc, pUrb ? pUrb->enmStatus : 0xff)); +} + +/** + * Release all interfaces (current config). + * + * @param pDevOsX The darwin proxy device. + */ +static void usbProxyDarwinReleaseAllInterfaces(PUSBPROXYDEVOSX pDevOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + + /* Kick the reaper thread out of sleep. */ + usbProxyDarwinReaperKick(pDevOsX); + + PUSBPROXYIFOSX pIf = pDevOsX->pIfHead; + pDevOsX->pIfHead = pDevOsX->pIfTail = NULL; + + while (pIf) + { + PUSBPROXYIFOSX pNext = pIf->pNext; + IOReturn irc; + + if (pIf->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops((PRTLISTANCHOR)&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + AssertRC(rc); + + CFRelease(pIf->RunLoopSrcRef); + pIf->RunLoopSrcRef = NULL; + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + } + + while (pIf->pIsocBufCols) + { + PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; + pIf->pIsocBufCols = pCur->pNext; + pCur->pNext = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvBuffer); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvBuffer = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvFrames); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvFrames = NULL; + + RTMemFree(pCur); + } + + irc = (*pIf->ppIfI)->USBInterfaceClose(pIf->ppIfI); + AssertMsg(irc == kIOReturnSuccess || irc == kIOReturnNoDevice, ("%#x\n", irc)); + + (*pIf->ppIfI)->Release(pIf->ppIfI); + pIf->ppIfI = NULL; + + RTMemFree(pIf); + + pIf = pNext; + } + RTCritSectLeave(&pDevOsX->CritSect); +} + + +/** + * Get the properties all the pipes associated with an interface. + * + * This is used when we seize all the interface and after SET_INTERFACE. + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param pIf The interface to get pipe props for. + */ +static int usbProxyDarwinGetPipeProperties(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYIFOSX pIf) +{ + /* + * Get the pipe (endpoint) count (it might have changed - even on open). + */ + int rc = VINF_SUCCESS; + bool fFullSpeed; + UInt32 u32UsecInFrame; + UInt8 cPipes; + IOReturn irc = (*pIf->ppIfI)->GetNumEndpoints(pIf->ppIfI, &cPipes); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + AssertRelease(cPipes < RT_ELEMENTS(pIf->aPipes)); + pIf->cPipes = cPipes + 1; + + /* Find out if this is a full-speed interface (needed for isochronous support). */ + irc = (*pIf->ppIfI)->GetFrameListTime(pIf->ppIfI, &u32UsecInFrame); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + fFullSpeed = u32UsecInFrame == kUSBFullSpeedMicrosecondsInFrame; + + /* + * Get the properties of each pipe. + */ + for (unsigned i = 0; i < pIf->cPipes; i++) + { + pIf->aPipes[i].u8PipeRef = i; + pIf->aPipes[i].fIsFullSpeed = fFullSpeed; + pIf->aPipes[i].u64NextFrameNo = 0; + irc = (*pIf->ppIfI)->GetPipeProperties(pIf->ppIfI, i, + &pIf->aPipes[i].u8Direction, + &pIf->aPipes[i].u8Endpoint, + &pIf->aPipes[i].u8TransferType, + &pIf->aPipes[i].u16MaxPacketSize, + &pIf->aPipes[i].u8Interval); + if (irc != kIOReturnSuccess) + { + LogRel(("USB: Failed to query properties for pipe %#d / interface %#x on device '%s'. (prot=%#x class=%#x)\n", + i, pIf->u8Interface, pDevOsX->pProxyDev->pUsbIns->pszName, pIf->u8Protocol, pIf->u8Class)); + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + pIf->cPipes = i; + break; + } + /* reconstruct bEndpoint */ + if (pIf->aPipes[i].u8Direction == kUSBIn) + pIf->aPipes[i].u8Endpoint |= 0x80; + Log2(("usbProxyDarwinGetPipeProperties: #If=%d EndPt=%#x Dir=%d Type=%d PipeRef=%#x MaxPktSize=%#x Interval=%#x\n", + pIf->u8Interface, pIf->aPipes[i].u8Endpoint, pIf->aPipes[i].u8Direction, pIf->aPipes[i].u8TransferType, + pIf->aPipes[i].u8PipeRef, pIf->aPipes[i].u16MaxPacketSize, pIf->aPipes[i].u8Interval)); + } + + /** @todo sort or hash these for speedy lookup... */ + return VINF_SUCCESS; +} + + +/** + * Seize all interfaces (current config). + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param fMakeTheBestOfIt If set we will not give up on error. This is for + * use during SET_CONFIGURATION and similar. + */ +static int usbProxyDarwinSeizeAllInterfaces(PUSBPROXYDEVOSX pDevOsX, bool fMakeTheBestOfIt) +{ + PUSBPROXYDEV pProxyDev = pDevOsX->pProxyDev; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Create a interface enumerator for all the interface (current config). + */ + io_iterator_t Interfaces = IO_OBJECT_NULL; + IOUSBFindInterfaceRequest Req; + Req.bInterfaceClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + Req.bAlternateSetting = kIOUSBFindInterfaceDontCare; + IOReturn irc = (*pDevOsX->ppDevI)->CreateInterfaceIterator(pDevOsX->ppDevI, &Req, &Interfaces); + int rc; + if (irc == kIOReturnSuccess) + { + /* + * Iterate the interfaces. + */ + io_object_t Interface; + rc = VINF_SUCCESS; + while ((Interface = IOIteratorNext(Interfaces))) + { + /* + * Create a plug-in and query the IOUSBInterfaceInterface (cute name). + */ + IOCFPlugInInterface **ppPlugInInterface = NULL; + kern_return_t krc; + SInt32 Score = 0; + krc = IOCreatePlugInInterfaceForService(Interface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + IOObjectRelease(Interface); + Interface = IO_OBJECT_NULL; + if (krc == KERN_SUCCESS) + { + IOUSBInterfaceInterface245 **ppIfI; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID245), + (LPVOID *)&ppIfI); + krc = IODestroyPlugInInterface(ppPlugInInterface); Assert(krc == KERN_SUCCESS); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Query some basic properties first. + * (This means we can print more informative messages on failure + * to seize the interface.) + */ + UInt8 u8Interface = 0xff; + irc = (*ppIfI)->GetInterfaceNumber(ppIfI, &u8Interface); + UInt8 u8AltSetting = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetAlternateSetting(ppIfI, &u8AltSetting); + UInt8 u8Class = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceClass(ppIfI, &u8Class); + UInt8 u8Protocol = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceProtocol(ppIfI, &u8Protocol); + UInt8 cEndpoints = 0; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetNumEndpoints(ppIfI, &cEndpoints); + if (irc == kIOReturnSuccess) + { + /* + * Try seize the interface. + */ + irc = (*ppIfI)->USBInterfaceOpenSeize(ppIfI); + if (irc == kIOReturnSuccess) + { + PUSBPROXYIFOSX pIf = (PUSBPROXYIFOSX)RTMemAllocZ(sizeof(*pIf)); + if (pIf) + { + /* + * Create the per-interface entry and query the + * endpoint data. + */ + /* initialize the entry */ + pIf->u8Interface = u8Interface; + pIf->u8AltSetting = u8AltSetting; + pIf->u8Class = u8Class; + pIf->u8Protocol = u8Protocol; + pIf->cPipes = cEndpoints; + pIf->ppIfI = ppIfI; + + /* query pipe/endpoint properties. */ + rc = usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + if (RT_SUCCESS(rc)) + { + /* + * Create the async event source and add it to the + * default current run loop. + * (Later: Add to the worker thread run loop instead.) + */ + irc = (*ppIfI)->CreateInterfaceAsyncEventSource(ppIfI, &pIf->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, + pIf->RunLoopSrcRef); + + /* + * Just link the interface into the list and we're good. + */ + pIf->pNext = NULL; + Log(("USB: Seized interface %#x (alt=%d prot=%#x class=%#x)\n", + u8Interface, u8AltSetting, u8Protocol, u8Class)); + if (pDevOsX->pIfTail) + pDevOsX->pIfTail = pDevOsX->pIfTail->pNext = pIf; + else + pDevOsX->pIfTail = pDevOsX->pIfHead = pIf; + continue; + } + rc = RTErrConvertFromDarwin(irc); + } + + /* failure cleanup. */ + RTMemFree(pIf); + } + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Interface %#x on device '%s' is being used by another process. (prot=%#x class=%#x)\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class)); + rc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open interface %#x on device '%s'. (prot=%#x class=%#x) krc=%#x\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class, irc)); + rc = VERR_OPEN_FAILED; + } + } + else + { + rc = RTErrConvertFromDarwin(irc); + LogRel(("USB: Failed to query interface properties on device '%s', irc=%#x.\n", + pProxyDev->pUsbIns->pszName, irc)); + } + (*ppIfI)->Release(ppIfI); + ppIfI = NULL; + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwinCOM(hrc); + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwin(krc); + if (!fMakeTheBestOfIt) + { + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + break; + } + } /* iterate */ + IOObjectRelease(Interfaces); + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + RTCritSectLeave(&pDevOsX->CritSect); + return rc; +} + + +/** + * Find a particular interface. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Interface The interface number. + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterface(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Interface) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + if (pIf->u8Interface == u8Interface) + return pIf; + +/* AssertMsgFailed(("Cannot find If#=%d\n", u8Interface)); - the 3rd quickcam interface is capture by the ****ing audio crap. */ + return NULL; +} + + +/** + * Find a particular endpoint. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Endpoint The endpoint. + * @param pu8PipeRef Where to store the darwin pipe ref. + * @param ppPipe Where to store the darwin pipe pointer. (optional) + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterfaceForEndpoint(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Endpoint, + uint8_t *pu8PipeRef, PPUSBPROXYPIPEOSX ppPipe) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + unsigned i = pIf->cPipes; + while (i-- > 0) + if (pIf->aPipes[i].u8Endpoint == u8Endpoint) + { + *pu8PipeRef = pIf->aPipes[i].u8PipeRef; + if (ppPipe) + *ppPipe = &pIf->aPipes[i]; + return pIf; + } + } + + AssertMsgFailed(("Cannot find EndPt=%#x\n", u8Endpoint)); + return NULL; +} + + +/** + * Gets an unsigned 32-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu32 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32)) + return true; + } + *pu32 = 0; + return false; +} + + +/** + * Gets an unsigned 64-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu64 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64)) + return true; + } + *pu64 = 0; + return false; +} + + +static DECLCALLBACK(void) usbProxyDarwinPerformWakeup(void *pInfo) +{ + RT_NOREF(pInfo); + return; +} + + +/* -=-=-=-=-=- The exported methods -=-=-=-=-=- */ + +/** + * Opens the USB Device. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The session id and/or location id of the device to open. + * The format of this string is something iokit.c in Main defines, currently + * it's sequences of "[l|s]=<value>" separated by ";". + */ +static DECLCALLBACK(int) usbProxyDarwinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlow(("usbProxyDarwinOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Init globals once. + */ + int vrc = RTOnce(&g_usbProxyDarwinOnce, usbProxyDarwinInitOnce, NULL); + AssertRCReturn(vrc, vrc); + + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + /* + * The idea here was to create a matching directory with the sessionID + * and locationID included, however this doesn't seem to work. So, we'll + * use the product id and vendor id to limit the set of matching device + * and manually match these two properties. sigh. + * (Btw. vendor and product id must be used *together* apparently.) + * + * Wonder if we could use the entry path? Docs indicates says we must + * use IOServiceGetMatchingServices and I'm not in a mood to explore + * this subject further right now. Maybe check this later. + */ + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName); + AssertReturn(RefMatchingDict != NULL, VERR_OPEN_FAILED); + + uint64_t u64SessionId = 0; + uint32_t u32LocationId = 0; + const char *psz = pszAddress; + do + { + const char chValue = *psz; + AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR); + uint64_t u64Value; + int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value); + AssertReleaseRCReturn(rc, rc); + AssertReleaseReturn(!*psz || *psz == ';', rc); + switch (chValue) + { + case 'l': + u32LocationId = (uint32_t)u64Value; + break; + case 's': + u64SessionId = u64Value; + break; + case 'p': + case 'v': + { +#if 0 /* Guess what, this doesn't 'ing work either! */ + SInt32 i32 = (int16_t)u64Value; + CFNumberRef Num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i32); + AssertBreak(Num); + CFDictionarySetValue(RefMatchingDict, chValue == 'p' ? CFSTR(kUSBProductID) : CFSTR(kUSBVendorID), Num); + CFRelease(Num); +#endif + break; + } + default: + AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR); + } + if (*psz == ';') + psz++; + } while (*psz); + + io_iterator_t USBDevices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%#x\n", irc), RTErrConvertFromDarwinIO(irc)); + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + unsigned cMatches = 0; + io_object_t USBDevice; + while ((USBDevice = IOIteratorNext(USBDevices))) + { + cMatches++; + CFMutableDictionaryRef PropsRef = 0; + kern_return_t krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + uint64_t u64CurSessionId; + uint32_t u32CurLocationId; + if ( ( !u64SessionId + || ( usbProxyDarwinDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId) + && u64CurSessionId == u64SessionId)) + && ( !u32LocationId + || ( usbProxyDarwinDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId) + && u32CurLocationId == u32LocationId)) + ) + { + CFRelease(PropsRef); + break; + } + CFRelease(PropsRef); + } + IOObjectRelease(USBDevice); + } + IOObjectRelease(USBDevices); + USBDevices = IO_OBJECT_NULL; + if (!USBDevice) + { + LogRel(("USB: Device '%s' not found (%d pid+vid matches)\n", pszAddress, cMatches)); + IOObjectRelease(USBDevices); + return VERR_VUSB_DEVICE_NAME_NOT_FOUND; + } + + /* + * Create a plugin interface for the device and query its IOUSBDeviceInterface. + */ + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Try open the device for exclusive access. + * If we fail, we'll try figure out who is using the device and + * convince them to let go of it... + */ + irc = (*ppDevI)->USBDeviceReEnumerate(ppDevI, kUSBReEnumerateCaptureDeviceMask); + Log(("USBDeviceReEnumerate (capture) returned irc=%#x\n", irc)); + + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + if (irc == kIOReturnExclusiveAccess) + { + RTThreadSleep(20); + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + } + if (irc == kIOReturnSuccess) + { + /* + * Init a proxy device instance. + */ + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + vrc = RTCritSectInit(&pDevOsX->CritSect); + if (RT_SUCCESS(vrc)) + { + pDevOsX->USBDevice = USBDevice; + pDevOsX->ppDevI = ppDevI; + pDevOsX->pProxyDev = pProxyDev; + pDevOsX->pTaxingHead = NULL; + pDevOsX->pTaxingTail = NULL; + pDevOsX->hRunLoopReapingLast = NULL; + + /* + * Try seize all the interface. + */ + char *pszDummyName = pProxyDev->pUsbIns->pszName; + pProxyDev->pUsbIns->pszName = (char *)pszAddress; + vrc = usbProxyDarwinSeizeAllInterfaces(pDevOsX, false /* give up on failure */); + pProxyDev->pUsbIns->pszName = pszDummyName; + if (RT_SUCCESS(vrc)) + { + /* + * Create the async event source and add it to the run loop. + */ + irc = (*ppDevI)->CreateDeviceAsyncEventSource(ppDevI, &pDevOsX->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + /* + * Determine the active configuration. + * Can cause hangs, so drop it for now. + */ + /** @todo test Palm. */ + //uint8_t u8Cfg; + //irc = (*ppDevI)->GetConfiguration(ppDevI, &u8Cfg); + if (irc != kIOReturnNoDevice) + { + CFRunLoopSourceContext CtxRunLoopSource; + CtxRunLoopSource.version = 0; + CtxRunLoopSource.info = NULL; + CtxRunLoopSource.retain = NULL; + CtxRunLoopSource.release = NULL; + CtxRunLoopSource.copyDescription = NULL; + CtxRunLoopSource.equal = NULL; + CtxRunLoopSource.hash = NULL; + CtxRunLoopSource.schedule = NULL; + CtxRunLoopSource.cancel = NULL; + CtxRunLoopSource.perform = usbProxyDarwinPerformWakeup; + pDevOsX->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource); + if (CFRunLoopSourceIsValid(pDevOsX->hRunLoopSrcWakeRef)) + { + //pProxyDev->iActiveCfg = irc == kIOReturnSuccess ? u8Cfg : -1; + RTListInit(&pDevOsX->HeadOfRunLoopWakeLst); + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 1; + + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + return VINF_SUCCESS; /* return */ + } + else + { + LogRel(("USB: Device '%s' out of memory allocating runloop source\n", pszAddress)); + vrc = VERR_NO_MEMORY; + } + } + vrc = VERR_VUSB_DEVICE_NOT_ATTACHED; + } + else + vrc = RTErrConvertFromDarwin(irc); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + } + /* else: already bitched */ + + RTCritSectDelete(&pDevOsX->CritSect); + } + + irc = (*ppDevI)->USBDeviceClose(ppDevI); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Device '%s' is being used by another process\n", pszAddress)); + vrc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open device '%s', irc=%#x.\n", pszAddress, irc)); + vrc = VERR_OPEN_FAILED; + } + } + else + { + LogRel(("USB: Failed to create plugin interface for device '%s', hrc=%#x.\n", pszAddress, hrc)); + vrc = VERR_OPEN_FAILED; + } + + (*ppDevI)->Release(ppDevI); + } + else + { + LogRel(("USB: Failed to open device '%s', plug-in creation failed with irc=%#x.\n", pszAddress, irc)); + vrc = RTErrConvertFromDarwin(irc); + } + + return vrc; +} + + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyDarwinClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyDarwinClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + AssertPtrReturnVoid(pDevOsX); + + /* + * Release interfaces we've laid claim to, then reset the device + * and finally close it. + */ + RTCritSectEnter(&pDevOsX->CritSect); + /* ?? */ + RTCritSectLeave(&pDevOsX->CritSect); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + + if (pDevOsX->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + + CFRelease(pDevOsX->RunLoopSrcRef); + pDevOsX->RunLoopSrcRef = NULL; + } + + if (pDevOsX->hRunLoopSrcWakeRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopWakeLst); + + CFRelease(pDevOsX->hRunLoopSrcWakeRef); + pDevOsX->hRunLoopSrcWakeRef = NULL; + } + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + + irc = (*pDevOsX->ppDevI)->USBDeviceClose(pDevOsX->ppDevI); + if (irc != kIOReturnSuccess && irc != kIOReturnNoDevice) + { + LogRel(("USB: USBDeviceClose -> %#x\n", irc)); + AssertMsgFailed(("irc=%#x\n", irc)); + } + + irc = (*pDevOsX->ppDevI)->USBDeviceReEnumerate(pDevOsX->ppDevI, kUSBReEnumerateReleaseDeviceMask); + Log(("USBDeviceReEnumerate (release) returned irc=%#x\n", irc)); + + (*pDevOsX->ppDevI)->Release(pDevOsX->ppDevI); + pDevOsX->ppDevI = NULL; + kern_return_t krc = IOObjectRelease(pDevOsX->USBDevice); Assert(krc == KERN_SUCCESS); NOREF(krc); + pDevOsX->USBDevice = IO_OBJECT_NULL; + pDevOsX->pProxyDev = NULL; + + /* + * Free all the resources. + */ + RTCritSectDelete(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX; + while ((pUrbOsX = pDevOsX->pFreeHead) != NULL) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTMemFree(pUrbOsX); + } + + LogFlow(("usbProxyDarwinClose: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset}*/ +static DECLCALLBACK(int) usbProxyDarwinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinReset: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + int rc; + if (irc == kIOReturnSuccess) + { + /** @todo Some docs say that some drivers will do a default config, check this out ... */ + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + + rc = VINF_SUCCESS; + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("irc=%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + LogFlow(("usbProxyDarwinReset: returns success %Rrc\n", rc)); + return rc; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyDarwinSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinSetConfig: pProxyDev=%s cfg=%#x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + IOReturn irc = (*pDevOsX->ppDevI)->SetConfiguration(pDevOsX->ppDevI, (uint8_t)iCfg); + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinSetConfig: Set configuration -> %#x\n", irc)); + return RTErrConvertFromDarwin(irc); + } + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + return VINF_SUCCESS; +} + + +/** + * Claims an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator (always VINF_SUCCESS). + */ +static DECLCALLBACK(int) usbProxyDarwinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + IOReturn irc = kIOReturnSuccess; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterface(pDevOsX, iIf); + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s iIf=%#x iAlt=%#x iCurAlt=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf ? pIf->u8AltSetting : 0xbeef)); + if (pIf) + { + /* Avoid SetAlternateInterface when possible as it will recreate the pipes. */ + if (iAlt != pIf->u8AltSetting) + { + irc = (*pIf->ppIfI)->SetAlternateInterface(pIf->ppIfI, iAlt); + if (irc == kIOReturnSuccess) + { + usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + return VINF_SUCCESS; + } + } + else + { + /* + * Just send the request anyway? + */ + IOUSBDevRequest Req; + Req.bmRequestType = 0x01; + Req.bRequest = 0x0b; /* SET_INTERFACE */ + Req.wIndex = iIf; + Req.wValue = iAlt; + Req.wLength = 0; + Req.wLenDone = 0; + Req.pData = NULL; + irc = (*pDevOsX->ppDevI)->DeviceRequest(pDevOsX->ppDevI, &Req); + Log(("usbProxyDarwinSetInterface: SET_INTERFACE(%d,%d) -> irc=%#x\n", iIf, iAlt, irc)); + return VINF_SUCCESS; + } + } + + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s eiIf=%#x iAlt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxyDarwinClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x\n", pProxyDev->pUsbIns->pszName, EndPt)); + + /* + * Clearing the zero control pipe doesn't make sense and isn't + * supported by the API. Just ignore it. + */ + if (EndPt == 0) + return VINF_SUCCESS; + + /* + * Find the interface/pipe combination and invoke the ClearPipeStallBothEnds + * method. (The ResetPipe and ClearPipeStall methods will not send the + * CLEAR_FEATURE(ENDPOINT_HALT) request that this method implements.) + */ + IOReturn irc = kIOReturnSuccess; + uint8_t u8PipeRef; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + { + irc = (*pIf->ppIfI)->ClearPipeStallBothEnds(pIf->ppIfI, u8PipeRef); + if (irc == kIOReturnSuccess) + return VINF_SUCCESS; + AssertMsg(irc == kIOReturnNoDevice || irc == kIOReturnNotResponding, ("irc=#x (control pipe?)\n", irc)); + } + + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, EndPt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyDarwinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Find the target interface / pipe. + */ + uint8_t u8PipeRef = 0xff; + PUSBPROXYIFOSX pIf = NULL; + PUSBPROXYPIPEOSX pPipe = NULL; + if (pUrb->EndPt) + { + /* Make sure the interface is there. */ + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, &pPipe); + if (!pIf) + { + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - can't find interface / pipe!!!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData)); + return VERR_NOT_FOUND; + } + } + /* else: pIf == NULL -> default control pipe.*/ + + /* + * Allocate a Darwin urb. + */ + PUSBPROXYURBOSX pUrbOsX = usbProxyDarwinUrbAlloc(pDevOsX); + if (!pUrbOsX) + return VERR_NO_MEMORY; + + pUrbOsX->u64SubmitTS = RTTimeMilliTS(); + pUrbOsX->pVUsbUrb = pUrb; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = pUrb->enmType; + + /* + * Submit the request. + */ + IOReturn irc = kIOReturnError; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + AssertMsgBreak(pUrb->cbData >= sizeof(VUSBSETUP), ("cbData=%d\n", pUrb->cbData)); + PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0]; + pUrbOsX->u.ControlMsg.bmRequestType = pSetup->bmRequestType; + pUrbOsX->u.ControlMsg.bRequest = pSetup->bRequest; + pUrbOsX->u.ControlMsg.wValue = pSetup->wValue; + pUrbOsX->u.ControlMsg.wIndex = pSetup->wIndex; + pUrbOsX->u.ControlMsg.wLength = pSetup->wLength; + pUrbOsX->u.ControlMsg.pData = pSetup + 1; + pUrbOsX->u.ControlMsg.wLenDone = pSetup->wLength; + + if (pIf) + irc = (*pIf->ppIfI)->ControlRequestAsync(pIf->ppIfI, u8PipeRef, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pDevOsX->ppDevI)->DeviceRequestAsync(pDevOsX->ppDevI, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + break; + } + + case VUSBXFERTYPE_BULK: + case VUSBXFERTYPE_INTR: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WritePipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadPipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + + break; + } + + case VUSBXFERTYPE_ISOC: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + +#ifdef USE_LOW_LATENCY_API + /* Allocate an isochronous buffer and copy over the data. */ + AssertBreak(pUrb->cbData <= 8192); + int rc = usbProxyDarwinUrbAllocIsocBuf(pUrbOsX, pIf); + AssertRCBreak(rc); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + memcpy(pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->abData, pUrb->cbData); + else + memset(pUrbOsX->u.Isoc.pBuf->pvBuf, 0xfe, pUrb->cbData); +#endif + + /* Get the current frame number (+2) and make sure it doesn't + overlap with the previous request. See WARNING in + ApplUSBUHCI::CreateIsochTransfer for details on the +2. */ + UInt64 FrameNo; + AbsoluteTime FrameTime; + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + AssertMsg(irc == kIOReturnSuccess, ("GetBusFrameNumber -> %#x\n", irc)); + FrameNo += 2; + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + + for (unsigned j = 0; ; j++) + { + unsigned i; + for (i = 0; i < pUrb->cIsocPkts; i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = pUrb->aIsocPkts[i].cb; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOUSBNotSent1Err; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + for (; i < RT_ELEMENTS(pUrbOsX->u.Isoc.aFrames); i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOReturnError; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + +#ifdef USE_LOW_LATENCY_API + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->LowLatencyWriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->LowLatencyReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#else + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#endif + if ( irc != kIOReturnIsoTooOld + || j >= 5) + { + Log(("%s: usbProxyDarwinUrbQueue: isoc: u64NextFrameNo=%RX64 FrameNo=%RX64 #Frames=%d j=%d (pipe=%d)\n", + pUrb->pszDesc, pPipe->u64NextFrameNo, FrameNo, pUrb->cIsocPkts, j, u8PipeRef)); + if (irc == kIOReturnSuccess) + { + if (pPipe->fIsFullSpeed) + pPipe->u64NextFrameNo = FrameNo + pUrb->cIsocPkts; + else + pPipe->u64NextFrameNo = FrameNo + 1; + } + break; + } + + /* try again... */ + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + FrameNo += j; + } + break; + } + + default: + AssertMsgFailed(("%s: enmType=%#x\n", pUrb->pszDesc, pUrb->enmType)); + break; + } + + /* + * Success? + */ + if (RT_LIKELY(irc == kIOReturnSuccess)) + { + Log(("%s: usbProxyDarwinUrbQueue: success\n", pUrb->pszDesc)); + return VINF_SUCCESS; + } + switch (irc) + { + case kIOUSBPipeStalled: + { + /* Increment in flight counter because the completion handler will decrease it always. */ + usbProxyDarwinUrbAsyncComplete(pUrbOsX, kIOUSBPipeStalled, 0); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x! (stall)\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return VINF_SUCCESS; + } + } + + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyDarwinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PVUSBURB pUrb = NULL; + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + CFRunLoopRef hRunLoopRef = CFRunLoopGetCurrent(); + + Assert(!pDevOsX->hRunLoopReaping); + + /* + * If the last seen runloop for reaping differs we have to check whether the + * the runloop sources are in the new runloop. + */ + if (pDevOsX->hRunLoopReapingLast != hRunLoopRef) + { + RTCritSectEnter(&pDevOsX->CritSect); + + /* Every pipe. */ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + if (!CFRunLoopContainsSource(hRunLoopRef, pIf->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + } + + /* Default control pipe. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + + /* Runloop wakeup source. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->hRunLoopSrcWakeRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + RTCritSectLeave(&pDevOsX->CritSect); + + pDevOsX->hRunLoopReapingLast = hRunLoopRef; + } + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, hRunLoopRef); + + if (ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false)) + { + /* Return immediately. */ + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + return NULL; + } + + /* + * Excercise the runloop until we get an URB or we time out. + */ + if ( !pDevOsX->pTaxingHead + && cMillies) + CFRunLoopRunInMode(g_pRunLoopMode, cMillies / 1000.0, true); + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false); + + /* + * Any URBs pending delivery? + */ + while ( pDevOsX->pTaxingHead + && !pUrb) + { + RTCritSectEnter(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX = pDevOsX->pTaxingHead; + if (pUrbOsX) + { + /* + * Remove from the taxing list. + */ + if (pUrbOsX->pNext) + pUrbOsX->pNext->pPrev = pUrbOsX->pPrev; + else if (pDevOsX->pTaxingTail == pUrbOsX) + pDevOsX->pTaxingTail = pUrbOsX->pPrev; + + if (pUrbOsX->pPrev) + pUrbOsX->pPrev->pNext = pUrbOsX->pNext; + else if (pDevOsX->pTaxingHead == pUrbOsX) + pDevOsX->pTaxingHead = pUrbOsX->pNext; + else + AssertFailed(); + + pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + pUrb->Dev.pvPrivate = NULL; + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + } + } + RTCritSectLeave(&pDevOsX->CritSect); + } + + if (pUrb) + LogFlowFunc(("LEAVE: %s: pProxyDev=%s returns %p\n", pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb)); + else + LogFlowFunc(("LEAVE: NULL pProxyDev=%s returns NULL\n", pProxyDev->pUsbIns->pszName)); + + return pUrb; +} + + +/** + * Cancels a URB. + * + * The URB requires reaping, so we don't change its state. + * + * @remark There isn't any way to cancel a specific async request + * on darwin. The interface only supports the aborting of + * all URBs pending on an interface / pipe pair. Provided + * the card does the URB cancelling before submitting new + * requests, we should probably be fine... + */ +static DECLCALLBACK(int) usbProxyDarwinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbCancel: pProxyDev=%s EndPt=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt)); + + /* + * Determine the interface / endpoint ref and invoke AbortPipe. + */ + IOReturn irc = kIOReturnSuccess; + if (!pUrb->EndPt) + irc = (*pDevOsX->ppDevI)->USBDeviceAbortPipeZero(pDevOsX->ppDevI); + else + { + uint8_t u8PipeRef; + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + irc = (*pIf->ppIfI)->AbortPipe(pIf->ppIfI, u8PipeRef); + else /* this may happen if a device reset, set configuration or set interface has been performed. */ + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d - cannot find the interface / pipe!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt)); + } + + int rc = VINF_SUCCESS; + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d -> %#x!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, irc)); + rc = RTErrConvertFromDarwin(irc); + } + + return rc; +} + + +static DECLCALLBACK(int) usbProxyDarwinWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + LogFlow(("usbProxyDarwinWakeup: pProxyDev=%p\n", pProxyDev)); + + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, true); + usbProxyDarwinReaperKick(pDevOsX); + return VINF_SUCCESS; +} + + +/** + * The Darwin USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVOSX), + usbProxyDarwinOpen, + NULL, + usbProxyDarwinClose, + usbProxyDarwinReset, + usbProxyDarwinSetConfig, + usbProxyDarwinClaimInterface, + usbProxyDarwinReleaseInterface, + usbProxyDarwinSetInterface, + usbProxyDarwinClearHaltedEp, + usbProxyDarwinUrbQueue, + usbProxyDarwinUrbCancel, + usbProxyDarwinUrbReap, + usbProxyDarwinWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/freebsd/Makefile.kup b/src/VBox/Devices/USB/freebsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/freebsd/Makefile.kup diff --git a/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp new file mode 100644 index 00000000..8b694639 --- /dev/null +++ b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp @@ -0,0 +1,1068 @@ +/* $Id: USBProxyDevice-freebsd.cpp $ */ +/** @file + * USB device proxy - the FreeBSD backend. + */ + +/* + * Includes contributions from Hans Petter Selasky + * + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#ifdef VBOX +# include <iprt/stdint.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usb_ioctl.h> + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/vusb.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/file.h> +#include <iprt/pipe.h> +#include "../USBProxyDevice.h" + +/** Maximum endpoints supported. */ +#define USBFBSD_MAXENDPOINTS 127 +#define USBFBSD_MAXFRAMES 56 + +/** This really needs to be defined in vusb.h! */ +#ifndef VUSB_DIR_TO_DEV +# define VUSB_DIR_TO_DEV 0x00 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct USBENDPOINTFBSD +{ + /** Flag whether it is opened. */ + bool fOpen; + /** Flag whether it is cancelling. */ + bool fCancelling; + /** Buffer pointers. */ + void *apvData[USBFBSD_MAXFRAMES]; + /** Buffer lengths. */ + uint32_t acbData[USBFBSD_MAXFRAMES]; + /** Initial buffer length. */ + uint32_t cbData0; + /** Pointer to the URB. */ + PVUSBURB pUrb; + /** Copy of endpoint number. */ + unsigned iEpNum; + /** Maximum transfer length. */ + unsigned cMaxIo; + /** Maximum frame count. */ + unsigned cMaxFrames; +} USBENDPOINTFBSD, *PUSBENDPOINTFBSD; + +/** + * Data for the FreeBSD usb proxy backend. + */ +typedef struct USBPROXYDEVFBSD +{ + /** The open file. */ + RTFILE hFile; + /** Flag whether an URB is cancelling. */ + bool fCancelling; + /** Flag whether initialised or not */ + bool fInit; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; + /** Software endpoint structures */ + USBENDPOINTFBSD aSwEndpoint[USBFBSD_MAXENDPOINTS]; + /** Kernel endpoint structures */ + struct usb_fs_endpoint aHwEndpoint[USBFBSD_MAXENDPOINTS]; +} USBPROXYDEVFBSD, *PUSBPROXYDEVFBSD; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint); + +/** + * Wrapper for the ioctl call. + * + * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also + * handle ENODEV (detached device) errors. + * + * @returns whatever ioctl returns. + * @param pProxyDev The proxy device. + * @param iCmd The ioctl command / function. + * @param pvArg The ioctl argument / data. + * @param fHandleNoDev Whether to handle ENXIO. + * @internal + */ +static int usbProxyFreeBSDDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, + void *pvArg, bool fHandleNoDev) +{ + int rc = VINF_SUCCESS; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDDoIoCtl: iCmd=%#x\n", iCmd)); + + do + { + rc = ioctl(RTFileToNative(pDevFBSD->hFile), iCmd, pvArg); + if (rc >= 0) + return VINF_SUCCESS; + } while (errno == EINTR); + + if (errno == ENXIO && fHandleNoDev) + { + Log(("usbProxyFreeBSDDoIoCtl: ENXIO -> unplugged. pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + errno = ENODEV; + } + else if (errno != EAGAIN) + { + LogFlow(("usbProxyFreeBSDDoIoCtl: Returned %d. pProxyDev=%s\n", + errno, pProxyDev->pUsbIns->pszName)); + } + return RTErrConvertFromErrno(errno); +} + +/** + * Init USB subsystem. + */ +static int usbProxyFreeBSDFsInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_init UsbFsInit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsInit: pProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit == true) + return VINF_SUCCESS; + + /* Zero default */ + memset(&UsbFsInit, 0, sizeof(UsbFsInit)); + + UsbFsInit.pEndpoints = pDevFBSD->aHwEndpoint; + UsbFsInit.ep_index_max = USBFBSD_MAXENDPOINTS; + + /* Init USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_INIT, &UsbFsInit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = true; + + return rc; +} + +/** + * Uninit USB subsystem. + */ +static int usbProxyFreeBSDFsUnInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_uninit UsbFsUninit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsUnInit: ProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit != true) + return VINF_SUCCESS; + + /* Close any open endpoints. */ + for (unsigned n = 0; n != USBFBSD_MAXENDPOINTS; n++) + usbProxyFreeBSDEndpointClose(pProxyDev, n); + + /* Zero default */ + memset(&UsbFsUninit, 0, sizeof(UsbFsUninit)); + + /* Uninit USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_UNINIT, &UsbFsUninit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = false; + + return rc; +} + +/** + * Setup a USB request packet. + */ +static void usbProxyFreeBSDSetupReq(struct usb_device_request *pSetupData, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, + uint16_t wIndex, uint16_t wLength) +{ + LogFlow(("usbProxyFreeBSDSetupReq: pSetupData=%p bmRequestType=%x " + "bRequest=%x wValue=%x wIndex=%x wLength=%x\n", (void *)pSetupData, + bmRequestType, bRequest, wValue, wIndex, wLength)); + + pSetupData->bmRequestType = bmRequestType; + pSetupData->bRequest = bRequest; + + /* Handle endianess here. Currently no swapping is needed. */ + pSetupData->wValue[0] = wValue & 0xff; + pSetupData->wValue[1] = (wValue >> 8) & 0xff; + pSetupData->wIndex[0] = wIndex & 0xff; + pSetupData->wIndex[1] = (wIndex >> 8) & 0xff; + pSetupData->wLength[0] = wLength & 0xff; + pSetupData->wLength[1] = (wLength >> 8) & 0xff; +} + +static int usbProxyFreeBSDEndpointOpen(PUSBPROXYDEV pProxyDev, int Endpoint, bool fIsoc, int index) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = NULL; /* shut up gcc */ + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_open UsbFsOpen; + int rc; + + LogFlow(("usbProxyFreeBSDEndpointOpen: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + for (; index < USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if ( pEndpointFBSD->fOpen + && !pEndpointFBSD->pUrb + && (int)pEndpointFBSD->iEpNum == Endpoint) + return index; + } + + if (index == USBFBSD_MAXENDPOINTS) + { + for (index = 0; index != USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if (!pEndpointFBSD->fOpen) + break; + } + if (index == USBFBSD_MAXENDPOINTS) + return -1; + } + /* set ppBuffer and pLength */ + + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + pXferEndpoint->ppBuffer = &pEndpointFBSD->apvData[0]; + pXferEndpoint->pLength = &pEndpointFBSD->acbData[0]; + + LogFlow(("usbProxyFreeBSDEndpointOpen: ep_index=%d ep_num=%d\n", + index, Endpoint)); + + memset(&UsbFsOpen, 0, sizeof(UsbFsOpen)); + + UsbFsOpen.ep_index = index; + UsbFsOpen.ep_no = Endpoint; + UsbFsOpen.max_bufsize = 256 * 1024; + /* Hardcoded assumption about the URBs we get. */ + + UsbFsOpen.max_frames = fIsoc ? USBFBSD_MAXFRAMES : 2; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_OPEN, &UsbFsOpen, true); + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + LogFlow(("usbProxyFreeBSDEndpointOpen: EBUSY\n")); + + return -1; + } + pEndpointFBSD->fOpen = true; + pEndpointFBSD->pUrb = NULL; + pEndpointFBSD->iEpNum = Endpoint; + pEndpointFBSD->cMaxIo = UsbFsOpen.max_bufsize; + pEndpointFBSD->cMaxFrames = UsbFsOpen.max_frames; + + return index; +} + +/** + * Close an endpoint. + * + * @returns VBox status code. + */ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = &pDevFBSD->aSwEndpoint[Endpoint]; + struct usb_fs_close UsbFsClose; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDEndpointClose: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + /* check for cancelling */ + if (pEndpointFBSD->pUrb != NULL) + { + pEndpointFBSD->fCancelling = true; + pDevFBSD->fCancelling = true; + } + + /* check for opened */ + if (pEndpointFBSD->fOpen) + { + pEndpointFBSD->fOpen = false; + + /* Zero default */ + memset(&UsbFsClose, 0, sizeof(UsbFsClose)); + + /* Set endpoint index */ + UsbFsClose.ep_index = Endpoint; + + /* Close endpoint */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_CLOSE, &UsbFsClose, true); + } + return rc; +} + +/** + * Opens the device file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress If we are using usbfs, this is the path to the + * device. If we are using sysfs, this is a string of + * the form "sysfs:<sysfs path>//device:<device node>". + * In the second case, the two paths are guaranteed + * not to contain the substring "//". + */ +static DECLCALLBACK(int) usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Try open the device node. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszAddress, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the FreeBSD backend data. + */ + pDevFBSD->hFile = hFile; + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_SUCCESS(rc)) + { + /* + * Create wakeup pipe. + */ + rc = RTPipeCreate(&pDevFBSD->hPipeWakeupR, &pDevFBSD->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyFreeBSDOpen(%p, %s): returns successfully hFile=%RTfile iActiveCfg=%d\n", + pProxyDev, pszAddress, pDevFBSD->hFile, pProxyDev->iActiveCfg)); + + return VINF_SUCCESS; + } + } + + RTFileClose(hFile); + } + else if (rc == VERR_ACCESS_DENIED) + rc = VERR_VUSB_USBFS_PERMISSION; + + Log(("usbProxyFreeBSDOpen(%p, %s) failed, rc=%d!\n", + pProxyDev, pszAddress, rc)); + + return rc; +} + + +/** + * Claims all the interfaces and figures out the + * current configuration. + * + * @returns VINF_SUCCESS. + * @param pProxyDev The proxy device. + */ +static DECLCALLBACK(int) usbProxyFreeBSDInit(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDInit: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_GET_CONFIG, + &pProxyDev->iActiveCfg, true); + if (RT_FAILURE(rc) || pProxyDev->iActiveCfg == 255) + { + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + } + else + { + pProxyDev->cIgnoreSetConfigs = 1; + pProxyDev->iActiveCfg++; + } + + Log(("usbProxyFreeBSDInit: iActiveCfg=%d\n", pProxyDev->iActiveCfg)); + + return rc; +} + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyFreeBSDClose(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + /* sanity check */ + AssertPtrReturnVoid(pDevFBSD); + + usbProxyFreeBSDFsUnInit(pProxyDev); + + RTPipeClose(pDevFBSD->hPipeWakeupR); + RTPipeClose(pDevFBSD->hPipeWakeupW); + + RTFileClose(pDevFBSD->hFile); + pDevFBSD->hFile = NIL_RTFILE; + + LogFlow(("usbProxyFreeBSDClose: returns\n")); +} + +/** + * Reset a device. + * + * @returns VBox status code. + * @param pDev The device to reset. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReset(PUSBPROXYDEV pProxyDev, bool fResetOnFreeBSD) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iParm; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDReset: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + if (!fResetOnFreeBSD) + goto done; + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Resetting is only possible as super-user, ignore any failures: */ + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DEVICEENUMERATE, &iParm, true); + if (RT_FAILURE(rc)) + { + /* Set the config instead of bus reset */ + iParm = 255; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + if (RT_SUCCESS(rc)) + { + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + } + } + usleep(10000); /* nice it! */ + + /* Allocate kernel ressources again. */ + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDInit(pProxyDev); + +done: + pProxyDev->cIgnoreSetConfigs = 2; + + return rc; +} + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyFreeBSDSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iCfgIndex; + int rc; + + LogFlow(("usbProxyFreeBSDSetConfig: pProxyDev=%s cfg=%x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + + if (iCfg == 0) + { + /* Unconfigure */ + iCfgIndex = 255; + } + else + { + /* Get the configuration index matching the value. */ + for (iCfgIndex = 0; iCfgIndex < pProxyDev->DevDesc.bNumConfigurations; iCfgIndex++) + { + if (pProxyDev->paCfgDescs[iCfgIndex].Core.bConfigurationValue == iCfg) + break; + } + + if (iCfgIndex == pProxyDev->DevDesc.bNumConfigurations) + { + LogFlow(("usbProxyFreeBSDSetConfig: configuration " + "%d not found\n", iCfg)); + return VERR_NOT_FOUND; + } + } + + /* Set the config */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iCfgIndex, true); + if (RT_FAILURE(rc)) + return rc; + + /* Allocate kernel ressources again. */ + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Claims an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDClaimInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + /* + * Try to detach kernel driver on this interface, ignore any + * failures + */ + usbProxyFreeBSDDoIoCtl(pProxyDev, USB_IFACE_DRIVER_DETACH, &iIf, true); + + /* Try to claim interface */ + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_CLAIM_INTERFACE, &iIf, true); +} + +/** + * Releases an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDReleaseInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_RELEASE_INTERFACE, &iIf, true); +} + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + struct usb_alt_interface UsbIntAlt; + int rc; + + LogFlow(("usbProxyFreeBSDSetInterface: pProxyDev=%p iIf=%x iAlt=%x\n", + pProxyDev, iIf, iAlt)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + memset(&UsbIntAlt, 0, sizeof(UsbIntAlt)); + UsbIntAlt.uai_interface_index = iIf; + UsbIntAlt.uai_alt_index = iAlt; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_ALTINTERFACE, &UsbIntAlt, true); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Setting interface %d %d " + "failed rc=%d\n", iIf, iAlt, rc)); + return rc; + } + + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Clears the halted endpoint 'ep_num'. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep_num) +{ + LogFlow(("usbProxyFreeBSDClearHaltedEp: pProxyDev=%s ep_num=%u\n", + pProxyDev->pUsbIns->pszName, ep_num)); + + /* + * Clearing the zero control pipe doesn't make sense. + * Just ignore it. + */ + if ((ep_num & 0xF) == 0) + return VINF_SUCCESS; + + struct usb_ctl_request Req; + memset(&Req, 0, sizeof(Req)); + usbProxyFreeBSDSetupReq(&Req.ucr_request, + VUSB_DIR_TO_DEV | VUSB_TO_ENDPOINT, + VUSB_REQ_CLEAR_FEATURE, 0, ep_num, 0); + + int rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DO_REQUEST, &Req, true); + + LogFlow(("usbProxyFreeBSDClearHaltedEp: rc=%Rrc\n", rc)); + return rc; +} + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_start UsbFsStart; + unsigned cFrames; + uint8_t *pbData; + int index; + int ep_num; + int rc; + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb=%p EndPt=%u Dir=%u\n", + pUrb, (unsigned)pUrb->EndPt, (unsigned)pUrb->enmDir)); + + ep_num = pUrb->EndPt; + if ((pUrb->enmType != VUSBXFERTYPE_MSG) && (pUrb->enmDir == VUSBDIRECTION_IN)) { + /* set IN-direction bit */ + ep_num |= 0x80; + } + + index = 0; + +retry: + + index = usbProxyFreeBSDEndpointOpen(pProxyDev, ep_num, + (pUrb->enmType == VUSBXFERTYPE_ISOC), + index); + + if (index < 0) + return VERR_INVALID_PARAMETER; + + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + + pbData = pUrb->abData; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->acbData[0] = 8; + + /* check wLength */ + if (pbData[6] || pbData[7]) + { + pEndpointFBSD->apvData[1] = pbData + 8; + pEndpointFBSD->acbData[1] = pbData[6] | (pbData[7] << 8); + cFrames = 2; + } + else + { + pEndpointFBSD->apvData[1] = NULL; + pEndpointFBSD->acbData[1] = 0; + cFrames = 1; + } + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb->cbData=%u, 0x%02x, " + "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + pUrb->cbData, pbData[0], pbData[1], pbData[2], pbData[3], + pbData[4], pbData[5], pbData[6], pbData[7])); + + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + break; + } + case VUSBXFERTYPE_ISOC: + { + unsigned i; + + for (i = 0; i < pUrb->cIsocPkts; i++) + { + if (i >= pEndpointFBSD->cMaxFrames) + break; + pEndpointFBSD->apvData[i] = pbData + pUrb->aIsocPkts[i].off; + pEndpointFBSD->acbData[i] = pUrb->aIsocPkts[i].cb; + } + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = i; + break; + } + default: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->cbData0 = pUrb->cbData; + + /* XXX maybe we have to loop */ + if (pUrb->cbData > pEndpointFBSD->cMaxIo) + pEndpointFBSD->acbData[0] = pEndpointFBSD->cMaxIo; + else + pEndpointFBSD->acbData[0] = pUrb->cbData; + + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = pUrb->fShortNotOk ? 0 : USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = 1; + break; + } + } + + /* store number of frames */ + pXferEndpoint->nFrames = cFrames; + + /* zero-default */ + memset(&UsbFsStart, 0, sizeof(UsbFsStart)); + + /* Start the transfer */ + UsbFsStart.ep_index = index; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_START, &UsbFsStart, true); + + LogFlow(("usbProxyFreeBSDUrbQueue: USB_FS_START returned rc=%d " + "len[0]=%u len[1]=%u cbData=%u index=%u ep_num=%u\n", rc, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1], + (unsigned)pUrb->cbData, + (unsigned)index, (unsigned)ep_num)); + + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + { + index++; + goto retry; + } + return rc; + } + pUrb->Dev.pvPrivate = (void *)(long)(index + 1); + pEndpointFBSD->pUrb = pUrb; + + return rc; +} + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyFreeBSDUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + struct usb_fs_endpoint *pXferEndpoint; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + PVUSBURB pUrb; + struct usb_fs_complete UsbFsComplete; + struct pollfd pfd[2]; + int rc; + + LogFlow(("usbProxyFreeBSDUrbReap: pProxyDev=%p, cMillies=%u\n", + pProxyDev, cMillies)); + +repeat: + + pUrb = NULL; + + /* check for cancelled transfers */ + if (pDevFBSD->fCancelling) + { + for (unsigned n = 0; n < USBFBSD_MAXENDPOINTS; n++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[n]; + if (pEndpointFBSD->fCancelling) + { + pEndpointFBSD->fCancelling = false; + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + + if (pUrb != NULL) + break; + } + } + + if (pUrb != NULL) + { + pUrb->enmStatus = VUSBSTATUS_INVALID; + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = 0; + break; + case VUSBXFERTYPE_ISOC: + pUrb->cbData = 0; + for (int n = 0; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + break; + default: + pUrb->cbData = 0; + break; + } + return pUrb; + } + pDevFBSD->fCancelling = false; + } + /* Zero default */ + + memset(&UsbFsComplete, 0, sizeof(UsbFsComplete)); + + /* Check if any endpoints are complete */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_COMPLETE, &UsbFsComplete, true); + if (RT_SUCCESS(rc)) + { + pXferEndpoint = &pDevFBSD->aHwEndpoint[UsbFsComplete.ep_index]; + pEndpointFBSD = &pDevFBSD->aSwEndpoint[UsbFsComplete.ep_index]; + + LogFlow(("usbProxyFreeBSDUrbReap: Reaped " + "URB %#p\n", pEndpointFBSD->pUrb)); + + if (pXferEndpoint->status == USB_ERR_CANCELLED) + goto repeat; + + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + if (pUrb == NULL) + goto repeat; + + switch (pXferEndpoint->status) + { + case USB_ERR_NORMAL_COMPLETION: + pUrb->enmStatus = VUSBSTATUS_OK; + break; + case USB_ERR_STALLED: + pUrb->enmStatus = VUSBSTATUS_STALL; + break; + default: + pUrb->enmStatus = VUSBSTATUS_INVALID; + break; + } + + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = pEndpointFBSD->acbData[0] + pEndpointFBSD->acbData[1]; + break; + case VUSBXFERTYPE_ISOC: + { + int n; + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + break; + pUrb->cbData = 0; + for (n = 0; n < (int)pUrb->cIsocPkts; n++) + { + if (n >= (int)pEndpointFBSD->cMaxFrames) + break; + pUrb->cbData += pEndpointFBSD->acbData[n]; + pUrb->aIsocPkts[n].cb = pEndpointFBSD->acbData[n]; + } + for (; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + + break; + } + default: + pUrb->cbData = pEndpointFBSD->acbData[0]; + break; + } + + LogFlow(("usbProxyFreeBSDUrbReap: Status=%d epindex=%u " + "len[0]=%d len[1]=%d\n", + (int)pXferEndpoint->status, + (unsigned)UsbFsComplete.ep_index, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1])); + + } + else if (cMillies != 0 && rc == VERR_RESOURCE_BUSY) + { + for (;;) + { + pfd[0].fd = RTFileToNative(pDevFBSD->hFile); + pfd[0].events = POLLIN | POLLRDNORM; + pfd[0].revents = 0; + + pfd[1].fd = RTPipeToNative(pDevFBSD->hPipeWakeupR); + pfd[1].events = POLLIN | POLLRDNORM; + pfd[1].revents = 0; + + rc = poll(pfd, 2, (cMillies == RT_INDEFINITE_WAIT) ? INFTIM : cMillies); + if (rc > 0) + { + if (pfd[1].revents & POLLIN) + { + /* Got woken up, drain pipe. */ + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevFBSD->hPipeWakeupR, &bRead, 1, &cbIgnored); + /* Make sure we return from this function */ + cMillies = 0; + } + break; + } + if (rc == 0) + return NULL; + if (errno != EAGAIN) + return NULL; + } + goto repeat; + } + return pUrb; +} + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int index; + + index = (int)(long)pUrb->Dev.pvPrivate - 1; + + if (index < 0 || index >= USBFBSD_MAXENDPOINTS) + return VINF_SUCCESS; /* invalid index, pretend success. */ + + LogFlow(("usbProxyFreeBSDUrbCancel: epindex=%u\n", (unsigned)index)); + return usbProxyFreeBSDEndpointClose(pProxyDev, index); +} + +static DECLCALLBACK(int) usbProxyFreeBSDWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevFBSD->hPipeWakeupW, "", 1, &cbIgnored); +} + +/** + * The FreeBSD USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVFBSD), + usbProxyFreeBSDOpen, + usbProxyFreeBSDInit, + usbProxyFreeBSDClose, + usbProxyFreeBSDReset, + usbProxyFreeBSDSetConfig, + usbProxyFreeBSDClaimInterface, + usbProxyFreeBSDReleaseInterface, + usbProxyFreeBSDSetInterface, + usbProxyFreeBSDClearHaltedEp, + usbProxyFreeBSDUrbQueue, + usbProxyFreeBSDUrbCancel, + usbProxyFreeBSDUrbReap, + usbProxyFreeBSDWakeup, + 0 +}; + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/linux/Makefile.kup b/src/VBox/Devices/USB/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/linux/Makefile.kup diff --git a/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp new file mode 100644 index 00000000..07a818f3 --- /dev/null +++ b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp @@ -0,0 +1,1708 @@ +/* $Id: USBProxyDevice-linux.cpp $ */ +/** @file + * USB device proxy - the Linux backend. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <iprt/stdint.h> +#include <iprt/err.h> +#include <iprt/pipe.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#ifdef VBOX_WITH_LINUX_COMPILER_H +# include <linux/compiler.h> +#endif +#include <linux/usbdevice_fs.h> + +#ifndef RDESKTOP +# include <VBox/vmm/pdm.h> +#else +# define RTCRITSECT void * +static inline int rtcsNoop() { return VINF_SUCCESS; } +static inline bool rtcsTrue() { return true; } +# define RTCritSectInit(a) rtcsNoop() +# define RTCritSectDelete(a) rtcsNoop() +# define RTCritSectEnter(a) rtcsNoop() +# define RTCritSectLeave(a) rtcsNoop() +# define RTCritSectIsOwner(a) rtcsTrue() +#endif +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/linux/sysfs.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/list.h> +#include <iprt/time.h> +#include "../USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Wrapper around the linux urb request structure. + * This is required to track in-flight and landed URBs. + */ +typedef struct USBPROXYURBLNX +{ + /** Node to link the URB in of the existing lists. */ + RTLISTNODE NodeList; + /** If we've split the VUSBURB up into multiple linux URBs, this is points to the head. */ + struct USBPROXYURBLNX *pSplitHead; + /** The next linux URB if split up. */ + struct USBPROXYURBLNX *pSplitNext; + /** Don't report these back. */ + bool fCanceledBySubmit; + /** This split element is reaped. */ + bool fSplitElementReaped; + /** This URB was discarded. */ + bool fDiscarded; + /** Size to transfer in remaining fragments of a split URB */ + uint32_t cbSplitRemaining; + +#if RT_GNUC_PREREQ(6, 0) /* gcc 6.2 complains about the [] member of KUrb */ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +#endif + /** The kernel URB data (variable size array included). */ + struct usbdevfs_urb KUrb; +#if RT_GNUC_PREREQ(6, 0) +# pragma GCC diagnostic pop +#endif +} USBPROXYURBLNX, *PUSBPROXYURBLNX; + +/** + * Data for the linux usb proxy backend. + */ +typedef struct USBPROXYDEVLNX +{ + /** The open file. */ + RTFILE hFile; + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** The list of free linux URBs (USBPROXYURBLNX). */ + RTLISTANCHOR ListFree; + /** The list of active linux URBs. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. (USBPROXYURBLNX) */ + RTLISTANCHOR ListInFlight; + /** Are we using sysfs to find the active configuration? */ + bool fUsingSysfs; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; + /** The device node/sysfs path of the device. + * Used to figure out the configuration after a reset. */ + char *pszPath; + /** Mask of claimed interfaces. */ + uint32_t fClaimedIfsMask; +} USBPROXYDEVLNX, *PUSBPROXYDEVLNX; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev); +static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf); +static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf); + + +/** + * Wrapper for the ioctl call. + * + * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also + * handle ENODEV (detached device) errors. + * + * @returns whatever ioctl returns. + * @param pProxyDev The proxy device. + * @param iCmd The ioctl command / function. + * @param pvArg The ioctl argument / data. + * @param fHandleNoDev Whether to handle ENODEV. + * @param cTries The number of retries. Use UINT32_MAX for (kind of) indefinite retries. + * @internal + */ +static int usbProxyLinuxDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, void *pvArg, bool fHandleNoDev, uint32_t cTries) +{ + int rc; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + do + { + do + { + rc = ioctl(RTFileToNative(pDevLnx->hFile), iCmd, pvArg); + if (rc >= 0) + return rc; + } while (errno == EINTR); + + if (errno == ENODEV && fHandleNoDev) + { + usbProxLinuxUrbUnplugged(pProxyDev); + Log(("usb-linux: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + errno = ENODEV; + break; + } + if (errno != EAGAIN) + break; + } while (cTries-- > 0); + + return rc; +} + + +/** + * The device has been unplugged. + * Cancel all in-flight URBs and put them up for reaping. + */ +static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Shoot down all flying URBs. + */ + RTCritSectEnter(&pDevLnx->CritSect); + pProxyDev->fDetached = true; + + PUSBPROXYURBLNX pUrbLnx; + PUSBPROXYURBLNX pUrbLnxNext; + RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + if (!pUrbLnx->fDiscarded) + { + pUrbLnx->fDiscarded = true; + /* Cancel the URB. It will be reaped normally. */ + ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_DISCARDURB, &pUrbLnx->KUrb); + if (!pUrbLnx->KUrb.status) + pUrbLnx->KUrb.status = -ENODEV; + } + } + + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * Set the connect state seen by kernel drivers + * @internal + */ +static void usbProxyLinuxSetConnected(PUSBPROXYDEV pProxyDev, int iIf, bool fConnect, bool fQuiet) +{ + if ( iIf >= 32 + || !(pProxyDev->fMaskedIfs & RT_BIT(iIf))) + { + struct usbdevfs_ioctl IoCtl; + if (!fQuiet) + LogFlow(("usbProxyLinuxSetConnected: pProxyDev=%s iIf=%#x fConnect=%s\n", + usbProxyGetName(pProxyDev), iIf, fConnect ? "true" : "false")); + + IoCtl.ifno = iIf; + IoCtl.ioctl_code = fConnect ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + IoCtl.data = NULL; + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_IOCTL, &IoCtl, true, UINT32_MAX) + && !fQuiet) + Log(("usbProxyLinuxSetConnected: failure, errno=%d. pProxyDev=%s\n", + errno, usbProxyGetName(pProxyDev))); + } +} + + +/** + * Links the given URB into the in flight list. + * + * @returns nothing. + * @param pDevLnx The proxy device instance - Linux specific data. + * @param pUrbLnx The URB to link into the in flight list. + */ +static void usbProxyLinuxUrbLinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) +{ + LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); + Assert(RTCritSectIsOwner(&pDevLnx->CritSect)); + Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); + RTListAppend(&pDevLnx->ListInFlight, &pUrbLnx->NodeList); +} + + +/** + * Unlinks the given URB from the in flight list. + * + * @returns nothing. + * @param pDevLnx The proxy device instance - Linux specific data. + * @param pUrbLnx The URB to link into the in flight list. + */ +static void usbProxyLinuxUrbUnlinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) +{ + LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); + RTCritSectEnter(&pDevLnx->CritSect); + + /* + * Remove from the active list. + */ + Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); + + RTListNodeRemove(&pUrbLnx->NodeList); + + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * Allocates a linux URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * @param pProxyDev The proxy device instance. + * @param pSplitHead The split list head if allocating for a split list. + */ +static PUSBPROXYURBLNX usbProxyLinuxUrbAlloc(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pSplitHead) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + PUSBPROXYURBLNX pUrbLnx; + + LogFlowFunc(("pProxyDev=%p pSplitHead=%p\n", pProxyDev, pSplitHead)); + + RTCritSectEnter(&pDevLnx->CritSect); + + /* + * Try remove a linux URB from the free list, if none there allocate a new one. + */ + pUrbLnx = RTListGetFirst(&pDevLnx->ListFree, USBPROXYURBLNX, NodeList); + if (pUrbLnx) + { + RTListNodeRemove(&pUrbLnx->NodeList); + RTCritSectLeave(&pDevLnx->CritSect); + } + else + { + RTCritSectLeave(&pDevLnx->CritSect); + PVUSBURB pVUrbDummy; RT_NOREF(pVUrbDummy); + pUrbLnx = (PUSBPROXYURBLNX)RTMemAlloc(RT_UOFFSETOF_DYN(USBPROXYURBLNX, + KUrb.iso_frame_desc[RT_ELEMENTS(pVUrbDummy->aIsocPkts)])); + if (!pUrbLnx) + return NULL; + } + + pUrbLnx->pSplitHead = pSplitHead; + pUrbLnx->pSplitNext = NULL; + pUrbLnx->fCanceledBySubmit = false; + pUrbLnx->fSplitElementReaped = false; + pUrbLnx->fDiscarded = false; + LogFlowFunc(("returns pUrbLnx=%p\n", pUrbLnx)); + return pUrbLnx; +} + + +/** + * Frees a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbLnx The linux URB to free. + */ +static void usbProxyLinuxUrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); + + /* + * Link it into the free list. + */ + RTCritSectEnter(&pDevLnx->CritSect); + RTListAppend(&pDevLnx->ListFree, &pUrbLnx->NodeList); + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * Frees split list of a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbLnx A linux URB to in the split list to be freed. + */ +static void usbProxyLinuxUrbFreeSplitList(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); + + RTCritSectEnter(&pDevLnx->CritSect); + + pUrbLnx = pUrbLnx->pSplitHead; + Assert(pUrbLnx); + while (pUrbLnx) + { + PUSBPROXYURBLNX pFree = pUrbLnx; + pUrbLnx = pUrbLnx->pSplitNext; + Assert(pFree->pSplitHead); + pFree->pSplitHead = pFree->pSplitNext = NULL; + usbProxyLinuxUrbFree(pProxyDev, pFree); + } + + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * This finds the device in the /proc/bus/usb/bus/addr file and finds + * the config with an asterix. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszDevNode The path to the device. We infere the location of + * the devices file, which bus and device number we're + * looking for. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfigUsbfs(PUSBPROXYDEV pProxyDev, const char *pszDevNode, int *piFirstCfg) +{ + RT_NOREF(pProxyDev); + + /* + * Set return defaults. + */ + int iActiveCfg = -1; + if (piFirstCfg) + *piFirstCfg = 1; + + /* + * Parse the usbfs device node path and turn it into a path to the "devices" file, + * picking up the device number and bus along the way. + */ + size_t cchDevNode = strlen(pszDevNode); + char *pszDevices = (char *)RTMemDupEx(pszDevNode, cchDevNode, sizeof("devices")); + AssertReturn(pszDevices, iActiveCfg); + + /* the device number */ + char *psz = pszDevices + cchDevNode; + while (*psz != '/') + psz--; + Assert(pszDevices < psz); + uint32_t uDev; + int rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uDev); + if (RT_SUCCESS(rc)) + { + /* the bus number */ + *psz-- = '\0'; + while (*psz != '/') + psz--; + Assert(pszDevices < psz); + uint32_t uBus; + rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uBus); + if (RT_SUCCESS(rc)) + { + strcpy(psz + 1, "devices"); + + /* + * Open and scan the devices file. + * We're ASSUMING that each device starts off with a 'T:' line. + */ + PRTSTREAM pFile; + rc = RTStrmOpen(pszDevices, "r", &pFile); + if (RT_SUCCESS(rc)) + { + char szLine[1024]; + while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) + { + /* we're only interested in 'T:' lines. */ + psz = RTStrStripL(szLine); + if (psz[0] != 'T' || psz[1] != ':') + continue; + + /* Skip ahead to 'Bus' and compare */ + psz = RTStrStripL(psz + 2); Assert(!strncmp(psz, RT_STR_TUPLE("Bus="))); + psz = RTStrStripL(psz + 4); + char *pszNext; + uint32_t u; + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_FAILURE(rc)) + continue; + if (u != uBus) + continue; + + /* Skip ahead to 'Dev#' and compare */ + psz = strstr(psz, "Dev#="); Assert(psz); + if (!psz) + continue; + psz = RTStrStripL(psz + 5); + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_FAILURE(rc)) + continue; + if (u != uDev) + continue; + + /* + * Ok, we've found the device. + * Scan until we find a selected configuration, the next device, or EOF. + */ + while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) + { + psz = RTStrStripL(szLine); + if (psz[0] == 'T') + break; + if (psz[0] != 'C' || psz[1] != ':') + continue; + const bool fActive = psz[2] == '*'; + if (!fActive && !piFirstCfg) + continue; + + /* Get the 'Cfg#' value. */ + psz = strstr(psz, "Cfg#="); Assert(psz); + if (psz) + { + psz = RTStrStripL(psz + 5); + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (piFirstCfg) + { + *piFirstCfg = u; + piFirstCfg = NULL; + } + if (fActive) + iActiveCfg = u; + } + } + if (fActive) + break; + } + break; + } + RTStrmClose(pFile); + } + } + } + RTMemFree(pszDevices); + + return iActiveCfg; +} + + +/** + * This finds the active configuration from sysfs. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszPath The sysfs path for the device. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfigSysfs(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) +{ +#ifdef VBOX_USB_WITH_SYSFS + if (piFirstCfg != NULL) + *piFirstCfg = pProxyDev->paCfgDescs != NULL + ? pProxyDev->paCfgDescs[0].Core.bConfigurationValue + : 1; + int64_t bCfg = 0; + int rc = RTLinuxSysFsReadIntFile(10, &bCfg, "%s/bConfigurationValue", pszPath); + if (RT_FAILURE(rc)) + bCfg = -1; + return (int)bCfg; +#else /* !VBOX_USB_WITH_SYSFS */ + return -1; +#endif /* !VBOX_USB_WITH_SYSFS */ +} + + +/** + * This finds the active configuration. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszPath The sysfs path for the device, or the usbfs device + * node path. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfig(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + if (pDevLnx->fUsingSysfs) + return usbProxyLinuxFindActiveConfigSysfs(pProxyDev, pszPath, piFirstCfg); + return usbProxyLinuxFindActiveConfigUsbfs(pProxyDev, pszPath, piFirstCfg); +} + + +/** + * Extracts the Linux file descriptor associated with the kernel USB device. + * This is used by rdesktop-vrdp for polling for events. + * @returns the FD, or asserts and returns -1 on error + * @param pProxyDev The device instance + */ +RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + AssertReturn(pDevLnx->hFile != NIL_RTFILE, -1); + return RTFileToNative(pDevLnx->hFile); +} + + +/** + * Opens the device file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress If we are using usbfs, this is the path to the + * device. If we are using sysfs, this is a string of + * the form "sysfs:<sysfs path>//device:<device node>". + * In the second case, the two paths are guaranteed + * not to contain the substring "//". + */ +static DECLCALLBACK(int) usbProxyLinuxOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlow(("usbProxyLinuxOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + const char *pszDevNode; + const char *pszPath; + size_t cchPath; + bool fUsingSysfs; + + /* + * Are we using sysfs or usbfs? + */ +#ifdef VBOX_USB_WITH_SYSFS + fUsingSysfs = strncmp(pszAddress, RT_STR_TUPLE("sysfs:")) == 0; + if (fUsingSysfs) + { + pszDevNode = strstr(pszAddress, "//device:"); + if (!pszDevNode) + { + LogRel(("usbProxyLinuxOpen: Invalid device address: '%s'\n", pszAddress)); + return VERR_INVALID_PARAMETER; + } + + pszPath = pszAddress + sizeof("sysfs:") - 1; + cchPath = pszDevNode - pszPath; + pszDevNode += sizeof("//device:") - 1; + } + else +#endif /* VBOX_USB_WITH_SYSFS */ + { +#ifndef VBOX_USB_WITH_SYSFS + fUsingSysfs = false; +#endif + pszPath = pszDevNode = pszAddress; + cchPath = strlen(pszPath); + } + + /* + * Try open the device node. + */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszDevNode, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the linux backend data. + */ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + RTListInit(&pDevLnx->ListFree); + RTListInit(&pDevLnx->ListInFlight); + pDevLnx->pszPath = RTStrDupN(pszPath, cchPath); + if (pDevLnx->pszPath) + { + rc = RTPipeCreate(&pDevLnx->hPipeWakeupR, &pDevLnx->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + pDevLnx->fUsingSysfs = fUsingSysfs; + pDevLnx->hFile = hFile; + pDevLnx->fClaimedIfsMask = 0; + rc = RTCritSectInit(&pDevLnx->CritSect); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyLinuxOpen(%p, %s): returns successfully File=%RTfile iActiveCfg=%d\n", + pProxyDev, pszAddress, pDevLnx->hFile, pProxyDev->iActiveCfg)); + + return VINF_SUCCESS; + } + RTPipeClose(pDevLnx->hPipeWakeupR); + RTPipeClose(pDevLnx->hPipeWakeupW); + } + } + else + rc = VERR_NO_MEMORY; + + RTFileClose(hFile); + } + else if (rc == VERR_ACCESS_DENIED) + rc = VERR_VUSB_USBFS_PERMISSION; + + Log(("usbProxyLinuxOpen(%p, %s) failed, rc=%Rrc!\n", pProxyDev, pszAddress, rc)); + return rc; +} + + +/** + * Claims all the interfaces and figures out the + * current configuration. + * + * @returns VINF_SUCCESS. + * @param pProxyDev The proxy device. + */ +static DECLCALLBACK(int) usbProxyLinuxInit(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Brute force rulez. + * usbProxyLinuxSetConnected check for masked interfaces. + */ + unsigned iIf; + for (iIf = 0; iIf < 256; iIf++) + usbProxyLinuxSetConnected(pProxyDev, iIf, false, true); + + /* + * Determine the active configuration. + * + * If there isn't any active configuration, we will get EHOSTUNREACH (113) errors + * when trying to read the device descriptors in usbProxyDevCreate. So, we'll make + * the first one active (usually 1) then. + */ + pProxyDev->cIgnoreSetConfigs = 1; + int iFirstCfg; + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, &iFirstCfg); + if (pProxyDev->iActiveCfg == -1) + { + usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iFirstCfg, false, UINT32_MAX); + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); + Log(("usbProxyLinuxInit: No active config! Tried to set %d: iActiveCfg=%d\n", iFirstCfg, pProxyDev->iActiveCfg)); + } + else + Log(("usbProxyLinuxInit(%p): iActiveCfg=%d\n", pProxyDev, pProxyDev->iActiveCfg)); + return VINF_SUCCESS; +} + + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyLinuxClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyLinuxClose: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + AssertPtrReturnVoid(pDevLnx); + + /* + * Try put the device in a state which linux can cope with before we release it. + * Resetting it would be a nice start, although we must remember + * that it might have been disconnected... + * + * Don't reset if we're masking interfaces or if construction failed. + */ + if (pProxyDev->fInited) + { + /* ASSUMES: thread == EMT */ + if ( pProxyDev->fMaskedIfs + || !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) + { + /* Connect drivers. */ + unsigned iIf; + for (iIf = 0; iIf < 256; iIf++) + usbProxyLinuxSetConnected(pProxyDev, iIf, true, true); + Log(("USB: Successfully reset device pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + } + else if (errno != ENODEV) + LogRel(("USB: Reset failed, errno=%d, pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); + else /* This will happen if device was detached. */ + Log(("USB: Reset failed, errno=%d (ENODEV), pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); + } + + /* + * Now we can free all the resources and close the device. + */ + RTCritSectDelete(&pDevLnx->CritSect); + + PUSBPROXYURBLNX pUrbLnx; + PUSBPROXYURBLNX pUrbLnxNext; + RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + RTListNodeRemove(&pUrbLnx->NodeList); + + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX) + && errno != ENODEV + && errno != ENOENT) + AssertMsgFailed(("errno=%d\n", errno)); + + if (pUrbLnx->pSplitHead) + { + PUSBPROXYURBLNX pCur = pUrbLnx->pSplitNext; + while (pCur) + { + PUSBPROXYURBLNX pFree = pCur; + pCur = pFree->pSplitNext; + if ( !pFree->fSplitElementReaped + && usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pFree->KUrb, false, UINT32_MAX) + && errno != ENODEV + && errno != ENOENT) + AssertMsgFailed(("errno=%d\n", errno)); + RTMemFree(pFree); + } + } + else + Assert(!pUrbLnx->pSplitNext); + RTMemFree(pUrbLnx); + } + + RTListForEachSafe(&pDevLnx->ListFree, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + RTListNodeRemove(&pUrbLnx->NodeList); + RTMemFree(pUrbLnx); + } + + RTFileClose(pDevLnx->hFile); + pDevLnx->hFile = NIL_RTFILE; + + RTPipeClose(pDevLnx->hPipeWakeupR); + RTPipeClose(pDevLnx->hPipeWakeupW); + + RTStrFree(pDevLnx->pszPath); + + LogFlow(("usbProxyLinuxClose: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset} */ +static DECLCALLBACK(int) usbProxyLinuxReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + RT_NOREF(fResetOnLinux); + Assert(!pProxyDev->fMaskedIfs); + LogFlow(("usbProxyLinuxReset: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + + uint32_t fActiveIfsMask = pDevLnx->fClaimedIfsMask; + unsigned i; + + /* + * Before reset, release claimed interfaces. This less than obvious move + * prevents Linux from rebinding in-kernel drivers to the device after reset. + */ + for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i) + { + if (fActiveIfsMask & RT_BIT(i)) + { + usbProxyLinuxReleaseInterface(pProxyDev, i); + } + } + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) + { + int rc = errno; + LogRel(("usb-linux: Reset failed, rc=%Rrc errno=%d.\n", RTErrConvertFromErrno(rc), rc)); + pProxyDev->iActiveCfg = -1; + return RTErrConvertFromErrno(rc); + } + + /* + * Now reclaim previously claimed interfaces. If that doesn't work, let's hope + * the guest/VUSB can recover from that. Can happen if reset changes configuration. + */ + for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i) + { + if (fActiveIfsMask & RT_BIT(i)) + { + usbProxyLinuxClaimInterface(pProxyDev, i); + } + } + + /* find the active config - damn annoying. */ + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); + LogFlow(("usbProxyLinuxReset: returns successfully iActiveCfg=%d\n", pProxyDev->iActiveCfg)); + + pProxyDev->cIgnoreSetConfigs = 2; + return VINF_SUCCESS; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyLinuxSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlow(("usbProxyLinuxSetConfig: pProxyDev=%s cfg=%#x\n", + usbProxyGetName(pProxyDev), iCfg)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iCfg, true, UINT32_MAX)) + { + Log(("usb-linux: Set configuration. errno=%d\n", errno)); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Claims an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlow(("usbProxyLinuxClaimInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); + usbProxyLinuxSetConnected(pProxyDev, iIf, false, false); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLAIMINTERFACE, &iIf, true, UINT32_MAX)) + { + pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf); + LogRel(("usb-linux: Claim interface. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + pDevLnx->fClaimedIfsMask |= RT_BIT(iIf); + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlow(("usbProxyLinuxReleaseInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RELEASEINTERFACE, &iIf, true, UINT32_MAX)) + { + LogRel(("usb-linux: Release interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf); + return VINF_SUCCESS; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf; + LogFlow(("usbProxyLinuxSetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt)); + + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETINTERFACE, &SetIf, true, UINT32_MAX)) + { + Log(("usb-linux: Set interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxyLinuxClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + LogFlow(("usbProxyLinuxClearHaltedEp: pProxyDev=%s EndPt=%u\n", usbProxyGetName(pProxyDev), EndPt)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLEAR_HALT, &EndPt, true, UINT32_MAX)) + { + /* + * Unfortunately this doesn't work on control pipes. + * Windows doing this on the default endpoint and possibly other pipes too, + * so we'll feign success for ENOENT errors. + */ + if (errno == ENOENT) + { + Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d - IGNORED\n", + errno, usbProxyGetName(pProxyDev), EndPt)); + return VINF_SUCCESS; + } + Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d\n", + errno, usbProxyGetName(pProxyDev), EndPt)); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Setup packet byte-swapping routines. + */ +static void usbProxyLinuxUrbSwapSetup(PVUSBSETUP pSetup) +{ + pSetup->wValue = RT_H2LE_U16(pSetup->wValue); + pSetup->wIndex = RT_H2LE_U16(pSetup->wIndex); + pSetup->wLength = RT_H2LE_U16(pSetup->wLength); +} + + +/** + * Clean up after a failed URB submit. + */ +static void usbProxyLinuxCleanupFailedSubmit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PUSBPROXYURBLNX pCur, + PVUSBURB pUrb, bool *pfUnplugged) +{ + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + + /* discard and reap later (walking with pUrbLnx). */ + if (pUrbLnx != pCur) + { + for (;;) + { + pUrbLnx->fCanceledBySubmit = true; + pUrbLnx->KUrb.usercontext = NULL; + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX)) + { + if (errno == ENODEV) + *pfUnplugged = true; + else if (errno == ENOENT) + pUrbLnx->fSplitElementReaped = true; + else + LogRel(("USB: Failed to discard %p! errno=%d (pUrb=%p)\n", pUrbLnx->KUrb.usercontext, errno, pUrb)); /* serious! */ + } + if (pUrbLnx->pSplitNext == pCur) + { + pUrbLnx->pSplitNext = NULL; + break; + } + pUrbLnx = pUrbLnx->pSplitNext; Assert(pUrbLnx); + } + } + + /* free the unsubmitted ones. */ + while (pCur) + { + PUSBPROXYURBLNX pFree = pCur; + pCur = pCur->pSplitNext; + usbProxyLinuxUrbFree(pProxyDev, pFree); + } + + /* send unplug event if we failed with ENODEV originally. */ + if (*pfUnplugged) + usbProxLinuxUrbUnplugged(pProxyDev); +} + +/** + * Submit one URB through the usbfs IOCTL interface, with + * retries + * + * @returns VBox status code. + */ +static int usbProxyLinuxSubmitURB(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged) +{ + RT_NOREF(pUrb); + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + unsigned cTries = 0; + + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pCur->KUrb)) + { + if (errno == EINTR) + continue; + if (errno == ENODEV) + { + Log(("usbProxyLinuxSubmitURB: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + *pfUnplugged = true; + return RTErrConvertFromErrno(errno); + } + + Log(("usb-linux: Submit URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", + pUrb, errno, pCur->KUrb.type, pCur->KUrb.endpoint, pCur->KUrb.buffer_length, cTries)); + if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ + continue; + + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + +/** The split size. 16K in known Linux kernel versions. */ +#define SPLIT_SIZE 0x4000 + +/** + * Create a URB fragment of up to SPLIT_SIZE size and hook it + * into the list of fragments. + * + * @returns pointer to newly allocated URB fragment or NULL. + */ +static PUSBPROXYURBLNX usbProxyLinuxSplitURBFragment(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pHead, PUSBPROXYURBLNX pCur) +{ + PUSBPROXYURBLNX pNew; + uint32_t cbLeft = pCur->cbSplitRemaining; + uint8_t *pb = (uint8_t *)pCur->KUrb.buffer; + + LogFlowFunc(("pProxyDev=%p pHead=%p pCur=%p\n", pProxyDev, pHead, pCur)); + + Assert(cbLeft != 0); + pNew = pCur->pSplitNext = usbProxyLinuxUrbAlloc(pProxyDev, pHead); + if (!pNew) + { + usbProxyLinuxUrbFreeSplitList(pProxyDev, pHead); + return NULL; + } + Assert(pNew->pSplitHead == pHead); + Assert(pNew->pSplitNext == NULL); + + pNew->KUrb = pHead->KUrb; + pNew->KUrb.buffer = pb + pCur->KUrb.buffer_length; + pNew->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); + pNew->KUrb.actual_length = 0; + + cbLeft -= pNew->KUrb.buffer_length; + Assert(cbLeft < INT32_MAX); + pNew->cbSplitRemaining = cbLeft; + LogFlowFunc(("returns pNew=%p\n", pNew)); + return pNew; +} + +/** + * Try splitting up a VUSB URB into smaller URBs which the + * linux kernel (usbfs) can deal with. + * + * NB: For ShortOK reads things get a little tricky - we don't + * know how much data is going to arrive and not all the + * fragment URBs might be filled. We can only safely set up one + * URB at a time -> worse performance but correct behaviour. + * + * @returns VBox status code. + * @param pProxyDev The proxy device. + * @param pUrbLnx The linux URB which was rejected because of being too big. + * @param pUrb The VUSB URB. + */ +static int usbProxyLinuxUrbQueueSplit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PVUSBURB pUrb) +{ + /* + * Split it up into SPLIT_SIZE sized blocks. + */ + const unsigned cKUrbs = (pUrb->cbData + SPLIT_SIZE - 1) / SPLIT_SIZE; + LogFlow(("usbProxyLinuxUrbQueueSplit: pUrb=%p cKUrbs=%d cbData=%d\n", pUrb, cKUrbs, pUrb->cbData)); + + uint32_t cbLeft = pUrb->cbData; + uint8_t *pb = &pUrb->abData[0]; + + /* the first one (already allocated) */ + switch (pUrb->enmType) + { + default: /* shut up gcc */ + case VUSBXFERTYPE_BULK: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; break; + case VUSBXFERTYPE_INTR: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; break; + case VUSBXFERTYPE_MSG: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; break; + case VUSBXFERTYPE_ISOC: + AssertMsgFailed(("We can't split isochronous URBs!\n")); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return VERR_INVALID_PARAMETER; /** @todo Better status code. */ + } + pUrbLnx->KUrb.endpoint = pUrb->EndPt; + if (pUrb->enmDir == VUSBDIRECTION_IN) + pUrbLnx->KUrb.endpoint |= 0x80; + pUrbLnx->KUrb.flags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; + pUrbLnx->KUrb.status = 0; + pUrbLnx->KUrb.buffer = pb; + pUrbLnx->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); + pUrbLnx->KUrb.actual_length = 0; + pUrbLnx->KUrb.start_frame = 0; + pUrbLnx->KUrb.number_of_packets = 0; + pUrbLnx->KUrb.error_count = 0; + pUrbLnx->KUrb.signr = 0; + pUrbLnx->KUrb.usercontext = pUrb; + pUrbLnx->pSplitHead = pUrbLnx; + pUrbLnx->pSplitNext = NULL; + + PUSBPROXYURBLNX pCur = pUrbLnx; + + cbLeft -= pUrbLnx->KUrb.buffer_length; + pUrbLnx->cbSplitRemaining = cbLeft; + + int rc = VINF_SUCCESS; + bool fUnplugged = false; + if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk) + { + /* Subsequent fragments will be queued only after the previous fragment is reaped + * and only if necessary. + */ + Log(("usb-linux: Large ShortOK read, only queuing first fragment.\n")); + Assert(pUrbLnx->cbSplitRemaining > 0 && pUrbLnx->cbSplitRemaining < 256 * _1K); + rc = usbProxyLinuxSubmitURB(pProxyDev, pUrbLnx, pUrb, &fUnplugged); + } + else + { + /* the rest. */ + unsigned i; + for (i = 1; i < cKUrbs; i++) + { + pCur = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx, pCur); + if (!pCur) + return VERR_NO_MEMORY; + } + Assert(pCur->cbSplitRemaining == 0); + + /* Submit the blocks. */ + pCur = pUrbLnx; + for (i = 0; i < cKUrbs; i++, pCur = pCur->pSplitNext) + { + rc = usbProxyLinuxSubmitURB(pProxyDev, pCur, pUrb, &fUnplugged); + if (RT_FAILURE(rc)) + break; + } + } + + if (RT_SUCCESS(rc)) + { + pUrb->Dev.pvPrivate = pUrbLnx; + usbProxyLinuxUrbLinkInFlight(USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX), pUrbLnx); + LogFlow(("usbProxyLinuxUrbQueueSplit: ok\n")); + return VINF_SUCCESS; + } + + usbProxyLinuxCleanupFailedSubmit(pProxyDev, pUrbLnx, pCur, pUrb, &fUnplugged); + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyLinuxUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + unsigned cTries; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + LogFlow(("usbProxyLinuxUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + usbProxyGetName(pProxyDev), pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Allocate a linux urb. + */ + PUSBPROXYURBLNX pUrbLnx = usbProxyLinuxUrbAlloc(pProxyDev, NULL); + if (!pUrbLnx) + return VERR_NO_MEMORY; + + pUrbLnx->KUrb.endpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + pUrbLnx->KUrb.status = 0; + pUrbLnx->KUrb.flags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; + pUrbLnx->KUrb.buffer = pUrb->abData; + pUrbLnx->KUrb.buffer_length = pUrb->cbData; + pUrbLnx->KUrb.actual_length = 0; + pUrbLnx->KUrb.start_frame = 0; + pUrbLnx->KUrb.number_of_packets = 0; + pUrbLnx->KUrb.error_count = 0; + pUrbLnx->KUrb.signr = 0; + pUrbLnx->KUrb.usercontext = pUrb; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; + if (pUrb->cbData < sizeof(VUSBSETUP)) + { + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return VERR_BUFFER_UNDERFLOW; + } + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + LogFlow(("usbProxyLinuxUrbQueue: message\n")); + break; + case VUSBXFERTYPE_BULK: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; + break; + case VUSBXFERTYPE_ISOC: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_ISO; + pUrbLnx->KUrb.flags |= USBDEVFS_URB_ISO_ASAP; + pUrbLnx->KUrb.number_of_packets = pUrb->cIsocPkts; + unsigned i; + for (i = 0; i < pUrb->cIsocPkts; i++) + { +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + pUrbLnx->KUrb.iso_frame_desc[i].length = pUrb->aIsocPkts[i].cb; + pUrbLnx->KUrb.iso_frame_desc[i].actual_length = 0; + pUrbLnx->KUrb.iso_frame_desc[i].status = 0x7fff; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + } + break; + case VUSBXFERTYPE_INTR: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; + break; + default: + rc = VERR_INVALID_PARAMETER; /** @todo better status code. */ + } + + /* + * We have to serialize access by using the critial section here because this + * thread might be suspended after submitting the URB but before linking it into + * the in flight list. This would get us in trouble when reaping the URB on another + * thread while it isn't in the in flight list. + * + * Linking the URB into the list before submitting it like it was done in the past is not + * possible either because submitting the URB might fail here because the device gets + * detached. The reaper thread gets this event too and might race this thread before we + * can unlink the URB from the active list and the common code might end up freeing + * the common URB structure twice. + */ + RTCritSectEnter(&pDevLnx->CritSect); + /* + * Submit it. + */ + cTries = 0; + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pUrbLnx->KUrb)) + { + if (errno == EINTR) + continue; + if (errno == ENODEV) + { + rc = RTErrConvertFromErrno(errno); + Log(("usbProxyLinuxUrbQueue: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + + RTCritSectLeave(&pDevLnx->CritSect); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + usbProxLinuxUrbUnplugged(pProxyDev); + return rc; + } + + /* + * usbfs has or used to have a low buffer limit (16KB) in order to prevent + * processes wasting kmalloc'ed memory. It will return EINVAL if break that + * limit, and we'll have to split the VUSB URB up into multiple linux URBs. + * + * Since this is a limit which is subject to change, we cannot check for it + * before submitting the URB. We just have to try and fail. + */ + if ( errno == EINVAL + && pUrb->cbData >= 8*_1K) + { + rc = usbProxyLinuxUrbQueueSplit(pProxyDev, pUrbLnx, pUrb); + RTCritSectLeave(&pDevLnx->CritSect); + return rc; + } + + Log(("usb-linux: Queue URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", + pUrb, errno, pUrbLnx->KUrb.type, pUrbLnx->KUrb.endpoint, pUrbLnx->KUrb.buffer_length, cTries)); + if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ + continue; + + RTCritSectLeave(&pDevLnx->CritSect); + rc = RTErrConvertFromErrno(errno); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return rc; + } + + usbProxyLinuxUrbLinkInFlight(pDevLnx, pUrbLnx); + RTCritSectLeave(&pDevLnx->CritSect); + + LogFlow(("usbProxyLinuxUrbQueue: ok\n")); + pUrb->Dev.pvPrivate = pUrbLnx; + return rc; +} + + +/** + * Translate the linux status to a VUSB status. + * + * @remarks see cc_to_error in ohci.h, uhci_map_status in uhci-q.c, + * sitd_complete+itd_complete in ehci-sched.c, and qtd_copy_status in + * ehci-q.c. + */ +static VUSBSTATUS vusbProxyLinuxStatusToVUsbStatus(int iStatus) +{ + switch (iStatus) + { + /** @todo VUSBSTATUS_NOT_ACCESSED */ + case -EXDEV: /* iso transfer, partial result. */ + case 0: + return VUSBSTATUS_OK; + + case -EILSEQ: + return VUSBSTATUS_CRC; + + case -EREMOTEIO: /* ehci and ohci uses this for underflow error. */ + return VUSBSTATUS_DATA_UNDERRUN; + case -EOVERFLOW: + return VUSBSTATUS_DATA_OVERRUN; + + case -ETIME: + case -ENODEV: + return VUSBSTATUS_DNR; + + //case -ECOMM: + // return VUSBSTATUS_BUFFER_OVERRUN; + //case -ENOSR: + // return VUSBSTATUS_BUFFER_UNDERRUN; + + case -EPROTO: + Log(("vusbProxyLinuxStatusToVUsbStatus: DNR/EPPROTO!!\n")); + return VUSBSTATUS_DNR; + + case -EPIPE: + Log(("vusbProxyLinuxStatusToVUsbStatus: STALL/EPIPE!!\n")); + return VUSBSTATUS_STALL; + + case -ESHUTDOWN: + Log(("vusbProxyLinuxStatusToVUsbStatus: SHUTDOWN!!\n")); + return VUSBSTATUS_STALL; + + case -ENOENT: + Log(("vusbProxyLinuxStatusToVUsbStatus: ENOENT!!\n")); + return VUSBSTATUS_STALL; + + default: + Log(("vusbProxyLinuxStatusToVUsbStatus: status %d!!\n", iStatus)); + return VUSBSTATUS_STALL; + } +} + + +/** + * Get and translates the linux status to a VUSB status. + */ +static VUSBSTATUS vusbProxyLinuxUrbGetStatus(PUSBPROXYURBLNX pUrbLnx) +{ + return vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.status); +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyLinuxUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PUSBPROXYURBLNX pUrbLnx = NULL; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Block for requested period. + * + * It seems to me that the path of poll() is shorter and + * involves less semaphores than ioctl() on usbfs. So, we'll + * do a poll regardless of whether cMillies == 0 or not. + */ + if (cMillies) + { + int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : cMillies; + + for (;;) + { + struct pollfd pfd[2]; + pfd[0].fd = RTFileToNative(pDevLnx->hFile); + pfd[0].events = POLLOUT | POLLWRNORM /* completed async */ + | POLLERR | POLLHUP /* disconnected */; + pfd[0].revents = 0; + + pfd[1].fd = RTPipeToNative(pDevLnx->hPipeWakeupR); + pfd[1].events = POLLIN | POLLHUP; + pfd[1].revents = 0; + + int rc = poll(&pfd[0], 2, cMilliesWait); + Log(("usbProxyLinuxUrbReap: poll rc = %d\n", rc)); + if (rc >= 1) + { + /* If the pipe caused the return drain it. */ + if (pfd[1].revents & POLLIN) + { + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevLnx->hPipeWakeupR, &bRead, 1, &cbIgnored); + } + break; + } + if (rc >= 0) + return NULL; + + if (errno != EAGAIN) + { + Log(("usb-linux: Reap URB - poll -> %d errno=%d pProxyDev=%s\n", rc, errno, usbProxyGetName(pProxyDev))); + return NULL; + } + Log(("usbProxyLinuxUrbReap: poll again - weird!!!\n")); + } + } + + /* + * Reap URBs, non-blocking. + */ + for (;;) + { + struct usbdevfs_urb *pKUrb; + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_REAPURBNDELAY, &pKUrb)) + if (errno != EINTR) + { + if (errno == ENODEV) + usbProxLinuxUrbUnplugged(pProxyDev); + else + Log(("usb-linux: Reap URB. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return NULL; + } + pUrbLnx = RT_FROM_MEMBER(pKUrb, USBPROXYURBLNX, KUrb); + + /* split list: Is the entire split list done yet? */ + if (pUrbLnx->pSplitHead) + { + pUrbLnx->fSplitElementReaped = true; + + /* for variable size URBs, we may need to queue more if the just-reaped URB was completely filled */ + if (pUrbLnx->cbSplitRemaining && (pKUrb->actual_length == pKUrb->buffer_length) && !pUrbLnx->pSplitNext) + { + bool fUnplugged = false; + bool fSucceeded; + + Assert(pUrbLnx->pSplitHead); + Assert((pKUrb->endpoint & 0x80) && !(pKUrb->flags & USBDEVFS_URB_SHORT_NOT_OK)); + PUSBPROXYURBLNX pNew = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx->pSplitHead, pUrbLnx); + if (!pNew) + { + Log(("usb-linux: Allocating URB fragment failed. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return NULL; + } + PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; + fSucceeded = usbProxyLinuxSubmitURB(pProxyDev, pNew, pUrb, &fUnplugged); + if (fUnplugged) + usbProxLinuxUrbUnplugged(pProxyDev); + if (!fSucceeded) + return NULL; + continue; /* try reaping another URB */ + } + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx->pSplitHead; pCur; pCur = pCur->pSplitNext) + if (!pCur->fSplitElementReaped) + { + pUrbLnx = NULL; + break; + } + if (!pUrbLnx) + continue; + pUrbLnx = pUrbLnx->pSplitHead; + } + break; + } + + /* + * Ok, we got one! + */ + PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; + if ( pUrb + && !pUrbLnx->fCanceledBySubmit) + { + if (pUrbLnx->pSplitHead) + { + /* split - find the end byte and the first error status. */ + Assert(pUrbLnx == pUrbLnx->pSplitHead); + uint8_t *pbEnd = &pUrb->abData[0]; + pUrb->enmStatus = VUSBSTATUS_OK; + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) + { + if (pCur->KUrb.actual_length) + pbEnd = (uint8_t *)pCur->KUrb.buffer + pCur->KUrb.actual_length; + if (pUrb->enmStatus == VUSBSTATUS_OK) + pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pCur); + } + pUrb->cbData = pbEnd - &pUrb->abData[0]; + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFreeSplitList(pProxyDev, pUrbLnx); + } + else + { + /* unsplit. */ + pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pUrbLnx); + pUrb->cbData = pUrbLnx->KUrb.actual_length; + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + unsigned i, off; + for (i = 0, off = 0; i < pUrb->cIsocPkts; i++) + { +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + pUrb->aIsocPkts[i].enmStatus = vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.iso_frame_desc[i].status); + Assert(pUrb->aIsocPkts[i].off == off); + pUrb->aIsocPkts[i].cb = pUrbLnx->KUrb.iso_frame_desc[i].actual_length; + off += pUrbLnx->KUrb.iso_frame_desc[i].length; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + } + } + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + } + pUrb->Dev.pvPrivate = NULL; + + /* some adjustments for message transfers. */ + if (pUrb->enmType == VUSBXFERTYPE_MSG) + { + pUrb->cbData += sizeof(VUSBSETUP); + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + } + } + else + { + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + pUrb = NULL; + } + + LogFlow(("usbProxyLinuxUrbReap: pProxyDev=%s returns %p\n", usbProxyGetName(pProxyDev), pUrb)); + return pUrb; +} + + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static DECLCALLBACK(int) usbProxyLinuxUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + PUSBPROXYURBLNX pUrbLnx = (PUSBPROXYURBLNX)pUrb->Dev.pvPrivate; + if (pUrbLnx->pSplitHead) + { + /* split */ + Assert(pUrbLnx == pUrbLnx->pSplitHead); + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) + { + if (pCur->fSplitElementReaped) + continue; + if ( !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX) + || errno == ENOENT) + continue; + if (errno == ENODEV) + break; + /** @todo Think about how to handle errors wrt. to the status code. */ + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n", + pUrb, errno, usbProxyGetName(pProxyDev))); + } + } + else + { + /* unsplit */ + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, true, UINT32_MAX) + && errno != ENODEV /* deal with elsewhere. */ + && errno != ENOENT) + { + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n", + pUrb, errno, usbProxyGetName(pProxyDev))); + rc = RTErrConvertFromErrno(errno); + } + } + + return rc; +} + + +static DECLCALLBACK(int) usbProxyLinuxWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevLnx->hPipeWakeupW, "", 1, &cbIgnored); +} + +/** + * The Linux USB Proxy Backend. + */ +const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVLNX), + usbProxyLinuxOpen, + usbProxyLinuxInit, + usbProxyLinuxClose, + usbProxyLinuxReset, + usbProxyLinuxSetConfig, + usbProxyLinuxClaimInterface, + usbProxyLinuxReleaseInterface, + usbProxyLinuxSetInterface, + usbProxyLinuxClearHaltedEp, + usbProxyLinuxUrbQueue, + usbProxyLinuxUrbCancel, + usbProxyLinuxUrbReap, + usbProxyLinuxWakeup, + 0 +}; + + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * End: + */ + diff --git a/src/VBox/Devices/USB/os2/Makefile.kup b/src/VBox/Devices/USB/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/os2/Makefile.kup diff --git a/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp new file mode 100644 index 00000000..3b1997e2 --- /dev/null +++ b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp @@ -0,0 +1,879 @@ +/* $Id: USBProxyDevice-os2.cpp $ */ +/** @file + * USB device proxy - the Linux backend. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/file.h> +#include "../USBProxyDevice.h" + +#define INCL_BASE +#define INCL_ERRORS +#include <os2.h> +#include <usbcalls.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure for keeping track of the URBs for a device. + */ +typedef struct USBPROXYURBOS2 +{ + /** Pointer to the virtual URB. */ + PVUSBURB pUrb; + /** Pointer to the next OS/2 URB. */ + struct USBPROXYURBOS2 *pNext; + /** Pointer to the previous OS/2 URB. */ + struct USBPROXYURBOS2 *pPrev; +} USBPROXYURBOS2, *PUSBPROXYURBOS2; + +/** + * Data for the OS/2 usb proxy backend. + */ +typedef struct USBPROXYDEVOS2 +{ + /** The async thread for this device. + * Currently only one thread is used, but this might have to change... */ + RTTHREAD Thread; + /** Thread termination indicator. */ + bool volatile fTerminate; + /** The USB handle. */ + USBHANDLE hDevice; + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** For blocking reap calls. */ + RTSEMEVENT EventSyncWait; + /** List of URBs to process. Doubly linked. */ + PUSBPROXYURBOS2 pTodoHead; + /** The tail pointer. */ + PUSBPROXYURBOS2 pTodoTail; + /** The list of free linux URBs. Singly linked. */ + PUSBPROXYURBOS2 pFreeHead; + /** The list of active linux URBs. Doubly linked. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. */ + PUSBPROXYURBOS2 pInFlightHead; + /** The list of landed linux URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBOS2 pTaxingHead; + /** The tail of the landed linux URBs. */ + PUSBPROXYURBOS2 pTaxingTail; +} USBPROXYDEVOS2, *PUSBPROXYDEVOS2; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef DYNAMIC_USBCALLS +static int usbProxyOs2GlobalInit(void); +#endif +static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev); +static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2); +static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef DYNAMIC_USBCALLS +static HMODULE g_hmod; +static APIRET (APIENTRY *g_pfnUsbOpen)(PUSBHANDLE, USHORT, USHORT, USHORT, USHORT); +static APIRET (APIENTRY *g_pfnUsbClose)(USBHANDLE); +static APIRET (APIENTRY *g_pfnUsbCtrlMessage)(USBHANDLE, UCHAR, UCHAR, USHORT, USHORT, USHORT, void *, ULONG); +static APIRET (APIENTRY *g_pfnUsbBulkRead2)(USBHANDLE, UCHAR, UCHAR, BOOL, PULONG, void *, ULONG); +static APIRET (APIENTRY *g_pfnUsbBulkWrite2)(USBHANDLE, UCHAR, UCHAR, BOOL, ULONG, void *, ULONG); +#else +# define g_pfnUsbOpen UsbOpen +# define g_pfnUsbClose UsbClose +# define g_pfnUsbCtrlMessage UsbCtrlMessage +# define g_pfnUsbBulkRead2 UsbBulkRead2 +# define g_pfnUsbBulkWrite2 UsbBulkWrite2 +#endif + + + +#ifdef DYNAMIC_USBCALLS +/** + * Loads usbcalls.dll and resolves the symbols we need. + * + * The usbcalls.dll will not be unloaded. + * + * @returns VBox status code. + */ +static int usbProxyOs2GlobalInit(void) +{ + int rc = DosLoadModule(NULL, 0, (PCSZ)"usbcalls", &g_hmod); + rc = RTErrConvertFromOS2(rc); + if (RT_SUCCESS(rc)) + { + if ( (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbOpen", (PPFN)&g_pfnUsbOpen)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbClose", (PPFN)&g_pfnUsbClose)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbCtrlMessage", (PPFN)&g_pfnUsbCtrlMessage)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkRead", (PPFN)&g_pfnUsbBulkRead)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkWrite", (PPFN)&g_pfnUsbBulkWrite)) == NO_ERROR + ) + { + + return VINF_SUCCESS; + } + + g_pfnUsbOpen = NULL; + g_pfnUsbClose = NULL; + g_pfnUsbCtrlMessage = NULL; + g_pfnUsbBulkRead = NULL; + g_pfnUsbBulkWrite = NULL; + DosFreeModule(g_hmod); + } + + g_hmod = NULLHANDLE; + return rc; +} +#endif + + + +/** + * Allocates a OS/2 URB request structure. + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * @param pProxyDev The proxy device instance. + */ +static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + PUSBPROXYURBOS2 pUrbOs2; + + RTCritSectEnter(&pDevOs2->CritSect); + + /* + * Try remove a linux URB from the free list, if none there allocate a new one. + */ + pUrbOs2 = pDevOs2->pFreeHead; + if (pUrbOs2) + pDevOs2->pFreeHead = pUrbOs2->pNext; + else + { + RTCritSectLeave(&pDevOs2->CritSect); + pUrbOs2 = (PUSBPROXYURBOS2)RTMemAlloc(sizeof(*pUrbOs2)); + if (!pUrbOs2) + return NULL; + RTCritSectEnter(&pDevOs2->CritSect); + } + + /* + * Link it into the active list + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pInFlightHead; + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = pUrbOs2; + pDevOs2->pInFlightHead = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); + return pUrbOs2; +} + + +/** + * Frees a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbOs2 The linux URB to free. + */ +static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + + RTCritSectEnter(&pDevOs2->CritSect); + + /* + * Remove from the active list. + */ + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = pUrbOs2->pPrev; + else if (pDevOs2->pTaxingTail == pUrbOs2) + pDevOs2->pTaxingTail = pUrbOs2->pPrev; + else if (pDevOs2->pTodoTail == pUrbOs2) + pDevOs2->pTodoTail = pUrbOs2->pPrev; + + if (pUrbOs2->pPrev) + pUrbOs2->pPrev->pNext = pUrbOs2->pNext; + else if (pDevOs2->pTaxingHead == pUrbOs2) + pDevOs2->pTaxingHead = pUrbOs2->pNext; + else if (pDevOs2->pInFlightHead == pUrbOs2) + pDevOs2->pInFlightHead = pUrbOs2->pNext; + else if (pDevOs2->pTodoHead == pUrbOs2) + pDevOs2->pTodoHead = pUrbOs2->pNext; + + /* + * Link it into the free list. + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pFreeHead; + pDevOs2->pFreeHead = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); +} + + +/** + * Thread for executing the URBs asynchronously. + * + * @returns VINF_SUCCESS. + * @param Thread Thread handle (IPRT). + * @param pvProxyDev Pointer to the proxy device we're servicing. + */ +static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev) +{ + PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pvProxyDev; + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + size_t cbLow = 0; + void *pvLow = NULL; + + + /* + * The main loop. + * + * We're always in the critsect, except when waiting or submitting a URB. + */ + int rc = RTCritSectEnter(&pDevOs2->CritSect); AssertRC(rc); + + while (!pDevOs2->fTerminate) + { + /* + * Anything to do? + */ + PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTodoHead; + if (pUrbOs2) + { + pDevOs2->pTodoHead = pUrbOs2->pNext; + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = NULL; + else + pDevOs2->pTodoTail = NULL; + + /* + * Move it to the in-flight list and submit it. + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pInFlightHead; + if (pDevOs2->pInFlightHead) + pDevOs2->pInFlightHead->pPrev = pUrbOs2; + //else + // pDevOs2->pInFlightTail = pUrbOs2; + Log3(("%s: usbProxyOs2AsyncThread: pPickup\n", pUrbOs2->pUrb->pszDesc)); + + RTCritSectLeave(&pDevOs2->CritSect); + + /* + * Process the URB. + */ + PVUSBURB pUrb = pUrbOs2->pUrb; + uint8_t *pbData = &pUrb->abData[0]; + ULONG cbData = pUrb->cbData; + if ( (uintptr_t)pbData >= 0x20000000 + || ((uintptr_t)pbData & 0xfff)) + { + if (cbData > cbLow) + { + if (pvLow) + DosFreeMem(pvLow); + cbLow = (cbData + 0xffff) & ~0xffff; + rc = DosAllocMem(&pvLow, cbLow, PAG_WRITE | PAG_READ | OBJ_TILE | PAG_COMMIT); + if (rc) + { + cbLow = 0; + pvLow = NULL; + } + } + if (pvLow) + pbData = (uint8_t *)memcpy(pvLow, pbData, cbData); + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + PVUSBSETUP pSetup = (PVUSBSETUP)&pbData[0]; + Log2(("%s: usbProxyOs2AsyncThread: CtlrMsg\n", pUrb->pszDesc)); + rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, /** @todo this API must take a endpoint number! */ + pSetup->bmRequestType, + pSetup->bRequest, + pSetup->wValue, + pSetup->wIndex, + pSetup->wLength, + pSetup + 1, + 5*60000 /* min */); + break; + } + + case VUSBXFERTYPE_BULK: + { + /* there is a thing with altnative interface thing here... */ + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + Log2(("%s: usbProxyOs2AsyncThread: BulkRead %d\n", pUrb->pszDesc, cbData)); + rc = g_pfnUsbBulkRead2(pDevOs2->hDevice, pUrb->EndPt | 0x80, 0, !pUrb->fShortNotOk, &cbData, pbData, 500);//5*6000); + } + else + { + Log2(("%s: usbProxyOs2AsyncThread: BulkWrite %d\n", pUrb->pszDesc, cbData)); + rc = g_pfnUsbBulkWrite2(pDevOs2->hDevice, pUrb->EndPt, 0, !pUrb->fShortNotOk, cbData, pbData, 500);//5*6000); + } + break; + } + + case VUSBXFERTYPE_INTR: + case VUSBXFERTYPE_ISOC: + default: + Log2(("%s: usbProxyOs2AsyncThread: Unsupported\n", pUrb->pszDesc)); + rc = USB_IORB_FAILED; + break; + } + + /* unbuffer */ + if (pbData == pvLow) + memcpy(pUrb->abData, pbData, pUrb->cbData); + + /* Convert rc to USB status code. */ + int orc = rc; + if (!rc) + pUrb->enmStatus = VUSBSTATUS_OK; + else if (rc == USB_ERROR_LESSTRANSFERED && !pUrb->fShortNotOk) + { + Assert(pUrb->cbData >= cbData); + pUrb->cbData = cbData; + pUrb->enmStatus = VUSBSTATUS_DATA_UNDERRUN; + } + else + pUrb->enmStatus = VUSBSTATUS_STALL; + Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d \n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc); + + /* + * Retire it to the completed list + */ + RTCritSectEnter(&pDevOs2->CritSect); + + pUrbOs2->pNext = NULL; + pUrbOs2->pPrev = pDevOs2->pTaxingTail; + if (pDevOs2->pTaxingTail) + pDevOs2->pTaxingTail->pNext = pUrbOs2; + else + pDevOs2->pTaxingHead = pUrbOs2; + pDevOs2->pTaxingTail = pUrbOs2; + + RTSemEventSignal(pDevOs2->EventSyncWait); + Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d!\n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc); + } + else + { + RTThreadUserReset(Thread); + RTCritSectLeave(&pDevOs2->CritSect); + + /* + * Wait for something to do. + */ + RTThreadUserWait(Thread, 30*1000 /* 30 sec */); + + RTCritSectEnter(&pDevOs2->CritSect); + } + } + + RTCritSectLeave(&pDevOs2->CritSect); + if (pvLow) + DosFreeMem(pvLow); + return VINF_SUCCESS; +} + + +/** + * Opens the /proc/bus/usb/bus/addr file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The path to the device. + */ +static int usbProxyOs2Open(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlow(("usbProxyOs2Open: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + int rc; + + /* + * Lazy init. + */ +#ifdef DYNAMIC_USBCALLS + if (!g_pfnUsbOpen) + { + rc = usbProxyOs2GlobalInit(); + if (RT_FAILURE(rc)) + return rc; + } +#else +static bool g_fInitialized = false; + if (!g_fInitialized) + { + rc = InitUsbCalls(); + if (rc != NO_ERROR) + return RTErrConvertFromOS2(rc); + g_fInitialized = true; + } +#endif + + /* + * Parse out the open parameters from the address string. + */ + uint16_t idProduct = 0; + uint16_t idVendor = 0; + uint16_t bcdDevice = 0; + uint32_t iEnum = 0; + const char *psz = pszAddress; + do + { + const char chValue = *psz; + AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR); + uint64_t u64Value; + int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value); + AssertReleaseRCReturn(rc, rc); + AssertReleaseReturn(!*psz || *psz == ';', rc); + switch (chValue) + { + case 'p': idProduct = (uint16_t)u64Value; break; + case 'v': idVendor = (uint16_t)u64Value; break; + case 'r': bcdDevice = (uint16_t)u64Value; break; + case 'e': iEnum = (uint16_t)u64Value; break; + default: + AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR); + } + if (*psz == ';') + psz++; + } while (*psz); + + + /* + * Try open (acquire) it. + */ + USBHANDLE hDevice = 0; + int urc = rc = g_pfnUsbOpen(&hDevice, idVendor, idProduct, bcdDevice, iEnum); + if (!rc) + { + /* + * Allocate and initialize the OS/2 backend data. + */ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)RTMemAllocZ(sizeof(*pDevOs2)); + if (pDevOs2) + { + pDevOs2->hDevice = hDevice; + pDevOs2->fTerminate = false; + rc = RTCritSectInit(&pDevOs2->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pDevOs2->EventSyncWait); + if (RT_SUCCESS(rc)) + { + pProxyDev->Backend.pv = pDevOs2; + + /** @todo + * Determine the active configuration. + */ + //pProxyDev->cIgnoreSetConfigs = 1; + //pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + + /* + * Create the async worker thread and we're done. + */ + rc = RTThreadCreate(&pDevOs2->Thread, usbProxyOs2AsyncThread, pProxyDev, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "usbproxy"); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyOs2Open(%p, %s): returns successfully - iActiveCfg=%d\n", + pProxyDev, pszAddress, pProxyDev->iActiveCfg)); + return VINF_SUCCESS; + } + + /* failure */ + RTSemEventDestroy(pDevOs2->EventSyncWait); + } + RTCritSectDelete(&pDevOs2->CritSect); + } + RTMemFree(pDevOs2); + } + else + rc = VERR_NO_MEMORY; + g_pfnUsbClose(hDevice); + } + else + rc = VERR_VUSB_USBFS_PERMISSION; /** @todo fix me */ + + Log(("usbProxyOs2Open(%p, %s) failed, rc=%Rrc! urc=%d\n", pProxyDev, pszAddress, rc, urc)); NOREF(urc); + pProxyDev->Backend.pv = NULL; + + NOREF(pvBackend); + return rc; +} + + +/** + * Closes the proxy device. + */ +static void usbProxyOs2Close(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyOs2Close: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + Assert(pDevOs2); + if (!pDevOs2) + return; + + /* + * Tell the thread to terminate. + */ + ASMAtomicXchgBool(&pDevOs2->fTerminate, true); + int rc = RTThreadUserSignal(pDevOs2->Thread); AssertRC(rc); + rc = RTThreadWait(pDevOs2->Thread, 60*1000 /* 1 min */, NULL); AssertRC(rc); + + /* + * Now we can free all the resources and close the device. + */ + RTCritSectDelete(&pDevOs2->CritSect); + RTSemEventDestroy(pDevOs2->EventSyncWait); + + Assert(!pDevOs2->pInFlightHead); + Assert(!pDevOs2->pTodoHead); + Assert(!pDevOs2->pTodoTail); + Assert(!pDevOs2->pTaxingHead); + Assert(!pDevOs2->pTaxingTail); + + PUSBPROXYURBOS2 pUrbOs2; + while ((pUrbOs2 = pDevOs2->pFreeHead) != NULL) + { + pDevOs2->pFreeHead = pUrbOs2->pNext; + RTMemFree(pUrbOs2); + } + + g_pfnUsbClose(pDevOs2->hDevice); + pDevOs2->hDevice = 0; + + RTMemFree(pDevOs2); + pProxyDev->Backend.pv = NULL; + LogFlow(("usbProxyOs2Close: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset} */ +static int usbProxyOs2Reset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + return VINF_SUCCESS; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static int usbProxyOs2SetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + /* + * This is sync - bad. + */ + int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, + 0x00, /* bmRequestType - ?? */ + 0x09, /* bRequest - ?? */ + iCfg, /* wValue - configuration */ + 0, /* wIndex*/ + 0, /* wLength */ + NULL, /* pvData */ + 50 /* Timeout (ms) */); + if (rc) + LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#X -> rc=%d\n", pProxyDev->pUsbIns->pszName, iCfg, rc)); + return rc == 0; +} + + +/** + * Claims an interface. + * @returns success indicator. + */ +static int usbProxyOs2ClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + LogFlow(("usbProxyOs2ClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return true; +} + + +/** + * Releases an interface. + * @returns success indicator. + */ +static int usbProxyOs2ReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + LogFlow(("usbProxyOs2ReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return true; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static int usbProxyOs2SetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + LogFlow(("usbProxyOs2SetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt)); + return true; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static bool usbProxyOs2ClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%x\n", pProxyDev->pUsbIns->pszName, EndPt)); + + /* + * This is sync - bad. + */ + int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, + 0x02, /* bmRequestType - ?? */ + 0x01, /* bRequest - ?? */ + 0, /* wValue - endpoint halt */ + EndPt, /* wIndex - endpoint # */ + 0, /* wLength */ + NULL, /* pvData */ + 50 /* Timeout (ms) */); + if (rc) + LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%u -> rc=%d\n", pProxyDev->pUsbIns->pszName, EndPt, rc)); + return rc == 0; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static int usbProxyOs2UrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2UrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Quickly validate the input. + */ + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + case VUSBDIRECTION_OUT: + break; + default: + AssertMsgFailed(("usbProxyOs2UrbQueue: Invalid direction %d\n", pUrb->enmDir)); + return false; + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + break; + case VUSBXFERTYPE_BULK: + break; +/// @todo case VUSBXFERTYPE_INTR: +// break; +// case VUSBXFERTYPE_ISOC: +// break; + default: + return false; + } + + /* + * Allocate an OS/2 urb tracking structure, initialize it, + * add it to the todo list, and wake up the async thread. + */ + PUSBPROXYURBOS2 pUrbOs2 = usbProxyOs2UrbAlloc(pProxyDev); + if (!pUrbOs2) + return false; + + pUrbOs2->pUrb = pUrb; + + RTCritSectEnter(&pDevOs2->CritSect); + + pUrbOs2->pNext = NULL; + pUrbOs2->pPrev = pDevOs2->pTodoTail; + if (pDevOs2->pTodoTail) + pDevOs2->pTodoTail->pNext = pUrbOs2; + else + pDevOs2->pTodoHead = pUrbOs2; + pDevOs2->pTodoTail = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); + + RTThreadUserSignal(pDevOs2->Thread); + return true; +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static PVUSBURB usbProxyOs2UrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PVUSBURB pUrb = NULL; + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + + RTCritSectEnter(&pDevOs2->CritSect); + for (;;) + { + /* + * Any URBs pending delivery? + */ + PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTaxingHead; + if (pUrbOs2) + { + pUrb = pUrbOs2->pUrb; + usbProxyOs2UrbFree(pProxyDev, pUrbOs2); + break; + } + + /* + * Block for something to completed, if requested and sensible. + */ + if (!cMillies) + break; + if ( !pDevOs2->pInFlightHead + && !pDevOs2->pTodoHead) + break; + + RTCritSectLeave(&pDevOs2->CritSect); + + int rc = RTSemEventWait(pDevOs2->EventSyncWait, cMillies); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc); + cMillies = 0; + + RTCritSectEnter(&pDevOs2->CritSect); + } + RTCritSectLeave(&pDevOs2->CritSect); + + LogFlow(("usbProxyOs2UrbReap: dev=%s returns %p\n", pProxyDev->pUsbIns->pszName, pUrb)); + return pUrb; +} + + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static void usbProxyOs2UrbCancel(PVUSBURB pUrb) +{ +#if 0 + PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pUrb->pDev; + PUSBPROXYURBOS2 pUrbOs2 = (PUSBPROXYURBOS2)pUrb->Dev.pvProxyUrb; + if (pUrbOs2->pSplitHead) + { + /* split */ + Assert(pUrbOs2 == pUrbOs2->pSplitHead); + for (PUSBPROXYURBOS2 pCur = pUrbOs2; pCur; pCur = pCur->pSplitNext) + { + if (pCur->fSplitElementReaped) + continue; + if ( !usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX) + || errno == ENOENT) + continue; + if (errno == ENODEV) + break; + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n", + pUrb, errno, pProxyDev->pUsbIns->pszName)); + } + } + else + { + /* unsplit */ + if ( usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbOs2->KUrb, true, UINT32_MAX) + && errno != ENODEV /* deal with elsewhere. */ + && errno != ENOENT) + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n", + pUrb, errno, pProxyDev->pUsbIns->pszName)); + } +#endif +} + + +/** + * The Linux USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + "host", + usbProxyOs2Open, + NULL, + usbProxyOs2Close, + usbProxyOs2Reset, + usbProxyOs2SetConfig, + usbProxyOs2ClaimInterface, + usbProxyOs2ReleaseInterface, + usbProxyOs2SetInterface, + usbProxyOs2ClearHaltedEp, + usbProxyOs2UrbQueue, + usbProxyOs2UrbCancel, + usbProxyOs2UrbReap, + 0 +}; + diff --git a/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp new file mode 100644 index 00000000..54e130d2 --- /dev/null +++ b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp @@ -0,0 +1,918 @@ +/* $Id: USBProxyDevice-solaris.cpp $ */ +/** @file + * USB device proxy - the Solaris backend. + */ + +/* + * Copyright (C) 2009-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <sys/poll.h> +#include <errno.h> +#include <strings.h> +#include <limits.h> + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/string.h> +#include <iprt/critsect.h> +#include <iprt/time.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/pipe.h> +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Log Prefix. */ +#define USBPROXY "USBProxy" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Wrapper around the solaris urb request structure. + * This is required to track in-flight and landed URBs. + */ +typedef struct USBPROXYURBSOL +{ + /** Pointer to the Solaris device. */ + struct USBPROXYDEVSOL *pDevSol; + /** Pointer to the VUSB URB (set to NULL if canceled). */ + PVUSBURB pVUsbUrb; + /** Pointer to the next solaris URB. */ + struct USBPROXYURBSOL *pNext; + /** Pointer to the previous solaris URB. */ + struct USBPROXYURBSOL *pPrev; +} USBPROXYURBSOL, *PUSBPROXYURBSOL; + +/** + * Data for the solaris usb proxy backend. + */ +typedef struct USBPROXYDEVSOL +{ + /** Path of the USB device in the devices tree (persistent). */ + char *pszDevicePath; + /** The connection to the client driver. */ + RTFILE hFile; + /** Pointer to the proxy device instance. */ + PUSBPROXYDEV pProxyDev; + /** Critical section protecting the two lists. */ + RTCRITSECT CritSect; + /** The list of free solaris URBs. Singly linked. */ + PUSBPROXYURBSOL pFreeHead; + /** The list of active solaris URBs. Doubly linked. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. */ + PUSBPROXYURBSOL pInFlightHead; + /** The list of landed solaris URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBSOL pTaxingHead; + /** The tail of the landed solaris URBs. */ + PUSBPROXYURBSOL pTaxingTail; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; +} USBPROXYDEVSOL, *PUSBPROXYDEVSOL; + +static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol); + + +/** + * Allocates a Solaris URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * + * @param pDevSol The solaris USB device. + */ +static PUSBPROXYURBSOL usbProxySolarisUrbAlloc(PUSBPROXYDEVSOL pDevSol) +{ + PUSBPROXYURBSOL pUrbSol; + + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Try remove a Solaris URB from the free list, if none there allocate a new one. + */ + pUrbSol = pDevSol->pFreeHead; + if (pUrbSol) + pDevSol->pFreeHead = pUrbSol->pNext; + else + { + RTCritSectLeave(&pDevSol->CritSect); + pUrbSol = (PUSBPROXYURBSOL)RTMemAlloc(sizeof(*pUrbSol)); + if (!pUrbSol) + return NULL; + RTCritSectEnter(&pDevSol->CritSect); + } + pUrbSol->pVUsbUrb = NULL; + pUrbSol->pDevSol = pDevSol; + + /* + * Link it into the active list + */ + pUrbSol->pPrev = NULL; + pUrbSol->pNext = pDevSol->pInFlightHead; + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol; + pDevSol->pInFlightHead = pUrbSol; + + RTCritSectLeave(&pDevSol->CritSect); + return pUrbSol; +} + + +/** + * Frees a Solaris URB request structure. + * + * @param pDevSol The Solaris USB device. + * @param pUrbSol The Solaris URB to free. + */ +static void usbProxySolarisUrbFree(PUSBPROXYDEVSOL pDevSol, PUSBPROXYURBSOL pUrbSol) +{ + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Remove from the active or taxing list. + */ + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol->pPrev; + else if (pDevSol->pTaxingTail == pUrbSol) + pDevSol->pTaxingTail = pUrbSol->pPrev; + + if (pUrbSol->pPrev) + pUrbSol->pPrev->pNext = pUrbSol->pNext; + else if (pDevSol->pTaxingHead == pUrbSol) + pDevSol->pTaxingHead = pUrbSol->pNext; + else if (pDevSol->pInFlightHead == pUrbSol) + pDevSol->pInFlightHead = pUrbSol->pNext; + else + AssertFailed(); + + /* + * Link it into the free list. + */ + pUrbSol->pPrev = NULL; + pUrbSol->pNext = pDevSol->pFreeHead; + pDevSol->pFreeHead = pUrbSol; + + pUrbSol->pVUsbUrb = NULL; + pUrbSol->pDevSol = NULL; + + RTCritSectLeave(&pDevSol->CritSect); +} + + +/* + * Close the connection to the USB client driver. + * + * This is required because our userland enumeration relies on drivers/device trees + * to recognize active devices, and hence if this device is unplugged we should no + * longer keep the client driver loaded. + */ +static void usbProxySolarisCloseFile(PUSBPROXYDEVSOL pDevSol) +{ + RTFileClose(pDevSol->hFile); + pDevSol->hFile = NIL_RTFILE; +} + + +/** + * The client driver IOCtl Wrapper function. + * + * @returns VBox status code. + * @param pDevSol The Solaris device instance. + * @param Function The Function. + * @param pvData Opaque pointer to the data. + * @param cbData Size of the data pointed to by pvData. + */ +static int usbProxySolarisIOCtl(PUSBPROXYDEVSOL pDevSol, unsigned Function, void *pvData, size_t cbData) +{ + if (RT_UNLIKELY(pDevSol->hFile == NIL_RTFILE)) + { + LogFlow((USBPROXY ":usbProxySolarisIOCtl: Connection to driver gone!\n")); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VBOXUSBREQ Req; + Req.u32Magic = VBOXUSB_MAGIC; + Req.rc = -1; + Req.cbData = cbData; + Req.pvDataR3 = pvData; + + int Ret = -1; + int rc = RTFileIoCtl(pDevSol->hFile, Function, &Req, sizeof(Req), &Ret); + if (RT_SUCCESS(rc)) + { + if (RT_FAILURE(Req.rc)) + { + if (Req.rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + pDevSol->pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + LogRel((USBPROXY ": Command %#x failed, USB Device '%s' disconnected!\n", Function, + pDevSol->pProxyDev->pUsbIns->pszName)); + } + else + LogRel((USBPROXY ": Command %#x failed. Req.rc=%Rrc\n", Function, Req.rc)); + } + + return Req.rc; + } + + LogRel((USBPROXY ": Function %#x failed. rc=%Rrc\n", Function, rc)); + return rc; +} + + +/** + * Get the active configuration from the device. The first time this is called + * our client driver would returned the cached configuration since the device is first plugged in. + * Subsequent get configuration requests are passed on to the device. + * + * @returns VBox status code. + * @param pDevSol The Solaris device instance. + * + */ +static inline int usbProxySolarisGetActiveConfig(PUSBPROXYDEVSOL pDevSol) +{ + VBOXUSBREQ_GET_CONFIG GetConfigReq; + bzero(&GetConfigReq, sizeof(GetConfigReq)); + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_CONFIG, &GetConfigReq, sizeof(GetConfigReq)); + if (RT_SUCCESS(rc)) + { + pDevSol->pProxyDev->iActiveCfg = GetConfigReq.bConfigValue; + pDevSol->pProxyDev->cIgnoreSetConfigs = 0; + } + else + { + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": Failed to get configuration. rc=%Rrc\n", rc)); + + pDevSol->pProxyDev->iActiveCfg = -1; + pDevSol->pProxyDev->cIgnoreSetConfigs = 0; + } + return rc; +} + + +/** + * Opens the USB device. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The unique device identifier. + * The format of this string is "VendorId:ProducIt:Release:StaticPath". + */ +static DECLCALLBACK(int) usbProxySolarisOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + LogFlowFunc((USBPROXY ":usbProxySolarisOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Initialize our USB R3 lib. + */ + int rc = USBLibInit(); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize the solaris backend data. + */ + AssertCompile(PATH_MAX >= MAXPATHLEN); + char szDeviceIdent[PATH_MAX+48]; + rc = RTStrPrintf(szDeviceIdent, sizeof(szDeviceIdent), "%s", pszAddress); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pDevSol->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Create wakeup pipe. + */ + rc = RTPipeCreate(&pDevSol->hPipeWakeupR, &pDevSol->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + int Instance; + char *pszDevicePath = NULL; + rc = USBLibGetClientInfo(szDeviceIdent, &pszDevicePath, &Instance); + if (RT_SUCCESS(rc)) + { + pDevSol->pszDevicePath = pszDevicePath; + + /* + * Open the client driver. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pDevSol->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + pDevSol->hFile = hFile; + pDevSol->pProxyDev = pProxyDev; + + /* + * Verify client driver version. + */ + VBOXUSBREQ_GET_VERSION GetVersionReq; + bzero(&GetVersionReq, sizeof(GetVersionReq)); + rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_VERSION, &GetVersionReq, sizeof(GetVersionReq)); + if (RT_SUCCESS(rc)) + { + if ( GetVersionReq.u32Major == VBOXUSB_VERSION_MAJOR + && GetVersionReq.u32Minor >= VBOXUSB_VERSION_MINOR) + { + /* + * Try & get the current cached config from Solaris. + */ + usbProxySolarisGetActiveConfig(pDevSol); + return VINF_SUCCESS; + } + else + { + LogRel((USBPROXY ": Version mismatch, Driver v%d.%d expecting ~v%d.%d\n", GetVersionReq.u32Major, + GetVersionReq.u32Minor, VBOXUSB_VERSION_MAJOR, VBOXUSB_VERSION_MINOR)); + rc = VERR_VERSION_MISMATCH; + } + } + else + LogRel((USBPROXY ": Failed to query driver version. rc=%Rrc\n", rc)); + + RTFileClose(pDevSol->hFile); + pDevSol->hFile = NIL_RTFILE; + pDevSol->pProxyDev = NULL; + } + else + LogRel((USBPROXY ": Failed to open device. rc=%Rrc pszDevicePath=%s\n", rc, pDevSol->pszDevicePath)); + + RTStrFree(pDevSol->pszDevicePath); + pDevSol->pszDevicePath = NULL; + } + else + { + LogRel((USBPROXY ": Failed to get client info. rc=%Rrc szDeviceIdent=%s\n", rc, szDeviceIdent)); + if (rc == VERR_NOT_FOUND) + rc = VERR_OPEN_FAILED; + } + RTPipeClose(pDevSol->hPipeWakeupR); + RTPipeClose(pDevSol->hPipeWakeupW); + } + + RTCritSectDelete(&pDevSol->CritSect); + } + else + LogRel((USBPROXY ": RTCritSectInit failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress)); + } + else + LogRel((USBPROXY ": RTStrAPrintf failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress)); + } + else + LogRel((USBPROXY ": USBLibInit failed. rc=%Rrc\n", rc)); + + USBLibTerm(); + return rc; +} + + +/** + * Close the USB device. + * + * @param pProxyDev The device instance. + */ +static DECLCALLBACK(void) usbProxySolarisClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow((USBPROXY ":usbProxySolarisClose: pProxyDev=%p\n", pProxyDev)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* Close the device (do not re-enumerate). */ + VBOXUSBREQ_CLOSE_DEVICE CloseReq; + CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_CLOSE; + usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq)); + + pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + + /* + * Now we can close it and free all the resources. + */ + RTCritSectDelete(&pDevSol->CritSect); + + PUSBPROXYURBSOL pUrbSol = NULL; + while ((pUrbSol = pDevSol->pInFlightHead) != NULL) + { + pDevSol->pInFlightHead = pUrbSol->pNext; + RTMemFree(pUrbSol); + } + + while ((pUrbSol = pDevSol->pFreeHead) != NULL) + { + pDevSol->pFreeHead = pUrbSol->pNext; + RTMemFree(pUrbSol); + } + + RTPipeClose(pDevSol->hPipeWakeupR); + RTPipeClose(pDevSol->hPipeWakeupW); + + RTStrFree(pDevSol->pszDevicePath); + pDevSol->pszDevicePath = NULL; + + USBLibTerm(); +} + + +/** + * Reset the device. + * + * @returns VBox status code. + * @param pProxyDev The device to reset. + * @param fRootHubReset Is this a root hub reset or device specific reset request. + */ +static DECLCALLBACK(int) usbProxySolarisReset(PUSBPROXYDEV pProxyDev, bool fRootHubReset) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisReset: pProxyDev=%s fRootHubReset=%d\n", pProxyDev->pUsbIns->pszName, fRootHubReset)); + + /** Pass all resets to the device. The Trekstor USB (1.1) stick requires this to work. */ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* Soft reset the device. */ + VBOXUSBREQ_CLOSE_DEVICE CloseReq; + CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_SOFT; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq)); + if (RT_SUCCESS(rc)) + { + /* Get the active config. Solaris USBA sets a default config. */ + usbProxySolarisGetActiveConfig(pDevSol); + } + else if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisReset: Failed! rc=%d\n", rc)); + + return rc; +} + + +/** + * Set the active configuration. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration value to set. + */ +static DECLCALLBACK(int) usbProxySolarisSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisSetConfig: pProxyDev=%p iCfg=%#x\n", pProxyDev, iCfg)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_SET_CONFIG SetConfigReq; + SetConfigReq.bConfigValue = iCfg; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_CONFIG, &SetConfigReq, sizeof(SetConfigReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisSetConfig: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Claims an interface. + * + * This is a stub on Solaris since we release/claim all interfaces at + * as and when required with endpoint opens. + * + * @returns success indicator (always true). + */ +static DECLCALLBACK(int) usbProxySolarisClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * + * This is a stub on Solaris since we release/claim all interfaces at + * as and when required with endpoint opens. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxySolarisReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + return VINF_SUCCESS; +} + + +/** + * Specify an alternate setting for the specified interface of the current configuration. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxySolarisSetInterface(PUSBPROXYDEV pProxyDev, int bIf, int bAlt) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisSetInterface: pProxyDev=%p bIf=%#x iAlt=%#x\n", pProxyDev, bIf, bAlt)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_SET_INTERFACE SetInterfaceReq; + SetInterfaceReq.bInterface = bIf; + SetInterfaceReq.bAlternate = bAlt; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_INTERFACE, &SetInterfaceReq, sizeof(SetInterfaceReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisSetInterface: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxySolarisClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisClearHaltedEp: pProxyDev=%p EndPt=%#x\n", pProxyDev, EndPt)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_CLEAR_EP ClearEpReq; + ClearEpReq.bEndpoint = EndPt; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLEAR_EP, &ClearEpReq, sizeof(ClearEpReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisClearHaltedEp: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxySolarisUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + LogFlowFunc((USBPROXY ": usbProxySolarisUrbQueue: pProxyDev=%s pUrb=%p pszDesc=%s EndPt=%#x enmDir=%d cbData=%d pvData=%p\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->pszDesc, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, pUrb->abData)); + + PUSBPROXYURBSOL pUrbSol = usbProxySolarisUrbAlloc(pDevSol); + if (RT_UNLIKELY(!pUrbSol)) + { + LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed to allocate URB\n")); + return VERR_NO_MEMORY; + } + + pUrbSol->pVUsbUrb = pUrb; + pUrbSol->pDevSol = pDevSol; + + uint8_t EndPt = pUrb->EndPt; + if (EndPt) + EndPt |= pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE; + + VBOXUSBREQ_URB UrbReq; + UrbReq.pvUrbR3 = pUrbSol; + UrbReq.bEndpoint = EndPt; + UrbReq.enmType = pUrb->enmType; + UrbReq.enmDir = pUrb->enmDir; + UrbReq.enmStatus = pUrb->enmStatus; + UrbReq.fShortOk = !pUrb->fShortNotOk; + UrbReq.cbData = pUrb->cbData; + UrbReq.pvData = &pUrb->abData[0]; + + Log6((USBPROXY ": Sending: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + UrbReq.cIsocPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + UrbReq.aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cb; + UrbReq.aIsocPkts[i].cbActPkt = 0; + UrbReq.aIsocPkts[i].enmStatus = VUSBSTATUS_INVALID; + } + } + + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SEND_URB, &UrbReq, sizeof(UrbReq)); + if (RT_SUCCESS(rc)) + { + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + LogFlow((USBPROXY ":usbProxySolarisUrbQueue: Success cbData=%d\n", pUrb->cbData)); + pUrb->Dev.pvPrivate = pUrbSol; + return VINF_SUCCESS; + } + + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + { + LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed! pProxyDev=%s pUrb=%p EndPt=%#x bEndpoint=%#x enmType=%d " + "enmDir=%d cbData=%u rc=%Rrc\n", pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, + UrbReq.bEndpoint, pUrb->enmType, pUrb->enmDir, pUrb->cbData, rc)); + } + + return rc; +} + + +/** + * Cancels a URB. + * + * The URB requires reaping, so we don't change its state. + * @remark There isn't any way to cancel a specific asynchronous request + * on Solaris. So we just abort pending URBs on the pipe. + */ +static DECLCALLBACK(int) usbProxySolarisUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)pUrb->Dev.pvPrivate; + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + LogFlowFunc((USBPROXY ": usbProxySolarisUrbCancel: pUrb=%p pUrbSol=%p pDevSol=%p\n", pUrb, pUrbSol, pUrbSol->pDevSol)); + + /* Aborting the control pipe isn't supported, pretend success. */ + if (!pUrb->EndPt) + return VINF_SUCCESS; + + VBOXUSBREQ_ABORT_PIPE AbortPipeReq; + AbortPipeReq.bEndpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE); + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_ABORT_PIPE, &AbortPipeReq, sizeof(AbortPipeReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisUrbCancel: Failed to abort pipe. rc=%Rrc\n", rc)); + + LogFlow((USBPROXY ": usbProxySolarisUrbCancel: returns rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxySolarisUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlowFunc((USBPROXY ":usbProxySolarisUrbReap pProxyDev=%p cMillies=%u\n", pProxyDev, cMillies)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* + * Don't block if nothing is in the air. + */ + if (!pDevSol->pInFlightHead) + return NULL; + + /* + * Deque URBs inflight or those landed. + */ + if (cMillies > 0) + { + for (;;) + { + int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : (int)cMillies; + + struct pollfd aFd[2]; + size_t const cFds = RT_ELEMENTS(aFd); + + aFd[0].fd = RTFileToNative(pDevSol->hFile); + aFd[0].events = POLLIN; + aFd[0].revents = 0; + + aFd[1].fd = RTPipeToNative(pDevSol->hPipeWakeupR); + aFd[1].events = POLLIN; + aFd[1].revents = 0; + + int rc = poll(&aFd[0], cFds, cMilliesWait); + if (rc > 0) + { + if (aFd[0].revents & POLLHUP) + { + LogRel((USBPROXY ": USB Device '%s' disconnected!\n", pDevSol->pProxyDev->pUsbIns->pszName)); + pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + } + + if (aFd[1].revents & POLLIN) + { + /* Got woken up, drain pipe. */ + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevSol->hPipeWakeupR, &bRead, 1, &cbIgnored); + + /* + * It is possible that we got woken up and have an URB pending + * for completion. Do it on the way out. Otherwise return + * immediately to the caller. + */ + if (!(aFd[0].revents & POLLIN)) + return NULL; + } + break; + } + else if (rc == 0) + return NULL; + else if (errno != EAGAIN) + { + LogFlow((USBPROXY ":usbProxySolarisUrbReap Poll rc=%d errno=%d\n", rc, errno)); + return NULL; + } + } + } + + usbProxySolarisUrbComplete(pDevSol); + + /* + * Any URBs pending delivery? + */ + PVUSBURB pUrb = NULL; + while ( pDevSol->pTaxingHead + && !pUrb) + { + RTCritSectEnter(&pDevSol->CritSect); + + PUSBPROXYURBSOL pUrbSol = pDevSol->pTaxingHead; + if (pUrbSol) + { + pUrb = pUrbSol->pVUsbUrb; + if (pUrb) + { + /* + * Remove it from the taxing list and move it to the free list. + */ + pUrb->Dev.pvPrivate = NULL; + usbProxySolarisUrbFree(pDevSol, pUrbSol); + } + } + RTCritSectLeave(&pDevSol->CritSect); + } + + return pUrb; +} + + +/** + * Reads a completed/error'd URB from the client driver (no waiting). + * + * @param pDevSol The Solaris device instance. + */ +static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisUrbComplete: pDevSol=%p\n", pDevSol)); + + VBOXUSBREQ_URB UrbReq; + bzero(&UrbReq, sizeof(UrbReq)); + + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_REAP_URB, &UrbReq, sizeof(UrbReq)); + if (RT_SUCCESS(rc)) + { + if (UrbReq.pvUrbR3) + { + PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)UrbReq.pvUrbR3; + PVUSBURB pUrb = pUrbSol->pVUsbUrb; + if (RT_LIKELY(pUrb)) + { + Assert(pUrb->u32Magic == VUSBURB_MAGIC); + + /* + * Update the URB. + */ + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + size_t cbData = 0; + for (unsigned i = 0; i < UrbReq.cIsocPkts; i++) + { + pUrb->aIsocPkts[i].cb = UrbReq.aIsocPkts[i].cbActPkt; + cbData += UrbReq.aIsocPkts[i].cbActPkt; + pUrb->aIsocPkts[i].enmStatus = UrbReq.aIsocPkts[i].enmStatus; + } + + LogFlow((USBPROXY ":usbProxySolarisUrbComplete: Isoc cbData=%d cbActPktSum=%d\n", pUrb->cbData, cbData)); + pUrb->cbData = cbData; + pUrb->enmStatus = UrbReq.enmStatus; + } + else + { + pUrb->cbData = UrbReq.cbData; + pUrb->enmStatus = UrbReq.enmStatus; + } + + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Remove from the active list. + */ + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol->pPrev; + if (pUrbSol->pPrev) + pUrbSol->pPrev->pNext = pUrbSol->pNext; + else + { + Assert(pDevSol->pInFlightHead == pUrbSol); + pDevSol->pInFlightHead = pUrbSol->pNext; + } + + /* + * Add to the tail of the taxing list. + */ + pUrbSol->pNext = NULL; + pUrbSol->pPrev = pDevSol->pTaxingTail; + if (pDevSol->pTaxingTail) + pDevSol->pTaxingTail->pNext = pUrbSol; + else + pDevSol->pTaxingHead = pUrbSol; + pDevSol->pTaxingTail = pUrbSol; + + RTCritSectLeave(&pDevSol->CritSect); + + Log6((USBPROXY ": Reaping: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + if (pUrb->cbData < 1024) + Log6(("%.*Rhxd\n", pUrb->cbData, pUrb->abData)); + return pUrb; + } + } + } + else + { + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": Reaping URB failed. rc=%Rrc\n", rc)); + } + + return NULL; +} + + +static DECLCALLBACK(int) usbProxySolarisWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevSol->hPipeWakeupW, "", 1, &cbIgnored); +} + + +/** + * The Solaris USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVSOL), + usbProxySolarisOpen, + NULL, + usbProxySolarisClose, + usbProxySolarisReset, + usbProxySolarisSetConfig, + usbProxySolarisClaimInterface, + usbProxySolarisReleaseInterface, + usbProxySolarisSetInterface, + usbProxySolarisClearHaltedEp, + usbProxySolarisUrbQueue, + usbProxySolarisUrbCancel, + usbProxySolarisUrbReap, + usbProxySolarisWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/testcase/Makefile.kup b/src/VBox/Devices/USB/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/testcase/Makefile.kup diff --git a/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp new file mode 100644 index 00000000..060f58fa --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp @@ -0,0 +1,604 @@ +/* $Id: tstOhciRegisterAccess.cpp $ */ +/** @file + * tstOhciRegisterAccess - OHCI Register Access Tests / Experiments. + */ + +/* + * Copyright (C) 2011-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/memobj.h> +#include <iprt/string.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/param.h> + +#include <VBox/sup.h> +#undef LogRel +#define LogRel(a) SUPR0Printf a + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Register names. */ +static const char * const g_apszRegNms[] = +{ + /* 00 */ "HcRevision", + /* 01 */ "HcControl", + /* 02 */ "HcCommandStatus", + /* 03 */ "HcInterruptStatus", + /* 04 */ "HcInterruptEnable", + /* 05 */ "HcInterruptDisable", + /* 06 */ "HcHCCA", + /* 07 */ "HcPeriodCurrentED", + /* 08 */ "HcControlHeadED", + /* 09 */ "HcControlCurrentED", + /* 10 */ "HcBulkHeadED", + /* 11 */ "HcBulkCurrentED", + /* 12 */ "HcDoneHead", + /* 13 */ "HcFmInterval", + /* 14 */ "HcFmRemaining", + /* 15 */ "HcFmNumber", + /* 16 */ "HcPeriodicStart", + /* 17 */ "HcLSThreshold", + /* 18 */ "HcRhDescriptorA", + /* 19 */ "HcRhDescriptorB", + /* 20 */ "HcRhStatus", + /* Variable number of root hub ports: */ + /* 21 */ "HcRhPortStatus[0]", + /* 22 */ "HcRhPortStatus[1]", + /* 23 */ "HcRhPortStatus[2]", + /* 24 */ "HcRhPortStatus[3]", + /* 25 */ "HcRhPortStatus[4]", + /* 26 */ "HcRhPortStatus[5]", + /* 27 */ "HcRhPortStatus[6]", + /* 28 */ "HcRhPortStatus[7]" +}; + + +static bool TestOhciWrites(RTVPTRUNION uPtr) +{ + static struct + { + unsigned iReg; + uint32_t fMask; + uint32_t uVal1; + uint32_t uVal2; + } const s_aRegs[] = + { +#if 0 /* deadly when missing bytes are taken as zero. */ + { 13 /* HcFmInterval */, 0xffffffff, 0x58871120, 0x01010101 }, +#endif + { 16 /* HcPeriodicStart */, 0x00003fff, 0x01020304, 0x02010403 }, + { 17 /* HcLSThreshold */, 0x00000fff, 0xffffffff, 0x66666666 }, + { 10 /* HcBulkHeadED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */ + { 11 /* HcBulkCurrentED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */ + }; + + bool fSuccess = true; + for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++) + { + uint32_t const iReg = s_aRegs[i].iReg; + RTVPTRUNION uPtrReg; + uPtrReg.pu32 = &uPtr.pu32[iReg]; + + uint32_t uInitialValue = *uPtrReg.pu32; + LogRel(("TestOhciWrites: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + bool fTryAgain = true; + const char *pszError = NULL; + uint32_t u32A = 0; + uint32_t uChangedValue = 0; + uint32_t uExpectedValue = 0; + + for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++) + { + pszError = NULL; + fTryAgain = false; + u32A = 0; + uChangedValue = 0; + uExpectedValue = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtrReg.pu32; + + /* + * DWORD writes. + */ + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + *uPtrReg.pu32 = uInitialValue; + u32A = *uPtrReg.pu32; + uChangedValue = s_aRegs[i].uVal1 != uInitialValue ? s_aRegs[i].uVal1 : s_aRegs[i].uVal2; + if (u32A == uInitialValue) + { + /* Change the value. */ + *uPtrReg.pu32 = uChangedValue; + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = uChangedValue & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + pszError = "Writing changed value failed"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 1"; + } + } + else + pszError = "Writing back initial value failed"; + + /* + * Write aligned word changes. + */ + for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + /* Change the value. */ + uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0xffff) << iWord * 16) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + { + static const char * const s_apsz[] = { "word 0", "word 1" }; + pszError = s_apsz[iWord]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 2"; + } + } + + /* + * Write aligned word change. We have to keep within the register, + * unfortunately. + */ + if (!pszError && !fTryAgain) + { + fTryAgain = *uPtrReg.pu32 != uInitialValue; + if (!fTryAgain) + { + /* Change the value. */ + *(uint16_t volatile *)&uPtrReg.pu8[1] = (uint16_t)(uChangedValue >> 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0x00ffff00)) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + pszError = "Unaligned word access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 3"; + } + } + } + + /* + * Write byte changes. + */ + for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + /* Change the value. */ + uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0xff) << iByte * 8) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 4"; + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } + + /* + * Complain on failure. + */ + if (fTryAgain) + LogRel(("TestOhciWrites: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue)); + else if (pszError) + { + LogRel(("TestOhciWrites: Error! Register %s failed: %s; Initial=%08RX32 Changed=%08RX32 Expected=%08RX32 u32A=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, uExpectedValue, u32A)); + fSuccess = false; + } + } + + return fSuccess; +} + + +static bool TestOhciReadOnly(RTVPTRUNION uPtr) +{ + static struct + { + unsigned iReg; + uint32_t cValues; + uint32_t auValues[8]; + } const s_aRegs[] = + { + { 0 /* HcRevision */, 8, { 0, UINT32_MAX, 0x10100110, 0x200, 0x111, 0x11f, 0xf110, 0x0f10 } }, + { 12 /* HcDoneHead */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } }, + { 14 /* HcFmRemaining */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } }, + { 15 /* HcFmNumber */, 5, { 0, UINT32_MAX, 0x55555555, 0x7899, 0x00012222, 0, 0, 0 } }, +#if 0 /* HCD can write this */ + { 17 /* HcLSThreshold */, 5, { 0x627, 0x628, 0x629, 0x666, 0x599, 0, 0, 0 } } /* ??? */ +#endif + }; + + bool fSuccess = true; + for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++) + { + uint32_t const iReg = s_aRegs[i].iReg; + RTVPTRUNION uPtrReg; + uPtrReg.pu32 = &uPtr.pu32[iReg]; + + uint32_t uInitialValue = *uPtrReg.pu32; + LogRel(("TestOhciReadOnly: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + bool fTryAgain = true; + const char *pszError = NULL; + uint32_t uChangedValue = 0; + uint32_t u32A = 0; + + for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++) + { + pszError = NULL; + fTryAgain = false; + u32A = 0; + uChangedValue = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtrReg.pu32; + + /* + * Try aligned dword, word and byte writes for now. + */ + for (unsigned iValue = 0; iValue < s_aRegs[i].cValues && !pszError && !fTryAgain; iValue++) + { + uChangedValue = s_aRegs[i].auValues[iValue]; + if (uInitialValue == uChangedValue) + continue; + + /* dword */ + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + *uPtrReg.pu32 = uChangedValue; + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + pszError = "dword access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 1"; + } + + /* word */ + for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 2"; + } + } + + /* byte */ + for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 3"; + } + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } + + /* + * Complain on failure. + */ + if (fTryAgain) + LogRel(("TestOhciReadOnly: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue)); + else if (pszError) + { + LogRel(("TestOhciReadOnly: Error! Register %s failed: %s; uInitialValue=%08RX32 uChangedValue=%08RX32 u32A=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, u32A)); + fSuccess = false; + } + } + + return fSuccess; +} + + +static bool TestOhciReads(RTVPTRUNION uPtr) +{ + /* + * We can read just about any register we like since read shouldn't have + * any side effects. However, some registers are volatile and makes for + * difficult targets, thus the ugly code. + */ + bool fSuccess = true; + uint32_t cMaxReg = RT_ELEMENTS(g_apszRegNms); + for (uint32_t iReg = 0; iReg < cMaxReg; iReg++, uPtr.pu32++) + { + const char *pszError = NULL; + bool fDone = false; + uint32_t uInitialValue = *uPtr.pu32; + uint32_t u32A = 0; + uint32_t u32B = 0; + uint32_t u32C = 0; + LogRel(("TestOhciReads: %p iReg=%2d %20s = %08RX32\n", uPtr.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + for (uint32_t iTry = 0; !fDone && iTry < 1024; iTry++) + { + pszError = NULL; + fDone = true; + u32A = u32B = u32C = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtr.pu32; + + /* Test byte access. */ + for (unsigned iByte = 0; iByte < 4; iByte++) + { + u32A = *uPtr.pu32; + u32B = uPtr.pu8[iByte]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + static uint32_t const a_au32Masks[] = + { + UINT32_C(0xffffff00), UINT32_C(0xffff00ff), UINT32_C(0xff00ffff), UINT32_C(0x00ffffff) + }; + u32B <<= iByte * 8; + u32B |= uInitialValue & a_au32Masks[iByte]; + if (u32B != uInitialValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + break; + } + } + + /* Test aligned word access. */ + if (fDone) + { + for (unsigned iWord = 0; iWord < 2; iWord++) + { + u32A = *uPtr.pu32; + u32B = uPtr.pu16[iWord]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + u32B <<= iWord * 16; + u32B |= uInitialValue & (iWord == 0 ? UINT32_C(0xffff0000) : UINT32_C(0x0000ffff)); + if (u32B != uInitialValue) + { + pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access"; + break; + } + } + } + + /* Test unaligned word access. */ + if (fDone) + { + for (int iWord = (uPtr.u & HOST_PAGE_OFFSET_MASK) == 0; iWord < 3; iWord++) + { + u32A = *uPtr.pu32; + u32B = *(volatile uint16_t *)&uPtr.pu8[iWord * 2 - 1]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + switch (iWord) + { + case 0: u32B = (u32B >> 8) | (u32A & UINT32_C(0xffffff00)); break; + case 1: u32B = (u32B << 8) | (u32A & UINT32_C(0xff0000ff)); break; + case 2: u32B = (u32B << 24) | (u32A & UINT32_C(0x00ffffff)); break; + } + if (u32B != u32A) + { + static const char * const s_apsz[] = { "unaligned word 0", "unaligned word 1", "unaligned word 2" }; + pszError = s_apsz[iWord]; + break; + } + } + } + + /* Test unaligned dword access. */ + if (fDone) + { + for (int iByte = (uPtr.u & HOST_PAGE_OFFSET_MASK) == 0 ? 0 : -3; iByte < 4; iByte++) + { + u32A = *uPtr.pu32; + u32B = *(volatile uint32_t *)&uPtr.pu8[iByte]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + switch (iByte) + { + case -3: u32B = (u32B >> 24) | (uInitialValue & UINT32_C(0xffffff00)); break; + case -2: u32B = (u32B >> 16) | (uInitialValue & UINT32_C(0xffff0000)); break; + case -1: u32B = (u32B >> 8) | (uInitialValue & UINT32_C(0xff000000)); break; + case 0: break; + case 1: u32B = (u32B << 8) | (uInitialValue & UINT32_C(0x000000ff)); break; + case 2: u32B = (u32B << 16) | (uInitialValue & UINT32_C(0x0000ffff)); break; + case 3: u32B = (u32B << 24) | (uInitialValue & UINT32_C(0x00ffffff)); break; + + } + if (u32B != u32A) + { + static const char * const s_apsz[] = + { + "unaligned dword -3", "unaligned dword -2", "unaligned dword -1", + "unaligned dword 0", "unaligned dword 1", "unaligned dword 2", "unaligned dword 3" + }; + pszError = s_apsz[iByte + 3]; + break; + } + + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } /* try loop */ + + /* + * Complain on failure. + */ + if (!fDone) + LogRel(("TestOhciReads: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, u32C)); + else if (pszError) + { + LogRel(("TestOhciReads: Error! Register %s failed: %s; uInitialValue=%08RX32 u32B=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, u32B)); + fSuccess = false; + } + } + + return fSuccess; +} + + +int tstOhciRegisterAccess(RTHCPHYS HCPhysOHCI) +{ + LogRel(("tstOhciRegisterAccess: HCPhysOHCI=%RHp\n", HCPhysOHCI)); + + /* + * Map the OHCI registers so we can access them. + */ + RTR0MEMOBJ hMemObj; + int rc = RTR0MemObjEnterPhys(&hMemObj, HCPhysOHCI, HOST_PAGE_SIZE, RTMEM_CACHE_POLICY_MMIO); + if (RT_FAILURE(rc)) + { + LogRel(("tstOhciRegisterAccess: Failed to enter OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc)); + return rc; + } + RTR0MEMOBJ hMapObj; + rc = RTR0MemObjMapKernel(&hMapObj, hMemObj, (void *)-1, 0 /*uAlignment*/, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + { + RTVPTRUNION uPtr; + uPtr.pv = (void volatile *)RTR0MemObjAddress(hMapObj); + LogRel(("tstOhciRegisterAccess: mapping address %p\n", uPtr.pv)); + if (RT_VALID_PTR(uPtr.pv)) + { + LogRel(("tstOhciRegisterAccess: HcRevision=%#x\n", *uPtr.pu32)); + + /* + * Do the access tests. + */ + bool fSuccess = TestOhciReads(uPtr); + if (fSuccess) + fSuccess = TestOhciReadOnly(uPtr); + if (fSuccess) + fSuccess = TestOhciWrites(uPtr); + if (fSuccess) + LogRel(("tstOhciRegisterAccess: Success!\n")); + else + LogRel(("tstOhciRegisterAccess: Failed!\n")); + } + else + rc = VERR_INTERNAL_ERROR_2; + + /* + * Clean up. + */ + RTR0MemObjFree(hMapObj, false); + } + else + LogRel(("tstOhciRegisterAccess: Failed to map OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc)); + RTR0MemObjFree(hMemObj, false); + LogRel(("tstOhciRegisterAccess: returns %Rrc\n", rc)); + return rc; +} diff --git a/src/VBox/Devices/USB/testcase/tstPalmOne.c b/src/VBox/Devices/USB/testcase/tstPalmOne.c new file mode 100644 index 00000000..c5d224c7 --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstPalmOne.c @@ -0,0 +1,412 @@ +/* $Id: tstPalmOne.c $ */ +/** @file + * USB PalmOne testcase + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/usbdevice_fs.h> + + +/** @name USB Control message recipient codes (from spec) + * @{ */ +#define VUSB_TO_DEVICE 0x0 +#define VUSB_TO_INTERFACE 0x1 +#define VUSB_TO_ENDPOINT 0x2 +#define VUSB_TO_OTHER 0x3 +#define VUSB_RECIP_MASK 0x1f +/** @} */ + +/** @name USB control pipe setup packet structure (from spec) + * @{ */ +#define VUSB_REQ_SHIFT (5) +#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT) +#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT) +#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT) +#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT) +#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT) +/** @} */ + +#define VUSB_DIR_TO_HOST 0x80 +typedef struct vusb_setup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VUSBSETUP, *PVUSBSETUP; +typedef const VUSBSETUP *PCVUSBSETUP; + + +int g_fd; + +int bitch(const char *pszMsg) +{ + printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno)); + return 1; +} + +void hex(const void *pv, ssize_t cb, const char *pszWhat) +{ + printf("%s: cb=%d\n", pszWhat, cb); + unsigned char *pb = (unsigned char *)pv; + int cch = 0; + int off = 0; + int cchPrecision = 16; + while (off < cb) + { + int i; + printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off); + + for (i = 0; i < cchPrecision && off + i < cb; i++) + printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]); + while (i++ < cchPrecision) + printf(" "); + printf(" "); + for (i = 0; i < cchPrecision && off + i < cb; i++) + { + uint8_t u8 = pb[i]; + fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout); + } + + /* next */ + pb += cchPrecision; + off += cchPrecision; + } + printf("\n"); +} + +int doioctl(int iCmd, void *pvData, const char *pszWho) +{ + int rc; + do + { + errno = 0; + rc = ioctl(g_fd, iCmd, pvData); + + } while (rc && errno == EAGAIN); + if (rc) + printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno)); + else + printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd); + return rc; +} + +int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_bulktransfer BulkMsg = {0}; + + BulkMsg.ep = EndPt; + BulkMsg.timeout = 1000; + BulkMsg.len = cbBuf; + BulkMsg.data = pvBuf; + int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho); +// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf); + if (rc >= 0) + return rc; + return -1; +#endif +} + +int send_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + return dobulk(EndPt, pvBuf, cbBuf, "send_bulk"); +} + +int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk"); + if (cb > 0) + printf("cb=%d\n", cb); + return cb; +} + +int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, + void *pvBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_ctrltransfer CtrlMsg = {0}; + + CtrlMsg.bRequestType = bmRequestType; + CtrlMsg.bRequest = bRequest; + CtrlMsg.wValue = wValue; + CtrlMsg.wLength = wLength; + CtrlMsg.timeout = 1000; + CtrlMsg.data = pvBuf; + + int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho); + printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength); + if (rc >= 0) + return rc; + return -1; +#endif +} + +static int claim_if(int iIf) +{ + return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if"); +} + +static int usb_set_connected(int ifnum, int conn) +{ + struct usbdevfs_ioctl io; + io.ifno = ifnum; + io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + io.data = NULL; + return doioctl(USBDEVFS_IOCTL, &io, "set_connected"); +} + +static int set_config(int iCfg) +{ + return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config"); +} + +static int set_interface(int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf = {0}; + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface"); +} + +/* can be exploited to check if there is an active config. */ +static int reset_ep(int EndPt) +{ + return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep"); +} + + +static void msd() +{ +#if 1 + unsigned InEndPt = 1; + unsigned OutEndPt = 1; +#else + unsigned InEndPt = 1; + unsigned OutEndPt = 2; +#endif + unsigned char abBuf[512]; + int i; + +// set_config(1); - the culprit + set_interface(0, 0); + +#if 0 + /* Send an Get Max LUN request */ + abBuf[0] = 0; + if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE, + 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0) + printf("max luns: %d\n", abBuf[0]); +#endif + + for (i = 0; i < 3; i++) + { + printf("i=%d\n", i); + + /* Send an INQUIRY command to ep 2 */ + memset(abBuf, 0, sizeof(abBuf)); + memcpy(abBuf, "USBC", 4); + *(uint32_t *)(&abBuf[4]) = 0x12330984 ; + //abBuf[8] = 0x08; + abBuf[8] = 0x24; + abBuf[0xc] = 0x80; + abBuf[0xe] = 0x06; /* cmd length */ + abBuf[0x0f] = 0x12; /* cmd - INQUIRY */ + abBuf[0x13] = 0x24; + + hex(abBuf, 31, "intquery req"); + if (send_bulk(OutEndPt, abBuf, 31) < 0) + return; + //usleep(15000); + + /* read result */ + memset(abBuf, 0, sizeof(abBuf)); + //printf("recv..\n"); + int cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry result"); + + /* sense? */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry sense?"); + usleep(150000); + } +} + +void palm(void) +{ +// set_config(1); //skip this +// reset_ep(6); + + /* This seems to be some kind of 'identify device' request. */ + uint8_t abVendor[0x14] = {0}; + int cb = doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_VENDOR | VUSB_TO_ENDPOINT, + 0x04, 0, 0, 0x14, abVendor, "vendor req"); + hex(abVendor, cb, "palm vendor req"); + + /* read from ep 6. */ + uint8_t abBuf[512]; + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 6); + hex(abBuf, cb, "bulk 1"); + + /* read from ep 6. */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 22); + hex(abBuf, cb, "bulk 2"); + +#if 0 + /* write to ep 6 */ + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 1; + abBuf[1] = 2; + abBuf[5] = 0x32; + if (send_bulk(7, abBuf, 6) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 0x12; + abBuf[1] = 1; + if (send_bulk(7, abBuf, 8) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[1] = 0x20; + abBuf[6] = 0x24; + if (send_bulk(7, abBuf, 6) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 0xff; + abBuf[1] = 0xff; + abBuf[2] = 0xff; + abBuf[3] = 0xff; + abBuf[4] = 0x3c; + abBuf[6] = 0x3c; + abBuf[0x0f] = 1; + abBuf[0x11] = 0x10; + abBuf[0x15] = 0x10; + abBuf[0x18] = 0x3c; + abBuf[0x1a] = 0x3c; + if (send_bulk(7, abBuf, 0x24) < 0) + return; + + /* read from ep 6 */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 64); + hex(abBuf, cb, "bulk 3"); + + /* read from ep 6. */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 50); + hex(abBuf, cb, "bulk 4"); +#endif +} + +int reset(void) +{ + int i = 0; + printf("resetting...\n"); + return doioctl(USBDEVFS_RESET, &i, "reset"); +} + +int main(int argc, char **argv) +{ + g_fd = open(argv[1], O_RDWR); + if (errno == ENOENT && g_fd < 0) + { + int i; + for (i = 0; i < 120; i++) + { + g_fd = open(argv[1], O_RDWR); + if (g_fd >= 0) + break; + printf("."); fflush(stdout); + usleep(500000); + } + printf("\n"); + } + if (g_fd < 0) + return bitch("open"); + +// reset(); +// set_config(0); +// set_config(1); + + usb_set_connected(0, 1); + claim_if(0); + +#if 0 + msd(); +#else + palm(); +#endif + return 0; +} diff --git a/src/VBox/Devices/USB/testcase/tstTrekStorGo.c b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c new file mode 100644 index 00000000..93a79968 --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c @@ -0,0 +1,323 @@ +/* $Id: tstTrekStorGo.c $ */ +/** @file + * Some simple inquiry test for the TrekStor USB-Stick GO, linux usbfs + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/usbdevice_fs.h> + + +/** @name USB Control message recipient codes (from spec) + * @{ */ +#define VUSB_TO_DEVICE 0x0 +#define VUSB_TO_INTERFACE 0x1 +#define VUSB_TO_ENDPOINT 0x2 +#define VUSB_TO_OTHER 0x3 +#define VUSB_RECIP_MASK 0x1f +/** @} */ + +/** @name USB control pipe setup packet structure (from spec) + * @{ */ +#define VUSB_REQ_SHIFT (5) +#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT) +#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT) +#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT) +#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT) +#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT) +/** @} */ + +#define VUSB_DIR_TO_HOST 0x80 +typedef struct vusb_setup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VUSBSETUP, *PVUSBSETUP; +typedef const VUSBSETUP *PCVUSBSETUP; + + +int g_fd; + +int bitch(const char *pszMsg) +{ + printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno)); + return 1; +} + +void hex(const void *pv, ssize_t cb, const char *pszWhat) +{ + printf("%s: cb=%d\n", pszWhat, cb); + unsigned char *pb = (unsigned char *)pv; + int cch = 0; + int off = 0; + int cchPrecision = 16; + while (off < cb) + { + int i; + printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off); + + for (i = 0; i < cchPrecision && off + i < cb; i++) + printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]); + while (i++ < cchPrecision) + printf(" "); + printf(" "); + for (i = 0; i < cchPrecision && off + i < cb; i++) + { + uint8_t u8 = pb[i]; + fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout); + } + + /* next */ + pb += cchPrecision; + off += cchPrecision; + } + printf("\n"); +} + +int doioctl(int iCmd, void *pvData, const char *pszWho) +{ + int rc; + do + { + errno = 0; + rc = ioctl(g_fd, iCmd, pvData); + + } while (rc && errno == EAGAIN); + if (rc) + printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno)); + else + printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd); + return rc; +} + +int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_bulktransfer BulkMsg = {0}; + + BulkMsg.ep = EndPt; + BulkMsg.timeout = 1000; + BulkMsg.len = cbBuf; + BulkMsg.data = pvBuf; + int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho); +// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf); + if (rc >= 0) + return rc; + return -1; +#endif +} + +int send_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + return dobulk(EndPt, pvBuf, cbBuf, "send_bulk"); +} + +int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk"); + if (cb > 0) + printf("cb=%d\n", cb); + return cb; +} + +int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, + void *pvBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_ctrltransfer CtrlMsg = {0}; + + CtrlMsg.bRequestType = bmRequestType; + CtrlMsg.bRequest = bRequest; + CtrlMsg.wValue = wValue; + CtrlMsg.wLength = wLength; + CtrlMsg.timeout = 1000; + CtrlMsg.data = pvBuf; + + int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho); + printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength); + if (rc >= 0) + return rc; + return -1; +#endif +} + +static int claim_if(int iIf) +{ + return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if"); +} + +static int usb_set_connected(int ifnum, int conn) +{ + struct usbdevfs_ioctl io; + io.ifno = ifnum; + io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + io.data = NULL; + return doioctl(USBDEVFS_IOCTL, &io, "set_connected"); +} + +static int set_config(int iCfg) +{ + return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config"); +} + +static int set_interface(int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf = {0}; + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface"); +} + +/* can be exploited to check if there is an active config. */ +static int reset_ep(int EndPt) +{ + return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep"); +} + + +static void msd() +{ +#if 1 + unsigned InEndPt = 1; + unsigned OutEndPt = 1; +#else + unsigned InEndPt = 1; + unsigned OutEndPt = 2; +#endif + unsigned char abBuf[512]; + int i; + +#if 0 + /* Send an Get Max LUN request */ + abBuf[0] = 0; + if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE, + 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0) + printf("max luns: %d\n", abBuf[0]); +#endif + + for (i = 0; i < 3; i++) + { + printf("i=%d\n", i); + + /* Send an INQUIRY command to ep 2 */ + memset(abBuf, 0, sizeof(abBuf)); + memcpy(abBuf, "USBC", 4); + *(uint32_t *)(&abBuf[4]) = 0x12330984 ; + //abBuf[8] = 0x08; + abBuf[8] = 0x24; + abBuf[0xc] = 0x80; + abBuf[0xe] = 0x06; /* cmd length */ + abBuf[0x0f] = 0x12; /* cmd - INQUIRY */ + abBuf[0x13] = 0x24; + + hex(abBuf, 31, "intquery req"); + if (send_bulk(OutEndPt, abBuf, 31) < 0) + return; + //usleep(15000); + + /* read result */ + memset(abBuf, 0, sizeof(abBuf)); + //printf("recv..\n"); + int cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry result"); + + /* sense? */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry sense?"); + usleep(150000); + } +} + +int reset(void) +{ + int i = 0; + printf("resetting...\n"); + return doioctl(USBDEVFS_RESET, &i, "reset"); +} + +int main(int argc, char **argv) +{ + g_fd = open(argv[1], O_RDWR); + if (g_fd < 0) + return bitch("open"); + + reset(); + + usb_set_connected(0, 1); + claim_if(0); + +// set_config(1); - the culprit! + set_interface(0, 0); + + msd(); + return 0; +} diff --git a/src/VBox/Devices/USB/usbip/Makefile.kup b/src/VBox/Devices/USB/usbip/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/usbip/Makefile.kup diff --git a/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp new file mode 100644 index 00000000..b91fcffd --- /dev/null +++ b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp @@ -0,0 +1,1818 @@ +/* $Id: USBProxyDevice-usbip.cpp $ */ +/** @file + * USB device proxy - USB/IP backend. + */ + +/* + * Copyright (C) 2014-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/socket.h> +#include <iprt/poll.h> +#include <iprt/tcp.h> +#include <iprt/pipe.h> +#include <iprt/list.h> +#include <iprt/semaphore.h> + +#include "../USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** The USB version number used for the protocol. */ +#define USBIP_VERSION UINT16_C(0x0111) +/** Request indicator in the command code. */ +#define USBIP_INDICATOR_REQ RT_BIT(15) + +/** Command/Reply code for OP_REQ/RET_DEVLIST. */ +#define USBIP_REQ_RET_DEVLIST UINT16_C(5) +/** Command/Reply code for OP_REQ/REP_IMPORT. */ +#define USBIP_REQ_RET_IMPORT UINT16_C(3) +/** USB submit command identifier. */ +#define USBIP_CMD_SUBMIT UINT32_C(1) +/** USB submit status identifier. */ +#define USBIP_RET_SUBMIT UINT32_C(3) +/** URB unlink (cancel) command identifier. */ +#define USBIP_CMD_UNLINK UINT32_C(2) +/** URB unlink (cancel) reply identifier. */ +#define USBIP_RET_UNLINK UINT32_C(4) + +/** Short read is not okay for the specified URB. */ +#define USBIP_XFER_FLAGS_SHORT_NOT_OK RT_BIT_32(0) +/** Queue the isochronous URB as soon as possible. */ +#define USBIP_XFER_FLAGS_ISO_ASAP RT_BIT_32(1) +/** Don't use DMA mappings for this URB. */ +#define USBIP_XFER_FLAGS_NO_TRANSFER_DMA_MAP RT_BIT_32(2) +/** Explain - only applies to UHCI. */ +#define USBIP_XFER_FLAGS_FSBR RT_BIT_32(4) + +/** URB direction - input. */ +#define USBIP_DIR_IN UINT32_C(1) +/** URB direction - output. */ +#define USBIP_DIR_OUT UINT32_C(0) + +/** @name USB/IP error codes. + * @{ */ +/** Success indicator. */ +#define USBIP_STATUS_SUCCESS INT32_C(0) +/** Pipe stalled. */ +#define USBIP_STATUS_PIPE_STALLED INT32_C(-32) +/** URB was unlinked by a call to usb_unlink_urb(). */ +#define USBIP_STATUS_URB_UNLINKED INT32_C(-104) +/** Short read. */ +#define USBIP_STATUS_SHORT_READ INT32_C(-121) +/** @} */ + +/** + * Exported device entry in the OP_RET_DEVLIST reply. + */ +#pragma pack(1) +typedef struct UsbIpExportedDevice +{ + /** Path of the device, zero terminated string. */ + char szPath[256]; + /** Bus ID of the exported device, zero terminated string. */ + char szBusId[32]; + /** Bus number. */ + uint32_t u32BusNum; + /** Device number. */ + uint32_t u32DevNum; + /** Speed indicator of the device. */ + uint32_t u32Speed; + /** Vendor ID of the device. */ + uint16_t u16VendorId; + /** Product ID of the device. */ + uint16_t u16ProductId; + /** Device release number. */ + uint16_t u16BcdDevice; + /** Device class. */ + uint8_t bDeviceClass; + /** Device Subclass. */ + uint8_t bDeviceSubClass; + /** Device protocol. */ + uint8_t bDeviceProtocol; + /** Configuration value. */ + uint8_t bConfigurationValue; + /** Current configuration value of the device. */ + uint8_t bNumConfigurations; + /** Number of interfaces for the device. */ + uint8_t bNumInterfaces; +} UsbIpExportedDevice; +/** Pointer to a exported device entry. */ +typedef UsbIpExportedDevice *PUsbIpExportedDevice; +#pragma pack() +AssertCompileSize(UsbIpExportedDevice, 312); + +/** + * Interface descriptor entry for an exported device. + */ +#pragma pack(1) +typedef struct UsbIpDeviceInterface +{ + /** Intefrace class. */ + uint8_t bInterfaceClass; + /** Interface sub class. */ + uint8_t bInterfaceSubClass; + /** Interface protocol identifier. */ + uint8_t bInterfaceProtocol; + /** Padding byte for alignment. */ + uint8_t bPadding; +} UsbIpDeviceInterface; +/** Pointer to an interface descriptor entry. */ +typedef UsbIpDeviceInterface *PUsbIpDeviceInterface; +#pragma pack() + +/** + * USB/IP Import request. + */ +#pragma pack(1) +typedef struct UsbIpReqImport +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t u32Status; + /** Bus Id of the device as zero terminated string. */ + char aszBusId[32]; +} UsbIpReqImport; +/** Pointer to a import request. */ +typedef UsbIpReqImport *PUsbIpReqImport; +#pragma pack() + +/** + * USB/IP Import reply. + * + * This is only the header, for successful + * imports the device details are sent to as + * defined in UsbIpExportedDevice. + */ +#pragma pack(1) +typedef struct UsbIpRetImport +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t u32Status; +} UsbIpRetImport; +/** Pointer to a import reply. */ +typedef UsbIpRetImport *PUsbIpRetImport; +#pragma pack() + +/** + * Command/Reply header common to the submit and unlink commands + * replies. + */ +#pragma pack(1) +typedef struct UsbIpReqRetHdr +{ + /** Request/Return code. */ + uint32_t u32ReqRet; + /** Sequence number to identify the URB. */ + uint32_t u32SeqNum; + /** Device id. */ + uint32_t u32DevId; + /** Direction of the endpoint (host->device, device->host). */ + uint32_t u32Direction; + /** Endpoint number. */ + uint32_t u32Endpoint; +} UsbIpReqRetHdr; +/** Pointer to a request/reply header. */ +typedef UsbIpReqRetHdr *PUsbIpReqRetHdr; +#pragma pack() + +/** + * USB/IP Submit request. + */ +#pragma pack(1) +typedef struct UsbIpReqSubmit +{ + /** The request header. */ + UsbIpReqRetHdr Hdr; + /** Transfer flags for the URB. */ + uint32_t u32XferFlags; + /** Transfer buffer length. */ + uint32_t u32TransferBufferLength; + /** Frame to transmit an ISO frame. */ + uint32_t u32StartFrame; + /** Number of isochronous packets. */ + uint32_t u32NumIsocPkts; + /** Maximum time for the request on the server side host controller. */ + uint32_t u32Interval; + /** Setup data for a control URB. */ + VUSBSETUP Setup; +} UsbIpReqSubmit; +/** Pointer to a submit request. */ +typedef UsbIpReqSubmit *PUsbIpReqSubmit; +#pragma pack() +AssertCompileSize(UsbIpReqSubmit, 48); + +/** + * USB/IP Submit reply. + */ +#pragma pack(1) +typedef struct UsbIpRetSubmit +{ + /** The reply header. */ + UsbIpReqRetHdr Hdr; + /** Status code. */ + int32_t u32Status; + /** Actual length of the reply buffer. */ + uint32_t u32ActualLength; + /** The actual selected frame for a isochronous transmit. */ + uint32_t u32StartFrame; + /** Number of isochronous packets. */ + uint32_t u32NumIsocPkts; + /** Number of failed isochronous packets. */ + uint32_t u32ErrorCount; + /** Setup data for a control URB. */ + VUSBSETUP Setup; +} UsbIpRetSubmit; +/** Pointer to a submit reply. */ +typedef UsbIpRetSubmit *PUsbIpRetSubmit; +#pragma pack() +AssertCompileSize(UsbIpRetSubmit, 48); + +/** + * Unlink URB request. + */ +#pragma pack(1) +typedef struct UsbIpReqUnlink +{ + /** The request header. */ + UsbIpReqRetHdr Hdr; + /** The sequence number to unlink. */ + uint32_t u32SeqNum; + /** Padding - unused. */ + uint8_t abPadding[24]; +} UsbIpReqUnlink; +/** Pointer to a URB unlink request. */ +typedef UsbIpReqUnlink *PUsbIpReqUnlink; +#pragma pack() +AssertCompileSize(UsbIpReqUnlink, 48); + +/** + * Unlink URB reply. + */ +#pragma pack(1) +typedef struct UsbIpRetUnlink +{ + /** The reply header. */ + UsbIpReqRetHdr Hdr; + /** Status of the request. */ + int32_t u32Status; + /** Padding - unused. */ + uint8_t abPadding[24]; +} UsbIpRetUnlink; +/** Pointer to a URB unlink request. */ +typedef UsbIpRetUnlink *PUsbIpRetUnlink; +#pragma pack() +AssertCompileSize(UsbIpRetUnlink, 48); + +/** + * Union of possible replies from the server during normal operation. + */ +#pragma pack(1) +typedef union UsbIpRet +{ + /** The header. */ + UsbIpReqRetHdr Hdr; + /** Submit reply. */ + UsbIpRetSubmit RetSubmit; + /** Unlink reply. */ + UsbIpRetUnlink RetUnlink; + /** Byte view. */ + uint8_t abReply[1]; +} UsbIpRet; +/** Pointer to a reply union. */ +typedef UsbIpRet *PUsbIpRet; +#pragma pack() + +/** + * Isochronous packet descriptor. +*/ +#pragma pack(1) +typedef struct UsbIpIsocPktDesc +{ + /** Offset */ + uint32_t u32Offset; + /** Length of the packet including padding. */ + uint32_t u32Length; + /** Size of the transmitted data. */ + uint32_t u32ActualLength; + /** Completion status for this packet. */ + int32_t i32Status; +} UsbIpIsocPktDesc; +/** Pointer to a isochronous packet descriptor. */ +typedef UsbIpIsocPktDesc *PUsbIpIsocPktDesc; +#pragma pack() + +/** + * USB/IP backend specific data for one URB. + * Required for tracking in flight and landed URBs. + */ +typedef struct USBPROXYURBUSBIP +{ + /** List node for the in flight or landed URB list. */ + RTLISTNODE NodeList; + /** Sequence number the assigned URB is identified by. */ + uint32_t u32SeqNumUrb; + /** Sequence number of the unlink command if the URB was cancelled. */ + uint32_t u32SeqNumUrbUnlink; + /** Flag whether the URB was cancelled. */ + bool fCancelled; + /** USB xfer type. */ + VUSBXFERTYPE enmType; + /** USB xfer direction. */ + VUSBDIRECTION enmDir; + /** Completion status. */ + VUSBSTATUS enmStatus; + /** Pointer to the VUSB URB. */ + PVUSBURB pVUsbUrb; +} USBPROXYURBUSBIP; +/** Pointer to a USB/IP URB. */ +typedef USBPROXYURBUSBIP *PUSBPROXYURBUSBIP; + +/** + * USB/IP data receive states. + */ +typedef enum USBPROXYUSBIPRECVSTATE +{ + /** Invalid receive state. */ + USBPROXYUSBIPRECVSTATE_INVALID = 0, + /** Currently receiving the common header structure. */ + USBPROXYUSBIPRECVSTATE_HDR_COMMON, + /** Currently receieving the rest of the header structure. */ + USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL, + /** Currently receiving data into the URB buffer. */ + USBPROXYUSBIPRECVSTATE_URB_BUFFER, + /** Currently receiving the isochronous packet descriptors. */ + USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, + /** Usual 32bit hack. */ + USBPROXYUSBIPRECVSTATE_32BIT_HACK = 0x7fffffff +} USBPROXYUSBIPRECVSTATE; +/** Pointer to an receive state. */ +typedef USBPROXYUSBIPRECVSTATE *PUSBPROXYUSBIPRECVSTATE; + +/** + * Backend data for the USB/IP USB Proxy device backend. + */ +typedef struct USBPROXYDEVUSBIP +{ + /** IPRT socket handle. */ + RTSOCKET hSocket; + /** Pollset with the wakeup pipe and socket. */ + RTPOLLSET hPollSet; + /** Pipe endpoint - read (in the pollset). */ + RTPIPE hPipeR; + /** Pipe endpoint - write. */ + RTPIPE hPipeW; + /** Next sequence number to use for identifying submitted URBs. */ + volatile uint32_t u32SeqNumNext; + /** Fast mutex protecting the lists below against concurrent access. */ + RTSEMFASTMUTEX hMtxLists; + /** List of in flight URBs. */ + RTLISTANCHOR ListUrbsInFlight; + /** List of landed URBs. */ + RTLISTANCHOR ListUrbsLanded; + /** List of URBs to submit. */ + RTLISTANCHOR ListUrbsToQueue; + /** Port of the USB/IP host to connect to. */ + uint32_t uPort; + /** USB/IP host address. */ + char *pszHost; + /** USB Bus ID of the device to capture. */ + char *pszBusId; + /** The device ID to use to identify the device. */ + uint32_t u32DevId; + /** Temporary buffer for the next reply header */ + UsbIpRet BufRet; + /** Temporary buffer to hold all isochronous packet descriptors. */ + UsbIpIsocPktDesc aIsocPktDesc[8]; + /** Pointer to the current buffer to write received data to. */ + uint8_t *pbRecv; + /** Number of bytes received so far. */ + size_t cbRecv; + /** Number of bytes left to receive. until we advance the state machine and process the data */ + size_t cbLeft; + /** The current receiving state. */ + USBPROXYUSBIPRECVSTATE enmRecvState; + /** The URB we currently receive a response for. */ + PUSBPROXYURBUSBIP pUrbUsbIp; +} USBPROXYDEVUSBIP, *PUSBPROXYDEVUSBIP; + +/** Pollset id of the socket. */ +#define USBIP_POLL_ID_SOCKET 0 +/** Pollset id of the pipe. */ +#define USBIP_POLL_ID_PIPE 1 + +/** USB/IP address prefix for identifcation. */ +#define USBIP_URI_PREFIX "usbip://" +/** USB/IP address prefix length. */ +#define USBIP_URI_PREFIX_LEN (sizeof(USBIP_URI_PREFIX) - 1) + +/** Waking reason for the USB I/P reaper: New URBs to queue. */ +#define USBIP_REAPER_WAKEUP_REASON_QUEUE 'Q' +/** Waking reason for the USB I/P reaper: External wakeup. */ +#define USBIP_REAPER_WAKEUP_REASON_EXTERNAL 'E' + +/** + * Converts a request/reply header from network to host endianness. + * + * @returns nothing. + * @param pHdr The header to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqRetHdrN2H(PUsbIpReqRetHdr pHdr) +{ + pHdr->u32ReqRet = RT_H2N_U32(pHdr->u32ReqRet); + pHdr->u32SeqNum = RT_H2N_U32(pHdr->u32SeqNum); + pHdr->u32DevId = RT_H2N_U32(pHdr->u32DevId); + pHdr->u32Direction = RT_H2N_U32(pHdr->u32Direction); + pHdr->u32Endpoint = RT_H2N_U32(pHdr->u32Endpoint); +} + +/** + * Converts a request/reply header from host to network endianness. + * + * @returns nothing. + * @param pHdr The header to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqRetHdrH2N(PUsbIpReqRetHdr pHdr) +{ + pHdr->u32ReqRet = RT_N2H_U32(pHdr->u32ReqRet); + pHdr->u32SeqNum = RT_N2H_U32(pHdr->u32SeqNum); + pHdr->u32DevId = RT_N2H_U32(pHdr->u32DevId); + pHdr->u32Direction = RT_N2H_U32(pHdr->u32Direction); + pHdr->u32Endpoint = RT_N2H_U32(pHdr->u32Endpoint); +} + +/** + * Converts a submit request from host to network endianness. + * + * @returns nothing. + * @param pReqSubmit The submit request to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqSubmitH2N(PUsbIpReqSubmit pReqSubmit) +{ + usbProxyUsbIpReqRetHdrH2N(&pReqSubmit->Hdr); + pReqSubmit->u32XferFlags = RT_H2N_U32(pReqSubmit->u32XferFlags); + pReqSubmit->u32TransferBufferLength = RT_H2N_U32(pReqSubmit->u32TransferBufferLength); + pReqSubmit->u32StartFrame = RT_H2N_U32(pReqSubmit->u32StartFrame); + pReqSubmit->u32NumIsocPkts = RT_H2N_U32(pReqSubmit->u32NumIsocPkts); + pReqSubmit->u32Interval = RT_H2N_U32(pReqSubmit->u32Interval); +} + +/** + * Converts a submit reply from network to host endianness. + * + * @returns nothing. + * @param pReqSubmit The submit reply to convert. + */ +DECLINLINE(void) usbProxyUsbIpRetSubmitN2H(PUsbIpRetSubmit pRetSubmit) +{ + usbProxyUsbIpReqRetHdrN2H(&pRetSubmit->Hdr); + pRetSubmit->u32Status = RT_N2H_U32(pRetSubmit->u32Status); + pRetSubmit->u32ActualLength = RT_N2H_U32(pRetSubmit->u32ActualLength); + pRetSubmit->u32StartFrame = RT_N2H_U32(pRetSubmit->u32StartFrame); + pRetSubmit->u32NumIsocPkts = RT_N2H_U32(pRetSubmit->u32NumIsocPkts); + pRetSubmit->u32ErrorCount = RT_N2H_U32(pRetSubmit->u32ErrorCount); +} + +/** + * Converts a isochronous packet descriptor from host to network endianness. + * + * @returns nothing. + * @param pIsocPktDesc The packet descriptor to convert. + */ +DECLINLINE(void) usbProxyUsbIpIsocPktDescH2N(PUsbIpIsocPktDesc pIsocPktDesc) +{ + pIsocPktDesc->u32Offset = RT_H2N_U32(pIsocPktDesc->u32Offset); + pIsocPktDesc->u32Length = RT_H2N_U32(pIsocPktDesc->u32Length); + pIsocPktDesc->u32ActualLength = RT_H2N_U32(pIsocPktDesc->u32ActualLength); + pIsocPktDesc->i32Status = RT_H2N_U32(pIsocPktDesc->i32Status); +} + +/** + * Converts a isochronous packet descriptor from network to host endianness. + * + * @returns nothing. + * @param pIsocPktDesc The packet descriptor to convert. + */ +DECLINLINE(void) usbProxyUsbIpIsocPktDescN2H(PUsbIpIsocPktDesc pIsocPktDesc) +{ + pIsocPktDesc->u32Offset = RT_N2H_U32(pIsocPktDesc->u32Offset); + pIsocPktDesc->u32Length = RT_N2H_U32(pIsocPktDesc->u32Length); + pIsocPktDesc->u32ActualLength = RT_N2H_U32(pIsocPktDesc->u32ActualLength); + pIsocPktDesc->i32Status = RT_N2H_U32(pIsocPktDesc->i32Status); +} + +/** + * Converts a unlink request from host to network endianness. + * + * @returns nothing. + * @param pReqUnlink The unlink request to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqUnlinkH2N(PUsbIpReqUnlink pReqUnlink) +{ + usbProxyUsbIpReqRetHdrH2N(&pReqUnlink->Hdr); + pReqUnlink->u32SeqNum = RT_H2N_U32(pReqUnlink->u32SeqNum); +} + +/** + * Converts a unlink reply from network to host endianness. + * + * @returns nothing. + * @param pRetUnlink The unlink reply to convert. + */ +DECLINLINE(void) usbProxyUsbIpRetUnlinkN2H(PUsbIpRetUnlink pRetUnlink) +{ + usbProxyUsbIpReqRetHdrN2H(&pRetUnlink->Hdr); + pRetUnlink->u32Status = RT_N2H_U32(pRetUnlink->u32Status); +} + +/** + * Convert the given exported device structure from host to network byte order. + * + * @returns nothing. + * @param pDevice The device structure to convert. + */ +DECLINLINE(void) usbProxyUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice) +{ + pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum); + pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum); + pDevice->u32Speed = RT_N2H_U16(pDevice->u32Speed); + pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId); + pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId); + pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice); +} + +/** + * Converts a USB/IP status code to a VUSB status code. + * + * @returns VUSB status code. + * @param i32Status The USB/IP status code from the reply. + */ +DECLINLINE(VUSBSTATUS) usbProxyUsbIpVUsbStatusConvertFromStatus(int32_t i32Status) +{ + if (RT_LIKELY( i32Status == USBIP_STATUS_SUCCESS + || i32Status == USBIP_STATUS_SHORT_READ)) + return VUSBSTATUS_OK; + + switch (i32Status) + { + case USBIP_STATUS_PIPE_STALLED: + return VUSBSTATUS_STALL; + default: + return VUSBSTATUS_DNR; + } + /* not reached */ +} + +/** + * Gets the next free sequence number. + * + * @returns Next free sequence number. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +DECLINLINE(uint32_t) usbProxyUsbIpSeqNumGet(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + uint32_t u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); + if (RT_UNLIKELY(!u32SeqNum)) + u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); + + return u32SeqNum; +} + +/** + * Links a given URB into the given list. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pList The list to link the URB into. + * @param pUrbUsbIp The URB to link. + */ +DECLINLINE(void) usbProxyUsbIpLinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PRTLISTANCHOR pList, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListAppend(pList, &pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); +} + +/** + * Unlinks a given URB from the current assigned list. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The URB to unlink. + */ +DECLINLINE(void) usbProxyUsbIpUnlinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListNodeRemove(&pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); +} + +/** + * Allocates a USB/IP proxy specific URB state. + * + * @returns Pointer to the USB/IP specific URB data or NULL on failure. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpUrbAlloc(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + NOREF(pProxyDevUsbIp); + return (PUSBPROXYURBUSBIP)RTMemAllocZ(sizeof(USBPROXYURBUSBIP)); +} + +/** + * Frees the given USB/IP URB state. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The USB/IP speciic URB data. + */ +static void usbProxyUsbIpUrbFree(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + NOREF(pProxyDevUsbIp); + RTMemFree(pUrbUsbIp); +} + +/** + * Parse the string representation of the host address. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data to parse the address for. + * @param pszAddress The address string to parse. + */ +static int usbProxyUsbIpParseAddress(PUSBPROXYDEVUSBIP pProxyDevUsbIp, const char *pszAddress) +{ + int rc = VINF_SUCCESS; + + if (!RTStrNCmp(pszAddress, USBIP_URI_PREFIX, USBIP_URI_PREFIX_LEN)) + { + pszAddress += USBIP_URI_PREFIX_LEN; + + const char *pszPortStart = RTStrStr(pszAddress, ":"); + if (pszPortStart) + { + pszPortStart++; + + const char *pszBusIdStart = RTStrStr(pszPortStart, ":"); + if (pszBusIdStart) + { + size_t cbHost = pszPortStart - pszAddress - 1; + size_t cbBusId = strlen(pszBusIdStart); + + pszBusIdStart++; + + rc = RTStrToUInt32Ex(pszPortStart, NULL, 10 /* uBase */, &pProxyDevUsbIp->uPort); + if ( rc == VINF_SUCCESS + || rc == VWRN_TRAILING_CHARS) + { + rc = RTStrAllocEx(&pProxyDevUsbIp->pszHost, cbHost + 1); + if (RT_SUCCESS(rc)) + rc = RTStrAllocEx(&pProxyDevUsbIp->pszBusId, cbBusId + 1); + if (RT_SUCCESS(rc)) + { + rc = RTStrCopyEx(pProxyDevUsbIp->pszHost, cbHost + 1, pszAddress, cbHost); + AssertRC(rc); + + rc = RTStrCopyEx(pProxyDevUsbIp->pszBusId, cbBusId + 1, pszBusIdStart, cbBusId); + AssertRC(rc); + + return VINF_SUCCESS; + } + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + +/** + * Connects to the USB/IP host and claims the device given in the proxy device data. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpConnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + int rc = VINF_SUCCESS; + rc = RTTcpClientConnect(pProxyDevUsbIp->pszHost, pProxyDevUsbIp->uPort, &pProxyDevUsbIp->hSocket); + if (RT_SUCCESS(rc)) + { + /* Disable send coalescing. */ + rc = RTTcpSetSendCoalescing(pProxyDevUsbIp->hSocket, false); + if (RT_FAILURE(rc)) + LogRel(("UsbIp: Disabling send coalescing failed (rc=%Rrc), continuing nevertheless but expect reduced performance\n", rc)); + + /* Import the device, i.e. claim it for our use. */ + UsbIpReqImport ReqImport; + ReqImport.u16Version = RT_H2N_U16(USBIP_VERSION); + ReqImport.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_IMPORT); + ReqImport.u32Status = RT_H2N_U32(USBIP_STATUS_SUCCESS); + rc = RTStrCopy(&ReqImport.aszBusId[0], sizeof(ReqImport.aszBusId), pProxyDevUsbIp->pszBusId); + if (rc == VINF_SUCCESS) + { + rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqImport, sizeof(ReqImport)); + if (RT_SUCCESS(rc)) + { + /* Read the reply. */ + UsbIpRetImport RetImport; + rc = RTTcpRead(pProxyDevUsbIp->hSocket, &RetImport, sizeof(RetImport), NULL); + if (RT_SUCCESS(rc)) + { + RetImport.u16Version = RT_N2H_U16(RetImport.u16Version); + RetImport.u16Cmd = RT_N2H_U16(RetImport.u16Cmd); + RetImport.u32Status = RT_N2H_U32(RetImport.u32Status); + if ( RetImport.u16Version == USBIP_VERSION + && RetImport.u16Cmd == USBIP_REQ_RET_IMPORT + && RetImport.u32Status == USBIP_STATUS_SUCCESS) + { + /* Read the device data. */ + UsbIpExportedDevice Device; + rc = RTTcpRead(pProxyDevUsbIp->hSocket, &Device, sizeof(Device), NULL); + if (RT_SUCCESS(rc)) + { + usbProxyUsbIpExportedDeviceN2H(&Device); + pProxyDevUsbIp->u32DevId = (Device.u32BusNum << 16) | Device.u32DevNum; + + rc = RTPollSetAddSocket(pProxyDevUsbIp->hPollSet, pProxyDevUsbIp->hSocket, + RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, USBIP_POLL_ID_SOCKET); + } + } + else + { + /* Check what went wrong and leave a meaningful error message in the log. */ + if (RetImport.u16Version != USBIP_VERSION) + LogRel(("UsbIp: Unexpected protocol version received from host (%#x vs. %#x)\n", + RetImport.u16Version, USBIP_VERSION)); + else if (RetImport.u16Cmd != USBIP_REQ_RET_IMPORT) + LogRel(("UsbIp: Unexpected reply code received from host (%#x vs. %#x)\n", + RetImport.u16Cmd, USBIP_REQ_RET_IMPORT)); + else if (RetImport.u32Status != 0) + LogRel(("UsbIp: Claiming the device has failed on the host with an unspecified error\n")); + else + AssertMsgFailed(("Something went wrong with if condition\n")); + } + } + } + } + else + { + LogRel(("UsbIp: Given bus ID is exceeds permitted protocol length: %u vs %u\n", + strlen(pProxyDevUsbIp->pszBusId) + 1, sizeof(ReqImport.aszBusId))); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_FAILURE(rc)) + RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); + } + if (RT_FAILURE(rc)) + LogRel(("UsbIp: Connecting to the host %s failed with %Rrc\n", pProxyDevUsbIp->pszHost, rc)); + return rc; +} + +/** + * Disconnects from the USB/IP host releasing the device given in the proxy device data. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpDisconnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + int rc = RTPollSetRemove(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET); + Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND); + + rc = RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); + if (RT_SUCCESS(rc)) + pProxyDevUsbIp->hSocket = NIL_RTSOCKET; + return rc; +} + +/** + * Returns the URB matching the given sequence number from the in flight list. + * + * @returns pointer to the URB matching the given sequence number or NULL + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNum The sequence number to search for. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpGetInFlightUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) +{ + bool fFound = false; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) + { + if (pIt->u32SeqNumUrb == u32SeqNum) + { + fFound = true; + break; + } + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return fFound ? pIt : NULL; +} + +/** + * Returns the URB matching the given sequence number from the cancel list. + * + * @returns pointer to the URB matching the given sequence number or NULL + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNum The sequence number to search for. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpGetCancelledUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) +{ + bool fFound = false; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) + { + if ( pIt->u32SeqNumUrbUnlink == u32SeqNum + && pIt->fCancelled == true) + { + fFound = true; + break; + } + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return fFound ? pIt : NULL; +} + +/** + * Resets the receive state for a new reply. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static void usbProxyUsbIpResetRecvState(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_COMMON; + pProxyDevUsbIp->pbRecv = (uint8_t *)&pProxyDevUsbIp->BufRet; + pProxyDevUsbIp->cbRecv = 0; + pProxyDevUsbIp->cbLeft = sizeof(UsbIpReqRetHdr); +} + +static void usbProxyUsbIpRecvStateAdvance(PUSBPROXYDEVUSBIP pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE enmState, + uint8_t *pbData, size_t cbData) +{ + pProxyDevUsbIp->enmRecvState = enmState; + pProxyDevUsbIp->cbRecv = 0; + pProxyDevUsbIp->cbLeft = cbData; + pProxyDevUsbIp->pbRecv = pbData; +} + +/** + * Handles reception of a USB/IP PDU. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param ppUrbUsbIp Where to store the pointer to the USB/IP URB which completed. + * Will be NULL if the received PDU is not complete and we have + * have to wait for more data or on failure. + */ +static int usbProxyUsbIpRecvPdu(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP *ppUrbUsbIp) +{ + int rc = VINF_SUCCESS; + size_t cbRead = 0; + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + + Assert(pProxyDevUsbIp->cbLeft); + + /* Read any available data first. */ + rc = RTTcpReadNB(pProxyDevUsbIp->hSocket, pProxyDevUsbIp->pbRecv, pProxyDevUsbIp->cbLeft, &cbRead); + if (RT_SUCCESS(rc)) + { + pProxyDevUsbIp->cbRecv += cbRead; + pProxyDevUsbIp->cbLeft -= cbRead; + pProxyDevUsbIp->pbRecv += cbRead; + + /* Process the received data if there is nothing to receive left for the current state. */ + if (!pProxyDevUsbIp->cbLeft) + { + switch (pProxyDevUsbIp->enmRecvState) + { + case USBPROXYUSBIPRECVSTATE_HDR_COMMON: + { + Assert(pProxyDevUsbIp->cbRecv == sizeof(UsbIpReqRetHdr)); + + /* + * Determine the residual amount of data to receive until + * the complete reply header was received. + */ + switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) + { + case USBIP_RET_SUBMIT: + pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetSubmit) - sizeof(UsbIpReqRetHdr); + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; + break; + case USBIP_RET_UNLINK: + pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetUnlink) - sizeof(UsbIpReqRetHdr); + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; + break; + default: + AssertLogRelMsgFailed(("Invalid reply header received: %d\n", + pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)); + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + + break; + } + case USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL: + { + switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) + { + case USBIP_RET_SUBMIT: + /* Get the URB from the in flight list. */ + pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetInFlightUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); + if (pProxyDevUsbIp->pUrbUsbIp) + { + usbProxyUsbIpRetSubmitN2H(&pProxyDevUsbIp->BufRet.RetSubmit); + + /* We still have to receive the transfer buffer, even in case of an error. */ + pProxyDevUsbIp->pUrbUsbIp->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetSubmit.u32Status); + if (pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_IN) + { + uint8_t *pbData = NULL; + size_t cbRet = 0; + + AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb); + if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG) + { + /* Preserve the setup request. */ + pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)]; + cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP); + } + else + { + pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0]; + cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength; + } + + if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength) + { + if (RT_LIKELY(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData >= cbRet)) + { + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = (uint32_t)cbRet; + usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER, + pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength); + } + else + { + /* + * Bogus length returned from the USB/IP remote server. + * Error out because there is no way to find the end of the current + * URB and the beginning of the next one. The error will cause closing the + * connection to the rogue remote and all URBs get completed with an error. + */ + LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid length %zu (max %zu)\n", + pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, cbRet, + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData)); + rc = VERR_NET_PROTOCOL_ERROR; + } + } + else + { + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + } + else + { + Assert(pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_OUT); + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + } + else + { + LogRel(("USB/IP: Received reply with sequence number %u doesn't match any local URB\n", + RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum))); + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + rc = VERR_NET_PROTOCOL_ERROR; + } + break; + case USBIP_RET_UNLINK: + pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetCancelledUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); + if (pProxyDevUsbIp->pUrbUsbIp) + { + usbProxyUsbIpRetUnlinkN2H(&pProxyDevUsbIp->BufRet.RetUnlink); + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + pUrbUsbIp->pVUsbUrb->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetUnlink.u32Status); + } + /* else: Probably received the data for the URB and is complete already. */ + + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + break; + } + + break; + } + case USBPROXYUSBIPRECVSTATE_URB_BUFFER: + if (pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->enmType == VUSBXFERTYPE_ISOC) + usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, + (uint8_t *)&pProxyDevUsbIp->aIsocPktDesc[0], + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc)); + else + { + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + break; + case USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS: + /* Process all received isochronous packet descriptors. */ + for (unsigned i = 0; i < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts; i++) + { + PVUSBURBISOCPTK pIsocPkt = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->aIsocPkts[i]; + PUsbIpIsocPktDesc pIsocPktUsbIp = &pProxyDevUsbIp->aIsocPktDesc[i]; + + usbProxyUsbIpIsocPktDescN2H(pIsocPktUsbIp); + pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pIsocPktUsbIp->i32Status); + + if (RT_LIKELY( pIsocPktUsbIp->u32Offset < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData + && pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData - pIsocPktUsbIp->u32Offset >= pIsocPktUsbIp->u32ActualLength)) + { + pIsocPkt->off = pIsocPktUsbIp->u32Offset; + pIsocPkt->cb = pIsocPktUsbIp->u32ActualLength; + } + else + { + /* + * The offset and length value in the isoc packet descriptor are bogus and would cause a buffer overflow later on, leave an + * error message and disconnect from the rogue remote end. + */ + LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid isoc packet descriptor %u (offset=%u length=%u)\n", + pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, i, + pIsocPktUsbIp->u32Offset, pIsocPktUsbIp->u32ActualLength)); + rc = VERR_NET_PROTOCOL_ERROR; + break; + } + } + + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + break; + default: + AssertLogRelMsgFailed(("USB/IP: Invalid receive state %d\n", pProxyDevUsbIp->enmRecvState)); + } + } + } + + if (RT_SUCCESS(rc)) + *ppUrbUsbIp = pUrbUsbIp; + else + { + /* Complete all URBs with DNR error and mark device as unplugged, the current one is still in the in flight list. */ + pProxyDevUsbIp->pUrbUsbIp = NULL; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + usbProxyUsbIpDisconnect(pProxyDevUsbIp); + + rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + PUSBPROXYURBUSBIP pItNext; + RTListForEachSafe(&pProxyDevUsbIp->ListUrbsInFlight, pIt, pItNext, USBPROXYURBUSBIP, NodeList) + { + if (pIt->pVUsbUrb) /* can be NULL for requests created by usbProxyUsbIpCtrlUrbExchangeSync(). */ + pIt->pVUsbUrb->enmStatus = VUSBSTATUS_CRC; + RTListNodeRemove(&pIt->NodeList); + RTListAppend(&pProxyDevUsbIp->ListUrbsLanded, &pIt->NodeList); + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + } + + return rc; +} + +/** + * Worker for queueing an URB on the main I/O thread. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The USB/IP URB to queue. + */ +static int usbProxyUsbIpUrbQueueWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + PVUSBURB pUrb = pUrbUsbIp->pVUsbUrb; + + pUrbUsbIp->u32SeqNumUrb = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + pUrbUsbIp->enmType = pUrb->enmType; + pUrbUsbIp->enmStatus = pUrb->enmStatus; + pUrbUsbIp->enmDir = pUrb->enmDir; + + UsbIpReqSubmit ReqSubmit; + + RT_ZERO(ReqSubmit); + ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; + ReqSubmit.Hdr.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; + ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqSubmit.Hdr.u32Endpoint = pUrb->EndPt; + ReqSubmit.Hdr.u32Direction = pUrb->enmDir == VUSBDIRECTION_IN ? USBIP_DIR_IN : USBIP_DIR_OUT; + ReqSubmit.u32XferFlags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_SHORT_NOT_OK; + + ReqSubmit.u32TransferBufferLength = pUrb->cbData; + ReqSubmit.u32StartFrame = 0; + ReqSubmit.u32NumIsocPkts = 0; + ReqSubmit.u32Interval = 0; + + RTSGSEG aSegReq[3]; /* Maximum number of segments used for a Isochronous transfer. */ + UsbIpIsocPktDesc aIsocPktsDesc[8]; + unsigned cSegsUsed = 1; + aSegReq[0].pvSeg = &ReqSubmit; + aSegReq[0].cbSeg = sizeof(ReqSubmit); + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + memcpy(&ReqSubmit.Setup, &pUrb->abData, sizeof(ReqSubmit.Setup)); + ReqSubmit.u32TransferBufferLength -= sizeof(VUSBSETUP); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData - sizeof(VUSBSETUP); + aSegReq[cSegsUsed].pvSeg = pUrb->abData + sizeof(VUSBSETUP); + if (aSegReq[cSegsUsed].cbSeg) + cSegsUsed++; + } + LogFlowFunc(("Message (Control) URB\n")); + break; + case VUSBXFERTYPE_ISOC: + LogFlowFunc(("Isochronous URB\n")); + ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_ISO_ASAP; + ReqSubmit.u32NumIsocPkts = pUrb->cIsocPkts; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData; + aSegReq[cSegsUsed].pvSeg = pUrb->abData; + cSegsUsed++; + } + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + aIsocPktsDesc[i].u32Offset = pUrb->aIsocPkts[i].off; + aIsocPktsDesc[i].u32Length = pUrb->aIsocPkts[i].cb; + aIsocPktsDesc[i].u32ActualLength = 0; /** @todo */ + aIsocPktsDesc[i].i32Status = pUrb->aIsocPkts[i].enmStatus; + usbProxyUsbIpIsocPktDescH2N(&aIsocPktsDesc[i]); + } + + if (pUrb->cIsocPkts) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc); + aSegReq[cSegsUsed].pvSeg = &aIsocPktsDesc[0]; + cSegsUsed++; + } + + break; + case VUSBXFERTYPE_BULK: + case VUSBXFERTYPE_INTR: + LogFlowFunc(("Bulk URB\n")); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData; + aSegReq[cSegsUsed].pvSeg = pUrb->abData; + cSegsUsed++; + } + break; + default: + return VERR_INVALID_PARAMETER; /** @todo better status code. */ + } + + usbProxyUsbIpReqSubmitH2N(&ReqSubmit); + + Assert(cSegsUsed <= RT_ELEMENTS(aSegReq)); + + /* Send the command. */ + RTSGBUF SgBufReq; + RTSgBufInit(&SgBufReq, &aSegReq[0], cSegsUsed); + + int rc = RTTcpSgWrite(pProxyDevUsbIp->hSocket, &SgBufReq); + if (RT_SUCCESS(rc)) + { + /* Link the URB into the list of in flight URBs. */ + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, pUrbUsbIp); + } + + return rc; +} + +/** + * Queues all pending URBs from the list. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpUrbsQueuePending(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + RTLISTANCHOR ListUrbsPending; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListMove(&ListUrbsPending, &pProxyDevUsbIp->ListUrbsToQueue); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + PUSBPROXYURBUSBIP pIter; + PUSBPROXYURBUSBIP pIterNext; + RTListForEachSafe(&ListUrbsPending, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + rc = usbProxyUsbIpUrbQueueWorker(pProxyDevUsbIp, pIter); + if (RT_FAILURE(rc)) + { + /* Complete URB with an error and place into landed list. */ + pIter->pVUsbUrb->enmStatus = VUSBSTATUS_DNR; + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pIter); + } + } + + return VINF_SUCCESS; +} + +/** + * Kick the reaper thread. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param bReason The wakeup reason. + */ +static char usbProxyReaperKick(PUSBPROXYDEVUSBIP pProxyDevUsbIp, char bReason) +{ + int rc = VINF_SUCCESS; + size_t cbWritten = 0; + + rc = RTPipeWrite(pProxyDevUsbIp->hPipeW, &bReason, 1, &cbWritten); + Assert(RT_SUCCESS(rc) || cbWritten == 0); + + return rc; +} + +/** + * Drain the wakeup pipe. + * + * @returns Wakeup reason. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static char usbProxyUsbIpWakeupPipeDrain(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + char bRead = 0; + size_t cbRead = 0; + int rc = RTPipeRead(pProxyDevUsbIp->hPipeR, &bRead, 1, &cbRead); + Assert(RT_SUCCESS(rc) && cbRead == 1); NOREF(rc); + + return bRead; +} + +/** + * Executes the poll/receive loop either until a URB is received (with an optional matching sequence number) or + * the given timeout has elapsed. + * + * @returns Pointer to the received USB/IP URB or NULL on timeout or error. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNumRet The sequence number of a specific reply to return the URB for, 0 if + * any received URB is accepted. + * @param fPollWakePipe Flag whether to poll the wakeup pipe. + * @param cMillies Maximum number of milliseconds to wait for an URB to arrive. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpPollWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNumRet, + bool fPollWakePipe, RTMSINTERVAL cMillies) +{ + int rc = VINF_SUCCESS; + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + + if (!fPollWakePipe) + { + rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_ERROR); + AssertRC(rc); + } + + while (!pUrbUsbIp && RT_SUCCESS(rc) && cMillies) + { + uint32_t uIdReady = 0; + uint32_t fEventsRecv = 0; + RTMSINTERVAL msStart = RTTimeMilliTS(); + RTMSINTERVAL msNow; + + rc = RTPoll(pProxyDevUsbIp->hPollSet, cMillies, &fEventsRecv, &uIdReady); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); + if (RT_SUCCESS(rc)) + { + msNow = RTTimeMilliTS(); + cMillies = msNow - msStart >= cMillies ? 0 : cMillies - (msNow - msStart); + + if (uIdReady == USBIP_POLL_ID_SOCKET) + { + rc = usbProxyUsbIpRecvPdu(pProxyDevUsbIp, &pUrbUsbIp); + if ( RT_SUCCESS(rc) + && pUrbUsbIp) + { + /* Link the URB into the landed list if a specifc reply is requested and the URB doesn't match. */ + if ( u32SeqNumRet != 0 + && pUrbUsbIp->u32SeqNumUrb != u32SeqNumRet) + { + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pUrbUsbIp); + pUrbUsbIp = NULL; + } + } + } + else + { + AssertLogRelMsg(uIdReady == USBIP_POLL_ID_PIPE, ("Invalid pollset ID given\n")); + + char bReason = usbProxyUsbIpWakeupPipeDrain(pProxyDevUsbIp); + if (bReason == USBIP_REAPER_WAKEUP_REASON_QUEUE) + usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); + else + { + Assert(bReason == USBIP_REAPER_WAKEUP_REASON_EXTERNAL); + break; + } + } + } + } + + if (!fPollWakePipe) + { + rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_READ); + AssertRC(rc); + } + + return pUrbUsbIp; +} + +/** + * Synchronously exchange a given control message with the remote device. + * + * @eturns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pSetup The setup message. + * + * @note This method is only used to implement the *SetConfig, *SetInterface and *ClearHaltedEp + * callbacks because the USB/IP protocol lacks dedicated requests for these. + * @remark It is assumed that this method is never called while usbProxyUsbIpUrbReap is called + * on another thread. + */ +static int usbProxyUsbIpCtrlUrbExchangeSync(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PVUSBSETUP pSetup) +{ + int rc = VINF_SUCCESS; + + UsbIpReqSubmit ReqSubmit; + USBPROXYURBUSBIP UsbIpUrb; + + RT_ZERO(ReqSubmit); + + uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; + ReqSubmit.Hdr.u32SeqNum = u32SeqNum; + ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqSubmit.Hdr.u32Direction = USBIP_DIR_OUT; + ReqSubmit.Hdr.u32Endpoint = 0; /* Only default control endpoint is allowed for these kind of messages. */ + ReqSubmit.u32XferFlags = 0; + ReqSubmit.u32TransferBufferLength = 0; + ReqSubmit.u32StartFrame = 0; + ReqSubmit.u32NumIsocPkts = 0; + ReqSubmit.u32Interval = 0; + memcpy(&ReqSubmit.Setup, pSetup, sizeof(ReqSubmit.Setup)); + usbProxyUsbIpReqSubmitH2N(&ReqSubmit); + + UsbIpUrb.u32SeqNumUrb = u32SeqNum; + UsbIpUrb.u32SeqNumUrbUnlink = 0; + UsbIpUrb.fCancelled = false; + UsbIpUrb.enmType = VUSBXFERTYPE_MSG; + UsbIpUrb.enmDir = VUSBDIRECTION_OUT; + UsbIpUrb.pVUsbUrb = NULL; + + /* Send the command. */ + rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqSubmit, sizeof(ReqSubmit)); + if (RT_SUCCESS(rc)) + { + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, &UsbIpUrb); + PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, u32SeqNum, false /*fPollWakePipe*/, + 30 * RT_MS_1SEC); + Assert( !pUrbUsbIp + || pUrbUsbIp == &UsbIpUrb); /* The returned URB should point to the URB we submitted. */ + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, &UsbIpUrb); + + if (!pUrbUsbIp) + rc = VERR_TIMEOUT; + } + + return rc; +} + + +/* + * The USB proxy device functions. + */ + +/** + * @interface_method_impl{USBPROXYBACK,pfnOpen} + */ +static DECLCALLBACK(int) usbProxyUsbIpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlowFunc(("pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + int rc = VINF_SUCCESS; + + RTListInit(&pDevUsbIp->ListUrbsInFlight); + RTListInit(&pDevUsbIp->ListUrbsLanded); + RTListInit(&pDevUsbIp->ListUrbsToQueue); + pDevUsbIp->hSocket = NIL_RTSOCKET; + pDevUsbIp->hPollSet = NIL_RTPOLLSET; + pDevUsbIp->hPipeW = NIL_RTPIPE; + pDevUsbIp->hPipeR = NIL_RTPIPE; + pDevUsbIp->u32SeqNumNext = 0; + pDevUsbIp->pszHost = NULL; + pDevUsbIp->pszBusId = NULL; + usbProxyUsbIpResetRecvState(pDevUsbIp); + + rc = RTSemFastMutexCreate(&pDevUsbIp->hMtxLists); + if (RT_SUCCESS(rc)) + { + /* Setup wakeup pipe and poll set first. */ + rc = RTPipeCreate(&pDevUsbIp->hPipeR, &pDevUsbIp->hPipeW, 0); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetCreate(&pDevUsbIp->hPollSet); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetAddPipe(pDevUsbIp->hPollSet, pDevUsbIp->hPipeR, + RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE); + if (RT_SUCCESS(rc)) + { + /* Connect to the USB/IP host. */ + rc = usbProxyUsbIpParseAddress(pDevUsbIp, pszAddress); + if (RT_SUCCESS(rc)) + rc = usbProxyUsbIpConnect(pDevUsbIp); + } + + if (RT_FAILURE(rc)) + { + RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); + int rc2 = RTPollSetDestroy(pDevUsbIp->hPollSet); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + { + int rc2 = RTPipeClose(pDevUsbIp->hPipeR); + AssertRC(rc2); + rc2 = RTPipeClose(pDevUsbIp->hPipeW); + AssertRC(rc2); + } + } + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClose} + */ +static DECLCALLBACK(void) usbProxyUsbIpClose(PUSBPROXYDEV pProxyDev) +{ + int rc = VINF_SUCCESS; + LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + if (pDevUsbIp->hSocket != NIL_RTSOCKET) + usbProxyUsbIpDisconnect(pDevUsbIp); + + /* Destroy the pipe and pollset if necessary. */ + if (pDevUsbIp->hPollSet != NIL_RTPOLLSET) + { + rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); + AssertRC(rc); + rc = RTPollSetDestroy(pDevUsbIp->hPollSet); + AssertRC(rc); + rc = RTPipeClose(pDevUsbIp->hPipeR); + AssertRC(rc); + rc = RTPipeClose(pDevUsbIp->hPipeW); + AssertRC(rc); + } + + if (pDevUsbIp->pszHost) + RTStrFree(pDevUsbIp->pszHost); + if (pDevUsbIp->pszBusId) + RTStrFree(pDevUsbIp->pszBusId); + + /* Clear the URB lists. */ + rc = RTSemFastMutexRequest(pDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIter; + PUSBPROXYURBUSBIP pIterNext; + RTListForEachSafe(&pDevUsbIp->ListUrbsInFlight, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + RTMemFree(pIter); + } + + RTListForEachSafe(&pDevUsbIp->ListUrbsLanded, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + RTMemFree(pIter); + } + RTSemFastMutexRelease(pDevUsbIp->hMtxLists); + RTSemFastMutexDestroy(pDevUsbIp->hMtxLists); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnReset} + */ +static DECLCALLBACK(int) usbProxyUsbIpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); + + int rc = VINF_SUCCESS; + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + if (fResetOnLinux) + { + Setup.bmRequestType = RT_BIT(5) | 0x03; /* Port request. */ + Setup.bRequest = 0x03; /* SET_FEATURE */ + Setup.wValue = 4; /* Port feature: Reset */ + Setup.wIndex = 0; /* Port number, irrelevant */ + Setup.wLength = 0; + rc = usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); + if (RT_SUCCESS(rc)) + { + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 2; + } + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnSetConfig} + */ +static DECLCALLBACK(int) usbProxyUsbIpSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlowFunc(("pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, iCfg)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0; + Setup.bRequest = 0x09; + Setup.wValue = iCfg; + Setup.wIndex = 0; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClaimInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnReleaseInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnSetInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting) +{ + LogFlowFunc(("pProxyDev=%p iIf=%#x setting=%#x\n", pProxyDev, iIf, setting)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0x1; + Setup.bRequest = 0x0b; /* SET_INTERFACE */ + Setup.wValue = setting; + Setup.wIndex = iIf; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClearHaltedEndpoint} + */ +static DECLCALLBACK(int) usbProxyUsbIpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int iEp) +{ + LogFlowFunc(("pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, iEp)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0x2; + Setup.bRequest = 0x01; /* CLEAR_FEATURE */ + Setup.wValue = 0x00; /* ENDPOINT_HALT */ + Setup.wIndex = iEp; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyUsbIpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlowFunc(("pUrb=%p\n", pUrb)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + + /* Allocate a USB/IP Urb. */ + PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpUrbAlloc(pProxyDevUsbIp); + if (!pUrbUsbIp) + return VERR_NO_MEMORY; + + pUrbUsbIp->fCancelled = false; + pUrbUsbIp->pVUsbUrb = pUrb; + pUrb->Dev.pvPrivate = pUrbUsbIp; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListAppend(&pProxyDevUsbIp->ListUrbsToQueue, &pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_QUEUE); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbReap} + */ +static DECLCALLBACK(PVUSBURB) usbProxyUsbIpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + PVUSBURB pUrb = NULL; + int rc = VINF_SUCCESS; + + /* Queue new URBs first. */ + rc = usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); + AssertRC(rc); + + /* Any URBs pending delivery? */ + if (!RTListIsEmpty(&pProxyDevUsbIp->ListUrbsLanded)) + pUrbUsbIp = RTListGetFirst(&pProxyDevUsbIp->ListUrbsLanded, USBPROXYURBUSBIP, NodeList); + else + pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, 0, true /*fPollWakePipe*/, cMillies); + + if (pUrbUsbIp) + { + pUrb = pUrbUsbIp->pVUsbUrb; + pUrb->enmStatus = pUrbUsbIp->enmStatus; + + /* unlink from the pending delivery list */ + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); + usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp); + } + + return pUrb; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbCancel} + */ +static DECLCALLBACK(int) usbProxyUsbIpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlowFunc(("pUrb=%p\n", pUrb)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + PUSBPROXYURBUSBIP pUrbUsbIp = (PUSBPROXYURBUSBIP)pUrb->Dev.pvPrivate; + UsbIpReqUnlink ReqUnlink; + + RT_ZERO(ReqUnlink); + + uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + ReqUnlink.Hdr.u32ReqRet = USBIP_CMD_UNLINK; + ReqUnlink.Hdr.u32SeqNum = u32SeqNum; + ReqUnlink.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqUnlink.Hdr.u32Direction = USBIP_DIR_OUT; + ReqUnlink.Hdr.u32Endpoint = pUrb->EndPt; + ReqUnlink.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; + + usbProxyUsbIpReqUnlinkH2N(&ReqUnlink); + int rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqUnlink, sizeof(ReqUnlink)); + if (RT_SUCCESS(rc)) + { + pUrbUsbIp->u32SeqNumUrbUnlink = u32SeqNum; + pUrbUsbIp->fCancelled = true; + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnWakeup} + */ +static DECLCALLBACK(int) usbProxyUsbIpWakeup(PUSBPROXYDEV pProxyDev) +{ + LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_EXTERNAL); +} + + +/** + * The USB/IP USB Proxy Backend operations. + */ +extern const USBPROXYBACK g_USBProxyDeviceUsbIp = +{ + /* pszName */ + "usbip", + /* cbBackend */ + sizeof(USBPROXYDEVUSBIP), + usbProxyUsbIpOpen, + NULL, + usbProxyUsbIpClose, + usbProxyUsbIpReset, + usbProxyUsbIpSetConfig, + usbProxyUsbIpClaimInterface, + usbProxyUsbIpReleaseInterface, + usbProxyUsbIpSetInterface, + usbProxyUsbIpClearHaltedEp, + usbProxyUsbIpUrbQueue, + usbProxyUsbIpUrbCancel, + usbProxyUsbIpUrbReap, + usbProxyUsbIpWakeup, + 0 +}; diff --git a/src/VBox/Devices/USB/vrdp/Makefile.kup b/src/VBox/Devices/USB/vrdp/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/vrdp/Makefile.kup diff --git a/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp new file mode 100644 index 00000000..b1969172 --- /dev/null +++ b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp @@ -0,0 +1,322 @@ +/* $Id: USBProxyDevice-vrdp.cpp $ */ +/** @file + * USB device proxy - the VRDP backend, calls the RemoteUSBBackend methods. + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vrdpusb.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/uuid.h> + +#include "../USBProxyDevice.h" + +/** + * Backend data for the VRDP USB Proxy device backend. + */ +typedef struct USBPROXYDEVVRDP +{ + REMOTEUSBCALLBACK *pCallback; + PREMOTEUSBDEVICE pDevice; +} USBPROXYDEVVRDP, *PUSBPROXYDEVVRDP; + + +/* + * The USB proxy device functions. + */ + +static DECLCALLBACK(int) usbProxyVrdpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlow(("usbProxyVrdpOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + PPDMUSBINS pUsbIns = pProxyDev->pUsbIns; + PCPDMUSBHLP pHlp = pUsbIns->pHlpR3; + + PCFGMNODE pCfgBackend = pHlp->pfnCFGMGetChild(pUsbIns->pCfg, "BackendCfg"); + AssertPtrReturn(pCfgBackend, VERR_NOT_FOUND); + + uint32_t idClient = 0; + int rc = pHlp->pfnCFGMQueryU32(pCfgBackend, "ClientId", &idClient); + AssertRCReturn(rc, rc); + + RTUUID UuidDev; + char *pszUuid = NULL; + + rc = pHlp->pfnCFGMQueryStringAlloc(pUsbIns->pCfg, "UUID", &pszUuid); + AssertRCReturn(rc, rc); + + rc = RTUuidFromStr(&UuidDev, pszUuid); + pHlp->pfnMMHeapFree(pUsbIns, pszUuid); + AssertMsgRCReturn(rc, ("Failed to convert UUID from string! rc=%Rrc\n", rc), rc); + + if (strncmp (pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) == 0) + { + RTUUID UuidRemoteUsbIf; + rc = RTUuidFromStr(&UuidRemoteUsbIf, REMOTEUSBIF_OID); AssertRC(rc); + + PREMOTEUSBIF pRemoteUsbIf = (PREMOTEUSBIF)PDMUsbHlpQueryGenericUserObject(pUsbIns, &UuidRemoteUsbIf); + AssertPtrReturn(pRemoteUsbIf, VERR_INVALID_PARAMETER); + + REMOTEUSBCALLBACK *pCallback = pRemoteUsbIf->pfnQueryRemoteUsbBackend(pRemoteUsbIf->pvUser, &UuidDev, idClient); + AssertPtrReturn(pCallback, VERR_INVALID_PARAMETER); + + PREMOTEUSBDEVICE pDevice = NULL; + rc = pCallback->pfnOpen(pCallback->pInstance, pszAddress, strlen (pszAddress) + 1, &pDevice); + if (RT_SUCCESS(rc)) + { + pDevVrdp->pCallback = pCallback; + pDevVrdp->pDevice = pDevice; + pProxyDev->iActiveCfg = 1; /** @todo that may not be always true. */ + pProxyDev->cIgnoreSetConfigs = 1; + return VINF_SUCCESS; + } + } + else + { + AssertFailed(); + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +static DECLCALLBACK(void) usbProxyVrdpClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyVrdpClose: pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + pDevVrdp->pCallback->pfnClose (pDevVrdp->pDevice); +} + +static DECLCALLBACK(int) usbProxyVrdpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + LogFlow(("usbProxyVrdpReset: pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnReset (pDevVrdp->pDevice); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 2; + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpSetConfig(PUSBPROXYDEV pProxyDev, int cfg) +{ + LogFlow(("usbProxyVrdpSetConfig: pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, cfg)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnSetConfig (pDevVrdp->pDevice, (uint8_t)cfg); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpClaimInterface(PUSBPROXYDEV pProxyDev, int ifnum) +{ + LogFlow(("usbProxyVrdpClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnClaimInterface (pDevVrdp->pDevice, (uint8_t)ifnum); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpReleaseInterface(PUSBPROXYDEV pProxyDev, int ifnum) +{ + LogFlow(("usbProxyVrdpReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnReleaseInterface (pDevVrdp->pDevice, (uint8_t)ifnum); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpSetInterface(PUSBPROXYDEV pProxyDev, int ifnum, int setting) +{ + LogFlow(("usbProxyVrdpSetInterface: pProxyDev=%p ifnum=%#x setting=%#x\n", pProxyDev, ifnum, setting)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnInterfaceSetting (pDevVrdp->pDevice, (uint8_t)ifnum, (uint8_t)setting); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + LogFlow(("usbProxyVrdpClearHaltedEp: pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, ep)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnClearHaltedEP (pDevVrdp->pDevice, (uint8_t)ep); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlow(("usbProxyVrdpUrbQueue: pUrb=%p\n", pUrb)); + + /** @todo implement isochronous transfers for USB over VRDP. */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + Log(("usbproxy: isochronous transfers aren't implemented yet.\n")); + return VERR_NOT_IMPLEMENTED; + } + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + int rc = pDevVrdp->pCallback->pfnQueueURB (pDevVrdp->pDevice, pUrb->enmType, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, + pUrb->abData, pUrb, (PREMOTEUSBQURB *)&pUrb->Dev.pvPrivate); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(PVUSBURB) usbProxyVrdpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlow(("usbProxyVrdpUrbReap: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + PVUSBURB pUrb = NULL; + uint32_t cbData = 0; + uint32_t u32Err = VUSBSTATUS_OK; + + int rc = pDevVrdp->pCallback->pfnReapURB (pDevVrdp->pDevice, cMillies, (void **)&pUrb, &cbData, &u32Err); + + LogFlow(("usbProxyVrdpUrbReap: rc = %Rrc, pUrb = %p\n", rc, pUrb)); + + if (RT_SUCCESS(rc) && pUrb) + { + pUrb->enmStatus = (VUSBSTATUS)u32Err; + pUrb->cbData = cbData; + pUrb->Dev.pvPrivate = NULL; + } + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return pUrb; +} + +static DECLCALLBACK(int) usbProxyVrdpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlow(("usbProxyVrdpUrbCancel: pUrb=%p\n", pUrb)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + pDevVrdp->pCallback->pfnCancelURB (pDevVrdp->pDevice, (PREMOTEUSBQURB)pUrb->Dev.pvPrivate); + return VINF_SUCCESS; /** @todo Enhance remote interface to pass a status code. */ +} + +static DECLCALLBACK(int) usbProxyVrdpWakeup(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyVrdpWakeup: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + return pDevVrdp->pCallback->pfnWakeup (pDevVrdp->pDevice); +} + +/** + * The VRDP USB Proxy Backend operations. + */ +extern const USBPROXYBACK g_USBProxyDeviceVRDP = +{ + /* pszName */ + "vrdp", + /* cbBackend */ + sizeof(USBPROXYDEVVRDP), + usbProxyVrdpOpen, + NULL, + usbProxyVrdpClose, + usbProxyVrdpReset, + usbProxyVrdpSetConfig, + usbProxyVrdpClaimInterface, + usbProxyVrdpReleaseInterface, + usbProxyVrdpSetInterface, + usbProxyVrdpClearHaltedEp, + usbProxyVrdpUrbQueue, + usbProxyVrdpUrbCancel, + usbProxyVrdpUrbReap, + usbProxyVrdpWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/win/Makefile.kup b/src/VBox/Devices/USB/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/win/Makefile.kup diff --git a/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp new file mode 100644 index 00000000..26c28f9b --- /dev/null +++ b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp @@ -0,0 +1,813 @@ +/* $Id: USBProxyDevice-win.cpp $ */ +/** @file + * USBPROXY - USB proxy, Win32 backend + */ + +/* + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <iprt/win/windows.h> + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/usb.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/asm.h> +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _QUEUED_URB +{ + PVUSBURB urb; + + USBSUP_URB urbwin; + OVERLAPPED overlapped; + DWORD cbReturned; + bool fCancelled; +} QUEUED_URB, *PQUEUED_URB; + +typedef struct +{ + /* Critical section to protect this structure. */ + RTCRITSECT CritSect; + HANDLE hDev; + uint8_t bInterfaceNumber; + bool fClaimed; + /** Set if reaper should exit ASAP. */ + bool fWakeUpNow; + /** The allocated size of paHandles and paQueuedUrbs. */ + unsigned cAllocatedUrbs; + /** The number of URBs in the array. */ + unsigned cQueuedUrbs; + /** Array of pointers to the in-flight URB structures. */ + PQUEUED_URB *paQueuedUrbs; + /** Array of handles, this is parallel to paQueuedUrbs. */ + PHANDLE paHandles; + /* Event sempahore to wakeup the reaper thead. */ + HANDLE hEventWakeup; + /** Number of queued URBs waiting to get into the handle list. */ + unsigned cPendingUrbs; + /** Array of pending URBs. */ + PQUEUED_URB aPendingUrbs[64]; +} PRIV_USBW32, *PPRIV_USBW32; + +/* All functions are returning 1 on success, 0 on error */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usbProxyWinSetInterface(PUSBPROXYDEV p, int iIf, int setting); + +/** + * Converts the given Windows error code to VBox handling unplugged devices. + * + * @returns VBox status code. + * @param pProxDev The USB proxy device instance. + * @param dwErr Windows error code. + */ +static int usbProxyWinHandleUnpluggedDevice(PUSBPROXYDEV pProxyDev, DWORD dwErr) +{ +#ifdef LOG_ENABLED + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); +#endif + + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %x unplugged!! (usbProxyWinHandleUnpluggedDevice)\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + else + AssertMsgFailed(("lasterr=%d\n", dwErr)); + return RTErrConvertFromWin32(dwErr); +} + +/** + * Open a USB device and create a backend instance for it. + * + * @returns VBox status code. + */ +static DECLCALLBACK(int) usbProxyWinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + int rc = VINF_SUCCESS; + pPriv->cAllocatedUrbs = 32; + pPriv->paHandles = (PHANDLE)RTMemAllocZ(sizeof(pPriv->paHandles[0]) * pPriv->cAllocatedUrbs); + pPriv->paQueuedUrbs = (PQUEUED_URB *)RTMemAllocZ(sizeof(pPriv->paQueuedUrbs[0]) * pPriv->cAllocatedUrbs); + if ( pPriv->paQueuedUrbs + && pPriv->paHandles) + { + /* + * Open the device. + */ + pPriv->hDev = CreateFile(pszAddress, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, // overlapped IO + NULL); // No template file + if (pPriv->hDev != INVALID_HANDLE_VALUE) + { + Log(("usbProxyWinOpen: hDev=%p\n", pPriv->hDev)); + + /* + * Check the version + */ + USBSUP_VERSION version = {0}; + DWORD cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_GET_VERSION, NULL, 0, &version, sizeof(version), &cbReturned, NULL)) + { + if ( version.u32Major == USBDRV_MAJOR_VERSION +#if USBDRV_MINOR_VERSION != 0 + && version.u32Minor >= USBDRV_MINOR_VERSION +#endif + ) + { + USBSUP_CLAIMDEV in; + in.bInterfaceNumber = 0; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLAIM_DEVICE, &in, sizeof(in), &in, sizeof(in), &cbReturned, NULL)) + { + if (in.fClaimed) + { + pPriv->fClaimed = true; +#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */ + pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 1; +#endif + + rc = RTCritSectInit(&pPriv->CritSect); + AssertRC(rc); + pPriv->hEventWakeup = CreateEvent(NULL, FALSE, FALSE, NULL); + Assert(pPriv->hEventWakeup); + + pPriv->paHandles[0] = pPriv->hEventWakeup; + + return VINF_SUCCESS; + } + + rc = VERR_GENERAL_FAILURE; + Log(("usbproxy: unable to claim device %x (%s)!!\n", pPriv->hDev, pszAddress)); + } + } + else + { + rc = VERR_VERSION_MISMATCH; + Log(("usbproxy: Version mismatch: %d.%d != %d.%d (cur)\n", + version.u32Major, version.u32Minor, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION)); + } + } + + /* Convert last error if necessary */ + if (RT_SUCCESS(rc)) + { + DWORD dwErr = GetLastError(); + Log(("usbproxy: last error %d\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(pPriv->hDev); + pPriv->hDev = INVALID_HANDLE_VALUE; + } + else + { + Log(("usbproxy: FAILED to open '%s'! last error %d\n", pszAddress, GetLastError())); + rc = VERR_FILE_NOT_FOUND; + } + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pPriv->paQueuedUrbs); + RTMemFree(pPriv->paHandles); + return rc; +} + +/** + * Copy the device and free resources associated with the backend. + */ +static DECLCALLBACK(void) usbProxyWinClose(PUSBPROXYDEV pProxyDev) +{ + /* Here we just close the device and free up p->priv + * there is no need to do anything like cancel outstanding requests + * that will have been done already + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + Assert(pPriv); + if (!pPriv) + return; + Log(("usbProxyWinClose: %p\n", pPriv->hDev)); + + if (pPriv->hDev != INVALID_HANDLE_VALUE) + { + Assert(pPriv->fClaimed); + + USBSUP_RELEASEDEV in; + DWORD cbReturned = 0; + in.bInterfaceNumber = pPriv->bInterfaceNumber; + if (!DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RELEASE_DEVICE, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + { + Log(("usbproxy: usbProxyWinClose: DeviceIoControl %#x failed with %#x!!\n", pPriv->hDev, GetLastError())); + } + if (!CloseHandle(pPriv->hDev)) + AssertLogRelMsgFailed(("usbproxy: usbProxyWinClose: CloseHandle %#x failed with %#x!!\n", pPriv->hDev, GetLastError())); + pPriv->hDev = INVALID_HANDLE_VALUE; + } + + CloseHandle(pPriv->hEventWakeup); + RTCritSectDelete(&pPriv->CritSect); + + RTMemFree(pPriv->paQueuedUrbs); + RTMemFree(pPriv->paHandles); +} + + +static DECLCALLBACK(int) usbProxyWinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + DWORD cbReturned; + int rc; + + Assert(pPriv); + + Log(("usbproxy: Reset %x\n", pPriv->hDev)); + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RESET, NULL, 0, NULL, 0, &cbReturned, NULL)) + { +#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */ + pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 2; +#else + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 0; +#endif + return VINF_SUCCESS; + } + + rc = GetLastError(); + if (rc == ERROR_DEVICE_REMOVED) + { + Log(("usbproxy: device %p unplugged!! (usbProxyWinReset)\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + return RTErrConvertFromWin32(rc); +} + +static DECLCALLBACK(int) usbProxyWinSetConfig(PUSBPROXYDEV pProxyDev, int cfg) +{ + /* Send a SET_CONFIGURATION command to the device. We don't do this + * as a normal control message, because the OS might not want to + * be left out of the loop on such a thing. + * + * It would be OK to send a SET_CONFIGURATION control URB at this + * point but it has to be synchronous. + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_SET_CONFIG in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Set config of %p to %d\n", pPriv->hDev, cfg)); + in.bConfigurationValue = cfg; + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SET_CONFIG, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +static DECLCALLBACK(int) usbProxyWinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + /* Called just before we use an interface. Needed on Linux to claim + * the interface from the OS, since even when proxying the host OS + * might want to allow other programs to use the unused interfaces. + * Not relevant for Windows. + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + pPriv->bInterfaceNumber = iIf; + + Assert(pPriv); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) usbProxyWinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + /* The opposite of claim_interface. */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) usbProxyWinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting) +{ + /* Select an alternate setting for an interface, the same applies + * here as for set_config, you may convert this in to a control + * message if you want but it must be synchronous + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_SELECT_INTERFACE in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Select interface of %x to %d/%d\n", pPriv->hDev, iIf, setting)); + in.bInterfaceNumber = iIf; + in.bAlternateSetting = setting; + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SELECT_INTERFACE, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * Clears the halted endpoint 'ep'. + */ +static DECLCALLBACK(int) usbProxyWinClearHaltedEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Clear endpoint %d of %x\n", ep, pPriv->hDev)); + in.bEndpoint = ep; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLEAR_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * Aborts a pipe/endpoint (cancels all outstanding URBs on the endpoint). + */ +static int usbProxyWinAbortEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Abort endpoint %d of %x\n", ep, pPriv->hDev)); + in.bEndpoint = ep; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyWinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + Assert(pPriv); + + /* Don't even bother if we can't wait for that many objects. */ + if (pPriv->cPendingUrbs + pPriv->cQueuedUrbs >= (MAXIMUM_WAIT_OBJECTS - 1)) + return VERR_OUT_OF_RESOURCES; + if (pPriv->cPendingUrbs >= RT_ELEMENTS(pPriv->aPendingUrbs)) + return VERR_OUT_OF_RESOURCES; + + /* + * Allocate and initialize a URB queue structure. + */ + /** @todo pool these */ + PQUEUED_URB pQUrbWin = (PQUEUED_URB)RTMemAllocZ(sizeof(QUEUED_URB)); + if (!pQUrbWin) + return VERR_NO_MEMORY; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_CTRL; break; /* you won't ever see these */ + case VUSBXFERTYPE_ISOC: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_ISOC; + pQUrbWin->urbwin.numIsoPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < pUrb->cIsocPkts; ++i) + { + pQUrbWin->urbwin.aIsoPkts[i].cb = pUrb->aIsocPkts[i].cb; + pQUrbWin->urbwin.aIsoPkts[i].off = pUrb->aIsocPkts[i].off; + pQUrbWin->urbwin.aIsoPkts[i].stat = USBSUP_XFER_OK; + } + break; + case VUSBXFERTYPE_BULK: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_BULK; break; + case VUSBXFERTYPE_INTR: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_INTR; break; + case VUSBXFERTYPE_MSG: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_MSG; break; + default: + AssertMsgFailed(("Invalid type %d\n", pUrb->enmType)); + return VERR_INVALID_PARAMETER; + } + + switch (pUrb->enmDir) + { + case VUSBDIRECTION_SETUP: + AssertFailed(); + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_SETUP; + break; + case VUSBDIRECTION_IN: + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_IN; + break; + case VUSBDIRECTION_OUT: + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_OUT; + break; + default: + AssertMsgFailed(("Invalid direction %d\n", pUrb->enmDir)); + return VERR_INVALID_PARAMETER; + } + + Log(("usbproxy: Queue URB %p ep=%d cbData=%d abData=%p cIsocPkts=%d\n", pUrb, pUrb->EndPt, pUrb->cbData, pUrb->abData, pUrb->cIsocPkts)); + + pQUrbWin->urb = pUrb; + pQUrbWin->urbwin.ep = pUrb->EndPt; + pQUrbWin->urbwin.len = pUrb->cbData; + pQUrbWin->urbwin.buf = pUrb->abData; + pQUrbWin->urbwin.error = USBSUP_XFER_OK; + pQUrbWin->urbwin.flags = USBSUP_FLAG_NONE; + if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk) + pQUrbWin->urbwin.flags = USBSUP_FLAG_SHORT_OK; + + int rc = VINF_SUCCESS; + pQUrbWin->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE) + { + pUrb->Dev.pvPrivate = pQUrbWin; + + if ( DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_SEND_URB, + &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin), + &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin), + &pQUrbWin->cbReturned, &pQUrbWin->overlapped) + || GetLastError() == ERROR_IO_PENDING) + { + /* insert into the queue */ + RTCritSectEnter(&pPriv->CritSect); + unsigned j = pPriv->cPendingUrbs; + Assert(j < RT_ELEMENTS(pPriv->aPendingUrbs)); + pPriv->aPendingUrbs[j] = pQUrbWin; + pPriv->cPendingUrbs++; + RTCritSectLeave(&pPriv->CritSect); + SetEvent(pPriv->hEventWakeup); + return VINF_SUCCESS; + } + else + { + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %p unplugged!! (usbProxyWinUrbQueue)\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + else + AssertMsgFailed(("dwErr=%X urbwin.error=%d (submit urb)\n", dwErr, pQUrbWin->urbwin.error)); + rc = RTErrConvertFromWin32(dwErr); + CloseHandle(pQUrbWin->overlapped.hEvent); + pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE; + } + } +#ifdef DEBUG_misha + else + { + AssertMsgFailed(("FAILED!!, hEvent(0x%p)\n", pQUrbWin->overlapped.hEvent)); + rc = VERR_NO_MEMORY; + } +#endif + + Assert(pQUrbWin->overlapped.hEvent == INVALID_HANDLE_VALUE); + RTMemFree(pQUrbWin); + return rc; +} + +/** + * Convert Windows proxy URB status to VUSB status. + * + * @returns VUSB status constant. + * @param win_status Windows USB proxy status constant. + */ +static VUSBSTATUS usbProxyWinStatusToVUsbStatus(USBSUP_ERROR win_status) +{ + VUSBSTATUS vusb_status; + + switch (win_status) + { + case USBSUP_XFER_OK: vusb_status = VUSBSTATUS_OK; break; + case USBSUP_XFER_STALL: vusb_status = VUSBSTATUS_STALL; break; + case USBSUP_XFER_DNR: vusb_status = VUSBSTATUS_DNR; break; + case USBSUP_XFER_CRC: vusb_status = VUSBSTATUS_CRC; break; + case USBSUP_XFER_NAC: vusb_status = VUSBSTATUS_NOT_ACCESSED; break; + case USBSUP_XFER_UNDERRUN: vusb_status = VUSBSTATUS_DATA_UNDERRUN; break; + case USBSUP_XFER_OVERRUN: vusb_status = VUSBSTATUS_DATA_OVERRUN; break; + default: + AssertMsgFailed(("USB: Invalid error %d\n", win_status)); + vusb_status = VUSBSTATUS_DNR; + break; + } + return vusb_status; +} + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not + * wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyWinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + AssertReturn(pPriv, NULL); + + /* + * There are some unnecessary calls, just return immediately or + * WaitForMultipleObjects will fail. + */ + if ( pPriv->cQueuedUrbs <= 0 + && pPriv->cPendingUrbs == 0) + { + Log(("usbproxy: Nothing pending\n")); + if ( cMillies != 0 + && pPriv->cPendingUrbs == 0) + { + /* Wait for the wakeup call. */ + Log(("usbproxy: Waiting for wakeup call\n")); + DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies; + DWORD rc = WaitForMultipleObjects(1, &pPriv->hEventWakeup, FALSE, cMilliesWait); + Log(("usbproxy: Initial wait rc=%X\n", rc)); + if (rc != WAIT_OBJECT_0) { + Log(("usbproxy: Initial wait failed, rc=%X\n", rc)); + return NULL; + } + } + return NULL; + } + +again: + /* Check for pending URBs. */ + Log(("usbproxy: %u pending URBs\n", pPriv->cPendingUrbs)); + if (pPriv->cPendingUrbs) + { + RTCritSectEnter(&pPriv->CritSect); + + /* Ensure we've got sufficient space in the arrays. */ + if (pPriv->cQueuedUrbs + pPriv->cPendingUrbs + 1 > pPriv->cAllocatedUrbs) + { + unsigned cNewMax = pPriv->cAllocatedUrbs + pPriv->cPendingUrbs + 1; + void *pv = RTMemRealloc(pPriv->paHandles, sizeof(pPriv->paHandles[0]) * (cNewMax + 1)); /* One extra for the wakeup event. */ + if (!pv) + { + AssertMsgFailed(("RTMemRealloc failed for paHandles[%d]", cNewMax)); + //break; + } + pPriv->paHandles = (PHANDLE)pv; + + pv = RTMemRealloc(pPriv->paQueuedUrbs, sizeof(pPriv->paQueuedUrbs[0]) * cNewMax); + if (!pv) + { + AssertMsgFailed(("RTMemRealloc failed for paQueuedUrbs[%d]", cNewMax)); + //break; + } + pPriv->paQueuedUrbs = (PQUEUED_URB *)pv; + pPriv->cAllocatedUrbs = cNewMax; + } + + /* Copy the pending URBs over. */ + for (unsigned i = 0; i < pPriv->cPendingUrbs; i++) + { + pPriv->paHandles[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i]->overlapped.hEvent; + pPriv->paQueuedUrbs[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i]; + } + pPriv->cQueuedUrbs += pPriv->cPendingUrbs; + pPriv->cPendingUrbs = 0; + pPriv->paHandles[pPriv->cQueuedUrbs] = pPriv->hEventWakeup; + pPriv->paHandles[pPriv->cQueuedUrbs + 1] = INVALID_HANDLE_VALUE; + + RTCritSectLeave(&pPriv->CritSect); + } + + /* + * Wait/poll. + * + * ASSUMPTION: Multiple usbProxyWinUrbReap calls can not be run concurrently + * with each other so racing the cQueuedUrbs access/modification can not occur. + * + * However, usbProxyWinUrbReap can be run concurrently with usbProxyWinUrbQueue + * and pPriv->paHandles access/realloc must be synchronized. + * + * NB: Due to the design of Windows overlapped I/O, DeviceIoControl calls to submit + * URBs use individual event objects. When a new URB is submitted, we have to add its + * event object to the list of objects that WaitForMultipleObjects is waiting on. Thus + * hEventWakeup has dual purpose, serving to handle proxy wakeup calls meant to abort + * reaper waits, but also waking up the reaper after every URB submit so that the newly + * submitted URB can be added to the list of waiters. + */ + unsigned cQueuedUrbs = ASMAtomicReadU32((volatile uint32_t *)&pPriv->cQueuedUrbs); + DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies; + PVUSBURB pUrb = NULL; + DWORD rc = WaitForMultipleObjects(cQueuedUrbs + 1, pPriv->paHandles, FALSE, cMilliesWait); + Log(("usbproxy: Wait (%d milliseconds) returned with rc=%X\n", cMilliesWait, rc)); + + /* If the wakeup event fired return immediately. */ + if (rc == WAIT_OBJECT_0 + cQueuedUrbs) + { + /* Get outta here flag set? If so, bail now. */ + if (ASMAtomicXchgBool(&pPriv->fWakeUpNow, false)) + { + Log(("usbproxy: Reaper woken up, returning NULL\n")); + return NULL; + } + + /* A new URBs was queued through usbProxyWinUrbQueue() and needs to be + * added to the wait list. Go again. + */ + Log(("usbproxy: Reaper woken up after queuing new URB, go again.\n")); + goto again; + } + + AssertCompile(WAIT_OBJECT_0 == 0); + if (/*rc >= WAIT_OBJECT_0 && */ rc < WAIT_OBJECT_0 + cQueuedUrbs) + { + RTCritSectEnter(&pPriv->CritSect); + unsigned iUrb = rc - WAIT_OBJECT_0; + PQUEUED_URB pQUrbWin = pPriv->paQueuedUrbs[iUrb]; + pUrb = pQUrbWin->urb; + + /* + * Remove it from the arrays. + */ + cQueuedUrbs = --pPriv->cQueuedUrbs; + if (cQueuedUrbs != iUrb) + { + /* Move the array forward */ + for (unsigned i=iUrb;i<cQueuedUrbs;i++) + { + pPriv->paHandles[i] = pPriv->paHandles[i+1]; + pPriv->paQueuedUrbs[i] = pPriv->paQueuedUrbs[i+1]; + } + } + pPriv->paHandles[cQueuedUrbs] = pPriv->hEventWakeup; + pPriv->paHandles[cQueuedUrbs + 1] = INVALID_HANDLE_VALUE; + pPriv->paQueuedUrbs[cQueuedUrbs] = NULL; + RTCritSectLeave(&pPriv->CritSect); + Assert(cQueuedUrbs == pPriv->cQueuedUrbs); + + /* + * Update the urb. + */ + pUrb->enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.error); + pUrb->cbData = (uint32_t)pQUrbWin->urbwin.len; + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; ++i) + { + /* NB: Windows won't change the packet offsets, but the packets may + * be only partially filled or completely empty. + */ + pUrb->aIsocPkts[i].enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.aIsoPkts[i].stat); + pUrb->aIsocPkts[i].cb = pQUrbWin->urbwin.aIsoPkts[i].cb; + } + } + Log(("usbproxy: pUrb=%p (#%d) ep=%d cbData=%d status=%d cIsocPkts=%d ready\n", + pUrb, rc - WAIT_OBJECT_0, pQUrbWin->urb->EndPt, pQUrbWin->urb->cbData, pUrb->enmStatus, pUrb->cIsocPkts)); + + /* free the urb queuing structure */ + if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE) + { + CloseHandle(pQUrbWin->overlapped.hEvent); + pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE; + } + RTMemFree(pQUrbWin); + } + else if ( rc == WAIT_FAILED + || (rc >= WAIT_ABANDONED_0 && rc < WAIT_ABANDONED_0 + cQueuedUrbs)) + AssertMsgFailed(("USB: WaitForMultipleObjects %d objects failed with rc=%d and last error %d\n", cQueuedUrbs, rc, GetLastError())); + + return pUrb; +} + + +/** + * Cancels an in-flight URB. + * + * The URB requires reaping, so we don't change its state. + * + * @remark There isn't a way to cancel a specific URB on Windows. + * on darwin. The interface only supports the aborting of + * all URBs pending on an endpoint. Luckily that is usually + * exactly what the guest wants to do. + */ +static DECLCALLBACK(int) usbProxyWinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + PQUEUED_URB pQUrbWin = (PQUEUED_URB)pUrb->Dev.pvPrivate; + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + AssertPtrReturn(pQUrbWin, VERR_INVALID_PARAMETER); + + in.bEndpoint = pUrb->EndPt | ((pUrb->EndPt && pUrb->enmDir == VUSBDIRECTION_IN) ? 0x80 : 0); + Log(("usbproxy: Cancel urb %p, endpoint %x\n", pUrb, in.bEndpoint)); + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %x unplugged!! (usbProxyWinUrbCancel)\n", pPriv->hDev)); + pProxyDev->fDetached = true; + return VINF_SUCCESS; /* Fake success and deal with the unplugged device elsewhere. */ + } + + AssertMsgFailed(("lastErr=%ld\n", dwErr)); + return RTErrConvertFromWin32(dwErr); +} + +static DECLCALLBACK(int) usbProxyWinWakeup(PUSBPROXYDEV pProxyDev) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + Log(("usbproxy: device %x wakeup\n", pPriv->hDev)); + ASMAtomicXchgBool(&pPriv->fWakeUpNow, true); + SetEvent(pPriv->hEventWakeup); + return VINF_SUCCESS; +} + +/** + * The Win32 USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(PRIV_USBW32), + usbProxyWinOpen, + NULL, + usbProxyWinClose, + usbProxyWinReset, + usbProxyWinSetConfig, + usbProxyWinClaimInterface, + usbProxyWinReleaseInterface, + usbProxyWinSetInterface, + usbProxyWinClearHaltedEndPt, + usbProxyWinUrbQueue, + usbProxyWinUrbCancel, + usbProxyWinUrbReap, + usbProxyWinWakeup, + 0 +}; + |