USBProxyDevice.cpp revision f5e53763b0a581b0299e98028c6c52192eb06785
/* $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;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
#include <VBox/usb.h>
#include <VBox/usbfilter.h>
#include <VBox/vmm/pdm.h>
#include <VBox/err.h>
#include <iprt/alloc.h>
#include <iprt/string.h>
#include <VBox/log.h>
#include <iprt/assert.h>
#include "USBProxyDevice.h"
#include "VUSBInternal.h"
#include "VBoxDD.h"
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** A dummy name used early during the construction phase to avoid log crashes. */
static char g_szDummyName[] = "proxy xxxx:yyyy";
/* 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)
{
LogFlow(("GetStdDescSync: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
for (;;)
{
/*
* Setup a MSG URB, queue and reap it.
*/
VUSBURB Urb;
AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K);
Urb.u32Magic = VUSBURB_MAGIC;
Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
Urb.pszDesc = (char*)"URB sync";
memset(&Urb.VUsb, 0, sizeof(Urb.VUsb));
memset(&Urb.Hci, 0, sizeof(Urb.Hci));
Urb.Dev.pvPrivate = NULL;
Urb.Dev.pNext = NULL;
Urb.pUsbIns = pProxyDev->pUsbIns;
Urb.DstAddress = 0;
Urb.EndPt = 0;
Urb.enmType = VUSBXFERTYPE_MSG;
Urb.enmDir = VUSBDIRECTION_IN;
Urb.fShortNotOk = false;
Urb.enmStatus = VUSBSTATUS_INVALID;
cbHint = RT_MIN(cbHint, sizeof(Urb.abData) - sizeof(VUSBSETUP));
Urb.cbData = cbHint + sizeof(VUSBSETUP);
PVUSBSETUP pSetup = (PVUSBSETUP)Urb.abData;
pSetup->bmRequestType = VUSB_DIR_TO_HOST | VUSB_REQ_STANDARD | VUSB_TO_DEVICE;
pSetup->bRequest = VUSB_REQ_GET_DESCRIPTOR;
pSetup->wValue = (iDescType << 8) | iIdx;
pSetup->wIndex = LangId;
pSetup->wLength = cbHint;
if (!pProxyDev->pOps->pfnUrbQueue(&Urb))
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.) */
PVUSBURB pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 10000 /* ms */);
if (!pUrbReaped)
{
pProxyDev->pOps->pfnUrbCancel(&Urb);
pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT);
}
if (pUrbReaped != &Urb)
{
Log(("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped));
break;
}
if (Urb.enmStatus != VUSBSTATUS_OK)
{
Log(("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus));
break;
}
/*
* Check the length, config descriptors have total_length field
*/
uint8_t *pbDesc = (uint8_t *)(pSetup + 1);
uint32_t cbDesc;
if (iDescType == VUSB_DT_CONFIG)
{
if (Urb.cbData < sizeof(VUSBSETUP) + 4)
{
Log(("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData));
break;
}
cbDesc = RT_LE2H_U16(((uint16_t *)pbDesc)[1]);
}
else
{
if (Urb.cbData < sizeof(VUSBSETUP) + 1)
{
Log(("GetStdDescSync: Urb.cbData=%#x (min 1)\n", Urb.cbData));
break;
}
cbDesc = ((uint8_t *)pbDesc)[0];
}
Log(("GetStdDescSync: got Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint));
if ( Urb.cbData == cbHint + sizeof(VUSBSETUP)
&& cbDesc > Urb.cbData - sizeof(VUSBSETUP))
{
cbHint = cbDesc;
if (cbHint > sizeof(Urb.abData))
{
AssertMsgFailed(("cbHint=%u\n", cbHint));
break;
}
continue;
}
Assert(cbDesc <= Urb.cbData - sizeof(VUSBSETUP));
#ifdef LOG_ENABLED
vusbUrbTrace(&Urb, "GetStdDescSync", true);
#endif
/*
* Fine, we got everything return a heap duplicate of the descriptor.
*/
return RTMemDup(pbDesc, cbDesc);
}
return NULL;
}
/**
* Frees a descriptor returned by GetStdDescSync().
*/
static void free_desc(void *pvDesc)
{
RTMemFree(pvDesc);
}
/**
* Get and a device descriptor and byteswap it appropriately.
*/
static bool usbProxyGetDeviceDesc(PUSBPROXYDEV pProxyDev, PVUSBDESCDEVICE pOut)
{
/*
* Get the descriptor from the device.
*/
PVUSBDESCDEVICE pIn = (PVUSBDESCDEVICE)GetStdDescSync(pProxyDev, VUSB_DT_DEVICE, 0, 0, VUSB_DT_DEVICE_MIN_LEN);
if (!pIn)
{
Log(("usbProxyGetDeviceDesc: pProxyDev=%s: GetStdDescSync failed\n", pProxyDev->pUsbIns->pszName));
return false;
}
if (pIn->bLength < VUSB_DT_DEVICE_MIN_LEN)
{
Log(("usb-proxy: pProxyDev=%s: Corrupted device descriptor. bLength=%d\n", pProxyDev->pUsbIns->pszName, pIn->bLength));
return false;
}
/*
* Convert it.
*/
pOut->bLength = VUSB_DT_DEVICE_MIN_LEN;
pOut->bDescriptorType = VUSB_DT_DEVICE;
pOut->bcdUSB = RT_LE2H_U16(pIn->bcdUSB);
pOut->bDeviceClass = pIn->bDeviceClass;
pOut->bDeviceSubClass = pIn->bDeviceSubClass;
pOut->bDeviceProtocol = pIn->bDeviceProtocol;
pOut->bMaxPacketSize0 = pIn->bMaxPacketSize0;
pOut->idVendor = RT_LE2H_U16(pIn->idVendor);
pOut->idProduct = RT_LE2H_U16(pIn->idProduct);
pOut->bcdDevice = RT_LE2H_U16(pIn->bcdDevice);
pOut->iManufacturer = pIn->iManufacturer;
pOut->iProduct = pIn->iProduct;
pOut->iSerialNumber = pIn->iSerialNumber;
pOut->bNumConfigurations = pIn->bNumConfigurations;
free_desc(pIn);
return true;
}
/**
* Count the numbers and types of each kind of descriptor that we need to
* copy out of the config descriptor
*/
struct desc_counts
{
size_t num_ed, num_id, num_if;
/** bitmap (128 bits) */
uint32_t idmap[4];
};
static int count_descriptors(struct desc_counts *cnt, uint8_t *buf, size_t len)
{
PVUSBDESCCONFIG cfg;
uint8_t *tmp, *end;
uint32_t i, x;
memset(cnt, 0, sizeof(*cnt));
end = buf + len;
cfg = (PVUSBDESCCONFIG)buf;
if ( cfg->bLength < VUSB_DT_CONFIG_MIN_LEN )
return 0;
if ( cfg->bLength > len )
return 0;
for (tmp = buf + cfg->bLength; ((tmp + 1) < end) && *tmp; tmp += *tmp)
{
uint8_t type;
uint32_t ifnum;
PVUSBDESCINTERFACE id;
PVUSBDESCENDPOINT ed;
type = *(tmp + 1);
switch ( type ) {
case VUSB_DT_INTERFACE:
id = (PVUSBDESCINTERFACE)tmp;
if ( id->bLength < VUSB_DT_INTERFACE_MIN_LEN )
return 0;
cnt->num_id++;
ifnum = id->bInterfaceNumber;
cnt->idmap[ifnum >> 6] |= (1 << (ifnum & 0x1f));
break;
case VUSB_DT_ENDPOINT:
ed = (PVUSBDESCENDPOINT)tmp;
if ( ed->bLength < VUSB_DT_ENDPOINT_MIN_LEN )
return 0;
cnt->num_ed++;
break;
default:
break;
}
}
/* count interfaces */
for(i=0; i < RT_ELEMENTS(cnt->idmap); i++)
for(x=1; x; x<<=1)
if ( cnt->idmap[i] & x )
cnt->num_if++;
return 1;
}
/* Setup a vusb_interface structure given some preallocated structures
* to use, (we counted them already)
*/
static int copy_interface(PVUSBINTERFACE pIf, uint8_t ifnum,
PVUSBDESCINTERFACEEX *id, PVUSBDESCENDPOINTEX *ed,
uint8_t *buf, size_t len)
{
PVUSBDESCINTERFACEEX cur_if = NULL;
uint32_t altmap[4] = {0,};
uint8_t *tmp, *end = buf + len;
uint8_t alt;
int state;
size_t num_ep = 0;
buf += *(uint8_t *)buf;
pIf->cSettings = 0;
pIf->paSettings = NULL;
for (tmp = buf, state = 0; ((tmp + 1) < end) && *tmp; tmp += *tmp)
{
uint8_t type;
PVUSBDESCINTERFACE ifd;
PVUSBDESCENDPOINT epd;
PVUSBDESCENDPOINTEX cur_ep;
type = tmp[1];
switch ( type ) {
case VUSB_DT_INTERFACE:
state = 0;
ifd = (PVUSBDESCINTERFACE)tmp;
/* Ignoring this interface */
if ( ifd->bInterfaceNumber != ifnum )
break;
/* Check we didn't see this alternate setting already
* because that will break stuff
*/
alt = ifd->bAlternateSetting;
if ( altmap[alt >> 6] & (1 << (alt & 0x1f)) )
return 0;
altmap[alt >> 6] |= (1 << (alt & 0x1f));
cur_if = *id;
(*id)++;
if ( pIf->cSettings == 0 )
pIf->paSettings = cur_if;
memcpy(cur_if, ifd, sizeof(cur_if->Core));
/** @todo copy any additional descriptor bytes into pvMore */
pIf->cSettings++;
state = 1;
num_ep = 0;
break;
case VUSB_DT_ENDPOINT:
if ( state == 0 )
break;
epd = (PVUSBDESCENDPOINT)tmp;
cur_ep = *ed;
(*ed)++;
if ( num_ep == 0 )
cur_if->paEndpoints = cur_ep;
if ( num_ep > cur_if->Core.bNumEndpoints )
return 0;
memcpy(cur_ep, epd, sizeof(cur_ep->Core));
/** @todo copy any additional descriptor bytes into pvMore */
cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize);
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.
*/
static bool copy_config(PUSBPROXYDEV pProxyDev, uint8_t idx, PVUSBDESCCONFIGEX out)
{
PVUSBDESCCONFIG cfg;
PVUSBINTERFACE pIf;
PVUSBDESCINTERFACEEX ifd;
PVUSBDESCENDPOINTEX epd;
struct desc_counts cnt;
void *descs;
size_t tot_len;
size_t cbIface;
uint32_t i, x;
descs = GetStdDescSync(pProxyDev, VUSB_DT_CONFIG, idx, 0, VUSB_DT_CONFIG_MIN_LEN);
if ( descs == NULL ) {
Log(("copy_config: GetStdDescSync failed\n"));
return false;
}
cfg = (PVUSBDESCCONFIG)descs;
tot_len = RT_LE2H_U16(cfg->wTotalLength);
if ( !count_descriptors(&cnt, (uint8_t *)descs, tot_len) ) {
Log(("copy_config: count_descriptors failed\n"));
goto err;
}
if ( cfg->bNumInterfaces != cnt.num_if )
Log(("usb-proxy: config%u: bNumInterfaces %u != %u\n",
idx, cfg->bNumInterfaces, cnt.num_if));
Log(("usb-proxy: config%u: %u bytes id=%u ed=%u if=%u\n",
idx, tot_len, cnt.num_id, cnt.num_ed, cnt.num_if));
cbIface = cnt.num_if * sizeof(VUSBINTERFACE)
+ cnt.num_id * sizeof(VUSBDESCINTERFACEEX)
+ cnt.num_ed * sizeof(VUSBDESCENDPOINTEX);
out->paIfs = (PCVUSBINTERFACE)RTMemAllocZ(cbIface);
if ( out->paIfs == NULL ) {
free_desc(descs);
return false;
}
pIf = (PVUSBINTERFACE)out->paIfs;
ifd = (PVUSBDESCINTERFACEEX)&pIf[cnt.num_if];
epd = (PVUSBDESCENDPOINTEX)&ifd[cnt.num_id];
out->Core.bLength = cfg->bLength;
out->Core.bDescriptorType = cfg->bDescriptorType;
out->Core.wTotalLength = 0; /* Auto Calculated */
out->Core.bNumInterfaces = (uint8_t)cnt.num_if;
out->Core.bConfigurationValue = cfg->bConfigurationValue;
out->Core.iConfiguration = cfg->iConfiguration;
out->Core.bmAttributes = cfg->bmAttributes;
out->Core.MaxPower = cfg->MaxPower;
for(i=0; i < 4; i++)
for(x=0; x < 32; x++)
if ( cnt.idmap[i] & (1 << x) )
if ( !copy_interface(pIf++, (i << 6) | x, &ifd, &epd, (uint8_t *)descs, tot_len) ) {
Log(("copy_interface(%d,,) failed\n", pIf - 1));
goto err;
}
free_desc(descs);
return true;
err:
Log(("usb-proxy: config%u: Corrupted configuration descriptor\n", idx));
free_desc(descs);
return false;
}
/**
* Edit out masked interface descriptors.
*
* @param pProxyDev The proxy device
*/
static void usbProxyDevEditOutMaskedIfs(PUSBPROXYDEV pProxyDev)
{
unsigned cRemoved = 0;
PVUSBDESCCONFIGEX paCfgs = pProxyDev->paCfgDescs;
for (unsigned iCfg = 0; iCfg < pProxyDev->DevDesc.bNumConfigurations; iCfg++)
{
PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs;
for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++)
for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++)
if ( paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber < 32
&& ((1 << paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber) & pProxyDev->fMaskedIfs))
{
Log(("usb-proxy: removing interface #%d (iIf=%d iAlt=%d) on config #%d (iCfg=%d)\n",
paIfs[iIf].paSettings[iAlt].Core.bInterfaceNumber, iIf, iAlt, paCfgs[iCfg].Core.bConfigurationValue, iCfg));
cRemoved++;
paCfgs[iCfg].Core.bNumInterfaces--;
unsigned cToCopy = paCfgs[iCfg].Core.bNumInterfaces - iIf;
if (cToCopy)
memmove(&paIfs[iIf], &paIfs[iIf + 1], sizeof(paIfs[0]) * cToCopy);
memset(&paIfs[iIf + cToCopy], '\0', sizeof(paIfs[0]));
break;
}
}
Log(("usb-proxy: edited out %d interface(s).\n", cRemoved));
}
/**
* @copydoc PDMUSBREG::pfnUsbReset
*
* USB Device Proxy: Call OS specific code to reset the device.
*/
static DECLCALLBACK(int) usbProxyDevReset(PPDMUSBINS pUsbIns, bool fResetOnLinux)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
if (pProxyDev->fMaskedIfs)
{
Log(("usbProxyDevReset: pProxyDev=%s - ignoring reset request fMaskedIfs=%#x\n", pUsbIns->pszName, pProxyDev->fMaskedIfs));
return VINF_SUCCESS;
}
LogFlow(("usbProxyDevReset: pProxyDev=%s\n", pUsbIns->pszName));
return pProxyDev->pOps->pfnReset(pProxyDev, fResetOnLinux);
}
/**
* @copydoc PDMUSBREG::pfnUsbGetDescriptorCache
*/
static DECLCALLBACK(PCPDMUSBDESCCACHE) usbProxyDevGetDescriptorCache(PPDMUSBINS pUsbIns)
{
PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
return &pThis->DescCache;
}
/**
* @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,
const void *pvOldCfgDesc, const void *pvOldIfState, const void *pvNewCfgDesc)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
LogFlow(("usbProxyDevSetConfiguration: pProxyDev=%s iActiveCfg=%d bConfigurationValue=%d\n",
pUsbIns->pszName, pProxyDev->iActiveCfg, bConfigurationValue));
/*
* Release the current config.
*/
if (pvOldCfgDesc)
{
PCVUSBDESCCONFIGEX pOldCfgDesc = (PCVUSBDESCCONFIGEX)pvOldCfgDesc;
PCVUSBINTERFACESTATE pOldIfState = (PCVUSBINTERFACESTATE)pvOldIfState;
for (unsigned i = 0; i < pOldCfgDesc->Core.bNumInterfaces; i++)
if (pOldIfState[i].pCurIfDesc)
pProxyDev->pOps->pfnReleaseInterface(pProxyDev, pOldIfState[i].pCurIfDesc->Core.bInterfaceNumber);
}
/*
* Do the actual SET_CONFIGURE.
* The mess here is because most backends will already have selected a
* configuration and there are a bunch of devices which will freak out
* if we do SET_CONFIGURE twice with the same value. (PalmOne, TrekStor USB-StickGO, ..)
*
* After open and reset the backend should use the members iActiveCfg and cIgnoreSetConfigs
* to indicate the new configuration state and what to do on the next SET_CONFIGURATION call.
*/
if ( pProxyDev->iActiveCfg != bConfigurationValue
|| ( bConfigurationValue == 0
&& pProxyDev->iActiveCfg != -1 /* this test doesn't make sense, we know it's 0 */
&& pProxyDev->cIgnoreSetConfigs >= 2)
|| !pProxyDev->cIgnoreSetConfigs)
{
pProxyDev->cIgnoreSetConfigs = 0;
if (!pProxyDev->pOps->pfnSetConfig(pProxyDev, bConfigurationValue))
{
pProxyDev->iActiveCfg = -1;
return VERR_GENERAL_FAILURE;
}
pProxyDev->iActiveCfg = bConfigurationValue;
}
else if (pProxyDev->cIgnoreSetConfigs > 0)
pProxyDev->cIgnoreSetConfigs--;
/*
* Claim the interfaces.
*/
PCVUSBDESCCONFIGEX pNewCfgDesc = (PCVUSBDESCCONFIGEX)pvNewCfgDesc;
Assert(pNewCfgDesc->Core.bConfigurationValue == bConfigurationValue);
for (unsigned iIf = 0; iIf < pNewCfgDesc->Core.bNumInterfaces; iIf++)
{
PCVUSBINTERFACE pIf = &pNewCfgDesc->paIfs[iIf];
for (uint32_t iAlt = 0; iAlt < pIf->cSettings; iAlt++)
{
if (pIf->paSettings[iAlt].Core.bAlternateSetting != 0)
continue;
pProxyDev->pOps->pfnClaimInterface(pProxyDev, pIf->paSettings[iAlt].Core.bInterfaceNumber);
/* ignore failures - the backend deals with that and does the necessary logging. */
break;
}
}
return VINF_SUCCESS;
}
/**
* @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)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
LogFlow(("usbProxyDevSetInterface: pProxyDev=%s bInterfaceNumber=%d bAlternateSetting=%d\n",
pUsbIns->pszName, bInterfaceNumber, bAlternateSetting));
/** @todo this is fishy, pfnSetInterface returns true/false from what I can see... */
if (pProxyDev->pOps->pfnSetInterface(pProxyDev, bInterfaceNumber, bAlternateSetting) < 0)
return VERR_GENERAL_FAILURE;
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUsbClearHaltedEndpoint
*
* USB Device Proxy: Call OS specific code to clear the endpoint.
*/
static DECLCALLBACK(int) usbProxyDevClearHaltedEndpoint(PPDMUSBINS pUsbIns, unsigned uEndpoint)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
LogFlow(("usbProxyDevClearHaltedEndpoint: pProxyDev=%s uEndpoint=%u\n",
pUsbIns->pszName, uEndpoint));
if (!pProxyDev->pOps->pfnClearHaltedEndpoint(pProxyDev, uEndpoint))
return VERR_GENERAL_FAILURE;
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbQueue
*
* USB Device Proxy: Call OS specific code.
*/
static DECLCALLBACK(int) usbProxyDevUrbQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
if (!pProxyDev->pOps->pfnUrbQueue(pUrb))
return pProxyDev->fDetached
? VERR_VUSB_DEVICE_NOT_ATTACHED
: VERR_VUSB_FAILED_TO_QUEUE_URB;
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbCancel
*
* USB Device Proxy: Call OS specific code.
*/
static DECLCALLBACK(int) usbProxyDevUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
pProxyDev->pOps->pfnUrbCancel(pUrb);
return VINF_SUCCESS;
}
/**
* @copydoc PDMUSBREG::pfnUrbReap
*
* USB Device Proxy: Call OS specific code.
*/
static DECLCALLBACK(PVUSBURB) usbProxyDevUrbReap(PPDMUSBINS pUsbIns, RTMSINTERVAL cMillies)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
PVUSBURB pUrb = pProxyDev->pOps->pfnUrbReap(pProxyDev, cMillies);
if ( pUrb
&& pUrb->enmState == VUSBURBSTATE_CANCELLED
&& pUrb->enmStatus == VUSBSTATUS_OK)
pUrb->enmStatus = VUSBSTATUS_DNR;
return pUrb;
}
/** @copydoc PDMUSBREG::pfnDestruct */
static DECLCALLBACK(void) usbProxyDestruct(PPDMUSBINS pUsbIns)
{
PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
Log(("usbProxyDestruct: destroying pProxyDev=%s\n", pUsbIns->pszName));
/* close it. */
if (pThis->fOpened)
{
pThis->pOps->pfnClose(pThis);
pThis->fOpened = false;
}
/* free the config descriptors. */
if (pThis->paCfgDescs)
{
for (unsigned i = 0; i < pThis->DevDesc.bNumConfigurations; i++)
RTMemFree((void *)pThis->paCfgDescs[i].paIfs);
/** @todo bugref{2693} cleanup */
RTMemFree(pThis->paCfgDescs);
pThis->paCfgDescs = NULL;
}
/* free dev */
if (&g_szDummyName[0] != pUsbIns->pszName)
RTStrFree(pUsbIns->pszName);
pUsbIns->pszName = NULL;
}
/**
* Helper function used by usbProxyConstruct when
* reading a filter from CFG.
*
* @returns VBox status code.
* @param pFilter The filter.
* @param enmFieldIdx The filter field indext.
* @param pNode The CFGM node.
* @param pszExact The exact value name.
* @param pszExpr The expression value name.
*/
static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, PCFGMNODE pNode, const char *pszExact, const char *pszExpr)
{
char szTmp[256];
/* try exact first */
uint16_t u16;
int rc = CFGMR3QueryU16(pNode, pszExact, &u16);
if (RT_SUCCESS(rc))
{
rc = USBFilterSetNumExact(pFilter, enmFieldIdx, u16, true);
AssertRCReturn(rc, rc);
/* make sure only the exact attribute is present. */
rc = CFGMR3QueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
{
szTmp[0] = '\0';
CFGMR3GetName(pNode, szTmp, sizeof(szTmp));
LogRel(("usbProxyConstruct: %s: Both %s and %s are present!\n", szTmp, pszExact, pszExpr));
return VERR_INVALID_PARAMETER;
}
return VINF_SUCCESS;
}
if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
{
szTmp[0] = '\0';
CFGMR3GetName(pNode, szTmp, sizeof(szTmp));
LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc));
return rc;
}
/* expression? */
rc = CFGMR3QueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
if (RT_SUCCESS(rc))
{
rc = USBFilterSetNumExpression(pFilter, enmFieldIdx, szTmp, true);
AssertRCReturn(rc, rc);
return VINF_SUCCESS;
}
if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
{
szTmp[0] = '\0';
CFGMR3GetName(pNode, szTmp, sizeof(szTmp));
LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExpr, rc));
return rc;
}
return VINF_SUCCESS;
}
/** @copydoc PDMUSBREG::pfnConstruct */
static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
{
PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
LogFlow(("usbProxyConstruct: pUsbIns=%p iInstance=%d\n", pUsbIns, iInstance));
/*
* Initialize the instance data.
*/
pThis->pUsbIns = pUsbIns;
pThis->pUsbIns->pszName = g_szDummyName;
pThis->iActiveCfg = -1;
pThis->fMaskedIfs = 0;
pThis->fOpened = false;
pThis->fInited = false;
/*
* Read the basic configuration.
*/
char szAddress[1024];
int rc = CFGMR3QueryString(pCfg, "Address", szAddress, sizeof(szAddress));
AssertRCReturn(rc, rc);
bool fRemote;
rc = CFGMR3QueryBool(pCfg, "Remote", &fRemote);
AssertRCReturn(rc, rc);
void *pvBackend;
rc = CFGMR3QueryPtr(pCfg, "pvBackend", &pvBackend);
AssertRCReturn(rc, rc);
/*
* Select backend and open the device.
*/
if (!fRemote)
pThis->pOps = &g_USBProxyDeviceHost;
else
pThis->pOps = &g_USBProxyDeviceVRDP;
rc = pThis->pOps->pfnOpen(pThis, szAddress, pvBackend);
if (RT_FAILURE(rc))
return rc;
pThis->fOpened = true;
/*
* Get the device descriptor and format the device name (for logging).
*/
if (!usbProxyGetDeviceDesc(pThis, &pThis->DevDesc))
{
Log(("usbProxyConstruct: usbProxyGetDeviceDesc failed\n"));
return VERR_READ_ERROR;
}
RTStrAPrintf(&pUsbIns->pszName, "%p[proxy %04x:%04x]", pThis, pThis->DevDesc.idVendor, pThis->DevDesc.idProduct); /** @todo append the user comment */
AssertReturn(pUsbIns->pszName, VERR_NO_MEMORY);
/*
* Get config descriptors.
*/
size_t cbConfigs = pThis->DevDesc.bNumConfigurations * sizeof(pThis->paCfgDescs[0]);
pThis->paCfgDescs = (PVUSBDESCCONFIGEX)RTMemAllocZ(cbConfigs);
AssertReturn(pThis->paCfgDescs, VERR_NO_MEMORY);
unsigned i;
for (i = 0; i < pThis->DevDesc.bNumConfigurations; i++)
if (!copy_config(pThis, i, (PVUSBDESCCONFIGEX)&pThis->paCfgDescs[i]))
break;
if (i < pThis->DevDesc.bNumConfigurations)
{
Log(("usbProxyConstruct: copy_config failed, i=%d\n", i));
return VERR_READ_ERROR;
}
/*
* Pickup best matching global configuration for this device.
* The global configuration is organized like this:
*
* GlobalConfig/Whatever/
* |- idVendor = 300
* |- idProduct = 300
* - Config/
*
* The first level contains filter attributes which we stuff into a USBFILTER
* structure and match against the device info that's available. The highest
* ranked match is will be used. If nothing is found, the values will be
* queried from the GlobalConfig node (simplifies code and might actually
* be useful).
*/
PCFGMNODE pCfgGlobalDev = pCfgGlobal;
PCFGMNODE pCur = CFGMR3GetFirstChild(pCfgGlobal);
if (pCur)
{
/*
* Create a device filter from the device configuration
* descriptor ++. No strings currently.
*/
USBFILTER Device;
USBFilterInit(&Device, USBFILTERTYPE_CAPTURE);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_VENDOR_ID, pThis->DevDesc.idVendor, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_PRODUCT_ID, pThis->DevDesc.idProduct, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_REV, pThis->DevDesc.bcdDevice, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_CLASS, pThis->DevDesc.bDeviceClass, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_SUB_CLASS, pThis->DevDesc.bDeviceSubClass, true); AssertRC(rc);
rc = USBFilterSetNumExact(&Device, USBFILTERIDX_DEVICE_PROTOCOL, pThis->DevDesc.bDeviceProtocol, true); AssertRC(rc);
/** @todo manufacturer, product and serial strings */
int iBestMatchRate = -1;
PCFGMNODE pBestMatch = NULL;
for (pCur = CFGMR3GetFirstChild(pCfgGlobal); pCur; pCur = CFGMR3GetNextChild(pCur))
{
/*
* Construct a filter from the attributes in the node.
*/
USBFILTER Filter;
USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE);
/* numeric */
if ( RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_VENDOR_ID, pCur, "idVendor", "idVendorExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pCur, "idProduct", "idProcutExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pCur, "bcdDevice", "bcdDeviceExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pCur, "bDeviceClass", "bDeviceClassExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pCur, "bDeviceSubClass", "bDeviceSubClassExpr"))
|| RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pCur, "bDeviceProtocol", "bDeviceProtocolExpr")))
continue; /* skip it */
/* strings */
/** @todo manufacturer, product and serial strings */
/* ignore unknown config values, but not without bitching. */
if (!CFGMR3AreValuesValid(pCur,
"idVendor\0idVendorExpr\0"
"idProduct\0idProductExpr\0"
"bcdDevice\0bcdDeviceExpr\0"
"bDeviceClass\0bDeviceClassExpr\0"
"bDeviceSubClass\0bDeviceSubClassExpr\0"
"bDeviceProtocol\0bDeviceProtocolExpr\0"))
LogRel(("usbProxyConstruct: Unknown value(s) in config filter (ignored)!\n"));
/*
* Try match it and on match see if it has is a higher rate hit
* than the previous match. Quit if its a 100% match.
*/
int iRate = USBFilterMatchRated(&Filter, &Device);
if (iRate > iBestMatchRate)
{
pBestMatch = pCur;
iBestMatchRate = iRate;
if (iRate >= 100)
break;
}
}
if (pBestMatch)
pCfgGlobalDev = CFGMR3GetChild(pBestMatch, "Config");
if (pCfgGlobalDev)
pCfgGlobalDev = pCfgGlobal;
}
/*
* Query the rest of the configuration using the global as fallback.
*/
rc = CFGMR3QueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
rc = CFGMR3QueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
pThis->fMaskedIfs = 0;
else
AssertRCReturn(rc, rc);
bool fForce11Device;
rc = CFGMR3QueryBool(pCfg, "Force11Device", &fForce11Device);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
fForce11Device = false;
else
AssertRCReturn(rc, rc);
bool fForce11PacketSize;
rc = CFGMR3QueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
rc = CFGMR3QueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize);
if (rc == VERR_CFGM_VALUE_NOT_FOUND)
fForce11PacketSize = false;
else
AssertRCReturn(rc, rc);
/*
* If we're masking interfaces, edit the descriptors.
*/
bool fEdited = pThis->fMaskedIfs != 0;
if (pThis->fMaskedIfs)
usbProxyDevEditOutMaskedIfs(pThis);
/*
* Do 2.0 -> 1.1 device edits if requested to do so.
*/
if ( fForce11PacketSize
&& pThis->DevDesc.bcdUSB >= 0x0200)
{
PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs;
for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++)
{
PVUSBINTERFACE paIfs = (PVUSBINTERFACE)paCfgs[iCfg].paIfs;
for (unsigned iIf = 0; iIf < paCfgs[iCfg].Core.bNumInterfaces; iIf++)
for (uint32_t iAlt = 0; iAlt < paIfs[iIf].cSettings; iAlt++)
{
/*
* USB 1.1 defines the max for control, interrupt and bulk to be 64 bytes.
* While isochronous has a max of 1023 bytes.
*/
PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints;
for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++)
{
const uint16_t cbMax = (paEps[iEp].Core.bmAttributes & 3) == 1 /* isoc */
? 1023
: 64;
if (paEps[iEp].Core.wMaxPacketSize > cbMax)
{
Log(("usb-proxy: pProxyDev=%s correcting wMaxPacketSize from %#x to %#x (mainly for vista)\n",
pUsbIns->pszName, paEps[iEp].Core.wMaxPacketSize, cbMax));
paEps[iEp].Core.wMaxPacketSize = cbMax;
fEdited = true;
}
}
}
}
}
if ( fForce11Device
&& pThis->DevDesc.bcdUSB == 0x0200)
{
/*
* Discourages windows from helping you find a 2.0 port.
*/
Log(("usb-proxy: %s correcting USB version 2.0 to 1.1 (to avoid Windows warning)\n", pUsbIns->pszName));
pThis->DevDesc.bcdUSB = 0x110;
fEdited = true;
}
/*
* Init the PDM/VUSB descriptor cache.
*/
pThis->DescCache.pDevice = &pThis->DevDesc;
pThis->DescCache.paConfigs = pThis->paCfgDescs;
pThis->DescCache.paLanguages = NULL;
pThis->DescCache.cLanguages = 0;
pThis->DescCache.fUseCachedDescriptors = fEdited;
pThis->DescCache.fUseCachedStringsDescriptors = false;
/*
* Call the backend if it wishes to do some more initializing
* after we've read the config and descriptors.
*/
if (pThis->pOps->pfnInit)
{
rc = pThis->pOps->pfnInit(pThis);
if (RT_FAILURE(rc))
return rc;
}
pThis->fInited = true;
/*
* We're good!
*/
Log(("usb-proxy: created pProxyDev=%s address '%s' fMaskedIfs=%#x (rc=%Rrc)\n",
pUsbIns->pszName, szAddress, pThis->fMaskedIfs, rc));
return VINF_SUCCESS;
}
/**
* The USB proxy device registration record.
*/
const PDMUSBREG g_UsbDevProxy =
{
/* u32Version */
PDM_USBREG_VERSION,
/* szName */
"USBProxy",
/* pszDescription */
"USB Proxy Device.",
/* fFlags */
0,
/* cMaxInstances */
~0,
/* cbInstance */
sizeof(USBPROXYDEV),
/* pfnConstruct */
usbProxyConstruct,
/* pfnDestruct */
usbProxyDestruct,
/* pfnVMInitComplete */
NULL,
/* pfnVMPowerOn */
NULL,
/* pfnVMReset */
NULL,
/* pfnVMSuspend */
NULL,
/* pfnVMResume */
NULL,
/* pfnVMPowerOff */
NULL,
/* pfnHotPlugged */
NULL,
/* pfnHotUnplugged */
NULL,
/* pfnDriverAttach */
NULL,
/* pfnDriverDetach */
NULL,
/* pfnQueryInterface */
NULL,
/* pfnUsbReset */
usbProxyDevReset,
/* pfnUsbGetDescriptorCache */
usbProxyDevGetDescriptorCache,
/* pfnUsbSetConfiguration */
usbProxyDevSetConfiguration,
/* pfnUsbSetInterface */
usbProxyDevSetInterface,
/* pfnUsbClearHaltedEndpoint */
usbProxyDevClearHaltedEndpoint,
/* pfnUrbNew */
NULL,
/* pfnUrbQueue */
usbProxyDevUrbQueue,
/* pfnUrbCancel */
usbProxyDevUrbCancel,
/* pfnUrbReap */
usbProxyDevUrbReap,
/* u32TheEnd */
PDM_USBREG_VERSION
};