/* $Id$ */
/** @file
* VirtualBox USB Monitor Driver, Solaris Hosts.
*/
/*
* Copyright (C) 2008-2013 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* 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.
*
* The contents of this file may alternatively be used under the terms
* of the Common Development and Distribution License Version 1.0
* (CDDL) only, as it comes in the "COPYING.CDDL" file of the
* VirtualBox OSE distribution, in which case the provisions of the
* CDDL are applicable instead of those of the GPL.
*
* You may elect to license modified versions of this file under the
* terms and conditions of either the GPL or the CDDL or both.
*/
/*******************************************************************************
* Header Files *
*******************************************************************************/
#ifdef DEBUG_ramshankar
# define LOG_ENABLED
#endif
#include "VBoxUSBFilterMgr.h"
#include <VBox/usblib-solaris.h>
#include <iprt/initterm.h>
#include <iprt/semaphore.h>
#define USBDRV_MINOR_VER 0
/*******************************************************************************
* Defined Constants And Macros *
*******************************************************************************/
/** The module name. */
/** The module description as seen in 'modinfo'. */
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int VBoxUSBMonSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal);
static int VBoxUSBMonSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult);
/*******************************************************************************
* Structures and Typedefs *
*******************************************************************************/
/**
*/
{
nodev, /* b strategy */
nodev, /* b dump */
nodev, /* b print */
nodev, /* c devmap */
nodev, /* c mmap */
nodev, /* c segmap */
nochpoll, /* c poll */
ddi_prop_op, /* property ops */
NULL, /* streamtab */
CB_REV /* revision */
};
/**
* dev_ops: for driver device operations
*/
{
DEVO_REV, /* driver build revision */
0, /* ref count */
nulldev, /* identify */
nulldev, /* probe */
nodev, /* reset */
(struct bus_ops *)0,
nodev, /* power */
};
/**
* modldrv: export driver specifics to the kernel
*/
{
&mod_driverops, /* extern from kernel */
};
/**
*/
{
NULL,
};
/**
* Client driver info.
*/
typedef struct vboxusbmon_client_t
{
/**
* Device state info.
*/
typedef struct
{
/*******************************************************************************
* Global Variables *
*******************************************************************************/
/** Global Device handle we only support one instance. */
/** Global Mutex. */
/** Number of userland clients that have kept us open. */
/** Global list of client drivers registered with us. */
/** Opaque pointer to list of soft states. */
static void *g_pVBoxUSBMonSolarisState;
/*******************************************************************************
* Internal Functions *
*******************************************************************************/
static int vboxUSBMonSolarisProcessIOCtl(int iFunction, void *pvState, void *pvData, size_t cbData, size_t *pcbReturnedData);
/*******************************************************************************
* Monitor Global Hooks *
*******************************************************************************/
static int vboxUSBMonSolarisClientInfo(vboxusbmon_state_t *pState, PVBOXUSB_CLIENT_INFO pClientInfo);
int VBoxUSBMonSolarisElectDriver(usb_dev_descr_t *pDevDesc, usb_dev_str_t *pDevStrings, char *pszDevicePath, int Bus, int Port,
char **ppszDrv, void *pvReserved);
/**
* Kernel entry points
*/
int _init(void)
{
int rc;
/*
* Prevent module autounloading.
*/
if (pModCtl)
else
/*
* Initialize IPRT R0 driver, which internally calls OS-specific r0 init.
*/
if (RT_SUCCESS(rc))
{
/*
* Initialize global mutex.
*/
rc = VBoxUSBFilterInit();
if (RT_SUCCESS(rc))
{
if (!rc)
{
if (!rc)
return rc;
}
else
}
else
RTR0Term();
}
else
return -1;
}
int _fini(void)
{
int rc;
if (!rc)
{
RTR0Term();
}
return rc;
}
{
}
/**
* Attach entry point, to attach a device to the system or resume it.
*
* @param pDip The module structure instance.
* @param enmCmd Attach type (ddi_attach_cmd_t)
*
* @returns corresponding solaris error code.
*/
{
switch (enmCmd)
{
case DDI_ATTACH:
{
if (RT_UNLIKELY(g_pDip))
{
return DDI_FAILURE;
}
"none", "none", 0660);
if (rc == DDI_SUCCESS)
{
return rc;
}
else
return DDI_FAILURE;
}
case DDI_RESUME:
{
/* We don't have to bother about power management. */
return DDI_SUCCESS;
}
default:
return DDI_FAILURE;
}
}
/**
* Detach entry point, to detach a device to the system or suspend it.
*
* @param pDip The module structure instance.
* @param enmCmd Attach type (ddi_attach_cmd_t)
*
* @returns corresponding solaris error code.
*/
{
switch (enmCmd)
{
case DDI_DETACH:
{
/*
* Free all registered clients' info.
*/
while (pCur)
{
}
return DDI_SUCCESS;
}
case DDI_SUSPEND:
{
/* We don't have to bother about power management. */
return DDI_SUCCESS;
}
default:
return DDI_FAILURE;
}
}
/**
* Info entry point, called by solaris kernel for obtaining driver info.
*
* @param pDip The module structure instance (do not use).
* @param enmCmd Information request type.
* @param pvArg Type specific argument.
* @param ppvResult Where to store the requested info.
*
* @returns corresponding solaris error code.
*/
static int VBoxUSBMonSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult)
{
switch (enmCmd)
{
case DDI_INFO_DEVT2DEVINFO:
break;
case DDI_INFO_DEVT2INSTANCE:
break;
default:
rc = DDI_FAILURE;
break;
}
return rc;
}
{
unsigned iOpenInstance;
/*
* Verify we are being opened as a character device.
*/
return EINVAL;
/*
* Verify that we're called after attach.
*/
if (!g_pDip)
{
return ENXIO;
}
{
{
return EINVAL;
}
}
{
{
break;
}
}
if (!pState)
{
return ENXIO;
}
return 0;
}
{
if (!pState)
{
return EFAULT;
}
{
{
}
else
{
}
}
else
/*
* Remove all filters for this client process.
*/
return 0;
}
{
return 0;
}
{
return 0;
}
/** @def IOCPARM_LEN
* Gets the length from the ioctl number.
*/
#ifndef IOCPARM_LEN
#endif
static int VBoxUSBMonSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal)
{
/*
* Get the session from the soft state item.
*/
if (!pState)
{
return EINVAL;
}
/*
* Read the request wrapper. Though We don't really need wrapper struct. now
* it's room for the future as Solaris isn't generous regarding the size.
*/
{
LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: bad request %#x size=%d expected=%d\n", Cmd, IOCPARM_LEN(Cmd), sizeof(ReqWrap)));
return ENOTTY;
}
if (RT_UNLIKELY(rc))
{
LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyin failed to read header pArg=%p Cmd=%d. rc=%d.\n", pArg, Cmd, rc));
return EINVAL;
}
{
LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: bad magic %#x; pArg=%p Cmd=%d.\n", ReqWrap.u32Magic, pArg, Cmd));
return EINVAL;
}
{
LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: bad size %#x; pArg=%p Cmd=%d.\n", ReqWrap.cbData, pArg, Cmd));
return EINVAL;
}
/*
* Read the request.
*/
if (RT_UNLIKELY(!pvBuf))
{
LogRel((DEVICE_NAME ":VBoxUSBMonSolarisIOCtl: RTMemTmpAlloc failed to alloc %d bytes.\n", ReqWrap.cbData));
return ENOMEM;
}
if (RT_UNLIKELY(rc))
{
LogRel((DEVICE_NAME ":VBoxUSBMonSolarisIOCtl: ddi_copyin failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, rc));
return EFAULT;
}
{
return EINVAL;
}
/*
* Process the IOCtl.
*/
rc = 0;
{
LogRel((DEVICE_NAME ":VBoxUSBMonSolarisIOCtl: too much output data %d expected %d\n", cbDataReturned, ReqWrap.cbData));
}
/*
* Copy the request back to user space.
*/
{
/*
* Copy the payload (if any) back to user space.
*/
if (cbDataReturned > 0)
{
if (RT_UNLIKELY(rc))
{
LogRel((DEVICE_NAME ":VBoxUSBMonSolarisIOCtl: ddi_copyout failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, rc));
}
}
}
else
{
}
return rc;
}
/**
* IOCtl processor for user to kernel and kernel to kernel communication.
*
* @returns VBox status code.
*
* @param iFunction The requested function.
* @param pvState Opaque pointer to driver state used for getting ring-3 process (Id).
* @param cbData The max size of the data buffer.
* @param pcbDataReturned Where to store the amount of returned data. Can be NULL.
*/
static int vboxUSBMonSolarisProcessIOCtl(int iFunction, void *pvState, void *pvData, size_t cbData, size_t *pcbReturnedData)
{
LogFunc((DEVICE_NAME ":solarisUSBProcessIOCtl iFunction=%d pvBuf=%p cbBuf=%zu\n", iFunction, pvData, cbData));
int rc;
do { \
{ \
return VERR_BUFFER_OVERFLOW; \
} \
{ \
return VERR_INVALID_POINTER; \
} \
} while (0)
switch (iFunction)
{
{
Log(("vboxUSBMonSolarisProcessIOCtl: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n",
Log(("vboxUSBMonSolarisProcessIOCtl: Manufacturer=%s Product=%s Serial=%s\n",
USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_MANUFACTURER_STR) : "<null>",
USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_PRODUCT_STR) : "<null>",
USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR) : "<null>"));
rc = USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false /* fMustBePresent */); AssertRC(rc);
Log((DEVICE_NAME ":vboxUSBMonSolarisProcessIOCtl: ADD_FILTER (Process:%d) returned %d\n", pState->Process, rc));
break;
}
{
*pcbReturnedData = 0;
Log((DEVICE_NAME ":vboxUSBMonSolarisProcessIOCtl: REMOVE_FILTER (Process:%d) returned %d\n", pState->Process, rc));
break;
}
{
*pcbReturnedData = 0;
Log((DEVICE_NAME ":vboxUSBMonSolarisProcessIOCtl: RESET_DEVICE (Process:%d) returned %d\n", pState->Process, rc));
break;
}
{
Log((DEVICE_NAME ":vboxUSBMonSolarisProcessIOCtl: CLIENT_INFO (Process:%d) returned %d\n", pState->Process, rc));
break;
}
{
*pcbReturnedData = sizeof(VBOXUSBREQ_GET_VERSION);
rc = VINF_SUCCESS;
break;
}
default:
{
LogRel((DEVICE_NAME ":vboxUSBMonSolarisProcessIOCtl: Unknown request (Process:%d) %#x\n", pState->Process, iFunction));
*pcbReturnedData = 0;
break;
}
}
return rc;
}
{
LogFunc((DEVICE_NAME ":vboxUSBMonSolarisResetDevice pszDevicePath=%s fReattach=%d\n", pszDevicePath, fReattach));
/*
* Try grabbing the dev_info_t.
*/
if (pDeviceInfo)
{
/*
* Grab the root device node from the parent hub for resetting.
*/
for (;;)
{
if (!pTmpDeviceInfo)
{
LogRel((DEVICE_NAME ":vboxUSBMonSolarisResetDevice failed to get parent device info for %s\n", pszDevicePath));
return VERR_GENERAL_FAILURE;
}
if (ddi_prop_exists(DDI_DEV_T_ANY, pTmpDeviceInfo, DDI_PROP_DONTPASS, "usb-port-count")) /* parent hub */
break;
}
/*
* Try re-enumerating the device.
*/
Log((DEVICE_NAME ":usb_reset_device for %s level=%s returned %d\n", pszDevicePath, fReattach ? "ReAttach" : "Default", rc));
switch (rc)
{
default: rc = VERR_UNRESOLVED_ERROR; break;
}
}
else
{
LogRel((DEVICE_NAME ":vboxUSBMonSolarisResetDevice Cannot obtain device info for %s\n", pszDevicePath));
}
return rc;
}
/**
* Query client driver information. This also has a side-effect that it informs
* the client driver which upcoming VM process should be allowed to open it.
*
* @returns VBox status code.
* @param pState Pointer to the device state.
* @param pClientInfo Pointer to the client info. object.
*/
static int vboxUSBMonSolarisClientInfo(vboxusbmon_state_t *pState, PVBOXUSB_CLIENT_INFO pClientInfo)
{
LogFunc((DEVICE_NAME ":vboxUSBMonSolarisClientInfo pState=%p pClientInfo=%p\n", pState, pClientInfo));
while (pCur)
{
if (strncmp(pClientInfo->szDeviceIdent, pCur->Info.szDeviceIdent, sizeof(pCur->Info.szDeviceIdent) - 1) == 0)
{
RTStrPrintf(pClientInfo->szClientPath, sizeof(pClientInfo->szClientPath), "%s", pCur->Info.szClientPath);
/*
* Inform the client driver that this is the client process that is going to open it. We can predict the future!
*/
int rc;
{
rc = pCur->Info.pfnSetConsumerCredentials(pState->Process, pCur->Info.Instance, NULL /* pvReserved */);
if (RT_FAILURE(rc))
}
else
Log((DEVICE_NAME ":vboxUSBMonSolarisClientInfo found. %s rc=%d\n", pClientInfo->szDeviceIdent, rc));
return rc;
}
}
LogRel((DEVICE_NAME ":vboxUSBMonSolarisClientInfo Failed to find client %s\n", pClientInfo->szDeviceIdent));
return VERR_NOT_FOUND;
}
/**
* Registers client driver.
*
* @returns VBox status code.
* @param pszDevicePath The device path of the client driver.
* @param Instance The client driver instance.
*/
{
LogFunc((DEVICE_NAME ":VBoxUSBMonSolarisRegisterClient pClientDip=%p pClientInfo=%p\n", pClientDip, pClientInfo));
{
{
strncpy(pClient->Info.szDeviceIdent, pClientInfo->szDeviceIdent, sizeof(pClient->Info.szDeviceIdent));
return VINF_SUCCESS;
}
else
return VERR_NO_MEMORY;
}
else
return VERR_INVALID_STATE;
}
/**
* Deregisters client driver.
*
* @returns VBox status code.
* @param pszDevicePath The device path of the client driver.
* @param Instance The client driver instance.
*/
{
{
while (pCur)
{
{
if (pPrev)
else
return VINF_SUCCESS;
}
}
LogRel((DEVICE_NAME ":VBoxUSBMonSolarisUnregisterClient Failed to find registered client %p\n", pClientDip));
return VERR_NOT_FOUND;
}
else
return VERR_INVALID_STATE;
}
/**
* USBA driver election callback.
*
* @returns USB_SUCCESS if we want to capture the device, USB_FAILURE otherwise.
* @param pDevDesc The parsed device descriptor (does not include subconfigs).
* @param pDevStrings Device strings: Manufacturer, Product, Serial Number.
* @param pszDevicePath The physical path of the device being attached.
* @param Bus The Bus number on which the device is on.
* @param Port The Port number on the bus.
* @param ppszDrv The name of the driver we wish to capture the device with.
* @param pvReserved Reserved for future use.
*/
int VBoxUSBMonSolarisElectDriver(usb_dev_descr_t *pDevDesc, usb_dev_str_t *pDevStrings, char *pszDevicePath, int Bus, int Port,
char **ppszDrv, void *pvReserved)
{
LogFunc((DEVICE_NAME ":VBoxUSBMonSolarisElectDriver pDevDesc=%p pDevStrings=%p pszDevicePath=%s Bus=%d Port=%d\n", pDevDesc,
/*
* Create a filter from the device being attached.
*/
USBFilterSetNumExact(&Filter, USBFILTERIDX_BUS, 0x0 /* Bus */, true); /* Use 0x0 as userland initFilterFromDevice function in Main: see comment on "SetMustBePresent" below */
USBFilterSetStringExact(&Filter, USBFILTERIDX_MANUFACTURER_STR, pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "", true);
USBFilterSetStringExact(&Filter, USBFILTERIDX_PRODUCT_STR, pDevStrings->usb_product ? pDevStrings->usb_product : "", true);
USBFilterSetStringExact(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR, pDevStrings->usb_serialno ? pDevStrings->usb_serialno : "", true);
/* This doesn't work like it should (USBFilterMatch fails on matching field (6) i.e. Bus despite this. Investigate later. */
Log((DEVICE_NAME ":VBoxUSBMonSolarisElectDriver: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n",
USBFilterGetString(&Filter, USBFILTERIDX_MANUFACTURER_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_MANUFACTURER_STR) : "<null>",
USBFilterGetString(&Filter, USBFILTERIDX_PRODUCT_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_PRODUCT_STR) : "<null>",
USBFilterGetString(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR) ? USBFilterGetString(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR) : "<null>"));
/*
* Run through user filters and try to see if it has a match.
*/
if (Owner == NIL_RTPROCESS)
{
Log((DEVICE_NAME ":No matching filters, device %#x:%#x uninteresting.\n", pDevDesc->idVendor, pDevDesc->idProduct));
return USB_FAILURE;
}
LogRel((DEVICE_NAME ": Capturing %s %#x:%#x:%s\n", pDevStrings->usb_product ? pDevStrings->usb_product : "<Unnamed USB device>",
return USB_SUCCESS;
}