USBProxyDevice-freebsd.cpp revision 4f3d37f3c8ea851c3d57304fac430764b77a84dc
/* $Id$ */
/** @file
* USB device proxy - the FreeBSD backend.
*/
/*
* Copyright (C) 2006-2007 Oracle Corporation
* Copyright (C) 2010 Hans Petter Selasky
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
/*******************************************************************************
* 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/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 "../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 File;
/** Software endpoint structures */
USBENDPOINTFBSD aSwEndpoint[USBFBSD_MAXENDPOINTS];
/** Flag whether an URB is cancelling. */
bool fCancelling;
/** Flag whether initialised or not */
bool fInit;
/** 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 = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
LogFlow(("usbProxyFreeBSDDoIoCtl: iCmd=%#x\n", iCmd));
do
{
rc = ioctl(pDevFBSD->File, 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 = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
PUSBENDPOINTFBSD pEndpointFBSD;
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 = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 "//".
* @param pvBackend Backend specific pointer, unused for the linux backend.
*/
static int usbProxyFreeBSDOpen(PUSBPROXYDEV pProxyDev, const char *pszAddress,
void *pvBackend)
{
int rc;
LogFlow(("usbProxyFreeBSDOpen: pProxyDev=%p pszAddress=%s\n", pProxyDev, pszAddress));
/*
* Try open the device node.
*/
RTFILE File;
rc = RTFileOpen(&File, pszAddress, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE);
if (RT_SUCCESS(rc))
{
/*
* Allocate and initialize the linux backend data.
*/
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD)
RTMemAllocZ(sizeof(USBPROXYDEVFBSD));
if (pDevFBSD)
{
pDevFBSD->File = File;
pProxyDev->Backend.pv = pDevFBSD;
rc = usbProxyFreeBSDFsInit(pProxyDev);
if (RT_SUCCESS(rc))
{
LogFlow(("usbProxyFreeBSDOpen(%p, %s): returns "
"successfully File=%d iActiveCfg=%d\n",
pProxyDev, pszAddress,
pDevFBSD->File, pProxyDev->iActiveCfg));
return VINF_SUCCESS;
}
RTMemFree(pDevFBSD);
}
else
rc = VERR_NO_MEMORY;
RTFileClose(File);
}
else if (rc == VERR_ACCESS_DENIED)
rc = VERR_VUSB_USBFS_PERMISSION;
Log(("usbProxyFreeBSDOpen(%p, %s) failed, rc=%d!\n",
pProxyDev, pszAddress, rc));
pProxyDev->Backend.pv = NULL;
NOREF(pvBackend);
return rc;
}
/**
* Claims all the interfaces and figures out the
* current configuration.
*
* @returns VINF_SUCCESS.
* @param pProxyDev The proxy device.
*/
static int usbProxyFreeBSDInit(PUSBPROXYDEV pProxyDev)
{
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 void usbProxyFreeBSDClose(PUSBPROXYDEV pProxyDev)
{
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
LogFlow(("usbProxyFreeBSDClose: pProxyDev=%s\n", pProxyDev->pUsbIns->pszName));
/* sanity check */
AssertPtrReturnVoid(pDevFBSD);
usbProxyFreeBSDFsUnInit(pProxyDev);
RTFileClose(pDevFBSD->File);
pDevFBSD->File = NIL_RTFILE;
RTMemFree(pDevFBSD);
pProxyDev->Backend.pv = NULL;
LogFlow(("usbProxyFreeBSDClose: returns\n"));
}
/**
* Reset a device.
*
* @returns VBox status code.
* @param pDev The device to reset.
*/
static int usbProxyFreeBSDReset(PUSBPROXYDEV pProxyDev, bool fResetOnFreeBSD)
{
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 int usbProxyFreeBSDSetConfig(PUSBPROXYDEV pProxyDev, int iCfg)
{
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 false;
}
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 false;
}
}
/* Set the config */
rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_SET_CONFIG, &iCfgIndex, true);
if (RT_FAILURE(rc))
return false;
/* Allocate kernel ressources again. */
rc = usbProxyFreeBSDFsInit(pProxyDev);
if (RT_FAILURE(rc))
return false;
return true;
}
/**
* Claims an interface.
* @returns success indicator.
*/
static 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
*/
rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_IFACE_DRIVER_DETACH, &iIf, true);
/* Try to claim interface */
rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_CLAIM_INTERFACE, &iIf, true);
if (RT_FAILURE(rc))
return false;
return true;
}
/**
* Releases an interface.
* @returns success indicator.
*/
static int usbProxyFreeBSDReleaseInterface(PUSBPROXYDEV pProxyDev, int iIf)
{
int rc;
LogFlow(("usbProxyFreeBSDReleaseInterface: pProxyDev=%s "
"ifnum=%x\n", pProxyDev->pUsbIns->pszName, iIf));
rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_RELEASE_INTERFACE, &iIf, true);
if (RT_FAILURE(rc))
return false;
return true;
}
/**
* SET_INTERFACE.
*
* @returns success indicator.
*/
static int
usbProxyFreeBSDSetInterface(PUSBPROXYDEV pProxyDev, int iIf, int iAlt)
{
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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 false;
}
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 false;
}
rc = usbProxyFreeBSDFsInit(pProxyDev);
if (RT_FAILURE(rc))
return false;
return true;
}
/**
* Clears the halted endpoint 'ep_num'.
*/
static bool usbProxyFreeBSDClearHaltedEp(PUSBPROXYDEV pProxyDev, unsigned int ep_num)
{
struct usb_ctl_request Req;
int rc;
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 true;
memset(&Req, 0, sizeof(Req));
usbProxyFreeBSDSetupReq(&Req.ucr_request,
VUSB_DIR_TO_DEV | VUSB_TO_ENDPOINT,
VUSB_REQ_CLEAR_FEATURE, 0, ep_num, 0);
rc = usbProxyFreeBSDDoIoCtl(pProxyDev, USB_DO_REQUEST, &Req, true);
LogFlow(("usbProxyFreeBSDClearHaltedEp: rc=%Rrc\n", rc));
if (RT_FAILURE(rc))
return false;
return true;
}
/**
* @copydoc USBPROXYBACK::pfnUrbQueue
*/
static int usbProxyFreeBSDUrbQueue(PVUSBURB pUrb)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUrb->pUsbIns, PUSBPROXYDEV);
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
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))
ep_num |= 0x80;
index = 0;
retry:
index = usbProxyFreeBSDEndpointOpen(pProxyDev, ep_num,
(pUrb->enmType == VUSBXFERTYPE_ISOC),
index);
if (index < 0)
return false;
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 false;
}
pUrb->Dev.pvPrivate = (void *)(long)(index + 1);
pEndpointFBSD->pUrb = pUrb;
return true;
}
/**
* 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 PVUSBURB usbProxyFreeBSDUrbReap(PUSBPROXYDEV pProxyDev, RTMSINTERVAL cMillies)
{
struct usb_fs_endpoint *pXferEndpoint;
PUSBPROXYDEVFBSD pDevFBSD = (PUSBPROXYDEVFBSD) pProxyDev->Backend.pv;
PUSBENDPOINTFBSD pEndpointFBSD;
PVUSBURB pUrb;
struct usb_fs_complete UsbFsComplete;
struct pollfd PollFd;
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 && rc == VERR_RESOURCE_BUSY)
{
/* Poll for finished transfers */
PollFd.fd = (int)pDevFBSD->File;
PollFd.events = POLLIN | POLLRDNORM;
PollFd.revents = 0;
rc = poll(&PollFd, 1, (cMillies == RT_INDEFINITE_WAIT) ? INFTIM : cMillies);
if (rc >= 1)
{
goto repeat;
}
else
{
LogFlow(("usbProxyFreeBSDUrbReap: "
"poll returned rc=%d\n", rc));
}
}
return pUrb;
}
/**
* Cancels the URB.
* The URB requires reaping, so we don't change its state.
*/
static void usbProxyFreeBSDUrbCancel(PVUSBURB pUrb)
{
PUSBPROXYDEV pProxyDev = PDMINS_2_DATA(pUrb->pUsbIns, PUSBPROXYDEV);
int index;
index = (int)(long)pUrb->Dev.pvPrivate - 1;
if (index < 0 || index >= USBFBSD_MAXENDPOINTS)
return;
LogFlow(("usbProxyFreeBSDUrbCancel: epindex=%u\n", (unsigned)index));
usbProxyFreeBSDEndpointClose(pProxyDev, index);
}
/**
* The FreeBSD USB Proxy Backend.
*/
extern const USBPROXYBACK g_USBProxyDeviceHost =
{
"host",
usbProxyFreeBSDOpen,
usbProxyFreeBSDInit,
usbProxyFreeBSDClose,
usbProxyFreeBSDReset,
usbProxyFreeBSDSetConfig,
usbProxyFreeBSDClaimInterface,
usbProxyFreeBSDReleaseInterface,
usbProxyFreeBSDSetInterface,
usbProxyFreeBSDClearHaltedEp,
usbProxyFreeBSDUrbQueue,
usbProxyFreeBSDUrbCancel,
usbProxyFreeBSDUrbReap,
0
};
/*
* Local Variables:
* mode: c
* c-file-style: "bsd"
* c-basic-offset: 4
* tab-width: 4
* indent-tabs-mode: s
* End:
*/