/* $Id: USBProxyDevice-linux.cpp 69500 2017-10-28 15:14:05Z vboxsync $ */ /** @file * USB device proxy - the Linux backend. */ /* * Copyright (C) 2006-2017 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. */ /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ /** Define NO_PORT_RESET to skip the slow and broken linux port reset. * Resetting will break PalmOne. */ #define NO_PORT_RESET /** Define NO_LOGICAL_RECONNECT to skip the broken logical reconnect handling. */ #define NO_LOGICAL_RECONNECT /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_USBPROXY #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef VBOX_WITH_LINUX_COMPILER_H # include #endif #include /* * Backlevel 2.4 headers doesn't have these two defines. * They were added some time between 2.4.21 and 2.4.26, probably in 2.4.23. */ #ifndef USBDEVFS_DISCONNECT # define USBDEVFS_DISCONNECT _IO('U', 22) # define USBDEVFS_CONNECT _IO('U', 23) #endif #ifndef USBDEVFS_URB_SHORT_NOT_OK # define USBDEVFS_URB_SHORT_NOT_OK 0 /* rhel3 doesn't have this. darn! */ #endif /* FedoraCore 4 does not have the bit defined by default. */ #ifndef POLLWRNORM # define POLLWRNORM 0x0100 #endif #ifndef RDESKTOP # include #else # define RTCRITSECT void * static inline int rtcsNoop() { return VINF_SUCCESS; } static inline bool rtcsTrue() { return true; } # define RTCritSectInit(a) rtcsNoop() # define RTCritSectDelete(a) rtcsNoop() # define RTCritSectEnter(a) rtcsNoop() # define RTCritSectLeave(a) rtcsNoop() # define RTCritSectIsOwner(a) rtcsTrue() #endif #include #include #include #include #include #include #include #include #include #include #include #if defined(NO_PORT_RESET) && !defined(NO_LOGICAL_RECONNECT) # include #endif #include #include "../USBProxyDevice.h" /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** * Wrapper around the linux urb request structure. * This is required to track in-flight and landed URBs. */ typedef struct USBPROXYURBLNX { /** The kernel URB data. */ #if RT_GNUC_PREREQ(6, 0) /* gcc 6.2 complains about the [] member of KUrb */ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wpedantic" #endif struct usbdevfs_urb KUrb; #if RT_GNUC_PREREQ(6, 0) # pragma GCC diagnostic pop #endif /** Space filler for the isochronous packets. */ struct usbdevfs_iso_packet_desc aIsocPktsDonUseTheseUseTheOnesInKUrb[8]; /** Node to link the URB in of the existing lists. */ RTLISTNODE NodeList; /** If we've split the VUSBURB up into multiple linux URBs, this is points to the head. */ struct USBPROXYURBLNX *pSplitHead; /** The next linux URB if split up. */ struct USBPROXYURBLNX *pSplitNext; /** Don't report these back. */ bool fCanceledBySubmit; /** This split element is reaped. */ bool fSplitElementReaped; /** Size to transfer in remaining fragments of a split URB */ uint32_t cbSplitRemaining; } USBPROXYURBLNX, *PUSBPROXYURBLNX; /** * Data for the linux usb proxy backend. */ typedef struct USBPROXYDEVLNX { /** The open file. */ RTFILE hFile; /** Critical section protecting the lists. */ RTCRITSECT CritSect; /** The list of free linux URBs (USBPROXYURBLNX). */ RTLISTANCHOR ListFree; /** The list of active linux URBs. * We must maintain this so we can properly reap URBs of a detached device. * Only the split head will appear in this list. (USBPROXYURBLNX) */ RTLISTANCHOR ListInFlight; /** The list of landed linux URBs. Doubly linked. * Only the split head will appear in this list. (USBPROXYURBLNX) */ RTLISTANCHOR ListTaxing; /** Are we using sysfs to find the active configuration? */ bool fUsingSysfs; /** Pipe handle for waiking up - writing end. */ RTPIPE hPipeWakeupW; /** Pipe handle for waiking up - reading end. */ RTPIPE hPipeWakeupR; /** The device node/sysfs path of the device. * Used to figure out the configuration after a reset. */ char *pszPath; } USBPROXYDEVLNX, *PUSBPROXYDEVLNX; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ static int usbProxyLinuxDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, void *pvArg, bool fHandleNoDev, uint32_t cTries); static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev); static void usbProxyLinuxSetConnected(PUSBPROXYDEV pProyxDev, int iIf, bool fConnect, bool fQuiet); static PUSBPROXYURBLNX usbProxyLinuxUrbAlloc(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pSplitHead); static void usbProxyLinuxUrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx); static void usbProxyLinuxUrbFreeSplitList(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx); static int usbProxyLinuxFindActiveConfig(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg); /** * Wrapper for the ioctl call. * * This wrapper will repeat the call if we get an EINTR or EAGAIN. It can also * handle ENODEV (detached device) errors. * * @returns whatever ioctl returns. * @param pProxyDev The proxy device. * @param iCmd The ioctl command / function. * @param pvArg The ioctl argument / data. * @param fHandleNoDev Whether to handle ENODEV. * @param cTries The number of retries. Use UINT32_MAX for (kind of) indefinite retries. * @internal */ static int usbProxyLinuxDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, void *pvArg, bool fHandleNoDev, uint32_t cTries) { int rc; PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); do { do { rc = ioctl(RTFileToNative(pDevLnx->hFile), iCmd, pvArg); if (rc >= 0) return rc; } while (errno == EINTR); if (errno == ENODEV && fHandleNoDev) { usbProxLinuxUrbUnplugged(pProxyDev); Log(("usb-linux: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); errno = ENODEV; break; } if (errno != EAGAIN) break; } while (cTries-- > 0); return rc; } /** * The device has been unplugged. * Cancel all in-flight URBs and put them up for reaping. */ static void usbProxLinuxUrbUnplugged(PUSBPROXYDEV pProxyDev) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); /* * Shoot down all flying URBs. */ RTCritSectEnter(&pDevLnx->CritSect); pProxyDev->fDetached = true; PUSBPROXYURBLNX pUrbLnx; PUSBPROXYURBLNX pUrbLnxNext; RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) { RTListNodeRemove(&pUrbLnx->NodeList); ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_DISCARDURB, &pUrbLnx->KUrb); /* not sure if this is required.. */ if (!pUrbLnx->KUrb.status) pUrbLnx->KUrb.status = -ENODEV; /* insert into the taxing list. */ if ( !pUrbLnx->pSplitHead || pUrbLnx == pUrbLnx->pSplitHead) RTListAppend(&pDevLnx->ListTaxing, &pUrbLnx->NodeList); } RTCritSectLeave(&pDevLnx->CritSect); } /** * Set the connect state seen by kernel drivers * @internal */ static void usbProxyLinuxSetConnected(PUSBPROXYDEV pProxyDev, int iIf, bool fConnect, bool fQuiet) { if ( iIf >= 32 || !(pProxyDev->fMaskedIfs & RT_BIT(iIf))) { struct usbdevfs_ioctl IoCtl; if (!fQuiet) LogFlow(("usbProxyLinuxSetConnected: pProxyDev=%s iIf=%#x fConnect=%s\n", usbProxyGetName(pProxyDev), iIf, fConnect ? "true" : "false")); IoCtl.ifno = iIf; IoCtl.ioctl_code = fConnect ? USBDEVFS_CONNECT : USBDEVFS_DISCONNECT; IoCtl.data = NULL; if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_IOCTL, &IoCtl, true, UINT32_MAX) && !fQuiet) Log(("usbProxyLinuxSetConnected: failure, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); } } /** * Links the given URB into the in flight list. * * @returns nothing. * @param pDevLnx The proxy device instance - Linux specific data. * @param pUrbLnx The URB to link into the in flight list. */ static void usbProxyLinuxUrbLinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) { LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); Assert(RTCritSectIsOwner(&pDevLnx->CritSect)); Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); RTListAppend(&pDevLnx->ListInFlight, &pUrbLnx->NodeList); } /** * Unlinks the given URB from the in flight list. * @returns nothing. * @param pDevLnx The proxy device instance - Linux specific data. * @param pUrbLnx The URB to link into the in flight list. */ static void usbProxyLinuxUrbUnlinkInFlight(PUSBPROXYDEVLNX pDevLnx, PUSBPROXYURBLNX pUrbLnx) { LogFlowFunc(("pDevLnx=%p pUrbLnx=%p\n", pDevLnx, pUrbLnx)); RTCritSectEnter(&pDevLnx->CritSect); /* * Remove from the active list. */ Assert(!pUrbLnx->pSplitHead || pUrbLnx->pSplitHead == pUrbLnx); RTListNodeRemove(&pUrbLnx->NodeList); RTCritSectLeave(&pDevLnx->CritSect); } /** * Allocates a linux URB request structure. * @returns Pointer to an active URB request. * @returns NULL on failure. * @param pProxyDev The proxy device instance. * @param pSplitHead The split list head if allocating for a split list. */ static PUSBPROXYURBLNX usbProxyLinuxUrbAlloc(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pSplitHead) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); PUSBPROXYURBLNX pUrbLnx; LogFlowFunc(("pProxyDev=%p pSplitHead=%p\n", pProxyDev, pSplitHead)); RTCritSectEnter(&pDevLnx->CritSect); /* * Try remove a linux URB from the free list, if none there allocate a new one. */ pUrbLnx = RTListGetFirst(&pDevLnx->ListFree, USBPROXYURBLNX, NodeList); if (pUrbLnx) { RTListNodeRemove(&pUrbLnx->NodeList); RTCritSectLeave(&pDevLnx->CritSect); } else { RTCritSectLeave(&pDevLnx->CritSect); pUrbLnx = (PUSBPROXYURBLNX)RTMemAlloc(sizeof(*pUrbLnx)); if (!pUrbLnx) return NULL; } pUrbLnx->pSplitHead = pSplitHead; pUrbLnx->pSplitNext = NULL; pUrbLnx->fCanceledBySubmit = false; pUrbLnx->fSplitElementReaped = false; LogFlowFunc(("returns pUrbLnx=%p\n", pUrbLnx)); return pUrbLnx; } /** * Frees a linux URB request structure. * * @param pProxyDev The proxy device instance. * @param pUrbLnx The linux URB to free. */ static void usbProxyLinuxUrbFree(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); /* * Link it into the free list. */ RTCritSectEnter(&pDevLnx->CritSect); RTListAppend(&pDevLnx->ListFree, &pUrbLnx->NodeList); RTCritSectLeave(&pDevLnx->CritSect); } /** * Frees split list of a linux URB request structure. * * @param pProxyDev The proxy device instance. * @param pUrbLnx A linux URB to in the split list to be freed. */ static void usbProxyLinuxUrbFreeSplitList(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); LogFlowFunc(("pProxyDev=%p pUrbLnx=%p\n", pProxyDev, pUrbLnx)); RTCritSectEnter(&pDevLnx->CritSect); pUrbLnx = pUrbLnx->pSplitHead; Assert(pUrbLnx); while (pUrbLnx) { PUSBPROXYURBLNX pFree = pUrbLnx; pUrbLnx = pUrbLnx->pSplitNext; Assert(pFree->pSplitHead); pFree->pSplitHead = pFree->pSplitNext = NULL; usbProxyLinuxUrbFree(pProxyDev, pFree); } RTCritSectLeave(&pDevLnx->CritSect); } /** * This finds the device in the /proc/bus/usb/bus/addr file and finds * the config with an asterix. * * @returns The Cfg#. * @returns -1 if no active config. * @param pProxyDev The proxy device instance. * @param pszDevNode The path to the device. We infere the location of * the devices file, which bus and device number we're * looking for. * @param piFirstCfg The first configuration. (optional) * @internal */ static int usbProxyLinuxFindActiveConfigUsbfs(PUSBPROXYDEV pProxyDev, const char *pszDevNode, int *piFirstCfg) { RT_NOREF(pProxyDev); /* * Set return defaults. */ int iActiveCfg = -1; if (piFirstCfg) *piFirstCfg = 1; /* * Parse the usbfs device node path and turn it into a path to the "devices" file, * picking up the device number and bus along the way. */ size_t cchDevNode = strlen(pszDevNode); char *pszDevices = (char *)RTMemDupEx(pszDevNode, cchDevNode, sizeof("devices")); AssertReturn(pszDevices, iActiveCfg); /* the device number */ char *psz = pszDevices + cchDevNode; while (*psz != '/') psz--; Assert(pszDevices < psz); uint32_t uDev; int rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uDev); if (RT_SUCCESS(rc)) { /* the bus number */ *psz-- = '\0'; while (*psz != '/') psz--; Assert(pszDevices < psz); uint32_t uBus; rc = RTStrToUInt32Ex(psz + 1, NULL, 10, &uBus); if (RT_SUCCESS(rc)) { strcpy(psz + 1, "devices"); /* * Open and scan the devices file. * We're ASSUMING that each device starts off with a 'T:' line. */ PRTSTREAM pFile; rc = RTStrmOpen(pszDevices, "r", &pFile); if (RT_SUCCESS(rc)) { char szLine[1024]; while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) { /* we're only interested in 'T:' lines. */ psz = RTStrStripL(szLine); if (psz[0] != 'T' || psz[1] != ':') continue; /* Skip ahead to 'Bus' and compare */ psz = RTStrStripL(psz + 2); Assert(!strncmp(psz, RT_STR_TUPLE("Bus="))); psz = RTStrStripL(psz + 4); char *pszNext; uint32_t u; rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); if (RT_FAILURE(rc)) continue; if (u != uBus) continue; /* Skip ahead to 'Dev#' and compare */ psz = strstr(psz, "Dev#="); Assert(psz); if (!psz) continue; psz = RTStrStripL(psz + 5); rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); if (RT_FAILURE(rc)) continue; if (u != uDev) continue; /* * Ok, we've found the device. * Scan until we find a selected configuration, the next device, or EOF. */ while (RT_SUCCESS(RTStrmGetLine(pFile, szLine, sizeof(szLine)))) { psz = RTStrStripL(szLine); if (psz[0] == 'T') break; if (psz[0] != 'C' || psz[1] != ':') continue; const bool fActive = psz[2] == '*'; if (!fActive && !piFirstCfg) continue; /* Get the 'Cfg#' value. */ psz = strstr(psz, "Cfg#="); Assert(psz); if (psz) { psz = RTStrStripL(psz + 5); rc = RTStrToUInt32Ex(psz, &pszNext, 10, &u); AssertRC(rc); if (RT_SUCCESS(rc)) { if (piFirstCfg) { *piFirstCfg = u; piFirstCfg = NULL; } if (fActive) iActiveCfg = u; } } if (fActive) break; } break; } RTStrmClose(pFile); } } } RTMemFree(pszDevices); return iActiveCfg; } /** * This finds the active configuration from sysfs. * * @returns The Cfg#. * @returns -1 if no active config. * @param pProxyDev The proxy device instance. * @param pszPath The sysfs path for the device. * @param piFirstCfg The first configuration. (optional) * @internal */ static int usbProxyLinuxFindActiveConfigSysfs(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) { #ifdef VBOX_USB_WITH_SYSFS if (piFirstCfg != NULL) *piFirstCfg = pProxyDev->paCfgDescs != NULL ? pProxyDev->paCfgDescs[0].Core.bConfigurationValue : 1; int64_t bCfg = 0; int rc = RTLinuxSysFsReadIntFile(10, &bCfg, "%s/bConfigurationValue", pszPath); if (RT_FAILURE(rc)) bCfg = -1; return (int)bCfg; #else /* !VBOX_USB_WITH_SYSFS */ return -1; #endif /* !VBOX_USB_WITH_SYSFS */ } /** * This finds the active configuration. * * @returns The Cfg#. * @returns -1 if no active config. * @param pProxyDev The proxy device instance. * @param pszPath The sysfs path for the device, or the usbfs device * node path. * @param piFirstCfg The first configuration. (optional) * @internal */ static int usbProxyLinuxFindActiveConfig(PUSBPROXYDEV pProxyDev, const char *pszPath, int *piFirstCfg) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); if (pDevLnx->fUsingSysfs) return usbProxyLinuxFindActiveConfigSysfs(pProxyDev, pszPath, piFirstCfg); return usbProxyLinuxFindActiveConfigUsbfs(pProxyDev, pszPath, piFirstCfg); } /** * Extracts the Linux file descriptor associated with the kernel USB device. * This is used by rdesktop-vrdp for polling for events. * @returns the FD, or asserts and returns -1 on error * @param pProxyDev The device instance */ RTDECL(int) USBProxyDeviceLinuxGetFD(PUSBPROXYDEV pProxyDev) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); AssertReturn(pDevLnx->hFile != NIL_RTFILE, -1); return RTFileToNative(pDevLnx->hFile); } /** * Opens the device file. * * @returns VBox status code. * @param pProxyDev The device instance. * @param pszAddress If we are using usbfs, this is the path to the * device. If we are using sysfs, this is a string of * the form "sysfs://device:". * In the second case, the two paths are guaranteed * not to contain the substring "//". * @param pvBackend Backend specific pointer, unused for the linux backend. */ static DECLCALLBACK(int) usbProxyLinuxOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress, void *pvBackend) { LogFlow(("usbProxyLinuxOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); const char *pszDevNode; const char *pszPath; size_t cchPath; bool fUsingSysfs; /* * Are we using sysfs or usbfs? */ #ifdef VBOX_USB_WITH_SYSFS fUsingSysfs = strncmp(pszAddress, RT_STR_TUPLE("sysfs:")) == 0; if (fUsingSysfs) { pszDevNode = strstr(pszAddress, "//device:"); if (!pszDevNode) { LogRel(("usbProxyLinuxOpen: Invalid device address: '%s'\n", pszAddress)); return VERR_INVALID_PARAMETER; } pszPath = pszAddress + sizeof("sysfs:") - 1; cchPath = pszDevNode - pszPath; pszDevNode += sizeof("//device:") - 1; } else #endif /* VBOX_USB_WITH_SYSFS */ { #ifndef VBOX_USB_WITH_SYSFS fUsingSysfs = false; #endif pszPath = pszDevNode = pszAddress; cchPath = strlen(pszPath); } /* * Try open the device node. */ RTFILE hFile; int rc = RTFileOpen(&hFile, pszDevNode, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); if (RT_SUCCESS(rc)) { /* * Initialize the linux backend data. */ PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); RTListInit(&pDevLnx->ListFree); RTListInit(&pDevLnx->ListInFlight); RTListInit(&pDevLnx->ListTaxing); pDevLnx->pszPath = RTStrDupN(pszPath, cchPath); if (pDevLnx->pszPath) { rc = RTPipeCreate(&pDevLnx->hPipeWakeupR, &pDevLnx->hPipeWakeupW, 0); if (RT_SUCCESS(rc)) { pDevLnx->fUsingSysfs = fUsingSysfs; pDevLnx->hFile = hFile; rc = RTCritSectInit(&pDevLnx->CritSect); if (RT_SUCCESS(rc)) { LogFlow(("usbProxyLinuxOpen(%p, %s): returns successfully File=%RTfile iActiveCfg=%d\n", pProxyDev, pszAddress, pDevLnx->hFile, pProxyDev->iActiveCfg)); return VINF_SUCCESS; } RTPipeClose(pDevLnx->hPipeWakeupR); RTPipeClose(pDevLnx->hPipeWakeupW); } } else rc = VERR_NO_MEMORY; RTFileClose(hFile); } else if (rc == VERR_ACCESS_DENIED) rc = VERR_VUSB_USBFS_PERMISSION; Log(("usbProxyLinuxOpen(%p, %s) failed, rc=%s!\n", pProxyDev, pszAddress, RTErrGetShort(rc))); NOREF(pvBackend); return rc; } /** * Claims all the interfaces and figures out the * current configuration. * * @returns VINF_SUCCESS. * @param pProxyDev The proxy device. */ static DECLCALLBACK(int) usbProxyLinuxInit(PUSBPROXYDEV pProxyDev) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); /* * Brute force rulez. * usbProxyLinuxSetConnected check for masked interfaces. */ unsigned iIf; for (iIf = 0; iIf < 256; iIf++) usbProxyLinuxSetConnected(pProxyDev, iIf, false, true); /* * Determine the active configuration. * * If there isn't any active configuration, we will get EHOSTUNREACH (113) errors * when trying to read the device descriptors in usbProxyDevCreate. So, we'll make * the first one active (usually 1) then. */ pProxyDev->cIgnoreSetConfigs = 1; int iFirstCfg; pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, &iFirstCfg); if (pProxyDev->iActiveCfg == -1) { usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iFirstCfg, false, UINT32_MAX); pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); Log(("usbProxyLinuxInit: No active config! Tried to set %d: iActiveCfg=%d\n", iFirstCfg, pProxyDev->iActiveCfg)); } else Log(("usbProxyLinuxInit(%p): iActiveCfg=%d\n", pProxyDev, pProxyDev->iActiveCfg)); return VINF_SUCCESS; } /** * Closes the proxy device. */ static DECLCALLBACK(void) usbProxyLinuxClose(PUSBPROXYDEV pProxyDev) { LogFlow(("usbProxyLinuxClose: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); AssertPtrReturnVoid(pDevLnx); /* * Try put the device in a state which linux can cope with before we release it. * Resetting it would be a nice start, although we must remember * that it might have been disconnected... * * Don't reset if we're masking interfaces or if construction failed. */ if (pProxyDev->fInited) { /* ASSUMES: thread == EMT */ if ( pProxyDev->fMaskedIfs || !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) { /* Connect drivers. */ unsigned iIf; for (iIf = 0; iIf < 256; iIf++) usbProxyLinuxSetConnected(pProxyDev, iIf, true, true); LogRel(("USB: Successfully reset device pProxyDev=%s\n", usbProxyGetName(pProxyDev))); } else if (errno != ENODEV) LogRel(("USB: Reset failed, errno=%d, pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); else Log(("USB: Reset failed, errno=%d (ENODEV), pProxyDev=%s.\n", errno, usbProxyGetName(pProxyDev))); } /* * Now we can free all the resources and close the device. */ RTCritSectDelete(&pDevLnx->CritSect); PUSBPROXYURBLNX pUrbLnx; PUSBPROXYURBLNX pUrbLnxNext; RTListForEachSafe(&pDevLnx->ListInFlight, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) { RTListNodeRemove(&pUrbLnx->NodeList); if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX) && errno != ENODEV && errno != ENOENT) AssertMsgFailed(("errno=%d\n", errno)); if (pUrbLnx->pSplitHead) { PUSBPROXYURBLNX pCur = pUrbLnx->pSplitNext; while (pCur) { PUSBPROXYURBLNX pFree = pCur; pCur = pFree->pSplitNext; if ( !pFree->fSplitElementReaped && usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pFree->KUrb, false, UINT32_MAX) && errno != ENODEV && errno != ENOENT) AssertMsgFailed(("errno=%d\n", errno)); RTMemFree(pFree); } } else Assert(!pUrbLnx->pSplitNext); RTMemFree(pUrbLnx); } RTListForEachSafe(&pDevLnx->ListFree, pUrbLnx, pUrbLnxNext, USBPROXYURBLNX, NodeList) { RTListNodeRemove(&pUrbLnx->NodeList); RTMemFree(pUrbLnx); } RTFileClose(pDevLnx->hFile); pDevLnx->hFile = NIL_RTFILE; RTPipeClose(pDevLnx->hPipeWakeupR); RTPipeClose(pDevLnx->hPipeWakeupW); RTStrFree(pDevLnx->pszPath); LogFlow(("usbProxyLinuxClose: returns\n")); } #if defined(NO_PORT_RESET) && !defined(NO_LOGICAL_RECONNECT) /** * Look for the logically reconnected device. * After 5 seconds we'll give up. * * @returns VBox status code. * @thread Reset thread or EMT. */ static int usb_reset_logical_reconnect(PUSBPROXYDEV pDev) { FILE * pFile; uint64_t u64StartTS = RTTimeMilliTS(); Log2(("usb_reset_logical_reconnect: pDev=%p:{.bBus=%#x, .bDevNum=%#x, .idVendor=%#x, .idProduct=%#x, .bcdDevice=%#x, .u64SerialHash=%#llx .bDevNumParent=%#x .bPort=%#x .bLevel=%#x}\n", pDev, pDev->Info.bBus, pDev->Info.bDevNum, pDev->Info.idVendor, pDev->Info.idProduct, pDev->Info.bcdDevice, pDev->Info.u64SerialHash, pDev->Info.bDevNumParent, pDev->Info.bPort, pDev->Info.bLevel)); /* First, let hubd get a chance to logically reconnect the device. */ if (!RTThreadYield()) RTThreadSleep(1); /* * Search for the new device address. */ pFile = get_devices_file(); if (!pFile) return VERR_FILE_NOT_FOUND; /* * Loop until found or 5seconds have elapsed. */ for (;;) { struct pollfd pfd; uint8_t tmp; int rc; char buf[512]; uint64_t u64Elapsed; int got = 0; struct usb_dev_entry id = {0}; /* * Since this is kernel ABI we don't need to be too fussy about * the parsing. */ while (fgets(buf, sizeof(buf), pFile)) { char *psz = strchr(buf, '\n'); if ( psz == NULL ) { AssertMsgFailed(("usb_reset_logical_reconnect: Line to long!!\n")); break; } *psz = '\0'; switch ( buf[0] ) { case 'T': /* topology */ /* Check if we've got enough for a device. */ if (got >= 2) { Log2(("usb_reset_logical_reconnect: {.bBus=%#x, .bDevNum=%#x, .idVendor=%#x, .idProduct=%#x, .bcdDevice=%#x, .u64SerialHash=%#llx, .bDevNumParent=%#x, .bPort=%#x, .bLevel=%#x}\n", id.bBus, id.bDevNum, id.idVendor, id.idProduct, id.bcdDevice, id.u64SerialHash, id.bDevNumParent, id.bPort, id.bLevel)); if ( id.bDevNumParent == pDev->Info.bDevNumParent && id.idVendor == pDev->Info.idVendor && id.idProduct == pDev->Info.idProduct && id.bcdDevice == pDev->Info.bcdDevice && id.u64SerialHash == pDev->Info.u64SerialHash && id.bBus == pDev->Info.bBus && id.bPort == pDev->Info.bPort && id.bLevel == pDev->Info.bLevel) { goto l_found; } } /* restart */ got = 0; memset(&id, 0, sizeof(id)); /*T: Bus=04 Lev=02 Prnt=02 Port=00 Cnt=01 Dev#= 3 Spd=1.5 MxCh= 0*/ Log2(("usb_reset_logical_reconnect: %s\n", buf)); buf[10] = '\0'; if ( !get_u8(buf + 8, &id.bBus) ) break; buf[49] = '\0'; psz = buf + 46; while ( *psz == ' ' ) psz++; if ( !get_u8(psz, &id.bDevNum) ) break; buf[17] = '\0'; if ( !get_u8(buf + 15, &id.bLevel) ) break; buf[25] = '\0'; if ( !get_u8(buf + 23, &id.bDevNumParent) ) break; buf[33] = '\0'; if ( !get_u8(buf + 31, &id.bPort) ) break; got++; break; case 'P': /* product */ Log2(("usb_reset_logical_reconnect: %s\n", buf)); buf[15] = '\0'; if ( !get_x16(buf + 11, &id.idVendor) ) break; buf[27] = '\0'; if ( !get_x16(buf + 23, &id.idProduct) ) break; buf[34] = '\0'; if ( buf[32] == ' ' ) buf[32] = '0'; id.bcdDevice = 0; if ( !get_x8(buf + 32, &tmp) ) break; id.bcdDevice = tmp << 8; if ( !get_x8(buf + 35, &tmp) ) break; id.bcdDevice |= tmp; got++; break; case 'S': /* String descriptor */ /* Skip past "S:" and then the whitespace */ for(psz = buf + 2; *psz != '\0'; psz++) if ( !RT_C_IS_SPACE(*psz) ) break; /* If it is a serial number string, skip past * "SerialNumber=" */ if (strncmp(psz, RT_STR_TUPLE("SerialNumber="))) break; Log2(("usb_reset_logical_reconnect: %s\n", buf)); psz += sizeof("SerialNumber=") - 1; usb_serial_hash(psz, &id.u64SerialHash); break; } } /* * Check last. */ if ( got >= 2 && id.bDevNumParent == pDev->Info.bDevNumParent && id.idVendor == pDev->Info.idVendor && id.idProduct == pDev->Info.idProduct && id.bcdDevice == pDev->Info.bcdDevice && id.u64SerialHash == pDev->Info.u64SerialHash && id.bBus == pDev->Info.bBus && id.bPort == pDev->Info.bPort && id.bLevel == pDev->Info.bLevel) { l_found: /* close the existing file descriptor. */ RTFileClose(pDevLnx->File); pDevLnx->File = NIL_RTFILE; /* open stuff at the new address. */ pDev->Info = id; if (usbProxyLinuxOpen(pDev, &id)) return VINF_SUCCESS; break; } /* * Wait for a while and then check the file again. */ u64Elapsed = RTTimeMilliTS() - u64StartTS; if (u64Elapsed >= 5000/*ms*/) break; /* done */ pfd.fd = fileno(pFile); pfd.events = POLLIN; rc = poll(&pfd, 1, 5000 - u64Elapsed); if (rc < 0) { AssertMsg(errno == EINTR, ("errno=%d\n", errno)); RTThreadSleep(32); /* paranoia: don't eat cpu on failure */ } rewind(pFile); } /* for loop */ return VERR_GENERAL_FAILURE; } #endif /* !NO_PORT_RESET && !NO_LOGICAL_RECONNECT */ /** @interface_method_impl{USBPROXYBACK,pfnReset} */ static DECLCALLBACK(int) usbProxyLinuxReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) { #ifdef NO_PORT_RESET PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); /* * Specific device resets are NOPs. * Root hub resets that affects all devices are executed. * * The reasoning is that when a root hub reset is done, the guest shouldn't * will have to re enumerate the devices after doing this kind of reset. * So, it doesn't really matter if a device is 'logically disconnected'. */ if ( !fResetOnLinux || pProxyDev->fMaskedIfs) LogFlow(("usbProxyLinuxReset: pProxyDev=%s - NO_PORT_RESET\n", usbProxyGetName(pProxyDev))); else { LogFlow(("usbProxyLinuxReset: pProxyDev=%s - Real Reset!\n", usbProxyGetName(pProxyDev))); if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) { int rc = errno; Log(("usb-linux: Reset failed, rc=%s errno=%d.\n", RTErrGetShort(RTErrConvertFromErrno(rc)), rc)); pProxyDev->iActiveCfg = -1; return RTErrConvertFromErrno(rc); } /* find the active config - damn annoying. */ pProxyDev->iActiveCfg = usbProxyLinuxFindActiveConfig(pProxyDev, pDevLnx->pszPath, NULL); LogFlow(("usbProxyLinuxReset: returns successfully iActiveCfg=%d\n", pProxyDev->iActiveCfg)); } pProxyDev->cIgnoreSetConfigs = 2; #else /* !NO_PORT_RESET */ /* * This is the alternative, we will always reset when asked to do so. * * The problem we're facing here is that on reset failure linux will do * a 'logical reconnect' on the device. This will invalidate the current * handle and we'll have to reopen the device. This is problematic to say * the least, especially since it happens pretty often. */ LogFlow(("usbProxyLinuxReset: pProxyDev=%s\n", usbProxyGetName(pProxyDev))); # ifndef NO_LOGICAL_RECONNECT ASMAtomicIncU32(&g_cResetActive); # endif if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RESET, NULL, false, 10)) { int rc = errno; # ifndef NO_LOGICAL_RECONNECT if (rc == ENODEV) { /* * This usually happens because of a 'logical disconnection'. * So, we're in for a real treat from our excellent OS now... */ rc2 = usb_reset_logical_reconnect(pProxyDev); if (RT_FAILURE(rc2)) usbProxLinuxUrbUnplugged(pProxyDev); if (RT_SUCCESS(rc2)) { ASMAtomicDecU32(&g_cResetActive); LogFlow(("usbProxyLinuxReset: returns success (after recovering disconnected device!)\n")); return VINF_SUCCESS; } } ASMAtomicDecU32(&g_cResetActive); # endif /* NO_LOGICAL_RECONNECT */ Log(("usb-linux: Reset failed, rc=%s errno=%d.\n", RTErrGetShort(RTErrConvertFromErrno(rc)), rc)); pProxyDev->iActiveCfg = -1; return RTErrConvertFromErrno(rc); } # ifndef NO_LOGICAL_RECONNECT ASMAtomicDecU32(&g_cResetActive); # endif pProxyDev->cIgnoreSetConfigs = 2; LogFlow(("usbProxyLinuxReset: returns success\n")); #endif /* !NO_PORT_RESET */ return VINF_SUCCESS; } /** * SET_CONFIGURATION. * * The caller makes sure that it's not called first time after open or reset * with the active interface. * * @returns success indicator. * @param pProxyDev The device instance data. * @param iCfg The configuration to set. */ static DECLCALLBACK(int) usbProxyLinuxSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) { LogFlow(("usbProxyLinuxSetConfig: pProxyDev=%s cfg=%#x\n", usbProxyGetName(pProxyDev), iCfg)); if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETCONFIGURATION, &iCfg, true, UINT32_MAX)) { Log(("usb-linux: Set configuration. errno=%d\n", errno)); return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** * Claims an interface. * @returns success indicator. */ static DECLCALLBACK(int) usbProxyLinuxClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) { LogFlow(("usbProxyLinuxClaimInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); usbProxyLinuxSetConnected(pProxyDev, iIf, false, false); if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLAIMINTERFACE, &iIf, true, UINT32_MAX)) { Log(("usb-linux: Claim interface. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** * Releases an interface. * @returns success indicator. */ static DECLCALLBACK(int) usbProxyLinuxReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) { LogFlow(("usbProxyLinuxReleaseInterface: pProxyDev=%s ifnum=%#x\n", usbProxyGetName(pProxyDev), iIf)); if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_RELEASEINTERFACE, &iIf, true, UINT32_MAX)) { Log(("usb-linux: Release interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** * SET_INTERFACE. * * @returns success indicator. */ static DECLCALLBACK(int) usbProxyLinuxSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) { struct usbdevfs_setinterface SetIf; LogFlow(("usbProxyLinuxSetInterface: pProxyDev=%p iIf=%#x iAlt=%#x\n", pProxyDev, iIf, iAlt)); SetIf.interface = iIf; SetIf.altsetting = iAlt; if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_SETINTERFACE, &SetIf, true, UINT32_MAX)) { Log(("usb-linux: Set interface, errno=%d. pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** * Clears the halted endpoint 'EndPt'. */ static DECLCALLBACK(int) usbProxyLinuxClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int EndPt) { LogFlow(("usbProxyLinuxClearHaltedEp: pProxyDev=%s EndPt=%u\n", usbProxyGetName(pProxyDev), EndPt)); if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_CLEAR_HALT, &EndPt, true, UINT32_MAX)) { /* * Unfortunately this doesn't work on control pipes. * Windows doing this on the default endpoint and possibly other pipes too, * so we'll feign success for ENOENT errors. */ if (errno == ENOENT) { Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d - IGNORED\n", errno, usbProxyGetName(pProxyDev), EndPt)); return VINF_SUCCESS; } Log(("usb-linux: clear_halted_ep failed errno=%d. pProxyDev=%s ep=%d\n", errno, usbProxyGetName(pProxyDev), EndPt)); return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** * Setup packet byte-swapping routines. */ static void usbProxyLinuxUrbSwapSetup(PVUSBSETUP pSetup) { pSetup->wValue = RT_H2LE_U16(pSetup->wValue); pSetup->wIndex = RT_H2LE_U16(pSetup->wIndex); pSetup->wLength = RT_H2LE_U16(pSetup->wLength); } /** * Clean up after a failed URB submit. */ static void usbProxyLinuxCleanupFailedSubmit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged) { if (pUrb->enmType == VUSBXFERTYPE_MSG) usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); /* discard and reap later (walking with pUrbLnx). */ if (pUrbLnx != pCur) { for (;;) { pUrbLnx->fCanceledBySubmit = true; pUrbLnx->KUrb.usercontext = NULL; if (usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, false, UINT32_MAX)) { if (errno == ENODEV) *pfUnplugged = true; else if (errno == ENOENT) pUrbLnx->fSplitElementReaped = true; else LogRel(("USB: Failed to discard %p! errno=%d (pUrb=%p)\n", pUrbLnx->KUrb.usercontext, errno, pUrb)); /* serious! */ } if (pUrbLnx->pSplitNext == pCur) { pUrbLnx->pSplitNext = NULL; break; } pUrbLnx = pUrbLnx->pSplitNext; Assert(pUrbLnx); } } /* free the unsubmitted ones. */ while (pCur) { PUSBPROXYURBLNX pFree = pCur; pCur = pCur->pSplitNext; usbProxyLinuxUrbFree(pProxyDev, pFree); } /* send unplug event if we failed with ENODEV originally. */ if (*pfUnplugged) usbProxLinuxUrbUnplugged(pProxyDev); } /** * Submit one URB through the usbfs IOCTL interface, with * retries * * @returns VBox status code. */ static int usbProxyLinuxSubmitURB(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pCur, PVUSBURB pUrb, bool *pfUnplugged) { RT_NOREF(pUrb); PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); unsigned cTries = 0; while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pCur->KUrb)) { if (errno == EINTR) continue; if (errno == ENODEV) { Log(("usbProxyLinuxSubmitURB: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); *pfUnplugged = true; return RTErrConvertFromErrno(errno); } Log(("usb-linux: Submit URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", pUrb, errno, pCur->KUrb.type, pCur->KUrb.endpoint, pCur->KUrb.buffer_length, cTries)); if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ continue; return RTErrConvertFromErrno(errno); } return VINF_SUCCESS; } /** The split size. 16K in known Linux kernel versions. */ #define SPLIT_SIZE 0x4000 /** * Create a URB fragment of up to SPLIT_SIZE size and hook it * into the list of fragments. * * @returns pointer to newly allocated URB fragment or NULL. */ static PUSBPROXYURBLNX usbProxyLinuxSplitURBFragment(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pHead, PUSBPROXYURBLNX pCur) { PUSBPROXYURBLNX pNew; uint32_t cbLeft = pCur->cbSplitRemaining; uint8_t *pb = (uint8_t *)pCur->KUrb.buffer; LogFlowFunc(("pProxyDev=%p pHead=%p pCur=%p\n", pProxyDev, pHead, pCur)); Assert(cbLeft != 0); pNew = pCur->pSplitNext = usbProxyLinuxUrbAlloc(pProxyDev, pHead); if (!pNew) { usbProxyLinuxUrbFreeSplitList(pProxyDev, pHead); return NULL; } Assert(pNew->pSplitHead == pHead); Assert(pNew->pSplitNext == NULL); pNew->KUrb = pHead->KUrb; pNew->KUrb.buffer = pb + pCur->KUrb.buffer_length; pNew->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); pNew->KUrb.actual_length = 0; cbLeft -= pNew->KUrb.buffer_length; Assert(cbLeft < INT32_MAX); pNew->cbSplitRemaining = cbLeft; LogFlowFunc(("returns pNew=%p\n", pNew)); return pNew; } /** * Try splitting up a VUSB URB into smaller URBs which the * linux kernel (usbfs) can deal with. * * NB: For ShortOK reads things get a little tricky - we don't * know how much data is going to arrive and not all the * fragment URBs might be filled. We can only safely set up one * URB at a time -> worse performance but correct behaviour. * * @returns VBox status code. * @param pProxyDev The proxy device. * @param pUrbLnx The linux URB which was rejected because of being too big. * @param pUrb The VUSB URB. */ static int usbProxyLinuxUrbQueueSplit(PUSBPROXYDEV pProxyDev, PUSBPROXYURBLNX pUrbLnx, PVUSBURB pUrb) { /* * Split it up into SPLIT_SIZE sized blocks. */ const unsigned cKUrbs = (pUrb->cbData + SPLIT_SIZE - 1) / SPLIT_SIZE; LogFlow(("usbProxyLinuxUrbQueueSplit: pUrb=%p cKUrbs=%d cbData=%d\n", pUrb, cKUrbs, pUrb->cbData)); uint32_t cbLeft = pUrb->cbData; uint8_t *pb = &pUrb->abData[0]; /* the first one (already allocated) */ switch (pUrb->enmType) { default: /* shut up gcc */ case VUSBXFERTYPE_BULK: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; break; case VUSBXFERTYPE_INTR: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; break; case VUSBXFERTYPE_MSG: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; break; case VUSBXFERTYPE_ISOC: AssertMsgFailed(("We can't split isochronous URBs!\n")); usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); return VERR_INVALID_PARAMETER; /** @todo Better status code. */ } pUrbLnx->KUrb.endpoint = pUrb->EndPt; if (pUrb->enmDir == VUSBDIRECTION_IN) pUrbLnx->KUrb.endpoint |= 0x80; pUrbLnx->KUrb.flags = 0; if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; pUrbLnx->KUrb.status = 0; pUrbLnx->KUrb.buffer = pb; pUrbLnx->KUrb.buffer_length = RT_MIN(cbLeft, SPLIT_SIZE); pUrbLnx->KUrb.actual_length = 0; pUrbLnx->KUrb.start_frame = 0; pUrbLnx->KUrb.number_of_packets = 0; pUrbLnx->KUrb.error_count = 0; pUrbLnx->KUrb.signr = 0; pUrbLnx->KUrb.usercontext = pUrb; pUrbLnx->pSplitHead = pUrbLnx; pUrbLnx->pSplitNext = NULL; PUSBPROXYURBLNX pCur = pUrbLnx; cbLeft -= pUrbLnx->KUrb.buffer_length; pUrbLnx->cbSplitRemaining = cbLeft; int rc = VINF_SUCCESS; bool fUnplugged = false; if (pUrb->enmDir == VUSBDIRECTION_IN && !pUrb->fShortNotOk) { /* Subsequent fragments will be queued only after the previous fragment is reaped * and only if necessary. */ Log(("usb-linux: Large ShortOK read, only queuing first fragment.\n")); Assert(pUrbLnx->cbSplitRemaining > 0 && pUrbLnx->cbSplitRemaining < 256 * _1K); rc = usbProxyLinuxSubmitURB(pProxyDev, pUrbLnx, pUrb, &fUnplugged); } else { /* the rest. */ unsigned i; for (i = 1; i < cKUrbs; i++) { pCur = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx, pCur); if (!pCur) return VERR_NO_MEMORY; } Assert(pCur->cbSplitRemaining == 0); /* Submit the blocks. */ pCur = pUrbLnx; for (i = 0; i < cKUrbs; i++, pCur = pCur->pSplitNext) { rc = usbProxyLinuxSubmitURB(pProxyDev, pCur, pUrb, &fUnplugged); if (RT_FAILURE(rc)) break; } } if (RT_SUCCESS(rc)) { pUrb->Dev.pvPrivate = pUrbLnx; usbProxyLinuxUrbLinkInFlight(USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX), pUrbLnx); LogFlow(("usbProxyLinuxUrbQueueSplit: ok\n")); return VINF_SUCCESS; } usbProxyLinuxCleanupFailedSubmit(pProxyDev, pUrbLnx, pCur, pUrb, &fUnplugged); return rc; } /** * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} */ static DECLCALLBACK(int) usbProxyLinuxUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) { int rc = VINF_SUCCESS; unsigned cTries; PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); LogFlow(("usbProxyLinuxUrbQueue: pProxyDev=%s pUrb=%p EndPt=%d cbData=%d\n", usbProxyGetName(pProxyDev), pUrb, pUrb->EndPt, pUrb->cbData)); /* * Allocate a linux urb. */ PUSBPROXYURBLNX pUrbLnx = usbProxyLinuxUrbAlloc(pProxyDev, NULL); if (!pUrbLnx) return VERR_NO_MEMORY; pUrbLnx->KUrb.endpoint = pUrb->EndPt | (pUrb->enmDir == VUSBDIRECTION_IN ? 0x80 : 0); pUrbLnx->KUrb.status = 0; pUrbLnx->KUrb.flags = 0; if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) pUrbLnx->KUrb.flags |= USBDEVFS_URB_SHORT_NOT_OK; pUrbLnx->KUrb.buffer = pUrb->abData; pUrbLnx->KUrb.buffer_length = pUrb->cbData; pUrbLnx->KUrb.actual_length = 0; pUrbLnx->KUrb.start_frame = 0; pUrbLnx->KUrb.number_of_packets = 0; pUrbLnx->KUrb.error_count = 0; pUrbLnx->KUrb.signr = 0; pUrbLnx->KUrb.usercontext = pUrb; switch (pUrb->enmType) { case VUSBXFERTYPE_MSG: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_CONTROL; if (pUrb->cbData < sizeof(VUSBSETUP)) { usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); return VERR_BUFFER_UNDERFLOW; } usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); LogFlow(("usbProxyLinuxUrbQueue: message\n")); break; case VUSBXFERTYPE_BULK: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_BULK; break; case VUSBXFERTYPE_ISOC: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_ISO; pUrbLnx->KUrb.flags |= USBDEVFS_URB_ISO_ASAP; pUrbLnx->KUrb.number_of_packets = pUrb->cIsocPkts; unsigned i; for (i = 0; i < pUrb->cIsocPkts; i++) { #if RT_GNUC_PREREQ(4, 6) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #endif pUrbLnx->KUrb.iso_frame_desc[i].length = pUrb->aIsocPkts[i].cb; pUrbLnx->KUrb.iso_frame_desc[i].actual_length = 0; pUrbLnx->KUrb.iso_frame_desc[i].status = 0x7fff; #if RT_GNUC_PREREQ(4, 6) # pragma GCC diagnostic pop #endif } break; case VUSBXFERTYPE_INTR: pUrbLnx->KUrb.type = USBDEVFS_URB_TYPE_INTERRUPT; break; default: rc = VERR_INVALID_PARAMETER; /** @todo better status code. */ } /* * We have to serialize access by using the critial section here because this * thread might be suspended after submitting the URB but before linking it into * the in flight list. This would get us in trouble when reaping the URB on another * thread while it isn't in the in flight list. * * Linking the URB into the list before submitting it like it was done in the past is not * possible either because submitting the URB might fail here because the device gets * detached. The reaper thread gets this event too and might race this thread before we * can unlink the URB from the active list and the common code might end up freeing * the common URB structure twice. */ RTCritSectEnter(&pDevLnx->CritSect); /* * Submit it. */ cTries = 0; while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_SUBMITURB, &pUrbLnx->KUrb)) { if (errno == EINTR) continue; if (errno == ENODEV) { rc = RTErrConvertFromErrno(errno); Log(("usbProxyLinuxUrbQueue: ENODEV -> unplugged. pProxyDev=%s\n", usbProxyGetName(pProxyDev))); if (pUrb->enmType == VUSBXFERTYPE_MSG) usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); RTCritSectLeave(&pDevLnx->CritSect); usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); usbProxLinuxUrbUnplugged(pProxyDev); return rc; } /* * usbfs has or used to have a low buffer limit (16KB) in order to prevent * processes wasting kmalloc'ed memory. It will return EINVAL if break that * limit, and we'll have to split the VUSB URB up into multiple linux URBs. * * Since this is a limit which is subject to change, we cannot check for it * before submitting the URB. We just have to try and fail. */ if ( errno == EINVAL && pUrb->cbData >= 8*_1K) { rc = usbProxyLinuxUrbQueueSplit(pProxyDev, pUrbLnx, pUrb); RTCritSectLeave(&pDevLnx->CritSect); return rc; } Log(("usb-linux: Queue URB %p -> %d!!! type=%d ep=%#x buffer_length=%#x cTries=%d\n", pUrb, errno, pUrbLnx->KUrb.type, pUrbLnx->KUrb.endpoint, pUrbLnx->KUrb.buffer_length, cTries)); if (errno != EBUSY && ++cTries < 3) /* this doesn't work for the floppy :/ */ continue; RTCritSectLeave(&pDevLnx->CritSect); rc = RTErrConvertFromErrno(errno); if (pUrb->enmType == VUSBXFERTYPE_MSG) usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); return rc; } usbProxyLinuxUrbLinkInFlight(pDevLnx, pUrbLnx); RTCritSectLeave(&pDevLnx->CritSect); LogFlow(("usbProxyLinuxUrbQueue: ok\n")); pUrb->Dev.pvPrivate = pUrbLnx; return rc; } /** * Translate the linux status to a VUSB status. * * @remarks see cc_to_error in ohci.h, uhci_map_status in uhci-q.c, * sitd_complete+itd_complete in ehci-sched.c, and qtd_copy_status in * ehci-q.c. */ static VUSBSTATUS vusbProxyLinuxStatusToVUsbStatus(int iStatus) { switch (iStatus) { /** @todo VUSBSTATUS_NOT_ACCESSED */ case -EXDEV: /* iso transfer, partial result. */ case 0: return VUSBSTATUS_OK; case -EILSEQ: return VUSBSTATUS_CRC; case -EREMOTEIO: /* ehci and ohci uses this for underflow error. */ return VUSBSTATUS_DATA_UNDERRUN; case -EOVERFLOW: return VUSBSTATUS_DATA_OVERRUN; case -ETIME: case -ENODEV: return VUSBSTATUS_DNR; //case -ECOMM: // return VUSBSTATUS_BUFFER_OVERRUN; //case -ENOSR: // return VUSBSTATUS_BUFFER_UNDERRUN; case -EPROTO: Log(("vusbProxyLinuxStatusToVUsbStatus: DNR/EPPROTO!!\n")); return VUSBSTATUS_DNR; case -EPIPE: Log(("vusbProxyLinuxStatusToVUsbStatus: STALL/EPIPE!!\n")); return VUSBSTATUS_STALL; case -ESHUTDOWN: Log(("vusbProxyLinuxStatusToVUsbStatus: SHUTDOWN!!\n")); return VUSBSTATUS_STALL; default: Log(("vusbProxyLinuxStatusToVUsbStatus: status %d!!\n", iStatus)); return VUSBSTATUS_STALL; } } /** * Get and translates the linux status to a VUSB status. */ static VUSBSTATUS vusbProxyLinuxUrbGetStatus(PUSBPROXYURBLNX pUrbLnx) { return vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.status); } /** * Reap URBs in-flight on a device. * * @returns Pointer to a completed URB. * @returns NULL if no URB was completed. * @param pProxyDev The device. * @param cMillies Number of milliseconds to wait. Use 0 to not wait at all. */ static DECLCALLBACK(PVUSBURB) usbProxyLinuxUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) { PUSBPROXYURBLNX pUrbLnx = NULL; PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); /* * Any URBs pending delivery? */ if (!RTListIsEmpty(&pDevLnx->ListTaxing)) { RTCritSectEnter(&pDevLnx->CritSect); pUrbLnx = RTListGetFirst(&pDevLnx->ListTaxing, USBPROXYURBLNX, NodeList); if (pUrbLnx) { /* unlink from the pending delivery list */ RTListNodeRemove(&pUrbLnx->NodeList); /* temporarily into the active list, so free works right. */ RTListAppend(&pDevLnx->ListInFlight, &pUrbLnx->NodeList); } RTCritSectLeave(&pDevLnx->CritSect); } if (!pUrbLnx) { /* * Block for requested period. * * It seems to me that the path of poll() is shorter and * involves less semaphores than ioctl() on usbfs. So, we'll * do a poll regardless of whether cMillies == 0 or not. */ if (cMillies) { int cMilliesWait = cMillies == RT_INDEFINITE_WAIT ? -1 : cMillies; for (;;) { struct pollfd pfd[2]; pfd[0].fd = RTFileToNative(pDevLnx->hFile); pfd[0].events = POLLOUT | POLLWRNORM /* completed async */ | POLLERR | POLLHUP /* disconnected */; pfd[0].revents = 0; pfd[1].fd = RTPipeToNative(pDevLnx->hPipeWakeupR); pfd[1].events = POLLIN | POLLHUP; pfd[1].revents = 0; int rc = poll(&pfd[0], 2, cMilliesWait); Log(("usbProxyLinuxUrbReap: poll rc = %d\n", rc)); if (rc >= 1) { /* If the pipe caused the return drain it. */ if (pfd[1].revents & POLLIN) { uint8_t bRead; size_t cbIgnored = 0; RTPipeRead(pDevLnx->hPipeWakeupR, &bRead, 1, &cbIgnored); } break; } if (rc >= 0) return NULL; if (errno != EAGAIN) { Log(("usb-linux: Reap URB - poll -> %d errno=%d pProxyDev=%s\n", rc, errno, usbProxyGetName(pProxyDev))); return NULL; } Log(("usbProxyLinuxUrbReap: poll again - weird!!!\n")); } } /* * Reap URBs, non-blocking. */ for (;;) { struct usbdevfs_urb *pKUrb; while (ioctl(RTFileToNative(pDevLnx->hFile), USBDEVFS_REAPURBNDELAY, &pKUrb)) if (errno != EINTR) { if (errno == ENODEV) usbProxLinuxUrbUnplugged(pProxyDev); else Log(("usb-linux: Reap URB. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); return NULL; } pUrbLnx = (PUSBPROXYURBLNX)pKUrb; /* split list: Is the entire split list done yet? */ if (pUrbLnx->pSplitHead) { pUrbLnx->fSplitElementReaped = true; /* for variable size URBs, we may need to queue more if the just-reaped URB was completely filled */ if (pUrbLnx->cbSplitRemaining && (pKUrb->actual_length == pKUrb->buffer_length) && !pUrbLnx->pSplitNext) { bool fUnplugged = false; bool fSucceeded; Assert(pUrbLnx->pSplitHead); Assert((pKUrb->endpoint & 0x80) && !(pKUrb->flags & USBDEVFS_URB_SHORT_NOT_OK)); PUSBPROXYURBLNX pNew = usbProxyLinuxSplitURBFragment(pProxyDev, pUrbLnx->pSplitHead, pUrbLnx); if (!pNew) { Log(("usb-linux: Allocating URB fragment failed. errno=%d pProxyDev=%s\n", errno, usbProxyGetName(pProxyDev))); return NULL; } PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; fSucceeded = usbProxyLinuxSubmitURB(pProxyDev, pNew, pUrb, &fUnplugged); if (fUnplugged) usbProxLinuxUrbUnplugged(pProxyDev); if (!fSucceeded) return NULL; continue; /* try reaping another URB */ } PUSBPROXYURBLNX pCur; for (pCur = pUrbLnx->pSplitHead; pCur; pCur = pCur->pSplitNext) if (!pCur->fSplitElementReaped) { pUrbLnx = NULL; break; } if (!pUrbLnx) continue; pUrbLnx = pUrbLnx->pSplitHead; } break; } } /* * Ok, we got one! */ PVUSBURB pUrb = (PVUSBURB)pUrbLnx->KUrb.usercontext; if ( pUrb && !pUrbLnx->fCanceledBySubmit) { if (pUrbLnx->pSplitHead) { /* split - find the end byte and the first error status. */ Assert(pUrbLnx == pUrbLnx->pSplitHead); uint8_t *pbEnd = &pUrb->abData[0]; pUrb->enmStatus = VUSBSTATUS_OK; PUSBPROXYURBLNX pCur; for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) { if (pCur->KUrb.actual_length) pbEnd = (uint8_t *)pCur->KUrb.buffer + pCur->KUrb.actual_length; if (pUrb->enmStatus == VUSBSTATUS_OK) pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pCur); } pUrb->cbData = pbEnd - &pUrb->abData[0]; usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); usbProxyLinuxUrbFreeSplitList(pProxyDev, pUrbLnx); } else { /* unsplit. */ pUrb->enmStatus = vusbProxyLinuxUrbGetStatus(pUrbLnx); pUrb->cbData = pUrbLnx->KUrb.actual_length; if (pUrb->enmType == VUSBXFERTYPE_ISOC) { unsigned i, off; for (i = 0, off = 0; i < pUrb->cIsocPkts; i++) { #if RT_GNUC_PREREQ(4, 6) # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Warray-bounds" #endif pUrb->aIsocPkts[i].enmStatus = vusbProxyLinuxStatusToVUsbStatus(pUrbLnx->KUrb.iso_frame_desc[i].status); Assert(pUrb->aIsocPkts[i].off == off); pUrb->aIsocPkts[i].cb = pUrbLnx->KUrb.iso_frame_desc[i].actual_length; off += pUrbLnx->KUrb.iso_frame_desc[i].length; #if RT_GNUC_PREREQ(4, 6) # pragma GCC diagnostic pop #endif } } usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); } pUrb->Dev.pvPrivate = NULL; /* some adjustments for message transfers. */ if (pUrb->enmType == VUSBXFERTYPE_MSG) { pUrb->cbData += sizeof(VUSBSETUP); usbProxyLinuxUrbSwapSetup((PVUSBSETUP)pUrb->abData); } } else { usbProxyLinuxUrbUnlinkInFlight(pDevLnx, pUrbLnx); usbProxyLinuxUrbFree(pProxyDev, pUrbLnx); pUrb = NULL; } LogFlow(("usbProxyLinuxUrbReap: pProxyDev=%s returns %p\n", usbProxyGetName(pProxyDev), pUrb)); return pUrb; } /** * Cancels the URB. * The URB requires reaping, so we don't change its state. */ static DECLCALLBACK(int) usbProxyLinuxUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) { int rc = VINF_SUCCESS; PUSBPROXYURBLNX pUrbLnx = (PUSBPROXYURBLNX)pUrb->Dev.pvPrivate; if (pUrbLnx->pSplitHead) { /* split */ Assert(pUrbLnx == pUrbLnx->pSplitHead); PUSBPROXYURBLNX pCur; for (pCur = pUrbLnx; pCur; pCur = pCur->pSplitNext) { if (pCur->fSplitElementReaped) continue; if ( !usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pCur->KUrb, true, UINT32_MAX) || errno == ENOENT) continue; if (errno == ENODEV) break; /** @todo Think about how to handle errors wrt. to the status code. */ Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!! (split)\n", pUrb, errno, usbProxyGetName(pProxyDev))); } } else { /* unsplit */ if ( usbProxyLinuxDoIoCtl(pProxyDev, USBDEVFS_DISCARDURB, &pUrbLnx->KUrb, true, UINT32_MAX) && errno != ENODEV /* deal with elsewhere. */ && errno != ENOENT) { Log(("usb-linux: Discard URB %p failed, errno=%d. pProxyDev=%s!!!\n", pUrb, errno, usbProxyGetName(pProxyDev))); rc = RTErrConvertFromErrno(errno); } } return rc; } static DECLCALLBACK(int) usbProxyLinuxWakeup(PUSBPROXYDEV pProxyDev) { PUSBPROXYDEVLNX pDevLnx = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVLNX); size_t cbIgnored; LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); return RTPipeWrite(pDevLnx->hPipeWakeupW, "", 1, &cbIgnored); } /** * The Linux USB Proxy Backend. */ const USBPROXYBACK g_USBProxyDeviceHost = { /* pszName */ "host", /* cbBackend */ sizeof(USBPROXYDEVLNX), usbProxyLinuxOpen, usbProxyLinuxInit, usbProxyLinuxClose, usbProxyLinuxReset, usbProxyLinuxSetConfig, usbProxyLinuxClaimInterface, usbProxyLinuxReleaseInterface, usbProxyLinuxSetInterface, usbProxyLinuxClearHaltedEp, usbProxyLinuxUrbQueue, usbProxyLinuxUrbCancel, usbProxyLinuxUrbReap, usbProxyLinuxWakeup, 0 }; /* * Local Variables: * mode: c * c-file-style: "bsd" * c-basic-offset: 4 * End: */