summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/USB
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:49:04 +0000
commit16f504a9dca3fe3b70568f67b7d41241ae485288 (patch)
treec60f36ada0496ba928b7161059ba5ab1ab224f9d /src/VBox/Devices/USB
parentInitial commit. (diff)
downloadvirtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.tar.xz
virtualbox-16f504a9dca3fe3b70568f67b7d41241ae485288.zip
Adding upstream version 7.0.6-dfsg.upstream/7.0.6-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/Devices/USB')
-rw-r--r--src/VBox/Devices/USB/DevEHCI.cpp5233
-rw-r--r--src/VBox/Devices/USB/DevOHCI.cpp6087
-rw-r--r--src/VBox/Devices/USB/DevXHCI.cpp8295
-rw-r--r--src/VBox/Devices/USB/DrvVUSBRootHub.cpp1880
-rw-r--r--src/VBox/Devices/USB/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice-stub.cpp61
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice.cpp1328
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice.h285
-rw-r--r--src/VBox/Devices/USB/VUSBDevice.cpp1884
-rw-r--r--src/VBox/Devices/USB/VUSBInternal.h742
-rw-r--r--src/VBox/Devices/USB/VUSBSniffer.cpp247
-rw-r--r--src/VBox/Devices/USB/VUSBSniffer.h111
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferInternal.h119
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp741
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp249
-rw-r--r--src/VBox/Devices/USB/VUSBSnifferVmx.cpp212
-rw-r--r--src/VBox/Devices/USB/VUSBUrb.cpp1501
-rw-r--r--src/VBox/Devices/USB/VUSBUrbPool.cpp245
-rw-r--r--src/VBox/Devices/USB/VUSBUrbTrace.cpp831
-rw-r--r--src/VBox/Devices/USB/darwin/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp2014
-rw-r--r--src/VBox/Devices/USB/freebsd/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp1068
-rw-r--r--src/VBox/Devices/USB/linux/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp1708
-rw-r--r--src/VBox/Devices/USB/os2/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp879
-rw-r--r--src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp918
-rw-r--r--src/VBox/Devices/USB/testcase/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp604
-rw-r--r--src/VBox/Devices/USB/testcase/tstPalmOne.c412
-rw-r--r--src/VBox/Devices/USB/testcase/tstTrekStorGo.c323
-rw-r--r--src/VBox/Devices/USB/usbip/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp1818
-rw-r--r--src/VBox/Devices/USB/vrdp/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp322
-rw-r--r--src/VBox/Devices/USB/win/Makefile.kup0
-rw-r--r--src/VBox/Devices/USB/win/USBProxyDevice-win.cpp813
38 files changed, 40930 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevEHCI.cpp b/src/VBox/Devices/USB/DevEHCI.cpp
new file mode 100644
index 00000000..ad5fde70
--- /dev/null
+++ b/src/VBox/Devices/USB/DevEHCI.cpp
@@ -0,0 +1,5233 @@
+/* $Id: DevEHCI.cpp $ */
+/** @file
+ * DevEHCI - Enhanced Host Controller Interface for USB.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_dev_ehci EHCI - Enhanced Host Controller Interface Emulation.
+ *
+ * This component implements an EHCI USB controller. It is split roughly
+ * into two main parts, the first part implements the register level
+ * specification of USB EHCI and the second part maintains the root hub (which
+ * is an integrated component of the device).
+ *
+ * The EHCI registers are used for the usual stuff like enabling and disabling
+ * interrupts. Since the USB time is divided in to 1ms frames and various
+ * interrupts may need to be triggered at frame boundary time, a timer-based
+ * approach was taken.
+ *
+ * Note that all processing is currently done on a frame boundary and
+ * no attempt is made to emulate events with micro-frame granularity.
+ *
+ * The actual USB transfers are stored in main memory (along with endpoint and
+ * transfer descriptors). The ED's for all the control and bulk endpoints are
+ * found by consulting the HcAsyncListAddr register (ASYNCLISTADDR).
+ * Interrupt and isochronous ED's are found by looking at the HcPeriodicListBase
+ * (PERIODICLISTBASE) register.
+ *
+ * At the start of every frame (in function ehciR3StartOfFrame) we traverse all
+ * enabled ED lists and queue up as many transfers as possible. No attention
+ * is paid to control/bulk service ratios or bandwidth requirements since our
+ * USB could conceivably contain a dozen high speed busses and this would
+ * artificially limit the performance.
+ *
+ * Once we have a transfer ready to go (in the appropriate ehciServiceXxx function)
+ * we allocate an URB on the stack, fill in all the relevant fields and submit
+ * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual
+ * USB core code coordinates everything else from this point onwards.
+ *
+ * When the URB has been successfully handed to the lower level driver, our
+ * prepare callback gets called and we can remove the TD from the ED transfer
+ * list. This stops us queueing it twice while it completes.
+ * bird: no, we don't remove it because that confuses the guest! (=> crashes)
+ *
+ * Completed URBs are reaped at the end of every frame (in function
+ * ehciR3FrameBoundaryTimer). Our completion routine makes use of the ED and TD
+ * fields in the URB to store the physical addresses of the descriptors so
+ * that they may be modified in the roothub callbacks. Our completion
+ * routine (ehciRhXferCompleteXxx) carries out a number of tasks:
+ * -# Retires the TD associated with the transfer, setting the
+ * relevant error code etc.
+ * -# Updates done-queue interrupt timer and potentially causes
+ * a writeback of the done-queue.
+ * -# If the transfer was device-to-host, we copy the data into
+ * the host memory.
+ *
+ * As for error handling EHCI allows for 3 retries before failing a transfer,
+ * an error count is stored in each transfer descriptor. A halt flag is also
+ * stored in the transfer descriptor. That allows for ED's to be disabled
+ * without stopping the bus and de-queuing them.
+ *
+ * When the bus is started and stopped, we call VUSBIDevPowerOn/Off() on our
+ * roothub to indicate it's powering up and powering down. Whenever we power
+ * down, the USB core makes sure to synchronously complete all outstanding
+ * requests so that the EHCI is never seen in an inconsistent state by the
+ * guest OS (Transfers are not meant to be unlinked until they've actually
+ * completed, but we can't do that unless we work synchronously, so we just
+ * have to fake it).
+ * bird: we do work synchronously now, anything causes guest crashes.
+ *
+ * The number of ports is configurable. The architectural maximum is 15, but
+ * some guests (e.g. OS/2) crash if they see more than 12 or so ports. Saved
+ * states always include the data for all 15 ports but HCSPARAMS determines
+ * the actual number visible to the guest.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_EHCI
+#include <VBox/pci.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/param.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+#ifdef IN_RING3
+# include <iprt/thread.h>
+# include <iprt/mem.h>
+# include <iprt/uuid.h>
+#endif
+#include <VBox/vusb.h>
+#ifdef VBOX_IN_EXTPACK_R3
+# include <VBox/version.h>
+#elif defined(VBOX_IN_EXTPACK)
+# include <VBox/sup.h>
+#endif
+#ifndef VBOX_IN_EXTPACK
+# include "VBoxDD.h"
+#endif
+
+
+/** The saved state version. */
+#define EHCI_SAVED_STATE_VERSION 7
+/** The saved state version before the EOF timers were removed. */
+#define EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL 6 /* Introduced in 5.2. */
+/** The saved state with support of 8 ports. */
+#define EHCI_SAVED_STATE_VERSION_8PORTS 5 /* Introduced in 3.1 or so. */
+
+/** Number of Downstream Ports on the root hub; 15 is the maximum
+ * the EHCI specification provides for. */
+#define EHCI_NDP_MAX 15
+
+/** The default Number of Downstream Ports reported to guests. */
+#define EHCI_NDP_DEFAULT 12
+
+/* Macro to query the number of currently configured ports. */
+#define EHCI_NDP_CFG(pehci) ((pehci)->hcs_params & EHCI_HCS_PARAMS_NDP_MASK)
+/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */
+#define EHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1)
+
+/** Size of the capability part of the MMIO page. */
+#define EHCI_CAPS_REG_SIZE 0x20
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * Host controller Transfer Descriptor data.
+ */
+typedef struct VUSBURBHCITDINT
+{
+ /** Type of TD. */
+ uint32_t TdType;
+ /** The address of the TD. */
+ RTGCPHYS TdAddr;
+ /** A copy of the TD. */
+ uint32_t TdCopy[16];
+} VUSBURBHCITDINT;
+
+/**
+ * The host controller data associated with each URB.
+ */
+typedef struct VUSBURBHCIINT
+{
+ /** The endpoint descriptor address. */
+ RTGCPHYS EdAddr;
+ /** Number of Tds in the array. */
+ uint32_t cTds;
+ /** When this URB was created.
+ * (Used for isochronous frames and for logging.) */
+ uint32_t u32FrameNo;
+ /** Flag indicating that the TDs have been unlinked. */
+ bool fUnlinked;
+} VUSBURBHCIINT;
+#endif
+
+/**
+ * An EHCI root hub port, shared.
+ */
+typedef struct EHCIHUBPORT
+{
+ /** The port register. */
+ uint32_t fReg;
+} EHCIHUBPORT;
+/** Pointer to a shared EHCI root hub port. */
+typedef EHCIHUBPORT *PEHCIHUBPORT;
+
+/**
+ * An EHCI root hub port, ring-3.
+ */
+typedef struct EHCIHUBPORTR3
+{
+ /** Flag whether there is a device attached to the port. */
+ bool fAttached;
+} EHCIHUBPORTR3;
+/** Pointer to a ring-3 EHCI root hub port. */
+typedef EHCIHUBPORTR3 *PEHCIHUBPORTR3;
+
+
+/**
+ * The EHCI root hub, shared.
+ */
+typedef struct EHCIROOTHUB
+{
+ /** Per-port state. */
+ EHCIHUBPORT aPorts[EHCI_NDP_MAX];
+ /** Unused, only needed for saved state compatibility. */
+ uint32_t unused;
+} EHCIROOTHUB;
+/** Pointer to the EHCI root hub. */
+typedef EHCIROOTHUB *PEHCIROOTHUB;
+
+
+/**
+ * The EHCI root hub, ring-3 edition.
+ *
+ * @implements PDMIBASE
+ * @implements VUSBIROOTHUBPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct EHCIROOTHUBR3
+{
+ /** Pointer to the base interface of the VUSB RootHub. */
+ R3PTRTYPE(PPDMIBASE) pIBase;
+ /** Pointer to the connector interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn;
+ /** The base interface exposed to the roothub driver. */
+ PDMIBASE IBase;
+ /** The roothub port interface exposed to the roothub driver. */
+ VUSBIROOTHUBPORT IRhPort;
+
+ /** The LED. */
+ PDMLED Led;
+ /** The LED ports. */
+ PDMILEDPORTS ILeds;
+ /** Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+
+ EHCIHUBPORTR3 aPorts[EHCI_NDP_MAX];
+} EHCIROOTHUBR3;
+/** Pointer to the ring-3 EHCI root hub state. */
+typedef EHCIROOTHUBR3 *PEHCIROOTHUBR3;
+
+
+/**
+ * EHCI device data, shared.
+ */
+typedef struct EHCI
+{
+ /** Async scheduler sleeping; triggered by empty list detection */
+ bool fAsyncTraversalTimerActive;
+
+ bool afAlignment0[7];
+
+ /** Start of current frame. */
+ uint64_t SofTime;
+ /** Root hub device */
+ EHCIROOTHUB RootHub;
+
+ /** @name Host Controller Capability Registers (R/O)
+ * @{ */
+ /** CAPLENGTH: base + cap_length = operational register start */
+ uint32_t cap_length;
+ /** HCIVERSION: host controller interface version */
+ uint32_t hci_version;
+ /** HCSPARAMS: Structural parameters */
+ uint32_t hcs_params;
+ /** HCCPARAMS: Capability parameters */
+ uint32_t hcc_params;
+ /** @} */
+
+ /** @name Host Controller Operational Registers (R/W)
+ * @{ */
+ /** USB command register */
+ uint32_t cmd;
+ /** USB status register */
+ uint32_t intr_status;
+ /** USB interrupt enable register */
+ uint32_t intr;
+ /** Frame index register; actually it's micro-frame number */
+ uint32_t frame_idx;
+ /** Control Data Structure Segment Register */
+ uint32_t ds_segment;
+ /** Periodic Frame List Base Address Register */
+ uint32_t periodic_list_base;
+ /** Current Asynchronous List Address Register */
+ uint32_t async_list_base;
+ /** Configure Flag Register */
+ uint32_t config;
+ /** @} */
+
+ /** @name Control partition (registers)
+ * @{ */
+ /** Interrupt interval; see interrupt threshold in the command register */
+ uint32_t uIrqInterval;
+ /** @} */
+
+ /** @name Frame counter partition (registers)
+ * @{ */
+ /** HcFmNumber.
+ * @remark The register size is 16-bit, but for debugging and performance
+ * reasons we maintain a 32-bit counter. */
+ uint32_t HcFmNumber;
+ /** Number of micro-frames per timer call */
+ uint32_t uFramesPerTimerCall;
+ /** @} */
+
+ /** Flag whether the framer thread should processing frames. */
+ volatile bool fBusStarted;
+
+ bool afAlignment1[3]; /**< Align CsIrq correctly. */
+
+ /* The following members are not part of saved state. */
+
+ /** Critical section synchronising interrupt handling. */
+ PDMCRITSECT CsIrq;
+
+ /** The MMIO region. */
+ IOMMMIOHANDLE hMmio;
+} EHCI;
+AssertCompileMemberAlignment(EHCI, CsIrq, 8);
+/** Pointer to shared EHCI device data. */
+typedef struct EHCI *PEHCI;
+
+
+/**
+ * EHCI device data, ring-3 edition.
+ */
+typedef struct EHCIR3
+{
+ /** Root hub device. */
+ EHCIROOTHUBR3 RootHub;
+
+ /** The number of virtual time ticks per frame. */
+ uint64_t cTicksPerFrame;
+ /** The number of virtual time ticks per USB bus tick. */
+ uint64_t cTicksPerUsbTick;
+
+ /** Pointer to the device instance. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** Number of in-flight TDs. */
+ unsigned cInFlight;
+ unsigned Alignment2; /**< Align aInFlight on a 8 byte boundary. */
+ /** Array of in-flight TDs. */
+ struct ehci_td_in_flight
+ {
+ /** Address of the transport descriptor. */
+ RTGCPHYS GCPhysTD;
+ /** Pointer to the URB. */
+ R3PTRTYPE(PVUSBURB) pUrb;
+ } aInFlight[257];
+
+ /** Detected canceled isochronous URBs. */
+ STAMCOUNTER StatCanceledIsocUrbs;
+ /** Detected canceled general URBs. */
+ STAMCOUNTER StatCanceledGenUrbs;
+ /** Dropped URBs (endpoint halted, or URB canceled). */
+ STAMCOUNTER StatDroppedUrbs;
+
+ /* The following members are not part of saved state. */
+
+ /** VM timer frequency used for frame timer calculations. */
+ uint64_t u64TimerHz;
+ /** Number of USB work cycles with no transfers. */
+ uint32_t cIdleCycles;
+ /** Current frame timer rate (default 1000). */
+ uint32_t uFrameRate;
+ /** Idle detection flag; must be cleared at start of frame */
+ bool fIdle;
+ bool afAlignment4[3];
+
+ /** Default frequency of the frame timer. */
+ uint32_t uFrameRateDefault;
+ /** How long to wait until the next frame. */
+ uint64_t nsWait;
+ /** The framer thread. */
+ R3PTRTYPE(PPDMTHREAD) hThreadFrame;
+ /** Event semaphore to interact with the framer thread. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrame;
+ /** Event semaphore to release the thread waiting for the framer thread to stop. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventFrameStopped;
+ /** Critical section to synchronize the framer and URB completion handler. */
+ RTCRITSECT CritSect;
+} EHCIR3;
+/** Pointer to ring-3 EHCI device data. */
+typedef struct EHCIR3 *PEHCIR3;
+
+
+/**
+ * EHCI device data, ring-0 edition.
+ */
+typedef struct EHCIR0
+{
+ uint32_t uUnused;
+} EHCIR0;
+/** Pointer to ring-0 EHCI device data. */
+typedef struct EHCIR0 *PEHCIR0;
+
+
+/**
+ * EHCI device data, raw-mode edition.
+ */
+typedef struct EHCIRC
+{
+ uint32_t uUnused;
+} EHCIRC;
+/** Pointer to raw-mode EHCI device data. */
+typedef struct EHCIRC *PEHCIRC;
+
+
+/** @typedef EHCICC
+ * The EHCI device data for the current context. */
+typedef CTX_SUFF(EHCI) EHCICC;
+/** @typedef PEHCICC
+ * Pointer to the EHCI device for the current context. */
+typedef CTX_SUFF(PEHCI) PEHCICC;
+
+
+/** @@name EHCI Transfer Descriptor Types
+ * @{ */
+/** Isochronous Transfer Descriptor */
+#define EHCI_DESCRIPTOR_ITD 0
+/** Queue Head */
+#define EHCI_DESCRIPTOR_QH 1
+/** Split Transaction Isochronous Transfer Descriptor */
+#define EHCI_DESCRIPTOR_SITD 2
+/** Frame Span Traversal Node */
+#define EHCI_DESCRIPTOR_FSTN 3
+/** @} */
+
+/** @@name EHCI Transfer service type
+ * @{ */
+typedef enum
+{
+ EHCI_SERVICE_PERIODIC = 0,
+ EHCI_SERVICE_ASYNC = 1
+} EHCI_SERVICE_TYPE;
+/** @} */
+
+/** @@name EHCI Frame List Element Pointer
+ * @{ */
+#define EHCI_FRAME_LIST_NEXTPTR_SHIFT 5
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t FrameAddr : 27;
+} EHCI_FRAME_LIST_PTR;
+AssertCompileSize(EHCI_FRAME_LIST_PTR, 4);
+/** @} */
+
+/** @@name EHCI Isochronous Transfer Descriptor (iTD)
+ * @{ */
+#define EHCI_TD_PTR_SHIFT 5
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t Pointer : 27;
+} EHCI_TD_PTR;
+AssertCompileSize(EHCI_TD_PTR, 4);
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t PG : 3;
+ uint32_t IOC : 1;
+ uint32_t Length : 12;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Active : 1;
+} EHCI_ITD_TRANSACTION;
+AssertCompileSize(EHCI_ITD_TRANSACTION, 4);
+
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t Reserved1 : 1;
+ uint32_t EndPt : 4;
+ uint32_t Ignore1 : 20;
+ uint32_t MaxPacket : 11;
+ uint32_t DirectionIn : 1;
+ uint32_t Ignore2 : 20;
+ uint32_t Multi : 2;
+ uint32_t Reserved10 : 10;
+ uint32_t Ignore3 : 20;
+} EHCI_ITD_MISC;
+AssertCompileSize(EHCI_ITD_MISC, 12);
+
+#define EHCI_BUFFER_PTR_SHIFT 12
+
+typedef struct
+{
+ uint32_t Reserved : 12;
+ uint32_t Pointer : 20; /* 4k aligned */
+} EHCI_BUFFER_PTR;
+AssertCompileSize(EHCI_BUFFER_PTR, 4);
+
+#define EHCI_NUM_ITD_TRANSACTIONS 8
+#define EHCI_NUM_ITD_PAGES 7
+
+typedef struct
+{
+ EHCI_TD_PTR Next;
+ EHCI_ITD_TRANSACTION Transaction[EHCI_NUM_ITD_TRANSACTIONS];
+ union
+ {
+ EHCI_ITD_MISC Misc;
+ EHCI_BUFFER_PTR Buffer[EHCI_NUM_ITD_PAGES];
+ } Buffer;
+} EHCI_ITD, *PEHCI_ITD;
+typedef const EHCI_ITD *PEHCI_CITD;
+AssertCompileSize(EHCI_ITD, 0x40);
+/** @} */
+
+/* ITD with extra padding to add 8th 'Buffer' entry. The PG member of
+ * EHCI_ITD_TRANSACTION can contain values in the 0-7 range, but only values
+ * 0-6 are valid. The extra padding is added to avoid cluttering the code
+ * with range checks; ehciR3ReadItd() initializes the pad with a safe value.
+ * The EHCI 1.0 specification explicitly says using PG value of 7 yields
+ * undefined behavior.
+ */
+typedef struct
+{
+ EHCI_ITD itd;
+ EHCI_BUFFER_PTR pad;
+} EHCI_ITD_PAD, *PEHCI_ITD_PAD;
+AssertCompileSize(EHCI_ITD_PAD, 0x44);
+
+/** @name Split Transaction Isochronous Transfer Descriptor (siTD)
+ * @{ */
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t Reserved : 1;
+ uint32_t EndPt : 4;
+ uint32_t Reserved2 : 4;
+ uint32_t HubAddress : 7;
+ uint32_t Reserved3 : 1;
+ uint32_t Port : 7;
+ uint32_t DirectionIn : 1;
+} EHCI_SITD_ADDR;
+AssertCompileSize(EHCI_SITD_ADDR, 4);
+
+typedef struct
+{
+ uint32_t SMask : 8;
+ uint32_t CMask : 8;
+ uint32_t Reserved : 16;
+} EHCI_SITD_SCHEDCTRL;
+AssertCompileSize(EHCI_SITD_SCHEDCTRL, 4);
+
+typedef struct
+{
+ /* 8 Status flags */
+ uint32_t Reserved : 1;
+ uint32_t SplitXState : 1;
+ uint32_t MisseduFrame : 1;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Error : 1;
+ uint32_t Active : 1;
+ uint32_t CPMask : 8;
+ uint32_t Length : 10;
+ uint32_t Reserved4 : 4;
+ uint32_t PageSelect : 1;
+ uint32_t IOC : 1;
+} EHCI_SITD_TRANSFER;
+AssertCompileSize(EHCI_SITD_TRANSFER, 4);
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_SITD_BUFFER0;
+AssertCompileSize(EHCI_SITD_BUFFER0, 4);
+
+typedef struct
+{
+ uint32_t TCount : 3;
+ uint32_t TPosition : 2;
+ uint32_t Reserved : 7;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_SITD_BUFFER1;
+AssertCompileSize(EHCI_SITD_BUFFER1, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_SITD_BACKPTR;
+AssertCompileSize(EHCI_SITD_BACKPTR, 4);
+
+typedef struct
+{
+ EHCI_TD_PTR NextSITD;
+ EHCI_SITD_ADDR Address;
+ EHCI_SITD_SCHEDCTRL ScheduleCtrl;
+ EHCI_SITD_TRANSFER Transfer;
+ EHCI_SITD_BUFFER0 Buffer0;
+ EHCI_SITD_BUFFER1 Buffer1;
+ EHCI_SITD_BACKPTR BackPtr;
+} EHCI_SITD, *PEHCI_SITD;
+typedef const EHCI_SITD *PEHCI_CSITD;
+AssertCompileSize(EHCI_SITD, 0x1C);
+/** @} */
+
+
+/** @name Queue Element Transfer Descriptor (qTD)
+ * @{ */
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_QTD_NEXTPTR;
+AssertCompileSize(EHCI_QTD_NEXTPTR, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 27;
+} EHCI_QTD_ALTNEXTPTR;
+AssertCompileSize(EHCI_QTD_ALTNEXTPTR, 4);
+
+#define EHCI_QTD_PID_OUT 0
+#define EHCI_QTD_PID_IN 1
+#define EHCI_QTD_PID_SETUP 2
+
+typedef struct
+{
+ /* 8 Status flags */
+ uint32_t PingState : 1;
+ uint32_t SplitXState : 1;
+ uint32_t MisseduFrame : 1;
+ uint32_t TransactError : 1;
+ uint32_t Babble : 1;
+ uint32_t DataBufError : 1;
+ uint32_t Halted : 1;
+ uint32_t Active : 1;
+ uint32_t PID : 2;
+ uint32_t ErrorCount : 2;
+ uint32_t CurrentPage : 3;
+ uint32_t IOC : 1;
+ uint32_t Length : 15;
+ uint32_t DataToggle : 1;
+} EHCI_QTD_TOKEN;
+AssertCompileSize(EHCI_QTD_TOKEN, 4);
+
+#define EHCI_QTD_HAS_ERROR(pQtdToken) (*((uint32_t *)pQtdToken) & 0x7F)
+
+typedef struct
+{
+ uint32_t Offset : 12;
+ uint32_t Reserved : 20;
+ uint32_t Ignore[4];
+} EHCI_QTD_OFFSET;
+AssertCompileSize(EHCI_QTD_OFFSET, 20);
+
+typedef struct
+{
+ EHCI_QTD_NEXTPTR Next;
+ EHCI_QTD_ALTNEXTPTR AltNext;
+ union
+ {
+ EHCI_QTD_TOKEN Bits;
+ uint32_t u32;
+ } Token;
+ union
+ {
+ EHCI_QTD_OFFSET Offset;
+ EHCI_BUFFER_PTR Buffer[5];
+ } Buffer;
+} EHCI_QTD, *PEHCI_QTD;
+typedef const EHCI_QTD *PEHCI_CQTD;
+AssertCompileSize(EHCI_QTD, 0x20);
+/** @} */
+
+
+/** @name Queue Head Descriptor (QHD)
+ * @{ */
+
+#define EHCI_QHD_EPT_SPEED_FULL 0 /**< 12 Mbps */
+#define EHCI_QHD_EPT_SPEED_LOW 1 /**< 1.5 Mbps */
+#define EHCI_QHD_EPT_SPEED_HIGH 2 /**< 480 Mbps */
+#define EHCI_QHD_EPT_SPEED_RESERVED 3
+
+typedef struct
+{
+ uint32_t DeviceAddress : 7;
+ uint32_t InActiveNext : 1;
+ uint32_t EndPt : 4;
+ uint32_t EndPtSpeed : 2;
+ uint32_t DataToggle : 1;
+ uint32_t HeadReclamation : 1;
+ uint32_t MaxLength : 11;
+ uint32_t ControlEPFlag : 1;
+ uint32_t NakCountReload : 4;
+} EHCI_QHD_EPCHARS;
+AssertCompileSize(EHCI_QHD_EPCHARS, 4);
+
+typedef struct
+{
+ uint32_t SMask : 8;
+ uint32_t CMask : 8;
+ uint32_t HubAddress : 7;
+ uint32_t Port : 7;
+ uint32_t Mult : 2;
+} EHCI_QHD_EPCAPS;
+AssertCompileSize(EHCI_QHD_EPCAPS, 4);
+
+typedef struct
+{
+ uint32_t Reserved : 5;
+ uint32_t Pointer : 27;
+} EHCI_QHD_CURRPTR;
+AssertCompileSize(EHCI_QHD_CURRPTR, 4);
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t NakCnt : 4;
+ uint32_t Pointer : 27;
+} EHCI_QHD_ALTNEXT;
+AssertCompileSize(EHCI_QHD_ALTNEXT, 4);
+
+typedef struct
+{
+ uint32_t CProgMask : 8;
+ uint32_t Reserved : 4;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_QHD_BUFFER1;
+AssertCompileSize(EHCI_QHD_BUFFER1, 4);
+
+typedef struct
+{
+ uint32_t FrameTag : 5;
+ uint32_t SBytes : 7;
+ uint32_t Pointer : 20; /**< 4k aligned */
+} EHCI_QHD_BUFFER2;
+AssertCompileSize(EHCI_QHD_BUFFER2, 4);
+
+typedef struct
+{
+ EHCI_TD_PTR Next;
+ EHCI_QHD_EPCHARS Characteristics;
+ EHCI_QHD_EPCAPS Caps;
+ EHCI_QHD_CURRPTR CurrQTD;
+ union
+ {
+ EHCI_QTD OrgQTD;
+ struct
+ {
+ uint32_t Identical1[2];
+ EHCI_QHD_ALTNEXT AltNextQTD;
+ uint32_t Identical2;
+ EHCI_QHD_BUFFER1 Buffer1;
+ EHCI_QHD_BUFFER2 Buffer2;
+ uint32_t Identical3[2];
+ } Status;
+ } Overlay;
+} EHCI_QHD, *PEHCI_QHD;
+typedef const EHCI_QHD *PEHCI_CQHD;
+AssertCompileSize(EHCI_QHD, 0x30);
+/** @} */
+
+/** @name Periodic Frame Span Traversal Node (FSTN)
+ * @{ */
+
+typedef struct
+{
+ uint32_t Terminate : 1;
+ uint32_t Type : 2;
+ uint32_t Reserved : 2;
+ uint32_t Ptr : 27;
+} EHCI_FSTN_PTR;
+AssertCompileSize(EHCI_FSTN_PTR, 4);
+
+typedef struct
+{
+ EHCI_FSTN_PTR NormalPtr;
+ EHCI_FSTN_PTR BackPtr;
+} EHCI_FSTN, *PEHCI_FSTN;
+typedef const EHCI_FSTN *PEHCI_CFSTN;
+AssertCompileSize(EHCI_FSTN, 8);
+
+/** @} */
+
+
+/**
+ * EHCI register operator.
+ */
+typedef struct EHCIOPREG
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PEHCI ehci, uint32_t iReg, uint32_t u32Value);
+} EHCIOPREG;
+
+
+/* EHCI Local stuff */
+#define EHCI_HCS_PARAMS_PORT_ROUTING_RULES RT_BIT(7)
+#define EHCI_HCS_PARAMS_PORT_POWER_CONTROL RT_BIT(4)
+#define EHCI_HCS_PARAMS_NDP_MASK (RT_BIT(0) | RT_BIT(1) | RT_BIT(2) | RT_BIT(3))
+
+/* controller may cache an isochronous data structure for an entire frame */
+#define EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING RT_BIT(7)
+#define EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING RT_BIT(2)
+#define EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST RT_BIT(1)
+#define EHCI_HCC_PARAMS_64BITS_ADDRESSING RT_BIT(0)
+
+/** @name Interrupt Enable Register bits (USBINTR)
+ * @{ */
+#define EHCI_INTR_ENABLE_THRESHOLD RT_BIT(0)
+#define EHCI_INTR_ENABLE_ERROR RT_BIT(1)
+#define EHCI_INTR_ENABLE_PORT_CHANGE RT_BIT(2)
+#define EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER RT_BIT(3)
+#define EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR RT_BIT(4)
+#define EHCI_INTR_ENABLE_ASYNC_ADVANCE RT_BIT(5)
+#define EHCI_INTR_ENABLE_MASK (EHCI_INTR_ENABLE_ASYNC_ADVANCE|EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR|EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER|EHCI_INTR_ENABLE_PORT_CHANGE|EHCI_INTR_ENABLE_ERROR|EHCI_INTR_ENABLE_THRESHOLD)
+/** @} */
+
+/** @name Configure Flag Register (CONFIGFLAG)
+ * @{ */
+#define EHCI_CONFIGFLAG_ROUTING RT_BIT(0)
+#define EHCI_CONFIGFLAG_MASK EHCI_CONFIGFLAG_ROUTING
+/** @} */
+
+/** @name Status Register (USBSTS)
+ * @{ */
+#define EHCI_STATUS_ASYNC_SCHED RT_BIT(15) /* RO */
+#define EHCI_STATUS_PERIOD_SCHED RT_BIT(14) /* RO */
+#define EHCI_STATUS_RECLAMATION RT_BIT(13) /* RO */
+#define EHCI_STATUS_HCHALTED RT_BIT(12) /* RO */
+#define EHCI_STATUS_INT_ON_ASYNC_ADV RT_BIT(5)
+#define EHCI_STATUS_HOST_SYSTEM_ERROR RT_BIT(4)
+#define EHCI_STATUS_FRAME_LIST_ROLLOVER RT_BIT(3)
+#define EHCI_STATUS_PORT_CHANGE_DETECT RT_BIT(2)
+#define EHCI_STATUS_ERROR_INT RT_BIT(1)
+#define EHCI_STATUS_THRESHOLD_INT RT_BIT(0)
+#define EHCI_STATUS_INTERRUPT_MASK (EHCI_STATUS_THRESHOLD_INT|EHCI_STATUS_ERROR_INT|EHCI_STATUS_PORT_CHANGE_DETECT|EHCI_STATUS_FRAME_LIST_ROLLOVER|EHCI_STATUS_HOST_SYSTEM_ERROR|EHCI_STATUS_INT_ON_ASYNC_ADV)
+/** @} */
+
+#define EHCI_PERIODIC_LIST_MASK UINT32_C(0xFFFFF000) /**< 4kb aligned */
+#define EHCI_ASYNC_LIST_MASK UINT32_C(0xFFFFFFE0) /**< 32-byte aligned */
+
+
+/** @name Port Status and Control Register bits (PORTSC)
+ * @{ */
+#define EHCI_PORT_CURRENT_CONNECT RT_BIT(0) /**< RO */
+#define EHCI_PORT_CONNECT_CHANGE RT_BIT(1)
+#define EHCI_PORT_PORT_ENABLED RT_BIT(2)
+#define EHCI_PORT_PORT_CHANGE RT_BIT(3)
+#define EHCI_PORT_OVER_CURRENT_ACTIVE RT_BIT(4) /**< RO */
+#define EHCI_PORT_OVER_CURRENT_CHANGE RT_BIT(5)
+#define EHCI_PORT_FORCE_PORT_RESUME RT_BIT(6)
+#define EHCI_PORT_SUSPEND RT_BIT(7)
+#define EHCI_PORT_RESET RT_BIT(8)
+#define EHCI_PORT_LINE_STATUS_MASK (RT_BIT(10) | RT_BIT(11)) /**< RO */
+#define EHCI_PORT_LINE_STATUS_SHIFT 10
+#define EHCI_PORT_POWER RT_BIT(12)
+#define EHCI_PORT_OWNER RT_BIT(13)
+#define EHCI_PORT_INDICATOR (RT_BIT(14) | RT_BIT(15))
+#define EHCI_PORT_TEST_CONTROL_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19))
+#define EHCI_PORT_TEST_CONTROL_SHIFT 16
+#define EHCI_PORT_WAKE_ON_CONNECT_ENABLE RT_BIT(20)
+#define EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE RT_BIT(21)
+#define EHCI_PORT_WAKE_OVER_CURRENT_ENABLE RT_BIT(22)
+#define EHCI_PORT_RESERVED (RT_BIT(9)|RT_BIT(23)|RT_BIT(24)|RT_BIT(25)|RT_BIT(26)|RT_BIT(27)|RT_BIT(28)|RT_BIT(29)|RT_BIT(30)|RT_BIT(31))
+
+#define EHCI_PORT_WAKE_MASK (EHCI_PORT_WAKE_ON_CONNECT_ENABLE|EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE|EHCI_PORT_WAKE_OVER_CURRENT_ENABLE)
+#define EHCI_PORT_CHANGE_MASK (EHCI_PORT_CONNECT_CHANGE|EHCI_PORT_PORT_CHANGE|EHCI_PORT_OVER_CURRENT_CHANGE)
+/** @} */
+
+/** @name Command Register bits (USBCMD)
+ * @{ */
+#define EHCI_CMD_RUN RT_BIT(0)
+#define EHCI_CMD_RESET RT_BIT(1)
+#define EHCI_CMD_FRAME_LIST_SIZE_MASK (RT_BIT(2) | RT_BIT(3))
+#define EHCI_CMD_FRAME_LIST_SIZE_SHIFT 2
+#define EHCI_CMD_PERIODIC_SCHED_ENABLE RT_BIT(4)
+#define EHCI_CMD_ASYNC_SCHED_ENABLE RT_BIT(5)
+#define EHCI_CMD_INT_ON_ADVANCE_DOORBELL RT_BIT(6)
+#define EHCI_CMD_SOFT_RESET RT_BIT(7) /**< optional */
+#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK (RT_BIT(8) | RT_BIT(9)) /**< optional */
+#define EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT 8
+#define EHCI_CMD_RESERVED RT_BIT(10)
+#define EHCI_CMD_ASYNC_SCHED_PARK_ENABLE RT_BIT(11) /**< optional */
+#define EHCI_CMD_RESERVED2 (RT_BIT(12) | RT_BIT(13) | RT_BIT(14) | RT_BIT(15))
+#define EHCI_CMD_INTERRUPT_THRESHOLD_MASK (RT_BIT(16) | RT_BIT(17) | RT_BIT(18) | RT_BIT(19) | RT_BIT(20) | RT_BIT(21) | RT_BIT(22) | RT_BIT(23))
+#define EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT 16
+#define EHCI_CMD_MASK (EHCI_CMD_INTERRUPT_THRESHOLD_MASK|EHCI_CMD_ASYNC_SCHED_PARK_ENABLE|EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK|EHCI_CMD_SOFT_RESET|EHCI_CMD_INT_ON_ADVANCE_DOORBELL|EHCI_CMD_ASYNC_SCHED_ENABLE|EHCI_CMD_PERIODIC_SCHED_ENABLE|EHCI_CMD_FRAME_LIST_SIZE_MASK|EHCI_CMD_RESET|EHCI_CMD_RUN)
+
+#define EHCI_DEFAULT_PERIODIC_LIST_SIZE 1024
+#define EHCI_DEFAULT_PERIODIC_LIST_MASK 0x3ff
+
+#define EHCI_FRINDEX_UFRAME_COUNT_MASK 0x7
+#define EHCI_FRINDEX_FRAME_INDEX_MASK EHCI_DEFAULT_PERIODIC_LIST_MASK
+#define EHCI_FRINDEX_FRAME_INDEX_SHIFT 3
+
+/** @} */
+
+/* Local EHCI definitions */
+#define EHCI_USB_RESET 0x00
+#define EHCI_USB_RESUME 0x40
+#define EHCI_USB_OPERATIONAL 0x80
+#define EHCI_USB_SUSPEND 0xc0
+
+#define EHCI_HARDWARE_TIMER_FREQ 8000 /**< 8000 hz = every 125 usec */
+#define EHCI_DEFAULT_TIMER_FREQ 1000
+#define EHCI_UFRAMES_PER_FRAME 8
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(VBOX_IN_EXTPACK_R0) && defined(RT_OS_SOLARIS)
+/* Dependency information for the native solaris loader. */
+extern "C" { char _depends_on[] = "vboxdrv VMMR0.r0"; }
+#endif
+
+#if defined(LOG_ENABLED) && defined(IN_RING3)
+static bool g_fLogControlEPs = false;
+static bool g_fLogInterruptEPs = false;
+#endif
+
+#ifdef IN_RING3
+/**
+ * SSM descriptor table for the EHCI structure.
+ */
+static SSMFIELD const g_aEhciFields[] =
+{
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY( EHCI, fBusStarted),
+ SSMFIELD_ENTRY_TERM()
+};
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+/* Update host controller state to reflect a device attach */
+static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp);
+
+static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd);
+static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate);
+#endif /* IN_RING3 */
+RT_C_DECLS_END
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ehciUpdateInterruptLocked(PPDMDEVINS pDevIns, PEHCI pThis, const char *msg)
+{
+ int level = 0;
+
+ if (pThis->intr_status & pThis->intr)
+ level = 1;
+
+ PDMDevHlpPCISetIrq(pDevIns, 0, level);
+ if (level)
+ {
+ uint32_t val = pThis->intr_status & pThis->intr;
+
+ Log2Func(("Fired off interrupt %#010x - INT=%d ERR=%d PCD=%d FLR=%d HSE=%d IAA=%d - %s\n",
+ val,
+ !!(val & EHCI_STATUS_THRESHOLD_INT),
+ !!(val & EHCI_STATUS_ERROR_INT),
+ !!(val & EHCI_STATUS_PORT_CHANGE_DETECT),
+ !!(val & EHCI_STATUS_FRAME_LIST_ROLLOVER),
+ !!(val & EHCI_STATUS_HOST_SYSTEM_ERROR),
+ !!(val & EHCI_STATUS_INT_ON_ASYNC_ADV),
+ msg));
+ RT_NOREF(val, msg);
+
+ /* host controller must clear the EHCI_CMD_INT_ON_ADVANCE_DOORBELL bit after setting it in the status register */
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ ASMAtomicAndU32(&pThis->cmd, ~EHCI_CMD_INT_ON_ADVANCE_DOORBELL);
+
+ }
+ else
+ Log2Func(("cleared interrupt\n"));
+}
+
+/**
+ * Set an interrupt, use the wrapper ehciSetInterrupt.
+ */
+DECLINLINE(int) ehciSetInterruptInt(PPDMDEVINS pDevIns, PEHCI pThis, int rcBusy, uint32_t intr, const char *msg)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if ( (pThis->intr_status & intr) != intr )
+ {
+ ASMAtomicOrU32(&pThis->intr_status, intr);
+ ehciUpdateInterruptLocked(pDevIns, pThis, msg);
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return rc;
+}
+
+/**
+ * Set an interrupt wrapper macro for logging purposes.
+ */
+#define ehciSetInterrupt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr) \
+ ehciSetInterruptInt(a_pDevIns, a_pEhci, a_rcBusy, a_fIntr, #a_fIntr)
+#define ehciR3SetInterrupt(a_pDevIns, a_pEhci, a_fIntr) \
+ ehciSetInterruptInt(a_pDevIns, a_pEhci, VERR_IGNORED, a_fIntr, #a_fIntr)
+
+#ifdef IN_RING3
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) ehciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThisCC->RootHub.IRhPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->RootHub.ILeds);
+ return NULL;
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ehciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.ILeds);
+ if (iLUN == 0)
+ {
+ *ppLed = &pThisCC->RootHub.Led;
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * Get the number of avialable ports in the hub.
+ *
+ * @returns The number of ports available.
+ * @param pInterface Pointer to this structure.
+ * @param pAvailable Bitmap indicating the available ports. Set bit == available port.
+ */
+static DECLCALLBACK(unsigned) ehciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ memset(pAvailable, 0, sizeof(*pAvailable));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ unsigned cPorts = 0;
+ for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (!pThisCC->RootHub.aPorts[iPort].fAttached)
+ {
+ cPorts++;
+ ASMBitSet(pAvailable, iPort + 1);
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return cPorts;
+}
+
+
+/**
+ * Gets the supported USB versions.
+ *
+ * @returns The mask of supported USB versions.
+ * @param pInterface Pointer to this structure.
+ */
+static DECLCALLBACK(uint32_t) ehciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ return VUSB_STDVER_20;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */
+static DECLCALLBACK(int) ehciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(!pThisCC->RootHub.aPorts[uPort].fAttached);
+ Assert(enmSpeed == VUSB_SPEED_HIGH); RT_NOREF(enmSpeed); /* Only HS devices should end up here! */
+
+ /*
+ * Attach it.
+ */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_OWNER); /* not attached to a companion controller */
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ pThisCC->RootHub.aPorts[uPort].fAttached = true;
+ ehciR3PortPower(pThis, pThisCC, uPort, 1 /* power on */);
+
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device is being detached from a port in the roothub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param uPort The port number assigned to the device.
+ */
+static DECLCALLBACK(void) ehciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= EHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(pThisCC->RootHub.aPorts[uPort].fAttached);
+
+ /*
+ * Detach it.
+ */
+ pThisCC->RootHub.aPorts[uPort].fAttached = false;
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_CURRENT_CONNECT);
+ if (pThis->RootHub.aPorts[uPort].fReg & EHCI_PORT_PORT_ENABLED)
+ {
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[uPort].fReg, ~EHCI_PORT_PORT_ENABLED);
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE | EHCI_PORT_PORT_CHANGE);
+ }
+ else
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[uPort].fReg, EHCI_PORT_CONNECT_CHANGE);
+
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_PORT_CHANGE_DETECT);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+}
+
+
+/**
+ * One of the roothub devices has completed its reset operation.
+ *
+ * Currently, we don't think anything is required to be done here
+ * so it's just a stub for forcing async resetting of the devices
+ * during a root hub reset.
+ *
+ * @param pDev The root hub device.
+ * @param uPort The port number of the device on the roothub being resetted.
+ * @param rc The result of the operation.
+ * @param pvUser Pointer to the controller.
+ */
+static DECLCALLBACK(void) ehciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser)
+{
+ LogRel(("EHCI: root hub reset completed with %Rrc\n", rc));
+ RT_NOREF(pDev, uPort, rc, pvUser);
+}
+
+
+/**
+ * Does a software or hardware reset of the controller.
+ *
+ * This is called in response to setting HcCommandStatus.HCR, hardware reset,
+ * and device construction.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The shared EHCI instance data.
+ * @param pThisCC The ring-3 EHCI instance data.
+ * @param fNewMode The new mode of operation. This is UsbSuspend if it's a
+ * software reset, and UsbReset if it's a hardware reset / cold boot.
+ * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub.
+ * This is really a just a hack for the non-working linux device reset.
+ * Linux has this feature called 'logical disconnect' if device reset fails
+ * which prevents us from doing resets when the guest asks for it - the guest
+ * will get confused when the device seems to be reconnected everytime it tries
+ * to reset it. But if we're at hardware reset time, we can allow a device to
+ * be 'reconnected' without upsetting the guest.
+ *
+ * @remark This hasn't got anything to do with software setting the mode to UsbReset.
+ */
+static void ehciR3DoReset(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux)
+{
+ LogFunc(("%s reset%s\n", fNewMode == EHCI_USB_RESET ? "hardware" : "software",
+ fResetOnLinux ? " (reset on linux)" : ""));
+
+ /*
+ * Cancel all outstanding URBs.
+ *
+ * We can't, and won't, deal with URBs until we're moved out of the
+ * suspend/reset state. Also, a real HC isn't going to send anything
+ * any more when a reset has been signaled.
+ *
+ * This must be done on the framer thread to avoid race conditions.
+ */
+ pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn);
+
+ /*
+ * Reset the hardware registers.
+ */
+ /** @todo other differences between hardware reset and VM reset? */
+
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING)
+ pThis->cmd = 0x80000 | EHCI_CMD_ASYNC_SCHED_PARK_ENABLE | (3 << EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT);
+ else
+ pThis->cmd = 0x80000;
+
+ pThis->intr_status = EHCI_STATUS_HCHALTED;
+ pThis->intr = 0;
+ pThis->frame_idx = 0;
+ pThis->ds_segment = 0;
+ pThis->periodic_list_base = 0; /* undefined */
+ pThis->async_list_base = 0; /* undefined */
+ pThis->config = 0;
+ pThis->uIrqInterval = (pThis->intr_status & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT;
+
+ /* We have to update interrupts as the IRQ may need to be cleared. */
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pThis->CsIrq, rcLock);
+
+ ehciUpdateInterruptLocked(pDevIns, pThis, "ehciR3DoReset");
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+
+ if (fNewMode == EHCI_USB_RESET)
+ {
+ /* Only a hardware reset reinits the port registers */
+ for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++)
+ {
+ if (pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL)
+ pThis->RootHub.aPorts[i].fReg = EHCI_PORT_OWNER;
+ else
+ pThis->RootHub.aPorts[i].fReg = EHCI_PORT_POWER | EHCI_PORT_OWNER;
+ }
+ }
+/** @todo Shouldn't we stop the SOF timer at this point? */
+
+ /*
+ * If this is a hardware reset, we will initialize the root hub too.
+ * Software resets doesn't do this according to the specs.
+ * (It's not possible to have device connected at the time of the
+ * device construction, so nothing to worry about there.)
+ */
+ if (fNewMode == EHCI_USB_RESET)
+ {
+ pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux);
+
+ /*
+ * Reattach the devices.
+ */
+ for (unsigned i = 0; i < EHCI_NDP_CFG(pThis); i++)
+ {
+ bool fAttached = pThisCC->RootHub.aPorts[i].fAttached;
+ pThisCC->RootHub.aPorts[i].fAttached = false;
+
+ if (fAttached)
+ ehciR3RhAttach(&pThisCC->RootHub.IRhPort, i+1, VUSB_SPEED_HIGH);
+ }
+ }
+}
+
+/**
+ * Reset the root hub.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this structure.
+ * @param fResetOnLinux This is used to indicate whether we're at VM reset time and
+ * can do real resets or if we're at any other time where that
+ * isn't such a good idea.
+ * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion!
+ * @thread EMT
+ */
+static DECLCALLBACK(int) ehciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFunc(("fResetOnLinux=%d\n", fResetOnLinux));
+
+ /* Soft reset first */
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */);
+
+ /*
+ * We're pretending to _reattach_ the devices without resetting them.
+ * Except, during VM reset where we use the opportunity to do a proper
+ * reset before the guest comes along and expects things.
+ *
+ * However, it's very very likely that we're not doing the right thing
+ * here when end up here on request from the guest (USB Reset state).
+ * The docs talks about root hub resetting, however what exact behaviour
+ * in terms of root hub status and changed bits, and HC interrupts aren't
+ * stated clearly. IF we get trouble and see the guest doing "USB Resets"
+ * we will have to look into this. For the time being we stick with simple.
+ */
+ for (unsigned iPort = 0; iPort < EHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (pThisCC->RootHub.aPorts[iPort].fAttached)
+ {
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ if (fResetOnLinux)
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux,
+ ehciR3RhResetDoneOneDev, pThis, pVM);
+ }
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reads physical memory.
+ */
+DECLINLINE(void) ehciPhysRead(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Reads physical memory - metadata.
+ */
+DECLINLINE(void) ehciPhysReadMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ehciPhysWrite(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ehciPhysWriteMeta(PPDMDEVINS pDevIns, RTGCPHYS Addr, const void *pvBuf, size_t cbBuf)
+{
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+
+/**
+ * Read an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ehciGetDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, uint32_t *pau32s, int c32s)
+{
+ ehciPhysReadMeta(pDevIns, Addr, pau32s, c32s * sizeof(uint32_t));
+# ifndef RT_LITTLE_ENDIAN
+ for(int i = 0; i < c32s; i++)
+ pau32s[i] = RT_H2LE_U32(pau32s[i]);
+# endif
+}
+
+
+/**
+ * Write an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ehciPutDWords(PPDMDEVINS pDevIns, RTGCPHYS Addr, const uint32_t *pau32s, int cu32s)
+{
+# ifdef RT_LITTLE_ENDIAN
+ ehciPhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2);
+# else
+ for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s))
+ {
+ uint32_t u32Tmp = RT_H2LE_U32(*pau32s);
+ ehciPhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp));
+ }
+# endif
+}
+
+
+DECLINLINE(void) ehciR3ReadFrameListPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_FRAME_LIST_PTR *pFrameList)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pFrameList, sizeof(*pFrameList) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadTDPtr(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, EHCI_TD_PTR *pTD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pTD, sizeof(*pTD) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD_PAD pPItd)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pPItd, sizeof(EHCI_ITD) >> 2);
+ pPItd->pad.Pointer = 0xFFFFF; /* Direct accesses at the last page under 4GB (ROM). */
+}
+
+DECLINLINE(void) ehciR3ReadSitd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_SITD pSitd)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pSitd, sizeof(*pSitd) >> 2);
+}
+
+DECLINLINE(void) ehciR3WriteItd(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_ITD pItd)
+{
+ /** @todo might need to be careful about write order in async io thread */
+ /*
+ * Only write to the fields the controller is allowed to write to,
+ * namely the eight double words coming after the next link pointer.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_ITD, Transaction[0]);
+ uint32_t offDWordsWrite = offWrite / sizeof(uint32_t);
+ Assert(!(offWrite % sizeof(uint32_t)));
+
+ ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pItd + offDWordsWrite, (sizeof(*pItd) >> 2) - offDWordsWrite);
+}
+
+DECLINLINE(void) ehciR3ReadQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQHD, sizeof(*pQHD) >> 2);
+}
+
+DECLINLINE(void) ehciR3ReadQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD)
+{
+ ehciGetDWords(pDevIns, GCPhys, (uint32_t *)pQTD, sizeof(*pQTD) >> 2);
+}
+
+DECLINLINE(void) ehciR3WriteQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QTD pQTD)
+{
+ /** @todo might need to be careful about write order in async io thread */
+ /*
+ * Only write to the fields the controller is allowed to write to,
+ * namely the two double words coming after the alternate next QTD pointer.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_QTD, Token.u32);
+ uint32_t offDWordsWrite = offWrite / sizeof(uint32_t);
+ Assert(!(offWrite % sizeof(uint32_t)));
+
+ ehciPutDWords(pDevIns, GCPhys + offWrite, (uint32_t *)pQTD + offDWordsWrite, (sizeof(*pQTD) >> 2) - offDWordsWrite);
+}
+
+
+/**
+ * Updates the QHD in guest memory only updating portions of the QHD the controller
+ * is allowed to write to.
+ *
+ * @returns nothing.
+ * @param pDevIns The device instance.
+ * @param GCPhys Physical guest address of the QHD.
+ * @param pQHD The QHD to update the guest memory with.
+ */
+DECLINLINE(void) ehciR3UpdateQHD(PPDMDEVINS pDevIns, RTGCPHYS GCPhys, PEHCI_QHD pQHD)
+{
+ /*
+ * Only update members starting from the current QTD pointer, everything
+ * before is readonly for the controller and the guest might have updated it
+ * behind our backs already.
+ */
+ uint32_t offWrite = RT_OFFSETOF(EHCI_QHD, CurrQTD);
+ ehciPhysWriteMeta(pDevIns, GCPhys + offWrite, (uint8_t *)pQHD + offWrite, sizeof(EHCI_QHD) - offWrite);
+}
+
+#ifdef LOG_ENABLED
+
+# if 0 /* unused */
+/**
+ * Dumps a TD queue. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpTdQueue(PEHCI pThis, RTGCPHYS GCPhysHead, const char *pszMsg)
+{
+ RT_NOREF(pThis, GCPhysHead, pszMsg);
+ AssertFailed();
+}
+# endif /* unused */
+
+/**
+ * Dumps an SITD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpSITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RT_NOREF(pDevIns, GCPhysHead, fList);
+ AssertFailed();
+}
+
+/**
+ * Dumps an FSTN list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpFSTN(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RT_NOREF(pDevIns, GCPhysHead, fList);
+ AssertFailed();
+}
+
+#ifdef LOG_ENABLED
+static const char *ehciPID2Str(uint32_t PID)
+{
+ switch(PID)
+ {
+ case EHCI_QTD_PID_OUT:
+ return "OUT";
+ case EHCI_QTD_PID_IN:
+ return "IN";
+ case EHCI_QTD_PID_SETUP:
+ return "SETUP";
+ default:
+ return "Invalid PID!";
+ }
+}
+#endif
+
+DECLINLINE(void) ehciR3DumpSingleQTD(RTGCPHYS GCPhys, PEHCI_QTD pQtd, const char *pszPrefix)
+{
+ if (pQtd->Token.Bits.Active)
+ {
+ Log2((" QTD%s: %RGp={", pszPrefix, GCPhys));
+ Log2((" Length=%x IOC=%d DT=%d CErr=%d C_Page=%d Status=%x PID=%s}\n", pQtd->Token.Bits.Length, pQtd->Token.Bits.IOC, pQtd->Token.Bits.DataToggle, pQtd->Token.Bits.ErrorCount, pQtd->Token.Bits.CurrentPage, pQtd->Token.u32 & 0xff, ehciPID2Str(pQtd->Token.Bits.PID)));
+ Log2((" QTD: %RGp={", GCPhys));
+ Log2((" Buf0=%x Offset=%x Buf1=%x Buf2=%x Buf3=%x Buf4=%x}\n", pQtd->Buffer.Buffer[0].Pointer, pQtd->Buffer.Offset.Offset, pQtd->Buffer.Buffer[1].Pointer, pQtd->Buffer.Buffer[2].Pointer, pQtd->Buffer.Buffer[3].Pointer, pQtd->Buffer.Buffer[4].Pointer));
+ Log2((" QTD: %RGp={", GCPhys));
+ Log2((" Next=%RGp T=%d AltNext=%RGp AltT=%d\n", (RTGCPHYS)pQtd->Next.Pointer << EHCI_TD_PTR_SHIFT, pQtd->Next.Terminate, (RTGCPHYS)pQtd->AltNext.Pointer << EHCI_TD_PTR_SHIFT, pQtd->AltNext.Terminate));
+ }
+ else
+ Log2((" QTD%s: %RGp={Not Active}\n", pszPrefix, GCPhys));
+}
+
+/**
+ * Dumps a QTD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpQTD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ for (;;)
+ {
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD */
+ ehciR3ReadQTD(pDevIns, GCPhys, &qtd);
+ ehciR3DumpSingleQTD(GCPhys, &qtd, "");
+
+ if (!fList || qtd.Next.Terminate || !qtd.Next.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)qtd.Next.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* detect if list item is self-cycled. */
+
+ GCPhys = qtd.Next.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (GCPhys == GCPhysHead)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+
+ /* alternative pointers */
+ GCPhys = GCPhysHead;
+ iterations = 0;
+
+ for (;;)
+ {
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD */
+ ehciR3ReadQTD(pDevIns, GCPhys, &qtd);
+ if (GCPhys != GCPhysHead)
+ ehciR3DumpSingleQTD(GCPhys, &qtd, "-A");
+
+ if (!fList || qtd.AltNext.Terminate || !qtd.AltNext.Pointer || qtd.Token.Bits.Halted || !qtd.Token.Bits.Active)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* detect if list item is self-cycled. */
+
+ GCPhys = qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (GCPhys == GCPhysHead)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+}
+
+/**
+ * Dumps a QHD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpQH(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ EHCI_QHD qhd;
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ Log2((" QH: %RGp={", GCPhys));
+
+ /* Read the whole QHD */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+
+ Log2(("HorzLnk=%RGp Typ=%u T=%u Addr=%x EndPt=%x Speed=%x MaxSize=%x NAK=%d C=%d RH=%d I=%d}\n",
+ ((RTGCPHYS)qhd.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Next.Type, qhd.Next.Terminate,
+ qhd.Characteristics.DeviceAddress, qhd.Characteristics.EndPt, qhd.Characteristics.EndPtSpeed,
+ qhd.Characteristics.MaxLength, qhd.Characteristics.NakCountReload, qhd.Characteristics.ControlEPFlag,
+ qhd.Characteristics.HeadReclamation, qhd.Characteristics.InActiveNext));
+ Log2((" Caps: Port=%x Hub=%x Multi=%x CMask=%x SMask=%x\n", qhd.Caps.Port, qhd.Caps.HubAddress,
+ qhd.Caps.Mult, qhd.Caps.CMask, qhd.Caps.SMask));
+ Log2((" CurrPtr=%RGp Next=%RGp T=%d AltNext=%RGp T=%d\n",
+ ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT),
+ ((RTGCPHYS)qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.Next.Terminate,
+ ((RTGCPHYS)qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT), qhd.Overlay.OrgQTD.AltNext.Terminate));
+ ehciR3DumpSingleQTD(qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qhd.Overlay.OrgQTD, "");
+ ehciR3DumpQTD(pDevIns, qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT, true);
+
+ Assert(qhd.Next.Pointer || qhd.Next.Terminate);
+ if ( !fList
+ || qhd.Next.Terminate
+ || !qhd.Next.Pointer)
+ return;
+
+ for (;;)
+ {
+ /* Read the next pointer */
+ EHCI_TD_PTR ptr;
+ ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr);
+
+ AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type));
+ Assert(ptr.Pointer || ptr.Terminate);
+ if ( ptr.Terminate
+ || !ptr.Pointer
+ || ptr.Type != EHCI_DESCRIPTOR_QH)
+ break;
+
+ /* next */
+ if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT))
+ break; /* Looping on itself. Bad guest! */
+
+ GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT;
+ if (GCPhys == GCPhysHead)
+ break; /* break the loop */
+
+ ehciR3DumpQH(pDevIns, GCPhys, false);
+
+ /* And again, if we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 64)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+ }
+}
+
+/**
+ * Dumps an ITD list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpITD(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, bool fList)
+{
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ for (;;)
+ {
+ Log2((" ITD: %RGp={", GCPhys));
+
+ /* Read the whole ITD */
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+ ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd);
+
+ Log2(("Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi));
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ Log2(("T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC,
+ pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer << EHCI_BUFFER_PTR_SHIFT));
+ }
+ }
+ Assert(pItd->Next.Pointer || pItd->Next.Terminate);
+ if (!fList || pItd->Next.Terminate || !pItd->Next.Pointer)
+ break;
+
+ /* And again, if we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+
+ /* next */
+ GCPhys = pItd->Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+}
+
+/**
+ * Dumps a periodic list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ehciR3DumpPeriodicList(PPDMDEVINS pDevIns, RTGCPHYS GCPhysHead, const char *pszMsg, bool fTDs)
+{
+ RT_NOREF(fTDs);
+ RTGCPHYS GCPhys = GCPhysHead;
+ unsigned iterations = 0;
+
+ if (pszMsg)
+ Log2(("%s:", pszMsg));
+
+ for (;;)
+ {
+ EHCI_FRAME_LIST_PTR FramePtr;
+
+ /* ED */
+ Log2((" %RGp={", GCPhys));
+ if (!GCPhys)
+ {
+ Log2(("END}\n"));
+ return;
+ }
+
+ /* Frame list pointer */
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+ if (FramePtr.Terminate)
+ {
+ Log2(("[Terminate]}\n"));
+ }
+ else
+ {
+ RTGCPHYS GCPhys1 = (RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT;
+ switch (FramePtr.Type)
+ {
+ case EHCI_DESCRIPTOR_ITD:
+ Log2(("[ITD]}\n"));
+ ehciR3DumpITD(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_SITD:
+ Log2(("[SITD]}\n"));
+ ehciR3DumpSITD(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_QH:
+ Log2(("[QH]}\n"));
+ ehciR3DumpQH(pDevIns, GCPhys1, false);
+ break;
+ case EHCI_DESCRIPTOR_FSTN:
+ Log2(("[FSTN]}\n"));
+ ehciR3DumpFSTN(pDevIns, GCPhys1, false);
+ break;
+ }
+ }
+
+ /* Same old. If we ran too many iterations, the list must be looping on itself.
+ * Just quit.
+ */
+ if (++iterations == 128)
+ {
+ LogFunc(("Too many iterations, exiting!\n"));
+ break;
+ }
+
+ /* next */
+ GCPhys = GCPhys + sizeof(FramePtr);
+ }
+}
+
+#endif /* LOG_ENABLED */
+
+
+DECLINLINE(int) ehciR3InFlightFindFree(PEHCICC pThisCC, const int iStart)
+{
+ unsigned i = iStart;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight)) {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ i++;
+ }
+ i = iStart;
+ while (i-- > 0) {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+ * Record an in-flight TD.
+ *
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @param pUrb The URB.
+ */
+static void ehciR3InFlightAdd(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD, PVUSBURB pUrb)
+{
+ int i = ehciR3InFlightFindFree(pThisCC, (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight));
+ if (i >= 0)
+ {
+#ifdef LOG_ENABLED
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+#endif
+ pThisCC->aInFlight[i].GCPhysTD = GCPhysTD;
+ pThisCC->aInFlight[i].pUrb = pUrb;
+ pThisCC->cInFlight++;
+ return;
+ }
+ AssertMsgFailed(("Out of space cInFlight=%d!\n", pThisCC->cInFlight));
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Record in-flight TDs for an URB.
+ *
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static void ehciR3InFlightAddUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ ehciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb);
+}
+
+
+/**
+ * Finds a in-flight TD.
+ *
+ * @returns Index of the record.
+ * @returns -1 if not found.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @remark This has to be fast.
+ */
+static int ehciR3InFlightFind(PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ unsigned cLeft = pThisCC->cInFlight;
+ unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight);
+ const int iLast = i;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight)) {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ i++;
+ }
+ i = iLast;
+ while (i-- > 0) {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ }
+ return -1;
+}
+
+
+/**
+ * Checks if a TD is in-flight.
+ *
+ * @returns true if in flight, false if not.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ehciR3IsTdInFlight(PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ return ehciR3InFlightFind(pThisCC, GCPhysTD) >= 0;
+}
+
+
+/**
+ * Removes a in-flight TD.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ehciR3InFlightRemove(PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysTD)
+{
+ int i = ehciR3InFlightFind(pThisCC, GCPhysTD);
+ if (i >= 0)
+ {
+#ifdef LOG_ENABLED
+ const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo;
+#else
+ const int cFramesInFlight = 0;
+#endif
+ Log2Func(("reaping TD=%RGp %d frames (%#010x-%#010x)\n",
+ GCPhysTD, cFramesInFlight, pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber));
+ pThisCC->aInFlight[i].GCPhysTD = 0;
+ pThisCC->aInFlight[i].pUrb = NULL;
+ pThisCC->cInFlight--;
+ return cFramesInFlight;
+ }
+ AssertMsgFailed(("TD %RGp is not in flight\n", GCPhysTD));
+ RT_NOREF(pThis);
+ return -1;
+}
+
+
+/**
+ * Removes all TDs associated with a URB from the in-flight tracking.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis EHCI instance data, shared edition.
+ * @param pThisCC EHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static int ehciR3InFlightRemoveUrb(PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ int cFramesInFlight = ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr);
+ if (pUrb->pHci->cTds > 1)
+ {
+ for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++)
+ if (ehciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0)
+ cFramesInFlight = -1;
+ }
+ return cFramesInFlight;
+}
+
+
+/**
+ * Checks that the transport descriptors associated with the URB
+ * hasn't been changed in any way indicating that they may have been canceled.
+ *
+ * This rountine also updates the TD copies contained within the URB.
+ *
+ * @returns true if the URB has been canceled, otherwise false.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb The URB in question.
+ * @param pItd The ITD pointer.
+ */
+static bool ehciR3ItdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_ITD pItd)
+{
+ RT_NOREF(pThisCC);
+ Assert(pItd);
+ if (!pUrb)
+ return true;
+
+ PEHCI_ITD pItdCopy = (PEHCI_ITD)pUrb->paTds[0].TdCopy;
+
+ /* Check transactions */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++)
+ {
+ if ( pItd->Transaction[i].Length != pItdCopy->Transaction[i].Length
+ || pItd->Transaction[i].Offset != pItdCopy->Transaction[i].Offset
+ || pItd->Transaction[i].PG != pItdCopy->Transaction[i].PG
+ || pItd->Transaction[i].Active != pItdCopy->Transaction[i].Active)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+ }
+
+ /* Check misc characteristics */
+ if ( pItd->Buffer.Misc.DeviceAddress != pItdCopy->Buffer.Misc.DeviceAddress
+ || pItd->Buffer.Misc.DirectionIn != pItdCopy->Buffer.Misc.DirectionIn
+ || pItd->Buffer.Misc.EndPt != pItdCopy->Buffer.Misc.EndPt)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled (misc): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+
+ /* Check buffer pointers */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Buffer.Buffer); i++)
+ {
+ if (pItd->Buffer.Buffer[i].Pointer != pItdCopy->Buffer.Buffer[i].Pointer)
+ {
+ Log(("%s: ehciR3ItdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pItd), pItd, sizeof(*pItd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledIsocUrbs);
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Checks that the transport descriptors associated with the URB
+ * hasn't been changed in any way indicating that they may have been canceled.
+ *
+ * This rountine also updates the TD copies contained within the URB.
+ *
+ * @returns true if the URB has been canceled, otherwise false.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb The URB in question.
+ * @param pQhd The QHD pointer
+ * @param pQtd The QTD pointer
+ */
+static bool ehciR3QhdHasUrbBeenCanceled(PEHCICC pThisCC, PVUSBURB pUrb, PEHCI_QHD pQhd, PEHCI_QTD pQtd)
+{
+ RT_NOREF(pQhd, pThisCC);
+ Assert(pQhd && pQtd);
+ if ( !pUrb
+ || !ehciR3IsTdInFlight(pThisCC, pUrb->paTds[0].TdAddr))
+ return true;
+
+ PEHCI_QTD pQtdCopy = (PEHCI_QTD)pUrb->paTds[0].TdCopy;
+
+ if ( pQtd->Token.Bits.Length != pQtdCopy->Token.Bits.Length
+ || pQtd->Token.Bits.Active != pQtdCopy->Token.Bits.Active
+ || pQtd->Token.Bits.DataToggle != pQtdCopy->Token.Bits.DataToggle
+ || pQtd->Token.Bits.CurrentPage != pQtdCopy->Token.Bits.CurrentPage
+ || pQtd->Token.Bits.PID != pQtdCopy->Token.Bits.PID
+ || pQtd->Buffer.Offset.Offset != pQtdCopy->Buffer.Offset.Offset)
+ {
+ Log(("%s: ehciQtdHasUrbBeenCanceled: TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs);
+ return true;
+ }
+
+
+ /* Check buffer pointers */
+ for (unsigned i = 0; i < RT_ELEMENTS(pQtd->Buffer.Buffer); i++)
+ {
+ if (pQtd->Buffer.Buffer[i].Pointer != pQtdCopy->Buffer.Buffer[i].Pointer)
+ {
+ Log(("%s: ehciQtdHasUrbBeenCanceled (buf): TdAddr=%RGp canceled! [iso]\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(*pQtd), pQtd, sizeof(*pQtd), &pUrb->paTds[0].TdCopy[0]));
+ STAM_COUNTER_INC(&pThisCC->StatCanceledGenUrbs);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * Set the ITD status bits acorresponding to the VUSB status code.
+ *
+ * @param enmStatus The VUSB status code.
+ * @param pItdStatus ITD status pointer
+ */
+static void ehciR3VUsbStatus2ItdStatus(VUSBSTATUS enmStatus, EHCI_ITD_TRANSACTION *pItdStatus)
+{
+ switch (enmStatus)
+ {
+ case VUSBSTATUS_OK:
+ pItdStatus->TransactError = 0;
+ pItdStatus->DataBufError = 0;
+ break; /* make sure error bits are cleared */
+ case VUSBSTATUS_STALL:
+ case VUSBSTATUS_DNR:
+ case VUSBSTATUS_CRC:
+ pItdStatus->TransactError = 1;
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ case VUSBSTATUS_DATA_OVERRUN:
+ pItdStatus->DataBufError = 1;
+ break;
+ case VUSBSTATUS_NOT_ACCESSED:
+ Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n"));
+ break; /* can't signal this other than setting the length to 0 */
+ default:
+ Log(("pUrb->enmStatus=%#x!!!\n", enmStatus));
+ break;;
+ }
+}
+
+/**
+ * Set the QTD status bits acorresponding to the VUSB status code.
+ *
+ * @param enmStatus The VUSB status code.
+ * @param pQtdStatus QTD status pointer
+ */
+static void ehciR3VUsbStatus2QtdStatus(VUSBSTATUS enmStatus, EHCI_QTD_TOKEN *pQtdStatus)
+{
+ /** @todo CERR */
+ switch (enmStatus)
+ {
+ case VUSBSTATUS_OK:
+ break; /* nothing to do */
+ case VUSBSTATUS_STALL:
+ pQtdStatus->Halted = 1;
+ pQtdStatus->Active = 0;
+ break; /* not an error! */
+ case VUSBSTATUS_DNR:
+ case VUSBSTATUS_CRC:
+ pQtdStatus->TransactError = 1;
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ case VUSBSTATUS_DATA_OVERRUN:
+ pQtdStatus->DataBufError = 1;
+ break;
+ case VUSBSTATUS_NOT_ACCESSED:
+ Log(("pUrb->enmStatus=VUSBSTATUS_NOT_ACCESSED!!!\n"));
+ break; /* can't signal this */
+ default:
+ Log(("pUrb->enmStatus=%#x!!!\n", enmStatus));
+ break;;
+ }
+}
+
+
+/**
+ * Heuristic to determine the transfer type
+ *
+ * @returns transfer type
+ * @param pQhd Queue head pointer
+ */
+static VUSBXFERTYPE ehciR3QueryTransferType(PEHCI_QHD pQhd)
+{
+ /* If it's EP0, we know what it is. */
+ if (!pQhd->Characteristics.EndPt)
+ return VUSBXFERTYPE_CTRL;
+
+ /* Non-zero SMask implies interrupt transfer. */
+ if (pQhd->Caps.SMask)
+ return VUSBXFERTYPE_INTR;
+
+ /* For non-HS EPs, control endpoints are clearly marked. */
+ if ( pQhd->Characteristics.ControlEPFlag
+ && pQhd->Characteristics.EndPtSpeed != EHCI_QHD_EPT_SPEED_HIGH)
+ return VUSBXFERTYPE_CTRL;
+
+ /* If we still don't know, it's guesswork from now on. */
+
+ /* 64 likely indicates an interrupt transfer (see @bugref{8314})*/
+ if (pQhd->Characteristics.MaxLength == 64)
+ return VUSBXFERTYPE_INTR;
+
+ /* At this point we hope it's a bulk transfer with max packet size of 512. */
+ Assert(pQhd->Characteristics.MaxLength == 512);
+ return VUSBXFERTYPE_BULK;
+}
+
+/**
+ * Worker for ehciR3RhXferCompletion that handles the completion of
+ * a URB made up of isochronous TDs.
+ *
+ * In general, all URBs should have status OK.
+ */
+static void ehciR3RhXferCompleteITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ /* Read the whole ITD */
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+ ehciR3ReadItd(pDevIns, pUrb->paTds[0].TdAddr, &PaddedItd);
+
+ /*
+ * Check that the URB hasn't been canceled and then try unlink the TDs.
+ *
+ * We drop the URB if the ED is marked halted/skip ASSUMING that this
+ * means the HCD has canceled the URB.
+ *
+ * If we succeed here (i.e. not dropping the URB), the TdCopy members will
+ * be updated but not yet written. We will delay the writing till we're done
+ * with the data copying, buffer pointer advancing and error handling.
+ */
+ bool fHasBeenCanceled = false;
+ int cFmAge = ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+ if ( cFmAge < 0
+ || (fHasBeenCanceled = ehciR3ItdHasUrbBeenCanceled(pThisCC, pUrb, pItd))
+ )
+ {
+ Log(("%s: ehciR3RhXferCompleteITD: DROPPED {ITD=%RGp cTds=%d TD0=%RGp age %d} because:%s%s!!!\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge,
+ cFmAge < 0 ? " td not-in-flight" : "",
+ fHasBeenCanceled ? " td canceled" : ""));
+ NOREF(fHasBeenCanceled);
+ STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs);
+ return;
+ }
+
+ bool fIOC = false, fError = false;
+
+ /*
+ * Copy the data back (if IN operation) and update the TDs.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ {
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]);
+ if (pItd->Transaction[i].IOC)
+ fIOC = true;
+
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && ( pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_OK
+ || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ || pUrb->aIsocPkts[i].enmStatus == VUSBSTATUS_DATA_OVERRUN))
+ {
+ Assert(pItd->Transaction[i].Active);
+
+ if (pItd->Transaction[i].Active)
+ {
+ const unsigned pg = pItd->Transaction[i].PG;
+ const unsigned cb = pUrb->aIsocPkts[i].cb;
+ pItd->Transaction[i].Length = cb; /* Set the actual size. */
+ /* Copy data. */
+ if (cb)
+ {
+ uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i].off];
+
+ RTGCPHYS GCPhysBuf = (RTGCPHYS)pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ GCPhysBuf += pItd->Transaction[i].Offset;
+
+ /* If the transfer would cross page boundary, use the next sequential PG pointer
+ * for the second part (section 4.7.1).
+ */
+ if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE)
+ {
+ unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset;
+ unsigned cb2 = cb - cb1;
+
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb1);
+ if ((pg + 1) >= EHCI_NUM_ITD_PAGES)
+ LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on completion.\n", pg + 1, pUrb->paTds[0].TdAddr));
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb + cb1, cb2);
+ }
+ else
+ ehciPhysWrite(pDevIns, GCPhysBuf, pb, cb);
+
+ Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n"
+ "%.*Rhxd\n",
+ i, pUrb->aIsocPkts[i].off, cb, pb, pb - &pUrb->abData[0], cb, pb));
+ }
+ }
+ }
+ pItd->Transaction[i].Active = 0; /* transfer is now officially finished */
+ } /* for */
+ }
+ else
+ {
+ LogFunc(("Taking untested code path at line %d...\n", __LINE__));
+ /*
+ * Most status codes only apply to the individual packets.
+ *
+ * If we get a URB level error code of this kind, we'll distribute
+ * it to all the packages unless some other status is available for
+ * a package. This is a bit fuzzy, and we will get rid of this code
+ * before long!
+ */
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ ehciR3VUsbStatus2ItdStatus(pUrb->aIsocPkts[i].enmStatus, &pItd->Transaction[i]);
+ if (pItd->Transaction[i].IOC)
+ fIOC = true;
+
+ pItd->Transaction[i].Active = 0; /* transfer is now officially finished */
+ }
+ }
+ fError = true;
+ }
+
+ /*
+ * Write back the modified TD.
+ */
+
+ Log(("%s: ehciR3RhXferCompleteITD: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp "
+ "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr,
+ pUrb->pHci->EdAddr,
+ pItd->Buffer.Buffer[pItd->Transaction[0].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[0].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[1].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[1].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[2].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[2].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[3].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[3].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[4].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[4].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[5].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[5].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[6].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[6].Length,
+ pItd->Buffer.Buffer[pItd->Transaction[7].PG].Pointer << EHCI_BUFFER_PTR_SHIFT, pItd->Transaction[7].Length
+ ));
+ ehciR3WriteItd(pDevIns, pUrb->paTds[0].TdAddr, pItd);
+
+ /*
+ * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction.
+ * Both error and completion interrupts may be signaled at the same time (see Table 2.10).
+ */
+ if (fError)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT);
+ if (fIOC)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT);
+}
+
+
+/**
+ * Worker for ehciR3RhXferCompletion that handles the completion of
+ * a URB made up of queue heads/descriptors
+ */
+static void ehciR3RhXferCompleteQH(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ EHCI_QHD qhd;
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD & QTD */
+ ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd);
+ AssertMsg(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT),
+ ("Out of order completion %RGp != %RGp Endpoint=%#x\n", pUrb->paTds[0].TdAddr,
+ ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT), pUrb->EndPt));
+ ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd);
+
+ /*
+ * Check that the URB hasn't been canceled and then try unlink the TDs.
+ *
+ * We drop the URB if the ED is marked halted/skip ASSUMING that this
+ * means the HCD has canceled the URB.
+ *
+ * If we succeed here (i.e. not dropping the URB), the TdCopy members will
+ * be updated but not yet written. We will delay the writing till we're done
+ * with the data copying, buffer pointer advancing and error handling.
+ */
+ bool fHasBeenCanceled = false;
+ if ((fHasBeenCanceled = ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd)))
+ {
+ Log(("%s: ehciRhXferCompletionQH: DROPPED {qTD=%RGp cTds=%d TD0=%RGp} because:%s%s!!!\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr,
+ (pUrb->paTds[0].TdAddr != ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)) ? " curptr changed" : "",
+ fHasBeenCanceled ? " td canceled" : ""));
+ NOREF(fHasBeenCanceled);
+ STAM_COUNTER_INC(&pThisCC->StatDroppedUrbs);
+
+ ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+ qtd.Token.Bits.Active = 0;
+ ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd);
+ return;
+ }
+ ehciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+
+ /* Update the status/error bits */
+ ehciR3VUsbStatus2QtdStatus(pUrb->enmStatus, &qtd.Token.Bits);
+
+ /*
+ * Write back IN buffers.
+ */
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && pUrb->cbData
+ && ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ )
+ )
+ {
+ unsigned curOffset = 0;
+ unsigned cbLeft = pUrb->cbData;
+
+ for (unsigned i=qtd.Token.Bits.CurrentPage;i<RT_ELEMENTS(qtd.Buffer.Buffer);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+ unsigned cbCurTransfer;
+
+ GCPhysBuf = qtd.Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ if (i == 0)
+ GCPhysBuf += qtd.Buffer.Offset.Offset;
+
+ cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK);
+ cbCurTransfer = RT_MIN(cbCurTransfer, cbLeft);
+
+ Log3Func(("packet data for page %d:\n"
+ "%.*Rhxd\n",
+ i,
+ cbCurTransfer, &pUrb->abData[curOffset]));
+
+ ehciPhysWrite(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer);
+ curOffset += cbCurTransfer;
+ cbLeft -= cbCurTransfer;
+
+ if (cbLeft == 0)
+ break;
+ Assert(cbLeft < qtd.Token.Bits.Length);
+ }
+ }
+
+ if ( pUrb->cbData
+ && ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ )
+ )
+ {
+ /* 3.5.3:
+ * This field specifies the total number of bytes to be moved
+ * with this transfer descriptor. This field is decremented by the number of bytes actually
+ * moved during the transaction, only on the successful completion of the transaction
+ */
+ Assert(qtd.Token.Bits.Length >= pUrb->cbData);
+ qtd.Token.Bits.Length -= pUrb->cbData;
+
+ /* Data was moved; toggle data toggle bit */
+ qtd.Token.Bits.DataToggle ^= 1;
+ }
+
+#ifdef LOG_ENABLED
+ ehciR3DumpSingleQTD(pUrb->paTds[0].TdAddr, &qtd, "");
+#endif
+ qtd.Token.Bits.Active = 0; /* transfer is now officially finished */
+
+ /*
+ * Write back the modified TD.
+ */
+ Log(("%s: ehciR3RhXferCompleteQH: pUrb->paTds[0].TdAddr=%RGp EdAddr=%RGp\n",
+ pUrb->pszDesc, pUrb->paTds[0].TdAddr,
+ pUrb->pHci->EdAddr));
+
+ ehciR3WriteQTD(pDevIns, pUrb->paTds[0].TdAddr, &qtd);
+
+ ehciR3QHUpdateOverlay(pDevIns, pThis, pThisCC, &qhd, pUrb->pHci->EdAddr, &qtd);
+
+ /*
+ * Signal an interrupt on the next interrupt threshold when IOC was set for any transaction.
+ * Both error and completion interrupts may be signaled at the same time (see Table 2.10).
+ */
+ if (EHCI_QTD_HAS_ERROR(&qtd.Token.Bits))
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_ERROR_INT);
+
+ bool fIOC = false;
+ if (qtd.Token.Bits.IOC) {
+ fIOC = true;
+ Log2Func(("Interrupting, IOC set\n"));
+ } else if (qtd.Token.Bits.Length && (qtd.Token.Bits.PID == EHCI_QTD_PID_IN)) {
+ fIOC = true; /* See 4.10.8 */
+ Log2Func(("Interrupting, short IN packet\n"));
+ }
+ if (fIOC)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_THRESHOLD_INT);
+}
+
+
+/**
+ * Transfer completion callback routine.
+ *
+ * VUSB will call this when a transfer have been completed
+ * in a one or another way.
+ *
+ * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(void) ehciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ LogFlow(("%s: ehciR3RhXferCompletion: EdAddr=%RGp cTds=%d TdAddr0=%RGp\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr));
+ LogFlow(("%s: ehciR3RhXferCompletion: cbData=%x status=%x\n", pUrb->pszDesc, pUrb->cbData, pUrb->enmStatus));
+
+ Assert(pUrb->pHci->cTds == 1);
+
+ RTCritSectEnter(&pThisCC->CritSect);
+ pThisCC->fIdle = false; /* Mark as active */
+
+ switch (pUrb->paTds[0].TdType)
+ {
+ case EHCI_DESCRIPTOR_QH:
+ ehciR3RhXferCompleteQH(pDevIns, pThis, pThisCC, pUrb);
+ break;
+
+ case EHCI_DESCRIPTOR_ITD:
+ ehciR3RhXferCompleteITD(pDevIns, pThis, pThisCC, pUrb);
+ break;
+
+ case EHCI_DESCRIPTOR_SITD:
+ case EHCI_DESCRIPTOR_FSTN:
+ AssertFailed();
+ break;
+ }
+
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+ RTCritSectLeave(&pThisCC->CritSect);
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Worker for ehciR3RhXferError that handles the error case of
+ * a URB made up of queue heads/descriptors
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be retried.
+ * @param pDevIns The device instance.
+ * @param pThisCC EHCI instance data, ring-3 edition. (For stats.)
+ * @param pUrb Pointer to the URB in question.
+ */
+static bool ehciR3RhXferErrorQH(PPDMDEVINS pDevIns, PEHCICC pThisCC, PVUSBURB pUrb)
+{
+ EHCI_QHD qhd;
+ EHCI_QTD qtd;
+
+ /* Read the whole QHD & QTD */
+ ehciR3ReadQHD(pDevIns, pUrb->pHci->EdAddr, &qhd);
+ Assert(pUrb->paTds[0].TdAddr == ((RTGCPHYS)qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT));
+ ehciR3ReadQTD(pDevIns, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT, &qtd);
+
+ /*
+ * Check if the TDs still are valid.
+ * This will make sure the TdCopy is up to date.
+ */
+ /** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */
+ if (ehciR3QhdHasUrbBeenCanceled(pThisCC, pUrb, &qhd, &qtd))
+ {
+ Log(("%s: ehciR3RhXferError: TdAddr0=%RGp canceled!\n", pUrb->pszDesc, pUrb->paTds[0].TdAddr));
+ return true;
+ }
+ return true;
+}
+
+/**
+ * Handle transfer errors.
+ *
+ * VUSB calls this when a transfer attempt failed. This function will respond
+ * indicating whether to retry or complete the URB with failure.
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be retried.
+ * @param pInterface Pointer to EHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(bool) ehciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PEHCICC pThisCC = RT_FROM_MEMBER(pInterface, EHCICC, RootHub.IRhPort);
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ bool fRetire = false;
+
+ RTCritSectEnter(&pThisCC->CritSect);
+ /*
+ * Don't retry on stall.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_STALL)
+ {
+ Log2(("%s: ehciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc));
+ fRetire = true;
+ }
+ else
+ {
+ switch (pUrb->paTds[0].TdType)
+ {
+ case EHCI_DESCRIPTOR_QH:
+ {
+ fRetire = ehciR3RhXferErrorQH(pDevIns, pThisCC, pUrb);
+ break;
+ }
+
+ /*
+ * Isochronous URBs can't be retried.
+ */
+ case EHCI_DESCRIPTOR_ITD:
+ case EHCI_DESCRIPTOR_SITD:
+ case EHCI_DESCRIPTOR_FSTN:
+ default:
+ fRetire = true;
+ break;
+ }
+ }
+
+ RTCritSectLeave(&pThisCC->CritSect);
+ return fRetire;
+}
+
+/**
+ * A worker for ehciR3ServiceQTD which submits the specified TD.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ */
+static bool ehciR3SubmitQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, RTGCPHYS GCPhysQHD,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQTD, PEHCI_QTD pQtd, const unsigned iFrame)
+{
+ /*
+ * Determine the endpoint direction.
+ */
+ VUSBDIRECTION enmDir;
+ switch(pQtd->Token.Bits.PID)
+ {
+ case EHCI_QTD_PID_OUT:
+ enmDir = VUSBDIRECTION_OUT;
+ break;
+ case EHCI_QTD_PID_IN:
+ enmDir = VUSBDIRECTION_IN;
+ break;
+ case EHCI_QTD_PID_SETUP:
+ enmDir = VUSBDIRECTION_SETUP;
+ break;
+ default:
+ return false;
+ }
+
+ VUSBXFERTYPE enmType;
+
+ enmType = ehciR3QueryTransferType(pQhd);
+
+ pThisCC->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize the URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pQhd->Characteristics.DeviceAddress, VUSB_DEVICE_PORT_INVALID,
+ enmType, enmDir, pQtd->Token.Bits.Length, 1, NULL);
+ if (!pUrb)
+ /* retry later... */
+ return false;
+
+ pUrb->EndPt = pQhd->Characteristics.EndPt;
+ pUrb->fShortNotOk = (enmDir != VUSBDIRECTION_IN); /** @todo ??? */
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->cTds = 1;
+ pUrb->pHci->EdAddr = GCPhysQHD;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = iFrame;
+ pUrb->paTds[0].TdAddr = GCPhysQTD;
+ pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_QH;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pQtd));
+ memcpy(pUrb->paTds[0].TdCopy, pQtd, sizeof(*pQtd));
+#if 0 /* color the data */
+ memset(pUrb->abData, 0xfe, cbTotal);
+#endif
+
+ /* copy the data */
+ if ( pQtd->Token.Bits.Length
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ unsigned curOffset = 0;
+ unsigned cbTransfer = pQtd->Token.Bits.Length;
+
+ for (unsigned i=pQtd->Token.Bits.CurrentPage;i<RT_ELEMENTS(pQtd->Buffer.Buffer);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+ unsigned cbCurTransfer;
+
+ GCPhysBuf = pQtd->Buffer.Buffer[i].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ if (i == 0)
+ GCPhysBuf += pQtd->Buffer.Offset.Offset;
+
+ cbCurTransfer = GUEST_PAGE_SIZE - (GCPhysBuf & GUEST_PAGE_OFFSET_MASK);
+ cbCurTransfer = RT_MIN(cbCurTransfer, cbTransfer);
+
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cbCurTransfer);
+
+ Log3Func(("packet data:\n"
+ "%.*Rhxd\n",
+ cbCurTransfer, &pUrb->abData[curOffset]));
+
+ curOffset += cbCurTransfer;
+ cbTransfer -= cbCurTransfer;
+
+ if (cbTransfer == 0)
+ break;
+ Assert(cbTransfer < pQtd->Token.Bits.Length);
+ }
+ }
+
+ /*
+ * Submit the URB.
+ */
+ ehciR3InFlightAddUrb(pThis, pThisCC, pUrb);
+ Log(("%s: ehciSubmitQtd: QtdAddr=%RGp GCPhysQHD=%RGp cbData=%#x\n",
+ pUrb->pszDesc, GCPhysQTD, GCPhysQHD, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSect);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ RTCritSectEnter(&pThisCC->CritSect);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ LogFunc(("failed GCPhysQtd=%RGp GCPhysQHD=%RGp pUrb=%p!!\n",
+ GCPhysQTD, GCPhysQHD, pUrb));
+ ehciR3InFlightRemove(pThis, pThisCC, GCPhysQTD);
+
+ /* Also mark the QH as halted and inactive and write back the changes. */
+ pQhd->Overlay.OrgQTD.Token.Bits.Active = 0;
+ pQhd->Overlay.OrgQTD.Token.Bits.Halted = 1;
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+ return false;
+}
+
+/**
+ * A worker for ehciR3ServiceITD which submits the specified TD.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ */
+static bool ehciR3SubmitITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_ITD pItd, RTGCPHYS ITdAddr, const unsigned iFrame)
+{
+ /*
+ * Determine the endpoint direction.
+ */
+ VUSBDIRECTION enmDir;
+ if(pItd->Buffer.Misc.DirectionIn)
+ enmDir = VUSBDIRECTION_IN;
+ else
+ enmDir = VUSBDIRECTION_OUT;
+
+ /*
+ * Extract the packet sizes and calc the total URB size.
+ */
+ struct
+ {
+ uint16_t cb;
+ } aPkts[EHCI_NUM_ITD_TRANSACTIONS];
+
+ unsigned cPackets = 0;
+ uint32_t cbTotal = 0;
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ aPkts[cPackets].cb = pItd->Transaction[i].Length;
+ cbTotal += pItd->Transaction[i].Length;
+ cPackets++;
+ }
+ }
+ Assert(cbTotal <= 24576);
+
+ pThisCC->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize the URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pItd->Buffer.Misc.DeviceAddress, VUSB_DEVICE_PORT_INVALID,
+ VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL);
+ if (!pUrb)
+ /* retry later... */
+ return false;
+
+ pUrb->EndPt = pItd->Buffer.Misc.EndPt;
+ pUrb->fShortNotOk = false;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->cTds = 1;
+ pUrb->pHci->EdAddr = ITdAddr;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = iFrame;
+ pUrb->paTds[0].TdAddr = ITdAddr;
+ pUrb->paTds[0].TdType = EHCI_DESCRIPTOR_ITD;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pItd));
+ memcpy(pUrb->paTds[0].TdCopy, pItd, sizeof(*pItd));
+#if 0 /* color the data */
+ memset(pUrb->abData, 0xfe, cbTotal);
+#endif
+
+ /* copy the data */
+ if ( cbTotal
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ unsigned curOffset = 0;
+
+ for (unsigned i=0;i<RT_ELEMENTS(pItd->Transaction);i++)
+ {
+ RTGCPHYS GCPhysBuf;
+
+ if (pItd->Transaction[i].Active)
+ {
+ const unsigned pg = pItd->Transaction[i].PG;
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ GCPhysBuf += pItd->Transaction[i].Offset;
+
+ /* If the transfer would cross page boundary, use the next sequential PG pointer
+ * for the second part (section 4.7.1).
+ */
+ if (pItd->Transaction[i].Offset + pItd->Transaction[i].Length > GUEST_PAGE_SIZE)
+ {
+ unsigned cb1 = GUEST_PAGE_SIZE - pItd->Transaction[i].Offset;
+ unsigned cb2 = pItd->Transaction[i].Length - cb1;
+
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], cb1);
+ if ((pg + 1) >= EHCI_NUM_ITD_PAGES)
+ LogRelMax(10, ("EHCI: Crossing to undefined page %d in iTD at %RGp on submit.\n", pg + 1, pUrb->paTds[0].TdAddr));
+
+ GCPhysBuf = pItd->Buffer.Buffer[pg + 1].Pointer << EHCI_BUFFER_PTR_SHIFT;
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset + cb1], cb2);
+ }
+ else
+ ehciPhysRead(pDevIns, GCPhysBuf, &pUrb->abData[curOffset], pItd->Transaction[i].Length);
+
+ curOffset += pItd->Transaction[i].Length;
+ }
+ }
+ }
+
+ /* setup the packets */
+ pUrb->cIsocPkts = cPackets;
+ unsigned off = 0;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
+ pUrb->aIsocPkts[i].off = off;
+ off += pUrb->aIsocPkts[i].cb = aPkts[i].cb;
+ }
+ Assert(off == cbTotal);
+
+ /*
+ * Submit the URB.
+ */
+ ehciR3InFlightAddUrb(pThis, pThisCC, pUrb);
+ Log(("%s: ehciR3SubmitITD: cbData=%#x cIsocPkts=%d TdAddr=%RGp (%#x)\n",
+ pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, ITdAddr, iFrame));
+ RTCritSectLeave(&pThisCC->CritSect);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ RTCritSectEnter(&pThisCC->CritSect);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ LogFunc(("failed pUrb=%p cbData=%#x cTds=%d ITdAddr0=%RGp - rc=%Rrc\n",
+ pUrb, cbTotal, 1, ITdAddr, rc));
+ ehciR3InFlightRemove(pThis, pThisCC, ITdAddr);
+ return false;
+}
+
+
+/**
+ * Services an ITD list (only for high-speed isochronous endpoints; all others use queues)
+ *
+ * An ITD can contain up to 8 transactions, which are all processed within a single frame.
+ * Note that FRINDEX includes the micro-frame number, but only bits [12:3] are used as an
+ * index into the periodic frame list (see 4.7.1).
+ */
+static void ehciR3ServiceITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(enmServiceType);
+ bool fAnyActive = false;
+ EHCI_ITD_PAD PaddedItd;
+ PEHCI_ITD pItd = &PaddedItd.itd;
+
+ if (ehciR3IsTdInFlight(pThisCC, GCPhys))
+ return;
+
+ /* Read the whole ITD */
+ ehciR3ReadItd(pDevIns, GCPhys, &PaddedItd);
+
+ Log2((" ITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x Mult=%d}\n", GCPhys, pItd->Buffer.Misc.DeviceAddress, pItd->Buffer.Misc.EndPt, (pItd->Buffer.Misc.DirectionIn) ? "in" : "out", pItd->Buffer.Misc.MaxPacket, pItd->Buffer.Misc.Multi));
+
+ /* Some basic checks */
+ for (unsigned i = 0; i < RT_ELEMENTS(pItd->Transaction); i++)
+ {
+ if (pItd->Transaction[i].Active)
+ {
+ fAnyActive = true;
+ if (pItd->Transaction[i].PG >= EHCI_NUM_ITD_PAGES)
+ {
+ /* Using out of range PG value (7) yields undefined behavior. We will attempt
+ * the last page below 4GB (which is ROM, not writable).
+ */
+ LogRelMax(10, ("EHCI: Illegal page value %d in iTD at %RGp.\n", pItd->Transaction[i].PG, (RTGCPHYS)GCPhys));
+ }
+
+ Log2((" T%d Len=%x Offset=%x PG=%d IOC=%d Buffer=%x\n", i, pItd->Transaction[i].Length, pItd->Transaction[i].Offset, pItd->Transaction[i].PG, pItd->Transaction[i].IOC,
+ pItd->Buffer.Buffer[pItd->Transaction[i].PG].Pointer));
+ }
+ }
+ /* We can't service one transaction every 125 usec, so we'll handle all 8 of them at once. */
+ if (fAnyActive)
+ ehciR3SubmitITD(pDevIns, pThis, pThisCC, pItd, GCPhys, iFrame);
+ else
+ Log2((" ITD not active, skipping.\n"));
+}
+
+/**
+ * Services an SITD list
+ */
+static void ehciR3ServiceSITD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(pThis, pThisCC, enmServiceType, iFrame);
+
+ /* Read the whole SITD */
+ EHCI_SITD sitd;
+ ehciR3ReadSitd(pDevIns, GCPhys, &sitd);
+
+ Log2((" SITD: %RGp={Addr=%x EndPt=%x Dir=%s MaxSize=%x}\n", GCPhys, sitd.Address.DeviceAddress, sitd.Address.EndPt, (sitd.Address.DirectionIn) ? "in" : "out", sitd.Transfer.Length));
+
+ if (sitd.Transfer.Active)
+ AssertMsgFailed(("SITD lists not implemented; active SITD should never occur!\n"));
+ else
+ Log2((" SITD not active, skipping.\n"));
+}
+
+/**
+ * Copies the currently active QTD to the QH overlay area
+ */
+static void ehciR3QHSetupOverlay(PPDMDEVINS pDevIns, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd, RTGCPHYS GCPhysQTD)
+{
+ bool fDataToggle = pQhd->Overlay.OrgQTD.Token.Bits.DataToggle;
+
+ Assert(pQtd->Token.Bits.Active);
+
+ Log2Func(("current pointer %RGp old %RGp\n", GCPhysQTD, ((RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)));
+ pQhd->CurrQTD.Pointer = GCPhysQTD >> EHCI_TD_PTR_SHIFT;
+ pQhd->CurrQTD.Reserved = 0;
+ pQhd->Overlay.OrgQTD = *pQtd;
+ /* All fields except those below are copied from the QTD; see 4.10.2 */
+ if (pQhd->Characteristics.DataToggle)
+ pQhd->Overlay.OrgQTD.Token.Bits.DataToggle = fDataToggle; /* Preserve data toggle bit in the queue head */
+
+ pQhd->Overlay.Status.Buffer1.CProgMask = 0;
+ pQhd->Overlay.Status.Buffer2.FrameTag = 0;
+ pQhd->Overlay.Status.AltNextQTD.NakCnt = pQhd->Characteristics.NakCountReload;
+ /* Note: ping state not changed if it's a high-speed device */
+
+ /* Save the current QTD to the overlay area */
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+}
+
+/**
+ * Updates the currently active QTD to the QH overlay area
+ */
+static void ehciR3QHUpdateOverlay(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD, PEHCI_QTD pQtd)
+{
+ Assert(!pQtd->Token.Bits.Active);
+ pQhd->Overlay.OrgQTD = *pQtd;
+ if (!pQhd->Overlay.OrgQTD.Next.Terminate)
+ {
+ EHCI_QTD qtdNext;
+ RTGCPHYS GCPhysNextQTD = pQhd->Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT;
+
+ if (ehciR3IsTdInFlight(pThisCC, GCPhysNextQTD))
+ {
+ /* Read the whole QTD */
+ ehciR3ReadQTD(pDevIns, GCPhysNextQTD, &qtdNext);
+ if (qtdNext.Token.Bits.Active)
+ {
+ ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtdNext, GCPhysNextQTD);
+ return;
+ }
+ else
+ {
+ /* Td has been cancelled! */
+ LogFunc(("in-flight qTD %RGp has been cancelled! (active=%d T=%d)\n", GCPhysNextQTD, qtdNext.Token.Bits.Active, pQhd->Overlay.OrgQTD.Next.Terminate));
+ /** @todo we don't properly cancel the URB; it will remain active on the host.... */
+ ehciR3InFlightRemove(pThis, pThisCC, GCPhysNextQTD);
+ }
+ }
+ }
+ /* Save the current QTD to the overlay area. */
+ ehciR3UpdateQHD(pDevIns, GCPhysQHD, pQhd);
+}
+
+/**
+ * Services a QTD list
+ */
+static RTGCPHYS ehciR3ServiceQTD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, PEHCI_QHD pQhd, RTGCPHYS GCPhysQHD,
+ RTGCPHYS GCPhysQTD, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ EHCI_QTD qtd;
+
+ /* Read the whole QTD */
+ ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd);
+
+ if (qtd.Token.Bits.Active)
+ {
+ if (!ehciR3IsTdInFlight(pThisCC, GCPhysQTD))
+ {
+ /* Don't queue more than one non-bulk transfer at a time */
+ if ( ehciR3QueryTransferType(pQhd) != VUSBXFERTYPE_BULK
+ && pQhd->Overlay.OrgQTD.Token.Bits.Active)
+ return 0;
+
+ Log2((" Length=%x IOC=%d DT=%d PID=%s}\n", qtd.Token.Bits.Length, qtd.Token.Bits.IOC, qtd.Token.Bits.DataToggle, ehciPID2Str(qtd.Token.Bits.PID)));
+ if ( !pQhd->Overlay.OrgQTD.Token.Bits.Active
+ || GCPhysQTD == (RTGCPHYS)pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)
+ ehciR3QHSetupOverlay(pDevIns, pQhd, GCPhysQHD, &qtd, GCPhysQTD);
+ else
+ Log2Func(("transfer %RGp in progress -> don't update the overlay\n", (RTGCPHYS)(pQhd->CurrQTD.Pointer << EHCI_TD_PTR_SHIFT)));
+
+ ehciR3SubmitQTD(pDevIns, pThis, pThisCC, GCPhysQHD, pQhd, GCPhysQTD, &qtd, iFrame);
+
+ /* Set the Reclamation bit in USBSTS (4.10.3) */
+ if (enmServiceType == EHCI_SERVICE_ASYNC)
+ {
+ Log2Func(("activity detected, set EHCI_STATUS_RECLAMATION\n"));
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION);
+ }
+
+ /* Reread the whole QTD; it might have been completed already and therefore changed */
+ ehciR3ReadQTD(pDevIns, GCPhysQTD, &qtd);
+ }
+ /* Table 4-10: any transfer with zero size: queue only one */
+ if (qtd.Token.Bits.Length == 0)
+ {
+ LogFunc(("queue only one: transfer with zero size\n"));
+ return 0;
+ }
+
+ /* We can't queue more than one TD if we can't decide here and now which TD we should take next */
+ if ( qtd.Token.Bits.Active /* only check if this urb is in-flight */
+ && qtd.Token.Bits.PID == EHCI_QTD_PID_IN
+ && !qtd.AltNext.Terminate
+ && !qtd.Next.Terminate
+ && qtd.Next.Pointer != qtd.AltNext.Pointer)
+ {
+ Log2Func(("Can't decide which pointer to take next; don't queue more than one!\n"));
+ return 0;
+ }
+ }
+ else
+ {
+ Log2((" Not active}\n"));
+ return 0;
+ }
+
+ /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */
+ if ( !qtd.Token.Bits.Active /* only check if no urbs are in-flight */
+ && qtd.Token.Bits.PID == EHCI_QTD_PID_IN /* short packets only apply to incoming tds */
+ && !qtd.AltNext.Terminate
+ && qtd.Token.Bits.Length)
+ {
+ Assert(qtd.AltNext.Pointer);
+ Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT)));
+ return qtd.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ Assert(qtd.Next.Pointer || qtd.Next.Terminate);
+ if (qtd.Next.Terminate || !qtd.Next.Pointer)
+ return 0;
+ return qtd.Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+}
+
+/**
+ * Services a QHD list
+ */
+static bool ehciR3ServiceQHD(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ EHCI_QHD qhd;
+
+ Log2Func(("%RGp={", GCPhys));
+
+ /* Read the whole QHD */ /** @todo reading too much */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+
+ /* Only interrupt qHDs should be linked from the periodic list; the S-mask field description
+ * in table 3-20 clearly says a zero S-mask on the periodic list yields undefined results. In reality,
+ * the Windows HCD links dummy qHDs at the start of the interrupt queue and these have an empty S-mask.
+ * If we're servicing the periodic list, check the S-mask first; that takes care of the dummy qHDs.
+ */
+ if (enmServiceType == EHCI_SERVICE_PERIODIC)
+ {
+ // If iFrame was a micro-frame number, we should check the S-mask against it. But
+ // we're processing all micro-frames at once, so we'll look at any qHD with non-zero S-mask
+ if (qhd.Caps.SMask == 0) {
+ Log2Func(("periodic qHD not scheduled for current frame -> next\n"));
+ return true;
+ }
+ else
+ Log2Func(("periodic qHD scheduled for current frame, processing\n"));
+ }
+ else
+ {
+ Assert(enmServiceType == EHCI_SERVICE_ASYNC);
+ /* Empty schedule detection (4.10.1), for async schedule only */
+ if (qhd.Characteristics.HeadReclamation) /* H-bit set but not an interrupt qHD */
+ {
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ {
+ Log2Func(("clear EHCI_STATUS_RECLAMATION\n"));
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_RECLAMATION);
+ }
+ else
+ {
+ Log2Func(("empty schedule -> bail out\n"));
+ pThis->fAsyncTraversalTimerActive = true;
+ return false; /** stop traversing the list */
+ }
+ }
+ }
+
+ /* no active qTD here or in the next queue element -> skip to next horizontal pointer (Figure 4.14 & 4.10.2) */
+ if ( !qhd.Overlay.OrgQTD.Token.Bits.Active
+ && qhd.Characteristics.InActiveNext)
+ {
+ Log2Func(("skip to next pointer (active)\n"));
+ return true;
+ }
+ /* we are ignoring the Inactivate on Next Transaction bit; only applies to periodic lists & low or full speed devices (table 3.9) */
+
+ /** We are not allowed to handle multiple TDs unless async park is enabled (and only for high-speed devices), but we can cheat a bit. */
+ unsigned PMCount = 1;
+ if ( (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ && qhd.Characteristics.EndPtSpeed == EHCI_QHD_EPT_SPEED_HIGH
+ && enmServiceType == EHCI_SERVICE_ASYNC)
+ {
+ PMCount = (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT;
+ Log2Func(("PM Count=%d\n", PMCount));
+
+ /* We will attempt to queue a bit more if we're allowed to queue more than one TD. */
+ if (PMCount != 1)
+ PMCount = 16;
+ }
+
+ /* Queue as many transfer descriptors as possible */
+ RTGCPHYS GCPhysQTD;
+ if (qhd.Overlay.OrgQTD.Token.Bits.Active)
+ {
+ Assert(ehciR3IsTdInFlight(pThisCC, qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT));
+ GCPhysQTD = qhd.CurrQTD.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ /* If the 'Bytes to Transfer' field is not zero and the T-bit in the AltNext pointer is zero, then use this pointer. (4.10.2) */
+ if ( !qhd.Overlay.OrgQTD.AltNext.Terminate
+ && qhd.Overlay.OrgQTD.Token.Bits.Length)
+ {
+ Assert(qhd.Overlay.OrgQTD.AltNext.Pointer);
+ Log2(("Taking alternate pointer %RGp\n", (RTGCPHYS)(qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT)));
+ GCPhysQTD = qhd.Overlay.OrgQTD.AltNext.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ else
+ {
+ Assert(qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Next.Terminate || qhd.Overlay.OrgQTD.Token.Bits.Halted);
+ if (qhd.Overlay.OrgQTD.Next.Terminate || !qhd.Overlay.OrgQTD.Next.Pointer || qhd.Overlay.OrgQTD.Token.Bits.Halted)
+ GCPhysQTD = 0;
+ else
+ GCPhysQTD = qhd.Overlay.OrgQTD.Next.Pointer << EHCI_TD_PTR_SHIFT;
+ }
+ }
+
+ while (GCPhysQTD && PMCount--)
+ {
+ GCPhysQTD = ehciR3ServiceQTD(pDevIns, pThis, pThisCC, &qhd, GCPhys, GCPhysQTD, enmServiceType, iFrame);
+
+ /* Reread the whole QHD; urb submit can call us right back which causes QH changes */ /** @todo reading too much */
+ ehciR3ReadQHD(pDevIns, GCPhys, &qhd);
+ }
+ return true;
+}
+
+/**
+ * Services a FSTN list
+ */
+static void ehciR3ServiceFSTN(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC,
+ RTGCPHYS GCPhys, EHCI_SERVICE_TYPE enmServiceType, const unsigned iFrame)
+{
+ RT_NOREF(pDevIns, pThis, pThisCC, GCPhys, enmServiceType, iFrame);
+ AssertMsgFailed(("FSTN lists not implemented; should never occur!\n"));
+}
+
+/**
+ * Services the async list.
+ *
+ * The async list has complex URB assembling, but that's taken
+ * care of at VUSB level (unlike the other transfer types).
+ */
+static void ehciR3ServiceAsyncList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame)
+{
+ RTGCPHYS GCPhysHead = pThis->async_list_base;
+ RTGCPHYS GCPhys = GCPhysHead;
+ EHCI_TD_PTR ptr;
+ unsigned cIterations = 0;
+
+ Assert(!(pThis->async_list_base & 0x1f));
+ Assert(pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE);
+ Assert(pThis->cmd & EHCI_CMD_RUN);
+
+ Log2Func(("%RGp\n", GCPhysHead));
+#ifdef LOG_ENABLED
+ ehciR3DumpQH(pDevIns, GCPhysHead, true);
+#endif
+
+ /* Signal the async advance doorbell interrupt (if required) */
+ if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL))
+// && !pThis->cInFlight)
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_INT_ON_ASYNC_ADV);
+
+ /* Process the list of qHDs */
+ for (;;)
+ {
+ /* Process the qHD */
+ if (!ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_ASYNC, iFrame))
+ break;
+
+ /* Read the next pointer */
+ RTGCPHYS GCPhysLast = GCPhys;
+ ehciR3ReadTDPtr(pDevIns, GCPhys, &ptr);
+
+ /* Detect obvious loops. */
+ if (GCPhys == ((RTGCPHYS)ptr.Pointer << EHCI_TD_PTR_SHIFT))
+ break;
+
+ /* Technically a zero address could be valid, but that's extremely unlikely! */
+ Assert(ptr.Pointer || ptr.Terminate);
+ if (ptr.Terminate || !ptr.Pointer)
+ break;
+
+ /* Not clear what we should do if this *is* something other than a qHD. */
+ AssertMsg(ptr.Type == EHCI_DESCRIPTOR_QH, ("Unexpected pointer to type %d\n", ptr.Type));
+ if (ptr.Type != EHCI_DESCRIPTOR_QH)
+ break;
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ AssertMsgBreak(++cIterations < 128, ("Too many iterations, exiting\n"));
+
+ /* next */
+ GCPhys = ptr.Pointer << EHCI_TD_PTR_SHIFT;
+ Assert(!(GCPhys & 0x1f));
+ if ( GCPhys == GCPhysHead
+ || GCPhys == GCPhysLast)
+ break; /* break the loop */
+ }
+
+#ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ehciR3DumpQH(pDevIns, GCPhysHead, true);
+#endif
+}
+
+
+/**
+ * Services the periodic list.
+ *
+ * On the interrupt portion of the periodic list we must reassemble URBs from multiple
+ * TDs using heuristics derived from USB tracing done in the guests and guest source
+ * code (when available).
+ */
+static void ehciR3ServicePeriodicList(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, const unsigned iFrame)
+{
+ Assert(pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE);
+
+#ifdef LOG_ENABLED
+ RTGCPHYS pFramePtrHead = 0;
+ if (g_fLogInterruptEPs)
+ {
+ RTGCPHYS pFramePtr = pThis->periodic_list_base + iFrame * sizeof(EHCI_FRAME_LIST_PTR);
+
+ pFramePtrHead = pFramePtr;
+
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x before", iFrame);
+ ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true);
+ }
+#endif
+
+ /*
+ * Iterate the periodic list.
+ */
+ EHCI_FRAME_LIST_PTR FramePtr;
+ RTGCPHYS GCPhys = pThis->periodic_list_base + iFrame * sizeof(FramePtr);
+ unsigned iterations = 0;
+
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+ while (!FramePtr.Terminate && (pThis->cmd & EHCI_CMD_RUN))
+ {
+ GCPhys = FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT;
+ /* Process the descriptor based on its type. Note that on the periodic
+ * list, HCDs may (and do) mix iTDs and qHDs more or less freely.
+ */
+ switch(FramePtr.Type)
+ {
+ case EHCI_DESCRIPTOR_ITD:
+ ehciR3ServiceITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_SITD:
+ ehciR3ServiceSITD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_QH:
+ ehciR3ServiceQHD(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ case EHCI_DESCRIPTOR_FSTN:
+ ehciR3ServiceFSTN(pDevIns, pThis, pThisCC, GCPhys, EHCI_SERVICE_PERIODIC, iFrame);
+ break;
+ }
+
+ /* If we ran too many iterations, the list must be looping in on itself.
+ * On a real controller loops wouldn't be fatal, as it will eventually
+ * run out of time in the micro-frame.
+ */
+ if (++iterations == 2048)
+ {
+ AssertMsgFailed(("ehciR3ServicePeriodicList: Too many iterations, exiting\n"));
+ break;
+ }
+ /* Read the next link */
+ ehciR3ReadFrameListPtr(pDevIns, GCPhys, &FramePtr);
+
+ /* Detect obvious loops. */
+ if (GCPhys == ((RTGCPHYS)FramePtr.FrameAddr << EHCI_FRAME_LIST_NEXTPTR_SHIFT))
+ break;
+ }
+
+#ifdef LOG_ENABLED
+ if (g_fLogInterruptEPs)
+ {
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iFrame);
+ ehciR3DumpPeriodicList(pDevIns, pFramePtrHead, sz, true);
+ }
+#endif
+}
+
+
+/**
+ * Calculate frame timer variables given a frame rate (1,000 Hz is the full speed).
+ */
+static void ehciR3CalcTimerIntervals(PEHCI pThis, PEHCICC pThisCC, uint32_t u32FrameRate)
+{
+ Assert(u32FrameRate <= EHCI_HARDWARE_TIMER_FREQ);
+
+ pThis->uFramesPerTimerCall = EHCI_HARDWARE_TIMER_FREQ / u32FrameRate;
+ pThisCC->nsWait = RT_NS_1SEC / u32FrameRate;
+ pThisCC->cTicksPerFrame = pThisCC->u64TimerHz / u32FrameRate;
+ if (!pThisCC->cTicksPerFrame)
+ pThisCC->cTicksPerFrame = 1;
+ pThisCC->uFrameRate = u32FrameRate;
+}
+
+/**
+ * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame.
+ */
+static void ehciR3StartOfFrame(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC)
+{
+ uint32_t uNewFrameRate = pThisCC->uFrameRate;
+#ifdef LOG_ENABLED
+ const uint32_t status_old = pThis->intr_status;
+#endif
+
+ pThis->SofTime += pThisCC->cTicksPerFrame;
+ unsigned iFrame = (pThis->frame_idx >> EHCI_FRINDEX_FRAME_INDEX_SHIFT) & EHCI_FRINDEX_FRAME_INDEX_MASK;
+
+ if (pThis->uIrqInterval < pThis->uFramesPerTimerCall)
+ pThis->uIrqInterval = 0;
+ else
+ pThis->uIrqInterval -= pThis->uFramesPerTimerCall;
+
+ /* Empty async list detection halted the async schedule */
+ if (pThis->fAsyncTraversalTimerActive)
+ {
+ /* Table 4.7 in 4.8.4.1 */
+ Log2Func(("setting STATUS_RECLAMATION after empty list detection\n"));
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_RECLAMATION);
+ pThis->fAsyncTraversalTimerActive = false;
+ }
+
+ /*
+ * Periodic EPs (Isochronous & Interrupt)
+ */
+ if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ {
+ int num_frames = RT_MAX(1, pThis->uFramesPerTimerCall >> EHCI_FRINDEX_FRAME_INDEX_SHIFT);
+ Assert(num_frames > 0 && num_frames < 1024);
+
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_PERIOD_SCHED);
+
+ if (pThis->cmd & EHCI_CMD_RUN)
+ {
+ /* If we're running the frame timer at a reduced rate, we still need to process
+ * all frames. Otherwise we risk completely missing newly scheduled periodic transfers.
+ */
+ for (int i = 0; i < num_frames; ++i)
+ ehciR3ServicePeriodicList(pDevIns, pThis, pThisCC, (iFrame + i) & EHCI_FRINDEX_FRAME_INDEX_MASK);
+ }
+ }
+ else
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED);
+
+ /*
+ * Async EPs (Control and Bulk)
+ */
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ {
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_ASYNC_SCHED);
+ if (pThis->cmd & EHCI_CMD_RUN)
+ ehciR3ServiceAsyncList(pDevIns, pThis, pThisCC, iFrame);
+ }
+ else
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED);
+
+ /*
+ * ASSUME the guest won't be very upset if a TD is completed, retired and handed
+ * back immediately. The idea is to be able to retire the data and/or status stages
+ * of a control transfer together with the setup stage, thus saving a frame. This
+ * behaviour is should be perfectly ok, since the setup (and maybe data) stages
+ * have already taken at least one frame to complete.
+ *
+ * But, when implementing the first synchronous virtual USB devices, we'll have to
+ * verify that the guest doesn't choke when having a TD returned in the same frame
+ * as it was submitted.
+ */
+
+#ifdef LOG_ENABLED
+ if (pThis->intr_status ^ status_old)
+ {
+ uint32_t val = pThis->intr_status;
+ uint32_t chg = val ^ status_old; NOREF(chg);
+ Log2Func(("HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", val & 1,
+ chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1,
+ chg & (3<<16)? "*" : "", (val >> 16) & 3));
+ }
+#endif
+
+ /*
+ * Adjust the frame timer interval based on idle detection.
+ */
+ if (pThisCC->fIdle)
+ {
+ pThisCC->cIdleCycles++;
+
+ /*
+ * Set the new frame rate based on how long we've been idle.
+ * Don't remain more than 2 seconds in each frame rate (except for lowest one).
+ */
+ /** @todo Experiment with these values. */
+ if (pThisCC->cIdleCycles == 2 * pThisCC->uFrameRate)
+ {
+ if (pThisCC->uFrameRate > 500)
+ uNewFrameRate = pThisCC->uFrameRate - 500;
+ else
+ uNewFrameRate = 50; /* Absolute minimum is 50 Hertz, i.e 20ms interval. */
+
+ pThisCC->cIdleCycles = 1;
+ }
+ }
+ else
+ {
+ if (pThisCC->cIdleCycles)
+ {
+ pThisCC->cIdleCycles = 0;
+ uNewFrameRate = pThisCC->uFrameRateDefault;
+ }
+ }
+ if (uNewFrameRate != pThisCC->uFrameRate)
+ ehciR3CalcTimerIntervals(pThis, pThisCC, uNewFrameRate);
+}
+
+/**
+ * Updates the HcFmNumber and frame_index values. HcFmNumber contains the current USB
+ * frame number, frame_idx is the current micro-frame. In other words,
+ *
+ * HcFmNumber == frame_idx << EHCI_FRAME_INDEX_SHIFT
+ */
+static void ehciR3BumpFrameNumber(PPDMDEVINS pDevIns, PEHCI pThis)
+{
+ pThis->HcFmNumber = pThis->frame_idx;
+
+ const uint32_t u32OldFmNumber = pThis->HcFmNumber;
+
+ pThis->HcFmNumber += pThis->uFramesPerTimerCall;
+
+ if ((u32OldFmNumber ^ pThis->HcFmNumber) & ~EHCI_FRINDEX_FRAME_INDEX_MASK)
+ {
+ Log2Func(("rollover!\n"));
+ ehciR3SetInterrupt(pDevIns, pThis, EHCI_STATUS_FRAME_LIST_ROLLOVER);
+ }
+
+ pThis->frame_idx = pThis->HcFmNumber;
+
+}
+
+/**
+ * @callback_method_impl{PFNPDMTHREADDEV, EHCI Frame Thread}
+ */
+static DECLCALLBACK(int) ehciR3ThreadFrame(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ int rc = VINF_SUCCESS;
+ while ( !ASMAtomicReadBool(&pThis->fBusStarted)
+ && pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ /* Make sure the SCHED status bits are clear. */
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_PERIOD_SCHED);
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_ASYNC_SCHED);
+
+ /* Signal the waiter that we are stopped now. */
+ rc = RTSemEventMultiSignal(pThisCC->hSemEventFrameStopped);
+ AssertRC(rc);
+
+ rc = RTSemEventMultiWait(pThisCC->hSemEventFrame, RT_INDEFINITE_WAIT);
+ RTSemEventMultiReset(pThisCC->hSemEventFrame);
+ }
+
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+
+ uint64_t const tsNanoStart = RTTimeNanoTS();
+
+ RTCritSectEnter(&pThisCC->CritSect);
+
+ /* Reset idle detection flag */
+ pThisCC->fIdle = true;
+
+ /* Frame boundary, so do EOF stuff here */
+ ehciR3StartOfFrame(pDevIns, pThis, pThisCC);
+
+ /* Start the next frame */
+ ehciR3BumpFrameNumber(pDevIns, pThis);
+
+ RTCritSectLeave(&pThisCC->CritSect);
+
+ /* Wait for the next round. */
+ uint64_t nsWait = (RTTimeNanoTS() + pThisCC->nsWait) - tsNanoStart;
+
+ rc = RTSemEventMultiWaitEx(pThisCC->hSemEventFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE,
+ nsWait);
+ AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ RTSemEventMultiReset(pThisCC->hSemEventFrame);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Unblock the framer thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) ehciR3ThreadFrameWakeup(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ RT_NOREF(pThread);
+ return RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Start sending SOF tokens across the USB bus, lists are processed in next frame.
+ */
+static void ehciR3BusStart(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC)
+{
+ pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn);
+ ehciR3BumpFrameNumber(pDevIns, pThis);
+
+ LogFunc(("Bus started\n"));
+
+ ASMAtomicAndU32(&pThis->intr_status, ~EHCI_STATUS_HCHALTED);
+ pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns) - pThisCC->cTicksPerFrame;
+ bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, true);
+ if (!fBusActive)
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+}
+
+/**
+ * Stop sending SOF tokens on the bus
+ */
+static void ehciR3BusStop(PEHCI pThis, PEHCICC pThisCC)
+{
+ LogFunc(("\n"));
+ bool fBusActive = ASMAtomicXchgBool(&pThis->fBusStarted, false);
+ if (fBusActive)
+ {
+ int rc = RTSemEventMultiReset(pThisCC->hSemEventFrameStopped);
+ AssertRC(rc);
+
+ /* Signal the frame thread to stop. */
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+
+ /* Wait for signal from the thread that it stopped. */
+ rc = RTSemEventMultiWait(pThisCC->hSemEventFrameStopped, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn);
+ ASMAtomicOrU32(&pThis->intr_status, EHCI_STATUS_HCHALTED);
+}
+
+/**
+ * Power a port up or down
+ */
+static void ehciR3PortPower(PEHCI pThis, PEHCICC pThisCC, unsigned iPort, bool fPowerUp)
+{
+ bool fOldPPS = !!(pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_POWER);
+ if (fPowerUp)
+ {
+ Log2Func(("port %d UP\n", iPort));
+ /* power up */
+ if (pThisCC->RootHub.aPorts[iPort].fAttached)
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT);
+ if (pThis->RootHub.aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT)
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_POWER);
+ if (pThisCC->RootHub.aPorts[iPort].fAttached && !fOldPPS)
+ VUSBIRhDevPowerOn(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort));
+ }
+ else
+ {
+ Log2(("Func port %d DOWN\n", iPort));
+ /* power down */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_POWER|EHCI_PORT_CURRENT_CONNECT));
+ if (pThisCC->RootHub.aPorts[iPort].fAttached && fOldPPS)
+ VUSBIRhDevPowerOff(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(iPort));
+ }
+}
+
+/**
+ * Completion callback for the VUSBIDevReset() operation.
+ * @thread EMT.
+ */
+static void ehciR3PortResetDone(PEHCI pThis, PEHCICC pThisCC, uint32_t uPort, int rc)
+{
+ Log2Func(("rc=%Rrc\n", rc));
+ Assert(uPort >= 1);
+ unsigned iPort = uPort - 1;
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Successful reset.
+ */
+ Log2Func(("Reset completed.\n"));
+ /* Note: XP relies on us clearing EHCI_PORT_CONNECT_CHANGE */
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND | EHCI_PORT_CONNECT_CHANGE));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_PORT_ENABLED);
+ }
+ else
+ {
+ /* desperate measures. */
+ if ( pThisCC->RootHub.aPorts[iPort].fAttached
+ && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED)
+ {
+ /*
+ * Damn, something weird happend during reset. We'll pretend the user did an
+ * incredible fast reconnect or something. (prolly not gonna work)
+ */
+ Log2Func(("The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CURRENT_CONNECT | EHCI_PORT_CONNECT_CHANGE);
+ }
+ else
+ {
+ /*
+ * The device has / will be disconnected.
+ */
+ Log2Func(("Disconnected (rc=%Rrc)!!!\n", rc));
+ ASMAtomicAndU32(&pThis->RootHub.aPorts[iPort].fReg, ~(EHCI_PORT_RESET | EHCI_PORT_SUSPEND));
+ ASMAtomicOrU32(&pThis->RootHub.aPorts[iPort].fReg, EHCI_PORT_CONNECT_CHANGE);
+ }
+ }
+}
+
+/**
+ * Sets a flag in a port status register but only set it if a device is
+ * connected, if not set ConnectStatusChange flag to force HCD to reevaluate
+ * connect status.
+ *
+ * @returns true if device was connected and the flag was cleared.
+ */
+static bool ehciR3RhPortSetIfConnected(PEHCIROOTHUB pRh, int iPort, uint32_t fValue)
+{
+ /*
+ * Writing a 0 has no effect
+ */
+ if (fValue == 0)
+ return false;
+
+ /*
+ * The port might be still/already disconnected.
+ */
+ if (!(pRh->aPorts[iPort].fReg & EHCI_PORT_CURRENT_CONNECT))
+ return false;
+
+ bool fRc = !(pRh->aPorts[iPort].fReg & fValue);
+
+ /* set the bit */
+ ASMAtomicOrU32(&pRh->aPorts[iPort].fReg, fValue);
+
+ return fRc;
+}
+
+#endif /* IN_RING3 */
+
+
+/**
+ * Read the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcCommand_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ /* Signal the async advance doorbell interrupt (if required)
+ * XP polls the command register to see when it can queue up more TDs.
+ */
+ if ( (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL))
+// && !pThis->cInFlight)
+ {
+ int rc = ehciSetInterrupt(pDevIns, pThis, VINF_IOM_R3_MMIO_READ, EHCI_STATUS_INT_ON_ASYNC_ADV);
+ if (rc != VINF_SUCCESS)
+ return rc;
+ }
+
+ *pu32Value = pThis->cmd;
+ RT_NOREF(iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcCommand_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+#endif
+ RT_NOREF(pDevIns, iReg);
+
+#ifdef LOG_ENABLED
+ Log(("HcCommand_w old=%x new=%x\n", pThis->cmd, val));
+ if (val & EHCI_CMD_RUN)
+ Log((" CMD_RUN\n"));
+ if (val & EHCI_CMD_RESET)
+ Log((" CMD_RESET\n"));
+ if (val & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ Log((" CMD_PERIODIC_SCHED_ENABLE\n"));
+ if (val & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ Log((" CMD_ASYNC_SCHED_ENABLE\n"));
+ if (val & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)
+ Log((" CMD_INT_ON_ADVANCE_DOORBELL\n"));
+ if (val & EHCI_CMD_SOFT_RESET)
+ Log((" CMD_SOFT_RESET\n"));
+ if (val & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ Log((" CMD_ASYNC_SCHED_PARK_ENABLE\n"));
+
+ Log((" CMD_FRAME_LIST_SIZE %d\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT));
+ Log((" CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (val & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT));
+ Log((" CMD_INTERRUPT_THRESHOLD %d\n", (val & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT));
+#endif
+
+ Assert(!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST)); /* hardcoded assumptions about list size */
+ if (!(pThis->hcc_params & EHCI_HCC_PARAMS_PROGRAMMABLE_FRAME_LIST))
+ {
+ if (val & EHCI_CMD_FRAME_LIST_SIZE_MASK)
+ Log(("Trying to change the frame list size to %d even though it's hardcoded at 1024 elements!!\n", (val & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT));
+
+ val &= ~EHCI_CMD_FRAME_LIST_SIZE_MASK; /* 00 = 1024 */
+ }
+ if (val & ~EHCI_CMD_MASK)
+ Log(("Unknown bits %#x are set!!!\n", val & ~0x0003000f));
+
+ uint32_t old_cmd = pThis->cmd;
+#ifdef IN_RING3
+ pThis->cmd = val;
+#endif
+
+ if (val & EHCI_CMD_RESET)
+ {
+#ifdef IN_RING3
+ LogRel(("EHCI: Hardware reset\n"));
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else if (val & EHCI_CMD_SOFT_RESET)
+ {
+#ifdef IN_RING3
+ LogRel(("EHCI: Software reset\n"));
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_SUSPEND, false /* N/A */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else
+ {
+ /* see what changed and take action on that. */
+ uint32_t old_state = old_cmd & EHCI_CMD_RUN;
+ uint32_t new_state = val & EHCI_CMD_RUN;
+
+ if (old_state != new_state)
+ {
+#ifdef IN_RING3
+ switch (new_state)
+ {
+ case EHCI_CMD_RUN:
+ LogRel(("EHCI: USB Operational\n"));
+ ehciR3BusStart(pDevIns, pThis, pThisCC);
+ break;
+ case 0:
+ ehciR3BusStop(pThis, pThisCC);
+ LogRel(("EHCI: USB Suspended\n"));
+ break;
+ }
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ }
+#ifndef IN_RING3
+ pThis->cmd = val;
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcStatus_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+#ifdef LOG_ENABLED
+ Log(("HcStatus_r current value %x\n", pThis->intr_status));
+ if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED)
+ Log((" STATUS_ASYNC_SCHED\n"));
+ if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED)
+ Log((" STATUS_PERIOD_SCHED\n"));
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ Log((" STATUS_RECLAMATION\n"));
+ if (pThis->intr_status & EHCI_STATUS_HCHALTED)
+ Log((" STATUS_HCHALTED\n"));
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ Log((" STATUS_INT_ON_ASYNC_ADV\n"));
+ if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ Log((" STATUS_HOST_SYSTEM_ERROR\n"));
+ if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ Log((" STATUS_FRAME_LIST_ROLLOVER\n"));
+ if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT)
+ Log((" STATUS_PORT_CHANGE_DETECT\n"));
+ if (pThis->intr_status & EHCI_STATUS_ERROR_INT)
+ Log((" STATUS_ERROR_INT\n"));
+ if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT)
+ Log((" STATUS_THRESHOLD_INT\n"));
+#endif
+ *pu32Value = pThis->intr_status;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcStatus_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+#ifdef LOG_ENABLED
+ Log(("HcStatus_w current value %x; new %x\n", pThis->intr_status, val));
+ if (val & EHCI_STATUS_ASYNC_SCHED)
+ Log((" STATUS_ASYNC_SCHED\n"));
+ if (val & EHCI_STATUS_PERIOD_SCHED)
+ Log((" STATUS_PERIOD_SCHED\n"));
+ if (val & EHCI_STATUS_RECLAMATION)
+ Log((" STATUS_RECLAMATION\n"));
+ if (val & EHCI_STATUS_HCHALTED)
+ Log((" STATUS_HCHALTED\n"));
+ if (val & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ Log((" STATUS_INT_ON_ASYNC_ADV\n"));
+ if (val & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ Log((" STATUS_HOST_SYSTEM_ERROR\n"));
+ if (val & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ Log((" STATUS_FRAME_LIST_ROLLOVER\n"));
+ if (val & EHCI_STATUS_PORT_CHANGE_DETECT)
+ Log((" STATUS_PORT_CHANGE_DETECT\n"));
+ if (val & EHCI_STATUS_ERROR_INT)
+ Log((" STATUS_ERROR_INT\n"));
+ if (val & EHCI_STATUS_THRESHOLD_INT)
+ Log((" STATUS_THRESHOLD_INT\n"));
+#endif
+ if ( (val & ~EHCI_STATUS_INTERRUPT_MASK)
+ && val != 0xffffffff /* ignore clear-all-like requests from xp. */)
+ Log(("Unknown bits %#x are set!!!\n", val & ~EHCI_STATUS_INTERRUPT_MASK));
+
+ /* Some bits are read-only */
+ val &= EHCI_STATUS_INTERRUPT_MASK;
+
+ /* "The Host Controller Driver may clear specific bits in this
+ * register by writing '1' to bit positions to be cleared"
+ */
+ ASMAtomicAndU32(&pThis->intr_status, ~val);
+ ehciUpdateInterruptLocked(pDevIns, pThis, "HcStatus_w");
+
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the USBINTR register of the host controller.
+ */
+static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ *pu32Value = pThis->intr;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBINTR register of the host controller.
+ */
+static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+#ifdef LOG_ENABLED
+ Log(("HcInterruptEnable_w -> new value %x\n", val));
+ if (val & EHCI_INTR_ENABLE_THRESHOLD)
+ Log((" INTR_ENABLE_THRESHOLD\n"));
+ if (val & EHCI_INTR_ENABLE_ERROR)
+ Log((" INTR_ENABLE_ERROR\n"));
+ if (val & EHCI_INTR_ENABLE_PORT_CHANGE)
+ Log((" INTR_ENABLE_PORT_CHANGE\n"));
+ if (val & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER)
+ Log((" INTR_ENABLE_FRAME_LIST_ROLLOVER\n"));
+ if (val & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR)
+ Log((" INTR_ENABLE_HOST_SYSTEM_ERROR\n"));
+ if (val & EHCI_INTR_ENABLE_ASYNC_ADVANCE)
+ Log((" INTR_ENABLE_ASYNC_ADVANCE\n"));
+ if (val & ~EHCI_INTR_ENABLE_MASK)
+ Log((" Illegal bits set %x!!\n", val & ~EHCI_INTR_ENABLE_MASK));
+#endif
+ VBOXSTRICTRC rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc == VINF_SUCCESS)
+ {
+ pThis->intr = val & EHCI_INTR_ENABLE_MASK;
+ ehciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w");
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ }
+ return rc;
+}
+
+/**
+ * Read the FRINDEX register of the host controller.
+ */
+static VBOXSTRICTRC HcFrameIndex_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current frame %x\n", pThis->frame_idx));
+ *pu32Value = pThis->frame_idx;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the FRINDEX register of the host controller.
+ */
+static VBOXSTRICTRC HcFrameIndex_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("pThis->frame_idx new index=%x\n", val));
+ if (!(pThis->intr_status & EHCI_STATUS_HCHALTED))
+ Log(("->>Updating the frame index while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->frame_idx, val);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the CTRLDSSEGMENT register of the host controller.
+ */
+static VBOXSTRICTRC HcControlDSSeg_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING)
+ *pu32Value = pThis->ds_segment;
+ else
+ *pu32Value = 0;
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the CTRLDSSEGMENT register of the host controller.
+ */
+static VBOXSTRICTRC HcControlDSSeg_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new base %x\n", val));
+ if (pThis->hcc_params & EHCI_HCC_PARAMS_64BITS_ADDRESSING)
+ ASMAtomicXchgU32(&pThis->ds_segment, val);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the PERIODICLISTBASE register of the host controller.
+ */
+static VBOXSTRICTRC HcPeriodicListBase_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current base %x\n", pThis->periodic_list_base));
+ *pu32Value = pThis->periodic_list_base;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the PERIODICLISTBASE register of the host controller.
+ */
+static VBOXSTRICTRC HcPeriodicListBase_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new base %x\n", val));
+ if (val & ~EHCI_PERIODIC_LIST_MASK)
+ Log(("->> Base not aligned on a 4kb boundary!!!!\n"));
+ if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED)
+ && (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE))
+ Log(("->>Updating the periodic list base while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->periodic_list_base, val & EHCI_PERIODIC_LIST_MASK);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the ASYNCLISTADDR register of the host controller.
+ */
+static VBOXSTRICTRC HcAsyncListAddr_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current base %x\n", pThis->async_list_base));
+ *pu32Value = pThis->async_list_base;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the ASYNCLISTADDR register of the host controller.
+ */
+static VBOXSTRICTRC HcAsyncListAddr_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new address %x\n", val));
+ if (val & ~EHCI_ASYNC_LIST_MASK)
+ Log(("->> Base not aligned on a 32-byte boundary!!!!\n"));
+ if ( !(pThis->intr_status & EHCI_STATUS_HCHALTED)
+ && (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE))
+ Log(("->>Updating the asynchronous list address while the controller is running!!!\n"));
+
+ ASMAtomicXchgU32(&pThis->async_list_base, val & EHCI_ASYNC_LIST_MASK);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the CONFIGFLAG register of the host controller.
+ */
+static VBOXSTRICTRC HcConfigFlag_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2Func(("current config=%x\n", pThis->config));
+ *pu32Value = pThis->config;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the CONFIGFLAG register of the host controller.
+ */
+static VBOXSTRICTRC HcConfigFlag_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ LogFunc(("new configuration routing %x\n", val & EHCI_CONFIGFLAG_ROUTING));
+ pThis->config = val & EHCI_CONFIGFLAG_MASK;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the PORTSC register of a port
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ const unsigned i = iReg - 1;
+ PEHCIHUBPORT p = &pThis->RootHub.aPorts[i];
+ RT_NOREF(pDevIns);
+
+ Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL));
+
+ if (p->fReg & EHCI_PORT_RESET)
+ {
+#ifdef IN_RING3
+ Log2Func(("port %u: Impatient guest!\n", i));
+ RTThreadYield();
+#else
+ Log2Func(("yield -> VINF_IOM_R3_MMIO_READ\n"));
+ return VINF_IOM_R3_MMIO_READ;
+#endif
+ }
+
+ *pu32Value = p->fReg;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the PORTSC register of a port
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PEHCI pThis, uint32_t iReg, uint32_t val)
+{
+ const unsigned i = iReg - 1;
+ PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[i];
+
+ if ( pPort->fReg == val
+ && !(val & EHCI_PORT_CHANGE_MASK))
+ return VINF_SUCCESS;
+
+#ifdef IN_RING3
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+
+ LogFunc(("port %d: old=%x new=%x\n", i, pPort->fReg, val));
+ Assert(!(pThis->hcs_params & EHCI_HCS_PARAMS_PORT_POWER_CONTROL));
+ Assert(pPort->fReg & EHCI_PORT_POWER);
+
+ if (val & EHCI_PORT_RESERVED)
+ Log(("Invalid bits set %x!!!\n", val & EHCI_PORT_RESERVED));
+
+ /* Write to clear any of the change bits: EHCI_PORT_CONNECT_CHANGE, EHCI_PORT_PORT_CHANGE and EHCI_PORT_OVER_CURRENT_CHANGE */
+ if (val & EHCI_PORT_CHANGE_MASK)
+ {
+ ASMAtomicAndU32(&pPort->fReg, ~(val & EHCI_PORT_CHANGE_MASK));
+ /* XP seems to need this after device detach */
+ if (!(pPort->fReg & EHCI_PORT_CURRENT_CONNECT))
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_CONNECT_CHANGE);
+ }
+
+ /* Writing the Port Enable/Disable bit as 1 has no effect; software cannot enable
+ * the port that way. Writing the bit as zero does disable the port, but does not
+ * set the corresponding 'changed' bit or trigger an interrupt.
+ */
+ if (!(val & EHCI_PORT_PORT_ENABLED) && (pPort->fReg & EHCI_PORT_PORT_ENABLED))
+ {
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_PORT_ENABLED);
+ LogFunc(("port %u: DISABLE\n", i));
+ }
+
+ if (val & EHCI_PORT_SUSPEND)
+ LogFunc(("port %u: SUSPEND - not implemented correctly!!!\n", i));
+
+ if (val & EHCI_PORT_RESET)
+ {
+ Log2Func(("Reset port\n"));
+ if ( ehciR3RhPortSetIfConnected(&pThis->RootHub, i, val & EHCI_PORT_RESET) )
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, EHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */, NULL /* sync */, pThis, pVM);
+ ehciR3PortResetDone(pThis, pThisCC, EHCI_PORT_2_VUSB_PORT(i), VINF_SUCCESS);
+ }
+ else if (pPort->fReg & EHCI_PORT_RESET)
+ {
+ /* the guest is getting impatient. */
+ Log2Func(("port %u: Impatient guest!\n", i));
+ RTThreadYield();
+ }
+ }
+
+ /* EHCI_PORT_POWER ignored as we don't support this in HCS_PARAMS */
+ /* EHCI_PORT_INDICATOR ignored as we don't support this in HCS_PARAMS */
+ /* EHCI_PORT_TEST_CONTROL_MASK ignored */
+ ASMAtomicAndU32(&pPort->fReg, ~EHCI_PORT_WAKE_MASK);
+ ASMAtomicOrU32(&pPort->fReg, (val & EHCI_PORT_WAKE_MASK));
+ return VINF_SUCCESS;
+
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+/**
+ * Register descriptor table
+ */
+static const EHCIOPREG g_aOpRegs[] =
+{
+ {"HcCommand" , HcCommand_r, HcCommand_w},
+ {"HcStatus", HcStatus_r, HcStatus_w},
+ {"HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w},
+ {"HcFrameIndex", HcFrameIndex_r, HcFrameIndex_w},
+ {"HcControlDSSeg", HcControlDSSeg_r, HcControlDSSeg_w},
+ {"HcPeriodicListBase", HcPeriodicListBase_r, HcPeriodicListBase_w},
+ {"HcAsyncListAddr", HcAsyncListAddr_r, HcAsyncListAddr_w}
+};
+
+/**
+ * Register descriptor table 2
+ * (Starting at offset 0x40)
+ */
+static const EHCIOPREG g_aOpRegs2[] =
+{
+ {"HcConfigFlag", HcConfigFlag_r, HcConfigFlag_w},
+
+ /* The number of port status register depends on the definition
+ * of EHCI_NDP_MAX macro
+ */
+ {"HcPortStatusCtrl[0]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[1]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[2]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[3]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[4]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[5]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[6]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[7]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[8]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[9]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[10]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[11]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[12]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[13]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+ {"HcPortStatusCtrl[14]", HcPortStatusCtrl_r, HcPortStatusCtrl_w},
+};
+
+/* Quick way to determine how many op regs are valid. Since at least one port must
+ * be configured (and no more than 15), there will be between 2 and 16 registers.
+ */
+#define NUM_OP_REGS2(pehci) (1 + EHCI_NDP_CFG(pehci))
+
+AssertCompile(RT_ELEMENTS(g_aOpRegs2) > 1);
+AssertCompile(RT_ELEMENTS(g_aOpRegs2) <= 16);
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. }
+ *
+ * @note We only accept 32-bit reads that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ehciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ RT_NOREF(pvUser);
+
+ Log2Func(("%RGp size=%d\n", off, cb));
+
+ if (off < EHCI_CAPS_REG_SIZE)
+ {
+ switch (off)
+ {
+ case 0x0: /* CAPLENGTH */
+ /* read CAPLENGTH + HCIVERSION in one go */
+ if (cb == 4)
+ {
+ *(uint32_t *)pv = (pThis->hci_version << 16) | pThis->cap_length;
+ return VINF_SUCCESS;
+ }
+
+ AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint8_t *)pv = pThis->cap_length;
+ break;
+
+ case 0x2: /* HCIVERSION */
+ AssertReturn(cb == 2, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint16_t *)pv = pThis->hci_version;
+ break;
+
+ case 0x4: /* HCSPARAMS (structural) */
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = pThis->hcs_params;
+ break;
+
+ case 0x8: /* HCCPARAMS (caps) */
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = pThis->hcc_params;
+ break;
+
+ case 0x9: /* one byte HCIPARAMS read (XP; EHCI extended capability offset) */
+ AssertReturn(cb == 1, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint8_t *)pv = (uint8_t)(pThis->hcc_params >> 8);
+ break;
+
+ case 0xC: /* HCSP-PORTROUTE (60 bits) */
+ case 0x10:
+ AssertReturn(cb == 4, VINF_IOM_MMIO_UNUSED_FF);
+ *(uint32_t *)pv = 0;
+ break;
+
+ default:
+ LogFunc(("Trying to read register %#x!!!\n", off));
+ return VINF_IOM_MMIO_UNUSED_FF;
+ }
+ Log2Func(("%RGp size=%d -> val=%x\n", off, cb, *(uint32_t *)pv));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access.
+ */
+ if (cb != sizeof(uint32_t))
+ {
+ Log2Func(("Bad read size!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_IOM_MMIO_UNUSED_FF; /* No idea what really would happen... */
+ }
+ if (off & 0x3)
+ {
+ Log2Func(("Unaligned read!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_IOM_MMIO_UNUSED_FF;
+ }
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ uint32_t iReg = (off - pThis->cap_length) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv);
+ Log2Func(("%RGp size=%d -> val=%x (rc=%d)\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ }
+ else if (iReg >= 0x10) /* 0x40 */
+ {
+ iReg -= 0x10;
+ if (iReg < NUM_OP_REGS2(pThis))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs2[iReg];
+ rc = pReg->pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv);
+ Log2Func(("%RGp size=%d -> val=%x (rc=%d)*\n", off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rc)));
+ }
+ else
+ {
+ LogFunc(("Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis)));
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+ }
+ else
+ {
+ LogFunc(("Trying to read register %u/%u (2)!!!\n", iReg, RT_ELEMENTS(g_aOpRegs)));
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register. }
+ *
+ * @note We only accept 32-bit writes that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ehciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ RT_NOREF(pvUser);
+
+ Log2Func(("%RGp %x size=%d\n", off, *(uint32_t *)pv, cb));
+
+ if (off < EHCI_CAPS_REG_SIZE)
+ {
+ /* These are read-only */
+ LogFunc(("Trying to write to register %#x!!!\n", off));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access.
+ */
+ if (cb != sizeof(uint32_t))
+ {
+ Log2Func(("Bad write size!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_SUCCESS;
+ }
+ if (off & 0x3)
+ {
+ Log2Func(("Unaligned write!!! off=%RGp cb=%d\n", off, cb));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ uint32_t iReg = (off - pThis->cap_length) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs[iReg];
+ rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv);
+ }
+ else if (iReg >= 0x10) /* 0x40 */
+ {
+ iReg -= 0x10;
+ if (iReg < NUM_OP_REGS2(pThis))
+ {
+ const EHCIOPREG *pReg = &g_aOpRegs2[iReg];
+ rc = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv);
+ }
+ else
+ {
+ LogFunc(("Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS2(pThis)));
+ rc = VINF_SUCCESS; /* ignore the invalid write */
+ }
+ }
+ else
+ {
+ LogFunc(("Trying to write to register %u/%u!!! (2)\n", iReg, RT_ELEMENTS(g_aOpRegs)));
+ rc = VINF_SUCCESS; /* ignore the invalid write */
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) ehciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ LogFlowFunc(("\n"));
+ return pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) ehciLoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ LogFlowFunc(("\n"));
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ if (uVersion == EHCI_SAVED_STATE_VERSION)
+ {
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFields[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ else if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL)
+ {
+ static SSMFIELD const g_aEhciFieldsPreTimerRemoval[] =
+ {
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[8].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[9].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[10].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[11].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[12].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[13].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[14].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aEhciFieldsPreTimerRemoval[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(EHCI_NDP_CFG(pThis) <= EHCI_NDP_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+ else if (uVersion == EHCI_SAVED_STATE_VERSION_8PORTS)
+ {
+ static SSMFIELD const s_aEhciFields8Ports[] =
+ {
+ SSMFIELD_ENTRY( EHCI, fAsyncTraversalTimerActive),
+ SSMFIELD_ENTRY( EHCI, SofTime),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.unused),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( EHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( EHCI, cap_length),
+ SSMFIELD_ENTRY( EHCI, hci_version),
+ SSMFIELD_ENTRY( EHCI, hcs_params),
+ SSMFIELD_ENTRY( EHCI, hcc_params),
+ SSMFIELD_ENTRY( EHCI, cmd),
+ SSMFIELD_ENTRY( EHCI, intr_status),
+ SSMFIELD_ENTRY( EHCI, intr),
+ SSMFIELD_ENTRY( EHCI, frame_idx),
+ SSMFIELD_ENTRY( EHCI, ds_segment),
+ SSMFIELD_ENTRY( EHCI, periodic_list_base),
+ SSMFIELD_ENTRY( EHCI, async_list_base),
+ SSMFIELD_ENTRY( EHCI, config),
+ SSMFIELD_ENTRY( EHCI, uIrqInterval),
+ SSMFIELD_ENTRY( EHCI, HcFmNumber),
+ SSMFIELD_ENTRY( EHCI, uFramesPerTimerCall),
+ SSMFIELD_ENTRY_TERM()
+ };
+
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &s_aEhciFields8Ports[0], NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+ AssertReturn(EHCI_NDP_CFG(pThis) == 8, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+ }
+ else
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /*
+ * The EOF timer changed from one to two in version 4 of the saved state,
+ * then was dropped entirely in version 7.
+ *
+ * Note! Looks like someone remove the code that dealt with versions 1 thru 4,
+ * without adjust the above comment.
+ */
+ if (uVersion == EHCI_SAVED_STATE_VERSION_PRE_TIMER_REMOVAL)
+ {
+ bool fActive1 = false;
+ pHlp->pfnTimerSkipLoad(pSSM, &fActive1);
+ bool fActive2 = false;
+ pHlp->pfnTimerSkipLoad(pSSM, &fActive2);
+ bool fNoSync = false;
+ rc = pHlp->pfnSSMGetBool(pSSM, &fNoSync);
+ if ( RT_SUCCESS(rc)
+ && (fActive1 || fActive2))
+ pThis->fBusStarted = true;
+ else
+ pThis->fBusStarted = false;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, Dumps EHCI control registers.}
+ */
+static DECLCALLBACK(void) ehciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ unsigned uPort;
+
+ /* Command register */
+ pHlp->pfnPrintf(pHlp, "USBCMD: %x\n", pThis->cmd);
+ if (pThis->cmd & EHCI_CMD_RUN)
+ pHlp->pfnPrintf(pHlp, " CMD_RUN\n");
+ if (pThis->cmd & EHCI_CMD_RESET)
+ pHlp->pfnPrintf(pHlp, " CMD_RESET\n");
+ if (pThis->cmd & EHCI_CMD_PERIODIC_SCHED_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_PERIODIC_SCHED_ENABLE\n");
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_ENABLE\n");
+ if (pThis->cmd & EHCI_CMD_INT_ON_ADVANCE_DOORBELL)
+ pHlp->pfnPrintf(pHlp, " CMD_INT_ON_ADVANCE_DOORBELL\n");
+ if (pThis->cmd & EHCI_CMD_SOFT_RESET)
+ pHlp->pfnPrintf(pHlp, " CMD_SOFT_RESET\n");
+ if (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_ENABLE)
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_ENABLE\n");
+
+ pHlp->pfnPrintf(pHlp, " CMD_FRAME_LIST_SIZE %d\n", (pThis->cmd & EHCI_CMD_FRAME_LIST_SIZE_MASK) >> EHCI_CMD_FRAME_LIST_SIZE_SHIFT);
+ pHlp->pfnPrintf(pHlp, " CMD_ASYNC_SCHED_PARK_MODE_COUNT %d\n", (pThis->cmd & EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_MASK) >> EHCI_CMD_ASYNC_SCHED_PARK_MODE_COUNT_SHIFT);
+ pHlp->pfnPrintf(pHlp, " CMD_INTERRUPT_THRESHOLD %d\n", (pThis->cmd & EHCI_CMD_INTERRUPT_THRESHOLD_MASK) >> EHCI_CMD_INTERRUPT_THRESHOLD_SHIFT);
+
+ /* Status register */
+ pHlp->pfnPrintf(pHlp, "USBSTS: %x\n", pThis->intr_status);
+ if (pThis->intr_status & EHCI_STATUS_ASYNC_SCHED)
+ pHlp->pfnPrintf(pHlp, " STATUS_ASYNC_SCHED\n");
+ if (pThis->intr_status & EHCI_STATUS_PERIOD_SCHED)
+ pHlp->pfnPrintf(pHlp, " STATUS_PERIOD_SCHED\n");
+ if (pThis->intr_status & EHCI_STATUS_RECLAMATION)
+ pHlp->pfnPrintf(pHlp, " STATUS_RECLAMATION\n");
+ if (pThis->intr_status & EHCI_STATUS_HCHALTED)
+ pHlp->pfnPrintf(pHlp, " STATUS_HCHALTED\n");
+ if (pThis->intr_status & EHCI_STATUS_INT_ON_ASYNC_ADV)
+ pHlp->pfnPrintf(pHlp, " STATUS_INT_ON_ASYNC_ADV\n");
+ if (pThis->intr_status & EHCI_STATUS_HOST_SYSTEM_ERROR)
+ pHlp->pfnPrintf(pHlp, " STATUS_HOST_SYSTEM_ERROR\n");
+ if (pThis->intr_status & EHCI_STATUS_FRAME_LIST_ROLLOVER)
+ pHlp->pfnPrintf(pHlp, " STATUS_FRAME_LIST_ROLLOVER\n");
+ if (pThis->intr_status & EHCI_STATUS_PORT_CHANGE_DETECT)
+ pHlp->pfnPrintf(pHlp, " STATUS_PORT_CHANGE_DETECT\n");
+ if (pThis->intr_status & EHCI_STATUS_ERROR_INT)
+ pHlp->pfnPrintf(pHlp, " STATUS_ERROR_INT\n");
+ if (pThis->intr_status & EHCI_STATUS_THRESHOLD_INT)
+ pHlp->pfnPrintf(pHlp, " STATUS_THRESHOLD_INT\n");
+
+ /* Interrupt enable register */
+ pHlp->pfnPrintf(pHlp, "USBINTR: %x\n", pThis->intr);
+ if (pThis->intr & EHCI_INTR_ENABLE_THRESHOLD)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_THRESHOLD\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_ERROR)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ERROR\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_PORT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_PORT_CHANGE\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_FRAME_LIST_ROLLOVER)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_FRAME_LIST_ROLLOVER\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_HOST_SYSTEM_ERROR)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_HOST_SYSTEM_ERROR\n");
+ if (pThis->intr & EHCI_INTR_ENABLE_ASYNC_ADVANCE)
+ pHlp->pfnPrintf(pHlp, " INTR_ENABLE_ASYNC_ADVANCE\n");
+ if (pThis->intr & ~EHCI_INTR_ENABLE_MASK)
+ pHlp->pfnPrintf(pHlp, " Illegal bits set %x!!\n", pThis->intr & ~EHCI_INTR_ENABLE_MASK);
+
+ /* Frame index register */
+ pHlp->pfnPrintf(pHlp, "FRINDEX: %x\n", pThis->frame_idx);
+
+ /* Control data structure segment */
+ pHlp->pfnPrintf(pHlp, "CTRLDSSEGMENT: %RX32\n", pThis->ds_segment);
+
+ /* Periodic frame list base address register */
+ pHlp->pfnPrintf(pHlp, "PERIODICLISTBASE: %RX32\n", pThis->periodic_list_base);
+
+ /* Current asynchronous list address register */
+ pHlp->pfnPrintf(pHlp, "ASYNCLISTADDR: %RX32\n", pThis->async_list_base);
+
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ for (uPort = 0; uPort < EHCI_NDP_CFG(pThis); ++uPort)
+ {
+ PEHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort];
+ pHlp->pfnPrintf(pHlp, "PORTSC for port %u:\n", uPort);
+ if (pPort->fReg & EHCI_PORT_CURRENT_CONNECT)
+ pHlp->pfnPrintf(pHlp, " PORT_CURRENT_CONNECT\n");
+ if (pPort->fReg & EHCI_PORT_CONNECT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_CONNECT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_PORT_ENABLED)
+ pHlp->pfnPrintf(pHlp, " PORT_PORT_ENABLED\n");
+ if (pPort->fReg & EHCI_PORT_PORT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_PORT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_OVER_CURRENT_ACTIVE)
+ pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_ACTIVE\n");
+ if (pPort->fReg & EHCI_PORT_OVER_CURRENT_CHANGE)
+ pHlp->pfnPrintf(pHlp, " PORT_OVER_CURRENT_CHANGE\n");
+ if (pPort->fReg & EHCI_PORT_FORCE_PORT_RESUME)
+ pHlp->pfnPrintf(pHlp, " PORT_FORCE_PORT_RESUME\n");
+ if (pPort->fReg & EHCI_PORT_SUSPEND)
+ pHlp->pfnPrintf(pHlp, " PORT_SUSPEND\n");
+ if (pPort->fReg & EHCI_PORT_RESET)
+ pHlp->pfnPrintf(pHlp, " PORT_RESET\n");
+ pHlp->pfnPrintf(pHlp, " LINE_STATUS: ");
+ switch ((pPort->fReg & EHCI_PORT_LINE_STATUS_MASK) >> EHCI_PORT_LINE_STATUS_SHIFT)
+ {
+ case 0:
+ pHlp->pfnPrintf(pHlp, " SE0 (0), not low-speed\n");
+ break;
+ case 1:
+ pHlp->pfnPrintf(pHlp, " K-state (1), low-speed device\n");
+ break;
+ case 2:
+ pHlp->pfnPrintf(pHlp, " J-state (2), not low-speed\n");
+ break;
+ default:
+ case 3:
+ pHlp->pfnPrintf(pHlp, " Undefined (3)\n");
+ break;
+ }
+ if (pPort->fReg & EHCI_PORT_POWER)
+ pHlp->pfnPrintf(pHlp, " PORT_POWER\n");
+ if (pPort->fReg & EHCI_PORT_OWNER)
+ pHlp->pfnPrintf(pHlp, " PORT_OWNER (1 = owned by companion HC)\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_ON_CONNECT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_CONNECT_ENABLE\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_ON_DISCONNECT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_ON_DISCONNECT_ENABLE\n");
+ if (pPort->fReg & EHCI_PORT_WAKE_OVER_CURRENT_ENABLE)
+ pHlp->pfnPrintf(pHlp, " PORT_WAKE_OVER_CURRENT_ENABLE\n");
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) ehciR3Reset(PPDMDEVINS pDevIns)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ /*
+ * There is no distinction between cold boot, warm reboot and software reboots,
+ * all of these are treated as cold boots. We are also doing the initialization
+ * job of a BIOS or SMM driver.
+ *
+ * Important: Don't confuse UsbReset with hardware reset. Hardware reset is
+ * just one way of getting into the UsbReset state.
+ */
+ ehciR3BusStop(pThis, pThisCC);
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, true /* reset devices */);
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ehciR3Resume(PPDMDEVINS pDevIns)
+{
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ /* Restart the frame thread if the timer is active. */
+ if (pThis->fBusStarted)
+ {
+ LogFlowFunc(("Bus was active, restart frame thread\n"));
+ RTSemEventMultiSignal(pThisCC->hSemEventFrame);
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) ehciR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ LogFlowFunc(("\n"));
+
+ if (pThisCC->hSemEventFrame != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThisCC->hSemEventFrame);
+ pThisCC->hSemEventFrame = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (pThisCC->hSemEventFrameStopped != NIL_RTSEMEVENTMULTI)
+ {
+ RTSemEventMultiDestroy(pThisCC->hSemEventFrameStopped);
+ pThisCC->hSemEventFrameStopped = NIL_RTSEMEVENTMULTI;
+ }
+
+ if (RTCritSectIsInitialized(&pThisCC->CritSect))
+ RTCritSectDelete(&pThisCC->CritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CsIrq);
+
+ /*
+ * Tear down the per endpoint in-flight tracking...
+ */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct,EHCI constructor}
+ */
+static DECLCALLBACK(int) ehciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+ PEHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PEHCICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ LogFlowFunc(("\n"));
+
+ /*
+ * Read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "DefaultFrameRateKHz|Ports", "");
+
+ /* Frame rate option. */
+ int rc = pHlp->pfnCFGMQueryU32Def(pCfg, "DefaultFrameRateKHz", &pThisCC->uFrameRateDefault, EHCI_DEFAULT_TIMER_FREQ / 1000);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("EHCI configuration error: failed to read DefaultFrameRateKHz as integer"));
+
+ if ( pThisCC->uFrameRateDefault > EHCI_HARDWARE_TIMER_FREQ / 1000
+ || pThisCC->uFrameRateDefault == 0)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("EHCI configuration error: DefaultFrameRateKHz must be in range [%u,%u]"),
+ 1, EHCI_HARDWARE_TIMER_FREQ / 1000);
+
+ /* Convert to Hertz. */
+ pThisCC->uFrameRateDefault *= 1000;
+
+ /* Number of ports option. */
+ uint32_t cPorts;
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, EHCI_NDP_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("EHCI configuration error: failed to read Ports as integer"));
+
+ if (cPorts == 0 || cPorts > EHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("EHCI configuration error: Ports must be in range [%u,%u]"),
+ 1, EHCI_NDP_MAX);
+
+ /*
+ * Init instance data.
+ */
+ pThisCC->pDevIns = pDevIns;
+
+ /* Intel 82801FB/FBM USB2 controller */
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, 0x8086);
+ PDMPciDevSetDeviceId(pPciDev, 0x265C);
+ PDMPciDevSetClassProg(pPciDev, 0x20); /* EHCI */
+ PDMPciDevSetClassSub(pPciDev, 0x03);
+ PDMPciDevSetClassBase(pPciDev, 0x0c);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+#endif
+ PDMPciDevSetByte(pPciDev, 0x60, 0x20); /* serial bus release number register; 0x20 = USB 2.0 */
+ /** @todo USBLEGSUP & USBLEGCTLSTS? Legacy interface for the BIOS (0xEECP+0 & 0xEECP+4) */
+
+ pThisCC->RootHub.IBase.pfnQueryInterface = ehciR3RhQueryInterface;
+ pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ehciR3RhGetAvailablePorts;
+ pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ehciR3RhGetUSBVersions;
+ pThisCC->RootHub.IRhPort.pfnAttach = ehciR3RhAttach;
+ pThisCC->RootHub.IRhPort.pfnDetach = ehciR3RhDetach;
+ pThisCC->RootHub.IRhPort.pfnReset = ehciR3RhReset;
+ pThisCC->RootHub.IRhPort.pfnXferCompletion = ehciR3RhXferCompletion;
+ pThisCC->RootHub.IRhPort.pfnXferError = ehciR3RhXferError;
+
+ /* USB LED */
+ pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC;
+ pThisCC->RootHub.ILeds.pfnQueryStatusLed = ehciR3RhQueryStatusLed;
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x80;
+ MsiReg.iMsiNextOffset = 0x00;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ PDMPciDevSetCapabilityList(pPciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0 /*iPciRegion*/, 4096 /*cbRegion*/, PCI_ADDRESS_SPACE_MEM,
+ ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED
+ | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE,
+ "USB EHCI", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+ /* Initialize capability registers */
+ pThis->cap_length = EHCI_CAPS_REG_SIZE;
+ pThis->hci_version = 0x100;
+ /* 31:24 Reserved
+ * 23:20 Debug Port Number
+ * 19:17 Reserved
+ * 16 Port indicators (P_INDICATOR) enabled/disabled
+ * 15:12 Number of companion controllers (N_CC)
+ * 11:8 Number of ports per companion controller (N_PCC)
+ * 7 Port routing controls enabled/disabled
+ * 6:5 Reserved
+ * 4 Port power control enabled/disabled -> disabled to simplify matters!
+ * 3:0 N_PORTS; number of ports
+ */
+ /* Currently only number of ports specified */
+ pThis->hcs_params = cPorts;
+
+ /* 31:16 Reserved
+ * 15:8 EHCI extended capabilities pointer (EECP) (0x40 or greater)
+ * 7:4 Isochronous scheduling threshold
+ * 3 Reserved
+ * 2 Asynchronous schedule park capability (allow several TDs to be handled per async queue head)
+ * 1 Programmable frame list flag (0=1024 frames fixed)
+ * 0 64 bits addressability
+ */
+ pThis->hcc_params = EHCI_HCC_PARAMS_ISOCHRONOUS_CACHING | EHCI_HCC_PARAMS_ASYNC_SCHEDULE_PARKING;
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, EHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, ehciR3SaveExec, NULL,
+ NULL, ehciLoadExec, NULL);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #0.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->RootHub.IBase, &pThisCC->RootHub.pIBase, "RootHub");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n"));
+ return rc;
+ }
+ pThisCC->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIROOTHUBCONNECTOR);
+ AssertMsgReturn(pThisCC->RootHub.pIRhConn,
+ ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->RootHub.IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThisCC->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else
+ AssertLogRelMsgReturn(rc == VERR_PDM_NO_ATTACHED_DRIVER, ("Failed to attach to status driver. rc=%Rrc\n", rc), rc);
+
+ /* Set URB parameters. */
+ rc = VUSBIRhSetUrbParams(pThisCC->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to set URB parameters"));
+
+ /*
+ * Calculate the timer intervals.
+ * This ASSUMES that the VM timer doesn't change frequency during the run.
+ */
+ pThisCC->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns);
+ ehciR3CalcTimerIntervals(pThis, pThisCC, pThisCC->uFrameRateDefault);
+ LogFunc(("cTicksPerFrame=%RU64 cTicksPerUsbTick=%RU64\n", pThisCC->cTicksPerFrame, pThisCC->cTicksPerUsbTick));
+
+ pThis->fBusStarted = false;
+
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "EHCI#%uIrq", iInstance);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("EHCI: Failed to create critical section"));
+
+ rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrame);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventMultiCreate(&pThisCC->hSemEventFrameStopped);
+ AssertRCReturn(rc, rc);
+
+ rc = RTCritSectInit(&pThisCC->CritSect);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create critical section"));
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->hThreadFrame, pThisCC, ehciR3ThreadFrame,
+ ehciR3ThreadFrameWakeup, 0, RTTHREADTYPE_IO, "EhciFramer");
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("EHCI: Failed to create worker thread"));
+
+ /*
+ * Do a hardware reset.
+ */
+ ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, false /* don't reset devices */);
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThisCC->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled).");
+#endif
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ehci", "EHCI control registers.", ehciR3InfoRegs);
+
+#ifdef DEBUG_sandervl
+// g_fLogInterruptEPs = true;
+ g_fLogControlEPs = true;
+#endif
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ehciRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PEHCI pThis = PDMDEVINS_2_DATA(pDevIns, PEHCI);
+
+ int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ehciMmioWrite, ehciMmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+const PDMDEVREG g_DeviceEHCI =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "usb-ehci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(EHCI),
+ /* .cbInstanceCC = */ sizeof(EHCICC),
+ /* .cbInstanceRC = */ sizeof(EHCIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "EHCI USB controller.\n",
+#if defined(IN_RING3)
+# ifdef VBOX_IN_EXTPACK
+ /* .pszRCMod = */ "VBoxEhciRC.rc",
+ /* .pszR0Mod = */ "VBoxEhciR0.r0",
+# else
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+# endif
+ /* .pfnConstruct = */ ehciR3Construct,
+ /* .pfnDestruct = */ ehciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ehciR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ ehciR3Resume,
+ /* .pfnAttach = */ NULL,
+ /* .pfnDetach = */ NULL,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ehciRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ehciRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#ifdef VBOX_IN_EXTPACK
+extern "C" const PDMDEVREG g_DeviceXHCI;
+
+# ifdef VBOX_IN_EXTPACK_R3
+
+/**
+ * @callback_method_impl{FNPDMVBOXDEVICESREGISTER}
+ */
+extern "C" DECLEXPORT(int) VBoxDevicesRegister(PPDMDEVREGCB pCallbacks, uint32_t u32Version)
+{
+ AssertLogRelMsgReturn(u32Version >= VBOX_VERSION,
+ ("u32Version=%#x VBOX_VERSION=%#x\n", u32Version, VBOX_VERSION),
+ VERR_EXTPACK_VBOX_VERSION_MISMATCH);
+ AssertLogRelMsgReturn(pCallbacks->u32Version == PDM_DEVREG_CB_VERSION,
+ ("pCallbacks->u32Version=%#x PDM_DEVREG_CB_VERSION=%#x\n", pCallbacks->u32Version, PDM_DEVREG_CB_VERSION),
+ VERR_VERSION_MISMATCH);
+
+ int rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceEHCI);
+
+ /* EHCI and xHCI devices live in the same module. */
+ extern const PDMDEVREG g_DeviceXHCI;
+ if (RT_SUCCESS(rc))
+ rc = pCallbacks->pfnRegister(pCallbacks, &g_DeviceXHCI);
+
+ return rc;
+}
+
+# else
+
+/** Pointer to the ring-0 device registrations for VBoxEhciR0/RC. */
+static PCPDMDEVREGR0 g_apDevRegs[] =
+{
+ &g_DeviceEHCI,
+ &g_DeviceXHCI,
+};
+
+/** Module device registration record for VBoxEhciR0/RC. */
+static PDMDEVMODREGR0 g_ModDevReg =
+{
+ /* .u32Version = */ PDM_DEVMODREGR0_VERSION,
+ /* .cDevRegs = */ RT_ELEMENTS(g_apDevRegs),
+ /* .papDevRegs = */ &g_apDevRegs[0],
+ /* .hMod = */ NULL,
+ /* .ListEntry = */ { NULL, NULL },
+};
+
+
+DECLEXPORT(int) ModuleInit(void *hMod)
+{
+ LogFlow(("VBoxEhciRZ/ModuleInit: %p\n", hMod));
+ return PDMR0DeviceRegisterModule(hMod, &g_ModDevReg);
+}
+
+
+DECLEXPORT(void) ModuleTerm(void *hMod)
+{
+ LogFlow(("VBoxEhciRZ/ModuleTerm: %p\n", hMod));
+ PDMR0DeviceDeregisterModule(hMod, &g_ModDevReg);
+}
+
+# endif
+#endif /* VBOX_IN_EXTPACK */
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
+
diff --git a/src/VBox/Devices/USB/DevOHCI.cpp b/src/VBox/Devices/USB/DevOHCI.cpp
new file mode 100644
index 00000000..aa4e2357
--- /dev/null
+++ b/src/VBox/Devices/USB/DevOHCI.cpp
@@ -0,0 +1,6087 @@
+/* $Id: DevOHCI.cpp $ */
+/** @file
+ * DevOHCI - Open Host Controller Interface for USB.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_dev_ohci OHCI - Open Host Controller Interface Emulation.
+ *
+ * This component implements an OHCI USB controller. It is split roughly in
+ * to two main parts, the first part implements the register level
+ * specification of USB OHCI and the second part maintains the root hub (which
+ * is an integrated component of the device).
+ *
+ * The OHCI registers are used for the usual stuff like enabling and disabling
+ * interrupts. Since the USB time is divided in to 1ms frames and various
+ * interrupts may need to be triggered at frame boundary time, a timer-based
+ * approach was taken. Whenever the bus is enabled ohci->eof_timer will be set.
+ *
+ * The actual USB transfers are stored in main memory (along with endpoint and
+ * transfer descriptors). The ED's for all the control and bulk endpoints are
+ * found by consulting the HcControlHeadED and HcBulkHeadED registers
+ * respectively. Interrupt ED's are different, they are found by looking
+ * in the HCCA (another communication area in main memory).
+ *
+ * At the start of every frame (in function ohci_sof) we traverse all enabled
+ * ED lists and queue up as many transfers as possible. No attention is paid
+ * to control/bulk service ratios or bandwidth requirements since our USB
+ * could conceivably contain a dozen high speed busses so this would
+ * artificially limit the performance.
+ *
+ * Once we have a transfer ready to go (in function ohciR3ServiceTd) we
+ * allocate an URB on the stack, fill in all the relevant fields and submit
+ * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual
+ * USB core code (vusb.c) coordinates everything else from this point onwards.
+ *
+ * When the URB has been successfully handed to the lower level driver, our
+ * prepare callback gets called and we can remove the TD from the ED transfer
+ * list. This stops us queueing it twice while it completes.
+ * bird: no, we don't remove it because that confuses the guest! (=> crashes)
+ *
+ * Completed URBs are reaped at the end of every frame (in function
+ * ohci_frame_boundary). Our completion routine makes use of the ED and TD
+ * fields in the URB to store the physical addresses of the descriptors so
+ * that they may be modified in the roothub callbacks. Our completion
+ * routine (ohciR3RhXferCompletion) carries out a number of tasks:
+ * -# Retires the TD associated with the transfer, setting the
+ * relevant error code etc.
+ * -# Updates done-queue interrupt timer and potentially causes
+ * a writeback of the done-queue.
+ * -# If the transfer was device-to-host, we copy the data in to
+ * the host memory.
+ *
+ * As for error handling OHCI allows for 3 retries before failing a transfer,
+ * an error count is stored in each transfer descriptor. A halt flag is also
+ * stored in the transfer descriptor. That allows for ED's to be disabled
+ * without stopping the bus and de-queuing them.
+ *
+ * When the bus is started and stopped we call VUSBIDevPowerOn/Off() on our
+ * roothub to indicate it's powering up and powering down. Whenever we power
+ * down, the USB core makes sure to synchronously complete all outstanding
+ * requests so that the OHCI is never seen in an inconsistent state by the
+ * guest OS (Transfers are not meant to be unlinked until they've actually
+ * completed, but we can't do that unless we work synchronously, so we just
+ * have to fake it).
+ * bird: we do work synchronously now, anything causes guest crashes.
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_OHCI
+#include <VBox/pci.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/mm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/AssertGuest.h>
+#include <iprt/assert.h>
+#include <iprt/string.h>
+#include <iprt/asm.h>
+#include <iprt/asm-math.h>
+#include <iprt/semaphore.h>
+#include <iprt/critsect.h>
+#include <iprt/param.h>
+#ifdef IN_RING3
+# include <iprt/alloca.h>
+# include <iprt/mem.h>
+# include <iprt/thread.h>
+# include <iprt/uuid.h>
+#endif
+#include <VBox/vusb.h>
+#include "VBoxDD.h"
+
+
+#define VBOX_WITH_OHCI_PHYS_READ_CACHE
+//#define VBOX_WITH_OHCI_PHYS_READ_STATS
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** The current saved state version. */
+#define OHCI_SAVED_STATE_VERSION OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER
+/** The current saved state version.
+ * @since 6.1.0beta3/rc1 */
+#define OHCI_SAVED_STATE_VERSION_NO_EOF_TIMER 6
+/** The current saved with the start-of-frame timer.
+ * @since 4.3.x */
+#define OHCI_SAVED_STATE_VERSION_EOF_TIMER 5
+/** The saved state with support of up to 8 ports.
+ * @since 3.1 or so */
+#define OHCI_SAVED_STATE_VERSION_8PORTS 4
+
+
+/** Maximum supported number of Downstream Ports on the root hub. 15 ports
+ * is the maximum defined by the OHCI spec. Must match the number of status
+ * register words to the 'opreg' array.
+ */
+#define OHCI_NDP_MAX 15
+
+/** Default NDP, chosen to be compatible with everything. */
+#define OHCI_NDP_DEFAULT 12
+
+/* Macro to query the number of currently configured ports. */
+#define OHCI_NDP_CFG(pohci) ((pohci)->RootHub.desc_a & OHCI_RHA_NDP)
+/** Macro to convert a EHCI port index (zero based) to a VUSB roothub port ID (one based). */
+#define OHCI_PORT_2_VUSB_PORT(a_uPort) ((a_uPort) + 1)
+
+/** Pointer to OHCI device data. */
+typedef struct OHCI *POHCI;
+/** Read-only pointer to the OHCI device data. */
+typedef struct OHCI const *PCOHCI;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * Host controller transfer descriptor data.
+ */
+typedef struct VUSBURBHCITDINT
+{
+ /** Type of TD. */
+ uint32_t TdType;
+ /** The address of the */
+ RTGCPHYS32 TdAddr;
+ /** A copy of the TD. */
+ uint32_t TdCopy[16];
+} VUSBURBHCITDINT;
+
+/**
+ * The host controller data associated with each URB.
+ */
+typedef struct VUSBURBHCIINT
+{
+ /** The endpoint descriptor address. */
+ RTGCPHYS32 EdAddr;
+ /** Number of Tds in the array. */
+ uint32_t cTds;
+ /** When this URB was created.
+ * (Used for isochronous frames and for logging.) */
+ uint32_t u32FrameNo;
+ /** Flag indicating that the TDs have been unlinked. */
+ bool fUnlinked;
+} VUSBURBHCIINT;
+#endif
+
+/**
+ * An OHCI root hub port.
+ */
+typedef struct OHCIHUBPORT
+{
+ /** The port register. */
+ uint32_t fReg;
+ /** Flag whether there is a device attached to the port. */
+ bool fAttached;
+ bool afPadding[3];
+} OHCIHUBPORT;
+/** Pointer to an OHCI hub port. */
+typedef OHCIHUBPORT *POHCIHUBPORT;
+
+/**
+ * The OHCI root hub, shared.
+ */
+typedef struct OHCIROOTHUB
+{
+ uint32_t status;
+ uint32_t desc_a;
+ uint32_t desc_b;
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment0; /**< Align aPorts on a 8 byte boundary. */
+#endif
+ OHCIHUBPORT aPorts[OHCI_NDP_MAX];
+} OHCIROOTHUB;
+/** Pointer to the OHCI root hub. */
+typedef OHCIROOTHUB *POHCIROOTHUB;
+
+
+/**
+ * The OHCI root hub, ring-3 data.
+ *
+ * @implements PDMIBASE
+ * @implements VUSBIROOTHUBPORT
+ * @implements PDMILEDPORTS
+ */
+typedef struct OHCIROOTHUBR3
+{
+ /** Pointer to the base interface of the VUSB RootHub. */
+ R3PTRTYPE(PPDMIBASE) pIBase;
+ /** Pointer to the connector interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn;
+ /** The base interface exposed to the roothub driver. */
+ PDMIBASE IBase;
+ /** The roothub port interface exposed to the roothub driver. */
+ VUSBIROOTHUBPORT IRhPort;
+
+ /** The LED. */
+ PDMLED Led;
+ /** The LED ports. */
+ PDMILEDPORTS ILeds;
+ /** Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+
+ OHCIHUBPORT aPorts[OHCI_NDP_MAX];
+ R3PTRTYPE(POHCI) pOhci;
+} OHCIROOTHUBR3;
+/** Pointer to the OHCI ring-3 root hub data. */
+typedef OHCIROOTHUBR3 *POHCIROOTHUBR3;
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+typedef struct OHCIPAGECACHE
+{
+ /** Last read physical page address. */
+ RTGCPHYS GCPhysReadCacheAddr;
+ /** Copy of last read physical page. */
+ uint8_t abPhysReadCache[GUEST_PAGE_SIZE];
+} OHCIPAGECACHE;
+typedef OHCIPAGECACHE *POHCIPAGECACHE;
+#endif
+
+/**
+ * OHCI device data, shared.
+ */
+typedef struct OHCI
+{
+ /** Start of current frame. */
+ uint64_t SofTime;
+ /** done queue interrupt counter */
+ uint32_t dqic : 3;
+ /** frame number overflow. */
+ uint32_t fno : 1;
+
+ /** Align roothub structure on a 8-byte boundary. */
+ uint32_t u32Alignment0;
+ /** Root hub device, shared data. */
+ OHCIROOTHUB RootHub;
+
+ /* OHCI registers */
+
+ /** @name Control partition
+ * @{ */
+ /** HcControl. */
+ uint32_t ctl;
+ /** HcCommandStatus. */
+ uint32_t status;
+ /** HcInterruptStatus. */
+ uint32_t intr_status;
+ /** HcInterruptEnabled. */
+ uint32_t intr;
+ /** @} */
+
+ /** @name Memory pointer partition
+ * @{ */
+ /** HcHCCA. */
+ uint32_t hcca;
+ /** HcPeriodCurrentEd. */
+ uint32_t per_cur;
+ /** HcControlCurrentED. */
+ uint32_t ctrl_cur;
+ /** HcControlHeadED. */
+ uint32_t ctrl_head;
+ /** HcBlockCurrendED. */
+ uint32_t bulk_cur;
+ /** HcBlockHeadED. */
+ uint32_t bulk_head;
+ /** HcDoneHead. */
+ uint32_t done;
+ /** @} */
+
+ /** @name Frame counter partition
+ * @{ */
+ /** HcFmInterval.FSMPS - FSLargestDataPacket */
+ uint32_t fsmps : 15;
+ /** HcFmInterval.FIT - FrameItervalToggle */
+ uint32_t fit : 1;
+ /** HcFmInterval.FI - FrameInterval */
+ uint32_t fi : 14;
+ /** HcFmRemaining.FRT - toggle bit. */
+ uint32_t frt : 1;
+ /** HcFmNumber.
+ * @remark The register size is 16-bit, but for debugging and performance
+ * reasons we maintain a 32-bit counter. */
+ uint32_t HcFmNumber;
+ /** HcPeriodicStart */
+ uint32_t pstart;
+ /** @} */
+
+ /** This member and all the following are not part of saved state. */
+ uint64_t SavedStateEnd;
+
+ /** The number of virtual time ticks per frame. */
+ uint64_t cTicksPerFrame;
+ /** The number of virtual time ticks per USB bus tick. */
+ uint64_t cTicksPerUsbTick;
+
+ /** Detected canceled isochronous URBs. */
+ STAMCOUNTER StatCanceledIsocUrbs;
+ /** Detected canceled general URBs. */
+ STAMCOUNTER StatCanceledGenUrbs;
+ /** Dropped URBs (endpoint halted, or URB canceled). */
+ STAMCOUNTER StatDroppedUrbs;
+
+ /** VM timer frequency used for frame timer calculations. */
+ uint64_t u64TimerHz;
+ /** Idle detection flag; must be cleared at start of frame */
+ bool fIdle;
+ /** A flag indicating that the bulk list may have in-flight URBs. */
+ bool fBulkNeedsCleaning;
+
+ bool afAlignment3[2];
+ uint32_t Alignment4; /**< Align size on a 8 byte boundary. */
+
+ /** Critical section synchronising interrupt handling. */
+ PDMCRITSECT CsIrq;
+
+ /** The MMIO region handle. */
+ IOMMMIOHANDLE hMmio;
+} OHCI;
+
+
+/**
+ * OHCI device data, ring-3.
+ */
+typedef struct OHCIR3
+{
+ /** The root hub, ring-3 portion. */
+ OHCIROOTHUBR3 RootHub;
+ /** Pointer to the device instance - R3 ptr. */
+ PPDMDEVINSR3 pDevInsR3;
+
+ /** Number of in-flight TDs. */
+ unsigned cInFlight;
+ unsigned Alignment0; /**< Align aInFlight on a 8 byte boundary. */
+ /** Array of in-flight TDs. */
+ struct ohci_td_in_flight
+ {
+ /** Address of the transport descriptor. */
+ uint32_t GCPhysTD;
+ /** Flag indicating an inactive (not-linked) URB. */
+ bool fInactive;
+ /** Pointer to the URB. */
+ R3PTRTYPE(PVUSBURB) pUrb;
+ } aInFlight[257];
+
+#if HC_ARCH_BITS == 32
+ uint32_t Alignment1;
+#endif
+
+ /** Number of in-done-queue TDs. */
+ unsigned cInDoneQueue;
+ /** Array of in-done-queue TDs. */
+ struct ohci_td_in_done_queue
+ {
+ /** Address of the transport descriptor. */
+ uint32_t GCPhysTD;
+ } aInDoneQueue[64];
+ /** When the tail of the done queue was added.
+ * Used to calculate the age of the done queue. */
+ uint32_t u32FmDoneQueueTail;
+#if R3_ARCH_BITS == 32
+ /** Align pLoad, the stats and the struct size correctly. */
+ uint32_t Alignment2;
+#endif
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /** Last read physical page for caching ED reads in the framer thread. */
+ OHCIPAGECACHE CacheED;
+ /** Last read physical page for caching TD reads in the framer thread. */
+ OHCIPAGECACHE CacheTD;
+#endif
+
+ /** Critical section to synchronize the framer and URB completion handler. */
+ RTCRITSECT CritSect;
+
+ /** The restored periodic frame rate. */
+ uint32_t uRestoredPeriodicFrameRate;
+} OHCIR3;
+/** Pointer to ring-3 OHCI state. */
+typedef OHCIR3 *POHCIR3;
+
+/**
+ * OHCI device data, ring-0.
+ */
+typedef struct OHCIR0
+{
+ uint32_t uUnused;
+} OHCIR0;
+/** Pointer to ring-0 OHCI state. */
+typedef OHCIR0 *POHCIR0;
+
+
+/**
+ * OHCI device data, raw-mode.
+ */
+typedef struct OHCIRC
+{
+ uint32_t uUnused;
+} OHCIRC;
+/** Pointer to raw-mode OHCI state. */
+typedef OHCIRC *POHCIRC;
+
+
+/** @typedef OHCICC
+ * The instance data for the current context. */
+typedef CTX_SUFF(OHCI) OHCICC;
+/** @typedef POHCICC
+ * Pointer to the instance data for the current context. */
+typedef CTX_SUFF(POHCI) POHCICC;
+
+
+/** Standard OHCI bus speed */
+#define OHCI_DEFAULT_TIMER_FREQ 1000
+
+/** Host Controller Communications Area
+ * @{ */
+#define OHCI_HCCA_NUM_INTR 32
+#define OHCI_HCCA_OFS (OHCI_HCCA_NUM_INTR * sizeof(uint32_t))
+typedef struct OCHIHCCA
+{
+ uint16_t frame;
+ uint16_t pad;
+ uint32_t done;
+} OCHIHCCA;
+AssertCompileSize(OCHIHCCA, 8);
+/** @} */
+
+/** @name OHCI Endpoint Descriptor
+ * @{ */
+
+#define ED_PTR_MASK (~(uint32_t)0xf)
+#define ED_HWINFO_MPS 0x07ff0000
+#define ED_HWINFO_ISO RT_BIT(15)
+#define ED_HWINFO_SKIP RT_BIT(14)
+#define ED_HWINFO_LOWSPEED RT_BIT(13)
+#define ED_HWINFO_IN RT_BIT(12)
+#define ED_HWINFO_OUT RT_BIT(11)
+#define ED_HWINFO_DIR (RT_BIT(11) | RT_BIT(12))
+#define ED_HWINFO_ENDPOINT 0x780 /* 4 bits */
+#define ED_HWINFO_ENDPOINT_SHIFT 7
+#define ED_HWINFO_FUNCTION 0x7f /* 7 bits */
+#define ED_HEAD_CARRY RT_BIT(1)
+#define ED_HEAD_HALTED RT_BIT(0)
+
+/**
+ * OHCI Endpoint Descriptor.
+ */
+typedef struct OHCIED
+{
+ /** Flags and stuff. */
+ uint32_t hwinfo;
+ /** TailP - TD Queue Tail pointer. Bits 0-3 ignored / preserved. */
+ uint32_t TailP;
+ /** HeadP - TD Queue head pointer. Bit 0 - Halted, Bit 1 - toggleCarry. Bit 2&3 - 0. */
+ uint32_t HeadP;
+ /** NextED - Next Endpoint Descriptor. Bits 0-3 ignored / preserved. */
+ uint32_t NextED;
+} OHCIED, *POHCIED;
+typedef const OHCIED *PCOHCIED;
+/** @} */
+AssertCompileSize(OHCIED, 16);
+
+
+/** @name Completion Codes
+ * @{ */
+#define OHCI_CC_NO_ERROR (UINT32_C(0x00) << 28)
+#define OHCI_CC_CRC (UINT32_C(0x01) << 28)
+#define OHCI_CC_STALL (UINT32_C(0x04) << 28)
+#define OHCI_CC_DEVICE_NOT_RESPONDING (UINT32_C(0x05) << 28)
+#define OHCI_CC_DNR OHCI_CC_DEVICE_NOT_RESPONDING
+#define OHCI_CC_PID_CHECK_FAILURE (UINT32_C(0x06) << 28)
+#define OHCI_CC_UNEXPECTED_PID (UINT32_C(0x07) << 28)
+#define OHCI_CC_DATA_OVERRUN (UINT32_C(0x08) << 28)
+#define OHCI_CC_DATA_UNDERRUN (UINT32_C(0x09) << 28)
+/* 0x0a..0x0b - reserved */
+#define OHCI_CC_BUFFER_OVERRUN (UINT32_C(0x0c) << 28)
+#define OHCI_CC_BUFFER_UNDERRUN (UINT32_C(0x0d) << 28)
+#define OHCI_CC_NOT_ACCESSED_0 (UINT32_C(0x0e) << 28)
+#define OHCI_CC_NOT_ACCESSED_1 (UINT32_C(0x0f) << 28)
+/** @} */
+
+
+/** @name OHCI General transfer descriptor
+ * @{ */
+
+/** Error count (EC) shift. */
+#define TD_ERRORS_SHIFT 26
+/** Error count max. (One greater than what the EC field can hold.) */
+#define TD_ERRORS_MAX 4
+
+/** CC - Condition code mask. */
+#define TD_HWINFO_CC (UINT32_C(0xf0000000))
+#define TD_HWINFO_CC_SHIFT 28
+/** EC - Error count. */
+#define TD_HWINFO_ERRORS (RT_BIT(26) | RT_BIT(27))
+/** T - Data toggle. */
+#define TD_HWINFO_TOGGLE (RT_BIT(24) | RT_BIT(25))
+#define TD_HWINFO_TOGGLE_HI (RT_BIT(25))
+#define TD_HWINFO_TOGGLE_LO (RT_BIT(24))
+/** DI - Delay interrupt. */
+#define TD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23))
+#define TD_HWINFO_IN (RT_BIT(20))
+#define TD_HWINFO_OUT (RT_BIT(19))
+/** DP - Direction / PID. */
+#define TD_HWINFO_DIR (RT_BIT(19) | RT_BIT(20))
+/** R - Buffer rounding. */
+#define TD_HWINFO_ROUNDING (RT_BIT(18))
+/** Bits that are reserved / unknown. */
+#define TD_HWINFO_UNKNOWN_MASK (UINT32_C(0x0003ffff))
+
+/** SETUP - to endpoint. */
+#define OHCI_TD_DIR_SETUP 0x0
+/** OUT - to endpoint. */
+#define OHCI_TD_DIR_OUT 0x1
+/** IN - from endpoint. */
+#define OHCI_TD_DIR_IN 0x2
+/** Reserved. */
+#define OHCI_TD_DIR_RESERVED 0x3
+
+/**
+ * OHCI general transfer descriptor
+ */
+typedef struct OHCITD
+{
+ uint32_t hwinfo;
+ /** CBP - Current Buffer Pointer. (32-bit physical address) */
+ uint32_t cbp;
+ /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */
+ uint32_t NextTD;
+ /** BE - Buffer End (inclusive). (32-bit physical address) */
+ uint32_t be;
+} OHCITD, *POHCITD;
+typedef const OHCITD *PCOHCITD;
+/** @} */
+AssertCompileSize(OHCIED, 16);
+
+
+/** @name OHCI isochronous transfer descriptor.
+ * @{ */
+/** SF - Start frame number. */
+#define ITD_HWINFO_SF 0xffff
+/** DI - Delay interrupt. (TD_HWINFO_DI) */
+#define ITD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23))
+#define ITD_HWINFO_DI_SHIFT 21
+/** FC - Frame count. */
+#define ITD_HWINFO_FC (RT_BIT(24) | RT_BIT(25) | RT_BIT(26))
+#define ITD_HWINFO_FC_SHIFT 24
+/** CC - Condition code mask. (=TD_HWINFO_CC) */
+#define ITD_HWINFO_CC UINT32_C(0xf0000000)
+#define ITD_HWINFO_CC_SHIFT 28
+/** The buffer page 0 mask (lower 12 bits are ignored). */
+#define ITD_BP0_MASK UINT32_C(0xfffff000)
+
+#define ITD_NUM_PSW 8
+/** OFFSET - offset of the package into the buffer page.
+ * (Only valid when CC set to Not Accessed.)
+ *
+ * Note that the top bit of the OFFSET field is overlapping with the
+ * first bit in the CC field. This is ok because both 0xf and 0xe are
+ * defined as "Not Accessed".
+ */
+#define ITD_PSW_OFFSET 0x1fff
+/** SIZE field mask for IN bound transfers.
+ * (Only valid when CC isn't Not Accessed.)*/
+#define ITD_PSW_SIZE 0x07ff
+/** CC field mask.
+ * USed to indicate the format of SIZE (Not Accessed -> OFFSET). */
+#define ITD_PSW_CC 0xf000
+#define ITD_PSW_CC_SHIFT 12
+
+/**
+ * OHCI isochronous transfer descriptor.
+ */
+typedef struct OHCIITD
+{
+ uint32_t HwInfo;
+ /** BP0 - Buffer Page 0. The lower 12 bits are ignored. */
+ uint32_t BP0;
+ /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */
+ uint32_t NextTD;
+ /** BE - Buffer End (inclusive). (32-bit physical address) */
+ uint32_t BE;
+ /** (OffsetN/)PSWN - package status word array (0..7).
+ * The format varies depending on whether the package has been completed or not. */
+ uint16_t aPSW[ITD_NUM_PSW];
+} OHCIITD, *POHCIITD;
+typedef const OHCIITD *PCOHCIITD;
+/** @} */
+AssertCompileSize(OHCIITD, 32);
+
+/**
+ * OHCI register operator.
+ */
+typedef struct OHCIOPREG
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value);
+} OHCIOPREG;
+
+
+/* OHCI Local stuff */
+#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) /* Control/Bulk Service Ratio. */
+#define OHCI_CTL_PLE (1<<2) /* Periodic List Enable. */
+#define OHCI_CTL_IE (1<<3) /* Isochronous Enable. */
+#define OHCI_CTL_CLE (1<<4) /* Control List Enable. */
+#define OHCI_CTL_BLE (1<<5) /* Bulk List Enable. */
+#define OHCI_CTL_HCFS ((1<<6)|(1<<7)) /* Host Controller Functional State. */
+#define OHCI_USB_RESET 0x00
+#define OHCI_USB_RESUME 0x40
+#define OHCI_USB_OPERATIONAL 0x80
+#define OHCI_USB_SUSPEND 0xc0
+#define OHCI_CTL_IR (1<<8) /* Interrupt Routing (host/SMI). */
+#define OHCI_CTL_RWC (1<<9) /* Remote Wakeup Connected. */
+#define OHCI_CTL_RWE (1<<10) /* Remote Wakeup Enabled. */
+
+#define OHCI_STATUS_HCR (1<<0) /* Host Controller Reset. */
+#define OHCI_STATUS_CLF (1<<1) /* Control List Filled. */
+#define OHCI_STATUS_BLF (1<<2) /* Bulk List Filled. */
+#define OHCI_STATUS_OCR (1<<3) /* Ownership Change Request. */
+#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) /* Scheduling Overrun Count. */
+
+/** @name Interrupt Status and Enabled/Disabled Flags
+ * @{ */
+/** SO - Scheduling overrun. */
+#define OHCI_INTR_SCHEDULING_OVERRUN RT_BIT(0)
+/** WDH - HcDoneHead writeback. */
+#define OHCI_INTR_WRITE_DONE_HEAD RT_BIT(1)
+/** SF - Start of frame. */
+#define OHCI_INTR_START_OF_FRAME RT_BIT(2)
+/** RD - Resume detect. */
+#define OHCI_INTR_RESUME_DETECT RT_BIT(3)
+/** UE - Unrecoverable error. */
+#define OHCI_INTR_UNRECOVERABLE_ERROR RT_BIT(4)
+/** FNO - Frame number overflow. */
+#define OHCI_INTR_FRAMENUMBER_OVERFLOW RT_BIT(5)
+/** RHSC- Root hub status change. */
+#define OHCI_INTR_ROOT_HUB_STATUS_CHANGE RT_BIT(6)
+/** OC - Ownership change. */
+#define OHCI_INTR_OWNERSHIP_CHANGE RT_BIT(30)
+/** MIE - Master interrupt enable. */
+#define OHCI_INTR_MASTER_INTERRUPT_ENABLED RT_BIT(31)
+/** @} */
+
+#define OHCI_HCCA_SIZE 0x100
+#define OHCI_HCCA_MASK UINT32_C(0xffffff00)
+
+#define OHCI_FMI_FI UINT32_C(0x00003fff) /* Frame Interval. */
+#define OHCI_FMI_FSMPS UINT32_C(0x7fff0000) /* Full-Speed Max Packet Size. */
+#define OHCI_FMI_FSMPS_SHIFT 16
+#define OHCI_FMI_FIT UINT32_C(0x80000000) /* Frame Interval Toggle. */
+#define OHCI_FMI_FIT_SHIFT 31
+
+#define OHCI_FR_FRT RT_BIT_32(31) /* Frame Remaining Toggle */
+
+#define OHCI_LS_THRESH 0x628 /* Low-Speed Threshold. */
+
+#define OHCI_RHA_NDP (0xff) /* Number of Downstream Ports. */
+#define OHCI_RHA_PSM RT_BIT_32(8) /* Power Switching Mode. */
+#define OHCI_RHA_NPS RT_BIT_32(9) /* No Power Switching. */
+#define OHCI_RHA_DT RT_BIT_32(10) /* Device Type. */
+#define OHCI_RHA_OCPM RT_BIT_32(11) /* Over-Current Protection Mode. */
+#define OHCI_RHA_NOCP RT_BIT_32(12) /* No Over-Current Protection. */
+#define OHCI_RHA_POTPGP UINT32_C(0xff000000) /* Power On To Power Good Time. */
+
+#define OHCI_RHS_LPS RT_BIT_32(0) /* Local Power Status. */
+#define OHCI_RHS_OCI RT_BIT_32(1) /* Over-Current Indicator. */
+#define OHCI_RHS_DRWE RT_BIT_32(15) /* Device Remote Wakeup Enable. */
+#define OHCI_RHS_LPSC RT_BIT_32(16) /* Local Power Status Change. */
+#define OHCI_RHS_OCIC RT_BIT_32(17) /* Over-Current Indicator Change. */
+#define OHCI_RHS_CRWE RT_BIT_32(31) /* Clear Remote Wakeup Enable. */
+
+/** @name HcRhPortStatus[n] - RH Port Status register (read).
+ * @{ */
+/** CCS - CurrentConnectionStatus - 0 = no device, 1 = device. */
+#define OHCI_PORT_CCS RT_BIT(0)
+/** ClearPortEnable (when writing CCS). */
+#define OHCI_PORT_CLRPE OHCI_PORT_CCS
+/** PES - PortEnableStatus. */
+#define OHCI_PORT_PES RT_BIT(1)
+/** PSS - PortSuspendStatus */
+#define OHCI_PORT_PSS RT_BIT(2)
+/** POCI- PortOverCurrentIndicator. */
+#define OHCI_PORT_POCI RT_BIT(3)
+/** ClearSuspendStatus (when writing POCI). */
+#define OHCI_PORT_CLRSS OHCI_PORT_POCI
+/** PRS - PortResetStatus */
+#define OHCI_PORT_PRS RT_BIT(4)
+/** PPS - PortPowerStatus */
+#define OHCI_PORT_PPS RT_BIT(8)
+/** LSDA - LowSpeedDeviceAttached */
+#define OHCI_PORT_LSDA RT_BIT(9)
+/** ClearPortPower (when writing LSDA). */
+#define OHCI_PORT_CLRPP OHCI_PORT_LSDA
+/** CSC - ConnectStatusChange */
+#define OHCI_PORT_CSC RT_BIT(16)
+/** PESC - PortEnableStatusChange */
+#define OHCI_PORT_PESC RT_BIT(17)
+/** PSSC - PortSuspendStatusChange */
+#define OHCI_PORT_PSSC RT_BIT(18)
+/** OCIC - OverCurrentIndicatorChange */
+#define OHCI_PORT_OCIC RT_BIT(19)
+/** PRSC - PortResetStatusChange */
+#define OHCI_PORT_PRSC RT_BIT(20)
+/** The mask of RW1C bits. */
+#define OHCI_PORT_CLEAR_CHANGE_MASK (OHCI_PORT_CSC | OHCI_PORT_PESC | OHCI_PORT_PSSC | OHCI_PORT_OCIC | OHCI_PORT_PRSC)
+/** @} */
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+/*
+ * Explain
+ */
+typedef struct OHCIDESCREADSTATS
+{
+ uint32_t cReads;
+ uint32_t cPageChange;
+ uint32_t cMinReadsPerPage;
+ uint32_t cMaxReadsPerPage;
+
+ uint32_t cReadsLastPage;
+ uint32_t u32LastPageAddr;
+} OHCIDESCREADSTATS;
+typedef OHCIDESCREADSTATS *POHCIDESCREADSTATS;
+
+typedef struct OHCIPHYSREADSTATS
+{
+ OHCIDESCREADSTATS ed;
+ OHCIDESCREADSTATS td;
+ OHCIDESCREADSTATS all;
+
+ uint32_t cCrossReads;
+ uint32_t cCacheReads;
+ uint32_t cPageReads;
+} OHCIPHYSREADSTATS;
+typedef OHCIPHYSREADSTATS *POHCIPHYSREADSTATS;
+typedef OHCIPHYSREADSTATS const *PCOHCIPHYSREADSTATS;
+#endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#if defined(VBOX_WITH_OHCI_PHYS_READ_STATS) && defined(IN_RING3)
+static OHCIPHYSREADSTATS g_PhysReadState;
+#endif
+
+#if defined(LOG_ENABLED) && defined(IN_RING3)
+static bool g_fLogBulkEPs = false;
+static bool g_fLogControlEPs = false;
+static bool g_fLogInterruptEPs = false;
+#endif
+#ifdef IN_RING3
+/**
+ * SSM descriptor table for the OHCI structure.
+ */
+static SSMFIELD const g_aOhciFields[] =
+{
+ SSMFIELD_ENTRY( OHCI, SofTime),
+ SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4),
+ SSMFIELD_ENTRY( OHCI, RootHub.status),
+ SSMFIELD_ENTRY( OHCI, RootHub.desc_a),
+ SSMFIELD_ENTRY( OHCI, RootHub.desc_b),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[8].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[9].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[10].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[11].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[12].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[13].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[14].fReg),
+ SSMFIELD_ENTRY( OHCI, ctl),
+ SSMFIELD_ENTRY( OHCI, status),
+ SSMFIELD_ENTRY( OHCI, intr_status),
+ SSMFIELD_ENTRY( OHCI, intr),
+ SSMFIELD_ENTRY( OHCI, hcca),
+ SSMFIELD_ENTRY( OHCI, per_cur),
+ SSMFIELD_ENTRY( OHCI, ctrl_cur),
+ SSMFIELD_ENTRY( OHCI, ctrl_head),
+ SSMFIELD_ENTRY( OHCI, bulk_cur),
+ SSMFIELD_ENTRY( OHCI, bulk_head),
+ SSMFIELD_ENTRY( OHCI, done),
+ SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4),
+ SSMFIELD_ENTRY( OHCI, HcFmNumber),
+ SSMFIELD_ENTRY( OHCI, pstart),
+ SSMFIELD_ENTRY_TERM()
+};
+
+/**
+ * SSM descriptor table for the older 8-port OHCI structure.
+ */
+static SSMFIELD const g_aOhciFields8Ports[] =
+{
+ SSMFIELD_ENTRY( OHCI, SofTime),
+ SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4),
+ SSMFIELD_ENTRY( OHCI, RootHub.status),
+ SSMFIELD_ENTRY( OHCI, RootHub.desc_a),
+ SSMFIELD_ENTRY( OHCI, RootHub.desc_b),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg),
+ SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg),
+ SSMFIELD_ENTRY( OHCI, ctl),
+ SSMFIELD_ENTRY( OHCI, status),
+ SSMFIELD_ENTRY( OHCI, intr_status),
+ SSMFIELD_ENTRY( OHCI, intr),
+ SSMFIELD_ENTRY( OHCI, hcca),
+ SSMFIELD_ENTRY( OHCI, per_cur),
+ SSMFIELD_ENTRY( OHCI, ctrl_cur),
+ SSMFIELD_ENTRY( OHCI, ctrl_head),
+ SSMFIELD_ENTRY( OHCI, bulk_cur),
+ SSMFIELD_ENTRY( OHCI, bulk_head),
+ SSMFIELD_ENTRY( OHCI, done),
+ SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4),
+ SSMFIELD_ENTRY( OHCI, HcFmNumber),
+ SSMFIELD_ENTRY( OHCI, pstart),
+ SSMFIELD_ENTRY_TERM()
+};
+#endif
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+RT_C_DECLS_BEGIN
+#ifdef IN_RING3
+/* Update host controller state to reflect a device attach */
+static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp);
+static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pOhci, POHCICC pThisCC, bool fHardware);
+static void ohciR3BusStop(POHCICC pThisCC);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache);
+#endif
+
+static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb);
+static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb);
+
+static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t GCPhysTD);
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD);
+# endif
+#endif /* IN_RING3 */
+RT_C_DECLS_END
+
+
+/**
+ * Update PCI IRQ levels
+ */
+static void ohciUpdateInterruptLocked(PPDMDEVINS pDevIns, POHCI ohci, const char *msg)
+{
+ int level = 0;
+
+ if ( (ohci->intr & OHCI_INTR_MASTER_INTERRUPT_ENABLED)
+ && (ohci->intr_status & ohci->intr)
+ && !(ohci->ctl & OHCI_CTL_IR))
+ level = 1;
+
+ PDMDevHlpPCISetIrq(pDevIns, 0, level);
+ if (level)
+ {
+ uint32_t val = ohci->intr_status & ohci->intr;
+ Log2(("ohci: Fired off interrupt %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d - %s\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1, msg)); NOREF(val); NOREF(msg);
+ }
+}
+
+#ifdef IN_RING3
+
+/**
+ * Set an interrupt, use the wrapper ohciSetInterrupt.
+ */
+DECLINLINE(int) ohciR3SetInterruptInt(PPDMDEVINS pDevIns, POHCI ohci, int rcBusy, uint32_t intr, const char *msg)
+{
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &ohci->CsIrq, rcBusy);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ if ( (ohci->intr_status & intr) != intr )
+ {
+ ohci->intr_status |= intr;
+ ohciUpdateInterruptLocked(pDevIns, ohci, msg);
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &ohci->CsIrq);
+ return rc;
+}
+
+/**
+ * Set an interrupt wrapper macro for logging purposes.
+ */
+# define ohciR3SetInterrupt(a_pDevIns, a_pOhci, a_fIntr) \
+ ohciR3SetInterruptInt(a_pDevIns, a_pOhci, VERR_IGNORED, a_fIntr, #a_fIntr)
+
+
+/**
+ * Sets the HC in the unrecoverable error state and raises the appropriate interrupt.
+ *
+ * @returns nothing.
+ * @param pDevIns The device instance.
+ * @param pThis The OHCI instance.
+ * @param iCode Diagnostic code.
+ */
+DECLINLINE(void) ohciR3RaiseUnrecoverableError(PPDMDEVINS pDevIns, POHCI pThis, int iCode)
+{
+ LogRelMax(10, ("OHCI#%d: Raising unrecoverable error (%d)\n", pDevIns->iInstance, iCode));
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_UNRECOVERABLE_ERROR);
+}
+
+
+/* Carry out a hardware remote wakeup */
+static void ohciR3RemoteWakeup(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+ if ((pThis->ctl & OHCI_CTL_HCFS) != OHCI_USB_SUSPEND)
+ return;
+ if (!(pThis->RootHub.status & OHCI_RHS_DRWE))
+ return;
+ ohciR3BusResume(pDevIns, pThis, pThisCC, true /* hardware */);
+}
+
+
+/**
+ * Query interface method for the roothub LUN.
+ */
+static DECLCALLBACK(void *) ohciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->RootHub.IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThisCC->RootHub.IRhPort);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->RootHub.ILeds);
+ return NULL;
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) ohciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ POHCICC pThisCC = RT_FROM_MEMBER(pInterface, OHCICC, RootHub.ILeds);
+ if (iLUN == 0)
+ {
+ *ppLed = &pThisCC->RootHub.Led;
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/** Converts a OHCI.roothub.IRhPort pointer to a OHCICC one. */
+#define VUSBIROOTHUBPORT_2_OHCI(a_pInterface) RT_FROM_MEMBER(a_pInterface, OHCICC, RootHub.IRhPort)
+
+/**
+ * Get the number of available ports in the hub.
+ *
+ * @returns The number of ports available.
+ * @param pInterface Pointer to this structure.
+ * @param pAvailable Bitmap indicating the available ports. Set bit == available port.
+ */
+static DECLCALLBACK(unsigned) ohciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ unsigned cPorts = 0;
+
+ memset(pAvailable, 0, sizeof(*pAvailable));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+
+ for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++)
+ if (!pThis->RootHub.aPorts[iPort].fAttached)
+ {
+ cPorts++;
+ ASMBitSet(pAvailable, iPort + 1);
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return cPorts;
+}
+
+
+/**
+ * Gets the supported USB versions.
+ *
+ * @returns The mask of supported USB versions.
+ * @param pInterface Pointer to this structure.
+ */
+static DECLCALLBACK(uint32_t) ohciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ return VUSB_STDVER_11;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */
+static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort, VUSBSPEED enmSpeed)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3RhAttach: uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(!pThis->RootHub.aPorts[uPort].fAttached);
+ /* Only LS/FS devices should end up here. */
+ Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL);
+
+ /*
+ * Attach it.
+ */
+ pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC;
+ if (enmSpeed == VUSB_SPEED_LOW)
+ pThis->RootHub.aPorts[uPort].fReg |= OHCI_PORT_LSDA;
+ pThis->RootHub.aPorts[uPort].fAttached = true;
+ ohciR3RhPortPower(&pThisCC->RootHub, uPort, 1 /* power on */);
+
+ ohciR3RemoteWakeup(pDevIns, pThis, pThisCC);
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device is being detached from a port in the roothub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param uPort The port number assigned to the device.
+ */
+static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, uint32_t uPort)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("ohciR3RhDetach: uPort=%u\n", uPort));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis));
+ uPort--;
+ Assert(pThis->RootHub.aPorts[uPort].fAttached);
+
+ /*
+ * Detach it.
+ */
+ pThis->RootHub.aPorts[uPort].fAttached = false;
+ if (pThis->RootHub.aPorts[uPort].fReg & OHCI_PORT_PES)
+ pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC | OHCI_PORT_PESC;
+ else
+ pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC;
+
+ ohciR3RemoteWakeup(pDevIns, pThis, pThisCC);
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+}
+
+
+/**
+ * One of the roothub devices has completed its reset operation.
+ *
+ * Currently, we don't think anything is required to be done here
+ * so it's just a stub for forcing async resetting of the devices
+ * during a root hub reset.
+ *
+ * @param pDev The root hub device.
+ * @param uPort The port of the device completing the reset.
+ * @param rc The result of the operation.
+ * @param pvUser Pointer to the controller.
+ */
+static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser)
+{
+ LogRel(("OHCI: root hub reset completed with %Rrc\n", rc));
+ RT_NOREF(pDev, uPort, rc, pvUser);
+}
+
+
+/**
+ * Reset the root hub.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this structure.
+ * @param fResetOnLinux This is used to indicate whether we're at VM reset time and
+ * can do real resets or if we're at any other time where that
+ * isn't such a good idea.
+ * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion!
+ * @thread EMT
+ */
+static DECLCALLBACK(int) ohciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ Log(("ohci: root hub reset%s\n", fResetOnLinux ? " (reset on linux)" : ""));
+
+ pThis->RootHub.status = 0;
+ pThis->RootHub.desc_a = OHCI_RHA_NPS | OHCI_NDP_CFG(pThis); /* Preserve NDP value. */
+ pThis->RootHub.desc_b = 0x0; /* Impl. specific */
+
+ /*
+ * We're pending to _reattach_ the device without resetting them.
+ * Except, during VM reset where we use the opportunity to do a proper
+ * reset before the guest comes along and expect things.
+ *
+ * However, it's very very likely that we're not doing the right thing
+ * here if coming from the guest (USB Reset state). The docs talks about
+ * root hub resetting, however what exact behaviour in terms of root hub
+ * status and changed bits, and HC interrupts aren't stated clearly. IF we
+ * get trouble and see the guest doing "USB Resets" we will have to look
+ * into this. For the time being we stick with simple.
+ */
+ for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (pThis->RootHub.aPorts[iPort].fAttached)
+ {
+ pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS;
+ if (fResetOnLinux)
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort), fResetOnLinux,
+ ohciR3RhResetDoneOneDev, pThis, pVM);
+ }
+ }
+ else
+ pThis->RootHub.aPorts[iPort].fReg = 0;
+ }
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Does a software or hardware reset of the controller.
+ *
+ * This is called in response to setting HcCommandStatus.HCR, hardware reset,
+ * and device construction.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The ohci instance data.
+ * @param pThisCC The ohci instance data, current context.
+ * @param fNewMode The new mode of operation. This is UsbSuspend if it's a
+ * software reset, and UsbReset if it's a hardware reset / cold boot.
+ * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub.
+ * This is really a just a hack for the non-working linux device reset.
+ * Linux has this feature called 'logical disconnect' if device reset fails
+ * which prevents us from doing resets when the guest asks for it - the guest
+ * will get confused when the device seems to be reconnected everytime it tries
+ * to reset it. But if we're at hardware reset time, we can allow a device to
+ * be 'reconnected' without upsetting the guest.
+ *
+ * @remark This hasn't got anything to do with software setting the mode to UsbReset.
+ */
+static void ohciR3DoReset(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, uint32_t fNewMode, bool fResetOnLinux)
+{
+ Log(("ohci: %s reset%s\n", fNewMode == OHCI_USB_RESET ? "hardware" : "software",
+ fResetOnLinux ? " (reset on linux)" : ""));
+
+ /* Clear list enable bits first, so that any processing currently in progress terminates quickly. */
+ pThis->ctl &= ~(OHCI_CTL_BLE | OHCI_CTL_CLE | OHCI_CTL_PLE);
+
+ /* Stop the bus in any case, disabling walking the lists. */
+ ohciR3BusStop(pThisCC);
+
+ /*
+ * Cancel all outstanding URBs.
+ *
+ * We can't, and won't, deal with URBs until we're moved out of the
+ * suspend/reset state. Also, a real HC isn't going to send anything
+ * any more when a reset has been signaled.
+ */
+ pThisCC->RootHub.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub.pIRhConn);
+ Assert(pThisCC->cInFlight == 0);
+
+ /*
+ * Reset the hardware registers.
+ */
+ if (fNewMode == OHCI_USB_RESET)
+ pThis->ctl = OHCI_CTL_RWC; /* We're the firmware, set RemoteWakeupConnected. */
+ else
+ pThis->ctl &= OHCI_CTL_IR | OHCI_CTL_RWC; /* IR and RWC are preserved on software reset. */
+
+ /* Clear the HCFS bits first to make setting the new state work. */
+ pThis->ctl &= ~OHCI_CTL_HCFS;
+ pThis->ctl |= fNewMode;
+ pThis->status = 0;
+ pThis->intr_status = 0;
+ pThis->intr = 0;
+ PDMDevHlpPCISetIrq(pDevIns, 0, 0);
+
+ pThis->hcca = 0;
+ pThis->per_cur = 0;
+ pThis->ctrl_head = pThis->ctrl_cur = 0;
+ pThis->bulk_head = pThis->bulk_cur = 0;
+ pThis->done = 0;
+
+ pThis->fsmps = 0x2778; /* To-Be-Defined, use the value linux sets...*/
+ pThis->fit = 0;
+ pThis->fi = 11999; /* (12MHz ticks, one frame is 1ms) */
+ pThis->frt = 0;
+ pThis->HcFmNumber = 0;
+ pThis->pstart = 0;
+
+ pThis->dqic = 0x7;
+ pThis->fno = 0;
+
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED);
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD);
+#endif
+
+ /*
+ * If this is a hardware reset, we will initialize the root hub too.
+ * Software resets doesn't do this according to the specs.
+ * (It's not possible to have device connected at the time of the
+ * device construction, so nothing to worry about there.)
+ */
+ if (fNewMode == OHCI_USB_RESET)
+ pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, fResetOnLinux);
+}
+
+
+/**
+ * Reads physical memory.
+ */
+DECLINLINE(void) ohciR3PhysRead(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPCIPhysReadUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Reads physical memory - metadata.
+ */
+DECLINLINE(void) ohciR3PhysReadMeta(PPDMDEVINS pDevIns, uint32_t Addr, void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPCIPhysReadMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Writes physical memory.
+ */
+DECLINLINE(void) ohciR3PhysWrite(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPCIPhysWriteUser(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Writes physical memory - metadata.
+ */
+DECLINLINE(void) ohciR3PhysWriteMeta(PPDMDEVINS pDevIns, uint32_t Addr, const void *pvBuf, size_t cbBuf)
+{
+ if (cbBuf)
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, Addr, pvBuf, cbBuf);
+}
+
+/**
+ * Read an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ohciR3GetDWords(PPDMDEVINS pDevIns, uint32_t Addr, uint32_t *pau32s, int c32s)
+{
+ ohciR3PhysReadMeta(pDevIns, Addr, pau32s, c32s * sizeof(uint32_t));
+# ifndef RT_LITTLE_ENDIAN
+ for(int i = 0; i < c32s; i++)
+ pau32s[i] = RT_H2LE_U32(pau32s[i]);
+# endif
+}
+
+/**
+ * Write an array of dwords from physical memory and correct endianness.
+ */
+DECLINLINE(void) ohciR3PutDWords(PPDMDEVINS pDevIns, uint32_t Addr, const uint32_t *pau32s, int cu32s)
+{
+# ifdef RT_LITTLE_ENDIAN
+ ohciR3PhysWriteMeta(pDevIns, Addr, pau32s, cu32s << 2);
+# else
+ for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s))
+ {
+ uint32_t u32Tmp = RT_H2LE_U32(*pau32s);
+ ohciR3PhysWriteMeta(pDevIns, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp));
+ }
+# endif
+}
+
+
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+
+static void descReadStatsReset(POHCIDESCREADSTATS p)
+{
+ p->cReads = 0;
+ p->cPageChange = 0;
+ p->cMinReadsPerPage = UINT32_MAX;
+ p->cMaxReadsPerPage = 0;
+
+ p->cReadsLastPage = 0;
+ p->u32LastPageAddr = 0;
+}
+
+static void physReadStatsReset(POHCIPHYSREADSTATS p)
+{
+ descReadStatsReset(&p->ed);
+ descReadStatsReset(&p->td);
+ descReadStatsReset(&p->all);
+
+ p->cCrossReads = 0;
+ p->cCacheReads = 0;
+ p->cPageReads = 0;
+}
+
+static void physReadStatsUpdateDesc(POHCIDESCREADSTATS p, uint32_t u32Addr)
+{
+ const uint32_t u32PageAddr = u32Addr & ~UINT32_C(0xFFF);
+
+ ++p->cReads;
+
+ if (p->u32LastPageAddr == 0)
+ {
+ /* First call. */
+ ++p->cReadsLastPage;
+ p->u32LastPageAddr = u32PageAddr;
+ }
+ else if (u32PageAddr != p->u32LastPageAddr)
+ {
+ /* New page. */
+ ++p->cPageChange;
+
+ p->cMinReadsPerPage = RT_MIN(p->cMinReadsPerPage, p->cReadsLastPage);
+ p->cMaxReadsPerPage = RT_MAX(p->cMaxReadsPerPage, p->cReadsLastPage);;
+
+ p->cReadsLastPage = 1;
+ p->u32LastPageAddr = u32PageAddr;
+ }
+ else
+ {
+ /* Read on the same page. */
+ ++p->cReadsLastPage;
+ }
+}
+
+static void physReadStatsPrint(POHCIPHYSREADSTATS p)
+{
+ p->ed.cMinReadsPerPage = RT_MIN(p->ed.cMinReadsPerPage, p->ed.cReadsLastPage);
+ p->ed.cMaxReadsPerPage = RT_MAX(p->ed.cMaxReadsPerPage, p->ed.cReadsLastPage);;
+
+ p->td.cMinReadsPerPage = RT_MIN(p->td.cMinReadsPerPage, p->td.cReadsLastPage);
+ p->td.cMaxReadsPerPage = RT_MAX(p->td.cMaxReadsPerPage, p->td.cReadsLastPage);;
+
+ p->all.cMinReadsPerPage = RT_MIN(p->all.cMinReadsPerPage, p->all.cReadsLastPage);
+ p->all.cMaxReadsPerPage = RT_MAX(p->all.cMaxReadsPerPage, p->all.cReadsLastPage);;
+
+ LogRel(("PHYSREAD:\n"
+ " ED: %d, %d, %d/%d\n"
+ " TD: %d, %d, %d/%d\n"
+ " ALL: %d, %d, %d/%d\n"
+ " C: %d, %d, %d\n"
+ "",
+ p->ed.cReads, p->ed.cPageChange, p->ed.cMinReadsPerPage, p->ed.cMaxReadsPerPage,
+ p->td.cReads, p->td.cPageChange, p->td.cMinReadsPerPage, p->td.cMaxReadsPerPage,
+ p->all.cReads, p->all.cPageChange, p->all.cMinReadsPerPage, p->all.cMaxReadsPerPage,
+ p->cCrossReads, p->cCacheReads, p->cPageReads
+ ));
+
+ physReadStatsReset(p);
+}
+
+# endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+
+static void ohciR3PhysReadCacheInvalidate(POHCIPAGECACHE pPageCache)
+{
+ pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS;
+}
+
+static void ohciR3PhysReadCacheRead(PPDMDEVINS pDevIns, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf)
+{
+ const RTGCPHYS PageAddr = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK;
+
+ if (PageAddr == ((GCPhys + cbBuf) & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK))
+ {
+ if (PageAddr != pPageCache->GCPhysReadCacheAddr)
+ {
+ PDMDevHlpPCIPhysRead(pDevIns, PageAddr, pPageCache->abPhysReadCache, sizeof(pPageCache->abPhysReadCache));
+ pPageCache->GCPhysReadCacheAddr = PageAddr;
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ ++g_PhysReadState.cPageReads;
+# endif
+ }
+
+ memcpy(pvBuf, &pPageCache->abPhysReadCache[GCPhys & GUEST_PAGE_OFFSET_MASK], cbBuf);
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ ++g_PhysReadState.cCacheReads;
+# endif
+ }
+ else
+ {
+ PDMDevHlpPCIPhysRead(pDevIns, GCPhys, pvBuf, cbBuf);
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ ++g_PhysReadState.cCrossReads;
+# endif
+ }
+}
+
+
+/**
+ * Updates the data in the given page cache if the given guest physical address is currently contained
+ * in the cache.
+ *
+ * @returns nothing.
+ * @param pPageCache The page cache to update.
+ * @param GCPhys The guest physical address needing the update.
+ * @param pvBuf Pointer to the buffer to update the page cache with.
+ * @param cbBuf Number of bytes to update.
+ */
+static void ohciR3PhysCacheUpdate(POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, const void *pvBuf, size_t cbBuf)
+{
+ const RTGCPHYS GCPhysPage = GCPhys & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK;
+
+ if (GCPhysPage == pPageCache->GCPhysReadCacheAddr)
+ {
+ uint32_t offPage = GCPhys & GUEST_PAGE_OFFSET_MASK;
+ memcpy(&pPageCache->abPhysReadCache[offPage], pvBuf, RT_MIN(GUEST_PAGE_SIZE - offPage, cbBuf));
+ }
+}
+
+/**
+ * Update any cached ED data with the given endpoint descriptor at the given address.
+ *
+ * @returns nothing.
+ * @param pThisCC The OHCI instance data for the current context.
+ * @param EdAddr Endpoint descriptor address.
+ * @param pEd The endpoint descriptor which got updated.
+ */
+DECLINLINE(void) ohciR3CacheEdUpdate(POHCICC pThisCC, RTGCPHYS32 EdAddr, PCOHCIED pEd)
+{
+ ohciR3PhysCacheUpdate(&pThisCC->CacheED, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, sizeof(uint32_t));
+}
+
+
+/**
+ * Update any cached TD data with the given transfer descriptor at the given address.
+ *
+ * @returns nothing.
+ * @param pThisCC The OHCI instance data, current context.
+ * @param TdAddr Transfer descriptor address.
+ * @param pTd The transfer descriptor which got updated.
+ */
+DECLINLINE(void) ohciR3CacheTdUpdate(POHCICC pThisCC, RTGCPHYS32 TdAddr, PCOHCITD pTd)
+{
+ ohciR3PhysCacheUpdate(&pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd));
+}
+
+# endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */
+
+/**
+ * Reads an OHCIED.
+ */
+DECLINLINE(void) ohciR3ReadEd(PPDMDEVINS pDevIns, uint32_t EdAddr, POHCIED pEd)
+{
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsUpdateDesc(&g_PhysReadState.ed, EdAddr);
+ physReadStatsUpdateDesc(&g_PhysReadState.all, EdAddr);
+# endif
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheED, EdAddr, pEd, sizeof(*pEd));
+#else
+ ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2);
+#endif
+}
+
+/**
+ * Reads an OHCITD.
+ */
+DECLINLINE(void) ohciR3ReadTd(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd)
+{
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsUpdateDesc(&g_PhysReadState.td, TdAddr);
+ physReadStatsUpdateDesc(&g_PhysReadState.all, TdAddr);
+# endif
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ ohciR3PhysReadCacheRead(pDevIns, &pThisCC->CacheTD, TdAddr, pTd, sizeof(*pTd));
+#else
+ ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2);
+#endif
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ uint32_t hichg;
+ hichg = pTd->hwinfo;
+ Log3(("ohciR3ReadTd(,%#010x,): R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x UNK=%#x\n",
+ TdAddr,
+ (pTd->hwinfo >> 18) & 1,
+ (pTd->hwinfo >> 19) & 3,
+ (pTd->hwinfo >> 21) & 7,
+ (pTd->hwinfo >> 24) & 3,
+ (pTd->hwinfo >> 26) & 3,
+ (pTd->hwinfo >> 28) &15,
+ pTd->cbp,
+ pTd->NextTD,
+ pTd->be,
+ pTd->hwinfo & TD_HWINFO_UNKNOWN_MASK));
+# if 0
+ if (LogIs3Enabled())
+ {
+ /*
+ * usbohci.sys (32-bit XP) allocates 0x80 bytes per TD:
+ * 0x00-0x0f is the OHCI TD.
+ * 0x10-0x1f for isochronous TDs
+ * 0x20 is the physical address of this TD.
+ * 0x24 is initialized with 0x64745948, probably a magic.
+ * 0x28 is some kind of flags. the first bit begin the allocated / not allocated indicator.
+ * 0x30 is a pointer to something. endpoint? interface? device?
+ * 0x38 is initialized to 0xdeadface. but is changed into a pointer or something.
+ * 0x40 looks like a pointer.
+ * The rest is unknown and initialized with zeros.
+ */
+ uint8_t abXpTd[0x80];
+ ohciR3PhysRead(pDevIns, TdAddr, abXpTd, sizeof(abXpTd));
+ Log3(("WinXpTd: alloc=%d PhysSelf=%RX32 s2=%RX32 magic=%RX32 s4=%RX32 s5=%RX32\n"
+ "%.*Rhxd\n",
+ abXpTd[28] & RT_BIT(0),
+ *((uint32_t *)&abXpTd[0x20]), *((uint32_t *)&abXpTd[0x30]),
+ *((uint32_t *)&abXpTd[0x24]), *((uint32_t *)&abXpTd[0x38]),
+ *((uint32_t *)&abXpTd[0x40]),
+ sizeof(abXpTd), &abXpTd[0]));
+ }
+# endif
+ }
+# endif
+}
+
+/**
+ * Reads an OHCIITD.
+ */
+DECLINLINE(void) ohciR3ReadITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, POHCIITD pITd)
+{
+ ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t));
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ Log3(("ohciR3ReadITd(,%#010x,): SF=%#06x (%#RX32) DI=%#x FC=%d CC=%#x BP0=%#010x NextTD=%#010x BE=%#010x\n",
+ ITdAddr,
+ pITd->HwInfo & 0xffff, pThis->HcFmNumber,
+ (pITd->HwInfo >> 21) & 7,
+ (pITd->HwInfo >> 24) & 7,
+ (pITd->HwInfo >> 28) &15,
+ pITd->BP0,
+ pITd->NextTD,
+ pITd->BE));
+ Log3(("psw0=%x:%03x psw1=%x:%03x psw2=%x:%03x psw3=%x:%03x psw4=%x:%03x psw5=%x:%03x psw6=%x:%03x psw7=%x:%03x\n",
+ pITd->aPSW[0] >> 12, pITd->aPSW[0] & 0xfff,
+ pITd->aPSW[1] >> 12, pITd->aPSW[1] & 0xfff,
+ pITd->aPSW[2] >> 12, pITd->aPSW[2] & 0xfff,
+ pITd->aPSW[3] >> 12, pITd->aPSW[3] & 0xfff,
+ pITd->aPSW[4] >> 12, pITd->aPSW[4] & 0xfff,
+ pITd->aPSW[5] >> 12, pITd->aPSW[5] & 0xfff,
+ pITd->aPSW[6] >> 12, pITd->aPSW[6] & 0xfff,
+ pITd->aPSW[7] >> 12, pITd->aPSW[7] & 0xfff));
+ }
+# else
+ RT_NOREF(pThis);
+# endif
+}
+
+
+/**
+ * Writes an OHCIED.
+ */
+DECLINLINE(void) ohciR3WriteEd(PPDMDEVINS pDevIns, uint32_t EdAddr, PCOHCIED pEd)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCIED EdOld;
+ uint32_t hichg;
+
+ ohciR3GetDWords(pDevIns, EdAddr, (uint32_t *)&EdOld, sizeof(EdOld) >> 2);
+ hichg = EdOld.hwinfo ^ pEd->hwinfo;
+ Log3(("ohciR3WriteEd(,%#010x,): %sFA=%#x %sEN=%#x %sD=%#x %sS=%d %sK=%d %sF=%d %sMPS=%#x %sTailP=%#010x %sHeadP=%#010x %sH=%d %sC=%d %sNextED=%#010x\n",
+ EdAddr,
+ (hichg >> 0) & 0x7f ? "*" : "", (pEd->hwinfo >> 0) & 0x7f,
+ (hichg >> 7) & 0xf ? "*" : "", (pEd->hwinfo >> 7) & 0xf,
+ (hichg >> 11) & 3 ? "*" : "", (pEd->hwinfo >> 11) & 3,
+ (hichg >> 13) & 1 ? "*" : "", (pEd->hwinfo >> 13) & 1,
+ (hichg >> 14) & 1 ? "*" : "", (pEd->hwinfo >> 14) & 1,
+ (hichg >> 15) & 1 ? "*" : "", (pEd->hwinfo >> 15) & 1,
+ (hichg >> 24) &0x3ff ? "*" : "", (pEd->hwinfo >> 16) &0x3ff,
+ EdOld.TailP != pEd->TailP ? "*" : "", pEd->TailP,
+ (EdOld.HeadP & ~3) != (pEd->HeadP & ~3) ? "*" : "", pEd->HeadP & ~3,
+ (EdOld.HeadP ^ pEd->HeadP) & 1 ? "*" : "", pEd->HeadP & 1,
+ (EdOld.HeadP ^ pEd->HeadP) & 2 ? "*" : "", (pEd->HeadP >> 1) & 1,
+ EdOld.NextED != pEd->NextED ? "*" : "", pEd->NextED));
+ }
+# endif
+
+ ohciR3PutDWords(pDevIns, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, 1);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ ohciR3CacheEdUpdate(pThisCC, EdAddr, pEd);
+#endif
+}
+
+
+/**
+ * Writes an OHCITD.
+ */
+DECLINLINE(void) ohciR3WriteTd(PPDMDEVINS pDevIns, uint32_t TdAddr, PCOHCITD pTd, const char *pszLogMsg)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCITD TdOld;
+ ohciR3GetDWords(pDevIns, TdAddr, (uint32_t *)&TdOld, sizeof(TdOld) >> 2);
+ uint32_t hichg = TdOld.hwinfo ^ pTd->hwinfo;
+ Log3(("ohciR3WriteTd(,%#010x,): %sR=%d %sDP=%d %sDI=%#x %sT=%d %sEC=%d %sCC=%#x %sCBP=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n",
+ TdAddr,
+ (hichg >> 18) & 1 ? "*" : "", (pTd->hwinfo >> 18) & 1,
+ (hichg >> 19) & 3 ? "*" : "", (pTd->hwinfo >> 19) & 3,
+ (hichg >> 21) & 7 ? "*" : "", (pTd->hwinfo >> 21) & 7,
+ (hichg >> 24) & 3 ? "*" : "", (pTd->hwinfo >> 24) & 3,
+ (hichg >> 26) & 3 ? "*" : "", (pTd->hwinfo >> 26) & 3,
+ (hichg >> 28) &15 ? "*" : "", (pTd->hwinfo >> 28) &15,
+ TdOld.cbp != pTd->cbp ? "*" : "", pTd->cbp,
+ TdOld.NextTD != pTd->NextTD ? "*" : "", pTd->NextTD,
+ TdOld.be != pTd->be ? "*" : "", pTd->be,
+ pszLogMsg));
+ }
+# else
+ RT_NOREF(pszLogMsg);
+# endif
+ ohciR3PutDWords(pDevIns, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2);
+#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ ohciR3CacheTdUpdate(pThisCC, TdAddr, pTd);
+#endif
+}
+
+/**
+ * Writes an OHCIITD.
+ */
+DECLINLINE(void) ohciR3WriteITd(PPDMDEVINS pDevIns, POHCI pThis, uint32_t ITdAddr, PCOHCIITD pITd, const char *pszLogMsg)
+{
+# ifdef LOG_ENABLED
+ if (LogIs3Enabled())
+ {
+ OHCIITD ITdOld;
+ ohciR3GetDWords(pDevIns, ITdAddr, (uint32_t *)&ITdOld, sizeof(ITdOld) / sizeof(uint32_t));
+ uint32_t HIChg = ITdOld.HwInfo ^ pITd->HwInfo;
+ Log3(("ohciR3WriteITd(,%#010x,): %sSF=%#x (now=%#RX32) %sDI=%#x %sFC=%d %sCC=%#x %sBP0=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n",
+ ITdAddr,
+ (HIChg & 0xffff) & 1 ? "*" : "", pITd->HwInfo & 0xffff, pThis->HcFmNumber,
+ (HIChg >> 21) & 7 ? "*" : "", (pITd->HwInfo >> 21) & 7,
+ (HIChg >> 24) & 7 ? "*" : "", (pITd->HwInfo >> 24) & 7,
+ (HIChg >> 28) &15 ? "*" : "", (pITd->HwInfo >> 28) &15,
+ ITdOld.BP0 != pITd->BP0 ? "*" : "", pITd->BP0,
+ ITdOld.NextTD != pITd->NextTD ? "*" : "", pITd->NextTD,
+ ITdOld.BE != pITd->BE ? "*" : "", pITd->BE,
+ pszLogMsg));
+ Log3(("psw0=%s%x:%s%03x psw1=%s%x:%s%03x psw2=%s%x:%s%03x psw3=%s%x:%s%03x psw4=%s%x:%s%03x psw5=%s%x:%s%03x psw6=%s%x:%s%03x psw7=%s%x:%s%03x\n",
+ (ITdOld.aPSW[0] >> 12) != (pITd->aPSW[0] >> 12) ? "*" : "", pITd->aPSW[0] >> 12, (ITdOld.aPSW[0] & 0xfff) != (pITd->aPSW[0] & 0xfff) ? "*" : "", pITd->aPSW[0] & 0xfff,
+ (ITdOld.aPSW[1] >> 12) != (pITd->aPSW[1] >> 12) ? "*" : "", pITd->aPSW[1] >> 12, (ITdOld.aPSW[1] & 0xfff) != (pITd->aPSW[1] & 0xfff) ? "*" : "", pITd->aPSW[1] & 0xfff,
+ (ITdOld.aPSW[2] >> 12) != (pITd->aPSW[2] >> 12) ? "*" : "", pITd->aPSW[2] >> 12, (ITdOld.aPSW[2] & 0xfff) != (pITd->aPSW[2] & 0xfff) ? "*" : "", pITd->aPSW[2] & 0xfff,
+ (ITdOld.aPSW[3] >> 12) != (pITd->aPSW[3] >> 12) ? "*" : "", pITd->aPSW[3] >> 12, (ITdOld.aPSW[3] & 0xfff) != (pITd->aPSW[3] & 0xfff) ? "*" : "", pITd->aPSW[3] & 0xfff,
+ (ITdOld.aPSW[4] >> 12) != (pITd->aPSW[4] >> 12) ? "*" : "", pITd->aPSW[4] >> 12, (ITdOld.aPSW[4] & 0xfff) != (pITd->aPSW[4] & 0xfff) ? "*" : "", pITd->aPSW[4] & 0xfff,
+ (ITdOld.aPSW[5] >> 12) != (pITd->aPSW[5] >> 12) ? "*" : "", pITd->aPSW[5] >> 12, (ITdOld.aPSW[5] & 0xfff) != (pITd->aPSW[5] & 0xfff) ? "*" : "", pITd->aPSW[5] & 0xfff,
+ (ITdOld.aPSW[6] >> 12) != (pITd->aPSW[6] >> 12) ? "*" : "", pITd->aPSW[6] >> 12, (ITdOld.aPSW[6] & 0xfff) != (pITd->aPSW[6] & 0xfff) ? "*" : "", pITd->aPSW[6] & 0xfff,
+ (ITdOld.aPSW[7] >> 12) != (pITd->aPSW[7] >> 12) ? "*" : "", pITd->aPSW[7] >> 12, (ITdOld.aPSW[7] & 0xfff) != (pITd->aPSW[7] & 0xfff) ? "*" : "", pITd->aPSW[7] & 0xfff));
+ }
+# else
+ RT_NOREF(pThis, pszLogMsg);
+# endif
+ ohciR3PutDWords(pDevIns, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t));
+}
+
+
+# ifdef LOG_ENABLED
+
+/**
+ * Core TD queue dumper. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpTdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull)
+{
+ uint32_t GCPhys = GCPhysHead;
+ int cIterations = 128;
+ for (;;)
+ {
+ OHCITD Td;
+ Log4(("%#010x%s%s", GCPhys,
+ GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "",
+ GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : ""));
+ if (GCPhys == 0 || GCPhys == GCPhysTail)
+ break;
+
+ /* can't use ohciR3ReadTd() because of Log4. */
+ ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&Td, sizeof(Td) >> 2);
+ if (fFull)
+ Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ",
+ (Td.hwinfo >> 18) & 1,
+ (Td.hwinfo >> 19) & 3,
+ (Td.hwinfo >> 21) & 7,
+ (Td.hwinfo >> 24) & 3,
+ (Td.hwinfo >> 26) & 3,
+ (Td.hwinfo >> 28) &15,
+ Td.cbp,
+ Td.NextTD,
+ Td.be));
+ else
+ Log4((" -> "));
+ GCPhys = Td.NextTD & ED_PTR_MASK;
+ Assert(GCPhys != GCPhysHead);
+ if (!--cIterations)
+ break;
+ }
+}
+
+/**
+ * Dumps a TD queue. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpTdQueue(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg)
+{
+ if (pszMsg)
+ Log4(("%s: ", pszMsg));
+ ohciR3DumpTdQueueCore(pDevIns, pThisCC, GCPhysHead, 0, true);
+ Log4(("\n"));
+}
+
+/**
+ * Core ITD queue dumper. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpITdQueueCore(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull)
+{
+ RT_NOREF(fFull);
+ uint32_t GCPhys = GCPhysHead;
+ int cIterations = 100;
+ for (;;)
+ {
+ OHCIITD ITd;
+ Log4(("%#010x%s%s", GCPhys,
+ GCPhys && ohciR3InFlightFind(pThisCC, GCPhys) >= 0 ? "~" : "",
+ GCPhys && ohciR3InDoneQueueFind(pThisCC, GCPhys) >= 0 ? "^" : ""));
+ if (GCPhys == 0 || GCPhys == GCPhysTail)
+ break;
+
+ /* can't use ohciR3ReadTd() because of Log4. */
+ ohciR3GetDWords(pDevIns, GCPhys, (uint32_t *)&ITd, sizeof(ITd) / sizeof(uint32_t));
+ /*if (fFull)
+ Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ",
+ (Td.hwinfo >> 18) & 1,
+ (Td.hwinfo >> 19) & 3,
+ (Td.hwinfo >> 21) & 7,
+ (Td.hwinfo >> 24) & 3,
+ (Td.hwinfo >> 26) & 3,
+ (Td.hwinfo >> 28) &15,
+ Td.cbp,
+ Td.NextTD,
+ Td.be));
+ else*/
+ Log4((" -> "));
+ GCPhys = ITd.NextTD & ED_PTR_MASK;
+ Assert(GCPhys != GCPhysHead);
+ if (!--cIterations)
+ break;
+ }
+}
+
+/**
+ * Dumps a ED list. LOG_ENABLED builds only.
+ */
+DECLINLINE(void) ohciR3DumpEdList(PPDMDEVINS pDevIns, POHCICC pThisCC, uint32_t GCPhysHead, const char *pszMsg, bool fTDs)
+{
+ RT_NOREF(fTDs);
+ uint32_t GCPhys = GCPhysHead;
+ if (pszMsg)
+ Log4(("%s:", pszMsg));
+ for (;;)
+ {
+ OHCIED Ed;
+
+ /* ED */
+ Log4((" %#010x={", GCPhys));
+ if (!GCPhys)
+ {
+ Log4(("END}\n"));
+ return;
+ }
+
+ /* TDs */
+ ohciR3ReadEd(pDevIns, GCPhys, &Ed);
+ if (Ed.hwinfo & ED_HWINFO_ISO)
+ Log4(("[I]"));
+ if ((Ed.HeadP & ED_HEAD_HALTED) || (Ed.hwinfo & ED_HWINFO_SKIP))
+ {
+ if ((Ed.HeadP & ED_HEAD_HALTED) && (Ed.hwinfo & ED_HWINFO_SKIP))
+ Log4(("SH}"));
+ else if (Ed.hwinfo & ED_HWINFO_SKIP)
+ Log4(("S-}"));
+ else
+ Log4(("-H}"));
+ }
+ else
+ {
+ if (Ed.hwinfo & ED_HWINFO_ISO)
+ ohciR3DumpITdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false);
+ else
+ ohciR3DumpTdQueueCore(pDevIns, pThisCC, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false);
+ Log4(("}"));
+ }
+
+ /* next */
+ GCPhys = Ed.NextED & ED_PTR_MASK;
+ Assert(GCPhys != GCPhysHead);
+ }
+ /* not reached */
+}
+
+# endif /* LOG_ENABLED */
+
+
+DECLINLINE(int) ohciR3InFlightFindFree(POHCICC pThisCC, const int iStart)
+{
+ unsigned i = iStart;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight))
+ {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ i++;
+ }
+ i = iStart;
+ while (i-- > 0)
+ {
+ if (pThisCC->aInFlight[i].pUrb == NULL)
+ return i;
+ }
+ return -1;
+}
+
+
+/**
+ * Record an in-flight TD.
+ *
+ * @param pThis OHCI instance data, shared edition.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @param pUrb The URB.
+ */
+static void ohciR3InFlightAdd(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD, PVUSBURB pUrb)
+{
+ int i = ohciR3InFlightFindFree(pThisCC, (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight));
+ if (i >= 0)
+ {
+# ifdef LOG_ENABLED
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+# endif
+ pThisCC->aInFlight[i].GCPhysTD = GCPhysTD;
+ pThisCC->aInFlight[i].pUrb = pUrb;
+ pThisCC->cInFlight++;
+ return;
+ }
+ AssertMsgFailed(("Out of space cInFlight=%d!\n", pThisCC->cInFlight));
+ RT_NOREF(pThis);
+}
+
+
+/**
+ * Record in-flight TDs for an URB.
+ *
+ * @param pThis OHCI instance data, shared edition.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static void ohciR3InFlightAddUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb)
+{
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ ohciR3InFlightAdd(pThis, pThisCC, pUrb->paTds[iTd].TdAddr, pUrb);
+}
+
+
+/**
+ * Finds a in-flight TD.
+ *
+ * @returns Index of the record.
+ * @returns -1 if not found.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ * @remark This has to be fast.
+ */
+static int ohciR3InFlightFind(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ unsigned cLeft = pThisCC->cInFlight;
+ unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThisCC->aInFlight);
+ const int iLast = i;
+ while (i < RT_ELEMENTS(pThisCC->aInFlight))
+ {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ i++;
+ }
+ i = iLast;
+ while (i-- > 0)
+ {
+ if (pThisCC->aInFlight[i].GCPhysTD == GCPhysTD && pThisCC->aInFlight[i].pUrb)
+ return i;
+ if (pThisCC->aInFlight[i].pUrb)
+ if (cLeft-- <= 1)
+ return -1;
+ }
+ return -1;
+}
+
+
+/**
+ * Checks if a TD is in-flight.
+ *
+ * @returns true if in flight, false if not.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ohciR3IsTdInFlight(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ return ohciR3InFlightFind(pThisCC, GCPhysTD) >= 0;
+}
+
+/**
+ * Returns a URB associated with an in-flight TD, if any.
+ *
+ * @returns pointer to URB if TD is in flight.
+ * @returns NULL if not in flight.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static PVUSBURB ohciR3TdInFlightUrb(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ int i;
+
+ i = ohciR3InFlightFind(pThisCC, GCPhysTD);
+ if ( i >= 0 )
+ return pThisCC->aInFlight[i].pUrb;
+ return NULL;
+}
+
+/**
+ * Removes a in-flight TD.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis OHCI instance data, shared edition (for logging).
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ohciR3InFlightRemove(POHCI pThis, POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ int i = ohciR3InFlightFind(pThisCC, GCPhysTD);
+ if (i >= 0)
+ {
+# ifdef LOG_ENABLED
+ const int cFramesInFlight = pThis->HcFmNumber - pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo;
+# else
+ const int cFramesInFlight = 0; RT_NOREF(pThis);
+# endif
+ Log2(("ohciR3InFlightRemove: reaping TD=%#010x %d frames (%#010x-%#010x)\n",
+ GCPhysTD, cFramesInFlight, pThisCC->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber));
+ pThisCC->aInFlight[i].GCPhysTD = 0;
+ pThisCC->aInFlight[i].pUrb = NULL;
+ pThisCC->cInFlight--;
+ return cFramesInFlight;
+ }
+ AssertMsgFailed(("TD %#010x is not in flight\n", GCPhysTD));
+ return -1;
+}
+
+
+/**
+ * Removes all TDs associated with a URB from the in-flight tracking.
+ *
+ * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight.
+ * @returns -1 if not found.
+ * @param pThis OHCI instance data, shared edition (for logging).
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param pUrb The URB.
+ */
+static int ohciR3InFlightRemoveUrb(POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb)
+{
+ int cFramesInFlight = ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[0].TdAddr);
+ if (pUrb->pHci->cTds > 1)
+ {
+ for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++)
+ if (ohciR3InFlightRemove(pThis, pThisCC, pUrb->paTds[iTd].TdAddr) < 0)
+ cFramesInFlight = -1;
+ }
+ return cFramesInFlight;
+}
+
+
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+
+/**
+ * Empties the in-done-queue.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ */
+static void ohciR3InDoneQueueZap(POHCICC pThisCC)
+{
+ pThisCC->cInDoneQueue = 0;
+}
+
+/**
+ * Finds a TD in the in-done-queue.
+ * @returns >= 0 on success.
+ * @returns -1 if not found.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static int ohciR3InDoneQueueFind(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ unsigned i = pThisCC->cInDoneQueue;
+ while (i-- > 0)
+ if (pThisCC->aInDoneQueue[i].GCPhysTD == GCPhysTD)
+ return i;
+ return -1;
+}
+
+/**
+ * Checks that the specified TD is not in the done queue.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static bool ohciR3InDoneQueueCheck(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ int i = ohciR3InDoneQueueFind(pThisCC, GCPhysTD);
+# if 0
+ /* This condition has been observed with the USB tablet emulation or with
+ * a real USB mouse and an SMP XP guest. I am also not sure if this is
+ * really a problem for us. The assertion checks that the guest doesn't
+ * re-submit a TD which is still in the done queue. It seems to me that
+ * this should only be a problem if we either keep track of TDs in the done
+ * queue somewhere else as well (in which case we should also free those
+ * references in time, and I can't see any code doing that) or if we
+ * manipulate TDs in the done queue in some way that might fail if they are
+ * re-submitted (can't see anything like that either).
+ */
+ AssertMsg(i < 0, ("TD %#010x (i=%d)\n", GCPhysTD, i));
+# endif
+ return i < 0;
+}
+
+
+# if defined(VBOX_STRICT) && defined(LOG_ENABLED)
+/**
+ * Adds a TD to the in-done-queue tracking, checking that it's not there already.
+ * @param pThisCC OHCI instance data, ring-3 edition.
+ * @param GCPhysTD Physical address of the TD.
+ */
+static void ohciR3InDoneQueueAdd(POHCICC pThisCC, uint32_t GCPhysTD)
+{
+ Assert(pThisCC->cInDoneQueue + 1 <= RT_ELEMENTS(pThisCC->aInDoneQueue));
+ if (ohciR3InDoneQueueCheck(pThisCC, GCPhysTD))
+ pThisCC->aInDoneQueue[pThisCC->cInDoneQueue++].GCPhysTD = GCPhysTD;
+}
+# endif /* VBOX_STRICT */
+# endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */
+
+
+/**
+ * OHCI Transport Buffer - represents a OHCI Transport Descriptor (TD).
+ * A TD may be split over max 2 pages.
+ */
+typedef struct OHCIBUF
+{
+ /** Pages involved. */
+ struct OHCIBUFVEC
+ {
+ /** The 32-bit physical address of this part. */
+ uint32_t Addr;
+ /** The length. */
+ uint32_t cb;
+ } aVecs[2];
+ /** Number of valid entries in aVecs. */
+ uint32_t cVecs;
+ /** The total length. */
+ uint32_t cbTotal;
+} OHCIBUF, *POHCIBUF;
+
+
+/**
+ * Sets up a OHCI transport buffer.
+ *
+ * @param pBuf OHCI buffer.
+ * @param cbp Current buffer pointer. 32-bit physical address.
+ * @param be Last byte in buffer (BufferEnd). 32-bit physical address.
+ */
+static void ohciR3BufInit(POHCIBUF pBuf, uint32_t cbp, uint32_t be)
+{
+ if (!cbp || !be)
+ {
+ pBuf->cVecs = 0;
+ pBuf->cbTotal = 0;
+ Log2(("ohci: cbp=%#010x be=%#010x cbTotal=0 EMPTY\n", cbp, be));
+ }
+ else if ((cbp & ~0xfff) == (be & ~0xfff) && (cbp <= be))
+ {
+ pBuf->aVecs[0].Addr = cbp;
+ pBuf->aVecs[0].cb = (be - cbp) + 1;
+ pBuf->cVecs = 1;
+ pBuf->cbTotal = pBuf->aVecs[0].cb;
+ Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u\n", cbp, be, pBuf->cbTotal));
+ }
+ else
+ {
+ pBuf->aVecs[0].Addr = cbp;
+ pBuf->aVecs[0].cb = 0x1000 - (cbp & 0xfff);
+ pBuf->aVecs[1].Addr = be & ~0xfff;
+ pBuf->aVecs[1].cb = (be & 0xfff) + 1;
+ pBuf->cVecs = 2;
+ pBuf->cbTotal = pBuf->aVecs[0].cb + pBuf->aVecs[1].cb;
+ Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u PAGE FLIP\n", cbp, be, pBuf->cbTotal));
+ }
+}
+
+/**
+ * Updates a OHCI transport buffer.
+ *
+ * This is called upon completion to adjust the sector lengths if
+ * the total length has changed. (received less then we had space for
+ * or a partial transfer.)
+ *
+ * @param pBuf The buffer to update. cbTotal contains the new total on input.
+ * While the aVecs[*].cb members is updated upon return.
+ */
+static void ohciR3BufUpdate(POHCIBUF pBuf)
+{
+ for (uint32_t i = 0, cbCur = 0; i < pBuf->cVecs; i++)
+ {
+ if (cbCur + pBuf->aVecs[i].cb > pBuf->cbTotal)
+ {
+ pBuf->aVecs[i].cb = pBuf->cbTotal - cbCur;
+ pBuf->cVecs = i + 1;
+ return;
+ }
+ cbCur += pBuf->aVecs[i].cb;
+ }
+}
+
+
+/** A worker for ohciR3UnlinkTds(). */
+static bool ohciR3UnlinkIsochronousTdInList(PPDMDEVINS pDevIns, POHCI pThis, uint32_t TdAddr, POHCIITD pITd, POHCIED pEd)
+{
+ const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK;
+ Log(("ohciUnlinkIsocTdInList: Unlinking non-head ITD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n",
+ TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr));
+ AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false);
+
+ uint32_t cIterations = 256;
+ uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK;
+ while ( CurTdAddr != LastTdAddr
+ && cIterations-- > 0)
+ {
+ OHCIITD ITd;
+ ohciR3ReadITd(pDevIns, pThis, CurTdAddr, &ITd);
+ if ((ITd.NextTD & ED_PTR_MASK) == TdAddr)
+ {
+ ITd.NextTD = (pITd->NextTD & ED_PTR_MASK) | (ITd.NextTD & ~ED_PTR_MASK);
+ ohciR3WriteITd(pDevIns, pThis, CurTdAddr, &ITd, "ohciUnlinkIsocTdInList");
+ pITd->NextTD &= ~ED_PTR_MASK;
+ return true;
+ }
+
+ /* next */
+ CurTdAddr = ITd.NextTD & ED_PTR_MASK;
+ }
+
+ Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations));
+ return false;
+}
+
+
+/** A worker for ohciR3UnlinkTds(). */
+static bool ohciR3UnlinkGeneralTdInList(PPDMDEVINS pDevIns, uint32_t TdAddr, POHCITD pTd, POHCIED pEd)
+{
+ const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK;
+ Log(("ohciR3UnlinkGeneralTdInList: Unlinking non-head TD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n",
+ TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr));
+ AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false);
+
+ uint32_t cIterations = 256;
+ uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK;
+ while ( CurTdAddr != LastTdAddr
+ && cIterations-- > 0)
+ {
+ OHCITD Td;
+ ohciR3ReadTd(pDevIns, CurTdAddr, &Td);
+ if ((Td.NextTD & ED_PTR_MASK) == TdAddr)
+ {
+ Td.NextTD = (pTd->NextTD & ED_PTR_MASK) | (Td.NextTD & ~ED_PTR_MASK);
+ ohciR3WriteTd(pDevIns, CurTdAddr, &Td, "ohciR3UnlinkGeneralTdInList");
+ pTd->NextTD &= ~ED_PTR_MASK;
+ return true;
+ }
+
+ /* next */
+ CurTdAddr = Td.NextTD & ED_PTR_MASK;
+ }
+
+ Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cIterations=%d)\n", TdAddr, cIterations));
+ return false;
+}
+
+
+/**
+ * Unlinks the TDs that makes up the URB from the ED.
+ *
+ * @returns success indicator. true if successfully unlinked.
+ * @returns false if the TD was not found in the list.
+ */
+static bool ohciR3UnlinkTds(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, POHCIED pEd)
+{
+ /*
+ * Don't unlink more than once.
+ */
+ if (pUrb->pHci->fUnlinked)
+ return true;
+ pUrb->pHci->fUnlinked = true;
+
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0];
+ const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr;
+
+ /*
+ * Unlink the TD from the ED list.
+ * The normal case is that it's at the head of the list.
+ */
+ Assert((ITdAddr & ED_PTR_MASK) == ITdAddr);
+ if ((pEd->HeadP & ED_PTR_MASK) == ITdAddr)
+ {
+ pEd->HeadP = (pITd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK);
+ pITd->NextTD &= ~ED_PTR_MASK;
+ }
+ else
+ {
+ /*
+ * It's probably somewhere in the list, not a unlikely situation with
+ * the current isochronous code.
+ */
+ if (!ohciR3UnlinkIsochronousTdInList(pDevIns, pThis, ITdAddr, pITd, pEd))
+ return false;
+ }
+ }
+ }
+ else
+ {
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0];
+ const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr;
+
+ /** @todo r=bird: Messing with the toggle flag in prepare is probably not correct
+ * when we encounter a STALL error, 4.3.1.3.7.2: ''If an endpoint returns a STALL
+ * PID, the Host Controller retires the General TD with the ConditionCode set
+ * to STALL and halts the endpoint. The CurrentBufferPointer, ErrorCount, and
+ * dataToggle fields retain the values that they had at the start of the
+ * transaction.'' */
+
+ /* update toggle and set data toggle carry */
+ pTd->hwinfo &= ~TD_HWINFO_TOGGLE;
+ if ( pTd->hwinfo & TD_HWINFO_TOGGLE_HI )
+ {
+ if ( !!(pTd->hwinfo & TD_HWINFO_TOGGLE_LO) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */
+ pTd->hwinfo |= TD_HWINFO_TOGGLE_LO;
+ else
+ pTd->hwinfo &= ~TD_HWINFO_TOGGLE_LO;
+ }
+ else
+ {
+ if ( !!(pEd->HeadP & ED_HEAD_CARRY) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */
+ pEd->HeadP |= ED_HEAD_CARRY;
+ else
+ pEd->HeadP &= ~ED_HEAD_CARRY;
+ }
+
+ /*
+ * Unlink the TD from the ED list.
+ * The normal case is that it's at the head of the list.
+ */
+ Assert((TdAddr & ED_PTR_MASK) == TdAddr);
+ if ((pEd->HeadP & ED_PTR_MASK) == TdAddr)
+ {
+ pEd->HeadP = (pTd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK);
+ pTd->NextTD &= ~ED_PTR_MASK;
+ }
+ else
+ {
+ /*
+ * The TD is probably somewhere in the list.
+ *
+ * This shouldn't ever happen unless there was a failure! Even on failure,
+ * we can screw up the HCD state by picking out a TD from within the list
+ * like this! If this turns out to be a problem, we have to find a better
+ * solution. For now we'll hope the HCD handles it...
+ */
+ if (!ohciR3UnlinkGeneralTdInList(pDevIns, TdAddr, pTd, pEd))
+ return false;
+ }
+
+ /*
+ * Only unlink the first TD on error.
+ * See comment in ohciR3RhXferCompleteGeneralURB().
+ */
+ if (pUrb->enmStatus != VUSBSTATUS_OK)
+ break;
+ }
+ }
+
+ return true;
+}
+
+
+/**
+ * Checks that the transport descriptors associated with the URB
+ * hasn't been changed in any way indicating that they may have been canceled.
+ *
+ * This rountine also updates the TD copies contained within the URB.
+ *
+ * @returns true if the URB has been canceled, otherwise false.
+ * @param pDevIns The device instance.
+ * @param pThis The OHCI instance.
+ * @param pUrb The URB in question.
+ * @param pEd The ED pointer (optional).
+ */
+static bool ohciR3HasUrbBeenCanceled(PPDMDEVINS pDevIns, POHCI pThis, PVUSBURB pUrb, PCOHCIED pEd)
+{
+ if (!pUrb)
+ return true;
+
+ /*
+ * Make sure we've got an endpoint descriptor so we can
+ * check for tail TDs.
+ */
+ OHCIED Ed;
+ if (!pEd)
+ {
+ ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed);
+ pEd = &Ed;
+ }
+
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ union
+ {
+ OHCIITD ITd;
+ uint32_t au32[8];
+ } u;
+ if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK)
+ == (pEd->TailP & ED_PTR_MASK))
+ {
+ Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)! [iso]\n",
+ pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr));
+ STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs);
+ return true;
+ }
+ ohciR3ReadITd(pDevIns, pThis, pUrb->paTds[iTd].TdAddr, &u.ITd);
+ if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */
+ || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* bp0 */
+ || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */
+ || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */
+ && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */)
+ || u.au32[4] != pUrb->paTds[iTd].TdCopy[4] /* psw0&1 */
+ || u.au32[5] != pUrb->paTds[iTd].TdCopy[5] /* psw2&3 */
+ || u.au32[6] != pUrb->paTds[iTd].TdCopy[6] /* psw4&5 */
+ || u.au32[7] != pUrb->paTds[iTd].TdCopy[7] /* psw6&7 */
+ )
+ {
+ Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled! [iso]\n",
+ pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(u.ITd), &u.ITd, sizeof(u.ITd), &pUrb->paTds[iTd].TdCopy[0]));
+ STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs);
+ return true;
+ }
+ pUrb->paTds[iTd].TdCopy[2] = u.au32[2];
+ }
+ }
+ else
+ {
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ union
+ {
+ OHCITD Td;
+ uint32_t au32[4];
+ } u;
+ if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK)
+ == (pEd->TailP & ED_PTR_MASK))
+ {
+ Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)!\n",
+ pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr));
+ STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs);
+ return true;
+ }
+ ohciR3ReadTd(pDevIns, pUrb->paTds[iTd].TdAddr, &u.Td);
+ if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */
+ || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* cbp */
+ || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */
+ || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */
+ && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */)
+ )
+ {
+ Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled!\n",
+ pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr));
+ Log2((" %.*Rhxs (cur)\n"
+ "!= %.*Rhxs (copy)\n",
+ sizeof(u.Td), &u.Td, sizeof(u.Td), &pUrb->paTds[iTd].TdCopy[0]));
+ STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs);
+ return true;
+ }
+ pUrb->paTds[iTd].TdCopy[2] = u.au32[2];
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Returns the OHCI_CC_* corresponding to the VUSB status code.
+ *
+ * @returns OHCI_CC_* value.
+ * @param enmStatus The VUSB status code.
+ */
+static uint32_t ohciR3VUsbStatus2OhciStatus(VUSBSTATUS enmStatus)
+{
+ switch (enmStatus)
+ {
+ case VUSBSTATUS_OK: return OHCI_CC_NO_ERROR;
+ case VUSBSTATUS_STALL: return OHCI_CC_STALL;
+ case VUSBSTATUS_CRC: return OHCI_CC_CRC;
+ case VUSBSTATUS_DATA_UNDERRUN: return OHCI_CC_DATA_UNDERRUN;
+ case VUSBSTATUS_DATA_OVERRUN: return OHCI_CC_DATA_OVERRUN;
+ case VUSBSTATUS_DNR: return OHCI_CC_DNR;
+ case VUSBSTATUS_NOT_ACCESSED: return OHCI_CC_NOT_ACCESSED_1;
+ default:
+ Log(("pUrb->enmStatus=%#x!!!\n", enmStatus));
+ return OHCI_CC_DNR;
+ }
+}
+
+
+/**
+ * Lock the given OHCI controller instance.
+ *
+ * @returns nothing.
+ * @param pThisCC The OHCI controller instance to lock, ring-3 edition.
+ */
+DECLINLINE(void) ohciR3Lock(POHCICC pThisCC)
+{
+ RTCritSectEnter(&pThisCC->CritSect);
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /* Clear all caches here to avoid reading stale data from previous lock holders. */
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED);
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD);
+# endif
+}
+
+
+/**
+ * Unlocks the given OHCI controller instance.
+ *
+ * @returns nothing.
+ * @param pThisCC The OHCI controller instance to unlock, ring-3 edition.
+ */
+DECLINLINE(void) ohciR3Unlock(POHCICC pThisCC)
+{
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /*
+ * Clear all caches here to avoid leaving stale data behind (paranoia^2,
+ * already done in ohciR3Lock).
+ */
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheED);
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD);
+# endif
+
+ RTCritSectLeave(&pThisCC->CritSect);
+}
+
+
+/**
+ * Worker for ohciR3RhXferCompletion that handles the completion of
+ * a URB made up of isochronous TDs.
+ *
+ * In general, all URBs should have status OK.
+ */
+static void ohciR3RhXferCompleteIsochronousURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb
+ /*, POHCIED pEd , int cFmAge*/)
+{
+ /*
+ * Copy the data back (if IN operation) and update the TDs.
+ */
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0];
+ const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr;
+ const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1;
+ unsigned R = (pUrb->pHci->u32FrameNo & ITD_HWINFO_SF) - (pITd->HwInfo & ITD_HWINFO_SF);
+ if (R >= 8)
+ R = 0; /* submitted ahead of time. */
+
+ /*
+ * Only one case of TD level condition code is document, so
+ * just set NO_ERROR here to reduce number duplicate code.
+ */
+ pITd->HwInfo &= ~TD_HWINFO_CC;
+ AssertCompile(OHCI_CC_NO_ERROR == 0);
+
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ {
+ /*
+ * Update the frames and copy back the data.
+ * We assume that we don't get incorrect lengths here.
+ */
+ for (unsigned i = 0; i < cFrames; i++)
+ {
+ if ( i < R
+ || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_NOT_ACCESSED)
+ {
+ /* It should already be NotAccessed. */
+ pITd->aPSW[i] |= 0xe000; /* (Don't touch the 12th bit.) */
+ continue;
+ }
+
+ /* Update the PSW (save the offset first in case of a IN). */
+ uint32_t off = pITd->aPSW[i] & ITD_PSW_OFFSET;
+ pITd->aPSW[i] = ohciR3VUsbStatus2OhciStatus(pUrb->aIsocPkts[i - R].enmStatus)
+ >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT);
+
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && ( pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_OK
+ || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_OVERRUN))
+ {
+ /* Set the size. */
+ const unsigned cb = pUrb->aIsocPkts[i - R].cb;
+ pITd->aPSW[i] |= cb & ITD_PSW_SIZE;
+ /* Copy data. */
+ if (cb)
+ {
+ uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i - R].off];
+ if (off + cb > 0x1000)
+ {
+ if (off < 0x1000)
+ {
+ /* both */
+ const unsigned cb0 = 0x1000 - off;
+ ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb0);
+ ohciR3PhysWrite(pDevIns, pITd->BE & ITD_BP0_MASK, pb + cb0, cb - cb0);
+ }
+ else /* only in the 2nd page */
+ ohciR3PhysWrite(pDevIns, (pITd->BE & ITD_BP0_MASK) + (off & ITD_BP0_MASK), pb, cb);
+ }
+ else /* only in the 1st page */
+ ohciR3PhysWrite(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb);
+ Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n"
+ "%.*Rhxd\n",
+ i + R, off, cb, pb, pb - &pUrb->abData[0], cb, pb));
+ //off += cb;
+ }
+ }
+ }
+
+ /*
+ * If the last package ended with a NotAccessed status, set ITD CC
+ * to DataOverrun to indicate scheduling overrun.
+ */
+ if (pUrb->aIsocPkts[pUrb->cIsocPkts - 1].enmStatus == VUSBSTATUS_NOT_ACCESSED)
+ pITd->HwInfo |= OHCI_CC_DATA_OVERRUN;
+ }
+ else
+ {
+ Log(("DevOHCI: Taking untested code path at line %d...\n", __LINE__));
+ /*
+ * Most status codes only applies to the individual packets.
+ *
+ * If we get a URB level error code of this kind, we'll distribute
+ * it to all the packages unless some other status is available for
+ * a package. This is a bit fuzzy, and we will get rid of this code
+ * before long!
+ */
+ //if (pUrb->enmStatus != VUSBSTATUS_DATA_OVERRUN)
+ {
+ const unsigned uCC = ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus)
+ >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT);
+ for (unsigned i = 0; i < cFrames; i++)
+ pITd->aPSW[i] = uCC;
+ }
+ //else
+ // pITd->HwInfo |= ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus);
+ }
+
+ /*
+ * Update the done queue interrupt timer.
+ */
+ uint32_t DoneInt = (pITd->HwInfo & ITD_HWINFO_DI) >> ITD_HWINFO_DI_SHIFT;
+ if ((pITd->HwInfo & TD_HWINFO_CC) != OHCI_CC_NO_ERROR)
+ DoneInt = 0; /* It's cleared on error. */
+ if ( DoneInt != 0x7
+ && DoneInt < pThis->dqic)
+ pThis->dqic = DoneInt;
+
+ /*
+ * Move on to the done list and write back the modified TD.
+ */
+# ifdef LOG_ENABLED
+ if (!pThis->done)
+ pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber;
+# ifdef VBOX_STRICT
+ ohciR3InDoneQueueAdd(pThisCC, ITdAddr);
+# endif
+# endif
+ pITd->NextTD = pThis->done;
+ pThis->done = ITdAddr;
+
+ Log(("%s: ohciR3RhXferCompleteIsochronousURB: ITdAddr=%#010x EdAddr=%#010x SF=%#x (%#x) CC=%#x FC=%d "
+ "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x R=%d\n",
+ pUrb->pszDesc, ITdAddr,
+ pUrb->pHci->EdAddr,
+ pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber,
+ (pITd->HwInfo & ITD_HWINFO_CC) >> ITD_HWINFO_CC_SHIFT,
+ (pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT,
+ pITd->aPSW[0] >> ITD_PSW_CC_SHIFT, pITd->aPSW[0] & ITD_PSW_SIZE,
+ pITd->aPSW[1] >> ITD_PSW_CC_SHIFT, pITd->aPSW[1] & ITD_PSW_SIZE,
+ pITd->aPSW[2] >> ITD_PSW_CC_SHIFT, pITd->aPSW[2] & ITD_PSW_SIZE,
+ pITd->aPSW[3] >> ITD_PSW_CC_SHIFT, pITd->aPSW[3] & ITD_PSW_SIZE,
+ pITd->aPSW[4] >> ITD_PSW_CC_SHIFT, pITd->aPSW[4] & ITD_PSW_SIZE,
+ pITd->aPSW[5] >> ITD_PSW_CC_SHIFT, pITd->aPSW[5] & ITD_PSW_SIZE,
+ pITd->aPSW[6] >> ITD_PSW_CC_SHIFT, pITd->aPSW[6] & ITD_PSW_SIZE,
+ pITd->aPSW[7] >> ITD_PSW_CC_SHIFT, pITd->aPSW[7] & ITD_PSW_SIZE,
+ R));
+ ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "retired");
+ }
+ RT_NOREF(pThisCC);
+}
+
+
+/**
+ * Worker for ohciR3RhXferCompletion that handles the completion of
+ * a URB made up of general TDs.
+ */
+static void ohciR3RhXferCompleteGeneralURB(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, PVUSBURB pUrb,
+ POHCIED pEd, int cFmAge)
+{
+ RT_NOREF(cFmAge);
+
+ /*
+ * Copy the data back (if IN operation) and update the TDs.
+ */
+ unsigned cbLeft = pUrb->cbData;
+ uint8_t *pb = &pUrb->abData[0];
+ for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++)
+ {
+ POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0];
+ const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr;
+
+ /*
+ * Setup a ohci transfer buffer and calc the new cbp value.
+ */
+ OHCIBUF Buf;
+ ohciR3BufInit(&Buf, pTd->cbp, pTd->be);
+ uint32_t NewCbp;
+ if (cbLeft >= Buf.cbTotal)
+ NewCbp = 0;
+ else
+ {
+ /* (len may have changed for short transfers) */
+ Buf.cbTotal = cbLeft;
+ ohciR3BufUpdate(&Buf);
+ Assert(Buf.cVecs >= 1);
+ NewCbp = Buf.aVecs[Buf.cVecs-1].Addr + Buf.aVecs[Buf.cVecs-1].cb;
+ }
+
+ /*
+ * Write back IN buffers.
+ */
+ if ( pUrb->enmDir == VUSBDIRECTION_IN
+ && ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN)
+ && Buf.cbTotal > 0)
+ {
+ Assert(Buf.cVecs > 0);
+
+ /* Be paranoid */
+ if ( Buf.aVecs[0].cb > cbLeft
+ || ( Buf.cVecs > 1
+ && Buf.aVecs[1].cb > (cbLeft - Buf.aVecs[0].cb)))
+ {
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 1);
+ return;
+ }
+
+ ohciR3PhysWrite(pDevIns, Buf.aVecs[0].Addr, pb, Buf.aVecs[0].cb);
+ if (Buf.cVecs > 1)
+ ohciR3PhysWrite(pDevIns, Buf.aVecs[1].Addr, pb + Buf.aVecs[0].cb, Buf.aVecs[1].cb);
+ }
+
+ /* advance the data buffer. */
+ cbLeft -= Buf.cbTotal;
+ pb += Buf.cbTotal;
+
+ /*
+ * Set writeback field.
+ */
+ /* zero out writeback fields for retirement */
+ pTd->hwinfo &= ~TD_HWINFO_CC;
+ /* always update the CurrentBufferPointer; essential for underrun/overrun errors */
+ pTd->cbp = NewCbp;
+
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ {
+ pTd->hwinfo &= ~TD_HWINFO_ERRORS;
+
+ /* update done queue interrupt timer */
+ uint32_t DoneInt = (pTd->hwinfo & TD_HWINFO_DI) >> 21;
+ if ( DoneInt != 0x7
+ && DoneInt < pThis->dqic)
+ pThis->dqic = DoneInt;
+ Log(("%s: ohciR3RhXferCompleteGeneralURB: ED=%#010x TD=%#010x Age=%d enmStatus=%d cbTotal=%#x NewCbp=%#010RX32 dqic=%d\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus, Buf.cbTotal, NewCbp, pThis->dqic));
+ }
+ else
+ {
+ Log(("%s: ohciR3RhXferCompleteGeneralURB: HALTED ED=%#010x TD=%#010x (age %d) pUrb->enmStatus=%d\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus));
+ pEd->HeadP |= ED_HEAD_HALTED;
+ pThis->dqic = 0; /* "If the Transfer Descriptor is being retired with an error,
+ * then the Done Queue Interrupt Counter is cleared as if the
+ * InterruptDelay field were zero."
+ */
+ switch (pUrb->enmStatus)
+ {
+ case VUSBSTATUS_STALL:
+ pTd->hwinfo |= OHCI_CC_STALL;
+ break;
+ case VUSBSTATUS_CRC:
+ pTd->hwinfo |= OHCI_CC_CRC;
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ pTd->hwinfo |= OHCI_CC_DATA_UNDERRUN;
+ break;
+ case VUSBSTATUS_DATA_OVERRUN:
+ pTd->hwinfo |= OHCI_CC_DATA_OVERRUN;
+ break;
+ default: /* what the hell */
+ Log(("pUrb->enmStatus=%#x!!!\n", pUrb->enmStatus));
+ RT_FALL_THRU();
+ case VUSBSTATUS_DNR:
+ pTd->hwinfo |= OHCI_CC_DNR;
+ break;
+ }
+ }
+
+ /*
+ * Move on to the done list and write back the modified TD.
+ */
+# ifdef LOG_ENABLED
+ if (!pThis->done)
+ pThisCC->u32FmDoneQueueTail = pThis->HcFmNumber;
+# ifdef VBOX_STRICT
+ ohciR3InDoneQueueAdd(pThisCC, TdAddr);
+# endif
+# endif
+ pTd->NextTD = pThis->done;
+ pThis->done = TdAddr;
+
+ ohciR3WriteTd(pDevIns, TdAddr, pTd, "retired");
+
+ /*
+ * If we've halted the endpoint, we stop here.
+ * ohciR3UnlinkTds() will make sure we've only unliked the first TD.
+ *
+ * The reason for this is that while we can have more than one TD in a URB, real
+ * OHCI hardware will only deal with one TD at the time and it's therefore incorrect
+ * to retire TDs after the endpoint has been halted. Win2k will crash or enter infinite
+ * kernel loop if we don't behave correctly. (See @bugref{1646}.)
+ */
+ if (pEd->HeadP & ED_HEAD_HALTED)
+ break;
+ }
+ RT_NOREF(pThisCC);
+}
+
+
+/**
+ * Transfer completion callback routine.
+ *
+ * VUSB will call this when a transfer have been completed
+ * in a one or another way.
+ *
+ * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ LogFlow(("%s: ohciR3RhXferCompletion: EdAddr=%#010RX32 cTds=%d TdAddr0=%#010RX32\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr));
+
+ ohciR3Lock(pThisCC);
+
+ int cFmAge = ohciR3InFlightRemoveUrb(pThis, pThisCC, pUrb);
+
+ /* Do nothing requiring memory access if the HC encountered an unrecoverable error. */
+ if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR))
+ {
+ pThis->fIdle = false; /* Mark as active */
+
+ /* get the current end point descriptor. */
+ OHCIED Ed;
+ ohciR3ReadEd(pDevIns, pUrb->pHci->EdAddr, &Ed);
+
+ /*
+ * Check that the URB hasn't been canceled and then try unlink the TDs.
+ *
+ * We drop the URB if the ED is marked halted/skip ASSUMING that this
+ * means the HCD has canceled the URB.
+ *
+ * If we succeed here (i.e. not dropping the URB), the TdCopy members will
+ * be updated but not yet written. We will delay the writing till we're done
+ * with the data copying, buffer pointer advancing and error handling.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_UNDO)
+ {
+ /* Leave the TD alone - the HCD doesn't want us talking to the device. */
+ Log(("%s: ohciR3RhXferCompletion: CANCELED {ED=%#010x cTds=%d TD0=%#010x age %d}\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge));
+ STAM_COUNTER_INC(&pThis->StatDroppedUrbs);
+ ohciR3Unlock(pThisCC);
+ return;
+ }
+ bool fHasBeenCanceled = false;
+ if ( (Ed.HeadP & ED_HEAD_HALTED)
+ || (Ed.hwinfo & ED_HWINFO_SKIP)
+ || cFmAge < 0
+ || (fHasBeenCanceled = ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, &Ed))
+ || !ohciR3UnlinkTds(pDevIns, pThis, pUrb, &Ed)
+ )
+ {
+ Log(("%s: ohciR3RhXferCompletion: DROPPED {ED=%#010x cTds=%d TD0=%#010x age %d} because:%s%s%s%s%s!!!\n",
+ pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge,
+ (Ed.HeadP & ED_HEAD_HALTED) ? " ep halted" : "",
+ (Ed.hwinfo & ED_HWINFO_SKIP) ? " ep skip" : "",
+ (Ed.HeadP & ED_PTR_MASK) != pUrb->paTds[0].TdAddr ? " ep head-changed" : "",
+ cFmAge < 0 ? " td not-in-flight" : "",
+ fHasBeenCanceled ? " td canceled" : ""));
+ NOREF(fHasBeenCanceled);
+ STAM_COUNTER_INC(&pThis->StatDroppedUrbs);
+ ohciR3Unlock(pThisCC);
+ return;
+ }
+
+ /*
+ * Complete the TD updating and write the back.
+ * When appropriate also copy data back to the guest memory.
+ */
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ ohciR3RhXferCompleteIsochronousURB(pDevIns, pThis, pThisCC, pUrb /*, &Ed , cFmAge*/);
+ else
+ ohciR3RhXferCompleteGeneralURB(pDevIns, pThis, pThisCC, pUrb, &Ed, cFmAge);
+
+ /* finally write back the endpoint descriptor. */
+ ohciR3WriteEd(pDevIns, pUrb->pHci->EdAddr, &Ed);
+ }
+
+ ohciR3Unlock(pThisCC);
+}
+
+
+/**
+ * Handle transfer errors.
+ *
+ * VUSB calls this when a transfer attempt failed. This function will respond
+ * indicating whether to retry or complete the URB with failure.
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be retried.
+ * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+
+ /*
+ * Isochronous URBs can't be retried.
+ */
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ return true;
+
+ /*
+ * Don't retry on stall.
+ */
+ if (pUrb->enmStatus == VUSBSTATUS_STALL)
+ {
+ Log2(("%s: ohciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc));
+ return true;
+ }
+
+ ohciR3Lock(pThisCC);
+ bool fRetire = false;
+ /*
+ * Check if the TDs still are valid.
+ * This will make sure the TdCopy is up to date.
+ */
+ const uint32_t TdAddr = pUrb->paTds[0].TdAddr;
+/** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */
+ if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, NULL))
+ {
+ Log(("%s: ohciR3RhXferError: TdAddr0=%#x canceled!\n", pUrb->pszDesc, TdAddr));
+ fRetire = true;
+ }
+ else
+ {
+ /*
+ * Get and update the error counter.
+ */
+ POHCITD pTd = (POHCITD)&pUrb->paTds[0].TdCopy[0];
+ unsigned cErrs = (pTd->hwinfo & TD_HWINFO_ERRORS) >> TD_ERRORS_SHIFT;
+ pTd->hwinfo &= ~TD_HWINFO_ERRORS;
+ cErrs++;
+ pTd->hwinfo |= (cErrs % TD_ERRORS_MAX) << TD_ERRORS_SHIFT;
+ ohciR3WriteTd(pDevIns, TdAddr, pTd, "ohciR3RhXferError");
+
+ if (cErrs >= TD_ERRORS_MAX - 1)
+ {
+ Log2(("%s: ohciR3RhXferError: too many errors, giving up!\n", pUrb->pszDesc));
+ fRetire = true;
+ }
+ else
+ Log2(("%s: ohciR3RhXferError: cErrs=%d: retrying...\n", pUrb->pszDesc, cErrs));
+ }
+
+ ohciR3Unlock(pThisCC);
+ return fRetire;
+}
+
+
+/**
+ * Service a general transport descriptor.
+ */
+static bool ohciR3ServiceTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType,
+ PCOHCIED pEd, uint32_t EdAddr, uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName)
+{
+ RT_NOREF(pszListName);
+
+ /*
+ * Read the TD and setup the buffer data.
+ */
+ OHCITD Td;
+ ohciR3ReadTd(pDevIns, TdAddr, &Td);
+ OHCIBUF Buf;
+ ohciR3BufInit(&Buf, Td.cbp, Td.be);
+
+ *pNextTdAddr = Td.NextTD & ED_PTR_MASK;
+
+ /*
+ * Determine the direction.
+ */
+ VUSBDIRECTION enmDir;
+ switch (pEd->hwinfo & ED_HWINFO_DIR)
+ {
+ case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break;
+ case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break;
+ default:
+ switch (Td.hwinfo & TD_HWINFO_DIR)
+ {
+ case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break;
+ case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break;
+ case 0: enmDir = VUSBDIRECTION_SETUP; break;
+ default:
+ Log(("ohciR3ServiceTd: Invalid direction!!!! Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Td.hwinfo, pEd->hwinfo));
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 2);
+ return false;
+ }
+ break;
+ }
+
+ pThis->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize a new URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID,
+ enmType, enmDir, Buf.cbTotal, 1, NULL);
+ if (!pUrb)
+ return false; /* retry later... */
+
+ pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT;
+ pUrb->fShortNotOk = !(Td.hwinfo & TD_HWINFO_ROUNDING);
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->EdAddr = EdAddr;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->cTds = 1;
+ pUrb->paTds[0].TdAddr = TdAddr;
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(Td));
+ memcpy(pUrb->paTds[0].TdCopy, &Td, sizeof(Td));
+
+ /* copy data if out bound transfer. */
+ pUrb->cbData = Buf.cbTotal;
+ if ( Buf.cbTotal
+ && Buf.cVecs > 0
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ /* Be paranoid. */
+ if ( Buf.aVecs[0].cb > pUrb->cbData
+ || ( Buf.cVecs > 1
+ && Buf.aVecs[1].cb > (pUrb->cbData - Buf.aVecs[0].cb)))
+ {
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 3);
+ VUSBIRhFreeUrb(pThisCC->RootHub.pIRhConn, pUrb);
+ return false;
+ }
+
+ ohciR3PhysRead(pDevIns, Buf.aVecs[0].Addr, pUrb->abData, Buf.aVecs[0].cb);
+ if (Buf.cVecs > 1)
+ ohciR3PhysRead(pDevIns, Buf.aVecs[1].Addr, &pUrb->abData[Buf.aVecs[0].cb], Buf.aVecs[1].cb);
+ }
+
+ /*
+ * Submit the URB.
+ */
+ ohciR3InFlightAdd(pThis, pThisCC, TdAddr, pUrb);
+ Log(("%s: ohciR3ServiceTd: submitting TdAddr=%#010x EdAddr=%#010x cbData=%#x\n",
+ pUrb->pszDesc, TdAddr, EdAddr, pUrb->cbData));
+
+ ohciR3Unlock(pThisCC);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ ohciR3Lock(pThisCC);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ Log(("ohciR3ServiceTd: failed submitting TdAddr=%#010x EdAddr=%#010x pUrb=%p!!\n",
+ TdAddr, EdAddr, pUrb));
+ ohciR3InFlightRemove(pThis, pThisCC, TdAddr);
+ return false;
+}
+
+
+/**
+ * Service a the head TD of an endpoint.
+ */
+static bool ohciR3ServiceHeadTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType,
+ PCOHCIED pEd, uint32_t EdAddr, const char *pszListName)
+{
+ /*
+ * Read the TD, after first checking if it's already in-flight.
+ */
+ uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK;
+ if (ohciR3IsTdInFlight(pThisCC, TdAddr))
+ return false;
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueCheck(pThisCC, TdAddr);
+# endif
+ return ohciR3ServiceTd(pDevIns, pThis, pThisCC, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName);
+}
+
+
+/**
+ * Service one or more general transport descriptors (bulk or interrupt).
+ */
+static bool ohciR3ServiceTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr,
+ uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName)
+{
+ RT_NOREF(pszListName);
+
+ /*
+ * Read the TDs involved in this URB.
+ */
+ struct OHCITDENTRY
+ {
+ /** The TD. */
+ OHCITD Td;
+ /** The associated OHCI buffer tracker. */
+ OHCIBUF Buf;
+ /** The TD address. */
+ uint32_t TdAddr;
+ /** Pointer to the next element in the chain (stack). */
+ struct OHCITDENTRY *pNext;
+ } Head;
+
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD);
+# endif
+
+ /* read the head */
+ ohciR3ReadTd(pDevIns, TdAddr, &Head.Td);
+ ohciR3BufInit(&Head.Buf, Head.Td.cbp, Head.Td.be);
+ Head.TdAddr = TdAddr;
+ Head.pNext = NULL;
+
+ /* combine with more TDs. */
+ struct OHCITDENTRY *pTail = &Head;
+ unsigned cbTotal = pTail->Buf.cbTotal;
+ unsigned cTds = 1;
+ while ( (pTail->Buf.cbTotal == 0x1000 || pTail->Buf.cbTotal == 0x2000)
+ && !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING) /* This isn't right for *BSD, but let's not . */
+ && (pTail->Td.NextTD & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK)
+ && cTds < 128)
+ {
+ struct OHCITDENTRY *pCur = (struct OHCITDENTRY *)alloca(sizeof(*pCur));
+
+ pCur->pNext = NULL;
+ pCur->TdAddr = pTail->Td.NextTD & ED_PTR_MASK;
+ ohciR3ReadTd(pDevIns, pCur->TdAddr, &pCur->Td);
+ ohciR3BufInit(&pCur->Buf, pCur->Td.cbp, pCur->Td.be);
+
+ /* Don't combine if the direction doesn't match up. There can't actually be
+ * a mismatch for bulk/interrupt EPs unless the guest is buggy.
+ */
+ if ( (pCur->Td.hwinfo & (TD_HWINFO_DIR))
+ != (Head.Td.hwinfo & (TD_HWINFO_DIR)))
+ break;
+
+ pTail->pNext = pCur;
+ pTail = pCur;
+ cbTotal += pCur->Buf.cbTotal;
+ cTds++;
+ }
+
+ /* calc next TD address */
+ *pNextTdAddr = pTail->Td.NextTD & ED_PTR_MASK;
+
+ /*
+ * Determine the direction.
+ */
+ VUSBDIRECTION enmDir;
+ switch (pEd->hwinfo & ED_HWINFO_DIR)
+ {
+ case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break;
+ case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break;
+ default:
+ Log(("ohciR3ServiceTdMultiple: WARNING! Ed.hwdinfo=%#x bulk or interrupt EP shouldn't rely on the TD for direction...\n", pEd->hwinfo));
+ switch (Head.Td.hwinfo & TD_HWINFO_DIR)
+ {
+ case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break;
+ case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break;
+ default:
+ Log(("ohciR3ServiceTdMultiple: Invalid direction!!!! Head.Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Head.Td.hwinfo, pEd->hwinfo));
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 4);
+ return false;
+ }
+ break;
+ }
+
+ pThis->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize a new URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID,
+ enmType, enmDir, cbTotal, cTds, "ohciR3ServiceTdMultiple");
+ if (!pUrb)
+ /* retry later... */
+ return false;
+ Assert(pUrb->cbData == cbTotal);
+
+ pUrb->enmType = enmType;
+ pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT;
+ pUrb->enmDir = enmDir;
+ pUrb->fShortNotOk = !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING);
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->cTds = cTds;
+ pUrb->pHci->EdAddr = EdAddr;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+
+ /* Copy data and TD information. */
+ unsigned iTd = 0;
+ uint8_t *pb = &pUrb->abData[0];
+ for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++)
+ {
+ /* data */
+ if ( cbTotal
+ && enmDir != VUSBDIRECTION_IN
+ && pCur->Buf.cVecs > 0)
+ {
+ ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[0].Addr, pb, pCur->Buf.aVecs[0].cb);
+ if (pCur->Buf.cVecs > 1)
+ ohciR3PhysRead(pDevIns, pCur->Buf.aVecs[1].Addr, pb + pCur->Buf.aVecs[0].cb, pCur->Buf.aVecs[1].cb);
+ }
+ pb += pCur->Buf.cbTotal;
+
+ /* TD info */
+ pUrb->paTds[iTd].TdAddr = pCur->TdAddr;
+ AssertCompile(sizeof(pUrb->paTds[iTd].TdCopy) >= sizeof(pCur->Td));
+ memcpy(pUrb->paTds[iTd].TdCopy, &pCur->Td, sizeof(pCur->Td));
+ }
+
+ /*
+ * Submit the URB.
+ */
+ ohciR3InFlightAddUrb(pThis, pThisCC, pUrb);
+ Log(("%s: ohciR3ServiceTdMultiple: submitting cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x\n",
+ pUrb->pszDesc, pUrb->cbData, EdAddr, cTds, TdAddr));
+ ohciR3Unlock(pThisCC);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ ohciR3Lock(pThisCC);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ Log(("ohciR3ServiceTdMultiple: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x - rc=%Rrc\n",
+ pUrb, cbTotal, EdAddr, cTds, TdAddr, rc));
+ /* NB: We cannot call ohciR3InFlightRemoveUrb() because the URB is already gone! */
+ for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++)
+ ohciR3InFlightRemove(pThis, pThisCC, pCur->TdAddr);
+ return false;
+}
+
+
+/**
+ * Service the head TD of an endpoint.
+ */
+static bool ohciR3ServiceHeadTdMultiple(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, VUSBXFERTYPE enmType,
+ PCOHCIED pEd, uint32_t EdAddr, const char *pszListName)
+{
+ /*
+ * First, check that it's not already in-flight.
+ */
+ uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK;
+ if (ohciR3IsTdInFlight(pThisCC, TdAddr))
+ return false;
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueCheck(pThisCC, TdAddr);
+# endif
+ return ohciR3ServiceTdMultiple(pDevIns, pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName);
+}
+
+
+/**
+ * A worker for ohciR3ServiceIsochronousEndpoint which unlinks a ITD
+ * that belongs to the past.
+ */
+static bool ohciR3ServiceIsochronousTdUnlink(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIITD pITd, uint32_t ITdAddr,
+ uint32_t ITdAddrPrev, PVUSBURB pUrb, POHCIED pEd, uint32_t EdAddr)
+{
+ LogFlow(("%s%sohciR3ServiceIsochronousTdUnlink: Unlinking ITD: ITdAddr=%#010x EdAddr=%#010x ITdAddrPrev=%#010x\n",
+ pUrb ? pUrb->pszDesc : "", pUrb ? ": " : "", ITdAddr, EdAddr, ITdAddrPrev));
+
+ /*
+ * Do the unlinking.
+ */
+ const uint32_t ITdAddrNext = pITd->NextTD & ED_PTR_MASK;
+ if (ITdAddrPrev)
+ {
+ /* Get validate the previous TD */
+ int iInFlightPrev = ohciR3InFlightFind(pThisCC, ITdAddrPrev);
+ AssertMsgReturn(iInFlightPrev >= 0, ("ITdAddr=%#RX32\n", ITdAddrPrev), false);
+ PVUSBURB pUrbPrev = pThisCC->aInFlight[iInFlightPrev].pUrb;
+ if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrbPrev, pEd)) /* ensures the copy is correct. */
+ return false;
+
+ /* Update the copy and write it back. */
+ POHCIITD pITdPrev = ((POHCIITD)pUrbPrev->paTds[0].TdCopy);
+ pITdPrev->NextTD = (pITdPrev->NextTD & ~ED_PTR_MASK) | ITdAddrNext;
+ ohciR3WriteITd(pDevIns, pThis, ITdAddrPrev, pITdPrev, "ohciR3ServiceIsochronousEndpoint");
+ }
+ else
+ {
+ /* It's the head node. update the copy from the caller and write it back. */
+ pEd->HeadP = (pEd->HeadP & ~ED_PTR_MASK) | ITdAddrNext;
+ ohciR3WriteEd(pDevIns, EdAddr, pEd);
+ }
+
+ /*
+ * If it's in flight, just mark the URB as unlinked (there is only one ITD per URB atm).
+ * Otherwise, retire it to the done queue with an error and cause a done line interrupt (?).
+ */
+ if (pUrb)
+ {
+ pUrb->pHci->fUnlinked = true;
+ if (ohciR3HasUrbBeenCanceled(pDevIns, pThis, pUrb, pEd)) /* ensures the copy is correct (paranoia). */
+ return false;
+
+ POHCIITD pITdCopy = ((POHCIITD)pUrb->paTds[0].TdCopy);
+ pITd->NextTD = pITdCopy->NextTD &= ~ED_PTR_MASK;
+ }
+ else
+ {
+ pITd->HwInfo &= ~ITD_HWINFO_CC;
+ pITd->HwInfo |= OHCI_CC_DATA_OVERRUN;
+
+ pITd->NextTD = pThis->done;
+ pThis->done = ITdAddr;
+
+ pThis->dqic = 0;
+ }
+
+ ohciR3WriteITd(pDevIns, pThis, ITdAddr, pITd, "ohciR3ServiceIsochronousTdUnlink");
+ return true;
+}
+
+
+/**
+ * A worker for ohciR3ServiceIsochronousEndpoint which submits the specified TD.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ * @param pDevIns The device instance.
+ * @param pThis The OHCI controller instance data, shared edition.
+ * @param pThisCC The OHCI controller instance data, ring-3 edition.
+ * @param pITd The transfer descriptor to service.
+ * @param ITdAddr The address of the transfer descriptor in gues memory.
+ * @param R The start packet (frame) relative to the start of frame in HwInfo.
+ * @param pEd The OHCI endpoint descriptor.
+ * @param EdAddr The endpoint descriptor address in guest memory.
+ */
+static bool ohciR3ServiceIsochronousTd(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC,
+ POHCIITD pITd, uint32_t ITdAddr, const unsigned R, PCOHCIED pEd, uint32_t EdAddr)
+{
+ /*
+ * Determine the endpoint direction.
+ */
+ VUSBDIRECTION enmDir;
+ switch (pEd->hwinfo & ED_HWINFO_DIR)
+ {
+ case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break;
+ case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break;
+ default:
+ Log(("ohciR3ServiceIsochronousTd: Invalid direction!!!! Ed.hwdinfo=%#x\n", pEd->hwinfo));
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 5);
+ return false;
+ }
+
+ /*
+ * Extract the packet sizes and calc the total URB size.
+ */
+ struct
+ {
+ uint16_t cb;
+ uint16_t off;
+ } aPkts[ITD_NUM_PSW];
+
+ /* first entry (R) */
+ uint32_t cbTotal = 0;
+ if (((uint32_t)pITd->aPSW[R] >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT))
+ {
+ Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, R, pITd->aPSW[R] >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/
+ pThis->intr_status |= OHCI_INTR_UNRECOVERABLE_ERROR;
+ return false;
+ }
+ uint16_t offPrev = aPkts[0].off = (pITd->aPSW[R] & ITD_PSW_OFFSET);
+
+ /* R+1..cFrames */
+ const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1;
+ for (unsigned iR = R + 1; iR < cFrames; iR++)
+ {
+ const uint16_t PSW = pITd->aPSW[iR];
+ const uint16_t off = aPkts[iR - R].off = (PSW & ITD_PSW_OFFSET);
+ cbTotal += aPkts[iR - R - 1].cb = off - offPrev;
+ if (off < offPrev)
+ {
+ Log(("ITdAddr=%RX32 PSW%d.offset=%#x < offPrev=%#x!\n", ITdAddr, iR, off, offPrev)); /* => Unrecoverable Error*/
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 6);
+ return false;
+ }
+ if (((uint32_t)PSW >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT))
+ {
+ Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, iR, PSW >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 7);
+ return false;
+ }
+ offPrev = off;
+ }
+
+ /* calc offEnd and figure out the size of the last packet. */
+ const uint32_t offEnd = (pITd->BE & 0xfff)
+ + (((pITd->BE & ITD_BP0_MASK) != (pITd->BP0 & ITD_BP0_MASK)) << 12)
+ + 1 /* BE is inclusive */;
+ if (offEnd < offPrev)
+ {
+ Log(("ITdAddr=%RX32 offEnd=%#x < offPrev=%#x!\n", ITdAddr, offEnd, offPrev)); /* => Unrecoverable Error*/
+ ohciR3RaiseUnrecoverableError(pDevIns, pThis, 8);
+ return false;
+ }
+ cbTotal += aPkts[cFrames - 1 - R].cb = offEnd - offPrev;
+ Assert(cbTotal <= 0x2000);
+
+ pThis->fIdle = false; /* Mark as active */
+
+ /*
+ * Allocate and initialize a new URB.
+ */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pThisCC->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, VUSB_DEVICE_PORT_INVALID,
+ VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL);
+ if (!pUrb)
+ /* retry later... */
+ return false;
+
+ pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT;
+ pUrb->fShortNotOk = false;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->EdAddr = EdAddr;
+ pUrb->pHci->cTds = 1;
+ pUrb->pHci->fUnlinked = false;
+ pUrb->pHci->u32FrameNo = pThis->HcFmNumber;
+ pUrb->paTds[0].TdAddr = ITdAddr;
+ AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pITd));
+ memcpy(pUrb->paTds[0].TdCopy, pITd, sizeof(*pITd));
+# if 0 /* color the data */
+ memset(pUrb->abData, 0xfe, cbTotal);
+# endif
+
+ /* copy the data */
+ if ( cbTotal
+ && enmDir != VUSBDIRECTION_IN)
+ {
+ const uint32_t off0 = pITd->aPSW[R] & ITD_PSW_OFFSET;
+ if (off0 < 0x1000)
+ {
+ if (offEnd > 0x1000)
+ {
+ /* both pages. */
+ const unsigned cb0 = 0x1000 - off0;
+ ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, &pUrb->abData[0], cb0);
+ ohciR3PhysRead(pDevIns, pITd->BE & ITD_BP0_MASK, &pUrb->abData[cb0], offEnd & 0xfff);
+ }
+ else /* a portion of the 1st page. */
+ ohciR3PhysRead(pDevIns, (pITd->BP0 & ITD_BP0_MASK) + off0, pUrb->abData, offEnd - off0);
+ }
+ else /* a portion of the 2nd page. */
+ ohciR3PhysRead(pDevIns, (pITd->BE & UINT32_C(0xfffff000)) + (off0 & 0xfff), pUrb->abData, cbTotal);
+ }
+
+ /* setup the packets */
+ pUrb->cIsocPkts = cFrames - R;
+ unsigned off = 0;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED;
+ pUrb->aIsocPkts[i].off = off;
+ off += pUrb->aIsocPkts[i].cb = aPkts[i].cb;
+ }
+ Assert(off == cbTotal);
+
+ /*
+ * Submit the URB.
+ */
+ ohciR3InFlightAdd(pThis, pThisCC, ITdAddr, pUrb);
+ Log(("%s: ohciR3ServiceIsochronousTd: submitting cbData=%#x cIsocPkts=%d EdAddr=%#010x TdAddr=%#010x SF=%#x (%#x)\n",
+ pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, EdAddr, ITdAddr, pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber));
+ ohciR3Unlock(pThisCC);
+ int rc = VUSBIRhSubmitUrb(pThisCC->RootHub.pIRhConn, pUrb, &pThisCC->RootHub.Led);
+ ohciR3Lock(pThisCC);
+ if (RT_SUCCESS(rc))
+ return true;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */
+ Log(("ohciR3ServiceIsochronousTd: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d ITdAddr0=%#010x - rc=%Rrc\n",
+ pUrb, cbTotal, EdAddr, 1, ITdAddr, rc));
+ ohciR3InFlightRemove(pThis, pThisCC, ITdAddr);
+ return false;
+}
+
+
+/**
+ * Service an isochronous endpoint.
+ */
+static void ohciR3ServiceIsochronousEndpoint(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, POHCIED pEd, uint32_t EdAddr)
+{
+ /*
+ * We currently process this as if the guest follows the interrupt end point chaining
+ * hierarchy described in the documenation. This means that for an isochronous endpoint
+ * with a 1 ms interval we expect to find in-flight TDs at the head of the list. We will
+ * skip over all in-flight TDs which timeframe has been exceed. Those which aren't in
+ * flight but which are too late will be retired (possibly out of order, but, we don't
+ * care right now).
+ *
+ * When we reach a TD which still has a buffer which is due for take off, we will
+ * stop iterating TDs. If it's in-flight, there isn't anything to be done. Otherwise
+ * we will push it onto the runway for immediate take off. In this process we
+ * might have to complete buffers which didn't make it on time, something which
+ * complicates the kind of status info we need to keep around for the TD.
+ *
+ * Note: We're currently not making any attempt at reassembling ITDs into URBs.
+ * However, this will become necessary because of EMT scheduling and guest
+ * like linux using one TD for each frame (simple but inefficient for us).
+ */
+ OHCIITD ITd;
+ uint32_t ITdAddr = pEd->HeadP & ED_PTR_MASK;
+ uint32_t ITdAddrPrev = 0;
+ uint32_t u32NextFrame = UINT32_MAX;
+ const uint16_t u16CurFrame = pThis->HcFmNumber;
+ for (;;)
+ {
+ /* check for end-of-chain. */
+ if ( ITdAddr == (pEd->TailP & ED_PTR_MASK)
+ || !ITdAddr)
+ break;
+
+ /*
+ * If isochronous endpoints are around, don't slow down the timer. Getting the timing right
+ * is difficult enough as it is.
+ */
+ pThis->fIdle = false;
+
+ /*
+ * Read the current ITD and check what we're supposed to do about it.
+ */
+ ohciR3ReadITd(pDevIns, pThis, ITdAddr, &ITd);
+ const uint32_t ITdAddrNext = ITd.NextTD & ED_PTR_MASK;
+ const int16_t R = u16CurFrame - (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF); /* 4.3.2.3 */
+ const int16_t cFrames = ((ITd.HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1;
+
+ if (R < cFrames)
+ {
+ /*
+ * It's inside the current or a future launch window.
+ *
+ * We will try maximize the TD in flight here to deal with EMT scheduling
+ * issues and similar stuff which will screw up the time. So, we will only
+ * stop submitting TD when we reach a gap (in time) or end of the list.
+ */
+ if ( R < 0 /* (a future frame) */
+ && (uint16_t)u32NextFrame != (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF))
+ break;
+ if (ohciR3InFlightFind(pThisCC, ITdAddr) < 0)
+ if (!ohciR3ServiceIsochronousTd(pDevIns, pThis, pThisCC, &ITd, ITdAddr, R < 0 ? 0 : R, pEd, EdAddr))
+ break;
+
+ ITdAddrPrev = ITdAddr;
+ }
+ else
+ {
+# if 1
+ /*
+ * Ok, the launch window for this TD has passed.
+ * If it's not in flight it should be retired with a DataOverrun status (TD).
+ *
+ * Don't remove in-flight TDs before they complete.
+ * Windows will, upon the completion of another ITD it seems, check for if
+ * any other TDs has been unlinked. If we unlink them before they really
+ * complete all the packet status codes will be NotAccessed and Windows
+ * will fail the URB with status USBD_STATUS_ISOCH_REQUEST_FAILED.
+ *
+ * I don't know if unlinking TDs out of order could cause similar problems,
+ * time will show.
+ */
+ int iInFlight = ohciR3InFlightFind(pThisCC, ITdAddr);
+ if (iInFlight >= 0)
+ ITdAddrPrev = ITdAddr;
+ else if (!ohciR3ServiceIsochronousTdUnlink(pDevIns, pThis, pThisCC, &ITd, ITdAddr, ITdAddrPrev, NULL, pEd, EdAddr))
+ {
+ Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n"));
+ break;
+ }
+# else /* BAD IDEA: */
+ /*
+ * Ok, the launch window for this TD has passed.
+ * If it's not in flight it should be retired with a DataOverrun status (TD).
+ *
+ * If it's in flight we will try unlink it from the list prematurely to
+ * help the guest to move on and shorten the list we have to walk. We currently
+ * are successful with the first URB but then it goes too slowly...
+ */
+ int iInFlight = ohciR3InFlightFind(pThis, ITdAddr);
+ if (!ohciR3ServiceIsochronousTdUnlink(pThis, &ITd, ITdAddr, ITdAddrPrev,
+ iInFlight < 0 ? NULL : pThis->aInFlight[iInFlight].pUrb,
+ pEd, EdAddr))
+ {
+ Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n"));
+ break;
+ }
+# endif
+ }
+
+ /* advance to the next ITD */
+ ITdAddr = ITdAddrNext;
+ u32NextFrame = (ITd.HwInfo & ITD_HWINFO_SF) + cFrames;
+ }
+}
+
+
+/**
+ * Checks if a endpoints has TDs queued and is ready to have them processed.
+ *
+ * @returns true if it's ok to process TDs.
+ * @param pEd The endpoint data.
+ */
+DECLINLINE(bool) ohciR3IsEdReady(PCOHCIED pEd)
+{
+ return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK)
+ && !(pEd->HeadP & ED_HEAD_HALTED)
+ && !(pEd->hwinfo & ED_HWINFO_SKIP);
+}
+
+
+/**
+ * Checks if an endpoint has TDs queued (not necessarily ready to have them processed).
+ *
+ * @returns true if endpoint may have TDs queued.
+ * @param pEd The endpoint data.
+ */
+DECLINLINE(bool) ohciR3IsEdPresent(PCOHCIED pEd)
+{
+ return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK)
+ && !(pEd->HeadP & ED_HEAD_HALTED);
+}
+
+
+/**
+ * Services the bulk list.
+ *
+ * On the bulk list we must reassemble URBs from multiple TDs using heuristics
+ * derived from USB tracing done in the guests and guest source code (when available).
+ */
+static void ohciR3ServiceBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true);
+ if (pThis->bulk_cur)
+ Log(("ohciR3ServiceBulkList: bulk_cur=%#010x before listprocessing!!! HCD have positioned us!!!\n", pThis->bulk_cur));
+# endif
+
+ /*
+ * ", HC will start processing the Bulk list and will set BF [BulkListFilled] to 0"
+ * - We've simplified and are always starting at the head of the list and working
+ * our way thru to the end each time.
+ */
+ pThis->status &= ~OHCI_STATUS_BLF;
+ pThis->fBulkNeedsCleaning = false;
+ pThis->bulk_cur = 0;
+
+ uint32_t EdAddr = pThis->bulk_head;
+ uint32_t cIterations = 256;
+ while (EdAddr
+ && (pThis->ctl & OHCI_CTL_BLE)
+ && (cIterations-- > 0))
+ {
+ OHCIED Ed;
+
+ /* Bail if previous processing ended up in the unrecoverable error state. */
+ if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)
+ break;
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed);
+ Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */
+ if (ohciR3IsEdReady(&Ed))
+ {
+ pThis->status |= OHCI_STATUS_BLF;
+ pThis->fBulkNeedsCleaning = true;
+
+# if 1
+ /*
+
+ * After we figured out that all the TDs submitted for dealing with MSD
+ * read/write data really makes up on single URB, and that we must
+ * reassemble these TDs into an URB before submitting it, there is no
+ * longer any need for servicing anything other than the head *URB*
+ * on a bulk endpoint.
+ */
+ ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_BULK, &Ed, EdAddr, "Bulk");
+# else
+ /*
+ * This alternative code was used before we started reassembling URBs from
+ * multiple TDs. We keep it handy for debugging.
+ */
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ if (!ohciR3IsTdInFlight(pThis, TdAddr))
+ {
+ do
+ {
+ if (!ohciR3ServiceTdMultiple(pThis, VUSBXFERTYPE_BULK, &Ed, EdAddr, TdAddr, &TdAddr, "Bulk"))
+ {
+ LogFlow(("ohciR3ServiceBulkList: ohciR3ServiceTdMultiple -> false\n"));
+ break;
+ }
+ if ( (TdAddr & ED_PTR_MASK) == (Ed.TailP & ED_PTR_MASK)
+ || !TdAddr /* paranoia */)
+ {
+ LogFlow(("ohciR3ServiceBulkList: TdAddr=%#010RX32 Ed.TailP=%#010RX32\n", TdAddr, Ed.TailP));
+ break;
+ }
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */
+ } while (ohciR3IsEdReady(&Ed));
+ }
+# endif
+ }
+ else
+ {
+ if (Ed.hwinfo & ED_HWINFO_SKIP)
+ {
+ LogFlow(("ohciR3ServiceBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP));
+ /* If the ED is in 'skip' state, no transactions on it are allowed and we must
+ * cancel outstanding URBs, if any.
+ */
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr);
+ if (pUrb)
+ pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb);
+ }
+ }
+
+ /* Trivial loop detection. */
+ if (EdAddr == (Ed.NextED & ED_PTR_MASK))
+ break;
+ /* Proceed to the next endpoint. */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk after ", true);
+# endif
+}
+
+
+/**
+ * Abort outstanding transfers on the bulk list.
+ *
+ * If the guest disabled bulk list processing, we must abort any outstanding transfers
+ * (that is, cancel in-flight URBs associated with the list). This is required because
+ * there may be outstanding read URBs that will never get a response from the device
+ * and would block further communication.
+ */
+static void ohciR3UndoBulkList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogBulkEPs)
+ ohciR3DumpEdList(pDevIns, pThisCC, pThis->bulk_head, "Bulk before", true);
+ if (pThis->bulk_cur)
+ Log(("ohciR3UndoBulkList: bulk_cur=%#010x before list processing!!! HCD has positioned us!!!\n", pThis->bulk_cur));
+# endif
+
+ /* This flag follows OHCI_STATUS_BLF, but BLF doesn't change when list processing is disabled. */
+ pThis->fBulkNeedsCleaning = false;
+
+ uint32_t EdAddr = pThis->bulk_head;
+ uint32_t cIterations = 256;
+ while (EdAddr
+ && (cIterations-- > 0))
+ {
+ OHCIED Ed;
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed);
+ Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */
+ if (ohciR3IsEdPresent(&Ed))
+ {
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ if (ohciR3IsTdInFlight(pThisCC, TdAddr))
+ {
+ LogFlow(("ohciR3UndoBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 UNDO\n", EdAddr, Ed.TailP));
+ PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr);
+ if (pUrb)
+ pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb);
+ }
+ }
+
+ /* Trivial loop detection. */
+ if (EdAddr == (Ed.NextED & ED_PTR_MASK))
+ break;
+ /* Proceed to the next endpoint. */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+}
+
+
+/**
+ * Services the control list.
+ *
+ * The control list has complex URB assembling, but that's taken
+ * care of at VUSB level (unlike the other transfer types).
+ */
+static void ohciR3ServiceCtrlList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+# ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl before", true);
+ if (pThis->ctrl_cur)
+ Log(("ohciR3ServiceCtrlList: ctrl_cur=%010x before list processing!!! HCD have positioned us!!!\n", pThis->ctrl_cur));
+# endif
+
+ /*
+ * ", HC will start processing the list and will set ControlListFilled to 0"
+ * - We've simplified and are always starting at the head of the list and working
+ * our way thru to the end each time.
+ */
+ pThis->status &= ~OHCI_STATUS_CLF;
+ pThis->ctrl_cur = 0;
+
+ uint32_t EdAddr = pThis->ctrl_head;
+ uint32_t cIterations = 256;
+ while ( EdAddr
+ && (pThis->ctl & OHCI_CTL_CLE)
+ && (cIterations-- > 0))
+ {
+ OHCIED Ed;
+
+ /* Bail if previous processing ended up in the unrecoverable error state. */
+ if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)
+ break;
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed);
+ Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */
+ if (ohciR3IsEdReady(&Ed))
+ {
+# if 1
+ /*
+ * Control TDs depends on order and stage. Only one can be in-flight
+ * at any given time. OTOH, some stages are completed immediately,
+ * so we process the list until we've got a head which is in-flight
+ * or reach the end of the list.
+ */
+ do
+ {
+ if ( !ohciR3ServiceHeadTd(pDevIns, pThis, pThisCC, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control")
+ || ohciR3IsTdInFlight(pThisCC, Ed.HeadP & ED_PTR_MASK))
+ {
+ pThis->status |= OHCI_STATUS_CLF;
+ break;
+ }
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed); /* It might have been updated on URB completion. */
+ } while (ohciR3IsEdReady(&Ed));
+# else
+ /* Simplistic, for debugging. */
+ ohciR3ServiceHeadTd(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control");
+ pThis->status |= OHCI_STATUS_CLF;
+# endif
+ }
+
+ /* Trivial loop detection. */
+ if (EdAddr == (Ed.NextED & ED_PTR_MASK))
+ break;
+ /* Proceed to the next endpoint. */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogControlEPs)
+ ohciR3DumpEdList(pDevIns, pThisCC, pThis->ctrl_head, "Ctrl after ", true);
+# endif
+}
+
+
+/**
+ * Services the periodic list.
+ *
+ * On the interrupt portion of the periodic list we must reassemble URBs from multiple
+ * TDs using heuristics derived from USB tracing done in the guests and guest source
+ * code (when available).
+ */
+static void ohciR3ServicePeriodicList(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+ /*
+ * Read the list head from the HCCA.
+ */
+ const unsigned iList = pThis->HcFmNumber % OHCI_HCCA_NUM_INTR;
+ uint32_t EdAddr;
+ ohciR3GetDWords(pDevIns, pThis->hcca + iList * sizeof(EdAddr), &EdAddr, 1);
+
+# ifdef LOG_ENABLED
+ const uint32_t EdAddrHead = EdAddr;
+ if (g_fLogInterruptEPs)
+ {
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x before", iList);
+ ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true);
+ }
+# endif
+
+ /*
+ * Iterate the endpoint list.
+ */
+ unsigned cIterations = 128;
+ while (EdAddr
+ && (pThis->ctl & OHCI_CTL_PLE)
+ && (cIterations-- > 0))
+ {
+ OHCIED Ed;
+
+ /* Bail if previous processing ended up in the unrecoverable error state. */
+ if (pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR)
+ break;
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed);
+ if (ohciR3IsEdReady(&Ed))
+ {
+ /*
+ * "There is no separate head pointer of isochronous transfers. The first
+ * isochronous Endpoint Descriptor simply links to the last interrupt
+ * Endpoint Descriptor."
+ */
+ if (!(Ed.hwinfo & ED_HWINFO_ISO))
+ {
+ /*
+ * Presently we will only process the head URB on an interrupt endpoint.
+ */
+ ohciR3ServiceHeadTdMultiple(pDevIns, pThis, pThisCC, VUSBXFERTYPE_INTR, &Ed, EdAddr, "Periodic");
+ }
+ else if (pThis->ctl & OHCI_CTL_IE)
+ {
+ /*
+ * Presently only the head ITD.
+ */
+ ohciR3ServiceIsochronousEndpoint(pDevIns, pThis, pThisCC, &Ed, EdAddr);
+ }
+ else
+ break;
+ }
+ else
+ {
+ if (Ed.hwinfo & ED_HWINFO_SKIP)
+ {
+ Log3(("ohciR3ServicePeriodicList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP));
+ /* If the ED is in 'skip' state, no transactions on it are allowed and we must
+ * cancel outstanding URBs, if any.
+ */
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ PVUSBURB pUrb = ohciR3TdInFlightUrb(pThisCC, TdAddr);
+ if (pUrb)
+ pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb);
+ }
+ }
+ /* Trivial loop detection. */
+ if (EdAddr == (Ed.NextED & ED_PTR_MASK))
+ break;
+ /* Proceed to the next endpoint. */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+
+# ifdef LOG_ENABLED
+ if (g_fLogInterruptEPs)
+ {
+ char sz[48];
+ RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iList);
+ ohciR3DumpEdList(pDevIns, pThisCC, EdAddrHead, sz, true);
+ }
+# endif
+}
+
+
+/**
+ * Update the HCCA.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The OHCI controller instance data, shared edition.
+ * @param pThisCC The OHCI controller instance data, ring-3 edition.
+ */
+static void ohciR3UpdateHCCA(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+ OCHIHCCA hcca;
+ ohciR3PhysRead(pDevIns, pThis->hcca + OHCI_HCCA_OFS, &hcca, sizeof(hcca));
+
+ hcca.frame = RT_H2LE_U16((uint16_t)pThis->HcFmNumber);
+ hcca.pad = 0;
+
+ bool fWriteDoneHeadInterrupt = false;
+ if ( pThis->dqic == 0
+ && (pThis->intr_status & OHCI_INTR_WRITE_DONE_HEAD) == 0)
+ {
+ uint32_t done = pThis->done;
+
+ if (pThis->intr_status & ~( OHCI_INTR_MASTER_INTERRUPT_ENABLED | OHCI_INTR_OWNERSHIP_CHANGE
+ | OHCI_INTR_WRITE_DONE_HEAD) )
+ done |= 0x1;
+
+ hcca.done = RT_H2LE_U32(done);
+ pThis->done = 0;
+ pThis->dqic = 0x7;
+
+ Log(("ohci: Writeback Done (%#010x) on frame %#x (age %#x)\n", hcca.done,
+ pThis->HcFmNumber, pThis->HcFmNumber - pThisCC->u32FmDoneQueueTail));
+# ifdef LOG_ENABLED
+ ohciR3DumpTdQueue(pDevIns, pThisCC, hcca.done & ED_PTR_MASK, "DoneQueue");
+# endif
+ Assert(RT_OFFSETOF(OCHIHCCA, done) == 4);
+# if defined(VBOX_STRICT) || defined(LOG_ENABLED)
+ ohciR3InDoneQueueZap(pThisCC);
+# endif
+ fWriteDoneHeadInterrupt = true;
+ }
+
+ Log3(("ohci: Updating HCCA on frame %#x\n", pThis->HcFmNumber));
+ ohciR3PhysWriteMeta(pDevIns, pThis->hcca + OHCI_HCCA_OFS, (uint8_t *)&hcca, sizeof(hcca));
+ if (fWriteDoneHeadInterrupt)
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_WRITE_DONE_HEAD);
+ RT_NOREF(pThisCC);
+}
+
+
+/**
+ * Go over the in-flight URB list and cancel any URBs that are no longer in use.
+ * This occurs when the host removes EDs or TDs from the lists and we don't notice
+ * the sKip bit. Such URBs must be promptly canceled, otherwise there is a risk
+ * they might "steal" data destined for another URB.
+ */
+static void ohciR3CancelOrphanedURBs(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+ bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK
+ || pThis->hcca < ~OHCI_HCCA_MASK);
+ unsigned i, cLeft;
+ int j;
+ uint32_t EdAddr;
+ PVUSBURB pUrb;
+
+ /* If the HCCA is not currently valid, or there are no in-flight URBs,
+ * there's nothing to do.
+ */
+ if (!fValidHCCA || !pThisCC->cInFlight)
+ return;
+
+ /* Initially mark all in-flight URBs as inactive. */
+ for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++)
+ {
+ if (pThisCC->aInFlight[i].pUrb)
+ {
+ pThisCC->aInFlight[i].fInactive = true;
+ cLeft--;
+ }
+ }
+ Assert(cLeft == 0);
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ /* Get hcca data to minimize calls to ohciR3GetDWords/PDMDevHlpPCIPhysRead. */
+ uint32_t au32HCCA[OHCI_HCCA_NUM_INTR];
+ ohciR3GetDWords(pDevIns, pThis->hcca, au32HCCA, OHCI_HCCA_NUM_INTR);
+# endif
+
+ /* Go over all bulk/control/interrupt endpoint lists; any URB found in these lists
+ * is marked as active again.
+ */
+ for (i = 0; i < OHCI_HCCA_NUM_INTR + 2; i++)
+ {
+ switch (i)
+ {
+ case OHCI_HCCA_NUM_INTR:
+ EdAddr = pThis->bulk_head;
+ break;
+ case OHCI_HCCA_NUM_INTR + 1:
+ EdAddr = pThis->ctrl_head;
+ break;
+ default:
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ EdAddr = au32HCCA[i];
+# else
+ ohciR3GetDWords(pDevIns, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1);
+# endif
+ break;
+ }
+
+ unsigned cIterED = 128;
+ while ( EdAddr
+ && (cIterED-- > 0))
+ {
+ OHCIED Ed;
+ OHCITD Td;
+
+ ohciR3ReadEd(pDevIns, EdAddr, &Ed);
+ uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK;
+ uint32_t TailP = Ed.TailP & ED_PTR_MASK;
+ unsigned cIterTD = 0;
+ if ( !(Ed.hwinfo & ED_HWINFO_SKIP)
+ && (TdAddr != TailP))
+ {
+# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE
+ ohciR3PhysReadCacheInvalidate(&pThisCC->CacheTD);
+# endif
+ do
+ {
+ ohciR3ReadTd(pDevIns, TdAddr, &Td);
+ j = ohciR3InFlightFind(pThisCC, TdAddr);
+ if (j > -1)
+ pThisCC->aInFlight[j].fInactive = false;
+ TdAddr = Td.NextTD & ED_PTR_MASK;
+ /* See #8125.
+ * Sometimes the ED is changed by the guest between ohciR3ReadEd above and here.
+ * Then the code reads TD pointed by the new TailP, which is not allowed.
+ * Luckily Windows guests have Td.NextTD = 0 in the tail TD.
+ * Also having a real TD at 0 is very unlikely.
+ * So do not continue.
+ */
+ if (TdAddr == 0)
+ break;
+ /* Failsafe for temporarily looped lists. */
+ if (++cIterTD == 128)
+ break;
+ } while (TdAddr != (Ed.TailP & ED_PTR_MASK));
+ }
+ /* Trivial loop detection. */
+ if (EdAddr == (Ed.NextED & ED_PTR_MASK))
+ break;
+ /* Proceed to the next endpoint. */
+ EdAddr = Ed.NextED & ED_PTR_MASK;
+ }
+ }
+
+ /* In-flight URBs still marked as inactive are not used anymore and need
+ * to be canceled.
+ */
+ for (i = 0, cLeft = pThisCC->cInFlight; cLeft && i < RT_ELEMENTS(pThisCC->aInFlight); i++)
+ {
+ if (pThisCC->aInFlight[i].pUrb)
+ {
+ cLeft--;
+ pUrb = pThisCC->aInFlight[i].pUrb;
+ if ( pThisCC->aInFlight[i].fInactive
+ && pUrb->enmState == VUSBURBSTATE_IN_FLIGHT
+ && pUrb->enmType != VUSBXFERTYPE_CTRL)
+ pThisCC->RootHub.pIRhConn->pfnCancelUrbsEp(pThisCC->RootHub.pIRhConn, pUrb);
+ }
+ }
+ Assert(cLeft == 0);
+}
+
+
+/**
+ * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame.
+ */
+static void ohciR3StartOfFrame(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+# ifdef LOG_ENABLED
+ const uint32_t status_old = pThis->status;
+# endif
+
+ /*
+ * Update HcFmRemaining.FRT and update start of frame time.
+ */
+ pThis->frt = pThis->fit;
+ pThis->SofTime += pThis->cTicksPerFrame;
+
+ /*
+ * Check that the HCCA address isn't bogus. Linux 2.4.x is known to start
+ * the bus with a hcca of 0 to work around problem with a specific controller.
+ */
+ bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK
+ || pThis->hcca < ~OHCI_HCCA_MASK);
+
+# if 1
+ /*
+ * Update the HCCA.
+ * Should be done after SOF but before HC read first ED in this frame.
+ */
+ if (fValidHCCA)
+ ohciR3UpdateHCCA(pDevIns, pThis, pThisCC);
+# endif
+
+ /* "After writing to HCCA, HC will set SF in HcInterruptStatus" - guest isn't executing, so ignore the order! */
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_START_OF_FRAME);
+
+ if (pThis->fno)
+ {
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_FRAMENUMBER_OVERFLOW);
+ pThis->fno = 0;
+ }
+
+ /* If the HCCA address is invalid, we're quitting here to avoid doing something which cannot be reported to the HCD. */
+ if (!fValidHCCA)
+ {
+ Log(("ohciR3StartOfFrame: skipping hcca part because hcca=%RX32 (our 'valid' range: %RX32-%RX32)\n",
+ pThis->hcca, ~OHCI_HCCA_MASK, OHCI_HCCA_MASK));
+ return;
+ }
+
+ /*
+ * Periodic EPs.
+ */
+ if (pThis->ctl & OHCI_CTL_PLE)
+ ohciR3ServicePeriodicList(pDevIns, pThis, pThisCC);
+
+ /*
+ * Control EPs.
+ */
+ if ( (pThis->ctl & OHCI_CTL_CLE)
+ && (pThis->status & OHCI_STATUS_CLF) )
+ ohciR3ServiceCtrlList(pDevIns, pThis, pThisCC);
+
+ /*
+ * Bulk EPs.
+ */
+ if ( (pThis->ctl & OHCI_CTL_BLE)
+ && (pThis->status & OHCI_STATUS_BLF))
+ ohciR3ServiceBulkList(pDevIns, pThis, pThisCC);
+ else if ((pThis->status & OHCI_STATUS_BLF)
+ && pThis->fBulkNeedsCleaning)
+ ohciR3UndoBulkList(pDevIns, pThis, pThisCC); /* If list disabled but not empty, abort endpoints. */
+
+# if 0
+ /*
+ * Update the HCCA after processing the lists and everything. A bit experimental.
+ *
+ * ASSUME the guest won't be very upset if a TD is completed, retired and handed
+ * back immediately. The idea is to be able to retire the data and/or status stages
+ * of a control transfer together with the setup stage, thus saving a frame. This
+ * behaviour is should be perfectly ok, since the setup (and maybe data) stages
+ * have already taken at least one frame to complete.
+ *
+ * But, when implementing the first synchronous virtual USB devices, we'll have to
+ * verify that the guest doesn't choke when having a TD returned in the same frame
+ * as it was submitted.
+ */
+ ohciR3UpdateHCCA(pThis);
+# endif
+
+# ifdef LOG_ENABLED
+ if (pThis->status ^ status_old)
+ {
+ uint32_t val = pThis->status;
+ uint32_t chg = val ^ status_old; NOREF(chg);
+ Log2(("ohciR3StartOfFrame: HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", val & 1,
+ chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1,
+ chg & (3<<16)? "*" : "", (val >> 16) & 3));
+ }
+# endif
+}
+
+
+/**
+ * Updates the HcFmNumber and FNO registers.
+ */
+static void ohciR3BumpFrameNumber(POHCI pThis)
+{
+ const uint16_t u16OldFmNumber = pThis->HcFmNumber++;
+ if ((u16OldFmNumber ^ pThis->HcFmNumber) & RT_BIT(15))
+ pThis->fno = 1;
+}
+
+
+/**
+ * Callback for periodic frame processing.
+ */
+static DECLCALLBACK(bool) ohciR3StartFrame(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameNo)
+{
+ RT_NOREF(u32FrameNo);
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+
+ ohciR3Lock(pThisCC);
+
+ /* Reset idle detection flag */
+ pThis->fIdle = true;
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsReset(&g_PhysReadState);
+# endif
+
+ if (!(pThis->intr_status & OHCI_INTR_UNRECOVERABLE_ERROR))
+ {
+ /* Frame boundary, so do EOF stuff here. */
+ ohciR3BumpFrameNumber(pThis);
+ if ( (pThis->dqic != 0x7) && (pThis->dqic != 0))
+ pThis->dqic--;
+
+ /* Clean up any URBs that have been removed. */
+ ohciR3CancelOrphanedURBs(pDevIns, pThis, pThisCC);
+
+ /* Start the next frame. */
+ ohciR3StartOfFrame(pDevIns, pThis, pThisCC);
+ }
+
+# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS
+ physReadStatsPrint(&g_PhysReadState);
+# endif
+
+ ohciR3Unlock(pThisCC);
+ return pThis->fIdle;
+}
+
+
+/**
+ * @interface_method_impl{VUSBIROOTHUBPORT,pfnFrameRateChanged}
+ */
+static DECLCALLBACK(void) ohciR3FrameRateChanged(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameRate)
+{
+ POHCICC pThisCC = VUSBIROOTHUBPORT_2_OHCI(pInterface);
+ PPDMDEVINS pDevIns = pThisCC->pDevInsR3;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+
+ Assert(u32FrameRate <= OHCI_DEFAULT_TIMER_FREQ);
+
+ pThis->cTicksPerFrame = pThis->u64TimerHz / u32FrameRate;
+ if (!pThis->cTicksPerFrame)
+ pThis->cTicksPerFrame = 1;
+ pThis->cTicksPerUsbTick = pThis->u64TimerHz >= VUSB_BUS_HZ ? pThis->u64TimerHz / VUSB_BUS_HZ : 1;
+}
+
+
+/**
+ * Start sending SOF tokens across the USB bus, lists are processed in
+ * next frame
+ */
+static void ohciR3BusStart(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC)
+{
+ pThisCC->RootHub.pIRhConn->pfnPowerOn(pThisCC->RootHub.pIRhConn);
+ pThis->dqic = 0x7;
+
+ Log(("ohci: Bus started\n"));
+
+ pThis->SofTime = PDMDevHlpTMTimeVirtGet(pDevIns);
+ int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ);
+ AssertRC(rc);
+}
+
+
+/**
+ * Stop sending SOF tokens on the bus
+ */
+static void ohciR3BusStop(POHCICC pThisCC)
+{
+ int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, 0);
+ AssertRC(rc);
+ pThisCC->RootHub.pIRhConn->pfnPowerOff(pThisCC->RootHub.pIRhConn);
+}
+
+
+/**
+ * Move in to resume state
+ */
+static void ohciR3BusResume(PPDMDEVINS pDevIns, POHCI pThis, POHCICC pThisCC, bool fHardware)
+{
+ pThis->ctl &= ~OHCI_CTL_HCFS;
+ pThis->ctl |= OHCI_USB_RESUME;
+
+ LogFunc(("fHardware=%RTbool RWE=%s\n",
+ fHardware, (pThis->ctl & OHCI_CTL_RWE) ? "on" : "off"));
+
+ if (fHardware && (pThis->ctl & OHCI_CTL_RWE))
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_RESUME_DETECT);
+
+ ohciR3BusStart(pDevIns, pThis, pThisCC);
+}
+
+
+/* Power a port up or down */
+static void ohciR3RhPortPower(POHCIROOTHUBR3 pRh, unsigned iPort, bool fPowerUp)
+{
+ POHCIHUBPORT pPort = &pRh->aPorts[iPort];
+ bool fOldPPS = !!(pPort->fReg & OHCI_PORT_PPS);
+
+ LogFlowFunc(("iPort=%u fPowerUp=%RTbool\n", iPort, fPowerUp));
+
+ if (fPowerUp)
+ {
+ /* power up */
+ if (pPort->fAttached)
+ pPort->fReg |= OHCI_PORT_CCS;
+ if (pPort->fReg & OHCI_PORT_CCS)
+ pPort->fReg |= OHCI_PORT_PPS;
+ if (pPort->fAttached && !fOldPPS)
+ VUSBIRhDevPowerOn(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort));
+ }
+ else
+ {
+ /* power down */
+ pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS);
+ if (pPort->fAttached && fOldPPS)
+ VUSBIRhDevPowerOff(pRh->pIRhConn, OHCI_PORT_2_VUSB_PORT(iPort));
+ }
+}
+
+#endif /* IN_RING3 */
+
+/**
+ * Read the HcRevision register.
+ */
+static VBOXSTRICTRC HcRevision_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ Log2(("HcRevision_r() -> 0x10\n"));
+ *pu32Value = 0x10; /* OHCI revision 1.0, no emulation. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRevision register.
+ */
+static VBOXSTRICTRC HcRevision_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t u32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg, u32Value);
+ Log2(("HcRevision_w(%#010x) - denied\n", u32Value));
+ ASSERT_GUEST_MSG_FAILED(("Invalid operation!!! u32Value=%#010x\n", u32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControl register.
+ */
+static VBOXSTRICTRC HcControl_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ uint32_t ctl = pThis->ctl;
+ Log2(("HcControl_r -> %#010x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n",
+ ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1,
+ (ctl >> 9) & 1, (ctl >> 10) & 1));
+ *pu32Value = ctl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the HcControl register.
+ */
+static VBOXSTRICTRC HcControl_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+
+ /* log it. */
+ uint32_t chg = pThis->ctl ^ val; NOREF(chg);
+ Log2(("HcControl_w(%#010x) => %sCBSR=%d %sPLE=%d %sIE=%d %sCLE=%d %sBLE=%d %sHCFS=%#x %sIR=%d %sRWC=%d %sRWE=%d\n",
+ val,
+ chg & 3 ? "*" : "", val & 3,
+ chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1,
+ chg & RT_BIT(4) ? "*" : "", (val >> 4) & 1,
+ chg & RT_BIT(5) ? "*" : "", (val >> 5) & 1,
+ chg & (3 << 6)? "*" : "", (val >> 6) & 3,
+ chg & RT_BIT(8) ? "*" : "", (val >> 8) & 1,
+ chg & RT_BIT(9) ? "*" : "", (val >> 9) & 1,
+ chg & RT_BIT(10) ? "*" : "", (val >> 10) & 1));
+ if (val & ~0x07ff)
+ Log2(("Unknown bits %#x are set!!!\n", val & ~0x07ff));
+
+ /* see what changed and take action on that. */
+ uint32_t old_state = pThis->ctl & OHCI_CTL_HCFS;
+ uint32_t new_state = val & OHCI_CTL_HCFS;
+
+#ifdef IN_RING3
+ pThis->ctl = val;
+ if (new_state != old_state)
+ {
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ switch (new_state)
+ {
+ case OHCI_USB_OPERATIONAL:
+ LogRel(("OHCI: USB Operational\n"));
+ ohciR3BusStart(pDevIns, pThis, pThisCC);
+ break;
+ case OHCI_USB_SUSPEND:
+ ohciR3BusStop(pThisCC);
+ LogRel(("OHCI: USB Suspended\n"));
+ break;
+ case OHCI_USB_RESUME:
+ LogRel(("OHCI: USB Resume\n"));
+ ohciR3BusResume(pDevIns, pThis, pThisCC, false /* not hardware */);
+ break;
+ case OHCI_USB_RESET:
+ {
+ LogRel(("OHCI: USB Reset\n"));
+ ohciR3BusStop(pThisCC);
+ /** @todo This should probably do a real reset, but we don't implement
+ * that correctly in the roothub reset callback yet. check it's
+ * comments and argument for more details. */
+ pThisCC->RootHub.pIRhConn->pfnReset(pThisCC->RootHub.pIRhConn, false /* don't do a real reset */);
+ break;
+ }
+ }
+ }
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns);
+ if ( new_state != old_state )
+ {
+ Log2(("HcControl_w: state changed -> VINF_IOM_R3_MMIO_WRITE\n"));
+ return VINF_IOM_R3_MMIO_WRITE;
+ }
+ pThis->ctl = val;
+#endif /* !IN_RING3 */
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcCommandStatus register.
+ */
+static VBOXSTRICTRC HcCommandStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t status = pThis->status;
+ Log2(("HcCommandStatus_r() -> %#010x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n",
+ status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3));
+ *pu32Value = status;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcCommandStatus register.
+ */
+static VBOXSTRICTRC HcCommandStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ /* log */
+ uint32_t chg = pThis->status ^ val; NOREF(chg);
+ Log2(("HcCommandStatus_w(%#010x) => %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", val & 1,
+ chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1,
+ chg & (3<<16)? "!!!":"", (pThis->status >> 16) & 3));
+ if (val & ~0x0003000f)
+ Log2(("Unknown bits %#x are set!!!\n", val & ~0x0003000f));
+
+ /* SOC is read-only */
+ val = (val & ~OHCI_STATUS_SOC);
+
+#ifdef IN_RING3
+ /* "bits written as '0' remain unchanged in the register" */
+ pThis->status |= val;
+ if (pThis->status & OHCI_STATUS_HCR)
+ {
+ LogRel(("OHCI: Software reset\n"));
+ ohciR3DoReset(pDevIns, pThis, PDMDEVINS_2_DATA_CC(pDevIns, POHCICC), OHCI_USB_SUSPEND, false /* N/A */);
+ }
+#else
+ if ((pThis->status | val) & OHCI_STATUS_HCR)
+ {
+ LogFlow(("HcCommandStatus_w: reset -> VINF_IOM_R3_MMIO_WRITE\n"));
+ return VINF_IOM_R3_MMIO_WRITE;
+ }
+ pThis->status |= val;
+#endif
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcInterruptStatus register.
+ */
+static VBOXSTRICTRC HcInterruptStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t val = pThis->intr_status;
+ Log2(("HcInterruptStatus_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1));
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcInterruptStatus register.
+ */
+static VBOXSTRICTRC HcInterruptStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+
+ uint32_t res = pThis->intr_status & ~val;
+ uint32_t chg = pThis->intr_status ^ res; NOREF(chg);
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ Log2(("HcInterruptStatus_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", res & 1,
+ chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1,
+ chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1,
+ chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1,
+ chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1,
+ chg & RT_BIT(30)? "*" : "", (res >> 30) & 1));
+ if ( (val & ~0xc000007f)
+ && val != 0xffffffff /* ignore clear-all-like requests from xp. */)
+ Log2(("Unknown bits %#x are set!!!\n", val & ~0xc000007f));
+
+ /* "The Host Controller Driver may clear specific bits in this
+ * register by writing '1' to bit positions to be cleared"
+ */
+ pThis->intr_status &= ~val;
+ ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptStatus_w");
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcInterruptEnable register
+ */
+static VBOXSTRICTRC HcInterruptEnable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t val = pThis->intr;
+ Log2(("HcInterruptEnable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1));
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to the HcInterruptEnable register.
+ */
+static VBOXSTRICTRC HcInterruptEnable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+ uint32_t res = pThis->intr | val;
+ uint32_t chg = pThis->intr ^ res; NOREF(chg);
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ Log2(("HcInterruptEnable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", res & 1,
+ chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1,
+ chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1,
+ chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1,
+ chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1,
+ chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1,
+ chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1));
+ if (val & ~0xc000007f)
+ Log2(("Uknown bits %#x are set!!!\n", val & ~0xc000007f));
+
+ pThis->intr |= val;
+ ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptEnable_w");
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Reads the HcInterruptDisable register.
+ */
+static VBOXSTRICTRC HcInterruptDisable_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+#if 1 /** @todo r=bird: "On read, the current value of the HcInterruptEnable register is returned." */
+ uint32_t val = pThis->intr;
+#else /* old code. */
+ uint32_t val = ~pThis->intr;
+#endif
+ Log2(("HcInterruptDisable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1));
+
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Writes to the HcInterruptDisable register.
+ */
+static VBOXSTRICTRC HcInterruptDisable_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+ uint32_t res = pThis->intr & ~val;
+ uint32_t chg = pThis->intr ^ res; NOREF(chg);
+
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ Log2(("HcInterruptDisable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n",
+ val,
+ chg & RT_BIT(0) ? "*" : "", res & 1,
+ chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1,
+ chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1,
+ chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1,
+ chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1,
+ chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1,
+ chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1,
+ chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1,
+ chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1));
+ /* Don't bitch about invalid bits here since it makes sense to disable
+ * interrupts you don't know about. */
+
+ pThis->intr &= ~val;
+ ohciUpdateInterruptLocked(pDevIns, pThis, "HcInterruptDisable_w");
+ PDMDevHlpCritSectLeave(pDevIns, &pThis->CsIrq);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcHCCA register (Host Controller Communications Area physical address).
+ */
+static VBOXSTRICTRC HcHCCA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcHCCA_r() -> %#010x\n", pThis->hcca));
+ *pu32Value = pThis->hcca;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcHCCA register (Host Controller Communications Area physical address).
+ */
+static VBOXSTRICTRC HcHCCA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t Value)
+{
+ Log2(("HcHCCA_w(%#010x) - old=%#010x new=%#010x\n", Value, pThis->hcca, Value & OHCI_HCCA_MASK));
+ pThis->hcca = Value & OHCI_HCCA_MASK;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcPeriodCurrentED register.
+ */
+static VBOXSTRICTRC HcPeriodCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcPeriodCurrentED_r() -> %#010x\n", pThis->per_cur));
+ *pu32Value = pThis->per_cur;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcPeriodCurrentED register.
+ */
+static VBOXSTRICTRC HcPeriodCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ Log(("HcPeriodCurrentED_w(%#010x) - old=%#010x new=%#010x (This is a read only register, only the linux guys don't respect that!)\n",
+ val, pThis->per_cur, val & ~7));
+ //AssertMsgFailed(("HCD (Host Controller Driver) should not write to HcPeriodCurrentED! val=%#010x (old=%#010x)\n", val, pThis->per_cur));
+ AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val));
+ pThis->per_cur = val & ~7;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControlHeadED register.
+ */
+static VBOXSTRICTRC HcControlHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcControlHeadED_r() -> %#010x\n", pThis->ctrl_head));
+ *pu32Value = pThis->ctrl_head;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcControlHeadED register.
+ */
+static VBOXSTRICTRC HcControlHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ Log2(("HcControlHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_head, val & ~7));
+ AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val));
+ pThis->ctrl_head = val & ~7;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcControlCurrentED register.
+ */
+static VBOXSTRICTRC HcControlCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcControlCurrentED_r() -> %#010x\n", pThis->ctrl_cur));
+ *pu32Value = pThis->ctrl_cur;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcControlCurrentED register.
+ */
+static VBOXSTRICTRC HcControlCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ Log2(("HcControlCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_cur, val & ~7));
+ AssertMsg(!(pThis->ctl & OHCI_CTL_CLE), ("Illegal write! HcControl.ControlListEnabled is set! val=%#010x\n", val));
+ AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val));
+ pThis->ctrl_cur = val & ~7;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcBulkHeadED register.
+ */
+static VBOXSTRICTRC HcBulkHeadED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcBulkHeadED_r() -> %#010x\n", pThis->bulk_head));
+ *pu32Value = pThis->bulk_head;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcBulkHeadED register.
+ */
+static VBOXSTRICTRC HcBulkHeadED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ Log2(("HcBulkHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_head, val & ~7));
+ AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val));
+ pThis->bulk_head = val & ~7; /** @todo The ATI OHCI controller on my machine enforces 16-byte address alignment. */
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcBulkCurrentED register.
+ */
+static VBOXSTRICTRC HcBulkCurrentED_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcBulkCurrentED_r() -> %#010x\n", pThis->bulk_cur));
+ *pu32Value = pThis->bulk_cur;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcBulkCurrentED register.
+ */
+static VBOXSTRICTRC HcBulkCurrentED_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ Log2(("HcBulkCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_cur, val & ~7));
+ AssertMsg(!(pThis->ctl & OHCI_CTL_BLE), ("Illegal write! HcControl.BulkListEnabled is set! val=%#010x\n", val));
+ AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val));
+ pThis->bulk_cur = val & ~7;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read the HcDoneHead register.
+ */
+static VBOXSTRICTRC HcDoneHead_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ Log2(("HcDoneHead_r() -> 0x%#08x\n", pThis->done));
+ *pu32Value = pThis->done;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcDoneHead register.
+ */
+static VBOXSTRICTRC HcDoneHead_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ Log2(("HcDoneHead_w(0x%#08x) - denied!!!\n", val));
+ /*AssertMsgFailed(("Illegal operation!!! val=%#010x\n", val)); - OS/2 does this */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read the HcFmInterval (Fm=Frame) register.
+ */
+static VBOXSTRICTRC HcFmInterval_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t val = (pThis->fit << 31) | (pThis->fsmps << 16) | (pThis->fi);
+ Log2(("HcFmInterval_r() -> 0x%#08x - FI=%d FSMPS=%d FIT=%d\n",
+ val, val & 0x3fff, (val >> 16) & 0x7fff, val >> 31));
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcFmInterval (Fm = Frame) register.
+ */
+static VBOXSTRICTRC HcFmInterval_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+
+ /* log */
+ uint32_t chg = val ^ ((pThis->fit << 31) | (pThis->fsmps << 16) | pThis->fi); NOREF(chg);
+ Log2(("HcFmInterval_w(%#010x) => %sFI=%d %sFSMPS=%d %sFIT=%d\n",
+ val,
+ chg & 0x00003fff ? "*" : "", val & 0x3fff,
+ chg & 0x7fff0000 ? "*" : "", (val >> 16) & 0x7fff,
+ chg >> 31 ? "*" : "", (val >> 31) & 1));
+ if (pThis->fi != (val & OHCI_FMI_FI))
+ {
+ Log(("ohci: FrameInterval: %#010x -> %#010x\n", pThis->fi, val & OHCI_FMI_FI));
+ AssertMsg(pThis->fit != ((val >> OHCI_FMI_FIT_SHIFT) & 1), ("HCD didn't toggle the FIT bit!!!\n"));
+ }
+
+ /* update */
+ pThis->fi = val & OHCI_FMI_FI;
+ pThis->fit = (val & OHCI_FMI_FIT) >> OHCI_FMI_FIT_SHIFT;
+ pThis->fsmps = (val & OHCI_FMI_FSMPS) >> OHCI_FMI_FSMPS_SHIFT;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcFmRemaining (Fm = Frame) register.
+ */
+static VBOXSTRICTRC HcFmRemaining_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(iReg);
+ uint32_t Value = pThis->frt << 31;
+ if ((pThis->ctl & OHCI_CTL_HCFS) == OHCI_USB_OPERATIONAL)
+ {
+ /*
+ * Being in USB operational state guarantees SofTime was set already.
+ */
+ uint64_t tks = PDMDevHlpTMTimeVirtGet(pDevIns) - pThis->SofTime;
+ if (tks < pThis->cTicksPerFrame) /* avoid muldiv if possible */
+ {
+ uint16_t fr;
+ tks = ASMMultU64ByU32DivByU32(1, tks, pThis->cTicksPerUsbTick);
+ fr = (uint16_t)(pThis->fi - tks);
+ Value |= fr;
+ }
+ }
+
+ Log2(("HcFmRemaining_r() -> %#010x - FR=%d FRT=%d\n", Value, Value & 0x3fff, Value >> 31));
+ *pu32Value = Value;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcFmRemaining (Fm = Frame) register.
+ */
+static VBOXSTRICTRC HcFmRemaining_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ Log2(("HcFmRemaining_w(%#010x) - denied\n", val));
+ AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcFmNumber (Fm = Frame) register.
+ */
+static VBOXSTRICTRC HcFmNumber_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ uint32_t val = (uint16_t)pThis->HcFmNumber;
+ Log2(("HcFmNumber_r() -> %#010x - FN=%#x(%d) (32-bit=%#x(%d))\n", val, val, val, pThis->HcFmNumber, pThis->HcFmNumber));
+ *pu32Value = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcFmNumber (Fm = Frame) register.
+ */
+static VBOXSTRICTRC HcFmNumber_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ Log2(("HcFmNumber_w(%#010x) - denied\n", val));
+ AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val));
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcPeriodicStart register.
+ * The register determines when in a frame to switch from control&bulk to periodic lists.
+ */
+static VBOXSTRICTRC HcPeriodicStart_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2(("HcPeriodicStart_r() -> %#010x - PS=%d\n", pThis->pstart, pThis->pstart & 0x3fff));
+ *pu32Value = pThis->pstart;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcPeriodicStart register.
+ * The register determines when in a frame to switch from control&bulk to periodic lists.
+ */
+static VBOXSTRICTRC HcPeriodicStart_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ Log2(("HcPeriodicStart_w(%#010x) => PS=%d\n", val, val & 0x3fff));
+ if (val & ~0x3fff)
+ Log2(("Unknown bits %#x are set!!!\n", val & ~0x3fff));
+ pThis->pstart = val; /** @todo r=bird: should we support setting the other bits? */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcLSThreshold register.
+ */
+static VBOXSTRICTRC HcLSThreshold_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ Log2(("HcLSThreshold_r() -> %#010x\n", OHCI_LS_THRESH));
+ *pu32Value = OHCI_LS_THRESH;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcLSThreshold register.
+ *
+ * Docs are inconsistent here:
+ *
+ * "Neither the Host Controller nor the Host Controller Driver are allowed to change this value."
+ *
+ * "This value is calculated by HCD with the consideration of transmission and setup overhead."
+ *
+ * The register is marked "R/W" the HCD column.
+ *
+ */
+static VBOXSTRICTRC HcLSThreshold_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ Log2(("HcLSThreshold_w(%#010x) => LST=0x%03x(%d)\n", val, val & 0x0fff, val & 0x0fff));
+ AssertMsg(val == OHCI_LS_THRESH,
+ ("HCD tried to write bad LS threshold: 0x%x (see function header)\n", val));
+ /** @todo the HCD can change this. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcRhDescriptorA register.
+ */
+static VBOXSTRICTRC HcRhDescriptorA_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ uint32_t val = pThis->RootHub.desc_a;
+#if 0 /* annoying */
+ Log2(("HcRhDescriptorA_r() -> %#010x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTGT=%#x\n",
+ val, val & 0xff, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1,
+ (val >> 12) & 1, (val >> 24) & 0xff));
+#endif
+ *pu32Value = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRhDescriptorA register.
+ */
+static VBOXSTRICTRC HcRhDescriptorA_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ uint32_t chg = val ^ pThis->RootHub.desc_a; NOREF(chg);
+ Log2(("HcRhDescriptorA_w(%#010x) => %sNDP=%d %sPSM=%d %sNPS=%d %sDT=%d %sOCPM=%d %sNOCP=%d %sPOTGT=%#x - %sPowerSwitching Set%sPower\n",
+ val,
+ chg & 0xff ?"!!!": "", val & 0xff,
+ (chg >> 8) & 1 ? "*" : "", (val >> 8) & 1,
+ (chg >> 9) & 1 ? "*" : "", (val >> 9) & 1,
+ (chg >> 10) & 1 ?"!!!": "", 0,
+ (chg >> 11) & 1 ? "*" : "", (val >> 11) & 1,
+ (chg >> 12) & 1 ? "*" : "", (val >> 12) & 1,
+ (chg >> 24)&0xff? "*" : "", (val >> 24) & 0xff,
+ val & OHCI_RHA_NPS ? "No" : "",
+ val & OHCI_RHA_PSM ? "Port" : "Global"));
+ if (val & ~0xff001fff)
+ Log2(("Unknown bits %#x are set!!!\n", val & ~0xff001fff));
+
+
+ if ((val & (OHCI_RHA_NDP | OHCI_RHA_DT)) != OHCI_NDP_CFG(pThis))
+ {
+ Log(("ohci: invalid write to NDP or DT in roothub descriptor A!!! val=0x%.8x\n", val));
+ val &= ~(OHCI_RHA_NDP | OHCI_RHA_DT);
+ val |= OHCI_NDP_CFG(pThis);
+ }
+
+ pThis->RootHub.desc_a = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcRhDescriptorB register.
+ */
+static VBOXSTRICTRC HcRhDescriptorB_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t val = pThis->RootHub.desc_b;
+ Log2(("HcRhDescriptorB_r() -> %#010x - DR=0x%04x PPCM=0x%04x\n",
+ val, val & 0xffff, val >> 16));
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRhDescriptorB register.
+ */
+static VBOXSTRICTRC HcRhDescriptorB_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ uint32_t chg = pThis->RootHub.desc_b ^ val; NOREF(chg);
+ Log2(("HcRhDescriptorB_w(%#010x) => %sDR=0x%04x %sPPCM=0x%04x\n",
+ val,
+ chg & 0xffff ? "!!!" : "", val & 0xffff,
+ chg >> 16 ? "!!!" : "", val >> 16));
+
+ if ( pThis->RootHub.desc_b != val )
+ Log(("ohci: unsupported write to root descriptor B!!! 0x%.8x -> 0x%.8x\n", pThis->RootHub.desc_b, val));
+ pThis->RootHub.desc_b = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the HcRhStatus (Rh = Root Hub) register.
+ */
+static VBOXSTRICTRC HcRhStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ uint32_t val = pThis->RootHub.status;
+ if (val & (OHCI_RHS_LPSC | OHCI_RHS_OCIC))
+ Log2(("HcRhStatus_r() -> %#010x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1));
+ *pu32Value = val;
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the HcRhStatus (Rh = Root Hub) register.
+ */
+static VBOXSTRICTRC HcRhStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+
+ /* log */
+ uint32_t old = pThis->RootHub.status;
+ uint32_t chg;
+ if (val & ~0x80038003)
+ Log2(("HcRhStatus_w: Unknown bits %#x are set!!!\n", val & ~0x80038003));
+ if ( (val & OHCI_RHS_LPSC) && (val & OHCI_RHS_LPS) )
+ Log2(("HcRhStatus_w: Warning both CGP and SGP are set! (Clear/Set Global Power)\n"));
+ if ( (val & OHCI_RHS_DRWE) && (val & OHCI_RHS_CRWE) )
+ Log2(("HcRhStatus_w: Warning both CRWE and SRWE are set! (Clear/Set Remote Wakeup Enable)\n"));
+
+
+ /* write 1 to clear OCIC */
+ if ( val & OHCI_RHS_OCIC )
+ pThis->RootHub.status &= ~OHCI_RHS_OCIC;
+
+ /* SetGlobalPower */
+ if ( val & OHCI_RHS_LPSC )
+ {
+ unsigned i;
+ Log2(("ohci: global power up\n"));
+ for (i = 0; i < OHCI_NDP_CFG(pThis); i++)
+ ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */);
+ }
+
+ /* ClearGlobalPower */
+ if ( val & OHCI_RHS_LPS )
+ {
+ unsigned i;
+ Log2(("ohci: global power down\n"));
+ for (i = 0; i < OHCI_NDP_CFG(pThis); i++)
+ ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */);
+ }
+
+ if ( val & OHCI_RHS_DRWE )
+ pThis->RootHub.status |= OHCI_RHS_DRWE;
+
+ if ( val & OHCI_RHS_CRWE )
+ pThis->RootHub.status &= ~OHCI_RHS_DRWE;
+
+ chg = pThis->RootHub.status ^ old;
+ Log2(("HcRhStatus_w(%#010x) => %sCGP=%d %sOCI=%d %sSRWE=%d %sSGP=%d %sOCIC=%d %sCRWE=%d\n",
+ val,
+ chg & 1 ? "*" : "", val & 1,
+ (chg >> 1) & 1 ?"!!!": "", (val >> 1) & 1,
+ (chg >> 15) & 1 ? "*" : "", (val >> 15) & 1,
+ (chg >> 16) & 1 ? "*" : "", (val >> 16) & 1,
+ (chg >> 17) & 1 ? "*" : "", (val >> 17) & 1,
+ (chg >> 31) & 1 ? "*" : "", (val >> 31) & 1));
+ RT_NOREF(pDevIns, iReg);
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+/**
+ * Read the HcRhPortStatus register of a port.
+ */
+static VBOXSTRICTRC HcRhPortStatus_r(PPDMDEVINS pDevIns, PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ const unsigned i = iReg - 21;
+ uint32_t val = pThis->RootHub.aPorts[i].fReg | OHCI_PORT_PPS; /* PortPowerStatus: see todo on power in _w function. */
+ if (val & OHCI_PORT_PRS)
+ {
+#ifdef IN_RING3
+ RTThreadYield();
+#else
+ Log2(("HcRhPortStatus_r: yield -> VINF_IOM_R3_MMIO_READ\n"));
+ return VINF_IOM_R3_MMIO_READ;
+#endif
+ }
+ if (val & (OHCI_PORT_PRS | OHCI_PORT_CLEAR_CHANGE_MASK))
+ Log2(("HcRhPortStatus_r(): port %u: -> %#010x - CCS=%d PES=%d PSS=%d POCI=%d RRS=%d PPS=%d LSDA=%d CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n",
+ i, val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1,
+ (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1));
+ *pu32Value = val;
+ RT_NOREF(pDevIns);
+ return VINF_SUCCESS;
+}
+
+#ifdef IN_RING3
+/**
+ * Completion callback for the vusb_dev_reset() operation.
+ * @thread EMT.
+ */
+static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser)
+{
+ RT_NOREF(pDev);
+
+ Assert(uPort >= 1);
+ PPDMDEVINS pDevIns = (PPDMDEVINS)pvUser;
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ POHCIHUBPORT pPort = &pThis->RootHub.aPorts[uPort - 1];
+
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Successful reset.
+ */
+ Log2(("ohciR3PortResetDone: Reset completed.\n"));
+ pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC);
+ pPort->fReg |= OHCI_PORT_PES | OHCI_PORT_PRSC;
+ }
+ else
+ {
+ /* desperate measures. */
+ if ( pPort->fAttached
+ && VUSBIRhDevGetState(pThisCC->RootHub.pIRhConn, uPort) == VUSB_DEVICE_STATE_ATTACHED)
+ {
+ /*
+ * Damn, something weird happened during reset. We'll pretend the user did an
+ * incredible fast reconnect or something. (probably not gonna work)
+ */
+ Log2(("ohciR3PortResetDone: The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc));
+ pPort->fReg = OHCI_PORT_CCS | OHCI_PORT_CSC;
+ }
+ else
+ {
+ /*
+ * The device have / will be disconnected.
+ */
+ Log2(("ohciR3PortResetDone: Disconnected (rc=%Rrc)!!!\n", rc));
+ pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC | OHCI_PORT_PRSC);
+ pPort->fReg |= OHCI_PORT_CSC;
+ }
+ }
+
+ /* Raise roothub status change interrupt. */
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+}
+
+/**
+ * Sets a flag in a port status register but only set it if a device is
+ * connected, if not set ConnectStatusChange flag to force HCD to reevaluate
+ * connect status.
+ *
+ * @returns true if device was connected and the flag was cleared.
+ */
+static bool ohciR3RhPortSetIfConnected(PPDMDEVINS pDevIns, POHCI pThis, int iPort, uint32_t fValue)
+{
+ /*
+ * Writing a 0 has no effect
+ */
+ if (fValue == 0)
+ return false;
+
+ /*
+ * If CurrentConnectStatus is cleared we set ConnectStatusChange.
+ */
+ if (!(pThis->RootHub.aPorts[iPort].fReg & OHCI_PORT_CCS))
+ {
+ pThis->RootHub.aPorts[iPort].fReg |= OHCI_PORT_CSC;
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+ return false;
+ }
+
+ bool fRc = !(pThis->RootHub.aPorts[iPort].fReg & fValue);
+
+ /* set the bit */
+ pThis->RootHub.aPorts[iPort].fReg |= fValue;
+
+ return fRc;
+}
+#endif /* IN_RING3 */
+
+/**
+ * Write to the HcRhPortStatus register of a port.
+ */
+static VBOXSTRICTRC HcRhPortStatus_w(PPDMDEVINS pDevIns, POHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ const unsigned i = iReg - 21;
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ POHCIHUBPORT p = &pThis->RootHub.aPorts[i];
+ uint32_t old_state = p->fReg;
+
+# ifdef LOG_ENABLED
+ /*
+ * Log it.
+ */
+ static const char *apszCmdNames[32] =
+ {
+ "ClearPortEnable", "SetPortEnable", "SetPortSuspend", "!!!ClearSuspendStatus",
+ "SetPortReset", "!!!5", "!!!6", "!!!7",
+ "SetPortPower", "ClearPortPower", "!!!10", "!!!11",
+ "!!!12", "!!!13", "!!!14", "!!!15",
+ "ClearCSC", "ClearPESC", "ClearPSSC", "ClearOCIC",
+ "ClearPRSC", "!!!21", "!!!22", "!!!23",
+ "!!!24", "!!!25", "!!!26", "!!!27",
+ "!!!28", "!!!29", "!!!30", "!!!31"
+ };
+ Log2(("HcRhPortStatus_w(%#010x): port %u:", val, i));
+ for (unsigned j = 0; j < RT_ELEMENTS(apszCmdNames); j++)
+ if (val & (1 << j))
+ Log2((" %s", apszCmdNames[j]));
+ Log2(("\n"));
+# endif
+
+ /* Write to clear any of the change bits: CSC, PESC, PSSC, OCIC and PRSC */
+ if (val & OHCI_PORT_CLEAR_CHANGE_MASK)
+ p->fReg &= ~(val & OHCI_PORT_CLEAR_CHANGE_MASK);
+
+ if (val & OHCI_PORT_CLRPE)
+ {
+ p->fReg &= ~OHCI_PORT_PES;
+ Log2(("HcRhPortStatus_w(): port %u: DISABLE\n", i));
+ }
+
+ if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PES))
+ Log2(("HcRhPortStatus_w(): port %u: ENABLE\n", i));
+
+ if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PSS))
+ Log2(("HcRhPortStatus_w(): port %u: SUSPEND - not implemented correctly!!!\n", i));
+
+ if (val & OHCI_PORT_PRS)
+ {
+ if (ohciR3RhPortSetIfConnected(pDevIns, pThis, i, val & OHCI_PORT_PRS))
+ {
+ PVM pVM = PDMDevHlpGetVM(pDevIns);
+ p->fReg &= ~OHCI_PORT_PRSC;
+ VUSBIRhDevReset(pThisCC->RootHub.pIRhConn, OHCI_PORT_2_VUSB_PORT(i), false /* don't reset on linux */,
+ ohciR3PortResetDone, pDevIns, pVM);
+ }
+ else if (p->fReg & OHCI_PORT_PRS)
+ {
+ /* the guest is getting impatient. */
+ Log2(("HcRhPortStatus_w(): port %u: Impatient guest!\n", i));
+ RTThreadYield();
+ }
+ }
+
+ if (!(pThis->RootHub.desc_a & OHCI_RHA_NPS))
+ {
+ /** @todo To implement per-device power-switching
+ * we need to check PortPowerControlMask to make
+ * sure it isn't gang powered
+ */
+ if (val & OHCI_PORT_CLRPP)
+ ohciR3RhPortPower(&pThisCC->RootHub, i, false /* power down */);
+ if (val & OHCI_PORT_PPS)
+ ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */);
+ }
+
+ /** @todo r=frank: ClearSuspendStatus. Timing? */
+ if (val & OHCI_PORT_CLRSS)
+ {
+ ohciR3RhPortPower(&pThisCC->RootHub, i, true /* power up */);
+ pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS;
+ pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC;
+ ohciR3SetInterrupt(pDevIns, pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE);
+ }
+
+ if (p->fReg != old_state)
+ {
+ uint32_t res = p->fReg;
+ uint32_t chg = res ^ old_state; NOREF(chg);
+ Log2(("HcRhPortStatus_w(%#010x): port %u: => %sCCS=%d %sPES=%d %sPSS=%d %sPOCI=%d %sRRS=%d %sPPS=%d %sLSDA=%d %sCSC=%d %sPESC=%d %sPSSC=%d %sOCIC=%d %sPRSC=%d\n",
+ val, i,
+ chg & 1 ? "*" : "", res & 1,
+ (chg >> 1) & 1 ? "*" : "", (res >> 1) & 1,
+ (chg >> 2) & 1 ? "*" : "", (res >> 2) & 1,
+ (chg >> 3) & 1 ? "*" : "", (res >> 3) & 1,
+ (chg >> 4) & 1 ? "*" : "", (res >> 4) & 1,
+ (chg >> 8) & 1 ? "*" : "", (res >> 8) & 1,
+ (chg >> 9) & 1 ? "*" : "", (res >> 9) & 1,
+ (chg >> 16) & 1 ? "*" : "", (res >> 16) & 1,
+ (chg >> 17) & 1 ? "*" : "", (res >> 17) & 1,
+ (chg >> 18) & 1 ? "*" : "", (res >> 18) & 1,
+ (chg >> 19) & 1 ? "*" : "", (res >> 19) & 1,
+ (chg >> 20) & 1 ? "*" : "", (res >> 20) & 1));
+ }
+ RT_NOREF(pDevIns);
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns, pThis, iReg, val);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+/**
+ * Register descriptor table
+ */
+static const OHCIOPREG g_aOpRegs[] =
+{
+ { "HcRevision", HcRevision_r, HcRevision_w }, /* 0 */
+ { "HcControl", HcControl_r, HcControl_w }, /* 1 */
+ { "HcCommandStatus", HcCommandStatus_r, HcCommandStatus_w }, /* 2 */
+ { "HcInterruptStatus", HcInterruptStatus_r, HcInterruptStatus_w }, /* 3 */
+ { "HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w }, /* 4 */
+ { "HcInterruptDisable", HcInterruptDisable_r, HcInterruptDisable_w }, /* 5 */
+ { "HcHCCA", HcHCCA_r, HcHCCA_w }, /* 6 */
+ { "HcPeriodCurrentED", HcPeriodCurrentED_r, HcPeriodCurrentED_w }, /* 7 */
+ { "HcControlHeadED", HcControlHeadED_r, HcControlHeadED_w }, /* 8 */
+ { "HcControlCurrentED", HcControlCurrentED_r, HcControlCurrentED_w }, /* 9 */
+ { "HcBulkHeadED", HcBulkHeadED_r, HcBulkHeadED_w }, /* 10 */
+ { "HcBulkCurrentED", HcBulkCurrentED_r, HcBulkCurrentED_w }, /* 11 */
+ { "HcDoneHead", HcDoneHead_r, HcDoneHead_w }, /* 12 */
+ { "HcFmInterval", HcFmInterval_r, HcFmInterval_w }, /* 13 */
+ { "HcFmRemaining", HcFmRemaining_r, HcFmRemaining_w }, /* 14 */
+ { "HcFmNumber", HcFmNumber_r, HcFmNumber_w }, /* 15 */
+ { "HcPeriodicStart", HcPeriodicStart_r, HcPeriodicStart_w }, /* 16 */
+ { "HcLSThreshold", HcLSThreshold_r, HcLSThreshold_w }, /* 17 */
+ { "HcRhDescriptorA", HcRhDescriptorA_r, HcRhDescriptorA_w }, /* 18 */
+ { "HcRhDescriptorB", HcRhDescriptorB_r, HcRhDescriptorB_w }, /* 19 */
+ { "HcRhStatus", HcRhStatus_r, HcRhStatus_w }, /* 20 */
+
+ /* The number of port status register depends on the definition
+ * of OHCI_NDP_MAX macro
+ */
+ { "HcRhPortStatus[0]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 21 */
+ { "HcRhPortStatus[1]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 22 */
+ { "HcRhPortStatus[2]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 23 */
+ { "HcRhPortStatus[3]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 24 */
+ { "HcRhPortStatus[4]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 25 */
+ { "HcRhPortStatus[5]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 26 */
+ { "HcRhPortStatus[6]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 27 */
+ { "HcRhPortStatus[7]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 28 */
+ { "HcRhPortStatus[8]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 29 */
+ { "HcRhPortStatus[9]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 30 */
+ { "HcRhPortStatus[10]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 31 */
+ { "HcRhPortStatus[11]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 32 */
+ { "HcRhPortStatus[12]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 33 */
+ { "HcRhPortStatus[13]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 34 */
+ { "HcRhPortStatus[14]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 35 */
+};
+
+/* Quick way to determine how many op regs are valid. Since at least one port must
+ * be configured (and no more than 15), there will be between 22 and 36 registers.
+ */
+#define NUM_OP_REGS(pohci) (21 + OHCI_NDP_CFG(pohci))
+
+AssertCompile(RT_ELEMENTS(g_aOpRegs) > 21);
+AssertCompile(RT_ELEMENTS(g_aOpRegs) <= 36);
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ohciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ RT_NOREF(pvUser);
+
+ /* Paranoia: Assert that IOMMMIO_FLAGS_READ_DWORD works. */
+ AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3);
+ AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ const uint32_t iReg = off >> 2;
+ if (iReg < NUM_OP_REGS(pThis))
+ rc = g_aOpRegs[iReg].pfnRead(pDevIns, pThis, iReg, (uint32_t *)pv);
+ else
+ {
+ Log(("ohci: Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis)));
+ rc = VINF_IOM_MMIO_UNUSED_FF;
+ }
+ return rc;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE}
+ */
+static DECLCALLBACK(VBOXSTRICTRC) ohciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ RT_NOREF(pvUser);
+
+ /* Paranoia: Assert that IOMMMIO_FLAGS_WRITE_DWORD_ZEROED works. */
+ AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3);
+ AssertReturn(!(off & 0x3), VERR_INTERNAL_ERROR_4);
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rc;
+ const uint32_t iReg = off >> 2;
+ if (iReg < NUM_OP_REGS(pThis))
+ rc = g_aOpRegs[iReg].pfnWrite(pDevIns, pThis, iReg, *(uint32_t const *)pv);
+ else
+ {
+ Log(("ohci: Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis)));
+ rc = VINF_SUCCESS;
+ }
+ return rc;
+}
+
+#ifdef IN_RING3
+
+/**
+ * Saves the state of the OHCI device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to save the state to.
+ */
+static DECLCALLBACK(int) ohciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ LogFlow(("ohciR3SaveExec:\n"));
+
+ int rc = pDevIns->pHlpR3->pfnSSMPutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL);
+ AssertRCReturn(rc, rc);
+
+ /* Save the periodic frame rate so we can we can tell if the bus was started or not when restoring. */
+ return pDevIns->pHlpR3->pfnSSMPutU32(pSSM, VUSBIRhGetPeriodicFrameRate(pThisCC->RootHub.pIRhConn));
+}
+
+
+/**
+ * Loads the state of the OHCI device.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pSSM The handle to the saved state.
+ * @param uVersion The data unit version number.
+ * @param uPass The data pass.
+ */
+static DECLCALLBACK(int) ohciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ LogFlow(("ohciR3LoadExec:\n"));
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+
+ if (uVersion >= OHCI_SAVED_STATE_VERSION_EOF_TIMER)
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL);
+ else if (uVersion == OHCI_SAVED_STATE_VERSION_8PORTS)
+ rc = pHlp->pfnSSMGetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields8Ports[0], NULL);
+ else
+ AssertMsgFailedReturn(("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Get the frame rate / started indicator.
+ *
+ * For older versions there is a timer saved here. We'll skip it and deduce
+ * the periodic frame rate from the host controller functional state.
+ */
+ if (uVersion > OHCI_SAVED_STATE_VERSION_EOF_TIMER)
+ {
+ rc = pHlp->pfnSSMGetU32(pSSM, &pThisCC->uRestoredPeriodicFrameRate);
+ AssertRCReturn(rc, rc);
+ }
+ else
+ {
+ rc = pHlp->pfnSSMSkipToEndOfUnit(pSSM);
+ AssertRCReturn(rc, rc);
+
+ uint32_t fHcfs = pThis->ctl & OHCI_CTL_HCFS;
+ switch (fHcfs)
+ {
+ case OHCI_USB_OPERATIONAL:
+ case OHCI_USB_RESUME:
+ pThisCC->uRestoredPeriodicFrameRate = OHCI_DEFAULT_TIMER_FREQ;
+ break;
+ default:
+ pThisCC->uRestoredPeriodicFrameRate = 0;
+ break;
+ }
+ }
+
+ /** @todo could we restore the frame rate here instead of in ohciR3Resume? */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Reset notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ohciR3Reset(PPDMDEVINS pDevIns)
+{
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ LogFlow(("ohciR3Reset:\n"));
+
+ /*
+ * There is no distinction between cold boot, warm reboot and software reboots,
+ * all of these are treated as cold boots. We are also doing the initialization
+ * job of a BIOS or SMM driver.
+ *
+ * Important: Don't confuse UsbReset with hardware reset. Hardware reset is
+ * just one way of getting into the UsbReset state.
+ */
+ ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, true /* reset devices */);
+}
+
+
+/**
+ * Resume notification.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(void) ohciR3Resume(PPDMDEVINS pDevIns)
+{
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+ LogFlowFunc(("\n"));
+
+ /* Restart the frame thread if it was active when the loaded state was saved. */
+ uint32_t uRestoredPeriodicFR = pThisCC->uRestoredPeriodicFrameRate;
+ pThisCC->uRestoredPeriodicFrameRate = 0;
+ if (uRestoredPeriodicFR)
+ {
+ LogFlowFunc(("Bus was active, enable periodic frame processing (rate: %u)\n", uRestoredPeriodicFR));
+ int rc = pThisCC->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThisCC->RootHub.pIRhConn, uRestoredPeriodicFR);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Info handler, device version. Dumps OHCI control registers.
+ *
+ * @param pDevIns Device instance which registered the info.
+ * @param pHlp Callback functions for doing output.
+ * @param pszArgs Argument string. Optional and specific to the handler.
+ */
+static DECLCALLBACK(void) ohciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ RT_NOREF(pszArgs);
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ uint32_t val, ctl, status;
+
+ /* Control register */
+ ctl = pThis->ctl;
+ pHlp->pfnPrintf(pHlp, "HcControl: %08x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n",
+ ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1,
+ (ctl >> 9) & 1, (ctl >> 10) & 1);
+
+ /* Command status register */
+ status = pThis->status;
+ pHlp->pfnPrintf(pHlp, "HcCommandStatus: %08x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n",
+ status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3);
+
+ /* Interrupt status register */
+ val = pThis->intr_status;
+ pHlp->pfnPrintf(pHlp, "HcInterruptStatus: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1);
+
+ /* Interrupt enable register */
+ val = pThis->intr;
+ pHlp->pfnPrintf(pHlp, "HcInterruptEnable: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n",
+ val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1,
+ (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1);
+
+ /* HCCA address register */
+ pHlp->pfnPrintf(pHlp, "HcHCCA: %08x\n", pThis->hcca);
+
+ /* Current periodic ED register */
+ pHlp->pfnPrintf(pHlp, "HcPeriodCurrentED: %08x\n", pThis->per_cur);
+
+ /* Control ED registers */
+ pHlp->pfnPrintf(pHlp, "HcControlHeadED: %08x\n", pThis->ctrl_head);
+ pHlp->pfnPrintf(pHlp, "HcControlCurrentED: %08x\n", pThis->ctrl_cur);
+
+ /* Bulk ED registers */
+ pHlp->pfnPrintf(pHlp, "HcBulkHeadED: %08x\n", pThis->bulk_head);
+ pHlp->pfnPrintf(pHlp, "HcBulkCurrentED: %08x\n", pThis->bulk_cur);
+
+ /* Done head register */
+ pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done);
+
+ /* Done head register */
+ pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done);
+
+ /* Root hub descriptor A */
+ val = pThis->RootHub.desc_a;
+ pHlp->pfnPrintf(pHlp, "HcRhDescriptorA: %08x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTPGT=%d\n",
+ val, (uint8_t)val, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, (val >> 12) & 1, (uint8_t)(val >> 24));
+
+ /* Root hub descriptor B */
+ val = pThis->RootHub.desc_b;
+ pHlp->pfnPrintf(pHlp, "HcRhDescriptorB: %08x - DR=%#04x PPCM=%#04x\n", val, (uint16_t)val, (uint16_t)(val >> 16));
+
+ /* Root hub status register */
+ val = pThis->RootHub.status;
+ pHlp->pfnPrintf(pHlp, "HcRhStatus: %08x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n\n",
+ val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1);
+
+ /* Port status registers */
+ for (unsigned i = 0; i < OHCI_NDP_CFG(pThis); ++i)
+ {
+ val = pThis->RootHub.aPorts[i].fReg;
+ pHlp->pfnPrintf(pHlp, "HcRhPortStatus%02d: CCS=%d PES =%d PSS =%d POCI=%d PRS =%d PPS=%d LSDA=%d\n"
+ " %08x - CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n",
+ i, val & 1, (val >> 1) & 1, (val >> 2) & 1,(val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1,
+ val, (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1);
+ }
+}
+
+
+/**
+ * Destruct a device instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance data.
+ */
+static DECLCALLBACK(int) ohciR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCICC);
+
+ if (RTCritSectIsInitialized(&pThisCC->CritSect))
+ RTCritSectDelete(&pThisCC->CritSect);
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->CsIrq);
+
+ /*
+ * Tear down the per endpoint in-flight tracking...
+ */
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct,OHCI constructor}
+ */
+static DECLCALLBACK(int) ohciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+ POHCIR3 pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, POHCIR3);
+
+ /*
+ * Init instance data.
+ */
+ pThisCC->pDevInsR3 = pDevIns;
+
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ PDMPCIDEV_ASSERT_VALID(pDevIns, pPciDev);
+
+ PDMPciDevSetVendorId(pPciDev, 0x106b);
+ PDMPciDevSetDeviceId(pPciDev, 0x003f);
+ PDMPciDevSetClassProg(pPciDev, 0x10); /* OHCI */
+ PDMPciDevSetClassSub(pPciDev, 0x03);
+ PDMPciDevSetClassBase(pPciDev, 0x0c);
+ PDMPciDevSetInterruptPin(pPciDev, 0x01);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMPciDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PDMPciDevSetCapabilityList(pPciDev, 0x80);
+#endif
+
+ pThisCC->RootHub.pOhci = pThis;
+ pThisCC->RootHub.IBase.pfnQueryInterface = ohciR3RhQueryInterface;
+ pThisCC->RootHub.IRhPort.pfnGetAvailablePorts = ohciR3RhGetAvailablePorts;
+ pThisCC->RootHub.IRhPort.pfnGetUSBVersions = ohciR3RhGetUSBVersions;
+ pThisCC->RootHub.IRhPort.pfnAttach = ohciR3RhAttach;
+ pThisCC->RootHub.IRhPort.pfnDetach = ohciR3RhDetach;
+ pThisCC->RootHub.IRhPort.pfnReset = ohciR3RhReset;
+ pThisCC->RootHub.IRhPort.pfnXferCompletion = ohciR3RhXferCompletion;
+ pThisCC->RootHub.IRhPort.pfnXferError = ohciR3RhXferError;
+ pThisCC->RootHub.IRhPort.pfnStartFrame = ohciR3StartFrame;
+ pThisCC->RootHub.IRhPort.pfnFrameRateChanged = ohciR3FrameRateChanged;
+
+ /* USB LED */
+ pThisCC->RootHub.Led.u32Magic = PDMLED_MAGIC;
+ pThisCC->RootHub.ILeds.pfnQueryStatusLed = ohciR3RhQueryStatusLed;
+
+
+ /*
+ * Read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "Ports", "");
+
+ /* Number of ports option. */
+ uint32_t cPorts;
+ int rc = pDevIns->pHlpR3->pfnCFGMQueryU32Def(pCfg, "Ports", &cPorts, OHCI_NDP_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("OHCI configuration error: failed to read Ports as integer"));
+ if (cPorts == 0 || cPorts > OHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("OHCI configuration error: Ports must be in range [%u,%u]"),
+ 1, OHCI_NDP_MAX);
+
+ /* Store the configured NDP; it will be used everywhere else from now on. */
+ pThis->RootHub.desc_a = cPorts;
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ if (RT_FAILURE(rc))
+ return rc;
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = 0x80;
+ MsiReg.iMsiNextOffset = 0x00;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE(rc))
+ {
+ PDMPciDevSetCapabilityList(pPciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, 4096, PCI_ADDRESS_SPACE_MEM, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED
+ | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE, "USB OHCI", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, ohciR3SaveExec, NULL,
+ NULL, ohciR3LoadExec, NULL);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #0.
+ */
+ rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThisCC->RootHub.IBase, &pThisCC->RootHub.pIBase, "RootHub");
+ if (RT_FAILURE(rc))
+ {
+ AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n"));
+ return rc;
+ }
+ pThisCC->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThisCC->RootHub.pIBase, VUSBIROOTHUBCONNECTOR);
+ AssertMsgReturn(pThisCC->RootHub.pIRhConn,
+ ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /*
+ * Attach status driver (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->RootHub.IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThisCC->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc));
+ return rc;
+ }
+
+ /* Set URB parameters. */
+ rc = VUSBIRhSetUrbParams(pThisCC->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT));
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to set URB parameters"));
+
+ /*
+ * Take down the virtual clock frequence for use in ohciR3FrameRateChanged().
+ * (Used to be a timer, thus the name.)
+ */
+ pThis->u64TimerHz = PDMDevHlpTMTimeVirtGetFreq(pDevIns);
+
+ /*
+ * Critical sections: explain
+ */
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "OHCI#%uIrq", iInstance);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section"));
+
+ rc = RTCritSectInit(&pThisCC->CritSect);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to create critical section"));
+
+ /*
+ * Do a hardware reset.
+ */
+ ohciR3DoReset(pDevIns, pThis, pThisCC, OHCI_USB_RESET, false /* don't reset devices */);
+
+# ifdef VBOX_WITH_STATISTICS
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledGenUrbs, STAMTYPE_COUNTER, "CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatDroppedUrbs, STAMTYPE_COUNTER, "DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled).");
+# endif
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "ohci", "OHCI control registers.", ohciR3InfoRegs);
+
+# if 0/*def DEBUG_bird*/
+// g_fLogInterruptEPs = true;
+ g_fLogControlEPs = true;
+ g_fLogBulkEPs = true;
+# endif
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) ohciRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ POHCI pThis = PDMDEVINS_2_DATA(pDevIns, POHCI);
+
+ int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, ohciMmioWrite, ohciMmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+const PDMDEVREG g_DeviceOHCI =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "usb-ohci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(OHCI),
+ /* .cbInstanceCC = */ sizeof(OHCICC),
+ /* .cbInstanceRC = */ 0,
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "OHCI USB controller.\n",
+#if defined(IN_RING3)
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+ /* .pfnConstruct = */ ohciR3Construct,
+ /* .pfnDestruct = */ ohciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ ohciR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ ohciR3Resume,
+ /* .pfnAttach = */ NULL,
+ /* .pfnDetach = */ NULL,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ ohciRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ ohciRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/USB/DevXHCI.cpp b/src/VBox/Devices/USB/DevXHCI.cpp
new file mode 100644
index 00000000..ede6413c
--- /dev/null
+++ b/src/VBox/Devices/USB/DevXHCI.cpp
@@ -0,0 +1,8295 @@
+/* $Id: DevXHCI.cpp $ */
+/** @file
+ * DevXHCI - eXtensible Host Controller Interface for USB.
+ */
+
+/*
+ * Copyright (C) 2012-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+/** @page pg_dev_xhci xHCI - eXtensible Host Controller Interface Emulation.
+ *
+ * This component implements an xHCI USB controller.
+ *
+ * The xHCI device is significantly different from the EHCI and OHCI
+ * controllers in that it is not timer driven. A worker thread is responsible
+ * for transferring data between xHCI and VUSB.
+ *
+ * Since there can be dozens or even hundreds of USB devices, and because USB
+ * transfers must share the same bus, only one worker thread is created (per
+ * host controller).
+ *
+ *
+ * The xHCI operational model is heavily based around a producer/consumer
+ * model utilizing rings -- Command, Event, and Transfer rings. The Event ring
+ * is only written by the xHC and is read-only for the HCD (Host Controller
+ * Driver). The Command/Transfer rings are only written by the HCD and are
+ * read-only for the xHC.
+ *
+ * The rings contain TRBs (Transfer Request Blocks). The TRBs represent not
+ * only data transfers but also commands and status information. Each type of
+ * ring only produces/consumes specific TRB types.
+ *
+ * When processing a ring, the xHC simply keeps advancing an internal pointer.
+ * For the Command/Transfer rings, the HCD uses Link TRBs to manage the ring
+ * storage in a fairly arbitrary manner. Since the HCD cannot write to the
+ * Event ring, the Event Ring Segment Table (ERST) is used to manage the ring
+ * storage instead.
+ *
+ * The Cycle bit is used to manage the ring buffer full/empty condition. The
+ * Producer and Consumer both have their own Cycle State (PCS/CCS). The Cycle
+ * bit of each TRB determines who owns it. The consumer only processes TRBs
+ * whose Cycle bit matches the CCS. HCD software typically toggles the Cycle
+ * bit on each pass through the ring. The Link TRB can be used to toggle the
+ * CCS accordingly.
+ *
+ * Multiple Transfer TRBs can be chained together (via the Chain bit) into a
+ * single Transfer Descriptor (TD). This provides a convenient capability for
+ * the HCD to turn a URB into a single TD regardless of how the URB is laid
+ * out in physical memory. If a transfer encounters an error or is terminated
+ * by a short packet, the entire TD (i.e. chain of TRBs) is retired.
+ *
+ * Note that the xHC detects and handles short packets on its own. Backends
+ * are always asked not to consider a short packet to be an error condition.
+ *
+ * Command and Event TRBs cannot be chained, thus an ED (Event Descriptor)
+ * or a Command Descriptor (CD) always consists of a single TRB.
+ *
+ * There is one Command ring per xHC, one Event ring per interrupter (one or
+ * more), and a potentially very large number of Transfer rings. There is a
+ * 1:1 mapping between Transfer Rings and USB pipes, hence each USB device
+ * uses 1-31 Transfer rings (at least one for the default control endpoint,
+ * up to 31 if all IN/OUT endpoints are used). USB 3.0 devices may also use
+ * up to 64K streams per endpoint, each with its Transfer ring, massively
+ * increasing the potential number of Transfer rings in use.
+ *
+ * When building a Transfer ring, it's possible to queue up a large number
+ * of TDs and as soon as the oldest ones are retired, queue up new TDs. The
+ * Transfer ring might thus never be empty.
+ *
+ * For tracking ring buffer position, the TRDP and TREP fields in an endpoint
+ * context are used. The TRDP is the 'TR Dequeue Pointer', i.e. the position
+ * of the next TRB to be completed. This field is visible by the HCD when the
+ * endpoint isn't running. It reflects TRBs completely processed by the xHC
+ * and hence no longer owned by the xHC.
+ *
+ * The TREP field is the 'TR Enqueue Pointer' and tracks the position of the
+ * next TRB to start processing (submit). This is purely internal to the
+ * xHC. The TREP can potentially get far ahead of the TRDP, but only in the
+ * part of the ring owned by the xHC (i.e. with matching DCS bit).
+ *
+ * Unlike most other xHCI data structures, transfer TRBs may describe memory
+ * buffers with no alignment restrictions (both starting position and size).
+ * In addition, there is no relationship between TRB boundaries and USB
+ * packet boundaries.
+ *
+ *
+ * Typically an event would be generated via the IOC bit (Interrupt On
+ * Completion) when the last TRB of a TD is completed. However, multiple IOC
+ * bits may be set per TD. This may be required when a TD equal or larger
+ * than 16MB is used, since transfer events utilize a 24-bit length field.
+ *
+ * There is also the option of using Transfer Event TRBs to report TRB
+ * completion. Transfer Event TRBs may be freely intermixed with transfer
+ * TRBs. Note that an event TRB will produce an event reporting the size of
+ * data transferred since the last event TRB or since the beginning of a TD.
+ * The xHC submits URBs such that they either comprise the entire TD or end
+ * at a Transfer Event TRB, thus there is no need to track the EDTLA
+ * separately.
+ *
+ * Transfer errors always generate events, irrespective of IOC settings. The
+ * xHC has always the option to generate events at implementation-specific
+ * points so that the HCD does not fall too far behind.
+ *
+ * Control transfers use special TDs. A Setup Stage TD consists of only a
+ * single Setup Stage TRB (there's no Chain bit). The optional Data Stage
+ * TD consists of a Data Stage TRB chained to zero or more Normal TRBs
+ * and/or Event Data TRBs. The Status Stage TD then consists of a Status
+ * Stage TRB optionally chained to an Event Data TRB. The HCD is responsible
+ * for building the TDs correctly.
+ *
+ * For isochronous transfers, only the first TRB of a TD is actually an
+ * isochronous TRB. If the TD is chained, it will contain Normal TRBs (and
+ * possibly Event Data TRBs).
+ *
+ *
+ * Isochronous transfers require multiple TDs/URBs to be in flight at a
+ * time. This complicates dealing with non-data TRBs (such as link or event
+ * data TRBs). These TRBs cannot be completed while a previous TRB is still
+ * in flight. They are completed either: a) when submitting URBs and there
+ * are no in-flight URBs, or b) just prior to completing an URB.
+ *
+ * This approach works because URBs must be completed strictly in-order. The
+ * TRDP and TREP determine whether there are in-flight TRBs (TREP equals
+ * TRDP if and only if there are no in-flight TRBs).
+ *
+ * When submitting TRBs and there is in-flight traffic, non-data TRBs must
+ * be examined and skipped over. Link TRBs need to be taken into account.
+ *
+ * Unfortunately, certain HCDs (looking at you, Microsoft!) violate the xHCI
+ * specification and make assumptions about how far ahead of the TRDP the
+ * xHC can get. We have to artificially limit the number of in-flight TDs
+ * for this reason.
+ *
+ * Non-isochronous TRBs do not require this treatment for correct function
+ * but are likely to benefit performance-wise from the pipelining.
+ *
+ * With high-speed and faster transfers, there is an added complication for
+ * endpoints with more than one transfer per frame, i.e. short intervals. At
+ * least some host USB stacks require URBs to cover an entire frame, which
+ * means we may have to glue together several TDs into a single URB.
+ *
+ *
+ * A buggy or malicious guest can create a transfer or command ring that
+ * loops in on itself (in the simplest case using a sequence of one or more
+ * link TRBs where the last TRB points to the beginning of the sequence).
+ * Such a loop would effectively hang the processing thread. Since we cannot
+ * easily detect a generic loop, and because even non-looped TRB/command
+ * rings might contain extremely large number of items, we limit the number
+ * of entries that we are willing to process at once. If the limit is
+ * crossed, the xHC reports a host controller error and shuts itself down
+ * until it's reset.
+ *
+ * Note that for TRB lists, both URB submission and completion must protect
+ * against loops because the lists in guest memory are not guaranteed to stay
+ * unchanged between submitting and completing URBs.
+ *
+ * The event ring is not susceptible to loops because the xHC is the producer,
+ * not consumer. The event ring can run out of space but that is not a fatal
+ * problem.
+ *
+ *
+ * The interrupt logic uses an internal IPE (Interrupt Pending Enable) bit
+ * which controls whether the register-visible IP (Interrupt Pending) bit
+ * can be set. The IPE bit is set when a non-blocking event (BEI bit clear)
+ * is enqueued. The IPE bit is cleared when the event ring is initialized or
+ * transitions to empty (i.e. ERDP == EREP). When IPE transtitions to set,
+ * it will set IP unless the EHB (Event Handler Busy) bit is set or IMODC
+ * (Interrupt Moderation Counter) is non-zero. When IMODC counts down to
+ * zero, it sets the IP bit if IPE is set and EHB is not. Setting the IP bit
+ * triggers interrupt delivery. Note that clearing the IPE bit does not
+ * change the IP bit state.
+ *
+ * Interrupt delivery depends on whether MSI/MSI-X is in use or not. With MSI,
+ * an interrupter's IP (Interrupt Pending) bit is cleared as soon as the MSI
+ * message is written; with classic PCI interrupt delivery, the HCD must clear
+ * the IP bit. However, the EHB (Event Handler Busy) bit is always set, which
+ * causes further interrupts to be blocked on the interrupter until the HCD
+ * processes pending events and clears the EHB bit.
+ *
+ * Note that clearing the EHB bit may immediately trigger an interrupt if
+ * additional event TRBs were queued up while the HCD was processing previous
+ * ones.
+ *
+ *
+ * Each enabled USB device has a corresponding slot ID, a doorbell, as well as
+ * a device context which can be accessed through the DCBAA (Device Context
+ * Base Address Array). Valid slot IDs are in the 1-255 range; the first entry
+ * (i.e. index 0) in the DCBAA may optionally point to the Scratchpad Buffer
+ * Array, while doorbell 0 is associated with the Command Ring.
+ *
+ * While 255 valid slot IDs is an xHCI architectural limit, existing xHC
+ * implementations usually set a considerably lower limit, such as 32. See
+ * the XHCI_NDS constant.
+ *
+ * It would be tempting to use the DCBAA to determine which slots are free.
+ * Unfortunately the xHC is not allowed to access DCBAA entries which map to
+ * disabled slots (see section 6.1). A parallel aSlotState array is hence used
+ * to internally track the slot state and find available slots. Once a slot
+ * is enabled, the slot context entry in the DCBAA is used to track the
+ * slot state.
+ *
+ *
+ * Unlike OHCI/UHCI/EHCI, the xHC much more closely tracks USB device state.
+ * HCDs are not allowed to issue SET_ADDRESS requests at all and must use
+ * the Address Device xHCI command instead.
+ *
+ * HCDs can use SET_CONFIGURATION and SET_INTERFACE requests normally, but
+ * must inform the xHC of the changes via Configure Endpoint and Evaluate
+ * Context commands. Similarly there are Reset Endpoint and Stop Endpoint
+ * commands to manage endpoint state.
+ *
+ * A corollary of the above is that unlike OHCI/UHCI/EHCI, with xHCI there
+ * are very clear rules and a straightforward protocol for managing
+ * ownership of structures in physical memory. During normal operation, the
+ * xHC owns all device context memory and the HCD must explicitly ask the xHC
+ * to relinquish the ownership.
+ *
+ * The xHCI architecture offers an interesting feature in that it reserves
+ * opaque fields for xHCI use in certain data structures (slot and endpoint
+ * contexts) and gives the xHC an option to request scratchpad buffers that
+ * a HCD must provide. The xHC may use the opaque storage and/or scratchpad
+ * buffers for saving internal state.
+ *
+ * For implementation reasons, the xHCI device creates two root hubs on the
+ * VUSB level; one for USB2 devices (USB 1.x and 2.0), one for USB3. The
+ * behavior of USB2 vs. USB3 ports is different, and a device can only be
+ * attached to either one or the other hub. However, there is a single array
+ * of ports to avoid overly complicating the code, given that port numbering
+ * is linear and encompasses both USB2 and USB3 ports.
+ *
+ *
+ * The default emulated device is an Intel 7-Series xHC aka Panther Point.
+ * This was Intel's first xHC and is widely supported. It is also possible
+ * to select an Intel 8-Series xHC aka Lynx Point; this is only useful for
+ * debugging and requires the 'other' set of Windows 7 drivers.
+ *
+ * For Windows XP guest support, it is possible to emulate a Renesas
+ * (formerly NEC) uPD720201 xHC. It would be possible to emulate the earlier
+ * NEC chips but those a) only support xHCI 0.96, and b) their drivers
+ * require a reboot during installation. Renesas' drivers also support
+ * Windows Vista and 7.
+ *
+ *
+ * NB: Endpoints are addressed differently in xHCI and USB. In USB,
+ * endpoint addresses are 8-bit values with the low four bits identifying
+ * the endpoint number and the high bit indicating the direction (0=OUT,
+ * 1=IN); see e.g. 9.3.4 in USB 2.0 spec. In xHCI, endpoint addresses are
+ * used as DCIs (Device Context Index) and for that reason, they're
+ * compressed into 5 bits where the lowest bit(!) indicates direction (again
+ * 1=IN) and bits 1-4 designate the endpoint number. Endpoint 0 is somewhat
+ * special and uses DCI 1. See 4.8.1 in xHCI spec.
+ *
+ *
+ * NB: A variable named iPort is a zero-based index into the port array.
+ * On the other hand, a variable named uPort is a one-based port number!
+ * The implementation (obviously) uses zero-based indexing, but USB ports
+ * are numbered starting with 1. The same is true of xHCI slot numbering.
+ * The macros IDX_TO_ID() and ID_TO_IDX(a) should be used to convert between
+ * the two numbering conventions to make the intent clear.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DEV_XHCI
+#include <VBox/pci.h>
+#include <VBox/msi.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#ifdef IN_RING3
+# include <iprt/uuid.h>
+# include <iprt/critsect.h>
+#endif
+#include <VBox/vusb.h>
+#ifdef VBOX_IN_EXTPACK_R3
+# include <VBox/version.h>
+#endif
+#ifndef VBOX_IN_EXTPACK
+# include "VBoxDD.h"
+#endif
+
+
+/*********************************************************************************************************************************
+* (Most of the) Defined Constants, Macros and Structures *
+*********************************************************************************************************************************/
+
+/* Optional error injection support via DBGF. */
+//#define XHCI_ERROR_INJECTION
+
+/** The saved state version. */
+#define XHCI_SAVED_STATE_VERSION 1
+
+
+/** Convert a zero-based index to a 1-based ID. */
+#define IDX_TO_ID(a) (a + 1)
+/** Convert a 1-based ID to a zero-based index. */
+#define ID_TO_IDX(a) (a - 1)
+
+/** PCI device related constants. */
+#define XHCI_PCI_MSI_CAP_OFS 0x80
+
+/** Number of LUNs/root hubs. One each for USB2/USB3. */
+#define XHCI_NUM_LUNS 2
+
+/** @name The following two constants were determined experimentally.
+ * They determine the maximum number of TDs allowed to be in flight.
+ * NB: For isochronous TDs, the number *must* be limited because
+ * Windows 8+ violates the xHCI specification and does not keep
+ * the transfer rings consistent.
+ * @{
+ */
+//#define XHCI_MAX_ISOC_IN_FLIGHT 3 /* Scarlett needs 3; was 12 */
+#define XHCI_MAX_ISOC_IN_FLIGHT 12
+#define XHCI_MAX_BULK_IN_FLIGHT 8
+/** @} */
+
+/** @name Implementation limit on the number of TRBs and commands
+ * the xHC is willing to process at once. A larger number is taken
+ * to indicate a broken or malicious guest, and causes a HC error.
+ * @{
+ */
+#define XHCI_MAX_NUM_CMDS 128
+#define XHCI_MAX_NUM_TRBS 1024
+/** @} */
+
+/** Implementation TD size limit. Prevents EDTLA wrap-around. */
+#define XHCI_MAX_TD_SIZE (16 * _1M - 1)
+
+/** Special value to prevent further queuing. */
+#define XHCI_NO_QUEUING_IN_FLIGHT (XHCI_MAX_BULK_IN_FLIGHT * 2)
+
+/* Structural Parameters #1 (HCSPARAMS1) values. */
+
+/** Maximum allowed Number of Downstream Ports on the root hub. Careful
+ * when changing -- other structures may need adjusting!
+ */
+#define XHCI_NDP_MAX 32
+
+/** Default number of USB 2.0 ports.
+ *
+ * @note AppleUSBXHCI does not handle more than 15 ports. At least OS X
+ * 10.8.2 crashes if we report more than 15 ports! Hence the default
+ * is 8 USB2 + 6 USB3 ports for a total of 14 so that OS X is happy.
+ */
+#define XHCI_NDP_20_DEFAULT 8
+
+/** Default number of USB 3.0 ports. */
+#define XHCI_NDP_30_DEFAULT 6
+
+/** Number of interrupters. */
+#define XHCI_NINTR 8
+
+/** Mask for interrupter indexing. */
+#define XHCI_INTR_MASK (XHCI_NINTR - 1)
+
+/* The following is only true if XHCI_NINTR is a (non-zero) power of two. */
+AssertCompile((XHCI_NINTR & XHCI_INTR_MASK) == 0);
+
+/** Number of Device Slots. Determines the number of doorbell
+ * registers and device slots, among other things. */
+#define XHCI_NDS 32
+
+/* Enforce xHCI architectural limits on HCSPARAMS1. */
+AssertCompile(XHCI_NDP_MAX < 255 && XHCI_NINTR < 1024 && XHCI_NDS < 255);
+AssertCompile(XHCI_NDP_20_DEFAULT + XHCI_NDP_30_DEFAULT <= XHCI_NDP_MAX);
+AssertCompile(XHCI_NDP_MAX <= XHCI_NDS);
+
+/* Structural Parameters #2 (HCSPARAMS2) values. */
+
+/** Isochronous Scheduling Threshold. */
+#define XHCI_IST (RT_BIT(3) | 1) /* One frame. */
+
+/** Max number of Event Ring Segment Table entries as a power of two. */
+#define XHCI_ERSTMAX_LOG2 5
+/** Max number of Event Ring Segment Table entries. */
+#define XHCI_ERSTMAX RT_BIT(XHCI_ERSTMAX_LOG2)
+
+/* Enforce xHCI architectural limits on HCSPARAMS2. */
+AssertCompile(XHCI_ERSTMAX_LOG2 < 16);
+
+
+/** Size of the xHCI memory-mapped I/O region. */
+#define XHCI_MMIO_SIZE _64K
+
+/** Size of the capability part of the MMIO region. */
+#define XHCI_CAPS_REG_SIZE 0x80
+
+/** Offset of the port registers in operational register space. */
+#define XHCI_PORT_REG_OFFSET 0x400
+
+/** Offset of xHCI extended capabilities in MMIO region. */
+#define XHCI_XECP_OFFSET 0x1000
+
+/** Offset of the run-time registers in MMIO region. */
+#define XHCI_RTREG_OFFSET 0x2000
+
+/** Offset of the doorbell registers in MMIO region. */
+#define XHCI_DOORBELL_OFFSET 0x3000
+
+/** Size of the extended capability area. */
+#define XHCI_EXT_CAP_SIZE 1024
+
+/* Make sure we can identify MMIO register accesses properly. */
+AssertCompile(XHCI_DOORBELL_OFFSET > XHCI_RTREG_OFFSET);
+AssertCompile(XHCI_XECP_OFFSET > XHCI_PORT_REG_OFFSET + XHCI_CAPS_REG_SIZE);
+AssertCompile(XHCI_RTREG_OFFSET > XHCI_XECP_OFFSET + XHCI_EXT_CAP_SIZE);
+
+
+/** Maximum size of a single extended capability. */
+#define MAX_XCAP_SIZE 256
+
+/** @name xHCI Extended capability types.
+ * @{ */
+#define XHCI_XCP_USB_LEGACY 1 /**< USB legacy support. */
+#define XHCI_XCP_PROTOCOL 2 /**< Protocols supported by ports. */
+#define XHCI_XCP_EXT_PM 3 /**< Extended power management (non-PCI). */
+#define XHCI_XCP_IOVIRT 4 /**< Hardware xHCI virtualization support. */
+#define XHCI_XCP_MSI 5 /**< Message interrupts (non-PCI). */
+#define XHCI_XCP_LOCAL_MEM 6 /**< Local memory (for debug support). */
+#define XHCI_XCP_USB_DEBUG 10 /**< USB debug capability. */
+#define XHCI_XCP_EXT_MSI 17 /**< MSI-X (non-PCI). */
+/** @} */
+
+
+/* xHCI Register Bits. */
+
+
+/** @name Capability Parameters (HCCPARAMS) bits
+ * @{ */
+#define XHCI_HCC_AC64 RT_BIT(0) /**< RO */
+#define XHCI_HCC_BNC RT_BIT(1) /**< RO */
+#define XHCI_HCC_CSZ RT_BIT(2) /**< RO */
+#define XHCI_HCC_PPC RT_BIT(3) /**< RO */
+#define XHCI_HCC_PIND RT_BIT(4) /**< RO */
+#define XHCI_HCC_LHRC RT_BIT(5) /**< RO */
+#define XHCI_HCC_LTC RT_BIT(6) /**< RO */
+#define XHCI_HCC_NSS RT_BIT(7) /**< RO */
+#define XHCI_HCC_MAXPSA_MASK (RT_BIT(12)|RT_BIT(13)|RT_BIT(14)| RT_BIT(15)) /**< RO */
+#define XHCI_HCC_MAXPSA_SHIFT 12
+#define XHCI_HCC_XECP_MASK 0xFFFF0000 /**< RO */
+#define XHCI_HCC_XECP_SHIFT 16
+/** @} */
+
+
+/** @name Command Register (USBCMD) bits
+ * @{ */
+#define XHCI_CMD_RS RT_BIT(0) /**< RW - Run/Stop */
+#define XHCI_CMD_HCRST RT_BIT(1) /**< RW - Host Controller Reset */
+#define XHCI_CMD_INTE RT_BIT(2) /**< RW - Interrupter Enable */
+#define XHCI_CMD_HSEE RT_BIT(3) /**< RW - Host System Error Enable */
+#define XHCI_CMD_LCRST RT_BIT(7) /**< RW - Light HC Reset */
+#define XHCI_CMD_CSS RT_BIT(8) /**< RW - Controller Save State */
+#define XHCI_CMD_CRS RT_BIT(9) /**< RW - Controller Restore State */
+#define XHCI_CMD_EWE RT_BIT(10) /**< RW - Enable Wrap Event */
+#define XHCI_CMD_EU3S RT_BIT(11) /**< RW - Enable U3 MFINDEX Stop */
+
+#define XHCI_CMD_MASK ( XHCI_CMD_RS | XHCI_CMD_HCRST | XHCI_CMD_INTE | XHCI_CMD_HSEE | XHCI_CMD_LCRST \
+ | XHCI_CMD_CSS | XHCI_CMD_CRS | XHCI_CMD_EWE | XHCI_CMD_EU3S)
+/** @} */
+
+
+/** @name Status Register (USBSTS) bits
+ * @{ */
+#define XHCI_STATUS_HCH RT_BIT(0) /**< RO - HC Halted */
+#define XHCI_STATUS_HSE RT_BIT(2) /**< RW1C - Host System Error */
+#define XHCI_STATUS_EINT RT_BIT(3) /**< RW1C - Event Interrupt */
+#define XHCI_STATUS_PCD RT_BIT(4) /**< RW1C - Port Change Detect */
+#define XHCI_STATUS_SSS RT_BIT(8) /**< RO - Save State Status */
+#define XHCI_STATUS_RSS RT_BIT(9) /**< RO - Resture State Status */
+#define XHCI_STATUS_SRE RT_BIT(10) /**< RW1C - Save/Restore Error */
+#define XHCI_STATUS_CNR RT_BIT(11) /**< RO - Controller Not Ready */
+#define XHCI_STATUS_HCE RT_BIT(12) /**< RO - Host Controller Error */
+
+#define XHCI_STATUS_WRMASK (XHCI_STATUS_HSE | XHCI_STATUS_EINT | XHCI_STATUS_PCD | XHCI_STATUS_SRE)
+/** @} */
+
+
+/** @name Default xHCI speed definitions (7.2.2.1.1)
+ * @{ */
+#define XHCI_SPD_FULL 1
+#define XHCI_SPD_LOW 2
+#define XHCI_SPD_HIGH 3
+#define XHCI_SPD_SUPER 4
+/** @} */
+
+/** @name Port Status and Control Register bits (PORTSCUSB2/PORTSCUSB3)
+ * @{ */
+#define XHCI_PORT_CCS RT_BIT(0) /**< ROS - Current Connection Status */
+#define XHCI_PORT_PED RT_BIT(1) /**< RW1S - Port Enabled/Disabled */
+#define XHCI_PORT_OCA RT_BIT(3) /**< RO - Over-current Active */
+#define XHCI_PORT_PR RT_BIT(4) /**< RW1S - Port Reset */
+#define XHCI_PORT_PLS_MASK (RT_BIT(5) | RT_BIT(6) | RT_BIT(7) | RT_BIT(8)) /**< RWS */
+#define XHCI_PORT_PLS_SHIFT 5
+#define XHCI_PORT_PP RT_BIT(9) /**< RWS - Port Power */
+#define XHCI_PORT_SPD_MASK (RT_BIT(10) | RT_BIT(11) | RT_BIT(12) | RT_BIT(13)) /**< ROS */
+#define XHCI_PORT_SPD_SHIFT 10
+#define XHCI_PORT_LWS RT_BIT(16) /**< RW - Link State Write Strobe */
+#define XHCI_PORT_CSC RT_BIT(17) /**< RW1CS - Connect Status Change */
+#define XHCI_PORT_PEC RT_BIT(18) /**< RW1CS - Port Enabled/Disabled Change */
+#define XHCI_PORT_WRC RT_BIT(19) /**< RW1CS - Warm Port Reset Change */
+#define XHCI_PORT_OCC RT_BIT(20) /**< RW1CS - Over-current Change */
+#define XHCI_PORT_PRC RT_BIT(21) /**< RW1CS - Port Reset Change */
+#define XHCI_PORT_PLC RT_BIT(22) /**< RW1CS - Port Link State Change */
+#define XHCI_PORT_CEC RT_BIT(23) /**< RW1CS - Port Config Error Change */
+#define XHCI_PORT_CAS RT_BIT(24) /**< RO - Cold Attach Status */
+#define XHCI_PORT_WCE RT_BIT(25) /**< RWS - Wake on Connect Enable */
+#define XHCI_PORT_WDE RT_BIT(26) /**< RWS - Wake on Disconnect Enable */
+#define XHCI_PORT_WOE RT_BIT(27) /**< RWS - Wake on Over-current Enable */
+#define XHCI_PORT_DR RT_BIT(30) /**< RO - Device (Not) Removable */
+#define XHCI_PORT_WPR RT_BIT(31) /**< RW1S - Warm Port Reset */
+
+#define XHCI_PORT_RESERVED (RT_BIT(2) | RT_BIT(14) | RT_BIT(15) | RT_BIT(28) | RT_BIT(29))
+
+#define XHCI_PORT_WAKE_MASK (XHCI_PORT_WCE|XHCI_PORT_WDE|XHCI_PORT_WOE)
+#define XHCI_PORT_CHANGE_MASK (XHCI_PORT_CSC|XHCI_PORT_PEC|XHCI_PORT_WRC|XHCI_PORT_OCC|XHCI_PORT_PRC|XHCI_PORT_PLC|XHCI_PORT_CEC)
+#define XHCI_PORT_CTL_RW_MASK (XHCI_PORT_PP|XHCI_PORT_LWS)
+#define XHCI_PORT_CTL_W1_MASK (XHCI_PORT_PED|XHCI_PORT_PR|XHCI_PORT_WPR)
+#define XHCI_PORT_RO_MASK (XHCI_PORT_CCS|XHCI_PORT_OCA|XHCI_PORT_SPD_MASK|XHCI_PORT_CAS|XHCI_PORT_DR)
+/** @} */
+
+/** @name Port Link State values
+ * @{ */
+#define XHCI_PLS_U0 0 /**< U0 State. */
+#define XHCI_PLS_U1 1 /**< U1 State. */
+#define XHCI_PLS_U2 2 /**< U2 State. */
+#define XHCI_PLS_U3 3 /**< U3 State (Suspended). */
+#define XHCI_PLS_DISABLED 4 /**< Disabled. */
+#define XHCI_PLS_RXDETECT 5 /**< RxDetect. */
+#define XHCI_PLS_INACTIVE 6 /**< Inactive. */
+#define XHCI_PLS_POLLING 7 /**< Polling. */
+#define XHCI_PLS_RECOVERY 8 /**< Recovery. */
+#define XHCI_PLS_HOTRST 9 /**< Hot Reset. */
+#define XHCI_PLS_CMPLMODE 10 /**< Compliance Mode. */
+#define XHCI_PLS_TSTMODE 11 /**< Test Mode. */
+/* Values 12-14 are reserved. */
+#define XHCI_PLS_RESUME 15 /**< Resume. */
+/** @} */
+
+
+/** @name Command Ring Control Register (CRCR) bits
+ * @{ */
+#define XHCI_CRCR_RCS RT_BIT(0) /**< RW - Ring Cycle State */
+#define XHCI_CRCR_CS RT_BIT(1) /**< RW1S - Command Stop */
+#define XHCI_CRCR_CA RT_BIT(2) /**< RW1S - Command Abort */
+#define XHCI_CRCR_CRR RT_BIT(3) /**< RO - Command Ring Running */
+
+#define XHCI_CRCR_RD_MASK UINT64_C(0xFFFFFFFFFFFFFFF8) /* Mask off bits always read as zero. */
+#define XHCI_CRCR_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0)
+#define XHCI_CRCR_UPD_MASK (XHCI_CRCR_ADDR_MASK | XHCI_CRCR_RCS)
+/** @} */
+
+
+/** @name Interrupter Management Register (IMAN) bits
+ * @{ */
+#define XHCI_IMAN_IP RT_BIT(0) /**< RW1C - Interrupt Pending */
+#define XHCI_IMAN_IE RT_BIT(1) /**< RW - Interrupt Enable */
+
+#define XHCI_IMAN_VALID_MASK (XHCI_IMAN_IP | XHCI_IMAN_IE)
+/** @} */
+
+
+/** @name Interrupter Moderation Register (IMOD) bits
+ * @{ */
+#define XHCI_IMOD_IMODC_MASK 0xFFFF0000 /**< RW */
+#define XHCI_IMOD_IMODC_SHIFT 16
+#define XHCI_IMOD_IMODI_MASK 0x0000FFFF /**< RW */
+/** @} */
+
+
+/** @name Event Ring Segment Table Size Register (ERSTSZ) bits
+ * @{ */
+#define XHCI_ERSTSZ_MASK 0x0000FFFF /**< RW */
+/** @} */
+
+/** @name Event Ring Segment Table Base Address Register (ERSTBA) bits
+ * @{ */
+#define XHCI_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0)
+/** @} */
+
+/** For reasons that are not obvious, NEC/Renesas xHCs only require 16-bit
+ * alignment for the ERST base. This is not in line with the xHCI spec
+ * (which requires 64-bit alignment) but is clearly documented by NEC.
+ */
+#define NEC_ERST_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0)
+
+/** Firmware revision reported in NEC/Renesas mode. Value chosen based on
+ * OS X driver check (OS X supports these chips since they're commonly
+ * found in ExpressCards).
+ */
+#define NEC_FW_REV 0x3028
+
+/** @name Event Ring Deqeue Pointer Register (ERDP) bits
+ * @{ */
+#define XHCI_ERDP_DESI_MASK 0x00000007 /**< RW - Dequeue ERST Segment Index */
+#define XHCI_ERDP_EHB RT_BIT(3) /**< RW1C - Event Handler Busy */
+#define XHCI_ERDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0) /**< RW - ERDP address mask */
+/** @} */
+
+/** @name Device Context Base Address Array (DCBAA) definitions
+ * @{ */
+#define XHCI_DCBAA_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFC0) /**< Applies to DCBAAP and its entries. */
+/** @} */
+
+/** @name Doorbell Register bits
+ * @{ */
+#define XHCI_DB_TGT_MASK 0x000000FF /**< DB Target mask. */
+#define XHCI_DB_STRMID_SHIFT 16 /**< DB Stream ID shift. */
+#define XHCI_DB_STRMID_MASK 0xFFFF0000 /**< DB Stream ID mask. */
+/** @} */
+
+/** Address mask for device/endpoint/input contexts. */
+#define XHCI_CTX_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0)
+
+/** @name TRB Completion Codes
+ * @{ */
+#define XHCI_TCC_INVALID 0 /**< CC field not updated. */
+#define XHCI_TCC_SUCCESS 1 /**< Successful TRB completion. */
+#define XHCI_TCC_DATA_BUF_ERR 2 /**< Overrun/underrun. */
+#define XHCI_TCC_BABBLE 3 /**< Babble detected. */
+#define XHCI_TCC_USB_XACT_ERR 4 /**< USB transaction error. */
+#define XHCI_TCC_TRB_ERR 5 /**< TRB error detected. */
+#define XHCI_TCC_STALL 6 /**< USB Stall detected. */
+#define XHCI_TCC_RSRC_ERR 7 /**< Inadequate xHC resources. */
+#define XHCI_TCC_BWIDTH_ERR 8 /**< Unable to allocate bandwidth. */
+#define XHCI_TCC_NO_SLOTS 9 /**< MaxSlots (NDS) exceeded. */
+#define XHCI_TCC_INV_STRM_TYP 10 /**< Invalid stream context type. */
+#define XHCI_TCC_SLOT_NOT_ENB 11 /**< Slot not enabled. */
+#define XHCI_TCC_EP_NOT_ENB 12 /**< Endpoint not enabled. */
+#define XHCI_TCC_SHORT_PKT 13 /**< Short packet detected. */
+#define XHCI_TCC_RING_UNDERRUN 14 /**< Transfer ring underrun. */
+#define XHCI_TCC_RING_OVERRUN 15 /**< Transfer ring overrun. */
+#define XHCI_TCC_VF_RING_FULL 16 /**< VF event ring full. */
+#define XHCI_TCC_PARM_ERR 17 /**< Invalid context parameter. */
+#define XHCI_TCC_BWIDTH_OVER 18 /**< Isoc bandwidth overrun. */
+#define XHCI_TCC_CTX_STATE_ERR 19 /**< Transition from illegal context state. */
+#define XHCI_TCC_NO_PING 20 /**< No ping response in time. */
+#define XHCI_TCC_EVT_RING_FULL 21 /**< Event Ring full. */
+#define XHCI_TCC_DEVICE_COMPAT 22 /**< Incompatible device detected. */
+#define XHCI_TCC_MISS_SVC 23 /**< Missed isoc service. */
+#define XHCI_TCC_CMDR_STOPPED 24 /**< Command ring stopped. */
+#define XHCI_TCC_CMD_ABORTED 25 /**< Command aborted. */
+#define XHCI_TCC_STOPPED 26 /**< Endpoint stopped. */
+#define XHCI_TCC_STP_INV_LEN 27 /**< EP stopped, invalid transfer length. */
+ /* 28 Reserved. */
+#define XHCI_TCC_MAX_EXIT_LAT 29 /**< Max exit latency too large. */
+ /* 30 Reserved. */
+#define XHCI_TCC_ISOC_OVERRUN 31 /**< Isochronous buffer overrun. */
+#define XHCI_TCC_EVT_LOST 32 /**< Event lost due to overrun. */
+#define XHCI_TCC_ERR_OTHER 33 /**< Implementation specific error. */
+#define XHCI_TCC_INV_STRM_ID 34 /**< Invalid stream ID. */
+#define XHCI_TCC_SEC_BWIDTH_ERR 35 /**< Secondary bandwidth error. */
+#define XHCI_TCC_SPLIT_ERR 36 /**< Split transaction error. */
+/** @} */
+
+#if defined(IN_RING3) && defined(LOG_ENABLED)
+/** Human-readable completion code descriptions for debugging. */
+static const char * const g_apszCmplCodes[] = {
+ "CC field not updated", "Successful TRB completion", "Overrun/underrun", "Babble detected", /* 0-3 */
+ "USB transaction error", "TRB error detected", "USB Stall detected", "Inadequate xHC resources", /* 4-7 */
+ "Unable to allocate bandwidth", "MaxSlots (NDS) exceeded", "Invalid stream context type", "Slot not enabled", /* 8-11 */
+ "Endpoint not enabled", "Short packet detected", "Transfer ring underrun", "Transfer ring overrun", /* 12-15 */
+ "VF event ring full", "Invalid context param", "Isoc bandwidth overrun", "Transition from illegal ctx state", /* 16-19 */
+ "No ping response in time", "Event Ring full", "Incompatible device detected", "Missed isoc service", /* 20-23 */
+ "Command ring stopped", "Command aborted", "Endpoint stopped", "EP stopped, invalid transfer length", /* 24-27 */
+ "Reserved", "Max exit latency too large", "Reserved", "Isochronous buffer overrun", /* 28-31 */
+ "Event lost due to overrun", "Implementation specific error", "Invalid stream ID", "Secondary bandwidth error", /* 32-35 */
+ "Split transaction error" /* 36 */
+};
+#endif
+
+
+/* TRBs marked as 'TRB' are only valid in the transfer ring. TRBs marked
+ * as 'Command' are only valid in the command ring. TRBs marked as 'Event'
+ * are the only ones generated in the event ring. The Link TRB is valid
+ * in both the transfer and command rings.
+ */
+
+/** @name TRB Types
+ * @{ */
+#define XHCI_TRB_INVALID 0 /**< Reserved/unused TRB type. */
+#define XHCI_TRB_NORMAL 1 /**< Normal TRB. */
+#define XHCI_TRB_SETUP_STG 2 /**< Setup Stage TRB. */
+#define XHCI_TRB_DATA_STG 3 /**< Data Stage TRB. */
+#define XHCI_TRB_STATUS_STG 4 /**< Status Stage TRB. */
+#define XHCI_TRB_ISOCH 5 /**< Isochronous TRB. */
+#define XHCI_TRB_LINK 6 /**< Link. */
+#define XHCI_TRB_EVT_DATA 7 /**< Event Data TRB. */
+#define XHCI_TRB_NOOP_XFER 8 /**< No-op transfer TRB. */
+#define XHCI_TRB_ENB_SLOT 9 /**< Enable Slot Command. */
+#define XHCI_TRB_DIS_SLOT 10 /**< Disable Slot Command. */
+#define XHCI_TRB_ADDR_DEV 11 /**< Address Device Command. */
+#define XHCI_TRB_CFG_EP 12 /**< Configure Endpoint Command. */
+#define XHCI_TRB_EVAL_CTX 13 /**< Evaluate Context Command. */
+#define XHCI_TRB_RESET_EP 14 /**< Reset Endpoint Command. */
+#define XHCI_TRB_STOP_EP 15 /**< Stop Endpoint Command. */
+#define XHCI_TRB_SET_DEQ_PTR 16 /**< Set TR Dequeue Pointer Command. */
+#define XHCI_TRB_RESET_DEV 17 /**< Reset Device Command. */
+#define XHCI_TRB_FORCE_EVT 18 /**< Force Event Command. */
+#define XHCI_TRB_NEG_BWIDTH 19 /**< Negotiate Bandwidth Command. */
+#define XHCI_TRB_SET_LTV 20 /**< Set Latency Tolerate Value Command. */
+#define XHCI_TRB_GET_PORT_BW 21 /**< Get Port Bandwidth Command. */
+#define XHCI_TRB_FORCE_HDR 22 /**< Force Header Command. */
+#define XHCI_TRB_NOOP_CMD 23 /**< No-op Command. */
+ /* 24-31 Reserved. */
+#define XHCI_TRB_XFER 32 /**< Transfer Event. */
+#define XHCI_TRB_CMD_CMPL 33 /**< Command Completion Event. */
+#define XHCI_TRB_PORT_SC 34 /**< Port Status Change Event. */
+#define XHCI_TRB_BW_REQ 35 /**< Bandwidth Request Event. */
+#define XHCI_TRB_DBELL 36 /**< Doorbell Event. */
+#define XHCI_TRB_HC_EVT 37 /**< Host Controller Event. */
+#define XHCI_TRB_DEV_NOTIFY 38 /**< Device Notification Event. */
+#define XHCI_TRB_MFIDX_WRAP 39 /**< MFINDEX Wrap Event. */
+ /* 40-47 Reserved. */
+#define NEC_TRB_CMD_CMPL 48 /**< Command Completion Event, NEC specific. */
+#define NEC_TRB_GET_FW_VER 49 /**< Get Firmware Version Command, NEC specific. */
+#define NEC_TRB_AUTHENTICATE 50 /**< Authenticate Command, NEC specific. */
+/** @} */
+
+#if defined(IN_RING3) && defined(LOG_ENABLED)
+/** Human-readable TRB names for debugging. */
+static const char * const g_apszTrbNames[] = {
+ "Reserved/unused TRB!!", "Normal TRB", "Setup Stage TRB", "Data Stage TRB", /* 0-3 */
+ "Status Stage TRB", "Isochronous TRB", "Link", "Event Data TRB", /* 4-7 */
+ "No-op transfer TRB", "Enable Slot", "Disable Slot", "Address Device", /* 8-11 */
+ "Configure Endpoint", "Evaluate Context", "Reset Endpoint", "Stop Endpoint", /* 12-15 */
+ "Set TR Dequeue Pointer", "Reset Device", "Force Event", "Negotiate Bandwidth", /* 16-19 */
+ "Set Latency Tolerate Value", "Get Port Bandwidth", "Force Header", "No-op", /* 20-23 */
+ "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 24-31 */
+ "Transfer", "Command Completion", "Port Status Change", "BW Request", /* 32-35 */
+ "Doorbell", "Host Controller", "Device Notification", "MFINDEX Wrap", /* 36-39 */
+ "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", "UNDEF", /* 40-47 */
+ "NEC FW Version Completion", "NEC Get FW Version", "NEC Authenticate" /* 48-50 */
+};
+#endif
+
+/** Generic TRB template. */
+typedef struct sXHCI_TRB_G {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_G;
+AssertCompile(sizeof(XHCI_TRB_G) == 0x10);
+
+/** Generic transfer TRB template. */
+typedef struct sXHCI_TRB_GX {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t xfr_len : 17; /**< Transfer length. */
+ uint32_t resvd2 : 5;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t isp : 1; /**< Interrupt on Short Packet. */
+ uint32_t ns : 1; /**< No Snoop. */
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t idt : 1; /**< Immediate Data. */
+ uint32_t resvd3 : 3;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_GX;
+AssertCompile(sizeof(XHCI_TRB_GX) == 0x10);
+
+
+/* -= Transfer TRB types =- */
+
+
+/** Normal Transfer TRB. */
+typedef struct sXHCI_TRB_NORM {
+ uint64_t data_ptr; /**< Pointer or data. */
+ uint32_t xfr_len : 17; /**< Transfer length. */
+ uint32_t td_size : 5; /**< Remaining packets. */
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t isp : 1; /**< Interrupt on Short Packet. */
+ uint32_t ns : 1; /**< No Snoop. */
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t idt : 1; /**< Immediate Data. */
+ uint32_t resvd0 : 2;
+ uint32_t bei : 1; /**< Block Event Interrupt. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd1 : 16;
+} XHCI_TRB_NORM;
+AssertCompile(sizeof(XHCI_TRB_NORM) == 0x10);
+
+/** Control Transfer - Setup Stage TRB. */
+typedef struct sXHCI_TRB_CTSP {
+ uint8_t bmRequestType; /**< See the USB spec. */
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+ uint32_t xfr_len : 17; /**< Transfer length (8). */
+ uint32_t resvd0 : 5;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 4;
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t idt : 1; /**< Immediate Data. */
+ uint32_t resvd2 : 2;
+ uint32_t bei : 1; /**< Block Event Interrupt. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t trt : 2; /**< Transfer Type. */
+ uint32_t resvd3 : 14;
+} XHCI_TRB_CTSP;
+AssertCompile(sizeof(XHCI_TRB_CTSP) == 0x10);
+
+/** Control Transfer - Data Stage TRB. */
+typedef struct sXHCI_TRB_CTDT {
+ uint64_t data_ptr; /**< Pointer or data. */
+ uint32_t xfr_len : 17; /**< Transfer length. */
+ uint32_t td_size : 5; /**< Remaining packets. */
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t isp : 1; /**< Interrupt on Short Packet. */
+ uint32_t ns : 1; /**< No Snoop. */
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t idt : 1; /**< Immediate Data. */
+ uint32_t resvd0 : 3;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t dir : 1; /**< Direction (1=IN). */
+ uint32_t resvd1 : 15;
+} XHCI_TRB_CTDT;
+AssertCompile(sizeof(XHCI_TRB_CTDT) == 0x10);
+
+/** Control Transfer - Status Stage TRB. */
+typedef struct sXHCI_TRB_CTSS {
+ uint64_t resvd0;
+ uint32_t resvd1 : 22;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t resvd2 : 2;
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t resvd3 : 4;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t dir : 1; /**< Direction (1=IN). */
+ uint32_t resvd4 : 15;
+} XHCI_TRB_CTSS;
+AssertCompile(sizeof(XHCI_TRB_CTSS) == 0x10);
+
+/** Isochronous Transfer TRB. */
+typedef struct sXHCI_TRB_ISOC {
+ uint64_t data_ptr; /**< Pointer or data. */
+ uint32_t xfr_len : 17; /**< Transfer length. */
+ uint32_t td_size : 5; /**< Remaining packets. */
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t isp : 1; /**< Interrupt on Short Packet. */
+ uint32_t ns : 1; /**< No Snoop. */
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t idt : 1; /**< Immediate Data. */
+ uint32_t tbc : 2; /**< Transfer Burst Count. */
+ uint32_t bei : 1; /**< Block Event Interrupt. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t tlbpc : 4; /**< Transfer Last Burst Packet Count. */
+ uint32_t frm_id : 11; /**< Frame ID. */
+ uint32_t sia : 1; /**< Start Isoch ASAP. */
+} XHCI_TRB_ISOC;
+AssertCompile(sizeof(XHCI_TRB_ISOC) == 0x10);
+
+/* Number of bits in the frame ID. */
+#define XHCI_FRAME_ID_BITS 11
+
+/** No Op Transfer TRB. */
+typedef struct sXHCI_TRB_NOPT {
+ uint64_t resvd0;
+ uint32_t resvd1 : 22;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next TRB. */
+ uint32_t resvd2 : 2;
+ uint32_t ch : 1; /**< Chain bit. */
+ uint32_t ioc : 1; /**< Interrupt On Completion. */
+ uint32_t resvd3 : 4;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_NOPT;
+AssertCompile(sizeof(XHCI_TRB_NOPT) == 0x10);
+
+
+/* -= Event TRB types =- */
+
+
+/** Transfer Event TRB. */
+typedef struct sXHCI_TRB_TE {
+ uint64_t trb_ptr; /**< TRB pointer. */
+ uint32_t xfr_len : 24; /**< Transfer length. */
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd0 : 1;
+ uint32_t ed : 1; /**< Event Data flag. */
+ uint32_t resvd1 : 7;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t ep_id : 5; /**< Endpoint ID. */
+ uint32_t resvd2 : 3;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_TE;
+AssertCompile(sizeof(XHCI_TRB_TE) == 0x10);
+
+/** Command Completion Event TRB. */
+typedef struct sXHCI_TRB_CCE {
+ uint64_t trb_ptr; /**< Command TRB pointer. */
+ uint32_t resvd0 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t vf_id : 8; /**< Virtual Function ID. */
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_CCE;
+AssertCompile(sizeof(XHCI_TRB_CCE) == 0x10);
+
+/** Port Staus Change Event TRB. */
+typedef struct sXHCI_TRB_PSCE {
+ uint32_t resvd0 : 24;
+ uint32_t port_id : 8; /**< Port ID. */
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_PSCE;
+AssertCompile(sizeof(XHCI_TRB_PSCE) == 0x10);
+
+/** Bandwidth Request Event TRB. */
+typedef struct sXHCI_TRB_BRE {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_BRE;
+AssertCompile(sizeof(XHCI_TRB_BRE) == 0x10);
+
+/** Doorbell Event TRB. */
+typedef struct sXHCI_TRB_DBE {
+ uint32_t reason : 5; /**< DB Reason/target. */
+ uint32_t resvd0 : 27;
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t vf_id : 8; /**< Virtual Function ID. */
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_DBE;
+AssertCompile(sizeof(XHCI_TRB_DBE) == 0x10);
+
+/** Host Controller Event TRB. */
+typedef struct sXHCI_TRB_HCE {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_HCE;
+AssertCompile(sizeof(XHCI_TRB_HCE) == 0x10);
+
+/** Device Notification Event TRB. */
+typedef struct sXHCI_TRB_DNE {
+ uint32_t resvd0 : 4;
+ uint32_t dn_type : 4; /**< Device Notification Type. */
+ uint32_t dnd_lo : 5; /**< Device Notification Data Lo. */
+ uint32_t dnd_hi; /**< Device Notification Data Hi. */
+ uint32_t resvd1 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd2 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd3 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_DNE;
+AssertCompile(sizeof(XHCI_TRB_DNE) == 0x10);
+
+/** MFINDEX Wrap Event TRB. */
+typedef struct sXHCI_TRB_MWE {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2 : 24;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_MWE;
+AssertCompile(sizeof(XHCI_TRB_MWE) == 0x10);
+
+/** NEC Specific Command Completion Event TRB. */
+typedef struct sXHCI_TRB_NCE {
+ uint64_t trb_ptr; /**< Command TRB pointer. */
+ uint32_t word1 : 16; /**< First result word. */
+ uint32_t resvd0 : 8;
+ uint32_t cc : 8; /**< Completion Code. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t word2 : 16; /**< Second result word. */
+} XHCI_TRB_NCE;
+AssertCompile(sizeof(XHCI_TRB_NCE) == 0x10);
+
+
+
+/* -= Command TRB types =- */
+
+
+/** No Op Command TRB. */
+typedef struct sXHCI_TRB_NOPC {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_NOPC;
+AssertCompile(sizeof(XHCI_TRB_NOPC) == 0x10);
+
+/** Enable Slot Command TRB. */
+typedef struct sXHCI_TRB_ESL {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 16;
+} XHCI_TRB_ESL;
+AssertCompile(sizeof(XHCI_TRB_ESL) == 0x10);
+
+/** Disable Slot Command TRB. */
+typedef struct sXHCI_TRB_DSL {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_DSL;
+AssertCompile(sizeof(XHCI_TRB_DSL) == 0x10);
+
+/** Address Device Command TRB. */
+typedef struct sXHCI_TRB_ADR {
+ uint64_t ctx_ptr; /**< Input Context pointer. */
+ uint32_t resvd0;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 8;
+ uint32_t bsr : 1; /**< Block Set Address Request. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd2 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_ADR;
+AssertCompile(sizeof(XHCI_TRB_ADR) == 0x10);
+
+/** Configure Endpoint Command TRB. */
+typedef struct sXHCI_TRB_CFG {
+ uint64_t ctx_ptr; /**< Input Context pointer. */
+ uint32_t resvd0;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 8;
+ uint32_t dc : 1; /**< Deconfigure. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd2 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_CFG;
+AssertCompile(sizeof(XHCI_TRB_CFG) == 0x10);
+
+/** Evaluate Context Command TRB. */
+typedef struct sXHCI_TRB_EVC {
+ uint64_t ctx_ptr; /**< Input Context pointer. */
+ uint32_t resvd0;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd2 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_EVC;
+AssertCompile(sizeof(XHCI_TRB_EVC) == 0x10);
+
+/** Reset Endpoint Command TRB. */
+typedef struct sXHCI_TRB_RSE {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 8;
+ uint32_t tsp : 1; /**< Transfer State Preserve. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t ep_id : 5; /**< Endpoint ID. */
+ uint32_t resvd4 : 3;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_RSE;
+AssertCompile(sizeof(XHCI_TRB_RSE) == 0x10);
+
+/** Stop Endpoint Command TRB. */
+typedef struct sXHCI_TRB_STP {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t ep_id : 5; /**< Endpoint ID. */
+ uint32_t resvd4 : 2;
+ uint32_t sp : 1; /**< Suspend. */
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_STP;
+AssertCompile(sizeof(XHCI_TRB_STP) == 0x10);
+
+/** Set TR Dequeue Pointer Command TRB. */
+typedef struct sXHCI_TRB_STDP {
+#if 0
+ uint64_t dcs : 1; /**< Dequeue Cycle State. */
+ uint64_t sct : 3; /**< Stream Context Type. */
+ uint64_t tr_dqp : 60; /**< New TR Dequeue Pointer (63:4). */
+#else
+ uint64_t tr_dqp;
+#endif
+ uint16_t resvd0;
+ uint16_t strm_id; /**< Stream ID. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t ep_id : 5; /**< Endpoint ID. */
+ uint32_t resvd2 : 3;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_STDP;
+AssertCompile(sizeof(XHCI_TRB_STDP) == 0x10);
+
+/** Reset Device Command TRB. */
+typedef struct sXHCI_TRB_RSD {
+ uint32_t resvd0;
+ uint32_t resvd1;
+ uint32_t resvd2;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd3 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd4 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_RSD;
+AssertCompile(sizeof(XHCI_TRB_RSD) == 0x10);
+
+/** Get Port Bandwidth Command TRB. */
+typedef struct sXHCI_TRB_GPBW {
+ uint64_t pbctx_ptr; /**< Port Bandwidth Context pointer. */
+ uint32_t resvd0;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t spd : 4; /**< Dev Speed. */
+ uint32_t resvd2 : 4;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_GPBW;
+AssertCompile(sizeof(XHCI_TRB_GPBW) == 0x10);
+
+/** Force Header Command TRB. */
+typedef struct sXHCI_TRB_FHD {
+ uint32_t pkt_typ : 5; /**< Packet Type. */
+ uint32_t hdr_lo : 27; /**< Header Info Lo. */
+ uint32_t hdr_mid; /**< Header Info Mid. */
+ uint32_t hdr_hi; /**< Header Info Hi. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd0 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd1 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_FHD;
+AssertCompile(sizeof(XHCI_TRB_FHD) == 0x10);
+
+/** NEC Specific Authenticate Command TRB. */
+typedef struct sXHCI_TRB_NAC {
+ uint64_t cookie; /**< Cookie to munge. */
+ uint32_t resvd0;
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t resvd1 : 9;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd2 : 8;
+ uint32_t slot_id : 8; /**< Slot ID. */
+} XHCI_TRB_NAC;
+AssertCompile(sizeof(XHCI_TRB_NAC) == 0x10);
+
+
+/* -= Other TRB types =- */
+
+
+/** Link TRB. */
+typedef struct sXHCI_TRB_LNK {
+ uint64_t rseg_ptr; /**< Ring Segment Pointer. */
+ uint32_t resvd0 : 22;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t toggle : 1; /**< Toggle Cycle flag. */
+ uint32_t resvd1 : 2;
+ uint32_t chain : 1; /**< Chain flag. */
+ uint32_t ioc : 1; /**< Interrupt On Completion flag. */
+ uint32_t resvd2 : 4;
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd3 : 16;
+} XHCI_TRB_LNK;
+AssertCompile(sizeof(XHCI_TRB_LNK) == 0x10);
+
+/** Event Data TRB. */
+typedef struct sXHCI_TRB_EVTD {
+ uint64_t evt_data; /**< Event Data. */
+ uint32_t resvd0 : 22;
+ uint32_t int_tgt : 10; /**< Interrupter target. */
+ uint32_t cycle : 1; /**< Cycle bit. */
+ uint32_t ent : 1; /**< Evaluate Next Target flag. */
+ uint32_t resvd1 : 2;
+ uint32_t chain : 1; /**< Chain flag. */
+ uint32_t ioc : 1; /**< Interrupt On Completion flag. */
+ uint32_t resvd2 : 3;
+ uint32_t bei : 1; /**< Block Event Interrupt flag. */
+ uint32_t type : 6; /**< TRB Type. */
+ uint32_t resvd3 : 16;
+} XHCI_TRB_EVTD;
+AssertCompile(sizeof(XHCI_TRB_EVTD) == 0x10);
+
+
+/* -= Union TRB types for the three rings =- */
+
+
+typedef union sXHCI_XFER_TRB {
+ XHCI_TRB_NORM norm;
+ XHCI_TRB_CTSP setup;
+ XHCI_TRB_CTDT data;
+ XHCI_TRB_CTSS status;
+ XHCI_TRB_ISOC isoc;
+ XHCI_TRB_EVTD evtd;
+ XHCI_TRB_NOPT nop;
+ XHCI_TRB_LNK link;
+ XHCI_TRB_GX gen;
+} XHCI_XFER_TRB;
+AssertCompile(sizeof(XHCI_XFER_TRB) == 0x10);
+
+typedef union sXHCI_COMMAND_TRB {
+ XHCI_TRB_ESL esl;
+ XHCI_TRB_DSL dsl;
+ XHCI_TRB_ADR adr;
+ XHCI_TRB_CFG cfg;
+ XHCI_TRB_EVC evc;
+ XHCI_TRB_RSE rse;
+ XHCI_TRB_STP stp;
+ XHCI_TRB_STDP stdp;
+ XHCI_TRB_RSD rsd;
+ XHCI_TRB_GPBW gpbw;
+ XHCI_TRB_FHD fhd;
+ XHCI_TRB_NAC nac;
+ XHCI_TRB_NOPC nopc;
+ XHCI_TRB_LNK link;
+ XHCI_TRB_G gen;
+} XHCI_COMMAND_TRB;
+AssertCompile(sizeof(XHCI_COMMAND_TRB) == 0x10);
+
+typedef union sXHCI_EVENT_TRB {
+ XHCI_TRB_TE te;
+ XHCI_TRB_CCE cce;
+ XHCI_TRB_PSCE psce;
+ XHCI_TRB_BRE bre;
+ XHCI_TRB_DBE dbe;
+ XHCI_TRB_HCE hce;
+ XHCI_TRB_DNE dne;
+ XHCI_TRB_MWE mwe;
+ XHCI_TRB_NCE nce;
+ XHCI_TRB_G gen;
+} XHCI_EVENT_TRB;
+AssertCompile(sizeof(XHCI_EVENT_TRB) == 0x10);
+
+
+
+/* -=-=-= Contexts =-=-=- */
+
+/** Slot Context. */
+typedef struct sXHCI_SLOT_CTX {
+ uint32_t route_str : 20; /**< Route String. */
+ uint32_t speed : 4; /**< Device speed. */
+ uint32_t resvd0 : 1;
+ uint32_t mtt : 1; /**< Multi-TT flag. */
+ uint32_t hub : 1; /**< Hub flag. */
+ uint32_t ctx_ent : 5; /**< Context entries. */
+ uint32_t max_lat : 16; /**< Max exit latency in usec. */
+ uint32_t rh_port : 8; /**< Root hub port number (1-based). */
+ uint32_t n_ports : 8; /**< No. of ports for hubs. */
+ uint32_t tt_slot : 8; /**< TT hub slot ID. */
+ uint32_t tt_port : 8; /**< TT port number. */
+ uint32_t ttt : 2; /**< TT Think Time. */
+ uint32_t resvd1 : 4;
+ uint32_t intr_tgt : 10; /**< Interrupter Target. */
+ uint32_t dev_addr : 8; /**< Device Address. */
+ uint32_t resvd2 : 19;
+ uint32_t slot_state : 5; /**< Slot State. */
+ uint32_t opaque[4]; /**< For xHC (i.e. our own) use. */
+} XHCI_SLOT_CTX;
+AssertCompile(sizeof(XHCI_SLOT_CTX) == 0x20);
+
+/** @name Slot Context states
+ * @{ */
+#define XHCI_SLTST_ENDIS 0 /**< Enabled/Disabled. */
+#define XHCI_SLTST_DEFAULT 1 /**< Default. */
+#define XHCI_SLTST_ADDRESSED 2 /**< Addressed. */
+#define XHCI_SLTST_CONFIGURED 3 /**< Configured. */
+/** @} */
+
+#ifdef IN_RING3
+/** Human-readable slot state descriptions for debugging. */
+static const char * const g_apszSltStates[] = {
+ "Enabled/Disabled", "Default", "Addressed", "Configured" /* 0-3 */
+};
+#endif
+
+/** Endpoint Context. */
+typedef struct sXHCI_EP_CTX {
+ uint32_t ep_state : 3; /**< Endpoint state. */
+ uint32_t resvd0 : 5;
+ uint32_t mult : 2; /**< SS isoc burst count. */
+ uint32_t maxps : 5; /**< Max Primary Streams. */
+ uint32_t lsa : 1; /**< Linear Stream Array. */
+ uint32_t interval : 8; /**< USB request interval. */
+ uint32_t resvd1 : 8;
+ uint32_t resvd2 : 1;
+ uint32_t c_err : 2; /**< Error count. */
+ uint32_t ep_type : 3; /**< Endpoint type. */
+ uint32_t resvd3 : 1;
+ uint32_t hid : 1; /**< Host Initiate Disable. */
+ uint32_t max_brs_sz : 8; /**< Max Burst Size. */
+ uint32_t max_pkt_sz : 16; /**< Max Packet Size. */
+ uint64_t trdp; /**< TR Dequeue Pointer. */
+ uint32_t avg_trb_len : 16; /**< Average TRB Length. */
+ uint32_t max_esit : 16; /**< Max EP Service Interval Time Payload. */
+ /**< The rest for xHC (i.e. our own) use. */
+ uint32_t last_frm : 16; /**< Last isochronous frame used (opaque). */
+ uint32_t ifc : 8; /**< isoch in-flight TD count (opaque). */
+ uint32_t last_cc : 8; /**< Last TRB completion code (opaque). */
+ uint64_t trep; /**< TR Enqueue Pointer (opaque). */
+} XHCI_EP_CTX;
+AssertCompile(sizeof(XHCI_EP_CTX) == 0x20);
+
+/** @name Endpoint Context states
+ * @{ */
+#define XHCI_EPST_DISABLED 0 /**< Disabled. */
+#define XHCI_EPST_RUNNING 1 /**< Running. */
+#define XHCI_EPST_HALTED 2 /**< Halted. */
+#define XHCI_EPST_STOPPED 3 /**< Not running/stopped. */
+#define XHCI_EPST_ERROR 4 /**< Not running/error. */
+/** @} */
+
+/** @name Endpoint Type values
+ * @{ */
+#define XHCI_EPTYPE_INVALID 0 /**< Not valid. */
+#define XHCI_EPTYPE_ISOCH_OUT 1 /**< Isochronous Out. */
+#define XHCI_EPTYPE_BULK_OUT 2 /**< Bulk Out. */
+#define XHCI_EPTYPE_INTR_OUT 3 /**< Interrupt Out. */
+#define XHCI_EPTYPE_CONTROL 4 /**< Control Bidi. */
+#define XHCI_EPTYPE_ISOCH_IN 5 /**< Isochronous In. */
+#define XHCI_EPTYPE_BULK_IN 6 /**< Bulk In. */
+#define XHCI_EPTYPE_INTR_IN 7 /**< Interrupt In. */
+/** @} */
+
+/* Pick out transfer type from endpoint. */
+#define XHCI_EP_XTYPE(a) (a & 3)
+
+/* Endpoint transfer types. */
+#define XHCI_XFTYPE_CONTROL 0
+#define XHCI_XFTYPE_ISOCH XHCI_EPTYPE_ISOCH_OUT
+#define XHCI_XFTYPE_BULK XHCI_EPTYPE_BULK_OUT
+#define XHCI_XFTYPE_INTR XHCI_EPTYPE_INTR_OUT
+
+/* Transfer Ring Dequeue Pointer address mask. */
+#define XHCI_TRDP_ADDR_MASK UINT64_C(0xFFFFFFFFFFFFFFF0)
+#define XHCI_TRDP_DCS_MASK RT_BIT(0) /* Dequeue Cycle State bit. */
+
+
+#ifdef IN_RING3
+
+/* Human-readable endpoint state descriptions for debugging. */
+static const char * const g_apszEpStates[] = {
+ "Disabled", "Running", "Halted", "Stopped", "Error" /* 0-4 */
+};
+
+/* Human-readable endpoint type descriptions for debugging. */
+static const char * const g_apszEpTypes[] = {
+ "Not Valid", "Isoch Out", "Bulk Out", "Interrupt Out", /* 0-3 */
+ "Control", "Isoch In", "Bulk In", "Interrupt In" /* 4-7 */
+};
+
+#endif /* IN_RING3 */
+
+/* Input Control Context. */
+typedef struct sXHCI_INPC_CTX {
+ uint32_t drop_flags; /* Drop Context flags (2-31). */
+ uint32_t add_flags; /* Add Context flags (0-31). */
+ uint32_t resvd[6];
+} XHCI_INPC_CTX;
+AssertCompile(sizeof(XHCI_INPC_CTX) == 0x20);
+
+/* Make sure all contexts are the same size. */
+AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_SLOT_CTX));
+AssertCompile(sizeof(XHCI_EP_CTX) == sizeof(XHCI_INPC_CTX));
+
+/* -= Event Ring Segment Table =- */
+
+/** Event Ring Segment Table Entry. */
+typedef struct sXHCI_ERSTE {
+ uint64_t addr;
+ uint16_t size;
+ uint16_t resvd0;
+ uint32_t resvd1;
+} XHCI_ERSTE;
+AssertCompile(sizeof(XHCI_ERSTE) == 0x10);
+
+
+/* -=-= Internal data structures not defined by xHCI =-=- */
+
+
+/** Device slot entry -- either slot context or endpoint context. */
+typedef union sXHCI_DS_ENTRY {
+ XHCI_SLOT_CTX sc; /**< Slot context. */
+ XHCI_EP_CTX ep; /**< Endpoint context. */
+} XHCI_DS_ENTRY;
+
+/** Full device context (slot context + 31 endpoint contexts). */
+typedef struct sXHCI_DEV_CTX {
+ XHCI_DS_ENTRY entry[32];
+} XHCI_DEV_CTX;
+AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_EP_CTX));
+AssertCompile(sizeof(XHCI_DEV_CTX) == 32 * sizeof(XHCI_SLOT_CTX));
+
+/** Pointer to the xHCI device state. */
+typedef struct XHCI *PXHCI;
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+/**
+ * The xHCI controller data associated with each URB.
+ */
+typedef struct VUSBURBHCIINT
+{
+ /** The slot index. */
+ uint8_t uSlotID;
+ /** Number of Tds in the array. */
+ uint32_t cTRB;
+} VUSBURBHCIINT;
+#endif
+
+/**
+ * An xHCI root hub port, shared.
+ */
+typedef struct XHCIHUBPORT
+{
+ /** PORTSC: Port status/control register (R/W). */
+ uint32_t portsc;
+ /** PORTPM: Power management status/control register (R/W). */
+ uint32_t portpm;
+ /** PORTLI: USB3 port link information (R/O). */
+ uint32_t portli;
+} XHCIHUBPORT;
+/** Pointer to a shared xHCI root hub port. */
+typedef XHCIHUBPORT *PXHCIHUBPORT;
+
+/**
+ * An xHCI root hub port, ring-3.
+ */
+typedef struct XHCIHUBPORTR3
+{
+ /** Flag whether there is a device attached to the port. */
+ bool fAttached;
+} XHCIHUBPORTR3;
+/** Pointer to a ring-3 xHCI root hub port. */
+typedef XHCIHUBPORTR3 *PXHCIHUBPORTR3;
+
+/**
+ * The xHCI root hub, ring-3 only.
+ *
+ * @implements PDMIBASE
+ * @implements VUSBIROOTHUBPORT
+ */
+typedef struct XHCIROOTHUBR3
+{
+ /** Pointer to the parent xHC. */
+ R3PTRTYPE(struct XHCIR3 *) pXhciR3;
+ /** Pointer to the base interface of the VUSB RootHub. */
+ R3PTRTYPE(PPDMIBASE) pIBase;
+ /** Pointer to the connector interface of the VUSB RootHub. */
+ R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn;
+ /** The base interface exposed to the roothub driver. */
+ PDMIBASE IBase;
+ /** The roothub port interface exposed to the roothub driver. */
+ VUSBIROOTHUBPORT IRhPort;
+
+ /** The LED for this hub. */
+ PDMLED Led;
+
+ /** Number of actually implemented ports. */
+ uint8_t cPortsImpl;
+ /** Index of first port for this hub. */
+ uint8_t uPortBase;
+
+ uint16_t Alignment0; /**< Force alignment. */
+#if HC_ARCH_BITS == 64
+ uint32_t Alignment1;
+#endif
+} XHCIROOTHUBR3;
+/** Pointer to a xHCI root hub (ring-3 only). */
+typedef XHCIROOTHUBR3 *PXHCIROOTHUBR3;
+
+/**
+ * An xHCI interrupter.
+ */
+typedef struct sXHCIINTRPTR
+{
+ /* Registers defined by xHCI. */
+ /** IMAN: Interrupt Management Register (R/W). */
+ uint32_t iman;
+ /** IMOD: Interrupt Moderation Register (R/W). */
+ uint32_t imod;
+ /** ERSTSZ: Event Ring Segment Table Size (R/W). */
+ uint32_t erstsz;
+ /* Reserved/padding. */
+ uint32_t reserved;
+ /** ERSTBA: Event Ring Segment Table Base Address (R/W). */
+ uint64_t erstba;
+ /** ERDP: Event Ring Dequeue Pointer (R/W). */
+ uint64_t erdp;
+ /* Interrupter lock. */
+ PDMCRITSECT lock;
+ /* Internal xHCI non-register state. */
+ /** Internal Event Ring enqueue pointer. */
+ uint64_t erep;
+ /** Internal ERDP re-write counter. */
+ uint32_t erdp_rewrites;
+ /** This interrupter's index (for logging). */
+ uint32_t index;
+ /** Internal index into Event Ring Segment Table. */
+ uint16_t erst_idx;
+ /** Internal index into Event Ring Segment. */
+ uint16_t trb_count;
+ /** Internal Event Ring Producer Cycle State. */
+ bool evtr_pcs;
+ /** Internal Interrupt Pending Enable flag. */
+ bool ipe;
+} XHCIINTRPTR, *PXHCIINTRPTR;
+
+/**
+ * xHCI device state.
+ * @implements PDMILEDPORTS
+ */
+typedef struct XHCI
+{
+ /** MFINDEX wraparound timer. */
+ TMTIMERHANDLE hWrapTimer;
+
+#ifdef XHCI_ERROR_INJECTION
+ bool fDropIntrHw;
+ bool fDropIntrIpe;
+ bool fDropUrb;
+ uint8_t Alignment00[1];
+#else
+ uint32_t Alignment00; /**< Force alignment. */
+#endif
+
+ /** Flag indicating a sleeping worker thread. */
+ volatile bool fWrkThreadSleeping;
+ volatile bool afPadding[3];
+
+ /** The event semaphore the worker thread waits on. */
+ SUPSEMEVENT hEvtProcess;
+
+ /** Bitmap for finished tasks (R3 -> Guest). */
+ volatile uint32_t u32TasksFinished;
+ /** Bitmap for finished queued tasks (R3 -> Guest). */
+ volatile uint32_t u32QueuedTasksFinished;
+ /** Bitmap for new queued tasks (Guest -> R3). */
+ volatile uint32_t u32TasksNew;
+
+ /** Copy of XHCIR3::RootHub2::cPortsImpl. */
+ uint8_t cUsb2Ports;
+ /** Copy of XHCIR3::RootHub3::cPortsImpl. */
+ uint8_t cUsb3Ports;
+ /** Sum of cUsb2Ports and cUsb3Ports. */
+ uint8_t cTotalPorts;
+ /** Explicit padding. */
+ uint8_t bPadding;
+
+ /** Start of current frame. */
+ uint64_t SofTime;
+ /** State of the individual ports. */
+ XHCIHUBPORT aPorts[XHCI_NDP_MAX];
+ /** Interrupters array. */
+ XHCIINTRPTR aInterrupters[XHCI_NINTR];
+
+ /** @name Host Controller Capability Registers
+ * @{ */
+ /** CAPLENGTH: base + CAPLENGTH = operational register start (R/O). */
+ uint32_t cap_length;
+ /** HCIVERSION: host controller interface version (R/O). */
+ uint32_t hci_version;
+ /** HCSPARAMS: Structural parameters 1 (R/O). */
+ uint32_t hcs_params1;
+ /** HCSPARAMS: Structural parameters 2 (R/O). */
+ uint32_t hcs_params2;
+ /** HCSPARAMS: Structural parameters 3 (R/O). */
+ uint32_t hcs_params3;
+ /** HCCPARAMS: Capability parameters (R/O). */
+ uint32_t hcc_params;
+ /** DBOFF: Doorbell offset (R/O). */
+ uint32_t dbell_off;
+ /** RTSOFF: Run-time register space offset (R/O). */
+ uint32_t rts_off;
+ /** @} */
+
+ /** @name Host Controller Operational Registers
+ * @{ */
+ /** USB command register - USBCMD (R/W). */
+ uint32_t cmd;
+ /** USB status register - USBSTS (R/W).*/
+ uint32_t status;
+ /** Device Control Notification register - DNCTRL (R/W). */
+ uint32_t dnctrl;
+ /** Configure Register (R/W). */
+ uint32_t config;
+ /** Command Ring Control Register - CRCR (R/W). */
+ uint64_t crcr;
+ /** Device Context Base Address Array Pointer (R/W). */
+ uint64_t dcbaap;
+ /** @} */
+
+ /** Extended Capabilities storage. */
+ uint8_t abExtCap[XHCI_EXT_CAP_SIZE];
+ /** Size of valid extended capabilities. */
+ uint32_t cbExtCap;
+
+ uint32_t Alignment1; /**< Align cmdr_dqp. */
+
+ /** @name Internal xHCI non-register state
+ * @{ */
+ /** Internal Command Ring dequeue pointer. */
+ uint64_t cmdr_dqp;
+ /** Internal Command Ring Consumer Cycle State. */
+ bool cmdr_ccs;
+ uint8_t aAlignment2[7]; /**< Force alignment. */
+ /** Internal Device Slot states. */
+ uint8_t aSlotState[XHCI_NDS];
+ /** Internal doorbell states. Each bit corresponds to an endpoint. */
+ uint32_t aBellsRung[XHCI_NDS];
+ /** @} */
+
+ /** @name Model specific configuration
+ * @{ */
+ /** ERST address mask. */
+ uint64_t erst_addr_mask;
+ /** @} */
+
+ /** The MMIO region. */
+ IOMMMIOHANDLE hMmio;
+
+ /** Detected isochronous URBs completed with error. */
+ STAMCOUNTER StatErrorIsocUrbs;
+ /** Detected isochronous packets (not URBs!) with error. */
+ STAMCOUNTER StatErrorIsocPkts;
+
+ /** Event TRBs written to event ring(s). */
+ STAMCOUNTER StatEventsWritten;
+ /** Event TRBs not written to event ring(s) due to HC being stopped. */
+ STAMCOUNTER StatEventsDropped;
+ /** Requests to set the IP bit. */
+ STAMCOUNTER StatIntrsPending;
+ /** Actual interrupt deliveries. */
+ STAMCOUNTER StatIntrsSet;
+ /** Interrupts not raised because they were disabled. */
+ STAMCOUNTER StatIntrsNotSet;
+ /** A pending interrupt was cleared. */
+ STAMCOUNTER StatIntrsCleared;
+ /** Number of TRBs that formed a single control URB. */
+ STAMCOUNTER StatTRBsPerCtlUrb;
+ /** Number of TRBs that formed a single data (bulk/interrupt) URB. */
+ STAMCOUNTER StatTRBsPerDtaUrb;
+ /** Number of TRBs that formed a single isochronous URB. */
+ STAMCOUNTER StatTRBsPerIsoUrb;
+ /** Size of a control URB in bytes. */
+ STAMCOUNTER StatUrbSizeCtrl;
+ /** Size of a data URB in bytes. */
+ STAMCOUNTER StatUrbSizeData;
+ /** Size of an isochronous URB in bytes. */
+ STAMCOUNTER StatUrbSizeIsoc;
+
+#ifdef VBOX_WITH_STATISTICS
+ /** @name Register access counters.
+ * @{ */
+ STAMCOUNTER StatRdCaps;
+ STAMCOUNTER StatRdCmdRingCtlHi;
+ STAMCOUNTER StatRdCmdRingCtlLo;
+ STAMCOUNTER StatRdConfig;
+ STAMCOUNTER StatRdDevCtxBaapHi;
+ STAMCOUNTER StatRdDevCtxBaapLo;
+ STAMCOUNTER StatRdDevNotifyCtrl;
+ STAMCOUNTER StatRdDoorBell;
+ STAMCOUNTER StatRdEvtRingDeqPtrHi;
+ STAMCOUNTER StatRdEvtRingDeqPtrLo;
+ STAMCOUNTER StatRdEvtRsTblBaseHi;
+ STAMCOUNTER StatRdEvtRsTblBaseLo;
+ STAMCOUNTER StatRdEvtRstblSize;
+ STAMCOUNTER StatRdEvtRsvd;
+ STAMCOUNTER StatRdIntrMgmt;
+ STAMCOUNTER StatRdIntrMod;
+ STAMCOUNTER StatRdMfIndex;
+ STAMCOUNTER StatRdPageSize;
+ STAMCOUNTER StatRdPortLinkInfo;
+ STAMCOUNTER StatRdPortPowerMgmt;
+ STAMCOUNTER StatRdPortRsvd;
+ STAMCOUNTER StatRdPortStatusCtrl;
+ STAMCOUNTER StatRdUsbCmd;
+ STAMCOUNTER StatRdUsbSts;
+ STAMCOUNTER StatRdUnknown;
+
+ STAMCOUNTER StatWrCmdRingCtlHi;
+ STAMCOUNTER StatWrCmdRingCtlLo;
+ STAMCOUNTER StatWrConfig;
+ STAMCOUNTER StatWrDevCtxBaapHi;
+ STAMCOUNTER StatWrDevCtxBaapLo;
+ STAMCOUNTER StatWrDevNotifyCtrl;
+ STAMCOUNTER StatWrDoorBell0;
+ STAMCOUNTER StatWrDoorBellN;
+ STAMCOUNTER StatWrEvtRingDeqPtrHi;
+ STAMCOUNTER StatWrEvtRingDeqPtrLo;
+ STAMCOUNTER StatWrEvtRsTblBaseHi;
+ STAMCOUNTER StatWrEvtRsTblBaseLo;
+ STAMCOUNTER StatWrEvtRstblSize;
+ STAMCOUNTER StatWrIntrMgmt;
+ STAMCOUNTER StatWrIntrMod;
+ STAMCOUNTER StatWrPortPowerMgmt;
+ STAMCOUNTER StatWrPortStatusCtrl;
+ STAMCOUNTER StatWrUsbCmd;
+ STAMCOUNTER StatWrUsbSts;
+ STAMCOUNTER StatWrUnknown;
+ /** @} */
+#endif
+} XHCI;
+
+/**
+ * xHCI device state, ring-3 edition.
+ * @implements PDMILEDPORTS
+ */
+typedef struct XHCIR3
+{
+ /** The async worker thread. */
+ R3PTRTYPE(PPDMTHREAD) pWorkerThread;
+ /** The device instance.
+ * @note This is only so interface functions can get their bearings. */
+ PPDMDEVINSR3 pDevIns;
+
+ /** Status LUN: The base interface. */
+ PDMIBASE IBase;
+ /** Status LUN: Leds interface. */
+ PDMILEDPORTS ILeds;
+ /** Status LUN: Partner of ILeds. */
+ R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector;
+
+ /** USB 2.0 Root hub device. */
+ XHCIROOTHUBR3 RootHub2;
+ /** USB 3.0 Root hub device. */
+ XHCIROOTHUBR3 RootHub3;
+
+ /** State of the individual ports. */
+ XHCIHUBPORTR3 aPorts[XHCI_NDP_MAX];
+
+ /** Critsect to synchronize worker and I/O completion threads. */
+ RTCRITSECT CritSectThrd;
+} XHCIR3;
+/** Pointer to ring-3 xHCI device state. */
+typedef XHCIR3 *PXHCIR3;
+
+/**
+ * xHCI device data, ring-0 edition.
+ */
+typedef struct XHCIR0
+{
+ uint32_t uUnused;
+} XHCIR0;
+/** Pointer to ring-0 xHCI device data. */
+typedef struct XHCIR0 *PXHCIR0;
+
+
+/**
+ * xHCI device data, raw-mode edition.
+ */
+typedef struct XHCIRC
+{
+ uint32_t uUnused;
+} XHCIRC;
+/** Pointer to raw-mode xHCI device data. */
+typedef struct XHCIRC *PXHCIRC;
+
+
+/** @typedef XHCICC
+ * The xHCI device data for the current context. */
+typedef CTX_SUFF(XHCI) XHCICC;
+/** @typedef PXHCICC
+ * Pointer to the xHCI device for the current context. */
+typedef CTX_SUFF(PXHCI) PXHCICC;
+
+
+/* -=-= Local implementation details =-=- */
+
+typedef enum sXHCI_JOB {
+ XHCI_JOB_PROCESS_CMDRING, /**< Process the command ring. */
+ XHCI_JOB_DOORBELL, /**< A doorbell (other than DB0) was rung. */
+ XHCI_JOB_XFER_DONE, /**< Transfer completed, look for more work. */
+ XHCI_JOB_MAX
+} XHCI_JOB;
+
+/* -=-=- Local xHCI definitions -=-=- */
+
+/** @name USB states.
+ * @{ */
+#define XHCI_USB_RESET 0x00
+#define XHCI_USB_RESUME 0x40
+#define XHCI_USB_OPERATIONAL 0x80
+#define XHCI_USB_SUSPEND 0xc0
+/** @} */
+
+/* Primary interrupter (for readability). */
+#define XHCI_PRIMARY_INTERRUPTER 0
+
+/** @name Device Slot states.
+ * @{ */
+#define XHCI_DEVSLOT_EMPTY 0
+#define XHCI_DEVSLOT_ENABLED 1
+#define XHCI_DEVSLOT_DEFAULT 2
+#define XHCI_DEVSLOT_ADDRESSED 3
+#define XHCI_DEVSLOT_CONFIGURED 4
+/** @} */
+
+/** Get the pointer to a root hub corresponding to given port index. */
+#define GET_PORT_PRH(a_pThisCC, a_uPort) \
+ ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl ? &(a_pThisCC)->RootHub3 : &(a_pThisCC)->RootHub2)
+#define GET_VUSB_PORT_FROM_XHCI_PORT(a_pRh, a_iPort) \
+ (((a_iPort) - (a_pRh)->uPortBase) + 1)
+#define GET_XHCI_PORT_FROM_VUSB_PORT(a_pRh, a_uPort) \
+ ((a_pRh)->uPortBase + (a_uPort) - 1)
+
+/** Check if port corresponding to index is USB3, using shared data. */
+#define IS_USB3_PORT_IDX_SHR(a_pThis, a_uPort) ((a_uPort) >= (a_pThis)->cUsb2Ports)
+
+/** Check if port corresponding to index is USB3, using ring-3 data. */
+#define IS_USB3_PORT_IDX_R3(a_pThisCC, a_uPort) ((a_uPort) >= (a_pThisCC)->RootHub2.cPortsImpl)
+
+/** Query the number of configured USB2 ports. */
+#define XHCI_NDP_USB2(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub2.cPortsImpl)
+
+/** Query the number of configured USB3 ports. */
+#define XHCI_NDP_USB3(a_pThisCC) ((unsigned)(a_pThisCC)->RootHub3.cPortsImpl)
+
+/** Query the total number of configured ports. */
+#define XHCI_NDP_CFG(a_pThis) ((unsigned)RT_MIN((a_pThis)->cTotalPorts, XHCI_NDP_MAX))
+
+
+#ifndef VBOX_DEVICE_STRUCT_TESTCASE
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+#ifdef IN_RING3
+
+/** Build a Protocol extended capability. */
+static uint32_t xhciR3BuildProtocolCaps(uint8_t *pbCap, uint32_t cbMax, int cPorts, int nPortOfs, int ver)
+{
+ uint32_t *pu32Cap = (uint32_t *)pbCap;
+ unsigned cPsi;
+
+ Assert(nPortOfs + cPorts < 255);
+ Assert(ver == 2 || ver == 3);
+
+ cPsi = 0; /* Currently only implied port speed IDs. */
+
+ /* Make sure there's enough room. */
+ if (cPsi * 4 + 16 > cbMax)
+ return 0;
+
+ /* Header - includes (USB) specification version. */
+ *pu32Cap++ = (ver << 24) | (0 << 16) | XHCI_XCP_PROTOCOL;
+ /* Specification - 'USB ' */
+ *pu32Cap++ = 0x20425355;
+ /* Port offsets and counts. 1-based! */
+ *pu32Cap++ = (cPsi << 28) | (cPorts << 8) | (nPortOfs + 1);
+ /* Reserved dword. */
+ *pu32Cap++ = 0;
+
+ return (uint8_t *)pu32Cap - pbCap;
+}
+
+
+/** Add an extended capability and link it into the chain. */
+static int xhciR3AddExtCap(PXHCI pThis, const uint8_t *pCap, uint32_t cbCap, uint32_t *puPrevOfs)
+{
+ Assert(*puPrevOfs <= pThis->cbExtCap);
+ Assert(!(cbCap & 3));
+
+ /* Check that the extended capability is sane. */
+ if (cbCap == 0)
+ return VERR_BUFFER_UNDERFLOW;
+ if (pThis->cbExtCap + cbCap > XHCI_EXT_CAP_SIZE)
+ return VERR_BUFFER_OVERFLOW;
+ if (cbCap > 255 * 4) /* Size must fit into 8-bit dword count. */
+ return VERR_BUFFER_OVERFLOW;
+
+ /* Copy over the capability data and update offsets. */
+ memcpy(pThis->abExtCap + pThis->cbExtCap, pCap, cbCap);
+ pThis->abExtCap[*puPrevOfs + 1] = cbCap >> 2;
+ pThis->abExtCap[pThis->cbExtCap + 1] = 0;
+ *puPrevOfs = pThis->cbExtCap;
+ pThis->cbExtCap += cbCap;
+ return VINF_SUCCESS;
+}
+
+/** Build the xHCI Extended Capabilities region. */
+static int xhciR3BuildExtCaps(PXHCI pThis, PXHCICC pThisCC)
+{
+ int rc;
+ uint8_t abXcp[MAX_XCAP_SIZE];
+ uint32_t cbXcp;
+ uint32_t uPrevOfs = 0;
+
+ Assert(XHCI_NDP_USB2(pThisCC));
+ Assert(XHCI_NDP_USB3(pThisCC));
+
+ /* Most of the extended capabilities are optional or not relevant for PCI
+ * implementations. However, the Supported Protocol caps are required.
+ */
+ cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB2(pThisCC), 0, 2);
+ rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs);
+ AssertReturn(RT_SUCCESS(rc), rc);
+
+ cbXcp = xhciR3BuildProtocolCaps(abXcp, sizeof(abXcp), XHCI_NDP_USB3(pThisCC), XHCI_NDP_USB2(pThisCC), 3);
+ rc = xhciR3AddExtCap(pThis, abXcp, cbXcp, &uPrevOfs);
+ AssertReturn(RT_SUCCESS(rc), rc);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Select an unused device address. Note that this may fail in the unlikely
+ * case where all possible addresses are exhausted.
+ */
+static uint8_t xhciR3SelectNewAddress(PXHCI pThis, uint8_t uSlotID)
+{
+ RT_NOREF(pThis, uSlotID);
+
+ /*
+ * Since there is a 1:1 mapping between USB devices and device slots, we
+ * should be able to assign a USB address which equals slot ID to any USB
+ * device. However, the address selection algorithm could be completely
+ * different (it is not defined by the xHCI spec).
+ */
+ return uSlotID;
+}
+
+
+/**
+ * Read the address of a device context for a slot from the DCBAA.
+ *
+ * @returns Given slot's device context base address.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uSlotID Slot ID to get the context address of.
+ */
+static uint64_t xhciR3FetchDevCtxAddr(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID)
+{
+ uint64_t uCtxAddr;
+ RTGCPHYS GCPhysDCBAAE;
+
+ Assert(uSlotID > 0);
+ Assert(uSlotID < XHCI_NDS);
+
+ /* Fetch the address of the output slot context from the DCBAA. */
+ GCPhysDCBAAE = pThis->dcbaap + uSlotID * sizeof(uint64_t);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysDCBAAE, &uCtxAddr, sizeof(uCtxAddr));
+ LogFlowFunc(("Slot ID %u, device context @ %RGp\n", uSlotID, uCtxAddr));
+ Assert(uCtxAddr);
+
+ return uCtxAddr & XHCI_CTX_ADDR_MASK;
+}
+
+
+/**
+ * Fetch a device's slot or endpoint context from memory.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param uSlotID Slot ID to access.
+ * @param uDCI Device Context Index.
+ * @param pCtx Pointer to storage for the context.
+ */
+static int xhciR3FetchDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, void *pCtx)
+{
+ RTGCPHYS GCPhysCtx;
+
+ GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ LogFlowFunc(("Reading device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI));
+ GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCtx, pCtx, sizeof(XHCI_SLOT_CTX));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Fetch a device's slot and endpoint contexts from guest memory.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param uSlotID Slot ID to access.
+ * @param uDCI Endpoint Device Context Index.
+ * @param pSlot Pointer to storage for the slot context.
+ * @param pEp Pointer to storage for the endpoint context.
+ */
+static int xhciR3FetchCtxAndEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_SLOT_CTX *pSlot, XHCI_EP_CTX *pEp)
+{
+ AssertPtr(pSlot);
+ AssertPtr(pEp);
+ Assert(uDCI); /* Can't be 0 -- that's the device context. */
+
+ /* Load the slot context. */
+ xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, pSlot);
+ /// @todo sanity check the slot context here?
+ Assert(pSlot->ctx_ent >= uDCI);
+
+ /* Load the endpoint context. */
+ xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, uDCI, pEp);
+ /// @todo sanity check the endpoint context here?
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Update an endpoint context in guest memory.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param uSlotID Slot ID to access.
+ * @param uDCI Endpoint Device Context Index.
+ * @param pEp Pointer to storage of the endpoint context.
+ */
+static int xhciR3WriteBackEp(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, XHCI_EP_CTX *pEp)
+{
+ RTGCPHYS GCPhysCtx;
+
+ AssertPtr(pEp);
+ Assert(uDCI); /* Can't be 0 -- that's the device context. */
+
+ /// @todo sanity check the endpoint context here?
+ /* Find the physical address. */
+ GCPhysCtx = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ LogFlowFunc(("Writing device context @ %RGp, DCI %u\n", GCPhysCtx, uDCI));
+ GCPhysCtx += uDCI * sizeof(XHCI_SLOT_CTX);
+ /* Write the updated context. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysCtx, pEp, sizeof(XHCI_EP_CTX));
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Modify an endpoint context such that it enters the running state.
+ *
+ * @param pEpCtx Pointer to the endpoint context.
+ */
+static void xhciR3EnableEP(XHCI_EP_CTX *pEpCtx)
+{
+ LogFlow(("Enabling EP, TRDP @ %RGp, DCS=%u\n", pEpCtx->trdp & XHCI_TRDP_ADDR_MASK, pEpCtx->trdp & XHCI_TRDP_DCS_MASK));
+ pEpCtx->ep_state = XHCI_EPST_RUNNING;
+ pEpCtx->trep = pEpCtx->trdp;
+}
+
+#endif /* IN_RING3 */
+
+#define MFIND_PERIOD_NS (UINT64_C(2048) * 1000000)
+
+/**
+ * Set up the MFINDEX wrap timer.
+ */
+static void xhciSetWrapTimer(PPDMDEVINS pDevIns, PXHCI pThis)
+{
+ uint64_t u64Now;
+ uint64_t u64LastWrap;
+ uint64_t u64Expire;
+ int rc;
+
+ /* Try to avoid drift. */
+ u64Now = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer);
+// u64LastWrap = u64Now - (u64Now % (0x3FFF * 125000));
+ u64LastWrap = u64Now / MFIND_PERIOD_NS * MFIND_PERIOD_NS;
+ /* The MFINDEX counter wraps around every 2048 milliseconds. */
+ u64Expire = u64LastWrap + (uint64_t)2048 * 1000000;
+ rc = PDMDevHlpTimerSet(pDevIns, pThis->hWrapTimer, u64Expire);
+ AssertRC(rc);
+}
+
+/**
+ * Determine whether MSI/MSI-X is enabled for this PCI device.
+ *
+ * This influences interrupt handling in xHCI. NB: There should be a PCIDevXxx
+ * function for this.
+ */
+static bool xhciIsMSIEnabled(PPDMPCIDEV pDevIns)
+{
+ uint16_t uMsgCtl;
+
+ uMsgCtl = PDMPciDevGetWord(pDevIns, XHCI_PCI_MSI_CAP_OFS + VBOX_MSI_CAP_MESSAGE_CONTROL);
+ return !!(uMsgCtl & VBOX_PCI_MSI_FLAGS_ENABLE);
+}
+
+/**
+ * Get the worker thread going -- there's something to do.
+ */
+static void xhciKickWorker(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_JOB enmJob, uint32_t uWorkDesc)
+{
+ RT_NOREF(enmJob, uWorkDesc);
+
+ /* Tell the worker thread there's something to do. */
+ if (ASMAtomicReadBool(&pThis->fWrkThreadSleeping))
+ {
+ LogFlowFunc(("Signal event semaphore\n"));
+ int rc = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+ AssertRC(rc);
+ }
+}
+
+/**
+ * Fetch the current ERST entry from guest memory.
+ */
+static void xhciFetchErstEntry(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip)
+{
+ RTGCPHYS GCPhysErste;
+ XHCI_ERSTE entry;
+
+ Assert(ip->erst_idx < ip->erstsz);
+ GCPhysErste = ip->erstba + ip->erst_idx * sizeof(XHCI_ERSTE);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry));
+
+ /*
+ * 6.5 claims values in 16-4096 range are valid, but does not say what
+ * happens for values outside of that range...
+ */
+ Assert((pThis->status & XHCI_STATUS_HCH) || (entry.size >= 16 && entry.size <= 4096));
+
+ /* Cache the entry data internally. */
+ ip->erep = entry.addr & pThis->erst_addr_mask;
+ ip->trb_count = entry.size;
+ Log(("Fetched ERST Entry at %RGp: %u entries at %RGp\n", GCPhysErste, ip->trb_count, ip->erep));
+}
+
+/**
+ * Set the interrupter's IP and EHB bits and trigger an interrupt if required.
+ *
+ * @param pDevIns The PDM device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param ip Pointer to the interrupter structure.
+ *
+ */
+static void xhciSetIntr(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip)
+{
+ Assert(pThis && ip);
+ LogFlowFunc(("old IP: %u\n", !!(ip->iman & XHCI_IMAN_IP)));
+
+ if (!(ip->iman & XHCI_IMAN_IP))
+ {
+ /// @todo assert that we own the interrupter lock
+ ASMAtomicOrU32(&pThis->status, XHCI_STATUS_EINT);
+ ASMAtomicOrU64(&ip->erdp, XHCI_ERDP_EHB);
+ ASMAtomicOrU32(&ip->iman, XHCI_IMAN_IP);
+ if ((ip->iman & XHCI_IMAN_IE) && (pThis->cmd & XHCI_CMD_INTE))
+ {
+#ifdef XHCI_ERROR_INJECTION
+ if (pThis->fDropIntrHw)
+ {
+ pThis->fDropIntrHw = false;
+ ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP);
+ }
+ else
+#endif
+ {
+ Log2(("Triggering interrupt on interrupter %u\n", ip->index));
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH);
+ STAM_COUNTER_INC(&pThis->StatIntrsSet);
+ }
+ }
+ else
+ {
+ Log2(("Not triggering interrupt on interrupter %u (interrupts disabled)\n", ip->index));
+ STAM_COUNTER_INC(&pThis->StatIntrsNotSet);
+ }
+
+ /* If MSI/MSI-X is in use, the IP bit is immediately cleared again. */
+ if (xhciIsMSIEnabled(pDevIns->apPciDevs[0]))
+ ASMAtomicAndU32(&ip->iman, ~XHCI_IMAN_IP);
+ }
+}
+
+#ifdef IN_RING3
+
+/**
+ * Set the interrupter's IPE bit. If this causes a 0->1 transition, an
+ * interrupt may be triggered.
+ *
+ * @param pDevIns The PDM device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param ip Pointer to the interrupter structure.
+ */
+static void xhciR3SetIntrPending(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip)
+{
+ uint16_t imodc = (ip->imod >> XHCI_IMOD_IMODC_SHIFT) & XHCI_IMOD_IMODC_MASK;
+
+ Assert(pThis && ip);
+ LogFlowFunc(("old IPE: %u, IMODC: %u, EREP: %RGp, EHB: %u\n", ip->ipe, imodc, (RTGCPHYS)ip->erep, !!(ip->erdp & XHCI_ERDP_EHB)));
+ STAM_COUNTER_INC(&pThis->StatIntrsPending);
+
+ if (!ip->ipe)
+ {
+#ifdef XHCI_ERROR_INJECTION
+ if (pThis->fDropIntrIpe)
+ {
+ pThis->fDropIntrIpe = false;
+ }
+ else
+#endif
+ {
+ ip->ipe = true;
+ if (!(ip->erdp & XHCI_ERDP_EHB) && (imodc == 0))
+ xhciSetIntr(pDevIns, pThis, ip);
+ }
+ }
+}
+
+
+/**
+ * Check if there is space available for writing at least two events on the
+ * event ring. See 4.9.4 for the state machine (right hand side of diagram).
+ * If there's only room for one event, the Event Ring Full TRB will need to
+ * be written out, hence the ring is considered full.
+ *
+ * @returns True if space is available, false otherwise.
+ * @param pDevIns The PDM device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param pIntr Pointer to the interrupter structure.
+ */
+static bool xhciR3IsEvtRingFull(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr)
+{
+ uint64_t next_ptr;
+ uint64_t erdp = pIntr->erdp & XHCI_ERDP_ADDR_MASK;
+
+ if (pIntr->trb_count > 1)
+ {
+ /* Check the current segment. */
+ next_ptr = pIntr->erep + sizeof(XHCI_EVENT_TRB);
+ }
+ else
+ {
+ uint16_t erst_idx;
+ XHCI_ERSTE entry;
+ RTGCPHYS GCPhysErste;
+
+ /* Need to check the next segment. */
+ erst_idx = pIntr->erst_idx + 1;
+ if (erst_idx == pIntr->erstsz)
+ erst_idx = 0;
+ GCPhysErste = pIntr->erstba + erst_idx * sizeof(XHCI_ERSTE);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysErste, &entry, sizeof(entry));
+ next_ptr = entry.addr & pThis->erst_addr_mask;
+ }
+
+ /// @todo We'll have to remember somewhere that the ring is full
+ return erdp == next_ptr;
+}
+
+/**
+ * Write an event to the given Event Ring. This implements a good chunk of
+ * the event ring state machine in section 4.9.4 of the xHCI spec.
+ *
+ * @returns VBox status code. Error if event could not be enqueued.
+ * @param pDevIns The PDM device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param pEvent Pointer to the Event TRB to be enqueued.
+ * @param iIntr Index of the interrupter to write to.
+ * @param fBlockInt Set if interrupt should be blocked (BEI bit).
+ */
+static int xhciR3WriteEvent(PPDMDEVINS pDevIns, PXHCI pThis, XHCI_EVENT_TRB *pEvent, unsigned iIntr, bool fBlockInt)
+{
+ PXHCIINTRPTR pIntr;
+ int rc = VINF_SUCCESS;
+
+ LogFlowFunc(("Interrupter: %u\n", iIntr));
+
+ /* If the HC isn't running, events can not be generated. However,
+ * especially port change events can be triggered at any time. We just
+ * drop them here -- it's often not an error condition.
+ */
+ if (pThis->cmd & XHCI_CMD_RS)
+ {
+ STAM_COUNTER_INC(&pThis->StatEventsWritten);
+ Assert(iIntr < XHCI_NINTR); /* Supplied by guest, potentially invalid. */
+ pIntr = &pThis->aInterrupters[iIntr & XHCI_INTR_MASK];
+
+ /*
+ * If the interrupter/event ring isn't in a sane state, just
+ * give up and report Host Controller Error (HCE).
+ */
+ // pIntr->erst_idx
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, &pIntr->lock, VERR_IGNORED); /* R3 only, no rcBusy. */
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, &pIntr->lock, rcLock); /* eventually, most call chains ignore the status. */
+
+ if (xhciR3IsEvtRingFull(pDevIns, pThis, pIntr))
+ {
+ LogRel(("xHCI: Event ring full!\n"));
+ }
+
+ /* Set the TRB's Cycle bit as appropriate. */
+ pEvent->gen.cycle = pIntr->evtr_pcs;
+
+ /* Write out the TRB and advance the EREP. */
+ /// @todo This either has to be atomic from the guest's POV or the cycle bit needs to be toggled last!!
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, pIntr->erep, pEvent, sizeof(*pEvent));
+ pIntr->erep += sizeof(*pEvent);
+ --pIntr->trb_count;
+
+ /* Advance to the next ERST entry if necessary. */
+ if (pIntr->trb_count == 0)
+ {
+ ++pIntr->erst_idx;
+ /* If necessary, roll over back to the beginning. */
+ if (pIntr->erst_idx == pIntr->erstsz)
+ {
+ pIntr->erst_idx = 0;
+ pIntr->evtr_pcs = !pIntr->evtr_pcs;
+ }
+ xhciFetchErstEntry(pDevIns, pThis, pIntr);
+ }
+
+ /* Set the IPE bit unless interrupts are blocked. */
+ if (!fBlockInt)
+ xhciR3SetIntrPending(pDevIns, pThis, pIntr);
+
+ PDMDevHlpCritSectLeave(pDevIns, &pIntr->lock);
+ }
+ else
+ {
+ STAM_COUNTER_INC(&pThis->StatEventsDropped);
+ Log(("Event dropped because HC is not running.\n"));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Post a port change TRB to an Event Ring.
+ */
+static int xhciR3GenPortChgEvent(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uPort)
+{
+ XHCI_EVENT_TRB ed; /* Event Descriptor */
+ LogFlowFunc(("Port ID: %u\n", uPort));
+
+ /*
+ * Devices can be "physically" attached/detached regardless of whether
+ * the HC is running or not, but the port status change events can only
+ * be generated when R/S is set; xhciR3WriteEvent() takes care of that.
+ */
+ RT_ZERO(ed);
+ ed.psce.cc = XHCI_TCC_SUCCESS;
+ ed.psce.port_id = uPort;
+ ed.psce.type = XHCI_TRB_PORT_SC;
+ return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false);
+}
+
+
+/**
+ * Post a command completion TRB to an Event Ring.
+ */
+static int xhciR3PostCmdCompletion(PPDMDEVINS pDevIns, PXHCI pThis, unsigned cc, unsigned uSlotID)
+{
+ XHCI_EVENT_TRB ed; /* Event Descriptor */
+ LogFlowFunc(("Cmd @ %RGp, Completion Code: %u (%s), Slot ID: %u\n", (RTGCPHYS)pThis->cmdr_dqp, cc,
+ cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!", uSlotID));
+
+ /* The Command Ring dequeue pointer still holds the address of the current
+ * command TRB. It is written to the completion event TRB as the command
+ * TRB pointer.
+ */
+ RT_ZERO(ed);
+ ed.cce.trb_ptr = pThis->cmdr_dqp;
+ ed.cce.cc = cc;
+ ed.cce.type = XHCI_TRB_CMD_CMPL;
+ ed.cce.slot_id = uSlotID;
+ return xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false);
+}
+
+
+/**
+ * Post a transfer event TRB to an Event Ring.
+ */
+static int xhciR3PostXferEvent(PPDMDEVINS pDevIns, PXHCI pThis, unsigned uIntTgt, unsigned uXferLen, unsigned cc,
+ unsigned uSlotID, unsigned uEpDCI, uint64_t uEvtData, bool fBlockInt, bool fEvent)
+{
+ XHCI_EVENT_TRB ed; /* Event Descriptor */
+ LogFlowFunc(("Xfer @ %RGp, Completion Code: %u (%s), Slot ID=%u DCI=%u Target=%u EvtData=%RX64 XfrLen=%u BEI=%u ED=%u\n",
+ (RTGCPHYS)pThis->cmdr_dqp, cc, cc < RT_ELEMENTS(g_apszCmplCodes) ? g_apszCmplCodes[cc] : "WHAT?!!",
+ uSlotID, uEpDCI, uIntTgt, uEvtData, uXferLen, fBlockInt, fEvent));
+
+ /* A transfer event may be either generated by TRB completion (in case
+ * fEvent=false) or by a special transfer event TRB (fEvent=true). In
+ * either case, interrupts may be suppressed.
+ */
+ RT_ZERO(ed);
+ ed.te.trb_ptr = uEvtData;
+ ed.te.xfr_len = uXferLen;
+ ed.te.cc = cc;
+ ed.te.ed = fEvent;
+ ed.te.type = XHCI_TRB_XFER;
+ ed.te.ep_id = uEpDCI;
+ ed.te.slot_id = uSlotID;
+ return xhciR3WriteEvent(pDevIns, pThis, &ed, uIntTgt, fBlockInt); /* Sets the cycle bit, too. */
+}
+
+
+static int xhciR3FindRhDevBySlot(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, PXHCIROOTHUBR3 *ppRh, uint32_t *puPort)
+{
+ XHCI_SLOT_CTX slot_ctx;
+ PXHCIROOTHUBR3 pRh;
+ unsigned iPort;
+ int rc;
+
+ /// @todo Do any of these need to be release assertions?
+ Assert(uSlotID <= RT_ELEMENTS(pThis->aSlotState));
+ Assert(pThis->aSlotState[ID_TO_IDX(uSlotID)] > XHCI_DEVSLOT_EMPTY);
+
+ /* Load the slot context. */
+ xhciR3FetchDevCtx(pDevIns, pThis, uSlotID, 0, &slot_ctx);
+
+ /* The port ID is stored in the slot context. */
+ iPort = ID_TO_IDX(slot_ctx.rh_port);
+ if (iPort < XHCI_NDP_CFG(pThis))
+ {
+ /* Find the corresponding root hub. */
+ pRh = GET_PORT_PRH(pThisCC, iPort);
+ Assert(pRh);
+
+ /* And the device; if the device was ripped out fAttached will be false. */
+ if (pThisCC->aPorts[iPort].fAttached)
+ {
+ /* Provide the information the caller asked for. */
+ if (ppRh)
+ *ppRh = pRh;
+ if (puPort)
+ *puPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort);
+ rc = VINF_SUCCESS;
+ }
+ else
+ {
+ LogFunc(("No device attached (port index %u)!\n", iPort));
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+ }
+ else
+ {
+ LogFunc(("Port out of range (index %u)!\n", iPort));
+ rc = VERR_INVALID_PARAMETER;
+ }
+ return rc;
+}
+
+
+static void xhciR3EndlessTrbError(PPDMDEVINS pDevIns, PXHCI pThis)
+{
+ /* Clear the R/S bit and indicate controller error. */
+ ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS);
+ ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE);
+
+ /* Ensure that XHCI_STATUS_HCH gets set by the worker thread. */
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0);
+
+ LogRelMax(10, ("xHCI: Attempted to process too many TRBs, stopping xHC!\n"));
+}
+
+/**
+ * TRB walker callback prototype.
+ *
+ * @returns true if walking should continue.
+ * @returns false if walking should be terminated.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param pXferTRB Pointer to the transfer TRB to handle.
+ * @param GCPhysXfrTRB Physical address of the TRB.
+ * @param pvContext User-defined walk context.
+ * @remarks We don't need to use DECLCALLBACKPTR here, since all users are in
+ * the same source file, but having the functions marked with
+ * DECLCALLBACK helps readability.
+ */
+typedef DECLCALLBACKPTR(bool, PFNTRBWALKCB,(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB,
+ RTGCPHYS GCPhysXfrTRB, void *pvContext));
+
+
+/**
+ * Walk a chain of TRBs which comprise a single TD.
+ *
+ * This is something we need to do potentially more than once when submitting a
+ * URB and then often again when completing the URB. Note that the walker does
+ * not update the endpoint state (TRDP/TREP/DCS) so that it can be re-run
+ * multiple times.
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param uTRP Initial TR pointer and DCS.
+ * @param pfnCbk Callback routine.
+ * @param pvContext User-defined walk context.
+ * @param pTREP Pointer to storage for final TR Enqueue Pointer/DCS.
+ */
+static int xhciR3WalkXferTrbChain(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uTRP,
+ PFNTRBWALKCB pfnCbk, void *pvContext, uint64_t *pTREP)
+{
+ RTGCPHYS GCPhysXfrTRB;
+ uint64_t uTREP;
+ XHCI_XFER_TRB XferTRB;
+ bool fContinue = true;
+ bool dcs;
+ int rc = VINF_SUCCESS;
+ unsigned cTrbs = 0;
+
+ AssertPtr(pvContext);
+ AssertPtr(pTREP);
+ Assert(uTRP);
+
+ /* Find the transfer TRB address and the DCS. */
+ GCPhysXfrTRB = uTRP & XHCI_TRDP_ADDR_MASK;
+ dcs = !!(uTRP & XHCI_TRDP_DCS_MASK); /* MSC upgrades bool to signed something when comparing with a uint8_t:1. */
+ LogFlowFunc(("Walking Transfer Ring, TREP:%RGp DCS=%u\n", GCPhysXfrTRB, dcs));
+
+ do {
+ /* Fetch the transfer TRB. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB));
+
+ if ((bool)XferTRB.gen.cycle == dcs)
+ {
+ Log2(("Walking TRB@%RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, XferTRB.gen.type,
+ XferTRB.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[XferTRB.gen.type] : "WHAT?!!",
+ XferTRB.gen.xfr_len, XferTRB.gen.ent, XferTRB.gen.isp, XferTRB.gen.ns, XferTRB.gen.ch, XferTRB.gen.ioc, XferTRB.gen.idt));
+
+ /* DCS matches, the TRB is ours to process. */
+ switch (XferTRB.gen.type) {
+ case XHCI_TRB_LINK:
+ Log2(("Link intra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", XferTRB.link.rseg_ptr, XferTRB.link.ioc, XferTRB.link.toggle, XferTRB.link.chain));
+ Assert(XferTRB.link.chain);
+ /* Do not update the actual TRDP/TREP and DCS yet, just the temporary images. */
+ GCPhysXfrTRB = XferTRB.link.rseg_ptr & XHCI_TRDP_ADDR_MASK;
+ if (XferTRB.link.toggle)
+ dcs = !dcs;
+ Assert(!XferTRB.link.ioc); /// @todo Needs to be reported.
+ break;
+ case XHCI_TRB_NORMAL:
+ case XHCI_TRB_ISOCH:
+ case XHCI_TRB_SETUP_STG:
+ case XHCI_TRB_DATA_STG:
+ case XHCI_TRB_STATUS_STG:
+ case XHCI_TRB_EVT_DATA:
+ fContinue = pfnCbk(pDevIns, pThis, &XferTRB, GCPhysXfrTRB, pvContext);
+ GCPhysXfrTRB += sizeof(XferTRB);
+ break;
+ default:
+ /* NB: No-op TRBs are not allowed within TDs (4.11.7). */
+ Log(("Bad TRB type %u found within TD!!\n", XferTRB.gen.type));
+ fContinue = false;
+ /// @todo Stop EP etc.?
+ }
+ }
+ else
+ {
+ /* We don't have a complete TD. Interesting times. */
+ Log2(("DCS mismatch, no more TRBs available.\n"));
+ fContinue = false;
+ rc = VERR_TRY_AGAIN;
+ }
+
+ /* Kill the xHC if the TRB list has no end in sight. */
+ if (++cTrbs > XHCI_MAX_NUM_TRBS)
+ {
+ /* Stop the xHC with an error. */
+ xhciR3EndlessTrbError(pDevIns, pThis);
+
+ /* Get out of the loop. */
+ fContinue = false;
+ rc = VERR_NOT_SUPPORTED; /* No good error code really... */
+ }
+ } while (fContinue);
+
+ /* Inform caller of the new TR Enqueue Pointer/DCS (not necessarily changed). */
+ Assert(!(GCPhysXfrTRB & ~XHCI_TRDP_ADDR_MASK));
+ uTREP = GCPhysXfrTRB | (unsigned)dcs;
+ Log2(("Final TRP after walk: %RGp\n", uTREP));
+ *pTREP = uTREP;
+
+ return rc;
+}
+
+
+/** Context for probing TD size. */
+typedef struct {
+ uint32_t uXferLen;
+ uint32_t cTRB;
+ uint32_t uXfrLenLastED;
+ uint32_t cTRBLastED;
+} XHCI_CTX_XFER_PROBE;
+
+
+/** Context for submitting 'out' TDs. */
+typedef struct {
+ PVUSBURB pUrb;
+ uint32_t uXferPos;
+ unsigned cTRB;
+} XHCI_CTX_XFER_SUBMIT;
+
+
+/** Context for completing TDs. */
+typedef struct {
+ PVUSBURB pUrb;
+ uint32_t uXferPos;
+ uint32_t uXferLeft;
+ unsigned cTRB;
+ uint32_t uEDTLA : 24;
+ uint32_t uLastCC : 8;
+ uint8_t uSlotID;
+ uint8_t uEpDCI;
+ bool fMaxCount;
+} XHCI_CTX_XFER_COMPLETE;
+
+
+/** Context for building isochronous URBs. */
+typedef struct {
+ PVUSBURB pUrb;
+ unsigned iPkt;
+ uint32_t offCur;
+ uint64_t uInitTREP;
+ bool fSubmitFailed;
+} XHCI_CTX_ISOCH;
+
+
+/**
+ * @callback_method_impl{PFNTRBWALKCB,
+ * Probe a TD and figure out how big it is so that a URB can be allocated to back it.}
+ */
+static DECLCALLBACK(bool)
+xhciR3WalkDataTRBsProbe(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext)
+{
+ RT_NOREF(pDevIns, pThis, GCPhysXfrTRB);
+ XHCI_CTX_XFER_PROBE *pCtx = (XHCI_CTX_XFER_PROBE *)pvContext;
+
+ pCtx->cTRB++;
+
+ /* Only consider TRBs which transfer data. */
+ switch (pXferTRB->gen.type)
+ {
+ case XHCI_TRB_NORMAL:
+ case XHCI_TRB_ISOCH:
+ case XHCI_TRB_SETUP_STG:
+ case XHCI_TRB_DATA_STG:
+ case XHCI_TRB_STATUS_STG:
+ pCtx->uXferLen += pXferTRB->norm.xfr_len;
+ if (RT_UNLIKELY(pCtx->uXferLen > XHCI_MAX_TD_SIZE))
+ {
+ /* NB: We let the TD size get a bit past the max so that we don't lose anything,
+ * but the EDTLA will wrap around.
+ */
+ LogRelMax(10, ("xHCI: TD size (%u) too big, not continuing!\n", pCtx->uXferLen));
+ return false;
+ }
+ break;
+ case XHCI_TRB_EVT_DATA:
+ /* Remember where the last seen Event Data TRB was. */
+ pCtx->cTRBLastED = pCtx->cTRB;
+ pCtx->uXfrLenLastED = pCtx->uXferLen;
+ break;
+ default: /* Could be a link TRB, too. */
+ break;
+ }
+
+ return pXferTRB->gen.ch;
+}
+
+
+/**
+ * @callback_method_impl{PFNTRBWALKCB,
+ * Copy data from a TD (TRB chain) into the corresponding TD. OUT direction only.}
+ */
+static DECLCALLBACK(bool)
+xhciR3WalkDataTRBsSubmit(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext)
+{
+ RT_NOREF(pThis, GCPhysXfrTRB);
+ XHCI_CTX_XFER_SUBMIT *pCtx = (XHCI_CTX_XFER_SUBMIT *)pvContext;
+ uint32_t uXferLen = pXferTRB->norm.xfr_len;
+
+
+ /* Only consider TRBs which transfer data. */
+ switch (pXferTRB->gen.type)
+ {
+ case XHCI_TRB_NORMAL:
+ case XHCI_TRB_ISOCH:
+ case XHCI_TRB_SETUP_STG:
+ case XHCI_TRB_DATA_STG:
+ case XHCI_TRB_STATUS_STG:
+ /* NB: Transfer length may be zero! */
+ /// @todo explain/verify abuse of various TRB types here (data stage mapped to normal etc.).
+ if (uXferLen)
+ {
+ /* Sanity check for broken guests (TRBs may have changed since probing). */
+ if (pCtx->uXferPos + uXferLen <= pCtx->pUrb->cbData)
+ {
+ /* Data might be immediate or elsewhere in memory. */
+ if (pXferTRB->norm.idt)
+ {
+ /* If an immediate data TRB claims there's more than 8 bytes, we have a problem. */
+ if (uXferLen > 8)
+ {
+ LogRelMax(10, ("xHCI: Immediate data TRB length %u bytes, ignoring!\n", uXferLen));
+ return false; /* Stop walking the chain immediately. */
+ }
+
+ Assert(uXferLen >= 1 && uXferLen <= 8);
+ Log2(("Copying %u bytes to URB offset %u (immediate data)\n", uXferLen, pCtx->uXferPos));
+ memcpy(pCtx->pUrb->abData + pCtx->uXferPos, pXferTRB, uXferLen);
+ }
+ else
+ {
+ PDMDevHlpPCIPhysReadUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen);
+ Log2(("Copying %u bytes to URB offset %u (from %RGp)\n", uXferLen, pCtx->uXferPos, pXferTRB->norm.data_ptr));
+ }
+ pCtx->uXferPos += uXferLen;
+ }
+ else
+ {
+ LogRelMax(10, ("xHCI: Attempted to submit too much data, ignoring!\n"));
+ return false; /* Stop walking the chain immediately. */
+ }
+
+ }
+ break;
+ default: /* Could be an event or status stage TRB, too. */
+ break;
+ }
+ pCtx->cTRB++;
+
+ /// @todo Maybe have to make certain that the number of probed TRBs matches? Potentially
+ /// by the time TRBs get submitted, there might be more of them available if the TD was
+ /// initially not fully written by HCD.
+
+ return pXferTRB->gen.ch;
+}
+
+
+/**
+ * Perform URB completion processing.
+ *
+ * Figure out how much data was really transferred, post events if required, and
+ * for IN transfers, copy data from the URB.
+ *
+ * @callback_method_impl{PFNTRBWALKCB}
+ */
+static DECLCALLBACK(bool)
+xhciR3WalkDataTRBsComplete(PPDMDEVINS pDevIns, PXHCI pThis, const XHCI_XFER_TRB *pXferTRB, RTGCPHYS GCPhysXfrTRB, void *pvContext)
+{
+ XHCI_CTX_XFER_COMPLETE *pCtx = (XHCI_CTX_XFER_COMPLETE *)pvContext;
+ int rc;
+ unsigned uXferLen;
+ unsigned uResidue;
+ uint8_t cc;
+ bool fKeepGoing = true;
+
+ switch (pXferTRB->gen.type)
+ {
+ case XHCI_TRB_NORMAL:
+ case XHCI_TRB_ISOCH:
+ case XHCI_TRB_SETUP_STG:
+ case XHCI_TRB_DATA_STG: /// @todo document abuse; esp. check BEI bit
+ case XHCI_TRB_STATUS_STG:
+ /* Assume successful transfer. */
+ uXferLen = pXferTRB->norm.xfr_len;
+ cc = XHCI_TCC_SUCCESS;
+
+ /* If there was a short packet, handle it accordingly. */
+ if (pCtx->uXferLeft < uXferLen)
+ {
+ /* The completion code is set regardless of IOC/ISP. It may be
+ * reported later via an Event Data TRB (4.10.1.1)
+ */
+ uXferLen = pCtx->uXferLeft;
+ cc = XHCI_TCC_SHORT_PKT;
+ }
+
+ if (pCtx->pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ Assert(!pXferTRB->norm.idt);
+
+ /* NB: Transfer length may be zero! */
+ if (uXferLen)
+ {
+ if (uXferLen <= pCtx->uXferLeft)
+ {
+ Log2(("Writing %u bytes to %RGp from URB offset %u (TRB@%RGp)\n", uXferLen, pXferTRB->norm.data_ptr, pCtx->uXferPos, GCPhysXfrTRB));
+ PDMDevHlpPCIPhysWriteUser(pDevIns, pXferTRB->norm.data_ptr, pCtx->pUrb->abData + pCtx->uXferPos, uXferLen);
+ }
+ else
+ {
+ LogRelMax(10, ("xHCI: Attempted to read too much data, ignoring!\n"));
+ }
+ }
+ }
+
+ /* Update position within TD. */
+ pCtx->uXferLeft -= uXferLen;
+ pCtx->uXferPos += uXferLen;
+ Log2(("Current uXferLeft=%u, uXferPos=%u (length was %u)\n", pCtx->uXferLeft, pCtx->uXferPos, uXferLen));
+
+ /* Keep track of the EDTLA and last completion status. */
+ pCtx->uEDTLA += uXferLen; /* May wrap around! */
+ pCtx->uLastCC = cc;
+
+ /* Report events as required. */
+ uResidue = pXferTRB->norm.xfr_len - uXferLen;
+ if (pXferTRB->norm.ioc || (pXferTRB->norm.isp && uResidue))
+ {
+ rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->norm.int_tgt, uResidue, cc,
+ pCtx->uSlotID, pCtx->uEpDCI, GCPhysXfrTRB, pXferTRB->norm.bei, false);
+ }
+ break;
+ case XHCI_TRB_EVT_DATA:
+ if (pXferTRB->evtd.ioc)
+ {
+ rc = xhciR3PostXferEvent(pDevIns, pThis, pXferTRB->evtd.int_tgt, pCtx->uEDTLA, pCtx->uLastCC,
+ pCtx->uSlotID, pCtx->uEpDCI, pXferTRB->evtd.evt_data, pXferTRB->evtd.bei, true);
+ }
+ /* Clear the EDTLA. */
+ pCtx->uEDTLA = 0;
+ break;
+ default:
+ AssertMsgFailed(("%#x\n", pXferTRB->gen.type));
+ break;
+ }
+
+ pCtx->cTRB--;
+ /* For TD fragments, enforce the maximum count, but only as long as the transfer
+ * is successful. In case of error we have to complete the entire TD! */
+ if (!pCtx->cTRB && pCtx->fMaxCount && pCtx->uLastCC == XHCI_TCC_SUCCESS)
+ {
+ Log2(("Stopping at the end of TD Fragment.\n"));
+ fKeepGoing = false;
+ }
+
+ /* NB: We currently do not enforce that the number of TRBs can't change between
+ * submission and completion. If we do, we'll have to store it somewhere for
+ * isochronous URBs.
+ */
+ return pXferTRB->gen.ch && fKeepGoing;
+}
+
+/**
+ * Process (consume) non-data TRBs on a transfer ring. This function
+ * completes TRBs which do not have any URB associated with them. Only
+ * used with running endpoints. Usable regardless of whether there are
+ * in-flight TRBs or not. Returns the next TRB and its address to the
+ * caller. May modify the endpoint context!
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state.
+ * @param uSlotID The slot corresponding to this USB device.
+ * @param uEpDCI The DCI of this endpoint.
+ * @param pEpCtx Endpoint context. May be modified.
+ * @param pXfer Storage for returning the next TRB to caller.
+ * @param pGCPhys Storage for returning the physical address of TRB.
+ */
+static int xhciR3ConsumeNonXferTRBs(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uEpDCI,
+ XHCI_EP_CTX *pEpCtx, XHCI_XFER_TRB *pXfer, RTGCPHYS *pGCPhys)
+{
+ XHCI_XFER_TRB xfer;
+ RTGCPHYS GCPhysXfrTRB = 0;
+ bool dcs;
+ bool fInFlight;
+ bool fContinue = true;
+ int rc;
+ unsigned cTrbs = 0;
+
+ LogFlowFunc(("Slot ID: %u, EP DCI %u\n", uSlotID, uEpDCI));
+ Assert(uSlotID > 0);
+ Assert(uSlotID <= XHCI_NDS);
+
+ Assert(pEpCtx->ep_state == XHCI_EPST_RUNNING);
+ do
+ {
+ /* Find the transfer TRB address. */
+ GCPhysXfrTRB = pEpCtx->trdp & XHCI_TRDP_ADDR_MASK;
+ dcs = !!(pEpCtx->trdp & XHCI_TRDP_DCS_MASK);
+
+ /* Determine whether there are any in-flight TRBs or not. This affects TREP
+ * processing -- when nothing is in flight, we have to move both TREP and TRDP;
+ * otherwise only the TRDP must be updated.
+ */
+ fInFlight = pEpCtx->trep != pEpCtx->trdp;
+ LogFlowFunc(("Skipping non-data TRBs, TREP:%RGp, TRDP:%RGp, in-flight: %RTbool\n", pEpCtx->trep, pEpCtx->trdp, fInFlight));
+
+ /* Fetch the transfer TRB. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer));
+
+ /* Make sure the Cycle State matches. */
+ if ((bool)xfer.gen.cycle == dcs)
+ {
+ Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type,
+ xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!",
+ xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt));
+
+ switch (xfer.gen.type) {
+ case XHCI_TRB_LINK:
+ Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain));
+ Assert(!xfer.link.chain);
+ /* Set new TRDP but leave DCS bit alone... */
+ pEpCtx->trdp = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (pEpCtx->trdp & XHCI_TRDP_DCS_MASK);
+ /* ...and flip the DCS bit if required. Then update the TREP. */
+ if (xfer.link.toggle)
+ pEpCtx->trdp = (pEpCtx->trdp & ~XHCI_TRDP_DCS_MASK) | (pEpCtx->trdp ^ XHCI_TRDP_DCS_MASK);
+ if (!fInFlight)
+ pEpCtx->trep = pEpCtx->trdp;
+ if (xfer.link.ioc)
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.link.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI,
+ GCPhysXfrTRB, false, false);
+ break;
+ case XHCI_TRB_NOOP_XFER:
+ Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent));
+ /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */
+ Assert(!xfer.link.chain);
+ /* Update enqueue/dequeue pointers. */
+ pEpCtx->trdp += sizeof(XHCI_XFER_TRB);
+ if (!fInFlight)
+ pEpCtx->trep += sizeof(XHCI_XFER_TRB);
+ if (xfer.nop.ioc)
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.nop.int_tgt, 0, XHCI_TCC_SUCCESS, uSlotID, uEpDCI,
+ GCPhysXfrTRB, false, false);
+ break;
+ default:
+ fContinue = false;
+ break;
+ }
+ }
+ else
+ {
+ LogFunc(("Transfer Ring empty\n"));
+ fContinue = false;
+ }
+
+ /* Kill the xHC if the TRB list has no end in sight. */
+ /* NB: The limit here could perhaps be much lower because a sequence of Link
+ * and No-op TRBs with no real work to be done would be highly suspect.
+ */
+ if (++cTrbs > XHCI_MAX_NUM_TRBS)
+ {
+ /* Stop the xHC with an error. */
+ xhciR3EndlessTrbError(pDevIns, pThis);
+
+ /* Get out of the loop. */
+ fContinue = false;
+ rc = VERR_NOT_SUPPORTED; /* No good error code really... */
+ }
+ } while (fContinue);
+
+ /* The caller will need the next TRB. Hand it over. */
+ Assert(GCPhysXfrTRB);
+ *pGCPhys = GCPhysXfrTRB;
+ *pXfer = xfer;
+ LogFlowFunc(("Final TREP:%RGp, TRDP:%RGp GCPhysXfrTRB:%RGp\n", pEpCtx->trep, pEpCtx->trdp, GCPhysXfrTRB));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Transfer completion callback routine.
+ *
+ * VUSB will call this when a transfer have been completed
+ * in a one or another way.
+ *
+ * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(void) xhciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_EP_CTX ep_ctx;
+ XHCI_XFER_TRB xfer;
+ RTGCPHYS GCPhysXfrTRB;
+ int rc;
+ unsigned uResidue = 0;
+ uint8_t uSlotID = pUrb->pHci->uSlotID;
+ uint8_t cc = XHCI_TCC_SUCCESS;
+ uint8_t uEpDCI;
+
+ /* Check for URBs completed synchronously as part of xHCI command execution.
+ * The URB will have zero cTRB as it's not associated with a TD.
+ */
+ if (!pUrb->pHci->cTRB)
+ {
+ LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n",
+ pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus));
+ LogFlow(("%s: xhciR3RhXferCompletion: Completing xHCI-generated request\n", pUrb->pszDesc));
+ return;
+ }
+
+ /* If the xHC isn't running, just drop the URB right here. */
+ if (pThis->status & XHCI_STATUS_HCH)
+ {
+ LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d cbData=%u status=%u\n",
+ pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB, pUrb->cbData, pUrb->enmStatus));
+ LogFlow(("%s: xhciR3RhXferCompletion: xHC halted, skipping URB completion\n", pUrb->pszDesc));
+ return;
+ }
+
+#ifdef XHCI_ERROR_INJECTION
+ if (pThis->fDropUrb)
+ {
+ LogFlow(("%s: xhciR3RhXferCompletion: Error injection, dropping URB!\n", pUrb->pszDesc));
+ pThis->fDropUrb = false;
+ return;
+ }
+#endif
+
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+
+ /* Convert USB endpoint address to xHCI format. */
+ if (pUrb->EndPt)
+ uEpDCI = pUrb->EndPt * 2 + (pUrb->enmDir == VUSBDIRECTION_IN ? 1 : 0);
+ else
+ uEpDCI = 1; /* EP 0 */
+
+ LogFlow(("%s: xhciR3RhXferCompletion: uSlotID=%u EP=%u cTRB=%d\n",
+ pUrb->pszDesc, uSlotID, pUrb->EndPt, pUrb->pHci->cTRB));
+ LogFlow(("%s: xhciR3RhXferCompletion: EP DCI=%u, cbData=%u status=%u\n", pUrb->pszDesc, uEpDCI, pUrb->cbData, pUrb->enmStatus));
+
+ /* Load the slot/endpoint contexts from guest memory. */
+ xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uEpDCI, &slot_ctx, &ep_ctx);
+
+ /* If the EP is disabled, we don't own it so we can't complete the URB.
+ * Leave this EP alone and drop the URB.
+ */
+ if (ep_ctx.ep_state != XHCI_EPST_RUNNING)
+ {
+ Log(("EP DCI %u not running (state %u), skipping URB completion\n", uEpDCI, ep_ctx.ep_state));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ return;
+ }
+
+ /* Now complete any non-transfer TRBs that might be on the transfer ring before
+ * the TRB(s) corresponding to this URB. Preloads the TRB as a side effect.
+ * Endpoint state now must be written back in case it was modified!
+ */
+ xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB);
+
+ /* Deal with failures which halt the EP first. */
+ if (RT_UNLIKELY(pUrb->enmStatus != VUSBSTATUS_OK))
+ {
+ switch(pUrb->enmStatus)
+ {
+ case VUSBSTATUS_STALL:
+ /* Halt the endpoint and inform the HCD.
+ * NB: The TRDP is NOT advanced in case of error.
+ */
+ ep_ctx.ep_state = XHCI_EPST_HALTED;
+ cc = XHCI_TCC_STALL;
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc,
+ uSlotID, uEpDCI, GCPhysXfrTRB, false, false);
+ break;
+ case VUSBSTATUS_DNR:
+ /* Halt the endpoint and inform the HCD.
+ * NB: The TRDP is NOT advanced in case of error.
+ */
+ ep_ctx.ep_state = XHCI_EPST_HALTED;
+ cc = XHCI_TCC_USB_XACT_ERR;
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc,
+ uSlotID, uEpDCI, GCPhysXfrTRB, false, false);
+ break;
+ case VUSBSTATUS_CRC: /// @todo Separate status for canceling?!
+ ep_ctx.ep_state = XHCI_EPST_HALTED;
+ cc = XHCI_TCC_USB_XACT_ERR;
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc,
+ uSlotID, uEpDCI, GCPhysXfrTRB, false, false);
+
+ /* NB: The TRDP is *not* advanced and TREP is reset. */
+ ep_ctx.trep = ep_ctx.trdp;
+ break;
+ case VUSBSTATUS_DATA_OVERRUN:
+ case VUSBSTATUS_DATA_UNDERRUN:
+ /* Halt the endpoint and inform the HCD.
+ * NB: The TRDP is NOT advanced in case of error.
+ */
+ ep_ctx.ep_state = XHCI_EPST_HALTED;
+ cc = XHCI_TCC_DATA_BUF_ERR;
+ rc = xhciR3PostXferEvent(pDevIns, pThis, xfer.gen.int_tgt, uResidue, cc,
+ uSlotID, uEpDCI, GCPhysXfrTRB, false, false);
+ break;
+ default:
+ AssertMsgFailed(("Unexpected URB status %u\n", pUrb->enmStatus));
+ }
+
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ STAM_COUNTER_INC(&pThis->StatErrorIsocUrbs);
+ }
+ else if (xfer.gen.type == XHCI_TRB_NORMAL)
+ {
+ XHCI_CTX_XFER_COMPLETE ctxComplete;
+ uint64_t uTRDP;
+
+ ctxComplete.pUrb = pUrb;
+ ctxComplete.uXferPos = 0;
+ ctxComplete.uXferLeft = pUrb->cbData;
+ ctxComplete.cTRB = pUrb->pHci->cTRB;
+ ctxComplete.uSlotID = uSlotID;
+ ctxComplete.uEpDCI = uEpDCI;
+ ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD.
+ ctxComplete.uLastCC = cc;
+ ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT;
+ xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP);
+ ep_ctx.last_cc = ctxComplete.uLastCC;
+ ep_ctx.trdp = uTRDP;
+
+ if (ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT)
+ ep_ctx.ifc -= XHCI_NO_QUEUING_IN_FLIGHT; /* TD fragment done, allow further queuing. */
+ else
+ ep_ctx.ifc--; /* TD done, decrement in-flight counter. */
+ }
+ else if (xfer.gen.type == XHCI_TRB_ISOCH)
+ {
+ XHCI_CTX_XFER_COMPLETE ctxComplete;
+ uint64_t uTRDP;
+ unsigned iPkt;
+
+ ctxComplete.pUrb = pUrb;
+ ctxComplete.uSlotID = uSlotID;
+ ctxComplete.uEpDCI = uEpDCI;
+
+ for (iPkt = 0; iPkt < pUrb->cIsocPkts; ++iPkt) {
+ ctxComplete.uXferPos = pUrb->aIsocPkts[iPkt].off;
+ ctxComplete.uXferLeft = pUrb->aIsocPkts[iPkt].cb;
+ ctxComplete.cTRB = pUrb->pHci->cTRB;
+ ctxComplete.uEDTLA = 0; // Zero at TD start.
+ ctxComplete.uLastCC = cc;
+ ctxComplete.fMaxCount = false;
+ if (pUrb->aIsocPkts[iPkt].enmStatus != VUSBSTATUS_OK)
+ STAM_COUNTER_INC(&pThis->StatErrorIsocPkts);
+ xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP);
+ ep_ctx.last_cc = ctxComplete.uLastCC;
+ ep_ctx.trdp = uTRDP;
+ xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx, &xfer, &GCPhysXfrTRB);
+ }
+ ep_ctx.ifc--; /* TD done, decrement in-flight counter. */
+ }
+ else if (xfer.gen.type == XHCI_TRB_SETUP_STG || xfer.gen.type == XHCI_TRB_DATA_STG || xfer.gen.type == XHCI_TRB_STATUS_STG)
+ {
+ XHCI_CTX_XFER_COMPLETE ctxComplete;
+ uint64_t uTRDP;
+
+ ctxComplete.pUrb = pUrb;
+ ctxComplete.uXferPos = 0;
+ ctxComplete.uXferLeft = pUrb->cbData;
+ ctxComplete.cTRB = pUrb->pHci->cTRB;
+ ctxComplete.uSlotID = uSlotID;
+ ctxComplete.uEpDCI = uEpDCI;
+ ctxComplete.uEDTLA = 0; // Always zero at the beginning of a new TD.
+ ctxComplete.uLastCC = cc;
+ ctxComplete.fMaxCount = ep_ctx.ifc >= XHCI_NO_QUEUING_IN_FLIGHT;
+ xhciR3WalkXferTrbChain(pDevIns, pThis, ep_ctx.trdp, xhciR3WalkDataTRBsComplete, &ctxComplete, &uTRDP);
+ ep_ctx.last_cc = ctxComplete.uLastCC;
+ ep_ctx.trdp = uTRDP;
+ }
+ else
+ {
+ AssertMsgFailed(("Unexpected TRB type %u\n", xfer.gen.type));
+ Log2(("TRB @ %RGp, type %u unexpected!\n", GCPhysXfrTRB, xfer.gen.type));
+ /* Advance the TRDP anyway so that the endpoint isn't completely stuck. */
+ ep_ctx.trdp += sizeof(XHCI_XFER_TRB);
+ }
+
+ /* Update the endpoint state. */
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, &ep_ctx);
+
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ {
+ /* Completion callback usually runs on a separate thread. Let the worker do more. */
+ Log2(("Ring bell for slot %u, DCI %u\n", uSlotID, uEpDCI));
+ ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], 1 << uEpDCI);
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_XFER_DONE, 0);
+ }
+}
+
+
+/**
+ * Handle transfer errors.
+ *
+ * VUSB calls this when a transfer attempt failed. This function will respond
+ * indicating whether to retry or complete the URB with failure.
+ *
+ * @returns true if the URB should be retired.
+ * @returns false if the URB should be re-tried.
+ * @param pInterface Pointer to XHCI::ROOTHUB::IRhPort.
+ * @param pUrb Pointer to the URB in question.
+ */
+static DECLCALLBACK(bool) xhciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PXHCI pThis = PDMDEVINS_2_DATA(pThisCC->pDevIns, PXHCI);
+ bool fRetire = true;
+
+ /* If the xHC isn't running, get out of here immediately. */
+ if (pThis->status & XHCI_STATUS_HCH)
+ {
+ Log(("xHC halted, skipping URB error handling\n"));
+ return fRetire;
+ }
+
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+
+ Assert(pUrb->pHci->cTRB); /* xHCI-generated URBs should not fail! */
+ if (!pUrb->pHci->cTRB)
+ {
+ Log(("%s: Failing xHCI-generated request!\n", pUrb->pszDesc));
+ }
+ else if (pUrb->enmStatus == VUSBSTATUS_STALL)
+ {
+ /* Don't retry on stall. */
+ Log2(("%s: xhciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc));
+ } else if (pUrb->enmStatus == VUSBSTATUS_CRC) {
+ /* Don't retry on CRC errors either. These indicate canceled URBs, among others. */
+ Log2(("%s: xhciR3RhXferError: CRC, giving up.\n", pUrb->pszDesc));
+ } else if (pUrb->enmStatus == VUSBSTATUS_DNR) {
+ /* Don't retry on DNR errors. These indicate the device vanished. */
+ Log2(("%s: xhciR3RhXferError: DNR, giving up.\n", pUrb->pszDesc));
+ } else if (pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) {
+ /* Don't retry on OVERRUN errors. These indicate a fatal error. */
+ Log2(("%s: xhciR3RhXferError: OVERRUN, giving up.\n", pUrb->pszDesc));
+ } else if (pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) {
+ /* Don't retry on UNDERRUN errors. These indicate a fatal error. */
+ Log2(("%s: xhciR3RhXferError: UNDERRUN, giving up.\n", pUrb->pszDesc));
+ } else {
+ /// @todo
+ AssertMsgFailed(("%#x\n", pUrb->enmStatus));
+ }
+
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ return fRetire;
+}
+
+
+/**
+ * Queue a TD composed of normal TRBs, event data TRBs, and suchlike.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param pRh Root hub for the device.
+ * @param GCPhysTRB Physical gues address of the TRB.
+ * @param pTrb Pointer to the contents of the first TRB.
+ * @param pEpCtx Pointer to the cached EP context.
+ * @param uSlotID ID of the associated slot context.
+ * @param uAddr The device address.
+ * @param uEpDCI The DCI(!) of the endpoint.
+ */
+static int xhciR3QueueDataTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB,
+ XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI)
+{
+ RT_NOREF(GCPhysTRB);
+ XHCI_CTX_XFER_PROBE ctxProbe;
+ XHCI_CTX_XFER_SUBMIT ctxSubmit;
+ uint64_t uTREP;
+ bool fFragOnly = false;
+ int rc;
+ VUSBXFERTYPE enmType;
+ VUSBDIRECTION enmDir;
+
+ /* Discover how big this TD is. */
+ RT_ZERO(ctxProbe);
+ rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP);
+ if (RT_SUCCESS(rc))
+ LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP));
+ else
+ {
+ LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP));
+ if (rc == VERR_TRY_AGAIN && pTrb->gen.type == XHCI_TRB_NORMAL && ctxProbe.cTRBLastED)
+ {
+ /* The TD is incomplete, but we have at least one TD fragment. We can create a URB for
+ * what we have but we can't safely queue any more because if any error occurs, the
+ * TD needs to fail as a whole.
+ * OS X Mavericks and Yosemite tend to trigger this case when reading from USB 3.0
+ * MSDs (transfers up to 1MB).
+ */
+ fFragOnly = true;
+
+ /* Because we currently do not maintain the EDTLA across URBs, we have to only submit
+ * TD fragments up to where we last saw an Event Data TRB. If there was no Event Data
+ * TRB, we'll just try waiting a bit longer for the TD to be complete or an Event Data
+ * TRB to show up. The guest is extremely likely to do one or the other, since otherwise
+ * it has no way to tell when the transfer completed.
+ */
+ ctxProbe.cTRB = ctxProbe.cTRBLastED;
+ ctxProbe.uXferLen = ctxProbe.uXfrLenLastED;
+ }
+ else
+ return rc;
+ }
+
+ /* Determine the transfer kind based on endpoint type. */
+ switch (pEpCtx->ep_type)
+ {
+ case XHCI_EPTYPE_BULK_IN:
+ case XHCI_EPTYPE_BULK_OUT:
+ enmType = VUSBXFERTYPE_BULK;
+ break;
+ case XHCI_EPTYPE_INTR_IN:
+ case XHCI_EPTYPE_INTR_OUT:
+ enmType = VUSBXFERTYPE_INTR;
+ break;
+ case XHCI_EPTYPE_CONTROL:
+ enmType = VUSBXFERTYPE_CTRL;
+ break;
+ case XHCI_EPTYPE_ISOCH_IN:
+ case XHCI_EPTYPE_ISOCH_OUT:
+ default:
+ enmType = VUSBXFERTYPE_INVALID;
+ AssertMsgFailed(("%#x\n", pEpCtx->ep_type));
+ }
+
+ /* Determine the direction based on endpoint type. */
+ switch (pEpCtx->ep_type)
+ {
+ case XHCI_EPTYPE_BULK_IN:
+ case XHCI_EPTYPE_INTR_IN:
+ enmDir = VUSBDIRECTION_IN;
+ break;
+ case XHCI_EPTYPE_BULK_OUT:
+ case XHCI_EPTYPE_INTR_OUT:
+ enmDir = VUSBDIRECTION_OUT;
+ break;
+ default:
+ enmDir = VUSBDIRECTION_INVALID;
+ AssertMsgFailed(("%#x\n", pEpCtx->ep_type));
+ }
+
+ /* Allocate and initialize a URB. */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, enmType, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB, NULL);
+ if (!pUrb)
+ return VERR_OUT_OF_RESOURCES; /// @todo handle error!
+
+ STAM_COUNTER_ADD(&pThis->StatTRBsPerDtaUrb, ctxProbe.cTRB);
+
+ /* See 4.5.1 about xHCI vs. USB endpoint addressing. */
+ Assert(uEpDCI);
+
+ pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */
+ pUrb->fShortNotOk = false; /* We detect short packets ourselves. */
+ pUrb->enmStatus = VUSBSTATUS_OK;
+
+ /// @todo Cross check that the EP type corresponds to direction. Probably
+ //should check when configuring device?
+ pUrb->pHci->uSlotID = uSlotID;
+
+ /* For OUT transfers, copy the TD data into the URB. */
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ ctxSubmit.pUrb = pUrb;
+ ctxSubmit.uXferPos = 0;
+ ctxSubmit.cTRB = 0;
+ xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP);
+ Assert(ctxProbe.cTRB == ctxSubmit.cTRB);
+ ctxProbe.cTRB = ctxSubmit.cTRB;
+ }
+
+ /* If only completing a fragment, remember the TRB count and increase
+ * the in-flight count past the limit so we won't queue any more.
+ */
+ pUrb->pHci->cTRB = ctxProbe.cTRB;
+ if (fFragOnly)
+ /* Bit of a hack -- prevent further queuing. */
+ pEpCtx->ifc += XHCI_NO_QUEUING_IN_FLIGHT;
+ else
+ /* Increment the in-flight counter before queuing more. */
+ pEpCtx->ifc++;
+
+ /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */
+ pEpCtx->trep = uTREP;
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx);
+
+ /*
+ * Submit the URB.
+ */
+ STAM_COUNTER_ADD(&pThis->StatUrbSizeData, pUrb->cbData);
+ Log(("%s: xhciR3QueueDataTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n",
+ pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources,
+ * or the user just ripped out the device.
+ */
+ /// @todo Mark the EP as halted and inactive and write back the changes.
+
+ return VERR_OUT_OF_RESOURCES;
+}
+
+
+/**
+ * Queue an isochronous TD composed of isochronous and normal TRBs, event
+ * data TRBs, and suchlike. This TD may either correspond to a single URB or
+ * form one packet of an isochronous URB.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param pRh Root hub for the device.
+ * @param GCPhysTRB Physical guest address of the TRB.
+ * @param pTrb Pointer to the contents of the first TRB.
+ * @param pEpCtx Pointer to the cached EP context.
+ * @param uSlotID ID of the associated slot context.
+ * @param uAddr The device address.
+ * @param uEpDCI The DCI(!) of the endpoint.
+ * @param pCtxIso Additional isochronous URB context.
+ */
+static int xhciR3QueueIsochTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB,
+ XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI,
+ XHCI_CTX_ISOCH *pCtxIso)
+{
+ RT_NOREF(GCPhysTRB, pTrb);
+ XHCI_CTX_XFER_PROBE ctxProbe;
+ XHCI_CTX_XFER_SUBMIT ctxSubmit;
+ uint64_t uTREP;
+ PVUSBURB pUrb;
+ unsigned cIsoPackets;
+ uint32_t cbPktMax;
+
+ /* Discover how big this TD is. */
+ RT_ZERO(ctxProbe);
+ xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP);
+ LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP));
+
+ /* See 4.5.1 about xHCI vs. USB endpoint addressing. */
+ Assert(uEpDCI);
+
+ /* For isochronous transfers, there's a bit of extra work to do. The interval
+ * is key and determines whether the TD will directly correspond to a URB or
+ * if it will only form part of a larger URB. In any case, one TD equals one
+ * 'packet' of an isochronous URB.
+ */
+ switch (pEpCtx->interval)
+ {
+ case 0: /* Every 2^0 * 125us, i.e. 8 per frame. */
+ cIsoPackets = 8;
+ break;
+ case 1: /* Every 2^1 * 125us, i.e. 4 per frame. */
+ cIsoPackets = 4;
+ break;
+ case 2: /* Every 2^2 * 125us, i.e. 2 per frame. */
+ cIsoPackets = 2;
+ break;
+ case 3: /* Every 2^3 * 125us, i.e. 1 per frame. */
+ default:/* Or any larger interval (every n frames).*/
+ cIsoPackets = 1;
+ break;
+ }
+
+ /* We do not know exactly how much data might be transferred until we
+ * look at all TDs/packets that constitute the URB. However, we do know
+ * the maximum possible size even without probing any TDs at all.
+ * The actual size is expected to be the same or at most slightly smaller,
+ * hence it makes sense to allocate the URB right away and copy data into
+ * it as we go, rather than doing complicated probing first.
+ * The Max Endpoint Service Interval Time (ESIT) Payload defines the
+ * maximum number of bytes that can be transferred per interval (4.14.2).
+ * Unfortunately Apple was lazy and their driver leaves the Max ESIT
+ * Payload as zero, so we have to do the math ourselves.
+ */
+
+ /* Calculate the maximum transfer size per (micro)frame. */
+ /// @todo This ought to be stored within the URB somewhere.
+ cbPktMax = pEpCtx->max_pkt_sz * (pEpCtx->max_brs_sz + 1) * (pEpCtx->mult + 1);
+ if (!pCtxIso->pUrb)
+ {
+ uint32_t cbUrbMax = cIsoPackets * cbPktMax;
+
+ /* Validate endpoint type. */
+ AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN || pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_OUT,
+ ("%#x\n", pEpCtx->ep_type));
+
+ /* Allocate and initialize a new URB. */
+ pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_ISOC,
+ (pEpCtx->ep_type == XHCI_EPTYPE_ISOCH_IN) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT,
+ cbUrbMax, ctxProbe.cTRB, NULL);
+ if (!pUrb)
+ return VERR_OUT_OF_RESOURCES; /// @todo handle error!
+
+ STAM_COUNTER_ADD(&pThis->StatTRBsPerIsoUrb, ctxProbe.cTRB);
+
+ LogFlowFunc(("Allocated URB with %u packets, %u bytes total (ESIT payload %u)\n", cIsoPackets, cbUrbMax, cbPktMax));
+
+ pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */
+ pUrb->fShortNotOk = false; /* We detect short packets ourselves. */
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->cIsocPkts = cIsoPackets;
+ pUrb->pHci->uSlotID = uSlotID;
+ pUrb->pHci->cTRB = ctxProbe.cTRB;
+
+ /* If TRB says so or if there are multiple packets per interval, don't even
+ * bother with frame counting and schedule everything ASAP.
+ */
+ if (pTrb->isoc.sia || cIsoPackets != 1)
+ pUrb->uStartFrameDelta = 0;
+ else
+ {
+ uint16_t uFrameDelta;
+ uint32_t uPort;
+
+ /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after
+ * writing back the EP state so that the completion callback can operate.
+ */
+ if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, NULL, &uPort)))
+ {
+
+ uFrameDelta = pRh->pIRhConn->pfnUpdateIsocFrameDelta(pRh->pIRhConn, uPort, uEpDCI / 2,
+ uEpDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT,
+ pTrb->isoc.frm_id, XHCI_FRAME_ID_BITS);
+ pUrb->uStartFrameDelta = uFrameDelta;
+ Log(("%s: Isoch frame delta set to %u\n", pUrb->pszDesc, uFrameDelta));
+ }
+ else
+ {
+ Log(("%s: Failed to find device for slot! Setting frame delta to zero.\n", pUrb->pszDesc));
+ pUrb->uStartFrameDelta = 0;
+ }
+ }
+
+ Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u FrmID=%u Isoch URB created\n",
+ pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData, pTrb->isoc.frm_id));
+
+ /* Set up the context for later use. */
+ pCtxIso->pUrb = pUrb;
+ /* Save the current TREP in case we need to rewind. */
+ pCtxIso->uInitTREP = pEpCtx->trep;
+ }
+ else
+ {
+ Assert(cIsoPackets > 1);
+ /* Grab the URB we initialized earlier. */
+ pUrb = pCtxIso->pUrb;
+ }
+
+ /* Set up the packet corresponding to this TD. */
+ pUrb->aIsocPkts[pCtxIso->iPkt].cb = RT_MIN(ctxProbe.uXferLen, cbPktMax);
+ pUrb->aIsocPkts[pCtxIso->iPkt].off = pCtxIso->offCur;
+ pUrb->aIsocPkts[pCtxIso->iPkt].enmStatus = VUSBSTATUS_NOT_ACCESSED;
+
+ /* For OUT transfers, copy the TD data into the URB. */
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ ctxSubmit.pUrb = pUrb;
+ ctxSubmit.uXferPos = pCtxIso->offCur;
+ ctxSubmit.cTRB = 0;
+ xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP);
+ Assert(ctxProbe.cTRB == ctxSubmit.cTRB);
+ }
+
+ /* Done preparing this packet. */
+ Assert(pCtxIso->iPkt < 8);
+ pCtxIso->iPkt++;
+ pCtxIso->offCur += ctxProbe.uXferLen;
+ Assert(pCtxIso->offCur <= pUrb->cbData);
+
+ /* Increment the in-flight counter before queuing more. */
+ if (pCtxIso->iPkt == pUrb->cIsocPkts)
+ pEpCtx->ifc++;
+
+ /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */
+ pEpCtx->trep = uTREP;
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx);
+
+ /* If the URB is complete, submit it. */
+ if (pCtxIso->iPkt == pUrb->cIsocPkts)
+ {
+ /* Change cbData to reflect how much data should be transferred. This can differ
+ * from how much data was allocated for the URB.
+ */
+ pUrb->cbData = pCtxIso->offCur;
+ STAM_COUNTER_ADD(&pThis->StatUrbSizeIsoc, pUrb->cbData);
+ Log(("%s: Addr=%u, EndPt=%u, enmDir=%u cIsocPkts=%u cbData=%u Isoch URB being submitted\n",
+ pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cIsocPkts, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ if (RT_FAILURE(rc))
+ {
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources,
+ * or the user just ripped out the device.
+ */
+ pCtxIso->fSubmitFailed = true;
+ /// @todo Mark the EP as halted and inactive and write back the changes.
+ return VERR_OUT_OF_RESOURCES;
+ }
+ /* Clear the isochronous URB context. */
+ RT_ZERO(*pCtxIso);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Queue a control TD composed of setup/data/status stage TRBs, event data
+ * TRBs, and suchlike.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param pRh Root hub for the device.
+ * @param GCPhysTRB Physical guest address of th TRB.
+ * @param pTrb Pointer to the contents of the first TRB.
+ * @param pEpCtx Pointer to the cached EP context.
+ * @param uSlotID ID of the associated slot context.
+ * @param uAddr The device address.
+ * @param uEpDCI The DCI(!) of the endpoint.
+ */
+static int xhciR3QueueControlTD(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, PXHCIROOTHUBR3 pRh, RTGCPHYS GCPhysTRB,
+ XHCI_XFER_TRB *pTrb, XHCI_EP_CTX *pEpCtx, uint8_t uSlotID, uint8_t uAddr, uint8_t uEpDCI)
+{
+ RT_NOREF(GCPhysTRB);
+ XHCI_CTX_XFER_PROBE ctxProbe;
+ XHCI_CTX_XFER_SUBMIT ctxSubmit;
+ uint64_t uTREP;
+ int rc;
+ VUSBDIRECTION enmDir;
+
+ /* Discover how big this TD is. */
+ RT_ZERO(ctxProbe);
+ rc = xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsProbe, &ctxProbe, &uTREP);
+ if (RT_SUCCESS(rc))
+ LogFlowFunc(("Probed %u TRBs, %u bytes total, TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, uTREP));
+ else
+ {
+ LogFlowFunc(("Probing failed after %u TRBs, %u bytes total (last ED after %u TRBs and %u bytes), TREP@%RX64\n", ctxProbe.cTRB, ctxProbe.uXferLen, ctxProbe.cTRBLastED, ctxProbe.uXfrLenLastED, uTREP));
+ return rc;
+ }
+
+ /* Determine the transfer direction. */
+ switch (pTrb->gen.type)
+ {
+ case XHCI_TRB_SETUP_STG:
+ enmDir = VUSBDIRECTION_SETUP;
+ /* For setup TRBs, there is always 8 bytes of immediate data. */
+ Assert(sizeof(VUSBSETUP) == 8);
+ Assert(ctxProbe.uXferLen == 8);
+ Log2(("bmRequestType:%02X bRequest:%02X wValue:%04X wIndex:%04X wLength:%04X\n", pTrb->setup.bmRequestType,
+ pTrb->setup.bRequest, pTrb->setup.wValue, pTrb->setup.wIndex, pTrb->setup.wLength));
+ break;
+ case XHCI_TRB_STATUS_STG:
+ enmDir = pTrb->status.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ break;
+ case XHCI_TRB_DATA_STG:
+ enmDir = pTrb->data.dir ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ break;
+ default:
+ AssertMsgFailed(("%#x\n", pTrb->gen.type)); /* Can't happen unless caller messed up. */
+ return VERR_INTERNAL_ERROR;
+ }
+
+ /* Allocate and initialize a URB. */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, uAddr, VUSB_DEVICE_PORT_INVALID, VUSBXFERTYPE_CTRL, enmDir, ctxProbe.uXferLen, ctxProbe.cTRB,
+ NULL);
+ if (!pUrb)
+ return VERR_OUT_OF_RESOURCES; /// @todo handle error!
+
+ STAM_COUNTER_ADD(&pThis->StatTRBsPerCtlUrb, ctxProbe.cTRB);
+
+ /* See 4.5.1 about xHCI vs. USB endpoint addressing. */
+ Assert(uEpDCI);
+
+ /* This had better be a control endpoint. */
+ AssertMsg(pEpCtx->ep_type == XHCI_EPTYPE_CONTROL, ("%#x\n", pEpCtx->ep_type));
+
+ pUrb->EndPt = uEpDCI / 2; /* DCI = EP * 2 + direction */
+ pUrb->fShortNotOk = false; /* We detect short packets ourselves. */
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->uSlotID = uSlotID;
+
+ /* For OUT/SETUP transfers, copy the TD data into the URB. */
+ if (pUrb->enmDir == VUSBDIRECTION_OUT || pUrb->enmDir == VUSBDIRECTION_SETUP)
+ {
+ ctxSubmit.pUrb = pUrb;
+ ctxSubmit.uXferPos = 0;
+ ctxSubmit.cTRB = 0;
+ xhciR3WalkXferTrbChain(pDevIns, pThis, pEpCtx->trep, xhciR3WalkDataTRBsSubmit, &ctxSubmit, &uTREP);
+ Assert(ctxProbe.cTRB == ctxSubmit.cTRB);
+ ctxProbe.cTRB = ctxSubmit.cTRB;
+ }
+
+ pUrb->pHci->cTRB = ctxProbe.cTRB;
+
+ /* Commit the updated TREP; submitting the URB may already invoke completion callbacks. */
+ pEpCtx->trep = uTREP;
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uEpDCI, pEpCtx);
+
+ /*
+ * Submit the URB.
+ */
+ STAM_COUNTER_ADD(&pThis->StatUrbSizeCtrl, pUrb->cbData);
+ Log(("%s: xhciR3QueueControlTD: Addr=%u, EndPt=%u, enmDir=%u cbData=%u\n",
+ pUrb->pszDesc, pUrb->DstAddress, pUrb->EndPt, pUrb->enmDir, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ if (RT_SUCCESS(rc))
+ return VINF_SUCCESS;
+
+ /* Failure cleanup. Can happen if we're still resetting the device or out of resources,
+ * or the user just ripped out the device.
+ */
+ /// @todo Mark the EP as halted and inactive and write back the changes.
+
+ return VERR_OUT_OF_RESOURCES;
+}
+
+
+/**
+ * Process a device context (transfer data).
+ *
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param uSlotID Slot/doorbell which had been rung.
+ * @param uDBVal Value written to the doorbell.
+ */
+static int xhciR3ProcessDevCtx(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint32_t uDBVal)
+{
+ uint8_t uDBTarget = uDBVal & XHCI_DB_TGT_MASK;
+ XHCI_CTX_ISOCH ctxIsoch = {0};
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_EP_CTX ep_ctx;
+ XHCI_XFER_TRB xfer;
+ RTGCPHYS GCPhysXfrTRB;
+ PXHCIROOTHUBR3 pRh;
+ bool dcs;
+ bool fContinue = true;
+ int rc;
+ unsigned cTrbs = 0;
+
+ LogFlowFunc(("Slot ID: %u, DB target %u, DB stream ID %u\n", uSlotID, uDBTarget, (uDBVal & XHCI_DB_STRMID_MASK) >> XHCI_DB_STRMID_SHIFT));
+ Assert(uSlotID > 0);
+ Assert(uSlotID <= XHCI_NDS);
+ /// @todo report errors for bogus DB targets
+ Assert(uDBTarget > 0);
+ Assert(uDBTarget < 32);
+
+ /// @todo Check for aborts and the like?
+
+ /* Load the slot and endpoint contexts. */
+ xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx);
+ /// @todo sanity check the context in here?
+
+ /* Select the root hub corresponding to the port. */
+ pRh = GET_PORT_PRH(pThisCC, ID_TO_IDX(slot_ctx.rh_port));
+
+ /* Stopped endpoints automatically transition to running state. */
+ if (RT_UNLIKELY(ep_ctx.ep_state == XHCI_EPST_STOPPED))
+ {
+ Log(("EP DCI %u stopped -> running\n", uDBTarget));
+ ep_ctx.ep_state = XHCI_EPST_RUNNING;
+ /* Update EP right here. Theoretically could be postponed, but we
+ * must ensure that the EP does get written back even if there is
+ * no other work to do.
+ */
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx);
+ }
+
+ /* If the EP isn't running, get outta here. */
+ if (RT_UNLIKELY(ep_ctx.ep_state != XHCI_EPST_RUNNING))
+ {
+ Log2(("EP DCI %u not running (state %u), bail!\n", uDBTarget, ep_ctx.ep_state));
+ return VINF_SUCCESS;
+ }
+
+ /* Get any non-transfer TRBs out of the way. */
+ xhciR3ConsumeNonXferTRBs(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx, &xfer, &GCPhysXfrTRB);
+ /// @todo This is inefficient.
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx);
+
+ do
+ {
+ /* Fetch the contexts again and find the TRB address at enqueue point. */
+ xhciR3FetchCtxAndEp(pDevIns, pThis, uSlotID, uDBTarget, &slot_ctx, &ep_ctx);
+ GCPhysXfrTRB = ep_ctx.trep & XHCI_TRDP_ADDR_MASK;
+ dcs = !!(ep_ctx.trep & XHCI_TRDP_DCS_MASK);
+ LogFlowFunc(("Processing Transfer Ring, TREP: %RGp\n", GCPhysXfrTRB));
+
+ /* Fetch the transfer TRB. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &xfer, sizeof(xfer));
+
+ /* Make sure the Cycle State matches. */
+ if ((bool)xfer.gen.cycle == dcs)
+ {
+ Log2(("TRB @ %RGp, type %u (%s) %u bytes ENT=%u ISP=%u NS=%u CH=%u IOC=%u IDT=%u\n", GCPhysXfrTRB, xfer.gen.type,
+ xfer.gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[xfer.gen.type] : "WHAT?!!",
+ xfer.gen.xfr_len, xfer.gen.ent, xfer.gen.isp, xfer.gen.ns, xfer.gen.ch, xfer.gen.ioc, xfer.gen.idt));
+
+ /* If there is an "in-flight" TRDP, check if we need to wait until the transfer completes. */
+ if ((ep_ctx.trdp & XHCI_TRDP_ADDR_MASK) != GCPhysXfrTRB)
+ {
+ switch (xfer.gen.type) {
+ case XHCI_TRB_ISOCH:
+ if (ep_ctx.ifc >= XHCI_MAX_ISOC_IN_FLIGHT)
+ {
+ Log(("%u isoch URBs in flight, backing off\n", ep_ctx.ifc));
+ fContinue = false;
+ break;
+ }
+ RT_FALL_THRU();
+ case XHCI_TRB_LINK:
+ Log2(("TRB OK, continuing @ %RX64\n", GCPhysXfrTRB));
+ break;
+ case XHCI_TRB_NORMAL:
+ if (XHCI_EP_XTYPE(ep_ctx.ep_type) != XHCI_XFTYPE_BULK)
+ {
+ Log2(("Normal TRB not bulk, not continuing @ %RX64\n", GCPhysXfrTRB));
+ fContinue = false;
+ break;
+ }
+ if (ep_ctx.ifc >= XHCI_MAX_BULK_IN_FLIGHT)
+ {
+ Log(("%u normal URBs in flight, backing off\n", ep_ctx.ifc));
+ fContinue = false;
+ break;
+ }
+ Log2(("Bulk TRB OK, continuing @ %RX64\n", GCPhysXfrTRB));
+ break;
+ case XHCI_TRB_EVT_DATA:
+ case XHCI_TRB_NOOP_XFER:
+ Log2(("TRB not OK, not continuing @ %RX64\n", GCPhysXfrTRB));
+ fContinue = false;
+ break;
+ default:
+ Log2(("Some other TRB (type %u), not continuing @ %RX64\n", xfer.gen.type, GCPhysXfrTRB));
+ fContinue = false;
+ break;
+ }
+ }
+ if (!fContinue)
+ break;
+
+ switch (xfer.gen.type) {
+ case XHCI_TRB_NORMAL:
+ Log(("Normal TRB: Ptr=%RGp IOC=%u CH=%u\n", xfer.norm.data_ptr, xfer.norm.ioc, xfer.norm.ch));
+ rc = xhciR3QueueDataTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID,
+ slot_ctx.dev_addr, uDBTarget);
+ break;
+ case XHCI_TRB_SETUP_STG:
+ Log(("Setup stage TRB: IOC=%u IDT=%u\n", xfer.setup.ioc, xfer.setup.idt));
+ rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID,
+ slot_ctx.dev_addr, uDBTarget);
+ break;
+ case XHCI_TRB_DATA_STG:
+ Log(("Data stage TRB: Ptr=%RGp IOC=%u CH=%u DIR=%u\n", xfer.data.data_ptr, xfer.data.ioc, xfer.data.ch, xfer.data.dir));
+ rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID,
+ slot_ctx.dev_addr, uDBTarget);
+ break;
+ case XHCI_TRB_STATUS_STG:
+ Log(("Status stage TRB: IOC=%u CH=%u DIR=%u\n", xfer.status.ioc, xfer.status.ch, xfer.status.dir));
+ rc = xhciR3QueueControlTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID,
+ slot_ctx.dev_addr, uDBTarget);
+ break;
+ case XHCI_TRB_ISOCH:
+ Log(("Isoch TRB: Ptr=%RGp IOC=%u CH=%u TLBPC=%u TBC=%u SIA=%u FrmID=%u\n", xfer.isoc.data_ptr, xfer.isoc.ioc, xfer.isoc.ch, xfer.isoc.tlbpc, xfer.isoc.tbc, xfer.isoc.sia, xfer.isoc.frm_id));
+ rc = xhciR3QueueIsochTD(pDevIns, pThis, pThisCC, pRh, GCPhysXfrTRB, &xfer, &ep_ctx, uSlotID,
+ slot_ctx.dev_addr, uDBTarget, &ctxIsoch);
+ break;
+ case XHCI_TRB_LINK:
+ Log2(("Link extra-TD: Ptr=%RGp IOC=%u TC=%u CH=%u\n", xfer.link.rseg_ptr, xfer.link.ioc, xfer.link.toggle, xfer.link.chain));
+ Assert(!xfer.link.chain);
+ /* Set new TREP but leave DCS bit alone... */
+ ep_ctx.trep = (xfer.link.rseg_ptr & XHCI_TRDP_ADDR_MASK) | (ep_ctx.trep & XHCI_TRDP_DCS_MASK);
+ /* ...and flip the DCS bit if required. Then update the TREP. */
+ if (xfer.link.toggle)
+ ep_ctx.trep = (ep_ctx.trep & ~XHCI_TRDP_DCS_MASK) | (ep_ctx.trep ^ XHCI_TRDP_DCS_MASK);
+ rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx);
+ break;
+ case XHCI_TRB_NOOP_XFER:
+ Log2(("No op xfer: IOC=%u CH=%u ENT=%u\n", xfer.nop.ioc, xfer.nop.ch, xfer.nop.ent));
+ /* A no-op transfer TRB must not be part of a chain. See 4.11.7. */
+ Assert(!xfer.link.chain);
+ /* Update enqueue pointer (TRB was not yet completed). */
+ ep_ctx.trep += sizeof(XHCI_XFER_TRB);
+ rc = xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx);
+ break;
+ default:
+ Log(("Unsupported TRB!!\n"));
+ rc = VERR_NOT_SUPPORTED;
+ break;
+ }
+ /* If queuing failed, stop right here. */
+ if (RT_FAILURE(rc))
+ fContinue = false;
+ }
+ else
+ {
+ LogFunc(("Transfer Ring empty\n"));
+ fContinue = false;
+
+ /* If an isochronous ring is empty, this is an overrun/underrun. At this point
+ * the ring will no longer be scheduled (until the doorbell is rung again)
+ * but it remains in the Running state. This error is only reported if someone
+ * rang the doorbell and there are no TDs available or in-flight.
+ */
+ if ( (ep_ctx.trep == ep_ctx.trdp) /* Nothing in-flight? */
+ && (ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN || ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_OUT))
+ {
+ /* There is no TRB associated with this error; the slot context
+ * determines the interrupter.
+ */
+ Log(("Isochronous ring %s, TRDP:%RGp\n", ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? "overrun" : "underrun", ep_ctx.trdp & XHCI_TRDP_ADDR_MASK));
+ rc = xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0,
+ ep_ctx.ep_type == XHCI_EPTYPE_ISOCH_IN ? XHCI_TCC_RING_OVERRUN : XHCI_TCC_RING_UNDERRUN,
+ uSlotID, uDBTarget, 0, false, false);
+ }
+
+ }
+
+ /* Kill the xHC if the TRB list has no end in sight. */
+ if (++cTrbs > XHCI_MAX_NUM_TRBS)
+ {
+ /* Stop the xHC with an error. */
+ xhciR3EndlessTrbError(pDevIns, pThis);
+
+ /* Get out of the loop. */
+ fContinue = false;
+ rc = VERR_NOT_SUPPORTED; /* No good error code really... */
+ }
+ } while (fContinue);
+
+ /* It can unfortunately happen that for endpoints with more than one
+ * transfer per USB frame, there won't be a complete multi-packet URB ready
+ * when we go looking for it. If that happens, we'll "rewind" the TREP and
+ * try again later. Since the URB construction is done under a lock, this
+ * is safe as we won't be accessing the endpoint concurrently.
+ */
+ if (ctxIsoch.pUrb)
+ {
+ Log(("Unfinished ISOC URB (%u packets out of %u)!\n", ctxIsoch.iPkt, ctxIsoch.pUrb->cIsocPkts));
+ /* If submitting failed, the URB is already freed. */
+ if (!ctxIsoch.fSubmitFailed)
+ VUSBIRhFreeUrb(pRh->pIRhConn, ctxIsoch.pUrb);
+ ep_ctx.trep = ctxIsoch.uInitTREP;
+ xhciR3WriteBackEp(pDevIns, pThis, uSlotID, uDBTarget, &ep_ctx);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A worker routine for Address Device command. Builds a URB containing
+ * a SET_ADDRESS requests and (synchronously) submits it to VUSB, then
+ * follows up with a status stage URB.
+ *
+ * @returns true on success.
+ * @returns false on failure to submit.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param uSlotID Slot ID to assign address to.
+ * @param uDevAddr New device address.
+ * @param iPort The xHCI root hub port index.
+ */
+static bool xhciR3IssueSetAddress(PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDevAddr, unsigned iPort)
+{
+ PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort);
+
+ Assert(uSlotID);
+ LogFlowFunc(("Slot %u port idx %u: new address is %u\n", uSlotID, iPort, uDevAddr));
+
+ /* For USB3 devices, force the port number. This simulates the fact that USB3 uses directed (unicast) traffic. */
+ if (!IS_USB3_PORT_IDX_R3(pThisCC, iPort))
+ iPort = VUSB_DEVICE_PORT_INVALID;
+ else
+ iPort = GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort);
+
+ /* Allocate and initialize a URB. NB: Zero cTds indicates a URB not submitted by guest. */
+ PVUSBURB pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_SETUP,
+ sizeof(VUSBSETUP), 0 /* cTds */, NULL);
+ if (!pUrb)
+ return false;
+
+ pUrb->EndPt = 0;
+ pUrb->fShortNotOk = true;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->uSlotID = uSlotID;
+ pUrb->pHci->cTRB = 0;
+
+ /* Build the request. */
+ PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData;
+ pSetup->bmRequestType = VUSB_DIR_TO_DEVICE | VUSB_REQ_STANDARD | VUSB_TO_DEVICE;
+ pSetup->bRequest = VUSB_REQ_SET_ADDRESS;
+ pSetup->wValue = uDevAddr;
+ pSetup->wIndex = 0;
+ pSetup->wLength = 0;
+
+ /* NB: We assume the address assignment is a synchronous operation. */
+
+ /* Submit the setup URB. */
+ Log(("%s: xhciSetAddress setup: cbData=%u\n", pUrb->pszDesc, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ int rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ if (RT_FAILURE(rc))
+ {
+ Log(("xhciSetAddress: setup stage failed pUrb=%p!!\n", pUrb));
+ return false;
+ }
+
+ /* To complete the SET_ADDRESS request, the status stage must succeed. */
+ pUrb = VUSBIRhNewUrb(pRh->pIRhConn, 0 /* address */, iPort, VUSBXFERTYPE_CTRL, VUSBDIRECTION_IN, 0 /* cbData */, 0 /* cTds */,
+ NULL);
+ if (!pUrb)
+ return false;
+
+ pUrb->EndPt = 0;
+ pUrb->fShortNotOk = true;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pUrb->pHci->uSlotID = uSlotID;
+ pUrb->pHci->cTRB = 0;
+
+ /* Submit the setup URB. */
+ Log(("%s: xhciSetAddress status: cbData=%u\n", pUrb->pszDesc, pUrb->cbData));
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ rc = VUSBIRhSubmitUrb(pRh->pIRhConn, pUrb, &pRh->Led);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ if (RT_FAILURE(rc))
+ {
+ Log(("xhciSetAddress: status stage failed pUrb=%p!!\n", pUrb));
+ return false;
+ }
+
+ Log(("xhciSetAddress: set address succeeded\n"));
+ return true;
+}
+
+
+/**
+ * Address a device.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param uInpCtxAddr Address of the input context.
+ * @param uSlotID Slot ID to assign address to.
+ * @param fBSR Block Set address Request flag.
+ */
+static unsigned xhciR3AddressDevice(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint64_t uInpCtxAddr,
+ uint8_t uSlotID, bool fBSR)
+{
+ RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK;
+ RTGCPHYS GCPhysInpSlot;
+ RTGCPHYS GCPhysOutSlot;
+ XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */
+ XHCI_SLOT_CTX inp_slot_ctx; /* Input Slot Context (ICI=1). */
+ XHCI_EP_CTX ep_ctx; /* Endpoint Context (ICI=2+). */
+ XHCI_SLOT_CTX out_slot_ctx; /* Output Slot Context. */
+ uint8_t dev_addr;
+ unsigned cc = XHCI_TCC_SUCCESS;
+
+ Assert(GCPhysInpCtx);
+ Assert(uSlotID);
+ LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx));
+
+ /* Determine the address of the output slot context. */
+ GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+
+ /* Fetch the output slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx));
+
+ /// @todo Check for valid context (6.2.2.1, 6.2.3.1)
+
+ /* See 4.6.5 */
+ do {
+ /* Parameter validation depends on whether the BSR flag is set or not. */
+ if (fBSR)
+ {
+ /* Check that the output slot context state is in Enabled state. */
+ if (out_slot_ctx.slot_state >= XHCI_SLTST_DEFAULT)
+ {
+ Log(("Output slot context state (%u) wrong (BSR)!\n", out_slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+ dev_addr = 0;
+ }
+ else
+ {
+ /* Check that the output slot context state is in Enabled or Default state. */
+ if (out_slot_ctx.slot_state > XHCI_SLTST_DEFAULT)
+ {
+ Log(("Output slot context state (%u) wrong (no-BSR)!\n", out_slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+ dev_addr = xhciR3SelectNewAddress(pThis, uSlotID);
+ }
+
+ /* Fetch the input control context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc));
+ Assert(icc.add_flags == (RT_BIT(0) | RT_BIT(1))); /* Should have been already checked. */
+ Assert(!icc.drop_flags);
+
+ /* Calculate the address of the input slot context (ICI=1/DCI=0). */
+ GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX);
+
+ /* Read the input slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &inp_slot_ctx, sizeof(inp_slot_ctx));
+
+ /* If BSR isn't set, issue the actual SET_ADDRESS request. */
+ if (!fBSR) {
+ unsigned iPort;
+
+ /* We have to dig out the port number/index to determine which virtual root hub to use. */
+ iPort = ID_TO_IDX(inp_slot_ctx.rh_port);
+ if (iPort >= XHCI_NDP_CFG(pThis))
+ {
+ Log(("Port out of range (index %u)!\n", iPort));
+ cc = XHCI_TCC_USB_XACT_ERR;
+ break;
+ }
+ if (!xhciR3IssueSetAddress(pThisCC, uSlotID, dev_addr, iPort))
+ {
+ Log(("SET_ADDRESS failed!\n"));
+ cc = XHCI_TCC_USB_XACT_ERR;
+ break;
+ }
+ }
+
+ /* Copy the slot context with appropriate modifications. */
+ out_slot_ctx = inp_slot_ctx;
+ if (fBSR)
+ out_slot_ctx.slot_state = XHCI_SLTST_DEFAULT;
+ else
+ out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED;
+ out_slot_ctx.dev_addr = dev_addr;
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx));
+
+ /* Point at the EP0 contexts. */
+ GCPhysInpSlot += sizeof(inp_slot_ctx);
+ GCPhysOutSlot += sizeof(out_slot_ctx);
+
+ /* Copy EP0 context with appropriate modifications. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &ep_ctx, sizeof(ep_ctx));
+ xhciR3EnableEP(&ep_ctx);
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &ep_ctx, sizeof(ep_ctx));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Reset a halted endpoint.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uSlotID Slot ID to work with.
+ * @param uDCI DCI of the endpoint to reset.
+ * @param fTSP The Transfer State Preserve flag.
+ */
+static unsigned xhciR3ResetEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, bool fTSP)
+{
+ RT_NOREF(fTSP);
+ RTGCPHYS GCPhysSlot;
+ RTGCPHYS GCPhysEndp;
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_EP_CTX endp_ctx;
+ unsigned cc = XHCI_TCC_SUCCESS;
+
+ Assert(uSlotID);
+
+ /* Determine the addresses of the contexts. */
+ GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX);
+
+ /* Fetch the slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx));
+
+ /* See 4.6.8 */
+ do {
+ /* Check that the slot context state is Default, Addressed, or Configured. */
+ if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT)
+ {
+ Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Fetch the endpoint context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ /* Check that the endpoint context state is Halted. */
+ if (endp_ctx.ep_state != XHCI_EPST_HALTED)
+ {
+ Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Transition EP state. */
+ endp_ctx.ep_state = XHCI_EPST_STOPPED;
+
+ /// @todo What can we do with the TSP flag?
+ /// @todo Anything to do WRT enabling the corresponding doorbell register?
+
+ /* Write back the updated endpoint context. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Stop a running endpoint.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis The xHCI device state, shared edition.
+ * @param pThisCC The xHCI device state, ring-3 edition.
+ * @param uSlotID Slot ID to work with.
+ * @param uDCI DCI of the endpoint to stop.
+ * @param fTSP The Suspend flag.
+ */
+static unsigned xhciR3StopEndpoint(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, uint8_t uSlotID, uint8_t uDCI, bool fTSP)
+{
+ RT_NOREF(fTSP);
+ RTGCPHYS GCPhysSlot;
+ RTGCPHYS GCPhysEndp;
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_EP_CTX endp_ctx;
+ unsigned cc = XHCI_TCC_SUCCESS;
+
+ Assert(uSlotID);
+
+ /* Determine the addresses of the contexts. */
+ GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX);
+
+ /* Fetch the slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx));
+
+ /* See 4.6.9 */
+ do {
+ /* Check that the slot context state is Default, Addressed, or Configured. */
+ if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT)
+ {
+ Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* The doorbell could be ringing; stop it if so. */
+ if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << uDCI))
+ {
+ Log(("Unring bell for slot ID %u, DCI %u\n", uSlotID, uDCI));
+ ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDCI));
+ }
+
+ /* Fetch the endpoint context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ /* Check that the endpoint context state is Running. */
+ if (endp_ctx.ep_state != XHCI_EPST_RUNNING)
+ {
+ Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Transition EP state. */
+ endp_ctx.ep_state = XHCI_EPST_STOPPED;
+
+ /* Write back the updated endpoint context *now*, before actually canceling anyhing. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ /// @todo What can we do with the SP flag?
+
+ PXHCIROOTHUBR3 pRh;
+ uint32_t uPort;
+
+ /* Abort the endpoint, i.e. cancel any outstanding URBs. This needs to be done after
+ * writing back the EP state so that the completion callback can operate.
+ */
+ if (RT_SUCCESS(xhciR3FindRhDevBySlot(pDevIns, pThis, pThisCC, uSlotID, &pRh, &uPort)))
+ {
+ /* Temporarily give up the lock so that the completion callbacks can run. */
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+ Log(("Aborting DCI %u -> ep=%u d=%u\n", uDCI, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT));
+ pRh->pIRhConn->pfnAbortEp(pRh->pIRhConn, uPort, uDCI / 2, uDCI & 1 ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT);
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+ }
+
+ /// @todo The completion callbacks should do more work for canceled URBs.
+ /* Once the completion callbacks had a chance to run, we have to adjust
+ * the endpoint state.
+ * NB: The guest may just ring the doorbell to continue and not execute
+ * 'Set TRDP' after stopping the endpoint.
+ */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ bool fXferWasInProgress = endp_ctx.trep != endp_ctx.trdp;
+
+ /* Reset the TREP, but the EDTLA should be left alone. */
+ endp_ctx.trep = endp_ctx.trdp;
+
+ if (fXferWasInProgress)
+ {
+ /* Fetch the transfer TRB to see the length. */
+ RTGCPHYS GCPhysXfrTRB = endp_ctx.trdp & XHCI_TRDP_ADDR_MASK;
+ XHCI_XFER_TRB XferTRB;
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysXfrTRB, &XferTRB, sizeof(XferTRB));
+
+ xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, XferTRB.gen.xfr_len, XHCI_TCC_STOPPED, uSlotID, uDCI,
+ GCPhysXfrTRB, false, false);
+ }
+ else
+ {
+ /* We need to generate a Force Stopped Event or FSE. Note that FSEs were optional
+ * in xHCI 0.96 but aren't in 1.0.
+ */
+ xhciR3PostXferEvent(pDevIns, pThis, slot_ctx.intr_tgt, 0, XHCI_TCC_STP_INV_LEN, uSlotID, uDCI,
+ endp_ctx.trdp & XHCI_TRDP_ADDR_MASK, false, false);
+ }
+
+ /* Write back the updated endpoint context again. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Set a new TR Dequeue Pointer for an endpoint.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uSlotID Slot ID to work with.
+ * @param uDCI DCI of the endpoint to reset.
+ * @param uTRDP The TRDP including DCS/ flag.
+ */
+static unsigned xhciR3SetTRDP(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID, uint8_t uDCI, uint64_t uTRDP)
+{
+ RTGCPHYS GCPhysSlot;
+ RTGCPHYS GCPhysEndp;
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_EP_CTX endp_ctx;
+ unsigned cc = XHCI_TCC_SUCCESS;
+
+ Assert(uSlotID);
+
+ /* Determine the addresses of the contexts. */
+ GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ GCPhysEndp = GCPhysSlot + uDCI * sizeof(XHCI_EP_CTX);
+
+ /* Fetch the slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx));
+
+ /* See 4.6.10 */
+ do {
+ /* Check that the slot context state is Default, Addressed, or Configured. */
+ if (slot_ctx.slot_state < XHCI_SLTST_DEFAULT)
+ {
+ Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Fetch the endpoint context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+
+ /* Check that the endpoint context state is Stopped or Error. */
+ if (endp_ctx.ep_state != XHCI_EPST_STOPPED && endp_ctx.ep_state != XHCI_EPST_ERROR)
+ {
+ Log(("Endpoint context state wrong (%u)!\n", endp_ctx.ep_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Update the TRDP/TREP and DCS. */
+ endp_ctx.trdp = uTRDP;
+ endp_ctx.trep = uTRDP;
+
+ /* Also clear the in-flight counter! */
+ endp_ctx.ifc = 0;
+
+ /// @todo Handle streams
+
+ /* Write back the updated endpoint context. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysEndp, &endp_ctx, sizeof(endp_ctx));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Prepare for a device reset.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uSlotID Slot ID to work with.
+ */
+static unsigned xhciR3ResetDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uSlotID)
+{
+ RTGCPHYS GCPhysSlot;
+ XHCI_SLOT_CTX slot_ctx;
+ XHCI_DEV_CTX dc;
+ unsigned num_ctx;
+ unsigned i;
+ unsigned cc = XHCI_TCC_SUCCESS;
+
+ Assert(uSlotID);
+
+ /* Determine the address of the slot/device context. */
+ GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+
+ /* Fetch the slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &slot_ctx, sizeof(slot_ctx));
+
+ /* See 4.6.11. */
+ do {
+ /* Check that the slot context state is Addressed or Configured. */
+ if (slot_ctx.slot_state < XHCI_SLTST_ADDRESSED)
+ {
+ Log(("Slot context state wrong (%u)!\n", slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Read the entire Device Context. */
+ num_ctx = slot_ctx.ctx_ent + 1; /* Slot context plus EPs. */
+ Assert(num_ctx);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX));
+
+ /// @todo Abort any outstanding transfers!
+
+ /* Set slot state to Default and reset the USB device address. */
+ dc.entry[0].sc.slot_state = XHCI_SLTST_DEFAULT;
+ dc.entry[0].sc.dev_addr = 0;
+
+ /* Disable all endpoints except for EP 0 (aka DCI 1). */
+ for (i = 2; i < num_ctx; ++i)
+ dc.entry[i].ep.ep_state = XHCI_EPST_DISABLED;
+
+ /* Write back the updated device context. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysSlot, &dc, num_ctx * sizeof(XHCI_SLOT_CTX));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Configure a device (even though the relevant command is called 'Configure
+ * Endpoint'. This includes adding/dropping endpoint contexts as directed by
+ * the input control context bits.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uInpCtxAddr Address of the input context.
+ * @param uSlotID Slot ID associated with the context.
+ * @param fDC Deconfigure flag set (input context unused).
+ */
+static unsigned xhciR3ConfigureDevice(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID, bool fDC)
+{
+ RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK;
+ RTGCPHYS GCPhysInpSlot;
+ RTGCPHYS GCPhysOutSlot;
+ RTGCPHYS GCPhysOutEndp;
+ XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */
+ XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */
+ XHCI_EP_CTX out_endp_ctx; /* Endpoint Context (DCI=1). */
+ unsigned cc = XHCI_TCC_SUCCESS;
+ uint32_t uAddFlags;
+ uint32_t uDropFlags;
+ unsigned num_inp_ctx;
+ unsigned num_out_ctx;
+ XHCI_DEV_CTX dc_inp;
+ XHCI_DEV_CTX dc_out;
+ unsigned uDCI;
+
+ Assert(uSlotID);
+ LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx));
+
+ /* Determine the address of the output slot context. */
+ GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ Assert(GCPhysOutSlot);
+
+ /* Fetch the output slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx));
+
+ /* See 4.6.6 */
+ do {
+ /* Check that the output slot context state is Addressed, or Configured. */
+ if (out_slot_ctx.slot_state < XHCI_SLTST_ADDRESSED)
+ {
+ Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Check for deconfiguration request. */
+ if (fDC) {
+ if (out_slot_ctx.slot_state == XHCI_SLTST_CONFIGURED) {
+ /* Disable all enabled endpoints. */
+ uDropFlags = 0xFFFFFFFC; /** @todo r=bird: Why do you set uDropFlags and uAddFlags in a code path that doesn't use
+ * them? This is a _very_ difficult function to get the hang of the way it's written.
+ * Stuff like this looks like there's a control flow flaw (as to the do-break-while-false
+ * loop which doesn't do any clean up or logging at the end and seems only sever the very
+ * dubious purpose of making sure ther's only one return statement). The insistance on
+ * C-style variable declarations (top of function), makes checking state harder, which is
+ * why it's discouraged. */
+ uAddFlags = 0;
+
+ /* Start with EP1. */
+ GCPhysOutEndp = GCPhysOutSlot + sizeof(XHCI_SLOT_CTX) + sizeof(XHCI_EP_CTX);
+
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx));
+ out_endp_ctx.ep_state = XHCI_EPST_DISABLED;
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutEndp, &out_endp_ctx, sizeof(out_endp_ctx));
+ GCPhysOutEndp += sizeof(XHCI_EP_CTX); /* Point to the next EP context. */
+
+ /* Finally update the output slot context. */
+ out_slot_ctx.ctx_ent = 1; /* Only EP0 left. */
+ out_slot_ctx.slot_state = XHCI_SLTST_ADDRESSED;
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx));
+ LogFlow(("Setting Output Slot State to Addressed, Context Entries = %u\n", out_slot_ctx.ctx_ent));
+ }
+ else
+ /* NB: Attempts to deconfigure a slot in Addressed state are ignored. */
+ Log(("Ignoring attempt to deconfigure slot in Addressed state!\n"));
+ break;
+ }
+
+ /* Fetch the input control context. */
+ Assert(GCPhysInpCtx);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc));
+ Assert(icc.add_flags || icc.drop_flags); /* Make sure there's something to do. */
+
+ uAddFlags = icc.add_flags;
+ uDropFlags = icc.drop_flags;
+ LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags));
+
+ /* If and only if any 'add context' flag is set, fetch the corresponding
+ * input device context.
+ */
+ if (uAddFlags) {
+ /* Calculate the address of the input slot context (ICI=1/DCI=0). */
+ GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX);
+
+ /* Read the input Slot Context plus all Endpoint Contexts up to and
+ * including the one with the highest 'add' bit set.
+ */
+ num_inp_ctx = ASMBitLastSetU32(uAddFlags);
+ Assert(num_inp_ctx);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY));
+
+ /// @todo Check that the highest set add flag isn't beyond input slot Context Entries
+
+ /// @todo Check input slot context according to 6.2.2.2
+ /// @todo Check input EP contexts according to 6.2.3.2
+ }
+/** @todo r=bird: Looks like MSC is right that dc_inp can be used uninitalized.
+ *
+ * However, this function is so hard to read I'm leaving the exorcism of it to
+ * the author and just zeroing it in the mean time.
+ *
+ */
+ else
+ RT_ZERO(dc_inp);
+
+ /* Read the output Slot Context plus all Endpoint Contexts up to and
+ * including the one with the highest 'add' or 'drop' bit set.
+ */
+ num_out_ctx = ASMBitLastSetU32(uAddFlags | uDropFlags);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY));
+
+ /* Drop contexts as directed by flags. */
+ for (uDCI = 2; uDCI < 32; ++uDCI)
+ {
+ if (!((1 << uDCI) & uDropFlags))
+ continue;
+
+ Log2(("Dropping EP DCI %u\n", uDCI));
+ dc_out.entry[uDCI].ep.ep_state = XHCI_EPST_DISABLED;
+ /// @todo Do we need to bother tracking resources/bandwidth?
+ }
+
+ /* Now add contexts as directed by flags. */
+ for (uDCI = 2; uDCI < 32; ++uDCI)
+ {
+ if (!((1 << uDCI) & uAddFlags))
+ continue;
+
+ Assert(!fDC);
+ /* Copy over EP context, set to running. */
+ Log2(("Adding EP DCI %u\n", uDCI));
+ dc_out.entry[uDCI].ep = dc_inp.entry[uDCI].ep;
+ xhciR3EnableEP(&dc_out.entry[uDCI].ep);
+ /// @todo Do we need to bother tracking resources/bandwidth?
+ }
+
+ /* Finally update the device context. */
+ if (fDC || dc_inp.entry[0].sc.ctx_ent == 1)
+ {
+ dc_out.entry[0].sc.slot_state = XHCI_SLTST_ADDRESSED;
+ dc_out.entry[0].sc.ctx_ent = 1;
+ LogFlow(("Setting Output Slot State to Addressed\n"));
+ }
+ else
+ {
+ uint32_t uKillFlags = uDropFlags & ~uAddFlags; /* Endpoints going away. */
+
+ /* At least one EP enabled. Update Context Entries and state. */
+ Assert(dc_inp.entry[0].sc.ctx_ent);
+ dc_out.entry[0].sc.slot_state = XHCI_SLTST_CONFIGURED;
+ if (ID_TO_IDX(ASMBitLastSetU32(uAddFlags)) > dc_out.entry[0].sc.ctx_ent)
+ {
+ /* Adding new endpoints. */
+ dc_out.entry[0].sc.ctx_ent = ID_TO_IDX(ASMBitLastSetU32(uAddFlags));
+ }
+ else if (ID_TO_IDX(ASMBitLastSetU32(uKillFlags)) == dc_out.entry[0].sc.ctx_ent)
+ {
+ /* Removing the last endpoint, find the last non-disabled one. */
+ unsigned num_ctx_ent;
+
+ Assert(dc_out.entry[0].sc.ctx_ent + 1u == num_out_ctx);
+ for (num_ctx_ent = dc_out.entry[0].sc.ctx_ent; num_ctx_ent > 1; --num_ctx_ent)
+ if (dc_out.entry[num_ctx_ent].ep.ep_state != XHCI_EPST_DISABLED)
+ break;
+ dc_out.entry[0].sc.ctx_ent = num_ctx_ent; /* Last valid index to be precise. */
+ }
+ LogFlow(("Setting Output Slot State to Configured, Context Entries = %u\n", dc_out.entry[0].sc.ctx_ent));
+ }
+
+ /* If there were no errors, write back the updated output context. */
+ LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot));
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Evaluate an input context. This involves modifying device and endpoint
+ * contexts as directed by the input control context add bits.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uInpCtxAddr Address of the input context.
+ * @param uSlotID Slot ID associated with the context.
+ */
+static unsigned xhciR3EvalContext(PPDMDEVINS pDevIns, PXHCI pThis, uint64_t uInpCtxAddr, uint8_t uSlotID)
+{
+ RTGCPHYS GCPhysInpCtx = uInpCtxAddr & XHCI_CTX_ADDR_MASK;
+ RTGCPHYS GCPhysInpSlot;
+ RTGCPHYS GCPhysOutSlot;
+ XHCI_INPC_CTX icc; /* Input Control Context (ICI=0). */
+ XHCI_SLOT_CTX out_slot_ctx; /* Slot context (DCI=0). */
+ unsigned cc = XHCI_TCC_SUCCESS;
+ uint32_t uAddFlags;
+ uint32_t uDropFlags;
+ unsigned num_inp_ctx;
+ unsigned num_out_ctx;
+ XHCI_DEV_CTX dc_inp;
+ XHCI_DEV_CTX dc_out;
+ unsigned uDCI;
+
+ Assert(GCPhysInpCtx);
+ Assert(uSlotID);
+ LogFlowFunc(("Slot ID %u, input control context @ %RGp\n", uSlotID, GCPhysInpCtx));
+
+ /* Determine the address of the output slot context. */
+ GCPhysOutSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ Assert(GCPhysOutSlot);
+
+ /* Fetch the output slot context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &out_slot_ctx, sizeof(out_slot_ctx));
+
+ /* See 4.6.7 */
+ do {
+ /* Check that the output slot context state is Default, Addressed, or Configured. */
+ if (out_slot_ctx.slot_state < XHCI_SLTST_DEFAULT)
+ {
+ Log(("Output slot context state wrong (%u)!\n", out_slot_ctx.slot_state));
+ cc = XHCI_TCC_CTX_STATE_ERR;
+ break;
+ }
+
+ /* Fetch the input control context. */
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpCtx, &icc, sizeof(icc));
+ uAddFlags = icc.add_flags;
+ uDropFlags = icc.drop_flags;
+ LogFlowFunc(("Add Flags=%08X, Drop Flags=%08X\n", uAddFlags, uDropFlags));
+
+ /* Drop flags "shall be cleared to 0" but also "do not apply" (4.6.7). Log & ignore. */
+ if (uDropFlags)
+ Log(("Drop flags set (%X) for evaluating context!\n", uDropFlags));
+
+ /* If no add flags are set, nothing will be done but an error is not reported
+ * according to the logic flow in 4.6.7.
+ */
+ if (!uAddFlags)
+ {
+ Log(("Warning: no add flags set for evaluating context!\n"));
+ break;
+ }
+
+ /* Calculate the address of the input slot context (ICI=1/DCI=0). */
+ GCPhysInpSlot = GCPhysInpCtx + sizeof(XHCI_INPC_CTX);
+
+ /* Read the output Slot Context plus all Endpoint Contexts up to and
+ * including the one with the highest 'add' bit set.
+ */
+ num_inp_ctx = ASMBitLastSetU32(uAddFlags);
+ Assert(num_inp_ctx);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysInpSlot, &dc_inp, num_inp_ctx * sizeof(XHCI_DS_ENTRY));
+
+ /* Read the output Slot Context plus all Endpoint Contexts up to and
+ * including the one with the highest 'add' bit set.
+ */
+ num_out_ctx = ASMBitLastSetU32(uAddFlags);
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY));
+
+ /// @todo Check input slot context according to 6.2.2.3
+ /// @todo Check input EP contexts according to 6.2.3.3
+ /// @todo Check that the highest set add flag isn't beyond input slot Context Entries
+
+ /* Evaluate endpoint contexts as directed by add flags. */
+ /// @todo 6.2.3.3 suggests only the A1 bit matters? Anything besides A0/A1 is ignored??
+ for (uDCI = 1; uDCI < 32; ++uDCI)
+ {
+ if (!((1 << uDCI) & uAddFlags))
+ continue;
+
+ /* Evaluate Max Packet Size. */
+ LogFunc(("DCI %u: Max Packet Size: %u -> %u\n", uDCI, dc_out.entry[uDCI].ep.max_pkt_sz, dc_inp.entry[uDCI].ep.max_pkt_sz));
+ dc_out.entry[uDCI].ep.max_pkt_sz = dc_inp.entry[uDCI].ep.max_pkt_sz;
+ }
+
+ /* Finally update the device context if directed to do so (A0 flag set). */
+ if (uAddFlags & RT_BIT(0))
+ {
+ /* 6.2.2.3 - evaluate Interrupter Target and Max Exit Latency. */
+ Log(("Interrupter Target: %u -> %u\n", dc_out.entry[0].sc.intr_tgt, dc_inp.entry[0].sc.intr_tgt));
+ Log(("Max Exit Latency : %u -> %u\n", dc_out.entry[0].sc.max_lat, dc_inp.entry[0].sc.max_lat));
+
+ /// @todo Non-zero Max Exit Latency (see 4.6.7)
+ dc_out.entry[0].sc.intr_tgt = dc_inp.entry[0].sc.intr_tgt;
+ dc_out.entry[0].sc.max_lat = dc_inp.entry[0].sc.max_lat;
+ }
+
+ /* If there were no errors, write back the updated output context. */
+ LogFlow(("Success, updating Output Context @ %RGp\n", GCPhysOutSlot));
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysOutSlot, &dc_out, num_out_ctx * sizeof(XHCI_DS_ENTRY));
+ } while (0);
+
+ return cc;
+}
+
+
+/**
+ * Query available port bandwidth.
+ *
+ * @returns TRB completion code.
+ * @param pDevIns The device instance.
+ * @param pThis Pointer to the xHCI state.
+ * @param uDevSpd Speed of not yet attached devices.
+ * @param uHubSlotID Hub Slot ID to query (unsupported).
+ * @param uBwCtx Bandwidth context physical address.
+ */
+static unsigned xhciR3GetPortBandwidth(PPDMDEVINS pDevIns, PXHCI pThis, uint8_t uDevSpd, uint8_t uHubSlotID, uint64_t uBwCtx)
+{
+ RT_NOREF(uHubSlotID);
+ RTGCPHYS GCPhysBwCtx;
+ unsigned cc = XHCI_TCC_SUCCESS;
+ unsigned ctx_size;
+ unsigned iPort;
+ uint8_t bw_ctx[RT_ALIGN_32(XHCI_NDP_MAX + 1, 4)] = {0};
+ uint8_t dev_spd;
+ uint8_t avail_bw;
+
+ Assert(!uHubSlotID);
+ Assert(uBwCtx);
+
+ /* See 4.6.15. */
+
+ /* Hubs are not supported because guests will never see them. The
+ * reported values are more or less dummy because we have no real
+ * information about the bandwidth available on the host. The reported
+ * values are optimistic, as if each port had its own separate Bus
+ * Instance aka BI.
+ */
+
+ GCPhysBwCtx = uBwCtx & XHCI_CTX_ADDR_MASK;
+
+ /* Number of ports + 1, rounded up to DWORDs. */
+ ctx_size = RT_ALIGN_32(XHCI_NDP_CFG(pThis) + 1, 4);
+ LogFlowFunc(("BW Context at %RGp, size %u\n", GCPhysBwCtx, ctx_size));
+ Assert(ctx_size <= sizeof(bw_ctx));
+
+ /* Go over all the ports. */
+ for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort)
+ {
+ /* Get the device speed from the port... */
+ dev_spd = (pThis->aPorts[iPort].portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT;
+ /* ...and if nothing is attached, use the provided default. */
+ if (!dev_spd)
+ dev_spd = uDevSpd;
+
+ /* For USB3 ports, report 90% available for SS devices (see 6.2.6). */
+ if (IS_USB3_PORT_IDX_SHR(pThis, iPort))
+ avail_bw = dev_spd == XHCI_SPD_SUPER ? 90 : 0;
+ else
+ /* For USB2 ports, report 80% available for HS and 90% for FS/LS. */
+ switch (dev_spd)
+ {
+ case XHCI_SPD_HIGH:
+ avail_bw = 80;
+ break;
+ case XHCI_SPD_FULL:
+ case XHCI_SPD_LOW:
+ avail_bw = 90;
+ break;
+ default:
+ avail_bw = 0;
+ }
+
+ /* The first entry in the context is reserved. */
+ bw_ctx[iPort + 1] = avail_bw;
+ }
+
+ /* Write back the bandwidth context. */
+ PDMDevHlpPCIPhysWriteMeta(pDevIns, GCPhysBwCtx, &bw_ctx, ctx_size);
+
+ return cc;
+}
+
+#define NEC_MAGIC ('x' | ('H' << 8) | ('C' << 16) | ('I' << 24))
+
+/**
+ * Take a 64-bit input, shake well, produce 32-bit token. This mechanism
+ * prevents NEC/Renesas drivers from running on 3rd party hardware. Mirrors
+ * code found in vendor's drivers.
+ */
+static uint32_t xhciR3NecAuthenticate(uint64_t cookie)
+{
+ uint32_t cookie_lo = RT_LODWORD(cookie);
+ uint32_t cookie_hi = RT_HIDWORD(cookie);
+ uint32_t shift_cnt;
+ uint32_t token;
+
+ shift_cnt = (cookie_hi >> 8) & 31;
+ token = ASMRotateRightU32(cookie_lo - NEC_MAGIC, shift_cnt);
+ shift_cnt = cookie_hi & 31;
+ token += ASMRotateLeftU32(cookie_lo + NEC_MAGIC, shift_cnt);
+ shift_cnt = (cookie_lo >> 16) & 31;
+ token -= ASMRotateLeftU32(cookie_hi ^ NEC_MAGIC, shift_cnt);
+
+ return ~token;
+}
+
+/**
+ * Process a single command TRB and post completion information.
+ */
+static int xhciR3ExecuteCommand(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC, XHCI_COMMAND_TRB *pCmd)
+{
+ XHCI_EVENT_TRB ed;
+ uint32_t token;
+ unsigned slot;
+ unsigned cc;
+ int rc = VINF_SUCCESS;
+ LogFlowFunc(("Executing command %u (%s) @ %RGp\n", pCmd->gen.type,
+ pCmd->gen.type < RT_ELEMENTS(g_apszTrbNames) ? g_apszTrbNames[pCmd->gen.type] : "WHAT?!!",
+ (RTGCPHYS)pThis->cmdr_dqp));
+
+ switch (pCmd->gen.type)
+ {
+ case XHCI_TRB_NOOP_CMD:
+ /* No-op, slot ID is always zero. */
+ rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_LINK:
+ /* Link; set the dequeue pointer. CH bit is ignored. */
+ Log(("Link: Ptr=%RGp IOC=%u TC=%u\n", pCmd->link.rseg_ptr, pCmd->link.ioc, pCmd->link.toggle));
+ if (pCmd->link.ioc) /* Command completion event is optional! */
+ rc = xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, 0);
+ /* Update the dequeue pointer and flip DCS if required. */
+ pThis->cmdr_dqp = pCmd->link.rseg_ptr & XHCI_TRDP_ADDR_MASK;
+ pThis->cmdr_ccs = pThis->cmdr_ccs ^ pCmd->link.toggle;
+ break;
+
+ case XHCI_TRB_ENB_SLOT:
+ /* Look for an empty device slot. */
+ for (slot = 0; slot < RT_ELEMENTS(pThis->aSlotState); ++slot)
+ {
+ if (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY)
+ {
+ /* Found a slot - transition to enabled state. */
+ pThis->aSlotState[slot] = XHCI_DEVSLOT_ENABLED;
+ break;
+ }
+ }
+ Log(("Enable Slot: found slot ID %u\n", IDX_TO_ID(slot)));
+
+ /* Post command completion event. */
+ if (slot == RT_ELEMENTS(pThis->aSlotState))
+ xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_NO_SLOTS, 0);
+ else
+ xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_SUCCESS, IDX_TO_ID(slot));
+
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_DIS_SLOT:
+ /* Disable the given device slot. */
+ Log(("Disable Slot: slot ID %u\n", pCmd->dsl.slot_id));
+ cc = XHCI_TCC_SUCCESS;
+ slot = ID_TO_IDX(pCmd->dsl.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ {
+ /// @todo set slot state of assoc. context to disabled
+ pThis->aSlotState[slot] = XHCI_DEVSLOT_EMPTY;
+ }
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->dsl.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_ADDR_DEV:
+ /* Address a device. */
+ Log(("Address Device: slot ID %u, BSR=%u\n", pCmd->adr.slot_id, pCmd->adr.bsr));
+ slot = ID_TO_IDX(pCmd->cfg.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3AddressDevice(pDevIns, pThis, pThisCC, pCmd->adr.ctx_ptr, pCmd->adr.slot_id, pCmd->adr.bsr);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->adr.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_CFG_EP:
+ /* Configure endpoint. */
+ Log(("Configure endpoint: slot ID %u, DC=%u, Ctx @ %RGp\n", pCmd->cfg.slot_id, pCmd->cfg.dc, pCmd->cfg.ctx_ptr));
+ slot = ID_TO_IDX(pCmd->cfg.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3ConfigureDevice(pDevIns, pThis, pCmd->cfg.ctx_ptr, pCmd->cfg.slot_id, pCmd->cfg.dc);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->cfg.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_EVAL_CTX:
+ /* Evaluate context. */
+ Log(("Evaluate context: slot ID %u, Ctx @ %RGp\n", pCmd->evc.slot_id, pCmd->evc.ctx_ptr));
+ slot = ID_TO_IDX(pCmd->evc.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3EvalContext(pDevIns, pThis, pCmd->evc.ctx_ptr, pCmd->evc.slot_id);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->evc.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_RESET_EP:
+ /* Reset the given endpoint. */
+ Log(("Reset Endpoint: slot ID %u, EP ID %u, TSP=%u\n", pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp));
+ cc = XHCI_TCC_SUCCESS;
+ slot = ID_TO_IDX(pCmd->rse.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3ResetEndpoint(pDevIns, pThis, pCmd->rse.slot_id, pCmd->rse.ep_id, pCmd->rse.tsp);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_STOP_EP:
+ /* Stop the given endpoint. */
+ Log(("Stop Endpoint: slot ID %u, EP ID %u, SP=%u\n", pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp));
+ cc = XHCI_TCC_SUCCESS;
+ slot = ID_TO_IDX(pCmd->stp.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3StopEndpoint(pDevIns, pThis, pThisCC, pCmd->stp.slot_id, pCmd->stp.ep_id, pCmd->stp.sp);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stp.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_SET_DEQ_PTR:
+ /* Set TR Dequeue Pointer. */
+ Log(("Set TRDP: slot ID %u, EP ID %u, TRDP=%RX64\n", pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp));
+ cc = XHCI_TCC_SUCCESS;
+ slot = ID_TO_IDX(pCmd->stdp.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3SetTRDP(pDevIns, pThis, pCmd->stdp.slot_id, pCmd->stdp.ep_id, pCmd->stdp.tr_dqp);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->stdp.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_RESET_DEV:
+ /* Reset a device. */
+ Log(("Reset Device: slot ID %u\n", pCmd->rsd.slot_id));
+ cc = XHCI_TCC_SUCCESS;
+ slot = ID_TO_IDX(pCmd->rsd.slot_id);
+ if ((slot >= RT_ELEMENTS(pThis->aSlotState)) || (pThis->aSlotState[slot] == XHCI_DEVSLOT_EMPTY))
+ cc = XHCI_TCC_SLOT_NOT_ENB;
+ else
+ cc = xhciR3ResetDevice(pDevIns, pThis, pCmd->rsd.slot_id);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, pCmd->rsd.slot_id);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case XHCI_TRB_GET_PORT_BW:
+ /* Get port bandwidth. */
+ Log(("Get Port Bandwidth: Dev Speed %u, Hub Slot ID %u, Context=%RX64\n", pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr));
+ cc = XHCI_TCC_SUCCESS;
+ if (pCmd->gpbw.slot_id)
+ cc = XHCI_TCC_PARM_ERR; /* Potential undefined behavior, see 4.6.15. */
+ else
+ cc = xhciR3GetPortBandwidth(pDevIns, pThis, pCmd->gpbw.spd, pCmd->gpbw.slot_id, pCmd->gpbw.pbctx_ptr);
+ xhciR3PostCmdCompletion(pDevIns, pThis, cc, 0);
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case NEC_TRB_GET_FW_VER:
+ /* Get NEC firmware version. */
+ Log(("Get NEC firmware version\n"));
+ cc = XHCI_TCC_SUCCESS;
+
+ RT_ZERO(ed);
+ ed.nce.word1 = NEC_FW_REV;
+ ed.nce.trb_ptr = pThis->cmdr_dqp;
+ ed.nce.cc = cc;
+ ed.nce.type = NEC_TRB_CMD_CMPL;
+
+ xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false);
+
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ case NEC_TRB_AUTHENTICATE:
+ /* NEC authentication. */
+ Log(("NEC authentication, cookie %RX64\n", pCmd->nac.cookie));
+ cc = XHCI_TCC_SUCCESS;
+
+ token = xhciR3NecAuthenticate(pCmd->nac.cookie);
+ RT_ZERO(ed);
+ ed.nce.word1 = RT_LOWORD(token);
+ ed.nce.word2 = RT_HIWORD(token);
+ ed.nce.trb_ptr = pThis->cmdr_dqp;
+ ed.nce.cc = cc;
+ ed.nce.type = NEC_TRB_CMD_CMPL;
+
+ xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false);
+
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+
+ default:
+ Log(("Unsupported command!\n"));
+ pThis->cmdr_dqp += sizeof(XHCI_COMMAND_TRB);
+ break;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Stop the Command Ring.
+ */
+static int xhciR3StopCommandRing(PPDMDEVINS pDevIns, PXHCI pThis)
+{
+ LogFlowFunc(("Command Ring stopping\n"));
+
+ Assert(pThis->crcr & (XHCI_CRCR_CA | XHCI_CRCR_CS));
+ Assert(pThis->crcr & XHCI_CRCR_CRR);
+ ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS));
+ return xhciR3PostCmdCompletion(pDevIns, pThis, XHCI_TCC_CMDR_STOPPED, 0);
+}
+
+
+/**
+ * Process the xHCI command ring.
+ */
+static int xhciR3ProcessCommandRing(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC)
+{
+ RTGCPHYS GCPhysCmdTRB;
+ XHCI_COMMAND_TRB cmd; /* Command Descriptor */
+ unsigned cCmds;
+
+ Assert(pThis->crcr & XHCI_CRCR_CRR);
+ LogFlowFunc(("Processing commands...\n"));
+
+ for (cCmds = 0;; cCmds++)
+ {
+ /* First check if the xHC is running at all. */
+ if (!(pThis->cmd & XHCI_CMD_RS))
+ {
+ /* Note that this will call xhciR3PostCmdCompletion() which will
+ * end up doing nothing because R/S is clear.
+ */
+ xhciR3StopCommandRing(pDevIns, pThis);
+ break;
+ }
+
+ /* Check if Command Ring was stopped in the meantime. */
+ if (pThis->crcr & (XHCI_CRCR_CS | XHCI_CRCR_CA))
+ {
+ /* NB: We currently do not abort commands. If we did, we would
+ * abort the currently running command and complete it with
+ * the XHCI_TCC_CMD_ABORTED status.
+ */
+ xhciR3StopCommandRing(pDevIns, pThis);
+ break;
+ }
+
+ /* Fetch the command TRB. */
+ GCPhysCmdTRB = pThis->cmdr_dqp;
+ PDMDevHlpPCIPhysReadMeta(pDevIns, GCPhysCmdTRB, &cmd, sizeof(cmd));
+
+ /* Make sure the Cycle State matches. */
+ if ((bool)cmd.gen.cycle == pThis->cmdr_ccs)
+ xhciR3ExecuteCommand(pDevIns, pThis, pThisCC, &cmd);
+ else
+ {
+ Log(("Command Ring empty\n"));
+ break;
+ }
+
+ /* Check if we're being fed suspiciously many commands. */
+ if (cCmds > XHCI_MAX_NUM_CMDS)
+ {
+ /* Clear the R/S bit and any command ring running bits.
+ * Note that the caller (xhciR3WorkerLoop) will set XHCI_STATUS_HCH.
+ */
+ ASMAtomicAndU32(&pThis->cmd, ~XHCI_CMD_RS);
+ ASMAtomicAndU64(&pThis->crcr, ~(XHCI_CRCR_CRR | XHCI_CRCR_CA | XHCI_CRCR_CS));
+ ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCE);
+ LogRelMax(10, ("xHCI: Attempted to execute too many commands, stopping xHC!\n"));
+ break;
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The xHCI asynchronous worker thread.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The xHCI device instance.
+ * @param pThread The worker thread.
+ */
+static DECLCALLBACK(int) xhciR3WorkerLoop(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+ int rc;
+
+ LogFlow(("xHCI entering worker thread loop.\n"));
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ uint32_t u32Tasks = 0;
+ uint8_t uSlotID;
+
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, true);
+ u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0);
+ if (!u32Tasks)
+ {
+ Assert(ASMAtomicReadBool(&pThis->fWrkThreadSleeping));
+ rc = PDMDevHlpSUPSemEventWaitNoResume(pDevIns, pThis->hEvtProcess, RT_INDEFINITE_WAIT);
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_INTERRUPTED, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+ LogFlowFunc(("Woken up with rc=%Rrc\n", rc));
+ u32Tasks = ASMAtomicXchgU32(&pThis->u32TasksNew, 0);
+ }
+
+ RTCritSectEnter(&pThisCC->CritSectThrd);
+
+ if (pThis->crcr & XHCI_CRCR_CRR)
+ xhciR3ProcessCommandRing(pDevIns, pThis, pThisCC);
+
+ /* Run down the list of doorbells that are ringing. */
+ for (uSlotID = 1; uSlotID < XHCI_NDS; ++uSlotID)
+ {
+ if (pThis->aSlotState[ID_TO_IDX(uSlotID)] >= XHCI_DEVSLOT_ENABLED)
+ {
+ while (pThis->aBellsRung[ID_TO_IDX(uSlotID)])
+ {
+ uint8_t bit;
+ uint32_t uDBVal = 0;
+
+ for (bit = 0; bit < 32; ++bit)
+ if (pThis->aBellsRung[ID_TO_IDX(uSlotID)] & (1 << bit))
+ {
+ uDBVal = bit;
+ break;
+ }
+
+ Log2(("Stop ringing bell for slot %u, DCI %u\n", uSlotID, uDBVal));
+ ASMAtomicAndU32(&pThis->aBellsRung[ID_TO_IDX(uSlotID)], ~(1 << uDBVal));
+ xhciR3ProcessDevCtx(pDevIns, pThis, pThisCC, uSlotID, uDBVal);
+ }
+ }
+ }
+
+ /* If the R/S bit is no longer set, halt the xHC. */
+ if (!(pThis->cmd & XHCI_CMD_RS))
+ {
+ Log(("R/S clear, halting the xHC.\n"));
+ ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH);
+ }
+
+ RTCritSectLeave(&pThisCC->CritSectThrd);
+
+ ASMAtomicWriteBool(&pThis->fWrkThreadSleeping, false);
+ } /* While running */
+
+ LogFlow(("xHCI worker thread exiting.\n"));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the worker thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDevIns The xHCI device instance.
+ * @param pThread The worker thread.
+ */
+static DECLCALLBACK(int) xhciR3WorkerWakeUp(PPDMDEVINS pDevIns, PPDMTHREAD pThread)
+{
+ NOREF(pThread);
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+
+ return PDMDevHlpSUPSemEventSignal(pDevIns, pThis->hEvtProcess);
+}
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) xhciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pRh->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pRh->IRhPort);
+ return NULL;
+}
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) xhciR3QueryStatusInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PXHCIR3 pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThisCC->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThisCC->ILeds);
+ return NULL;
+}
+
+/**
+ * Gets the pointer to the status LED of a unit.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the interface structure containing the called function pointer.
+ * @param iLUN The unit which status LED we desire.
+ * @param ppLed Where to store the LED pointer.
+ */
+static DECLCALLBACK(int) xhciR3QueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed)
+{
+ PXHCICC pThisCC = RT_FROM_MEMBER(pInterface, XHCIR3, ILeds);
+
+ if (iLUN < XHCI_NUM_LUNS)
+ {
+ *ppLed = iLUN ? &pThisCC->RootHub3.Led : &pThisCC->RootHub2.Led;
+ Assert((*ppLed)->u32Magic == PDMLED_MAGIC);
+ return VINF_SUCCESS;
+ }
+ return VERR_PDM_LUN_NOT_FOUND;
+}
+
+
+/**
+ * Get the number of ports available in the hub.
+ *
+ * @returns The number of ports available.
+ * @param pInterface Pointer to this structure.
+ * @param pAvailable Bitmap indicating the available ports. Set bit == available port.
+ */
+static DECLCALLBACK(unsigned) xhciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ unsigned iPort;
+ unsigned cPorts = 0;
+ LogFlow(("xhciR3RhGetAvailablePorts\n"));
+
+ memset(pAvailable, 0, sizeof(*pAvailable));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ for (iPort = pRh->uPortBase; iPort < (unsigned)pRh->uPortBase + pRh->cPortsImpl; iPort++)
+ {
+ Assert(iPort < XHCI_NDP_CFG(PDMDEVINS_2_DATA(pDevIns, PXHCI)));
+ if (!pThisCC->aPorts[iPort].fAttached)
+ {
+ cPorts++;
+ ASMBitSet(pAvailable, IDX_TO_ID(iPort - pRh->uPortBase));
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return cPorts;
+}
+
+
+/**
+ * Get the supported USB versions for USB2 hubs.
+ *
+ * @returns The mask of supported USB versions.
+ * @param pInterface Pointer to this structure.
+ */
+static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions2(PVUSBIROOTHUBPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ return VUSB_STDVER_11 | VUSB_STDVER_20;
+}
+
+
+/**
+ * Get the supported USB versions for USB2 hubs.
+ *
+ * @returns The mask of supported USB versions.
+ * @param pInterface Pointer to this structure.
+ */
+static DECLCALLBACK(uint32_t) xhciR3RhGetUSBVersions3(PVUSBIROOTHUBPORT pInterface)
+{
+ RT_NOREF(pInterface);
+ return VUSB_STDVER_30;
+}
+
+
+/**
+ * Start sending SOF tokens across the USB bus, lists are processed in the
+ * next frame.
+ */
+static void xhciR3BusStart(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC)
+{
+ unsigned iPort;
+
+ pThisCC->RootHub2.pIRhConn->pfnPowerOn(pThisCC->RootHub2.pIRhConn);
+ pThisCC->RootHub3.pIRhConn->pfnPowerOn(pThisCC->RootHub3.pIRhConn);
+// xhciR3BumpFrameNumber(pThis);
+
+ Log(("xHCI: Bus started\n"));
+
+ Assert(pThis->status & XHCI_STATUS_HCH);
+ ASMAtomicAndU32(&pThis->status, ~XHCI_STATUS_HCH);
+
+ /* HCH gates PSCEG (4.19.2). When clearing HCH, re-evaluate port changes. */
+ for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort)
+ {
+ if (pThis->aPorts[iPort].portsc & XHCI_PORT_CHANGE_MASK)
+ xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort));
+ }
+
+ /// @todo record the starting time?
+// pThis->SofTime = TMTimerGet(pThis->CTX_SUFF(pEndOfFrameTimer)) - pThis->cTicksPerFrame;
+}
+
+/**
+ * Stop sending SOF tokens on the bus and processing the data.
+ */
+static void xhciR3BusStop(PPDMDEVINS pDevIns, PXHCI pThis, PXHCICC pThisCC)
+{
+ LogFlow(("xhciR3BusStop\n"));
+
+ /* Stop the controller and Command Ring. */
+ pThis->cmd &= ~XHCI_CMD_RS;
+ pThis->crcr |= XHCI_CRCR_CS;
+
+ /* Power off the root hubs. */
+ pThisCC->RootHub2.pIRhConn->pfnPowerOff(pThisCC->RootHub2.pIRhConn);
+ pThisCC->RootHub3.pIRhConn->pfnPowerOff(pThisCC->RootHub3.pIRhConn);
+
+ /* The worker thread will halt the HC (set HCH) when done. */
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0);
+}
+
+
+/**
+ * Power a port up or down
+ */
+static void xhciR3PortPower(PXHCI pThis, PXHCICC pThisCC, unsigned iPort, bool fPowerUp)
+{
+ PXHCIHUBPORT pPort = &pThis->aPorts[iPort];
+ PXHCIHUBPORTR3 pPortR3 = &pThisCC->aPorts[iPort];
+ PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort);
+
+ bool fOldPPS = !!(pPort->portsc & XHCI_PORT_PP);
+ LogFlow(("xhciR3PortPower (port %u) %s\n", IDX_TO_ID(iPort), fPowerUp ? "UP" : "DOWN"));
+
+ if (fPowerUp)
+ {
+ /* Power up a port. */
+ if (pPortR3->fAttached)
+ ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS);
+ if (pPort->portsc & XHCI_PORT_CCS)
+ ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_PP);
+ if (pPortR3->fAttached && !fOldPPS)
+ VUSBIRhDevPowerOn(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort));
+ }
+ else
+ {
+ /* Power down. */
+ ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_PP | XHCI_PORT_CCS));
+ if (pPortR3->fAttached && fOldPPS)
+ VUSBIRhDevPowerOff(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort));
+ }
+}
+
+
+/**
+ * Port reset done callback.
+ *
+ * @returns nothing.
+ * @param pDevIns The device instance data.
+ * @param iPort The XHCI port index of the port being resetted.
+ */
+static void xhciR3PortResetDone(PPDMDEVINS pDevIns, unsigned iPort)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+
+ Log2(("xhciR3PortResetDone\n"));
+
+ AssertReturnVoid(iPort < XHCI_NDP_CFG(pThis));
+
+ /*
+ * Successful reset.
+ */
+ Log2(("xhciR3PortResetDone: Reset completed.\n"));
+
+ uint32_t fChangeMask = XHCI_PORT_PED | XHCI_PORT_PRC;
+ /* For USB2 ports, transition the link state. */
+ if (!IS_USB3_PORT_IDX_SHR(pThis, iPort))
+ {
+ pThis->aPorts[iPort].portsc &= ~XHCI_PORT_PLS_MASK;
+ pThis->aPorts[iPort].portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT;
+ }
+ else
+ {
+ if (pThis->aPorts[iPort].portsc & XHCI_PORT_WPR)
+ fChangeMask |= XHCI_PORT_WRC;
+ }
+
+ ASMAtomicAndU32(&pThis->aPorts[iPort].portsc, ~(XHCI_PORT_PR | XHCI_PORT_WPR));
+ ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fChangeMask);
+ /// @todo Set USBSTS.PCD and manage PSCEG correctly!
+ /// @todo just guessing?!
+// ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CSC | XHCI_PORT_PLC);
+
+ /// @todo Is this the right place?
+ xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort));
+}
+
+
+/**
+ * Sets a flag in a port status register, but only if a device is connected;
+ * if not, set ConnectStatusChange flag to force HCD to reevaluate connect status.
+ *
+ * @returns true if device was connected and the flag was cleared.
+ */
+static bool xhciR3RhPortSetIfConnected(PXHCI pThis, int iPort, uint32_t fValue)
+{
+ /*
+ * Writing a 0 has no effect
+ */
+ if (fValue == 0)
+ return false;
+
+ /*
+ * The port might be still/already disconnected.
+ */
+ if (!(pThis->aPorts[iPort].portsc & XHCI_PORT_CCS))
+ return false;
+
+ bool fRc = !(pThis->aPorts[iPort].portsc & fValue);
+
+ /* Set the bit. */
+ ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, fValue);
+
+ return fRc;
+}
+
+
+/** Translate VUSB speed enum to xHCI definition. */
+static unsigned xhciR3UsbSpd2XhciSpd(VUSBSPEED enmSpeed)
+{
+ unsigned uSpd;
+
+ switch (enmSpeed)
+ {
+ default: AssertMsgFailed(("%d\n", enmSpeed));
+ RT_FALL_THRU();
+ case VUSB_SPEED_LOW: uSpd = XHCI_SPD_LOW; break;
+ case VUSB_SPEED_FULL: uSpd = XHCI_SPD_FULL; break;
+ case VUSB_SPEED_HIGH: uSpd = XHCI_SPD_HIGH; break;
+ case VUSB_SPEED_SUPER: uSpd = XHCI_SPD_SUPER; break;
+ }
+ return uSpd;
+}
+
+/** @interface_method_impl{VUSBIROOTHUBPORT,pfnAttach} */
+static DECLCALLBACK(int) xhciR3RhAttach(PVUSBIROOTHUBPORT pInterface, unsigned uPort, VUSBSPEED enmSpeed)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCIHUBPORT pPort;
+ unsigned iPort;
+ LogFlow(("xhciR3RhAttach: uPort=%u (iPort=%u)\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase));
+
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= pRh->cPortsImpl);
+ iPort = ID_TO_IDX(uPort) + pRh->uPortBase;
+ Assert(iPort < XHCI_NDP_CFG(pThis));
+ pPort = &pThis->aPorts[iPort];
+ Assert(!pThisCC->aPorts[iPort].fAttached);
+ Assert(enmSpeed != VUSB_SPEED_UNKNOWN);
+
+ /*
+ * Attach it.
+ */
+ ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CCS | XHCI_PORT_CSC);
+ pThisCC->aPorts[iPort].fAttached = true;
+ xhciR3PortPower(pThis, pThisCC, iPort, 1 /* power on */);
+
+ /* USB3 ports automatically transition to Enabled state. */
+ if (IS_USB3_PORT_IDX_R3(pThisCC, iPort))
+ {
+ Assert(enmSpeed == VUSB_SPEED_SUPER);
+ pPort->portsc |= XHCI_PORT_PED;
+ pPort->portsc &= ~XHCI_PORT_PLS_MASK;
+ pPort->portsc |= XHCI_PLS_U0 << XHCI_PORT_PLS_SHIFT;
+ pPort->portsc &= ~XHCI_PORT_SPD_MASK;
+ pPort->portsc |= XHCI_SPD_SUPER << XHCI_PORT_SPD_SHIFT;
+ VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort),
+ false, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns));
+ }
+ else
+ {
+ Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL || enmSpeed == VUSB_SPEED_HIGH);
+ pPort->portsc &= ~XHCI_PORT_SPD_MASK;
+ pPort->portsc |= xhciR3UsbSpd2XhciSpd(enmSpeed) << XHCI_PORT_SPD_SHIFT;
+ }
+
+ xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort));
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * A device is being detached from a port in the root hub.
+ *
+ * @param pInterface Pointer to this structure.
+ * @param uPort The 1-based port number assigned to the device.
+ */
+static DECLCALLBACK(void) xhciR3RhDetach(PVUSBIROOTHUBPORT pInterface, unsigned uPort)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCIHUBPORT pPort;
+ unsigned iPort;
+ LogFlow(("xhciR3RhDetach: uPort=%u iPort=%u\n", uPort, ID_TO_IDX(uPort) + pRh->uPortBase));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ PDM_CRITSECT_RELEASE_ASSERT_RC_DEV(pDevIns, pDevIns->pCritSectRoR3, rcLock);
+
+ /*
+ * Validate and adjust input.
+ */
+ Assert(uPort >= 1 && uPort <= pRh->cPortsImpl);
+ iPort = ID_TO_IDX(uPort) + pRh->uPortBase;
+ Assert(iPort < XHCI_NDP_CFG(pThis));
+ pPort = &pThis->aPorts[iPort];
+ Assert(pThisCC->aPorts[iPort].fAttached);
+
+ /*
+ * Detach it.
+ */
+ pThisCC->aPorts[iPort].fAttached = false;
+ ASMAtomicAndU32(&pPort->portsc, ~(XHCI_PORT_CCS | XHCI_PORT_SPD_MASK | XHCI_PORT_PLS_MASK));
+ ASMAtomicOrU32(&pPort->portsc, XHCI_PORT_CSC);
+ /* Link state goes to RxDetect. */
+ ASMAtomicOrU32(&pPort->portsc, XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT);
+ /* Disconnect clears the port enable bit. */
+ if (pPort->portsc & XHCI_PORT_PED)
+ ASMAtomicAndU32(&pPort->portsc, ~XHCI_PORT_PED);
+
+ xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort));
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+}
+
+
+/**
+ * One of the root hub devices has completed its reset
+ * operation.
+ *
+ * Currently, we don't think anything is required to be done here
+ * so it's just a stub for forcing async resetting of the devices
+ * during a root hub reset.
+ *
+ * @param pDev The root hub device.
+ * @param rc The result of the operation.
+ * @param uPort The port number of the device on the roothub being resetted.
+ * @param pvUser Pointer to the controller.
+ */
+static DECLCALLBACK(void) xhciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, uint32_t uPort, int rc, void *pvUser)
+{
+ LogRel(("xHCI: Root hub-attached device reset completed with %Rrc\n", rc));
+ RT_NOREF(pDev, uPort, rc, pvUser);
+}
+
+
+/**
+ * Does a software or hardware reset of the controller.
+ *
+ * This is called in response to setting HcCommandStatus.HCR, hardware reset,
+ * and device construction.
+ *
+ * @param pThis The shared XHCI instance data
+ * @param pThisCC The ring-3 XHCI instance data
+ * @param fNewMode The new mode of operation. This is UsbSuspend if
+ * it's a software reset, and UsbReset if it's a
+ * hardware reset / cold boot.
+ * @param fTrueReset Set if we can do a real reset of the devices
+ * attached to the root hub. This is really a just a
+ * hack for the non-working linux device reset. Linux
+ * has this feature called 'logical disconnect' if
+ * device reset fails which prevents us from doing
+ * resets when the guest asks for it - the guest will
+ * get confused when the device seems to be
+ * reconnected everytime it tries to reset it. But if
+ * we're at hardware reset time, we can allow a device
+ * to be 'reconnected' without upsetting the guest.
+ *
+ * @remark This has nothing to do with software setting the
+ * mode to UsbReset.
+ */
+static void xhciR3DoReset(PXHCI pThis, PXHCICC pThisCC, uint32_t fNewMode, bool fTrueReset)
+{
+ LogFunc(("%s reset%s\n", fNewMode == XHCI_USB_RESET ? "Hardware" : "Software",
+ fTrueReset ? " (really reset devices)" : ""));
+
+ /*
+ * Cancel all outstanding URBs.
+ *
+ * We can't, and won't, deal with URBs until we're moved out of the
+ * suspend/reset state. Also, a real HC isn't going to send anything
+ * any more when a reset has been signaled.
+ */
+ pThisCC->RootHub2.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub2.pIRhConn);
+ pThisCC->RootHub3.pIRhConn->pfnCancelAllUrbs(pThisCC->RootHub3.pIRhConn);
+
+ /*
+ * Reset the hardware registers.
+ */
+ /** @todo other differences between hardware reset and VM reset? */
+
+ pThis->cmd = 0;
+ pThis->status = XHCI_STATUS_HCH;
+ pThis->dnctrl = 0;
+ pThis->crcr = 0;
+ pThis->dcbaap = 0;
+ pThis->config = 0;
+
+ /*
+ * Reset the internal state.
+ */
+ pThis->cmdr_dqp = 0;
+ pThis->cmdr_ccs = 0;
+
+ RT_ZERO(pThis->aSlotState);
+ RT_ZERO(pThis->aBellsRung);
+
+ /* Zap everything but the lock. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i)
+ {
+ pThis->aInterrupters[i].iman = 0;
+ pThis->aInterrupters[i].imod = 0;
+ pThis->aInterrupters[i].erstsz = 0;
+ pThis->aInterrupters[i].erstba = 0;
+ pThis->aInterrupters[i].erdp = 0;
+ pThis->aInterrupters[i].erep = 0;
+ pThis->aInterrupters[i].erst_idx = 0;
+ pThis->aInterrupters[i].trb_count = 0;
+ pThis->aInterrupters[i].evtr_pcs = false;
+ pThis->aInterrupters[i].ipe = false;
+ }
+
+ if (fNewMode == XHCI_USB_RESET)
+ {
+ /* Only a hardware reset reinits the port registers. */
+ for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++)
+ {
+ /* Need to preserve the speed of attached devices. */
+ pThis->aPorts[i].portsc &= XHCI_PORT_SPD_MASK;
+ pThis->aPorts[i].portsc |= XHCI_PLS_RXDETECT << XHCI_PORT_PLS_SHIFT;
+ /* If Port Power Control is not supported, ports are always powered on. */
+ if (!(pThis->hcc_params & XHCI_HCC_PPC))
+ pThis->aPorts[i].portsc |= XHCI_PORT_PP;
+ }
+ }
+
+ /*
+ * If this is a hardware reset, we will initialize the root hub too.
+ * Software resets doesn't do this according to the specs.
+ * (It's not possible to have a device connected at the time of the
+ * device construction, so nothing to worry about there.)
+ */
+ if (fNewMode == XHCI_USB_RESET)
+ {
+ pThisCC->RootHub2.pIRhConn->pfnReset(pThisCC->RootHub2.pIRhConn, fTrueReset);
+ pThisCC->RootHub3.pIRhConn->pfnReset(pThisCC->RootHub3.pIRhConn, fTrueReset);
+
+ /*
+ * Reattach the devices.
+ */
+ for (unsigned i = 0; i < XHCI_NDP_CFG(pThis); i++)
+ {
+ bool fAttached = pThisCC->aPorts[i].fAttached;
+ PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, i);
+ pThisCC->aPorts[i].fAttached = false;
+
+ if (fAttached)
+ {
+ VUSBSPEED enmSpeed = VUSBIRhDevGetSpeed(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i));
+ xhciR3RhAttach(&pRh->IRhPort, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, i), enmSpeed);
+ }
+ }
+ }
+}
+
+/**
+ * Reset the root hub.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to this structure.
+ * @param fTrueReset This is used to indicate whether we're at VM reset
+ * time and can do real resets or if we're at any other
+ * time where that isn't such a good idea.
+ * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion!
+ * @thread EMT
+ */
+static DECLCALLBACK(int) xhciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fTrueReset)
+{
+ PXHCIROOTHUBR3 pRh = RT_FROM_MEMBER(pInterface, XHCIROOTHUBR3, IRhPort);
+ PXHCICC pThisCC = pRh->pXhciR3;
+ PPDMDEVINS pDevIns = pThisCC->pDevIns;
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+
+ Log(("xhciR3RhReset fTrueReset=%d\n", fTrueReset));
+ int const rcLock = PDMDevHlpCritSectEnter(pDevIns, pDevIns->pCritSectRoR3, VERR_IGNORED);
+ AssertRCReturn(rcLock, rcLock);
+
+ /* Soft reset first */
+ xhciR3DoReset(pThis, pThisCC, XHCI_USB_SUSPEND, false /* N/A */);
+
+ /*
+ * We're pretending to _reattach_ the devices without resetting them.
+ * Except, during VM reset where we use the opportunity to do a proper
+ * reset before the guest comes along and expects things.
+ *
+ * However, it's very very likely that we're not doing the right thing
+ * here when end up here on request from the guest (USB Reset state).
+ * The docs talk about root hub resetting, however what exact behaviour
+ * in terms of root hub status and changed bits, and HC interrupts aren't
+ * stated clearly. IF we get trouble and see the guest doing "USB Resets"
+ * we will have to look into this. For the time being we stick with simple.
+ */
+ for (unsigned iPort = pRh->uPortBase; iPort < XHCI_NDP_CFG(pThis); iPort++)
+ {
+ if (pThisCC->aPorts[iPort].fAttached)
+ {
+ ASMAtomicOrU32(&pThis->aPorts[iPort].portsc, XHCI_PORT_CCS | XHCI_PORT_CSC);
+ if (fTrueReset)
+ VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), fTrueReset,
+ xhciR3RhResetDoneOneDev, pDevIns, PDMDevHlpGetVM(pDevIns));
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, pDevIns->pCritSectRoR3);
+ return VINF_SUCCESS;
+}
+
+#endif /* IN_RING3 */
+
+
+
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+/* xHCI Operational Register access routines */
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+
+
+
+/**
+ * Read the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcUsbcmd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdUsbCmd);
+ *pu32Value = pThis->cmd;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBCMD register of the host controller.
+ */
+static VBOXSTRICTRC HcUsbcmd_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef IN_RING3
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+#endif
+ RT_NOREF(iReg);
+ STAM_COUNTER_INC(&pThis->StatWrUsbCmd);
+#ifdef LOG_ENABLED
+ Log(("HcUsbcmd_w old=%x new=%x\n", pThis->cmd, val));
+ if (val & XHCI_CMD_RS)
+ Log((" XHCI_CMD_RS\n"));
+ if (val & XHCI_CMD_HCRST)
+ Log((" XHCI_CMD_HCRST\n"));
+ if (val & XHCI_CMD_INTE )
+ Log((" XHCI_CMD_INTE\n"));
+ if (val & XHCI_CMD_HSEE)
+ Log((" XHCI_CMD_HSEE\n"));
+ if (val & XHCI_CMD_LCRST)
+ Log((" XHCI_CMD_LCRST\n"));
+ if (val & XHCI_CMD_CSS)
+ Log((" XHCI_CMD_CSS\n"));
+ if (val & XHCI_CMD_CRS)
+ Log((" XHCI_CMD_CRS\n"));
+ if (val & XHCI_CMD_EWE)
+ Log((" XHCI_CMD_EWE\n"));
+ if (val & XHCI_CMD_EU3S)
+ Log((" XHCI_CMD_EU3S\n"));
+#endif
+
+ if (val & ~XHCI_CMD_MASK)
+ Log(("Unknown USBCMD bits %#x are set!\n", val & ~XHCI_CMD_MASK));
+
+ uint32_t old_cmd = pThis->cmd;
+#ifdef IN_RING3
+ pThis->cmd = val;
+#endif
+
+ if (val & XHCI_CMD_HCRST)
+ {
+#ifdef IN_RING3
+ LogRel(("xHCI: Hardware reset\n"));
+ xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else if (val & XHCI_CMD_LCRST)
+ {
+#ifdef IN_RING3
+ LogRel(("xHCI: Software reset\n"));
+ xhciR3DoReset(pThis, pThisCC, XHCI_USB_SUSPEND, false /* N/A */);
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+ else if (pThis->status & XHCI_STATUS_HCE)
+ {
+ /* If HCE is set, don't restart the controller. Only a reset
+ * will clear the HCE bit.
+ */
+ Log(("xHCI: HCE bit set, ignoring USBCMD register changes!\n"));
+ pThis->cmd = old_cmd;
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ /* See what changed and take action on that. First the R/S bit. */
+ uint32_t old_state = old_cmd & XHCI_CMD_RS;
+ uint32_t new_state = val & XHCI_CMD_RS;
+
+ if (old_state != new_state)
+ {
+#ifdef IN_RING3
+ switch (new_state)
+ {
+ case XHCI_CMD_RS:
+ LogRel(("xHCI: USB Operational\n"));
+ xhciR3BusStart(pDevIns, pThis, pThisCC);
+ break;
+ case 0:
+ xhciR3BusStop(pDevIns, pThis, pThisCC);
+ LogRel(("xHCI: USB Suspended\n"));
+ break;
+ }
+#else
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif
+ }
+
+ /* Check EWE (Enable MFINDEX Wraparound Event) changes. */
+ old_state = old_cmd & XHCI_CMD_EWE;
+ new_state = val & XHCI_CMD_EWE;
+
+ if (old_state != new_state)
+ {
+ switch (new_state)
+ {
+ case XHCI_CMD_EWE:
+ Log(("xHCI: MFINDEX Wrap timer started\n"));
+ xhciSetWrapTimer(pDevIns, pThis);
+ break;
+ case 0:
+ PDMDevHlpTimerStop(pDevIns, pThis->hWrapTimer);
+ Log(("xHCI: MFINDEX Wrap timer stopped\n"));
+ break;
+ }
+ }
+
+ /* INTE transitions need to twiddle interrupts. */
+ old_state = old_cmd & XHCI_CMD_INTE;
+ new_state = val & XHCI_CMD_INTE;
+ if (old_state != new_state)
+ {
+ switch (new_state)
+ {
+ case XHCI_CMD_INTE:
+ /* Check whether the event interrupt bit is set and trigger an interrupt. */
+ if (pThis->status & XHCI_STATUS_EINT)
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH);
+ break;
+ case 0:
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW);
+ break;
+ }
+ }
+
+ /* We currently do nothing for state save/restore. If we did, the CSS/CRS command bits
+ * would set the SSS/RSS status bits until the operation is done. The CSS/CRS bits are
+ * never read as one.
+ */
+ /// @todo 4.9.4 describes internal state that needs to be saved/restored:
+ /// ERSTE, ERST Count, EREP, and TRB Count
+ /// Command Ring Dequeue Pointer?
+ if (val & XHCI_CMD_CSS)
+ {
+ Log(("xHCI: Save State requested\n"));
+ val &= ~XHCI_CMD_CSS;
+ }
+
+ if (val & XHCI_CMD_CRS)
+ {
+ Log(("xHCI: Restore State requested\n"));
+ val &= ~XHCI_CMD_CRS;
+ }
+ }
+#ifndef IN_RING3
+ pThis->cmd = val;
+#endif
+ return VINF_SUCCESS;
+}
+
+#ifdef LOG_ENABLED
+static void HcUsbstsLogBits(uint32_t val)
+{
+ if (val & XHCI_STATUS_HCH)
+ Log((" XHCI_STATUS_HCH (HC Halted)\n"));
+ if (val & XHCI_STATUS_HSE)
+ Log((" XHCI_STATUS_HSE (Host System Error)\n"));
+ if (val & XHCI_STATUS_EINT)
+ Log((" XHCI_STATUS_EINT (Event Interrupt)\n"));
+ if (val & XHCI_STATUS_PCD)
+ Log((" XHCI_STATUS_PCD (Port Change Detect)\n"));
+ if (val & XHCI_STATUS_SSS)
+ Log((" XHCI_STATUS_SSS (Save State Status)\n"));
+ if (val & XHCI_STATUS_RSS)
+ Log((" XHCI_STATUS_RSS (Restore State Status)\n"));
+ if (val & XHCI_STATUS_SRE)
+ Log((" XHCI_STATUS_SRE (Save/Restore Error)\n"));
+ if (val & XHCI_STATUS_CNR)
+ Log((" XHCI_STATUS_CNR (Controller Not Ready)\n"));
+ if (val & XHCI_STATUS_HCE)
+ Log((" XHCI_STATUS_HCE (Host Controller Error)\n"));
+}
+#endif
+
+/**
+ * Read the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcUsbsts_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+#ifdef LOG_ENABLED
+ Log(("HcUsbsts_r current value %x\n", pThis->status));
+ HcUsbstsLogBits(pThis->status);
+#endif
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdUsbSts);
+
+ *pu32Value = pThis->status;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write to the USBSTS register of the host controller.
+ */
+static VBOXSTRICTRC HcUsbsts_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+#ifdef LOG_ENABLED
+ Log(("HcUsbsts_w current value %x; new %x\n", pThis->status, val));
+ HcUsbstsLogBits(val);
+#endif
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrUsbSts);
+
+ if ( (val & ~XHCI_STATUS_WRMASK)
+ && val != 0xffffffff /* Ignore clear-all-like requests. */)
+ Log(("Unknown USBSTS bits %#x are set!\n", val & ~XHCI_STATUS_WRMASK));
+
+ /* Most bits are read-only. */
+ val &= XHCI_STATUS_WRMASK;
+
+ /* "The Host Controller Driver may clear specific bits in this
+ * register by writing '1' to bit positions to be cleared"
+ */
+ ASMAtomicAndU32(&pThis->status, ~val);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the PAGESIZE register of the host controller.
+ */
+static VBOXSTRICTRC HcPagesize_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdPageSize);
+ *pu32Value = 1; /* 2^(bit n + 12) -> 4K page size only. */
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the DNCTRL (Device Notification Control) register.
+ */
+static VBOXSTRICTRC HcDevNotifyCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdDevNotifyCtrl);
+ *pu32Value = pThis->dnctrl;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the DNCTRL (Device Notification Control) register.
+ */
+static VBOXSTRICTRC HcDevNotifyCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrDevNotifyCtrl);
+ pThis->dnctrl = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the low dword of CRCR (Command Ring Control) register.
+ */
+static VBOXSTRICTRC HcCmdRingCtlLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlLo);
+ *pu32Value = (uint32_t)(pThis->crcr & XHCI_CRCR_RD_MASK);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the low dword of CRCR (Command Ring Control) register.
+ */
+static VBOXSTRICTRC HcCmdRingCtlLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(iReg);
+ STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlLo);
+ /* NB: A dword write to the low half clears the high half. */
+
+ /* Sticky Abort/Stop bits - update register and kick the worker thread. */
+ if (val & (XHCI_CRCR_CA | XHCI_CRCR_CS))
+ {
+ pThis->crcr |= val & (XHCI_CRCR_CA | XHCI_CRCR_CS);
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0);
+ }
+
+ /*
+ * If the command ring is not running, the internal dequeue pointer
+ * and the cycle state is updated. Otherwise the update is ignored.
+ */
+ if (!(pThis->crcr & XHCI_CRCR_CRR))
+ {
+ pThis->crcr = (pThis->crcr & ~XHCI_CRCR_UPD_MASK) | (val & XHCI_CRCR_UPD_MASK);
+ /// @todo cmdr_dqp: atomic? volatile?
+ pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK;
+ pThis->cmdr_ccs = pThis->crcr & XHCI_CRCR_RCS;
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the high dword of CRCR (Command Ring Control) register.
+ */
+static VBOXSTRICTRC HcCmdRingCtlHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdCmdRingCtlHi);
+ *pu32Value = pThis->crcr >> 32;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the high dword of CRCR (Command Ring Control) register.
+ */
+static VBOXSTRICTRC HcCmdRingCtlHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrCmdRingCtlHi);
+ if (!(pThis->crcr & XHCI_CRCR_CRR))
+ {
+ pThis->crcr = ((uint64_t)val << 32) | (uint32_t)pThis->crcr;
+ pThis->cmdr_dqp = pThis->crcr & XHCI_CRCR_ADDR_MASK;
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the low dword of the DCBAAP register.
+ */
+static VBOXSTRICTRC HcDevCtxBAAPLo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapLo);
+ *pu32Value = (uint32_t)pThis->dcbaap;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the low dword of the DCBAAP register.
+ */
+static VBOXSTRICTRC HcDevCtxBAAPLo_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapLo);
+ /* NB: A dword write to the low half clears the high half. */
+ /// @todo Should this mask off the reserved bits?
+ pThis->dcbaap = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the high dword of the DCBAAP register.
+ */
+static VBOXSTRICTRC HcDevCtxBAAPHi_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdDevCtxBaapHi);
+ *pu32Value = pThis->dcbaap >> 32;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the high dword of the DCBAAP register.
+ */
+static VBOXSTRICTRC HcDevCtxBAAPHi_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrDevCtxBaapHi);
+ pThis->dcbaap = ((uint64_t)val << 32) | (uint32_t)pThis->dcbaap;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the CONFIG register.
+ */
+static VBOXSTRICTRC HcConfig_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatRdConfig);
+ *pu32Value = pThis->config;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the CONFIG register.
+ */
+static VBOXSTRICTRC HcConfig_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t val)
+{
+ RT_NOREF(pDevIns, iReg);
+ STAM_COUNTER_INC(&pThis->StatWrConfig);
+ /// @todo side effects?
+ pThis->config = val;
+ return VINF_SUCCESS;
+}
+
+
+
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+/* xHCI Port Register access routines */
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+
+
+
+/**
+ * Read the PORTSC register.
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value)
+{
+ PXHCIHUBPORT p = &pThis->aPorts[iPort];
+ RT_NOREF(pDevIns);
+ STAM_COUNTER_INC(&pThis->StatRdPortStatusCtrl);
+
+ Assert(!(pThis->hcc_params & XHCI_HCC_PPC));
+
+ if (p->portsc & XHCI_PORT_PR)
+ {
+/// @todo Probably not needed?
+#ifdef IN_RING3
+ Log2(("HcPortStatusCtrl_r(): port %u: Impatient guest!\n", IDX_TO_ID(iPort)));
+ RTThreadYield();
+#else
+ Log2(("HcPortStatusCtrl_r: yield -> VINF_IOM_R3_MMIO_READ\n"));
+ return VINF_IOM_R3_MMIO_READ;
+#endif
+ }
+
+ /* The WPR bit is always read as zero. */
+ *pu32Value = p->portsc & ~XHCI_PORT_WPR;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the PORTSC register.
+ */
+static VBOXSTRICTRC HcPortStatusCtrl_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val)
+{
+ PXHCIHUBPORT p = &pThis->aPorts[iPort];
+#ifdef IN_RING3
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+#endif
+ STAM_COUNTER_INC(&pThis->StatWrPortStatusCtrl);
+
+ /* If no register change results, we're done. */
+ if ( p->portsc == val
+ && !(val & XHCI_PORT_CHANGE_MASK))
+ return VINF_SUCCESS;
+
+ /* If port state is not changing (status bits are being cleared etc.), we can do it in any context.
+ * This case occurs when the R/W control bits are not changing and the W1C bits are not being set.
+ */
+ if ( (p->portsc & XHCI_PORT_CTL_RW_MASK) == (val & XHCI_PORT_CTL_RW_MASK)
+ && !(val & XHCI_PORT_CTL_W1_MASK))
+ {
+ Log(("HcPortStatusCtrl_w port %u (status only): old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val));
+
+ if (val & XHCI_PORT_RESERVED)
+ Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED));
+
+ /* A write to clear any of the change notification bits. */
+ if (val & XHCI_PORT_CHANGE_MASK)
+ p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK);
+
+ /* Update the wake mask. */
+ p->portsc &= ~XHCI_PORT_WAKE_MASK;
+ p->portsc |= val & XHCI_PORT_WAKE_MASK;
+
+ /* There may still be differences between 'portsc' and 'val' in
+ * the R/O bits; that does not count as a register change and is fine.
+ * The RW1x control bits are not considered either since those only matter
+ * if set in 'val'. Since the LWS bit was not set, the PLS bits should not
+ * be compared. The port change bits may differ as well since the guest
+ * could be clearing only some or none of them.
+ */
+ AssertMsg(!(val & XHCI_PORT_CTL_W1_MASK), ("val=%X\n", val));
+ AssertMsg(!(val & XHCI_PORT_LWS), ("val=%X\n", val));
+ AssertMsg((val & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)) == (p->portsc & ~(XHCI_PORT_RO_MASK|XHCI_PORT_CTL_W1_MASK|XHCI_PORT_PLS_MASK|XHCI_PORT_CHANGE_MASK)), ("val=%X vs. portsc=%X\n", val, p->portsc));
+ return VINF_SUCCESS;
+ }
+
+ /* Actual USB port state changes need to be done in R3. */
+#ifdef IN_RING3
+ Log(("HcPortStatusCtrl_w port %u: old=%x new=%x\n", IDX_TO_ID(iPort), p->portsc, val));
+ Assert(!(pThis->hcc_params & XHCI_HCC_PPC));
+ Assert(p->portsc & XHCI_PORT_PP);
+
+ if (val & XHCI_PORT_RESERVED)
+ Log(("Reserved bits set %x!\n", val & XHCI_PORT_RESERVED));
+
+ /* A write to clear any of the change notification bits. */
+ if (val & XHCI_PORT_CHANGE_MASK)
+ p->portsc &= ~(val & XHCI_PORT_CHANGE_MASK);
+
+ /* Writing the Port Enable/Disable bit as 1 disables a port; it cannot be
+ * enabled that way. Writing the bit as zero does does nothing.
+ */
+ if ((val & XHCI_PORT_PED) && (p->portsc & XHCI_PORT_PED))
+ {
+ p->portsc &= ~XHCI_PORT_PED;
+ Log(("HcPortStatusCtrl_w(): port %u: DISABLE\n", IDX_TO_ID(iPort)));
+ }
+
+ if (!(val & XHCI_PORT_PP) && (p->portsc & XHCI_PORT_PP))
+ {
+ p->portsc &= ~XHCI_PORT_PP;
+ Log(("HcPortStatusCtrl_w(): port %u: POWER OFF\n", IDX_TO_ID(iPort)));
+ }
+
+ /* Warm Port Reset - USB3 only; see 4.19.5.1. */
+ if ((val & XHCI_PORT_WPR) && IS_USB3_PORT_IDX_SHR(pThis, iPort))
+ {
+ Log(("HcPortStatusCtrl_w(): port %u: WARM RESET\n", IDX_TO_ID(iPort)));
+ if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR | XHCI_PORT_WPR))
+ {
+ PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort);
+
+ VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns));
+ xhciR3PortResetDone(pDevIns, iPort);
+ }
+ }
+
+ if (val & XHCI_PORT_PR)
+ {
+ Log(("HcPortStatusCtrl_w(): port %u: RESET\n", IDX_TO_ID(iPort)));
+ if (xhciR3RhPortSetIfConnected(pThis, iPort, XHCI_PORT_PR))
+ {
+ PXHCIROOTHUBR3 pRh = GET_PORT_PRH(pThisCC, iPort);
+
+ VUSBIRhDevReset(pRh->pIRhConn, GET_VUSB_PORT_FROM_XHCI_PORT(pRh, iPort), false /* don't reset on linux */, NULL /* sync */, NULL, PDMDevHlpGetVM(pDevIns));
+ xhciR3PortResetDone(pDevIns, iPort);
+ }
+ else if (p->portsc & XHCI_PORT_PR)
+ {
+ /* the guest is getting impatient. */
+ Log2(("HcPortStatusCtrl_w(): port %u: Impatient guest!\n", IDX_TO_ID(iPort)));
+ RTThreadYield();
+ }
+ }
+
+ /// @todo Do some sanity checking on the new link state?
+ /* Update the link state if requested. */
+ if (val & XHCI_PORT_LWS)
+ {
+ unsigned old_pls;
+ unsigned new_pls;
+
+ old_pls = (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT;
+ new_pls = (val & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT;
+
+ p->portsc &= ~XHCI_PORT_PLS_MASK;
+ p->portsc |= new_pls << XHCI_PORT_PLS_SHIFT;
+ Log2(("HcPortStatusCtrl_w(): port %u: Updating link state from %u to %u\n", IDX_TO_ID(iPort), old_pls, new_pls));
+ /* U3->U0 (USB3) and Resume->U0 transitions set the PLC flag. See 4.15.2.2 */
+ if (new_pls == XHCI_PLS_U0)
+ if (old_pls == XHCI_PLS_U3 || old_pls == XHCI_PLS_RESUME)
+ {
+ p->portsc |= XHCI_PORT_PLC;
+ xhciR3GenPortChgEvent(pDevIns, pThis, IDX_TO_ID(iPort));
+ }
+ }
+
+ /// @todo which other bits can we safely ignore?
+
+ /* Update the wake mask. */
+ p->portsc &= ~XHCI_PORT_WAKE_MASK;
+ p->portsc |= val & XHCI_PORT_WAKE_MASK;
+
+ return VINF_SUCCESS;
+#else /* !IN_RING3 */
+ RT_NOREF(pDevIns);
+ return VINF_IOM_R3_MMIO_WRITE;
+#endif /* !IN_RING3 */
+}
+
+
+/**
+ * Read the PORTPMSC register.
+ */
+static VBOXSTRICTRC HcPortPowerMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value)
+{
+ PXHCIHUBPORT p = &pThis->aPorts[iPort];
+ RT_NOREF(pDevIns);
+ STAM_COUNTER_INC(&pThis->StatRdPortPowerMgmt);
+
+ *pu32Value = p->portpm;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Write the PORTPMSC register.
+ */
+static VBOXSTRICTRC HcPortPowerMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t val)
+{
+ PXHCIHUBPORT p = &pThis->aPorts[iPort];
+ RT_NOREF(pDevIns);
+ STAM_COUNTER_INC(&pThis->StatWrPortPowerMgmt);
+
+ /// @todo anything to do here?
+ p->portpm = val;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read the PORTLI register.
+ */
+static VBOXSTRICTRC HcPortLinkInfo_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value)
+{
+ PXHCIHUBPORT p = &pThis->aPorts[iPort];
+ RT_NOREF(pDevIns);
+ STAM_COUNTER_INC(&pThis->StatRdPortLinkInfo);
+
+ /* The link information is R/O; we probably can't get it at all. If we
+ * do maintain it for USB3 ports, we also have to reset it (5.4.10).
+ */
+ *pu32Value = p->portli;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the reserved register. Linux likes to do this.
+ */
+static VBOXSTRICTRC HcPortRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iPort, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, iPort);
+ STAM_COUNTER_INC(&pThis->StatRdPortRsvd);
+ *pu32Value = 0;
+ return VINF_SUCCESS;
+}
+
+
+
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+/* xHCI Interrupter Register access routines */
+/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
+
+
+
+/**
+ * Read the IMAN register.
+ */
+static VBOXSTRICTRC HcIntrMgmt_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdIntrMgmt);
+
+ *pu32Value = ip->iman;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the IMAN register.
+ */
+static VBOXSTRICTRC HcIntrMgmt_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ uint32_t uNew = val & XHCI_IMAN_VALID_MASK;
+ STAM_COUNTER_INC(&pThis->StatWrIntrMgmt);
+
+ if (val & ~XHCI_IMAN_VALID_MASK)
+ Log(("Reserved bits set %x!\n", val & ~XHCI_IMAN_VALID_MASK));
+
+ /* If the Interrupt Pending (IP) bit is set, writing one clears it.
+ * Note that when MSIs are enabled, the bit auto-clears almost immediately.
+ */
+ if (val & ip->iman & XHCI_IMAN_IP)
+ {
+ Log2(("clearing interrupt on interrupter %u\n", ip->index));
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_LOW);
+ STAM_COUNTER_INC(&pThis->StatIntrsCleared);
+ uNew &= ~XHCI_IMAN_IP;
+ }
+ else
+ {
+ /* Preserve the current IP bit. */
+ uNew = (uNew & ~XHCI_IMAN_IP) | (ip->iman & XHCI_IMAN_IP);
+ }
+
+ /* Trigger an interrupt if the IP bit is set and IE transitions from 0 to 1. */
+ if ( (uNew & XHCI_IMAN_IE)
+ && !(ip->iman & XHCI_IMAN_IE)
+ && (ip->iman & XHCI_IMAN_IP)
+ && (pThis->cmd & XHCI_CMD_INTE))
+ PDMDevHlpPCISetIrq(pDevIns, 0, PDM_IRQ_LEVEL_HIGH);
+
+ ip->iman = uNew;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the IMOD register.
+ */
+static VBOXSTRICTRC HcIntrMod_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdIntrMod);
+
+ *pu32Value = ip->imod;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the IMOD register.
+ */
+static VBOXSTRICTRC HcIntrMod_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatWrIntrMod);
+
+ /// @todo Does writing a zero to IMODC/IMODI potentially trigger
+ /// an interrupt?
+ ip->imod = val;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the ERSTSZ register.
+ */
+static VBOXSTRICTRC HcEvtRSTblSize_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRstblSize);
+
+ *pu32Value = ip->erstsz;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the ERSTSZ register.
+ */
+static VBOXSTRICTRC HcEvtRSTblSize_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatWrEvtRstblSize);
+
+ if (val & ~XHCI_ERSTSZ_MASK)
+ Log(("Reserved bits set %x!\n", val & ~XHCI_ERSTSZ_MASK));
+ if (val > XHCI_ERSTMAX)
+ Log(("ERSTSZ (%u) > ERSTMAX (%u)!\n", val, XHCI_ERSTMAX));
+
+ /* Enforce the maximum size. */
+ ip->erstsz = RT_MIN(val, XHCI_ERSTMAX);
+
+ if (!ip->index && !ip->erstsz) /* Windows 8 does this temporarily. Thanks guys... */
+ Log(("ERSTSZ is zero for primary interrupter: undefined behavior!\n"));
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the reserved register. Linux likes to do this.
+ */
+static VBOXSTRICTRC HcEvtRsvd_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis, ip);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRsvd);
+ *pu32Value = 0;
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the low dword of the ERSTBA register.
+ */
+static VBOXSTRICTRC HcEvtRSTblBaseLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseLo);
+
+ *pu32Value = (uint32_t)ip->erstba;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Write the low dword of the ERSTBA register.
+ */
+static VBOXSTRICTRC HcEvtRSTblBaseLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseLo);
+
+ if (val & ~pThis->erst_addr_mask)
+ Log(("Reserved bits set %x!\n", val & ~pThis->erst_addr_mask));
+
+ /* NB: A dword write to the low half clears the high half. */
+ ip->erstba = val & pThis->erst_addr_mask;
+
+ /* Initialize the internal event ring state. */
+ ip->evtr_pcs = 1;
+ ip->erst_idx = 0;
+ ip->ipe = false;
+
+ /* Fetch the first ERST entry now. Not later! That "sets the Event Ring
+ * State Machine:EREP Advancement to the Start state"
+ */
+ xhciFetchErstEntry(pDevIns, pThis, ip);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the high dword of the ERSTBA register.
+ */
+static VBOXSTRICTRC HcEvtRSTblBaseHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRsTblBaseHi);
+
+ *pu32Value = (uint32_t)(ip->erstba >> 32);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the high dword of the ERSTBA register.
+ */
+static VBOXSTRICTRC HcEvtRSTblBaseHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ STAM_COUNTER_INC(&pThis->StatWrEvtRsTblBaseHi);
+
+ /* Update the high dword while preserving the low one. */
+ ip->erstba = ((uint64_t)val << 32) | (uint32_t)ip->erstba;
+
+ /* We shouldn't be doing this when AC64 is set. But High Sierra
+ * ignores that because it "knows" the xHC handles 64-bit addressing,
+ * so we're going to assume that OSes are not going to write junk into
+ * ERSTBAH when they don't see AC64 set.
+ */
+ xhciFetchErstEntry(pDevIns, pThis, ip);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Read the low dword of the ERDP register.
+ */
+static VBOXSTRICTRC HcEvtRingDeqPtrLo_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pThis);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrLo);
+
+ /* Lock to avoid incomplete update being seen. */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_READ);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ *pu32Value = (uint32_t)ip->erdp;
+
+ PDMDevHlpCritSectLeave(pDevIns, &ip->lock);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the low dword of the ERDP register.
+ */
+static VBOXSTRICTRC HcEvtRingDeqPtrLo_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ uint64_t old_erdp;
+ uint64_t new_erdp;
+ STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrLo);
+
+ /* NB: A dword write to the low half clears the high half.
+ * The high dword should be ignored when AC64=0, but High Sierra
+ * does not care what we report. Therefore a write to the low dword
+ * handles all the control bits and a write to the high dword still
+ * updates the ERDP address. On a 64-bit host, there must be a
+ * back-to-back low dword + high dword access. We are going to boldly
+ * assume that the guest will not place the event ring across the 4G
+ * boundary (i.e. storing the bottom part in the firmware ROM).
+ */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ old_erdp = ip->erdp & XHCI_ERDP_ADDR_MASK; /* Remember old ERDP address. */
+ new_erdp = ip->erdp & XHCI_ERDP_EHB; /* Preserve EHB */
+
+ /* If the Event Handler Busy (EHB) bit is set, writing a one clears it. */
+ if (val & ip->erdp & XHCI_ERDP_EHB)
+ {
+ Log2(("clearing EHB on interrupter %p\n", ip));
+ new_erdp &= ~XHCI_ERDP_EHB;
+ }
+ /// @todo Check if this might inadvertently set EHB!
+
+ new_erdp |= val & ~XHCI_ERDP_EHB;
+ ip->erdp = new_erdp;
+
+ /* Check if the ERDP changed. See workaround below. */
+ if (old_erdp != (new_erdp & XHCI_ERDP_ADDR_MASK))
+ ip->erdp_rewrites = 0;
+ else
+ ++ip->erdp_rewrites;
+
+ LogFlowFunc(("ERDP: %RGp, EREP: %RGp\n", (RTGCPHYS)(ip->erdp & XHCI_ERDP_ADDR_MASK), (RTGCPHYS)ip->erep));
+
+ if ((ip->erdp & XHCI_ERDP_ADDR_MASK) == ip->erep)
+ {
+ Log2(("Event Ring empty, clearing IPE\n"));
+ ip->ipe = false;
+ }
+ else if (ip->ipe && (val & XHCI_ERDP_EHB))
+ {
+ /* EHB is being cleared but the ring isn't empty and IPE is still set. */
+ if (RT_UNLIKELY(old_erdp == (new_erdp & XHCI_ERDP_ADDR_MASK) && ip->erdp_rewrites > 2))
+ {
+ /* If guest does not advance the ERDP, do not trigger an interrupt
+ * again. Workaround for buggy xHCI initialization in Linux 4.6 which
+ * enables interrupts before setting up internal driver state. That
+ * leads to the guest IRQ handler not actually handling events and
+ * infinitely re-triggering interrupts. However, only do this if the
+ * guest has already written the same ERDP value a few times. The Intel
+ * xHCI driver always writes the same ERDP twice and we must still
+ * re-trigger interrupts in that case.
+ * See @bugref{8546}.
+ */
+ Log2(("Event Ring not empty, ERDP not advanced, not re-triggering interrupt!\n"));
+ ip->ipe = false;
+ }
+ else
+ {
+ Log2(("Event Ring not empty, re-triggering interrupt\n"));
+ xhciSetIntr(pDevIns, pThis, ip);
+ }
+ }
+
+ PDMDevHlpCritSectLeave(pDevIns, &ip->lock);
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Read the high dword of the ERDP register.
+ */
+static VBOXSTRICTRC HcEvtRingDeqPtrHi_r(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t *pu32Value)
+{
+ RT_NOREF(pDevIns, pThis);
+ STAM_COUNTER_INC(&pThis->StatRdEvtRingDeqPtrHi);
+
+ *pu32Value = (uint32_t)(ip->erdp >> 32);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Write the high dword of the ERDP register.
+ */
+static VBOXSTRICTRC HcEvtRingDeqPtrHi_w(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR ip, uint32_t val)
+{
+ RT_NOREF(pThis);
+ STAM_COUNTER_INC(&pThis->StatWrEvtRingDeqPtrHi);
+
+ /* See HcEvtRingDeqPtrLo_w for semantics. */
+ int rc = PDMDevHlpCritSectEnter(pDevIns, &ip->lock, VINF_IOM_R3_MMIO_WRITE);
+ if (rc != VINF_SUCCESS)
+ return rc;
+
+ /* Update the high dword while preserving the low one. */
+ ip->erdp = ((uint64_t)val << 32) | (uint32_t)ip->erdp;
+
+ PDMDevHlpCritSectLeave(pDevIns, &ip->lock);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * xHCI register access routines.
+ */
+typedef struct
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnRead )(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnWrite)(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t iReg, uint32_t u32Value);
+} XHCIREGACC;
+
+/**
+ * xHCI interrupter register access routines.
+ */
+typedef struct
+{
+ const char *pszName;
+ VBOXSTRICTRC (*pfnIntrRead )(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t *pu32Value);
+ VBOXSTRICTRC (*pfnIntrWrite)(PPDMDEVINS pDevIns, PXHCI pThis, PXHCIINTRPTR pIntr, uint32_t u32Value);
+} XHCIINTRREGACC;
+
+/**
+ * Operational registers descriptor table.
+ */
+static const XHCIREGACC g_aOpRegs[] =
+{
+ {"USBCMD" , HcUsbcmd_r, HcUsbcmd_w },
+ {"USBSTS", HcUsbsts_r, HcUsbsts_w },
+ {"PAGESIZE", HcPagesize_r, NULL },
+ {"Unused", NULL, NULL },
+ {"Unused", NULL, NULL },
+ {"DNCTRL", HcDevNotifyCtrl_r, HcDevNotifyCtrl_w },
+ {"CRCRL", HcCmdRingCtlLo_r, HcCmdRingCtlLo_w },
+ {"CRCRH", HcCmdRingCtlHi_r, HcCmdRingCtlHi_w },
+ {"Unused", NULL, NULL },
+ {"Unused", NULL, NULL },
+ {"Unused", NULL, NULL },
+ {"Unused", NULL, NULL },
+ {"DCBAAPL", HcDevCtxBAAPLo_r, HcDevCtxBAAPLo_w },
+ {"DCBAAPH", HcDevCtxBAAPHi_r, HcDevCtxBAAPHi_w },
+ {"CONFIG", HcConfig_r, HcConfig_w }
+};
+
+
+/**
+ * Port registers descriptor table (for a single port). The number of ports
+ * and their associated registers depends on the NDP value.
+ */
+static const XHCIREGACC g_aPortRegs[] =
+{
+ /*
+ */
+ {"PORTSC", HcPortStatusCtrl_r, HcPortStatusCtrl_w },
+ {"PORTPMSC", HcPortPowerMgmt_r, HcPortPowerMgmt_w },
+ {"PORTLI", HcPortLinkInfo_r, NULL },
+ {"Reserved", HcPortRsvd_r, NULL }
+};
+AssertCompile(RT_ELEMENTS(g_aPortRegs) * sizeof(uint32_t) == 0x10);
+
+
+/**
+ * Interrupter runtime registers descriptor table (for a single interrupter).
+ * The number of interrupters depends on the XHCI_NINTR value.
+ */
+static const XHCIINTRREGACC g_aIntrRegs[] =
+{
+ {"IMAN", HcIntrMgmt_r, HcIntrMgmt_w },
+ {"IMOD", HcIntrMod_r, HcIntrMod_w },
+ {"ERSTSZ", HcEvtRSTblSize_r, HcEvtRSTblSize_w },
+ {"Reserved", HcEvtRsvd_r, NULL },
+ {"ERSTBAL", HcEvtRSTblBaseLo_r, HcEvtRSTblBaseLo_w },
+ {"ERSTBAH", HcEvtRSTblBaseHi_r, HcEvtRSTblBaseHi_w },
+ {"ERDPL", HcEvtRingDeqPtrLo_r, HcEvtRingDeqPtrLo_w },
+ {"ERDPH", HcEvtRingDeqPtrHi_r, HcEvtRingDeqPtrHi_w }
+};
+AssertCompile(RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t) == 0x20);
+
+
+/**
+ * Read the MFINDEX register.
+ */
+static int HcMfIndex_r(PPDMDEVINS pDevIns, PXHCI pThis, uint32_t *pu32Value)
+{
+ uint64_t uNanoTime;
+ uint64_t uMfTime;
+ STAM_COUNTER_INC(&pThis->StatRdMfIndex);
+
+ /* MFINDEX increments once per micro-frame, i.e. 8 times per millisecond
+ * or every 125us. The resolution is only 14 bits, meaning that MFINDEX
+ * wraps around after it reaches 0x3FFF (16383) or every 2048 milliseconds.
+ */
+ /// @todo MFINDEX should only be running when R/S is set. May not matter.
+ uNanoTime = PDMDevHlpTimerGet(pDevIns, pThis->hWrapTimer);
+ uMfTime = uNanoTime / 125000;
+
+ *pu32Value = uMfTime & 0x3FFF;
+ Log2(("MFINDEX read: %u\n", *pu32Value));
+ return VINF_SUCCESS;
+}
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWREAD, Read a MMIO register.}
+ *
+ * @note We only accept 32-bit writes that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) xhciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void *pv, unsigned cb)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ const uint32_t offReg = (uint32_t)off;
+ uint32_t * const pu32 = (uint32_t *)pv;
+ uint32_t iReg;
+ RT_NOREF(pvUser);
+
+ Log2(("xhciRead %RGp (offset %04X) size=%d\n", off, offReg, cb));
+
+ if (offReg < XHCI_CAPS_REG_SIZE)
+ {
+ switch (offReg)
+ {
+ case 0x0: /* CAPLENGTH + HCIVERSION */
+ *pu32 = (pThis->hci_version << 16) | pThis->cap_length;
+ break;
+
+ case 0x4: /* HCSPARAMS1 (structural) */
+ Log2(("HCSPARAMS1 read\n"));
+ *pu32 = pThis->hcs_params1;
+ break;
+
+ case 0x8: /* HCSPARAMS2 (structural) */
+ Log2(("HCSPARAMS2 read\n"));
+ *pu32 = pThis->hcs_params2;
+ break;
+
+ case 0xC: /* HCSPARAMS3 (structural) */
+ Log2(("HCSPARAMS3 read\n"));
+ *pu32 = pThis->hcs_params3;
+ break;
+
+ case 0x10: /* HCCPARAMS1 (caps) */
+ Log2(("HCCPARAMS1 read\n"));
+ *pu32 = pThis->hcc_params;
+ break;
+
+ case 0x14: /* DBOFF (doorbell offset) */
+ Log2(("DBOFF read\n"));
+ *pu32 = pThis->dbell_off;
+ break;
+
+ case 0x18: /* RTSOFF (run-time register offset) */
+ Log2(("RTSOFF read\n"));
+ *pu32 = pThis->rts_off;
+ break;
+
+ case 0x1C: /* HCCPARAMS2 (caps) */
+ Log2(("HCCPARAMS2 read\n"));
+ *pu32 = 0; /* xHCI 1.1 only */
+ break;
+
+ default:
+ Log(("xHCI: Trying to read unknown capability register %u!\n", offReg));
+ STAM_COUNTER_INC(&pThis->StatRdUnknown);
+ return VINF_IOM_MMIO_UNUSED_FF;
+ }
+ STAM_COUNTER_INC(&pThis->StatRdCaps);
+ Log2(("xhciRead %RGp size=%d -> val=%x\n", off, cb, *pu32));
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access (in case of IOM bugs or incorrect MMIO registration).
+ */
+ AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb),
+ VINF_IOM_MMIO_UNUSED_FF /* No idea what really would happen... */);
+ /** r=bird: If you don't have an idea what would happen for non-dword reads,
+ * then the flags passed to IOM when creating the MMIO region are doubtful, right? */
+ AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_IOM_MMIO_UNUSED_FF);
+
+ /*
+ * Validate the register and call the read operator.
+ */
+ VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF;
+ if (offReg >= XHCI_DOORBELL_OFFSET)
+ {
+ /* The doorbell registers are effectively write-only and return 0 when read. */
+ iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2;
+ if (iReg < XHCI_NDS)
+ {
+ STAM_COUNTER_INC(&pThis->StatRdDoorBell);
+ *pu32 = 0;
+ rcStrict = VINF_SUCCESS;
+ Log2(("xhciRead: DBellReg (DB %u) %RGp size=%d -> val=%x (rc=%d)\n", iReg, off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ else if (offReg >= XHCI_RTREG_OFFSET)
+ {
+ /* Run-time registers. */
+ Assert(offReg < XHCI_DOORBELL_OFFSET);
+ /* The MFINDEX register would be interrupter -1... */
+ if (offReg < XHCI_RTREG_OFFSET + RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t))
+ {
+ if (offReg == XHCI_RTREG_OFFSET)
+ rcStrict = HcMfIndex_r(pDevIns, pThis, pu32);
+ else
+ {
+ /* The silly Linux xHCI driver reads the reserved registers. */
+ STAM_COUNTER_INC(&pThis->StatRdUnknown);
+ *pu32 = 0;
+ rcStrict = VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0);
+ const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1;
+
+ if (iIntr < XHCI_NINTR)
+ {
+ iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1);
+ const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg];
+ if (pReg->pfnIntrRead)
+ {
+ PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr];
+ rcStrict = pReg->pfnIntrRead(pDevIns, pThis, pIntr, pu32);
+ Log2(("xhciRead: IntrReg (intr %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ }
+ }
+ else if (offReg >= XHCI_XECP_OFFSET)
+ {
+ /* Extended Capability registers. */
+ Assert(offReg < XHCI_RTREG_OFFSET);
+ uint32_t offXcp = offReg - XHCI_XECP_OFFSET;
+
+ if (offXcp + cb <= RT_MIN(pThis->cbExtCap, sizeof(pThis->abExtCap))) /* can't trust cbExtCap in ring-0. */
+ {
+ *pu32 = *(uint32_t *)&pThis->abExtCap[offXcp];
+ rcStrict = VINF_SUCCESS;
+ }
+ Log2(("xhciRead: ExtCapReg %RGp size=%d -> val=%x (rc=%d)\n", off, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ else
+ {
+ /* Operational registers (incl. port registers). */
+ Assert(offReg < XHCI_XECP_OFFSET);
+ iReg = (offReg - XHCI_CAPS_REG_SIZE) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const XHCIREGACC *pReg = &g_aOpRegs[iReg];
+ if (pReg->pfnRead)
+ {
+ rcStrict = pReg->pfnRead(pDevIns, pThis, iReg, pu32);
+ Log2(("xhciRead: OpReg %RGp (%s) size=%d -> val=%x (rc=%d)\n", off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2))
+ {
+ iReg -= (XHCI_PORT_REG_OFFSET >> 2);
+ const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs);
+ if (iPort < XHCI_NDP_CFG(pThis))
+ {
+ iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1);
+ Assert(iReg < RT_ELEMENTS(g_aPortRegs));
+ const XHCIREGACC *pReg = &g_aPortRegs[iReg];
+ if (pReg->pfnRead)
+ {
+ rcStrict = pReg->pfnRead(pDevIns, pThis, iPort, pu32);
+ Log2(("xhciRead: PortReg (port %u): %RGp (%s) size=%d -> val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ }
+ }
+
+ if (rcStrict != VINF_IOM_MMIO_UNUSED_FF)
+ { /* likely */ }
+ else
+ {
+ STAM_COUNTER_INC(&pThis->StatRdUnknown);
+ Log(("xHCI: Trying to read unimplemented register at offset %04X!\n", offReg));
+ }
+
+ return rcStrict;
+}
+
+
+/**
+ * @callback_method_impl{FNIOMMMIONEWWRITE, Write to a MMIO register.}
+ *
+ * @note We only accept 32-bit writes that are 32-bit aligned.
+ */
+static DECLCALLBACK(VBOXSTRICTRC) xhciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS off, void const *pv, unsigned cb)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ const uint32_t offReg = (uint32_t)off;
+ uint32_t * const pu32 = (uint32_t *)pv;
+ uint32_t iReg;
+ RT_NOREF(pvUser);
+
+ Log2(("xhciWrite %RGp (offset %04X) %x size=%d\n", off, offReg, *(uint32_t *)pv, cb));
+
+ if (offReg < XHCI_CAPS_REG_SIZE)
+ {
+ /* These are read-only */
+ Log(("xHCI: Trying to write to register %u!\n", offReg));
+ STAM_COUNTER_INC(&pThis->StatWrUnknown);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Validate the access (in case of IOM bug or incorrect MMIO registration).
+ */
+ AssertMsgReturn(cb == sizeof(uint32_t), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS);
+ AssertMsgReturn(!(off & 0x3), ("IOM bug? %RGp LB %d\n", off, cb), VINF_SUCCESS);
+
+ /*
+ * Validate the register and call the write operator.
+ */
+ VBOXSTRICTRC rcStrict = VINF_IOM_MMIO_UNUSED_FF;
+ if (offReg >= XHCI_DOORBELL_OFFSET)
+ {
+ /* Let's spring into action... as long as the xHC is running. */
+ iReg = (offReg - XHCI_DOORBELL_OFFSET) >> 2;
+ if ((pThis->cmd & XHCI_CMD_RS) && iReg < XHCI_NDS)
+ {
+ if (iReg == 0)
+ {
+ /* DB0 aka Command Ring. */
+ STAM_COUNTER_INC(&pThis->StatWrDoorBell0);
+ if (*pu32 == 0)
+ {
+ /* Set the Command Ring state to Running if not already set. */
+ if (!(pThis->crcr & XHCI_CRCR_CRR))
+ {
+ Log(("Command ring entered Running state\n"));
+ ASMAtomicOrU64(&pThis->crcr, XHCI_CRCR_CRR);
+ }
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_PROCESS_CMDRING, 0);
+ }
+ else
+ Log2(("Ignoring DB0 write with value %X!\n", *pu32));
+ }
+ else
+ {
+ /* Device context doorbell. Do basic parameter checking to avoid
+ * waking up the worker thread needlessly.
+ */
+ STAM_COUNTER_INC(&pThis->StatWrDoorBellN);
+ uint8_t uDBTarget = *pu32 & XHCI_DB_TGT_MASK;
+ Assert(uDBTarget < 32); /// @todo Report an error? Or just ignore?
+ if (uDBTarget < 32)
+ {
+ Log2(("Ring bell for slot %u, DCI %u\n", iReg, uDBTarget));
+ ASMAtomicOrU32(&pThis->aBellsRung[ID_TO_IDX(iReg)], 1 << uDBTarget);
+ xhciKickWorker(pDevIns, pThis, XHCI_JOB_DOORBELL, *pu32);
+ }
+ else
+ Log2(("Ignoring DB%u write with bad target %u!\n", iReg, uDBTarget));
+ }
+ rcStrict = VINF_SUCCESS;
+ Log2(("xhciWrite: DBellReg (DB %u) %RGp size=%d <- val=%x (rc=%d)\n", iReg, off, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ else if (offReg >= XHCI_RTREG_OFFSET)
+ {
+ /* Run-time registers. */
+ Assert(offReg < XHCI_DOORBELL_OFFSET);
+ /* NB: The MFINDEX register is R/O. */
+ if (offReg >= XHCI_RTREG_OFFSET + (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)))
+ {
+ Assert((offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) > 0);
+ const uint32_t iIntr = (offReg - XHCI_RTREG_OFFSET) / (RT_ELEMENTS(g_aIntrRegs) * sizeof(uint32_t)) - 1;
+
+ if (iIntr < XHCI_NINTR)
+ {
+ iReg = (offReg >> 2) & (RT_ELEMENTS(g_aIntrRegs) - 1);
+ const XHCIINTRREGACC *pReg = &g_aIntrRegs[iReg];
+ if (pReg->pfnIntrWrite)
+ {
+ PXHCIINTRPTR pIntr = &pThis->aInterrupters[iIntr];
+ rcStrict = pReg->pfnIntrWrite(pDevIns, pThis, pIntr, *pu32);
+ Log2(("xhciWrite: IntrReg (intr %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", iIntr, off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ }
+ }
+ else
+ {
+ /* Operational registers (incl. port registers). */
+ Assert(offReg < XHCI_RTREG_OFFSET);
+ iReg = (offReg - pThis->cap_length) >> 2;
+ if (iReg < RT_ELEMENTS(g_aOpRegs))
+ {
+ const XHCIREGACC *pReg = &g_aOpRegs[iReg];
+ if (pReg->pfnWrite)
+ {
+ rcStrict = pReg->pfnWrite(pDevIns, pThis, iReg, *(uint32_t *)pv);
+ Log2(("xhciWrite: OpReg %RGp (%s) size=%d <- val=%x (rc=%d)\n", off, pReg->pszName, cb, *(uint32_t *)pv, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ else if (iReg >= (XHCI_PORT_REG_OFFSET >> 2))
+ {
+ iReg -= (XHCI_PORT_REG_OFFSET >> 2);
+ const uint32_t iPort = iReg / RT_ELEMENTS(g_aPortRegs);
+ if (iPort < XHCI_NDP_CFG(pThis))
+ {
+ iReg = (offReg >> 2) & (RT_ELEMENTS(g_aPortRegs) - 1);
+ Assert(iReg < RT_ELEMENTS(g_aPortRegs));
+ const XHCIREGACC *pReg = &g_aPortRegs[iReg];
+ if (pReg->pfnWrite)
+ {
+ rcStrict = pReg->pfnWrite(pDevIns, pThis, iPort, *pu32);
+ Log2(("xhciWrite: PortReg (port %u): %RGp (%s) size=%d <- val=%x (rc=%d)\n", IDX_TO_ID(iPort), off, pReg->pszName, cb, *pu32, VBOXSTRICTRC_VAL(rcStrict)));
+ }
+ }
+ }
+ }
+
+ if (rcStrict != VINF_IOM_MMIO_UNUSED_FF)
+ { /* likely */ }
+ else
+ {
+ /* Ignore writes to unimplemented or read-only registers. */
+ STAM_COUNTER_INC(&pThis->StatWrUnknown);
+ Log(("xHCI: Trying to write unimplemented or R/O register at offset %04X!\n", offReg));
+ rcStrict = VINF_SUCCESS;
+ }
+
+ return rcStrict;
+}
+
+
+#ifdef IN_RING3
+
+/**
+ * @callback_method_impl{FNTMTIMERDEV,
+ * Provides periodic MFINDEX wrap events. See 4.14.2.}
+ */
+static DECLCALLBACK(void) xhciR3WrapTimer(PPDMDEVINS pDevIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PXHCI pThis = (PXHCI)pvUser;
+ XHCI_EVENT_TRB ed;
+ LogFlow(("xhciR3WrapTimer:\n"));
+ RT_NOREF(hTimer);
+
+ /*
+ * Post the MFINDEX Wrap event and rearm the timer. Only called
+ * when the EWE bit is set in command register.
+ */
+ RT_ZERO(ed);
+ ed.mwe.cc = XHCI_TCC_SUCCESS;
+ ed.mwe.type = XHCI_TRB_MFIDX_WRAP;
+ xhciR3WriteEvent(pDevIns, pThis, &ed, XHCI_PRIMARY_INTERRUPTER, false);
+
+ xhciSetWrapTimer(pDevIns, pThis);
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVSAVEEXEC}
+ */
+static DECLCALLBACK(int) xhciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint32_t iPort;
+ uint32_t iSlot;
+ uint32_t iIntr;
+
+ LogFlow(("xhciR3SaveExec: \n"));
+
+ /* Save HC operational registers. */
+ pHlp->pfnSSMPutU32(pSSM, pThis->cmd);
+ pHlp->pfnSSMPutU32(pSSM, pThis->status);
+ pHlp->pfnSSMPutU32(pSSM, pThis->dnctrl);
+ pHlp->pfnSSMPutU64(pSSM, pThis->crcr);
+ pHlp->pfnSSMPutU64(pSSM, pThis->dcbaap);
+ pHlp->pfnSSMPutU32(pSSM, pThis->config);
+
+ /* Save HC non-register state. */
+ pHlp->pfnSSMPutU64(pSSM, pThis->cmdr_dqp);
+ pHlp->pfnSSMPutBool(pSSM, pThis->cmdr_ccs);
+
+ /* Save per-slot state. */
+ pHlp->pfnSSMPutU32(pSSM, XHCI_NDS);
+ for (iSlot = 0; iSlot < XHCI_NDS; ++iSlot)
+ {
+ pHlp->pfnSSMPutU8 (pSSM, pThis->aSlotState[iSlot]);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aBellsRung[iSlot]);
+ }
+
+ /* Save root hub (port) state. */
+ pHlp->pfnSSMPutU32(pSSM, XHCI_NDP_CFG(pThis));
+ for (iPort = 0; iPort < XHCI_NDP_CFG(pThis); ++iPort)
+ {
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portsc);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aPorts[iPort].portpm);
+ }
+
+ /* Save interrupter state. */
+ pHlp->pfnSSMPutU32(pSSM, XHCI_NINTR);
+ for (iIntr = 0; iIntr < XHCI_NINTR; ++iIntr)
+ {
+ pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].iman);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].imod);
+ pHlp->pfnSSMPutU32(pSSM, pThis->aInterrupters[iIntr].erstsz);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erstba);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erdp);
+ pHlp->pfnSSMPutU64(pSSM, pThis->aInterrupters[iIntr].erep);
+ pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].erst_idx);
+ pHlp->pfnSSMPutU16(pSSM, pThis->aInterrupters[iIntr].trb_count);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].evtr_pcs);
+ pHlp->pfnSSMPutBool(pSSM, pThis->aInterrupters[iIntr].ipe);
+ }
+
+ /* Terminator marker. */
+ pHlp->pfnSSMPutU32(pSSM, UINT32_MAX);
+
+ /* If not continuing after save, force HC into non-running state to avoid trouble later. */
+ if (pHlp->pfnSSMHandleGetAfter(pSSM) != SSMAFTER_CONTINUE)
+ pThis->cmd &= ~XHCI_CMD_RS;
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDEVLOADEXEC}
+ */
+static DECLCALLBACK(int) xhciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ int rc;
+ uint32_t cPorts;
+ uint32_t iPort;
+ uint32_t cSlots;
+ uint32_t iSlot;
+ uint32_t cIntrs;
+ uint32_t iIntr;
+ uint64_t u64Dummy;
+ uint32_t u32Dummy;
+ uint16_t u16Dummy;
+ uint8_t u8Dummy;
+ bool fDummy;
+
+ LogFlow(("xhciR3LoadExec:\n"));
+
+ Assert(uPass == SSM_PASS_FINAL); NOREF(uPass);
+ if (uVersion != XHCI_SAVED_STATE_VERSION)
+ return VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION;
+
+ /* Load HC operational registers. */
+ pHlp->pfnSSMGetU32(pSSM, &pThis->cmd);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->status);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->dnctrl);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->crcr);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->dcbaap);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->config);
+
+ /* Load HC non-register state. */
+ pHlp->pfnSSMGetU64(pSSM, &pThis->cmdr_dqp);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->cmdr_ccs);
+
+ /* Load per-slot state. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &cSlots);
+ AssertRCReturn(rc, rc);
+ if (cSlots > 256) /* Sanity check. */
+ return VERR_SSM_INVALID_STATE;
+ for (iSlot = 0; iSlot < cSlots; ++iSlot)
+ {
+ /* Load only as many slots as we have; discard any extras. */
+ if (iSlot < XHCI_NDS)
+ {
+ pHlp->pfnSSMGetU8 (pSSM, &pThis->aSlotState[iSlot]);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aBellsRung[iSlot]);
+ }
+ else
+ {
+ pHlp->pfnSSMGetU8 (pSSM, &u8Dummy);
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ }
+ }
+
+ /* Load root hub (port) state. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &cPorts);
+ AssertRCReturn(rc, rc);
+ if (cPorts > 256) /* Sanity check. */
+ return VERR_SSM_INVALID_STATE;
+
+ for (iPort = 0; iPort < cPorts; ++iPort)
+ {
+ /* Load only as many ports as we have; discard any extras. */
+ if (iPort < XHCI_NDP_CFG(pThis))
+ {
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portsc);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aPorts[iPort].portpm);
+ }
+ else
+ {
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ }
+ }
+
+ /* Load interrupter state. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &cIntrs);
+ AssertRCReturn(rc, rc);
+ if (cIntrs > 256) /* Sanity check. */
+ return VERR_SSM_INVALID_STATE;
+ for (iIntr = 0; iIntr < cIntrs; ++iIntr)
+ {
+ /* Load only as many interrupters as we have; discard any extras. */
+ if (iIntr < XHCI_NINTR)
+ {
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].iman);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].imod);
+ pHlp->pfnSSMGetU32(pSSM, &pThis->aInterrupters[iIntr].erstsz);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erstba);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erdp);
+ pHlp->pfnSSMGetU64(pSSM, &pThis->aInterrupters[iIntr].erep);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].erst_idx);
+ pHlp->pfnSSMGetU16(pSSM, &pThis->aInterrupters[iIntr].trb_count);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].evtr_pcs);
+ pHlp->pfnSSMGetBool(pSSM, &pThis->aInterrupters[iIntr].ipe);
+ }
+ else
+ {
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ pHlp->pfnSSMGetU64(pSSM, &u64Dummy);
+ pHlp->pfnSSMGetU64(pSSM, &u64Dummy);
+ pHlp->pfnSSMGetU64(pSSM, &u64Dummy);
+ pHlp->pfnSSMGetU16(pSSM, &u16Dummy);
+ pHlp->pfnSSMGetU16(pSSM, &u16Dummy);
+ pHlp->pfnSSMGetBool(pSSM, &fDummy);
+ pHlp->pfnSSMGetBool(pSSM, &fDummy);
+ }
+ }
+
+ /* Terminator marker. */
+ rc = pHlp->pfnSSMGetU32(pSSM, &u32Dummy);
+ AssertRCReturn(rc, rc);
+ AssertReturn(u32Dummy == UINT32_MAX, VERR_SSM_DATA_UNIT_FORMAT_CHANGED);
+
+ return rc;
+}
+
+
+/* -=-=-=-=- DBGF -=-=-=-=- */
+
+/**
+ * @callback_method_impl{FNDBGFHANDLERDEV, Dumps xHCI state.}
+ */
+static DECLCALLBACK(void) xhciR3Info(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ RTGCPHYS GPAddr;
+ bool fVerbose = false;
+ unsigned i, j;
+ uint64_t u64Val;
+
+ /* Parse arguments. */
+ if (pszArgs)
+ fVerbose = strstr(pszArgs, "verbose") != NULL;
+
+#ifdef XHCI_ERROR_INJECTION
+ if (pszArgs && strstr(pszArgs, "dropintrhw"))
+ {
+ pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (external)!\n");
+ pThis->fDropIntrHw = true;
+ return;
+ }
+
+ if (pszArgs && strstr(pszArgs, "dropintrint"))
+ {
+ pHlp->pfnPrintf(pHlp, "Dropping the next interrupt (internal)!\n");
+ pThis->fDropIntrIpe = true;
+ return;
+ }
+
+ if (pszArgs && strstr(pszArgs, "dropurb"))
+ {
+ pHlp->pfnPrintf(pHlp, "Dropping the next URB!\n");
+ pThis->fDropUrb = true;
+ return;
+ }
+#endif
+
+ /* Show basic information. */
+ pHlp->pfnPrintf(pHlp,
+ "%s#%d: PCI MMIO=%RGp IRQ=%u MSI=%s R0=%RTbool RC=%RTbool\n",
+ pDevIns->pReg->szName,
+ pDevIns->iInstance,
+ PDMDevHlpMmioGetMappingAddress(pDevIns, pThis->hMmio),
+ PCIDevGetInterruptLine(pDevIns->apPciDevs[0]),
+#ifdef VBOX_WITH_MSI_DEVICES
+ xhciIsMSIEnabled(pDevIns->apPciDevs[0]) ? "on" : "off",
+#else
+ "none",
+#endif
+ pDevIns->fR0Enabled, pDevIns->fRCEnabled);
+
+ /* Command register. */
+ pHlp->pfnPrintf(pHlp, "USBCMD: %X:", pThis->cmd);
+ if (pThis->cmd & XHCI_CMD_EU3S) pHlp->pfnPrintf(pHlp, " EU3S" );
+ if (pThis->cmd & XHCI_CMD_EWE) pHlp->pfnPrintf(pHlp, " EWE" );
+ if (pThis->cmd & XHCI_CMD_CRS) pHlp->pfnPrintf(pHlp, " CRS" );
+ if (pThis->cmd & XHCI_CMD_CSS) pHlp->pfnPrintf(pHlp, " CSS" );
+ if (pThis->cmd & XHCI_CMD_LCRST) pHlp->pfnPrintf(pHlp, " LCRST" );
+ if (pThis->cmd & XHCI_CMD_HSEE) pHlp->pfnPrintf(pHlp, " HSEE" );
+ if (pThis->cmd & XHCI_CMD_INTE) pHlp->pfnPrintf(pHlp, " INTE" );
+ if (pThis->cmd & XHCI_CMD_HCRST) pHlp->pfnPrintf(pHlp, " HCRST" );
+ if (pThis->cmd & XHCI_CMD_RS) pHlp->pfnPrintf(pHlp, " RS" );
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ /* Status register. */
+ pHlp->pfnPrintf(pHlp, "USBSTS: %X:", pThis->status);
+ if (pThis->status & XHCI_STATUS_HCH) pHlp->pfnPrintf(pHlp, " HCH" );
+ if (pThis->status & XHCI_STATUS_HSE) pHlp->pfnPrintf(pHlp, " HSE" );
+ if (pThis->status & XHCI_STATUS_EINT) pHlp->pfnPrintf(pHlp, " EINT" );
+ if (pThis->status & XHCI_STATUS_PCD) pHlp->pfnPrintf(pHlp, " PCD" );
+ if (pThis->status & XHCI_STATUS_SSS) pHlp->pfnPrintf(pHlp, " SSS" );
+ if (pThis->status & XHCI_STATUS_RSS) pHlp->pfnPrintf(pHlp, " RSS" );
+ if (pThis->status & XHCI_STATUS_SRE) pHlp->pfnPrintf(pHlp, " SRE" );
+ if (pThis->status & XHCI_STATUS_CNR) pHlp->pfnPrintf(pHlp, " CNR" );
+ if (pThis->status & XHCI_STATUS_HCE) pHlp->pfnPrintf(pHlp, " HCE" );
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ /* Device Notification Control and Configure registers. */
+ pHlp->pfnPrintf(pHlp, "DNCTRL: %X CONFIG: %X (%u slots)\n", pThis->dnctrl, pThis->config, pThis->config);
+
+ /* Device Context Base Address Array. */
+ GPAddr = pThis->dcbaap & XHCI_DCBAA_ADDR_MASK;
+ pHlp->pfnPrintf(pHlp, "DCBAA ptr: %RGp\n", GPAddr);
+ /* The DCBAA must be valid in 'run' state. */
+ if (fVerbose && (pThis->cmd & XHCI_CMD_RS))
+ {
+ PDMDevHlpPCIPhysRead(pDevIns, GPAddr, &u64Val, sizeof(u64Val));
+ pHlp->pfnPrintf(pHlp, " Scratchpad buffer: %RX64\n", u64Val);
+ }
+
+ /* Command Ring Control Register. */
+ pHlp->pfnPrintf(pHlp, "CRCR: %X:", pThis->crcr & ~XHCI_CRCR_ADDR_MASK);
+ if (pThis->crcr & XHCI_CRCR_RCS) pHlp->pfnPrintf(pHlp, " RCS");
+ if (pThis->crcr & XHCI_CRCR_CS) pHlp->pfnPrintf(pHlp, " CS" );
+ if (pThis->crcr & XHCI_CRCR_CA) pHlp->pfnPrintf(pHlp, " CA" );
+ if (pThis->crcr & XHCI_CRCR_CRR) pHlp->pfnPrintf(pHlp, " CRR");
+ pHlp->pfnPrintf(pHlp, "\n");
+ GPAddr = pThis->crcr & XHCI_CRCR_ADDR_MASK;
+ pHlp->pfnPrintf(pHlp, "CRCR ptr : %RGp\n", GPAddr);
+
+ /* Interrupters. */
+ if (fVerbose)
+ {
+ for (i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i)
+ {
+ if (pThis->aInterrupters[i].erstsz)
+ {
+ XHCIINTRPTR *ir = &pThis->aInterrupters[i];
+
+ pHlp->pfnPrintf(pHlp, "Interrupter %d (IPE=%u)\n", i, ir->ipe);
+
+ /* The Interrupt Management Register. */
+ pHlp->pfnPrintf(pHlp, " IMAN : %X:", ir->iman);
+ if (ir->iman & XHCI_IMAN_IP) pHlp->pfnPrintf(pHlp, " IP");
+ if (ir->iman & XHCI_IMAN_IE) pHlp->pfnPrintf(pHlp, " IE");
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ /* The Interrupt Moderation Register. */
+ pHlp->pfnPrintf(pHlp, " IMOD : %X:", ir->imod);
+ pHlp->pfnPrintf(pHlp, " IMODI=%u", ir->imod & XHCI_IMOD_IMODI_MASK);
+ pHlp->pfnPrintf(pHlp, " IMODC=%u", (ir->imod & XHCI_IMOD_IMODC_MASK) >> XHCI_IMOD_IMODC_SHIFT);
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ pHlp->pfnPrintf(pHlp, " ERSTSZ: %X\n", ir->erstsz);
+ pHlp->pfnPrintf(pHlp, " ERSTBA: %RGp\n", (RTGCPHYS)ir->erstba);
+
+ pHlp->pfnPrintf(pHlp, " ERDP : %RGp:", (RTGCPHYS)ir->erdp);
+ pHlp->pfnPrintf(pHlp, " EHB=%u", !!(ir->erdp & XHCI_ERDP_EHB));
+ pHlp->pfnPrintf(pHlp, " DESI=%u", ir->erdp & XHCI_ERDP_DESI_MASK);
+ pHlp->pfnPrintf(pHlp, " ptr=%RGp", ir->erdp & XHCI_ERDP_ADDR_MASK);
+ pHlp->pfnPrintf(pHlp, "\n");
+
+ pHlp->pfnPrintf(pHlp, " EREP : %RGp", ir->erep);
+ pHlp->pfnPrintf(pHlp, " Free TRBs in seg=%u", ir->trb_count);
+ pHlp->pfnPrintf(pHlp, "\n");
+ }
+ }
+ }
+
+ /* Port control/status. */
+ for (i = 0; i < XHCI_NDP_CFG(pThis); ++i)
+ {
+ PXHCIHUBPORT p = &pThis->aPorts[i];
+
+ pHlp->pfnPrintf(pHlp, "Port %02u (USB%c): ", IDX_TO_ID(i), IS_USB3_PORT_IDX_SHR(pThis, i) ? '3' : '2');
+
+ /* Port Status register. */
+ pHlp->pfnPrintf(pHlp, "PORTSC: %8X:", p->portsc);
+ if (p->portsc & XHCI_PORT_CCS) pHlp->pfnPrintf(pHlp, " CCS" );
+ if (p->portsc & XHCI_PORT_PED) pHlp->pfnPrintf(pHlp, " PED" );
+ if (p->portsc & XHCI_PORT_OCA) pHlp->pfnPrintf(pHlp, " OCA" );
+ if (p->portsc & XHCI_PORT_PR ) pHlp->pfnPrintf(pHlp, " PR" );
+ pHlp->pfnPrintf(pHlp, " PLS=%u", (p->portsc & XHCI_PORT_PLS_MASK) >> XHCI_PORT_PLS_SHIFT);
+ if (p->portsc & XHCI_PORT_PP ) pHlp->pfnPrintf(pHlp, " PP" );
+ pHlp->pfnPrintf(pHlp, " SPD=%u", (p->portsc & XHCI_PORT_SPD_MASK) >> XHCI_PORT_SPD_SHIFT);
+ if (p->portsc & XHCI_PORT_LWS) pHlp->pfnPrintf(pHlp, " LWS" );
+ if (p->portsc & XHCI_PORT_CSC) pHlp->pfnPrintf(pHlp, " CSC" );
+ if (p->portsc & XHCI_PORT_PEC) pHlp->pfnPrintf(pHlp, " PEC" );
+ if (p->portsc & XHCI_PORT_WRC) pHlp->pfnPrintf(pHlp, " WRC" );
+ if (p->portsc & XHCI_PORT_OCC) pHlp->pfnPrintf(pHlp, " OCC" );
+ if (p->portsc & XHCI_PORT_PRC) pHlp->pfnPrintf(pHlp, " PRC" );
+ if (p->portsc & XHCI_PORT_PLC) pHlp->pfnPrintf(pHlp, " PLC" );
+ if (p->portsc & XHCI_PORT_CEC) pHlp->pfnPrintf(pHlp, " CEC" );
+ if (p->portsc & XHCI_PORT_CAS) pHlp->pfnPrintf(pHlp, " CAS" );
+ if (p->portsc & XHCI_PORT_WCE) pHlp->pfnPrintf(pHlp, " WCE" );
+ if (p->portsc & XHCI_PORT_WDE) pHlp->pfnPrintf(pHlp, " WDE" );
+ if (p->portsc & XHCI_PORT_WOE) pHlp->pfnPrintf(pHlp, " WOE" );
+ if (p->portsc & XHCI_PORT_DR ) pHlp->pfnPrintf(pHlp, " DR" );
+ if (p->portsc & XHCI_PORT_WPR) pHlp->pfnPrintf(pHlp, " WPR" );
+ pHlp->pfnPrintf(pHlp, "\n");
+ }
+
+ /* Device contexts. */
+ if (fVerbose && (pThis->cmd & XHCI_CMD_RS))
+ {
+ for (i = 0; i < XHCI_NDS; ++i)
+ {
+ if (pThis->aSlotState[i] > XHCI_DEVSLOT_EMPTY)
+ {
+ RTGCPHYS GCPhysSlot;
+ XHCI_DEV_CTX ctxDevice;
+ XHCI_SLOT_CTX ctxSlot;
+ const char *pcszDesc;
+ uint8_t uSlotID = IDX_TO_ID(i);
+
+ /* Find the slot address/ */
+ GCPhysSlot = xhciR3FetchDevCtxAddr(pDevIns, pThis, uSlotID);
+ pHlp->pfnPrintf(pHlp, "Slot %d (device context @ %RGp)\n", uSlotID, GCPhysSlot);
+ if (!GCPhysSlot)
+ {
+ pHlp->pfnPrintf(pHlp, "Bad context address, skipping!\n");
+ continue;
+ }
+
+ /* Just read in the whole lot and sort in which contexts are valid later. */
+ PDMDevHlpPCIPhysRead(pDevIns, GCPhysSlot, &ctxDevice, sizeof(ctxDevice));
+
+ ctxSlot = ctxDevice.entry[0].sc;
+ pcszDesc = ctxSlot.slot_state < RT_ELEMENTS(g_apszSltStates) ? g_apszSltStates[ctxSlot.slot_state] : "BAD!!!";
+ pHlp->pfnPrintf(pHlp, " Speed:%u Entries:%u RhPort:%u", ctxSlot.speed, ctxSlot.ctx_ent, ctxSlot.rh_port);
+ pHlp->pfnPrintf(pHlp, " Address:%u State:%s \n", ctxSlot.dev_addr, pcszDesc);
+
+ /* Endpoint contexts. */
+ for (j = 1; j <= ctxSlot.ctx_ent; ++j)
+ {
+ XHCI_EP_CTX ctxEP = ctxDevice.entry[j].ep;
+
+ /* Skip disabled endpoints -- they may be unused and do not
+ * contain valid data in any case.
+ */
+ if (ctxEP.ep_state == XHCI_EPST_DISABLED)
+ continue;
+
+ pcszDesc = ctxEP.ep_state < RT_ELEMENTS(g_apszEpStates) ? g_apszEpStates[ctxEP.ep_state] : "BAD!!!";
+ pHlp->pfnPrintf(pHlp, " Endpoint DCI %u State:%s", j, pcszDesc);
+ pcszDesc = ctxEP.ep_type < RT_ELEMENTS(g_apszEpTypes) ? g_apszEpTypes[ctxEP.ep_type] : "BAD!!!";
+ pHlp->pfnPrintf(pHlp, " Type:%s\n",pcszDesc);
+
+ pHlp->pfnPrintf(pHlp, " Mult:%u MaxPStreams:%u LSA:%u Interval:%u\n",
+ ctxEP.mult, ctxEP.maxps, ctxEP.lsa, ctxEP.interval);
+ pHlp->pfnPrintf(pHlp, " CErr:%u HID:%u MaxPS:%u MaxBS:%u",
+ ctxEP.c_err, ctxEP.hid, ctxEP.max_pkt_sz, ctxEP.max_brs_sz);
+ pHlp->pfnPrintf(pHlp, " AvgTRBLen:%u MaxESIT:%u",
+ ctxEP.avg_trb_len, ctxEP.max_esit);
+ pHlp->pfnPrintf(pHlp, " LastFrm:%u IFC:%u LastCC:%u\n",
+ ctxEP.last_frm, ctxEP.ifc, ctxEP.last_cc);
+ pHlp->pfnPrintf(pHlp, " TRDP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trdp & XHCI_TRDP_ADDR_MASK),
+ ctxEP.trdp & XHCI_TRDP_DCS_MASK);
+ pHlp->pfnPrintf(pHlp, " TREP:%RGp DCS:%u\n", (RTGCPHYS)(ctxEP.trep & XHCI_TRDP_ADDR_MASK),
+ ctxEP.trep & XHCI_TRDP_DCS_MASK);
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnReset}
+ */
+static DECLCALLBACK(void) xhciR3Reset(PPDMDEVINS pDevIns)
+{
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+ LogFlow(("xhciR3Reset:\n"));
+
+ /*
+ * There is no distinction between cold boot, warm reboot and software reboots,
+ * all of these are treated as cold boots. We are also doing the initialization
+ * job of a BIOS or SMM driver.
+ *
+ * Important: Don't confuse UsbReset with hardware reset. Hardware reset is
+ * just one way of getting into the UsbReset state.
+ */
+
+ /* Set the HC Halted bit now to prevent completion callbacks from running
+ *(there is really no point when resetting).
+ */
+ ASMAtomicOrU32(&pThis->status, XHCI_STATUS_HCH);
+
+ xhciR3BusStop(pDevIns, pThis, pThisCC);
+ xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, true /* reset devices */);
+}
+
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnDestruct}
+ */
+static DECLCALLBACK(int) xhciR3Destruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns);
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+ LogFlow(("xhciR3Destruct:\n"));
+
+ /*
+ * Destroy interrupter locks.
+ */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i)
+ {
+ if (PDMDevHlpCritSectIsInitialized(pDevIns, &pThis->aInterrupters[i].lock))
+ PDMDevHlpCritSectDelete(pDevIns, &pThis->aInterrupters[i].lock);
+ }
+
+ /*
+ * Clean up the worker thread and associated machinery.
+ */
+ if (pThis->hEvtProcess != NIL_SUPSEMEVENT)
+ {
+ PDMDevHlpSUPSemEventClose(pDevIns, pThis->hEvtProcess);
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+ }
+ if (RTCritSectIsInitialized(&pThisCC->CritSectThrd))
+ RTCritSectDelete(&pThisCC->CritSectThrd);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Worker for xhciR3Construct that registers a LUN (USB root hub).
+ */
+static int xhciR3RegisterHub(PPDMDEVINS pDevIns, PXHCIROOTHUBR3 pRh, int iLun, const char *pszDesc)
+{
+ int rc = PDMDevHlpDriverAttach(pDevIns, iLun, &pRh->IBase, &pRh->pIBase, pszDesc);
+ AssertMsgRCReturn(rc, ("Configuration error: Failed to attach root hub driver to LUN #%d! (%Rrc)\n", iLun, rc), rc);
+
+ pRh->pIRhConn = PDMIBASE_QUERY_INTERFACE(pRh->pIBase, VUSBIROOTHUBCONNECTOR);
+ AssertMsgReturn(pRh->pIRhConn,
+ ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"),
+ VERR_PDM_MISSING_INTERFACE);
+
+ /* Set URB parameters. */
+ rc = VUSBIRhSetUrbParams(pRh->pIRhConn, sizeof(VUSBURBHCIINT), 0);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, N_("OHCI: Failed to set URB parameters"));
+
+ return rc;
+}
+
+/**
+ * @interface_method_impl{PDMDEVREG,pfnConstruct,XHCI
+ * constructor}
+ */
+static DECLCALLBACK(int) xhciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+ PXHCICC pThisCC = PDMDEVINS_2_DATA_CC(pDevIns, PXHCICC);
+ PCPDMDEVHLPR3 pHlp = pDevIns->pHlpR3;
+ uint32_t cUsb2Ports;
+ uint32_t cUsb3Ports;
+ int rc;
+ LogFlow(("xhciR3Construct:\n"));
+ RT_NOREF(iInstance);
+
+ /*
+ * Initialize data so the destructor runs smoothly.
+ */
+ pThis->hEvtProcess = NIL_SUPSEMEVENT;
+
+ /*
+ * Validate and read configuration.
+ */
+ PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "USB2Ports|USB3Ports|ChipType", "");
+
+ /* Number of USB2 ports option. */
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB2Ports", &cUsb2Ports, XHCI_NDP_20_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("xHCI configuration error: failed to read USB2Ports as integer"));
+
+ if (cUsb2Ports == 0 || cUsb2Ports > XHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("xHCI configuration error: USB2Ports must be in range [%u,%u]"),
+ 1, XHCI_NDP_MAX);
+
+ /* Number of USB3 ports option. */
+ rc = pHlp->pfnCFGMQueryU32Def(pCfg, "USB3Ports", &cUsb3Ports, XHCI_NDP_30_DEFAULT);
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, rc,
+ N_("xHCI configuration error: failed to read USB3Ports as integer"));
+
+ if (cUsb3Ports == 0 || cUsb3Ports > XHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("xHCI configuration error: USB3Ports must be in range [%u,%u]"),
+ 1, XHCI_NDP_MAX);
+
+ /* Check that the total number of ports is within limits.*/
+ if (cUsb2Ports + cUsb3Ports > XHCI_NDP_MAX)
+ return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS,
+ N_("xHCI configuration error: USB2Ports + USB3Ports must be in range [%u,%u]"),
+ 1, XHCI_NDP_MAX);
+
+ /* Determine the model. */
+ char szChipType[16];
+ rc = pHlp->pfnCFGMQueryStringDef(pCfg, "ChipType", &szChipType[0], sizeof(szChipType), "PantherPoint");
+ if (RT_FAILURE(rc))
+ return PDMDEV_SET_ERROR(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES,
+ N_("xHCI configuration error: Querying \"ChipType\" as string failed"));
+
+ /*
+ * The default model is Panther Point (8086:1E31), Intel's first and most widely
+ * supported xHCI implementation. For debugging, the Lynx Point (8086:8C31) model
+ * can be selected. These two models work with the 7 Series and 8 Series Intel xHCI
+ * drivers for Windows 7, respectively. There is no functional difference.
+ * For Windows XP support, it's also possible to present a Renesas uPD720201 xHC;
+ * this is an evolution of the original NEC xHCI chip.
+ */
+ bool fChipLynxPoint = false;
+ bool fChipRenesas = false;
+ if (!strcmp(szChipType, "PantherPoint"))
+ fChipLynxPoint = false;
+ else if (!strcmp(szChipType, "LynxPoint"))
+ fChipLynxPoint = true;
+ else if (!strcmp(szChipType, "uPD720201"))
+ fChipRenesas = true;
+ else
+ return PDMDevHlpVMSetError(pDevIns, VERR_PDM_DEVINS_UNKNOWN_CFG_VALUES, RT_SRC_POS,
+ N_("xHCI configuration error: The \"ChipType\" value \"%s\" is unsupported"), szChipType);
+
+ LogFunc(("cUsb2Ports=%u cUsb3Ports=%u szChipType=%s (%d,%d) fR0Enabled=%d fRCEnabled=%d\n", cUsb2Ports, cUsb3Ports,
+ szChipType, fChipLynxPoint, fChipRenesas, pDevIns->fR0Enabled, pDevIns->fRCEnabled));
+
+ /* Set up interrupter locks. */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aInterrupters); ++i)
+ {
+ rc = PDMDevHlpCritSectInit(pDevIns, &pThis->aInterrupters[i].lock, RT_SRC_POS, "xHCIIntr#%u", i);
+ if (RT_FAILURE(rc))
+ return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS,
+ N_("xHCI: Failed to create critical section for interrupter %u"), i);
+ pThis->aInterrupters[i].index = i; /* Stash away index, mostly for logging/debugging. */
+ }
+
+
+ /*
+ * Init instance data.
+ */
+ pThisCC->pDevIns = pDevIns;
+
+ PPDMPCIDEV pPciDev = pDevIns->apPciDevs[0];
+ if (fChipRenesas)
+ {
+ pThis->erst_addr_mask = NEC_ERST_ADDR_MASK;
+ PCIDevSetVendorId(pPciDev, 0x1912);
+ PCIDevSetDeviceId(pPciDev, 0x0014);
+ PCIDevSetByte(pPciDev, VBOX_PCI_REVISION_ID, 0x02);
+ }
+ else
+ {
+ pThis->erst_addr_mask = XHCI_ERST_ADDR_MASK;
+ PCIDevSetVendorId(pPciDev, 0x8086);
+ if (fChipLynxPoint)
+ PCIDevSetDeviceId(pPciDev, 0x8C31); /* Lynx Point / 8 Series */
+ else
+ PCIDevSetDeviceId(pPciDev, 0x1E31); /* Panther Point / 7 Series */
+ }
+
+ PCIDevSetClassProg(pPciDev, 0x30); /* xHCI */
+ PCIDevSetClassSub(pPciDev, 0x03); /* USB 3.0 */
+ PCIDevSetClassBase(pPciDev, 0x0C);
+ PCIDevSetInterruptPin(pPciDev, 0x01);
+#ifdef VBOX_WITH_MSI_DEVICES
+ PCIDevSetStatus(pPciDev, VBOX_PCI_STATUS_CAP_LIST);
+ PCIDevSetCapabilityList(pPciDev, 0x80);
+#endif
+ PDMPciDevSetByte(pPciDev, 0x60, 0x20); /* serial bus release number register; 0x20 = USB 2.0 */
+ /** @todo USBLEGSUP & USBLEGCTLSTS? Legacy interface for the BIOS (0xEECP+0 & 0xEECP+4) */
+
+ pThis->cTotalPorts = (uint8_t)(cUsb2Ports + cUsb3Ports);
+
+ /* Set up the USB2 root hub interface. */
+ pThis->cUsb2Ports = (uint8_t)cUsb2Ports;
+ pThisCC->RootHub2.pXhciR3 = pThisCC;
+ pThisCC->RootHub2.cPortsImpl = cUsb2Ports;
+ pThisCC->RootHub2.uPortBase = 0;
+ pThisCC->RootHub2.IBase.pfnQueryInterface = xhciR3RhQueryInterface;
+ pThisCC->RootHub2.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts;
+ pThisCC->RootHub2.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions2;
+ pThisCC->RootHub2.IRhPort.pfnAttach = xhciR3RhAttach;
+ pThisCC->RootHub2.IRhPort.pfnDetach = xhciR3RhDetach;
+ pThisCC->RootHub2.IRhPort.pfnReset = xhciR3RhReset;
+ pThisCC->RootHub2.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion;
+ pThisCC->RootHub2.IRhPort.pfnXferError = xhciR3RhXferError;
+
+ /* Now the USB3 root hub interface. */
+ pThis->cUsb3Ports = (uint8_t)cUsb3Ports;
+ pThisCC->RootHub3.pXhciR3 = pThisCC;
+ pThisCC->RootHub3.cPortsImpl = cUsb3Ports;
+ pThisCC->RootHub3.uPortBase = XHCI_NDP_USB2(pThisCC);
+ pThisCC->RootHub3.IBase.pfnQueryInterface = xhciR3RhQueryInterface;
+ pThisCC->RootHub3.IRhPort.pfnGetAvailablePorts = xhciR3RhGetAvailablePorts;
+ pThisCC->RootHub3.IRhPort.pfnGetUSBVersions = xhciR3RhGetUSBVersions3;
+ pThisCC->RootHub3.IRhPort.pfnAttach = xhciR3RhAttach;
+ pThisCC->RootHub3.IRhPort.pfnDetach = xhciR3RhDetach;
+ pThisCC->RootHub3.IRhPort.pfnReset = xhciR3RhReset;
+ pThisCC->RootHub3.IRhPort.pfnXferCompletion = xhciR3RhXferCompletion;
+ pThisCC->RootHub3.IRhPort.pfnXferError = xhciR3RhXferError;
+
+ /* USB LED */
+ pThisCC->RootHub2.Led.u32Magic = PDMLED_MAGIC;
+ pThisCC->RootHub3.Led.u32Magic = PDMLED_MAGIC;
+ pThisCC->IBase.pfnQueryInterface = xhciR3QueryStatusInterface;
+ pThisCC->ILeds.pfnQueryStatusLed = xhciR3QueryStatusLed;
+
+ /* Initialize the capability registers */
+ pThis->cap_length = XHCI_CAPS_REG_SIZE;
+ pThis->hci_version = 0x100; /* Version 1.0 */
+ pThis->hcs_params1 = (XHCI_NDP_CFG(pThis) << 24) | (XHCI_NINTR << 8) | XHCI_NDS;
+ pThis->hcs_params2 = (XHCI_ERSTMAX_LOG2 << 4) | XHCI_IST;
+ pThis->hcs_params3 = (4 << 16) | 1; /* Matches Intel 7 Series xHCI. */
+ /* Note: The Intel 7 Series xHCI does not have port power control (XHCI_HCC_PPC). */
+ pThis->hcc_params = ((XHCI_XECP_OFFSET >> 2) << XHCI_HCC_XECP_SHIFT); /// @todo other fields
+ pThis->dbell_off = XHCI_DOORBELL_OFFSET;
+ pThis->rts_off = XHCI_RTREG_OFFSET;
+
+ /*
+ * Set up extended capabilities.
+ */
+ rc = xhciR3BuildExtCaps(pThis, pThisCC);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register PCI device and I/O region.
+ */
+ rc = PDMDevHlpPCIRegister(pDevIns, pPciDev);
+ AssertRCReturn(rc, rc);
+
+#ifdef VBOX_WITH_MSI_DEVICES
+ PDMMSIREG MsiReg;
+ RT_ZERO(MsiReg);
+ MsiReg.cMsiVectors = 1;
+ MsiReg.iMsiCapOffset = XHCI_PCI_MSI_CAP_OFS;
+ MsiReg.iMsiNextOffset = 0x00;
+ rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg);
+ if (RT_FAILURE (rc))
+ {
+ PCIDevSetCapabilityList(pPciDev, 0x0);
+ /* That's OK, we can work without MSI */
+ }
+#endif
+
+ rc = PDMDevHlpPCIIORegionCreateMmio(pDevIns, 0, XHCI_MMIO_SIZE, PCI_ADDRESS_SPACE_MEM,
+ xhciMmioWrite, xhciMmioRead, NULL,
+ IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED
+ /*| IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE*/,
+ "USB xHCI", &pThis->hMmio);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Register the saved state data unit.
+ */
+ rc = PDMDevHlpSSMRegisterEx(pDevIns, XHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL,
+ NULL, NULL, NULL,
+ NULL, xhciR3SaveExec, NULL,
+ NULL, xhciR3LoadExec, NULL);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #0 (USB3 root hub).
+ * NB: USB3 must come first so that emulated devices which support both USB2
+ * and USB3 are attached to the USB3 hub.
+ */
+ rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub3, 0, "RootHubUSB3");
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach to the VBox USB RootHub Driver on LUN #1 (USB2 root hub).
+ */
+ rc = xhciR3RegisterHub(pDevIns, &pThisCC->RootHub2, 1, "RootHubUSB2");
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Attach the status LED (optional).
+ */
+ PPDMIBASE pBase;
+ rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThisCC->IBase, &pBase, "Status Port");
+ if (RT_SUCCESS(rc))
+ pThisCC->pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS);
+ else if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
+ {
+ AssertMsgFailed(("xHCI: Failed to attach to status driver. rc=%Rrc\n", rc));
+ return PDMDEV_SET_ERROR(pDevIns, rc, N_("xHCI cannot attach to status driver"));
+ }
+
+ /*
+ * Create the MFINDEX wrap event timer.
+ */
+ rc = PDMDevHlpTimerCreate(pDevIns, TMCLOCK_VIRTUAL, xhciR3WrapTimer, pThis,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_RING0, "xHCI MFINDEX Wrap", &pThis->hWrapTimer);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Set up the worker thread.
+ */
+ rc = PDMDevHlpSUPSemEventCreate(pDevIns, &pThis->hEvtProcess);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = RTCritSectInit(&pThisCC->CritSectThrd);
+ AssertLogRelRCReturn(rc, rc);
+
+ rc = PDMDevHlpThreadCreate(pDevIns, &pThisCC->pWorkerThread, pThis, xhciR3WorkerLoop, xhciR3WorkerWakeUp,
+ 0, RTTHREADTYPE_IO, "xHCI");
+ AssertLogRelRCReturn(rc, rc);
+
+ /*
+ * Do a hardware reset.
+ */
+ xhciR3DoReset(pThis, pThisCC, XHCI_USB_RESET, false /* don't reset devices */);
+
+# ifdef VBOX_WITH_STATISTICS
+ /*
+ * Register statistics.
+ */
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocUrbs, STAMTYPE_COUNTER, "IsocUrbsErr", STAMUNIT_OCCURENCES, "Isoch URBs completed w/error.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatErrorIsocPkts, STAMTYPE_COUNTER, "IsocPktsErr", STAMUNIT_OCCURENCES, "Isoch packets completed w/error.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsWritten, STAMTYPE_COUNTER, "EventsWritten", STAMUNIT_OCCURENCES, "Event TRBs delivered.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatEventsDropped, STAMTYPE_COUNTER, "EventsDropped", STAMUNIT_OCCURENCES, "Event TRBs dropped (HC stopped).");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsPending, STAMTYPE_COUNTER, "IntrsPending", STAMUNIT_OCCURENCES, "Requests to set the IP bit.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsSet, STAMTYPE_COUNTER, "IntrsSet", STAMUNIT_OCCURENCES, "Actual interrupts delivered.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsNotSet, STAMTYPE_COUNTER, "IntrsNotSet", STAMUNIT_OCCURENCES, "Interrupts not delivered/disabled.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatIntrsCleared, STAMTYPE_COUNTER, "IntrsCleared", STAMUNIT_OCCURENCES, "Interrupts cleared by guest.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerCtlUrb, STAMTYPE_COUNTER, "UrbTrbsCtl", STAMUNIT_COUNT, "TRBs per one control URB.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerDtaUrb, STAMTYPE_COUNTER, "UrbTrbsDta", STAMUNIT_COUNT, "TRBs per one data (bulk/intr) URB.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatTRBsPerIsoUrb, STAMTYPE_COUNTER, "UrbTrbsIso", STAMUNIT_COUNT, "TRBs per one isochronous URB.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeCtrl, STAMTYPE_COUNTER, "UrbSizeCtl", STAMUNIT_COUNT, "Size of a control URB in bytes.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeData, STAMTYPE_COUNTER, "UrbSizeDta", STAMUNIT_COUNT, "Size of a data (bulk/intr) URB in bytes.");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatUrbSizeIsoc, STAMTYPE_COUNTER, "UrbSizeIso", STAMUNIT_COUNT, "Size of an isochronous URB in bytes.");
+
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCaps, STAMTYPE_COUNTER, "Regs/RdCaps", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/RdCmdRingCtlLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdConfig, STAMTYPE_COUNTER, "Regs/RdConfig", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/RdDevCtxBaapLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/RdDevNotifyCtrl", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdDoorBell, STAMTYPE_COUNTER, "Regs/RdDoorBell", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/RdEvtRingDeqPtrLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/RdEvtRsTblBaseLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRstblSize, STAMTYPE_COUNTER, "Regs/RdEvtRstblSize", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdEvtRsvd, STAMTYPE_COUNTER, "Regs/RdEvtRsvd", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMgmt, STAMTYPE_COUNTER, "Regs/RdIntrMgmt", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdIntrMod, STAMTYPE_COUNTER, "Regs/RdIntrMod", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdMfIndex, STAMTYPE_COUNTER, "Regs/RdMfIndex", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPageSize, STAMTYPE_COUNTER, "Regs/RdPageSize", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortLinkInfo, STAMTYPE_COUNTER, "Regs/RdPortLinkInfo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortPowerMgmt, STAMTYPE_COUNTER, "Regs/RdPortPowerMgmt", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortRsvd, STAMTYPE_COUNTER, "Regs/RdPortRsvd", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdPortStatusCtrl, STAMTYPE_COUNTER, "Regs/RdPortStatusCtrl", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbCmd, STAMTYPE_COUNTER, "Regs/RdUsbCmd", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUsbSts, STAMTYPE_COUNTER, "Regs/RdUsbSts", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatRdUnknown, STAMTYPE_COUNTER, "Regs/RdUnknown", STAMUNIT_COUNT, "");
+
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlHi, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrCmdRingCtlLo, STAMTYPE_COUNTER, "Regs/WrCmdRingCtlLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrConfig, STAMTYPE_COUNTER, "Regs/WrConfig", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapHi, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevCtxBaapLo, STAMTYPE_COUNTER, "Regs/WrDevCtxBaapLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDevNotifyCtrl, STAMTYPE_COUNTER, "Regs/WrDevNotifyCtrl", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBell0, STAMTYPE_COUNTER, "Regs/WrDoorBell0", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrDoorBellN, STAMTYPE_COUNTER, "Regs/WrDoorBellN", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrHi, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRingDeqPtrLo, STAMTYPE_COUNTER, "Regs/WrEvtRingDeqPtrLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseHi, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseHi", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRsTblBaseLo, STAMTYPE_COUNTER, "Regs/WrEvtRsTblBaseLo", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrEvtRstblSize, STAMTYPE_COUNTER, "Regs/WrEvtRstblSize", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMgmt, STAMTYPE_COUNTER, "Regs/WrIntrMgmt", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrIntrMod, STAMTYPE_COUNTER, "Regs/WrIntrMod", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortPowerMgmt, STAMTYPE_COUNTER, "Regs/WrPortPowerMgmt", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrPortStatusCtrl, STAMTYPE_COUNTER, "Regs/WrPortStatusCtrl", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbCmd, STAMTYPE_COUNTER, "Regs/WrUsbCmd", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUsbSts, STAMTYPE_COUNTER, "Regs/WrUsbSts", STAMUNIT_COUNT, "");
+ PDMDevHlpSTAMRegister(pDevIns, &pThis->StatWrUnknown, STAMTYPE_COUNTER, "Regs/WrUnknown", STAMUNIT_COUNT, "");
+# endif /* VBOX_WITH_STATISTICS */
+
+ /*
+ * Register debugger info callbacks.
+ */
+ PDMDevHlpDBGFInfoRegister(pDevIns, "xhci", "xHCI registers.", xhciR3Info);
+
+ return VINF_SUCCESS;
+}
+
+#else /* !IN_RING3 */
+
+/**
+ * @callback_method_impl{PDMDEVREGR0,pfnConstruct}
+ */
+static DECLCALLBACK(int) xhciRZConstruct(PPDMDEVINS pDevIns)
+{
+ PDMDEV_CHECK_VERSIONS_RETURN(pDevIns);
+ PXHCI pThis = PDMDEVINS_2_DATA(pDevIns, PXHCI);
+
+ int rc = PDMDevHlpMmioSetUpContext(pDevIns, pThis->hMmio, xhciMmioWrite, xhciMmioRead, NULL /*pvUser*/);
+ AssertRCReturn(rc, rc);
+
+ return VINF_SUCCESS;
+}
+
+#endif /* !IN_RING3 */
+
+/* Without this, g_DeviceXHCI won't be visible outside this module! */
+extern "C" const PDMDEVREG g_DeviceXHCI;
+
+const PDMDEVREG g_DeviceXHCI =
+{
+ /* .u32version = */ PDM_DEVREG_VERSION,
+ /* .uReserved0 = */ 0,
+ /* .szName = */ "usb-xhci",
+ /* .fFlags = */ PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RZ | PDM_DEVREG_FLAGS_NEW_STYLE,
+ /* .fClass = */ PDM_DEVREG_CLASS_BUS_USB,
+ /* .cMaxInstances = */ ~0U,
+ /* .uSharedVersion = */ 42,
+ /* .cbInstanceShared = */ sizeof(XHCI),
+ /* .cbInstanceCC = */ sizeof(XHCICC),
+ /* .cbInstanceRC = */ sizeof(XHCIRC),
+ /* .cMaxPciDevices = */ 1,
+ /* .cMaxMsixVectors = */ 0,
+ /* .pszDescription = */ "xHCI USB controller.\n",
+#if defined(IN_RING3)
+# ifdef VBOX_IN_EXTPACK
+ /* .pszRCMod = */ "VBoxEhciRC.rc",
+ /* .pszR0Mod = */ "VBoxEhciR0.r0",
+# else
+ /* .pszRCMod = */ "VBoxDDRC.rc",
+ /* .pszR0Mod = */ "VBoxDDR0.r0",
+# endif
+ /* .pfnConstruct = */ xhciR3Construct,
+ /* .pfnDestruct = */ xhciR3Destruct,
+ /* .pfnRelocate = */ NULL,
+ /* .pfnMemSetup = */ NULL,
+ /* .pfnPowerOn = */ NULL,
+ /* .pfnReset = */ xhciR3Reset,
+ /* .pfnSuspend = */ NULL,
+ /* .pfnResume = */ NULL,
+ /* .pfnAttach = */ NULL,
+ /* .pfnDetach = */ NULL,
+ /* .pfnQueryInterface = */ NULL,
+ /* .pfnInitComplete = */ NULL,
+ /* .pfnPowerOff = */ NULL,
+ /* .pfnSoftReset = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RING0)
+ /* .pfnEarlyConstruct = */ NULL,
+ /* .pfnConstruct = */ xhciRZConstruct,
+ /* .pfnDestruct = */ NULL,
+ /* .pfnFinalDestruct = */ NULL,
+ /* .pfnRequest = */ NULL,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#elif defined(IN_RC)
+ /* .pfnConstruct = */ xhciRZConstruct,
+ /* .pfnReserved0 = */ NULL,
+ /* .pfnReserved1 = */ NULL,
+ /* .pfnReserved2 = */ NULL,
+ /* .pfnReserved3 = */ NULL,
+ /* .pfnReserved4 = */ NULL,
+ /* .pfnReserved5 = */ NULL,
+ /* .pfnReserved6 = */ NULL,
+ /* .pfnReserved7 = */ NULL,
+#else
+# error "Not in IN_RING3, IN_RING0 or IN_RC!"
+#endif
+ /* .u32VersionEnd = */ PDM_DEVREG_VERSION
+};
+
+#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */
diff --git a/src/VBox/Devices/USB/DrvVUSBRootHub.cpp b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp
new file mode 100644
index 00000000..53b8811c
--- /dev/null
+++ b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp
@@ -0,0 +1,1880 @@
+/* $Id: DrvVUSBRootHub.cpp $ */
+/** @file
+ * Virtual USB - Root Hub Driver.
+ */
+
+/*
+ * Copyright (C) 2005-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/** @page pg_dev_vusb VUSB - Virtual USB
+ *
+ * @todo read thru this and correct typos. Merge with old docs.
+ *
+ *
+ * The Virtual USB component glues USB devices and host controllers together.
+ * The VUSB takes the form of a PDM driver which is attached to the HCI. USB
+ * devices are created by, attached to, and managed by the VUSB roothub. The
+ * VUSB also exposes an interface which is used by Main to attach and detach
+ * proxied USB devices.
+ *
+ *
+ * @section sec_dev_vusb_urb The Life of an URB
+ *
+ * The URB is created when the HCI calls the roothub (VUSB) method pfnNewUrb.
+ * VUSB has a pool of URBs, if no free URBs are available a new one is
+ * allocated. The returned URB starts life in the ALLOCATED state and all
+ * fields are initialized with sensible defaults.
+ *
+ * The HCI then copies any request data into the URB if it's an host2dev
+ * transfer. It then submits the URB by calling the pfnSubmitUrb roothub
+ * method.
+ *
+ * pfnSubmitUrb will start by checking if it knows the device address, and if
+ * it doesn't the URB is completed with a device-not-ready error. When the
+ * device address is known to it, action is taken based on the kind of
+ * transfer it is. There are four kinds of transfers: 1. control, 2. bulk,
+ * 3. interrupt, and 4. isochronous. In either case something eventually ends
+ * up being submitted to the device.
+ *
+ *
+ * If an URB fails submitting, may or may not be completed. This depends on
+ * heuristics in some cases and on the kind of failure in others. If
+ * pfnSubmitUrb returns a failure, the HCI should retry submitting it at a
+ * later time. If pfnSubmitUrb returns success the URB is submitted, and it
+ * can even been completed.
+ *
+ * The URB is in the IN_FLIGHT state from the time it's successfully submitted
+ * and till it's reaped or cancelled.
+ *
+ * When an URB transfer or in some case submit failure occurs, the pfnXferError
+ * callback of the HCI is consulted about what to do. If pfnXferError indicates
+ * that the URB should be retried, pfnSubmitUrb will fail. If it indicates that
+ * it should fail, the URB will be completed.
+ *
+ * Completing an URB means that the URB status is set and the HCI
+ * pfnXferCompletion callback is invoked with the URB. The HCI is the supposed
+ * to report the transfer status to the guest OS. After completion the URB
+ * is freed and returned to the pool, unless it was cancelled. If it was
+ * cancelled it will have to await reaping before it's actually freed.
+ *
+ *
+ * @subsection subsec_dev_vusb_urb_ctrl Control
+ *
+ * The control transfer is the most complex one, from VUSB's point of view,
+ * with its three stages and being bi-directional. A control transfer starts
+ * with a SETUP packet containing the request description and two basic
+ * parameters. It is followed by zero or more DATA packets which either picks
+ * up incoming data (dev2host) or supplies the request data (host2dev). This
+ * can then be followed by a STATUS packet which gets the status of the whole
+ * transfer.
+ *
+ * What makes the control transfer complicated is that for a host2dev request
+ * the URB is assembled from the SETUP and DATA stage, and for a dev2host
+ * request the returned data must be kept around for the DATA stage. For both
+ * transfer directions the status of the transfer has to be kept around for
+ * the STATUS stage.
+ *
+ * To complicate matters further, VUSB must intercept and in some cases emulate
+ * some of the standard requests in order to keep the virtual device state
+ * correct and provide the correct virtualization of a device.
+ *
+ * @subsection subsec_dev_vusb_urb_bulk Bulk and Interrupt
+ *
+ * The bulk and interrupt transfer types are relativly simple compared to the
+ * control transfer. VUSB is not inspecting the request content or anything,
+ * but passes it down the device.
+ *
+ * @subsection subsec_dev_vusb_urb_isoc Isochronous
+ *
+ * This kind of transfers hasn't yet been implemented.
+ *
+ */
+
+
+/** @page pg_dev_vusb_old VUSB - Virtual USB Core
+ *
+ * The virtual USB core is controlled by the roothub and the underlying HCI
+ * emulator, it is responsible for device addressing, managing configurations,
+ * interfaces and endpoints, assembling and splitting multi-part control
+ * messages and in general acts as a middle layer between the USB device
+ * emulation code and USB HCI emulation code.
+ *
+ * All USB devices are represented by a struct vusb_dev. This structure
+ * contains things like the device state, device address, all the configuration
+ * descriptors, the currently selected configuration and a mapping between
+ * endpoint addresses and endpoint descriptors.
+ *
+ * Each vusb_dev also has a pointer to a vusb_dev_ops structure which serves as
+ * the virtual method table and includes a virtual constructor and destructor.
+ * After a vusb_dev is created it may be attached to a hub device such as a
+ * roothub (using vusbHubAttach). Although each hub structure has cPorts
+ * and cDevices fields, it is the responsibility of the hub device to allocate
+ * a free port for the new device.
+ *
+ * Devices can chose one of two interfaces for dealing with requests, the
+ * synchronous interface or the asynchronous interface. The synchronous
+ * interface is much simpler and ought to be used for devices which are
+ * unlikely to sleep for long periods in order to serve requests. The
+ * asynchronous interface on the other hand is more difficult to use but is
+ * useful for the USB proxy or if one were to write a mass storage device
+ * emulator. Currently the synchronous interface only supports control and bulk
+ * endpoints and is no longer used by anything.
+ *
+ * In order to use the asynchronous interface, the queue_urb, cancel_urb and
+ * pfnUrbReap fields must be set in the devices vusb_dev_ops structure. The
+ * queue_urb method is used to submit a request to a device without blocking,
+ * it returns 1 if successful and 0 on any kind of failure. A successfully
+ * queued URB is completed when the pfnUrbReap method returns it. Each function
+ * address is reference counted so that pfnUrbReap will only be called if there
+ * are URBs outstanding. For a roothub to reap an URB from any one of it's
+ * devices, the vusbRhReapAsyncUrbs() function is used.
+ *
+ * There are four types of messages an URB may contain:
+ * -# Control - represents a single packet of a multi-packet control
+ * transfer, these are only really used by the host controller to
+ * submit the parts to the usb core.
+ * -# Message - the usb core assembles multiple control transfers in
+ * to single message transfers. In this case the data buffer
+ * contains the setup packet in little endian followed by the full
+ * buffer. In the case of an host-to-device control message, the
+ * message packet is created when the STATUS transfer is seen. In
+ * the case of device-to-host messages, the message packet is
+ * created after the SETUP transfer is seen. Also, certain control
+ * requests never go the real device and get handled synchronously.
+ * -# Bulk - Currently the only endpoint type that does error checking
+ * and endpoint halting.
+ * -# Interrupt - The only non-periodic type supported.
+ *
+ * Hubs are special cases of devices, they have a number of downstream ports
+ * that other devices can be attached to and removed from.
+ *
+ * After a device has been attached (vusbHubAttach):
+ * -# The hub attach method is called, which sends a hub status
+ * change message to the OS.
+ * -# The OS resets the device, and it appears on the default
+ * address with it's config 0 selected (a pseudo-config that
+ * contains only 1 interface with 1 endpoint - the default
+ * message pipe).
+ * -# The OS assigns the device a new address and selects an
+ * appropriate config.
+ * -# The device is ready.
+ *
+ * After a device has been detached (vusbDevDetach):
+ * -# All pending URBs are cancelled.
+ * -# The devices address is unassigned.
+ * -# The hub detach method is called which signals the OS
+ * of the status change.
+ * -# The OS unlinks the ED's for that device.
+ *
+ * A device can also request detachment from within its own methods by
+ * calling vusbDevUnplugged().
+ *
+ * Roothubs are responsible for driving the whole system, they are special
+ * cases of hubs and as such implement attach and detach methods, each one
+ * is described by a struct vusb_roothub. Once a roothub has submitted an
+ * URB to the USB core, a number of callbacks to the roothub are required
+ * for when the URB completes, since the roothub typically wants to inform
+ * the OS when transfers are completed.
+ *
+ * There are four callbacks to be concerned with:
+ * -# prepare - This is called after the URB is successfully queued.
+ * -# completion - This is called after the URB completed.
+ * -# error - This is called if the URB errored, some systems have
+ * automatic resubmission of failed requests, so this callback
+ * should keep track of the error count and return 1 if the count
+ * is above the number of allowed resubmissions.
+ * -# halt_ep - This is called after errors on bulk pipes in order
+ * to halt the pipe.
+ *
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/err.h>
+#include <iprt/alloc.h>
+#include <VBox/log.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/uuid.h>
+#include "VUSBInternal.h"
+#include "VBoxDD.h"
+
+
+#define VUSB_ROOTHUB_SAVED_STATE_VERSION 1
+
+
+/**
+ * Data used for reattaching devices on a state load.
+ */
+typedef struct VUSBROOTHUBLOAD
+{
+ /** Timer used once after state load to inform the guest about new devices.
+ * We do this to be sure the guest get any disconnect / reconnect on the
+ * same port. */
+ TMTIMERHANDLE hTimer;
+ /** Number of detached devices. */
+ unsigned cDevs;
+ /** Array of devices which were detached. */
+ PVUSBDEV apDevs[VUSB_DEVICES_MAX];
+} VUSBROOTHUBLOAD;
+
+
+/**
+ * Returns the attached VUSB device for the given port or NULL if none is attached.
+ *
+ * @returns Pointer to the VUSB device or NULL if not found.
+ * @param pThis The VUSB roothub device instance.
+ * @param uPort The port to get the device for.
+ * @param pszWho Caller of this method.
+ *
+ * @note The reference count of the VUSB device structure is retained to prevent it from going away.
+ */
+static PVUSBDEV vusbR3RhGetVUsbDevByPortRetain(PVUSBROOTHUB pThis, uint32_t uPort, const char *pszWho)
+{
+ PVUSBDEV pDev = NULL;
+
+ AssertReturn(uPort < RT_ELEMENTS(pThis->apDevByPort), NULL);
+
+ RTCritSectEnter(&pThis->CritSectDevices);
+
+ pDev = pThis->apDevByPort[uPort];
+ if (RT_LIKELY(pDev))
+ vusbDevRetain(pDev, pszWho);
+
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ return pDev;
+}
+
+
+/**
+ * Returns the attached VUSB device for the given port or NULL if none is attached.
+ *
+ * @returns Pointer to the VUSB device or NULL if not found.
+ * @param pThis The VUSB roothub device instance.
+ * @param u8Address The address to get the device for.
+ * @param pszWho Caller of this method.
+ *
+ * @note The reference count of the VUSB device structure is retained to prevent it from going away.
+ */
+static PVUSBDEV vusbR3RhGetVUsbDevByAddrRetain(PVUSBROOTHUB pThis, uint8_t u8Address, const char *pszWho)
+{
+ PVUSBDEV pDev = NULL;
+
+ AssertReturn(u8Address < RT_ELEMENTS(pThis->apDevByAddr), NULL);
+
+ RTCritSectEnter(&pThis->CritSectDevices);
+
+ pDev = pThis->apDevByAddr[u8Address];
+ if (RT_LIKELY(pDev))
+ vusbDevRetain(pDev, pszWho);
+
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ return pDev;
+}
+
+
+/**
+ * Returns a human readable string fromthe given USB speed enum.
+ *
+ * @returns Human readable string.
+ * @param enmSpeed The speed to stringify.
+ */
+static const char *vusbGetSpeedString(VUSBSPEED enmSpeed)
+{
+ const char *pszSpeed = NULL;
+
+ switch (enmSpeed)
+ {
+ case VUSB_SPEED_LOW:
+ pszSpeed = "Low";
+ break;
+ case VUSB_SPEED_FULL:
+ pszSpeed = "Full";
+ break;
+ case VUSB_SPEED_HIGH:
+ pszSpeed = "High";
+ break;
+ case VUSB_SPEED_VARIABLE:
+ pszSpeed = "Variable";
+ break;
+ case VUSB_SPEED_SUPER:
+ pszSpeed = "Super";
+ break;
+ case VUSB_SPEED_SUPERPLUS:
+ pszSpeed = "SuperPlus";
+ break;
+ default:
+ pszSpeed = "Unknown";
+ break;
+ }
+ return pszSpeed;
+}
+
+
+/**
+ * Attaches a device to a specific hub.
+ *
+ * This function is called by the vusb_add_device() and vusbRhAttachDevice().
+ *
+ * @returns VBox status code.
+ * @param pThis The roothub to attach it to.
+ * @param pDev The device to attach.
+ * @thread EMT
+ */
+static int vusbHubAttach(PVUSBROOTHUB pThis, PVUSBDEV pDev)
+{
+ LogFlow(("vusbHubAttach: pThis=%p[%s] pDev=%p[%s]\n", pThis, pThis->pszName, pDev, pDev->pUsbIns->pszName));
+
+ /*
+ * Assign a port.
+ */
+ int iPort = ASMBitFirstSet(&pThis->Bitmap, sizeof(pThis->Bitmap) * 8);
+ if (iPort < 0)
+ {
+ LogRel(("VUSB: No ports available!\n"));
+ return VERR_VUSB_NO_PORTS;
+ }
+ ASMBitClear(&pThis->Bitmap, iPort);
+ pThis->cDevices++;
+ pDev->i16Port = iPort;
+
+ /* Call the device attach helper, so it can initialize its state. */
+ int rc = vusbDevAttach(pDev, pThis);
+ if (RT_SUCCESS(rc))
+ {
+ RTCritSectEnter(&pThis->CritSectDevices);
+ Assert(!pThis->apDevByPort[iPort]);
+ pThis->apDevByPort[iPort] = pDev;
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ /*
+ * Call the HCI attach routine and let it have its say before the device is
+ * linked into the device list of this hub.
+ */
+ VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice);
+ rc = pThis->pIRhPort->pfnAttach(pThis->pIRhPort, iPort, enmSpeed);
+ if (RT_SUCCESS(rc))
+ {
+ LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName,
+ iPort, pThis->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed)));
+ return VINF_SUCCESS;
+ }
+
+ /* Remove from the port in case of failure. */
+ RTCritSectEnter(&pThis->CritSectDevices);
+ Assert(!pThis->apDevByPort[iPort]);
+ pThis->apDevByPort[iPort] = NULL;
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ vusbDevDetach(pDev);
+ }
+
+ ASMBitSet(&pThis->Bitmap, iPort);
+ pThis->cDevices--;
+ pDev->i16Port = -1;
+ LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc));
+
+ return rc;
+}
+
+
+/**
+ * Detaches the given device from the given roothub.
+ *
+ * @returns VBox status code.
+ * @param pThis The roothub to detach the device from.
+ * @param pDev The device to detach.
+ */
+static int vusbHubDetach(PVUSBROOTHUB pThis, PVUSBDEV pDev)
+{
+ Assert(pDev->i16Port != -1);
+
+ /*
+ * Detach the device and mark the port as available.
+ */
+ unsigned uPort = pDev->i16Port;
+ pDev->i16Port = -1;
+ pThis->pIRhPort->pfnDetach(pThis->pIRhPort, uPort);
+ ASMBitSet(&pThis->Bitmap, uPort);
+ pThis->cDevices--;
+
+ /* Check that it's attached and remove it. */
+ RTCritSectEnter(&pThis->CritSectDevices);
+ Assert(pThis->apDevByPort[uPort] == pDev);
+ pThis->apDevByPort[uPort] = NULL;
+
+ if (pDev->u8Address != VUSB_INVALID_ADDRESS)
+ {
+ Assert(pThis->apDevByAddr[pDev->u8Address] == pDev);
+ pThis->apDevByAddr[pDev->u8Address] = NULL;
+
+ pDev->u8Address = VUSB_INVALID_ADDRESS;
+ pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
+ }
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ /* Cancel all in-flight URBs from this device. */
+ vusbDevCancelAllUrbs(pDev, true);
+
+ /* Free resources. */
+ vusbDevDetach(pDev);
+ return VINF_SUCCESS;
+}
+
+
+
+/* -=-=-=-=-=- PDMUSBHUBREG methods -=-=-=-=-=- */
+
+/** @interface_method_impl{PDMUSBHUBREG,pfnAttachDevice} */
+static DECLCALLBACK(int) vusbPDMHubAttachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, const char *pszCaptureFilename, uint32_t *piPort)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+
+ /*
+ * Allocate a new VUSB device and initialize it.
+ */
+ PVUSBDEV pDev = (PVUSBDEV)RTMemAllocZ(sizeof(*pDev));
+ AssertReturn(pDev, VERR_NO_MEMORY);
+ int rc = vusbDevInit(pDev, pUsbIns, pszCaptureFilename);
+ if (RT_SUCCESS(rc))
+ {
+ pUsbIns->pvVUsbDev2 = pDev;
+ rc = vusbHubAttach(pThis, pDev);
+ if (RT_SUCCESS(rc))
+ {
+ *piPort = UINT32_MAX; /// @todo implement piPort
+ return rc;
+ }
+
+ RTMemFree(pDev->paIfStates);
+ pUsbIns->pvVUsbDev2 = NULL;
+ }
+ vusbDevRelease(pDev, "vusbPDMHubAttachDevice");
+ return rc;
+}
+
+
+/** @interface_method_impl{PDMUSBHUBREG,pfnDetachDevice} */
+static DECLCALLBACK(int) vusbPDMHubDetachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, uint32_t iPort)
+{
+ RT_NOREF(iPort);
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ PVUSBDEV pDev = (PVUSBDEV)pUsbIns->pvVUsbDev2;
+ Assert(pDev);
+
+ LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, pDev->i16Port, pThis->pszName));
+
+ /*
+ * Deal with pending async reset.
+ * (anything but reset)
+ */
+ vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET);
+ vusbHubDetach(pThis, pDev);
+ vusbDevRelease(pDev, "vusbPDMHubDetachDevice");
+ return VINF_SUCCESS;
+}
+
+/**
+ * The hub registration structure.
+ */
+static const PDMUSBHUBREG g_vusbHubReg =
+{
+ PDM_USBHUBREG_VERSION,
+ vusbPDMHubAttachDevice,
+ vusbPDMHubDetachDevice,
+ PDM_USBHUBREG_VERSION
+};
+
+
+/* -=-=-=-=-=- VUSBIROOTHUBCONNECTOR methods -=-=-=-=-=- */
+
+
+/**
+ * Callback for freeing an URB.
+ * @param pUrb The URB to free.
+ */
+static DECLCALLBACK(void) vusbRhFreeUrb(PVUSBURB pUrb)
+{
+ /*
+ * Assert sanity.
+ */
+ vusbUrbAssert(pUrb);
+ PVUSBROOTHUB pRh = (PVUSBROOTHUB)pUrb->pVUsb->pvFreeCtx;
+ Assert(pRh);
+
+ Assert(pUrb->enmState != VUSBURBSTATE_FREE);
+
+#ifdef LOG_ENABLED
+ vusbUrbTrace(pUrb, "vusbRhFreeUrb", true);
+#endif
+
+ /*
+ * Free the URB description (logging builds only).
+ */
+ if (pUrb->pszDesc)
+ {
+ RTStrFree(pUrb->pszDesc);
+ pUrb->pszDesc = NULL;
+ }
+
+ /* The URB comes from the roothub if there is no device (invalid address). */
+ if (pUrb->pVUsb->pDev)
+ {
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+
+ vusbUrbPoolFree(&pUrb->pVUsb->pDev->UrbPool, pUrb);
+ vusbDevRelease(pDev, "vusbRhFreeUrb");
+ }
+ else
+ vusbUrbPoolFree(&pRh->UrbPool, pUrb);
+}
+
+
+/**
+ * Worker routine for vusbRhConnNewUrb().
+ */
+static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag)
+{
+ RT_NOREF(pszTag);
+ PVUSBURBPOOL pUrbPool = &pRh->UrbPool;
+
+ if (RT_UNLIKELY(cbData > (32 * _1M)))
+ {
+ LogFunc(("Bad URB size (%u)!\n", cbData));
+ return NULL;
+ }
+
+ PVUSBDEV pDev;
+ if (uPort == VUSB_DEVICE_PORT_INVALID)
+ pDev = vusbR3RhGetVUsbDevByAddrRetain(pRh, DstAddress, "vusbRhNewUrb");
+ else
+ pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhNewUrb");
+
+ if (pDev)
+ pUrbPool = &pDev->UrbPool;
+
+ PVUSBURB pUrb = vusbUrbPoolAlloc(pUrbPool, enmType, enmDir, cbData,
+ pRh->cbHci, pRh->cbHciTd, cTds);
+ if (RT_LIKELY(pUrb))
+ {
+ pUrb->pVUsb->pvFreeCtx = pRh;
+ pUrb->pVUsb->pfnFree = vusbRhFreeUrb;
+ pUrb->DstAddress = DstAddress;
+ pUrb->pVUsb->pDev = pDev;
+
+#ifdef LOG_ENABLED
+ const char *pszType = NULL;
+
+ switch(pUrb->enmType)
+ {
+ case VUSBXFERTYPE_CTRL:
+ pszType = "ctrl";
+ break;
+ case VUSBXFERTYPE_INTR:
+ pszType = "intr";
+ break;
+ case VUSBXFERTYPE_BULK:
+ pszType = "bulk";
+ break;
+ case VUSBXFERTYPE_ISOC:
+ pszType = "isoc";
+ break;
+ default:
+ pszType = "invld";
+ break;
+ }
+
+ pRh->iSerial = (pRh->iSerial + 1) % 10000;
+ RTStrAPrintf(&pUrb->pszDesc, "URB %p %s%c%04d (%s)", pUrb, pszType,
+ (pUrb->enmDir == VUSBDIRECTION_IN) ? '<' : ((pUrb->enmDir == VUSBDIRECTION_SETUP) ? 's' : '>'),
+ pRh->iSerial, pszTag ? pszTag : "<none>");
+
+ vusbUrbTrace(pUrb, "vusbRhNewUrb", false);
+#endif
+ }
+
+ return pUrb;
+}
+
+
+/**
+ * Calculate frame timer variables given a frame rate.
+ */
+static void vusbRhR3CalcTimerIntervals(PVUSBROOTHUB pThis, uint32_t u32FrameRate)
+{
+ pThis->nsWait = RT_NS_1SEC / u32FrameRate;
+ pThis->uFrameRate = u32FrameRate;
+ /* Inform the HCD about the new frame rate. */
+ pThis->pIRhPort->pfnFrameRateChanged(pThis->pIRhPort, u32FrameRate);
+}
+
+
+/**
+ * Calculates the new frame rate based on the idle detection and number of idle
+ * cycles.
+ *
+ * @returns nothing.
+ * @param pThis The roothub instance data.
+ * @param fIdle Flag whether the last frame didn't produce any activity.
+ */
+static void vusbRhR3FrameRateCalcNew(PVUSBROOTHUB pThis, bool fIdle)
+{
+ uint32_t uNewFrameRate = pThis->uFrameRate;
+
+ /*
+ * Adjust the frame timer interval based on idle detection.
+ */
+ if (fIdle)
+ {
+ pThis->cIdleCycles++;
+ /* Set the new frame rate based on how long we've been idle. Tunable. */
+ switch (pThis->cIdleCycles)
+ {
+ case 4: uNewFrameRate = 500; break; /* 2ms interval */
+ case 16:uNewFrameRate = 125; break; /* 8ms interval */
+ case 24:uNewFrameRate = 50; break; /* 20ms interval */
+ default: break;
+ }
+ /* Avoid overflow. */
+ if (pThis->cIdleCycles > 60000)
+ pThis->cIdleCycles = 20000;
+ }
+ else
+ {
+ if (pThis->cIdleCycles)
+ {
+ pThis->cIdleCycles = 0;
+ uNewFrameRate = pThis->uFrameRateDefault;
+ }
+ }
+
+ if ( uNewFrameRate != pThis->uFrameRate
+ && uNewFrameRate)
+ {
+ LogFlow(("Frame rate changed from %u to %u\n", pThis->uFrameRate, uNewFrameRate));
+ vusbRhR3CalcTimerIntervals(pThis, uNewFrameRate);
+ }
+}
+
+
+/**
+ * The core frame processing routine keeping track of the elapsed time and calling into
+ * the device emulation above us to do the work.
+ *
+ * @returns Relative timespan when to process the next frame.
+ * @param pThis The roothub instance data.
+ * @param fCallback Flag whether this method is called from the URB completion callback or
+ * from the worker thread (only used for statistics).
+ */
+DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback)
+{
+ uint64_t tsNext = 0;
+ uint64_t tsNanoStart = RTTimeNanoTS();
+
+ /* Don't do anything if we are not supposed to process anything (EHCI and XHCI). */
+ if (!pThis->uFrameRateDefault)
+ return 0;
+
+ if (ASMAtomicXchgBool(&pThis->fFrameProcessing, true))
+ return pThis->nsWait;
+
+ if ( tsNanoStart > pThis->tsFrameProcessed
+ && tsNanoStart - pThis->tsFrameProcessed >= 750 * RT_NS_1US)
+ {
+ LogFlowFunc(("Starting new frame at ts %llu\n", tsNanoStart));
+
+ bool fIdle = pThis->pIRhPort->pfnStartFrame(pThis->pIRhPort, 0 /* u32FrameNo */);
+ vusbRhR3FrameRateCalcNew(pThis, fIdle);
+
+ uint64_t tsNow = RTTimeNanoTS();
+ tsNext = (tsNanoStart + pThis->nsWait) > tsNow ? (tsNanoStart + pThis->nsWait) - tsNow : 0;
+ pThis->tsFrameProcessed = tsNanoStart;
+ LogFlowFunc(("Current frame took %llu nano seconds to process, next frame in %llu ns\n", tsNow - tsNanoStart, tsNext));
+ if (fCallback)
+ STAM_COUNTER_INC(&pThis->StatFramesProcessedClbk);
+ else
+ STAM_COUNTER_INC(&pThis->StatFramesProcessedThread);
+ }
+ else
+ {
+ tsNext = (pThis->tsFrameProcessed + pThis->nsWait) > tsNanoStart ? (pThis->tsFrameProcessed + pThis->nsWait) - tsNanoStart : 0;
+ LogFlowFunc(("Next frame is too far away in the future, waiting... (tsNanoStart=%llu tsFrameProcessed=%llu)\n",
+ tsNanoStart, pThis->tsFrameProcessed));
+ }
+
+ ASMAtomicXchgBool(&pThis->fFrameProcessing, false);
+ LogFlowFunc(("returns %llu\n", tsNext));
+ return tsNext;
+}
+
+
+/**
+ * Worker for processing frames periodically.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pThread The PDM thread structure for the thread this worker runs on.
+ */
+static DECLCALLBACK(int) vusbRhR3PeriodFrameWorker(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pDrvIns);
+ int rc = VINF_SUCCESS;
+ PVUSBROOTHUB pThis = (PVUSBROOTHUB)pThread->pvUser;
+
+ if (pThread->enmState == PDMTHREADSTATE_INITIALIZING)
+ return VINF_SUCCESS;
+
+ while (pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ while ( !ASMAtomicReadU32(&pThis->uFrameRateDefault)
+ && pThread->enmState == PDMTHREADSTATE_RUNNING)
+ {
+ /* Signal the waiter that we are stopped now. */
+ rc = RTSemEventMultiSignal(pThis->hSemEventPeriodFrameStopped);
+ AssertRC(rc);
+
+ rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrame, RT_INDEFINITE_WAIT);
+ RTSemEventMultiReset(pThis->hSemEventPeriodFrame);
+
+ /*
+ * Notify the device above about the frame rate changed if we are supposed to
+ * process frames.
+ */
+ uint32_t uFrameRate = ASMAtomicReadU32(&pThis->uFrameRateDefault);
+ if (uFrameRate)
+ vusbRhR3CalcTimerIntervals(pThis, uFrameRate);
+ }
+
+ AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc);
+ if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING))
+ break;
+
+ uint64_t tsNext = vusbRhR3ProcessFrame(pThis, false /* fCallback */);
+
+ if (tsNext >= 250 * RT_NS_1US)
+ {
+ rc = RTSemEventMultiWaitEx(pThis->hSemEventPeriodFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE,
+ tsNext);
+ AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc));
+ RTSemEventMultiReset(pThis->hSemEventPeriodFrame);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Unblock the periodic frame thread so it can respond to a state change.
+ *
+ * @returns VBox status code.
+ * @param pDrvIns The driver instance.
+ * @param pThread The send thread.
+ */
+static DECLCALLBACK(int) vusbRhR3PeriodFrameWorkerWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread)
+{
+ RT_NOREF(pThread);
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ return RTSemEventMultiSignal(pThis->hSemEventPeriodFrame);
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetUrbParams} */
+static DECLCALLBACK(int) vusbRhSetUrbParams(PVUSBIROOTHUBCONNECTOR pInterface, size_t cbHci, size_t cbHciTd)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+
+ pRh->cbHci = cbHci;
+ pRh->cbHciTd = cbHciTd;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReset} */
+static DECLCALLBACK(int) vusbR3RhReset(PVUSBIROOTHUBCONNECTOR pInterface, bool fResetOnLinux)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux);
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOn} */
+static DECLCALLBACK(int) vusbR3RhPowerOn(PVUSBIROOTHUBCONNECTOR pInterface)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ LogFlow(("vusR3bRhPowerOn: pRh=%p\n", pRh));
+
+ Assert( pRh->enmState != VUSB_DEVICE_STATE_DETACHED
+ && pRh->enmState != VUSB_DEVICE_STATE_RESET);
+
+ if (pRh->enmState == VUSB_DEVICE_STATE_ATTACHED)
+ pRh->enmState = VUSB_DEVICE_STATE_POWERED;
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnPowerOff} */
+static DECLCALLBACK(int) vusbR3RhPowerOff(PVUSBIROOTHUBCONNECTOR pInterface)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ LogFlow(("vusbR3RhDevPowerOff: pThis=%p\n", pThis));
+
+ Assert( pThis->enmState != VUSB_DEVICE_STATE_DETACHED
+ && pThis->enmState != VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Cancel all URBs and reap them.
+ */
+ VUSBIRhCancelAllUrbs(&pThis->IRhConnector);
+ for (uint32_t uPort = 0; uPort < RT_ELEMENTS(pThis->apDevByPort); uPort++)
+ VUSBIRhReapAsyncUrbs(&pThis->IRhConnector, uPort, 0);
+
+ pThis->enmState = VUSB_DEVICE_STATE_ATTACHED;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnNewUrb} */
+static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, uint32_t uPort, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ return vusbRhNewUrb(pRh, DstAddress, uPort, enmType, enmDir, cbData, cTds, pszTag);
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnFreeUrb} */
+static DECLCALLBACK(int) vusbRhConnFreeUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb)
+{
+ RT_NOREF(pInterface);
+ pUrb->pVUsb->pfnFree(pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSubmitUrb} */
+static DECLCALLBACK(int) vusbRhSubmitUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb, PPDMLED pLed)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ STAM_PROFILE_START(&pRh->StatSubmitUrb, a);
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Total and per-type submit statistics.
+ */
+ Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));
+ STAM_COUNTER_INC(&pRh->Total.StatUrbsSubmitted);
+ STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsSubmitted);
+
+ STAM_COUNTER_ADD(&pRh->Total.StatReqBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqBytes, pUrb->cbData);
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatReqReadBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqReadBytes, pUrb->cbData);
+ }
+ else
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatReqWriteBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqWriteBytes, pUrb->cbData);
+ }
+
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ STAM_COUNTER_ADD(&pRh->StatIsocReqPkts, pUrb->cIsocPkts);
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ STAM_COUNTER_ADD(&pRh->StatIsocReqReadPkts, pUrb->cIsocPkts);
+ else
+ STAM_COUNTER_ADD(&pRh->StatIsocReqWritePkts, pUrb->cIsocPkts);
+ }
+#endif
+
+ /* If there is a sniffer on the roothub record the URB there. */
+ if (pRh->hSniffer != VUSBSNIFFER_NIL)
+ {
+ int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT);
+ if (RT_FAILURE(rc))
+ LogRel(("VUSB: Capturing URB submit event on the root hub failed with %Rrc\n", rc));
+ }
+
+ /*
+ * The device was resolved when we allocated the URB.
+ * Submit it to the device if we found it, if not fail with device-not-ready.
+ */
+ int rc;
+ if ( pUrb->pVUsb->pDev
+ && pUrb->pVUsb->pDev->pUsbIns)
+ {
+ switch (pUrb->enmDir)
+ {
+ case VUSBDIRECTION_IN:
+ pLed->Asserted.s.fReading = pLed->Actual.s.fReading = 1;
+ rc = vusbUrbSubmit(pUrb);
+ pLed->Actual.s.fReading = 0;
+ break;
+ case VUSBDIRECTION_OUT:
+ pLed->Asserted.s.fWriting = pLed->Actual.s.fWriting = 1;
+ rc = vusbUrbSubmit(pUrb);
+ pLed->Actual.s.fWriting = 0;
+ break;
+ default:
+ rc = vusbUrbSubmit(pUrb);
+ break;
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("vusbRhSubmitUrb: freeing pUrb=%p\n", pUrb));
+ pUrb->pVUsb->pfnFree(pUrb);
+ }
+ }
+ else
+ {
+ Log(("vusb: pRh=%p: SUBMIT: Address %i not found!!!\n", pRh, pUrb->DstAddress));
+
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ vusbUrbCompletionRhEx(pRh, pUrb);
+ rc = VINF_SUCCESS;
+ }
+
+ STAM_PROFILE_STOP(&pRh->StatSubmitUrb, a);
+ return rc;
+}
+
+
+static DECLCALLBACK(int) vusbRhReapAsyncUrbsWorker(PVUSBDEV pDev, RTMSINTERVAL cMillies)
+{
+ if (!cMillies)
+ vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, 0);
+ else
+ {
+ uint64_t u64Start = RTTimeMilliTS();
+ do
+ {
+ vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, RT_MIN(cMillies >> 8, 10));
+ } while ( !RTListIsEmpty(&pDev->LstAsyncUrbs)
+ && RTTimeMilliTS() - u64Start < cMillies);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReapAsyncUrbs} */
+static DECLCALLBACK(void) vusbRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, RTMSINTERVAL cMillies)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); NOREF(pRh);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhReapAsyncUrbs");
+
+ if (!pDev)
+ return;
+
+ if (!RTListIsEmpty(&pDev->LstAsyncUrbs))
+ {
+ STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies);
+ AssertRC(rc);
+ STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a);
+ }
+
+ vusbDevRelease(pDev, "vusbRhReapAsyncUrbs");
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelUrbsEp} */
+static DECLCALLBACK(int) vusbRhCancelUrbsEp(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ AssertReturn(pRh, VERR_INVALID_PARAMETER);
+ AssertReturn(pUrb, VERR_INVALID_PARAMETER);
+
+ /// @todo This method of URB canceling may not work on non-Linux hosts.
+ /*
+ * Cancel and reap the URB(s) on an endpoint.
+ */
+ LogFlow(("vusbRhCancelUrbsEp: pRh=%p pUrb=%p\n", pRh, pUrb));
+
+ vusbUrbCancelAsync(pUrb, CANCELMODE_UNDO);
+
+ /* The reaper thread will take care of completing the URB. */
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Worker doing the actual cancelling of all outstanding URBs on the device I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pDev USB device instance data.
+ */
+static DECLCALLBACK(int) vusbRhCancelAllUrbsWorker(PVUSBDEV pDev)
+{
+ /*
+ * Cancel the URBS.
+ *
+ * Not using th CritAsyncUrbs critical section here is safe
+ * as the I/O thread is the only thread accessing this struture at the
+ * moment.
+ */
+ PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext;
+ RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst)
+ {
+ PVUSBURB pUrb = pVUsbUrb->pUrb;
+ /* Call the worker directly. */
+ vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL);
+ }
+
+ return VINF_SUCCESS;
+}
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelAllUrbs} */
+static DECLCALLBACK(void) vusbRhCancelAllUrbs(PVUSBIROOTHUBCONNECTOR pInterface)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+
+ RTCritSectEnter(&pThis->CritSectDevices);
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++)
+ {
+ PVUSBDEV pDev = pThis->apDevByPort[i];
+ if (pDev)
+ vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev);
+ }
+ RTCritSectLeave(&pThis->CritSectDevices);
+}
+
+/**
+ * Worker doing the actual cancelling of all outstanding per-EP URBs on the
+ * device I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pDev USB device instance data.
+ * @param EndPt Endpoint number.
+ * @param enmDir Endpoint direction.
+ */
+static DECLCALLBACK(int) vusbRhAbortEpWorker(PVUSBDEV pDev, int EndPt, VUSBDIRECTION enmDir)
+{
+ /*
+ * Iterate the URBs, find ones corresponding to given EP, and cancel them.
+ */
+ PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext;
+ RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst)
+ {
+ PVUSBURB pUrb = pVUsbUrb->pUrb;
+
+ Assert(pUrb->pVUsb->pDev == pDev);
+
+ /* For the default control EP, direction does not matter. */
+ if (pUrb->EndPt == EndPt && (pUrb->enmDir == enmDir || !EndPt))
+ {
+ LogFlow(("%s: vusbRhAbortEpWorker: CANCELING URB\n", pUrb->pszDesc));
+ int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_UNDO);
+ AssertRC(rc);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAbortEp} */
+static DECLCALLBACK(int) vusbRhAbortEp(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, int EndPt, VUSBDIRECTION enmDir)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhAbortEp");
+
+ if (pDev->pHub != pRh)
+ AssertFailedReturn(VERR_INVALID_PARAMETER);
+
+ vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhAbortEpWorker, 3, pDev, EndPt, enmDir);
+ vusbDevRelease(pDev, "vusbRhAbortEp");
+
+ /* The reaper thread will take care of completing the URB. */
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetPeriodicFrameProcessing} */
+static DECLCALLBACK(int) vusbRhSetFrameProcessing(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uFrameRate)
+{
+ int rc = VINF_SUCCESS;
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+
+ /* Create the frame thread lazily. */
+ if ( !pThis->hThreadPeriodFrame
+ && uFrameRate)
+ {
+ ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate);
+ pThis->uFrameRate = uFrameRate;
+ vusbRhR3CalcTimerIntervals(pThis, uFrameRate);
+
+ rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrame);
+ AssertRCReturn(rc, rc);
+
+ rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrameStopped);
+ AssertRCReturn(rc, rc);
+
+ rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->hThreadPeriodFrame, pThis, vusbRhR3PeriodFrameWorker,
+ vusbRhR3PeriodFrameWorkerWakeup, 0, RTTHREADTYPE_IO, "VUsbPeriodFrm");
+ AssertRCReturn(rc, rc);
+
+ VMSTATE enmState = PDMDrvHlpVMState(pThis->pDrvIns);
+ if ( enmState == VMSTATE_RUNNING
+ || enmState == VMSTATE_RUNNING_LS)
+ {
+ rc = PDMDrvHlpThreadResume(pThis->pDrvIns, pThis->hThreadPeriodFrame);
+ AssertRCReturn(rc, rc);
+ }
+ }
+ else if ( pThis->hThreadPeriodFrame
+ && !uFrameRate)
+ {
+ /* Stop processing. */
+ uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate);
+ if (uFrameRateOld)
+ {
+ rc = RTSemEventMultiReset(pThis->hSemEventPeriodFrameStopped);
+ AssertRC(rc);
+
+ /* Signal the frame thread to stop. */
+ RTSemEventMultiSignal(pThis->hSemEventPeriodFrame);
+
+ /* Wait for signal from the thread that it stopped. */
+ rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrameStopped, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ }
+ else if ( pThis->hThreadPeriodFrame
+ && uFrameRate)
+ {
+ /* Just switch to the new frame rate and let the periodic frame thread pick it up. */
+ uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate);
+
+ /* Signal the frame thread to continue if it was stopped. */
+ if (!uFrameRateOld)
+ RTSemEventMultiSignal(pThis->hSemEventPeriodFrame);
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnGetPeriodicFrameRate} */
+static DECLCALLBACK(uint32_t) vusbRhGetPeriodicFrameRate(PVUSBIROOTHUBCONNECTOR pInterface)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+
+ return pThis->uFrameRate;
+}
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnUpdateIsocFrameDelta} */
+static DECLCALLBACK(uint32_t) vusbRhUpdateIsocFrameDelta(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort,
+ int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits)
+{
+ PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ AssertReturn(pRh, 0);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pRh, uPort, "vusbRhUpdateIsocFrameDelta"); AssertPtr(pDev);
+ PVUSBPIPE pPipe = &pDev->aPipes[EndPt];
+ uint32_t *puLastFrame;
+ int32_t uFrameDelta;
+ uint32_t uMaxVal = 1 << uBits;
+
+ puLastFrame = enmDir == VUSBDIRECTION_IN ? &pPipe->uLastFrameIn : &pPipe->uLastFrameOut;
+ uFrameDelta = uNewFrameID - *puLastFrame;
+ *puLastFrame = uNewFrameID;
+ /* Take care of wrap-around. */
+ if (uFrameDelta < 0)
+ uFrameDelta += uMaxVal;
+
+ vusbDevRelease(pDev, "vusbRhUpdateIsocFrameDelta");
+ return (uint16_t)uFrameDelta;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevReset} */
+static DECLCALLBACK(int) vusbR3RhDevReset(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort, bool fResetOnLinux,
+ PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevReset");
+ AssertPtrReturn(pDev, VERR_VUSB_DEVICE_NOT_ATTACHED);
+
+ int rc = VUSBIDevReset(&pDev->IDevice, fResetOnLinux, pfnDone, pvUser, pVM);
+ vusbDevRelease(pDev, "vusbR3RhDevReset");
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOn} */
+static DECLCALLBACK(int) vusbR3RhDevPowerOn(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOn");
+ AssertPtr(pDev);
+
+ int rc = VUSBIDevPowerOn(&pDev->IDevice);
+ vusbDevRelease(pDev, "vusbR3RhDevPowerOn");
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevPowerOff} */
+static DECLCALLBACK(int) vusbR3RhDevPowerOff(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevPowerOff");
+ AssertPtr(pDev);
+
+ int rc = VUSBIDevPowerOff(&pDev->IDevice);
+ vusbDevRelease(pDev, "vusbR3RhDevPowerOff");
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetState} */
+static DECLCALLBACK(VUSBDEVICESTATE) vusbR3RhDevGetState(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetState");
+ AssertPtr(pDev);
+
+ VUSBDEVICESTATE enmState = VUSBIDevGetState(&pDev->IDevice);
+ vusbDevRelease(pDev, "vusbR3RhDevGetState");
+ return enmState;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevIsSavedStateSupported} */
+static DECLCALLBACK(bool) vusbR3RhDevIsSavedStateSupported(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevIsSavedStateSupported");
+ AssertPtr(pDev);
+
+ bool fSavedStateSupported = VUSBIDevIsSavedStateSupported(&pDev->IDevice);
+ vusbDevRelease(pDev, "vusbR3RhDevIsSavedStateSupported");
+ return fSavedStateSupported;
+}
+
+
+/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDevGetSpeed} */
+static DECLCALLBACK(VUSBSPEED) vusbR3RhDevGetSpeed(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uPort)
+{
+ PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface);
+ PVUSBDEV pDev = vusbR3RhGetVUsbDevByPortRetain(pThis, uPort, "vusbR3RhDevGetSpeed");
+ AssertPtr(pDev);
+
+ VUSBSPEED enmSpeed = pDev->IDevice.pfnGetSpeed(&pDev->IDevice);
+ vusbDevRelease(pDev, "vusbR3RhDevGetSpeed");
+ return enmSpeed;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDRVSAVEPREP, All URBs needs to be canceled.}
+ */
+static DECLCALLBACK(int) vusbR3RhSavePrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ LogFlow(("vusbR3RhSavePrep:\n"));
+ RT_NOREF(pSSM);
+
+ /*
+ * Detach all proxied devices.
+ */
+ RTCritSectEnter(&pThis->CritSectDevices);
+
+ /** @todo we a) can't tell which are proxied, and b) this won't work well when continuing after saving! */
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++)
+ {
+ PVUSBDEV pDev = pThis->apDevByPort[i];
+ if (pDev)
+ {
+ if (!VUSBIDevIsSavedStateSupported(&pDev->IDevice))
+ {
+ int rc = vusbHubDetach(pThis, pDev);
+ AssertRC(rc);
+
+ /*
+ * Save the device pointers here so we can reattach them afterwards.
+ * This will work fine even if the save fails since the Done handler is
+ * called unconditionally if the Prep handler was called.
+ */
+ pThis->apDevByPort[i] = pDev;
+ }
+ }
+ }
+
+ RTCritSectLeave(&pThis->CritSectDevices);
+
+ /*
+ * Kill old load data which might be hanging around.
+ */
+ if (pThis->pLoad)
+ {
+ PDMDrvHlpTimerDestroy(pDrvIns, pThis->pLoad->hTimer);
+ pThis->pLoad->hTimer = NIL_TMTIMERHANDLE;
+ PDMDrvHlpMMHeapFree(pDrvIns, pThis->pLoad);
+ pThis->pLoad = NULL;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDRVSAVEDONE}
+ */
+static DECLCALLBACK(int) vusbR3RhSaveDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ PVUSBDEV aPortsOld[VUSB_DEVICES_MAX];
+ unsigned i;
+ LogFlow(("vusbR3RhSaveDone:\n"));
+ RT_NOREF(pSSM);
+
+ /* Save the current data. */
+ memcpy(aPortsOld, pThis->apDevByPort, sizeof(aPortsOld));
+ AssertCompile(sizeof(aPortsOld) == sizeof(pThis->apDevByPort));
+
+ /*
+ * NULL the dev pointers.
+ */
+ for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++)
+ if (pThis->apDevByPort[i] && !VUSBIDevIsSavedStateSupported(&pThis->apDevByPort[i]->IDevice))
+ pThis->apDevByPort[i] = NULL;
+
+ /*
+ * Attach the devices.
+ */
+ for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++)
+ {
+ PVUSBDEV pDev = aPortsOld[i];
+ if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice))
+ vusbHubAttach(pThis, pDev);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDRVLOADPREP, This must detach the devices
+ * currently attached and save them for reconnect after the state load has been
+ * completed.}
+ */
+static DECLCALLBACK(int) vusbR3RhLoadPrep(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ int rc = VINF_SUCCESS;
+ LogFlow(("vusbR3RhLoadPrep:\n"));
+ RT_NOREF(pSSM);
+
+ if (!pThis->pLoad)
+ {
+ VUSBROOTHUBLOAD Load;
+ unsigned i;
+
+ /// @todo This is all bogus.
+ /*
+ * Detach all devices which are present in this session. Save them in the load
+ * structure so we can reattach them after restoring the guest.
+ */
+ Load.hTimer = NIL_TMTIMERHANDLE;
+ Load.cDevs = 0;
+ for (i = 0; i < RT_ELEMENTS(pThis->apDevByPort); i++)
+ {
+ PVUSBDEV pDev = pThis->apDevByPort[i];
+ if (pDev && !VUSBIDevIsSavedStateSupported(&pDev->IDevice))
+ {
+ Load.apDevs[Load.cDevs++] = pDev;
+ vusbHubDetach(pThis, pDev);
+ Assert(!pThis->apDevByPort[i]);
+ }
+ }
+
+ /*
+ * Any devices to reattach? If so, duplicate the Load struct.
+ */
+ if (Load.cDevs)
+ {
+ pThis->pLoad = (PVUSBROOTHUBLOAD)RTMemAllocZ(sizeof(Load));
+ if (!pThis->pLoad)
+ return VERR_NO_MEMORY;
+ *pThis->pLoad = Load;
+ }
+ }
+ /* else: we ASSUME no device can be attached or detached in the time
+ * between a state load and the pLoad stuff processing. */
+ return rc;
+}
+
+
+/**
+ * Reattaches devices after a saved state load.
+ */
+static DECLCALLBACK(void) vusbR3RhLoadReattachDevices(PPDMDRVINS pDrvIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ PVUSBROOTHUBLOAD pLoad = pThis->pLoad;
+ LogFlow(("vusbR3RhLoadReattachDevices:\n"));
+ Assert(hTimer == pLoad->hTimer); RT_NOREF(pvUser);
+
+ /*
+ * Reattach devices.
+ */
+ for (unsigned i = 0; i < pLoad->cDevs; i++)
+ vusbHubAttach(pThis, pLoad->apDevs[i]);
+
+ /*
+ * Cleanup.
+ */
+ PDMDrvHlpTimerDestroy(pDrvIns, hTimer);
+ pLoad->hTimer = NIL_TMTIMERHANDLE;
+ RTMemFree(pLoad);
+ pThis->pLoad = NULL;
+}
+
+
+/**
+ * @callback_method_impl{FNSSMDRVLOADDONE}
+ */
+static DECLCALLBACK(int) vusbR3RhLoadDone(PPDMDRVINS pDrvIns, PSSMHANDLE pSSM)
+{
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ LogFlow(("vusbR3RhLoadDone:\n"));
+ RT_NOREF(pSSM);
+
+ /*
+ * Start a timer if we've got devices to reattach
+ */
+ if (pThis->pLoad)
+ {
+ int rc = PDMDrvHlpTMTimerCreate(pDrvIns, TMCLOCK_VIRTUAL, vusbR3RhLoadReattachDevices, NULL,
+ TMTIMER_FLAGS_NO_CRIT_SECT | TMTIMER_FLAGS_NO_RING0,
+ "VUSB reattach on load", &pThis->pLoad->hTimer);
+ if (RT_SUCCESS(rc))
+ rc = PDMDrvHlpTimerSetMillies(pDrvIns, pThis->pLoad->hTimer, 250);
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/* -=-=-=-=-=- PDM Base interface methods -=-=-=-=-=- */
+
+
+/**
+ * @interface_method_impl{PDMIBASE,pfnQueryInterface}
+ */
+static DECLCALLBACK(void *) vusbRhQueryInterface(PPDMIBASE pInterface, const char *pszIID)
+{
+ PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
+ PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+
+ PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase);
+ PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBCONNECTOR, &pRh->IRhConnector);
+ return NULL;
+}
+
+
+/* -=-=-=-=-=- PDM Driver methods -=-=-=-=-=- */
+
+
+/**
+ * Destruct a driver instance.
+ *
+ * Most VM resources are freed by the VM. This callback is provided so that any non-VM
+ * resources can be freed correctly.
+ *
+ * @param pDrvIns The driver instance data.
+ */
+static DECLCALLBACK(void) vusbRhDestruct(PPDMDRVINS pDrvIns)
+{
+ PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns);
+
+ vusbUrbPoolDestroy(&pRh->UrbPool);
+ if (pRh->pszName)
+ {
+ RTStrFree(pRh->pszName);
+ pRh->pszName = NULL;
+ }
+ if (pRh->hSniffer != VUSBSNIFFER_NIL)
+ VUSBSnifferDestroy(pRh->hSniffer);
+
+ if (pRh->hSemEventPeriodFrame)
+ RTSemEventMultiDestroy(pRh->hSemEventPeriodFrame);
+
+ if (pRh->hSemEventPeriodFrameStopped)
+ RTSemEventMultiDestroy(pRh->hSemEventPeriodFrameStopped);
+
+ RTCritSectDelete(&pRh->CritSectDevices);
+}
+
+
+/**
+ * Construct a root hub driver instance.
+ *
+ * @copydoc FNPDMDRVCONSTRUCT
+ */
+static DECLCALLBACK(int) vusbRhConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags)
+{
+ RT_NOREF(fFlags);
+ PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns);
+ PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB);
+ PCPDMDRVHLPR3 pHlp = pDrvIns->pHlpR3;
+
+ LogFlow(("vusbRhConstruct: Instance %d\n", pDrvIns->iInstance));
+
+ /*
+ * Validate configuration.
+ */
+ PDMDRV_VALIDATE_CONFIG_RETURN(pDrvIns, "CaptureFilename", "");
+
+ /*
+ * Check that there are no drivers below us.
+ */
+ AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER,
+ ("Configuration error: Not possible to attach anything to this driver!\n"),
+ VERR_PDM_DRVINS_NO_ATTACH);
+
+ /*
+ * Initialize the critical sections.
+ */
+ int rc = RTCritSectInit(&pThis->CritSectDevices);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ char *pszCaptureFilename = NULL;
+ rc = pHlp->pfnCFGMQueryStringAlloc(pCfg, "CaptureFilename", &pszCaptureFilename);
+ if ( RT_FAILURE(rc)
+ && rc != VERR_CFGM_VALUE_NOT_FOUND)
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("Configuration error: Failed to query value of \"CaptureFilename\""));
+
+ /*
+ * Initialize the data members.
+ */
+ pDrvIns->IBase.pfnQueryInterface = vusbRhQueryInterface;
+ /* the usb device */
+ pThis->enmState = VUSB_DEVICE_STATE_ATTACHED;
+ //pThis->hub.cPorts - later
+ pThis->cDevices = 0;
+ RTStrAPrintf(&pThis->pszName, "RootHub#%d", pDrvIns->iInstance);
+ /* misc */
+ pThis->pDrvIns = pDrvIns;
+ /* the connector */
+ pThis->IRhConnector.pfnSetUrbParams = vusbRhSetUrbParams;
+ pThis->IRhConnector.pfnReset = vusbR3RhReset;
+ pThis->IRhConnector.pfnPowerOn = vusbR3RhPowerOn;
+ pThis->IRhConnector.pfnPowerOff = vusbR3RhPowerOff;
+ pThis->IRhConnector.pfnNewUrb = vusbRhConnNewUrb;
+ pThis->IRhConnector.pfnFreeUrb = vusbRhConnFreeUrb;
+ pThis->IRhConnector.pfnSubmitUrb = vusbRhSubmitUrb;
+ pThis->IRhConnector.pfnReapAsyncUrbs = vusbRhReapAsyncUrbs;
+ pThis->IRhConnector.pfnCancelUrbsEp = vusbRhCancelUrbsEp;
+ pThis->IRhConnector.pfnCancelAllUrbs = vusbRhCancelAllUrbs;
+ pThis->IRhConnector.pfnAbortEp = vusbRhAbortEp;
+ pThis->IRhConnector.pfnSetPeriodicFrameProcessing = vusbRhSetFrameProcessing;
+ pThis->IRhConnector.pfnGetPeriodicFrameRate = vusbRhGetPeriodicFrameRate;
+ pThis->IRhConnector.pfnUpdateIsocFrameDelta = vusbRhUpdateIsocFrameDelta;
+ pThis->IRhConnector.pfnDevReset = vusbR3RhDevReset;
+ pThis->IRhConnector.pfnDevPowerOn = vusbR3RhDevPowerOn;
+ pThis->IRhConnector.pfnDevPowerOff = vusbR3RhDevPowerOff;
+ pThis->IRhConnector.pfnDevGetState = vusbR3RhDevGetState;
+ pThis->IRhConnector.pfnDevIsSavedStateSupported = vusbR3RhDevIsSavedStateSupported;
+ pThis->IRhConnector.pfnDevGetSpeed = vusbR3RhDevGetSpeed;
+ pThis->hSniffer = VUSBSNIFFER_NIL;
+ pThis->cbHci = 0;
+ pThis->cbHciTd = 0;
+ pThis->fFrameProcessing = false;
+#ifdef LOG_ENABLED
+ pThis->iSerial = 0;
+#endif
+ /*
+ * Resolve interface(s).
+ */
+ pThis->pIRhPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, VUSBIROOTHUBPORT);
+ AssertMsgReturn(pThis->pIRhPort, ("Configuration error: the device/driver above us doesn't expose any VUSBIROOTHUBPORT interface!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE);
+
+ /*
+ * Get number of ports and the availability bitmap.
+ * ASSUME that the number of ports reported now at creation time is the max number.
+ */
+ pThis->cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap);
+ Log(("vusbRhConstruct: cPorts=%d\n", pThis->cPorts));
+
+ /*
+ * Get the USB version of the attached HC.
+ * ASSUME that version 2.0 implies high-speed.
+ */
+ pThis->fHcVersions = pThis->pIRhPort->pfnGetUSBVersions(pThis->pIRhPort);
+ Log(("vusbRhConstruct: fHcVersions=%u\n", pThis->fHcVersions));
+
+ rc = vusbUrbPoolInit(&pThis->UrbPool);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ if (pszCaptureFilename)
+ {
+ rc = VUSBSnifferCreate(&pThis->hSniffer, 0, pszCaptureFilename, NULL, NULL);
+ if (RT_FAILURE(rc))
+ return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS,
+ N_("VUSBSniffer cannot open '%s' for writing. The directory must exist and it must be writable for the current user"),
+ pszCaptureFilename);
+
+ PDMDrvHlpMMHeapFree(pDrvIns, pszCaptureFilename);
+ }
+
+ /*
+ * Register ourselves as a USB hub.
+ * The current implementation uses the VUSBIRHCONFIG interface for communication.
+ */
+ PCPDMUSBHUBHLP pHlpUsb; /* not used currently */
+ rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->cPorts, &g_vusbHubReg, &pHlpUsb);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /*
+ * Register the saved state data unit for attaching devices.
+ */
+ rc = PDMDrvHlpSSMRegisterEx(pDrvIns, VUSB_ROOTHUB_SAVED_STATE_VERSION, 0,
+ NULL, NULL, NULL,
+ vusbR3RhSavePrep, NULL, vusbR3RhSaveDone,
+ vusbR3RhLoadPrep, NULL, vusbR3RhLoadDone);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Statistics. (It requires a 30" monitor or extremely tiny fonts to edit this "table".)
+ */
+#ifdef VBOX_WITH_STATISTICS
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs submitted.", "/VUSB/%d/UrbsSubmitted", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsSubmitted/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsSubmitted/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsSubmitted/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsSubmitted/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs cancelled. (included in failed)", "/VUSB/%d/UrbsCancelled", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsCancelled/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsCancelled/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsCancelled/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsCancelled/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs failing.", "/VUSB/%d/UrbsFailed", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsFailed/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsFailed/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsFailed/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsFailed/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested transfer.", "/VUSB/%d/ReqBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqBytes/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested read transfer.", "/VUSB/%d/ReqReadBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqReadBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqReadBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqReadBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqReadBytes/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested write transfer.", "/VUSB/%d/ReqWriteBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqWriteBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqWriteBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqWriteBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqWriteBytes/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total transfer.", "/VUSB/%d/ActBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActBytes/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total read transfer.", "/VUSB/%d/ActReadBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActReadBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActReadBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActReadBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActReadBytes/Isoc", pDrvIns->iInstance);
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total write transfer.", "/VUSB/%d/ActWriteBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActWriteBytes/Bulk", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActWriteBytes/Ctrl", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActWriteBytes/Intr", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActWriteBytes/Isoc", pDrvIns->iInstance);
+
+ /* bulk */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Bulk/Urbs", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Bulk/UrbsFailed", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Bulk/UrbsFailed/Cancelled", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Bulk/ActBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ActBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ActBytes/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Bulk/ReqBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ReqBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ReqBytes/Write", pDrvIns->iInstance);
+
+ /* control */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Ctrl/Urbs", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Ctrl/UrbsFailed", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Ctrl/UrbsFailed/Cancelled", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Ctrl/ActBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ActBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ActBytes/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Ctrl/ReqBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ReqBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ReqBytes/Write", pDrvIns->iInstance);
+
+ /* interrupt */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Intr/Urbs", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Intr/UrbsFailed", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Intr/UrbsFailed/Cancelled", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Intr/ActBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ActBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ActBytes/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Intr/ReqBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ReqBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ReqBytes/Write", pDrvIns->iInstance);
+
+ /* isochronous */
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Isoc/Urbs", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Isoc/UrbsFailed", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Isoc/UrbsFailed/Cancelled", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Isoc/ActBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ActBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ActBytes/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Isoc/ReqBytes", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ReqBytes/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ReqBytes/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of isochronous packets returning data.", "/VUSB/%d/Isoc/ActPkts", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ActPkts/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ActPkts/Write", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Requested number of isochronous packets.", "/VUSB/%d/Isoc/ReqPkts", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ReqPkts/Read", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ReqPkts/Write", pDrvIns->iInstance);
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pThis->aStatIsocDetails); i++)
+ {
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Pkts, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok0", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun0", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataOverrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataOverrun", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].NotAccessed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/NotAccessed", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Misc, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Misc", pDrvIns->iInstance, i);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Bytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, ".", "/VUSB/%d/Isoc/%d/Bytes", pDrvIns->iInstance, i);
+ }
+
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReapAsyncUrbs, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling the vusbRhReapAsyncUrbs body (omitting calls when nothing is in-flight).",
+ "/VUSB/%d/ReapAsyncUrbs", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatSubmitUrb, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_TICKS_PER_CALL, "Profiling the vusbRhSubmitUrb body.",
+ "/VUSB/%d/SubmitUrb", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedThread, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the dedicated thread",
+ "/VUSB/%d/FramesProcessedThread", pDrvIns->iInstance);
+ PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedClbk, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the URB completion callback",
+ "/VUSB/%d/FramesProcessedClbk", pDrvIns->iInstance);
+#endif
+ PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->UrbPool.cUrbsInPool, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs in the pool.",
+ "/VUSB/%d/cUrbsInPool", pDrvIns->iInstance);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * VUSB Root Hub driver registration record.
+ */
+const PDMDRVREG g_DrvVUSBRootHub =
+{
+ /* u32Version */
+ PDM_DRVREG_VERSION,
+ /* szName */
+ "VUSBRootHub",
+ /* szRCMod */
+ "",
+ /* szR0Mod */
+ "",
+ /* pszDescription */
+ "VUSB Root Hub Driver.",
+ /* fFlags */
+ PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
+ /* fClass. */
+ PDM_DRVREG_CLASS_USB,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(VUSBROOTHUB),
+ /* pfnConstruct */
+ vusbRhConstruct,
+ /* pfnDestruct */
+ vusbRhDestruct,
+ /* pfnRelocate */
+ NULL,
+ /* pfnIOCtl */
+ NULL,
+ /* pfnPowerOn */
+ NULL,
+ /* pfnReset */
+ NULL,
+ /* pfnSuspend */
+ NULL,
+ /* pfnResume */
+ NULL,
+ /* pfnAttach */
+ NULL,
+ /* pfnDetach */
+ NULL,
+ /* pfnPowerOff */
+ NULL,
+ /* pfnSoftReset */
+ NULL,
+ /* u32EndVersion */
+ PDM_DRVREG_VERSION
+};
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "bsd"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: s
+ * End:
+ */
+
diff --git a/src/VBox/Devices/USB/Makefile.kup b/src/VBox/Devices/USB/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/Makefile.kup
diff --git a/src/VBox/Devices/USB/USBProxyDevice-stub.cpp b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp
new file mode 100644
index 00000000..9f8dd353
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp
@@ -0,0 +1,61 @@
+/* $Id: USBProxyDevice-stub.cpp $ */
+/** @file
+ * USB device proxy - Stub.
+ */
+
+/*
+ * Copyright (C) 2008-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/vmm/pdm.h>
+
+#include "USBProxyDevice.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/**
+ * Stub USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ "host",
+ 0, /* cbBackend */
+ NULL, /* Open */
+ NULL, /* Init */
+ NULL, /* Close */
+ NULL, /* Reset */
+ NULL, /* SetConfig */
+ NULL, /* ClaimInterface */
+ NULL, /* ReleaseInterface */
+ NULL, /* SetInterface */
+ NULL, /* ClearHaltedEp */
+ NULL, /* UrbQueue */
+ NULL, /* UrbCancel */
+ NULL, /* UrbReap */
+ 0
+};
+
diff --git a/src/VBox/Devices/USB/USBProxyDevice.cpp b/src/VBox/Devices/USB/USBProxyDevice.cpp
new file mode 100644
index 00000000..66c1452b
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice.cpp
@@ -0,0 +1,1328 @@
+/* $Id: USBProxyDevice.cpp $ */
+/** @file
+ * USBProxy - USB device proxy.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#include <VBox/usb.h>
+#include <VBox/usbfilter.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include "USBProxyDevice.h"
+#include "VUSBInternal.h"
+#include "VBoxDD.h"
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** A dummy name used early during the construction phase to avoid log crashes. */
+static char g_szDummyName[] = "proxy xxxx:yyyy";
+
+/**
+ * Array of supported proxy backends.
+ */
+static PCUSBPROXYBACK g_aUsbProxies[] =
+{
+ &g_USBProxyDeviceHost,
+ &g_USBProxyDeviceVRDP,
+ &g_USBProxyDeviceUsbIp
+};
+
+/* Synchronously obtain a standard USB descriptor for a device, used in order
+ * to grab configuration descriptors when we first add the device
+ */
+static void *GetStdDescSync(PUSBPROXYDEV pProxyDev, uint8_t iDescType, uint8_t iIdx, uint16_t LangId, uint16_t cbHint)
+{
+#define GET_DESC_RETRIES 6
+ int cRetries = 0;
+ uint16_t cbInitialHint = cbHint;
+
+ LogFlow(("GetStdDescSync: pProxyDev=%s, iDescType=%d, iIdx=%d, LangId=%04X, cbHint=%u\n", pProxyDev->pUsbIns->pszName, iDescType, iIdx, LangId, cbHint));
+ for (;;)
+ {
+ /*
+ * Setup a MSG URB, queue and reap it.
+ */
+ int rc = VINF_SUCCESS;
+ VUSBURB Urb;
+ AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K);
+ RT_ZERO(Urb);
+ Urb.u32Magic = VUSBURB_MAGIC;
+ Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
+ Urb.pszDesc = (char*)"URB sync";
+ Urb.DstAddress = 0;
+ Urb.EndPt = 0;
+ Urb.enmType = VUSBXFERTYPE_MSG;
+ Urb.enmDir = VUSBDIRECTION_IN;
+ Urb.fShortNotOk = false;
+ Urb.enmStatus = VUSBSTATUS_INVALID;
+ cbHint = RT_MIN(cbHint, sizeof(Urb.abData) - sizeof(VUSBSETUP));
+ Urb.cbData = cbHint + sizeof(VUSBSETUP);
+
+ PVUSBSETUP pSetup = (PVUSBSETUP)Urb.abData;
+ pSetup->bmRequestType = VUSB_DIR_TO_HOST | VUSB_REQ_STANDARD | VUSB_TO_DEVICE;
+ pSetup->bRequest = VUSB_REQ_GET_DESCRIPTOR;
+ pSetup->wValue = (iDescType << 8) | iIdx;
+ pSetup->wIndex = LangId;
+ pSetup->wLength = cbHint;
+
+ uint8_t *pbDesc = (uint8_t *)(pSetup + 1);
+ uint32_t cbDesc = 0;
+ PVUSBURB pUrbReaped = NULL;
+
+ rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, &Urb);
+ if (RT_FAILURE(rc))
+ {
+ Log(("GetStdDescSync: pfnUrbQueue failed, rc=%d\n", rc));
+ goto err;
+ }
+
+ /* Don't wait forever, it's just a simple request that should
+ return immediately. Since we're executing in the EMT thread
+ it's important not to get stuck here. (Some of the builtin
+ iMac devices may refuse to respond for instance.) */
+ pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 5000 /* ms */);
+ if (!pUrbReaped)
+ {
+ Log(("GetStdDescSync: pfnUrbReap returned NULL, cancel and re-reap\n"));
+ rc = pProxyDev->pOps->pfnUrbCancel(pProxyDev, &Urb);
+ AssertRC(rc);
+ /** @todo This breaks the comment above... */
+ pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT);
+ }
+ if (pUrbReaped != &Urb)
+ {
+ Log(("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped));
+ goto err;
+ }
+
+ if (Urb.enmStatus != VUSBSTATUS_OK)
+ {
+ Log(("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus));
+ goto err;
+ }
+
+ /*
+ * Check the length, config descriptors have total_length field
+ */
+ if (iDescType == VUSB_DT_CONFIG)
+ {
+ if (Urb.cbData < sizeof(VUSBSETUP) + 4)
+ {
+ Log(("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData));
+ goto err;
+ }
+ cbDesc = RT_LE2H_U16(((uint16_t *)pbDesc)[1]);
+ }
+ else
+ {
+ if (Urb.cbData < sizeof(VUSBSETUP) + 1)
+ {
+ Log(("GetStdDescSync: Urb.cbData=%#x (min 1)\n", Urb.cbData));
+ goto err;
+ }
+ cbDesc = ((uint8_t *)pbDesc)[0];
+ }
+
+ Log(("GetStdDescSync: got Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint));
+
+ if ( Urb.cbData == cbHint + sizeof(VUSBSETUP)
+ && cbDesc > Urb.cbData - sizeof(VUSBSETUP))
+ {
+ cbHint = cbDesc;
+ Log(("GetStdDescSync: Part descriptor, Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint));
+
+ if (cbHint > sizeof(Urb.abData))
+ {
+ Log(("GetStdDescSync: cbHint=%u, Urb.abData=%u, retrying immediately\n", cbHint, sizeof(Urb.abData)));
+ /* Not an error, go again without incrementing retry count or delaying. */
+ continue;
+ }
+
+ goto err;
+ }
+
+ if (cbDesc > Urb.cbData - sizeof(VUSBSETUP))
+ {
+ Log(("GetStdDescSync: Descriptor length too short, cbDesc=%u, Urb.cbData=%u\n", cbDesc, Urb.cbData));
+ goto err;
+ }
+
+ if ( cbInitialHint != cbHint
+ && ( cbDesc != cbHint
+ || Urb.cbData < cbInitialHint) )
+ {
+ Log(("GetStdDescSync: Descriptor length incorrect, cbDesc=%u, Urb.cbData=%u, cbHint=%u\n", cbDesc, Urb.cbData, cbHint));
+ goto err;
+ }
+
+#ifdef LOG_ENABLED
+ vusbUrbTrace(&Urb, "GetStdDescSync", true);
+#endif
+
+ /*
+ * Fine, we got everything return a heap duplicate of the descriptor.
+ */
+ return RTMemDup(pbDesc, cbDesc);
+
+err:
+ cRetries++;
+ if (cRetries < GET_DESC_RETRIES)
+ {
+ Log(("GetStdDescSync: Retrying %u/%u\n", cRetries, GET_DESC_RETRIES));
+ RTThreadSleep(100);
+ continue;
+ }
+ else
+ {
+ Log(("GetStdDescSync: Retries exceeded %u/%u. Giving up.\n", cRetries, GET_DESC_RETRIES));
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+/**
+ * Frees a descriptor returned by GetStdDescSync().
+ */
+static void free_desc(void *pvDesc)
+{
+ RTMemFree(pvDesc);
+}
+
+/**
+ * Get and a device descriptor and byteswap it appropriately.
+ */
+static bool usbProxyGetDeviceDesc(PUSBPROXYDEV pProxyDev, PVUSBDESCDEVICE pOut)
+{
+ /*
+ * Get the descriptor from the device.
+ */
+ PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN);
+ if (!pIn)
+ {
+ Log(("usbProxyGetDeviceDesc: pProxyDev=%s: GetStdDescSync failed\n", pProxyDev->pUsbIns->pszName));
+ return false;
+ }
+ if (pIn->bLength < VUSB_DT_DEVICE_MIN_LEN)
+ {
+ Log(("usb-proxy: pProxyDev=%s: Corrupted device descriptor. bLength=%d\n", pProxyDev->pUsbIns->pszName, pIn->bLength));
+ return false;
+ }
+
+ /*
+ * Convert it.
+ */
+ pOut->bLength = VUSB_DT_DEVICE_MIN_LEN;
+ pOut->bDescriptorType = VUSB_DT_DEVICE;
+ pOut->bcdUSB = RT_LE2H_U16(pIn->bcdUSB);
+ pOut->bDeviceClass = pIn->bDeviceClass;
+ pOut->bDeviceSubClass = pIn->bDeviceSubClass;
+ pOut->bDeviceProtocol = pIn->bDeviceProtocol;
+ pOut->bMaxPacketSize0 = pIn->bMaxPacketSize0;
+ pOut->idVendor = RT_LE2H_U16(pIn->idVendor);
+ pOut->idProduct = RT_LE2H_U16(pIn->idProduct);
+ pOut->bcdDevice = RT_LE2H_U16(pIn->bcdDevice);
+ pOut->iManufacturer = pIn->iManufacturer;
+ pOut->iProduct = pIn->iProduct;
+ pOut->iSerialNumber = pIn->iSerialNumber;
+ pOut->bNumConfigurations = pIn->bNumConfigurations;
+
+ free_desc(pIn);
+ return true;
+}
+
+/**
+ * Count the numbers and types of each kind of descriptor that we need to
+ * copy out of the config descriptor
+ */
+struct desc_counts
+{
+ size_t num_ed, num_id, num_if;
+ /** bitmap (128 bits) */
+ uint32_t idmap[4];
+};
+
+static int count_descriptors(struct desc_counts *cnt, uint8_t *buf, size_t len)
+{
+ PVUSBDESCCONFIG cfg;
+ uint8_t *tmp, *end;
+ uint32_t i, x;
+
+ memset(cnt, 0, sizeof(*cnt));
+
+ end = buf + len;
+
+ cfg = (PVUSBDESCCONFIG)buf;
+ if ( cfg->bLength < VUSB_DT_CONFIG_MIN_LEN )
+ return 0;
+ if ( cfg->bLength > len )
+ return 0;
+
+ for (tmp = buf + cfg->bLength; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ uint8_t type;
+ uint32_t ifnum;
+ PVUSBDESCINTERFACE id;
+ PVUSBDESCENDPOINT ed;
+
+ type = *(tmp + 1);
+
+ switch ( type ) {
+ case VUSB_DT_INTERFACE:
+ id = (PVUSBDESCINTERFACE)tmp;
+ if ( id->bLength < VUSB_DT_INTERFACE_MIN_LEN )
+ return 0;
+ cnt->num_id++;
+ ifnum = id->bInterfaceNumber;
+ cnt->idmap[ifnum >> 6] |= (1 << (ifnum & 0x1f));
+ break;
+ case VUSB_DT_ENDPOINT:
+ ed = (PVUSBDESCENDPOINT)tmp;
+ if ( ed->bLength < VUSB_DT_ENDPOINT_MIN_LEN )
+ return 0;
+ cnt->num_ed++;
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* count interfaces */
+ for(i=0; i < RT_ELEMENTS(cnt->idmap); i++)
+ for(x=1; x; x<<=1)
+ if ( cnt->idmap[i] & x )
+ cnt->num_if++;
+
+ return 1;
+}
+
+/* Given the pointer to a configuration/interface/endpoint descriptor, find any following
+ * non-standard (vendor or class) descriptors.
+ */
+static const void *collect_stray_bits(uint8_t *this_desc, uint8_t *end, uint16_t *cbExtra)
+{
+ uint8_t *tmp, *buf;
+ uint8_t type;
+
+ Assert(*(this_desc + 1) == VUSB_DT_INTERFACE || *(this_desc + 1) == VUSB_DT_ENDPOINT || *(this_desc + 1) == VUSB_DT_CONFIG);
+ buf = this_desc;
+
+ /* Skip the current configuration/interface/endpoint descriptor. */
+ buf += *(uint8_t *)buf;
+
+ /* Loop until we find another descriptor we understand. */
+ for (tmp = buf; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ type = *(tmp + 1);
+ if (type == VUSB_DT_INTERFACE || type == VUSB_DT_ENDPOINT)
+ break;
+ }
+ *cbExtra = tmp - buf;
+ if (*cbExtra)
+ return buf;
+ else
+ return NULL;
+}
+
+/* Setup a vusb_interface structure given some preallocated structures
+ * to use, (we counted them already)
+ */
+static int copy_interface(PVUSBINTERFACE pIf, uint8_t ifnum,
+ PVUSBDESCINTERFACEEX *id, PVUSBDESCENDPOINTEX *ed,
+ uint8_t *buf, size_t len)
+{
+ PVUSBDESCINTERFACEEX cur_if = NULL;
+ uint32_t altmap[4] = {0,};
+ uint8_t *tmp, *end = buf + len;
+ uint8_t alt;
+ int state;
+ size_t num_ep = 0;
+
+ buf += *(uint8_t *)buf;
+
+ pIf->cSettings = 0;
+ pIf->paSettings = NULL;
+
+ for (tmp = buf, state = 0; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ uint8_t type;
+ PVUSBDESCINTERFACE ifd;
+ PVUSBDESCENDPOINT epd;
+ PVUSBDESCENDPOINTEX cur_ep;
+
+ type = tmp[1];
+
+ switch ( type ) {
+ case VUSB_DT_INTERFACE:
+ state = 0;
+ ifd = (PVUSBDESCINTERFACE)tmp;
+
+ /* Ignoring this interface */
+ if ( ifd->bInterfaceNumber != ifnum )
+ break;
+
+ /* Check we didn't see this alternate setting already
+ * because that will break stuff
+ */
+ alt = ifd->bAlternateSetting;
+ if ( altmap[alt >> 6] & (1 << (alt & 0x1f)) )
+ return 0;
+ altmap[alt >> 6] |= (1 << (alt & 0x1f));
+
+ cur_if = *id;
+ (*id)++;
+ if ( pIf->cSettings == 0 )
+ pIf->paSettings = cur_if;
+
+ memcpy(cur_if, ifd, sizeof(cur_if->Core));
+
+ /* Point to additional interface descriptor bytes, if any. */
+ AssertCompile(sizeof(cur_if->Core) == VUSB_DT_INTERFACE_MIN_LEN);
+ if (cur_if->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN > 0)
+ cur_if->pvMore = tmp + VUSB_DT_INTERFACE_MIN_LEN;
+ else
+ cur_if->pvMore = NULL;
+
+ cur_if->pvClass = collect_stray_bits(tmp, end, &cur_if->cbClass);
+
+ pIf->cSettings++;
+
+ state = 1;
+ num_ep = 0;
+ break;
+ case VUSB_DT_ENDPOINT:
+ if ( state == 0 )
+ break;
+
+ epd = (PVUSBDESCENDPOINT)tmp;
+
+ cur_ep = *ed;
+ (*ed)++;
+
+ if ( num_ep == 0 )
+ cur_if->paEndpoints = cur_ep;
+
+ if ( num_ep > cur_if->Core.bNumEndpoints )
+ return 0;
+
+ memcpy(cur_ep, epd, sizeof(cur_ep->Core));
+
+ /* Point to additional endpoint descriptor bytes, if any. */
+ AssertCompile(sizeof(cur_ep->Core) == VUSB_DT_ENDPOINT_MIN_LEN);
+ if (cur_ep->Core.bLength - VUSB_DT_ENDPOINT_MIN_LEN > 0)
+ cur_ep->pvMore = tmp + VUSB_DT_ENDPOINT_MIN_LEN;
+ else
+ cur_ep->pvMore = NULL;
+
+ cur_ep->pvClass = collect_stray_bits(tmp, end, &cur_ep->cbClass);
+
+ cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize);
+
+ num_ep++;
+ break;
+ default:
+ /* Skip unknown descriptors. */
+ break;
+ }
+ }
+
+ return 1;
+}
+
+/**
+ * Copy all of a devices config descriptors, this is needed so that the USB
+ * core layer knows all about how to map the different functions on to the
+ * virtual USB bus.
+ */
+static bool copy_config(PUSBPROXYDEV pProxyDev, uint8_t idx, PVUSBDESCCONFIGEX out)
+{
+ PVUSBDESCCONFIG cfg;
+ PVUSBINTERFACE pIf;
+ PVUSBDESCINTERFACEEX ifd;
+ PVUSBDESCENDPOINTEX epd;
+ struct desc_counts cnt;
+ void *descs;
+ size_t tot_len;
+ size_t cbIface;
+ uint32_t i, x;
+ uint8_t *tmp, *end;
+
+ descs = GetStdDescSync(pProxyDev, VUSB_DT_CONFIG, idx, 0, VUSB_DT_CONFIG_MIN_LEN);
+ if ( descs == NULL ) {
+ Log(("copy_config: GetStdDescSync failed\n"));
+ return false;
+ }
+
+ cfg = (PVUSBDESCCONFIG)descs;
+ tot_len = RT_LE2H_U16(cfg->wTotalLength);
+
+ if ( !count_descriptors(&cnt, (uint8_t *)descs, tot_len) ) {
+ Log(("copy_config: count_descriptors failed\n"));
+ goto err;
+ }
+
+ if ( cfg->bNumInterfaces != cnt.num_if )
+ Log(("usb-proxy: config%u: bNumInterfaces %u != %u\n",
+ idx, cfg->bNumInterfaces, cnt.num_if));
+
+ Log(("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n",
+ idx, tot_len, cnt.num_id, cnt.num_ed, cnt.num_if));
+
+ cbIface = cnt.num_if * sizeof(VUSBINTERFACE)
+ + cnt.num_id * sizeof(VUSBDESCINTERFACEEX)
+ + cnt.num_ed * sizeof(VUSBDESCENDPOINTEX);
+ out->paIfs = (PCVUSBINTERFACE)RTMemAllocZ(cbIface);
+ if ( out->paIfs == NULL ) {
+ free_desc(descs);
+ return false;
+ }
+
+ /* Stash a pointer to the raw config descriptor; we may need bits of it later. */
+ out->pvOriginal = descs;
+
+ pIf = (PVUSBINTERFACE)out->paIfs;
+ ifd = (PVUSBDESCINTERFACEEX)&pIf[cnt.num_if];
+ epd = (PVUSBDESCENDPOINTEX)&ifd[cnt.num_id];
+
+ out->Core.bLength = cfg->bLength;
+ out->Core.bDescriptorType = cfg->bDescriptorType;
+ out->Core.wTotalLength = 0; /* Auto Calculated */
+ out->Core.bNumInterfaces = (uint8_t)cnt.num_if;
+ out->Core.bConfigurationValue = cfg->bConfigurationValue;
+ out->Core.iConfiguration = cfg->iConfiguration;
+ out->Core.bmAttributes = cfg->bmAttributes;
+ out->Core.MaxPower = cfg->MaxPower;
+
+ tmp = (uint8_t *)out->pvOriginal;
+ end = tmp + tot_len;
+
+ /* Point to additional configuration descriptor bytes, if any. */
+ AssertCompile(sizeof(out->Core) == VUSB_DT_CONFIG_MIN_LEN);
+ if (out->Core.bLength - VUSB_DT_CONFIG_MIN_LEN > 0)
+ out->pvMore = tmp + VUSB_DT_CONFIG_MIN_LEN;
+ else
+ out->pvMore = NULL;
+
+ /* Typically there might be an interface association descriptor here. */
+ out->pvClass = collect_stray_bits(tmp, end, &out->cbClass);
+
+ for(i=0; i < 4; i++)
+ for(x=0; x < 32; x++)
+ if ( cnt.idmap[i] & (1 << x) )
+ if ( !copy_interface(pIf++, (i << 6) | x, &ifd, &epd, (uint8_t *)out->pvOriginal, tot_len) ) {
+ Log(("copy_interface(%d,,) failed\n", pIf - 1));
+ goto err;
+ }
+
+ return true;
+err:
+ Log(("usb-proxy: config%u: Corrupted configuration descriptor\n", idx));
+ free_desc(descs);
+ return false;
+}
+
+
+/**
+ * Edit out masked interface descriptors.
+ *
+ * @param pProxyDev The proxy device
+ */
+static void usbProxyDevEditOutMaskedIfs(PUSBPROXYDEV pProxyDev)
+{
+ unsigned cRemoved = 0;
+
+ PVUSBDESCCONFIGEX paCfgs = pProxyDev->paCfgDescs;
+ for (unsigned iCfg = 0; iCfg < pProxyDev->DevDesc.bNumConfigurations; iCfg++)
+ {
+ PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs;
+ for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++)
+ for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++)
+ if ( paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber < 32
+ && ((1 << paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber) & pProxyDev->fMaskedIfs))
+ {
+ Log(("usb-proxy: removing interface #%d (iIf=%d iAlt=%d) on config #%d (iCfg=%d)\n",
+ paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber, iIf, iAlt, paCfgs[iCfg].Core.bConfigurationValue, iCfg));
+ cRemoved++;
+
+ paCfgs[iCfg].Core.bNumInterfaces--;
+ unsigned cToCopy = paCfgs[iCfg].Core.bNumInterfaces - iIf;
+ if (cToCopy)
+ memmove(&paIfs[iIf], &paIfs[iIf + 1], sizeof(paIfs[0]) * cToCopy);
+ memset(&paIfs[iIf + cToCopy], '\0', sizeof(paIfs[0]));
+ break;
+ }
+ }
+
+ Log(("usb-proxy: edited out %d interface(s).\n", cRemoved));
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbReset}
+ *
+ * USB Device Proxy: Call OS specific code to reset the device.
+ */
+static DECLCALLBACK(int) usbProxyDevReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+
+ if (pProxyDev->fMaskedIfs)
+ {
+ Log(("usbProxyDevReset: pProxyDev=%s - ignoring reset request fMaskedIfs=%#x\n", pUsbIns->pszName, pProxyDev->fMaskedIfs));
+ return VINF_SUCCESS;
+ }
+ LogFlow(("usbProxyDevReset: pProxyDev=%s\n", pUsbIns->pszName));
+ return pProxyDev->pOps->pfnReset(pProxyDev, fResetOnLinux);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbProxyDevGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ return &pThis->DescCache;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration}
+ *
+ * USB Device Proxy: Release claimed interfaces, tell the OS+device about the config change, claim the new interfaces.
+ */
+static DECLCALLBACK(int) usbProxyDevSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
+ const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ LogFlow(("usbProxyDevSetConfiguration: pProxyDev=%s iActiveCfg=%d bConfigurationValue=%d\n",
+ pUsbIns->pszName, pProxyDev->iActiveCfg, bConfigurationValue));
+
+ /*
+ * Release the current config.
+ */
+ if (pvOldCfgDesc)
+ {
+ PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)pvOldCfgDesc;
+ PCVUSBINTERFACESTATE pOldIfState = (PCVUSBINTERFACESTATE)pvOldIfState;
+ for (unsigned i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++)
+ if (pOldIfState[i].pCurIfDesc)
+ pProxyDev->pOps->pfnReleaseInterface(pProxyDev, pOldIfState[i].pCurIfDesc->Core.bInterfaceNumber);
+ }
+
+ /*
+ * Do the actual SET_CONFIGURE.
+ * The mess here is because most backends will already have selected a
+ * configuration and there are a bunch of devices which will freak out
+ * if we do SET_CONFIGURE twice with the same value. (PalmOne, TrekStor USB-StickGO, ..)
+ *
+ * After open and reset the backend should use the members iActiveCfg and cIgnoreSetConfigs
+ * to indicate the new configuration state and what to do on the next SET_CONFIGURATION call.
+ */
+ if ( pProxyDev->iActiveCfg != bConfigurationValue
+ || ( bConfigurationValue == 0
+ && pProxyDev->iActiveCfg != -1 /* this test doesn't make sense, we know it's 0 */
+ && pProxyDev->cIgnoreSetConfigs >= 2)
+ || !pProxyDev->cIgnoreSetConfigs)
+ {
+ pProxyDev->cIgnoreSetConfigs = 0;
+ int rc = pProxyDev->pOps->pfnSetConfig(pProxyDev, bConfigurationValue);
+ if (RT_FAILURE(rc))
+ {
+ pProxyDev->iActiveCfg = -1;
+ return rc;
+ }
+ pProxyDev->iActiveCfg = bConfigurationValue;
+ }
+ else if (pProxyDev->cIgnoreSetConfigs > 0)
+ pProxyDev->cIgnoreSetConfigs--;
+
+ /*
+ * Claim the interfaces.
+ */
+ PCVUSBDESCCONFIGEX pNewCfgDesc = (PCVUSBDESCCONFIGEX)pvNewCfgDesc;
+ Assert(pNewCfgDesc->Core.bConfigurationValue == bConfigurationValue);
+ for (unsigned iIf = 0; iIf < pNewCfgDesc->Core.bNumInterfaces; iIf++)
+ {
+ PCVUSBINTERFACE pIf = &pNewCfgDesc->paIfs[iIf];
+ for (uint32_t iAlt = 0; iAlt < pIf->cSettings; iAlt++)
+ {
+ if (pIf->paSettings[iAlt].Core.bAlternateSetting != 0)
+ continue;
+ pProxyDev->pOps->pfnClaimInterface(pProxyDev, pIf->paSettings[iAlt].Core.bInterfaceNumber);
+ /* ignore failures - the backend deals with that and does the necessary logging. */
+ break;
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface}
+ *
+ * USB Device Proxy: Call OS specific code to select alternate interface settings.
+ */
+static DECLCALLBACK(int) usbProxyDevSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ LogFlow(("usbProxyDevSetInterface: pProxyDev=%s bInterfaceNumber=%d bAlternateSetting=%d\n",
+ pUsbIns->pszName, bInterfaceNumber, bAlternateSetting));
+
+ return pProxyDev->pOps->pfnSetInterface(pProxyDev, bInterfaceNumber, bAlternateSetting);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint}
+ *
+ * USB Device Proxy: Call OS specific code to clear the endpoint.
+ */
+static DECLCALLBACK(int) usbProxyDevClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ LogFlow(("usbProxyDevClearHaltedEndpoint: pProxyDev=%s uEndpoint=%u\n",
+ pUsbIns->pszName, uEndpoint));
+
+ return pProxyDev->pOps->pfnClearHaltedEndpoint(pProxyDev, uEndpoint);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevUrbQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ int rc = VINF_SUCCESS;
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, pUrb);
+ if (RT_FAILURE(rc))
+ return pProxyDev->fDetached
+ ? VERR_VUSB_DEVICE_NOT_ATTACHED
+ : VERR_VUSB_FAILED_TO_QUEUE_URB;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ return pProxyDev->pOps->pfnUrbCancel(pProxyDev, pUrb);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbReap}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyDevUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ PVUSBURB pUrb = pProxyDev->pOps->pfnUrbReap(pProxyDev, cMillies);
+ if ( pUrb
+ && pUrb->enmState == VUSBURBSTATE_CANCELLED
+ && pUrb->enmStatus == VUSBSTATUS_OK)
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ return pUrb;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+
+ return pProxyDev->pOps->pfnWakeup(pProxyDev);
+}
+
+
+/** @interface_method_impl{PDMUSBREG,pfnDestruct} */
+static DECLCALLBACK(void) usbProxyDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns);
+ PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ Log(("usbProxyDestruct: destroying pProxyDev=%s\n", pUsbIns->pszName));
+
+ /* close it. */
+ if (pThis->fOpened)
+ {
+ pThis->pOps->pfnClose(pThis);
+ pThis->fOpened = false;
+ }
+
+ /* free the config descriptors. */
+ if (pThis->paCfgDescs)
+ {
+ for (unsigned i = 0; i < pThis->DevDesc.bNumConfigurations; i++)
+ {
+ RTMemFree((void *)pThis->paCfgDescs[i].paIfs);
+ RTMemFree((void *)pThis->paCfgDescs[i].pvOriginal);
+ }
+ RTMemFree(pThis->paCfgDescs);
+ pThis->paCfgDescs = NULL;
+ }
+
+ /* free dev */
+ if (&g_szDummyName[0] != pUsbIns->pszName)
+ RTStrFree(pUsbIns->pszName);
+ pUsbIns->pszName = NULL;
+
+ if (pThis->pvInstanceDataR3)
+ RTMemFree(pThis->pvInstanceDataR3);
+}
+
+
+/**
+ * Helper function used by usbProxyConstruct when
+ * reading a filter from CFG.
+ *
+ * @returns VBox status code.
+ * @param pFilter The filter.
+ * @param enmFieldIdx The filter field indext.
+ * @param pHlp The USB helper callback table.
+ * @param pNode The CFGM node.
+ * @param pszExact The exact value name.
+ * @param pszExpr The expression value name.
+ */
+static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx,
+ PCPDMUSBHLP pHlp, PCFGMNODE pNode,
+ const char *pszExact, const char *pszExpr)
+{
+ char szTmp[256];
+
+ /* try exact first */
+ uint16_t u16;
+ int rc = pHlp->pfnCFGMQueryU16(pNode, pszExact, &u16);
+ if (RT_SUCCESS(rc))
+ {
+ rc = USBFilterSetNumExact(pFilter, enmFieldIdx, u16, true);
+ AssertRCReturn(rc, rc);
+
+ /* make sure only the exact attribute is present. */
+ rc = pHlp->pfnCFGMQueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
+ if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
+ {
+ szTmp[0] = '\0';
+ pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: Both %s and %s are present!\n", szTmp, pszExact, pszExpr));
+ return VERR_INVALID_PARAMETER;
+ }
+ return VINF_SUCCESS;
+ }
+ if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
+ {
+ szTmp[0] = '\0';
+ pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc));
+ return rc;
+ }
+
+ /* expression? */
+ rc = pHlp->pfnCFGMQueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
+ if (RT_SUCCESS(rc))
+ {
+ rc = USBFilterSetNumExpression(pFilter, enmFieldIdx, szTmp, true);
+ AssertRCReturn(rc, rc);
+ return VINF_SUCCESS;
+ }
+ if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
+ {
+ szTmp[0] = '\0';
+ pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExpr, rc));
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMUSBREG,pfnConstruct} */
+static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ RT_NOREF(iInstance);
+ PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ LogFlow(("usbProxyConstruct: pUsbIns=%p iInstance=%d\n", pUsbIns, iInstance));
+
+ /*
+ * Initialize the instance data.
+ */
+ pThis->pUsbIns = pUsbIns;
+ pThis->pUsbIns->pszName = g_szDummyName;
+ pThis->iActiveCfg = -1;
+ pThis->fMaskedIfs = 0;
+ pThis->fOpened = false;
+ pThis->fInited = false;
+
+ /*
+ * Read the basic configuration.
+ */
+ char szAddress[1024];
+ int rc = pHlp->pfnCFGMQueryString(pCfg, "Address", szAddress, sizeof(szAddress));
+ AssertRCReturn(rc, rc);
+
+ char szBackend[64];
+ rc = pHlp->pfnCFGMQueryString(pCfg, "Backend", szBackend, sizeof(szBackend));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Select backend and open the device.
+ */
+ rc = VERR_NOT_FOUND;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aUsbProxies); i++)
+ {
+ if (!RTStrICmp(szBackend, g_aUsbProxies[i]->pszName))
+ {
+ pThis->pOps = g_aUsbProxies[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ return PDMUSB_SET_ERROR(pUsbIns, rc, N_("USBProxy: Failed to find backend"));
+
+ pThis->pvInstanceDataR3 = RTMemAllocZ(pThis->pOps->cbBackend);
+ if (!pThis->pvInstanceDataR3)
+ return PDMUSB_SET_ERROR(pUsbIns, VERR_NO_MEMORY, N_("USBProxy: can't allocate memory for host backend"));
+
+ rc = pThis->pOps->pfnOpen(pThis, szAddress);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("usbProxyConstruct: Failed to open '%s', rc=%Rrc\n", szAddress, rc));
+ return rc;
+ }
+ pThis->fOpened = true;
+
+ /*
+ * Get the device descriptor and format the device name (for logging).
+ */
+ if (!usbProxyGetDeviceDesc(pThis, &pThis->DevDesc))
+ {
+ Log(("usbProxyConstruct: usbProxyGetDeviceDesc failed\n"));
+ return VERR_READ_ERROR;
+ }
+
+ RTStrAPrintf(&pUsbIns->pszName, "%p[proxy %04x:%04x]", pThis, pThis->DevDesc.idVendor, pThis->DevDesc.idProduct); /** @todo append the user comment */
+ AssertReturn(pUsbIns->pszName, VERR_NO_MEMORY);
+
+ /*
+ * Get config descriptors.
+ */
+ size_t cbConfigs = pThis->DevDesc.bNumConfigurations * sizeof(pThis->paCfgDescs[0]);
+ pThis->paCfgDescs = (PVUSBDESCCONFIGEX)RTMemAllocZ(cbConfigs);
+ AssertReturn(pThis->paCfgDescs, VERR_NO_MEMORY);
+
+ unsigned i;
+ for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++)
+ if (!copy_config(pThis, i, (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i]))
+ break;
+ if (i < pThis->DevDesc.bNumConfigurations)
+ {
+ Log(("usbProxyConstruct: copy_config failed, i=%d\n", i));
+ return VERR_READ_ERROR;
+ }
+
+ /*
+ * Pickup best matching global configuration for this device.
+ * The global configuration is organized like this:
+ *
+ * GlobalConfig/Whatever/
+ * |- idVendor = 300
+ * |- idProduct = 300
+ * - Config/
+ *
+ * The first level contains filter attributes which we stuff into a USBFILTER
+ * structure and match against the device info that's available. The highest
+ * ranked match is will be used. If nothing is found, the values will be
+ * queried from the GlobalConfig node (simplifies code and might actually
+ * be useful).
+ */
+ PCFGMNODE pCfgGlobalDev = pCfgGlobal;
+ PCFGMNODE pCur = pHlp->pfnCFGMGetFirstChild(pCfgGlobal);
+ if (pCur)
+ {
+ /*
+ * Create a device filter from the device configuration
+ * descriptor ++. No strings currently.
+ */
+ USBFILTER Device;
+ USBFilterInit(&Device, USBFILTERTYPE_CAPTURE);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_VENDOR_ID, pThis->DevDesc.idVendor, true); AssertRC(rc);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_PRODUCT_ID, pThis->DevDesc.idProduct, true); AssertRC(rc);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_REV, pThis->DevDesc.bcdDevice, true); AssertRC(rc);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_CLASS, pThis->DevDesc.bDeviceClass, true); AssertRC(rc);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_SUB_CLASS, pThis->DevDesc.bDeviceSubClass, true); AssertRC(rc);
+ rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_PROTOCOL, pThis->DevDesc.bDeviceProtocol, true); AssertRC(rc);
+ /** @todo manufacturer, product and serial strings */
+
+ int iBestMatchRate = -1;
+ PCFGMNODE pBestMatch = NULL;
+ for (pCur = pHlp->pfnCFGMGetFirstChild(pCfgGlobal); pCur; pCur = pHlp->pfnCFGMGetNextChild(pCur))
+ {
+ /*
+ * Construct a filter from the attributes in the node.
+ */
+ USBFILTER Filter;
+ USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE);
+
+ /* numeric */
+ if ( RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_VENDOR_ID, pHlp, pCur, "idVendor", "idVendorExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pHlp, pCur, "idProduct", "idProcutExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pHlp, pCur, "bcdDevice", "bcdDeviceExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pHlp, pCur, "bDeviceClass", "bDeviceClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pHlp, pCur, "bDeviceSubClass", "bDeviceSubClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pHlp, pCur, "bDeviceProtocol", "bDeviceProtocolExpr")))
+ continue; /* skip it */
+
+ /* strings */
+ /** @todo manufacturer, product and serial strings */
+
+ /* ignore unknown config values, but not without bitching. */
+ if (!pHlp->pfnCFGMAreValuesValid(pCur,
+ "idVendor\0idVendorExpr\0"
+ "idProduct\0idProductExpr\0"
+ "bcdDevice\0bcdDeviceExpr\0"
+ "bDeviceClass\0bDeviceClassExpr\0"
+ "bDeviceSubClass\0bDeviceSubClassExpr\0"
+ "bDeviceProtocol\0bDeviceProtocolExpr\0"))
+ LogRel(("usbProxyConstruct: Unknown value(s) in config filter (ignored)!\n"));
+
+ /*
+ * Try match it and on match see if it has is a higher rate hit
+ * than the previous match. Quit if its a 100% match.
+ */
+ int iRate = USBFilterMatchRated(&Filter, &Device);
+ if (iRate > iBestMatchRate)
+ {
+ pBestMatch = pCur;
+ iBestMatchRate = iRate;
+ if (iRate >= 100)
+ break;
+ }
+ }
+ if (pBestMatch)
+ pCfgGlobalDev = pHlp->pfnCFGMGetChild(pBestMatch, "Config");
+ if (pCfgGlobalDev)
+ pCfgGlobalDev = pCfgGlobal;
+ }
+
+ /*
+ * Query the rest of the configuration using the global as fallback.
+ */
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fMaskedIfs = 0;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11Device;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11Device = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11PacketSize;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11PacketSize = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fEditAudioSyncEp;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fEditAudioSyncEp = true; /* NB: On by default! */
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fEditRemoteWake;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "EditRemoteWake", &fEditRemoteWake);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditRemoteWake", &fEditRemoteWake);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fEditRemoteWake = true; /* NB: On by default! */
+ else
+ AssertRCReturn(rc, rc);
+
+ /*
+ * If we're masking interfaces, edit the descriptors.
+ */
+ bool fEdited = pThis->fMaskedIfs != 0;
+ if (pThis->fMaskedIfs)
+ usbProxyDevEditOutMaskedIfs(pThis);
+
+ /*
+ * Do 2.0 -> 1.1 device edits if requested to do so.
+ */
+ if ( fForce11PacketSize
+ && pThis->DevDesc.bcdUSB >= 0x0200)
+ {
+ PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs;
+ for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++)
+ {
+ PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs;
+ for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++)
+ for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++)
+ {
+ /*
+ * USB 1.1 defines the max for control, interrupt and bulk to be 64 bytes.
+ * While isochronous has a max of 1023 bytes.
+ */
+ PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints;
+ if (!paEps)
+ continue;
+
+ for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++)
+ {
+ const uint16_t cbMax = (paEps[iEp].Core.bmAttributes & 3) == 1 /* isoc */
+ ? 1023
+ : 64;
+ if (paEps[iEp].Core.wMaxPacketSize > cbMax)
+ {
+ Log(("usb-proxy: pProxyDev=%s correcting wMaxPacketSize from %#x to %#x (mainly for vista)\n",
+ pUsbIns->pszName, paEps[iEp].Core.wMaxPacketSize, cbMax));
+ paEps[iEp].Core.wMaxPacketSize = cbMax;
+ fEdited = true;
+ }
+ }
+ }
+ }
+ }
+
+ if ( fForce11Device
+ && pThis->DevDesc.bcdUSB == 0x0200)
+ {
+ /*
+ * Discourages windows from helping you find a 2.0 port.
+ */
+ Log(("usb-proxy: %s correcting USB version 2.0 to 1.1 (to avoid Windows warning)\n", pUsbIns->pszName));
+ pThis->DevDesc.bcdUSB = 0x110;
+ fEdited = true;
+ }
+
+
+ /*
+ * Turn asynchronous audio endpoints into synchronous ones, see @bugref{8769}
+ */
+ if (fEditAudioSyncEp)
+ {
+ PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs;
+ for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++)
+ {
+ PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs;
+ for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++)
+ for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++)
+ {
+ /* If not an audio class interface, skip. */
+ if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceClass != 1)
+ continue;
+
+ /* If not a streaming interface, skip. */
+ if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceSubClass != 2)
+ continue;
+
+ PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints;
+ if (!paEps)
+ continue;
+
+ for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++)
+ {
+ /* isoch/asynch/data*/
+ if ((paEps[iEp].Core.bmAttributes == 5) && (paEps[iEp].Core.bLength == 9))
+ {
+ uint8_t *pbExtra = (uint8_t *)paEps[iEp].pvMore; /* unconst*/
+ if (pbExtra[1] == 0)
+ continue; /* If bSynchAddress is zero, leave the descriptor alone. */
+
+ Log(("usb-proxy: pProxyDev=%s async audio with bmAttr=%02X [%02X, %02X] on EP %02X\n",
+ pUsbIns->pszName, paEps[iEp].Core.bmAttributes, pbExtra[0], pbExtra[1], paEps[iEp].Core.bEndpointAddress));
+ paEps[iEp].Core.bmAttributes = 0xD; /* isoch/synch/data*/
+ pbExtra[1] = 0; /* Clear bSynchAddress. */
+ fEdited = true;
+ LogRel(("VUSB: Modified '%s' async audio endpoint 0x%02x\n", pUsbIns->pszName, paEps[iEp].Core.bEndpointAddress));
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Disable remote wakeup capability, see @bugref{9839}. This is done on
+ * a device/configuration level, no need to dig too deep through the descriptors.
+ * On most backends, we can't perform a real selective suspend, and more importantly
+ * can't receive a remote wake notification. If a guest suspends the device and waits
+ * for a remote wake, the device is effectively dead.
+ */
+ if (fEditRemoteWake)
+ {
+ PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs;
+ for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++)
+ {
+ Log(("usb-proxy: pProxyDev=%s configuration %d with bmAttr=%02X\n",
+ pUsbIns->pszName, paCfgs[iCfg].Core.bmAttributes, iCfg));
+ if (paCfgs[iCfg].Core.bmAttributes & RT_BIT(5))
+ {
+ paCfgs[iCfg].Core.bmAttributes = paCfgs[iCfg].Core.bmAttributes & ~RT_BIT(5); /* Remote wakeup. */
+ fEdited = true;
+ LogRel(("VUSB: Disabled '%s' remote wakeup for configuration %d\n", pUsbIns->pszName, iCfg));
+ }
+ }
+ }
+
+ /*
+ * Init the PDM/VUSB descriptor cache.
+ */
+ pThis->DescCache.pDevice = &pThis->DevDesc;
+ pThis->DescCache.paConfigs = pThis->paCfgDescs;
+ pThis->DescCache.paLanguages = NULL;
+ pThis->DescCache.cLanguages = 0;
+ pThis->DescCache.fUseCachedDescriptors = fEdited;
+ pThis->DescCache.fUseCachedStringsDescriptors = false;
+
+ /*
+ * Call the backend if it wishes to do some more initializing
+ * after we've read the config and descriptors.
+ */
+ if (pThis->pOps->pfnInit)
+ {
+ rc = pThis->pOps->pfnInit(pThis);
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+ pThis->fInited = true;
+
+ /*
+ * We're good!
+ */
+ Log(("usb-proxy: created pProxyDev=%s address '%s' fMaskedIfs=%#x (rc=%Rrc)\n",
+ pUsbIns->pszName, szAddress, pThis->fMaskedIfs, rc));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The USB proxy device registration record.
+ */
+const PDMUSBREG g_UsbDevProxy =
+{
+ /* u32Version */
+ PDM_USBREG_VERSION,
+ /* szName */
+ "USBProxy",
+ /* pszDescription */
+ "USB Proxy Device.",
+ /* fFlags */
+ 0,
+ /* cMaxInstances */
+ ~0U,
+ /* cbInstance */
+ sizeof(USBPROXYDEV),
+ /* pfnConstruct */
+ usbProxyConstruct,
+ /* pfnDestruct */
+ usbProxyDestruct,
+ /* pfnVMInitComplete */
+ NULL,
+ /* pfnVMPowerOn */
+ NULL,
+ /* pfnVMReset */
+ NULL,
+ /* pfnVMSuspend */
+ NULL,
+ /* pfnVMResume */
+ NULL,
+ /* pfnVMPowerOff */
+ NULL,
+ /* pfnHotPlugged */
+ NULL,
+ /* pfnHotUnplugged */
+ NULL,
+ /* pfnDriverAttach */
+ NULL,
+ /* pfnDriverDetach */
+ NULL,
+ /* pfnQueryInterface */
+ NULL,
+ /* pfnUsbReset */
+ usbProxyDevReset,
+ /* pfnUsbGetDescriptorCache */
+ usbProxyDevGetDescriptorCache,
+ /* pfnUsbSetConfiguration */
+ usbProxyDevSetConfiguration,
+ /* pfnUsbSetInterface */
+ usbProxyDevSetInterface,
+ /* pfnUsbClearHaltedEndpoint */
+ usbProxyDevClearHaltedEndpoint,
+ /* pfnUrbNew */
+ NULL,
+ /* pfnUrbQueue */
+ usbProxyDevUrbQueue,
+ /* pfnUrbCancel */
+ usbProxyDevUrbCancel,
+ /* pfnUrbReap */
+ usbProxyDevUrbReap,
+ /* pfnWakeup */
+ usbProxyDevWakeup,
+
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
+
diff --git a/src/VBox/Devices/USB/USBProxyDevice.h b/src/VBox/Devices/USB/USBProxyDevice.h
new file mode 100644
index 00000000..79687bff
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice.h
@@ -0,0 +1,285 @@
+/* $Id: USBProxyDevice.h $ */
+/** @file
+ * USBPROXY - USB proxy header
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_USB_USBProxyDevice_h
+#define VBOX_INCLUDED_SRC_USB_USBProxyDevice_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <VBox/vusb.h>
+
+RT_C_DECLS_BEGIN
+
+
+/** Pointer to a USB proxy device. */
+typedef struct USBPROXYDEV *PUSBPROXYDEV;
+
+/**
+ * USB Proxy Device Backend
+ */
+typedef struct USBPROXYBACK
+{
+ /** Name of the backend. */
+ const char *pszName;
+ /** Size of the backend specific data. */
+ size_t cbBackend;
+
+ /**
+ * Opens the USB device specfied by pszAddress.
+ *
+ * This method will initialize backend private data. If the backend has
+ * already selected a configuration for the device, this must be indicated
+ * in USBPROXYDEV::iActiveCfg.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param pszAddress Host specific USB device address.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnOpen, (PUSBPROXYDEV pProxyDev, const char *pszAddress));
+
+ /**
+ * Optional callback for initializing the device after the configuration
+ * has been established.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnInit, (PUSBPROXYDEV pProxyDev));
+
+ /**
+ * Closes handle to the host USB device.
+ *
+ * @param pProxyDev The USB Proxy Device instance.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnClose, (PUSBPROXYDEV pProxyDev));
+
+ /**
+ * Reset a device.
+ *
+ * The backend must update iActualCfg and fIgnoreEqualSetConfig.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param fResetOnLinux It's safe to do reset on linux, we can deal with devices
+ * being logically reconnected.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnReset, (PUSBPROXYDEV pProxyDev, bool fResetOnLinux));
+
+ /**
+ * Sets the given configuration of the device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param iCfg The configuration to set.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetConfig, (PUSBPROXYDEV pProxyDev, int iCfg));
+
+ /**
+ * Claim an interface for use by the prox device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param iIf Interface number to claim.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnClaimInterface, (PUSBPROXYDEV pProxyDev, int iIf));
+
+ /**
+ * Releases an interface which was claimed before.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param iIf Interface number to release.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnReleaseInterface, (PUSBPROXYDEV pProxyDev, int iIf));
+
+ /**
+ * Sets the given alternate interface for the device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param iIf Interface to use.
+ * @param iSetting The alternate setting to use.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnSetInterface, (PUSBPROXYDEV pProxyDev, int iIf, int iSetting));
+
+ /**
+ * Clears the given halted endpoint.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param iEp The endpoint to clear.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnClearHaltedEndpoint, (PUSBPROXYDEV pDev, unsigned int iEp));
+
+ /**
+ * Queue a new URB.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param pUrb The URB to queue.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnUrbQueue, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb));
+
+ /**
+ * Cancel an in-flight URB.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param pUrb The URB to cancel.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnUrbCancel, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb));
+
+ /**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The USB Proxy Device instance.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not
+ * wait at all.
+ */
+ DECLR3CALLBACKMEMBER(PVUSBURB, pfnUrbReap, (PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies));
+
+ /**
+ * Kicks the thread waiting in pfnUrbReap to make it return.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The USB Proxy Device instance.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnWakeup, (PUSBPROXYDEV pProxyDev));
+
+ /** Dummy entry for making sure we've got all members initialized. */
+ uint32_t uDummy;
+} USBPROXYBACK;
+/** Pointer to a USB Proxy Device Backend. */
+typedef USBPROXYBACK *PUSBPROXYBACK;
+/** Pointer to a const USB Proxy Device Backend. */
+typedef const USBPROXYBACK *PCUSBPROXYBACK;
+
+/** The Host backend. */
+extern const USBPROXYBACK g_USBProxyDeviceHost;
+/** The remote desktop backend. */
+extern const USBPROXYBACK g_USBProxyDeviceVRDP;
+/** The USB/IP backend. */
+extern const USBPROXYBACK g_USBProxyDeviceUsbIp;
+
+#ifdef RDESKTOP
+typedef struct VUSBDEV
+{
+ char* pszName;
+} VUSBDEV, *PVUSBDEV;
+#endif
+
+/**
+ * USB Proxy device.
+ */
+typedef struct USBPROXYDEV
+{
+ /** The device descriptor. */
+ VUSBDESCDEVICE DevDesc;
+ /** The configuration descriptor array. */
+ PVUSBDESCCONFIGEX paCfgDescs;
+#ifndef RDESKTOP
+ /** The descriptor cache.
+ * Contains &DevDesc and paConfigDescs. */
+ PDMUSBDESCCACHE DescCache;
+ /** Pointer to the PDM USB device instance. */
+ PPDMUSBINS pUsbIns;
+#endif
+
+ /** Pointer to the backend. */
+ PCUSBPROXYBACK pOps;
+ /** The currently active configuration.
+ * It's -1 if no configuration is active. This is set to -1 before open and reset,
+ * the backend will change it if open or reset implies SET_CONFIGURATION. */
+ int iActiveCfg;
+ /** Ignore one or two SET_CONFIGURATION operation.
+ * See usbProxyDevSetCfg for details. */
+ int cIgnoreSetConfigs;
+ /** Mask of the interfaces that the guest shall not see. */
+ uint32_t fMaskedIfs;
+ /** Whether we've opened the device or not.
+ * For dealing with failed construction (the destruct method is always called). */
+ bool fOpened;
+ /** Whether we've called pfnInit or not.
+ * For dealing with failed construction (the destruct method is always called). */
+ bool fInited;
+ /** Whether the device has been detached.
+ * This is hack for making PDMUSBREG::pfnUsbQueue return the right status code. */
+ bool fDetached;
+ /** Backend specific data, the size is stored in pOps::cbBackend. */
+ void *pvInstanceDataR3;
+
+#ifdef RDESKTOP
+ /** @name VRDP client (rdesktop) related members.
+ * @{ */
+ /** The vrdp device ID. */
+ uint32_t idVrdp;
+ /** The VUSB device structure - must be the first structure member. */
+ VUSBDEV Dev;
+ /** The next device in rdesktop-vrdp's linked list. */
+ PUSBPROXYDEV pNext;
+ /** The previous device in rdesktop-vrdp's linked list. */
+ PUSBPROXYDEV pPrev;
+ /** Linked list of in-flight URBs */
+ PVUSBURB pUrbs;
+ /** @} */
+#endif
+} USBPROXYDEV;
+
+/** @def USBPROXYDEV_2_DATA
+ * Converts a USB proxy Device, pointer to a pointer to the backend specific instance data.
+ */
+#define USBPROXYDEV_2_DATA(a_pProxyDev, a_Type) ( (a_Type)(a_pProxyDev)->pvInstanceDataR3 )
+
+
+DECLINLINE(const char *) usbProxyGetName(PUSBPROXYDEV pProxyDev)
+{
+#ifndef RDESKTOP
+ return pProxyDev->pUsbIns->pszName;
+#else
+ return pProxyDev->Dev.pszName;
+#endif
+}
+
+#ifdef RDESKTOP
+DECLINLINE(PUSBPROXYDEV) usbProxyFromVusbDev(PVUSBDEV pDev)
+{
+ return RT_FROM_MEMBER(pDev, USBPROXYDEV, Dev);
+}
+#endif
+
+#ifdef RT_OS_LINUX
+RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev);
+#endif
+
+RT_C_DECLS_END
+
+#endif /* !VBOX_INCLUDED_SRC_USB_USBProxyDevice_h */
+
diff --git a/src/VBox/Devices/USB/VUSBDevice.cpp b/src/VBox/Devices/USB/VUSBDevice.cpp
new file mode 100644
index 00000000..50f93d3a
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBDevice.cpp
@@ -0,0 +1,1884 @@
+/* $Id: VUSBDevice.cpp $ */
+/** @file
+ * Virtual USB - Device.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include "VUSBInternal.h"
+
+#include "VUSBSniffer.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Argument package of vusbDevResetThread().
+ */
+typedef struct vusb_reset_args
+{
+ /** Pointer to the device which is being reset. */
+ PVUSBDEV pDev;
+ /** The reset return code. */
+ int rc;
+ /** Pointer to the completion callback. */
+ PFNVUSBRESETDONE pfnDone;
+ /** User argument to pfnDone. */
+ void *pvUser;
+} VUSBRESETARGS, *PVUSBRESETARGS;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Default message pipe. */
+const VUSBDESCENDPOINTEX g_Endpoint0 =
+{
+ {
+ /* .bLength = */ VUSB_DT_ENDPOINT_MIN_LEN,
+ /* .bDescriptorType = */ VUSB_DT_ENDPOINT,
+ /* .bEndpointAddress = */ 0,
+ /* .bmAttributes = */ 0,
+ /* .wMaxPacketSize = */ 64,
+ /* .bInterval = */ 0
+ },
+ NULL
+};
+
+/** Default configuration. */
+const VUSBDESCCONFIGEX g_Config0 =
+{
+ {
+ /* .bLength = */ VUSB_DT_CONFIG_MIN_LEN,
+ /* .bDescriptorType = */ VUSB_DT_CONFIG,
+ /* .WTotalLength = */ 0, /* (auto-calculated) */
+ /* .bNumInterfaces = */ 0,
+ /* .bConfigurationValue =*/ 0,
+ /* .iConfiguration = */ 0,
+ /* .bmAttributes = */ 0x80,
+ /* .MaxPower = */ 14
+ },
+ NULL,
+ NULL
+};
+
+
+
+static PCVUSBDESCCONFIGEX vusbDevFindCfgDesc(PVUSBDEV pDev, int iCfg)
+{
+ if (iCfg == 0)
+ return &g_Config0;
+
+ for (unsigned i = 0; i < pDev->pDescCache->pDevice->bNumConfigurations; i++)
+ if (pDev->pDescCache->paConfigs[i].Core.bConfigurationValue == iCfg)
+ return &pDev->pDescCache->paConfigs[i];
+ return NULL;
+}
+
+static PVUSBINTERFACESTATE vusbDevFindIfState(PVUSBDEV pDev, int iIf)
+{
+ for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++)
+ if (pDev->paIfStates[i].pIf->paSettings[0].Core.bInterfaceNumber == iIf)
+ return &pDev->paIfStates[i];
+ return NULL;
+}
+
+static PCVUSBDESCINTERFACEEX vusbDevFindAltIfDesc(PCVUSBINTERFACESTATE pIfState, int iAlt)
+{
+ for (uint32_t i = 0; i < pIfState->pIf->cSettings; i++)
+ if (pIfState->pIf->paSettings[i].Core.bAlternateSetting == iAlt)
+ return &pIfState->pIf->paSettings[i];
+ return NULL;
+}
+
+void vusbDevMapEndpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc)
+{
+ uint8_t i8Addr = pEndPtDesc->Core.bEndpointAddress & 0xF;
+ PVUSBPIPE pPipe = &pDev->aPipes[i8Addr];
+ LogFlow(("vusbDevMapEndpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n",
+ pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes,
+ pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3]));
+
+ if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0)
+ {
+ Log(("vusb: map message pipe on address %u\n", i8Addr));
+ pPipe->in = pEndPtDesc;
+ pPipe->out = pEndPtDesc;
+ }
+ else if (pEndPtDesc->Core.bEndpointAddress & 0x80)
+ {
+ Log(("vusb: map input pipe on address %u\n", i8Addr));
+ pPipe->in = pEndPtDesc;
+ }
+ else
+ {
+ Log(("vusb: map output pipe on address %u\n", i8Addr));
+ pPipe->out = pEndPtDesc;
+ }
+
+ if (pPipe->pCtrl)
+ {
+ vusbMsgFreeExtraData(pPipe->pCtrl);
+ pPipe->pCtrl = NULL;
+ }
+}
+
+static void unmap_endpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc)
+{
+ uint8_t EndPt = pEndPtDesc->Core.bEndpointAddress & 0xF;
+ PVUSBPIPE pPipe = &pDev->aPipes[EndPt];
+ LogFlow(("unmap_endpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n",
+ pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes,
+ pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3]));
+
+ if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0)
+ {
+ Log(("vusb: unmap MSG pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
+ pPipe->in = NULL;
+ pPipe->out = NULL;
+ }
+ else if (pEndPtDesc->Core.bEndpointAddress & 0x80)
+ {
+ Log(("vusb: unmap IN pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
+ pPipe->in = NULL;
+ }
+ else
+ {
+ Log(("vusb: unmap OUT pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
+ pPipe->out = NULL;
+ }
+
+ if (pPipe->pCtrl)
+ {
+ vusbMsgFreeExtraData(pPipe->pCtrl);
+ pPipe->pCtrl = NULL;
+ }
+}
+
+static void map_interface(PVUSBDEV pDev, PCVUSBDESCINTERFACEEX pIfDesc)
+{
+ LogFlow(("map_interface: pDev=%p[%s] pIfDesc=%p:{.iInterface=%d, .bAlternateSetting=%d}\n",
+ pDev, pDev->pUsbIns->pszName, pIfDesc, pIfDesc->Core.iInterface, pIfDesc->Core.bAlternateSetting));
+
+ for (unsigned i = 0; i < pIfDesc->Core.bNumEndpoints; i++)
+ {
+ if ((pIfDesc->paEndpoints[i].Core.bEndpointAddress & 0xF) == VUSB_PIPE_DEFAULT)
+ Log(("vusb: Endpoint 0x%x on interface %u.%u tried to override the default message pipe!!!\n",
+ pIfDesc->paEndpoints[i].Core.bEndpointAddress, pIfDesc->Core.bInterfaceNumber, pIfDesc->Core.bAlternateSetting));
+ else
+ vusbDevMapEndpoint(pDev, &pIfDesc->paEndpoints[i]);
+ }
+}
+
+
+/**
+ * Worker that resets the pipe data on select config and detach.
+ *
+ * This leaves the critical section unmolested
+ *
+ * @param pPipe The pipe which data should be reset.
+ */
+static void vusbDevResetPipeData(PVUSBPIPE pPipe)
+{
+ vusbMsgFreeExtraData(pPipe->pCtrl);
+ pPipe->pCtrl = NULL;
+
+ RT_ZERO(pPipe->in);
+ RT_ZERO(pPipe->out);
+ pPipe->async = 0;
+}
+
+
+bool vusbDevDoSelectConfig(PVUSBDEV pDev, PCVUSBDESCCONFIGEX pCfgDesc)
+{
+ LogFlow(("vusbDevDoSelectConfig: pDev=%p[%s] pCfgDesc=%p:{.iConfiguration=%d}\n",
+ pDev, pDev->pUsbIns->pszName, pCfgDesc, pCfgDesc->Core.iConfiguration));
+
+ /*
+ * Clean up all pipes and interfaces.
+ */
+ unsigned i;
+ for (i = 0; i < VUSB_PIPE_MAX; i++)
+ if (i != VUSB_PIPE_DEFAULT)
+ vusbDevResetPipeData(&pDev->aPipes[i]);
+ memset(pDev->paIfStates, 0, pCfgDesc->Core.bNumInterfaces * sizeof(pDev->paIfStates[0]));
+
+ /*
+ * Map in the default setting for every interface.
+ */
+ for (i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
+ {
+ PCVUSBINTERFACE pIf;
+ struct vusb_interface_state *pIfState;
+
+ pIf = &pCfgDesc->paIfs[i];
+ pIfState = &pDev->paIfStates[i];
+ pIfState->pIf = pIf;
+
+ /*
+ * Find the 0 setting, if it is not present we just use
+ * the lowest numbered one.
+ */
+ for (uint32_t j = 0; j < pIf->cSettings; j++)
+ {
+ if ( !pIfState->pCurIfDesc
+ || pIf->paSettings[j].Core.bAlternateSetting < pIfState->pCurIfDesc->Core.bAlternateSetting)
+ pIfState->pCurIfDesc = &pIf->paSettings[j];
+ if (pIfState->pCurIfDesc->Core.bAlternateSetting == 0)
+ break;
+ }
+
+ if (pIfState->pCurIfDesc)
+ map_interface(pDev, pIfState->pCurIfDesc);
+ }
+
+ pDev->pCurCfgDesc = pCfgDesc;
+
+ if (pCfgDesc->Core.bmAttributes & 0x40)
+ pDev->u16Status |= (1 << VUSB_DEV_SELF_POWERED);
+ else
+ pDev->u16Status &= ~(1 << VUSB_DEV_SELF_POWERED);
+
+ return true;
+}
+
+/**
+ * Standard device request: SET_CONFIGURATION
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqSetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt, pbBuf, pcbBuf);
+ unsigned iCfg = pSetup->wValue & 0xff;
+
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
+ {
+ Log(("vusb: error: %s: SET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Check that the device is in a valid state.
+ * (The caller has already checked that it's not being reset.)
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState == VUSB_DEVICE_STATE_DEFAULT)
+ {
+ LogFlow(("vusbDevStdReqSetConfig: %s: default dev state !!?\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ PCVUSBDESCCONFIGEX pNewCfgDesc = vusbDevFindCfgDesc(pDev, iCfg);
+ if (!pNewCfgDesc)
+ {
+ Log(("vusb: error: %s: config %i not found !!!\n", pDev->pUsbIns->pszName, iCfg));
+ return false;
+ }
+
+ if (iCfg == 0)
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS);
+ else
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_CONFIGURED);
+ if (pDev->pUsbIns->pReg->pfnUsbSetConfiguration)
+ {
+ RTCritSectEnter(&pDev->pHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5,
+ pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue,
+ pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc);
+ RTCritSectLeave(&pDev->pHub->CritSectDevices);
+ if (RT_FAILURE(rc))
+ {
+ Log(("vusb: error: %s: failed to set config %i (%Rrc) !!!\n", pDev->pUsbIns->pszName, iCfg, rc));
+ return false;
+ }
+ }
+ Log(("vusb: %p[%s]: SET_CONFIGURATION: Selected config %u\n", pDev, pDev->pUsbIns->pszName, iCfg));
+ return vusbDevDoSelectConfig(pDev, pNewCfgDesc);
+}
+
+
+/**
+ * Standard device request: GET_CONFIGURATION
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqGetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt);
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
+ {
+ Log(("vusb: error: %s: GET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Check that the device is in a valid state.
+ * (The caller has already checked that it's not being reset.)
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if ( enmState != VUSB_DEVICE_STATE_CONFIGURED
+ && enmState != VUSB_DEVICE_STATE_ADDRESS)
+ {
+ LogFlow(("vusbDevStdReqGetConfig: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState));
+ return false;
+ }
+
+ if (*pcbBuf < 1)
+ {
+ LogFlow(("vusbDevStdReqGetConfig: %s: no space for data!\n", pDev->pUsbIns->pszName));
+ return true;
+ }
+
+ uint8_t iCfg;
+ if (enmState == VUSB_DEVICE_STATE_ADDRESS)
+ iCfg = 0;
+ else
+ iCfg = pDev->pCurCfgDesc->Core.bConfigurationValue;
+
+ *pbBuf = iCfg;
+ *pcbBuf = 1;
+ LogFlow(("vusbDevStdReqGetConfig: %s: returns iCfg=%d\n", pDev->pUsbIns->pszName, iCfg));
+ return true;
+}
+
+/**
+ * Standard device request: GET_INTERFACE
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqGetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt);
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE)
+ {
+ Log(("vusb: error: %s: GET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Check that the device is in a valid state.
+ * (The caller has already checked that it's not being reset.)
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState != VUSB_DEVICE_STATE_CONFIGURED)
+ {
+ LogFlow(("vusbDevStdReqGetInterface: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState));
+ return false;
+ }
+
+ if (*pcbBuf < 1)
+ {
+ LogFlow(("vusbDevStdReqGetInterface: %s: no space for data!\n", pDev->pUsbIns->pszName));
+ return true;
+ }
+
+ for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++)
+ {
+ PCVUSBDESCINTERFACEEX pIfDesc = pDev->paIfStates[i].pCurIfDesc;
+ if ( pIfDesc
+ && pSetup->wIndex == pIfDesc->Core.bInterfaceNumber)
+ {
+ *pbBuf = pIfDesc->Core.bAlternateSetting;
+ *pcbBuf = 1;
+ Log(("vusb: %s: GET_INTERFACE: %u.%u\n", pDev->pUsbIns->pszName, pIfDesc->Core.bInterfaceNumber, *pbBuf));
+ return true;
+ }
+ }
+
+ Log(("vusb: error: %s: GET_INTERFACE - unknown iface %u !!!\n", pDev->pUsbIns->pszName, pSetup->wIndex));
+ return false;
+}
+
+/**
+ * Standard device request: SET_INTERFACE
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqSetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt, pbBuf, pcbBuf);
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE)
+ {
+ Log(("vusb: error: %s: SET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Check that the device is in a valid state.
+ * (The caller has already checked that it's not being reset.)
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState != VUSB_DEVICE_STATE_CONFIGURED)
+ {
+ LogFlow(("vusbDevStdReqSetInterface: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState));
+ return false;
+ }
+
+ /*
+ * Find the interface.
+ */
+ uint8_t iIf = pSetup->wIndex;
+ PVUSBINTERFACESTATE pIfState = vusbDevFindIfState(pDev, iIf);
+ if (!pIfState)
+ {
+ LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find interface %u !!!\n", pDev->pUsbIns->pszName, iIf));
+ return false;
+ }
+ uint8_t iAlt = pSetup->wValue;
+ PCVUSBDESCINTERFACEEX pIfDesc = vusbDevFindAltIfDesc(pIfState, iAlt);
+ if (!pIfDesc)
+ {
+ LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u !!!\n", pDev->pUsbIns->pszName, iIf, iAlt));
+ return false;
+ }
+
+ if (pDev->pUsbIns->pReg->pfnUsbSetInterface)
+ {
+ RTCritSectEnter(&pDev->pHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt);
+ RTCritSectLeave(&pDev->pHub->CritSectDevices);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u (%Rrc)\n", pDev->pUsbIns->pszName, iIf, iAlt, rc));
+ return false;
+ }
+ }
+
+ for (unsigned i = 0; i < pIfState->pCurIfDesc->Core.bNumEndpoints; i++)
+ unmap_endpoint(pDev, &pIfState->pCurIfDesc->paEndpoints[i]);
+
+ Log(("vusb: SET_INTERFACE: Selected %u.%u\n", iIf, iAlt));
+
+ map_interface(pDev, pIfDesc);
+ pIfState->pCurIfDesc = pIfDesc;
+
+ return true;
+}
+
+/**
+ * Standard device request: SET_ADDRESS
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqSetAddress(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt, pbBuf, pcbBuf);
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
+ {
+ Log(("vusb: error: %s: SET_ADDRESS - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Check that the device is in a valid state.
+ * (The caller has already checked that it's not being reset.)
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if ( enmState != VUSB_DEVICE_STATE_DEFAULT
+ && enmState != VUSB_DEVICE_STATE_ADDRESS)
+ {
+ LogFlow(("vusbDevStdReqSetAddress: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState));
+ return false;
+ }
+
+ pDev->u8NewAddress = pSetup->wValue;
+ return true;
+}
+
+/**
+ * Standard device request: CLEAR_FEATURE
+ * @returns success indicator.
+ *
+ * @remark This is only called for VUSB_TO_ENDPOINT && ep == 0 && wValue == ENDPOINT_HALT.
+ * All other cases of CLEAR_FEATURE is handled in the normal async/sync manner.
+ */
+static bool vusbDevStdReqClearFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(pbBuf, pcbBuf);
+ switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
+ {
+ case VUSB_TO_DEVICE:
+ Log(("vusb: ClearFeature: dev(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
+ break;
+ case VUSB_TO_INTERFACE:
+ Log(("vusb: ClearFeature: iface(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
+ break;
+ case VUSB_TO_ENDPOINT:
+ Log(("vusb: ClearFeature: ep(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
+ if ( !EndPt /* Default control pipe only */
+ && pSetup->wValue == 0 /* ENDPOINT_HALT */
+ && pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint)
+ {
+ RTCritSectEnter(&pDev->pHub->CritSectDevices);
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint,
+ 2, pDev->pUsbIns, pSetup->wIndex);
+ RTCritSectLeave(&pDev->pHub->CritSectDevices);
+ return RT_SUCCESS(rc);
+ }
+ break;
+ default:
+ AssertMsgFailed(("VUSB_TO_OTHER!\n"));
+ break;
+ }
+
+ AssertMsgFailed(("Invalid safe check !!!\n"));
+ return false;
+}
+
+/**
+ * Standard device request: SET_FEATURE
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqSetFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(pDev, EndPt, pbBuf, pcbBuf);
+ switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
+ {
+ case VUSB_TO_DEVICE:
+ Log(("vusb: SetFeature: dev(%u): selector=%u\n",
+ pSetup->wIndex, pSetup->wValue));
+ break;
+ case VUSB_TO_INTERFACE:
+ Log(("vusb: SetFeature: if(%u): selector=%u\n",
+ pSetup->wIndex, pSetup->wValue));
+ break;
+ case VUSB_TO_ENDPOINT:
+ Log(("vusb: SetFeature: ep(%u): selector=%u\n",
+ pSetup->wIndex, pSetup->wValue));
+ break;
+ default:
+ AssertMsgFailed(("VUSB_TO_OTHER!\n"));
+ return false;
+ }
+ AssertMsgFailed(("This stuff is bogus\n"));
+ return false;
+}
+
+static bool vusbDevStdReqGetStatus(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt);
+ if (*pcbBuf != 2)
+ {
+ LogFlow(("vusbDevStdReqGetStatus: %s: buffer is too small! (%d)\n", pDev->pUsbIns->pszName, *pcbBuf));
+ return false;
+ }
+
+ uint16_t u16Status;
+ switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
+ {
+ case VUSB_TO_DEVICE:
+ u16Status = pDev->u16Status;
+ LogFlow(("vusbDevStdReqGetStatus: %s: device status %#x (%d)\n", pDev->pUsbIns->pszName, u16Status, u16Status));
+ break;
+ case VUSB_TO_INTERFACE:
+ u16Status = 0;
+ LogFlow(("vusbDevStdReqGetStatus: %s: bogus interface status request!!\n", pDev->pUsbIns->pszName));
+ break;
+ case VUSB_TO_ENDPOINT:
+ u16Status = 0;
+ LogFlow(("vusbDevStdReqGetStatus: %s: bogus endpoint status request!!\n", pDev->pUsbIns->pszName));
+ break;
+ default:
+ AssertMsgFailed(("VUSB_TO_OTHER!\n"));
+ return false;
+ }
+
+ *(uint16_t *)pbBuf = u16Status;
+ return true;
+}
+
+
+/**
+ * Finds a cached string.
+ *
+ * @returns Pointer to the cached string if found. NULL if not.
+ * @param paLanguages The languages to search.
+ * @param cLanguages The number of languages in the table.
+ * @param idLang The language ID.
+ * @param iString The string index.
+ */
+static PCPDMUSBDESCCACHESTRING FindCachedString(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages,
+ uint16_t idLang, uint8_t iString)
+{
+ /** @todo binary lookups! */
+ unsigned iCurLang = cLanguages;
+ while (iCurLang-- > 0)
+ if (paLanguages[iCurLang].idLang == idLang)
+ {
+ PCPDMUSBDESCCACHESTRING paStrings = paLanguages[iCurLang].paStrings;
+ unsigned iCurStr = paLanguages[iCurLang].cStrings;
+ while (iCurStr-- > 0)
+ if (paStrings[iCurStr].idx == iString)
+ return &paStrings[iCurStr];
+ break;
+ }
+ return NULL;
+}
+
+
+/** Macro for copying descriptor data. */
+#define COPY_DATA(pbDst, cbLeft, pvSrc, cbSrc) \
+ do { \
+ uint32_t cbSrc_ = cbSrc; \
+ uint32_t cbCopy = RT_MIN(cbLeft, cbSrc_); \
+ if (cbCopy) \
+ memcpy(pbBuf, pvSrc, cbCopy); \
+ cbLeft -= cbCopy; \
+ if (!cbLeft) \
+ return; \
+ pbBuf += cbCopy; \
+ } while (0)
+
+/**
+ * Internal function for reading the language IDs.
+ */
+static void ReadCachedStringDesc(PCPDMUSBDESCCACHESTRING pString, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ uint32_t cbLeft = *pcbBuf;
+
+ RTUTF16 wsz[128]; /* 128-1 => bLength=0xff */
+ PRTUTF16 pwsz = wsz;
+ size_t cwc;
+ int rc = RTStrToUtf16Ex(pString->psz, RT_ELEMENTS(wsz) - 1, &pwsz, RT_ELEMENTS(wsz), &cwc);
+ if (RT_FAILURE(rc))
+ {
+ AssertRC(rc);
+ wsz[0] = 'e';
+ wsz[1] = 'r';
+ wsz[2] = 'r';
+ cwc = 3;
+ }
+
+ VUSBDESCSTRING StringDesc;
+ StringDesc.bLength = (uint8_t)(sizeof(StringDesc) + cwc * sizeof(RTUTF16));
+ StringDesc.bDescriptorType = VUSB_DT_STRING;
+ COPY_DATA(pbBuf, cbLeft, &StringDesc, sizeof(StringDesc));
+ COPY_DATA(pbBuf, cbLeft, wsz, (uint32_t)cwc * sizeof(RTUTF16));
+
+ /* updated the size of the output buffer. */
+ *pcbBuf -= cbLeft;
+}
+
+
+/**
+ * Internal function for reading the language IDs.
+ */
+static void ReadCachedLangIdDesc(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages,
+ uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ uint32_t cbLeft = *pcbBuf;
+
+ VUSBDESCLANGID LangIdDesc;
+ size_t cbDesc = sizeof(LangIdDesc) + cLanguages * sizeof(paLanguages[0].idLang);
+ LangIdDesc.bLength = (uint8_t)RT_MIN(0xff, cbDesc);
+ LangIdDesc.bDescriptorType = VUSB_DT_STRING;
+ COPY_DATA(pbBuf, cbLeft, &LangIdDesc, sizeof(LangIdDesc));
+
+ unsigned iLanguage = cLanguages;
+ while (iLanguage-- > 0)
+ COPY_DATA(pbBuf, cbLeft, &paLanguages[iLanguage].idLang, sizeof(paLanguages[iLanguage].idLang));
+
+ /* updated the size of the output buffer. */
+ *pcbBuf -= cbLeft;
+}
+
+
+/**
+ * Internal function which performs a descriptor read on the cached descriptors.
+ */
+static void ReadCachedConfigDesc(PCVUSBDESCCONFIGEX pCfgDesc, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ uint32_t cbLeft = *pcbBuf;
+
+ /*
+ * Make a copy of the config descriptor and calculate the wTotalLength field.
+ */
+ VUSBDESCCONFIG CfgDesc;
+ memcpy(&CfgDesc, pCfgDesc, VUSB_DT_CONFIG_MIN_LEN);
+ uint32_t cbTotal = 0;
+ cbTotal += pCfgDesc->Core.bLength;
+ cbTotal += pCfgDesc->cbClass;
+ for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
+ {
+ PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i];
+ for (uint32_t j = 0; j < pIf->cSettings; j++)
+ {
+ cbTotal += pIf->paSettings[j].cbIAD;
+ cbTotal += pIf->paSettings[j].Core.bLength;
+ cbTotal += pIf->paSettings[j].cbClass;
+ for (unsigned k = 0; k < pIf->paSettings[j].Core.bNumEndpoints; k++)
+ {
+ cbTotal += pIf->paSettings[j].paEndpoints[k].Core.bLength;
+ cbTotal += pIf->paSettings[j].paEndpoints[k].cbSsepc;
+ cbTotal += pIf->paSettings[j].paEndpoints[k].cbClass;
+ }
+ }
+ }
+ CfgDesc.wTotalLength = RT_H2LE_U16(cbTotal);
+
+ /*
+ * Copy the config descriptor
+ */
+ COPY_DATA(pbBuf, cbLeft, &CfgDesc, VUSB_DT_CONFIG_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvMore, pCfgDesc->Core.bLength - VUSB_DT_CONFIG_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvClass, pCfgDesc->cbClass);
+
+ /*
+ * Copy out all the interfaces for this configuration
+ */
+ for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
+ {
+ PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i];
+ for (uint32_t j = 0; j < pIf->cSettings; j++)
+ {
+ PCVUSBDESCINTERFACEEX pIfDesc = &pIf->paSettings[j];
+
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->pIAD, pIfDesc->cbIAD);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc, VUSB_DT_INTERFACE_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->pvMore, pIfDesc->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->pvClass, pIfDesc->cbClass);
+
+ /*
+ * Copy out all the endpoints for this interface
+ */
+ for (unsigned k = 0; k < pIfDesc->Core.bNumEndpoints; k++)
+ {
+ VUSBDESCENDPOINT EndPtDesc;
+ memcpy(&EndPtDesc, &pIfDesc->paEndpoints[k], VUSB_DT_ENDPOINT_MIN_LEN);
+ EndPtDesc.wMaxPacketSize = RT_H2LE_U16(EndPtDesc.wMaxPacketSize);
+
+ COPY_DATA(pbBuf, cbLeft, &EndPtDesc, VUSB_DT_ENDPOINT_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvMore, EndPtDesc.bLength - VUSB_DT_ENDPOINT_MIN_LEN);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvSsepc, pIfDesc->paEndpoints[k].cbSsepc);
+ COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvClass, pIfDesc->paEndpoints[k].cbClass);
+ }
+ }
+ }
+
+ /* updated the size of the output buffer. */
+ *pcbBuf -= cbLeft;
+}
+
+/**
+ * Internal function which performs a descriptor read on the cached descriptors.
+ */
+static void ReadCachedDeviceDesc(PCVUSBDESCDEVICE pDevDesc, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ uint32_t cbLeft = *pcbBuf;
+
+ /*
+ * Duplicate the device description and update some fields we keep in cpu type.
+ */
+ Assert(sizeof(VUSBDESCDEVICE) == 18);
+ VUSBDESCDEVICE DevDesc = *pDevDesc;
+ DevDesc.bcdUSB = RT_H2LE_U16(DevDesc.bcdUSB);
+ DevDesc.idVendor = RT_H2LE_U16(DevDesc.idVendor);
+ DevDesc.idProduct = RT_H2LE_U16(DevDesc.idProduct);
+ DevDesc.bcdDevice = RT_H2LE_U16(DevDesc.bcdDevice);
+
+ COPY_DATA(pbBuf, cbLeft, &DevDesc, sizeof(DevDesc));
+ COPY_DATA(pbBuf, cbLeft, pDevDesc + 1, pDevDesc->bLength - sizeof(DevDesc));
+
+ /* updated the size of the output buffer. */
+ *pcbBuf -= cbLeft;
+}
+
+#undef COPY_DATA
+
+/**
+ * Checks whether a descriptor read can be satisfied by reading from the
+ * descriptor cache or has to be passed to the device.
+ * If we have descriptors cached, it is generally safe to satisfy descriptor reads
+ * from the cache. As usual, there is broken USB software and hardware out there
+ * and guests might try to read a nonexistent desciptor (out of range index for
+ * string or configuration descriptor) and rely on it not failing.
+ * Since we cannot very well guess if such invalid requests should really succeed,
+ * and what exactly should happen if they do, we pass such requests to the device.
+ * If the descriptor was cached because it was edited, and the guest bypasses the
+ * edited cache by reading a descriptor with an invalid index, it is probably
+ * best to smash the USB device with a large hammer.
+ *
+ * See @bugref{10016}.
+ *
+ * @returns false if request must be passed to device.
+ */
+bool vusbDevIsDescriptorInCache(PVUSBDEV pDev, PCVUSBSETUP pSetup)
+{
+ unsigned int iIndex = (pSetup->wValue & 0xff);
+ Assert(pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR);
+
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE)
+ {
+ if (pDev->pDescCache->fUseCachedDescriptors)
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ case VUSB_DT_DEVICE:
+ if (iIndex == 0)
+ return true;
+
+ LogRelMax(10, ("VUSB: %s: Warning: Reading device descriptor with non-zero index %u (wLength=%u), passing request to device\n",
+ pDev->pUsbIns->pszName, iIndex, pSetup->wLength));
+ break;
+
+ case VUSB_DT_CONFIG:
+ if (iIndex < pDev->pDescCache->pDevice->bNumConfigurations)
+ return true;
+
+ LogRelMax(10, ("VUSB: %s: Warning: Reading configuration descriptor invalid index %u (bNumConfigurations=%u, wLength=%u), passing request to device\n",
+ pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations, pSetup->wLength));
+ break;
+
+ case VUSB_DT_STRING:
+ if (pDev->pDescCache->fUseCachedStringsDescriptors)
+ {
+ if (pSetup->wIndex == 0) /* Language IDs. */
+ return true;
+
+ if (FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages,
+ pSetup->wIndex, iIndex))
+ return true;
+ }
+ break;
+
+ default:
+ break;
+ }
+ Log(("VUSB: %s: Descriptor not cached: type=%u descidx=%u lang=%u len=%u, passing request to device\n",
+ pDev->pUsbIns->pszName, pSetup->wValue >> 8, iIndex, pSetup->wIndex, pSetup->wLength));
+ }
+ }
+ return false;
+}
+
+
+/**
+ * Standard device request: GET_DESCRIPTOR
+ * @returns success indicator.
+ */
+static bool vusbDevStdReqGetDescriptor(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
+{
+ RT_NOREF(EndPt);
+ if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE)
+ {
+ switch (pSetup->wValue >> 8)
+ {
+ case VUSB_DT_DEVICE:
+ ReadCachedDeviceDesc(pDev->pDescCache->pDevice, pbBuf, pcbBuf);
+ LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of device descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
+ return true;
+
+ case VUSB_DT_CONFIG:
+ {
+ unsigned int iIndex = (pSetup->wValue & 0xff);
+ if (iIndex >= pDev->pDescCache->pDevice->bNumConfigurations)
+ {
+ LogFlow(("vusbDevStdReqGetDescriptor: %s: iIndex=%u >= bNumConfigurations=%d !!!\n",
+ pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations));
+ return false;
+ }
+ ReadCachedConfigDesc(&pDev->pDescCache->paConfigs[iIndex], pbBuf, pcbBuf);
+ LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of config descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
+ return true;
+ }
+
+ case VUSB_DT_STRING:
+ {
+ if (pSetup->wIndex == 0)
+ {
+ ReadCachedLangIdDesc(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, pbBuf, pcbBuf);
+ LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of language ID (string) descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
+ return true;
+ }
+ PCPDMUSBDESCCACHESTRING pString;
+ pString = FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages,
+ pSetup->wIndex, pSetup->wValue & 0xff);
+ if (pString)
+ {
+ ReadCachedStringDesc(pString, pbBuf, pcbBuf);
+ LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of string descriptors \"%s\"\n",
+ pDev->pUsbIns->pszName, *pcbBuf, pString->psz));
+ return true;
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+ Log(("vusb: %s: warning: unknown descriptor: type=%u descidx=%u lang=%u len=%u!!!\n",
+ pDev->pUsbIns->pszName, pSetup->wValue >> 8, pSetup->wValue & 0xff, pSetup->wIndex, pSetup->wLength));
+ return false;
+}
+
+
+/**
+ * Service the standard USB requests.
+ *
+ * Devices may call this from controlmsg() if you want vusb core to handle your standard
+ * request, it's not necessary - you could handle them manually
+ *
+ * @param pDev The device.
+ * @param EndPoint The endpoint.
+ * @param pSetup Pointer to the setup request structure.
+ * @param pvBuf Buffer?
+ * @param pcbBuf ?
+ */
+bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPoint, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf)
+{
+ static bool (* const s_apfnStdReq[VUSB_REQ_MAX])(PVUSBDEV, int, PVUSBSETUP, uint8_t *, uint32_t *) =
+ {
+ vusbDevStdReqGetStatus,
+ vusbDevStdReqClearFeature,
+ NULL,
+ vusbDevStdReqSetFeature,
+ NULL,
+ vusbDevStdReqSetAddress,
+ vusbDevStdReqGetDescriptor,
+ NULL,
+ vusbDevStdReqGetConfig,
+ vusbDevStdReqSetConfig,
+ vusbDevStdReqGetInterface,
+ vusbDevStdReqSetInterface,
+ NULL /* for iso */
+ };
+
+ /*
+ * Check that the device is in a valid state.
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: standard control message ignored, the device is resetting\n", pDev->pUsbIns->pszName));
+ return false;
+ }
+
+ /*
+ * Do the request if it's one we want to deal with.
+ */
+ if ( pSetup->bRequest >= VUSB_REQ_MAX
+ || !s_apfnStdReq[pSetup->bRequest])
+ {
+ Log(("vusb: warning: standard req not implemented: message %u: val=%u idx=%u len=%u !!!\n",
+ pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ return false;
+ }
+
+ return s_apfnStdReq[pSetup->bRequest](pDev, EndPoint, pSetup, (uint8_t *)pvBuf, pcbBuf);
+}
+
+
+/**
+ * Sets the address of a device.
+ *
+ * Called by status_completion() and vusbDevResetWorker().
+ */
+void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address)
+{
+ LogFlow(("vusbDevSetAddress: pDev=%p[%s]/%i u8Address=%#x\n",
+ pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address));
+
+ /*
+ * Check that the device is in a valid state.
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ VUSBDEV_ASSERT_VALID_STATE(enmState);
+ if ( enmState == VUSB_DEVICE_STATE_ATTACHED
+ || enmState == VUSB_DEVICE_STATE_DETACHED)
+ {
+ LogFlow(("vusbDevSetAddress: %s: fails because %d < POWERED\n", pDev->pUsbIns->pszName, pDev->enmState));
+ return;
+ }
+ if (enmState == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: set address ignored, the device is resetting\n", pDev->pUsbIns->pszName));
+ return;
+ }
+
+ /*
+ * Ok, get on with it.
+ */
+ if (pDev->u8Address == u8Address)
+ return;
+
+ /** @todo The following logic belongs to the roothub and should actually be in that file. */
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ AssertPtrReturnVoid(pRh);
+
+ RTCritSectEnter(&pRh->CritSectDevices);
+
+ /* Remove the device from the current address. */
+ if (pDev->u8Address != VUSB_INVALID_ADDRESS)
+ {
+ Assert(pRh->apDevByAddr[pDev->u8Address] == pDev);
+ pRh->apDevByAddr[pDev->u8Address] = NULL;
+ }
+
+ if (u8Address == VUSB_DEFAULT_ADDRESS)
+ {
+ PVUSBDEV pDevDef = pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS];
+
+ if (pDevDef)
+ {
+ pDevDef->u8Address = VUSB_INVALID_ADDRESS;
+ pDevDef->u8NewAddress = VUSB_INVALID_ADDRESS;
+ vusbDevSetStateCmp(pDevDef, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT);
+ Log(("2 DEFAULT ADDRS\n"));
+ }
+
+ pRh->apDevByAddr[VUSB_DEFAULT_ADDRESS] = pDev;
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT);
+ }
+ else
+ {
+ Assert(!pRh->apDevByAddr[u8Address]);
+ pRh->apDevByAddr[u8Address] = pDev;
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS);
+ }
+
+ pDev->u8Address = u8Address;
+ RTCritSectLeave(&pRh->CritSectDevices);
+
+ Log(("vusb: %p[%s]/%i: Assigned address %u\n",
+ pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address));
+}
+
+
+static DECLCALLBACK(int) vusbDevCancelAllUrbsWorker(PVUSBDEV pDev, bool fDetaching)
+{
+ /*
+ * Iterate the URBs and cancel them.
+ */
+ PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext;
+ RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst)
+ {
+ PVUSBURB pUrb = pVUsbUrb->pUrb;
+
+ Assert(pUrb->pVUsb->pDev == pDev);
+
+ LogFlow(("%s: vusbDevCancelAllUrbs: CANCELING URB\n", pUrb->pszDesc));
+ int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL);
+ AssertRC(rc);
+ }
+
+ /*
+ * Reap any URBs which became ripe during cancel now.
+ */
+ RTCritSectEnter(&pDev->CritSectAsyncUrbs);
+ unsigned cReaped;
+ do
+ {
+ cReaped = 0;
+ pVUsbUrb = RTListGetFirst(&pDev->LstAsyncUrbs, VUSBURBVUSBINT, NdLst);
+ while (pVUsbUrb)
+ {
+ PVUSBURBVUSB pNext = RTListGetNext(&pDev->LstAsyncUrbs, pVUsbUrb, VUSBURBVUSBINT, NdLst);
+ PVUSBURB pUrb = pVUsbUrb->pUrb;
+ Assert(pUrb->pVUsb->pDev == pDev);
+
+ PVUSBURB pRipe = NULL;
+ if (pUrb->enmState == VUSBURBSTATE_REAPED)
+ pRipe = pUrb;
+ else if (pUrb->enmState == VUSBURBSTATE_CANCELLED)
+#ifdef RT_OS_WINDOWS /** @todo Windows doesn't do cancelling, thus this kludge to prevent really bad
+ * things from happening if we leave a pending URB behinds. */
+ pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 1500 : 0 /*ms*/);
+#else
+ pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 10 : 0 /*ms*/);
+#endif
+ else
+ AssertMsgFailed(("pUrb=%p enmState=%d\n", pUrb, pUrb->enmState));
+ if (pRipe)
+ {
+ if ( pNext
+ && pRipe == pNext->pUrb)
+ pNext = RTListGetNext(&pDev->LstAsyncUrbs, pNext, VUSBURBVUSBINT, NdLst);
+ vusbUrbRipe(pRipe);
+ cReaped++;
+ }
+
+ pVUsbUrb = pNext;
+ }
+ } while (cReaped > 0);
+
+ /*
+ * If we're detaching, we'll have to orphan any leftover URBs.
+ */
+ if (fDetaching)
+ {
+ RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst)
+ {
+ PVUSBURB pUrb = pVUsbUrb->pUrb;
+ Assert(pUrb->pVUsb->pDev == pDev);
+
+ AssertMsgFailed(("%s: Leaking left over URB! state=%d pDev=%p[%s]\n",
+ pUrb->pszDesc, pUrb->enmState, pDev, pDev->pUsbIns->pszName));
+ vusbUrbUnlink(pUrb);
+ /* Unlink isn't enough, because boundary timer and detaching will try to reap it.
+ * It was tested with MSD & iphone attachment to vSMP guest, if
+ * it breaks anything, please add comment here, why we should unlink only.
+ */
+ pUrb->pVUsb->pfnFree(pUrb);
+ }
+ }
+ RTCritSectLeave(&pDev->CritSectAsyncUrbs);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Cancels and completes (with CRC failure) all async URBs pending
+ * on a device. This is typically done as part of a reset and
+ * before detaching a device.
+ *
+ * @returns nothing.
+ * @param pDev The VUSB device instance.
+ * @param fDetaching If set, we will unconditionally unlink (and leak)
+ * any URBs which isn't reaped.
+ */
+DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching)
+{
+ int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevCancelAllUrbsWorker, 2, pDev, fDetaching);
+ AssertRC(rc);
+}
+
+
+static DECLCALLBACK(int) vusbDevUrbIoThread(RTTHREAD hThread, void *pvUser)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pvUser;
+
+ /* Notify the starter that we are up and running. */
+ RTThreadUserSignal(hThread);
+
+ LogFlowFunc(("Entering work loop\n"));
+
+ while (!ASMAtomicReadBool(&pDev->fTerminate))
+ {
+ if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET)
+ vusbUrbDoReapAsyncDev(pDev, RT_INDEFINITE_WAIT);
+
+ /* Process any URBs waiting to be cancelled first. */
+ int rc = RTReqQueueProcess(pDev->hReqQueueSync, 0); /* Don't wait if there is nothing to do. */
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc);
+ }
+
+ return VINF_SUCCESS;
+}
+
+int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev)
+{
+ ASMAtomicXchgBool(&pDev->fWokenUp, true);
+ return pDev->pUsbIns->pReg->pfnWakeup(pDev->pUsbIns);
+}
+
+/**
+ * Create the URB I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pDev The VUSB device.
+ */
+int vusbDevUrbIoThreadCreate(PVUSBDEV pDev)
+{
+ int rc = VINF_SUCCESS;
+
+ ASMAtomicXchgBool(&pDev->fTerminate, false);
+ rc = RTThreadCreateF(&pDev->hUrbIoThread, vusbDevUrbIoThread, pDev, 0, RTTHREADTYPE_IO,
+ RTTHREADFLAGS_WAITABLE, "USBDevIo-%d", pDev->i16Port);
+ if (RT_SUCCESS(rc))
+ {
+ /* Wait for it to become active. */
+ rc = RTThreadUserWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT);
+ }
+
+ return rc;
+}
+
+/**
+ * Destro the URB I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pDev The VUSB device.
+ */
+int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev)
+{
+ int rc = VINF_SUCCESS;
+ int rcThread = VINF_SUCCESS;
+
+ ASMAtomicXchgBool(&pDev->fTerminate, true);
+ vusbDevUrbIoThreadWakeup(pDev);
+
+ rc = RTThreadWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT, &rcThread);
+ if (RT_SUCCESS(rc))
+ rc = rcThread;
+
+ pDev->hUrbIoThread = NIL_RTTHREAD;
+
+ return rc;
+}
+
+
+/**
+ * Attaches a device to the given hub.
+ *
+ * @returns VBox status code.
+ * @param pDev The device to attach.
+ * @param pHub The roothub to attach to.
+ */
+int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub)
+{
+ AssertMsg(pDev->enmState == VUSB_DEVICE_STATE_DETACHED, ("enmState=%d\n", pDev->enmState));
+
+ pDev->pHub = pHub;
+ pDev->enmState = VUSB_DEVICE_STATE_ATTACHED;
+
+ /* noone else ever messes with the default pipe while we are attached */
+ vusbDevMapEndpoint(pDev, &g_Endpoint0);
+ vusbDevDoSelectConfig(pDev, &g_Config0);
+
+ /* Create I/O thread and attach to the hub. */
+ int rc = vusbDevUrbIoThreadCreate(pDev);
+ if (RT_FAILURE(rc))
+ {
+ pDev->pHub = NULL;
+ pDev->enmState = VUSB_DEVICE_STATE_DETACHED;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Detaches a device from the hub it's attached to.
+ *
+ * @returns VBox status code.
+ * @param pDev The device to detach.
+ *
+ * @remark This can be called in any state but reset.
+ */
+int vusbDevDetach(PVUSBDEV pDev)
+{
+ LogFlow(("vusbDevDetach: pDev=%p[%s] enmState=%#x\n", pDev, pDev->pUsbIns->pszName, pDev->enmState));
+ VUSBDEV_ASSERT_VALID_STATE(pDev->enmState);
+ Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Destroy I/O thread and request queue last because they might still be used
+ * when cancelling URBs.
+ */
+ vusbDevUrbIoThreadDestroy(pDev);
+
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_DETACHED);
+ pDev->pHub = NULL;
+
+ /* Remove the configuration */
+ pDev->pCurCfgDesc = NULL;
+ for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
+ vusbDevResetPipeData(&pDev->aPipes[i]);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Destroys a device, detaching it from the hub if necessary.
+ *
+ * @param pDev The device.
+ * @thread any.
+ */
+void vusbDevDestroy(PVUSBDEV pDev)
+{
+ LogFlow(("vusbDevDestroy: pDev=%p[%s] enmState=%d\n", pDev, pDev->pUsbIns->pszName, pDev->enmState));
+
+ RTMemFree(pDev->paIfStates);
+
+ PDMUsbHlpTimerDestroy(pDev->pUsbIns, pDev->hResetTimer);
+ pDev->hResetTimer = NIL_TMTIMERHANDLE;
+
+ for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
+ {
+ Assert(pDev->aPipes[i].pCtrl == NULL);
+ RTCritSectDelete(&pDev->aPipes[i].CritSectCtrl);
+ }
+
+ if (pDev->hSniffer != VUSBSNIFFER_NIL)
+ VUSBSnifferDestroy(pDev->hSniffer);
+
+ vusbUrbPoolDestroy(&pDev->UrbPool);
+
+ int rc = RTReqQueueDestroy(pDev->hReqQueueSync);
+ AssertRC(rc);
+ pDev->hReqQueueSync = NIL_RTREQQUEUE;
+
+ RTCritSectDelete(&pDev->CritSectAsyncUrbs);
+ /* Not using vusbDevSetState() deliberately here because it would assert on the state. */
+ pDev->enmState = VUSB_DEVICE_STATE_DESTROYED;
+ pDev->pUsbIns->pvVUsbDev2 = NULL;
+ RTMemFree(pDev);
+}
+
+
+/* -=-=-=-=-=- VUSBIDEVICE methods -=-=-=-=-=- */
+
+
+/**
+ * The actual reset has been done, do completion on EMT.
+ *
+ * There are several things we have to do now, like set default
+ * config and address, and cleanup the state of control pipes.
+ *
+ * It's possible that the device has a delayed destroy request
+ * pending when we get here. This can happen for async resetting.
+ * We deal with it here, since we're now executing on the EMT
+ * thread and the destruction will be properly serialized now.
+ *
+ * @param pDev The device that is being reset.
+ * @param rc The vusbDevResetWorker return code.
+ * @param pfnDone The done callback specified by the caller of vusbDevReset().
+ * @param pvUser The user argument for the callback.
+ */
+static void vusbDevResetDone(PVUSBDEV pDev, int rc, PFNVUSBRESETDONE pfnDone, void *pvUser)
+{
+ VUSBDEV_ASSERT_VALID_STATE(pDev->enmState);
+ Assert(pDev->enmState == VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Do control pipe cleanup regardless of state and result.
+ */
+ for (unsigned i = 0; i < VUSB_PIPE_MAX; i++)
+ if (pDev->aPipes[i].pCtrl)
+ vusbMsgResetExtraData(pDev->aPipes[i].pCtrl);
+
+ /*
+ * Switch to the default state.
+ */
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT);
+ pDev->u16Status = 0;
+ vusbDevDoSelectConfig(pDev, &g_Config0);
+ vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS);
+ if (pfnDone)
+ pfnDone(&pDev->IDevice, pDev->i16Port, rc, pvUser);
+}
+
+
+/**
+ * @callback_method_impl{FNTMTIMERUSB,
+ * Timer callback for doing reset completion.}
+ */
+static DECLCALLBACK(void) vusbDevResetDoneTimer(PPDMUSBINS pUsbIns, TMTIMERHANDLE hTimer, void *pvUser)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pvUser;
+ PVUSBRESETARGS pArgs = (PVUSBRESETARGS)pDev->pvArgs;
+ Assert(pDev->pUsbIns == pUsbIns);
+ RT_NOREF(pUsbIns, hTimer);
+
+ AssertPtr(pArgs);
+
+ /*
+ * Reset-done processing and cleanup.
+ */
+ pDev->pvArgs = NULL;
+ vusbDevResetDone(pDev, pArgs->rc, pArgs->pfnDone, pArgs->pvUser);
+ RTMemFree(pArgs);
+}
+
+
+/**
+ * Perform the actual reset.
+ *
+ * @thread EMT or a VUSB reset thread.
+ */
+static DECLCALLBACK(int) vusbDevResetWorker(PVUSBDEV pDev, bool fResetOnLinux, bool fUseTimer, PVUSBRESETARGS pArgs)
+{
+ uint64_t const uTimerDeadline = !fUseTimer ? 0
+ : PDMUsbHlpTimerGet(pDev->pUsbIns, pDev->hResetTimer)
+ + PDMUsbHlpTimerFromMilli(pDev->pUsbIns, pDev->hResetTimer, 10);
+
+ int rc = VINF_SUCCESS;
+ if (pDev->pUsbIns->pReg->pfnUsbReset)
+ rc = pDev->pUsbIns->pReg->pfnUsbReset(pDev->pUsbIns, fResetOnLinux);
+
+ if (pArgs)
+ {
+ pArgs->rc = rc;
+ rc = VINF_SUCCESS;
+ }
+
+ if (fUseTimer)
+ {
+ /*
+ * We use a timer to communicate the result back to EMT.
+ * This avoids suspend + poweroff issues, and it should give
+ * us more accurate scheduling than making this thread sleep.
+ */
+ int rc2 = PDMUsbHlpTimerSet(pDev->pUsbIns, pDev->hResetTimer, uTimerDeadline);
+ AssertReleaseRC(rc2);
+ }
+
+ LogFlow(("vusbDevResetWorker: %s: returns %Rrc\n", pDev->pUsbIns->pszName, rc));
+ return rc;
+}
+
+
+/**
+ * Resets a device.
+ *
+ * Since a device reset shall take at least 10ms from the guest point of view,
+ * it must be performed asynchronously. We create a thread which performs this
+ * operation and ensures it will take at least 10ms.
+ *
+ * At times - like init - a synchronous reset is required, this can be done
+ * by passing NULL for pfnDone.
+ *
+ * While the device is being reset it is in the VUSB_DEVICE_STATE_RESET state.
+ * On completion it will be in the VUSB_DEVICE_STATE_DEFAULT state if successful,
+ * or in the VUSB_DEVICE_STATE_DETACHED state if the rest failed.
+ *
+ * @returns VBox status code.
+ *
+ * @param pDevice Pointer to the VUSB device interface.
+ * @param fResetOnLinux Whether it's safe to reset the device(s) on a linux
+ * host system. See discussion of logical reconnects elsewhere.
+ * @param pfnDone Pointer to the completion routine. If NULL a synchronous
+ * reset is preformed not respecting the 10ms.
+ * @param pvUser Opaque user data to pass to the done callback.
+ * @param pVM Pointer to the VM handle for performing the done function
+ * on the EMT thread.
+ * @thread EMT
+ */
+static DECLCALLBACK(int) vusbIDeviceReset(PVUSBIDEVICE pDevice, bool fResetOnLinux,
+ PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM)
+{
+ RT_NOREF(pVM);
+ PVUSBDEV pDev = (PVUSBDEV)pDevice;
+ Assert(!pfnDone || pVM);
+ LogFlow(("vusb: reset: [%s]/%i\n", pDev->pUsbIns->pszName, pDev->i16Port));
+
+ /*
+ * Only one reset operation at a time.
+ */
+ const VUSBDEVICESTATE enmStateOld = vusbDevSetState(pDev, VUSB_DEVICE_STATE_RESET);
+ if (enmStateOld == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: reset request is ignored, the device is already resetting!\n", pDev->pUsbIns->pszName));
+ return VERR_VUSB_DEVICE_IS_RESETTING;
+ }
+
+ /*
+ * First, cancel all async URBs.
+ */
+ vusbDevCancelAllUrbs(pDev, false);
+
+ /* Async or sync? */
+ if (pfnDone)
+ {
+ /*
+ * Async fashion.
+ */
+ PVUSBRESETARGS pArgs = (PVUSBRESETARGS)RTMemTmpAlloc(sizeof(*pArgs));
+ if (pArgs)
+ {
+ pArgs->pDev = pDev;
+ pArgs->pfnDone = pfnDone;
+ pArgs->pvUser = pvUser;
+ pArgs->rc = VINF_SUCCESS;
+ AssertPtrNull(pDev->pvArgs);
+ pDev->pvArgs = pArgs;
+ int rc = vusbDevIoThreadExec(pDev, 0 /* fFlags */, (PFNRT)vusbDevResetWorker, 4, pDev, fResetOnLinux, true, pArgs);
+ if (RT_SUCCESS(rc))
+ return rc;
+
+ RTMemTmpFree(pArgs);
+ }
+ /* fall back to sync on failure */
+ }
+
+ /*
+ * Sync fashion.
+ */
+ int rc = vusbDevResetWorker(pDev, fResetOnLinux, false, NULL);
+ vusbDevResetDone(pDev, rc, pfnDone, pvUser);
+ return rc;
+}
+
+
+/**
+ * Powers on the device.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the device interface structure.
+ */
+static DECLCALLBACK(int) vusbIDevicePowerOn(PVUSBIDEVICE pInterface)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pInterface;
+ LogFlow(("vusbDevPowerOn: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName));
+
+ /*
+ * Check that the device is in a valid state.
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState == VUSB_DEVICE_STATE_DETACHED)
+ {
+ Log(("vusb: warning: attempt to power on detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName));
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+ if (enmState == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: power on ignored, the device is resetting!\n", pDev->pUsbIns->pszName));
+ return VERR_VUSB_DEVICE_IS_RESETTING;
+ }
+
+ /*
+ * Do the job.
+ */
+ if (enmState == VUSB_DEVICE_STATE_ATTACHED)
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_POWERED);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Powers off the device.
+ *
+ * @returns VBox status code.
+ * @param pInterface Pointer to the device interface structure.
+ */
+static DECLCALLBACK(int) vusbIDevicePowerOff(PVUSBIDEVICE pInterface)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pInterface;
+ LogFlow(("vusbDevPowerOff: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName));
+
+ /*
+ * Check that the device is in a valid state.
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState == VUSB_DEVICE_STATE_DETACHED)
+ {
+ Log(("vusb: warning: attempt to power off detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName));
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+ if (enmState == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName));
+ return VERR_VUSB_DEVICE_IS_RESETTING;
+ }
+
+ vusbDevSetState(pDev, VUSB_DEVICE_STATE_ATTACHED);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Get the state of the device.
+ *
+ * @returns Device state.
+ * @param pInterface Pointer to the device interface structure.
+ */
+static DECLCALLBACK(VUSBDEVICESTATE) vusbIDeviceGetState(PVUSBIDEVICE pInterface)
+{
+ return vusbDevGetState((PVUSBDEV)pInterface);
+}
+
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnIsSavedStateSupported}
+ */
+static DECLCALLBACK(bool) vusbIDeviceIsSavedStateSupported(PVUSBIDEVICE pInterface)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pInterface;
+ bool fSavedStateSupported = RT_BOOL(pDev->pUsbIns->pReg->fFlags & PDM_USBREG_SAVED_STATE_SUPPORTED);
+
+ LogFlowFunc(("pInterface=%p\n", pInterface));
+
+ LogFlowFunc(("returns %RTbool\n", fSavedStateSupported));
+ return fSavedStateSupported;
+}
+
+
+/**
+ * @interface_method_impl{VUSBIDEVICE,pfnGetState}
+ */
+static DECLCALLBACK(VUSBSPEED) vusbIDeviceGetSpeed(PVUSBIDEVICE pInterface)
+{
+ PVUSBDEV pDev = (PVUSBDEV)pInterface;
+ VUSBSPEED enmSpeed = pDev->pUsbIns->enmSpeed;
+
+ LogFlowFunc(("pInterface=%p, returns %u\n", pInterface, enmSpeed));
+ return enmSpeed;
+}
+
+
+/**
+ * The maximum number of interfaces the device can have in all of it's configuration.
+ *
+ * @returns Number of interfaces.
+ * @param pDev The device.
+ */
+size_t vusbDevMaxInterfaces(PVUSBDEV pDev)
+{
+ uint8_t cMax = 0;
+ unsigned i = pDev->pDescCache->pDevice->bNumConfigurations;
+ while (i-- > 0)
+ {
+ if (pDev->pDescCache->paConfigs[i].Core.bNumInterfaces > cMax)
+ cMax = pDev->pDescCache->paConfigs[i].Core.bNumInterfaces;
+ }
+
+ return cMax;
+}
+
+
+/**
+ * Executes a given function on the I/O thread.
+ *
+ * @returns IPRT status code.
+ * @param pDev The USB device instance data.
+ * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_*
+ * @param pfnFunction The function to execute.
+ * @param cArgs Number of arguments to the function.
+ * @param Args The parameter list.
+ *
+ * @remarks See remarks on RTReqQueueCallV
+ */
+DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args)
+{
+ int rc = VINF_SUCCESS;
+ PRTREQ hReq = NULL;
+
+ Assert(pDev->hUrbIoThread != NIL_RTTHREAD);
+ if (RT_LIKELY(pDev->hUrbIoThread != NIL_RTTHREAD))
+ {
+ uint32_t fReqFlags = RTREQFLAGS_IPRT_STATUS;
+
+ if (!(fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC))
+ fReqFlags |= RTREQFLAGS_NO_WAIT;
+
+ rc = RTReqQueueCallV(pDev->hReqQueueSync, &hReq, 0 /* cMillies */, fReqFlags, pfnFunction, cArgs, Args);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
+
+ /* In case we are called on the I/O thread just process the request. */
+ if ( pDev->hUrbIoThread == RTThreadSelf()
+ && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC))
+ {
+ int rc2 = RTReqQueueProcess(pDev->hReqQueueSync, 0);
+ Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT); NOREF(rc2);
+ }
+ else
+ vusbDevUrbIoThreadWakeup(pDev);
+
+ if ( rc == VERR_TIMEOUT
+ && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC))
+ {
+ rc = RTReqWait(hReq, RT_INDEFINITE_WAIT);
+ AssertRC(rc);
+ }
+ RTReqRelease(hReq);
+ }
+ else
+ rc = VERR_INVALID_STATE;
+
+ return rc;
+}
+
+
+/**
+ * Executes a given function on the I/O thread.
+ *
+ * @returns IPRT status code.
+ * @param pDev The USB device instance data.
+ * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_*
+ * @param pfnFunction The function to execute.
+ * @param cArgs Number of arguments to the function.
+ * @param ... The parameter list.
+ *
+ * @remarks See remarks on RTReqQueueCallV
+ */
+DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ int rc = VINF_SUCCESS;
+ va_list va;
+
+ va_start(va, cArgs);
+ rc = vusbDevIoThreadExecV(pDev, fFlags, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Executes a given function synchronously on the I/O thread waiting for it to complete.
+ *
+ * @returns IPRT status code.
+ * @param pDev The USB device instance data
+ * @param pfnFunction The function to execute.
+ * @param cArgs Number of arguments to the function.
+ * @param ... The parameter list.
+ *
+ * @remarks See remarks on RTReqQueueCallV
+ */
+DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...)
+{
+ int rc = VINF_SUCCESS;
+ va_list va;
+
+ va_start(va, cArgs);
+ rc = vusbDevIoThreadExecV(pDev, VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC, pfnFunction, cArgs, va);
+ va_end(va);
+ return rc;
+}
+
+
+/**
+ * Initialize a new VUSB device.
+ *
+ * @returns VBox status code.
+ * @param pDev The VUSB device to initialize.
+ * @param pUsbIns Pointer to the PDM USB Device instance.
+ * @param pszCaptureFilename Optional fileame to capture the traffic to.
+ */
+int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename)
+{
+ /*
+ * Initialize the device data members.
+ * (All that are Non-Zero at least.)
+ */
+ Assert(!pDev->IDevice.pfnReset);
+ Assert(!pDev->IDevice.pfnPowerOn);
+ Assert(!pDev->IDevice.pfnPowerOff);
+ Assert(!pDev->IDevice.pfnGetState);
+ Assert(!pDev->IDevice.pfnIsSavedStateSupported);
+
+ pDev->IDevice.pfnReset = vusbIDeviceReset;
+ pDev->IDevice.pfnPowerOn = vusbIDevicePowerOn;
+ pDev->IDevice.pfnPowerOff = vusbIDevicePowerOff;
+ pDev->IDevice.pfnGetState = vusbIDeviceGetState;
+ pDev->IDevice.pfnIsSavedStateSupported = vusbIDeviceIsSavedStateSupported;
+ pDev->IDevice.pfnGetSpeed = vusbIDeviceGetSpeed;
+ pDev->pUsbIns = pUsbIns;
+ pDev->pHub = NULL;
+ pDev->enmState = VUSB_DEVICE_STATE_DETACHED;
+ pDev->cRefs = 1;
+ pDev->u8Address = VUSB_INVALID_ADDRESS;
+ pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
+ pDev->i16Port = -1;
+ pDev->u16Status = 0;
+ pDev->pDescCache = NULL;
+ pDev->pCurCfgDesc = NULL;
+ pDev->paIfStates = NULL;
+ RTListInit(&pDev->LstAsyncUrbs);
+ memset(&pDev->aPipes[0], 0, sizeof(pDev->aPipes));
+ for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
+ {
+ int rc = RTCritSectInit(&pDev->aPipes[i].CritSectCtrl);
+ AssertRCReturn(rc, rc);
+ }
+ pDev->hResetTimer = NIL_TMTIMERHANDLE;
+ pDev->hSniffer = VUSBSNIFFER_NIL;
+
+ int rc = RTCritSectInit(&pDev->CritSectAsyncUrbs);
+ AssertRCReturn(rc, rc);
+
+ /* Create the URB pool. */
+ rc = vusbUrbPoolInit(&pDev->UrbPool);
+ AssertRCReturn(rc, rc);
+
+ /* Setup request queue executing synchronous tasks on the I/O thread. */
+ rc = RTReqQueueCreate(&pDev->hReqQueueSync);
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Create the reset timer. Make sure the name is unique as we're generic code.
+ */
+ static uint32_t volatile s_iSeq;
+ char szDesc[32];
+ RTStrPrintf(szDesc, sizeof(szDesc), "VUSB Reset #%u", ASMAtomicIncU32(&s_iSeq));
+ rc = PDMUsbHlpTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/,
+ szDesc, &pDev->hResetTimer);
+ AssertRCReturn(rc, rc);
+
+ if (pszCaptureFilename)
+ {
+ rc = VUSBSnifferCreate(&pDev->hSniffer, 0, pszCaptureFilename, NULL, NULL);
+ AssertRCReturn(rc, rc);
+ }
+
+ /*
+ * Get the descriptor cache from the device. (shall cannot fail)
+ */
+ pDev->pDescCache = pUsbIns->pReg->pfnUsbGetDescriptorCache(pUsbIns);
+ AssertPtr(pDev->pDescCache);
+#ifdef VBOX_STRICT
+ if (pDev->pDescCache->fUseCachedStringsDescriptors)
+ {
+ int32_t iPrevId = -1;
+ for (unsigned iLang = 0; iLang < pDev->pDescCache->cLanguages; iLang++)
+ {
+ Assert((int32_t)pDev->pDescCache->paLanguages[iLang].idLang > iPrevId);
+ iPrevId = pDev->pDescCache->paLanguages[iLang].idLang;
+
+ int32_t idxPrevStr = -1;
+ PCPDMUSBDESCCACHESTRING paStrings = pDev->pDescCache->paLanguages[iLang].paStrings;
+ unsigned cStrings = pDev->pDescCache->paLanguages[iLang].cStrings;
+ for (unsigned iStr = 0; iStr < cStrings; iStr++)
+ {
+ Assert((int32_t)paStrings[iStr].idx > idxPrevStr);
+ idxPrevStr = paStrings[iStr].idx;
+ size_t cch = strlen(paStrings[iStr].psz);
+ Assert(cch <= 127);
+ }
+ }
+ }
+#endif
+
+ /*
+ * Allocate memory for the interface states.
+ */
+ size_t cbIface = vusbDevMaxInterfaces(pDev) * sizeof(*pDev->paIfStates);
+ pDev->paIfStates = (PVUSBINTERFACESTATE)RTMemAllocZ(cbIface);
+ AssertMsgReturn(pDev->paIfStates, ("RTMemAllocZ(%d) failed\n", cbIface), VERR_NO_MEMORY);
+
+ return VINF_SUCCESS;
+}
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "bsd"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: s
+ * End:
+ */
+
diff --git a/src/VBox/Devices/USB/VUSBInternal.h b/src/VBox/Devices/USB/VUSBInternal.h
new file mode 100644
index 00000000..d4ac13b7
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBInternal.h
@@ -0,0 +1,742 @@
+/* $Id: VUSBInternal.h $ */
+/** @file
+ * Virtual USB - Internal header.
+ *
+ * This subsystem implements USB devices in a host controller independent
+ * way. All the host controller code has to do is use VUSBROOTHUB for its
+ * root hub implementation and any emulated USB device may be plugged into
+ * the virtual bus.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_USB_VUSBInternal_h
+#define VBOX_INCLUDED_SRC_USB_VUSBInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <VBox/types.h>
+#include <VBox/vusb.h>
+#include <VBox/vmm/stam.h>
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/vmm/pdmusb.h>
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/req.h>
+#include <iprt/asm.h>
+#include <iprt/list.h>
+
+#include "VUSBSniffer.h"
+
+RT_C_DECLS_BEGIN
+
+
+/** @defgroup grp_vusb_int VUSB Internals.
+ * @ingroup grp_vusb
+ * @internal
+ * @{
+ */
+
+/** @defgroup grp_vusb_int_dev Internal Device Operations, Structures and Constants.
+ * @{
+ */
+
+/** Pointer to a Virtual USB device (core). */
+typedef struct VUSBDEV *PVUSBDEV;
+/** Pointer to a VUSB root hub. */
+typedef struct VUSBROOTHUB *PVUSBROOTHUB;
+
+
+/** Number of the default control endpoint */
+#define VUSB_PIPE_DEFAULT 0
+
+/** @name Device addresses
+ * @{ */
+#define VUSB_DEFAULT_ADDRESS 0
+#define VUSB_INVALID_ADDRESS UINT8_C(0xff)
+/** @} */
+
+/** @name Feature bits (1<<FEATURE for the u16Status bit)
+ * @{ */
+#define VUSB_DEV_SELF_POWERED 0
+#define VUSB_DEV_REMOTE_WAKEUP 1
+#define VUSB_EP_HALT 0
+/** @} */
+
+/** Maximum number of endpoint addresses */
+#define VUSB_PIPE_MAX 16
+
+/**
+ * The VUSB URB data.
+ */
+typedef struct VUSBURBVUSBINT
+{
+ /** Node for one of the lists the URB can be in. */
+ RTLISTNODE NdLst;
+ /** Pointer to the URB this structure is part of. */
+ PVUSBURB pUrb;
+ /** Pointer to the original for control messages. */
+ PVUSBURB pCtrlUrb;
+ /** Pointer to the VUSB device.
+ * This may be NULL if the destination address is invalid. */
+ PVUSBDEV pDev;
+ /** Specific to the pfnFree function. */
+ void *pvFreeCtx;
+ /**
+ * Callback which will free the URB once it's reaped and completed.
+ * @param pUrb The URB.
+ */
+ DECLCALLBACKMEMBER(void, pfnFree,(PVUSBURB pUrb));
+ /** Submit timestamp. (logging only) */
+ uint64_t u64SubmitTS;
+} VUSBURBVUSBINT;
+
+/**
+ * Control-pipe stages.
+ */
+typedef enum CTLSTAGE
+{
+ /** the control pipe is in the setup stage. */
+ CTLSTAGE_SETUP = 0,
+ /** the control pipe is in the data stage. */
+ CTLSTAGE_DATA,
+ /** the control pipe is in the status stage. */
+ CTLSTAGE_STATUS
+} CTLSTAGE;
+
+/**
+ * Extra data for a control pipe.
+ *
+ * This is state information needed for the special multi-stage
+ * transfers performed on this kind of pipes.
+ */
+typedef struct vusb_ctrl_extra
+{
+ /** Current pipe stage. */
+ CTLSTAGE enmStage;
+ /** Success indicator. */
+ bool fOk;
+ /** Set if the message URB has been submitted. */
+ bool fSubmitted;
+ /** Pointer to the SETUP.
+ * This is a pointer to Urb->abData[0]. */
+ PVUSBSETUP pMsg;
+ /** Current DATA pointer.
+ * This starts at pMsg + 1 and is incremented at we read/write data. */
+ uint8_t *pbCur;
+ /** The amount of data left to read on IN operations.
+ * On OUT operations this is not used. */
+ uint32_t cbLeft;
+ /** The amount of data we can house.
+ * This starts at the default 8KB, and this structure will be reallocated to
+ * accommodate any larger request (unlikely). */
+ uint32_t cbMax;
+ /** VUSB internal data for the extra URB. */
+ VUSBURBVUSBINT VUsbExtra;
+ /** The message URB. */
+ VUSBURB Urb;
+} VUSBCTRLEXTRA, *PVUSBCTRLEXTRA;
+
+void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra);
+void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra);
+
+/**
+ * A VUSB pipe
+ */
+typedef struct vusb_pipe
+{
+ PCVUSBDESCENDPOINTEX in;
+ PCVUSBDESCENDPOINTEX out;
+ /** Pointer to the extra state data required to run a control pipe. */
+ PVUSBCTRLEXTRA pCtrl;
+ /** Critical section serializing access to the extra state data for a control pipe. */
+ RTCRITSECT CritSectCtrl;
+ /** Count of active async transfers. */
+ volatile uint32_t async;
+ /** Last scheduled frame - only valid for isochronous IN endpoints. */
+ uint32_t uLastFrameIn;
+ /** Last scheduled frame - only valid for isochronous OUT endpoints. */
+ uint32_t uLastFrameOut;
+} VUSBPIPE;
+/** Pointer to a VUSB pipe structure. */
+typedef VUSBPIPE *PVUSBPIPE;
+
+
+/**
+ * Interface state and possible settings.
+ */
+typedef struct vusb_interface_state
+{
+ /** Pointer to the interface descriptor of the currently selected (active)
+ * interface. */
+ PCVUSBDESCINTERFACEEX pCurIfDesc;
+ /** Pointer to the interface settings. */
+ PCVUSBINTERFACE pIf;
+} VUSBINTERFACESTATE;
+/** Pointer to interface state. */
+typedef VUSBINTERFACESTATE *PVUSBINTERFACESTATE;
+/** Pointer to const interface state. */
+typedef const VUSBINTERFACESTATE *PCVUSBINTERFACESTATE;
+
+
+/**
+ * VUSB URB pool.
+ */
+typedef struct VUSBURBPOOL
+{
+ /** Critical section protecting the pool. */
+ RTCRITSECT CritSectPool;
+ /** Chain of free URBs by type. (Singly linked) */
+ RTLISTANCHOR aLstFreeUrbs[VUSBXFERTYPE_ELEMENTS];
+ /** The number of URBs in the pool. */
+ volatile uint32_t cUrbsInPool;
+ /** Align the size to a 8 byte boundary. */
+ uint32_t Alignment0;
+} VUSBURBPOOL;
+/** Pointer to a VUSB URB pool. */
+typedef VUSBURBPOOL *PVUSBURBPOOL;
+
+AssertCompileSizeAlignment(VUSBURBPOOL, 8);
+
+/**
+ * A Virtual USB device (core).
+ *
+ * @implements VUSBIDEVICE
+ */
+typedef struct VUSBDEV
+{
+ /** The device interface exposed to the HCI. */
+ VUSBIDEVICE IDevice;
+ /** Pointer to the PDM USB device instance. */
+ PPDMUSBINS pUsbIns;
+ /** Pointer to the roothub this device is attached to. */
+ PVUSBROOTHUB pHub;
+ /** The device state. */
+ VUSBDEVICESTATE volatile enmState;
+ /** Reference counter to protect the device structure from going away. */
+ uint32_t volatile cRefs;
+
+ /** The device address. */
+ uint8_t u8Address;
+ /** The new device address. */
+ uint8_t u8NewAddress;
+ /** The port. */
+ int16_t i16Port;
+ /** Device status. (VUSB_DEV_SELF_POWERED or not.) */
+ uint16_t u16Status;
+
+ /** Pointer to the descriptor cache.
+ * (Provided by the device thru the pfnGetDescriptorCache method.) */
+ PCPDMUSBDESCCACHE pDescCache;
+ /** Current configuration. */
+ PCVUSBDESCCONFIGEX pCurCfgDesc;
+
+ /** Current interface state (including alternate interface setting) - maximum
+ * valid index is config->bNumInterfaces
+ */
+ PVUSBINTERFACESTATE paIfStates;
+
+ /** Pipe/direction -> endpoint descriptor mapping */
+ VUSBPIPE aPipes[VUSB_PIPE_MAX];
+ /** Critical section protecting the active URB list. */
+ RTCRITSECT CritSectAsyncUrbs;
+ /** List of active async URBs. */
+ RTLISTANCHOR LstAsyncUrbs;
+
+ /** Dumper state. */
+ union VUSBDEVURBDUMPERSTATE
+ {
+ /** The current scsi command. */
+ uint8_t u8ScsiCmd;
+ } Urb;
+
+ /** The reset timer handle. */
+ TMTIMERHANDLE hResetTimer;
+ /** Reset handler arguments. */
+ void *pvArgs;
+ /** URB submit and reap thread. */
+ RTTHREAD hUrbIoThread;
+ /** Request queue for executing tasks on the I/O thread which should be done
+ * synchronous and without any other thread accessing the USB device. */
+ RTREQQUEUE hReqQueueSync;
+ /** Sniffer instance for this device if configured. */
+ VUSBSNIFFER hSniffer;
+ /** Flag whether the URB I/O thread should terminate. */
+ bool volatile fTerminate;
+ /** Flag whether the I/O thread was woken up. */
+ bool volatile fWokenUp;
+#if HC_ARCH_BITS == 32
+ /** Align the size to a 8 byte boundary. */
+ bool afAlignment0[2];
+#endif
+ /** The pool of free URBs for faster allocation. */
+ VUSBURBPOOL UrbPool;
+} VUSBDEV;
+AssertCompileSizeAlignment(VUSBDEV, 8);
+
+
+int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename);
+void vusbDevDestroy(PVUSBDEV pDev);
+bool vusbDevDoSelectConfig(PVUSBDEV dev, PCVUSBDESCCONFIGEX pCfg);
+void vusbDevMapEndpoint(PVUSBDEV dev, PCVUSBDESCENDPOINTEX ep);
+int vusbDevDetach(PVUSBDEV pDev);
+int vusbDevAttach(PVUSBDEV pDev, PVUSBROOTHUB pHub);
+DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev);
+size_t vusbDevMaxInterfaces(PVUSBDEV dev);
+
+void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address);
+bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf);
+
+
+/** @} */
+
+
+/** @defgroup grp_vusb_int_hub Internal Hub Operations, Structures and Constants.
+ * @{
+ */
+
+
+/** @} */
+
+
+/** @defgroup grp_vusb_int_roothub Internal Root Hub Operations, Structures and Constants.
+ * @{
+ */
+
+/**
+ * Per transfer type statistics.
+ */
+typedef struct VUSBROOTHUBTYPESTATS
+{
+ STAMCOUNTER StatUrbsSubmitted;
+ STAMCOUNTER StatUrbsFailed;
+ STAMCOUNTER StatUrbsCancelled;
+
+ STAMCOUNTER StatReqBytes;
+ STAMCOUNTER StatReqReadBytes;
+ STAMCOUNTER StatReqWriteBytes;
+
+ STAMCOUNTER StatActBytes;
+ STAMCOUNTER StatActReadBytes;
+ STAMCOUNTER StatActWriteBytes;
+} VUSBROOTHUBTYPESTATS, *PVUSBROOTHUBTYPESTATS;
+
+
+
+/** Pointer to a VUSBROOTHUBLOAD struct. */
+typedef struct VUSBROOTHUBLOAD *PVUSBROOTHUBLOAD;
+
+/**
+ * The instance data of a root hub driver.
+ *
+ * This extends the generic VUSB hub.
+ *
+ * @implements VUSBIROOTHUBCONNECTOR
+ */
+typedef struct VUSBROOTHUB
+{
+ /** Pointer to the driver instance. */
+ PPDMDRVINS pDrvIns;
+ /** Pointer to the root hub port interface we're attached to. */
+ PVUSBIROOTHUBPORT pIRhPort;
+ /** Connector interface exposed upwards. */
+ VUSBIROOTHUBCONNECTOR IRhConnector;
+
+ /** Critical section protecting the device arrays. */
+ RTCRITSECT CritSectDevices;
+ /** Array of pointers to USB devices indexed by the port the device is on. */
+ PVUSBDEV apDevByPort[VUSB_DEVICES_MAX];
+ /** Array of pointers to USB devices indexed by the address assigned. */
+ PVUSBDEV apDevByAddr[VUSB_DEVICES_MAX];
+ /** Structure after a saved state load to re-attach devices. */
+ PVUSBROOTHUBLOAD pLoad;
+
+ /** Roothub device state. */
+ VUSBDEVICESTATE enmState;
+ /** Number of ports this roothub offers. */
+ uint16_t cPorts;
+ /** Number of devices attached to this roothub currently. */
+ uint16_t cDevices;
+ /** Name of the roothub. Used for logging. */
+ char *pszName;
+ /** URB pool for URBs from the roothub. */
+ VUSBURBPOOL UrbPool;
+
+#if HC_ARCH_BITS == 32
+ uint32_t Alignment0;
+#endif
+
+ /** Availability Bitmap. */
+ VUSBPORTBITMAP Bitmap;
+
+ /** Sniffer instance for the root hub. */
+ VUSBSNIFFER hSniffer;
+ /** Version of the attached Host Controller. */
+ uint32_t fHcVersions;
+ /** Size of the HCI specific data for each URB. */
+ size_t cbHci;
+ /** Size of the HCI specific TD. */
+ size_t cbHciTd;
+
+ /** The periodic frame processing thread. */
+ R3PTRTYPE(PPDMTHREAD) hThreadPeriodFrame;
+ /** Event semaphore to interact with the periodic frame processing thread. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrame;
+ /** Event semaphore to release the thread waiting for the periodic frame processing thread to stop. */
+ R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrameStopped;
+ /** Current default frame rate for periodic frame processing thread. */
+ volatile uint32_t uFrameRateDefault;
+ /** Current frame rate (can be lower than the default frame rate if there is no activity). */
+ uint32_t uFrameRate;
+ /** How long to wait until the next frame. */
+ uint64_t nsWait;
+ /** Timestamp when the last frame was processed. */
+ uint64_t tsFrameProcessed;
+ /** Number of USB work cycles with no transfers. */
+ uint32_t cIdleCycles;
+
+ /** Flag whether a frame is currently being processed. */
+ volatile bool fFrameProcessing;
+
+#if HC_ARCH_BITS == 32
+ uint32_t Alignment1;
+#endif
+
+#ifdef LOG_ENABLED
+ /** A serial number for URBs submitted on the roothub instance.
+ * Only logging builds. */
+ uint32_t iSerial;
+ /** Alignment */
+ uint32_t Alignment2;
+#endif
+#ifdef VBOX_WITH_STATISTICS
+ VUSBROOTHUBTYPESTATS Total;
+ VUSBROOTHUBTYPESTATS aTypes[VUSBXFERTYPE_MSG];
+ STAMCOUNTER StatIsocReqPkts;
+ STAMCOUNTER StatIsocReqReadPkts;
+ STAMCOUNTER StatIsocReqWritePkts;
+ STAMCOUNTER StatIsocActPkts;
+ STAMCOUNTER StatIsocActReadPkts;
+ STAMCOUNTER StatIsocActWritePkts;
+ struct
+ {
+ STAMCOUNTER Pkts;
+ STAMCOUNTER Ok;
+ STAMCOUNTER Ok0;
+ STAMCOUNTER DataUnderrun;
+ STAMCOUNTER DataUnderrun0;
+ STAMCOUNTER DataOverrun;
+ STAMCOUNTER NotAccessed;
+ STAMCOUNTER Misc;
+ STAMCOUNTER Bytes;
+ } aStatIsocDetails[8];
+
+ STAMPROFILE StatReapAsyncUrbs;
+ STAMPROFILE StatSubmitUrb;
+ STAMCOUNTER StatFramesProcessedClbk;
+ STAMCOUNTER StatFramesProcessedThread;
+#endif
+} VUSBROOTHUB;
+AssertCompileMemberAlignment(VUSBROOTHUB, IRhConnector, 8);
+AssertCompileMemberAlignment(VUSBROOTHUB, Bitmap, 8);
+AssertCompileMemberAlignment(VUSBROOTHUB, CritSectDevices, 8);
+#ifdef VBOX_WITH_STATISTICS
+AssertCompileMemberAlignment(VUSBROOTHUB, Total, 8);
+#endif
+
+/** Converts a pointer to VUSBROOTHUB::IRhConnector to a PVUSBROOTHUB. */
+#define VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface) (PVUSBROOTHUB)( (uintptr_t)(pInterface) - RT_UOFFSETOF(VUSBROOTHUB, IRhConnector) )
+
+/**
+ * URB cancellation modes
+ */
+typedef enum CANCELMODE
+{
+ /** complete the URB with an error (CRC). */
+ CANCELMODE_FAIL = 0,
+ /** do not change the URB contents. */
+ CANCELMODE_UNDO
+} CANCELMODE;
+
+/** @} */
+
+
+
+/** @defgroup grp_vusb_int_urb Internal URB Operations, Structures and Constants.
+ * @{ */
+int vusbUrbSubmit(PVUSBURB pUrb);
+void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies);
+void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies);
+void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode);
+void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode);
+void vusbUrbRipe(PVUSBURB pUrb);
+void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb);
+int vusbUrbSubmitHardError(PVUSBURB pUrb);
+int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb);
+int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev);
+int vusbDevUrbIoThreadCreate(PVUSBDEV pDev);
+int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev);
+DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching);
+DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args);
+DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...);
+DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...);
+DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode);
+
+DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback);
+
+int vusbUrbQueueAsyncRh(PVUSBURB pUrb);
+
+bool vusbDevIsDescriptorInCache(PVUSBDEV pDev, PCVUSBSETUP pSetup);
+
+/**
+ * Initializes the given URB pool.
+ *
+ * @returns VBox status code.
+ * @param pUrbPool The URB pool to initialize.
+ */
+DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool);
+
+/**
+ * Destroy a given URB pool freeing all ressources.
+ *
+ * @returns nothing.
+ * @param pUrbPool The URB pool to destroy.
+ */
+DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool);
+
+/**
+ * Allocate a new URB from the given URB pool.
+ *
+ * @returns Pointer to the new URB or NULL if out of memory.
+ * @param pUrbPool The URB pool to allocate from.
+ * @param enmType Type of the URB.
+ * @param enmDir The direction of the URB.
+ * @param cbData The number of bytes to allocate for the data buffer.
+ * @param cbHci Size of the data private to the HCI for each URB when allocated.
+ * @param cbHciTd Size of one transfer descriptor.
+ * @param cTds Number of transfer descriptors.
+ */
+DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, size_t cbData,
+ size_t cbHci, size_t cbHciTd, unsigned cTds);
+
+/**
+ * Frees a given URB.
+ *
+ * @returns nothing.
+ * @param pUrbPool The URB pool the URB was allocated from.
+ * @param pUrb The URB to free.
+ */
+DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb);
+
+#ifdef LOG_ENABLED
+/**
+ * Logs an URB in the debug log.
+ *
+ * @returns nothing.
+ * @param pUrb The URB to log.
+ * @param pszMsg Additional message to log.
+ * @param fComplete Flag whther the URB is completing.
+ */
+DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete);
+
+/**
+ * Return the USB direction as a string from the given enum.
+ */
+DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir);
+
+/**
+ * Return the URB type as string from the given enum.
+ */
+DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType);
+
+/**
+ * Return the URB status as string from the given enum.
+ */
+DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus);
+#endif
+
+DECLINLINE(void) vusbUrbUnlink(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+
+ RTCritSectEnter(&pDev->CritSectAsyncUrbs);
+ RTListNodeRemove(&pUrb->pVUsb->NdLst);
+ RTCritSectLeave(&pDev->CritSectAsyncUrbs);
+}
+
+
+DECLINLINE(int) vusbUrbErrorRh(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED);
+
+ return vusbUrbErrorRhEx(pRh, pUrb);
+}
+
+
+DECLINLINE(void) vusbUrbCompletionRh(PVUSBURB pUrb)
+{
+ PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev);
+ AssertPtrReturnVoid(pRh);
+
+ vusbUrbCompletionRhEx(pRh, pUrb);
+}
+
+
+/** @def vusbUrbAssert
+ * Asserts that a URB is valid.
+ */
+#ifdef VBOX_STRICT
+# define vusbUrbAssert(pUrb) do { \
+ AssertPtr((pUrb)); \
+ AssertMsg((pUrb)->u32Magic == VUSBURB_MAGIC, ("%#x", (pUrb)->u32Magic)); \
+ AssertMsg((pUrb)->enmState > VUSBURBSTATE_INVALID && (pUrb)->enmState < VUSBURBSTATE_END, \
+ ("%d\n", (pUrb)->enmState)); \
+ } while (0)
+#else
+# define vusbUrbAssert(pUrb) do {} while (0)
+#endif
+
+/**
+ * @def VUSBDEV_ASSERT_VALID_STATE
+ * Asserts that the give device state is valid.
+ */
+#define VUSBDEV_ASSERT_VALID_STATE(enmState) \
+ AssertMsg((enmState) > VUSB_DEVICE_STATE_INVALID && (enmState) < VUSB_DEVICE_STATE_DESTROYED, ("enmState=%#x\n", enmState));
+
+/** Executes a function synchronously. */
+#define VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC RT_BIT_32(0)
+
+/** @} */
+
+
+/**
+ * Gets the roothub of a device.
+ *
+ * @returns Pointer to the roothub instance the device is attached to.
+ * @returns NULL if not attached to any hub.
+ * @param pDev Pointer to the device in question.
+ */
+DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev)
+{
+ if (!pDev->pHub)
+ return NULL;
+ return pDev->pHub;
+}
+
+
+/**
+ * Returns the state of the USB device.
+ *
+ * @returns State of the USB device.
+ * @param pDev Pointer to the device.
+ */
+DECLINLINE(VUSBDEVICESTATE) vusbDevGetState(PVUSBDEV pDev)
+{
+ VUSBDEVICESTATE enmState = (VUSBDEVICESTATE)ASMAtomicReadU32((volatile uint32_t *)&pDev->enmState);
+ VUSBDEV_ASSERT_VALID_STATE(enmState);
+ return enmState;
+}
+
+
+/**
+ * Sets the given state for the USB device.
+ *
+ * @returns The old state of the device.
+ * @param pDev Pointer to the device.
+ * @param enmState The new state to set.
+ */
+DECLINLINE(VUSBDEVICESTATE) vusbDevSetState(PVUSBDEV pDev, VUSBDEVICESTATE enmState)
+{
+ VUSBDEV_ASSERT_VALID_STATE(enmState);
+ VUSBDEVICESTATE enmStateOld = (VUSBDEVICESTATE)ASMAtomicXchgU32((volatile uint32_t *)&pDev->enmState, enmState);
+ VUSBDEV_ASSERT_VALID_STATE(enmStateOld);
+ return enmStateOld;
+}
+
+
+/**
+ * Compare and exchange the states for the given USB device.
+ *
+ * @returns true if the state was changed.
+ * @returns false if the state wasn't changed.
+ * @param pDev Pointer to the device.
+ * @param enmStateNew The new state to set.
+ * @param enmStateOld The old state to compare with.
+ */
+DECLINLINE(bool) vusbDevSetStateCmp(PVUSBDEV pDev, VUSBDEVICESTATE enmStateNew, VUSBDEVICESTATE enmStateOld)
+{
+ VUSBDEV_ASSERT_VALID_STATE(enmStateNew);
+ VUSBDEV_ASSERT_VALID_STATE(enmStateOld);
+ return ASMAtomicCmpXchgU32((volatile uint32_t *)&pDev->enmState, enmStateNew, enmStateOld);
+}
+
+/**
+ * Retains the given VUSB device pointer.
+ *
+ * @returns New reference count.
+ * @param pThis The VUSB device pointer.
+ * @param pszWho Caller of the retaining.
+ */
+DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis, const char *pszWho)
+{
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs);
+ LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho);
+ AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ return cRefs;
+}
+
+/**
+ * Releases the given VUSB device pointer.
+ *
+ * @returns New reference count.
+ * @retval 0 if no onw is holding a reference anymore causing the device to be destroyed.
+ * @param pThis The VUSB device pointer.
+ * @param pszWho Caller of the retaining.
+ */
+DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis, const char *pszWho)
+{
+ AssertPtrReturn(pThis, UINT32_MAX);
+
+ uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs);
+ LogFlowFunc(("pThis=%p{.cRefs=%u}[%s]\n", pThis, cRefs, pszWho)); RT_NOREF(pszWho);
+ AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis));
+ if (cRefs == 0)
+ vusbDevDestroy(pThis);
+ return cRefs;
+}
+
+/** Strings for the CTLSTAGE enum values. */
+extern const char * const g_apszCtlStates[4];
+
+/** @} */
+RT_C_DECLS_END
+#endif /* !VBOX_INCLUDED_SRC_USB_VUSBInternal_h */
+
diff --git a/src/VBox/Devices/USB/VUSBSniffer.cpp b/src/VBox/Devices/USB/VUSBSniffer.cpp
new file mode 100644
index 00000000..96282294
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSniffer.cpp
@@ -0,0 +1,247 @@
+/* $Id: VUSBSniffer.cpp $ */
+/** @file
+ * Virtual USB - Sniffer facility.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/log.h>
+#include <iprt/file.h>
+#include <iprt/errcore.h>
+#include <iprt/path.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/time.h>
+
+#include "VUSBSnifferInternal.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal VUSB sniffer state.
+ */
+typedef struct VUSBSNIFFERINT
+{
+ /** The file handle to dump to. */
+ RTFILE hFile;
+ /** Fast Mutex protecting the state against concurrent access. */
+ RTSEMFASTMUTEX hMtx;
+ /** File stream. */
+ VUSBSNIFFERSTRM Strm;
+ /** Pointer to the used format. */
+ PCVUSBSNIFFERFMT pFmt;
+ /** Format specific state - variable in size. */
+ uint8_t abFmt[1];
+} VUSBSNIFFERINT;
+/** Pointer to the internal VUSB sniffer state. */
+typedef VUSBSNIFFERINT *PVUSBSNIFFERINT;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+static PCVUSBSNIFFERFMT s_aVUsbSnifferFmts[] =
+{
+ &g_VUsbSnifferFmtPcapNg,
+ &g_VUsbSnifferFmtUsbMon,
+ &g_VUsbSnifferFmtVmx,
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/** @interface_method_impl{VUSBSNIFFERSTRM,pfnWrite} */
+static DECLCALLBACK(int) vusbSnifferStrmWrite(PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf)
+{
+ PVUSBSNIFFERINT pThis = RT_FROM_MEMBER(pStrm, VUSBSNIFFERINT, Strm);
+
+ return RTFileWrite(pThis->hFile, pvBuf, cbBuf, NULL);
+}
+
+/**
+ * Returns a supporting format writer taken from the given format name.
+ *
+ * @returns Pointer to the format structure or NULL if none was found.
+ * @param pszFmt The format to use.
+ */
+static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromString(const char *pszFmt)
+{
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++)
+ {
+ if (!RTStrICmp(pszFmt, s_aVUsbSnifferFmts[i]->szName))
+ return s_aVUsbSnifferFmts[i];
+ }
+
+ return NULL;
+}
+
+/**
+ * Returns a supporting format writer taken from the file suffix.
+ *
+ * @returns Pointer to the format structure or NULL if none was found.
+ * @param pszFilename The file name to take the suffix from.
+ */
+static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromFilename(const char *pszFilename)
+{
+ const char *pszFileExt = RTPathSuffix(pszFilename);
+ if (!pszFileExt)
+ return NULL;
+
+ pszFileExt++; /* Skip the dot. */
+
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++)
+ {
+ unsigned idxFileExt = 0;
+
+ while (s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt])
+ {
+ if (!RTStrICmp(pszFileExt, s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt]))
+ return s_aVUsbSnifferFmts[i];
+
+ idxFileExt++;
+ }
+ }
+
+ return NULL;
+}
+
+
+DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags,
+ const char *pszCaptureFilename, const char *pszFmt,
+ const char *pszDesc)
+{
+ RT_NOREF(pszDesc);
+ int rc = VINF_SUCCESS;
+ PVUSBSNIFFERINT pThis = NULL;
+ PCVUSBSNIFFERFMT pFmt = NULL;
+
+ if (pszFmt)
+ pFmt = vusbSnifferGetFmtFromString(pszFmt);
+ else
+ pFmt = vusbSnifferGetFmtFromFilename(pszCaptureFilename);
+
+ if (!pFmt)
+ return VERR_NOT_FOUND;
+
+ pThis = (PVUSBSNIFFERINT)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBSNIFFERINT, abFmt[pFmt->cbFmt]));
+ if (pThis)
+ {
+ pThis->hFile = NIL_RTFILE;
+ pThis->hMtx = NIL_RTSEMFASTMUTEX;
+ pThis->pFmt = pFmt;
+ pThis->Strm.pfnWrite = vusbSnifferStrmWrite;
+
+ rc = RTSemFastMutexCreate(&pThis->hMtx);
+ if (RT_SUCCESS(rc))
+ {
+ uint32_t fFileFlags = RTFILE_O_DENY_NONE | RTFILE_O_WRITE | RTFILE_O_READ;
+ if (fFlags & VUSBSNIFFER_F_NO_REPLACE)
+ fFileFlags |= RTFILE_O_CREATE;
+ else
+ fFileFlags |= RTFILE_O_CREATE_REPLACE;
+
+ rc = RTFileOpen(&pThis->hFile, pszCaptureFilename, fFileFlags);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pThis->pFmt->pfnInit((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], &pThis->Strm);
+ if (RT_SUCCESS(rc))
+ {
+ *phSniffer = pThis;
+ return VINF_SUCCESS;
+ }
+
+ RTFileClose(pThis->hFile);
+ pThis->hFile = NIL_RTFILE;
+ RTFileDelete(pszCaptureFilename);
+ }
+ RTSemFastMutexDestroy(pThis->hMtx);
+ pThis->hMtx = NIL_RTSEMFASTMUTEX;
+ }
+
+ RTMemFree(pThis);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Destroys the given VUSB sniffer instance.
+ *
+ * @returns nothing.
+ * @param hSniffer The sniffer instance to destroy.
+ */
+DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer)
+{
+ PVUSBSNIFFERINT pThis = hSniffer;
+
+ int rc = RTSemFastMutexRequest(pThis->hMtx);
+ AssertRC(rc);
+
+ pThis->pFmt->pfnDestroy((PVUSBSNIFFERFMTINT)&pThis->abFmt[0]);
+
+ if (pThis->hFile != NIL_RTFILE)
+ RTFileClose(pThis->hFile);
+
+ RTSemFastMutexRelease(pThis->hMtx);
+ RTSemFastMutexDestroy(pThis->hMtx);
+ RTMemFree(pThis);
+}
+
+/**
+ * Records an VUSB event.
+ *
+ * @returns VBox status code.
+ * @param hSniffer The sniffer instance.
+ * @param pUrb The URB triggering the event.
+ * @param enmEvent The type of event to record.
+ */
+DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)
+{
+ int rc = VINF_SUCCESS;
+ PVUSBSNIFFERINT pThis = hSniffer;
+
+ /* Write the packet to the capture file. */
+ rc = RTSemFastMutexRequest(pThis->hMtx);
+ if (RT_SUCCESS(rc))
+ {
+ rc = pThis->pFmt->pfnRecordEvent((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], pUrb, enmEvent);
+ RTSemFastMutexRelease(pThis->hMtx);
+ }
+
+ return rc;
+}
+
diff --git a/src/VBox/Devices/USB/VUSBSniffer.h b/src/VBox/Devices/USB/VUSBSniffer.h
new file mode 100644
index 00000000..30d21301
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSniffer.h
@@ -0,0 +1,111 @@
+/* $Id: VUSBSniffer.h $ */
+/** @file
+ * Virtual USB - Sniffer facility.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_USB_VUSBSniffer_h
+#define VBOX_INCLUDED_SRC_USB_VUSBSniffer_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <VBox/types.h>
+#include <VBox/vusb.h>
+
+RT_C_DECLS_BEGIN
+
+/** Opaque VUSB sniffer handle. */
+typedef struct VUSBSNIFFERINT *VUSBSNIFFER;
+/** Pointer to a VUSB sniffer handle. */
+typedef VUSBSNIFFER *PVUSBSNIFFER;
+
+/** NIL sniffer instance handle. */
+#define VUSBSNIFFER_NIL ((VUSBSNIFFER)0)
+
+/**
+ * VUSB Sniffer event types.
+ */
+typedef enum VUSBSNIFFEREVENT
+{
+ /** Invalid event. */
+ VUSBSNIFFEREVENT_INVALID = 0,
+ /** URB submit event. */
+ VUSBSNIFFEREVENT_SUBMIT,
+ /** URB complete event. */
+ VUSBSNIFFEREVENT_COMPLETE,
+ /** URB submit failed event. */
+ VUSBSNIFFEREVENT_ERROR_SUBMIT,
+ /** URB completed with error event. */
+ VUSBSNIFFEREVENT_ERROR_COMPLETE,
+ /** 32bit hack. */
+ VUSBSNIFFEREVENT_32BIT_HACK = 0x7fffffff
+} VUSBSNIFFEREVENT;
+
+/** VUSB Sniffer creation flags.
+ * @{ */
+/** Default flags. */
+#define VUSBSNIFFER_F_DEFAULT 0
+/** Don't overwrite any existing capture file. */
+#define VUSBSNIFFER_F_NO_REPLACE RT_BIT_32(0)
+/** @} */
+
+/**
+ * Create a new VUSB sniffer instance dumping to the given capture file.
+ *
+ * @returns VBox status code.
+ * @param phSniffer Where to store the handle to the sniffer instance on success.
+ * @param fFlags Flags, combination of VUSBSNIFFER_F_*
+ * @param pszCaptureFilename The filename to use for capturing the sniffed data.
+ * @param pszFmt The format of the dump, NULL to select one based on the filename
+ * extension.
+ * @param pszDesc Optional description for the dump.
+ */
+DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags,
+ const char *pszCaptureFilename, const char *pszFmt,
+ const char *pszDesc);
+
+/**
+ * Destroys the given VUSB sniffer instance.
+ *
+ * @returns nothing.
+ * @param hSniffer The sniffer instance to destroy.
+ */
+DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer);
+
+/**
+ * Records an VUSB event.
+ *
+ * @returns VBox status code.
+ * @param hSniffer The sniffer instance.
+ * @param pUrb The URB triggering the event.
+ * @param enmEvent The type of event to record.
+ */
+DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent);
+
+
+RT_C_DECLS_END
+#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSniffer_h */
+
diff --git a/src/VBox/Devices/USB/VUSBSnifferInternal.h b/src/VBox/Devices/USB/VUSBSnifferInternal.h
new file mode 100644
index 00000000..feed9a1d
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferInternal.h
@@ -0,0 +1,119 @@
+/* $Id: VUSBSnifferInternal.h $ */
+/** @file
+ * Virtual USB Sniffer facility - Internal header.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#ifndef VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h
+#define VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h
+#ifndef RT_WITHOUT_PRAGMA_ONCE
+# pragma once
+#endif
+
+#include <VBox/cdefs.h>
+#include <VBox/types.h>
+
+#include "VUSBSniffer.h"
+
+RT_C_DECLS_BEGIN
+
+/** Pointer to a stream operations structure. */
+typedef struct VUSBSNIFFERSTRM *PVUSBSNIFFERSTRM;
+/** Pointer to the internal format specific state. */
+typedef struct VUSBSNIFFERFMTINT *PVUSBSNIFFERFMTINT;
+
+/**
+ * Stream operations structure.
+ */
+typedef struct VUSBSNIFFERSTRM
+{
+ /**
+ * Write the given buffer to the underlying stream.
+ *
+ * @returns VBox status code.
+ * @param pStrm The pointer to the interface containing this method.
+ * @param pvBuf The buffer to write.
+ * @param cbBuf How many bytes to write.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnWrite, (PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf));
+
+} VUSBSNIFFERSTRM;
+
+
+/**
+ * VUSB Sniffer format backend.
+ */
+typedef struct VUSBSNIFFERFMT
+{
+ /** Name of the format. */
+ char szName[16];
+ /** Description of the format. */
+ const char *pszDesc;
+ /** Supported file extensions - terminated with a NULL entry. */
+ const char **papszFileExts;
+ /** Size of the format specific state structure in bytes. */
+ size_t cbFmt;
+
+ /**
+ * Initializes the format.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the unitialized format specific state.
+ * @param pStrm The stream to use for writing.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnInit, (PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm));
+
+ /**
+ * Destroys the format instance.
+ *
+ * @returns nothing.
+ * @param pThis Pointer to the format specific state.
+ */
+ DECLR3CALLBACKMEMBER(void, pfnDestroy, (PVUSBSNIFFERFMTINT pThis));
+
+ /**
+ * Records the given VUSB event.
+ *
+ * @returns VBox status code.
+ * @param pThis Pointer to the format specific state.
+ * @param pUrb The URB triggering the event.
+ * @param enmEvent The type of event to record.
+ */
+ DECLR3CALLBACKMEMBER(int, pfnRecordEvent, (PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent));
+
+} VUSBSNIFFERFMT;
+/** Pointer to a VUSB Sniffer format backend. */
+typedef VUSBSNIFFERFMT *PVUSBSNIFFERFMT;
+/** Pointer to a const VUSB Sniffer format backend. */
+typedef const VUSBSNIFFERFMT *PCVUSBSNIFFERFMT;
+
+/** PCAP-Ng format writer. */
+extern const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg;
+/** VMware VMX log format writer. */
+extern const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx;
+/** Linux UsbMon log format writer. */
+extern const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon;
+
+RT_C_DECLS_END
+#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h */
diff --git a/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp
new file mode 100644
index 00000000..7f9877cb
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp
@@ -0,0 +1,741 @@
+/* $Id: VUSBSnifferPcapNg.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - PCAP-NG format writer.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/log.h>
+#include <iprt/buildconfig.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+
+#include "VUSBSnifferInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** DumpFile Section Header Block type. */
+#define DUMPFILE_SHB_BLOCK_TYPE UINT32_C(0x0a0d0d0a)
+/** The byte order magic value. */
+#define DUMPFILE_SHB_BYTE_ORDER_MAGIC UINT32_C(0x1a2b3c4d)
+/** Current major version. */
+#define DUMPFILE_SHB_VERSION_MAJOR UINT16_C(1)
+/** Current minor version. */
+#define DUMPFILE_SHB_VERSION_MINOR UINT16_C(0)
+
+/** Block type for the interface descriptor block. */
+#define DUMPFILE_IDB_BLOCK_TYPE UINT32_C(0x00000001)
+/** USB link type. */
+#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX UINT16_C(189)
+#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED UINT16_C(220)
+
+/** Block type for an enhanced packet block. */
+#define DUMPFILE_EPB_BLOCK_TYPE UINT32_C(0x00000006)
+
+/** USB packet event types. */
+#define DUMPFILE_USB_EVENT_TYPE_SUBMIT ('S')
+#define DUMPFILE_USB_EVENT_TYPE_COMPLETE ('C')
+#define DUMPFILE_USB_EVENT_TYPE_ERROR ('E')
+
+#define DUMPFILE_OPTION_CODE_END UINT16_C(0)
+#define DUMPFILE_OPTION_CODE_COMMENT UINT16_C(1)
+
+#define DUMPFILE_OPTION_CODE_HARDWARE UINT16_C(2)
+#define DUMPFILE_OPTION_CODE_OS UINT16_C(3)
+#define DUMPFILE_OPTION_CODE_USERAPP UINT16_C(4)
+
+#define DUMPFILE_IDB_OPTION_TS_RESOLUTION UINT16_C(9)
+
+
+/*********************************************************************************************************************************
+* DumpFile format structures *
+*********************************************************************************************************************************/
+
+/**
+ * DumpFile Block header.
+ */
+typedef struct DumpFileBlockHdr
+{
+ /** Block type. */
+ uint32_t u32BlockType;
+ /** Block total length. */
+ uint32_t u32BlockTotalLength;
+} DumpFileBlockHdr;
+/** Pointer to a block header. */
+typedef DumpFileBlockHdr *PDumpFileBlockHdr;
+
+/**
+ * DumpFile Option header.
+ */
+typedef struct DumpFileOptionHdr
+{
+ /** Option code. */
+ uint16_t u16OptionCode;
+ /** Block total length. */
+ uint16_t u16OptionLength;
+} DumpFileOptionHdr;
+/** Pointer to a option header. */
+typedef DumpFileOptionHdr *PDumpFileOptionHdr;
+
+/**
+ * DumpFile Section Header Block.
+ */
+typedef struct DumpFileShb
+{
+ /** Block header. */
+ DumpFileBlockHdr Hdr;
+ /** Byte order magic. */
+ uint32_t u32ByteOrderMagic;
+ /** Major version. */
+ uint16_t u16VersionMajor;
+ /** Minor version. */
+ uint16_t u16VersionMinor;
+ /** Section length. */
+ uint64_t u64SectionLength;
+} DumpFileShb;
+/** Pointer to a Section Header Block. */
+typedef DumpFileShb *PDumpFileShb;
+
+/**
+ * DumpFile Interface description block.
+ */
+typedef struct DumpFileIdb
+{
+ /** Block header. */
+ DumpFileBlockHdr Hdr;
+ /** Link type. */
+ uint16_t u16LinkType;
+ /** Reserved. */
+ uint16_t u16Reserved;
+ /** Maximum number of bytes dumped from each packet. */
+ uint32_t u32SnapLen;
+} DumpFileIdb;
+/** Pointer to an Interface description block. */
+typedef DumpFileIdb *PDumpFileIdb;
+
+/**
+ * DumpFile Enhanced packet block.
+ */
+typedef struct DumpFileEpb
+{
+ /** Block header. */
+ DumpFileBlockHdr Hdr;
+ /** Interface ID. */
+ uint32_t u32InterfaceId;
+ /** Timestamp (high). */
+ uint32_t u32TimestampHigh;
+ /** Timestamp (low). */
+ uint32_t u32TimestampLow;
+ /** Captured packet length. */
+ uint32_t u32CapturedLen;
+ /** Original packet length. */
+ uint32_t u32PacketLen;
+} DumpFileEpb;
+/** Pointer to an Enhanced packet block. */
+typedef DumpFileEpb *PDumpFileEpb;
+
+/**
+ * USB setup URB data.
+ */
+typedef struct DumpFileUsbSetup
+{
+ uint8_t bmRequestType;
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+} DumpFileUsbSetup;
+typedef DumpFileUsbSetup *PDumpFileUsbSetup;
+
+/**
+ * USB Isochronous data.
+ */
+typedef struct DumpFileIsoRec
+{
+ int32_t i32ErrorCount;
+ int32_t i32NumDesc;
+} DumpFileIsoRec;
+typedef DumpFileIsoRec *PDumpFileIsoRec;
+
+/**
+ * USB packet header (Linux mmapped variant).
+ */
+typedef struct DumpFileUsbHeaderLnxMmapped
+{
+ /** Packet Id. */
+ uint64_t u64Id;
+ /** Event type. */
+ uint8_t u8EventType;
+ /** Transfer type. */
+ uint8_t u8TransferType;
+ /** Endpoint number. */
+ uint8_t u8EndpointNumber;
+ /** Device address. */
+ uint8_t u8DeviceAddress;
+ /** Bus id. */
+ uint16_t u16BusId;
+ /** Setup flag != 0 if the URB setup header is not present. */
+ uint8_t u8SetupFlag;
+ /** Data present flag != 0 if the URB data is not present. */
+ uint8_t u8DataFlag;
+ /** Timestamp (second part). */
+ uint64_t u64TimestampSec;
+ /** Timestamp (us part). */
+ uint32_t u32TimestampUSec;
+ /** Status. */
+ int32_t i32Status;
+ /** URB length. */
+ uint32_t u32UrbLength;
+ /** Recorded data length. */
+ uint32_t u32DataLength;
+ /** Union of data for different URB types. */
+ union
+ {
+ DumpFileUsbSetup UsbSetup;
+ DumpFileIsoRec IsoRec;
+ } u;
+ int32_t i32Interval;
+ int32_t i32StartFrame;
+ /** Copy of transfer flags. */
+ uint32_t u32XferFlags;
+ /** Number of isochronous descriptors. */
+ uint32_t u32NumDesc;
+} DumpFileUsbHeaderLnxMmapped;
+/** Pointer to a USB packet header. */
+typedef DumpFileUsbHeaderLnxMmapped *PDumpFileUsbHeaderLnxMmapped;
+
+AssertCompileSize(DumpFileUsbHeaderLnxMmapped, 64);
+
+/**
+ * USB packet isochronous descriptor.
+ */
+typedef struct DumpFileUsbIsoDesc
+{
+ int32_t i32Status;
+ uint32_t u32Offset;
+ uint32_t u32Len;
+ uint8_t au8Padding[4];
+} DumpFileUsbIsoDesc;
+typedef DumpFileUsbIsoDesc *PDumpFileUsbIsoDesc;
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal VUSB sniffer state.
+ */
+typedef struct VUSBSNIFFERFMTINT
+{
+ /** Stream handle. */
+ PVUSBSNIFFERSTRM pStrm;
+ /** Current size of the block being written. */
+ uint32_t cbBlockCur;
+ /** Maximum size allocated for the block. */
+ uint32_t cbBlockMax;
+ /** Current block header. */
+ PDumpFileBlockHdr pBlockHdr;
+ /** Pointer to the block data which will be written on commit. */
+ uint8_t *pbBlockData;
+} VUSBSNIFFERFMTINT;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Supported file extensions.
+ */
+static const char *s_apszFileExts[] =
+{
+ "pcap",
+ "pcapng",
+ NULL
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+/**
+ * Allocates additional space for the block.
+ *
+ * @returns Pointer to the new unused space or NULL if out of memory.
+ * @param pThis The VUSB sniffer instance.
+ * @param cbAdditional The additional memory requested.
+ */
+static void *vusbSnifferBlockAllocSpace(PVUSBSNIFFERFMTINT pThis, uint32_t cbAdditional)
+{
+ /* Fast path where we have enough memory allocated. */
+ if (pThis->cbBlockCur + cbAdditional <= pThis->cbBlockMax)
+ {
+ void *pv = pThis->pbBlockData + pThis->cbBlockCur;
+ pThis->cbBlockCur += cbAdditional;
+ return pv;
+ }
+
+ /* Allocate additional memory. */
+ uint32_t cbNew = pThis->cbBlockCur + cbAdditional;
+ uint8_t *pbDataNew = (uint8_t *)RTMemRealloc(pThis->pbBlockData, cbNew);
+ if (pbDataNew)
+ {
+ pThis->pbBlockData = pbDataNew;
+ pThis->pBlockHdr = (PDumpFileBlockHdr)pbDataNew;
+
+ void *pv = pThis->pbBlockData + pThis->cbBlockCur;
+ pThis->cbBlockCur = cbNew;
+ pThis->cbBlockMax = cbNew;
+ return pv;
+ }
+
+ return NULL;
+}
+
+/**
+ * Adds new data to the current block.
+ *
+ * @returns VBox status code.
+ * @param pThis The VUSB sniffer instance.
+ * @param pvData The data to add.
+ * @param cbData Amount of data to add.
+ */
+static int vusbSnifferBlockAddData(PVUSBSNIFFERFMTINT pThis, const void *pvData, uint32_t cbData)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pThis->cbBlockCur);
+ AssertPtr(pThis->pBlockHdr);
+
+ void *pv = vusbSnifferBlockAllocSpace(pThis, cbData);
+ if (pv)
+ memcpy(pv, pvData, cbData);
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Aligns the current block data to a 32bit boundary.
+ *
+ * @returns VBox status code.
+ * @param pThis The VUSB sniffer instance.
+ */
+static int vusbSnifferBlockAlign(PVUSBSNIFFERFMTINT pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ Assert(pThis->cbBlockCur);
+
+ /* Pad to 32bits. */
+ uint8_t abPad[3] = { 0 };
+ uint32_t cbPad = RT_ALIGN_32(pThis->cbBlockCur, 4) - pThis->cbBlockCur;
+
+ Assert(cbPad <= 3);
+ if (cbPad)
+ rc = vusbSnifferBlockAddData(pThis, abPad, cbPad);
+
+ return rc;
+}
+
+/**
+ * Commits the current block to the capture file.
+ *
+ * @returns VBox status code.
+ * @param pThis The VUSB sniffer instance.
+ */
+static int vusbSnifferBlockCommit(PVUSBSNIFFERFMTINT pThis)
+{
+ int rc = VINF_SUCCESS;
+
+ AssertPtr(pThis->pBlockHdr);
+
+ rc = vusbSnifferBlockAlign(pThis);
+ if (RT_SUCCESS(rc))
+ {
+ /* Update the block total length field. */
+ uint32_t *pcbTotalLength = (uint32_t *)vusbSnifferBlockAllocSpace(pThis, 4);
+ if (pcbTotalLength)
+ {
+ *pcbTotalLength = pThis->cbBlockCur;
+ pThis->pBlockHdr->u32BlockTotalLength = pThis->cbBlockCur;
+
+ /* Write the data. */
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, pThis->pbBlockData, pThis->cbBlockCur);
+ pThis->cbBlockCur = 0;
+ pThis->pBlockHdr = NULL;
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ }
+
+ return rc;
+}
+
+/**
+ * Starts a new block for capturing.
+ *
+ * @returns VBox status code.
+ * @param pThis The VUSB sniffer instance.
+ * @param pBlockHdr Pointer to the block header for the new block.
+ * @param cbData Amount of data added with this block.
+ */
+static int vusbSnifferBlockNew(PVUSBSNIFFERFMTINT pThis, PDumpFileBlockHdr pBlockHdr, uint32_t cbData)
+{
+ int rc = VINF_SUCCESS;
+
+ /* Validate we don't get called while another block is active. */
+ Assert(!pThis->cbBlockCur);
+ Assert(!pThis->pBlockHdr);
+ pThis->pBlockHdr = (PDumpFileBlockHdr)vusbSnifferBlockAllocSpace(pThis, cbData);
+ if (pThis->pBlockHdr)
+ memcpy(pThis->pBlockHdr, pBlockHdr, cbData);
+ else
+ rc = VERR_NO_MEMORY;
+
+ return rc;
+}
+
+/**
+ * Add a new option to the current block.
+ *
+ * @returns VBox status code.
+ * @param pThis The VUSB sniffer instance.
+ * @param u16OptionCode The option code identifying the type of option.
+ * @param pvOption Raw data for the option.
+ * @param cbOption Size of the optiob data.
+ */
+static int vusbSnifferAddOption(PVUSBSNIFFERFMTINT pThis, uint16_t u16OptionCode, const void *pvOption, size_t cbOption)
+{
+ AssertStmt((uint16_t)cbOption == cbOption, cbOption = UINT16_MAX);
+ DumpFileOptionHdr OptHdr;
+ OptHdr.u16OptionCode = u16OptionCode;
+ OptHdr.u16OptionLength = (uint16_t)cbOption;
+ int rc = vusbSnifferBlockAddData(pThis, &OptHdr, sizeof(OptHdr));
+ if ( RT_SUCCESS(rc)
+ && u16OptionCode != DUMPFILE_OPTION_CODE_END
+ && cbOption != 0)
+ {
+ rc = vusbSnifferBlockAddData(pThis, pvOption, (uint16_t)cbOption);
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferBlockAlign(pThis);
+ }
+
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */
+static DECLCALLBACK(int) vusbSnifferFmtPcapNgInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm)
+{
+ pThis->pStrm = pStrm;
+ pThis->cbBlockCur = 0;
+ pThis->cbBlockMax = 0;
+ pThis->pbBlockData = NULL;
+
+ /* Write header and link type blocks. */
+ DumpFileShb Shb;
+
+ Shb.Hdr.u32BlockType = DUMPFILE_SHB_BLOCK_TYPE;
+ Shb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */
+ Shb.u32ByteOrderMagic = DUMPFILE_SHB_BYTE_ORDER_MAGIC;
+ Shb.u16VersionMajor = DUMPFILE_SHB_VERSION_MAJOR;
+ Shb.u16VersionMinor = DUMPFILE_SHB_VERSION_MINOR;
+ Shb.u64SectionLength = UINT64_C(0xffffffffffffffff); /* -1 */
+
+ /* Write the blocks. */
+ int rc = vusbSnifferBlockNew(pThis, &Shb.Hdr, sizeof(Shb));
+ if (RT_SUCCESS(rc))
+ {
+ const char *pszOpt = RTBldCfgTargetDotArch();
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_HARDWARE, pszOpt, strlen(pszOpt) + 1);
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ char szTmp[512];
+ size_t cbTmp = sizeof(szTmp);
+
+ RT_ZERO(szTmp);
+
+ /* Build the OS code. */
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, cbTmp);
+ if (RT_SUCCESS(rc))
+ {
+ size_t cb = strlen(szTmp);
+
+ szTmp[cb] = ' ';
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, &szTmp[cb + 1], cbTmp - (cb + 1));
+ if (RT_SUCCESS(rc))
+ {
+ cb = strlen(szTmp);
+ szTmp[cb] = ' ';
+ rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, &szTmp[cb + 1], cbTmp - (cb + 1));
+ }
+ }
+
+ if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW)
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_OS, szTmp, strlen(szTmp) + 1);
+ else
+ rc = VINF_SUCCESS; /* Skip OS code if building the string failed. */
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /** @todo Add product info. */
+ }
+
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0);
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferBlockCommit(pThis);
+
+ /* Write Interface descriptor block. */
+ if (RT_SUCCESS(rc))
+ {
+ DumpFileIdb Idb;
+
+ Idb.Hdr.u32BlockType = DUMPFILE_IDB_BLOCK_TYPE;
+ Idb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */
+ Idb.u16LinkType = DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED;
+ Idb.u16Reserved = 0;
+ Idb.u32SnapLen = UINT32_C(0xffffffff);
+
+ rc = vusbSnifferBlockNew(pThis, &Idb.Hdr, sizeof(Idb));
+ if (RT_SUCCESS(rc))
+ {
+ uint8_t u8TsResolution = 9; /* Nano second resolution. */
+ /* Add timestamp resolution option. */
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_IDB_OPTION_TS_RESOLUTION,
+ &u8TsResolution, sizeof(u8TsResolution));
+ }
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0);
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferBlockCommit(pThis);
+ }
+
+ if ( RT_FAILURE(rc)
+ && pThis->pbBlockData)
+ RTMemFree(pThis->pbBlockData);
+
+ return rc;
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */
+static DECLCALLBACK(void) vusbSnifferFmtPcapNgDestroy(PVUSBSNIFFERFMTINT pThis)
+{
+ if (pThis->pbBlockData)
+ RTMemFree(pThis->pbBlockData);
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */
+static DECLCALLBACK(int) vusbSnifferFmtPcapNgRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)
+{
+ DumpFileEpb Epb;
+ DumpFileUsbHeaderLnxMmapped UsbHdr;
+ uint32_t cbCapturedLength = sizeof(UsbHdr);
+ uint8_t *pbData = NULL;
+
+ RTTIMESPEC TimeNow;
+ RTTimeNow(&TimeNow);
+ uint64_t u64TimestampEvent = RTTimeSpecGetNano(&TimeNow);
+
+ /* Start with the enhanced packet block. */
+ Epb.Hdr.u32BlockType = DUMPFILE_EPB_BLOCK_TYPE;
+ Epb.Hdr.u32BlockTotalLength = 0;
+ Epb.u32InterfaceId = 0;
+ Epb.u32TimestampHigh = (u64TimestampEvent >> 32) & UINT32_C(0xffffffff);
+ Epb.u32TimestampLow = u64TimestampEvent & UINT32_C(0xffffffff);
+
+ UsbHdr.u64Id = (uintptr_t)pUrb; /** @todo check whether the pointer is a good ID. */
+ uint32_t cbUrbLength;
+ switch (enmEvent)
+ {
+ case VUSBSNIFFEREVENT_SUBMIT:
+ UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_SUBMIT;
+ cbUrbLength = pUrb->cbData;
+ break;
+ case VUSBSNIFFEREVENT_COMPLETE:
+ UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_COMPLETE;
+ cbUrbLength = pUrb->cbData;
+ break;
+ case VUSBSNIFFEREVENT_ERROR_SUBMIT:
+ case VUSBSNIFFEREVENT_ERROR_COMPLETE:
+ UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_ERROR;
+ cbUrbLength = 0;
+ break;
+ default:
+ AssertMsgFailed(("Invalid event type %d\n", enmEvent));
+ cbUrbLength = 0;
+ }
+ uint32_t cbDataLength = cbUrbLength;
+ pbData = &pUrb->abData[0];
+
+ uint32_t cIsocPkts = 0;
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_ISOC:
+ {
+ int32_t cErrors = 0;
+
+ UsbHdr.u8TransferType = 0;
+ cIsocPkts = pUrb->cIsocPkts;
+ for (unsigned i = 0; i < cIsocPkts; i++)
+ if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK
+ && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED)
+ cErrors++;
+
+ UsbHdr.u.IsoRec.i32ErrorCount = cErrors;
+ UsbHdr.u.IsoRec.i32NumDesc = pUrb->cIsocPkts;
+ cbCapturedLength += cIsocPkts * sizeof(DumpFileUsbIsoDesc);
+ break;
+ }
+ case VUSBXFERTYPE_BULK:
+ UsbHdr.u8TransferType = 3;
+ break;
+ case VUSBXFERTYPE_INTR:
+ UsbHdr.u8TransferType = 1;
+ break;
+ case VUSBXFERTYPE_CTRL:
+ case VUSBXFERTYPE_MSG:
+ UsbHdr.u8TransferType = 2;
+ break;
+ default:
+ AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType));
+ }
+
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ if (enmEvent == VUSBSNIFFEREVENT_SUBMIT)
+ cbDataLength = 0;
+ }
+ else if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE
+ || pUrb->enmType == VUSBXFERTYPE_CTRL
+ || pUrb->enmType == VUSBXFERTYPE_MSG)
+ cbDataLength = 0;
+ }
+ else if ( pUrb->enmDir == VUSBDIRECTION_SETUP
+ && cbDataLength >= sizeof(VUSBSETUP))
+ cbDataLength -= sizeof(VUSBSETUP);
+
+ Epb.u32CapturedLen = cbCapturedLength + cbDataLength;
+ Epb.u32PacketLen = cbCapturedLength + cbUrbLength;
+
+ UsbHdr.u8EndpointNumber = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00);
+ UsbHdr.u8DeviceAddress = pUrb->DstAddress;
+ UsbHdr.u16BusId = 0;
+ UsbHdr.u8DataFlag = cbDataLength ? 0 : 1;
+ UsbHdr.u64TimestampSec = u64TimestampEvent / RT_NS_1SEC_64;
+ UsbHdr.u32TimestampUSec = u64TimestampEvent / RT_NS_1US_64 - UsbHdr.u64TimestampSec * RT_US_1SEC;
+ UsbHdr.i32Status = pUrb->enmStatus;
+ UsbHdr.u32UrbLength = cbUrbLength;
+ UsbHdr.u32DataLength = cbDataLength + cIsocPkts * sizeof(DumpFileUsbIsoDesc);
+ UsbHdr.i32Interval = 0;
+ UsbHdr.i32StartFrame = 0;
+ UsbHdr.u32XferFlags = 0;
+ UsbHdr.u32NumDesc = cIsocPkts;
+
+ if ( (pUrb->enmType == VUSBXFERTYPE_MSG || pUrb->enmType == VUSBXFERTYPE_CTRL)
+ && enmEvent == VUSBSNIFFEREVENT_SUBMIT)
+ {
+ PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData;
+
+ UsbHdr.u.UsbSetup.bmRequestType = pSetup->bmRequestType;
+ UsbHdr.u.UsbSetup.bRequest = pSetup->bRequest;
+ UsbHdr.u.UsbSetup.wValue = pSetup->wValue;
+ UsbHdr.u.UsbSetup.wIndex = pSetup->wIndex;
+ UsbHdr.u.UsbSetup.wLength = pSetup->wLength;
+ UsbHdr.u8SetupFlag = 0;
+ }
+ else
+ UsbHdr.u8SetupFlag = '-'; /* Follow usbmon source here. */
+
+ /* Write the packet to the capture file. */
+ int rc = vusbSnifferBlockNew(pThis, &Epb.Hdr, sizeof(Epb));
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferBlockAddData(pThis, &UsbHdr, sizeof(UsbHdr));
+
+ /* Add Isochronous descriptors now. */
+ for (unsigned i = 0; i < cIsocPkts && RT_SUCCESS(rc); i++)
+ {
+ DumpFileUsbIsoDesc IsoDesc;
+ IsoDesc.i32Status = pUrb->aIsocPkts[i].enmStatus;
+ IsoDesc.u32Offset = pUrb->aIsocPkts[i].off;
+ IsoDesc.u32Len = pUrb->aIsocPkts[i].cb;
+ rc = vusbSnifferBlockAddData(pThis, &IsoDesc, sizeof(IsoDesc));
+ }
+
+ /* Record data. */
+ if ( RT_SUCCESS(rc)
+ && cbDataLength)
+ rc = vusbSnifferBlockAddData(pThis, pbData, cbDataLength);
+
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0);
+
+ if (RT_SUCCESS(rc))
+ rc = vusbSnifferBlockCommit(pThis);
+
+ return rc;
+}
+
+/**
+ * VUSB sniffer format writer.
+ */
+const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg =
+{
+ /** szName */
+ "PCAPNG",
+ /** pszDesc */
+ "PCAP-NG format writer compatible with WireShark",
+ /** papszFileExts */
+ &s_apszFileExts[0],
+ /** cbFmt */
+ sizeof(VUSBSNIFFERFMTINT),
+ /** pfnInit */
+ vusbSnifferFmtPcapNgInit,
+ /** pfnDestroy */
+ vusbSnifferFmtPcapNgDestroy,
+ /** pfnRecordEvent */
+ vusbSnifferFmtPcapNgRecordEvent
+};
+
diff --git a/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp
new file mode 100644
index 00000000..ef09fb3b
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp
@@ -0,0 +1,249 @@
+/* $Id: VUSBSnifferUsbMon.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - Linux usbmon ASCII format.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/log.h>
+#include <iprt/mem.h>
+#include <iprt/buildconfig.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+
+#include "VUSBSnifferInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal VUSB sniffer state.
+ */
+typedef struct VUSBSNIFFERFMTINT
+{
+ /** Stream handle. */
+ PVUSBSNIFFERSTRM pStrm;
+} VUSBSNIFFERFMTINT;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Supported file extensions.
+ */
+static const char *s_apszFileExts[] =
+{
+ "mon",
+ "usbmon",
+ NULL
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */
+static DECLCALLBACK(int) vusbSnifferFmtUsbMonInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm)
+{
+ pThis->pStrm = pStrm;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */
+static DECLCALLBACK(void) vusbSnifferFmtUsbMonDestroy(PVUSBSNIFFERFMTINT pThis)
+{
+ RT_NOREF(pThis);
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */
+static DECLCALLBACK(int) vusbSnifferFmtUsbMonRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)
+{
+ char aszLineBuf[512];
+ char chEvtType = 'X';
+ char chDir = 'X';
+ char chEptType = 'X';
+
+ switch (enmEvent)
+ {
+ case VUSBSNIFFEREVENT_SUBMIT:
+ chEvtType = 'S';
+ break;
+ case VUSBSNIFFEREVENT_COMPLETE:
+ chEvtType = 'C';
+ break;
+ case VUSBSNIFFEREVENT_ERROR_SUBMIT:
+ case VUSBSNIFFEREVENT_ERROR_COMPLETE:
+ chEvtType = 'E';
+ break;
+ default:
+ AssertMsgFailed(("Invalid event type %d\n", enmEvent));
+ }
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_ISOC:
+ chEptType = 'Z';
+ break;
+ case VUSBXFERTYPE_BULK:
+ chEptType = 'B';
+ break;
+ case VUSBXFERTYPE_INTR:
+ chEptType = 'I';
+ break;
+ case VUSBXFERTYPE_CTRL:
+ case VUSBXFERTYPE_MSG:
+ chEptType = 'C';
+ break;
+ default:
+ AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType));
+ }
+
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ chDir = 'i';
+ else if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ chDir = 'o';
+ else if (pUrb->enmDir == VUSBDIRECTION_SETUP)
+ chDir = 'o';
+
+ RT_ZERO(aszLineBuf);
+
+ /* Assemble the static part. */
+ size_t cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%p %llu %c %c%c:%u:%u:%u ",
+ pUrb, RTTimeNanoTS() / RT_NS_1US, chEvtType, chEptType, chDir,
+ 0, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00));
+ int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ if (RT_SUCCESS(rc))
+ {
+ /* Log the setup packet for control requests, the status otherwise. */
+ if ( (pUrb->enmType == VUSBXFERTYPE_CTRL || pUrb->enmType == VUSBXFERTYPE_MSG)
+ && enmEvent == VUSBSNIFFEREVENT_SUBMIT)
+ {
+ PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData;
+
+ cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "s %02x %02x %04x %04x %04x ",
+ pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue,
+ pSetup->wIndex, pSetup->wLength);
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ }
+ else
+ {
+ bool fLogAdditionalStatus = pUrb->enmType == VUSBXFERTYPE_ISOC
+ || pUrb->enmType == VUSBXFERTYPE_INTR;
+
+ cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d%s", pUrb->enmStatus,
+ fLogAdditionalStatus ? "" : " ");
+
+ /* There are additional fields to log for isochronous and interrupt URBs. */
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ if (enmEvent == VUSBSNIFFEREVENT_COMPLETE)
+ {
+ uint32_t u32ErrorCount = 0;
+
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK
+ && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED)
+ u32ErrorCount++;
+
+ cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u:%u ",
+ 1 /* Interval */, 0 /* Frame number */, u32ErrorCount);
+ }
+ else
+ cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u ",
+ 1 /* Interval */, 0 /* Frame number */);
+ }
+ else if (pUrb->enmType == VUSBXFERTYPE_INTR)
+ cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u ",
+ 1 /* Interval */);
+
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ }
+
+ /* Log the packet descriptors for isochronous URBs. */
+ if ( RT_SUCCESS(rc)
+ && pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%u ", pUrb->cIsocPkts);
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ for (unsigned i = 0; i < pUrb->cIsocPkts && RT_SUCCESS(rc); i++)
+ {
+ cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d:%u:%u ",
+ pUrb->aIsocPkts[i].enmStatus, pUrb->aIsocPkts[i].off,
+ pUrb->aIsocPkts[i].cb);
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ /* Print data length */
+ cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d n\n", pUrb->cbData);
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch);
+ }
+
+ /** @todo Dump the data */
+ }
+
+ return rc;
+}
+
+/**
+ * VUSB sniffer format writer.
+ */
+const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon =
+{
+ /** szName */
+ "USBMON",
+ /** pszDesc */
+ "UsbMon format writer compatible with vusb-analyzer: http://vusb-analyzer.sourceforge.net",
+ /** papszFileExts */
+ &s_apszFileExts[0],
+ /** cbFmt */
+ sizeof(VUSBSNIFFERFMTINT),
+ /** pfnInit */
+ vusbSnifferFmtUsbMonInit,
+ /** pfnDestroy */
+ vusbSnifferFmtUsbMonDestroy,
+ /** pfnRecordEvent */
+ vusbSnifferFmtUsbMonRecordEvent
+};
+
diff --git a/src/VBox/Devices/USB/VUSBSnifferVmx.cpp b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp
new file mode 100644
index 00000000..b69548a4
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp
@@ -0,0 +1,212 @@
+/* $Id: VUSBSnifferVmx.cpp $ */
+/** @file
+ * Virtual USB Sniffer facility - VMX USBIO format.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/log.h>
+#include <iprt/mem.h>
+#include <iprt/buildconfig.h>
+#include <iprt/string.h>
+#include <iprt/system.h>
+#include <iprt/time.h>
+
+#include "VUSBSnifferInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * The internal VUSB sniffer state.
+ */
+typedef struct VUSBSNIFFERFMTINT
+{
+ /** Stream handle. */
+ PVUSBSNIFFERSTRM pStrm;
+} VUSBSNIFFERFMTINT;
+
+
+/*********************************************************************************************************************************
+* Static Variables *
+*********************************************************************************************************************************/
+
+/**
+ * Supported file extensions.
+ */
+static const char *s_apszFileExts[] =
+{
+ "vmx",
+ "vmware",
+ "usbio",
+ NULL
+};
+
+
+/**
+ * Month strings.
+ */
+static const char *s_apszMonths[] =
+{
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec"
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+static int vusbSnifferFmtVmxLogData(PVUSBSNIFFERFMTINT pThis, PRTTIME pTime, uint8_t *pbBuf, size_t cbBuf)
+{
+ int rc;
+ char szLineBuf[256];
+ size_t off = 0;
+
+ do
+ {
+ size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf),
+ "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %03zx: %16.*Rhxs\n",
+ s_apszMonths[pTime->u8Month - 1], pTime->u8MonthDay,
+ pTime->u8Hour, pTime->u8Minute, pTime->u8Second, 3, pTime->u32Nanosecond,
+ off, RT_MIN(cbBuf - off, 16), pbBuf);
+ rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch);
+ off += RT_MIN(cbBuf, 16);
+ pbBuf += RT_MIN(cbBuf, 16);
+ } while (RT_SUCCESS(rc) && off < cbBuf);
+
+ return rc;
+}
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */
+static DECLCALLBACK(int) vusbSnifferFmtVmxInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm)
+{
+ pThis->pStrm = pStrm;
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */
+static DECLCALLBACK(void) vusbSnifferFmtVmxDestroy(PVUSBSNIFFERFMTINT pThis)
+{
+ NOREF(pThis);
+}
+
+
+/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */
+static DECLCALLBACK(int) vusbSnifferFmtVmxRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)
+{
+ RTTIMESPEC TimeNow;
+ RTTIME Time;
+ char szLineBuf[256];
+ const char *pszEvt = enmEvent == VUSBSNIFFEREVENT_SUBMIT ? "Down" : "Up";
+ uint8_t cIsocPkts = pUrb->enmType == VUSBXFERTYPE_ISOC ? pUrb->cIsocPkts : 0;
+
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ return VINF_SUCCESS;
+
+ RT_ZERO(szLineBuf);
+
+ RTTimeNow(&TimeNow);
+ RTTimeExplode(&Time, &TimeNow);
+
+ size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf),
+ "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %s dev=%u endpt=%x datalen=%u numPackets=%u status=%u 0\n",
+ s_apszMonths[Time.u8Month - 1], Time.u8MonthDay, Time.u8Hour, Time.u8Minute, Time.u8Second, 3, Time.u32Nanosecond,
+ pszEvt, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00),
+ pUrb->cbData, cIsocPkts, pUrb->enmStatus);
+ int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch);
+ if (RT_SUCCESS(rc))
+ {
+ /* Log the data in the appropriate stage. */
+ if ( pUrb->enmType == VUSBXFERTYPE_CTRL
+ || pUrb->enmType == VUSBXFERTYPE_MSG)
+ {
+ if (enmEvent == VUSBSNIFFEREVENT_SUBMIT)
+ rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP));
+ else if (enmEvent == VUSBSNIFFEREVENT_COMPLETE)
+ {
+ rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP));
+ if ( RT_SUCCESS(rc)
+ && pUrb->cbData > sizeof(VUSBSETUP))
+ rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[sizeof(VUSBSETUP)], pUrb->cbData - sizeof(VUSBSETUP));
+ }
+ }
+ else
+ {
+ if ( enmEvent == VUSBSNIFFEREVENT_SUBMIT
+ && pUrb->enmDir == VUSBDIRECTION_OUT)
+ rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData);
+ else if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE
+ && pUrb->enmDir == VUSBDIRECTION_IN)
+ rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData);
+ }
+ }
+
+ return rc;
+}
+
+/**
+ * VUSB sniffer format writer.
+ */
+const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx =
+{
+ /** szName */
+ "VMX",
+ /** pszDesc */
+ "VMX log format writer supported by vusb-analyzer: http://vusb-analyzer.sourceforge.net",
+ /** papszFileExts */
+ &s_apszFileExts[0],
+ /** cbFmt */
+ sizeof(VUSBSNIFFERFMTINT),
+ /** pfnInit */
+ vusbSnifferFmtVmxInit,
+ /** pfnDestroy */
+ vusbSnifferFmtVmxDestroy,
+ /** pfnRecordEvent */
+ vusbSnifferFmtVmxRecordEvent
+};
+
diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp
new file mode 100644
index 00000000..3c14f756
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrb.cpp
@@ -0,0 +1,1501 @@
+/* $Id: VUSBUrb.cpp $ */
+/** @file
+ * Virtual USB - URBs.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/vmapi.h>
+#include <VBox/err.h>
+#include <iprt/alloc.h>
+#include <VBox/log.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include "VUSBInternal.h"
+
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Strings for the CTLSTAGE enum values. */
+const char * const g_apszCtlStates[4] =
+{
+ "SETUP",
+ "DATA",
+ "STATUS",
+ "N/A"
+};
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+
+
+/**
+ * Complete a SETUP stage URB.
+ *
+ * This is used both for dev2host and host2dev kind of transfers.
+ * It is used by both the sync and async control paths.
+ */
+static void vusbMsgSetupCompletion(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ PVUSBSETUP pSetup = pExtra->pMsg;
+
+ LogFlow(("%s: vusbMsgSetupCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage %s->DATA\n",
+ pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe, g_apszCtlStates[pExtra->enmStage])); NOREF(pSetup);
+ pExtra->enmStage = CTLSTAGE_DATA;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+}
+
+/**
+ * Complete a DATA stage URB.
+ *
+ * This is used both for dev2host and host2dev kind of transfers.
+ * It is used by both the sync and async control paths.
+ */
+static void vusbMsgDataCompletion(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ PVUSBSETUP pSetup = pExtra->pMsg;
+
+ LogFlow(("%s: vusbMsgDataCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage DATA\n",
+ pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe)); NOREF(pSetup);
+
+ pUrb->enmStatus = VUSBSTATUS_OK;
+}
+
+/**
+ * Complete a STATUS stage URB.
+ *
+ * This is used both for dev2host and host2dev kind of transfers.
+ * It is used by both the sync and async control paths.
+ */
+static void vusbMsgStatusCompletion(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+
+ if (pExtra->fOk)
+ {
+ /*
+ * vusbDevStdReqSetAddress requests are deferred.
+ */
+ if (pDev->u8NewAddress != VUSB_INVALID_ADDRESS)
+ {
+ vusbDevSetAddress(pDev, pDev->u8NewAddress);
+ pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
+ }
+
+ LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=OK stage %s->SETUP\n",
+ pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage]));
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ }
+ else
+ {
+ LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=STALL stage %s->SETUP\n",
+ pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage]));
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+ }
+
+ /*
+ * Done with this message sequence.
+ */
+ pExtra->pbCur = NULL;
+ pExtra->enmStage = CTLSTAGE_SETUP;
+}
+
+/**
+ * This is a worker function for vusbMsgCompletion and
+ * vusbMsgSubmitSynchronously used to complete the original URB.
+ *
+ * @param pUrb The URB originating from the HCI.
+ */
+static void vusbCtrlCompletion(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ LogFlow(("%s: vusbCtrlCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName));
+
+ switch (pExtra->enmStage)
+ {
+ case CTLSTAGE_SETUP:
+ vusbMsgSetupCompletion(pUrb);
+ break;
+ case CTLSTAGE_DATA:
+ vusbMsgDataCompletion(pUrb);
+ break;
+ case CTLSTAGE_STATUS:
+ vusbMsgStatusCompletion(pUrb);
+ break;
+ }
+}
+
+/**
+ * Called from vusbUrbCompletionRh when it encounters a
+ * message type URB.
+ *
+ * @param pUrb The URB within the control pipe extra state data.
+ */
+static void vusbMsgCompletion(PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+
+ RTCritSectEnter(&pPipe->CritSectCtrl);
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+
+#ifdef LOG_ENABLED
+ LogFlow(("%s: vusbMsgCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName));
+ vusbUrbTrace(pUrb, "vusbMsgCompletion", true);
+#endif
+ Assert(&pExtra->Urb == pUrb);
+
+
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ pExtra->fOk = true;
+ else
+ pExtra->fOk = false;
+ pExtra->cbLeft = pUrb->cbData - sizeof(VUSBSETUP);
+
+ /*
+ * Complete the original URB.
+ */
+ PVUSBURB pCtrlUrb = pUrb->pVUsb->pCtrlUrb;
+ pCtrlUrb->enmState = VUSBURBSTATE_REAPED;
+ vusbCtrlCompletion(pCtrlUrb);
+
+ /*
+ * 'Free' the message URB, i.e. put it back to the allocated state.
+ */
+ Assert( pUrb->enmState == VUSBURBSTATE_REAPED
+ || pUrb->enmState == VUSBURBSTATE_CANCELLED);
+ if (pUrb->enmState != VUSBURBSTATE_CANCELLED)
+ {
+ pUrb->enmState = VUSBURBSTATE_ALLOCATED;
+ pUrb->fCompleting = false;
+ }
+ RTCritSectLeave(&pPipe->CritSectCtrl);
+
+ /* Complete the original control URB on the root hub now. */
+ vusbUrbCompletionRh(pCtrlUrb);
+}
+
+/**
+ * Deal with URB errors, talking thru the RH to the HCI.
+ *
+ * @returns true if it could be retried.
+ * @returns false if it should be completed with failure.
+ * @param pRh The roothub the URB originated from.
+ * @param pUrb The URB in question.
+ */
+int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh));
+ RT_NOREF(pDev);
+ return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb);
+}
+
+/**
+ * Does URB completion on roothub level.
+ *
+ * @param pRh The roothub the URB originated from.
+ * @param pUrb The URB to complete.
+ */
+void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb)
+{
+ LogFlow(("%s: vusbUrbCompletionRh: type=%s status=%s\n",
+ pUrb->pszDesc, vusbUrbTypeName(pUrb->enmType), vusbUrbStatusName(pUrb->enmStatus)));
+ AssertMsg( pUrb->enmState == VUSBURBSTATE_REAPED
+ || pUrb->enmState == VUSBURBSTATE_CANCELLED, ("%d\n", pUrb->enmState));
+
+ if ( pUrb->pVUsb->pDev
+ && pUrb->pVUsb->pDev->hSniffer)
+ {
+ int rc = VUSBSnifferRecordEvent(pUrb->pVUsb->pDev->hSniffer, pUrb,
+ pUrb->enmStatus == VUSBSTATUS_OK
+ ? VUSBSNIFFEREVENT_COMPLETE
+ : VUSBSNIFFEREVENT_ERROR_COMPLETE);
+ if (RT_FAILURE(rc))
+ LogRel(("VUSB: Capturing URB completion event failed with %Rrc\n", rc));
+ }
+
+ /* If there is a sniffer on the roothub record the completed URB there too. */
+ if (pRh->hSniffer != VUSBSNIFFER_NIL)
+ {
+ int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb,
+ pUrb->enmStatus == VUSBSTATUS_OK
+ ? VUSBSNIFFEREVENT_COMPLETE
+ : VUSBSNIFFEREVENT_ERROR_COMPLETE);
+ if (RT_FAILURE(rc))
+ LogRel(("VUSB: Capturing URB completion event on the root hub failed with %Rrc\n", rc));
+ }
+
+#ifdef VBOX_WITH_STATISTICS
+ /*
+ * Total and per-type submit statistics.
+ */
+ if (pUrb->enmType != VUSBXFERTYPE_MSG)
+ {
+ Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));
+
+ if ( pUrb->enmStatus == VUSBSTATUS_OK
+ || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN
+ || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN)
+ {
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ const unsigned cb = pUrb->aIsocPkts[i].cb;
+ if (cb)
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActBytes, cb);
+ STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, cb);
+ STAM_COUNTER_ADD(&pRh->aStatIsocDetails[i].Bytes, cb);
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, cb);
+ STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, cb);
+ }
+ else
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, cb);
+ STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, cb);
+ }
+ STAM_COUNTER_INC(&pRh->StatIsocActPkts);
+ STAM_COUNTER_INC(&pRh->StatIsocActReadPkts);
+ }
+ STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Pkts);
+ switch (pUrb->aIsocPkts[i].enmStatus)
+ {
+ case VUSBSTATUS_OK:
+ if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok);
+ else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0);
+ break;
+ case VUSBSTATUS_DATA_UNDERRUN:
+ if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun);
+ else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0);
+ break;
+ case VUSBSTATUS_DATA_OVERRUN: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataOverrun); break;
+ case VUSBSTATUS_NOT_ACCESSED: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].NotAccessed); break;
+ default: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Misc); break;
+ }
+ }
+ }
+ else
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActBytes, pUrb->cbData);
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActReadBytes, pUrb->cbData);
+ }
+ else
+ {
+ STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, pUrb->cbData);
+ STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActWriteBytes, pUrb->cbData);
+ }
+ }
+ }
+ else
+ {
+ /* (Note. this also counts the cancelled packets) */
+ STAM_COUNTER_INC(&pRh->Total.StatUrbsFailed);
+ STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsFailed);
+ }
+ }
+#endif /* VBOX_WITH_STATISTICS */
+
+ /*
+ * Msg transfers are special virtual transfers associated with
+ * vusb, not the roothub
+ */
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ vusbMsgCompletion(pUrb);
+ return;
+ case VUSBXFERTYPE_ISOC:
+ /* Don't bother with error callback for isochronous URBs. */
+ break;
+
+#if 1 /** @todo r=bird: OHCI say ''If the Transfer Descriptor is being
+ * retired because of an error, the Host Controller must update
+ * the Halt bit of the Endpoint Descriptor.''
+ *
+ * So, I'll subject all transfertypes to the same halt stuff now. It could
+ * just happen to fix the logitech disconnect trap in win2k.
+ */
+ default:
+#endif
+ case VUSBXFERTYPE_BULK:
+ if (pUrb->enmStatus != VUSBSTATUS_OK)
+ vusbUrbErrorRhEx(pRh, pUrb);
+ break;
+ }
+#ifdef LOG_ENABLED
+ vusbUrbTrace(pUrb, "vusbUrbCompletionRh", true);
+#endif
+
+ pRh->pIRhPort->pfnXferCompletion(pRh->pIRhPort, pUrb);
+ if (pUrb->enmState == VUSBURBSTATE_REAPED)
+ {
+ LogFlow(("%s: vusbUrbCompletionRh: Freeing URB\n", pUrb->pszDesc));
+ pUrb->pVUsb->pfnFree(pUrb);
+ }
+
+ vusbRhR3ProcessFrame(pRh, true /* fCallback */);
+}
+
+
+/**
+ * Certain control requests must not ever be forwarded to the device because
+ * they are required by the vusb core in order to maintain the vusb internal
+ * data structures.
+ */
+DECLINLINE(bool) vusbUrbIsRequestSafe(PCVUSBSETUP pSetup, PVUSBURB pUrb)
+{
+ if ((pSetup->bmRequestType & VUSB_REQ_MASK) != VUSB_REQ_STANDARD)
+ return true;
+
+ switch (pSetup->bRequest)
+ {
+ case VUSB_REQ_CLEAR_FEATURE:
+ return pUrb->EndPt != 0 /* not default control pipe */
+ || pSetup->wValue != 0 /* not ENDPOINT_HALT */
+ || !pUrb->pVUsb->pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint; /* not special need for backend */
+ case VUSB_REQ_SET_ADDRESS:
+ case VUSB_REQ_SET_CONFIGURATION:
+ case VUSB_REQ_GET_CONFIGURATION:
+ case VUSB_REQ_SET_INTERFACE:
+ case VUSB_REQ_GET_INTERFACE:
+ return false;
+
+ /*
+ * If the device wishes it, we'll use the cached device and
+ * configuration descriptors. (We return false when we want to use the
+ * cache. Yeah, it's a bit weird to read.)
+ */
+ case VUSB_REQ_GET_DESCRIPTOR:
+ return !vusbDevIsDescriptorInCache(pUrb->pVUsb->pDev, pSetup);
+
+ default:
+ return true;
+ }
+}
+
+
+/**
+ * Queues an URB for asynchronous transfer.
+ * A list of asynchronous URBs is kept by the roothub.
+ *
+ * @returns VBox status code (from pfnUrbQueue).
+ * @param pUrb The URB.
+ */
+int vusbUrbQueueAsyncRh(PVUSBURB pUrb)
+{
+#ifdef LOG_ENABLED
+ vusbUrbTrace(pUrb, "vusbUrbQueueAsyncRh", false);
+#endif
+
+ /* Immediately return in case of error.
+ * XXX There is still a race: The Rh might vanish after this point! */
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
+ if (!pRh)
+ {
+ Log(("vusbUrbQueueAsyncRh returning VERR_OBJECT_DESTROYED\n"));
+ return VERR_OBJECT_DESTROYED;
+ }
+
+ RTCritSectEnter(&pDev->CritSectAsyncUrbs);
+ int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("%s: vusbUrbQueueAsyncRh: returns %Rrc (queue_urb)\n", pUrb->pszDesc, rc));
+ RTCritSectLeave(&pDev->CritSectAsyncUrbs);
+ return rc;
+ }
+
+ ASMAtomicIncU32(&pDev->aPipes[pUrb->EndPt].async);
+
+ /* Queue the Urb on the roothub */
+ RTListAppend(&pDev->LstAsyncUrbs, &pUrb->pVUsb->NdLst);
+ RTCritSectLeave(&pDev->CritSectAsyncUrbs);
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Send a control message *synchronously*.
+ * @return
+ */
+static void vusbMsgSubmitSynchronously(PVUSBURB pUrb, bool fSafeRequest)
+{
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ Assert(pDev);
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ PVUSBSETUP pSetup = pExtra->pMsg;
+ LogFlow(("%s: vusbMsgSubmitSynchronously: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : ""));
+
+ uint8_t *pbData = (uint8_t *)pExtra->pMsg + sizeof(*pSetup);
+ uint32_t cbData = pSetup->wLength;
+ bool fOk = false;
+ if (!fSafeRequest)
+ fOk = vusbDevStandardRequest(pDev, pUrb->EndPt, pSetup, pbData, &cbData);
+ else
+ AssertMsgFailed(("oops\n"));
+
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ if (fOk)
+ {
+ pSetup->wLength = cbData;
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ pExtra->fOk = true;
+ }
+ else
+ {
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+ pExtra->fOk = false;
+ }
+ pExtra->cbLeft = cbData; /* used by IN only */
+
+ vusbCtrlCompletion(pUrb);
+ vusbUrbCompletionRh(pUrb);
+
+ /*
+ * 'Free' the message URB, i.e. put it back to the allocated state.
+ */
+ pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED;
+ pExtra->Urb.fCompleting = false;
+}
+
+/**
+ * Callback for dealing with device reset.
+ */
+void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra)
+{
+ if (!pExtra)
+ return;
+ pExtra->enmStage = CTLSTAGE_SETUP;
+ if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED)
+ {
+ pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED;
+ pExtra->Urb.fCompleting = false;
+ }
+}
+
+
+/**
+ * Callback to free a cancelled message URB.
+ *
+ * This is yet another place we're we have to performance acrobatics to
+ * deal with cancelled URBs. sigh.
+ *
+ * The deal here is that we never free message URBs since they are integrated
+ * into the message pipe state. But since cancel can leave URBs unreaped and in
+ * a state which require them not to be freed, we'll have to do two things.
+ * First, if a new message URB is processed we'll have to get a new message
+ * pipe state. Second, we cannot just free the damn state structure because
+ * that might lead to heap corruption since it might still be in-flight.
+ *
+ * The URB embedded into the message pipe control structure will start in an
+ * ALLOCATED state. When submitted it will be go to the IN-FLIGHT state. When
+ * reaped it will go from REAPED to ALLOCATED. When completed in the CANCELLED
+ * state it will remain in that state (as does normal URBs).
+ *
+ * If a new message urb comes up while it's in the CANCELLED state, we will
+ * orphan it and it will be freed here in vusbMsgFreeUrb. We indicate this
+ * by setting pVUsb->pvFreeCtx to NULL.
+ *
+ * If we have to free the message state structure because of device destruction,
+ * configuration changes, or similar, we will orphan the message pipe state in
+ * the same way by setting pVUsb->pvFreeCtx to NULL and let this function free it.
+ *
+ * @param pUrb
+ */
+static DECLCALLBACK(void) vusbMsgFreeUrb(PVUSBURB pUrb)
+{
+ vusbUrbAssert(pUrb);
+ PVUSBCTRLEXTRA pExtra = (PVUSBCTRLEXTRA)((uint8_t *)pUrb - RT_UOFFSETOF(VUSBCTRLEXTRA, Urb));
+ if ( pUrb->enmState == VUSBURBSTATE_CANCELLED
+ && !pUrb->pVUsb->pvFreeCtx)
+ {
+ LogFlow(("vusbMsgFreeUrb: Freeing orphan: %p (pUrb=%p)\n", pExtra, pUrb));
+ RTMemFree(pExtra);
+ }
+ else
+ {
+ Assert(pUrb->pVUsb->pvFreeCtx == &pExtra->Urb);
+ pUrb->enmState = VUSBURBSTATE_ALLOCATED;
+ pUrb->fCompleting = false;
+ }
+}
+
+/**
+ * Frees the extra state data associated with a message pipe.
+ *
+ * @param pExtra The data.
+ */
+void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra)
+{
+ if (!pExtra)
+ return;
+ if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED)
+ {
+ pExtra->Urb.u32Magic = 0;
+ pExtra->Urb.enmState = VUSBURBSTATE_FREE;
+ if (pExtra->Urb.pszDesc)
+ RTStrFree(pExtra->Urb.pszDesc);
+ RTMemFree(pExtra);
+ }
+ else
+ pExtra->Urb.pVUsb->pvFreeCtx = NULL; /* see vusbMsgFreeUrb */
+}
+
+/**
+ * Allocates the extra state data required for a control pipe.
+ *
+ * @returns Pointer to the allocated and initialized state data.
+ * @returns NULL on out of memory condition.
+ * @param pUrb A URB we can copy default data from.
+ */
+static PVUSBCTRLEXTRA vusbMsgAllocExtraData(PVUSBURB pUrb)
+{
+/** @todo reuse these? */
+ PVUSBCTRLEXTRA pExtra;
+ /* The initial allocation tries to balance wasted memory versus the need to re-allocate
+ * the message data. Experience shows that an 8K initial allocation in practice never needs
+ * to be expanded but almost certainly wastes 4K or more memory.
+ */
+ const size_t cbMax = _2K + sizeof(VUSBSETUP);
+ pExtra = (PVUSBCTRLEXTRA)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbMax]));
+ if (pExtra)
+ {
+ pExtra->enmStage = CTLSTAGE_SETUP;
+ //pExtra->fOk = false;
+ pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData;
+ pExtra->pbCur = (uint8_t *)(pExtra->pMsg + 1);
+ //pExtra->cbLeft = 0;
+ pExtra->cbMax = cbMax;
+
+ //pExtra->Urb.Dev.pvProxyUrb = NULL;
+ pExtra->Urb.u32Magic = VUSBURB_MAGIC;
+ pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED;
+ pExtra->Urb.fCompleting = false;
+#ifdef LOG_ENABLED
+ RTStrAPrintf(&pExtra->Urb.pszDesc, "URB %p msg->%p", &pExtra->Urb, pUrb);
+#endif
+ pExtra->Urb.pVUsb = &pExtra->VUsbExtra;
+ //pExtra->Urb.pVUsb->pCtrlUrb = NULL;
+ //pExtra->Urb.pVUsb->pNext = NULL;
+ //pExtra->Urb.pVUsb->ppPrev = NULL;
+ pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;
+ pExtra->Urb.pVUsb->pDev = pUrb->pVUsb->pDev;
+ pExtra->Urb.pVUsb->pfnFree = vusbMsgFreeUrb;
+ pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb;
+ //pExtra->Urb.Hci = {0};
+ //pExtra->Urb.Dev.pvProxyUrb = NULL;
+ pExtra->Urb.DstAddress = pUrb->DstAddress;
+ pExtra->Urb.EndPt = pUrb->EndPt;
+ pExtra->Urb.enmType = VUSBXFERTYPE_MSG;
+ pExtra->Urb.enmDir = VUSBDIRECTION_INVALID;
+ //pExtra->Urb.fShortNotOk = false;
+ pExtra->Urb.enmStatus = VUSBSTATUS_INVALID;
+ //pExtra->Urb.cbData = 0;
+ vusbUrbAssert(&pExtra->Urb);
+ }
+ return pExtra;
+}
+
+/**
+ * Sets up the message.
+ *
+ * The message is associated with the pipe, in what's currently called
+ * control pipe extra state data (pointed to by pPipe->pCtrl). If this
+ * is a OUT message, we will no go on collecting data URB. If it's a
+ * IN message, we'll send it and then queue any incoming data for the
+ * URBs collecting it.
+ *
+ * @returns Success indicator.
+ */
+static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf)
+{
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf;
+
+ /*
+ * Validate length.
+ */
+ if (cbBuf < sizeof(VUSBSETUP))
+ {
+ LogFlow(("vusbMsgSetup: pPipe=%p cbBuf=%u < %u (failure) !!!\n",
+ pPipe, cbBuf, sizeof(VUSBSETUP)));
+ return false;
+ }
+
+ /*
+ * Check if we've got an cancelled message URB. Allocate a new one in that case.
+ */
+ if (pExtra->Urb.enmState == VUSBURBSTATE_CANCELLED)
+ {
+ void *pvNew = RTMemDup(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax]));
+ if (!pvNew)
+ {
+ Log(("vusbMsgSetup: out of memory!!! cbReq=%zu\n", RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax])));
+ return false;
+ }
+ pExtra->Urb.pVUsb->pvFreeCtx = NULL;
+ LogFlow(("vusbMsgSetup: Replacing canceled pExtra=%p with %p.\n", pExtra, pvNew));
+ pPipe->pCtrl = pExtra = (PVUSBCTRLEXTRA)pvNew;
+ pExtra->Urb.pVUsb = &pExtra->VUsbExtra;
+ pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;
+ pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData;
+ pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED;
+ pExtra->Urb.fCompleting = false;
+ }
+
+ /*
+ * Check that we've got sufficient space in the message URB.
+ */
+ if (pExtra->cbMax < cbBuf + pSetupIn->wLength)
+ {
+ uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength, 64);
+ PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]));
+ if (!pNew)
+ {
+ Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n",
+ cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])));
+ return false;
+ }
+ if (pExtra != pNew)
+ {
+ LogFunc(("Reallocated %u -> %u\n", pExtra->cbMax, cbReq));
+ pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData;
+ pExtra = pNew;
+ pPipe->pCtrl = pExtra;
+ pExtra->Urb.pVUsb = &pExtra->VUsbExtra;
+ pExtra->Urb.pVUsb->pUrb = &pExtra->Urb;
+ pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb;
+ }
+
+ pExtra->cbMax = cbReq;
+ }
+ Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED);
+
+ /*
+ * Copy the setup data and prepare for data.
+ */
+ PVUSBSETUP pSetup = pExtra->pMsg;
+ pExtra->fSubmitted = false;
+ pExtra->Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
+ pExtra->pbCur = (uint8_t *)(pSetup + 1);
+ pSetup->bmRequestType = pSetupIn->bmRequestType;
+ pSetup->bRequest = pSetupIn->bRequest;
+ pSetup->wValue = RT_LE2H_U16(pSetupIn->wValue);
+ pSetup->wIndex = RT_LE2H_U16(pSetupIn->wIndex);
+ pSetup->wLength = RT_LE2H_U16(pSetupIn->wLength);
+
+ LogFlow(("vusbMsgSetup(%p,,%d): bmRequestType=%#04x bRequest=%#04x wValue=%#06x wIndex=%#06x wLength=0x%.4x\n",
+ pPipe, cbBuf, pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+ return true;
+}
+
+/**
+ * Build the message URB from the given control URB and accompanying message
+ * pipe state which we grab from the device for the URB.
+ *
+ * @param pUrb The URB to submit.
+ * @param pSetup The setup packet for the message transfer.
+ * @param pExtra Pointer to the additional state requred for a control transfer.
+ * @param pPipe The message pipe state.
+ */
+static void vusbMsgDoTransfer(PVUSBURB pUrb, PVUSBSETUP pSetup, PVUSBCTRLEXTRA pExtra, PVUSBPIPE pPipe)
+{
+ RT_NOREF(pPipe);
+
+ /*
+ * Mark this transfer as sent (cleared at setup time).
+ */
+ Assert(!pExtra->fSubmitted);
+ pExtra->fSubmitted = true;
+
+ /*
+ * Do we have to do this synchronously?
+ */
+ bool fSafeRequest = vusbUrbIsRequestSafe(pSetup, pUrb);
+ if (!fSafeRequest)
+ {
+ vusbMsgSubmitSynchronously(pUrb, fSafeRequest);
+ return;
+ }
+
+ /*
+ * Do it asynchronously.
+ */
+ LogFlow(("%s: vusbMsgDoTransfer: ep=%d pMsgUrb=%p pPipe=%p stage=%s\n",
+ pUrb->pszDesc, pUrb->EndPt, &pExtra->Urb, pPipe, g_apszCtlStates[pExtra->enmStage]));
+ Assert(pExtra->Urb.enmType == VUSBXFERTYPE_MSG);
+ Assert(pExtra->Urb.EndPt == pUrb->EndPt);
+ pExtra->Urb.enmDir = (pSetup->bmRequestType & VUSB_DIR_TO_HOST) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT;
+ pExtra->Urb.cbData = pSetup->wLength + sizeof(*pSetup);
+ pExtra->Urb.pVUsb->pCtrlUrb = pUrb;
+ int rc = vusbUrbQueueAsyncRh(&pExtra->Urb);
+ if (RT_FAILURE(rc))
+ {
+ /*
+ * If we fail submitting it, will not retry but fail immediately.
+ *
+ * This keeps things simple. The host OS will have retried if
+ * it's a proxied device, and if it's a virtual one it really means
+ * it if it's failing a control message.
+ */
+ LogFlow(("%s: vusbMsgDoTransfer: failed submitting urb! failing it with %s (rc=%Rrc)!!!\n",
+ pUrb->pszDesc, rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? "DNR" : "CRC", rc));
+ pExtra->Urb.enmStatus = rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? VUSBSTATUS_DNR : VUSBSTATUS_CRC;
+ pExtra->Urb.enmState = VUSBURBSTATE_REAPED;
+ vusbMsgCompletion(&pExtra->Urb);
+ }
+}
+
+/**
+ * Fails a URB request with a pipe STALL error.
+ *
+ * @returns VINF_SUCCESS indicating that we've completed the URB.
+ * @param pUrb The URB in question.
+ */
+static int vusbMsgStall(PVUSBURB pUrb)
+{
+ PVUSBPIPE pPipe = &pUrb->pVUsb->pDev->aPipes[pUrb->EndPt];
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+ LogFlow(("%s: vusbMsgStall: pPipe=%p err=STALL stage %s->SETUP\n",
+ pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage]));
+
+ pExtra->pbCur = NULL;
+ pExtra->enmStage = CTLSTAGE_SETUP;
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+ vusbUrbCompletionRh(pUrb);
+ return VINF_SUCCESS;
+}
+
+/**
+ * Submit a control message.
+ *
+ * Here we implement the USB defined traffic that occurs in message pipes
+ * (aka control endpoints). We want to provide a single function for device
+ * drivers so that they don't all have to reimplement the usb logic for
+ * themselves. This means we need to keep a little bit of state information
+ * because control transfers occur over multiple bus transactions. We may
+ * also need to buffer data over multiple data stages.
+ *
+ * @returns VBox status code.
+ * @param pUrb The URB to submit.
+ */
+static int vusbUrbSubmitCtrl(PVUSBURB pUrb)
+{
+#ifdef LOG_ENABLED
+ vusbUrbTrace(pUrb, "vusbUrbSubmitCtrl", false);
+#endif
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt];
+
+ RTCritSectEnter(&pPipe->CritSectCtrl);
+ PVUSBCTRLEXTRA pExtra = pPipe->pCtrl;
+
+ if (!pExtra && !(pExtra = pPipe->pCtrl = vusbMsgAllocExtraData(pUrb)))
+ {
+ RTCritSectLeave(&pPipe->CritSectCtrl);
+ return VERR_VUSB_NO_URB_MEMORY;
+ }
+ PVUSBSETUP pSetup = pExtra->pMsg;
+
+ if (pPipe->async)
+ {
+ AssertMsgFailed(("%u\n", pPipe->async));
+ RTCritSectLeave(&pPipe->CritSectCtrl);
+ return VERR_GENERAL_FAILURE;
+ }
+
+ /*
+ * A setup packet always resets the transaction and the
+ * end of data transmission is signified by change in
+ * data direction.
+ */
+ if (pUrb->enmDir == VUSBDIRECTION_SETUP)
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->SETUP\n",
+ pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage]));
+ pExtra->enmStage = CTLSTAGE_SETUP;
+ }
+ else if ( pExtra->enmStage == CTLSTAGE_DATA
+ /* (the STATUS stage direction goes the other way) */
+ && !!(pSetup->bmRequestType & VUSB_DIR_TO_HOST) != (pUrb->enmDir == VUSBDIRECTION_IN))
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->STATUS\n",
+ pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage]));
+ pExtra->enmStage = CTLSTAGE_STATUS;
+ }
+
+ /*
+ * Act according to the current message stage.
+ */
+ switch (pExtra->enmStage)
+ {
+ case CTLSTAGE_SETUP:
+ /*
+ * When stall handshake is returned, all subsequent packets
+ * must generate stall until a setup packet arrives.
+ */
+ if (pUrb->enmDir != VUSBDIRECTION_SETUP)
+ {
+ Log(("%s: vusbUrbSubmitCtrl: Stall at setup stage (dir=%#x)!!\n", pUrb->pszDesc, pUrb->enmDir));
+ vusbMsgStall(pUrb);
+ break;
+ }
+
+ /* Store setup details, return DNR if corrupt */
+ if (!vusbMsgSetup(pPipe, pUrb->abData, pUrb->cbData))
+ {
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ vusbUrbCompletionRh(pUrb);
+ break;
+ }
+ if (pPipe->pCtrl != pExtra)
+ {
+ pExtra = pPipe->pCtrl;
+ pSetup = pExtra->pMsg;
+ }
+
+ /* pre-buffer our output if it's device-to-host */
+ if (pSetup->bmRequestType & VUSB_DIR_TO_HOST)
+ vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe);
+ else if (pSetup->wLength)
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: need data\n", pUrb->pszDesc));
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ vusbMsgSetupCompletion(pUrb);
+ vusbUrbCompletionRh(pUrb);
+ }
+ /*
+ * If there is no DATA stage, we must send it now since there are
+ * no requirement of a STATUS stage.
+ */
+ else
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: sending\n", pUrb->pszDesc));
+ vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe);
+ }
+ break;
+
+ case CTLSTAGE_DATA:
+ {
+ /*
+ * If a data stage exceeds the target buffer indicated in
+ * setup return stall, if data stage returns stall there
+ * will be no status stage.
+ */
+ uint8_t *pbData = (uint8_t *)(pExtra->pMsg + 1);
+ if ((uintptr_t)&pExtra->pbCur[pUrb->cbData] > (uintptr_t)&pbData[pSetup->wLength])
+ {
+ /* In the device -> host direction, the device never returns more data than
+ what was requested (wLength). So, we can just cap cbData. */
+ ssize_t const cbLeft = &pbData[pSetup->wLength] - pExtra->pbCur;
+ if (pSetup->bmRequestType & VUSB_DIR_TO_HOST)
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: Adjusting DATA request: %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft));
+ pUrb->cbData = cbLeft >= 0 ? (uint32_t)cbLeft : 0;
+ }
+ /* In the host -> direction it's undefined what happens if the host provides
+ more data than what wLength inidicated. However, in 2007, iPhone detection
+ via iTunes would issue wLength=0 but provide a data URB which we needed to
+ pass on to the device anyway, so we'll just quietly adjust wLength if it's
+ zero and get on with the work.
+
+ What confuses me (bird) here, though, is that we've already sent the SETUP
+ URB to the device when we received it, and all we end up doing is an
+ unnecessary memcpy and completing the URB, but never actually sending the
+ data to the device. So, I guess this stuff is still a little iffy.
+
+ Note! We currently won't be doing any resizing, as we've disabled resizing
+ in general.
+ P.S. We used to have a very strange (pUrb->cbData % pSetup->wLength) == 0
+ thing too that joined the pUrb->cbData adjusting above. */
+ else if ( pSetup->wLength == 0
+ && pUrb->cbData <= pExtra->cbMax)
+ {
+ Log(("%s: vusbUrbSubmitCtrl: pAdjusting wLength: %u -> %u (iPhone hack)\n",
+ pUrb->pszDesc, pSetup->wLength, pUrb->cbData));
+ pSetup->wLength = pUrb->cbData;
+ Assert(cbLeft >= (ssize_t)pUrb->cbData);
+ }
+ else
+ {
+ Log(("%s: vusbUrbSubmitCtrl: Stall at data stage!! wLength=%u cbData=%d cbMax=%d cbLeft=%dz\n",
+ pUrb->pszDesc, pSetup->wLength, pUrb->cbData, pExtra->cbMax, cbLeft));
+ vusbMsgStall(pUrb);
+ break;
+ }
+ }
+
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ /* put data received from the device. */
+ const uint32_t cbRead = RT_MIN(pUrb->cbData, pExtra->cbLeft);
+ memcpy(pUrb->abData, pExtra->pbCur, cbRead);
+
+ /* advance */
+ pExtra->pbCur += cbRead;
+ if (pUrb->cbData == cbRead)
+ pExtra->cbLeft -= pUrb->cbData;
+ else
+ {
+ /* adjust the pUrb->cbData to reflect the number of bytes containing actual data. */
+ LogFlow(("%s: vusbUrbSubmitCtrl: adjusting last DATA pUrb->cbData, %d -> %d\n",
+ pUrb->pszDesc, pUrb->cbData, pExtra->cbLeft));
+ pUrb->cbData = cbRead;
+ pExtra->cbLeft = 0;
+ }
+ }
+ else
+ {
+ /* get data for sending when completed. */
+ AssertStmt((ssize_t)pUrb->cbData <= pExtra->cbMax - (pExtra->pbCur - pbData), /* paranoia: checked above */
+ pUrb->cbData = pExtra->cbMax - (uint32_t)RT_MIN(pExtra->pbCur - pbData, pExtra->cbMax));
+ memcpy(pExtra->pbCur, pUrb->abData, pUrb->cbData);
+
+ /* advance */
+ pExtra->pbCur += pUrb->cbData;
+
+ /*
+ * If we've got the necessary data, we'll send it now since there are
+ * no requirement of a STATUS stage.
+ */
+ if ( !pExtra->fSubmitted
+ && pExtra->pbCur - pbData >= pSetup->wLength)
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: stage=DATA - to dev: sending\n", pUrb->pszDesc));
+ vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe);
+ break;
+ }
+ }
+
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ vusbMsgDataCompletion(pUrb);
+ vusbUrbCompletionRh(pUrb);
+ break;
+ }
+
+ case CTLSTAGE_STATUS:
+ if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST)
+ || pExtra->fSubmitted)
+ {
+ Assert(pExtra->fSubmitted);
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ vusbMsgStatusCompletion(pUrb);
+ vusbUrbCompletionRh(pUrb);
+ }
+ else
+ {
+ LogFlow(("%s: vusbUrbSubmitCtrl: stage=STATUS - to dev: sending\n", pUrb->pszDesc));
+ vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe);
+ }
+ break;
+ }
+
+ RTCritSectLeave(&pPipe->CritSectCtrl);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Submit a interrupt URB.
+ *
+ * @returns VBox status code.
+ * @param pUrb The URB to submit.
+ */
+static int vusbUrbSubmitInterrupt(PVUSBURB pUrb)
+{
+ LogFlow(("%s: vusbUrbSubmitInterrupt: (sync)\n", pUrb->pszDesc));
+ return vusbUrbQueueAsyncRh(pUrb);
+}
+
+
+/**
+ * Submit a bulk URB.
+ *
+ * @returns VBox status code.
+ * @param pUrb The URB to submit.
+ */
+static int vusbUrbSubmitBulk(PVUSBURB pUrb)
+{
+ LogFlow(("%s: vusbUrbSubmitBulk: (async)\n", pUrb->pszDesc));
+ return vusbUrbQueueAsyncRh(pUrb);
+}
+
+
+/**
+ * Submit an isochronous URB.
+ *
+ * @returns VBox status code.
+ * @param pUrb The URB to submit.
+ */
+static int vusbUrbSubmitIsochronous(PVUSBURB pUrb)
+{
+ LogFlow(("%s: vusbUrbSubmitIsochronous: (async)\n", pUrb->pszDesc));
+ return vusbUrbQueueAsyncRh(pUrb);
+}
+
+
+/**
+ * Fail a URB with a 'hard-error' sort of error.
+ *
+ * @return VINF_SUCCESS (the Urb status indicates the error).
+ * @param pUrb The URB.
+ */
+int vusbUrbSubmitHardError(PVUSBURB pUrb)
+{
+ /* FIXME: Find out the correct return code from the spec */
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ vusbUrbCompletionRh(pUrb);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Submit a URB.
+ */
+int vusbUrbSubmit(PVUSBURB pUrb)
+{
+ vusbUrbAssert(pUrb);
+ Assert(pUrb->enmState == VUSBURBSTATE_ALLOCATED);
+ PVUSBDEV pDev = pUrb->pVUsb->pDev;
+ PVUSBPIPE pPipe = NULL;
+ Assert(pDev);
+
+ /*
+ * Check that the device is in a valid state.
+ */
+ const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
+ if (enmState == VUSB_DEVICE_STATE_RESET)
+ {
+ LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName));
+ pUrb->enmStatus = VUSBSTATUS_DNR;
+ /* This will postpone the TDs until we're done with the resetting. */
+ return VERR_VUSB_DEVICE_IS_RESETTING;
+ }
+
+#ifdef LOG_ENABLED
+ /* stamp it */
+ pUrb->pVUsb->u64SubmitTS = RTTimeNanoTS();
+#endif
+
+ /** @todo Check max packet size here too? */
+
+ /*
+ * Validate the pipe.
+ */
+ if (pUrb->EndPt >= VUSB_PIPE_MAX)
+ {
+ Log(("%s: pDev=%p[%s]: SUBMIT: ep %i >= %i!!!\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pUrb->EndPt, VUSB_PIPE_MAX));
+ return vusbUrbSubmitHardError(pUrb);
+ }
+ PCVUSBDESCENDPOINTEX pEndPtDesc;
+ switch (pUrb->enmDir)
+ {
+ case VUSBDIRECTION_IN:
+ pEndPtDesc = pDev->aPipes[pUrb->EndPt].in;
+ pPipe = &pDev->aPipes[pUrb->EndPt];
+ break;
+ case VUSBDIRECTION_SETUP:
+ case VUSBDIRECTION_OUT:
+ default:
+ pEndPtDesc = pDev->aPipes[pUrb->EndPt].out;
+ pPipe = &pDev->aPipes[pUrb->EndPt];
+ break;
+ }
+ if (!pEndPtDesc)
+ {
+ Log(("%s: pDev=%p[%s]: SUBMIT: no endpoint!!! dir=%s e=%i\n",
+ pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbDirName(pUrb->enmDir), pUrb->EndPt));
+ return vusbUrbSubmitHardError(pUrb);
+ }
+
+ /*
+ * Check for correct transfer types.
+ * Our type codes are the same - what a coincidence.
+ */
+ if ((pEndPtDesc->Core.bmAttributes & 0x3) != pUrb->enmType)
+ {
+ /* Bulk and interrupt transfers are identical on the bus level (the only difference
+ * is in how they are scheduled by the HCD/HC) and need an exemption.
+ * Atheros AR9271 is a known offender; its configuration descriptors include
+ * interrupt endpoints, but drivers (Win7/8, Linux kernel pre-3.05) treat them
+ * as bulk endpoints.
+ */
+ if ( (pUrb->enmType == VUSBXFERTYPE_BULK && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_INTR)
+ || (pUrb->enmType == VUSBXFERTYPE_INTR && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_BULK))
+ {
+ Log2(("%s: pDev=%p[%s]: SUBMIT: mixing bulk/interrupt transfers on DstAddress=%i ep=%i dir=%s\n",
+ pUrb->pszDesc, pDev, pDev->pUsbIns->pszName,
+ pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir)));
+ }
+ else
+ {
+ Log(("%s: pDev=%p[%s]: SUBMIT: %s transfer requested for %#x endpoint on DstAddress=%i ep=%i dir=%s\n",
+ pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbTypeName(pUrb->enmType), pEndPtDesc->Core.bmAttributes,
+ pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir)));
+ return vusbUrbSubmitHardError(pUrb);
+ }
+ }
+
+ /*
+ * If there's a URB in the read-ahead buffer, use it.
+ */
+ int rc;
+
+ if (pDev->hSniffer)
+ {
+ rc = VUSBSnifferRecordEvent(pDev->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT);
+ if (RT_FAILURE(rc))
+ LogRel(("VUSB: Capturing URB submit event failed with %Rrc\n", rc));
+ }
+
+ /*
+ * Take action based on type.
+ */
+ pUrb->enmState = VUSBURBSTATE_IN_FLIGHT;
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_CTRL:
+ rc = vusbUrbSubmitCtrl(pUrb);
+ break;
+ case VUSBXFERTYPE_BULK:
+ rc = vusbUrbSubmitBulk(pUrb);
+ break;
+ case VUSBXFERTYPE_INTR:
+ rc = vusbUrbSubmitInterrupt(pUrb);
+ break;
+ case VUSBXFERTYPE_ISOC:
+ rc = vusbUrbSubmitIsochronous(pUrb);
+ break;
+ default:
+ AssertMsgFailed(("Unexpected pUrb type %d\n", pUrb->enmType));
+ return vusbUrbSubmitHardError(pUrb);
+ }
+
+ /*
+ * The device was detached, so we fail everything.
+ * (We should really detach and destroy the device, but we'll have to wait till Main reacts.)
+ */
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ rc = vusbUrbSubmitHardError(pUrb);
+ /*
+ * We don't increment error count if async URBs are in flight, in
+ * this case we just assume we need to throttle back, this also
+ * makes sure we don't halt bulk endpoints at the wrong time.
+ */
+ else if ( RT_FAILURE(rc)
+ && !ASMAtomicReadU32(&pDev->aPipes[pUrb->EndPt].async)
+ /* && pUrb->enmType == VUSBXFERTYPE_BULK ?? */
+ && !vusbUrbErrorRh(pUrb))
+ {
+ /* don't retry it anymore. */
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ pUrb->enmStatus = VUSBSTATUS_CRC;
+ vusbUrbCompletionRh(pUrb);
+ return VINF_SUCCESS;
+ }
+
+ return rc;
+}
+
+
+/**
+ * Reap in-flight URBs.
+ *
+ * @param pUrbLst Pointer to the head of the URB list.
+ * @param cMillies Number of milliseconds to block in each reap operation.
+ * Use 0 to not block at all.
+ */
+void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies)
+{
+ PVUSBURBVUSB pVUsbUrb = RTListGetFirst(pUrbLst, VUSBURBVUSBINT, NdLst);
+ while (pVUsbUrb)
+ {
+ vusbUrbAssert(pVUsbUrb->pUrb);
+ PVUSBURBVUSB pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, VUSBURBVUSBINT, NdLst);
+ PVUSBDEV pDev = pVUsbUrb->pDev;
+
+ /* Don't touch resetting devices - paranoid safety precaution. */
+ if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET)
+ {
+ /*
+ * Reap most URBs pending on a single device.
+ */
+ PVUSBURB pRipe;
+
+ /**
+ * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other
+ * and leaked URBs (shouldn't be affected by leaked URBs).
+ */
+ Assert(pDev->pUsbIns);
+ while ( pDev->pUsbIns
+ && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL))
+ {
+ vusbUrbAssert(pRipe);
+ if (pVUsbUrbNext && pRipe == pVUsbUrbNext->pUrb)
+ pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrbNext, VUSBURBVUSBINT, NdLst);
+ vusbUrbRipe(pRipe);
+ }
+ }
+
+ /* next */
+ pVUsbUrb = pVUsbUrbNext;
+ }
+}
+
+/**
+ * Reap URBs on a per device level.
+ *
+ * @returns nothing.
+ * @param pDev The device instance to reap URBs for.
+ * @param cMillies Number of milliseconds to block in each reap operation.
+ * Use 0 to not block at all.
+ */
+void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies)
+{
+ Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET);
+
+ /*
+ * Reap most URBs pending on a single device.
+ */
+ PVUSBURB pRipe;
+
+ /**
+ * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other
+ * and leaked URBs (shouldn't be affected by leaked URBs).
+ */
+
+ if (ASMAtomicXchgBool(&pDev->fWokenUp, false))
+ return;
+
+ Assert(pDev->pUsbIns);
+ while ( pDev->pUsbIns
+ && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL))
+ {
+ vusbUrbAssert(pRipe);
+ vusbUrbRipe(pRipe);
+ if (ASMAtomicXchgBool(&pDev->fWokenUp, false))
+ break;
+ }
+}
+
+/**
+ * Completes the URB.
+ */
+static void vusbUrbCompletion(PVUSBURB pUrb)
+{
+ Assert(pUrb->pVUsb->pDev->aPipes);
+ ASMAtomicDecU32(&pUrb->pVUsb->pDev->aPipes[pUrb->EndPt].async);
+
+ if (pUrb->enmState == VUSBURBSTATE_REAPED)
+ vusbUrbUnlink(pUrb);
+
+ vusbUrbCompletionRh(pUrb);
+}
+
+/**
+ * The worker for vusbUrbCancel() which is executed on the I/O thread.
+ *
+ * @returns IPRT status code.
+ * @param pUrb The URB to cancel.
+ * @param enmMode The way the URB should be canceled.
+ */
+DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode)
+{
+ vusbUrbAssert(pUrb);
+#ifdef VBOX_WITH_STATISTICS
+ PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev);
+#endif
+ if (pUrb->enmState == VUSBURBSTATE_IN_FLIGHT)
+ {
+ LogFlow(("%s: vusbUrbCancel: Canceling in-flight\n", pUrb->pszDesc));
+ STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled);
+ if (pUrb->enmType != VUSBXFERTYPE_MSG)
+ {
+ STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));});
+ STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled);
+ }
+
+ pUrb->enmState = VUSBURBSTATE_CANCELLED;
+ PPDMUSBINS pUsbIns = pUrb->pVUsb->pDev->pUsbIns;
+ pUsbIns->pReg->pfnUrbCancel(pUsbIns, pUrb);
+ Assert(pUrb->enmState == VUSBURBSTATE_CANCELLED || pUrb->enmState == VUSBURBSTATE_REAPED);
+
+ pUrb->enmStatus = VUSBSTATUS_CRC;
+ vusbUrbCompletion(pUrb);
+ }
+ else if (pUrb->enmState == VUSBURBSTATE_REAPED)
+ {
+ LogFlow(("%s: vusbUrbCancel: Canceling reaped urb\n", pUrb->pszDesc));
+ STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled);
+ if (pUrb->enmType != VUSBXFERTYPE_MSG)
+ {
+ STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));});
+ STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled);
+ }
+
+ pUrb->enmStatus = VUSBSTATUS_CRC;
+ vusbUrbCompletion(pUrb);
+ }
+ else
+ {
+ AssertMsg(pUrb->enmState == VUSBURBSTATE_CANCELLED, ("Invalid state %d, pUrb=%p\n", pUrb->enmState, pUrb));
+ switch (enmMode)
+ {
+ default:
+ AssertMsgFailed(("Invalid cancel mode\n"));
+ RT_FALL_THRU();
+ case CANCELMODE_FAIL:
+ pUrb->enmStatus = VUSBSTATUS_CRC;
+ break;
+ case CANCELMODE_UNDO:
+ pUrb->enmStatus = VUSBSTATUS_UNDO;
+ break;
+
+ }
+ }
+ return VINF_SUCCESS;
+}
+
+/**
+ * Cancels an URB with CRC failure.
+ *
+ * Cancelling an URB is a tricky thing. The USBProxy backend can not
+ * all cancel it and we must keep the URB around until it's ripe and
+ * can be reaped the normal way. However, we must complete the URB
+ * now, before leaving this function. This is not nice. sigh.
+ *
+ * This function will cancel the URB if it's in-flight and complete
+ * it. The device will in its pfnCancel method be given the chance to
+ * say that the URB doesn't need reaping and should be unlinked.
+ *
+ * An URB which is in the cancel state after pfnCancel will remain in that
+ * state and in the async list until its reaped. When it's finally reaped
+ * it will be unlinked and freed without doing any completion.
+ *
+ * There are different modes of canceling an URB. When devices are being
+ * disconnected etc., they will be completed with an error (CRC). However,
+ * when the HC needs to temporarily halt communication with a device, the
+ * URB/TD must be left alone if possible.
+ *
+ * @param pUrb The URB to cancel.
+ * @param mode The way the URB should be canceled.
+ */
+void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode)
+{
+ int rc = vusbDevIoThreadExecSync(pUrb->pVUsb->pDev, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode);
+ AssertRC(rc);
+}
+
+
+/**
+ * Async version of vusbUrbCancel() - doesn't wait for the cancelling to be complete.
+ */
+void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode)
+{
+ /* Don't try to cancel the URB when completion is in progress at the moment. */
+ if (!ASMAtomicXchgBool(&pUrb->fCompleting, true))
+ {
+ int rc = vusbDevIoThreadExec(pUrb->pVUsb->pDev, 0 /* fFlags */, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode);
+ AssertRC(rc);
+ }
+}
+
+
+/**
+ * Deals with a ripe URB (i.e. after reaping it).
+ *
+ * If an URB is in the reaped or in-flight state, we'll
+ * complete it. If it's cancelled, we'll simply free it.
+ * Any other states should never get here.
+ *
+ * @param pUrb The URB.
+ */
+void vusbUrbRipe(PVUSBURB pUrb)
+{
+ if ( pUrb->enmState == VUSBURBSTATE_IN_FLIGHT
+ || pUrb->enmState == VUSBURBSTATE_REAPED)
+ {
+ pUrb->enmState = VUSBURBSTATE_REAPED;
+ if (!ASMAtomicXchgBool(&pUrb->fCompleting, true))
+ vusbUrbCompletion(pUrb);
+ }
+ else if (pUrb->enmState == VUSBURBSTATE_CANCELLED)
+ {
+ vusbUrbUnlink(pUrb);
+ LogFlow(("%s: vusbUrbRipe: Freeing cancelled URB\n", pUrb->pszDesc));
+ pUrb->pVUsb->pfnFree(pUrb);
+ }
+ else
+ AssertMsgFailed(("Invalid URB state %d; %s\n", pUrb->enmState, pUrb->pszDesc));
+}
+
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "bsd"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: s
+ * End:
+ */
+
diff --git a/src/VBox/Devices/USB/VUSBUrbPool.cpp b/src/VBox/Devices/USB/VUSBUrbPool.cpp
new file mode 100644
index 00000000..3353c86d
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrbPool.cpp
@@ -0,0 +1,245 @@
+/* $Id: VUSBUrbPool.cpp $ */
+/** @file
+ * Virtual USB - URB pool.
+ */
+
+/*
+ * Copyright (C) 2016-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/log.h>
+#include <iprt/errcore.h>
+#include <iprt/mem.h>
+#include <iprt/critsect.h>
+
+#include "VUSBInternal.h"
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+/** Maximum age for one URB. */
+#define VUSBURB_AGE_MAX 10
+
+/** Convert from an URB to the URB header. */
+#define VUSBURBPOOL_URB_2_URBHDR(a_pUrb) RT_FROM_MEMBER(a_pUrb, VUSBURBHDR, Urb);
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/**
+ * URB header not visible to the caller allocating an URB
+ * and only for internal tracking.
+ */
+typedef struct VUSBURBHDR
+{
+ /** List node for keeping the URB in the free list. */
+ RTLISTNODE NdFree;
+ /** Size of the data allocated for the URB (Only the variable part including the
+ * HCI and TDs). */
+ size_t cbAllocated;
+ /** Age of the URB waiting on the list, if it is waiting for too long without being used
+ * again it will be freed. */
+ uint32_t cAge;
+#if HC_ARCH_BITS == 64
+ uint32_t u32Alignment0;
+#endif
+ /** The embedded URB. */
+ VUSBURB Urb;
+} VUSBURBHDR;
+/** Pointer to a URB header. */
+typedef VUSBURBHDR *PVUSBURBHDR;
+
+AssertCompileSizeAlignment(VUSBURBHDR, 8);
+
+
+
+DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool)
+{
+ int rc = RTCritSectInit(&pUrbPool->CritSectPool);
+ if (RT_SUCCESS(rc))
+ {
+ pUrbPool->cUrbsInPool = 0;
+ for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++)
+ RTListInit(&pUrbPool->aLstFreeUrbs[i]);
+ }
+
+ return rc;
+}
+
+
+DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool)
+{
+ RTCritSectEnter(&pUrbPool->CritSectPool);
+ for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++)
+ {
+ PVUSBURBHDR pHdr, pHdrNext;
+ RTListForEachSafe(&pUrbPool->aLstFreeUrbs[i], pHdr, pHdrNext, VUSBURBHDR, NdFree)
+ {
+ RTListNodeRemove(&pHdr->NdFree);
+
+ pHdr->cbAllocated = 0;
+ pHdr->Urb.u32Magic = 0;
+ pHdr->Urb.enmState = VUSBURBSTATE_INVALID;
+ RTMemFree(pHdr);
+ }
+ }
+ RTCritSectLeave(&pUrbPool->CritSectPool);
+ RTCritSectDelete(&pUrbPool->CritSectPool);
+}
+
+
+DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType,
+ VUSBDIRECTION enmDir, size_t cbData, size_t cbHci,
+ size_t cbHciTd, unsigned cTds)
+{
+ Assert((uint32_t)cbData == cbData);
+ Assert((uint32_t)cbHci == cbHci);
+
+ /*
+ * Reuse or allocate a new URB.
+ */
+ /** @todo The allocations should be done by the device, at least as an option, since the devices
+ * frequently wish to associate their own stuff with the in-flight URB or need special buffering
+ * (isochronous on Darwin for instance). */
+ /* Get the required amount of additional memory to allocate the whole state. */
+ size_t cbMem = cbData + sizeof(VUSBURBVUSBINT) + cbHci + cTds * cbHciTd;
+
+ AssertReturn((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs), NULL);
+
+ RTCritSectEnter(&pUrbPool->CritSectPool);
+ PVUSBURBHDR pHdr = NULL;
+ PVUSBURBHDR pIt, pItNext;
+ RTListForEachSafe(&pUrbPool->aLstFreeUrbs[enmType], pIt, pItNext, VUSBURBHDR, NdFree)
+ {
+ if (pIt->cbAllocated >= cbMem)
+ {
+ RTListNodeRemove(&pIt->NdFree);
+ Assert(pIt->Urb.u32Magic == VUSBURB_MAGIC);
+ Assert(pIt->Urb.enmState == VUSBURBSTATE_FREE);
+ /*
+ * If the allocation is far too big we increase the age counter too
+ * so we don't waste memory for a lot of small transfers
+ */
+ if (pIt->cbAllocated >= 2 * cbMem)
+ pIt->cAge++;
+ else
+ pIt->cAge = 0;
+ pHdr = pIt;
+ break;
+ }
+ else
+ {
+ /* Increase age and free if it reached a threshold. */
+ pIt->cAge++;
+ if (pIt->cAge == VUSBURB_AGE_MAX)
+ {
+ RTListNodeRemove(&pIt->NdFree);
+ ASMAtomicDecU32(&pUrbPool->cUrbsInPool);
+ RTMemFree(pIt);
+ }
+ }
+ }
+
+ if (!pHdr)
+ {
+ /* allocate a new one. */
+ size_t cbDataAllocated = cbMem <= _4K ? RT_ALIGN_32(cbMem, _1K)
+ : cbMem <= _32K ? RT_ALIGN_32(cbMem, _4K)
+ : RT_ALIGN_32(cbMem, 16*_1K);
+
+ pHdr = (PVUSBURBHDR)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBURBHDR, Urb.abData[cbDataAllocated]));
+ if (RT_UNLIKELY(!pHdr))
+ {
+ RTCritSectLeave(&pUrbPool->CritSectPool);
+ AssertLogRelFailedReturn(NULL);
+ }
+
+ pHdr->cbAllocated = cbDataAllocated;
+ pHdr->cAge = 0;
+ ASMAtomicIncU32(&pUrbPool->cUrbsInPool);
+ }
+ RTCritSectLeave(&pUrbPool->CritSectPool);
+
+ Assert(pHdr->cbAllocated >= cbMem);
+
+ /*
+ * (Re)init the URB
+ */
+ uint32_t offAlloc = (uint32_t)cbData;
+ PVUSBURB pUrb = &pHdr->Urb;
+ pUrb->u32Magic = VUSBURB_MAGIC;
+ pUrb->enmState = VUSBURBSTATE_ALLOCATED;
+ pUrb->fCompleting = false;
+ pUrb->pszDesc = NULL;
+ pUrb->pVUsb = (PVUSBURBVUSB)&pUrb->abData[offAlloc];
+ offAlloc += sizeof(VUSBURBVUSBINT);
+ pUrb->pVUsb->pUrb = pUrb;
+ pUrb->pVUsb->pvFreeCtx = NULL;
+ pUrb->pVUsb->pfnFree = NULL;
+ pUrb->pVUsb->pCtrlUrb = NULL;
+ pUrb->pVUsb->u64SubmitTS = 0;
+ pUrb->Dev.pvPrivate = NULL;
+ pUrb->Dev.pNext = NULL;
+ pUrb->EndPt = UINT8_MAX;
+ pUrb->enmType = enmType;
+ pUrb->enmDir = enmDir;
+ pUrb->fShortNotOk = false;
+ pUrb->enmStatus = VUSBSTATUS_INVALID;
+ pUrb->cbData = (uint32_t)cbData;
+ pUrb->pHci = cbHci ? (PVUSBURBHCI)&pUrb->abData[offAlloc] : NULL;
+ offAlloc += (uint32_t)cbHci;
+ pUrb->paTds = (cbHciTd && cTds) ? (PVUSBURBHCITD)&pUrb->abData[offAlloc] : NULL;
+
+ return pUrb;
+}
+
+
+DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb)
+{
+ PVUSBURBHDR pHdr = VUSBURBPOOL_URB_2_URBHDR(pUrb);
+
+ /* URBs which aged too much because they are too big are freed. */
+ if (pHdr->cAge == VUSBURB_AGE_MAX)
+ {
+ ASMAtomicDecU32(&pUrbPool->cUrbsInPool);
+ RTMemFree(pHdr);
+ }
+ else
+ {
+ /* Put it into the list of free URBs. */
+ VUSBXFERTYPE enmType = pUrb->enmType;
+ AssertReturnVoid((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs));
+ RTCritSectEnter(&pUrbPool->CritSectPool);
+ pUrb->enmState = VUSBURBSTATE_FREE;
+ RTListAppend(&pUrbPool->aLstFreeUrbs[enmType], &pHdr->NdFree);
+ RTCritSectLeave(&pUrbPool->CritSectPool);
+ }
+}
+
diff --git a/src/VBox/Devices/USB/VUSBUrbTrace.cpp b/src/VBox/Devices/USB/VUSBUrbTrace.cpp
new file mode 100644
index 00000000..8bf9a27b
--- /dev/null
+++ b/src/VBox/Devices/USB/VUSBUrbTrace.cpp
@@ -0,0 +1,831 @@
+/* $Id: VUSBUrbTrace.cpp $ */
+/** @file
+ * Virtual USB - URBs.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_VUSB
+#include <VBox/vmm/pdm.h>
+#include <VBox/vmm/vmapi.h>
+#include <iprt/errcore.h>
+#include <iprt/alloc.h>
+#include <VBox/log.h>
+#include <iprt/time.h>
+#include <iprt/thread.h>
+#include <iprt/semaphore.h>
+#include <iprt/string.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/env.h>
+#include "VUSBInternal.h"
+
+
+
+#ifdef LOG_ENABLED
+DECLINLINE(const char *) GetScsiErrCd(uint8_t ScsiErr)
+{
+ switch (ScsiErr)
+ {
+ case 0: return "?";
+ }
+ return "?";
+}
+
+DECLINLINE(const char *) GetScsiKCQ(uint8_t Key, uint8_t ASC, uint8_t ASCQ)
+{
+ switch (Key)
+ {
+ case 0:
+ switch (RT_MAKE_U16(ASC, ASCQ))
+ {
+ case RT_MAKE_U16(0x00, 0x00): return "No error";
+ }
+ break;
+
+ case 1:
+ return "Soft Error";
+
+ case 2:
+ return "Not Ready";
+
+ case 3:
+ return "Medium Error";
+
+ case 4:
+ return "Hard Error";
+
+ case 5:
+ return "Illegal Request";
+
+ case 6:
+ return "Unit Attention";
+
+ case 7:
+ return "Write Protected";
+
+ case 0xb:
+ return "Aborted Command";
+ }
+ return "?";
+}
+
+DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus)
+{
+ /** Strings for the URB statuses. */
+ static const char * const s_apszNames[] =
+ {
+ "OK",
+ "STALL",
+ "ERR_DNR",
+ "ERR_CRC",
+ "DATA_UNDERRUN",
+ "DATA_OVERRUN",
+ "NOT_ACCESSED",
+ "7", "8", "9", "10", "11", "12", "13", "14", "15"
+ };
+
+ return enmStatus < (int)RT_ELEMENTS(s_apszNames)
+ ? s_apszNames[enmStatus]
+ : enmStatus == VUSBSTATUS_INVALID
+ ? "INVALID"
+ : "??";
+}
+
+DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir)
+{
+ /** Strings for the URB directions. */
+ static const char * const s_apszNames[] =
+ {
+ "setup",
+ "in",
+ "out"
+ };
+
+ return enmDir < (int)RT_ELEMENTS(s_apszNames)
+ ? s_apszNames[enmDir]
+ : "??";
+}
+
+DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType)
+{
+ /** Strings for the URB types. */
+ static const char * const s_apszName[] =
+ {
+ "control-part",
+ "isochronous",
+ "bulk",
+ "interrupt",
+ "control"
+ };
+
+ return enmType < (int)RT_ELEMENTS(s_apszName)
+ ? s_apszName[enmType]
+ : "??";
+}
+
+/**
+ * Logs an URB.
+ *
+ * Note that pUrb->pVUsb->pDev and pUrb->pVUsb->pDev->pUsbIns can all be NULL.
+ */
+DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete)
+{
+ PVUSBDEV pDev = pUrb->pVUsb ? pUrb->pVUsb->pDev : NULL; /* Can be NULL when called from usbProxyConstruct and friends. */
+ PVUSBPIPE pPipe = pDev ? &pDev->aPipes[pUrb->EndPt] : NULL;
+ const uint8_t *pbData = pUrb->abData;
+ uint32_t cbData = pUrb->cbData;
+ PCVUSBSETUP pSetup = NULL;
+ bool fDescriptors = false;
+ static size_t s_cchMaxMsg = 10;
+ size_t cchMsg = strlen(pszMsg);
+ if (cchMsg > s_cchMaxMsg)
+ s_cchMaxMsg = cchMsg;
+
+ Log(("%s: %*s: pDev=%p[%s] rc=%s a=%i e=%u d=%s t=%s cb=%#x(%d) ts=%RU64 (%RU64 ns ago) %s\n",
+ pUrb->pszDesc, s_cchMaxMsg, pszMsg,
+ pDev,
+ pUrb->pVUsb && pUrb->pVUsb->pDev && pUrb->pVUsb->pDev->pUsbIns ? pUrb->pVUsb->pDev->pUsbIns->pszName : "",
+ vusbUrbStatusName(pUrb->enmStatus),
+ pDev ? pDev->u8Address : -1,
+ pUrb->EndPt,
+ vusbUrbDirName(pUrb->enmDir),
+ vusbUrbTypeName(pUrb->enmType),
+ pUrb->cbData,
+ pUrb->cbData,
+ pUrb->pVUsb ? pUrb->pVUsb->u64SubmitTS : 0,
+ pUrb->pVUsb ? RTTimeNanoTS() - pUrb->pVUsb->u64SubmitTS : 0,
+ pUrb->fShortNotOk ? "ShortNotOk" : "ShortOk"));
+
+#ifndef DEBUG_bird
+ if ( pUrb->enmType == VUSBXFERTYPE_CTRL
+ && pUrb->enmStatus == VUSBSTATUS_OK)
+ return;
+#endif
+
+ if ( pUrb->enmType == VUSBXFERTYPE_MSG
+ || ( pUrb->enmDir == VUSBDIRECTION_SETUP
+ && pUrb->enmType == VUSBXFERTYPE_CTRL
+ && cbData))
+ {
+ static const char * const s_apszReqDirs[] = {"host2dev", "dev2host"};
+ static const char * const s_apszReqTypes[] = {"std", "class", "vendor", "reserved"};
+ static const char * const s_apszReqRecipients[] = {"dev", "if", "endpoint", "other"};
+ static const char * const s_apszRequests[] =
+ {
+ "GET_STATUS", "CLEAR_FEATURE", "2?", "SET_FEATURE",
+ "4?", "SET_ADDRESS", "GET_DESCRIPTOR", "SET_DESCRIPTOR",
+ "GET_CONFIGURATION", "SET_CONFIGURATION", "GET_INTERFACE", "SET_INTERFACE",
+ "SYNCH_FRAME"
+ };
+ pSetup = (PVUSBSETUP)pUrb->abData;
+ pbData += sizeof(*pSetup);
+ cbData -= sizeof(*pSetup);
+
+ Log(("%s: %*s: CTRL: bmRequestType=0x%.2x (%s %s %s) bRequest=0x%.2x (%s) wValue=0x%.4x wIndex=0x%.4x wLength=0x%.4x\n",
+ pUrb->pszDesc, s_cchMaxMsg, pszMsg,
+ pSetup->bmRequestType, s_apszReqDirs[pSetup->bmRequestType >> 7], s_apszReqTypes[(pSetup->bmRequestType >> 5) & 0x3],
+ (unsigned)(pSetup->bmRequestType & 0xf) < RT_ELEMENTS(s_apszReqRecipients) ? s_apszReqRecipients[pSetup->bmRequestType & 0xf] : "??",
+ pSetup->bRequest, pSetup->bRequest < RT_ELEMENTS(s_apszRequests) ? s_apszRequests[pSetup->bRequest] : "??",
+ pSetup->wValue, pSetup->wIndex, pSetup->wLength));
+
+ if ( pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR
+ && fComplete
+ && pUrb->enmStatus == VUSBSTATUS_OK
+ && ((pSetup->bmRequestType >> 5) & 0x3) < 2 /* vendor */)
+ fDescriptors = true;
+ }
+ else if ( fComplete
+ && pUrb->enmDir == VUSBDIRECTION_IN
+ && pUrb->enmType == VUSBXFERTYPE_CTRL
+ && pUrb->enmStatus == VUSBSTATUS_OK
+ && pPipe->pCtrl
+ && pPipe->pCtrl->enmStage == CTLSTAGE_DATA
+ && cbData > 0)
+ {
+ pSetup = pPipe->pCtrl->pMsg;
+ if (pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR)
+ {
+ /* HID report (0x22) and physical (0x23) descriptors do not use standard format
+ * with descriptor length/type at the front. Don't try to dump them, we'll only
+ * misinterpret them.
+ */
+ if ( ((pSetup->bmRequestType >> 5) & 0x3) == 1 /* class */
+ && ((RT_HIBYTE(pSetup->wValue) == 0x22) || (RT_HIBYTE(pSetup->wValue) == 0x23)))
+ {
+ fDescriptors = false;
+ }
+ }
+ else
+ fDescriptors = true;
+ }
+
+ /*
+ * Dump descriptors.
+ */
+ if (fDescriptors)
+ {
+ const uint8_t *pb = pbData;
+ const uint8_t *pbEnd = pbData + cbData;
+ while (pb + 1 < pbEnd)
+ {
+ const unsigned cbLeft = pbEnd - pb;
+ const unsigned cbLength = *pb;
+ unsigned cb = cbLength;
+ uint8_t bDescriptorType = pb[1];
+
+ /* length out of bounds? */
+ if (cbLength > cbLeft)
+ {
+ cb = cbLeft;
+ if (cbLength != 0xff) /* ignore this */
+ Log(("URB: %*s: DESC: warning descriptor length goes beyond the end of the URB! cbLength=%d cbLeft=%d\n",
+ s_cchMaxMsg, pszMsg, cbLength, cbLeft));
+ }
+
+ if (cb >= 2)
+ {
+ Log(("URB: %*s: DESC: %04x: %25s = %#04x (%d)\n"
+ "URB: %*s: %04x: %25s = %#04x (",
+ s_cchMaxMsg, pszMsg, pb - pbData, "bLength", cbLength, cbLength,
+ s_cchMaxMsg, pszMsg, pb - pbData + 1, "bDescriptorType", bDescriptorType));
+
+ #pragma pack(1)
+ #define BYTE_FIELD(strct, memb) \
+ if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \
+ Log(("URB: %*s: %04x: %25s = %#04x\n", s_cchMaxMsg, pszMsg, \
+ pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)]))
+ #define BYTE_FIELD_START(strct, memb) do { \
+ if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \
+ { \
+ Log(("URB: %*s: %04x: %25s = %#04x", s_cchMaxMsg, pszMsg, \
+ pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)]))
+ #define BYTE_FIELD_END(strct, memb) \
+ Log(("\n")); \
+ } } while (0)
+ #define WORD_FIELD(strct, memb) \
+ if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \
+ Log(("URB: %*s: %04x: %25s = %#06x\n", s_cchMaxMsg, pszMsg, \
+ pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)]))
+ #define BCD_FIELD(strct, memb) \
+ if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \
+ Log(("URB: %*s: %04x: %25s = %#06x (%02x.%02x)\n", s_cchMaxMsg, pszMsg, \
+ pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)], \
+ pb[RT_OFFSETOF(strct, memb) + 1], pb[RT_OFFSETOF(strct, memb)]))
+ #define SIZE_CHECK(strct) \
+ if (cb > sizeof(strct)) \
+ Log(("URB: %*s: %04x: WARNING %d extra byte(s) %.*Rhxs\n", s_cchMaxMsg, pszMsg, \
+ pb + sizeof(strct) - pbData, cb - sizeof(strct), cb - sizeof(strct), pb + sizeof(strct))); \
+ else if (cb < sizeof(strct)) \
+ Log(("URB: %*s: %04x: WARNING %d missing byte(s)! Expected size %d.\n", s_cchMaxMsg, pszMsg, \
+ pb + cb - pbData, sizeof(strct) - cb, sizeof(strct)))
+
+ /* on type */
+ switch (bDescriptorType)
+ {
+ case VUSB_DT_DEVICE:
+ {
+ struct dev_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdUSB;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint16_t idVendor;
+ uint16_t idProduct;
+ uint16_t bcdDevice;
+ uint8_t iManufacturer;
+ uint8_t iProduct;
+ uint8_t iSerialNumber;
+ uint8_t bNumConfigurations;
+ } *pDesc = (struct dev_desc *)pb; NOREF(pDesc);
+ Log(("DEV)\n"));
+ BCD_FIELD( struct dev_desc, bcdUSB);
+ BYTE_FIELD(struct dev_desc, bDeviceClass);
+ BYTE_FIELD(struct dev_desc, bDeviceSubClass);
+ BYTE_FIELD(struct dev_desc, bDeviceProtocol);
+ BYTE_FIELD(struct dev_desc, bMaxPacketSize0);
+ WORD_FIELD(struct dev_desc, idVendor);
+ WORD_FIELD(struct dev_desc, idProduct);
+ BCD_FIELD( struct dev_desc, bcdDevice);
+ BYTE_FIELD(struct dev_desc, iManufacturer);
+ BYTE_FIELD(struct dev_desc, iProduct);
+ BYTE_FIELD(struct dev_desc, iSerialNumber);
+ BYTE_FIELD(struct dev_desc, bNumConfigurations);
+ SIZE_CHECK(struct dev_desc);
+ break;
+ }
+
+ case VUSB_DT_CONFIG:
+ {
+ struct cfg_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t wTotalLength;
+ uint8_t bNumInterfaces;
+ uint8_t bConfigurationValue;
+ uint8_t iConfiguration;
+ uint8_t bmAttributes;
+ uint8_t MaxPower;
+ } *pDesc = (struct cfg_desc *)pb; NOREF(pDesc);
+ Log(("CFG)\n"));
+ WORD_FIELD(struct cfg_desc, wTotalLength);
+ BYTE_FIELD(struct cfg_desc, bNumInterfaces);
+ BYTE_FIELD(struct cfg_desc, bConfigurationValue);
+ BYTE_FIELD(struct cfg_desc, iConfiguration);
+ BYTE_FIELD_START(struct cfg_desc, bmAttributes);
+ static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" };
+ static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" };
+ static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" };
+ Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)],
+ s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)]));
+ BYTE_FIELD_END(struct cfg_desc, bmAttributes);
+ BYTE_FIELD(struct cfg_desc, MaxPower);
+ SIZE_CHECK(struct cfg_desc);
+ break;
+ }
+
+ case VUSB_DT_STRING:
+ if (!pSetup->wIndex)
+ {
+ /* langid array */
+ uint16_t *pu16 = (uint16_t *)pb + 1;
+ Log(("LANGIDs)\n"));
+ while ((uintptr_t)pu16 + 2 - (uintptr_t)pb <= cb)
+ {
+ Log(("URB: %*s: %04x: wLANGID[%#x] = %#06x\n",
+ s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, pu16 - (uint16_t *)pb, *pu16));
+ pu16++;
+ }
+ if (cb & 1)
+ Log(("URB: %*s: %04x: WARNING descriptor size is odd! extra byte: %02\n",
+ s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, *(uint8_t *)pu16));
+ }
+ else
+ {
+ /** a string. */
+ Log(("STRING)\n"));
+ if (cb > 2)
+ Log(("URB: %*s: %04x: Length=%d String=%.*ls\n",
+ s_cchMaxMsg, pszMsg, pb - pbData, cb - 2, cb / 2 - 1, pb + 2));
+ else
+ Log(("URB: %*s: %04x: Length=0\n", s_cchMaxMsg, pszMsg, pb - pbData));
+ }
+ break;
+
+ case VUSB_DT_INTERFACE:
+ {
+ struct if_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bInterfaceNumber;
+ uint8_t bAlternateSetting;
+ uint8_t bNumEndpoints;
+ uint8_t bInterfaceClass;
+ uint8_t bInterfaceSubClass;
+ uint8_t bInterfaceProtocol;
+ uint8_t iInterface;
+ } *pDesc = (struct if_desc *)pb; NOREF(pDesc);
+ Log(("IF)\n"));
+ BYTE_FIELD(struct if_desc, bInterfaceNumber);
+ BYTE_FIELD(struct if_desc, bAlternateSetting);
+ BYTE_FIELD(struct if_desc, bNumEndpoints);
+ BYTE_FIELD(struct if_desc, bInterfaceClass);
+ BYTE_FIELD(struct if_desc, bInterfaceSubClass);
+ BYTE_FIELD(struct if_desc, bInterfaceProtocol);
+ BYTE_FIELD(struct if_desc, iInterface);
+ SIZE_CHECK(struct if_desc);
+ break;
+ }
+
+ case VUSB_DT_ENDPOINT:
+ {
+ struct ep_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint8_t bEndpointAddress;
+ uint8_t bmAttributes;
+ uint16_t wMaxPacketSize;
+ uint8_t bInterval;
+ } *pDesc = (struct ep_desc *)pb; NOREF(pDesc);
+ Log(("EP)\n"));
+ BYTE_FIELD(struct ep_desc, bEndpointAddress);
+ BYTE_FIELD(struct ep_desc, bmAttributes);
+ WORD_FIELD(struct ep_desc, wMaxPacketSize);
+ BYTE_FIELD(struct ep_desc, bInterval);
+ SIZE_CHECK(struct ep_desc);
+ break;
+ }
+
+ case VUSB_DT_DEVICE_QUALIFIER:
+ {
+ struct dq_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdUSB;
+ uint8_t bDeviceClass;
+ uint8_t bDeviceSubClass;
+ uint8_t bDeviceProtocol;
+ uint8_t bMaxPacketSize0;
+ uint8_t bNumConfigurations;
+ uint8_t bReserved;
+ } *pDQDesc = (struct dq_desc *)pb; NOREF(pDQDesc);
+ Log(("DEVQ)\n"));
+ BCD_FIELD( struct dq_desc, bcdUSB);
+ BYTE_FIELD(struct dq_desc, bDeviceClass);
+ BYTE_FIELD(struct dq_desc, bDeviceSubClass);
+ BYTE_FIELD(struct dq_desc, bDeviceProtocol);
+ BYTE_FIELD(struct dq_desc, bMaxPacketSize0);
+ BYTE_FIELD(struct dq_desc, bNumConfigurations);
+ BYTE_FIELD(struct dq_desc, bReserved);
+ SIZE_CHECK(struct dq_desc);
+ break;
+ }
+
+ case VUSB_DT_OTHER_SPEED_CFG:
+ {
+ struct oth_cfg_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t wTotalLength;
+ uint8_t bNumInterfaces;
+ uint8_t bConfigurationValue;
+ uint8_t iConfiguration;
+ uint8_t bmAttributes;
+ uint8_t MaxPower;
+ } *pDesc = (struct oth_cfg_desc *)pb; NOREF(pDesc);
+ Log(("OCFG)\n"));
+ WORD_FIELD(struct oth_cfg_desc, wTotalLength);
+ BYTE_FIELD(struct oth_cfg_desc, bNumInterfaces);
+ BYTE_FIELD(struct oth_cfg_desc, bConfigurationValue);
+ BYTE_FIELD(struct oth_cfg_desc, iConfiguration);
+ BYTE_FIELD_START(struct oth_cfg_desc, bmAttributes);
+ static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" };
+ static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" };
+ static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" };
+ Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)],
+ s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)]));
+ BYTE_FIELD_END(struct oth_cfg_desc, bmAttributes);
+ BYTE_FIELD(struct oth_cfg_desc, MaxPower);
+ SIZE_CHECK(struct oth_cfg_desc);
+ break;
+ }
+
+ case 0x21:
+ {
+ struct hid_desc
+ {
+ uint8_t bLength;
+ uint8_t bDescriptorType;
+ uint16_t bcdHid;
+ uint8_t bCountry;
+ uint8_t bNumDescriptors;
+ uint8_t bReportType;
+ uint16_t wReportLength;
+ } *pDesc = (struct hid_desc *)pb; NOREF(pDesc);
+ Log(("EP)\n"));
+ BCD_FIELD( struct hid_desc, bcdHid);
+ BYTE_FIELD(struct hid_desc, bCountry);
+ BYTE_FIELD(struct hid_desc, bNumDescriptors);
+ BYTE_FIELD(struct hid_desc, bReportType);
+ WORD_FIELD(struct hid_desc, wReportLength);
+ SIZE_CHECK(struct hid_desc);
+ break;
+ }
+
+ case 0xff:
+ Log(("UNKNOWN-ignore)\n"));
+ break;
+
+ default:
+ Log(("UNKNOWN)!!!\n"));
+ break;
+ }
+
+ #undef BYTE_FIELD
+ #undef WORD_FIELD
+ #undef BCD_FIELD
+ #undef SIZE_CHECK
+ #pragma pack()
+ }
+ else
+ {
+ Log(("URB: %*s: DESC: %04x: bLength=%d bDescriptorType=%d - invalid length\n",
+ s_cchMaxMsg, pszMsg, pb - pbData, cb, bDescriptorType));
+ break;
+ }
+
+ /* next */
+ pb += cb;
+ }
+ }
+
+ /*
+ * SCSI
+ */
+ if ( pUrb->enmType == VUSBXFERTYPE_BULK
+ && pUrb->enmDir == VUSBDIRECTION_OUT
+ && pUrb->cbData >= 12
+ && !memcmp(pUrb->abData, "USBC", 4))
+ {
+ const struct usbc
+ {
+ uint32_t Signature;
+ uint32_t Tag;
+ uint32_t DataTransferLength;
+ uint8_t Flags;
+ uint8_t Lun;
+ uint8_t Length;
+ uint8_t CDB[13];
+ } *pUsbC = (struct usbc *)pUrb->abData;
+ Log(("URB: %*s: SCSI: Tag=%#x DataTransferLength=%#x Flags=%#x Lun=%#x Length=%#x CDB=%.*Rhxs\n",
+ s_cchMaxMsg, pszMsg, pUsbC->Tag, pUsbC->DataTransferLength, pUsbC->Flags, pUsbC->Lun,
+ pUsbC->Length, pUsbC->Length, pUsbC->CDB));
+ const uint8_t *pb = &pUsbC->CDB[0];
+ switch (pb[0])
+ {
+ case 0x00: /* test unit read */
+ Log(("URB: %*s: SCSI: TEST_UNIT_READY LUN=%d Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[5]));
+ break;
+ case 0x03: /* Request Sense command */
+ Log(("URB: %*s: SCSI: REQUEST_SENSE LUN=%d AlcLen=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[4], pb[5]));
+ break;
+ case 0x12: /* Inquiry command. */
+ Log(("URB: %*s: SCSI: INQUIRY EVPD=%d LUN=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg, pb[1] & 1, pb[1] >> 5, pb[2], pb[4], pb[5]));
+ break;
+ case 0x1a: /* Mode Sense(6) command */
+ Log(("URB: %*s: SCSI: MODE_SENSE6 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f, pb[4], pb[5]));
+ break;
+ case 0x5a:
+ Log(("URB: %*s: SCSI: MODE_SENSE10 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f,
+ RT_MAKE_U16(pb[8], pb[7]), pb[9]));
+ break;
+ case 0x25: /* Read Capacity(6) command. */
+ Log(("URB: %*s: SCSI: READ_CAPACITY\n",
+ s_cchMaxMsg, pszMsg));
+ break;
+ case 0x28: /* Read(10) command. */
+ Log(("URB: %*s: SCSI: READ10 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5,
+ RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]),
+ RT_MAKE_U16(pb[8], pb[7]), pb[9]));
+ break;
+ case 0xa8: /* Read(12) command. */
+ Log(("URB: %*s: SCSI: READ12 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5,
+ RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]),
+ RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]),
+ pb[11]));
+ break;
+ case 0x3e: /* Read Long command. */
+ Log(("URB: %*s: SCSI: READ LONG RelAdr=%d Correct=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, !!(pb[1] & RT_BIT(1)), pb[1] >> 5,
+ RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]),
+ pb[11]));
+ break;
+ case 0x2a: /* Write(10) command. */
+ Log(("URB: %*s: SCSI: WRITE10 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, !!(pb[1] & RT_BIT(2)), !!(pb[1] & RT_BIT(3)),
+ !!(pb[1] & RT_BIT(4)), pb[1] >> 5,
+ RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]),
+ RT_MAKE_U16(pb[8], pb[7]), pb[9]));
+ break;
+ case 0xaa: /* Write(12) command. */
+ Log(("URB: %*s: SCSI: WRITE12 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)),
+ !!(pb[1] & RT_BIT(4)), pb[1] >> 5,
+ RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]),
+ RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]),
+ pb[11]));
+ break;
+ case 0x3f: /* Write Long command. */
+ Log(("URB: %*s: SCSI: WRITE LONG RelAdr=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n",
+ s_cchMaxMsg, pszMsg,
+ pb[1] & 1, pb[1] >> 5,
+ RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]),
+ pb[11]));
+ break;
+ case 0x35: /* Synchronize Cache(10) command. */
+ Log(("URB: %*s: SCSI: SYNCHRONIZE_CACHE10\n",
+ s_cchMaxMsg, pszMsg));
+ break;
+ case 0xa0: /* Report LUNs command. */
+ Log(("URB: %*s: SCSI: REPORT_LUNS\n",
+ s_cchMaxMsg, pszMsg));
+ break;
+ default:
+ Log(("URB: %*s: SCSI: cmd=%#x\n",
+ s_cchMaxMsg, pszMsg, pb[0]));
+ break;
+ }
+ if (pDev)
+ pDev->Urb.u8ScsiCmd = pb[0];
+ }
+ else if ( fComplete
+ && pUrb->enmType == VUSBXFERTYPE_BULK
+ && pUrb->enmDir == VUSBDIRECTION_IN
+ && pUrb->cbData >= 12
+ && !memcmp(pUrb->abData, "USBS", 4))
+ {
+ const struct usbs
+ {
+ uint32_t Signature;
+ uint32_t Tag;
+ uint32_t DataResidue;
+ uint8_t Status;
+ uint8_t CDB[3];
+ } *pUsbS = (struct usbs *)pUrb->abData;
+ static const char * const s_apszStatuses[] = { "PASSED", "FAILED", "PHASE ERROR", "RESERVED" };
+ Log(("URB: %*s: SCSI: Tag=%#x DataResidue=%#RX32 Status=%#RX8 %s\n",
+ s_cchMaxMsg, pszMsg, pUsbS->Tag, pUsbS->DataResidue, pUsbS->Status,
+ s_apszStatuses[pUsbS->Status < RT_ELEMENTS(s_apszStatuses) ? pUsbS->Status : RT_ELEMENTS(s_apszStatuses) - 1]));
+ if (pDev)
+ pDev->Urb.u8ScsiCmd = 0xff;
+ }
+ else if ( fComplete
+ && pUrb->enmType == VUSBXFERTYPE_BULK
+ && pUrb->enmDir == VUSBDIRECTION_IN
+ && pDev
+ && pDev->Urb.u8ScsiCmd != 0xff)
+ {
+ const uint8_t *pb = pUrb->abData;
+ switch (pDev->Urb.u8ScsiCmd)
+ {
+ case 0x03: /* REQUEST_SENSE */
+ Log(("URB: %*s: SCSI: RESPONSE: REQUEST_SENSE (%s)\n",
+ s_cchMaxMsg, pszMsg, pb[0] & 7 ? "scsi compliant" : "not scsi compliant"));
+ Log(("URB: %*s: SCSI: ErrCd=%#RX8 (%s) Seg=%#RX8 Filemark=%d EOM=%d ILI=%d\n",
+ s_cchMaxMsg, pszMsg, pb[0] & 0x7f, GetScsiErrCd(pb[0] & 0x7f), pb[1],
+ pb[2] >> 7, !!(pb[2] & RT_BIT(6)), !!(pb[2] & RT_BIT(5))));
+ Log(("URB: %*s: SCSI: SenseKey=%#x ASC=%#RX8 ASCQ=%#RX8 : %s\n",
+ s_cchMaxMsg, pszMsg, pb[2] & 0xf, pb[12], pb[13],
+ GetScsiKCQ(pb[2] & 0xf, pb[12], pb[13])));
+ /** @todo more later */
+ break;
+
+ case 0x12: /* INQUIRY. */
+ {
+ unsigned cb = pb[4] + 5;
+ Log(("URB: %*s: SCSI: RESPONSE: INQUIRY\n"
+ "URB: %*s: SCSI: PeripheralQualifier=%d PeripheralType=%#RX8 RMB=%d DevTypeMod=%#RX8\n",
+ s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg,
+ pb[0] >> 5, pb[0] & 0x1f, pb[1] >> 7, pb[1] & 0x7f));
+ Log(("URB: %*s: SCSI: ISOVer=%d ECMAVer=%d ANSIVer=%d\n",
+ s_cchMaxMsg, pszMsg, pb[2] >> 6, (pb[2] >> 3) & 7, pb[2] & 7));
+ Log(("URB: %*s: SCSI: AENC=%d TrmlOP=%d RespDataFmt=%d (%s) AddLen=%d\n",
+ s_cchMaxMsg, pszMsg, pb[3] >> 7, (pb[3] >> 6) & 1,
+ pb[3] & 0xf, pb[3] & 0xf ? "legacy" : "scsi", pb[4]));
+ if (cb < 8)
+ break;
+ Log(("URB: %*s: SCSI: RelAdr=%d WBus32=%d WBus16=%d Sync=%d Linked=%d CmdQue=%d SftRe=%d\n",
+ s_cchMaxMsg, pszMsg, pb[7] >> 7, !!(pb[7] >> 6), !!(pb[7] >> 5), !!(pb[7] >> 4),
+ !!(pb[7] >> 3), !!(pb[7] >> 1), pb[7] & 1));
+ if (cb < 16)
+ break;
+ Log(("URB: %*s: SCSI: VendorId=%.8s\n", s_cchMaxMsg, pszMsg, &pb[8]));
+ if (cb < 32)
+ break;
+ Log(("URB: %*s: SCSI: ProductId=%.16s\n", s_cchMaxMsg, pszMsg, &pb[16]));
+ if (cb < 36)
+ break;
+ Log(("URB: %*s: SCSI: ProdRevLvl=%.4s\n", s_cchMaxMsg, pszMsg, &pb[32]));
+ if (cb > 36)
+ Log(("URB: %*s: SCSI: VendorSpecific=%.*s\n",
+ s_cchMaxMsg, pszMsg, RT_MIN(cb - 36, 20), &pb[36]));
+ if (cb > 96)
+ Log(("URB: %*s: SCSI: VendorParam=%.*Rhxs\n",
+ s_cchMaxMsg, pszMsg, cb - 96, &pb[96]));
+ break;
+ }
+
+ case 0x25: /* Read Capacity(6) command. */
+ Log(("URB: %*s: SCSI: RESPONSE: READ_CAPACITY\n"
+ "URB: %*s: SCSI: LBA=%#RX32 BlockLen=%#RX32\n",
+ s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg,
+ RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]),
+ RT_MAKE_U32_FROM_U8(pb[7], pb[6], pb[5], pb[4])));
+ break;
+ }
+
+ pDev->Urb.u8ScsiCmd = 0xff;
+ }
+
+ /*
+ * The Quickcam control pipe.
+ */
+ if ( pSetup
+ && ((pSetup->bmRequestType >> 5) & 0x3) >= 2 /* vendor */
+ && (fComplete || !(pSetup->bmRequestType >> 7))
+ && pDev
+ && pDev->pDescCache
+ && pDev->pDescCache->pDevice
+ && pDev->pDescCache->pDevice->idVendor == 0x046d
+ && ( pDev->pDescCache->pDevice->idProduct == 0x8f6
+ || pDev->pDescCache->pDevice->idProduct == 0x8f5
+ || pDev->pDescCache->pDevice->idProduct == 0x8f0)
+ )
+ {
+ pbData = (const uint8_t *)(pSetup + 1);
+ cbData = pUrb->cbData - sizeof(*pSetup);
+
+ if ( pSetup->bRequest == 0x04
+ && pSetup->wIndex == 0
+ && (cbData == 1 || cbData == 2))
+ {
+ /* the value */
+ unsigned uVal = pbData[0];
+ if (cbData > 1)
+ uVal |= (unsigned)pbData[1] << 8;
+
+ const char *pszReg = NULL;
+ switch (pSetup->wValue)
+ {
+ case 0: pszReg = "i2c init"; break;
+ case 0x0423: pszReg = "STV_REG23"; break;
+ case 0x0509: pszReg = "RED something"; break;
+ case 0x050a: pszReg = "GREEN something"; break;
+ case 0x050b: pszReg = "BLUE something"; break;
+ case 0x143f: pszReg = "COMMIT? INIT DONE?"; break;
+ case 0x1440: pszReg = "STV_ISO_ENABLE"; break;
+ case 0x1442: pszReg = uVal & (RT_BIT(7)|RT_BIT(5)) ? "BUTTON PRESSED" : "BUTTON" ; break;
+ case 0x1443: pszReg = "STV_SCAN_RATE"; break;
+ case 0x1445: pszReg = "LED?"; break;
+ case 0x1500: pszReg = "STV_REG00"; break;
+ case 0x1501: pszReg = "STV_REG01"; break;
+ case 0x1502: pszReg = "STV_REG02"; break;
+ case 0x1503: pszReg = "STV_REG03"; break;
+ case 0x1504: pszReg = "STV_REG04"; break;
+ case 0x15c1: pszReg = "STV_ISO_SIZE"; break;
+ case 0x15c3: pszReg = "STV_Y_CTRL"; break;
+ case 0x1680: pszReg = "STV_X_CTRL"; break;
+ case 0xe00a: pszReg = "ProductId"; break;
+ default: pszReg = "[no clue]"; break;
+ }
+ if (pszReg)
+ Log(("URB: %*s: QUICKCAM: %s %#x (%d) %s '%s' (%#x)\n",
+ s_cchMaxMsg, pszMsg,
+ (pSetup->bmRequestType >> 7) ? "read" : "write", uVal, uVal, (pSetup->bmRequestType >> 7) ? "from" : "to",
+ pszReg, pSetup->wValue));
+ }
+ else if (cbData)
+ Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: %.*Rhxs\n", s_cchMaxMsg, pszMsg,
+ pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex, cbData, pbData));
+ else
+ Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: (no data)\n", s_cchMaxMsg, pszMsg,
+ pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex));
+ }
+
+#if 1
+ if ( cbData /** @todo Fix RTStrFormatV to communicate .* so formatter doesn't apply defaults when cbData=0. */
+ && (fComplete
+ ? pUrb->enmDir != VUSBDIRECTION_OUT
+ : pUrb->enmDir == VUSBDIRECTION_OUT))
+ Log3(("%16.*Rhxd\n", cbData, pbData));
+#endif
+ if (pUrb->enmType == VUSBXFERTYPE_MSG && pUrb->pVUsb && pUrb->pVUsb->pCtrlUrb)
+ vusbUrbTrace(pUrb->pVUsb->pCtrlUrb, "NESTED MSG", fComplete);
+}
+#endif /* LOG_ENABLED */
+
diff --git a/src/VBox/Devices/USB/darwin/Makefile.kup b/src/VBox/Devices/USB/darwin/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/darwin/Makefile.kup
diff --git a/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp
new file mode 100644
index 00000000..92929904
--- /dev/null
+++ b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp
@@ -0,0 +1,2014 @@
+/* $Id: USBProxyDevice-darwin.cpp $ */
+/** @file
+ * USB device proxy - the Darwin backend.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#define __STDC_LIMIT_MACROS
+#define __STDC_CONSTANT_MACROS
+
+#include <mach/mach.h>
+#include <Carbon/Carbon.h>
+#include <IOKit/IOKitLib.h>
+#include <mach/mach_error.h>
+#include <IOKit/usb/IOUSBLib.h>
+#include <IOKit/IOCFPlugIn.h>
+#ifndef __MAC_10_10 /* Quick hack: The following two masks appeared in 10.10. */
+# define kUSBReEnumerateReleaseDeviceMask RT_BIT_32(29)
+# define kUSBReEnumerateCaptureDeviceMask RT_BIT_32(30)
+#endif
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdm.h>
+
+#include <iprt/assert.h>
+#include <iprt/critsect.h>
+#include <iprt/list.h>
+#include <iprt/mem.h>
+#include <iprt/once.h>
+#include <iprt/string.h>
+#include <iprt/time.h>
+
+#include "../USBProxyDevice.h"
+#include <VBox/usblib.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** An experiment... */
+//#define USE_LOW_LATENCY_API 1
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/** Forward declaration of the Darwin interface structure. */
+typedef struct USBPROXYIFOSX *PUSBPROXYIFOSX;
+
+
+/**
+ * A low latency isochronous buffer.
+ *
+ * These are allocated in chunks on an interface level, see USBPROXYISOCBUFCOL.
+ */
+typedef struct USBPROXYISOCBUF
+{
+ /** Whether this buffer is in use or not. */
+ bool volatile fUsed;
+ /** Pointer to the buffer. */
+ void *pvBuf;
+ /** Pointer to an array of 8 frames. */
+ IOUSBLowLatencyIsocFrame *paFrames;
+} USBPROXYISOCBUF, *PUSBPROXYISOCBUF;
+
+
+/**
+ * Isochronous buffer collection (associated with an interface).
+ *
+ * These are allocated in decent sized chunks and there isn't supposed
+ * to be too many of these per interface.
+ */
+typedef struct USBPROXYISOCBUFCOL
+{
+ /** Write or Read buffers? */
+ USBLowLatencyBufferType enmType;
+ /** The next buffer collection on this interface. */
+ struct USBPROXYISOCBUFCOL *pNext;
+ /** The buffer. */
+ void *pvBuffer;
+ /** The frame. */
+ void *pvFrames;
+ /** The buffers.
+ * The number of buffers here is decided by pvFrame begin allocated in
+ * GUEST_PAGE_SIZE chunks. The size of IOUSBLowLatencyIsocFrame is 16 bytes
+ * and we require 8 of those per buffer. GUEST_PAGE_SIZE / (16 * 8) = 32.
+ * @remarks Don't allocate too many as it may temporarily halt the system if
+ * some pool is low / exhausted. (Contiguous memory woes on mach.)
+ */
+ USBPROXYISOCBUF aBuffers[/*32*/ 4];
+} USBPROXYISOCBUFCOL, *PUSBPROXYISOCBUFCOL;
+
+AssertCompileSize(IOUSBLowLatencyIsocFrame, 16);
+
+/**
+ * Per-urb data for the Darwin usb proxy backend.
+ *
+ * This is required to track in-flight and landed URBs
+ * since we take down the URBs in a different thread (perhaps).
+ */
+typedef struct USBPROXYURBOSX
+{
+ /** Pointer to the next Darwin URB. */
+ struct USBPROXYURBOSX *pNext;
+ /** Pointer to the previous Darwin URB. */
+ struct USBPROXYURBOSX *pPrev;
+ /** The millisecond timestamp when this URB was submitted. */
+ uint64_t u64SubmitTS;
+ /** Pointer to the VUSB URB.
+ * This is set to NULL if canceled. */
+ PVUSBURB pVUsbUrb;
+ /** Pointer to the Darwin device. */
+ struct USBPROXYDEVOSX *pDevOsX;
+ /** The transfer type. */
+ VUSBXFERTYPE enmType;
+ /** Union with data depending on transfer type. */
+ union
+ {
+ /** The control message. */
+ IOUSBDevRequest ControlMsg;
+ /** The Isochronous Data. */
+ struct
+ {
+#ifdef USE_LOW_LATENCY_API
+ /** The low latency isochronous buffer. */
+ PUSBPROXYISOCBUF pBuf;
+ /** Array of frames parallel to the one in VUSBURB. (Same as pBuf->paFrames.) */
+ IOUSBLowLatencyIsocFrame *aFrames;
+#else
+ /** Array of frames parallel to the one in VUSBURB. */
+ IOUSBIsocFrame aFrames[8];
+#endif
+ } Isoc;
+ } u;
+} USBPROXYURBOSX, *PUSBPROXYURBOSX;
+
+/**
+ * Per-pipe data for the Darwin usb proxy backend.
+ */
+typedef struct USBPROXYPIPEOSX
+{
+ /** The endpoint number. */
+ uint8_t u8Endpoint;
+ /** The IOKit pipe reference. */
+ uint8_t u8PipeRef;
+ /** The pipe Transfer type type. */
+ uint8_t u8TransferType;
+ /** The pipe direction. */
+ uint8_t u8Direction;
+ /** The endpoint interval. (interrupt) */
+ uint8_t u8Interval;
+ /** Full-speed device indicator (isochronous pipes only). */
+ bool fIsFullSpeed;
+ /** The max packet size. */
+ uint16_t u16MaxPacketSize;
+ /** The next frame number (isochronous pipes only). */
+ uint64_t u64NextFrameNo;
+} USBPROXYPIPEOSX, *PUSBPROXYPIPEOSX, **PPUSBPROXYPIPEOSX;
+
+typedef struct RUNLOOPREFLIST
+{
+ RTLISTNODE List;
+ CFRunLoopRef RunLoopRef;
+} RUNLOOPREFLIST, *PRUNLOOPREFLIST;
+typedef RUNLOOPREFLIST **PPRUNLOOPREFLIST;
+
+/**
+ * Per-interface data for the Darwin usb proxy backend.
+ */
+typedef struct USBPROXYIFOSX
+{
+ /** Pointer to the next interface. */
+ struct USBPROXYIFOSX *pNext;
+ /** The interface number. */
+ uint8_t u8Interface;
+ /** The current alternative interface setting.
+ * This is used to skip unnecessary SetAltInterface calls. */
+ uint8_t u8AltSetting;
+ /** The interface class. (not really used) */
+ uint8_t u8Class;
+ /** The interface protocol. (not really used) */
+ uint8_t u8Protocol;
+ /** The number of pipes. */
+ uint8_t cPipes;
+ /** Array containing all the pipes. (Currently unsorted.) */
+ USBPROXYPIPEOSX aPipes[kUSBMaxPipes];
+ /** The IOUSBDeviceInterface. */
+ IOUSBInterfaceInterface245 **ppIfI;
+ /** The run loop source for the async operations on the interface level. */
+ CFRunLoopSourceRef RunLoopSrcRef;
+ /** List of isochronous buffer collections.
+ * These are allocated on demand by the URB queuing routine and then recycled until the interface is destroyed. */
+ RTLISTANCHOR HeadOfRunLoopLst;
+ PUSBPROXYISOCBUFCOL pIsocBufCols;
+} USBPROXYIFOSX, *PUSBPROXYIFOSX, **PPUSBPROXYIFOSX;
+/** Pointer to a pointer to an darwin interface. */
+typedef USBPROXYIFOSX **PPUSBPROXYIFOSX;
+
+/**
+ * Per-device Data for the Darwin usb proxy backend.
+ */
+typedef struct USBPROXYDEVOSX
+{
+ /** The USB Device IOService object. */
+ io_object_t USBDevice;
+ /** The IOUSBDeviceInterface. */
+ IOUSBDeviceInterface245 **ppDevI;
+ /** The run loop source for the async operations on the device level
+ * (i.e. the default control pipe stuff). */
+ CFRunLoopSourceRef RunLoopSrcRef;
+ /** we want to add and remove RunLoopSourceRefs to run loop's of
+ * every EMT thread participated in USB processing. */
+ RTLISTANCHOR HeadOfRunLoopLst;
+ /** Pointer to the proxy device instance. */
+ PUSBPROXYDEV pProxyDev;
+
+ /** Pointer to the first interface. */
+ PUSBPROXYIFOSX pIfHead;
+ /** Pointer to the last interface. */
+ PUSBPROXYIFOSX pIfTail;
+
+ /** Critical section protecting the lists. */
+ RTCRITSECT CritSect;
+ /** The list of free Darwin URBs. Singly linked. */
+ PUSBPROXYURBOSX pFreeHead;
+ /** The list of landed Darwin URBs. Doubly linked.
+ * Only the split head will appear in this list. */
+ PUSBPROXYURBOSX pTaxingHead;
+ /** The tail of the landed Darwin URBs. */
+ PUSBPROXYURBOSX pTaxingTail;
+ /** Last reaper runloop reference, there can be only one runloop at a time. */
+ CFRunLoopRef hRunLoopReapingLast;
+ /** Runloop source for waking up the reaper thread. */
+ CFRunLoopSourceRef hRunLoopSrcWakeRef;
+ /** List of threads used for reaping which can be woken up. */
+ RTLISTANCHOR HeadOfRunLoopWakeLst;
+ /** Runloop reference of the thread reaping. */
+ volatile CFRunLoopRef hRunLoopReaping;
+ /** Flag whether the reaping thread is about the be waked. */
+ volatile bool fReapingThreadWake;
+} USBPROXYDEVOSX, *PUSBPROXYDEVOSX;
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+static RTONCE g_usbProxyDarwinOnce = RTONCE_INITIALIZER;
+/** The runloop mode we use.
+ * Since it's difficult to remove this, we leak it to prevent crashes.
+ * @bugref{4407} */
+static CFStringRef g_pRunLoopMode = NULL;
+/** The IO Master Port.
+ * Not worth cleaning up. */
+static mach_port_t g_MasterPort = MACH_PORT_NULL;
+
+
+/**
+ * Init once callback that sets up g_MasterPort and g_pRunLoopMode.
+ *
+ * @returns IPRT status code.
+ *
+ * @param pvUser1 NULL, ignored.
+ */
+static DECLCALLBACK(int32_t) usbProxyDarwinInitOnce(void *pvUser1)
+{
+ RT_NOREF(pvUser1);
+
+ int rc;
+ kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort);
+ if (krc == KERN_SUCCESS)
+ {
+ g_pRunLoopMode = CFStringCreateWithCString(kCFAllocatorDefault, "VBoxUsbProxyMode", kCFStringEncodingUTF8);
+ if (g_pRunLoopMode)
+ return VINF_SUCCESS;
+ rc = VERR_INTERNAL_ERROR_5;
+ }
+ else
+ rc = RTErrConvertFromDarwin(krc);
+ return rc;
+}
+
+/**
+ * Kicks the reaper thread if it sleeps currently to respond to state changes
+ * or to pick up completed URBs.
+ *
+ * @returns nothing.
+ * @param pDevOsX The darwin device instance data.
+ */
+static void usbProxyDarwinReaperKick(PUSBPROXYDEVOSX pDevOsX)
+{
+ CFRunLoopRef hRunLoopWake = (CFRunLoopRef)ASMAtomicReadPtr((void * volatile *)&pDevOsX->hRunLoopReaping);
+ if (hRunLoopWake)
+ {
+ LogFlowFunc(("Waking runloop %p\n", hRunLoopWake));
+ CFRunLoopSourceSignal(pDevOsX->hRunLoopSrcWakeRef);
+ CFRunLoopWakeUp(hRunLoopWake);
+ }
+}
+
+/**
+ * Adds Source ref to current run loop and adds it the list of runloops.
+ */
+static int usbProxyDarwinAddRunLoopRef(PRTLISTANCHOR pListHead,
+ CFRunLoopSourceRef SourceRef)
+{
+ AssertPtrReturn(pListHead, VERR_INVALID_PARAMETER);
+ AssertReturn(CFRunLoopSourceIsValid(SourceRef), VERR_INVALID_PARAMETER);
+
+ if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), SourceRef, g_pRunLoopMode))
+ return VINF_SUCCESS;
+
+ /* Add to the list */
+ PRUNLOOPREFLIST pListNode = (PRUNLOOPREFLIST)RTMemAllocZ(sizeof(RUNLOOPREFLIST));
+ if (!pListNode)
+ return VERR_NO_MEMORY;
+
+ pListNode->RunLoopRef = CFRunLoopGetCurrent();
+
+ CFRetain(pListNode->RunLoopRef);
+ CFRetain(SourceRef); /* We want to be aware of releasing */
+
+ CFRunLoopAddSource(pListNode->RunLoopRef, SourceRef, g_pRunLoopMode);
+
+ RTListInit(&pListNode->List);
+
+ RTListAppend((PRTLISTNODE)pListHead, &pListNode->List);
+
+ return VINF_SUCCESS;
+}
+
+
+/*
+ * Removes all source reference from mode of run loop's we've registered them.
+ *
+ */
+static int usbProxyDarwinRemoveSourceRefFromAllRunLoops(PRTLISTANCHOR pHead,
+ CFRunLoopSourceRef SourceRef)
+{
+ AssertPtrReturn(pHead, VERR_INVALID_PARAMETER);
+
+ while (!RTListIsEmpty(pHead))
+ {
+ PRUNLOOPREFLIST pNode = RTListGetFirst(pHead, RUNLOOPREFLIST, List);
+ /* XXX: Should Release Reference? */
+ Assert(CFGetRetainCount(pNode->RunLoopRef));
+
+ CFRunLoopRemoveSource(pNode->RunLoopRef, SourceRef, g_pRunLoopMode);
+ CFRelease(SourceRef);
+ CFRelease(pNode->RunLoopRef);
+
+ RTListNodeRemove(&pNode->List);
+
+ RTMemFree(pNode);
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Allocates a Darwin URB request structure.
+ *
+ * @returns Pointer to an active URB request.
+ * @returns NULL on failure.
+ *
+ * @param pDevOsX The darwin proxy device.
+ */
+static PUSBPROXYURBOSX usbProxyDarwinUrbAlloc(PUSBPROXYDEVOSX pDevOsX)
+{
+ PUSBPROXYURBOSX pUrbOsX;
+
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ /*
+ * Try remove a Darwin URB from the free list, if none there allocate a new one.
+ */
+ pUrbOsX = pDevOsX->pFreeHead;
+ if (pUrbOsX)
+ {
+ pDevOsX->pFreeHead = pUrbOsX->pNext;
+ RTCritSectLeave(&pDevOsX->CritSect);
+ }
+ else
+ {
+ RTCritSectLeave(&pDevOsX->CritSect);
+ pUrbOsX = (PUSBPROXYURBOSX)RTMemAlloc(sizeof(*pUrbOsX));
+ if (!pUrbOsX)
+ return NULL;
+ }
+ pUrbOsX->pVUsbUrb = NULL;
+ pUrbOsX->pDevOsX = pDevOsX;
+ pUrbOsX->enmType = VUSBXFERTYPE_INVALID;
+
+ return pUrbOsX;
+}
+
+
+#ifdef USE_LOW_LATENCY_API
+/**
+ * Allocates an low latency isochronous buffer.
+ *
+ * @returns VBox status code.
+ * @param pUrbOsX The OsX URB to allocate it for.
+ * @param pIf The interface to allocated it from.
+ */
+static int usbProxyDarwinUrbAllocIsocBuf(PUSBPROXYURBOSX pUrbOsX, PUSBPROXYIFOSX pIf)
+{
+ USBLowLatencyBufferType enmLLType = pUrbOsX->pVUsbUrb->enmDir == VUSBDIRECTION_IN
+ ? kUSBLowLatencyWriteBuffer : kUSBLowLatencyReadBuffer;
+
+ /*
+ * Walk the buffer collection list and look for an unused one.
+ */
+ pUrbOsX->u.Isoc.pBuf = NULL;
+ for (PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; pCur; pCur = pCur->pNext)
+ if (pCur->enmType == enmLLType)
+ for (unsigned i = 0; i < RT_ELEMENTS(pCur->aBuffers); i++)
+ if (!pCur->aBuffers[i].fUsed)
+ {
+ pCur->aBuffers[i].fUsed = true;
+ pUrbOsX->u.Isoc.pBuf = &pCur->aBuffers[i];
+ AssertPtr(pUrbOsX->u.Isoc.pBuf);
+ AssertPtr(pUrbOsX->u.Isoc.pBuf->pvBuf);
+ pUrbOsX->u.Isoc.aFrames = pCur->aBuffers[i].paFrames;
+ AssertPtr(pUrbOsX->u.Isoc.aFrames);
+ return VINF_SUCCESS;
+ }
+
+ /*
+ * Didn't find an empty one, create a new buffer collection and take the first buffer.
+ */
+ PUSBPROXYISOCBUFCOL pNew = (PUSBPROXYISOCBUFCOL)RTMemAllocZ(sizeof(*pNew));
+ AssertReturn(pNew, VERR_NO_MEMORY);
+
+ IOReturn irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvBuffer, 8192 * RT_ELEMENTS(pNew->aBuffers), enmLLType);
+ if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvBuffer))
+ {
+ AssertPtr(pNew->pvBuffer);
+ irc = kIOReturnNoMemory;
+ }
+ if (irc == kIOReturnSuccess)
+ {
+ /** @todo GUEST_PAGE_SIZE or HOST_PAGE_SIZE or just 4K? */
+ irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvFrames, GUEST_PAGE_SIZE, kUSBLowLatencyFrameListBuffer);
+ if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvFrames))
+ {
+ AssertPtr(pNew->pvFrames);
+ irc = kIOReturnNoMemory;
+ }
+ if (irc == kIOReturnSuccess)
+ {
+ for (unsigned i = 0; i < RT_ELEMENTS(pNew->aBuffers); i++)
+ {
+ //pNew->aBuffers[i].fUsed = false;
+ pNew->aBuffers[i].paFrames = &((IOUSBLowLatencyIsocFrame *)pNew->pvFrames)[i * 8];
+ pNew->aBuffers[i].pvBuf = (uint8_t *)pNew->pvBuffer + i * 8192;
+ }
+
+ pNew->aBuffers[0].fUsed = true;
+ pUrbOsX->u.Isoc.aFrames = pNew->aBuffers[0].paFrames;
+ pUrbOsX->u.Isoc.pBuf = &pNew->aBuffers[0];
+
+ pNew->enmType = enmLLType;
+ pNew->pNext = pIf->pIsocBufCols;
+ pIf->pIsocBufCols = pNew;
+
+#if 0 /* doesn't help :-/ */
+ /*
+ * If this is the first time we're here, try mess with the policy?
+ */
+ if (!pNew->pNext)
+ for (unsigned iPipe = 0; iPipe < pIf->cPipes; iPipe++)
+ if (pIf->aPipes[iPipe].u8TransferType == kUSBIsoc)
+ {
+ irc = (*pIf->ppIfI)->SetPipePolicy(pIf->ppIfI, pIf->aPipes[iPipe].u8PipeRef,
+ pIf->aPipes[iPipe].u16MaxPacketSize, pIf->aPipes[iPipe].u8Interval);
+ AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc));
+ }
+#endif
+
+ return VINF_SUCCESS;
+ }
+
+ /* bail out */
+ (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pNew->pvBuffer);
+ }
+ AssertMsgFailed(("%#x\n", irc));
+ RTMemFree(pNew);
+
+ return RTErrConvertFromDarwin(irc);
+}
+#endif /* USE_LOW_LATENCY_API */
+
+
+/**
+ * Frees a Darwin URB request structure.
+ *
+ * @param pDevOsX The darwin proxy device.
+ * @param pUrbOsX The Darwin URB to free.
+ */
+static void usbProxyDarwinUrbFree(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYURBOSX pUrbOsX)
+{
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+#ifdef USE_LOW_LATENCY_API
+ /*
+ * Free low latency stuff.
+ */
+ if ( pUrbOsX->enmType == VUSBXFERTYPE_ISOC
+ && pUrbOsX->u.Isoc.pBuf)
+ {
+ pUrbOsX->u.Isoc.pBuf->fUsed = false;
+ pUrbOsX->u.Isoc.pBuf = NULL;
+ }
+#endif
+
+ /*
+ * Link it into the free list.
+ */
+ pUrbOsX->pPrev = NULL;
+ pUrbOsX->pNext = pDevOsX->pFreeHead;
+ pDevOsX->pFreeHead = pUrbOsX;
+
+ pUrbOsX->pVUsbUrb = NULL;
+ pUrbOsX->pDevOsX = NULL;
+ pUrbOsX->enmType = VUSBXFERTYPE_INVALID;
+
+ RTCritSectLeave(&pDevOsX->CritSect);
+}
+
+/**
+ * Translate the IOKit status code to a VUSB status.
+ *
+ * @returns VUSB URB status code.
+ * @param irc IOKit status code.
+ */
+static VUSBSTATUS vusbProxyDarwinStatusToVUsbStatus(IOReturn irc)
+{
+ switch (irc)
+ {
+ /* IOKit OHCI VUSB */
+ case kIOReturnSuccess: /* 0 */ return VUSBSTATUS_OK;
+ case kIOUSBCRCErr: /* 1 */ return VUSBSTATUS_CRC;
+ //case kIOUSBBitstufErr: /* 2 */ return VUSBSTATUS_;
+ //case kIOUSBDataToggleErr: /* 3 */ return VUSBSTATUS_;
+ case kIOUSBPipeStalled: /* 4 */ return VUSBSTATUS_STALL;
+ case kIOReturnNotResponding: /* 5 */ return VUSBSTATUS_DNR;
+ //case kIOUSBPIDCheckErr: /* 6 */ return VUSBSTATUS_;
+ //case kIOUSBWrongPIDErr: /* 7 */ return VUSBSTATUS_;
+ case kIOReturnOverrun: /* 8 */ return VUSBSTATUS_DATA_OVERRUN;
+ case kIOReturnUnderrun: /* 9 */ return VUSBSTATUS_DATA_UNDERRUN;
+ //case kIOUSBReserved1Err: /* 10 */ return VUSBSTATUS_;
+ //case kIOUSBReserved2Err: /* 11 */ return VUSBSTATUS_;
+ //case kIOUSBBufferOverrunErr: /* 12 */ return VUSBSTATUS_;
+ //case kIOUSBBufferUnderrunErr: /* 13 */ return VUSBSTATUS_;
+ case kIOUSBNotSent1Err: /* 14 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/;
+ case kIOUSBNotSent2Err: /* 15 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/;
+
+ /* Other errors */
+ case kIOUSBTransactionTimeout: return VUSBSTATUS_DNR;
+ //case kIOReturnAborted: return VUSBSTATUS_CRC; - see on SET_INTERFACE...
+
+ default:
+ Log(("vusbProxyDarwinStatusToVUsbStatus: irc=%#x!!\n", irc));
+ return VUSBSTATUS_STALL;
+ }
+}
+
+
+/**
+ * Completion callback for an async URB transfer.
+ *
+ * @param pvUrbOsX The Darwin URB.
+ * @param irc The status of the operation.
+ * @param Size Possible the transfer size.
+ */
+static void usbProxyDarwinUrbAsyncComplete(void *pvUrbOsX, IOReturn irc, void *Size)
+{
+ PUSBPROXYURBOSX pUrbOsX = (PUSBPROXYURBOSX)pvUrbOsX;
+ PUSBPROXYDEVOSX pDevOsX = pUrbOsX->pDevOsX;
+ const uint32_t cb = (uintptr_t)Size;
+
+ /*
+ * Do status updates.
+ */
+ PVUSBURB pUrb = pUrbOsX->pVUsbUrb;
+ if (pUrb)
+ {
+ Assert(pUrb->u32Magic == VUSBURB_MAGIC);
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+#ifdef USE_LOW_LATENCY_API
+ /* copy the data. */
+ //if (pUrb->enmDir == VUSBDIRECTION_IN)
+ memcpy(pUrb->abData, pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->cbData);
+#endif
+ Log3(("AsyncComplete isoc - raw data (%d bytes):\n"
+ "%16.*Rhxd\n", pUrb->cbData, pUrb->cbData, pUrb->abData));
+ uint32_t off = 0;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+#ifdef USE_LOW_LATENCY_API
+ Log2((" %d{%d/%d-%x-%RX64}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus,
+ RT_MAKE_U64(pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo, pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi) ));
+#else
+ Log2((" %d{%d/%d-%x}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus));
+#endif
+ pUrb->aIsocPkts[i].enmStatus = vusbProxyDarwinStatusToVUsbStatus(pUrbOsX->u.Isoc.aFrames[i].frStatus);
+ pUrb->aIsocPkts[i].cb = pUrbOsX->u.Isoc.aFrames[i].frActCount;
+ off += pUrbOsX->u.Isoc.aFrames[i].frActCount;
+ }
+ Log2(("\n"));
+#if 0 /** @todo revisit this, wasn't working previously. */
+ for (int i = (int)pUrb->cIsocPkts - 1; i >= 0; i--)
+ Assert( !pUrbOsX->u.Isoc.aFrames[i].frActCount
+ && !pUrbOsX->u.Isoc.aFrames[i].frReqCount
+ && !pUrbOsX->u.Isoc.aFrames[i].frStatus);
+#endif
+ pUrb->cbData = off; /* 'Size' seems to be pointing at an error code or something... */
+ pUrb->enmStatus = VUSBSTATUS_OK; /* Don't use 'irc'. OHCI expects OK unless it's a really bad error. */
+ }
+ else
+ {
+ pUrb->cbData = cb;
+ pUrb->enmStatus = vusbProxyDarwinStatusToVUsbStatus(irc);
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ pUrb->cbData += sizeof(VUSBSETUP);
+ }
+ }
+
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ /*
+ * Link it into the taxing list.
+ */
+ pUrbOsX->pNext = NULL;
+ pUrbOsX->pPrev = pDevOsX->pTaxingTail;
+ if (pDevOsX->pTaxingTail)
+ pDevOsX->pTaxingTail->pNext = pUrbOsX;
+ else
+ pDevOsX->pTaxingHead = pUrbOsX;
+ pDevOsX->pTaxingTail = pUrbOsX;
+
+ RTCritSectLeave(&pDevOsX->CritSect);
+
+ LogFlow(("%s: usbProxyDarwinUrbAsyncComplete: cb=%d EndPt=%#x irc=%#x (%d)\n",
+ pUrb->pszDesc, cb, pUrb ? pUrb->EndPt : 0xff, irc, pUrb ? pUrb->enmStatus : 0xff));
+}
+
+/**
+ * Release all interfaces (current config).
+ *
+ * @param pDevOsX The darwin proxy device.
+ */
+static void usbProxyDarwinReleaseAllInterfaces(PUSBPROXYDEVOSX pDevOsX)
+{
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ /* Kick the reaper thread out of sleep. */
+ usbProxyDarwinReaperKick(pDevOsX);
+
+ PUSBPROXYIFOSX pIf = pDevOsX->pIfHead;
+ pDevOsX->pIfHead = pDevOsX->pIfTail = NULL;
+
+ while (pIf)
+ {
+ PUSBPROXYIFOSX pNext = pIf->pNext;
+ IOReturn irc;
+
+ if (pIf->RunLoopSrcRef)
+ {
+ int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops((PRTLISTANCHOR)&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef);
+ AssertRC(rc);
+
+ CFRelease(pIf->RunLoopSrcRef);
+ pIf->RunLoopSrcRef = NULL;
+ RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst);
+ }
+
+ while (pIf->pIsocBufCols)
+ {
+ PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols;
+ pIf->pIsocBufCols = pCur->pNext;
+ pCur->pNext = NULL;
+
+ irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvBuffer);
+ AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc));
+ pCur->pvBuffer = NULL;
+
+ irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvFrames);
+ AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc));
+ pCur->pvFrames = NULL;
+
+ RTMemFree(pCur);
+ }
+
+ irc = (*pIf->ppIfI)->USBInterfaceClose(pIf->ppIfI);
+ AssertMsg(irc == kIOReturnSuccess || irc == kIOReturnNoDevice, ("%#x\n", irc));
+
+ (*pIf->ppIfI)->Release(pIf->ppIfI);
+ pIf->ppIfI = NULL;
+
+ RTMemFree(pIf);
+
+ pIf = pNext;
+ }
+ RTCritSectLeave(&pDevOsX->CritSect);
+}
+
+
+/**
+ * Get the properties all the pipes associated with an interface.
+ *
+ * This is used when we seize all the interface and after SET_INTERFACE.
+ *
+ * @returns VBox status code.
+ * @param pDevOsX The darwin proxy device.
+ * @param pIf The interface to get pipe props for.
+ */
+static int usbProxyDarwinGetPipeProperties(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYIFOSX pIf)
+{
+ /*
+ * Get the pipe (endpoint) count (it might have changed - even on open).
+ */
+ int rc = VINF_SUCCESS;
+ bool fFullSpeed;
+ UInt32 u32UsecInFrame;
+ UInt8 cPipes;
+ IOReturn irc = (*pIf->ppIfI)->GetNumEndpoints(pIf->ppIfI, &cPipes);
+ if (irc != kIOReturnSuccess)
+ {
+ pIf->cPipes = 0;
+ if (irc == kIOReturnNoDevice)
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ else
+ rc = RTErrConvertFromDarwin(irc);
+ return rc;
+ }
+ AssertRelease(cPipes < RT_ELEMENTS(pIf->aPipes));
+ pIf->cPipes = cPipes + 1;
+
+ /* Find out if this is a full-speed interface (needed for isochronous support). */
+ irc = (*pIf->ppIfI)->GetFrameListTime(pIf->ppIfI, &u32UsecInFrame);
+ if (irc != kIOReturnSuccess)
+ {
+ pIf->cPipes = 0;
+ if (irc == kIOReturnNoDevice)
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ else
+ rc = RTErrConvertFromDarwin(irc);
+ return rc;
+ }
+ fFullSpeed = u32UsecInFrame == kUSBFullSpeedMicrosecondsInFrame;
+
+ /*
+ * Get the properties of each pipe.
+ */
+ for (unsigned i = 0; i < pIf->cPipes; i++)
+ {
+ pIf->aPipes[i].u8PipeRef = i;
+ pIf->aPipes[i].fIsFullSpeed = fFullSpeed;
+ pIf->aPipes[i].u64NextFrameNo = 0;
+ irc = (*pIf->ppIfI)->GetPipeProperties(pIf->ppIfI, i,
+ &pIf->aPipes[i].u8Direction,
+ &pIf->aPipes[i].u8Endpoint,
+ &pIf->aPipes[i].u8TransferType,
+ &pIf->aPipes[i].u16MaxPacketSize,
+ &pIf->aPipes[i].u8Interval);
+ if (irc != kIOReturnSuccess)
+ {
+ LogRel(("USB: Failed to query properties for pipe %#d / interface %#x on device '%s'. (prot=%#x class=%#x)\n",
+ i, pIf->u8Interface, pDevOsX->pProxyDev->pUsbIns->pszName, pIf->u8Protocol, pIf->u8Class));
+ if (irc == kIOReturnNoDevice)
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ else
+ rc = RTErrConvertFromDarwin(irc);
+ pIf->cPipes = i;
+ break;
+ }
+ /* reconstruct bEndpoint */
+ if (pIf->aPipes[i].u8Direction == kUSBIn)
+ pIf->aPipes[i].u8Endpoint |= 0x80;
+ Log2(("usbProxyDarwinGetPipeProperties: #If=%d EndPt=%#x Dir=%d Type=%d PipeRef=%#x MaxPktSize=%#x Interval=%#x\n",
+ pIf->u8Interface, pIf->aPipes[i].u8Endpoint, pIf->aPipes[i].u8Direction, pIf->aPipes[i].u8TransferType,
+ pIf->aPipes[i].u8PipeRef, pIf->aPipes[i].u16MaxPacketSize, pIf->aPipes[i].u8Interval));
+ }
+
+ /** @todo sort or hash these for speedy lookup... */
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Seize all interfaces (current config).
+ *
+ * @returns VBox status code.
+ * @param pDevOsX The darwin proxy device.
+ * @param fMakeTheBestOfIt If set we will not give up on error. This is for
+ * use during SET_CONFIGURATION and similar.
+ */
+static int usbProxyDarwinSeizeAllInterfaces(PUSBPROXYDEVOSX pDevOsX, bool fMakeTheBestOfIt)
+{
+ PUSBPROXYDEV pProxyDev = pDevOsX->pProxyDev;
+
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ /*
+ * Create a interface enumerator for all the interface (current config).
+ */
+ io_iterator_t Interfaces = IO_OBJECT_NULL;
+ IOUSBFindInterfaceRequest Req;
+ Req.bInterfaceClass = kIOUSBFindInterfaceDontCare;
+ Req.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
+ Req.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
+ Req.bAlternateSetting = kIOUSBFindInterfaceDontCare;
+ IOReturn irc = (*pDevOsX->ppDevI)->CreateInterfaceIterator(pDevOsX->ppDevI, &Req, &Interfaces);
+ int rc;
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * Iterate the interfaces.
+ */
+ io_object_t Interface;
+ rc = VINF_SUCCESS;
+ while ((Interface = IOIteratorNext(Interfaces)))
+ {
+ /*
+ * Create a plug-in and query the IOUSBInterfaceInterface (cute name).
+ */
+ IOCFPlugInInterface **ppPlugInInterface = NULL;
+ kern_return_t krc;
+ SInt32 Score = 0;
+ krc = IOCreatePlugInInterfaceForService(Interface, kIOUSBInterfaceUserClientTypeID,
+ kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score);
+ IOObjectRelease(Interface);
+ Interface = IO_OBJECT_NULL;
+ if (krc == KERN_SUCCESS)
+ {
+ IOUSBInterfaceInterface245 **ppIfI;
+ HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+ CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID245),
+ (LPVOID *)&ppIfI);
+ krc = IODestroyPlugInInterface(ppPlugInInterface); Assert(krc == KERN_SUCCESS);
+ ppPlugInInterface = NULL;
+ if (hrc == S_OK)
+ {
+ /*
+ * Query some basic properties first.
+ * (This means we can print more informative messages on failure
+ * to seize the interface.)
+ */
+ UInt8 u8Interface = 0xff;
+ irc = (*ppIfI)->GetInterfaceNumber(ppIfI, &u8Interface);
+ UInt8 u8AltSetting = 0xff;
+ if (irc == kIOReturnSuccess)
+ irc = (*ppIfI)->GetAlternateSetting(ppIfI, &u8AltSetting);
+ UInt8 u8Class = 0xff;
+ if (irc == kIOReturnSuccess)
+ irc = (*ppIfI)->GetInterfaceClass(ppIfI, &u8Class);
+ UInt8 u8Protocol = 0xff;
+ if (irc == kIOReturnSuccess)
+ irc = (*ppIfI)->GetInterfaceProtocol(ppIfI, &u8Protocol);
+ UInt8 cEndpoints = 0;
+ if (irc == kIOReturnSuccess)
+ irc = (*ppIfI)->GetNumEndpoints(ppIfI, &cEndpoints);
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * Try seize the interface.
+ */
+ irc = (*ppIfI)->USBInterfaceOpenSeize(ppIfI);
+ if (irc == kIOReturnSuccess)
+ {
+ PUSBPROXYIFOSX pIf = (PUSBPROXYIFOSX)RTMemAllocZ(sizeof(*pIf));
+ if (pIf)
+ {
+ /*
+ * Create the per-interface entry and query the
+ * endpoint data.
+ */
+ /* initialize the entry */
+ pIf->u8Interface = u8Interface;
+ pIf->u8AltSetting = u8AltSetting;
+ pIf->u8Class = u8Class;
+ pIf->u8Protocol = u8Protocol;
+ pIf->cPipes = cEndpoints;
+ pIf->ppIfI = ppIfI;
+
+ /* query pipe/endpoint properties. */
+ rc = usbProxyDarwinGetPipeProperties(pDevOsX, pIf);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create the async event source and add it to the
+ * default current run loop.
+ * (Later: Add to the worker thread run loop instead.)
+ */
+ irc = (*ppIfI)->CreateInterfaceAsyncEventSource(ppIfI, &pIf->RunLoopSrcRef);
+ if (irc == kIOReturnSuccess)
+ {
+ RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst);
+ usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst,
+ pIf->RunLoopSrcRef);
+
+ /*
+ * Just link the interface into the list and we're good.
+ */
+ pIf->pNext = NULL;
+ Log(("USB: Seized interface %#x (alt=%d prot=%#x class=%#x)\n",
+ u8Interface, u8AltSetting, u8Protocol, u8Class));
+ if (pDevOsX->pIfTail)
+ pDevOsX->pIfTail = pDevOsX->pIfTail->pNext = pIf;
+ else
+ pDevOsX->pIfTail = pDevOsX->pIfHead = pIf;
+ continue;
+ }
+ rc = RTErrConvertFromDarwin(irc);
+ }
+
+ /* failure cleanup. */
+ RTMemFree(pIf);
+ }
+ }
+ else if (irc == kIOReturnExclusiveAccess)
+ {
+ LogRel(("USB: Interface %#x on device '%s' is being used by another process. (prot=%#x class=%#x)\n",
+ u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class));
+ rc = VERR_SHARING_VIOLATION;
+ }
+ else
+ {
+ LogRel(("USB: Failed to open interface %#x on device '%s'. (prot=%#x class=%#x) krc=%#x\n",
+ u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class, irc));
+ rc = VERR_OPEN_FAILED;
+ }
+ }
+ else
+ {
+ rc = RTErrConvertFromDarwin(irc);
+ LogRel(("USB: Failed to query interface properties on device '%s', irc=%#x.\n",
+ pProxyDev->pUsbIns->pszName, irc));
+ }
+ (*ppIfI)->Release(ppIfI);
+ ppIfI = NULL;
+ }
+ else if (RT_SUCCESS(rc))
+ rc = RTErrConvertFromDarwinCOM(hrc);
+ }
+ else if (RT_SUCCESS(rc))
+ rc = RTErrConvertFromDarwin(krc);
+ if (!fMakeTheBestOfIt)
+ {
+ usbProxyDarwinReleaseAllInterfaces(pDevOsX);
+ break;
+ }
+ } /* iterate */
+ IOObjectRelease(Interfaces);
+ }
+ else if (irc == kIOReturnNoDevice)
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ else
+ {
+ AssertMsgFailed(("%#x\n", irc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+
+ RTCritSectLeave(&pDevOsX->CritSect);
+ return rc;
+}
+
+
+/**
+ * Find a particular interface.
+ *
+ * @returns The requested interface or NULL if not found.
+ * @param pDevOsX The darwin proxy device.
+ * @param u8Interface The interface number.
+ */
+static PUSBPROXYIFOSX usbProxyDarwinGetInterface(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Interface)
+{
+ if (!pDevOsX->pIfHead)
+ usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */);
+
+ PUSBPROXYIFOSX pIf;
+ for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext)
+ if (pIf->u8Interface == u8Interface)
+ return pIf;
+
+/* AssertMsgFailed(("Cannot find If#=%d\n", u8Interface)); - the 3rd quickcam interface is capture by the ****ing audio crap. */
+ return NULL;
+}
+
+
+/**
+ * Find a particular endpoint.
+ *
+ * @returns The requested interface or NULL if not found.
+ * @param pDevOsX The darwin proxy device.
+ * @param u8Endpoint The endpoint.
+ * @param pu8PipeRef Where to store the darwin pipe ref.
+ * @param ppPipe Where to store the darwin pipe pointer. (optional)
+ */
+static PUSBPROXYIFOSX usbProxyDarwinGetInterfaceForEndpoint(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Endpoint,
+ uint8_t *pu8PipeRef, PPUSBPROXYPIPEOSX ppPipe)
+{
+ if (!pDevOsX->pIfHead)
+ usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */);
+
+ PUSBPROXYIFOSX pIf;
+ for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext)
+ {
+ unsigned i = pIf->cPipes;
+ while (i-- > 0)
+ if (pIf->aPipes[i].u8Endpoint == u8Endpoint)
+ {
+ *pu8PipeRef = pIf->aPipes[i].u8PipeRef;
+ if (ppPipe)
+ *ppPipe = &pIf->aPipes[i];
+ return pIf;
+ }
+ }
+
+ AssertMsgFailed(("Cannot find EndPt=%#x\n", u8Endpoint));
+ return NULL;
+}
+
+
+/**
+ * Gets an unsigned 32-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu32 Where to store the key value.
+ */
+static bool usbProxyDarwinDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32))
+ return true;
+ }
+ *pu32 = 0;
+ return false;
+}
+
+
+/**
+ * Gets an unsigned 64-bit integer value.
+ *
+ * @returns Success indicator (true/false).
+ * @param DictRef The dictionary.
+ * @param KeyStrRef The key name.
+ * @param pu64 Where to store the key value.
+ */
+static bool usbProxyDarwinDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64)
+{
+ CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef);
+ if (ValRef)
+ {
+ if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64))
+ return true;
+ }
+ *pu64 = 0;
+ return false;
+}
+
+
+static DECLCALLBACK(void) usbProxyDarwinPerformWakeup(void *pInfo)
+{
+ RT_NOREF(pInfo);
+ return;
+}
+
+
+/* -=-=-=-=-=- The exported methods -=-=-=-=-=- */
+
+/**
+ * Opens the USB Device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device instance.
+ * @param pszAddress The session id and/or location id of the device to open.
+ * The format of this string is something iokit.c in Main defines, currently
+ * it's sequences of "[l|s]=<value>" separated by ";".
+ */
+static DECLCALLBACK(int) usbProxyDarwinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ LogFlow(("usbProxyDarwinOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ /*
+ * Init globals once.
+ */
+ int vrc = RTOnce(&g_usbProxyDarwinOnce, usbProxyDarwinInitOnce, NULL);
+ AssertRCReturn(vrc, vrc);
+
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+
+ /*
+ * The idea here was to create a matching directory with the sessionID
+ * and locationID included, however this doesn't seem to work. So, we'll
+ * use the product id and vendor id to limit the set of matching device
+ * and manually match these two properties. sigh.
+ * (Btw. vendor and product id must be used *together* apparently.)
+ *
+ * Wonder if we could use the entry path? Docs indicates says we must
+ * use IOServiceGetMatchingServices and I'm not in a mood to explore
+ * this subject further right now. Maybe check this later.
+ */
+ CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName);
+ AssertReturn(RefMatchingDict != NULL, VERR_OPEN_FAILED);
+
+ uint64_t u64SessionId = 0;
+ uint32_t u32LocationId = 0;
+ const char *psz = pszAddress;
+ do
+ {
+ const char chValue = *psz;
+ AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR);
+ uint64_t u64Value;
+ int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value);
+ AssertReleaseRCReturn(rc, rc);
+ AssertReleaseReturn(!*psz || *psz == ';', rc);
+ switch (chValue)
+ {
+ case 'l':
+ u32LocationId = (uint32_t)u64Value;
+ break;
+ case 's':
+ u64SessionId = u64Value;
+ break;
+ case 'p':
+ case 'v':
+ {
+#if 0 /* Guess what, this doesn't 'ing work either! */
+ SInt32 i32 = (int16_t)u64Value;
+ CFNumberRef Num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i32);
+ AssertBreak(Num);
+ CFDictionarySetValue(RefMatchingDict, chValue == 'p' ? CFSTR(kUSBProductID) : CFSTR(kUSBVendorID), Num);
+ CFRelease(Num);
+#endif
+ break;
+ }
+ default:
+ AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR);
+ }
+ if (*psz == ';')
+ psz++;
+ } while (*psz);
+
+ io_iterator_t USBDevices = IO_OBJECT_NULL;
+ IOReturn irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices);
+ AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%#x\n", irc), RTErrConvertFromDarwinIO(irc));
+ RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */
+
+ unsigned cMatches = 0;
+ io_object_t USBDevice;
+ while ((USBDevice = IOIteratorNext(USBDevices)))
+ {
+ cMatches++;
+ CFMutableDictionaryRef PropsRef = 0;
+ kern_return_t krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions);
+ if (krc == KERN_SUCCESS)
+ {
+ uint64_t u64CurSessionId;
+ uint32_t u32CurLocationId;
+ if ( ( !u64SessionId
+ || ( usbProxyDarwinDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId)
+ && u64CurSessionId == u64SessionId))
+ && ( !u32LocationId
+ || ( usbProxyDarwinDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId)
+ && u32CurLocationId == u32LocationId))
+ )
+ {
+ CFRelease(PropsRef);
+ break;
+ }
+ CFRelease(PropsRef);
+ }
+ IOObjectRelease(USBDevice);
+ }
+ IOObjectRelease(USBDevices);
+ USBDevices = IO_OBJECT_NULL;
+ if (!USBDevice)
+ {
+ LogRel(("USB: Device '%s' not found (%d pid+vid matches)\n", pszAddress, cMatches));
+ IOObjectRelease(USBDevices);
+ return VERR_VUSB_DEVICE_NAME_NOT_FOUND;
+ }
+
+ /*
+ * Create a plugin interface for the device and query its IOUSBDeviceInterface.
+ */
+ SInt32 Score = 0;
+ IOCFPlugInInterface **ppPlugInInterface = NULL;
+ irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID,
+ kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score);
+ if (irc == kIOReturnSuccess)
+ {
+ IOUSBDeviceInterface245 **ppDevI = NULL;
+ HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface,
+ CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245),
+ (LPVOID *)&ppDevI);
+ irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess);
+ ppPlugInInterface = NULL;
+ if (hrc == S_OK)
+ {
+ /*
+ * Try open the device for exclusive access.
+ * If we fail, we'll try figure out who is using the device and
+ * convince them to let go of it...
+ */
+ irc = (*ppDevI)->USBDeviceReEnumerate(ppDevI, kUSBReEnumerateCaptureDeviceMask);
+ Log(("USBDeviceReEnumerate (capture) returned irc=%#x\n", irc));
+
+ irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI);
+ if (irc == kIOReturnExclusiveAccess)
+ {
+ RTThreadSleep(20);
+ irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI);
+ }
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * Init a proxy device instance.
+ */
+ RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst);
+ vrc = RTCritSectInit(&pDevOsX->CritSect);
+ if (RT_SUCCESS(vrc))
+ {
+ pDevOsX->USBDevice = USBDevice;
+ pDevOsX->ppDevI = ppDevI;
+ pDevOsX->pProxyDev = pProxyDev;
+ pDevOsX->pTaxingHead = NULL;
+ pDevOsX->pTaxingTail = NULL;
+ pDevOsX->hRunLoopReapingLast = NULL;
+
+ /*
+ * Try seize all the interface.
+ */
+ char *pszDummyName = pProxyDev->pUsbIns->pszName;
+ pProxyDev->pUsbIns->pszName = (char *)pszAddress;
+ vrc = usbProxyDarwinSeizeAllInterfaces(pDevOsX, false /* give up on failure */);
+ pProxyDev->pUsbIns->pszName = pszDummyName;
+ if (RT_SUCCESS(vrc))
+ {
+ /*
+ * Create the async event source and add it to the run loop.
+ */
+ irc = (*ppDevI)->CreateDeviceAsyncEventSource(ppDevI, &pDevOsX->RunLoopSrcRef);
+ if (irc == kIOReturnSuccess)
+ {
+ /*
+ * Determine the active configuration.
+ * Can cause hangs, so drop it for now.
+ */
+ /** @todo test Palm. */
+ //uint8_t u8Cfg;
+ //irc = (*ppDevI)->GetConfiguration(ppDevI, &u8Cfg);
+ if (irc != kIOReturnNoDevice)
+ {
+ CFRunLoopSourceContext CtxRunLoopSource;
+ CtxRunLoopSource.version = 0;
+ CtxRunLoopSource.info = NULL;
+ CtxRunLoopSource.retain = NULL;
+ CtxRunLoopSource.release = NULL;
+ CtxRunLoopSource.copyDescription = NULL;
+ CtxRunLoopSource.equal = NULL;
+ CtxRunLoopSource.hash = NULL;
+ CtxRunLoopSource.schedule = NULL;
+ CtxRunLoopSource.cancel = NULL;
+ CtxRunLoopSource.perform = usbProxyDarwinPerformWakeup;
+ pDevOsX->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource);
+ if (CFRunLoopSourceIsValid(pDevOsX->hRunLoopSrcWakeRef))
+ {
+ //pProxyDev->iActiveCfg = irc == kIOReturnSuccess ? u8Cfg : -1;
+ RTListInit(&pDevOsX->HeadOfRunLoopWakeLst);
+ pProxyDev->iActiveCfg = -1;
+ pProxyDev->cIgnoreSetConfigs = 1;
+
+ usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef);
+ return VINF_SUCCESS; /* return */
+ }
+ else
+ {
+ LogRel(("USB: Device '%s' out of memory allocating runloop source\n", pszAddress));
+ vrc = VERR_NO_MEMORY;
+ }
+ }
+ vrc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+ else
+ vrc = RTErrConvertFromDarwin(irc);
+
+ usbProxyDarwinReleaseAllInterfaces(pDevOsX);
+ }
+ /* else: already bitched */
+
+ RTCritSectDelete(&pDevOsX->CritSect);
+ }
+
+ irc = (*ppDevI)->USBDeviceClose(ppDevI);
+ AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc));
+ }
+ else if (irc == kIOReturnExclusiveAccess)
+ {
+ LogRel(("USB: Device '%s' is being used by another process\n", pszAddress));
+ vrc = VERR_SHARING_VIOLATION;
+ }
+ else
+ {
+ LogRel(("USB: Failed to open device '%s', irc=%#x.\n", pszAddress, irc));
+ vrc = VERR_OPEN_FAILED;
+ }
+ }
+ else
+ {
+ LogRel(("USB: Failed to create plugin interface for device '%s', hrc=%#x.\n", pszAddress, hrc));
+ vrc = VERR_OPEN_FAILED;
+ }
+
+ (*ppDevI)->Release(ppDevI);
+ }
+ else
+ {
+ LogRel(("USB: Failed to open device '%s', plug-in creation failed with irc=%#x.\n", pszAddress, irc));
+ vrc = RTErrConvertFromDarwin(irc);
+ }
+
+ return vrc;
+}
+
+
+/**
+ * Closes the proxy device.
+ */
+static DECLCALLBACK(void) usbProxyDarwinClose(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow(("usbProxyDarwinClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ AssertPtrReturnVoid(pDevOsX);
+
+ /*
+ * Release interfaces we've laid claim to, then reset the device
+ * and finally close it.
+ */
+ RTCritSectEnter(&pDevOsX->CritSect);
+ /* ?? */
+ RTCritSectLeave(&pDevOsX->CritSect);
+
+ usbProxyDarwinReleaseAllInterfaces(pDevOsX);
+
+ if (pDevOsX->RunLoopSrcRef)
+ {
+ int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef);
+ AssertRC(rc);
+
+ RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst);
+
+ CFRelease(pDevOsX->RunLoopSrcRef);
+ pDevOsX->RunLoopSrcRef = NULL;
+ }
+
+ if (pDevOsX->hRunLoopSrcWakeRef)
+ {
+ int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef);
+ AssertRC(rc);
+
+ RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopWakeLst);
+
+ CFRelease(pDevOsX->hRunLoopSrcWakeRef);
+ pDevOsX->hRunLoopSrcWakeRef = NULL;
+ }
+
+ IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI);
+
+ irc = (*pDevOsX->ppDevI)->USBDeviceClose(pDevOsX->ppDevI);
+ if (irc != kIOReturnSuccess && irc != kIOReturnNoDevice)
+ {
+ LogRel(("USB: USBDeviceClose -> %#x\n", irc));
+ AssertMsgFailed(("irc=%#x\n", irc));
+ }
+
+ irc = (*pDevOsX->ppDevI)->USBDeviceReEnumerate(pDevOsX->ppDevI, kUSBReEnumerateReleaseDeviceMask);
+ Log(("USBDeviceReEnumerate (release) returned irc=%#x\n", irc));
+
+ (*pDevOsX->ppDevI)->Release(pDevOsX->ppDevI);
+ pDevOsX->ppDevI = NULL;
+ kern_return_t krc = IOObjectRelease(pDevOsX->USBDevice); Assert(krc == KERN_SUCCESS); NOREF(krc);
+ pDevOsX->USBDevice = IO_OBJECT_NULL;
+ pDevOsX->pProxyDev = NULL;
+
+ /*
+ * Free all the resources.
+ */
+ RTCritSectDelete(&pDevOsX->CritSect);
+
+ PUSBPROXYURBOSX pUrbOsX;
+ while ((pUrbOsX = pDevOsX->pFreeHead) != NULL)
+ {
+ pDevOsX->pFreeHead = pUrbOsX->pNext;
+ RTMemFree(pUrbOsX);
+ }
+
+ LogFlow(("usbProxyDarwinClose: returns\n"));
+}
+
+
+/** @interface_method_impl{USBPROXYBACK,pfnReset}*/
+static DECLCALLBACK(int) usbProxyDarwinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ RT_NOREF(fResetOnLinux);
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ LogFlow(("usbProxyDarwinReset: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI);
+ int rc;
+ if (irc == kIOReturnSuccess)
+ {
+ /** @todo Some docs say that some drivers will do a default config, check this out ... */
+ pProxyDev->cIgnoreSetConfigs = 0;
+ pProxyDev->iActiveCfg = -1;
+
+ rc = VINF_SUCCESS;
+ }
+ else if (irc == kIOReturnNoDevice)
+ rc = VERR_VUSB_DEVICE_NOT_ATTACHED;
+ else
+ {
+ AssertMsgFailed(("irc=%#x\n", irc));
+ rc = VERR_GENERAL_FAILURE;
+ }
+
+ LogFlow(("usbProxyDarwinReset: returns success %Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * SET_CONFIGURATION.
+ *
+ * The caller makes sure that it's not called first time after open or reset
+ * with the active interface.
+ *
+ * @returns success indicator.
+ * @param pProxyDev The device instance data.
+ * @param iCfg The configuration to set.
+ */
+static DECLCALLBACK(int) usbProxyDarwinSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ LogFlow(("usbProxyDarwinSetConfig: pProxyDev=%s cfg=%#x\n",
+ pProxyDev->pUsbIns->pszName, iCfg));
+
+ IOReturn irc = (*pDevOsX->ppDevI)->SetConfiguration(pDevOsX->ppDevI, (uint8_t)iCfg);
+ if (irc != kIOReturnSuccess)
+ {
+ Log(("usbProxyDarwinSetConfig: Set configuration -> %#x\n", irc));
+ return RTErrConvertFromDarwin(irc);
+ }
+
+ usbProxyDarwinReleaseAllInterfaces(pDevOsX);
+ usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Claims an interface.
+ *
+ * This is a stub on Darwin since we release/claim all interfaces at
+ * open/reset/setconfig time.
+ *
+ * @returns success indicator (always VINF_SUCCESS).
+ */
+static DECLCALLBACK(int) usbProxyDarwinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ RT_NOREF(pProxyDev, iIf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Releases an interface.
+ *
+ * This is a stub on Darwin since we release/claim all interfaces at
+ * open/reset/setconfig time.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyDarwinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ RT_NOREF(pProxyDev, iIf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * SET_INTERFACE.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyDarwinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ IOReturn irc = kIOReturnSuccess;
+ PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterface(pDevOsX, iIf);
+ LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s iIf=%#x iAlt=%#x iCurAlt=%#x\n",
+ pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf ? pIf->u8AltSetting : 0xbeef));
+ if (pIf)
+ {
+ /* Avoid SetAlternateInterface when possible as it will recreate the pipes. */
+ if (iAlt != pIf->u8AltSetting)
+ {
+ irc = (*pIf->ppIfI)->SetAlternateInterface(pIf->ppIfI, iAlt);
+ if (irc == kIOReturnSuccess)
+ {
+ usbProxyDarwinGetPipeProperties(pDevOsX, pIf);
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ /*
+ * Just send the request anyway?
+ */
+ IOUSBDevRequest Req;
+ Req.bmRequestType = 0x01;
+ Req.bRequest = 0x0b; /* SET_INTERFACE */
+ Req.wIndex = iIf;
+ Req.wValue = iAlt;
+ Req.wLength = 0;
+ Req.wLenDone = 0;
+ Req.pData = NULL;
+ irc = (*pDevOsX->ppDevI)->DeviceRequest(pDevOsX->ppDevI, &Req);
+ Log(("usbProxyDarwinSetInterface: SET_INTERFACE(%d,%d) -> irc=%#x\n", iIf, iAlt, irc));
+ return VINF_SUCCESS;
+ }
+ }
+
+ LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s eiIf=%#x iAlt=%#x - failure - pIf=%p irc=%#x\n",
+ pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf, irc));
+ return RTErrConvertFromDarwin(irc);
+}
+
+
+/**
+ * Clears the halted endpoint 'EndPt'.
+ */
+static DECLCALLBACK(int) usbProxyDarwinClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x\n", pProxyDev->pUsbIns->pszName, EndPt));
+
+ /*
+ * Clearing the zero control pipe doesn't make sense and isn't
+ * supported by the API. Just ignore it.
+ */
+ if (EndPt == 0)
+ return VINF_SUCCESS;
+
+ /*
+ * Find the interface/pipe combination and invoke the ClearPipeStallBothEnds
+ * method. (The ResetPipe and ClearPipeStall methods will not send the
+ * CLEAR_FEATURE(ENDPOINT_HALT) request that this method implements.)
+ */
+ IOReturn irc = kIOReturnSuccess;
+ uint8_t u8PipeRef;
+ PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL);
+ if (pIf)
+ {
+ irc = (*pIf->ppIfI)->ClearPipeStallBothEnds(pIf->ppIfI, u8PipeRef);
+ if (irc == kIOReturnSuccess)
+ return VINF_SUCCESS;
+ AssertMsg(irc == kIOReturnNoDevice || irc == kIOReturnNotResponding, ("irc=#x (control pipe?)\n", irc));
+ }
+
+ LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x - failure - pIf=%p irc=%#x\n",
+ pProxyDev->pUsbIns->pszName, EndPt, pIf, irc));
+ return RTErrConvertFromDarwin(irc);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxyDarwinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n",
+ pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData));
+
+ /*
+ * Find the target interface / pipe.
+ */
+ uint8_t u8PipeRef = 0xff;
+ PUSBPROXYIFOSX pIf = NULL;
+ PUSBPROXYPIPEOSX pPipe = NULL;
+ if (pUrb->EndPt)
+ {
+ /* Make sure the interface is there. */
+ const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0);
+ pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, &pPipe);
+ if (!pIf)
+ {
+ LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - can't find interface / pipe!!!\n",
+ pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData));
+ return VERR_NOT_FOUND;
+ }
+ }
+ /* else: pIf == NULL -> default control pipe.*/
+
+ /*
+ * Allocate a Darwin urb.
+ */
+ PUSBPROXYURBOSX pUrbOsX = usbProxyDarwinUrbAlloc(pDevOsX);
+ if (!pUrbOsX)
+ return VERR_NO_MEMORY;
+
+ pUrbOsX->u64SubmitTS = RTTimeMilliTS();
+ pUrbOsX->pVUsbUrb = pUrb;
+ pUrbOsX->pDevOsX = pDevOsX;
+ pUrbOsX->enmType = pUrb->enmType;
+
+ /*
+ * Submit the request.
+ */
+ IOReturn irc = kIOReturnError;
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ {
+ AssertMsgBreak(pUrb->cbData >= sizeof(VUSBSETUP), ("cbData=%d\n", pUrb->cbData));
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0];
+ pUrbOsX->u.ControlMsg.bmRequestType = pSetup->bmRequestType;
+ pUrbOsX->u.ControlMsg.bRequest = pSetup->bRequest;
+ pUrbOsX->u.ControlMsg.wValue = pSetup->wValue;
+ pUrbOsX->u.ControlMsg.wIndex = pSetup->wIndex;
+ pUrbOsX->u.ControlMsg.wLength = pSetup->wLength;
+ pUrbOsX->u.ControlMsg.pData = pSetup + 1;
+ pUrbOsX->u.ControlMsg.wLenDone = pSetup->wLength;
+
+ if (pIf)
+ irc = (*pIf->ppIfI)->ControlRequestAsync(pIf->ppIfI, u8PipeRef, &pUrbOsX->u.ControlMsg,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+ else
+ irc = (*pDevOsX->ppDevI)->DeviceRequestAsync(pDevOsX->ppDevI, &pUrbOsX->u.ControlMsg,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+ break;
+ }
+
+ case VUSBXFERTYPE_BULK:
+ case VUSBXFERTYPE_INTR:
+ {
+ AssertBreak(pIf);
+ Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT);
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ irc = (*pIf->ppIfI)->WritePipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+ else
+ irc = (*pIf->ppIfI)->ReadPipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+
+ break;
+ }
+
+ case VUSBXFERTYPE_ISOC:
+ {
+ AssertBreak(pIf);
+ Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT);
+
+#ifdef USE_LOW_LATENCY_API
+ /* Allocate an isochronous buffer and copy over the data. */
+ AssertBreak(pUrb->cbData <= 8192);
+ int rc = usbProxyDarwinUrbAllocIsocBuf(pUrbOsX, pIf);
+ AssertRCBreak(rc);
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ memcpy(pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->abData, pUrb->cbData);
+ else
+ memset(pUrbOsX->u.Isoc.pBuf->pvBuf, 0xfe, pUrb->cbData);
+#endif
+
+ /* Get the current frame number (+2) and make sure it doesn't
+ overlap with the previous request. See WARNING in
+ ApplUSBUHCI::CreateIsochTransfer for details on the +2. */
+ UInt64 FrameNo;
+ AbsoluteTime FrameTime;
+ irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime);
+ AssertMsg(irc == kIOReturnSuccess, ("GetBusFrameNumber -> %#x\n", irc));
+ FrameNo += 2;
+ if (FrameNo <= pPipe->u64NextFrameNo)
+ FrameNo = pPipe->u64NextFrameNo;
+
+ for (unsigned j = 0; ; j++)
+ {
+ unsigned i;
+ for (i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ pUrbOsX->u.Isoc.aFrames[i].frReqCount = pUrb->aIsocPkts[i].cb;
+ pUrbOsX->u.Isoc.aFrames[i].frActCount = 0;
+ pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOUSBNotSent1Err;
+#ifdef USE_LOW_LATENCY_API
+ pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0;
+ pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0;
+#endif
+ }
+ for (; i < RT_ELEMENTS(pUrbOsX->u.Isoc.aFrames); i++)
+ {
+ pUrbOsX->u.Isoc.aFrames[i].frReqCount = 0;
+ pUrbOsX->u.Isoc.aFrames[i].frActCount = 0;
+ pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOReturnError;
+#ifdef USE_LOW_LATENCY_API
+ pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0;
+ pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0;
+#endif
+ }
+
+#ifdef USE_LOW_LATENCY_API
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ irc = (*pIf->ppIfI)->LowLatencyWriteIsochPipeAsync(pIf->ppIfI, u8PipeRef,
+ pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+ else
+ irc = (*pIf->ppIfI)->LowLatencyReadIsochPipeAsync(pIf->ppIfI, u8PipeRef,
+ pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames,
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+#else
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ irc = (*pIf->ppIfI)->WriteIsochPipeAsync(pIf->ppIfI, u8PipeRef,
+ pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0],
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+ else
+ irc = (*pIf->ppIfI)->ReadIsochPipeAsync(pIf->ppIfI, u8PipeRef,
+ pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0],
+ usbProxyDarwinUrbAsyncComplete, pUrbOsX);
+#endif
+ if ( irc != kIOReturnIsoTooOld
+ || j >= 5)
+ {
+ Log(("%s: usbProxyDarwinUrbQueue: isoc: u64NextFrameNo=%RX64 FrameNo=%RX64 #Frames=%d j=%d (pipe=%d)\n",
+ pUrb->pszDesc, pPipe->u64NextFrameNo, FrameNo, pUrb->cIsocPkts, j, u8PipeRef));
+ if (irc == kIOReturnSuccess)
+ {
+ if (pPipe->fIsFullSpeed)
+ pPipe->u64NextFrameNo = FrameNo + pUrb->cIsocPkts;
+ else
+ pPipe->u64NextFrameNo = FrameNo + 1;
+ }
+ break;
+ }
+
+ /* try again... */
+ irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime);
+ if (FrameNo <= pPipe->u64NextFrameNo)
+ FrameNo = pPipe->u64NextFrameNo;
+ FrameNo += j;
+ }
+ break;
+ }
+
+ default:
+ AssertMsgFailed(("%s: enmType=%#x\n", pUrb->pszDesc, pUrb->enmType));
+ break;
+ }
+
+ /*
+ * Success?
+ */
+ if (RT_LIKELY(irc == kIOReturnSuccess))
+ {
+ Log(("%s: usbProxyDarwinUrbQueue: success\n", pUrb->pszDesc));
+ return VINF_SUCCESS;
+ }
+ switch (irc)
+ {
+ case kIOUSBPipeStalled:
+ {
+ /* Increment in flight counter because the completion handler will decrease it always. */
+ usbProxyDarwinUrbAsyncComplete(pUrbOsX, kIOUSBPipeStalled, 0);
+ Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x! (stall)\n",
+ pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc));
+ return VINF_SUCCESS;
+ }
+ }
+
+ usbProxyDarwinUrbFree(pDevOsX, pUrbOsX);
+ Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x!\n",
+ pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc));
+ return RTErrConvertFromDarwin(irc);
+}
+
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyDarwinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ PVUSBURB pUrb = NULL;
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ CFRunLoopRef hRunLoopRef = CFRunLoopGetCurrent();
+
+ Assert(!pDevOsX->hRunLoopReaping);
+
+ /*
+ * If the last seen runloop for reaping differs we have to check whether the
+ * the runloop sources are in the new runloop.
+ */
+ if (pDevOsX->hRunLoopReapingLast != hRunLoopRef)
+ {
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ /* Every pipe. */
+ if (!pDevOsX->pIfHead)
+ usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */);
+
+ PUSBPROXYIFOSX pIf;
+ for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext)
+ {
+ if (!CFRunLoopContainsSource(hRunLoopRef, pIf->RunLoopSrcRef, g_pRunLoopMode))
+ usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef);
+ }
+
+ /* Default control pipe. */
+ if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->RunLoopSrcRef, g_pRunLoopMode))
+ usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef);
+
+ /* Runloop wakeup source. */
+ if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->hRunLoopSrcWakeRef, g_pRunLoopMode))
+ usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef);
+ RTCritSectLeave(&pDevOsX->CritSect);
+
+ pDevOsX->hRunLoopReapingLast = hRunLoopRef;
+ }
+
+ ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, hRunLoopRef);
+
+ if (ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false))
+ {
+ /* Return immediately. */
+ ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL);
+ return NULL;
+ }
+
+ /*
+ * Excercise the runloop until we get an URB or we time out.
+ */
+ if ( !pDevOsX->pTaxingHead
+ && cMillies)
+ CFRunLoopRunInMode(g_pRunLoopMode, cMillies / 1000.0, true);
+
+ ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL);
+ ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false);
+
+ /*
+ * Any URBs pending delivery?
+ */
+ while ( pDevOsX->pTaxingHead
+ && !pUrb)
+ {
+ RTCritSectEnter(&pDevOsX->CritSect);
+
+ PUSBPROXYURBOSX pUrbOsX = pDevOsX->pTaxingHead;
+ if (pUrbOsX)
+ {
+ /*
+ * Remove from the taxing list.
+ */
+ if (pUrbOsX->pNext)
+ pUrbOsX->pNext->pPrev = pUrbOsX->pPrev;
+ else if (pDevOsX->pTaxingTail == pUrbOsX)
+ pDevOsX->pTaxingTail = pUrbOsX->pPrev;
+
+ if (pUrbOsX->pPrev)
+ pUrbOsX->pPrev->pNext = pUrbOsX->pNext;
+ else if (pDevOsX->pTaxingHead == pUrbOsX)
+ pDevOsX->pTaxingHead = pUrbOsX->pNext;
+ else
+ AssertFailed();
+
+ pUrb = pUrbOsX->pVUsbUrb;
+ if (pUrb)
+ {
+ pUrb->Dev.pvPrivate = NULL;
+ usbProxyDarwinUrbFree(pDevOsX, pUrbOsX);
+ }
+ }
+ RTCritSectLeave(&pDevOsX->CritSect);
+ }
+
+ if (pUrb)
+ LogFlowFunc(("LEAVE: %s: pProxyDev=%s returns %p\n", pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb));
+ else
+ LogFlowFunc(("LEAVE: NULL pProxyDev=%s returns NULL\n", pProxyDev->pUsbIns->pszName));
+
+ return pUrb;
+}
+
+
+/**
+ * Cancels a URB.
+ *
+ * The URB requires reaping, so we don't change its state.
+ *
+ * @remark There isn't any way to cancel a specific async request
+ * on darwin. The interface only supports the aborting of
+ * all URBs pending on an interface / pipe pair. Provided
+ * the card does the URB cancelling before submitting new
+ * requests, we should probably be fine...
+ */
+static DECLCALLBACK(int) usbProxyDarwinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+ LogFlow(("%s: usbProxyDarwinUrbCancel: pProxyDev=%s EndPt=%d\n",
+ pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt));
+
+ /*
+ * Determine the interface / endpoint ref and invoke AbortPipe.
+ */
+ IOReturn irc = kIOReturnSuccess;
+ if (!pUrb->EndPt)
+ irc = (*pDevOsX->ppDevI)->USBDeviceAbortPipeZero(pDevOsX->ppDevI);
+ else
+ {
+ uint8_t u8PipeRef;
+ const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0);
+ PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL);
+ if (pIf)
+ irc = (*pIf->ppIfI)->AbortPipe(pIf->ppIfI, u8PipeRef);
+ else /* this may happen if a device reset, set configuration or set interface has been performed. */
+ Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d - cannot find the interface / pipe!\n",
+ pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt));
+ }
+
+ int rc = VINF_SUCCESS;
+ if (irc != kIOReturnSuccess)
+ {
+ Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d -> %#x!\n",
+ pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, irc));
+ rc = RTErrConvertFromDarwin(irc);
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) usbProxyDarwinWakeup(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX);
+
+ LogFlow(("usbProxyDarwinWakeup: pProxyDev=%p\n", pProxyDev));
+
+ ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, true);
+ usbProxyDarwinReaperKick(pDevOsX);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * The Darwin USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ /* pszName */
+ "host",
+ /* cbBackend */
+ sizeof(USBPROXYDEVOSX),
+ usbProxyDarwinOpen,
+ NULL,
+ usbProxyDarwinClose,
+ usbProxyDarwinReset,
+ usbProxyDarwinSetConfig,
+ usbProxyDarwinClaimInterface,
+ usbProxyDarwinReleaseInterface,
+ usbProxyDarwinSetInterface,
+ usbProxyDarwinClearHaltedEp,
+ usbProxyDarwinUrbQueue,
+ usbProxyDarwinUrbCancel,
+ usbProxyDarwinUrbReap,
+ usbProxyDarwinWakeup,
+ 0
+};
+
diff --git a/src/VBox/Devices/USB/freebsd/Makefile.kup b/src/VBox/Devices/USB/freebsd/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/freebsd/Makefile.kup
diff --git a/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp
new file mode 100644
index 00000000..8b694639
--- /dev/null
+++ b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp
@@ -0,0 +1,1068 @@
+/* $Id: USBProxyDevice-freebsd.cpp $ */
+/** @file
+ * USB device proxy - the FreeBSD backend.
+ */
+
+/*
+ * Includes contributions from Hans Petter Selasky
+ *
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#ifdef VBOX
+# include <iprt/stdint.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usb_ioctl.h>
+
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <VBox/vusb.h>
+#include <iprt/assert.h>
+#include <iprt/stream.h>
+#include <iprt/alloc.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/file.h>
+#include <iprt/pipe.h>
+#include "../USBProxyDevice.h"
+
+/** Maximum endpoints supported. */
+#define USBFBSD_MAXENDPOINTS 127
+#define USBFBSD_MAXFRAMES 56
+
+/** This really needs to be defined in vusb.h! */
+#ifndef VUSB_DIR_TO_DEV
+# define VUSB_DIR_TO_DEV 0x00
+#endif
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct USBENDPOINTFBSD
+{
+ /** Flag whether it is opened. */
+ bool fOpen;
+ /** Flag whether it is cancelling. */
+ bool fCancelling;
+ /** Buffer pointers. */
+ void *apvData[USBFBSD_MAXFRAMES];
+ /** Buffer lengths. */
+ uint32_t acbData[USBFBSD_MAXFRAMES];
+ /** Initial buffer length. */
+ uint32_t cbData0;
+ /** Pointer to the URB. */
+ PVUSBURB pUrb;
+ /** Copy of endpoint number. */
+ unsigned iEpNum;
+ /** Maximum transfer length. */
+ unsigned cMaxIo;
+ /** Maximum frame count. */
+ unsigned cMaxFrames;
+} USBENDPOINTFBSD, *PUSBENDPOINTFBSD;
+
+/**
+ * Data for the FreeBSD usb proxy backend.
+ */
+typedef struct USBPROXYDEVFBSD
+{
+ /** The open file. */
+ RTFILE hFile;
+ /** Flag whether an URB is cancelling. */
+ bool fCancelling;
+ /** Flag whether initialised or not */
+ bool fInit;
+ /** Pipe handle for waking up - writing end. */
+ RTPIPE hPipeWakeupW;
+ /** Pipe handle for waking up - reading end. */
+ RTPIPE hPipeWakeupR;
+ /** Software endpoint structures */
+ USBENDPOINTFBSD aSwEndpoint[USBFBSD_MAXENDPOINTS];
+ /** Kernel endpoint structures */
+ struct usb_fs_endpoint aHwEndpoint[USBFBSD_MAXENDPOINTS];
+} USBPROXYDEVFBSD, *PUSBPROXYDEVFBSD;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint);
+
+/**
+ * Wrapper for the ioctl call.
+ *
+ * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also
+ * handle ENODEV (detached device) errors.
+ *
+ * @returns whatever ioctl returns.
+ * @param pProxyDev The proxy device.
+ * @param iCmd The ioctl command / function.
+ * @param pvArg The ioctl argument / data.
+ * @param fHandleNoDev Whether to handle ENXIO.
+ * @internal
+ */
+static int usbProxyFreeBSDDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd,
+ void *pvArg, bool fHandleNoDev)
+{
+ int rc = VINF_SUCCESS;
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+
+ LogFlow(("usbProxyFreeBSDDoIoCtl: iCmd=%#x\n", iCmd));
+
+ do
+ {
+ rc = ioctl(RTFileToNative(pDevFBSD->hFile), iCmd, pvArg);
+ if (rc >= 0)
+ return VINF_SUCCESS;
+ } while (errno == EINTR);
+
+ if (errno == ENXIO && fHandleNoDev)
+ {
+ Log(("usbProxyFreeBSDDoIoCtl: ENXIO -> unplugged. pProxyDev=%s\n",
+ pProxyDev->pUsbIns->pszName));
+ errno = ENODEV;
+ }
+ else if (errno != EAGAIN)
+ {
+ LogFlow(("usbProxyFreeBSDDoIoCtl: Returned %d. pProxyDev=%s\n",
+ errno, pProxyDev->pUsbIns->pszName));
+ }
+ return RTErrConvertFromErrno(errno);
+}
+
+/**
+ * Init USB subsystem.
+ */
+static int usbProxyFreeBSDFsInit(PUSBPROXYDEV pProxyDev)
+{
+ struct usb_fs_init UsbFsInit;
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDFsInit: pProxyDev=%p\n", (void *)pProxyDev));
+
+ /* Sanity check */
+ AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER);
+
+ if (pDevFBSD->fInit == true)
+ return VINF_SUCCESS;
+
+ /* Zero default */
+ memset(&UsbFsInit, 0, sizeof(UsbFsInit));
+
+ UsbFsInit.pEndpoints = pDevFBSD->aHwEndpoint;
+ UsbFsInit.ep_index_max = USBFBSD_MAXENDPOINTS;
+
+ /* Init USB subsystem */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_INIT, &UsbFsInit, false);
+ if (RT_SUCCESS(rc))
+ pDevFBSD->fInit = true;
+
+ return rc;
+}
+
+/**
+ * Uninit USB subsystem.
+ */
+static int usbProxyFreeBSDFsUnInit(PUSBPROXYDEV pProxyDev)
+{
+ struct usb_fs_uninit UsbFsUninit;
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDFsUnInit: ProxyDev=%p\n", (void *)pProxyDev));
+
+ /* Sanity check */
+ AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER);
+
+ if (pDevFBSD->fInit != true)
+ return VINF_SUCCESS;
+
+ /* Close any open endpoints. */
+ for (unsigned n = 0; n != USBFBSD_MAXENDPOINTS; n++)
+ usbProxyFreeBSDEndpointClose(pProxyDev, n);
+
+ /* Zero default */
+ memset(&UsbFsUninit, 0, sizeof(UsbFsUninit));
+
+ /* Uninit USB subsystem */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_UNINIT, &UsbFsUninit, false);
+ if (RT_SUCCESS(rc))
+ pDevFBSD->fInit = false;
+
+ return rc;
+}
+
+/**
+ * Setup a USB request packet.
+ */
+static void usbProxyFreeBSDSetupReq(struct usb_device_request *pSetupData,
+ uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue,
+ uint16_t wIndex, uint16_t wLength)
+{
+ LogFlow(("usbProxyFreeBSDSetupReq: pSetupData=%p bmRequestType=%x "
+ "bRequest=%x wValue=%x wIndex=%x wLength=%x\n", (void *)pSetupData,
+ bmRequestType, bRequest, wValue, wIndex, wLength));
+
+ pSetupData->bmRequestType = bmRequestType;
+ pSetupData->bRequest = bRequest;
+
+ /* Handle endianess here. Currently no swapping is needed. */
+ pSetupData->wValue[0] = wValue & 0xff;
+ pSetupData->wValue[1] = (wValue >> 8) & 0xff;
+ pSetupData->wIndex[0] = wIndex & 0xff;
+ pSetupData->wIndex[1] = (wIndex >> 8) & 0xff;
+ pSetupData->wLength[0] = wLength & 0xff;
+ pSetupData->wLength[1] = (wLength >> 8) & 0xff;
+}
+
+static int usbProxyFreeBSDEndpointOpen(PUSBPROXYDEV pProxyDev, int Endpoint, bool fIsoc, int index)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ PUSBENDPOINTFBSD pEndpointFBSD = NULL; /* shut up gcc */
+ struct usb_fs_endpoint *pXferEndpoint;
+ struct usb_fs_open UsbFsOpen;
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDEndpointOpen: pProxyDev=%p Endpoint=%d\n",
+ (void *)pProxyDev, Endpoint));
+
+ for (; index < USBFBSD_MAXENDPOINTS; index++)
+ {
+ pEndpointFBSD = &pDevFBSD->aSwEndpoint[index];
+ if (pEndpointFBSD->fCancelling)
+ continue;
+ if ( pEndpointFBSD->fOpen
+ && !pEndpointFBSD->pUrb
+ && (int)pEndpointFBSD->iEpNum == Endpoint)
+ return index;
+ }
+
+ if (index == USBFBSD_MAXENDPOINTS)
+ {
+ for (index = 0; index != USBFBSD_MAXENDPOINTS; index++)
+ {
+ pEndpointFBSD = &pDevFBSD->aSwEndpoint[index];
+ if (pEndpointFBSD->fCancelling)
+ continue;
+ if (!pEndpointFBSD->fOpen)
+ break;
+ }
+ if (index == USBFBSD_MAXENDPOINTS)
+ return -1;
+ }
+ /* set ppBuffer and pLength */
+
+ pXferEndpoint = &pDevFBSD->aHwEndpoint[index];
+ pXferEndpoint->ppBuffer = &pEndpointFBSD->apvData[0];
+ pXferEndpoint->pLength = &pEndpointFBSD->acbData[0];
+
+ LogFlow(("usbProxyFreeBSDEndpointOpen: ep_index=%d ep_num=%d\n",
+ index, Endpoint));
+
+ memset(&UsbFsOpen, 0, sizeof(UsbFsOpen));
+
+ UsbFsOpen.ep_index = index;
+ UsbFsOpen.ep_no = Endpoint;
+ UsbFsOpen.max_bufsize = 256 * 1024;
+ /* Hardcoded assumption about the URBs we get. */
+
+ UsbFsOpen.max_frames = fIsoc ? USBFBSD_MAXFRAMES : 2;
+
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_OPEN, &UsbFsOpen, true);
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_RESOURCE_BUSY)
+ LogFlow(("usbProxyFreeBSDEndpointOpen: EBUSY\n"));
+
+ return -1;
+ }
+ pEndpointFBSD->fOpen = true;
+ pEndpointFBSD->pUrb = NULL;
+ pEndpointFBSD->iEpNum = Endpoint;
+ pEndpointFBSD->cMaxIo = UsbFsOpen.max_bufsize;
+ pEndpointFBSD->cMaxFrames = UsbFsOpen.max_frames;
+
+ return index;
+}
+
+/**
+ * Close an endpoint.
+ *
+ * @returns VBox status code.
+ */
+static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ PUSBENDPOINTFBSD pEndpointFBSD = &pDevFBSD->aSwEndpoint[Endpoint];
+ struct usb_fs_close UsbFsClose;
+ int rc = VINF_SUCCESS;
+
+ LogFlow(("usbProxyFreeBSDEndpointClose: pProxyDev=%p Endpoint=%d\n",
+ (void *)pProxyDev, Endpoint));
+
+ /* check for cancelling */
+ if (pEndpointFBSD->pUrb != NULL)
+ {
+ pEndpointFBSD->fCancelling = true;
+ pDevFBSD->fCancelling = true;
+ }
+
+ /* check for opened */
+ if (pEndpointFBSD->fOpen)
+ {
+ pEndpointFBSD->fOpen = false;
+
+ /* Zero default */
+ memset(&UsbFsClose, 0, sizeof(UsbFsClose));
+
+ /* Set endpoint index */
+ UsbFsClose.ep_index = Endpoint;
+
+ /* Close endpoint */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_CLOSE, &UsbFsClose, true);
+ }
+ return rc;
+}
+
+/**
+ * Opens the device file.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device instance.
+ * @param pszAddress If we are using usbfs, this is the path to the
+ * device. If we are using sysfs, this is a string of
+ * the form "sysfs:<sysfs path>//device:<device node>".
+ * In the second case, the two paths are guaranteed
+ * not to contain the substring "//".
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ /*
+ * Try open the device node.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pszAddress, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the FreeBSD backend data.
+ */
+ pDevFBSD->hFile = hFile;
+ rc = usbProxyFreeBSDFsInit(pProxyDev);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create wakeup pipe.
+ */
+ rc = RTPipeCreate(&pDevFBSD->hPipeWakeupR, &pDevFBSD->hPipeWakeupW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("usbProxyFreeBSDOpen(%p, %s): returns successfully hFile=%RTfile iActiveCfg=%d\n",
+ pProxyDev, pszAddress, pDevFBSD->hFile, pProxyDev->iActiveCfg));
+
+ return VINF_SUCCESS;
+ }
+ }
+
+ RTFileClose(hFile);
+ }
+ else if (rc == VERR_ACCESS_DENIED)
+ rc = VERR_VUSB_USBFS_PERMISSION;
+
+ Log(("usbProxyFreeBSDOpen(%p, %s) failed, rc=%d!\n",
+ pProxyDev, pszAddress, rc));
+
+ return rc;
+}
+
+
+/**
+ * Claims all the interfaces and figures out the
+ * current configuration.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pProxyDev The proxy device.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDInit(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDInit: pProxyDev=%s\n",
+ pProxyDev->pUsbIns->pszName));
+
+ /* Retrieve current active configuration. */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_GET_CONFIG,
+ &pProxyDev->iActiveCfg, true);
+ if (RT_FAILURE(rc) || pProxyDev->iActiveCfg == 255)
+ {
+ pProxyDev->cIgnoreSetConfigs = 0;
+ pProxyDev->iActiveCfg = -1;
+ }
+ else
+ {
+ pProxyDev->cIgnoreSetConfigs = 1;
+ pProxyDev->iActiveCfg++;
+ }
+
+ Log(("usbProxyFreeBSDInit: iActiveCfg=%d\n", pProxyDev->iActiveCfg));
+
+ return rc;
+}
+
+/**
+ * Closes the proxy device.
+ */
+static DECLCALLBACK(void) usbProxyFreeBSDClose(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+
+ LogFlow(("usbProxyFreeBSDClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ /* sanity check */
+ AssertPtrReturnVoid(pDevFBSD);
+
+ usbProxyFreeBSDFsUnInit(pProxyDev);
+
+ RTPipeClose(pDevFBSD->hPipeWakeupR);
+ RTPipeClose(pDevFBSD->hPipeWakeupW);
+
+ RTFileClose(pDevFBSD->hFile);
+ pDevFBSD->hFile = NIL_RTFILE;
+
+ LogFlow(("usbProxyFreeBSDClose: returns\n"));
+}
+
+/**
+ * Reset a device.
+ *
+ * @returns VBox status code.
+ * @param pDev The device to reset.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDReset(PUSBPROXYDEV pProxyDev, bool fResetOnFreeBSD)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int iParm;
+ int rc = VINF_SUCCESS;
+
+ LogFlow(("usbProxyFreeBSDReset: pProxyDev=%s\n",
+ pProxyDev->pUsbIns->pszName));
+
+ if (!fResetOnFreeBSD)
+ goto done;
+
+ /* We need to release kernel ressources first. */
+ rc = usbProxyFreeBSDFsUnInit(pProxyDev);
+ if (RT_FAILURE(rc))
+ goto done;
+
+ /* Resetting is only possible as super-user, ignore any failures: */
+ iParm = 0;
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DEVICEENUMERATE, &iParm, true);
+ if (RT_FAILURE(rc))
+ {
+ /* Set the config instead of bus reset */
+ iParm = 255;
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true);
+ if (RT_SUCCESS(rc))
+ {
+ iParm = 0;
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true);
+ }
+ }
+ usleep(10000); /* nice it! */
+
+ /* Allocate kernel ressources again. */
+ rc = usbProxyFreeBSDFsInit(pProxyDev);
+ if (RT_FAILURE(rc))
+ goto done;
+
+ /* Retrieve current active configuration. */
+ rc = usbProxyFreeBSDInit(pProxyDev);
+
+done:
+ pProxyDev->cIgnoreSetConfigs = 2;
+
+ return rc;
+}
+
+/**
+ * SET_CONFIGURATION.
+ *
+ * The caller makes sure that it's not called first time after open or reset
+ * with the active interface.
+ *
+ * @returns success indicator.
+ * @param pProxyDev The device instance data.
+ * @param iCfg The configuration to set.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ int iCfgIndex;
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDSetConfig: pProxyDev=%s cfg=%x\n",
+ pProxyDev->pUsbIns->pszName, iCfg));
+
+ /* We need to release kernel ressources first. */
+ rc = usbProxyFreeBSDFsUnInit(pProxyDev);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources "
+ "failed failed rc=%d\n", rc));
+ return rc;
+ }
+
+ if (iCfg == 0)
+ {
+ /* Unconfigure */
+ iCfgIndex = 255;
+ }
+ else
+ {
+ /* Get the configuration index matching the value. */
+ for (iCfgIndex = 0; iCfgIndex < pProxyDev->DevDesc.bNumConfigurations; iCfgIndex++)
+ {
+ if (pProxyDev->paCfgDescs[iCfgIndex].Core.bConfigurationValue == iCfg)
+ break;
+ }
+
+ if (iCfgIndex == pProxyDev->DevDesc.bNumConfigurations)
+ {
+ LogFlow(("usbProxyFreeBSDSetConfig: configuration "
+ "%d not found\n", iCfg));
+ return VERR_NOT_FOUND;
+ }
+ }
+
+ /* Set the config */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iCfgIndex, true);
+ if (RT_FAILURE(rc))
+ return rc;
+
+ /* Allocate kernel ressources again. */
+ return usbProxyFreeBSDFsInit(pProxyDev);
+}
+
+/**
+ * Claims an interface.
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDClaimInterface: pProxyDev=%s "
+ "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf));
+
+ /*
+ * Try to detach kernel driver on this interface, ignore any
+ * failures
+ */
+ usbProxyFreeBSDDoIoCtl(pProxyDev, USB_IFACE_DRIVER_DETACH, &iIf, true);
+
+ /* Try to claim interface */
+ return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_CLAIM_INTERFACE, &iIf, true);
+}
+
+/**
+ * Releases an interface.
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDReleaseInterface: pProxyDev=%s "
+ "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf));
+
+ return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_RELEASE_INTERFACE, &iIf, true);
+}
+
+/**
+ * SET_INTERFACE.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ struct usb_alt_interface UsbIntAlt;
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDSetInterface: pProxyDev=%p iIf=%x iAlt=%x\n",
+ pProxyDev, iIf, iAlt));
+
+ /* We need to release kernel ressources first. */
+ rc = usbProxyFreeBSDFsUnInit(pProxyDev);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources "
+ "failed failed rc=%d\n", rc));
+ return rc;
+ }
+ memset(&UsbIntAlt, 0, sizeof(UsbIntAlt));
+ UsbIntAlt.uai_interface_index = iIf;
+ UsbIntAlt.uai_alt_index = iAlt;
+
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_ALTINTERFACE, &UsbIntAlt, true);
+ if (RT_FAILURE(rc))
+ {
+ LogFlow(("usbProxyFreeBSDSetInterface: Setting interface %d %d "
+ "failed rc=%d\n", iIf, iAlt, rc));
+ return rc;
+ }
+
+ return usbProxyFreeBSDFsInit(pProxyDev);
+}
+
+/**
+ * Clears the halted endpoint 'ep_num'.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep_num)
+{
+ LogFlow(("usbProxyFreeBSDClearHaltedEp: pProxyDev=%s ep_num=%u\n",
+ pProxyDev->pUsbIns->pszName, ep_num));
+
+ /*
+ * Clearing the zero control pipe doesn't make sense.
+ * Just ignore it.
+ */
+ if ((ep_num & 0xF) == 0)
+ return VINF_SUCCESS;
+
+ struct usb_ctl_request Req;
+ memset(&Req, 0, sizeof(Req));
+ usbProxyFreeBSDSetupReq(&Req.ucr_request,
+ VUSB_DIR_TO_DEV | VUSB_TO_ENDPOINT,
+ VUSB_REQ_CLEAR_FEATURE, 0, ep_num, 0);
+
+ int rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DO_REQUEST, &Req, true);
+
+ LogFlow(("usbProxyFreeBSDClearHaltedEp: rc=%Rrc\n", rc));
+ return rc;
+}
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ PUSBENDPOINTFBSD pEndpointFBSD;
+ struct usb_fs_endpoint *pXferEndpoint;
+ struct usb_fs_start UsbFsStart;
+ unsigned cFrames;
+ uint8_t *pbData;
+ int index;
+ int ep_num;
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDUrbQueue: pUrb=%p EndPt=%u Dir=%u\n",
+ pUrb, (unsigned)pUrb->EndPt, (unsigned)pUrb->enmDir));
+
+ ep_num = pUrb->EndPt;
+ if ((pUrb->enmType != VUSBXFERTYPE_MSG) && (pUrb->enmDir == VUSBDIRECTION_IN)) {
+ /* set IN-direction bit */
+ ep_num |= 0x80;
+ }
+
+ index = 0;
+
+retry:
+
+ index = usbProxyFreeBSDEndpointOpen(pProxyDev, ep_num,
+ (pUrb->enmType == VUSBXFERTYPE_ISOC),
+ index);
+
+ if (index < 0)
+ return VERR_INVALID_PARAMETER;
+
+ pEndpointFBSD = &pDevFBSD->aSwEndpoint[index];
+ pXferEndpoint = &pDevFBSD->aHwEndpoint[index];
+
+ pbData = pUrb->abData;
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ {
+ pEndpointFBSD->apvData[0] = pbData;
+ pEndpointFBSD->acbData[0] = 8;
+
+ /* check wLength */
+ if (pbData[6] || pbData[7])
+ {
+ pEndpointFBSD->apvData[1] = pbData + 8;
+ pEndpointFBSD->acbData[1] = pbData[6] | (pbData[7] << 8);
+ cFrames = 2;
+ }
+ else
+ {
+ pEndpointFBSD->apvData[1] = NULL;
+ pEndpointFBSD->acbData[1] = 0;
+ cFrames = 1;
+ }
+
+ LogFlow(("usbProxyFreeBSDUrbQueue: pUrb->cbData=%u, 0x%02x, "
+ "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n",
+ pUrb->cbData, pbData[0], pbData[1], pbData[2], pbData[3],
+ pbData[4], pbData[5], pbData[6], pbData[7]));
+
+ pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE;
+ pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK;
+ break;
+ }
+ case VUSBXFERTYPE_ISOC:
+ {
+ unsigned i;
+
+ for (i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ if (i >= pEndpointFBSD->cMaxFrames)
+ break;
+ pEndpointFBSD->apvData[i] = pbData + pUrb->aIsocPkts[i].off;
+ pEndpointFBSD->acbData[i] = pUrb->aIsocPkts[i].cb;
+ }
+ /* Timeout handling will be done during reap. */
+ pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE;
+ pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK;
+ cFrames = i;
+ break;
+ }
+ default:
+ {
+ pEndpointFBSD->apvData[0] = pbData;
+ pEndpointFBSD->cbData0 = pUrb->cbData;
+
+ /* XXX maybe we have to loop */
+ if (pUrb->cbData > pEndpointFBSD->cMaxIo)
+ pEndpointFBSD->acbData[0] = pEndpointFBSD->cMaxIo;
+ else
+ pEndpointFBSD->acbData[0] = pUrb->cbData;
+
+ /* Timeout handling will be done during reap. */
+ pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE;
+ pXferEndpoint->flags = pUrb->fShortNotOk ? 0 : USB_FS_FLAG_MULTI_SHORT_OK;
+ cFrames = 1;
+ break;
+ }
+ }
+
+ /* store number of frames */
+ pXferEndpoint->nFrames = cFrames;
+
+ /* zero-default */
+ memset(&UsbFsStart, 0, sizeof(UsbFsStart));
+
+ /* Start the transfer */
+ UsbFsStart.ep_index = index;
+
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_START, &UsbFsStart, true);
+
+ LogFlow(("usbProxyFreeBSDUrbQueue: USB_FS_START returned rc=%d "
+ "len[0]=%u len[1]=%u cbData=%u index=%u ep_num=%u\n", rc,
+ (unsigned)pEndpointFBSD->acbData[0],
+ (unsigned)pEndpointFBSD->acbData[1],
+ (unsigned)pUrb->cbData,
+ (unsigned)index, (unsigned)ep_num));
+
+ if (RT_FAILURE(rc))
+ {
+ if (rc == VERR_RESOURCE_BUSY)
+ {
+ index++;
+ goto retry;
+ }
+ return rc;
+ }
+ pUrb->Dev.pvPrivate = (void *)(long)(index + 1);
+ pEndpointFBSD->pUrb = pUrb;
+
+ return rc;
+}
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyFreeBSDUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ struct usb_fs_endpoint *pXferEndpoint;
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ PUSBENDPOINTFBSD pEndpointFBSD;
+ PVUSBURB pUrb;
+ struct usb_fs_complete UsbFsComplete;
+ struct pollfd pfd[2];
+ int rc;
+
+ LogFlow(("usbProxyFreeBSDUrbReap: pProxyDev=%p, cMillies=%u\n",
+ pProxyDev, cMillies));
+
+repeat:
+
+ pUrb = NULL;
+
+ /* check for cancelled transfers */
+ if (pDevFBSD->fCancelling)
+ {
+ for (unsigned n = 0; n < USBFBSD_MAXENDPOINTS; n++)
+ {
+ pEndpointFBSD = &pDevFBSD->aSwEndpoint[n];
+ if (pEndpointFBSD->fCancelling)
+ {
+ pEndpointFBSD->fCancelling = false;
+ pUrb = pEndpointFBSD->pUrb;
+ pEndpointFBSD->pUrb = NULL;
+
+ if (pUrb != NULL)
+ break;
+ }
+ }
+
+ if (pUrb != NULL)
+ {
+ pUrb->enmStatus = VUSBSTATUS_INVALID;
+ pUrb->Dev.pvPrivate = NULL;
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ pUrb->cbData = 0;
+ break;
+ case VUSBXFERTYPE_ISOC:
+ pUrb->cbData = 0;
+ for (int n = 0; n < (int)pUrb->cIsocPkts; n++)
+ pUrb->aIsocPkts[n].cb = 0;
+ break;
+ default:
+ pUrb->cbData = 0;
+ break;
+ }
+ return pUrb;
+ }
+ pDevFBSD->fCancelling = false;
+ }
+ /* Zero default */
+
+ memset(&UsbFsComplete, 0, sizeof(UsbFsComplete));
+
+ /* Check if any endpoints are complete */
+ rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_COMPLETE, &UsbFsComplete, true);
+ if (RT_SUCCESS(rc))
+ {
+ pXferEndpoint = &pDevFBSD->aHwEndpoint[UsbFsComplete.ep_index];
+ pEndpointFBSD = &pDevFBSD->aSwEndpoint[UsbFsComplete.ep_index];
+
+ LogFlow(("usbProxyFreeBSDUrbReap: Reaped "
+ "URB %#p\n", pEndpointFBSD->pUrb));
+
+ if (pXferEndpoint->status == USB_ERR_CANCELLED)
+ goto repeat;
+
+ pUrb = pEndpointFBSD->pUrb;
+ pEndpointFBSD->pUrb = NULL;
+ if (pUrb == NULL)
+ goto repeat;
+
+ switch (pXferEndpoint->status)
+ {
+ case USB_ERR_NORMAL_COMPLETION:
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ break;
+ case USB_ERR_STALLED:
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+ break;
+ default:
+ pUrb->enmStatus = VUSBSTATUS_INVALID;
+ break;
+ }
+
+ pUrb->Dev.pvPrivate = NULL;
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ pUrb->cbData = pEndpointFBSD->acbData[0] + pEndpointFBSD->acbData[1];
+ break;
+ case VUSBXFERTYPE_ISOC:
+ {
+ int n;
+
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ break;
+ pUrb->cbData = 0;
+ for (n = 0; n < (int)pUrb->cIsocPkts; n++)
+ {
+ if (n >= (int)pEndpointFBSD->cMaxFrames)
+ break;
+ pUrb->cbData += pEndpointFBSD->acbData[n];
+ pUrb->aIsocPkts[n].cb = pEndpointFBSD->acbData[n];
+ }
+ for (; n < (int)pUrb->cIsocPkts; n++)
+ pUrb->aIsocPkts[n].cb = 0;
+
+ break;
+ }
+ default:
+ pUrb->cbData = pEndpointFBSD->acbData[0];
+ break;
+ }
+
+ LogFlow(("usbProxyFreeBSDUrbReap: Status=%d epindex=%u "
+ "len[0]=%d len[1]=%d\n",
+ (int)pXferEndpoint->status,
+ (unsigned)UsbFsComplete.ep_index,
+ (unsigned)pEndpointFBSD->acbData[0],
+ (unsigned)pEndpointFBSD->acbData[1]));
+
+ }
+ else if (cMillies != 0 && rc == VERR_RESOURCE_BUSY)
+ {
+ for (;;)
+ {
+ pfd[0].fd = RTFileToNative(pDevFBSD->hFile);
+ pfd[0].events = POLLIN | POLLRDNORM;
+ pfd[0].revents = 0;
+
+ pfd[1].fd = RTPipeToNative(pDevFBSD->hPipeWakeupR);
+ pfd[1].events = POLLIN | POLLRDNORM;
+ pfd[1].revents = 0;
+
+ rc = poll(pfd, 2, (cMillies == RT_INDEFINITE_WAIT) ? INFTIM : cMillies);
+ if (rc > 0)
+ {
+ if (pfd[1].revents & POLLIN)
+ {
+ /* Got woken up, drain pipe. */
+ uint8_t bRead;
+ size_t cbIgnored = 0;
+ RTPipeRead(pDevFBSD->hPipeWakeupR, &bRead, 1, &cbIgnored);
+ /* Make sure we return from this function */
+ cMillies = 0;
+ }
+ break;
+ }
+ if (rc == 0)
+ return NULL;
+ if (errno != EAGAIN)
+ return NULL;
+ }
+ goto repeat;
+ }
+ return pUrb;
+}
+
+/**
+ * Cancels the URB.
+ * The URB requires reaping, so we don't change its state.
+ */
+static DECLCALLBACK(int) usbProxyFreeBSDUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ int index;
+
+ index = (int)(long)pUrb->Dev.pvPrivate - 1;
+
+ if (index < 0 || index >= USBFBSD_MAXENDPOINTS)
+ return VINF_SUCCESS; /* invalid index, pretend success. */
+
+ LogFlow(("usbProxyFreeBSDUrbCancel: epindex=%u\n", (unsigned)index));
+ return usbProxyFreeBSDEndpointClose(pProxyDev, index);
+}
+
+static DECLCALLBACK(int) usbProxyFreeBSDWakeup(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD);
+ size_t cbIgnored;
+
+ LogFlowFunc(("pProxyDev=%p\n", pProxyDev));
+
+ return RTPipeWrite(pDevFBSD->hPipeWakeupW, "", 1, &cbIgnored);
+}
+
+/**
+ * The FreeBSD USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ /* pszName */
+ "host",
+ /* cbBackend */
+ sizeof(USBPROXYDEVFBSD),
+ usbProxyFreeBSDOpen,
+ usbProxyFreeBSDInit,
+ usbProxyFreeBSDClose,
+ usbProxyFreeBSDReset,
+ usbProxyFreeBSDSetConfig,
+ usbProxyFreeBSDClaimInterface,
+ usbProxyFreeBSDReleaseInterface,
+ usbProxyFreeBSDSetInterface,
+ usbProxyFreeBSDClearHaltedEp,
+ usbProxyFreeBSDUrbQueue,
+ usbProxyFreeBSDUrbCancel,
+ usbProxyFreeBSDUrbReap,
+ usbProxyFreeBSDWakeup,
+ 0
+};
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "bsd"
+ * c-basic-offset: 4
+ * tab-width: 4
+ * indent-tabs-mode: s
+ * End:
+ */
+
diff --git a/src/VBox/Devices/USB/linux/Makefile.kup b/src/VBox/Devices/USB/linux/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/linux/Makefile.kup
diff --git a/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp
new file mode 100644
index 00000000..07a818f3
--- /dev/null
+++ b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp
@@ -0,0 +1,1708 @@
+/* $Id: USBProxyDevice-linux.cpp $ */
+/** @file
+ * USB device proxy - the Linux backend.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+
+#include <iprt/stdint.h>
+#include <iprt/err.h>
+#include <iprt/pipe.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#ifdef VBOX_WITH_LINUX_COMPILER_H
+# include <linux/compiler.h>
+#endif
+#include <linux/usbdevice_fs.h>
+
+#ifndef RDESKTOP
+# include <VBox/vmm/pdm.h>
+#else
+# define RTCRITSECT void *
+static inline int rtcsNoop() { return VINF_SUCCESS; }
+static inline bool rtcsTrue() { return true; }
+# define RTCritSectInit(a) rtcsNoop()
+# define RTCritSectDelete(a) rtcsNoop()
+# define RTCritSectEnter(a) rtcsNoop()
+# define RTCritSectLeave(a) rtcsNoop()
+# define RTCritSectIsOwner(a) rtcsTrue()
+#endif
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/alloc.h>
+#include <iprt/assert.h>
+#include <iprt/asm.h>
+#include <iprt/ctype.h>
+#include <iprt/file.h>
+#include <iprt/linux/sysfs.h>
+#include <iprt/stream.h>
+#include <iprt/string.h>
+#include <iprt/list.h>
+#include <iprt/time.h>
+#include "../USBProxyDevice.h"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Wrapper around the linux urb request structure.
+ * This is required to track in-flight and landed URBs.
+ */
+typedef struct USBPROXYURBLNX
+{
+ /** Node to link the URB in of the existing lists. */
+ RTLISTNODE NodeList;
+ /** If we've split the VUSBURB up into multiple linux URBs, this is points to the head. */
+ struct USBPROXYURBLNX *pSplitHead;
+ /** The next linux URB if split up. */
+ struct USBPROXYURBLNX *pSplitNext;
+ /** Don't report these back. */
+ bool fCanceledBySubmit;
+ /** This split element is reaped. */
+ bool fSplitElementReaped;
+ /** This URB was discarded. */
+ bool fDiscarded;
+ /** Size to transfer in remaining fragments of a split URB */
+ uint32_t cbSplitRemaining;
+
+#if RT_GNUC_PREREQ(6, 0) /* gcc 6.2 complains about the [] member of KUrb */
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wpedantic"
+#endif
+ /** The kernel URB data (variable size array included). */
+ struct usbdevfs_urb KUrb;
+#if RT_GNUC_PREREQ(6, 0)
+# pragma GCC diagnostic pop
+#endif
+} USBPROXYURBLNX, *PUSBPROXYURBLNX;
+
+/**
+ * Data for the linux usb proxy backend.
+ */
+typedef struct USBPROXYDEVLNX
+{
+ /** The open file. */
+ RTFILE hFile;
+ /** Critical section protecting the lists. */
+ RTCRITSECT CritSect;
+ /** The list of free linux URBs (USBPROXYURBLNX). */
+ RTLISTANCHOR ListFree;
+ /** The list of active linux URBs.
+ * We must maintain this so we can properly reap URBs of a detached device.
+ * Only the split head will appear in this list. (USBPROXYURBLNX) */
+ RTLISTANCHOR ListInFlight;
+ /** Are we using sysfs to find the active configuration? */
+ bool fUsingSysfs;
+ /** Pipe handle for waking up - writing end. */
+ RTPIPE hPipeWakeupW;
+ /** Pipe handle for waking up - reading end. */
+ RTPIPE hPipeWakeupR;
+ /** The device node/sysfs path of the device.
+ * Used to figure out the configuration after a reset. */
+ char *pszPath;
+ /** Mask of claimed interfaces. */
+ uint32_t fClaimedIfsMask;
+} USBPROXYDEVLNX, *PUSBPROXYDEVLNX;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev);
+static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf);
+static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf);
+
+
+/**
+ * Wrapper for the ioctl call.
+ *
+ * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also
+ * handle ENODEV (detached device) errors.
+ *
+ * @returns whatever ioctl returns.
+ * @param pProxyDev The proxy device.
+ * @param iCmd The ioctl command / function.
+ * @param pvArg The ioctl argument / data.
+ * @param fHandleNoDev Whether to handle ENODEV.
+ * @param cTries The number of retries. Use UINT32_MAX for (kind of) indefinite retries.
+ * @internal
+ */
+static int usbProxyLinuxDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, void *pvArg, bool fHandleNoDev, uint32_t cTries)
+{
+ int rc;
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ do
+ {
+ do
+ {
+ rc = ioctl(RTFileToNative(pDevLnx->hFile), iCmd, pvArg);
+ if (rc >= 0)
+ return rc;
+ } while (errno == EINTR);
+
+ if (errno == ENODEV && fHandleNoDev)
+ {
+ usbProxLinuxUrbUnplugged(pProxyDev);
+ Log(("usb-linux: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+ errno = ENODEV;
+ break;
+ }
+ if (errno != EAGAIN)
+ break;
+ } while (cTries-- > 0);
+
+ return rc;
+}
+
+
+/**
+ * The device has been unplugged.
+ * Cancel all in-flight URBs and put them up for reaping.
+ */
+static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ /*
+ * Shoot down all flying URBs.
+ */
+ RTCritSectEnter(&pDevLnx->CritSect);
+ pProxyDev->fDetached = true;
+
+ PUSBPROXYURBLNX pUrbLnx;
+ PUSBPROXYURBLNX pUrbLnxNext;
+ RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList)
+ {
+ if (!pUrbLnx->fDiscarded)
+ {
+ pUrbLnx->fDiscarded = true;
+ /* Cancel the URB. It will be reaped normally. */
+ ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_DISCARDURB, &pUrbLnx->KUrb);
+ if (!pUrbLnx->KUrb.status)
+ pUrbLnx->KUrb.status = -ENODEV;
+ }
+ }
+
+ RTCritSectLeave(&pDevLnx->CritSect);
+}
+
+
+/**
+ * Set the connect state seen by kernel drivers
+ * @internal
+ */
+static void usbProxyLinuxSetConnected(PUSBPROXYDEV pProxyDev, int iIf, bool fConnect, bool fQuiet)
+{
+ if ( iIf >= 32
+ || !(pProxyDev->fMaskedIfs & RT_BIT(iIf)))
+ {
+ struct usbdevfs_ioctl IoCtl;
+ if (!fQuiet)
+ LogFlow(("usbProxyLinuxSetConnected: pProxyDev=%s iIf=%#x fConnect=%s\n",
+ usbProxyGetName(pProxyDev), iIf, fConnect ? "true" : "false"));
+
+ IoCtl.ifno = iIf;
+ IoCtl.ioctl_code = fConnect ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT;
+ IoCtl.data = NULL;
+ if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_IOCTL, &IoCtl, true, UINT32_MAX)
+ && !fQuiet)
+ Log(("usbProxyLinuxSetConnected: failure, errno=%d. pProxyDev=%s\n",
+ errno, usbProxyGetName(pProxyDev)));
+ }
+}
+
+
+/**
+ * Links the given URB into the in flight list.
+ *
+ * @returns nothing.
+ * @param pDevLnx The proxy device instance - Linux specific data.
+ * @param pUrbLnx The URB to link into the in flight list.
+ */
+static void usbProxyLinuxUrbLinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx)
+{
+ LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx));
+ Assert(RTCritSectIsOwner(&pDevLnx->CritSect));
+ Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx);
+ RTListAppend(&pDevLnx->ListInFlight, &pUrbLnx->NodeList);
+}
+
+
+/**
+ * Unlinks the given URB from the in flight list.
+ *
+ * @returns nothing.
+ * @param pDevLnx The proxy device instance - Linux specific data.
+ * @param pUrbLnx The URB to link into the in flight list.
+ */
+static void usbProxyLinuxUrbUnlinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx)
+{
+ LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx));
+ RTCritSectEnter(&pDevLnx->CritSect);
+
+ /*
+ * Remove from the active list.
+ */
+ Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx);
+
+ RTListNodeRemove(&pUrbLnx->NodeList);
+
+ RTCritSectLeave(&pDevLnx->CritSect);
+}
+
+
+/**
+ * Allocates a linux URB request structure.
+ *
+ * @returns Pointer to an active URB request.
+ * @returns NULL on failure.
+ * @param pProxyDev The proxy device instance.
+ * @param pSplitHead The split list head if allocating for a split list.
+ */
+static PUSBPROXYURBLNX usbProxyLinuxUrbAlloc(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pSplitHead)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ PUSBPROXYURBLNX pUrbLnx;
+
+ LogFlowFunc(("pProxyDev=%p pSplitHead=%p\n", pProxyDev, pSplitHead));
+
+ RTCritSectEnter(&pDevLnx->CritSect);
+
+ /*
+ * Try remove a linux URB from the free list, if none there allocate a new one.
+ */
+ pUrbLnx = RTListGetFirst(&pDevLnx->ListFree, USBPROXYURBLNX, NodeList);
+ if (pUrbLnx)
+ {
+ RTListNodeRemove(&pUrbLnx->NodeList);
+ RTCritSectLeave(&pDevLnx->CritSect);
+ }
+ else
+ {
+ RTCritSectLeave(&pDevLnx->CritSect);
+ PVUSBURB pVUrbDummy; RT_NOREF(pVUrbDummy);
+ pUrbLnx = (PUSBPROXYURBLNX)RTMemAlloc(RT_UOFFSETOF_DYN(USBPROXYURBLNX,
+ KUrb.iso_frame_desc[RT_ELEMENTS(pVUrbDummy->aIsocPkts)]));
+ if (!pUrbLnx)
+ return NULL;
+ }
+
+ pUrbLnx->pSplitHead = pSplitHead;
+ pUrbLnx->pSplitNext = NULL;
+ pUrbLnx->fCanceledBySubmit = false;
+ pUrbLnx->fSplitElementReaped = false;
+ pUrbLnx->fDiscarded = false;
+ LogFlowFunc(("returns pUrbLnx=%p\n", pUrbLnx));
+ return pUrbLnx;
+}
+
+
+/**
+ * Frees a linux URB request structure.
+ *
+ * @param pProxyDev The proxy device instance.
+ * @param pUrbLnx The linux URB to free.
+ */
+static void usbProxyLinuxUrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx));
+
+ /*
+ * Link it into the free list.
+ */
+ RTCritSectEnter(&pDevLnx->CritSect);
+ RTListAppend(&pDevLnx->ListFree, &pUrbLnx->NodeList);
+ RTCritSectLeave(&pDevLnx->CritSect);
+}
+
+
+/**
+ * Frees split list of a linux URB request structure.
+ *
+ * @param pProxyDev The proxy device instance.
+ * @param pUrbLnx A linux URB to in the split list to be freed.
+ */
+static void usbProxyLinuxUrbFreeSplitList(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx));
+
+ RTCritSectEnter(&pDevLnx->CritSect);
+
+ pUrbLnx = pUrbLnx->pSplitHead;
+ Assert(pUrbLnx);
+ while (pUrbLnx)
+ {
+ PUSBPROXYURBLNX pFree = pUrbLnx;
+ pUrbLnx = pUrbLnx->pSplitNext;
+ Assert(pFree->pSplitHead);
+ pFree->pSplitHead = pFree->pSplitNext = NULL;
+ usbProxyLinuxUrbFree(pProxyDev, pFree);
+ }
+
+ RTCritSectLeave(&pDevLnx->CritSect);
+}
+
+
+/**
+ * This finds the device in the /proc/bus/usb/bus/addr file and finds
+ * the config with an asterix.
+ *
+ * @returns The Cfg#.
+ * @returns -1 if no active config.
+ * @param pProxyDev The proxy device instance.
+ * @param pszDevNode The path to the device. We infere the location of
+ * the devices file, which bus and device number we're
+ * looking for.
+ * @param piFirstCfg The first configuration. (optional)
+ * @internal
+ */
+static int usbProxyLinuxFindActiveConfigUsbfs(PUSBPROXYDEV pProxyDev, const char *pszDevNode, int *piFirstCfg)
+{
+ RT_NOREF(pProxyDev);
+
+ /*
+ * Set return defaults.
+ */
+ int iActiveCfg = -1;
+ if (piFirstCfg)
+ *piFirstCfg = 1;
+
+ /*
+ * Parse the usbfs device node path and turn it into a path to the "devices" file,
+ * picking up the device number and bus along the way.
+ */
+ size_t cchDevNode = strlen(pszDevNode);
+ char *pszDevices = (char *)RTMemDupEx(pszDevNode, cchDevNode, sizeof("devices"));
+ AssertReturn(pszDevices, iActiveCfg);
+
+ /* the device number */
+ char *psz = pszDevices + cchDevNode;
+ while (*psz != '/')
+ psz--;
+ Assert(pszDevices < psz);
+ uint32_t uDev;
+ int rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uDev);
+ if (RT_SUCCESS(rc))
+ {
+ /* the bus number */
+ *psz-- = '\0';
+ while (*psz != '/')
+ psz--;
+ Assert(pszDevices < psz);
+ uint32_t uBus;
+ rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uBus);
+ if (RT_SUCCESS(rc))
+ {
+ strcpy(psz + 1, "devices");
+
+ /*
+ * Open and scan the devices file.
+ * We're ASSUMING that each device starts off with a 'T:' line.
+ */
+ PRTSTREAM pFile;
+ rc = RTStrmOpen(pszDevices, "r", &pFile);
+ if (RT_SUCCESS(rc))
+ {
+ char szLine[1024];
+ while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine))))
+ {
+ /* we're only interested in 'T:' lines. */
+ psz = RTStrStripL(szLine);
+ if (psz[0] != 'T' || psz[1] != ':')
+ continue;
+
+ /* Skip ahead to 'Bus' and compare */
+ psz = RTStrStripL(psz + 2); Assert(!strncmp(psz, RT_STR_TUPLE("Bus=")));
+ psz = RTStrStripL(psz + 4);
+ char *pszNext;
+ uint32_t u;
+ rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc);
+ if (RT_FAILURE(rc))
+ continue;
+ if (u != uBus)
+ continue;
+
+ /* Skip ahead to 'Dev#' and compare */
+ psz = strstr(psz, "Dev#="); Assert(psz);
+ if (!psz)
+ continue;
+ psz = RTStrStripL(psz + 5);
+ rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc);
+ if (RT_FAILURE(rc))
+ continue;
+ if (u != uDev)
+ continue;
+
+ /*
+ * Ok, we've found the device.
+ * Scan until we find a selected configuration, the next device, or EOF.
+ */
+ while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine))))
+ {
+ psz = RTStrStripL(szLine);
+ if (psz[0] == 'T')
+ break;
+ if (psz[0] != 'C' || psz[1] != ':')
+ continue;
+ const bool fActive = psz[2] == '*';
+ if (!fActive && !piFirstCfg)
+ continue;
+
+ /* Get the 'Cfg#' value. */
+ psz = strstr(psz, "Cfg#="); Assert(psz);
+ if (psz)
+ {
+ psz = RTStrStripL(psz + 5);
+ rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if (piFirstCfg)
+ {
+ *piFirstCfg = u;
+ piFirstCfg = NULL;
+ }
+ if (fActive)
+ iActiveCfg = u;
+ }
+ }
+ if (fActive)
+ break;
+ }
+ break;
+ }
+ RTStrmClose(pFile);
+ }
+ }
+ }
+ RTMemFree(pszDevices);
+
+ return iActiveCfg;
+}
+
+
+/**
+ * This finds the active configuration from sysfs.
+ *
+ * @returns The Cfg#.
+ * @returns -1 if no active config.
+ * @param pProxyDev The proxy device instance.
+ * @param pszPath The sysfs path for the device.
+ * @param piFirstCfg The first configuration. (optional)
+ * @internal
+ */
+static int usbProxyLinuxFindActiveConfigSysfs(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg)
+{
+#ifdef VBOX_USB_WITH_SYSFS
+ if (piFirstCfg != NULL)
+ *piFirstCfg = pProxyDev->paCfgDescs != NULL
+ ? pProxyDev->paCfgDescs[0].Core.bConfigurationValue
+ : 1;
+ int64_t bCfg = 0;
+ int rc = RTLinuxSysFsReadIntFile(10, &bCfg, "%s/bConfigurationValue", pszPath);
+ if (RT_FAILURE(rc))
+ bCfg = -1;
+ return (int)bCfg;
+#else /* !VBOX_USB_WITH_SYSFS */
+ return -1;
+#endif /* !VBOX_USB_WITH_SYSFS */
+}
+
+
+/**
+ * This finds the active configuration.
+ *
+ * @returns The Cfg#.
+ * @returns -1 if no active config.
+ * @param pProxyDev The proxy device instance.
+ * @param pszPath The sysfs path for the device, or the usbfs device
+ * node path.
+ * @param piFirstCfg The first configuration. (optional)
+ * @internal
+ */
+static int usbProxyLinuxFindActiveConfig(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ if (pDevLnx->fUsingSysfs)
+ return usbProxyLinuxFindActiveConfigSysfs(pProxyDev, pszPath, piFirstCfg);
+ return usbProxyLinuxFindActiveConfigUsbfs(pProxyDev, pszPath, piFirstCfg);
+}
+
+
+/**
+ * Extracts the Linux file descriptor associated with the kernel USB device.
+ * This is used by rdesktop-vrdp for polling for events.
+ * @returns the FD, or asserts and returns -1 on error
+ * @param pProxyDev The device instance
+ */
+RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ AssertReturn(pDevLnx->hFile != NIL_RTFILE, -1);
+ return RTFileToNative(pDevLnx->hFile);
+}
+
+
+/**
+ * Opens the device file.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device instance.
+ * @param pszAddress If we are using usbfs, this is the path to the
+ * device. If we are using sysfs, this is a string of
+ * the form "sysfs:<sysfs path>//device:<device node>".
+ * In the second case, the two paths are guaranteed
+ * not to contain the substring "//".
+ */
+static DECLCALLBACK(int) usbProxyLinuxOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ LogFlow(("usbProxyLinuxOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+ const char *pszDevNode;
+ const char *pszPath;
+ size_t cchPath;
+ bool fUsingSysfs;
+
+ /*
+ * Are we using sysfs or usbfs?
+ */
+#ifdef VBOX_USB_WITH_SYSFS
+ fUsingSysfs = strncmp(pszAddress, RT_STR_TUPLE("sysfs:")) == 0;
+ if (fUsingSysfs)
+ {
+ pszDevNode = strstr(pszAddress, "//device:");
+ if (!pszDevNode)
+ {
+ LogRel(("usbProxyLinuxOpen: Invalid device address: '%s'\n", pszAddress));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ pszPath = pszAddress + sizeof("sysfs:") - 1;
+ cchPath = pszDevNode - pszPath;
+ pszDevNode += sizeof("//device:") - 1;
+ }
+ else
+#endif /* VBOX_USB_WITH_SYSFS */
+ {
+#ifndef VBOX_USB_WITH_SYSFS
+ fUsingSysfs = false;
+#endif
+ pszPath = pszDevNode = pszAddress;
+ cchPath = strlen(pszPath);
+ }
+
+ /*
+ * Try open the device node.
+ */
+ RTFILE hFile;
+ int rc = RTFileOpen(&hFile, pszDevNode, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Initialize the linux backend data.
+ */
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ RTListInit(&pDevLnx->ListFree);
+ RTListInit(&pDevLnx->ListInFlight);
+ pDevLnx->pszPath = RTStrDupN(pszPath, cchPath);
+ if (pDevLnx->pszPath)
+ {
+ rc = RTPipeCreate(&pDevLnx->hPipeWakeupR, &pDevLnx->hPipeWakeupW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ pDevLnx->fUsingSysfs = fUsingSysfs;
+ pDevLnx->hFile = hFile;
+ pDevLnx->fClaimedIfsMask = 0;
+ rc = RTCritSectInit(&pDevLnx->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("usbProxyLinuxOpen(%p, %s): returns successfully File=%RTfile iActiveCfg=%d\n",
+ pProxyDev, pszAddress, pDevLnx->hFile, pProxyDev->iActiveCfg));
+
+ return VINF_SUCCESS;
+ }
+ RTPipeClose(pDevLnx->hPipeWakeupR);
+ RTPipeClose(pDevLnx->hPipeWakeupW);
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTFileClose(hFile);
+ }
+ else if (rc == VERR_ACCESS_DENIED)
+ rc = VERR_VUSB_USBFS_PERMISSION;
+
+ Log(("usbProxyLinuxOpen(%p, %s) failed, rc=%Rrc!\n", pProxyDev, pszAddress, rc));
+ return rc;
+}
+
+
+/**
+ * Claims all the interfaces and figures out the
+ * current configuration.
+ *
+ * @returns VINF_SUCCESS.
+ * @param pProxyDev The proxy device.
+ */
+static DECLCALLBACK(int) usbProxyLinuxInit(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ /*
+ * Brute force rulez.
+ * usbProxyLinuxSetConnected check for masked interfaces.
+ */
+ unsigned iIf;
+ for (iIf = 0; iIf < 256; iIf++)
+ usbProxyLinuxSetConnected(pProxyDev, iIf, false, true);
+
+ /*
+ * Determine the active configuration.
+ *
+ * If there isn't any active configuration, we will get EHOSTUNREACH (113) errors
+ * when trying to read the device descriptors in usbProxyDevCreate. So, we'll make
+ * the first one active (usually 1) then.
+ */
+ pProxyDev->cIgnoreSetConfigs = 1;
+ int iFirstCfg;
+ pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, &iFirstCfg);
+ if (pProxyDev->iActiveCfg == -1)
+ {
+ usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iFirstCfg, false, UINT32_MAX);
+ pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL);
+ Log(("usbProxyLinuxInit: No active config! Tried to set %d: iActiveCfg=%d\n", iFirstCfg, pProxyDev->iActiveCfg));
+ }
+ else
+ Log(("usbProxyLinuxInit(%p): iActiveCfg=%d\n", pProxyDev, pProxyDev->iActiveCfg));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Closes the proxy device.
+ */
+static DECLCALLBACK(void) usbProxyLinuxClose(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow(("usbProxyLinuxClose: pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ AssertPtrReturnVoid(pDevLnx);
+
+ /*
+ * Try put the device in a state which linux can cope with before we release it.
+ * Resetting it would be a nice start, although we must remember
+ * that it might have been disconnected...
+ *
+ * Don't reset if we're masking interfaces or if construction failed.
+ */
+ if (pProxyDev->fInited)
+ {
+ /* ASSUMES: thread == EMT */
+ if ( pProxyDev->fMaskedIfs
+ || !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10))
+ {
+ /* Connect drivers. */
+ unsigned iIf;
+ for (iIf = 0; iIf < 256; iIf++)
+ usbProxyLinuxSetConnected(pProxyDev, iIf, true, true);
+ Log(("USB: Successfully reset device pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+ }
+ else if (errno != ENODEV)
+ LogRel(("USB: Reset failed, errno=%d, pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev)));
+ else /* This will happen if device was detached. */
+ Log(("USB: Reset failed, errno=%d (ENODEV), pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev)));
+ }
+
+ /*
+ * Now we can free all the resources and close the device.
+ */
+ RTCritSectDelete(&pDevLnx->CritSect);
+
+ PUSBPROXYURBLNX pUrbLnx;
+ PUSBPROXYURBLNX pUrbLnxNext;
+ RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList)
+ {
+ RTListNodeRemove(&pUrbLnx->NodeList);
+
+ if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX)
+ && errno != ENODEV
+ && errno != ENOENT)
+ AssertMsgFailed(("errno=%d\n", errno));
+
+ if (pUrbLnx->pSplitHead)
+ {
+ PUSBPROXYURBLNX pCur = pUrbLnx->pSplitNext;
+ while (pCur)
+ {
+ PUSBPROXYURBLNX pFree = pCur;
+ pCur = pFree->pSplitNext;
+ if ( !pFree->fSplitElementReaped
+ && usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pFree->KUrb, false, UINT32_MAX)
+ && errno != ENODEV
+ && errno != ENOENT)
+ AssertMsgFailed(("errno=%d\n", errno));
+ RTMemFree(pFree);
+ }
+ }
+ else
+ Assert(!pUrbLnx->pSplitNext);
+ RTMemFree(pUrbLnx);
+ }
+
+ RTListForEachSafe(&pDevLnx->ListFree, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList)
+ {
+ RTListNodeRemove(&pUrbLnx->NodeList);
+ RTMemFree(pUrbLnx);
+ }
+
+ RTFileClose(pDevLnx->hFile);
+ pDevLnx->hFile = NIL_RTFILE;
+
+ RTPipeClose(pDevLnx->hPipeWakeupR);
+ RTPipeClose(pDevLnx->hPipeWakeupW);
+
+ RTStrFree(pDevLnx->pszPath);
+
+ LogFlow(("usbProxyLinuxClose: returns\n"));
+}
+
+
+/** @interface_method_impl{USBPROXYBACK,pfnReset} */
+static DECLCALLBACK(int) usbProxyLinuxReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ RT_NOREF(fResetOnLinux);
+ Assert(!pProxyDev->fMaskedIfs);
+ LogFlow(("usbProxyLinuxReset: pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+
+ uint32_t fActiveIfsMask = pDevLnx->fClaimedIfsMask;
+ unsigned i;
+
+ /*
+ * Before reset, release claimed interfaces. This less than obvious move
+ * prevents Linux from rebinding in-kernel drivers to the device after reset.
+ */
+ for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i)
+ {
+ if (fActiveIfsMask & RT_BIT(i))
+ {
+ usbProxyLinuxReleaseInterface(pProxyDev, i);
+ }
+ }
+
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10))
+ {
+ int rc = errno;
+ LogRel(("usb-linux: Reset failed, rc=%Rrc errno=%d.\n", RTErrConvertFromErrno(rc), rc));
+ pProxyDev->iActiveCfg = -1;
+ return RTErrConvertFromErrno(rc);
+ }
+
+ /*
+ * Now reclaim previously claimed interfaces. If that doesn't work, let's hope
+ * the guest/VUSB can recover from that. Can happen if reset changes configuration.
+ */
+ for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i)
+ {
+ if (fActiveIfsMask & RT_BIT(i))
+ {
+ usbProxyLinuxClaimInterface(pProxyDev, i);
+ }
+ }
+
+ /* find the active config - damn annoying. */
+ pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL);
+ LogFlow(("usbProxyLinuxReset: returns successfully iActiveCfg=%d\n", pProxyDev->iActiveCfg));
+
+ pProxyDev->cIgnoreSetConfigs = 2;
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * SET_CONFIGURATION.
+ *
+ * The caller makes sure that it's not called first time after open or reset
+ * with the active interface.
+ *
+ * @returns success indicator.
+ * @param pProxyDev The device instance data.
+ * @param iCfg The configuration to set.
+ */
+static DECLCALLBACK(int) usbProxyLinuxSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ LogFlow(("usbProxyLinuxSetConfig: pProxyDev=%s cfg=%#x\n",
+ usbProxyGetName(pProxyDev), iCfg));
+
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iCfg, true, UINT32_MAX))
+ {
+ Log(("usb-linux: Set configuration. errno=%d\n", errno));
+ return RTErrConvertFromErrno(errno);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Claims an interface.
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ LogFlow(("usbProxyLinuxClaimInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf));
+ usbProxyLinuxSetConnected(pProxyDev, iIf, false, false);
+
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLAIMINTERFACE, &iIf, true, UINT32_MAX))
+ {
+ pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf);
+ LogRel(("usb-linux: Claim interface. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev)));
+ return RTErrConvertFromErrno(errno);
+ }
+ pDevLnx->fClaimedIfsMask |= RT_BIT(iIf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Releases an interface.
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ LogFlow(("usbProxyLinuxReleaseInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf));
+
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RELEASEINTERFACE, &iIf, true, UINT32_MAX))
+ {
+ LogRel(("usb-linux: Release interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev)));
+ return RTErrConvertFromErrno(errno);
+ }
+ pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * SET_INTERFACE.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxyLinuxSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt)
+{
+ struct usbdevfs_setinterface SetIf;
+ LogFlow(("usbProxyLinuxSetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt));
+
+ SetIf.interface = iIf;
+ SetIf.altsetting = iAlt;
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETINTERFACE, &SetIf, true, UINT32_MAX))
+ {
+ Log(("usb-linux: Set interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev)));
+ return RTErrConvertFromErrno(errno);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Clears the halted endpoint 'EndPt'.
+ */
+static DECLCALLBACK(int) usbProxyLinuxClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt)
+{
+ LogFlow(("usbProxyLinuxClearHaltedEp: pProxyDev=%s EndPt=%u\n", usbProxyGetName(pProxyDev), EndPt));
+
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLEAR_HALT, &EndPt, true, UINT32_MAX))
+ {
+ /*
+ * Unfortunately this doesn't work on control pipes.
+ * Windows doing this on the default endpoint and possibly other pipes too,
+ * so we'll feign success for ENOENT errors.
+ */
+ if (errno == ENOENT)
+ {
+ Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d - IGNORED\n",
+ errno, usbProxyGetName(pProxyDev), EndPt));
+ return VINF_SUCCESS;
+ }
+ Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d\n",
+ errno, usbProxyGetName(pProxyDev), EndPt));
+ return RTErrConvertFromErrno(errno);
+ }
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Setup packet byte-swapping routines.
+ */
+static void usbProxyLinuxUrbSwapSetup(PVUSBSETUP pSetup)
+{
+ pSetup->wValue = RT_H2LE_U16(pSetup->wValue);
+ pSetup->wIndex = RT_H2LE_U16(pSetup->wIndex);
+ pSetup->wLength = RT_H2LE_U16(pSetup->wLength);
+}
+
+
+/**
+ * Clean up after a failed URB submit.
+ */
+static void usbProxyLinuxCleanupFailedSubmit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PUSBPROXYURBLNX pCur,
+ PVUSBURB pUrb, bool *pfUnplugged)
+{
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData);
+
+ /* discard and reap later (walking with pUrbLnx). */
+ if (pUrbLnx != pCur)
+ {
+ for (;;)
+ {
+ pUrbLnx->fCanceledBySubmit = true;
+ pUrbLnx->KUrb.usercontext = NULL;
+ if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX))
+ {
+ if (errno == ENODEV)
+ *pfUnplugged = true;
+ else if (errno == ENOENT)
+ pUrbLnx->fSplitElementReaped = true;
+ else
+ LogRel(("USB: Failed to discard %p! errno=%d (pUrb=%p)\n", pUrbLnx->KUrb.usercontext, errno, pUrb)); /* serious! */
+ }
+ if (pUrbLnx->pSplitNext == pCur)
+ {
+ pUrbLnx->pSplitNext = NULL;
+ break;
+ }
+ pUrbLnx = pUrbLnx->pSplitNext; Assert(pUrbLnx);
+ }
+ }
+
+ /* free the unsubmitted ones. */
+ while (pCur)
+ {
+ PUSBPROXYURBLNX pFree = pCur;
+ pCur = pCur->pSplitNext;
+ usbProxyLinuxUrbFree(pProxyDev, pFree);
+ }
+
+ /* send unplug event if we failed with ENODEV originally. */
+ if (*pfUnplugged)
+ usbProxLinuxUrbUnplugged(pProxyDev);
+}
+
+/**
+ * Submit one URB through the usbfs IOCTL interface, with
+ * retries
+ *
+ * @returns VBox status code.
+ */
+static int usbProxyLinuxSubmitURB(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged)
+{
+ RT_NOREF(pUrb);
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ unsigned cTries = 0;
+
+ while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pCur->KUrb))
+ {
+ if (errno == EINTR)
+ continue;
+ if (errno == ENODEV)
+ {
+ Log(("usbProxyLinuxSubmitURB: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+ *pfUnplugged = true;
+ return RTErrConvertFromErrno(errno);
+ }
+
+ Log(("usb-linux: Submit URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n",
+ pUrb, errno, pCur->KUrb.type, pCur->KUrb.endpoint, pCur->KUrb.buffer_length, cTries));
+ if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */
+ continue;
+
+ return RTErrConvertFromErrno(errno);
+ }
+ return VINF_SUCCESS;
+}
+
+/** The split size. 16K in known Linux kernel versions. */
+#define SPLIT_SIZE 0x4000
+
+/**
+ * Create a URB fragment of up to SPLIT_SIZE size and hook it
+ * into the list of fragments.
+ *
+ * @returns pointer to newly allocated URB fragment or NULL.
+ */
+static PUSBPROXYURBLNX usbProxyLinuxSplitURBFragment(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pHead, PUSBPROXYURBLNX pCur)
+{
+ PUSBPROXYURBLNX pNew;
+ uint32_t cbLeft = pCur->cbSplitRemaining;
+ uint8_t *pb = (uint8_t *)pCur->KUrb.buffer;
+
+ LogFlowFunc(("pProxyDev=%p pHead=%p pCur=%p\n", pProxyDev, pHead, pCur));
+
+ Assert(cbLeft != 0);
+ pNew = pCur->pSplitNext = usbProxyLinuxUrbAlloc(pProxyDev, pHead);
+ if (!pNew)
+ {
+ usbProxyLinuxUrbFreeSplitList(pProxyDev, pHead);
+ return NULL;
+ }
+ Assert(pNew->pSplitHead == pHead);
+ Assert(pNew->pSplitNext == NULL);
+
+ pNew->KUrb = pHead->KUrb;
+ pNew->KUrb.buffer = pb + pCur->KUrb.buffer_length;
+ pNew->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE);
+ pNew->KUrb.actual_length = 0;
+
+ cbLeft -= pNew->KUrb.buffer_length;
+ Assert(cbLeft < INT32_MAX);
+ pNew->cbSplitRemaining = cbLeft;
+ LogFlowFunc(("returns pNew=%p\n", pNew));
+ return pNew;
+}
+
+/**
+ * Try splitting up a VUSB URB into smaller URBs which the
+ * linux kernel (usbfs) can deal with.
+ *
+ * NB: For ShortOK reads things get a little tricky - we don't
+ * know how much data is going to arrive and not all the
+ * fragment URBs might be filled. We can only safely set up one
+ * URB at a time -> worse performance but correct behaviour.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The proxy device.
+ * @param pUrbLnx The linux URB which was rejected because of being too big.
+ * @param pUrb The VUSB URB.
+ */
+static int usbProxyLinuxUrbQueueSplit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PVUSBURB pUrb)
+{
+ /*
+ * Split it up into SPLIT_SIZE sized blocks.
+ */
+ const unsigned cKUrbs = (pUrb->cbData + SPLIT_SIZE - 1) / SPLIT_SIZE;
+ LogFlow(("usbProxyLinuxUrbQueueSplit: pUrb=%p cKUrbs=%d cbData=%d\n", pUrb, cKUrbs, pUrb->cbData));
+
+ uint32_t cbLeft = pUrb->cbData;
+ uint8_t *pb = &pUrb->abData[0];
+
+ /* the first one (already allocated) */
+ switch (pUrb->enmType)
+ {
+ default: /* shut up gcc */
+ case VUSBXFERTYPE_BULK: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; break;
+ case VUSBXFERTYPE_INTR: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; break;
+ case VUSBXFERTYPE_MSG: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; break;
+ case VUSBXFERTYPE_ISOC:
+ AssertMsgFailed(("We can't split isochronous URBs!\n"));
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ return VERR_INVALID_PARAMETER; /** @todo Better status code. */
+ }
+ pUrbLnx->KUrb.endpoint = pUrb->EndPt;
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ pUrbLnx->KUrb.endpoint |= 0x80;
+ pUrbLnx->KUrb.flags = 0;
+ if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk)
+ pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK;
+ pUrbLnx->KUrb.status = 0;
+ pUrbLnx->KUrb.buffer = pb;
+ pUrbLnx->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE);
+ pUrbLnx->KUrb.actual_length = 0;
+ pUrbLnx->KUrb.start_frame = 0;
+ pUrbLnx->KUrb.number_of_packets = 0;
+ pUrbLnx->KUrb.error_count = 0;
+ pUrbLnx->KUrb.signr = 0;
+ pUrbLnx->KUrb.usercontext = pUrb;
+ pUrbLnx->pSplitHead = pUrbLnx;
+ pUrbLnx->pSplitNext = NULL;
+
+ PUSBPROXYURBLNX pCur = pUrbLnx;
+
+ cbLeft -= pUrbLnx->KUrb.buffer_length;
+ pUrbLnx->cbSplitRemaining = cbLeft;
+
+ int rc = VINF_SUCCESS;
+ bool fUnplugged = false;
+ if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk)
+ {
+ /* Subsequent fragments will be queued only after the previous fragment is reaped
+ * and only if necessary.
+ */
+ Log(("usb-linux: Large ShortOK read, only queuing first fragment.\n"));
+ Assert(pUrbLnx->cbSplitRemaining > 0 && pUrbLnx->cbSplitRemaining < 256 * _1K);
+ rc = usbProxyLinuxSubmitURB(pProxyDev, pUrbLnx, pUrb, &fUnplugged);
+ }
+ else
+ {
+ /* the rest. */
+ unsigned i;
+ for (i = 1; i < cKUrbs; i++)
+ {
+ pCur = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx, pCur);
+ if (!pCur)
+ return VERR_NO_MEMORY;
+ }
+ Assert(pCur->cbSplitRemaining == 0);
+
+ /* Submit the blocks. */
+ pCur = pUrbLnx;
+ for (i = 0; i < cKUrbs; i++, pCur = pCur->pSplitNext)
+ {
+ rc = usbProxyLinuxSubmitURB(pProxyDev, pCur, pUrb, &fUnplugged);
+ if (RT_FAILURE(rc))
+ break;
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ {
+ pUrb->Dev.pvPrivate = pUrbLnx;
+ usbProxyLinuxUrbLinkInFlight(USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX), pUrbLnx);
+ LogFlow(("usbProxyLinuxUrbQueueSplit: ok\n"));
+ return VINF_SUCCESS;
+ }
+
+ usbProxyLinuxCleanupFailedSubmit(pProxyDev, pUrbLnx, pCur, pUrb, &fUnplugged);
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxyLinuxUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ int rc = VINF_SUCCESS;
+ unsigned cTries;
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ LogFlow(("usbProxyLinuxUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n",
+ usbProxyGetName(pProxyDev), pUrb, pUrb->EndPt, pUrb->cbData));
+
+ /*
+ * Allocate a linux urb.
+ */
+ PUSBPROXYURBLNX pUrbLnx = usbProxyLinuxUrbAlloc(pProxyDev, NULL);
+ if (!pUrbLnx)
+ return VERR_NO_MEMORY;
+
+ pUrbLnx->KUrb.endpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0);
+ pUrbLnx->KUrb.status = 0;
+ pUrbLnx->KUrb.flags = 0;
+ if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk)
+ pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK;
+ pUrbLnx->KUrb.buffer = pUrb->abData;
+ pUrbLnx->KUrb.buffer_length = pUrb->cbData;
+ pUrbLnx->KUrb.actual_length = 0;
+ pUrbLnx->KUrb.start_frame = 0;
+ pUrbLnx->KUrb.number_of_packets = 0;
+ pUrbLnx->KUrb.error_count = 0;
+ pUrbLnx->KUrb.signr = 0;
+ pUrbLnx->KUrb.usercontext = pUrb;
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL;
+ if (pUrb->cbData < sizeof(VUSBSETUP))
+ {
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ return VERR_BUFFER_UNDERFLOW;
+ }
+ usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData);
+ LogFlow(("usbProxyLinuxUrbQueue: message\n"));
+ break;
+ case VUSBXFERTYPE_BULK:
+ pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK;
+ break;
+ case VUSBXFERTYPE_ISOC:
+ pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_ISO;
+ pUrbLnx->KUrb.flags |= USBDEVFS_URB_ISO_ASAP;
+ pUrbLnx->KUrb.number_of_packets = pUrb->cIsocPkts;
+ unsigned i;
+ for (i = 0; i < pUrb->cIsocPkts; i++)
+ {
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+ pUrbLnx->KUrb.iso_frame_desc[i].length = pUrb->aIsocPkts[i].cb;
+ pUrbLnx->KUrb.iso_frame_desc[i].actual_length = 0;
+ pUrbLnx->KUrb.iso_frame_desc[i].status = 0x7fff;
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ }
+ break;
+ case VUSBXFERTYPE_INTR:
+ pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT;
+ break;
+ default:
+ rc = VERR_INVALID_PARAMETER; /** @todo better status code. */
+ }
+
+ /*
+ * We have to serialize access by using the critial section here because this
+ * thread might be suspended after submitting the URB but before linking it into
+ * the in flight list. This would get us in trouble when reaping the URB on another
+ * thread while it isn't in the in flight list.
+ *
+ * Linking the URB into the list before submitting it like it was done in the past is not
+ * possible either because submitting the URB might fail here because the device gets
+ * detached. The reaper thread gets this event too and might race this thread before we
+ * can unlink the URB from the active list and the common code might end up freeing
+ * the common URB structure twice.
+ */
+ RTCritSectEnter(&pDevLnx->CritSect);
+ /*
+ * Submit it.
+ */
+ cTries = 0;
+ while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pUrbLnx->KUrb))
+ {
+ if (errno == EINTR)
+ continue;
+ if (errno == ENODEV)
+ {
+ rc = RTErrConvertFromErrno(errno);
+ Log(("usbProxyLinuxUrbQueue: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev)));
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData);
+
+ RTCritSectLeave(&pDevLnx->CritSect);
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ usbProxLinuxUrbUnplugged(pProxyDev);
+ return rc;
+ }
+
+ /*
+ * usbfs has or used to have a low buffer limit (16KB) in order to prevent
+ * processes wasting kmalloc'ed memory. It will return EINVAL if break that
+ * limit, and we'll have to split the VUSB URB up into multiple linux URBs.
+ *
+ * Since this is a limit which is subject to change, we cannot check for it
+ * before submitting the URB. We just have to try and fail.
+ */
+ if ( errno == EINVAL
+ && pUrb->cbData >= 8*_1K)
+ {
+ rc = usbProxyLinuxUrbQueueSplit(pProxyDev, pUrbLnx, pUrb);
+ RTCritSectLeave(&pDevLnx->CritSect);
+ return rc;
+ }
+
+ Log(("usb-linux: Queue URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n",
+ pUrb, errno, pUrbLnx->KUrb.type, pUrbLnx->KUrb.endpoint, pUrbLnx->KUrb.buffer_length, cTries));
+ if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */
+ continue;
+
+ RTCritSectLeave(&pDevLnx->CritSect);
+ rc = RTErrConvertFromErrno(errno);
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData);
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ return rc;
+ }
+
+ usbProxyLinuxUrbLinkInFlight(pDevLnx, pUrbLnx);
+ RTCritSectLeave(&pDevLnx->CritSect);
+
+ LogFlow(("usbProxyLinuxUrbQueue: ok\n"));
+ pUrb->Dev.pvPrivate = pUrbLnx;
+ return rc;
+}
+
+
+/**
+ * Translate the linux status to a VUSB status.
+ *
+ * @remarks see cc_to_error in ohci.h, uhci_map_status in uhci-q.c,
+ * sitd_complete+itd_complete in ehci-sched.c, and qtd_copy_status in
+ * ehci-q.c.
+ */
+static VUSBSTATUS vusbProxyLinuxStatusToVUsbStatus(int iStatus)
+{
+ switch (iStatus)
+ {
+ /** @todo VUSBSTATUS_NOT_ACCESSED */
+ case -EXDEV: /* iso transfer, partial result. */
+ case 0:
+ return VUSBSTATUS_OK;
+
+ case -EILSEQ:
+ return VUSBSTATUS_CRC;
+
+ case -EREMOTEIO: /* ehci and ohci uses this for underflow error. */
+ return VUSBSTATUS_DATA_UNDERRUN;
+ case -EOVERFLOW:
+ return VUSBSTATUS_DATA_OVERRUN;
+
+ case -ETIME:
+ case -ENODEV:
+ return VUSBSTATUS_DNR;
+
+ //case -ECOMM:
+ // return VUSBSTATUS_BUFFER_OVERRUN;
+ //case -ENOSR:
+ // return VUSBSTATUS_BUFFER_UNDERRUN;
+
+ case -EPROTO:
+ Log(("vusbProxyLinuxStatusToVUsbStatus: DNR/EPPROTO!!\n"));
+ return VUSBSTATUS_DNR;
+
+ case -EPIPE:
+ Log(("vusbProxyLinuxStatusToVUsbStatus: STALL/EPIPE!!\n"));
+ return VUSBSTATUS_STALL;
+
+ case -ESHUTDOWN:
+ Log(("vusbProxyLinuxStatusToVUsbStatus: SHUTDOWN!!\n"));
+ return VUSBSTATUS_STALL;
+
+ case -ENOENT:
+ Log(("vusbProxyLinuxStatusToVUsbStatus: ENOENT!!\n"));
+ return VUSBSTATUS_STALL;
+
+ default:
+ Log(("vusbProxyLinuxStatusToVUsbStatus: status %d!!\n", iStatus));
+ return VUSBSTATUS_STALL;
+ }
+}
+
+
+/**
+ * Get and translates the linux status to a VUSB status.
+ */
+static VUSBSTATUS vusbProxyLinuxUrbGetStatus(PUSBPROXYURBLNX pUrbLnx)
+{
+ return vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.status);
+}
+
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyLinuxUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ PUSBPROXYURBLNX pUrbLnx = NULL;
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+
+ /*
+ * Block for requested period.
+ *
+ * It seems to me that the path of poll() is shorter and
+ * involves less semaphores than ioctl() on usbfs. So, we'll
+ * do a poll regardless of whether cMillies == 0 or not.
+ */
+ if (cMillies)
+ {
+ int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : cMillies;
+
+ for (;;)
+ {
+ struct pollfd pfd[2];
+ pfd[0].fd = RTFileToNative(pDevLnx->hFile);
+ pfd[0].events = POLLOUT | POLLWRNORM /* completed async */
+ | POLLERR | POLLHUP /* disconnected */;
+ pfd[0].revents = 0;
+
+ pfd[1].fd = RTPipeToNative(pDevLnx->hPipeWakeupR);
+ pfd[1].events = POLLIN | POLLHUP;
+ pfd[1].revents = 0;
+
+ int rc = poll(&pfd[0], 2, cMilliesWait);
+ Log(("usbProxyLinuxUrbReap: poll rc = %d\n", rc));
+ if (rc >= 1)
+ {
+ /* If the pipe caused the return drain it. */
+ if (pfd[1].revents & POLLIN)
+ {
+ uint8_t bRead;
+ size_t cbIgnored = 0;
+ RTPipeRead(pDevLnx->hPipeWakeupR, &bRead, 1, &cbIgnored);
+ }
+ break;
+ }
+ if (rc >= 0)
+ return NULL;
+
+ if (errno != EAGAIN)
+ {
+ Log(("usb-linux: Reap URB - poll -> %d errno=%d pProxyDev=%s\n", rc, errno, usbProxyGetName(pProxyDev)));
+ return NULL;
+ }
+ Log(("usbProxyLinuxUrbReap: poll again - weird!!!\n"));
+ }
+ }
+
+ /*
+ * Reap URBs, non-blocking.
+ */
+ for (;;)
+ {
+ struct usbdevfs_urb *pKUrb;
+ while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_REAPURBNDELAY, &pKUrb))
+ if (errno != EINTR)
+ {
+ if (errno == ENODEV)
+ usbProxLinuxUrbUnplugged(pProxyDev);
+ else
+ Log(("usb-linux: Reap URB. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev)));
+ return NULL;
+ }
+ pUrbLnx = RT_FROM_MEMBER(pKUrb, USBPROXYURBLNX, KUrb);
+
+ /* split list: Is the entire split list done yet? */
+ if (pUrbLnx->pSplitHead)
+ {
+ pUrbLnx->fSplitElementReaped = true;
+
+ /* for variable size URBs, we may need to queue more if the just-reaped URB was completely filled */
+ if (pUrbLnx->cbSplitRemaining && (pKUrb->actual_length == pKUrb->buffer_length) && !pUrbLnx->pSplitNext)
+ {
+ bool fUnplugged = false;
+ bool fSucceeded;
+
+ Assert(pUrbLnx->pSplitHead);
+ Assert((pKUrb->endpoint & 0x80) && !(pKUrb->flags & USBDEVFS_URB_SHORT_NOT_OK));
+ PUSBPROXYURBLNX pNew = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx->pSplitHead, pUrbLnx);
+ if (!pNew)
+ {
+ Log(("usb-linux: Allocating URB fragment failed. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev)));
+ return NULL;
+ }
+ PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext;
+ fSucceeded = usbProxyLinuxSubmitURB(pProxyDev, pNew, pUrb, &fUnplugged);
+ if (fUnplugged)
+ usbProxLinuxUrbUnplugged(pProxyDev);
+ if (!fSucceeded)
+ return NULL;
+ continue; /* try reaping another URB */
+ }
+ PUSBPROXYURBLNX pCur;
+ for (pCur = pUrbLnx->pSplitHead; pCur; pCur = pCur->pSplitNext)
+ if (!pCur->fSplitElementReaped)
+ {
+ pUrbLnx = NULL;
+ break;
+ }
+ if (!pUrbLnx)
+ continue;
+ pUrbLnx = pUrbLnx->pSplitHead;
+ }
+ break;
+ }
+
+ /*
+ * Ok, we got one!
+ */
+ PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext;
+ if ( pUrb
+ && !pUrbLnx->fCanceledBySubmit)
+ {
+ if (pUrbLnx->pSplitHead)
+ {
+ /* split - find the end byte and the first error status. */
+ Assert(pUrbLnx == pUrbLnx->pSplitHead);
+ uint8_t *pbEnd = &pUrb->abData[0];
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ PUSBPROXYURBLNX pCur;
+ for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext)
+ {
+ if (pCur->KUrb.actual_length)
+ pbEnd = (uint8_t *)pCur->KUrb.buffer + pCur->KUrb.actual_length;
+ if (pUrb->enmStatus == VUSBSTATUS_OK)
+ pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pCur);
+ }
+ pUrb->cbData = pbEnd - &pUrb->abData[0];
+ usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx);
+ usbProxyLinuxUrbFreeSplitList(pProxyDev, pUrbLnx);
+ }
+ else
+ {
+ /* unsplit. */
+ pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pUrbLnx);
+ pUrb->cbData = pUrbLnx->KUrb.actual_length;
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ unsigned i, off;
+ for (i = 0, off = 0; i < pUrb->cIsocPkts; i++)
+ {
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Warray-bounds"
+#endif
+ pUrb->aIsocPkts[i].enmStatus = vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.iso_frame_desc[i].status);
+ Assert(pUrb->aIsocPkts[i].off == off);
+ pUrb->aIsocPkts[i].cb = pUrbLnx->KUrb.iso_frame_desc[i].actual_length;
+ off += pUrbLnx->KUrb.iso_frame_desc[i].length;
+#if RT_GNUC_PREREQ(4, 6)
+# pragma GCC diagnostic pop
+#endif
+ }
+ }
+ usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx);
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ }
+ pUrb->Dev.pvPrivate = NULL;
+
+ /* some adjustments for message transfers. */
+ if (pUrb->enmType == VUSBXFERTYPE_MSG)
+ {
+ pUrb->cbData += sizeof(VUSBSETUP);
+ usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData);
+ }
+ }
+ else
+ {
+ usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx);
+ usbProxyLinuxUrbFree(pProxyDev, pUrbLnx);
+ pUrb = NULL;
+ }
+
+ LogFlow(("usbProxyLinuxUrbReap: pProxyDev=%s returns %p\n", usbProxyGetName(pProxyDev), pUrb));
+ return pUrb;
+}
+
+
+/**
+ * Cancels the URB.
+ * The URB requires reaping, so we don't change its state.
+ */
+static DECLCALLBACK(int) usbProxyLinuxUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ int rc = VINF_SUCCESS;
+ PUSBPROXYURBLNX pUrbLnx = (PUSBPROXYURBLNX)pUrb->Dev.pvPrivate;
+ if (pUrbLnx->pSplitHead)
+ {
+ /* split */
+ Assert(pUrbLnx == pUrbLnx->pSplitHead);
+ PUSBPROXYURBLNX pCur;
+ for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext)
+ {
+ if (pCur->fSplitElementReaped)
+ continue;
+ if ( !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX)
+ || errno == ENOENT)
+ continue;
+ if (errno == ENODEV)
+ break;
+ /** @todo Think about how to handle errors wrt. to the status code. */
+ Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n",
+ pUrb, errno, usbProxyGetName(pProxyDev)));
+ }
+ }
+ else
+ {
+ /* unsplit */
+ if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, true, UINT32_MAX)
+ && errno != ENODEV /* deal with elsewhere. */
+ && errno != ENOENT)
+ {
+ Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n",
+ pUrb, errno, usbProxyGetName(pProxyDev)));
+ rc = RTErrConvertFromErrno(errno);
+ }
+ }
+
+ return rc;
+}
+
+
+static DECLCALLBACK(int) usbProxyLinuxWakeup(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX);
+ size_t cbIgnored;
+
+ LogFlowFunc(("pProxyDev=%p\n", pProxyDev));
+
+ return RTPipeWrite(pDevLnx->hPipeWakeupW, "", 1, &cbIgnored);
+}
+
+/**
+ * The Linux USB Proxy Backend.
+ */
+const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ /* pszName */
+ "host",
+ /* cbBackend */
+ sizeof(USBPROXYDEVLNX),
+ usbProxyLinuxOpen,
+ usbProxyLinuxInit,
+ usbProxyLinuxClose,
+ usbProxyLinuxReset,
+ usbProxyLinuxSetConfig,
+ usbProxyLinuxClaimInterface,
+ usbProxyLinuxReleaseInterface,
+ usbProxyLinuxSetInterface,
+ usbProxyLinuxClearHaltedEp,
+ usbProxyLinuxUrbQueue,
+ usbProxyLinuxUrbCancel,
+ usbProxyLinuxUrbReap,
+ usbProxyLinuxWakeup,
+ 0
+};
+
+
+/*
+ * Local Variables:
+ * mode: c
+ * c-file-style: "bsd"
+ * c-basic-offset: 4
+ * End:
+ */
+
diff --git a/src/VBox/Devices/USB/os2/Makefile.kup b/src/VBox/Devices/USB/os2/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/os2/Makefile.kup
diff --git a/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp
new file mode 100644
index 00000000..3b1997e2
--- /dev/null
+++ b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp
@@ -0,0 +1,879 @@
+/* $Id: USBProxyDevice-os2.cpp $ */
+/** @file
+ * USB device proxy - the Linux backend.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/stream.h>
+#include <iprt/alloc.h>
+#include <iprt/thread.h>
+#include <iprt/time.h>
+#include <iprt/asm.h>
+#include <iprt/string.h>
+#include <iprt/semaphore.h>
+#include <iprt/file.h>
+#include "../USBProxyDevice.h"
+
+#define INCL_BASE
+#define INCL_ERRORS
+#include <os2.h>
+#include <usbcalls.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Structure for keeping track of the URBs for a device.
+ */
+typedef struct USBPROXYURBOS2
+{
+ /** Pointer to the virtual URB. */
+ PVUSBURB pUrb;
+ /** Pointer to the next OS/2 URB. */
+ struct USBPROXYURBOS2 *pNext;
+ /** Pointer to the previous OS/2 URB. */
+ struct USBPROXYURBOS2 *pPrev;
+} USBPROXYURBOS2, *PUSBPROXYURBOS2;
+
+/**
+ * Data for the OS/2 usb proxy backend.
+ */
+typedef struct USBPROXYDEVOS2
+{
+ /** The async thread for this device.
+ * Currently only one thread is used, but this might have to change... */
+ RTTHREAD Thread;
+ /** Thread termination indicator. */
+ bool volatile fTerminate;
+ /** The USB handle. */
+ USBHANDLE hDevice;
+ /** Critical section protecting the lists. */
+ RTCRITSECT CritSect;
+ /** For blocking reap calls. */
+ RTSEMEVENT EventSyncWait;
+ /** List of URBs to process. Doubly linked. */
+ PUSBPROXYURBOS2 pTodoHead;
+ /** The tail pointer. */
+ PUSBPROXYURBOS2 pTodoTail;
+ /** The list of free linux URBs. Singly linked. */
+ PUSBPROXYURBOS2 pFreeHead;
+ /** The list of active linux URBs. Doubly linked.
+ * We must maintain this so we can properly reap URBs of a detached device.
+ * Only the split head will appear in this list. */
+ PUSBPROXYURBOS2 pInFlightHead;
+ /** The list of landed linux URBs. Doubly linked.
+ * Only the split head will appear in this list. */
+ PUSBPROXYURBOS2 pTaxingHead;
+ /** The tail of the landed linux URBs. */
+ PUSBPROXYURBOS2 pTaxingTail;
+} USBPROXYDEVOS2, *PUSBPROXYDEVOS2;
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+#ifdef DYNAMIC_USBCALLS
+static int usbProxyOs2GlobalInit(void);
+#endif
+static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev);
+static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2);
+static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev);
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+#ifdef DYNAMIC_USBCALLS
+static HMODULE g_hmod;
+static APIRET (APIENTRY *g_pfnUsbOpen)(PUSBHANDLE, USHORT, USHORT, USHORT, USHORT);
+static APIRET (APIENTRY *g_pfnUsbClose)(USBHANDLE);
+static APIRET (APIENTRY *g_pfnUsbCtrlMessage)(USBHANDLE, UCHAR, UCHAR, USHORT, USHORT, USHORT, void *, ULONG);
+static APIRET (APIENTRY *g_pfnUsbBulkRead2)(USBHANDLE, UCHAR, UCHAR, BOOL, PULONG, void *, ULONG);
+static APIRET (APIENTRY *g_pfnUsbBulkWrite2)(USBHANDLE, UCHAR, UCHAR, BOOL, ULONG, void *, ULONG);
+#else
+# define g_pfnUsbOpen UsbOpen
+# define g_pfnUsbClose UsbClose
+# define g_pfnUsbCtrlMessage UsbCtrlMessage
+# define g_pfnUsbBulkRead2 UsbBulkRead2
+# define g_pfnUsbBulkWrite2 UsbBulkWrite2
+#endif
+
+
+
+#ifdef DYNAMIC_USBCALLS
+/**
+ * Loads usbcalls.dll and resolves the symbols we need.
+ *
+ * The usbcalls.dll will not be unloaded.
+ *
+ * @returns VBox status code.
+ */
+static int usbProxyOs2GlobalInit(void)
+{
+ int rc = DosLoadModule(NULL, 0, (PCSZ)"usbcalls", &g_hmod);
+ rc = RTErrConvertFromOS2(rc);
+ if (RT_SUCCESS(rc))
+ {
+ if ( (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbOpen", (PPFN)&g_pfnUsbOpen)) == NO_ERROR
+ && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbClose", (PPFN)&g_pfnUsbClose)) == NO_ERROR
+ && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbCtrlMessage", (PPFN)&g_pfnUsbCtrlMessage)) == NO_ERROR
+ && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkRead", (PPFN)&g_pfnUsbBulkRead)) == NO_ERROR
+ && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkWrite", (PPFN)&g_pfnUsbBulkWrite)) == NO_ERROR
+ )
+ {
+
+ return VINF_SUCCESS;
+ }
+
+ g_pfnUsbOpen = NULL;
+ g_pfnUsbClose = NULL;
+ g_pfnUsbCtrlMessage = NULL;
+ g_pfnUsbBulkRead = NULL;
+ g_pfnUsbBulkWrite = NULL;
+ DosFreeModule(g_hmod);
+ }
+
+ g_hmod = NULLHANDLE;
+ return rc;
+}
+#endif
+
+
+
+/**
+ * Allocates a OS/2 URB request structure.
+ * @returns Pointer to an active URB request.
+ * @returns NULL on failure.
+ * @param pProxyDev The proxy device instance.
+ */
+static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ PUSBPROXYURBOS2 pUrbOs2;
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+
+ /*
+ * Try remove a linux URB from the free list, if none there allocate a new one.
+ */
+ pUrbOs2 = pDevOs2->pFreeHead;
+ if (pUrbOs2)
+ pDevOs2->pFreeHead = pUrbOs2->pNext;
+ else
+ {
+ RTCritSectLeave(&pDevOs2->CritSect);
+ pUrbOs2 = (PUSBPROXYURBOS2)RTMemAlloc(sizeof(*pUrbOs2));
+ if (!pUrbOs2)
+ return NULL;
+ RTCritSectEnter(&pDevOs2->CritSect);
+ }
+
+ /*
+ * Link it into the active list
+ */
+ pUrbOs2->pPrev = NULL;
+ pUrbOs2->pNext = pDevOs2->pInFlightHead;
+ if (pUrbOs2->pNext)
+ pUrbOs2->pNext->pPrev = pUrbOs2;
+ pDevOs2->pInFlightHead = pUrbOs2;
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+ return pUrbOs2;
+}
+
+
+/**
+ * Frees a linux URB request structure.
+ *
+ * @param pProxyDev The proxy device instance.
+ * @param pUrbOs2 The linux URB to free.
+ */
+static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2)
+{
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+
+ /*
+ * Remove from the active list.
+ */
+ if (pUrbOs2->pNext)
+ pUrbOs2->pNext->pPrev = pUrbOs2->pPrev;
+ else if (pDevOs2->pTaxingTail == pUrbOs2)
+ pDevOs2->pTaxingTail = pUrbOs2->pPrev;
+ else if (pDevOs2->pTodoTail == pUrbOs2)
+ pDevOs2->pTodoTail = pUrbOs2->pPrev;
+
+ if (pUrbOs2->pPrev)
+ pUrbOs2->pPrev->pNext = pUrbOs2->pNext;
+ else if (pDevOs2->pTaxingHead == pUrbOs2)
+ pDevOs2->pTaxingHead = pUrbOs2->pNext;
+ else if (pDevOs2->pInFlightHead == pUrbOs2)
+ pDevOs2->pInFlightHead = pUrbOs2->pNext;
+ else if (pDevOs2->pTodoHead == pUrbOs2)
+ pDevOs2->pTodoHead = pUrbOs2->pNext;
+
+ /*
+ * Link it into the free list.
+ */
+ pUrbOs2->pPrev = NULL;
+ pUrbOs2->pNext = pDevOs2->pFreeHead;
+ pDevOs2->pFreeHead = pUrbOs2;
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+}
+
+
+/**
+ * Thread for executing the URBs asynchronously.
+ *
+ * @returns VINF_SUCCESS.
+ * @param Thread Thread handle (IPRT).
+ * @param pvProxyDev Pointer to the proxy device we're servicing.
+ */
+static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev)
+{
+ PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pvProxyDev;
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ size_t cbLow = 0;
+ void *pvLow = NULL;
+
+
+ /*
+ * The main loop.
+ *
+ * We're always in the critsect, except when waiting or submitting a URB.
+ */
+ int rc = RTCritSectEnter(&pDevOs2->CritSect); AssertRC(rc);
+
+ while (!pDevOs2->fTerminate)
+ {
+ /*
+ * Anything to do?
+ */
+ PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTodoHead;
+ if (pUrbOs2)
+ {
+ pDevOs2->pTodoHead = pUrbOs2->pNext;
+ if (pUrbOs2->pNext)
+ pUrbOs2->pNext->pPrev = NULL;
+ else
+ pDevOs2->pTodoTail = NULL;
+
+ /*
+ * Move it to the in-flight list and submit it.
+ */
+ pUrbOs2->pPrev = NULL;
+ pUrbOs2->pNext = pDevOs2->pInFlightHead;
+ if (pDevOs2->pInFlightHead)
+ pDevOs2->pInFlightHead->pPrev = pUrbOs2;
+ //else
+ // pDevOs2->pInFlightTail = pUrbOs2;
+ Log3(("%s: usbProxyOs2AsyncThread: pPickup\n", pUrbOs2->pUrb->pszDesc));
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+
+ /*
+ * Process the URB.
+ */
+ PVUSBURB pUrb = pUrbOs2->pUrb;
+ uint8_t *pbData = &pUrb->abData[0];
+ ULONG cbData = pUrb->cbData;
+ if ( (uintptr_t)pbData >= 0x20000000
+ || ((uintptr_t)pbData & 0xfff))
+ {
+ if (cbData > cbLow)
+ {
+ if (pvLow)
+ DosFreeMem(pvLow);
+ cbLow = (cbData + 0xffff) & ~0xffff;
+ rc = DosAllocMem(&pvLow, cbLow, PAG_WRITE | PAG_READ | OBJ_TILE | PAG_COMMIT);
+ if (rc)
+ {
+ cbLow = 0;
+ pvLow = NULL;
+ }
+ }
+ if (pvLow)
+ pbData = (uint8_t *)memcpy(pvLow, pbData, cbData);
+ }
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ {
+ PVUSBSETUP pSetup = (PVUSBSETUP)&pbData[0];
+ Log2(("%s: usbProxyOs2AsyncThread: CtlrMsg\n", pUrb->pszDesc));
+ rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, /** @todo this API must take a endpoint number! */
+ pSetup->bmRequestType,
+ pSetup->bRequest,
+ pSetup->wValue,
+ pSetup->wIndex,
+ pSetup->wLength,
+ pSetup + 1,
+ 5*60000 /* min */);
+ break;
+ }
+
+ case VUSBXFERTYPE_BULK:
+ {
+ /* there is a thing with altnative interface thing here... */
+
+ if (pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ Log2(("%s: usbProxyOs2AsyncThread: BulkRead %d\n", pUrb->pszDesc, cbData));
+ rc = g_pfnUsbBulkRead2(pDevOs2->hDevice, pUrb->EndPt | 0x80, 0, !pUrb->fShortNotOk, &cbData, pbData, 500);//5*6000);
+ }
+ else
+ {
+ Log2(("%s: usbProxyOs2AsyncThread: BulkWrite %d\n", pUrb->pszDesc, cbData));
+ rc = g_pfnUsbBulkWrite2(pDevOs2->hDevice, pUrb->EndPt, 0, !pUrb->fShortNotOk, cbData, pbData, 500);//5*6000);
+ }
+ break;
+ }
+
+ case VUSBXFERTYPE_INTR:
+ case VUSBXFERTYPE_ISOC:
+ default:
+ Log2(("%s: usbProxyOs2AsyncThread: Unsupported\n", pUrb->pszDesc));
+ rc = USB_IORB_FAILED;
+ break;
+ }
+
+ /* unbuffer */
+ if (pbData == pvLow)
+ memcpy(pUrb->abData, pbData, pUrb->cbData);
+
+ /* Convert rc to USB status code. */
+ int orc = rc;
+ if (!rc)
+ pUrb->enmStatus = VUSBSTATUS_OK;
+ else if (rc == USB_ERROR_LESSTRANSFERED && !pUrb->fShortNotOk)
+ {
+ Assert(pUrb->cbData >= cbData);
+ pUrb->cbData = cbData;
+ pUrb->enmStatus = VUSBSTATUS_DATA_UNDERRUN;
+ }
+ else
+ pUrb->enmStatus = VUSBSTATUS_STALL;
+ Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d \n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc);
+
+ /*
+ * Retire it to the completed list
+ */
+ RTCritSectEnter(&pDevOs2->CritSect);
+
+ pUrbOs2->pNext = NULL;
+ pUrbOs2->pPrev = pDevOs2->pTaxingTail;
+ if (pDevOs2->pTaxingTail)
+ pDevOs2->pTaxingTail->pNext = pUrbOs2;
+ else
+ pDevOs2->pTaxingHead = pUrbOs2;
+ pDevOs2->pTaxingTail = pUrbOs2;
+
+ RTSemEventSignal(pDevOs2->EventSyncWait);
+ Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d!\n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc);
+ }
+ else
+ {
+ RTThreadUserReset(Thread);
+ RTCritSectLeave(&pDevOs2->CritSect);
+
+ /*
+ * Wait for something to do.
+ */
+ RTThreadUserWait(Thread, 30*1000 /* 30 sec */);
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+ }
+ }
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+ if (pvLow)
+ DosFreeMem(pvLow);
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Opens the /proc/bus/usb/bus/addr file.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device instance.
+ * @param pszAddress The path to the device.
+ */
+static int usbProxyOs2Open(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ LogFlow(("usbProxyOs2Open: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+ int rc;
+
+ /*
+ * Lazy init.
+ */
+#ifdef DYNAMIC_USBCALLS
+ if (!g_pfnUsbOpen)
+ {
+ rc = usbProxyOs2GlobalInit();
+ if (RT_FAILURE(rc))
+ return rc;
+ }
+#else
+static bool g_fInitialized = false;
+ if (!g_fInitialized)
+ {
+ rc = InitUsbCalls();
+ if (rc != NO_ERROR)
+ return RTErrConvertFromOS2(rc);
+ g_fInitialized = true;
+ }
+#endif
+
+ /*
+ * Parse out the open parameters from the address string.
+ */
+ uint16_t idProduct = 0;
+ uint16_t idVendor = 0;
+ uint16_t bcdDevice = 0;
+ uint32_t iEnum = 0;
+ const char *psz = pszAddress;
+ do
+ {
+ const char chValue = *psz;
+ AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR);
+ uint64_t u64Value;
+ int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value);
+ AssertReleaseRCReturn(rc, rc);
+ AssertReleaseReturn(!*psz || *psz == ';', rc);
+ switch (chValue)
+ {
+ case 'p': idProduct = (uint16_t)u64Value; break;
+ case 'v': idVendor = (uint16_t)u64Value; break;
+ case 'r': bcdDevice = (uint16_t)u64Value; break;
+ case 'e': iEnum = (uint16_t)u64Value; break;
+ default:
+ AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR);
+ }
+ if (*psz == ';')
+ psz++;
+ } while (*psz);
+
+
+ /*
+ * Try open (acquire) it.
+ */
+ USBHANDLE hDevice = 0;
+ int urc = rc = g_pfnUsbOpen(&hDevice, idVendor, idProduct, bcdDevice, iEnum);
+ if (!rc)
+ {
+ /*
+ * Allocate and initialize the OS/2 backend data.
+ */
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)RTMemAllocZ(sizeof(*pDevOs2));
+ if (pDevOs2)
+ {
+ pDevOs2->hDevice = hDevice;
+ pDevOs2->fTerminate = false;
+ rc = RTCritSectInit(&pDevOs2->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTSemEventCreate(&pDevOs2->EventSyncWait);
+ if (RT_SUCCESS(rc))
+ {
+ pProxyDev->Backend.pv = pDevOs2;
+
+ /** @todo
+ * Determine the active configuration.
+ */
+ //pProxyDev->cIgnoreSetConfigs = 1;
+ //pProxyDev->iActiveCfg = 1;
+ pProxyDev->cIgnoreSetConfigs = 0;
+ pProxyDev->iActiveCfg = -1;
+
+ /*
+ * Create the async worker thread and we're done.
+ */
+ rc = RTThreadCreate(&pDevOs2->Thread, usbProxyOs2AsyncThread, pProxyDev, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "usbproxy");
+ if (RT_SUCCESS(rc))
+ {
+ LogFlow(("usbProxyOs2Open(%p, %s): returns successfully - iActiveCfg=%d\n",
+ pProxyDev, pszAddress, pProxyDev->iActiveCfg));
+ return VINF_SUCCESS;
+ }
+
+ /* failure */
+ RTSemEventDestroy(pDevOs2->EventSyncWait);
+ }
+ RTCritSectDelete(&pDevOs2->CritSect);
+ }
+ RTMemFree(pDevOs2);
+ }
+ else
+ rc = VERR_NO_MEMORY;
+ g_pfnUsbClose(hDevice);
+ }
+ else
+ rc = VERR_VUSB_USBFS_PERMISSION; /** @todo fix me */
+
+ Log(("usbProxyOs2Open(%p, %s) failed, rc=%Rrc! urc=%d\n", pProxyDev, pszAddress, rc, urc)); NOREF(urc);
+ pProxyDev->Backend.pv = NULL;
+
+ NOREF(pvBackend);
+ return rc;
+}
+
+
+/**
+ * Closes the proxy device.
+ */
+static void usbProxyOs2Close(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow(("usbProxyOs2Close: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ Assert(pDevOs2);
+ if (!pDevOs2)
+ return;
+
+ /*
+ * Tell the thread to terminate.
+ */
+ ASMAtomicXchgBool(&pDevOs2->fTerminate, true);
+ int rc = RTThreadUserSignal(pDevOs2->Thread); AssertRC(rc);
+ rc = RTThreadWait(pDevOs2->Thread, 60*1000 /* 1 min */, NULL); AssertRC(rc);
+
+ /*
+ * Now we can free all the resources and close the device.
+ */
+ RTCritSectDelete(&pDevOs2->CritSect);
+ RTSemEventDestroy(pDevOs2->EventSyncWait);
+
+ Assert(!pDevOs2->pInFlightHead);
+ Assert(!pDevOs2->pTodoHead);
+ Assert(!pDevOs2->pTodoTail);
+ Assert(!pDevOs2->pTaxingHead);
+ Assert(!pDevOs2->pTaxingTail);
+
+ PUSBPROXYURBOS2 pUrbOs2;
+ while ((pUrbOs2 = pDevOs2->pFreeHead) != NULL)
+ {
+ pDevOs2->pFreeHead = pUrbOs2->pNext;
+ RTMemFree(pUrbOs2);
+ }
+
+ g_pfnUsbClose(pDevOs2->hDevice);
+ pDevOs2->hDevice = 0;
+
+ RTMemFree(pDevOs2);
+ pProxyDev->Backend.pv = NULL;
+ LogFlow(("usbProxyOs2Close: returns\n"));
+}
+
+
+/** @interface_method_impl{USBPROXYBACK,pfnReset} */
+static int usbProxyOs2Reset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * SET_CONFIGURATION.
+ *
+ * The caller makes sure that it's not called first time after open or reset
+ * with the active interface.
+ *
+ * @returns success indicator.
+ * @param pProxyDev The device instance data.
+ * @param iCfg The configuration to set.
+ */
+static int usbProxyOs2SetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#x\n",
+ pProxyDev->pUsbIns->pszName, iCfg));
+
+ /*
+ * This is sync - bad.
+ */
+ int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice,
+ 0x00, /* bmRequestType - ?? */
+ 0x09, /* bRequest - ?? */
+ iCfg, /* wValue - configuration */
+ 0, /* wIndex*/
+ 0, /* wLength */
+ NULL, /* pvData */
+ 50 /* Timeout (ms) */);
+ if (rc)
+ LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#X -> rc=%d\n", pProxyDev->pUsbIns->pszName, iCfg, rc));
+ return rc == 0;
+}
+
+
+/**
+ * Claims an interface.
+ * @returns success indicator.
+ */
+static int usbProxyOs2ClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ LogFlow(("usbProxyOs2ClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf));
+ return true;
+}
+
+
+/**
+ * Releases an interface.
+ * @returns success indicator.
+ */
+static int usbProxyOs2ReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ LogFlow(("usbProxyOs2ReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf));
+ return true;
+}
+
+
+/**
+ * SET_INTERFACE.
+ *
+ * @returns success indicator.
+ */
+static int usbProxyOs2SetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt)
+{
+ LogFlow(("usbProxyOs2SetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt));
+ return true;
+}
+
+
+/**
+ * Clears the halted endpoint 'EndPt'.
+ */
+static bool usbProxyOs2ClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt)
+{
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%x\n", pProxyDev->pUsbIns->pszName, EndPt));
+
+ /*
+ * This is sync - bad.
+ */
+ int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice,
+ 0x02, /* bmRequestType - ?? */
+ 0x01, /* bRequest - ?? */
+ 0, /* wValue - endpoint halt */
+ EndPt, /* wIndex - endpoint # */
+ 0, /* wLength */
+ NULL, /* pvData */
+ 50 /* Timeout (ms) */);
+ if (rc)
+ LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%u -> rc=%d\n", pProxyDev->pUsbIns->pszName, EndPt, rc));
+ return rc == 0;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static int usbProxyOs2UrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+ LogFlow(("usbProxyOs2UrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n",
+ pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData));
+
+ /*
+ * Quickly validate the input.
+ */
+ switch (pUrb->enmDir)
+ {
+ case VUSBDIRECTION_IN:
+ case VUSBDIRECTION_OUT:
+ break;
+ default:
+ AssertMsgFailed(("usbProxyOs2UrbQueue: Invalid direction %d\n", pUrb->enmDir));
+ return false;
+ }
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ break;
+ case VUSBXFERTYPE_BULK:
+ break;
+/// @todo case VUSBXFERTYPE_INTR:
+// break;
+// case VUSBXFERTYPE_ISOC:
+// break;
+ default:
+ return false;
+ }
+
+ /*
+ * Allocate an OS/2 urb tracking structure, initialize it,
+ * add it to the todo list, and wake up the async thread.
+ */
+ PUSBPROXYURBOS2 pUrbOs2 = usbProxyOs2UrbAlloc(pProxyDev);
+ if (!pUrbOs2)
+ return false;
+
+ pUrbOs2->pUrb = pUrb;
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+
+ pUrbOs2->pNext = NULL;
+ pUrbOs2->pPrev = pDevOs2->pTodoTail;
+ if (pDevOs2->pTodoTail)
+ pDevOs2->pTodoTail->pNext = pUrbOs2;
+ else
+ pDevOs2->pTodoHead = pUrbOs2;
+ pDevOs2->pTodoTail = pUrbOs2;
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+
+ RTThreadUserSignal(pDevOs2->Thread);
+ return true;
+}
+
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all.
+ */
+static PVUSBURB usbProxyOs2UrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ PVUSBURB pUrb = NULL;
+ PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv;
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+ for (;;)
+ {
+ /*
+ * Any URBs pending delivery?
+ */
+ PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTaxingHead;
+ if (pUrbOs2)
+ {
+ pUrb = pUrbOs2->pUrb;
+ usbProxyOs2UrbFree(pProxyDev, pUrbOs2);
+ break;
+ }
+
+ /*
+ * Block for something to completed, if requested and sensible.
+ */
+ if (!cMillies)
+ break;
+ if ( !pDevOs2->pInFlightHead
+ && !pDevOs2->pTodoHead)
+ break;
+
+ RTCritSectLeave(&pDevOs2->CritSect);
+
+ int rc = RTSemEventWait(pDevOs2->EventSyncWait, cMillies);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc);
+ cMillies = 0;
+
+ RTCritSectEnter(&pDevOs2->CritSect);
+ }
+ RTCritSectLeave(&pDevOs2->CritSect);
+
+ LogFlow(("usbProxyOs2UrbReap: dev=%s returns %p\n", pProxyDev->pUsbIns->pszName, pUrb));
+ return pUrb;
+}
+
+
+/**
+ * Cancels the URB.
+ * The URB requires reaping, so we don't change its state.
+ */
+static void usbProxyOs2UrbCancel(PVUSBURB pUrb)
+{
+#if 0
+ PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pUrb->pDev;
+ PUSBPROXYURBOS2 pUrbOs2 = (PUSBPROXYURBOS2)pUrb->Dev.pvProxyUrb;
+ if (pUrbOs2->pSplitHead)
+ {
+ /* split */
+ Assert(pUrbOs2 == pUrbOs2->pSplitHead);
+ for (PUSBPROXYURBOS2 pCur = pUrbOs2; pCur; pCur = pCur->pSplitNext)
+ {
+ if (pCur->fSplitElementReaped)
+ continue;
+ if ( !usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX)
+ || errno == ENOENT)
+ continue;
+ if (errno == ENODEV)
+ break;
+ Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n",
+ pUrb, errno, pProxyDev->pUsbIns->pszName));
+ }
+ }
+ else
+ {
+ /* unsplit */
+ if ( usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbOs2->KUrb, true, UINT32_MAX)
+ && errno != ENODEV /* deal with elsewhere. */
+ && errno != ENOENT)
+ Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n",
+ pUrb, errno, pProxyDev->pUsbIns->pszName));
+ }
+#endif
+}
+
+
+/**
+ * The Linux USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ "host",
+ usbProxyOs2Open,
+ NULL,
+ usbProxyOs2Close,
+ usbProxyOs2Reset,
+ usbProxyOs2SetConfig,
+ usbProxyOs2ClaimInterface,
+ usbProxyOs2ReleaseInterface,
+ usbProxyOs2SetInterface,
+ usbProxyOs2ClearHaltedEp,
+ usbProxyOs2UrbQueue,
+ usbProxyOs2UrbCancel,
+ usbProxyOs2UrbReap,
+ 0
+};
+
diff --git a/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp
new file mode 100644
index 00000000..54e130d2
--- /dev/null
+++ b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp
@@ -0,0 +1,918 @@
+/* $Id: USBProxyDevice-solaris.cpp $ */
+/** @file
+ * USB device proxy - the Solaris backend.
+ */
+
+/*
+ * Copyright (C) 2009-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#include <sys/poll.h>
+#include <errno.h>
+#include <strings.h>
+#include <limits.h>
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdm.h>
+
+#include <iprt/string.h>
+#include <iprt/critsect.h>
+#include <iprt/time.h>
+#include <iprt/file.h>
+#include <iprt/mem.h>
+#include <iprt/pipe.h>
+#include "../USBProxyDevice.h"
+#include <VBox/usblib.h>
+
+
+/*********************************************************************************************************************************
+* Defined Constants And Macros *
+*********************************************************************************************************************************/
+/** Log Prefix. */
+#define USBPROXY "USBProxy"
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+/**
+ * Wrapper around the solaris urb request structure.
+ * This is required to track in-flight and landed URBs.
+ */
+typedef struct USBPROXYURBSOL
+{
+ /** Pointer to the Solaris device. */
+ struct USBPROXYDEVSOL *pDevSol;
+ /** Pointer to the VUSB URB (set to NULL if canceled). */
+ PVUSBURB pVUsbUrb;
+ /** Pointer to the next solaris URB. */
+ struct USBPROXYURBSOL *pNext;
+ /** Pointer to the previous solaris URB. */
+ struct USBPROXYURBSOL *pPrev;
+} USBPROXYURBSOL, *PUSBPROXYURBSOL;
+
+/**
+ * Data for the solaris usb proxy backend.
+ */
+typedef struct USBPROXYDEVSOL
+{
+ /** Path of the USB device in the devices tree (persistent). */
+ char *pszDevicePath;
+ /** The connection to the client driver. */
+ RTFILE hFile;
+ /** Pointer to the proxy device instance. */
+ PUSBPROXYDEV pProxyDev;
+ /** Critical section protecting the two lists. */
+ RTCRITSECT CritSect;
+ /** The list of free solaris URBs. Singly linked. */
+ PUSBPROXYURBSOL pFreeHead;
+ /** The list of active solaris URBs. Doubly linked.
+ * We must maintain this so we can properly reap URBs of a detached device.
+ * Only the split head will appear in this list. */
+ PUSBPROXYURBSOL pInFlightHead;
+ /** The list of landed solaris URBs. Doubly linked.
+ * Only the split head will appear in this list. */
+ PUSBPROXYURBSOL pTaxingHead;
+ /** The tail of the landed solaris URBs. */
+ PUSBPROXYURBSOL pTaxingTail;
+ /** Pipe handle for waking up - writing end. */
+ RTPIPE hPipeWakeupW;
+ /** Pipe handle for waking up - reading end. */
+ RTPIPE hPipeWakeupR;
+} USBPROXYDEVSOL, *PUSBPROXYDEVSOL;
+
+static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol);
+
+
+/**
+ * Allocates a Solaris URB request structure.
+ *
+ * @returns Pointer to an active URB request.
+ * @returns NULL on failure.
+ *
+ * @param pDevSol The solaris USB device.
+ */
+static PUSBPROXYURBSOL usbProxySolarisUrbAlloc(PUSBPROXYDEVSOL pDevSol)
+{
+ PUSBPROXYURBSOL pUrbSol;
+
+ RTCritSectEnter(&pDevSol->CritSect);
+
+ /*
+ * Try remove a Solaris URB from the free list, if none there allocate a new one.
+ */
+ pUrbSol = pDevSol->pFreeHead;
+ if (pUrbSol)
+ pDevSol->pFreeHead = pUrbSol->pNext;
+ else
+ {
+ RTCritSectLeave(&pDevSol->CritSect);
+ pUrbSol = (PUSBPROXYURBSOL)RTMemAlloc(sizeof(*pUrbSol));
+ if (!pUrbSol)
+ return NULL;
+ RTCritSectEnter(&pDevSol->CritSect);
+ }
+ pUrbSol->pVUsbUrb = NULL;
+ pUrbSol->pDevSol = pDevSol;
+
+ /*
+ * Link it into the active list
+ */
+ pUrbSol->pPrev = NULL;
+ pUrbSol->pNext = pDevSol->pInFlightHead;
+ if (pUrbSol->pNext)
+ pUrbSol->pNext->pPrev = pUrbSol;
+ pDevSol->pInFlightHead = pUrbSol;
+
+ RTCritSectLeave(&pDevSol->CritSect);
+ return pUrbSol;
+}
+
+
+/**
+ * Frees a Solaris URB request structure.
+ *
+ * @param pDevSol The Solaris USB device.
+ * @param pUrbSol The Solaris URB to free.
+ */
+static void usbProxySolarisUrbFree(PUSBPROXYDEVSOL pDevSol, PUSBPROXYURBSOL pUrbSol)
+{
+ RTCritSectEnter(&pDevSol->CritSect);
+
+ /*
+ * Remove from the active or taxing list.
+ */
+ if (pUrbSol->pNext)
+ pUrbSol->pNext->pPrev = pUrbSol->pPrev;
+ else if (pDevSol->pTaxingTail == pUrbSol)
+ pDevSol->pTaxingTail = pUrbSol->pPrev;
+
+ if (pUrbSol->pPrev)
+ pUrbSol->pPrev->pNext = pUrbSol->pNext;
+ else if (pDevSol->pTaxingHead == pUrbSol)
+ pDevSol->pTaxingHead = pUrbSol->pNext;
+ else if (pDevSol->pInFlightHead == pUrbSol)
+ pDevSol->pInFlightHead = pUrbSol->pNext;
+ else
+ AssertFailed();
+
+ /*
+ * Link it into the free list.
+ */
+ pUrbSol->pPrev = NULL;
+ pUrbSol->pNext = pDevSol->pFreeHead;
+ pDevSol->pFreeHead = pUrbSol;
+
+ pUrbSol->pVUsbUrb = NULL;
+ pUrbSol->pDevSol = NULL;
+
+ RTCritSectLeave(&pDevSol->CritSect);
+}
+
+
+/*
+ * Close the connection to the USB client driver.
+ *
+ * This is required because our userland enumeration relies on drivers/device trees
+ * to recognize active devices, and hence if this device is unplugged we should no
+ * longer keep the client driver loaded.
+ */
+static void usbProxySolarisCloseFile(PUSBPROXYDEVSOL pDevSol)
+{
+ RTFileClose(pDevSol->hFile);
+ pDevSol->hFile = NIL_RTFILE;
+}
+
+
+/**
+ * The client driver IOCtl Wrapper function.
+ *
+ * @returns VBox status code.
+ * @param pDevSol The Solaris device instance.
+ * @param Function The Function.
+ * @param pvData Opaque pointer to the data.
+ * @param cbData Size of the data pointed to by pvData.
+ */
+static int usbProxySolarisIOCtl(PUSBPROXYDEVSOL pDevSol, unsigned Function, void *pvData, size_t cbData)
+{
+ if (RT_UNLIKELY(pDevSol->hFile == NIL_RTFILE))
+ {
+ LogFlow((USBPROXY ":usbProxySolarisIOCtl: Connection to driver gone!\n"));
+ return VERR_VUSB_DEVICE_NOT_ATTACHED;
+ }
+
+ VBOXUSBREQ Req;
+ Req.u32Magic = VBOXUSB_MAGIC;
+ Req.rc = -1;
+ Req.cbData = cbData;
+ Req.pvDataR3 = pvData;
+
+ int Ret = -1;
+ int rc = RTFileIoCtl(pDevSol->hFile, Function, &Req, sizeof(Req), &Ret);
+ if (RT_SUCCESS(rc))
+ {
+ if (RT_FAILURE(Req.rc))
+ {
+ if (Req.rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ pDevSol->pProxyDev->fDetached = true;
+ usbProxySolarisCloseFile(pDevSol);
+ LogRel((USBPROXY ": Command %#x failed, USB Device '%s' disconnected!\n", Function,
+ pDevSol->pProxyDev->pUsbIns->pszName));
+ }
+ else
+ LogRel((USBPROXY ": Command %#x failed. Req.rc=%Rrc\n", Function, Req.rc));
+ }
+
+ return Req.rc;
+ }
+
+ LogRel((USBPROXY ": Function %#x failed. rc=%Rrc\n", Function, rc));
+ return rc;
+}
+
+
+/**
+ * Get the active configuration from the device. The first time this is called
+ * our client driver would returned the cached configuration since the device is first plugged in.
+ * Subsequent get configuration requests are passed on to the device.
+ *
+ * @returns VBox status code.
+ * @param pDevSol The Solaris device instance.
+ *
+ */
+static inline int usbProxySolarisGetActiveConfig(PUSBPROXYDEVSOL pDevSol)
+{
+ VBOXUSBREQ_GET_CONFIG GetConfigReq;
+ bzero(&GetConfigReq, sizeof(GetConfigReq));
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_CONFIG, &GetConfigReq, sizeof(GetConfigReq));
+ if (RT_SUCCESS(rc))
+ {
+ pDevSol->pProxyDev->iActiveCfg = GetConfigReq.bConfigValue;
+ pDevSol->pProxyDev->cIgnoreSetConfigs = 0;
+ }
+ else
+ {
+ if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": Failed to get configuration. rc=%Rrc\n", rc));
+
+ pDevSol->pProxyDev->iActiveCfg = -1;
+ pDevSol->pProxyDev->cIgnoreSetConfigs = 0;
+ }
+ return rc;
+}
+
+
+/**
+ * Opens the USB device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device instance.
+ * @param pszAddress The unique device identifier.
+ * The format of this string is "VendorId:ProducIt:Release:StaticPath".
+ */
+static DECLCALLBACK(int) usbProxySolarisOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ LogFlowFunc((USBPROXY ":usbProxySolarisOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ /*
+ * Initialize our USB R3 lib.
+ */
+ int rc = USBLibInit();
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Allocate and initialize the solaris backend data.
+ */
+ AssertCompile(PATH_MAX >= MAXPATHLEN);
+ char szDeviceIdent[PATH_MAX+48];
+ rc = RTStrPrintf(szDeviceIdent, sizeof(szDeviceIdent), "%s", pszAddress);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTCritSectInit(&pDevSol->CritSect);
+ if (RT_SUCCESS(rc))
+ {
+ /*
+ * Create wakeup pipe.
+ */
+ rc = RTPipeCreate(&pDevSol->hPipeWakeupR, &pDevSol->hPipeWakeupW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ int Instance;
+ char *pszDevicePath = NULL;
+ rc = USBLibGetClientInfo(szDeviceIdent, &pszDevicePath, &Instance);
+ if (RT_SUCCESS(rc))
+ {
+ pDevSol->pszDevicePath = pszDevicePath;
+
+ /*
+ * Open the client driver.
+ */
+ RTFILE hFile;
+ rc = RTFileOpen(&hFile, pDevSol->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
+ if (RT_SUCCESS(rc))
+ {
+ pDevSol->hFile = hFile;
+ pDevSol->pProxyDev = pProxyDev;
+
+ /*
+ * Verify client driver version.
+ */
+ VBOXUSBREQ_GET_VERSION GetVersionReq;
+ bzero(&GetVersionReq, sizeof(GetVersionReq));
+ rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_VERSION, &GetVersionReq, sizeof(GetVersionReq));
+ if (RT_SUCCESS(rc))
+ {
+ if ( GetVersionReq.u32Major == VBOXUSB_VERSION_MAJOR
+ && GetVersionReq.u32Minor >= VBOXUSB_VERSION_MINOR)
+ {
+ /*
+ * Try & get the current cached config from Solaris.
+ */
+ usbProxySolarisGetActiveConfig(pDevSol);
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ LogRel((USBPROXY ": Version mismatch, Driver v%d.%d expecting ~v%d.%d\n", GetVersionReq.u32Major,
+ GetVersionReq.u32Minor, VBOXUSB_VERSION_MAJOR, VBOXUSB_VERSION_MINOR));
+ rc = VERR_VERSION_MISMATCH;
+ }
+ }
+ else
+ LogRel((USBPROXY ": Failed to query driver version. rc=%Rrc\n", rc));
+
+ RTFileClose(pDevSol->hFile);
+ pDevSol->hFile = NIL_RTFILE;
+ pDevSol->pProxyDev = NULL;
+ }
+ else
+ LogRel((USBPROXY ": Failed to open device. rc=%Rrc pszDevicePath=%s\n", rc, pDevSol->pszDevicePath));
+
+ RTStrFree(pDevSol->pszDevicePath);
+ pDevSol->pszDevicePath = NULL;
+ }
+ else
+ {
+ LogRel((USBPROXY ": Failed to get client info. rc=%Rrc szDeviceIdent=%s\n", rc, szDeviceIdent));
+ if (rc == VERR_NOT_FOUND)
+ rc = VERR_OPEN_FAILED;
+ }
+ RTPipeClose(pDevSol->hPipeWakeupR);
+ RTPipeClose(pDevSol->hPipeWakeupW);
+ }
+
+ RTCritSectDelete(&pDevSol->CritSect);
+ }
+ else
+ LogRel((USBPROXY ": RTCritSectInit failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress));
+ }
+ else
+ LogRel((USBPROXY ": RTStrAPrintf failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress));
+ }
+ else
+ LogRel((USBPROXY ": USBLibInit failed. rc=%Rrc\n", rc));
+
+ USBLibTerm();
+ return rc;
+}
+
+
+/**
+ * Close the USB device.
+ *
+ * @param pProxyDev The device instance.
+ */
+static DECLCALLBACK(void) usbProxySolarisClose(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow((USBPROXY ":usbProxySolarisClose: pProxyDev=%p\n", pProxyDev));
+
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ /* Close the device (do not re-enumerate). */
+ VBOXUSBREQ_CLOSE_DEVICE CloseReq;
+ CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_CLOSE;
+ usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq));
+
+ pProxyDev->fDetached = true;
+ usbProxySolarisCloseFile(pDevSol);
+
+ /*
+ * Now we can close it and free all the resources.
+ */
+ RTCritSectDelete(&pDevSol->CritSect);
+
+ PUSBPROXYURBSOL pUrbSol = NULL;
+ while ((pUrbSol = pDevSol->pInFlightHead) != NULL)
+ {
+ pDevSol->pInFlightHead = pUrbSol->pNext;
+ RTMemFree(pUrbSol);
+ }
+
+ while ((pUrbSol = pDevSol->pFreeHead) != NULL)
+ {
+ pDevSol->pFreeHead = pUrbSol->pNext;
+ RTMemFree(pUrbSol);
+ }
+
+ RTPipeClose(pDevSol->hPipeWakeupR);
+ RTPipeClose(pDevSol->hPipeWakeupW);
+
+ RTStrFree(pDevSol->pszDevicePath);
+ pDevSol->pszDevicePath = NULL;
+
+ USBLibTerm();
+}
+
+
+/**
+ * Reset the device.
+ *
+ * @returns VBox status code.
+ * @param pProxyDev The device to reset.
+ * @param fRootHubReset Is this a root hub reset or device specific reset request.
+ */
+static DECLCALLBACK(int) usbProxySolarisReset(PUSBPROXYDEV pProxyDev, bool fRootHubReset)
+{
+ LogFlowFunc((USBPROXY ": usbProxySolarisReset: pProxyDev=%s fRootHubReset=%d\n", pProxyDev->pUsbIns->pszName, fRootHubReset));
+
+ /** Pass all resets to the device. The Trekstor USB (1.1) stick requires this to work. */
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ /* Soft reset the device. */
+ VBOXUSBREQ_CLOSE_DEVICE CloseReq;
+ CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_SOFT;
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq));
+ if (RT_SUCCESS(rc))
+ {
+ /* Get the active config. Solaris USBA sets a default config. */
+ usbProxySolarisGetActiveConfig(pDevSol);
+ }
+ else if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": usbProxySolarisReset: Failed! rc=%d\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * Set the active configuration.
+ *
+ * The caller makes sure that it's not called first time after open or reset
+ * with the active interface.
+ *
+ * @returns success indicator.
+ * @param pProxyDev The device instance data.
+ * @param iCfg The configuration value to set.
+ */
+static DECLCALLBACK(int) usbProxySolarisSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ LogFlowFunc((USBPROXY ": usbProxySolarisSetConfig: pProxyDev=%p iCfg=%#x\n", pProxyDev, iCfg));
+
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+ AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);
+
+ VBOXUSBREQ_SET_CONFIG SetConfigReq;
+ SetConfigReq.bConfigValue = iCfg;
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_CONFIG, &SetConfigReq, sizeof(SetConfigReq));
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": usbProxySolarisSetConfig: Failed! rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * Claims an interface.
+ *
+ * This is a stub on Solaris since we release/claim all interfaces at
+ * as and when required with endpoint opens.
+ *
+ * @returns success indicator (always true).
+ */
+static DECLCALLBACK(int) usbProxySolarisClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Releases an interface.
+ *
+ * This is a stub on Solaris since we release/claim all interfaces at
+ * as and when required with endpoint opens.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxySolarisReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * Specify an alternate setting for the specified interface of the current configuration.
+ *
+ * @returns success indicator.
+ */
+static DECLCALLBACK(int) usbProxySolarisSetInterface(PUSBPROXYDEV pProxyDev, int bIf, int bAlt)
+{
+ LogFlowFunc((USBPROXY ": usbProxySolarisSetInterface: pProxyDev=%p bIf=%#x iAlt=%#x\n", pProxyDev, bIf, bAlt));
+
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+ AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);
+
+ VBOXUSBREQ_SET_INTERFACE SetInterfaceReq;
+ SetInterfaceReq.bInterface = bIf;
+ SetInterfaceReq.bAlternate = bAlt;
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_INTERFACE, &SetInterfaceReq, sizeof(SetInterfaceReq));
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": usbProxySolarisSetInterface: Failed! rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * Clears the halted endpoint 'EndPt'.
+ */
+static DECLCALLBACK(int) usbProxySolarisClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt)
+{
+ LogFlowFunc((USBPROXY ": usbProxySolarisClearHaltedEp: pProxyDev=%p EndPt=%#x\n", pProxyDev, EndPt));
+
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+ AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);
+
+ VBOXUSBREQ_CLEAR_EP ClearEpReq;
+ ClearEpReq.bEndpoint = EndPt;
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLEAR_EP, &ClearEpReq, sizeof(ClearEpReq));
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": usbProxySolarisClearHaltedEp: Failed! rc=%Rrc\n", rc));
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxySolarisUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ LogFlowFunc((USBPROXY ": usbProxySolarisUrbQueue: pProxyDev=%s pUrb=%p pszDesc=%s EndPt=%#x enmDir=%d cbData=%d pvData=%p\n",
+ pProxyDev->pUsbIns->pszName, pUrb, pUrb->pszDesc, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, pUrb->abData));
+
+ PUSBPROXYURBSOL pUrbSol = usbProxySolarisUrbAlloc(pDevSol);
+ if (RT_UNLIKELY(!pUrbSol))
+ {
+ LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed to allocate URB\n"));
+ return VERR_NO_MEMORY;
+ }
+
+ pUrbSol->pVUsbUrb = pUrb;
+ pUrbSol->pDevSol = pDevSol;
+
+ uint8_t EndPt = pUrb->EndPt;
+ if (EndPt)
+ EndPt |= pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE;
+
+ VBOXUSBREQ_URB UrbReq;
+ UrbReq.pvUrbR3 = pUrbSol;
+ UrbReq.bEndpoint = EndPt;
+ UrbReq.enmType = pUrb->enmType;
+ UrbReq.enmDir = pUrb->enmDir;
+ UrbReq.enmStatus = pUrb->enmStatus;
+ UrbReq.fShortOk = !pUrb->fShortNotOk;
+ UrbReq.cbData = pUrb->cbData;
+ UrbReq.pvData = &pUrb->abData[0];
+
+ Log6((USBPROXY ": Sending: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData));
+
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ UrbReq.cIsocPkts = pUrb->cIsocPkts;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ UrbReq.aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cb;
+ UrbReq.aIsocPkts[i].cbActPkt = 0;
+ UrbReq.aIsocPkts[i].enmStatus = VUSBSTATUS_INVALID;
+ }
+ }
+
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SEND_URB, &UrbReq, sizeof(UrbReq));
+ if (RT_SUCCESS(rc))
+ {
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ LogFlow((USBPROXY ":usbProxySolarisUrbQueue: Success cbData=%d\n", pUrb->cbData));
+ pUrb->Dev.pvPrivate = pUrbSol;
+ return VINF_SUCCESS;
+ }
+
+ if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed! pProxyDev=%s pUrb=%p EndPt=%#x bEndpoint=%#x enmType=%d "
+ "enmDir=%d cbData=%u rc=%Rrc\n", pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt,
+ UrbReq.bEndpoint, pUrb->enmType, pUrb->enmDir, pUrb->cbData, rc));
+ }
+
+ return rc;
+}
+
+
+/**
+ * Cancels a URB.
+ *
+ * The URB requires reaping, so we don't change its state.
+ * @remark There isn't any way to cancel a specific asynchronous request
+ * on Solaris. So we just abort pending URBs on the pipe.
+ */
+static DECLCALLBACK(int) usbProxySolarisUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)pUrb->Dev.pvPrivate;
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+ AssertPtrReturn(pDevSol, VERR_INVALID_POINTER);
+
+ LogFlowFunc((USBPROXY ": usbProxySolarisUrbCancel: pUrb=%p pUrbSol=%p pDevSol=%p\n", pUrb, pUrbSol, pUrbSol->pDevSol));
+
+ /* Aborting the control pipe isn't supported, pretend success. */
+ if (!pUrb->EndPt)
+ return VINF_SUCCESS;
+
+ VBOXUSBREQ_ABORT_PIPE AbortPipeReq;
+ AbortPipeReq.bEndpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE);
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_ABORT_PIPE, &AbortPipeReq, sizeof(AbortPipeReq));
+ if ( RT_FAILURE(rc)
+ && rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": usbProxySolarisUrbCancel: Failed to abort pipe. rc=%Rrc\n", rc));
+
+ LogFlow((USBPROXY ": usbProxySolarisUrbCancel: returns rc=%Rrc\n", rc));
+ return rc;
+}
+
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxySolarisUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ LogFlowFunc((USBPROXY ":usbProxySolarisUrbReap pProxyDev=%p cMillies=%u\n", pProxyDev, cMillies));
+
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+
+ /*
+ * Don't block if nothing is in the air.
+ */
+ if (!pDevSol->pInFlightHead)
+ return NULL;
+
+ /*
+ * Deque URBs inflight or those landed.
+ */
+ if (cMillies > 0)
+ {
+ for (;;)
+ {
+ int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : (int)cMillies;
+
+ struct pollfd aFd[2];
+ size_t const cFds = RT_ELEMENTS(aFd);
+
+ aFd[0].fd = RTFileToNative(pDevSol->hFile);
+ aFd[0].events = POLLIN;
+ aFd[0].revents = 0;
+
+ aFd[1].fd = RTPipeToNative(pDevSol->hPipeWakeupR);
+ aFd[1].events = POLLIN;
+ aFd[1].revents = 0;
+
+ int rc = poll(&aFd[0], cFds, cMilliesWait);
+ if (rc > 0)
+ {
+ if (aFd[0].revents & POLLHUP)
+ {
+ LogRel((USBPROXY ": USB Device '%s' disconnected!\n", pDevSol->pProxyDev->pUsbIns->pszName));
+ pProxyDev->fDetached = true;
+ usbProxySolarisCloseFile(pDevSol);
+ }
+
+ if (aFd[1].revents & POLLIN)
+ {
+ /* Got woken up, drain pipe. */
+ uint8_t bRead;
+ size_t cbIgnored = 0;
+ RTPipeRead(pDevSol->hPipeWakeupR, &bRead, 1, &cbIgnored);
+
+ /*
+ * It is possible that we got woken up and have an URB pending
+ * for completion. Do it on the way out. Otherwise return
+ * immediately to the caller.
+ */
+ if (!(aFd[0].revents & POLLIN))
+ return NULL;
+ }
+ break;
+ }
+ else if (rc == 0)
+ return NULL;
+ else if (errno != EAGAIN)
+ {
+ LogFlow((USBPROXY ":usbProxySolarisUrbReap Poll rc=%d errno=%d\n", rc, errno));
+ return NULL;
+ }
+ }
+ }
+
+ usbProxySolarisUrbComplete(pDevSol);
+
+ /*
+ * Any URBs pending delivery?
+ */
+ PVUSBURB pUrb = NULL;
+ while ( pDevSol->pTaxingHead
+ && !pUrb)
+ {
+ RTCritSectEnter(&pDevSol->CritSect);
+
+ PUSBPROXYURBSOL pUrbSol = pDevSol->pTaxingHead;
+ if (pUrbSol)
+ {
+ pUrb = pUrbSol->pVUsbUrb;
+ if (pUrb)
+ {
+ /*
+ * Remove it from the taxing list and move it to the free list.
+ */
+ pUrb->Dev.pvPrivate = NULL;
+ usbProxySolarisUrbFree(pDevSol, pUrbSol);
+ }
+ }
+ RTCritSectLeave(&pDevSol->CritSect);
+ }
+
+ return pUrb;
+}
+
+
+/**
+ * Reads a completed/error'd URB from the client driver (no waiting).
+ *
+ * @param pDevSol The Solaris device instance.
+ */
+static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol)
+{
+ LogFlowFunc((USBPROXY ": usbProxySolarisUrbComplete: pDevSol=%p\n", pDevSol));
+
+ VBOXUSBREQ_URB UrbReq;
+ bzero(&UrbReq, sizeof(UrbReq));
+
+ int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_REAP_URB, &UrbReq, sizeof(UrbReq));
+ if (RT_SUCCESS(rc))
+ {
+ if (UrbReq.pvUrbR3)
+ {
+ PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)UrbReq.pvUrbR3;
+ PVUSBURB pUrb = pUrbSol->pVUsbUrb;
+ if (RT_LIKELY(pUrb))
+ {
+ Assert(pUrb->u32Magic == VUSBURB_MAGIC);
+
+ /*
+ * Update the URB.
+ */
+ if ( pUrb->enmType == VUSBXFERTYPE_ISOC
+ && pUrb->enmDir == VUSBDIRECTION_IN)
+ {
+ size_t cbData = 0;
+ for (unsigned i = 0; i < UrbReq.cIsocPkts; i++)
+ {
+ pUrb->aIsocPkts[i].cb = UrbReq.aIsocPkts[i].cbActPkt;
+ cbData += UrbReq.aIsocPkts[i].cbActPkt;
+ pUrb->aIsocPkts[i].enmStatus = UrbReq.aIsocPkts[i].enmStatus;
+ }
+
+ LogFlow((USBPROXY ":usbProxySolarisUrbComplete: Isoc cbData=%d cbActPktSum=%d\n", pUrb->cbData, cbData));
+ pUrb->cbData = cbData;
+ pUrb->enmStatus = UrbReq.enmStatus;
+ }
+ else
+ {
+ pUrb->cbData = UrbReq.cbData;
+ pUrb->enmStatus = UrbReq.enmStatus;
+ }
+
+ RTCritSectEnter(&pDevSol->CritSect);
+
+ /*
+ * Remove from the active list.
+ */
+ if (pUrbSol->pNext)
+ pUrbSol->pNext->pPrev = pUrbSol->pPrev;
+ if (pUrbSol->pPrev)
+ pUrbSol->pPrev->pNext = pUrbSol->pNext;
+ else
+ {
+ Assert(pDevSol->pInFlightHead == pUrbSol);
+ pDevSol->pInFlightHead = pUrbSol->pNext;
+ }
+
+ /*
+ * Add to the tail of the taxing list.
+ */
+ pUrbSol->pNext = NULL;
+ pUrbSol->pPrev = pDevSol->pTaxingTail;
+ if (pDevSol->pTaxingTail)
+ pDevSol->pTaxingTail->pNext = pUrbSol;
+ else
+ pDevSol->pTaxingHead = pUrbSol;
+ pDevSol->pTaxingTail = pUrbSol;
+
+ RTCritSectLeave(&pDevSol->CritSect);
+
+ Log6((USBPROXY ": Reaping: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData));
+ if (pUrb->cbData < 1024)
+ Log6(("%.*Rhxd\n", pUrb->cbData, pUrb->abData));
+ return pUrb;
+ }
+ }
+ }
+ else
+ {
+ if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED)
+ LogRel((USBPROXY ": Reaping URB failed. rc=%Rrc\n", rc));
+ }
+
+ return NULL;
+}
+
+
+static DECLCALLBACK(int) usbProxySolarisWakeup(PUSBPROXYDEV pProxyDev)
+{
+ PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL);
+ size_t cbIgnored;
+
+ LogFlowFunc(("pProxyDev=%p\n", pProxyDev));
+
+ return RTPipeWrite(pDevSol->hPipeWakeupW, "", 1, &cbIgnored);
+}
+
+
+/**
+ * The Solaris USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ /* pszName */
+ "host",
+ /* cbBackend */
+ sizeof(USBPROXYDEVSOL),
+ usbProxySolarisOpen,
+ NULL,
+ usbProxySolarisClose,
+ usbProxySolarisReset,
+ usbProxySolarisSetConfig,
+ usbProxySolarisClaimInterface,
+ usbProxySolarisReleaseInterface,
+ usbProxySolarisSetInterface,
+ usbProxySolarisClearHaltedEp,
+ usbProxySolarisUrbQueue,
+ usbProxySolarisUrbCancel,
+ usbProxySolarisUrbReap,
+ usbProxySolarisWakeup,
+ 0
+};
+
diff --git a/src/VBox/Devices/USB/testcase/Makefile.kup b/src/VBox/Devices/USB/testcase/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/Makefile.kup
diff --git a/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp
new file mode 100644
index 00000000..060f58fa
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp
@@ -0,0 +1,604 @@
+/* $Id: tstOhciRegisterAccess.cpp $ */
+/** @file
+ * tstOhciRegisterAccess - OHCI Register Access Tests / Experiments.
+ */
+
+/*
+ * Copyright (C) 2011-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#include <VBox/log.h>
+#include <iprt/mem.h>
+#include <iprt/memobj.h>
+#include <iprt/string.h>
+#include <iprt/asm-amd64-x86.h>
+#include <iprt/param.h>
+
+#include <VBox/sup.h>
+#undef LogRel
+#define LogRel(a) SUPR0Printf a
+
+
+/*********************************************************************************************************************************
+* Global Variables *
+*********************************************************************************************************************************/
+/** Register names. */
+static const char * const g_apszRegNms[] =
+{
+ /* 00 */ "HcRevision",
+ /* 01 */ "HcControl",
+ /* 02 */ "HcCommandStatus",
+ /* 03 */ "HcInterruptStatus",
+ /* 04 */ "HcInterruptEnable",
+ /* 05 */ "HcInterruptDisable",
+ /* 06 */ "HcHCCA",
+ /* 07 */ "HcPeriodCurrentED",
+ /* 08 */ "HcControlHeadED",
+ /* 09 */ "HcControlCurrentED",
+ /* 10 */ "HcBulkHeadED",
+ /* 11 */ "HcBulkCurrentED",
+ /* 12 */ "HcDoneHead",
+ /* 13 */ "HcFmInterval",
+ /* 14 */ "HcFmRemaining",
+ /* 15 */ "HcFmNumber",
+ /* 16 */ "HcPeriodicStart",
+ /* 17 */ "HcLSThreshold",
+ /* 18 */ "HcRhDescriptorA",
+ /* 19 */ "HcRhDescriptorB",
+ /* 20 */ "HcRhStatus",
+ /* Variable number of root hub ports: */
+ /* 21 */ "HcRhPortStatus[0]",
+ /* 22 */ "HcRhPortStatus[1]",
+ /* 23 */ "HcRhPortStatus[2]",
+ /* 24 */ "HcRhPortStatus[3]",
+ /* 25 */ "HcRhPortStatus[4]",
+ /* 26 */ "HcRhPortStatus[5]",
+ /* 27 */ "HcRhPortStatus[6]",
+ /* 28 */ "HcRhPortStatus[7]"
+};
+
+
+static bool TestOhciWrites(RTVPTRUNION uPtr)
+{
+ static struct
+ {
+ unsigned iReg;
+ uint32_t fMask;
+ uint32_t uVal1;
+ uint32_t uVal2;
+ } const s_aRegs[] =
+ {
+#if 0 /* deadly when missing bytes are taken as zero. */
+ { 13 /* HcFmInterval */, 0xffffffff, 0x58871120, 0x01010101 },
+#endif
+ { 16 /* HcPeriodicStart */, 0x00003fff, 0x01020304, 0x02010403 },
+ { 17 /* HcLSThreshold */, 0x00000fff, 0xffffffff, 0x66666666 },
+ { 10 /* HcBulkHeadED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */
+ { 11 /* HcBulkCurrentED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */
+ };
+
+ bool fSuccess = true;
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++)
+ {
+ uint32_t const iReg = s_aRegs[i].iReg;
+ RTVPTRUNION uPtrReg;
+ uPtrReg.pu32 = &uPtr.pu32[iReg];
+
+ uint32_t uInitialValue = *uPtrReg.pu32;
+ LogRel(("TestOhciWrites: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue));
+
+ bool fTryAgain = true;
+ const char *pszError = NULL;
+ uint32_t u32A = 0;
+ uint32_t uChangedValue = 0;
+ uint32_t uExpectedValue = 0;
+
+ for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++)
+ {
+ pszError = NULL;
+ fTryAgain = false;
+ u32A = 0;
+ uChangedValue = 0;
+ uExpectedValue = 0;
+
+ RTCCUINTREG const fFlags = ASMIntDisableFlags();
+ uInitialValue = *uPtrReg.pu32;
+
+ /*
+ * DWORD writes.
+ */
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+ *uPtrReg.pu32 = uInitialValue;
+ u32A = *uPtrReg.pu32;
+ uChangedValue = s_aRegs[i].uVal1 != uInitialValue ? s_aRegs[i].uVal1 : s_aRegs[i].uVal2;
+ if (u32A == uInitialValue)
+ {
+ /* Change the value. */
+ *uPtrReg.pu32 = uChangedValue;
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ uExpectedValue = uChangedValue & s_aRegs[i].fMask;
+ if (u32A != uExpectedValue)
+ pszError = "Writing changed value failed";
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 1";
+ }
+ }
+ else
+ pszError = "Writing back initial value failed";
+
+ /*
+ * Write aligned word changes.
+ */
+ for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++)
+ {
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+
+ /* Change the value. */
+ uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16);
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ uExpectedValue = (uChangedValue & UINT32_C(0xffff) << iWord * 16) & s_aRegs[i].fMask;
+ if (u32A != uExpectedValue)
+ {
+ static const char * const s_apsz[] = { "word 0", "word 1" };
+ pszError = s_apsz[iWord];
+ }
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 2";
+ }
+ }
+
+ /*
+ * Write aligned word change. We have to keep within the register,
+ * unfortunately.
+ */
+ if (!pszError && !fTryAgain)
+ {
+ fTryAgain = *uPtrReg.pu32 != uInitialValue;
+ if (!fTryAgain)
+ {
+ /* Change the value. */
+ *(uint16_t volatile *)&uPtrReg.pu8[1] = (uint16_t)(uChangedValue >> 8);
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ uExpectedValue = (uChangedValue & UINT32_C(0x00ffff00)) & s_aRegs[i].fMask;
+ if (u32A != uExpectedValue)
+ pszError = "Unaligned word access";
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 3";
+ }
+ }
+ }
+
+ /*
+ * Write byte changes.
+ */
+ for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++)
+ {
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+
+ /* Change the value. */
+ uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8);
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ uExpectedValue = (uChangedValue & UINT32_C(0xff) << iByte * 8) & s_aRegs[i].fMask;
+ if (u32A != uExpectedValue)
+ {
+ static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" };
+ pszError = s_apsz[iByte];
+ }
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 4";
+ }
+ }
+
+ ASMSetFlags(fFlags);
+ ASMNopPause();
+ }
+
+ /*
+ * Complain on failure.
+ */
+ if (fTryAgain)
+ LogRel(("TestOhciWrites: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n",
+ g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue));
+ else if (pszError)
+ {
+ LogRel(("TestOhciWrites: Error! Register %s failed: %s; Initial=%08RX32 Changed=%08RX32 Expected=%08RX32 u32A=%08RX32\n",
+ g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, uExpectedValue, u32A));
+ fSuccess = false;
+ }
+ }
+
+ return fSuccess;
+}
+
+
+static bool TestOhciReadOnly(RTVPTRUNION uPtr)
+{
+ static struct
+ {
+ unsigned iReg;
+ uint32_t cValues;
+ uint32_t auValues[8];
+ } const s_aRegs[] =
+ {
+ { 0 /* HcRevision */, 8, { 0, UINT32_MAX, 0x10100110, 0x200, 0x111, 0x11f, 0xf110, 0x0f10 } },
+ { 12 /* HcDoneHead */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } },
+ { 14 /* HcFmRemaining */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } },
+ { 15 /* HcFmNumber */, 5, { 0, UINT32_MAX, 0x55555555, 0x7899, 0x00012222, 0, 0, 0 } },
+#if 0 /* HCD can write this */
+ { 17 /* HcLSThreshold */, 5, { 0x627, 0x628, 0x629, 0x666, 0x599, 0, 0, 0 } } /* ??? */
+#endif
+ };
+
+ bool fSuccess = true;
+ for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++)
+ {
+ uint32_t const iReg = s_aRegs[i].iReg;
+ RTVPTRUNION uPtrReg;
+ uPtrReg.pu32 = &uPtr.pu32[iReg];
+
+ uint32_t uInitialValue = *uPtrReg.pu32;
+ LogRel(("TestOhciReadOnly: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue));
+
+ bool fTryAgain = true;
+ const char *pszError = NULL;
+ uint32_t uChangedValue = 0;
+ uint32_t u32A = 0;
+
+ for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++)
+ {
+ pszError = NULL;
+ fTryAgain = false;
+ u32A = 0;
+ uChangedValue = 0;
+
+ RTCCUINTREG const fFlags = ASMIntDisableFlags();
+ uInitialValue = *uPtrReg.pu32;
+
+ /*
+ * Try aligned dword, word and byte writes for now.
+ */
+ for (unsigned iValue = 0; iValue < s_aRegs[i].cValues && !pszError && !fTryAgain; iValue++)
+ {
+ uChangedValue = s_aRegs[i].auValues[iValue];
+ if (uInitialValue == uChangedValue)
+ continue;
+
+ /* dword */
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+
+ *uPtrReg.pu32 = uChangedValue;
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ if (u32A != uInitialValue)
+ pszError = "dword access";
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 1";
+ }
+
+ /* word */
+ for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++)
+ {
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+ uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16);
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ if (u32A != uInitialValue)
+ pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access";
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 2";
+ }
+ }
+
+ /* byte */
+ for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++)
+ {
+ if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue)))
+ break;
+ uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8);
+ u32A = *uPtrReg.pu32;
+ *uPtrReg.pu32 = uInitialValue;
+ if (u32A != uInitialValue)
+ {
+ static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" };
+ pszError = s_apsz[iByte];
+ }
+ else
+ {
+ u32A = *uPtrReg.pu32;
+ if (u32A != uInitialValue)
+ pszError = "Restore error 3";
+ }
+ }
+ }
+
+ ASMSetFlags(fFlags);
+ ASMNopPause();
+ }
+
+ /*
+ * Complain on failure.
+ */
+ if (fTryAgain)
+ LogRel(("TestOhciReadOnly: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n",
+ g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue));
+ else if (pszError)
+ {
+ LogRel(("TestOhciReadOnly: Error! Register %s failed: %s; uInitialValue=%08RX32 uChangedValue=%08RX32 u32A=%08RX32\n",
+ g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, u32A));
+ fSuccess = false;
+ }
+ }
+
+ return fSuccess;
+}
+
+
+static bool TestOhciReads(RTVPTRUNION uPtr)
+{
+ /*
+ * We can read just about any register we like since read shouldn't have
+ * any side effects. However, some registers are volatile and makes for
+ * difficult targets, thus the ugly code.
+ */
+ bool fSuccess = true;
+ uint32_t cMaxReg = RT_ELEMENTS(g_apszRegNms);
+ for (uint32_t iReg = 0; iReg < cMaxReg; iReg++, uPtr.pu32++)
+ {
+ const char *pszError = NULL;
+ bool fDone = false;
+ uint32_t uInitialValue = *uPtr.pu32;
+ uint32_t u32A = 0;
+ uint32_t u32B = 0;
+ uint32_t u32C = 0;
+ LogRel(("TestOhciReads: %p iReg=%2d %20s = %08RX32\n", uPtr.pv, iReg, g_apszRegNms[iReg], uInitialValue));
+
+ for (uint32_t iTry = 0; !fDone && iTry < 1024; iTry++)
+ {
+ pszError = NULL;
+ fDone = true;
+ u32A = u32B = u32C = 0;
+
+ RTCCUINTREG const fFlags = ASMIntDisableFlags();
+ uInitialValue = *uPtr.pu32;
+
+ /* Test byte access. */
+ for (unsigned iByte = 0; iByte < 4; iByte++)
+ {
+ u32A = *uPtr.pu32;
+ u32B = uPtr.pu8[iByte];
+ u32C = *uPtr.pu32;
+ if (u32A != uInitialValue || u32C != uInitialValue)
+ {
+ fDone = false;
+ break;
+ }
+
+ static uint32_t const a_au32Masks[] =
+ {
+ UINT32_C(0xffffff00), UINT32_C(0xffff00ff), UINT32_C(0xff00ffff), UINT32_C(0x00ffffff)
+ };
+ u32B <<= iByte * 8;
+ u32B |= uInitialValue & a_au32Masks[iByte];
+ if (u32B != uInitialValue)
+ {
+ static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" };
+ pszError = s_apsz[iByte];
+ break;
+ }
+ }
+
+ /* Test aligned word access. */
+ if (fDone)
+ {
+ for (unsigned iWord = 0; iWord < 2; iWord++)
+ {
+ u32A = *uPtr.pu32;
+ u32B = uPtr.pu16[iWord];
+ u32C = *uPtr.pu32;
+ if (u32A != uInitialValue || u32C != uInitialValue)
+ {
+ fDone = false;
+ break;
+ }
+
+ u32B <<= iWord * 16;
+ u32B |= uInitialValue & (iWord == 0 ? UINT32_C(0xffff0000) : UINT32_C(0x0000ffff));
+ if (u32B != uInitialValue)
+ {
+ pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access";
+ break;
+ }
+ }
+ }
+
+ /* Test unaligned word access. */
+ if (fDone)
+ {
+ for (int iWord = (uPtr.u & HOST_PAGE_OFFSET_MASK) == 0; iWord < 3; iWord++)
+ {
+ u32A = *uPtr.pu32;
+ u32B = *(volatile uint16_t *)&uPtr.pu8[iWord * 2 - 1];
+ u32C = *uPtr.pu32;
+ if (u32A != uInitialValue || u32C != uInitialValue)
+ {
+ fDone = false;
+ break;
+ }
+
+ switch (iWord)
+ {
+ case 0: u32B = (u32B >> 8) | (u32A & UINT32_C(0xffffff00)); break;
+ case 1: u32B = (u32B << 8) | (u32A & UINT32_C(0xff0000ff)); break;
+ case 2: u32B = (u32B << 24) | (u32A & UINT32_C(0x00ffffff)); break;
+ }
+ if (u32B != u32A)
+ {
+ static const char * const s_apsz[] = { "unaligned word 0", "unaligned word 1", "unaligned word 2" };
+ pszError = s_apsz[iWord];
+ break;
+ }
+ }
+ }
+
+ /* Test unaligned dword access. */
+ if (fDone)
+ {
+ for (int iByte = (uPtr.u & HOST_PAGE_OFFSET_MASK) == 0 ? 0 : -3; iByte < 4; iByte++)
+ {
+ u32A = *uPtr.pu32;
+ u32B = *(volatile uint32_t *)&uPtr.pu8[iByte];
+ u32C = *uPtr.pu32;
+ if (u32A != uInitialValue || u32C != uInitialValue)
+ {
+ fDone = false;
+ break;
+ }
+
+ switch (iByte)
+ {
+ case -3: u32B = (u32B >> 24) | (uInitialValue & UINT32_C(0xffffff00)); break;
+ case -2: u32B = (u32B >> 16) | (uInitialValue & UINT32_C(0xffff0000)); break;
+ case -1: u32B = (u32B >> 8) | (uInitialValue & UINT32_C(0xff000000)); break;
+ case 0: break;
+ case 1: u32B = (u32B << 8) | (uInitialValue & UINT32_C(0x000000ff)); break;
+ case 2: u32B = (u32B << 16) | (uInitialValue & UINT32_C(0x0000ffff)); break;
+ case 3: u32B = (u32B << 24) | (uInitialValue & UINT32_C(0x00ffffff)); break;
+
+ }
+ if (u32B != u32A)
+ {
+ static const char * const s_apsz[] =
+ {
+ "unaligned dword -3", "unaligned dword -2", "unaligned dword -1",
+ "unaligned dword 0", "unaligned dword 1", "unaligned dword 2", "unaligned dword 3"
+ };
+ pszError = s_apsz[iByte + 3];
+ break;
+ }
+
+ }
+ }
+
+ ASMSetFlags(fFlags);
+ ASMNopPause();
+ } /* try loop */
+
+ /*
+ * Complain on failure.
+ */
+ if (!fDone)
+ LogRel(("TestOhciReads: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n",
+ g_apszRegNms[iReg], uInitialValue, u32A, u32C));
+ else if (pszError)
+ {
+ LogRel(("TestOhciReads: Error! Register %s failed: %s; uInitialValue=%08RX32 u32B=%08RX32\n",
+ g_apszRegNms[iReg], pszError, uInitialValue, u32B));
+ fSuccess = false;
+ }
+ }
+
+ return fSuccess;
+}
+
+
+int tstOhciRegisterAccess(RTHCPHYS HCPhysOHCI)
+{
+ LogRel(("tstOhciRegisterAccess: HCPhysOHCI=%RHp\n", HCPhysOHCI));
+
+ /*
+ * Map the OHCI registers so we can access them.
+ */
+ RTR0MEMOBJ hMemObj;
+ int rc = RTR0MemObjEnterPhys(&hMemObj, HCPhysOHCI, HOST_PAGE_SIZE, RTMEM_CACHE_POLICY_MMIO);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("tstOhciRegisterAccess: Failed to enter OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc));
+ return rc;
+ }
+ RTR0MEMOBJ hMapObj;
+ rc = RTR0MemObjMapKernel(&hMapObj, hMemObj, (void *)-1, 0 /*uAlignment*/, RTMEM_PROT_READ | RTMEM_PROT_WRITE);
+ if (RT_SUCCESS(rc))
+ {
+ RTVPTRUNION uPtr;
+ uPtr.pv = (void volatile *)RTR0MemObjAddress(hMapObj);
+ LogRel(("tstOhciRegisterAccess: mapping address %p\n", uPtr.pv));
+ if (RT_VALID_PTR(uPtr.pv))
+ {
+ LogRel(("tstOhciRegisterAccess: HcRevision=%#x\n", *uPtr.pu32));
+
+ /*
+ * Do the access tests.
+ */
+ bool fSuccess = TestOhciReads(uPtr);
+ if (fSuccess)
+ fSuccess = TestOhciReadOnly(uPtr);
+ if (fSuccess)
+ fSuccess = TestOhciWrites(uPtr);
+ if (fSuccess)
+ LogRel(("tstOhciRegisterAccess: Success!\n"));
+ else
+ LogRel(("tstOhciRegisterAccess: Failed!\n"));
+ }
+ else
+ rc = VERR_INTERNAL_ERROR_2;
+
+ /*
+ * Clean up.
+ */
+ RTR0MemObjFree(hMapObj, false);
+ }
+ else
+ LogRel(("tstOhciRegisterAccess: Failed to map OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc));
+ RTR0MemObjFree(hMemObj, false);
+ LogRel(("tstOhciRegisterAccess: returns %Rrc\n", rc));
+ return rc;
+}
diff --git a/src/VBox/Devices/USB/testcase/tstPalmOne.c b/src/VBox/Devices/USB/testcase/tstPalmOne.c
new file mode 100644
index 00000000..c5d224c7
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstPalmOne.c
@@ -0,0 +1,412 @@
+/* $Id: tstPalmOne.c $ */
+/** @file
+ * USB PalmOne testcase
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <linux/usbdevice_fs.h>
+
+
+/** @name USB Control message recipient codes (from spec)
+ * @{ */
+#define VUSB_TO_DEVICE 0x0
+#define VUSB_TO_INTERFACE 0x1
+#define VUSB_TO_ENDPOINT 0x2
+#define VUSB_TO_OTHER 0x3
+#define VUSB_RECIP_MASK 0x1f
+/** @} */
+
+/** @name USB control pipe setup packet structure (from spec)
+ * @{ */
+#define VUSB_REQ_SHIFT (5)
+#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT)
+/** @} */
+
+#define VUSB_DIR_TO_HOST 0x80
+typedef struct vusb_setup
+{
+ uint8_t bmRequestType;
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+} VUSBSETUP, *PVUSBSETUP;
+typedef const VUSBSETUP *PCVUSBSETUP;
+
+
+int g_fd;
+
+int bitch(const char *pszMsg)
+{
+ printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno));
+ return 1;
+}
+
+void hex(const void *pv, ssize_t cb, const char *pszWhat)
+{
+ printf("%s: cb=%d\n", pszWhat, cb);
+ unsigned char *pb = (unsigned char *)pv;
+ int cch = 0;
+ int off = 0;
+ int cchPrecision = 16;
+ while (off < cb)
+ {
+ int i;
+ printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off);
+
+ for (i = 0; i < cchPrecision && off + i < cb; i++)
+ printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]);
+ while (i++ < cchPrecision)
+ printf(" ");
+ printf(" ");
+ for (i = 0; i < cchPrecision && off + i < cb; i++)
+ {
+ uint8_t u8 = pb[i];
+ fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout);
+ }
+
+ /* next */
+ pb += cchPrecision;
+ off += cchPrecision;
+ }
+ printf("\n");
+}
+
+int doioctl(int iCmd, void *pvData, const char *pszWho)
+{
+ int rc;
+ do
+ {
+ errno = 0;
+ rc = ioctl(g_fd, iCmd, pvData);
+
+ } while (rc && errno == EAGAIN);
+ if (rc)
+ printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno));
+ else
+ printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd);
+ return rc;
+}
+
+int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho)
+{
+#if 0
+ struct usbdevfs_urb KUrb = {0};
+ KUrb.type = USBDEVFS_URB_TYPE_BULK;
+ KUrb.endpoint = EndPt;
+ KUrb.buffer = pvBuf;
+ KUrb.buffer_length = cbBuf;
+ KUrb.actual_length = 0; //cbBuf
+ KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */
+ if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho))
+ {
+ struct usbdevfs_urb *pKUrb = NULL;
+ if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho)
+ && pKUrb == &KUrb)
+ return KUrb.actual_length;
+ }
+ return -1;
+#else
+ struct usbdevfs_bulktransfer BulkMsg = {0};
+
+ BulkMsg.ep = EndPt;
+ BulkMsg.timeout = 1000;
+ BulkMsg.len = cbBuf;
+ BulkMsg.data = pvBuf;
+ int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho);
+// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf);
+ if (rc >= 0)
+ return rc;
+ return -1;
+#endif
+}
+
+int send_bulk(int EndPt, void *pvBuf, size_t cbBuf)
+{
+ return dobulk(EndPt, pvBuf, cbBuf, "send_bulk");
+}
+
+int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf)
+{
+ int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk");
+ if (cb > 0)
+ printf("cb=%d\n", cb);
+ return cb;
+}
+
+int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength,
+ void *pvBuf, const char *pszWho)
+{
+#if 0
+ struct usbdevfs_urb KUrb = {0};
+ KUrb.type = USBDEVFS_URB_TYPE_BULK;
+ KUrb.endpoint = EndPt;
+ KUrb.buffer = pvBuf;
+ KUrb.buffer_length = cbBuf;
+ KUrb.actual_length = 0; //cbBuf
+ KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */
+ if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho))
+ {
+ struct usbdevfs_urb *pKUrb = NULL;
+ if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho)
+ && pKUrb == &KUrb)
+ return KUrb.actual_length;
+ }
+ return -1;
+#else
+ struct usbdevfs_ctrltransfer CtrlMsg = {0};
+
+ CtrlMsg.bRequestType = bmRequestType;
+ CtrlMsg.bRequest = bRequest;
+ CtrlMsg.wValue = wValue;
+ CtrlMsg.wLength = wLength;
+ CtrlMsg.timeout = 1000;
+ CtrlMsg.data = pvBuf;
+
+ int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho);
+ printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength);
+ if (rc >= 0)
+ return rc;
+ return -1;
+#endif
+}
+
+static int claim_if(int iIf)
+{
+ return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if");
+}
+
+static int usb_set_connected(int ifnum, int conn)
+{
+ struct usbdevfs_ioctl io;
+ io.ifno = ifnum;
+ io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT;
+ io.data = NULL;
+ return doioctl(USBDEVFS_IOCTL, &io, "set_connected");
+}
+
+static int set_config(int iCfg)
+{
+ return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config");
+}
+
+static int set_interface(int iIf, int iAlt)
+{
+ struct usbdevfs_setinterface SetIf = {0};
+ SetIf.interface = iIf;
+ SetIf.altsetting = iAlt;
+ return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface");
+}
+
+/* can be exploited to check if there is an active config. */
+static int reset_ep(int EndPt)
+{
+ return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep");
+}
+
+
+static void msd()
+{
+#if 1
+ unsigned InEndPt = 1;
+ unsigned OutEndPt = 1;
+#else
+ unsigned InEndPt = 1;
+ unsigned OutEndPt = 2;
+#endif
+ unsigned char abBuf[512];
+ int i;
+
+// set_config(1); - the culprit
+ set_interface(0, 0);
+
+#if 0
+ /* Send an Get Max LUN request */
+ abBuf[0] = 0;
+ if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE,
+ 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0)
+ printf("max luns: %d\n", abBuf[0]);
+#endif
+
+ for (i = 0; i < 3; i++)
+ {
+ printf("i=%d\n", i);
+
+ /* Send an INQUIRY command to ep 2 */
+ memset(abBuf, 0, sizeof(abBuf));
+ memcpy(abBuf, "USBC", 4);
+ *(uint32_t *)(&abBuf[4]) = 0x12330984 ;
+ //abBuf[8] = 0x08;
+ abBuf[8] = 0x24;
+ abBuf[0xc] = 0x80;
+ abBuf[0xe] = 0x06; /* cmd length */
+ abBuf[0x0f] = 0x12; /* cmd - INQUIRY */
+ abBuf[0x13] = 0x24;
+
+ hex(abBuf, 31, "intquery req");
+ if (send_bulk(OutEndPt, abBuf, 31) < 0)
+ return;
+ //usleep(15000);
+
+ /* read result */
+ memset(abBuf, 0, sizeof(abBuf));
+ //printf("recv..\n");
+ int cb = recv_bulk(InEndPt, abBuf, 36);
+ hex(abBuf, cb, "inquiry result");
+
+ /* sense? */
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(InEndPt, abBuf, 36);
+ hex(abBuf, cb, "inquiry sense?");
+ usleep(150000);
+ }
+}
+
+void palm(void)
+{
+// set_config(1); //skip this
+// reset_ep(6);
+
+ /* This seems to be some kind of 'identify device' request. */
+ uint8_t abVendor[0x14] = {0};
+ int cb = doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_VENDOR | VUSB_TO_ENDPOINT,
+ 0x04, 0, 0, 0x14, abVendor, "vendor req");
+ hex(abVendor, cb, "palm vendor req");
+
+ /* read from ep 6. */
+ uint8_t abBuf[512];
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(6, abBuf, 6);
+ hex(abBuf, cb, "bulk 1");
+
+ /* read from ep 6. */
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(6, abBuf, 22);
+ hex(abBuf, cb, "bulk 2");
+
+#if 0
+ /* write to ep 6 */
+ memset(abBuf, 0, sizeof(abBuf));
+ abBuf[0] = 1;
+ abBuf[1] = 2;
+ abBuf[5] = 0x32;
+ if (send_bulk(7, abBuf, 6) < 0)
+ return;
+
+ memset(abBuf, 0, sizeof(abBuf));
+ abBuf[0] = 0x12;
+ abBuf[1] = 1;
+ if (send_bulk(7, abBuf, 8) < 0)
+ return;
+
+ memset(abBuf, 0, sizeof(abBuf));
+ abBuf[1] = 0x20;
+ abBuf[6] = 0x24;
+ if (send_bulk(7, abBuf, 6) < 0)
+ return;
+
+ memset(abBuf, 0, sizeof(abBuf));
+ abBuf[0] = 0xff;
+ abBuf[1] = 0xff;
+ abBuf[2] = 0xff;
+ abBuf[3] = 0xff;
+ abBuf[4] = 0x3c;
+ abBuf[6] = 0x3c;
+ abBuf[0x0f] = 1;
+ abBuf[0x11] = 0x10;
+ abBuf[0x15] = 0x10;
+ abBuf[0x18] = 0x3c;
+ abBuf[0x1a] = 0x3c;
+ if (send_bulk(7, abBuf, 0x24) < 0)
+ return;
+
+ /* read from ep 6 */
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(6, abBuf, 64);
+ hex(abBuf, cb, "bulk 3");
+
+ /* read from ep 6. */
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(6, abBuf, 50);
+ hex(abBuf, cb, "bulk 4");
+#endif
+}
+
+int reset(void)
+{
+ int i = 0;
+ printf("resetting...\n");
+ return doioctl(USBDEVFS_RESET, &i, "reset");
+}
+
+int main(int argc, char **argv)
+{
+ g_fd = open(argv[1], O_RDWR);
+ if (errno == ENOENT && g_fd < 0)
+ {
+ int i;
+ for (i = 0; i < 120; i++)
+ {
+ g_fd = open(argv[1], O_RDWR);
+ if (g_fd >= 0)
+ break;
+ printf("."); fflush(stdout);
+ usleep(500000);
+ }
+ printf("\n");
+ }
+ if (g_fd < 0)
+ return bitch("open");
+
+// reset();
+// set_config(0);
+// set_config(1);
+
+ usb_set_connected(0, 1);
+ claim_if(0);
+
+#if 0
+ msd();
+#else
+ palm();
+#endif
+ return 0;
+}
diff --git a/src/VBox/Devices/USB/testcase/tstTrekStorGo.c b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c
new file mode 100644
index 00000000..93a79968
--- /dev/null
+++ b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c
@@ -0,0 +1,323 @@
+/* $Id: tstTrekStorGo.c $ */
+/** @file
+ * Some simple inquiry test for the TrekStor USB-Stick GO, linux usbfs
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/vfs.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <linux/usbdevice_fs.h>
+
+
+/** @name USB Control message recipient codes (from spec)
+ * @{ */
+#define VUSB_TO_DEVICE 0x0
+#define VUSB_TO_INTERFACE 0x1
+#define VUSB_TO_ENDPOINT 0x2
+#define VUSB_TO_OTHER 0x3
+#define VUSB_RECIP_MASK 0x1f
+/** @} */
+
+/** @name USB control pipe setup packet structure (from spec)
+ * @{ */
+#define VUSB_REQ_SHIFT (5)
+#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT)
+#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT)
+/** @} */
+
+#define VUSB_DIR_TO_HOST 0x80
+typedef struct vusb_setup
+{
+ uint8_t bmRequestType;
+ uint8_t bRequest;
+ uint16_t wValue;
+ uint16_t wIndex;
+ uint16_t wLength;
+} VUSBSETUP, *PVUSBSETUP;
+typedef const VUSBSETUP *PCVUSBSETUP;
+
+
+int g_fd;
+
+int bitch(const char *pszMsg)
+{
+ printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno));
+ return 1;
+}
+
+void hex(const void *pv, ssize_t cb, const char *pszWhat)
+{
+ printf("%s: cb=%d\n", pszWhat, cb);
+ unsigned char *pb = (unsigned char *)pv;
+ int cch = 0;
+ int off = 0;
+ int cchPrecision = 16;
+ while (off < cb)
+ {
+ int i;
+ printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off);
+
+ for (i = 0; i < cchPrecision && off + i < cb; i++)
+ printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]);
+ while (i++ < cchPrecision)
+ printf(" ");
+ printf(" ");
+ for (i = 0; i < cchPrecision && off + i < cb; i++)
+ {
+ uint8_t u8 = pb[i];
+ fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout);
+ }
+
+ /* next */
+ pb += cchPrecision;
+ off += cchPrecision;
+ }
+ printf("\n");
+}
+
+int doioctl(int iCmd, void *pvData, const char *pszWho)
+{
+ int rc;
+ do
+ {
+ errno = 0;
+ rc = ioctl(g_fd, iCmd, pvData);
+
+ } while (rc && errno == EAGAIN);
+ if (rc)
+ printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno));
+ else
+ printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd);
+ return rc;
+}
+
+int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho)
+{
+#if 0
+ struct usbdevfs_urb KUrb = {0};
+ KUrb.type = USBDEVFS_URB_TYPE_BULK;
+ KUrb.endpoint = EndPt;
+ KUrb.buffer = pvBuf;
+ KUrb.buffer_length = cbBuf;
+ KUrb.actual_length = 0; //cbBuf
+ KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */
+ if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho))
+ {
+ struct usbdevfs_urb *pKUrb = NULL;
+ if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho)
+ && pKUrb == &KUrb)
+ return KUrb.actual_length;
+ }
+ return -1;
+#else
+ struct usbdevfs_bulktransfer BulkMsg = {0};
+
+ BulkMsg.ep = EndPt;
+ BulkMsg.timeout = 1000;
+ BulkMsg.len = cbBuf;
+ BulkMsg.data = pvBuf;
+ int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho);
+// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf);
+ if (rc >= 0)
+ return rc;
+ return -1;
+#endif
+}
+
+int send_bulk(int EndPt, void *pvBuf, size_t cbBuf)
+{
+ return dobulk(EndPt, pvBuf, cbBuf, "send_bulk");
+}
+
+int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf)
+{
+ int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk");
+ if (cb > 0)
+ printf("cb=%d\n", cb);
+ return cb;
+}
+
+int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength,
+ void *pvBuf, const char *pszWho)
+{
+#if 0
+ struct usbdevfs_urb KUrb = {0};
+ KUrb.type = USBDEVFS_URB_TYPE_BULK;
+ KUrb.endpoint = EndPt;
+ KUrb.buffer = pvBuf;
+ KUrb.buffer_length = cbBuf;
+ KUrb.actual_length = 0; //cbBuf
+ KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */
+ if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho))
+ {
+ struct usbdevfs_urb *pKUrb = NULL;
+ if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho)
+ && pKUrb == &KUrb)
+ return KUrb.actual_length;
+ }
+ return -1;
+#else
+ struct usbdevfs_ctrltransfer CtrlMsg = {0};
+
+ CtrlMsg.bRequestType = bmRequestType;
+ CtrlMsg.bRequest = bRequest;
+ CtrlMsg.wValue = wValue;
+ CtrlMsg.wLength = wLength;
+ CtrlMsg.timeout = 1000;
+ CtrlMsg.data = pvBuf;
+
+ int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho);
+ printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength);
+ if (rc >= 0)
+ return rc;
+ return -1;
+#endif
+}
+
+static int claim_if(int iIf)
+{
+ return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if");
+}
+
+static int usb_set_connected(int ifnum, int conn)
+{
+ struct usbdevfs_ioctl io;
+ io.ifno = ifnum;
+ io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT;
+ io.data = NULL;
+ return doioctl(USBDEVFS_IOCTL, &io, "set_connected");
+}
+
+static int set_config(int iCfg)
+{
+ return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config");
+}
+
+static int set_interface(int iIf, int iAlt)
+{
+ struct usbdevfs_setinterface SetIf = {0};
+ SetIf.interface = iIf;
+ SetIf.altsetting = iAlt;
+ return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface");
+}
+
+/* can be exploited to check if there is an active config. */
+static int reset_ep(int EndPt)
+{
+ return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep");
+}
+
+
+static void msd()
+{
+#if 1
+ unsigned InEndPt = 1;
+ unsigned OutEndPt = 1;
+#else
+ unsigned InEndPt = 1;
+ unsigned OutEndPt = 2;
+#endif
+ unsigned char abBuf[512];
+ int i;
+
+#if 0
+ /* Send an Get Max LUN request */
+ abBuf[0] = 0;
+ if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE,
+ 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0)
+ printf("max luns: %d\n", abBuf[0]);
+#endif
+
+ for (i = 0; i < 3; i++)
+ {
+ printf("i=%d\n", i);
+
+ /* Send an INQUIRY command to ep 2 */
+ memset(abBuf, 0, sizeof(abBuf));
+ memcpy(abBuf, "USBC", 4);
+ *(uint32_t *)(&abBuf[4]) = 0x12330984 ;
+ //abBuf[8] = 0x08;
+ abBuf[8] = 0x24;
+ abBuf[0xc] = 0x80;
+ abBuf[0xe] = 0x06; /* cmd length */
+ abBuf[0x0f] = 0x12; /* cmd - INQUIRY */
+ abBuf[0x13] = 0x24;
+
+ hex(abBuf, 31, "intquery req");
+ if (send_bulk(OutEndPt, abBuf, 31) < 0)
+ return;
+ //usleep(15000);
+
+ /* read result */
+ memset(abBuf, 0, sizeof(abBuf));
+ //printf("recv..\n");
+ int cb = recv_bulk(InEndPt, abBuf, 36);
+ hex(abBuf, cb, "inquiry result");
+
+ /* sense? */
+ memset(abBuf, 0, sizeof(abBuf));
+ cb = recv_bulk(InEndPt, abBuf, 36);
+ hex(abBuf, cb, "inquiry sense?");
+ usleep(150000);
+ }
+}
+
+int reset(void)
+{
+ int i = 0;
+ printf("resetting...\n");
+ return doioctl(USBDEVFS_RESET, &i, "reset");
+}
+
+int main(int argc, char **argv)
+{
+ g_fd = open(argv[1], O_RDWR);
+ if (g_fd < 0)
+ return bitch("open");
+
+ reset();
+
+ usb_set_connected(0, 1);
+ claim_if(0);
+
+// set_config(1); - the culprit!
+ set_interface(0, 0);
+
+ msd();
+ return 0;
+}
diff --git a/src/VBox/Devices/USB/usbip/Makefile.kup b/src/VBox/Devices/USB/usbip/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/usbip/Makefile.kup
diff --git a/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp
new file mode 100644
index 00000000..b91fcffd
--- /dev/null
+++ b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp
@@ -0,0 +1,1818 @@
+/* $Id: USBProxyDevice-usbip.cpp $ */
+/** @file
+ * USB device proxy - USB/IP backend.
+ */
+
+/*
+ * Copyright (C) 2014-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/vmm/pdm.h>
+
+#include <iprt/asm.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/socket.h>
+#include <iprt/poll.h>
+#include <iprt/tcp.h>
+#include <iprt/pipe.h>
+#include <iprt/list.h>
+#include <iprt/semaphore.h>
+
+#include "../USBProxyDevice.h"
+
+
+/*********************************************************************************************************************************
+* Constants And Macros, Structures and Typedefs *
+*********************************************************************************************************************************/
+
+/** The USB version number used for the protocol. */
+#define USBIP_VERSION UINT16_C(0x0111)
+/** Request indicator in the command code. */
+#define USBIP_INDICATOR_REQ RT_BIT(15)
+
+/** Command/Reply code for OP_REQ/RET_DEVLIST. */
+#define USBIP_REQ_RET_DEVLIST UINT16_C(5)
+/** Command/Reply code for OP_REQ/REP_IMPORT. */
+#define USBIP_REQ_RET_IMPORT UINT16_C(3)
+/** USB submit command identifier. */
+#define USBIP_CMD_SUBMIT UINT32_C(1)
+/** USB submit status identifier. */
+#define USBIP_RET_SUBMIT UINT32_C(3)
+/** URB unlink (cancel) command identifier. */
+#define USBIP_CMD_UNLINK UINT32_C(2)
+/** URB unlink (cancel) reply identifier. */
+#define USBIP_RET_UNLINK UINT32_C(4)
+
+/** Short read is not okay for the specified URB. */
+#define USBIP_XFER_FLAGS_SHORT_NOT_OK RT_BIT_32(0)
+/** Queue the isochronous URB as soon as possible. */
+#define USBIP_XFER_FLAGS_ISO_ASAP RT_BIT_32(1)
+/** Don't use DMA mappings for this URB. */
+#define USBIP_XFER_FLAGS_NO_TRANSFER_DMA_MAP RT_BIT_32(2)
+/** Explain - only applies to UHCI. */
+#define USBIP_XFER_FLAGS_FSBR RT_BIT_32(4)
+
+/** URB direction - input. */
+#define USBIP_DIR_IN UINT32_C(1)
+/** URB direction - output. */
+#define USBIP_DIR_OUT UINT32_C(0)
+
+/** @name USB/IP error codes.
+ * @{ */
+/** Success indicator. */
+#define USBIP_STATUS_SUCCESS INT32_C(0)
+/** Pipe stalled. */
+#define USBIP_STATUS_PIPE_STALLED INT32_C(-32)
+/** URB was unlinked by a call to usb_unlink_urb(). */
+#define USBIP_STATUS_URB_UNLINKED INT32_C(-104)
+/** Short read. */
+#define USBIP_STATUS_SHORT_READ INT32_C(-121)
+/** @} */
+
+/**
+ * Exported device entry in the OP_RET_DEVLIST reply.
+ */
+#pragma pack(1)
+typedef struct UsbIpExportedDevice
+{
+ /** Path of the device, zero terminated string. */
+ char szPath[256];
+ /** Bus ID of the exported device, zero terminated string. */
+ char szBusId[32];
+ /** Bus number. */
+ uint32_t u32BusNum;
+ /** Device number. */
+ uint32_t u32DevNum;
+ /** Speed indicator of the device. */
+ uint32_t u32Speed;
+ /** Vendor ID of the device. */
+ uint16_t u16VendorId;
+ /** Product ID of the device. */
+ uint16_t u16ProductId;
+ /** Device release number. */
+ uint16_t u16BcdDevice;
+ /** Device class. */
+ uint8_t bDeviceClass;
+ /** Device Subclass. */
+ uint8_t bDeviceSubClass;
+ /** Device protocol. */
+ uint8_t bDeviceProtocol;
+ /** Configuration value. */
+ uint8_t bConfigurationValue;
+ /** Current configuration value of the device. */
+ uint8_t bNumConfigurations;
+ /** Number of interfaces for the device. */
+ uint8_t bNumInterfaces;
+} UsbIpExportedDevice;
+/** Pointer to a exported device entry. */
+typedef UsbIpExportedDevice *PUsbIpExportedDevice;
+#pragma pack()
+AssertCompileSize(UsbIpExportedDevice, 312);
+
+/**
+ * Interface descriptor entry for an exported device.
+ */
+#pragma pack(1)
+typedef struct UsbIpDeviceInterface
+{
+ /** Intefrace class. */
+ uint8_t bInterfaceClass;
+ /** Interface sub class. */
+ uint8_t bInterfaceSubClass;
+ /** Interface protocol identifier. */
+ uint8_t bInterfaceProtocol;
+ /** Padding byte for alignment. */
+ uint8_t bPadding;
+} UsbIpDeviceInterface;
+/** Pointer to an interface descriptor entry. */
+typedef UsbIpDeviceInterface *PUsbIpDeviceInterface;
+#pragma pack()
+
+/**
+ * USB/IP Import request.
+ */
+#pragma pack(1)
+typedef struct UsbIpReqImport
+{
+ /** Protocol version number. */
+ uint16_t u16Version;
+ /** Command code. */
+ uint16_t u16Cmd;
+ /** Status field, unused. */
+ int32_t u32Status;
+ /** Bus Id of the device as zero terminated string. */
+ char aszBusId[32];
+} UsbIpReqImport;
+/** Pointer to a import request. */
+typedef UsbIpReqImport *PUsbIpReqImport;
+#pragma pack()
+
+/**
+ * USB/IP Import reply.
+ *
+ * This is only the header, for successful
+ * imports the device details are sent to as
+ * defined in UsbIpExportedDevice.
+ */
+#pragma pack(1)
+typedef struct UsbIpRetImport
+{
+ /** Protocol version number. */
+ uint16_t u16Version;
+ /** Command code. */
+ uint16_t u16Cmd;
+ /** Status field, unused. */
+ int32_t u32Status;
+} UsbIpRetImport;
+/** Pointer to a import reply. */
+typedef UsbIpRetImport *PUsbIpRetImport;
+#pragma pack()
+
+/**
+ * Command/Reply header common to the submit and unlink commands
+ * replies.
+ */
+#pragma pack(1)
+typedef struct UsbIpReqRetHdr
+{
+ /** Request/Return code. */
+ uint32_t u32ReqRet;
+ /** Sequence number to identify the URB. */
+ uint32_t u32SeqNum;
+ /** Device id. */
+ uint32_t u32DevId;
+ /** Direction of the endpoint (host->device, device->host). */
+ uint32_t u32Direction;
+ /** Endpoint number. */
+ uint32_t u32Endpoint;
+} UsbIpReqRetHdr;
+/** Pointer to a request/reply header. */
+typedef UsbIpReqRetHdr *PUsbIpReqRetHdr;
+#pragma pack()
+
+/**
+ * USB/IP Submit request.
+ */
+#pragma pack(1)
+typedef struct UsbIpReqSubmit
+{
+ /** The request header. */
+ UsbIpReqRetHdr Hdr;
+ /** Transfer flags for the URB. */
+ uint32_t u32XferFlags;
+ /** Transfer buffer length. */
+ uint32_t u32TransferBufferLength;
+ /** Frame to transmit an ISO frame. */
+ uint32_t u32StartFrame;
+ /** Number of isochronous packets. */
+ uint32_t u32NumIsocPkts;
+ /** Maximum time for the request on the server side host controller. */
+ uint32_t u32Interval;
+ /** Setup data for a control URB. */
+ VUSBSETUP Setup;
+} UsbIpReqSubmit;
+/** Pointer to a submit request. */
+typedef UsbIpReqSubmit *PUsbIpReqSubmit;
+#pragma pack()
+AssertCompileSize(UsbIpReqSubmit, 48);
+
+/**
+ * USB/IP Submit reply.
+ */
+#pragma pack(1)
+typedef struct UsbIpRetSubmit
+{
+ /** The reply header. */
+ UsbIpReqRetHdr Hdr;
+ /** Status code. */
+ int32_t u32Status;
+ /** Actual length of the reply buffer. */
+ uint32_t u32ActualLength;
+ /** The actual selected frame for a isochronous transmit. */
+ uint32_t u32StartFrame;
+ /** Number of isochronous packets. */
+ uint32_t u32NumIsocPkts;
+ /** Number of failed isochronous packets. */
+ uint32_t u32ErrorCount;
+ /** Setup data for a control URB. */
+ VUSBSETUP Setup;
+} UsbIpRetSubmit;
+/** Pointer to a submit reply. */
+typedef UsbIpRetSubmit *PUsbIpRetSubmit;
+#pragma pack()
+AssertCompileSize(UsbIpRetSubmit, 48);
+
+/**
+ * Unlink URB request.
+ */
+#pragma pack(1)
+typedef struct UsbIpReqUnlink
+{
+ /** The request header. */
+ UsbIpReqRetHdr Hdr;
+ /** The sequence number to unlink. */
+ uint32_t u32SeqNum;
+ /** Padding - unused. */
+ uint8_t abPadding[24];
+} UsbIpReqUnlink;
+/** Pointer to a URB unlink request. */
+typedef UsbIpReqUnlink *PUsbIpReqUnlink;
+#pragma pack()
+AssertCompileSize(UsbIpReqUnlink, 48);
+
+/**
+ * Unlink URB reply.
+ */
+#pragma pack(1)
+typedef struct UsbIpRetUnlink
+{
+ /** The reply header. */
+ UsbIpReqRetHdr Hdr;
+ /** Status of the request. */
+ int32_t u32Status;
+ /** Padding - unused. */
+ uint8_t abPadding[24];
+} UsbIpRetUnlink;
+/** Pointer to a URB unlink request. */
+typedef UsbIpRetUnlink *PUsbIpRetUnlink;
+#pragma pack()
+AssertCompileSize(UsbIpRetUnlink, 48);
+
+/**
+ * Union of possible replies from the server during normal operation.
+ */
+#pragma pack(1)
+typedef union UsbIpRet
+{
+ /** The header. */
+ UsbIpReqRetHdr Hdr;
+ /** Submit reply. */
+ UsbIpRetSubmit RetSubmit;
+ /** Unlink reply. */
+ UsbIpRetUnlink RetUnlink;
+ /** Byte view. */
+ uint8_t abReply[1];
+} UsbIpRet;
+/** Pointer to a reply union. */
+typedef UsbIpRet *PUsbIpRet;
+#pragma pack()
+
+/**
+ * Isochronous packet descriptor.
+*/
+#pragma pack(1)
+typedef struct UsbIpIsocPktDesc
+{
+ /** Offset */
+ uint32_t u32Offset;
+ /** Length of the packet including padding. */
+ uint32_t u32Length;
+ /** Size of the transmitted data. */
+ uint32_t u32ActualLength;
+ /** Completion status for this packet. */
+ int32_t i32Status;
+} UsbIpIsocPktDesc;
+/** Pointer to a isochronous packet descriptor. */
+typedef UsbIpIsocPktDesc *PUsbIpIsocPktDesc;
+#pragma pack()
+
+/**
+ * USB/IP backend specific data for one URB.
+ * Required for tracking in flight and landed URBs.
+ */
+typedef struct USBPROXYURBUSBIP
+{
+ /** List node for the in flight or landed URB list. */
+ RTLISTNODE NodeList;
+ /** Sequence number the assigned URB is identified by. */
+ uint32_t u32SeqNumUrb;
+ /** Sequence number of the unlink command if the URB was cancelled. */
+ uint32_t u32SeqNumUrbUnlink;
+ /** Flag whether the URB was cancelled. */
+ bool fCancelled;
+ /** USB xfer type. */
+ VUSBXFERTYPE enmType;
+ /** USB xfer direction. */
+ VUSBDIRECTION enmDir;
+ /** Completion status. */
+ VUSBSTATUS enmStatus;
+ /** Pointer to the VUSB URB. */
+ PVUSBURB pVUsbUrb;
+} USBPROXYURBUSBIP;
+/** Pointer to a USB/IP URB. */
+typedef USBPROXYURBUSBIP *PUSBPROXYURBUSBIP;
+
+/**
+ * USB/IP data receive states.
+ */
+typedef enum USBPROXYUSBIPRECVSTATE
+{
+ /** Invalid receive state. */
+ USBPROXYUSBIPRECVSTATE_INVALID = 0,
+ /** Currently receiving the common header structure. */
+ USBPROXYUSBIPRECVSTATE_HDR_COMMON,
+ /** Currently receieving the rest of the header structure. */
+ USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL,
+ /** Currently receiving data into the URB buffer. */
+ USBPROXYUSBIPRECVSTATE_URB_BUFFER,
+ /** Currently receiving the isochronous packet descriptors. */
+ USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS,
+ /** Usual 32bit hack. */
+ USBPROXYUSBIPRECVSTATE_32BIT_HACK = 0x7fffffff
+} USBPROXYUSBIPRECVSTATE;
+/** Pointer to an receive state. */
+typedef USBPROXYUSBIPRECVSTATE *PUSBPROXYUSBIPRECVSTATE;
+
+/**
+ * Backend data for the USB/IP USB Proxy device backend.
+ */
+typedef struct USBPROXYDEVUSBIP
+{
+ /** IPRT socket handle. */
+ RTSOCKET hSocket;
+ /** Pollset with the wakeup pipe and socket. */
+ RTPOLLSET hPollSet;
+ /** Pipe endpoint - read (in the pollset). */
+ RTPIPE hPipeR;
+ /** Pipe endpoint - write. */
+ RTPIPE hPipeW;
+ /** Next sequence number to use for identifying submitted URBs. */
+ volatile uint32_t u32SeqNumNext;
+ /** Fast mutex protecting the lists below against concurrent access. */
+ RTSEMFASTMUTEX hMtxLists;
+ /** List of in flight URBs. */
+ RTLISTANCHOR ListUrbsInFlight;
+ /** List of landed URBs. */
+ RTLISTANCHOR ListUrbsLanded;
+ /** List of URBs to submit. */
+ RTLISTANCHOR ListUrbsToQueue;
+ /** Port of the USB/IP host to connect to. */
+ uint32_t uPort;
+ /** USB/IP host address. */
+ char *pszHost;
+ /** USB Bus ID of the device to capture. */
+ char *pszBusId;
+ /** The device ID to use to identify the device. */
+ uint32_t u32DevId;
+ /** Temporary buffer for the next reply header */
+ UsbIpRet BufRet;
+ /** Temporary buffer to hold all isochronous packet descriptors. */
+ UsbIpIsocPktDesc aIsocPktDesc[8];
+ /** Pointer to the current buffer to write received data to. */
+ uint8_t *pbRecv;
+ /** Number of bytes received so far. */
+ size_t cbRecv;
+ /** Number of bytes left to receive. until we advance the state machine and process the data */
+ size_t cbLeft;
+ /** The current receiving state. */
+ USBPROXYUSBIPRECVSTATE enmRecvState;
+ /** The URB we currently receive a response for. */
+ PUSBPROXYURBUSBIP pUrbUsbIp;
+} USBPROXYDEVUSBIP, *PUSBPROXYDEVUSBIP;
+
+/** Pollset id of the socket. */
+#define USBIP_POLL_ID_SOCKET 0
+/** Pollset id of the pipe. */
+#define USBIP_POLL_ID_PIPE 1
+
+/** USB/IP address prefix for identifcation. */
+#define USBIP_URI_PREFIX "usbip://"
+/** USB/IP address prefix length. */
+#define USBIP_URI_PREFIX_LEN (sizeof(USBIP_URI_PREFIX) - 1)
+
+/** Waking reason for the USB I/P reaper: New URBs to queue. */
+#define USBIP_REAPER_WAKEUP_REASON_QUEUE 'Q'
+/** Waking reason for the USB I/P reaper: External wakeup. */
+#define USBIP_REAPER_WAKEUP_REASON_EXTERNAL 'E'
+
+/**
+ * Converts a request/reply header from network to host endianness.
+ *
+ * @returns nothing.
+ * @param pHdr The header to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpReqRetHdrN2H(PUsbIpReqRetHdr pHdr)
+{
+ pHdr->u32ReqRet = RT_H2N_U32(pHdr->u32ReqRet);
+ pHdr->u32SeqNum = RT_H2N_U32(pHdr->u32SeqNum);
+ pHdr->u32DevId = RT_H2N_U32(pHdr->u32DevId);
+ pHdr->u32Direction = RT_H2N_U32(pHdr->u32Direction);
+ pHdr->u32Endpoint = RT_H2N_U32(pHdr->u32Endpoint);
+}
+
+/**
+ * Converts a request/reply header from host to network endianness.
+ *
+ * @returns nothing.
+ * @param pHdr The header to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpReqRetHdrH2N(PUsbIpReqRetHdr pHdr)
+{
+ pHdr->u32ReqRet = RT_N2H_U32(pHdr->u32ReqRet);
+ pHdr->u32SeqNum = RT_N2H_U32(pHdr->u32SeqNum);
+ pHdr->u32DevId = RT_N2H_U32(pHdr->u32DevId);
+ pHdr->u32Direction = RT_N2H_U32(pHdr->u32Direction);
+ pHdr->u32Endpoint = RT_N2H_U32(pHdr->u32Endpoint);
+}
+
+/**
+ * Converts a submit request from host to network endianness.
+ *
+ * @returns nothing.
+ * @param pReqSubmit The submit request to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpReqSubmitH2N(PUsbIpReqSubmit pReqSubmit)
+{
+ usbProxyUsbIpReqRetHdrH2N(&pReqSubmit->Hdr);
+ pReqSubmit->u32XferFlags = RT_H2N_U32(pReqSubmit->u32XferFlags);
+ pReqSubmit->u32TransferBufferLength = RT_H2N_U32(pReqSubmit->u32TransferBufferLength);
+ pReqSubmit->u32StartFrame = RT_H2N_U32(pReqSubmit->u32StartFrame);
+ pReqSubmit->u32NumIsocPkts = RT_H2N_U32(pReqSubmit->u32NumIsocPkts);
+ pReqSubmit->u32Interval = RT_H2N_U32(pReqSubmit->u32Interval);
+}
+
+/**
+ * Converts a submit reply from network to host endianness.
+ *
+ * @returns nothing.
+ * @param pReqSubmit The submit reply to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpRetSubmitN2H(PUsbIpRetSubmit pRetSubmit)
+{
+ usbProxyUsbIpReqRetHdrN2H(&pRetSubmit->Hdr);
+ pRetSubmit->u32Status = RT_N2H_U32(pRetSubmit->u32Status);
+ pRetSubmit->u32ActualLength = RT_N2H_U32(pRetSubmit->u32ActualLength);
+ pRetSubmit->u32StartFrame = RT_N2H_U32(pRetSubmit->u32StartFrame);
+ pRetSubmit->u32NumIsocPkts = RT_N2H_U32(pRetSubmit->u32NumIsocPkts);
+ pRetSubmit->u32ErrorCount = RT_N2H_U32(pRetSubmit->u32ErrorCount);
+}
+
+/**
+ * Converts a isochronous packet descriptor from host to network endianness.
+ *
+ * @returns nothing.
+ * @param pIsocPktDesc The packet descriptor to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpIsocPktDescH2N(PUsbIpIsocPktDesc pIsocPktDesc)
+{
+ pIsocPktDesc->u32Offset = RT_H2N_U32(pIsocPktDesc->u32Offset);
+ pIsocPktDesc->u32Length = RT_H2N_U32(pIsocPktDesc->u32Length);
+ pIsocPktDesc->u32ActualLength = RT_H2N_U32(pIsocPktDesc->u32ActualLength);
+ pIsocPktDesc->i32Status = RT_H2N_U32(pIsocPktDesc->i32Status);
+}
+
+/**
+ * Converts a isochronous packet descriptor from network to host endianness.
+ *
+ * @returns nothing.
+ * @param pIsocPktDesc The packet descriptor to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpIsocPktDescN2H(PUsbIpIsocPktDesc pIsocPktDesc)
+{
+ pIsocPktDesc->u32Offset = RT_N2H_U32(pIsocPktDesc->u32Offset);
+ pIsocPktDesc->u32Length = RT_N2H_U32(pIsocPktDesc->u32Length);
+ pIsocPktDesc->u32ActualLength = RT_N2H_U32(pIsocPktDesc->u32ActualLength);
+ pIsocPktDesc->i32Status = RT_N2H_U32(pIsocPktDesc->i32Status);
+}
+
+/**
+ * Converts a unlink request from host to network endianness.
+ *
+ * @returns nothing.
+ * @param pReqUnlink The unlink request to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpReqUnlinkH2N(PUsbIpReqUnlink pReqUnlink)
+{
+ usbProxyUsbIpReqRetHdrH2N(&pReqUnlink->Hdr);
+ pReqUnlink->u32SeqNum = RT_H2N_U32(pReqUnlink->u32SeqNum);
+}
+
+/**
+ * Converts a unlink reply from network to host endianness.
+ *
+ * @returns nothing.
+ * @param pRetUnlink The unlink reply to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpRetUnlinkN2H(PUsbIpRetUnlink pRetUnlink)
+{
+ usbProxyUsbIpReqRetHdrN2H(&pRetUnlink->Hdr);
+ pRetUnlink->u32Status = RT_N2H_U32(pRetUnlink->u32Status);
+}
+
+/**
+ * Convert the given exported device structure from host to network byte order.
+ *
+ * @returns nothing.
+ * @param pDevice The device structure to convert.
+ */
+DECLINLINE(void) usbProxyUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice)
+{
+ pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum);
+ pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum);
+ pDevice->u32Speed = RT_N2H_U16(pDevice->u32Speed);
+ pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId);
+ pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId);
+ pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice);
+}
+
+/**
+ * Converts a USB/IP status code to a VUSB status code.
+ *
+ * @returns VUSB status code.
+ * @param i32Status The USB/IP status code from the reply.
+ */
+DECLINLINE(VUSBSTATUS) usbProxyUsbIpVUsbStatusConvertFromStatus(int32_t i32Status)
+{
+ if (RT_LIKELY( i32Status == USBIP_STATUS_SUCCESS
+ || i32Status == USBIP_STATUS_SHORT_READ))
+ return VUSBSTATUS_OK;
+
+ switch (i32Status)
+ {
+ case USBIP_STATUS_PIPE_STALLED:
+ return VUSBSTATUS_STALL;
+ default:
+ return VUSBSTATUS_DNR;
+ }
+ /* not reached */
+}
+
+/**
+ * Gets the next free sequence number.
+ *
+ * @returns Next free sequence number.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+DECLINLINE(uint32_t) usbProxyUsbIpSeqNumGet(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ uint32_t u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext);
+ if (RT_UNLIKELY(!u32SeqNum))
+ u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext);
+
+ return u32SeqNum;
+}
+
+/**
+ * Links a given URB into the given list.
+ *
+ * @returns nothing.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param pList The list to link the URB into.
+ * @param pUrbUsbIp The URB to link.
+ */
+DECLINLINE(void) usbProxyUsbIpLinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PRTLISTANCHOR pList, PUSBPROXYURBUSBIP pUrbUsbIp)
+{
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ RTListAppend(pList, &pUrbUsbIp->NodeList);
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+}
+
+/**
+ * Unlinks a given URB from the current assigned list.
+ *
+ * @returns nothing.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param pUrbUsbIp The URB to unlink.
+ */
+DECLINLINE(void) usbProxyUsbIpUnlinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp)
+{
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ RTListNodeRemove(&pUrbUsbIp->NodeList);
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+}
+
+/**
+ * Allocates a USB/IP proxy specific URB state.
+ *
+ * @returns Pointer to the USB/IP specific URB data or NULL on failure.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static PUSBPROXYURBUSBIP usbProxyUsbIpUrbAlloc(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ NOREF(pProxyDevUsbIp);
+ return (PUSBPROXYURBUSBIP)RTMemAllocZ(sizeof(USBPROXYURBUSBIP));
+}
+
+/**
+ * Frees the given USB/IP URB state.
+ *
+ * @returns nothing.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param pUrbUsbIp The USB/IP speciic URB data.
+ */
+static void usbProxyUsbIpUrbFree(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp)
+{
+ NOREF(pProxyDevUsbIp);
+ RTMemFree(pUrbUsbIp);
+}
+
+/**
+ * Parse the string representation of the host address.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data to parse the address for.
+ * @param pszAddress The address string to parse.
+ */
+static int usbProxyUsbIpParseAddress(PUSBPROXYDEVUSBIP pProxyDevUsbIp, const char *pszAddress)
+{
+ int rc = VINF_SUCCESS;
+
+ if (!RTStrNCmp(pszAddress, USBIP_URI_PREFIX, USBIP_URI_PREFIX_LEN))
+ {
+ pszAddress += USBIP_URI_PREFIX_LEN;
+
+ const char *pszPortStart = RTStrStr(pszAddress, ":");
+ if (pszPortStart)
+ {
+ pszPortStart++;
+
+ const char *pszBusIdStart = RTStrStr(pszPortStart, ":");
+ if (pszBusIdStart)
+ {
+ size_t cbHost = pszPortStart - pszAddress - 1;
+ size_t cbBusId = strlen(pszBusIdStart);
+
+ pszBusIdStart++;
+
+ rc = RTStrToUInt32Ex(pszPortStart, NULL, 10 /* uBase */, &pProxyDevUsbIp->uPort);
+ if ( rc == VINF_SUCCESS
+ || rc == VWRN_TRAILING_CHARS)
+ {
+ rc = RTStrAllocEx(&pProxyDevUsbIp->pszHost, cbHost + 1);
+ if (RT_SUCCESS(rc))
+ rc = RTStrAllocEx(&pProxyDevUsbIp->pszBusId, cbBusId + 1);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTStrCopyEx(pProxyDevUsbIp->pszHost, cbHost + 1, pszAddress, cbHost);
+ AssertRC(rc);
+
+ rc = RTStrCopyEx(pProxyDevUsbIp->pszBusId, cbBusId + 1, pszBusIdStart, cbBusId);
+ AssertRC(rc);
+
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+ }
+ else
+ rc = VERR_INVALID_PARAMETER;
+
+ return rc;
+}
+
+/**
+ * Connects to the USB/IP host and claims the device given in the proxy device data.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static int usbProxyUsbIpConnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ int rc = VINF_SUCCESS;
+ rc = RTTcpClientConnect(pProxyDevUsbIp->pszHost, pProxyDevUsbIp->uPort, &pProxyDevUsbIp->hSocket);
+ if (RT_SUCCESS(rc))
+ {
+ /* Disable send coalescing. */
+ rc = RTTcpSetSendCoalescing(pProxyDevUsbIp->hSocket, false);
+ if (RT_FAILURE(rc))
+ LogRel(("UsbIp: Disabling send coalescing failed (rc=%Rrc), continuing nevertheless but expect reduced performance\n", rc));
+
+ /* Import the device, i.e. claim it for our use. */
+ UsbIpReqImport ReqImport;
+ ReqImport.u16Version = RT_H2N_U16(USBIP_VERSION);
+ ReqImport.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_IMPORT);
+ ReqImport.u32Status = RT_H2N_U32(USBIP_STATUS_SUCCESS);
+ rc = RTStrCopy(&ReqImport.aszBusId[0], sizeof(ReqImport.aszBusId), pProxyDevUsbIp->pszBusId);
+ if (rc == VINF_SUCCESS)
+ {
+ rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqImport, sizeof(ReqImport));
+ if (RT_SUCCESS(rc))
+ {
+ /* Read the reply. */
+ UsbIpRetImport RetImport;
+ rc = RTTcpRead(pProxyDevUsbIp->hSocket, &RetImport, sizeof(RetImport), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ RetImport.u16Version = RT_N2H_U16(RetImport.u16Version);
+ RetImport.u16Cmd = RT_N2H_U16(RetImport.u16Cmd);
+ RetImport.u32Status = RT_N2H_U32(RetImport.u32Status);
+ if ( RetImport.u16Version == USBIP_VERSION
+ && RetImport.u16Cmd == USBIP_REQ_RET_IMPORT
+ && RetImport.u32Status == USBIP_STATUS_SUCCESS)
+ {
+ /* Read the device data. */
+ UsbIpExportedDevice Device;
+ rc = RTTcpRead(pProxyDevUsbIp->hSocket, &Device, sizeof(Device), NULL);
+ if (RT_SUCCESS(rc))
+ {
+ usbProxyUsbIpExportedDeviceN2H(&Device);
+ pProxyDevUsbIp->u32DevId = (Device.u32BusNum << 16) | Device.u32DevNum;
+
+ rc = RTPollSetAddSocket(pProxyDevUsbIp->hPollSet, pProxyDevUsbIp->hSocket,
+ RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, USBIP_POLL_ID_SOCKET);
+ }
+ }
+ else
+ {
+ /* Check what went wrong and leave a meaningful error message in the log. */
+ if (RetImport.u16Version != USBIP_VERSION)
+ LogRel(("UsbIp: Unexpected protocol version received from host (%#x vs. %#x)\n",
+ RetImport.u16Version, USBIP_VERSION));
+ else if (RetImport.u16Cmd != USBIP_REQ_RET_IMPORT)
+ LogRel(("UsbIp: Unexpected reply code received from host (%#x vs. %#x)\n",
+ RetImport.u16Cmd, USBIP_REQ_RET_IMPORT));
+ else if (RetImport.u32Status != 0)
+ LogRel(("UsbIp: Claiming the device has failed on the host with an unspecified error\n"));
+ else
+ AssertMsgFailed(("Something went wrong with if condition\n"));
+ }
+ }
+ }
+ }
+ else
+ {
+ LogRel(("UsbIp: Given bus ID is exceeds permitted protocol length: %u vs %u\n",
+ strlen(pProxyDevUsbIp->pszBusId) + 1, sizeof(ReqImport.aszBusId)));
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ if (RT_FAILURE(rc))
+ RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/);
+ }
+ if (RT_FAILURE(rc))
+ LogRel(("UsbIp: Connecting to the host %s failed with %Rrc\n", pProxyDevUsbIp->pszHost, rc));
+ return rc;
+}
+
+/**
+ * Disconnects from the USB/IP host releasing the device given in the proxy device data.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static int usbProxyUsbIpDisconnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ int rc = RTPollSetRemove(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET);
+ Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND);
+
+ rc = RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/);
+ if (RT_SUCCESS(rc))
+ pProxyDevUsbIp->hSocket = NIL_RTSOCKET;
+ return rc;
+}
+
+/**
+ * Returns the URB matching the given sequence number from the in flight list.
+ *
+ * @returns pointer to the URB matching the given sequence number or NULL
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param u32SeqNum The sequence number to search for.
+ */
+static PUSBPROXYURBUSBIP usbProxyUsbIpGetInFlightUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum)
+{
+ bool fFound = false;
+
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ PUSBPROXYURBUSBIP pIt;
+ RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList)
+ {
+ if (pIt->u32SeqNumUrb == u32SeqNum)
+ {
+ fFound = true;
+ break;
+ }
+ }
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Returns the URB matching the given sequence number from the cancel list.
+ *
+ * @returns pointer to the URB matching the given sequence number or NULL
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param u32SeqNum The sequence number to search for.
+ */
+static PUSBPROXYURBUSBIP usbProxyUsbIpGetCancelledUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum)
+{
+ bool fFound = false;
+
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ PUSBPROXYURBUSBIP pIt;
+ RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList)
+ {
+ if ( pIt->u32SeqNumUrbUnlink == u32SeqNum
+ && pIt->fCancelled == true)
+ {
+ fFound = true;
+ break;
+ }
+ }
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+
+ return fFound ? pIt : NULL;
+}
+
+/**
+ * Resets the receive state for a new reply.
+ *
+ * @returns nothing.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static void usbProxyUsbIpResetRecvState(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_COMMON;
+ pProxyDevUsbIp->pbRecv = (uint8_t *)&pProxyDevUsbIp->BufRet;
+ pProxyDevUsbIp->cbRecv = 0;
+ pProxyDevUsbIp->cbLeft = sizeof(UsbIpReqRetHdr);
+}
+
+static void usbProxyUsbIpRecvStateAdvance(PUSBPROXYDEVUSBIP pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE enmState,
+ uint8_t *pbData, size_t cbData)
+{
+ pProxyDevUsbIp->enmRecvState = enmState;
+ pProxyDevUsbIp->cbRecv = 0;
+ pProxyDevUsbIp->cbLeft = cbData;
+ pProxyDevUsbIp->pbRecv = pbData;
+}
+
+/**
+ * Handles reception of a USB/IP PDU.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param ppUrbUsbIp Where to store the pointer to the USB/IP URB which completed.
+ * Will be NULL if the received PDU is not complete and we have
+ * have to wait for more data or on failure.
+ */
+static int usbProxyUsbIpRecvPdu(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP *ppUrbUsbIp)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbRead = 0;
+ PUSBPROXYURBUSBIP pUrbUsbIp = NULL;
+
+ Assert(pProxyDevUsbIp->cbLeft);
+
+ /* Read any available data first. */
+ rc = RTTcpReadNB(pProxyDevUsbIp->hSocket, pProxyDevUsbIp->pbRecv, pProxyDevUsbIp->cbLeft, &cbRead);
+ if (RT_SUCCESS(rc))
+ {
+ pProxyDevUsbIp->cbRecv += cbRead;
+ pProxyDevUsbIp->cbLeft -= cbRead;
+ pProxyDevUsbIp->pbRecv += cbRead;
+
+ /* Process the received data if there is nothing to receive left for the current state. */
+ if (!pProxyDevUsbIp->cbLeft)
+ {
+ switch (pProxyDevUsbIp->enmRecvState)
+ {
+ case USBPROXYUSBIPRECVSTATE_HDR_COMMON:
+ {
+ Assert(pProxyDevUsbIp->cbRecv == sizeof(UsbIpReqRetHdr));
+
+ /*
+ * Determine the residual amount of data to receive until
+ * the complete reply header was received.
+ */
+ switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet))
+ {
+ case USBIP_RET_SUBMIT:
+ pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetSubmit) - sizeof(UsbIpReqRetHdr);
+ pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL;
+ break;
+ case USBIP_RET_UNLINK:
+ pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetUnlink) - sizeof(UsbIpReqRetHdr);
+ pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL;
+ break;
+ default:
+ AssertLogRelMsgFailed(("Invalid reply header received: %d\n",
+ pProxyDevUsbIp->BufRet.Hdr.u32ReqRet));
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ }
+
+ break;
+ }
+ case USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL:
+ {
+ switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet))
+ {
+ case USBIP_RET_SUBMIT:
+ /* Get the URB from the in flight list. */
+ pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetInFlightUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum));
+ if (pProxyDevUsbIp->pUrbUsbIp)
+ {
+ usbProxyUsbIpRetSubmitN2H(&pProxyDevUsbIp->BufRet.RetSubmit);
+
+ /* We still have to receive the transfer buffer, even in case of an error. */
+ pProxyDevUsbIp->pUrbUsbIp->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetSubmit.u32Status);
+ if (pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_IN)
+ {
+ uint8_t *pbData = NULL;
+ size_t cbRet = 0;
+
+ AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb);
+ if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG)
+ {
+ /* Preserve the setup request. */
+ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)];
+ cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP);
+ }
+ else
+ {
+ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0];
+ cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength;
+ }
+
+ if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength)
+ {
+ if (RT_LIKELY(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData >= cbRet))
+ {
+ pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = (uint32_t)cbRet;
+ usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER,
+ pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength);
+ }
+ else
+ {
+ /*
+ * Bogus length returned from the USB/IP remote server.
+ * Error out because there is no way to find the end of the current
+ * URB and the beginning of the next one. The error will cause closing the
+ * connection to the rogue remote and all URBs get completed with an error.
+ */
+ LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid length %zu (max %zu)\n",
+ pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, cbRet,
+ pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ }
+ else
+ {
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ }
+ }
+ else
+ {
+ Assert(pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_OUT);
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ }
+ }
+ else
+ {
+ LogRel(("USB/IP: Received reply with sequence number %u doesn't match any local URB\n",
+ RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)));
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ rc = VERR_NET_PROTOCOL_ERROR;
+ }
+ break;
+ case USBIP_RET_UNLINK:
+ pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetCancelledUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum));
+ if (pProxyDevUsbIp->pUrbUsbIp)
+ {
+ usbProxyUsbIpRetUnlinkN2H(&pProxyDevUsbIp->BufRet.RetUnlink);
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ pUrbUsbIp->pVUsbUrb->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetUnlink.u32Status);
+ }
+ /* else: Probably received the data for the URB and is complete already. */
+
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ break;
+ }
+
+ break;
+ }
+ case USBPROXYUSBIPRECVSTATE_URB_BUFFER:
+ if (pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->enmType == VUSBXFERTYPE_ISOC)
+ usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS,
+ (uint8_t *)&pProxyDevUsbIp->aIsocPktDesc[0],
+ pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc));
+ else
+ {
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ }
+ break;
+ case USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS:
+ /* Process all received isochronous packet descriptors. */
+ for (unsigned i = 0; i < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts; i++)
+ {
+ PVUSBURBISOCPTK pIsocPkt = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->aIsocPkts[i];
+ PUsbIpIsocPktDesc pIsocPktUsbIp = &pProxyDevUsbIp->aIsocPktDesc[i];
+
+ usbProxyUsbIpIsocPktDescN2H(pIsocPktUsbIp);
+ pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pIsocPktUsbIp->i32Status);
+
+ if (RT_LIKELY( pIsocPktUsbIp->u32Offset < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData
+ && pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData - pIsocPktUsbIp->u32Offset >= pIsocPktUsbIp->u32ActualLength))
+ {
+ pIsocPkt->off = pIsocPktUsbIp->u32Offset;
+ pIsocPkt->cb = pIsocPktUsbIp->u32ActualLength;
+ }
+ else
+ {
+ /*
+ * The offset and length value in the isoc packet descriptor are bogus and would cause a buffer overflow later on, leave an
+ * error message and disconnect from the rogue remote end.
+ */
+ LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid isoc packet descriptor %u (offset=%u length=%u)\n",
+ pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, i,
+ pIsocPktUsbIp->u32Offset, pIsocPktUsbIp->u32ActualLength));
+ rc = VERR_NET_PROTOCOL_ERROR;
+ break;
+ }
+ }
+
+ pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ break;
+ default:
+ AssertLogRelMsgFailed(("USB/IP: Invalid receive state %d\n", pProxyDevUsbIp->enmRecvState));
+ }
+ }
+ }
+
+ if (RT_SUCCESS(rc))
+ *ppUrbUsbIp = pUrbUsbIp;
+ else
+ {
+ /* Complete all URBs with DNR error and mark device as unplugged, the current one is still in the in flight list. */
+ pProxyDevUsbIp->pUrbUsbIp = NULL;
+ usbProxyUsbIpResetRecvState(pProxyDevUsbIp);
+ usbProxyUsbIpDisconnect(pProxyDevUsbIp);
+
+ rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ PUSBPROXYURBUSBIP pIt;
+ PUSBPROXYURBUSBIP pItNext;
+ RTListForEachSafe(&pProxyDevUsbIp->ListUrbsInFlight, pIt, pItNext, USBPROXYURBUSBIP, NodeList)
+ {
+ if (pIt->pVUsbUrb) /* can be NULL for requests created by usbProxyUsbIpCtrlUrbExchangeSync(). */
+ pIt->pVUsbUrb->enmStatus = VUSBSTATUS_CRC;
+ RTListNodeRemove(&pIt->NodeList);
+ RTListAppend(&pProxyDevUsbIp->ListUrbsLanded, &pIt->NodeList);
+ }
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+ }
+
+ return rc;
+}
+
+/**
+ * Worker for queueing an URB on the main I/O thread.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param pUrbUsbIp The USB/IP URB to queue.
+ */
+static int usbProxyUsbIpUrbQueueWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp)
+{
+ PVUSBURB pUrb = pUrbUsbIp->pVUsbUrb;
+
+ pUrbUsbIp->u32SeqNumUrb = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp);
+ pUrbUsbIp->enmType = pUrb->enmType;
+ pUrbUsbIp->enmStatus = pUrb->enmStatus;
+ pUrbUsbIp->enmDir = pUrb->enmDir;
+
+ UsbIpReqSubmit ReqSubmit;
+
+ RT_ZERO(ReqSubmit);
+ ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT;
+ ReqSubmit.Hdr.u32SeqNum = pUrbUsbIp->u32SeqNumUrb;
+ ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId;
+ ReqSubmit.Hdr.u32Endpoint = pUrb->EndPt;
+ ReqSubmit.Hdr.u32Direction = pUrb->enmDir == VUSBDIRECTION_IN ? USBIP_DIR_IN : USBIP_DIR_OUT;
+ ReqSubmit.u32XferFlags = 0;
+ if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk)
+ ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_SHORT_NOT_OK;
+
+ ReqSubmit.u32TransferBufferLength = pUrb->cbData;
+ ReqSubmit.u32StartFrame = 0;
+ ReqSubmit.u32NumIsocPkts = 0;
+ ReqSubmit.u32Interval = 0;
+
+ RTSGSEG aSegReq[3]; /* Maximum number of segments used for a Isochronous transfer. */
+ UsbIpIsocPktDesc aIsocPktsDesc[8];
+ unsigned cSegsUsed = 1;
+ aSegReq[0].pvSeg = &ReqSubmit;
+ aSegReq[0].cbSeg = sizeof(ReqSubmit);
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_MSG:
+ memcpy(&ReqSubmit.Setup, &pUrb->abData, sizeof(ReqSubmit.Setup));
+ ReqSubmit.u32TransferBufferLength -= sizeof(VUSBSETUP);
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ aSegReq[cSegsUsed].cbSeg = pUrb->cbData - sizeof(VUSBSETUP);
+ aSegReq[cSegsUsed].pvSeg = pUrb->abData + sizeof(VUSBSETUP);
+ if (aSegReq[cSegsUsed].cbSeg)
+ cSegsUsed++;
+ }
+ LogFlowFunc(("Message (Control) URB\n"));
+ break;
+ case VUSBXFERTYPE_ISOC:
+ LogFlowFunc(("Isochronous URB\n"));
+ ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_ISO_ASAP;
+ ReqSubmit.u32NumIsocPkts = pUrb->cIsocPkts;
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ aSegReq[cSegsUsed].cbSeg = pUrb->cbData;
+ aSegReq[cSegsUsed].pvSeg = pUrb->abData;
+ cSegsUsed++;
+ }
+
+ for (unsigned i = 0; i < pUrb->cIsocPkts; i++)
+ {
+ aIsocPktsDesc[i].u32Offset = pUrb->aIsocPkts[i].off;
+ aIsocPktsDesc[i].u32Length = pUrb->aIsocPkts[i].cb;
+ aIsocPktsDesc[i].u32ActualLength = 0; /** @todo */
+ aIsocPktsDesc[i].i32Status = pUrb->aIsocPkts[i].enmStatus;
+ usbProxyUsbIpIsocPktDescH2N(&aIsocPktsDesc[i]);
+ }
+
+ if (pUrb->cIsocPkts)
+ {
+ aSegReq[cSegsUsed].cbSeg = pUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc);
+ aSegReq[cSegsUsed].pvSeg = &aIsocPktsDesc[0];
+ cSegsUsed++;
+ }
+
+ break;
+ case VUSBXFERTYPE_BULK:
+ case VUSBXFERTYPE_INTR:
+ LogFlowFunc(("Bulk URB\n"));
+ if (pUrb->enmDir == VUSBDIRECTION_OUT)
+ {
+ aSegReq[cSegsUsed].cbSeg = pUrb->cbData;
+ aSegReq[cSegsUsed].pvSeg = pUrb->abData;
+ cSegsUsed++;
+ }
+ break;
+ default:
+ return VERR_INVALID_PARAMETER; /** @todo better status code. */
+ }
+
+ usbProxyUsbIpReqSubmitH2N(&ReqSubmit);
+
+ Assert(cSegsUsed <= RT_ELEMENTS(aSegReq));
+
+ /* Send the command. */
+ RTSGBUF SgBufReq;
+ RTSgBufInit(&SgBufReq, &aSegReq[0], cSegsUsed);
+
+ int rc = RTTcpSgWrite(pProxyDevUsbIp->hSocket, &SgBufReq);
+ if (RT_SUCCESS(rc))
+ {
+ /* Link the URB into the list of in flight URBs. */
+ usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, pUrbUsbIp);
+ }
+
+ return rc;
+}
+
+/**
+ * Queues all pending URBs from the list.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static int usbProxyUsbIpUrbsQueuePending(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ RTLISTANCHOR ListUrbsPending;
+
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ RTListMove(&ListUrbsPending, &pProxyDevUsbIp->ListUrbsToQueue);
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+
+ PUSBPROXYURBUSBIP pIter;
+ PUSBPROXYURBUSBIP pIterNext;
+ RTListForEachSafe(&ListUrbsPending, pIter, pIterNext, USBPROXYURBUSBIP, NodeList)
+ {
+ RTListNodeRemove(&pIter->NodeList);
+ rc = usbProxyUsbIpUrbQueueWorker(pProxyDevUsbIp, pIter);
+ if (RT_FAILURE(rc))
+ {
+ /* Complete URB with an error and place into landed list. */
+ pIter->pVUsbUrb->enmStatus = VUSBSTATUS_DNR;
+ usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pIter);
+ }
+ }
+
+ return VINF_SUCCESS;
+}
+
+/**
+ * Kick the reaper thread.
+ *
+ * @returns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param bReason The wakeup reason.
+ */
+static char usbProxyReaperKick(PUSBPROXYDEVUSBIP pProxyDevUsbIp, char bReason)
+{
+ int rc = VINF_SUCCESS;
+ size_t cbWritten = 0;
+
+ rc = RTPipeWrite(pProxyDevUsbIp->hPipeW, &bReason, 1, &cbWritten);
+ Assert(RT_SUCCESS(rc) || cbWritten == 0);
+
+ return rc;
+}
+
+/**
+ * Drain the wakeup pipe.
+ *
+ * @returns Wakeup reason.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ */
+static char usbProxyUsbIpWakeupPipeDrain(PUSBPROXYDEVUSBIP pProxyDevUsbIp)
+{
+ char bRead = 0;
+ size_t cbRead = 0;
+ int rc = RTPipeRead(pProxyDevUsbIp->hPipeR, &bRead, 1, &cbRead);
+ Assert(RT_SUCCESS(rc) && cbRead == 1); NOREF(rc);
+
+ return bRead;
+}
+
+/**
+ * Executes the poll/receive loop either until a URB is received (with an optional matching sequence number) or
+ * the given timeout has elapsed.
+ *
+ * @returns Pointer to the received USB/IP URB or NULL on timeout or error.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param u32SeqNumRet The sequence number of a specific reply to return the URB for, 0 if
+ * any received URB is accepted.
+ * @param fPollWakePipe Flag whether to poll the wakeup pipe.
+ * @param cMillies Maximum number of milliseconds to wait for an URB to arrive.
+ */
+static PUSBPROXYURBUSBIP usbProxyUsbIpPollWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNumRet,
+ bool fPollWakePipe, RTMSINTERVAL cMillies)
+{
+ int rc = VINF_SUCCESS;
+ PUSBPROXYURBUSBIP pUrbUsbIp = NULL;
+
+ if (!fPollWakePipe)
+ {
+ rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_ERROR);
+ AssertRC(rc);
+ }
+
+ while (!pUrbUsbIp && RT_SUCCESS(rc) && cMillies)
+ {
+ uint32_t uIdReady = 0;
+ uint32_t fEventsRecv = 0;
+ RTMSINTERVAL msStart = RTTimeMilliTS();
+ RTMSINTERVAL msNow;
+
+ rc = RTPoll(pProxyDevUsbIp->hPollSet, cMillies, &fEventsRecv, &uIdReady);
+ Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
+ if (RT_SUCCESS(rc))
+ {
+ msNow = RTTimeMilliTS();
+ cMillies = msNow - msStart >= cMillies ? 0 : cMillies - (msNow - msStart);
+
+ if (uIdReady == USBIP_POLL_ID_SOCKET)
+ {
+ rc = usbProxyUsbIpRecvPdu(pProxyDevUsbIp, &pUrbUsbIp);
+ if ( RT_SUCCESS(rc)
+ && pUrbUsbIp)
+ {
+ /* Link the URB into the landed list if a specifc reply is requested and the URB doesn't match. */
+ if ( u32SeqNumRet != 0
+ && pUrbUsbIp->u32SeqNumUrb != u32SeqNumRet)
+ {
+ usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp);
+ usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pUrbUsbIp);
+ pUrbUsbIp = NULL;
+ }
+ }
+ }
+ else
+ {
+ AssertLogRelMsg(uIdReady == USBIP_POLL_ID_PIPE, ("Invalid pollset ID given\n"));
+
+ char bReason = usbProxyUsbIpWakeupPipeDrain(pProxyDevUsbIp);
+ if (bReason == USBIP_REAPER_WAKEUP_REASON_QUEUE)
+ usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp);
+ else
+ {
+ Assert(bReason == USBIP_REAPER_WAKEUP_REASON_EXTERNAL);
+ break;
+ }
+ }
+ }
+ }
+
+ if (!fPollWakePipe)
+ {
+ rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_READ);
+ AssertRC(rc);
+ }
+
+ return pUrbUsbIp;
+}
+
+/**
+ * Synchronously exchange a given control message with the remote device.
+ *
+ * @eturns VBox status code.
+ * @param pProxyDevUsbIp The USB/IP proxy device data.
+ * @param pSetup The setup message.
+ *
+ * @note This method is only used to implement the *SetConfig, *SetInterface and *ClearHaltedEp
+ * callbacks because the USB/IP protocol lacks dedicated requests for these.
+ * @remark It is assumed that this method is never called while usbProxyUsbIpUrbReap is called
+ * on another thread.
+ */
+static int usbProxyUsbIpCtrlUrbExchangeSync(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PVUSBSETUP pSetup)
+{
+ int rc = VINF_SUCCESS;
+
+ UsbIpReqSubmit ReqSubmit;
+ USBPROXYURBUSBIP UsbIpUrb;
+
+ RT_ZERO(ReqSubmit);
+
+ uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp);
+ ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT;
+ ReqSubmit.Hdr.u32SeqNum = u32SeqNum;
+ ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId;
+ ReqSubmit.Hdr.u32Direction = USBIP_DIR_OUT;
+ ReqSubmit.Hdr.u32Endpoint = 0; /* Only default control endpoint is allowed for these kind of messages. */
+ ReqSubmit.u32XferFlags = 0;
+ ReqSubmit.u32TransferBufferLength = 0;
+ ReqSubmit.u32StartFrame = 0;
+ ReqSubmit.u32NumIsocPkts = 0;
+ ReqSubmit.u32Interval = 0;
+ memcpy(&ReqSubmit.Setup, pSetup, sizeof(ReqSubmit.Setup));
+ usbProxyUsbIpReqSubmitH2N(&ReqSubmit);
+
+ UsbIpUrb.u32SeqNumUrb = u32SeqNum;
+ UsbIpUrb.u32SeqNumUrbUnlink = 0;
+ UsbIpUrb.fCancelled = false;
+ UsbIpUrb.enmType = VUSBXFERTYPE_MSG;
+ UsbIpUrb.enmDir = VUSBDIRECTION_OUT;
+ UsbIpUrb.pVUsbUrb = NULL;
+
+ /* Send the command. */
+ rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqSubmit, sizeof(ReqSubmit));
+ if (RT_SUCCESS(rc))
+ {
+ usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, &UsbIpUrb);
+ PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, u32SeqNum, false /*fPollWakePipe*/,
+ 30 * RT_MS_1SEC);
+ Assert( !pUrbUsbIp
+ || pUrbUsbIp == &UsbIpUrb); /* The returned URB should point to the URB we submitted. */
+ usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, &UsbIpUrb);
+
+ if (!pUrbUsbIp)
+ rc = VERR_TIMEOUT;
+ }
+
+ return rc;
+}
+
+
+/*
+ * The USB proxy device functions.
+ */
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnOpen}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ LogFlowFunc(("pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ int rc = VINF_SUCCESS;
+
+ RTListInit(&pDevUsbIp->ListUrbsInFlight);
+ RTListInit(&pDevUsbIp->ListUrbsLanded);
+ RTListInit(&pDevUsbIp->ListUrbsToQueue);
+ pDevUsbIp->hSocket = NIL_RTSOCKET;
+ pDevUsbIp->hPollSet = NIL_RTPOLLSET;
+ pDevUsbIp->hPipeW = NIL_RTPIPE;
+ pDevUsbIp->hPipeR = NIL_RTPIPE;
+ pDevUsbIp->u32SeqNumNext = 0;
+ pDevUsbIp->pszHost = NULL;
+ pDevUsbIp->pszBusId = NULL;
+ usbProxyUsbIpResetRecvState(pDevUsbIp);
+
+ rc = RTSemFastMutexCreate(&pDevUsbIp->hMtxLists);
+ if (RT_SUCCESS(rc))
+ {
+ /* Setup wakeup pipe and poll set first. */
+ rc = RTPipeCreate(&pDevUsbIp->hPipeR, &pDevUsbIp->hPipeW, 0);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetCreate(&pDevUsbIp->hPollSet);
+ if (RT_SUCCESS(rc))
+ {
+ rc = RTPollSetAddPipe(pDevUsbIp->hPollSet, pDevUsbIp->hPipeR,
+ RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE);
+ if (RT_SUCCESS(rc))
+ {
+ /* Connect to the USB/IP host. */
+ rc = usbProxyUsbIpParseAddress(pDevUsbIp, pszAddress);
+ if (RT_SUCCESS(rc))
+ rc = usbProxyUsbIpConnect(pDevUsbIp);
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE);
+ int rc2 = RTPollSetDestroy(pDevUsbIp->hPollSet);
+ AssertRC(rc2);
+ }
+ }
+
+ if (RT_FAILURE(rc))
+ {
+ int rc2 = RTPipeClose(pDevUsbIp->hPipeR);
+ AssertRC(rc2);
+ rc2 = RTPipeClose(pDevUsbIp->hPipeW);
+ AssertRC(rc2);
+ }
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnClose}
+ */
+static DECLCALLBACK(void) usbProxyUsbIpClose(PUSBPROXYDEV pProxyDev)
+{
+ int rc = VINF_SUCCESS;
+ LogFlowFunc(("pProxyDev = %p\n", pProxyDev));
+
+ PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ if (pDevUsbIp->hSocket != NIL_RTSOCKET)
+ usbProxyUsbIpDisconnect(pDevUsbIp);
+
+ /* Destroy the pipe and pollset if necessary. */
+ if (pDevUsbIp->hPollSet != NIL_RTPOLLSET)
+ {
+ rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE);
+ AssertRC(rc);
+ rc = RTPollSetDestroy(pDevUsbIp->hPollSet);
+ AssertRC(rc);
+ rc = RTPipeClose(pDevUsbIp->hPipeR);
+ AssertRC(rc);
+ rc = RTPipeClose(pDevUsbIp->hPipeW);
+ AssertRC(rc);
+ }
+
+ if (pDevUsbIp->pszHost)
+ RTStrFree(pDevUsbIp->pszHost);
+ if (pDevUsbIp->pszBusId)
+ RTStrFree(pDevUsbIp->pszBusId);
+
+ /* Clear the URB lists. */
+ rc = RTSemFastMutexRequest(pDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ PUSBPROXYURBUSBIP pIter;
+ PUSBPROXYURBUSBIP pIterNext;
+ RTListForEachSafe(&pDevUsbIp->ListUrbsInFlight, pIter, pIterNext, USBPROXYURBUSBIP, NodeList)
+ {
+ RTListNodeRemove(&pIter->NodeList);
+ RTMemFree(pIter);
+ }
+
+ RTListForEachSafe(&pDevUsbIp->ListUrbsLanded, pIter, pIterNext, USBPROXYURBUSBIP, NodeList)
+ {
+ RTListNodeRemove(&pIter->NodeList);
+ RTMemFree(pIter);
+ }
+ RTSemFastMutexRelease(pDevUsbIp->hMtxLists);
+ RTSemFastMutexDestroy(pDevUsbIp->hMtxLists);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnReset}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ LogFlowFunc(("pProxyDev = %p\n", pProxyDev));
+
+ int rc = VINF_SUCCESS;
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ VUSBSETUP Setup;
+
+ if (fResetOnLinux)
+ {
+ Setup.bmRequestType = RT_BIT(5) | 0x03; /* Port request. */
+ Setup.bRequest = 0x03; /* SET_FEATURE */
+ Setup.wValue = 4; /* Port feature: Reset */
+ Setup.wIndex = 0; /* Port number, irrelevant */
+ Setup.wLength = 0;
+ rc = usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup);
+ if (RT_SUCCESS(rc))
+ {
+ pProxyDev->iActiveCfg = -1;
+ pProxyDev->cIgnoreSetConfigs = 2;
+ }
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnSetConfig}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
+{
+ LogFlowFunc(("pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, iCfg));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ VUSBSETUP Setup;
+
+ Setup.bmRequestType = 0;
+ Setup.bRequest = 0x09;
+ Setup.wValue = iCfg;
+ Setup.wIndex = 0;
+ Setup.wLength = 0;
+ return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnClaimInterface}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ RT_NOREF(pProxyDev, iIf);
+ LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnReleaseInterface}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ RT_NOREF(pProxyDev, iIf);
+ LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf));
+ return VINF_SUCCESS;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnSetInterface}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting)
+{
+ LogFlowFunc(("pProxyDev=%p iIf=%#x setting=%#x\n", pProxyDev, iIf, setting));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ VUSBSETUP Setup;
+
+ Setup.bmRequestType = 0x1;
+ Setup.bRequest = 0x0b; /* SET_INTERFACE */
+ Setup.wValue = setting;
+ Setup.wIndex = iIf;
+ Setup.wLength = 0;
+ return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnClearHaltedEndpoint}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int iEp)
+{
+ LogFlowFunc(("pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, iEp));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ VUSBSETUP Setup;
+
+ Setup.bmRequestType = 0x2;
+ Setup.bRequest = 0x01; /* CLEAR_FEATURE */
+ Setup.wValue = 0x00; /* ENDPOINT_HALT */
+ Setup.wIndex = iEp;
+ Setup.wLength = 0;
+ return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ LogFlowFunc(("pUrb=%p\n", pUrb));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+
+ /* Allocate a USB/IP Urb. */
+ PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpUrbAlloc(pProxyDevUsbIp);
+ if (!pUrbUsbIp)
+ return VERR_NO_MEMORY;
+
+ pUrbUsbIp->fCancelled = false;
+ pUrbUsbIp->pVUsbUrb = pUrb;
+ pUrb->Dev.pvPrivate = pUrbUsbIp;
+
+ int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists);
+ AssertRC(rc);
+ RTListAppend(&pProxyDevUsbIp->ListUrbsToQueue, &pUrbUsbIp->NodeList);
+ RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists);
+
+ return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_QUEUE);
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbReap}
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyUsbIpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ PUSBPROXYURBUSBIP pUrbUsbIp = NULL;
+ PVUSBURB pUrb = NULL;
+ int rc = VINF_SUCCESS;
+
+ /* Queue new URBs first. */
+ rc = usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp);
+ AssertRC(rc);
+
+ /* Any URBs pending delivery? */
+ if (!RTListIsEmpty(&pProxyDevUsbIp->ListUrbsLanded))
+ pUrbUsbIp = RTListGetFirst(&pProxyDevUsbIp->ListUrbsLanded, USBPROXYURBUSBIP, NodeList);
+ else
+ pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, 0, true /*fPollWakePipe*/, cMillies);
+
+ if (pUrbUsbIp)
+ {
+ pUrb = pUrbUsbIp->pVUsbUrb;
+ pUrb->enmStatus = pUrbUsbIp->enmStatus;
+
+ /* unlink from the pending delivery list */
+ usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp);
+ usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp);
+ }
+
+ return pUrb;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbCancel}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ LogFlowFunc(("pUrb=%p\n", pUrb));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ PUSBPROXYURBUSBIP pUrbUsbIp = (PUSBPROXYURBUSBIP)pUrb->Dev.pvPrivate;
+ UsbIpReqUnlink ReqUnlink;
+
+ RT_ZERO(ReqUnlink);
+
+ uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp);
+ ReqUnlink.Hdr.u32ReqRet = USBIP_CMD_UNLINK;
+ ReqUnlink.Hdr.u32SeqNum = u32SeqNum;
+ ReqUnlink.Hdr.u32DevId = pProxyDevUsbIp->u32DevId;
+ ReqUnlink.Hdr.u32Direction = USBIP_DIR_OUT;
+ ReqUnlink.Hdr.u32Endpoint = pUrb->EndPt;
+ ReqUnlink.u32SeqNum = pUrbUsbIp->u32SeqNumUrb;
+
+ usbProxyUsbIpReqUnlinkH2N(&ReqUnlink);
+ int rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqUnlink, sizeof(ReqUnlink));
+ if (RT_SUCCESS(rc))
+ {
+ pUrbUsbIp->u32SeqNumUrbUnlink = u32SeqNum;
+ pUrbUsbIp->fCancelled = true;
+ }
+
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnWakeup}
+ */
+static DECLCALLBACK(int) usbProxyUsbIpWakeup(PUSBPROXYDEV pProxyDev)
+{
+ LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP);
+ return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_EXTERNAL);
+}
+
+
+/**
+ * The USB/IP USB Proxy Backend operations.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceUsbIp =
+{
+ /* pszName */
+ "usbip",
+ /* cbBackend */
+ sizeof(USBPROXYDEVUSBIP),
+ usbProxyUsbIpOpen,
+ NULL,
+ usbProxyUsbIpClose,
+ usbProxyUsbIpReset,
+ usbProxyUsbIpSetConfig,
+ usbProxyUsbIpClaimInterface,
+ usbProxyUsbIpReleaseInterface,
+ usbProxyUsbIpSetInterface,
+ usbProxyUsbIpClearHaltedEp,
+ usbProxyUsbIpUrbQueue,
+ usbProxyUsbIpUrbCancel,
+ usbProxyUsbIpUrbReap,
+ usbProxyUsbIpWakeup,
+ 0
+};
diff --git a/src/VBox/Devices/USB/vrdp/Makefile.kup b/src/VBox/Devices/USB/vrdp/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/vrdp/Makefile.kup
diff --git a/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp
new file mode 100644
index 00000000..b1969172
--- /dev/null
+++ b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp
@@ -0,0 +1,322 @@
+/* $Id: USBProxyDevice-vrdp.cpp $ */
+/** @file
+ * USB device proxy - the VRDP backend, calls the RemoteUSBBackend methods.
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+
+#include <VBox/log.h>
+#include <VBox/err.h>
+#include <VBox/vrdpusb.h>
+#include <VBox/vmm/pdm.h>
+
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/string.h>
+#include <iprt/uuid.h>
+
+#include "../USBProxyDevice.h"
+
+/**
+ * Backend data for the VRDP USB Proxy device backend.
+ */
+typedef struct USBPROXYDEVVRDP
+{
+ REMOTEUSBCALLBACK *pCallback;
+ PREMOTEUSBDEVICE pDevice;
+} USBPROXYDEVVRDP, *PUSBPROXYDEVVRDP;
+
+
+/*
+ * The USB proxy device functions.
+ */
+
+static DECLCALLBACK(int) usbProxyVrdpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ LogFlow(("usbProxyVrdpOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+ PPDMUSBINS pUsbIns = pProxyDev->pUsbIns;
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ PCFGMNODE pCfgBackend = pHlp->pfnCFGMGetChild(pUsbIns->pCfg, "BackendCfg");
+ AssertPtrReturn(pCfgBackend, VERR_NOT_FOUND);
+
+ uint32_t idClient = 0;
+ int rc = pHlp->pfnCFGMQueryU32(pCfgBackend, "ClientId", &idClient);
+ AssertRCReturn(rc, rc);
+
+ RTUUID UuidDev;
+ char *pszUuid = NULL;
+
+ rc = pHlp->pfnCFGMQueryStringAlloc(pUsbIns->pCfg, "UUID", &pszUuid);
+ AssertRCReturn(rc, rc);
+
+ rc = RTUuidFromStr(&UuidDev, pszUuid);
+ pHlp->pfnMMHeapFree(pUsbIns, pszUuid);
+ AssertMsgRCReturn(rc, ("Failed to convert UUID from string! rc=%Rrc\n", rc), rc);
+
+ if (strncmp (pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) == 0)
+ {
+ RTUUID UuidRemoteUsbIf;
+ rc = RTUuidFromStr(&UuidRemoteUsbIf, REMOTEUSBIF_OID); AssertRC(rc);
+
+ PREMOTEUSBIF pRemoteUsbIf = (PREMOTEUSBIF)PDMUsbHlpQueryGenericUserObject(pUsbIns, &UuidRemoteUsbIf);
+ AssertPtrReturn(pRemoteUsbIf, VERR_INVALID_PARAMETER);
+
+ REMOTEUSBCALLBACK *pCallback = pRemoteUsbIf->pfnQueryRemoteUsbBackend(pRemoteUsbIf->pvUser, &UuidDev, idClient);
+ AssertPtrReturn(pCallback, VERR_INVALID_PARAMETER);
+
+ PREMOTEUSBDEVICE pDevice = NULL;
+ rc = pCallback->pfnOpen(pCallback->pInstance, pszAddress, strlen (pszAddress) + 1, &pDevice);
+ if (RT_SUCCESS(rc))
+ {
+ pDevVrdp->pCallback = pCallback;
+ pDevVrdp->pDevice = pDevice;
+ pProxyDev->iActiveCfg = 1; /** @todo that may not be always true. */
+ pProxyDev->cIgnoreSetConfigs = 1;
+ return VINF_SUCCESS;
+ }
+ }
+ else
+ {
+ AssertFailed();
+ rc = VERR_INVALID_PARAMETER;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(void) usbProxyVrdpClose(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow(("usbProxyVrdpClose: pProxyDev = %p\n", pProxyDev));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ pDevVrdp->pCallback->pfnClose (pDevVrdp->pDevice);
+}
+
+static DECLCALLBACK(int) usbProxyVrdpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ RT_NOREF(fResetOnLinux);
+ LogFlow(("usbProxyVrdpReset: pProxyDev = %p\n", pProxyDev));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnReset (pDevVrdp->pDevice);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ pProxyDev->iActiveCfg = -1;
+ pProxyDev->cIgnoreSetConfigs = 2;
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpSetConfig(PUSBPROXYDEV pProxyDev, int cfg)
+{
+ LogFlow(("usbProxyVrdpSetConfig: pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, cfg));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnSetConfig (pDevVrdp->pDevice, (uint8_t)cfg);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpClaimInterface(PUSBPROXYDEV pProxyDev, int ifnum)
+{
+ LogFlow(("usbProxyVrdpClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnClaimInterface (pDevVrdp->pDevice, (uint8_t)ifnum);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpReleaseInterface(PUSBPROXYDEV pProxyDev, int ifnum)
+{
+ LogFlow(("usbProxyVrdpReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnReleaseInterface (pDevVrdp->pDevice, (uint8_t)ifnum);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpSetInterface(PUSBPROXYDEV pProxyDev, int ifnum, int setting)
+{
+ LogFlow(("usbProxyVrdpSetInterface: pProxyDev=%p ifnum=%#x setting=%#x\n", pProxyDev, ifnum, setting));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnInterfaceSetting (pDevVrdp->pDevice, (uint8_t)ifnum, (uint8_t)setting);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep)
+{
+ LogFlow(("usbProxyVrdpClearHaltedEp: pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, ep));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ int rc = pDevVrdp->pCallback->pfnClearHaltedEP (pDevVrdp->pDevice, (uint8_t)ep);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ LogFlow(("usbProxyVrdpUrbQueue: pUrb=%p\n", pUrb));
+
+ /** @todo implement isochronous transfers for USB over VRDP. */
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ Log(("usbproxy: isochronous transfers aren't implemented yet.\n"));
+ return VERR_NOT_IMPLEMENTED;
+ }
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+ int rc = pDevVrdp->pCallback->pfnQueueURB (pDevVrdp->pDevice, pUrb->enmType, pUrb->EndPt, pUrb->enmDir, pUrb->cbData,
+ pUrb->abData, pUrb, (PREMOTEUSBQURB *)&pUrb->Dev.pvPrivate);
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return rc;
+}
+
+static DECLCALLBACK(PVUSBURB) usbProxyVrdpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ LogFlow(("usbProxyVrdpUrbReap: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+
+ PVUSBURB pUrb = NULL;
+ uint32_t cbData = 0;
+ uint32_t u32Err = VUSBSTATUS_OK;
+
+ int rc = pDevVrdp->pCallback->pfnReapURB (pDevVrdp->pDevice, cMillies, (void **)&pUrb, &cbData, &u32Err);
+
+ LogFlow(("usbProxyVrdpUrbReap: rc = %Rrc, pUrb = %p\n", rc, pUrb));
+
+ if (RT_SUCCESS(rc) && pUrb)
+ {
+ pUrb->enmStatus = (VUSBSTATUS)u32Err;
+ pUrb->cbData = cbData;
+ pUrb->Dev.pvPrivate = NULL;
+ }
+
+ if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED)
+ {
+ Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice));
+ pProxyDev->fDetached = true;
+ }
+
+ return pUrb;
+}
+
+static DECLCALLBACK(int) usbProxyVrdpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ LogFlow(("usbProxyVrdpUrbCancel: pUrb=%p\n", pUrb));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+ pDevVrdp->pCallback->pfnCancelURB (pDevVrdp->pDevice, (PREMOTEUSBQURB)pUrb->Dev.pvPrivate);
+ return VINF_SUCCESS; /** @todo Enhance remote interface to pass a status code. */
+}
+
+static DECLCALLBACK(int) usbProxyVrdpWakeup(PUSBPROXYDEV pProxyDev)
+{
+ LogFlow(("usbProxyVrdpWakeup: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
+
+ PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP);
+ return pDevVrdp->pCallback->pfnWakeup (pDevVrdp->pDevice);
+}
+
+/**
+ * The VRDP USB Proxy Backend operations.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceVRDP =
+{
+ /* pszName */
+ "vrdp",
+ /* cbBackend */
+ sizeof(USBPROXYDEVVRDP),
+ usbProxyVrdpOpen,
+ NULL,
+ usbProxyVrdpClose,
+ usbProxyVrdpReset,
+ usbProxyVrdpSetConfig,
+ usbProxyVrdpClaimInterface,
+ usbProxyVrdpReleaseInterface,
+ usbProxyVrdpSetInterface,
+ usbProxyVrdpClearHaltedEp,
+ usbProxyVrdpUrbQueue,
+ usbProxyVrdpUrbCancel,
+ usbProxyVrdpUrbReap,
+ usbProxyVrdpWakeup,
+ 0
+};
+
diff --git a/src/VBox/Devices/USB/win/Makefile.kup b/src/VBox/Devices/USB/win/Makefile.kup
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/src/VBox/Devices/USB/win/Makefile.kup
diff --git a/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp
new file mode 100644
index 00000000..26c28f9b
--- /dev/null
+++ b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp
@@ -0,0 +1,813 @@
+/* $Id: USBProxyDevice-win.cpp $ */
+/** @file
+ * USBPROXY - USB proxy, Win32 backend
+ */
+
+/*
+ * Copyright (C) 2006-2022 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#include <iprt/win/windows.h>
+
+#include <VBox/vmm/pdm.h>
+#include <VBox/err.h>
+#include <VBox/usb.h>
+#include <VBox/log.h>
+#include <iprt/assert.h>
+#include <iprt/alloc.h>
+#include <iprt/err.h>
+#include <iprt/string.h>
+#include <iprt/thread.h>
+#include <iprt/asm.h>
+#include "../USBProxyDevice.h"
+#include <VBox/usblib.h>
+
+
+/*********************************************************************************************************************************
+* Structures and Typedefs *
+*********************************************************************************************************************************/
+typedef struct _QUEUED_URB
+{
+ PVUSBURB urb;
+
+ USBSUP_URB urbwin;
+ OVERLAPPED overlapped;
+ DWORD cbReturned;
+ bool fCancelled;
+} QUEUED_URB, *PQUEUED_URB;
+
+typedef struct
+{
+ /* Critical section to protect this structure. */
+ RTCRITSECT CritSect;
+ HANDLE hDev;
+ uint8_t bInterfaceNumber;
+ bool fClaimed;
+ /** Set if reaper should exit ASAP. */
+ bool fWakeUpNow;
+ /** The allocated size of paHandles and paQueuedUrbs. */
+ unsigned cAllocatedUrbs;
+ /** The number of URBs in the array. */
+ unsigned cQueuedUrbs;
+ /** Array of pointers to the in-flight URB structures. */
+ PQUEUED_URB *paQueuedUrbs;
+ /** Array of handles, this is parallel to paQueuedUrbs. */
+ PHANDLE paHandles;
+ /* Event sempahore to wakeup the reaper thead. */
+ HANDLE hEventWakeup;
+ /** Number of queued URBs waiting to get into the handle list. */
+ unsigned cPendingUrbs;
+ /** Array of pending URBs. */
+ PQUEUED_URB aPendingUrbs[64];
+} PRIV_USBW32, *PPRIV_USBW32;
+
+/* All functions are returning 1 on success, 0 on error */
+
+
+/*********************************************************************************************************************************
+* Internal Functions *
+*********************************************************************************************************************************/
+static int usbProxyWinSetInterface(PUSBPROXYDEV p, int iIf, int setting);
+
+/**
+ * Converts the given Windows error code to VBox handling unplugged devices.
+ *
+ * @returns VBox status code.
+ * @param pProxDev The USB proxy device instance.
+ * @param dwErr Windows error code.
+ */
+static int usbProxyWinHandleUnpluggedDevice(PUSBPROXYDEV pProxyDev, DWORD dwErr)
+{
+#ifdef LOG_ENABLED
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+#endif
+
+ if ( dwErr == ERROR_INVALID_HANDLE_STATE
+ || dwErr == ERROR_BAD_COMMAND)
+ {
+ Log(("usbproxy: device %x unplugged!! (usbProxyWinHandleUnpluggedDevice)\n", pPriv->hDev));
+ pProxyDev->fDetached = true;
+ }
+ else
+ AssertMsgFailed(("lasterr=%d\n", dwErr));
+ return RTErrConvertFromWin32(dwErr);
+}
+
+/**
+ * Open a USB device and create a backend instance for it.
+ *
+ * @returns VBox status code.
+ */
+static DECLCALLBACK(int) usbProxyWinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+
+ int rc = VINF_SUCCESS;
+ pPriv->cAllocatedUrbs = 32;
+ pPriv->paHandles = (PHANDLE)RTMemAllocZ(sizeof(pPriv->paHandles[0]) * pPriv->cAllocatedUrbs);
+ pPriv->paQueuedUrbs = (PQUEUED_URB *)RTMemAllocZ(sizeof(pPriv->paQueuedUrbs[0]) * pPriv->cAllocatedUrbs);
+ if ( pPriv->paQueuedUrbs
+ && pPriv->paHandles)
+ {
+ /*
+ * Open the device.
+ */
+ pPriv->hDev = CreateFile(pszAddress,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_WRITE | FILE_SHARE_READ,
+ NULL, // no SECURITY_ATTRIBUTES structure
+ OPEN_EXISTING, // No special create flags
+ FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, // overlapped IO
+ NULL); // No template file
+ if (pPriv->hDev != INVALID_HANDLE_VALUE)
+ {
+ Log(("usbProxyWinOpen: hDev=%p\n", pPriv->hDev));
+
+ /*
+ * Check the version
+ */
+ USBSUP_VERSION version = {0};
+ DWORD cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_GET_VERSION, NULL, 0, &version, sizeof(version), &cbReturned, NULL))
+ {
+ if ( version.u32Major == USBDRV_MAJOR_VERSION
+#if USBDRV_MINOR_VERSION != 0
+ && version.u32Minor >= USBDRV_MINOR_VERSION
+#endif
+ )
+ {
+ USBSUP_CLAIMDEV in;
+ in.bInterfaceNumber = 0;
+
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLAIM_DEVICE, &in, sizeof(in), &in, sizeof(in), &cbReturned, NULL))
+ {
+ if (in.fClaimed)
+ {
+ pPriv->fClaimed = true;
+#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */
+ pProxyDev->iActiveCfg = 1;
+ pProxyDev->cIgnoreSetConfigs = 1;
+#endif
+
+ rc = RTCritSectInit(&pPriv->CritSect);
+ AssertRC(rc);
+ pPriv->hEventWakeup = CreateEvent(NULL, FALSE, FALSE, NULL);
+ Assert(pPriv->hEventWakeup);
+
+ pPriv->paHandles[0] = pPriv->hEventWakeup;
+
+ return VINF_SUCCESS;
+ }
+
+ rc = VERR_GENERAL_FAILURE;
+ Log(("usbproxy: unable to claim device %x (%s)!!\n", pPriv->hDev, pszAddress));
+ }
+ }
+ else
+ {
+ rc = VERR_VERSION_MISMATCH;
+ Log(("usbproxy: Version mismatch: %d.%d != %d.%d (cur)\n",
+ version.u32Major, version.u32Minor, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION));
+ }
+ }
+
+ /* Convert last error if necessary */
+ if (RT_SUCCESS(rc))
+ {
+ DWORD dwErr = GetLastError();
+ Log(("usbproxy: last error %d\n", dwErr));
+ rc = RTErrConvertFromWin32(dwErr);
+ }
+
+ CloseHandle(pPriv->hDev);
+ pPriv->hDev = INVALID_HANDLE_VALUE;
+ }
+ else
+ {
+ Log(("usbproxy: FAILED to open '%s'! last error %d\n", pszAddress, GetLastError()));
+ rc = VERR_FILE_NOT_FOUND;
+ }
+ }
+ else
+ rc = VERR_NO_MEMORY;
+
+ RTMemFree(pPriv->paQueuedUrbs);
+ RTMemFree(pPriv->paHandles);
+ return rc;
+}
+
+/**
+ * Copy the device and free resources associated with the backend.
+ */
+static DECLCALLBACK(void) usbProxyWinClose(PUSBPROXYDEV pProxyDev)
+{
+ /* Here we just close the device and free up p->priv
+ * there is no need to do anything like cancel outstanding requests
+ * that will have been done already
+ */
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ Assert(pPriv);
+ if (!pPriv)
+ return;
+ Log(("usbProxyWinClose: %p\n", pPriv->hDev));
+
+ if (pPriv->hDev != INVALID_HANDLE_VALUE)
+ {
+ Assert(pPriv->fClaimed);
+
+ USBSUP_RELEASEDEV in;
+ DWORD cbReturned = 0;
+ in.bInterfaceNumber = pPriv->bInterfaceNumber;
+ if (!DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RELEASE_DEVICE, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ {
+ Log(("usbproxy: usbProxyWinClose: DeviceIoControl %#x failed with %#x!!\n", pPriv->hDev, GetLastError()));
+ }
+ if (!CloseHandle(pPriv->hDev))
+ AssertLogRelMsgFailed(("usbproxy: usbProxyWinClose: CloseHandle %#x failed with %#x!!\n", pPriv->hDev, GetLastError()));
+ pPriv->hDev = INVALID_HANDLE_VALUE;
+ }
+
+ CloseHandle(pPriv->hEventWakeup);
+ RTCritSectDelete(&pPriv->CritSect);
+
+ RTMemFree(pPriv->paQueuedUrbs);
+ RTMemFree(pPriv->paHandles);
+}
+
+
+static DECLCALLBACK(int) usbProxyWinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux)
+{
+ RT_NOREF(fResetOnLinux);
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ DWORD cbReturned;
+ int rc;
+
+ Assert(pPriv);
+
+ Log(("usbproxy: Reset %x\n", pPriv->hDev));
+
+ /* Here we just need to assert reset signalling on the USB device */
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RESET, NULL, 0, NULL, 0, &cbReturned, NULL))
+ {
+#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */
+ pProxyDev->iActiveCfg = 1;
+ pProxyDev->cIgnoreSetConfigs = 2;
+#else
+ pProxyDev->iActiveCfg = -1;
+ pProxyDev->cIgnoreSetConfigs = 0;
+#endif
+ return VINF_SUCCESS;
+ }
+
+ rc = GetLastError();
+ if (rc == ERROR_DEVICE_REMOVED)
+ {
+ Log(("usbproxy: device %p unplugged!! (usbProxyWinReset)\n", pPriv->hDev));
+ pProxyDev->fDetached = true;
+ }
+ return RTErrConvertFromWin32(rc);
+}
+
+static DECLCALLBACK(int) usbProxyWinSetConfig(PUSBPROXYDEV pProxyDev, int cfg)
+{
+ /* Send a SET_CONFIGURATION command to the device. We don't do this
+ * as a normal control message, because the OS might not want to
+ * be left out of the loop on such a thing.
+ *
+ * It would be OK to send a SET_CONFIGURATION control URB at this
+ * point but it has to be synchronous.
+ */
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ USBSUP_SET_CONFIG in;
+ DWORD cbReturned;
+
+ Assert(pPriv);
+
+ Log(("usbproxy: Set config of %p to %d\n", pPriv->hDev, cfg));
+ in.bConfigurationValue = cfg;
+
+ /* Here we just need to assert reset signalling on the USB device */
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SET_CONFIG, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ return VINF_SUCCESS;
+
+ return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError());
+}
+
+static DECLCALLBACK(int) usbProxyWinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ /* Called just before we use an interface. Needed on Linux to claim
+ * the interface from the OS, since even when proxying the host OS
+ * might want to allow other programs to use the unused interfaces.
+ * Not relevant for Windows.
+ */
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+
+ pPriv->bInterfaceNumber = iIf;
+
+ Assert(pPriv);
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) usbProxyWinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
+{
+ RT_NOREF(pProxyDev, iIf);
+ /* The opposite of claim_interface. */
+ return VINF_SUCCESS;
+}
+
+static DECLCALLBACK(int) usbProxyWinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting)
+{
+ /* Select an alternate setting for an interface, the same applies
+ * here as for set_config, you may convert this in to a control
+ * message if you want but it must be synchronous
+ */
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ USBSUP_SELECT_INTERFACE in;
+ DWORD cbReturned;
+
+ Assert(pPriv);
+
+ Log(("usbproxy: Select interface of %x to %d/%d\n", pPriv->hDev, iIf, setting));
+ in.bInterfaceNumber = iIf;
+ in.bAlternateSetting = setting;
+
+ /* Here we just need to assert reset signalling on the USB device */
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SELECT_INTERFACE, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ return VINF_SUCCESS;
+
+ return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError());
+}
+
+/**
+ * Clears the halted endpoint 'ep'.
+ */
+static DECLCALLBACK(int) usbProxyWinClearHaltedEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ USBSUP_CLEAR_ENDPOINT in;
+ DWORD cbReturned;
+
+ Assert(pPriv);
+
+ Log(("usbproxy: Clear endpoint %d of %x\n", ep, pPriv->hDev));
+ in.bEndpoint = ep;
+
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLEAR_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ return VINF_SUCCESS;
+
+ return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError());
+}
+
+/**
+ * Aborts a pipe/endpoint (cancels all outstanding URBs on the endpoint).
+ */
+static int usbProxyWinAbortEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ USBSUP_CLEAR_ENDPOINT in;
+ DWORD cbReturned;
+
+ Assert(pPriv);
+
+ Log(("usbproxy: Abort endpoint %d of %x\n", ep, pPriv->hDev));
+ in.bEndpoint = ep;
+
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ return VINF_SUCCESS;
+
+ return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError());
+}
+
+/**
+ * @interface_method_impl{USBPROXYBACK,pfnUrbQueue}
+ */
+static DECLCALLBACK(int) usbProxyWinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ Assert(pPriv);
+
+ /* Don't even bother if we can't wait for that many objects. */
+ if (pPriv->cPendingUrbs + pPriv->cQueuedUrbs >= (MAXIMUM_WAIT_OBJECTS - 1))
+ return VERR_OUT_OF_RESOURCES;
+ if (pPriv->cPendingUrbs >= RT_ELEMENTS(pPriv->aPendingUrbs))
+ return VERR_OUT_OF_RESOURCES;
+
+ /*
+ * Allocate and initialize a URB queue structure.
+ */
+ /** @todo pool these */
+ PQUEUED_URB pQUrbWin = (PQUEUED_URB)RTMemAllocZ(sizeof(QUEUED_URB));
+ if (!pQUrbWin)
+ return VERR_NO_MEMORY;
+
+ switch (pUrb->enmType)
+ {
+ case VUSBXFERTYPE_CTRL: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_CTRL; break; /* you won't ever see these */
+ case VUSBXFERTYPE_ISOC: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_ISOC;
+ pQUrbWin->urbwin.numIsoPkts = pUrb->cIsocPkts;
+ for (unsigned i = 0; i < pUrb->cIsocPkts; ++i)
+ {
+ pQUrbWin->urbwin.aIsoPkts[i].cb = pUrb->aIsocPkts[i].cb;
+ pQUrbWin->urbwin.aIsoPkts[i].off = pUrb->aIsocPkts[i].off;
+ pQUrbWin->urbwin.aIsoPkts[i].stat = USBSUP_XFER_OK;
+ }
+ break;
+ case VUSBXFERTYPE_BULK: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_BULK; break;
+ case VUSBXFERTYPE_INTR: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_INTR; break;
+ case VUSBXFERTYPE_MSG: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_MSG; break;
+ default:
+ AssertMsgFailed(("Invalid type %d\n", pUrb->enmType));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ switch (pUrb->enmDir)
+ {
+ case VUSBDIRECTION_SETUP:
+ AssertFailed();
+ pQUrbWin->urbwin.dir = USBSUP_DIRECTION_SETUP;
+ break;
+ case VUSBDIRECTION_IN:
+ pQUrbWin->urbwin.dir = USBSUP_DIRECTION_IN;
+ break;
+ case VUSBDIRECTION_OUT:
+ pQUrbWin->urbwin.dir = USBSUP_DIRECTION_OUT;
+ break;
+ default:
+ AssertMsgFailed(("Invalid direction %d\n", pUrb->enmDir));
+ return VERR_INVALID_PARAMETER;
+ }
+
+ Log(("usbproxy: Queue URB %p ep=%d cbData=%d abData=%p cIsocPkts=%d\n", pUrb, pUrb->EndPt, pUrb->cbData, pUrb->abData, pUrb->cIsocPkts));
+
+ pQUrbWin->urb = pUrb;
+ pQUrbWin->urbwin.ep = pUrb->EndPt;
+ pQUrbWin->urbwin.len = pUrb->cbData;
+ pQUrbWin->urbwin.buf = pUrb->abData;
+ pQUrbWin->urbwin.error = USBSUP_XFER_OK;
+ pQUrbWin->urbwin.flags = USBSUP_FLAG_NONE;
+ if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk)
+ pQUrbWin->urbwin.flags = USBSUP_FLAG_SHORT_OK;
+
+ int rc = VINF_SUCCESS;
+ pQUrbWin->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE)
+ {
+ pUrb->Dev.pvPrivate = pQUrbWin;
+
+ if ( DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_SEND_URB,
+ &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin),
+ &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin),
+ &pQUrbWin->cbReturned, &pQUrbWin->overlapped)
+ || GetLastError() == ERROR_IO_PENDING)
+ {
+ /* insert into the queue */
+ RTCritSectEnter(&pPriv->CritSect);
+ unsigned j = pPriv->cPendingUrbs;
+ Assert(j < RT_ELEMENTS(pPriv->aPendingUrbs));
+ pPriv->aPendingUrbs[j] = pQUrbWin;
+ pPriv->cPendingUrbs++;
+ RTCritSectLeave(&pPriv->CritSect);
+ SetEvent(pPriv->hEventWakeup);
+ return VINF_SUCCESS;
+ }
+ else
+ {
+ DWORD dwErr = GetLastError();
+ if ( dwErr == ERROR_INVALID_HANDLE_STATE
+ || dwErr == ERROR_BAD_COMMAND)
+ {
+ Log(("usbproxy: device %p unplugged!! (usbProxyWinUrbQueue)\n", pPriv->hDev));
+ pProxyDev->fDetached = true;
+ }
+ else
+ AssertMsgFailed(("dwErr=%X urbwin.error=%d (submit urb)\n", dwErr, pQUrbWin->urbwin.error));
+ rc = RTErrConvertFromWin32(dwErr);
+ CloseHandle(pQUrbWin->overlapped.hEvent);
+ pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE;
+ }
+ }
+#ifdef DEBUG_misha
+ else
+ {
+ AssertMsgFailed(("FAILED!!, hEvent(0x%p)\n", pQUrbWin->overlapped.hEvent));
+ rc = VERR_NO_MEMORY;
+ }
+#endif
+
+ Assert(pQUrbWin->overlapped.hEvent == INVALID_HANDLE_VALUE);
+ RTMemFree(pQUrbWin);
+ return rc;
+}
+
+/**
+ * Convert Windows proxy URB status to VUSB status.
+ *
+ * @returns VUSB status constant.
+ * @param win_status Windows USB proxy status constant.
+ */
+static VUSBSTATUS usbProxyWinStatusToVUsbStatus(USBSUP_ERROR win_status)
+{
+ VUSBSTATUS vusb_status;
+
+ switch (win_status)
+ {
+ case USBSUP_XFER_OK: vusb_status = VUSBSTATUS_OK; break;
+ case USBSUP_XFER_STALL: vusb_status = VUSBSTATUS_STALL; break;
+ case USBSUP_XFER_DNR: vusb_status = VUSBSTATUS_DNR; break;
+ case USBSUP_XFER_CRC: vusb_status = VUSBSTATUS_CRC; break;
+ case USBSUP_XFER_NAC: vusb_status = VUSBSTATUS_NOT_ACCESSED; break;
+ case USBSUP_XFER_UNDERRUN: vusb_status = VUSBSTATUS_DATA_UNDERRUN; break;
+ case USBSUP_XFER_OVERRUN: vusb_status = VUSBSTATUS_DATA_OVERRUN; break;
+ default:
+ AssertMsgFailed(("USB: Invalid error %d\n", win_status));
+ vusb_status = VUSBSTATUS_DNR;
+ break;
+ }
+ return vusb_status;
+}
+
+/**
+ * Reap URBs in-flight on a device.
+ *
+ * @returns Pointer to a completed URB.
+ * @returns NULL if no URB was completed.
+ * @param pProxyDev The device.
+ * @param cMillies Number of milliseconds to wait. Use 0 to not
+ * wait at all.
+ */
+static DECLCALLBACK(PVUSBURB) usbProxyWinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ AssertReturn(pPriv, NULL);
+
+ /*
+ * There are some unnecessary calls, just return immediately or
+ * WaitForMultipleObjects will fail.
+ */
+ if ( pPriv->cQueuedUrbs <= 0
+ && pPriv->cPendingUrbs == 0)
+ {
+ Log(("usbproxy: Nothing pending\n"));
+ if ( cMillies != 0
+ && pPriv->cPendingUrbs == 0)
+ {
+ /* Wait for the wakeup call. */
+ Log(("usbproxy: Waiting for wakeup call\n"));
+ DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies;
+ DWORD rc = WaitForMultipleObjects(1, &pPriv->hEventWakeup, FALSE, cMilliesWait);
+ Log(("usbproxy: Initial wait rc=%X\n", rc));
+ if (rc != WAIT_OBJECT_0) {
+ Log(("usbproxy: Initial wait failed, rc=%X\n", rc));
+ return NULL;
+ }
+ }
+ return NULL;
+ }
+
+again:
+ /* Check for pending URBs. */
+ Log(("usbproxy: %u pending URBs\n", pPriv->cPendingUrbs));
+ if (pPriv->cPendingUrbs)
+ {
+ RTCritSectEnter(&pPriv->CritSect);
+
+ /* Ensure we've got sufficient space in the arrays. */
+ if (pPriv->cQueuedUrbs + pPriv->cPendingUrbs + 1 > pPriv->cAllocatedUrbs)
+ {
+ unsigned cNewMax = pPriv->cAllocatedUrbs + pPriv->cPendingUrbs + 1;
+ void *pv = RTMemRealloc(pPriv->paHandles, sizeof(pPriv->paHandles[0]) * (cNewMax + 1)); /* One extra for the wakeup event. */
+ if (!pv)
+ {
+ AssertMsgFailed(("RTMemRealloc failed for paHandles[%d]", cNewMax));
+ //break;
+ }
+ pPriv->paHandles = (PHANDLE)pv;
+
+ pv = RTMemRealloc(pPriv->paQueuedUrbs, sizeof(pPriv->paQueuedUrbs[0]) * cNewMax);
+ if (!pv)
+ {
+ AssertMsgFailed(("RTMemRealloc failed for paQueuedUrbs[%d]", cNewMax));
+ //break;
+ }
+ pPriv->paQueuedUrbs = (PQUEUED_URB *)pv;
+ pPriv->cAllocatedUrbs = cNewMax;
+ }
+
+ /* Copy the pending URBs over. */
+ for (unsigned i = 0; i < pPriv->cPendingUrbs; i++)
+ {
+ pPriv->paHandles[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i]->overlapped.hEvent;
+ pPriv->paQueuedUrbs[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i];
+ }
+ pPriv->cQueuedUrbs += pPriv->cPendingUrbs;
+ pPriv->cPendingUrbs = 0;
+ pPriv->paHandles[pPriv->cQueuedUrbs] = pPriv->hEventWakeup;
+ pPriv->paHandles[pPriv->cQueuedUrbs + 1] = INVALID_HANDLE_VALUE;
+
+ RTCritSectLeave(&pPriv->CritSect);
+ }
+
+ /*
+ * Wait/poll.
+ *
+ * ASSUMPTION: Multiple usbProxyWinUrbReap calls can not be run concurrently
+ * with each other so racing the cQueuedUrbs access/modification can not occur.
+ *
+ * However, usbProxyWinUrbReap can be run concurrently with usbProxyWinUrbQueue
+ * and pPriv->paHandles access/realloc must be synchronized.
+ *
+ * NB: Due to the design of Windows overlapped I/O, DeviceIoControl calls to submit
+ * URBs use individual event objects. When a new URB is submitted, we have to add its
+ * event object to the list of objects that WaitForMultipleObjects is waiting on. Thus
+ * hEventWakeup has dual purpose, serving to handle proxy wakeup calls meant to abort
+ * reaper waits, but also waking up the reaper after every URB submit so that the newly
+ * submitted URB can be added to the list of waiters.
+ */
+ unsigned cQueuedUrbs = ASMAtomicReadU32((volatile uint32_t *)&pPriv->cQueuedUrbs);
+ DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies;
+ PVUSBURB pUrb = NULL;
+ DWORD rc = WaitForMultipleObjects(cQueuedUrbs + 1, pPriv->paHandles, FALSE, cMilliesWait);
+ Log(("usbproxy: Wait (%d milliseconds) returned with rc=%X\n", cMilliesWait, rc));
+
+ /* If the wakeup event fired return immediately. */
+ if (rc == WAIT_OBJECT_0 + cQueuedUrbs)
+ {
+ /* Get outta here flag set? If so, bail now. */
+ if (ASMAtomicXchgBool(&pPriv->fWakeUpNow, false))
+ {
+ Log(("usbproxy: Reaper woken up, returning NULL\n"));
+ return NULL;
+ }
+
+ /* A new URBs was queued through usbProxyWinUrbQueue() and needs to be
+ * added to the wait list. Go again.
+ */
+ Log(("usbproxy: Reaper woken up after queuing new URB, go again.\n"));
+ goto again;
+ }
+
+ AssertCompile(WAIT_OBJECT_0 == 0);
+ if (/*rc >= WAIT_OBJECT_0 && */ rc < WAIT_OBJECT_0 + cQueuedUrbs)
+ {
+ RTCritSectEnter(&pPriv->CritSect);
+ unsigned iUrb = rc - WAIT_OBJECT_0;
+ PQUEUED_URB pQUrbWin = pPriv->paQueuedUrbs[iUrb];
+ pUrb = pQUrbWin->urb;
+
+ /*
+ * Remove it from the arrays.
+ */
+ cQueuedUrbs = --pPriv->cQueuedUrbs;
+ if (cQueuedUrbs != iUrb)
+ {
+ /* Move the array forward */
+ for (unsigned i=iUrb;i<cQueuedUrbs;i++)
+ {
+ pPriv->paHandles[i] = pPriv->paHandles[i+1];
+ pPriv->paQueuedUrbs[i] = pPriv->paQueuedUrbs[i+1];
+ }
+ }
+ pPriv->paHandles[cQueuedUrbs] = pPriv->hEventWakeup;
+ pPriv->paHandles[cQueuedUrbs + 1] = INVALID_HANDLE_VALUE;
+ pPriv->paQueuedUrbs[cQueuedUrbs] = NULL;
+ RTCritSectLeave(&pPriv->CritSect);
+ Assert(cQueuedUrbs == pPriv->cQueuedUrbs);
+
+ /*
+ * Update the urb.
+ */
+ pUrb->enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.error);
+ pUrb->cbData = (uint32_t)pQUrbWin->urbwin.len;
+ if (pUrb->enmType == VUSBXFERTYPE_ISOC)
+ {
+ for (unsigned i = 0; i < pUrb->cIsocPkts; ++i)
+ {
+ /* NB: Windows won't change the packet offsets, but the packets may
+ * be only partially filled or completely empty.
+ */
+ pUrb->aIsocPkts[i].enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.aIsoPkts[i].stat);
+ pUrb->aIsocPkts[i].cb = pQUrbWin->urbwin.aIsoPkts[i].cb;
+ }
+ }
+ Log(("usbproxy: pUrb=%p (#%d) ep=%d cbData=%d status=%d cIsocPkts=%d ready\n",
+ pUrb, rc - WAIT_OBJECT_0, pQUrbWin->urb->EndPt, pQUrbWin->urb->cbData, pUrb->enmStatus, pUrb->cIsocPkts));
+
+ /* free the urb queuing structure */
+ if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(pQUrbWin->overlapped.hEvent);
+ pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE;
+ }
+ RTMemFree(pQUrbWin);
+ }
+ else if ( rc == WAIT_FAILED
+ || (rc >= WAIT_ABANDONED_0 && rc < WAIT_ABANDONED_0 + cQueuedUrbs))
+ AssertMsgFailed(("USB: WaitForMultipleObjects %d objects failed with rc=%d and last error %d\n", cQueuedUrbs, rc, GetLastError()));
+
+ return pUrb;
+}
+
+
+/**
+ * Cancels an in-flight URB.
+ *
+ * The URB requires reaping, so we don't change its state.
+ *
+ * @remark There isn't a way to cancel a specific URB on Windows.
+ * on darwin. The interface only supports the aborting of
+ * all URBs pending on an endpoint. Luckily that is usually
+ * exactly what the guest wants to do.
+ */
+static DECLCALLBACK(int) usbProxyWinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+ PQUEUED_URB pQUrbWin = (PQUEUED_URB)pUrb->Dev.pvPrivate;
+ USBSUP_CLEAR_ENDPOINT in;
+ DWORD cbReturned;
+
+ AssertPtrReturn(pQUrbWin, VERR_INVALID_PARAMETER);
+
+ in.bEndpoint = pUrb->EndPt | ((pUrb->EndPt && pUrb->enmDir == VUSBDIRECTION_IN) ? 0x80 : 0);
+ Log(("usbproxy: Cancel urb %p, endpoint %x\n", pUrb, in.bEndpoint));
+
+ cbReturned = 0;
+ if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL))
+ return VINF_SUCCESS;
+
+ DWORD dwErr = GetLastError();
+ if ( dwErr == ERROR_INVALID_HANDLE_STATE
+ || dwErr == ERROR_BAD_COMMAND)
+ {
+ Log(("usbproxy: device %x unplugged!! (usbProxyWinUrbCancel)\n", pPriv->hDev));
+ pProxyDev->fDetached = true;
+ return VINF_SUCCESS; /* Fake success and deal with the unplugged device elsewhere. */
+ }
+
+ AssertMsgFailed(("lastErr=%ld\n", dwErr));
+ return RTErrConvertFromWin32(dwErr);
+}
+
+static DECLCALLBACK(int) usbProxyWinWakeup(PUSBPROXYDEV pProxyDev)
+{
+ PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32);
+
+ Log(("usbproxy: device %x wakeup\n", pPriv->hDev));
+ ASMAtomicXchgBool(&pPriv->fWakeUpNow, true);
+ SetEvent(pPriv->hEventWakeup);
+ return VINF_SUCCESS;
+}
+
+/**
+ * The Win32 USB Proxy Backend.
+ */
+extern const USBPROXYBACK g_USBProxyDeviceHost =
+{
+ /* pszName */
+ "host",
+ /* cbBackend */
+ sizeof(PRIV_USBW32),
+ usbProxyWinOpen,
+ NULL,
+ usbProxyWinClose,
+ usbProxyWinReset,
+ usbProxyWinSetConfig,
+ usbProxyWinClaimInterface,
+ usbProxyWinReleaseInterface,
+ usbProxyWinSetInterface,
+ usbProxyWinClearHaltedEndPt,
+ usbProxyWinUrbQueue,
+ usbProxyWinUrbCancel,
+ usbProxyWinUrbReap,
+ usbProxyWinWakeup,
+ 0
+};
+