summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/USB/USBProxyDevice.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/USB/USBProxyDevice.cpp')
-rw-r--r--src/VBox/Devices/USB/USBProxyDevice.cpp1328
1 files changed, 1328 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/USBProxyDevice.cpp b/src/VBox/Devices/USB/USBProxyDevice.cpp
new file mode 100644
index 00000000..ab377159
--- /dev/null
+++ b/src/VBox/Devices/USB/USBProxyDevice.cpp
@@ -0,0 +1,1328 @@
+/* $Id: USBProxyDevice.cpp $ */
+/** @file
+ * USBProxy - USB device proxy.
+ */
+
+/*
+ * Copyright (C) 2006-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* 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";
+
+/**
+ * Array of supported proxy backends.
+ */
+static PCUSBPROXYBACK g_aUsbProxies[] =
+{
+ &g_USBProxyDeviceHost,
+ &g_USBProxyDeviceVRDP,
+ &g_USBProxyDeviceUsbIp
+};
+
+/* 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)
+{
+#define GET_DESC_RETRIES 6
+ int cRetries = 0;
+ uint16_t cbInitialHint = cbHint;
+
+ LogFlow(("GetStdDescSync: pProxyDev=%s, iDescType=%d, iIdx=%d, LangId=%04X, cbHint=%u\n", pProxyDev->pUsbIns->pszName, iDescType, iIdx, LangId, cbHint));
+ for (;;)
+ {
+ /*
+ * Setup a MSG URB, queue and reap it.
+ */
+ int rc = VINF_SUCCESS;
+ VUSBURB Urb;
+ AssertCompile(RT_SIZEOFMEMB(VUSBURB, abData) >= _4K);
+ RT_ZERO(Urb);
+ Urb.u32Magic = VUSBURB_MAGIC;
+ Urb.enmState = VUSBURBSTATE_IN_FLIGHT;
+ Urb.pszDesc = (char*)"URB sync";
+ 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;
+
+ uint8_t *pbDesc = (uint8_t *)(pSetup + 1);
+ uint32_t cbDesc = 0;
+ PVUSBURB pUrbReaped = NULL;
+
+ rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, &Urb);
+ if (RT_FAILURE(rc))
+ {
+ Log(("GetStdDescSync: pfnUrbQueue failed, rc=%d\n", rc));
+ goto err;
+ }
+
+ /* 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 refuse to respond for instance.) */
+ pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, 5000 /* ms */);
+ if (!pUrbReaped)
+ {
+ Log(("GetStdDescSync: pfnUrbReap returned NULL, cancel and re-reap\n"));
+ rc = pProxyDev->pOps->pfnUrbCancel(pProxyDev, &Urb);
+ AssertRC(rc);
+ /** @todo This breaks the comment above... */
+ pUrbReaped = pProxyDev->pOps->pfnUrbReap(pProxyDev, RT_INDEFINITE_WAIT);
+ }
+ if (pUrbReaped != &Urb)
+ {
+ Log(("GetStdDescSync: pfnUrbReap failed, pUrbReaped=%p\n", pUrbReaped));
+ goto err;
+ }
+
+ if (Urb.enmStatus != VUSBSTATUS_OK)
+ {
+ Log(("GetStdDescSync: Urb.enmStatus=%d\n", Urb.enmStatus));
+ goto err;
+ }
+
+ /*
+ * Check the length, config descriptors have total_length field
+ */
+ if (iDescType == VUSB_DT_CONFIG)
+ {
+ if (Urb.cbData < sizeof(VUSBSETUP) + 4)
+ {
+ Log(("GetStdDescSync: Urb.cbData=%#x (min 4)\n", Urb.cbData));
+ goto err;
+ }
+ 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));
+ goto err;
+ }
+ 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;
+ Log(("GetStdDescSync: Part descriptor, Urb.cbData=%u, cbDesc=%u cbHint=%u\n", Urb.cbData, cbDesc, cbHint));
+
+ if (cbHint > sizeof(Urb.abData))
+ {
+ Log(("GetStdDescSync: cbHint=%u, Urb.abData=%u, retrying immediately\n", cbHint, sizeof(Urb.abData)));
+ /* Not an error, go again without incrementing retry count or delaying. */
+ continue;
+ }
+
+ goto err;
+ }
+
+ if (cbDesc > Urb.cbData - sizeof(VUSBSETUP))
+ {
+ Log(("GetStdDescSync: Descriptor length too short, cbDesc=%u, Urb.cbData=%u\n", cbDesc, Urb.cbData));
+ goto err;
+ }
+
+ if ( cbInitialHint != cbHint
+ && ( cbDesc != cbHint
+ || Urb.cbData < cbInitialHint) )
+ {
+ Log(("GetStdDescSync: Descriptor length incorrect, cbDesc=%u, Urb.cbData=%u, cbHint=%u\n", cbDesc, Urb.cbData, cbHint));
+ goto err;
+ }
+
+#ifdef LOG_ENABLED
+ vusbUrbTrace(&Urb, "GetStdDescSync", true);
+#endif
+
+ /*
+ * Fine, we got everything return a heap duplicate of the descriptor.
+ */
+ return RTMemDup(pbDesc, cbDesc);
+
+err:
+ cRetries++;
+ if (cRetries < GET_DESC_RETRIES)
+ {
+ Log(("GetStdDescSync: Retrying %u/%u\n", cRetries, GET_DESC_RETRIES));
+ RTThreadSleep(100);
+ continue;
+ }
+ else
+ {
+ Log(("GetStdDescSync: Retries exceeded %u/%u. Giving up.\n", cRetries, GET_DESC_RETRIES));
+ break;
+ }
+ }
+
+ 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;
+}
+
+/* Given the pointer to a configuration/interface/endpoint descriptor, find any following
+ * non-standard (vendor or class) descriptors.
+ */
+static const void *collect_stray_bits(uint8_t *this_desc, uint8_t *end, uint16_t *cbExtra)
+{
+ uint8_t *tmp, *buf;
+ uint8_t type;
+
+ Assert(*(this_desc + 1) == VUSB_DT_INTERFACE || *(this_desc + 1) == VUSB_DT_ENDPOINT || *(this_desc + 1) == VUSB_DT_CONFIG);
+ buf = this_desc;
+
+ /* Skip the current configuration/interface/endpoint descriptor. */
+ buf += *(uint8_t *)buf;
+
+ /* Loop until we find another descriptor we understand. */
+ for (tmp = buf; ((tmp + 1) < end) && *tmp; tmp += *tmp)
+ {
+ type = *(tmp + 1);
+ if (type == VUSB_DT_INTERFACE || type == VUSB_DT_ENDPOINT)
+ break;
+ }
+ *cbExtra = tmp - buf;
+ if (*cbExtra)
+ return buf;
+ else
+ return NULL;
+}
+
+/* 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));
+
+ /* Point to additional interface descriptor bytes, if any. */
+ AssertCompile(sizeof(cur_if->Core) == VUSB_DT_INTERFACE_MIN_LEN);
+ if (cur_if->Core.bLength - VUSB_DT_INTERFACE_MIN_LEN > 0)
+ cur_if->pvMore = tmp + VUSB_DT_INTERFACE_MIN_LEN;
+ else
+ cur_if->pvMore = NULL;
+
+ cur_if->pvClass = collect_stray_bits(tmp, end, &cur_if->cbClass);
+
+ 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));
+
+ /* Point to additional endpoint descriptor bytes, if any. */
+ AssertCompile(sizeof(cur_ep->Core) == VUSB_DT_ENDPOINT_MIN_LEN);
+ if (cur_ep->Core.bLength - VUSB_DT_ENDPOINT_MIN_LEN > 0)
+ cur_ep->pvMore = tmp + VUSB_DT_ENDPOINT_MIN_LEN;
+ else
+ cur_ep->pvMore = NULL;
+
+ cur_ep->pvClass = collect_stray_bits(tmp, end, &cur_ep->cbClass);
+
+ cur_ep->Core.wMaxPacketSize = RT_LE2H_U16(cur_ep->Core.wMaxPacketSize);
+
+ num_ep++;
+ break;
+ default:
+ /* Skip unknown descriptors. */
+ 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;
+ uint8_t *tmp, *end;
+
+ 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;
+ }
+
+ /* Stash a pointer to the raw config descriptor; we may need bits of it later. */
+ out->pvOriginal = descs;
+
+ 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;
+
+ tmp = (uint8_t *)out->pvOriginal;
+ end = tmp + tot_len;
+
+ /* Point to additional configuration descriptor bytes, if any. */
+ AssertCompile(sizeof(out->Core) == VUSB_DT_CONFIG_MIN_LEN);
+ if (out->Core.bLength - VUSB_DT_CONFIG_MIN_LEN > 0)
+ out->pvMore = tmp + VUSB_DT_CONFIG_MIN_LEN;
+ else
+ out->pvMore = NULL;
+
+ /* Typically there might be an interface association descriptor here. */
+ out->pvClass = collect_stray_bits(tmp, end, &out->cbClass);
+
+ 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 *)out->pvOriginal, tot_len) ) {
+ Log(("copy_interface(%d,,) failed\n", pIf - 1));
+ goto err;
+ }
+
+ 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));
+}
+
+
+/**
+ * @interface_method_impl{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);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUsbGetDescriptorCache}
+ */
+static DECLCALLBACK(PCPDMUSBDESCCACHE) usbProxyDevGetDescriptorCache(PPDMUSBINS pUsbIns)
+{
+ PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ return &pThis->DescCache;
+}
+
+
+/**
+ * @interface_method_impl{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;
+ int rc = pProxyDev->pOps->pfnSetConfig(pProxyDev, bConfigurationValue);
+ if (RT_FAILURE(rc))
+ {
+ pProxyDev->iActiveCfg = -1;
+ return rc;
+ }
+ 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;
+}
+
+
+/**
+ * @interface_method_impl{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));
+
+ return pProxyDev->pOps->pfnSetInterface(pProxyDev, bInterfaceNumber, bAlternateSetting);
+}
+
+
+/**
+ * @interface_method_impl{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));
+
+ return pProxyDev->pOps->pfnClearHaltedEndpoint(pProxyDev, uEndpoint);
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbQueue}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevUrbQueue(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ int rc = VINF_SUCCESS;
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ rc = pProxyDev->pOps->pfnUrbQueue(pProxyDev, pUrb);
+ if (RT_FAILURE(rc))
+ return pProxyDev->fDetached
+ ? VERR_VUSB_DEVICE_NOT_ATTACHED
+ : VERR_VUSB_FAILED_TO_QUEUE_URB;
+ return rc;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnUrbCancel}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevUrbCancel(PPDMUSBINS pUsbIns, PVUSBURB pUrb)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ return pProxyDev->pOps->pfnUrbCancel(pProxyDev, pUrb);
+}
+
+
+/**
+ * @interface_method_impl{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;
+}
+
+
+/**
+ * @interface_method_impl{PDMUSBREG,pfnWakeup}
+ *
+ * USB Device Proxy: Call OS specific code.
+ */
+static DECLCALLBACK(int) usbProxyDevWakeup(PPDMUSBINS pUsbIns)
+{
+ PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+
+ return pProxyDev->pOps->pfnWakeup(pProxyDev);
+}
+
+
+/** @interface_method_impl{PDMUSBREG,pfnDestruct} */
+static DECLCALLBACK(void) usbProxyDestruct(PPDMUSBINS pUsbIns)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN_VOID(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);
+ RTMemFree((void *)pThis->paCfgDescs[i].pvOriginal);
+ }
+ RTMemFree(pThis->paCfgDescs);
+ pThis->paCfgDescs = NULL;
+ }
+
+ /* free dev */
+ if (&g_szDummyName[0] != pUsbIns->pszName)
+ RTStrFree(pUsbIns->pszName);
+ pUsbIns->pszName = NULL;
+
+ if (pThis->pvInstanceDataR3)
+ RTMemFree(pThis->pvInstanceDataR3);
+}
+
+
+/**
+ * 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 pHlp The USB helper callback table.
+ * @param pNode The CFGM node.
+ * @param pszExact The exact value name.
+ * @param pszExpr The expression value name.
+ */
+static int usbProxyQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx,
+ PCPDMUSBHLP pHlp, PCFGMNODE pNode,
+ const char *pszExact, const char *pszExpr)
+{
+ char szTmp[256];
+
+ /* try exact first */
+ uint16_t u16;
+ int rc = pHlp->pfnCFGMQueryU16(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 = pHlp->pfnCFGMQueryString(pNode, pszExpr, szTmp, sizeof(szTmp));
+ if (RT_UNLIKELY(rc != VERR_CFGM_VALUE_NOT_FOUND))
+ {
+ szTmp[0] = '\0';
+ pHlp->pfnCFGMGetName(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';
+ pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExact, rc));
+ return rc;
+ }
+
+ /* expression? */
+ rc = pHlp->pfnCFGMQueryString(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';
+ pHlp->pfnCFGMGetName(pNode, szTmp, sizeof(szTmp));
+ LogRel(("usbProxyConstruct: %s: %s query failed, rc=%Rrc\n", szTmp, pszExpr, rc));
+ return rc;
+ }
+
+ return VINF_SUCCESS;
+}
+
+
+/** @interface_method_impl{PDMUSBREG,pfnConstruct} */
+static DECLCALLBACK(int) usbProxyConstruct(PPDMUSBINS pUsbIns, int iInstance, PCFGMNODE pCfg, PCFGMNODE pCfgGlobal)
+{
+ PDMUSB_CHECK_VERSIONS_RETURN(pUsbIns);
+ RT_NOREF(iInstance);
+ PUSBPROXYDEV pThis = PDMINS_2_DATA(pUsbIns, PUSBPROXYDEV);
+ PCPDMUSBHLP pHlp = pUsbIns->pHlpR3;
+
+ 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 = pHlp->pfnCFGMQueryString(pCfg, "Address", szAddress, sizeof(szAddress));
+ AssertRCReturn(rc, rc);
+
+ char szBackend[64];
+ rc = pHlp->pfnCFGMQueryString(pCfg, "Backend", szBackend, sizeof(szBackend));
+ AssertRCReturn(rc, rc);
+
+ /*
+ * Select backend and open the device.
+ */
+ rc = VERR_NOT_FOUND;
+ for (unsigned i = 0; i < RT_ELEMENTS(g_aUsbProxies); i++)
+ {
+ if (!RTStrICmp(szBackend, g_aUsbProxies[i]->pszName))
+ {
+ pThis->pOps = g_aUsbProxies[i];
+ rc = VINF_SUCCESS;
+ break;
+ }
+ }
+ if (RT_FAILURE(rc))
+ return PDMUSB_SET_ERROR(pUsbIns, rc, N_("USBProxy: Failed to find backend"));
+
+ pThis->pvInstanceDataR3 = RTMemAllocZ(pThis->pOps->cbBackend);
+ if (!pThis->pvInstanceDataR3)
+ return PDMUSB_SET_ERROR(pUsbIns, VERR_NO_MEMORY, N_("USBProxy: can't allocate memory for host backend"));
+
+ rc = pThis->pOps->pfnOpen(pThis, szAddress);
+ if (RT_FAILURE(rc))
+ {
+ LogRel(("usbProxyConstruct: Failed to open '%s', rc=%Rrc\n", szAddress, 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 = pHlp->pfnCFGMGetFirstChild(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 = pHlp->pfnCFGMGetFirstChild(pCfgGlobal); pCur; pCur = pHlp->pfnCFGMGetNextChild(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, pHlp, pCur, "idVendor", "idVendorExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_PRODUCT_ID, pHlp, pCur, "idProduct", "idProcutExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_REV, pHlp, pCur, "bcdDevice", "bcdDeviceExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_CLASS, pHlp, pCur, "bDeviceClass", "bDeviceClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pHlp, pCur, "bDeviceSubClass", "bDeviceSubClassExpr"))
+ || RT_FAILURE(usbProxyQueryNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pHlp, pCur, "bDeviceProtocol", "bDeviceProtocolExpr")))
+ continue; /* skip it */
+
+ /* strings */
+ /** @todo manufacturer, product and serial strings */
+
+ /* ignore unknown config values, but not without bitching. */
+ if (!pHlp->pfnCFGMAreValuesValid(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 = pHlp->pfnCFGMGetChild(pBestMatch, "Config");
+ if (pCfgGlobalDev)
+ pCfgGlobalDev = pCfgGlobal;
+ }
+
+ /*
+ * Query the rest of the configuration using the global as fallback.
+ */
+ rc = pHlp->pfnCFGMQueryU32(pCfg, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryU32(pCfgGlobalDev, "MaskedIfs", &pThis->fMaskedIfs);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ pThis->fMaskedIfs = 0;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11Device;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11Device", &fForce11Device);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11Device = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fForce11PacketSize;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "Force11PacketSize", &fForce11PacketSize);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fForce11PacketSize = false;
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fEditAudioSyncEp;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditAudioSyncEp", &fEditAudioSyncEp);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fEditAudioSyncEp = true; /* NB: On by default! */
+ else
+ AssertRCReturn(rc, rc);
+
+ bool fEditRemoteWake;
+ rc = pHlp->pfnCFGMQueryBool(pCfg, "EditRemoteWake", &fEditRemoteWake);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ rc = pHlp->pfnCFGMQueryBool(pCfgGlobalDev, "EditRemoteWake", &fEditRemoteWake);
+ if (rc == VERR_CFGM_VALUE_NOT_FOUND)
+ fEditRemoteWake = true; /* NB: On by default! */
+ 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;
+ if (!paEps)
+ continue;
+
+ 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;
+ }
+
+
+ /*
+ * Turn asynchronous audio endpoints into synchronous ones, see @bugref{8769}
+ */
+ if (fEditAudioSyncEp)
+ {
+ 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++)
+ {
+ /* If not an audio class interface, skip. */
+ if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceClass != 1)
+ continue;
+
+ /* If not a streaming interface, skip. */
+ if (paIfs[iIf].paSettings[iAlt].Core.bInterfaceSubClass != 2)
+ continue;
+
+ PVUSBDESCENDPOINTEX paEps = (PVUSBDESCENDPOINTEX)paIfs[iIf].paSettings[iAlt].paEndpoints;
+ if (!paEps)
+ continue;
+
+ for (unsigned iEp = 0; iEp < paIfs[iIf].paSettings[iAlt].Core.bNumEndpoints; iEp++)
+ {
+ /* isoch/asynch/data*/
+ if ((paEps[iEp].Core.bmAttributes == 5) && (paEps[iEp].Core.bLength == 9))
+ {
+ uint8_t *pbExtra = (uint8_t *)paEps[iEp].pvMore; /* unconst*/
+ if (pbExtra[1] == 0)
+ continue; /* If bSynchAddress is zero, leave the descriptor alone. */
+
+ Log(("usb-proxy: pProxyDev=%s async audio with bmAttr=%02X [%02X, %02X] on EP %02X\n",
+ pUsbIns->pszName, paEps[iEp].Core.bmAttributes, pbExtra[0], pbExtra[1], paEps[iEp].Core.bEndpointAddress));
+ paEps[iEp].Core.bmAttributes = 0xD; /* isoch/synch/data*/
+ pbExtra[1] = 0; /* Clear bSynchAddress. */
+ fEdited = true;
+ LogRel(("VUSB: Modified '%s' async audio endpoint 0x%02x\n", pUsbIns->pszName, paEps[iEp].Core.bEndpointAddress));
+ }
+ }
+ }
+ }
+ }
+
+ /*
+ * Disable remote wakeup capability, see @bugref{9839}. This is done on
+ * a device/configuration level, no need to dig too deep through the descriptors.
+ * On most backends, we can't perform a real selective suspend, and more importantly
+ * can't receive a remote wake notification. If a guest suspends the device and waits
+ * for a remote wake, the device is effectively dead.
+ */
+ if (fEditRemoteWake)
+ {
+ PVUSBDESCCONFIGEX paCfgs = pThis->paCfgDescs;
+ for (unsigned iCfg = 0; iCfg < pThis->DevDesc.bNumConfigurations; iCfg++)
+ {
+ Log(("usb-proxy: pProxyDev=%s configuration %d with bmAttr=%02X\n",
+ pUsbIns->pszName, paCfgs[iCfg].Core.bmAttributes, iCfg));
+ if (paCfgs[iCfg].Core.bmAttributes & RT_BIT(5))
+ {
+ paCfgs[iCfg].Core.bmAttributes = paCfgs[iCfg].Core.bmAttributes & ~RT_BIT(5); /* Remote wakeup. */
+ fEdited = true;
+ LogRel(("VUSB: Disabled '%s' remote wakeup for configuration %d\n", pUsbIns->pszName, iCfg));
+ }
+ }
+ }
+
+ /*
+ * 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 */
+ ~0U,
+ /* 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,
+ /* pfnWakeup */
+ usbProxyDevWakeup,
+
+ /* u32TheEnd */
+ PDM_USBREG_VERSION
+};
+