diff options
Diffstat (limited to 'src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp')
-rw-r--r-- | src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp | 1068 |
1 files changed, 1068 insertions, 0 deletions
diff --git a/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp new file mode 100644 index 00000000..8b694639 --- /dev/null +++ b/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp @@ -0,0 +1,1068 @@ +/* $Id: USBProxyDevice-freebsd.cpp $ */ +/** @file + * USB device proxy - the FreeBSD backend. + */ + +/* + * Includes contributions from Hans Petter Selasky + * + * Copyright (C) 2006-2022 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#ifdef VBOX +# include <iprt/stdint.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/ioctl.h> +#include <sys/poll.h> +#include <stdint.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <limits.h> +#include <unistd.h> +#include <fcntl.h> +#include <errno.h> +#include <dev/usb/usb.h> +#include <dev/usb/usbdi.h> +#include <dev/usb/usb_ioctl.h> + +#include <VBox/vmm/pdm.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <VBox/vusb.h> +#include <iprt/assert.h> +#include <iprt/stream.h> +#include <iprt/alloc.h> +#include <iprt/thread.h> +#include <iprt/time.h> +#include <iprt/asm.h> +#include <iprt/string.h> +#include <iprt/file.h> +#include <iprt/pipe.h> +#include "../USBProxyDevice.h" + +/** Maximum endpoints supported. */ +#define USBFBSD_MAXENDPOINTS 127 +#define USBFBSD_MAXFRAMES 56 + +/** This really needs to be defined in vusb.h! */ +#ifndef VUSB_DIR_TO_DEV +# define VUSB_DIR_TO_DEV 0x00 +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct USBENDPOINTFBSD +{ + /** Flag whether it is opened. */ + bool fOpen; + /** Flag whether it is cancelling. */ + bool fCancelling; + /** Buffer pointers. */ + void *apvData[USBFBSD_MAXFRAMES]; + /** Buffer lengths. */ + uint32_t acbData[USBFBSD_MAXFRAMES]; + /** Initial buffer length. */ + uint32_t cbData0; + /** Pointer to the URB. */ + PVUSBURB pUrb; + /** Copy of endpoint number. */ + unsigned iEpNum; + /** Maximum transfer length. */ + unsigned cMaxIo; + /** Maximum frame count. */ + unsigned cMaxFrames; +} USBENDPOINTFBSD, *PUSBENDPOINTFBSD; + +/** + * Data for the FreeBSD usb proxy backend. + */ +typedef struct USBPROXYDEVFBSD +{ + /** The open file. */ + RTFILE hFile; + /** Flag whether an URB is cancelling. */ + bool fCancelling; + /** Flag whether initialised or not */ + bool fInit; + /** Pipe handle for waking up - writing end. */ + RTPIPE hPipeWakeupW; + /** Pipe handle for waking up - reading end. */ + RTPIPE hPipeWakeupR; + /** Software endpoint structures */ + USBENDPOINTFBSD aSwEndpoint[USBFBSD_MAXENDPOINTS]; + /** Kernel endpoint structures */ + struct usb_fs_endpoint aHwEndpoint[USBFBSD_MAXENDPOINTS]; +} USBPROXYDEVFBSD, *PUSBPROXYDEVFBSD; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint); + +/** + * 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 ENXIO. + * @internal + */ +static int usbProxyFreeBSDDoIoCtl(PUSBPROXYDEV pProxyDev, unsigned long iCmd, + void *pvArg, bool fHandleNoDev) +{ + int rc = VINF_SUCCESS; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDDoIoCtl: iCmd=%#x\n", iCmd)); + + do + { + rc = ioctl(RTFileToNative(pDevFBSD->hFile), iCmd, pvArg); + if (rc >= 0) + return VINF_SUCCESS; + } while (errno == EINTR); + + if (errno == ENXIO && fHandleNoDev) + { + Log(("usbProxyFreeBSDDoIoCtl: ENXIO -> unplugged. pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + errno = ENODEV; + } + else if (errno != EAGAIN) + { + LogFlow(("usbProxyFreeBSDDoIoCtl: Returned %d. pProxyDev=%s\n", + errno, pProxyDev->pUsbIns->pszName)); + } + return RTErrConvertFromErrno(errno); +} + +/** + * Init USB subsystem. + */ +static int usbProxyFreeBSDFsInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_init UsbFsInit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsInit: pProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit == true) + return VINF_SUCCESS; + + /* Zero default */ + memset(&UsbFsInit, 0, sizeof(UsbFsInit)); + + UsbFsInit.pEndpoints = pDevFBSD->aHwEndpoint; + UsbFsInit.ep_index_max = USBFBSD_MAXENDPOINTS; + + /* Init USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_INIT, &UsbFsInit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = true; + + return rc; +} + +/** + * Uninit USB subsystem. + */ +static int usbProxyFreeBSDFsUnInit(PUSBPROXYDEV pProxyDev) +{ + struct usb_fs_uninit UsbFsUninit; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDFsUnInit: ProxyDev=%p\n", (void *)pProxyDev)); + + /* Sanity check */ + AssertPtrReturn(pDevFBSD, VERR_INVALID_PARAMETER); + + if (pDevFBSD->fInit != true) + return VINF_SUCCESS; + + /* Close any open endpoints. */ + for (unsigned n = 0; n != USBFBSD_MAXENDPOINTS; n++) + usbProxyFreeBSDEndpointClose(pProxyDev, n); + + /* Zero default */ + memset(&UsbFsUninit, 0, sizeof(UsbFsUninit)); + + /* Uninit USB subsystem */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_UNINIT, &UsbFsUninit, false); + if (RT_SUCCESS(rc)) + pDevFBSD->fInit = false; + + return rc; +} + +/** + * Setup a USB request packet. + */ +static void usbProxyFreeBSDSetupReq(struct usb_device_request *pSetupData, + uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, + uint16_t wIndex, uint16_t wLength) +{ + LogFlow(("usbProxyFreeBSDSetupReq: pSetupData=%p bmRequestType=%x " + "bRequest=%x wValue=%x wIndex=%x wLength=%x\n", (void *)pSetupData, + bmRequestType, bRequest, wValue, wIndex, wLength)); + + pSetupData->bmRequestType = bmRequestType; + pSetupData->bRequest = bRequest; + + /* Handle endianess here. Currently no swapping is needed. */ + pSetupData->wValue[0] = wValue & 0xff; + pSetupData->wValue[1] = (wValue >> 8) & 0xff; + pSetupData->wIndex[0] = wIndex & 0xff; + pSetupData->wIndex[1] = (wIndex >> 8) & 0xff; + pSetupData->wLength[0] = wLength & 0xff; + pSetupData->wLength[1] = (wLength >> 8) & 0xff; +} + +static int usbProxyFreeBSDEndpointOpen(PUSBPROXYDEV pProxyDev, int Endpoint, bool fIsoc, int index) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = NULL; /* shut up gcc */ + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_open UsbFsOpen; + int rc; + + LogFlow(("usbProxyFreeBSDEndpointOpen: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + for (; index < USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if ( pEndpointFBSD->fOpen + && !pEndpointFBSD->pUrb + && (int)pEndpointFBSD->iEpNum == Endpoint) + return index; + } + + if (index == USBFBSD_MAXENDPOINTS) + { + for (index = 0; index != USBFBSD_MAXENDPOINTS; index++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + if (pEndpointFBSD->fCancelling) + continue; + if (!pEndpointFBSD->fOpen) + break; + } + if (index == USBFBSD_MAXENDPOINTS) + return -1; + } + /* set ppBuffer and pLength */ + + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + pXferEndpoint->ppBuffer = &pEndpointFBSD->apvData[0]; + pXferEndpoint->pLength = &pEndpointFBSD->acbData[0]; + + LogFlow(("usbProxyFreeBSDEndpointOpen: ep_index=%d ep_num=%d\n", + index, Endpoint)); + + memset(&UsbFsOpen, 0, sizeof(UsbFsOpen)); + + UsbFsOpen.ep_index = index; + UsbFsOpen.ep_no = Endpoint; + UsbFsOpen.max_bufsize = 256 * 1024; + /* Hardcoded assumption about the URBs we get. */ + + UsbFsOpen.max_frames = fIsoc ? USBFBSD_MAXFRAMES : 2; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_OPEN, &UsbFsOpen, true); + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + LogFlow(("usbProxyFreeBSDEndpointOpen: EBUSY\n")); + + return -1; + } + pEndpointFBSD->fOpen = true; + pEndpointFBSD->pUrb = NULL; + pEndpointFBSD->iEpNum = Endpoint; + pEndpointFBSD->cMaxIo = UsbFsOpen.max_bufsize; + pEndpointFBSD->cMaxFrames = UsbFsOpen.max_frames; + + return index; +} + +/** + * Close an endpoint. + * + * @returns VBox status code. + */ +static int usbProxyFreeBSDEndpointClose(PUSBPROXYDEV pProxyDev, int Endpoint) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD = &pDevFBSD->aSwEndpoint[Endpoint]; + struct usb_fs_close UsbFsClose; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDEndpointClose: pProxyDev=%p Endpoint=%d\n", + (void *)pProxyDev, Endpoint)); + + /* check for cancelling */ + if (pEndpointFBSD->pUrb != NULL) + { + pEndpointFBSD->fCancelling = true; + pDevFBSD->fCancelling = true; + } + + /* check for opened */ + if (pEndpointFBSD->fOpen) + { + pEndpointFBSD->fOpen = false; + + /* Zero default */ + memset(&UsbFsClose, 0, sizeof(UsbFsClose)); + + /* Set endpoint index */ + UsbFsClose.ep_index = Endpoint; + + /* Close endpoint */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_CLOSE, &UsbFsClose, true); + } + return rc; +} + +/** + * 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:<sysfs path>//device:<device node>". + * In the second case, the two paths are guaranteed + * not to contain the substring "//". + */ +static DECLCALLBACK(int) usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress)); + + /* + * Try open the device node. + */ + RTFILE hFile; + rc = RTFileOpen(&hFile, pszAddress, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + /* + * Initialize the FreeBSD backend data. + */ + pDevFBSD->hFile = hFile; + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_SUCCESS(rc)) + { + /* + * Create wakeup pipe. + */ + rc = RTPipeCreate(&pDevFBSD->hPipeWakeupR, &pDevFBSD->hPipeWakeupW, 0); + if (RT_SUCCESS(rc)) + { + LogFlow(("usbProxyFreeBSDOpen(%p, %s): returns successfully hFile=%RTfile iActiveCfg=%d\n", + pProxyDev, pszAddress, pDevFBSD->hFile, pProxyDev->iActiveCfg)); + + return VINF_SUCCESS; + } + } + + RTFileClose(hFile); + } + else if (rc == VERR_ACCESS_DENIED) + rc = VERR_VUSB_USBFS_PERMISSION; + + Log(("usbProxyFreeBSDOpen(%p, %s) failed, rc=%d!\n", + pProxyDev, pszAddress, rc)); + + return rc; +} + + +/** + * Claims all the interfaces and figures out the + * current configuration. + * + * @returns VINF_SUCCESS. + * @param pProxyDev The proxy device. + */ +static DECLCALLBACK(int) usbProxyFreeBSDInit(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int rc; + + LogFlow(("usbProxyFreeBSDInit: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_GET_CONFIG, + &pProxyDev->iActiveCfg, true); + if (RT_FAILURE(rc) || pProxyDev->iActiveCfg == 255) + { + pProxyDev->cIgnoreSetConfigs = 0; + pProxyDev->iActiveCfg = -1; + } + else + { + pProxyDev->cIgnoreSetConfigs = 1; + pProxyDev->iActiveCfg++; + } + + Log(("usbProxyFreeBSDInit: iActiveCfg=%d\n", pProxyDev->iActiveCfg)); + + return rc; +} + +/** + * Closes the proxy device. + */ +static DECLCALLBACK(void) usbProxyFreeBSDClose(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + + LogFlow(("usbProxyFreeBSDClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName)); + + /* sanity check */ + AssertPtrReturnVoid(pDevFBSD); + + usbProxyFreeBSDFsUnInit(pProxyDev); + + RTPipeClose(pDevFBSD->hPipeWakeupR); + RTPipeClose(pDevFBSD->hPipeWakeupW); + + RTFileClose(pDevFBSD->hFile); + pDevFBSD->hFile = NIL_RTFILE; + + LogFlow(("usbProxyFreeBSDClose: returns\n")); +} + +/** + * Reset a device. + * + * @returns VBox status code. + * @param pDev The device to reset. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReset(PUSBPROXYDEV pProxyDev, bool fResetOnFreeBSD) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iParm; + int rc = VINF_SUCCESS; + + LogFlow(("usbProxyFreeBSDReset: pProxyDev=%s\n", + pProxyDev->pUsbIns->pszName)); + + if (!fResetOnFreeBSD) + goto done; + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Resetting is only possible as super-user, ignore any failures: */ + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DEVICEENUMERATE, &iParm, true); + if (RT_FAILURE(rc)) + { + /* Set the config instead of bus reset */ + iParm = 255; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + if (RT_SUCCESS(rc)) + { + iParm = 0; + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iParm, true); + } + } + usleep(10000); /* nice it! */ + + /* Allocate kernel ressources again. */ + rc = usbProxyFreeBSDFsInit(pProxyDev); + if (RT_FAILURE(rc)) + goto done; + + /* Retrieve current active configuration. */ + rc = usbProxyFreeBSDInit(pProxyDev); + +done: + pProxyDev->cIgnoreSetConfigs = 2; + + return rc; +} + +/** + * 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) usbProxyFreeBSDSetConfig(PUSBPROXYDEV pProxyDev, int iCfg) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + int iCfgIndex; + int rc; + + LogFlow(("usbProxyFreeBSDSetConfig: pProxyDev=%s cfg=%x\n", + pProxyDev->pUsbIns->pszName, iCfg)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + + if (iCfg == 0) + { + /* Unconfigure */ + iCfgIndex = 255; + } + else + { + /* Get the configuration index matching the value. */ + for (iCfgIndex = 0; iCfgIndex < pProxyDev->DevDesc.bNumConfigurations; iCfgIndex++) + { + if (pProxyDev->paCfgDescs[iCfgIndex].Core.bConfigurationValue == iCfg) + break; + } + + if (iCfgIndex == pProxyDev->DevDesc.bNumConfigurations) + { + LogFlow(("usbProxyFreeBSDSetConfig: configuration " + "%d not found\n", iCfg)); + return VERR_NOT_FOUND; + } + } + + /* Set the config */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iCfgIndex, true); + if (RT_FAILURE(rc)) + return rc; + + /* Allocate kernel ressources again. */ + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Claims an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClaimInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDClaimInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + /* + * Try to detach kernel driver on this interface, ignore any + * failures + */ + usbProxyFreeBSDDoIoCtl(pProxyDev, USB_IFACE_DRIVER_DETACH, &iIf, true); + + /* Try to claim interface */ + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_CLAIM_INTERFACE, &iIf, true); +} + +/** + * Releases an interface. + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf) +{ + int rc; + + LogFlow(("usbProxyFreeBSDReleaseInterface: pProxyDev=%s " + "ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf)); + + return usbProxyFreeBSDDoIoCtl(pProxyDev, USB_RELEASE_INTERFACE, &iIf, true); +} + +/** + * SET_INTERFACE. + * + * @returns success indicator. + */ +static DECLCALLBACK(int) usbProxyFreeBSDSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + struct usb_alt_interface UsbIntAlt; + int rc; + + LogFlow(("usbProxyFreeBSDSetInterface: pProxyDev=%p iIf=%x iAlt=%x\n", + pProxyDev, iIf, iAlt)); + + /* We need to release kernel ressources first. */ + rc = usbProxyFreeBSDFsUnInit(pProxyDev); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Freeing kernel resources " + "failed failed rc=%d\n", rc)); + return rc; + } + memset(&UsbIntAlt, 0, sizeof(UsbIntAlt)); + UsbIntAlt.uai_interface_index = iIf; + UsbIntAlt.uai_alt_index = iAlt; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_ALTINTERFACE, &UsbIntAlt, true); + if (RT_FAILURE(rc)) + { + LogFlow(("usbProxyFreeBSDSetInterface: Setting interface %d %d " + "failed rc=%d\n", iIf, iAlt, rc)); + return rc; + } + + return usbProxyFreeBSDFsInit(pProxyDev); +} + +/** + * Clears the halted endpoint 'ep_num'. + */ +static DECLCALLBACK(int) usbProxyFreeBSDClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep_num) +{ + LogFlow(("usbProxyFreeBSDClearHaltedEp: pProxyDev=%s ep_num=%u\n", + pProxyDev->pUsbIns->pszName, ep_num)); + + /* + * Clearing the zero control pipe doesn't make sense. + * Just ignore it. + */ + if ((ep_num & 0xF) == 0) + return VINF_SUCCESS; + + struct usb_ctl_request Req; + memset(&Req, 0, sizeof(Req)); + usbProxyFreeBSDSetupReq(&Req.ucr_request, + VUSB_DIR_TO_DEV | VUSB_TO_ENDPOINT, + VUSB_REQ_CLEAR_FEATURE, 0, ep_num, 0); + + int rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DO_REQUEST, &Req, true); + + LogFlow(("usbProxyFreeBSDClearHaltedEp: rc=%Rrc\n", rc)); + return rc; +} + +/** + * @interface_method_impl{USBPROXYBACK,pfnUrbQueue} + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbQueue(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + struct usb_fs_endpoint *pXferEndpoint; + struct usb_fs_start UsbFsStart; + unsigned cFrames; + uint8_t *pbData; + int index; + int ep_num; + int rc; + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb=%p EndPt=%u Dir=%u\n", + pUrb, (unsigned)pUrb->EndPt, (unsigned)pUrb->enmDir)); + + ep_num = pUrb->EndPt; + if ((pUrb->enmType != VUSBXFERTYPE_MSG) && (pUrb->enmDir == VUSBDIRECTION_IN)) { + /* set IN-direction bit */ + ep_num |= 0x80; + } + + index = 0; + +retry: + + index = usbProxyFreeBSDEndpointOpen(pProxyDev, ep_num, + (pUrb->enmType == VUSBXFERTYPE_ISOC), + index); + + if (index < 0) + return VERR_INVALID_PARAMETER; + + pEndpointFBSD = &pDevFBSD->aSwEndpoint[index]; + pXferEndpoint = &pDevFBSD->aHwEndpoint[index]; + + pbData = pUrb->abData; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->acbData[0] = 8; + + /* check wLength */ + if (pbData[6] || pbData[7]) + { + pEndpointFBSD->apvData[1] = pbData + 8; + pEndpointFBSD->acbData[1] = pbData[6] | (pbData[7] << 8); + cFrames = 2; + } + else + { + pEndpointFBSD->apvData[1] = NULL; + pEndpointFBSD->acbData[1] = 0; + cFrames = 1; + } + + LogFlow(("usbProxyFreeBSDUrbQueue: pUrb->cbData=%u, 0x%02x, " + "0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x\n", + pUrb->cbData, pbData[0], pbData[1], pbData[2], pbData[3], + pbData[4], pbData[5], pbData[6], pbData[7])); + + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + break; + } + case VUSBXFERTYPE_ISOC: + { + unsigned i; + + for (i = 0; i < pUrb->cIsocPkts; i++) + { + if (i >= pEndpointFBSD->cMaxFrames) + break; + pEndpointFBSD->apvData[i] = pbData + pUrb->aIsocPkts[i].off; + pEndpointFBSD->acbData[i] = pUrb->aIsocPkts[i].cb; + } + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = i; + break; + } + default: + { + pEndpointFBSD->apvData[0] = pbData; + pEndpointFBSD->cbData0 = pUrb->cbData; + + /* XXX maybe we have to loop */ + if (pUrb->cbData > pEndpointFBSD->cMaxIo) + pEndpointFBSD->acbData[0] = pEndpointFBSD->cMaxIo; + else + pEndpointFBSD->acbData[0] = pUrb->cbData; + + /* Timeout handling will be done during reap. */ + pXferEndpoint->timeout = USB_FS_TIMEOUT_NONE; + pXferEndpoint->flags = pUrb->fShortNotOk ? 0 : USB_FS_FLAG_MULTI_SHORT_OK; + cFrames = 1; + break; + } + } + + /* store number of frames */ + pXferEndpoint->nFrames = cFrames; + + /* zero-default */ + memset(&UsbFsStart, 0, sizeof(UsbFsStart)); + + /* Start the transfer */ + UsbFsStart.ep_index = index; + + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_START, &UsbFsStart, true); + + LogFlow(("usbProxyFreeBSDUrbQueue: USB_FS_START returned rc=%d " + "len[0]=%u len[1]=%u cbData=%u index=%u ep_num=%u\n", rc, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1], + (unsigned)pUrb->cbData, + (unsigned)index, (unsigned)ep_num)); + + if (RT_FAILURE(rc)) + { + if (rc == VERR_RESOURCE_BUSY) + { + index++; + goto retry; + } + return rc; + } + pUrb->Dev.pvPrivate = (void *)(long)(index + 1); + pEndpointFBSD->pUrb = pUrb; + + return rc; +} + +/** + * 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) usbProxyFreeBSDUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies) +{ + struct usb_fs_endpoint *pXferEndpoint; + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + PUSBENDPOINTFBSD pEndpointFBSD; + PVUSBURB pUrb; + struct usb_fs_complete UsbFsComplete; + struct pollfd pfd[2]; + int rc; + + LogFlow(("usbProxyFreeBSDUrbReap: pProxyDev=%p, cMillies=%u\n", + pProxyDev, cMillies)); + +repeat: + + pUrb = NULL; + + /* check for cancelled transfers */ + if (pDevFBSD->fCancelling) + { + for (unsigned n = 0; n < USBFBSD_MAXENDPOINTS; n++) + { + pEndpointFBSD = &pDevFBSD->aSwEndpoint[n]; + if (pEndpointFBSD->fCancelling) + { + pEndpointFBSD->fCancelling = false; + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + + if (pUrb != NULL) + break; + } + } + + if (pUrb != NULL) + { + pUrb->enmStatus = VUSBSTATUS_INVALID; + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = 0; + break; + case VUSBXFERTYPE_ISOC: + pUrb->cbData = 0; + for (int n = 0; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + break; + default: + pUrb->cbData = 0; + break; + } + return pUrb; + } + pDevFBSD->fCancelling = false; + } + /* Zero default */ + + memset(&UsbFsComplete, 0, sizeof(UsbFsComplete)); + + /* Check if any endpoints are complete */ + rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_FS_COMPLETE, &UsbFsComplete, true); + if (RT_SUCCESS(rc)) + { + pXferEndpoint = &pDevFBSD->aHwEndpoint[UsbFsComplete.ep_index]; + pEndpointFBSD = &pDevFBSD->aSwEndpoint[UsbFsComplete.ep_index]; + + LogFlow(("usbProxyFreeBSDUrbReap: Reaped " + "URB %#p\n", pEndpointFBSD->pUrb)); + + if (pXferEndpoint->status == USB_ERR_CANCELLED) + goto repeat; + + pUrb = pEndpointFBSD->pUrb; + pEndpointFBSD->pUrb = NULL; + if (pUrb == NULL) + goto repeat; + + switch (pXferEndpoint->status) + { + case USB_ERR_NORMAL_COMPLETION: + pUrb->enmStatus = VUSBSTATUS_OK; + break; + case USB_ERR_STALLED: + pUrb->enmStatus = VUSBSTATUS_STALL; + break; + default: + pUrb->enmStatus = VUSBSTATUS_INVALID; + break; + } + + pUrb->Dev.pvPrivate = NULL; + + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + pUrb->cbData = pEndpointFBSD->acbData[0] + pEndpointFBSD->acbData[1]; + break; + case VUSBXFERTYPE_ISOC: + { + int n; + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + break; + pUrb->cbData = 0; + for (n = 0; n < (int)pUrb->cIsocPkts; n++) + { + if (n >= (int)pEndpointFBSD->cMaxFrames) + break; + pUrb->cbData += pEndpointFBSD->acbData[n]; + pUrb->aIsocPkts[n].cb = pEndpointFBSD->acbData[n]; + } + for (; n < (int)pUrb->cIsocPkts; n++) + pUrb->aIsocPkts[n].cb = 0; + + break; + } + default: + pUrb->cbData = pEndpointFBSD->acbData[0]; + break; + } + + LogFlow(("usbProxyFreeBSDUrbReap: Status=%d epindex=%u " + "len[0]=%d len[1]=%d\n", + (int)pXferEndpoint->status, + (unsigned)UsbFsComplete.ep_index, + (unsigned)pEndpointFBSD->acbData[0], + (unsigned)pEndpointFBSD->acbData[1])); + + } + else if (cMillies != 0 && rc == VERR_RESOURCE_BUSY) + { + for (;;) + { + pfd[0].fd = RTFileToNative(pDevFBSD->hFile); + pfd[0].events = POLLIN | POLLRDNORM; + pfd[0].revents = 0; + + pfd[1].fd = RTPipeToNative(pDevFBSD->hPipeWakeupR); + pfd[1].events = POLLIN | POLLRDNORM; + pfd[1].revents = 0; + + rc = poll(pfd, 2, (cMillies == RT_INDEFINITE_WAIT) ? INFTIM : cMillies); + if (rc > 0) + { + if (pfd[1].revents & POLLIN) + { + /* Got woken up, drain pipe. */ + uint8_t bRead; + size_t cbIgnored = 0; + RTPipeRead(pDevFBSD->hPipeWakeupR, &bRead, 1, &cbIgnored); + /* Make sure we return from this function */ + cMillies = 0; + } + break; + } + if (rc == 0) + return NULL; + if (errno != EAGAIN) + return NULL; + } + goto repeat; + } + return pUrb; +} + +/** + * Cancels the URB. + * The URB requires reaping, so we don't change its state. + */ +static DECLCALLBACK(int) usbProxyFreeBSDUrbCancel(PUSBPROXYDEV pProxyDev, PVUSBURB pUrb) +{ + int index; + + index = (int)(long)pUrb->Dev.pvPrivate - 1; + + if (index < 0 || index >= USBFBSD_MAXENDPOINTS) + return VINF_SUCCESS; /* invalid index, pretend success. */ + + LogFlow(("usbProxyFreeBSDUrbCancel: epindex=%u\n", (unsigned)index)); + return usbProxyFreeBSDEndpointClose(pProxyDev, index); +} + +static DECLCALLBACK(int) usbProxyFreeBSDWakeup(PUSBPROXYDEV pProxyDev) +{ + PUSBPROXYDEVFBSD pDevFBSD = USBPROXYDEV_2_DATA(pProxyDev, PUSBPROXYDEVFBSD); + size_t cbIgnored; + + LogFlowFunc(("pProxyDev=%p\n", pProxyDev)); + + return RTPipeWrite(pDevFBSD->hPipeWakeupW, "", 1, &cbIgnored); +} + +/** + * The FreeBSD USB Proxy Backend. + */ +extern const USBPROXYBACK g_USBProxyDeviceHost = +{ + /* pszName */ + "host", + /* cbBackend */ + sizeof(USBPROXYDEVFBSD), + usbProxyFreeBSDOpen, + usbProxyFreeBSDInit, + usbProxyFreeBSDClose, + usbProxyFreeBSDReset, + usbProxyFreeBSDSetConfig, + usbProxyFreeBSDClaimInterface, + usbProxyFreeBSDReleaseInterface, + usbProxyFreeBSDSetInterface, + usbProxyFreeBSDClearHaltedEp, + usbProxyFreeBSDUrbQueue, + usbProxyFreeBSDUrbCancel, + usbProxyFreeBSDUrbReap, + usbProxyFreeBSDWakeup, + 0 +}; + +/* + * Local Variables: + * mode: c + * c-file-style: "bsd" + * c-basic-offset: 4 + * tab-width: 4 + * indent-tabs-mode: s + * End: + */ + |