diff options
Diffstat (limited to 'src/VBox/Devices/USB')
36 files changed, 26769 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/DevOHCI.cpp b/src/VBox/Devices/USB/DevOHCI.cpp new file mode 100644 index 00000000..f6643acb --- /dev/null +++ b/src/VBox/Devices/USB/DevOHCI.cpp @@ -0,0 +1,6139 @@ +/* $Id: DevOHCI.cpp $ */ +/** @file + * DevOHCI - Open Host Controller Interface for USB. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +/** @page pg_dev_ohci OHCI - Open Host Controller Interface Emulation. + * + * This component implements an OHCI USB controller. It is split roughly in + * to two main parts, the first part implements the register level + * specification of USB OHCI and the second part maintains the root hub (which + * is an integrated component of the device). + * + * The OHCI registers are used for the usual stuff like enabling and disabling + * interrupts. Since the USB time is divided in to 1ms frames and various + * interrupts may need to be triggered at frame boundary time, a timer-based + * approach was taken. Whenever the bus is enabled ohci->eof_timer will be set. + * + * The actual USB transfers are stored in main memory (along with endpoint and + * transfer descriptors). The ED's for all the control and bulk endpoints are + * found by consulting the HcControlHeadED and HcBulkHeadED registers + * respectively. Interrupt ED's are different, they are found by looking + * in the HCCA (another communication area in main memory). + * + * At the start of every frame (in function ohci_sof) we traverse all enabled + * ED lists and queue up as many transfers as possible. No attention is paid + * to control/bulk service ratios or bandwidth requirements since our USB + * could conceivably contain a dozen high speed busses so this would + * artificially limit the performance. + * + * Once we have a transfer ready to go (in function ohciR3ServiceTd) we + * allocate an URB on the stack, fill in all the relevant fields and submit + * it using the VUSBIRhSubmitUrb function. The roothub device and the virtual + * USB core code (vusb.c) coordinates everything else from this point onwards. + * + * When the URB has been successfully handed to the lower level driver, our + * prepare callback gets called and we can remove the TD from the ED transfer + * list. This stops us queueing it twice while it completes. + * bird: no, we don't remove it because that confuses the guest! (=> crashes) + * + * Completed URBs are reaped at the end of every frame (in function + * ohci_frame_boundary). Our completion routine makes use of the ED and TD + * fields in the URB to store the physical addresses of the descriptors so + * that they may be modified in the roothub callbacks. Our completion + * routine (ohciRhXferComplete) carries out a number of tasks: + * -# Retires the TD associated with the transfer, setting the + * relevant error code etc. + * -# Updates done-queue interrupt timer and potentially causes + * a writeback of the done-queue. + * -# If the transfer was device-to-host, we copy the data in to + * the host memory. + * + * As for error handling OHCI allows for 3 retries before failing a transfer, + * an error count is stored in each transfer descriptor. A halt flag is also + * stored in the transfer descriptor. That allows for ED's to be disabled + * without stopping the bus and de-queuing them. + * + * When the bus is started and stopped we call VUSBIDevPowerOn/Off() on our + * roothub to indicate it's powering up and powering down. Whenever we power + * down, the USB core makes sure to synchronously complete all outstanding + * requests so that the OHCI is never seen in an inconsistent state by the + * guest OS (Transfers are not meant to be unlinked until they've actually + * completed, but we can't do that unless we work synchronously, so we just + * have to fake it). + * bird: we do work synchronously now, anything causes guest crashes. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DEV_OHCI +#include <VBox/pci.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/mm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/asm.h> +#include <iprt/asm-math.h> +#include <iprt/semaphore.h> +#include <iprt/critsect.h> +#include <iprt/param.h> +#ifdef IN_RING3 +# include <iprt/alloca.h> +# include <iprt/mem.h> +# include <iprt/thread.h> +# include <iprt/uuid.h> +#endif +#include <VBox/vusb.h> +#include "VBoxDD.h" + + +#define VBOX_WITH_OHCI_PHYS_READ_CACHE +//#define VBOX_WITH_OHCI_PHYS_READ_STATS + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** The current saved state version. */ +#define OHCI_SAVED_STATE_VERSION 5 /* Introduced post-4.3. */ +/** The saved state with support of up to 8 ports. */ +#define OHCI_SAVED_STATE_VERSION_8PORTS 4 /* Introduced in 3.1 or so. */ + + +/** Maximum supported number of Downstream Ports on the root hub. 15 ports + * is the maximum defined by the OHCI spec. Must match the number of status + * register words to the 'opreg' array. + */ +#define OHCI_NDP_MAX 15 + +/** Default NDP, chosen to be compatible with everything. */ +#define OHCI_NDP_DEFAULT 12 + +/* Macro to query the number of currently configured ports. */ +#define OHCI_NDP_CFG(pohci) ((pohci)->RootHub.desc_a & OHCI_RHA_NDP) + +/** Pointer to OHCI device data. */ +typedef struct OHCI *POHCI; +/** Read-only pointer to the OHCI device data. */ +typedef struct OHCI const *PCOHCI; + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE +/** + * Host controller transfer descriptor data. + */ +typedef struct VUSBURBHCITDINT +{ + /** Type of TD. */ + uint32_t TdType; + /** The address of the */ + RTGCPHYS32 TdAddr; + /** A copy of the TD. */ + uint32_t TdCopy[16]; +} VUSBURBHCITDINT; + +/** + * The host controller data associated with each URB. + */ +typedef struct VUSBURBHCIINT +{ + /** The endpoint descriptor address. */ + RTGCPHYS32 EdAddr; + /** Number of Tds in the array. */ + uint32_t cTds; + /** When this URB was created. + * (Used for isochronous frames and for logging.) */ + uint32_t u32FrameNo; + /** Flag indicating that the TDs have been unlinked. */ + bool fUnlinked; +} VUSBURBHCIINT; +#endif + +/** + * An OHCI root hub port. + */ +typedef struct OHCIHUBPORT +{ + /** The port register. */ + uint32_t fReg; +#if HC_ARCH_BITS == 64 + uint32_t Alignment0; /**< Align the pointer correctly. */ +#endif + /** The device attached to the port. */ + R3PTRTYPE(PVUSBIDEVICE) pDev; +} OHCIHUBPORT; +#if HC_ARCH_BITS == 64 +AssertCompile(sizeof(OHCIHUBPORT) == 16); /* saved state */ +#endif +/** Pointer to an OHCI hub port. */ +typedef OHCIHUBPORT *POHCIHUBPORT; + +/** + * The OHCI root hub. + * + * @implements PDMIBASE + * @implements VUSBIROOTHUBPORT + * @implements PDMILEDPORTS + */ +typedef struct ohci_roothub +{ + /** Pointer to the base interface of the VUSB RootHub. */ + R3PTRTYPE(PPDMIBASE) pIBase; + /** Pointer to the connector interface of the VUSB RootHub. */ + R3PTRTYPE(PVUSBIROOTHUBCONNECTOR) pIRhConn; + /** Pointer to the device interface of the VUSB RootHub. */ + R3PTRTYPE(PVUSBIDEVICE) pIDev; + /** The base interface exposed to the roothub driver. */ + PDMIBASE IBase; + /** The roothub port interface exposed to the roothub driver. */ + VUSBIROOTHUBPORT IRhPort; + + /** The LED. */ + PDMLED Led; + /** The LED ports. */ + PDMILEDPORTS ILeds; + /** Partner of ILeds. */ + R3PTRTYPE(PPDMILEDCONNECTORS) pLedsConnector; + + uint32_t status; + uint32_t desc_a; + uint32_t desc_b; +#if HC_ARCH_BITS == 64 + uint32_t Alignment0; /**< Align aPorts on a 8 byte boundary. */ +#endif + OHCIHUBPORT aPorts[OHCI_NDP_MAX]; + R3PTRTYPE(POHCI) pOhci; +} OHCIROOTHUB; +/** Pointer to the OHCI root hub. */ +typedef OHCIROOTHUB *POHCIROOTHUB; + + +/** + * Data used for reattaching devices on a state load. + */ +typedef struct ohci_load { + /** Timer used once after state load to inform the guest about new devices. + * We do this to be sure the guest get any disconnect / reconnect on the + * same port. */ + PTMTIMERR3 pTimer; + /** Number of detached devices. */ + unsigned cDevs; + /** Array of devices which were detached. */ + PVUSBIDEVICE apDevs[OHCI_NDP_MAX]; +} OHCILOAD; +/** Pointer to an OHCILOAD structure. */ +typedef OHCILOAD *POHCILOAD; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +typedef struct OHCIPAGECACHE +{ + /** Last read physical page address. */ + RTGCPHYS GCPhysReadCacheAddr; + /** Copy of last read physical page. */ + uint8_t au8PhysReadCache[PAGE_SIZE]; +} OHCIPAGECACHE, *POHCIPAGECACHE; +#endif + +/** + * OHCI device data. + */ +typedef struct OHCI +{ + /** The PCI device. */ + PDMPCIDEV PciDev; + + /** Pointer to the device instance - R3 ptr. */ + PPDMDEVINSR3 pDevInsR3; + /** The End-Of-Frame timer - R3 Ptr. */ + PTMTIMERR3 pEndOfFrameTimerR3; + + /** Pointer to the device instance - R0 ptr */ + PPDMDEVINSR0 pDevInsR0; + /** The End-Of-Frame timer - R0 Ptr. */ + PTMTIMERR0 pEndOfFrameTimerR0; + + /** Pointer to the device instance - RC ptr. */ + PPDMDEVINSRC pDevInsRC; + /** The End-Of-Frame timer - RC Ptr. */ + PTMTIMERRC pEndOfFrameTimerRC; + + /** Start of current frame. */ + uint64_t SofTime; + /* done queue interrupt counter */ + uint32_t dqic : 3; + /** frame number overflow. */ + uint32_t fno : 1; + /** Address of the MMIO region assigned by PCI. */ + RTGCPHYS32 MMIOBase; + + /* Root hub device */ + OHCIROOTHUB RootHub; + + /* OHCI registers */ + + /** @name Control partition + * @{ */ + /** HcControl. */ + uint32_t ctl; + /** HcCommandStatus. */ + uint32_t status; + /** HcInterruptStatus. */ + uint32_t intr_status; + /** HcInterruptEnabled. */ + uint32_t intr; + /** @} */ + + /** @name Memory pointer partition + * @{ */ + /** HcHCCA. */ + uint32_t hcca; + /** HcPeriodCurrentEd. */ + uint32_t per_cur; + /** HcControlCurrentED. */ + uint32_t ctrl_cur; + /** HcControlHeadED. */ + uint32_t ctrl_head; + /** HcBlockCurrendED. */ + uint32_t bulk_cur; + /** HcBlockHeadED. */ + uint32_t bulk_head; + /** HcDoneHead. */ + uint32_t done; + /** @} */ + + /** @name Frame counter partition + * @{ */ + /** HcFmInterval.FSMPS - FSLargestDataPacket */ + uint32_t fsmps : 15; + /** HcFmInterval.FIT - FrameItervalToggle */ + uint32_t fit : 1; + /** HcFmInterval.FI - FrameInterval */ + uint32_t fi : 14; + /** HcFmRemaining.FRT - toggle bit. */ + uint32_t frt : 1; + /** HcFmNumber. + * @remark The register size is 16-bit, but for debugging and performance + * reasons we maintain a 32-bit counter. */ + uint32_t HcFmNumber; + /** HcPeriodicStart */ + uint32_t pstart; + /** @} */ + + /** The number of virtual time ticks per frame. */ + uint64_t cTicksPerFrame; + /** The number of virtual time ticks per USB bus tick. */ + uint64_t cTicksPerUsbTick; + + /** Number of in-flight TDs. */ + unsigned cInFlight; + unsigned Alignment0; /**< Align aInFlight on a 8 byte boundary. */ + /** Array of in-flight TDs. */ + struct ohci_td_in_flight + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + /** Flag indicating an inactive (not-linked) URB. */ + bool fInactive; + /** Pointer to the URB. */ + R3PTRTYPE(PVUSBURB) pUrb; + } aInFlight[257]; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment1; +#endif + + /** Number of in-done-queue TDs. */ + unsigned cInDoneQueue; + /** Array of in-done-queue TDs. */ + struct ohci_td_in_done_queue + { + /** Address of the transport descriptor. */ + uint32_t GCPhysTD; + } aInDoneQueue[64]; + /** When the tail of the done queue was added. + * Used to calculate the age of the done queue. */ + uint32_t u32FmDoneQueueTail; +#if R3_ARCH_BITS == 32 + /** Align pLoad, the stats and the struct size correctly. */ + uint32_t Alignment2; +#endif + /** Pointer to state load data. */ + R3PTRTYPE(POHCILOAD) pLoad; + + /** Detected canceled isochronous URBs. */ + STAMCOUNTER StatCanceledIsocUrbs; + /** Detected canceled general URBs. */ + STAMCOUNTER StatCanceledGenUrbs; + /** Dropped URBs (endpoint halted, or URB canceled). */ + STAMCOUNTER StatDroppedUrbs; + /** Profiling ohciR3FrameBoundaryTimer. */ + STAMPROFILE StatTimer; + + /** This member and all the following are not part of saved state. */ + uint64_t SavedStateEnd; + + /** VM timer frequency used for frame timer calculations. */ + uint64_t u64TimerHz; + /** Idle detection flag; must be cleared at start of frame */ + bool fIdle; + /** A flag indicating that the bulk list may have in-flight URBs. */ + bool fBulkNeedsCleaning; + + /** Whether RC/R0 is enabled. */ + bool fRZEnabled; + + uint32_t Alignment3; /**< Align size on a 8 byte boundary. */ + + /** Critical section synchronising interrupt handling. */ + PDMCRITSECT CsIrq; + /** Critical section to synchronize the framer and URB completion handler. */ + RTCRITSECT CritSect; +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /** Last read physical page for caching ED reads in the framer thread. */ + R3PTRTYPE(POHCIPAGECACHE) pCacheED; + /** Last read physical page for caching TD reads in the framer thread. */ + R3PTRTYPE(POHCIPAGECACHE) pCacheTD; +#endif + +} OHCI; + +/* Standard OHCI bus speed */ +#define OHCI_DEFAULT_TIMER_FREQ 1000 + +/* Host Controller Communications Area */ +#define OHCI_HCCA_NUM_INTR 32 +#define OHCI_HCCA_OFS (OHCI_HCCA_NUM_INTR * sizeof(uint32_t)) +struct ohci_hcca +{ + uint16_t frame; + uint16_t pad; + uint32_t done; +}; +AssertCompileSize(ohci_hcca, 8); + +/** @name OHCI Endpoint Descriptor + * @{ */ + +#define ED_PTR_MASK (~(uint32_t)0xf) +#define ED_HWINFO_MPS 0x07ff0000 +#define ED_HWINFO_ISO RT_BIT(15) +#define ED_HWINFO_SKIP RT_BIT(14) +#define ED_HWINFO_LOWSPEED RT_BIT(13) +#define ED_HWINFO_IN RT_BIT(12) +#define ED_HWINFO_OUT RT_BIT(11) +#define ED_HWINFO_DIR (RT_BIT(11) | RT_BIT(12)) +#define ED_HWINFO_ENDPOINT 0x780 /* 4 bits */ +#define ED_HWINFO_ENDPOINT_SHIFT 7 +#define ED_HWINFO_FUNCTION 0x7f /* 7 bits */ +#define ED_HEAD_CARRY RT_BIT(1) +#define ED_HEAD_HALTED RT_BIT(0) + +/** + * OHCI Endpoint Descriptor. + */ +typedef struct OHCIED +{ + /** Flags and stuff. */ + uint32_t hwinfo; + /** TailP - TD Queue Tail pointer. Bits 0-3 ignored / preserved. */ + uint32_t TailP; + /** HeadP - TD Queue head pointer. Bit 0 - Halted, Bit 1 - toggleCarry. Bit 2&3 - 0. */ + uint32_t HeadP; + /** NextED - Next Endpoint Descriptor. Bits 0-3 ignored / preserved. */ + uint32_t NextED; +} OHCIED, *POHCIED; +typedef const OHCIED *PCOHCIED; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name Completion Codes + * @{ */ +#define OHCI_CC_NO_ERROR (UINT32_C(0x00) << 28) +#define OHCI_CC_CRC (UINT32_C(0x01) << 28) +#define OHCI_CC_STALL (UINT32_C(0x04) << 28) +#define OHCI_CC_DEVICE_NOT_RESPONDING (UINT32_C(0x05) << 28) +#define OHCI_CC_DNR OHCI_CC_DEVICE_NOT_RESPONDING +#define OHCI_CC_PID_CHECK_FAILURE (UINT32_C(0x06) << 28) +#define OHCI_CC_UNEXPECTED_PID (UINT32_C(0x07) << 28) +#define OHCI_CC_DATA_OVERRUN (UINT32_C(0x08) << 28) +#define OHCI_CC_DATA_UNDERRUN (UINT32_C(0x09) << 28) +/* 0x0a..0x0b - reserved */ +#define OHCI_CC_BUFFER_OVERRUN (UINT32_C(0x0c) << 28) +#define OHCI_CC_BUFFER_UNDERRUN (UINT32_C(0x0d) << 28) +#define OHCI_CC_NOT_ACCESSED_0 (UINT32_C(0x0e) << 28) +#define OHCI_CC_NOT_ACCESSED_1 (UINT32_C(0x0f) << 28) +/** @} */ + + +/** @name OHCI General transfer descriptor + * @{ */ + +/** Error count (EC) shift. */ +#define TD_ERRORS_SHIFT 26 +/** Error count max. (One greater than what the EC field can hold.) */ +#define TD_ERRORS_MAX 4 + +/** CC - Condition code mask. */ +#define TD_HWINFO_CC (UINT32_C(0xf0000000)) +#define TD_HWINFO_CC_SHIFT 28 +/** EC - Error count. */ +#define TD_HWINFO_ERRORS (RT_BIT(26) | RT_BIT(27)) +/** T - Data toggle. */ +#define TD_HWINFO_TOGGLE (RT_BIT(24) | RT_BIT(25)) +#define TD_HWINFO_TOGGLE_HI (RT_BIT(25)) +#define TD_HWINFO_TOGGLE_LO (RT_BIT(24)) +/** DI - Delay interrupt. */ +#define TD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define TD_HWINFO_IN (RT_BIT(20)) +#define TD_HWINFO_OUT (RT_BIT(19)) +/** DP - Direction / PID. */ +#define TD_HWINFO_DIR (RT_BIT(19) | RT_BIT(20)) +/** R - Buffer rounding. */ +#define TD_HWINFO_ROUNDING (RT_BIT(18)) +/** Bits that are reserved / unknown. */ +#define TD_HWINFO_UNKNOWN_MASK (UINT32_C(0x0003ffff)) + +/** SETUP - to endpoint. */ +#define OHCI_TD_DIR_SETUP 0x0 +/** OUT - to endpoint. */ +#define OHCI_TD_DIR_OUT 0x1 +/** IN - from endpoint. */ +#define OHCI_TD_DIR_IN 0x2 +/** Reserved. */ +#define OHCI_TD_DIR_RESERVED 0x3 + +/** + * OHCI general transfer descriptor + */ +typedef struct OHCITD +{ + uint32_t hwinfo; + /** CBP - Current Buffer Pointer. (32-bit physical address) */ + uint32_t cbp; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t be; +} OHCITD, *POHCITD; +typedef const OHCITD *PCOHCITD; +/** @} */ +AssertCompileSize(OHCIED, 16); + + +/** @name OHCI isochronous transfer descriptor. + * @{ */ +/** SF - Start frame number. */ +#define ITD_HWINFO_SF 0xffff +/** DI - Delay interrupt. (TD_HWINFO_DI) */ +#define ITD_HWINFO_DI (RT_BIT(21) | RT_BIT(22) | RT_BIT(23)) +#define ITD_HWINFO_DI_SHIFT 21 +/** FC - Frame count. */ +#define ITD_HWINFO_FC (RT_BIT(24) | RT_BIT(25) | RT_BIT(26)) +#define ITD_HWINFO_FC_SHIFT 24 +/** CC - Condition code mask. (=TD_HWINFO_CC) */ +#define ITD_HWINFO_CC UINT32_C(0xf0000000) +#define ITD_HWINFO_CC_SHIFT 28 +/** The buffer page 0 mask (lower 12 bits are ignored). */ +#define ITD_BP0_MASK UINT32_C(0xfffff000) + +#define ITD_NUM_PSW 8 +/** OFFSET - offset of the package into the buffer page. + * (Only valid when CC set to Not Accessed.) + * + * Note that the top bit of the OFFSET field is overlapping with the + * first bit in the CC field. This is ok because both 0xf and 0xe are + * defined as "Not Accessed". + */ +#define ITD_PSW_OFFSET 0x1fff +/** SIZE field mask for IN bound transfers. + * (Only valid when CC isn't Not Accessed.)*/ +#define ITD_PSW_SIZE 0x07ff +/** CC field mask. + * USed to indicate the format of SIZE (Not Accessed -> OFFSET). */ +#define ITD_PSW_CC 0xf000 +#define ITD_PSW_CC_SHIFT 12 + +/** + * OHCI isochronous transfer descriptor. + */ +typedef struct OHCIITD +{ + uint32_t HwInfo; + /** BP0 - Buffer Page 0. The lower 12 bits are ignored. */ + uint32_t BP0; + /** NextTD - Link to the next transfer descriptor. (32-bit physical address, dword aligned) */ + uint32_t NextTD; + /** BE - Buffer End (inclusive). (32-bit physical address) */ + uint32_t BE; + /** (OffsetN/)PSWN - package status word array (0..7). + * The format varies depending on whether the package has been completed or not. */ + uint16_t aPSW[ITD_NUM_PSW]; +} OHCIITD, *POHCIITD; +typedef const OHCIITD *PCOHCIITD; +/** @} */ +AssertCompileSize(OHCIITD, 32); + +/** + * OHCI register operator. + */ +typedef struct ohci_opreg +{ + const char *pszName; + int (*pfnRead )(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value); + int (*pfnWrite)(POHCI pThis, uint32_t iReg, uint32_t u32Value); +} OHCIOPREG; + + +/* OHCI Local stuff */ +#define OHCI_CTL_CBSR ((1<<0)|(1<<1)) /* Control/Bulk Service Ratio. */ +#define OHCI_CTL_PLE (1<<2) /* Periodic List Enable. */ +#define OHCI_CTL_IE (1<<3) /* Isochronous Enable. */ +#define OHCI_CTL_CLE (1<<4) /* Control List Enable. */ +#define OHCI_CTL_BLE (1<<5) /* Bulk List Enable. */ +#define OHCI_CTL_HCFS ((1<<6)|(1<<7)) /* Host Controller Functional State. */ +#define OHCI_USB_RESET 0x00 +#define OHCI_USB_RESUME 0x40 +#define OHCI_USB_OPERATIONAL 0x80 +#define OHCI_USB_SUSPEND 0xc0 +#define OHCI_CTL_IR (1<<8) /* Interrupt Routing (host/SMI). */ +#define OHCI_CTL_RWC (1<<9) /* Remote Wakeup Connected. */ +#define OHCI_CTL_RWE (1<<10) /* Remote Wakeup Enabled. */ + +#define OHCI_STATUS_HCR (1<<0) /* Host Controller Reset. */ +#define OHCI_STATUS_CLF (1<<1) /* Control List Filled. */ +#define OHCI_STATUS_BLF (1<<2) /* Bulk List Filled. */ +#define OHCI_STATUS_OCR (1<<3) /* Ownership Change Request. */ +#define OHCI_STATUS_SOC ((1<<6)|(1<<7)) /* Scheduling Overrun Count. */ + +/** @name Interrupt Status and Enabled/Disabled Flags + * @{ */ +/** SO - Scheduling overrun. */ +#define OHCI_INTR_SCHEDULING_OVERRUN RT_BIT(0) +/** WDH - HcDoneHead writeback. */ +#define OHCI_INTR_WRITE_DONE_HEAD RT_BIT(1) +/** SF - Start of frame. */ +#define OHCI_INTR_START_OF_FRAME RT_BIT(2) +/** RD - Resume detect. */ +#define OHCI_INTR_RESUME_DETECT RT_BIT(3) +/** UE - Unrecoverable error. */ +#define OHCI_INTR_UNRECOVERABLE_ERROR RT_BIT(4) +/** FNO - Frame number overflow. */ +#define OHCI_INTR_FRAMENUMBER_OVERFLOW RT_BIT(5) +/** RHSC- Root hub status change. */ +#define OHCI_INTR_ROOT_HUB_STATUS_CHANGE RT_BIT(6) +/** OC - Ownership change. */ +#define OHCI_INTR_OWNERSHIP_CHANGE RT_BIT(30) +/** MIE - Master interrupt enable. */ +#define OHCI_INTR_MASTER_INTERRUPT_ENABLED RT_BIT(31) +/** @} */ + +#define OHCI_HCCA_SIZE 0x100 +#define OHCI_HCCA_MASK UINT32_C(0xffffff00) + +#define OHCI_FMI_FI UINT32_C(0x00003fff) /* Frame Interval. */ +#define OHCI_FMI_FSMPS UINT32_C(0x7fff0000) /* Full-Speed Max Packet Size. */ +#define OHCI_FMI_FSMPS_SHIFT 16 +#define OHCI_FMI_FIT UINT32_C(0x80000000) /* Frame Interval Toggle. */ +#define OHCI_FMI_FIT_SHIFT 31 + +#define OHCI_FR_FRT RT_BIT_32(31) /* Frame Remaining Toggle */ + +#define OHCI_LS_THRESH 0x628 /* Low-Speed Threshold. */ + +#define OHCI_RHA_NDP (0xff) /* Number of Downstream Ports. */ +#define OHCI_RHA_PSM RT_BIT_32(8) /* Power Switching Mode. */ +#define OHCI_RHA_NPS RT_BIT_32(9) /* No Power Switching. */ +#define OHCI_RHA_DT RT_BIT_32(10) /* Device Type. */ +#define OHCI_RHA_OCPM RT_BIT_32(11) /* Over-Current Protection Mode. */ +#define OHCI_RHA_NOCP RT_BIT_32(12) /* No Over-Current Protection. */ +#define OHCI_RHA_POTPGP UINT32_C(0xff000000) /* Power On To Power Good Time. */ + +#define OHCI_RHS_LPS RT_BIT_32(0) /* Local Power Status. */ +#define OHCI_RHS_OCI RT_BIT_32(1) /* Over-Current Indicator. */ +#define OHCI_RHS_DRWE RT_BIT_32(15) /* Device Remote Wakeup Enable. */ +#define OHCI_RHS_LPSC RT_BIT_32(16) /* Local Power Status Change. */ +#define OHCI_RHS_OCIC RT_BIT_32(17) /* Over-Current Indicator Change. */ +#define OHCI_RHS_CRWE RT_BIT_32(31) /* Clear Remote Wakeup Enable. */ + +/** @name HcRhPortStatus[n] - RH Port Status register (read). + * @{ */ +/** CCS - CurrentConnectionStatus - 0 = no device, 1 = device. */ +#define OHCI_PORT_CCS RT_BIT(0) +/** ClearPortEnable (when writing CCS). */ +#define OHCI_PORT_CLRPE OHCI_PORT_CCS +/** PES - PortEnableStatus. */ +#define OHCI_PORT_PES RT_BIT(1) +/** PSS - PortSuspendStatus */ +#define OHCI_PORT_PSS RT_BIT(2) +/** POCI- PortOverCurrentIndicator. */ +#define OHCI_PORT_POCI RT_BIT(3) +/** ClearSuspendStatus (when writing POCI). */ +#define OHCI_PORT_CLRSS OHCI_PORT_POCI +/** PRS - PortResetStatus */ +#define OHCI_PORT_PRS RT_BIT(4) +/** PPS - PortPowerStatus */ +#define OHCI_PORT_PPS RT_BIT(8) +/** LSDA - LowSpeedDeviceAttached */ +#define OHCI_PORT_LSDA RT_BIT(9) +/** ClearPortPower (when writing LSDA). */ +#define OHCI_PORT_CLRPP OHCI_PORT_LSDA +/** CSC - ConnectStatusChange */ +#define OHCI_PORT_CSC RT_BIT(16) +/** PESC - PortEnableStatusChange */ +#define OHCI_PORT_PESC RT_BIT(17) +/** PSSC - PortSuspendStatusChange */ +#define OHCI_PORT_PSSC RT_BIT(18) +/** OCIC - OverCurrentIndicatorChange */ +#define OHCI_PORT_OCIC RT_BIT(19) +/** PRSC - PortResetStatusChange */ +#define OHCI_PORT_PRSC RT_BIT(20) +/** The mask of RW1C bits. */ +#define OHCI_PORT_CLEAR_CHANGE_MASK (OHCI_PORT_CSC | OHCI_PORT_PESC | OHCI_PORT_PSSC | OHCI_PORT_OCIC | OHCI_PORT_PRSC) +/** @} */ + + +#ifndef VBOX_DEVICE_STRUCT_TESTCASE + +#ifdef VBOX_WITH_OHCI_PHYS_READ_STATS +/* + * Explain + */ +typedef struct OHCIDESCREADSTATS +{ + uint32_t cReads; + uint32_t cPageChange; + uint32_t cMinReadsPerPage; + uint32_t cMaxReadsPerPage; + + uint32_t cReadsLastPage; + uint32_t u32LastPageAddr; +} OHCIDESCREADSTATS; +typedef OHCIDESCREADSTATS *POHCIDESCREADSTATS; + +typedef struct OHCIPHYSREADSTATS +{ + OHCIDESCREADSTATS ed; + OHCIDESCREADSTATS td; + OHCIDESCREADSTATS all; + + uint32_t cCrossReads; + uint32_t cCacheReads; + uint32_t cPageReads; +} OHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS *POHCIPHYSREADSTATS; +typedef OHCIPHYSREADSTATS const *PCOHCIPHYSREADSTATS; +#endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#if defined(VBOX_WITH_OHCI_PHYS_READ_STATS) && defined(IN_RING3) +static OHCIPHYSREADSTATS g_PhysReadState; +#endif + +#if defined(LOG_ENABLED) && defined(IN_RING3) +static bool g_fLogBulkEPs = false; +static bool g_fLogControlEPs = false; +static bool g_fLogInterruptEPs = false; +#endif +#ifdef IN_RING3 +/** + * SSM descriptor table for the OHCI structure. + */ +static SSMFIELD const g_aOhciFields[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[8].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[9].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[10].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[11].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[12].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[13].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[14].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; + +/** + * SSM descriptor table for the older 8-port OHCI structure. + */ +static SSMFIELD const g_aOhciFields8Ports[] = +{ + SSMFIELD_ENTRY( OHCI, SofTime), + SSMFIELD_ENTRY_CUSTOM( dpic+fno, RT_OFFSETOF(OHCI, SofTime) + RT_SIZEOFMEMB(OHCI, SofTime), 4), + SSMFIELD_ENTRY( OHCI, RootHub.status), + SSMFIELD_ENTRY( OHCI, RootHub.desc_a), + SSMFIELD_ENTRY( OHCI, RootHub.desc_b), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[0].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[1].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[2].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[3].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[4].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[5].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[6].fReg), + SSMFIELD_ENTRY( OHCI, RootHub.aPorts[7].fReg), + SSMFIELD_ENTRY( OHCI, ctl), + SSMFIELD_ENTRY( OHCI, status), + SSMFIELD_ENTRY( OHCI, intr_status), + SSMFIELD_ENTRY( OHCI, intr), + SSMFIELD_ENTRY( OHCI, hcca), + SSMFIELD_ENTRY( OHCI, per_cur), + SSMFIELD_ENTRY( OHCI, ctrl_cur), + SSMFIELD_ENTRY( OHCI, ctrl_head), + SSMFIELD_ENTRY( OHCI, bulk_cur), + SSMFIELD_ENTRY( OHCI, bulk_head), + SSMFIELD_ENTRY( OHCI, done), + SSMFIELD_ENTRY_CUSTOM( fsmps+fit+fi+frt, RT_OFFSETOF(OHCI, done) + RT_SIZEOFMEMB(OHCI, done), 4), + SSMFIELD_ENTRY( OHCI, HcFmNumber), + SSMFIELD_ENTRY( OHCI, pstart), + SSMFIELD_ENTRY_TERM() +}; +#endif + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +#ifdef IN_RING3 +/* Update host controller state to reflect a device attach */ +static void ohciR3RhPortPower(POHCIROOTHUB pRh, unsigned iPort, bool fPowerUp); +static void ohciR3BusResume(POHCI ohci, bool fHardware); +static void ohciR3BusStop(POHCI pThis); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE +static void ohciR3PhysReadCacheClear(POHCIPAGECACHE pPageCache); +#endif + +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb); + +static int ohciR3InFlightFind(POHCI pThis, uint32_t GCPhysTD); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) +static int ohciR3InDoneQueueFind(POHCI pThis, uint32_t GCPhysTD); +# endif +static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser); +#endif /* IN_RING3 */ +RT_C_DECLS_END + + +/** + * Update PCI IRQ levels + */ +static void ohciUpdateInterruptLocked(POHCI ohci, const char *msg) +{ + int level = 0; + + if ( (ohci->intr & OHCI_INTR_MASTER_INTERRUPT_ENABLED) + && (ohci->intr_status & ohci->intr) + && !(ohci->ctl & OHCI_CTL_IR)) + level = 1; + + PDMDevHlpPCISetIrq(ohci->CTX_SUFF(pDevIns), 0, level); + if (level) + { + uint32_t val = ohci->intr_status & ohci->intr; + Log2(("ohci: Fired off interrupt %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d - %s\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, msg)); NOREF(val); NOREF(msg); + } +} + +#ifdef IN_RING3 + +/** + * Set an interrupt, use the wrapper ohciSetInterrupt. + */ +DECLINLINE(int) ohciR3SetInterruptInt(POHCI ohci, int rcBusy, uint32_t intr, const char *msg) +{ + int rc = PDMCritSectEnter(&ohci->CsIrq, rcBusy); + if (rc != VINF_SUCCESS) + return rc; + + if ( (ohci->intr_status & intr) != intr ) + { + ohci->intr_status |= intr; + ohciUpdateInterruptLocked(ohci, msg); + } + + PDMCritSectLeave(&ohci->CsIrq); + return rc; +} + +/** + * Set an interrupt wrapper macro for logging purposes. + */ +# define ohciR3SetInterrupt(ohci, intr) ohciR3SetInterruptInt(ohci, VERR_IGNORED, intr, #intr) + + +/* Carry out a hardware remote wakeup */ +static void ohciR3RemoteWakeup(POHCI pThis) +{ + if ((pThis->ctl & OHCI_CTL_HCFS) != OHCI_USB_SUSPEND) + return; + if (!(pThis->RootHub.status & OHCI_RHS_DRWE)) + return; + ohciR3BusResume(pThis, true /* hardware */); +} + + +/** + * Query interface method for the roothub LUN. + */ +static DECLCALLBACK(void *) ohciR3RhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + POHCI pThis = RT_FROM_MEMBER(pInterface, OHCI, RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pThis->RootHub.IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBPORT, &pThis->RootHub.IRhPort); + PDMIBASE_RETURN_INTERFACE(pszIID, PDMILEDPORTS, &pThis->RootHub.ILeds); + return NULL; +} + +/** + * Gets the pointer to the status LED of a unit. + * + * @returns VBox status code. + * @param pInterface Pointer to the interface structure containing the called function pointer. + * @param iLUN The unit which status LED we desire. + * @param ppLed Where to store the LED pointer. + */ +static DECLCALLBACK(int) ohciR3RhQueryStatusLed(PPDMILEDPORTS pInterface, unsigned iLUN, PPDMLED *ppLed) +{ + POHCI pThis = (POHCI)((uintptr_t)pInterface - RT_OFFSETOF(OHCI, RootHub.ILeds)); + if (iLUN == 0) + { + *ppLed = &pThis->RootHub.Led; + return VINF_SUCCESS; + } + return VERR_PDM_LUN_NOT_FOUND; +} + + +/** Converts a OHCI.roothub.IRhPort pointer to a POHCI. */ +#define VUSBIROOTHUBPORT_2_OHCI(pInterface) ((POHCI)( (uintptr_t)(pInterface) - RT_OFFSETOF(OHCI, RootHub.IRhPort) )) + +/** + * Get the number of available ports in the hub. + * + * @returns The number of ports available. + * @param pInterface Pointer to this structure. + * @param pAvailable Bitmap indicating the available ports. Set bit == available port. + */ +static DECLCALLBACK(unsigned) ohciR3RhGetAvailablePorts(PVUSBIROOTHUBPORT pInterface, PVUSBPORTBITMAP pAvailable) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + unsigned iPort; + unsigned cPorts = 0; + + memset(pAvailable, 0, sizeof(*pAvailable)); + + PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED); + for (iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) + { + if (!pThis->RootHub.aPorts[iPort].pDev) + { + cPorts++; + ASMBitSet(pAvailable, iPort + 1); + } + } + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); + + return cPorts; +} + + +/** + * Gets the supported USB versions. + * + * @returns The mask of supported USB versions. + * @param pInterface Pointer to this structure. + */ +static DECLCALLBACK(uint32_t) ohciR3RhGetUSBVersions(PVUSBIROOTHUBPORT pInterface) +{ + RT_NOREF(pInterface); + return VUSB_STDVER_11; +} + + +/** + * A device is being attached to a port in the roothub. + * + * @param pInterface Pointer to this structure. + * @param pDev Pointer to the device being attached. + * @param uPort The port number assigned to the device. + */ +static DECLCALLBACK(int) ohciR3RhAttach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + VUSBSPEED enmSpeed; + LogFlow(("ohciR3RhAttach: pDev=%p uPort=%u\n", pDev, uPort)); + PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(!pThis->RootHub.aPorts[uPort].pDev); + enmSpeed = pDev->pfnGetSpeed(pDev); + /* Only LS/FS devices can end up here. */ + Assert(enmSpeed == VUSB_SPEED_LOW || enmSpeed == VUSB_SPEED_FULL); + + /* + * Attach it. + */ + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + if (enmSpeed == VUSB_SPEED_LOW) + pThis->RootHub.aPorts[uPort].fReg |= OHCI_PORT_LSDA; + pThis->RootHub.aPorts[uPort].pDev = pDev; + ohciR3RhPortPower(&pThis->RootHub, uPort, 1 /* power on */); + + ohciR3RemoteWakeup(pThis); + ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); + return VINF_SUCCESS; +} + + +/** + * A device is being detached from a port in the roothub. + * + * @param pInterface Pointer to this structure. + * @param pDev Pointer to the device being detached. + * @param uPort The port number assigned to the device. + */ +static DECLCALLBACK(void) ohciR3RhDetach(PVUSBIROOTHUBPORT pInterface, PVUSBIDEVICE pDev, unsigned uPort) +{ + RT_NOREF(pDev); + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + LogFlow(("ohciR3RhDetach: pDev=%p uPort=%u\n", pDev, uPort)); + PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED); + + /* + * Validate and adjust input. + */ + Assert(uPort >= 1 && uPort <= OHCI_NDP_CFG(pThis)); + uPort--; + Assert(pThis->RootHub.aPorts[uPort].pDev == pDev); + + /* + * Detach it. + */ + pThis->RootHub.aPorts[uPort].pDev = NULL; + if (pThis->RootHub.aPorts[uPort].fReg & OHCI_PORT_PES) + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC | OHCI_PORT_PESC; + else + pThis->RootHub.aPorts[uPort].fReg = OHCI_PORT_CSC; + + ohciR3RemoteWakeup(pThis); + ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); +} + + +/** + * One of the roothub devices has completed its reset operation. + * + * Currently, we don't think anything is required to be done here + * so it's just a stub for forcing async resetting of the devices + * during a root hub reset. + * + * @param pDev The root hub device. + * @param rc The result of the operation. + * @param pvUser Pointer to the controller. + */ +static DECLCALLBACK(void) ohciR3RhResetDoneOneDev(PVUSBIDEVICE pDev, int rc, void *pvUser) +{ + LogRel(("OHCI: root hub reset completed with %Rrc\n", rc)); + NOREF(pDev); NOREF(rc); NOREF(pvUser); +} + + +/** + * Reset the root hub. + * + * @returns VBox status code. + * @param pInterface Pointer to this structure. + * @param fResetOnLinux This is used to indicate whether we're at VM reset time and + * can do real resets or if we're at any other time where that + * isn't such a good idea. + * @remark Do NOT call VUSBIDevReset on the root hub in an async fashion! + * @thread EMT + */ +static DECLCALLBACK(int) ohciR3RhReset(PVUSBIROOTHUBPORT pInterface, bool fResetOnLinux) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED); + + Log(("ohci: root hub reset%s\n", fResetOnLinux ? " (reset on linux)" : "")); + + pThis->RootHub.status = 0; + pThis->RootHub.desc_a = OHCI_RHA_NPS | OHCI_NDP_CFG(pThis); /* Preserve NDP value. */ + pThis->RootHub.desc_b = 0x0; /* Impl. specific */ + + /* + * We're pending to _reattach_ the device without resetting them. + * Except, during VM reset where we use the opportunity to do a proper + * reset before the guest comes along and expect things. + * + * However, it's very very likely that we're not doing the right thing + * here if coming from the guest (USB Reset state). The docs talks about + * root hub resetting, however what exact behaviour in terms of root hub + * status and changed bits, and HC interrupts aren't stated clearly. IF we + * get trouble and see the guest doing "USB Resets" we will have to look + * into this. For the time being we stick with simple. + */ + for (unsigned iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) + { + if (pThis->RootHub.aPorts[iPort].pDev) + { + pThis->RootHub.aPorts[iPort].fReg = OHCI_PORT_CCS | OHCI_PORT_CSC | OHCI_PORT_PPS; + if (fResetOnLinux) + { + PVM pVM = PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns)); + VUSBIDevReset(pThis->RootHub.aPorts[iPort].pDev, fResetOnLinux, ohciR3RhResetDoneOneDev, pThis, pVM); + } + } + else + pThis->RootHub.aPorts[iPort].fReg = 0; + } + ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); + return VINF_SUCCESS; +} + +/** + * Does a software or hardware reset of the controller. + * + * This is called in response to setting HcCommandStatus.HCR, hardware reset, + * and device construction. + * + * @param pThis The ohci instance data. + * @param fNewMode The new mode of operation. This is UsbSuspend if it's a + * software reset, and UsbReset if it's a hardware reset / cold boot. + * @param fResetOnLinux Set if we can do a real reset of the devices attached to the root hub. + * This is really a just a hack for the non-working linux device reset. + * Linux has this feature called 'logical disconnect' if device reset fails + * which prevents us from doing resets when the guest asks for it - the guest + * will get confused when the device seems to be reconnected everytime it tries + * to reset it. But if we're at hardware reset time, we can allow a device to + * be 'reconnected' without upsetting the guest. + * + * @remark This hasn't got anything to do with software setting the mode to UsbReset. + */ +static void ohciR3DoReset(POHCI pThis, uint32_t fNewMode, bool fResetOnLinux) +{ + Log(("ohci: %s reset%s\n", fNewMode == OHCI_USB_RESET ? "hardware" : "software", + fResetOnLinux ? " (reset on linux)" : "")); + + /* Stop the bus in any case, disabling walking the lists. */ + ohciR3BusStop(pThis); + + /* + * Cancel all outstanding URBs. + * + * We can't, and won't, deal with URBs until we're moved out of the + * suspend/reset state. Also, a real HC isn't going to send anything + * any more when a reset has been signaled. + */ + pThis->RootHub.pIRhConn->pfnCancelAllUrbs(pThis->RootHub.pIRhConn); + + /* + * Reset the hardware registers. + */ + if (fNewMode == OHCI_USB_RESET) + pThis->ctl = OHCI_CTL_RWC; /* We're the firmware, set RemoteWakeupConnected. */ + else + pThis->ctl &= OHCI_CTL_IR | OHCI_CTL_RWC; /* IR and RWC are preserved on software reset. */ + + /* Clear the HCFS bits first to make setting the new state work. */ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= fNewMode; + pThis->status = 0; + pThis->intr_status = 0; + pThis->intr = 0; + PDMDevHlpPCISetIrq(pThis->CTX_SUFF(pDevIns), 0, 0); + + pThis->hcca = 0; + pThis->per_cur = 0; + pThis->ctrl_head = pThis->ctrl_cur = 0; + pThis->bulk_head = pThis->bulk_cur = 0; + pThis->done = 0; + + pThis->fsmps = 0x2778; /* To-Be-Defined, use the value linux sets...*/ + pThis->fit = 0; + pThis->fi = 11999; /* (12MHz ticks, one frame is 1ms) */ + pThis->frt = 0; + pThis->HcFmNumber = 0; + pThis->pstart = 0; + + pThis->dqic = 0x7; + pThis->fno = 0; + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheClear(pThis->pCacheED); + ohciR3PhysReadCacheClear(pThis->pCacheTD); +#endif + + /* + * If this is a hardware reset, we will initialize the root hub too. + * Software resets doesn't do this according to the specs. + * (It's not possible to have device connected at the time of the + * device construction, so nothing to worry about there.) + */ + if (fNewMode == OHCI_USB_RESET) + VUSBIDevReset(pThis->RootHub.pIDev, fResetOnLinux, NULL, NULL, NULL); +} + + +/** + * Reads physical memory. + */ +DECLINLINE(void) ohciR3PhysRead(POHCI pThis, uint32_t Addr, void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPhysRead(pThis->CTX_SUFF(pDevIns), Addr, pvBuf, cbBuf); +} + +/** + * Writes physical memory. + */ +DECLINLINE(void) ohciR3PhysWrite(POHCI pThis, uint32_t Addr, const void *pvBuf, size_t cbBuf) +{ + if (cbBuf) + PDMDevHlpPCIPhysWrite(pThis->CTX_SUFF(pDevIns), Addr, pvBuf, cbBuf); +} + +/** + * Read an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ohciR3GetDWords(POHCI pThis, uint32_t Addr, uint32_t *pau32s, int c32s) +{ + ohciR3PhysRead(pThis, Addr, pau32s, c32s * sizeof(uint32_t)); +# ifndef RT_LITTLE_ENDIAN + for(int i = 0; i < c32s; i++) + pau32s[i] = RT_H2LE_U32(pau32s[i]); +# endif +} + +/** + * Write an array of dwords from physical memory and correct endianness. + */ +DECLINLINE(void) ohciR3PutDWords(POHCI pThis, uint32_t Addr, const uint32_t *pau32s, int cu32s) +{ +# ifdef RT_LITTLE_ENDIAN + ohciR3PhysWrite(pThis, Addr, pau32s, cu32s << 2); +# else + for (int i = 0; i < c32s; i++, pau32s++, Addr += sizeof(*pau32s)) + { + uint32_t u32Tmp = RT_H2LE_U32(*pau32s); + ohciR3PhysWrite(pThis, Addr, (uint8_t *)&u32Tmp, sizeof(u32Tmp)); + } +# endif +} + + + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + +static void descReadStatsReset(POHCIDESCREADSTATS p) +{ + p->cReads = 0; + p->cPageChange = 0; + p->cMinReadsPerPage = UINT32_MAX; + p->cMaxReadsPerPage = 0; + + p->cReadsLastPage = 0; + p->u32LastPageAddr = 0; +} + +static void physReadStatsReset(POHCIPHYSREADSTATS p) +{ + descReadStatsReset(&p->ed); + descReadStatsReset(&p->td); + descReadStatsReset(&p->all); + + p->cCrossReads = 0; + p->cCacheReads = 0; + p->cPageReads = 0; +} + +static void physReadStatsUpdateDesc(POHCIDESCREADSTATS p, uint32_t u32Addr) +{ + const uint32_t u32PageAddr = u32Addr & ~UINT32_C(0xFFF); + + ++p->cReads; + + if (p->u32LastPageAddr == 0) + { + /* First call. */ + ++p->cReadsLastPage; + p->u32LastPageAddr = u32PageAddr; + } + else if (u32PageAddr != p->u32LastPageAddr) + { + /* New page. */ + ++p->cPageChange; + + p->cMinReadsPerPage = RT_MIN(p->cMinReadsPerPage, p->cReadsLastPage); + p->cMaxReadsPerPage = RT_MAX(p->cMaxReadsPerPage, p->cReadsLastPage);; + + p->cReadsLastPage = 1; + p->u32LastPageAddr = u32PageAddr; + } + else + { + /* Read on the same page. */ + ++p->cReadsLastPage; + } +} + +static void physReadStatsPrint(POHCIPHYSREADSTATS p) +{ + p->ed.cMinReadsPerPage = RT_MIN(p->ed.cMinReadsPerPage, p->ed.cReadsLastPage); + p->ed.cMaxReadsPerPage = RT_MAX(p->ed.cMaxReadsPerPage, p->ed.cReadsLastPage);; + + p->td.cMinReadsPerPage = RT_MIN(p->td.cMinReadsPerPage, p->td.cReadsLastPage); + p->td.cMaxReadsPerPage = RT_MAX(p->td.cMaxReadsPerPage, p->td.cReadsLastPage);; + + p->all.cMinReadsPerPage = RT_MIN(p->all.cMinReadsPerPage, p->all.cReadsLastPage); + p->all.cMaxReadsPerPage = RT_MAX(p->all.cMaxReadsPerPage, p->all.cReadsLastPage);; + + LogRel(("PHYSREAD:\n" + " ED: %d, %d, %d/%d\n" + " TD: %d, %d, %d/%d\n" + " ALL: %d, %d, %d/%d\n" + " C: %d, %d, %d\n" + "", + p->ed.cReads, p->ed.cPageChange, p->ed.cMinReadsPerPage, p->ed.cMaxReadsPerPage, + p->td.cReads, p->td.cPageChange, p->td.cMinReadsPerPage, p->td.cMaxReadsPerPage, + p->all.cReads, p->all.cPageChange, p->all.cMinReadsPerPage, p->all.cMaxReadsPerPage, + p->cCrossReads, p->cCacheReads, p->cPageReads + )); + + physReadStatsReset(p); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_STATS */ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + +static POHCIPAGECACHE ohciR3PhysReadCacheAlloc(void) +{ + return (POHCIPAGECACHE)RTMemAlloc(sizeof(OHCIPAGECACHE)); +} + +static void ohciR3PhysReadCacheFree(POHCIPAGECACHE pPageCache) +{ + RTMemFree(pPageCache); +} + +static void ohciR3PhysReadCacheClear(POHCIPAGECACHE pPageCache) +{ + pPageCache->GCPhysReadCacheAddr = NIL_RTGCPHYS; +} + +static void ohciR3PhysReadCacheRead(POHCI pThis, POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS PageAddr = PAGE_ADDRESS(GCPhys); + + if (PageAddr == PAGE_ADDRESS(GCPhys + cbBuf)) + { + if (PageAddr != pPageCache->GCPhysReadCacheAddr) + { + PDMDevHlpPhysRead(pThis->pDevInsR3, PageAddr, + pPageCache->au8PhysReadCache, sizeof(pPageCache->au8PhysReadCache)); + pPageCache->GCPhysReadCacheAddr = PageAddr; +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cPageReads; +# endif + } + + memcpy(pvBuf, &pPageCache->au8PhysReadCache[GCPhys & PAGE_OFFSET_MASK], cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCacheReads; +# endif + } + else + { + PDMDevHlpPhysRead(pThis->pDevInsR3, GCPhys, pvBuf, cbBuf); +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + ++g_PhysReadState.cCrossReads; +# endif + } +} + + +/** + * Updates the data in the given page cache if the given guest physical address is currently contained + * in the cache. + * + * @returns nothing. + * @param pPageCache The page cache to update. + * @param GCPhys The guest physical address needing the update. + * @param pvBuf Pointer to the buffer to update the page cache with. + * @param cbBuf Number of bytes to update. + */ +static void ohciR3PhysCacheUpdate(POHCIPAGECACHE pPageCache, RTGCPHYS GCPhys, const void *pvBuf, size_t cbBuf) +{ + const RTGCPHYS GCPhysPage = PAGE_ADDRESS(GCPhys); + + if (GCPhysPage == pPageCache->GCPhysReadCacheAddr) + { + uint32_t offPage = GCPhys & PAGE_OFFSET_MASK; + memcpy(&pPageCache->au8PhysReadCache[offPage], pvBuf, RT_MIN(PAGE_SIZE - offPage, cbBuf)); + } +} + +/** + * Update any cached ED data with the given endpoint descriptor at the given address. + * + * @returns nothing. + * @param pThis The OHCI instance data. + * @param EdAddr Endpoint descriptor address. + * @param pEd The endpoint descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheEdUpdate(POHCI pThis, RTGCPHYS32 EdAddr, PCOHCIED pEd) +{ + ohciR3PhysCacheUpdate(pThis->pCacheED, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, sizeof(uint32_t)); +} + + +/** + * Update any cached TD data with the given transfer descriptor at the given address. + * + * @returns nothing. + * @param pThis The OHCI instance data. + * @param TdAddr Transfer descriptor address. + * @param pTd The transfer descriptor which got updated. + */ +DECLINLINE(void) ohciR3CacheTdUpdate(POHCI pThis, RTGCPHYS32 TdAddr, PCOHCITD pTd) +{ + ohciR3PhysCacheUpdate(pThis->pCacheTD, TdAddr, pTd, sizeof(*pTd)); +} + +# endif /* VBOX_WITH_OHCI_PHYS_READ_CACHE */ + +/** + * Reads an OHCIED. + */ +DECLINLINE(void) ohciR3ReadEd(POHCI pThis, uint32_t EdAddr, POHCIED pEd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.ed, EdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, EdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheRead(pThis, pThis->pCacheED, EdAddr, pEd, sizeof(*pEd)); +#else + ohciR3GetDWords(pThis, EdAddr, (uint32_t *)pEd, sizeof(*pEd) >> 2); +#endif +} + +/** + * Reads an OHCITD. + */ +DECLINLINE(void) ohciR3ReadTd(POHCI pThis, uint32_t TdAddr, POHCITD pTd) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsUpdateDesc(&g_PhysReadState.td, TdAddr); + physReadStatsUpdateDesc(&g_PhysReadState.all, TdAddr); +# endif +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheRead(pThis, pThis->pCacheTD, TdAddr, pTd, sizeof(*pTd)); +#else + ohciR3GetDWords(pThis, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#endif +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + uint32_t hichg; + hichg = pTd->hwinfo; + Log3(("ohciR3ReadTd(,%#010x,): R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x UNK=%#x\n", + TdAddr, + (pTd->hwinfo >> 18) & 1, + (pTd->hwinfo >> 19) & 3, + (pTd->hwinfo >> 21) & 7, + (pTd->hwinfo >> 24) & 3, + (pTd->hwinfo >> 26) & 3, + (pTd->hwinfo >> 28) &15, + pTd->cbp, + pTd->NextTD, + pTd->be, + pTd->hwinfo & TD_HWINFO_UNKNOWN_MASK)); +# if 0 + if (LogIs3Enabled()) + { + /* + * usbohci.sys (32-bit XP) allocates 0x80 bytes per TD: + * 0x00-0x0f is the OHCI TD. + * 0x10-0x1f for isochronous TDs + * 0x20 is the physical address of this TD. + * 0x24 is initialized with 0x64745948, probably a magic. + * 0x28 is some kind of flags. the first bit begin the allocated / not allocated indicator. + * 0x30 is a pointer to something. endpoint? interface? device? + * 0x38 is initialized to 0xdeadface. but is changed into a pointer or something. + * 0x40 looks like a pointer. + * The rest is unknown and initialized with zeros. + */ + uint8_t abXpTd[0x80]; + ohciR3PhysRead(pThis, TdAddr, abXpTd, sizeof(abXpTd)); + Log3(("WinXpTd: alloc=%d PhysSelf=%RX32 s2=%RX32 magic=%RX32 s4=%RX32 s5=%RX32\n" + "%.*Rhxd\n", + abXpTd[28] & RT_BIT(0), + *((uint32_t *)&abXpTd[0x20]), *((uint32_t *)&abXpTd[0x30]), + *((uint32_t *)&abXpTd[0x24]), *((uint32_t *)&abXpTd[0x38]), + *((uint32_t *)&abXpTd[0x40]), + sizeof(abXpTd), &abXpTd[0])); + } +# endif + } +# endif +} + +/** + * Reads an OHCIITD. + */ +DECLINLINE(void) ohciR3ReadITd(POHCI pThis, uint32_t ITdAddr, POHCIITD pITd) +{ + ohciR3GetDWords(pThis, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + Log3(("ohciR3ReadITd(,%#010x,): SF=%#06x (%#RX32) DI=%#x FC=%d CC=%#x BP0=%#010x NextTD=%#010x BE=%#010x\n", + ITdAddr, + pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (pITd->HwInfo >> 21) & 7, + (pITd->HwInfo >> 24) & 7, + (pITd->HwInfo >> 28) &15, + pITd->BP0, + pITd->NextTD, + pITd->BE)); + Log3(("psw0=%x:%03x psw1=%x:%03x psw2=%x:%03x psw3=%x:%03x psw4=%x:%03x psw5=%x:%03x psw6=%x:%03x psw7=%x:%03x\n", + pITd->aPSW[0] >> 12, pITd->aPSW[0] & 0xfff, + pITd->aPSW[1] >> 12, pITd->aPSW[1] & 0xfff, + pITd->aPSW[2] >> 12, pITd->aPSW[2] & 0xfff, + pITd->aPSW[3] >> 12, pITd->aPSW[3] & 0xfff, + pITd->aPSW[4] >> 12, pITd->aPSW[4] & 0xfff, + pITd->aPSW[5] >> 12, pITd->aPSW[5] & 0xfff, + pITd->aPSW[6] >> 12, pITd->aPSW[6] & 0xfff, + pITd->aPSW[7] >> 12, pITd->aPSW[7] & 0xfff)); + } +# endif +} + + +/** + * Writes an OHCIED. + */ +DECLINLINE(void) ohciR3WriteEd(POHCI pThis, uint32_t EdAddr, PCOHCIED pEd) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIED EdOld; + uint32_t hichg; + + ohciR3GetDWords(pThis, EdAddr, (uint32_t *)&EdOld, sizeof(EdOld) >> 2); + hichg = EdOld.hwinfo ^ pEd->hwinfo; + Log3(("ohciR3WriteEd(,%#010x,): %sFA=%#x %sEN=%#x %sD=%#x %sS=%d %sK=%d %sF=%d %sMPS=%#x %sTailP=%#010x %sHeadP=%#010x %sH=%d %sC=%d %sNextED=%#010x\n", + EdAddr, + (hichg >> 0) & 0x7f ? "*" : "", (pEd->hwinfo >> 0) & 0x7f, + (hichg >> 7) & 0xf ? "*" : "", (pEd->hwinfo >> 7) & 0xf, + (hichg >> 11) & 3 ? "*" : "", (pEd->hwinfo >> 11) & 3, + (hichg >> 13) & 1 ? "*" : "", (pEd->hwinfo >> 13) & 1, + (hichg >> 14) & 1 ? "*" : "", (pEd->hwinfo >> 14) & 1, + (hichg >> 15) & 1 ? "*" : "", (pEd->hwinfo >> 15) & 1, + (hichg >> 24) &0x3ff ? "*" : "", (pEd->hwinfo >> 16) &0x3ff, + EdOld.TailP != pEd->TailP ? "*" : "", pEd->TailP, + (EdOld.HeadP & ~3) != (pEd->HeadP & ~3) ? "*" : "", pEd->HeadP & ~3, + (EdOld.HeadP ^ pEd->HeadP) & 1 ? "*" : "", pEd->HeadP & 1, + (EdOld.HeadP ^ pEd->HeadP) & 2 ? "*" : "", (pEd->HeadP >> 1) & 1, + EdOld.NextED != pEd->NextED ? "*" : "", pEd->NextED)); + } +# endif + + ohciR3PutDWords(pThis, EdAddr + RT_OFFSETOF(OHCIED, HeadP), &pEd->HeadP, 1); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3CacheEdUpdate(pThis, EdAddr, pEd); +#endif +} + + +/** + * Writes an OHCITD. + */ +DECLINLINE(void) ohciR3WriteTd(POHCI pThis, uint32_t TdAddr, PCOHCITD pTd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCITD TdOld; + ohciR3GetDWords(pThis, TdAddr, (uint32_t *)&TdOld, sizeof(TdOld) >> 2); + uint32_t hichg = TdOld.hwinfo ^ pTd->hwinfo; + Log3(("ohciR3WriteTd(,%#010x,): %sR=%d %sDP=%d %sDI=%#x %sT=%d %sEC=%d %sCC=%#x %sCBP=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + TdAddr, + (hichg >> 18) & 1 ? "*" : "", (pTd->hwinfo >> 18) & 1, + (hichg >> 19) & 3 ? "*" : "", (pTd->hwinfo >> 19) & 3, + (hichg >> 21) & 7 ? "*" : "", (pTd->hwinfo >> 21) & 7, + (hichg >> 24) & 3 ? "*" : "", (pTd->hwinfo >> 24) & 3, + (hichg >> 26) & 3 ? "*" : "", (pTd->hwinfo >> 26) & 3, + (hichg >> 28) &15 ? "*" : "", (pTd->hwinfo >> 28) &15, + TdOld.cbp != pTd->cbp ? "*" : "", pTd->cbp, + TdOld.NextTD != pTd->NextTD ? "*" : "", pTd->NextTD, + TdOld.be != pTd->be ? "*" : "", pTd->be, + pszLogMsg)); + } +# else + RT_NOREF(pszLogMsg); +# endif + ohciR3PutDWords(pThis, TdAddr, (uint32_t *)pTd, sizeof(*pTd) >> 2); +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3CacheTdUpdate(pThis, TdAddr, pTd); +#endif +} + +/** + * Writes an OHCIITD. + */ +DECLINLINE(void) ohciR3WriteITd(POHCI pThis, uint32_t ITdAddr, PCOHCIITD pITd, const char *pszLogMsg) +{ +# ifdef LOG_ENABLED + if (LogIs3Enabled()) + { + OHCIITD ITdOld; + ohciR3GetDWords(pThis, ITdAddr, (uint32_t *)&ITdOld, sizeof(ITdOld) / sizeof(uint32_t)); + uint32_t HIChg = ITdOld.HwInfo ^ pITd->HwInfo; + Log3(("ohciR3WriteITd(,%#010x,): %sSF=%#x (now=%#RX32) %sDI=%#x %sFC=%d %sCC=%#x %sBP0=%#010x %sNextTD=%#010x %sBE=%#010x (%s)\n", + ITdAddr, + (HIChg & 0xffff) & 1 ? "*" : "", pITd->HwInfo & 0xffff, pThis->HcFmNumber, + (HIChg >> 21) & 7 ? "*" : "", (pITd->HwInfo >> 21) & 7, + (HIChg >> 24) & 7 ? "*" : "", (pITd->HwInfo >> 24) & 7, + (HIChg >> 28) &15 ? "*" : "", (pITd->HwInfo >> 28) &15, + ITdOld.BP0 != pITd->BP0 ? "*" : "", pITd->BP0, + ITdOld.NextTD != pITd->NextTD ? "*" : "", pITd->NextTD, + ITdOld.BE != pITd->BE ? "*" : "", pITd->BE, + pszLogMsg)); + Log3(("psw0=%s%x:%s%03x psw1=%s%x:%s%03x psw2=%s%x:%s%03x psw3=%s%x:%s%03x psw4=%s%x:%s%03x psw5=%s%x:%s%03x psw6=%s%x:%s%03x psw7=%s%x:%s%03x\n", + (ITdOld.aPSW[0] >> 12) != (pITd->aPSW[0] >> 12) ? "*" : "", pITd->aPSW[0] >> 12, (ITdOld.aPSW[0] & 0xfff) != (pITd->aPSW[0] & 0xfff) ? "*" : "", pITd->aPSW[0] & 0xfff, + (ITdOld.aPSW[1] >> 12) != (pITd->aPSW[1] >> 12) ? "*" : "", pITd->aPSW[1] >> 12, (ITdOld.aPSW[1] & 0xfff) != (pITd->aPSW[1] & 0xfff) ? "*" : "", pITd->aPSW[1] & 0xfff, + (ITdOld.aPSW[2] >> 12) != (pITd->aPSW[2] >> 12) ? "*" : "", pITd->aPSW[2] >> 12, (ITdOld.aPSW[2] & 0xfff) != (pITd->aPSW[2] & 0xfff) ? "*" : "", pITd->aPSW[2] & 0xfff, + (ITdOld.aPSW[3] >> 12) != (pITd->aPSW[3] >> 12) ? "*" : "", pITd->aPSW[3] >> 12, (ITdOld.aPSW[3] & 0xfff) != (pITd->aPSW[3] & 0xfff) ? "*" : "", pITd->aPSW[3] & 0xfff, + (ITdOld.aPSW[4] >> 12) != (pITd->aPSW[4] >> 12) ? "*" : "", pITd->aPSW[4] >> 12, (ITdOld.aPSW[4] & 0xfff) != (pITd->aPSW[4] & 0xfff) ? "*" : "", pITd->aPSW[4] & 0xfff, + (ITdOld.aPSW[5] >> 12) != (pITd->aPSW[5] >> 12) ? "*" : "", pITd->aPSW[5] >> 12, (ITdOld.aPSW[5] & 0xfff) != (pITd->aPSW[5] & 0xfff) ? "*" : "", pITd->aPSW[5] & 0xfff, + (ITdOld.aPSW[6] >> 12) != (pITd->aPSW[6] >> 12) ? "*" : "", pITd->aPSW[6] >> 12, (ITdOld.aPSW[6] & 0xfff) != (pITd->aPSW[6] & 0xfff) ? "*" : "", pITd->aPSW[6] & 0xfff, + (ITdOld.aPSW[7] >> 12) != (pITd->aPSW[7] >> 12) ? "*" : "", pITd->aPSW[7] >> 12, (ITdOld.aPSW[7] & 0xfff) != (pITd->aPSW[7] & 0xfff) ? "*" : "", pITd->aPSW[7] & 0xfff)); + } +# else + RT_NOREF(pszLogMsg); +# endif + ohciR3PutDWords(pThis, ITdAddr, (uint32_t *)pITd, sizeof(*pITd) / sizeof(uint32_t)); +} + + +# ifdef LOG_ENABLED + +/** + * Core TD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueueCore(POHCI pThis, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + uint32_t GCPhys = GCPhysHead; + int cMax = 100; + for (;;) + { + OHCITD Td; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThis, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThis, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pThis, GCPhys, (uint32_t *)&Td, sizeof(Td) >> 2); + if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else + Log4((" -> ")); + GCPhys = Td.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + Assert(cMax-- > 0); NOREF(cMax); + } +} + +/** + * Dumps a TD queue. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpTdQueue(POHCI pThis, uint32_t GCPhysHead, const char *pszMsg) +{ + if (pszMsg) + Log4(("%s: ", pszMsg)); + ohciR3DumpTdQueueCore(pThis, GCPhysHead, 0, true); + Log4(("\n")); +} + +/** + * Core ITD queue dumper. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpITdQueueCore(POHCI pThis, uint32_t GCPhysHead, uint32_t GCPhysTail, bool fFull) +{ + RT_NOREF(fFull); + uint32_t GCPhys = GCPhysHead; + int cMax = 100; + for (;;) + { + OHCIITD ITd; + Log4(("%#010x%s%s", GCPhys, + GCPhys && ohciR3InFlightFind(pThis, GCPhys) >= 0 ? "~" : "", + GCPhys && ohciR3InDoneQueueFind(pThis, GCPhys) >= 0 ? "^" : "")); + if (GCPhys == 0 || GCPhys == GCPhysTail) + break; + + /* can't use ohciR3ReadTd() because of Log4. */ + ohciR3GetDWords(pThis, GCPhys, (uint32_t *)&ITd, sizeof(ITd) / sizeof(uint32_t)); + /*if (fFull) + Log4((" [R=%d DP=%d DI=%d T=%d EC=%d CC=%#x CBP=%#010x NextTD=%#010x BE=%#010x] -> ", + (Td.hwinfo >> 18) & 1, + (Td.hwinfo >> 19) & 3, + (Td.hwinfo >> 21) & 7, + (Td.hwinfo >> 24) & 3, + (Td.hwinfo >> 26) & 3, + (Td.hwinfo >> 28) &15, + Td.cbp, + Td.NextTD, + Td.be)); + else*/ + Log4((" -> ")); + GCPhys = ITd.NextTD & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + Assert(cMax-- > 0); NOREF(cMax); + } +} + +/** + * Dumps a ED list. LOG_ENABLED builds only. + */ +DECLINLINE(void) ohciR3DumpEdList(POHCI pThis, uint32_t GCPhysHead, const char *pszMsg, bool fTDs) +{ + RT_NOREF(fTDs); + uint32_t GCPhys = GCPhysHead; + if (pszMsg) + Log4(("%s:", pszMsg)); + for (;;) + { + OHCIED Ed; + + /* ED */ + Log4((" %#010x={", GCPhys)); + if (!GCPhys) + { + Log4(("END}\n")); + return; + } + + /* TDs */ + ohciR3ReadEd(pThis, GCPhys, &Ed); + if (Ed.hwinfo & ED_HWINFO_ISO) + Log4(("[I]")); + if ((Ed.HeadP & ED_HEAD_HALTED) || (Ed.hwinfo & ED_HWINFO_SKIP)) + { + if ((Ed.HeadP & ED_HEAD_HALTED) && (Ed.hwinfo & ED_HWINFO_SKIP)) + Log4(("SH}")); + else if (Ed.hwinfo & ED_HWINFO_SKIP) + Log4(("S-}")); + else + Log4(("-H}")); + } + else + { + if (Ed.hwinfo & ED_HWINFO_ISO) + ohciR3DumpITdQueueCore(pThis, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + else + ohciR3DumpTdQueueCore(pThis, Ed.HeadP & ED_PTR_MASK, Ed.TailP & ED_PTR_MASK, false); + Log4(("}")); + } + + /* next */ + GCPhys = Ed.NextED & ED_PTR_MASK; + Assert(GCPhys != GCPhysHead); + } + /* not reached */ +} + +# endif /* LOG_ENABLED */ + + +DECLINLINE(int) ohciR3InFlightFindFree(POHCI pThis, const int iStart) +{ + unsigned i = iStart; + while (i < RT_ELEMENTS(pThis->aInFlight)) + { + if (pThis->aInFlight[i].GCPhysTD == 0) + return i; + i++; + } + i = iStart; + while (i-- > 0) + { + if (pThis->aInFlight[i].GCPhysTD == 0) + return i; + } + return -1; +} + + +/** + * Record an in-flight TD. + * + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + * @param pUrb The URB. + */ +static void ohciR3InFlightAdd(POHCI pThis, uint32_t GCPhysTD, PVUSBURB pUrb) +{ + int i = ohciR3InFlightFindFree(pThis, (GCPhysTD >> 4) % RT_ELEMENTS(pThis->aInFlight)); + if (i >= 0) + { +# ifdef LOG_ENABLED + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; +# endif + pThis->aInFlight[i].GCPhysTD = GCPhysTD; + pThis->aInFlight[i].pUrb = pUrb; + pThis->cInFlight++; + return; + } + AssertMsgFailed(("Out of space cInFlight=%d!\n", pThis->cInFlight)); +} + + +/** + * Record in-flight TDs for an URB. + * + * @param pThis OHCI instance data. + * @param pUrb The URB. + */ +static void ohciR3InFlightAddUrb(POHCI pThis, PVUSBURB pUrb) +{ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + ohciR3InFlightAdd(pThis, pUrb->paTds[iTd].TdAddr, pUrb); +} + + +/** + * Finds a in-flight TD. + * + * @returns Index of the record. + * @returns -1 if not found. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + * @remark This has to be fast. + */ +static int ohciR3InFlightFind(POHCI pThis, uint32_t GCPhysTD) +{ + unsigned cLeft = pThis->cInFlight; + unsigned i = (GCPhysTD >> 4) % RT_ELEMENTS(pThis->aInFlight); + const int iLast = i; + while (i < RT_ELEMENTS(pThis->aInFlight)) + { + if (pThis->aInFlight[i].GCPhysTD == GCPhysTD) + return i; + if (pThis->aInFlight[i].GCPhysTD) + if (cLeft-- <= 1) + return -1; + i++; + } + i = iLast; + while (i-- > 0) + { + if (pThis->aInFlight[i].GCPhysTD == GCPhysTD) + return i; + if (pThis->aInFlight[i].GCPhysTD) + if (cLeft-- <= 1) + return -1; + } + return -1; +} + + +/** + * Checks if a TD is in-flight. + * + * @returns true if in flight, false if not. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3IsTdInFlight(POHCI pThis, uint32_t GCPhysTD) +{ + return ohciR3InFlightFind(pThis, GCPhysTD) >= 0; +} + +/** + * Returns a URB associated with an in-flight TD, if any. + * + * @returns pointer to URB if TD is in flight. + * @returns NULL if not in flight. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static PVUSBURB ohciR3TdInFlightUrb(POHCI pThis, uint32_t GCPhysTD) +{ + int i; + + i = ohciR3InFlightFind(pThis, GCPhysTD); + if ( i >= 0 ) + return pThis->aInFlight[i].pUrb; + return NULL; +} + +/** + * Removes a in-flight TD. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InFlightRemove(POHCI pThis, uint32_t GCPhysTD) +{ + int i = ohciR3InFlightFind(pThis, GCPhysTD); + if (i >= 0) + { +# ifdef LOG_ENABLED + const int cFramesInFlight = pThis->HcFmNumber - pThis->aInFlight[i].pUrb->pHci->u32FrameNo; +# else + const int cFramesInFlight = 0; +# endif + Log2(("ohciR3InFlightRemove: reaping TD=%#010x %d frames (%#010x-%#010x)\n", + GCPhysTD, cFramesInFlight, pThis->aInFlight[i].pUrb->pHci->u32FrameNo, pThis->HcFmNumber)); + pThis->aInFlight[i].GCPhysTD = 0; + pThis->aInFlight[i].pUrb = NULL; + pThis->cInFlight--; + return cFramesInFlight; + } + AssertMsgFailed(("TD %#010x is not in flight\n", GCPhysTD)); + return -1; +} + + +/** + * Removes all TDs associated with a URB from the in-flight tracking. + * + * @returns 0 if found. For logged builds this is the number of frames the TD has been in-flight. + * @returns -1 if not found. + * @param pThis OHCI instance data. + * @param pUrb The URB. + */ +static int ohciR3InFlightRemoveUrb(POHCI pThis, PVUSBURB pUrb) +{ + int cFramesInFlight = ohciR3InFlightRemove(pThis, pUrb->paTds[0].TdAddr); + if (pUrb->pHci->cTds > 1) + { + for (unsigned iTd = 1; iTd < pUrb->pHci->cTds; iTd++) + if (ohciR3InFlightRemove(pThis, pUrb->paTds[iTd].TdAddr) < 0) + cFramesInFlight = -1; + } + return cFramesInFlight; +} + + +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + +/** + * Empties the in-done-queue. + * @param pThis OHCI instance data. + */ +static void ohciR3InDoneQueueZap(POHCI pThis) +{ + pThis->cInDoneQueue = 0; +} + +/** + * Finds a TD in the in-done-queue. + * @returns >= 0 on success. + * @returns -1 if not found. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static int ohciR3InDoneQueueFind(POHCI pThis, uint32_t GCPhysTD) +{ + unsigned i = pThis->cInDoneQueue; + while (i-- > 0) + if (pThis->aInDoneQueue[i].GCPhysTD == GCPhysTD) + return i; + return -1; +} + +/** + * Checks that the specified TD is not in the done queue. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static bool ohciR3InDoneQueueCheck(POHCI pThis, uint32_t GCPhysTD) +{ + int i = ohciR3InDoneQueueFind(pThis, GCPhysTD); +# if 0 + /* This condition has been observed with the USB tablet emulation or with + * a real USB mouse and an SMP XP guest. I am also not sure if this is + * really a problem for us. The assertion checks that the guest doesn't + * re-submit a TD which is still in the done queue. It seems to me that + * this should only be a problem if we either keep track of TDs in the done + * queue somewhere else as well (in which case we should also free those + * references in time, and I can't see any code doing that) or if we + * manipulate TDs in the done queue in some way that might fail if they are + * re-submitted (can't see anything like that either). + */ + AssertMsg(i < 0, ("TD %#010x (i=%d)\n", GCPhysTD, i)); +# endif + return i < 0; +} + + +# if defined(VBOX_STRICT) && defined(LOG_ENABLED) +/** + * Adds a TD to the in-done-queue tracking, checking that it's not there already. + * @param pThis OHCI instance data. + * @param GCPhysTD Physical address of the TD. + */ +static void ohciR3InDoneQueueAdd(POHCI pThis, uint32_t GCPhysTD) +{ + Assert(pThis->cInDoneQueue + 1 <= RT_ELEMENTS(pThis->aInDoneQueue)); + if (ohciR3InDoneQueueCheck(pThis, GCPhysTD)) + pThis->aInDoneQueue[pThis->cInDoneQueue++].GCPhysTD = GCPhysTD; +} +# endif /* VBOX_STRICT */ +# endif /* defined(VBOX_STRICT) || defined(LOG_ENABLED) */ + + +/** + * OHCI Transport Buffer - represents a OHCI Transport Descriptor (TD). + * A TD may be split over max 2 pages. + */ +typedef struct OHCIBUF +{ + /** Pages involved. */ + struct OHCIBUFVEC + { + /** The 32-bit physical address of this part. */ + uint32_t Addr; + /** The length. */ + uint32_t cb; + } aVecs[2]; + /** Number of valid entries in aVecs. */ + uint32_t cVecs; + /** The total length. */ + uint32_t cbTotal; +} OHCIBUF, *POHCIBUF; + + +/** + * Sets up a OHCI transport buffer. + * + * @param pBuf Ohci buffer. + * @param cbp Current buffer pointer. 32-bit physical address. + * @param be Last byte in buffer (BufferEnd). 32-bit physical address. + */ +static void ohciR3BufInit(POHCIBUF pBuf, uint32_t cbp, uint32_t be) +{ + if (!cbp || !be) + { + pBuf->cVecs = 0; + pBuf->cbTotal = 0; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=0 EMPTY\n", cbp, be)); + } + else if ((cbp & ~0xfff) == (be & ~0xfff)) + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = (be - cbp) + 1; + pBuf->cVecs = 1; + pBuf->cbTotal = pBuf->aVecs[0].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u\n", cbp, be, pBuf->cbTotal)); + } + else + { + pBuf->aVecs[0].Addr = cbp; + pBuf->aVecs[0].cb = 0x1000 - (cbp & 0xfff); + pBuf->aVecs[1].Addr = be & ~0xfff; + pBuf->aVecs[1].cb = (be & 0xfff) + 1; + pBuf->cVecs = 2; + pBuf->cbTotal = pBuf->aVecs[0].cb + pBuf->aVecs[1].cb; + Log2(("ohci: cbp=%#010x be=%#010x cbTotal=%u PAGE FLIP\n", cbp, be, pBuf->cbTotal)); + } +} + +/** + * Updates a OHCI transport buffer. + * + * This is called upon completion to adjust the sector lengths if + * the total length has changed. (received less then we had space for + * or a partial transfer.) + * + * @param pBuf The buffer to update. cbTotal contains the new total on input. + * While the aVecs[*].cb members is updated upon return. + */ +static void ohciR3BufUpdate(POHCIBUF pBuf) +{ + for (uint32_t i = 0, cbCur = 0; i < pBuf->cVecs; i++) + { + if (cbCur + pBuf->aVecs[i].cb > pBuf->cbTotal) + { + pBuf->aVecs[i].cb = pBuf->cbTotal - cbCur; + pBuf->cVecs = i + 1; + return; + } + cbCur += pBuf->aVecs[i].cb; + } +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkIsochronousTdInList(POHCI pThis, uint32_t TdAddr, POHCIITD pITd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciUnlinkIsocTdInList: Unlinking non-head ITD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cMax = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cMax-- > 0) + { + OHCIITD ITd; + ohciR3ReadITd(pThis, CurTdAddr, &ITd); + if ((ITd.NextTD & ED_PTR_MASK) == TdAddr) + { + ITd.NextTD = (pITd->NextTD & ED_PTR_MASK) | (ITd.NextTD & ~ED_PTR_MASK); + ohciR3WriteITd(pThis, CurTdAddr, &ITd, "ohciUnlinkIsocTdInList"); + pITd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = ITd.NextTD & ED_PTR_MASK; + } + + Log(("ohciUnlinkIsocTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cMax=%d)\n", TdAddr, cMax)); + return false; +} + + +/** A worker for ohciR3UnlinkTds(). */ +static bool ohciR3UnlinkGeneralTdInList(POHCI pThis, uint32_t TdAddr, POHCITD pTd, POHCIED pEd) +{ + const uint32_t LastTdAddr = pEd->TailP & ED_PTR_MASK; + Log(("ohciR3UnlinkGeneralTdInList: Unlinking non-head TD! TdAddr=%#010RX32 HeadTdAddr=%#010RX32 LastEdAddr=%#010RX32\n", + TdAddr, pEd->HeadP & ED_PTR_MASK, LastTdAddr)); + AssertMsgReturn(LastTdAddr != TdAddr, ("TdAddr=%#010RX32\n", TdAddr), false); + + uint32_t cMax = 256; + uint32_t CurTdAddr = pEd->HeadP & ED_PTR_MASK; + while ( CurTdAddr != LastTdAddr + && cMax-- > 0) + { + OHCITD Td; + ohciR3ReadTd(pThis, CurTdAddr, &Td); + if ((Td.NextTD & ED_PTR_MASK) == TdAddr) + { + Td.NextTD = (pTd->NextTD & ED_PTR_MASK) | (Td.NextTD & ~ED_PTR_MASK); + ohciR3WriteTd(pThis, CurTdAddr, &Td, "ohciR3UnlinkGeneralTdInList"); + pTd->NextTD &= ~ED_PTR_MASK; + return true; + } + + /* next */ + CurTdAddr = Td.NextTD & ED_PTR_MASK; + } + + Log(("ohciR3UnlinkGeneralTdInList: TdAddr=%#010RX32 wasn't found in the list!!! (cMax=%d)\n", TdAddr, cMax)); + return false; +} + + +/** + * Unlinks the TDs that makes up the URB from the ED. + * + * @returns success indicator. true if successfully unlinked. + * @returns false if the TD was not found in the list. + */ +static bool ohciR3UnlinkTds(POHCI pThis, PVUSBURB pUrb, POHCIED pEd) +{ + /* + * Don't unlink more than once. + */ + if (pUrb->pHci->fUnlinked) + return true; + pUrb->pHci->fUnlinked = true; + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((ITdAddr & ED_PTR_MASK) == ITdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == ITdAddr) + { + pEd->HeadP = (pITd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pITd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * It's probably somewhere in the list, not a unlikely situation with + * the current isochronous code. + */ + if (!ohciR3UnlinkIsochronousTdInList(pThis, ITdAddr, pITd, pEd)) + return false; + } + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /** @todo r=bird: Messing with the toggle flag in prepare is probably not correct + * when we encounter a STALL error, 4.3.1.3.7.2: ''If an endpoint returns a STALL + * PID, the Host Controller retires the General TD with the ConditionCode set + * to STALL and halts the endpoint. The CurrentBufferPointer, ErrorCount, and + * dataToggle fields retain the values that they had at the start of the + * transaction.'' */ + + /* update toggle and set data toggle carry */ + pTd->hwinfo &= ~TD_HWINFO_TOGGLE; + if ( pTd->hwinfo & TD_HWINFO_TOGGLE_HI ) + { + if ( !!(pTd->hwinfo & TD_HWINFO_TOGGLE_LO) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pTd->hwinfo |= TD_HWINFO_TOGGLE_LO; + else + pTd->hwinfo &= ~TD_HWINFO_TOGGLE_LO; + } + else + { + if ( !!(pEd->HeadP & ED_HEAD_CARRY) ) /** @todo r=bird: is it just me or doesn't this make sense at all? */ + pEd->HeadP |= ED_HEAD_CARRY; + else + pEd->HeadP &= ~ED_HEAD_CARRY; + } + + /* + * Unlink the TD from the ED list. + * The normal case is that it's at the head of the list. + */ + Assert((TdAddr & ED_PTR_MASK) == TdAddr); + if ((pEd->HeadP & ED_PTR_MASK) == TdAddr) + { + pEd->HeadP = (pTd->NextTD & ED_PTR_MASK) | (pEd->HeadP & ~ED_PTR_MASK); + pTd->NextTD &= ~ED_PTR_MASK; + } + else + { + /* + * The TD is probably somewhere in the list. + * + * This shouldn't ever happen unless there was a failure! Even on failure, + * we can screw up the HCD state by picking out a TD from within the list + * like this! If this turns out to be a problem, we have to find a better + * solution. For now we'll hope the HCD handles it... + */ + if (!ohciR3UnlinkGeneralTdInList(pThis, TdAddr, pTd, pEd)) + return false; + } + + /* + * Only unlink the first TD on error. + * See comment in ohciR3RhXferCompleteGeneralURB(). + */ + if (pUrb->enmStatus != VUSBSTATUS_OK) + break; + } + } + + return true; +} + + +/** + * Checks that the transport descriptors associated with the URB + * hasn't been changed in any way indicating that they may have been canceled. + * + * This rountine also updates the TD copies contained within the URB. + * + * @returns true if the URB has been canceled, otherwise false. + * @param pThis The OHCI instance. + * @param pUrb The URB in question. + * @param pEd The ED pointer (optional). + */ +static bool ohciR3HasUrbBeenCanceled(POHCI pThis, PVUSBURB pUrb, PCOHCIED pEd) +{ + if (!pUrb) + return true; + + /* + * Make sure we've got an endpoint descriptor so we can + * check for tail TDs. + */ + OHCIED Ed; + if (!pEd) + { + ohciR3ReadEd(pThis, pUrb->pHci->EdAddr, &Ed); + pEd = &Ed; + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCIITD ITd; + uint32_t au32[8]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + ohciR3ReadITd(pThis, pUrb->paTds[iTd].TdAddr, &u.ITd); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* bp0 */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + || u.au32[4] != pUrb->paTds[iTd].TdCopy[4] /* psw0&1 */ + || u.au32[5] != pUrb->paTds[iTd].TdCopy[5] /* psw2&3 */ + || u.au32[6] != pUrb->paTds[iTd].TdCopy[6] /* psw4&5 */ + || u.au32[7] != pUrb->paTds[iTd].TdCopy[7] /* psw6&7 */ + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled! [iso]\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.ITd), &u.ITd, sizeof(u.ITd), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledIsocUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + else + { + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + union + { + OHCITD Td; + uint32_t au32[4]; + } u; + if ( (pUrb->paTds[iTd].TdAddr & ED_PTR_MASK) + == (pEd->TailP & ED_PTR_MASK)) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled (tail)!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + ohciR3ReadTd(pThis, pUrb->paTds[iTd].TdAddr, &u.Td); + if ( u.au32[0] != pUrb->paTds[iTd].TdCopy[0] /* hwinfo */ + || u.au32[1] != pUrb->paTds[iTd].TdCopy[1] /* cbp */ + || u.au32[3] != pUrb->paTds[iTd].TdCopy[3] /* be */ + || ( u.au32[2] != pUrb->paTds[iTd].TdCopy[2] /* NextTD */ + && iTd + 1 < pUrb->pHci->cTds /* ignore the last one */) + ) + { + Log(("%s: ohciR3HasUrbBeenCanceled: iTd=%d cTds=%d TdAddr=%#010RX32 canceled!\n", + pUrb->pszDesc, iTd, pUrb->pHci->cTds, pUrb->paTds[iTd].TdAddr)); + Log2((" %.*Rhxs (cur)\n" + "!= %.*Rhxs (copy)\n", + sizeof(u.Td), &u.Td, sizeof(u.Td), &pUrb->paTds[iTd].TdCopy[0])); + STAM_COUNTER_INC(&pThis->StatCanceledGenUrbs); + return true; + } + pUrb->paTds[iTd].TdCopy[2] = u.au32[2]; + } + } + return false; +} + + +/** + * Returns the OHCI_CC_* corresponding to the VUSB status code. + * + * @returns OHCI_CC_* value. + * @param enmStatus The VUSB status code. + */ +static uint32_t ohciR3VUsbStatus2OhciStatus(VUSBSTATUS enmStatus) +{ + switch (enmStatus) + { + case VUSBSTATUS_OK: return OHCI_CC_NO_ERROR; + case VUSBSTATUS_STALL: return OHCI_CC_STALL; + case VUSBSTATUS_CRC: return OHCI_CC_CRC; + case VUSBSTATUS_DATA_UNDERRUN: return OHCI_CC_DATA_UNDERRUN; + case VUSBSTATUS_DATA_OVERRUN: return OHCI_CC_DATA_OVERRUN; + case VUSBSTATUS_DNR: return OHCI_CC_DNR; + case VUSBSTATUS_NOT_ACCESSED: return OHCI_CC_NOT_ACCESSED_1; + default: + Log(("pUrb->enmStatus=%#x!!!\n", enmStatus)); + return OHCI_CC_DNR; + } +} + + +/** + * Lock the given OHCI controller instance. + * + * @returns nothing. + * @param pThis The OHCI controller instance to lock. + */ +DECLINLINE(void) ohciR3Lock(POHCI pThis) +{ + RTCritSectEnter(&pThis->CritSect); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Clear all caches here to avoid reading stale data from previous lock holders. */ + ohciR3PhysReadCacheClear(pThis->pCacheED); + ohciR3PhysReadCacheClear(pThis->pCacheTD); +# endif +} + + +/** + * Unlocks the given OHCI controller instance. + * + * @returns nothing. + * @param pThis The OHCI controller instance to unlock. + */ +DECLINLINE(void) ohciR3Unlock(POHCI pThis) +{ +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* + * Clear all caches here to avoid leaving stale data behind (paranoia^2, + * already done in ohciR3Lock). + */ + ohciR3PhysReadCacheClear(pThis->pCacheED); + ohciR3PhysReadCacheClear(pThis->pCacheTD); +# endif + + RTCritSectLeave(&pThis->CritSect); +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of isochronous TDs. + * + * In general, all URBs should have status OK. + */ +static void ohciR3RhXferCompleteIsochronousURB(POHCI pThis, PVUSBURB pUrb /*, POHCIED pEd , int cFmAge*/) +{ + /* + * Copy the data back (if IN operation) and update the TDs. + */ + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCIITD pITd = (POHCIITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t ITdAddr = pUrb->paTds[iTd].TdAddr; + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + unsigned R = (pUrb->pHci->u32FrameNo & ITD_HWINFO_SF) - (pITd->HwInfo & ITD_HWINFO_SF); + if (R >= 8) + R = 0; /* submitted ahead of time. */ + + /* + * Only one case of TD level condition code is document, so + * just set NO_ERROR here to reduce number duplicate code. + */ + pITd->HwInfo &= ~TD_HWINFO_CC; + AssertCompile(OHCI_CC_NO_ERROR == 0); + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + /* + * Update the frames and copy back the data. + * We assume that we don't get incorrect lengths here. + */ + for (unsigned i = 0; i < cFrames; i++) + { + if ( i < R + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_NOT_ACCESSED) + { + /* It should already be NotAccessed. */ + pITd->aPSW[i] |= 0xe000; /* (Don't touch the 12th bit.) */ + continue; + } + + /* Update the PSW (save the offset first in case of a IN). */ + uint32_t off = pITd->aPSW[i] & ITD_PSW_OFFSET; + pITd->aPSW[i] = ohciR3VUsbStatus2OhciStatus(pUrb->aIsocPkts[i - R].enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_OK + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->aIsocPkts[i - R].enmStatus == VUSBSTATUS_DATA_OVERRUN)) + { + /* Set the size. */ + const unsigned cb = pUrb->aIsocPkts[i - R].cb; + pITd->aPSW[i] |= cb & ITD_PSW_SIZE; + /* Copy data. */ + if (cb) + { + uint8_t *pb = &pUrb->abData[pUrb->aIsocPkts[i - R].off]; + if (off + cb > 0x1000) + { + if (off < 0x1000) + { + /* both */ + const unsigned cb0 = 0x1000 - off; + ohciR3PhysWrite(pThis, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb0); + ohciR3PhysWrite(pThis, pITd->BE & ITD_BP0_MASK, pb + cb0, cb - cb0); + } + else /* only in the 2nd page */ + ohciR3PhysWrite(pThis, (pITd->BE & ITD_BP0_MASK) + (off & ITD_BP0_MASK), pb, cb); + } + else /* only in the 1st page */ + ohciR3PhysWrite(pThis, (pITd->BP0 & ITD_BP0_MASK) + off, pb, cb); + Log5(("packet %d: off=%#x cb=%#x pb=%p (%#x)\n" + "%.*Rhxd\n", + i + R, off, cb, pb, pb - &pUrb->abData[0], cb, pb)); + //off += cb; + } + } + } + + /* + * If the last package ended with a NotAccessed status, set ITD CC + * to DataOverrun to indicate scheduling overrun. + */ + if (pUrb->aIsocPkts[pUrb->cIsocPkts - 1].enmStatus == VUSBSTATUS_NOT_ACCESSED) + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + } + else + { + Log(("DevOHCI: Taking untested code path at line %d...\n", __LINE__)); + /* + * Most status codes only applies to the individual packets. + * + * If we get a URB level error code of this kind, we'll distribute + * it to all the packages unless some other status is available for + * a package. This is a bit fuzzy, and we will get rid of this code + * before long! + */ + //if (pUrb->enmStatus != VUSBSTATUS_DATA_OVERRUN) + { + const unsigned uCC = ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus) + >> (TD_HWINFO_CC_SHIFT - ITD_PSW_CC_SHIFT); + for (unsigned i = 0; i < cFrames; i++) + pITd->aPSW[i] = uCC; + } + //else + // pITd->HwInfo |= ohciR3VUsbStatus2OhciStatus(pUrb->enmStatus); + } + + /* + * Update the done queue interrupt timer. + */ + uint32_t DoneInt = (pITd->HwInfo & ITD_HWINFO_DI) >> ITD_HWINFO_DI_SHIFT; + if ((pITd->HwInfo & TD_HWINFO_CC) != OHCI_CC_NO_ERROR) + DoneInt = 0; /* It's cleared on error. */ + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThis->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThis, ITdAddr); +# endif +# endif + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + Log(("%s: ohciR3RhXferCompleteIsochronousURB: ITdAddr=%#010x EdAddr=%#010x SF=%#x (%#x) CC=%#x FC=%d " + "psw0=%x:%x psw1=%x:%x psw2=%x:%x psw3=%x:%x psw4=%x:%x psw5=%x:%x psw6=%x:%x psw7=%x:%x R=%d\n", + pUrb->pszDesc, ITdAddr, + pUrb->pHci->EdAddr, + pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber, + (pITd->HwInfo & ITD_HWINFO_CC) >> ITD_HWINFO_CC_SHIFT, + (pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT, + pITd->aPSW[0] >> ITD_PSW_CC_SHIFT, pITd->aPSW[0] & ITD_PSW_SIZE, + pITd->aPSW[1] >> ITD_PSW_CC_SHIFT, pITd->aPSW[1] & ITD_PSW_SIZE, + pITd->aPSW[2] >> ITD_PSW_CC_SHIFT, pITd->aPSW[2] & ITD_PSW_SIZE, + pITd->aPSW[3] >> ITD_PSW_CC_SHIFT, pITd->aPSW[3] & ITD_PSW_SIZE, + pITd->aPSW[4] >> ITD_PSW_CC_SHIFT, pITd->aPSW[4] & ITD_PSW_SIZE, + pITd->aPSW[5] >> ITD_PSW_CC_SHIFT, pITd->aPSW[5] & ITD_PSW_SIZE, + pITd->aPSW[6] >> ITD_PSW_CC_SHIFT, pITd->aPSW[6] & ITD_PSW_SIZE, + pITd->aPSW[7] >> ITD_PSW_CC_SHIFT, pITd->aPSW[7] & ITD_PSW_SIZE, + R)); + ohciR3WriteITd(pThis, ITdAddr, pITd, "retired"); + } +} + + +/** + * Worker for ohciR3RhXferCompletion that handles the completion of + * a URB made up of general TDs. + */ +static void ohciR3RhXferCompleteGeneralURB(POHCI pThis, PVUSBURB pUrb, POHCIED pEd, int cFmAge) +{ + RT_NOREF(cFmAge); + + /* + * Copy the data back (if IN operation) and update the TDs. + */ + unsigned cbLeft = pUrb->cbData; + uint8_t *pb = &pUrb->abData[0]; + for (unsigned iTd = 0; iTd < pUrb->pHci->cTds; iTd++) + { + POHCITD pTd = (POHCITD)&pUrb->paTds[iTd].TdCopy[0]; + const uint32_t TdAddr = pUrb->paTds[iTd].TdAddr; + + /* + * Setup a ohci transfer buffer and calc the new cbp value. + */ + OHCIBUF Buf; + ohciR3BufInit(&Buf, pTd->cbp, pTd->be); + uint32_t NewCbp; + if (cbLeft >= Buf.cbTotal) + NewCbp = 0; + else + { + /* (len may have changed for short transfers) */ + Buf.cbTotal = cbLeft; + ohciR3BufUpdate(&Buf); + Assert(Buf.cVecs >= 1); + NewCbp = Buf.aVecs[Buf.cVecs-1].Addr + Buf.aVecs[Buf.cVecs-1].cb; + } + + /* + * Write back IN buffers. + */ + if ( pUrb->enmDir == VUSBDIRECTION_IN + && ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN) + && Buf.cbTotal > 0) + { + Assert(Buf.cVecs > 0); + ohciR3PhysWrite(pThis, Buf.aVecs[0].Addr, pb, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysWrite(pThis, Buf.aVecs[1].Addr, pb + Buf.aVecs[0].cb, Buf.aVecs[1].cb); + } + + /* advance the data buffer. */ + cbLeft -= Buf.cbTotal; + pb += Buf.cbTotal; + + /* + * Set writeback field. + */ + /* zero out writeback fields for retirement */ + pTd->hwinfo &= ~TD_HWINFO_CC; + /* always update the CurrentBufferPointer; essential for underrun/overrun errors */ + pTd->cbp = NewCbp; + + if (pUrb->enmStatus == VUSBSTATUS_OK) + { + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + + /* update done queue interrupt timer */ + uint32_t DoneInt = (pTd->hwinfo & TD_HWINFO_DI) >> 21; + if ( DoneInt != 0x7 + && DoneInt < pThis->dqic) + pThis->dqic = DoneInt; + Log(("%s: ohciR3RhXferCompleteGeneralURB: ED=%#010x TD=%#010x Age=%d enmStatus=%d cbTotal=%#x NewCbp=%#010RX32 dqic=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus, Buf.cbTotal, NewCbp, pThis->dqic)); + } + else + { + Log(("%s: ohciR3RhXferCompleteGeneralURB: HALTED ED=%#010x TD=%#010x (age %d) pUrb->enmStatus=%d\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, TdAddr, cFmAge, pUrb->enmStatus)); + pEd->HeadP |= ED_HEAD_HALTED; + pThis->dqic = 0; /* "If the Transfer Descriptor is being retired with an error, + * then the Done Queue Interrupt Counter is cleared as if the + * InterruptDelay field were zero." + */ + switch (pUrb->enmStatus) + { + case VUSBSTATUS_STALL: + pTd->hwinfo |= OHCI_CC_STALL; + break; + case VUSBSTATUS_CRC: + pTd->hwinfo |= OHCI_CC_CRC; + break; + case VUSBSTATUS_DATA_UNDERRUN: + pTd->hwinfo |= OHCI_CC_DATA_UNDERRUN; + break; + case VUSBSTATUS_DATA_OVERRUN: + pTd->hwinfo |= OHCI_CC_DATA_OVERRUN; + break; + default: /* what the hell */ + Log(("pUrb->enmStatus=%#x!!!\n", pUrb->enmStatus)); + RT_FALL_THRU(); + case VUSBSTATUS_DNR: + pTd->hwinfo |= OHCI_CC_DNR; + break; + } + } + + /* + * Move on to the done list and write back the modified TD. + */ +# ifdef LOG_ENABLED + if (!pThis->done) + pThis->u32FmDoneQueueTail = pThis->HcFmNumber; +# ifdef VBOX_STRICT + ohciR3InDoneQueueAdd(pThis, TdAddr); +# endif +# endif + pTd->NextTD = pThis->done; + pThis->done = TdAddr; + + ohciR3WriteTd(pThis, TdAddr, pTd, "retired"); + + /* + * If we've halted the endpoint, we stop here. + * ohciR3UnlinkTds() will make sure we've only unliked the first TD. + * + * The reason for this is that while we can have more than one TD in a URB, real + * OHCI hardware will only deal with one TD at the time and it's therefore incorrect + * to retire TDs after the endpoint has been halted. Win2k will crash or enter infinite + * kernel loop if we don't behave correctly. (See @bugref{1646}.) + */ + if (pEd->HeadP & ED_HEAD_HALTED) + break; + } +} + + +/** + * Transfer completion callback routine. + * + * VUSB will call this when a transfer have been completed + * in a one or another way. + * + * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(void) ohciR3RhXferCompletion(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + LogFlow(("%s: ohciR3RhXferCompletion: EdAddr=%#010RX32 cTds=%d TdAddr0=%#010RX32\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr)); + + ohciR3Lock(pThis); + pThis->fIdle = false; /* Mark as active */ + + /* get the current end point descriptor. */ + OHCIED Ed; + ohciR3ReadEd(pThis, pUrb->pHci->EdAddr, &Ed); + + /* + * Check that the URB hasn't been canceled and then try unlink the TDs. + * + * We drop the URB if the ED is marked halted/skip ASSUMING that this + * means the HCD has canceled the URB. + * + * If we succeed here (i.e. not dropping the URB), the TdCopy members will + * be updated but not yet written. We will delay the writing till we're done + * with the data copying, buffer pointer advancing and error handling. + */ + int cFmAge = ohciR3InFlightRemoveUrb(pThis, pUrb); + if (pUrb->enmStatus == VUSBSTATUS_UNDO) + { + /* Leave the TD alone - the HCD doesn't want us talking to the device. */ + Log(("%s: ohciR3RhXferCompletion: CANCELED {ED=%#010x cTds=%d TD0=%#010x age %d}\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge)); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThis); + return; + } + bool fHasBeenCanceled = false; + if ( (Ed.HeadP & ED_HEAD_HALTED) + || (Ed.hwinfo & ED_HWINFO_SKIP) + || cFmAge < 0 + || (fHasBeenCanceled = ohciR3HasUrbBeenCanceled(pThis, pUrb, &Ed)) + || !ohciR3UnlinkTds(pThis, pUrb, &Ed) + ) + { + Log(("%s: ohciR3RhXferCompletion: DROPPED {ED=%#010x cTds=%d TD0=%#010x age %d} because:%s%s%s%s%s!!!\n", + pUrb->pszDesc, pUrb->pHci->EdAddr, pUrb->pHci->cTds, pUrb->paTds[0].TdAddr, cFmAge, + (Ed.HeadP & ED_HEAD_HALTED) ? " ep halted" : "", + (Ed.hwinfo & ED_HWINFO_SKIP) ? " ep skip" : "", + (Ed.HeadP & ED_PTR_MASK) != pUrb->paTds[0].TdAddr ? " ep head-changed" : "", + cFmAge < 0 ? " td not-in-flight" : "", + fHasBeenCanceled ? " td canceled" : "")); + NOREF(fHasBeenCanceled); + STAM_COUNTER_INC(&pThis->StatDroppedUrbs); + ohciR3Unlock(pThis); + return; + } + + /* + * Complete the TD updating and write the back. + * When appropriate also copy data back to the guest memory. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + ohciR3RhXferCompleteIsochronousURB(pThis, pUrb /*, &Ed , cFmAge*/); + else + ohciR3RhXferCompleteGeneralURB(pThis, pUrb, &Ed, cFmAge); + + /* finally write back the endpoint descriptor. */ + ohciR3WriteEd(pThis, pUrb->pHci->EdAddr, &Ed); + ohciR3Unlock(pThis); +} + + +/** + * Handle transfer errors. + * + * VUSB calls this when a transfer attempt failed. This function will respond + * indicating whether to retry or complete the URB with failure. + * + * @returns true if the URB should be retired. + * @returns false if the URB should be retried. + * @param pInterface Pointer to OHCI::ROOTHUB::IRhPort. + * @param pUrb Pointer to the URB in question. + */ +static DECLCALLBACK(bool) ohciR3RhXferError(PVUSBIROOTHUBPORT pInterface, PVUSBURB pUrb) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + + /* + * Isochronous URBs can't be retried. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + return true; + + /* + * Don't retry on stall. + */ + if (pUrb->enmStatus == VUSBSTATUS_STALL) + { + Log2(("%s: ohciR3RhXferError: STALL, giving up.\n", pUrb->pszDesc)); + return true; + } + + ohciR3Lock(pThis); + bool fRetire = false; + /* + * Check if the TDs still are valid. + * This will make sure the TdCopy is up to date. + */ + const uint32_t TdAddr = pUrb->paTds[0].TdAddr; +/** @todo IMPORTANT! we must check if the ED is still valid at this point!!! */ + if (ohciR3HasUrbBeenCanceled(pThis, pUrb, NULL)) + { + Log(("%s: ohciR3RhXferError: TdAddr0=%#x canceled!\n", pUrb->pszDesc, TdAddr)); + fRetire = true; + } + else + { + /* + * Get and update the error counter. + */ + POHCITD pTd = (POHCITD)&pUrb->paTds[0].TdCopy[0]; + unsigned cErrs = (pTd->hwinfo & TD_HWINFO_ERRORS) >> TD_ERRORS_SHIFT; + pTd->hwinfo &= ~TD_HWINFO_ERRORS; + cErrs++; + pTd->hwinfo |= (cErrs % TD_ERRORS_MAX) << TD_ERRORS_SHIFT; + ohciR3WriteTd(pThis, TdAddr, pTd, "ohciR3RhXferError"); + + if (cErrs >= TD_ERRORS_MAX - 1) + { + Log2(("%s: ohciR3RhXferError: too many errors, giving up!\n", pUrb->pszDesc)); + fRetire = true; + } + else + Log2(("%s: ohciR3RhXferError: cErrs=%d: retrying...\n", pUrb->pszDesc, cErrs)); + } + + ohciR3Unlock(pThis); + return fRetire; +} + + +/** + * Service a general transport descriptor. + */ +static bool ohciR3ServiceTd(POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, uint32_t TdAddr, + uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TD and setup the buffer data. + */ + OHCITD Td; + ohciR3ReadTd(pThis, TdAddr, &Td); + OHCIBUF Buf; + ohciR3BufInit(&Buf, Td.cbp, Td.be); + + *pNextTdAddr = Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + switch (Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + case 0: enmDir = VUSBDIRECTION_SETUP; break; + default: + Log(("ohciR3ServiceTd: Invalid direction!!!! Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Td.hwinfo, pEd->hwinfo)); + /** @todo Do the correct thing here */ + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + enmType, enmDir, Buf.cbTotal, 1, NULL); + if (!pUrb) + return false; /* retry later... */ + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = !(Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->cTds = 1; + pUrb->paTds[0].TdAddr = TdAddr; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(Td)); + memcpy(pUrb->paTds[0].TdCopy, &Td, sizeof(Td)); + + /* copy data if out bound transfer. */ + pUrb->cbData = Buf.cbTotal; + if ( Buf.cbTotal + && Buf.cVecs > 0 + && enmDir != VUSBDIRECTION_IN) + { + ohciR3PhysRead(pThis, Buf.aVecs[0].Addr, pUrb->abData, Buf.aVecs[0].cb); + if (Buf.cVecs > 1) + ohciR3PhysRead(pThis, Buf.aVecs[1].Addr, &pUrb->abData[Buf.aVecs[0].cb], Buf.aVecs[1].cb); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAdd(pThis, TdAddr, pUrb); + Log(("%s: ohciR3ServiceTd: submitting TdAddr=%#010x EdAddr=%#010x cbData=%#x\n", + pUrb->pszDesc, TdAddr, EdAddr, pUrb->cbData)); + + ohciR3Unlock(pThis); + int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led); + ohciR3Lock(pThis); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTd: failed submitting TdAddr=%#010x EdAddr=%#010x pUrb=%p!!\n", + TdAddr, EdAddr, pUrb)); + ohciR3InFlightRemove(pThis, TdAddr); + return false; +} + + +/** + * Service a the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTd(POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * Read the TD, after first checking if it's already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThis, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThis, TdAddr); +# endif + return ohciR3ServiceTd(pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * Service one or more general transport descriptors (bulk or interrupt). + */ +static bool ohciR3ServiceTdMultiple(POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, + uint32_t TdAddr, uint32_t *pNextTdAddr, const char *pszListName) +{ + RT_NOREF(pszListName); + + /* + * Read the TDs involved in this URB. + */ + struct OHCITDENTRY + { + /** The TD. */ + OHCITD Td; + /** The associated OHCI buffer tracker. */ + OHCIBUF Buf; + /** The TD address. */ + uint32_t TdAddr; + /** Pointer to the next element in the chain (stack). */ + struct OHCITDENTRY *pNext; + } Head; + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheClear(pThis->pCacheTD); +# endif + + /* read the head */ + ohciR3ReadTd(pThis, TdAddr, &Head.Td); + ohciR3BufInit(&Head.Buf, Head.Td.cbp, Head.Td.be); + Head.TdAddr = TdAddr; + Head.pNext = NULL; + + /* combine with more TDs. */ + struct OHCITDENTRY *pTail = &Head; + unsigned cbTotal = pTail->Buf.cbTotal; + unsigned cTds = 1; + while ( (pTail->Buf.cbTotal == 0x1000 || pTail->Buf.cbTotal == 0x2000) + && !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING) /* This isn't right for *BSD, but let's not . */ + && (pTail->Td.NextTD & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && cTds < 128) + { + struct OHCITDENTRY *pCur = (struct OHCITDENTRY *)alloca(sizeof(*pCur)); + + pCur->pNext = NULL; + pCur->TdAddr = pTail->Td.NextTD & ED_PTR_MASK; + ohciR3ReadTd(pThis, pCur->TdAddr, &pCur->Td); + ohciR3BufInit(&pCur->Buf, pCur->Td.cbp, pCur->Td.be); + + /* Don't combine if the direction doesn't match up. There can't actually be + * a mismatch for bulk/interrupt EPs unless the guest is buggy. + */ + if ( (pCur->Td.hwinfo & (TD_HWINFO_DIR)) + != (Head.Td.hwinfo & (TD_HWINFO_DIR))) + break; + + pTail->pNext = pCur; + pTail = pCur; + cbTotal += pCur->Buf.cbTotal; + cTds++; + } + + /* calc next TD address */ + *pNextTdAddr = pTail->Td.NextTD & ED_PTR_MASK; + + /* + * Determine the direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: WARNING! Ed.hwdinfo=%#x bulk or interrupt EP shouldn't rely on the TD for direction...\n", pEd->hwinfo)); + switch (Head.Td.hwinfo & TD_HWINFO_DIR) + { + case TD_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case TD_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceTdMultiple: Invalid direction!!!! Head.Td.hwinfo=%#x Ed.hwdinfo=%#x\n", Head.Td.hwinfo, pEd->hwinfo)); + /** @todo Do the correct thing here */ + return false; + } + break; + } + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + enmType, enmDir, cbTotal, cTds, "ohciR3ServiceTdMultiple"); + if (!pUrb) + /* retry later... */ + return false; + Assert(pUrb->cbData == cbTotal); + + pUrb->enmType = enmType; + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->enmDir = enmDir; + pUrb->fShortNotOk = !(pTail->Td.hwinfo & TD_HWINFO_ROUNDING); + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->cTds = cTds; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + + /* Copy data and TD information. */ + unsigned iTd = 0; + uint8_t *pb = &pUrb->abData[0]; + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + { + /* data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN + && pCur->Buf.cVecs > 0) + { + ohciR3PhysRead(pThis, pCur->Buf.aVecs[0].Addr, pb, pCur->Buf.aVecs[0].cb); + if (pCur->Buf.cVecs > 1) + ohciR3PhysRead(pThis, pCur->Buf.aVecs[1].Addr, pb + pCur->Buf.aVecs[0].cb, pCur->Buf.aVecs[1].cb); + } + pb += pCur->Buf.cbTotal; + + /* TD info */ + pUrb->paTds[iTd].TdAddr = pCur->TdAddr; + AssertCompile(sizeof(pUrb->paTds[iTd].TdCopy) >= sizeof(pCur->Td)); + memcpy(pUrb->paTds[iTd].TdCopy, &pCur->Td, sizeof(pCur->Td)); + } + + /* + * Submit the URB. + */ + ohciR3InFlightAddUrb(pThis, pUrb); + Log(("%s: ohciR3ServiceTdMultiple: submitting cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x\n", + pUrb->pszDesc, pUrb->cbData, EdAddr, cTds, TdAddr)); + ohciR3Unlock(pThis); + int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led); + ohciR3Lock(pThis); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceTdMultiple: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d TdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, cTds, TdAddr, rc)); + for (struct OHCITDENTRY *pCur = &Head; pCur; pCur = pCur->pNext, iTd++) + ohciR3InFlightRemove(pThis, pCur->TdAddr); + return false; +} + + +/** + * Service the head TD of an endpoint. + */ +static bool ohciR3ServiceHeadTdMultiple(POHCI pThis, VUSBXFERTYPE enmType, PCOHCIED pEd, uint32_t EdAddr, const char *pszListName) +{ + /* + * First, check that it's not already in-flight. + */ + uint32_t TdAddr = pEd->HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThis, TdAddr)) + return false; +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueCheck(pThis, TdAddr); +# endif + return ohciR3ServiceTdMultiple(pThis, enmType, pEd, EdAddr, TdAddr, &TdAddr, pszListName); +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which unlinks a ITD + * that belongs to the past. + */ +static bool ohciR3ServiceIsochronousTdUnlink(POHCI pThis, POHCIITD pITd, uint32_t ITdAddr, uint32_t ITdAddrPrev, + PVUSBURB pUrb, POHCIED pEd, uint32_t EdAddr) +{ + LogFlow(("%s%sohciR3ServiceIsochronousTdUnlink: Unlinking ITD: ITdAddr=%#010x EdAddr=%#010x ITdAddrPrev=%#010x\n", + pUrb ? pUrb->pszDesc : "", pUrb ? ": " : "", ITdAddr, EdAddr, ITdAddrPrev)); + + /* + * Do the unlinking. + */ + const uint32_t ITdAddrNext = pITd->NextTD & ED_PTR_MASK; + if (ITdAddrPrev) + { + /* Get validate the previous TD */ + int iInFlightPrev = ohciR3InFlightFind(pThis, ITdAddrPrev); + AssertMsgReturn(iInFlightPrev >= 0, ("ITdAddr=%#RX32\n", ITdAddrPrev), false); + PVUSBURB pUrbPrev = pThis->aInFlight[iInFlightPrev].pUrb; + if (ohciR3HasUrbBeenCanceled(pThis, pUrbPrev, pEd)) /* ensures the copy is correct. */ + return false; + + /* Update the copy and write it back. */ + POHCIITD pITdPrev = ((POHCIITD)pUrbPrev->paTds[0].TdCopy); + pITdPrev->NextTD = (pITdPrev->NextTD & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteITd(pThis, ITdAddrPrev, pITdPrev, "ohciR3ServiceIsochronousEndpoint"); + } + else + { + /* It's the head node. update the copy from the caller and write it back. */ + pEd->HeadP = (pEd->HeadP & ~ED_PTR_MASK) | ITdAddrNext; + ohciR3WriteEd(pThis, EdAddr, pEd); + } + + /* + * If it's in flight, just mark the URB as unlinked (there is only one ITD per URB atm). + * Otherwise, retire it to the done queue with an error and cause a done line interrupt (?). + */ + if (pUrb) + { + pUrb->pHci->fUnlinked = true; + if (ohciR3HasUrbBeenCanceled(pThis, pUrb, pEd)) /* ensures the copy is correct (paranoia). */ + return false; + + POHCIITD pITdCopy = ((POHCIITD)pUrb->paTds[0].TdCopy); + pITd->NextTD = pITdCopy->NextTD &= ~ED_PTR_MASK; + } + else + { + pITd->HwInfo &= ~ITD_HWINFO_CC; + pITd->HwInfo |= OHCI_CC_DATA_OVERRUN; + + pITd->NextTD = pThis->done; + pThis->done = ITdAddr; + + pThis->dqic = 0; + } + + ohciR3WriteITd(pThis, ITdAddr, pITd, "ohciR3ServiceIsochronousTdUnlink"); + return true; +} + + +/** + * A worker for ohciR3ServiceIsochronousEndpoint which submits the specified + * TD. + * + * @returns true on success. + * @returns false on failure to submit. + * @param pThis The OHCI controller instance data. + * @param pITd The transfer descriptor to service. + * @param ITdAddr The address of the transfer descriptor in gues memory. + * @param R The start packet (frame) relative to the start of frame in HwInfo. + * @param pEd The OHCI endpoint descriptor. + * @param EdAddr The endpoint descriptor address in guest memory. + */ +static bool ohciR3ServiceIsochronousTd(POHCI pThis, POHCIITD pITd, uint32_t ITdAddr, const unsigned R, PCOHCIED pEd, uint32_t EdAddr) +{ + /* + * Determine the endpoint direction. + */ + VUSBDIRECTION enmDir; + switch (pEd->hwinfo & ED_HWINFO_DIR) + { + case ED_HWINFO_OUT: enmDir = VUSBDIRECTION_OUT; break; + case ED_HWINFO_IN: enmDir = VUSBDIRECTION_IN; break; + default: + Log(("ohciR3ServiceIsochronousTd: Invalid direction!!!! Ed.hwdinfo=%#x\n", pEd->hwinfo)); + /* Should probably raise an unrecoverable HC error here */ + return false; + } + + /* + * Extract the packet sizes and calc the total URB size. + */ + struct + { + uint16_t cb; + uint16_t off; + } aPkts[ITD_NUM_PSW]; + + /* first entry (R) */ + uint32_t cbTotal = 0; + if (((uint32_t)pITd->aPSW[R] >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, R, pITd->aPSW[R] >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + uint16_t offPrev = aPkts[0].off = (pITd->aPSW[R] & ITD_PSW_OFFSET); + + /* R+1..cFrames */ + const unsigned cFrames = ((pITd->HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + for (unsigned iR = R + 1; iR < cFrames; iR++) + { + const uint16_t PSW = pITd->aPSW[iR]; + const uint16_t off = aPkts[iR - R].off = (PSW & ITD_PSW_OFFSET); + cbTotal += aPkts[iR - R - 1].cb = off - offPrev; + if (off < offPrev) + Log(("ITdAddr=%RX32 PSW%d.offset=%#x < offPrev=%#x!\n", ITdAddr, iR, off, offPrev)); /* => Unrecoverable Error*/ + if (((uint32_t)PSW >> ITD_PSW_CC_SHIFT) < (OHCI_CC_NOT_ACCESSED_0 >> TD_HWINFO_CC_SHIFT)) + Log(("ITdAddr=%RX32 PSW%d.CC=%#x < 'Not Accessed'!\n", ITdAddr, iR, PSW >> ITD_PSW_CC_SHIFT)); /* => Unrecoverable Error*/ + offPrev = off; + } + + /* calc offEnd and figure out the size of the last packet. */ + const uint32_t offEnd = (pITd->BE & 0xfff) + + (((pITd->BE & ITD_BP0_MASK) != (pITd->BP0 & ITD_BP0_MASK)) << 12) + + 1 /* BE is inclusive */; + if (offEnd < offPrev) + Log(("ITdAddr=%RX32 offEnd=%#x < offPrev=%#x!\n", ITdAddr, offEnd, offPrev)); /* => Unrecoverable Error*/ + cbTotal += aPkts[cFrames - 1 - R].cb = offEnd - offPrev; + Assert(cbTotal <= 0x2000); + + pThis->fIdle = false; /* Mark as active */ + + /* + * Allocate and initialize a new URB. + */ + PVUSBURB pUrb = VUSBIRhNewUrb(pThis->RootHub.pIRhConn, pEd->hwinfo & ED_HWINFO_FUNCTION, NULL, + VUSBXFERTYPE_ISOC, enmDir, cbTotal, 1, NULL); + if (!pUrb) + /* retry later... */ + return false; + + pUrb->EndPt = (pEd->hwinfo & ED_HWINFO_ENDPOINT) >> ED_HWINFO_ENDPOINT_SHIFT; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_OK; + pUrb->pHci->EdAddr = EdAddr; + pUrb->pHci->cTds = 1; + pUrb->pHci->fUnlinked = false; + pUrb->pHci->u32FrameNo = pThis->HcFmNumber; + pUrb->paTds[0].TdAddr = ITdAddr; + AssertCompile(sizeof(pUrb->paTds[0].TdCopy) >= sizeof(*pITd)); + memcpy(pUrb->paTds[0].TdCopy, pITd, sizeof(*pITd)); +# if 0 /* color the data */ + memset(pUrb->abData, 0xfe, cbTotal); +# endif + + /* copy the data */ + if ( cbTotal + && enmDir != VUSBDIRECTION_IN) + { + const uint32_t off0 = pITd->aPSW[R] & ITD_PSW_OFFSET; + if (off0 < 0x1000) + { + if (offEnd > 0x1000) + { + /* both pages. */ + const unsigned cb0 = 0x1000 - off0; + ohciR3PhysRead(pThis, (pITd->BP0 & ITD_BP0_MASK) + off0, &pUrb->abData[0], cb0); + ohciR3PhysRead(pThis, pITd->BE & ITD_BP0_MASK, &pUrb->abData[cb0], offEnd & 0xfff); + } + else /* a portion of the 1st page. */ + ohciR3PhysRead(pThis, (pITd->BP0 & ITD_BP0_MASK) + off0, pUrb->abData, offEnd - off0); + } + else /* a portion of the 2nd page. */ + ohciR3PhysRead(pThis, (pITd->BE & UINT32_C(0xfffff000)) + (off0 & 0xfff), pUrb->abData, cbTotal); + } + + /* setup the packets */ + pUrb->cIsocPkts = cFrames - R; + unsigned off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + pUrb->aIsocPkts[i].enmStatus = VUSBSTATUS_NOT_ACCESSED; + pUrb->aIsocPkts[i].off = off; + off += pUrb->aIsocPkts[i].cb = aPkts[i].cb; + } + Assert(off == cbTotal); + + /* + * Submit the URB. + */ + ohciR3InFlightAddUrb(pThis, pUrb); + Log(("%s: ohciR3ServiceIsochronousTd: submitting cbData=%#x cIsocPkts=%d EdAddr=%#010x TdAddr=%#010x SF=%#x (%#x)\n", + pUrb->pszDesc, pUrb->cbData, pUrb->cIsocPkts, EdAddr, ITdAddr, pITd->HwInfo & ITD_HWINFO_SF, pThis->HcFmNumber)); + ohciR3Unlock(pThis); + int rc = VUSBIRhSubmitUrb(pThis->RootHub.pIRhConn, pUrb, &pThis->RootHub.Led); + ohciR3Lock(pThis); + if (RT_SUCCESS(rc)) + return true; + + /* Failure cleanup. Can happen if we're still resetting the device or out of resources. */ + Log(("ohciR3ServiceIsochronousTd: failed submitting pUrb=%p cbData=%#x EdAddr=%#010x cTds=%d ITdAddr0=%#010x - rc=%Rrc\n", + pUrb, cbTotal, EdAddr, 1, ITdAddr, rc)); + ohciR3InFlightRemove(pThis, ITdAddr); + return false; +} + + +/** + * Service an isochronous endpoint. + */ +static void ohciR3ServiceIsochronousEndpoint(POHCI pThis, POHCIED pEd, uint32_t EdAddr) +{ + /* + * We currently process this as if the guest follows the interrupt end point chaining + * hierarchy described in the documenation. This means that for an isochronous endpoint + * with a 1 ms interval we expect to find in-flight TDs at the head of the list. We will + * skip over all in-flight TDs which timeframe has been exceed. Those which aren't in + * flight but which are too late will be retired (possibly out of order, but, we don't + * care right now). + * + * When we reach a TD which still has a buffer which is due for take off, we will + * stop iterating TDs. If it's in-flight, there isn't anything to be done. Otherwise + * we will push it onto the runway for immediate take off. In this process we + * might have to complete buffers which didn't make it on time, something which + * complicates the kind of status info we need to keep around for the TD. + * + * Note: We're currently not making any attempt at reassembling ITDs into URBs. + * However, this will become necessary because of EMT scheduling and guest + * like linux using one TD for each frame (simple but inefficient for us). + */ + OHCIITD ITd; + uint32_t ITdAddr = pEd->HeadP & ED_PTR_MASK; + uint32_t ITdAddrPrev = 0; + uint32_t u32NextFrame = UINT32_MAX; + const uint16_t u16CurFrame = pThis->HcFmNumber; + for (;;) + { + /* check for end-of-chain. */ + if ( ITdAddr == (pEd->TailP & ED_PTR_MASK) + || !ITdAddr) + break; + + /* + * If isochronous endpoints are around, don't slow down the timer. Getting the timing right + * is difficult enough as it is. + */ + pThis->fIdle = false; + + /* + * Read the current ITD and check what we're supposed to do about it. + */ + ohciR3ReadITd(pThis, ITdAddr, &ITd); + const uint32_t ITdAddrNext = ITd.NextTD & ED_PTR_MASK; + const int16_t R = u16CurFrame - (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF); /* 4.3.2.3 */ + const int16_t cFrames = ((ITd.HwInfo & ITD_HWINFO_FC) >> ITD_HWINFO_FC_SHIFT) + 1; + + if (R < cFrames) + { + /* + * It's inside the current or a future launch window. + * + * We will try maximize the TD in flight here to deal with EMT scheduling + * issues and similar stuff which will screw up the time. So, we will only + * stop submitting TD when we reach a gap (in time) or end of the list. + */ + if ( R < 0 /* (a future frame) */ + && (uint16_t)u32NextFrame != (uint16_t)(ITd.HwInfo & ITD_HWINFO_SF)) + break; + if (ohciR3InFlightFind(pThis, ITdAddr) < 0) + if (!ohciR3ServiceIsochronousTd(pThis, &ITd, ITdAddr, R < 0 ? 0 : R, pEd, EdAddr)) + break; + + ITdAddrPrev = ITdAddr; + } + else + { +# if 1 + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * Don't remove in-flight TDs before they complete. + * Windows will, upon the completion of another ITD it seems, check for if + * any other TDs has been unlinked. If we unlink them before they really + * complete all the packet status codes will be NotAccessed and Windows + * will fail the URB with status USBD_STATUS_ISOCH_REQUEST_FAILED. + * + * I don't know if unlinking TDs out of order could cause similar problems, + * time will show. + */ + int iInFlight = ohciR3InFlightFind(pThis, ITdAddr); + if (iInFlight >= 0) + ITdAddrPrev = ITdAddr; + else if (!ohciR3ServiceIsochronousTdUnlink(pThis, &ITd, ITdAddr, ITdAddrPrev, + NULL, pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# else /* BAD IDEA: */ + /* + * Ok, the launch window for this TD has passed. + * If it's not in flight it should be retired with a DataOverrun status (TD). + * + * If it's in flight we will try unlink it from the list prematurely to + * help the guest to move on and shorten the list we have to walk. We currently + * are successful with the first URB but then it goes too slowly... + */ + int iInFlight = ohciR3InFlightFind(pThis, ITdAddr); + if (!ohciR3ServiceIsochronousTdUnlink(pThis, &ITd, ITdAddr, ITdAddrPrev, + iInFlight < 0 ? NULL : pThis->aInFlight[iInFlight].pUrb, + pEd, EdAddr)) + { + Log(("ohciR3ServiceIsochronousEndpoint: Failed unlinking old ITD.\n")); + break; + } +# endif + } + + /* advance to the next ITD */ + ITdAddr = ITdAddrNext; + u32NextFrame = (ITd.HwInfo & ITD_HWINFO_SF) + cFrames; + } +} + + +/** + * Checks if a endpoints has TDs queued and is ready to have them processed. + * + * @returns true if it's ok to process TDs. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdReady(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED) + && !(pEd->hwinfo & ED_HWINFO_SKIP); +} + + +/** + * Checks if an endpoint has TDs queued (not necessarily ready to have them processed). + * + * @returns true if endpoint may have TDs queued. + * @param pEd The endpoint data. + */ +DECLINLINE(bool) ohciR3IsEdPresent(PCOHCIED pEd) +{ + return (pEd->HeadP & ED_PTR_MASK) != (pEd->TailP & ED_PTR_MASK) + && !(pEd->HeadP & ED_HEAD_HALTED); +} + + +/** + * Services the bulk list. + * + * On the bulk list we must reassemble URBs from multiple TDs using heuristics + * derived from USB tracing done in the guests and guest source code (when available). + */ +static void ohciR3ServiceBulkList(POHCI pThis) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pThis, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3ServiceBulkList: bulk_cur=%#010x before listprocessing!!! HCD have positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* + * ", HC will start processing the Bulk list and will set BF [BulkListFilled] to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = false; + pThis->bulk_cur = 0; + + uint32_t EdAddr = pThis->bulk_head; + while (EdAddr) + { + OHCIED Ed; + ohciR3ReadEd(pThis, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { + pThis->status |= OHCI_STATUS_BLF; + pThis->fBulkNeedsCleaning = true; + +# if 1 + /* + + * After we figured out that all the TDs submitted for dealing with MSD + * read/write data really makes up on single URB, and that we must + * reassemble these TDs into an URB before submitting it, there is no + * longer any need for servicing anything other than the head *URB* + * on a bulk endpoint. + */ + ohciR3ServiceHeadTdMultiple(pThis, VUSBXFERTYPE_BULK, &Ed, EdAddr, "Bulk"); +# else + /* + * This alternative code was used before we started reassembling URBs from + * multiple TDs. We keep it handy for debugging. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (!ohciR3IsTdInFlight(pThis, TdAddr)) + { + do + { + if (!ohciR3ServiceTdMultiple(pThis, VUSBXFERTYPE_BULK, &Ed, EdAddr, TdAddr, &TdAddr, "Bulk")) + { + LogFlow(("ohciR3ServiceBulkList: ohciR3ServiceTdMultiple -> false\n")); + break; + } + if ( (TdAddr & ED_PTR_MASK) == (Ed.TailP & ED_PTR_MASK) + || !TdAddr /* paranoia */) + { + LogFlow(("ohciR3ServiceBulkList: TdAddr=%#010RX32 Ed.TailP=%#010RX32\n", TdAddr, Ed.TailP)); + break; + } + + ohciR3ReadEd(pThis, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); + } +# endif + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + LogFlow(("ohciR3ServiceBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThis, TdAddr); + if (pUrb) + pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb); + } + } + + /* next end point */ + EdAddr = Ed.NextED & ED_PTR_MASK; + + } + +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pThis, pThis->bulk_head, "Bulk after ", true); +# endif +} + +/** + * Abort outstanding transfers on the bulk list. + * + * If the guest disabled bulk list processing, we must abort any outstanding transfers + * (that is, cancel in-flight URBs associated with the list). This is required because + * there may be outstanding read URBs that will never get a response from the device + * and would block further communication. + */ +static void ohciR3UndoBulkList(POHCI pThis) +{ +# ifdef LOG_ENABLED + if (g_fLogBulkEPs) + ohciR3DumpEdList(pThis, pThis->bulk_head, "Bulk before", true); + if (pThis->bulk_cur) + Log(("ohciR3UndoBulkList: bulk_cur=%#010x before list processing!!! HCD has positioned us!!!\n", pThis->bulk_cur)); +# endif + + /* This flag follows OHCI_STATUS_BLF, but BLF doesn't change when list processing is disabled. */ + pThis->fBulkNeedsCleaning = false; + + uint32_t EdAddr = pThis->bulk_head; + while (EdAddr) + { + OHCIED Ed; + + ohciR3ReadEd(pThis, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdPresent(&Ed)) + { + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + if (ohciR3IsTdInFlight(pThis, TdAddr)) + { + LogFlow(("ohciR3UndoBulkList: Ed=%#010RX32 Ed.TailP=%#010RX32 UNDO\n", EdAddr, Ed.TailP)); + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThis, TdAddr); + if (pUrb) + pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb); + } + } + /* next endpoint */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } +} + + +/** + * Services the control list. + * + * The control list has complex URB assembling, but that's taken + * care of at VUSB level (unlike the other transfer types). + */ +static void ohciR3ServiceCtrlList(POHCI pThis) +{ +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pThis, pThis->ctrl_head, "Ctrl before", true); + if (pThis->ctrl_cur) + Log(("ohciR3ServiceCtrlList: ctrl_cur=%010x before list processing!!! HCD have positioned us!!!\n", pThis->ctrl_cur)); +# endif + + /* + * ", HC will start processing the list and will set ControlListFilled to 0" + * - We've simplified and are always starting at the head of the list and working + * our way thru to the end each time. + */ + pThis->status &= ~OHCI_STATUS_CLF; + pThis->ctrl_cur = 0; + + uint32_t EdAddr = pThis->ctrl_head; + while (EdAddr) + { + OHCIED Ed; + ohciR3ReadEd(pThis, EdAddr, &Ed); + Assert(!(Ed.hwinfo & ED_HWINFO_ISO)); /* the guest is screwing us */ + if (ohciR3IsEdReady(&Ed)) + { +# if 1 + /* + * Control TDs depends on order and stage. Only one can be in-flight + * at any given time. OTOH, some stages are completed immediately, + * so we process the list until we've got a head which is in-flight + * or reach the end of the list. + */ + do + { + if ( !ohciR3ServiceHeadTd(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control") + || ohciR3IsTdInFlight(pThis, Ed.HeadP & ED_PTR_MASK)) + { + pThis->status |= OHCI_STATUS_CLF; + break; + } + ohciR3ReadEd(pThis, EdAddr, &Ed); /* It might have been updated on URB completion. */ + } while (ohciR3IsEdReady(&Ed)); +# else + /* Simplistic, for debugging. */ + ohciR3ServiceHeadTd(pThis, VUSBXFERTYPE_CTRL, &Ed, EdAddr, "Control"); + pThis->status |= OHCI_STATUS_CLF; +# endif + } + + /* next end point */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogControlEPs) + ohciR3DumpEdList(pThis, pThis->ctrl_head, "Ctrl after ", true); +# endif +} + + +/** + * Services the periodic list. + * + * On the interrupt portion of the periodic list we must reassemble URBs from multiple + * TDs using heuristics derived from USB tracing done in the guests and guest source + * code (when available). + */ +static void ohciR3ServicePeriodicList(POHCI pThis) +{ + /* + * Read the list head from the HCCA. + */ + const unsigned iList = pThis->HcFmNumber % OHCI_HCCA_NUM_INTR; + uint32_t EdAddr; + ohciR3GetDWords(pThis, pThis->hcca + iList * sizeof(EdAddr), &EdAddr, 1); + +# ifdef LOG_ENABLED + const uint32_t EdAddrHead = EdAddr; + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x before", iList); + ohciR3DumpEdList(pThis, EdAddrHead, sz, true); + } +# endif + + /* + * Iterate the endpoint list. + */ + while (EdAddr) + { + OHCIED Ed; + + ohciR3ReadEd(pThis, EdAddr, &Ed); + if (ohciR3IsEdReady(&Ed)) + { + /* + * "There is no separate head pointer of isochronous transfers. The first + * isochronous Endpoint Descriptor simply links to the last interrupt + * Endpoint Descriptor." + */ + if (!(Ed.hwinfo & ED_HWINFO_ISO)) + { + /* + * Presently we will only process the head URB on an interrupt endpoint. + */ + ohciR3ServiceHeadTdMultiple(pThis, VUSBXFERTYPE_INTR, &Ed, EdAddr, "Periodic"); + } + else if (pThis->ctl & OHCI_CTL_IE) + { + /* + * Presently only the head ITD. + */ + ohciR3ServiceIsochronousEndpoint(pThis, &Ed, EdAddr); + } + else + break; + } + else + { + if (Ed.hwinfo & ED_HWINFO_SKIP) + { + Log3(("ohciR3ServicePeriodicList: Ed=%#010RX32 Ed.TailP=%#010RX32 SKIP\n", EdAddr, Ed.TailP)); + /* If the ED is in 'skip' state, no transactions on it are allowed and we must + * cancel outstanding URBs, if any. + */ + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + PVUSBURB pUrb = ohciR3TdInFlightUrb(pThis, TdAddr); + if (pUrb) + pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb); + } + } + /* next end point */ + EdAddr = Ed.NextED & ED_PTR_MASK; + } + +# ifdef LOG_ENABLED + if (g_fLogInterruptEPs) + { + char sz[48]; + RTStrPrintf(sz, sizeof(sz), "Int%02x after ", iList); + ohciR3DumpEdList(pThis, EdAddrHead, sz, true); + } +# endif +} + + +/** + * Update the HCCA. + * + * @param pThis The OHCI instance data. + */ +static void ohciR3UpdateHCCA(POHCI pThis) +{ + struct ohci_hcca hcca; + ohciR3PhysRead(pThis, pThis->hcca + OHCI_HCCA_OFS, &hcca, sizeof(hcca)); + + hcca.frame = RT_H2LE_U16((uint16_t)pThis->HcFmNumber); + hcca.pad = 0; + + bool fWriteDoneHeadInterrupt = false; + if ( pThis->dqic == 0 + && (pThis->intr_status & OHCI_INTR_WRITE_DONE_HEAD) == 0) + { + uint32_t done = pThis->done; + + if (pThis->intr_status & ~( OHCI_INTR_MASTER_INTERRUPT_ENABLED | OHCI_INTR_OWNERSHIP_CHANGE + | OHCI_INTR_WRITE_DONE_HEAD) ) + done |= 0x1; + + hcca.done = RT_H2LE_U32(done); + pThis->done = 0; + pThis->dqic = 0x7; + + Log(("ohci: Writeback Done (%#010x) on frame %#x (age %#x)\n", hcca.done, + pThis->HcFmNumber, pThis->HcFmNumber - pThis->u32FmDoneQueueTail)); +# ifdef LOG_ENABLED + ohciR3DumpTdQueue(pThis, hcca.done & ED_PTR_MASK, "DoneQueue"); +# endif + Assert(RT_OFFSETOF(struct ohci_hcca, done) == 4); +# if defined(VBOX_STRICT) || defined(LOG_ENABLED) + ohciR3InDoneQueueZap(pThis); +# endif + fWriteDoneHeadInterrupt = true; + } + + Log3(("ohci: Updating HCCA on frame %#x\n", pThis->HcFmNumber)); + ohciR3PhysWrite(pThis, pThis->hcca + OHCI_HCCA_OFS, (uint8_t *)&hcca, sizeof(hcca)); + if (fWriteDoneHeadInterrupt) + ohciR3SetInterrupt(pThis, OHCI_INTR_WRITE_DONE_HEAD); +} + + +/** + * Go over the in-flight URB list and cancel any URBs that are no longer in use. + * This occurs when the host removes EDs or TDs from the lists and we don't notice + * the sKip bit. Such URBs must be promptly canceled, otherwise there is a risk + * they might "steal" data destined for another URB. + */ +static void ohciR3CancelOrphanedURBs(POHCI pThis) +{ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + unsigned i, cLeft; + int j; + uint32_t EdAddr; + PVUSBURB pUrb; + + /* If the HCCA is not currently valid, or there are no in-flight URBs, + * there's nothing to do. + */ + if (!fValidHCCA || !pThis->cInFlight) + return; + + /* Initially mark all in-flight URBs as inactive. */ + for (i = 0, cLeft = pThis->cInFlight; cLeft && i < RT_ELEMENTS(pThis->aInFlight); i++) + { + if (pThis->aInFlight[i].pUrb) + { + pThis->aInFlight[i].fInactive = true; + cLeft--; + } + } + Assert(cLeft == 0); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + /* Get hcca data to minimize calls to ohciR3GetDWords/PDMDevHlpPhysRead. */ + uint32_t au32HCCA[OHCI_HCCA_NUM_INTR]; + ohciR3GetDWords(pThis, pThis->hcca, au32HCCA, OHCI_HCCA_NUM_INTR); +# endif + + /* Go over all bulk/control/interrupt endpoint lists; any URB found in these lists + * is marked as active again. + */ + for (i = 0; i < OHCI_HCCA_NUM_INTR + 2; i++) + { + switch (i) + { + case OHCI_HCCA_NUM_INTR: + EdAddr = pThis->bulk_head; + break; + case OHCI_HCCA_NUM_INTR + 1: + EdAddr = pThis->ctrl_head; + break; + default: +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + EdAddr = au32HCCA[i]; +# else + ohciR3GetDWords(pThis, pThis->hcca + i * sizeof(EdAddr), &EdAddr, 1); +# endif + break; + } + while (EdAddr) + { + OHCIED Ed; + OHCITD Td; + + ohciR3ReadEd(pThis, EdAddr, &Ed); + uint32_t TdAddr = Ed.HeadP & ED_PTR_MASK; + uint32_t TailP = Ed.TailP & ED_PTR_MASK; + unsigned k = 0; + if ( !(Ed.hwinfo & ED_HWINFO_SKIP) + && (TdAddr != TailP)) + { +# ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheClear(pThis->pCacheTD); +# endif + do + { + ohciR3ReadTd(pThis, TdAddr, &Td); + j = ohciR3InFlightFind(pThis, TdAddr); + if (j > -1) + pThis->aInFlight[j].fInactive = false; + TdAddr = Td.NextTD & ED_PTR_MASK; + /* See #8125. + * Sometimes the ED is changed by the guest between ohciR3ReadEd above and here. + * Then the code reads TD pointed by the new TailP, which is not allowed. + * Luckily Windows guests have Td.NextTD = 0 in the tail TD. + * Also having a real TD at 0 is very unlikely. + * So do not continue. + */ + if (TdAddr == 0) + break; + /* Failsafe for temporarily looped lists. */ + if (++k == 128) + break; + } while (TdAddr != (Ed.TailP & ED_PTR_MASK)); + } + EdAddr = Ed.NextED & ED_PTR_MASK; + } + } + + /* In-flight URBs still marked as inactive are not used anymore and need + * to be canceled. + */ + for (i = 0, cLeft = pThis->cInFlight; cLeft && i < RT_ELEMENTS(pThis->aInFlight); i++) + { + if (pThis->aInFlight[i].pUrb) + { + cLeft--; + pUrb = pThis->aInFlight[i].pUrb; + if (pThis->aInFlight[i].fInactive + && pUrb->enmState == VUSBURBSTATE_IN_FLIGHT + && pUrb->enmType != VUSBXFERTYPE_CTRL) + pThis->RootHub.pIRhConn->pfnCancelUrbsEp(pThis->RootHub.pIRhConn, pUrb); + } + } + Assert(cLeft == 0); +} + +/** + * Generate a Start-Of-Frame event, and set a timer for End-Of-Frame. + */ +static void ohciR3StartOfFrame(POHCI pThis) +{ +# ifdef LOG_ENABLED + const uint32_t status_old = pThis->status; +# endif + + /* + * Update HcFmRemaining.FRT and update start of frame time. + */ + pThis->frt = pThis->fit; + pThis->SofTime += pThis->cTicksPerFrame; + + /* + * Check that the HCCA address isn't bogus. Linux 2.4.x is known to start + * the bus with a hcca of 0 to work around problem with a specific controller. + */ + bool fValidHCCA = !( pThis->hcca >= OHCI_HCCA_MASK + || pThis->hcca < ~OHCI_HCCA_MASK); + +# if 1 + /* + * Update the HCCA. + * Should be done after SOF but before HC read first ED in this frame. + */ + if (fValidHCCA) + ohciR3UpdateHCCA(pThis); +# endif + + /* "After writing to HCCA, HC will set SF in HcInterruptStatus" - guest isn't executing, so ignore the order! */ + ohciR3SetInterrupt(pThis, OHCI_INTR_START_OF_FRAME); + + if (pThis->fno) + { + ohciR3SetInterrupt(pThis, OHCI_INTR_FRAMENUMBER_OVERFLOW); + pThis->fno = 0; + } + + /* If the HCCA address is invalid, we're quitting here to avoid doing something which cannot be reported to the HCD. */ + if (!fValidHCCA) + { + Log(("ohciR3StartOfFrame: skipping hcca part because hcca=%RX32 (our 'valid' range: %RX32-%RX32)\n", + pThis->hcca, ~OHCI_HCCA_MASK, OHCI_HCCA_MASK)); + return; + } + + /* + * Periodic EPs. + */ + if (pThis->ctl & OHCI_CTL_PLE) + ohciR3ServicePeriodicList(pThis); + + /* + * Control EPs. + */ + if ( (pThis->ctl & OHCI_CTL_CLE) + && (pThis->status & OHCI_STATUS_CLF) ) + ohciR3ServiceCtrlList(pThis); + + /* + * Bulk EPs. + */ + if ( (pThis->ctl & OHCI_CTL_BLE) + && (pThis->status & OHCI_STATUS_BLF)) + ohciR3ServiceBulkList(pThis); + else if ((pThis->status & OHCI_STATUS_BLF) + && pThis->fBulkNeedsCleaning) + ohciR3UndoBulkList(pThis); /* If list disabled but not empty, abort endpoints. */ + +# if 0 + /* + * Update the HCCA after processing the lists and everything. A bit experimental. + * + * ASSUME the guest won't be very upset if a TD is completed, retired and handed + * back immediately. The idea is to be able to retire the data and/or status stages + * of a control transfer together with the setup stage, thus saving a frame. This + * behaviour is should be perfectly ok, since the setup (and maybe data) stages + * have already taken at least one frame to complete. + * + * But, when implementing the first synchronous virtual USB devices, we'll have to + * verify that the guest doesn't choke when having a TD returned in the same frame + * as it was submitted. + */ + ohciR3UpdateHCCA(pThis); +# endif + +# ifdef LOG_ENABLED + if (pThis->status ^ status_old) + { + uint32_t val = pThis->status; + uint32_t chg = val ^ status_old; NOREF(chg); + Log2(("ohciR3StartOfFrame: HcCommandStatus=%#010x: %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", val & 1, + chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & (3<<16)? "*" : "", (val >> 16) & 3)); + } +# endif +} + +/** + * Updates the HcFmNumber and FNO registers. + */ +static void ohciR3BumpFrameNumber(POHCI pThis) +{ + const uint16_t u16OldFmNumber = pThis->HcFmNumber++; + if ((u16OldFmNumber ^ pThis->HcFmNumber) & RT_BIT(15)) + pThis->fno = 1; +} + +/** + * Callback for periodic frame processing. + */ +static DECLCALLBACK(bool) ohciR3StartFrame(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameNo) +{ + RT_NOREF(u32FrameNo); + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + + ohciR3Lock(pThis); + + /* Reset idle detection flag */ + pThis->fIdle = true; + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsReset(&g_PhysReadState); +# endif + + /* Frame boundary, so do EOF stuff here. */ + ohciR3BumpFrameNumber(pThis); + if ( (pThis->dqic != 0x7) && (pThis->dqic != 0)) + pThis->dqic--; + + /* Clean up any URBs that have been removed. */ + ohciR3CancelOrphanedURBs(pThis); + + /* Start the next frame. */ + ohciR3StartOfFrame(pThis); + +# ifdef VBOX_WITH_OHCI_PHYS_READ_STATS + physReadStatsPrint(&g_PhysReadState); +# endif + + ohciR3Unlock(pThis); + return pThis->fIdle; +} + +/** @interface_method_impl{VUSBIROOTHUBPORT,pfnFrameRateChanged} */ +static DECLCALLBACK(void) ohciR3FrameRateChanged(PVUSBIROOTHUBPORT pInterface, uint32_t u32FrameRate) +{ + POHCI pThis = VUSBIROOTHUBPORT_2_OHCI(pInterface); + + Assert(u32FrameRate <= OHCI_DEFAULT_TIMER_FREQ); + + pThis->cTicksPerFrame = pThis->u64TimerHz / u32FrameRate; + if (!pThis->cTicksPerFrame) + pThis->cTicksPerFrame = 1; + pThis->cTicksPerUsbTick = pThis->u64TimerHz >= VUSB_BUS_HZ ? pThis->u64TimerHz / VUSB_BUS_HZ : 1; +} + +/** + * Do frame processing on frame boundary + */ +static DECLCALLBACK(void) ohciR3FrameBoundaryTimer(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pDevIns, pTimer, pvUser); +} + +/** + * Start sending SOF tokens across the USB bus, lists are processed in + * next frame + */ +static void ohciR3BusStart(POHCI pThis) +{ + VUSBIDevPowerOn(pThis->RootHub.pIDev); + pThis->dqic = 0x7; + + Log(("ohci: %s: Bus started\n", pThis->PciDev.pszNameR3)); + + pThis->SofTime = PDMDevHlpTMTimeVirtGet(pThis->CTX_SUFF(pDevIns)); + int rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ); + AssertRC(rc); +} + +/** + * Stop sending SOF tokens on the bus + */ +static void ohciR3BusStop(POHCI pThis) +{ + int rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, 0); + AssertRC(rc); + VUSBIDevPowerOff(pThis->RootHub.pIDev); +} + +/** + * Move in to resume state + */ +static void ohciR3BusResume(POHCI pThis, bool fHardware) +{ + pThis->ctl &= ~OHCI_CTL_HCFS; + pThis->ctl |= OHCI_USB_RESUME; + + LogFunc(("fHardware=%RTbool RWE=%s\n", + fHardware, (pThis->ctl & OHCI_CTL_RWE) ? "on" : "off")); + + if (fHardware && (pThis->ctl & OHCI_CTL_RWE)) + ohciR3SetInterrupt(pThis, OHCI_INTR_RESUME_DETECT); + + ohciR3BusStart(pThis); +} + + +/* Power a port up or down */ +static void ohciR3RhPortPower(POHCIROOTHUB pRh, unsigned iPort, bool fPowerUp) +{ + POHCIHUBPORT pPort = &pRh->aPorts[iPort]; + bool fOldPPS = !!(pPort->fReg & OHCI_PORT_PPS); + + LogFlowFunc(("iPort=%u fPowerUp=%RTbool\n", iPort, fPowerUp)); + + if (fPowerUp) + { + /* power up */ + if (pPort->pDev) + pPort->fReg |= OHCI_PORT_CCS; + if (pPort->fReg & OHCI_PORT_CCS) + pPort->fReg |= OHCI_PORT_PPS; + if (pPort->pDev && !fOldPPS) + VUSBIDevPowerOn(pPort->pDev); + } + else + { + /* power down */ + pPort->fReg &= ~(OHCI_PORT_PPS | OHCI_PORT_CCS | OHCI_PORT_PSS | OHCI_PORT_PRS); + if (pPort->pDev && fOldPPS) + VUSBIDevPowerOff(pPort->pDev); + } +} + +#endif /* IN_RING3 */ + +/** + * Read the HcRevision register. + */ +static int HcRevision_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF2(pThis, iReg); + Log2(("HcRevision_r() -> 0x10\n")); + *pu32Value = 0x10; /* OHCI revision 1.0, no emulation. */ + return VINF_SUCCESS; +} + +/** + * Write to the HcRevision register. + */ +static int HcRevision_w(POHCI pThis, uint32_t iReg, uint32_t u32Value) +{ + RT_NOREF3(pThis, iReg, u32Value); + Log2(("HcRevision_w(%#010x) - denied\n", u32Value)); + AssertMsgFailed(("Invalid operation!!! u32Value=%#010x\n", u32Value)); + return VINF_SUCCESS; +} + +/** + * Read the HcControl register. + */ +static int HcControl_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF1(iReg); + uint32_t ctl = pThis->ctl; + Log2(("HcControl_r -> %#010x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1)); + *pu32Value = ctl; + return VINF_SUCCESS; +} + +/** + * Write the HcControl register. + */ +static int HcControl_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + + /* log it. */ + uint32_t chg = pThis->ctl ^ val; NOREF(chg); + Log2(("HcControl_w(%#010x) => %sCBSR=%d %sPLE=%d %sIE=%d %sCLE=%d %sBLE=%d %sHCFS=%#x %sIR=%d %sRWC=%d %sRWE=%d\n", + val, + chg & 3 ? "*" : "", val & 3, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (val >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (val >> 5) & 1, + chg & (3 << 6)? "*" : "", (val >> 6) & 3, + chg & RT_BIT(8) ? "*" : "", (val >> 8) & 1, + chg & RT_BIT(9) ? "*" : "", (val >> 9) & 1, + chg & RT_BIT(10) ? "*" : "", (val >> 10) & 1)); + if (val & ~0x07ff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x07ff)); + + /* see what changed and take action on that. */ + uint32_t old_state = pThis->ctl & OHCI_CTL_HCFS; + uint32_t new_state = val & OHCI_CTL_HCFS; + +#ifdef IN_RING3 + pThis->ctl = val; + if (new_state != old_state) + { + switch (new_state) + { + case OHCI_USB_OPERATIONAL: + LogRel(("OHCI: USB Operational\n")); + ohciR3BusStart(pThis); + break; + case OHCI_USB_SUSPEND: + ohciR3BusStop(pThis); + LogRel(("OHCI: USB Suspended\n")); + break; + case OHCI_USB_RESUME: + LogRel(("OHCI: USB Resume\n")); + ohciR3BusResume(pThis, false /* not hardware */); + break; + case OHCI_USB_RESET: + { + LogRel(("OHCI: USB Reset\n")); + ohciR3BusStop(pThis); + /** @todo This should probably do a real reset, but we don't implement + * that correctly in the roothub reset callback yet. check it's + * comments and argument for more details. */ + VUSBIDevReset(pThis->RootHub.pIDev, false /* don't do a real reset */, NULL, NULL, NULL); + break; + } + } + } +#else /* !IN_RING3 */ + if ( new_state != old_state ) + { + Log2(("HcControl_w: state changed -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->ctl = val; +#endif /* !IN_RING3 */ + + return VINF_SUCCESS; +} + +/** + * Read the HcCommandStatus register. + */ +static int HcCommandStatus_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t status = pThis->status; + Log2(("HcCommandStatus_r() -> %#010x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3)); + *pu32Value = status; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcCommandStatus register. + */ +static int HcCommandStatus_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + + /* log */ + uint32_t chg = pThis->status ^ val; NOREF(chg); + Log2(("HcCommandStatus_w(%#010x) => %sHCR=%d %sCLF=%d %sBLF=%d %sOCR=%d %sSOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", val & 1, + chg & RT_BIT(1) ? "*" : "", (val >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (val >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (val >> 3) & 1, + chg & (3<<16)? "!!!":"", (pThis->status >> 16) & 3)); + if (val & ~0x0003000f) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x0003000f)); + + /* SOC is read-only */ + val = (val & ~OHCI_STATUS_SOC); + +#ifdef IN_RING3 + /* "bits written as '0' remain unchanged in the register" */ + pThis->status |= val; + if (pThis->status & OHCI_STATUS_HCR) + { + LogRel(("OHCI: Software reset\n")); + ohciR3DoReset(pThis, OHCI_USB_SUSPEND, false /* N/A */); + } +#else + if ((pThis->status | val) & OHCI_STATUS_HCR) + { + LogFlow(("HcCommandStatus_w: reset -> VINF_IOM_R3_MMIO_WRITE\n")); + return VINF_IOM_R3_MMIO_WRITE; + } + pThis->status |= val; +#endif + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptStatus register. + */ +static int HcInterruptStatus_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr_status; + Log2(("HcInterruptStatus_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1)); + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcInterruptStatus register. + */ +static int HcInterruptStatus_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + + uint32_t res = pThis->intr_status & ~val; + uint32_t chg = pThis->intr_status ^ res; NOREF(chg); + + int rc = PDMCritSectEnter(&pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptStatus_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30)? "*" : "", (res >> 30) & 1)); + if ( (val & ~0xc000007f) + && val != 0xffffffff /* ignore clear-all-like requests from xp. */) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xc000007f)); + + /* "The Host Controller Driver may clear specific bits in this + * register by writing '1' to bit positions to be cleared" + */ + pThis->intr_status &= ~val; + ohciUpdateInterruptLocked(pThis, "HcInterruptStatus_w"); + PDMCritSectLeave(&pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcInterruptEnable register + */ +static int HcInterruptEnable_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->intr; + Log2(("HcInterruptEnable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptEnable register. + */ +static int HcInterruptEnable_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + uint32_t res = pThis->intr | val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMCritSectEnter(&pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptEnable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + if (val & ~0xc000007f) + Log2(("Uknown bits %#x are set!!!\n", val & ~0xc000007f)); + + pThis->intr |= val; + ohciUpdateInterruptLocked(pThis, "HcInterruptEnable_w"); + PDMCritSectLeave(&pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Reads the HcInterruptDisable register. + */ +static int HcInterruptDisable_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ +#if 1 /** @todo r=bird: "On read, the current value of the HcInterruptEnable register is returned." */ + uint32_t val = pThis->intr; +#else /* old code. */ + uint32_t val = ~pThis->intr; +#endif + Log2(("HcInterruptDisable_r() -> %#010x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1)); + + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Writes to the HcInterruptDisable register. + */ +static int HcInterruptDisable_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + uint32_t res = pThis->intr & ~val; + uint32_t chg = pThis->intr ^ res; NOREF(chg); + + int rc = PDMCritSectEnter(&pThis->CsIrq, VINF_IOM_R3_MMIO_WRITE); + if (rc != VINF_SUCCESS) + return rc; + + Log2(("HcInterruptDisable_w(%#010x) => %sSO=%d %sWDH=%d %sSF=%d %sRD=%d %sUE=%d %sFNO=%d %sRHSC=%d %sOC=%d %sMIE=%d\n", + val, + chg & RT_BIT(0) ? "*" : "", res & 1, + chg & RT_BIT(1) ? "*" : "", (res >> 1) & 1, + chg & RT_BIT(2) ? "*" : "", (res >> 2) & 1, + chg & RT_BIT(3) ? "*" : "", (res >> 3) & 1, + chg & RT_BIT(4) ? "*" : "", (res >> 4) & 1, + chg & RT_BIT(5) ? "*" : "", (res >> 5) & 1, + chg & RT_BIT(6) ? "*" : "", (res >> 6) & 1, + chg & RT_BIT(30) ? "*" : "", (res >> 30) & 1, + chg & RT_BIT(31) ? "*" : "", (res >> 31) & 1)); + /* Don't bitch about invalid bits here since it makes sense to disable + * interrupts you don't know about. */ + + pThis->intr &= ~val; + ohciUpdateInterruptLocked(pThis, "HcInterruptDisable_w"); + PDMCritSectLeave(&pThis->CsIrq); + return VINF_SUCCESS; +} + +/** + * Read the HcHCCA register (Host Controller Communications Area physical address). + */ +static int HcHCCA_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcHCCA_r() -> %#010x\n", pThis->hcca)); + *pu32Value = pThis->hcca; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcHCCA register (Host Controller Communications Area physical address). + */ +static int HcHCCA_w(POHCI pThis, uint32_t iReg, uint32_t Value) +{ + Log2(("HcHCCA_w(%#010x) - old=%#010x new=%#010x\n", Value, pThis->hcca, Value & OHCI_HCCA_MASK)); + pThis->hcca = Value & OHCI_HCCA_MASK; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodCurrentED register. + */ +static int HcPeriodCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcPeriodCurrentED_r() -> %#010x\n", pThis->per_cur)); + *pu32Value = pThis->per_cur; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodCurrentED register. + */ +static int HcPeriodCurrentED_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log(("HcPeriodCurrentED_w(%#010x) - old=%#010x new=%#010x (This is a read only register, only the linux guys don't respect that!)\n", + val, pThis->per_cur, val & ~7)); + //AssertMsgFailed(("HCD (Host Controller Driver) should not write to HcPeriodCurrentED! val=%#010x (old=%#010x)\n", val, pThis->per_cur)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->per_cur = val & ~7; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlHeadED register. + */ +static int HcControlHeadED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlHeadED_r() -> %#010x\n", pThis->ctrl_head)); + *pu32Value = pThis->ctrl_head; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlHeadED register. + */ +static int HcControlHeadED_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_head = val & ~7; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcControlCurrentED register. + */ +static int HcControlCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcControlCurrentED_r() -> %#010x\n", pThis->ctrl_cur)); + *pu32Value = pThis->ctrl_cur; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcControlCurrentED register. + */ +static int HcControlCurrentED_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcControlCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->ctrl_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_CLE), ("Illegal write! HcControl.ControlListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->ctrl_cur = val & ~7; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkHeadED register. + */ +static int HcBulkHeadED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkHeadED_r() -> %#010x\n", pThis->bulk_head)); + *pu32Value = pThis->bulk_head; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkHeadED register. + */ +static int HcBulkHeadED_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkHeadED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_head, val & ~7)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_head = val & ~7; /** @todo The ATI OHCI controller on my machine enforces 16-byte address alignment. */ + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Read the HcBulkCurrentED register. + */ +static int HcBulkCurrentED_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcBulkCurrentED_r() -> %#010x\n", pThis->bulk_cur)); + *pu32Value = pThis->bulk_cur; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcBulkCurrentED register. + */ +static int HcBulkCurrentED_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + Log2(("HcBulkCurrentED_w(%#010x) - old=%#010x new=%#010x\n", val, pThis->bulk_cur, val & ~7)); + AssertMsg(!(pThis->ctl & OHCI_CTL_BLE), ("Illegal write! HcControl.BulkListEnabled is set! val=%#010x\n", val)); + AssertMsg(!(val & 7), ("Invalid alignment, val=%#010x\n", val)); + pThis->bulk_cur = val & ~7; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + + +/** + * Read the HcDoneHead register. + */ +static int HcDoneHead_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + Log2(("HcDoneHead_r() -> 0x%#08x\n", pThis->done)); + *pu32Value = pThis->done; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcDoneHead register. + */ +static int HcDoneHead_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF3(pThis, iReg, val); + Log2(("HcDoneHead_w(0x%#08x) - denied!!!\n", val)); + /*AssertMsgFailed(("Illegal operation!!! val=%#010x\n", val)); - OS/2 does this */ + return VINF_SUCCESS; +} + + +/** + * Read the HcFmInterval (Fm=Frame) register. + */ +static int HcFmInterval_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = (pThis->fit << 31) | (pThis->fsmps << 16) | (pThis->fi); + Log2(("HcFmInterval_r() -> 0x%#08x - FI=%d FSMPS=%d FIT=%d\n", + val, val & 0x3fff, (val >> 16) & 0x7fff, val >> 31)); + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcFmInterval (Fm = Frame) register. + */ +static int HcFmInterval_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + + /* log */ + uint32_t chg = val ^ ((pThis->fit << 31) | (pThis->fsmps << 16) | pThis->fi); NOREF(chg); + Log2(("HcFmInterval_w(%#010x) => %sFI=%d %sFSMPS=%d %sFIT=%d\n", + val, + chg & 0x00003fff ? "*" : "", val & 0x3fff, + chg & 0x7fff0000 ? "*" : "", (val >> 16) & 0x7fff, + chg >> 31 ? "*" : "", (val >> 31) & 1)); + if ( pThis->fi != (val & OHCI_FMI_FI) ) + { + Log(("ohci: FrameInterval: %#010x -> %#010x\n", pThis->fi, val & OHCI_FMI_FI)); + AssertMsg(pThis->fit != ((val >> OHCI_FMI_FIT_SHIFT) & 1), ("HCD didn't toggle the FIT bit!!!\n")); + } + + /* update */ + pThis->fi = val & OHCI_FMI_FI; + pThis->fit = (val & OHCI_FMI_FIT) >> OHCI_FMI_FIT_SHIFT; + pThis->fsmps = (val & OHCI_FMI_FSMPS) >> OHCI_FMI_FSMPS_SHIFT; + return VINF_SUCCESS; +} + +/** + * Read the HcFmRemaining (Fm = Frame) register. + */ +static int HcFmRemaining_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF1(iReg); + uint32_t Value = pThis->frt << 31; + if ((pThis->ctl & OHCI_CTL_HCFS) == OHCI_USB_OPERATIONAL) + { + /* + * Being in USB operational state guarantees SofTime was set already. + */ + uint64_t tks = PDMDevHlpTMTimeVirtGet(pThis->CTX_SUFF(pDevIns)) - pThis->SofTime; + if (tks < pThis->cTicksPerFrame) /* avoid muldiv if possible */ + { + uint16_t fr; + tks = ASMMultU64ByU32DivByU32(1, tks, pThis->cTicksPerUsbTick); + fr = (uint16_t)(pThis->fi - tks); + Value |= fr; + } + } + + Log2(("HcFmRemaining_r() -> %#010x - FR=%d FRT=%d\n", Value, Value & 0x3fff, Value >> 31)); + *pu32Value = Value; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmRemaining (Fm = Frame) register. + */ +static int HcFmRemaining_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF3(pThis, iReg, val); + Log2(("HcFmRemaining_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcFmNumber (Fm = Frame) register. + */ +static int HcFmNumber_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF1(iReg); + uint32_t val = (uint16_t)pThis->HcFmNumber; + Log2(("HcFmNumber_r() -> %#010x - FN=%#x(%d) (32-bit=%#x(%d))\n", val, val, val, pThis->HcFmNumber, pThis->HcFmNumber)); + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcFmNumber (Fm = Frame) register. + */ +static int HcFmNumber_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF3(pThis, iReg, val); + Log2(("HcFmNumber_w(%#010x) - denied\n", val)); + AssertMsgFailed(("Invalid operation!!! val=%#010x\n", val)); + return VINF_SUCCESS; +} + +/** + * Read the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static int HcPeriodicStart_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF1(iReg); + Log2(("HcPeriodicStart_r() -> %#010x - PS=%d\n", pThis->pstart, pThis->pstart & 0x3fff)); + *pu32Value = pThis->pstart; + return VINF_SUCCESS; +} + +/** + * Write to the HcPeriodicStart register. + * The register determines when in a frame to switch from control&bulk to periodic lists. + */ +static int HcPeriodicStart_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + Log2(("HcPeriodicStart_w(%#010x) => PS=%d\n", val, val & 0x3fff)); + if (val & ~0x3fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0x3fff)); + pThis->pstart = val; /** @todo r=bird: should we support setting the other bits? */ + return VINF_SUCCESS; +} + +/** + * Read the HcLSThreshold register. + */ +static int HcLSThreshold_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF2(pThis, iReg); + Log2(("HcLSThreshold_r() -> %#010x\n", OHCI_LS_THRESH)); + *pu32Value = OHCI_LS_THRESH; + return VINF_SUCCESS; +} + +/** + * Write to the HcLSThreshold register. + * + * Docs are inconsistent here: + * + * "Neither the Host Controller nor the Host Controller Driver are allowed to change this value." + * + * "This value is calculated by HCD with the consideration of transmission and setup overhead." + * + * The register is marked "R/W" the HCD column. + * + */ +static int HcLSThreshold_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF3(pThis, iReg, val); + Log2(("HcLSThreshold_w(%#010x) => LST=0x%03x(%d)\n", val, val & 0x0fff, val & 0x0fff)); + AssertMsg(val == OHCI_LS_THRESH, + ("HCD tried to write bad LS threshold: 0x%x (see function header)\n", val)); + /** @todo the HCD can change this. */ + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorA register. + */ +static int HcRhDescriptorA_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + RT_NOREF1(iReg); + uint32_t val = pThis->RootHub.desc_a; +#if 0 /* annoying */ + Log2(("HcRhDescriptorA_r() -> %#010x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTGT=%#x\n", + val, val & 0xff, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, + (val >> 12) & 1, (val >> 24) & 0xff)); +#endif + *pu32Value = val; + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorA register. + */ +static int HcRhDescriptorA_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + uint32_t chg = val ^ pThis->RootHub.desc_a; NOREF(chg); + Log2(("HcRhDescriptorA_w(%#010x) => %sNDP=%d %sPSM=%d %sNPS=%d %sDT=%d %sOCPM=%d %sNOCP=%d %sPOTGT=%#x - %sPowerSwitching Set%sPower\n", + val, + chg & 0xff ?"!!!": "", val & 0xff, + (chg >> 8) & 1 ? "*" : "", (val >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (val >> 9) & 1, + (chg >> 10) & 1 ?"!!!": "", 0, + (chg >> 11) & 1 ? "*" : "", (val >> 11) & 1, + (chg >> 12) & 1 ? "*" : "", (val >> 12) & 1, + (chg >> 24)&0xff? "*" : "", (val >> 24) & 0xff, + val & OHCI_RHA_NPS ? "No" : "", + val & OHCI_RHA_PSM ? "Port" : "Global")); + if (val & ~0xff001fff) + Log2(("Unknown bits %#x are set!!!\n", val & ~0xff001fff)); + + + if ((val & (OHCI_RHA_NDP | OHCI_RHA_DT)) != OHCI_NDP_CFG(pThis)) + { + Log(("ohci: %s: invalid write to NDP or DT in roothub descriptor A!!! val=0x%.8x\n", + pThis->PciDev.pszNameR3, val)); + val &= ~(OHCI_RHA_NDP | OHCI_RHA_DT); + val |= OHCI_NDP_CFG(pThis); + } + + pThis->RootHub.desc_a = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhDescriptorB register. + */ +static int HcRhDescriptorB_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.desc_b; + Log2(("HcRhDescriptorB_r() -> %#010x - DR=0x%04x PPCM=0x%04x\n", + val, val & 0xffff, val >> 16)); + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhDescriptorB register. + */ +static int HcRhDescriptorB_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ + RT_NOREF1(iReg); + uint32_t chg = pThis->RootHub.desc_b ^ val; NOREF(chg); + Log2(("HcRhDescriptorB_w(%#010x) => %sDR=0x%04x %sPPCM=0x%04x\n", + val, + chg & 0xffff ? "!!!" : "", val & 0xffff, + chg >> 16 ? "!!!" : "", val >> 16)); + + if ( pThis->RootHub.desc_b != val ) + Log(("ohci: %s: unsupported write to root descriptor B!!! 0x%.8x -> 0x%.8x\n", + pThis->PciDev.pszNameR3, pThis->RootHub.desc_b, val)); + pThis->RootHub.desc_b = val; + return VINF_SUCCESS; +} + +/** + * Read the HcRhStatus (Rh = Root Hub) register. + */ +static int HcRhStatus_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + uint32_t val = pThis->RootHub.status; + if (val & (OHCI_RHS_LPSC | OHCI_RHS_OCIC)) + Log2(("HcRhStatus_r() -> %#010x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1)); + *pu32Value = val; + RT_NOREF1(iReg); + return VINF_SUCCESS; +} + +/** + * Write to the HcRhStatus (Rh = Root Hub) register. + */ +static int HcRhStatus_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + /* log */ + uint32_t old = pThis->RootHub.status; + uint32_t chg; + if (val & ~0x80038003) + Log2(("HcRhStatus_w: Unknown bits %#x are set!!!\n", val & ~0x80038003)); + if ( (val & OHCI_RHS_LPSC) && (val & OHCI_RHS_LPS) ) + Log2(("HcRhStatus_w: Warning both CGP and SGP are set! (Clear/Set Global Power)\n")); + if ( (val & OHCI_RHS_DRWE) && (val & OHCI_RHS_CRWE) ) + Log2(("HcRhStatus_w: Warning both CRWE and SRWE are set! (Clear/Set Remote Wakeup Enable)\n")); + + + /* write 1 to clear OCIC */ + if ( val & OHCI_RHS_OCIC ) + pThis->RootHub.status &= ~OHCI_RHS_OCIC; + + /* SetGlobalPower */ + if ( val & OHCI_RHS_LPSC ) + { + unsigned i; + Log2(("ohci: %s: global power up\n", pThis->PciDev.pszNameR3)); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + } + + /* ClearGlobalPower */ + if ( val & OHCI_RHS_LPS ) + { + unsigned i; + Log2(("ohci: %s: global power down\n", pThis->PciDev.pszNameR3)); + for (i = 0; i < OHCI_NDP_CFG(pThis); i++) + ohciR3RhPortPower(&pThis->RootHub, i, false /* power down */); + } + + if ( val & OHCI_RHS_DRWE ) + pThis->RootHub.status |= OHCI_RHS_DRWE; + + if ( val & OHCI_RHS_CRWE ) + pThis->RootHub.status &= ~OHCI_RHS_DRWE; + + chg = pThis->RootHub.status ^ old; + Log2(("HcRhStatus_w(%#010x) => %sCGP=%d %sOCI=%d %sSRWE=%d %sSGP=%d %sOCIC=%d %sCRWE=%d\n", + val, + chg & 1 ? "*" : "", val & 1, + (chg >> 1) & 1 ?"!!!": "", (val >> 1) & 1, + (chg >> 15) & 1 ? "*" : "", (val >> 15) & 1, + (chg >> 16) & 1 ? "*" : "", (val >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (val >> 17) & 1, + (chg >> 31) & 1 ? "*" : "", (val >> 31) & 1)); + RT_NOREF1(iReg); + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF3(pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Read the HcRhPortStatus register of a port. + */ +static int HcRhPortStatus_r(PCOHCI pThis, uint32_t iReg, uint32_t *pu32Value) +{ + const unsigned i = iReg - 21; + uint32_t val = pThis->RootHub.aPorts[i].fReg | OHCI_PORT_PPS; /* PortPowerStatus: see todo on power in _w function. */ + if (val & OHCI_PORT_PRS) + { +#ifdef IN_RING3 + RTThreadYield(); +#else + Log2(("HcRhPortStatus_r: yield -> VINF_IOM_R3_MMIO_READ\n")); + return VINF_IOM_R3_MMIO_READ; +#endif + } + if (val & (OHCI_PORT_PRS | OHCI_PORT_CLEAR_CHANGE_MASK)) + Log2(("HcRhPortStatus_r(): port %u: -> %#010x - CCS=%d PES=%d PSS=%d POCI=%d RRS=%d PPS=%d LSDA=%d CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1)); + *pu32Value = val; + return VINF_SUCCESS; +} + +#ifdef IN_RING3 +/** + * Completion callback for the vusb_dev_reset() operation. + * @thread EMT. + */ +static DECLCALLBACK(void) ohciR3PortResetDone(PVUSBIDEVICE pDev, int rc, void *pvUser) +{ + POHCI pThis = (POHCI)pvUser; + + /* + * Find the port in question + */ + POHCIHUBPORT pPort = NULL; + unsigned iPort; + for (iPort = 0; iPort < OHCI_NDP_CFG(pThis); iPort++) /* lazy bird */ + if (pThis->RootHub.aPorts[iPort].pDev == pDev) + { + pPort = &pThis->RootHub.aPorts[iPort]; + break; + } + if (!pPort) + { + Assert(pPort); /* sometimes happens because of @bugref{1510} */ + return; + } + + if (RT_SUCCESS(rc)) + { + /* + * Successful reset. + */ + Log2(("ohciR3PortResetDone: Reset completed.\n")); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC); + pPort->fReg |= OHCI_PORT_PES | OHCI_PORT_PRSC; + } + else + { + /* desperate measures. */ + if ( pPort->pDev + && VUSBIDevGetState(pPort->pDev) == VUSB_DEVICE_STATE_ATTACHED) + { + /* + * Damn, something weird happened during reset. We'll pretend the user did an + * incredible fast reconnect or something. (probably not gonna work) + */ + Log2(("ohciR3PortResetDone: The reset failed (rc=%Rrc)!!! Pretending reconnect at the speed of light.\n", rc)); + pPort->fReg = OHCI_PORT_CCS | OHCI_PORT_CSC; + } + else + { + /* + * The device have / will be disconnected. + */ + Log2(("ohciR3PortResetDone: Disconnected (rc=%Rrc)!!!\n", rc)); + pPort->fReg &= ~(OHCI_PORT_PRS | OHCI_PORT_PSS | OHCI_PORT_PSSC | OHCI_PORT_PRSC); + pPort->fReg |= OHCI_PORT_CSC; + } + } + + /* Raise roothub status change interrupt. */ + ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); +} + +/** + * Sets a flag in a port status register but only set it if a device is + * connected, if not set ConnectStatusChange flag to force HCD to reevaluate + * connect status. + * + * @returns true if device was connected and the flag was cleared. + */ +static bool ohciR3RhPortSetIfConnected(POHCIROOTHUB pRh, int iPort, uint32_t fValue) +{ + /* + * Writing a 0 has no effect + */ + if (fValue == 0) + return false; + + /* + * If CurrentConnectStatus is cleared we set ConnectStatusChange. + */ + if (!(pRh->aPorts[iPort].fReg & OHCI_PORT_CCS)) + { + pRh->aPorts[iPort].fReg |= OHCI_PORT_CSC; + ohciR3SetInterrupt(pRh->pOhci, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + return false; + } + + bool fRc = !(pRh->aPorts[iPort].fReg & fValue); + + /* set the bit */ + pRh->aPorts[iPort].fReg |= fValue; + + return fRc; +} +#endif /* IN_RING3 */ + +/** + * Write to the HcRhPortStatus register of a port. + */ +static int HcRhPortStatus_w(POHCI pThis, uint32_t iReg, uint32_t val) +{ +#ifdef IN_RING3 + const unsigned i = iReg - 21; + POHCIHUBPORT p = &pThis->RootHub.aPorts[i]; + uint32_t old_state = p->fReg; + +# ifdef LOG_ENABLED + /* + * Log it. + */ + static const char *apszCmdNames[32] = + { + "ClearPortEnable", "SetPortEnable", "SetPortSuspend", "!!!ClearSuspendStatus", + "SetPortReset", "!!!5", "!!!6", "!!!7", + "SetPortPower", "ClearPortPower", "!!!10", "!!!11", + "!!!12", "!!!13", "!!!14", "!!!15", + "ClearCSC", "ClearPESC", "ClearPSSC", "ClearOCIC", + "ClearPRSC", "!!!21", "!!!22", "!!!23", + "!!!24", "!!!25", "!!!26", "!!!27", + "!!!28", "!!!29", "!!!30", "!!!31" + }; + Log2(("HcRhPortStatus_w(%#010x): port %u:", val, i)); + for (unsigned j = 0; j < RT_ELEMENTS(apszCmdNames); j++) + if (val & (1 << j)) + Log2((" %s", apszCmdNames[j])); + Log2(("\n")); +# endif + + /* Write to clear any of the change bits: CSC, PESC, PSSC, OCIC and PRSC */ + if (val & OHCI_PORT_CLEAR_CHANGE_MASK) + p->fReg &= ~(val & OHCI_PORT_CLEAR_CHANGE_MASK); + + if (val & OHCI_PORT_CLRPE) + { + p->fReg &= ~OHCI_PORT_PES; + Log2(("HcRhPortStatus_w(): port %u: DISABLE\n", i)); + } + + if (ohciR3RhPortSetIfConnected(&pThis->RootHub, i, val & OHCI_PORT_PES)) + Log2(("HcRhPortStatus_w(): port %u: ENABLE\n", i)); + + if (ohciR3RhPortSetIfConnected(&pThis->RootHub, i, val & OHCI_PORT_PSS)) + Log2(("HcRhPortStatus_w(): port %u: SUSPEND - not implemented correctly!!!\n", i)); + + if (val & OHCI_PORT_PRS) + { + if (ohciR3RhPortSetIfConnected(&pThis->RootHub, i, val & OHCI_PORT_PRS)) + { + PVM pVM = PDMDevHlpGetVM(pThis->CTX_SUFF(pDevIns)); + p->fReg &= ~OHCI_PORT_PRSC; + VUSBIDevReset(p->pDev, false /* don't reset on linux */, ohciR3PortResetDone, pThis, pVM); + } + else if (p->fReg & OHCI_PORT_PRS) + { + /* the guest is getting impatient. */ + Log2(("HcRhPortStatus_w(): port %u: Impatient guest!\n", i)); + RTThreadYield(); + } + } + + if (!(pThis->RootHub.desc_a & OHCI_RHA_NPS)) + { + /** @todo To implement per-device power-switching + * we need to check PortPowerControlMask to make + * sure it isn't gang powered + */ + if (val & OHCI_PORT_CLRPP) + ohciR3RhPortPower(&pThis->RootHub, i, false /* power down */); + if (val & OHCI_PORT_PPS) + ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + } + + /** @todo r=frank: ClearSuspendStatus. Timing? */ + if (val & OHCI_PORT_CLRSS) + { + ohciR3RhPortPower(&pThis->RootHub, i, true /* power up */); + pThis->RootHub.aPorts[i].fReg &= ~OHCI_PORT_PSS; + pThis->RootHub.aPorts[i].fReg |= OHCI_PORT_PSSC; + ohciR3SetInterrupt(pThis, OHCI_INTR_ROOT_HUB_STATUS_CHANGE); + } + + if (p->fReg != old_state) + { + uint32_t res = p->fReg; + uint32_t chg = res ^ old_state; NOREF(chg); + Log2(("HcRhPortStatus_w(%#010x): port %u: => %sCCS=%d %sPES=%d %sPSS=%d %sPOCI=%d %sRRS=%d %sPPS=%d %sLSDA=%d %sCSC=%d %sPESC=%d %sPSSC=%d %sOCIC=%d %sPRSC=%d\n", + val, i, + chg & 1 ? "*" : "", res & 1, + (chg >> 1) & 1 ? "*" : "", (res >> 1) & 1, + (chg >> 2) & 1 ? "*" : "", (res >> 2) & 1, + (chg >> 3) & 1 ? "*" : "", (res >> 3) & 1, + (chg >> 4) & 1 ? "*" : "", (res >> 4) & 1, + (chg >> 8) & 1 ? "*" : "", (res >> 8) & 1, + (chg >> 9) & 1 ? "*" : "", (res >> 9) & 1, + (chg >> 16) & 1 ? "*" : "", (res >> 16) & 1, + (chg >> 17) & 1 ? "*" : "", (res >> 17) & 1, + (chg >> 18) & 1 ? "*" : "", (res >> 18) & 1, + (chg >> 19) & 1 ? "*" : "", (res >> 19) & 1, + (chg >> 20) & 1 ? "*" : "", (res >> 20) & 1)); + } + return VINF_SUCCESS; +#else /* !IN_RING3 */ + RT_NOREF3(pThis, iReg, val); + return VINF_IOM_R3_MMIO_WRITE; +#endif /* !IN_RING3 */ +} + +/** + * Register descriptor table + */ +static const OHCIOPREG g_aOpRegs[] = +{ + { "HcRevision", HcRevision_r, HcRevision_w }, /* 0 */ + { "HcControl", HcControl_r, HcControl_w }, /* 1 */ + { "HcCommandStatus", HcCommandStatus_r, HcCommandStatus_w }, /* 2 */ + { "HcInterruptStatus", HcInterruptStatus_r, HcInterruptStatus_w }, /* 3 */ + { "HcInterruptEnable", HcInterruptEnable_r, HcInterruptEnable_w }, /* 4 */ + { "HcInterruptDisable", HcInterruptDisable_r, HcInterruptDisable_w }, /* 5 */ + { "HcHCCA", HcHCCA_r, HcHCCA_w }, /* 6 */ + { "HcPeriodCurrentED", HcPeriodCurrentED_r, HcPeriodCurrentED_w }, /* 7 */ + { "HcControlHeadED", HcControlHeadED_r, HcControlHeadED_w }, /* 8 */ + { "HcControlCurrentED", HcControlCurrentED_r, HcControlCurrentED_w }, /* 9 */ + { "HcBulkHeadED", HcBulkHeadED_r, HcBulkHeadED_w }, /* 10 */ + { "HcBulkCurrentED", HcBulkCurrentED_r, HcBulkCurrentED_w }, /* 11 */ + { "HcDoneHead", HcDoneHead_r, HcDoneHead_w }, /* 12 */ + { "HcFmInterval", HcFmInterval_r, HcFmInterval_w }, /* 13 */ + { "HcFmRemaining", HcFmRemaining_r, HcFmRemaining_w }, /* 14 */ + { "HcFmNumber", HcFmNumber_r, HcFmNumber_w }, /* 15 */ + { "HcPeriodicStart", HcPeriodicStart_r, HcPeriodicStart_w }, /* 16 */ + { "HcLSThreshold", HcLSThreshold_r, HcLSThreshold_w }, /* 17 */ + { "HcRhDescriptorA", HcRhDescriptorA_r, HcRhDescriptorA_w }, /* 18 */ + { "HcRhDescriptorB", HcRhDescriptorB_r, HcRhDescriptorB_w }, /* 19 */ + { "HcRhStatus", HcRhStatus_r, HcRhStatus_w }, /* 20 */ + + /* The number of port status register depends on the definition + * of OHCI_NDP_MAX macro + */ + { "HcRhPortStatus[0]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 21 */ + { "HcRhPortStatus[1]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 22 */ + { "HcRhPortStatus[2]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 23 */ + { "HcRhPortStatus[3]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 24 */ + { "HcRhPortStatus[4]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 25 */ + { "HcRhPortStatus[5]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 26 */ + { "HcRhPortStatus[6]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 27 */ + { "HcRhPortStatus[7]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 28 */ + { "HcRhPortStatus[8]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 29 */ + { "HcRhPortStatus[9]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 30 */ + { "HcRhPortStatus[10]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 31 */ + { "HcRhPortStatus[11]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 32 */ + { "HcRhPortStatus[12]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 33 */ + { "HcRhPortStatus[13]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 34 */ + { "HcRhPortStatus[14]", HcRhPortStatus_r, HcRhPortStatus_w }, /* 35 */ +}; + +/* Quick way to determine how many op regs are valid. Since at least one port must + * be configured (and no more than 15), there will be between 22 and 36 registers. + */ +#define NUM_OP_REGS(pohci) (21 + OHCI_NDP_CFG(pohci)) + +AssertCompile(RT_ELEMENTS(g_aOpRegs) > 21); +AssertCompile(RT_ELEMENTS(g_aOpRegs) <= 36); + +/** + * @callback_method_impl{FNIOMMMIOREAD} + */ +PDMBOTHCBDECL(int) ohciMmioRead(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void *pv, unsigned cb) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + RT_NOREF1(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_READ_DWORD works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(GCPhysAddr & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + int rc; + const uint32_t iReg = (GCPhysAddr - pThis->MMIOBase) >> 2; + if (iReg < NUM_OP_REGS(pThis)) + { + const OHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnRead(pThis, iReg, (uint32_t *)pv); + } + else + { + Log(("ohci: Trying to read register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_IOM_MMIO_UNUSED_FF; + } + return rc; +} + + +/** + * @callback_method_impl{FNIOMMMIOWRITE} + */ +PDMBOTHCBDECL(int) ohciMmioWrite(PPDMDEVINS pDevIns, void *pvUser, RTGCPHYS GCPhysAddr, void const *pv, unsigned cb) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + RT_NOREF1(pvUser); + + /* Paranoia: Assert that IOMMMIO_FLAGS_WRITE_DWORD_ZEROED works. */ + AssertReturn(cb == sizeof(uint32_t), VERR_INTERNAL_ERROR_3); + AssertReturn(!(GCPhysAddr & 0x3), VERR_INTERNAL_ERROR_4); + + /* + * Validate the register and call the read operator. + */ + int rc; + const uint32_t iReg = (GCPhysAddr - pThis->MMIOBase) >> 2; + if (iReg < NUM_OP_REGS(pThis)) + { + const OHCIOPREG *pReg = &g_aOpRegs[iReg]; + rc = pReg->pfnWrite(pThis, iReg, *(uint32_t const *)pv); + } + else + { + Log(("ohci: Trying to write to register %u/%u!!!\n", iReg, NUM_OP_REGS(pThis))); + rc = VINF_SUCCESS; + } + return rc; +} + +#ifdef IN_RING3 + +/** + * @callback_method_impl{FNPCIIOREGIONMAP} + */ +static DECLCALLBACK(int) ohciR3Map(PPDMDEVINS pDevIns, PPDMPCIDEV pPciDev, uint32_t iRegion, + RTGCPHYS GCPhysAddress, RTGCPHYS cb, PCIADDRESSSPACE enmType) +{ + RT_NOREF(iRegion, enmType); + POHCI pThis = (POHCI)pPciDev; + int rc = PDMDevHlpMMIORegister(pDevIns, GCPhysAddress, cb, NULL /*pvUser*/, + IOMMMIO_FLAGS_READ_DWORD | IOMMMIO_FLAGS_WRITE_DWORD_ZEROED + | IOMMMIO_FLAGS_DBGSTOP_ON_COMPLICATED_WRITE, + ohciMmioWrite, ohciMmioRead, "USB OHCI"); + if (RT_FAILURE(rc)) + return rc; + + if (pThis->fRZEnabled) + { + rc = PDMDevHlpMMIORegisterRC(pDevIns, GCPhysAddress, cb, NIL_RTRCPTR /*pvUser*/, "ohciMmioWrite", "ohciMmioRead"); + if (RT_FAILURE(rc)) + return rc; + + rc = PDMDevHlpMMIORegisterR0(pDevIns, GCPhysAddress, cb, NIL_RTR0PTR /*pvUser*/, "ohciMmioWrite", "ohciMmioRead"); + if (RT_FAILURE(rc)) + return rc; + } + + pThis->MMIOBase = GCPhysAddress; + return VINF_SUCCESS; +} + + +/** + * Prepares for state saving. + * All URBs needs to be canceled. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to save the state to. + */ +static DECLCALLBACK(int) ohciR3SavePrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pSSM); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + POHCIROOTHUB pRh = &pThis->RootHub; + LogFlow(("ohciR3SavePrep: \n")); + + /* + * Detach all proxied devices. + */ + PDMCritSectEnter(pThis->pDevInsR3->pCritSectRoR3, VERR_IGNORED); + /** @todo this won't work well when continuing after saving! */ + for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) + { + PVUSBIDEVICE pDev = pRh->aPorts[i].pDev; + if (pDev) + { + if (!VUSBIDevIsSavedStateSupported(pDev)) + { + VUSBIRhDetachDevice(pRh->pIRhConn, pDev); + /* + * Save the device pointers here so we can reattach them afterwards. + * This will work fine even if the save fails since the Done handler is + * called unconditionally if the Prep handler was called. + */ + pRh->aPorts[i].pDev = pDev; + } + } + } + + /* + * If the bus was started set the timer. This is ugly but avoids changing the + * saved state version for now so we can backport the changes to other branches. + */ + /** @todo Do it properly for 4.4 by changing the saved state. */ + if (VUSBIRhGetPeriodicFrameRate(pRh->pIRhConn) != 0) + { + /* Calculate a new timer expiration so this saved state works with older releases. */ + uint64_t u64Expire = PDMDevHlpTMTimeVirtGet(pThis->CTX_SUFF(pDevIns)) + pThis->cTicksPerFrame; + + LogFlowFunc(("Bus is active, setting timer to %llu\n", u64Expire)); + int rc = TMTimerSet(pThis->pEndOfFrameTimerR3, u64Expire); + AssertRC(rc); + } + + PDMCritSectLeave(pThis->pDevInsR3->pCritSectRoR3); + + /* + * Kill old load data which might be hanging around. + */ + if (pThis->pLoad) + { + TMR3TimerDestroy(pThis->pLoad->pTimer); + MMR3HeapFree(pThis->pLoad); + pThis->pLoad = NULL; + } + return VINF_SUCCESS; +} + + +/** + * Saves the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to save the state to. + */ +static DECLCALLBACK(int) ohciR3SaveExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3SaveExec: \n")); + + int rc = SSMR3PutStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + if (RT_SUCCESS(rc)) + rc = TMR3TimerSave(pThis->CTX_SUFF(pEndOfFrameTimer), pSSM); + return rc; +} + + +/** + * Done state save operation. + * + * @returns VBox load code. + * @param pDevIns Device instance of the device which registered the data unit. + * @param pSSM SSM operation handle. + */ +static DECLCALLBACK(int) ohciR3SaveDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pSSM); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3SaveDone: \n")); + + /* + * NULL the dev pointers. + */ + POHCIROOTHUB pRh = &pThis->RootHub; + OHCIROOTHUB Rh = *pRh; + for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) + { + if ( pRh->aPorts[i].pDev + && !VUSBIDevIsSavedStateSupported(pRh->aPorts[i].pDev)) + pRh->aPorts[i].pDev = NULL; + } + + /* + * Attach the devices. + */ + for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) + { + PVUSBIDEVICE pDev = Rh.aPorts[i].pDev; + if ( pDev + && !VUSBIDevIsSavedStateSupported(pDev)) + VUSBIRhAttachDevice(pRh->pIRhConn, pDev); + } + + return VINF_SUCCESS; +} + + +/** + * Prepare loading the state of the OHCI device. + * This must detach the devices currently attached and save + * the up for reconnect after the state load have been completed + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to the saved state. + */ +static DECLCALLBACK(int) ohciR3LoadPrep(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pSSM); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3LoadPrep:\n")); + if (!pThis->pLoad) + { + /* + * Detach all devices which are present in this session. Save them in the load + * structure so we can reattach them after restoring the guest. + */ + POHCIROOTHUB pRh = &pThis->RootHub; + OHCILOAD Load; + Load.pTimer = NULL; + Load.cDevs = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pRh->aPorts); i++) + { + PVUSBIDEVICE pDev = pRh->aPorts[i].pDev; + if ( pDev + && !VUSBIDevIsSavedStateSupported(pDev)) + { + Load.apDevs[Load.cDevs++] = pDev; + VUSBIRhDetachDevice(pRh->pIRhConn, pDev); + Assert(!pRh->aPorts[i].pDev); + } + } + + /* + * Any devices to reattach, if so duplicate the Load struct. + */ + if (Load.cDevs) + { + pThis->pLoad = (POHCILOAD)PDMDevHlpMMHeapAlloc(pDevIns, sizeof(Load)); + if (!pThis->pLoad) + return VERR_NO_MEMORY; + *pThis->pLoad = Load; + } + } + /* else: we ASSUME no device can be attached or detach in the period + * between a state load and the pLoad stuff is processed. */ + return VINF_SUCCESS; +} + + +/** + * Loads the state of the OHCI device. + * + * @returns VBox status code. + * @param pDevIns The device instance. + * @param pSSM The handle to the saved state. + * @param uVersion The data unit version number. + * @param uPass The data pass. + */ +static DECLCALLBACK(int) ohciR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSSM, uint32_t uVersion, uint32_t uPass) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + int rc; + LogFlow(("ohciR3LoadExec:\n")); + Assert(uPass == SSM_PASS_FINAL); NOREF(uPass); + + if (uVersion == OHCI_SAVED_STATE_VERSION) + { + rc = SSMR3GetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields[0], NULL); + if (RT_FAILURE(rc)) + return rc; + } + else if (uVersion == OHCI_SAVED_STATE_VERSION_8PORTS) + { + rc = SSMR3GetStructEx(pSSM, pThis, sizeof(*pThis), 0 /*fFlags*/, &g_aOhciFields8Ports[0], NULL); + if (RT_FAILURE(rc)) + return rc; + } + else + AssertMsgFailedReturn(("%d\n", uVersion), VERR_SSM_UNSUPPORTED_DATA_UNIT_VERSION); + + /* + * Finally restore the timer. + */ + return TMR3TimerLoad(pThis->pEndOfFrameTimerR3, pSSM); +} + + +/** + * Done state load operation. + * + * @returns VBox load code. + * @param pDevIns Device instance of the device which registered the data unit. + * @param pSSM SSM operation handle. + */ +static DECLCALLBACK(int) ohciR3LoadDone(PPDMDEVINS pDevIns, PSSMHANDLE pSSM) +{ + RT_NOREF(pSSM); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3LoadDone:\n")); + + /* + * Start a timer if we've got devices to reattach + */ + if (pThis->pLoad) + { + int rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ohciR3LoadReattachDevices, pThis, + TMTIMER_FLAGS_NO_CRIT_SECT, "OHCI reattach devices on load", + &pThis->pLoad->pTimer); + if (RT_SUCCESS(rc)) + rc = TMTimerSetMillies(pThis->pLoad->pTimer, 250); + return rc; + } + + return VINF_SUCCESS; +} + + +/** + * Reattaches devices after a saved state load. + */ +static DECLCALLBACK(void) ohciR3LoadReattachDevices(PPDMDEVINS pDevIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pDevIns); + POHCI pThis = (POHCI)pvUser; + POHCILOAD pLoad = pThis->pLoad; + POHCIROOTHUB pRh = &pThis->RootHub; + LogFlow(("ohciR3LoadReattachDevices:\n")); + + /* + * Reattach devices. + */ + for (unsigned i = 0; i < pLoad->cDevs; i++) + VUSBIRhAttachDevice(pRh->pIRhConn, pLoad->apDevs[i]); + + /* + * Cleanup. + */ + TMR3TimerDestroy(pTimer); + MMR3HeapFree(pLoad); + pThis->pLoad = NULL; +} + + +/** + * Reset notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Reset(PPDMDEVINS pDevIns) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlow(("ohciR3Reset:\n")); + + /* + * There is no distinction between cold boot, warm reboot and software reboots, + * all of these are treated as cold boots. We are also doing the initialization + * job of a BIOS or SMM driver. + * + * Important: Don't confuse UsbReset with hardware reset. Hardware reset is + * just one way of getting into the UsbReset state. + */ + ohciR3DoReset(pThis, OHCI_USB_RESET, true /* reset devices */); +} + + +/** + * Resume notification. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(void) ohciR3Resume(PPDMDEVINS pDevIns) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + LogFlowFunc(("\n")); + + /* Restart the frame thread if the timer is active. */ + if (TMTimerIsActive(pThis->pEndOfFrameTimerR3)) + { + int rc = TMTimerStop(pThis->pEndOfFrameTimerR3); + AssertRC(rc); + + LogFlowFunc(("Bus was active, enable periodic frame processing\n")); + rc = pThis->RootHub.pIRhConn->pfnSetPeriodicFrameProcessing(pThis->RootHub.pIRhConn, OHCI_DEFAULT_TIMER_FREQ); + AssertRC(rc); + } +} + + +/** + * Info handler, device version. Dumps OHCI control registers. + * + * @param pDevIns Device instance which registered the info. + * @param pHlp Callback functions for doing output. + * @param pszArgs Argument string. Optional and specific to the handler. + */ +static DECLCALLBACK(void) ohciR3InfoRegs(PPDMDEVINS pDevIns, PCDBGFINFOHLP pHlp, const char *pszArgs) +{ + RT_NOREF(pszArgs); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + uint32_t val, ctl, status; + + /* Control register */ + ctl = pThis->ctl; + pHlp->pfnPrintf(pHlp, "HcControl: %08x - CBSR=%d PLE=%d IE=%d CLE=%d BLE=%d HCFS=%#x IR=%d RWC=%d RWE=%d\n", + ctl, ctl & 3, (ctl >> 2) & 1, (ctl >> 3) & 1, (ctl >> 4) & 1, (ctl >> 5) & 1, (ctl >> 6) & 3, (ctl >> 8) & 1, + (ctl >> 9) & 1, (ctl >> 10) & 1); + + /* Command status register */ + status = pThis->status; + pHlp->pfnPrintf(pHlp, "HcCommandStatus: %08x - HCR=%d CLF=%d BLF=%d OCR=%d SOC=%d\n", + status, status & 1, (status >> 1) & 1, (status >> 2) & 1, (status >> 3) & 1, (status >> 16) & 3); + + /* Interrupt status register */ + val = pThis->intr_status; + pHlp->pfnPrintf(pHlp, "HcInterruptStatus: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1); + + /* Interrupt enable register */ + val = pThis->intr; + pHlp->pfnPrintf(pHlp, "HcInterruptEnable: %08x - SO=%d WDH=%d SF=%d RD=%d UE=%d FNO=%d RHSC=%d OC=%d MIE=%d\n", + val, val & 1, (val >> 1) & 1, (val >> 2) & 1, (val >> 3) & 1, (val >> 4) & 1, (val >> 5) & 1, + (val >> 6) & 1, (val >> 30) & 1, (val >> 31) & 1); + + /* HCCA address register */ + pHlp->pfnPrintf(pHlp, "HcHCCA: %08x\n", pThis->hcca); + + /* Current periodic ED register */ + pHlp->pfnPrintf(pHlp, "HcPeriodCurrentED: %08x\n", pThis->per_cur); + + /* Control ED registers */ + pHlp->pfnPrintf(pHlp, "HcControlHeadED: %08x\n", pThis->ctrl_head); + pHlp->pfnPrintf(pHlp, "HcControlCurrentED: %08x\n", pThis->ctrl_cur); + + /* Bulk ED registers */ + pHlp->pfnPrintf(pHlp, "HcBulkHeadED: %08x\n", pThis->bulk_head); + pHlp->pfnPrintf(pHlp, "HcBulkCurrentED: %08x\n", pThis->bulk_cur); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Done head register */ + pHlp->pfnPrintf(pHlp, "HcDoneHead: %08x\n", pThis->done); + + /* Root hub descriptor A */ + val = pThis->RootHub.desc_a; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorA: %08x - NDP=%d PSM=%d NPS=%d DT=%d OCPM=%d NOCP=%d POTPGT=%d\n", + val, (uint8_t)val, (val >> 8) & 1, (val >> 9) & 1, (val >> 10) & 1, (val >> 11) & 1, (val >> 12) & 1, (uint8_t)(val >> 24)); + + /* Root hub descriptor B */ + val = pThis->RootHub.desc_b; + pHlp->pfnPrintf(pHlp, "HcRhDescriptorB: %08x - DR=%#04x PPCM=%#04x\n", val, (uint16_t)val, (uint16_t)(val >> 16)); + + /* Root hub status register */ + val = pThis->RootHub.status; + pHlp->pfnPrintf(pHlp, "HcRhStatus: %08x - LPS=%d OCI=%d DRWE=%d LPSC=%d OCIC=%d CRWE=%d\n\n", + val, val & 1, (val >> 1) & 1, (val >> 15) & 1, (val >> 16) & 1, (val >> 17) & 1, (val >> 31) & 1); + + /* Port status registers */ + for (unsigned i = 0; i < OHCI_NDP_CFG(pThis); ++i) + { + val = pThis->RootHub.aPorts[i].fReg; + pHlp->pfnPrintf(pHlp, "HcRhPortStatus%02d: CCS=%d PES =%d PSS =%d POCI=%d PRS =%d PPS=%d LSDA=%d\n" + " %08x - CSC=%d PESC=%d PSSC=%d OCIC=%d PRSC=%d\n", + i, val & 1, (val >> 1) & 1, (val >> 2) & 1,(val >> 3) & 1, (val >> 4) & 1, (val >> 8) & 1, (val >> 9) & 1, + val, (val >> 16) & 1, (val >> 17) & 1, (val >> 18) & 1, (val >> 19) & 1, (val >> 20) & 1); + } +} + + +/** + * Relocate device instance data. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + * @param offDelta The relocation delta. + */ +static DECLCALLBACK(void) ohciR3Relocate(PPDMDEVINS pDevIns, RTGCINTPTR offDelta) +{ + RT_NOREF(offDelta); + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); + pThis->pEndOfFrameTimerRC = TMTimerRCPtr(pThis->pEndOfFrameTimerR3); +} + + +/** + * Destruct a device instance. + * + * Most VM resources are freed by the VM. This callback is provided so that any non-VM + * resources can be freed correctly. + * + * @returns VBox status code. + * @param pDevIns The device instance data. + */ +static DECLCALLBACK(int) ohciR3Destruct(PPDMDEVINS pDevIns) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + PDMDEV_CHECK_VERSIONS_RETURN_QUIET(pDevIns); + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + ohciR3PhysReadCacheFree(pThis->pCacheED); + pThis->pCacheED = NULL; + ohciR3PhysReadCacheFree(pThis->pCacheTD); + pThis->pCacheTD = NULL; +#endif + + if (RTCritSectIsInitialized(&pThis->CritSect)) + RTCritSectDelete(&pThis->CritSect); + PDMR3CritSectDelete(&pThis->CsIrq); + + /* + * Tear down the per endpoint in-flight tracking... + */ + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMDEVREG,pfnConstruct,OHCI constructor} + */ +static DECLCALLBACK(int) ohciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE pCfg) +{ + POHCI pThis = PDMINS_2_DATA(pDevIns, POHCI); + uint32_t cPorts; + PDMDEV_CHECK_VERSIONS_RETURN(pDevIns); + + /* + * Init instance data. + */ + pThis->pDevInsR3 = pDevIns; + pThis->pDevInsR0 = PDMDEVINS_2_R0PTR(pDevIns); + pThis->pDevInsRC = PDMDEVINS_2_RCPTR(pDevIns); + + PCIDevSetVendorId (&pThis->PciDev, 0x106b); + PCIDevSetDeviceId (&pThis->PciDev, 0x003f); + PCIDevSetClassProg (&pThis->PciDev, 0x10); /* OHCI */ + PCIDevSetClassSub (&pThis->PciDev, 0x03); + PCIDevSetClassBase (&pThis->PciDev, 0x0c); + PCIDevSetInterruptPin (&pThis->PciDev, 0x01); +#ifdef VBOX_WITH_MSI_DEVICES + PCIDevSetStatus (&pThis->PciDev, VBOX_PCI_STATUS_CAP_LIST); + PCIDevSetCapabilityList(&pThis->PciDev, 0x80); +#endif + + pThis->RootHub.pOhci = pThis; + pThis->RootHub.IBase.pfnQueryInterface = ohciR3RhQueryInterface; + pThis->RootHub.IRhPort.pfnGetAvailablePorts = ohciR3RhGetAvailablePorts; + pThis->RootHub.IRhPort.pfnGetUSBVersions = ohciR3RhGetUSBVersions; + pThis->RootHub.IRhPort.pfnAttach = ohciR3RhAttach; + pThis->RootHub.IRhPort.pfnDetach = ohciR3RhDetach; + pThis->RootHub.IRhPort.pfnReset = ohciR3RhReset; + pThis->RootHub.IRhPort.pfnXferCompletion = ohciR3RhXferCompletion; + pThis->RootHub.IRhPort.pfnXferError = ohciR3RhXferError; + pThis->RootHub.IRhPort.pfnStartFrame = ohciR3StartFrame; + pThis->RootHub.IRhPort.pfnFrameRateChanged = ohciR3FrameRateChanged; + + /* USB LED */ + pThis->RootHub.Led.u32Magic = PDMLED_MAGIC; + pThis->RootHub.ILeds.pfnQueryStatusLed = ohciR3RhQueryStatusLed; + + + /* + * Read configuration. + */ + PDMDEV_VALIDATE_CONFIG_RETURN(pDevIns, "RZEnabled", ""); + int rc = CFGMR3QueryBoolDef(pCfg, "RZEnabled", &pThis->fRZEnabled, true); + AssertLogRelRCReturn(rc, rc); + + /* Number of ports option. */ + rc = CFGMR3QueryU32Def(pCfg, "Ports", &cPorts, OHCI_NDP_DEFAULT); + if (RT_FAILURE(rc)) + return PDMDEV_SET_ERROR(pDevIns, rc, + N_("OHCI configuration error: failed to read Ports as integer")); + + if (cPorts == 0 || cPorts > OHCI_NDP_MAX) + return PDMDevHlpVMSetError(pDevIns, VERR_INVALID_PARAMETER, RT_SRC_POS, + N_("OHCI configuration error: Ports must be in range [%u,%u]"), + 1, OHCI_NDP_MAX); + + /* Store the configured NDP; it will be used everywhere else from now on. */ + pThis->RootHub.desc_a = cPorts; + + /* + * Register PCI device and I/O region. + */ + rc = PDMDevHlpPCIRegister(pDevIns, &pThis->PciDev); + if (RT_FAILURE(rc)) + return rc; + +#ifdef VBOX_WITH_MSI_DEVICES + PDMMSIREG MsiReg; + RT_ZERO(MsiReg); + MsiReg.cMsiVectors = 1; + MsiReg.iMsiCapOffset = 0x80; + MsiReg.iMsiNextOffset = 0x00; + rc = PDMDevHlpPCIRegisterMsi(pDevIns, &MsiReg); + if (RT_FAILURE(rc)) + { + PCIDevSetCapabilityList(&pThis->PciDev, 0x0); + /* That's OK, we can work without MSI */ + } +#endif + + rc = PDMDevHlpPCIIORegionRegister(pDevIns, 0, 4096, PCI_ADDRESS_SPACE_MEM, ohciR3Map); + if (RT_FAILURE(rc)) + return rc; + + /* + * Create the end-of-frame timer. + */ + rc = PDMDevHlpTMTimerCreate(pDevIns, TMCLOCK_VIRTUAL, ohciR3FrameBoundaryTimer, pThis, + TMTIMER_FLAGS_DEFAULT_CRIT_SECT, "USB Frame Timer", + &pThis->pEndOfFrameTimerR3); + if (RT_FAILURE(rc)) + return rc; + pThis->pEndOfFrameTimerR0 = TMTimerR0Ptr(pThis->pEndOfFrameTimerR3); + pThis->pEndOfFrameTimerRC = TMTimerRCPtr(pThis->pEndOfFrameTimerR3); + + /* + * Register the saved state data unit. + */ + rc = PDMDevHlpSSMRegisterEx(pDevIns, OHCI_SAVED_STATE_VERSION, sizeof(*pThis), NULL, + NULL, NULL, NULL, + ohciR3SavePrep, ohciR3SaveExec, ohciR3SaveDone, + ohciR3LoadPrep, ohciR3LoadExec, ohciR3LoadDone); + if (RT_FAILURE(rc)) + return rc; + + /* + * Attach to the VBox USB RootHub Driver on LUN #0. + */ + rc = PDMDevHlpDriverAttach(pDevIns, 0, &pThis->RootHub.IBase, &pThis->RootHub.pIBase, "RootHub"); + if (RT_FAILURE(rc)) + { + AssertMsgFailed(("Configuration error: No roothub driver attached to LUN #0!\n")); + return rc; + } + pThis->RootHub.pIRhConn = PDMIBASE_QUERY_INTERFACE(pThis->RootHub.pIBase, VUSBIROOTHUBCONNECTOR); + AssertMsgReturn(pThis->RootHub.pIRhConn, + ("Configuration error: The driver doesn't provide the VUSBIROOTHUBCONNECTOR interface!\n"), + VERR_PDM_MISSING_INTERFACE); + pThis->RootHub.pIDev = PDMIBASE_QUERY_INTERFACE(pThis->RootHub.pIBase, VUSBIDEVICE); + AssertMsgReturn(pThis->RootHub.pIDev, + ("Configuration error: The driver doesn't provide the VUSBIDEVICE interface!\n"), + VERR_PDM_MISSING_INTERFACE); + + /* + * Attach status driver (optional). + */ + PPDMIBASE pBase; + rc = PDMDevHlpDriverAttach(pDevIns, PDM_STATUS_LUN, &pThis->RootHub.IBase, &pBase, "Status Port"); + if (RT_SUCCESS(rc)) + pThis->RootHub.pLedsConnector = PDMIBASE_QUERY_INTERFACE(pBase, PDMILEDCONNECTORS); + else if (rc != VERR_PDM_NO_ATTACHED_DRIVER) + { + AssertMsgFailed(("Failed to attach to status driver. rc=%Rrc\n", rc)); + return rc; + } + + /* Set URB parameters. */ + rc = VUSBIRhSetUrbParams(pThis->RootHub.pIRhConn, sizeof(VUSBURBHCIINT), sizeof(VUSBURBHCITDINT)); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("OHCI: Failed to set URB parameters")); + + /* + * Calculate the timer intervals. + * This assumes that the VM timer doesn't change frequency during the run. + */ + pThis->u64TimerHz = TMTimerGetFreq(pThis->CTX_SUFF(pEndOfFrameTimer)); + + rc = PDMDevHlpCritSectInit(pDevIns, &pThis->CsIrq, RT_SRC_POS, "OHCI#%uIrq", iInstance); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("OHCI: Failed to create critical section")); + + rc = RTCritSectInit(&pThis->CritSect); + if (RT_FAILURE(rc)) + return PDMDevHlpVMSetError(pDevIns, rc, RT_SRC_POS, + N_("OHCI: Failed to create critical section")); + +#ifdef VBOX_WITH_OHCI_PHYS_READ_CACHE + pThis->pCacheED = ohciR3PhysReadCacheAlloc(); + pThis->pCacheTD = ohciR3PhysReadCacheAlloc(); + if (pThis->pCacheED == NULL || pThis->pCacheTD == NULL) + return PDMDevHlpVMSetError(pDevIns, VERR_NO_MEMORY, RT_SRC_POS, + N_("OHCI: Failed to allocate PhysRead cache")); +#endif + + /* + * Do a hardware reset. + */ + ohciR3DoReset(pThis, OHCI_USB_RESET, false /* don't reset devices */); + +#ifdef VBOX_WITH_STATISTICS + /* + * Register statistics. + */ + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledIsocUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/CanceledIsocUrbs", STAMUNIT_OCCURENCES, "Detected canceled isochronous URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatCanceledGenUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/CanceledGenUrbs", STAMUNIT_OCCURENCES, "Detected canceled general URBs."); + PDMDevHlpSTAMRegister(pDevIns, &pThis->StatDroppedUrbs, STAMTYPE_COUNTER, "/Devices/OHCI/DroppedUrbs", STAMUNIT_OCCURENCES, "Dropped URBs (endpoint halted, or URB canceled)."); +#endif + + /* + * Register debugger info callbacks. + */ + PDMDevHlpDBGFInfoRegister(pDevIns, "ohci", "OHCI control registers.", ohciR3InfoRegs); + +#if 0/*def DEBUG_bird*/ +// g_fLogInterruptEPs = true; + g_fLogControlEPs = true; + g_fLogBulkEPs = true; +#endif + + return VINF_SUCCESS; +} + + +const PDMDEVREG g_DeviceOHCI = +{ + /* u32version */ + PDM_DEVREG_VERSION, + /* szName */ + "usb-ohci", + /* szRCMod */ + "VBoxDDRC.rc", + /* szR0Mod */ + "VBoxDDR0.r0", + /* pszDescription */ + "OHCI USB controller.\n", + /* fFlags */ + PDM_DEVREG_FLAGS_DEFAULT_BITS | PDM_DEVREG_FLAGS_RC | PDM_DEVREG_FLAGS_R0, + /* fClass */ + PDM_DEVREG_CLASS_BUS_USB, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(OHCI), + /* pfnConstruct */ + ohciR3Construct, + /* pfnDestruct */ + ohciR3Destruct, + /* pfnRelocate */ + ohciR3Relocate, + /* pfnMemSetup */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + ohciR3Reset, + /* pfnSuspend */ + NULL, + /* pfnResume */ + ohciR3Resume, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnQueryInterface */ + NULL, + /* pfnInitComplete */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32VersionEnd */ + PDM_DEVREG_VERSION +}; + +#endif /* IN_RING3 */ +#endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/Devices/USB/DrvVUSBRootHub.cpp b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp new file mode 100644 index 00000000..24b10a7a --- /dev/null +++ b/src/VBox/Devices/USB/DrvVUSBRootHub.cpp @@ -0,0 +1,1592 @@ +/* $Id: DrvVUSBRootHub.cpp $ */ +/** @file + * Virtual USB - Root Hub Driver. + */ + +/* + * Copyright (C) 2005-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/** @page pg_dev_vusb VUSB - Virtual USB + * + * @todo read thru this and correct typos. Merge with old docs. + * + * + * The Virtual USB component glues USB devices and host controllers together. + * The VUSB takes the form of a PDM driver which is attached to the HCI. USB + * devices are created by, attached to, and managed by the VUSB roothub. The + * VUSB also exposes an interface which is used by Main to attach and detach + * proxied USB devices. + * + * + * @section sec_dev_vusb_urb The Life of an URB + * + * The URB is created when the HCI calls the roothub (VUSB) method pfnNewUrb. + * VUSB has a pool of URBs, if no free URBs are available a new one is + * allocated. The returned URB starts life in the ALLOCATED state and all + * fields are initialized with sensible defaults. + * + * The HCI then copies any request data into the URB if it's an host2dev + * transfer. It then submits the URB by calling the pfnSubmitUrb roothub + * method. + * + * pfnSubmitUrb will start by checking if it knows the device address, and if + * it doesn't the URB is completed with a device-not-ready error. When the + * device address is known to it, action is taken based on the kind of + * transfer it is. There are four kinds of transfers: 1. control, 2. bulk, + * 3. interrupt, and 4. isochronous. In either case something eventually ends + * up being submitted to the device. + * + * + * If an URB fails submitting, may or may not be completed. This depends on + * heuristics in some cases and on the kind of failure in others. If + * pfnSubmitUrb returns a failure, the HCI should retry submitting it at a + * later time. If pfnSubmitUrb returns success the URB is submitted, and it + * can even been completed. + * + * The URB is in the IN_FLIGHT state from the time it's successfully submitted + * and till it's reaped or cancelled. + * + * When an URB transfer or in some case submit failure occurs, the pfnXferError + * callback of the HCI is consulted about what to do. If pfnXferError indicates + * that the URB should be retried, pfnSubmitUrb will fail. If it indicates that + * it should fail, the URB will be completed. + * + * Completing an URB means that the URB status is set and the HCI + * pfnXferCompletion callback is invoked with the URB. The HCI is the supposed + * to report the transfer status to the guest OS. After completion the URB + * is freed and returned to the pool, unless it was cancelled. If it was + * cancelled it will have to await reaping before it's actually freed. + * + * + * @subsection subsec_dev_vusb_urb_ctrl Control + * + * The control transfer is the most complex one, from VUSB's point of view, + * with its three stages and being bi-directional. A control transfer starts + * with a SETUP packet containing the request description and two basic + * parameters. It is followed by zero or more DATA packets which either picks + * up incoming data (dev2host) or supplies the request data (host2dev). This + * can then be followed by a STATUS packet which gets the status of the whole + * transfer. + * + * What makes the control transfer complicated is that for a host2dev request + * the URB is assembled from the SETUP and DATA stage, and for a dev2host + * request the returned data must be kept around for the DATA stage. For both + * transfer directions the status of the transfer has to be kept around for + * the STATUS stage. + * + * To complicate matters further, VUSB must intercept and in some cases emulate + * some of the standard requests in order to keep the virtual device state + * correct and provide the correct virtualization of a device. + * + * @subsection subsec_dev_vusb_urb_bulk Bulk and Interrupt + * + * The bulk and interrupt transfer types are relativly simple compared to the + * control transfer. VUSB is not inspecting the request content or anything, + * but passes it down the device. + * + * @subsection subsec_dev_vusb_urb_isoc Isochronous + * + * This kind of transfers hasn't yet been implemented. + * + */ + + +/** @page pg_dev_vusb_old VUSB - Virtual USB Core + * + * The virtual USB core is controlled by the roothub and the underlying HCI + * emulator, it is responsible for device addressing, managing configurations, + * interfaces and endpoints, assembling and splitting multi-part control + * messages and in general acts as a middle layer between the USB device + * emulation code and USB HCI emulation code. + * + * All USB devices are represented by a struct vusb_dev. This structure + * contains things like the device state, device address, all the configuration + * descriptors, the currently selected configuration and a mapping between + * endpoint addresses and endpoint descriptors. + * + * Each vusb_dev also has a pointer to a vusb_dev_ops structure which serves as + * the virtual method table and includes a virtual constructor and destructor. + * After a vusb_dev is created it may be attached to a hub device such as a + * roothub (using vusbHubAttach). Although each hub structure has cPorts + * and cDevices fields, it is the responsibility of the hub device to allocate + * a free port for the new device. + * + * Devices can chose one of two interfaces for dealing with requests, the + * synchronous interface or the asynchronous interface. The synchronous + * interface is much simpler and ought to be used for devices which are + * unlikely to sleep for long periods in order to serve requests. The + * asynchronous interface on the other hand is more difficult to use but is + * useful for the USB proxy or if one were to write a mass storage device + * emulator. Currently the synchronous interface only supports control and bulk + * endpoints and is no longer used by anything. + * + * In order to use the asynchronous interface, the queue_urb, cancel_urb and + * pfnUrbReap fields must be set in the devices vusb_dev_ops structure. The + * queue_urb method is used to submit a request to a device without blocking, + * it returns 1 if successful and 0 on any kind of failure. A successfully + * queued URB is completed when the pfnUrbReap method returns it. Each function + * address is reference counted so that pfnUrbReap will only be called if there + * are URBs outstanding. For a roothub to reap an URB from any one of it's + * devices, the vusbRhReapAsyncUrbs() function is used. + * + * There are four types of messages an URB may contain: + * -# Control - represents a single packet of a multi-packet control + * transfer, these are only really used by the host controller to + * submit the parts to the usb core. + * -# Message - the usb core assembles multiple control transfers in + * to single message transfers. In this case the data buffer + * contains the setup packet in little endian followed by the full + * buffer. In the case of an host-to-device control message, the + * message packet is created when the STATUS transfer is seen. In + * the case of device-to-host messages, the message packet is + * created after the SETUP transfer is seen. Also, certain control + * requests never go the real device and get handled synchronously. + * -# Bulk - Currently the only endpoint type that does error checking + * and endpoint halting. + * -# Interrupt - The only non-periodic type supported. + * + * Hubs are special cases of devices, they have a number of downstream ports + * that other devices can be attached to and removed from. + * + * After a device has been attached (vusbHubAttach): + * -# The hub attach method is called, which sends a hub status + * change message to the OS. + * -# The OS resets the device, and it appears on the default + * address with it's config 0 selected (a pseudo-config that + * contains only 1 interface with 1 endpoint - the default + * message pipe). + * -# The OS assigns the device a new address and selects an + * appropriate config. + * -# The device is ready. + * + * After a device has been detached (vusbDevDetach): + * -# All pending URBs are cancelled. + * -# The devices address is unassigned. + * -# The hub detach method is called which signals the OS + * of the status change. + * -# The OS unlinks the ED's for that device. + * + * A device can also request detachment from within its own methods by + * calling vusbDevUnplugged(). + * + * Roothubs are responsible for driving the whole system, they are special + * cases of hubs and as such implement attach and detach methods, each one + * is described by a struct vusb_roothub. Once a roothub has submitted an + * URB to the USB core, a number of callbacks to the roothub are required + * for when the URB completes, since the roothub typically wants to inform + * the OS when transfers are completed. + * + * There are four callbacks to be concerned with: + * -# prepare - This is called after the URB is successfully queued. + * -# completion - This is called after the URB completed. + * -# error - This is called if the URB errored, some systems have + * automatic resubmission of failed requests, so this callback + * should keep track of the error count and return 1 if the count + * is above the number of allowed resubmissions. + * -# halt_ep - This is called after errors on bulk pipes in order + * to halt the pipe. + * + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/uuid.h> +#include "VUSBInternal.h" +#include "VBoxDD.h" + + + +/** + * Attaches a device to a specific hub. + * + * This function is called by the vusb_add_device() and vusbRhAttachDevice(). + * + * @returns VBox status code. + * @param pHub The hub to attach it to. + * @param pDev The device to attach. + * @thread EMT + */ +static int vusbHubAttach(PVUSBHUB pHub, PVUSBDEV pDev) +{ + LogFlow(("vusbHubAttach: pHub=%p[%s] pDev=%p[%s]\n", pHub, pHub->pszName, pDev, pDev->pUsbIns->pszName)); + AssertMsg(pDev->enmState == VUSB_DEVICE_STATE_DETACHED, ("enmState=%d\n", pDev->enmState)); + + pDev->pHub = pHub; + pDev->enmState = VUSB_DEVICE_STATE_ATTACHED; + + /* noone else ever messes with the default pipe while we are attached */ + vusbDevMapEndpoint(pDev, &g_Endpoint0); + vusbDevDoSelectConfig(pDev, &g_Config0); + + int rc = pHub->pOps->pfnAttach(pHub, pDev); + if (RT_FAILURE(rc)) + { + pDev->pHub = NULL; + pDev->enmState = VUSB_DEVICE_STATE_DETACHED; + } + return rc; +} + + +/* -=-=-=-=-=- PDMUSBHUBREG methods -=-=-=-=-=- */ + +/** @interface_method_impl{PDMUSBHUBREG,pfnAttachDevice} */ +static DECLCALLBACK(int) vusbPDMHubAttachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, const char *pszCaptureFilename, uint32_t *piPort) +{ + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + + /* + * Allocate a new VUSB device and initialize it. + */ + PVUSBDEV pDev = (PVUSBDEV)RTMemAllocZ(sizeof(*pDev)); + AssertReturn(pDev, VERR_NO_MEMORY); + int rc = vusbDevInit(pDev, pUsbIns, pszCaptureFilename); + if (RT_SUCCESS(rc)) + { + pUsbIns->pvVUsbDev2 = pDev; + rc = vusbHubAttach(&pThis->Hub, pDev); + if (RT_SUCCESS(rc)) + { + *piPort = UINT32_MAX; /// @todo implement piPort + return rc; + } + + RTMemFree(pDev->paIfStates); + pUsbIns->pvVUsbDev2 = NULL; + } + vusbDevRelease(pDev); + return rc; +} + + +/** @interface_method_impl{PDMUSBHUBREG,pfnDetachDevice} */ +static DECLCALLBACK(int) vusbPDMHubDetachDevice(PPDMDRVINS pDrvIns, PPDMUSBINS pUsbIns, uint32_t iPort) +{ + RT_NOREF(pDrvIns, iPort); + PVUSBDEV pDev = (PVUSBDEV)pUsbIns->pvVUsbDev2; + Assert(pDev); + + /* + * Deal with pending async reset. + * (anything but reset) + */ + vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET); + + /* + * Detach and free resources. + */ + if (pDev->pHub) + vusbDevDetach(pDev); + + vusbDevRelease(pDev); + return VINF_SUCCESS; +} + +/** + * The hub registration structure. + */ +static const PDMUSBHUBREG g_vusbHubReg = +{ + PDM_USBHUBREG_VERSION, + vusbPDMHubAttachDevice, + vusbPDMHubDetachDevice, + PDM_USBHUBREG_VERSION +}; + + +/* -=-=-=-=-=- VUSBIROOTHUBCONNECTOR methods -=-=-=-=-=- */ + + +/** + * Finds an device attached to a roothub by it's address. + * + * @returns Pointer to the device. + * @returns NULL if not found. + * @param pRh Pointer to the root hub. + * @param Address The device address. + */ +static PVUSBDEV vusbRhFindDevByAddress(PVUSBROOTHUB pRh, uint8_t Address) +{ + unsigned iHash = vusbHashAddress(Address); + PVUSBDEV pDev = NULL; + + RTCritSectEnter(&pRh->CritSectDevices); + for (PVUSBDEV pCur = pRh->apAddrHash[iHash]; pCur; pCur = pCur->pNextHash) + if (pCur->u8Address == Address) + { + pDev = pCur; + break; + } + + if (pDev) + vusbDevRetain(pDev); + RTCritSectLeave(&pRh->CritSectDevices); + return pDev; +} + + +/** + * Callback for freeing an URB. + * @param pUrb The URB to free. + */ +static DECLCALLBACK(void) vusbRhFreeUrb(PVUSBURB pUrb) +{ + /* + * Assert sanity. + */ + vusbUrbAssert(pUrb); + PVUSBROOTHUB pRh = (PVUSBROOTHUB)pUrb->pVUsb->pvFreeCtx; + Assert(pRh); + + Assert(pUrb->enmState != VUSBURBSTATE_FREE); + + /* + * Free the URB description (logging builds only). + */ + if (pUrb->pszDesc) + { + RTStrFree(pUrb->pszDesc); + pUrb->pszDesc = NULL; + } + + /* The URB comes from the roothub if there is no device (invalid address). */ + if (pUrb->pVUsb->pDev) + { + PVUSBDEV pDev = pUrb->pVUsb->pDev; + + vusbUrbPoolFree(&pUrb->pVUsb->pDev->UrbPool, pUrb); + vusbDevRelease(pDev); + } + else + vusbUrbPoolFree(&pRh->Hub.Dev.UrbPool, pUrb); +} + + +/** + * Worker routine for vusbRhConnNewUrb(). + */ +static PVUSBURB vusbRhNewUrb(PVUSBROOTHUB pRh, uint8_t DstAddress, PVUSBDEV pDev, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) +{ + RT_NOREF(pszTag); + PVUSBURBPOOL pUrbPool = &pRh->Hub.Dev.UrbPool; + + if (!pDev) + pDev = vusbRhFindDevByAddress(pRh, DstAddress); + else + vusbDevRetain(pDev); + + if (pDev) + pUrbPool = &pDev->UrbPool; + + PVUSBURB pUrb = vusbUrbPoolAlloc(pUrbPool, enmType, enmDir, cbData, + pRh->cbHci, pRh->cbHciTd, cTds); + if (RT_LIKELY(pUrb)) + { + pUrb->pVUsb->pvFreeCtx = pRh; + pUrb->pVUsb->pfnFree = vusbRhFreeUrb; + pUrb->DstAddress = DstAddress; + pUrb->pVUsb->pDev = pDev; + +#ifdef LOG_ENABLED + const char *pszType = NULL; + + switch(pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: + pszType = "ctrl"; + break; + case VUSBXFERTYPE_INTR: + pszType = "intr"; + break; + case VUSBXFERTYPE_BULK: + pszType = "bulk"; + break; + case VUSBXFERTYPE_ISOC: + pszType = "isoc"; + break; + default: + pszType = "invld"; + break; + } + + pRh->iSerial = (pRh->iSerial + 1) % 10000; + RTStrAPrintf(&pUrb->pszDesc, "URB %p %s%c%04d (%s)", pUrb, pszType, + (pUrb->enmDir == VUSBDIRECTION_IN) ? '<' : ((pUrb->enmDir == VUSBDIRECTION_SETUP) ? 's' : '>'), + pRh->iSerial, pszTag ? pszTag : "<none>"); +#endif + } + + return pUrb; +} + + +/** + * Calculate frame timer variables given a frame rate. + */ +static void vusbRhR3CalcTimerIntervals(PVUSBROOTHUB pThis, uint32_t u32FrameRate) +{ + pThis->nsWait = RT_NS_1SEC / u32FrameRate; + pThis->uFrameRate = u32FrameRate; + /* Inform the HCD about the new frame rate. */ + pThis->pIRhPort->pfnFrameRateChanged(pThis->pIRhPort, u32FrameRate); +} + + +/** + * Calculates the new frame rate based on the idle detection and number of idle + * cycles. + * + * @returns nothing. + * @param pThis The roothub instance data. + * @param fIdle Flag whether the last frame didn't produce any activity. + */ +static void vusbRhR3FrameRateCalcNew(PVUSBROOTHUB pThis, bool fIdle) +{ + uint32_t uNewFrameRate = pThis->uFrameRate; + + /* + * Adjust the frame timer interval based on idle detection. + */ + if (fIdle) + { + pThis->cIdleCycles++; + /* Set the new frame rate based on how long we've been idle. Tunable. */ + switch (pThis->cIdleCycles) + { + case 4: uNewFrameRate = 500; break; /* 2ms interval */ + case 16:uNewFrameRate = 125; break; /* 8ms interval */ + case 24:uNewFrameRate = 50; break; /* 20ms interval */ + default: break; + } + /* Avoid overflow. */ + if (pThis->cIdleCycles > 60000) + pThis->cIdleCycles = 20000; + } + else + { + if (pThis->cIdleCycles) + { + pThis->cIdleCycles = 0; + uNewFrameRate = pThis->uFrameRateDefault; + } + } + + if ( uNewFrameRate != pThis->uFrameRate + && uNewFrameRate) + { + LogFlow(("Frame rate changed from %u to %u\n", pThis->uFrameRate, uNewFrameRate)); + vusbRhR3CalcTimerIntervals(pThis, uNewFrameRate); + } +} + + +/** + * The core frame processing routine keeping track of the elapsed time and calling into + * the device emulation above us to do the work. + * + * @returns Relative timespan when to process the next frame. + * @param pThis The roothub instance data. + * @param fCallback Flag whether this method is called from the URB completion callback or + * from the worker thread (only used for statistics). + */ +DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback) +{ + uint64_t tsNext = 0; + uint64_t tsNanoStart = RTTimeNanoTS(); + + /* Don't do anything if we are not supposed to process anything (EHCI and XHCI). */ + if (!pThis->uFrameRateDefault) + return 0; + + if (ASMAtomicXchgBool(&pThis->fFrameProcessing, true)) + return pThis->nsWait; + + if ( tsNanoStart > pThis->tsFrameProcessed + && tsNanoStart - pThis->tsFrameProcessed >= 750 * RT_NS_1US) + { + LogFlowFunc(("Starting new frame at ts %llu\n", tsNanoStart)); + + bool fIdle = pThis->pIRhPort->pfnStartFrame(pThis->pIRhPort, 0 /* u32FrameNo */); + vusbRhR3FrameRateCalcNew(pThis, fIdle); + + uint64_t tsNow = RTTimeNanoTS(); + tsNext = (tsNanoStart + pThis->nsWait) > tsNow ? (tsNanoStart + pThis->nsWait) - tsNow : 0; + pThis->tsFrameProcessed = tsNanoStart; + LogFlowFunc(("Current frame took %llu nano seconds to process, next frame in %llu ns\n", tsNow - tsNanoStart, tsNext)); + if (fCallback) + STAM_COUNTER_INC(&pThis->StatFramesProcessedClbk); + else + STAM_COUNTER_INC(&pThis->StatFramesProcessedThread); + } + else + { + tsNext = (pThis->tsFrameProcessed + pThis->nsWait) > tsNanoStart ? (pThis->tsFrameProcessed + pThis->nsWait) - tsNanoStart : 0; + LogFlowFunc(("Next frame is too far away in the future, waiting... (tsNanoStart=%llu tsFrameProcessed=%llu)\n", + tsNanoStart, pThis->tsFrameProcessed)); + } + + ASMAtomicXchgBool(&pThis->fFrameProcessing, false); + LogFlowFunc(("returns %llu\n", tsNext)); + return tsNext; +} + + +/** + * Worker for processing frames periodically. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pThread The PDM thread structure for the thread this worker runs on. + */ +static DECLCALLBACK(int) vusbRhR3PeriodFrameWorker(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pDrvIns); + int rc = VINF_SUCCESS; + PVUSBROOTHUB pThis = (PVUSBROOTHUB)pThread->pvUser; + + if (pThread->enmState == PDMTHREADSTATE_INITIALIZING) + return VINF_SUCCESS; + + while (pThread->enmState == PDMTHREADSTATE_RUNNING) + { + while ( !ASMAtomicReadU32(&pThis->uFrameRateDefault) + && pThread->enmState == PDMTHREADSTATE_RUNNING) + { + /* Signal the waiter that we are stopped now. */ + rc = RTSemEventMultiSignal(pThis->hSemEventPeriodFrameStopped); + AssertRC(rc); + + rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrame, RT_INDEFINITE_WAIT); + RTSemEventMultiReset(pThis->hSemEventPeriodFrame); + + /* + * Notify the device above about the frame rate changed if we are supposed to + * process frames. + */ + uint32_t uFrameRate = ASMAtomicReadU32(&pThis->uFrameRateDefault); + if (uFrameRate) + vusbRhR3CalcTimerIntervals(pThis, uFrameRate); + } + + AssertLogRelMsgReturn(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc), rc); + if (RT_UNLIKELY(pThread->enmState != PDMTHREADSTATE_RUNNING)) + break; + + uint64_t tsNext = vusbRhR3ProcessFrame(pThis, false /* fCallback */); + + if (tsNext >= 250 * RT_NS_1US) + { + rc = RTSemEventMultiWaitEx(pThis->hSemEventPeriodFrame, RTSEMWAIT_FLAGS_RELATIVE | RTSEMWAIT_FLAGS_NANOSECS | RTSEMWAIT_FLAGS_UNINTERRUPTIBLE, + tsNext); + AssertLogRelMsg(RT_SUCCESS(rc) || rc == VERR_TIMEOUT, ("%Rrc\n", rc)); + RTSemEventMultiReset(pThis->hSemEventPeriodFrame); + } + } + + return VINF_SUCCESS; +} + + +/** + * Unblock the periodic frame thread so it can respond to a state change. + * + * @returns VBox status code. + * @param pDrvIns The driver instance. + * @param pThread The send thread. + */ +static DECLCALLBACK(int) vusbRhR3PeriodFrameWorkerWakeup(PPDMDRVINS pDrvIns, PPDMTHREAD pThread) +{ + RT_NOREF(pThread); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + return RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetUrbParams} */ +static DECLCALLBACK(int) vusbRhSetUrbParams(PVUSBIROOTHUBCONNECTOR pInterface, size_t cbHci, size_t cbHciTd) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + pRh->cbHci = cbHci; + pRh->cbHciTd = cbHciTd; + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnNewUrb} */ +static DECLCALLBACK(PVUSBURB) vusbRhConnNewUrb(PVUSBIROOTHUBCONNECTOR pInterface, uint8_t DstAddress, PVUSBIDEVICE pDev, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, uint32_t cbData, uint32_t cTds, const char *pszTag) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + return vusbRhNewUrb(pRh, DstAddress, (PVUSBDEV)pDev, enmType, enmDir, cbData, cTds, pszTag); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnFreeUrb} */ +static DECLCALLBACK(int) vusbRhConnFreeUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb) +{ + RT_NOREF(pInterface); + pUrb->pVUsb->pfnFree(pUrb); + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSubmitUrb} */ +static DECLCALLBACK(int) vusbRhSubmitUrb(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb, PPDMLED pLed) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + STAM_PROFILE_START(&pRh->StatSubmitUrb, a); + +#ifdef VBOX_WITH_STATISTICS + /* + * Total and per-type submit statistics. + */ + Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsSubmitted); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsSubmitted); + + STAM_COUNTER_ADD(&pRh->Total.StatReqBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqBytes, pUrb->cbData); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatReqReadBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqReadBytes, pUrb->cbData); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatReqWriteBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatReqWriteBytes, pUrb->cbData); + } + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + STAM_COUNTER_ADD(&pRh->StatIsocReqPkts, pUrb->cIsocPkts); + if (pUrb->enmDir == VUSBDIRECTION_IN) + STAM_COUNTER_ADD(&pRh->StatIsocReqReadPkts, pUrb->cIsocPkts); + else + STAM_COUNTER_ADD(&pRh->StatIsocReqWritePkts, pUrb->cIsocPkts); + } +#endif + + /* If there is a sniffer on the roothub record the URB there. */ + if (pRh->hSniffer != VUSBSNIFFER_NIL) + { + int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB submit event on the root hub failed with %Rrc\n", rc)); + } + + /* + * The device was resolved when we allocated the URB. + * Submit it to the device if we found it, if not fail with device-not-ready. + */ + int rc; + if ( pUrb->pVUsb->pDev + && pUrb->pVUsb->pDev->pUsbIns) + { + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + pLed->Asserted.s.fReading = pLed->Actual.s.fReading = 1; + rc = vusbUrbSubmit(pUrb); + pLed->Actual.s.fReading = 0; + break; + case VUSBDIRECTION_OUT: + pLed->Asserted.s.fWriting = pLed->Actual.s.fWriting = 1; + rc = vusbUrbSubmit(pUrb); + pLed->Actual.s.fWriting = 0; + break; + default: + rc = vusbUrbSubmit(pUrb); + break; + } + + if (RT_FAILURE(rc)) + { + LogFlow(("vusbRhSubmitUrb: freeing pUrb=%p\n", pUrb)); + pUrb->pVUsb->pfnFree(pUrb); + } + } + else + { + vusbDevRetain(&pRh->Hub.Dev); + pUrb->pVUsb->pDev = &pRh->Hub.Dev; + Log(("vusb: pRh=%p: SUBMIT: Address %i not found!!!\n", pRh, pUrb->DstAddress)); + + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + rc = VINF_SUCCESS; + } + + STAM_PROFILE_STOP(&pRh->StatSubmitUrb, a); + return rc; +} + + +static DECLCALLBACK(int) vusbRhReapAsyncUrbsWorker(PVUSBDEV pDev, RTMSINTERVAL cMillies) +{ + if (!cMillies) + vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, 0); + else + { + uint64_t u64Start = RTTimeMilliTS(); + do + { + vusbUrbDoReapAsync(&pDev->LstAsyncUrbs, RT_MIN(cMillies >> 8, 10)); + } while ( !RTListIsEmpty(&pDev->LstAsyncUrbs) + && RTTimeMilliTS() - u64Start < cMillies); + } + + return VINF_SUCCESS; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnReapAsyncUrbs} */ +static DECLCALLBACK(void) vusbRhReapAsyncUrbs(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, RTMSINTERVAL cMillies) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); NOREF(pRh); + PVUSBDEV pDev = (PVUSBDEV)pDevice; + + if (RTListIsEmpty(&pDev->LstAsyncUrbs)) + return; + + STAM_PROFILE_START(&pRh->StatReapAsyncUrbs, a); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhReapAsyncUrbsWorker, 2, pDev, cMillies); + AssertRC(rc); + STAM_PROFILE_STOP(&pRh->StatReapAsyncUrbs, a); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelUrbsEp} */ +static DECLCALLBACK(int) vusbRhCancelUrbsEp(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBURB pUrb) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + AssertReturn(pRh, VERR_INVALID_PARAMETER); + AssertReturn(pUrb, VERR_INVALID_PARAMETER); + + /// @todo This method of URB canceling may not work on non-Linux hosts. + /* + * Cancel and reap the URB(s) on an endpoint. + */ + LogFlow(("vusbRhCancelUrbsEp: pRh=%p pUrb=%p\n", pRh, pUrb)); + + vusbUrbCancelAsync(pUrb, CANCELMODE_UNDO); + + /* The reaper thread will take care of completing the URB. */ + + return VINF_SUCCESS; +} + +/** + * Worker doing the actual cancelling of all outstanding URBs on the device I/O thread. + * + * @returns VBox status code. + * @param pDev USB device instance data. + */ +static DECLCALLBACK(int) vusbRhCancelAllUrbsWorker(PVUSBDEV pDev) +{ + /* + * Cancel the URBS. + * + * Not using th CritAsyncUrbs critical section here is safe + * as the I/O thread is the only thread accessing this struture at the + * moment. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + /* Call the worker directly. */ + vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL); + } + + return VINF_SUCCESS; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnCancelAllUrbs} */ +static DECLCALLBACK(void) vusbRhCancelAllUrbs(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + RTCritSectEnter(&pRh->CritSectDevices); + PVUSBDEV pDev = pRh->pDevices; + while (pDev) + { + vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhCancelAllUrbsWorker, 1, pDev); + pDev = pDev->pNext; + } + RTCritSectLeave(&pRh->CritSectDevices); +} + +/** + * Worker doing the actual cancelling of all outstanding per-EP URBs on the + * device I/O thread. + * + * @returns VBox status code. + * @param pDev USB device instance data. + * @param EndPt Endpoint number. + * @param enmDir Endpoint direction. + */ +static DECLCALLBACK(int) vusbRhAbortEpWorker(PVUSBDEV pDev, int EndPt, VUSBDIRECTION enmDir) +{ + /* + * Iterate the URBs, find ones corresponding to given EP, and cancel them. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + + Assert(pUrb->pVUsb->pDev == pDev); + + /* For the default control EP, direction does not matter. */ + if (pUrb->EndPt == EndPt && (pUrb->enmDir == enmDir || !EndPt)) + { + LogFlow(("%s: vusbRhAbortEpWorker: CANCELING URB\n", pUrb->pszDesc)); + int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_UNDO); + AssertRC(rc); + } + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAbortEp} */ +static DECLCALLBACK(int) vusbRhAbortEp(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, int EndPt, VUSBDIRECTION enmDir) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub) + AssertFailedReturn(VERR_INVALID_PARAMETER); + + RTCritSectEnter(&pRh->CritSectDevices); + PVUSBDEV pDev = (PVUSBDEV)pDevice; + vusbDevIoThreadExecSync(pDev, (PFNRT)vusbRhAbortEpWorker, 3, pDev, EndPt, enmDir); + RTCritSectLeave(&pRh->CritSectDevices); + + /* The reaper thread will take care of completing the URB. */ + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnAttachDevice} */ +static DECLCALLBACK(int) vusbRhAttachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + return vusbHubAttach(&pRh->Hub, (PVUSBDEV)pDevice); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnDetachDevice} */ +static DECLCALLBACK(int) vusbRhDetachDevice(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + if (&pRh->Hub != ((PVUSBDEV)pDevice)->pHub) + AssertFailedReturn(VERR_INVALID_PARAMETER); + return vusbDevDetach((PVUSBDEV)pDevice); +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnSetPeriodicFrameProcessing} */ +static DECLCALLBACK(int) vusbRhSetFrameProcessing(PVUSBIROOTHUBCONNECTOR pInterface, uint32_t uFrameRate) +{ + int rc = VINF_SUCCESS; + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + /* Create the frame thread lazily. */ + if ( !pThis->hThreadPeriodFrame + && uFrameRate) + { + ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + pThis->uFrameRate = uFrameRate; + vusbRhR3CalcTimerIntervals(pThis, uFrameRate); + + rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrame); + AssertRCReturn(rc, rc); + + rc = RTSemEventMultiCreate(&pThis->hSemEventPeriodFrameStopped); + AssertRCReturn(rc, rc); + + rc = PDMDrvHlpThreadCreate(pThis->pDrvIns, &pThis->hThreadPeriodFrame, pThis, vusbRhR3PeriodFrameWorker, + vusbRhR3PeriodFrameWorkerWakeup, 0, RTTHREADTYPE_IO, "VUsbPeriodFrm"); + AssertRCReturn(rc, rc); + + VMSTATE enmState = PDMDrvHlpVMState(pThis->pDrvIns); + if ( enmState == VMSTATE_RUNNING + || enmState == VMSTATE_RUNNING_LS + || enmState == VMSTATE_RUNNING_FT) + { + rc = PDMR3ThreadResume(pThis->hThreadPeriodFrame); + AssertRCReturn(rc, rc); + } + } + else if ( pThis->hThreadPeriodFrame + && !uFrameRate) + { + /* Stop processing. */ + uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + if (uFrameRateOld) + { + rc = RTSemEventMultiReset(pThis->hSemEventPeriodFrameStopped); + AssertRC(rc); + + /* Signal the frame thread to stop. */ + RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); + + /* Wait for signal from the thread that it stopped. */ + rc = RTSemEventMultiWait(pThis->hSemEventPeriodFrameStopped, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + } + else if ( pThis->hThreadPeriodFrame + && uFrameRate) + { + /* Just switch to the new frame rate and let the periodic frame thread pick it up. */ + uint32_t uFrameRateOld = ASMAtomicXchgU32(&pThis->uFrameRateDefault, uFrameRate); + + /* Signal the frame thread to continue if it was stopped. */ + if (!uFrameRateOld) + RTSemEventMultiSignal(pThis->hSemEventPeriodFrame); + } + + return rc; +} + + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnGetPeriodicFrameRate} */ +static DECLCALLBACK(uint32_t) vusbRhGetPeriodicFrameRate(PVUSBIROOTHUBCONNECTOR pInterface) +{ + PVUSBROOTHUB pThis = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + + return pThis->uFrameRate; +} + +/** @interface_method_impl{VUSBIROOTHUBCONNECTOR,pfnUpdateIsocFrameDelta} */ +static DECLCALLBACK(uint32_t) vusbRhUpdateIsocFrameDelta(PVUSBIROOTHUBCONNECTOR pInterface, PVUSBIDEVICE pDevice, + int EndPt, VUSBDIRECTION enmDir, uint16_t uNewFrameID, uint8_t uBits) +{ + PVUSBROOTHUB pRh = VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface); + AssertReturn(pRh, 0); + PVUSBDEV pDev = (PVUSBDEV)pDevice; + PVUSBPIPE pPipe = &pDev->aPipes[EndPt]; + uint32_t *puLastFrame; + int32_t uFrameDelta; + uint32_t uMaxVal = 1 << uBits; + + puLastFrame = enmDir == VUSBDIRECTION_IN ? &pPipe->uLastFrameIn : &pPipe->uLastFrameOut; + uFrameDelta = uNewFrameID - *puLastFrame; + *puLastFrame = uNewFrameID; + /* Take care of wrap-around. */ + if (uFrameDelta < 0) + uFrameDelta += uMaxVal; + + return (uint16_t)uFrameDelta; +} + +/* -=-=-=-=-=- VUSB Device methods (for the root hub) -=-=-=-=-=- */ + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnReset} + */ +static DECLCALLBACK(int) vusbRhDevReset(PVUSBIDEVICE pInterface, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + RT_NOREF(pfnDone, pvUser, pVM); + PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); + Assert(!pfnDone); + return pRh->pIRhPort->pfnReset(pRh->pIRhPort, fResetOnLinux); /** @todo change rc from bool to vbox status everywhere! */ +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnPowerOn} + */ +static DECLCALLBACK(int) vusbRhDevPowerOn(PVUSBIDEVICE pInterface) +{ + PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); + LogFlow(("vusbRhDevPowerOn: pRh=%p\n", pRh)); + + Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED + && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET); + + if (pRh->Hub.Dev.enmState == VUSB_DEVICE_STATE_ATTACHED) + pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_POWERED; + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnPowerOff} + */ +static DECLCALLBACK(int) vusbRhDevPowerOff(PVUSBIDEVICE pInterface) +{ + PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); + LogFlow(("vusbRhDevPowerOff: pRh=%p\n", pRh)); + + Assert( pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_DETACHED + && pRh->Hub.Dev.enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Cancel all URBs and reap them. + */ + VUSBIRhCancelAllUrbs(&pRh->IRhConnector); + RTCritSectEnter(&pRh->CritSectDevices); + PVUSBDEV pDev = pRh->pDevices; + while (pDev) + { + VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, (PVUSBIDEVICE)pDev, 0); + pDev = pDev->pNext; + } + RTCritSectLeave(&pRh->CritSectDevices); + + pRh->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED; + return VINF_SUCCESS; +} + +/** + * @interface_method_impl{VUSBIDEVICE,pfnGetState} + */ +static DECLCALLBACK(VUSBDEVICESTATE) vusbRhDevGetState(PVUSBIDEVICE pInterface) +{ + PVUSBROOTHUB pRh = RT_FROM_MEMBER(pInterface, VUSBROOTHUB, Hub.Dev.IDevice); + return pRh->Hub.Dev.enmState; +} + + +static const char *vusbGetSpeedString(VUSBSPEED enmSpeed) +{ + const char *pszSpeed = NULL; + + switch (enmSpeed) + { + case VUSB_SPEED_LOW: + pszSpeed = "Low"; + break; + case VUSB_SPEED_FULL: + pszSpeed = "Full"; + break; + case VUSB_SPEED_HIGH: + pszSpeed = "High"; + break; + case VUSB_SPEED_VARIABLE: + pszSpeed = "Variable"; + break; + case VUSB_SPEED_SUPER: + pszSpeed = "Super"; + break; + case VUSB_SPEED_SUPERPLUS: + pszSpeed = "SuperPlus"; + break; + default: + pszSpeed = "Unknown"; + break; + } + return pszSpeed; +} + +/* -=-=-=-=-=- VUSB Hub methods -=-=-=-=-=- */ + + +/** + * Attach the device to the hub. + * Port assignments and all such stuff is up to this routine. + * + * @returns VBox status code. + * @param pHub Pointer to the hub. + * @param pDev Pointer to the device. + */ +static int vusbRhHubOpAttach(PVUSBHUB pHub, PVUSBDEV pDev) +{ + PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub; + + /* + * Assign a port. + */ + int iPort = ASMBitFirstSet(&pRh->Bitmap, sizeof(pRh->Bitmap) * 8); + if (iPort < 0) + { + LogRel(("VUSB: No ports available!\n")); + return VERR_VUSB_NO_PORTS; + } + ASMBitClear(&pRh->Bitmap, iPort); + pHub->cDevices++; + pDev->i16Port = iPort; + + /* + * Call the HCI attach routine and let it have its say before the device is + * linked into the device list of this hub. + */ + int rc = pRh->pIRhPort->pfnAttach(pRh->pIRhPort, &pDev->IDevice, iPort); + if (RT_SUCCESS(rc)) + { + RTCritSectEnter(&pRh->CritSectDevices); + pDev->pNext = pRh->pDevices; + pRh->pDevices = pDev; + RTCritSectLeave(&pRh->CritSectDevices); + LogRel(("VUSB: Attached '%s' to port %d on %s (%sSpeed)\n", pDev->pUsbIns->pszName, + iPort, pHub->pszName, vusbGetSpeedString(pDev->pUsbIns->enmSpeed))); + } + else + { + ASMBitSet(&pRh->Bitmap, iPort); + pHub->cDevices--; + pDev->i16Port = -1; + LogRel(("VUSB: Failed to attach '%s' to port %d, rc=%Rrc\n", pDev->pUsbIns->pszName, iPort, rc)); + } + return rc; +} + + +/** + * Detach the device from the hub. + * + * @returns VBox status code. + * @param pHub Pointer to the hub. + * @param pDev Pointer to the device. + */ +static void vusbRhHubOpDetach(PVUSBHUB pHub, PVUSBDEV pDev) +{ + PVUSBROOTHUB pRh = (PVUSBROOTHUB)pHub; + Assert(pDev->i16Port != -1); + + /* + * Check that it's attached and unlink it from the linked list. + */ + RTCritSectEnter(&pRh->CritSectDevices); + if (pRh->pDevices != pDev) + { + PVUSBDEV pPrev = pRh->pDevices; + while (pPrev && pPrev->pNext != pDev) + pPrev = pPrev->pNext; + Assert(pPrev); + pPrev->pNext = pDev->pNext; + } + else + pRh->pDevices = pDev->pNext; + pDev->pNext = NULL; + RTCritSectLeave(&pRh->CritSectDevices); + + /* + * Detach the device and mark the port as available. + */ + unsigned uPort = pDev->i16Port; + pRh->pIRhPort->pfnDetach(pRh->pIRhPort, &pDev->IDevice, uPort); + LogRel(("VUSB: Detached '%s' from port %u on %s\n", pDev->pUsbIns->pszName, uPort, pHub->pszName)); + ASMBitSet(&pRh->Bitmap, uPort); + pHub->cDevices--; +} + + +/** + * The Hub methods implemented by the root hub. + */ +static const VUSBHUBOPS s_VUsbRhHubOps = +{ + vusbRhHubOpAttach, + vusbRhHubOpDetach +}; + + + +/* -=-=-=-=-=- PDM Base interface methods -=-=-=-=-=- */ + + +/** + * @interface_method_impl{PDMIBASE,pfnQueryInterface} + */ +static DECLCALLBACK(void *) vusbRhQueryInterface(PPDMIBASE pInterface, const char *pszIID) +{ + PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface); + PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + + PDMIBASE_RETURN_INTERFACE(pszIID, PDMIBASE, &pDrvIns->IBase); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIROOTHUBCONNECTOR, &pRh->IRhConnector); + PDMIBASE_RETURN_INTERFACE(pszIID, VUSBIDEVICE, &pRh->Hub.Dev.IDevice); + return NULL; +} + + +/* -=-=-=-=-=- PDM Driver methods -=-=-=-=-=- */ + + +/** + * Destruct a driver instance. + * + * Most VM resources are freed by the VM. This callback is provided so that any non-VM + * resources can be freed correctly. + * + * @param pDrvIns The driver instance data. + */ +static DECLCALLBACK(void) vusbRhDestruct(PPDMDRVINS pDrvIns) +{ + PVUSBROOTHUB pRh = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + PDMDRV_CHECK_VERSIONS_RETURN_VOID(pDrvIns); + + vusbUrbPoolDestroy(&pRh->Hub.Dev.UrbPool); + if (pRh->Hub.pszName) + { + RTStrFree(pRh->Hub.pszName); + pRh->Hub.pszName = NULL; + } + if (pRh->hSniffer != VUSBSNIFFER_NIL) + VUSBSnifferDestroy(pRh->hSniffer); + + if (pRh->hSemEventPeriodFrame) + RTSemEventMultiDestroy(pRh->hSemEventPeriodFrame); + + if (pRh->hSemEventPeriodFrameStopped) + RTSemEventMultiDestroy(pRh->hSemEventPeriodFrameStopped); + + RTCritSectDelete(&pRh->CritSectDevices); +} + + +/** + * Construct a root hub driver instance. + * + * @copydoc FNPDMDRVCONSTRUCT + */ +static DECLCALLBACK(int) vusbRhConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg, uint32_t fFlags) +{ + RT_NOREF(fFlags); + PDMDRV_CHECK_VERSIONS_RETURN(pDrvIns); + LogFlow(("vusbRhConstruct: Instance %d\n", pDrvIns->iInstance)); + PVUSBROOTHUB pThis = PDMINS_2_DATA(pDrvIns, PVUSBROOTHUB); + + /* + * Validate configuration. + */ + if (!CFGMR3AreValuesValid(pCfg, "CaptureFilename\0")) + return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES; + + /* + * Check that there are no drivers below us. + */ + AssertMsgReturn(PDMDrvHlpNoAttach(pDrvIns) == VERR_PDM_NO_ATTACHED_DRIVER, + ("Configuration error: Not possible to attach anything to this driver!\n"), + VERR_PDM_DRVINS_NO_ATTACH); + + /* + * Initialize the critical sections. + */ + int rc = RTCritSectInit(&pThis->CritSectDevices); + if (RT_FAILURE(rc)) + return rc; + + char *pszCaptureFilename = NULL; + rc = CFGMR3QueryStringAlloc(pCfg, "CaptureFilename", &pszCaptureFilename); + if ( RT_FAILURE(rc) + && rc != VERR_CFGM_VALUE_NOT_FOUND) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("Configuration error: Failed to query value of \"CaptureFilename\"")); + + /* + * Initialize the data members. + */ + pDrvIns->IBase.pfnQueryInterface = vusbRhQueryInterface; + /* the usb device */ + pThis->Hub.Dev.enmState = VUSB_DEVICE_STATE_ATTACHED; + pThis->Hub.Dev.u8Address = VUSB_INVALID_ADDRESS; + pThis->Hub.Dev.u8NewAddress = VUSB_INVALID_ADDRESS; + pThis->Hub.Dev.i16Port = -1; + pThis->Hub.Dev.cRefs = 1; + pThis->Hub.Dev.IDevice.pfnReset = vusbRhDevReset; + pThis->Hub.Dev.IDevice.pfnPowerOn = vusbRhDevPowerOn; + pThis->Hub.Dev.IDevice.pfnPowerOff = vusbRhDevPowerOff; + pThis->Hub.Dev.IDevice.pfnGetState = vusbRhDevGetState; + /* the hub */ + pThis->Hub.pOps = &s_VUsbRhHubOps; + pThis->Hub.pRootHub = pThis; + //pThis->hub.cPorts - later + pThis->Hub.cDevices = 0; + pThis->Hub.Dev.pHub = &pThis->Hub; + RTStrAPrintf(&pThis->Hub.pszName, "RootHub#%d", pDrvIns->iInstance); + /* misc */ + pThis->pDrvIns = pDrvIns; + /* the connector */ + pThis->IRhConnector.pfnSetUrbParams = vusbRhSetUrbParams; + pThis->IRhConnector.pfnNewUrb = vusbRhConnNewUrb; + pThis->IRhConnector.pfnFreeUrb = vusbRhConnFreeUrb; + pThis->IRhConnector.pfnSubmitUrb = vusbRhSubmitUrb; + pThis->IRhConnector.pfnReapAsyncUrbs = vusbRhReapAsyncUrbs; + pThis->IRhConnector.pfnCancelUrbsEp = vusbRhCancelUrbsEp; + pThis->IRhConnector.pfnCancelAllUrbs = vusbRhCancelAllUrbs; + pThis->IRhConnector.pfnAbortEp = vusbRhAbortEp; + pThis->IRhConnector.pfnAttachDevice = vusbRhAttachDevice; + pThis->IRhConnector.pfnDetachDevice = vusbRhDetachDevice; + pThis->IRhConnector.pfnSetPeriodicFrameProcessing = vusbRhSetFrameProcessing; + pThis->IRhConnector.pfnGetPeriodicFrameRate = vusbRhGetPeriodicFrameRate; + pThis->IRhConnector.pfnUpdateIsocFrameDelta = vusbRhUpdateIsocFrameDelta; + pThis->hSniffer = VUSBSNIFFER_NIL; + pThis->cbHci = 0; + pThis->cbHciTd = 0; + pThis->fFrameProcessing = false; +#ifdef LOG_ENABLED + pThis->iSerial = 0; +#endif + /* + * Resolve interface(s). + */ + pThis->pIRhPort = PDMIBASE_QUERY_INTERFACE(pDrvIns->pUpBase, VUSBIROOTHUBPORT); + AssertMsgReturn(pThis->pIRhPort, ("Configuration error: the device/driver above us doesn't expose any VUSBIROOTHUBPORT interface!\n"), VERR_PDM_MISSING_INTERFACE_ABOVE); + + /* + * Get number of ports and the availability bitmap. + * ASSUME that the number of ports reported now at creation time is the max number. + */ + pThis->Hub.cPorts = pThis->pIRhPort->pfnGetAvailablePorts(pThis->pIRhPort, &pThis->Bitmap); + Log(("vusbRhConstruct: cPorts=%d\n", pThis->Hub.cPorts)); + + /* + * Get the USB version of the attached HC. + * ASSUME that version 2.0 implies high-speed. + */ + pThis->fHcVersions = pThis->pIRhPort->pfnGetUSBVersions(pThis->pIRhPort); + Log(("vusbRhConstruct: fHcVersions=%u\n", pThis->fHcVersions)); + + rc = vusbUrbPoolInit(&pThis->Hub.Dev.UrbPool); + if (RT_FAILURE(rc)) + return rc; + + if (pszCaptureFilename) + { + rc = VUSBSnifferCreate(&pThis->hSniffer, 0, pszCaptureFilename, NULL, NULL); + if (RT_FAILURE(rc)) + return PDMDrvHlpVMSetError(pDrvIns, rc, RT_SRC_POS, + N_("VUSBSniffer cannot open '%s' for writing. The directory must exist and it must be writable for the current user"), + pszCaptureFilename); + + MMR3HeapFree(pszCaptureFilename); + } + + /* + * Register ourselves as a USB hub. + * The current implementation uses the VUSBIRHCONFIG interface for communication. + */ + PCPDMUSBHUBHLP pHlp; /* not used currently */ + rc = PDMDrvHlpUSBRegisterHub(pDrvIns, pThis->fHcVersions, pThis->Hub.cPorts, &g_vusbHubReg, &pHlp); + if (RT_FAILURE(rc)) + return rc; + + /* + * Statistics. (It requires a 30" monitor or extremely tiny fonts to edit this "table".) + */ +#ifdef VBOX_WITH_STATISTICS + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs submitted.", "/VUSB/%d/UrbsSubmitted", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsSubmitted/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsSubmitted/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsSubmitted/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsSubmitted/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs cancelled. (included in failed)", "/VUSB/%d/UrbsCancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsCancelled/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsCancelled/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsCancelled/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsCancelled/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs failing.", "/VUSB/%d/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Bulk transfer.", "/VUSB/%d/UrbsFailed/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Control transfer.", "/VUSB/%d/UrbsFailed/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Interrupt transfer.", "/VUSB/%d/UrbsFailed/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Isochronous transfer.", "/VUSB/%d/UrbsFailed/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested transfer.", "/VUSB/%d/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested read transfer.", "/VUSB/%d/ReqReadBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqReadBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqReadBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqReadBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqReadBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Total requested write transfer.", "/VUSB/%d/ReqWriteBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ReqWriteBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ReqWriteBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ReqWriteBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ReqWriteBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total transfer.", "/VUSB/%d/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total read transfer.", "/VUSB/%d/ActReadBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActReadBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActReadBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActReadBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActReadBytes/Isoc", pDrvIns->iInstance); + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->Total.StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Actual total write transfer.", "/VUSB/%d/ActWriteBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Bulk transfer.", "/VUSB/%d/ActWriteBytes/Bulk", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Control transfer.", "/VUSB/%d/ActWriteBytes/Ctrl", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Interrupt transfer.", "/VUSB/%d/ActWriteBytes/Intr", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Isochronous transfer.", "/VUSB/%d/ActWriteBytes/Isoc", pDrvIns->iInstance); + + /* bulk */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Bulk/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Bulk/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Bulk/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Bulk/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Bulk/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Bulk/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_BULK].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Bulk/ReqBytes/Write", pDrvIns->iInstance); + + /* control */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Ctrl/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Ctrl/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Ctrl/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Ctrl/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Ctrl/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Ctrl/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_CTRL].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Ctrl/ReqBytes/Write", pDrvIns->iInstance); + + /* interrupt */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Intr/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Intr/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Intr/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Intr/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Intr/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Intr/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_INTR].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Intr/ReqBytes/Write", pDrvIns->iInstance); + + /* isochronous */ + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsSubmitted, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of submitted URBs.", "/VUSB/%d/Isoc/Urbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsFailed, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of failed URBs.", "/VUSB/%d/Isoc/UrbsFailed", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatUrbsCancelled, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of cancelled URBs.", "/VUSB/%d/Isoc/UrbsFailed/Cancelled", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Number of bytes transferred.", "/VUSB/%d/Isoc/ActBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ActBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ActBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Requested number of bytes.", "/VUSB/%d/Isoc/ReqBytes", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqReadBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Read.", "/VUSB/%d/Isoc/ReqBytes/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aTypes[VUSBXFERTYPE_ISOC].StatReqWriteBytes, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_BYTES, "Write.", "/VUSB/%d/Isoc/ReqBytes/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Number of isochronous packets returning data.", "/VUSB/%d/Isoc/ActPkts", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ActPkts/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocActWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ActPkts/Write", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Requested number of isochronous packets.", "/VUSB/%d/Isoc/ReqPkts", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqReadPkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Read.", "/VUSB/%d/Isoc/ReqPkts/Read", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatIsocReqWritePkts, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "Write.", "/VUSB/%d/Isoc/ReqPkts/Write", pDrvIns->iInstance); + + for (unsigned i = 0; i < RT_ELEMENTS(pThis->aStatIsocDetails); i++) + { + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Pkts, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Ok0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Ok0", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataUnderrun0, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataUnderrun0", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].DataOverrun, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/DataOverrun", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].NotAccessed, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/NotAccessed", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Misc, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_COUNT, ".", "/VUSB/%d/Isoc/%d/Misc", pDrvIns->iInstance, i); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->aStatIsocDetails[i].Bytes, STAMTYPE_COUNTER, STAMVISIBILITY_USED, STAMUNIT_BYTES, ".", "/VUSB/%d/Isoc/%d/Bytes", pDrvIns->iInstance, i); + } + + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatReapAsyncUrbs, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Profiling the vusbRhReapAsyncUrbs body (omitting calls when nothing is in-flight).", "/VUSB/%d/ReapAsyncUrbs", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatSubmitUrb, STAMTYPE_PROFILE, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Profiling the vusbRhSubmitUrb body.", "/VUSB/%d/SubmitUrb", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedThread, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the dedicated thread", "/VUSB/%d/FramesProcessedThread", pDrvIns->iInstance); + PDMDrvHlpSTAMRegisterF(pDrvIns, &pThis->StatFramesProcessedClbk, STAMTYPE_COUNTER, STAMVISIBILITY_ALWAYS, STAMUNIT_OCCURENCES, "Processed frames in the URB completion callback", "/VUSB/%d/FramesProcessedClbk", pDrvIns->iInstance); +#endif + PDMDrvHlpSTAMRegisterF(pDrvIns, (void *)&pThis->Hub.Dev.UrbPool.cUrbsInPool, STAMTYPE_U32, STAMVISIBILITY_ALWAYS, STAMUNIT_COUNT, "The number of URBs in the pool.", "/VUSB/%d/cUrbsInPool", pDrvIns->iInstance); + + return VINF_SUCCESS; +} + + +/** + * VUSB Root Hub driver registration record. + */ +const PDMDRVREG g_DrvVUSBRootHub = +{ + /* u32Version */ + PDM_DRVREG_VERSION, + /* szName */ + "VUSBRootHub", + /* szRCMod */ + "", + /* szR0Mod */ + "", + /* pszDescription */ + "VUSB Root Hub Driver.", + /* fFlags */ + PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT, + /* fClass. */ + PDM_DRVREG_CLASS_USB, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(VUSBROOTHUB), + /* pfnConstruct */ + vusbRhConstruct, + /* pfnDestruct */ + vusbRhDestruct, + /* pfnRelocate */ + NULL, + /* pfnIOCtl */ + NULL, + /* pfnPowerOn */ + NULL, + /* pfnReset */ + NULL, + /* pfnSuspend */ + NULL, + /* pfnResume */ + NULL, + /* pfnAttach */ + NULL, + /* pfnDetach */ + NULL, + /* pfnPowerOff */ + NULL, + /* pfnSoftReset */ + NULL, + /* u32EndVersion */ + PDM_DRVREG_VERSION +}; + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/Makefile.kup b/src/VBox/Devices/USB/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/Makefile.kup diff --git a/src/VBox/Devices/USB/USBProxyDevice-stub.cpp b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp new file mode 100644 index 00000000..2744d6b3 --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice-stub.cpp @@ -0,0 +1,51 @@ +/* $Id: USBProxyDevice-stub.cpp $ */ +/** @file + * USB device proxy - Stub. + */ + +/* + * Copyright (C) 2008-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/vmm/pdm.h> + +#include "USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Stub USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + "host", + 0, /* cbBackend */ + NULL, /* Open */ + NULL, /* Init */ + NULL, /* Close */ + NULL, /* Reset */ + NULL, /* SetConfig */ + NULL, /* ClaimInterface */ + NULL, /* ReleaseInterface */ + NULL, /* SetInterface */ + NULL, /* ClearHaltedEp */ + NULL, /* UrbQueue */ + NULL, /* UrbCancel */ + NULL, /* UrbReap */ + 0 +}; + diff --git a/src/VBox/Devices/USB/USBProxyDevice.cpp b/src/VBox/Devices/USB/USBProxyDevice.cpp new file mode 100644 index 00000000..0a38363f --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice.cpp @@ -0,0 +1,1284 @@ +/* $Id: USBProxyDevice.cpp $ */ +/** @file + * USBProxy - USB device proxy. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <VBox/usb.h> +#include <VBox/usbfilter.h> +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include "USBProxyDevice.h" +#include "VUSBInternal.h" +#include "VBoxDD.h" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** A dummy name used early during the construction phase to avoid log crashes. */ +static char g_szDummyName[] = "proxy xxxx:yyyy"; + +/** + * Array of supported proxy backends. + */ +static PCUSBPROXYBACK g_aUsbProxies[] = +{ + &g_USBProxyDeviceHost, + &g_USBProxyDeviceVRDP, + &g_USBProxyDeviceUsbIp +}; + +/* Synchronously obtain a standard USB descriptor for a device, used in order + * to grab configuration descriptors when we first add the device + */ +static void *GetStdDescSync(PUSBPROXYDEV pProxyDev, uint8_t iDescType, uint8_t iIdx, uint16_t LangId, uint16_t cbHint) +{ +#define GET_DESC_RETRIES 6 + int cRetries = 0; + uint16_t cbInitialHint = cbHint; + + LogFlow(("GetStdDescSync: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + for (;;) + { + /* + * Setup a MSG URB, queue and reap it. + */ + int rc = VINF_SUCCESS; + VUSBURB Urb; + AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K); + Urb.u32Magic = VUSBURB_MAGIC; + Urb.enmState = VUSBURBSTATE_IN_FLIGHT; + Urb.pszDesc = (char*)"URB sync"; + Urb.pHci = NULL; + Urb.paTds = NULL; + Urb.Dev.pvPrivate = NULL; + Urb.Dev.pNext = NULL; + Urb.DstAddress = 0; + Urb.EndPt = 0; + Urb.enmType = VUSBXFERTYPE_MSG; + Urb.enmDir = VUSBDIRECTION_IN; + Urb.fShortNotOk = false; + Urb.enmStatus = VUSBSTATUS_INVALID; + Urb.pVUsb = NULL; + cbHint = RT_MIN(cbHint, sizeof(Urb.abData) - sizeof(VUSBSETUP)); + Urb.cbData = cbHint + sizeof(VUSBSETUP); + + PVUSBSETUP pSetup = (PVUSBSETUP)Urb.abData; + pSetup->bmRequestType = VUSB_DIR_TO_HOST | VUSB_REQ_STANDARD | VUSB_TO_DEVICE; + pSetup->bRequest = VUSB_REQ_GET_DESCRIPTOR; + pSetup->wValue = (iDescType << 8) | iIdx; + pSetup->wIndex = LangId; + pSetup->wLength = cbHint; + + uint8_t *pbDesc = (uint8_t *)(pSetup + 1); + uint32_t cbDesc = 0; + PVUSBURB pUrbReaped = NULL; + + rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, &Urb); + if (RT_FAILURE(rc)) + { + Log(("GetStdDescSync: pfnUrbReap failed, rc=%d\n", rc)); + goto err; + } + + /* Don't wait forever, it's just a simple request that should + return immediately. Since we're executing in the EMT thread + it's important not to get stuck here. (Some of the builtin + iMac devices may refuse to respond for instance.) */ + pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 5000 /* ms */); + if (!pUrbReaped) + { + rc = pProxyDev->pOps->pfnUrbCancel(pProxyDev, &Urb); + AssertRC(rc); + /** @todo This breaks the comment above... */ + pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT); + } + if (pUrbReaped != &Urb) + { + Log(("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped)); + goto err; + } + + if (Urb.enmStatus != VUSBSTATUS_OK) + { + Log(("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus)); + goto err; + } + + /* + * Check the length, config descriptors have total_length field + */ + if (iDescType == VUSB_DT_CONFIG) + { + if (Urb.cbData < sizeof(VUSBSETUP) + 4) + { + Log(("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData)); + goto err; + } + cbDesc = RT_LE2H_U16(((uint16_t *)pbDesc)[1]); + } + else + { + if (Urb.cbData < sizeof(VUSBSETUP) + 1) + { + Log(("GetStdDescSync: Urb.cbData=%#x (min 1)\n", Urb.cbData)); + goto err; + } + cbDesc = ((uint8_t *)pbDesc)[0]; + } + + Log(("GetStdDescSync: got Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint)); + + if ( Urb.cbData == cbHint + sizeof(VUSBSETUP) + && cbDesc > Urb.cbData - sizeof(VUSBSETUP)) + { + cbHint = cbDesc; + Log(("GetStdDescSync: Part descriptor, Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint)); + + if (cbHint > sizeof(Urb.abData)) + Log(("GetStdDescSync: cbHint=%u, Urb.abData=%u\n", cbHint, sizeof(Urb.abData))); + + goto err; + } + + if (cbDesc > Urb.cbData - sizeof(VUSBSETUP)) + { + Log(("GetStdDescSync: Descriptor length too short, cbDesc=%u, Urb.cbData=%u\n", cbDesc, Urb.cbData)); + goto err; + } + + if ( cbInitialHint != cbHint + && ( cbDesc != cbHint + || Urb.cbData < cbInitialHint) ) + { + Log(("GetStdDescSync: Descriptor length incorrect, cbDesc=%u, Urb.cbData=%u, cbHint=%u\n", cbDesc, Urb.cbData, cbHint)); + goto err; + } + +#ifdef LOG_ENABLED + vusbUrbTrace(&Urb, "GetStdDescSync", true); +#endif + + /* + * Fine, we got everything return a heap duplicate of the descriptor. + */ + return RTMemDup(pbDesc, cbDesc); + +err: + cRetries++; + if (cRetries < GET_DESC_RETRIES) + { + Log(("GetStdDescSync: Retrying %u/%u\n", cRetries, GET_DESC_RETRIES)); + RTThreadSleep(100); + continue; + } + else + { + Log(("GetStdDescSync: Retries exceeded %u/%u. Giving up.\n", cRetries, GET_DESC_RETRIES)); + break; + } + } + + return NULL; +} + +/** + * Frees a descriptor returned by GetStdDescSync(). + */ +static void free_desc(void *pvDesc) +{ + RTMemFree(pvDesc); +} + +/** + * Get and a device descriptor and byteswap it appropriately. + */ +static bool usbProxyGetDeviceDesc(PUSBPROXYDEV pProxyDev, PVUSBDESCDEVICE pOut) +{ + /* + * Get the descriptor from the device. + */ + PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN); + if (!pIn) + { + Log(("usbProxyGetDeviceDesc: pProxyDev=%s: GetStdDescSync failed\n", pProxyDev->pUsbIns->pszName)); + return false; + } + if (pIn->bLength < VUSB_DT_DEVICE_MIN_LEN) + { + Log(("usb-proxy: pProxyDev=%s: Corrupted device descriptor. bLength=%d\n", pProxyDev->pUsbIns->pszName, pIn->bLength)); + return false; + } + + /* + * Convert it. + */ + pOut->bLength = VUSB_DT_DEVICE_MIN_LEN; + pOut->bDescriptorType = VUSB_DT_DEVICE; + pOut->bcdUSB = RT_LE2H_U16(pIn->bcdUSB); + pOut->bDeviceClass = pIn->bDeviceClass; + pOut->bDeviceSubClass = pIn->bDeviceSubClass; + pOut->bDeviceProtocol = pIn->bDeviceProtocol; + pOut->bMaxPacketSize0 = pIn->bMaxPacketSize0; + pOut->idVendor = RT_LE2H_U16(pIn->idVendor); + pOut->idProduct = RT_LE2H_U16(pIn->idProduct); + pOut->bcdDevice = RT_LE2H_U16(pIn->bcdDevice); + pOut->iManufacturer = pIn->iManufacturer; + pOut->iProduct = pIn->iProduct; + pOut->iSerialNumber = pIn->iSerialNumber; + pOut->bNumConfigurations = pIn->bNumConfigurations; + + free_desc(pIn); + return true; +} + +/** + * Count the numbers and types of each kind of descriptor that we need to + * copy out of the config descriptor + */ +struct desc_counts +{ + size_t num_ed, num_id, num_if; + /** bitmap (128 bits) */ + uint32_t idmap[4]; +}; + +static int count_descriptors(struct desc_counts *cnt, uint8_t *buf, size_t len) +{ + PVUSBDESCCONFIG cfg; + uint8_t *tmp, *end; + uint32_t i, x; + + memset(cnt, 0, sizeof(*cnt)); + + end = buf + len; + + cfg = (PVUSBDESCCONFIG)buf; + if ( cfg->bLength < VUSB_DT_CONFIG_MIN_LEN ) + return 0; + if ( cfg->bLength > len ) + return 0; + + for (tmp = buf + cfg->bLength; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + uint8_t type; + uint32_t ifnum; + PVUSBDESCINTERFACE id; + PVUSBDESCENDPOINT ed; + + type = *(tmp + 1); + + switch ( type ) { + case VUSB_DT_INTERFACE: + id = (PVUSBDESCINTERFACE)tmp; + if ( id->bLength < VUSB_DT_INTERFACE_MIN_LEN ) + return 0; + cnt->num_id++; + ifnum = id->bInterfaceNumber; + cnt->idmap[ifnum >> 6] |= (1 << (ifnum & 0x1f)); + break; + case VUSB_DT_ENDPOINT: + ed = (PVUSBDESCENDPOINT)tmp; + if ( ed->bLength < VUSB_DT_ENDPOINT_MIN_LEN ) + return 0; + cnt->num_ed++; + break; + default: + break; + } + } + + /* count interfaces */ + for(i=0; i < RT_ELEMENTS(cnt->idmap); i++) + for(x=1; x; x<<=1) + if ( cnt->idmap[i] & x ) + cnt->num_if++; + + return 1; +} + +/* Given the pointer to a configuration/interface/endpoint descriptor, find any following + * non-standard (vendor or class) descriptors. + */ +static const void *collect_stray_bits(uint8_t *this_desc, uint8_t *end, uint16_t *cbExtra) +{ + uint8_t *tmp, *buf; + uint8_t type; + + Assert(*(this_desc + 1) == VUSB_DT_INTERFACE || *(this_desc + 1) == VUSB_DT_ENDPOINT || *(this_desc + 1) == VUSB_DT_CONFIG); + buf = this_desc; + + /* Skip the current configuration/interface/endpoint descriptor. */ + buf += *(uint8_t *)buf; + + /* Loop until we find another descriptor we understand. */ + for (tmp = buf; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + type = *(tmp + 1); + if (type == VUSB_DT_INTERFACE || type == VUSB_DT_ENDPOINT) + break; + } + *cbExtra = tmp - buf; + if (*cbExtra) + return buf; + else + return NULL; +} + +/* Setup a vusb_interface structure given some preallocated structures + * to use, (we counted them already) + */ +static int copy_interface(PVUSBINTERFACE pIf, uint8_t ifnum, + PVUSBDESCINTERFACEEX *id, PVUSBDESCENDPOINTEX *ed, + uint8_t *buf, size_t len) +{ + PVUSBDESCINTERFACEEX cur_if = NULL; + uint32_t altmap[4] = {0,}; + uint8_t *tmp, *end = buf + len; + uint8_t alt; + int state; + size_t num_ep = 0; + + buf += *(uint8_t *)buf; + + pIf->cSettings = 0; + pIf->paSettings = NULL; + + for (tmp = buf, state = 0; ((tmp + 1) < end) && *tmp; tmp += *tmp) + { + uint8_t type; + PVUSBDESCINTERFACE ifd; + PVUSBDESCENDPOINT epd; + PVUSBDESCENDPOINTEX cur_ep; + + type = tmp[1]; + + switch ( type ) { + case VUSB_DT_INTERFACE: + state = 0; + ifd = (PVUSBDESCINTERFACE)tmp; + + /* Ignoring this interface */ + if ( ifd->bInterfaceNumber != ifnum ) + break; + + /* Check we didn't see this alternate setting already + * because that will break stuff + */ + alt = ifd->bAlternateSetting; + if ( altmap[alt >> 6] & (1 << (alt & 0x1f)) ) + return 0; + altmap[alt >> 6] |= (1 << (alt & 0x1f)); + + cur_if = *id; + (*id)++; + if ( pIf->cSettings == 0 ) + pIf->paSettings = cur_if; + + memcpy(cur_if, ifd, sizeof(cur_if->Core)); + + /* Point to additional interface descriptor bytes, if any. */ + AssertCompile(sizeof(cur_if->Core) == VUSB_DT_INTERFACE_MIN_LEN); + if (cur_if->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN > 0) + cur_if->pvMore = tmp + VUSB_DT_INTERFACE_MIN_LEN; + else + cur_if->pvMore = NULL; + + cur_if->pvClass = collect_stray_bits(tmp, end, &cur_if->cbClass); + + pIf->cSettings++; + + state = 1; + num_ep = 0; + break; + case VUSB_DT_ENDPOINT: + if ( state == 0 ) + break; + + epd = (PVUSBDESCENDPOINT)tmp; + + cur_ep = *ed; + (*ed)++; + + if ( num_ep == 0 ) + cur_if->paEndpoints = cur_ep; + + if ( num_ep > cur_if->Core.bNumEndpoints ) + return 0; + + memcpy(cur_ep, epd, sizeof(cur_ep->Core)); + + /* Point to additional endpoint descriptor bytes, if any. */ + AssertCompile(sizeof(cur_ep->Core) == VUSB_DT_ENDPOINT_MIN_LEN); + if (cur_ep->Core.bLength - VUSB_DT_ENDPOINT_MIN_LEN > 0) + cur_ep->pvMore = tmp + VUSB_DT_ENDPOINT_MIN_LEN; + else + cur_ep->pvMore = NULL; + + cur_ep->pvClass = collect_stray_bits(tmp, end, &cur_ep->cbClass); + + cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize); + + num_ep++; + break; + default: + /* Skip unknown descriptors. */ + break; + } + } + + return 1; +} + +/** + * Copy all of a devices config descriptors, this is needed so that the USB + * core layer knows all about how to map the different functions on to the + * virtual USB bus. + */ +static bool copy_config(PUSBPROXYDEV pProxyDev, uint8_t idx, PVUSBDESCCONFIGEX out) +{ + PVUSBDESCCONFIG cfg; + PVUSBINTERFACE pIf; + PVUSBDESCINTERFACEEX ifd; + PVUSBDESCENDPOINTEX epd; + struct desc_counts cnt; + void *descs; + size_t tot_len; + size_t cbIface; + uint32_t i, x; + uint8_t *tmp, *end; + + descs = GetStdDescSync(pProxyDev, VUSB_DT_CONFIG, idx, 0, VUSB_DT_CONFIG_MIN_LEN); + if ( descs == NULL ) { + Log(("copy_config: GetStdDescSync failed\n")); + return false; + } + + cfg = (PVUSBDESCCONFIG)descs; + tot_len = RT_LE2H_U16(cfg->wTotalLength); + + if ( !count_descriptors(&cnt, (uint8_t *)descs, tot_len) ) { + Log(("copy_config: count_descriptors failed\n")); + goto err; + } + + if ( cfg->bNumInterfaces != cnt.num_if ) + Log(("usb-proxy: config%u: bNumInterfaces %u != %u\n", + idx, cfg->bNumInterfaces, cnt.num_if)); + + Log(("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n", + idx, tot_len, cnt.num_id, cnt.num_ed, cnt.num_if)); + + cbIface = cnt.num_if * sizeof(VUSBINTERFACE) + + cnt.num_id * sizeof(VUSBDESCINTERFACEEX) + + cnt.num_ed * sizeof(VUSBDESCENDPOINTEX); + out->paIfs = (PCVUSBINTERFACE)RTMemAllocZ(cbIface); + if ( out->paIfs == NULL ) { + free_desc(descs); + return false; + } + + /* Stash a pointer to the raw config descriptor; we may need bits of it later. */ + out->pvOriginal = descs; + + pIf = (PVUSBINTERFACE)out->paIfs; + ifd = (PVUSBDESCINTERFACEEX)&pIf[cnt.num_if]; + epd = (PVUSBDESCENDPOINTEX)&ifd[cnt.num_id]; + + out->Core.bLength = cfg->bLength; + out->Core.bDescriptorType = cfg->bDescriptorType; + out->Core.wTotalLength = 0; /* Auto Calculated */ + out->Core.bNumInterfaces = (uint8_t)cnt.num_if; + out->Core.bConfigurationValue = cfg->bConfigurationValue; + out->Core.iConfiguration = cfg->iConfiguration; + out->Core.bmAttributes = cfg->bmAttributes; + out->Core.MaxPower = cfg->MaxPower; + + tmp = (uint8_t *)out->pvOriginal; + end = tmp + tot_len; + + /* Point to additional configuration descriptor bytes, if any. */ + AssertCompile(sizeof(out->Core) == VUSB_DT_CONFIG_MIN_LEN); + if (out->Core.bLength - VUSB_DT_CONFIG_MIN_LEN > 0) + out->pvMore = tmp + VUSB_DT_CONFIG_MIN_LEN; + else + out->pvMore = NULL; + + /* Typically there might be an interface association descriptor here. */ + out->pvClass = collect_stray_bits(tmp, end, &out->cbClass); + + for(i=0; i < 4; i++) + for(x=0; x < 32; x++) + if ( cnt.idmap[i] & (1 << x) ) + if ( !copy_interface(pIf++, (i << 6) | x, &ifd, &epd, (uint8_t *)out->pvOriginal, tot_len) ) { + Log(("copy_interface(%d,,) failed\n", pIf - 1)); + goto err; + } + + return true; +err: + Log(("usb-proxy: config%u: Corrupted configuration descriptor\n", idx)); + free_desc(descs); + return false; +} + + +/** + * Edit out masked interface descriptors. + * + * @param pProxyDev The proxy device + */ +static void usbProxyDevEditOutMaskedIfs(PUSBPROXYDEV pProxyDev) +{ + unsigned cRemoved = 0; + + PVUSBDESCCONFIGEX paCfgs = pProxyDev->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pProxyDev->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + if ( paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber < 32 + && ((1 << paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber) & pProxyDev->fMaskedIfs)) + { + Log(("usb-proxy: removing interface #%d (iIf=%d iAlt=%d) on config #%d (iCfg=%d)\n", + paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber, iIf, iAlt, paCfgs[iCfg].Core.bConfigurationValue, iCfg)); + cRemoved++; + + paCfgs[iCfg].Core.bNumInterfaces--; + unsigned cToCopy = paCfgs[iCfg].Core.bNumInterfaces - iIf; + if (cToCopy) + memmove(&paIfs[iIf], &paIfs[iIf + 1], sizeof(paIfs[0]) * cToCopy); + memset(&paIfs[iIf + cToCopy], '\0', sizeof(paIfs[0])); + break; + } + } + + Log(("usb-proxy: edited out %d interface(s).\n", cRemoved)); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbReset} + * + * USB Device Proxy: Call OS specific code to reset the device. + */ +static DECLCALLBACK(int) usbProxyDevReset(PPDMUSBINS pUsbIns, bool fResetOnLinux) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + + if (pProxyDev->fMaskedIfs) + { + Log(("usbProxyDevReset: pProxyDev=%s - ignoring reset request fMaskedIfs=%#x\n", pUsbIns->pszName, pProxyDev->fMaskedIfs)); + return VINF_SUCCESS; + } + LogFlow(("usbProxyDevReset: pProxyDev=%s\n", pUsbIns->pszName)); + return pProxyDev->pOps->pfnReset(pProxyDev, fResetOnLinux); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache} + */ +static DECLCALLBACK(PCPDMUSBDESCCACHE) usbProxyDevGetDescriptorCache(PPDMUSBINS pUsbIns) +{ + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + return &pThis->DescCache; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbSetConfiguration} + * + * USB Device Proxy: Release claimed interfaces, tell the OS+device about the config change, claim the new interfaces. + */ +static DECLCALLBACK(int) usbProxyDevSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue, + const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevSetConfiguration: pProxyDev=%s iActiveCfg=%d bConfigurationValue=%d\n", + pUsbIns->pszName, pProxyDev->iActiveCfg, bConfigurationValue)); + + /* + * Release the current config. + */ + if (pvOldCfgDesc) + { + PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)pvOldCfgDesc; + PCVUSBINTERFACESTATE pOldIfState = (PCVUSBINTERFACESTATE)pvOldIfState; + for (unsigned i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++) + if (pOldIfState[i].pCurIfDesc) + pProxyDev->pOps->pfnReleaseInterface(pProxyDev, pOldIfState[i].pCurIfDesc->Core.bInterfaceNumber); + } + + /* + * Do the actual SET_CONFIGURE. + * The mess here is because most backends will already have selected a + * configuration and there are a bunch of devices which will freak out + * if we do SET_CONFIGURE twice with the same value. (PalmOne, TrekStor USB-StickGO, ..) + * + * After open and reset the backend should use the members iActiveCfg and cIgnoreSetConfigs + * to indicate the new configuration state and what to do on the next SET_CONFIGURATION call. + */ + if ( pProxyDev->iActiveCfg != bConfigurationValue + || ( bConfigurationValue == 0 + && pProxyDev->iActiveCfg != -1 /* this test doesn't make sense, we know it's 0 */ + && pProxyDev->cIgnoreSetConfigs >= 2) + || !pProxyDev->cIgnoreSetConfigs) + { + pProxyDev->cIgnoreSetConfigs = 0; + int rc = pProxyDev->pOps->pfnSetConfig(pProxyDev, bConfigurationValue); + if (RT_FAILURE(rc)) + { + pProxyDev->iActiveCfg = -1; + return rc; + } + pProxyDev->iActiveCfg = bConfigurationValue; + } + else if (pProxyDev->cIgnoreSetConfigs > 0) + pProxyDev->cIgnoreSetConfigs--; + + /* + * Claim the interfaces. + */ + PCVUSBDESCCONFIGEX pNewCfgDesc = (PCVUSBDESCCONFIGEX)pvNewCfgDesc; + Assert(pNewCfgDesc->Core.bConfigurationValue == bConfigurationValue); + for (unsigned iIf = 0; iIf < pNewCfgDesc->Core.bNumInterfaces; iIf++) + { + PCVUSBINTERFACE pIf = &pNewCfgDesc->paIfs[iIf]; + for (uint32_t iAlt = 0; iAlt < pIf->cSettings; iAlt++) + { + if (pIf->paSettings[iAlt].Core.bAlternateSetting != 0) + continue; + pProxyDev->pOps->pfnClaimInterface(pProxyDev, pIf->paSettings[iAlt].Core.bInterfaceNumber); + /* ignore failures - the backend deals with that and does the necessary logging. */ + break; + } + } + + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbSetInterface} + * + * USB Device Proxy: Call OS specific code to select alternate interface settings. + */ +static DECLCALLBACK(int) usbProxyDevSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevSetInterface: pProxyDev=%s bInterfaceNumber=%d bAlternateSetting=%d\n", + pUsbIns->pszName, bInterfaceNumber, bAlternateSetting)); + + return pProxyDev->pOps->pfnSetInterface(pProxyDev, bInterfaceNumber, bAlternateSetting); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUsbClearHaltedEndpoint} + * + * USB Device Proxy: Call OS specific code to clear the endpoint. + */ +static DECLCALLBACK(int) usbProxyDevClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyDevClearHaltedEndpoint: pProxyDev=%s uEndpoint=%u\n", + pUsbIns->pszName, uEndpoint)); + + return pProxyDev->pOps->pfnClearHaltedEndpoint(pProxyDev, uEndpoint); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbQueue} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevUrbQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, pUrb); + if (RT_FAILURE(rc)) + return pProxyDev->fDetached + ? VERR_VUSB_DEVICE_NOT_ATTACHED + : VERR_VUSB_FAILED_TO_QUEUE_URB; + return rc; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbCancel} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + return pProxyDev->pOps->pfnUrbCancel(pProxyDev, pUrb); +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnUrbReap} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(PVUSBURB) usbProxyDevUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + PVUSBURB pUrb = pProxyDev->pOps->pfnUrbReap(pProxyDev, cMillies); + if ( pUrb + && pUrb->enmState == VUSBURBSTATE_CANCELLED + && pUrb->enmStatus == VUSBSTATUS_OK) + pUrb->enmStatus = VUSBSTATUS_DNR; + return pUrb; +} + + +/** + * @interface_method_impl{PDMUSBREG,pfnWakeup} + * + * USB Device Proxy: Call OS specific code. + */ +static DECLCALLBACK(int) usbProxyDevWakeup(PPDMUSBINS pUsbIns) +{ + PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + + return pProxyDev->pOps->pfnWakeup(pProxyDev); +} + + +/** @interface_method_impl{PDMUSBREG,pfnDestruct} */ +static DECLCALLBACK(void) usbProxyDestruct(PPDMUSBINS pUsbIns) +{ + PDMUSB_CHECK_VERSIONS_RETURN_VOID(pUsbIns); + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + Log(("usbProxyDestruct: destroying pProxyDev=%s\n", pUsbIns->pszName)); + + /* close it. */ + if (pThis->fOpened) + { + pThis->pOps->pfnClose(pThis); + pThis->fOpened = false; + } + + /* free the config descriptors. */ + if (pThis->paCfgDescs) + { + for (unsigned i = 0; i < pThis->DevDesc.bNumConfigurations; i++) + { + RTMemFree((void *)pThis->paCfgDescs[i].paIfs); + RTMemFree((void *)pThis->paCfgDescs[i].pvOriginal); + } + RTMemFree(pThis->paCfgDescs); + pThis->paCfgDescs = NULL; + } + + /* free dev */ + if (&g_szDummyName[0] != pUsbIns->pszName) + RTStrFree(pUsbIns->pszName); + pUsbIns->pszName = NULL; + + if (pThis->pvInstanceDataR3) + RTMemFree(pThis->pvInstanceDataR3); +} + + +/** + * Helper function used by usbProxyConstruct when + * reading a filter from CFG. + * + * @returns VBox status code. + * @param pFilter The filter. + * @param enmFieldIdx The filter field indext. + * @param pNode The CFGM node. + * @param pszExact The exact value name. + * @param pszExpr The expression value name. + */ +static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, PCFGMNODE pNode, const char *pszExact, const char *pszExpr) +{ + char szTmp[256]; + + /* try exact first */ + uint16_t u16; + int rc = CFGMR3QueryU16(pNode, pszExact, &u16); + if (RT_SUCCESS(rc)) + { + rc = USBFilterSetNumExact(pFilter, enmFieldIdx, u16, true); + AssertRCReturn(rc, rc); + + /* make sure only the exact attribute is present. */ + rc = CFGMR3QueryString(pNode, pszExpr, szTmp, sizeof(szTmp)); + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + CFGMR3GetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: Both %s and %s are present!\n", szTmp, pszExact, pszExpr)); + return VERR_INVALID_PARAMETER; + } + return VINF_SUCCESS; + } + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + CFGMR3GetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc)); + return rc; + } + + /* expression? */ + rc = CFGMR3QueryString(pNode, pszExpr, szTmp, sizeof(szTmp)); + if (RT_SUCCESS(rc)) + { + rc = USBFilterSetNumExpression(pFilter, enmFieldIdx, szTmp, true); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; + } + if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND)) + { + szTmp[0] = '\0'; + CFGMR3GetName(pNode, szTmp, sizeof(szTmp)); + LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExpr, rc)); + return rc; + } + + return VINF_SUCCESS; +} + + +/** @interface_method_impl{PDMUSBREG,pfnConstruct} */ +static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal) +{ + PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns); + RT_NOREF(iInstance); + PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV); + LogFlow(("usbProxyConstruct: pUsbIns=%p iInstance=%d\n", pUsbIns, iInstance)); + + /* + * Initialize the instance data. + */ + pThis->pUsbIns = pUsbIns; + pThis->pUsbIns->pszName = g_szDummyName; + pThis->iActiveCfg = -1; + pThis->fMaskedIfs = 0; + pThis->fOpened = false; + pThis->fInited = false; + + /* + * Read the basic configuration. + */ + char szAddress[1024]; + int rc = CFGMR3QueryString(pCfg, "Address", szAddress, sizeof(szAddress)); + AssertRCReturn(rc, rc); + + char szBackend[64]; + rc = CFGMR3QueryString(pCfg, "Backend", szBackend, sizeof(szBackend)); + AssertRCReturn(rc, rc); + + void *pvBackend; + rc = CFGMR3QueryPtr(pCfg, "pvBackend", &pvBackend); + AssertRCReturn(rc, rc); + + /* + * Select backend and open the device. + */ + rc = VERR_NOT_FOUND; + for (unsigned i = 0; i < RT_ELEMENTS(g_aUsbProxies); i++) + { + if (!RTStrICmp(szBackend, g_aUsbProxies[i]->pszName)) + { + pThis->pOps = g_aUsbProxies[i]; + rc = VINF_SUCCESS; + break; + } + } + if (RT_FAILURE(rc)) + return PDMUSB_SET_ERROR(pUsbIns, rc, N_("USBProxy: Failed to find backend")); + + pThis->pvInstanceDataR3 = RTMemAllocZ(pThis->pOps->cbBackend); + if (!pThis->pvInstanceDataR3) + return PDMUSB_SET_ERROR(pUsbIns, VERR_NO_MEMORY, N_("USBProxy: can't allocate memory for host backend")); + + rc = pThis->pOps->pfnOpen(pThis, szAddress, pvBackend); + if (RT_FAILURE(rc)) + { + LogRel(("usbProxyConstruct: Failed to open '%s', rc=%Rrc\n", szAddress, rc)); + return rc; + } + pThis->fOpened = true; + + /* + * Get the device descriptor and format the device name (for logging). + */ + if (!usbProxyGetDeviceDesc(pThis, &pThis->DevDesc)) + { + Log(("usbProxyConstruct: usbProxyGetDeviceDesc failed\n")); + return VERR_READ_ERROR; + } + + RTStrAPrintf(&pUsbIns->pszName, "%p[proxy %04x:%04x]", pThis, pThis->DevDesc.idVendor, pThis->DevDesc.idProduct); /** @todo append the user comment */ + AssertReturn(pUsbIns->pszName, VERR_NO_MEMORY); + + /* + * Get config descriptors. + */ + size_t cbConfigs = pThis->DevDesc.bNumConfigurations * sizeof(pThis->paCfgDescs[0]); + pThis->paCfgDescs = (PVUSBDESCCONFIGEX)RTMemAllocZ(cbConfigs); + AssertReturn(pThis->paCfgDescs, VERR_NO_MEMORY); + + unsigned i; + for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++) + if (!copy_config(pThis, i, (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i])) + break; + if (i < pThis->DevDesc.bNumConfigurations) + { + Log(("usbProxyConstruct: copy_config failed, i=%d\n", i)); + return VERR_READ_ERROR; + } + + /* + * Pickup best matching global configuration for this device. + * The global configuration is organized like this: + * + * GlobalConfig/Whatever/ + * |- idVendor = 300 + * |- idProduct = 300 + * - Config/ + * + * The first level contains filter attributes which we stuff into a USBFILTER + * structure and match against the device info that's available. The highest + * ranked match is will be used. If nothing is found, the values will be + * queried from the GlobalConfig node (simplifies code and might actually + * be useful). + */ + PCFGMNODE pCfgGlobalDev = pCfgGlobal; + PCFGMNODE pCur = CFGMR3GetFirstChild(pCfgGlobal); + if (pCur) + { + /* + * Create a device filter from the device configuration + * descriptor ++. No strings currently. + */ + USBFILTER Device; + USBFilterInit(&Device, USBFILTERTYPE_CAPTURE); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_VENDOR_ID, pThis->DevDesc.idVendor, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_PRODUCT_ID, pThis->DevDesc.idProduct, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_REV, pThis->DevDesc.bcdDevice, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_CLASS, pThis->DevDesc.bDeviceClass, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_SUB_CLASS, pThis->DevDesc.bDeviceSubClass, true); AssertRC(rc); + rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_PROTOCOL, pThis->DevDesc.bDeviceProtocol, true); AssertRC(rc); + /** @todo manufacturer, product and serial strings */ + + int iBestMatchRate = -1; + PCFGMNODE pBestMatch = NULL; + for (pCur = CFGMR3GetFirstChild(pCfgGlobal); pCur; pCur = CFGMR3GetNextChild(pCur)) + { + /* + * Construct a filter from the attributes in the node. + */ + USBFILTER Filter; + USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE); + + /* numeric */ + if ( RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_VENDOR_ID, pCur, "idVendor", "idVendorExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pCur, "idProduct", "idProcutExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pCur, "bcdDevice", "bcdDeviceExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pCur, "bDeviceClass", "bDeviceClassExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pCur, "bDeviceSubClass", "bDeviceSubClassExpr")) + || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pCur, "bDeviceProtocol", "bDeviceProtocolExpr"))) + continue; /* skip it */ + + /* strings */ + /** @todo manufacturer, product and serial strings */ + + /* ignore unknown config values, but not without bitching. */ + if (!CFGMR3AreValuesValid(pCur, + "idVendor\0idVendorExpr\0" + "idProduct\0idProductExpr\0" + "bcdDevice\0bcdDeviceExpr\0" + "bDeviceClass\0bDeviceClassExpr\0" + "bDeviceSubClass\0bDeviceSubClassExpr\0" + "bDeviceProtocol\0bDeviceProtocolExpr\0")) + LogRel(("usbProxyConstruct: Unknown value(s) in config filter (ignored)!\n")); + + /* + * Try match it and on match see if it has is a higher rate hit + * than the previous match. Quit if its a 100% match. + */ + int iRate = USBFilterMatchRated(&Filter, &Device); + if (iRate > iBestMatchRate) + { + pBestMatch = pCur; + iBestMatchRate = iRate; + if (iRate >= 100) + break; + } + } + if (pBestMatch) + pCfgGlobalDev = CFGMR3GetChild(pBestMatch, "Config"); + if (pCfgGlobalDev) + pCfgGlobalDev = pCfgGlobal; + } + + /* + * Query the rest of the configuration using the global as fallback. + */ + rc = CFGMR3QueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = CFGMR3QueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + pThis->fMaskedIfs = 0; + else + AssertRCReturn(rc, rc); + + bool fForce11Device; + rc = CFGMR3QueryBool(pCfg, "Force11Device", &fForce11Device); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fForce11Device = false; + else + AssertRCReturn(rc, rc); + + bool fForce11PacketSize; + rc = CFGMR3QueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fForce11PacketSize = false; + else + AssertRCReturn(rc, rc); + + bool fEditAudioSyncEp; + rc = CFGMR3QueryBool(pCfg, "EditAudioSyncEp", &fEditAudioSyncEp); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + rc = CFGMR3QueryBool(pCfgGlobalDev, "EditAudioSyncEp", &fEditAudioSyncEp); + if (rc == VERR_CFGM_VALUE_NOT_FOUND) + fEditAudioSyncEp = true; /* NB: On by default! */ + else + AssertRCReturn(rc, rc); + + /* + * If we're masking interfaces, edit the descriptors. + */ + bool fEdited = pThis->fMaskedIfs != 0; + if (pThis->fMaskedIfs) + usbProxyDevEditOutMaskedIfs(pThis); + + /* + * Do 2.0 -> 1.1 device edits if requested to do so. + */ + if ( fForce11PacketSize + && pThis->DevDesc.bcdUSB >= 0x0200) + { + PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + { + /* + * USB 1.1 defines the max for control, interrupt and bulk to be 64 bytes. + * While isochronous has a max of 1023 bytes. + */ + PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints; + if (!paEps) + continue; + + for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++) + { + const uint16_t cbMax = (paEps[iEp].Core.bmAttributes & 3) == 1 /* isoc */ + ? 1023 + : 64; + if (paEps[iEp].Core.wMaxPacketSize > cbMax) + { + Log(("usb-proxy: pProxyDev=%s correcting wMaxPacketSize from %#x to %#x (mainly for vista)\n", + pUsbIns->pszName, paEps[iEp].Core.wMaxPacketSize, cbMax)); + paEps[iEp].Core.wMaxPacketSize = cbMax; + fEdited = true; + } + } + } + } + } + + if ( fForce11Device + && pThis->DevDesc.bcdUSB == 0x0200) + { + /* + * Discourages windows from helping you find a 2.0 port. + */ + Log(("usb-proxy: %s correcting USB version 2.0 to 1.1 (to avoid Windows warning)\n", pUsbIns->pszName)); + pThis->DevDesc.bcdUSB = 0x110; + fEdited = true; + } + + + /* + * Turn asynchronous audio endpoints into synchronous ones, see @bugref{8769} + */ + if (fEditAudioSyncEp) + { + PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs; + for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++) + { + PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs; + for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++) + for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++) + { + /* If not an audio class interface, skip. */ + if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceClass != 1) + continue; + + /* If not a streaming interface, skip. */ + if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceSubClass != 2) + continue; + + PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints; + if (!paEps) + continue; + + for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++) + { + /* isoch/asynch/data*/ + if ((paEps[iEp].Core.bmAttributes == 5) && (paEps[iEp].Core.bLength == 9)) + { + uint8_t *pbExtra = (uint8_t *)paEps[iEp].pvMore; /* unconst*/ + if (pbExtra[1] == 0) + continue; /* If bSynchAddress is zero, leave the descriptor alone. */ + + Log(("usb-proxy: pProxyDev=%s async audio with bmAttr=%02X [%02X, %02X] on EP %02X\n", + pUsbIns->pszName, paEps[iEp].Core.bmAttributes, pbExtra[0], pbExtra[1], paEps[iEp].Core.bEndpointAddress)); + paEps[iEp].Core.bmAttributes = 0xD; /* isoch/synch/data*/ + pbExtra[1] = 0; /* Clear bSynchAddress. */ + fEdited = true; + LogRel(("VUSB: Modified '%s' async audio endpoint 0x%02x\n", pUsbIns->pszName, paEps[iEp].Core.bEndpointAddress)); + } + } + } + } + } + + /* + * Init the PDM/VUSB descriptor cache. + */ + pThis->DescCache.pDevice = &pThis->DevDesc; + pThis->DescCache.paConfigs = pThis->paCfgDescs; + pThis->DescCache.paLanguages = NULL; + pThis->DescCache.cLanguages = 0; + pThis->DescCache.fUseCachedDescriptors = fEdited; + pThis->DescCache.fUseCachedStringsDescriptors = false; + + /* + * Call the backend if it wishes to do some more initializing + * after we've read the config and descriptors. + */ + if (pThis->pOps->pfnInit) + { + rc = pThis->pOps->pfnInit(pThis); + if (RT_FAILURE(rc)) + return rc; + } + pThis->fInited = true; + + /* + * We're good! + */ + Log(("usb-proxy: created pProxyDev=%s address '%s' fMaskedIfs=%#x (rc=%Rrc)\n", + pUsbIns->pszName, szAddress, pThis->fMaskedIfs, rc)); + return VINF_SUCCESS; +} + + +/** + * The USB proxy device registration record. + */ +const PDMUSBREG g_UsbDevProxy = +{ + /* u32Version */ + PDM_USBREG_VERSION, + /* szName */ + "USBProxy", + /* pszDescription */ + "USB Proxy Device.", + /* fFlags */ + 0, + /* cMaxInstances */ + ~0U, + /* cbInstance */ + sizeof(USBPROXYDEV), + /* pfnConstruct */ + usbProxyConstruct, + /* pfnDestruct */ + usbProxyDestruct, + /* pfnVMInitComplete */ + NULL, + /* pfnVMPowerOn */ + NULL, + /* pfnVMReset */ + NULL, + /* pfnVMSuspend */ + NULL, + /* pfnVMResume */ + NULL, + /* pfnVMPowerOff */ + NULL, + /* pfnHotPlugged */ + NULL, + /* pfnHotUnplugged */ + NULL, + /* pfnDriverAttach */ + NULL, + /* pfnDriverDetach */ + NULL, + /* pfnQueryInterface */ + NULL, + /* pfnUsbReset */ + usbProxyDevReset, + /* pfnUsbGetDescriptorCache */ + usbProxyDevGetDescriptorCache, + /* pfnUsbSetConfiguration */ + usbProxyDevSetConfiguration, + /* pfnUsbSetInterface */ + usbProxyDevSetInterface, + /* pfnUsbClearHaltedEndpoint */ + usbProxyDevClearHaltedEndpoint, + /* pfnUrbNew */ + NULL, + /* pfnUrbQueue */ + usbProxyDevUrbQueue, + /* pfnUrbCancel */ + usbProxyDevUrbCancel, + /* pfnUrbReap */ + usbProxyDevUrbReap, + /* pfnWakeup */ + usbProxyDevWakeup, + + /* u32TheEnd */ + PDM_USBREG_VERSION +}; + diff --git a/src/VBox/Devices/USB/USBProxyDevice.h b/src/VBox/Devices/USB/USBProxyDevice.h new file mode 100644 index 00000000..4e05bdfe --- /dev/null +++ b/src/VBox/Devices/USB/USBProxyDevice.h @@ -0,0 +1,276 @@ +/* $Id: USBProxyDevice.h $ */ +/** @file + * USBPROXY - USB proxy header + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_USB_USBProxyDevice_h +#define VBOX_INCLUDED_SRC_USB_USBProxyDevice_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/vusb.h> + +RT_C_DECLS_BEGIN + + +/** Pointer to a USB proxy device. */ +typedef struct USBPROXYDEV *PUSBPROXYDEV; + +/** + * USB Proxy Device Backend + */ +typedef struct USBPROXYBACK +{ + /** Name of the backend. */ + const char *pszName; + /** Size of the backend specific data. */ + size_t cbBackend; + + /** + * Opens the USB device specfied by pszAddress. + * + * This method will initialize backend private data. If the backend has + * already selected a configuration for the device, this must be indicated + * in USBPROXYDEV::iActiveCfg. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pszAddress Host specific USB device address. + * @param pvBackend Pointer to backend specific data. + */ + DECLR3CALLBACKMEMBER(int, pfnOpen, (PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend)); + + /** + * Optional callback for initializing the device after the configuration + * has been established. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUSBPROXYDEV pProxyDev)); + + /** + * Closes handle to the host USB device. + * + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(void, pfnClose, (PUSBPROXYDEV pProxyDev)); + + /** + * Reset a device. + * + * The backend must update iActualCfg and fIgnoreEqualSetConfig. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param fResetOnLinux It's safe to do reset on linux, we can deal with devices + * being logically reconnected. + */ + DECLR3CALLBACKMEMBER(int, pfnReset, (PUSBPROXYDEV pProxyDev, bool fResetOnLinux)); + + /** + * Sets the given configuration of the device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iCfg The configuration to set. + */ + DECLR3CALLBACKMEMBER(int, pfnSetConfig, (PUSBPROXYDEV pProxyDev, int iCfg)); + + /** + * Claim an interface for use by the prox device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface number to claim. + */ + DECLR3CALLBACKMEMBER(int, pfnClaimInterface, (PUSBPROXYDEV pProxyDev, int iIf)); + + /** + * Releases an interface which was claimed before. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface number to release. + */ + DECLR3CALLBACKMEMBER(int, pfnReleaseInterface, (PUSBPROXYDEV pProxyDev, int iIf)); + + /** + * Sets the given alternate interface for the device. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iIf Interface to use. + * @param iSetting The alternate setting to use. + */ + DECLR3CALLBACKMEMBER(int, pfnSetInterface, (PUSBPROXYDEV pProxyDev, int iIf, int iSetting)); + + /** + * Clears the given halted endpoint. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param iEp The endpoint to clear. + */ + DECLR3CALLBACKMEMBER(int, pfnClearHaltedEndpoint, (PUSBPROXYDEV pDev, unsigned int iEp)); + + /** + * Queue a new URB. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pUrb The URB to queue. + */ + DECLR3CALLBACKMEMBER(int, pfnUrbQueue, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)); + + /** + * Cancel an in-flight URB. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + * @param pUrb The URB to cancel. + */ + DECLR3CALLBACKMEMBER(int, pfnUrbCancel, (PUSBPROXYDEV pProxyDev, PVUSBURB pUrb)); + + /** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The USB Proxy Device instance. + * @param cMillies Number of milliseconds to wait. Use 0 to not + * wait at all. + */ + DECLR3CALLBACKMEMBER(PVUSBURB, pfnUrbReap, (PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)); + + /** + * Kicks the thread waiting in pfnUrbReap to make it return. + * + * @returns VBox status code. + * @param pProxyDev The USB Proxy Device instance. + */ + DECLR3CALLBACKMEMBER(int, pfnWakeup, (PUSBPROXYDEV pProxyDev)); + + /** Dummy entry for making sure we've got all members initialized. */ + uint32_t uDummy; +} USBPROXYBACK; +/** Pointer to a USB Proxy Device Backend. */ +typedef USBPROXYBACK *PUSBPROXYBACK; +/** Pointer to a const USB Proxy Device Backend. */ +typedef const USBPROXYBACK *PCUSBPROXYBACK; + +/** The Host backend. */ +extern const USBPROXYBACK g_USBProxyDeviceHost; +/** The remote desktop backend. */ +extern const USBPROXYBACK g_USBProxyDeviceVRDP; +/** The USB/IP backend. */ +extern const USBPROXYBACK g_USBProxyDeviceUsbIp; + +#ifdef RDESKTOP +typedef struct VUSBDEV +{ + char* pszName; +} VUSBDEV, *PVUSBDEV; +#endif + +/** + * USB Proxy device. + */ +typedef struct USBPROXYDEV +{ + /** The device descriptor. */ + VUSBDESCDEVICE DevDesc; + /** The configuration descriptor array. */ + PVUSBDESCCONFIGEX paCfgDescs; +#ifndef RDESKTOP + /** The descriptor cache. + * Contains &DevDesc and paConfigDescs. */ + PDMUSBDESCCACHE DescCache; + /** Pointer to the PDM USB device instance. */ + PPDMUSBINS pUsbIns; +#endif + + /** Pointer to the backend. */ + PCUSBPROXYBACK pOps; + /** The currently active configuration. + * It's -1 if no configuration is active. This is set to -1 before open and reset, + * the backend will change it if open or reset implies SET_CONFIGURATION. */ + int iActiveCfg; + /** Ignore one or two SET_CONFIGURATION operation. + * See usbProxyDevSetCfg for details. */ + int cIgnoreSetConfigs; + /** Mask of the interfaces that the guest shall not see. */ + uint32_t fMaskedIfs; + /** Whether we've opened the device or not. + * For dealing with failed construction (the destruct method is always called). */ + bool fOpened; + /** Whether we've called pfnInit or not. + * For dealing with failed construction (the destruct method is always called). */ + bool fInited; + /** Whether the device has been detached. + * This is hack for making PDMUSBREG::pfnUsbQueue return the right status code. */ + bool fDetached; + /** Backend specific data, the size is stored in pOps::cbBackend. */ + void *pvInstanceDataR3; + +#ifdef RDESKTOP + /** @name VRDP client (rdesktop) related members. + * @{ */ + /** The vrdp device ID. */ + uint32_t idVrdp; + /** The VUSB device structure - must be the first structure member. */ + VUSBDEV Dev; + /** The next device in rdesktop-vrdp's linked list. */ + PUSBPROXYDEV pNext; + /** The previous device in rdesktop-vrdp's linked list. */ + PUSBPROXYDEV pPrev; + /** Linked list of in-flight URBs */ + PVUSBURB pUrbs; + /** @} */ +#endif +} USBPROXYDEV; + +/** @def USBPROXYDEV_2_DATA + * Converts a USB proxy Device, pointer to a pointer to the backend specific instance data. + */ +#define USBPROXYDEV_2_DATA(a_pProxyDev, a_Type) ( (a_Type)(a_pProxyDev)->pvInstanceDataR3 ) + + +DECLINLINE(const char *) usbProxyGetName(PUSBPROXYDEV pProxyDev) +{ +#ifndef RDESKTOP + return pProxyDev->pUsbIns->pszName; +#else + return pProxyDev->Dev.pszName; +#endif +} + +#ifdef RDESKTOP +DECLINLINE(PUSBPROXYDEV) usbProxyFromVusbDev(PVUSBDEV pDev) +{ + return RT_FROM_MEMBER(pDev, USBPROXYDEV, Dev); +} +#endif + +#ifdef RT_OS_LINUX +RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev); +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_USB_USBProxyDevice_h */ + diff --git a/src/VBox/Devices/USB/VUSBDevice.cpp b/src/VBox/Devices/USB/VUSBDevice.cpp new file mode 100644 index 00000000..40be6825 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBDevice.cpp @@ -0,0 +1,1849 @@ +/* $Id: VUSBDevice.cpp $ */ +/** @file + * Virtual USB - Device. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include "VUSBInternal.h" + +#include "VUSBSniffer.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Argument package of vusbDevResetThread(). + */ +typedef struct vusb_reset_args +{ + /** Pointer to the device which is being reset. */ + PVUSBDEV pDev; + /** The reset return code. */ + int rc; + /** Pointer to the completion callback. */ + PFNVUSBRESETDONE pfnDone; + /** User argument to pfnDone. */ + void *pvUser; +} VUSBRESETARGS, *PVUSBRESETARGS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Default message pipe. */ +const VUSBDESCENDPOINTEX g_Endpoint0 = +{ + { + /* .bLength = */ VUSB_DT_ENDPOINT_MIN_LEN, + /* .bDescriptorType = */ VUSB_DT_ENDPOINT, + /* .bEndpointAddress = */ 0, + /* .bmAttributes = */ 0, + /* .wMaxPacketSize = */ 64, + /* .bInterval = */ 0 + }, + NULL +}; + +/** Default configuration. */ +const VUSBDESCCONFIGEX g_Config0 = +{ + { + /* .bLength = */ VUSB_DT_CONFIG_MIN_LEN, + /* .bDescriptorType = */ VUSB_DT_CONFIG, + /* .WTotalLength = */ 0, /* (auto-calculated) */ + /* .bNumInterfaces = */ 0, + /* .bConfigurationValue =*/ 0, + /* .iConfiguration = */ 0, + /* .bmAttributes = */ 0x80, + /* .MaxPower = */ 14 + }, + NULL, + NULL +}; + + + +static PCVUSBDESCCONFIGEX vusbDevFindCfgDesc(PVUSBDEV pDev, int iCfg) +{ + if (iCfg == 0) + return &g_Config0; + + for (unsigned i = 0; i < pDev->pDescCache->pDevice->bNumConfigurations; i++) + if (pDev->pDescCache->paConfigs[i].Core.bConfigurationValue == iCfg) + return &pDev->pDescCache->paConfigs[i]; + return NULL; +} + +static PVUSBINTERFACESTATE vusbDevFindIfState(PVUSBDEV pDev, int iIf) +{ + for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++) + if (pDev->paIfStates[i].pIf->paSettings[0].Core.bInterfaceNumber == iIf) + return &pDev->paIfStates[i]; + return NULL; +} + +static PCVUSBDESCINTERFACEEX vusbDevFindAltIfDesc(PCVUSBINTERFACESTATE pIfState, int iAlt) +{ + for (uint32_t i = 0; i < pIfState->pIf->cSettings; i++) + if (pIfState->pIf->paSettings[i].Core.bAlternateSetting == iAlt) + return &pIfState->pIf->paSettings[i]; + return NULL; +} + +void vusbDevMapEndpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc) +{ + uint8_t i8Addr = pEndPtDesc->Core.bEndpointAddress & 0xF; + PVUSBPIPE pPipe = &pDev->aPipes[i8Addr]; + LogFlow(("vusbDevMapEndpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n", + pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes, + pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3])); + + if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0) + { + Log(("vusb: map message pipe on address %u\n", i8Addr)); + pPipe->in = pEndPtDesc; + pPipe->out = pEndPtDesc; + } + else if (pEndPtDesc->Core.bEndpointAddress & 0x80) + { + Log(("vusb: map input pipe on address %u\n", i8Addr)); + pPipe->in = pEndPtDesc; + } + else + { + Log(("vusb: map output pipe on address %u\n", i8Addr)); + pPipe->out = pEndPtDesc; + } + + if (pPipe->pCtrl) + { + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + } +} + +static void unmap_endpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc) +{ + uint8_t EndPt = pEndPtDesc->Core.bEndpointAddress & 0xF; + PVUSBPIPE pPipe = &pDev->aPipes[EndPt]; + LogFlow(("unmap_endpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n", + pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes, + pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3])); + + if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0) + { + Log(("vusb: unmap MSG pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->in = NULL; + pPipe->out = NULL; + } + else if (pEndPtDesc->Core.bEndpointAddress & 0x80) + { + Log(("vusb: unmap IN pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->in = NULL; + } + else + { + Log(("vusb: unmap OUT pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress)); + pPipe->out = NULL; + } + + if (pPipe->pCtrl) + { + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + } +} + +static void map_interface(PVUSBDEV pDev, PCVUSBDESCINTERFACEEX pIfDesc) +{ + LogFlow(("map_interface: pDev=%p[%s] pIfDesc=%p:{.iInterface=%d, .bAlternateSetting=%d}\n", + pDev, pDev->pUsbIns->pszName, pIfDesc, pIfDesc->Core.iInterface, pIfDesc->Core.bAlternateSetting)); + + for (unsigned i = 0; i < pIfDesc->Core.bNumEndpoints; i++) + { + if ((pIfDesc->paEndpoints[i].Core.bEndpointAddress & 0xF) == VUSB_PIPE_DEFAULT) + Log(("vusb: Endpoint 0x%x on interface %u.%u tried to override the default message pipe!!!\n", + pIfDesc->paEndpoints[i].Core.bEndpointAddress, pIfDesc->Core.bInterfaceNumber, pIfDesc->Core.bAlternateSetting)); + else + vusbDevMapEndpoint(pDev, &pIfDesc->paEndpoints[i]); + } +} + + +/** + * Worker that resets the pipe data on select config and detach. + * + * This leaves the critical section unmolested + * + * @param pPipe The pipe which data should be reset. + */ +static void vusbDevResetPipeData(PVUSBPIPE pPipe) +{ + vusbMsgFreeExtraData(pPipe->pCtrl); + pPipe->pCtrl = NULL; + + RT_ZERO(pPipe->in); + RT_ZERO(pPipe->out); + pPipe->async = 0; +} + + +bool vusbDevDoSelectConfig(PVUSBDEV pDev, PCVUSBDESCCONFIGEX pCfgDesc) +{ + LogFlow(("vusbDevDoSelectConfig: pDev=%p[%s] pCfgDesc=%p:{.iConfiguration=%d}\n", + pDev, pDev->pUsbIns->pszName, pCfgDesc, pCfgDesc->Core.iConfiguration)); + + /* + * Clean up all pipes and interfaces. + */ + unsigned i; + for (i = 0; i < VUSB_PIPE_MAX; i++) + if (i != VUSB_PIPE_DEFAULT) + vusbDevResetPipeData(&pDev->aPipes[i]); + memset(pDev->paIfStates, 0, pCfgDesc->Core.bNumInterfaces * sizeof(pDev->paIfStates[0])); + + /* + * Map in the default setting for every interface. + */ + for (i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf; + struct vusb_interface_state *pIfState; + + pIf = &pCfgDesc->paIfs[i]; + pIfState = &pDev->paIfStates[i]; + pIfState->pIf = pIf; + + /* + * Find the 0 setting, if it is not present we just use + * the lowest numbered one. + */ + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + if ( !pIfState->pCurIfDesc + || pIf->paSettings[j].Core.bAlternateSetting < pIfState->pCurIfDesc->Core.bAlternateSetting) + pIfState->pCurIfDesc = &pIf->paSettings[j]; + if (pIfState->pCurIfDesc->Core.bAlternateSetting == 0) + break; + } + + if (pIfState->pCurIfDesc) + map_interface(pDev, pIfState->pCurIfDesc); + } + + pDev->pCurCfgDesc = pCfgDesc; + + if (pCfgDesc->Core.bmAttributes & 0x40) + pDev->u16Status |= (1 << VUSB_DEV_SELF_POWERED); + else + pDev->u16Status &= ~(1 << VUSB_DEV_SELF_POWERED); + + return true; +} + +/** + * Standard device request: SET_CONFIGURATION + * @returns success indicator. + */ +static bool vusbDevStdReqSetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + unsigned iCfg = pSetup->wValue & 0xff; + + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: SET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DEFAULT) + { + LogFlow(("vusbDevStdReqSetConfig: %s: default dev state !!?\n", pDev->pUsbIns->pszName)); + return false; + } + + PCVUSBDESCCONFIGEX pNewCfgDesc = vusbDevFindCfgDesc(pDev, iCfg); + if (!pNewCfgDesc) + { + Log(("vusb: error: %s: config %i not found !!!\n", pDev->pUsbIns->pszName, iCfg)); + return false; + } + + if (iCfg == 0) + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS); + else + vusbDevSetState(pDev, VUSB_DEVICE_STATE_CONFIGURED); + if (pDev->pUsbIns->pReg->pfnUsbSetConfiguration) + { + RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5, + pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue, + pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc); + RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + if (RT_FAILURE(rc)) + { + Log(("vusb: error: %s: failed to set config %i (%Rrc) !!!\n", pDev->pUsbIns->pszName, iCfg, rc)); + return false; + } + } + Log(("vusb: %p[%s]: SET_CONFIGURATION: Selected config %u\n", pDev, pDev->pUsbIns->pszName, iCfg)); + return vusbDevDoSelectConfig(pDev, pNewCfgDesc); +} + + +/** + * Standard device request: GET_CONFIGURATION + * @returns success indicator. + */ +static bool vusbDevStdReqGetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: GET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if ( enmState != VUSB_DEVICE_STATE_CONFIGURED + && enmState != VUSB_DEVICE_STATE_ADDRESS) + { + LogFlow(("vusbDevStdReqGetConfig: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + if (*pcbBuf < 1) + { + LogFlow(("vusbDevStdReqGetConfig: %s: no space for data!\n", pDev->pUsbIns->pszName)); + return true; + } + + uint8_t iCfg; + if (enmState == VUSB_DEVICE_STATE_ADDRESS) + iCfg = 0; + else + iCfg = pDev->pCurCfgDesc->Core.bConfigurationValue; + + *pbBuf = iCfg; + *pcbBuf = 1; + LogFlow(("vusbDevStdReqGetConfig: %s: returns iCfg=%d\n", pDev->pUsbIns->pszName, iCfg)); + return true; +} + +/** + * Standard device request: GET_INTERFACE + * @returns success indicator. + */ +static bool vusbDevStdReqGetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE) + { + Log(("vusb: error: %s: GET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState != VUSB_DEVICE_STATE_CONFIGURED) + { + LogFlow(("vusbDevStdReqGetInterface: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + if (*pcbBuf < 1) + { + LogFlow(("vusbDevStdReqGetInterface: %s: no space for data!\n", pDev->pUsbIns->pszName)); + return true; + } + + for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBDESCINTERFACEEX pIfDesc = pDev->paIfStates[i].pCurIfDesc; + if ( pIfDesc + && pSetup->wIndex == pIfDesc->Core.bInterfaceNumber) + { + *pbBuf = pIfDesc->Core.bAlternateSetting; + *pcbBuf = 1; + Log(("vusb: %s: GET_INTERFACE: %u.%u\n", pDev->pUsbIns->pszName, pIfDesc->Core.bInterfaceNumber, *pbBuf)); + return true; + } + } + + Log(("vusb: error: %s: GET_INTERFACE - unknown iface %u !!!\n", pDev->pUsbIns->pszName, pSetup->wIndex)); + return false; +} + +/** + * Standard device request: SET_INTERFACE + * @returns success indicator. + */ +static bool vusbDevStdReqSetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE) + { + Log(("vusb: error: %s: SET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState != VUSB_DEVICE_STATE_CONFIGURED) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + /* + * Find the interface. + */ + uint8_t iIf = pSetup->wIndex; + PVUSBINTERFACESTATE pIfState = vusbDevFindIfState(pDev, iIf); + if (!pIfState) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find interface %u !!!\n", pDev->pUsbIns->pszName, iIf)); + return false; + } + uint8_t iAlt = pSetup->wValue; + PCVUSBDESCINTERFACEEX pIfDesc = vusbDevFindAltIfDesc(pIfState, iAlt); + if (!pIfDesc) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u !!!\n", pDev->pUsbIns->pszName, iIf, iAlt)); + return false; + } + + if (pDev->pUsbIns->pReg->pfnUsbSetInterface) + { + RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt); + RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + if (RT_FAILURE(rc)) + { + LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u (%Rrc)\n", pDev->pUsbIns->pszName, iIf, iAlt, rc)); + return false; + } + } + + for (unsigned i = 0; i < pIfState->pCurIfDesc->Core.bNumEndpoints; i++) + unmap_endpoint(pDev, &pIfState->pCurIfDesc->paEndpoints[i]); + + Log(("vusb: SET_INTERFACE: Selected %u.%u\n", iIf, iAlt)); + + map_interface(pDev, pIfDesc); + pIfState->pCurIfDesc = pIfDesc; + + return true; +} + +/** + * Standard device request: SET_ADDRESS + * @returns success indicator. + */ +static bool vusbDevStdReqSetAddress(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt, pbBuf, pcbBuf); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + { + Log(("vusb: error: %s: SET_ADDRESS - invalid request (dir) !!!\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Check that the device is in a valid state. + * (The caller has already checked that it's not being reset.) + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if ( enmState != VUSB_DEVICE_STATE_DEFAULT + && enmState != VUSB_DEVICE_STATE_ADDRESS) + { + LogFlow(("vusbDevStdReqSetAddress: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState)); + return false; + } + + pDev->u8NewAddress = pSetup->wValue; + return true; +} + +/** + * Standard device request: CLEAR_FEATURE + * @returns success indicator. + * + * @remark This is only called for VUSB_TO_ENDPOINT && ep == 0 && wValue == ENDPOINT_HALT. + * All other cases of CLEAR_FEATURE is handled in the normal async/sync manner. + */ +static bool vusbDevStdReqClearFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(pbBuf, pcbBuf); + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + Log(("vusb: ClearFeature: dev(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_INTERFACE: + Log(("vusb: ClearFeature: iface(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_ENDPOINT: + Log(("vusb: ClearFeature: ep(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue)); + if ( !EndPt /* Default control pipe only */ + && pSetup->wValue == 0 /* ENDPOINT_HALT */ + && pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint) + { + RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint, + 2, pDev->pUsbIns, pSetup->wIndex); + RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); + return RT_SUCCESS(rc); + } + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + break; + } + + AssertMsgFailed(("Invalid safe check !!!\n")); + return false; +} + +/** + * Standard device request: SET_FEATURE + * @returns success indicator. + */ +static bool vusbDevStdReqSetFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(pDev, EndPt, pbBuf, pcbBuf); + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + Log(("vusb: SetFeature: dev(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_INTERFACE: + Log(("vusb: SetFeature: if(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + case VUSB_TO_ENDPOINT: + Log(("vusb: SetFeature: ep(%u): selector=%u\n", + pSetup->wIndex, pSetup->wValue)); + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + return false; + } + AssertMsgFailed(("This stuff is bogus\n")); + return false; +} + +static bool vusbDevStdReqGetStatus(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if (*pcbBuf != 2) + { + LogFlow(("vusbDevStdReqGetStatus: %s: buffer is too small! (%d)\n", pDev->pUsbIns->pszName, *pcbBuf)); + return false; + } + + uint16_t u16Status; + switch (pSetup->bmRequestType & VUSB_RECIP_MASK) + { + case VUSB_TO_DEVICE: + u16Status = pDev->u16Status; + LogFlow(("vusbDevStdReqGetStatus: %s: device status %#x (%d)\n", pDev->pUsbIns->pszName, u16Status, u16Status)); + break; + case VUSB_TO_INTERFACE: + u16Status = 0; + LogFlow(("vusbDevStdReqGetStatus: %s: bogus interface status request!!\n", pDev->pUsbIns->pszName)); + break; + case VUSB_TO_ENDPOINT: + u16Status = 0; + LogFlow(("vusbDevStdReqGetStatus: %s: bogus endpoint status request!!\n", pDev->pUsbIns->pszName)); + break; + default: + AssertMsgFailed(("VUSB_TO_OTHER!\n")); + return false; + } + + *(uint16_t *)pbBuf = u16Status; + return true; +} + + +/** + * Finds a cached string. + * + * @returns Pointer to the cached string if found. NULL if not. + * @param paLanguages The languages to search. + * @param cLanguages The number of languages in the table. + * @param idLang The language ID. + * @param iString The string index. + */ +static PCPDMUSBDESCCACHESTRING FindCachedString(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages, + uint16_t idLang, uint8_t iString) +{ + /** @todo binary lookups! */ + unsigned iCurLang = cLanguages; + while (iCurLang-- > 0) + if (paLanguages[iCurLang].idLang == idLang) + { + PCPDMUSBDESCCACHESTRING paStrings = paLanguages[iCurLang].paStrings; + unsigned iCurStr = paLanguages[iCurLang].cStrings; + while (iCurStr-- > 0) + if (paStrings[iCurStr].idx == iString) + return &paStrings[iCurStr]; + break; + } + return NULL; +} + + +/** Macro for copying descriptor data. */ +#define COPY_DATA(pbDst, cbLeft, pvSrc, cbSrc) \ + do { \ + uint32_t cbSrc_ = cbSrc; \ + uint32_t cbCopy = RT_MIN(cbLeft, cbSrc_); \ + if (cbCopy) \ + memcpy(pbBuf, pvSrc, cbCopy); \ + cbLeft -= cbCopy; \ + if (!cbLeft) \ + return; \ + pbBuf += cbCopy; \ + } while (0) + +/** + * Internal function for reading the language IDs. + */ +static void ReadCachedStringDesc(PCPDMUSBDESCCACHESTRING pString, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + RTUTF16 wsz[128]; /* 128-1 => bLength=0xff */ + PRTUTF16 pwsz = wsz; + size_t cwc; + int rc = RTStrToUtf16Ex(pString->psz, RT_ELEMENTS(wsz) - 1, &pwsz, RT_ELEMENTS(wsz), &cwc); + if (RT_FAILURE(rc)) + { + AssertRC(rc); + wsz[0] = 'e'; + wsz[1] = 'r'; + wsz[2] = 'r'; + cwc = 3; + } + + VUSBDESCSTRING StringDesc; + StringDesc.bLength = (uint8_t)(sizeof(StringDesc) + cwc * sizeof(RTUTF16)); + StringDesc.bDescriptorType = VUSB_DT_STRING; + COPY_DATA(pbBuf, cbLeft, &StringDesc, sizeof(StringDesc)); + COPY_DATA(pbBuf, cbLeft, wsz, (uint32_t)cwc * sizeof(RTUTF16)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + + +/** + * Internal function for reading the language IDs. + */ +static void ReadCachedLangIdDesc(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages, + uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + VUSBDESCLANGID LangIdDesc; + size_t cbDesc = sizeof(LangIdDesc) + cLanguages * sizeof(paLanguages[0].idLang); + LangIdDesc.bLength = (uint8_t)RT_MIN(0xff, cbDesc); + LangIdDesc.bDescriptorType = VUSB_DT_STRING; + COPY_DATA(pbBuf, cbLeft, &LangIdDesc, sizeof(LangIdDesc)); + + unsigned iLanguage = cLanguages; + while (iLanguage-- > 0) + COPY_DATA(pbBuf, cbLeft, &paLanguages[iLanguage].idLang, sizeof(paLanguages[iLanguage].idLang)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + + +/** + * Internal function which performs a descriptor read on the cached descriptors. + */ +static void ReadCachedConfigDesc(PCVUSBDESCCONFIGEX pCfgDesc, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + /* + * Make a copy of the config descriptor and calculate the wTotalLength field. + */ + VUSBDESCCONFIG CfgDesc; + memcpy(&CfgDesc, pCfgDesc, VUSB_DT_CONFIG_MIN_LEN); + uint32_t cbTotal = 0; + cbTotal += pCfgDesc->Core.bLength; + cbTotal += pCfgDesc->cbClass; + for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i]; + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + cbTotal += pIf->paSettings[j].cbIAD; + cbTotal += pIf->paSettings[j].Core.bLength; + cbTotal += pIf->paSettings[j].cbClass; + for (unsigned k = 0; k < pIf->paSettings[j].Core.bNumEndpoints; k++) + { + cbTotal += pIf->paSettings[j].paEndpoints[k].Core.bLength; + cbTotal += pIf->paSettings[j].paEndpoints[k].cbSsepc; + cbTotal += pIf->paSettings[j].paEndpoints[k].cbClass; + } + } + } + CfgDesc.wTotalLength = RT_H2LE_U16(cbTotal); + + /* + * Copy the config descriptor + */ + COPY_DATA(pbBuf, cbLeft, &CfgDesc, VUSB_DT_CONFIG_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvMore, pCfgDesc->Core.bLength - VUSB_DT_CONFIG_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvClass, pCfgDesc->cbClass); + + /* + * Copy out all the interfaces for this configuration + */ + for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++) + { + PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i]; + for (uint32_t j = 0; j < pIf->cSettings; j++) + { + PCVUSBDESCINTERFACEEX pIfDesc = &pIf->paSettings[j]; + + COPY_DATA(pbBuf, cbLeft, pIfDesc->pIAD, pIfDesc->cbIAD); + COPY_DATA(pbBuf, cbLeft, pIfDesc, VUSB_DT_INTERFACE_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->pvMore, pIfDesc->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->pvClass, pIfDesc->cbClass); + + /* + * Copy out all the endpoints for this interface + */ + for (unsigned k = 0; k < pIfDesc->Core.bNumEndpoints; k++) + { + VUSBDESCENDPOINT EndPtDesc; + memcpy(&EndPtDesc, &pIfDesc->paEndpoints[k], VUSB_DT_ENDPOINT_MIN_LEN); + EndPtDesc.wMaxPacketSize = RT_H2LE_U16(EndPtDesc.wMaxPacketSize); + + COPY_DATA(pbBuf, cbLeft, &EndPtDesc, VUSB_DT_ENDPOINT_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvMore, EndPtDesc.bLength - VUSB_DT_ENDPOINT_MIN_LEN); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvSsepc, pIfDesc->paEndpoints[k].cbSsepc); + COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvClass, pIfDesc->paEndpoints[k].cbClass); + } + } + } + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + +/** + * Internal function which performs a descriptor read on the cached descriptors. + */ +static void ReadCachedDeviceDesc(PCVUSBDESCDEVICE pDevDesc, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + uint32_t cbLeft = *pcbBuf; + + /* + * Duplicate the device description and update some fields we keep in cpu type. + */ + Assert(sizeof(VUSBDESCDEVICE) == 18); + VUSBDESCDEVICE DevDesc = *pDevDesc; + DevDesc.bcdUSB = RT_H2LE_U16(DevDesc.bcdUSB); + DevDesc.idVendor = RT_H2LE_U16(DevDesc.idVendor); + DevDesc.idProduct = RT_H2LE_U16(DevDesc.idProduct); + DevDesc.bcdDevice = RT_H2LE_U16(DevDesc.bcdDevice); + + COPY_DATA(pbBuf, cbLeft, &DevDesc, sizeof(DevDesc)); + COPY_DATA(pbBuf, cbLeft, pDevDesc + 1, pDevDesc->bLength - sizeof(DevDesc)); + + /* updated the size of the output buffer. */ + *pcbBuf -= cbLeft; +} + +#undef COPY_DATA + +/** + * Standard device request: GET_DESCRIPTOR + * @returns success indicator. + * @remark not really used yet as we consider GET_DESCRIPTOR 'safe'. + */ +static bool vusbDevStdReqGetDescriptor(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf) +{ + RT_NOREF(EndPt); + if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE) + { + switch (pSetup->wValue >> 8) + { + case VUSB_DT_DEVICE: + ReadCachedDeviceDesc(pDev->pDescCache->pDevice, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of device descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + + case VUSB_DT_CONFIG: + { + unsigned int iIndex = (pSetup->wValue & 0xff); + if (iIndex >= pDev->pDescCache->pDevice->bNumConfigurations) + { + LogFlow(("vusbDevStdReqGetDescriptor: %s: iIndex=%p >= bNumConfigurations=%d !!!\n", + pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations)); + return false; + } + ReadCachedConfigDesc(&pDev->pDescCache->paConfigs[iIndex], pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of config descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + } + + case VUSB_DT_STRING: + { + if (pSetup->wIndex == 0) + { + ReadCachedLangIdDesc(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of language ID (string) descriptors\n", pDev->pUsbIns->pszName, *pcbBuf)); + return true; + } + PCPDMUSBDESCCACHESTRING pString; + pString = FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, + pSetup->wIndex, pSetup->wValue & 0xff); + if (pString) + { + ReadCachedStringDesc(pString, pbBuf, pcbBuf); + LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of string descriptors \"%s\"\n", + pDev->pUsbIns->pszName, *pcbBuf, pString->psz)); + return true; + } + break; + } + + default: + break; + } + } + Log(("vusb: %s: warning: unknown descriptor: type=%u descidx=%u lang=%u len=%u!!!\n", + pDev->pUsbIns->pszName, pSetup->wValue >> 8, pSetup->wValue & 0xff, pSetup->wIndex, pSetup->wLength)); + return false; +} + + +/** + * Service the standard USB requests. + * + * Devices may call this from controlmsg() if you want vusb core to handle your standard + * request, it's not necessary - you could handle them manually + * + * @param pDev The device. + * @param EndPoint The endpoint. + * @param pSetup Pointer to the setup request structure. + * @param pvBuf Buffer? + * @param pcbBuf ? + */ +bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPoint, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf) +{ + static bool (* const s_apfnStdReq[VUSB_REQ_MAX])(PVUSBDEV, int, PVUSBSETUP, uint8_t *, uint32_t *) = + { + vusbDevStdReqGetStatus, + vusbDevStdReqClearFeature, + NULL, + vusbDevStdReqSetFeature, + NULL, + vusbDevStdReqSetAddress, + vusbDevStdReqGetDescriptor, + NULL, + vusbDevStdReqGetConfig, + vusbDevStdReqSetConfig, + vusbDevStdReqGetInterface, + vusbDevStdReqSetInterface, + NULL /* for iso */ + }; + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: standard control message ignored, the device is resetting\n", pDev->pUsbIns->pszName)); + return false; + } + + /* + * Do the request if it's one we want to deal with. + */ + if ( pSetup->bRequest >= VUSB_REQ_MAX + || !s_apfnStdReq[pSetup->bRequest]) + { + Log(("vusb: warning: standard req not implemented: message %u: val=%u idx=%u len=%u !!!\n", + pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + return false; + } + + return s_apfnStdReq[pSetup->bRequest](pDev, EndPoint, pSetup, (uint8_t *)pvBuf, pcbBuf); +} + + +/** + * Add a device to the address hash + */ +static void vusbDevAddressHash(PVUSBDEV pDev) +{ + if (pDev->u8Address == VUSB_INVALID_ADDRESS) + return; + uint8_t u8Hash = vusbHashAddress(pDev->u8Address); + pDev->pNextHash = pDev->pHub->pRootHub->apAddrHash[u8Hash]; + pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev; +} + +/** + * Remove a device from the address hash + */ +static void vusbDevAddressUnHash(PVUSBDEV pDev) +{ + if (pDev->u8Address == VUSB_INVALID_ADDRESS) + return; + + uint8_t u8Hash = vusbHashAddress(pDev->u8Address); + pDev->u8Address = VUSB_INVALID_ADDRESS; + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + + RTCritSectEnter(&pDev->pHub->pRootHub->CritSectDevices); + PVUSBDEV pCur = pDev->pHub->pRootHub->apAddrHash[u8Hash]; + if (pCur == pDev) + { + /* special case, we're at the head */ + pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev->pNextHash; + pDev->pNextHash = NULL; + } + else + { + /* search the list */ + PVUSBDEV pPrev; + for (pPrev = pCur, pCur = pCur->pNextHash; + pCur; + pPrev = pCur, pCur = pCur->pNextHash) + { + if (pCur == pDev) + { + pPrev->pNextHash = pCur->pNextHash; + pDev->pNextHash = NULL; + break; + } + } + } + RTCritSectLeave(&pDev->pHub->pRootHub->CritSectDevices); +} + +/** + * Sets the address of a device. + * + * Called by status_completion() and vusbDevResetWorker(). + */ +void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address) +{ + LogFlow(("vusbDevSetAddress: pDev=%p[%s]/%i u8Address=%#x\n", + pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + VUSBDEV_ASSERT_VALID_STATE(enmState); + if ( enmState == VUSB_DEVICE_STATE_ATTACHED + || enmState == VUSB_DEVICE_STATE_DETACHED) + { + LogFlow(("vusbDevSetAddress: %s: fails because %d < POWERED\n", pDev->pUsbIns->pszName, pDev->enmState)); + return; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: set address ignored, the device is resetting\n", pDev->pUsbIns->pszName)); + return; + } + + /* + * Ok, get on with it. + */ + if (pDev->u8Address == u8Address) + return; + + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + AssertPtrReturnVoid(pRh); + if (pDev->u8Address == VUSB_DEFAULT_ADDRESS) + pRh->pDefaultAddress = NULL; + + vusbDevAddressUnHash(pDev); + + if (u8Address == VUSB_DEFAULT_ADDRESS) + { + if (pRh->pDefaultAddress != NULL) + { + vusbDevAddressUnHash(pRh->pDefaultAddress); + vusbDevSetStateCmp(pRh->pDefaultAddress, VUSB_DEVICE_STATE_POWERED, VUSB_DEVICE_STATE_DEFAULT); + Log(("2 DEFAULT ADDRS\n")); + } + + pRh->pDefaultAddress = pDev; + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); + } + else + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS); + + pDev->u8Address = u8Address; + vusbDevAddressHash(pDev); + + Log(("vusb: %p[%s]/%i: Assigned address %u\n", + pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address)); +} + + +static DECLCALLBACK(int) vusbDevCancelAllUrbsWorker(PVUSBDEV pDev, bool fDetaching) +{ + /* + * Iterate the URBs and cancel them. + */ + PVUSBURBVUSB pVUsbUrb, pVUsbUrbNext; + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + + Assert(pUrb->pVUsb->pDev == pDev); + + LogFlow(("%s: vusbDevCancelAllUrbs: CANCELING URB\n", pUrb->pszDesc)); + int rc = vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL); + AssertRC(rc); + } + + /* + * Reap any URBs which became ripe during cancel now. + */ + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + unsigned cReaped; + do + { + cReaped = 0; + pVUsbUrb = RTListGetFirst(&pDev->LstAsyncUrbs, VUSBURBVUSBINT, NdLst); + while (pVUsbUrb) + { + PVUSBURBVUSB pNext = RTListGetNext(&pDev->LstAsyncUrbs, pVUsbUrb, VUSBURBVUSBINT, NdLst); + PVUSBURB pUrb = pVUsbUrb->pUrb; + Assert(pUrb->pVUsb->pDev == pDev); + + PVUSBURB pRipe = NULL; + if (pUrb->enmState == VUSBURBSTATE_REAPED) + pRipe = pUrb; + else if (pUrb->enmState == VUSBURBSTATE_CANCELLED) +#ifdef RT_OS_WINDOWS /** @todo Windows doesn't do cancelling, thus this kludge to prevent really bad + * things from happening if we leave a pending URB behinds. */ + pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 1500 : 0 /*ms*/); +#else + pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 10 : 0 /*ms*/); +#endif + else + AssertMsgFailed(("pUrb=%p enmState=%d\n", pUrb, pUrb->enmState)); + if (pRipe) + { + if ( pNext + && pRipe == pNext->pUrb) + pNext = RTListGetNext(&pDev->LstAsyncUrbs, pNext, VUSBURBVUSBINT, NdLst); + vusbUrbRipe(pRipe); + cReaped++; + } + + pVUsbUrb = pNext; + } + } while (cReaped > 0); + + /* + * If we're detaching, we'll have to orphan any leftover URBs. + */ + if (fDetaching) + { + RTListForEachSafe(&pDev->LstAsyncUrbs, pVUsbUrb, pVUsbUrbNext, VUSBURBVUSBINT, NdLst) + { + PVUSBURB pUrb = pVUsbUrb->pUrb; + Assert(pUrb->pVUsb->pDev == pDev); + + AssertMsgFailed(("%s: Leaking left over URB! state=%d pDev=%p[%s]\n", + pUrb->pszDesc, pUrb->enmState, pDev, pDev->pUsbIns->pszName)); + vusbUrbUnlink(pUrb); + /* Unlink isn't enough, because boundary timer and detaching will try to reap it. + * It was tested with MSD & iphone attachment to vSMP guest, if + * it breaks anything, please add comment here, why we should unlink only. + */ + pUrb->pVUsb->pfnFree(pUrb); + } + } + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + return VINF_SUCCESS; +} + +/** + * Cancels and completes (with CRC failure) all async URBs pending + * on a device. This is typically done as part of a reset and + * before detaching a device. + * + * @returns nothing. + * @param pDev The VUSB device instance. + * @param fDetaching If set, we will unconditionally unlink (and leak) + * any URBs which isn't reaped. + */ +DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching) +{ + int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevCancelAllUrbsWorker, 2, pDev, fDetaching); + AssertRC(rc); +} + + +static DECLCALLBACK(int) vusbDevUrbIoThread(RTTHREAD hThread, void *pvUser) +{ + PVUSBDEV pDev = (PVUSBDEV)pvUser; + + /* Notify the starter that we are up and running. */ + RTThreadUserSignal(hThread); + + LogFlowFunc(("Entering work loop\n")); + + while (!ASMAtomicReadBool(&pDev->fTerminate)) + { + if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET) + vusbUrbDoReapAsyncDev(pDev, RT_INDEFINITE_WAIT); + + /* Process any URBs waiting to be cancelled first. */ + int rc = RTReqQueueProcess(pDev->hReqQueueSync, 0); /* Don't wait if there is nothing to do. */ + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc); + } + + return VINF_SUCCESS; +} + +int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev) +{ + ASMAtomicXchgBool(&pDev->fWokenUp, true); + return pDev->pUsbIns->pReg->pfnWakeup(pDev->pUsbIns); +} + +/** + * Create the URB I/O thread. + * + * @returns VBox status code. + * @param pDev The VUSB device. + */ +int vusbDevUrbIoThreadCreate(PVUSBDEV pDev) +{ + int rc = VINF_SUCCESS; + + ASMAtomicXchgBool(&pDev->fTerminate, false); + rc = RTThreadCreateF(&pDev->hUrbIoThread, vusbDevUrbIoThread, pDev, 0, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "USBDevIo-%d", pDev->i16Port); + if (RT_SUCCESS(rc)) + { + /* Wait for it to become active. */ + rc = RTThreadUserWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT); + } + + return rc; +} + +/** + * Destro the URB I/O thread. + * + * @returns VBox status code. + * @param pDev The VUSB device. + */ +int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev) +{ + int rc = VINF_SUCCESS; + int rcThread = VINF_SUCCESS; + + ASMAtomicXchgBool(&pDev->fTerminate, true); + vusbDevUrbIoThreadWakeup(pDev); + + rc = RTThreadWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc)) + rc = rcThread; + + pDev->hUrbIoThread = NIL_RTTHREAD; + + return rc; +} + + +/** + * Detaches a device from the hub it's attached to. + * + * @returns VBox status code. + * @param pDev The device to detach. + * + * @remark This can be called in any state but reset. + */ +int vusbDevDetach(PVUSBDEV pDev) +{ + LogFlow(("vusbDevDetach: pDev=%p[%s] enmState=%#x\n", pDev, pDev->pUsbIns->pszName, pDev->enmState)); + VUSBDEV_ASSERT_VALID_STATE(pDev->enmState); + Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET); + + vusbDevCancelAllUrbs(pDev, true); + vusbDevAddressUnHash(pDev); + + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + if (!pRh) + AssertMsgFailedReturn(("Not attached!\n"), VERR_VUSB_DEVICE_NOT_ATTACHED); + if (pRh->pDefaultAddress == pDev) + pRh->pDefaultAddress = NULL; + + pDev->pHub->pOps->pfnDetach(pDev->pHub, pDev); + pDev->i16Port = -1; + + /* + * Destroy I/O thread and request queue last because they might still be used + * when cancelling URBs. + */ + vusbDevUrbIoThreadDestroy(pDev); + + int rc = RTReqQueueDestroy(pDev->hReqQueueSync); + AssertRC(rc); + pDev->hReqQueueSync = NIL_RTREQQUEUE; + + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DETACHED); + pDev->pHub = NULL; + + /* Remove the configuration */ + pDev->pCurCfgDesc = NULL; + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + vusbDevResetPipeData(&pDev->aPipes[i]); + return VINF_SUCCESS; +} + + +/** + * Destroys a device, detaching it from the hub if necessary. + * + * @param pDev The device. + * @thread any. + */ +void vusbDevDestroy(PVUSBDEV pDev) +{ + LogFlow(("vusbDevDestroy: pDev=%p[%s] enmState=%d\n", pDev, pDev->pUsbIns->pszName, pDev->enmState)); + + RTMemFree(pDev->paIfStates); + TMR3TimerDestroy(pDev->pResetTimer); + pDev->pResetTimer = NULL; + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + { + Assert(pDev->aPipes[i].pCtrl == NULL); + RTCritSectDelete(&pDev->aPipes[i].CritSectCtrl); + } + + if (pDev->hSniffer != VUSBSNIFFER_NIL) + VUSBSnifferDestroy(pDev->hSniffer); + + vusbUrbPoolDestroy(&pDev->UrbPool); + + RTCritSectDelete(&pDev->CritSectAsyncUrbs); + /* Not using vusbDevSetState() deliberately here because it would assert on the state. */ + pDev->enmState = VUSB_DEVICE_STATE_DESTROYED; + pDev->pUsbIns->pvVUsbDev2 = NULL; + RTMemFree(pDev); +} + + +/* -=-=-=-=-=- VUSBIDEVICE methods -=-=-=-=-=- */ + + +/** + * The actual reset has been done, do completion on EMT. + * + * There are several things we have to do now, like set default + * config and address, and cleanup the state of control pipes. + * + * It's possible that the device has a delayed destroy request + * pending when we get here. This can happen for async resetting. + * We deal with it here, since we're now executing on the EMT + * thread and the destruction will be properly serialized now. + * + * @param pDev The device that is being reset. + * @param rc The vusbDevResetWorker return code. + * @param pfnDone The done callback specified by the caller of vusbDevReset(). + * @param pvUser The user argument for the callback. + */ +static void vusbDevResetDone(PVUSBDEV pDev, int rc, PFNVUSBRESETDONE pfnDone, void *pvUser) +{ + VUSBDEV_ASSERT_VALID_STATE(pDev->enmState); + Assert(pDev->enmState == VUSB_DEVICE_STATE_RESET); + + /* + * Do control pipe cleanup regardless of state and result. + */ + for (unsigned i = 0; i < VUSB_PIPE_MAX; i++) + if (pDev->aPipes[i].pCtrl) + vusbMsgResetExtraData(pDev->aPipes[i].pCtrl); + + /* + * Switch to the default state. + */ + vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT); + pDev->u16Status = 0; + vusbDevDoSelectConfig(pDev, &g_Config0); + if (!vusbDevIsRh(pDev)) + vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS); + if (pfnDone) + pfnDone(&pDev->IDevice, rc, pvUser); +} + + +/** + * Timer callback for doing reset completion. + * + * @param pUsbIns The USB device instance. + * @param pTimer The timer instance. + * @param pvUser The VUSB device data. + * @thread EMT + */ +static DECLCALLBACK(void) vusbDevResetDoneTimer(PPDMUSBINS pUsbIns, PTMTIMER pTimer, void *pvUser) +{ + RT_NOREF(pUsbIns, pTimer); + PVUSBDEV pDev = (PVUSBDEV)pvUser; + PVUSBRESETARGS pArgs = (PVUSBRESETARGS)pDev->pvArgs; + Assert(pDev->pUsbIns == pUsbIns); + + AssertPtr(pArgs); + + /* + * Reset-done processing and cleanup. + */ + pDev->pvArgs = NULL; + vusbDevResetDone(pDev, pArgs->rc, pArgs->pfnDone, pArgs->pvUser); + RTMemFree(pArgs); +} + + +/** + * Perform the actual reset. + * + * @thread EMT or a VUSB reset thread. + */ +static int vusbDevResetWorker(PVUSBDEV pDev, bool fResetOnLinux, bool fUseTimer, PVUSBRESETARGS pArgs) +{ + int rc = VINF_SUCCESS; + uint64_t u64EndTS = TMTimerGet(pDev->pResetTimer) + TMTimerFromMilli(pDev->pResetTimer, 10); + + if (pDev->pUsbIns->pReg->pfnUsbReset) + rc = pDev->pUsbIns->pReg->pfnUsbReset(pDev->pUsbIns, fResetOnLinux); + + if (pArgs) + { + pArgs->rc = rc; + rc = VINF_SUCCESS; + } + + if (fUseTimer) + { + /* + * We use a timer to communicate the result back to EMT. + * This avoids suspend + poweroff issues, and it should give + * us more accurate scheduling than making this thread sleep. + */ + int rc2 = TMTimerSet(pDev->pResetTimer, u64EndTS); + AssertReleaseRC(rc2); + } + + LogFlow(("vusbDevResetWorker: %s: returns %Rrc\n", pDev->pUsbIns->pszName, rc)); + return rc; +} + + +/** + * Resets a device. + * + * Since a device reset shall take at least 10ms from the guest point of view, + * it must be performed asynchronously. We create a thread which performs this + * operation and ensures it will take at least 10ms. + * + * At times - like init - a synchronous reset is required, this can be done + * by passing NULL for pfnDone. + * + * While the device is being reset it is in the VUSB_DEVICE_STATE_RESET state. + * On completion it will be in the VUSB_DEVICE_STATE_DEFAULT state if successful, + * or in the VUSB_DEVICE_STATE_DETACHED state if the rest failed. + * + * @returns VBox status code. + * + * @param pDevice Pointer to the VUSB device interface. + * @param fResetOnLinux Whether it's safe to reset the device(s) on a linux + * host system. See discussion of logical reconnects elsewhere. + * @param pfnDone Pointer to the completion routine. If NULL a synchronous + * reset is preformed not respecting the 10ms. + * @param pvUser Opaque user data to pass to the done callback. + * @param pVM Pointer to the VM handle for performing the done function + * on the EMT thread. + * @thread EMT + */ +static DECLCALLBACK(int) vusbIDeviceReset(PVUSBIDEVICE pDevice, bool fResetOnLinux, + PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM) +{ + RT_NOREF(pVM); + PVUSBDEV pDev = (PVUSBDEV)pDevice; + Assert(!pfnDone || pVM); + LogFlow(("vusb: reset: [%s]/%i\n", pDev->pUsbIns->pszName, pDev->i16Port)); + + /* + * Only one reset operation at a time. + */ + const VUSBDEVICESTATE enmStateOld = vusbDevSetState(pDev, VUSB_DEVICE_STATE_RESET); + if (enmStateOld == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: reset request is ignored, the device is already resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + /* + * First, cancel all async URBs. + */ + vusbDevCancelAllUrbs(pDev, false); + + /* Async or sync? */ + if (pfnDone) + { + /* + * Async fashion. + */ + PVUSBRESETARGS pArgs = (PVUSBRESETARGS)RTMemTmpAlloc(sizeof(*pArgs)); + if (pArgs) + { + pArgs->pDev = pDev; + pArgs->pfnDone = pfnDone; + pArgs->pvUser = pvUser; + pArgs->rc = VINF_SUCCESS; + AssertPtrNull(pDev->pvArgs); + pDev->pvArgs = pArgs; + int rc = vusbDevIoThreadExec(pDev, 0 /* fFlags */, (PFNRT)vusbDevResetWorker, 4, pDev, fResetOnLinux, true, pArgs); + if (RT_SUCCESS(rc)) + return rc; + + RTMemTmpFree(pArgs); + } + /* fall back to sync on failure */ + } + + /* + * Sync fashion. + */ + int rc = vusbDevResetWorker(pDev, fResetOnLinux, false, NULL); + vusbDevResetDone(pDev, rc, pfnDone, pvUser); + return rc; +} + + +/** + * Powers on the device. + * + * @returns VBox status code. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(int) vusbIDevicePowerOn(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + LogFlow(("vusbDevPowerOn: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DETACHED) + { + Log(("vusb: warning: attempt to power on detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power on ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + /* + * Do the job. + */ + if (enmState == VUSB_DEVICE_STATE_ATTACHED) + vusbDevSetState(pDev, VUSB_DEVICE_STATE_POWERED); + + return VINF_SUCCESS; +} + + +/** + * Powers off the device. + * + * @returns VBox status code. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(int) vusbIDevicePowerOff(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + LogFlow(("vusbDevPowerOff: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName)); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_DETACHED) + { + Log(("vusb: warning: attempt to power off detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + return VERR_VUSB_DEVICE_IS_RESETTING; + } + + /* + * If it's a root hub, we will have to cancel all URBs and reap them. + */ + if (vusbDevIsRh(pDev)) + { + PVUSBROOTHUB pRh = (PVUSBROOTHUB)pDev; + VUSBIRhCancelAllUrbs(&pRh->IRhConnector); + VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, pInterface, 0); + } + + vusbDevSetState(pDev, VUSB_DEVICE_STATE_ATTACHED); + return VINF_SUCCESS; +} + + +/** + * Get the state of the device. + * + * @returns Device state. + * @param pInterface Pointer to the device interface structure. + */ +static DECLCALLBACK(VUSBDEVICESTATE) vusbIDeviceGetState(PVUSBIDEVICE pInterface) +{ + return vusbDevGetState((PVUSBDEV)pInterface); +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnIsSavedStateSupported} + */ +static DECLCALLBACK(bool) vusbIDeviceIsSavedStateSupported(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + bool fSavedStateSupported = RT_BOOL(pDev->pUsbIns->pReg->fFlags & PDM_USBREG_SAVED_STATE_SUPPORTED); + + LogFlowFunc(("pInterface=%p\n", pInterface)); + + LogFlowFunc(("returns %RTbool\n", fSavedStateSupported)); + return fSavedStateSupported; +} + + +/** + * @interface_method_impl{VUSBIDEVICE,pfnGetState} + */ +static DECLCALLBACK(VUSBSPEED) vusbIDeviceGetSpeed(PVUSBIDEVICE pInterface) +{ + PVUSBDEV pDev = (PVUSBDEV)pInterface; + VUSBSPEED enmSpeed = pDev->pUsbIns->enmSpeed; + + LogFlowFunc(("pInterface=%p, returns %u\n", pInterface, enmSpeed)); + return enmSpeed; +} + + +/** + * The maximum number of interfaces the device can have in all of it's configuration. + * + * @returns Number of interfaces. + * @param pDev The device. + */ +size_t vusbDevMaxInterfaces(PVUSBDEV pDev) +{ + uint8_t cMax = 0; + unsigned i = pDev->pDescCache->pDevice->bNumConfigurations; + while (i-- > 0) + { + if (pDev->pDescCache->paConfigs[i].Core.bNumInterfaces > cMax) + cMax = pDev->pDescCache->paConfigs[i].Core.bNumInterfaces; + } + + return cMax; +} + + +/** + * Executes a given function on the I/O thread. + * + * @returns IPRT status code. + * @param pDev The USB device instance data. + * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_* + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param Args The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args) +{ + int rc = VINF_SUCCESS; + PRTREQ hReq = NULL; + + Assert(pDev->hUrbIoThread != NIL_RTTHREAD); + if (RT_LIKELY(pDev->hUrbIoThread != NIL_RTTHREAD)) + { + uint32_t fReqFlags = RTREQFLAGS_IPRT_STATUS; + + if (!(fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + fReqFlags |= RTREQFLAGS_NO_WAIT; + + rc = RTReqQueueCallV(pDev->hReqQueueSync, &hReq, 0 /* cMillies */, fReqFlags, pfnFunction, cArgs, Args); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); + + /* In case we are called on the I/O thread just process the request. */ + if ( pDev->hUrbIoThread == RTThreadSelf() + && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + { + int rc2 = RTReqQueueProcess(pDev->hReqQueueSync, 0); + Assert(RT_SUCCESS(rc2) || rc2 == VERR_TIMEOUT); NOREF(rc2); + } + else + vusbDevUrbIoThreadWakeup(pDev); + + if ( rc == VERR_TIMEOUT + && (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC)) + { + rc = RTReqWait(hReq, RT_INDEFINITE_WAIT); + AssertRC(rc); + } + RTReqRelease(hReq); + } + else + rc = VERR_INVALID_STATE; + + return rc; +} + + +/** + * Executes a given function on the I/O thread. + * + * @returns IPRT status code. + * @param pDev The USB device instance data. + * @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_* + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param ... The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...) +{ + int rc = VINF_SUCCESS; + va_list va; + + va_start(va, cArgs); + rc = vusbDevIoThreadExecV(pDev, fFlags, pfnFunction, cArgs, va); + va_end(va); + return rc; +} + + +/** + * Executes a given function synchronously on the I/O thread waiting for it to complete. + * + * @returns IPRT status code. + * @param pDev The USB device instance data + * @param pfnFunction The function to execute. + * @param cArgs Number of arguments to the function. + * @param ... The parameter list. + * + * @remarks See remarks on RTReqQueueCallV + */ +DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...) +{ + int rc = VINF_SUCCESS; + va_list va; + + va_start(va, cArgs); + rc = vusbDevIoThreadExecV(pDev, VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC, pfnFunction, cArgs, va); + va_end(va); + return rc; +} + + +static DECLCALLBACK(int) vusbDevGetDescriptorCacheWorker(PPDMUSBINS pUsbIns, PCPDMUSBDESCCACHE *ppDescCache) +{ + *ppDescCache = pUsbIns->pReg->pfnUsbGetDescriptorCache(pUsbIns); + return VINF_SUCCESS; +} + +/** + * Initialize a new VUSB device. + * + * @returns VBox status code. + * @param pDev The VUSB device to initialize. + * @param pUsbIns Pointer to the PDM USB Device instance. + * @param pszCaptureFilename Optional fileame to capture the traffic to. + */ +int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename) +{ + /* + * Initialize the device data members. + * (All that are Non-Zero at least.) + */ + Assert(!pDev->IDevice.pfnReset); + Assert(!pDev->IDevice.pfnPowerOn); + Assert(!pDev->IDevice.pfnPowerOff); + Assert(!pDev->IDevice.pfnGetState); + Assert(!pDev->IDevice.pfnIsSavedStateSupported); + + pDev->IDevice.pfnReset = vusbIDeviceReset; + pDev->IDevice.pfnPowerOn = vusbIDevicePowerOn; + pDev->IDevice.pfnPowerOff = vusbIDevicePowerOff; + pDev->IDevice.pfnGetState = vusbIDeviceGetState; + pDev->IDevice.pfnIsSavedStateSupported = vusbIDeviceIsSavedStateSupported; + pDev->IDevice.pfnGetSpeed = vusbIDeviceGetSpeed; + pDev->pUsbIns = pUsbIns; + pDev->pNext = NULL; + pDev->pNextHash = NULL; + pDev->pHub = NULL; + pDev->enmState = VUSB_DEVICE_STATE_DETACHED; + pDev->cRefs = 1; + pDev->u8Address = VUSB_INVALID_ADDRESS; + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + pDev->i16Port = -1; + pDev->u16Status = 0; + pDev->pDescCache = NULL; + pDev->pCurCfgDesc = NULL; + pDev->paIfStates = NULL; + RTListInit(&pDev->LstAsyncUrbs); + memset(&pDev->aPipes[0], 0, sizeof(pDev->aPipes)); + for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++) + { + int rc = RTCritSectInit(&pDev->aPipes[i].CritSectCtrl); + AssertRCReturn(rc, rc); + } + pDev->pResetTimer = NULL; + pDev->hSniffer = VUSBSNIFFER_NIL; + + int rc = RTCritSectInit(&pDev->CritSectAsyncUrbs); + AssertRCReturn(rc, rc); + + /* Create the URB pool. */ + rc = vusbUrbPoolInit(&pDev->UrbPool); + AssertRCReturn(rc, rc); + + /* Setup request queue executing synchronous tasks on the I/O thread. */ + rc = RTReqQueueCreate(&pDev->hReqQueueSync); + AssertRCReturn(rc, rc); + + /* Create I/O thread. */ + rc = vusbDevUrbIoThreadCreate(pDev); + AssertRCReturn(rc, rc); + + /* + * Create the reset timer. + */ + rc = PDMUsbHlpTMTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/, + "USB Device Reset Timer", &pDev->pResetTimer); + AssertRCReturn(rc, rc); + + if (pszCaptureFilename) + { + rc = VUSBSnifferCreate(&pDev->hSniffer, 0, pszCaptureFilename, NULL, NULL); + AssertRCReturn(rc, rc); + } + + /* + * Get the descriptor cache from the device. (shall cannot fail) + */ + rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevGetDescriptorCacheWorker, 2, pUsbIns, &pDev->pDescCache); + AssertRC(rc); + AssertPtr(pDev->pDescCache); +#ifdef VBOX_STRICT + if (pDev->pDescCache->fUseCachedStringsDescriptors) + { + int32_t iPrevId = -1; + for (unsigned iLang = 0; iLang < pDev->pDescCache->cLanguages; iLang++) + { + Assert((int32_t)pDev->pDescCache->paLanguages[iLang].idLang > iPrevId); + iPrevId = pDev->pDescCache->paLanguages[iLang].idLang; + + int32_t idxPrevStr = -1; + PCPDMUSBDESCCACHESTRING paStrings = pDev->pDescCache->paLanguages[iLang].paStrings; + unsigned cStrings = pDev->pDescCache->paLanguages[iLang].cStrings; + for (unsigned iStr = 0; iStr < cStrings; iStr++) + { + Assert((int32_t)paStrings[iStr].idx > idxPrevStr); + idxPrevStr = paStrings[iStr].idx; + size_t cch = strlen(paStrings[iStr].psz); + Assert(cch <= 127); + } + } + } +#endif + + /* + * Allocate memory for the interface states. + */ + size_t cbIface = vusbDevMaxInterfaces(pDev) * sizeof(*pDev->paIfStates); + pDev->paIfStates = (PVUSBINTERFACESTATE)RTMemAllocZ(cbIface); + AssertMsgReturn(pDev->paIfStates, ("RTMemAllocZ(%d) failed\n", cbIface), VERR_NO_MEMORY); + + return VINF_SUCCESS; +} + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/VUSBInternal.h b/src/VBox/Devices/USB/VUSBInternal.h new file mode 100644 index 00000000..1ec05db8 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBInternal.h @@ -0,0 +1,747 @@ +/* $Id: VUSBInternal.h $ */ +/** @file + * Virtual USB - Internal header. + * + * This subsystem implements USB devices in a host controller independent + * way. All the host controller code has to do is use VUSBHUB for its + * root hub implementation and any emulated USB device may be plugged into + * the virtual bus. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBInternal_h +#define VBOX_INCLUDED_SRC_USB_VUSBInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/vusb.h> +#include <VBox/vmm/stam.h> +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/vmm/pdmusb.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/req.h> +#include <iprt/asm.h> +#include <iprt/list.h> + +#include "VUSBSniffer.h" + +RT_C_DECLS_BEGIN + + +/** @name Internal Device Operations, Structures and Constants. + * @{ + */ + +/** Pointer to a Virtual USB device (core). */ +typedef struct VUSBDEV *PVUSBDEV; +/** Pointer to a VUSB hub device. */ +typedef struct VUSBHUB *PVUSBHUB; +/** Pointer to a VUSB root hub. */ +typedef struct VUSBROOTHUB *PVUSBROOTHUB; + + +/** Number of the default control endpoint */ +#define VUSB_PIPE_DEFAULT 0 + +/** @name Device addresses + * @{ */ +#define VUSB_DEFAULT_ADDRESS 0 +#define VUSB_INVALID_ADDRESS UINT8_C(0xff) +/** @} */ + +/** @name Feature bits (1<<FEATURE for the u16Status bit) + * @{ */ +#define VUSB_DEV_SELF_POWERED 0 +#define VUSB_DEV_REMOTE_WAKEUP 1 +#define VUSB_EP_HALT 0 +/** @} */ + +/** Maximum number of endpoint addresses */ +#define VUSB_PIPE_MAX 16 + +/** + * The VUSB URB data. + */ +typedef struct VUSBURBVUSBINT +{ + /** Node for one of the lists the URB can be in. */ + RTLISTNODE NdLst; + /** Pointer to the URB this structure is part of. */ + PVUSBURB pUrb; + /** Pointer to the original for control messages. */ + PVUSBURB pCtrlUrb; + /** Pointer to the VUSB device. + * This may be NULL if the destination address is invalid. */ + PVUSBDEV pDev; + /** Specific to the pfnFree function. */ + void *pvFreeCtx; + /** + * Callback which will free the URB once it's reaped and completed. + * @param pUrb The URB. + */ + DECLCALLBACKMEMBER(void, pfnFree)(PVUSBURB pUrb); + /** Submit timestamp. (logging only) */ + uint64_t u64SubmitTS; +} VUSBURBVUSBINT; + +/** + * Control-pipe stages. + */ +typedef enum CTLSTAGE +{ + /** the control pipe is in the setup stage. */ + CTLSTAGE_SETUP = 0, + /** the control pipe is in the data stage. */ + CTLSTAGE_DATA, + /** the control pipe is in the status stage. */ + CTLSTAGE_STATUS +} CTLSTAGE; + +/** + * Extra data for a control pipe. + * + * This is state information needed for the special multi-stage + * transfers performed on this kind of pipes. + */ +typedef struct vusb_ctrl_extra +{ + /** Current pipe stage. */ + CTLSTAGE enmStage; + /** Success indicator. */ + bool fOk; + /** Set if the message URB has been submitted. */ + bool fSubmitted; + /** Pointer to the SETUP. + * This is a pointer to Urb->abData[0]. */ + PVUSBSETUP pMsg; + /** Current DATA pointer. + * This starts at pMsg + 1 and is incremented at we read/write data. */ + uint8_t *pbCur; + /** The amount of data left to read on IN operations. + * On OUT operations this is not used. */ + uint32_t cbLeft; + /** The amount of data we can house. + * This starts at the default 8KB, and this structure will be reallocated to + * accommodate any larger request (unlikely). */ + uint32_t cbMax; + /** The message URB. */ + VUSBURB Urb; +} VUSBCTRLEXTRA, *PVUSBCTRLEXTRA; + +void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra); +void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra); + +/** + * A VUSB pipe + */ +typedef struct vusb_pipe +{ + PCVUSBDESCENDPOINTEX in; + PCVUSBDESCENDPOINTEX out; + /** Pointer to the extra state data required to run a control pipe. */ + PVUSBCTRLEXTRA pCtrl; + /** Critical section serializing access to the extra state data for a control pipe. */ + RTCRITSECT CritSectCtrl; + /** Count of active async transfers. */ + volatile uint32_t async; + /** Last scheduled frame - only valid for isochronous IN endpoints. */ + uint32_t uLastFrameIn; + /** Last scheduled frame - only valid for isochronous OUT endpoints. */ + uint32_t uLastFrameOut; +} VUSBPIPE; +/** Pointer to a VUSB pipe structure. */ +typedef VUSBPIPE *PVUSBPIPE; + + +/** + * Interface state and possible settings. + */ +typedef struct vusb_interface_state +{ + /** Pointer to the interface descriptor of the currently selected (active) + * interface. */ + PCVUSBDESCINTERFACEEX pCurIfDesc; + /** Pointer to the interface settings. */ + PCVUSBINTERFACE pIf; +} VUSBINTERFACESTATE; +/** Pointer to interface state. */ +typedef VUSBINTERFACESTATE *PVUSBINTERFACESTATE; +/** Pointer to const interface state. */ +typedef const VUSBINTERFACESTATE *PCVUSBINTERFACESTATE; + + +/** + * VUSB URB pool. + */ +typedef struct VUSBURBPOOL +{ + /** Critical section protecting the pool. */ + RTCRITSECT CritSectPool; + /** Chain of free URBs by type. (Singly linked) */ + RTLISTANCHOR aLstFreeUrbs[VUSBXFERTYPE_ELEMENTS]; + /** The number of URBs in the pool. */ + volatile uint32_t cUrbsInPool; + /** Align the size to a 8 byte boundary. */ + uint32_t Alignment0; +} VUSBURBPOOL; +/** Pointer to a VUSB URB pool. */ +typedef VUSBURBPOOL *PVUSBURBPOOL; + +AssertCompileSizeAlignment(VUSBURBPOOL, 8); + +/** + * A Virtual USB device (core). + * + * @implements VUSBIDEVICE + */ +typedef struct VUSBDEV +{ + /** The device interface exposed to the HCI. */ + VUSBIDEVICE IDevice; + /** Pointer to the PDM USB device instance. */ + PPDMUSBINS pUsbIns; + /** Next device in the chain maintained by the roothub. */ + PVUSBDEV pNext; + /** Pointer to the next device with the same address hash. */ + PVUSBDEV pNextHash; + /** Pointer to the hub this device is attached to. */ + PVUSBHUB pHub; + /** The device state. */ + VUSBDEVICESTATE volatile enmState; + /** Reference counter to protect the device structure from going away. */ + uint32_t volatile cRefs; + + /** The device address. */ + uint8_t u8Address; + /** The new device address. */ + uint8_t u8NewAddress; + /** The port. */ + int16_t i16Port; + /** Device status. (VUSB_DEV_SELF_POWERED or not.) */ + uint16_t u16Status; + + /** Pointer to the descriptor cache. + * (Provided by the device thru the pfnGetDescriptorCache method.) */ + PCPDMUSBDESCCACHE pDescCache; + /** Current configuration. */ + PCVUSBDESCCONFIGEX pCurCfgDesc; + + /** Current interface state (including alternate interface setting) - maximum + * valid index is config->bNumInterfaces + */ + PVUSBINTERFACESTATE paIfStates; + + /** Pipe/direction -> endpoint descriptor mapping */ + VUSBPIPE aPipes[VUSB_PIPE_MAX]; + /** Critical section protecting the active URB list. */ + RTCRITSECT CritSectAsyncUrbs; + /** List of active async URBs. */ + RTLISTANCHOR LstAsyncUrbs; + + /** Dumper state. */ + union VUSBDEVURBDUMPERSTATE + { + /** The current scsi command. */ + uint8_t u8ScsiCmd; + } Urb; + + /** The reset timer handle. */ + PTMTIMER pResetTimer; + /** Reset handler arguments. */ + void *pvArgs; + /** URB submit and reap thread. */ + RTTHREAD hUrbIoThread; + /** Request queue for executing tasks on the I/O thread which should be done + * synchronous and without any other thread accessing the USB device. */ + RTREQQUEUE hReqQueueSync; + /** Sniffer instance for this device if configured. */ + VUSBSNIFFER hSniffer; + /** Flag whether the URB I/O thread should terminate. */ + bool volatile fTerminate; + /** Flag whether the I/O thread was woken up. */ + bool volatile fWokenUp; +#if HC_ARCH_BITS == 32 + /** Align the size to a 8 byte boundary. */ + bool afAlignment0[2]; +#endif + /** The pool of free URBs for faster allocation. */ + VUSBURBPOOL UrbPool; +} VUSBDEV; +AssertCompileSizeAlignment(VUSBDEV, 8); + + +int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename); +void vusbDevDestroy(PVUSBDEV pDev); + +DECLINLINE(bool) vusbDevIsRh(PVUSBDEV pDev) +{ + return (pDev->pHub == (PVUSBHUB)pDev); +} + +bool vusbDevDoSelectConfig(PVUSBDEV dev, PCVUSBDESCCONFIGEX pCfg); +void vusbDevMapEndpoint(PVUSBDEV dev, PCVUSBDESCENDPOINTEX ep); +int vusbDevDetach(PVUSBDEV pDev); +DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev); +size_t vusbDevMaxInterfaces(PVUSBDEV dev); + +void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address); +bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf); + + +/** @} */ + + +/** @name Internal Hub Operations, Structures and Constants. + * @{ + */ + + +/** Virtual method table for USB hub devices. + * Hub and roothub drivers need to implement these functions in addition to the + * vusb_dev_ops. + */ +typedef struct VUSBHUBOPS +{ + int (*pfnAttach)(PVUSBHUB pHub, PVUSBDEV pDev); + void (*pfnDetach)(PVUSBHUB pHub, PVUSBDEV pDev); +} VUSBHUBOPS; +/** Pointer to a const HUB method table. */ +typedef const VUSBHUBOPS *PCVUSBHUBOPS; + +/** A VUSB Hub Device - Hub and roothub drivers need to use this struct + * @todo eliminate this (PDM / roothubs only). + */ +typedef struct VUSBHUB +{ + VUSBDEV Dev; + PCVUSBHUBOPS pOps; + PVUSBROOTHUB pRootHub; + uint16_t cPorts; + uint16_t cDevices; + /** Name of the hub. Used for logging. */ + char *pszName; +} VUSBHUB; +AssertCompileMemberAlignment(VUSBHUB, pOps, 8); +AssertCompileSizeAlignment(VUSBHUB, 8); + +/** @} */ + + +/** @name Internal Root Hub Operations, Structures and Constants. + * @{ + */ + +/** + * Per transfer type statistics. + */ +typedef struct VUSBROOTHUBTYPESTATS +{ + STAMCOUNTER StatUrbsSubmitted; + STAMCOUNTER StatUrbsFailed; + STAMCOUNTER StatUrbsCancelled; + + STAMCOUNTER StatReqBytes; + STAMCOUNTER StatReqReadBytes; + STAMCOUNTER StatReqWriteBytes; + + STAMCOUNTER StatActBytes; + STAMCOUNTER StatActReadBytes; + STAMCOUNTER StatActWriteBytes; +} VUSBROOTHUBTYPESTATS, *PVUSBROOTHUBTYPESTATS; + + + +/** The address hash table size. */ +#define VUSB_ADDR_HASHSZ 5 + +/** + * The instance data of a root hub driver. + * + * This extends the generic VUSB hub. + * + * @implements VUSBIROOTHUBCONNECTOR + */ +typedef struct VUSBROOTHUB +{ + /** The HUB. + * @todo remove this? */ + VUSBHUB Hub; + /** Address hash table. */ + PVUSBDEV apAddrHash[VUSB_ADDR_HASHSZ]; + /** The default address. */ + PVUSBDEV pDefaultAddress; + + /** Pointer to the driver instance. */ + PPDMDRVINS pDrvIns; + /** Pointer to the root hub port interface we're attached to. */ + PVUSBIROOTHUBPORT pIRhPort; + /** Connector interface exposed upwards. */ + VUSBIROOTHUBCONNECTOR IRhConnector; + + /** Critical section protecting the device list. */ + RTCRITSECT CritSectDevices; + /** Chain of devices attached to this hub. */ + PVUSBDEV pDevices; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment0; +#endif + + /** Availability Bitmap. */ + VUSBPORTBITMAP Bitmap; + + /** Sniffer instance for the root hub. */ + VUSBSNIFFER hSniffer; + /** Version of the attached Host Controller. */ + uint32_t fHcVersions; + /** Size of the HCI specific data for each URB. */ + size_t cbHci; + /** Size of the HCI specific TD. */ + size_t cbHciTd; + + /** The periodic frame processing thread. */ + R3PTRTYPE(PPDMTHREAD) hThreadPeriodFrame; + /** Event semaphore to interact with the periodic frame processing thread. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrame; + /** Event semaphore to release the thread waiting for the periodic frame processing thread to stop. */ + R3PTRTYPE(RTSEMEVENTMULTI) hSemEventPeriodFrameStopped; + /** Current default frame rate for periodic frame processing thread. */ + volatile uint32_t uFrameRateDefault; + /** Current frame rate (can be lower than the default frame rate if there is no activity). */ + uint32_t uFrameRate; + /** How long to wait until the next frame. */ + uint64_t nsWait; + /** Timestamp when the last frame was processed. */ + uint64_t tsFrameProcessed; + /** Number of USB work cycles with no transfers. */ + uint32_t cIdleCycles; + + /** Flag whether a frame is currently being processed. */ + volatile bool fFrameProcessing; + +#if HC_ARCH_BITS == 32 + uint32_t Alignment1; +#endif + +#ifdef LOG_ENABLED + /** A serial number for URBs submitted on the roothub instance. + * Only logging builds. */ + uint32_t iSerial; + /** Alignment */ + uint32_t Alignment2; +#endif +#ifdef VBOX_WITH_STATISTICS + VUSBROOTHUBTYPESTATS Total; + VUSBROOTHUBTYPESTATS aTypes[VUSBXFERTYPE_MSG]; + STAMCOUNTER StatIsocReqPkts; + STAMCOUNTER StatIsocReqReadPkts; + STAMCOUNTER StatIsocReqWritePkts; + STAMCOUNTER StatIsocActPkts; + STAMCOUNTER StatIsocActReadPkts; + STAMCOUNTER StatIsocActWritePkts; + struct + { + STAMCOUNTER Pkts; + STAMCOUNTER Ok; + STAMCOUNTER Ok0; + STAMCOUNTER DataUnderrun; + STAMCOUNTER DataUnderrun0; + STAMCOUNTER DataOverrun; + STAMCOUNTER NotAccessed; + STAMCOUNTER Misc; + STAMCOUNTER Bytes; + } aStatIsocDetails[8]; + + STAMPROFILE StatReapAsyncUrbs; + STAMPROFILE StatSubmitUrb; + STAMCOUNTER StatFramesProcessedClbk; + STAMCOUNTER StatFramesProcessedThread; +#endif +} VUSBROOTHUB; +AssertCompileMemberAlignment(VUSBROOTHUB, IRhConnector, 8); +AssertCompileMemberAlignment(VUSBROOTHUB, Bitmap, 8); +AssertCompileMemberAlignment(VUSBROOTHUB, CritSectDevices, 8); +#ifdef VBOX_WITH_STATISTICS +AssertCompileMemberAlignment(VUSBROOTHUB, Total, 8); +#endif + +/** Converts a pointer to VUSBROOTHUB::IRhConnector to a PVUSBROOTHUB. */ +#define VUSBIROOTHUBCONNECTOR_2_VUSBROOTHUB(pInterface) (PVUSBROOTHUB)( (uintptr_t)(pInterface) - RT_UOFFSETOF(VUSBROOTHUB, IRhConnector) ) + +/** + * URB cancellation modes + */ +typedef enum CANCELMODE +{ + /** complete the URB with an error (CRC). */ + CANCELMODE_FAIL = 0, + /** do not change the URB contents. */ + CANCELMODE_UNDO +} CANCELMODE; + +/* @} */ + + + +/** @name Internal URB Operations, Structures and Constants. + * @{ */ +int vusbUrbSubmit(PVUSBURB pUrb); +void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies); +void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies); +void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode); +void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode); +void vusbUrbRipe(PVUSBURB pUrb); +void vusbUrbCompletionRh(PVUSBURB pUrb); +int vusbUrbSubmitHardError(PVUSBURB pUrb); +int vusbUrbErrorRh(PVUSBURB pUrb); +int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev); +int vusbDevUrbIoThreadCreate(PVUSBDEV pDev); +int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev); +DECLHIDDEN(void) vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching); +DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args); +DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...); +DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...); +DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode); + +DECLHIDDEN(uint64_t) vusbRhR3ProcessFrame(PVUSBROOTHUB pThis, bool fCallback); + +int vusbUrbQueueAsyncRh(PVUSBURB pUrb); + +/** + * Initializes the given URB pool. + * + * @returns VBox status code. + * @param pUrbPool The URB pool to initialize. + */ +DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool); + +/** + * Destroy a given URB pool freeing all ressources. + * + * @returns nothing. + * @param pUrbPool The URB pool to destroy. + */ +DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool); + +/** + * Allocate a new URB from the given URB pool. + * + * @returns Pointer to the new URB or NULL if out of memory. + * @param pUrbPool The URB pool to allocate from. + * @param enmType Type of the URB. + * @param enmDir The direction of the URB. + * @param cbData The number of bytes to allocate for the data buffer. + * @param cbHci Size of the data private to the HCI for each URB when allocated. + * @param cbHciTd Size of one transfer descriptor. + * @param cTds Number of transfer descriptors. + */ +DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, size_t cbData, + size_t cbHci, size_t cbHciTd, unsigned cTds); + +/** + * Frees a given URB. + * + * @returns nothing. + * @param pUrbPool The URB pool the URB was allocated from. + * @param pUrb The URB to free. + */ +DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb); + +#ifdef LOG_ENABLED +/** + * Logs an URB in the debug log. + * + * @returns nothing. + * @param pUrb The URB to log. + * @param pszMsg Additional message to log. + * @param fComplete Flag whther the URB is completing. + */ +DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete); + +/** + * Return the USB direction as a string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir); + +/** + * Return the URB type as string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType); + +/** + * Return the URB status as string from the given enum. + */ +DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus); +#endif + +DECLINLINE(void) vusbUrbUnlink(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + RTListNodeRemove(&pUrb->pVUsb->NdLst); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); +} + +/** @def vusbUrbAssert + * Asserts that a URB is valid. + */ +#ifdef VBOX_STRICT +# define vusbUrbAssert(pUrb) do { \ + AssertMsg(VALID_PTR((pUrb)), ("%p\n", (pUrb))); \ + AssertMsg((pUrb)->u32Magic == VUSBURB_MAGIC, ("%#x", (pUrb)->u32Magic)); \ + AssertMsg((pUrb)->enmState > VUSBURBSTATE_INVALID && (pUrb)->enmState < VUSBURBSTATE_END, \ + ("%d\n", (pUrb)->enmState)); \ + } while (0) +#else +# define vusbUrbAssert(pUrb) do {} while (0) +#endif + +/** + * @def VUSBDEV_ASSERT_VALID_STATE + * Asserts that the give device state is valid. + */ +#define VUSBDEV_ASSERT_VALID_STATE(enmState) \ + AssertMsg((enmState) > VUSB_DEVICE_STATE_INVALID && (enmState) < VUSB_DEVICE_STATE_DESTROYED, ("enmState=%#x\n", enmState)); + +/** Executes a function synchronously. */ +#define VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC RT_BIT_32(0) + +/** @} */ + + + + +/** + * Addresses are between 0 and 127 inclusive + */ +DECLINLINE(uint8_t) vusbHashAddress(uint8_t Address) +{ + uint8_t u8Hash = Address; + u8Hash ^= (Address >> 2); + u8Hash ^= (Address >> 3); + u8Hash %= VUSB_ADDR_HASHSZ; + return u8Hash; +} + + +/** + * Gets the roothub of a device. + * + * @returns Pointer to the roothub instance the device is attached to. + * @returns NULL if not attached to any hub. + * @param pDev Pointer to the device in question. + */ +DECLINLINE(PVUSBROOTHUB) vusbDevGetRh(PVUSBDEV pDev) +{ + if (!pDev->pHub) + return NULL; + return pDev->pHub->pRootHub; +} + + +/** + * Returns the state of the USB device. + * + * @returns State of the USB device. + * @param pDev Pointer to the device. + */ +DECLINLINE(VUSBDEVICESTATE) vusbDevGetState(PVUSBDEV pDev) +{ + VUSBDEVICESTATE enmState = (VUSBDEVICESTATE)ASMAtomicReadU32((volatile uint32_t *)&pDev->enmState); + VUSBDEV_ASSERT_VALID_STATE(enmState); + return enmState; +} + + +/** + * Sets the given state for the USB device. + * + * @returns The old state of the device. + * @param pDev Pointer to the device. + * @param enmState The new state to set. + */ +DECLINLINE(VUSBDEVICESTATE) vusbDevSetState(PVUSBDEV pDev, VUSBDEVICESTATE enmState) +{ + VUSBDEV_ASSERT_VALID_STATE(enmState); + VUSBDEVICESTATE enmStateOld = (VUSBDEVICESTATE)ASMAtomicXchgU32((volatile uint32_t *)&pDev->enmState, enmState); + VUSBDEV_ASSERT_VALID_STATE(enmStateOld); + return enmStateOld; +} + + +/** + * Compare and exchange the states for the given USB device. + * + * @returns true if the state was changed. + * @returns false if the state wasn't changed. + * @param pDev Pointer to the device. + * @param enmStateNew The new state to set. + * @param enmStateOld The old state to compare with. + */ +DECLINLINE(bool) vusbDevSetStateCmp(PVUSBDEV pDev, VUSBDEVICESTATE enmStateNew, VUSBDEVICESTATE enmStateOld) +{ + VUSBDEV_ASSERT_VALID_STATE(enmStateNew); + VUSBDEV_ASSERT_VALID_STATE(enmStateOld); + return ASMAtomicCmpXchgU32((volatile uint32_t *)&pDev->enmState, enmStateNew, enmStateOld); +} + +/** + * Retains the given VUSB device pointer. + * + * @returns New reference count. + * @param pThis The VUSB device pointer. + */ +DECLINLINE(uint32_t) vusbDevRetain(PVUSBDEV pThis) +{ + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicIncU32(&pThis->cRefs); + AssertMsg(cRefs > 1 && cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + return cRefs; +} + +/** + * Releases the given VUSB device pointer. + * + * @returns New reference count. + * @retval 0 if no onw is holding a reference anymore causing the device to be destroyed. + */ +DECLINLINE(uint32_t) vusbDevRelease(PVUSBDEV pThis) +{ + AssertPtrReturn(pThis, UINT32_MAX); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + AssertMsg(cRefs < _1M, ("%#x %p\n", cRefs, pThis)); + if (cRefs == 0) + vusbDevDestroy(pThis); + return cRefs; +} + +/** Strings for the CTLSTAGE enum values. */ +extern const char * const g_apszCtlStates[4]; +/** Default message pipe. */ +extern const VUSBDESCENDPOINTEX g_Endpoint0; +/** Default configuration. */ +extern const VUSBDESCCONFIGEX g_Config0; + +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBInternal_h */ + diff --git a/src/VBox/Devices/USB/VUSBSniffer.cpp b/src/VBox/Devices/USB/VUSBSniffer.cpp new file mode 100644 index 00000000..2661f9d6 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSniffer.cpp @@ -0,0 +1,237 @@ +/* $Id: VUSBSniffer.cpp $ */ +/** @file + * Virtual USB - Sniffer facility. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/file.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERINT +{ + /** The file handle to dump to. */ + RTFILE hFile; + /** Fast Mutex protecting the state against concurrent access. */ + RTSEMFASTMUTEX hMtx; + /** File stream. */ + VUSBSNIFFERSTRM Strm; + /** Pointer to the used format. */ + PCVUSBSNIFFERFMT pFmt; + /** Format specific state - variable in size. */ + uint8_t abFmt[1]; +} VUSBSNIFFERINT; +/** Pointer to the internal VUSB sniffer state. */ +typedef VUSBSNIFFERINT *PVUSBSNIFFERINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +static PCVUSBSNIFFERFMT s_aVUsbSnifferFmts[] = +{ + &g_VUsbSnifferFmtPcapNg, + &g_VUsbSnifferFmtUsbMon, + &g_VUsbSnifferFmtVmx, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** @interface_method_impl{VUSBSNIFFERSTRM,pfnWrite} */ +static DECLCALLBACK(int) vusbSnifferStrmWrite(PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf) +{ + PVUSBSNIFFERINT pThis = RT_FROM_MEMBER(pStrm, VUSBSNIFFERINT, Strm); + + return RTFileWrite(pThis->hFile, pvBuf, cbBuf, NULL); +} + +/** + * Returns a supporting format writer taken from the given format name. + * + * @returns Pointer to the format structure or NULL if none was found. + * @param pszFmt The format to use. + */ +static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromString(const char *pszFmt) +{ + for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++) + { + if (!RTStrICmp(pszFmt, s_aVUsbSnifferFmts[i]->szName)) + return s_aVUsbSnifferFmts[i]; + } + + return NULL; +} + +/** + * Returns a supporting format writer taken from the file suffix. + * + * @returns Pointer to the format structure or NULL if none was found. + * @param pszFilename The file name to take the suffix from. + */ +static PCVUSBSNIFFERFMT vusbSnifferGetFmtFromFilename(const char *pszFilename) +{ + const char *pszFileExt = RTPathSuffix(pszFilename); + if (!pszFileExt) + return NULL; + + pszFileExt++; /* Skip the dot. */ + + for (unsigned i = 0; i < RT_ELEMENTS(s_aVUsbSnifferFmts); i++) + { + unsigned idxFileExt = 0; + + while (s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt]) + { + if (!RTStrICmp(pszFileExt, s_aVUsbSnifferFmts[i]->papszFileExts[idxFileExt])) + return s_aVUsbSnifferFmts[i]; + + idxFileExt++; + } + } + + return NULL; +} + + +DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags, + const char *pszCaptureFilename, const char *pszFmt, + const char *pszDesc) +{ + RT_NOREF(pszDesc); + int rc = VINF_SUCCESS; + PVUSBSNIFFERINT pThis = NULL; + PCVUSBSNIFFERFMT pFmt = NULL; + + if (pszFmt) + pFmt = vusbSnifferGetFmtFromString(pszFmt); + else + pFmt = vusbSnifferGetFmtFromFilename(pszCaptureFilename); + + if (!pFmt) + return VERR_NOT_FOUND; + + pThis = (PVUSBSNIFFERINT)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBSNIFFERINT, abFmt[pFmt->cbFmt])); + if (pThis) + { + pThis->hFile = NIL_RTFILE; + pThis->hMtx = NIL_RTSEMFASTMUTEX; + pThis->pFmt = pFmt; + pThis->Strm.pfnWrite = vusbSnifferStrmWrite; + + rc = RTSemFastMutexCreate(&pThis->hMtx); + if (RT_SUCCESS(rc)) + { + uint32_t fFileFlags = RTFILE_O_DENY_NONE | RTFILE_O_WRITE | RTFILE_O_READ; + if (fFlags & VUSBSNIFFER_F_NO_REPLACE) + fFileFlags |= RTFILE_O_CREATE; + else + fFileFlags |= RTFILE_O_CREATE_REPLACE; + + rc = RTFileOpen(&pThis->hFile, pszCaptureFilename, fFileFlags); + if (RT_SUCCESS(rc)) + { + rc = pThis->pFmt->pfnInit((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], &pThis->Strm); + if (RT_SUCCESS(rc)) + { + *phSniffer = pThis; + return VINF_SUCCESS; + } + + RTFileClose(pThis->hFile); + pThis->hFile = NIL_RTFILE; + RTFileDelete(pszCaptureFilename); + } + RTSemFastMutexDestroy(pThis->hMtx); + pThis->hMtx = NIL_RTSEMFASTMUTEX; + } + + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Destroys the given VUSB sniffer instance. + * + * @returns nothing. + * @param hSniffer The sniffer instance to destroy. + */ +DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer) +{ + PVUSBSNIFFERINT pThis = hSniffer; + + int rc = RTSemFastMutexRequest(pThis->hMtx); + AssertRC(rc); + + pThis->pFmt->pfnDestroy((PVUSBSNIFFERFMTINT)&pThis->abFmt[0]); + + if (pThis->hFile != NIL_RTFILE) + RTFileClose(pThis->hFile); + + RTSemFastMutexRelease(pThis->hMtx); + RTSemFastMutexDestroy(pThis->hMtx); + RTMemFree(pThis); +} + +/** + * Records an VUSB event. + * + * @returns VBox status code. + * @param hSniffer The sniffer instance. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ +DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + int rc = VINF_SUCCESS; + PVUSBSNIFFERINT pThis = hSniffer; + + /* Write the packet to the capture file. */ + rc = RTSemFastMutexRequest(pThis->hMtx); + if (RT_SUCCESS(rc)) + { + rc = pThis->pFmt->pfnRecordEvent((PVUSBSNIFFERFMTINT)&pThis->abFmt[0], pUrb, enmEvent); + RTSemFastMutexRelease(pThis->hMtx); + } + + return rc; +} + diff --git a/src/VBox/Devices/USB/VUSBSniffer.h b/src/VBox/Devices/USB/VUSBSniffer.h new file mode 100644 index 00000000..7009f9cb --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSniffer.h @@ -0,0 +1,101 @@ +/* $Id: VUSBSniffer.h $ */ +/** @file + * Virtual USB - Sniffer facility. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBSniffer_h +#define VBOX_INCLUDED_SRC_USB_VUSBSniffer_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/vusb.h> + +RT_C_DECLS_BEGIN + +/** Opaque VUSB sniffer handle. */ +typedef struct VUSBSNIFFERINT *VUSBSNIFFER; +/** Pointer to a VUSB sniffer handle. */ +typedef VUSBSNIFFER *PVUSBSNIFFER; + +/** NIL sniffer instance handle. */ +#define VUSBSNIFFER_NIL ((VUSBSNIFFER)0) + +/** + * VUSB Sniffer event types. + */ +typedef enum VUSBSNIFFEREVENT +{ + /** Invalid event. */ + VUSBSNIFFEREVENT_INVALID = 0, + /** URB submit event. */ + VUSBSNIFFEREVENT_SUBMIT, + /** URB complete event. */ + VUSBSNIFFEREVENT_COMPLETE, + /** URB submit failed event. */ + VUSBSNIFFEREVENT_ERROR_SUBMIT, + /** URB completed with error event. */ + VUSBSNIFFEREVENT_ERROR_COMPLETE, + /** 32bit hack. */ + VUSBSNIFFEREVENT_32BIT_HACK = 0x7fffffff +} VUSBSNIFFEREVENT; + +/** VUSB Sniffer creation flags. + * @{ */ +/** Default flags. */ +#define VUSBSNIFFER_F_DEFAULT 0 +/** Don't overwrite any existing capture file. */ +#define VUSBSNIFFER_F_NO_REPLACE RT_BIT_32(0) +/** @} */ + +/** + * Create a new VUSB sniffer instance dumping to the given capture file. + * + * @returns VBox status code. + * @param phSniffer Where to store the handle to the sniffer instance on success. + * @param fFlags Flags, combination of VUSBSNIFFER_F_* + * @param pszCaptureFilename The filename to use for capturing the sniffed data. + * @param pszFmt The format of the dump, NULL to select one based on the filename + * extension. + * @param pszDesc Optional description for the dump. + */ +DECLHIDDEN(int) VUSBSnifferCreate(PVUSBSNIFFER phSniffer, uint32_t fFlags, + const char *pszCaptureFilename, const char *pszFmt, + const char *pszDesc); + +/** + * Destroys the given VUSB sniffer instance. + * + * @returns nothing. + * @param hSniffer The sniffer instance to destroy. + */ +DECLHIDDEN(void) VUSBSnifferDestroy(VUSBSNIFFER hSniffer); + +/** + * Records an VUSB event. + * + * @returns VBox status code. + * @param hSniffer The sniffer instance. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ +DECLHIDDEN(int) VUSBSnifferRecordEvent(VUSBSNIFFER hSniffer, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent); + + +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSniffer_h */ + diff --git a/src/VBox/Devices/USB/VUSBSnifferInternal.h b/src/VBox/Devices/USB/VUSBSnifferInternal.h new file mode 100644 index 00000000..8a57d13f --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferInternal.h @@ -0,0 +1,109 @@ +/* $Id: VUSBSnifferInternal.h $ */ +/** @file + * Virtual USB Sniffer facility - Internal header. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#ifndef VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h +#define VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> + +#include "VUSBSniffer.h" + +RT_C_DECLS_BEGIN + +/** Pointer to a stream operations structure. */ +typedef struct VUSBSNIFFERSTRM *PVUSBSNIFFERSTRM; +/** Pointer to the internal format specific state. */ +typedef struct VUSBSNIFFERFMTINT *PVUSBSNIFFERFMTINT; + +/** + * Stream operations structure. + */ +typedef struct VUSBSNIFFERSTRM +{ + /** + * Write the given buffer to the underlying stream. + * + * @returns VBox status code. + * @param pStrm The pointer to the interface containing this method. + * @param pvBuf The buffer to write. + * @param cbBuf How many bytes to write. + */ + DECLR3CALLBACKMEMBER(int, pfnWrite, (PVUSBSNIFFERSTRM pStrm, const void *pvBuf, size_t cbBuf)); + +} VUSBSNIFFERSTRM; + + +/** + * VUSB Sniffer format backend. + */ +typedef struct VUSBSNIFFERFMT +{ + /** Name of the format. */ + char szName[16]; + /** Description of the format. */ + const char *pszDesc; + /** Supported file extensions - terminated with a NULL entry. */ + const char **papszFileExts; + /** Size of the format specific state structure in bytes. */ + size_t cbFmt; + + /** + * Initializes the format. + * + * @returns VBox status code. + * @param pThis Pointer to the unitialized format specific state. + * @param pStrm The stream to use for writing. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm)); + + /** + * Destroys the format instance. + * + * @returns nothing. + * @param pThis Pointer to the format specific state. + */ + DECLR3CALLBACKMEMBER(void, pfnDestroy, (PVUSBSNIFFERFMTINT pThis)); + + /** + * Records the given VUSB event. + * + * @returns VBox status code. + * @param pThis Pointer to the format specific state. + * @param pUrb The URB triggering the event. + * @param enmEvent The type of event to record. + */ + DECLR3CALLBACKMEMBER(int, pfnRecordEvent, (PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent)); + +} VUSBSNIFFERFMT; +/** Pointer to a VUSB Sniffer format backend. */ +typedef VUSBSNIFFERFMT *PVUSBSNIFFERFMT; +/** Pointer to a const VUSB Sniffer format backend. */ +typedef const VUSBSNIFFERFMT *PCVUSBSNIFFERFMT; + +/** PCAP-Ng format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg; +/** VMware VMX log format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx; +/** Linux UsbMon log format writer. */ +extern const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon; + +RT_C_DECLS_END +#endif /* !VBOX_INCLUDED_SRC_USB_VUSBSnifferInternal_h */ diff --git a/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp new file mode 100644 index 00000000..f3881455 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferPcapNg.cpp @@ -0,0 +1,731 @@ +/* $Id: VUSBSnifferPcapNg.cpp $ */ +/** @file + * Virtual USB Sniffer facility - PCAP-NG format writer. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/buildconfig.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** DumpFile Section Header Block type. */ +#define DUMPFILE_SHB_BLOCK_TYPE UINT32_C(0x0a0d0d0a) +/** The byte order magic value. */ +#define DUMPFILE_SHB_BYTE_ORDER_MAGIC UINT32_C(0x1a2b3c4d) +/** Current major version. */ +#define DUMPFILE_SHB_VERSION_MAJOR UINT16_C(1) +/** Current minor version. */ +#define DUMPFILE_SHB_VERSION_MINOR UINT16_C(0) + +/** Block type for the interface descriptor block. */ +#define DUMPFILE_IDB_BLOCK_TYPE UINT32_C(0x00000001) +/** USB link type. */ +#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX UINT16_C(189) +#define DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED UINT16_C(220) + +/** Block type for an enhanced packet block. */ +#define DUMPFILE_EPB_BLOCK_TYPE UINT32_C(0x00000006) + +/** USB packet event types. */ +#define DUMPFILE_USB_EVENT_TYPE_SUBMIT ('S') +#define DUMPFILE_USB_EVENT_TYPE_COMPLETE ('C') +#define DUMPFILE_USB_EVENT_TYPE_ERROR ('E') + +#define DUMPFILE_OPTION_CODE_END UINT16_C(0) +#define DUMPFILE_OPTION_CODE_COMMENT UINT16_C(1) + +#define DUMPFILE_OPTION_CODE_HARDWARE UINT16_C(2) +#define DUMPFILE_OPTION_CODE_OS UINT16_C(3) +#define DUMPFILE_OPTION_CODE_USERAPP UINT16_C(4) + +#define DUMPFILE_IDB_OPTION_TS_RESOLUTION UINT16_C(9) + + +/********************************************************************************************************************************* +* DumpFile format structures * +*********************************************************************************************************************************/ + +/** + * DumpFile Block header. + */ +typedef struct DumpFileBlockHdr +{ + /** Block type. */ + uint32_t u32BlockType; + /** Block total length. */ + uint32_t u32BlockTotalLength; +} DumpFileBlockHdr; +/** Pointer to a block header. */ +typedef DumpFileBlockHdr *PDumpFileBlockHdr; + +/** + * DumpFile Option header. + */ +typedef struct DumpFileOptionHdr +{ + /** Option code. */ + uint16_t u16OptionCode; + /** Block total length. */ + uint16_t u16OptionLength; +} DumpFileOptionHdr; +/** Pointer to a option header. */ +typedef DumpFileOptionHdr *PDumpFileOptionHdr; + +/** + * DumpFile Section Header Block. + */ +typedef struct DumpFileShb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Byte order magic. */ + uint32_t u32ByteOrderMagic; + /** Major version. */ + uint16_t u16VersionMajor; + /** Minor version. */ + uint16_t u16VersionMinor; + /** Section length. */ + uint64_t u64SectionLength; +} DumpFileShb; +/** Pointer to a Section Header Block. */ +typedef DumpFileShb *PDumpFileShb; + +/** + * DumpFile Interface description block. + */ +typedef struct DumpFileIdb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Link type. */ + uint16_t u16LinkType; + /** Reserved. */ + uint16_t u16Reserved; + /** Maximum number of bytes dumped from each packet. */ + uint32_t u32SnapLen; +} DumpFileIdb; +/** Pointer to an Interface description block. */ +typedef DumpFileIdb *PDumpFileIdb; + +/** + * DumpFile Enhanced packet block. + */ +typedef struct DumpFileEpb +{ + /** Block header. */ + DumpFileBlockHdr Hdr; + /** Interface ID. */ + uint32_t u32InterfaceId; + /** Timestamp (high). */ + uint32_t u32TimestampHigh; + /** Timestamp (low). */ + uint32_t u32TimestampLow; + /** Captured packet length. */ + uint32_t u32CapturedLen; + /** Original packet length. */ + uint32_t u32PacketLen; +} DumpFileEpb; +/** Pointer to an Enhanced packet block. */ +typedef DumpFileEpb *PDumpFileEpb; + +/** + * USB setup URB data. + */ +typedef struct DumpFileUsbSetup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} DumpFileUsbSetup; +typedef DumpFileUsbSetup *PDumpFileUsbSetup; + +/** + * USB Isochronous data. + */ +typedef struct DumpFileIsoRec +{ + int32_t i32ErrorCount; + int32_t i32NumDesc; +} DumpFileIsoRec; +typedef DumpFileIsoRec *PDumpFileIsoRec; + +/** + * USB packet header (Linux mmapped variant). + */ +typedef struct DumpFileUsbHeaderLnxMmapped +{ + /** Packet Id. */ + uint64_t u64Id; + /** Event type. */ + uint8_t u8EventType; + /** Transfer type. */ + uint8_t u8TransferType; + /** Endpoint number. */ + uint8_t u8EndpointNumber; + /** Device address. */ + uint8_t u8DeviceAddress; + /** Bus id. */ + uint16_t u16BusId; + /** Setup flag != 0 if the URB setup header is not present. */ + uint8_t u8SetupFlag; + /** Data present flag != 0 if the URB data is not present. */ + uint8_t u8DataFlag; + /** Timestamp (second part). */ + uint64_t u64TimestampSec; + /** Timestamp (us part). */ + uint32_t u32TimestampUSec; + /** Status. */ + int32_t i32Status; + /** URB length. */ + uint32_t u32UrbLength; + /** Recorded data length. */ + uint32_t u32DataLength; + /** Union of data for different URB types. */ + union + { + DumpFileUsbSetup UsbSetup; + DumpFileIsoRec IsoRec; + } u; + int32_t i32Interval; + int32_t i32StartFrame; + /** Copy of transfer flags. */ + uint32_t u32XferFlags; + /** Number of isochronous descriptors. */ + uint32_t u32NumDesc; +} DumpFileUsbHeaderLnxMmapped; +/** Pointer to a USB packet header. */ +typedef DumpFileUsbHeaderLnxMmapped *PDumpFileUsbHeaderLnxMmapped; + +AssertCompileSize(DumpFileUsbHeaderLnxMmapped, 64); + +/** + * USB packet isochronous descriptor. + */ +typedef struct DumpFileUsbIsoDesc +{ + int32_t i32Status; + uint32_t u32Offset; + uint32_t u32Len; + uint8_t au8Padding[4]; +} DumpFileUsbIsoDesc; +typedef DumpFileUsbIsoDesc *PDumpFileUsbIsoDesc; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; + /** Current size of the block being written. */ + uint32_t cbBlockCur; + /** Maximum size allocated for the block. */ + uint32_t cbBlockMax; + /** Current block header. */ + PDumpFileBlockHdr pBlockHdr; + /** Pointer to the block data which will be written on commit. */ + uint8_t *pbBlockData; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "pcap", + "pcapng", + NULL +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Allocates additional space for the block. + * + * @returns Pointer to the new unused space or NULL if out of memory. + * @param pThis The VUSB sniffer instance. + * @param cbAdditional The additional memory requested. + */ +static void *vusbSnifferBlockAllocSpace(PVUSBSNIFFERFMTINT pThis, uint32_t cbAdditional) +{ + /* Fast path where we have enough memory allocated. */ + if (pThis->cbBlockCur + cbAdditional <= pThis->cbBlockMax) + { + void *pv = pThis->pbBlockData + pThis->cbBlockCur; + pThis->cbBlockCur += cbAdditional; + return pv; + } + + /* Allocate additional memory. */ + uint32_t cbNew = pThis->cbBlockCur + cbAdditional; + uint8_t *pbDataNew = (uint8_t *)RTMemRealloc(pThis->pbBlockData, cbNew); + if (pbDataNew) + { + pThis->pbBlockData = pbDataNew; + pThis->pBlockHdr = (PDumpFileBlockHdr)pbDataNew; + + void *pv = pThis->pbBlockData + pThis->cbBlockCur; + pThis->cbBlockCur = cbNew; + pThis->cbBlockMax = cbNew; + return pv; + } + + return NULL; +} + +/** + * Adds new data to the current block. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param pvData The data to add. + * @param cbData Amount of data to add. + */ +static int vusbSnifferBlockAddData(PVUSBSNIFFERFMTINT pThis, const void *pvData, uint32_t cbData) +{ + int rc = VINF_SUCCESS; + + Assert(pThis->cbBlockCur); + AssertPtr(pThis->pBlockHdr); + + void *pv = vusbSnifferBlockAllocSpace(pThis, cbData); + if (pv) + memcpy(pv, pvData, cbData); + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Aligns the current block data to a 32bit boundary. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + */ +static int vusbSnifferBlockAlign(PVUSBSNIFFERFMTINT pThis) +{ + int rc = VINF_SUCCESS; + + Assert(pThis->cbBlockCur); + + /* Pad to 32bits. */ + uint8_t abPad[3] = { 0 }; + uint32_t cbPad = RT_ALIGN_32(pThis->cbBlockCur, 4) - pThis->cbBlockCur; + + Assert(cbPad <= 3); + if (cbPad) + rc = vusbSnifferBlockAddData(pThis, abPad, cbPad); + + return rc; +} + +/** + * Commits the current block to the capture file. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + */ +static int vusbSnifferBlockCommit(PVUSBSNIFFERFMTINT pThis) +{ + int rc = VINF_SUCCESS; + + AssertPtr(pThis->pBlockHdr); + + rc = vusbSnifferBlockAlign(pThis); + if (RT_SUCCESS(rc)) + { + /* Update the block total length field. */ + uint32_t *pcbTotalLength = (uint32_t *)vusbSnifferBlockAllocSpace(pThis, 4); + if (pcbTotalLength) + { + *pcbTotalLength = pThis->cbBlockCur; + pThis->pBlockHdr->u32BlockTotalLength = pThis->cbBlockCur; + + /* Write the data. */ + rc = pThis->pStrm->pfnWrite(pThis->pStrm, pThis->pbBlockData, pThis->cbBlockCur); + pThis->cbBlockCur = 0; + pThis->pBlockHdr = NULL; + } + else + rc = VERR_NO_MEMORY; + } + + return rc; +} + +/** + * Starts a new block for capturing. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param pBlockHdr Pointer to the block header for the new block. + * @param cbData Amount of data added with this block. + */ +static int vusbSnifferBlockNew(PVUSBSNIFFERFMTINT pThis, PDumpFileBlockHdr pBlockHdr, uint32_t cbData) +{ + int rc = VINF_SUCCESS; + + /* Validate we don't get called while another block is active. */ + Assert(!pThis->cbBlockCur); + Assert(!pThis->pBlockHdr); + pThis->pBlockHdr = (PDumpFileBlockHdr)vusbSnifferBlockAllocSpace(pThis, cbData); + if (pThis->pBlockHdr) + memcpy(pThis->pBlockHdr, pBlockHdr, cbData); + else + rc = VERR_NO_MEMORY; + + return rc; +} + +/** + * Add a new option to the current block. + * + * @returns VBox status code. + * @param pThis The VUSB sniffer instance. + * @param u16OptionCode The option code identifying the type of option. + * @param pvOption Raw data for the option. + * @param cbOption Size of the optiob data. + */ +static int vusbSnifferAddOption(PVUSBSNIFFERFMTINT pThis, uint16_t u16OptionCode, const void *pvOption, size_t cbOption) +{ + AssertStmt((uint16_t)cbOption == cbOption, cbOption = UINT16_MAX); + DumpFileOptionHdr OptHdr; + OptHdr.u16OptionCode = u16OptionCode; + OptHdr.u16OptionLength = (uint16_t)cbOption; + int rc = vusbSnifferBlockAddData(pThis, &OptHdr, sizeof(OptHdr)); + if ( RT_SUCCESS(rc) + && u16OptionCode != DUMPFILE_OPTION_CODE_END + && cbOption != 0) + { + rc = vusbSnifferBlockAddData(pThis, pvOption, (uint16_t)cbOption); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockAlign(pThis); + } + + return rc; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtPcapNgInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + pThis->cbBlockCur = 0; + pThis->cbBlockMax = 0; + pThis->pbBlockData = NULL; + + /* Write header and link type blocks. */ + DumpFileShb Shb; + + Shb.Hdr.u32BlockType = DUMPFILE_SHB_BLOCK_TYPE; + Shb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */ + Shb.u32ByteOrderMagic = DUMPFILE_SHB_BYTE_ORDER_MAGIC; + Shb.u16VersionMajor = DUMPFILE_SHB_VERSION_MAJOR; + Shb.u16VersionMinor = DUMPFILE_SHB_VERSION_MINOR; + Shb.u64SectionLength = UINT64_C(0xffffffffffffffff); /* -1 */ + + /* Write the blocks. */ + int rc = vusbSnifferBlockNew(pThis, &Shb.Hdr, sizeof(Shb)); + if (RT_SUCCESS(rc)) + { + const char *pszOpt = RTBldCfgTargetDotArch(); + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_HARDWARE, pszOpt, strlen(pszOpt) + 1); + } + + if (RT_SUCCESS(rc)) + { + char szTmp[512]; + size_t cbTmp = sizeof(szTmp); + + RT_ZERO(szTmp); + + /* Build the OS code. */ + rc = RTSystemQueryOSInfo(RTSYSOSINFO_PRODUCT, szTmp, cbTmp); + if (RT_SUCCESS(rc)) + { + size_t cb = strlen(szTmp); + + szTmp[cb] = ' '; + rc = RTSystemQueryOSInfo(RTSYSOSINFO_RELEASE, &szTmp[cb + 1], cbTmp - (cb + 1)); + if (RT_SUCCESS(rc)) + { + cb = strlen(szTmp); + szTmp[cb] = ' '; + rc = RTSystemQueryOSInfo(RTSYSOSINFO_VERSION, &szTmp[cb + 1], cbTmp - (cb + 1)); + } + } + + if (RT_SUCCESS(rc) || rc == VERR_BUFFER_OVERFLOW) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_OS, szTmp, strlen(szTmp) + 1); + else + rc = VINF_SUCCESS; /* Skip OS code if building the string failed. */ + } + + if (RT_SUCCESS(rc)) + { + /** @todo Add product info. */ + } + + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + + /* Write Interface descriptor block. */ + if (RT_SUCCESS(rc)) + { + DumpFileIdb Idb; + + Idb.Hdr.u32BlockType = DUMPFILE_IDB_BLOCK_TYPE; + Idb.Hdr.u32BlockTotalLength = 0; /* Filled out by lower layer. */ + Idb.u16LinkType = DUMPFILE_IDB_LINK_TYPE_USB_LINUX_MMAPED; + Idb.u16Reserved = 0; + Idb.u32SnapLen = UINT32_C(0xffffffff); + + rc = vusbSnifferBlockNew(pThis, &Idb.Hdr, sizeof(Idb)); + if (RT_SUCCESS(rc)) + { + uint8_t u8TsResolution = 9; /* Nano second resolution. */ + /* Add timestamp resolution option. */ + rc = vusbSnifferAddOption(pThis, DUMPFILE_IDB_OPTION_TS_RESOLUTION, + &u8TsResolution, sizeof(u8TsResolution)); + } + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + } + + if ( RT_FAILURE(rc) + && pThis->pbBlockData) + RTMemFree(pThis->pbBlockData); + + return rc; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtPcapNgDestroy(PVUSBSNIFFERFMTINT pThis) +{ + if (pThis->pbBlockData) + RTMemFree(pThis->pbBlockData); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtPcapNgRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + DumpFileEpb Epb; + DumpFileUsbHeaderLnxMmapped UsbHdr; + uint32_t cbCapturedLength = sizeof(UsbHdr); + uint8_t *pbData = NULL; + + RTTIMESPEC TimeNow; + RTTimeNow(&TimeNow); + uint64_t u64TimestampEvent = RTTimeSpecGetNano(&TimeNow); + + /* Start with the enhanced packet block. */ + Epb.Hdr.u32BlockType = DUMPFILE_EPB_BLOCK_TYPE; + Epb.Hdr.u32BlockTotalLength = 0; + Epb.u32InterfaceId = 0; + Epb.u32TimestampHigh = (u64TimestampEvent >> 32) & UINT32_C(0xffffffff); + Epb.u32TimestampLow = u64TimestampEvent & UINT32_C(0xffffffff); + + UsbHdr.u64Id = (uintptr_t)pUrb; /** @todo check whether the pointer is a good ID. */ + uint32_t cbUrbLength; + switch (enmEvent) + { + case VUSBSNIFFEREVENT_SUBMIT: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_SUBMIT; + cbUrbLength = pUrb->cbData; + break; + case VUSBSNIFFEREVENT_COMPLETE: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_COMPLETE; + cbUrbLength = pUrb->cbData; + break; + case VUSBSNIFFEREVENT_ERROR_SUBMIT: + case VUSBSNIFFEREVENT_ERROR_COMPLETE: + UsbHdr.u8EventType = DUMPFILE_USB_EVENT_TYPE_ERROR; + cbUrbLength = 0; + break; + default: + AssertMsgFailed(("Invalid event type %d\n", enmEvent)); + cbUrbLength = 0; + } + uint32_t cbDataLength = cbUrbLength; + pbData = &pUrb->abData[0]; + + uint32_t cIsocPkts = 0; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_ISOC: + { + int32_t cErrors = 0; + + UsbHdr.u8TransferType = 0; + cIsocPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < cIsocPkts; i++) + if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK + && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED) + cErrors++; + + UsbHdr.u.IsoRec.i32ErrorCount = cErrors; + UsbHdr.u.IsoRec.i32NumDesc = pUrb->cIsocPkts; + cbCapturedLength += cIsocPkts * sizeof(DumpFileUsbIsoDesc); + break; + } + case VUSBXFERTYPE_BULK: + UsbHdr.u8TransferType = 3; + break; + case VUSBXFERTYPE_INTR: + UsbHdr.u8TransferType = 1; + break; + case VUSBXFERTYPE_CTRL: + case VUSBXFERTYPE_MSG: + UsbHdr.u8TransferType = 2; + break; + default: + AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType)); + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + if (enmEvent == VUSBSNIFFEREVENT_SUBMIT) + cbDataLength = 0; + } + else if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE + || pUrb->enmType == VUSBXFERTYPE_CTRL + || pUrb->enmType == VUSBXFERTYPE_MSG) + cbDataLength = 0; + } + else if ( pUrb->enmDir == VUSBDIRECTION_SETUP + && cbDataLength >= sizeof(VUSBSETUP)) + cbDataLength -= sizeof(VUSBSETUP); + + Epb.u32CapturedLen = cbCapturedLength + cbDataLength; + Epb.u32PacketLen = cbCapturedLength + cbUrbLength; + + UsbHdr.u8EndpointNumber = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00); + UsbHdr.u8DeviceAddress = pUrb->DstAddress; + UsbHdr.u16BusId = 0; + UsbHdr.u8DataFlag = cbDataLength ? 0 : 1; + UsbHdr.u64TimestampSec = u64TimestampEvent / RT_NS_1SEC_64; + UsbHdr.u32TimestampUSec = u64TimestampEvent / RT_NS_1US_64 - UsbHdr.u64TimestampSec * RT_US_1SEC; + UsbHdr.i32Status = pUrb->enmStatus; + UsbHdr.u32UrbLength = cbUrbLength; + UsbHdr.u32DataLength = cbDataLength + cIsocPkts * sizeof(DumpFileUsbIsoDesc); + UsbHdr.i32Interval = 0; + UsbHdr.i32StartFrame = 0; + UsbHdr.u32XferFlags = 0; + UsbHdr.u32NumDesc = cIsocPkts; + + if ( (pUrb->enmType == VUSBXFERTYPE_MSG || pUrb->enmType == VUSBXFERTYPE_CTRL) + && enmEvent == VUSBSNIFFEREVENT_SUBMIT) + { + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + + UsbHdr.u.UsbSetup.bmRequestType = pSetup->bmRequestType; + UsbHdr.u.UsbSetup.bRequest = pSetup->bRequest; + UsbHdr.u.UsbSetup.wValue = pSetup->wValue; + UsbHdr.u.UsbSetup.wIndex = pSetup->wIndex; + UsbHdr.u.UsbSetup.wLength = pSetup->wLength; + UsbHdr.u8SetupFlag = 0; + } + else + UsbHdr.u8SetupFlag = '-'; /* Follow usbmon source here. */ + + /* Write the packet to the capture file. */ + int rc = vusbSnifferBlockNew(pThis, &Epb.Hdr, sizeof(Epb)); + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockAddData(pThis, &UsbHdr, sizeof(UsbHdr)); + + /* Add Isochronous descriptors now. */ + for (unsigned i = 0; i < cIsocPkts && RT_SUCCESS(rc); i++) + { + DumpFileUsbIsoDesc IsoDesc; + IsoDesc.i32Status = pUrb->aIsocPkts[i].enmStatus; + IsoDesc.u32Offset = pUrb->aIsocPkts[i].off; + IsoDesc.u32Len = pUrb->aIsocPkts[i].cb; + rc = vusbSnifferBlockAddData(pThis, &IsoDesc, sizeof(IsoDesc)); + } + + /* Record data. */ + if ( RT_SUCCESS(rc) + && cbDataLength) + rc = vusbSnifferBlockAddData(pThis, pbData, cbDataLength); + + if (RT_SUCCESS(rc)) + rc = vusbSnifferAddOption(pThis, DUMPFILE_OPTION_CODE_END, NULL, 0); + + if (RT_SUCCESS(rc)) + rc = vusbSnifferBlockCommit(pThis); + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtPcapNg = +{ + /** szName */ + "PCAPNG", + /** pszDesc */ + "PCAP-NG format writer compatible with WireShark", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtPcapNgInit, + /** pfnDestroy */ + vusbSnifferFmtPcapNgDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtPcapNgRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp new file mode 100644 index 00000000..e4bccf86 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferUsbMon.cpp @@ -0,0 +1,239 @@ +/* $Id: VUSBSnifferUsbMon.cpp $ */ +/** @file + * Virtual USB Sniffer facility - Linux usbmon ASCII format. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/buildconfig.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "mon", + "usbmon", + NULL +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtUsbMonInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtUsbMonDestroy(PVUSBSNIFFERFMTINT pThis) +{ + RT_NOREF(pThis); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtUsbMonRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + char aszLineBuf[512]; + char chEvtType = 'X'; + char chDir = 'X'; + char chEptType = 'X'; + + switch (enmEvent) + { + case VUSBSNIFFEREVENT_SUBMIT: + chEvtType = 'S'; + break; + case VUSBSNIFFEREVENT_COMPLETE: + chEvtType = 'C'; + break; + case VUSBSNIFFEREVENT_ERROR_SUBMIT: + case VUSBSNIFFEREVENT_ERROR_COMPLETE: + chEvtType = 'E'; + break; + default: + AssertMsgFailed(("Invalid event type %d\n", enmEvent)); + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_ISOC: + chEptType = 'Z'; + break; + case VUSBXFERTYPE_BULK: + chEptType = 'B'; + break; + case VUSBXFERTYPE_INTR: + chEptType = 'I'; + break; + case VUSBXFERTYPE_CTRL: + case VUSBXFERTYPE_MSG: + chEptType = 'C'; + break; + default: + AssertMsgFailed(("invalid transfer type %d\n", pUrb->enmType)); + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + chDir = 'i'; + else if (pUrb->enmDir == VUSBDIRECTION_OUT) + chDir = 'o'; + else if (pUrb->enmDir == VUSBDIRECTION_SETUP) + chDir = 'o'; + + RT_ZERO(aszLineBuf); + + /* Assemble the static part. */ + size_t cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%p %llu %c %c%c:%u:%u:%u ", + pUrb, RTTimeNanoTS() / RT_NS_1US, chEvtType, chEptType, chDir, + 0, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00)); + int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + if (RT_SUCCESS(rc)) + { + /* Log the setup packet for control requests, the status otherwise. */ + if ( (pUrb->enmType == VUSBXFERTYPE_CTRL || pUrb->enmType == VUSBXFERTYPE_MSG) + && enmEvent == VUSBSNIFFEREVENT_SUBMIT) + { + PVUSBSETUP pSetup = (PVUSBSETUP)pUrb->abData; + + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "s %02x %02x %04x %04x %04x ", + pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, + pSetup->wIndex, pSetup->wLength); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + else + { + bool fLogAdditionalStatus = pUrb->enmType == VUSBXFERTYPE_ISOC + || pUrb->enmType == VUSBXFERTYPE_INTR; + + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d%s", pUrb->enmStatus, + fLogAdditionalStatus ? "" : " "); + + /* There are additional fields to log for isochronous and interrupt URBs. */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + if (enmEvent == VUSBSNIFFEREVENT_COMPLETE) + { + uint32_t u32ErrorCount = 0; + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + if ( pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_OK + && pUrb->aIsocPkts[i].enmStatus != VUSBSTATUS_NOT_ACCESSED) + u32ErrorCount++; + + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u:%u ", + 1 /* Interval */, 0 /* Frame number */, u32ErrorCount); + } + else + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u:%u ", + 1 /* Interval */, 0 /* Frame number */); + } + else if (pUrb->enmType == VUSBXFERTYPE_INTR) + cch += RTStrPrintf(&aszLineBuf[cch], sizeof(aszLineBuf) - cch, ":%u ", + 1 /* Interval */); + + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + + /* Log the packet descriptors for isochronous URBs. */ + if ( RT_SUCCESS(rc) + && pUrb->enmType == VUSBXFERTYPE_ISOC) + { + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%u ", pUrb->cIsocPkts); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + for (unsigned i = 0; i < pUrb->cIsocPkts && RT_SUCCESS(rc); i++) + { + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d:%u:%u ", + pUrb->aIsocPkts[i].enmStatus, pUrb->aIsocPkts[i].off, + pUrb->aIsocPkts[i].cb); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + } + + if (RT_SUCCESS(rc)) + { + /* Print data length */ + cch = RTStrPrintf(&aszLineBuf[0], sizeof(aszLineBuf), "%d n\n", pUrb->cbData); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &aszLineBuf[0], cch); + } + + /** @todo Dump the data */ + } + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtUsbMon = +{ + /** szName */ + "USBMON", + /** pszDesc */ + "UsbMon format writer compatible with vusb-analyzer: http://vusb-analyzer.sourceforge.net", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtUsbMonInit, + /** pfnDestroy */ + vusbSnifferFmtUsbMonDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtUsbMonRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBSnifferVmx.cpp b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp new file mode 100644 index 00000000..dffbc327 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBSnifferVmx.cpp @@ -0,0 +1,202 @@ +/* $Id: VUSBSnifferVmx.cpp $ */ +/** @file + * Virtual USB Sniffer facility - VMX USBIO format. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/buildconfig.h> +#include <iprt/string.h> +#include <iprt/system.h> +#include <iprt/time.h> + +#include "VUSBSnifferInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * The internal VUSB sniffer state. + */ +typedef struct VUSBSNIFFERFMTINT +{ + /** Stream handle. */ + PVUSBSNIFFERSTRM pStrm; +} VUSBSNIFFERFMTINT; + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + +/** + * Supported file extensions. + */ +static const char *s_apszFileExts[] = +{ + "vmx", + "vmware", + "usbio", + NULL +}; + + +/** + * Month strings. + */ +static const char *s_apszMonths[] = +{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec" +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +static int vusbSnifferFmtVmxLogData(PVUSBSNIFFERFMTINT pThis, PRTTIME pTime, uint8_t *pbBuf, size_t cbBuf) +{ + int rc; + char szLineBuf[256]; + size_t off = 0; + + do + { + size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf), + "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %03zx: %16.*Rhxs\n", + s_apszMonths[pTime->u8Month - 1], pTime->u8MonthDay, + pTime->u8Hour, pTime->u8Minute, pTime->u8Second, 3, pTime->u32Nanosecond, + off, RT_MIN(cbBuf - off, 16), pbBuf); + rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch); + off += RT_MIN(cbBuf, 16); + pbBuf += RT_MIN(cbBuf, 16); + } while (RT_SUCCESS(rc) && off < cbBuf); + + return rc; +} + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnInit} */ +static DECLCALLBACK(int) vusbSnifferFmtVmxInit(PVUSBSNIFFERFMTINT pThis, PVUSBSNIFFERSTRM pStrm) +{ + pThis->pStrm = pStrm; + return VINF_SUCCESS; +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnDestroy} */ +static DECLCALLBACK(void) vusbSnifferFmtVmxDestroy(PVUSBSNIFFERFMTINT pThis) +{ + NOREF(pThis); +} + + +/** @interface_method_impl{VUSBSNIFFERFMT,pfnRecordEvent} */ +static DECLCALLBACK(int) vusbSnifferFmtVmxRecordEvent(PVUSBSNIFFERFMTINT pThis, PVUSBURB pUrb, VUSBSNIFFEREVENT enmEvent) +{ + RTTIMESPEC TimeNow; + RTTIME Time; + char szLineBuf[256]; + const char *pszEvt = enmEvent == VUSBSNIFFEREVENT_SUBMIT ? "Down" : "Up"; + uint8_t cIsocPkts = pUrb->enmType == VUSBXFERTYPE_ISOC ? pUrb->cIsocPkts : 0; + + if (pUrb->enmType == VUSBXFERTYPE_MSG) + return VINF_SUCCESS; + + RT_ZERO(szLineBuf); + + RTTimeNow(&TimeNow); + RTTimeExplode(&Time, &TimeNow); + + size_t cch = RTStrPrintf(&szLineBuf[0], sizeof(szLineBuf), + "%s %02u %02u:%02u:%02u.%3.*u: vmx| USBIO: %s dev=%u endpt=%x datalen=%u numPackets=%u status=%u 0\n", + s_apszMonths[Time.u8Month - 1], Time.u8MonthDay, Time.u8Hour, Time.u8Minute, Time.u8Second, 3, Time.u32Nanosecond, + pszEvt, pUrb->DstAddress, pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0x00), + pUrb->cbData, cIsocPkts, pUrb->enmStatus); + int rc = pThis->pStrm->pfnWrite(pThis->pStrm, &szLineBuf[0], cch); + if (RT_SUCCESS(rc)) + { + /* Log the data in the appropriate stage. */ + if ( pUrb->enmType == VUSBXFERTYPE_CTRL + || pUrb->enmType == VUSBXFERTYPE_MSG) + { + if (enmEvent == VUSBSNIFFEREVENT_SUBMIT) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP)); + else if (enmEvent == VUSBSNIFFEREVENT_COMPLETE) + { + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], sizeof(VUSBSETUP)); + if ( RT_SUCCESS(rc) + && pUrb->cbData > sizeof(VUSBSETUP)) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[sizeof(VUSBSETUP)], pUrb->cbData - sizeof(VUSBSETUP)); + } + } + else + { + if ( enmEvent == VUSBSNIFFEREVENT_SUBMIT + && pUrb->enmDir == VUSBDIRECTION_OUT) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData); + else if ( enmEvent == VUSBSNIFFEREVENT_COMPLETE + && pUrb->enmDir == VUSBDIRECTION_IN) + rc = vusbSnifferFmtVmxLogData(pThis, &Time, &pUrb->abData[0], pUrb->cbData); + } + } + + return rc; +} + +/** + * VUSB sniffer format writer. + */ +const VUSBSNIFFERFMT g_VUsbSnifferFmtVmx = +{ + /** szName */ + "VMX", + /** pszDesc */ + "VMX log format writer supported by vusb-analyzer: http://vusb-analyzer.sourceforge.net", + /** papszFileExts */ + &s_apszFileExts[0], + /** cbFmt */ + sizeof(VUSBSNIFFERFMTINT), + /** pfnInit */ + vusbSnifferFmtVmxInit, + /** pfnDestroy */ + vusbSnifferFmtVmxDestroy, + /** pfnRecordEvent */ + vusbSnifferFmtVmxRecordEvent +}; + diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp new file mode 100644 index 00000000..fa28b72c --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrb.cpp @@ -0,0 +1,1477 @@ +/* $Id: VUSBUrb.cpp $ */ +/** @file + * Virtual USB - URBs. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <VBox/err.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/env.h> +#include "VUSBInternal.h" + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Strings for the CTLSTAGE enum values. */ +const char * const g_apszCtlStates[4] = +{ + "SETUP", + "DATA", + "STATUS", + "N/A" +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Complete a SETUP stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgSetupCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgSetupCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage %s->DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe, g_apszCtlStates[pExtra->enmStage])); NOREF(pSetup); + pExtra->enmStage = CTLSTAGE_DATA; + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a DATA stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgDataCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgDataCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe)); NOREF(pSetup); + + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a STATUS stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgStatusCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (pExtra->fOk) + { + /* + * vusbDevStdReqSetAddress requests are deferred. + */ + if (pDev->u8NewAddress != VUSB_INVALID_ADDRESS) + { + vusbDevSetAddress(pDev, pDev->u8NewAddress); + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + } + + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=OK stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_OK; + } + else + { + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_STALL; + } + + /* + * Done with this message sequence. + */ + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; +} + +/** + * This is a worker function for vusbMsgCompletion and + * vusbMsgSubmitSynchronously used to complete the original URB. + * + * @param pUrb The URB originating from the HCI. + */ +static void vusbCtrlCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbCtrlCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + vusbMsgSetupCompletion(pUrb); + break; + case CTLSTAGE_DATA: + vusbMsgDataCompletion(pUrb); + break; + case CTLSTAGE_STATUS: + vusbMsgStatusCompletion(pUrb); + break; + } +} + +/** + * Called from vusbUrbCompletionRh when it encounters a + * message type URB. + * + * @param pUrb The URB within the control pipe extra state data. + */ +static void vusbMsgCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + +#ifdef LOG_ENABLED + LogFlow(("%s: vusbMsgCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + vusbUrbTrace(pUrb, "vusbMsgCompletion", true); +#endif + Assert(&pExtra->Urb == pUrb); + + + if (pUrb->enmStatus == VUSBSTATUS_OK) + pExtra->fOk = true; + else + pExtra->fOk = false; + pExtra->cbLeft = pUrb->cbData - sizeof(VUSBSETUP); + + /* + * Complete the original URB. + */ + PVUSBURB pCtrlUrb = pUrb->pVUsb->pCtrlUrb; + pCtrlUrb->enmState = VUSBURBSTATE_REAPED; + vusbCtrlCompletion(pCtrlUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + Assert( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED); + if (pUrb->enmState != VUSBURBSTATE_CANCELLED) + { + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } + RTCritSectLeave(&pPipe->CritSectCtrl); + + /* Complete the original control URB on the root hub now. */ + vusbUrbCompletionRh(pCtrlUrb); +} + +/** + * Deal with URB errors, talking thru the RH to the HCI. + * + * @returns true if it could be retried. + * @returns false if it should be completed with failure. + * @param pUrb The URB in question. + */ +int vusbUrbErrorRh(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + AssertPtrReturn(pRh, VERR_VUSB_DEVICE_NOT_ATTACHED); + LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh)); + return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb); +} + +/** + * Does URB completion on roothub level. + * + * @param pUrb The URB to complete. + */ +void vusbUrbCompletionRh(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbCompletionRh: type=%s status=%s\n", + pUrb->pszDesc, vusbUrbTypeName(pUrb->enmType), vusbUrbStatusName(pUrb->enmStatus))); + AssertMsg( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED, ("%d\n", pUrb->enmState)); + + if ( pUrb->pVUsb->pDev + && pUrb->pVUsb->pDev->hSniffer) + { + int rc = VUSBSnifferRecordEvent(pUrb->pVUsb->pDev->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event failed with %Rrc\n", rc)); + } + + PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); + AssertPtrReturnVoid(pRh); + + /* If there is a sniffer on the roothub record the completed URB there too. */ + if (pRh->hSniffer != VUSBSNIFFER_NIL) + { + int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event on the root hub failed with %Rrc\n", rc)); + } + +#ifdef VBOX_WITH_STATISTICS + /* + * Total and per-type submit statistics. + */ + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes)); + + if ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) + { + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + const unsigned cb = pUrb->aIsocPkts[i].cb; + if (cb) + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aStatIsocDetails[i].Bytes, cb); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, cb); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, cb); + } + STAM_COUNTER_INC(&pRh->StatIsocActPkts); + STAM_COUNTER_INC(&pRh->StatIsocActReadPkts); + } + STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Pkts); + switch (pUrb->aIsocPkts[i].enmStatus) + { + case VUSBSTATUS_OK: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0); break; + case VUSBSTATUS_DATA_UNDERRUN: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0); break; + case VUSBSTATUS_DATA_OVERRUN: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataOverrun); break; + case VUSBSTATUS_NOT_ACCESSED: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].NotAccessed); break; + default: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Misc); break; + } + } + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActBytes, pUrb->cbData); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActReadBytes, pUrb->cbData); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActWriteBytes, pUrb->cbData); + } + } + } + else + { + /* (Note. this also counts the cancelled packets) */ + STAM_COUNTER_INC(&pRh->Total.StatUrbsFailed); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsFailed); + } + } +#endif /* VBOX_WITH_STATISTICS */ + + /* + * Msg transfers are special virtual transfers associated with + * vusb, not the roothub + */ + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + vusbMsgCompletion(pUrb); + return; + case VUSBXFERTYPE_ISOC: + /* Don't bother with error callback for isochronous URBs. */ + break; + +#if 1 /** @todo r=bird: OHCI say ''If the Transfer Descriptor is being + * retired because of an error, the Host Controller must update + * the Halt bit of the Endpoint Descriptor.'' + * + * So, I'll subject all transfertypes to the same halt stuff now. It could + * just happen to fix the logitech disconnect trap in win2k. + */ + default: +#endif + case VUSBXFERTYPE_BULK: + if (pUrb->enmStatus != VUSBSTATUS_OK) + vusbUrbErrorRh(pUrb); + break; + } +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbCompletionRh", true); +#endif + + pRh->pIRhPort->pfnXferCompletion(pRh->pIRhPort, pUrb); + if (pUrb->enmState == VUSBURBSTATE_REAPED) + { + LogFlow(("%s: vusbUrbCompletionRh: Freeing URB\n", pUrb->pszDesc)); + pUrb->pVUsb->pfnFree(pUrb); + } + + vusbRhR3ProcessFrame(pRh, true /* fCallback */); +} + + +/** + * Certain control requests must not ever be forwarded to the device because + * they are required by the vusb core in order to maintain the vusb internal + * data structures. + */ +DECLINLINE(bool) vusbUrbIsRequestSafe(PCVUSBSETUP pSetup, PVUSBURB pUrb) +{ + if ((pSetup->bmRequestType & VUSB_REQ_MASK) != VUSB_REQ_STANDARD) + return true; + + switch (pSetup->bRequest) + { + case VUSB_REQ_CLEAR_FEATURE: + return pUrb->EndPt != 0 /* not default control pipe */ + || pSetup->wValue != 0 /* not ENDPOINT_HALT */ + || !pUrb->pVUsb->pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint; /* not special need for backend */ + case VUSB_REQ_SET_ADDRESS: + case VUSB_REQ_SET_CONFIGURATION: + case VUSB_REQ_GET_CONFIGURATION: + case VUSB_REQ_SET_INTERFACE: + case VUSB_REQ_GET_INTERFACE: + return false; + + /* + * If the device wishes it, we'll use the cached device and + * configuration descriptors. (We return false when we want to use the + * cache. Yeah, it's a bit weird to read.) + */ + case VUSB_REQ_GET_DESCRIPTOR: + if ( !pUrb->pVUsb->pDev->pDescCache->fUseCachedDescriptors + || (pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE) + return true; + switch (pSetup->wValue >> 8) + { + case VUSB_DT_DEVICE: + case VUSB_DT_CONFIG: + return false; + case VUSB_DT_STRING: + return !pUrb->pVUsb->pDev->pDescCache->fUseCachedStringsDescriptors; + default: + return true; + } + + default: + return true; + } +} + + +/** + * Queues an URB for asynchronous transfer. + * A list of asynchronous URBs is kept by the roothub. + * + * @returns VBox status code (from pfnUrbQueue). + * @param pUrb The URB. + */ +int vusbUrbQueueAsyncRh(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbQueueAsyncRh", false); +#endif + + /* Immediately return in case of error. + * XXX There is still a race: The Rh might vanish after this point! */ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + if (!pRh) + { + Log(("vusbUrbQueueAsyncRh returning VERR_OBJECT_DESTROYED\n")); + return VERR_OBJECT_DESTROYED; + } + + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb); + if (RT_FAILURE(rc)) + { + LogFlow(("%s: vusbUrbQueueAsyncRh: returns %Rrc (queue_urb)\n", pUrb->pszDesc, rc)); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + return rc; + } + + ASMAtomicIncU32(&pDev->aPipes[pUrb->EndPt].async); + + /* Queue the Urb on the roothub */ + RTListAppend(&pDev->LstAsyncUrbs, &pUrb->pVUsb->NdLst); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + + return VINF_SUCCESS; +} + + +/** + * Send a control message *synchronously*. + * @return + */ +static void vusbMsgSubmitSynchronously(PVUSBURB pUrb, bool fSafeRequest) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + Assert(pDev); + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + LogFlow(("%s: vusbMsgSubmitSynchronously: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "")); + + uint8_t *pbData = (uint8_t *)pExtra->pMsg + sizeof(*pSetup); + uint32_t cbData = pSetup->wLength; + bool fOk = false; + if (!fSafeRequest) + fOk = vusbDevStandardRequest(pDev, pUrb->EndPt, pSetup, pbData, &cbData); + else + AssertMsgFailed(("oops\n")); + + pUrb->enmState = VUSBURBSTATE_REAPED; + if (fOk) + { + pSetup->wLength = cbData; + pUrb->enmStatus = VUSBSTATUS_OK; + pExtra->fOk = true; + } + else + { + pUrb->enmStatus = VUSBSTATUS_STALL; + pExtra->fOk = false; + } + pExtra->cbLeft = cbData; /* used by IN only */ + + vusbCtrlCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +} + +/** + * Callback for dealing with device reset. + */ +void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + pExtra->enmStage = CTLSTAGE_SETUP; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } +} + + +/** + * Callback to free a cancelled message URB. + * + * This is yet another place we're we have to performance acrobatics to + * deal with cancelled URBs. sigh. + * + * The deal here is that we never free message URBs since they are integrated + * into the message pipe state. But since cancel can leave URBs unreaped and in + * a state which require them not to be freed, we'll have to do two things. + * First, if a new message URB is processed we'll have to get a new message + * pipe state. Second, we cannot just free the damn state structure because + * that might lead to heap corruption since it might still be in-flight. + * + * The URB embedded into the message pipe control structure will start in an + * ALLOCATED state. When submitted it will be go to the IN-FLIGHT state. When + * reaped it will go from REAPED to ALLOCATED. When completed in the CANCELLED + * state it will remain in that state (as does normal URBs). + * + * If a new message urb comes up while it's in the CANCELLED state, we will + * orphan it and it will be freed here in vusbMsgFreeUrb. We indicate this + * by setting pVUsb->pvFreeCtx to NULL. + * + * If we have to free the message state structure because of device destruction, + * configuration changes, or similar, we will orphan the message pipe state in + * the same way by setting pVUsb->pvFreeCtx to NULL and let this function free it. + * + * @param pUrb + */ +static DECLCALLBACK(void) vusbMsgFreeUrb(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + PVUSBCTRLEXTRA pExtra = (PVUSBCTRLEXTRA)((uint8_t *)pUrb - RT_UOFFSETOF(VUSBCTRLEXTRA, Urb)); + if ( pUrb->enmState == VUSBURBSTATE_CANCELLED + && !pUrb->pVUsb->pvFreeCtx) + { + LogFlow(("vusbMsgFreeUrb: Freeing orphan: %p (pUrb=%p)\n", pExtra, pUrb)); + RTMemFree(pExtra); + } + else + { + Assert(pUrb->pVUsb->pvFreeCtx == &pExtra->Urb); + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } +} + +/** + * Frees the extra state data associated with a message pipe. + * + * @param pExtra The data. + */ +void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.u32Magic = 0; + pExtra->Urb.enmState = VUSBURBSTATE_FREE; + if (pExtra->Urb.pszDesc) + RTStrFree(pExtra->Urb.pszDesc); + RTMemFree(pExtra); + } + else + pExtra->Urb.pVUsb->pvFreeCtx = NULL; /* see vusbMsgFreeUrb */ +} + +/** + * Allocates the extra state data required for a control pipe. + * + * @returns Pointer to the allocated and initialized state data. + * @returns NULL on out of memory condition. + * @param pUrb A URB we can copy default data from. + */ +static PVUSBCTRLEXTRA vusbMsgAllocExtraData(PVUSBURB pUrb) +{ +/** @todo reuse these? */ + PVUSBCTRLEXTRA pExtra; + const size_t cbMax = sizeof(VUSBURBVUSBINT) + sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP); + pExtra = (PVUSBCTRLEXTRA)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbMax])); + if (pExtra) + { + pExtra->enmStage = CTLSTAGE_SETUP; + //pExtra->fOk = false; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->pbCur = (uint8_t *)(pExtra->pMsg + 1); + //pExtra->cbLeft = 0; + pExtra->cbMax = cbMax; + + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.u32Magic = VUSBURB_MAGIC; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +#ifdef LOG_ENABLED + RTStrAPrintf(&pExtra->Urb.pszDesc, "URB %p msg->%p", &pExtra->Urb, pUrb); +#endif + pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP)]; + //pExtra->Urb.pVUsb->pCtrlUrb = NULL; + //pExtra->Urb.pVUsb->pNext = NULL; + //pExtra->Urb.pVUsb->ppPrev = NULL; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->Urb.pVUsb->pDev = pUrb->pVUsb->pDev; + pExtra->Urb.pVUsb->pfnFree = vusbMsgFreeUrb; + pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; + //pExtra->Urb.Hci = {0}; + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.DstAddress = pUrb->DstAddress; + pExtra->Urb.EndPt = pUrb->EndPt; + pExtra->Urb.enmType = VUSBXFERTYPE_MSG; + pExtra->Urb.enmDir = VUSBDIRECTION_INVALID; + //pExtra->Urb.fShortNotOk = false; + pExtra->Urb.enmStatus = VUSBSTATUS_INVALID; + //pExtra->Urb.cbData = 0; + vusbUrbAssert(&pExtra->Urb); + } + return pExtra; +} + +/** + * Sets up the message. + * + * The message is associated with the pipe, in what's currently called + * control pipe extra state data (pointed to by pPipe->pCtrl). If this + * is a OUT message, we will no go on collecting data URB. If it's a + * IN message, we'll send it and then queue any incoming data for the + * URBs collecting it. + * + * @returns Success indicator. + */ +static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf) +{ + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf; + + /* + * Validate length. + */ + if (cbBuf < sizeof(VUSBSETUP)) + { + LogFlow(("vusbMsgSetup: pPipe=%p cbBuf=%u < %u (failure) !!!\n", + pPipe, cbBuf, sizeof(VUSBSETUP))); + return false; + } + + /* + * Check if we've got an cancelled message URB. Allocate a new one in that case. + */ + if (pExtra->Urb.enmState == VUSBURBSTATE_CANCELLED) + { + void *pvNew = RTMemDup(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax])); + if (!pvNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%zu\n", RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax]))); + return false; + } + pExtra->Urb.pVUsb->pvFreeCtx = NULL; + LogFlow(("vusbMsgSetup: Replacing canceled pExtra=%p with %p.\n", pExtra, pvNew)); + pPipe->pCtrl = pExtra = (PVUSBCTRLEXTRA)pvNew; + pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[sizeof(pExtra->Urb.abData) + sizeof(VUSBSETUP)]; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } + + /* + * Check that we've got sufficient space in the message URB. + */ + if (pExtra->cbMax < cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT)) + { + uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength + sizeof(VUSBURBVUSBINT), 1024); + PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemRealloc(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])); + if (!pNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n", + cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]))); + return false; + } + if (pExtra != pNew) + { + pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData; + pExtra = pNew; + } + pExtra->Urb.pVUsb = (PVUSBURBVUSB)&pExtra->Urb.abData[cbBuf + pSetupIn->wLength]; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->cbMax = cbReq; + } + Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED); + + /* + * Copy the setup data and prepare for data. + */ + PVUSBSETUP pSetup = pExtra->pMsg; + pExtra->fSubmitted = false; + pExtra->Urb.enmState = VUSBURBSTATE_IN_FLIGHT; + pExtra->pbCur = (uint8_t *)(pSetup + 1); + pSetup->bmRequestType = pSetupIn->bmRequestType; + pSetup->bRequest = pSetupIn->bRequest; + pSetup->wValue = RT_LE2H_U16(pSetupIn->wValue); + pSetup->wIndex = RT_LE2H_U16(pSetupIn->wIndex); + pSetup->wLength = RT_LE2H_U16(pSetupIn->wLength); + + LogFlow(("vusbMsgSetup(%p,,%d): bmRequestType=%#04x bRequest=%#04x wValue=%#06x wIndex=%#06x wLength=0x%.4x\n", + pPipe, cbBuf, pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + return true; +} + +/** + * Build the message URB from the given control URB and accompanying message + * pipe state which we grab from the device for the URB. + * + * @param pUrb The URB to submit. + * @param pSetup The setup packet for the message transfer. + * @param pExtra Pointer to the additional state requred for a control transfer. + * @param pPipe The message pipe state. + */ +static void vusbMsgDoTransfer(PVUSBURB pUrb, PVUSBSETUP pSetup, PVUSBCTRLEXTRA pExtra, PVUSBPIPE pPipe) +{ + RT_NOREF(pPipe); + + /* + * Mark this transfer as sent (cleared at setup time). + */ + Assert(!pExtra->fSubmitted); + pExtra->fSubmitted = true; + + /* + * Do we have to do this synchronously? + */ + bool fSafeRequest = vusbUrbIsRequestSafe(pSetup, pUrb); + if (!fSafeRequest) + { + vusbMsgSubmitSynchronously(pUrb, fSafeRequest); + return; + } + + /* + * Do it asynchronously. + */ + LogFlow(("%s: vusbMsgDoTransfer: ep=%d pMsgUrb=%p pPipe=%p stage=%s\n", + pUrb->pszDesc, pUrb->EndPt, &pExtra->Urb, pPipe, g_apszCtlStates[pExtra->enmStage])); + Assert(pExtra->Urb.enmType == VUSBXFERTYPE_MSG); + Assert(pExtra->Urb.EndPt == pUrb->EndPt); + pExtra->Urb.enmDir = (pSetup->bmRequestType & VUSB_DIR_TO_HOST) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + pExtra->Urb.cbData = pSetup->wLength + sizeof(*pSetup); + pExtra->Urb.pVUsb->pCtrlUrb = pUrb; + int rc = vusbUrbQueueAsyncRh(&pExtra->Urb); + if (RT_FAILURE(rc)) + { + /* + * If we fail submitting it, will not retry but fail immediately. + * + * This keeps things simple. The host OS will have retried if + * it's a proxied device, and if it's a virtual one it really means + * it if it's failing a control message. + */ + LogFlow(("%s: vusbMsgDoTransfer: failed submitting urb! failing it with %s (rc=%Rrc)!!!\n", + pUrb->pszDesc, rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? "DNR" : "CRC", rc)); + pExtra->Urb.enmStatus = rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? VUSBSTATUS_DNR : VUSBSTATUS_CRC; + pExtra->Urb.enmState = VUSBURBSTATE_REAPED; + vusbMsgCompletion(&pExtra->Urb); + } +} + +/** + * Fails a URB request with a pipe STALL error. + * + * @returns VINF_SUCCESS indicating that we've completed the URB. + * @param pUrb The URB in question. + */ +static int vusbMsgStall(PVUSBURB pUrb) +{ + PVUSBPIPE pPipe = &pUrb->pVUsb->pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbMsgStall: pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_STALL; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + +/** + * Submit a control message. + * + * Here we implement the USB defined traffic that occurs in message pipes + * (aka control endpoints). We want to provide a single function for device + * drivers so that they don't all have to reimplement the usb logic for + * themselves. This means we need to keep a little bit of state information + * because control transfers occur over multiple bus transactions. We may + * also need to buffer data over multiple data stages. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitCtrl(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbSubmitCtrl", false); +#endif + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (!pExtra && !(pExtra = pPipe->pCtrl = vusbMsgAllocExtraData(pUrb))) + { + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_VUSB_NO_URB_MEMORY; + } + PVUSBSETUP pSetup = pExtra->pMsg; + + if (pPipe->async) + { + AssertMsgFailed(("%u\n", pPipe->async)); + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_GENERAL_FAILURE; + } + + /* + * A setup packet always resets the transaction and the + * end of data transmission is signified by change in + * data direction. + */ + if (pUrb->enmDir == VUSBDIRECTION_SETUP) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_SETUP; + } + else if ( pExtra->enmStage == CTLSTAGE_DATA + /* (the STATUS stage direction goes the other way) */ + && !!(pSetup->bmRequestType & VUSB_DIR_TO_HOST) != (pUrb->enmDir == VUSBDIRECTION_IN)) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->STATUS\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_STATUS; + } + + /* + * Act according to the current message stage. + */ + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + /* + * When stall handshake is returned, all subsequent packets + * must generate stall until a setup packet arrives. + */ + if (pUrb->enmDir != VUSBDIRECTION_SETUP) + { + Log(("%s: vusbUrbSubmitCtrl: Stall at setup stage (dir=%#x)!!\n", pUrb->pszDesc, pUrb->enmDir)); + vusbMsgStall(pUrb); + break; + } + + /* Store setup details, return DNR if corrupt */ + if (!vusbMsgSetup(pPipe, pUrb->abData, pUrb->cbData)) + { + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + break; + } + if (pPipe->pCtrl != pExtra) + { + pExtra = pPipe->pCtrl; + pSetup = pExtra->pMsg; + } + + /* pre-buffer our output if it's device-to-host */ + if (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + else if (pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: need data\n", pUrb->pszDesc)); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgSetupCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + /* + * If there is no DATA stage, we must send it now since there are + * no requirement of a STATUS stage. + */ + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + + case CTLSTAGE_DATA: + { + /* + * If a data stage exceeds the target buffer indicated in + * setup return stall, if data stage returns stall there + * will be no status stage. + */ + uint8_t *pbData = (uint8_t *)(pExtra->pMsg + 1); + if (&pExtra->pbCur[pUrb->cbData] > &pbData[pSetup->wLength]) + { + if (!pSetup->wLength) /* happens during iPhone detection with iTunes (correct?) */ + { + Log(("%s: vusbUrbSubmitCtrl: pSetup->wLength == 0!! (iPhone)\n", pUrb->pszDesc)); + pSetup->wLength = pUrb->cbData; + } + + /* Variable length data transfers */ + if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + || pSetup->wLength == 0 + || (pUrb->cbData % pSetup->wLength) == 0) /* magic which need explaining... */ + { + uint8_t *pbEnd = pbData + pSetup->wLength; + int cbLeft = pbEnd - pExtra->pbCur; + LogFlow(("%s: vusbUrbSubmitCtrl: Var DATA, pUrb->cbData %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft)); + pUrb->cbData = cbLeft; + } + else + { + Log(("%s: vusbUrbSubmitCtrl: Stall at data stage!!\n", pUrb->pszDesc)); + vusbMsgStall(pUrb); + break; + } + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + /* put data received from the device. */ + const uint32_t cbRead = RT_MIN(pUrb->cbData, pExtra->cbLeft); + memcpy(pUrb->abData, pExtra->pbCur, cbRead); + + /* advance */ + pExtra->pbCur += cbRead; + if (pUrb->cbData == cbRead) + pExtra->cbLeft -= pUrb->cbData; + else + { + /* adjust the pUrb->cbData to reflect the number of bytes containing actual data. */ + LogFlow(("%s: vusbUrbSubmitCtrl: adjusting last DATA pUrb->cbData, %d -> %d\n", + pUrb->pszDesc, pUrb->cbData, pExtra->cbLeft)); + pUrb->cbData = cbRead; + pExtra->cbLeft = 0; + } + } + else + { + /* get data for sending when completed. */ + memcpy(pExtra->pbCur, pUrb->abData, pUrb->cbData); + + /* advance */ + pExtra->pbCur += pUrb->cbData; + + /* + * If we've got the necessary data, we'll send it now since there are + * no requirement of a STATUS stage. + */ + if ( !pExtra->fSubmitted + && pExtra->pbCur - pbData >= pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=DATA - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + break; + } + } + + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgDataCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + break; + } + + case CTLSTAGE_STATUS: + if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + || pExtra->fSubmitted) + { + Assert(pExtra->fSubmitted); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgStatusCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=STATUS - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + } + + RTCritSectLeave(&pPipe->CritSectCtrl); + return VINF_SUCCESS; +} + + +/** + * Submit a interrupt URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitInterrupt(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitInterrupt: (sync)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit a bulk URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitBulk(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitBulk: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit an isochronous URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitIsochronous(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitIsochronous: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Fail a URB with a 'hard-error' sort of error. + * + * @return VINF_SUCCESS (the Urb status indicates the error). + * @param pUrb The URB. + */ +int vusbUrbSubmitHardError(PVUSBURB pUrb) +{ + /* FIXME: Find out the correct return code from the spec */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + + +/** + * Submit a URB. + */ +int vusbUrbSubmit(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + Assert(pUrb->enmState == VUSBURBSTATE_ALLOCATED); + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = NULL; + Assert(pDev); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + pUrb->enmStatus = VUSBSTATUS_DNR; + /* This will postpone the TDs until we're done with the resetting. */ + return VERR_VUSB_DEVICE_IS_RESETTING; + } + +#ifdef LOG_ENABLED + /* stamp it */ + pUrb->pVUsb->u64SubmitTS = RTTimeNanoTS(); +#endif + + /** @todo Check max packet size here too? */ + + /* + * Validate the pipe. + */ + if (pUrb->EndPt >= VUSB_PIPE_MAX) + { + Log(("%s: pDev=%p[%s]: SUBMIT: ep %i >= %i!!!\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pUrb->EndPt, VUSB_PIPE_MAX)); + return vusbUrbSubmitHardError(pUrb); + } + PCVUSBDESCENDPOINTEX pEndPtDesc; + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].in; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + case VUSBDIRECTION_SETUP: + case VUSBDIRECTION_OUT: + default: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].out; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + } + if (!pEndPtDesc) + { + Log(("%s: pDev=%p[%s]: SUBMIT: no endpoint!!! dir=%s e=%i\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbDirName(pUrb->enmDir), pUrb->EndPt)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * Check for correct transfer types. + * Our type codes are the same - what a coincidence. + */ + if ((pEndPtDesc->Core.bmAttributes & 0x3) != pUrb->enmType) + { + /* Bulk and interrupt transfers are identical on the bus level (the only difference + * is in how they are scheduled by the HCD/HC) and need an exemption. + * Atheros AR9271 is a known offender; its configuration descriptors include + * interrupt endpoints, but drivers (Win7/8, Linux kernel pre-3.05) treat them + * as bulk endpoints. + */ + if ( (pUrb->enmType == VUSBXFERTYPE_BULK && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_INTR) + || (pUrb->enmType == VUSBXFERTYPE_INTR && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_BULK)) + { + Log2(("%s: pDev=%p[%s]: SUBMIT: mixing bulk/interrupt transfers on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + } + else + { + Log(("%s: pDev=%p[%s]: SUBMIT: %s transfer requested for %#x endpoint on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbTypeName(pUrb->enmType), pEndPtDesc->Core.bmAttributes, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + return vusbUrbSubmitHardError(pUrb); + } + } + + /* + * If there's a URB in the read-ahead buffer, use it. + */ + int rc; + + if (pDev->hSniffer) + { + rc = VUSBSnifferRecordEvent(pDev->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB submit event failed with %Rrc\n", rc)); + } + + /* + * Take action based on type. + */ + pUrb->enmState = VUSBURBSTATE_IN_FLIGHT; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: + rc = vusbUrbSubmitCtrl(pUrb); + break; + case VUSBXFERTYPE_BULK: + rc = vusbUrbSubmitBulk(pUrb); + break; + case VUSBXFERTYPE_INTR: + rc = vusbUrbSubmitInterrupt(pUrb); + break; + case VUSBXFERTYPE_ISOC: + rc = vusbUrbSubmitIsochronous(pUrb); + break; + default: + AssertMsgFailed(("Unexpected pUrb type %d\n", pUrb->enmType)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * The device was detached, so we fail everything. + * (We should really detach and destroy the device, but we'll have to wait till Main reacts.) + */ + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + rc = vusbUrbSubmitHardError(pUrb); + /* + * We don't increment error count if async URBs are in flight, in + * this case we just assume we need to throttle back, this also + * makes sure we don't halt bulk endpoints at the wrong time. + */ + else if ( RT_FAILURE(rc) + && !ASMAtomicReadU32(&pDev->aPipes[pUrb->EndPt].async) + /* && pUrb->enmType == VUSBXFERTYPE_BULK ?? */ + && !vusbUrbErrorRh(pUrb)) + { + /* don't retry it anymore. */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; + } + + return rc; +} + + +/** + * Reap in-flight URBs. + * + * @param pUrbLst Pointer to the head of the URB list. + * @param cMillies Number of milliseconds to block in each reap operation. + * Use 0 to not block at all. + */ +void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies) +{ + PVUSBURBVUSB pVUsbUrb = RTListGetFirst(pUrbLst, VUSBURBVUSBINT, NdLst); + while (pVUsbUrb) + { + vusbUrbAssert(pVUsbUrb->pUrb); + PVUSBURBVUSB pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, VUSBURBVUSBINT, NdLst); + PVUSBDEV pDev = pVUsbUrb->pDev; + + /* Don't touch resetting devices - paranoid safety precaution. */ + if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET) + { + /* + * Reap most URBs pending on a single device. + */ + PVUSBURB pRipe; + + /** + * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other + * and leaked URBs (shouldn't be affected by leaked URBs). + */ + Assert(pDev->pUsbIns); + while ( pDev->pUsbIns + && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL)) + { + vusbUrbAssert(pRipe); + if (pRipe == pVUsbUrbNext->pUrb) + pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, VUSBURBVUSBINT, NdLst); + vusbUrbRipe(pRipe); + } + } + + /* next */ + pVUsbUrb = pVUsbUrbNext; + } +} + +/** + * Reap URBs on a per device level. + * + * @returns nothing. + * @param pDev The device instance to reap URBs for. + * @param cMillies Number of milliseconds to block in each reap operation. + * Use 0 to not block at all. + */ +void vusbUrbDoReapAsyncDev(PVUSBDEV pDev, RTMSINTERVAL cMillies) +{ + Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET); + + /* + * Reap most URBs pending on a single device. + */ + PVUSBURB pRipe; + + /** + * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other + * and leaked URBs (shouldn't be affected by leaked URBs). + */ + + if (ASMAtomicXchgBool(&pDev->fWokenUp, false)) + return; + + Assert(pDev->pUsbIns); + while ( pDev->pUsbIns + && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL)) + { + vusbUrbAssert(pRipe); + vusbUrbRipe(pRipe); + if (ASMAtomicXchgBool(&pDev->fWokenUp, false)) + break; + } +} + +/** + * Completes the URB. + */ +static void vusbUrbCompletion(PVUSBURB pUrb) +{ + Assert(pUrb->pVUsb->pDev->aPipes); + ASMAtomicDecU32(&pUrb->pVUsb->pDev->aPipes[pUrb->EndPt].async); + + if (pUrb->enmState == VUSBURBSTATE_REAPED) + vusbUrbUnlink(pUrb); + + vusbUrbCompletionRh(pUrb); +} + +/** + * The worker for vusbUrbCancel() which is executed on the I/O thread. + * + * @returns IPRT status code. + * @param pUrb The URB to cancel. + * @param enmMode The way the URB should be canceled. + */ +DECLHIDDEN(int) vusbUrbCancelWorker(PVUSBURB pUrb, CANCELMODE enmMode) +{ + vusbUrbAssert(pUrb); +#ifdef VBOX_WITH_STATISTICS + PVUSBROOTHUB pRh = vusbDevGetRh(pUrb->pVUsb->pDev); +#endif + if (pUrb->enmState == VUSBURBSTATE_IN_FLIGHT) + { + LogFlow(("%s: vusbUrbCancel: Canceling in-flight\n", pUrb->pszDesc)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled); + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));}); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled); + } + + pUrb->enmState = VUSBURBSTATE_CANCELLED; + PPDMUSBINS pUsbIns = pUrb->pVUsb->pDev->pUsbIns; + pUsbIns->pReg->pfnUrbCancel(pUsbIns, pUrb); + Assert(pUrb->enmState == VUSBURBSTATE_CANCELLED || pUrb->enmState == VUSBURBSTATE_REAPED); + + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletion(pUrb); + } + else if (pUrb->enmState == VUSBURBSTATE_REAPED) + { + LogFlow(("%s: vusbUrbCancel: Canceling reaped urb\n", pUrb->pszDesc)); + STAM_COUNTER_INC(&pRh->Total.StatUrbsCancelled); + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + STAM_STATS({Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes));}); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsCancelled); + } + + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletion(pUrb); + } + else + { + AssertMsg(pUrb->enmState == VUSBURBSTATE_CANCELLED, ("Invalid state %d, pUrb=%p\n", pUrb->enmState, pUrb)); + switch (enmMode) + { + default: + AssertMsgFailed(("Invalid cancel mode\n")); + RT_FALL_THRU(); + case CANCELMODE_FAIL: + pUrb->enmStatus = VUSBSTATUS_CRC; + break; + case CANCELMODE_UNDO: + pUrb->enmStatus = VUSBSTATUS_UNDO; + break; + + } + } + return VINF_SUCCESS; +} + +/** + * Cancels an URB with CRC failure. + * + * Cancelling an URB is a tricky thing. The USBProxy backend can not + * all cancel it and we must keep the URB around until it's ripe and + * can be reaped the normal way. However, we must complete the URB + * now, before leaving this function. This is not nice. sigh. + * + * This function will cancel the URB if it's in-flight and complete + * it. The device will in its pfnCancel method be given the chance to + * say that the URB doesn't need reaping and should be unlinked. + * + * An URB which is in the cancel state after pfnCancel will remain in that + * state and in the async list until its reaped. When it's finally reaped + * it will be unlinked and freed without doing any completion. + * + * There are different modes of canceling an URB. When devices are being + * disconnected etc., they will be completed with an error (CRC). However, + * when the HC needs to temporarily halt communication with a device, the + * URB/TD must be left alone if possible. + * + * @param pUrb The URB to cancel. + * @param mode The way the URB should be canceled. + */ +void vusbUrbCancel(PVUSBURB pUrb, CANCELMODE mode) +{ + int rc = vusbDevIoThreadExecSync(pUrb->pVUsb->pDev, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode); + AssertRC(rc); +} + + +/** + * Async version of vusbUrbCancel() - doesn't wait for the cancelling to be complete. + */ +void vusbUrbCancelAsync(PVUSBURB pUrb, CANCELMODE mode) +{ + /* Don't try to cancel the URB when completion is in progress at the moment. */ + if (!ASMAtomicXchgBool(&pUrb->fCompleting, true)) + { + int rc = vusbDevIoThreadExec(pUrb->pVUsb->pDev, 0 /* fFlags */, (PFNRT)vusbUrbCancelWorker, 2, pUrb, mode); + AssertRC(rc); + } +} + + +/** + * Deals with a ripe URB (i.e. after reaping it). + * + * If an URB is in the reaped or in-flight state, we'll + * complete it. If it's cancelled, we'll simply free it. + * Any other states should never get here. + * + * @param pUrb The URB. + */ +void vusbUrbRipe(PVUSBURB pUrb) +{ + if ( pUrb->enmState == VUSBURBSTATE_IN_FLIGHT + || pUrb->enmState == VUSBURBSTATE_REAPED) + { + pUrb->enmState = VUSBURBSTATE_REAPED; + if (!ASMAtomicXchgBool(&pUrb->fCompleting, true)) + vusbUrbCompletion(pUrb); + } + else if (pUrb->enmState == VUSBURBSTATE_CANCELLED) + { + vusbUrbUnlink(pUrb); + LogFlow(("%s: vusbUrbRipe: Freeing cancelled URB\n", pUrb->pszDesc)); + pUrb->pVUsb->pfnFree(pUrb); + } + else + AssertMsgFailed(("Invalid URB state %d; %s\n", pUrb->enmState, pUrb->pszDesc)); +} + + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/VUSBUrbPool.cpp b/src/VBox/Devices/USB/VUSBUrbPool.cpp new file mode 100644 index 00000000..3e390342 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrbPool.cpp @@ -0,0 +1,243 @@ +/* $Id: VUSBUrbPool.cpp $ */ +/** @file + * Virtual USB - URB pool. + */ + +/* + * Copyright (C) 2016-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/log.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/critsect.h> + +#include "VUSBInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** Maximum age for one URB. */ +#define VUSBURB_AGE_MAX 10 + +/** Convert from an URB to the URB header. */ +#define VUSBURBPOOL_URB_2_URBHDR(a_pUrb) RT_FROM_MEMBER(a_pUrb, VUSBURBHDR, Urb); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * URB header not visible to the caller allocating an URB + * and only for internal tracking. + */ +typedef struct VUSBURBHDR +{ + /** List node for keeping the URB in the free list. */ + RTLISTNODE NdFree; + /** Size of the data allocated for the URB (Only the variable part including the + * HCI and TDs). */ + size_t cbAllocated; + /** Age of the URB waiting on the list, if it is waiting for too long without being used + * again it will be freed. */ + uint32_t cAge; +#if HC_ARCH_BITS == 64 + uint32_t u32Alignment0; +#endif + /** The embedded URB. */ + VUSBURB Urb; +} VUSBURBHDR; +/** Pointer to a URB header. */ +typedef VUSBURBHDR *PVUSBURBHDR; + +AssertCompileSizeAlignment(VUSBURBHDR, 8); + + +/********************************************************************************************************************************* +* Static Variables * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +DECLHIDDEN(int) vusbUrbPoolInit(PVUSBURBPOOL pUrbPool) +{ + int rc = RTCritSectInit(&pUrbPool->CritSectPool); + if (RT_SUCCESS(rc)) + { + pUrbPool->cUrbsInPool = 0; + for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++) + RTListInit(&pUrbPool->aLstFreeUrbs[i]); + } + + return rc; +} + + +DECLHIDDEN(void) vusbUrbPoolDestroy(PVUSBURBPOOL pUrbPool) +{ + RTCritSectEnter(&pUrbPool->CritSectPool); + for (unsigned i = 0; i < RT_ELEMENTS(pUrbPool->aLstFreeUrbs); i++) + { + PVUSBURBHDR pHdr, pHdrNext; + RTListForEachSafe(&pUrbPool->aLstFreeUrbs[i], pHdr, pHdrNext, VUSBURBHDR, NdFree) + { + RTListNodeRemove(&pHdr->NdFree); + + pHdr->cbAllocated = 0; + pHdr->Urb.u32Magic = 0; + pHdr->Urb.enmState = VUSBURBSTATE_INVALID; + RTMemFree(pHdr); + } + } + RTCritSectLeave(&pUrbPool->CritSectPool); + RTCritSectDelete(&pUrbPool->CritSectPool); +} + + +DECLHIDDEN(PVUSBURB) vusbUrbPoolAlloc(PVUSBURBPOOL pUrbPool, VUSBXFERTYPE enmType, + VUSBDIRECTION enmDir, size_t cbData, size_t cbHci, + size_t cbHciTd, unsigned cTds) +{ + Assert((uint32_t)cbData == cbData); + Assert((uint32_t)cbHci == cbHci); + + /* + * Reuse or allocate a new URB. + */ + /** @todo The allocations should be done by the device, at least as an option, since the devices + * frequently wish to associate their own stuff with the in-flight URB or need special buffering + * (isochronous on Darwin for instance). */ + /* Get the required amount of additional memory to allocate the whole state. */ + size_t cbMem = cbData + sizeof(VUSBURBVUSBINT) + cbHci + cTds * cbHciTd; + + AssertReturn((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs), NULL); + + RTCritSectEnter(&pUrbPool->CritSectPool); + PVUSBURBHDR pHdr = NULL; + PVUSBURBHDR pIt, pItNext; + RTListForEachSafe(&pUrbPool->aLstFreeUrbs[enmType], pIt, pItNext, VUSBURBHDR, NdFree) + { + if (pIt->cbAllocated >= cbMem) + { + RTListNodeRemove(&pIt->NdFree); + Assert(pIt->Urb.u32Magic == VUSBURB_MAGIC); + Assert(pIt->Urb.enmState == VUSBURBSTATE_FREE); + /* + * If the allocation is far too big we increase the age counter too + * so we don't waste memory for a lot of small transfers + */ + if (pIt->cbAllocated >= 2 * cbMem) + pIt->cAge++; + else + pIt->cAge = 0; + pHdr = pIt; + break; + } + else + { + /* Increase age and free if it reached a threshold. */ + pIt->cAge++; + if (pIt->cAge == VUSBURB_AGE_MAX) + { + RTListNodeRemove(&pIt->NdFree); + ASMAtomicDecU32(&pUrbPool->cUrbsInPool); + RTMemFree(pIt); + } + } + } + + if (!pHdr) + { + /* allocate a new one. */ + size_t cbDataAllocated = cbMem <= _4K ? RT_ALIGN_32(cbMem, _1K) + : cbMem <= _32K ? RT_ALIGN_32(cbMem, _4K) + : RT_ALIGN_32(cbMem, 16*_1K); + + pHdr = (PVUSBURBHDR)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBURBHDR, Urb.abData[cbDataAllocated])); + if (RT_UNLIKELY(!pHdr)) + { + RTCritSectLeave(&pUrbPool->CritSectPool); + AssertLogRelFailedReturn(NULL); + } + + pHdr->cbAllocated = cbDataAllocated; + pHdr->cAge = 0; + ASMAtomicIncU32(&pUrbPool->cUrbsInPool); + } + RTCritSectLeave(&pUrbPool->CritSectPool); + + Assert(pHdr->cbAllocated >= cbMem); + + /* + * (Re)init the URB + */ + uint32_t offAlloc = (uint32_t)cbData; + PVUSBURB pUrb = &pHdr->Urb; + pUrb->u32Magic = VUSBURB_MAGIC; + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + pUrb->pszDesc = NULL; + pUrb->pVUsb = (PVUSBURBVUSB)&pUrb->abData[offAlloc]; + offAlloc += sizeof(VUSBURBVUSBINT); + pUrb->pVUsb->pUrb = pUrb; + pUrb->pVUsb->pvFreeCtx = NULL; + pUrb->pVUsb->pfnFree = NULL; + pUrb->pVUsb->pCtrlUrb = NULL; + pUrb->pVUsb->u64SubmitTS = 0; + pUrb->Dev.pvPrivate = NULL; + pUrb->Dev.pNext = NULL; + pUrb->EndPt = UINT8_MAX; + pUrb->enmType = enmType; + pUrb->enmDir = enmDir; + pUrb->fShortNotOk = false; + pUrb->enmStatus = VUSBSTATUS_INVALID; + pUrb->cbData = (uint32_t)cbData; + pUrb->pHci = cbHci ? (PVUSBURBHCI)&pUrb->abData[offAlloc] : NULL; + offAlloc += (uint32_t)cbHci; + pUrb->paTds = (cbHciTd && cTds) ? (PVUSBURBHCITD)&pUrb->abData[offAlloc] : NULL; + + return pUrb; +} + + +DECLHIDDEN(void) vusbUrbPoolFree(PVUSBURBPOOL pUrbPool, PVUSBURB pUrb) +{ + PVUSBURBHDR pHdr = VUSBURBPOOL_URB_2_URBHDR(pUrb); + + /* URBs which aged too much because they are too big are freed. */ + if (pHdr->cAge == VUSBURB_AGE_MAX) + { + ASMAtomicDecU32(&pUrbPool->cUrbsInPool); + RTMemFree(pHdr); + } + else + { + /* Put it into the list of free URBs. */ + VUSBXFERTYPE enmType = pUrb->enmType; + AssertReturnVoid((size_t)enmType < RT_ELEMENTS(pUrbPool->aLstFreeUrbs)); + RTCritSectEnter(&pUrbPool->CritSectPool); + pUrb->enmState = VUSBURBSTATE_FREE; + RTListAppend(&pUrbPool->aLstFreeUrbs[enmType], &pHdr->NdFree); + RTCritSectLeave(&pUrbPool->CritSectPool); + } +} + diff --git a/src/VBox/Devices/USB/VUSBUrbTrace.cpp b/src/VBox/Devices/USB/VUSBUrbTrace.cpp new file mode 100644 index 00000000..8044938a --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrbTrace.cpp @@ -0,0 +1,809 @@ +/* $Id: VUSBUrbTrace.cpp $ */ +/** @file + * Virtual USB - URBs. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_VUSB +#include <VBox/vmm/pdm.h> +#include <VBox/vmm/vmapi.h> +#include <iprt/errcore.h> +#include <iprt/alloc.h> +#include <VBox/log.h> +#include <iprt/time.h> +#include <iprt/thread.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/env.h> +#include "VUSBInternal.h" + + + +#ifdef LOG_ENABLED +DECLINLINE(const char *) GetScsiErrCd(uint8_t ScsiErr) +{ + switch (ScsiErr) + { + case 0: return "?"; + } + return "?"; +} + +DECLINLINE(const char *) GetScsiKCQ(uint8_t Key, uint8_t ASC, uint8_t ASCQ) +{ + switch (Key) + { + case 0: + switch (RT_MAKE_U16(ASC, ASCQ)) + { + case RT_MAKE_U16(0x00, 0x00): return "No error"; + } + break; + + case 1: + return "Soft Error"; + + case 2: + return "Not Ready"; + + case 3: + return "Medium Error"; + + case 4: + return "Hard Error"; + + case 5: + return "Illegal Request"; + + case 6: + return "Unit Attention"; + + case 7: + return "Write Protected"; + + case 0xb: + return "Aborted Command"; + } + return "?"; +} + +DECLHIDDEN(const char *) vusbUrbStatusName(VUSBSTATUS enmStatus) +{ + /** Strings for the URB statuses. */ + static const char * const s_apszNames[] = + { + "OK", + "STALL", + "ERR_DNR", + "ERR_CRC", + "DATA_UNDERRUN", + "DATA_OVERRUN", + "NOT_ACCESSED", + "7", "8", "9", "10", "11", "12", "13", "14", "15" + }; + + return enmStatus < (int)RT_ELEMENTS(s_apszNames) + ? s_apszNames[enmStatus] + : enmStatus == VUSBSTATUS_INVALID + ? "INVALID" + : "??"; +} + +DECLHIDDEN(const char *) vusbUrbDirName(VUSBDIRECTION enmDir) +{ + /** Strings for the URB directions. */ + static const char * const s_apszNames[] = + { + "setup", + "in", + "out" + }; + + return enmDir < (int)RT_ELEMENTS(s_apszNames) + ? s_apszNames[enmDir] + : "??"; +} + +DECLHIDDEN(const char *) vusbUrbTypeName(VUSBXFERTYPE enmType) +{ + /** Strings for the URB types. */ + static const char * const s_apszName[] = + { + "control-part", + "isochronous", + "bulk", + "interrupt", + "control" + }; + + return enmType < (int)RT_ELEMENTS(s_apszName) + ? s_apszName[enmType] + : "??"; +} + +/** + * Logs an URB. + * + * Note that pUrb->pVUsb->pDev and pUrb->pVUsb->pDev->pUsbIns can all be NULL. + */ +DECLHIDDEN(void) vusbUrbTrace(PVUSBURB pUrb, const char *pszMsg, bool fComplete) +{ + PVUSBDEV pDev = pUrb->pVUsb ? pUrb->pVUsb->pDev : NULL; /* Can be NULL when called from usbProxyConstruct and friends. */ + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + const uint8_t *pbData = pUrb->abData; + uint32_t cbData = pUrb->cbData; + PCVUSBSETUP pSetup = NULL; + bool fDescriptors = false; + static size_t s_cchMaxMsg = 10; + size_t cchMsg = strlen(pszMsg); + if (cchMsg > s_cchMaxMsg) + s_cchMaxMsg = cchMsg; + + Log(("%s: %*s: pDev=%p[%s] rc=%s a=%i e=%u d=%s t=%s cb=%#x(%d) ts=%RU64 (%RU64 ns ago) %s\n", + pUrb->pszDesc, s_cchMaxMsg, pszMsg, + pDev, + pUrb->pVUsb && pUrb->pVUsb->pDev ? pUrb->pVUsb->pDev->pUsbIns->pszName : "", + vusbUrbStatusName(pUrb->enmStatus), + pDev ? pDev->u8Address : -1, + pUrb->EndPt, + vusbUrbDirName(pUrb->enmDir), + vusbUrbTypeName(pUrb->enmType), + pUrb->cbData, + pUrb->cbData, + pUrb->pVUsb ? pUrb->pVUsb->u64SubmitTS : 0, + pUrb->pVUsb ? RTTimeNanoTS() - pUrb->pVUsb->u64SubmitTS : 0, + pUrb->fShortNotOk ? "ShortNotOk" : "ShortOk")); + +#ifndef DEBUG_bird + if ( pUrb->enmType == VUSBXFERTYPE_CTRL + && pUrb->enmStatus == VUSBSTATUS_OK) + return; +#endif + + if ( pUrb->enmType == VUSBXFERTYPE_MSG + || ( pUrb->enmDir == VUSBDIRECTION_SETUP + && pUrb->enmType == VUSBXFERTYPE_CTRL + && cbData)) + { + static const char * const s_apszReqDirs[] = {"host2dev", "dev2host"}; + static const char * const s_apszReqTypes[] = {"std", "class", "vendor", "reserved"}; + static const char * const s_apszReqRecipients[] = {"dev", "if", "endpoint", "other"}; + static const char * const s_apszRequests[] = + { + "GET_STATUS", "CLEAR_FEATURE", "2?", "SET_FEATURE", + "4?", "SET_ADDRESS", "GET_DESCRIPTOR", "SET_DESCRIPTOR", + "GET_CONFIGURATION", "SET_CONFIGURATION", "GET_INTERFACE", "SET_INTERFACE", + "SYNCH_FRAME" + }; + pSetup = (PVUSBSETUP)pUrb->abData; + pbData += sizeof(*pSetup); + cbData -= sizeof(*pSetup); + + Log(("%s: %*s: CTRL: bmRequestType=0x%.2x (%s %s %s) bRequest=0x%.2x (%s) wValue=0x%.4x wIndex=0x%.4x wLength=0x%.4x\n", + pUrb->pszDesc, s_cchMaxMsg, pszMsg, + pSetup->bmRequestType, s_apszReqDirs[pSetup->bmRequestType >> 7], s_apszReqTypes[(pSetup->bmRequestType >> 5) & 0x3], + (unsigned)(pSetup->bmRequestType & 0xf) < RT_ELEMENTS(s_apszReqRecipients) ? s_apszReqRecipients[pSetup->bmRequestType & 0xf] : "??", + pSetup->bRequest, pSetup->bRequest < RT_ELEMENTS(s_apszRequests) ? s_apszRequests[pSetup->bRequest] : "??", + pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + + if ( pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR + && fComplete + && pUrb->enmStatus == VUSBSTATUS_OK + && ((pSetup->bmRequestType >> 5) & 0x3) < 2 /* vendor */) + fDescriptors = true; + } + else if ( fComplete + && pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->enmType == VUSBXFERTYPE_CTRL + && pUrb->enmStatus == VUSBSTATUS_OK + && pPipe->pCtrl + && pPipe->pCtrl->enmStage == CTLSTAGE_DATA + && cbData > 0) + { + pSetup = pPipe->pCtrl->pMsg; + if (pSetup->bRequest == VUSB_REQ_GET_DESCRIPTOR) + fDescriptors = true; + } + + /* + * Dump descriptors. + */ + if (fDescriptors) + { + const uint8_t *pb = pbData; + const uint8_t *pbEnd = pbData + cbData; + while (pb + 1 < pbEnd) + { + const unsigned cbLeft = pbEnd - pb; + const unsigned cbLength = *pb; + unsigned cb = cbLength; + uint8_t bDescriptorType = pb[1]; + + /* length out of bounds? */ + if (cbLength > cbLeft) + { + cb = cbLeft; + if (cbLength != 0xff) /* ignore this */ + Log(("URB: %*s: DESC: warning descriptor length goes beyond the end of the URB! cbLength=%d cbLeft=%d\n", + s_cchMaxMsg, pszMsg, cbLength, cbLeft)); + } + + if (cb >= 2) + { + Log(("URB: %*s: DESC: %04x: %25s = %#04x (%d)\n" + "URB: %*s: %04x: %25s = %#04x (", + s_cchMaxMsg, pszMsg, pb - pbData, "bLength", cbLength, cbLength, + s_cchMaxMsg, pszMsg, pb - pbData + 1, "bDescriptorType", bDescriptorType)); + + #pragma pack(1) + #define BYTE_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \ + Log(("URB: %*s: %04x: %25s = %#04x\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)])) + #define BYTE_FIELD_START(strct, memb) do { \ + if ((unsigned)RT_OFFSETOF(strct, memb) < cb) \ + { \ + Log(("URB: %*s: %04x: %25s = %#04x", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, pb[RT_OFFSETOF(strct, memb)])) + #define BYTE_FIELD_END(strct, memb) \ + Log(("\n")); \ + } } while (0) + #define WORD_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \ + Log(("URB: %*s: %04x: %25s = %#06x\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)])) + #define BCD_FIELD(strct, memb) \ + if ((unsigned)RT_OFFSETOF(strct, memb) + 1 < cb) \ + Log(("URB: %*s: %04x: %25s = %#06x (%02x.%02x)\n", s_cchMaxMsg, pszMsg, \ + pb + RT_OFFSETOF(strct, memb) - pbData, #memb, *(uint16_t *)&pb[RT_OFFSETOF(strct, memb)], \ + pb[RT_OFFSETOF(strct, memb) + 1], pb[RT_OFFSETOF(strct, memb)])) + #define SIZE_CHECK(strct) \ + if (cb > sizeof(strct)) \ + Log(("URB: %*s: %04x: WARNING %d extra byte(s) %.*Rhxs\n", s_cchMaxMsg, pszMsg, \ + pb + sizeof(strct) - pbData, cb - sizeof(strct), cb - sizeof(strct), pb + sizeof(strct))); \ + else if (cb < sizeof(strct)) \ + Log(("URB: %*s: %04x: WARNING %d missing byte(s)! Expected size %d.\n", s_cchMaxMsg, pszMsg, \ + pb + cb - pbData, sizeof(strct) - cb, sizeof(strct))) + + /* on type */ + switch (bDescriptorType) + { + case VUSB_DT_DEVICE: + { + struct dev_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; + } *pDesc = (struct dev_desc *)pb; NOREF(pDesc); + Log(("DEV)\n")); + BCD_FIELD( struct dev_desc, bcdUSB); + BYTE_FIELD(struct dev_desc, bDeviceClass); + BYTE_FIELD(struct dev_desc, bDeviceSubClass); + BYTE_FIELD(struct dev_desc, bDeviceProtocol); + BYTE_FIELD(struct dev_desc, bMaxPacketSize0); + WORD_FIELD(struct dev_desc, idVendor); + WORD_FIELD(struct dev_desc, idProduct); + BCD_FIELD( struct dev_desc, bcdDevice); + BYTE_FIELD(struct dev_desc, iManufacturer); + BYTE_FIELD(struct dev_desc, iProduct); + BYTE_FIELD(struct dev_desc, iSerialNumber); + BYTE_FIELD(struct dev_desc, bNumConfigurations); + SIZE_CHECK(struct dev_desc); + break; + } + + case VUSB_DT_CONFIG: + { + struct cfg_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t MaxPower; + } *pDesc = (struct cfg_desc *)pb; NOREF(pDesc); + Log(("CFG)\n")); + WORD_FIELD(struct cfg_desc, wTotalLength); + BYTE_FIELD(struct cfg_desc, bNumInterfaces); + BYTE_FIELD(struct cfg_desc, bConfigurationValue); + BYTE_FIELD(struct cfg_desc, iConfiguration); + BYTE_FIELD_START(struct cfg_desc, bmAttributes); + static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; + static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" }; + static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" }; + Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)], + s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)])); + BYTE_FIELD_END(struct cfg_desc, bmAttributes); + BYTE_FIELD(struct cfg_desc, MaxPower); + SIZE_CHECK(struct cfg_desc); + break; + } + + case VUSB_DT_STRING: + if (!pSetup->wIndex) + { + /* langid array */ + uint16_t *pu16 = (uint16_t *)pb + 1; + Log(("LANGIDs)\n")); + while ((uintptr_t)pu16 + 2 - (uintptr_t)pb <= cb) + { + Log(("URB: %*s: %04x: wLANGID[%#x] = %#06x\n", + s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, pu16 - (uint16_t *)pb, *pu16)); + pu16++; + } + if (cb & 1) + Log(("URB: %*s: %04x: WARNING descriptor size is odd! extra byte: %02\n", + s_cchMaxMsg, pszMsg, (uint8_t *)pu16 - pbData, *(uint8_t *)pu16)); + } + else + { + /** a string. */ + Log(("STRING)\n")); + if (cb > 2) + Log(("URB: %*s: %04x: Length=%d String=%.*ls\n", + s_cchMaxMsg, pszMsg, pb - pbData, cb - 2, cb / 2 - 1, pb + 2)); + else + Log(("URB: %*s: %04x: Length=0!\n", s_cchMaxMsg, pszMsg, pb - pbData)); + } + break; + + case VUSB_DT_INTERFACE: + { + struct if_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bInterfaceNumber; + uint8_t bAlternateSetting; + uint8_t bNumEndpoints; + uint8_t bInterfaceClass; + uint8_t bInterfaceSubClass; + uint8_t bInterfaceProtocol; + uint8_t iInterface; + } *pDesc = (struct if_desc *)pb; NOREF(pDesc); + Log(("IF)\n")); + BYTE_FIELD(struct if_desc, bInterfaceNumber); + BYTE_FIELD(struct if_desc, bAlternateSetting); + BYTE_FIELD(struct if_desc, bNumEndpoints); + BYTE_FIELD(struct if_desc, bInterfaceClass); + BYTE_FIELD(struct if_desc, bInterfaceSubClass); + BYTE_FIELD(struct if_desc, bInterfaceProtocol); + BYTE_FIELD(struct if_desc, iInterface); + SIZE_CHECK(struct if_desc); + break; + } + + case VUSB_DT_ENDPOINT: + { + struct ep_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint8_t bEndpointAddress; + uint8_t bmAttributes; + uint16_t wMaxPacketSize; + uint8_t bInterval; + } *pDesc = (struct ep_desc *)pb; NOREF(pDesc); + Log(("EP)\n")); + BYTE_FIELD(struct ep_desc, bEndpointAddress); + BYTE_FIELD(struct ep_desc, bmAttributes); + WORD_FIELD(struct ep_desc, wMaxPacketSize); + BYTE_FIELD(struct ep_desc, bInterval); + SIZE_CHECK(struct ep_desc); + break; + } + + case VUSB_DT_DEVICE_QUALIFIER: + { + struct dq_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint8_t bNumConfigurations; + uint8_t bReserved; + } *pDQDesc = (struct dq_desc *)pb; NOREF(pDQDesc); + Log(("DEVQ)\n")); + BCD_FIELD( struct dq_desc, bcdUSB); + BYTE_FIELD(struct dq_desc, bDeviceClass); + BYTE_FIELD(struct dq_desc, bDeviceSubClass); + BYTE_FIELD(struct dq_desc, bDeviceProtocol); + BYTE_FIELD(struct dq_desc, bMaxPacketSize0); + BYTE_FIELD(struct dq_desc, bNumConfigurations); + BYTE_FIELD(struct dq_desc, bReserved); + SIZE_CHECK(struct dq_desc); + break; + } + + case VUSB_DT_OTHER_SPEED_CFG: + { + struct oth_cfg_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t wTotalLength; + uint8_t bNumInterfaces; + uint8_t bConfigurationValue; + uint8_t iConfiguration; + uint8_t bmAttributes; + uint8_t MaxPower; + } *pDesc = (struct oth_cfg_desc *)pb; NOREF(pDesc); + Log(("OCFG)\n")); + WORD_FIELD(struct oth_cfg_desc, wTotalLength); + BYTE_FIELD(struct oth_cfg_desc, bNumInterfaces); + BYTE_FIELD(struct oth_cfg_desc, bConfigurationValue); + BYTE_FIELD(struct oth_cfg_desc, iConfiguration); + BYTE_FIELD_START(struct oth_cfg_desc, bmAttributes); + static const char * const s_apszTransType[4] = { "Control", "Isochronous", "Bulk", "Interrupt" }; + static const char * const s_apszSyncType[4] = { "NoSync", "Asynchronous", "Adaptive", "Synchronous" }; + static const char * const s_apszUsageType[4] = { "Data ep", "Feedback ep.", "Implicit feedback Data ep.", "Reserved" }; + Log((" %s - %s - %s", s_apszTransType[(pDesc->bmAttributes & 0x3)], + s_apszSyncType[((pDesc->bmAttributes >> 2) & 0x3)], s_apszUsageType[((pDesc->bmAttributes >> 4) & 0x3)])); + BYTE_FIELD_END(struct oth_cfg_desc, bmAttributes); + BYTE_FIELD(struct oth_cfg_desc, MaxPower); + SIZE_CHECK(struct oth_cfg_desc); + break; + } + + case 0x21: + { + struct hid_desc + { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdHid; + uint8_t bCountry; + uint8_t bNumDescriptors; + uint8_t bReportType; + uint16_t wReportLength; + } *pDesc = (struct hid_desc *)pb; NOREF(pDesc); + Log(("EP)\n")); + BCD_FIELD( struct hid_desc, bcdHid); + BYTE_FIELD(struct hid_desc, bCountry); + BYTE_FIELD(struct hid_desc, bNumDescriptors); + BYTE_FIELD(struct hid_desc, bReportType); + WORD_FIELD(struct hid_desc, wReportLength); + SIZE_CHECK(struct hid_desc); + break; + } + + case 0xff: + Log(("UNKNOWN-ignore)\n")); + break; + + default: + Log(("UNKNOWN)!!!\n")); + break; + } + + #undef BYTE_FIELD + #undef WORD_FIELD + #undef BCD_FIELD + #undef SIZE_CHECK + #pragma pack() + } + else + { + Log(("URB: %*s: DESC: %04x: bLength=%d bDescriptorType=%d - invalid length\n", + s_cchMaxMsg, pszMsg, pb - pbData, cb, bDescriptorType)); + break; + } + + /* next */ + pb += cb; + } + } + + /* + * SCSI + */ + if ( pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_OUT + && pUrb->cbData >= 12 + && !memcmp(pUrb->abData, "USBC", 4)) + { + const struct usbc + { + uint32_t Signature; + uint32_t Tag; + uint32_t DataTransferLength; + uint8_t Flags; + uint8_t Lun; + uint8_t Length; + uint8_t CDB[13]; + } *pUsbC = (struct usbc *)pUrb->abData; + Log(("URB: %*s: SCSI: Tag=%#x DataTransferLength=%#x Flags=%#x Lun=%#x Length=%#x CDB=%.*Rhxs\n", + s_cchMaxMsg, pszMsg, pUsbC->Tag, pUsbC->DataTransferLength, pUsbC->Flags, pUsbC->Lun, + pUsbC->Length, pUsbC->Length, pUsbC->CDB)); + const uint8_t *pb = &pUsbC->CDB[0]; + switch (pb[0]) + { + case 0x00: /* test unit read */ + Log(("URB: %*s: SCSI: TEST_UNIT_READY LUN=%d Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[5])); + break; + case 0x03: /* Request Sense command */ + Log(("URB: %*s: SCSI: REQUEST_SENSE LUN=%d AlcLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, pb[4], pb[5])); + break; + case 0x12: /* Inquiry command. */ + Log(("URB: %*s: SCSI: INQUIRY EVPD=%d LUN=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] & 1, pb[1] >> 5, pb[2], pb[4], pb[5])); + break; + case 0x1a: /* Mode Sense(6) command */ + Log(("URB: %*s: SCSI: MODE_SENSE6 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX8 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f, pb[4], pb[5])); + break; + case 0x5a: + Log(("URB: %*s: SCSI: MODE_SENSE10 LUN=%d DBD=%d PC=%d PgCd=%#RX8 AlcLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, pb[1] >> 5, !!(pb[1] & RT_BIT(3)), pb[2] >> 6, pb[2] & 0x3f, + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0x25: /* Read Capacity(6) command. */ + Log(("URB: %*s: SCSI: READ_CAPACITY\n", + s_cchMaxMsg, pszMsg)); + break; + case 0x28: /* Read(10) command. */ + Log(("URB: %*s: SCSI: READ10 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0xa8: /* Read(12) command. */ + Log(("URB: %*s: SCSI: READ12 RelAdr=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]), + pb[11])); + break; + case 0x3e: /* Read Long command. */ + Log(("URB: %*s: SCSI: READ LONG RelAdr=%d Correct=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(1)), pb[1] >> 5, + RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]), + pb[11])); + break; + case 0x2a: /* Write(10) command. */ + Log(("URB: %*s: SCSI: WRITE10 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(2)), !!(pb[1] & RT_BIT(3)), + !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U16(pb[8], pb[7]), pb[9])); + break; + case 0xaa: /* Write(12) command. */ + Log(("URB: %*s: SCSI: WRITE12 RelAdr=%d EBP=%d FUA=%d DPO=%d LUN=%d LBA=%#RX32 Len=%#RX32 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, !!(pb[1] & RT_BIT(3)), !!(pb[1] & RT_BIT(4)), + !!(pb[1] & RT_BIT(4)), pb[1] >> 5, + RT_MAKE_U32_FROM_U8(pb[5], pb[4], pb[3], pb[2]), + RT_MAKE_U32_FROM_U8(pb[9], pb[8], pb[7], pb[6]), + pb[11])); + break; + case 0x3f: /* Write Long command. */ + Log(("URB: %*s: SCSI: WRITE LONG RelAdr=%d LUN=%d LBA=%#RX16 ByteLen=%#RX16 Ctrl=%#RX8\n", + s_cchMaxMsg, pszMsg, + pb[1] & 1, pb[1] >> 5, + RT_MAKE_U16(pb[3], pb[2]), RT_MAKE_U16(pb[6], pb[5]), + pb[11])); + break; + case 0x35: /* Synchronize Cache(10) command. */ + Log(("URB: %*s: SCSI: SYNCHRONIZE_CACHE10\n", + s_cchMaxMsg, pszMsg)); + break; + case 0xa0: /* Report LUNs command. */ + Log(("URB: %*s: SCSI: REPORT_LUNS\n", + s_cchMaxMsg, pszMsg)); + break; + default: + Log(("URB: %*s: SCSI: cmd=%#x\n", + s_cchMaxMsg, pszMsg, pb[0])); + break; + } + if (pDev) + pDev->Urb.u8ScsiCmd = pb[0]; + } + else if ( fComplete + && pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->cbData >= 12 + && !memcmp(pUrb->abData, "USBS", 4)) + { + const struct usbs + { + uint32_t Signature; + uint32_t Tag; + uint32_t DataResidue; + uint8_t Status; + uint8_t CDB[3]; + } *pUsbS = (struct usbs *)pUrb->abData; + static const char * const s_apszStatuses[] = { "PASSED", "FAILED", "PHASE ERROR", "RESERVED" }; + Log(("URB: %*s: SCSI: Tag=%#x DataResidue=%#RX32 Status=%#RX8 %s\n", + s_cchMaxMsg, pszMsg, pUsbS->Tag, pUsbS->DataResidue, pUsbS->Status, + s_apszStatuses[pUsbS->Status < RT_ELEMENTS(s_apszStatuses) ? pUsbS->Status : RT_ELEMENTS(s_apszStatuses) - 1])); + if (pDev) + pDev->Urb.u8ScsiCmd = 0xff; + } + else if ( fComplete + && pUrb->enmType == VUSBXFERTYPE_BULK + && pUrb->enmDir == VUSBDIRECTION_IN + && pDev + && pDev->Urb.u8ScsiCmd != 0xff) + { + const uint8_t *pb = pUrb->abData; + switch (pDev->Urb.u8ScsiCmd) + { + case 0x03: /* REQUEST_SENSE */ + Log(("URB: %*s: SCSI: RESPONSE: REQUEST_SENSE (%s)\n", + s_cchMaxMsg, pszMsg, pb[0] & 7 ? "scsi compliant" : "not scsi compliant")); + Log(("URB: %*s: SCSI: ErrCd=%#RX8 (%s) Seg=%#RX8 Filemark=%d EOM=%d ILI=%d\n", + s_cchMaxMsg, pszMsg, pb[0] & 0x7f, GetScsiErrCd(pb[0] & 0x7f), pb[1], + pb[2] >> 7, !!(pb[2] & RT_BIT(6)), !!(pb[2] & RT_BIT(5)))); + Log(("URB: %*s: SCSI: SenseKey=%#x ASC=%#RX8 ASCQ=%#RX8 : %s\n", + s_cchMaxMsg, pszMsg, pb[2] & 0xf, pb[12], pb[13], + GetScsiKCQ(pb[2] & 0xf, pb[12], pb[13]))); + /** @todo more later */ + break; + + case 0x12: /* INQUIRY. */ + { + unsigned cb = pb[4] + 5; + Log(("URB: %*s: SCSI: RESPONSE: INQUIRY\n" + "URB: %*s: SCSI: PeripheralQualifier=%d PeripheralType=%#RX8 RMB=%d DevTypeMod=%#RX8\n", + s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg, + pb[0] >> 5, pb[0] & 0x1f, pb[1] >> 7, pb[1] & 0x7f)); + Log(("URB: %*s: SCSI: ISOVer=%d ECMAVer=%d ANSIVer=%d\n", + s_cchMaxMsg, pszMsg, pb[2] >> 6, (pb[2] >> 3) & 7, pb[2] & 7)); + Log(("URB: %*s: SCSI: AENC=%d TrmlOP=%d RespDataFmt=%d (%s) AddLen=%d\n", + s_cchMaxMsg, pszMsg, pb[3] >> 7, (pb[3] >> 6) & 1, + pb[3] & 0xf, pb[3] & 0xf ? "legacy" : "scsi", pb[4])); + if (cb < 8) + break; + Log(("URB: %*s: SCSI: RelAdr=%d WBus32=%d WBus16=%d Sync=%d Linked=%d CmdQue=%d SftRe=%d\n", + s_cchMaxMsg, pszMsg, pb[7] >> 7, !!(pb[7] >> 6), !!(pb[7] >> 5), !!(pb[7] >> 4), + !!(pb[7] >> 3), !!(pb[7] >> 1), pb[7] & 1)); + if (cb < 16) + break; + Log(("URB: %*s: SCSI: VendorId=%.8s\n", s_cchMaxMsg, pszMsg, &pb[8])); + if (cb < 32) + break; + Log(("URB: %*s: SCSI: ProductId=%.16s\n", s_cchMaxMsg, pszMsg, &pb[16])); + if (cb < 36) + break; + Log(("URB: %*s: SCSI: ProdRevLvl=%.4s\n", s_cchMaxMsg, pszMsg, &pb[32])); + if (cb > 36) + Log(("URB: %*s: SCSI: VendorSpecific=%.*s\n", + s_cchMaxMsg, pszMsg, RT_MIN(cb - 36, 20), &pb[36])); + if (cb > 96) + Log(("URB: %*s: SCSI: VendorParam=%.*Rhxs\n", + s_cchMaxMsg, pszMsg, cb - 96, &pb[96])); + break; + } + + case 0x25: /* Read Capacity(6) command. */ + Log(("URB: %*s: SCSI: RESPONSE: READ_CAPACITY\n" + "URB: %*s: SCSI: LBA=%#RX32 BlockLen=%#RX32\n", + s_cchMaxMsg, pszMsg, s_cchMaxMsg, pszMsg, + RT_MAKE_U32_FROM_U8(pb[3], pb[2], pb[1], pb[0]), + RT_MAKE_U32_FROM_U8(pb[7], pb[6], pb[5], pb[4]))); + break; + } + + pDev->Urb.u8ScsiCmd = 0xff; + } + + /* + * The Quickcam control pipe. + */ + if ( pSetup + && ((pSetup->bmRequestType >> 5) & 0x3) >= 2 /* vendor */ + && (fComplete || !(pSetup->bmRequestType >> 7)) + && pDev + && pDev->pDescCache + && pDev->pDescCache->pDevice + && pDev->pDescCache->pDevice->idVendor == 0x046d + && ( pDev->pDescCache->pDevice->idProduct == 0x8f6 + || pDev->pDescCache->pDevice->idProduct == 0x8f5 + || pDev->pDescCache->pDevice->idProduct == 0x8f0) + ) + { + pbData = (const uint8_t *)(pSetup + 1); + cbData = pUrb->cbData - sizeof(*pSetup); + + if ( pSetup->bRequest == 0x04 + && pSetup->wIndex == 0 + && (cbData == 1 || cbData == 2)) + { + /* the value */ + unsigned uVal = pbData[0]; + if (cbData > 1) + uVal |= (unsigned)pbData[1] << 8; + + const char *pszReg = NULL; + switch (pSetup->wValue) + { + case 0: pszReg = "i2c init"; break; + case 0x0423: pszReg = "STV_REG23"; break; + case 0x0509: pszReg = "RED something"; break; + case 0x050a: pszReg = "GREEN something"; break; + case 0x050b: pszReg = "BLUE something"; break; + case 0x143f: pszReg = "COMMIT? INIT DONE?"; break; + case 0x1440: pszReg = "STV_ISO_ENABLE"; break; + case 0x1442: pszReg = uVal & (RT_BIT(7)|RT_BIT(5)) ? "BUTTON PRESSED" : "BUTTON" ; break; + case 0x1443: pszReg = "STV_SCAN_RATE"; break; + case 0x1445: pszReg = "LED?"; break; + case 0x1500: pszReg = "STV_REG00"; break; + case 0x1501: pszReg = "STV_REG01"; break; + case 0x1502: pszReg = "STV_REG02"; break; + case 0x1503: pszReg = "STV_REG03"; break; + case 0x1504: pszReg = "STV_REG04"; break; + case 0x15c1: pszReg = "STV_ISO_SIZE"; break; + case 0x15c3: pszReg = "STV_Y_CTRL"; break; + case 0x1680: pszReg = "STV_X_CTRL"; break; + case 0xe00a: pszReg = "ProductId"; break; + default: pszReg = "[no clue]"; break; + } + if (pszReg) + Log(("URB: %*s: QUICKCAM: %s %#x (%d) %s '%s' (%#x)\n", + s_cchMaxMsg, pszMsg, + (pSetup->bmRequestType >> 7) ? "read" : "write", uVal, uVal, (pSetup->bmRequestType >> 7) ? "from" : "to", + pszReg, pSetup->wValue)); + } + else if (cbData) + Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: %.*Rhxs\n", s_cchMaxMsg, pszMsg, + pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex, cbData, pbData)); + else + Log(("URB: %*s: QUICKCAM: Unknown request: bRequest=%#x bmRequestType=%#x wValue=%#x wIndex=%#x: (no data)\n", s_cchMaxMsg, pszMsg, + pSetup->bRequest, pSetup->bmRequestType, pSetup->wValue, pSetup->wIndex)); + } + +#if 1 + if ( cbData /** @todo Fix RTStrFormatV to communicate .* so formatter doesn't apply defaults when cbData=0. */ + && (fComplete + ? pUrb->enmDir != VUSBDIRECTION_OUT + : pUrb->enmDir == VUSBDIRECTION_OUT)) + Log3(("%16.*Rhxd\n", cbData, pbData)); +#endif + if (pUrb->enmType == VUSBXFERTYPE_MSG && pUrb->pVUsb && pUrb->pVUsb->pCtrlUrb) + vusbUrbTrace(pUrb->pVUsb->pCtrlUrb, "NESTED MSG", fComplete); +} +#endif /* LOG_ENABLED */ + diff --git a/src/VBox/Devices/USB/darwin/Makefile.kup b/src/VBox/Devices/USB/darwin/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/darwin/Makefile.kup diff --git a/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp new file mode 100644 index 00000000..475e0981 --- /dev/null +++ b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp @@ -0,0 +1,2006 @@ +/* $Id: USBProxyDevice-darwin.cpp $ */ +/** @file + * USB device proxy - the Darwin backend. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#define __STDC_LIMIT_MACROS +#define __STDC_CONSTANT_MACROS + +#include <mach/mach.h> +#include <Carbon/Carbon.h> +#include <IOKit/IOKitLib.h> +#include <mach/mach_error.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/list.h> +#include <iprt/mem.h> +#include <iprt/once.h> +#include <iprt/string.h> +#include <iprt/time.h> + +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** An experiment... */ +//#define USE_LOW_LATENCY_API 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Forward declaration of the Darwin interface structure. */ +typedef struct USBPROXYIFOSX *PUSBPROXYIFOSX; + + +/** + * A low latency isochronous buffer. + * + * These are allocated in chunks on an interface level, see USBPROXYISOCBUFCOL. + */ +typedef struct USBPROXYISOCBUF +{ + /** Whether this buffer is in use or not. */ + bool volatile fUsed; + /** Pointer to the buffer. */ + void *pvBuf; + /** Pointer to an array of 8 frames. */ + IOUSBLowLatencyIsocFrame *paFrames; +} USBPROXYISOCBUF, *PUSBPROXYISOCBUF; + + +/** + * Isochronous buffer collection (associated with an interface). + * + * These are allocated in decent sized chunks and there isn't supposed + * to be too many of these per interface. + */ +typedef struct USBPROXYISOCBUFCOL +{ + /** Write or Read buffers? */ + USBLowLatencyBufferType enmType; + /** The next buffer collection on this interface. */ + struct USBPROXYISOCBUFCOL *pNext; + /** The buffer. */ + void *pvBuffer; + /** The frame. */ + void *pvFrames; + /** The buffers. + * The number of buffers here is decided by pvFrame begin allocated in + * PAGE_SIZE chunks. The size of IOUSBLowLatencyIsocFrame is 16 bytes + * and we require 8 of those per buffer. PAGE_SIZE / (16 * 8) = 32. + * @remarks Don't allocate too many as it may temporarily halt the system if + * some pool is low / exhausted. (Contiguous memory woes on mach.) + */ + USBPROXYISOCBUF aBuffers[/*32*/ 4]; +} USBPROXYISOCBUFCOL, *PUSBPROXYISOCBUFCOL; + +AssertCompileSize(IOUSBLowLatencyIsocFrame, 16); + +/** + * Per-urb data for the Darwin usb proxy backend. + * + * This is required to track in-flight and landed URBs + * since we take down the URBs in a different thread (perhaps). + */ +typedef struct USBPROXYURBOSX +{ + /** Pointer to the next Darwin URB. */ + struct USBPROXYURBOSX *pNext; + /** Pointer to the previous Darwin URB. */ + struct USBPROXYURBOSX *pPrev; + /** The millisecond timestamp when this URB was submitted. */ + uint64_t u64SubmitTS; + /** Pointer to the VUSB URB. + * This is set to NULL if canceled. */ + PVUSBURB pVUsbUrb; + /** Pointer to the Darwin device. */ + struct USBPROXYDEVOSX *pDevOsX; + /** The transfer type. */ + VUSBXFERTYPE enmType; + /** Union with data depending on transfer type. */ + union + { + /** The control message. */ + IOUSBDevRequest ControlMsg; + /** The Isochronous Data. */ + struct + { +#ifdef USE_LOW_LATENCY_API + /** The low latency isochronous buffer. */ + PUSBPROXYISOCBUF pBuf; + /** Array of frames parallel to the one in VUSBURB. (Same as pBuf->paFrames.) */ + IOUSBLowLatencyIsocFrame *aFrames; +#else + /** Array of frames parallel to the one in VUSBURB. */ + IOUSBIsocFrame aFrames[8]; +#endif + } Isoc; + } u; +} USBPROXYURBOSX, *PUSBPROXYURBOSX; + +/** + * Per-pipe data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYPIPEOSX +{ + /** The endpoint number. */ + uint8_t u8Endpoint; + /** The IOKit pipe reference. */ + uint8_t u8PipeRef; + /** The pipe Transfer type type. */ + uint8_t u8TransferType; + /** The pipe direction. */ + uint8_t u8Direction; + /** The endpoint interval. (interrupt) */ + uint8_t u8Interval; + /** Full-speed device indicator (isochronous pipes only). */ + bool fIsFullSpeed; + /** The max packet size. */ + uint16_t u16MaxPacketSize; + /** The next frame number (isochronous pipes only). */ + uint64_t u64NextFrameNo; +} USBPROXYPIPEOSX, *PUSBPROXYPIPEOSX, **PPUSBPROXYPIPEOSX; + +typedef struct RUNLOOPREFLIST +{ + RTLISTNODE List; + CFRunLoopRef RunLoopRef; +} RUNLOOPREFLIST, *PRUNLOOPREFLIST; +typedef RUNLOOPREFLIST **PPRUNLOOPREFLIST; + +/** + * Per-interface data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYIFOSX +{ + /** Pointer to the next interface. */ + struct USBPROXYIFOSX *pNext; + /** The interface number. */ + uint8_t u8Interface; + /** The current alternative interface setting. + * This is used to skip unnecessary SetAltInterface calls. */ + uint8_t u8AltSetting; + /** The interface class. (not really used) */ + uint8_t u8Class; + /** The interface protocol. (not really used) */ + uint8_t u8Protocol; + /** The number of pipes. */ + uint8_t cPipes; + /** Array containing all the pipes. (Currently unsorted.) */ + USBPROXYPIPEOSX aPipes[kUSBMaxPipes]; + /** The IOUSBDeviceInterface. */ + IOUSBInterfaceInterface245 **ppIfI; + /** The run loop source for the async operations on the interface level. */ + CFRunLoopSourceRef RunLoopSrcRef; + /** List of isochronous buffer collections. + * These are allocated on demand by the URB queuing routine and then recycled until the interface is destroyed. */ + RTLISTANCHOR HeadOfRunLoopLst; + PUSBPROXYISOCBUFCOL pIsocBufCols; +} USBPROXYIFOSX, *PUSBPROXYIFOSX, **PPUSBPROXYIFOSX; +/** Pointer to a pointer to an darwin interface. */ +typedef USBPROXYIFOSX **PPUSBPROXYIFOSX; + +/** + * Per-device Data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYDEVOSX +{ + /** The USB Device IOService object. */ + io_object_t USBDevice; + /** The IOUSBDeviceInterface. */ + IOUSBDeviceInterface245 **ppDevI; + /** The run loop source for the async operations on the device level + * (i.e. the default control pipe stuff). */ + CFRunLoopSourceRef RunLoopSrcRef; + /** we want to add and remove RunLoopSourceRefs to run loop's of + * every EMT thread participated in USB processing. */ + RTLISTANCHOR HeadOfRunLoopLst; + /** Pointer to the proxy device instance. */ + PUSBPROXYDEV pProxyDev; + + /** Pointer to the first interface. */ + PUSBPROXYIFOSX pIfHead; + /** Pointer to the last interface. */ + PUSBPROXYIFOSX pIfTail; + + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** The list of free Darwin URBs. Singly linked. */ + PUSBPROXYURBOSX pFreeHead; + /** The list of landed Darwin URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBOSX pTaxingHead; + /** The tail of the landed Darwin URBs. */ + PUSBPROXYURBOSX pTaxingTail; + /** Last reaper runloop reference, there can be only one runloop at a time. */ + CFRunLoopRef hRunLoopReapingLast; + /** Runloop source for waking up the reaper thread. */ + CFRunLoopSourceRef hRunLoopSrcWakeRef; + /** List of threads used for reaping which can be woken up. */ + RTLISTANCHOR HeadOfRunLoopWakeLst; + /** Runloop reference of the thread reaping. */ + volatile CFRunLoopRef hRunLoopReaping; + /** Flag whether the reaping thread is about the be waked. */ + volatile bool fReapingThreadWake; +} USBPROXYDEVOSX, *PUSBPROXYDEVOSX; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTONCE g_usbProxyDarwinOnce = RTONCE_INITIALIZER; +/** The runloop mode we use. + * Since it's difficult to remove this, we leak it to prevent crashes. + * @bugref{4407} */ +static CFStringRef g_pRunLoopMode = NULL; +/** The IO Master Port. + * Not worth cleaning up. */ +static mach_port_t g_MasterPort = MACH_PORT_NULL; + + +/** + * Init once callback that sets up g_MasterPort and g_pRunLoopMode. + * + * @returns IPRT status code. + * + * @param pvUser1 NULL, ignored. + */ +static DECLCALLBACK(int32_t) usbProxyDarwinInitOnce(void *pvUser1) +{ + RT_NOREF(pvUser1); + + int rc; + kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort); + if (krc == KERN_SUCCESS) + { + g_pRunLoopMode = CFStringCreateWithCString(kCFAllocatorDefault, "VBoxUsbProxyMode", kCFStringEncodingUTF8); + if (g_pRunLoopMode) + return VINF_SUCCESS; + rc = VERR_INTERNAL_ERROR_5; + } + else + rc = RTErrConvertFromDarwin(krc); + return rc; +} + +/** + * Kicks the reaper thread if it sleeps currently to respond to state changes + * or to pick up completed URBs. + * + * @returns nothing. + * @param pDevOsX The darwin device instance data. + */ +static void usbProxyDarwinReaperKick(PUSBPROXYDEVOSX pDevOsX) +{ + CFRunLoopRef hRunLoopWake = (CFRunLoopRef)ASMAtomicReadPtr((void * volatile *)&pDevOsX->hRunLoopReaping); + if (hRunLoopWake) + { + LogFlowFunc(("Waking runloop %p\n", hRunLoopWake)); + CFRunLoopSourceSignal(pDevOsX->hRunLoopSrcWakeRef); + CFRunLoopWakeUp(hRunLoopWake); + } +} + +/** + * Adds Source ref to current run loop and adds it the list of runloops. + */ +static int usbProxyDarwinAddRunLoopRef(PRTLISTANCHOR pListHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pListHead, VERR_INVALID_PARAMETER); + AssertReturn(CFRunLoopSourceIsValid(SourceRef), VERR_INVALID_PARAMETER); + + if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), SourceRef, g_pRunLoopMode)) + return VINF_SUCCESS; + + /* Add to the list */ + PRUNLOOPREFLIST pListNode = (PRUNLOOPREFLIST)RTMemAllocZ(sizeof(RUNLOOPREFLIST)); + if (!pListNode) + return VERR_NO_MEMORY; + + pListNode->RunLoopRef = CFRunLoopGetCurrent(); + + CFRetain(pListNode->RunLoopRef); + CFRetain(SourceRef); /* We want to be aware of releasing */ + + CFRunLoopAddSource(pListNode->RunLoopRef, SourceRef, g_pRunLoopMode); + + RTListInit(&pListNode->List); + + RTListAppend((PRTLISTNODE)pListHead, &pListNode->List); + + return VINF_SUCCESS; +} + + +/* + * Removes all source reference from mode of run loop's we've registered them. + * + */ +static int usbProxyDarwinRemoveSourceRefFromAllRunLoops(PRTLISTANCHOR pHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pHead, VERR_INVALID_PARAMETER); + + while (!RTListIsEmpty(pHead)) + { + PRUNLOOPREFLIST pNode = RTListGetFirst(pHead, RUNLOOPREFLIST, List); + /* XXX: Should Release Reference? */ + Assert(CFGetRetainCount(pNode->RunLoopRef)); + + CFRunLoopRemoveSource(pNode->RunLoopRef, SourceRef, g_pRunLoopMode); + CFRelease(SourceRef); + CFRelease(pNode->RunLoopRef); + + RTListNodeRemove(&pNode->List); + + RTMemFree(pNode); + } + + return VINF_SUCCESS; +} + + +/** + * Allocates a Darwin URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * + * @param pDevOsX The darwin proxy device. + */ +static PUSBPROXYURBOSX usbProxyDarwinUrbAlloc(PUSBPROXYDEVOSX pDevOsX) +{ + PUSBPROXYURBOSX pUrbOsX; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Try remove a Darwin URB from the free list, if none there allocate a new one. + */ + pUrbOsX = pDevOsX->pFreeHead; + if (pUrbOsX) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTCritSectLeave(&pDevOsX->CritSect); + } + else + { + RTCritSectLeave(&pDevOsX->CritSect); + pUrbOsX = (PUSBPROXYURBOSX)RTMemAlloc(sizeof(*pUrbOsX)); + if (!pUrbOsX) + return NULL; + } + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + return pUrbOsX; +} + + +#ifdef USE_LOW_LATENCY_API +/** + * Allocates an low latency isochronous buffer. + * + * @returns VBox status code. + * @param pUrbOsX The OsX URB to allocate it for. + * @param pIf The interface to allocated it from. + */ +static int usbProxyDarwinUrbAllocIsocBuf(PUSBPROXYURBOSX pUrbOsX, PUSBPROXYIFOSX pIf) +{ + USBLowLatencyBufferType enmLLType = pUrbOsX->pVUsbUrb->enmDir == VUSBDIRECTION_IN + ? kUSBLowLatencyWriteBuffer : kUSBLowLatencyReadBuffer; + + /* + * Walk the buffer collection list and look for an unused one. + */ + pUrbOsX->u.Isoc.pBuf = NULL; + for (PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; pCur; pCur = pCur->pNext) + if (pCur->enmType == enmLLType) + for (unsigned i = 0; i < RT_ELEMENTS(pCur->aBuffers); i++) + if (!pCur->aBuffers[i].fUsed) + { + pCur->aBuffers[i].fUsed = true; + pUrbOsX->u.Isoc.pBuf = &pCur->aBuffers[i]; + AssertPtr(pUrbOsX->u.Isoc.pBuf); + AssertPtr(pUrbOsX->u.Isoc.pBuf->pvBuf); + pUrbOsX->u.Isoc.aFrames = pCur->aBuffers[i].paFrames; + AssertPtr(pUrbOsX->u.Isoc.aFrames); + return VINF_SUCCESS; + } + + /* + * Didn't find an empty one, create a new buffer collection and take the first buffer. + */ + PUSBPROXYISOCBUFCOL pNew = (PUSBPROXYISOCBUFCOL)RTMemAllocZ(sizeof(*pNew)); + AssertReturn(pNew, VERR_NO_MEMORY); + + IOReturn irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvBuffer, 8192 * RT_ELEMENTS(pNew->aBuffers), enmLLType); + if (irc == kIOReturnSuccess != VALID_PTR(pNew->pvBuffer)) + { + AssertPtr(pNew->pvBuffer); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvFrames, PAGE_SIZE, kUSBLowLatencyFrameListBuffer); + if (irc == kIOReturnSuccess != VALID_PTR(pNew->pvFrames)) + { + AssertPtr(pNew->pvFrames); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + for (unsigned i = 0; i < RT_ELEMENTS(pNew->aBuffers); i++) + { + //pNew->aBuffers[i].fUsed = false; + pNew->aBuffers[i].paFrames = &((IOUSBLowLatencyIsocFrame *)pNew->pvFrames)[i * 8]; + pNew->aBuffers[i].pvBuf = (uint8_t *)pNew->pvBuffer + i * 8192; + } + + pNew->aBuffers[0].fUsed = true; + pUrbOsX->u.Isoc.aFrames = pNew->aBuffers[0].paFrames; + pUrbOsX->u.Isoc.pBuf = &pNew->aBuffers[0]; + + pNew->enmType = enmLLType; + pNew->pNext = pIf->pIsocBufCols; + pIf->pIsocBufCols = pNew; + +#if 0 /* doesn't help :-/ */ + /* + * If this is the first time we're here, try mess with the policy? + */ + if (!pNew->pNext) + for (unsigned iPipe = 0; iPipe < pIf->cPipes; iPipe++) + if (pIf->aPipes[iPipe].u8TransferType == kUSBIsoc) + { + irc = (*pIf->ppIfI)->SetPipePolicy(pIf->ppIfI, pIf->aPipes[iPipe].u8PipeRef, + pIf->aPipes[iPipe].u16MaxPacketSize, pIf->aPipes[iPipe].u8Interval); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } +#endif + + return VINF_SUCCESS; + } + + /* bail out */ + (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pNew->pvBuffer); + } + AssertMsgFailed(("%#x\n", irc)); + RTMemFree(pNew); + + return RTErrConvertFromDarwin(irc); +} +#endif /* USE_LOW_LATENCY_API */ + + +/** + * Frees a Darwin URB request structure. + * + * @param pDevOsX The darwin proxy device. + * @param pUrbOsX The Darwin URB to free. + */ +static void usbProxyDarwinUrbFree(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYURBOSX pUrbOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + +#ifdef USE_LOW_LATENCY_API + /* + * Free low latency stuff. + */ + if ( pUrbOsX->enmType == VUSBXFERTYPE_ISOC + && pUrbOsX->u.Isoc.pBuf) + { + pUrbOsX->u.Isoc.pBuf->fUsed = false; + pUrbOsX->u.Isoc.pBuf = NULL; + } +#endif + + /* + * Link it into the free list. + */ + pUrbOsX->pPrev = NULL; + pUrbOsX->pNext = pDevOsX->pFreeHead; + pDevOsX->pFreeHead = pUrbOsX; + + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = NULL; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + RTCritSectLeave(&pDevOsX->CritSect); +} + +/** + * Translate the IOKit status code to a VUSB status. + * + * @returns VUSB URB status code. + * @param irc IOKit status code. + */ +static VUSBSTATUS vusbProxyDarwinStatusToVUsbStatus(IOReturn irc) +{ + switch (irc) + { + /* IOKit OHCI VUSB */ + case kIOReturnSuccess: /* 0 */ return VUSBSTATUS_OK; + case kIOUSBCRCErr: /* 1 */ return VUSBSTATUS_CRC; + //case kIOUSBBitstufErr: /* 2 */ return VUSBSTATUS_; + //case kIOUSBDataToggleErr: /* 3 */ return VUSBSTATUS_; + case kIOUSBPipeStalled: /* 4 */ return VUSBSTATUS_STALL; + case kIOReturnNotResponding: /* 5 */ return VUSBSTATUS_DNR; + //case kIOUSBPIDCheckErr: /* 6 */ return VUSBSTATUS_; + //case kIOUSBWrongPIDErr: /* 7 */ return VUSBSTATUS_; + case kIOReturnOverrun: /* 8 */ return VUSBSTATUS_DATA_OVERRUN; + case kIOReturnUnderrun: /* 9 */ return VUSBSTATUS_DATA_UNDERRUN; + //case kIOUSBReserved1Err: /* 10 */ return VUSBSTATUS_; + //case kIOUSBReserved2Err: /* 11 */ return VUSBSTATUS_; + //case kIOUSBBufferOverrunErr: /* 12 */ return VUSBSTATUS_; + //case kIOUSBBufferUnderrunErr: /* 13 */ return VUSBSTATUS_; + case kIOUSBNotSent1Err: /* 14 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + case kIOUSBNotSent2Err: /* 15 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + + /* Other errors */ + case kIOUSBTransactionTimeout: return VUSBSTATUS_DNR; + //case kIOReturnAborted: return VUSBSTATUS_CRC; - see on SET_INTERFACE... + + default: + Log(("vusbProxyDarwinStatusToVUsbStatus: irc=%#x!!\n", irc)); + return VUSBSTATUS_STALL; + } +} + + +/** + * Completion callback for an async URB transfer. + * + * @param pvUrbOsX The Darwin URB. + * @param irc The status of the operation. + * @param Size Possible the transfer size. + */ +static void usbProxyDarwinUrbAsyncComplete(void *pvUrbOsX, IOReturn irc, void *Size) +{ + PUSBPROXYURBOSX pUrbOsX = (PUSBPROXYURBOSX)pvUrbOsX; + PUSBPROXYDEVOSX pDevOsX = pUrbOsX->pDevOsX; + const uint32_t cb = (uintptr_t)Size; + + /* + * Do status updates. + */ + PVUSBURB pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + Assert(pUrb->u32Magic == VUSBURB_MAGIC); + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { +#ifdef USE_LOW_LATENCY_API + /* copy the data. */ + //if (pUrb->enmDir == VUSBDIRECTION_IN) + memcpy(pUrb->abData, pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->cbData); +#endif + Log3(("AsyncComplete isoc - raw data (%d bytes):\n" + "%16.*Rhxd\n", pUrb->cbData, pUrb->cbData, pUrb->abData)); + uint32_t off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { +#ifdef USE_LOW_LATENCY_API + Log2((" %d{%d/%d-%x-%RX64}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus, + RT_MAKE_U64(pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo, pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi) )); +#else + Log2((" %d{%d/%d-%x}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus)); +#endif + pUrb->aIsocPkts[i].enmStatus = vusbProxyDarwinStatusToVUsbStatus(pUrbOsX->u.Isoc.aFrames[i].frStatus); + pUrb->aIsocPkts[i].cb = pUrbOsX->u.Isoc.aFrames[i].frActCount; + off += pUrbOsX->u.Isoc.aFrames[i].frActCount; + } + Log2(("\n")); +#if 0 /** @todo revisit this, wasn't working previously. */ + for (int i = (int)pUrb->cIsocPkts - 1; i >= 0; i--) + Assert( !pUrbOsX->u.Isoc.aFrames[i].frActCount + && !pUrbOsX->u.Isoc.aFrames[i].frReqCount + && !pUrbOsX->u.Isoc.aFrames[i].frStatus); +#endif + pUrb->cbData = off; /* 'Size' seems to be pointing at an error code or something... */ + pUrb->enmStatus = VUSBSTATUS_OK; /* Don't use 'irc'. OHCI expects OK unless it's a really bad error. */ + } + else + { + pUrb->cbData = cb; + pUrb->enmStatus = vusbProxyDarwinStatusToVUsbStatus(irc); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + pUrb->cbData += sizeof(VUSBSETUP); + } + } + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Link it into the taxing list. + */ + pUrbOsX->pNext = NULL; + pUrbOsX->pPrev = pDevOsX->pTaxingTail; + if (pDevOsX->pTaxingTail) + pDevOsX->pTaxingTail->pNext = pUrbOsX; + else + pDevOsX->pTaxingHead = pUrbOsX; + pDevOsX->pTaxingTail = pUrbOsX; + + RTCritSectLeave(&pDevOsX->CritSect); + + LogFlow(("%s: usbProxyDarwinUrbAsyncComplete: cb=%d EndPt=%#x irc=%#x (%d)\n", + pUrb->pszDesc, cb, pUrb ? pUrb->EndPt : 0xff, irc, pUrb ? pUrb->enmStatus : 0xff)); +} + +/** + * Release all interfaces (current config). + * + * @param pDevOsX The darwin proxy device. + */ +static void usbProxyDarwinReleaseAllInterfaces(PUSBPROXYDEVOSX pDevOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + + /* Kick the reaper thread out of sleep. */ + usbProxyDarwinReaperKick(pDevOsX); + + PUSBPROXYIFOSX pIf = pDevOsX->pIfHead; + pDevOsX->pIfHead = pDevOsX->pIfTail = NULL; + + while (pIf) + { + PUSBPROXYIFOSX pNext = pIf->pNext; + IOReturn irc; + + if (pIf->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops((PRTLISTANCHOR)&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + AssertRC(rc); + + CFRelease(pIf->RunLoopSrcRef); + pIf->RunLoopSrcRef = NULL; + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + } + + while (pIf->pIsocBufCols) + { + PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; + pIf->pIsocBufCols = pCur->pNext; + pCur->pNext = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvBuffer); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvBuffer = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvFrames); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvFrames = NULL; + + RTMemFree(pCur); + } + + irc = (*pIf->ppIfI)->USBInterfaceClose(pIf->ppIfI); + AssertMsg(irc == kIOReturnSuccess || irc == kIOReturnNoDevice, ("%#x\n", irc)); + + (*pIf->ppIfI)->Release(pIf->ppIfI); + pIf->ppIfI = NULL; + + RTMemFree(pIf); + + pIf = pNext; + } + RTCritSectLeave(&pDevOsX->CritSect); +} + + +/** + * Get the properties all the pipes associated with an interface. + * + * This is used when we seize all the interface and after SET_INTERFACE. + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param pIf The interface to get pipe props for. + */ +static int usbProxyDarwinGetPipeProperties(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYIFOSX pIf) +{ + /* + * Get the pipe (endpoint) count (it might have changed - even on open). + */ + int rc = VINF_SUCCESS; + bool fFullSpeed; + UInt32 u32UsecInFrame; + UInt8 cPipes; + IOReturn irc = (*pIf->ppIfI)->GetNumEndpoints(pIf->ppIfI, &cPipes); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + AssertRelease(cPipes < RT_ELEMENTS(pIf->aPipes)); + pIf->cPipes = cPipes + 1; + + /* Find out if this is a full-speed interface (needed for isochronous support). */ + irc = (*pIf->ppIfI)->GetFrameListTime(pIf->ppIfI, &u32UsecInFrame); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + fFullSpeed = u32UsecInFrame == kUSBFullSpeedMicrosecondsInFrame; + + /* + * Get the properties of each pipe. + */ + for (unsigned i = 0; i < pIf->cPipes; i++) + { + pIf->aPipes[i].u8PipeRef = i; + pIf->aPipes[i].fIsFullSpeed = fFullSpeed; + pIf->aPipes[i].u64NextFrameNo = 0; + irc = (*pIf->ppIfI)->GetPipeProperties(pIf->ppIfI, i, + &pIf->aPipes[i].u8Direction, + &pIf->aPipes[i].u8Endpoint, + &pIf->aPipes[i].u8TransferType, + &pIf->aPipes[i].u16MaxPacketSize, + &pIf->aPipes[i].u8Interval); + if (irc != kIOReturnSuccess) + { + LogRel(("USB: Failed to query properties for pipe %#d / interface %#x on device '%s'. (prot=%#x class=%#x)\n", + i, pIf->u8Interface, pDevOsX->pProxyDev->pUsbIns->pszName, pIf->u8Protocol, pIf->u8Class)); + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + pIf->cPipes = i; + break; + } + /* reconstruct bEndpoint */ + if (pIf->aPipes[i].u8Direction == kUSBIn) + pIf->aPipes[i].u8Endpoint |= 0x80; + Log2(("usbProxyDarwinGetPipeProperties: #If=%d EndPt=%#x Dir=%d Type=%d PipeRef=%#x MaxPktSize=%#x Interval=%#x\n", + pIf->u8Interface, pIf->aPipes[i].u8Endpoint, pIf->aPipes[i].u8Direction, pIf->aPipes[i].u8TransferType, + pIf->aPipes[i].u8PipeRef, pIf->aPipes[i].u16MaxPacketSize, pIf->aPipes[i].u8Interval)); + } + + /** @todo sort or hash these for speedy lookup... */ + return VINF_SUCCESS; +} + + +/** + * Seize all interfaces (current config). + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param fMakeTheBestOfIt If set we will not give up on error. This is for + * use during SET_CONFIGURATION and similar. + */ +static int usbProxyDarwinSeizeAllInterfaces(PUSBPROXYDEVOSX pDevOsX, bool fMakeTheBestOfIt) +{ + PUSBPROXYDEV pProxyDev = pDevOsX->pProxyDev; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Create a interface enumerator for all the interface (current config). + */ + io_iterator_t Interfaces = IO_OBJECT_NULL; + IOUSBFindInterfaceRequest Req; + Req.bInterfaceClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + Req.bAlternateSetting = kIOUSBFindInterfaceDontCare; + IOReturn irc = (*pDevOsX->ppDevI)->CreateInterfaceIterator(pDevOsX->ppDevI, &Req, &Interfaces); + int rc; + if (irc == kIOReturnSuccess) + { + /* + * Iterate the interfaces. + */ + io_object_t Interface; + rc = VINF_SUCCESS; + while ((Interface = IOIteratorNext(Interfaces))) + { + /* + * Create a plug-in and query the IOUSBInterfaceInterface (cute name). + */ + IOCFPlugInInterface **ppPlugInInterface = NULL; + kern_return_t krc; + SInt32 Score = 0; + krc = IOCreatePlugInInterfaceForService(Interface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + IOObjectRelease(Interface); + Interface = IO_OBJECT_NULL; + if (krc == KERN_SUCCESS) + { + IOUSBInterfaceInterface245 **ppIfI; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID245), + (LPVOID *)&ppIfI); + krc = IODestroyPlugInInterface(ppPlugInInterface); Assert(krc == KERN_SUCCESS); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Query some basic properties first. + * (This means we can print more informative messages on failure + * to seize the interface.) + */ + UInt8 u8Interface = 0xff; + irc = (*ppIfI)->GetInterfaceNumber(ppIfI, &u8Interface); + UInt8 u8AltSetting = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetAlternateSetting(ppIfI, &u8AltSetting); + UInt8 u8Class = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceClass(ppIfI, &u8Class); + UInt8 u8Protocol = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceProtocol(ppIfI, &u8Protocol); + UInt8 cEndpoints = 0; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetNumEndpoints(ppIfI, &cEndpoints); + if (irc == kIOReturnSuccess) + { + /* + * Try seize the interface. + */ + irc = (*ppIfI)->USBInterfaceOpenSeize(ppIfI); + if (irc == kIOReturnSuccess) + { + PUSBPROXYIFOSX pIf = (PUSBPROXYIFOSX)RTMemAllocZ(sizeof(*pIf)); + if (pIf) + { + /* + * Create the per-interface entry and query the + * endpoint data. + */ + /* initialize the entry */ + pIf->u8Interface = u8Interface; + pIf->u8AltSetting = u8AltSetting; + pIf->u8Class = u8Class; + pIf->u8Protocol = u8Protocol; + pIf->cPipes = cEndpoints; + pIf->ppIfI = ppIfI; + + /* query pipe/endpoint properties. */ + rc = usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + if (RT_SUCCESS(rc)) + { + /* + * Create the async event source and add it to the + * default current run loop. + * (Later: Add to the worker thread run loop instead.) + */ + irc = (*ppIfI)->CreateInterfaceAsyncEventSource(ppIfI, &pIf->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, + pIf->RunLoopSrcRef); + + /* + * Just link the interface into the list and we're good. + */ + pIf->pNext = NULL; + Log(("USB: Seized interface %#x (alt=%d prot=%#x class=%#x)\n", + u8Interface, u8AltSetting, u8Protocol, u8Class)); + if (pDevOsX->pIfTail) + pDevOsX->pIfTail = pDevOsX->pIfTail->pNext = pIf; + else + pDevOsX->pIfTail = pDevOsX->pIfHead = pIf; + continue; + } + rc = RTErrConvertFromDarwin(irc); + } + + /* failure cleanup. */ + RTMemFree(pIf); + } + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Interface %#x on device '%s' is being used by another process. (prot=%#x class=%#x)\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class)); + rc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open interface %#x on device '%s'. (prot=%#x class=%#x) krc=%#x\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class, irc)); + rc = VERR_OPEN_FAILED; + } + } + else + { + rc = RTErrConvertFromDarwin(irc); + LogRel(("USB: Failed to query interface properties on device '%s', irc=%#x.\n", + pProxyDev->pUsbIns->pszName, irc)); + } + (*ppIfI)->Release(ppIfI); + ppIfI = NULL; + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwinCOM(hrc); + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwin(krc); + if (!fMakeTheBestOfIt) + { + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + break; + } + } /* iterate */ + IOObjectRelease(Interfaces); + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + RTCritSectLeave(&pDevOsX->CritSect); + return rc; +} + + +/** + * Find a particular interface. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Interface The interface number. + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterface(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Interface) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + if (pIf->u8Interface == u8Interface) + return pIf; + +/* AssertMsgFailed(("Cannot find If#=%d\n", u8Interface)); - the 3rd quickcam interface is capture by the ****ing audio crap. */ + return NULL; +} + + +/** + * Find a particular endpoint. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Endpoint The endpoint. + * @param pu8PipeRef Where to store the darwin pipe ref. + * @param ppPipe Where to store the darwin pipe pointer. (optional) + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterfaceForEndpoint(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Endpoint, + uint8_t *pu8PipeRef, PPUSBPROXYPIPEOSX ppPipe) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + unsigned i = pIf->cPipes; + while (i-- > 0) + if (pIf->aPipes[i].u8Endpoint == u8Endpoint) + { + *pu8PipeRef = pIf->aPipes[i].u8PipeRef; + if (ppPipe) + *ppPipe = &pIf->aPipes[i]; + return pIf; + } + } + + AssertMsgFailed(("Cannot find EndPt=%#x\n", u8Endpoint)); + return NULL; +} + + +/** + * Gets an unsigned 32-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu32 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32)) + return true; + } + *pu32 = 0; + return false; +} + + +/** + * Gets an unsigned 64-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu64 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64)) + return true; + } + *pu64 = 0; + return false; +} + + +static DECLCALLBACK(void) usbProxyDarwinPerformWakeup(void *pInfo) +{ + RT_NOREF(pInfo); + return; +} + + +/* -=-=-=-=-=- The exported methods -=-=-=-=-=- */ + + +/** + * Opens the USB Device. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The session id and/or location id of the device to open. + * The format of this string is something iokit.c in Main defines, currently + * it's sequences of "[l|s]=<value>" separated by ";". + * @param pvBackend Backend specific pointer, unused for the Darwin backend. + */ +static DECLCALLBACK(int) usbProxyDarwinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + RT_NOREF(pvBackend); + LogFlow(("usbProxyDarwinOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Init globals once. + */ + int vrc = RTOnce(&g_usbProxyDarwinOnce, usbProxyDarwinInitOnce, NULL); + AssertRCReturn(vrc, vrc); + + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + /* + * The idea here was to create a matching directory with the sessionID + * and locationID included, however this doesn't seem to work. So, we'll + * use the product id and vendor id to limit the set of matching device + * and manually match these two properties. sigh. + * (Btw. vendor and product id must be used *together* apparently.) + * + * Wonder if we could use the entry path? Docs indicates says we must + * use IOServiceGetMatchingServices and I'm not in a mood to explore + * this subject further right now. Maybe check this later. + */ + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName); + AssertReturn(RefMatchingDict != IO_OBJECT_NULL, VERR_OPEN_FAILED); + + uint64_t u64SessionId = 0; + uint32_t u32LocationId = 0; + const char *psz = pszAddress; + do + { + const char chValue = *psz; + AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR); + uint64_t u64Value; + int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value); + AssertReleaseRCReturn(rc, rc); + AssertReleaseReturn(!*psz || *psz == ';', rc); + switch (chValue) + { + case 'l': + u32LocationId = (uint32_t)u64Value; + break; + case 's': + u64SessionId = u64Value; + break; + case 'p': + case 'v': + { +#if 0 /* Guess what, this doesn't 'ing work either! */ + SInt32 i32 = (int16_t)u64Value; + CFNumberRef Num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i32); + AssertBreak(Num); + CFDictionarySetValue(RefMatchingDict, chValue == 'p' ? CFSTR(kUSBProductID) : CFSTR(kUSBVendorID), Num); + CFRelease(Num); +#endif + break; + } + default: + AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR); + } + if (*psz == ';') + psz++; + } while (*psz); + + io_iterator_t USBDevices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%#x\n", irc), RTErrConvertFromDarwinIO(irc)); + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + unsigned cMatches = 0; + io_object_t USBDevice; + while ((USBDevice = IOIteratorNext(USBDevices))) + { + cMatches++; + CFMutableDictionaryRef PropsRef = 0; + kern_return_t krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + uint64_t u64CurSessionId; + uint32_t u32CurLocationId; + if ( ( !u64SessionId + || ( usbProxyDarwinDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId) + && u64CurSessionId == u64SessionId)) + && ( !u32LocationId + || ( usbProxyDarwinDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId) + && u32CurLocationId == u32LocationId)) + ) + { + CFRelease(PropsRef); + break; + } + CFRelease(PropsRef); + } + IOObjectRelease(USBDevice); + } + IOObjectRelease(USBDevices); + USBDevices = IO_OBJECT_NULL; + if (!USBDevice) + { + LogRel(("USB: Device '%s' not found (%d pid+vid matches)\n", pszAddress, cMatches)); + IOObjectRelease(USBDevices); + return VERR_VUSB_DEVICE_NAME_NOT_FOUND; + } + + /* + * Call the USBLib init to make sure we're a valid VBoxUSB client. + * For now we'll ignore failures here and just plunge on, it might still work... + */ + vrc = USBLibInit(); + if (RT_FAILURE(vrc)) + LogRel(("USB: USBLibInit failed - %Rrc\n", vrc)); + + /* + * Create a plugin interface for the device and query its IOUSBDeviceInterface. + */ + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Try open the device for exclusive access. + * If we fail, we'll try figure out who is using the device and + * convince them to let go of it... + */ + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + if (irc == kIOReturnExclusiveAccess) + { + RTThreadSleep(20); + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + } + if (irc == kIOReturnSuccess) + { + /* + * Init a proxy device instance. + */ + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + vrc = RTCritSectInit(&pDevOsX->CritSect); + if (RT_SUCCESS(vrc)) + { + pDevOsX->USBDevice = USBDevice; + pDevOsX->ppDevI = ppDevI; + pDevOsX->pProxyDev = pProxyDev; + pDevOsX->pTaxingHead = NULL; + pDevOsX->pTaxingTail = NULL; + pDevOsX->hRunLoopReapingLast = NULL; + + /* + * Try seize all the interface. + */ + char *pszDummyName = pProxyDev->pUsbIns->pszName; + pProxyDev->pUsbIns->pszName = (char *)pszAddress; + vrc = usbProxyDarwinSeizeAllInterfaces(pDevOsX, false /* give up on failure */); + pProxyDev->pUsbIns->pszName = pszDummyName; + if (RT_SUCCESS(vrc)) + { + /* + * Create the async event source and add it to the run loop. + */ + irc = (*ppDevI)->CreateDeviceAsyncEventSource(ppDevI, &pDevOsX->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + /* + * Determine the active configuration. + * Can cause hangs, so drop it for now. + */ + /** @todo test Palm. */ + //uint8_t u8Cfg; + //irc = (*ppDevI)->GetConfiguration(ppDevI, &u8Cfg); + if (irc != kIOReturnNoDevice) + { + CFRunLoopSourceContext CtxRunLoopSource; + CtxRunLoopSource.version = 0; + CtxRunLoopSource.info = NULL; + CtxRunLoopSource.retain = NULL; + CtxRunLoopSource.release = NULL; + CtxRunLoopSource.copyDescription = NULL; + CtxRunLoopSource.equal = NULL; + CtxRunLoopSource.hash = NULL; + CtxRunLoopSource.schedule = NULL; + CtxRunLoopSource.cancel = NULL; + CtxRunLoopSource.perform = usbProxyDarwinPerformWakeup; + pDevOsX->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource); + if (CFRunLoopSourceIsValid(pDevOsX->hRunLoopSrcWakeRef)) + { + //pProxyDev->iActiveCfg = irc == kIOReturnSuccess ? u8Cfg : -1; + RTListInit(&pDevOsX->HeadOfRunLoopWakeLst); + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 1; + + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + return VINF_SUCCESS; /* return */ + } + else + { + LogRel(("USB: Device '%s' out of memory allocating runloop source\n", pszAddress)); + vrc = VERR_NO_MEMORY; + } + } + vrc = VERR_VUSB_DEVICE_NOT_ATTACHED; + } + else + vrc = RTErrConvertFromDarwin(irc); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + } + /* else: already bitched */ + + RTCritSectDelete(&pDevOsX->CritSect); + } + + irc = (*ppDevI)->USBDeviceClose(ppDevI); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Device '%s' is being used by another process\n", pszAddress)); + vrc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open device '%s', irc=%#x.\n", pszAddress, irc)); + vrc = VERR_OPEN_FAILED; + } + } + else + { + LogRel(("USB: Failed to create plugin interface for device '%s', hrc=%#x.\n", pszAddress, hrc)); + vrc = VERR_OPEN_FAILED; + } + + (*ppDevI)->Release(ppDevI); + } + else + { + LogRel(("USB: Failed to open device '%s', plug-in creation failed with irc=%#x.\n", pszAddress, irc)); + vrc = RTErrConvertFromDarwin(irc); + } + + USBLibTerm(); + return vrc; +} + + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyDarwinClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyDarwinClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + AssertPtrReturnVoid(pDevOsX); + + /* + * Release interfaces we've laid claim to, then reset the device + * and finally close it. + */ + RTCritSectEnter(&pDevOsX->CritSect); + /* ?? */ + RTCritSectLeave(&pDevOsX->CritSect); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + + if (pDevOsX->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + + CFRelease(pDevOsX->RunLoopSrcRef); + pDevOsX->RunLoopSrcRef = NULL; + } + + if (pDevOsX->hRunLoopSrcWakeRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopWakeLst); + + CFRelease(pDevOsX->hRunLoopSrcWakeRef); + pDevOsX->hRunLoopSrcWakeRef = NULL; + } + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + + irc = (*pDevOsX->ppDevI)->USBDeviceClose(pDevOsX->ppDevI); + if (irc != kIOReturnSuccess && irc != kIOReturnNoDevice) + { + LogRel(("USB: USBDeviceClose -> %#x\n", irc)); + AssertMsgFailed(("irc=%#x\n", irc)); + } + + (*pDevOsX->ppDevI)->Release(pDevOsX->ppDevI); + pDevOsX->ppDevI = NULL; + kern_return_t krc = IOObjectRelease(pDevOsX->USBDevice); Assert(krc == KERN_SUCCESS); NOREF(krc); + pDevOsX->USBDevice = IO_OBJECT_NULL; + pDevOsX->pProxyDev = NULL; + + /* + * Free all the resources. + */ + RTCritSectDelete(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX; + while ((pUrbOsX = pDevOsX->pFreeHead) != NULL) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTMemFree(pUrbOsX); + } + + USBLibTerm(); + LogFlow(("usbProxyDarwinClose: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset}*/ +static DECLCALLBACK(int) usbProxyDarwinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinReset: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + int rc; + if (irc == kIOReturnSuccess) + { + /** @todo Some docs say that some drivers will do a default config, check this out ... */ + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + + rc = VINF_SUCCESS; + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("irc=%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + LogFlow(("usbProxyDarwinReset: returns success %Rrc\n", rc)); + return rc; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyDarwinSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinSetConfig: pProxyDev=%s cfg=%#x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + IOReturn irc = (*pDevOsX->ppDevI)->SetConfiguration(pDevOsX->ppDevI, (uint8_t)iCfg); + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinSetConfig: Set configuration -> %#x\n", irc)); + return RTErrConvertFromDarwin(irc); + } + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + return VINF_SUCCESS; +} + + +/** + * Claims an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator (always VINF_SUCCESS). + */ +static DECLCALLBACK(int) usbProxyDarwinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + IOReturn irc = kIOReturnSuccess; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterface(pDevOsX, iIf); + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s iIf=%#x iAlt=%#x iCurAlt=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf ? pIf->u8AltSetting : 0xbeef)); + if (pIf) + { + /* Avoid SetAlternateInterface when possible as it will recreate the pipes. */ + if (iAlt != pIf->u8AltSetting) + { + irc = (*pIf->ppIfI)->SetAlternateInterface(pIf->ppIfI, iAlt); + if (irc == kIOReturnSuccess) + { + usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + return VINF_SUCCESS; + } + } + else + { + /* + * Just send the request anyway? + */ + IOUSBDevRequest Req; + Req.bmRequestType = 0x01; + Req.bRequest = 0x0b; /* SET_INTERFACE */ + Req.wIndex = iIf; + Req.wValue = iAlt; + Req.wLength = 0; + Req.wLenDone = 0; + Req.pData = NULL; + irc = (*pDevOsX->ppDevI)->DeviceRequest(pDevOsX->ppDevI, &Req); + Log(("usbProxyDarwinSetInterface: SET_INTERFACE(%d,%d) -> irc=%#x\n", iIf, iAlt, irc)); + return VINF_SUCCESS; + } + } + + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s eiIf=%#x iAlt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxyDarwinClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x\n", pProxyDev->pUsbIns->pszName, EndPt)); + + /* + * Clearing the zero control pipe doesn't make sense and isn't + * supported by the API. Just ignore it. + */ + if (EndPt == 0) + return VINF_SUCCESS; + + /* + * Find the interface/pipe combination and invoke the ClearPipeStallBothEnds + * method. (The ResetPipe and ClearPipeStall methods will not send the + * CLEAR_FEATURE(ENDPOINT_HALT) request that this method implements.) + */ + IOReturn irc = kIOReturnSuccess; + uint8_t u8PipeRef; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + { + irc = (*pIf->ppIfI)->ClearPipeStallBothEnds(pIf->ppIfI, u8PipeRef); + if (irc == kIOReturnSuccess) + return VINF_SUCCESS; + AssertMsg(irc == kIOReturnNoDevice || irc == kIOReturnNotResponding, ("irc=#x (control pipe?)\n", irc)); + } + + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, EndPt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyDarwinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Find the target interface / pipe. + */ + uint8_t u8PipeRef = 0xff; + PUSBPROXYIFOSX pIf = NULL; + PUSBPROXYPIPEOSX pPipe = NULL; + if (pUrb->EndPt) + { + /* Make sure the interface is there. */ + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, &pPipe); + if (!pIf) + { + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - can't find interface / pipe!!!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData)); + return VERR_NOT_FOUND; + } + } + /* else: pIf == NULL -> default control pipe.*/ + + /* + * Allocate a Darwin urb. + */ + PUSBPROXYURBOSX pUrbOsX = usbProxyDarwinUrbAlloc(pDevOsX); + if (!pUrbOsX) + return VERR_NO_MEMORY; + + pUrbOsX->u64SubmitTS = RTTimeMilliTS(); + pUrbOsX->pVUsbUrb = pUrb; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = pUrb->enmType; + + /* + * Submit the request. + */ + IOReturn irc = kIOReturnError; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + AssertMsgBreak(pUrb->cbData >= sizeof(VUSBSETUP), ("cbData=%d\n", pUrb->cbData)); + PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0]; + pUrbOsX->u.ControlMsg.bmRequestType = pSetup->bmRequestType; + pUrbOsX->u.ControlMsg.bRequest = pSetup->bRequest; + pUrbOsX->u.ControlMsg.wValue = pSetup->wValue; + pUrbOsX->u.ControlMsg.wIndex = pSetup->wIndex; + pUrbOsX->u.ControlMsg.wLength = pSetup->wLength; + pUrbOsX->u.ControlMsg.pData = pSetup + 1; + pUrbOsX->u.ControlMsg.wLenDone = pSetup->wLength; + + if (pIf) + irc = (*pIf->ppIfI)->ControlRequestAsync(pIf->ppIfI, u8PipeRef, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pDevOsX->ppDevI)->DeviceRequestAsync(pDevOsX->ppDevI, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + break; + } + + case VUSBXFERTYPE_BULK: + case VUSBXFERTYPE_INTR: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WritePipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadPipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + + break; + } + + case VUSBXFERTYPE_ISOC: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + +#ifdef USE_LOW_LATENCY_API + /* Allocate an isochronous buffer and copy over the data. */ + AssertBreak(pUrb->cbData <= 8192); + int rc = usbProxyDarwinUrbAllocIsocBuf(pUrbOsX, pIf); + AssertRCBreak(rc); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + memcpy(pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->abData, pUrb->cbData); + else + memset(pUrbOsX->u.Isoc.pBuf->pvBuf, 0xfe, pUrb->cbData); +#endif + + /* Get the current frame number (+2) and make sure it doesn't + overlap with the previous request. See WARNING in + ApplUSBUHCI::CreateIsochTransfer for details on the +2. */ + UInt64 FrameNo; + AbsoluteTime FrameTime; + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + AssertMsg(irc == kIOReturnSuccess, ("GetBusFrameNumber -> %#x\n", irc)); + FrameNo += 2; + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + + for (unsigned j = 0; ; j++) + { + unsigned i; + for (i = 0; i < pUrb->cIsocPkts; i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = pUrb->aIsocPkts[i].cb; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOUSBNotSent1Err; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + for (; i < RT_ELEMENTS(pUrbOsX->u.Isoc.aFrames); i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOReturnError; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + +#ifdef USE_LOW_LATENCY_API + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->LowLatencyWriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->LowLatencyReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#else + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#endif + if ( irc != kIOReturnIsoTooOld + || j >= 5) + { + Log(("%s: usbProxyDarwinUrbQueue: isoc: u64NextFrameNo=%RX64 FrameNo=%RX64 #Frames=%d j=%d (pipe=%d)\n", + pUrb->pszDesc, pPipe->u64NextFrameNo, FrameNo, pUrb->cIsocPkts, j, u8PipeRef)); + if (irc == kIOReturnSuccess) + { + if (pPipe->fIsFullSpeed) + pPipe->u64NextFrameNo = FrameNo + pUrb->cIsocPkts; + else + pPipe->u64NextFrameNo = FrameNo + 1; + } + break; + } + + /* try again... */ + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + FrameNo += j; + } + break; + } + + default: + AssertMsgFailed(("%s: enmType=%#x\n", pUrb->pszDesc, pUrb->enmType)); + break; + } + + /* + * Success? + */ + if (RT_LIKELY(irc == kIOReturnSuccess)) + { + Log(("%s: usbProxyDarwinUrbQueue: success\n", pUrb->pszDesc)); + return VINF_SUCCESS; + } + switch (irc) + { + case kIOUSBPipeStalled: + { + /* Increment in flight counter because the completion handler will decrease it always. */ + usbProxyDarwinUrbAsyncComplete(pUrbOsX, kIOUSBPipeStalled, 0); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x! (stall)\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return VINF_SUCCESS; + } + } + + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyDarwinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PVUSBURB pUrb = NULL; + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + CFRunLoopRef hRunLoopRef = CFRunLoopGetCurrent(); + + Assert(!pDevOsX->hRunLoopReaping); + + /* + * If the last seen runloop for reaping differs we have to check whether the + * the runloop sources are in the new runloop. + */ + if (pDevOsX->hRunLoopReapingLast != hRunLoopRef) + { + RTCritSectEnter(&pDevOsX->CritSect); + + /* Every pipe. */ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + if (!CFRunLoopContainsSource(hRunLoopRef, pIf->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + } + + /* Default control pipe. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + + /* Runloop wakeup source. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->hRunLoopSrcWakeRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + RTCritSectLeave(&pDevOsX->CritSect); + + pDevOsX->hRunLoopReapingLast = hRunLoopRef; + } + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, hRunLoopRef); + + if (ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false)) + { + /* Return immediately. */ + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + return NULL; + } + + /* + * Excercise the runloop until we get an URB or we time out. + */ + if ( !pDevOsX->pTaxingHead + && cMillies) + CFRunLoopRunInMode(g_pRunLoopMode, cMillies / 1000.0, true); + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false); + + /* + * Any URBs pending delivery? + */ + while ( pDevOsX->pTaxingHead + && !pUrb) + { + RTCritSectEnter(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX = pDevOsX->pTaxingHead; + if (pUrbOsX) + { + /* + * Remove from the taxing list. + */ + if (pUrbOsX->pNext) + pUrbOsX->pNext->pPrev = pUrbOsX->pPrev; + else if (pDevOsX->pTaxingTail == pUrbOsX) + pDevOsX->pTaxingTail = pUrbOsX->pPrev; + + if (pUrbOsX->pPrev) + pUrbOsX->pPrev->pNext = pUrbOsX->pNext; + else if (pDevOsX->pTaxingHead == pUrbOsX) + pDevOsX->pTaxingHead = pUrbOsX->pNext; + else + AssertFailed(); + + pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + pUrb->Dev.pvPrivate = NULL; + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + } + } + RTCritSectLeave(&pDevOsX->CritSect); + } + + if (pUrb) + LogFlowFunc(("LEAVE: %s: pProxyDev=%s returns %p\n", pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb)); + else + LogFlowFunc(("LEAVE: NULL pProxyDev=%s returns NULL\n", pProxyDev->pUsbIns->pszName)); + + return pUrb; +} + + +/** + * Cancels a URB. + * + * The URB requires reaping, so we don't change its state. + * + * @remark There isn't any way to cancel a specific async request + * on darwin. The interface only supports the aborting of + * all URBs pending on an interface / pipe pair. Provided + * the card does the URB cancelling before submitting new + * requests, we should probably be fine... + */ +static DECLCALLBACK(int) usbProxyDarwinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbCancel: pProxyDev=%s EndPt=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt)); + + /* + * Determine the interface / endpoint ref and invoke AbortPipe. + */ + IOReturn irc = kIOReturnSuccess; + if (!pUrb->EndPt) + irc = (*pDevOsX->ppDevI)->USBDeviceAbortPipeZero(pDevOsX->ppDevI); + else + { + uint8_t u8PipeRef; + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + irc = (*pIf->ppIfI)->AbortPipe(pIf->ppIfI, u8PipeRef); + else /* this may happen if a device reset, set configuration or set interface has been performed. */ + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d - cannot find the interface / pipe!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt)); + } + + int rc = VINF_SUCCESS; + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d -> %#x!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, irc)); + rc = RTErrConvertFromDarwin(irc); + } + + return rc; +} + + +static DECLCALLBACK(int) usbProxyDarwinWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + LogFlow(("usbProxyDarwinWakeup: pProxyDev=%p\n", pProxyDev)); + + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, true); + usbProxyDarwinReaperKick(pDevOsX); + return VINF_SUCCESS; +} + + +/** + * The Darwin USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVOSX), + usbProxyDarwinOpen, + NULL, + usbProxyDarwinClose, + usbProxyDarwinReset, + usbProxyDarwinSetConfig, + usbProxyDarwinClaimInterface, + usbProxyDarwinReleaseInterface, + usbProxyDarwinSetInterface, + usbProxyDarwinClearHaltedEp, + usbProxyDarwinUrbQueue, + usbProxyDarwinUrbCancel, + usbProxyDarwinUrbReap, + usbProxyDarwinWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/freebsd/Makefile.kup b/src/VBox/Devices/USB/freebsd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/freebsd/Makefile.kup diff --git a/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp new file mode 100644 index 00000000..e68a2025 --- /dev/null +++ b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp @@ -0,0 +1,1062 @@ +/* $Id: USBProxyDevice-freebsd.cpp $ */ +/** @file + * USB device proxy - the FreeBSD backend. + */ + +/* + * Includes contributions from Hans Petter Selasky + * + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#ifdef VBOX +# include <iprt/stdint.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usb_ioctl.h> + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/vusb.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/file.h> +#include <iprt/pipe.h> +#include "../USBProxyDevice.h" + +/** Maximum endpoints supported. */ +#define USBFBSD_MAXENDPOINTS 127 +#define USBFBSD_MAXFRAMES 56 + +/** This really needs to be defined in vusb.h! */ +#ifndef VUSB_DIR_TO_DEV +# define VUSB_DIR_TO_DEV 0x00 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct USBENDPOINTFBSD +{ + /** Flag whether it is opened. */ + bool fOpen; + /** Flag whether it is cancelling. */ + bool fCancelling; + /** Buffer pointers. */ + void *apvData[USBFBSD_MAXFRAMES]; + /** Buffer lengths. */ + uint32_t acbData[USBFBSD_MAXFRAMES]; + /** Initial buffer length. */ + uint32_t cbData0; + /** Pointer to the URB. */ + PVUSBURB pUrb; + /** Copy of endpoint number. */ + unsigned iEpNum; + /** Maximum transfer length. */ + unsigned cMaxIo; + /** Maximum frame count. */ + unsigned cMaxFrames; +} USBENDPOINTFBSD, *PUSBENDPOINTFBSD; + +/** + * Data for the FreeBSD usb proxy backend. + */ +typedef struct USBPROXYDEVFBSD +{ + /** The open file. */ + RTFILE hFile; + /** Flag whether an URB is cancelling. */ + bool fCancelling; + /** Flag whether initialised or not */ + bool fInit; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; + /** Software endpoint structures */ + USBENDPOINTFBSD aSwEndpoint[USBFBSD_MAXENDPOINTS]; + /** Kernel endpoint structures */ + struct usb_fs_endpoint aHwEndpoint[USBFBSD_MAXENDPOINTS]; +} USBPROXYDEVFBSD, *PUSBPROXYDEVFBSD; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint); + +/** + * Wrapper for the ioctl call. + * + * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also + * handle ENODEV (detached device) errors. + * + * @returns whatever ioctl returns. + * @param pProxyDev The proxy device. + * @param iCmd The ioctl command / function. + * @param pvArg The ioctl argument / data. + * @param fHandleNoDev Whether to handle ENXIO. + * @internal + */ +static int usbProxyFreeBSDDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, + void *pvArg, bool fHandleNoDev) +{ + int rc = VINF_SUCCESS; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDDoIoCtl: iCmd=%#x\n", iCmd)); + + do + { + rc = ioctl(RTFileToNative(pDevFBSD->hFile), iCmd, pvArg); + if (rc >= 0) + return VINF_SUCCESS; + } while (errno == EINTR); + + if (errno == ENXIO && fHandleNoDev) + { + Log(("usbProxyFreeBSDDoIoCtl: ENXIO -> unplugged. pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + errno = ENODEV; + } + else if (errno != EAGAIN) + { + LogFlow(("usbProxyFreeBSDDoIoCtl: Returned %d. pProxyDev=%s\n", + errno, pProxyDev->pUsbIns->pszName)); + } + return RTErrConvertFromErrno(errno); +} + +/** + * Init USB subsystem. + */ +static int usbProxyFreeBSDFsInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_init UsbFsInit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsInit: pProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit == true) + return VINF_SUCCESS; + + /* Zero default */ + memset(&UsbFsInit, 0, sizeof(UsbFsInit)); + + UsbFsInit.pEndpoints = pDevFBSD->aHwEndpoint; + UsbFsInit.ep_index_max = USBFBSD_MAXENDPOINTS; + + /* Init USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_INIT, &UsbFsInit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = true; + + return rc; +} + +/** + * Uninit USB subsystem. + */ +static int usbProxyFreeBSDFsUnInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_uninit UsbFsUninit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsUnInit: ProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit != true) + return VINF_SUCCESS; + + /* Close any open endpoints. */ + for (unsigned n = 0; n != USBFBSD_MAXENDPOINTS; n++) + usbProxyFreeBSDEndpointClose(pProxyDev, n); + + /* Zero default */ + memset(&UsbFsUninit, 0, sizeof(UsbFsUninit)); + + /* Uninit USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_UNINIT, &UsbFsUninit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = false; + + return rc; +} + +/** + * Setup a USB request packet. + */ +static void usbProxyFreeBSDSetupReq(struct usb_device_request *pSetupData, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, + uint16_t wIndex, uint16_t wLength) +{ + LogFlow(("usbProxyFreeBSDSetupReq: pSetupData=%p bmRequestType=%x " + "bRequest=%x wValue=%x wIndex=%x wLength=%x\n", (void *)pSetupData, + bmRequestType, bRequest, wValue, wIndex, wLength)); + + pSetupData->bmRequestType = bmRequestType; + pSetupData->bRequest = bRequest; + + /* Handle endianess here. Currently no swapping is needed. */ + pSetupData->wValue[0] = wValue & 0xff; + pSetupData->wValue[1] = (wValue >> 8) & 0xff; + pSetupData->wIndex[0] = wIndex & 0xff; + pSetupData->wIndex[1] = (wIndex >> 8) & 0xff; + pSetupData->wLength[0] = wLength & 0xff; + pSetupData->wLength[1] = (wLength >> 8) & 0xff; +} + +static int usbProxyFreeBSDEndpointOpen(PUSBPROXYDEV pProxyDev, int Endpoint, bool fIsoc, int index) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = NULL; /* shut up gcc */ + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_open UsbFsOpen; + int rc; + + LogFlow(("usbProxyFreeBSDEndpointOpen: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + for (; index < USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if ( pEndpointFBSD->fOpen + && !pEndpointFBSD->pUrb + && (int)pEndpointFBSD->iEpNum == Endpoint) + return index; + } + + if (index == USBFBSD_MAXENDPOINTS) + { + for (index = 0; index != USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if (!pEndpointFBSD->fOpen) + break; + } + if (index == USBFBSD_MAXENDPOINTS) + return -1; + } + /* set ppBuffer and pLength */ + + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + pXferEndpoint->ppBuffer = &pEndpointFBSD->apvData[0]; + pXferEndpoint->pLength = &pEndpointFBSD->acbData[0]; + + LogFlow(("usbProxyFreeBSDEndpointOpen: ep_index=%d ep_num=%d\n", + index, Endpoint)); + + memset(&UsbFsOpen, 0, sizeof(UsbFsOpen)); + + UsbFsOpen.ep_index = index; + UsbFsOpen.ep_no = Endpoint; + UsbFsOpen.max_bufsize = 256 * 1024; + /* Hardcoded assumption about the URBs we get. */ + + UsbFsOpen.max_frames = fIsoc ? USBFBSD_MAXFRAMES : 2; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_OPEN, &UsbFsOpen, true); + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + LogFlow(("usbProxyFreeBSDEndpointOpen: EBUSY\n")); + + return -1; + } + pEndpointFBSD->fOpen = true; + pEndpointFBSD->pUrb = NULL; + pEndpointFBSD->iEpNum = Endpoint; + pEndpointFBSD->cMaxIo = UsbFsOpen.max_bufsize; + pEndpointFBSD->cMaxFrames = UsbFsOpen.max_frames; + + return index; +} + +/** + * Close an endpoint. + * + * @returns VBox status code. + */ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = &pDevFBSD->aSwEndpoint[Endpoint]; + struct usb_fs_close UsbFsClose; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDEndpointClose: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + /* check for cancelling */ + if (pEndpointFBSD->pUrb != NULL) + { + pEndpointFBSD->fCancelling = true; + pDevFBSD->fCancelling = true; + } + + /* check for opened */ + if (pEndpointFBSD->fOpen) + { + pEndpointFBSD->fOpen = false; + + /* Zero default */ + memset(&UsbFsClose, 0, sizeof(UsbFsClose)); + + /* Set endpoint index */ + UsbFsClose.ep_index = Endpoint; + + /* Close endpoint */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_CLOSE, &UsbFsClose, true); + } + return rc; +} + +/** + * Opens the device file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress If we are using usbfs, this is the path to the + * device. If we are using sysfs, this is a string of + * the form "sysfs:<sysfs path>//device:<device node>". + * In the second case, the two paths are guaranteed + * not to contain the substring "//". + * @param pvBackend Backend specific pointer, unused for the linux backend. + */ +static DECLCALLBACK(int) usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, + void *pvBackend) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + NOREF(pvBackend); + + /* + * Try open the device node. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszAddress, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the FreeBSD backend data. + */ + pDevFBSD->hFile = hFile; + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_SUCCESS(rc)) + { + /* + * Create wakeup pipe. + */ + rc = RTPipeCreate(&pDevFBSD->hPipeWakeupR, &pDevFBSD->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyFreeBSDOpen(%p, %s): returns successfully hFile=%RTfile iActiveCfg=%d\n", + pProxyDev, pszAddress, pDevFBSD->hFile, pProxyDev->iActiveCfg)); + + return VINF_SUCCESS; + } + } + + RTFileClose(hFile); + } + else if (rc == VERR_ACCESS_DENIED) + rc = VERR_VUSB_USBFS_PERMISSION; + + Log(("usbProxyFreeBSDOpen(%p, %s) failed, rc=%d!\n", + pProxyDev, pszAddress, rc)); + + return rc; +} + + +/** + * Claims all the interfaces and figures out the + * current configuration. + * + * @returns VINF_SUCCESS. + * @param pProxyDev The proxy device. + */ +static DECLCALLBACK(int) usbProxyFreeBSDInit(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDInit: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_GET_CONFIG, + &pProxyDev->iActiveCfg, true); + if (RT_FAILURE(rc) || pProxyDev->iActiveCfg == 255) + { + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + } + else + { + pProxyDev->cIgnoreSetConfigs = 1; + pProxyDev->iActiveCfg++; + } + + Log(("usbProxyFreeBSDInit: iActiveCfg=%d\n", pProxyDev->iActiveCfg)); + + return rc; +} + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyFreeBSDClose(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + /* sanity check */ + AssertPtrReturnVoid(pDevFBSD); + + usbProxyFreeBSDFsUnInit(pProxyDev); + + RTPipeClose(pDevFBSD->hPipeWakeupR); + RTPipeClose(pDevFBSD->hPipeWakeupW); + + RTFileClose(pDevFBSD->hFile); + pDevFBSD->hFile = NIL_RTFILE; + + LogFlow(("usbProxyFreeBSDClose: returns\n")); +} + +/** + * Reset a device. + * + * @returns VBox status code. + * @param pDev The device to reset. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReset(PUSBPROXYDEV pProxyDev, bool fResetOnFreeBSD) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iParm; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDReset: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + if (!fResetOnFreeBSD) + goto done; + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Resetting is only possible as super-user, ignore any failures: */ + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DEVICEENUMERATE, &iParm, true); + if (RT_FAILURE(rc)) + { + /* Set the config instead of bus reset */ + iParm = 255; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + if (RT_SUCCESS(rc)) + { + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + } + } + usleep(10000); /* nice it! */ + + /* Allocate kernel ressources again. */ + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDInit(pProxyDev); + +done: + pProxyDev->cIgnoreSetConfigs = 2; + + return rc; +} + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyFreeBSDSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iCfgIndex; + int rc; + + LogFlow(("usbProxyFreeBSDSetConfig: pProxyDev=%s cfg=%x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + + if (iCfg == 0) + { + /* Unconfigure */ + iCfgIndex = 255; + } + else + { + /* Get the configuration index matching the value. */ + for (iCfgIndex = 0; iCfgIndex < pProxyDev->DevDesc.bNumConfigurations; iCfgIndex++) + { + if (pProxyDev->paCfgDescs[iCfgIndex].Core.bConfigurationValue == iCfg) + break; + } + + if (iCfgIndex == pProxyDev->DevDesc.bNumConfigurations) + { + LogFlow(("usbProxyFreeBSDSetConfig: configuration " + "%d not found\n", iCfg)); + return VERR_NOT_FOUND; + } + } + + /* Set the config */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iCfgIndex, true); + if (RT_FAILURE(rc)) + return rc; + + /* Allocate kernel ressources again. */ + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Claims an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDClaimInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + /* + * Try to detach kernel driver on this interface, ignore any + * failures + */ + usbProxyFreeBSDDoIoCtl(pProxyDev, USB_IFACE_DRIVER_DETACH, &iIf, true); + + /* Try to claim interface */ + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_CLAIM_INTERFACE, &iIf, true); +} + +/** + * Releases an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDReleaseInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_RELEASE_INTERFACE, &iIf, true); +} + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + struct usb_alt_interface UsbIntAlt; + int rc; + + LogFlow(("usbProxyFreeBSDSetInterface: pProxyDev=%p iIf=%x iAlt=%x\n", + pProxyDev, iIf, iAlt)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + memset(&UsbIntAlt, 0, sizeof(UsbIntAlt)); + UsbIntAlt.uai_interface_index = iIf; + UsbIntAlt.uai_alt_index = iAlt; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_ALTINTERFACE, &UsbIntAlt, true); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Setting interface %d %d " + "failed rc=%d\n", iIf, iAlt, rc)); + return rc; + } + + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Clears the halted endpoint 'ep_num'. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep_num) +{ + LogFlow(("usbProxyFreeBSDClearHaltedEp: pProxyDev=%s ep_num=%u\n", + pProxyDev->pUsbIns->pszName, ep_num)); + + /* + * Clearing the zero control pipe doesn't make sense. + * Just ignore it. + */ + if ((ep_num & 0xF) == 0) + return VINF_SUCCESS; + + struct usb_ctl_request Req; + memset(&Req, 0, sizeof(Req)); + usbProxyFreeBSDSetupReq(&Req.ucr_request, + VUSB_DIR_TO_DEV | VUSB_TO_ENDPOINT, + VUSB_REQ_CLEAR_FEATURE, 0, ep_num, 0); + + int rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DO_REQUEST, &Req, true); + + LogFlow(("usbProxyFreeBSDClearHaltedEp: rc=%Rrc\n", rc)); + return rc; +} + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_start UsbFsStart; + unsigned cFrames; + uint8_t *pbData; + int index; + int ep_num; + int rc; + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb=%p EndPt=%u Dir=%u\n", + pUrb, (unsigned)pUrb->EndPt, (unsigned)pUrb->enmDir)); + + ep_num = pUrb->EndPt; + if ((pUrb->enmType != VUSBXFERTYPE_MSG) && (pUrb->enmDir == VUSBDIRECTION_IN)) { + /* set IN-direction bit */ + ep_num |= 0x80; + } + + index = 0; + +retry: + + index = usbProxyFreeBSDEndpointOpen(pProxyDev, ep_num, + (pUrb->enmType == VUSBXFERTYPE_ISOC), + index); + + if (index < 0) + return VERR_INVALID_PARAMETER; + + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + + pbData = pUrb->abData; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->acbData[0] = 8; + + /* check wLength */ + if (pbData[6] || pbData[7]) + { + pEndpointFBSD->apvData[1] = pbData + 8; + pEndpointFBSD->acbData[1] = pbData[6] | (pbData[7] << 8); + cFrames = 2; + } + else + { + pEndpointFBSD->apvData[1] = NULL; + pEndpointFBSD->acbData[1] = 0; + cFrames = 1; + } + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb->cbData=%u, 0x%02x, " + "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + pUrb->cbData, pbData[0], pbData[1], pbData[2], pbData[3], + pbData[4], pbData[5], pbData[6], pbData[7])); + + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + break; + } + case VUSBXFERTYPE_ISOC: + { + unsigned i; + + for (i = 0; i < pUrb->cIsocPkts; i++) + { + if (i >= pEndpointFBSD->cMaxFrames) + break; + pEndpointFBSD->apvData[i] = pbData + pUrb->aIsocPkts[i].off; + pEndpointFBSD->acbData[i] = pUrb->aIsocPkts[i].cb; + } + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = i; + break; + } + default: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->cbData0 = pUrb->cbData; + + /* XXX maybe we have to loop */ + if (pUrb->cbData > pEndpointFBSD->cMaxIo) + pEndpointFBSD->acbData[0] = pEndpointFBSD->cMaxIo; + else + pEndpointFBSD->acbData[0] = pUrb->cbData; + + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = pUrb->fShortNotOk ? 0 : USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = 1; + break; + } + } + + /* store number of frames */ + pXferEndpoint->nFrames = cFrames; + + /* zero-default */ + memset(&UsbFsStart, 0, sizeof(UsbFsStart)); + + /* Start the transfer */ + UsbFsStart.ep_index = index; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_START, &UsbFsStart, true); + + LogFlow(("usbProxyFreeBSDUrbQueue: USB_FS_START returned rc=%d " + "len[0]=%u len[1]=%u cbData=%u index=%u ep_num=%u\n", rc, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1], + (unsigned)pUrb->cbData, + (unsigned)index, (unsigned)ep_num)); + + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + { + index++; + goto retry; + } + return rc; + } + pUrb->Dev.pvPrivate = (void *)(long)(index + 1); + pEndpointFBSD->pUrb = pUrb; + + return rc; +} + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyFreeBSDUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + struct usb_fs_endpoint *pXferEndpoint; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + PVUSBURB pUrb; + struct usb_fs_complete UsbFsComplete; + struct pollfd pfd[2]; + int rc; + + LogFlow(("usbProxyFreeBSDUrbReap: pProxyDev=%p, cMillies=%u\n", + pProxyDev, cMillies)); + +repeat: + + pUrb = NULL; + + /* check for cancelled transfers */ + if (pDevFBSD->fCancelling) + { + for (unsigned n = 0; n < USBFBSD_MAXENDPOINTS; n++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[n]; + if (pEndpointFBSD->fCancelling) + { + pEndpointFBSD->fCancelling = false; + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + + if (pUrb != NULL) + break; + } + } + + if (pUrb != NULL) + { + pUrb->enmStatus = VUSBSTATUS_INVALID; + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = 0; + break; + case VUSBXFERTYPE_ISOC: + pUrb->cbData = 0; + for (int n = 0; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + break; + default: + pUrb->cbData = 0; + break; + } + return pUrb; + } + pDevFBSD->fCancelling = false; + } + /* Zero default */ + + memset(&UsbFsComplete, 0, sizeof(UsbFsComplete)); + + /* Check if any endpoints are complete */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_COMPLETE, &UsbFsComplete, true); + if (RT_SUCCESS(rc)) + { + pXferEndpoint = &pDevFBSD->aHwEndpoint[UsbFsComplete.ep_index]; + pEndpointFBSD = &pDevFBSD->aSwEndpoint[UsbFsComplete.ep_index]; + + LogFlow(("usbProxyFreeBSDUrbReap: Reaped " + "URB %#p\n", pEndpointFBSD->pUrb)); + + if (pXferEndpoint->status == USB_ERR_CANCELLED) + goto repeat; + + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + if (pUrb == NULL) + goto repeat; + + switch (pXferEndpoint->status) + { + case USB_ERR_NORMAL_COMPLETION: + pUrb->enmStatus = VUSBSTATUS_OK; + break; + case USB_ERR_STALLED: + pUrb->enmStatus = VUSBSTATUS_STALL; + break; + default: + pUrb->enmStatus = VUSBSTATUS_INVALID; + break; + } + + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = pEndpointFBSD->acbData[0] + pEndpointFBSD->acbData[1]; + break; + case VUSBXFERTYPE_ISOC: + { + int n; + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + break; + pUrb->cbData = 0; + for (n = 0; n < (int)pUrb->cIsocPkts; n++) + { + if (n >= (int)pEndpointFBSD->cMaxFrames) + break; + pUrb->cbData += pEndpointFBSD->acbData[n]; + pUrb->aIsocPkts[n].cb = pEndpointFBSD->acbData[n]; + } + for (; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + + break; + } + default: + pUrb->cbData = pEndpointFBSD->acbData[0]; + break; + } + + LogFlow(("usbProxyFreeBSDUrbReap: Status=%d epindex=%u " + "len[0]=%d len[1]=%d\n", + (int)pXferEndpoint->status, + (unsigned)UsbFsComplete.ep_index, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1])); + + } + else if (cMillies != 0 && rc == VERR_RESOURCE_BUSY) + { + for (;;) + { + pfd[0].fd = RTFileToNative(pDevFBSD->hFile); + pfd[0].events = POLLIN | POLLRDNORM; + pfd[0].revents = 0; + + pfd[1].fd = RTPipeToNative(pDevFBSD->hPipeWakeupR); + pfd[1].events = POLLIN | POLLRDNORM; + pfd[1].revents = 0; + + rc = poll(pfd, 2, (cMillies == RT_INDEFINITE_WAIT) ? INFTIM : cMillies); + if (rc > 0) + { + if (pfd[1].revents & POLLIN) + { + /* Got woken up, drain pipe. */ + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevFBSD->hPipeWakeupR, &bRead, 1, &cbIgnored); + /* Make sure we return from this function */ + cMillies = 0; + } + break; + } + if (rc == 0) + return NULL; + if (errno != EAGAIN) + return NULL; + } + goto repeat; + } + return pUrb; +} + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int index; + + index = (int)(long)pUrb->Dev.pvPrivate - 1; + + if (index < 0 || index >= USBFBSD_MAXENDPOINTS) + return VINF_SUCCESS; /* invalid index, pretend success. */ + + LogFlow(("usbProxyFreeBSDUrbCancel: epindex=%u\n", (unsigned)index)); + return usbProxyFreeBSDEndpointClose(pProxyDev, index); +} + +static DECLCALLBACK(int) usbProxyFreeBSDWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevFBSD->hPipeWakeupW, "", 1, &cbIgnored); +} + +/** + * The FreeBSD USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVFBSD), + usbProxyFreeBSDOpen, + usbProxyFreeBSDInit, + usbProxyFreeBSDClose, + usbProxyFreeBSDReset, + usbProxyFreeBSDSetConfig, + usbProxyFreeBSDClaimInterface, + usbProxyFreeBSDReleaseInterface, + usbProxyFreeBSDSetInterface, + usbProxyFreeBSDClearHaltedEp, + usbProxyFreeBSDUrbQueue, + usbProxyFreeBSDUrbCancel, + usbProxyFreeBSDUrbReap, + usbProxyFreeBSDWakeup, + 0 +}; + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + diff --git a/src/VBox/Devices/USB/linux/Makefile.kup b/src/VBox/Devices/USB/linux/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/linux/Makefile.kup diff --git a/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp new file mode 100644 index 00000000..a0333473 --- /dev/null +++ b/src/VBox/Devices/USB/linux/USBProxyDevice-linux.cpp @@ -0,0 +1,1698 @@ +/* $Id: USBProxyDevice-linux.cpp $ */ +/** @file + * USB device proxy - the Linux backend. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <iprt/stdint.h> +#include <iprt/err.h> +#include <iprt/pipe.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#ifdef VBOX_WITH_LINUX_COMPILER_H +# include <linux/compiler.h> +#endif +#include <linux/usbdevice_fs.h> + +#ifndef RDESKTOP +# include <VBox/vmm/pdm.h> +#else +# define RTCRITSECT void * +static inline int rtcsNoop() { return VINF_SUCCESS; } +static inline bool rtcsTrue() { return true; } +# define RTCritSectInit(a) rtcsNoop() +# define RTCritSectDelete(a) rtcsNoop() +# define RTCritSectEnter(a) rtcsNoop() +# define RTCritSectLeave(a) rtcsNoop() +# define RTCritSectIsOwner(a) rtcsTrue() +#endif +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/alloc.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/file.h> +#include <iprt/linux/sysfs.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/list.h> +#include <iprt/time.h> +#include "../USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Wrapper around the linux urb request structure. + * This is required to track in-flight and landed URBs. + */ +typedef struct USBPROXYURBLNX +{ + /** The kernel URB data. */ +#if RT_GNUC_PREREQ(6, 0) + /* gcc 6.2 complains about the [] member of KUrb */ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wpedantic" +#endif + struct usbdevfs_urb KUrb; +#if RT_GNUC_PREREQ(6, 0) +# pragma GCC diagnostic pop +#endif + /** Space filler for the isochronous packets. */ + struct usbdevfs_iso_packet_desc aIsocPktsDonUseTheseUseTheOnesInKUrb[8]; + /** Node to link the URB in of the existing lists. */ + RTLISTNODE NodeList; + /** If we've split the VUSBURB up into multiple linux URBs, this is points to the head. */ + struct USBPROXYURBLNX *pSplitHead; + /** The next linux URB if split up. */ + struct USBPROXYURBLNX *pSplitNext; + /** Don't report these back. */ + bool fCanceledBySubmit; + /** This split element is reaped. */ + bool fSplitElementReaped; + /** This URB was discarded. */ + bool fDiscarded; + /** Size to transfer in remaining fragments of a split URB */ + uint32_t cbSplitRemaining; +} USBPROXYURBLNX, *PUSBPROXYURBLNX; + +/** + * Data for the linux usb proxy backend. + */ +typedef struct USBPROXYDEVLNX +{ + /** The open file. */ + RTFILE hFile; + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** The list of free linux URBs (USBPROXYURBLNX). */ + RTLISTANCHOR ListFree; + /** The list of active linux URBs. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. (USBPROXYURBLNX) */ + RTLISTANCHOR ListInFlight; + /** Are we using sysfs to find the active configuration? */ + bool fUsingSysfs; + /** Pipe handle for waiking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waiking up - reading end. */ + RTPIPE hPipeWakeupR; + /** The device node/sysfs path of the device. + * Used to figure out the configuration after a reset. */ + char *pszPath; + /** Mask of claimed interfaces. */ + uint32_t fClaimedIfsMask; +} USBPROXYDEVLNX, *PUSBPROXYDEVLNX; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev); +static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf); +static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf); + + +/** + * Wrapper for the ioctl call. + * + * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also + * handle ENODEV (detached device) errors. + * + * @returns whatever ioctl returns. + * @param pProxyDev The proxy device. + * @param iCmd The ioctl command / function. + * @param pvArg The ioctl argument / data. + * @param fHandleNoDev Whether to handle ENODEV. + * @param cTries The number of retries. Use UINT32_MAX for (kind of) indefinite retries. + * @internal + */ +static int usbProxyLinuxDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, void *pvArg, bool fHandleNoDev, uint32_t cTries) +{ + int rc; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + do + { + do + { + rc = ioctl(RTFileToNative(pDevLnx->hFile), iCmd, pvArg); + if (rc >= 0) + return rc; + } while (errno == EINTR); + + if (errno == ENODEV && fHandleNoDev) + { + usbProxLinuxUrbUnplugged(pProxyDev); + Log(("usb-linux: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + errno = ENODEV; + break; + } + if (errno != EAGAIN) + break; + } while (cTries-- > 0); + + return rc; +} + + +/** + * The device has been unplugged. + * Cancel all in-flight URBs and put them up for reaping. + */ +static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Shoot down all flying URBs. + */ + RTCritSectEnter(&pDevLnx->CritSect); + pProxyDev->fDetached = true; + + PUSBPROXYURBLNX pUrbLnx; + PUSBPROXYURBLNX pUrbLnxNext; + RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + if (!pUrbLnx->fDiscarded) + { + pUrbLnx->fDiscarded = true; + /* Cancel the URB. It will be reaped normally. */ + ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_DISCARDURB, &pUrbLnx->KUrb); + if (!pUrbLnx->KUrb.status) + pUrbLnx->KUrb.status = -ENODEV; + } + } + + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * Set the connect state seen by kernel drivers + * @internal + */ +static void usbProxyLinuxSetConnected(PUSBPROXYDEV pProxyDev, int iIf, bool fConnect, bool fQuiet) +{ + if ( iIf >= 32 + || !(pProxyDev->fMaskedIfs & RT_BIT(iIf))) + { + struct usbdevfs_ioctl IoCtl; + if (!fQuiet) + LogFlow(("usbProxyLinuxSetConnected: pProxyDev=%s iIf=%#x fConnect=%s\n", + usbProxyGetName(pProxyDev), iIf, fConnect ? "true" : "false")); + + IoCtl.ifno = iIf; + IoCtl.ioctl_code = fConnect ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + IoCtl.data = NULL; + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_IOCTL, &IoCtl, true, UINT32_MAX) + && !fQuiet) + Log(("usbProxyLinuxSetConnected: failure, errno=%d. pProxyDev=%s\n", + errno, usbProxyGetName(pProxyDev))); + } +} + + +/** + * Links the given URB into the in flight list. + * + * @returns nothing. + * @param pDevLnx The proxy device instance - Linux specific data. + * @param pUrbLnx The URB to link into the in flight list. + */ +static void usbProxyLinuxUrbLinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) +{ + LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); + Assert(RTCritSectIsOwner(&pDevLnx->CritSect)); + Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); + RTListAppend(&pDevLnx->ListInFlight, &pUrbLnx->NodeList); +} + +/** + * Unlinks the given URB from the in flight list. + * @returns nothing. + * @param pDevLnx The proxy device instance - Linux specific data. + * @param pUrbLnx The URB to link into the in flight list. + */ +static void usbProxyLinuxUrbUnlinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) +{ + LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); + RTCritSectEnter(&pDevLnx->CritSect); + + /* + * Remove from the active list. + */ + Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); + + RTListNodeRemove(&pUrbLnx->NodeList); + + RTCritSectLeave(&pDevLnx->CritSect); +} + +/** + * Allocates a linux URB request structure. + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * @param pProxyDev The proxy device instance. + * @param pSplitHead The split list head if allocating for a split list. + */ +static PUSBPROXYURBLNX usbProxyLinuxUrbAlloc(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pSplitHead) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + PUSBPROXYURBLNX pUrbLnx; + + LogFlowFunc(("pProxyDev=%p pSplitHead=%p\n", pProxyDev, pSplitHead)); + + RTCritSectEnter(&pDevLnx->CritSect); + + /* + * Try remove a linux URB from the free list, if none there allocate a new one. + */ + pUrbLnx = RTListGetFirst(&pDevLnx->ListFree, USBPROXYURBLNX, NodeList); + if (pUrbLnx) + { + RTListNodeRemove(&pUrbLnx->NodeList); + RTCritSectLeave(&pDevLnx->CritSect); + } + else + { + RTCritSectLeave(&pDevLnx->CritSect); + pUrbLnx = (PUSBPROXYURBLNX)RTMemAlloc(sizeof(*pUrbLnx)); + if (!pUrbLnx) + return NULL; + } + + pUrbLnx->pSplitHead = pSplitHead; + pUrbLnx->pSplitNext = NULL; + pUrbLnx->fCanceledBySubmit = false; + pUrbLnx->fSplitElementReaped = false; + pUrbLnx->fDiscarded = false; + LogFlowFunc(("returns pUrbLnx=%p\n", pUrbLnx)); + return pUrbLnx; +} + + +/** + * Frees a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbLnx The linux URB to free. + */ +static void usbProxyLinuxUrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); + + /* + * Link it into the free list. + */ + RTCritSectEnter(&pDevLnx->CritSect); + RTListAppend(&pDevLnx->ListFree, &pUrbLnx->NodeList); + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * Frees split list of a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbLnx A linux URB to in the split list to be freed. + */ +static void usbProxyLinuxUrbFreeSplitList(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); + + RTCritSectEnter(&pDevLnx->CritSect); + + pUrbLnx = pUrbLnx->pSplitHead; + Assert(pUrbLnx); + while (pUrbLnx) + { + PUSBPROXYURBLNX pFree = pUrbLnx; + pUrbLnx = pUrbLnx->pSplitNext; + Assert(pFree->pSplitHead); + pFree->pSplitHead = pFree->pSplitNext = NULL; + usbProxyLinuxUrbFree(pProxyDev, pFree); + } + + RTCritSectLeave(&pDevLnx->CritSect); +} + + +/** + * This finds the device in the /proc/bus/usb/bus/addr file and finds + * the config with an asterix. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszDevNode The path to the device. We infere the location of + * the devices file, which bus and device number we're + * looking for. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfigUsbfs(PUSBPROXYDEV pProxyDev, const char *pszDevNode, int *piFirstCfg) +{ + RT_NOREF(pProxyDev); + + /* + * Set return defaults. + */ + int iActiveCfg = -1; + if (piFirstCfg) + *piFirstCfg = 1; + + /* + * Parse the usbfs device node path and turn it into a path to the "devices" file, + * picking up the device number and bus along the way. + */ + size_t cchDevNode = strlen(pszDevNode); + char *pszDevices = (char *)RTMemDupEx(pszDevNode, cchDevNode, sizeof("devices")); + AssertReturn(pszDevices, iActiveCfg); + + /* the device number */ + char *psz = pszDevices + cchDevNode; + while (*psz != '/') + psz--; + Assert(pszDevices < psz); + uint32_t uDev; + int rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uDev); + if (RT_SUCCESS(rc)) + { + /* the bus number */ + *psz-- = '\0'; + while (*psz != '/') + psz--; + Assert(pszDevices < psz); + uint32_t uBus; + rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uBus); + if (RT_SUCCESS(rc)) + { + strcpy(psz + 1, "devices"); + + /* + * Open and scan the devices file. + * We're ASSUMING that each device starts off with a 'T:' line. + */ + PRTSTREAM pFile; + rc = RTStrmOpen(pszDevices, "r", &pFile); + if (RT_SUCCESS(rc)) + { + char szLine[1024]; + while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) + { + /* we're only interested in 'T:' lines. */ + psz = RTStrStripL(szLine); + if (psz[0] != 'T' || psz[1] != ':') + continue; + + /* Skip ahead to 'Bus' and compare */ + psz = RTStrStripL(psz + 2); Assert(!strncmp(psz, RT_STR_TUPLE("Bus="))); + psz = RTStrStripL(psz + 4); + char *pszNext; + uint32_t u; + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_FAILURE(rc)) + continue; + if (u != uBus) + continue; + + /* Skip ahead to 'Dev#' and compare */ + psz = strstr(psz, "Dev#="); Assert(psz); + if (!psz) + continue; + psz = RTStrStripL(psz + 5); + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_FAILURE(rc)) + continue; + if (u != uDev) + continue; + + /* + * Ok, we've found the device. + * Scan until we find a selected configuration, the next device, or EOF. + */ + while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) + { + psz = RTStrStripL(szLine); + if (psz[0] == 'T') + break; + if (psz[0] != 'C' || psz[1] != ':') + continue; + const bool fActive = psz[2] == '*'; + if (!fActive && !piFirstCfg) + continue; + + /* Get the 'Cfg#' value. */ + psz = strstr(psz, "Cfg#="); Assert(psz); + if (psz) + { + psz = RTStrStripL(psz + 5); + rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (piFirstCfg) + { + *piFirstCfg = u; + piFirstCfg = NULL; + } + if (fActive) + iActiveCfg = u; + } + } + if (fActive) + break; + } + break; + } + RTStrmClose(pFile); + } + } + } + RTMemFree(pszDevices); + + return iActiveCfg; +} + + +/** + * This finds the active configuration from sysfs. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszPath The sysfs path for the device. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfigSysfs(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) +{ +#ifdef VBOX_USB_WITH_SYSFS + if (piFirstCfg != NULL) + *piFirstCfg = pProxyDev->paCfgDescs != NULL + ? pProxyDev->paCfgDescs[0].Core.bConfigurationValue + : 1; + int64_t bCfg = 0; + int rc = RTLinuxSysFsReadIntFile(10, &bCfg, "%s/bConfigurationValue", pszPath); + if (RT_FAILURE(rc)) + bCfg = -1; + return (int)bCfg; +#else /* !VBOX_USB_WITH_SYSFS */ + return -1; +#endif /* !VBOX_USB_WITH_SYSFS */ +} + + +/** + * This finds the active configuration. + * + * @returns The Cfg#. + * @returns -1 if no active config. + * @param pProxyDev The proxy device instance. + * @param pszPath The sysfs path for the device, or the usbfs device + * node path. + * @param piFirstCfg The first configuration. (optional) + * @internal + */ +static int usbProxyLinuxFindActiveConfig(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + if (pDevLnx->fUsingSysfs) + return usbProxyLinuxFindActiveConfigSysfs(pProxyDev, pszPath, piFirstCfg); + return usbProxyLinuxFindActiveConfigUsbfs(pProxyDev, pszPath, piFirstCfg); +} + + +/** + * Extracts the Linux file descriptor associated with the kernel USB device. + * This is used by rdesktop-vrdp for polling for events. + * @returns the FD, or asserts and returns -1 on error + * @param pProxyDev The device instance + */ +RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + AssertReturn(pDevLnx->hFile != NIL_RTFILE, -1); + return RTFileToNative(pDevLnx->hFile); +} + + +/** + * Opens the device file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress If we are using usbfs, this is the path to the + * device. If we are using sysfs, this is a string of + * the form "sysfs:<sysfs path>//device:<device node>". + * In the second case, the two paths are guaranteed + * not to contain the substring "//". + * @param pvBackend Backend specific pointer, unused for the linux backend. + */ +static DECLCALLBACK(int) usbProxyLinuxOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + LogFlow(("usbProxyLinuxOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + const char *pszDevNode; + const char *pszPath; + size_t cchPath; + bool fUsingSysfs; + + /* + * Are we using sysfs or usbfs? + */ +#ifdef VBOX_USB_WITH_SYSFS + fUsingSysfs = strncmp(pszAddress, RT_STR_TUPLE("sysfs:")) == 0; + if (fUsingSysfs) + { + pszDevNode = strstr(pszAddress, "//device:"); + if (!pszDevNode) + { + LogRel(("usbProxyLinuxOpen: Invalid device address: '%s'\n", pszAddress)); + return VERR_INVALID_PARAMETER; + } + + pszPath = pszAddress + sizeof("sysfs:") - 1; + cchPath = pszDevNode - pszPath; + pszDevNode += sizeof("//device:") - 1; + } + else +#endif /* VBOX_USB_WITH_SYSFS */ + { +#ifndef VBOX_USB_WITH_SYSFS + fUsingSysfs = false; +#endif + pszPath = pszDevNode = pszAddress; + cchPath = strlen(pszPath); + } + + /* + * Try open the device node. + */ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszDevNode, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the linux backend data. + */ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + RTListInit(&pDevLnx->ListFree); + RTListInit(&pDevLnx->ListInFlight); + pDevLnx->pszPath = RTStrDupN(pszPath, cchPath); + if (pDevLnx->pszPath) + { + rc = RTPipeCreate(&pDevLnx->hPipeWakeupR, &pDevLnx->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + pDevLnx->fUsingSysfs = fUsingSysfs; + pDevLnx->hFile = hFile; + pDevLnx->fClaimedIfsMask = 0; + rc = RTCritSectInit(&pDevLnx->CritSect); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyLinuxOpen(%p, %s): returns successfully File=%RTfile iActiveCfg=%d\n", + pProxyDev, pszAddress, pDevLnx->hFile, pProxyDev->iActiveCfg)); + + return VINF_SUCCESS; + } + RTPipeClose(pDevLnx->hPipeWakeupR); + RTPipeClose(pDevLnx->hPipeWakeupW); + } + } + else + rc = VERR_NO_MEMORY; + + RTFileClose(hFile); + } + else if (rc == VERR_ACCESS_DENIED) + rc = VERR_VUSB_USBFS_PERMISSION; + + Log(("usbProxyLinuxOpen(%p, %s) failed, rc=%s!\n", pProxyDev, pszAddress, + RTErrGetShort(rc))); + + NOREF(pvBackend); + return rc; +} + + +/** + * Claims all the interfaces and figures out the + * current configuration. + * + * @returns VINF_SUCCESS. + * @param pProxyDev The proxy device. + */ +static DECLCALLBACK(int) usbProxyLinuxInit(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Brute force rulez. + * usbProxyLinuxSetConnected check for masked interfaces. + */ + unsigned iIf; + for (iIf = 0; iIf < 256; iIf++) + usbProxyLinuxSetConnected(pProxyDev, iIf, false, true); + + /* + * Determine the active configuration. + * + * If there isn't any active configuration, we will get EHOSTUNREACH (113) errors + * when trying to read the device descriptors in usbProxyDevCreate. So, we'll make + * the first one active (usually 1) then. + */ + pProxyDev->cIgnoreSetConfigs = 1; + int iFirstCfg; + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, &iFirstCfg); + if (pProxyDev->iActiveCfg == -1) + { + usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iFirstCfg, false, UINT32_MAX); + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); + Log(("usbProxyLinuxInit: No active config! Tried to set %d: iActiveCfg=%d\n", iFirstCfg, pProxyDev->iActiveCfg)); + } + else + Log(("usbProxyLinuxInit(%p): iActiveCfg=%d\n", pProxyDev, pProxyDev->iActiveCfg)); + return VINF_SUCCESS; +} + + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyLinuxClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyLinuxClose: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + AssertPtrReturnVoid(pDevLnx); + + /* + * Try put the device in a state which linux can cope with before we release it. + * Resetting it would be a nice start, although we must remember + * that it might have been disconnected... + * + * Don't reset if we're masking interfaces or if construction failed. + */ + if (pProxyDev->fInited) + { + /* ASSUMES: thread == EMT */ + if ( pProxyDev->fMaskedIfs + || !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) + { + /* Connect drivers. */ + unsigned iIf; + for (iIf = 0; iIf < 256; iIf++) + usbProxyLinuxSetConnected(pProxyDev, iIf, true, true); + Log(("USB: Successfully reset device pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + } + else if (errno != ENODEV) + LogRel(("USB: Reset failed, errno=%d, pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); + else /* This will happen if device was detached. */ + Log(("USB: Reset failed, errno=%d (ENODEV), pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); + } + + /* + * Now we can free all the resources and close the device. + */ + RTCritSectDelete(&pDevLnx->CritSect); + + PUSBPROXYURBLNX pUrbLnx; + PUSBPROXYURBLNX pUrbLnxNext; + RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + RTListNodeRemove(&pUrbLnx->NodeList); + + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX) + && errno != ENODEV + && errno != ENOENT) + AssertMsgFailed(("errno=%d\n", errno)); + + if (pUrbLnx->pSplitHead) + { + PUSBPROXYURBLNX pCur = pUrbLnx->pSplitNext; + while (pCur) + { + PUSBPROXYURBLNX pFree = pCur; + pCur = pFree->pSplitNext; + if ( !pFree->fSplitElementReaped + && usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pFree->KUrb, false, UINT32_MAX) + && errno != ENODEV + && errno != ENOENT) + AssertMsgFailed(("errno=%d\n", errno)); + RTMemFree(pFree); + } + } + else + Assert(!pUrbLnx->pSplitNext); + RTMemFree(pUrbLnx); + } + + RTListForEachSafe(&pDevLnx->ListFree, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) + { + RTListNodeRemove(&pUrbLnx->NodeList); + RTMemFree(pUrbLnx); + } + + RTFileClose(pDevLnx->hFile); + pDevLnx->hFile = NIL_RTFILE; + + RTPipeClose(pDevLnx->hPipeWakeupR); + RTPipeClose(pDevLnx->hPipeWakeupW); + + RTStrFree(pDevLnx->pszPath); + + LogFlow(("usbProxyLinuxClose: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset} */ +static DECLCALLBACK(int) usbProxyLinuxReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + RT_NOREF(fResetOnLinux); + Assert(!pProxyDev->fMaskedIfs); + LogFlow(("usbProxyLinuxReset: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + + uint32_t fActiveIfsMask = pDevLnx->fClaimedIfsMask; + unsigned i; + + /* + * Before reset, release claimed interfaces. This less than obvious move + * prevents Linux from rebinding in-kernel drivers to the device after reset. + */ + for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i) + { + if (fActiveIfsMask & RT_BIT(i)) + { + usbProxyLinuxReleaseInterface(pProxyDev, i); + } + } + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) + { + int rc = errno; + LogRel(("usb-linux: Reset failed, rc=%s errno=%d.\n", + RTErrGetShort(RTErrConvertFromErrno(rc)), rc)); + pProxyDev->iActiveCfg = -1; + return RTErrConvertFromErrno(rc); + } + + /* + * Now reclaim previously claimed interfaces. If that doesn't work, let's hope + * the guest/VUSB can recover from that. Can happen if reset changes configuration. + */ + for (i = 0; i < (sizeof(fActiveIfsMask) * 8); ++i) + { + if (fActiveIfsMask & RT_BIT(i)) + { + usbProxyLinuxClaimInterface(pProxyDev, i); + } + } + + /* find the active config - damn annoying. */ + pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); + LogFlow(("usbProxyLinuxReset: returns successfully iActiveCfg=%d\n", pProxyDev->iActiveCfg)); + + pProxyDev->cIgnoreSetConfigs = 2; + return VINF_SUCCESS; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyLinuxSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlow(("usbProxyLinuxSetConfig: pProxyDev=%s cfg=%#x\n", + usbProxyGetName(pProxyDev), iCfg)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iCfg, true, UINT32_MAX)) + { + Log(("usb-linux: Set configuration. errno=%d\n", errno)); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Claims an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlow(("usbProxyLinuxClaimInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); + usbProxyLinuxSetConnected(pProxyDev, iIf, false, false); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLAIMINTERFACE, &iIf, true, UINT32_MAX)) + { + pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf); + LogRel(("usb-linux: Claim interface. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + pDevLnx->fClaimedIfsMask |= RT_BIT(iIf); + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + LogFlow(("usbProxyLinuxReleaseInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RELEASEINTERFACE, &iIf, true, UINT32_MAX)) + { + LogRel(("usb-linux: Release interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + pDevLnx->fClaimedIfsMask &= ~RT_BIT(iIf); + return VINF_SUCCESS; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyLinuxSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf; + LogFlow(("usbProxyLinuxSetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt)); + + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETINTERFACE, &SetIf, true, UINT32_MAX)) + { + Log(("usb-linux: Set interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxyLinuxClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + LogFlow(("usbProxyLinuxClearHaltedEp: pProxyDev=%s EndPt=%u\n", usbProxyGetName(pProxyDev), EndPt)); + + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLEAR_HALT, &EndPt, true, UINT32_MAX)) + { + /* + * Unfortunately this doesn't work on control pipes. + * Windows doing this on the default endpoint and possibly other pipes too, + * so we'll feign success for ENOENT errors. + */ + if (errno == ENOENT) + { + Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d - IGNORED\n", + errno, usbProxyGetName(pProxyDev), EndPt)); + return VINF_SUCCESS; + } + Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d\n", + errno, usbProxyGetName(pProxyDev), EndPt)); + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + + +/** + * Setup packet byte-swapping routines. + */ +static void usbProxyLinuxUrbSwapSetup(PVUSBSETUP pSetup) +{ + pSetup->wValue = RT_H2LE_U16(pSetup->wValue); + pSetup->wIndex = RT_H2LE_U16(pSetup->wIndex); + pSetup->wLength = RT_H2LE_U16(pSetup->wLength); +} + + +/** + * Clean up after a failed URB submit. + */ +static void usbProxyLinuxCleanupFailedSubmit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged) +{ + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + + /* discard and reap later (walking with pUrbLnx). */ + if (pUrbLnx != pCur) + { + for (;;) + { + pUrbLnx->fCanceledBySubmit = true; + pUrbLnx->KUrb.usercontext = NULL; + if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX)) + { + if (errno == ENODEV) + *pfUnplugged = true; + else if (errno == ENOENT) + pUrbLnx->fSplitElementReaped = true; + else + LogRel(("USB: Failed to discard %p! errno=%d (pUrb=%p)\n", pUrbLnx->KUrb.usercontext, errno, pUrb)); /* serious! */ + } + if (pUrbLnx->pSplitNext == pCur) + { + pUrbLnx->pSplitNext = NULL; + break; + } + pUrbLnx = pUrbLnx->pSplitNext; Assert(pUrbLnx); + } + } + + /* free the unsubmitted ones. */ + while (pCur) + { + PUSBPROXYURBLNX pFree = pCur; + pCur = pCur->pSplitNext; + usbProxyLinuxUrbFree(pProxyDev, pFree); + } + + /* send unplug event if we failed with ENODEV originally. */ + if (*pfUnplugged) + usbProxLinuxUrbUnplugged(pProxyDev); +} + +/** + * Submit one URB through the usbfs IOCTL interface, with + * retries + * + * @returns VBox status code. + */ +static int usbProxyLinuxSubmitURB(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged) +{ + RT_NOREF(pUrb); + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + unsigned cTries = 0; + + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pCur->KUrb)) + { + if (errno == EINTR) + continue; + if (errno == ENODEV) + { + Log(("usbProxyLinuxSubmitURB: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + *pfUnplugged = true; + return RTErrConvertFromErrno(errno); + } + + Log(("usb-linux: Submit URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", + pUrb, errno, pCur->KUrb.type, pCur->KUrb.endpoint, pCur->KUrb.buffer_length, cTries)); + if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ + continue; + + return RTErrConvertFromErrno(errno); + } + return VINF_SUCCESS; +} + +/** The split size. 16K in known Linux kernel versions. */ +#define SPLIT_SIZE 0x4000 + +/** + * Create a URB fragment of up to SPLIT_SIZE size and hook it + * into the list of fragments. + * + * @returns pointer to newly allocated URB fragment or NULL. + */ +static PUSBPROXYURBLNX usbProxyLinuxSplitURBFragment(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pHead, PUSBPROXYURBLNX pCur) +{ + PUSBPROXYURBLNX pNew; + uint32_t cbLeft = pCur->cbSplitRemaining; + uint8_t *pb = (uint8_t *)pCur->KUrb.buffer; + + LogFlowFunc(("pProxyDev=%p pHead=%p pCur=%p\n", pProxyDev, pHead, pCur)); + + Assert(cbLeft != 0); + pNew = pCur->pSplitNext = usbProxyLinuxUrbAlloc(pProxyDev, pHead); + if (!pNew) + { + usbProxyLinuxUrbFreeSplitList(pProxyDev, pHead); + return NULL; + } + Assert(pNew->pSplitHead == pHead); + Assert(pNew->pSplitNext == NULL); + + pNew->KUrb = pHead->KUrb; + pNew->KUrb.buffer = pb + pCur->KUrb.buffer_length; + pNew->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); + pNew->KUrb.actual_length = 0; + + cbLeft -= pNew->KUrb.buffer_length; + Assert(cbLeft < INT32_MAX); + pNew->cbSplitRemaining = cbLeft; + LogFlowFunc(("returns pNew=%p\n", pNew)); + return pNew; +} + +/** + * Try splitting up a VUSB URB into smaller URBs which the + * linux kernel (usbfs) can deal with. + * + * NB: For ShortOK reads things get a little tricky - we don't + * know how much data is going to arrive and not all the + * fragment URBs might be filled. We can only safely set up one + * URB at a time -> worse performance but correct behaviour. + * + * @returns VBox status code. + * @param pProxyDev The proxy device. + * @param pUrbLnx The linux URB which was rejected because of being too big. + * @param pUrb The VUSB URB. + */ +static int usbProxyLinuxUrbQueueSplit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PVUSBURB pUrb) +{ + /* + * Split it up into SPLIT_SIZE sized blocks. + */ + const unsigned cKUrbs = (pUrb->cbData + SPLIT_SIZE - 1) / SPLIT_SIZE; + LogFlow(("usbProxyLinuxUrbQueueSplit: pUrb=%p cKUrbs=%d cbData=%d\n", pUrb, cKUrbs, pUrb->cbData)); + + uint32_t cbLeft = pUrb->cbData; + uint8_t *pb = &pUrb->abData[0]; + + /* the first one (already allocated) */ + switch (pUrb->enmType) + { + default: /* shut up gcc */ + case VUSBXFERTYPE_BULK: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; break; + case VUSBXFERTYPE_INTR: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; break; + case VUSBXFERTYPE_MSG: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; break; + case VUSBXFERTYPE_ISOC: + AssertMsgFailed(("We can't split isochronous URBs!\n")); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return VERR_INVALID_PARAMETER; /** @todo Better status code. */ + } + pUrbLnx->KUrb.endpoint = pUrb->EndPt; + if (pUrb->enmDir == VUSBDIRECTION_IN) + pUrbLnx->KUrb.endpoint |= 0x80; + pUrbLnx->KUrb.flags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; + pUrbLnx->KUrb.status = 0; + pUrbLnx->KUrb.buffer = pb; + pUrbLnx->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); + pUrbLnx->KUrb.actual_length = 0; + pUrbLnx->KUrb.start_frame = 0; + pUrbLnx->KUrb.number_of_packets = 0; + pUrbLnx->KUrb.error_count = 0; + pUrbLnx->KUrb.signr = 0; + pUrbLnx->KUrb.usercontext = pUrb; + pUrbLnx->pSplitHead = pUrbLnx; + pUrbLnx->pSplitNext = NULL; + + PUSBPROXYURBLNX pCur = pUrbLnx; + + cbLeft -= pUrbLnx->KUrb.buffer_length; + pUrbLnx->cbSplitRemaining = cbLeft; + + int rc = VINF_SUCCESS; + bool fUnplugged = false; + if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk) + { + /* Subsequent fragments will be queued only after the previous fragment is reaped + * and only if necessary. + */ + Log(("usb-linux: Large ShortOK read, only queuing first fragment.\n")); + Assert(pUrbLnx->cbSplitRemaining > 0 && pUrbLnx->cbSplitRemaining < 256 * _1K); + rc = usbProxyLinuxSubmitURB(pProxyDev, pUrbLnx, pUrb, &fUnplugged); + } + else + { + /* the rest. */ + unsigned i; + for (i = 1; i < cKUrbs; i++) + { + pCur = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx, pCur); + if (!pCur) + return VERR_NO_MEMORY; + } + Assert(pCur->cbSplitRemaining == 0); + + /* Submit the blocks. */ + pCur = pUrbLnx; + for (i = 0; i < cKUrbs; i++, pCur = pCur->pSplitNext) + { + rc = usbProxyLinuxSubmitURB(pProxyDev, pCur, pUrb, &fUnplugged); + if (RT_FAILURE(rc)) + break; + } + } + + if (RT_SUCCESS(rc)) + { + pUrb->Dev.pvPrivate = pUrbLnx; + usbProxyLinuxUrbLinkInFlight(USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX), pUrbLnx); + LogFlow(("usbProxyLinuxUrbQueueSplit: ok\n")); + return VINF_SUCCESS; + } + + usbProxyLinuxCleanupFailedSubmit(pProxyDev, pUrbLnx, pCur, pUrb, &fUnplugged); + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyLinuxUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + unsigned cTries; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + LogFlow(("usbProxyLinuxUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + usbProxyGetName(pProxyDev), pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Allocate a linux urb. + */ + PUSBPROXYURBLNX pUrbLnx = usbProxyLinuxUrbAlloc(pProxyDev, NULL); + if (!pUrbLnx) + return VERR_NO_MEMORY; + + pUrbLnx->KUrb.endpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + pUrbLnx->KUrb.status = 0; + pUrbLnx->KUrb.flags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; + pUrbLnx->KUrb.buffer = pUrb->abData; + pUrbLnx->KUrb.buffer_length = pUrb->cbData; + pUrbLnx->KUrb.actual_length = 0; + pUrbLnx->KUrb.start_frame = 0; + pUrbLnx->KUrb.number_of_packets = 0; + pUrbLnx->KUrb.error_count = 0; + pUrbLnx->KUrb.signr = 0; + pUrbLnx->KUrb.usercontext = pUrb; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; + if (pUrb->cbData < sizeof(VUSBSETUP)) + { + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return VERR_BUFFER_UNDERFLOW; + } + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + LogFlow(("usbProxyLinuxUrbQueue: message\n")); + break; + case VUSBXFERTYPE_BULK: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; + break; + case VUSBXFERTYPE_ISOC: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_ISO; + pUrbLnx->KUrb.flags |= USBDEVFS_URB_ISO_ASAP; + pUrbLnx->KUrb.number_of_packets = pUrb->cIsocPkts; + unsigned i; + for (i = 0; i < pUrb->cIsocPkts; i++) + { +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + pUrbLnx->KUrb.iso_frame_desc[i].length = pUrb->aIsocPkts[i].cb; + pUrbLnx->KUrb.iso_frame_desc[i].actual_length = 0; + pUrbLnx->KUrb.iso_frame_desc[i].status = 0x7fff; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + } + break; + case VUSBXFERTYPE_INTR: + pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; + break; + default: + rc = VERR_INVALID_PARAMETER; /** @todo better status code. */ + } + + /* + * We have to serialize access by using the critial section here because this + * thread might be suspended after submitting the URB but before linking it into + * the in flight list. This would get us in trouble when reaping the URB on another + * thread while it isn't in the in flight list. + * + * Linking the URB into the list before submitting it like it was done in the past is not + * possible either because submitting the URB might fail here because the device gets + * detached. The reaper thread gets this event too and might race this thread before we + * can unlink the URB from the active list and the common code might end up freeing + * the common URB structure twice. + */ + RTCritSectEnter(&pDevLnx->CritSect); + /* + * Submit it. + */ + cTries = 0; + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pUrbLnx->KUrb)) + { + if (errno == EINTR) + continue; + if (errno == ENODEV) + { + rc = RTErrConvertFromErrno(errno); + Log(("usbProxyLinuxUrbQueue: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + + RTCritSectLeave(&pDevLnx->CritSect); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + usbProxLinuxUrbUnplugged(pProxyDev); + return rc; + } + + /* + * usbfs has or used to have a low buffer limit (16KB) in order to prevent + * processes wasting kmalloc'ed memory. It will return EINVAL if break that + * limit, and we'll have to split the VUSB URB up into multiple linux URBs. + * + * Since this is a limit which is subject to change, we cannot check for it + * before submitting the URB. We just have to try and fail. + */ + if ( errno == EINVAL + && pUrb->cbData >= 8*_1K) + { + rc = usbProxyLinuxUrbQueueSplit(pProxyDev, pUrbLnx, pUrb); + RTCritSectLeave(&pDevLnx->CritSect); + return rc; + } + + Log(("usb-linux: Queue URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", + pUrb, errno, pUrbLnx->KUrb.type, pUrbLnx->KUrb.endpoint, pUrbLnx->KUrb.buffer_length, cTries)); + if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ + continue; + + RTCritSectLeave(&pDevLnx->CritSect); + rc = RTErrConvertFromErrno(errno); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + return rc; + } + + usbProxyLinuxUrbLinkInFlight(pDevLnx, pUrbLnx); + RTCritSectLeave(&pDevLnx->CritSect); + + LogFlow(("usbProxyLinuxUrbQueue: ok\n")); + pUrb->Dev.pvPrivate = pUrbLnx; + return rc; +} + + +/** + * Translate the linux status to a VUSB status. + * + * @remarks see cc_to_error in ohci.h, uhci_map_status in uhci-q.c, + * sitd_complete+itd_complete in ehci-sched.c, and qtd_copy_status in + * ehci-q.c. + */ +static VUSBSTATUS vusbProxyLinuxStatusToVUsbStatus(int iStatus) +{ + switch (iStatus) + { + /** @todo VUSBSTATUS_NOT_ACCESSED */ + case -EXDEV: /* iso transfer, partial result. */ + case 0: + return VUSBSTATUS_OK; + + case -EILSEQ: + return VUSBSTATUS_CRC; + + case -EREMOTEIO: /* ehci and ohci uses this for underflow error. */ + return VUSBSTATUS_DATA_UNDERRUN; + case -EOVERFLOW: + return VUSBSTATUS_DATA_OVERRUN; + + case -ETIME: + case -ENODEV: + return VUSBSTATUS_DNR; + + //case -ECOMM: + // return VUSBSTATUS_BUFFER_OVERRUN; + //case -ENOSR: + // return VUSBSTATUS_BUFFER_UNDERRUN; + + case -EPROTO: + Log(("vusbProxyLinuxStatusToVUsbStatus: DNR/EPPROTO!!\n")); + return VUSBSTATUS_DNR; + + case -EPIPE: + Log(("vusbProxyLinuxStatusToVUsbStatus: STALL/EPIPE!!\n")); + return VUSBSTATUS_STALL; + + case -ESHUTDOWN: + Log(("vusbProxyLinuxStatusToVUsbStatus: SHUTDOWN!!\n")); + return VUSBSTATUS_STALL; + + case -ENOENT: + Log(("vusbProxyLinuxStatusToVUsbStatus: ENOENT!!\n")); + return VUSBSTATUS_STALL; + + default: + Log(("vusbProxyLinuxStatusToVUsbStatus: status %d!!\n", iStatus)); + return VUSBSTATUS_STALL; + } +} + + +/** + * Get and translates the linux status to a VUSB status. + */ +static VUSBSTATUS vusbProxyLinuxUrbGetStatus(PUSBPROXYURBLNX pUrbLnx) +{ + return vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.status); +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyLinuxUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PUSBPROXYURBLNX pUrbLnx = NULL; + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + + /* + * Block for requested period. + * + * It seems to me that the path of poll() is shorter and + * involves less semaphores than ioctl() on usbfs. So, we'll + * do a poll regardless of whether cMillies == 0 or not. + */ + if (cMillies) + { + int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : cMillies; + + for (;;) + { + struct pollfd pfd[2]; + pfd[0].fd = RTFileToNative(pDevLnx->hFile); + pfd[0].events = POLLOUT | POLLWRNORM /* completed async */ + | POLLERR | POLLHUP /* disconnected */; + pfd[0].revents = 0; + + pfd[1].fd = RTPipeToNative(pDevLnx->hPipeWakeupR); + pfd[1].events = POLLIN | POLLHUP; + pfd[1].revents = 0; + + int rc = poll(&pfd[0], 2, cMilliesWait); + Log(("usbProxyLinuxUrbReap: poll rc = %d\n", rc)); + if (rc >= 1) + { + /* If the pipe caused the return drain it. */ + if (pfd[1].revents & POLLIN) + { + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevLnx->hPipeWakeupR, &bRead, 1, &cbIgnored); + } + break; + } + if (rc >= 0) + return NULL; + + if (errno != EAGAIN) + { + Log(("usb-linux: Reap URB - poll -> %d errno=%d pProxyDev=%s\n", rc, errno, usbProxyGetName(pProxyDev))); + return NULL; + } + Log(("usbProxyLinuxUrbReap: poll again - weird!!!\n")); + } + } + + /* + * Reap URBs, non-blocking. + */ + for (;;) + { + struct usbdevfs_urb *pKUrb; + while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_REAPURBNDELAY, &pKUrb)) + if (errno != EINTR) + { + if (errno == ENODEV) + usbProxLinuxUrbUnplugged(pProxyDev); + else + Log(("usb-linux: Reap URB. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return NULL; + } + pUrbLnx = (PUSBPROXYURBLNX)pKUrb; + + /* split list: Is the entire split list done yet? */ + if (pUrbLnx->pSplitHead) + { + pUrbLnx->fSplitElementReaped = true; + + /* for variable size URBs, we may need to queue more if the just-reaped URB was completely filled */ + if (pUrbLnx->cbSplitRemaining && (pKUrb->actual_length == pKUrb->buffer_length) && !pUrbLnx->pSplitNext) + { + bool fUnplugged = false; + bool fSucceeded; + + Assert(pUrbLnx->pSplitHead); + Assert((pKUrb->endpoint & 0x80) && !(pKUrb->flags & USBDEVFS_URB_SHORT_NOT_OK)); + PUSBPROXYURBLNX pNew = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx->pSplitHead, pUrbLnx); + if (!pNew) + { + Log(("usb-linux: Allocating URB fragment failed. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); + return NULL; + } + PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; + fSucceeded = usbProxyLinuxSubmitURB(pProxyDev, pNew, pUrb, &fUnplugged); + if (fUnplugged) + usbProxLinuxUrbUnplugged(pProxyDev); + if (!fSucceeded) + return NULL; + continue; /* try reaping another URB */ + } + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx->pSplitHead; pCur; pCur = pCur->pSplitNext) + if (!pCur->fSplitElementReaped) + { + pUrbLnx = NULL; + break; + } + if (!pUrbLnx) + continue; + pUrbLnx = pUrbLnx->pSplitHead; + } + break; + } + + /* + * Ok, we got one! + */ + PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; + if ( pUrb + && !pUrbLnx->fCanceledBySubmit) + { + if (pUrbLnx->pSplitHead) + { + /* split - find the end byte and the first error status. */ + Assert(pUrbLnx == pUrbLnx->pSplitHead); + uint8_t *pbEnd = &pUrb->abData[0]; + pUrb->enmStatus = VUSBSTATUS_OK; + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) + { + if (pCur->KUrb.actual_length) + pbEnd = (uint8_t *)pCur->KUrb.buffer + pCur->KUrb.actual_length; + if (pUrb->enmStatus == VUSBSTATUS_OK) + pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pCur); + } + pUrb->cbData = pbEnd - &pUrb->abData[0]; + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFreeSplitList(pProxyDev, pUrbLnx); + } + else + { + /* unsplit. */ + pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pUrbLnx); + pUrb->cbData = pUrbLnx->KUrb.actual_length; + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + unsigned i, off; + for (i = 0, off = 0; i < pUrb->cIsocPkts; i++) + { +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Warray-bounds" +#endif + pUrb->aIsocPkts[i].enmStatus = vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.iso_frame_desc[i].status); + Assert(pUrb->aIsocPkts[i].off == off); + pUrb->aIsocPkts[i].cb = pUrbLnx->KUrb.iso_frame_desc[i].actual_length; + off += pUrbLnx->KUrb.iso_frame_desc[i].length; +#if RT_GNUC_PREREQ(4, 6) +# pragma GCC diagnostic pop +#endif + } + } + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + } + pUrb->Dev.pvPrivate = NULL; + + /* some adjustments for message transfers. */ + if (pUrb->enmType == VUSBXFERTYPE_MSG) + { + pUrb->cbData += sizeof(VUSBSETUP); + usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); + } + } + else + { + usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); + usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); + pUrb = NULL; + } + + LogFlow(("usbProxyLinuxUrbReap: pProxyDev=%s returns %p\n", usbProxyGetName(pProxyDev), pUrb)); + return pUrb; +} + + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static DECLCALLBACK(int) usbProxyLinuxUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int rc = VINF_SUCCESS; + PUSBPROXYURBLNX pUrbLnx = (PUSBPROXYURBLNX)pUrb->Dev.pvPrivate; + if (pUrbLnx->pSplitHead) + { + /* split */ + Assert(pUrbLnx == pUrbLnx->pSplitHead); + PUSBPROXYURBLNX pCur; + for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) + { + if (pCur->fSplitElementReaped) + continue; + if ( !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX) + || errno == ENOENT) + continue; + if (errno == ENODEV) + break; + /** @todo Think about how to handle errors wrt. to the status code. */ + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n", + pUrb, errno, usbProxyGetName(pProxyDev))); + } + } + else + { + /* unsplit */ + if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, true, UINT32_MAX) + && errno != ENODEV /* deal with elsewhere. */ + && errno != ENOENT) + { + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n", + pUrb, errno, usbProxyGetName(pProxyDev))); + rc = RTErrConvertFromErrno(errno); + } + } + + return rc; +} + + +static DECLCALLBACK(int) usbProxyLinuxWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevLnx->hPipeWakeupW, "", 1, &cbIgnored); +} + +/** + * The Linux USB Proxy Backend. + */ +const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVLNX), + usbProxyLinuxOpen, + usbProxyLinuxInit, + usbProxyLinuxClose, + usbProxyLinuxReset, + usbProxyLinuxSetConfig, + usbProxyLinuxClaimInterface, + usbProxyLinuxReleaseInterface, + usbProxyLinuxSetInterface, + usbProxyLinuxClearHaltedEp, + usbProxyLinuxUrbQueue, + usbProxyLinuxUrbCancel, + usbProxyLinuxUrbReap, + usbProxyLinuxWakeup, + 0 +}; + + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * End: + */ + diff --git a/src/VBox/Devices/USB/os2/Makefile.kup b/src/VBox/Devices/USB/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/os2/Makefile.kup diff --git a/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp new file mode 100644 index 00000000..0464e6e6 --- /dev/null +++ b/src/VBox/Devices/USB/os2/USBProxyDevice-os2.cpp @@ -0,0 +1,870 @@ +/* $Id: USBProxyDevice-os2.cpp $ */ +/** @file + * USB device proxy - the Linux backend. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/semaphore.h> +#include <iprt/file.h> +#include "../USBProxyDevice.h" + +#define INCL_BASE +#define INCL_ERRORS +#include <os2.h> +#include <usbcalls.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Structure for keeping track of the URBs for a device. + */ +typedef struct USBPROXYURBOS2 +{ + /** Pointer to the virtual URB. */ + PVUSBURB pUrb; + /** Pointer to the next OS/2 URB. */ + struct USBPROXYURBOS2 *pNext; + /** Pointer to the previous OS/2 URB. */ + struct USBPROXYURBOS2 *pPrev; +} USBPROXYURBOS2, *PUSBPROXYURBOS2; + +/** + * Data for the OS/2 usb proxy backend. + */ +typedef struct USBPROXYDEVOS2 +{ + /** The async thread for this device. + * Currently only one thread is used, but this might have to change... */ + RTTHREAD Thread; + /** Thread termination indicator. */ + bool volatile fTerminate; + /** The USB handle. */ + USBHANDLE hDevice; + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** For blocking reap calls. */ + RTSEMEVENT EventSyncWait; + /** List of URBs to process. Doubly linked. */ + PUSBPROXYURBOS2 pTodoHead; + /** The tail pointer. */ + PUSBPROXYURBOS2 pTodoTail; + /** The list of free linux URBs. Singly linked. */ + PUSBPROXYURBOS2 pFreeHead; + /** The list of active linux URBs. Doubly linked. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. */ + PUSBPROXYURBOS2 pInFlightHead; + /** The list of landed linux URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBOS2 pTaxingHead; + /** The tail of the landed linux URBs. */ + PUSBPROXYURBOS2 pTaxingTail; +} USBPROXYDEVOS2, *PUSBPROXYDEVOS2; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +#ifdef DYNAMIC_USBCALLS +static int usbProxyOs2GlobalInit(void); +#endif +static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev); +static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2); +static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef DYNAMIC_USBCALLS +static HMODULE g_hmod; +static APIRET (APIENTRY *g_pfnUsbOpen)(PUSBHANDLE, USHORT, USHORT, USHORT, USHORT); +static APIRET (APIENTRY *g_pfnUsbClose)(USBHANDLE); +static APIRET (APIENTRY *g_pfnUsbCtrlMessage)(USBHANDLE, UCHAR, UCHAR, USHORT, USHORT, USHORT, void *, ULONG); +static APIRET (APIENTRY *g_pfnUsbBulkRead2)(USBHANDLE, UCHAR, UCHAR, BOOL, PULONG, void *, ULONG); +static APIRET (APIENTRY *g_pfnUsbBulkWrite2)(USBHANDLE, UCHAR, UCHAR, BOOL, ULONG, void *, ULONG); +#else +# define g_pfnUsbOpen UsbOpen +# define g_pfnUsbClose UsbClose +# define g_pfnUsbCtrlMessage UsbCtrlMessage +# define g_pfnUsbBulkRead2 UsbBulkRead2 +# define g_pfnUsbBulkWrite2 UsbBulkWrite2 +#endif + + + +#ifdef DYNAMIC_USBCALLS +/** + * Loads usbcalls.dll and resolves the symbols we need. + * + * The usbcalls.dll will not be unloaded. + * + * @returns VBox status code. + */ +static int usbProxyOs2GlobalInit(void) +{ + int rc = DosLoadModule(NULL, 0, (PCSZ)"usbcalls", &g_hmod); + rc = RTErrConvertFromOS2(rc); + if (RT_SUCCESS(rc)) + { + if ( (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbOpen", (PPFN)&g_pfnUsbOpen)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbClose", (PPFN)&g_pfnUsbClose)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbCtrlMessage", (PPFN)&g_pfnUsbCtrlMessage)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkRead", (PPFN)&g_pfnUsbBulkRead)) == NO_ERROR + && (rc = DosQueryProcAddr(g_hmod, 0, (PCSZ)"UsbBulkWrite", (PPFN)&g_pfnUsbBulkWrite)) == NO_ERROR + ) + { + + return VINF_SUCCESS; + } + + g_pfnUsbOpen = NULL; + g_pfnUsbClose = NULL; + g_pfnUsbCtrlMessage = NULL; + g_pfnUsbBulkRead = NULL; + g_pfnUsbBulkWrite = NULL; + DosFreeModule(g_hmod); + } + + g_hmod = NULLHANDLE; + return rc; +} +#endif + + + +/** + * Allocates a OS/2 URB request structure. + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * @param pProxyDev The proxy device instance. + */ +static PUSBPROXYURBOS2 usbProxyOs2UrbAlloc(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + PUSBPROXYURBOS2 pUrbOs2; + + RTCritSectEnter(&pDevOs2->CritSect); + + /* + * Try remove a linux URB from the free list, if none there allocate a new one. + */ + pUrbOs2 = pDevOs2->pFreeHead; + if (pUrbOs2) + pDevOs2->pFreeHead = pUrbOs2->pNext; + else + { + RTCritSectLeave(&pDevOs2->CritSect); + pUrbOs2 = (PUSBPROXYURBOS2)RTMemAlloc(sizeof(*pUrbOs2)); + if (!pUrbOs2) + return NULL; + RTCritSectEnter(&pDevOs2->CritSect); + } + + /* + * Link it into the active list + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pInFlightHead; + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = pUrbOs2; + pDevOs2->pInFlightHead = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); + return pUrbOs2; +} + + +/** + * Frees a linux URB request structure. + * + * @param pProxyDev The proxy device instance. + * @param pUrbOs2 The linux URB to free. + */ +static void usbProxyOs2UrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBOS2 pUrbOs2) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + + RTCritSectEnter(&pDevOs2->CritSect); + + /* + * Remove from the active list. + */ + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = pUrbOs2->pPrev; + else if (pDevOs2->pTaxingTail == pUrbOs2) + pDevOs2->pTaxingTail = pUrbOs2->pPrev; + else if (pDevOs2->pTodoTail == pUrbOs2) + pDevOs2->pTodoTail = pUrbOs2->pPrev; + + if (pUrbOs2->pPrev) + pUrbOs2->pPrev->pNext = pUrbOs2->pNext; + else if (pDevOs2->pTaxingHead == pUrbOs2) + pDevOs2->pTaxingHead = pUrbOs2->pNext; + else if (pDevOs2->pInFlightHead == pUrbOs2) + pDevOs2->pInFlightHead = pUrbOs2->pNext; + else if (pDevOs2->pTodoHead == pUrbOs2) + pDevOs2->pTodoHead = pUrbOs2->pNext; + + /* + * Link it into the free list. + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pFreeHead; + pDevOs2->pFreeHead = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); +} + + +/** + * Thread for executing the URBs asynchronously. + * + * @returns VINF_SUCCESS. + * @param Thread Thread handle (IPRT). + * @param pvProxyDev Pointer to the proxy device we're servicing. + */ +static DECLCALLBACK(int) usbProxyOs2AsyncThread(RTTHREAD Thread, void *pvProxyDev) +{ + PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pvProxyDev; + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + size_t cbLow = 0; + void *pvLow = NULL; + + + /* + * The main loop. + * + * We're always in the critsect, except when waiting or submitting a URB. + */ + int rc = RTCritSectEnter(&pDevOs2->CritSect); AssertRC(rc); + + while (!pDevOs2->fTerminate) + { + /* + * Anything to do? + */ + PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTodoHead; + if (pUrbOs2) + { + pDevOs2->pTodoHead = pUrbOs2->pNext; + if (pUrbOs2->pNext) + pUrbOs2->pNext->pPrev = NULL; + else + pDevOs2->pTodoTail = NULL; + + /* + * Move it to the in-flight list and submit it. + */ + pUrbOs2->pPrev = NULL; + pUrbOs2->pNext = pDevOs2->pInFlightHead; + if (pDevOs2->pInFlightHead) + pDevOs2->pInFlightHead->pPrev = pUrbOs2; + //else + // pDevOs2->pInFlightTail = pUrbOs2; + Log3(("%s: usbProxyOs2AsyncThread: pPickup\n", pUrbOs2->pUrb->pszDesc)); + + RTCritSectLeave(&pDevOs2->CritSect); + + /* + * Process the URB. + */ + PVUSBURB pUrb = pUrbOs2->pUrb; + uint8_t *pbData = &pUrb->abData[0]; + ULONG cbData = pUrb->cbData; + if ( (uintptr_t)pbData >= 0x20000000 + || ((uintptr_t)pbData & 0xfff)) + { + if (cbData > cbLow) + { + if (pvLow) + DosFreeMem(pvLow); + cbLow = (cbData + 0xffff) & ~0xffff; + rc = DosAllocMem(&pvLow, cbLow, PAG_WRITE | PAG_READ | OBJ_TILE | PAG_COMMIT); + if (rc) + { + cbLow = 0; + pvLow = NULL; + } + } + if (pvLow) + pbData = (uint8_t *)memcpy(pvLow, pbData, cbData); + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + PVUSBSETUP pSetup = (PVUSBSETUP)&pbData[0]; + Log2(("%s: usbProxyOs2AsyncThread: CtlrMsg\n", pUrb->pszDesc)); + rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, /** @todo this API must take a endpoint number! */ + pSetup->bmRequestType, + pSetup->bRequest, + pSetup->wValue, + pSetup->wIndex, + pSetup->wLength, + pSetup + 1, + 5*60000 /* min */); + break; + } + + case VUSBXFERTYPE_BULK: + { + /* there is a thing with altnative interface thing here... */ + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + Log2(("%s: usbProxyOs2AsyncThread: BulkRead %d\n", pUrb->pszDesc, cbData)); + rc = g_pfnUsbBulkRead2(pDevOs2->hDevice, pUrb->EndPt | 0x80, 0, !pUrb->fShortNotOk, &cbData, pbData, 500);//5*6000); + } + else + { + Log2(("%s: usbProxyOs2AsyncThread: BulkWrite %d\n", pUrb->pszDesc, cbData)); + rc = g_pfnUsbBulkWrite2(pDevOs2->hDevice, pUrb->EndPt, 0, !pUrb->fShortNotOk, cbData, pbData, 500);//5*6000); + } + break; + } + + case VUSBXFERTYPE_INTR: + case VUSBXFERTYPE_ISOC: + default: + Log2(("%s: usbProxyOs2AsyncThread: Unsupported\n", pUrb->pszDesc)); + rc = USB_IORB_FAILED; + break; + } + + /* unbuffer */ + if (pbData == pvLow) + memcpy(pUrb->abData, pbData, pUrb->cbData); + + /* Convert rc to USB status code. */ + int orc = rc; + if (!rc) + pUrb->enmStatus = VUSBSTATUS_OK; + else if (rc == USB_ERROR_LESSTRANSFERED && !pUrb->fShortNotOk) + { + Assert(pUrb->cbData >= cbData); + pUrb->cbData = cbData; + pUrb->enmStatus = VUSBSTATUS_DATA_UNDERRUN; + } + else + pUrb->enmStatus = VUSBSTATUS_STALL; + Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d \n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc); + + /* + * Retire it to the completed list + */ + RTCritSectEnter(&pDevOs2->CritSect); + + pUrbOs2->pNext = NULL; + pUrbOs2->pPrev = pDevOs2->pTaxingTail; + if (pDevOs2->pTaxingTail) + pDevOs2->pTaxingTail->pNext = pUrbOs2; + else + pDevOs2->pTaxingHead = pUrbOs2; + pDevOs2->pTaxingTail = pUrbOs2; + + RTSemEventSignal(pDevOs2->EventSyncWait); + Log2(("%s: usbProxyOs2AsyncThread: orc=%d enmStatus=%d cbData=%d!\n", pUrb->pszDesc, orc, pUrb->enmStatus, pUrb->cbData)); NOREF(orc); + } + else + { + RTThreadUserReset(Thread); + RTCritSectLeave(&pDevOs2->CritSect); + + /* + * Wait for something to do. + */ + RTThreadUserWait(Thread, 30*1000 /* 30 sec */); + + RTCritSectEnter(&pDevOs2->CritSect); + } + } + + RTCritSectLeave(&pDevOs2->CritSect); + if (pvLow) + DosFreeMem(pvLow); + return VINF_SUCCESS; +} + + +/** + * Opens the /proc/bus/usb/bus/addr file. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The path to the device. + * @param pvBackend Backend specific pointer, unused for the linux backend. + */ +static int usbProxyOs2Open(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + LogFlow(("usbProxyOs2Open: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + int rc; + + /* + * Lazy init. + */ +#ifdef DYNAMIC_USBCALLS + if (!g_pfnUsbOpen) + { + rc = usbProxyOs2GlobalInit(); + if (RT_FAILURE(rc)) + return rc; + } +#else +static bool g_fInitialized = false; + if (!g_fInitialized) + { + rc = InitUsbCalls(); + if (rc != NO_ERROR) + return RTErrConvertFromOS2(rc); + g_fInitialized = true; + } +#endif + + /* + * Parse out the open parameters from the address string. + */ + uint16_t idProduct = 0; + uint16_t idVendor = 0; + uint16_t bcdDevice = 0; + uint32_t iEnum = 0; + const char *psz = pszAddress; + do + { + const char chValue = *psz; + AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR); + uint64_t u64Value; + int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value); + AssertReleaseRCReturn(rc, rc); + AssertReleaseReturn(!*psz || *psz == ';', rc); + switch (chValue) + { + case 'p': idProduct = (uint16_t)u64Value; break; + case 'v': idVendor = (uint16_t)u64Value; break; + case 'r': bcdDevice = (uint16_t)u64Value; break; + case 'e': iEnum = (uint16_t)u64Value; break; + default: + AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR); + } + if (*psz == ';') + psz++; + } while (*psz); + + + /* + * Try open (acquire) it. + */ + USBHANDLE hDevice = 0; + int urc = rc = g_pfnUsbOpen(&hDevice, idVendor, idProduct, bcdDevice, iEnum); + if (!rc) + { + /* + * Allocate and initialize the OS/2 backend data. + */ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)RTMemAllocZ(sizeof(*pDevOs2)); + if (pDevOs2) + { + pDevOs2->hDevice = hDevice; + pDevOs2->fTerminate = false; + rc = RTCritSectInit(&pDevOs2->CritSect); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventCreate(&pDevOs2->EventSyncWait); + if (RT_SUCCESS(rc)) + { + pProxyDev->Backend.pv = pDevOs2; + + /** @todo + * Determine the active configuration. + */ + //pProxyDev->cIgnoreSetConfigs = 1; + //pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + + /* + * Create the async worker thread and we're done. + */ + rc = RTThreadCreate(&pDevOs2->Thread, usbProxyOs2AsyncThread, pProxyDev, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "usbproxy"); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyOs2Open(%p, %s): returns successfully - iActiveCfg=%d\n", + pProxyDev, pszAddress, pProxyDev->iActiveCfg)); + return VINF_SUCCESS; + } + + /* failure */ + RTSemEventDestroy(pDevOs2->EventSyncWait); + } + RTCritSectDelete(&pDevOs2->CritSect); + } + RTMemFree(pDevOs2); + } + else + rc = VERR_NO_MEMORY; + g_pfnUsbClose(hDevice); + } + else + rc = VERR_VUSB_USBFS_PERMISSION; /** @todo fix me */ + + Log(("usbProxyOs2Open(%p, %s) failed, rc=%Rrc! urc=%d\n", pProxyDev, pszAddress, rc, urc)); NOREF(urc); + pProxyDev->Backend.pv = NULL; + + NOREF(pvBackend); + return rc; +} + + +/** + * Closes the proxy device. + */ +static void usbProxyOs2Close(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyOs2Close: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + Assert(pDevOs2); + if (!pDevOs2) + return; + + /* + * Tell the thread to terminate. + */ + ASMAtomicXchgBool(&pDevOs2->fTerminate, true); + int rc = RTThreadUserSignal(pDevOs2->Thread); AssertRC(rc); + rc = RTThreadWait(pDevOs2->Thread, 60*1000 /* 1 min */, NULL); AssertRC(rc); + + /* + * Now we can free all the resources and close the device. + */ + RTCritSectDelete(&pDevOs2->CritSect); + RTSemEventDestroy(pDevOs2->EventSyncWait); + + Assert(!pDevOs2->pInFlightHead); + Assert(!pDevOs2->pTodoHead); + Assert(!pDevOs2->pTodoTail); + Assert(!pDevOs2->pTaxingHead); + Assert(!pDevOs2->pTaxingTail); + + PUSBPROXYURBOS2 pUrbOs2; + while ((pUrbOs2 = pDevOs2->pFreeHead) != NULL) + { + pDevOs2->pFreeHead = pUrbOs2->pNext; + RTMemFree(pUrbOs2); + } + + g_pfnUsbClose(pDevOs2->hDevice); + pDevOs2->hDevice = 0; + + RTMemFree(pDevOs2); + pProxyDev->Backend.pv = NULL; + LogFlow(("usbProxyOs2Close: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset} */ +static int usbProxyOs2Reset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + return VINF_SUCCESS; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static int usbProxyOs2SetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + /* + * This is sync - bad. + */ + int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, + 0x00, /* bmRequestType - ?? */ + 0x09, /* bRequest - ?? */ + iCfg, /* wValue - configuration */ + 0, /* wIndex*/ + 0, /* wLength */ + NULL, /* pvData */ + 50 /* Timeout (ms) */); + if (rc) + LogFlow(("usbProxyOs2SetConfig: pProxyDev=%s cfg=%#X -> rc=%d\n", pProxyDev->pUsbIns->pszName, iCfg, rc)); + return rc == 0; +} + + +/** + * Claims an interface. + * @returns success indicator. + */ +static int usbProxyOs2ClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + LogFlow(("usbProxyOs2ClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return true; +} + + +/** + * Releases an interface. + * @returns success indicator. + */ +static int usbProxyOs2ReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + LogFlow(("usbProxyOs2ReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return true; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static int usbProxyOs2SetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + LogFlow(("usbProxyOs2SetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt)); + return true; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static bool usbProxyOs2ClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%x\n", pProxyDev->pUsbIns->pszName, EndPt)); + + /* + * This is sync - bad. + */ + int rc = g_pfnUsbCtrlMessage(pDevOs2->hDevice, + 0x02, /* bmRequestType - ?? */ + 0x01, /* bRequest - ?? */ + 0, /* wValue - endpoint halt */ + EndPt, /* wIndex - endpoint # */ + 0, /* wLength */ + NULL, /* pvData */ + 50 /* Timeout (ms) */); + if (rc) + LogFlow(("usbProxyOs2ClearHaltedEp: pProxyDev=%s EndPt=%u -> rc=%d\n", pProxyDev->pUsbIns->pszName, EndPt, rc)); + return rc == 0; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static int usbProxyOs2UrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + LogFlow(("usbProxyOs2UrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Quickly validate the input. + */ + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + case VUSBDIRECTION_OUT: + break; + default: + AssertMsgFailed(("usbProxyOs2UrbQueue: Invalid direction %d\n", pUrb->enmDir)); + return false; + } + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + break; + case VUSBXFERTYPE_BULK: + break; +/// @todo case VUSBXFERTYPE_INTR: +// break; +// case VUSBXFERTYPE_ISOC: +// break; + default: + return false; + } + + /* + * Allocate an OS/2 urb tracking structure, initialize it, + * add it to the todo list, and wake up the async thread. + */ + PUSBPROXYURBOS2 pUrbOs2 = usbProxyOs2UrbAlloc(pProxyDev); + if (!pUrbOs2) + return false; + + pUrbOs2->pUrb = pUrb; + + RTCritSectEnter(&pDevOs2->CritSect); + + pUrbOs2->pNext = NULL; + pUrbOs2->pPrev = pDevOs2->pTodoTail; + if (pDevOs2->pTodoTail) + pDevOs2->pTodoTail->pNext = pUrbOs2; + else + pDevOs2->pTodoHead = pUrbOs2; + pDevOs2->pTodoTail = pUrbOs2; + + RTCritSectLeave(&pDevOs2->CritSect); + + RTThreadUserSignal(pDevOs2->Thread); + return true; +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static PVUSBURB usbProxyOs2UrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PVUSBURB pUrb = NULL; + PUSBPROXYDEVOS2 pDevOs2 = (PUSBPROXYDEVOS2)pProxyDev->Backend.pv; + + RTCritSectEnter(&pDevOs2->CritSect); + for (;;) + { + /* + * Any URBs pending delivery? + */ + PUSBPROXYURBOS2 pUrbOs2 = pDevOs2->pTaxingHead; + if (pUrbOs2) + { + pUrb = pUrbOs2->pUrb; + usbProxyOs2UrbFree(pProxyDev, pUrbOs2); + break; + } + + /* + * Block for something to completed, if requested and sensible. + */ + if (!cMillies) + break; + if ( !pDevOs2->pInFlightHead + && !pDevOs2->pTodoHead) + break; + + RTCritSectLeave(&pDevOs2->CritSect); + + int rc = RTSemEventWait(pDevOs2->EventSyncWait, cMillies); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); NOREF(rc); + cMillies = 0; + + RTCritSectEnter(&pDevOs2->CritSect); + } + RTCritSectLeave(&pDevOs2->CritSect); + + LogFlow(("usbProxyOs2UrbReap: dev=%s returns %p\n", pProxyDev->pUsbIns->pszName, pUrb)); + return pUrb; +} + + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static void usbProxyOs2UrbCancel(PVUSBURB pUrb) +{ +#if 0 + PUSBPROXYDEV pProxyDev = (PUSBPROXYDEV)pUrb->pDev; + PUSBPROXYURBOS2 pUrbOs2 = (PUSBPROXYURBOS2)pUrb->Dev.pvProxyUrb; + if (pUrbOs2->pSplitHead) + { + /* split */ + Assert(pUrbOs2 == pUrbOs2->pSplitHead); + for (PUSBPROXYURBOS2 pCur = pUrbOs2; pCur; pCur = pCur->pSplitNext) + { + if (pCur->fSplitElementReaped) + continue; + if ( !usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX) + || errno == ENOENT) + continue; + if (errno == ENODEV) + break; + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n", + pUrb, errno, pProxyDev->pUsbIns->pszName)); + } + } + else + { + /* unsplit */ + if ( usbProxyOs2DoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbOs2->KUrb, true, UINT32_MAX) + && errno != ENODEV /* deal with elsewhere. */ + && errno != ENOENT) + Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n", + pUrb, errno, pProxyDev->pUsbIns->pszName)); + } +#endif +} + + +/** + * The Linux USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + "host", + usbProxyOs2Open, + NULL, + usbProxyOs2Close, + usbProxyOs2Reset, + usbProxyOs2SetConfig, + usbProxyOs2ClaimInterface, + usbProxyOs2ReleaseInterface, + usbProxyOs2SetInterface, + usbProxyOs2ClearHaltedEp, + usbProxyOs2UrbQueue, + usbProxyOs2UrbCancel, + usbProxyOs2UrbReap, + 0 +}; + diff --git a/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp new file mode 100644 index 00000000..b727475a --- /dev/null +++ b/src/VBox/Devices/USB/solaris/USBProxyDevice-solaris.cpp @@ -0,0 +1,909 @@ +/* $Id: USBProxyDevice-solaris.cpp $ */ +/** @file + * USB device proxy - the Solaris backend. + */ + +/* + * Copyright (C) 2009-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <sys/poll.h> +#include <errno.h> +#include <strings.h> +#include <limits.h> + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/string.h> +#include <iprt/critsect.h> +#include <iprt/time.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/pipe.h> +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Log Prefix. */ +#define USBPROXY "USBProxy" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * Wrapper around the solaris urb request structure. + * This is required to track in-flight and landed URBs. + */ +typedef struct USBPROXYURBSOL +{ + /** Pointer to the Solaris device. */ + struct USBPROXYDEVSOL *pDevSol; + /** Pointer to the VUSB URB (set to NULL if canceled). */ + PVUSBURB pVUsbUrb; + /** Pointer to the next solaris URB. */ + struct USBPROXYURBSOL *pNext; + /** Pointer to the previous solaris URB. */ + struct USBPROXYURBSOL *pPrev; +} USBPROXYURBSOL, *PUSBPROXYURBSOL; + +/** + * Data for the solaris usb proxy backend. + */ +typedef struct USBPROXYDEVSOL +{ + /** Path of the USB device in the devices tree (persistent). */ + char *pszDevicePath; + /** The connection to the client driver. */ + RTFILE hFile; + /** Pointer to the proxy device instance. */ + PUSBPROXYDEV pProxyDev; + /** Critical section protecting the two lists. */ + RTCRITSECT CritSect; + /** The list of free solaris URBs. Singly linked. */ + PUSBPROXYURBSOL pFreeHead; + /** The list of active solaris URBs. Doubly linked. + * We must maintain this so we can properly reap URBs of a detached device. + * Only the split head will appear in this list. */ + PUSBPROXYURBSOL pInFlightHead; + /** The list of landed solaris URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBSOL pTaxingHead; + /** The tail of the landed solaris URBs. */ + PUSBPROXYURBSOL pTaxingTail; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; +} USBPROXYDEVSOL, *PUSBPROXYDEVSOL; + +static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol); + + +/** + * Allocates a Solaris URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * + * @param pDevSol The solaris USB device. + */ +static PUSBPROXYURBSOL usbProxySolarisUrbAlloc(PUSBPROXYDEVSOL pDevSol) +{ + PUSBPROXYURBSOL pUrbSol; + + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Try remove a Solaris URB from the free list, if none there allocate a new one. + */ + pUrbSol = pDevSol->pFreeHead; + if (pUrbSol) + pDevSol->pFreeHead = pUrbSol->pNext; + else + { + RTCritSectLeave(&pDevSol->CritSect); + pUrbSol = (PUSBPROXYURBSOL)RTMemAlloc(sizeof(*pUrbSol)); + if (!pUrbSol) + return NULL; + RTCritSectEnter(&pDevSol->CritSect); + } + pUrbSol->pVUsbUrb = NULL; + pUrbSol->pDevSol = pDevSol; + + /* + * Link it into the active list + */ + pUrbSol->pPrev = NULL; + pUrbSol->pNext = pDevSol->pInFlightHead; + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol; + pDevSol->pInFlightHead = pUrbSol; + + RTCritSectLeave(&pDevSol->CritSect); + return pUrbSol; +} + + +/** + * Frees a Solaris URB request structure. + * + * @param pDevSol The Solaris USB device. + * @param pUrbSol The Solaris URB to free. + */ +static void usbProxySolarisUrbFree(PUSBPROXYDEVSOL pDevSol, PUSBPROXYURBSOL pUrbSol) +{ + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Remove from the active or taxing list. + */ + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol->pPrev; + else if (pDevSol->pTaxingTail == pUrbSol) + pDevSol->pTaxingTail = pUrbSol->pPrev; + + if (pUrbSol->pPrev) + pUrbSol->pPrev->pNext = pUrbSol->pNext; + else if (pDevSol->pTaxingHead == pUrbSol) + pDevSol->pTaxingHead = pUrbSol->pNext; + else if (pDevSol->pInFlightHead == pUrbSol) + pDevSol->pInFlightHead = pUrbSol->pNext; + else + AssertFailed(); + + /* + * Link it into the free list. + */ + pUrbSol->pPrev = NULL; + pUrbSol->pNext = pDevSol->pFreeHead; + pDevSol->pFreeHead = pUrbSol; + + pUrbSol->pVUsbUrb = NULL; + pUrbSol->pDevSol = NULL; + + RTCritSectLeave(&pDevSol->CritSect); +} + + +/* + * Close the connection to the USB client driver. + * + * This is required because our userland enumeration relies on drivers/device trees + * to recognize active devices, and hence if this device is unplugged we should no + * longer keep the client driver loaded. + */ +static void usbProxySolarisCloseFile(PUSBPROXYDEVSOL pDevSol) +{ + RTFileClose(pDevSol->hFile); + pDevSol->hFile = NIL_RTFILE; +} + + +/** + * The client driver IOCtl Wrapper function. + * + * @returns VBox status code. + * @param pDevSol The Solaris device instance. + * @param Function The Function. + * @param pvData Opaque pointer to the data. + * @param cbData Size of the data pointed to by pvData. + */ +static int usbProxySolarisIOCtl(PUSBPROXYDEVSOL pDevSol, unsigned Function, void *pvData, size_t cbData) +{ + if (RT_UNLIKELY(pDevSol->hFile == NIL_RTFILE)) + { + LogFlow((USBPROXY ":usbProxySolarisIOCtl: Connection to driver gone!\n")); + return VERR_VUSB_DEVICE_NOT_ATTACHED; + } + + VBOXUSBREQ Req; + Req.u32Magic = VBOXUSB_MAGIC; + Req.rc = -1; + Req.cbData = cbData; + Req.pvDataR3 = pvData; + + int Ret = -1; + int rc = RTFileIoCtl(pDevSol->hFile, Function, &Req, sizeof(Req), &Ret); + if (RT_SUCCESS(rc)) + { + if (RT_FAILURE(Req.rc)) + { + if (Req.rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + pDevSol->pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + LogRel((USBPROXY ": Command %#x failed, USB Device '%s' disconnected!\n", Function, + pDevSol->pProxyDev->pUsbIns->pszName)); + } + else + LogRel((USBPROXY ": Command %#x failed. Req.rc=%Rrc\n", Function, Req.rc)); + } + + return Req.rc; + } + + LogRel((USBPROXY ": Function %#x failed. rc=%Rrc\n", Function, rc)); + return rc; +} + + +/** + * Get the active configuration from the device. The first time this is called + * our client driver would returned the cached configuration since the device is first plugged in. + * Subsequent get configuration requests are passed on to the device. + * + * @returns VBox status code. + * @param pDevSol The Solaris device instance. + * + */ +static inline int usbProxySolarisGetActiveConfig(PUSBPROXYDEVSOL pDevSol) +{ + VBOXUSBREQ_GET_CONFIG GetConfigReq; + bzero(&GetConfigReq, sizeof(GetConfigReq)); + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_CONFIG, &GetConfigReq, sizeof(GetConfigReq)); + if (RT_SUCCESS(rc)) + { + pDevSol->pProxyDev->iActiveCfg = GetConfigReq.bConfigValue; + pDevSol->pProxyDev->cIgnoreSetConfigs = 0; + } + else + { + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": Failed to get configuration. rc=%Rrc\n", rc)); + + pDevSol->pProxyDev->iActiveCfg = -1; + pDevSol->pProxyDev->cIgnoreSetConfigs = 0; + } + return rc; +} + + +/** + * Opens the USB device. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The unique device identifier. + * The format of this string is "VendorId:ProducIt:Release:StaticPath". + * @param pvBackend Backend specific pointer, unused for the solaris backend. + */ +static DECLCALLBACK(int) usbProxySolarisOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + LogFlowFunc((USBPROXY ":usbProxySolarisOpen: pProxyDev=%p pszAddress=%s pvBackend=%p\n", pProxyDev, pszAddress, pvBackend)); + + /* + * Initialize our USB R3 lib. + */ + int rc = USBLibInit(); + if (RT_SUCCESS(rc)) + { + /* + * Allocate and initialize the solaris backend data. + */ + AssertCompile(PATH_MAX >= MAXPATHLEN); + char szDeviceIdent[PATH_MAX+48]; + rc = RTStrPrintf(szDeviceIdent, sizeof(szDeviceIdent), "%s", pszAddress); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&pDevSol->CritSect); + if (RT_SUCCESS(rc)) + { + /* + * Create wakeup pipe. + */ + rc = RTPipeCreate(&pDevSol->hPipeWakeupR, &pDevSol->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + int Instance; + char *pszDevicePath = NULL; + rc = USBLibGetClientInfo(szDeviceIdent, &pszDevicePath, &Instance); + if (RT_SUCCESS(rc)) + { + pDevSol->pszDevicePath = pszDevicePath; + + /* + * Open the client driver. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pDevSol->pszDevicePath, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + pDevSol->hFile = hFile; + pDevSol->pProxyDev = pProxyDev; + + /* + * Verify client driver version. + */ + VBOXUSBREQ_GET_VERSION GetVersionReq; + bzero(&GetVersionReq, sizeof(GetVersionReq)); + rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_GET_VERSION, &GetVersionReq, sizeof(GetVersionReq)); + if (RT_SUCCESS(rc)) + { + if ( GetVersionReq.u32Major == VBOXUSB_VERSION_MAJOR + && GetVersionReq.u32Minor >= VBOXUSB_VERSION_MINOR) + { + /* + * Try & get the current cached config from Solaris. + */ + usbProxySolarisGetActiveConfig(pDevSol); + return VINF_SUCCESS; + } + else + { + LogRel((USBPROXY ": Version mismatch, Driver v%d.%d expecting ~v%d.%d\n", GetVersionReq.u32Major, + GetVersionReq.u32Minor, VBOXUSB_VERSION_MAJOR, VBOXUSB_VERSION_MINOR)); + rc = VERR_VERSION_MISMATCH; + } + } + else + LogRel((USBPROXY ": Failed to query driver version. rc=%Rrc\n", rc)); + + RTFileClose(pDevSol->hFile); + pDevSol->hFile = NIL_RTFILE; + pDevSol->pProxyDev = NULL; + } + else + LogRel((USBPROXY ": Failed to open device. rc=%Rrc pszDevicePath=%s\n", rc, pDevSol->pszDevicePath)); + + RTStrFree(pDevSol->pszDevicePath); + pDevSol->pszDevicePath = NULL; + } + else + { + LogRel((USBPROXY ": Failed to get client info. rc=%Rrc szDeviceIdent=%s\n", rc, szDeviceIdent)); + if (rc == VERR_NOT_FOUND) + rc = VERR_OPEN_FAILED; + } + RTPipeClose(pDevSol->hPipeWakeupR); + RTPipeClose(pDevSol->hPipeWakeupW); + } + + RTCritSectDelete(&pDevSol->CritSect); + } + else + LogRel((USBPROXY ": RTCritSectInit failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress)); + } + else + LogRel((USBPROXY ": RTStrAPrintf failed. rc=%Rrc pszAddress=%s\n", rc, pszAddress)); + } + else + LogRel((USBPROXY ": USBLibInit failed. rc=%Rrc\n", rc)); + + USBLibTerm(); + return rc; +} + + +/** + * Close the USB device. + * + * @param pProxyDev The device instance. + */ +static DECLCALLBACK(void) usbProxySolarisClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow((USBPROXY ":usbProxySolarisClose: pProxyDev=%p\n", pProxyDev)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* Close the device (do not re-enumerate). */ + VBOXUSBREQ_CLOSE_DEVICE CloseReq; + CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_CLOSE; + usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq)); + + pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + + /* + * Now we can close it and free all the resources. + */ + RTCritSectDelete(&pDevSol->CritSect); + + PUSBPROXYURBSOL pUrbSol = NULL; + while ((pUrbSol = pDevSol->pInFlightHead) != NULL) + { + pDevSol->pInFlightHead = pUrbSol->pNext; + RTMemFree(pUrbSol); + } + + while ((pUrbSol = pDevSol->pFreeHead) != NULL) + { + pDevSol->pFreeHead = pUrbSol->pNext; + RTMemFree(pUrbSol); + } + + RTPipeClose(pDevSol->hPipeWakeupR); + RTPipeClose(pDevSol->hPipeWakeupW); + + RTStrFree(pDevSol->pszDevicePath); + pDevSol->pszDevicePath = NULL; + + USBLibTerm(); +} + + +/** + * Reset the device. + * + * @returns VBox status code. + * @param pProxyDev The device to reset. + * @param fRootHubReset Is this a root hub reset or device specific reset request. + */ +static DECLCALLBACK(int) usbProxySolarisReset(PUSBPROXYDEV pProxyDev, bool fRootHubReset) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisReset: pProxyDev=%s fRootHubReset=%d\n", pProxyDev->pUsbIns->pszName, fRootHubReset)); + + /** Pass all resets to the device. The Trekstor USB (1.1) stick requires this to work. */ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* Soft reset the device. */ + VBOXUSBREQ_CLOSE_DEVICE CloseReq; + CloseReq.ResetLevel = VBOXUSB_RESET_LEVEL_SOFT; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLOSE_DEVICE, &CloseReq, sizeof(CloseReq)); + if (RT_SUCCESS(rc)) + { + /* Get the active config. Solaris USBA sets a default config. */ + usbProxySolarisGetActiveConfig(pDevSol); + } + else if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisReset: Failed! rc=%d\n", rc)); + + return rc; +} + + +/** + * Set the active configuration. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration value to set. + */ +static DECLCALLBACK(int) usbProxySolarisSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisSetConfig: pProxyDev=%p iCfg=%#x\n", pProxyDev, iCfg)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_SET_CONFIG SetConfigReq; + SetConfigReq.bConfigValue = iCfg; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_CONFIG, &SetConfigReq, sizeof(SetConfigReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisSetConfig: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Claims an interface. + * + * This is a stub on Solaris since we release/claim all interfaces at + * as and when required with endpoint opens. + * + * @returns success indicator (always true). + */ +static DECLCALLBACK(int) usbProxySolarisClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * + * This is a stub on Solaris since we release/claim all interfaces at + * as and when required with endpoint opens. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxySolarisReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + return VINF_SUCCESS; +} + + +/** + * Specify an alternate setting for the specified interface of the current configuration. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxySolarisSetInterface(PUSBPROXYDEV pProxyDev, int bIf, int bAlt) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisSetInterface: pProxyDev=%p bIf=%#x iAlt=%#x\n", pProxyDev, bIf, bAlt)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_SET_INTERFACE SetInterfaceReq; + SetInterfaceReq.bInterface = bIf; + SetInterfaceReq.bAlternate = bAlt; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SET_INTERFACE, &SetInterfaceReq, sizeof(SetInterfaceReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisSetInterface: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxySolarisClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisClearHaltedEp: pProxyDev=%p EndPt=%#x\n", pProxyDev, EndPt)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + VBOXUSBREQ_CLEAR_EP ClearEpReq; + ClearEpReq.bEndpoint = EndPt; + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_CLEAR_EP, &ClearEpReq, sizeof(ClearEpReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisClearHaltedEp: Failed! rc=%Rrc\n", rc)); + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxySolarisUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + LogFlowFunc((USBPROXY ": usbProxySolarisUrbQueue: pProxyDev=%s pUrb=%p pszDesc=%s EndPt=%#x enmDir=%d cbData=%d pvData=%p\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->pszDesc, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, pUrb->abData)); + + PUSBPROXYURBSOL pUrbSol = usbProxySolarisUrbAlloc(pDevSol); + if (RT_UNLIKELY(!pUrbSol)) + { + LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed to allocate URB\n")); + return VERR_NO_MEMORY; + } + + pUrbSol->pVUsbUrb = pUrb; + pUrbSol->pDevSol = pDevSol; + + uint8_t EndPt = pUrb->EndPt; + if (EndPt) + EndPt |= pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE; + + VBOXUSBREQ_URB UrbReq; + UrbReq.pvUrbR3 = pUrbSol; + UrbReq.bEndpoint = EndPt; + UrbReq.enmType = pUrb->enmType; + UrbReq.enmDir = pUrb->enmDir; + UrbReq.enmStatus = pUrb->enmStatus; + UrbReq.fShortOk = !pUrb->fShortNotOk; + UrbReq.cbData = pUrb->cbData; + UrbReq.pvData = &pUrb->abData[0]; + + Log6((USBPROXY ": Sending: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + UrbReq.cIsocPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + UrbReq.aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cb; + UrbReq.aIsocPkts[i].cbActPkt = 0; + UrbReq.aIsocPkts[i].enmStatus = VUSBSTATUS_INVALID; + } + } + + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_SEND_URB, &UrbReq, sizeof(UrbReq)); + if (RT_SUCCESS(rc)) + { + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + LogFlow((USBPROXY ":usbProxySolarisUrbQueue: Success cbData=%d\n", pUrb->cbData)); + pUrb->Dev.pvPrivate = pUrbSol; + return VINF_SUCCESS; + } + + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + { + LogRel((USBPROXY ": usbProxySolarisUrbQueue: Failed! pProxyDev=%s pUrb=%p EndPt=%#x bEndpoint=%#x enmType=%d " + "enmDir=%d cbData=%u rc=%Rrc\n", pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, + UrbReq.bEndpoint, pUrb->enmType, pUrb->enmDir, pUrb->cbData, rc)); + } + + return rc; +} + + +/** + * Cancels a URB. + * + * The URB requires reaping, so we don't change its state. + * @remark There isn't any way to cancel a specific asynchronous request + * on Solaris. So we just abort pending URBs on the pipe. + */ +static DECLCALLBACK(int) usbProxySolarisUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)pUrb->Dev.pvPrivate; + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + AssertPtrReturn(pDevSol, VERR_INVALID_POINTER); + + LogFlowFunc((USBPROXY ": usbProxySolarisUrbCancel: pUrb=%p pUrbSol=%p pDevSol=%p\n", pUrb, pUrbSol, pUrbSol->pDevSol)); + + /* Aborting the control pipe isn't supported, pretend success. */ + if (!pUrb->EndPt) + return VINF_SUCCESS; + + VBOXUSBREQ_ABORT_PIPE AbortPipeReq; + AbortPipeReq.bEndpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? VUSB_DIR_TO_HOST : VUSB_DIR_TO_DEVICE); + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_ABORT_PIPE, &AbortPipeReq, sizeof(AbortPipeReq)); + if ( RT_FAILURE(rc) + && rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": usbProxySolarisUrbCancel: Failed to abort pipe. rc=%Rrc\n", rc)); + + LogFlow((USBPROXY ": usbProxySolarisUrbCancel: returns rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxySolarisUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlowFunc((USBPROXY ":usbProxySolarisUrbReap pProxyDev=%p cMillies=%u\n", pProxyDev, cMillies)); + + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + + /* + * Don't block if nothing is in the air. + */ + if (!pDevSol->pInFlightHead) + return NULL; + + /* + * Deque URBs inflight or those landed. + */ + if (cMillies > 0) + { + for (;;) + { + int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : (int)cMillies; + + struct pollfd aFd[2]; + size_t const cFds = RT_ELEMENTS(aFd); + + aFd[0].fd = RTFileToNative(pDevSol->hFile); + aFd[0].events = POLLIN; + aFd[0].revents = 0; + + aFd[1].fd = RTPipeToNative(pDevSol->hPipeWakeupR); + aFd[1].events = POLLIN; + aFd[1].revents = 0; + + int rc = poll(&aFd[0], cFds, cMilliesWait); + if (rc > 0) + { + if (aFd[0].revents & POLLHUP) + { + LogRel((USBPROXY ": USB Device '%s' disconnected!\n", pDevSol->pProxyDev->pUsbIns->pszName)); + pProxyDev->fDetached = true; + usbProxySolarisCloseFile(pDevSol); + } + + if (aFd[1].revents & POLLIN) + { + /* Got woken up, drain pipe. */ + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevSol->hPipeWakeupR, &bRead, 1, &cbIgnored); + + /* + * It is possible that we got woken up and have an URB pending + * for completion. Do it on the way out. Otherwise return + * immediately to the caller. + */ + if (!(aFd[0].revents & POLLIN)) + return NULL; + } + break; + } + else if (rc == 0) + return NULL; + else if (errno != EAGAIN) + { + LogFlow((USBPROXY ":usbProxySolarisUrbReap Poll rc=%d errno=%d\n", rc, errno)); + return NULL; + } + } + } + + usbProxySolarisUrbComplete(pDevSol); + + /* + * Any URBs pending delivery? + */ + PVUSBURB pUrb = NULL; + while ( pDevSol->pTaxingHead + && !pUrb) + { + RTCritSectEnter(&pDevSol->CritSect); + + PUSBPROXYURBSOL pUrbSol = pDevSol->pTaxingHead; + if (pUrbSol) + { + pUrb = pUrbSol->pVUsbUrb; + if (pUrb) + { + /* + * Remove it from the taxing list and move it to the free list. + */ + pUrb->Dev.pvPrivate = NULL; + usbProxySolarisUrbFree(pDevSol, pUrbSol); + } + } + RTCritSectLeave(&pDevSol->CritSect); + } + + return pUrb; +} + + +/** + * Reads a completed/error'd URB from the client driver (no waiting). + * + * @param pDevSol The Solaris device instance. + */ +static PVUSBURB usbProxySolarisUrbComplete(PUSBPROXYDEVSOL pDevSol) +{ + LogFlowFunc((USBPROXY ": usbProxySolarisUrbComplete: pDevSol=%p\n", pDevSol)); + + VBOXUSBREQ_URB UrbReq; + bzero(&UrbReq, sizeof(UrbReq)); + + int rc = usbProxySolarisIOCtl(pDevSol, VBOXUSB_IOCTL_REAP_URB, &UrbReq, sizeof(UrbReq)); + if (RT_SUCCESS(rc)) + { + if (UrbReq.pvUrbR3) + { + PUSBPROXYURBSOL pUrbSol = (PUSBPROXYURBSOL)UrbReq.pvUrbR3; + PVUSBURB pUrb = pUrbSol->pVUsbUrb; + if (RT_LIKELY(pUrb)) + { + Assert(pUrb->u32Magic == VUSBURB_MAGIC); + + /* + * Update the URB. + */ + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + size_t cbData = 0; + for (unsigned i = 0; i < UrbReq.cIsocPkts; i++) + { + pUrb->aIsocPkts[i].cb = UrbReq.aIsocPkts[i].cbActPkt; + cbData += UrbReq.aIsocPkts[i].cbActPkt; + pUrb->aIsocPkts[i].enmStatus = UrbReq.aIsocPkts[i].enmStatus; + } + + LogFlow((USBPROXY ":usbProxySolarisUrbComplete: Isoc cbData=%d cbActPktSum=%d\n", pUrb->cbData, cbData)); + pUrb->cbData = cbData; + pUrb->enmStatus = UrbReq.enmStatus; + } + else + { + pUrb->cbData = UrbReq.cbData; + pUrb->enmStatus = UrbReq.enmStatus; + } + + RTCritSectEnter(&pDevSol->CritSect); + + /* + * Remove from the active list. + */ + if (pUrbSol->pNext) + pUrbSol->pNext->pPrev = pUrbSol->pPrev; + if (pUrbSol->pPrev) + pUrbSol->pPrev->pNext = pUrbSol->pNext; + else + { + Assert(pDevSol->pInFlightHead == pUrbSol); + pDevSol->pInFlightHead = pUrbSol->pNext; + } + + /* + * Add to the tail of the taxing list. + */ + pUrbSol->pNext = NULL; + pUrbSol->pPrev = pDevSol->pTaxingTail; + if (pDevSol->pTaxingTail) + pDevSol->pTaxingTail->pNext = pUrbSol; + else + pDevSol->pTaxingHead = pUrbSol; + pDevSol->pTaxingTail = pUrbSol; + + RTCritSectLeave(&pDevSol->CritSect); + + Log6((USBPROXY ": Reaping: EndPt=%#x Dir=%d cbData=%u\n", pUrb->EndPt, pUrb->enmDir, pUrb->cbData)); + if (pUrb->cbData < 1024) + Log6(("%.*Rhxd\n", pUrb->cbData, pUrb->abData)); + return pUrb; + } + } + } + else + { + if (rc != VERR_VUSB_DEVICE_NOT_ATTACHED) + LogRel((USBPROXY ": Reaping URB failed. rc=%Rrc\n", rc)); + } + + return NULL; +} + + +static DECLCALLBACK(int) usbProxySolarisWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVSOL pDevSol = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVSOL); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevSol->hPipeWakeupW, "", 1, &cbIgnored); +} + + +/** + * The Solaris USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVSOL), + usbProxySolarisOpen, + NULL, + usbProxySolarisClose, + usbProxySolarisReset, + usbProxySolarisSetConfig, + usbProxySolarisClaimInterface, + usbProxySolarisReleaseInterface, + usbProxySolarisSetInterface, + usbProxySolarisClearHaltedEp, + usbProxySolarisUrbQueue, + usbProxySolarisUrbCancel, + usbProxySolarisUrbReap, + usbProxySolarisWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/testcase/Makefile.kup b/src/VBox/Devices/USB/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/testcase/Makefile.kup diff --git a/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp new file mode 100644 index 00000000..149eb8ba --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstOhciRegisterAccess.cpp @@ -0,0 +1,594 @@ +/* $Id: tstOhciRegisterAccess.cpp $ */ +/** @file + * tstOhciRegisterAccess - OHCI Register Access Tests / Experiments. + */ + +/* + * Copyright (C) 2011-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <VBox/log.h> +#include <iprt/mem.h> +#include <iprt/memobj.h> +#include <iprt/string.h> +#include <iprt/asm-amd64-x86.h> +#include <iprt/param.h> + +#include <VBox/sup.h> +#undef LogRel +#define LogRel(a) SUPR0Printf a + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Register names. */ +static const char * const g_apszRegNms[] = +{ + /* 00 */ "HcRevision", + /* 01 */ "HcControl", + /* 02 */ "HcCommandStatus", + /* 03 */ "HcInterruptStatus", + /* 04 */ "HcInterruptEnable", + /* 05 */ "HcInterruptDisable", + /* 06 */ "HcHCCA", + /* 07 */ "HcPeriodCurrentED", + /* 08 */ "HcControlHeadED", + /* 09 */ "HcControlCurrentED", + /* 10 */ "HcBulkHeadED", + /* 11 */ "HcBulkCurrentED", + /* 12 */ "HcDoneHead", + /* 13 */ "HcFmInterval", + /* 14 */ "HcFmRemaining", + /* 15 */ "HcFmNumber", + /* 16 */ "HcPeriodicStart", + /* 17 */ "HcLSThreshold", + /* 18 */ "HcRhDescriptorA", + /* 19 */ "HcRhDescriptorB", + /* 20 */ "HcRhStatus", + /* Variable number of root hub ports: */ + /* 21 */ "HcRhPortStatus[0]", + /* 22 */ "HcRhPortStatus[1]", + /* 23 */ "HcRhPortStatus[2]", + /* 24 */ "HcRhPortStatus[3]", + /* 25 */ "HcRhPortStatus[4]", + /* 26 */ "HcRhPortStatus[5]", + /* 27 */ "HcRhPortStatus[6]", + /* 28 */ "HcRhPortStatus[7]" +}; + + +static bool TestOhciWrites(RTVPTRUNION uPtr) +{ + static struct + { + unsigned iReg; + uint32_t fMask; + uint32_t uVal1; + uint32_t uVal2; + } const s_aRegs[] = + { +#if 0 /* deadly when missing bytes are taken as zero. */ + { 13 /* HcFmInterval */, 0xffffffff, 0x58871120, 0x01010101 }, +#endif + { 16 /* HcPeriodicStart */, 0x00003fff, 0x01020304, 0x02010403 }, + { 17 /* HcLSThreshold */, 0x00000fff, 0xffffffff, 0x66666666 }, + { 10 /* HcBulkHeadED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */ + { 11 /* HcBulkCurrentED */, 0xfffffff0, 0xffffffff, 0xfefefef8 }, /* a bit risky... */ + }; + + bool fSuccess = true; + for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++) + { + uint32_t const iReg = s_aRegs[i].iReg; + RTVPTRUNION uPtrReg; + uPtrReg.pu32 = &uPtr.pu32[iReg]; + + uint32_t uInitialValue = *uPtrReg.pu32; + LogRel(("TestOhciWrites: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + bool fTryAgain = true; + const char *pszError = NULL; + uint32_t u32A = 0; + uint32_t uChangedValue = 0; + uint32_t uExpectedValue = 0; + + for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++) + { + pszError = NULL; + fTryAgain = false; + u32A = 0; + uChangedValue = 0; + uExpectedValue = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtrReg.pu32; + + /* + * DWORD writes. + */ + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + *uPtrReg.pu32 = uInitialValue; + u32A = *uPtrReg.pu32; + uChangedValue = s_aRegs[i].uVal1 != uInitialValue ? s_aRegs[i].uVal1 : s_aRegs[i].uVal2; + if (u32A == uInitialValue) + { + /* Change the value. */ + *uPtrReg.pu32 = uChangedValue; + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = uChangedValue & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + pszError = "Writing changed value failed"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 1"; + } + } + else + pszError = "Writing back initial value failed"; + + /* + * Write aligned word changes. + */ + for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + /* Change the value. */ + uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0xffff) << iWord * 16) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + { + static const char * const s_apsz[] = { "word 0", "word 1" }; + pszError = s_apsz[iWord]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 2"; + } + } + + /* + * Write aligned word change. We have to keep within the register, + * unfortunately. + */ + if (!pszError && !fTryAgain) + { + fTryAgain = *uPtrReg.pu32 != uInitialValue; + if (!fTryAgain) + { + /* Change the value. */ + *(uint16_t volatile *)&uPtrReg.pu8[1] = (uint16_t)(uChangedValue >> 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0x00ffff00)) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + pszError = "Unaligned word access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 3"; + } + } + } + + /* + * Write byte changes. + */ + for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + /* Change the value. */ + uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + uExpectedValue = (uChangedValue & UINT32_C(0xff) << iByte * 8) & s_aRegs[i].fMask; + if (u32A != uExpectedValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 4"; + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } + + /* + * Complain on failure. + */ + if (fTryAgain) + LogRel(("TestOhciWrites: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue)); + else if (pszError) + { + LogRel(("TestOhciWrites: Error! Register %s failed: %s; Initial=%08RX32 Changed=%08RX32 Expected=%08RX32 u32A=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, uExpectedValue, u32A)); + fSuccess = false; + } + } + + return fSuccess; +} + + +static bool TestOhciReadOnly(RTVPTRUNION uPtr) +{ + static struct + { + unsigned iReg; + uint32_t cValues; + uint32_t auValues[8]; + } const s_aRegs[] = + { + { 0 /* HcRevision */, 8, { 0, UINT32_MAX, 0x10100110, 0x200, 0x111, 0x11f, 0xf110, 0x0f10 } }, + { 12 /* HcDoneHead */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } }, + { 14 /* HcFmRemaining */, 3, { 0, UINT32_MAX, 0x55555555, 0, 0, 0, 0, 0 } }, + { 15 /* HcFmNumber */, 5, { 0, UINT32_MAX, 0x55555555, 0x7899, 0x00012222, 0, 0, 0 } }, +#if 0 /* HCD can write this */ + { 17 /* HcLSThreshold */, 5, { 0x627, 0x628, 0x629, 0x666, 0x599, 0, 0, 0 } } /* ??? */ +#endif + }; + + bool fSuccess = true; + for (unsigned i = 0; i < RT_ELEMENTS(s_aRegs); i++) + { + uint32_t const iReg = s_aRegs[i].iReg; + RTVPTRUNION uPtrReg; + uPtrReg.pu32 = &uPtr.pu32[iReg]; + + uint32_t uInitialValue = *uPtrReg.pu32; + LogRel(("TestOhciReadOnly: %p iReg=%2d %20s = %08RX32\n", uPtrReg.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + bool fTryAgain = true; + const char *pszError = NULL; + uint32_t uChangedValue = 0; + uint32_t u32A = 0; + + for (uint32_t iTry = 0; fTryAgain && iTry < 1024; iTry++) + { + pszError = NULL; + fTryAgain = false; + u32A = 0; + uChangedValue = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtrReg.pu32; + + /* + * Try aligned dword, word and byte writes for now. + */ + for (unsigned iValue = 0; iValue < s_aRegs[i].cValues && !pszError && !fTryAgain; iValue++) + { + uChangedValue = s_aRegs[i].auValues[iValue]; + if (uInitialValue == uChangedValue) + continue; + + /* dword */ + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + + *uPtrReg.pu32 = uChangedValue; + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + pszError = "dword access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 1"; + } + + /* word */ + for (unsigned iWord = 0; iWord < 2 && !pszError && !fTryAgain; iWord++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + uPtrReg.pu16[iWord] = (uint16_t)(uChangedValue >> iWord * 16); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access"; + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 2"; + } + } + + /* byte */ + for (unsigned iByte = 0; iByte < 4 && !pszError && !fTryAgain; iByte++) + { + if ((fTryAgain = (*uPtrReg.pu32 != uInitialValue))) + break; + uPtrReg.pu8[iByte] = (uint8_t)(uChangedValue >> iByte * 8); + u32A = *uPtrReg.pu32; + *uPtrReg.pu32 = uInitialValue; + if (u32A != uInitialValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + } + else + { + u32A = *uPtrReg.pu32; + if (u32A != uInitialValue) + pszError = "Restore error 3"; + } + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } + + /* + * Complain on failure. + */ + if (fTryAgain) + LogRel(("TestOhciReadOnly: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, uChangedValue, uInitialValue)); + else if (pszError) + { + LogRel(("TestOhciReadOnly: Error! Register %s failed: %s; uInitialValue=%08RX32 uChangedValue=%08RX32 u32A=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, uChangedValue, u32A)); + fSuccess = false; + } + } + + return fSuccess; +} + + +static bool TestOhciReads(RTVPTRUNION uPtr) +{ + /* + * We can read just about any register we like since read shouldn't have + * any side effects. However, some registers are volatile and makes for + * difficult targets, thus the ugly code. + */ + bool fSuccess = true; + uint32_t cMaxReg = RT_ELEMENTS(g_apszRegNms); + for (uint32_t iReg = 0; iReg < cMaxReg; iReg++, uPtr.pu32++) + { + const char *pszError = NULL; + bool fDone = false; + uint32_t uInitialValue = *uPtr.pu32; + uint32_t u32A = 0; + uint32_t u32B = 0; + uint32_t u32C = 0; + LogRel(("TestOhciReads: %p iReg=%2d %20s = %08RX32\n", uPtr.pv, iReg, g_apszRegNms[iReg], uInitialValue)); + + for (uint32_t iTry = 0; !fDone && iTry < 1024; iTry++) + { + pszError = NULL; + fDone = true; + u32A = u32B = u32C = 0; + + RTCCUINTREG const fFlags = ASMIntDisableFlags(); + uInitialValue = *uPtr.pu32; + + /* Test byte access. */ + for (unsigned iByte = 0; iByte < 4; iByte++) + { + u32A = *uPtr.pu32; + u32B = uPtr.pu8[iByte]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + static uint32_t const a_au32Masks[] = + { + UINT32_C(0xffffff00), UINT32_C(0xffff00ff), UINT32_C(0xff00ffff), UINT32_C(0x00ffffff) + }; + u32B <<= iByte * 8; + u32B |= uInitialValue & a_au32Masks[iByte]; + if (u32B != uInitialValue) + { + static const char * const s_apsz[] = { "byte 0", "byte 1", "byte 2", "byte 3" }; + pszError = s_apsz[iByte]; + break; + } + } + + /* Test aligned word access. */ + if (fDone) + { + for (unsigned iWord = 0; iWord < 2; iWord++) + { + u32A = *uPtr.pu32; + u32B = uPtr.pu16[iWord]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + u32B <<= iWord * 16; + u32B |= uInitialValue & (iWord == 0 ? UINT32_C(0xffff0000) : UINT32_C(0x0000ffff)); + if (u32B != uInitialValue) + { + pszError = iWord == 0 ? "aligned word 0 access" : "aligned word 1 access"; + break; + } + } + } + + /* Test unaligned word access. */ + if (fDone) + { + for (int iWord = ((uintptr_t)uPtr.pv & PAGE_OFFSET_MASK) == 0; iWord < 3; iWord++) + { + u32A = *uPtr.pu32; + u32B = *(volatile uint16_t *)&uPtr.pu8[iWord * 2 - 1]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + switch (iWord) + { + case 0: u32B = (u32B >> 8) | (u32A & UINT32_C(0xffffff00)); break; + case 1: u32B = (u32B << 8) | (u32A & UINT32_C(0xff0000ff)); break; + case 2: u32B = (u32B << 24) | (u32A & UINT32_C(0x00ffffff)); break; + } + if (u32B != u32A) + { + static const char * const s_apsz[] = { "unaligned word 0", "unaligned word 1", "unaligned word 2" }; + pszError = s_apsz[iWord]; + break; + } + } + } + + /* Test unaligned dword access. */ + if (fDone) + { + for (int iByte = ((uintptr_t)uPtr.pv & PAGE_OFFSET_MASK) == 0 ? 0 : -3; iByte < 4; iByte++) + { + u32A = *uPtr.pu32; + u32B = *(volatile uint32_t *)&uPtr.pu8[iByte]; + u32C = *uPtr.pu32; + if (u32A != uInitialValue || u32C != uInitialValue) + { + fDone = false; + break; + } + + switch (iByte) + { + case -3: u32B = (u32B >> 24) | (uInitialValue & UINT32_C(0xffffff00)); break; + case -2: u32B = (u32B >> 16) | (uInitialValue & UINT32_C(0xffff0000)); break; + case -1: u32B = (u32B >> 8) | (uInitialValue & UINT32_C(0xff000000)); break; + case 0: break; + case 1: u32B = (u32B << 8) | (uInitialValue & UINT32_C(0x000000ff)); break; + case 2: u32B = (u32B << 16) | (uInitialValue & UINT32_C(0x0000ffff)); break; + case 3: u32B = (u32B << 24) | (uInitialValue & UINT32_C(0x00ffffff)); break; + + } + if (u32B != u32A) + { + static const char * const s_apsz[] = + { + "unaligned dword -3", "unaligned dword -2", "unaligned dword -1", + "unaligned dword 0", "unaligned dword 1", "unaligned dword 2", "unaligned dword 3" + }; + pszError = s_apsz[iByte + 3]; + break; + } + + } + } + + ASMSetFlags(fFlags); + ASMNopPause(); + } /* try loop */ + + /* + * Complain on failure. + */ + if (!fDone) + LogRel(("TestOhciReads: Warning! Register %s was never stable enough for testing! %08RX32 %08RX32 %08RX32\n", + g_apszRegNms[iReg], uInitialValue, u32A, u32C)); + else if (pszError) + { + LogRel(("TestOhciReads: Error! Register %s failed: %s; uInitialValue=%08RX32 u32B=%08RX32\n", + g_apszRegNms[iReg], pszError, uInitialValue, u32B)); + fSuccess = false; + } + } + + return fSuccess; +} + + +int tstOhciRegisterAccess(RTHCPHYS HCPhysOHCI) +{ + LogRel(("tstOhciRegisterAccess: HCPhysOHCI=%RHp\n", HCPhysOHCI)); + + /* + * Map the OHCI registers so we can access them. + */ + RTR0MEMOBJ hMemObj; + int rc = RTR0MemObjEnterPhys(&hMemObj, HCPhysOHCI, PAGE_SIZE, RTMEM_CACHE_POLICY_MMIO); + if (RT_FAILURE(rc)) + { + LogRel(("tstOhciRegisterAccess: Failed to enter OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc)); + return rc; + } + RTR0MEMOBJ hMapObj; + rc = RTR0MemObjMapKernel(&hMapObj, hMemObj, (void *)-1, 0 /*uAlignment*/, RTMEM_PROT_READ | RTMEM_PROT_WRITE); + if (RT_SUCCESS(rc)) + { + RTVPTRUNION uPtr; + uPtr.pv = (void volatile *)RTR0MemObjAddress(hMapObj); + LogRel(("tstOhciRegisterAccess: mapping address %p\n", uPtr.pv)); + if (RT_VALID_PTR(uPtr.pv)) + { + LogRel(("tstOhciRegisterAccess: HcRevision=%#x\n", *uPtr.pu32)); + + /* + * Do the access tests. + */ + bool fSuccess = TestOhciReads(uPtr); + if (fSuccess) + fSuccess = TestOhciReadOnly(uPtr); + if (fSuccess) + fSuccess = TestOhciWrites(uPtr); + if (fSuccess) + LogRel(("tstOhciRegisterAccess: Success!\n")); + else + LogRel(("tstOhciRegisterAccess: Failed!\n")); + } + else + rc = VERR_INTERNAL_ERROR_2; + + /* + * Clean up. + */ + RTR0MemObjFree(hMapObj, false); + } + else + LogRel(("tstOhciRegisterAccess: Failed to map OHCI memory at %RHp: %Rrc\n", HCPhysOHCI, rc)); + RTR0MemObjFree(hMemObj, false); + LogRel(("tstOhciRegisterAccess: returns %Rrc\n", rc)); + return rc; +} diff --git a/src/VBox/Devices/USB/testcase/tstPalmOne.c b/src/VBox/Devices/USB/testcase/tstPalmOne.c new file mode 100644 index 00000000..7fc73d83 --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstPalmOne.c @@ -0,0 +1,402 @@ +/* $Id: tstPalmOne.c $ */ +/** @file + * USB PalmOne testcase + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/usbdevice_fs.h> + + +/** @name USB Control message recipient codes (from spec) + * @{ */ +#define VUSB_TO_DEVICE 0x0 +#define VUSB_TO_INTERFACE 0x1 +#define VUSB_TO_ENDPOINT 0x2 +#define VUSB_TO_OTHER 0x3 +#define VUSB_RECIP_MASK 0x1f +/** @} */ + +/** @name USB control pipe setup packet structure (from spec) + * @{ */ +#define VUSB_REQ_SHIFT (5) +#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT) +#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT) +#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT) +#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT) +#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT) +/** @} */ + +#define VUSB_DIR_TO_HOST 0x80 +typedef struct vusb_setup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VUSBSETUP, *PVUSBSETUP; +typedef const VUSBSETUP *PCVUSBSETUP; + + +int g_fd; + +int bitch(const char *pszMsg) +{ + printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno)); + return 1; +} + +void hex(const void *pv, ssize_t cb, const char *pszWhat) +{ + printf("%s: cb=%d\n", pszWhat, cb); + unsigned char *pb = (unsigned char *)pv; + int cch = 0; + int off = 0; + int cchPrecision = 16; + while (off < cb) + { + int i; + printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off); + + for (i = 0; i < cchPrecision && off + i < cb; i++) + printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]); + while (i++ < cchPrecision) + printf(" "); + printf(" "); + for (i = 0; i < cchPrecision && off + i < cb; i++) + { + uint8_t u8 = pb[i]; + fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout); + } + + /* next */ + pb += cchPrecision; + off += cchPrecision; + } + printf("\n"); +} + +int doioctl(int iCmd, void *pvData, const char *pszWho) +{ + int rc; + do + { + errno = 0; + rc = ioctl(g_fd, iCmd, pvData); + + } while (rc && errno == EAGAIN); + if (rc) + printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno)); + else + printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd); + return rc; +} + +int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_bulktransfer BulkMsg = {0}; + + BulkMsg.ep = EndPt; + BulkMsg.timeout = 1000; + BulkMsg.len = cbBuf; + BulkMsg.data = pvBuf; + int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho); +// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf); + if (rc >= 0) + return rc; + return -1; +#endif +} + +int send_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + return dobulk(EndPt, pvBuf, cbBuf, "send_bulk"); +} + +int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk"); + if (cb > 0) + printf("cb=%d\n", cb); + return cb; +} + +int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, + void *pvBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_ctrltransfer CtrlMsg = {0}; + + CtrlMsg.bRequestType = bmRequestType; + CtrlMsg.bRequest = bRequest; + CtrlMsg.wValue = wValue; + CtrlMsg.wLength = wLength; + CtrlMsg.timeout = 1000; + CtrlMsg.data = pvBuf; + + int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho); + printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength); + if (rc >= 0) + return rc; + return -1; +#endif +} + +static int claim_if(int iIf) +{ + return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if"); +} + +static int usb_set_connected(int ifnum, int conn) +{ + struct usbdevfs_ioctl io; + io.ifno = ifnum; + io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + io.data = NULL; + return doioctl(USBDEVFS_IOCTL, &io, "set_connected"); +} + +static int set_config(int iCfg) +{ + return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config"); +} + +static int set_interface(int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf = {0}; + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface"); +} + +/* can be exploited to check if there is an active config. */ +static int reset_ep(int EndPt) +{ + return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep"); +} + + +static void msd() +{ +#if 1 + unsigned InEndPt = 1; + unsigned OutEndPt = 1; +#else + unsigned InEndPt = 1; + unsigned OutEndPt = 2; +#endif + unsigned char abBuf[512]; + int i; + +// set_config(1); - the culprit + set_interface(0, 0); + +#if 0 + /* Send an Get Max LUN request */ + abBuf[0] = 0; + if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE, + 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0) + printf("max luns: %d\n", abBuf[0]); +#endif + + for (i = 0; i < 3; i++) + { + printf("i=%d\n", i); + + /* Send an INQUIRY command to ep 2 */ + memset(abBuf, 0, sizeof(abBuf)); + memcpy(abBuf, "USBC", 4); + *(uint32_t *)(&abBuf[4]) = 0x12330984 ; + //abBuf[8] = 0x08; + abBuf[8] = 0x24; + abBuf[0xc] = 0x80; + abBuf[0xe] = 0x06; /* cmd length */ + abBuf[0x0f] = 0x12; /* cmd - INQUIRY */ + abBuf[0x13] = 0x24; + + hex(abBuf, 31, "intquery req"); + if (send_bulk(OutEndPt, abBuf, 31) < 0) + return; + //usleep(15000); + + /* read result */ + memset(abBuf, 0, sizeof(abBuf)); + //printf("recv..\n"); + int cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry result"); + + /* sense? */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry sense?"); + usleep(150000); + } +} + +void palm(void) +{ +// set_config(1); //skip this +// reset_ep(6); + + /* This seems to be some kind of 'identify device' request. */ + uint8_t abVendor[0x14] = {0}; + int cb = doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_VENDOR | VUSB_TO_ENDPOINT, + 0x04, 0, 0, 0x14, abVendor, "vendor req"); + hex(abVendor, cb, "palm vendor req"); + + /* read from ep 6. */ + uint8_t abBuf[512]; + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 6); + hex(abBuf, cb, "bulk 1"); + + /* read from ep 6. */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 22); + hex(abBuf, cb, "bulk 2"); + +#if 0 + /* write to ep 6 */ + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 1; + abBuf[1] = 2; + abBuf[5] = 0x32; + if (send_bulk(7, abBuf, 6) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 0x12; + abBuf[1] = 1; + if (send_bulk(7, abBuf, 8) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[1] = 0x20; + abBuf[6] = 0x24; + if (send_bulk(7, abBuf, 6) < 0) + return; + + memset(abBuf, 0, sizeof(abBuf)); + abBuf[0] = 0xff; + abBuf[1] = 0xff; + abBuf[2] = 0xff; + abBuf[3] = 0xff; + abBuf[4] = 0x3c; + abBuf[6] = 0x3c; + abBuf[0x0f] = 1; + abBuf[0x11] = 0x10; + abBuf[0x15] = 0x10; + abBuf[0x18] = 0x3c; + abBuf[0x1a] = 0x3c; + if (send_bulk(7, abBuf, 0x24) < 0) + return; + + /* read from ep 6 */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 64); + hex(abBuf, cb, "bulk 3"); + + /* read from ep 6. */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(6, abBuf, 50); + hex(abBuf, cb, "bulk 4"); +#endif +} + +int reset(void) +{ + int i = 0; + printf("resetting...\n"); + return doioctl(USBDEVFS_RESET, &i, "reset"); +} + +int main(int argc, char **argv) +{ + g_fd = open(argv[1], O_RDWR); + if (errno == ENOENT && g_fd < 0) + { + int i; + for (i = 0; i < 120; i++) + { + g_fd = open(argv[1], O_RDWR); + if (g_fd >= 0) + break; + printf("."); fflush(stdout); + usleep(500000); + } + printf("\n"); + } + if (g_fd < 0) + return bitch("open"); + +// reset(); +// set_config(0); +// set_config(1); + + usb_set_connected(0, 1); + claim_if(0); + +#if 0 + msd(); +#else + palm(); +#endif + return 0; +} diff --git a/src/VBox/Devices/USB/testcase/tstTrekStorGo.c b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c new file mode 100644 index 00000000..cc05c106 --- /dev/null +++ b/src/VBox/Devices/USB/testcase/tstTrekStorGo.c @@ -0,0 +1,313 @@ +/* $Id: tstTrekStorGo.c $ */ +/** @file + * Some simple inquiry test for the TrekStor USB-Stick GO, linux usbfs + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/vfs.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <linux/usbdevice_fs.h> + + +/** @name USB Control message recipient codes (from spec) + * @{ */ +#define VUSB_TO_DEVICE 0x0 +#define VUSB_TO_INTERFACE 0x1 +#define VUSB_TO_ENDPOINT 0x2 +#define VUSB_TO_OTHER 0x3 +#define VUSB_RECIP_MASK 0x1f +/** @} */ + +/** @name USB control pipe setup packet structure (from spec) + * @{ */ +#define VUSB_REQ_SHIFT (5) +#define VUSB_REQ_STANDARD (0x0 << VUSB_REQ_SHIFT) +#define VUSB_REQ_CLASS (0x1 << VUSB_REQ_SHIFT) +#define VUSB_REQ_VENDOR (0x2 << VUSB_REQ_SHIFT) +#define VUSB_REQ_RESERVED (0x3 << VUSB_REQ_SHIFT) +#define VUSB_REQ_MASK (0x3 << VUSB_REQ_SHIFT) +/** @} */ + +#define VUSB_DIR_TO_HOST 0x80 +typedef struct vusb_setup +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VUSBSETUP, *PVUSBSETUP; +typedef const VUSBSETUP *PCVUSBSETUP; + + +int g_fd; + +int bitch(const char *pszMsg) +{ + printf("failure: %s: %d %s\n", pszMsg, errno, strerror(errno)); + return 1; +} + +void hex(const void *pv, ssize_t cb, const char *pszWhat) +{ + printf("%s: cb=%d\n", pszWhat, cb); + unsigned char *pb = (unsigned char *)pv; + int cch = 0; + int off = 0; + int cchPrecision = 16; + while (off < cb) + { + int i; + printf("%s%0*x %04x:", off ? "\n" : "", sizeof(pb) * 2, (uintptr_t)pb, off); + + for (i = 0; i < cchPrecision && off + i < cb; i++) + printf(off + i < cb ? !(i & 7) && i ? "-%02x" : " %02x" : " ", pb[i]); + while (i++ < cchPrecision) + printf(" "); + printf(" "); + for (i = 0; i < cchPrecision && off + i < cb; i++) + { + uint8_t u8 = pb[i]; + fputc(u8 < 127 && u8 >= 32 ? u8 : '.', stdout); + } + + /* next */ + pb += cchPrecision; + off += cchPrecision; + } + printf("\n"); +} + +int doioctl(int iCmd, void *pvData, const char *pszWho) +{ + int rc; + do + { + errno = 0; + rc = ioctl(g_fd, iCmd, pvData); + + } while (rc && errno == EAGAIN); + if (rc) + printf("doioctl: %s: iCmd=%#x errno=%d %s\n", pszWho, iCmd, errno, strerror(errno)); + else + printf("doioctl: %s: iCmd=%#x ok\n", pszWho, iCmd); + return rc; +} + +int dobulk(int EndPt, void *pvBuf, size_t cbBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_bulktransfer BulkMsg = {0}; + + BulkMsg.ep = EndPt; + BulkMsg.timeout = 1000; + BulkMsg.len = cbBuf; + BulkMsg.data = pvBuf; + int rc = doioctl(USBDEVFS_BULK, &BulkMsg, pszWho); +// printf("rc=%d BulkMsg.len=%d cbBuf=%d\n", rc, BulkMsg.len, cbBuf); + if (rc >= 0) + return rc; + return -1; +#endif +} + +int send_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + return dobulk(EndPt, pvBuf, cbBuf, "send_bulk"); +} + +int recv_bulk(int EndPt, void *pvBuf, size_t cbBuf) +{ + int cb = dobulk(EndPt | 0x80, pvBuf, cbBuf, "recv_bulk"); + if (cb > 0) + printf("cb=%d\n", cb); + return cb; +} + +int doctrl(uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint16_t wLength, + void *pvBuf, const char *pszWho) +{ +#if 0 + struct usbdevfs_urb KUrb = {0}; + KUrb.type = USBDEVFS_URB_TYPE_BULK; + KUrb.endpoint = EndPt; + KUrb.buffer = pvBuf; + KUrb.buffer_length = cbBuf; + KUrb.actual_length = 0; //cbBuf + KUrb.flags = 0; /* ISO_ASAP/SHORT_NOT_OK */ + if (!doioctl(USBDEVFS_SUBMITURB, &KUrb, pszWho)) + { + struct usbdevfs_urb *pKUrb = NULL; + if (!doioctl(USBDEVFS_REAPURB, &pKUrb, pszWho) + && pKUrb == &KUrb) + return KUrb.actual_length; + } + return -1; +#else + struct usbdevfs_ctrltransfer CtrlMsg = {0}; + + CtrlMsg.bRequestType = bmRequestType; + CtrlMsg.bRequest = bRequest; + CtrlMsg.wValue = wValue; + CtrlMsg.wLength = wLength; + CtrlMsg.timeout = 1000; + CtrlMsg.data = pvBuf; + + int rc = doioctl(USBDEVFS_CONTROL, &CtrlMsg, pszWho); + printf("rc=%d CtrlMsg.wLength=%d\n", rc, CtrlMsg.wLength); + if (rc >= 0) + return rc; + return -1; +#endif +} + +static int claim_if(int iIf) +{ + return doioctl(USBDEVFS_CLAIMINTERFACE, &iIf, "claim_if"); +} + +static int usb_set_connected(int ifnum, int conn) +{ + struct usbdevfs_ioctl io; + io.ifno = ifnum; + io.ioctl_code = (conn) ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; + io.data = NULL; + return doioctl(USBDEVFS_IOCTL, &io, "set_connected"); +} + +static int set_config(int iCfg) +{ + return doioctl(USBDEVFS_SETCONFIGURATION, &iCfg, "set_config"); +} + +static int set_interface(int iIf, int iAlt) +{ + struct usbdevfs_setinterface SetIf = {0}; + SetIf.interface = iIf; + SetIf.altsetting = iAlt; + return doioctl(USBDEVFS_SETINTERFACE, &SetIf, "set_interface"); +} + +/* can be exploited to check if there is an active config. */ +static int reset_ep(int EndPt) +{ + return doioctl(USBDEVFS_RESETEP, &EndPt, "reset_ep"); +} + + +static void msd() +{ +#if 1 + unsigned InEndPt = 1; + unsigned OutEndPt = 1; +#else + unsigned InEndPt = 1; + unsigned OutEndPt = 2; +#endif + unsigned char abBuf[512]; + int i; + +#if 0 + /* Send an Get Max LUN request */ + abBuf[0] = 0; + if (doctrl(VUSB_DIR_TO_HOST | VUSB_REQ_CLASS | VUSB_TO_INTERFACE, + 0xfe /* max lun */, 0, 1, 1, abBuf, "get max lun") >= 0) + printf("max luns: %d\n", abBuf[0]); +#endif + + for (i = 0; i < 3; i++) + { + printf("i=%d\n", i); + + /* Send an INQUIRY command to ep 2 */ + memset(abBuf, 0, sizeof(abBuf)); + memcpy(abBuf, "USBC", 4); + *(uint32_t *)(&abBuf[4]) = 0x12330984 ; + //abBuf[8] = 0x08; + abBuf[8] = 0x24; + abBuf[0xc] = 0x80; + abBuf[0xe] = 0x06; /* cmd length */ + abBuf[0x0f] = 0x12; /* cmd - INQUIRY */ + abBuf[0x13] = 0x24; + + hex(abBuf, 31, "intquery req"); + if (send_bulk(OutEndPt, abBuf, 31) < 0) + return; + //usleep(15000); + + /* read result */ + memset(abBuf, 0, sizeof(abBuf)); + //printf("recv..\n"); + int cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry result"); + + /* sense? */ + memset(abBuf, 0, sizeof(abBuf)); + cb = recv_bulk(InEndPt, abBuf, 36); + hex(abBuf, cb, "inquiry sense?"); + usleep(150000); + } +} + +int reset(void) +{ + int i = 0; + printf("resetting...\n"); + return doioctl(USBDEVFS_RESET, &i, "reset"); +} + +int main(int argc, char **argv) +{ + g_fd = open(argv[1], O_RDWR); + if (g_fd < 0) + return bitch("open"); + + reset(); + + usb_set_connected(0, 1); + claim_if(0); + +// set_config(1); - the culprit! + set_interface(0, 0); + + msd(); + return 0; +} diff --git a/src/VBox/Devices/USB/usbip/Makefile.kup b/src/VBox/Devices/USB/usbip/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/usbip/Makefile.kup diff --git a/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp new file mode 100644 index 00000000..9faffc71 --- /dev/null +++ b/src/VBox/Devices/USB/usbip/USBProxyDevice-usbip.cpp @@ -0,0 +1,1764 @@ +/* $Id: USBProxyDevice-usbip.cpp $ */ +/** @file + * USB device proxy - USB/IP backend. + */ + +/* + * Copyright (C) 2014-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/socket.h> +#include <iprt/poll.h> +#include <iprt/tcp.h> +#include <iprt/pipe.h> +#include <iprt/list.h> +#include <iprt/semaphore.h> + +#include "../USBProxyDevice.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** The USB version number used for the protocol. */ +#define USBIP_VERSION UINT16_C(0x0111) +/** Request indicator in the command code. */ +#define USBIP_INDICATOR_REQ RT_BIT(15) + +/** Command/Reply code for OP_REQ/RET_DEVLIST. */ +#define USBIP_REQ_RET_DEVLIST UINT16_C(5) +/** Command/Reply code for OP_REQ/REP_IMPORT. */ +#define USBIP_REQ_RET_IMPORT UINT16_C(3) +/** USB submit command identifier. */ +#define USBIP_CMD_SUBMIT UINT32_C(1) +/** USB submit status identifier. */ +#define USBIP_RET_SUBMIT UINT32_C(3) +/** URB unlink (cancel) command identifier. */ +#define USBIP_CMD_UNLINK UINT32_C(2) +/** URB unlink (cancel) reply identifier. */ +#define USBIP_RET_UNLINK UINT32_C(4) + +/** Short read is not okay for the specified URB. */ +#define USBIP_XFER_FLAGS_SHORT_NOT_OK RT_BIT_32(0) +/** Queue the isochronous URB as soon as possible. */ +#define USBIP_XFER_FLAGS_ISO_ASAP RT_BIT_32(1) +/** Don't use DMA mappings for this URB. */ +#define USBIP_XFER_FLAGS_NO_TRANSFER_DMA_MAP RT_BIT_32(2) +/** Explain - only applies to UHCI. */ +#define USBIP_XFER_FLAGS_FSBR RT_BIT_32(4) + +/** URB direction - input. */ +#define USBIP_DIR_IN UINT32_C(1) +/** URB direction - output. */ +#define USBIP_DIR_OUT UINT32_C(0) + +/** @name USB/IP error codes. + * @{ */ +/** Success indicator. */ +#define USBIP_STATUS_SUCCESS INT32_C(0) +/** Pipe stalled. */ +#define USBIP_STATUS_PIPE_STALLED INT32_C(-32) +/** URB was unlinked by a call to usb_unlink_urb(). */ +#define USBIP_STATUS_URB_UNLINKED INT32_C(-104) +/** Short read. */ +#define USBIP_STATUS_SHORT_READ INT32_C(-121) +/** @} */ + +/** + * Exported device entry in the OP_RET_DEVLIST reply. + */ +#pragma pack(1) +typedef struct UsbIpExportedDevice +{ + /** Path of the device, zero terminated string. */ + char szPath[256]; + /** Bus ID of the exported device, zero terminated string. */ + char szBusId[32]; + /** Bus number. */ + uint32_t u32BusNum; + /** Device number. */ + uint32_t u32DevNum; + /** Speed indicator of the device. */ + uint32_t u32Speed; + /** Vendor ID of the device. */ + uint16_t u16VendorId; + /** Product ID of the device. */ + uint16_t u16ProductId; + /** Device release number. */ + uint16_t u16BcdDevice; + /** Device class. */ + uint8_t bDeviceClass; + /** Device Subclass. */ + uint8_t bDeviceSubClass; + /** Device protocol. */ + uint8_t bDeviceProtocol; + /** Configuration value. */ + uint8_t bConfigurationValue; + /** Current configuration value of the device. */ + uint8_t bNumConfigurations; + /** Number of interfaces for the device. */ + uint8_t bNumInterfaces; +} UsbIpExportedDevice; +/** Pointer to a exported device entry. */ +typedef UsbIpExportedDevice *PUsbIpExportedDevice; +#pragma pack() +AssertCompileSize(UsbIpExportedDevice, 312); + +/** + * Interface descriptor entry for an exported device. + */ +#pragma pack(1) +typedef struct UsbIpDeviceInterface +{ + /** Intefrace class. */ + uint8_t bInterfaceClass; + /** Interface sub class. */ + uint8_t bInterfaceSubClass; + /** Interface protocol identifier. */ + uint8_t bInterfaceProtocol; + /** Padding byte for alignment. */ + uint8_t bPadding; +} UsbIpDeviceInterface; +/** Pointer to an interface descriptor entry. */ +typedef UsbIpDeviceInterface *PUsbIpDeviceInterface; +#pragma pack() + +/** + * USB/IP Import request. + */ +#pragma pack(1) +typedef struct UsbIpReqImport +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t u32Status; + /** Bus Id of the device as zero terminated string. */ + char aszBusId[32]; +} UsbIpReqImport; +/** Pointer to a import request. */ +typedef UsbIpReqImport *PUsbIpReqImport; +#pragma pack() + +/** + * USB/IP Import reply. + * + * This is only the header, for successful + * imports the device details are sent to as + * defined in UsbIpExportedDevice. + */ +#pragma pack(1) +typedef struct UsbIpRetImport +{ + /** Protocol version number. */ + uint16_t u16Version; + /** Command code. */ + uint16_t u16Cmd; + /** Status field, unused. */ + int32_t u32Status; +} UsbIpRetImport; +/** Pointer to a import reply. */ +typedef UsbIpRetImport *PUsbIpRetImport; +#pragma pack() + +/** + * Command/Reply header common to the submit and unlink commands + * replies. + */ +#pragma pack(1) +typedef struct UsbIpReqRetHdr +{ + /** Request/Return code. */ + uint32_t u32ReqRet; + /** Sequence number to identify the URB. */ + uint32_t u32SeqNum; + /** Device id. */ + uint32_t u32DevId; + /** Direction of the endpoint (host->device, device->host). */ + uint32_t u32Direction; + /** Endpoint number. */ + uint32_t u32Endpoint; +} UsbIpReqRetHdr; +/** Pointer to a request/reply header. */ +typedef UsbIpReqRetHdr *PUsbIpReqRetHdr; +#pragma pack() + +/** + * USB/IP Submit request. + */ +#pragma pack(1) +typedef struct UsbIpReqSubmit +{ + /** The request header. */ + UsbIpReqRetHdr Hdr; + /** Transfer flags for the URB. */ + uint32_t u32XferFlags; + /** Transfer buffer length. */ + uint32_t u32TransferBufferLength; + /** Frame to transmit an ISO frame. */ + uint32_t u32StartFrame; + /** Number of isochronous packets. */ + uint32_t u32NumIsocPkts; + /** Maximum time for the request on the server side host controller. */ + uint32_t u32Interval; + /** Setup data for a control URB. */ + VUSBSETUP Setup; +} UsbIpReqSubmit; +/** Pointer to a submit request. */ +typedef UsbIpReqSubmit *PUsbIpReqSubmit; +#pragma pack() +AssertCompileSize(UsbIpReqSubmit, 48); + +/** + * USB/IP Submit reply. + */ +#pragma pack(1) +typedef struct UsbIpRetSubmit +{ + /** The reply header. */ + UsbIpReqRetHdr Hdr; + /** Status code. */ + int32_t u32Status; + /** Actual length of the reply buffer. */ + uint32_t u32ActualLength; + /** The actual selected frame for a isochronous transmit. */ + uint32_t u32StartFrame; + /** Number of isochronous packets. */ + uint32_t u32NumIsocPkts; + /** Number of failed isochronous packets. */ + uint32_t u32ErrorCount; + /** Setup data for a control URB. */ + VUSBSETUP Setup; +} UsbIpRetSubmit; +/** Pointer to a submit reply. */ +typedef UsbIpRetSubmit *PUsbIpRetSubmit; +#pragma pack() +AssertCompileSize(UsbIpRetSubmit, 48); + +/** + * Unlink URB request. + */ +#pragma pack(1) +typedef struct UsbIpReqUnlink +{ + /** The request header. */ + UsbIpReqRetHdr Hdr; + /** The sequence number to unlink. */ + uint32_t u32SeqNum; + /** Padding - unused. */ + uint8_t abPadding[24]; +} UsbIpReqUnlink; +/** Pointer to a URB unlink request. */ +typedef UsbIpReqUnlink *PUsbIpReqUnlink; +#pragma pack() +AssertCompileSize(UsbIpReqUnlink, 48); + +/** + * Unlink URB reply. + */ +#pragma pack(1) +typedef struct UsbIpRetUnlink +{ + /** The reply header. */ + UsbIpReqRetHdr Hdr; + /** Status of the request. */ + int32_t u32Status; + /** Padding - unused. */ + uint8_t abPadding[24]; +} UsbIpRetUnlink; +/** Pointer to a URB unlink request. */ +typedef UsbIpRetUnlink *PUsbIpRetUnlink; +#pragma pack() +AssertCompileSize(UsbIpRetUnlink, 48); + +/** + * Union of possible replies from the server during normal operation. + */ +#pragma pack(1) +typedef union UsbIpRet +{ + /** The header. */ + UsbIpReqRetHdr Hdr; + /** Submit reply. */ + UsbIpRetSubmit RetSubmit; + /** Unlink reply. */ + UsbIpRetUnlink RetUnlink; + /** Byte view. */ + uint8_t abReply[1]; +} UsbIpRet; +/** Pointer to a reply union. */ +typedef UsbIpRet *PUsbIpRet; +#pragma pack() + +/** + * Isochronous packet descriptor. +*/ +#pragma pack(1) +typedef struct UsbIpIsocPktDesc +{ + /** Offset */ + uint32_t u32Offset; + /** Length of the packet including padding. */ + uint32_t u32Length; + /** Size of the transmitted data. */ + uint32_t u32ActualLength; + /** Completion status for this packet. */ + int32_t i32Status; +} UsbIpIsocPktDesc; +/** Pointer to a isochronous packet descriptor. */ +typedef UsbIpIsocPktDesc *PUsbIpIsocPktDesc; +#pragma pack() + +/** + * USB/IP backend specific data for one URB. + * Required for tracking in flight and landed URBs. + */ +typedef struct USBPROXYURBUSBIP +{ + /** List node for the in flight or landed URB list. */ + RTLISTNODE NodeList; + /** Sequence number the assigned URB is identified by. */ + uint32_t u32SeqNumUrb; + /** Sequence number of the unlink command if the URB was cancelled. */ + uint32_t u32SeqNumUrbUnlink; + /** Flag whether the URB was cancelled. */ + bool fCancelled; + /** USB xfer type. */ + VUSBXFERTYPE enmType; + /** USB xfer direction. */ + VUSBDIRECTION enmDir; + /** Completion status. */ + VUSBSTATUS enmStatus; + /** Pointer to the VUSB URB. */ + PVUSBURB pVUsbUrb; +} USBPROXYURBUSBIP; +/** Pointer to a USB/IP URB. */ +typedef USBPROXYURBUSBIP *PUSBPROXYURBUSBIP; + +/** + * USB/IP data receive states. + */ +typedef enum USBPROXYUSBIPRECVSTATE +{ + /** Invalid receive state. */ + USBPROXYUSBIPRECVSTATE_INVALID = 0, + /** Currently receiving the common header structure. */ + USBPROXYUSBIPRECVSTATE_HDR_COMMON, + /** Currently receieving the rest of the header structure. */ + USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL, + /** Currently receiving data into the URB buffer. */ + USBPROXYUSBIPRECVSTATE_URB_BUFFER, + /** Currently receiving the isochronous packet descriptors. */ + USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, + /** Usual 32bit hack. */ + USBPROXYUSBIPRECVSTATE_32BIT_HACK = 0x7fffffff +} USBPROXYUSBIPRECVSTATE; +/** Pointer to an receive state. */ +typedef USBPROXYUSBIPRECVSTATE *PUSBPROXYUSBIPRECVSTATE; + +/** + * Backend data for the USB/IP USB Proxy device backend. + */ +typedef struct USBPROXYDEVUSBIP +{ + /** IPRT socket handle. */ + RTSOCKET hSocket; + /** Pollset with the wakeup pipe and socket. */ + RTPOLLSET hPollSet; + /** Pipe endpoint - read (in the pollset). */ + RTPIPE hPipeR; + /** Pipe endpoint - write. */ + RTPIPE hPipeW; + /** Next sequence number to use for identifying submitted URBs. */ + volatile uint32_t u32SeqNumNext; + /** Fast mutex protecting the lists below against concurrent access. */ + RTSEMFASTMUTEX hMtxLists; + /** List of in flight URBs. */ + RTLISTANCHOR ListUrbsInFlight; + /** List of landed URBs. */ + RTLISTANCHOR ListUrbsLanded; + /** List of URBs to submit. */ + RTLISTANCHOR ListUrbsToQueue; + /** Port of the USB/IP host to connect to. */ + uint32_t uPort; + /** USB/IP host address. */ + char *pszHost; + /** USB Bus ID of the device to capture. */ + char *pszBusId; + /** The device ID to use to identify the device. */ + uint32_t u32DevId; + /** Temporary buffer for the next reply header */ + UsbIpRet BufRet; + /** Temporary buffer to hold all isochronous packet descriptors. */ + UsbIpIsocPktDesc aIsocPktDesc[8]; + /** Pointer to the current buffer to write received data to. */ + uint8_t *pbRecv; + /** Number of bytes received so far. */ + size_t cbRecv; + /** Number of bytes left to receive. until we advance the state machine and process the data */ + size_t cbLeft; + /** The current receiving state. */ + USBPROXYUSBIPRECVSTATE enmRecvState; + /** The URB we currently receive a response for. */ + PUSBPROXYURBUSBIP pUrbUsbIp; +} USBPROXYDEVUSBIP, *PUSBPROXYDEVUSBIP; + +/** Pollset id of the socket. */ +#define USBIP_POLL_ID_SOCKET 0 +/** Pollset id of the pipe. */ +#define USBIP_POLL_ID_PIPE 1 + +/** USB/IP address prefix for identifcation. */ +#define USBIP_URI_PREFIX "usbip://" +/** USB/IP address prefix length. */ +#define USBIP_URI_PREFIX_LEN (sizeof(USBIP_URI_PREFIX) - 1) + +/** Waking reason for the USB I/P reaper: New URBs to queue. */ +#define USBIP_REAPER_WAKEUP_REASON_QUEUE 'Q' +/** Waking reason for the USB I/P reaper: External wakeup. */ +#define USBIP_REAPER_WAKEUP_REASON_EXTERNAL 'E' + +/** + * Converts a request/reply header from network to host endianness. + * + * @returns nothing. + * @param pHdr The header to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqRetHdrN2H(PUsbIpReqRetHdr pHdr) +{ + pHdr->u32ReqRet = RT_H2N_U32(pHdr->u32ReqRet); + pHdr->u32SeqNum = RT_H2N_U32(pHdr->u32SeqNum); + pHdr->u32DevId = RT_H2N_U32(pHdr->u32DevId); + pHdr->u32Direction = RT_H2N_U32(pHdr->u32Direction); + pHdr->u32Endpoint = RT_H2N_U32(pHdr->u32Endpoint); +} + +/** + * Converts a request/reply header from host to network endianness. + * + * @returns nothing. + * @param pHdr The header to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqRetHdrH2N(PUsbIpReqRetHdr pHdr) +{ + pHdr->u32ReqRet = RT_N2H_U32(pHdr->u32ReqRet); + pHdr->u32SeqNum = RT_N2H_U32(pHdr->u32SeqNum); + pHdr->u32DevId = RT_N2H_U32(pHdr->u32DevId); + pHdr->u32Direction = RT_N2H_U32(pHdr->u32Direction); + pHdr->u32Endpoint = RT_N2H_U32(pHdr->u32Endpoint); +} + +/** + * Converts a submit request from host to network endianness. + * + * @returns nothing. + * @param pReqSubmit The submit request to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqSubmitH2N(PUsbIpReqSubmit pReqSubmit) +{ + usbProxyUsbIpReqRetHdrH2N(&pReqSubmit->Hdr); + pReqSubmit->u32XferFlags = RT_H2N_U32(pReqSubmit->u32XferFlags); + pReqSubmit->u32TransferBufferLength = RT_H2N_U32(pReqSubmit->u32TransferBufferLength); + pReqSubmit->u32StartFrame = RT_H2N_U32(pReqSubmit->u32StartFrame); + pReqSubmit->u32NumIsocPkts = RT_H2N_U32(pReqSubmit->u32NumIsocPkts); + pReqSubmit->u32Interval = RT_H2N_U32(pReqSubmit->u32Interval); +} + +/** + * Converts a submit reply from network to host endianness. + * + * @returns nothing. + * @param pReqSubmit The submit reply to convert. + */ +DECLINLINE(void) usbProxyUsbIpRetSubmitN2H(PUsbIpRetSubmit pRetSubmit) +{ + usbProxyUsbIpReqRetHdrN2H(&pRetSubmit->Hdr); + pRetSubmit->u32Status = RT_N2H_U32(pRetSubmit->u32Status); + pRetSubmit->u32ActualLength = RT_N2H_U32(pRetSubmit->u32ActualLength); + pRetSubmit->u32StartFrame = RT_N2H_U32(pRetSubmit->u32StartFrame); + pRetSubmit->u32NumIsocPkts = RT_N2H_U32(pRetSubmit->u32NumIsocPkts); + pRetSubmit->u32ErrorCount = RT_N2H_U32(pRetSubmit->u32ErrorCount); +} + +/** + * Converts a isochronous packet descriptor from host to network endianness. + * + * @returns nothing. + * @param pIsocPktDesc The packet descriptor to convert. + */ +DECLINLINE(void) usbProxyUsbIpIsocPktDescH2N(PUsbIpIsocPktDesc pIsocPktDesc) +{ + pIsocPktDesc->u32Offset = RT_H2N_U32(pIsocPktDesc->u32Offset); + pIsocPktDesc->u32Length = RT_H2N_U32(pIsocPktDesc->u32Length); + pIsocPktDesc->u32ActualLength = RT_H2N_U32(pIsocPktDesc->u32ActualLength); + pIsocPktDesc->i32Status = RT_H2N_U32(pIsocPktDesc->i32Status); +} + +/** + * Converts a isochronous packet descriptor from network to host endianness. + * + * @returns nothing. + * @param pIsocPktDesc The packet descriptor to convert. + */ +DECLINLINE(void) usbProxyUsbIpIsocPktDescN2H(PUsbIpIsocPktDesc pIsocPktDesc) +{ + pIsocPktDesc->u32Offset = RT_N2H_U32(pIsocPktDesc->u32Offset); + pIsocPktDesc->u32Length = RT_N2H_U32(pIsocPktDesc->u32Length); + pIsocPktDesc->u32ActualLength = RT_N2H_U32(pIsocPktDesc->u32ActualLength); + pIsocPktDesc->i32Status = RT_N2H_U32(pIsocPktDesc->i32Status); +} + +/** + * Converts a unlink request from host to network endianness. + * + * @returns nothing. + * @param pReqUnlink The unlink request to convert. + */ +DECLINLINE(void) usbProxyUsbIpReqUnlinkH2N(PUsbIpReqUnlink pReqUnlink) +{ + usbProxyUsbIpReqRetHdrH2N(&pReqUnlink->Hdr); + pReqUnlink->u32SeqNum = RT_H2N_U32(pReqUnlink->u32SeqNum); +} + +/** + * Converts a unlink reply from network to host endianness. + * + * @returns nothing. + * @param pRetUnlink The unlink reply to convert. + */ +DECLINLINE(void) usbProxyUsbIpRetUnlinkN2H(PUsbIpRetUnlink pRetUnlink) +{ + usbProxyUsbIpReqRetHdrN2H(&pRetUnlink->Hdr); + pRetUnlink->u32Status = RT_N2H_U32(pRetUnlink->u32Status); +} + +/** + * Convert the given exported device structure from host to network byte order. + * + * @returns nothing. + * @param pDevice The device structure to convert. + */ +DECLINLINE(void) usbProxyUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice) +{ + pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum); + pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum); + pDevice->u32Speed = RT_N2H_U16(pDevice->u32Speed); + pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId); + pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId); + pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice); +} + +/** + * Converts a USB/IP status code to a VUSB status code. + * + * @returns VUSB status code. + * @param i32Status The USB/IP status code from the reply. + */ +DECLINLINE(VUSBSTATUS) usbProxyUsbIpVUsbStatusConvertFromStatus(int32_t i32Status) +{ + if (RT_LIKELY( i32Status == USBIP_STATUS_SUCCESS + || i32Status == USBIP_STATUS_SHORT_READ)) + return VUSBSTATUS_OK; + + switch (i32Status) + { + case USBIP_STATUS_PIPE_STALLED: + return VUSBSTATUS_STALL; + default: + return VUSBSTATUS_DNR; + } + /* not reached */ +} + +/** + * Gets the next free sequence number. + * + * @returns Next free sequence number. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +DECLINLINE(uint32_t) usbProxyUsbIpSeqNumGet(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + uint32_t u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); + if (RT_UNLIKELY(!u32SeqNum)) + u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); + + return u32SeqNum; +} + +/** + * Links a given URB into the given list. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pList The list to link the URB into. + * @param pUrbUsbIp The URB to link. + */ +DECLINLINE(void) usbProxyUsbIpLinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PRTLISTANCHOR pList, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListAppend(pList, &pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); +} + +/** + * Unlinks a given URB from the current assigned list. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The URB to unlink. + */ +DECLINLINE(void) usbProxyUsbIpUnlinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListNodeRemove(&pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); +} + +/** + * Allocates a USB/IP proxy specific URB state. + * + * @returns Pointer to the USB/IP specific URB data or NULL on failure. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpUrbAlloc(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + NOREF(pProxyDevUsbIp); + return (PUSBPROXYURBUSBIP)RTMemAllocZ(sizeof(USBPROXYURBUSBIP)); +} + +/** + * Frees the given USB/IP URB state. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The USB/IP speciic URB data. + */ +static void usbProxyUsbIpUrbFree(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + NOREF(pProxyDevUsbIp); + RTMemFree(pUrbUsbIp); +} + +/** + * Parse the string representation of the host address. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data to parse the address for. + * @param pszAddress The address string to parse. + */ +static int usbProxyUsbIpParseAddress(PUSBPROXYDEVUSBIP pProxyDevUsbIp, const char *pszAddress) +{ + int rc = VINF_SUCCESS; + + if (!RTStrNCmp(pszAddress, USBIP_URI_PREFIX, USBIP_URI_PREFIX_LEN)) + { + pszAddress += USBIP_URI_PREFIX_LEN; + + const char *pszPortStart = RTStrStr(pszAddress, ":"); + if (pszPortStart) + { + pszPortStart++; + + const char *pszBusIdStart = RTStrStr(pszPortStart, ":"); + if (pszBusIdStart) + { + size_t cbHost = pszPortStart - pszAddress - 1; + size_t cbBusId = strlen(pszBusIdStart); + + pszBusIdStart++; + + rc = RTStrToUInt32Ex(pszPortStart, NULL, 10 /* uBase */, &pProxyDevUsbIp->uPort); + if ( rc == VINF_SUCCESS + || rc == VWRN_TRAILING_CHARS) + { + rc = RTStrAllocEx(&pProxyDevUsbIp->pszHost, cbHost + 1); + if (RT_SUCCESS(rc)) + rc = RTStrAllocEx(&pProxyDevUsbIp->pszBusId, cbBusId + 1); + if (RT_SUCCESS(rc)) + { + rc = RTStrCopyEx(pProxyDevUsbIp->pszHost, cbHost + 1, pszAddress, cbHost); + AssertRC(rc); + + rc = RTStrCopyEx(pProxyDevUsbIp->pszBusId, cbBusId + 1, pszBusIdStart, cbBusId); + AssertRC(rc); + + return VINF_SUCCESS; + } + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + +/** + * Connects to the USB/IP host and claims the device given in the proxy device data. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpConnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + int rc = VINF_SUCCESS; + rc = RTTcpClientConnect(pProxyDevUsbIp->pszHost, pProxyDevUsbIp->uPort, &pProxyDevUsbIp->hSocket); + if (RT_SUCCESS(rc)) + { + /* Disable send coalescing. */ + rc = RTTcpSetSendCoalescing(pProxyDevUsbIp->hSocket, false); + if (RT_FAILURE(rc)) + LogRel(("UsbIp: Disabling send coalescing failed (rc=%Rrc), continuing nevertheless but expect reduced performance\n", rc)); + + /* Import the device, i.e. claim it for our use. */ + UsbIpReqImport ReqImport; + ReqImport.u16Version = RT_H2N_U16(USBIP_VERSION); + ReqImport.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_IMPORT); + ReqImport.u32Status = RT_H2N_U32(USBIP_STATUS_SUCCESS); + rc = RTStrCopy(&ReqImport.aszBusId[0], sizeof(ReqImport.aszBusId), pProxyDevUsbIp->pszBusId); + if (rc == VINF_SUCCESS) + { + rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqImport, sizeof(ReqImport)); + if (RT_SUCCESS(rc)) + { + /* Read the reply. */ + UsbIpRetImport RetImport; + rc = RTTcpRead(pProxyDevUsbIp->hSocket, &RetImport, sizeof(RetImport), NULL); + if (RT_SUCCESS(rc)) + { + RetImport.u16Version = RT_N2H_U16(RetImport.u16Version); + RetImport.u16Cmd = RT_N2H_U16(RetImport.u16Cmd); + RetImport.u32Status = RT_N2H_U32(RetImport.u32Status); + if ( RetImport.u16Version == USBIP_VERSION + && RetImport.u16Cmd == USBIP_REQ_RET_IMPORT + && RetImport.u32Status == USBIP_STATUS_SUCCESS) + { + /* Read the device data. */ + UsbIpExportedDevice Device; + rc = RTTcpRead(pProxyDevUsbIp->hSocket, &Device, sizeof(Device), NULL); + if (RT_SUCCESS(rc)) + { + usbProxyUsbIpExportedDeviceN2H(&Device); + pProxyDevUsbIp->u32DevId = (Device.u32BusNum << 16) | Device.u32DevNum; + + rc = RTPollSetAddSocket(pProxyDevUsbIp->hPollSet, pProxyDevUsbIp->hSocket, + RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, USBIP_POLL_ID_SOCKET); + } + } + else + { + /* Check what went wrong and leave a meaningful error message in the log. */ + if (RetImport.u16Version != USBIP_VERSION) + LogRel(("UsbIp: Unexpected protocol version received from host (%#x vs. %#x)\n", + RetImport.u16Version, USBIP_VERSION)); + else if (RetImport.u16Cmd != USBIP_REQ_RET_IMPORT) + LogRel(("UsbIp: Unexpected reply code received from host (%#x vs. %#x)\n", + RetImport.u16Cmd, USBIP_REQ_RET_IMPORT)); + else if (RetImport.u32Status != 0) + LogRel(("UsbIp: Claiming the device has failed on the host with an unspecified error\n")); + else + AssertMsgFailed(("Something went wrong with if condition\n")); + } + } + } + } + else + { + LogRel(("UsbIp: Given bus ID is exceeds permitted protocol length: %u vs %u\n", + strlen(pProxyDevUsbIp->pszBusId) + 1, sizeof(ReqImport.aszBusId))); + rc = VERR_INVALID_PARAMETER; + } + + if (RT_FAILURE(rc)) + RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); + } + if (RT_FAILURE(rc)) + LogRel(("UsbIp: Connecting to the host %s failed with %Rrc\n", pProxyDevUsbIp->pszHost, rc)); + return rc; +} + +/** + * Disconnects from the USB/IP host releasing the device given in the proxy device data. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpDisconnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + int rc = VINF_SUCCESS; + + rc = RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); + if (RT_SUCCESS(rc)) + pProxyDevUsbIp->hSocket = NIL_RTSOCKET; + return rc; +} + +/** + * Returns the URB matching the given sequence number from the in flight list. + * + * @returns pointer to the URB matching the given sequence number or NULL + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNum The sequence number to search for. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpGetInFlightUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) +{ + bool fFound = false; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) + { + if (pIt->u32SeqNumUrb == u32SeqNum) + { + fFound = true; + break; + } + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return fFound ? pIt : NULL; +} + +/** + * Returns the URB matching the given sequence number from the cancel list. + * + * @returns pointer to the URB matching the given sequence number or NULL + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNum The sequence number to search for. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpGetCancelledUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) +{ + bool fFound = false; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIt; + RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) + { + if ( pIt->u32SeqNumUrbUnlink == u32SeqNum + && pIt->fCancelled == true) + { + fFound = true; + break; + } + } + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return fFound ? pIt : NULL; +} + +/** + * Resets the receive state for a new reply. + * + * @returns nothing. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static void usbProxyUsbIpResetRecvState(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_COMMON; + pProxyDevUsbIp->pbRecv = (uint8_t *)&pProxyDevUsbIp->BufRet; + pProxyDevUsbIp->cbRecv = 0; + pProxyDevUsbIp->cbLeft = sizeof(UsbIpReqRetHdr); +} + +static void usbProxyUsbIpRecvStateAdvance(PUSBPROXYDEVUSBIP pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE enmState, + uint8_t *pbData, size_t cbData) +{ + pProxyDevUsbIp->enmRecvState = enmState; + pProxyDevUsbIp->cbRecv = 0; + pProxyDevUsbIp->cbLeft = cbData; + pProxyDevUsbIp->pbRecv = pbData; +} + +/** + * Handles reception of a USB/IP PDU. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param ppUrbUsbIp Where to store the pointer to the USB/IP URB which completed. + * Will be NULL if the received PDU is not complete and we have + * have to wait for more data or on failure. + */ +static int usbProxyUsbIpRecvPdu(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP *ppUrbUsbIp) +{ + int rc = VINF_SUCCESS; + size_t cbRead = 0; + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + + Assert(pProxyDevUsbIp->cbLeft); + + /* Read any available data first. */ + rc = RTTcpReadNB(pProxyDevUsbIp->hSocket, pProxyDevUsbIp->pbRecv, pProxyDevUsbIp->cbLeft, &cbRead); + if (RT_SUCCESS(rc)) + { + pProxyDevUsbIp->cbRecv += cbRead; + pProxyDevUsbIp->cbLeft -= cbRead; + pProxyDevUsbIp->pbRecv += cbRead; + + /* Process the received data if there is nothing to receive left for the current state. */ + if (!pProxyDevUsbIp->cbLeft) + { + switch (pProxyDevUsbIp->enmRecvState) + { + case USBPROXYUSBIPRECVSTATE_HDR_COMMON: + { + Assert(pProxyDevUsbIp->cbRecv == sizeof(UsbIpReqRetHdr)); + + /* + * Determine the residual amount of data to receive until + * the complete reply header was received. + */ + switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) + { + case USBIP_RET_SUBMIT: + pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetSubmit) - sizeof(UsbIpReqRetHdr); + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; + break; + case USBIP_RET_UNLINK: + pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetUnlink) - sizeof(UsbIpReqRetHdr); + pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; + break; + default: + AssertLogRelMsgFailed(("Invalid reply header received: %d\n", + pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)); + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + + break; + } + case USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL: + { + /** @todo Verify that the directions match, verify that the length doesn't exceed the buffer. */ + + switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) + { + case USBIP_RET_SUBMIT: + /* Get the URB from the in flight list. */ + pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetInFlightUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); + if (pProxyDevUsbIp->pUrbUsbIp) + { + usbProxyUsbIpRetSubmitN2H(&pProxyDevUsbIp->BufRet.RetSubmit); + + /* We still have to receive the transfer buffer, even in case of an error. */ + pProxyDevUsbIp->pUrbUsbIp->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetSubmit.u32Status); + if (pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_IN) + { + uint8_t *pbData = NULL; + + AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb); + if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG) + { + /* Preserve the setup request. */ + pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)]; + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP); + } + else + { + pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0]; + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength; + } + + if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength) + usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER, + pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength); + else + { + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + } + else + { + Assert(pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_OUT); + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + } + else + { + LogRel(("USB/IP: Received reply with sequence number %u doesn't match any local URB\n", + RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum))); + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + break; + case USBIP_RET_UNLINK: + pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetCancelledUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); + if (pProxyDevUsbIp->pUrbUsbIp) + { + usbProxyUsbIpRetUnlinkN2H(&pProxyDevUsbIp->BufRet.RetUnlink); + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + pUrbUsbIp->pVUsbUrb->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetUnlink.u32Status); + } + /* else: Probably received the data for the URB and is complete already. */ + + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + break; + } + + break; + } + case USBPROXYUSBIPRECVSTATE_URB_BUFFER: + if (pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->enmType == VUSBXFERTYPE_ISOC) + usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, + (uint8_t *)&pProxyDevUsbIp->aIsocPktDesc[0], + pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc)); + else + { + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + } + break; + case USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS: + /* Process all received isochronous packet descriptors. */ + for (unsigned i = 0; i < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts; i++) + { + PVUSBURBISOCPTK pIsocPkt = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->aIsocPkts[i]; + usbProxyUsbIpIsocPktDescN2H(&pProxyDevUsbIp->aIsocPktDesc[i]); + pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->aIsocPktDesc[i].i32Status); + pIsocPkt->off = pProxyDevUsbIp->aIsocPktDesc[i].u32Offset; + pIsocPkt->cb = pProxyDevUsbIp->aIsocPktDesc[i].u32ActualLength; + } + + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); + break; + default: + AssertLogRelMsgFailed(("USB/IP: Invalid receive state %d\n", pProxyDevUsbIp->enmRecvState)); + } + } + } + else + { + /** @todo Complete all URBs with DNR error and mark device as unplugged. */ +#if 0 + pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; + pUrbUsbIp->pVUsbUrb->enmStatus = VUSBSTATUS_DNR; + usbProxyUsbIpResetRecvState(pProxyDevUsbIp); +#endif + } + + if (RT_SUCCESS(rc)) + *ppUrbUsbIp = pUrbUsbIp; + + return rc; +} + +/** + * Worker for queueing an URB on the main I/O thread. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pUrbUsbIp The USB/IP URB to queue. + */ +static int usbProxyUsbIpUrbQueueWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) +{ + PVUSBURB pUrb = pUrbUsbIp->pVUsbUrb; + + pUrbUsbIp->u32SeqNumUrb = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + pUrbUsbIp->enmType = pUrb->enmType; + pUrbUsbIp->enmStatus = pUrb->enmStatus; + pUrbUsbIp->enmDir = pUrb->enmDir; + + UsbIpReqSubmit ReqSubmit; + + RT_ZERO(ReqSubmit); + ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; + ReqSubmit.Hdr.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; + ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqSubmit.Hdr.u32Endpoint = pUrb->EndPt; + ReqSubmit.Hdr.u32Direction = pUrb->enmDir == VUSBDIRECTION_IN ? USBIP_DIR_IN : USBIP_DIR_OUT; + ReqSubmit.u32XferFlags = 0; + if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) + ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_SHORT_NOT_OK; + + ReqSubmit.u32TransferBufferLength = pUrb->cbData; + ReqSubmit.u32StartFrame = 0; + ReqSubmit.u32NumIsocPkts = 0; + ReqSubmit.u32Interval = 0; + + RTSGSEG aSegReq[3]; /* Maximum number of segments used for a Isochronous transfer. */ + UsbIpIsocPktDesc aIsocPktsDesc[8]; + unsigned cSegsUsed = 1; + aSegReq[0].pvSeg = &ReqSubmit; + aSegReq[0].cbSeg = sizeof(ReqSubmit); + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + memcpy(&ReqSubmit.Setup, &pUrb->abData, sizeof(ReqSubmit.Setup)); + ReqSubmit.u32TransferBufferLength -= sizeof(VUSBSETUP); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData - sizeof(VUSBSETUP); + aSegReq[cSegsUsed].pvSeg = pUrb->abData + sizeof(VUSBSETUP); + if (aSegReq[cSegsUsed].cbSeg) + cSegsUsed++; + } + LogFlowFunc(("Message (Control) URB\n")); + break; + case VUSBXFERTYPE_ISOC: + LogFlowFunc(("Isochronous URB\n")); + ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_ISO_ASAP; + ReqSubmit.u32NumIsocPkts = pUrb->cIsocPkts; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData; + aSegReq[cSegsUsed].pvSeg = pUrb->abData; + cSegsUsed++; + } + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + aIsocPktsDesc[i].u32Offset = pUrb->aIsocPkts[i].off; + aIsocPktsDesc[i].u32Length = pUrb->aIsocPkts[i].cb; + aIsocPktsDesc[i].u32ActualLength = 0; /** @todo */ + aIsocPktsDesc[i].i32Status = pUrb->aIsocPkts[i].enmStatus; + usbProxyUsbIpIsocPktDescH2N(&aIsocPktsDesc[i]); + } + + if (pUrb->cIsocPkts) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc); + aSegReq[cSegsUsed].pvSeg = &aIsocPktsDesc[0]; + cSegsUsed++; + } + + break; + case VUSBXFERTYPE_BULK: + case VUSBXFERTYPE_INTR: + LogFlowFunc(("Bulk URB\n")); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + aSegReq[cSegsUsed].cbSeg = pUrb->cbData; + aSegReq[cSegsUsed].pvSeg = pUrb->abData; + cSegsUsed++; + } + break; + default: + usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp); + return VERR_INVALID_PARAMETER; /** @todo better status code. */ + } + + usbProxyUsbIpReqSubmitH2N(&ReqSubmit); + + Assert(cSegsUsed <= RT_ELEMENTS(aSegReq)); + + /* Send the command. */ + RTSGBUF SgBufReq; + RTSgBufInit(&SgBufReq, &aSegReq[0], cSegsUsed); + + int rc = RTTcpSgWrite(pProxyDevUsbIp->hSocket, &SgBufReq); + if (RT_SUCCESS(rc)) + { + /* Link the URB into the list of in flight URBs. */ + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, pUrbUsbIp); + } + + return rc; +} + +/** + * Queues all pending URBs from the list. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static int usbProxyUsbIpUrbsQueuePending(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + RTLISTANCHOR ListUrbsPending; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListMove(&ListUrbsPending, &pProxyDevUsbIp->ListUrbsToQueue); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + PUSBPROXYURBUSBIP pIter; + PUSBPROXYURBUSBIP pIterNext; + RTListForEachSafe(&ListUrbsPending, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + rc = usbProxyUsbIpUrbQueueWorker(pProxyDevUsbIp, pIter); + if (RT_FAILURE(rc)) + { + /** @todo Complete the URB with an error. */ + usbProxyUsbIpUrbFree(pProxyDevUsbIp, pIter); + } + } + + return VINF_SUCCESS; +} + +/** + * Kick the reaper thread. + * + * @returns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param bReason The wakeup reason. + */ +static char usbProxyReaperKick(PUSBPROXYDEVUSBIP pProxyDevUsbIp, char bReason) +{ + int rc = VINF_SUCCESS; + size_t cbWritten = 0; + + rc = RTPipeWrite(pProxyDevUsbIp->hPipeW, &bReason, 1, &cbWritten); + Assert(RT_SUCCESS(rc) || cbWritten == 0); + + return rc; +} + +/** + * Drain the wakeup pipe. + * + * @returns Wakeup reason. + * @param pProxyDevUsbIp The USB/IP proxy device data. + */ +static char usbProxyUsbIpWakeupPipeDrain(PUSBPROXYDEVUSBIP pProxyDevUsbIp) +{ + char bRead = 0; + size_t cbRead = 0; + int rc = RTPipeRead(pProxyDevUsbIp->hPipeR, &bRead, 1, &cbRead); + Assert(RT_SUCCESS(rc) && cbRead == 1); NOREF(rc); + + return bRead; +} + +/** + * Executes the poll/receive loop either until a URB is received (with an optional matching sequence number) or + * the given timeout has elapsed. + * + * @returns Pointer to the received USB/IP URB or NULL on timeout or error. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param u32SeqNumRet The sequence number of a specific reply to return the URB for, 0 if + * any received URB is accepted. + * @param fPollWakePipe Flag whether to poll the wakeup pipe. + * @param cMillies Maximum number of milliseconds to wait for an URB to arrive. + */ +static PUSBPROXYURBUSBIP usbProxyUsbIpPollWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNumRet, + bool fPollWakePipe, RTMSINTERVAL cMillies) +{ + int rc = VINF_SUCCESS; + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + + if (!fPollWakePipe) + { + rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_ERROR); + AssertRC(rc); + } + + while (!pUrbUsbIp && RT_SUCCESS(rc) && cMillies) + { + uint32_t uIdReady = 0; + uint32_t fEventsRecv = 0; + RTMSINTERVAL msStart = RTTimeMilliTS(); + RTMSINTERVAL msNow; + + rc = RTPoll(pProxyDevUsbIp->hPollSet, cMillies, &fEventsRecv, &uIdReady); + Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); + if (RT_SUCCESS(rc)) + { + msNow = RTTimeMilliTS(); + cMillies = msNow - msStart >= cMillies ? 0 : cMillies - (msNow - msStart); + + if (uIdReady == USBIP_POLL_ID_SOCKET) + { + rc = usbProxyUsbIpRecvPdu(pProxyDevUsbIp, &pUrbUsbIp); + if ( RT_SUCCESS(rc) + && pUrbUsbIp) + { + /* Link the URB into the landed list if a specifc reply is requested and the URB doesn't match. */ + if ( u32SeqNumRet != 0 + && pUrbUsbIp->u32SeqNumUrb != u32SeqNumRet) + { + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pUrbUsbIp); + pUrbUsbIp = NULL; + } + } + } + else + { + AssertLogRelMsg(uIdReady == USBIP_POLL_ID_PIPE, ("Invalid pollset ID given\n")); + + char bReason = usbProxyUsbIpWakeupPipeDrain(pProxyDevUsbIp); + if (bReason == USBIP_REAPER_WAKEUP_REASON_QUEUE) + usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); + else + { + Assert(bReason == USBIP_REAPER_WAKEUP_REASON_EXTERNAL); + break; + } + } + } + } + + if (!fPollWakePipe) + { + rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_READ); + AssertRC(rc); + } + + return pUrbUsbIp; +} + +/** + * Synchronously exchange a given control message with the remote device. + * + * @eturns VBox status code. + * @param pProxyDevUsbIp The USB/IP proxy device data. + * @param pSetup The setup message. + * + * @note This method is only used to implement the *SetConfig, *SetInterface and *ClearHaltedEp + * callbacks because the USB/IP protocol lacks dedicated requests for these. + * @remark It is assumed that this method is never called while usbProxyUsbIpUrbReap is called + * on another thread. + */ +static int usbProxyUsbIpCtrlUrbExchangeSync(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PVUSBSETUP pSetup) +{ + int rc = VINF_SUCCESS; + + UsbIpReqSubmit ReqSubmit; + USBPROXYURBUSBIP UsbIpUrb; + + RT_ZERO(ReqSubmit); + + uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; + ReqSubmit.Hdr.u32SeqNum = u32SeqNum; + ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqSubmit.Hdr.u32Direction = USBIP_DIR_OUT; + ReqSubmit.Hdr.u32Endpoint = 0; /* Only default control endpoint is allowed for these kind of messages. */ + ReqSubmit.u32XferFlags = 0; + ReqSubmit.u32TransferBufferLength = 0; + ReqSubmit.u32StartFrame = 0; + ReqSubmit.u32NumIsocPkts = 0; + ReqSubmit.u32Interval = 0; + memcpy(&ReqSubmit.Setup, pSetup, sizeof(ReqSubmit.Setup)); + usbProxyUsbIpReqSubmitH2N(&ReqSubmit); + + UsbIpUrb.u32SeqNumUrb = u32SeqNum; + UsbIpUrb.u32SeqNumUrbUnlink = 0; + UsbIpUrb.fCancelled = false; + UsbIpUrb.enmType = VUSBXFERTYPE_MSG; + UsbIpUrb.enmDir = VUSBDIRECTION_OUT; + UsbIpUrb.pVUsbUrb = NULL; + + /* Send the command. */ + rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqSubmit, sizeof(ReqSubmit)); + if (RT_SUCCESS(rc)) + { + usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, &UsbIpUrb); + PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, u32SeqNum, false /*fPollWakePipe*/, + 30 * RT_MS_1SEC); + Assert( !pUrbUsbIp + || pUrbUsbIp == &UsbIpUrb); /* The returned URB should point to the URB we submitted. */ + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, &UsbIpUrb); + + if (!pUrbUsbIp) + rc = VERR_TIMEOUT; + } + + return rc; +} + + +/* + * The USB proxy device functions. + */ + +/** + * @interface_method_impl{USBPROXYBACK,pfnOpen} + */ +static DECLCALLBACK(int) usbProxyUsbIpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + RT_NOREF(pvBackend); + LogFlowFunc(("pProxyDev=%p pszAddress=%s, pvBackend=%p\n", pProxyDev, pszAddress, pvBackend)); + + PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + int rc = VINF_SUCCESS; + + RTListInit(&pDevUsbIp->ListUrbsInFlight); + RTListInit(&pDevUsbIp->ListUrbsLanded); + RTListInit(&pDevUsbIp->ListUrbsToQueue); + pDevUsbIp->hSocket = NIL_RTSOCKET; + pDevUsbIp->hPollSet = NIL_RTPOLLSET; + pDevUsbIp->hPipeW = NIL_RTPIPE; + pDevUsbIp->hPipeR = NIL_RTPIPE; + pDevUsbIp->u32SeqNumNext = 0; + pDevUsbIp->pszHost = NULL; + pDevUsbIp->pszBusId = NULL; + usbProxyUsbIpResetRecvState(pDevUsbIp); + + rc = RTSemFastMutexCreate(&pDevUsbIp->hMtxLists); + if (RT_SUCCESS(rc)) + { + /* Setup wakeup pipe and poll set first. */ + rc = RTPipeCreate(&pDevUsbIp->hPipeR, &pDevUsbIp->hPipeW, 0); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetCreate(&pDevUsbIp->hPollSet); + if (RT_SUCCESS(rc)) + { + rc = RTPollSetAddPipe(pDevUsbIp->hPollSet, pDevUsbIp->hPipeR, + RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE); + if (RT_SUCCESS(rc)) + { + /* Connect to the USB/IP host. */ + rc = usbProxyUsbIpParseAddress(pDevUsbIp, pszAddress); + if (RT_SUCCESS(rc)) + rc = usbProxyUsbIpConnect(pDevUsbIp); + } + + if (RT_FAILURE(rc)) + { + RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); + int rc2 = RTPollSetDestroy(pDevUsbIp->hPollSet); + AssertRC(rc2); + } + } + + if (RT_FAILURE(rc)) + { + int rc2 = RTPipeClose(pDevUsbIp->hPipeR); + AssertRC(rc2); + rc2 = RTPipeClose(pDevUsbIp->hPipeW); + AssertRC(rc2); + } + } + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClose} + */ +static DECLCALLBACK(void) usbProxyUsbIpClose(PUSBPROXYDEV pProxyDev) +{ + int rc = VINF_SUCCESS; + LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + /* Destroy the pipe and pollset if necessary. */ + if (pDevUsbIp->hPollSet != NIL_RTPOLLSET) + { + if (pDevUsbIp->hSocket != NIL_RTSOCKET) + { + rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET); + Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND); + } + rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); + AssertRC(rc); + rc = RTPollSetDestroy(pDevUsbIp->hPollSet); + AssertRC(rc); + rc = RTPipeClose(pDevUsbIp->hPipeR); + AssertRC(rc); + rc = RTPipeClose(pDevUsbIp->hPipeW); + AssertRC(rc); + } + + if (pDevUsbIp->hSocket != NIL_RTSOCKET) + usbProxyUsbIpDisconnect(pDevUsbIp); + if (pDevUsbIp->pszHost) + RTStrFree(pDevUsbIp->pszHost); + if (pDevUsbIp->pszBusId) + RTStrFree(pDevUsbIp->pszBusId); + + /* Clear the URB lists. */ + rc = RTSemFastMutexRequest(pDevUsbIp->hMtxLists); + AssertRC(rc); + PUSBPROXYURBUSBIP pIter; + PUSBPROXYURBUSBIP pIterNext; + RTListForEachSafe(&pDevUsbIp->ListUrbsInFlight, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + RTMemFree(pIter); + } + + RTListForEachSafe(&pDevUsbIp->ListUrbsLanded, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) + { + RTListNodeRemove(&pIter->NodeList); + RTMemFree(pIter); + } + RTSemFastMutexRelease(pDevUsbIp->hMtxLists); + RTSemFastMutexDestroy(pDevUsbIp->hMtxLists); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnReset} + */ +static DECLCALLBACK(int) usbProxyUsbIpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); + + int rc = VINF_SUCCESS; + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + if (fResetOnLinux) + { + Setup.bmRequestType = RT_BIT(5) | 0x03; /* Port request. */ + Setup.bRequest = 0x03; /* SET_FEATURE */ + Setup.wValue = 4; /* Port feature: Reset */ + Setup.wIndex = 0; /* Port number, irrelevant */ + Setup.wLength = 0; + rc = usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); + if (RT_SUCCESS(rc)) + { + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 2; + } + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnSetConfig} + */ +static DECLCALLBACK(int) usbProxyUsbIpSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + LogFlowFunc(("pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, iCfg)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0; + Setup.bRequest = 0x09; + Setup.wValue = iCfg; + Setup.wIndex = 0; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClaimInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnReleaseInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnSetInterface} + */ +static DECLCALLBACK(int) usbProxyUsbIpSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting) +{ + LogFlowFunc(("pProxyDev=%p iIf=%#x setting=%#x\n", pProxyDev, iIf, setting)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0x1; + Setup.bRequest = 0x0b; /* SET_INTERFACE */ + Setup.wValue = setting; + Setup.wIndex = iIf; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnClearHaltedEndpoint} + */ +static DECLCALLBACK(int) usbProxyUsbIpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int iEp) +{ + LogFlowFunc(("pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, iEp)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + VUSBSETUP Setup; + + Setup.bmRequestType = 0x2; + Setup.bRequest = 0x01; /* CLEAR_FEATURE */ + Setup.wValue = 0x00; /* ENDPOINT_HALT */ + Setup.wIndex = iEp; + Setup.wLength = 0; + return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyUsbIpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlowFunc(("pUrb=%p\n", pUrb)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + + /* Allocate a USB/IP Urb. */ + PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpUrbAlloc(pProxyDevUsbIp); + if (!pUrbUsbIp) + return VERR_NO_MEMORY; + + pUrbUsbIp->fCancelled = false; + pUrbUsbIp->pVUsbUrb = pUrb; + pUrb->Dev.pvPrivate = pUrbUsbIp; + + int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); + AssertRC(rc); + RTListAppend(&pProxyDevUsbIp->ListUrbsToQueue, &pUrbUsbIp->NodeList); + RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); + + return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_QUEUE); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbReap} + */ +static DECLCALLBACK(PVUSBURB) usbProxyUsbIpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + PUSBPROXYURBUSBIP pUrbUsbIp = NULL; + PVUSBURB pUrb = NULL; + int rc = VINF_SUCCESS; + + /* Queue new URBs first. */ + rc = usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); + AssertRC(rc); + + /* Any URBs pending delivery? */ + if (!RTListIsEmpty(&pProxyDevUsbIp->ListUrbsLanded)) + pUrbUsbIp = RTListGetFirst(&pProxyDevUsbIp->ListUrbsLanded, USBPROXYURBUSBIP, NodeList); + else + pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, 0, true /*fPollWakePipe*/, cMillies); + + if (pUrbUsbIp) + { + pUrb = pUrbUsbIp->pVUsbUrb; + pUrb->enmStatus = pUrbUsbIp->enmStatus; + + /* unlink from the pending delivery list */ + usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); + usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp); + } + + return pUrb; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbCancel} + */ +static DECLCALLBACK(int) usbProxyUsbIpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlowFunc(("pUrb=%p\n", pUrb)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + PUSBPROXYURBUSBIP pUrbUsbIp = (PUSBPROXYURBUSBIP)pUrb->Dev.pvPrivate; + UsbIpReqUnlink ReqUnlink; + + RT_ZERO(ReqUnlink); + + uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); + ReqUnlink.Hdr.u32ReqRet = USBIP_CMD_UNLINK; + ReqUnlink.Hdr.u32SeqNum = u32SeqNum; + ReqUnlink.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; + ReqUnlink.Hdr.u32Direction = USBIP_DIR_OUT; + ReqUnlink.Hdr.u32Endpoint = pUrb->EndPt; + ReqUnlink.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; + + usbProxyUsbIpReqUnlinkH2N(&ReqUnlink); + int rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqUnlink, sizeof(ReqUnlink)); + if (RT_SUCCESS(rc)) + { + pUrbUsbIp->u32SeqNumUrbUnlink = u32SeqNum; + pUrbUsbIp->fCancelled = true; + } + + return rc; +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnWakeup} + */ +static DECLCALLBACK(int) usbProxyUsbIpWakeup(PUSBPROXYDEV pProxyDev) +{ + LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); + return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_EXTERNAL); +} + + +/** + * The USB/IP USB Proxy Backend operations. + */ +extern const USBPROXYBACK g_USBProxyDeviceUsbIp = +{ + /* pszName */ + "usbip", + /* cbBackend */ + sizeof(USBPROXYDEVUSBIP), + usbProxyUsbIpOpen, + NULL, + usbProxyUsbIpClose, + usbProxyUsbIpReset, + usbProxyUsbIpSetConfig, + usbProxyUsbIpClaimInterface, + usbProxyUsbIpReleaseInterface, + usbProxyUsbIpSetInterface, + usbProxyUsbIpClearHaltedEp, + usbProxyUsbIpUrbQueue, + usbProxyUsbIpUrbCancel, + usbProxyUsbIpUrbReap, + usbProxyUsbIpWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/vrdp/Makefile.kup b/src/VBox/Devices/USB/vrdp/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/vrdp/Makefile.kup diff --git a/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp new file mode 100644 index 00000000..28b27a0a --- /dev/null +++ b/src/VBox/Devices/USB/vrdp/USBProxyDevice-vrdp.cpp @@ -0,0 +1,287 @@ +/* $Id: USBProxyDevice-vrdp.cpp $ */ +/** @file + * USB device proxy - the VRDP backend, calls the RemoteUSBBackend methods. + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY + +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/vrdpusb.h> +#include <VBox/vmm/pdm.h> + +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/string.h> + +#include "../USBProxyDevice.h" + +/** + * Backend data for the VRDP USB Proxy device backend. + */ +typedef struct USBPROXYDEVVRDP +{ + REMOTEUSBCALLBACK *pCallback; + PREMOTEUSBDEVICE pDevice; +} USBPROXYDEVVRDP, *PUSBPROXYDEVVRDP; + + +/* + * The USB proxy device functions. + */ + +static DECLCALLBACK(int) usbProxyVrdpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + LogFlow(("usbProxyVrdpOpen: pProxyDev=%p pszAddress=%s, pvBackend=%p\n", pProxyDev, pszAddress, pvBackend)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + int rc = VINF_SUCCESS; + + if (strncmp (pszAddress, REMOTE_USB_BACKEND_PREFIX_S, REMOTE_USB_BACKEND_PREFIX_LEN) == 0) + { + REMOTEUSBCALLBACK *pCallback = (REMOTEUSBCALLBACK *)pvBackend; + PREMOTEUSBDEVICE pDevice = NULL; + + rc = pCallback->pfnOpen (pCallback->pInstance, pszAddress, strlen (pszAddress) + 1, &pDevice); + + if (RT_SUCCESS (rc)) + { + pDevVrdp->pCallback = pCallback; + pDevVrdp->pDevice = pDevice; + pProxyDev->iActiveCfg = 1; /** @todo that may not be always true. */ + pProxyDev->cIgnoreSetConfigs = 1; + return VINF_SUCCESS; + } + } + else + { + AssertFailed(); + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +static DECLCALLBACK(void) usbProxyVrdpClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyVrdpClose: pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + pDevVrdp->pCallback->pfnClose (pDevVrdp->pDevice); +} + +static DECLCALLBACK(int) usbProxyVrdpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + LogFlow(("usbProxyVrdpReset: pProxyDev = %p\n", pProxyDev)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnReset (pDevVrdp->pDevice); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 2; + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpSetConfig(PUSBPROXYDEV pProxyDev, int cfg) +{ + LogFlow(("usbProxyVrdpSetConfig: pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, cfg)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnSetConfig (pDevVrdp->pDevice, (uint8_t)cfg); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpClaimInterface(PUSBPROXYDEV pProxyDev, int ifnum) +{ + LogFlow(("usbProxyVrdpClaimInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnClaimInterface (pDevVrdp->pDevice, (uint8_t)ifnum); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpReleaseInterface(PUSBPROXYDEV pProxyDev, int ifnum) +{ + LogFlow(("usbProxyVrdpReleaseInterface: pProxyDev=%s ifnum=%#x\n", pProxyDev->pUsbIns->pszName, ifnum)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnReleaseInterface (pDevVrdp->pDevice, (uint8_t)ifnum); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpSetInterface(PUSBPROXYDEV pProxyDev, int ifnum, int setting) +{ + LogFlow(("usbProxyVrdpSetInterface: pProxyDev=%p ifnum=%#x setting=%#x\n", pProxyDev, ifnum, setting)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnInterfaceSetting (pDevVrdp->pDevice, (uint8_t)ifnum, (uint8_t)setting); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + LogFlow(("usbProxyVrdpClearHaltedEp: pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, ep)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + int rc = pDevVrdp->pCallback->pfnClearHaltedEP (pDevVrdp->pDevice, (uint8_t)ep); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(int) usbProxyVrdpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlow(("usbProxyVrdpUrbQueue: pUrb=%p\n", pUrb)); + + /** @todo implement isochronous transfers for USB over VRDP. */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + Log(("usbproxy: isochronous transfers aren't implemented yet.\n")); + return VERR_NOT_IMPLEMENTED; + } + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + int rc = pDevVrdp->pCallback->pfnQueueURB (pDevVrdp->pDevice, pUrb->enmType, pUrb->EndPt, pUrb->enmDir, pUrb->cbData, + pUrb->abData, pUrb, (PREMOTEUSBQURB *)&pUrb->Dev.pvPrivate); + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return rc; +} + +static DECLCALLBACK(PVUSBURB) usbProxyVrdpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + LogFlow(("usbProxyVrdpUrbReap: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + + PVUSBURB pUrb = NULL; + uint32_t cbData = 0; + uint32_t u32Err = VUSBSTATUS_OK; + + int rc = pDevVrdp->pCallback->pfnReapURB (pDevVrdp->pDevice, cMillies, (void **)&pUrb, &cbData, &u32Err); + + LogFlow(("usbProxyVrdpUrbReap: rc = %Rrc, pUrb = %p\n", rc, pUrb)); + + if (RT_SUCCESS(rc) && pUrb) + { + pUrb->enmStatus = (VUSBSTATUS)u32Err; + pUrb->cbData = cbData; + pUrb->Dev.pvPrivate = NULL; + } + + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + { + Log(("usb-vrdp: remote device %p unplugged!!\n", pDevVrdp->pDevice)); + pProxyDev->fDetached = true; + } + + return pUrb; +} + +static DECLCALLBACK(int) usbProxyVrdpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + LogFlow(("usbProxyVrdpUrbCancel: pUrb=%p\n", pUrb)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + pDevVrdp->pCallback->pfnCancelURB (pDevVrdp->pDevice, (PREMOTEUSBQURB)pUrb->Dev.pvPrivate); + return VINF_SUCCESS; /** @todo Enhance remote interface to pass a status code. */ +} + +static DECLCALLBACK(int) usbProxyVrdpWakeup(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyVrdpWakeup: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + PUSBPROXYDEVVRDP pDevVrdp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVVRDP); + return pDevVrdp->pCallback->pfnWakeup (pDevVrdp->pDevice); +} + +/** + * The VRDP USB Proxy Backend operations. + */ +extern const USBPROXYBACK g_USBProxyDeviceVRDP = +{ + /* pszName */ + "vrdp", + /* cbBackend */ + sizeof(USBPROXYDEVVRDP), + usbProxyVrdpOpen, + NULL, + usbProxyVrdpClose, + usbProxyVrdpReset, + usbProxyVrdpSetConfig, + usbProxyVrdpClaimInterface, + usbProxyVrdpReleaseInterface, + usbProxyVrdpSetInterface, + usbProxyVrdpClearHaltedEp, + usbProxyVrdpUrbQueue, + usbProxyVrdpUrbCancel, + usbProxyVrdpUrbReap, + usbProxyVrdpWakeup, + 0 +}; + diff --git a/src/VBox/Devices/USB/win/Makefile.kup b/src/VBox/Devices/USB/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/Devices/USB/win/Makefile.kup diff --git a/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp new file mode 100644 index 00000000..60ff5976 --- /dev/null +++ b/src/VBox/Devices/USB/win/USBProxyDevice-win.cpp @@ -0,0 +1,778 @@ +/* $Id: USBProxyDevice-win.cpp $ */ +/** @file + * USBPROXY - USB proxy, Win32 backend + */ + +/* + * Copyright (C) 2006-2019 Oracle Corporation + * + * This file is part of VirtualBox Open Source Edition (OSE), as + * available from http://www.virtualbox.org. This file is free software; + * you can redistribute it and/or modify it under the terms of the GNU + * General Public License (GPL) as published by the Free Software + * Foundation, in version 2 as it comes in the "COPYING" file of the + * VirtualBox OSE distribution. VirtualBox OSE is distributed in the + * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <iprt/win/windows.h> + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/usb.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/err.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/asm.h> +#include "../USBProxyDevice.h" +#include <VBox/usblib.h> + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _QUEUED_URB +{ + PVUSBURB urb; + + USBSUP_URB urbwin; + OVERLAPPED overlapped; + DWORD cbReturned; + bool fCancelled; +} QUEUED_URB, *PQUEUED_URB; + +typedef struct +{ + /* Critical section to protect this structure. */ + RTCRITSECT CritSect; + HANDLE hDev; + uint8_t bInterfaceNumber; + bool fClaimed; + /** The allocated size of paHandles and paQueuedUrbs. */ + unsigned cAllocatedUrbs; + /** The number of URBs in the array. */ + unsigned cQueuedUrbs; + /** Array of pointers to the in-flight URB structures. */ + PQUEUED_URB *paQueuedUrbs; + /** Array of handles, this is parallel to paQueuedUrbs. */ + PHANDLE paHandles; + /* Event sempahore to wakeup the reaper thead. */ + HANDLE hEventWakeup; + /** Number of queued URBs waiting to get into the handle list. */ + unsigned cPendingUrbs; + /** Array of pending URBs. */ + PQUEUED_URB aPendingUrbs[64]; +} PRIV_USBW32, *PPRIV_USBW32; + +/* All functions are returning 1 on success, 0 on error */ + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usbProxyWinSetInterface(PUSBPROXYDEV p, int iIf, int setting); + +/** + * Converts the given Windows error code to VBox handling unplugged devices. + * + * @returns VBox status code. + * @param pProxDev The USB proxy device instance. + * @param dwErr Windows error code. + */ +static int usbProxyWinHandleUnpluggedDevice(PUSBPROXYDEV pProxyDev, DWORD dwErr) +{ +#ifdef LOG_ENABLED + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); +#endif + + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %x unplugged!!\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + else + AssertMsgFailed(("lasterr=%d\n", dwErr)); + return RTErrConvertFromWin32(dwErr); +} + +/** + * Open a USB device and create a backend instance for it. + * + * @returns VBox status code. + */ +static DECLCALLBACK(int) usbProxyWinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) +{ + RT_NOREF(pvBackend); + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + int rc = VINF_SUCCESS; + pPriv->cAllocatedUrbs = 32; + pPriv->paHandles = (PHANDLE)RTMemAllocZ(sizeof(pPriv->paHandles[0]) * pPriv->cAllocatedUrbs); + pPriv->paQueuedUrbs = (PQUEUED_URB *)RTMemAllocZ(sizeof(pPriv->paQueuedUrbs[0]) * pPriv->cAllocatedUrbs); + if ( pPriv->paQueuedUrbs + && pPriv->paHandles) + { + /* + * Open the device. + */ + pPriv->hDev = CreateFile(pszAddress, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_WRITE | FILE_SHARE_READ, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM | FILE_FLAG_OVERLAPPED, // overlapped IO + NULL); // No template file + if (pPriv->hDev != INVALID_HANDLE_VALUE) + { + Log(("usbProxyWinOpen: hDev=%p\n", pPriv->hDev)); + + /* + * Check the version + */ + USBSUP_VERSION version = {0}; + DWORD cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_GET_VERSION, NULL, 0, &version, sizeof(version), &cbReturned, NULL)) + { + if ( version.u32Major == USBDRV_MAJOR_VERSION +#if USBDRV_MINOR_VERSION != 0 + && version.u32Minor >= USBDRV_MINOR_VERSION +#endif + ) + { + USBSUP_CLAIMDEV in; + in.bInterfaceNumber = 0; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLAIM_DEVICE, &in, sizeof(in), &in, sizeof(in), &cbReturned, NULL)) + { + if (in.fClaimed) + { + pPriv->fClaimed = true; +#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */ + pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 1; +#endif + + rc = RTCritSectInit(&pPriv->CritSect); + AssertRC(rc); + pPriv->hEventWakeup = CreateEvent(NULL, FALSE, FALSE, NULL); + Assert(pPriv->hEventWakeup); + + pPriv->paHandles[0] = pPriv->hEventWakeup; + + return VINF_SUCCESS; + } + + rc = VERR_GENERAL_FAILURE; + Log(("usbproxy: unable to claim device %x (%s)!!\n", pPriv->hDev, pszAddress)); + } + } + else + { + rc = VERR_VERSION_MISMATCH; + Log(("usbproxy: Version mismatch: %d.%d != %d.%d (cur)\n", + version.u32Major, version.u32Minor, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION)); + } + } + + /* Convert last error if necessary */ + if (RT_SUCCESS(rc)) + { + DWORD dwErr = GetLastError(); + Log(("usbproxy: last error %d\n", dwErr)); + rc = RTErrConvertFromWin32(dwErr); + } + + CloseHandle(pPriv->hDev); + pPriv->hDev = INVALID_HANDLE_VALUE; + } + else + { + Log(("usbproxy: FAILED to open '%s'! last error %d\n", pszAddress, GetLastError())); + rc = VERR_FILE_NOT_FOUND; + } + } + else + rc = VERR_NO_MEMORY; + + RTMemFree(pPriv->paQueuedUrbs); + RTMemFree(pPriv->paHandles); + return rc; +} + +/** + * Copy the device and free resources associated with the backend. + */ +static DECLCALLBACK(void) usbProxyWinClose(PUSBPROXYDEV pProxyDev) +{ + /* Here we just close the device and free up p->priv + * there is no need to do anything like cancel outstanding requests + * that will have been done already + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + Assert(pPriv); + if (!pPriv) + return; + Log(("usbProxyWinClose: %p\n", pPriv->hDev)); + + if (pPriv->hDev != INVALID_HANDLE_VALUE) + { + Assert(pPriv->fClaimed); + + USBSUP_RELEASEDEV in; + DWORD cbReturned = 0; + in.bInterfaceNumber = pPriv->bInterfaceNumber; + if (!DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RELEASE_DEVICE, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + { + Log(("usbproxy: usbProxyWinClose: DeviceIoControl %#x failed with %#x!!\n", pPriv->hDev, GetLastError())); + } + if (!CloseHandle(pPriv->hDev)) + AssertLogRelMsgFailed(("usbproxy: usbProxyWinClose: CloseHandle %#x failed with %#x!!\n", pPriv->hDev, GetLastError())); + pPriv->hDev = INVALID_HANDLE_VALUE; + } + + CloseHandle(pPriv->hEventWakeup); + RTCritSectDelete(&pPriv->CritSect); + + RTMemFree(pPriv->paQueuedUrbs); + RTMemFree(pPriv->paHandles); +} + + +static DECLCALLBACK(int) usbProxyWinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + DWORD cbReturned; + int rc; + + Assert(pPriv); + + Log(("usbproxy: Reset %x\n", pPriv->hDev)); + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_RESET, NULL, 0, NULL, 0, &cbReturned, NULL)) + { +#if 0 /** @todo this needs to be enabled if windows chooses a default config. Test with the TrekStor GO Stick. */ + pProxyDev->iActiveCfg = 1; + pProxyDev->cIgnoreSetConfigs = 2; +#else + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 0; +#endif + return VINF_SUCCESS; + } + + rc = GetLastError(); + if (rc == ERROR_DEVICE_REMOVED) + { + Log(("usbproxy: device %p unplugged!!\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + return RTErrConvertFromWin32(rc); +} + +static DECLCALLBACK(int) usbProxyWinSetConfig(PUSBPROXYDEV pProxyDev, int cfg) +{ + /* Send a SET_CONFIGURATION command to the device. We don't do this + * as a normal control message, because the OS might not want to + * be left out of the loop on such a thing. + * + * It would be OK to send a SET_CONFIGURATION control URB at this + * point but it has to be synchronous. + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_SET_CONFIG in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Set config of %p to %d\n", pPriv->hDev, cfg)); + in.bConfigurationValue = cfg; + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SET_CONFIG, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +static DECLCALLBACK(int) usbProxyWinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + /* Called just before we use an interface. Needed on Linux to claim + * the interface from the OS, since even when proxying the host OS + * might want to allow other programs to use the unused interfaces. + * Not relevant for Windows. + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + pPriv->bInterfaceNumber = iIf; + + Assert(pPriv); + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) usbProxyWinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + /* The opposite of claim_interface. */ + return VINF_SUCCESS; +} + +static DECLCALLBACK(int) usbProxyWinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting) +{ + /* Select an alternate setting for an interface, the same applies + * here as for set_config, you may convert this in to a control + * message if you want but it must be synchronous + */ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_SELECT_INTERFACE in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Select interface of %x to %d/%d\n", pPriv->hDev, iIf, setting)); + in.bInterfaceNumber = iIf; + in.bAlternateSetting = setting; + + /* Here we just need to assert reset signalling on the USB device */ + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_SELECT_INTERFACE, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * Clears the halted endpoint 'ep'. + */ +static DECLCALLBACK(int) usbProxyWinClearHaltedEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Clear endpoint %d of %x\n", ep, pPriv->hDev)); + in.bEndpoint = ep; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_CLEAR_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * Aborts a pipe/endpoint (cancels all outstanding URBs on the endpoint). + */ +static int usbProxyWinAbortEndPt(PUSBPROXYDEV pProxyDev, unsigned int ep) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + Assert(pPriv); + + Log(("usbproxy: Abort endpoint %d of %x\n", ep, pPriv->hDev)); + in.bEndpoint = ep; + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + return usbProxyWinHandleUnpluggedDevice(pProxyDev, GetLastError()); +} + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyWinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + Assert(pPriv); + + /* Don't even bother if we can't wait for that many objects. */ + if (pPriv->cPendingUrbs + pPriv->cQueuedUrbs >= (MAXIMUM_WAIT_OBJECTS - 1)) + return VERR_OUT_OF_RESOURCES; + if (pPriv->cPendingUrbs >= RT_ELEMENTS(pPriv->aPendingUrbs)) + return VERR_OUT_OF_RESOURCES; + + /* + * Allocate and initialize a URB queue structure. + */ + /** @todo pool these */ + PQUEUED_URB pQUrbWin = (PQUEUED_URB)RTMemAllocZ(sizeof(QUEUED_URB)); + if (!pQUrbWin) + return VERR_NO_MEMORY; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_CTRL; break; /* you won't ever see these */ + case VUSBXFERTYPE_ISOC: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_ISOC; + pQUrbWin->urbwin.numIsoPkts = pUrb->cIsocPkts; + for (unsigned i = 0; i < pUrb->cIsocPkts; ++i) + { + pQUrbWin->urbwin.aIsoPkts[i].cb = pUrb->aIsocPkts[i].cb; + pQUrbWin->urbwin.aIsoPkts[i].off = pUrb->aIsocPkts[i].off; + pQUrbWin->urbwin.aIsoPkts[i].stat = USBSUP_XFER_OK; + } + break; + case VUSBXFERTYPE_BULK: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_BULK; break; + case VUSBXFERTYPE_INTR: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_INTR; break; + case VUSBXFERTYPE_MSG: pQUrbWin->urbwin.type = USBSUP_TRANSFER_TYPE_MSG; break; + default: + AssertMsgFailed(("Invalid type %d\n", pUrb->enmType)); + return VERR_INVALID_PARAMETER; + } + + switch (pUrb->enmDir) + { + case VUSBDIRECTION_SETUP: + AssertFailed(); + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_SETUP; + break; + case VUSBDIRECTION_IN: + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_IN; + break; + case VUSBDIRECTION_OUT: + pQUrbWin->urbwin.dir = USBSUP_DIRECTION_OUT; + break; + default: + AssertMsgFailed(("Invalid direction %d\n", pUrb->enmDir)); + return VERR_INVALID_PARAMETER; + } + + Log(("usbproxy: Queue URB %p ep=%d cbData=%d abData=%p cIsocPkts=%d\n", pUrb, pUrb->EndPt, pUrb->cbData, pUrb->abData, pUrb->cIsocPkts)); + + pQUrbWin->urb = pUrb; + pQUrbWin->urbwin.ep = pUrb->EndPt; + pQUrbWin->urbwin.len = pUrb->cbData; + pQUrbWin->urbwin.buf = pUrb->abData; + pQUrbWin->urbwin.error = USBSUP_XFER_OK; + pQUrbWin->urbwin.flags = USBSUP_FLAG_NONE; + if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk) + pQUrbWin->urbwin.flags = USBSUP_FLAG_SHORT_OK; + + int rc = VINF_SUCCESS; + pQUrbWin->overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE) + { + pUrb->Dev.pvPrivate = pQUrbWin; + + if ( DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_SEND_URB, + &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin), + &pQUrbWin->urbwin, sizeof(pQUrbWin->urbwin), + &pQUrbWin->cbReturned, &pQUrbWin->overlapped) + || GetLastError() == ERROR_IO_PENDING) + { + /* insert into the queue */ + RTCritSectEnter(&pPriv->CritSect); + unsigned j = pPriv->cPendingUrbs; + Assert(j < RT_ELEMENTS(pPriv->aPendingUrbs)); + pPriv->aPendingUrbs[j] = pQUrbWin; + pPriv->cPendingUrbs++; + RTCritSectLeave(&pPriv->CritSect); + SetEvent(pPriv->hEventWakeup); + return VINF_SUCCESS; + } + else + { + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %p unplugged!!\n", pPriv->hDev)); + pProxyDev->fDetached = true; + } + else + AssertMsgFailed(("dwErr=%X urbwin.error=%d (submit urb)\n", dwErr, pQUrbWin->urbwin.error)); + rc = RTErrConvertFromWin32(dwErr); + CloseHandle(pQUrbWin->overlapped.hEvent); + pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE; + } + } +#ifdef DEBUG_misha + else + { + AssertMsgFailed(("FAILED!!, hEvent(0x%p)\n", pQUrbWin->overlapped.hEvent)); + rc = VERR_NO_MEMORY; + } +#endif + + Assert(pQUrbWin->overlapped.hEvent == INVALID_HANDLE_VALUE); + RTMemFree(pQUrbWin); + return rc; +} + +/** + * Convert Windows proxy URB status to VUSB status. + * + * @returns VUSB status constant. + * @param win_status Windows USB proxy status constant. + */ +static VUSBSTATUS usbProxyWinStatusToVUsbStatus(USBSUP_ERROR win_status) +{ + VUSBSTATUS vusb_status; + + switch (win_status) + { + case USBSUP_XFER_OK: vusb_status = VUSBSTATUS_OK; break; + case USBSUP_XFER_STALL: vusb_status = VUSBSTATUS_STALL; break; + case USBSUP_XFER_DNR: vusb_status = VUSBSTATUS_DNR; break; + case USBSUP_XFER_CRC: vusb_status = VUSBSTATUS_CRC; break; + case USBSUP_XFER_NAC: vusb_status = VUSBSTATUS_NOT_ACCESSED; break; + case USBSUP_XFER_UNDERRUN: vusb_status = VUSBSTATUS_DATA_UNDERRUN; break; + case USBSUP_XFER_OVERRUN: vusb_status = VUSBSTATUS_DATA_OVERRUN; break; + default: + AssertMsgFailed(("USB: Invalid error %d\n", win_status)); + vusb_status = VUSBSTATUS_DNR; + break; + } + return vusb_status; +} + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not + * wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyWinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + AssertReturn(pPriv, NULL); + + /* + * There are some unnecessary calls, just return immediately or + * WaitForMultipleObjects will fail. + */ + if ( pPriv->cQueuedUrbs <= 0 + && pPriv->cPendingUrbs == 0) + { + if ( cMillies != 0 + && pPriv->cPendingUrbs == 0) + { + /* Wait for the wakeup call. */ + DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies; + DWORD dwRc = WaitForMultipleObjects(1, &pPriv->hEventWakeup, FALSE, cMilliesWait); + NOREF(dwRc); + } + + return NULL; + } + +again: + /* Check for pending URBs. */ + if (pPriv->cPendingUrbs) + { + RTCritSectEnter(&pPriv->CritSect); + + /* Ensure we've got sufficient space in the arrays. */ + if (pPriv->cQueuedUrbs + pPriv->cPendingUrbs + 1 > pPriv->cAllocatedUrbs) + { + unsigned cNewMax = pPriv->cAllocatedUrbs + pPriv->cPendingUrbs + 1; + void *pv = RTMemRealloc(pPriv->paHandles, sizeof(pPriv->paHandles[0]) * (cNewMax + 1)); /* One extra for the wakeup event. */ + if (!pv) + { + AssertMsgFailed(("RTMemRealloc failed for paHandles[%d]", cNewMax)); + //break; + } + pPriv->paHandles = (PHANDLE)pv; + + pv = RTMemRealloc(pPriv->paQueuedUrbs, sizeof(pPriv->paQueuedUrbs[0]) * cNewMax); + if (!pv) + { + AssertMsgFailed(("RTMemRealloc failed for paQueuedUrbs[%d]", cNewMax)); + //break; + } + pPriv->paQueuedUrbs = (PQUEUED_URB *)pv; + pPriv->cAllocatedUrbs = cNewMax; + } + + /* Copy the pending URBs over. */ + for (unsigned i = 0; i < pPriv->cPendingUrbs; i++) + { + pPriv->paHandles[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i]->overlapped.hEvent; + pPriv->paQueuedUrbs[pPriv->cQueuedUrbs + i] = pPriv->aPendingUrbs[i]; + } + pPriv->cQueuedUrbs += pPriv->cPendingUrbs; + pPriv->cPendingUrbs = 0; + pPriv->paHandles[pPriv->cQueuedUrbs] = pPriv->hEventWakeup; + pPriv->paHandles[pPriv->cQueuedUrbs + 1] = INVALID_HANDLE_VALUE; + + RTCritSectLeave(&pPriv->CritSect); + } + + /* + * Wait/poll. + * + * ASSUMPTIONS: + * 1. The usbProxyWinUrbReap can not be run concurrently with each other + * so racing the cQueuedUrbs access/modification can not occur. + * 2. The usbProxyWinUrbReap can not be run concurrently with + * usbProxyWinUrbQueue so they can not race the pPriv->paHandles + * access/realloc. + */ + unsigned cQueuedUrbs = ASMAtomicReadU32((volatile uint32_t *)&pPriv->cQueuedUrbs); + DWORD cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? INFINITE : cMillies; + PVUSBURB pUrb = NULL; + DWORD rc = WaitForMultipleObjects(cQueuedUrbs + 1, pPriv->paHandles, FALSE, cMilliesWait); + + /* If the wakeup event fired return immediately. */ + if (rc == WAIT_OBJECT_0 + cQueuedUrbs) + { + if (pPriv->cPendingUrbs) + goto again; + return NULL; + } + + AssertCompile(WAIT_OBJECT_0 == 0); + if (/*rc >= WAIT_OBJECT_0 && */ rc < WAIT_OBJECT_0 + cQueuedUrbs) + { + RTCritSectEnter(&pPriv->CritSect); + unsigned iUrb = rc - WAIT_OBJECT_0; + PQUEUED_URB pQUrbWin = pPriv->paQueuedUrbs[iUrb]; + pUrb = pQUrbWin->urb; + + /* + * Remove it from the arrays. + */ + cQueuedUrbs = --pPriv->cQueuedUrbs; + if (cQueuedUrbs != iUrb) + { + /* Move the array forward */ + for (unsigned i=iUrb;i<cQueuedUrbs;i++) + { + pPriv->paHandles[i] = pPriv->paHandles[i+1]; + pPriv->paQueuedUrbs[i] = pPriv->paQueuedUrbs[i+1]; + } + } + pPriv->paHandles[cQueuedUrbs] = pPriv->hEventWakeup; + pPriv->paHandles[cQueuedUrbs + 1] = INVALID_HANDLE_VALUE; + pPriv->paQueuedUrbs[cQueuedUrbs] = NULL; + RTCritSectLeave(&pPriv->CritSect); + Assert(cQueuedUrbs == pPriv->cQueuedUrbs); + + /* + * Update the urb. + */ + pUrb->enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.error); + pUrb->cbData = (uint32_t)pQUrbWin->urbwin.len; + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; ++i) + { + /* NB: Windows won't change the packet offsets, but the packets may + * be only partially filled or completely empty. + */ + pUrb->aIsocPkts[i].enmStatus = usbProxyWinStatusToVUsbStatus(pQUrbWin->urbwin.aIsoPkts[i].stat); + pUrb->aIsocPkts[i].cb = pQUrbWin->urbwin.aIsoPkts[i].cb; + } + } + Log(("usbproxy: pUrb=%p (#%d) ep=%d cbData=%d status=%d cIsocPkts=%d ready\n", + pUrb, rc - WAIT_OBJECT_0, pQUrbWin->urb->EndPt, pQUrbWin->urb->cbData, pUrb->enmStatus, pUrb->cIsocPkts)); + + /* free the urb queuing structure */ + if (pQUrbWin->overlapped.hEvent != INVALID_HANDLE_VALUE) + { + CloseHandle(pQUrbWin->overlapped.hEvent); + pQUrbWin->overlapped.hEvent = INVALID_HANDLE_VALUE; + } + RTMemFree(pQUrbWin); + } + else if ( rc == WAIT_FAILED + || (rc >= WAIT_ABANDONED_0 && rc < WAIT_ABANDONED_0 + cQueuedUrbs)) + AssertMsgFailed(("USB: WaitForMultipleObjects %d objects failed with rc=%d and last error %d\n", cQueuedUrbs, rc, GetLastError())); + + return pUrb; +} + + +/** + * Cancels an in-flight URB. + * + * The URB requires reaping, so we don't change its state. + * + * @remark There isn't a way to cancel a specific URB on Windows. + * on darwin. The interface only supports the aborting of + * all URBs pending on an endpoint. Luckily that is usually + * exactly what the guest wants to do. + */ +static DECLCALLBACK(int) usbProxyWinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + PQUEUED_URB pQUrbWin = (PQUEUED_URB)pUrb->Dev.pvPrivate; + USBSUP_CLEAR_ENDPOINT in; + DWORD cbReturned; + + AssertPtrReturn(pQUrbWin, VERR_INVALID_PARAMETER); + + in.bEndpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + Log(("usbproxy: Cancel urb %p, endpoint %x\n", pUrb, in.bEndpoint)); + + cbReturned = 0; + if (DeviceIoControl(pPriv->hDev, SUPUSB_IOCTL_USB_ABORT_ENDPOINT, &in, sizeof(in), NULL, 0, &cbReturned, NULL)) + return VINF_SUCCESS; + + DWORD dwErr = GetLastError(); + if ( dwErr == ERROR_INVALID_HANDLE_STATE + || dwErr == ERROR_BAD_COMMAND) + { + Log(("usbproxy: device %x unplugged!!\n", pPriv->hDev)); + pProxyDev->fDetached = true; + return VINF_SUCCESS; /* Fake success and deal with the unplugged device elsewhere. */ + } + + AssertMsgFailed(("lastErr=%ld\n", dwErr)); + return RTErrConvertFromWin32(dwErr); +} + +static DECLCALLBACK(int) usbProxyWinWakeup(PUSBPROXYDEV pProxyDev) +{ + PPRIV_USBW32 pPriv = USBPROXYDEV_2_DATA(pProxyDev, PPRIV_USBW32); + + SetEvent(pPriv->hEventWakeup); + return VINF_SUCCESS; +} + +/** + * The Win32 USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(PRIV_USBW32), + usbProxyWinOpen, + NULL, + usbProxyWinClose, + usbProxyWinReset, + usbProxyWinSetConfig, + usbProxyWinClaimInterface, + usbProxyWinReleaseInterface, + usbProxyWinSetInterface, + usbProxyWinClearHaltedEndPt, + usbProxyWinUrbQueue, + usbProxyWinUrbCancel, + usbProxyWinUrbReap, + usbProxyWinWakeup, + 0 +}; + |