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/VUSBUrb.cpp | 1509 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 1509 insertions(+) create mode 100644 src/VBox/Devices/USB/VUSBUrb.cpp (limited to 'src/VBox/Devices/USB/VUSBUrb.cpp') diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp new file mode 100644 index 00000000..595277f9 --- /dev/null +++ b/src/VBox/Devices/USB/VUSBUrb.cpp @@ -0,0 +1,1509 @@ +/* $Id: VUSBUrb.cpp $ */ +/** @file + * Virtual USB - URBs. + */ + +/* + * 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_VUSB +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "VUSBInternal.h" + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Strings for the CTLSTAGE enum values. */ +const char * const g_apszCtlStates[4] = +{ + "SETUP", + "DATA", + "STATUS", + "N/A" +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Complete a SETUP stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgSetupCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgSetupCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage %s->DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe, g_apszCtlStates[pExtra->enmStage])); NOREF(pSetup); + pExtra->enmStage = CTLSTAGE_DATA; + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a DATA stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgDataCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + + LogFlow(("%s: vusbMsgDataCompletion: cbData=%d wLength=%#x cbLeft=%d pPipe=%p stage DATA\n", + pUrb->pszDesc, pUrb->cbData, pSetup->wLength, pExtra->cbLeft, pPipe)); NOREF(pSetup); + + pUrb->enmStatus = VUSBSTATUS_OK; +} + +/** + * Complete a STATUS stage URB. + * + * This is used both for dev2host and host2dev kind of transfers. + * It is used by both the sync and async control paths. + */ +static void vusbMsgStatusCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (pExtra->fOk) + { + /* + * vusbDevStdReqSetAddress requests are deferred. + */ + if (pDev->u8NewAddress != VUSB_INVALID_ADDRESS) + { + vusbDevSetAddress(pDev, pDev->u8NewAddress); + pDev->u8NewAddress = VUSB_INVALID_ADDRESS; + } + + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=OK stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_OK; + } + else + { + LogFlow(("%s: vusbMsgStatusCompletion: pDev=%p[%s] pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pPipe, g_apszCtlStates[pExtra->enmStage])); + pUrb->enmStatus = VUSBSTATUS_STALL; + } + + /* + * Done with this message sequence. + */ + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; +} + +/** + * This is a worker function for vusbMsgCompletion and + * vusbMsgSubmitSynchronously used to complete the original URB. + * + * @param pUrb The URB originating from the HCI. + */ +static void vusbCtrlCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbCtrlCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + vusbMsgSetupCompletion(pUrb); + break; + case CTLSTAGE_DATA: + vusbMsgDataCompletion(pUrb); + break; + case CTLSTAGE_STATUS: + vusbMsgStatusCompletion(pUrb); + break; + } +} + +/** + * Called from vusbUrbCompletionRh when it encounters a + * message type URB. + * + * @param pUrb The URB within the control pipe extra state data. + */ +static void vusbMsgCompletion(PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + +#ifdef LOG_ENABLED + LogFlow(("%s: vusbMsgCompletion: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName)); + vusbUrbTrace(pUrb, "vusbMsgCompletion", true); +#endif + Assert(&pExtra->Urb == pUrb); + + + if (pUrb->enmStatus == VUSBSTATUS_OK) + pExtra->fOk = true; + else + pExtra->fOk = false; + pExtra->cbLeft = pUrb->cbData - sizeof(VUSBSETUP); + + /* + * Complete the original URB. + */ + PVUSBURB pCtrlUrb = pUrb->pVUsb->pCtrlUrb; + pCtrlUrb->enmState = VUSBURBSTATE_REAPED; + vusbCtrlCompletion(pCtrlUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + Assert( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED); + if (pUrb->enmState != VUSBURBSTATE_CANCELLED) + { + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } + RTCritSectLeave(&pPipe->CritSectCtrl); + + /* Complete the original control URB on the root hub now. */ + vusbUrbCompletionRh(pCtrlUrb); +} + +/** + * Deal with URB errors, talking thru the RH to the HCI. + * + * @returns true if it could be retried. + * @returns false if it should be completed with failure. + * @param pRh The roothub the URB originated from. + * @param pUrb The URB in question. + */ +int vusbUrbErrorRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + LogFlow(("%s: vusbUrbErrorRh: pDev=%p[%s] rh=%p\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "", pRh)); + RT_NOREF(pDev); + return pRh->pIRhPort->pfnXferError(pRh->pIRhPort, pUrb); +} + +/** + * Does URB completion on roothub level. + * + * @param pRh The roothub the URB originated from. + * @param pUrb The URB to complete. + */ +void vusbUrbCompletionRhEx(PVUSBROOTHUB pRh, PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbCompletionRh: type=%s status=%s\n", + pUrb->pszDesc, vusbUrbTypeName(pUrb->enmType), vusbUrbStatusName(pUrb->enmStatus))); + AssertMsg( pUrb->enmState == VUSBURBSTATE_REAPED + || pUrb->enmState == VUSBURBSTATE_CANCELLED, ("%d\n", pUrb->enmState)); + + if ( pUrb->pVUsb->pDev + && pUrb->pVUsb->pDev->hSniffer) + { + int rc = VUSBSnifferRecordEvent(pUrb->pVUsb->pDev->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event failed with %Rrc\n", rc)); + } + + /* If there is a sniffer on the roothub record the completed URB there too. */ + if (pRh->hSniffer != VUSBSNIFFER_NIL) + { + int rc = VUSBSnifferRecordEvent(pRh->hSniffer, pUrb, + pUrb->enmStatus == VUSBSTATUS_OK + ? VUSBSNIFFEREVENT_COMPLETE + : VUSBSNIFFEREVENT_ERROR_COMPLETE); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB completion event on the root hub failed with %Rrc\n", rc)); + } + +#ifdef VBOX_WITH_STATISTICS + /* + * Total and per-type submit statistics. + */ + if (pUrb->enmType != VUSBXFERTYPE_MSG) + { + Assert(pUrb->enmType >= 0 && pUrb->enmType < (int)RT_ELEMENTS(pRh->aTypes)); + + if ( pUrb->enmStatus == VUSBSTATUS_OK + || pUrb->enmStatus == VUSBSTATUS_DATA_UNDERRUN + || pUrb->enmStatus == VUSBSTATUS_DATA_OVERRUN) + { + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + const unsigned cb = pUrb->aIsocPkts[i].cb; + if (cb) + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActBytes, cb); + STAM_COUNTER_ADD(&pRh->aStatIsocDetails[i].Bytes, cb); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActReadBytes, cb); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, cb); + STAM_COUNTER_ADD(&pRh->aTypes[VUSBXFERTYPE_ISOC].StatActWriteBytes, cb); + } + STAM_COUNTER_INC(&pRh->StatIsocActPkts); + STAM_COUNTER_INC(&pRh->StatIsocActReadPkts); + } + STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Pkts); + switch (pUrb->aIsocPkts[i].enmStatus) + { + case VUSBSTATUS_OK: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Ok0); + break; + case VUSBSTATUS_DATA_UNDERRUN: + if (cb) STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun); + else STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataUnderrun0); + break; + case VUSBSTATUS_DATA_OVERRUN: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].DataOverrun); break; + case VUSBSTATUS_NOT_ACCESSED: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].NotAccessed); break; + default: STAM_COUNTER_INC(&pRh->aStatIsocDetails[i].Misc); break; + } + } + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActBytes, pUrb->cbData); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + STAM_COUNTER_ADD(&pRh->Total.StatActReadBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActReadBytes, pUrb->cbData); + } + else + { + STAM_COUNTER_ADD(&pRh->Total.StatActWriteBytes, pUrb->cbData); + STAM_COUNTER_ADD(&pRh->aTypes[pUrb->enmType].StatActWriteBytes, pUrb->cbData); + } + } + } + else + { + /* (Note. this also counts the cancelled packets) */ + STAM_COUNTER_INC(&pRh->Total.StatUrbsFailed); + STAM_COUNTER_INC(&pRh->aTypes[pUrb->enmType].StatUrbsFailed); + } + } +#endif /* VBOX_WITH_STATISTICS */ + + /* + * Msg transfers are special virtual transfers associated with + * vusb, not the roothub + */ + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + vusbMsgCompletion(pUrb); + return; + case VUSBXFERTYPE_ISOC: + /* Don't bother with error callback for isochronous URBs. */ + break; + +#if 1 /** @todo r=bird: OHCI say ''If the Transfer Descriptor is being + * retired because of an error, the Host Controller must update + * the Halt bit of the Endpoint Descriptor.'' + * + * So, I'll subject all transfertypes to the same halt stuff now. It could + * just happen to fix the logitech disconnect trap in win2k. + */ + default: +#endif + case VUSBXFERTYPE_BULK: + if (pUrb->enmStatus != VUSBSTATUS_OK) + vusbUrbErrorRhEx(pRh, pUrb); + break; + } +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbCompletionRh", true); +#endif + + pRh->pIRhPort->pfnXferCompletion(pRh->pIRhPort, pUrb); + if (pUrb->enmState == VUSBURBSTATE_REAPED) + { + LogFlow(("%s: vusbUrbCompletionRh: Freeing URB\n", pUrb->pszDesc)); + pUrb->pVUsb->pfnFree(pUrb); + } + + vusbRhR3ProcessFrame(pRh, true /* fCallback */); +} + + +/** + * Certain control requests must not ever be forwarded to the device because + * they are required by the vusb core in order to maintain the vusb internal + * data structures. + */ +DECLINLINE(bool) vusbUrbIsRequestSafe(PCVUSBSETUP pSetup, PVUSBURB pUrb) +{ + if ((pSetup->bmRequestType & VUSB_REQ_MASK) != VUSB_REQ_STANDARD) + return true; + + switch (pSetup->bRequest) + { + case VUSB_REQ_CLEAR_FEATURE: + return pUrb->EndPt != 0 /* not default control pipe */ + || pSetup->wValue != 0 /* not ENDPOINT_HALT */ + || !pUrb->pVUsb->pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint; /* not special need for backend */ + case VUSB_REQ_SET_ADDRESS: + case VUSB_REQ_SET_CONFIGURATION: + case VUSB_REQ_GET_CONFIGURATION: + case VUSB_REQ_SET_INTERFACE: + case VUSB_REQ_GET_INTERFACE: + return false; + + /* + * If the device wishes it, we'll use the cached device and + * configuration descriptors. (We return false when we want to use the + * cache. Yeah, it's a bit weird to read.) + */ + case VUSB_REQ_GET_DESCRIPTOR: + return !vusbDevIsDescriptorInCache(pUrb->pVUsb->pDev, pSetup); + + default: + return true; + } +} + + +/** + * Queues an URB for asynchronous transfer. + * A list of asynchronous URBs is kept by the roothub. + * + * @returns VBox status code (from pfnUrbQueue). + * @param pUrb The URB. + */ +int vusbUrbQueueAsyncRh(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbQueueAsyncRh", false); +#endif + + /* Immediately return in case of error. + * XXX There is still a race: The Rh might vanish after this point! */ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBROOTHUB pRh = vusbDevGetRh(pDev); + if (!pRh) + { + Log(("vusbUrbQueueAsyncRh returning VERR_OBJECT_DESTROYED\n")); + return VERR_OBJECT_DESTROYED; + } + + RTCritSectEnter(&pDev->CritSectAsyncUrbs); + int rc = pDev->pUsbIns->pReg->pfnUrbQueue(pDev->pUsbIns, pUrb); + if (RT_FAILURE(rc)) + { + LogFlow(("%s: vusbUrbQueueAsyncRh: returns %Rrc (queue_urb)\n", pUrb->pszDesc, rc)); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + return rc; + } + + ASMAtomicIncU32(&pDev->aPipes[pUrb->EndPt].async); + + /* Queue the Urb on the roothub */ + RTListAppend(&pDev->LstAsyncUrbs, &pUrb->pVUsb->NdLst); + RTCritSectLeave(&pDev->CritSectAsyncUrbs); + + return VINF_SUCCESS; +} + + +/** + * Send a control message *synchronously*. + */ +static void vusbMsgSubmitSynchronously(PVUSBURB pUrb, bool fSafeRequest) +{ + PVUSBDEV pDev = pUrb->pVUsb->pDev; + Assert(pDev); + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + PVUSBSETUP pSetup = pExtra->pMsg; + LogFlow(("%s: vusbMsgSubmitSynchronously: pDev=%p[%s]\n", pUrb->pszDesc, pDev, pDev->pUsbIns ? pDev->pUsbIns->pszName : "")); + + uint8_t *pbData = (uint8_t *)pExtra->pMsg + sizeof(*pSetup); + uint32_t cbData = pSetup->wLength; + bool fOk = false; + if (!fSafeRequest) + fOk = vusbDevStandardRequest(pDev, pUrb->EndPt, pSetup, pbData, &cbData); + else + AssertMsgFailed(("oops\n")); + + pUrb->enmState = VUSBURBSTATE_REAPED; + if (fOk) + { + pSetup->wLength = cbData; + pUrb->enmStatus = VUSBSTATUS_OK; + pExtra->fOk = true; + } + else + { + pUrb->enmStatus = VUSBSTATUS_STALL; + pExtra->fOk = false; + } + pExtra->cbLeft = cbData; /* used by IN only */ + + vusbCtrlCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + + /* + * 'Free' the message URB, i.e. put it back to the allocated state. + */ + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +} + +/** + * Callback for dealing with device reset. + */ +void vusbMsgResetExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + pExtra->enmStage = CTLSTAGE_SETUP; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } +} + + +/** + * Callback to free a cancelled message URB. + * + * This is yet another place we're we have to performance acrobatics to + * deal with cancelled URBs. sigh. + * + * The deal here is that we never free message URBs since they are integrated + * into the message pipe state. But since cancel can leave URBs unreaped and in + * a state which require them not to be freed, we'll have to do two things. + * First, if a new message URB is processed we'll have to get a new message + * pipe state. Second, we cannot just free the damn state structure because + * that might lead to heap corruption since it might still be in-flight. + * + * The URB embedded into the message pipe control structure will start in an + * ALLOCATED state. When submitted it will be go to the IN-FLIGHT state. When + * reaped it will go from REAPED to ALLOCATED. When completed in the CANCELLED + * state it will remain in that state (as does normal URBs). + * + * If a new message urb comes up while it's in the CANCELLED state, we will + * orphan it and it will be freed here in vusbMsgFreeUrb. We indicate this + * by setting pVUsb->pvFreeCtx to NULL. + * + * If we have to free the message state structure because of device destruction, + * configuration changes, or similar, we will orphan the message pipe state in + * the same way by setting pVUsb->pvFreeCtx to NULL and let this function free it. + * + * @param pUrb + */ +static DECLCALLBACK(void) vusbMsgFreeUrb(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + PVUSBCTRLEXTRA pExtra = (PVUSBCTRLEXTRA)((uint8_t *)pUrb - RT_UOFFSETOF(VUSBCTRLEXTRA, Urb)); + if ( pUrb->enmState == VUSBURBSTATE_CANCELLED + && !pUrb->pVUsb->pvFreeCtx) + { + LogFlow(("vusbMsgFreeUrb: Freeing orphan: %p (pUrb=%p)\n", pExtra, pUrb)); + RTMemFree(pExtra); + } + else + { + Assert(pUrb->pVUsb->pvFreeCtx == &pExtra->Urb); + pUrb->enmState = VUSBURBSTATE_ALLOCATED; + pUrb->fCompleting = false; + } +} + +/** + * Frees the extra state data associated with a message pipe. + * + * @param pExtra The data. + */ +void vusbMsgFreeExtraData(PVUSBCTRLEXTRA pExtra) +{ + if (!pExtra) + return; + if (pExtra->Urb.enmState != VUSBURBSTATE_CANCELLED) + { + pExtra->Urb.u32Magic = 0; + pExtra->Urb.enmState = VUSBURBSTATE_FREE; + if (pExtra->Urb.pszDesc) + RTStrFree(pExtra->Urb.pszDesc); + RTMemFree(pExtra); + } + else + pExtra->Urb.pVUsb->pvFreeCtx = NULL; /* see vusbMsgFreeUrb */ +} + +/** + * Allocates the extra state data required for a control pipe. + * + * @returns Pointer to the allocated and initialized state data. + * @returns NULL on out of memory condition. + * @param pUrb A URB we can copy default data from. + */ +static PVUSBCTRLEXTRA vusbMsgAllocExtraData(PVUSBURB pUrb) +{ +/** @todo reuse these? */ + PVUSBCTRLEXTRA pExtra; + /* The initial allocation tries to balance wasted memory versus the need to re-allocate + * the message data. Experience shows that an 8K initial allocation in practice never needs + * to be expanded but almost certainly wastes 4K or more memory. + */ + const size_t cbMax = _2K + sizeof(VUSBSETUP); + pExtra = (PVUSBCTRLEXTRA)RTMemAllocZ(RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbMax])); + if (pExtra) + { + pExtra->enmStage = CTLSTAGE_SETUP; + //pExtra->fOk = false; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->pbCur = (uint8_t *)(pExtra->pMsg + 1); + //pExtra->cbLeft = 0; + pExtra->cbMax = cbMax; + + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.u32Magic = VUSBURB_MAGIC; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; +#ifdef LOG_ENABLED + RTStrAPrintf(&pExtra->Urb.pszDesc, "URB %p msg->%p", &pExtra->Urb, pUrb); +#endif + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + //pExtra->Urb.pVUsb->pCtrlUrb = NULL; + //pExtra->Urb.pVUsb->pNext = NULL; + //pExtra->Urb.pVUsb->ppPrev = NULL; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->Urb.pVUsb->pDev = pUrb->pVUsb->pDev; + pExtra->Urb.pVUsb->pfnFree = vusbMsgFreeUrb; + pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; + //pExtra->Urb.Hci = {0}; + //pExtra->Urb.Dev.pvProxyUrb = NULL; + pExtra->Urb.DstAddress = pUrb->DstAddress; + pExtra->Urb.EndPt = pUrb->EndPt; + pExtra->Urb.enmType = VUSBXFERTYPE_MSG; + pExtra->Urb.enmDir = VUSBDIRECTION_INVALID; + //pExtra->Urb.fShortNotOk = false; + pExtra->Urb.enmStatus = VUSBSTATUS_INVALID; + //pExtra->Urb.cbData = 0; + vusbUrbAssert(&pExtra->Urb); + } + return pExtra; +} + +/** + * Sets up the message. + * + * The message is associated with the pipe, in what's currently called + * control pipe extra state data (pointed to by pPipe->pCtrl). If this + * is a OUT message, we will no go on collecting data URB. If it's a + * IN message, we'll send it and then queue any incoming data for the + * URBs collecting it. + * + * @returns Success indicator. + */ +static bool vusbMsgSetup(PVUSBPIPE pPipe, const void *pvBuf, uint32_t cbBuf) +{ + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + const VUSBSETUP *pSetupIn = (PVUSBSETUP)pvBuf; + + /* + * Validate length. + */ + if (cbBuf < sizeof(VUSBSETUP)) + { + LogFlow(("vusbMsgSetup: pPipe=%p cbBuf=%u < %u (failure) !!!\n", + pPipe, cbBuf, sizeof(VUSBSETUP))); + return false; + } + + /* Paranoia: Clear data memory that was previously used + * by the guest. See @bugref{10438}. + */ + PVUSBSETUP pOldSetup = pExtra->pMsg; + uint32_t cbClean = sizeof(VUSBSETUP) + pOldSetup->wLength; + cbClean = RT_MIN(cbClean, pExtra->cbMax); + memset(pExtra->Urb.abData, 0, cbClean); + + /* + * Check if we've got an cancelled message URB. Allocate a new one in that case. + */ + if (pExtra->Urb.enmState == VUSBURBSTATE_CANCELLED) + { + void *pvNew = RTMemDup(pExtra, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax])); + if (!pvNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%zu\n", RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax]))); + return false; + } + pExtra->Urb.pVUsb->pvFreeCtx = NULL; + LogFlow(("vusbMsgSetup: Replacing canceled pExtra=%p with %p.\n", pExtra, pvNew)); + pPipe->pCtrl = pExtra = (PVUSBCTRLEXTRA)pvNew; + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->pMsg = (PVUSBSETUP)pExtra->Urb.abData; + pExtra->Urb.enmState = VUSBURBSTATE_ALLOCATED; + pExtra->Urb.fCompleting = false; + } + + /* + * Check that we've got sufficient space in the message URB. + */ + if (pExtra->cbMax < cbBuf + pSetupIn->wLength) + { + uint32_t cbReq = RT_ALIGN_32(cbBuf + pSetupIn->wLength, 64); + PVUSBCTRLEXTRA pNew = (PVUSBCTRLEXTRA)RTMemReallocZ(pExtra, + RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[pExtra->cbMax]), + RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq])); + if (!pNew) + { + Log(("vusbMsgSetup: out of memory!!! cbReq=%u %zu\n", + cbReq, RT_UOFFSETOF_DYN(VUSBCTRLEXTRA, Urb.abData[cbReq]))); + return false; + } + if (pExtra != pNew) + { + LogFunc(("Reallocated %u -> %u\n", pExtra->cbMax, cbReq)); + pNew->pMsg = (PVUSBSETUP)pNew->Urb.abData; + pExtra = pNew; + pPipe->pCtrl = pExtra; + pExtra->Urb.pVUsb = &pExtra->VUsbExtra; + pExtra->Urb.pVUsb->pUrb = &pExtra->Urb; + pExtra->Urb.pVUsb->pvFreeCtx = &pExtra->Urb; + } + + pExtra->cbMax = cbReq; + } + Assert(pExtra->Urb.enmState == VUSBURBSTATE_ALLOCATED); + + /* + * Copy the setup data and prepare for data. + */ + PVUSBSETUP pSetup = pExtra->pMsg; + pExtra->fSubmitted = false; + pExtra->Urb.enmState = VUSBURBSTATE_IN_FLIGHT; + pExtra->pbCur = (uint8_t *)(pSetup + 1); + pSetup->bmRequestType = pSetupIn->bmRequestType; + pSetup->bRequest = pSetupIn->bRequest; + pSetup->wValue = RT_LE2H_U16(pSetupIn->wValue); + pSetup->wIndex = RT_LE2H_U16(pSetupIn->wIndex); + pSetup->wLength = RT_LE2H_U16(pSetupIn->wLength); + + LogFlow(("vusbMsgSetup(%p,,%d): bmRequestType=%#04x bRequest=%#04x wValue=%#06x wIndex=%#06x wLength=0x%.4x\n", + pPipe, cbBuf, pSetup->bmRequestType, pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength)); + return true; +} + +/** + * Build the message URB from the given control URB and accompanying message + * pipe state which we grab from the device for the URB. + * + * @param pUrb The URB to submit. + * @param pSetup The setup packet for the message transfer. + * @param pExtra Pointer to the additional state requred for a control transfer. + * @param pPipe The message pipe state. + */ +static void vusbMsgDoTransfer(PVUSBURB pUrb, PVUSBSETUP pSetup, PVUSBCTRLEXTRA pExtra, PVUSBPIPE pPipe) +{ + RT_NOREF(pPipe); + + /* + * Mark this transfer as sent (cleared at setup time). + */ + Assert(!pExtra->fSubmitted); + pExtra->fSubmitted = true; + + /* + * Do we have to do this synchronously? + */ + bool fSafeRequest = vusbUrbIsRequestSafe(pSetup, pUrb); + if (!fSafeRequest) + { + vusbMsgSubmitSynchronously(pUrb, fSafeRequest); + return; + } + + /* + * Do it asynchronously. + */ + LogFlow(("%s: vusbMsgDoTransfer: ep=%d pMsgUrb=%p pPipe=%p stage=%s\n", + pUrb->pszDesc, pUrb->EndPt, &pExtra->Urb, pPipe, g_apszCtlStates[pExtra->enmStage])); + Assert(pExtra->Urb.enmType == VUSBXFERTYPE_MSG); + Assert(pExtra->Urb.EndPt == pUrb->EndPt); + pExtra->Urb.enmDir = (pSetup->bmRequestType & VUSB_DIR_TO_HOST) ? VUSBDIRECTION_IN : VUSBDIRECTION_OUT; + pExtra->Urb.cbData = pSetup->wLength + sizeof(*pSetup); + pExtra->Urb.pVUsb->pCtrlUrb = pUrb; + int rc = vusbUrbQueueAsyncRh(&pExtra->Urb); + if (RT_FAILURE(rc)) + { + /* + * If we fail submitting it, will not retry but fail immediately. + * + * This keeps things simple. The host OS will have retried if + * it's a proxied device, and if it's a virtual one it really means + * it if it's failing a control message. + */ + LogFlow(("%s: vusbMsgDoTransfer: failed submitting urb! failing it with %s (rc=%Rrc)!!!\n", + pUrb->pszDesc, rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? "DNR" : "CRC", rc)); + pExtra->Urb.enmStatus = rc == VERR_VUSB_DEVICE_NOT_ATTACHED ? VUSBSTATUS_DNR : VUSBSTATUS_CRC; + pExtra->Urb.enmState = VUSBURBSTATE_REAPED; + vusbMsgCompletion(&pExtra->Urb); + } +} + +/** + * Fails a URB request with a pipe STALL error. + * + * @returns VINF_SUCCESS indicating that we've completed the URB. + * @param pUrb The URB in question. + */ +static int vusbMsgStall(PVUSBURB pUrb) +{ + PVUSBPIPE pPipe = &pUrb->pVUsb->pDev->aPipes[pUrb->EndPt]; + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + LogFlow(("%s: vusbMsgStall: pPipe=%p err=STALL stage %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + + pExtra->pbCur = NULL; + pExtra->enmStage = CTLSTAGE_SETUP; + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_STALL; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + +/** + * Submit a control message. + * + * Here we implement the USB defined traffic that occurs in message pipes + * (aka control endpoints). We want to provide a single function for device + * drivers so that they don't all have to reimplement the usb logic for + * themselves. This means we need to keep a little bit of state information + * because control transfers occur over multiple bus transactions. We may + * also need to buffer data over multiple data stages. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitCtrl(PVUSBURB pUrb) +{ +#ifdef LOG_ENABLED + vusbUrbTrace(pUrb, "vusbUrbSubmitCtrl", false); +#endif + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = &pDev->aPipes[pUrb->EndPt]; + + RTCritSectEnter(&pPipe->CritSectCtrl); + PVUSBCTRLEXTRA pExtra = pPipe->pCtrl; + + if (!pExtra && !(pExtra = pPipe->pCtrl = vusbMsgAllocExtraData(pUrb))) + { + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_VUSB_NO_URB_MEMORY; + } + PVUSBSETUP pSetup = pExtra->pMsg; + + if (pPipe->async) + { + AssertMsgFailed(("%u\n", pPipe->async)); + RTCritSectLeave(&pPipe->CritSectCtrl); + return VERR_GENERAL_FAILURE; + } + + /* + * A setup packet always resets the transaction and the + * end of data transmission is signified by change in + * data direction. + */ + if (pUrb->enmDir == VUSBDIRECTION_SETUP) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->SETUP\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_SETUP; + } + else if ( pExtra->enmStage == CTLSTAGE_DATA + /* (the STATUS stage direction goes the other way) */ + && !!(pSetup->bmRequestType & VUSB_DIR_TO_HOST) != (pUrb->enmDir == VUSBDIRECTION_IN)) + { + LogFlow(("%s: vusbUrbSubmitCtrl: pPipe=%p state %s->STATUS\n", + pUrb->pszDesc, pPipe, g_apszCtlStates[pExtra->enmStage])); + pExtra->enmStage = CTLSTAGE_STATUS; + } + + /* + * Act according to the current message stage. + */ + switch (pExtra->enmStage) + { + case CTLSTAGE_SETUP: + /* + * When stall handshake is returned, all subsequent packets + * must generate stall until a setup packet arrives. + */ + if (pUrb->enmDir != VUSBDIRECTION_SETUP) + { + Log(("%s: vusbUrbSubmitCtrl: Stall at setup stage (dir=%#x)!!\n", pUrb->pszDesc, pUrb->enmDir)); + vusbMsgStall(pUrb); + break; + } + + /* Store setup details, return DNR if corrupt */ + if (!vusbMsgSetup(pPipe, pUrb->abData, pUrb->cbData)) + { + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + break; + } + if (pPipe->pCtrl != pExtra) + { + pExtra = pPipe->pCtrl; + pSetup = pExtra->pMsg; + } + + /* pre-buffer our output if it's device-to-host */ + if (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + else if (pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: need data\n", pUrb->pszDesc)); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgSetupCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + /* + * If there is no DATA stage, we must send it now since there are + * no requirement of a STATUS stage. + */ + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=SETUP - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + + case CTLSTAGE_DATA: + { + /* + * If a data stage exceeds the target buffer indicated in + * setup return stall, if data stage returns stall there + * will be no status stage. + */ + uint8_t *pbData = (uint8_t *)(pExtra->pMsg + 1); + if ((uintptr_t)&pExtra->pbCur[pUrb->cbData] > (uintptr_t)&pbData[pSetup->wLength]) + { + /* In the device -> host direction, the device never returns more data than + what was requested (wLength). So, we can just cap cbData. */ + ssize_t const cbLeft = &pbData[pSetup->wLength] - pExtra->pbCur; + if (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + { + LogFlow(("%s: vusbUrbSubmitCtrl: Adjusting DATA request: %d -> %d\n", pUrb->pszDesc, pUrb->cbData, cbLeft)); + pUrb->cbData = cbLeft >= 0 ? (uint32_t)cbLeft : 0; + } + /* In the host -> direction it's undefined what happens if the host provides + more data than what wLength inidicated. However, in 2007, iPhone detection + via iTunes would issue wLength=0 but provide a data URB which we needed to + pass on to the device anyway, so we'll just quietly adjust wLength if it's + zero and get on with the work. + + What confuses me (bird) here, though, is that we've already sent the SETUP + URB to the device when we received it, and all we end up doing is an + unnecessary memcpy and completing the URB, but never actually sending the + data to the device. So, I guess this stuff is still a little iffy. + + Note! We currently won't be doing any resizing, as we've disabled resizing + in general. + P.S. We used to have a very strange (pUrb->cbData % pSetup->wLength) == 0 + thing too that joined the pUrb->cbData adjusting above. */ + else if ( pSetup->wLength == 0 + && pUrb->cbData <= pExtra->cbMax) + { + Log(("%s: vusbUrbSubmitCtrl: pAdjusting wLength: %u -> %u (iPhone hack)\n", + pUrb->pszDesc, pSetup->wLength, pUrb->cbData)); + pSetup->wLength = pUrb->cbData; + Assert(cbLeft >= (ssize_t)pUrb->cbData); + } + else + { + Log(("%s: vusbUrbSubmitCtrl: Stall at data stage!! wLength=%u cbData=%d cbMax=%d cbLeft=%dz\n", + pUrb->pszDesc, pSetup->wLength, pUrb->cbData, pExtra->cbMax, cbLeft)); + vusbMsgStall(pUrb); + break; + } + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + /* put data received from the device. */ + const uint32_t cbRead = RT_MIN(pUrb->cbData, pExtra->cbLeft); + memcpy(pUrb->abData, pExtra->pbCur, cbRead); + + /* advance */ + pExtra->pbCur += cbRead; + if (pUrb->cbData == cbRead) + pExtra->cbLeft -= pUrb->cbData; + else + { + /* adjust the pUrb->cbData to reflect the number of bytes containing actual data. */ + LogFlow(("%s: vusbUrbSubmitCtrl: adjusting last DATA pUrb->cbData, %d -> %d\n", + pUrb->pszDesc, pUrb->cbData, pExtra->cbLeft)); + pUrb->cbData = cbRead; + pExtra->cbLeft = 0; + } + } + else + { + /* get data for sending when completed. */ + AssertStmt((ssize_t)pUrb->cbData <= pExtra->cbMax - (pExtra->pbCur - pbData), /* paranoia: checked above */ + pUrb->cbData = pExtra->cbMax - (uint32_t)RT_MIN(pExtra->pbCur - pbData, pExtra->cbMax)); + memcpy(pExtra->pbCur, pUrb->abData, pUrb->cbData); + + /* advance */ + pExtra->pbCur += pUrb->cbData; + + /* + * If we've got the necessary data, we'll send it now since there are + * no requirement of a STATUS stage. + */ + if ( !pExtra->fSubmitted + && pExtra->pbCur - pbData >= pSetup->wLength) + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=DATA - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + break; + } + } + + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgDataCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + break; + } + + case CTLSTAGE_STATUS: + if ( (pSetup->bmRequestType & VUSB_DIR_TO_HOST) + || pExtra->fSubmitted) + { + Assert(pExtra->fSubmitted); + pUrb->enmState = VUSBURBSTATE_REAPED; + vusbMsgStatusCompletion(pUrb); + vusbUrbCompletionRh(pUrb); + } + else + { + LogFlow(("%s: vusbUrbSubmitCtrl: stage=STATUS - to dev: sending\n", pUrb->pszDesc)); + vusbMsgDoTransfer(pUrb, pSetup, pExtra, pPipe); + } + break; + } + + RTCritSectLeave(&pPipe->CritSectCtrl); + return VINF_SUCCESS; +} + + +/** + * Submit a interrupt URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitInterrupt(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitInterrupt: (sync)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit a bulk URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitBulk(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitBulk: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Submit an isochronous URB. + * + * @returns VBox status code. + * @param pUrb The URB to submit. + */ +static int vusbUrbSubmitIsochronous(PVUSBURB pUrb) +{ + LogFlow(("%s: vusbUrbSubmitIsochronous: (async)\n", pUrb->pszDesc)); + return vusbUrbQueueAsyncRh(pUrb); +} + + +/** + * Fail a URB with a 'hard-error' sort of error. + * + * @return VINF_SUCCESS (the Urb status indicates the error). + * @param pUrb The URB. + */ +int vusbUrbSubmitHardError(PVUSBURB pUrb) +{ + /* FIXME: Find out the correct return code from the spec */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_DNR; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; +} + + +/** + * Submit a URB. + */ +int vusbUrbSubmit(PVUSBURB pUrb) +{ + vusbUrbAssert(pUrb); + Assert(pUrb->enmState == VUSBURBSTATE_ALLOCATED); + PVUSBDEV pDev = pUrb->pVUsb->pDev; + PVUSBPIPE pPipe = NULL; + Assert(pDev); + + /* + * Check that the device is in a valid state. + */ + const VUSBDEVICESTATE enmState = vusbDevGetState(pDev); + if (enmState == VUSB_DEVICE_STATE_RESET) + { + LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName)); + pUrb->enmStatus = VUSBSTATUS_DNR; + /* This will postpone the TDs until we're done with the resetting. */ + return VERR_VUSB_DEVICE_IS_RESETTING; + } + +#ifdef LOG_ENABLED + /* stamp it */ + pUrb->pVUsb->u64SubmitTS = RTTimeNanoTS(); +#endif + + /** @todo Check max packet size here too? */ + + /* + * Validate the pipe. + */ + if (pUrb->EndPt >= VUSB_PIPE_MAX) + { + Log(("%s: pDev=%p[%s]: SUBMIT: ep %i >= %i!!!\n", pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, pUrb->EndPt, VUSB_PIPE_MAX)); + return vusbUrbSubmitHardError(pUrb); + } + PCVUSBDESCENDPOINTEX pEndPtDesc; + switch (pUrb->enmDir) + { + case VUSBDIRECTION_IN: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].in; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + case VUSBDIRECTION_SETUP: + case VUSBDIRECTION_OUT: + default: + pEndPtDesc = pDev->aPipes[pUrb->EndPt].out; + pPipe = &pDev->aPipes[pUrb->EndPt]; + break; + } + if (!pEndPtDesc) + { + Log(("%s: pDev=%p[%s]: SUBMIT: no endpoint!!! dir=%s e=%i\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbDirName(pUrb->enmDir), pUrb->EndPt)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * Check for correct transfer types. + * Our type codes are the same - what a coincidence. + */ + if ((pEndPtDesc->Core.bmAttributes & 0x3) != pUrb->enmType) + { + /* Bulk and interrupt transfers are identical on the bus level (the only difference + * is in how they are scheduled by the HCD/HC) and need an exemption. + * Atheros AR9271 is a known offender; its configuration descriptors include + * interrupt endpoints, but drivers (Win7/8, Linux kernel pre-3.05) treat them + * as bulk endpoints. + */ + if ( (pUrb->enmType == VUSBXFERTYPE_BULK && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_INTR) + || (pUrb->enmType == VUSBXFERTYPE_INTR && (pEndPtDesc->Core.bmAttributes & 0x3) == VUSBXFERTYPE_BULK)) + { + Log2(("%s: pDev=%p[%s]: SUBMIT: mixing bulk/interrupt transfers on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + } + else + { + Log(("%s: pDev=%p[%s]: SUBMIT: %s transfer requested for %#x endpoint on DstAddress=%i ep=%i dir=%s\n", + pUrb->pszDesc, pDev, pDev->pUsbIns->pszName, vusbUrbTypeName(pUrb->enmType), pEndPtDesc->Core.bmAttributes, + pUrb->DstAddress, pUrb->EndPt, vusbUrbDirName(pUrb->enmDir))); + return vusbUrbSubmitHardError(pUrb); + } + } + + /* + * If there's a URB in the read-ahead buffer, use it. + */ + int rc; + + if (pDev->hSniffer) + { + rc = VUSBSnifferRecordEvent(pDev->hSniffer, pUrb, VUSBSNIFFEREVENT_SUBMIT); + if (RT_FAILURE(rc)) + LogRel(("VUSB: Capturing URB submit event failed with %Rrc\n", rc)); + } + + /* + * Take action based on type. + */ + pUrb->enmState = VUSBURBSTATE_IN_FLIGHT; + switch (pUrb->enmType) + { + case VUSBXFERTYPE_CTRL: + rc = vusbUrbSubmitCtrl(pUrb); + break; + case VUSBXFERTYPE_BULK: + rc = vusbUrbSubmitBulk(pUrb); + break; + case VUSBXFERTYPE_INTR: + rc = vusbUrbSubmitInterrupt(pUrb); + break; + case VUSBXFERTYPE_ISOC: + rc = vusbUrbSubmitIsochronous(pUrb); + break; + default: + AssertMsgFailed(("Unexpected pUrb type %d\n", pUrb->enmType)); + return vusbUrbSubmitHardError(pUrb); + } + + /* + * The device was detached, so we fail everything. + * (We should really detach and destroy the device, but we'll have to wait till Main reacts.) + */ + if (rc == VERR_VUSB_DEVICE_NOT_ATTACHED) + rc = vusbUrbSubmitHardError(pUrb); + /* + * We don't increment error count if async URBs are in flight, in + * this case we just assume we need to throttle back, this also + * makes sure we don't halt bulk endpoints at the wrong time. + */ + else if ( RT_FAILURE(rc) + && !ASMAtomicReadU32(&pDev->aPipes[pUrb->EndPt].async) + /* && pUrb->enmType == VUSBXFERTYPE_BULK ?? */ + && !vusbUrbErrorRh(pUrb)) + { + /* don't retry it anymore. */ + pUrb->enmState = VUSBURBSTATE_REAPED; + pUrb->enmStatus = VUSBSTATUS_CRC; + vusbUrbCompletionRh(pUrb); + return VINF_SUCCESS; + } + + return rc; +} + + +/** + * Reap in-flight URBs. + * + * @param pUrbLst Pointer to the head of the URB list. + * @param cMillies Number of milliseconds to block in each reap operation. + * Use 0 to not block at all. + */ +void vusbUrbDoReapAsync(PRTLISTANCHOR pUrbLst, RTMSINTERVAL cMillies) +{ + PVUSBURBVUSB pVUsbUrb = RTListGetFirst(pUrbLst, VUSBURBVUSBINT, NdLst); + while (pVUsbUrb) + { + vusbUrbAssert(pVUsbUrb->pUrb); + PVUSBURBVUSB pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrb, VUSBURBVUSBINT, NdLst); + PVUSBDEV pDev = pVUsbUrb->pDev; + + /* Don't touch resetting devices - paranoid safety precaution. */ + if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET) + { + /* + * Reap most URBs pending on a single device. + */ + PVUSBURB pRipe; + + /** + * This is workaround for race(should be fixed) detach on one EMT thread and frame boundary timer on other + * and leaked URBs (shouldn't be affected by leaked URBs). + */ + Assert(pDev->pUsbIns); + while ( pDev->pUsbIns + && ((pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, cMillies)) != NULL)) + { + vusbUrbAssert(pRipe); + if (pVUsbUrbNext && pRipe == pVUsbUrbNext->pUrb) + pVUsbUrbNext = RTListGetNext(pUrbLst, pVUsbUrbNext, VUSBURBVUSBINT, NdLst); + vusbUrbRipe(pRipe); + } + } + + /* next */ + pVUsbUrb = pVUsbUrbNext; + } +} + +/** + * Reap URBs on a per device level. + * + * @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: + */ + -- cgit v1.2.3