summaryrefslogtreecommitdiffstats
path: root/src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp')
-rw-r--r--src/VBox/Devices/USB/freebsd/USBProxyDevice-freebsd.cpp1068
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..8119eb42
--- /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-2023 Oracle and/or its affiliates.
+ *
+ * This file is part of VirtualBox base platform packages, as
+ * available from https://www.virtualbox.org.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation, in version 3 of the
+ * License.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses>.
+ *
+ * SPDX-License-Identifier: GPL-3.0-only
+ */
+
+
+/*********************************************************************************************************************************
+* Header Files *
+*********************************************************************************************************************************/
+#define LOG_GROUP LOG_GROUP_DRV_USBPROXY
+#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:
+ */
+