summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/USB
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/USB')
-rw-r--r--src/VBox/Devices/USB/DevOHCI.cpp6139
-rw-r--r--src/VBox/Devices/USB/DrvVUSBRootHub.cpp1592
-rw-r--r--src/VBox/Devices/USB/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice-stub.cpp51
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice.cpp1284
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice.h276
-rw-r--r--src/VBox/Devices/USB/VUSBDevice.cpp1849
-rw-r--r--src/VBox/Devices/USB/VUSBInternal.h747
-rw-r--r--src/VBox/Devices/USB/VUSBSniffer.cpp237
-rw-r--r--src/VBox/Devices/USB/VUSBSniffer.h101
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferInternal.h109
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp731
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp239
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferVmx.cpp202
-rw-r--r--src/VBox/Devices/USB/VUSBUrb.cpp1477
-rw-r--r--src/VBox/Devices/USB/VUSBUrbPool.cpp243
-rw-r--r--src/VBox/Devices/USB/VUSBUrbTrace.cpp809
-rw-r--r--src/VBox/Devices/USB/darwin/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp2006
-rw-r--r--src/VBox/Devices/USB/freebsd/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp1062
-rw-r--r--src/VBox/Devices/USB/linux/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp1698
-rw-r--r--src/VBox/Devices/USB/os2/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp870
-rw-r--r--src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp909
-rw-r--r--src/VBox/Devices/USB/testcase/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp594
-rw-r--r--src/VBox/Devices/USB/testcase/tstPalmOne.c402
-rw-r--r--src/VBox/Devices/USB/testcase/tstTrekStorGo.c313
-rw-r--r--src/VBox/Devices/USB/usbip/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp1764
-rw-r--r--src/VBox/Devices/USB/vrdp/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp287
-rw-r--r--src/VBox/Devices/USB/win/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/win/USBProxyDevice-win.cpp778
36 files changed, 26769 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevOHCI.cpp b/src/VBox/Devices/USB/DevOHCI.cpp
new file mode 100644
index 00000000..f6643acb
--- /dev/null
+++ b/src/VBox/Devices/USB/DevOHCI.cpp
@@ -0,0 +1,6139 @@
+/* $Id: DevOHCI.cpp $ */
+/** @file
+ * DevOHCI - Open Host Controller Interface for USB.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+/** @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 (ohciRhXferComplete) 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 <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 5 /* Introduced post-4.3. */
+/** The saved state with support of up to 8 ports. */
+#define OHCI_SAVED_STATE_VERSION_8PORTS 4 /* Introduced in 3.1 or so. */
+
+
+/** 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)
+
+/** 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;
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0; /**< Align the pointer correctly. */
+#endif
+ /** The device attached to the port. */
+ R3PTRTYPE(PVUSBIDEVICE) pDev;
+} OHCIHUBPORT;
+#if HC_ARCH_BITS == 64
+AssertCompile(sizeof(OHCIHUBPORT) == 16); /* saved state */
+#endif
+/** Pointer to an OHCI hub port. */
+typedef OHCIHUBPORT *POHCIHUBPORT;
+
+/**
+ * The OHCI root hub.
+ *
+ * @implements PDMIBASE
+ * @implements VUSBIROOTHUBPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct ohci_roothub
+{
+ /** Pointer to the base interface of the VUSB RootHub. */
+ R3PTRTYPE(PPDMIBASE) pIBase;
+ /** Pointer to the connector interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn;
+ /** Pointer to the device interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIDEVICE) pIDev;
+ /** 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;
+
+ 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];
+ R3PTRTYPE(POHCI) pOhci;
+} OHCIROOTHUB;
+/** Pointer to the OHCI root hub. */
+typedef OHCIROOTHUB *POHCIROOTHUB;
+
+
+/**
+ * Data used for reattaching devices on a state load.
+ */
+typedef struct ohci_load {
+ /** 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. */
+ PTMTIMERR3 pTimer;
+ /** Number of detached devices. */
+ unsigned cDevs;
+ /** Array of devices which were detached. */
+ PVUSBIDEVICE apDevs[OHCI_NDP_MAX];
+} OHCILOAD;
+/** Pointer to an OHCILOAD structure. */
+typedef OHCILOAD *POHCILOAD;
+
+#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 au8PhysReadCache[PAGE_SIZE];
+} OHCIPAGECACHE, *POHCIPAGECACHE;
+#endif
+
+/**
+ * OHCI device data.
+ */
+typedef struct OHCI
+{
+ /** The PCI device. */
+ PDMPCIDEV PciDev;
+
+ /** Pointer to the device instance - R3 ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+ /** The End-Of-Frame timer - R3 Ptr. */
+ PTMTIMERR3 pEndOfFrameTimerR3;
+
+ /** Pointer to the device instance - R0 ptr */
+ PPDMDEVINSR0 pDevInsR0;
+ /** The End-Of-Frame timer - R0 Ptr. */
+ PTMTIMERR0 pEndOfFrameTimerR0;
+
+ /** Pointer to the device instance - RC ptr. */
+ PPDMDEVINSRC pDevInsRC;
+ /** The End-Of-Frame timer - RC Ptr. */
+ PTMTIMERRC pEndOfFrameTimerRC;
+
+ /** Start of current frame. */
+ uint64_t SofTime;
+ /* done queue interrupt counter */
+ uint32_t dqic : 3;
+ /** frame number overflow. */
+ uint32_t fno : 1;
+ /** Address of the MMIO region assigned by PCI. */
+ RTGCPHYS32 MMIOBase;
+
+ /* Root hub device */
+ 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;
+ /** @} */
+
+ /** The number of virtual time ticks per frame. */
+ uint64_t cTicksPerFrame;
+ /** The number of virtual time ticks per USB bus tick. */
+ uint64_t cTicksPerUsbTick;
+
+ /** 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
+ /** Pointer to state load data. */
+ R3PTRTYPE(POHCILOAD) pLoad;
+
+ /** Detected canceled isochronous URBs. */
+ STAMCOUNTER StatCanceledIsocUrbs;
+ /** Detected canceled general URBs. */
+ STAMCOUNTER StatCanceledGenUrbs;
+ /** Dropped URBs (endpoint halted, or URB canceled). */
+ STAMCOUNTER StatDroppedUrbs;
+ /** Profiling ohciR3FrameBoundaryTimer. */
+ STAMPROFILE StatTimer;
+
+ /** This member and all the following are not part of saved state. */
+ uint64_t SavedStateEnd;
+
+ /** 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;
+
+ /** Whether RC/R0 is enabled. */
+ bool fRZEnabled;
+
+ uint32_t Alignment3; /**< Align size on a 8 byte boundary. */
+
+ /** Critical section synchronising interrupt handling. */
+ PDMCRITSECT CsIrq;
+ /** Critical section to synchronize the framer and URB completion handler. */
+ RTCRITSECT CritSect;
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /** Last read physical page for caching ED reads in the framer thread. */
+ R3PTRTYPE(POHCIPAGECACHE) pCacheED;
+ /** Last read physical page for caching TD reads in the framer thread. */
+ R3PTRTYPE(POHCIPAGECACHE) pCacheTD;
+#endif
+
+} OHCI;
+
+/* 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))
+struct ohci_hcca
+{
+ uint16_t frame;
+ uint16_t pad;
+ uint32_t done;
+};
+AssertCompileSize(ohci_hcca, 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 ohci_opreg
+{
+ const char *pszName;
+ int (*pfnRead )(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value);
+ int (*pfnWrite)(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(POHCIROOTHUB pRh, unsigned iPort, bool fPowerUp);
+static void ohciR3BusResume(POHCI ohci, bool fHardware);
+static void ohciR3BusStop(POHCI pThis);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+static void ohciR3PhysReadCacheClear(POHCIPAGECACHE pPageCache);
+#endif
+
+static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb);
+static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb);
+
+static int ohciR3InFlightFind(POHCI pThis, uint32_t GCPhysTD);
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+static int ohciR3InDoneQueueFind(POHCI pThis, uint32_t GCPhysTD);
+# endif
+static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser);
+#endif /* IN_RING3 */
+RT_C_DECLS_END
+
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ohciUpdateInterruptLocked(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(ohci->CTX_SUFF(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(POHCI ohci, int rcBusy, uint32_t intr, const char *msg)
+{
+ int rc = PDMCritSectEnter(&ohci->CsIrq, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if ( (ohci->intr_status & intr) != intr )
+ {
+ ohci->intr_status |= intr;
+ ohciUpdateInterruptLocked(ohci, msg);
+ }
+
+ PDMCritSectLeave(&ohci->CsIrq);
+ return rc;
+}
+
+/**
+ * Set an interrupt wrapper macro for logging purposes.
+ */
+# define ohciR3SetInterrupt(ohci, intr) ohciR3SetInterruptInt(ohci, VERR_IGNORED, intr, #intr)
+
+
+/* Carry out a hardware remote wakeup */
+static void ohciR3RemoteWakeup(POHCI pThis)
+{
+ if ((pThis->ctl & OHCI_CTL_HCFS) != OHCI_USB_SUSPEND)
+ return;
+ if (!(pThis->RootHub.status & OHCI_RHS_DRWE))
+ return;
+ ohciR3BusResume(pThis, true /* hardware */);
+}
+
+
+/**
+ * Query interface method for the roothub LUN.
+ */
+static DECLCALLBACK(void *) ohciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ POHCI pThis = RT_FROM_MEMBER(pInterface, OHCI, RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThis->RootHub.IRhPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->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)
+{
+ POHCI pThis = (POHCI)((uintptr_t)pInterface - RT_OFFSETOF(OHCI, RootHub.ILeds));
+ if (iLUN == 0)
+ {
+ *ppLed = &pThis->RootHub.Led;
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/** Converts a OHCI.roothub.IRhPort pointer to a POHCI. */
+#define VUSBIROOTHUBPORT_2_OHCI(pInterface) ((POHCI)( (uintptr_t)(pInterface) - RT_OFFSETOF(OHCI, 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)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ unsigned iPort;
+ unsigned cPorts = 0;
+
+ memset(pAvailable, 0, sizeof(*pAvailable));
+
+ PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED);
+ for (iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (!pThis->RootHub.aPorts[iPort].pDev)
+ {
+ cPorts++;
+ ASMBitSet(pAvailable, iPort + 1);
+ }
+ }
+ PDMCritSectLeave(pThis->pDevInsR3->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;
+}
+
+
+/**
+ * A device is being attached to a port in the roothub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param pDev Pointer to the device being attached.
+ * @param uPort The port number assigned to the device.
+ */
+static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ VUSBSPEED enmSpeed;
+ LogFlow(("ohciR3RhAttach: pDev=%p uPort=%u\n", pDev, uPort));
+ PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(!pThis->RootHub.aPorts[uPort].pDev);
+ enmSpeed = pDev->pfnGetSpeed(pDev);
+ /* Only LS/FS devices can 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].pDev = pDev;
+ ohciR3RhPortPower(&pThis->RootHub, uPort, 1 /* power on */);
+
+ ohciR3RemoteWakeup(pThis);
+ ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device is being detached from a port in the roothub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param pDev Pointer to the device being detached.
+ * @param uPort The port number assigned to the device.
+ */
+static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort)
+{
+ RT_NOREF(pDev);
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ LogFlow(("ohciR3RhDetach: pDev=%p uPort=%u\n", pDev, uPort));
+ PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(pThis->RootHub.aPorts[uPort].pDev == pDev);
+
+ /*
+ * Detach it.
+ */
+ pThis->RootHub.aPorts[uPort].pDev = NULL;
+ 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(pThis);
+ ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMCritSectLeave(pThis->pDevInsR3->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 rc The result of the operation.
+ * @param pvUser Pointer to the controller.
+ */
+static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, int rc, void *pvUser)
+{
+ LogRel(("OHCI: root hub reset completed with %Rrc\n", rc));
+ NOREF(pDev); NOREF(rc); NOREF(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)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED);
+
+ 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].pDev)
+ {
+ pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS;
+ if (fResetOnLinux)
+ {
+ PVM pVM = PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns));
+ VUSBIDevReset(pThis->RootHub.aPorts[iPort].pDev, fResetOnLinux, ohciR3RhResetDoneOneDev, pThis, pVM);
+ }
+ }
+ else
+ pThis->RootHub.aPorts[iPort].fReg = 0;
+ }
+ ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMCritSectLeave(pThis->pDevInsR3->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 pThis The ohci 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 ohciR3DoReset(POHCI pThis, uint32_t fNewMode, bool fResetOnLinux)
+{
+ Log(("ohci: %s reset%s\n", fNewMode == OHCI_USB_RESET ? "hardware" : "software",
+ fResetOnLinux ? " (reset on linux)" : ""));
+
+ /* Stop the bus in any case, disabling walking the lists. */
+ ohciR3BusStop(pThis);
+
+ /*
+ * 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.
+ */
+ pThis->RootHub.pIRhConn->pfnCancelAllUrbs(pThis->RootHub.pIRhConn);
+
+ /*
+ * 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(pThis->CTX_SUFF(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
+ ohciR3PhysReadCacheClear(pThis->pCacheED);
+ ohciR3PhysReadCacheClear(pThis->pCacheTD);
+#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)
+ VUSBIDevReset(pThis->RootHub.pIDev, fResetOnLinux, NULL, NULL, NULL);
+}
+
+
+/**
+ * Reads physical memory.
+ */
+DECLINLINE(void) ohciR3PhysRead(POHCI pThis, uint32_t Addr, void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ohciR3PhysWrite(POHCI pThis, uint32_t Addr, const void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Read an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ohciR3GetDWords(POHCI pThis, uint32_t Addr, uint32_t *pau32s, int c32s)
+{
+ ohciR3PhysRead(pThis, 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(POHCI pThis, uint32_t Addr, const uint32_t *pau32s, int cu32s)
+{
+# ifdef RT_LITTLE_ENDIAN
+ ohciR3PhysWrite(pThis, Addr, pau32s, cu32s << 2);
+# else
+ for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s))
+ {
+ uint32_t u32Tmp = RT_H2LE_U32(*pau32s);
+ ohciR3PhysWrite(pThis, 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 POHCIPAGECACHE ohciR3PhysReadCacheAlloc(void)
+{
+ return (POHCIPAGECACHE)RTMemAlloc(sizeof(OHCIPAGECACHE));
+}
+
+static void ohciR3PhysReadCacheFree(POHCIPAGECACHE pPageCache)
+{
+ RTMemFree(pPageCache);
+}
+
+static void ohciR3PhysReadCacheClear(POHCIPAGECACHE pPageCache)
+{
+ pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS;
+}
+
+static void ohciR3PhysReadCacheRead(POHCI pThis, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf)
+{
+ const RTGCPHYS PageAddr = PAGE_ADDRESS(GCPhys);
+
+ if (PageAddr == PAGE_ADDRESS(GCPhys + cbBuf))
+ {
+ if (PageAddr != pPageCache->GCPhysReadCacheAddr)
+ {
+ PDMDevHlpPhysRead(pThis->pDevInsR3, PageAddr,
+ pPageCache->au8PhysReadCache, sizeof(pPageCache->au8PhysReadCache));
+ pPageCache->GCPhysReadCacheAddr = PageAddr;
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ ++g_PhysReadState.cPageReads;
+# endif
+ }
+
+ memcpy(pvBuf, &pPageCache->au8PhysReadCache[GCPhys & PAGE_OFFSET_MASK], cbBuf);
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ ++g_PhysReadState.cCacheReads;
+# endif
+ }
+ else
+ {
+ PDMDevHlpPhysRead(pThis->pDevInsR3, 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 = PAGE_ADDRESS(GCPhys);
+
+ if (GCPhysPage == pPageCache->GCPhysReadCacheAddr)
+ {
+ uint32_t offPage = GCPhys & PAGE_OFFSET_MASK;
+ memcpy(&pPageCache->au8PhysReadCache[offPage], pvBuf, RT_MIN(PAGE_SIZE - offPage, cbBuf));
+ }
+}
+
+/**
+ * Update any cached ED data with the given endpoint descriptor at the given address.
+ *
+ * @returns nothing.
+ * @param pThis The OHCI instance data.
+ * @param EdAddr Endpoint descriptor address.
+ * @param pEd The endpoint descriptor which got updated.
+ */
+DECLINLINE(void) ohciR3CacheEdUpdate(POHCI pThis, RTGCPHYS32 EdAddr, PCOHCIED pEd)
+{
+ ohciR3PhysCacheUpdate(pThis->pCacheED, 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 pThis The OHCI instance data.
+ * @param TdAddr Transfer descriptor address.
+ * @param pTd The transfer descriptor which got updated.
+ */
+DECLINLINE(void) ohciR3CacheTdUpdate(POHCI pThis, RTGCPHYS32 TdAddr, PCOHCITD pTd)
+{
+ ohciR3PhysCacheUpdate(pThis->pCacheTD, TdAddr, pTd, sizeof(*pTd));
+}
+
+# endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */
+
+/**
+ * Reads an OHCIED.
+ */
+DECLINLINE(void) ohciR3ReadEd(POHCI pThis, 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
+ ohciR3PhysReadCacheRead(pThis, pThis->pCacheED, EdAddr, pEd, sizeof(*pEd));
+#else
+ ohciR3GetDWords(pThis, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2);
+#endif
+}
+
+/**
+ * Reads an OHCITD.
+ */
+DECLINLINE(void) ohciR3ReadTd(POHCI pThis, 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
+ ohciR3PhysReadCacheRead(pThis, pThis->pCacheTD, TdAddr, pTd, sizeof(*pTd));
+#else
+ ohciR3GetDWords(pThis, 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(pThis, 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(POHCI pThis, uint32_t ITdAddr, POHCIITD pITd)
+{
+ ohciR3GetDWords(pThis, 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));
+ }
+# endif
+}
+
+
+/**
+ * Writes an OHCIED.
+ */
+DECLINLINE(void) ohciR3WriteEd(POHCI pThis, uint32_t EdAddr, PCOHCIED pEd)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCIED EdOld;
+ uint32_t hichg;
+
+ ohciR3GetDWords(pThis, 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(pThis, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, 1);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3CacheEdUpdate(pThis, EdAddr, pEd);
+#endif
+}
+
+
+/**
+ * Writes an OHCITD.
+ */
+DECLINLINE(void) ohciR3WriteTd(POHCI pThis, uint32_t TdAddr, PCOHCITD pTd, const char *pszLogMsg)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCITD TdOld;
+ ohciR3GetDWords(pThis, 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(pThis, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3CacheTdUpdate(pThis, TdAddr, pTd);
+#endif
+}
+
+/**
+ * Writes an OHCIITD.
+ */
+DECLINLINE(void) ohciR3WriteITd(POHCI pThis, uint32_t ITdAddr, PCOHCIITD pITd, const char *pszLogMsg)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCIITD ITdOld;
+ ohciR3GetDWords(pThis, 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(pszLogMsg);
+# endif
+ ohciR3PutDWords(pThis, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t));
+}
+
+
+# ifdef LOG_ENABLED
+
+/**
+ * Core TD queue dumper. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpTdQueueCore(POHCI pThis, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull)
+{
+ uint32_t GCPhys = GCPhysHead;
+ int cMax = 100;
+ for (;;)
+ {
+ OHCITD Td;
+ Log4(("%#010x%s%s", GCPhys,
+ GCPhys && ohciR3InFlightFind(pThis, GCPhys) >= 0 ? "~" : "",
+ GCPhys && ohciR3InDoneQueueFind(pThis, GCPhys) >= 0 ? "^" : ""));
+ if (GCPhys == 0 || GCPhys == GCPhysTail)
+ break;
+
+ /* can't use ohciR3ReadTd() because of Log4. */
+ ohciR3GetDWords(pThis, 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);
+ Assert(cMax-- > 0); NOREF(cMax);
+ }
+}
+
+/**
+ * Dumps a TD queue. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpTdQueue(POHCI pThis, uint32_t GCPhysHead, const char *pszMsg)
+{
+ if (pszMsg)
+ Log4(("%s: ", pszMsg));
+ ohciR3DumpTdQueueCore(pThis, GCPhysHead, 0, true);
+ Log4(("\n"));
+}
+
+/**
+ * Core ITD queue dumper. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpITdQueueCore(POHCI pThis, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull)
+{
+ RT_NOREF(fFull);
+ uint32_t GCPhys = GCPhysHead;
+ int cMax = 100;
+ for (;;)
+ {
+ OHCIITD ITd;
+ Log4(("%#010x%s%s", GCPhys,
+ GCPhys && ohciR3InFlightFind(pThis, GCPhys) >= 0 ? "~" : "",
+ GCPhys && ohciR3InDoneQueueFind(pThis, GCPhys) >= 0 ? "^" : ""));
+ if (GCPhys == 0 || GCPhys == GCPhysTail)
+ break;
+
+ /* can't use ohciR3ReadTd() because of Log4. */
+ ohciR3GetDWords(pThis, 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);
+ Assert(cMax-- > 0); NOREF(cMax);
+ }
+}
+
+/**
+ * Dumps a ED list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpEdList(POHCI pThis, 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(pThis, 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(pThis, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false);
+ else
+ ohciR3DumpTdQueueCore(pThis, 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(POHCI pThis, const int iStart)
+{
+ unsigned i = iStart;
+ while (i < RT_ELEMENTS(pThis->aInFlight))
+ {
+ if (pThis->aInFlight[i].GCPhysTD == 0)
+ return i;
+ i++;
+ }
+ i = iStart;
+ while (i-- > 0)
+ {
+ if (pThis->aInFlight[i].GCPhysTD == 0)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+ * Record an in-flight TD.
+ *
+ * @param pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ * @param pUrb The URB.
+ */
+static void ohciR3InFlightAdd(POHCI pThis, uint32_t GCPhysTD, PVUSBURB pUrb)
+{
+ int i = ohciR3InFlightFindFree(pThis, (GCPhysTD >> 4) % RT_ELEMENTS(pThis->aInFlight));
+ if (i >= 0)
+ {
+# ifdef LOG_ENABLED
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+# endif
+ pThis->aInFlight[i].GCPhysTD = GCPhysTD;
+ pThis->aInFlight[i].pUrb = pUrb;
+ pThis->cInFlight++;
+ return;
+ }
+ AssertMsgFailed(("Out of space cInFlight=%d!\n", pThis->cInFlight));
+}
+
+
+/**
+ * Record in-flight TDs for an URB.
+ *
+ * @param pThis OHCI instance data.
+ * @param pUrb The URB.
+ */
+static void ohciR3InFlightAddUrb(POHCI pThis, PVUSBURB pUrb)
+{
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ ohciR3InFlightAdd(pThis, pUrb->paTds[iTd].TdAddr, pUrb);
+}
+
+
+/**
+ * Finds a in-flight TD.
+ *
+ * @returns Index of the record.
+ * @returns -1 if not found.
+ * @param pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ * @remark This has to be fast.
+ */
+static int ohciR3InFlightFind(POHCI pThis, uint32_t GCPhysTD)
+{
+ unsigned cLeft = pThis->cInFlight;
+ unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThis->aInFlight);
+ const int iLast = i;
+ while (i < RT_ELEMENTS(pThis->aInFlight))
+ {
+ if (pThis->aInFlight[i].GCPhysTD == GCPhysTD)
+ return i;
+ if (pThis->aInFlight[i].GCPhysTD)
+ if (cLeft-- <= 1)
+ return -1;
+ i++;
+ }
+ i = iLast;
+ while (i-- > 0)
+ {
+ if (pThis->aInFlight[i].GCPhysTD == GCPhysTD)
+ return i;
+ if (pThis->aInFlight[i].GCPhysTD)
+ if (cLeft-- <= 1)
+ return -1;
+ }
+ return -1;
+}
+
+
+/**
+ * Checks if a TD is in-flight.
+ *
+ * @returns true if in flight, false if not.
+ * @param pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ohciR3IsTdInFlight(POHCI pThis, uint32_t GCPhysTD)
+{
+ return ohciR3InFlightFind(pThis, 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 pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static PVUSBURB ohciR3TdInFlightUrb(POHCI pThis, uint32_t GCPhysTD)
+{
+ int i;
+
+ i = ohciR3InFlightFind(pThis, GCPhysTD);
+ if ( i >= 0 )
+ return pThis->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.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ohciR3InFlightRemove(POHCI pThis, uint32_t GCPhysTD)
+{
+ int i = ohciR3InFlightFind(pThis, GCPhysTD);
+ if (i >= 0)
+ {
+# ifdef LOG_ENABLED
+ const int cFramesInFlight = pThis->HcFmNumber - pThis->aInFlight[i].pUrb->pHci->u32FrameNo;
+# else
+ const int cFramesInFlight = 0;
+# endif
+ Log2(("ohciR3InFlightRemove: reaping TD=%#010x %d frames (%#010x-%#010x)\n",
+ GCPhysTD, cFramesInFlight, pThis->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber));
+ pThis->aInFlight[i].GCPhysTD = 0;
+ pThis->aInFlight[i].pUrb = NULL;
+ pThis->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.
+ * @param pUrb The URB.
+ */
+static int ohciR3InFlightRemoveUrb(POHCI pThis, PVUSBURB pUrb)
+{
+ int cFramesInFlight = ohciR3InFlightRemove(pThis, pUrb->paTds[0].TdAddr);
+ if (pUrb->pHci->cTds > 1)
+ {
+ for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++)
+ if (ohciR3InFlightRemove(pThis, pUrb->paTds[iTd].TdAddr) < 0)
+ cFramesInFlight = -1;
+ }
+ return cFramesInFlight;
+}
+
+
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+
+/**
+ * Empties the in-done-queue.
+ * @param pThis OHCI instance data.
+ */
+static void ohciR3InDoneQueueZap(POHCI pThis)
+{
+ pThis->cInDoneQueue = 0;
+}
+
+/**
+ * Finds a TD in the in-done-queue.
+ * @returns >= 0 on success.
+ * @returns -1 if not found.
+ * @param pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ohciR3InDoneQueueFind(POHCI pThis, uint32_t GCPhysTD)
+{
+ unsigned i = pThis->cInDoneQueue;
+ while (i-- > 0)
+ if (pThis->aInDoneQueue[i].GCPhysTD == GCPhysTD)
+ return i;
+ return -1;
+}
+
+/**
+ * Checks that the specified TD is not in the done queue.
+ * @param pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ohciR3InDoneQueueCheck(POHCI pThis, uint32_t GCPhysTD)
+{
+ int i = ohciR3InDoneQueueFind(pThis, 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 pThis OHCI instance data.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static void ohciR3InDoneQueueAdd(POHCI pThis, uint32_t GCPhysTD)
+{
+ Assert(pThis->cInDoneQueue + 1 <= RT_ELEMENTS(pThis->aInDoneQueue));
+ if (ohciR3InDoneQueueCheck(pThis, GCPhysTD))
+ pThis->aInDoneQueue[pThis->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))
+ {
+ 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(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 cMax = 256;
+ uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK;
+ while ( CurTdAddr != LastTdAddr
+ && cMax-- > 0)
+ {
+ OHCIITD ITd;
+ ohciR3ReadITd(pThis, CurTdAddr, &ITd);
+ if ((ITd.NextTD & ED_PTR_MASK) == TdAddr)
+ {
+ ITd.NextTD = (pITd->NextTD & ED_PTR_MASK) | (ITd.NextTD & ~ED_PTR_MASK);
+ ohciR3WriteITd(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!!! (cMax=%d)\n", TdAddr, cMax));
+ return false;
+}
+
+
+/** A worker for ohciR3UnlinkTds(). */
+static bool ohciR3UnlinkGeneralTdInList(POHCI pThis, 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 cMax = 256;
+ uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK;
+ while ( CurTdAddr != LastTdAddr
+ && cMax-- > 0)
+ {
+ OHCITD Td;
+ ohciR3ReadTd(pThis, CurTdAddr, &Td);
+ if ((Td.NextTD & ED_PTR_MASK) == TdAddr)
+ {
+ Td.NextTD = (pTd->NextTD & ED_PTR_MASK) | (Td.NextTD & ~ED_PTR_MASK);
+ ohciR3WriteTd(pThis, 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!!! (cMax=%d)\n", TdAddr, cMax));
+ 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(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(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(pThis, 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 pThis The OHCI instance.
+ * @param pUrb The URB in question.
+ * @param pEd The ED pointer (optional).
+ */
+static bool ohciR3HasUrbBeenCanceled(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(pThis, 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(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(pThis, 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 pThis The OHCI controller instance to lock.
+ */
+DECLINLINE(void) ohciR3Lock(POHCI pThis)
+{
+ RTCritSectEnter(&pThis->CritSect);
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /* Clear all caches here to avoid reading stale data from previous lock holders. */
+ ohciR3PhysReadCacheClear(pThis->pCacheED);
+ ohciR3PhysReadCacheClear(pThis->pCacheTD);
+# endif
+}
+
+
+/**
+ * Unlocks the given OHCI controller instance.
+ *
+ * @returns nothing.
+ * @param pThis The OHCI controller instance to unlock.
+ */
+DECLINLINE(void) ohciR3Unlock(POHCI pThis)
+{
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /*
+ * Clear all caches here to avoid leaving stale data behind (paranoia^2,
+ * already done in ohciR3Lock).
+ */
+ ohciR3PhysReadCacheClear(pThis->pCacheED);
+ ohciR3PhysReadCacheClear(pThis->pCacheTD);
+# endif
+
+ RTCritSectLeave(&pThis->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(POHCI pThis, 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(pThis, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb0);
+ ohciR3PhysWrite(pThis, pITd->BE & ITD_BP0_MASK, pb + cb0, cb - cb0);
+ }
+ else /* only in the 2nd page */
+ ohciR3PhysWrite(pThis, (pITd->BE & ITD_BP0_MASK) + (off & ITD_BP0_MASK), pb, cb);
+ }
+ else /* only in the 1st page */
+ ohciR3PhysWrite(pThis, (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)
+ pThis->u32FmDoneQueueTail = pThis->HcFmNumber;
+# ifdef VBOX_STRICT
+ ohciR3InDoneQueueAdd(pThis, 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(pThis, ITdAddr, pITd, "retired");
+ }
+}
+
+
+/**
+ * Worker for ohciR3RhXferCompletion that handles the completion of
+ * a URB made up of general TDs.
+ */
+static void ohciR3RhXferCompleteGeneralURB(POHCI pThis, 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);
+ ohciR3PhysWrite(pThis, Buf.aVecs[0].Addr, pb, Buf.aVecs[0].cb);
+ if (Buf.cVecs > 1)
+ ohciR3PhysWrite(pThis, 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)
+ pThis->u32FmDoneQueueTail = pThis->HcFmNumber;
+# ifdef VBOX_STRICT
+ ohciR3InDoneQueueAdd(pThis, TdAddr);
+# endif
+# endif
+ pTd->NextTD = pThis->done;
+ pThis->done = TdAddr;
+
+ ohciR3WriteTd(pThis, 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;
+ }
+}
+
+
+/**
+ * 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)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ LogFlow(("%s: ohciR3RhXferCompletion: EdAddr=%#010RX32 cTds=%d TdAddr0=%#010RX32\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr));
+
+ ohciR3Lock(pThis);
+ pThis->fIdle = false; /* Mark as active */
+
+ /* get the current end point descriptor. */
+ OHCIED Ed;
+ ohciR3ReadEd(pThis, 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.
+ */
+ int cFmAge = ohciR3InFlightRemoveUrb(pThis, pUrb);
+ 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(pThis);
+ return;
+ }
+ bool fHasBeenCanceled = false;
+ if ( (Ed.HeadP & ED_HEAD_HALTED)
+ || (Ed.hwinfo & ED_HWINFO_SKIP)
+ || cFmAge < 0
+ || (fHasBeenCanceled = ohciR3HasUrbBeenCanceled(pThis, pUrb, &Ed))
+ || !ohciR3UnlinkTds(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(pThis);
+ 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(pThis, pUrb /*, &Ed , cFmAge*/);
+ else
+ ohciR3RhXferCompleteGeneralURB(pThis, pUrb, &Ed, cFmAge);
+
+ /* finally write back the endpoint descriptor. */
+ ohciR3WriteEd(pThis, pUrb->pHci->EdAddr, &Ed);
+ ohciR3Unlock(pThis);
+}
+
+
+/**
+ * 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)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+
+ /*
+ * 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(pThis);
+ 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(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(pThis, 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(pThis);
+ return fRetire;
+}
+
+
+/**
+ * Service a general transport descriptor.
+ */
+static bool ohciR3ServiceTd(POHCI pThis, 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(pThis, 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));
+ /** @todo Do the correct thing here */
+ return false;
+ }
+ break;
+ }
+
+ pThis->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize a new URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL,
+ 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)
+ {
+ ohciR3PhysRead(pThis, Buf.aVecs[0].Addr, pUrb->abData, Buf.aVecs[0].cb);
+ if (Buf.cVecs > 1)
+ ohciR3PhysRead(pThis, Buf.aVecs[1].Addr, &pUrb->abData[Buf.aVecs[0].cb], Buf.aVecs[1].cb);
+ }
+
+ /*
+ * Submit the URB.
+ */
+ ohciR3InFlightAdd(pThis, TdAddr, pUrb);
+ Log(("%s: ohciR3ServiceTd: submitting TdAddr=%#010x EdAddr=%#010x cbData=%#x\n",
+ pUrb->pszDesc, TdAddr, EdAddr, pUrb->cbData));
+
+ ohciR3Unlock(pThis);
+ int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led);
+ ohciR3Lock(pThis);
+ 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, TdAddr);
+ return false;
+}
+
+
+/**
+ * Service a the head TD of an endpoint.
+ */
+static bool ohciR3ServiceHeadTd(POHCI pThis, 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(pThis, TdAddr))
+ return false;
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueCheck(pThis, TdAddr);
+# endif
+ return ohciR3ServiceTd(pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName);
+}
+
+
+/**
+ * Service one or more general transport descriptors (bulk or interrupt).
+ */
+static bool ohciR3ServiceTdMultiple(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;
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheClear(pThis->pCacheTD);
+# endif
+
+ /* read the head */
+ ohciR3ReadTd(pThis, 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(pThis, 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));
+ /** @todo Do the correct thing here */
+ return false;
+ }
+ break;
+ }
+
+ pThis->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize a new URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL,
+ 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(pThis, pCur->Buf.aVecs[0].Addr, pb, pCur->Buf.aVecs[0].cb);
+ if (pCur->Buf.cVecs > 1)
+ ohciR3PhysRead(pThis, 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, pUrb);
+ Log(("%s: ohciR3ServiceTdMultiple: submitting cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x\n",
+ pUrb->pszDesc, pUrb->cbData, EdAddr, cTds, TdAddr));
+ ohciR3Unlock(pThis);
+ int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led);
+ ohciR3Lock(pThis);
+ 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));
+ for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++)
+ ohciR3InFlightRemove(pThis, pCur->TdAddr);
+ return false;
+}
+
+
+/**
+ * Service the head TD of an endpoint.
+ */
+static bool ohciR3ServiceHeadTdMultiple(POHCI pThis, 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(pThis, TdAddr))
+ return false;
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueCheck(pThis, TdAddr);
+# endif
+ return ohciR3ServiceTdMultiple(pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName);
+}
+
+
+/**
+ * A worker for ohciR3ServiceIsochronousEndpoint which unlinks a ITD
+ * that belongs to the past.
+ */
+static bool ohciR3ServiceIsochronousTdUnlink(POHCI pThis, 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(pThis, ITdAddrPrev);
+ AssertMsgReturn(iInFlightPrev >= 0, ("ITdAddr=%#RX32\n", ITdAddrPrev), false);
+ PVUSBURB pUrbPrev = pThis->aInFlight[iInFlightPrev].pUrb;
+ if (ohciR3HasUrbBeenCanceled(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(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(pThis, 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(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(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 pThis The OHCI controller instance data.
+ * @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(POHCI pThis, 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));
+ /* Should probably raise an unrecoverable HC error here */
+ 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*/
+ 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*/
+ 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*/
+ 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*/
+ 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(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL,
+ 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(pThis, (pITd->BP0 & ITD_BP0_MASK) + off0, &pUrb->abData[0], cb0);
+ ohciR3PhysRead(pThis, pITd->BE & ITD_BP0_MASK, &pUrb->abData[cb0], offEnd & 0xfff);
+ }
+ else /* a portion of the 1st page. */
+ ohciR3PhysRead(pThis, (pITd->BP0 & ITD_BP0_MASK) + off0, pUrb->abData, offEnd - off0);
+ }
+ else /* a portion of the 2nd page. */
+ ohciR3PhysRead(pThis, (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.
+ */
+ ohciR3InFlightAddUrb(pThis, 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(pThis);
+ int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led);
+ ohciR3Lock(pThis);
+ 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, ITdAddr);
+ return false;
+}
+
+
+/**
+ * Service an isochronous endpoint.
+ */
+static void ohciR3ServiceIsochronousEndpoint(POHCI pThis, 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(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(pThis, ITdAddr) < 0)
+ if (!ohciR3ServiceIsochronousTd(pThis, &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(pThis, ITdAddr);
+ if (iInFlight >= 0)
+ ITdAddrPrev = ITdAddr;
+ else if (!ohciR3ServiceIsochronousTdUnlink(pThis, &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(POHCI pThis)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pThis, 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;
+ while (EdAddr)
+ {
+ OHCIED Ed;
+ ohciR3ReadEd(pThis, 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(pThis, 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(pThis, 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(pThis, TdAddr);
+ if (pUrb)
+ pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb);
+ }
+ }
+
+ /* next end point */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pThis, 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(POHCI pThis)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pThis, 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;
+ while (EdAddr)
+ {
+ OHCIED Ed;
+
+ ohciR3ReadEd(pThis, 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(pThis, TdAddr))
+ {
+ LogFlow(("ohciR3UndoBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 UNDO\n", EdAddr, Ed.TailP));
+ PVUSBURB pUrb = ohciR3TdInFlightUrb(pThis, TdAddr);
+ if (pUrb)
+ pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb);
+ }
+ }
+ /* 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(POHCI pThis)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ohciR3DumpEdList(pThis, 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;
+ while (EdAddr)
+ {
+ OHCIED Ed;
+ ohciR3ReadEd(pThis, 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(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control")
+ || ohciR3IsTdInFlight(pThis, Ed.HeadP & ED_PTR_MASK))
+ {
+ pThis->status |= OHCI_STATUS_CLF;
+ break;
+ }
+ ohciR3ReadEd(pThis, 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
+ }
+
+ /* next end point */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ohciR3DumpEdList(pThis, 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(POHCI pThis)
+{
+ /*
+ * Read the list head from the HCCA.
+ */
+ const unsigned iList = pThis->HcFmNumber % OHCI_HCCA_NUM_INTR;
+ uint32_t EdAddr;
+ ohciR3GetDWords(pThis, 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(pThis, EdAddrHead, sz, true);
+ }
+# endif
+
+ /*
+ * Iterate the endpoint list.
+ */
+ while (EdAddr)
+ {
+ OHCIED Ed;
+
+ ohciR3ReadEd(pThis, 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(pThis, VUSBXFERTYPE_INTR, &Ed, EdAddr, "Periodic");
+ }
+ else if (pThis->ctl & OHCI_CTL_IE)
+ {
+ /*
+ * Presently only the head ITD.
+ */
+ ohciR3ServiceIsochronousEndpoint(pThis, &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(pThis, TdAddr);
+ if (pUrb)
+ pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb);
+ }
+ }
+ /* next end point */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogInterruptEPs)
+ {
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iList);
+ ohciR3DumpEdList(pThis, EdAddrHead, sz, true);
+ }
+# endif
+}
+
+
+/**
+ * Update the HCCA.
+ *
+ * @param pThis The OHCI instance data.
+ */
+static void ohciR3UpdateHCCA(POHCI pThis)
+{
+ struct ohci_hcca hcca;
+ ohciR3PhysRead(pThis, 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 - pThis->u32FmDoneQueueTail));
+# ifdef LOG_ENABLED
+ ohciR3DumpTdQueue(pThis, hcca.done & ED_PTR_MASK, "DoneQueue");
+# endif
+ Assert(RT_OFFSETOF(struct ohci_hcca, done) == 4);
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueZap(pThis);
+# endif
+ fWriteDoneHeadInterrupt = true;
+ }
+
+ Log3(("ohci: Updating HCCA on frame %#x\n", pThis->HcFmNumber));
+ ohciR3PhysWrite(pThis, pThis->hcca + OHCI_HCCA_OFS, (uint8_t *)&hcca, sizeof(hcca));
+ if (fWriteDoneHeadInterrupt)
+ ohciR3SetInterrupt(pThis, OHCI_INTR_WRITE_DONE_HEAD);
+}
+
+
+/**
+ * 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(POHCI pThis)
+{
+ 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 || !pThis->cInFlight)
+ return;
+
+ /* Initially mark all in-flight URBs as inactive. */
+ for (i = 0, cLeft = pThis->cInFlight; cLeft && i < RT_ELEMENTS(pThis->aInFlight); i++)
+ {
+ if (pThis->aInFlight[i].pUrb)
+ {
+ pThis->aInFlight[i].fInactive = true;
+ cLeft--;
+ }
+ }
+ Assert(cLeft == 0);
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /* Get hcca data to minimize calls to ohciR3GetDWords/PDMDevHlpPhysRead. */
+ uint32_t au32HCCA[OHCI_HCCA_NUM_INTR];
+ ohciR3GetDWords(pThis, 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(pThis, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1);
+# endif
+ break;
+ }
+ while (EdAddr)
+ {
+ OHCIED Ed;
+ OHCITD Td;
+
+ ohciR3ReadEd(pThis, EdAddr, &Ed);
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ uint32_t TailP = Ed.TailP & ED_PTR_MASK;
+ unsigned k = 0;
+ if ( !(Ed.hwinfo & ED_HWINFO_SKIP)
+ && (TdAddr != TailP))
+ {
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheClear(pThis->pCacheTD);
+# endif
+ do
+ {
+ ohciR3ReadTd(pThis, TdAddr, &Td);
+ j = ohciR3InFlightFind(pThis, TdAddr);
+ if (j > -1)
+ pThis->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 (++k == 128)
+ break;
+ } while (TdAddr != (Ed.TailP & ED_PTR_MASK));
+ }
+ 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 = pThis->cInFlight; cLeft && i < RT_ELEMENTS(pThis->aInFlight); i++)
+ {
+ if (pThis->aInFlight[i].pUrb)
+ {
+ cLeft--;
+ pUrb = pThis->aInFlight[i].pUrb;
+ if (pThis->aInFlight[i].fInactive
+ && pUrb->enmState == VUSBURBSTATE_IN_FLIGHT
+ && pUrb->enmType != VUSBXFERTYPE_CTRL)
+ pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb);
+ }
+ }
+ Assert(cLeft == 0);
+}
+
+/**
+ * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame.
+ */
+static void ohciR3StartOfFrame(POHCI pThis)
+{
+# 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(pThis);
+# endif
+
+ /* "After writing to HCCA, HC will set SF in HcInterruptStatus" - guest isn't executing, so ignore the order! */
+ ohciR3SetInterrupt(pThis, OHCI_INTR_START_OF_FRAME);
+
+ if (pThis->fno)
+ {
+ ohciR3SetInterrupt(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(pThis);
+
+ /*
+ * Control EPs.
+ */
+ if ( (pThis->ctl & OHCI_CTL_CLE)
+ && (pThis->status & OHCI_STATUS_CLF) )
+ ohciR3ServiceCtrlList(pThis);
+
+ /*
+ * Bulk EPs.
+ */
+ if ( (pThis->ctl & OHCI_CTL_BLE)
+ && (pThis->status & OHCI_STATUS_BLF))
+ ohciR3ServiceBulkList(pThis);
+ else if ((pThis->status & OHCI_STATUS_BLF)
+ && pThis->fBulkNeedsCleaning)
+ ohciR3UndoBulkList(pThis); /* 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);
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+
+ ohciR3Lock(pThis);
+
+ /* Reset idle detection flag */
+ pThis->fIdle = true;
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsReset(&g_PhysReadState);
+# endif
+
+ /* 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(pThis);
+
+ /* Start the next frame. */
+ ohciR3StartOfFrame(pThis);
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsPrint(&g_PhysReadState);
+# endif
+
+ ohciR3Unlock(pThis);
+ return pThis->fIdle;
+}
+
+/** @interface_method_impl{VUSBIROOTHUBPORT,pfnFrameRateChanged} */
+static DECLCALLBACK(void) ohciR3FrameRateChanged(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameRate)
+{
+ POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+
+ 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;
+}
+
+/**
+ * Do frame processing on frame boundary
+ */
+static DECLCALLBACK(void) ohciR3FrameBoundaryTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns, pTimer, pvUser);
+}
+
+/**
+ * Start sending SOF tokens across the USB bus, lists are processed in
+ * next frame
+ */
+static void ohciR3BusStart(POHCI pThis)
+{
+ VUSBIDevPowerOn(pThis->RootHub.pIDev);
+ pThis->dqic = 0x7;
+
+ Log(("ohci: %s: Bus started\n", pThis->PciDev.pszNameR3));
+
+ pThis->SofTime = PDMDevHlpTMTimeVirtGet(pThis->CTX_SUFF(pDevIns));
+ int rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ);
+ AssertRC(rc);
+}
+
+/**
+ * Stop sending SOF tokens on the bus
+ */
+static void ohciR3BusStop(POHCI pThis)
+{
+ int rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, 0);
+ AssertRC(rc);
+ VUSBIDevPowerOff(pThis->RootHub.pIDev);
+}
+
+/**
+ * Move in to resume state
+ */
+static void ohciR3BusResume(POHCI pThis, 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(pThis, OHCI_INTR_RESUME_DETECT);
+
+ ohciR3BusStart(pThis);
+}
+
+
+/* Power a port up or down */
+static void ohciR3RhPortPower(POHCIROOTHUB 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->pDev)
+ pPort->fReg |= OHCI_PORT_CCS;
+ if (pPort->fReg & OHCI_PORT_CCS)
+ pPort->fReg |= OHCI_PORT_PPS;
+ if (pPort->pDev && !fOldPPS)
+ VUSBIDevPowerOn(pPort->pDev);
+ }
+ else
+ {
+ /* power down */
+ pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS);
+ if (pPort->pDev && fOldPPS)
+ VUSBIDevPowerOff(pPort->pDev);
+ }
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Read the HcRevision register.
+ */
+static int HcRevision_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(pThis, iReg);
+ Log2(("HcRevision_r() -> 0x10\n"));
+ *pu32Value = 0x10; /* OHCI revision 1.0, no emulation. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRevision register.
+ */
+static int HcRevision_w(POHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF3(pThis, iReg, u32Value);
+ Log2(("HcRevision_w(%#010x) - denied\n", u32Value));
+ AssertMsgFailed(("Invalid operation!!! u32Value=%#010x\n", u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControl register.
+ */
+static int HcControl_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(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 int HcControl_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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)
+ {
+ switch (new_state)
+ {
+ case OHCI_USB_OPERATIONAL:
+ LogRel(("OHCI: USB Operational\n"));
+ ohciR3BusStart(pThis);
+ break;
+ case OHCI_USB_SUSPEND:
+ ohciR3BusStop(pThis);
+ LogRel(("OHCI: USB Suspended\n"));
+ break;
+ case OHCI_USB_RESUME:
+ LogRel(("OHCI: USB Resume\n"));
+ ohciR3BusResume(pThis, false /* not hardware */);
+ break;
+ case OHCI_USB_RESET:
+ {
+ LogRel(("OHCI: USB Reset\n"));
+ ohciR3BusStop(pThis);
+ /** @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. */
+ VUSBIDevReset(pThis->RootHub.pIDev, false /* don't do a real reset */, NULL, NULL, NULL);
+ break;
+ }
+ }
+ }
+#else /* !IN_RING3 */
+ 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 int HcCommandStatus_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcCommandStatus register.
+ */
+static int HcCommandStatus_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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(pThis, 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 int HcInterruptStatus_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcInterruptStatus register.
+ */
+static int HcInterruptStatus_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(iReg);
+
+ uint32_t res = pThis->intr_status & ~val;
+ uint32_t chg = pThis->intr_status ^ res; NOREF(chg);
+
+ int rc = PDMCritSectEnter(&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(pThis, "HcInterruptStatus_w");
+ PDMCritSectLeave(&pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcInterruptEnable register
+ */
+static int HcInterruptEnable_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to the HcInterruptEnable register.
+ */
+static int HcInterruptEnable_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(iReg);
+ uint32_t res = pThis->intr | val;
+ uint32_t chg = pThis->intr ^ res; NOREF(chg);
+
+ int rc = PDMCritSectEnter(&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(pThis, "HcInterruptEnable_w");
+ PDMCritSectLeave(&pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads the HcInterruptDisable register.
+ */
+static int HcInterruptDisable_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to the HcInterruptDisable register.
+ */
+static int HcInterruptDisable_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(iReg);
+ uint32_t res = pThis->intr & ~val;
+ uint32_t chg = pThis->intr ^ res; NOREF(chg);
+
+ int rc = PDMCritSectEnter(&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(pThis, "HcInterruptDisable_w");
+ PDMCritSectLeave(&pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcHCCA register (Host Controller Communications Area physical address).
+ */
+static int HcHCCA_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcHCCA_r() -> %#010x\n", pThis->hcca));
+ *pu32Value = pThis->hcca;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcHCCA register (Host Controller Communications Area physical address).
+ */
+static int HcHCCA_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcPeriodCurrentED register.
+ */
+static int HcPeriodCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcPeriodCurrentED_r() -> %#010x\n", pThis->per_cur));
+ *pu32Value = pThis->per_cur;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcPeriodCurrentED register.
+ */
+static int HcPeriodCurrentED_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControlHeadED register.
+ */
+static int HcControlHeadED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcControlHeadED_r() -> %#010x\n", pThis->ctrl_head));
+ *pu32Value = pThis->ctrl_head;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcControlHeadED register.
+ */
+static int HcControlHeadED_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControlCurrentED register.
+ */
+static int HcControlCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcControlCurrentED_r() -> %#010x\n", pThis->ctrl_cur));
+ *pu32Value = pThis->ctrl_cur;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcControlCurrentED register.
+ */
+static int HcControlCurrentED_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcBulkHeadED register.
+ */
+static int HcBulkHeadED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcBulkHeadED_r() -> %#010x\n", pThis->bulk_head));
+ *pu32Value = pThis->bulk_head;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcBulkHeadED register.
+ */
+static int HcBulkHeadED_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcBulkCurrentED register.
+ */
+static int HcBulkCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcBulkCurrentED_r() -> %#010x\n", pThis->bulk_cur));
+ *pu32Value = pThis->bulk_cur;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcBulkCurrentED register.
+ */
+static int HcBulkCurrentED_w(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read the HcDoneHead register.
+ */
+static int HcDoneHead_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcDoneHead_r() -> 0x%#08x\n", pThis->done));
+ *pu32Value = pThis->done;
+ RT_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcDoneHead register.
+ */
+static int HcDoneHead_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF3(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 int HcFmInterval_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcFmInterval (Fm = Frame) register.
+ */
+static int HcFmInterval_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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 int HcFmRemaining_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(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(pThis->CTX_SUFF(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 int HcFmRemaining_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF3(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 int HcFmNumber_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(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 int HcFmNumber_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF3(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 int HcPeriodicStart_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(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 int HcPeriodicStart_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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 int HcLSThreshold_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF2(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 int HcLSThreshold_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF3(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 int HcRhDescriptorA_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF1(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 int HcRhDescriptorA_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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: %s: invalid write to NDP or DT in roothub descriptor A!!! val=0x%.8x\n",
+ pThis->PciDev.pszNameR3, 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 int HcRhDescriptorB_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRhDescriptorB register.
+ */
+static int HcRhDescriptorB_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF1(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: %s: unsupported write to root descriptor B!!! 0x%.8x -> 0x%.8x\n",
+ pThis->PciDev.pszNameR3, pThis->RootHub.desc_b, val));
+ pThis->RootHub.desc_b = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcRhStatus (Rh = Root Hub) register.
+ */
+static int HcRhStatus_r(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_NOREF1(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRhStatus (Rh = Root Hub) register.
+ */
+static int HcRhStatus_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ /* 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: %s: global power up\n", pThis->PciDev.pszNameR3));
+ for (i = 0; i < OHCI_NDP_CFG(pThis); i++)
+ ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */);
+ }
+
+ /* ClearGlobalPower */
+ if ( val & OHCI_RHS_LPS )
+ {
+ unsigned i;
+ Log2(("ohci: %s: global power down\n", pThis->PciDev.pszNameR3));
+ for (i = 0; i < OHCI_NDP_CFG(pThis); i++)
+ ohciR3RhPortPower(&pThis->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_NOREF1(iReg);
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ RT_NOREF3(pThis, iReg, val);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+/**
+ * Read the HcRhPortStatus register of a port.
+ */
+static int HcRhPortStatus_r(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;
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+/**
+ * Completion callback for the vusb_dev_reset() operation.
+ * @thread EMT.
+ */
+static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, int rc, void *pvUser)
+{
+ POHCI pThis = (POHCI)pvUser;
+
+ /*
+ * Find the port in question
+ */
+ POHCIHUBPORT pPort = NULL;
+ unsigned iPort;
+ for (iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) /* lazy bird */
+ if (pThis->RootHub.aPorts[iPort].pDev == pDev)
+ {
+ pPort = &pThis->RootHub.aPorts[iPort];
+ break;
+ }
+ if (!pPort)
+ {
+ Assert(pPort); /* sometimes happens because of @bugref{1510} */
+ return;
+ }
+
+ 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->pDev
+ && VUSBIDevGetState(pPort->pDev) == 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(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(POHCIROOTHUB pRh, int iPort, uint32_t fValue)
+{
+ /*
+ * Writing a 0 has no effect
+ */
+ if (fValue == 0)
+ return false;
+
+ /*
+ * If CurrentConnectStatus is cleared we set ConnectStatusChange.
+ */
+ if (!(pRh->aPorts[iPort].fReg & OHCI_PORT_CCS))
+ {
+ pRh->aPorts[iPort].fReg |= OHCI_PORT_CSC;
+ ohciR3SetInterrupt(pRh->pOhci, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+ return false;
+ }
+
+ bool fRc = !(pRh->aPorts[iPort].fReg & fValue);
+
+ /* set the bit */
+ pRh->aPorts[iPort].fReg |= fValue;
+
+ return fRc;
+}
+#endif /* IN_RING3 */
+
+/**
+ * Write to the HcRhPortStatus register of a port.
+ */
+static int HcRhPortStatus_w(POHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ const unsigned i = iReg - 21;
+ 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(&pThis->RootHub, i, val & OHCI_PORT_PES))
+ Log2(("HcRhPortStatus_w(): port %u: ENABLE\n", i));
+
+ if (ohciR3RhPortSetIfConnected(&pThis->RootHub, i, val & OHCI_PORT_PSS))
+ Log2(("HcRhPortStatus_w(): port %u: SUSPEND - not implemented correctly!!!\n", i));
+
+ if (val & OHCI_PORT_PRS)
+ {
+ if (ohciR3RhPortSetIfConnected(&pThis->RootHub, i, val & OHCI_PORT_PRS))
+ {
+ PVM pVM = PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns));
+ p->fReg &= ~OHCI_PORT_PRSC;
+ VUSBIDevReset(p->pDev, false /* don't reset on linux */, ohciR3PortResetDone, pThis, 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(&pThis->RootHub, i, false /* power down */);
+ if (val & OHCI_PORT_PPS)
+ ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */);
+ }
+
+ /** @todo r=frank: ClearSuspendStatus. Timing? */
+ if (val & OHCI_PORT_CLRSS)
+ {
+ ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */);
+ pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS;
+ pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC;
+ ohciR3SetInterrupt(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));
+ }
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ RT_NOREF3(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{FNIOMMMIOREAD}
+ */
+PDMBOTHCBDECL(int) ohciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ RT_NOREF1(pvUser);
+
+ /* Paranoia: Assert that IOMMMIO_FLAGS_READ_DWORD works. */
+ AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3);
+ AssertReturn(!(GCPhysAddr & 0x3), VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ int rc;
+ const uint32_t iReg = (GCPhysAddr - pThis->MMIOBase) >> 2;
+ if (iReg < NUM_OP_REGS(pThis))
+ {
+ const OHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnRead(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{FNIOMMMIOWRITE}
+ */
+PDMBOTHCBDECL(int) ohciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ RT_NOREF1(pvUser);
+
+ /* Paranoia: Assert that IOMMMIO_FLAGS_WRITE_DWORD_ZEROED works. */
+ AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3);
+ AssertReturn(!(GCPhysAddr & 0x3), VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ int rc;
+ const uint32_t iReg = (GCPhysAddr - pThis->MMIOBase) >> 2;
+ if (iReg < NUM_OP_REGS(pThis))
+ {
+ const OHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnWrite(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
+
+/**
+ * @callback_method_impl{FNPCIIOREGIONMAP}
+ */
+static DECLCALLBACK(int) ohciR3Map(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion,
+ RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType)
+{
+ RT_NOREF(iRegion, enmType);
+ POHCI pThis = (POHCI)pPciDev;
+ int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED
+ | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE,
+ ohciMmioWrite, ohciMmioRead, "USB OHCI");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pThis->fRZEnabled)
+ {
+ rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/, "ohciMmioWrite", "ohciMmioRead");
+ if (RT_FAILURE(rc))
+ return rc;
+
+ rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/, "ohciMmioWrite", "ohciMmioRead");
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+
+ pThis->MMIOBase = GCPhysAddress;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Prepares for state saving.
+ * All URBs needs to be canceled.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to save the state to.
+ */
+static DECLCALLBACK(int) ohciR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ POHCIROOTHUB pRh = &pThis->RootHub;
+ LogFlow(("ohciR3SavePrep: \n"));
+
+ /*
+ * Detach all proxied devices.
+ */
+ PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED);
+ /** @todo this won't work well when continuing after saving! */
+ for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++)
+ {
+ PVUSBIDEVICE pDev = pRh->aPorts[i].pDev;
+ if (pDev)
+ {
+ if (!VUSBIDevIsSavedStateSupported(pDev))
+ {
+ VUSBIRhDetachDevice(pRh->pIRhConn, pDev);
+ /*
+ * 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.
+ */
+ pRh->aPorts[i].pDev = pDev;
+ }
+ }
+ }
+
+ /*
+ * If the bus was started set the timer. This is ugly but avoids changing the
+ * saved state version for now so we can backport the changes to other branches.
+ */
+ /** @todo Do it properly for 4.4 by changing the saved state. */
+ if (VUSBIRhGetPeriodicFrameRate(pRh->pIRhConn) != 0)
+ {
+ /* Calculate a new timer expiration so this saved state works with older releases. */
+ uint64_t u64Expire = PDMDevHlpTMTimeVirtGet(pThis->CTX_SUFF(pDevIns)) + pThis->cTicksPerFrame;
+
+ LogFlowFunc(("Bus is active, setting timer to %llu\n", u64Expire));
+ int rc = TMTimerSet(pThis->pEndOfFrameTimerR3, u64Expire);
+ AssertRC(rc);
+ }
+
+ PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3);
+
+ /*
+ * Kill old load data which might be hanging around.
+ */
+ if (pThis->pLoad)
+ {
+ TMR3TimerDestroy(pThis->pLoad->pTimer);
+ MMR3HeapFree(pThis->pLoad);
+ pThis->pLoad = NULL;
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * 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 = PDMINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3SaveExec: \n"));
+
+ int rc = SSMR3PutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL);
+ if (RT_SUCCESS(rc))
+ rc = TMR3TimerSave(pThis->CTX_SUFF(pEndOfFrameTimer), pSSM);
+ return rc;
+}
+
+
+/**
+ * Done state save operation.
+ *
+ * @returns VBox load code.
+ * @param pDevIns Device instance of the device which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) ohciR3SaveDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3SaveDone: \n"));
+
+ /*
+ * NULL the dev pointers.
+ */
+ POHCIROOTHUB pRh = &pThis->RootHub;
+ OHCIROOTHUB Rh = *pRh;
+ for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++)
+ {
+ if ( pRh->aPorts[i].pDev
+ && !VUSBIDevIsSavedStateSupported(pRh->aPorts[i].pDev))
+ pRh->aPorts[i].pDev = NULL;
+ }
+
+ /*
+ * Attach the devices.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++)
+ {
+ PVUSBIDEVICE pDev = Rh.aPorts[i].pDev;
+ if ( pDev
+ && !VUSBIDevIsSavedStateSupported(pDev))
+ VUSBIRhAttachDevice(pRh->pIRhConn, pDev);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Prepare loading the state of the OHCI device.
+ * This must detach the devices currently attached and save
+ * the up for reconnect after the state load have been completed
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to the saved state.
+ */
+static DECLCALLBACK(int) ohciR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3LoadPrep:\n"));
+ if (!pThis->pLoad)
+ {
+ /*
+ * Detach all devices which are present in this session. Save them in the load
+ * structure so we can reattach them after restoring the guest.
+ */
+ POHCIROOTHUB pRh = &pThis->RootHub;
+ OHCILOAD Load;
+ Load.pTimer = NULL;
+ Load.cDevs = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++)
+ {
+ PVUSBIDEVICE pDev = pRh->aPorts[i].pDev;
+ if ( pDev
+ && !VUSBIDevIsSavedStateSupported(pDev))
+ {
+ Load.apDevs[Load.cDevs++] = pDev;
+ VUSBIRhDetachDevice(pRh->pIRhConn, pDev);
+ Assert(!pRh->aPorts[i].pDev);
+ }
+ }
+
+ /*
+ * Any devices to reattach, if so duplicate the Load struct.
+ */
+ if (Load.cDevs)
+ {
+ pThis->pLoad = (POHCILOAD)PDMDevHlpMMHeapAlloc(pDevIns, sizeof(Load));
+ if (!pThis->pLoad)
+ return VERR_NO_MEMORY;
+ *pThis->pLoad = Load;
+ }
+ }
+ /* else: we ASSUME no device can be attached or detach in the period
+ * between a state load and the pLoad stuff is processed. */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * 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 = PDMINS_2_DATA(pDevIns, POHCI);
+ int rc;
+ LogFlow(("ohciR3LoadExec:\n"));
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ if (uVersion == OHCI_SAVED_STATE_VERSION)
+ {
+ rc = SSMR3GetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (uVersion == OHCI_SAVED_STATE_VERSION_8PORTS)
+ {
+ rc = SSMR3GetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields8Ports[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else
+ AssertMsgFailedReturn(("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+
+ /*
+ * Finally restore the timer.
+ */
+ return TMR3TimerLoad(pThis->pEndOfFrameTimerR3, pSSM);
+}
+
+
+/**
+ * Done state load operation.
+ *
+ * @returns VBox load code.
+ * @param pDevIns Device instance of the device which registered the data unit.
+ * @param pSSM SSM operation handle.
+ */
+static DECLCALLBACK(int) ohciR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ RT_NOREF(pSSM);
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3LoadDone:\n"));
+
+ /*
+ * Start a timer if we've got devices to reattach
+ */
+ if (pThis->pLoad)
+ {
+ int rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ohciR3LoadReattachDevices, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT, "OHCI reattach devices on load",
+ &pThis->pLoad->pTimer);
+ if (RT_SUCCESS(rc))
+ rc = TMTimerSetMillies(pThis->pLoad->pTimer, 250);
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reattaches devices after a saved state load.
+ */
+static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pDevIns);
+ POHCI pThis = (POHCI)pvUser;
+ POHCILOAD pLoad = pThis->pLoad;
+ POHCIROOTHUB pRh = &pThis->RootHub;
+ LogFlow(("ohciR3LoadReattachDevices:\n"));
+
+ /*
+ * Reattach devices.
+ */
+ for (unsigned i = 0; i < pLoad->cDevs; i++)
+ VUSBIRhAttachDevice(pRh->pIRhConn, pLoad->apDevs[i]);
+
+ /*
+ * Cleanup.
+ */
+ TMR3TimerDestroy(pTimer);
+ MMR3HeapFree(pLoad);
+ pThis->pLoad = NULL;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ohciR3Reset(PPDMDEVINS pDevIns)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ 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(pThis, OHCI_USB_RESET, true /* reset devices */);
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ohciR3Resume(PPDMDEVINS pDevIns)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ LogFlowFunc(("\n"));
+
+ /* Restart the frame thread if the timer is active. */
+ if (TMTimerIsActive(pThis->pEndOfFrameTimerR3))
+ {
+ int rc = TMTimerStop(pThis->pEndOfFrameTimerR3);
+ AssertRC(rc);
+
+ LogFlowFunc(("Bus was active, enable periodic frame processing\n"));
+ rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ);
+ 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 = PDMINS_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);
+ }
+}
+
+
+/**
+ * Relocate device instance data.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ * @param offDelta The relocation delta.
+ */
+static DECLCALLBACK(void) ohciR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta)
+{
+ RT_NOREF(offDelta);
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+ pThis->pEndOfFrameTimerRC = TMTimerRCPtr(pThis->pEndOfFrameTimerR3);
+}
+
+
+/**
+ * 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)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheFree(pThis->pCacheED);
+ pThis->pCacheED = NULL;
+ ohciR3PhysReadCacheFree(pThis->pCacheTD);
+ pThis->pCacheTD = NULL;
+#endif
+
+ if (RTCritSectIsInitialized(&pThis->CritSect))
+ RTCritSectDelete(&pThis->CritSect);
+ PDMR3CritSectDelete(&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)
+{
+ POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI);
+ uint32_t cPorts;
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+
+ /*
+ * Init instance data.
+ */
+ pThis->pDevInsR3 = pDevIns;
+ pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns);
+ pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns);
+
+ PCIDevSetVendorId (&pThis->PciDev, 0x106b);
+ PCIDevSetDeviceId (&pThis->PciDev, 0x003f);
+ PCIDevSetClassProg (&pThis->PciDev, 0x10); /* OHCI */
+ PCIDevSetClassSub (&pThis->PciDev, 0x03);
+ PCIDevSetClassBase (&pThis->PciDev, 0x0c);
+ PCIDevSetInterruptPin (&pThis->PciDev, 0x01);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x80);
+#endif
+
+ pThis->RootHub.pOhci = pThis;
+ pThis->RootHub.IBase.pfnQueryInterface = ohciR3RhQueryInterface;
+ pThis->RootHub.IRhPort.pfnGetAvailablePorts = ohciR3RhGetAvailablePorts;
+ pThis->RootHub.IRhPort.pfnGetUSBVersions = ohciR3RhGetUSBVersions;
+ pThis->RootHub.IRhPort.pfnAttach = ohciR3RhAttach;
+ pThis->RootHub.IRhPort.pfnDetach = ohciR3RhDetach;
+ pThis->RootHub.IRhPort.pfnReset = ohciR3RhReset;
+ pThis->RootHub.IRhPort.pfnXferCompletion = ohciR3RhXferCompletion;
+ pThis->RootHub.IRhPort.pfnXferError = ohciR3RhXferError;
+ pThis->RootHub.IRhPort.pfnStartFrame = ohciR3StartFrame;
+ pThis->RootHub.IRhPort.pfnFrameRateChanged = ohciR3FrameRateChanged;
+
+ /* USB LED */
+ pThis->RootHub.Led.u32Magic = PDMLED_MAGIC;
+ pThis->RootHub.ILeds.pfnQueryStatusLed = ohciR3RhQueryStatusLed;
+
+
+ /*
+ * Read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "RZEnabled", "");
+ int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true);
+ AssertLogRelRCReturn(rc, rc);
+
+ /* Number of ports option. */
+ rc = CFGMR3QueryU32Def(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, &pThis->PciDev);
+ 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))
+ {
+ PCIDevSetCapabilityList(&pThis->PciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 4096, PCI_ADDRESS_SPACE_MEM, ohciR3Map);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Create the end-of-frame timer.
+ */
+ rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ohciR3FrameBoundaryTimer, pThis,
+ TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "USB Frame Timer",
+ &pThis->pEndOfFrameTimerR3);
+ if (RT_FAILURE(rc))
+ return rc;
+ pThis->pEndOfFrameTimerR0 = TMTimerR0Ptr(pThis->pEndOfFrameTimerR3);
+ pThis->pEndOfFrameTimerRC = TMTimerRCPtr(pThis->pEndOfFrameTimerR3);
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ ohciR3SavePrep, ohciR3SaveExec, ohciR3SaveDone,
+ ohciR3LoadPrep, ohciR3LoadExec, ohciR3LoadDone);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #0.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThis->RootHub.IBase, &pThis->RootHub.pIBase, "RootHub");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n"));
+ return rc;
+ }
+ pThis->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThis->RootHub.pIBase, VUSBIROOTHUBCONNECTOR);
+ AssertMsgReturn(pThis->RootHub.pIRhConn,
+ ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+ pThis->RootHub.pIDev = PDMIBASE_QUERY_INTERFACE(pThis->RootHub.pIBase, VUSBIDEVICE);
+ AssertMsgReturn(pThis->RootHub.pIDev,
+ ("Configuration error: The driver doesn't provide the VUSBIDEVICE interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->RootHub.IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThis->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(pThis->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("OHCI: Failed to set URB parameters"));
+
+ /*
+ * Calculate the timer intervals.
+ * This assumes that the VM timer doesn't change frequency during the run.
+ */
+ pThis->u64TimerHz = TMTimerGetFreq(pThis->CTX_SUFF(pEndOfFrameTimer));
+
+ 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(&pThis->CritSect);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("OHCI: Failed to create critical section"));
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ pThis->pCacheED = ohciR3PhysReadCacheAlloc();
+ pThis->pCacheTD = ohciR3PhysReadCacheAlloc();
+ if (pThis->pCacheED == NULL || pThis->pCacheTD == NULL)
+ return PDMDevHlpVMSetError(pDevIns, VERR_NO_MEMORY, RT_SRC_POS,
+ N_("OHCI: Failed to allocate PhysRead cache"));
+#endif
+
+ /*
+ * Do a hardware reset.
+ */
+ ohciR3DoReset(pThis, OHCI_USB_RESET, false /* don't reset devices */);
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledGenUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatDroppedUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/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;
+}
+
+
+const PDMDEVREG g_DeviceOHCI =
+{
+ /* u32version */
+ PDM_DEVREG_VERSION,
+ /* szName */
+ "usb-ohci",
+ /* szRCMod */
+ "VBoxDDRC.rc",
+ /* szR0Mod */
+ "VBoxDDR0.r0",
+ /* pszDescription */
+ "OHCI USB controller.\n",
+ /* fFlags */
+ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0,
+ /* fClass */
+ PDM_DEVREG_CLASS_BUS_USB,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(OHCI),
+ /* pfnConstruct */
+ ohciR3Construct,
+ /* pfnDestruct */
+ ohciR3Destruct,
+ /* pfnRelocate */
+ ohciR3Relocate,
+ /* pfnMemSetup */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ ohciR3Reset,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ ohciR3Resume,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnInitComplete */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32VersionEnd */
+ PDM_DEVREG_VERSION
+};
+
+#endif /* IN_RING3 */
+#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..24b10a7a
--- /dev/null
+++ b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp
@@ -0,0 +1,1592 @@
+/* $Id: DrvVUSBRootHub.cpp $ */
+/** @file
+ * Virtual USB - Root Hub Driver.
+ */
+
+/*
+ * Copyright (C) 2005-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/** @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"
+
+
+
+/**
+ * Attaches a device to a specific hub.
+ *
+ * This function is called by the vusb_add_device() and vusbRhAttachDevice().
+ *
+ * @returns VBox status code.
+ * @param pHub The hub to attach it to.
+ * @param pDev The device to attach.
+ * @thread EMT
+ */
+static int vusbHubAttach(PVUSBHUB pHub, PVUSBDEV pDev)
+{
+ LogFlow(("vusbHubAttach: pHub=%p[%s] pDev=%p[%s]\n", pHub, pHub->pszName, pDev, pDev->pUsbIns->pszName));
+ 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);
+
+ int rc = pHub->pOps->pfnAttach(pHub, pDev);
+ if (RT_FAILURE(rc))
+ {
+ pDev->pHub = NULL;
+ pDev->enmState = VUSB_DEVICE_STATE_DETACHED;
+ }
+ return rc;
+}
+
+
+/* -=-=-=-=-=- 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->Hub, pDev);
+ if (RT_SUCCESS(rc))
+ {
+ *piPort = UINT32_MAX; /// @todo implement piPort
+ return rc;
+ }
+
+ RTMemFree(pDev->paIfStates);
+ pUsbIns->pvVUsbDev2 = NULL;
+ }
+ vusbDevRelease(pDev);
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMUSBHUBREG,pfnDetachDevice} */
+static DECLCALLBACK(int) vusbPDMHubDetachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, uint32_t iPort)
+{
+ RT_NOREF(pDrvIns, iPort);
+ PVUSBDEV pDev = (PVUSBDEV)pUsbIns->pvVUsbDev2;
+ Assert(pDev);
+
+ /*
+ * Deal with pending async reset.
+ * (anything but reset)
+ */
+ vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Detach and free resources.
+ */
+ if (pDev->pHub)
+ vusbDevDetach(pDev);
+
+ vusbDevRelease(pDev);
+ return VINF_SUCCESS;
+}
+
+/**
+ * The hub registration structure.
+ */
+static const PDMUSBHUBREG g_vusbHubReg =
+{
+ PDM_USBHUBREG_VERSION,
+ vusbPDMHubAttachDevice,
+ vusbPDMHubDetachDevice,
+ PDM_USBHUBREG_VERSION
+};
+
+
+/* -=-=-=-=-=- VUSBIROOTHUBCONNECTOR methods -=-=-=-=-=- */
+
+
+/**
+ * Finds an device attached to a roothub by it's address.
+ *
+ * @returns Pointer to the device.
+ * @returns NULL if not found.
+ * @param pRh Pointer to the root hub.
+ * @param Address The device address.
+ */
+static PVUSBDEV vusbRhFindDevByAddress(PVUSBROOTHUB pRh, uint8_t Address)
+{
+ unsigned iHash = vusbHashAddress(Address);
+ PVUSBDEV pDev = NULL;
+
+ RTCritSectEnter(&pRh->CritSectDevices);
+ for (PVUSBDEV pCur = pRh->apAddrHash[iHash]; pCur; pCur = pCur->pNextHash)
+ if (pCur->u8Address == Address)
+ {
+ pDev = pCur;
+ break;
+ }
+
+ if (pDev)
+ vusbDevRetain(pDev);
+ RTCritSectLeave(&pRh->CritSectDevices);
+ return pDev;
+}
+
+
+/**
+ * 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);
+
+ /*
+ * 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);
+ }
+ else
+ vusbUrbPoolFree(&pRh->Hub.Dev.UrbPool, pUrb);
+}
+
+
+/**
+ * Worker routine for vusbRhConnNewUrb().
+ */
+static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, PVUSBDEV pDev, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag)
+{
+ RT_NOREF(pszTag);
+ PVUSBURBPOOL pUrbPool = &pRh->Hub.Dev.UrbPool;
+
+ if (!pDev)
+ pDev = vusbRhFindDevByAddress(pRh, DstAddress);
+ else
+ vusbDevRetain(pDev);
+
+ 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>");
+#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,pfnNewUrb} */
+static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, PVUSBIDEVICE pDev, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ return vusbRhNewUrb(pRh, DstAddress, (PVUSBDEV)pDev, 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
+ {
+ vusbDevRetain(&pRh->Hub.Dev);
+ pUrb->pVUsb->pDev = &pRh->Hub.Dev;
+ Log(("vusb: pRh=%p: SUBMIT: Address %i not found!!!\n", pRh, pUrb->DstAddress));
+
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ vusbUrbCompletionRh(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, PVUSBIDEVICE pDevice, RTMSINTERVAL cMillies)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); NOREF(pRh);
+ PVUSBDEV pDev = (PVUSBDEV)pDevice;
+
+ if (RTListIsEmpty(&pDev->LstAsyncUrbs))
+ return;
+
+ STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a);
+}
+
+
+/** @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 pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+
+ RTCritSectEnter(&pRh->CritSectDevices);
+ PVUSBDEV pDev = pRh->pDevices;
+ while (pDev)
+ {
+ vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev);
+ pDev = pDev->pNext;
+ }
+ RTCritSectLeave(&pRh->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, PVUSBIDEVICE pDevice, int EndPt, VUSBDIRECTION enmDir)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub)
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ RTCritSectEnter(&pRh->CritSectDevices);
+ PVUSBDEV pDev = (PVUSBDEV)pDevice;
+ vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhAbortEpWorker, 3, pDev, EndPt, enmDir);
+ RTCritSectLeave(&pRh->CritSectDevices);
+
+ /* The reaper thread will take care of completing the URB. */
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAttachDevice} */
+static DECLCALLBACK(int) vusbRhAttachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ return vusbHubAttach(&pRh->Hub, (PVUSBDEV)pDevice);
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDetachDevice} */
+static DECLCALLBACK(int) vusbRhDetachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub)
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+ return vusbDevDetach((PVUSBDEV)pDevice);
+}
+
+
+/** @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
+ || enmState == VMSTATE_RUNNING_FT)
+ {
+ rc = PDMR3ThreadResume(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, PVUSBIDEVICE pDevice,
+ int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ AssertReturn(pRh, 0);
+ PVUSBDEV pDev = (PVUSBDEV)pDevice;
+ 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;
+
+ return (uint16_t)uFrameDelta;
+}
+
+/* -=-=-=-=-=- VUSB Device methods (for the root hub) -=-=-=-=-=- */
+
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnReset}
+ */
+static DECLCALLBACK(int) vusbRhDevReset(PVUSBIDEVICE pInterface, bool fResetOnLinux,
+ PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM)
+{
+ RT_NOREF(pfnDone, pvUser, pVM);
+ PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice);
+ Assert(!pfnDone);
+ return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux); /** @todo change rc from bool to vbox status everywhere! */
+}
+
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnPowerOn}
+ */
+static DECLCALLBACK(int) vusbRhDevPowerOn(PVUSBIDEVICE pInterface)
+{
+ PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice);
+ LogFlow(("vusbRhDevPowerOn: pRh=%p\n", pRh));
+
+ Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED
+ && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET);
+
+ if (pRh->Hub.Dev.enmState == VUSB_DEVICE_STATE_ATTACHED)
+ pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_POWERED;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnPowerOff}
+ */
+static DECLCALLBACK(int) vusbRhDevPowerOff(PVUSBIDEVICE pInterface)
+{
+ PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice);
+ LogFlow(("vusbRhDevPowerOff: pRh=%p\n", pRh));
+
+ Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED
+ && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Cancel all URBs and reap them.
+ */
+ VUSBIRhCancelAllUrbs(&pRh->IRhConnector);
+ RTCritSectEnter(&pRh->CritSectDevices);
+ PVUSBDEV pDev = pRh->pDevices;
+ while (pDev)
+ {
+ VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, (PVUSBIDEVICE)pDev, 0);
+ pDev = pDev->pNext;
+ }
+ RTCritSectLeave(&pRh->CritSectDevices);
+
+ pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED;
+ return VINF_SUCCESS;
+}
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnGetState}
+ */
+static DECLCALLBACK(VUSBDEVICESTATE) vusbRhDevGetState(PVUSBIDEVICE pInterface)
+{
+ PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice);
+ return pRh->Hub.Dev.enmState;
+}
+
+
+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;
+}
+
+/* -=-=-=-=-=- VUSB Hub methods -=-=-=-=-=- */
+
+
+/**
+ * Attach the device to the hub.
+ * Port assignments and all such stuff is up to this routine.
+ *
+ * @returns VBox status code.
+ * @param pHub Pointer to the hub.
+ * @param pDev Pointer to the device.
+ */
+static int vusbRhHubOpAttach(PVUSBHUB pHub, PVUSBDEV pDev)
+{
+ PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub;
+
+ /*
+ * Assign a port.
+ */
+ int iPort = ASMBitFirstSet(&pRh->Bitmap, sizeof(pRh->Bitmap) * 8);
+ if (iPort < 0)
+ {
+ LogRel(("VUSB: No ports available!\n"));
+ return VERR_VUSB_NO_PORTS;
+ }
+ ASMBitClear(&pRh->Bitmap, iPort);
+ pHub->cDevices++;
+ pDev->i16Port = iPort;
+
+ /*
+ * Call the HCI attach routine and let it have its say before the device is
+ * linked into the device list of this hub.
+ */
+ int rc = pRh->pIRhPort->pfnAttach(pRh->pIRhPort, &pDev->IDevice, iPort);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectEnter(&pRh->CritSectDevices);
+ pDev->pNext = pRh->pDevices;
+ pRh->pDevices = pDev;
+ RTCritSectLeave(&pRh->CritSectDevices);
+ LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName,
+ iPort, pHub->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed)));
+ }
+ else
+ {
+ ASMBitSet(&pRh->Bitmap, iPort);
+ pHub->cDevices--;
+ pDev->i16Port = -1;
+ LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc));
+ }
+ return rc;
+}
+
+
+/**
+ * Detach the device from the hub.
+ *
+ * @returns VBox status code.
+ * @param pHub Pointer to the hub.
+ * @param pDev Pointer to the device.
+ */
+static void vusbRhHubOpDetach(PVUSBHUB pHub, PVUSBDEV pDev)
+{
+ PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub;
+ Assert(pDev->i16Port != -1);
+
+ /*
+ * Check that it's attached and unlink it from the linked list.
+ */
+ RTCritSectEnter(&pRh->CritSectDevices);
+ if (pRh->pDevices != pDev)
+ {
+ PVUSBDEV pPrev = pRh->pDevices;
+ while (pPrev && pPrev->pNext != pDev)
+ pPrev = pPrev->pNext;
+ Assert(pPrev);
+ pPrev->pNext = pDev->pNext;
+ }
+ else
+ pRh->pDevices = pDev->pNext;
+ pDev->pNext = NULL;
+ RTCritSectLeave(&pRh->CritSectDevices);
+
+ /*
+ * Detach the device and mark the port as available.
+ */
+ unsigned uPort = pDev->i16Port;
+ pRh->pIRhPort->pfnDetach(pRh->pIRhPort, &pDev->IDevice, uPort);
+ LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, uPort, pHub->pszName));
+ ASMBitSet(&pRh->Bitmap, uPort);
+ pHub->cDevices--;
+}
+
+
+/**
+ * The Hub methods implemented by the root hub.
+ */
+static const VUSBHUBOPS s_VUsbRhHubOps =
+{
+ vusbRhHubOpAttach,
+ vusbRhHubOpDetach
+};
+
+
+
+/* -=-=-=-=-=- 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);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIDEVICE, &pRh->Hub.Dev.IDevice);
+ 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->Hub.Dev.UrbPool);
+ if (pRh->Hub.pszName)
+ {
+ RTStrFree(pRh->Hub.pszName);
+ pRh->Hub.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);
+ LogFlow(("vusbRhConstruct: Instance %d\n", pDrvIns->iInstance));
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+
+ /*
+ * Validate configuration.
+ */
+ if (!CFGMR3AreValuesValid(pCfg, "CaptureFilename\0"))
+ return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
+
+ /*
+ * 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 = CFGMR3QueryStringAlloc(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->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED;
+ pThis->Hub.Dev.u8Address = VUSB_INVALID_ADDRESS;
+ pThis->Hub.Dev.u8NewAddress = VUSB_INVALID_ADDRESS;
+ pThis->Hub.Dev.i16Port = -1;
+ pThis->Hub.Dev.cRefs = 1;
+ pThis->Hub.Dev.IDevice.pfnReset = vusbRhDevReset;
+ pThis->Hub.Dev.IDevice.pfnPowerOn = vusbRhDevPowerOn;
+ pThis->Hub.Dev.IDevice.pfnPowerOff = vusbRhDevPowerOff;
+ pThis->Hub.Dev.IDevice.pfnGetState = vusbRhDevGetState;
+ /* the hub */
+ pThis->Hub.pOps = &s_VUsbRhHubOps;
+ pThis->Hub.pRootHub = pThis;
+ //pThis->hub.cPorts - later
+ pThis->Hub.cDevices = 0;
+ pThis->Hub.Dev.pHub = &pThis->Hub;
+ RTStrAPrintf(&pThis->Hub.pszName, "RootHub#%d", pDrvIns->iInstance);
+ /* misc */
+ pThis->pDrvIns = pDrvIns;
+ /* the connector */
+ pThis->IRhConnector.pfnSetUrbParams = vusbRhSetUrbParams;
+ 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.pfnAttachDevice = vusbRhAttachDevice;
+ pThis->IRhConnector.pfnDetachDevice = vusbRhDetachDevice;
+ pThis->IRhConnector.pfnSetPeriodicFrameProcessing = vusbRhSetFrameProcessing;
+ pThis->IRhConnector.pfnGetPeriodicFrameRate = vusbRhGetPeriodicFrameRate;
+ pThis->IRhConnector.pfnUpdateIsocFrameDelta = vusbRhUpdateIsocFrameDelta;
+ 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->Hub.cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap);
+ Log(("vusbRhConstruct: cPorts=%d\n", pThis->Hub.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->Hub.Dev.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);
+
+ MMR3HeapFree(pszCaptureFilename);
+ }
+
+ /*
+ * Register ourselves as a USB hub.
+ * The current implementation uses the VUSBIRHCONFIG interface for communication.
+ */
+ PCPDMUSBHUBHLP pHlp; /* not used currently */
+ rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->Hub.cPorts, &g_vusbHubReg, &pHlp);
+ if (RT_FAILURE(rc))
+ return 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_OCCURENCES, "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_OCCURENCES, "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->Hub.Dev.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..2744d6b3
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp
@@ -0,0 +1,51 @@
+/* $Id: USBProxyDevice-stub.cpp $ */
+/** @file
+ * USB device proxy - Stub.
+ */
+
+/*
+ * Copyright (C) 2008-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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..0a38363f
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice.cpp
@@ -0,0 +1,1284 @@
+/* $Id: USBProxyDevice.cpp $ */
+/** @file
+ * USBProxy - USB device proxy.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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\n", pProxyDev->pUsbIns->pszName));
+ for (;;)
+ {
+ /*
+ * Setup a MSG URB, queue and reap it.
+ */
+ int rc = VINF_SUCCESS;
+ VUSBURB Urb;
+ AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K);
+ Urb.u32Magic = VUSBURB_MAGIC;
+ Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
+ Urb.pszDesc = (char*)"URB sync";
+ Urb.pHci = NULL;
+ Urb.paTds = NULL;
+ Urb.Dev.pvPrivate = NULL;
+ Urb.Dev.pNext = NULL;
+ Urb.DstAddress = 0;
+ Urb.EndPt = 0;
+ Urb.enmType = VUSBXFERTYPE_MSG;
+ Urb.enmDir = VUSBDIRECTION_IN;
+ Urb.fShortNotOk = false;
+ Urb.enmStatus = VUSBSTATUS_INVALID;
+ Urb.pVUsb = NULL;
+ 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: pfnUrbReap 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)
+ {
+ 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\n", cbHint, sizeof(Urb.abData)));
+
+ 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 pNode The CFGM node.
+ * @param pszExact The exact value name.
+ * @param pszExpr The expression value name.
+ */
+static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, PCFGMNODE pNode, const char *pszExact, const char *pszExpr)
+{
+ char szTmp[256];
+
+ /* try exact first */
+ uint16_t u16;
+ int rc = CFGMR3QueryU16(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 = CFGMR3QueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
+ if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
+ {
+ szTmp[0] = '\0';
+ CFGMR3GetName(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';
+ CFGMR3GetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc));
+ return rc;
+ }
+
+ /* expression? */
+ rc = CFGMR3QueryString(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';
+ CFGMR3GetName(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);
+ 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 = CFGMR3QueryString(pCfg, "Address", szAddress, sizeof(szAddress));
+ AssertRCReturn(rc, rc);
+
+ char szBackend[64];
+ rc = CFGMR3QueryString(pCfg, "Backend", szBackend, sizeof(szBackend));
+ AssertRCReturn(rc, rc);
+
+ void *pvBackend;
+ rc = CFGMR3QueryPtr(pCfg, "pvBackend", &pvBackend);
+ 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, pvBackend);
+ 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 = CFGMR3GetFirstChild(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 = CFGMR3GetFirstChild(pCfgGlobal); pCur; pCur = CFGMR3GetNextChild(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, pCur, "idVendor", "idVendorExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pCur, "idProduct", "idProcutExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pCur, "bcdDevice", "bcdDeviceExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pCur, "bDeviceClass", "bDeviceClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pCur, "bDeviceSubClass", "bDeviceSubClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pCur, "bDeviceProtocol", "bDeviceProtocolExpr")))
+ continue; /* skip it */
+
+ /* strings */
+ /** @todo manufacturer, product and serial strings */
+
+ /* ignore unknown config values, but not without bitching. */
+ if (!CFGMR3AreValuesValid(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 = CFGMR3GetChild(pBestMatch, "Config");
+ if (pCfgGlobalDev)
+ pCfgGlobalDev = pCfgGlobal;
+ }
+
+ /*
+ * Query the rest of the configuration using the global as fallback.
+ */
+ rc = CFGMR3QueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = CFGMR3QueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fMaskedIfs = 0;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11Device;
+ rc = CFGMR3QueryBool(pCfg, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11Device = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11PacketSize;
+ rc = CFGMR3QueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11PacketSize = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fEditAudioSyncEp;
+ rc = CFGMR3QueryBool(pCfg, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = CFGMR3QueryBool(pCfgGlobalDev, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fEditAudioSyncEp = 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));
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * 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..4e05bdfe
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice.h
@@ -0,0 +1,276 @@
+/* $Id: USBProxyDevice.h $ */
+/** @file
+ * USBPROXY - USB proxy header
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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.
+ * @param pvBackend Pointer to backend specific data.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOpen, (PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend));
+
+ /**
+ * 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..40be6825
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBDevice.cpp
@@ -0,0 +1,1849 @@
+/* $Id: VUSBDevice.cpp $ */
+/** @file
+ * Virtual USB - Device.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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->pRootHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5,
+ pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue,
+ pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc);
+ RTCritSectLeave(&pDev->pHub->pRootHub->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->pRootHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt);
+ RTCritSectLeave(&pDev->pHub->pRootHub->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->pRootHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint,
+ 2, pDev->pUsbIns, pSetup->wIndex);
+ RTCritSectLeave(&pDev->pHub->pRootHub->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
+
+/**
+ * Standard device request: GET_DESCRIPTOR
+ * @returns success indicator.
+ * @remark not really used yet as we consider GET_DESCRIPTOR 'safe'.
+ */
+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=%p >= 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);
+}
+
+
+/**
+ * Add a device to the address hash
+ */
+static void vusbDevAddressHash(PVUSBDEV pDev)
+{
+ if (pDev->u8Address == VUSB_INVALID_ADDRESS)
+ return;
+ uint8_t u8Hash = vusbHashAddress(pDev->u8Address);
+ pDev->pNextHash = pDev->pHub->pRootHub->apAddrHash[u8Hash];
+ pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev;
+}
+
+/**
+ * Remove a device from the address hash
+ */
+static void vusbDevAddressUnHash(PVUSBDEV pDev)
+{
+ if (pDev->u8Address == VUSB_INVALID_ADDRESS)
+ return;
+
+ uint8_t u8Hash = vusbHashAddress(pDev->u8Address);
+ pDev->u8Address = VUSB_INVALID_ADDRESS;
+ pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
+
+ RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices);
+ PVUSBDEV pCur = pDev->pHub->pRootHub->apAddrHash[u8Hash];
+ if (pCur == pDev)
+ {
+ /* special case, we're at the head */
+ pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev->pNextHash;
+ pDev->pNextHash = NULL;
+ }
+ else
+ {
+ /* search the list */
+ PVUSBDEV pPrev;
+ for (pPrev = pCur, pCur = pCur->pNextHash;
+ pCur;
+ pPrev = pCur, pCur = pCur->pNextHash)
+ {
+ if (pCur == pDev)
+ {
+ pPrev->pNextHash = pCur->pNextHash;
+ pDev->pNextHash = NULL;
+ break;
+ }
+ }
+ }
+ RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices);
+}
+
+/**
+ * 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;
+
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ AssertPtrReturnVoid(pRh);
+ if (pDev->u8Address == VUSB_DEFAULT_ADDRESS)
+ pRh->pDefaultAddress = NULL;
+
+ vusbDevAddressUnHash(pDev);
+
+ if (u8Address == VUSB_DEFAULT_ADDRESS)
+ {
+ if (pRh->pDefaultAddress != NULL)
+ {
+ vusbDevAddressUnHash(pRh->pDefaultAddress);
+ vusbDevSetStateCmp(pRh->pDefaultAddress, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT);
+ Log(("2 DEFAULT ADDRS\n"));
+ }
+
+ pRh->pDefaultAddress = pDev;
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT);
+ }
+ else
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS);
+
+ pDev->u8Address = u8Address;
+ vusbDevAddressHash(pDev);
+
+ 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;
+}
+
+
+/**
+ * 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);
+
+ vusbDevCancelAllUrbs(pDev, true);
+ vusbDevAddressUnHash(pDev);
+
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ if (!pRh)
+ AssertMsgFailedReturn(("Not attached!\n"), VERR_VUSB_DEVICE_NOT_ATTACHED);
+ if (pRh->pDefaultAddress == pDev)
+ pRh->pDefaultAddress = NULL;
+
+ pDev->pHub->pOps->pfnDetach(pDev->pHub, pDev);
+ pDev->i16Port = -1;
+
+ /*
+ * Destroy I/O thread and request queue last because they might still be used
+ * when cancelling URBs.
+ */
+ vusbDevUrbIoThreadDestroy(pDev);
+
+ int rc = RTReqQueueDestroy(pDev->hReqQueueSync);
+ AssertRC(rc);
+ pDev->hReqQueueSync = NIL_RTREQQUEUE;
+
+ 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);
+ TMR3TimerDestroy(pDev->pResetTimer);
+ pDev->pResetTimer = NULL;
+ 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);
+
+ 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);
+ if (!vusbDevIsRh(pDev))
+ vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS);
+ if (pfnDone)
+ pfnDone(&pDev->IDevice, rc, pvUser);
+}
+
+
+/**
+ * Timer callback for doing reset completion.
+ *
+ * @param pUsbIns The USB device instance.
+ * @param pTimer The timer instance.
+ * @param pvUser The VUSB device data.
+ * @thread EMT
+ */
+static DECLCALLBACK(void) vusbDevResetDoneTimer(PPDMUSBINS pUsbIns, PTMTIMER pTimer, void *pvUser)
+{
+ RT_NOREF(pUsbIns, pTimer);
+ PVUSBDEV pDev = (PVUSBDEV)pvUser;
+ PVUSBRESETARGS pArgs = (PVUSBRESETARGS)pDev->pvArgs;
+ Assert(pDev->pUsbIns == pUsbIns);
+
+ 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 int vusbDevResetWorker(PVUSBDEV pDev, bool fResetOnLinux, bool fUseTimer, PVUSBRESETARGS pArgs)
+{
+ int rc = VINF_SUCCESS;
+ uint64_t u64EndTS = TMTimerGet(pDev->pResetTimer) + TMTimerFromMilli(pDev->pResetTimer, 10);
+
+ 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 = TMTimerSet(pDev->pResetTimer, u64EndTS);
+ 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;
+ }
+
+ /*
+ * If it's a root hub, we will have to cancel all URBs and reap them.
+ */
+ if (vusbDevIsRh(pDev))
+ {
+ PVUSBROOTHUB pRh = (PVUSBROOTHUB)pDev;
+ VUSBIRhCancelAllUrbs(&pRh->IRhConnector);
+ VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, pInterface, 0);
+ }
+
+ 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;
+}
+
+
+static DECLCALLBACK(int) vusbDevGetDescriptorCacheWorker(PPDMUSBINS pUsbIns, PCPDMUSBDESCCACHE *ppDescCache)
+{
+ *ppDescCache = pUsbIns->pReg->pfnUsbGetDescriptorCache(pUsbIns);
+ return VINF_SUCCESS;
+}
+
+/**
+ * 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->pNext = NULL;
+ pDev->pNextHash = NULL;
+ 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->pResetTimer = NULL;
+ 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 I/O thread. */
+ rc = vusbDevUrbIoThreadCreate(pDev);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the reset timer.
+ */
+ rc = PDMUsbHlpTMTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/,
+ "USB Device Reset Timer", &pDev->pResetTimer);
+ 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)
+ */
+ rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevGetDescriptorCacheWorker, 2, pUsbIns, &pDev->pDescCache);
+ AssertRC(rc);
+ 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..1ec05db8
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBInternal.h
@@ -0,0 +1,747 @@
+/* $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 VUSBHUB for its
+ * root hub implementation and any emulated USB device may be plugged into
+ * the virtual bus.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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
+
+
+/** @name Internal Device Operations, Structures and Constants.
+ * @{
+ */
+
+/** Pointer to a Virtual USB device (core). */
+typedef struct VUSBDEV *PVUSBDEV;
+/** Pointer to a VUSB hub device. */
+typedef struct VUSBHUB *PVUSBHUB;
+/** 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;
+ /** 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;
+ /** Next device in the chain maintained by the roothub. */
+ PVUSBDEV pNext;
+ /** Pointer to the next device with the same address hash. */
+ PVUSBDEV pNextHash;
+ /** Pointer to the hub this device is attached to. */
+ PVUSBHUB 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. */
+ PTMTIMER pResetTimer;
+ /** 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);
+
+DECLINLINE(bool) vusbDevIsRh(PVUSBDEV pDev)
+{
+ return (pDev->pHub == (PVUSBHUB)pDev);
+}
+
+bool vusbDevDoSelectConfig(PVUSBDEV dev, PCVUSBDESCCONFIGEX pCfg);
+void vusbDevMapEndpoint(PVUSBDEV dev, PCVUSBDESCENDPOINTEX ep);
+int vusbDevDetach(PVUSBDEV pDev);
+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);
+
+
+/** @} */
+
+
+/** @name Internal Hub Operations, Structures and Constants.
+ * @{
+ */
+
+
+/** Virtual method table for USB hub devices.
+ * Hub and roothub drivers need to implement these functions in addition to the
+ * vusb_dev_ops.
+ */
+typedef struct VUSBHUBOPS
+{
+ int (*pfnAttach)(PVUSBHUB pHub, PVUSBDEV pDev);
+ void (*pfnDetach)(PVUSBHUB pHub, PVUSBDEV pDev);
+} VUSBHUBOPS;
+/** Pointer to a const HUB method table. */
+typedef const VUSBHUBOPS *PCVUSBHUBOPS;
+
+/** A VUSB Hub Device - Hub and roothub drivers need to use this struct
+ * @todo eliminate this (PDM / roothubs only).
+ */
+typedef struct VUSBHUB
+{
+ VUSBDEV Dev;
+ PCVUSBHUBOPS pOps;
+ PVUSBROOTHUB pRootHub;
+ uint16_t cPorts;
+ uint16_t cDevices;
+ /** Name of the hub. Used for logging. */
+ char *pszName;
+} VUSBHUB;
+AssertCompileMemberAlignment(VUSBHUB, pOps, 8);
+AssertCompileSizeAlignment(VUSBHUB, 8);
+
+/** @} */
+
+
+/** @name 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;
+
+
+
+/** The address hash table size. */
+#define VUSB_ADDR_HASHSZ 5
+
+/**
+ * The instance data of a root hub driver.
+ *
+ * This extends the generic VUSB hub.
+ *
+ * @implements VUSBIROOTHUBCONNECTOR
+ */
+typedef struct VUSBROOTHUB
+{
+ /** The HUB.
+ * @todo remove this? */
+ VUSBHUB Hub;
+ /** Address hash table. */
+ PVUSBDEV apAddrHash[VUSB_ADDR_HASHSZ];
+ /** The default address. */
+ PVUSBDEV pDefaultAddress;
+
+ /** 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 list. */
+ RTCRITSECT CritSectDevices;
+ /** Chain of devices attached to this hub. */
+ PVUSBDEV pDevices;
+
+#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;
+
+/* @} */
+
+
+
+/** @name 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 vusbUrbCompletionRh(PVUSBURB pUrb);
+int vusbUrbSubmitHardError(PVUSBURB pUrb);
+int vusbUrbErrorRh(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);
+
+/**
+ * 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);
+}
+
+/** @def vusbUrbAssert
+ * Asserts that a URB is valid.
+ */
+#ifdef VBOX_STRICT
+# define vusbUrbAssert(pUrb) do { \
+ AssertMsg(VALID_PTR((pUrb)), ("%p\n", (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)
+
+/** @} */
+
+
+
+
+/**
+ * Addresses are between 0 and 127 inclusive
+ */
+DECLINLINE(uint8_t) vusbHashAddress(uint8_t Address)
+{
+ uint8_t u8Hash = Address;
+ u8Hash ^= (Address >> 2);
+ u8Hash ^= (Address >> 3);
+ u8Hash %= VUSB_ADDR_HASHSZ;
+ return u8Hash;
+}
+
+
+/**
+ * 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->pRootHub;
+}
+
+
+/**
+ * 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.
+ */
+DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis)
+{
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ 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.
+ */
+DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis)
+{
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ 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];
+/** Default message pipe. */
+extern const VUSBDESCENDPOINTEX g_Endpoint0;
+/** Default configuration. */
+extern const VUSBDESCCONFIGEX g_Config0;
+
+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..2661f9d6
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSniffer.cpp
@@ -0,0 +1,237 @@
+/* $Id: VUSBSniffer.cpp $ */
+/** @file
+ * Virtual USB - Sniffer facility.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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..7009f9cb
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSniffer.h
@@ -0,0 +1,101 @@
+/* $Id: VUSBSniffer.h $ */
+/** @file
+ * Virtual USB - Sniffer facility.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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..8a57d13f
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferInternal.h
@@ -0,0 +1,109 @@
+/* $Id: VUSBSnifferInternal.h $ */
+/** @file
+ * Virtual USB Sniffer facility - Internal header.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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..f3881455
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp
@@ -0,0 +1,731 @@
+/* $Id: VUSBSnifferPcapNg.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - PCAP-NG format writer.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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..e4bccf86
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp
@@ -0,0 +1,239 @@
+/* $Id: VUSBSnifferUsbMon.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - Linux usbmon ASCII format.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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..dffbc327
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp
@@ -0,0 +1,202 @@
+/* $Id: VUSBSnifferVmx.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - VMX USBIO format.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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..fa28b72c
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrb.cpp
@@ -0,0 +1,1477 @@
+/* $Id: VUSBUrb.cpp $ */
+/** @file
+ * Virtual USB - URBs.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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 pUrb The URB in question.
+ */
+int vusbUrbErrorRh(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED);
+ LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh));
+ return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb);
+}
+
+/**
+ * Does URB completion on roothub level.
+ *
+ * @param pUrb The URB to complete.
+ */
+void vusbUrbCompletionRh(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));
+ }
+
+ PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev);
+ AssertPtrReturnVoid(pRh);
+
+ /* 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)
+ vusbUrbErrorRh(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:
+ if ( !pUrb->pVUsb->pDev->pDescCache->fUseCachedDescriptors
+ || (pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
+ return true;
+ switch (pSetup->wValue >> 8)
+ {
+ case VUSB_DT_DEVICE:
+ case VUSB_DT_CONFIG:
+ return false;
+ case VUSB_DT_STRING:
+ return !pUrb->pVUsb->pDev->pDescCache->fUseCachedStringsDescriptors;
+ default:
+ return true;
+ }
+
+ 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;
+ const size_t cbMax = sizeof(VUSBURBVUSBINT) + sizeof(pExtra->Urb.abData) + 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 = (PVUSBURBVUSB)&pExtra->Urb.abData[sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP)];
+ //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 = (PVUSBURBVUSB)&pExtra->Urb.abData[sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP)];
+ 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 + sizeof(VUSBURBVUSBINT))
+ {
+ uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024);
+ 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)
+ {
+ pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData;
+ pExtra = pNew;
+ }
+ pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength];
+ pExtra->Urb.pVUsb->pUrb = &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 (&pExtra->pbCur[pUrb->cbData] > &pbData[pSetup->wLength])
+ {
+ if (!pSetup->wLength) /* happens during iPhone detection with iTunes (correct?) */
+ {
+ Log(("%s: vusbUrbSubmitCtrl: pSetup->wLength == 0!! (iPhone)\n", pUrb->pszDesc));
+ pSetup->wLength = pUrb->cbData;
+ }
+
+ /* Variable length data transfers */
+ if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST)
+ || pSetup->wLength == 0
+ || (pUrb->cbData % pSetup->wLength) == 0) /* magic which need explaining... */
+ {
+ uint8_t *pbEnd = pbData + pSetup->wLength;
+ int cbLeft = pbEnd - pExtra->pbCur;
+ LogFlow(("%s: vusbUrbSubmitCtrl: Var DATA, pUrb->cbData %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft));
+ pUrb->cbData = cbLeft;
+ }
+ else
+ {
+ Log(("%s: vusbUrbSubmitCtrl: Stall at data stage!!\n", pUrb->pszDesc));
+ 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. */
+ 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 (pRipe == pVUsbUrbNext->pUrb)
+ pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, 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..3e390342
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrbPool.cpp
@@ -0,0 +1,243 @@
+/* $Id: VUSBUrbPool.cpp $ */
+/** @file
+ * Virtual USB - URB pool.
+ */
+
+/*
+ * Copyright (C) 2016-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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);
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+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..8044938a
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrbTrace.cpp
@@ -0,0 +1,809 @@
+/* $Id: VUSBUrbTrace.cpp $ */
+/** @file
+ * Virtual USB - URBs.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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->aPipes[pUrb->EndPt];
+ 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->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)
+ 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..475e0981
--- /dev/null
+++ b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp
@@ -0,0 +1,2006 @@
+/* $Id: USBProxyDevice-darwin.cpp $ */
+/** @file
+ * USB device proxy - the Darwin backend.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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>
+
+#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
+ * PAGE_SIZE chunks. The size of IOUSBLowLatencyIsocFrame is 16 bytes
+ * and we require 8 of those per buffer. 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 != VALID_PTR(pNew->pvBuffer))
+ {
+ AssertPtr(pNew->pvBuffer);
+ irc = kIOReturnNoMemory;
+ }
+ if (irc == kIOReturnSuccess)
+ {
+ irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvFrames, PAGE_SIZE, kUSBLowLatencyFrameListBuffer);
+ if (irc == kIOReturnSuccess != 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 ";".
+ * @param pvBackend Backend specific pointer, unused for the Darwin backend.
+ */
+static DECLCALLBACK(int) usbProxyDarwinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)
+{
+ RT_NOREF(pvBackend);
+ 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 != IO_OBJECT_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;
+ }
+
+ /*
+ * Call the USBLib init to make sure we're a valid VBoxUSB client.
+ * For now we'll ignore failures here and just plunge on, it might still work...
+ */
+ vrc = USBLibInit();
+ if (RT_FAILURE(vrc))
+ LogRel(("USB: USBLibInit failed - %Rrc\n", vrc));
+
+ /*
+ * 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)->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);
+ }
+
+ USBLibTerm();
+ 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));
+ }
+
+ (*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);
+ }
+
+ USBLibTerm();
+ 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..e68a2025
--- /dev/null
+++ b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp
@@ -0,0 +1,1062 @@
+/* $Id: USBProxyDevice-freebsd.cpp $ */
+/** @file
+ * USB device proxy - the FreeBSD backend.
+ */
+
+/*
+ * Includes contributions from Hans Petter Selasky
+ *
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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 "//".
+ * @param pvBackend Backend specific pointer, unused for the linux backend.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress,
+ void *pvBackend)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ NOREF(pvBackend);
+
+ /*
+ * 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..a0333473
--- /dev/null
+++ b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp
@@ -0,0 +1,1698 @@
+/* $Id: USBProxyDevice-linux.cpp $ */
+/** @file
+ * USB device proxy - the Linux backend.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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
+{
+ /** The kernel URB data. */
+#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
+ struct usbdevfs_urb KUrb;
+#if RT_GNUC_PREREQ(6, 0)
+# pragma GCC diagnostic pop
+#endif
+ /** Space filler for the isochronous packets. */
+ struct usbdevfs_iso_packet_desc aIsocPktsDonUseTheseUseTheOnesInKUrb[8];
+ /** 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;
+} 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 waiking up - writing end. */
+ RTPIPE hPipeWakeupW;
+ /** Pipe handle for waiking 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);
+ pUrbLnx = (PUSBPROXYURBLNX)RTMemAlloc(sizeof(*pUrbLnx));
+ 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 "//".
+ * @param pvBackend Backend specific pointer, unused for the linux backend.
+ */
+static DECLCALLBACK(int) usbProxyLinuxOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)
+{
+ 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=%s!\n", pProxyDev, pszAddress,
+ RTErrGetShort(rc)));
+
+ NOREF(pvBackend);
+ 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=%s errno=%d.\n",
+ RTErrGetShort(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 = (PUSBPROXYURBLNX)pKUrb;
+
+ /* 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..0464e6e6
--- /dev/null
+++ b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp
@@ -0,0 +1,870 @@
+/* $Id: USBProxyDevice-os2.cpp $ */
+/** @file
+ * USB device proxy - the Linux backend.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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.
+ * @param pvBackend Backend specific pointer, unused for the linux backend.
+ */
+static int usbProxyOs2Open(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)
+{
+ 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..b727475a
--- /dev/null
+++ b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp
@@ -0,0 +1,909 @@
+/* $Id: USBProxyDevice-solaris.cpp $ */
+/** @file
+ * USB device proxy - the Solaris backend.
+ */
+
+/*
+ * Copyright (C) 2009-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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".
+ * @param pvBackend Backend specific pointer, unused for the solaris backend.
+ */
+static DECLCALLBACK(int) usbProxySolarisOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)
+{
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ LogFlowFunc((USBPROXY ":usbProxySolarisOpen: pProxyDev=%p pszAddress=%s pvBackend=%p\n", pProxyDev, pszAddress, pvBackend));
+
+ /*
+ * 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..149eb8ba
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp
@@ -0,0 +1,594 @@
+/* $Id: tstOhciRegisterAccess.cpp $ */
+/** @file
+ * tstOhciRegisterAccess - OHCI Register Access Tests / Experiments.
+ */
+
+/*
+ * Copyright (C) 2011-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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 = ((uintptr_t)uPtr.pv & 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 = ((uintptr_t)uPtr.pv & 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, 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..7fc73d83
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstPalmOne.c
@@ -0,0 +1,402 @@
+/* $Id: tstPalmOne.c $ */
+/** @file
+ * USB PalmOne testcase
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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..cc05c106
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c
@@ -0,0 +1,313 @@
+/* $Id: tstTrekStorGo.c $ */
+/** @file
+ * Some simple inquiry test for the TrekStor USB-Stick GO, linux usbfs
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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..9faffc71
--- /dev/null
+++ b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp
@@ -0,0 +1,1764 @@
+/* $Id: USBProxyDevice-usbip.cpp $ */
+/** @file
+ * USB device proxy - USB/IP backend.
+ */
+
+/*
+ * Copyright (C) 2014-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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 = VINF_SUCCESS;
+
+ 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:
+ {
+ /** @todo Verify that the directions match, verify that the length doesn't exceed the buffer. */
+
+ 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;
+
+ AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb);
+ if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG)
+ {
+ /* Preserve the setup request. */
+ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)];
+ pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP);
+ }
+ else
+ {
+ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0];
+ pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength;
+ }
+
+ if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength)
+ usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER,
+ pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength);
+ 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);
+ }
+ 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];
+ usbProxyUsbIpIsocPktDescN2H(&pProxyDevUsbIp->aIsocPktDesc[i]);
+ pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->aIsocPktDesc[i].i32Status);
+ pIsocPkt->off = pProxyDevUsbIp->aIsocPktDesc[i].u32Offset;
+ pIsocPkt->cb = pProxyDevUsbIp->aIsocPktDesc[i].u32ActualLength;
+ }
+
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ break;
+ default:
+ AssertLogRelMsgFailed(("USB/IP: Invalid receive state %d\n", pProxyDevUsbIp->enmRecvState));
+ }
+ }
+ }
+ else
+ {
+ /** @todo Complete all URBs with DNR error and mark device as unplugged. */
+#if 0
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ pUrbUsbIp->pVUsbUrb->enmStatus = VUSBSTATUS_DNR;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+#endif
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppUrbUsbIp = pUrbUsbIp;
+
+ 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:
+ usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp);
+ 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))
+ {
+ /** @todo Complete the URB with an error. */
+ usbProxyUsbIpUrbFree(pProxyDevUsbIp, 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, void *pvBackend)
+{
+ RT_NOREF(pvBackend);
+ LogFlowFunc(("pProxyDev=%p pszAddress=%s, pvBackend=%p\n", pProxyDev, pszAddress, pvBackend));
+
+ 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);
+ /* Destroy the pipe and pollset if necessary. */
+ if (pDevUsbIp->hPollSet != NIL_RTPOLLSET)
+ {
+ if (pDevUsbIp->hSocket != NIL_RTSOCKET)
+ {
+ rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+ }
+ 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->hSocket != NIL_RTSOCKET)
+ usbProxyUsbIpDisconnect(pDevUsbIp);
+ 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..28b27a0a
--- /dev/null
+++ b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp
@@ -0,0 +1,287 @@
+/* $Id: USBProxyDevice-vrdp.cpp $ */
+/** @file
+ * USB device proxy - the VRDP backend, calls the RemoteUSBBackend methods.
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+#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 "../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, void *pvBackend)
+{
+ LogFlow(("usbProxyVrdpOpen: pProxyDev=%p pszAddress=%s, pvBackend=%p\n", pProxyDev, pszAddress, pvBackend));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+ int rc = VINF_SUCCESS;
+
+ if (strncmp (pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) == 0)
+ {
+ REMOTEUSBCALLBACK *pCallback = (REMOTEUSBCALLBACK *)pvBackend;
+ 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..60ff5976
--- /dev/null
+++ b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp
@@ -0,0 +1,778 @@
+/* $Id: USBProxyDevice-win.cpp $ */
+/** @file
+ * USBPROXY - USB proxy, Win32 backend
+ */
+
+/*
+ * Copyright (C) 2006-2019 Oracle Corporation
+ *
+ * This file is part of VirtualBox Open Source Edition (OSE), as
+ * available from http://www.virtualbox.org. This file is free software;
+ * you can redistribute it and/or modify it under the terms of the GNU
+ * General Public License (GPL) as published by the Free Software
+ * Foundation, in version 2 as it comes in the "COPYING" file of the
+ * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
+ * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
+ */
+
+
+/*********************************************************************************************************************************
+* 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;
+ /** 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!!\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, void *pvBackend)
+{
+ RT_NOREF(pvBackend);
+ 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!!\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!!\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)
+ {
+ if ( cMillies != 0
+ && pPriv->cPendingUrbs == 0)
+ {
+ /* Wait for the wakeup call. */
+ DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies;
+ DWORD dwRc = WaitForMultipleObjects(1, &pPriv->hEventWakeup, FALSE, cMilliesWait);
+ NOREF(dwRc);
+ }
+
+ return NULL;
+ }
+
+again:
+ /* Check for pending URBs. */
+ 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.
+ *
+ * ASSUMPTIONS:
+ * 1. The usbProxyWinUrbReap can not be run concurrently with each other
+ * so racing the cQueuedUrbs access/modification can not occur.
+ * 2. The usbProxyWinUrbReap can not be run concurrently with
+ * usbProxyWinUrbQueue so they can not race the pPriv->paHandles
+ * access/realloc.
+ */
+ 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);
+
+ /* If the wakeup event fired return immediately. */
+ if (rc == WAIT_OBJECT_0 + cQueuedUrbs)
+ {
+ if (pPriv->cPendingUrbs)
+ goto again;
+ return NULL;
+ }
+
+ 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->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!!\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);
+
+ 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
+};
+