VUSBDevice.cpp revision dcc837f3a6f10996beb8aa7965f67f7f6c273fb4
/* $Id$ */
/** @file
* Virtual USB - Device.
*/
/*
* Copyright (C) 2006-2012 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_VUSB
#include <VBox/vmm/pdm.h>
#include <VBox/vmm/vmapi.h>
#include <VBox/err.h>
#include <VBox/log.h>
#include <iprt/alloc.h>
#include <iprt/time.h>
#include <iprt/thread.h>
#include <iprt/semaphore.h>
#include <iprt/string.h>
#include <iprt/assert.h>
#include <iprt/asm.h>
#include "VUSBInternal.h"
#include "VUSBSniffer.h"
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
* Argument package of vusbDevResetThread().
*/
typedef struct vusb_reset_args
{
/** Pointer to the device which is being reset. */
PVUSBDEV pDev;
/** The reset return code. */
int rc;
/** Pointer to the completion callback. */
PFNVUSBRESETDONE pfnDone;
/** User argument to pfnDone. */
void *pvUser;
} VUSBRESETARGS, *PVUSBRESETARGS;
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Default message pipe. */
const VUSBDESCENDPOINTEX g_Endpoint0 =
{
{
/* .bLength = */ VUSB_DT_ENDPOINT_MIN_LEN,
/* .bDescriptorType = */ VUSB_DT_ENDPOINT,
/* .bEndpointAddress = */ 0,
/* .bmAttributes = */ 0,
/* .wMaxPacketSize = */ 64,
/* .bInterval = */ 0
},
NULL
};
/** Default configuration. */
const VUSBDESCCONFIGEX g_Config0 =
{
{
/* .bLength = */ VUSB_DT_CONFIG_MIN_LEN,
/* .bDescriptorType = */ VUSB_DT_CONFIG,
/* .WTotalLength = */ 0, /* (auto-calculated) */
/* .bNumInterfaces = */ 0,
/* .bConfigurationValue =*/ 0,
/* .iConfiguration = */ 0,
/* .bmAttributes = */ 0x80,
/* .MaxPower = */ 14
},
NULL,
NULL
};
static PCVUSBDESCCONFIGEX vusbDevFindCfgDesc(PVUSBDEV pDev, int iCfg)
{
if (iCfg == 0)
return &g_Config0;
for (unsigned i = 0; i < pDev->pDescCache->pDevice->bNumConfigurations; i++)
if (pDev->pDescCache->paConfigs[i].Core.bConfigurationValue == iCfg)
return &pDev->pDescCache->paConfigs[i];
return NULL;
}
static PVUSBINTERFACESTATE vusbDevFindIfState(PVUSBDEV pDev, int iIf)
{
for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++)
if (pDev->paIfStates[i].pIf->paSettings[0].Core.bInterfaceNumber == iIf)
return &pDev->paIfStates[i];
return NULL;
}
static PCVUSBDESCINTERFACEEX vusbDevFindAltIfDesc(PVUSBDEV pDev, PCVUSBINTERFACESTATE pIfState, int iAlt)
{
for (uint32_t i = 0; i < pIfState->pIf->cSettings; i++)
if (pIfState->pIf->paSettings[i].Core.bAlternateSetting == iAlt)
return &pIfState->pIf->paSettings[i];
return NULL;
}
void vusbDevMapEndpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc)
{
uint8_t i8Addr = pEndPtDesc->Core.bEndpointAddress & 0xF;
PVUSBPIPE pPipe = &pDev->aPipes[i8Addr];
LogFlow(("vusbDevMapEndpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n",
pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes,
pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3]));
if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0)
{
Log(("vusb: map message pipe on address %u\n", i8Addr));
pPipe->in = pEndPtDesc;
pPipe->out = pEndPtDesc;
}
else if (pEndPtDesc->Core.bEndpointAddress & 0x80)
{
Log(("vusb: map input pipe on address %u\n", i8Addr));
pPipe->in = pEndPtDesc;
#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) || defined(RT_OS_DARWIN)
/*
* For high-speed isochronous input endpoints, spin off a read-ahead buffering thread.
*/
if ((pEndPtDesc->Core.bmAttributes & 0x03) == 1)
pPipe->hReadAhead = vusbReadAheadStart(pDev, pPipe);
#endif
}
else
{
Log(("vusb: map output pipe on address %u\n", i8Addr));
pPipe->out = pEndPtDesc;
}
if (pPipe->pCtrl)
{
vusbMsgFreeExtraData(pPipe->pCtrl);
pPipe->pCtrl = NULL;
}
}
static void unmap_endpoint(PVUSBDEV pDev, PCVUSBDESCENDPOINTEX pEndPtDesc)
{
uint8_t EndPt = pEndPtDesc->Core.bEndpointAddress & 0xF;
PVUSBPIPE pPipe = &pDev->aPipes[EndPt];
LogFlow(("unmap_endpoint: pDev=%p[%s] pEndPtDesc=%p{.bEndpointAddress=%#x, .bmAttributes=%#x} p=%p stage %s->SETUP\n",
pDev, pDev->pUsbIns->pszName, pEndPtDesc, pEndPtDesc->Core.bEndpointAddress, pEndPtDesc->Core.bmAttributes,
pPipe, g_apszCtlStates[pPipe->pCtrl ? pPipe->pCtrl->enmStage : 3]));
if ((pEndPtDesc->Core.bmAttributes & 0x3) == 0)
{
Log(("vusb: unmap MSG pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
pPipe->in = NULL;
pPipe->out = NULL;
}
else if (pEndPtDesc->Core.bEndpointAddress & 0x80)
{
Log(("vusb: unmap IN pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
pPipe->in = NULL;
/* If there was a read-ahead thread associated with this endpoint, tell it to go away. */
if (pPipe->hReadAhead)
{
Log(("vusb: and tell read-ahead thread for the endpoint to terminate\n"));
vusbReadAheadStop(pPipe->hReadAhead);
pPipe->hReadAhead = NULL;
}
}
else
{
Log(("vusb: unmap OUT pipe from address %u (%#x)\n", EndPt, pEndPtDesc->Core.bEndpointAddress));
pPipe->out = NULL;
}
if (pPipe->pCtrl)
{
vusbMsgFreeExtraData(pPipe->pCtrl);
pPipe->pCtrl = NULL;
}
}
static void map_interface(PVUSBDEV pDev, PCVUSBDESCINTERFACEEX pIfDesc)
{
LogFlow(("map_interface: pDev=%p[%s] pIfDesc=%p:{.iInterface=%d, .bAlternateSetting=%d}\n",
pDev, pDev->pUsbIns->pszName, pIfDesc, pIfDesc->Core.iInterface, pIfDesc->Core.bAlternateSetting));
for (unsigned i = 0; i < pIfDesc->Core.bNumEndpoints; i++)
{
if ((pIfDesc->paEndpoints[i].Core.bEndpointAddress & 0xF) == VUSB_PIPE_DEFAULT)
Log(("vusb: Endpoint 0x%x on interface %u.%u tried to override the default message pipe!!!\n",
pIfDesc->paEndpoints[i].Core.bEndpointAddress, pIfDesc->Core.bInterfaceNumber, pIfDesc->Core.bAlternateSetting));
else
vusbDevMapEndpoint(pDev, &pIfDesc->paEndpoints[i]);
}
}
/**
* Worker that resets the pipe data on select config and detach.
*
* This leaves the critical section unmolested
*
* @param pPipe The pipe which data should be reset.
*/
static void vusbDevResetPipeData(PVUSBPIPE pPipe)
{
vusbMsgFreeExtraData(pPipe->pCtrl);
pPipe->pCtrl = NULL;
if (pPipe->hReadAhead)
{
vusbReadAheadStop(pPipe->hReadAhead);
pPipe->hReadAhead = NULL;
}
RT_ZERO(pPipe->in);
RT_ZERO(pPipe->out);
pPipe->async = 0;
}
bool vusbDevDoSelectConfig(PVUSBDEV pDev, PCVUSBDESCCONFIGEX pCfgDesc)
{
LogFlow(("vusbDevDoSelectConfig: pDev=%p[%s] pCfgDesc=%p:{.iConfiguration=%d}\n",
pDev, pDev->pUsbIns->pszName, pCfgDesc, pCfgDesc->Core.iConfiguration));
/*
* Clean up all pipes and interfaces.
*/
unsigned i;
for (i = 0; i < VUSB_PIPE_MAX; i++)
if (i != VUSB_PIPE_DEFAULT)
vusbDevResetPipeData(&pDev->aPipes[i]);
memset(pDev->paIfStates, 0, pCfgDesc->Core.bNumInterfaces * sizeof(pDev->paIfStates[0]));
/*
* Map in the default setting for every interface.
*/
for (i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
{
PCVUSBINTERFACE pIf;
struct vusb_interface_state *pIfState;
pIf = &pCfgDesc->paIfs[i];
pIfState = &pDev->paIfStates[i];
pIfState->pIf = pIf;
/*
* Find the 0 setting, if it is not present we just use
* the lowest numbered one.
*/
for (uint32_t j = 0; j < pIf->cSettings; j++)
{
if ( !pIfState->pCurIfDesc
|| pIf->paSettings[j].Core.bAlternateSetting < pIfState->pCurIfDesc->Core.bAlternateSetting)
pIfState->pCurIfDesc = &pIf->paSettings[j];
if (pIfState->pCurIfDesc->Core.bAlternateSetting == 0)
break;
}
if (pIfState->pCurIfDesc)
map_interface(pDev, pIfState->pCurIfDesc);
}
pDev->pCurCfgDesc = pCfgDesc;
if (pCfgDesc->Core.bmAttributes & 0x40)
pDev->u16Status |= (1 << VUSB_DEV_SELF_POWERED);
else
pDev->u16Status &= ~(1 << VUSB_DEV_SELF_POWERED);
return true;
}
/**
* Standard device request: SET_CONFIGURATION
* @returns success indicator.
*/
static bool vusbDevStdReqSetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
unsigned iCfg = pSetup->wValue & 0xff;
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
{
Log(("vusb: error: %s: SET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName, iCfg));
return false;
}
/*
* Check that the device is in a valid state.
* (The caller has already checked that it's not being reset.)
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState == VUSB_DEVICE_STATE_DEFAULT)
{
LogFlow(("vusbDevStdReqSetConfig: %s: default dev state !!?\n", pDev->pUsbIns->pszName));
return false;
}
PCVUSBDESCCONFIGEX pNewCfgDesc = vusbDevFindCfgDesc(pDev, iCfg);
if (!pNewCfgDesc)
{
Log(("vusb: error: %s: config %i not found !!!\n", pDev->pUsbIns->pszName, iCfg));
return false;
}
if (iCfg == 0)
vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS);
else
vusbDevSetState(pDev, VUSB_DEVICE_STATE_CONFIGURED);
if (pDev->pUsbIns->pReg->pfnUsbSetConfiguration)
{
int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetConfiguration, 5,
pDev->pUsbIns, pNewCfgDesc->Core.bConfigurationValue,
pDev->pCurCfgDesc, pDev->paIfStates, pNewCfgDesc);
if (RT_FAILURE(rc))
{
Log(("vusb: error: %s: failed to set config %i (%Rrc) !!!\n", pDev->pUsbIns->pszName, iCfg, rc));
return false;
}
}
Log(("vusb: %p[%s]: SET_CONFIGURATION: Selected config %u\n", pDev, pDev->pUsbIns->pszName, iCfg));
return vusbDevDoSelectConfig(pDev, pNewCfgDesc);
}
/**
* Standard device request: GET_CONFIGURATION
* @returns success indicator.
*/
static bool vusbDevStdReqGetConfig(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
{
Log(("vusb: error: %s: GET_CONFIGURATION - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
return false;
}
/*
* Check that the device is in a valid state.
* (The caller has already checked that it's not being reset.)
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if ( enmState != VUSB_DEVICE_STATE_CONFIGURED
&& enmState != VUSB_DEVICE_STATE_ADDRESS)
{
LogFlow(("vusbDevStdReqGetConfig: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState));
return false;
}
if (*pcbBuf < 1)
{
LogFlow(("vusbDevStdReqGetConfig: %s: no space for data!\n", pDev->pUsbIns->pszName));
return true;
}
uint8_t iCfg;
if (enmState == VUSB_DEVICE_STATE_ADDRESS)
iCfg = 0;
else
iCfg = pDev->pCurCfgDesc->Core.bConfigurationValue;
*pbBuf = iCfg;
*pcbBuf = 1;
LogFlow(("vusbDevStdReqGetConfig: %s: returns iCfg=%d\n", pDev->pUsbIns->pszName, iCfg));
return true;
}
/**
* Standard device request: GET_INTERFACE
* @returns success indicator.
*/
static bool vusbDevStdReqGetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE)
{
Log(("vusb: error: %s: GET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
return false;
}
/*
* Check that the device is in a valid state.
* (The caller has already checked that it's not being reset.)
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState != VUSB_DEVICE_STATE_CONFIGURED)
{
LogFlow(("vusbDevStdReqGetInterface: error: %s: invalid device state %d!!!\n", pDev->pUsbIns->pszName, enmState));
return false;
}
if (*pcbBuf < 1)
{
LogFlow(("vusbDevStdReqGetInterface: %s: no space for data!\n", pDev->pUsbIns->pszName));
return true;
}
for (unsigned i = 0; i < pDev->pCurCfgDesc->Core.bNumInterfaces; i++)
{
PCVUSBDESCINTERFACEEX pIfDesc = pDev->paIfStates[i].pCurIfDesc;
if ( pIfDesc
&& pSetup->wIndex == pIfDesc->Core.bInterfaceNumber)
{
*pbBuf = pIfDesc->Core.bAlternateSetting;
*pcbBuf = 1;
Log(("vusb: %s: GET_INTERFACE: %u.%u\n", pDev->pUsbIns->pszName, pIfDesc->Core.bInterfaceNumber, *pbBuf));
return true;
}
}
Log(("vusb: error: %s: GET_INTERFACE - unknown iface %u !!!\n", pDev->pUsbIns->pszName, pSetup->wIndex));
return false;
}
/**
* Standard device request: SET_INTERFACE
* @returns success indicator.
*/
static bool vusbDevStdReqSetInterface(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_INTERFACE)
{
Log(("vusb: error: %s: SET_INTERFACE - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
return false;
}
/*
* Check that the device is in a valid state.
* (The caller has already checked that it's not being reset.)
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState != VUSB_DEVICE_STATE_CONFIGURED)
{
LogFlow(("vusbDevStdReqSetInterface: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState));
return false;
}
/*
* Find the interface.
*/
uint8_t iIf = pSetup->wIndex;
PVUSBINTERFACESTATE pIfState = vusbDevFindIfState(pDev, iIf);
if (!pIfState)
{
LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find interface %u !!!\n", pDev->pUsbIns->pszName, iIf));
return false;
}
uint8_t iAlt = pSetup->wValue;
PCVUSBDESCINTERFACEEX pIfDesc = vusbDevFindAltIfDesc(pDev, pIfState, iAlt);
if (!pIfDesc)
{
LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u !!!\n", pDev->pUsbIns->pszName, iIf, iAlt));
return false;
}
if (pDev->pUsbIns->pReg->pfnUsbSetInterface)
{
int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbSetInterface, 3, pDev->pUsbIns, iIf, iAlt);
if (RT_FAILURE(rc))
{
LogFlow(("vusbDevStdReqSetInterface: error: %s: couldn't find alt interface %u.%u (%Rrc)\n", pDev->pUsbIns->pszName, iIf, iAlt, rc));
return false;
}
}
for (unsigned i = 0; i < pIfState->pCurIfDesc->Core.bNumEndpoints; i++)
unmap_endpoint(pDev, &pIfState->pCurIfDesc->paEndpoints[i]);
Log(("vusb: SET_INTERFACE: Selected %u.%u\n", iIf, iAlt));
map_interface(pDev, pIfDesc);
pIfState->pCurIfDesc = pIfDesc;
return true;
}
/**
* Standard device request: SET_ADDRESS
* @returns success indicator.
*/
static bool vusbDevStdReqSetAddress(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) != VUSB_TO_DEVICE)
{
Log(("vusb: error: %s: SET_ADDRESS - invalid request (dir) !!!\n", pDev->pUsbIns->pszName));
return false;
}
/*
* Check that the device is in a valid state.
* (The caller has already checked that it's not being reset.)
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if ( enmState != VUSB_DEVICE_STATE_DEFAULT
&& enmState != VUSB_DEVICE_STATE_ADDRESS)
{
LogFlow(("vusbDevStdReqSetAddress: error: %s: invalid device state %d !!!\n", pDev->pUsbIns->pszName, enmState));
return false;
}
pDev->u8NewAddress = pSetup->wValue;
return true;
}
/**
* Standard device request: CLEAR_FEATURE
* @returns success indicator.
*
* @remark This is only called for VUSB_TO_ENDPOINT && ep == 0 && wValue == ENDPOINT_HALT.
* All other cases of CLEAR_FEATURE is handled in the normal async/sync manner.
*/
static bool vusbDevStdReqClearFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
{
case VUSB_TO_DEVICE:
Log(("vusb: ClearFeature: dev(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
break;
case VUSB_TO_INTERFACE:
Log(("vusb: ClearFeature: iface(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
break;
case VUSB_TO_ENDPOINT:
Log(("vusb: ClearFeature: ep(%u): selector=%u\n", pSetup->wIndex, pSetup->wValue));
if ( !EndPt /* Default control pipe only */
&& pSetup->wValue == 0 /* ENDPOINT_HALT */
&& pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint)
{
int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)pDev->pUsbIns->pReg->pfnUsbClearHaltedEndpoint,
2, pDev->pUsbIns, pSetup->wIndex);
return RT_SUCCESS(rc);
}
break;
default:
AssertMsgFailed(("VUSB_TO_OTHER!\n"));
break;
}
AssertMsgFailed(("Invalid safe check !!!\n"));
return false;
}
/**
* Standard device request: SET_FEATURE
* @returns success indicator.
*/
static bool vusbDevStdReqSetFeature(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
{
case VUSB_TO_DEVICE:
Log(("vusb: SetFeature: dev(%u): selector=%u\n",
pSetup->wIndex, pSetup->wValue));
break;
case VUSB_TO_INTERFACE:
Log(("vusb: SetFeature: if(%u): selector=%u\n",
pSetup->wIndex, pSetup->wValue));
break;
case VUSB_TO_ENDPOINT:
Log(("vusb: SetFeature: ep(%u): selector=%u\n",
pSetup->wIndex, pSetup->wValue));
break;
default:
AssertMsgFailed(("VUSB_TO_OTHER!\n"));
return false;
}
AssertMsgFailed(("This stuff is bogus\n"));
return false;
}
static bool vusbDevStdReqGetStatus(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if (*pcbBuf != 2)
{
LogFlow(("vusbDevStdReqGetStatus: %s: buffer is too small! (%d)\n", pDev->pUsbIns->pszName, *pcbBuf));
return false;
}
uint16_t u16Status;
switch (pSetup->bmRequestType & VUSB_RECIP_MASK)
{
case VUSB_TO_DEVICE:
u16Status = pDev->u16Status;
LogFlow(("vusbDevStdReqGetStatus: %s: device status %#x (%d)\n", pDev->pUsbIns->pszName, u16Status, u16Status));
break;
case VUSB_TO_INTERFACE:
u16Status = 0;
LogFlow(("vusbDevStdReqGetStatus: %s: bogus interface status request!!\n", pDev->pUsbIns->pszName));
break;
case VUSB_TO_ENDPOINT:
u16Status = 0;
LogFlow(("vusbDevStdReqGetStatus: %s: bogus endpoint status request!!\n", pDev->pUsbIns->pszName));
break;
default:
AssertMsgFailed(("VUSB_TO_OTHER!\n"));
return false;
}
*(uint16_t *)pbBuf = u16Status;
return true;
}
/**
* Finds a cached string.
*
* @returns Pointer to the cached string if found. NULL if not.
* @param paLanguages The languages to search.
* @param cLanguages The number of languages in the table.
* @param idLang The language ID.
* @param iString The string index.
*/
static PCPDMUSBDESCCACHESTRING FindCachedString(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages,
uint16_t idLang, uint8_t iString)
{
/** @todo binary lookups! */
unsigned iCurLang = cLanguages;
while (iCurLang-- > 0)
if (paLanguages[iCurLang].idLang == idLang)
{
PCPDMUSBDESCCACHESTRING paStrings = paLanguages[iCurLang].paStrings;
unsigned iCurStr = paLanguages[iCurLang].cStrings;
while (iCurStr-- > 0)
if (paStrings[iCurStr].idx == iString)
return &paStrings[iCurStr];
break;
}
return NULL;
}
/** Macro for copying descriptor data. */
#define COPY_DATA(pbDst, cbLeft, pvSrc, cbSrc) \
do { \
uint32_t cbSrc_ = cbSrc; \
uint32_t cbCopy = RT_MIN(cbLeft, cbSrc_); \
memcpy(pbBuf, pvSrc, cbCopy); \
cbLeft -= cbCopy; \
if (!cbLeft) \
return; \
pbBuf += cbCopy; \
} while (0)
/**
* Internal function for reading the language IDs.
*/
static void ReadCachedStringDesc(PCPDMUSBDESCCACHESTRING pString, uint8_t *pbBuf, uint32_t *pcbBuf)
{
uint32_t cbLeft = *pcbBuf;
RTUTF16 wsz[128]; /* 128-1 => bLength=0xff */
PRTUTF16 pwsz = wsz;
size_t cwc;
int rc = RTStrToUtf16Ex(pString->psz, RT_ELEMENTS(wsz) - 1, &pwsz, RT_ELEMENTS(wsz), &cwc);
if (RT_FAILURE(rc))
{
AssertRC(rc);
wsz[0] = 'e';
wsz[1] = 'r';
wsz[2] = 'r';
cwc = 3;
}
VUSBDESCSTRING StringDesc;
StringDesc.bLength = (uint8_t)(sizeof(StringDesc) + cwc * sizeof(RTUTF16));
StringDesc.bDescriptorType = VUSB_DT_STRING;
COPY_DATA(pbBuf, cbLeft, &StringDesc, sizeof(StringDesc));
COPY_DATA(pbBuf, cbLeft, wsz, (uint32_t)cwc * sizeof(RTUTF16));
/* updated the size of the output buffer. */
*pcbBuf -= cbLeft;
}
/**
* Internal function for reading the language IDs.
*/
static void ReadCachedLangIdDesc(PCPDMUSBDESCCACHELANG paLanguages, unsigned cLanguages,
uint8_t *pbBuf, uint32_t *pcbBuf)
{
uint32_t cbLeft = *pcbBuf;
VUSBDESCLANGID LangIdDesc;
size_t cbDesc = sizeof(LangIdDesc) + cLanguages * sizeof(paLanguages[0].idLang);
LangIdDesc.bLength = (uint8_t)RT_MIN(0xff, cbDesc);
LangIdDesc.bDescriptorType = VUSB_DT_STRING;
COPY_DATA(pbBuf, cbLeft, &LangIdDesc, sizeof(LangIdDesc));
unsigned iLanguage = cLanguages;
while (iLanguage-- > 0)
COPY_DATA(pbBuf, cbLeft, &paLanguages[iLanguage].idLang, sizeof(paLanguages[iLanguage].idLang));
/* updated the size of the output buffer. */
*pcbBuf -= cbLeft;
}
/**
* Internal function which performs a descriptor read on the cached descriptors.
*/
static void ReadCachedConfigDesc(PCVUSBDESCCONFIGEX pCfgDesc, uint8_t *pbBuf, uint32_t *pcbBuf)
{
uint32_t cbLeft = *pcbBuf;
/** @todo See @bugref{2693} */
/*
* Make a copy of the config descriptor and calculate the wTotalLength field.
*/
VUSBDESCCONFIG CfgDesc;
memcpy(&CfgDesc, pCfgDesc, VUSB_DT_CONFIG_MIN_LEN);
uint32_t cbTotal = pCfgDesc->Core.bLength;
for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
{
PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i];
for (uint32_t j = 0; j < pIf->cSettings; j++)
{
cbTotal += pIf->paSettings[j].cbIAD;
cbTotal += pIf->paSettings[j].Core.bLength;
cbTotal += pIf->paSettings[j].cbClass;
for (unsigned k = 0; k < pIf->paSettings[j].Core.bNumEndpoints; k++)
{
cbTotal += pIf->paSettings[j].paEndpoints[k].Core.bLength;
cbTotal += pIf->paSettings[j].paEndpoints[k].cbClass;
}
}
}
CfgDesc.wTotalLength = RT_H2LE_U16(cbTotal);
/*
* Copy the config descriptor
*/
COPY_DATA(pbBuf, cbLeft, &CfgDesc, VUSB_DT_CONFIG_MIN_LEN);
COPY_DATA(pbBuf, cbLeft, pCfgDesc->pvMore, pCfgDesc->Core.bLength - VUSB_DT_CONFIG_MIN_LEN);
/*
* Copy out all the interfaces for this configuration
*/
for (unsigned i = 0; i < pCfgDesc->Core.bNumInterfaces; i++)
{
PCVUSBINTERFACE pIf = &pCfgDesc->paIfs[i];
for (uint32_t j = 0; j < pIf->cSettings; j++)
{
PCVUSBDESCINTERFACEEX pIfDesc = &pIf->paSettings[j];
COPY_DATA(pbBuf, cbLeft, pIfDesc->pIAD, pIfDesc->cbIAD);
COPY_DATA(pbBuf, cbLeft, pIfDesc, VUSB_DT_INTERFACE_MIN_LEN);
COPY_DATA(pbBuf, cbLeft, pIfDesc->pvMore, pIfDesc->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN);
COPY_DATA(pbBuf, cbLeft, pIfDesc->pvClass, pIfDesc->cbClass);
/*
* Copy out all the endpoints for this interface
*/
for (unsigned k = 0; k < pIfDesc->Core.bNumEndpoints; k++)
{
VUSBDESCENDPOINT EndPtDesc;
memcpy(&EndPtDesc, &pIfDesc->paEndpoints[k], VUSB_DT_ENDPOINT_MIN_LEN);
EndPtDesc.wMaxPacketSize = RT_H2LE_U16(EndPtDesc.wMaxPacketSize);
COPY_DATA(pbBuf, cbLeft, &EndPtDesc, VUSB_DT_ENDPOINT_MIN_LEN);
COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvMore, EndPtDesc.bLength - VUSB_DT_ENDPOINT_MIN_LEN);
COPY_DATA(pbBuf, cbLeft, pIfDesc->paEndpoints[k].pvClass, pIfDesc->paEndpoints[k].cbClass);
}
}
}
/* updated the size of the output buffer. */
*pcbBuf -= cbLeft;
}
/**
* Internal function which performs a descriptor read on the cached descriptors.
*/
static void ReadCachedDeviceDesc(PCVUSBDESCDEVICE pDevDesc, uint8_t *pbBuf, uint32_t *pcbBuf)
{
uint32_t cbLeft = *pcbBuf;
/*
* Duplicate the device description and update some fields we keep in cpu type.
*/
Assert(sizeof(VUSBDESCDEVICE) == 18);
VUSBDESCDEVICE DevDesc = *pDevDesc;
DevDesc.bcdUSB = RT_H2LE_U16(DevDesc.bcdUSB);
DevDesc.idVendor = RT_H2LE_U16(DevDesc.idVendor);
DevDesc.idProduct = RT_H2LE_U16(DevDesc.idProduct);
DevDesc.bcdDevice = RT_H2LE_U16(DevDesc.bcdDevice);
COPY_DATA(pbBuf, cbLeft, &DevDesc, sizeof(DevDesc));
COPY_DATA(pbBuf, cbLeft, pDevDesc + 1, pDevDesc->bLength - sizeof(DevDesc));
/* updated the size of the output buffer. */
*pcbBuf -= cbLeft;
}
#undef COPY_DATA
/**
* Standard device request: GET_DESCRIPTOR
* @returns success indicator.
* @remark not really used yet as we consider GET_DESCRIPTOR 'safe'.
*/
static bool vusbDevStdReqGetDescriptor(PVUSBDEV pDev, int EndPt, PVUSBSETUP pSetup, uint8_t *pbBuf, uint32_t *pcbBuf)
{
if ((pSetup->bmRequestType & VUSB_RECIP_MASK) == VUSB_TO_DEVICE)
{
switch (pSetup->wValue >> 8)
{
case VUSB_DT_DEVICE:
ReadCachedDeviceDesc(pDev->pDescCache->pDevice, pbBuf, pcbBuf);
LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of device descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
return true;
case VUSB_DT_CONFIG:
{
unsigned int iIndex = (pSetup->wValue & 0xff);
if (iIndex >= pDev->pDescCache->pDevice->bNumConfigurations)
{
LogFlow(("vusbDevStdReqGetDescriptor: %s: iIndex=%p >= bNumConfigurations=%d !!!\n",
pDev->pUsbIns->pszName, iIndex, pDev->pDescCache->pDevice->bNumConfigurations));
return false;
}
ReadCachedConfigDesc(&pDev->pDescCache->paConfigs[iIndex], pbBuf, pcbBuf);
LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of config descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
return true;
}
case VUSB_DT_STRING:
{
if (pSetup->wIndex == 0)
{
ReadCachedLangIdDesc(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages, pbBuf, pcbBuf);
LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of language ID (string) descriptors\n", pDev->pUsbIns->pszName, *pcbBuf));
return true;
}
PCPDMUSBDESCCACHESTRING pString;
pString = FindCachedString(pDev->pDescCache->paLanguages, pDev->pDescCache->cLanguages,
pSetup->wIndex, pSetup->wValue & 0xff);
if (pString)
{
ReadCachedStringDesc(pString, pbBuf, pcbBuf);
LogFlow(("vusbDevStdReqGetDescriptor: %s: %u bytes of string descriptors \"%s\"\n",
pDev->pUsbIns->pszName, *pcbBuf, pString->psz));
return true;
}
break;
}
default:
break;
}
}
Log(("vusb: %s: warning: unknown descriptor: type=%u descidx=%u lang=%u len=%u!!!\n",
pDev->pUsbIns->pszName, pSetup->wValue >> 8, pSetup->wValue & 0xff, pSetup->wIndex, pSetup->wLength));
return false;
}
/**
* Service the standard USB requests.
*
* Devices may call this from controlmsg() if you want vusb core to handle your standard
* request, it's not necessary - you could handle them manually
*
* @param pDev The device.
* @param EndPoint The endpoint.
* @param pSetup Pointer to the setup request structure.
* @param pvBuf Buffer?
* @param pcbBuf ?
*/
bool vusbDevStandardRequest(PVUSBDEV pDev, int EndPoint, PVUSBSETUP pSetup, void *pvBuf, uint32_t *pcbBuf)
{
static bool (* const s_apfnStdReq[VUSB_REQ_MAX])(PVUSBDEV, int, PVUSBSETUP, uint8_t *, uint32_t *) =
{
vusbDevStdReqGetStatus,
vusbDevStdReqClearFeature,
NULL,
vusbDevStdReqSetFeature,
NULL,
vusbDevStdReqSetAddress,
vusbDevStdReqGetDescriptor,
NULL,
vusbDevStdReqGetConfig,
vusbDevStdReqSetConfig,
vusbDevStdReqGetInterface,
vusbDevStdReqSetInterface,
NULL /* for iso */
};
/*
* Check that the device is in a valid state.
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState == VUSB_DEVICE_STATE_RESET)
{
LogRel(("VUSB: %s: standard control message ignored, the device is resetting\n", pDev->pUsbIns->pszName));
return false;
}
/*
* Do the request if it's one we want to deal with.
*/
if ( pSetup->bRequest >= VUSB_REQ_MAX
|| !s_apfnStdReq[pSetup->bRequest])
{
Log(("vusb: warning: standard req not implemented: message %u: val=%u idx=%u len=%u !!!\n",
pSetup->bRequest, pSetup->wValue, pSetup->wIndex, pSetup->wLength));
return false;
}
return s_apfnStdReq[pSetup->bRequest](pDev, EndPoint, pSetup, (uint8_t *)pvBuf, pcbBuf);
}
/**
* Add a device to the address hash
*/
static void vusbDevAddressHash(PVUSBDEV pDev)
{
if (pDev->u8Address == VUSB_INVALID_ADDRESS)
return;
uint8_t u8Hash = vusbHashAddress(pDev->u8Address);
pDev->pNextHash = pDev->pHub->pRootHub->apAddrHash[u8Hash];
pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev;
}
/**
* Remove a device from the address hash
*/
static void vusbDevAddressUnHash(PVUSBDEV pDev)
{
if (pDev->u8Address == VUSB_INVALID_ADDRESS)
return;
uint8_t u8Hash = vusbHashAddress(pDev->u8Address);
pDev->u8Address = VUSB_INVALID_ADDRESS;
pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
PVUSBDEV pCur = pDev->pHub->pRootHub->apAddrHash[u8Hash];
if (pCur == pDev)
{
/* special case, we're at the head */
pDev->pHub->pRootHub->apAddrHash[u8Hash] = pDev->pNextHash;
pDev->pNextHash = NULL;
}
else
{
/* search the list */
PVUSBDEV pPrev;
for (pPrev = pCur, pCur = pCur->pNextHash;
pCur;
pPrev = pCur, pCur = pCur->pNextHash)
{
if (pCur == pDev)
{
pPrev->pNextHash = pCur->pNextHash;
pDev->pNextHash = NULL;
break;
}
}
}
}
/**
* Sets the address of a device.
*
* Called by status_completion() and vusbDevResetWorker().
*/
void vusbDevSetAddress(PVUSBDEV pDev, uint8_t u8Address)
{
LogFlow(("vusbDevSetAddress: pDev=%p[%s]/%i u8Address=%#x\n",
pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address));
/*
* Check that the device is in a valid state.
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
VUSBDEV_ASSERT_VALID_STATE(enmState);
if ( enmState == VUSB_DEVICE_STATE_ATTACHED
|| enmState == VUSB_DEVICE_STATE_DETACHED)
{
LogFlow(("vusbDevSetAddress: %s: fails because %d < POWERED\n", pDev->pUsbIns->pszName, pDev->enmState));
return;
}
if (enmState == VUSB_DEVICE_STATE_RESET)
{
LogRel(("VUSB: %s: set address ignored, the device is resetting\n", pDev->pUsbIns->pszName));
return;
}
/*
* Ok, get on with it.
*/
if (pDev->u8Address == u8Address)
return;
PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
AssertPtrReturnVoid(pRh);
if (pDev->u8Address == VUSB_DEFAULT_ADDRESS)
pRh->pDefaultAddress = NULL;
vusbDevAddressUnHash(pDev);
if (u8Address == VUSB_DEFAULT_ADDRESS)
{
if (pRh->pDefaultAddress != NULL)
{
vusbDevAddressUnHash(pRh->pDefaultAddress);
vusbDevSetState(pRh->pDefaultAddress, VUSB_DEVICE_STATE_POWERED);
Log(("2 DEFAULT ADDRS\n"));
}
pRh->pDefaultAddress = pDev;
vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT);
}
else
vusbDevSetState(pDev, VUSB_DEVICE_STATE_ADDRESS);
pDev->u8Address = u8Address;
vusbDevAddressHash(pDev);
Log(("vusb: %p[%s]/%i: Assigned address %u\n",
pDev, pDev->pUsbIns->pszName, pDev->i16Port, u8Address));
}
static DECLCALLBACK(int) vusbDevCancelAllUrbsWorker(PVUSBDEV pDev, bool fDetaching)
{
/*
* Iterate the URBs and cancel them.
*/
PVUSBURB pUrb = pDev->pAsyncUrbHead;
while (pUrb)
{
PVUSBURB pNext = pUrb->VUsb.pNext;
Assert(pUrb->VUsb.pDev == pDev);
LogFlow(("%s: vusbDevCancelAllUrbs: CANCELING URB\n", pUrb->pszDesc));
vusbUrbCancelWorker(pUrb, CANCELMODE_FAIL);
pUrb = pNext;
}
/*
* Reap any URBs which became ripe during cancel now.
*/
unsigned cReaped;
do
{
cReaped = 0;
pUrb = pDev->pAsyncUrbHead;
while (pUrb)
{
PVUSBURB pNext = pUrb->VUsb.pNext;
Assert(pUrb->VUsb.pDev == pDev);
PVUSBURB pRipe = NULL;
if (pUrb->enmState == VUSBURBSTATE_REAPED)
pRipe = pUrb;
else if (pUrb->enmState == VUSBURBSTATE_CANCELLED)
#ifdef RT_OS_WINDOWS /** @todo Windows doesn't do cancelling, thus this kludge to prevent really bad
* things from happening if we leave a pending URB behinds. */
pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 1500 : 0 /*ms*/);
#else
pRipe = pDev->pUsbIns->pReg->pfnUrbReap(pDev->pUsbIns, fDetaching ? 10 : 0 /*ms*/);
#endif
else
AssertMsgFailed(("pUrb=%p enmState=%d\n", pUrb, pUrb->enmState));
if (pRipe)
{
if (pRipe == pNext)
pNext = pNext->VUsb.pNext;
vusbUrbRipe(pRipe);
cReaped++;
}
pUrb = pNext;
}
} while (cReaped > 0);
/*
* If we're detaching, we'll have to orphan any leftover URBs.
*/
if (fDetaching)
{
pUrb = pDev->pAsyncUrbHead;
while (pUrb)
{
PVUSBURB pNext = pUrb->VUsb.pNext;
Assert(pUrb->VUsb.pDev == pDev);
AssertMsgFailed(("%s: Leaking left over URB! state=%d pDev=%p[%s]\n",
pUrb->pszDesc, pUrb->enmState, pDev, pDev->pUsbIns->pszName));
vusbUrbUnlink(pUrb);
/* Unlink isn't enough, because boundary timer and detaching will try to reap it.
* It was tested with MSD & iphone attachment to vSMP guest, if
* it breaks anything, please add comment here, why we should unlink only.
*/
pUrb->VUsb.pfnFree(pUrb);
pUrb = pNext;
}
}
return VINF_SUCCESS;
}
/**
* Cancels and completes (with CRC failure) all async URBs pending
* on a device. This is typically done as part of a reset and
* before detaching a device.
*
* @param fDetaching If set, we will unconditionally unlink (and leak)
* any URBs which isn't reaped.
*/
static void vusbDevCancelAllUrbs(PVUSBDEV pDev, bool fDetaching)
{
int rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevCancelAllUrbsWorker, 2, pDev, fDetaching);
AssertRC(rc);
}
static DECLCALLBACK(int) vusbDevUrbIoThread(RTTHREAD hThread, void *pvUser)
{
PVUSBDEV pDev = (PVUSBDEV)pvUser;
/* Notify the starter that we are up and running. */
RTThreadUserSignal(hThread);
LogFlowFunc(("Entering work loop\n"));
while (!ASMAtomicReadBool(&pDev->fTerminate))
{
if (vusbDevGetState(pDev) != VUSB_DEVICE_STATE_RESET)
vusbUrbDoReapAsyncDev(pDev, RT_INDEFINITE_WAIT);
/* Process any URBs waiting to be cancelled first. */
int rc = RTReqQueueProcess(pDev->hReqQueueSync, 0); /* Don't wait if there is nothing to do. */
Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
}
return VINF_SUCCESS;
}
int vusbDevUrbIoThreadWakeup(PVUSBDEV pDev)
{
ASMAtomicXchgBool(&pDev->fWokenUp, true);
return pDev->pUsbIns->pReg->pfnWakeup(pDev->pUsbIns);
}
/**
* Create the URB I/O thread.
*
* @returns VBox status code.
* @param pDev The VUSB device.
*/
int vusbDevUrbIoThreadCreate(PVUSBDEV pDev)
{
int rc = VINF_SUCCESS;
ASMAtomicXchgBool(&pDev->fTerminate, false);
rc = RTThreadCreateF(&pDev->hUrbIoThread, vusbDevUrbIoThread, pDev, 0, RTTHREADTYPE_IO,
RTTHREADFLAGS_WAITABLE, "USBDevIo-%d", pDev->i16Port);
if (RT_SUCCESS(rc))
{
/* Wait for it to become active. */
rc = RTThreadUserWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT);
}
return rc;
}
/**
* Destro the URB I/O thread.
*
* @returns VBox status code.
* @param pDev The VUSB device.
*/
int vusbDevUrbIoThreadDestroy(PVUSBDEV pDev)
{
int rc = VINF_SUCCESS;
int rcThread = VINF_SUCCESS;
ASMAtomicXchgBool(&pDev->fTerminate, true);
vusbDevUrbIoThreadWakeup(pDev);
rc = RTThreadWait(pDev->hUrbIoThread, RT_INDEFINITE_WAIT, &rcThread);
if (RT_SUCCESS(rc))
rc = rcThread;
pDev->hUrbIoThread = NIL_RTTHREAD;
return rc;
}
/**
* Detaches a device from the hub it's attached to.
*
* @returns VBox status code.
* @param pDev The device to detach.
*
* @remark This can be called in any state but reset.
*/
int vusbDevDetach(PVUSBDEV pDev)
{
LogFlow(("vusbDevDetach: pDev=%p[%s] enmState=%#x\n", pDev, pDev->pUsbIns->pszName, pDev->enmState));
VUSBDEV_ASSERT_VALID_STATE(pDev->enmState);
Assert(pDev->enmState != VUSB_DEVICE_STATE_RESET);
vusbDevCancelAllUrbs(pDev, true);
vusbDevAddressUnHash(pDev);
PVUSBROOTHUB pRh = vusbDevGetRh(pDev);
if (!pRh)
AssertMsgFailedReturn(("Not attached!\n"), VERR_VUSB_DEVICE_NOT_ATTACHED);
if (pRh->pDefaultAddress == pDev)
pRh->pDefaultAddress = NULL;
pDev->pHub->pOps->pfnDetach(pDev->pHub, pDev);
pDev->i16Port = -1;
vusbDevSetState(pDev, VUSB_DEVICE_STATE_DETACHED);
pDev->pHub = NULL;
/* Remove the configuration */
pDev->pCurCfgDesc = NULL;
for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
vusbDevResetPipeData(&pDev->aPipes[i]);
return VINF_SUCCESS;
}
/**
* Destroys a device, detaching it from the hub if necessary.
*
* @param pDev The device.
* @thread EMT
*/
void vusbDevDestroy(PVUSBDEV pDev)
{
LogFlow(("vusbDevDestroy: pDev=%p[%s] enmState=%d\n", pDev, pDev->pUsbIns->pszName, pDev->enmState));
/*
* Deal with pending async reset.
* (anything but reset)
*/
vusbDevSetStateCmp(pDev, VUSB_DEVICE_STATE_DEFAULT, VUSB_DEVICE_STATE_RESET);
/*
* Detach and free resources.
*/
if (pDev->pHub)
vusbDevDetach(pDev);
RTMemFree(pDev->paIfStates);
TMR3TimerDestroy(pDev->pResetTimer);
pDev->pResetTimer = NULL;
for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
{
Assert(pDev->aPipes[i].pCtrl == NULL);
RTCritSectDelete(&pDev->aPipes[i].CritSectCtrl);
}
/*
* Destroy I/O thread and request queue last because they might still be used
* when cancelling URBs.
*/
vusbDevUrbIoThreadDestroy(pDev);
int rc = RTReqQueueDestroy(pDev->hReqQueueSync);
AssertRC(rc);
if (pDev->hSniffer != VUSBSNIFFER_NIL)
VUSBSnifferDestroy(pDev->hSniffer);
RTCritSectDelete(&pDev->CritSectAsyncUrbs);
/* Not using vusbDevSetState() deliberately here because it would assert on the state. */
pDev->enmState = VUSB_DEVICE_STATE_DESTROYED;
}
/* -=-=-=-=-=- VUSBIDEVICE methods -=-=-=-=-=- */
/**
* The actual reset has been done, do completion on EMT.
*
* There are several things we have to do now, like set default
* config and address, and cleanup the state of control pipes.
*
* It's possible that the device has a delayed destroy request
* pending when we get here. This can happen for async resetting.
* We deal with it here, since we're now executing on the EMT
* thread and the destruction will be properly serialized now.
*
* @param pDev The device that is being reset.
* @param rc The vusbDevResetWorker return code.
* @param pfnDone The done callback specified by the caller of vusbDevReset().
* @param pvUser The user argument for the callback.
*/
static void vusbDevResetDone(PVUSBDEV pDev, int rc, PFNVUSBRESETDONE pfnDone, void *pvUser)
{
VUSBDEV_ASSERT_VALID_STATE(pDev->enmState);
Assert(pDev->enmState == VUSB_DEVICE_STATE_RESET);
/*
* Do control pipe cleanup regardless of state and result.
*/
for (unsigned i = 0; i < VUSB_PIPE_MAX; i++)
if (pDev->aPipes[i].pCtrl)
vusbMsgResetExtraData(pDev->aPipes[i].pCtrl);
/*
* Switch to the default state.
*/
vusbDevSetState(pDev, VUSB_DEVICE_STATE_DEFAULT);
pDev->u16Status = 0;
vusbDevDoSelectConfig(pDev, &g_Config0);
if (!vusbDevIsRh(pDev))
vusbDevSetAddress(pDev, VUSB_DEFAULT_ADDRESS);
if (pfnDone)
pfnDone(&pDev->IDevice, rc, pvUser);
}
/**
* Timer callback for doing reset completion.
*
* @param pUsbIns The USB device instance.
* @param pTimer The timer instance.
* @param pvUser The VUSB device data.
* @thread EMT
*/
static DECLCALLBACK(void) vusbDevResetDoneTimer(PPDMUSBINS pUsbIns, PTMTIMER pTimer, void *pvUser)
{
PVUSBDEV pDev = (PVUSBDEV)pvUser;
PVUSBRESETARGS pArgs = (PVUSBRESETARGS)pDev->pvArgs;
Assert(pDev->pUsbIns == pUsbIns);
/*
* Reset-done processing and cleanup.
*/
vusbDevResetDone(pDev, pArgs->rc, pArgs->pfnDone, pArgs->pvUser);
pDev->pvArgs = NULL;
RTMemFree(pArgs);
}
/**
* Perform the actual reset.
*
* @thread EMT or a VUSB reset thread.
*/
static int vusbDevResetWorker(PVUSBDEV pDev, bool fResetOnLinux, bool fUseTimer, PVUSBRESETARGS pArgs)
{
int rc = VINF_SUCCESS;
uint64_t u64EndTS = TMTimerGet(pDev->pResetTimer) + TMTimerFromMilli(pDev->pResetTimer, 10);
if (pDev->pUsbIns->pReg->pfnUsbReset)
rc = pDev->pUsbIns->pReg->pfnUsbReset(pDev->pUsbIns, fResetOnLinux);
if (fUseTimer)
{
/*
* We use a timer to communicate the result back to EMT.
* This avoids suspend + poweroff issues, and it should give
* us more accurate scheduling than making this thread sleep.
*/
int rc2 = TMTimerSet(pDev->pResetTimer, u64EndTS);
AssertReleaseRC(rc2);
}
if (pArgs)
{
pArgs->rc = rc;
rc = VINF_SUCCESS;
}
LogFlow(("vusbDevResetWorker: %s: returns %Rrc\n", pDev->pUsbIns->pszName, rc));
return rc;
}
/**
* Resets a device.
*
* Since a device reset shall take at least 10ms from the guest point of view,
* it must be performed asynchronously. We create a thread which performs this
* operation and ensures it will take at least 10ms.
*
* At times - like init - a synchronous reset is required, this can be done
* by passing NULL for pfnDone.
*
* While the device is being reset it is in the VUSB_DEVICE_STATE_RESET state.
* On completion it will be in the VUSB_DEVICE_STATE_DEFAULT state if successful,
* or in the VUSB_DEVICE_STATE_DETACHED state if the rest failed.
*
* @returns VBox status code.
*
* @param pDev Pointer to the VUSB device interface.
* @param fResetOnLinux Whether it's safe to reset the device(s) on a linux
* host system. See discussion of logical reconnects elsewhere.
* @param pfnDone Pointer to the completion routine. If NULL a synchronous
* reset is preformed not respecting the 10ms.
* @param pVM Pointer to the VM handle for performing the done function
* on the EMT thread.
* @thread EMT
*/
DECLCALLBACK(int) vusbIDeviceReset(PVUSBIDEVICE pDevice, bool fResetOnLinux, PFNVUSBRESETDONE pfnDone, void *pvUser, PVM pVM)
{
PVUSBDEV pDev = (PVUSBDEV)pDevice;
Assert(!pfnDone || pVM);
LogFlow(("vusb: reset: [%s]/%i\n", pDev->pUsbIns->pszName, pDev->i16Port));
/*
* Only one reset operation at a time.
*/
const VUSBDEVICESTATE enmStateOld = vusbDevSetState(pDev, VUSB_DEVICE_STATE_RESET);
if (enmStateOld == VUSB_DEVICE_STATE_RESET)
{
LogRel(("VUSB: %s: reset request is ignored, the device is already resetting!\n", pDev->pUsbIns->pszName));
return VERR_VUSB_DEVICE_IS_RESETTING;
}
/*
* First, cancel all async URBs.
*/
vusbDevCancelAllUrbs(pDev, false);
/* Async or sync? */
if (pfnDone)
{
/*
* Async fashion.
*/
PVUSBRESETARGS pArgs = (PVUSBRESETARGS)RTMemTmpAlloc(sizeof(*pArgs));
if (pArgs)
{
pArgs->pDev = pDev;
pArgs->pfnDone = pfnDone;
pArgs->pvUser = pvUser;
pArgs->rc = VINF_SUCCESS;
pDev->pvArgs = pArgs;
int rc = vusbDevIoThreadExec(pDev, 0 /* fFlags */, (PFNRT)vusbDevResetWorker, 4, pDev, fResetOnLinux, true, pArgs);
if (RT_SUCCESS(rc))
return rc;
RTMemTmpFree(pArgs);
}
/* fall back to sync on failure */
}
/*
* Sync fashion.
*/
int rc = vusbDevResetWorker(pDev, fResetOnLinux, false, NULL);
vusbDevResetDone(pDev, rc, pfnDone, pvUser);
return rc;
}
/**
* Powers on the device.
*
* @returns VBox status code.
* @param pInterface Pointer to the device interface structure.
*/
DECLCALLBACK(int) vusbIDevicePowerOn(PVUSBIDEVICE pInterface)
{
PVUSBDEV pDev = (PVUSBDEV)pInterface;
LogFlow(("vusbDevPowerOn: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName));
/*
* Check that the device is in a valid state.
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState == VUSB_DEVICE_STATE_DETACHED)
{
Log(("vusb: warning: attempt to power on detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName));
return VERR_VUSB_DEVICE_NOT_ATTACHED;
}
if (enmState == VUSB_DEVICE_STATE_RESET)
{
LogRel(("VUSB: %s: power on ignored, the device is resetting!\n", pDev->pUsbIns->pszName));
return VERR_VUSB_DEVICE_IS_RESETTING;
}
/*
* Do the job.
*/
if (enmState == VUSB_DEVICE_STATE_ATTACHED)
vusbDevSetState(pDev, VUSB_DEVICE_STATE_POWERED);
return VINF_SUCCESS;
}
/**
* Powers off the device.
*
* @returns VBox status code.
* @param pInterface Pointer to the device interface structure.
*/
DECLCALLBACK(int) vusbIDevicePowerOff(PVUSBIDEVICE pInterface)
{
PVUSBDEV pDev = (PVUSBDEV)pInterface;
LogFlow(("vusbDevPowerOff: pDev=%p[%s]\n", pDev, pDev->pUsbIns->pszName));
/*
* Check that the device is in a valid state.
*/
const VUSBDEVICESTATE enmState = vusbDevGetState(pDev);
if (enmState == VUSB_DEVICE_STATE_DETACHED)
{
Log(("vusb: warning: attempt to power off detached device %p[%s]\n", pDev, pDev->pUsbIns->pszName));
return VERR_VUSB_DEVICE_NOT_ATTACHED;
}
if (enmState == VUSB_DEVICE_STATE_RESET)
{
LogRel(("VUSB: %s: power off ignored, the device is resetting!\n", pDev->pUsbIns->pszName));
return VERR_VUSB_DEVICE_IS_RESETTING;
}
/*
* If it's a root hub, we will have to cancel all URBs and reap them.
*/
if (vusbDevIsRh(pDev))
{
PVUSBROOTHUB pRh = (PVUSBROOTHUB)pDev;
VUSBIRhCancelAllUrbs(&pRh->IRhConnector);
VUSBIRhReapAsyncUrbs(&pRh->IRhConnector, pInterface, 0);
}
vusbDevSetState(pDev, VUSB_DEVICE_STATE_ATTACHED);
return VINF_SUCCESS;
}
/**
* Get the state of the device.
*
* @returns Device state.
* @param pInterface Pointer to the device interface structure.
*/
DECLCALLBACK(VUSBDEVICESTATE) vusbIDeviceGetState(PVUSBIDEVICE pInterface)
{
return vusbDevGetState((PVUSBDEV)pInterface);
}
/**
* @interface_method_impl{VUSBIDEVICE,pfnIsEmulated}
*/
DECLCALLBACK(bool) vusbIDeviceIsEmulated(PVUSBIDEVICE pInterface)
{
PVUSBDEV pDev = (PVUSBDEV)pInterface;
bool fEmulated = !!(pDev->pUsbIns->pReg->fFlags & PDM_USBREG_EMULATED_DEVICE);
LogFlowFunc(("pInterface=%p\n", pInterface));
LogFlowFunc(("returns %RTbool\n", fEmulated));
return fEmulated;
}
/**
* The maximum number of interfaces the device can have in all of it's configuration.
*
* @returns Number of interfaces.
* @param pDev The device.
*/
size_t vusbDevMaxInterfaces(PVUSBDEV pDev)
{
uint8_t cMax = 0;
unsigned i = pDev->pDescCache->pDevice->bNumConfigurations;
while (i-- > 0)
{
if (pDev->pDescCache->paConfigs[i].Core.bNumInterfaces > cMax)
cMax = pDev->pDescCache->paConfigs[i].Core.bNumInterfaces;
}
return cMax;
}
/**
* Executes a given function on the I/O thread.
*
* @returns IPRT status code.
* @param pDev The USB device instance data.
* @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_*
* @param pfnFunction The function to execute.
* @param cArgs Number of arguments to the function.
* @param Args The parameter list.
*
* @remarks See remarks on RTReqQueueCallV
*/
DECLHIDDEN(int) vusbDevIoThreadExecV(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, va_list Args)
{
int rc = VINF_SUCCESS;
PRTREQ hReq = NULL;
Assert(pDev->hUrbIoThread != NIL_RTTHREAD);
if (RT_LIKELY(pDev->hUrbIoThread != NIL_RTTHREAD))
{
uint32_t fReqFlags = RTREQFLAGS_IPRT_STATUS;
if (!(fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC))
fReqFlags |= RTREQFLAGS_NO_WAIT;
rc = RTReqQueueCallV(pDev->hReqQueueSync, &hReq, 0 /* cMillies */, fReqFlags, pfnFunction, cArgs, Args);
Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT);
vusbDevUrbIoThreadWakeup(pDev);
if ( rc == VERR_TIMEOUT
&& (fFlags & VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC))
{
rc = RTReqWait(hReq, RT_INDEFINITE_WAIT);
AssertRC(rc);
}
RTReqRelease(hReq);
}
else
rc = VERR_INVALID_STATE;
return rc;
}
/**
* Executes a given function on the I/O thread.
*
* @returns IPRT status code.
* @param pDev The USB device instance data.
* @param fFlags Combination of VUSB_DEV_IO_THREAD_EXEC_FLAGS_*
* @param pfnFunction The function to execute.
* @param cArgs Number of arguments to the function.
* @param ... The parameter list.
*
* @remarks See remarks on RTReqQueueCallV
*/
DECLHIDDEN(int) vusbDevIoThreadExec(PVUSBDEV pDev, uint32_t fFlags, PFNRT pfnFunction, unsigned cArgs, ...)
{
int rc = VINF_SUCCESS;
va_list va;
va_start(va, cArgs);
rc = vusbDevIoThreadExecV(pDev, fFlags, pfnFunction, cArgs, va);
va_end(va);
return rc;
}
/**
* Executes a given function synchronously on the I/O thread waiting for it to complete.
*
* @returns IPRT status code.
* @param pDev The USB device instance data
* @param pfnFunction The function to execute.
* @param cArgs Number of arguments to the function.
* @param ... The parameter list.
*
* @remarks See remarks on RTReqQueueCallV
*/
DECLHIDDEN(int) vusbDevIoThreadExecSync(PVUSBDEV pDev, PFNRT pfnFunction, unsigned cArgs, ...)
{
int rc = VINF_SUCCESS;
va_list va;
va_start(va, cArgs);
rc = vusbDevIoThreadExecV(pDev, VUSB_DEV_IO_THREAD_EXEC_FLAGS_SYNC, pfnFunction, cArgs, va);
va_end(va);
return rc;
}
static DECLCALLBACK(int) vusbDevGetDescriptorCacheWorker(PPDMUSBINS pUsbIns, PCPDMUSBDESCCACHE *ppDescCache)
{
*ppDescCache = pUsbIns->pReg->pfnUsbGetDescriptorCache(pUsbIns);
return VINF_SUCCESS;
}
/**
* Initialize a new VUSB device.
*
* @returns VBox status code.
* @param pDev The VUSB device to initialize.
* @param pUsbIns Pointer to the PDM USB Device instance.
*/
int vusbDevInit(PVUSBDEV pDev, PPDMUSBINS pUsbIns, const char *pszCaptureFilename)
{
/*
* Initialize the device data members.
* (All that are Non-Zero at least.)
*/
Assert(!pDev->IDevice.pfnReset);
Assert(!pDev->IDevice.pfnPowerOn);
Assert(!pDev->IDevice.pfnPowerOff);
Assert(!pDev->IDevice.pfnGetState);
Assert(!pDev->IDevice.pfnIsEmulated);
pDev->IDevice.pfnReset = vusbIDeviceReset;
pDev->IDevice.pfnPowerOn = vusbIDevicePowerOn;
pDev->IDevice.pfnPowerOff = vusbIDevicePowerOff;
pDev->IDevice.pfnGetState = vusbIDeviceGetState;
pDev->IDevice.pfnIsEmulated = vusbIDeviceIsEmulated;
pDev->pUsbIns = pUsbIns;
pDev->pNext = NULL;
pDev->pNextHash = NULL;
pDev->pHub = NULL;
pDev->enmState = VUSB_DEVICE_STATE_DETACHED;
pDev->u8Address = VUSB_INVALID_ADDRESS;
pDev->u8NewAddress = VUSB_INVALID_ADDRESS;
pDev->i16Port = -1;
pDev->u16Status = 0;
pDev->pDescCache = NULL;
pDev->pCurCfgDesc = NULL;
pDev->paIfStates = NULL;
memset(&pDev->aPipes[0], 0, sizeof(pDev->aPipes));
for (unsigned i = 0; i < RT_ELEMENTS(pDev->aPipes); i++)
{
int rc = RTCritSectInit(&pDev->aPipes[i].CritSectCtrl);
AssertRCReturn(rc, rc);
}
pDev->pResetTimer = NULL;
pDev->hSniffer = VUSBSNIFFER_NIL;
int rc = RTCritSectInit(&pDev->CritSectAsyncUrbs);
AssertRCReturn(rc, rc);
/* Setup request queue executing synchronous tasks on the I/O thread. */
rc = RTReqQueueCreate(&pDev->hReqQueueSync);
AssertRCReturn(rc, rc);
/* Create I/O thread. */
rc = vusbDevUrbIoThreadCreate(pDev);
AssertRCReturn(rc, rc);
/*
* Create the reset timer.
*/
rc = PDMUsbHlpTMTimerCreate(pDev->pUsbIns, TMCLOCK_VIRTUAL, vusbDevResetDoneTimer, pDev, 0 /*fFlags*/,
"USB Device Reset Timer", &pDev->pResetTimer);
AssertRCReturn(rc, rc);
if (pszCaptureFilename)
{
rc = VUSBSnifferCreate(&pDev->hSniffer, 0, pszCaptureFilename, NULL);
AssertRCReturn(rc, rc);
}
/*
* Get the descriptor cache from the device. (shall cannot fail)
*/
rc = vusbDevIoThreadExecSync(pDev, (PFNRT)vusbDevGetDescriptorCacheWorker, 2, pUsbIns, &pDev->pDescCache);
AssertRC(rc);
AssertPtr(pDev->pDescCache);
#ifdef VBOX_STRICT
if (pDev->pDescCache->fUseCachedStringsDescriptors)
{
int32_t iPrevId = -1;
for (unsigned iLang = 0; iLang < pDev->pDescCache->cLanguages; iLang++)
{
Assert((int32_t)pDev->pDescCache->paLanguages[iLang].idLang > iPrevId);
iPrevId = pDev->pDescCache->paLanguages[iLang].idLang;
int32_t idxPrevStr = -1;
PCPDMUSBDESCCACHESTRING paStrings = pDev->pDescCache->paLanguages[iLang].paStrings;
unsigned cStrings = pDev->pDescCache->paLanguages[iLang].cStrings;
for (unsigned iStr = 0; iStr < cStrings; iStr++)
{
Assert((int32_t)paStrings[iStr].idx > idxPrevStr);
idxPrevStr = paStrings[iStr].idx;
size_t cch = strlen(paStrings[iStr].psz);
Assert(cch <= 127);
}
}
}
#endif
/*
* Allocate memory for the interface states.
*/
size_t cbIface = vusbDevMaxInterfaces(pDev) * sizeof(*pDev->paIfStates);
pDev->paIfStates = (PVUSBINTERFACESTATE)RTMemAllocZ(cbIface);
AssertMsgReturn(pDev->paIfStates, ("RTMemAllocZ(%d) failed\n", cbIface), VERR_NO_MEMORY);
return VINF_SUCCESS;
}
/*
* Local Variables:
* mode: c
* c-file-style: "bsd"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: s
* End:
*/