USBProxyDevice.cpp revision b7bd5d53fc66113d06f6b3667129864edc780ef8
/* $Id$ */
/** @file
* USBProxy - USB device proxy.
*/
/*
* Copyright (C) 2006-2007 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
#include <VBox/usbfilter.h>
#include "USBProxyDevice.h"
#include "VUSBInternal.h"
#include "VBoxDD.h"
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** A dummy name used early during the construction phase to avoid log crashes. */
static char g_szDummyName[] = "proxy xxxx:yyyy";
/* Synchronously obtain a standard USB descriptor for a device, used in order
* to grab configuration descriptors when we first add the device
*/
static void *GetStdDescSync(PUSBPROXYDEV pProxyDev, uint8_t iDescType, uint8_t iIdx, uint16_t LangId, uint16_t cbHint)
{
for (;;)
{
/*
* Setup a MSG URB, queue and reap it.
*/
Urb.DstAddress = 0;
Urb.fShortNotOk = false;
break;
/* Don't wait forever, it's just a simple request that should
return immediately. Since we're executing in the EMT thread
it's important not to get stuck here. (Some of the builtin
iMac devices may not refuse respond for instance.) */
if (!pUrbReaped)
{
}
if (pUrbReaped != &Urb)
{
break;
}
{
break;
}
/*
* Check the length, config descriptors have total_length field
*/
if (iDescType == VUSB_DT_CONFIG)
{
{
break;
}
}
else
{
{
break;
}
}
{
{
break;
}
continue;
}
#ifdef LOG_ENABLED
#endif
/*
* Fine, we got everything return a heap duplicate of the descriptor.
*/
}
return NULL;
}
/**
* Frees a descriptor returned by GetStdDescSync().
*/
{
}
/**
* Get and a device descriptor and byteswap it appropriately.
*/
{
/*
* Get the descriptor from the device.
*/
PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN);
if (!pIn)
{
return false;
}
{
Log(("usb-proxy: pProxyDev=%s: Corrupted device descriptor. bLength=%d\n", pProxyDev->pUsbIns->pszName, pIn->bLength));
return false;
}
/*
* Convert it.
*/
return true;
}
/**
* Count the numbers and types of each kind of descriptor that we need to
* copy out of the config descriptor
*/
struct desc_counts
{
/** bitmap (128 bits) */
};
{
uint32_t i, x;
return 0;
return 0;
{
switch ( type ) {
case VUSB_DT_INTERFACE:
return 0;
break;
case VUSB_DT_ENDPOINT:
return 0;
break;
default:
break;
}
}
/* count interfaces */
for(x=1; x; x<<=1)
return 1;
}
/* Setup a vusb_interface structure given some preallocated structures
* to use, (we counted them already)
*/
{
int state;
{
switch ( type ) {
case VUSB_DT_INTERFACE:
state = 0;
/* Ignoring this interface */
break;
/* Check we didn't see this alternate setting already
* because that will break stuff
*/
return 0;
(*id)++;
/* Point to additional interface descriptor bytes, if any. */
else
state = 1;
num_ep = 0;
break;
case VUSB_DT_ENDPOINT:
if ( state == 0 )
break;
(*ed)++;
if ( num_ep == 0 )
return 0;
/* Point to additional endpoint descriptor bytes, if any. */
else
num_ep++;
break;
default:
/** @todo Here be dragons! Additional descriptors needs copying into pvClass
* (RTMemDup be your friend). @bugref{2693} */
break;
}
}
return 1;
}
/**
* Copy all of a devices config descriptors, this is needed so that the USB
* core layer knows all about how to map the different functions on to the
* virtual USB bus.
*/
{
struct desc_counts cnt;
void *descs;
uint32_t i, x;
Log(("copy_config: GetStdDescSync failed\n"));
return false;
}
Log(("copy_config: count_descriptors failed\n"));
goto err;
}
Log(("usb-proxy: config%u: bNumInterfaces %u != %u\n",
Log(("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n",
return false;
}
/* Stash a pointer to the raw config descriptor; we may need bits of it later. */
for(i=0; i < 4; i++)
for(x=0; x < 32; x++)
goto err;
}
return true;
err:
return false;
}
/**
* Edit out masked interface descriptors.
*
* @param pProxyDev The proxy device
*/
{
unsigned cRemoved = 0;
{
{
Log(("usb-proxy: removing interface #%d (iIf=%d iAlt=%d) on config #%d (iCfg=%d)\n",
paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber, iIf, iAlt, paCfgs[iCfg].Core.bConfigurationValue, iCfg));
cRemoved++;
if (cToCopy)
break;
}
}
}
/**
* @copydoc PDMUSBREG::pfnUsbReset
*
* USB Device Proxy: Call OS specific code to reset the device.
*/
{
if (pProxyDev->fMaskedIfs)
{
Log(("usbProxyDevReset: pProxyDev=%s - ignoring reset request fMaskedIfs=%#x\n", pUsbIns->pszName, pProxyDev->fMaskedIfs));
return VINF_SUCCESS;
}
}
/**
* @copydoc PDMUSBREG::pfnUsbGetDescriptorCache
*/
{
}
/**
* @copydoc PDMUSBREG::pfnUsbSetConfiguration
*
* USB Device Proxy: Release claimed interfaces, tell the OS+device about the config change, claim the new interfaces.
*/
static DECLCALLBACK(int) usbProxyDevSetConfiguration(PPDMUSBINS pUsbIns, uint8_t bConfigurationValue,
{
LogFlow(("usbProxyDevSetConfiguration: pProxyDev=%s iActiveCfg=%d bConfigurationValue=%d\n",
/*
* Release the current config.
*/
if (pvOldCfgDesc)
{
if (pOldIfState[i].pCurIfDesc)
}
/*
* Do the actual SET_CONFIGURE.
* The mess here is because most backends will already have selected a
* configuration and there are a bunch of devices which will freak out
* if we do SET_CONFIGURE twice with the same value. (PalmOne, TrekStor USB-StickGO, ..)
*
* After open and reset the backend should use the members iActiveCfg and cIgnoreSetConfigs
* to indicate the new configuration state and what to do on the next SET_CONFIGURATION call.
*/
|| ( bConfigurationValue == 0
|| !pProxyDev->cIgnoreSetConfigs)
{
pProxyDev->cIgnoreSetConfigs = 0;
{
return VERR_GENERAL_FAILURE;
}
}
else if (pProxyDev->cIgnoreSetConfigs > 0)
/*
* Claim the interfaces.
*/
{
{
continue;
/* ignore failures - the backend deals with that and does the necessary logging. */
break;
}
}
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUsbSetInterface
*
* USB Device Proxy: Call OS specific code to select alternate interface settings.
*/
static DECLCALLBACK(int) usbProxyDevSetInterface(PPDMUSBINS pUsbIns, uint8_t bInterfaceNumber, uint8_t bAlternateSetting)
{
LogFlow(("usbProxyDevSetInterface: pProxyDev=%s bInterfaceNumber=%d bAlternateSetting=%d\n",
return VERR_GENERAL_FAILURE;
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUsbClearHaltedEndpoint
*
* USB Device Proxy: Call OS specific code to clear the endpoint.
*/
{
LogFlow(("usbProxyDevClearHaltedEndpoint: pProxyDev=%s uEndpoint=%u\n",
return VERR_GENERAL_FAILURE;
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbQueue
*
* USB Device Proxy: Call OS specific code.
*/
{
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbCancel
*
* USB Device Proxy: Call OS specific code.
*/
{
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbReap
*
* USB Device Proxy: Call OS specific code.
*/
{
if ( pUrb
return pUrb;
}
/** @copydoc PDMUSBREG::pfnDestruct */
{
/* close it. */
{
}
/* free the config descriptors. */
if (pThis->paCfgDescs)
{
{
}
/** @todo bugref{2693} cleanup */
}
/* free dev */
}
/**
* Helper function used by usbProxyConstruct when
* reading a filter from CFG.
*
* @returns VBox status code.
* @param pFilter The filter.
* @param enmFieldIdx The filter field indext.
* @param pNode The CFGM node.
* @param pszExact The exact value name.
* @param pszExpr The expression value name.
*/
static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, PCFGMNODE pNode, const char *pszExact, const char *pszExpr)
{
char szTmp[256];
/* try exact first */
if (RT_SUCCESS(rc))
{
/* make sure only the exact attribute is present. */
{
szTmp[0] = '\0';
return VERR_INVALID_PARAMETER;
}
return VINF_SUCCESS;
}
{
szTmp[0] = '\0';
return rc;
}
/* expression? */
if (RT_SUCCESS(rc))
{
return VINF_SUCCESS;
}
{
szTmp[0] = '\0';
return rc;
}
return VINF_SUCCESS;
}
/** @copydoc PDMUSBREG::pfnConstruct */
static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
{
/*
* Initialize the instance data.
*/
pThis->fMaskedIfs = 0;
/*
* Read the basic configuration.
*/
char szAddress[1024];
bool fRemote;
void *pvBackend;
/*
* Select backend and open the device.
*/
if (!fRemote)
else
if (RT_FAILURE(rc))
return rc;
/*
* Get the device descriptor and format the device name (for logging).
*/
{
Log(("usbProxyConstruct: usbProxyGetDeviceDesc failed\n"));
return VERR_READ_ERROR;
}
RTStrAPrintf(&pUsbIns->pszName, "%p[proxy %04x:%04x]", pThis, pThis->DevDesc.idVendor, pThis->DevDesc.idProduct); /** @todo append the user comment */
/*
* Get config descriptors.
*/
unsigned i;
break;
{
Log(("usbProxyConstruct: copy_config failed, i=%d\n", i));
return VERR_READ_ERROR;
}
/*
* Pickup best matching global configuration for this device.
* The global configuration is organized like this:
*
* |- idVendor = 300
* |- idProduct = 300
* - Config/
*
* The first level contains filter attributes which we stuff into a USBFILTER
* structure and match against the device info that's available. The highest
* ranked match is will be used. If nothing is found, the values will be
* queried from the GlobalConfig node (simplifies code and might actually
* be useful).
*/
if (pCur)
{
/*
* Create a device filter from the device configuration
* descriptor ++. No strings currently.
*/
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_VENDOR_ID, pThis->DevDesc.idVendor, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_PRODUCT_ID, pThis->DevDesc.idProduct, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_REV, pThis->DevDesc.bcdDevice, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_CLASS, pThis->DevDesc.bDeviceClass, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_SUB_CLASS, pThis->DevDesc.bDeviceSubClass, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_PROTOCOL, pThis->DevDesc.bDeviceProtocol, true); AssertRC(rc);
/** @todo manufacturer, product and serial strings */
int iBestMatchRate = -1;
{
/*
* Construct a filter from the attributes in the node.
*/
/* numeric */
if ( RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_VENDOR_ID, pCur, "idVendor", "idVendorExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pCur, "idProduct", "idProcutExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pCur, "bcdDevice", "bcdDeviceExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pCur, "bDeviceClass", "bDeviceClassExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pCur, "bDeviceSubClass", "bDeviceSubClassExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pCur, "bDeviceProtocol", "bDeviceProtocolExpr")))
continue; /* skip it */
/* strings */
/** @todo manufacturer, product and serial strings */
/* ignore unknown config values, but not without bitching. */
if (!CFGMR3AreValuesValid(pCur,
"idVendor\0idVendorExpr\0"
"idProduct\0idProductExpr\0"
"bcdDevice\0bcdDeviceExpr\0"
"bDeviceClass\0bDeviceClassExpr\0"
"bDeviceSubClass\0bDeviceSubClassExpr\0"
"bDeviceProtocol\0bDeviceProtocolExpr\0"))
LogRel(("usbProxyConstruct: Unknown value(s) in config filter (ignored)!\n"));
/*
* Try match it and on match see if it has is a higher rate hit
* than the previous match. Quit if its a 100% match.
*/
if (iRate > iBestMatchRate)
{
pBestMatch = pCur;
if (iRate >= 100)
break;
}
}
if (pBestMatch)
if (pCfgGlobalDev)
}
/*
* Query the rest of the configuration using the global as fallback.
*/
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
pThis->fMaskedIfs = 0;
else
bool fForce11Device;
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
fForce11Device = false;
else
bool fForce11PacketSize;
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
fForce11PacketSize = false;
else
/*
* If we're masking interfaces, edit the descriptors.
*/
if (pThis->fMaskedIfs)
/*
* Do 2.0 -> 1.1 device edits if requested to do so.
*/
if ( fForce11PacketSize
{
{
{
/*
* USB 1.1 defines the max for control, interrupt and bulk to be 64 bytes.
* While isochronous has a max of 1023 bytes.
*/
{
? 1023
: 64;
{
Log(("usb-proxy: pProxyDev=%s correcting wMaxPacketSize from %#x to %#x (mainly for vista)\n",
fEdited = true;
}
}
}
}
}
if ( fForce11Device
{
/*
* Discourages windows from helping you find a 2.0 port.
*/
Log(("usb-proxy: %s correcting USB version 2.0 to 1.1 (to avoid Windows warning)\n", pUsbIns->pszName));
fEdited = true;
}
/*
*/
/*
* Call the backend if it wishes to do some more initializing
* after we've read the config and descriptors.
*/
{
if (RT_FAILURE(rc))
return rc;
}
/*
* We're good!
*/
Log(("usb-proxy: created pProxyDev=%s address '%s' fMaskedIfs=%#x (rc=%Rrc)\n",
return VINF_SUCCESS;
}
/**
* The USB proxy device registration record.
*/
const PDMUSBREG g_UsbDevProxy =
{
/* u32Version */
/* szName */
"USBProxy",
/* pszDescription */
"USB Proxy Device.",
/* fFlags */
0,
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(USBPROXYDEV),
/* pfnConstruct */
/* pfnDestruct */
/* pfnVMInitComplete */
NULL,
/* pfnVMPowerOn */
NULL,
/* pfnVMReset */
NULL,
/* pfnVMSuspend */
NULL,
/* pfnVMResume */
NULL,
/* pfnVMPowerOff */
NULL,
/* pfnHotPlugged */
NULL,
/* pfnHotUnplugged */
NULL,
/* pfnDriverAttach */
NULL,
/* pfnDriverDetach */
NULL,
/* pfnQueryInterface */
NULL,
/* pfnUsbReset */
/* pfnUsbGetDescriptorCache */
/* pfnUsbSetConfiguration */
/* pfnUsbSetInterface */
/* pfnUsbClearHaltedEndpoint */
/* pfnUrbNew */
NULL,
/* pfnUrbQueue */
/* pfnUrbCancel */
/* pfnUrbReap */
/* u32TheEnd */
};