/* $Id: USBProxyDevice-usbip.cpp $ */ /** @file * USB device proxy - USB/IP backend. */ /* * Copyright (C) 2014-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 . * * SPDX-License-Identifier: GPL-3.0-only */ /********************************************************************************************************************************* * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_USBPROXY #include #include #include #include #include #include #include #include #include #include #include #include #include #include "../USBProxyDevice.h" /********************************************************************************************************************************* * Constants And Macros, Structures and Typedefs * *********************************************************************************************************************************/ /** The USB version number used for the protocol. */ #define USBIP_VERSION UINT16_C(0x0111) /** Request indicator in the command code. */ #define USBIP_INDICATOR_REQ RT_BIT(15) /** Command/Reply code for OP_REQ/RET_DEVLIST. */ #define USBIP_REQ_RET_DEVLIST UINT16_C(5) /** Command/Reply code for OP_REQ/REP_IMPORT. */ #define USBIP_REQ_RET_IMPORT UINT16_C(3) /** USB submit command identifier. */ #define USBIP_CMD_SUBMIT UINT32_C(1) /** USB submit status identifier. */ #define USBIP_RET_SUBMIT UINT32_C(3) /** URB unlink (cancel) command identifier. */ #define USBIP_CMD_UNLINK UINT32_C(2) /** URB unlink (cancel) reply identifier. */ #define USBIP_RET_UNLINK UINT32_C(4) /** Short read is not okay for the specified URB. */ #define USBIP_XFER_FLAGS_SHORT_NOT_OK RT_BIT_32(0) /** Queue the isochronous URB as soon as possible. */ #define USBIP_XFER_FLAGS_ISO_ASAP RT_BIT_32(1) /** Don't use DMA mappings for this URB. */ #define USBIP_XFER_FLAGS_NO_TRANSFER_DMA_MAP RT_BIT_32(2) /** Explain - only applies to UHCI. */ #define USBIP_XFER_FLAGS_FSBR RT_BIT_32(4) /** URB direction - input. */ #define USBIP_DIR_IN UINT32_C(1) /** URB direction - output. */ #define USBIP_DIR_OUT UINT32_C(0) /** @name USB/IP error codes. * @{ */ /** Success indicator. */ #define USBIP_STATUS_SUCCESS INT32_C(0) /** Pipe stalled. */ #define USBIP_STATUS_PIPE_STALLED INT32_C(-32) /** URB was unlinked by a call to usb_unlink_urb(). */ #define USBIP_STATUS_URB_UNLINKED INT32_C(-104) /** Short read. */ #define USBIP_STATUS_SHORT_READ INT32_C(-121) /** @} */ /** * Exported device entry in the OP_RET_DEVLIST reply. */ #pragma pack(1) typedef struct UsbIpExportedDevice { /** Path of the device, zero terminated string. */ char szPath[256]; /** Bus ID of the exported device, zero terminated string. */ char szBusId[32]; /** Bus number. */ uint32_t u32BusNum; /** Device number. */ uint32_t u32DevNum; /** Speed indicator of the device. */ uint32_t u32Speed; /** Vendor ID of the device. */ uint16_t u16VendorId; /** Product ID of the device. */ uint16_t u16ProductId; /** Device release number. */ uint16_t u16BcdDevice; /** Device class. */ uint8_t bDeviceClass; /** Device Subclass. */ uint8_t bDeviceSubClass; /** Device protocol. */ uint8_t bDeviceProtocol; /** Configuration value. */ uint8_t bConfigurationValue; /** Current configuration value of the device. */ uint8_t bNumConfigurations; /** Number of interfaces for the device. */ uint8_t bNumInterfaces; } UsbIpExportedDevice; /** Pointer to a exported device entry. */ typedef UsbIpExportedDevice *PUsbIpExportedDevice; #pragma pack() AssertCompileSize(UsbIpExportedDevice, 312); /** * Interface descriptor entry for an exported device. */ #pragma pack(1) typedef struct UsbIpDeviceInterface { /** Intefrace class. */ uint8_t bInterfaceClass; /** Interface sub class. */ uint8_t bInterfaceSubClass; /** Interface protocol identifier. */ uint8_t bInterfaceProtocol; /** Padding byte for alignment. */ uint8_t bPadding; } UsbIpDeviceInterface; /** Pointer to an interface descriptor entry. */ typedef UsbIpDeviceInterface *PUsbIpDeviceInterface; #pragma pack() /** * USB/IP Import request. */ #pragma pack(1) typedef struct UsbIpReqImport { /** Protocol version number. */ uint16_t u16Version; /** Command code. */ uint16_t u16Cmd; /** Status field, unused. */ int32_t u32Status; /** Bus Id of the device as zero terminated string. */ char aszBusId[32]; } UsbIpReqImport; /** Pointer to a import request. */ typedef UsbIpReqImport *PUsbIpReqImport; #pragma pack() /** * USB/IP Import reply. * * This is only the header, for successful * imports the device details are sent to as * defined in UsbIpExportedDevice. */ #pragma pack(1) typedef struct UsbIpRetImport { /** Protocol version number. */ uint16_t u16Version; /** Command code. */ uint16_t u16Cmd; /** Status field, unused. */ int32_t u32Status; } UsbIpRetImport; /** Pointer to a import reply. */ typedef UsbIpRetImport *PUsbIpRetImport; #pragma pack() /** * Command/Reply header common to the submit and unlink commands * replies. */ #pragma pack(1) typedef struct UsbIpReqRetHdr { /** Request/Return code. */ uint32_t u32ReqRet; /** Sequence number to identify the URB. */ uint32_t u32SeqNum; /** Device id. */ uint32_t u32DevId; /** Direction of the endpoint (host->device, device->host). */ uint32_t u32Direction; /** Endpoint number. */ uint32_t u32Endpoint; } UsbIpReqRetHdr; /** Pointer to a request/reply header. */ typedef UsbIpReqRetHdr *PUsbIpReqRetHdr; #pragma pack() /** * USB/IP Submit request. */ #pragma pack(1) typedef struct UsbIpReqSubmit { /** The request header. */ UsbIpReqRetHdr Hdr; /** Transfer flags for the URB. */ uint32_t u32XferFlags; /** Transfer buffer length. */ uint32_t u32TransferBufferLength; /** Frame to transmit an ISO frame. */ uint32_t u32StartFrame; /** Number of isochronous packets. */ uint32_t u32NumIsocPkts; /** Maximum time for the request on the server side host controller. */ uint32_t u32Interval; /** Setup data for a control URB. */ VUSBSETUP Setup; } UsbIpReqSubmit; /** Pointer to a submit request. */ typedef UsbIpReqSubmit *PUsbIpReqSubmit; #pragma pack() AssertCompileSize(UsbIpReqSubmit, 48); /** * USB/IP Submit reply. */ #pragma pack(1) typedef struct UsbIpRetSubmit { /** The reply header. */ UsbIpReqRetHdr Hdr; /** Status code. */ int32_t u32Status; /** Actual length of the reply buffer. */ uint32_t u32ActualLength; /** The actual selected frame for a isochronous transmit. */ uint32_t u32StartFrame; /** Number of isochronous packets. */ uint32_t u32NumIsocPkts; /** Number of failed isochronous packets. */ uint32_t u32ErrorCount; /** Setup data for a control URB. */ VUSBSETUP Setup; } UsbIpRetSubmit; /** Pointer to a submit reply. */ typedef UsbIpRetSubmit *PUsbIpRetSubmit; #pragma pack() AssertCompileSize(UsbIpRetSubmit, 48); /** * Unlink URB request. */ #pragma pack(1) typedef struct UsbIpReqUnlink { /** The request header. */ UsbIpReqRetHdr Hdr; /** The sequence number to unlink. */ uint32_t u32SeqNum; /** Padding - unused. */ uint8_t abPadding[24]; } UsbIpReqUnlink; /** Pointer to a URB unlink request. */ typedef UsbIpReqUnlink *PUsbIpReqUnlink; #pragma pack() AssertCompileSize(UsbIpReqUnlink, 48); /** * Unlink URB reply. */ #pragma pack(1) typedef struct UsbIpRetUnlink { /** The reply header. */ UsbIpReqRetHdr Hdr; /** Status of the request. */ int32_t u32Status; /** Padding - unused. */ uint8_t abPadding[24]; } UsbIpRetUnlink; /** Pointer to a URB unlink request. */ typedef UsbIpRetUnlink *PUsbIpRetUnlink; #pragma pack() AssertCompileSize(UsbIpRetUnlink, 48); /** * Union of possible replies from the server during normal operation. */ #pragma pack(1) typedef union UsbIpRet { /** The header. */ UsbIpReqRetHdr Hdr; /** Submit reply. */ UsbIpRetSubmit RetSubmit; /** Unlink reply. */ UsbIpRetUnlink RetUnlink; /** Byte view. */ uint8_t abReply[1]; } UsbIpRet; /** Pointer to a reply union. */ typedef UsbIpRet *PUsbIpRet; #pragma pack() /** * Isochronous packet descriptor. */ #pragma pack(1) typedef struct UsbIpIsocPktDesc { /** Offset */ uint32_t u32Offset; /** Length of the packet including padding. */ uint32_t u32Length; /** Size of the transmitted data. */ uint32_t u32ActualLength; /** Completion status for this packet. */ int32_t i32Status; } UsbIpIsocPktDesc; /** Pointer to a isochronous packet descriptor. */ typedef UsbIpIsocPktDesc *PUsbIpIsocPktDesc; #pragma pack() /** * USB/IP backend specific data for one URB. * Required for tracking in flight and landed URBs. */ typedef struct USBPROXYURBUSBIP { /** List node for the in flight or landed URB list. */ RTLISTNODE NodeList; /** Sequence number the assigned URB is identified by. */ uint32_t u32SeqNumUrb; /** Sequence number of the unlink command if the URB was cancelled. */ uint32_t u32SeqNumUrbUnlink; /** Flag whether the URB was cancelled. */ bool fCancelled; /** USB xfer type. */ VUSBXFERTYPE enmType; /** USB xfer direction. */ VUSBDIRECTION enmDir; /** Completion status. */ VUSBSTATUS enmStatus; /** Pointer to the VUSB URB. */ PVUSBURB pVUsbUrb; } USBPROXYURBUSBIP; /** Pointer to a USB/IP URB. */ typedef USBPROXYURBUSBIP *PUSBPROXYURBUSBIP; /** * USB/IP data receive states. */ typedef enum USBPROXYUSBIPRECVSTATE { /** Invalid receive state. */ USBPROXYUSBIPRECVSTATE_INVALID = 0, /** Currently receiving the common header structure. */ USBPROXYUSBIPRECVSTATE_HDR_COMMON, /** Currently receieving the rest of the header structure. */ USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL, /** Currently receiving data into the URB buffer. */ USBPROXYUSBIPRECVSTATE_URB_BUFFER, /** Currently receiving the isochronous packet descriptors. */ USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, /** Usual 32bit hack. */ USBPROXYUSBIPRECVSTATE_32BIT_HACK = 0x7fffffff } USBPROXYUSBIPRECVSTATE; /** Pointer to an receive state. */ typedef USBPROXYUSBIPRECVSTATE *PUSBPROXYUSBIPRECVSTATE; /** * Backend data for the USB/IP USB Proxy device backend. */ typedef struct USBPROXYDEVUSBIP { /** IPRT socket handle. */ RTSOCKET hSocket; /** Pollset with the wakeup pipe and socket. */ RTPOLLSET hPollSet; /** Pipe endpoint - read (in the pollset). */ RTPIPE hPipeR; /** Pipe endpoint - write. */ RTPIPE hPipeW; /** Next sequence number to use for identifying submitted URBs. */ volatile uint32_t u32SeqNumNext; /** Fast mutex protecting the lists below against concurrent access. */ RTSEMFASTMUTEX hMtxLists; /** List of in flight URBs. */ RTLISTANCHOR ListUrbsInFlight; /** List of landed URBs. */ RTLISTANCHOR ListUrbsLanded; /** List of URBs to submit. */ RTLISTANCHOR ListUrbsToQueue; /** Port of the USB/IP host to connect to. */ uint32_t uPort; /** USB/IP host address. */ char *pszHost; /** USB Bus ID of the device to capture. */ char *pszBusId; /** The device ID to use to identify the device. */ uint32_t u32DevId; /** Temporary buffer for the next reply header */ UsbIpRet BufRet; /** Temporary buffer to hold all isochronous packet descriptors. */ UsbIpIsocPktDesc aIsocPktDesc[8]; /** Pointer to the current buffer to write received data to. */ uint8_t *pbRecv; /** Number of bytes received so far. */ size_t cbRecv; /** Number of bytes left to receive. until we advance the state machine and process the data */ size_t cbLeft; /** The current receiving state. */ USBPROXYUSBIPRECVSTATE enmRecvState; /** The URB we currently receive a response for. */ PUSBPROXYURBUSBIP pUrbUsbIp; } USBPROXYDEVUSBIP, *PUSBPROXYDEVUSBIP; /** Pollset id of the socket. */ #define USBIP_POLL_ID_SOCKET 0 /** Pollset id of the pipe. */ #define USBIP_POLL_ID_PIPE 1 /** USB/IP address prefix for identifcation. */ #define USBIP_URI_PREFIX "usbip://" /** USB/IP address prefix length. */ #define USBIP_URI_PREFIX_LEN (sizeof(USBIP_URI_PREFIX) - 1) /** Waking reason for the USB I/P reaper: New URBs to queue. */ #define USBIP_REAPER_WAKEUP_REASON_QUEUE 'Q' /** Waking reason for the USB I/P reaper: External wakeup. */ #define USBIP_REAPER_WAKEUP_REASON_EXTERNAL 'E' /** * Converts a request/reply header from network to host endianness. * * @param pHdr The header to convert. */ DECLINLINE(void) usbProxyUsbIpReqRetHdrN2H(PUsbIpReqRetHdr pHdr) { pHdr->u32ReqRet = RT_H2N_U32(pHdr->u32ReqRet); pHdr->u32SeqNum = RT_H2N_U32(pHdr->u32SeqNum); pHdr->u32DevId = RT_H2N_U32(pHdr->u32DevId); pHdr->u32Direction = RT_H2N_U32(pHdr->u32Direction); pHdr->u32Endpoint = RT_H2N_U32(pHdr->u32Endpoint); } /** * Converts a request/reply header from host to network endianness. * * @param pHdr The header to convert. */ DECLINLINE(void) usbProxyUsbIpReqRetHdrH2N(PUsbIpReqRetHdr pHdr) { pHdr->u32ReqRet = RT_N2H_U32(pHdr->u32ReqRet); pHdr->u32SeqNum = RT_N2H_U32(pHdr->u32SeqNum); pHdr->u32DevId = RT_N2H_U32(pHdr->u32DevId); pHdr->u32Direction = RT_N2H_U32(pHdr->u32Direction); pHdr->u32Endpoint = RT_N2H_U32(pHdr->u32Endpoint); } /** * Converts a submit request from host to network endianness. * * @param pReqSubmit The submit request to convert. */ DECLINLINE(void) usbProxyUsbIpReqSubmitH2N(PUsbIpReqSubmit pReqSubmit) { usbProxyUsbIpReqRetHdrH2N(&pReqSubmit->Hdr); pReqSubmit->u32XferFlags = RT_H2N_U32(pReqSubmit->u32XferFlags); pReqSubmit->u32TransferBufferLength = RT_H2N_U32(pReqSubmit->u32TransferBufferLength); pReqSubmit->u32StartFrame = RT_H2N_U32(pReqSubmit->u32StartFrame); pReqSubmit->u32NumIsocPkts = RT_H2N_U32(pReqSubmit->u32NumIsocPkts); pReqSubmit->u32Interval = RT_H2N_U32(pReqSubmit->u32Interval); } /** * Converts a submit reply from network to host endianness. * * @param pReqSubmit The submit reply to convert. */ DECLINLINE(void) usbProxyUsbIpRetSubmitN2H(PUsbIpRetSubmit pRetSubmit) { usbProxyUsbIpReqRetHdrN2H(&pRetSubmit->Hdr); pRetSubmit->u32Status = RT_N2H_U32(pRetSubmit->u32Status); pRetSubmit->u32ActualLength = RT_N2H_U32(pRetSubmit->u32ActualLength); pRetSubmit->u32StartFrame = RT_N2H_U32(pRetSubmit->u32StartFrame); pRetSubmit->u32NumIsocPkts = RT_N2H_U32(pRetSubmit->u32NumIsocPkts); pRetSubmit->u32ErrorCount = RT_N2H_U32(pRetSubmit->u32ErrorCount); } /** * Converts a isochronous packet descriptor from host to network endianness. * * @param pIsocPktDesc The packet descriptor to convert. */ DECLINLINE(void) usbProxyUsbIpIsocPktDescH2N(PUsbIpIsocPktDesc pIsocPktDesc) { pIsocPktDesc->u32Offset = RT_H2N_U32(pIsocPktDesc->u32Offset); pIsocPktDesc->u32Length = RT_H2N_U32(pIsocPktDesc->u32Length); pIsocPktDesc->u32ActualLength = RT_H2N_U32(pIsocPktDesc->u32ActualLength); pIsocPktDesc->i32Status = RT_H2N_U32(pIsocPktDesc->i32Status); } /** * Converts a isochronous packet descriptor from network to host endianness. * * @param pIsocPktDesc The packet descriptor to convert. */ DECLINLINE(void) usbProxyUsbIpIsocPktDescN2H(PUsbIpIsocPktDesc pIsocPktDesc) { pIsocPktDesc->u32Offset = RT_N2H_U32(pIsocPktDesc->u32Offset); pIsocPktDesc->u32Length = RT_N2H_U32(pIsocPktDesc->u32Length); pIsocPktDesc->u32ActualLength = RT_N2H_U32(pIsocPktDesc->u32ActualLength); pIsocPktDesc->i32Status = RT_N2H_U32(pIsocPktDesc->i32Status); } /** * Converts a unlink request from host to network endianness. * * @param pReqUnlink The unlink request to convert. */ DECLINLINE(void) usbProxyUsbIpReqUnlinkH2N(PUsbIpReqUnlink pReqUnlink) { usbProxyUsbIpReqRetHdrH2N(&pReqUnlink->Hdr); pReqUnlink->u32SeqNum = RT_H2N_U32(pReqUnlink->u32SeqNum); } /** * Converts a unlink reply from network to host endianness. * * @param pRetUnlink The unlink reply to convert. */ DECLINLINE(void) usbProxyUsbIpRetUnlinkN2H(PUsbIpRetUnlink pRetUnlink) { usbProxyUsbIpReqRetHdrN2H(&pRetUnlink->Hdr); pRetUnlink->u32Status = RT_N2H_U32(pRetUnlink->u32Status); } /** * Convert the given exported device structure from host to network byte order. * * @param pDevice The device structure to convert. */ DECLINLINE(void) usbProxyUsbIpExportedDeviceN2H(PUsbIpExportedDevice pDevice) { pDevice->u32BusNum = RT_N2H_U32(pDevice->u32BusNum); pDevice->u32DevNum = RT_N2H_U32(pDevice->u32DevNum); pDevice->u32Speed = RT_N2H_U16(pDevice->u32Speed); pDevice->u16VendorId = RT_N2H_U16(pDevice->u16VendorId); pDevice->u16ProductId = RT_N2H_U16(pDevice->u16ProductId); pDevice->u16BcdDevice = RT_N2H_U16(pDevice->u16BcdDevice); } /** * Converts a USB/IP status code to a VUSB status code. * * @returns VUSB status code. * @param i32Status The USB/IP status code from the reply. */ DECLINLINE(VUSBSTATUS) usbProxyUsbIpVUsbStatusConvertFromStatus(int32_t i32Status) { if (RT_LIKELY( i32Status == USBIP_STATUS_SUCCESS || i32Status == USBIP_STATUS_SHORT_READ)) return VUSBSTATUS_OK; switch (i32Status) { case USBIP_STATUS_PIPE_STALLED: return VUSBSTATUS_STALL; default: return VUSBSTATUS_DNR; } /* not reached */ } /** * Gets the next free sequence number. * * @returns Next free sequence number. * @param pProxyDevUsbIp The USB/IP proxy device data. */ DECLINLINE(uint32_t) usbProxyUsbIpSeqNumGet(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { uint32_t u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); if (RT_UNLIKELY(!u32SeqNum)) u32SeqNum = ASMAtomicIncU32(&pProxyDevUsbIp->u32SeqNumNext); return u32SeqNum; } /** * Links a given URB into the given list. * * @param pProxyDevUsbIp The USB/IP proxy device data. * @param pList The list to link the URB into. * @param pUrbUsbIp The URB to link. */ DECLINLINE(void) usbProxyUsbIpLinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PRTLISTANCHOR pList, PUSBPROXYURBUSBIP pUrbUsbIp) { int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); RTListAppend(pList, &pUrbUsbIp->NodeList); RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); } /** * Unlinks a given URB from the current assigned list. * * @param pProxyDevUsbIp The USB/IP proxy device data. * @param pUrbUsbIp The URB to unlink. */ DECLINLINE(void) usbProxyUsbIpUnlinkUrb(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) { int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); RTListNodeRemove(&pUrbUsbIp->NodeList); RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); } /** * Allocates a USB/IP proxy specific URB state. * * @returns Pointer to the USB/IP specific URB data or NULL on failure. * @param pProxyDevUsbIp The USB/IP proxy device data. */ static PUSBPROXYURBUSBIP usbProxyUsbIpUrbAlloc(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { NOREF(pProxyDevUsbIp); return (PUSBPROXYURBUSBIP)RTMemAllocZ(sizeof(USBPROXYURBUSBIP)); } /** * Frees the given USB/IP URB state. * * @param pProxyDevUsbIp The USB/IP proxy device data. * @param pUrbUsbIp The USB/IP speciic URB data. */ static void usbProxyUsbIpUrbFree(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) { NOREF(pProxyDevUsbIp); RTMemFree(pUrbUsbIp); } /** * Parse the string representation of the host address. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data to parse the address for. * @param pszAddress The address string to parse. */ static int usbProxyUsbIpParseAddress(PUSBPROXYDEVUSBIP pProxyDevUsbIp, const char *pszAddress) { int rc = VINF_SUCCESS; if (!RTStrNCmp(pszAddress, USBIP_URI_PREFIX, USBIP_URI_PREFIX_LEN)) { pszAddress += USBIP_URI_PREFIX_LEN; const char *pszPortStart = RTStrStr(pszAddress, ":"); if (pszPortStart) { pszPortStart++; const char *pszBusIdStart = RTStrStr(pszPortStart, ":"); if (pszBusIdStart) { size_t cbHost = pszPortStart - pszAddress - 1; size_t cbBusId = strlen(pszBusIdStart); pszBusIdStart++; rc = RTStrToUInt32Ex(pszPortStart, NULL, 10 /* uBase */, &pProxyDevUsbIp->uPort); if ( rc == VINF_SUCCESS || rc == VWRN_TRAILING_CHARS) { rc = RTStrAllocEx(&pProxyDevUsbIp->pszHost, cbHost + 1); if (RT_SUCCESS(rc)) rc = RTStrAllocEx(&pProxyDevUsbIp->pszBusId, cbBusId + 1); if (RT_SUCCESS(rc)) { rc = RTStrCopyEx(pProxyDevUsbIp->pszHost, cbHost + 1, pszAddress, cbHost); AssertRC(rc); rc = RTStrCopyEx(pProxyDevUsbIp->pszBusId, cbBusId + 1, pszBusIdStart, cbBusId); AssertRC(rc); return VINF_SUCCESS; } } else rc = VERR_INVALID_PARAMETER; } else rc = VERR_INVALID_PARAMETER; } else rc = VERR_INVALID_PARAMETER; } else rc = VERR_INVALID_PARAMETER; return rc; } /** * Connects to the USB/IP host and claims the device given in the proxy device data. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. */ static int usbProxyUsbIpConnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { int rc = VINF_SUCCESS; rc = RTTcpClientConnect(pProxyDevUsbIp->pszHost, pProxyDevUsbIp->uPort, &pProxyDevUsbIp->hSocket); if (RT_SUCCESS(rc)) { /* Disable send coalescing. */ rc = RTTcpSetSendCoalescing(pProxyDevUsbIp->hSocket, false); if (RT_FAILURE(rc)) LogRel(("UsbIp: Disabling send coalescing failed (rc=%Rrc), continuing nevertheless but expect reduced performance\n", rc)); /* Import the device, i.e. claim it for our use. */ UsbIpReqImport ReqImport; ReqImport.u16Version = RT_H2N_U16(USBIP_VERSION); ReqImport.u16Cmd = RT_H2N_U16(USBIP_INDICATOR_REQ | USBIP_REQ_RET_IMPORT); ReqImport.u32Status = RT_H2N_U32(USBIP_STATUS_SUCCESS); rc = RTStrCopy(&ReqImport.aszBusId[0], sizeof(ReqImport.aszBusId), pProxyDevUsbIp->pszBusId); if (rc == VINF_SUCCESS) { rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqImport, sizeof(ReqImport)); if (RT_SUCCESS(rc)) { /* Read the reply. */ UsbIpRetImport RetImport; rc = RTTcpRead(pProxyDevUsbIp->hSocket, &RetImport, sizeof(RetImport), NULL); if (RT_SUCCESS(rc)) { RetImport.u16Version = RT_N2H_U16(RetImport.u16Version); RetImport.u16Cmd = RT_N2H_U16(RetImport.u16Cmd); RetImport.u32Status = RT_N2H_U32(RetImport.u32Status); if ( RetImport.u16Version == USBIP_VERSION && RetImport.u16Cmd == USBIP_REQ_RET_IMPORT && RetImport.u32Status == USBIP_STATUS_SUCCESS) { /* Read the device data. */ UsbIpExportedDevice Device; rc = RTTcpRead(pProxyDevUsbIp->hSocket, &Device, sizeof(Device), NULL); if (RT_SUCCESS(rc)) { usbProxyUsbIpExportedDeviceN2H(&Device); pProxyDevUsbIp->u32DevId = (Device.u32BusNum << 16) | Device.u32DevNum; rc = RTPollSetAddSocket(pProxyDevUsbIp->hPollSet, pProxyDevUsbIp->hSocket, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, USBIP_POLL_ID_SOCKET); } } else { /* Check what went wrong and leave a meaningful error message in the log. */ if (RetImport.u16Version != USBIP_VERSION) LogRel(("UsbIp: Unexpected protocol version received from host (%#x vs. %#x)\n", RetImport.u16Version, USBIP_VERSION)); else if (RetImport.u16Cmd != USBIP_REQ_RET_IMPORT) LogRel(("UsbIp: Unexpected reply code received from host (%#x vs. %#x)\n", RetImport.u16Cmd, USBIP_REQ_RET_IMPORT)); else if (RetImport.u32Status != 0) LogRel(("UsbIp: Claiming the device has failed on the host with an unspecified error\n")); else AssertMsgFailed(("Something went wrong with if condition\n")); } } } } else { LogRel(("UsbIp: Given bus ID is exceeds permitted protocol length: %u vs %u\n", strlen(pProxyDevUsbIp->pszBusId) + 1, sizeof(ReqImport.aszBusId))); rc = VERR_INVALID_PARAMETER; } if (RT_FAILURE(rc)) RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); } if (RT_FAILURE(rc)) LogRel(("UsbIp: Connecting to the host %s failed with %Rrc\n", pProxyDevUsbIp->pszHost, rc)); return rc; } /** * Disconnects from the USB/IP host releasing the device given in the proxy device data. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. */ static int usbProxyUsbIpDisconnect(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { int rc = RTPollSetRemove(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_SOCKET); Assert(RT_SUCCESS(rc) || rc == VERR_POLL_HANDLE_ID_NOT_FOUND); rc = RTTcpClientCloseEx(pProxyDevUsbIp->hSocket, false /*fGracefulShutdown*/); if (RT_SUCCESS(rc)) pProxyDevUsbIp->hSocket = NIL_RTSOCKET; return rc; } /** * Returns the URB matching the given sequence number from the in flight list. * * @returns pointer to the URB matching the given sequence number or NULL * @param pProxyDevUsbIp The USB/IP proxy device data. * @param u32SeqNum The sequence number to search for. */ static PUSBPROXYURBUSBIP usbProxyUsbIpGetInFlightUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) { bool fFound = false; int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); PUSBPROXYURBUSBIP pIt; RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) { if (pIt->u32SeqNumUrb == u32SeqNum) { fFound = true; break; } } RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); return fFound ? pIt : NULL; } /** * Returns the URB matching the given sequence number from the cancel list. * * @returns pointer to the URB matching the given sequence number or NULL * @param pProxyDevUsbIp The USB/IP proxy device data. * @param u32SeqNum The sequence number to search for. */ static PUSBPROXYURBUSBIP usbProxyUsbIpGetCancelledUrbFromSeqNum(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNum) { bool fFound = false; int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); PUSBPROXYURBUSBIP pIt; RTListForEach(&pProxyDevUsbIp->ListUrbsInFlight, pIt, USBPROXYURBUSBIP, NodeList) { if ( pIt->u32SeqNumUrbUnlink == u32SeqNum && pIt->fCancelled == true) { fFound = true; break; } } RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); return fFound ? pIt : NULL; } /** * Resets the receive state for a new reply. * * @param pProxyDevUsbIp The USB/IP proxy device data. */ static void usbProxyUsbIpResetRecvState(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_COMMON; pProxyDevUsbIp->pbRecv = (uint8_t *)&pProxyDevUsbIp->BufRet; pProxyDevUsbIp->cbRecv = 0; pProxyDevUsbIp->cbLeft = sizeof(UsbIpReqRetHdr); } static void usbProxyUsbIpRecvStateAdvance(PUSBPROXYDEVUSBIP pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE enmState, uint8_t *pbData, size_t cbData) { pProxyDevUsbIp->enmRecvState = enmState; pProxyDevUsbIp->cbRecv = 0; pProxyDevUsbIp->cbLeft = cbData; pProxyDevUsbIp->pbRecv = pbData; } /** * Handles reception of a USB/IP PDU. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. * @param ppUrbUsbIp Where to store the pointer to the USB/IP URB which completed. * Will be NULL if the received PDU is not complete and we have * have to wait for more data or on failure. */ static int usbProxyUsbIpRecvPdu(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP *ppUrbUsbIp) { int rc = VINF_SUCCESS; size_t cbRead = 0; PUSBPROXYURBUSBIP pUrbUsbIp = NULL; Assert(pProxyDevUsbIp->cbLeft); /* Read any available data first. */ rc = RTTcpReadNB(pProxyDevUsbIp->hSocket, pProxyDevUsbIp->pbRecv, pProxyDevUsbIp->cbLeft, &cbRead); if (RT_SUCCESS(rc)) { pProxyDevUsbIp->cbRecv += cbRead; pProxyDevUsbIp->cbLeft -= cbRead; pProxyDevUsbIp->pbRecv += cbRead; /* Process the received data if there is nothing to receive left for the current state. */ if (!pProxyDevUsbIp->cbLeft) { switch (pProxyDevUsbIp->enmRecvState) { case USBPROXYUSBIPRECVSTATE_HDR_COMMON: { Assert(pProxyDevUsbIp->cbRecv == sizeof(UsbIpReqRetHdr)); /* * Determine the residual amount of data to receive until * the complete reply header was received. */ switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) { case USBIP_RET_SUBMIT: pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetSubmit) - sizeof(UsbIpReqRetHdr); pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; break; case USBIP_RET_UNLINK: pProxyDevUsbIp->cbLeft = sizeof(UsbIpRetUnlink) - sizeof(UsbIpReqRetHdr); pProxyDevUsbIp->enmRecvState = USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL; break; default: AssertLogRelMsgFailed(("Invalid reply header received: %d\n", pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)); usbProxyUsbIpResetRecvState(pProxyDevUsbIp); } break; } case USBPROXYUSBIPRECVSTATE_HDR_RESIDUAL: { switch (RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32ReqRet)) { case USBIP_RET_SUBMIT: /* Get the URB from the in flight list. */ pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetInFlightUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); if (pProxyDevUsbIp->pUrbUsbIp) { usbProxyUsbIpRetSubmitN2H(&pProxyDevUsbIp->BufRet.RetSubmit); /* We still have to receive the transfer buffer, even in case of an error. */ pProxyDevUsbIp->pUrbUsbIp->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetSubmit.u32Status); if (pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_IN) { uint8_t *pbData = NULL; size_t cbRet = 0; AssertPtr(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb); if (pProxyDevUsbIp->pUrbUsbIp->enmType == VUSBXFERTYPE_MSG) { /* Preserve the setup request. */ pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[sizeof(VUSBSETUP)]; cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength + sizeof(VUSBSETUP); } else { pbData = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->abData[0]; cbRet = pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength; } if (pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength) { if (RT_LIKELY(pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData >= cbRet)) { pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData = (uint32_t)cbRet; usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_URB_BUFFER, pbData, pProxyDevUsbIp->BufRet.RetSubmit.u32ActualLength); } else { /* * Bogus length returned from the USB/IP remote server. * Error out because there is no way to find the end of the current * URB and the beginning of the next one. The error will cause closing the * connection to the rogue remote and all URBs get completed with an error. */ LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid length %zu (max %zu)\n", pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, cbRet, pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData)); rc = VERR_NET_PROTOCOL_ERROR; } } else { pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); } } else { Assert(pProxyDevUsbIp->pUrbUsbIp->enmDir == VUSBDIRECTION_OUT); pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); } } else { LogRel(("USB/IP: Received reply with sequence number %u doesn't match any local URB\n", RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum))); usbProxyUsbIpResetRecvState(pProxyDevUsbIp); rc = VERR_NET_PROTOCOL_ERROR; } break; case USBIP_RET_UNLINK: pProxyDevUsbIp->pUrbUsbIp = usbProxyUsbIpGetCancelledUrbFromSeqNum(pProxyDevUsbIp, RT_N2H_U32(pProxyDevUsbIp->BufRet.Hdr.u32SeqNum)); if (pProxyDevUsbIp->pUrbUsbIp) { usbProxyUsbIpRetUnlinkN2H(&pProxyDevUsbIp->BufRet.RetUnlink); pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; pUrbUsbIp->pVUsbUrb->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pProxyDevUsbIp->BufRet.RetUnlink.u32Status); } /* else: Probably received the data for the URB and is complete already. */ usbProxyUsbIpResetRecvState(pProxyDevUsbIp); break; } break; } case USBPROXYUSBIPRECVSTATE_URB_BUFFER: if (pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->enmType == VUSBXFERTYPE_ISOC) usbProxyUsbIpRecvStateAdvance(pProxyDevUsbIp, USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS, (uint8_t *)&pProxyDevUsbIp->aIsocPktDesc[0], pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc)); else { pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); } break; case USBPROXYUSBIPRECVSTATE_ISOC_PKT_DESCS: /* Process all received isochronous packet descriptors. */ for (unsigned i = 0; i < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cIsocPkts; i++) { PVUSBURBISOCPTK pIsocPkt = &pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->aIsocPkts[i]; PUsbIpIsocPktDesc pIsocPktUsbIp = &pProxyDevUsbIp->aIsocPktDesc[i]; usbProxyUsbIpIsocPktDescN2H(pIsocPktUsbIp); pIsocPkt->enmStatus = usbProxyUsbIpVUsbStatusConvertFromStatus(pIsocPktUsbIp->i32Status); if (RT_LIKELY( pIsocPktUsbIp->u32Offset < pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData && pProxyDevUsbIp->pUrbUsbIp->pVUsbUrb->cbData - pIsocPktUsbIp->u32Offset >= pIsocPktUsbIp->u32ActualLength)) { pIsocPkt->off = pIsocPktUsbIp->u32Offset; pIsocPkt->cb = pIsocPktUsbIp->u32ActualLength; } else { /* * The offset and length value in the isoc packet descriptor are bogus and would cause a buffer overflow later on, leave an * error message and disconnect from the rogue remote end. */ LogRelMax(10, ("USB/IP: Received reply with sequence number %u contains invalid isoc packet descriptor %u (offset=%u length=%u)\n", pProxyDevUsbIp->BufRet.Hdr.u32SeqNum, i, pIsocPktUsbIp->u32Offset, pIsocPktUsbIp->u32ActualLength)); rc = VERR_NET_PROTOCOL_ERROR; break; } } pUrbUsbIp = pProxyDevUsbIp->pUrbUsbIp; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); break; default: AssertLogRelMsgFailed(("USB/IP: Invalid receive state %d\n", pProxyDevUsbIp->enmRecvState)); } } } if (RT_SUCCESS(rc)) *ppUrbUsbIp = pUrbUsbIp; else { /* Complete all URBs with DNR error and mark device as unplugged, the current one is still in the in flight list. */ pProxyDevUsbIp->pUrbUsbIp = NULL; usbProxyUsbIpResetRecvState(pProxyDevUsbIp); usbProxyUsbIpDisconnect(pProxyDevUsbIp); rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); PUSBPROXYURBUSBIP pIt; PUSBPROXYURBUSBIP pItNext; RTListForEachSafe(&pProxyDevUsbIp->ListUrbsInFlight, pIt, pItNext, USBPROXYURBUSBIP, NodeList) { if (pIt->pVUsbUrb) /* can be NULL for requests created by usbProxyUsbIpCtrlUrbExchangeSync(). */ pIt->pVUsbUrb->enmStatus = VUSBSTATUS_CRC; RTListNodeRemove(&pIt->NodeList); RTListAppend(&pProxyDevUsbIp->ListUrbsLanded, &pIt->NodeList); } RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); } return rc; } /** * Worker for queueing an URB on the main I/O thread. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. * @param pUrbUsbIp The USB/IP URB to queue. */ static int usbProxyUsbIpUrbQueueWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PUSBPROXYURBUSBIP pUrbUsbIp) { PVUSBURB pUrb = pUrbUsbIp->pVUsbUrb; pUrbUsbIp->u32SeqNumUrb = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); pUrbUsbIp->enmType = pUrb->enmType; pUrbUsbIp->enmStatus = pUrb->enmStatus; pUrbUsbIp->enmDir = pUrb->enmDir; UsbIpReqSubmit ReqSubmit; RT_ZERO(ReqSubmit); ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; ReqSubmit.Hdr.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; ReqSubmit.Hdr.u32Endpoint = pUrb->EndPt; ReqSubmit.Hdr.u32Direction = pUrb->enmDir == VUSBDIRECTION_IN ? USBIP_DIR_IN : USBIP_DIR_OUT; ReqSubmit.u32XferFlags = 0; if (pUrb->enmDir == VUSBDIRECTION_IN && pUrb->fShortNotOk) ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_SHORT_NOT_OK; ReqSubmit.u32TransferBufferLength = pUrb->cbData; ReqSubmit.u32StartFrame = 0; ReqSubmit.u32NumIsocPkts = 0; ReqSubmit.u32Interval = 0; RTSGSEG aSegReq[3]; /* Maximum number of segments used for a Isochronous transfer. */ UsbIpIsocPktDesc aIsocPktsDesc[8]; unsigned cSegsUsed = 1; aSegReq[0].pvSeg = &ReqSubmit; aSegReq[0].cbSeg = sizeof(ReqSubmit); switch (pUrb->enmType) { case VUSBXFERTYPE_MSG: memcpy(&ReqSubmit.Setup, &pUrb->abData, sizeof(ReqSubmit.Setup)); ReqSubmit.u32TransferBufferLength -= sizeof(VUSBSETUP); if (pUrb->enmDir == VUSBDIRECTION_OUT) { aSegReq[cSegsUsed].cbSeg = pUrb->cbData - sizeof(VUSBSETUP); aSegReq[cSegsUsed].pvSeg = pUrb->abData + sizeof(VUSBSETUP); if (aSegReq[cSegsUsed].cbSeg) cSegsUsed++; } LogFlowFunc(("Message (Control) URB\n")); break; case VUSBXFERTYPE_ISOC: LogFlowFunc(("Isochronous URB\n")); ReqSubmit.u32XferFlags |= USBIP_XFER_FLAGS_ISO_ASAP; ReqSubmit.u32NumIsocPkts = pUrb->cIsocPkts; if (pUrb->enmDir == VUSBDIRECTION_OUT) { aSegReq[cSegsUsed].cbSeg = pUrb->cbData; aSegReq[cSegsUsed].pvSeg = pUrb->abData; cSegsUsed++; } for (unsigned i = 0; i < pUrb->cIsocPkts; i++) { aIsocPktsDesc[i].u32Offset = pUrb->aIsocPkts[i].off; aIsocPktsDesc[i].u32Length = pUrb->aIsocPkts[i].cb; aIsocPktsDesc[i].u32ActualLength = 0; /** @todo */ aIsocPktsDesc[i].i32Status = pUrb->aIsocPkts[i].enmStatus; usbProxyUsbIpIsocPktDescH2N(&aIsocPktsDesc[i]); } if (pUrb->cIsocPkts) { aSegReq[cSegsUsed].cbSeg = pUrb->cIsocPkts * sizeof(UsbIpIsocPktDesc); aSegReq[cSegsUsed].pvSeg = &aIsocPktsDesc[0]; cSegsUsed++; } break; case VUSBXFERTYPE_BULK: case VUSBXFERTYPE_INTR: LogFlowFunc(("Bulk URB\n")); if (pUrb->enmDir == VUSBDIRECTION_OUT) { aSegReq[cSegsUsed].cbSeg = pUrb->cbData; aSegReq[cSegsUsed].pvSeg = pUrb->abData; cSegsUsed++; } break; default: return VERR_INVALID_PARAMETER; /** @todo better status code. */ } usbProxyUsbIpReqSubmitH2N(&ReqSubmit); Assert(cSegsUsed <= RT_ELEMENTS(aSegReq)); /* Send the command. */ RTSGBUF SgBufReq; RTSgBufInit(&SgBufReq, &aSegReq[0], cSegsUsed); int rc = RTTcpSgWrite(pProxyDevUsbIp->hSocket, &SgBufReq); if (RT_SUCCESS(rc)) { /* Link the URB into the list of in flight URBs. */ usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, pUrbUsbIp); } return rc; } /** * Queues all pending URBs from the list. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. */ static int usbProxyUsbIpUrbsQueuePending(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { RTLISTANCHOR ListUrbsPending; int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); RTListMove(&ListUrbsPending, &pProxyDevUsbIp->ListUrbsToQueue); RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); PUSBPROXYURBUSBIP pIter; PUSBPROXYURBUSBIP pIterNext; RTListForEachSafe(&ListUrbsPending, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) { RTListNodeRemove(&pIter->NodeList); rc = usbProxyUsbIpUrbQueueWorker(pProxyDevUsbIp, pIter); if (RT_FAILURE(rc)) { /* Complete URB with an error and place into landed list. */ pIter->pVUsbUrb->enmStatus = VUSBSTATUS_DNR; usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pIter); } } return VINF_SUCCESS; } /** * Kick the reaper thread. * * @returns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. * @param bReason The wakeup reason. */ static char usbProxyReaperKick(PUSBPROXYDEVUSBIP pProxyDevUsbIp, char bReason) { int rc = VINF_SUCCESS; size_t cbWritten = 0; rc = RTPipeWrite(pProxyDevUsbIp->hPipeW, &bReason, 1, &cbWritten); Assert(RT_SUCCESS(rc) || cbWritten == 0); return rc; } /** * Drain the wakeup pipe. * * @returns Wakeup reason. * @param pProxyDevUsbIp The USB/IP proxy device data. */ static char usbProxyUsbIpWakeupPipeDrain(PUSBPROXYDEVUSBIP pProxyDevUsbIp) { char bRead = 0; size_t cbRead = 0; int rc = RTPipeRead(pProxyDevUsbIp->hPipeR, &bRead, 1, &cbRead); Assert(RT_SUCCESS(rc) && cbRead == 1); NOREF(rc); return bRead; } /** * Executes the poll/receive loop either until a URB is received (with an optional matching sequence number) or * the given timeout has elapsed. * * @returns Pointer to the received USB/IP URB or NULL on timeout or error. * @param pProxyDevUsbIp The USB/IP proxy device data. * @param u32SeqNumRet The sequence number of a specific reply to return the URB for, 0 if * any received URB is accepted. * @param fPollWakePipe Flag whether to poll the wakeup pipe. * @param cMillies Maximum number of milliseconds to wait for an URB to arrive. */ static PUSBPROXYURBUSBIP usbProxyUsbIpPollWorker(PUSBPROXYDEVUSBIP pProxyDevUsbIp, uint32_t u32SeqNumRet, bool fPollWakePipe, RTMSINTERVAL cMillies) { int rc = VINF_SUCCESS; PUSBPROXYURBUSBIP pUrbUsbIp = NULL; if (!fPollWakePipe) { rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_ERROR); AssertRC(rc); } while (!pUrbUsbIp && RT_SUCCESS(rc) && cMillies) { uint32_t uIdReady = 0; uint32_t fEventsRecv = 0; RTMSINTERVAL msStart = RTTimeMilliTS(); RTMSINTERVAL msNow; rc = RTPoll(pProxyDevUsbIp->hPollSet, cMillies, &fEventsRecv, &uIdReady); Assert(RT_SUCCESS(rc) || rc == VERR_TIMEOUT); if (RT_SUCCESS(rc)) { msNow = RTTimeMilliTS(); cMillies = msNow - msStart >= cMillies ? 0 : cMillies - (msNow - msStart); if (uIdReady == USBIP_POLL_ID_SOCKET) { rc = usbProxyUsbIpRecvPdu(pProxyDevUsbIp, &pUrbUsbIp); if ( RT_SUCCESS(rc) && pUrbUsbIp) { /* Link the URB into the landed list if a specifc reply is requested and the URB doesn't match. */ if ( u32SeqNumRet != 0 && pUrbUsbIp->u32SeqNumUrb != u32SeqNumRet) { usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsLanded, pUrbUsbIp); pUrbUsbIp = NULL; } } } else { AssertLogRelMsg(uIdReady == USBIP_POLL_ID_PIPE, ("Invalid pollset ID given\n")); char bReason = usbProxyUsbIpWakeupPipeDrain(pProxyDevUsbIp); if (bReason == USBIP_REAPER_WAKEUP_REASON_QUEUE) usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); else { Assert(bReason == USBIP_REAPER_WAKEUP_REASON_EXTERNAL); break; } } } } if (!fPollWakePipe) { rc = RTPollSetEventsChange(pProxyDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE, RTPOLL_EVT_READ); AssertRC(rc); } return pUrbUsbIp; } /** * Synchronously exchange a given control message with the remote device. * * @eturns VBox status code. * @param pProxyDevUsbIp The USB/IP proxy device data. * @param pSetup The setup message. * * @note This method is only used to implement the *SetConfig, *SetInterface and *ClearHaltedEp * callbacks because the USB/IP protocol lacks dedicated requests for these. * @remark It is assumed that this method is never called while usbProxyUsbIpUrbReap is called * on another thread. */ static int usbProxyUsbIpCtrlUrbExchangeSync(PUSBPROXYDEVUSBIP pProxyDevUsbIp, PVUSBSETUP pSetup) { int rc = VINF_SUCCESS; UsbIpReqSubmit ReqSubmit; USBPROXYURBUSBIP UsbIpUrb; RT_ZERO(ReqSubmit); uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); ReqSubmit.Hdr.u32ReqRet = USBIP_CMD_SUBMIT; ReqSubmit.Hdr.u32SeqNum = u32SeqNum; ReqSubmit.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; ReqSubmit.Hdr.u32Direction = USBIP_DIR_OUT; ReqSubmit.Hdr.u32Endpoint = 0; /* Only default control endpoint is allowed for these kind of messages. */ ReqSubmit.u32XferFlags = 0; ReqSubmit.u32TransferBufferLength = 0; ReqSubmit.u32StartFrame = 0; ReqSubmit.u32NumIsocPkts = 0; ReqSubmit.u32Interval = 0; memcpy(&ReqSubmit.Setup, pSetup, sizeof(ReqSubmit.Setup)); usbProxyUsbIpReqSubmitH2N(&ReqSubmit); UsbIpUrb.u32SeqNumUrb = u32SeqNum; UsbIpUrb.u32SeqNumUrbUnlink = 0; UsbIpUrb.fCancelled = false; UsbIpUrb.enmType = VUSBXFERTYPE_MSG; UsbIpUrb.enmDir = VUSBDIRECTION_OUT; UsbIpUrb.pVUsbUrb = NULL; /* Send the command. */ rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqSubmit, sizeof(ReqSubmit)); if (RT_SUCCESS(rc)) { usbProxyUsbIpLinkUrb(pProxyDevUsbIp, &pProxyDevUsbIp->ListUrbsInFlight, &UsbIpUrb); PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, u32SeqNum, false /*fPollWakePipe*/, 30 * RT_MS_1SEC); Assert( !pUrbUsbIp || pUrbUsbIp == &UsbIpUrb); /* The returned URB should point to the URB we submitted. */ usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, &UsbIpUrb); if (!pUrbUsbIp) rc = VERR_TIMEOUT; } return rc; } /* * The USB proxy device functions. */ /** * @interface_method_impl{USBPROXYBACK,pfnOpen} */ static DECLCALLBACK(int) usbProxyUsbIpOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) { LogFlowFunc(("pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); int rc = VINF_SUCCESS; RTListInit(&pDevUsbIp->ListUrbsInFlight); RTListInit(&pDevUsbIp->ListUrbsLanded); RTListInit(&pDevUsbIp->ListUrbsToQueue); pDevUsbIp->hSocket = NIL_RTSOCKET; pDevUsbIp->hPollSet = NIL_RTPOLLSET; pDevUsbIp->hPipeW = NIL_RTPIPE; pDevUsbIp->hPipeR = NIL_RTPIPE; pDevUsbIp->u32SeqNumNext = 0; pDevUsbIp->pszHost = NULL; pDevUsbIp->pszBusId = NULL; usbProxyUsbIpResetRecvState(pDevUsbIp); rc = RTSemFastMutexCreate(&pDevUsbIp->hMtxLists); if (RT_SUCCESS(rc)) { /* Setup wakeup pipe and poll set first. */ rc = RTPipeCreate(&pDevUsbIp->hPipeR, &pDevUsbIp->hPipeW, 0); if (RT_SUCCESS(rc)) { rc = RTPollSetCreate(&pDevUsbIp->hPollSet); if (RT_SUCCESS(rc)) { rc = RTPollSetAddPipe(pDevUsbIp->hPollSet, pDevUsbIp->hPipeR, RTPOLL_EVT_READ, USBIP_POLL_ID_PIPE); if (RT_SUCCESS(rc)) { /* Connect to the USB/IP host. */ rc = usbProxyUsbIpParseAddress(pDevUsbIp, pszAddress); if (RT_SUCCESS(rc)) rc = usbProxyUsbIpConnect(pDevUsbIp); } if (RT_FAILURE(rc)) { RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); int rc2 = RTPollSetDestroy(pDevUsbIp->hPollSet); AssertRC(rc2); } } if (RT_FAILURE(rc)) { int rc2 = RTPipeClose(pDevUsbIp->hPipeR); AssertRC(rc2); rc2 = RTPipeClose(pDevUsbIp->hPipeW); AssertRC(rc2); } } } return rc; } /** * @interface_method_impl{USBPROXYBACK,pfnClose} */ static DECLCALLBACK(void) usbProxyUsbIpClose(PUSBPROXYDEV pProxyDev) { int rc = VINF_SUCCESS; LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); PUSBPROXYDEVUSBIP pDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); if (pDevUsbIp->hSocket != NIL_RTSOCKET) usbProxyUsbIpDisconnect(pDevUsbIp); /* Destroy the pipe and pollset if necessary. */ if (pDevUsbIp->hPollSet != NIL_RTPOLLSET) { rc = RTPollSetRemove(pDevUsbIp->hPollSet, USBIP_POLL_ID_PIPE); AssertRC(rc); rc = RTPollSetDestroy(pDevUsbIp->hPollSet); AssertRC(rc); rc = RTPipeClose(pDevUsbIp->hPipeR); AssertRC(rc); rc = RTPipeClose(pDevUsbIp->hPipeW); AssertRC(rc); } if (pDevUsbIp->pszHost) RTStrFree(pDevUsbIp->pszHost); if (pDevUsbIp->pszBusId) RTStrFree(pDevUsbIp->pszBusId); /* Clear the URB lists. */ rc = RTSemFastMutexRequest(pDevUsbIp->hMtxLists); AssertRC(rc); PUSBPROXYURBUSBIP pIter; PUSBPROXYURBUSBIP pIterNext; RTListForEachSafe(&pDevUsbIp->ListUrbsInFlight, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) { RTListNodeRemove(&pIter->NodeList); RTMemFree(pIter); } RTListForEachSafe(&pDevUsbIp->ListUrbsLanded, pIter, pIterNext, USBPROXYURBUSBIP, NodeList) { RTListNodeRemove(&pIter->NodeList); RTMemFree(pIter); } RTSemFastMutexRelease(pDevUsbIp->hMtxLists); RTSemFastMutexDestroy(pDevUsbIp->hMtxLists); } /** * @interface_method_impl{USBPROXYBACK,pfnReset} */ static DECLCALLBACK(int) usbProxyUsbIpReset(PUSBPROXYDEV pProxyDev, bool fResetOnLinux) { LogFlowFunc(("pProxyDev = %p\n", pProxyDev)); int rc = VINF_SUCCESS; PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); VUSBSETUP Setup; if (fResetOnLinux) { Setup.bmRequestType = RT_BIT(5) | 0x03; /* Port request. */ Setup.bRequest = 0x03; /* SET_FEATURE */ Setup.wValue = 4; /* Port feature: Reset */ Setup.wIndex = 0; /* Port number, irrelevant */ Setup.wLength = 0; rc = usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); if (RT_SUCCESS(rc)) { pProxyDev->iActiveCfg = -1; pProxyDev->cIgnoreSetConfigs = 2; } } return rc; } /** * @interface_method_impl{USBPROXYBACK,pfnSetConfig} */ static DECLCALLBACK(int) usbProxyUsbIpSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) { LogFlowFunc(("pProxyDev=%s cfg=%#x\n", pProxyDev->pUsbIns->pszName, iCfg)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); VUSBSETUP Setup; Setup.bmRequestType = 0; Setup.bRequest = 0x09; Setup.wValue = iCfg; Setup.wIndex = 0; Setup.wLength = 0; return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); } /** * @interface_method_impl{USBPROXYBACK,pfnClaimInterface} */ static DECLCALLBACK(int) usbProxyUsbIpClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) { RT_NOREF(pProxyDev, iIf); LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); return VINF_SUCCESS; } /** * @interface_method_impl{USBPROXYBACK,pfnReleaseInterface} */ static DECLCALLBACK(int) usbProxyUsbIpReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) { RT_NOREF(pProxyDev, iIf); LogFlowFunc(("pProxyDev=%s iIf=%#x\n", pProxyDev->pUsbIns->pszName, iIf)); return VINF_SUCCESS; } /** * @interface_method_impl{USBPROXYBACK,pfnSetInterface} */ static DECLCALLBACK(int) usbProxyUsbIpSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int setting) { LogFlowFunc(("pProxyDev=%p iIf=%#x setting=%#x\n", pProxyDev, iIf, setting)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); VUSBSETUP Setup; Setup.bmRequestType = 0x1; Setup.bRequest = 0x0b; /* SET_INTERFACE */ Setup.wValue = setting; Setup.wIndex = iIf; Setup.wLength = 0; return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); } /** * @interface_method_impl{USBPROXYBACK,pfnClearHaltedEndpoint} */ static DECLCALLBACK(int) usbProxyUsbIpClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int iEp) { LogFlowFunc(("pProxyDev=%s ep=%u\n", pProxyDev->pUsbIns->pszName, iEp)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); VUSBSETUP Setup; Setup.bmRequestType = 0x2; Setup.bRequest = 0x01; /* CLEAR_FEATURE */ Setup.wValue = 0x00; /* ENDPOINT_HALT */ Setup.wIndex = iEp; Setup.wLength = 0; return usbProxyUsbIpCtrlUrbExchangeSync(pProxyDevUsbIp, &Setup); } /** * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} */ static DECLCALLBACK(int) usbProxyUsbIpUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) { LogFlowFunc(("pUrb=%p\n", pUrb)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); /* Allocate a USB/IP Urb. */ PUSBPROXYURBUSBIP pUrbUsbIp = usbProxyUsbIpUrbAlloc(pProxyDevUsbIp); if (!pUrbUsbIp) return VERR_NO_MEMORY; pUrbUsbIp->fCancelled = false; pUrbUsbIp->pVUsbUrb = pUrb; pUrb->Dev.pvPrivate = pUrbUsbIp; int rc = RTSemFastMutexRequest(pProxyDevUsbIp->hMtxLists); AssertRC(rc); RTListAppend(&pProxyDevUsbIp->ListUrbsToQueue, &pUrbUsbIp->NodeList); RTSemFastMutexRelease(pProxyDevUsbIp->hMtxLists); return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_QUEUE); } /** * @interface_method_impl{USBPROXYBACK,pfnUrbReap} */ static DECLCALLBACK(PVUSBURB) usbProxyUsbIpUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) { LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); PUSBPROXYURBUSBIP pUrbUsbIp = NULL; PVUSBURB pUrb = NULL; int rc = VINF_SUCCESS; /* Queue new URBs first. */ rc = usbProxyUsbIpUrbsQueuePending(pProxyDevUsbIp); AssertRC(rc); /* Any URBs pending delivery? */ if (!RTListIsEmpty(&pProxyDevUsbIp->ListUrbsLanded)) pUrbUsbIp = RTListGetFirst(&pProxyDevUsbIp->ListUrbsLanded, USBPROXYURBUSBIP, NodeList); else pUrbUsbIp = usbProxyUsbIpPollWorker(pProxyDevUsbIp, 0, true /*fPollWakePipe*/, cMillies); if (pUrbUsbIp) { pUrb = pUrbUsbIp->pVUsbUrb; pUrb->enmStatus = pUrbUsbIp->enmStatus; /* unlink from the pending delivery list */ usbProxyUsbIpUnlinkUrb(pProxyDevUsbIp, pUrbUsbIp); usbProxyUsbIpUrbFree(pProxyDevUsbIp, pUrbUsbIp); } return pUrb; } /** * @interface_method_impl{USBPROXYBACK,pfnUrbCancel} */ static DECLCALLBACK(int) usbProxyUsbIpUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) { LogFlowFunc(("pUrb=%p\n", pUrb)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); PUSBPROXYURBUSBIP pUrbUsbIp = (PUSBPROXYURBUSBIP)pUrb->Dev.pvPrivate; UsbIpReqUnlink ReqUnlink; RT_ZERO(ReqUnlink); uint32_t u32SeqNum = usbProxyUsbIpSeqNumGet(pProxyDevUsbIp); ReqUnlink.Hdr.u32ReqRet = USBIP_CMD_UNLINK; ReqUnlink.Hdr.u32SeqNum = u32SeqNum; ReqUnlink.Hdr.u32DevId = pProxyDevUsbIp->u32DevId; ReqUnlink.Hdr.u32Direction = USBIP_DIR_OUT; ReqUnlink.Hdr.u32Endpoint = pUrb->EndPt; ReqUnlink.u32SeqNum = pUrbUsbIp->u32SeqNumUrb; usbProxyUsbIpReqUnlinkH2N(&ReqUnlink); int rc = RTTcpWrite(pProxyDevUsbIp->hSocket, &ReqUnlink, sizeof(ReqUnlink)); if (RT_SUCCESS(rc)) { pUrbUsbIp->u32SeqNumUrbUnlink = u32SeqNum; pUrbUsbIp->fCancelled = true; } return rc; } /** * @interface_method_impl{USBPROXYBACK,pfnWakeup} */ static DECLCALLBACK(int) usbProxyUsbIpWakeup(PUSBPROXYDEV pProxyDev) { LogFlowFunc(("pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); PUSBPROXYDEVUSBIP pProxyDevUsbIp = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVUSBIP); return usbProxyReaperKick(pProxyDevUsbIp, USBIP_REAPER_WAKEUP_REASON_EXTERNAL); } /** * The USB/IP USB Proxy Backend operations. */ extern const USBPROXYBACK g_USBProxyDeviceUsbIp = { /* pszName */ "usbip", /* cbBackend */ sizeof(USBPROXYDEVUSBIP), usbProxyUsbIpOpen, NULL, usbProxyUsbIpClose, usbProxyUsbIpReset, usbProxyUsbIpSetConfig, usbProxyUsbIpClaimInterface, usbProxyUsbIpReleaseInterface, usbProxyUsbIpSetInterface, usbProxyUsbIpClearHaltedEp, usbProxyUsbIpUrbQueue, usbProxyUsbIpUrbCancel, usbProxyUsbIpUrbReap, usbProxyUsbIpWakeup, 0 };