From f215e02bf85f68d3a6106c2a1f4f7f063f819064 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 11 Apr 2024 10:17:27 +0200 Subject: Adding upstream version 7.0.14-dfsg. Signed-off-by: Daniel Baumann --- src/VBox/Devices/USB/darwin/Makefile.kup | 0 .../Devices/USB/darwin/USBProxyDevice-darwin.cpp | 2013 ++++++++++++++++++++ 2 files changed, 2013 insertions(+) create mode 100644 src/VBox/Devices/USB/darwin/Makefile.kup create mode 100644 src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp (limited to 'src/VBox/Devices/USB/darwin') 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 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..e76eb5f8 --- /dev/null +++ b/src/VBox/Devices/USB/darwin/USBProxyDevice-darwin.cpp @@ -0,0 +1,2013 @@ +/* $Id: USBProxyDevice-darwin.cpp $ */ +/** @file + * USB device proxy - the Darwin backend. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#define __STDC_LIMIT_MACROS +#define __STDC_CONSTANT_MACROS + +#include +#include +#include +#include +#include +#include +#ifndef __MAC_10_10 /* Quick hack: The following two masks appeared in 10.10. */ +# define kUSBReEnumerateReleaseDeviceMask RT_BIT_32(29) +# define kUSBReEnumerateCaptureDeviceMask RT_BIT_32(30) +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../USBProxyDevice.h" +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** An experiment... */ +//#define USE_LOW_LATENCY_API 1 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Forward declaration of the Darwin interface structure. */ +typedef struct USBPROXYIFOSX *PUSBPROXYIFOSX; + + +/** + * A low latency isochronous buffer. + * + * These are allocated in chunks on an interface level, see USBPROXYISOCBUFCOL. + */ +typedef struct USBPROXYISOCBUF +{ + /** Whether this buffer is in use or not. */ + bool volatile fUsed; + /** Pointer to the buffer. */ + void *pvBuf; + /** Pointer to an array of 8 frames. */ + IOUSBLowLatencyIsocFrame *paFrames; +} USBPROXYISOCBUF, *PUSBPROXYISOCBUF; + + +/** + * Isochronous buffer collection (associated with an interface). + * + * These are allocated in decent sized chunks and there isn't supposed + * to be too many of these per interface. + */ +typedef struct USBPROXYISOCBUFCOL +{ + /** Write or Read buffers? */ + USBLowLatencyBufferType enmType; + /** The next buffer collection on this interface. */ + struct USBPROXYISOCBUFCOL *pNext; + /** The buffer. */ + void *pvBuffer; + /** The frame. */ + void *pvFrames; + /** The buffers. + * The number of buffers here is decided by pvFrame begin allocated in + * GUEST_PAGE_SIZE chunks. The size of IOUSBLowLatencyIsocFrame is 16 bytes + * and we require 8 of those per buffer. GUEST_PAGE_SIZE / (16 * 8) = 32. + * @remarks Don't allocate too many as it may temporarily halt the system if + * some pool is low / exhausted. (Contiguous memory woes on mach.) + */ + USBPROXYISOCBUF aBuffers[/*32*/ 4]; +} USBPROXYISOCBUFCOL, *PUSBPROXYISOCBUFCOL; + +AssertCompileSize(IOUSBLowLatencyIsocFrame, 16); + +/** + * Per-urb data for the Darwin usb proxy backend. + * + * This is required to track in-flight and landed URBs + * since we take down the URBs in a different thread (perhaps). + */ +typedef struct USBPROXYURBOSX +{ + /** Pointer to the next Darwin URB. */ + struct USBPROXYURBOSX *pNext; + /** Pointer to the previous Darwin URB. */ + struct USBPROXYURBOSX *pPrev; + /** The millisecond timestamp when this URB was submitted. */ + uint64_t u64SubmitTS; + /** Pointer to the VUSB URB. + * This is set to NULL if canceled. */ + PVUSBURB pVUsbUrb; + /** Pointer to the Darwin device. */ + struct USBPROXYDEVOSX *pDevOsX; + /** The transfer type. */ + VUSBXFERTYPE enmType; + /** Union with data depending on transfer type. */ + union + { + /** The control message. */ + IOUSBDevRequest ControlMsg; + /** The Isochronous Data. */ + struct + { +#ifdef USE_LOW_LATENCY_API + /** The low latency isochronous buffer. */ + PUSBPROXYISOCBUF pBuf; + /** Array of frames parallel to the one in VUSBURB. (Same as pBuf->paFrames.) */ + IOUSBLowLatencyIsocFrame *aFrames; +#else + /** Array of frames parallel to the one in VUSBURB. */ + IOUSBIsocFrame aFrames[8]; +#endif + } Isoc; + } u; +} USBPROXYURBOSX, *PUSBPROXYURBOSX; + +/** + * Per-pipe data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYPIPEOSX +{ + /** The endpoint number. */ + uint8_t u8Endpoint; + /** The IOKit pipe reference. */ + uint8_t u8PipeRef; + /** The pipe Transfer type type. */ + uint8_t u8TransferType; + /** The pipe direction. */ + uint8_t u8Direction; + /** The endpoint interval. (interrupt) */ + uint8_t u8Interval; + /** Full-speed device indicator (isochronous pipes only). */ + bool fIsFullSpeed; + /** The max packet size. */ + uint16_t u16MaxPacketSize; + /** The next frame number (isochronous pipes only). */ + uint64_t u64NextFrameNo; +} USBPROXYPIPEOSX, *PUSBPROXYPIPEOSX, **PPUSBPROXYPIPEOSX; + +typedef struct RUNLOOPREFLIST +{ + RTLISTNODE List; + CFRunLoopRef RunLoopRef; +} RUNLOOPREFLIST, *PRUNLOOPREFLIST; +typedef RUNLOOPREFLIST **PPRUNLOOPREFLIST; + +/** + * Per-interface data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYIFOSX +{ + /** Pointer to the next interface. */ + struct USBPROXYIFOSX *pNext; + /** The interface number. */ + uint8_t u8Interface; + /** The current alternative interface setting. + * This is used to skip unnecessary SetAltInterface calls. */ + uint8_t u8AltSetting; + /** The interface class. (not really used) */ + uint8_t u8Class; + /** The interface protocol. (not really used) */ + uint8_t u8Protocol; + /** The number of pipes. */ + uint8_t cPipes; + /** Array containing all the pipes. (Currently unsorted.) */ + USBPROXYPIPEOSX aPipes[kUSBMaxPipes]; + /** The IOUSBDeviceInterface. */ + IOUSBInterfaceInterface245 **ppIfI; + /** The run loop source for the async operations on the interface level. */ + CFRunLoopSourceRef RunLoopSrcRef; + /** List of isochronous buffer collections. + * These are allocated on demand by the URB queuing routine and then recycled until the interface is destroyed. */ + RTLISTANCHOR HeadOfRunLoopLst; + PUSBPROXYISOCBUFCOL pIsocBufCols; +} USBPROXYIFOSX, *PUSBPROXYIFOSX, **PPUSBPROXYIFOSX; +/** Pointer to a pointer to an darwin interface. */ +typedef USBPROXYIFOSX **PPUSBPROXYIFOSX; + +/** + * Per-device Data for the Darwin usb proxy backend. + */ +typedef struct USBPROXYDEVOSX +{ + /** The USB Device IOService object. */ + io_object_t USBDevice; + /** The IOUSBDeviceInterface. */ + IOUSBDeviceInterface245 **ppDevI; + /** The run loop source for the async operations on the device level + * (i.e. the default control pipe stuff). */ + CFRunLoopSourceRef RunLoopSrcRef; + /** we want to add and remove RunLoopSourceRefs to run loop's of + * every EMT thread participated in USB processing. */ + RTLISTANCHOR HeadOfRunLoopLst; + /** Pointer to the proxy device instance. */ + PUSBPROXYDEV pProxyDev; + + /** Pointer to the first interface. */ + PUSBPROXYIFOSX pIfHead; + /** Pointer to the last interface. */ + PUSBPROXYIFOSX pIfTail; + + /** Critical section protecting the lists. */ + RTCRITSECT CritSect; + /** The list of free Darwin URBs. Singly linked. */ + PUSBPROXYURBOSX pFreeHead; + /** The list of landed Darwin URBs. Doubly linked. + * Only the split head will appear in this list. */ + PUSBPROXYURBOSX pTaxingHead; + /** The tail of the landed Darwin URBs. */ + PUSBPROXYURBOSX pTaxingTail; + /** Last reaper runloop reference, there can be only one runloop at a time. */ + CFRunLoopRef hRunLoopReapingLast; + /** Runloop source for waking up the reaper thread. */ + CFRunLoopSourceRef hRunLoopSrcWakeRef; + /** List of threads used for reaping which can be woken up. */ + RTLISTANCHOR HeadOfRunLoopWakeLst; + /** Runloop reference of the thread reaping. */ + volatile CFRunLoopRef hRunLoopReaping; + /** Flag whether the reaping thread is about the be waked. */ + volatile bool fReapingThreadWake; +} USBPROXYDEVOSX, *PUSBPROXYDEVOSX; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static RTONCE g_usbProxyDarwinOnce = RTONCE_INITIALIZER; +/** The runloop mode we use. + * Since it's difficult to remove this, we leak it to prevent crashes. + * @bugref{4407} */ +static CFStringRef g_pRunLoopMode = NULL; +/** The IO Master Port. + * Not worth cleaning up. */ +static mach_port_t g_MasterPort = MACH_PORT_NULL; + + +/** + * Init once callback that sets up g_MasterPort and g_pRunLoopMode. + * + * @returns IPRT status code. + * + * @param pvUser1 NULL, ignored. + */ +static DECLCALLBACK(int32_t) usbProxyDarwinInitOnce(void *pvUser1) +{ + RT_NOREF(pvUser1); + + int rc; + kern_return_t krc = IOMasterPort(MACH_PORT_NULL, &g_MasterPort); + if (krc == KERN_SUCCESS) + { + g_pRunLoopMode = CFStringCreateWithCString(kCFAllocatorDefault, "VBoxUsbProxyMode", kCFStringEncodingUTF8); + if (g_pRunLoopMode) + return VINF_SUCCESS; + rc = VERR_INTERNAL_ERROR_5; + } + else + rc = RTErrConvertFromDarwin(krc); + return rc; +} + +/** + * Kicks the reaper thread if it sleeps currently to respond to state changes + * or to pick up completed URBs. + * + * @param pDevOsX The darwin device instance data. + */ +static void usbProxyDarwinReaperKick(PUSBPROXYDEVOSX pDevOsX) +{ + CFRunLoopRef hRunLoopWake = (CFRunLoopRef)ASMAtomicReadPtr((void * volatile *)&pDevOsX->hRunLoopReaping); + if (hRunLoopWake) + { + LogFlowFunc(("Waking runloop %p\n", hRunLoopWake)); + CFRunLoopSourceSignal(pDevOsX->hRunLoopSrcWakeRef); + CFRunLoopWakeUp(hRunLoopWake); + } +} + +/** + * Adds Source ref to current run loop and adds it the list of runloops. + */ +static int usbProxyDarwinAddRunLoopRef(PRTLISTANCHOR pListHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pListHead, VERR_INVALID_PARAMETER); + AssertReturn(CFRunLoopSourceIsValid(SourceRef), VERR_INVALID_PARAMETER); + + if (CFRunLoopContainsSource(CFRunLoopGetCurrent(), SourceRef, g_pRunLoopMode)) + return VINF_SUCCESS; + + /* Add to the list */ + PRUNLOOPREFLIST pListNode = (PRUNLOOPREFLIST)RTMemAllocZ(sizeof(RUNLOOPREFLIST)); + if (!pListNode) + return VERR_NO_MEMORY; + + pListNode->RunLoopRef = CFRunLoopGetCurrent(); + + CFRetain(pListNode->RunLoopRef); + CFRetain(SourceRef); /* We want to be aware of releasing */ + + CFRunLoopAddSource(pListNode->RunLoopRef, SourceRef, g_pRunLoopMode); + + RTListInit(&pListNode->List); + + RTListAppend((PRTLISTNODE)pListHead, &pListNode->List); + + return VINF_SUCCESS; +} + + +/* + * Removes all source reference from mode of run loop's we've registered them. + * + */ +static int usbProxyDarwinRemoveSourceRefFromAllRunLoops(PRTLISTANCHOR pHead, + CFRunLoopSourceRef SourceRef) +{ + AssertPtrReturn(pHead, VERR_INVALID_PARAMETER); + + while (!RTListIsEmpty(pHead)) + { + PRUNLOOPREFLIST pNode = RTListGetFirst(pHead, RUNLOOPREFLIST, List); + /* XXX: Should Release Reference? */ + Assert(CFGetRetainCount(pNode->RunLoopRef)); + + CFRunLoopRemoveSource(pNode->RunLoopRef, SourceRef, g_pRunLoopMode); + CFRelease(SourceRef); + CFRelease(pNode->RunLoopRef); + + RTListNodeRemove(&pNode->List); + + RTMemFree(pNode); + } + + return VINF_SUCCESS; +} + + +/** + * Allocates a Darwin URB request structure. + * + * @returns Pointer to an active URB request. + * @returns NULL on failure. + * + * @param pDevOsX The darwin proxy device. + */ +static PUSBPROXYURBOSX usbProxyDarwinUrbAlloc(PUSBPROXYDEVOSX pDevOsX) +{ + PUSBPROXYURBOSX pUrbOsX; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Try remove a Darwin URB from the free list, if none there allocate a new one. + */ + pUrbOsX = pDevOsX->pFreeHead; + if (pUrbOsX) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTCritSectLeave(&pDevOsX->CritSect); + } + else + { + RTCritSectLeave(&pDevOsX->CritSect); + pUrbOsX = (PUSBPROXYURBOSX)RTMemAlloc(sizeof(*pUrbOsX)); + if (!pUrbOsX) + return NULL; + } + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + return pUrbOsX; +} + + +#ifdef USE_LOW_LATENCY_API +/** + * Allocates an low latency isochronous buffer. + * + * @returns VBox status code. + * @param pUrbOsX The OsX URB to allocate it for. + * @param pIf The interface to allocated it from. + */ +static int usbProxyDarwinUrbAllocIsocBuf(PUSBPROXYURBOSX pUrbOsX, PUSBPROXYIFOSX pIf) +{ + USBLowLatencyBufferType enmLLType = pUrbOsX->pVUsbUrb->enmDir == VUSBDIRECTION_IN + ? kUSBLowLatencyWriteBuffer : kUSBLowLatencyReadBuffer; + + /* + * Walk the buffer collection list and look for an unused one. + */ + pUrbOsX->u.Isoc.pBuf = NULL; + for (PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; pCur; pCur = pCur->pNext) + if (pCur->enmType == enmLLType) + for (unsigned i = 0; i < RT_ELEMENTS(pCur->aBuffers); i++) + if (!pCur->aBuffers[i].fUsed) + { + pCur->aBuffers[i].fUsed = true; + pUrbOsX->u.Isoc.pBuf = &pCur->aBuffers[i]; + AssertPtr(pUrbOsX->u.Isoc.pBuf); + AssertPtr(pUrbOsX->u.Isoc.pBuf->pvBuf); + pUrbOsX->u.Isoc.aFrames = pCur->aBuffers[i].paFrames; + AssertPtr(pUrbOsX->u.Isoc.aFrames); + return VINF_SUCCESS; + } + + /* + * Didn't find an empty one, create a new buffer collection and take the first buffer. + */ + PUSBPROXYISOCBUFCOL pNew = (PUSBPROXYISOCBUFCOL)RTMemAllocZ(sizeof(*pNew)); + AssertReturn(pNew, VERR_NO_MEMORY); + + IOReturn irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvBuffer, 8192 * RT_ELEMENTS(pNew->aBuffers), enmLLType); + if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvBuffer)) + { + AssertPtr(pNew->pvBuffer); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + /** @todo GUEST_PAGE_SIZE or HOST_PAGE_SIZE or just 4K? */ + irc = (*pIf->ppIfI)->LowLatencyCreateBuffer(pIf->ppIfI, &pNew->pvFrames, GUEST_PAGE_SIZE, kUSBLowLatencyFrameListBuffer); + if ((irc == kIOReturnSuccess) != RT_VALID_PTR(pNew->pvFrames)) + { + AssertPtr(pNew->pvFrames); + irc = kIOReturnNoMemory; + } + if (irc == kIOReturnSuccess) + { + for (unsigned i = 0; i < RT_ELEMENTS(pNew->aBuffers); i++) + { + //pNew->aBuffers[i].fUsed = false; + pNew->aBuffers[i].paFrames = &((IOUSBLowLatencyIsocFrame *)pNew->pvFrames)[i * 8]; + pNew->aBuffers[i].pvBuf = (uint8_t *)pNew->pvBuffer + i * 8192; + } + + pNew->aBuffers[0].fUsed = true; + pUrbOsX->u.Isoc.aFrames = pNew->aBuffers[0].paFrames; + pUrbOsX->u.Isoc.pBuf = &pNew->aBuffers[0]; + + pNew->enmType = enmLLType; + pNew->pNext = pIf->pIsocBufCols; + pIf->pIsocBufCols = pNew; + +#if 0 /* doesn't help :-/ */ + /* + * If this is the first time we're here, try mess with the policy? + */ + if (!pNew->pNext) + for (unsigned iPipe = 0; iPipe < pIf->cPipes; iPipe++) + if (pIf->aPipes[iPipe].u8TransferType == kUSBIsoc) + { + irc = (*pIf->ppIfI)->SetPipePolicy(pIf->ppIfI, pIf->aPipes[iPipe].u8PipeRef, + pIf->aPipes[iPipe].u16MaxPacketSize, pIf->aPipes[iPipe].u8Interval); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } +#endif + + return VINF_SUCCESS; + } + + /* bail out */ + (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pNew->pvBuffer); + } + AssertMsgFailed(("%#x\n", irc)); + RTMemFree(pNew); + + return RTErrConvertFromDarwin(irc); +} +#endif /* USE_LOW_LATENCY_API */ + + +/** + * Frees a Darwin URB request structure. + * + * @param pDevOsX The darwin proxy device. + * @param pUrbOsX The Darwin URB to free. + */ +static void usbProxyDarwinUrbFree(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYURBOSX pUrbOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + +#ifdef USE_LOW_LATENCY_API + /* + * Free low latency stuff. + */ + if ( pUrbOsX->enmType == VUSBXFERTYPE_ISOC + && pUrbOsX->u.Isoc.pBuf) + { + pUrbOsX->u.Isoc.pBuf->fUsed = false; + pUrbOsX->u.Isoc.pBuf = NULL; + } +#endif + + /* + * Link it into the free list. + */ + pUrbOsX->pPrev = NULL; + pUrbOsX->pNext = pDevOsX->pFreeHead; + pDevOsX->pFreeHead = pUrbOsX; + + pUrbOsX->pVUsbUrb = NULL; + pUrbOsX->pDevOsX = NULL; + pUrbOsX->enmType = VUSBXFERTYPE_INVALID; + + RTCritSectLeave(&pDevOsX->CritSect); +} + +/** + * Translate the IOKit status code to a VUSB status. + * + * @returns VUSB URB status code. + * @param irc IOKit status code. + */ +static VUSBSTATUS vusbProxyDarwinStatusToVUsbStatus(IOReturn irc) +{ + switch (irc) + { + /* IOKit OHCI VUSB */ + case kIOReturnSuccess: /* 0 */ return VUSBSTATUS_OK; + case kIOUSBCRCErr: /* 1 */ return VUSBSTATUS_CRC; + //case kIOUSBBitstufErr: /* 2 */ return VUSBSTATUS_; + //case kIOUSBDataToggleErr: /* 3 */ return VUSBSTATUS_; + case kIOUSBPipeStalled: /* 4 */ return VUSBSTATUS_STALL; + case kIOReturnNotResponding: /* 5 */ return VUSBSTATUS_DNR; + //case kIOUSBPIDCheckErr: /* 6 */ return VUSBSTATUS_; + //case kIOUSBWrongPIDErr: /* 7 */ return VUSBSTATUS_; + case kIOReturnOverrun: /* 8 */ return VUSBSTATUS_DATA_OVERRUN; + case kIOReturnUnderrun: /* 9 */ return VUSBSTATUS_DATA_UNDERRUN; + //case kIOUSBReserved1Err: /* 10 */ return VUSBSTATUS_; + //case kIOUSBReserved2Err: /* 11 */ return VUSBSTATUS_; + //case kIOUSBBufferOverrunErr: /* 12 */ return VUSBSTATUS_; + //case kIOUSBBufferUnderrunErr: /* 13 */ return VUSBSTATUS_; + case kIOUSBNotSent1Err: /* 14 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + case kIOUSBNotSent2Err: /* 15 */ return VUSBSTATUS_NOT_ACCESSED/*VUSBSTATUS_OK*/; + + /* Other errors */ + case kIOUSBTransactionTimeout: return VUSBSTATUS_DNR; + //case kIOReturnAborted: return VUSBSTATUS_CRC; - see on SET_INTERFACE... + + default: + Log(("vusbProxyDarwinStatusToVUsbStatus: irc=%#x!!\n", irc)); + return VUSBSTATUS_STALL; + } +} + + +/** + * Completion callback for an async URB transfer. + * + * @param pvUrbOsX The Darwin URB. + * @param irc The status of the operation. + * @param Size Possible the transfer size. + */ +static void usbProxyDarwinUrbAsyncComplete(void *pvUrbOsX, IOReturn irc, void *Size) +{ + PUSBPROXYURBOSX pUrbOsX = (PUSBPROXYURBOSX)pvUrbOsX; + PUSBPROXYDEVOSX pDevOsX = pUrbOsX->pDevOsX; + const uint32_t cb = (uintptr_t)Size; + + /* + * Do status updates. + */ + PVUSBURB pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + Assert(pUrb->u32Magic == VUSBURB_MAGIC); + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { +#ifdef USE_LOW_LATENCY_API + /* copy the data. */ + //if (pUrb->enmDir == VUSBDIRECTION_IN) + memcpy(pUrb->abData, pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->cbData); +#endif + Log3(("AsyncComplete isoc - raw data (%d bytes):\n" + "%16.*Rhxd\n", pUrb->cbData, pUrb->cbData, pUrb->abData)); + uint32_t off = 0; + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { +#ifdef USE_LOW_LATENCY_API + Log2((" %d{%d/%d-%x-%RX64}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus, + RT_MAKE_U64(pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo, pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi) )); +#else + Log2((" %d{%d/%d-%x}", i, pUrbOsX->u.Isoc.aFrames[i].frActCount, pUrb->aIsocPkts[i].cb, pUrbOsX->u.Isoc.aFrames[i].frStatus)); +#endif + pUrb->aIsocPkts[i].enmStatus = vusbProxyDarwinStatusToVUsbStatus(pUrbOsX->u.Isoc.aFrames[i].frStatus); + pUrb->aIsocPkts[i].cb = pUrbOsX->u.Isoc.aFrames[i].frActCount; + off += pUrbOsX->u.Isoc.aFrames[i].frActCount; + } + Log2(("\n")); +#if 0 /** @todo revisit this, wasn't working previously. */ + for (int i = (int)pUrb->cIsocPkts - 1; i >= 0; i--) + Assert( !pUrbOsX->u.Isoc.aFrames[i].frActCount + && !pUrbOsX->u.Isoc.aFrames[i].frReqCount + && !pUrbOsX->u.Isoc.aFrames[i].frStatus); +#endif + pUrb->cbData = off; /* 'Size' seems to be pointing at an error code or something... */ + pUrb->enmStatus = VUSBSTATUS_OK; /* Don't use 'irc'. OHCI expects OK unless it's a really bad error. */ + } + else + { + pUrb->cbData = cb; + pUrb->enmStatus = vusbProxyDarwinStatusToVUsbStatus(irc); + if (pUrb->enmType == VUSBXFERTYPE_MSG) + pUrb->cbData += sizeof(VUSBSETUP); + } + } + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Link it into the taxing list. + */ + pUrbOsX->pNext = NULL; + pUrbOsX->pPrev = pDevOsX->pTaxingTail; + if (pDevOsX->pTaxingTail) + pDevOsX->pTaxingTail->pNext = pUrbOsX; + else + pDevOsX->pTaxingHead = pUrbOsX; + pDevOsX->pTaxingTail = pUrbOsX; + + RTCritSectLeave(&pDevOsX->CritSect); + + LogFlow(("%s: usbProxyDarwinUrbAsyncComplete: cb=%d EndPt=%#x irc=%#x (%d)\n", + pUrb->pszDesc, cb, pUrb ? pUrb->EndPt : 0xff, irc, pUrb ? pUrb->enmStatus : 0xff)); +} + +/** + * Release all interfaces (current config). + * + * @param pDevOsX The darwin proxy device. + */ +static void usbProxyDarwinReleaseAllInterfaces(PUSBPROXYDEVOSX pDevOsX) +{ + RTCritSectEnter(&pDevOsX->CritSect); + + /* Kick the reaper thread out of sleep. */ + usbProxyDarwinReaperKick(pDevOsX); + + PUSBPROXYIFOSX pIf = pDevOsX->pIfHead; + pDevOsX->pIfHead = pDevOsX->pIfTail = NULL; + + while (pIf) + { + PUSBPROXYIFOSX pNext = pIf->pNext; + IOReturn irc; + + if (pIf->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops((PRTLISTANCHOR)&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + AssertRC(rc); + + CFRelease(pIf->RunLoopSrcRef); + pIf->RunLoopSrcRef = NULL; + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + } + + while (pIf->pIsocBufCols) + { + PUSBPROXYISOCBUFCOL pCur = pIf->pIsocBufCols; + pIf->pIsocBufCols = pCur->pNext; + pCur->pNext = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvBuffer); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvBuffer = NULL; + + irc = (*pIf->ppIfI)->LowLatencyDestroyBuffer(pIf->ppIfI, pCur->pvFrames); + AssertMsg(irc == kIOReturnSuccess || irc == MACH_SEND_INVALID_DEST, ("%#x\n", irc)); + pCur->pvFrames = NULL; + + RTMemFree(pCur); + } + + irc = (*pIf->ppIfI)->USBInterfaceClose(pIf->ppIfI); + AssertMsg(irc == kIOReturnSuccess || irc == kIOReturnNoDevice, ("%#x\n", irc)); + + (*pIf->ppIfI)->Release(pIf->ppIfI); + pIf->ppIfI = NULL; + + RTMemFree(pIf); + + pIf = pNext; + } + RTCritSectLeave(&pDevOsX->CritSect); +} + + +/** + * Get the properties all the pipes associated with an interface. + * + * This is used when we seize all the interface and after SET_INTERFACE. + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param pIf The interface to get pipe props for. + */ +static int usbProxyDarwinGetPipeProperties(PUSBPROXYDEVOSX pDevOsX, PUSBPROXYIFOSX pIf) +{ + /* + * Get the pipe (endpoint) count (it might have changed - even on open). + */ + int rc = VINF_SUCCESS; + bool fFullSpeed; + UInt32 u32UsecInFrame; + UInt8 cPipes; + IOReturn irc = (*pIf->ppIfI)->GetNumEndpoints(pIf->ppIfI, &cPipes); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + AssertRelease(cPipes < RT_ELEMENTS(pIf->aPipes)); + pIf->cPipes = cPipes + 1; + + /* Find out if this is a full-speed interface (needed for isochronous support). */ + irc = (*pIf->ppIfI)->GetFrameListTime(pIf->ppIfI, &u32UsecInFrame); + if (irc != kIOReturnSuccess) + { + pIf->cPipes = 0; + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + return rc; + } + fFullSpeed = u32UsecInFrame == kUSBFullSpeedMicrosecondsInFrame; + + /* + * Get the properties of each pipe. + */ + for (unsigned i = 0; i < pIf->cPipes; i++) + { + pIf->aPipes[i].u8PipeRef = i; + pIf->aPipes[i].fIsFullSpeed = fFullSpeed; + pIf->aPipes[i].u64NextFrameNo = 0; + irc = (*pIf->ppIfI)->GetPipeProperties(pIf->ppIfI, i, + &pIf->aPipes[i].u8Direction, + &pIf->aPipes[i].u8Endpoint, + &pIf->aPipes[i].u8TransferType, + &pIf->aPipes[i].u16MaxPacketSize, + &pIf->aPipes[i].u8Interval); + if (irc != kIOReturnSuccess) + { + LogRel(("USB: Failed to query properties for pipe %#d / interface %#x on device '%s'. (prot=%#x class=%#x)\n", + i, pIf->u8Interface, pDevOsX->pProxyDev->pUsbIns->pszName, pIf->u8Protocol, pIf->u8Class)); + if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + rc = RTErrConvertFromDarwin(irc); + pIf->cPipes = i; + break; + } + /* reconstruct bEndpoint */ + if (pIf->aPipes[i].u8Direction == kUSBIn) + pIf->aPipes[i].u8Endpoint |= 0x80; + Log2(("usbProxyDarwinGetPipeProperties: #If=%d EndPt=%#x Dir=%d Type=%d PipeRef=%#x MaxPktSize=%#x Interval=%#x\n", + pIf->u8Interface, pIf->aPipes[i].u8Endpoint, pIf->aPipes[i].u8Direction, pIf->aPipes[i].u8TransferType, + pIf->aPipes[i].u8PipeRef, pIf->aPipes[i].u16MaxPacketSize, pIf->aPipes[i].u8Interval)); + } + + /** @todo sort or hash these for speedy lookup... */ + return VINF_SUCCESS; +} + + +/** + * Seize all interfaces (current config). + * + * @returns VBox status code. + * @param pDevOsX The darwin proxy device. + * @param fMakeTheBestOfIt If set we will not give up on error. This is for + * use during SET_CONFIGURATION and similar. + */ +static int usbProxyDarwinSeizeAllInterfaces(PUSBPROXYDEVOSX pDevOsX, bool fMakeTheBestOfIt) +{ + PUSBPROXYDEV pProxyDev = pDevOsX->pProxyDev; + + RTCritSectEnter(&pDevOsX->CritSect); + + /* + * Create a interface enumerator for all the interface (current config). + */ + io_iterator_t Interfaces = IO_OBJECT_NULL; + IOUSBFindInterfaceRequest Req; + Req.bInterfaceClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceSubClass = kIOUSBFindInterfaceDontCare; + Req.bInterfaceProtocol = kIOUSBFindInterfaceDontCare; + Req.bAlternateSetting = kIOUSBFindInterfaceDontCare; + IOReturn irc = (*pDevOsX->ppDevI)->CreateInterfaceIterator(pDevOsX->ppDevI, &Req, &Interfaces); + int rc; + if (irc == kIOReturnSuccess) + { + /* + * Iterate the interfaces. + */ + io_object_t Interface; + rc = VINF_SUCCESS; + while ((Interface = IOIteratorNext(Interfaces))) + { + /* + * Create a plug-in and query the IOUSBInterfaceInterface (cute name). + */ + IOCFPlugInInterface **ppPlugInInterface = NULL; + kern_return_t krc; + SInt32 Score = 0; + krc = IOCreatePlugInInterfaceForService(Interface, kIOUSBInterfaceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + IOObjectRelease(Interface); + Interface = IO_OBJECT_NULL; + if (krc == KERN_SUCCESS) + { + IOUSBInterfaceInterface245 **ppIfI; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID245), + (LPVOID *)&ppIfI); + krc = IODestroyPlugInInterface(ppPlugInInterface); Assert(krc == KERN_SUCCESS); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Query some basic properties first. + * (This means we can print more informative messages on failure + * to seize the interface.) + */ + UInt8 u8Interface = 0xff; + irc = (*ppIfI)->GetInterfaceNumber(ppIfI, &u8Interface); + UInt8 u8AltSetting = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetAlternateSetting(ppIfI, &u8AltSetting); + UInt8 u8Class = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceClass(ppIfI, &u8Class); + UInt8 u8Protocol = 0xff; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetInterfaceProtocol(ppIfI, &u8Protocol); + UInt8 cEndpoints = 0; + if (irc == kIOReturnSuccess) + irc = (*ppIfI)->GetNumEndpoints(ppIfI, &cEndpoints); + if (irc == kIOReturnSuccess) + { + /* + * Try seize the interface. + */ + irc = (*ppIfI)->USBInterfaceOpenSeize(ppIfI); + if (irc == kIOReturnSuccess) + { + PUSBPROXYIFOSX pIf = (PUSBPROXYIFOSX)RTMemAllocZ(sizeof(*pIf)); + if (pIf) + { + /* + * Create the per-interface entry and query the + * endpoint data. + */ + /* initialize the entry */ + pIf->u8Interface = u8Interface; + pIf->u8AltSetting = u8AltSetting; + pIf->u8Class = u8Class; + pIf->u8Protocol = u8Protocol; + pIf->cPipes = cEndpoints; + pIf->ppIfI = ppIfI; + + /* query pipe/endpoint properties. */ + rc = usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + if (RT_SUCCESS(rc)) + { + /* + * Create the async event source and add it to the + * default current run loop. + * (Later: Add to the worker thread run loop instead.) + */ + irc = (*ppIfI)->CreateInterfaceAsyncEventSource(ppIfI, &pIf->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + RTListInit((PRTLISTNODE)&pIf->HeadOfRunLoopLst); + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, + pIf->RunLoopSrcRef); + + /* + * Just link the interface into the list and we're good. + */ + pIf->pNext = NULL; + Log(("USB: Seized interface %#x (alt=%d prot=%#x class=%#x)\n", + u8Interface, u8AltSetting, u8Protocol, u8Class)); + if (pDevOsX->pIfTail) + pDevOsX->pIfTail = pDevOsX->pIfTail->pNext = pIf; + else + pDevOsX->pIfTail = pDevOsX->pIfHead = pIf; + continue; + } + rc = RTErrConvertFromDarwin(irc); + } + + /* failure cleanup. */ + RTMemFree(pIf); + } + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Interface %#x on device '%s' is being used by another process. (prot=%#x class=%#x)\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class)); + rc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open interface %#x on device '%s'. (prot=%#x class=%#x) krc=%#x\n", + u8Interface, pProxyDev->pUsbIns->pszName, u8Protocol, u8Class, irc)); + rc = VERR_OPEN_FAILED; + } + } + else + { + rc = RTErrConvertFromDarwin(irc); + LogRel(("USB: Failed to query interface properties on device '%s', irc=%#x.\n", + pProxyDev->pUsbIns->pszName, irc)); + } + (*ppIfI)->Release(ppIfI); + ppIfI = NULL; + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwinCOM(hrc); + } + else if (RT_SUCCESS(rc)) + rc = RTErrConvertFromDarwin(krc); + if (!fMakeTheBestOfIt) + { + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + break; + } + } /* iterate */ + IOObjectRelease(Interfaces); + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + RTCritSectLeave(&pDevOsX->CritSect); + return rc; +} + + +/** + * Find a particular interface. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Interface The interface number. + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterface(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Interface) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + if (pIf->u8Interface == u8Interface) + return pIf; + +/* AssertMsgFailed(("Cannot find If#=%d\n", u8Interface)); - the 3rd quickcam interface is capture by the ****ing audio crap. */ + return NULL; +} + + +/** + * Find a particular endpoint. + * + * @returns The requested interface or NULL if not found. + * @param pDevOsX The darwin proxy device. + * @param u8Endpoint The endpoint. + * @param pu8PipeRef Where to store the darwin pipe ref. + * @param ppPipe Where to store the darwin pipe pointer. (optional) + */ +static PUSBPROXYIFOSX usbProxyDarwinGetInterfaceForEndpoint(PUSBPROXYDEVOSX pDevOsX, uint8_t u8Endpoint, + uint8_t *pu8PipeRef, PPUSBPROXYPIPEOSX ppPipe) +{ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + unsigned i = pIf->cPipes; + while (i-- > 0) + if (pIf->aPipes[i].u8Endpoint == u8Endpoint) + { + *pu8PipeRef = pIf->aPipes[i].u8PipeRef; + if (ppPipe) + *ppPipe = &pIf->aPipes[i]; + return pIf; + } + } + + AssertMsgFailed(("Cannot find EndPt=%#x\n", u8Endpoint)); + return NULL; +} + + +/** + * Gets an unsigned 32-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu32 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32)) + return true; + } + *pu32 = 0; + return false; +} + + +/** + * Gets an unsigned 64-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu64 Where to store the key value. + */ +static bool usbProxyDarwinDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64)) + return true; + } + *pu64 = 0; + return false; +} + + +static DECLCALLBACK(void) usbProxyDarwinPerformWakeup(void *pInfo) +{ + RT_NOREF(pInfo); + return; +} + + +/* -=-=-=-=-=- The exported methods -=-=-=-=-=- */ + +/** + * Opens the USB Device. + * + * @returns VBox status code. + * @param pProxyDev The device instance. + * @param pszAddress The session id and/or location id of the device to open. + * The format of this string is something iokit.c in Main defines, currently + * it's sequences of "[l|s]=" separated by ";". + */ +static DECLCALLBACK(int) usbProxyDarwinOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + LogFlow(("usbProxyDarwinOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Init globals once. + */ + int vrc = RTOnce(&g_usbProxyDarwinOnce, usbProxyDarwinInitOnce, NULL); + AssertRCReturn(vrc, vrc); + + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + /* + * The idea here was to create a matching directory with the sessionID + * and locationID included, however this doesn't seem to work. So, we'll + * use the product id and vendor id to limit the set of matching device + * and manually match these two properties. sigh. + * (Btw. vendor and product id must be used *together* apparently.) + * + * Wonder if we could use the entry path? Docs indicates says we must + * use IOServiceGetMatchingServices and I'm not in a mood to explore + * this subject further right now. Maybe check this later. + */ + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName); + AssertReturn(RefMatchingDict != NULL, VERR_OPEN_FAILED); + + uint64_t u64SessionId = 0; + uint32_t u32LocationId = 0; + const char *psz = pszAddress; + do + { + const char chValue = *psz; + AssertReleaseReturn(psz[1] == '=', VERR_INTERNAL_ERROR); + uint64_t u64Value; + int rc = RTStrToUInt64Ex(psz + 2, (char **)&psz, 0, &u64Value); + AssertReleaseRCReturn(rc, rc); + AssertReleaseReturn(!*psz || *psz == ';', rc); + switch (chValue) + { + case 'l': + u32LocationId = (uint32_t)u64Value; + break; + case 's': + u64SessionId = u64Value; + break; + case 'p': + case 'v': + { +#if 0 /* Guess what, this doesn't 'ing work either! */ + SInt32 i32 = (int16_t)u64Value; + CFNumberRef Num = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt32Type, &i32); + AssertBreak(Num); + CFDictionarySetValue(RefMatchingDict, chValue == 'p' ? CFSTR(kUSBProductID) : CFSTR(kUSBVendorID), Num); + CFRelease(Num); +#endif + break; + } + default: + AssertReleaseMsgFailedReturn(("chValue=%#x\n", chValue), VERR_INTERNAL_ERROR); + } + if (*psz == ';') + psz++; + } while (*psz); + + io_iterator_t USBDevices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(g_MasterPort, RefMatchingDict, &USBDevices); + AssertMsgReturn(irc == kIOReturnSuccess, ("irc=%#x\n", irc), RTErrConvertFromDarwinIO(irc)); + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + unsigned cMatches = 0; + io_object_t USBDevice; + while ((USBDevice = IOIteratorNext(USBDevices))) + { + cMatches++; + CFMutableDictionaryRef PropsRef = 0; + kern_return_t krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + uint64_t u64CurSessionId; + uint32_t u32CurLocationId; + if ( ( !u64SessionId + || ( usbProxyDarwinDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId) + && u64CurSessionId == u64SessionId)) + && ( !u32LocationId + || ( usbProxyDarwinDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId) + && u32CurLocationId == u32LocationId)) + ) + { + CFRelease(PropsRef); + break; + } + CFRelease(PropsRef); + } + IOObjectRelease(USBDevice); + } + IOObjectRelease(USBDevices); + USBDevices = IO_OBJECT_NULL; + if (!USBDevice) + { + LogRel(("USB: Device '%s' not found (%d pid+vid matches)\n", pszAddress, cMatches)); + IOObjectRelease(USBDevices); + return VERR_VUSB_DEVICE_NAME_NOT_FOUND; + } + + /* + * Create a plugin interface for the device and query its IOUSBDeviceInterface. + */ + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Try open the device for exclusive access. + * If we fail, we'll try figure out who is using the device and + * convince them to let go of it... + */ + irc = (*ppDevI)->USBDeviceReEnumerate(ppDevI, kUSBReEnumerateCaptureDeviceMask); + Log(("USBDeviceReEnumerate (capture) returned irc=%#x\n", irc)); + + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + if (irc == kIOReturnExclusiveAccess) + { + RTThreadSleep(20); + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + } + if (irc == kIOReturnSuccess) + { + /* + * Init a proxy device instance. + */ + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + vrc = RTCritSectInit(&pDevOsX->CritSect); + if (RT_SUCCESS(vrc)) + { + pDevOsX->USBDevice = USBDevice; + pDevOsX->ppDevI = ppDevI; + pDevOsX->pProxyDev = pProxyDev; + pDevOsX->pTaxingHead = NULL; + pDevOsX->pTaxingTail = NULL; + pDevOsX->hRunLoopReapingLast = NULL; + + /* + * Try seize all the interface. + */ + char *pszDummyName = pProxyDev->pUsbIns->pszName; + pProxyDev->pUsbIns->pszName = (char *)pszAddress; + vrc = usbProxyDarwinSeizeAllInterfaces(pDevOsX, false /* give up on failure */); + pProxyDev->pUsbIns->pszName = pszDummyName; + if (RT_SUCCESS(vrc)) + { + /* + * Create the async event source and add it to the run loop. + */ + irc = (*ppDevI)->CreateDeviceAsyncEventSource(ppDevI, &pDevOsX->RunLoopSrcRef); + if (irc == kIOReturnSuccess) + { + /* + * Determine the active configuration. + * Can cause hangs, so drop it for now. + */ + /** @todo test Palm. */ + //uint8_t u8Cfg; + //irc = (*ppDevI)->GetConfiguration(ppDevI, &u8Cfg); + if (irc != kIOReturnNoDevice) + { + CFRunLoopSourceContext CtxRunLoopSource; + CtxRunLoopSource.version = 0; + CtxRunLoopSource.info = NULL; + CtxRunLoopSource.retain = NULL; + CtxRunLoopSource.release = NULL; + CtxRunLoopSource.copyDescription = NULL; + CtxRunLoopSource.equal = NULL; + CtxRunLoopSource.hash = NULL; + CtxRunLoopSource.schedule = NULL; + CtxRunLoopSource.cancel = NULL; + CtxRunLoopSource.perform = usbProxyDarwinPerformWakeup; + pDevOsX->hRunLoopSrcWakeRef = CFRunLoopSourceCreate(NULL, 0, &CtxRunLoopSource); + if (CFRunLoopSourceIsValid(pDevOsX->hRunLoopSrcWakeRef)) + { + //pProxyDev->iActiveCfg = irc == kIOReturnSuccess ? u8Cfg : -1; + RTListInit(&pDevOsX->HeadOfRunLoopWakeLst); + pProxyDev->iActiveCfg = -1; + pProxyDev->cIgnoreSetConfigs = 1; + + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + return VINF_SUCCESS; /* return */ + } + else + { + LogRel(("USB: Device '%s' out of memory allocating runloop source\n", pszAddress)); + vrc = VERR_NO_MEMORY; + } + } + vrc = VERR_VUSB_DEVICE_NOT_ATTACHED; + } + else + vrc = RTErrConvertFromDarwin(irc); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + } + /* else: already bitched */ + + RTCritSectDelete(&pDevOsX->CritSect); + } + + irc = (*ppDevI)->USBDeviceClose(ppDevI); + AssertMsg(irc == kIOReturnSuccess, ("%#x\n", irc)); + } + else if (irc == kIOReturnExclusiveAccess) + { + LogRel(("USB: Device '%s' is being used by another process\n", pszAddress)); + vrc = VERR_SHARING_VIOLATION; + } + else + { + LogRel(("USB: Failed to open device '%s', irc=%#x.\n", pszAddress, irc)); + vrc = VERR_OPEN_FAILED; + } + } + else + { + LogRel(("USB: Failed to create plugin interface for device '%s', hrc=%#x.\n", pszAddress, hrc)); + vrc = VERR_OPEN_FAILED; + } + + (*ppDevI)->Release(ppDevI); + } + else + { + LogRel(("USB: Failed to open device '%s', plug-in creation failed with irc=%#x.\n", pszAddress, irc)); + vrc = RTErrConvertFromDarwin(irc); + } + + return vrc; +} + + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyDarwinClose(PUSBPROXYDEV pProxyDev) +{ + LogFlow(("usbProxyDarwinClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + AssertPtrReturnVoid(pDevOsX); + + /* + * Release interfaces we've laid claim to, then reset the device + * and finally close it. + */ + RTCritSectEnter(&pDevOsX->CritSect); + /* ?? */ + RTCritSectLeave(&pDevOsX->CritSect); + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + + if (pDevOsX->RunLoopSrcRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopLst); + + CFRelease(pDevOsX->RunLoopSrcRef); + pDevOsX->RunLoopSrcRef = NULL; + } + + if (pDevOsX->hRunLoopSrcWakeRef) + { + int rc = usbProxyDarwinRemoveSourceRefFromAllRunLoops(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + AssertRC(rc); + + RTListInit((PRTLISTNODE)&pDevOsX->HeadOfRunLoopWakeLst); + + CFRelease(pDevOsX->hRunLoopSrcWakeRef); + pDevOsX->hRunLoopSrcWakeRef = NULL; + } + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + + irc = (*pDevOsX->ppDevI)->USBDeviceClose(pDevOsX->ppDevI); + if (irc != kIOReturnSuccess && irc != kIOReturnNoDevice) + { + LogRel(("USB: USBDeviceClose -> %#x\n", irc)); + AssertMsgFailed(("irc=%#x\n", irc)); + } + + irc = (*pDevOsX->ppDevI)->USBDeviceReEnumerate(pDevOsX->ppDevI, kUSBReEnumerateReleaseDeviceMask); + Log(("USBDeviceReEnumerate (release) returned irc=%#x\n", irc)); + + (*pDevOsX->ppDevI)->Release(pDevOsX->ppDevI); + pDevOsX->ppDevI = NULL; + kern_return_t krc = IOObjectRelease(pDevOsX->USBDevice); Assert(krc == KERN_SUCCESS); NOREF(krc); + pDevOsX->USBDevice = IO_OBJECT_NULL; + pDevOsX->pProxyDev = NULL; + + /* + * Free all the resources. + */ + RTCritSectDelete(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX; + while ((pUrbOsX = pDevOsX->pFreeHead) != NULL) + { + pDevOsX->pFreeHead = pUrbOsX->pNext; + RTMemFree(pUrbOsX); + } + + LogFlow(("usbProxyDarwinClose: returns\n")); +} + + +/** @interface_method_impl{USBPROXYBACK,pfnReset}*/ +static DECLCALLBACK(int) usbProxyDarwinReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) +{ + RT_NOREF(fResetOnLinux); + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinReset: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + IOReturn irc = (*pDevOsX->ppDevI)->ResetDevice(pDevOsX->ppDevI); + int rc; + if (irc == kIOReturnSuccess) + { + /** @todo Some docs say that some drivers will do a default config, check this out ... */ + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + + rc = VINF_SUCCESS; + } + else if (irc == kIOReturnNoDevice) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + else + { + AssertMsgFailed(("irc=%#x\n", irc)); + rc = VERR_GENERAL_FAILURE; + } + + LogFlow(("usbProxyDarwinReset: returns success %Rrc\n", rc)); + return rc; +} + + +/** + * SET_CONFIGURATION. + * + * The caller makes sure that it's not called first time after open or reset + * with the active interface. + * + * @returns success indicator. + * @param pProxyDev The device instance data. + * @param iCfg The configuration to set. + */ +static DECLCALLBACK(int) usbProxyDarwinSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinSetConfig: pProxyDev=%s cfg=%#x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + IOReturn irc = (*pDevOsX->ppDevI)->SetConfiguration(pDevOsX->ppDevI, (uint8_t)iCfg); + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinSetConfig: Set configuration -> %#x\n", irc)); + return RTErrConvertFromDarwin(irc); + } + + usbProxyDarwinReleaseAllInterfaces(pDevOsX); + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + return VINF_SUCCESS; +} + + +/** + * Claims an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator (always VINF_SUCCESS). + */ +static DECLCALLBACK(int) usbProxyDarwinClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * Releases an interface. + * + * This is a stub on Darwin since we release/claim all interfaces at + * open/reset/setconfig time. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + RT_NOREF(pProxyDev, iIf); + return VINF_SUCCESS; +} + + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyDarwinSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + IOReturn irc = kIOReturnSuccess; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterface(pDevOsX, iIf); + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s iIf=%#x iAlt=%#x iCurAlt=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf ? pIf->u8AltSetting : 0xbeef)); + if (pIf) + { + /* Avoid SetAlternateInterface when possible as it will recreate the pipes. */ + if (iAlt != pIf->u8AltSetting) + { + irc = (*pIf->ppIfI)->SetAlternateInterface(pIf->ppIfI, iAlt); + if (irc == kIOReturnSuccess) + { + usbProxyDarwinGetPipeProperties(pDevOsX, pIf); + return VINF_SUCCESS; + } + } + else + { + /* + * Just send the request anyway? + */ + IOUSBDevRequest Req; + Req.bmRequestType = 0x01; + Req.bRequest = 0x0b; /* SET_INTERFACE */ + Req.wIndex = iIf; + Req.wValue = iAlt; + Req.wLength = 0; + Req.wLenDone = 0; + Req.pData = NULL; + irc = (*pDevOsX->ppDevI)->DeviceRequest(pDevOsX->ppDevI, &Req); + Log(("usbProxyDarwinSetInterface: SET_INTERFACE(%d,%d) -> irc=%#x\n", iIf, iAlt, irc)); + return VINF_SUCCESS; + } + } + + LogFlow(("usbProxyDarwinSetInterface: pProxyDev=%s eiIf=%#x iAlt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, iIf, iAlt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Clears the halted endpoint 'EndPt'. + */ +static DECLCALLBACK(int) usbProxyDarwinClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x\n", pProxyDev->pUsbIns->pszName, EndPt)); + + /* + * Clearing the zero control pipe doesn't make sense and isn't + * supported by the API. Just ignore it. + */ + if (EndPt == 0) + return VINF_SUCCESS; + + /* + * Find the interface/pipe combination and invoke the ClearPipeStallBothEnds + * method. (The ResetPipe and ClearPipeStall methods will not send the + * CLEAR_FEATURE(ENDPOINT_HALT) request that this method implements.) + */ + IOReturn irc = kIOReturnSuccess; + uint8_t u8PipeRef; + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + { + irc = (*pIf->ppIfI)->ClearPipeStallBothEnds(pIf->ppIfI, u8PipeRef); + if (irc == kIOReturnSuccess) + return VINF_SUCCESS; + AssertMsg(irc == kIOReturnNoDevice || irc == kIOReturnNotResponding, ("irc=#x (control pipe?)\n", irc)); + } + + LogFlow(("usbProxyDarwinClearHaltedEp: pProxyDev=%s EndPt=%#x - failure - pIf=%p irc=%#x\n", + pProxyDev->pUsbIns->pszName, EndPt, pIf, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyDarwinUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, pUrb->cbData)); + + /* + * Find the target interface / pipe. + */ + uint8_t u8PipeRef = 0xff; + PUSBPROXYIFOSX pIf = NULL; + PUSBPROXYPIPEOSX pPipe = NULL; + if (pUrb->EndPt) + { + /* Make sure the interface is there. */ + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, &pPipe); + if (!pIf) + { + LogFlow(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - can't find interface / pipe!!!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData)); + return VERR_NOT_FOUND; + } + } + /* else: pIf == NULL -> default control pipe.*/ + + /* + * Allocate a Darwin urb. + */ + PUSBPROXYURBOSX pUrbOsX = usbProxyDarwinUrbAlloc(pDevOsX); + if (!pUrbOsX) + return VERR_NO_MEMORY; + + pUrbOsX->u64SubmitTS = RTTimeMilliTS(); + pUrbOsX->pVUsbUrb = pUrb; + pUrbOsX->pDevOsX = pDevOsX; + pUrbOsX->enmType = pUrb->enmType; + + /* + * Submit the request. + */ + IOReturn irc = kIOReturnError; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + AssertMsgBreak(pUrb->cbData >= sizeof(VUSBSETUP), ("cbData=%d\n", pUrb->cbData)); + PVUSBSETUP pSetup = (PVUSBSETUP)&pUrb->abData[0]; + pUrbOsX->u.ControlMsg.bmRequestType = pSetup->bmRequestType; + pUrbOsX->u.ControlMsg.bRequest = pSetup->bRequest; + pUrbOsX->u.ControlMsg.wValue = pSetup->wValue; + pUrbOsX->u.ControlMsg.wIndex = pSetup->wIndex; + pUrbOsX->u.ControlMsg.wLength = pSetup->wLength; + pUrbOsX->u.ControlMsg.pData = pSetup + 1; + pUrbOsX->u.ControlMsg.wLenDone = pSetup->wLength; + + if (pIf) + irc = (*pIf->ppIfI)->ControlRequestAsync(pIf->ppIfI, u8PipeRef, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pDevOsX->ppDevI)->DeviceRequestAsync(pDevOsX->ppDevI, &pUrbOsX->u.ControlMsg, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + break; + } + + case VUSBXFERTYPE_BULK: + case VUSBXFERTYPE_INTR: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WritePipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadPipeAsync(pIf->ppIfI, u8PipeRef, pUrb->abData, pUrb->cbData, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + + break; + } + + case VUSBXFERTYPE_ISOC: + { + AssertBreak(pIf); + Assert(pUrb->enmDir == VUSBDIRECTION_IN || pUrb->enmDir == VUSBDIRECTION_OUT); + +#ifdef USE_LOW_LATENCY_API + /* Allocate an isochronous buffer and copy over the data. */ + AssertBreak(pUrb->cbData <= 8192); + int rc = usbProxyDarwinUrbAllocIsocBuf(pUrbOsX, pIf); + AssertRCBreak(rc); + if (pUrb->enmDir == VUSBDIRECTION_OUT) + memcpy(pUrbOsX->u.Isoc.pBuf->pvBuf, pUrb->abData, pUrb->cbData); + else + memset(pUrbOsX->u.Isoc.pBuf->pvBuf, 0xfe, pUrb->cbData); +#endif + + /* Get the current frame number (+2) and make sure it doesn't + overlap with the previous request. See WARNING in + ApplUSBUHCI::CreateIsochTransfer for details on the +2. */ + UInt64 FrameNo; + AbsoluteTime FrameTime; + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + AssertMsg(irc == kIOReturnSuccess, ("GetBusFrameNumber -> %#x\n", irc)); + FrameNo += 2; + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + + for (unsigned j = 0; ; j++) + { + unsigned i; + for (i = 0; i < pUrb->cIsocPkts; i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = pUrb->aIsocPkts[i].cb; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOUSBNotSent1Err; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + for (; i < RT_ELEMENTS(pUrbOsX->u.Isoc.aFrames); i++) + { + pUrbOsX->u.Isoc.aFrames[i].frReqCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frActCount = 0; + pUrbOsX->u.Isoc.aFrames[i].frStatus = kIOReturnError; +#ifdef USE_LOW_LATENCY_API + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.hi = 0; + pUrbOsX->u.Isoc.aFrames[i].frTimeStamp.lo = 0; +#endif + } + +#ifdef USE_LOW_LATENCY_API + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->LowLatencyWriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->LowLatencyReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrbOsX->u.Isoc.pBuf->pvBuf, FrameNo, pUrb->cIsocPkts, 0, pUrbOsX->u.Isoc.aFrames, + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#else + if (pUrb->enmDir == VUSBDIRECTION_OUT) + irc = (*pIf->ppIfI)->WriteIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); + else + irc = (*pIf->ppIfI)->ReadIsochPipeAsync(pIf->ppIfI, u8PipeRef, + pUrb->abData, FrameNo, pUrb->cIsocPkts, &pUrbOsX->u.Isoc.aFrames[0], + usbProxyDarwinUrbAsyncComplete, pUrbOsX); +#endif + if ( irc != kIOReturnIsoTooOld + || j >= 5) + { + Log(("%s: usbProxyDarwinUrbQueue: isoc: u64NextFrameNo=%RX64 FrameNo=%RX64 #Frames=%d j=%d (pipe=%d)\n", + pUrb->pszDesc, pPipe->u64NextFrameNo, FrameNo, pUrb->cIsocPkts, j, u8PipeRef)); + if (irc == kIOReturnSuccess) + { + if (pPipe->fIsFullSpeed) + pPipe->u64NextFrameNo = FrameNo + pUrb->cIsocPkts; + else + pPipe->u64NextFrameNo = FrameNo + 1; + } + break; + } + + /* try again... */ + irc = (*pIf->ppIfI)->GetBusFrameNumber(pIf->ppIfI, &FrameNo, &FrameTime); + if (FrameNo <= pPipe->u64NextFrameNo) + FrameNo = pPipe->u64NextFrameNo; + FrameNo += j; + } + break; + } + + default: + AssertMsgFailed(("%s: enmType=%#x\n", pUrb->pszDesc, pUrb->enmType)); + break; + } + + /* + * Success? + */ + if (RT_LIKELY(irc == kIOReturnSuccess)) + { + Log(("%s: usbProxyDarwinUrbQueue: success\n", pUrb->pszDesc)); + return VINF_SUCCESS; + } + switch (irc) + { + case kIOUSBPipeStalled: + { + /* Increment in flight counter because the completion handler will decrease it always. */ + usbProxyDarwinUrbAsyncComplete(pUrbOsX, kIOUSBPipeStalled, 0); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x! (stall)\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return VINF_SUCCESS; + } + } + + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + Log(("%s: usbProxyDarwinUrbQueue: pProxyDev=%s EndPt=%d cbData=%d - failed irc=%#x!\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt, pUrb->cbData, irc)); + return RTErrConvertFromDarwin(irc); +} + + +/** + * Reap URBs in-flight on a device. + * + * @returns Pointer to a completed URB. + * @returns NULL if no URB was completed. + * @param pProxyDev The device. + * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. + */ +static DECLCALLBACK(PVUSBURB) usbProxyDarwinUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + PVUSBURB pUrb = NULL; + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + CFRunLoopRef hRunLoopRef = CFRunLoopGetCurrent(); + + Assert(!pDevOsX->hRunLoopReaping); + + /* + * If the last seen runloop for reaping differs we have to check whether the + * the runloop sources are in the new runloop. + */ + if (pDevOsX->hRunLoopReapingLast != hRunLoopRef) + { + RTCritSectEnter(&pDevOsX->CritSect); + + /* Every pipe. */ + if (!pDevOsX->pIfHead) + usbProxyDarwinSeizeAllInterfaces(pDevOsX, true /* make the best out of it */); + + PUSBPROXYIFOSX pIf; + for (pIf = pDevOsX->pIfHead; pIf; pIf = pIf->pNext) + { + if (!CFRunLoopContainsSource(hRunLoopRef, pIf->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pIf->HeadOfRunLoopLst, pIf->RunLoopSrcRef); + } + + /* Default control pipe. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->RunLoopSrcRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopLst, pDevOsX->RunLoopSrcRef); + + /* Runloop wakeup source. */ + if (!CFRunLoopContainsSource(hRunLoopRef, pDevOsX->hRunLoopSrcWakeRef, g_pRunLoopMode)) + usbProxyDarwinAddRunLoopRef(&pDevOsX->HeadOfRunLoopWakeLst, pDevOsX->hRunLoopSrcWakeRef); + RTCritSectLeave(&pDevOsX->CritSect); + + pDevOsX->hRunLoopReapingLast = hRunLoopRef; + } + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, hRunLoopRef); + + if (ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false)) + { + /* Return immediately. */ + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + return NULL; + } + + /* + * Excercise the runloop until we get an URB or we time out. + */ + if ( !pDevOsX->pTaxingHead + && cMillies) + CFRunLoopRunInMode(g_pRunLoopMode, cMillies / 1000.0, true); + + ASMAtomicXchgPtr((void * volatile *)&pDevOsX->hRunLoopReaping, NULL); + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, false); + + /* + * Any URBs pending delivery? + */ + while ( pDevOsX->pTaxingHead + && !pUrb) + { + RTCritSectEnter(&pDevOsX->CritSect); + + PUSBPROXYURBOSX pUrbOsX = pDevOsX->pTaxingHead; + if (pUrbOsX) + { + /* + * Remove from the taxing list. + */ + if (pUrbOsX->pNext) + pUrbOsX->pNext->pPrev = pUrbOsX->pPrev; + else if (pDevOsX->pTaxingTail == pUrbOsX) + pDevOsX->pTaxingTail = pUrbOsX->pPrev; + + if (pUrbOsX->pPrev) + pUrbOsX->pPrev->pNext = pUrbOsX->pNext; + else if (pDevOsX->pTaxingHead == pUrbOsX) + pDevOsX->pTaxingHead = pUrbOsX->pNext; + else + AssertFailed(); + + pUrb = pUrbOsX->pVUsbUrb; + if (pUrb) + { + pUrb->Dev.pvPrivate = NULL; + usbProxyDarwinUrbFree(pDevOsX, pUrbOsX); + } + } + RTCritSectLeave(&pDevOsX->CritSect); + } + + if (pUrb) + LogFlowFunc(("LEAVE: %s: pProxyDev=%s returns %p\n", pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb)); + else + LogFlowFunc(("LEAVE: NULL pProxyDev=%s returns NULL\n", pProxyDev->pUsbIns->pszName)); + + return pUrb; +} + + +/** + * Cancels a URB. + * + * The URB requires reaping, so we don't change its state. + * + * @remark There isn't any way to cancel a specific async request + * on darwin. The interface only supports the aborting of + * all URBs pending on an interface / pipe pair. Provided + * the card does the URB cancelling before submitting new + * requests, we should probably be fine... + */ +static DECLCALLBACK(int) usbProxyDarwinUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + LogFlow(("%s: usbProxyDarwinUrbCancel: pProxyDev=%s EndPt=%d\n", + pUrb->pszDesc, pProxyDev->pUsbIns->pszName, pUrb->EndPt)); + + /* + * Determine the interface / endpoint ref and invoke AbortPipe. + */ + IOReturn irc = kIOReturnSuccess; + if (!pUrb->EndPt) + irc = (*pDevOsX->ppDevI)->USBDeviceAbortPipeZero(pDevOsX->ppDevI); + else + { + uint8_t u8PipeRef; + const uint8_t EndPt = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); + PUSBPROXYIFOSX pIf = usbProxyDarwinGetInterfaceForEndpoint(pDevOsX, EndPt, &u8PipeRef, NULL); + if (pIf) + irc = (*pIf->ppIfI)->AbortPipe(pIf->ppIfI, u8PipeRef); + else /* this may happen if a device reset, set configuration or set interface has been performed. */ + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d - cannot find the interface / pipe!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt)); + } + + int rc = VINF_SUCCESS; + if (irc != kIOReturnSuccess) + { + Log(("usbProxyDarwinUrbCancel: pProxyDev=%s pUrb=%p EndPt=%d -> %#x!\n", + pProxyDev->pUsbIns->pszName, pUrb, pUrb->EndPt, irc)); + rc = RTErrConvertFromDarwin(irc); + } + + return rc; +} + + +static DECLCALLBACK(int) usbProxyDarwinWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVOSX pDevOsX = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVOSX); + + LogFlow(("usbProxyDarwinWakeup: pProxyDev=%p\n", pProxyDev)); + + ASMAtomicXchgBool(&pDevOsX->fReapingThreadWake, true); + usbProxyDarwinReaperKick(pDevOsX); + return VINF_SUCCESS; +} + + +/** + * The Darwin USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVOSX), + usbProxyDarwinOpen, + NULL, + usbProxyDarwinClose, + usbProxyDarwinReset, + usbProxyDarwinSetConfig, + usbProxyDarwinClaimInterface, + usbProxyDarwinReleaseInterface, + usbProxyDarwinSetInterface, + usbProxyDarwinClearHaltedEp, + usbProxyDarwinUrbQueue, + usbProxyDarwinUrbCancel, + usbProxyDarwinUrbReap, + usbProxyDarwinWakeup, + 0 +}; + -- cgit v1.2.3