diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 14:19:18 +0000 |
commit | 4035b1bfb1e5843a539a8b624d21952b756974d1 (patch) | |
tree | f1e9cd5bf548cbc57ff2fddfb2b4aa9ae95587e2 /src/VBox/HostDrivers/VBoxUSB | |
parent | Initial commit. (diff) | |
download | virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.tar.xz virtualbox-4035b1bfb1e5843a539a8b624d21952b756974d1.zip |
Adding upstream version 6.1.22-dfsg.upstream/6.1.22-dfsgupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/VBox/HostDrivers/VBoxUSB')
63 files changed, 24186 insertions, 0 deletions
diff --git a/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk new file mode 100644 index 00000000..7f772c5f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/Makefile.kmk @@ -0,0 +1,77 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Windows USB drivers. +# + +# +# Copyright (C) 2006-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# Include sub-makefiles. +if1of ($(KBUILD_TARGET), darwin solaris win) + include $(PATH_SUB_CURRENT)/$(KBUILD_TARGET)/Makefile.kmk +endif + +# +# USBLib - The static USB Library for use in VBoxSVC and VBoxDD. +# +# Note! Drivers using USBFilter.cpp should just add ../USBFilter.cpp +# to their source list. +# +LIBRARIES += USBLib +USBLib_TEMPLATE = VBOXR3 +USBLib_SDKS.win = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) +USBLib_DEFS = IN_USBLIB +USBLib_DEFS.os2 = STATIC_USBCALLS +USBLib_DEFS.win = _WIN32_WINNT=0x0501 +USBLib_SOURCES = \ + USBLib.cpp \ + USBFilter.cpp + +# OS specific bits if applicable. +USBLib_SOURCES.darwin = \ + darwin/USBLib-darwin.cpp +USBLib_SOURCES.os2 = \ + os2/usbcalls.c +USBLib_SOURCES.solaris = \ + solaris/USBLib-solaris.cpp +USBLib_SOURCES.win = \ + win/lib/VBoxUsbLib-win.cpp + +ifdef VBOX_WITH_TESTCASES + # + # USBFilter testcase. + # + PROGRAMS += tstUSBFilter + tstUSBFilter_TEMPLATE = VBOXR3TSTEXE + tstUSBFilter_DEFS = IN_USBLIB + tstUSBFilter_SOURCES = \ + testcase/tstUSBFilter.cpp + tstUSBFilter_LIBS = \ + $(USBLib_1_TARGET) +endif # VBOX_WITH_TESTCASES + + +# generate rules +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp new file mode 100644 index 00000000..823277a0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/USBFilter.cpp @@ -0,0 +1,1826 @@ +/* $Id: USBFilter.cpp $ */ +/** @file + * VirtualBox USB filter abstraction. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usbfilter.h> +#include <VBox/usblib.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/string.h> +#include <iprt/assert.h> +#include <iprt/ctype.h> + + +/** @todo split this up for the sake of device drivers and such. */ + + +/** + * Initializes an USBFILTER structure. + * + * @param pFilter The filter to initialize. + * @param enmType The filter type. If not valid, the filter will not + * be properly initialized and all other calls will fail. + */ +USBLIB_DECL(void) USBFilterInit(PUSBFILTER pFilter, USBFILTERTYPE enmType) +{ + memset(pFilter, 0, sizeof(*pFilter)); + AssertReturnVoid(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END); + pFilter->u32Magic = USBFILTER_MAGIC; + pFilter->enmType = enmType; + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + pFilter->aFields[i].enmMatch = USBFILTERMATCH_IGNORE; +} + + +/** + * Make a clone of the specified filter. + * + * @param pFilter The target filter. + * @param pToClone The source filter. + */ +USBLIB_DECL(void) USBFilterClone(PUSBFILTER pFilter, PCUSBFILTER pToClone) +{ + memcpy(pFilter, pToClone, sizeof(*pToClone)); +} + + +/** + * Deletes (invalidates) an USBFILTER structure. + * + * @param pFilter The filter to delete. + */ +USBLIB_DECL(void) USBFilterDelete(PUSBFILTER pFilter) +{ + pFilter->u32Magic = ~USBFILTER_MAGIC; + pFilter->enmType = USBFILTERTYPE_INVALID; + pFilter->offCurEnd = 0xfffff; +} + + +/** + * Skips blanks. + * + * @returns Next non-blank char in the string. + * @param psz The string. + */ +DECLINLINE(const char *) usbfilterSkipBlanks(const char *psz) +{ + while (RT_C_IS_BLANK(*psz)) + psz++; + return psz; +} + + +/** + * Worker for usbfilterReadNumber that parses a hexadecimal number. + * + * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS. + * @param pszExpr Where to start converting, first char is a valid digit. + * @param ppszExpr See usbfilterReadNumber. + * @param pu16Val See usbfilterReadNumber. + */ +static int usbfilterReadNumberHex(const char *pszExpr, const char **ppszExpr, uint16_t *pu16Val) +{ + int rc = VINF_SUCCESS; + uint32_t u32 = 0; + do + { + unsigned uDigit = *pszExpr >= 'a' && *pszExpr <= 'f' + ? *pszExpr - 'a' + 10 + : *pszExpr >= 'A' && *pszExpr <= 'F' + ? *pszExpr - 'A' + 10 + : *pszExpr - '0'; + if (uDigit >= 16) + break; + u32 *= 16; + u32 += uDigit; + if (u32 > UINT16_MAX) + rc = VWRN_NUMBER_TOO_BIG; + } while (*++pszExpr); + + *ppszExpr = usbfilterSkipBlanks(pszExpr); + *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX; + return VINF_SUCCESS; +} + + +/** + * Worker for usbfilterReadNumber that parses a decimal number. + * + * @returns Same as usbfilterReadNumber, except for VERR_NO_DIGITS. + * @param pszExpr Where to start converting, first char is a valid digit. + * @param uBase The base - 8 or 16. + * @param ppszExpr See usbfilterReadNumber. + * @param pu16Val See usbfilterReadNumber. + */ +static int usbfilterReadNumberDecimal(const char *pszExpr, unsigned uBase, const char **ppszExpr, uint16_t *pu16Val) +{ + int rc = VINF_SUCCESS; + uint32_t u32 = 0; + do + { + unsigned uDigit = *pszExpr - '0'; + if (uDigit >= uBase) + break; + u32 *= uBase; + u32 += uDigit; + if (u32 > UINT16_MAX) + rc = VWRN_NUMBER_TOO_BIG; + } while (*++pszExpr); + + *ppszExpr = usbfilterSkipBlanks(pszExpr); + *pu16Val = rc == VINF_SUCCESS ? u32 : UINT16_MAX; + return rc; +} + + +/** + * Reads a number from a numeric expression. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if all is fine. *ppszExpr and *pu16Val are updated. + * @retval VWRN_NUMBER_TOO_BIG if the number exceeds unsigned 16-bit, both *ppszExpr and *pu16Val are updated. + * @retval VERR_NO_DIGITS if there aren't any digits. + * + * @param ppszExpr Pointer to the current expression pointer. + * This is advanced past the expression and trailing blanks on success. + * @param pu16Val Where to store the value on success. + */ +static int usbfilterReadNumber(const char **ppszExpr, uint16_t *pu16Val) +{ + const char *pszExpr = usbfilterSkipBlanks(*ppszExpr); + if (!RT_C_IS_DIGIT(*pszExpr)) + return VERR_NO_DIGITS; + + if (*pszExpr == '0') + { + if (pszExpr[1] == 'x' || pszExpr[1] == 'X') + { + if (!RT_C_IS_XDIGIT(pszExpr[2])) + return VERR_NO_DIGITS; + return usbfilterReadNumberHex(pszExpr + 2, ppszExpr, pu16Val); + } + if (RT_C_IS_ODIGIT(pszExpr[1])) + return usbfilterReadNumberDecimal(pszExpr + 1, 8, ppszExpr, pu16Val); + /* Solitary 0! */ + if (RT_C_IS_DIGIT(pszExpr[1])) + return VERR_NO_DIGITS; + } + return usbfilterReadNumberDecimal(pszExpr, 10, ppszExpr, pu16Val); +} + + +/** + * Validates a numeric expression. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if valid. + * @retval VERR_INVALID_PARAMETER if invalid. + * @retval VERR_NO_DIGITS if some expression is short of digits. + * + * @param pszExpr The numeric expression. + */ +static int usbfilterValidateNumExpression(const char *pszExpr) +{ + /* + * An empty expression is fine. + */ + if (!*pszExpr) + return VINF_SUCCESS; + + /* + * The string format is: "(<m>|([<m>]-[<n>]))|(<m>|([<m>]-[<n>]))+" + * where <m> and <n> are numbers in the decimal, hex (0xNNN) or octal (0NNN) + * form. Spaces are allowed around <m> and <n>. + */ + unsigned cSubExpressions = 0; + while (*pszExpr) + { + /* + * Skip remnants of the previous expression and any empty expressions. + * ('|' is the expression separator.) + */ + while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr)) + pszExpr++; + if (!*pszExpr) + break; + + /* + * Parse the expression. + */ + int rc; + uint16_t u16First = 0; + uint16_t u16Last = 0; + if (*pszExpr == '-') + { + /* -N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M or M-N */ + rc = usbfilterReadNumber(&pszExpr, &u16First); + if (RT_SUCCESS(rc)) + { + if (*pszExpr == '-') + { + /* M-N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M */ + u16Last = u16First; + } + } + } + if (RT_FAILURE(rc)) + return rc; + + /* + * We should either be at the end of the string or at + * an expression separator (|). + */ + if (*pszExpr && *pszExpr != '|' ) + return VERR_INVALID_PARAMETER; + + cSubExpressions++; + } + + return cSubExpressions ? VINF_SUCCESS : VERR_INVALID_PARAMETER; +} + + +/** + * Validates a string pattern. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if valid. + * @retval VERR_INVALID_PARAMETER if invalid. + * + * @param psz The string pattern. + */ +static int usbfilterValidateStringPattern(const char *psz) +{ + /* + * This is only becomes important if we start doing + * sets ([0-9]) and such like. + */ + RT_NOREF1(psz); + return VINF_SUCCESS; +} + + +/** + * Thoroughly validates the USB Filter. + * + * @returns Appropriate VBox status code. + * @param pFilter The filter to validate. + */ +USBLIB_DECL(int) USBFilterValidate(PCUSBFILTER pFilter) +{ + if (!VALID_PTR(pFilter)) + return VERR_INVALID_POINTER; + + if (pFilter->u32Magic != USBFILTER_MAGIC) + return VERR_INVALID_MAGIC; + + if ( pFilter->enmType <= USBFILTERTYPE_INVALID + || pFilter->enmType >= USBFILTERTYPE_END) + { + Log(("USBFilter: %p - enmType=%d!\n", pFilter, pFilter->enmType)); + return VERR_INVALID_PARAMETER; + } + + if (pFilter->offCurEnd >= sizeof(pFilter->achStrTab)) + { + Log(("USBFilter: %p - offCurEnd=%#x!\n", pFilter, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + + /* Validate that string value offsets are inside the string table. */ + for (uint32_t i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch) + && pFilter->aFields[i].u16Value > pFilter->offCurEnd) + { + Log(("USBFilter: %p - bad offset=%#x\n", pFilter, pFilter->aFields[i].u16Value)); + return VERR_INVALID_PARAMETER; + } + } + + /* + * Validate the string table. + */ + if (pFilter->achStrTab[0]) + { + Log(("USBFilter: %p - bad null string\n", pFilter)); + return VERR_INVALID_PARAMETER; + } + + const char *psz = &pFilter->achStrTab[1]; + while (psz < &pFilter->achStrTab[pFilter->offCurEnd]) + { + const char *pszEnd = RTStrEnd(psz, &pFilter->achStrTab[sizeof(pFilter->achStrTab)] - psz); + if (!pszEnd) + { + Log(("USBFilter: %p - string at %#x isn't terminated!\n", + pFilter, psz - &pFilter->achStrTab[0])); + return VERR_INVALID_PARAMETER; + } + + uint16_t off = (uint16_t)(uintptr_t)(psz - &pFilter->achStrTab[0]); + unsigned i; + for (i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch) + && pFilter->aFields[i].u16Value == off) + break; + if (i >= RT_ELEMENTS(pFilter->aFields)) + { + Log(("USBFilter: %p - string at %#x isn't used by anyone! (%s)\n", + pFilter, psz - &pFilter->achStrTab[0], psz)); + return VERR_INVALID_PARAMETER; + } + + psz = pszEnd + 1; + } + + if ((uintptr_t)(psz - &pFilter->achStrTab[0] - 1) != pFilter->offCurEnd) + { + Log(("USBFilter: %p - offCurEnd=%#x currently at %#x\n", + pFilter, pFilter->offCurEnd, psz - &pFilter->achStrTab[0] - 1)); + return VERR_INVALID_PARAMETER; + } + + while (psz < &pFilter->achStrTab[sizeof(pFilter->achStrTab)]) + { + if (*psz) + { + Log(("USBFilter: %p - str tab isn't zero padded! %#x: %c\n", + pFilter, psz - &pFilter->achStrTab[0], *psz)); + return VERR_INVALID_PARAMETER; + } + psz++; + } + + + /* + * Validate the fields. + */ + int rc; + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + if (pFilter->aFields[i].u16Value) + { + Log(("USBFilter: %p - #%d/%d u16Value=%d expected 0!\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + if (!USBFilterIsNumericField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not numeric field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if (!USBFilterIsNumericField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not numeric field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value]; + rc = usbfilterValidateNumExpression(psz); + if (RT_FAILURE(rc)) + { + Log(("USBFilter: %p - #%d / %d - bad num expr: %s (rc=%Rrc)\n", + pFilter, i, pFilter->aFields[i].enmMatch, psz, rc)); + return rc; + } + break; + + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + if (!USBFilterIsStringField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not string field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + break; + + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + if (!USBFilterIsStringField((USBFILTERIDX)i)) + { + Log(("USBFilter: %p - #%d / %d - not string field\n", + pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + if ( pFilter->aFields[i].u16Value >= pFilter->offCurEnd + && pFilter->offCurEnd) + { + Log(("USBFilter: %p - #%d / %d - off=%#x max=%#x\n", + pFilter, i, pFilter->aFields[i].enmMatch, pFilter->aFields[i].u16Value, pFilter->offCurEnd)); + return VERR_INVALID_PARAMETER; + } + psz = &pFilter->achStrTab[pFilter->aFields[i].u16Value]; + rc = usbfilterValidateStringPattern(psz); + if (RT_FAILURE(rc)) + { + Log(("USBFilter: %p - #%d / %d - bad string pattern: %s (rc=%Rrc)\n", + pFilter, i, pFilter->aFields[i].enmMatch, psz, rc)); + return rc; + } + break; + + default: + Log(("USBFilter: %p - #%d enmMatch=%d!\n", pFilter, i, pFilter->aFields[i].enmMatch)); + return VERR_INVALID_PARAMETER; + } + } + + return VINF_SUCCESS; +} + + +/** + * Find the specified field in the string table. + * + * @returns Pointer to the string in the string table on success. + * NULL if the field is invalid or it doesn't have a string value. + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +DECLINLINE(const char *) usbfilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + { + switch (pFilter->aFields[enmFieldIdx].enmMatch) + { + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + Assert(pFilter->aFields[enmFieldIdx].u16Value < sizeof(pFilter->achStrTab)); + return &pFilter->achStrTab[pFilter->aFields[enmFieldIdx].u16Value]; + + default: + AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch)); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + break; + } + } + return NULL; +} + + +/** + * Gets a number value of a field. + * + * The field must contain a numeric value. + * + * @returns The field value on success, -1 on failure (invalid input / not numeric). + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +DECLINLINE(int) usbfilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + { + switch (pFilter->aFields[enmFieldIdx].enmMatch) + { + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return pFilter->aFields[enmFieldIdx].u16Value; + + default: + AssertMsgFailed(("%d\n", pFilter->aFields[enmFieldIdx].enmMatch)); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + break; + } + } + return -1; +} + + +/** + * Performs simple pattern matching. + * + * @returns true on match and false on mismatch. + * @param pszExpr The numeric expression. + * @param u16Value The value to match. + */ +static bool usbfilterMatchNumExpression(const char *pszExpr, uint16_t u16Value) +{ + /* + * The string format is: "(<m>|([<m>]-[<n>]))|(<m>|([<m>]-[<n>]))+" + * where <m> and <n> are numbers in the decimal, hex (0xNNN) or octal (0NNN) + * form. Spaces are allowed around <m> and <n>. + */ + while (*pszExpr) + { + /* + * Skip remnants of the previous expression and any empty expressions. + * ('|' is the expression separator.) + */ + while (*pszExpr == '|' || RT_C_IS_BLANK(*pszExpr)) + pszExpr++; + if (!*pszExpr) + break; + + /* + * Parse the expression. + */ + int rc; + uint16_t u16First = 0; + uint16_t u16Last = 0; + if (*pszExpr == '-') + { + /* -N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M or M-N */ + rc = usbfilterReadNumber(&pszExpr, &u16First); + if (RT_SUCCESS(rc)) + { + pszExpr = usbfilterSkipBlanks(pszExpr); + if (*pszExpr == '-') + { + /* M-N */ + pszExpr++; + rc = usbfilterReadNumber(&pszExpr, &u16Last); + } + else + { + /* M */ + u16Last = u16First; + } + } + } + + /* On success, we should either be at the end of the string or + at an expression separator (|). */ + if (RT_SUCCESS(rc) && *pszExpr && *pszExpr != '|' ) + rc = VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + /* + * Swap the values if the order is mixed up. + */ + if (u16First > u16Last) + { + uint16_t u16Tmp = u16First; + u16First = u16Last; + u16Last = u16Tmp; + } + + /* + * Perform the compare. + */ + if ( u16Value >= u16First + && u16Value <= u16Last) + return true; + } + else + { + /* + * Skip the bad expression. + * ('|' is the expression separator.) + */ + while (*pszExpr && *pszExpr != '|') + pszExpr++; + } + } + + return false; +} + + +/** + * Performs simple pattern matching. + * + * @returns true on match and false on mismatch. + * @param pszPattern The pattern to match against. + * @param psz The string to match. + */ +static bool usbfilterMatchStringPattern(const char *pszPattern, const char *psz) +{ + char ch; + while ((ch = *pszPattern++)) + { + if (ch == '?') + { + /* + * Matches one char or end of string. + */ + if (*psz) + psz++; + } + else if (ch == '*') + { + /* + * Matches zero or more characters. + */ + /* skip subsequent wildcards */ + while ( (ch = *pszPattern) == '*' + || ch == '?') + pszPattern++; + if (!ch) + /* Pattern ends with a '*' and thus matches the rest of psz. */ + return true; + + /* Find the length of the following exact pattern sequence. */ + ssize_t cchMatch = 1; + while ( (ch = pszPattern[cchMatch]) != '\0' + && ch != '*' + && ch != '?') + cchMatch++; + + /* Check if the exact pattern sequence is too long. */ + ssize_t cch = strlen(psz); + cch -= cchMatch; + if (cch < 0) + return false; + + /* Is the rest an exact match? */ + if (!ch) + return memcmp(psz + cch, pszPattern, cchMatch) == 0; + + /* + * This is where things normally starts to get recursive or ugly. + * + * Just to make life simple, we'll skip the nasty stuff and say + * that we will do a maximal wildcard match and forget about any + * alternative matches. + * + * If somebody is bored out of their mind one day, feel free to + * implement correct matching without using recursion. + */ + ch = *pszPattern; + const char *pszMatch = NULL; + while ( cch-- >= 0 + && *psz) + { + if ( *psz == ch + && !strncmp(psz, pszPattern, cchMatch)) + pszMatch = psz; + psz++; + } + if (!pszMatch) + return false; + + /* advance */ + psz = pszMatch + cchMatch; + pszPattern += cchMatch; + } + else + { + /* exact match */ + if (ch != *psz) + return false; + psz++; + } + } + + return *psz == '\0'; +} + + +/** + * Match a filter against a device. + * + * @returns true if they match, false if not. + * + * @param pFilter The filter to match with. + * @param pDevice The device data. This is a filter (type ignored) that + * contains 'exact' values for all present fields and 'ignore' + * values for the non-present fields. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(bool) USBFilterMatch(PCUSBFILTER pFilter, PCUSBFILTER pDevice) +{ + return USBFilterMatchRated(pFilter, pDevice) > 0; +} + + +#if 0 /*def IN_RING0*/ /** @todo convert to proper logging. */ +extern "C" int printf(const char *format, ...); +# define dprintf(a) printf a +#else +# define dprintf(a) do {} while (0) +#endif + +/** + * Match a filter against a device and rate the result. + * + * @returns -1 if no match, matching rate between 1 and 100 (inclusive) if matched. + * + * @param pFilter The filter to match with. + * @param pDevice The device data. This is a filter (type ignored) that + * contains 'exact' values for all present fields and 'ignore' + * values for the non-present fields. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(int) USBFilterMatchRated(PCUSBFILTER pFilter, PCUSBFILTER pDevice) +{ + unsigned iRate = 0; +dprintf(("USBFilterMatchRated: %p %p\n", pFilter, pDevice)); + + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + iRate += 2; + break; + + case USBFILTERMATCH_PRESENT: + if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE) + { +dprintf(("filter match[%d]: !present\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXACT: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value) + { +if (pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE) + dprintf(("filter match[%d]: !num_exact device=ignore\n", i)); +else + dprintf(("filter match[%d]: !num_exact %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXACT_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && pFilter->aFields[i].u16Value != pDevice->aFields[i].u16Value) + { +dprintf(("filter match[%d]: !num_exact_np %#x (filter) != %#x (device)\n", i, pFilter->aFields[i].u16Value, pDevice->aFields[i].u16Value)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_NUM_EXPRESSION: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), + pDevice->aFields[i].u16Value)) + { +dprintf(("filter match[%d]: !num_expression\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && !usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), + pDevice->aFields[i].u16Value)) + { +dprintf(("filter match[%d]: !num_expression_no\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_STR_EXACT: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_exact\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_STR_EXACT_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_exact_np\n", i)); + return -1; + } + iRate += 2; + break; + + case USBFILTERMATCH_STR_PATTERN: + if ( pDevice->aFields[i].enmMatch == USBFILTERMATCH_IGNORE + || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_pattern\n", i)); + return -1; + } + iRate += 1; + break; + + case USBFILTERMATCH_STR_PATTERN_NP: + if ( pDevice->aFields[i].enmMatch != USBFILTERMATCH_IGNORE + && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), + usbfilterGetString(pDevice, (USBFILTERIDX)i))) + { +dprintf(("filter match[%d]: !str_pattern_np\n", i)); + return -1; + } + iRate += 1; + break; + + default: + AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch)); + return -1; + } + } + + /* iRate is the range 0..2*cFields - recalc to percent. */ +dprintf(("filter match: iRate=%d", iRate)); + return iRate == 2 * RT_ELEMENTS(pFilter->aFields) + ? 100 + : (iRate * 100) / (2 * RT_ELEMENTS(pFilter->aFields)); +} + + +/** + * Match a filter against a USBDEVICE. + * + * @returns true if they match, false if not. + * + * @param pFilter The filter to match with. + * @param pDevice The device to match. + * + * @remark Both the filter and the device are ASSUMED to be valid because + * we don't wish to waste any time in this function. + */ +USBLIB_DECL(bool) USBFilterMatchDevice(PCUSBFILTER pFilter, PUSBDEVICE pDevice) +{ + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + break; + + case USBFILTERMATCH_PRESENT: + { + const char *psz; + switch (i) + { + case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break; + case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break; + case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break; + default: psz = ""; break; + } + if (!psz) + return false; + break; + } + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + { + uint16_t u16Value; + switch (i) + { + case USBFILTERIDX_VENDOR_ID: u16Value = pDevice->idVendor; break; + case USBFILTERIDX_PRODUCT_ID: u16Value = pDevice->idProduct; break; + case USBFILTERIDX_DEVICE: u16Value = pDevice->bcdDevice; break; + case USBFILTERIDX_DEVICE_CLASS: u16Value = pDevice->bDeviceClass; break; + case USBFILTERIDX_DEVICE_SUB_CLASS: u16Value = pDevice->bDeviceSubClass; break; + case USBFILTERIDX_DEVICE_PROTOCOL: u16Value = pDevice->bDeviceProtocol; break; + case USBFILTERIDX_BUS: u16Value = pDevice->bBus; break; + case USBFILTERIDX_PORT: u16Value = pDevice->bPort; break; + default: u16Value = UINT16_MAX; break; + + } + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + if (pFilter->aFields[i].u16Value != u16Value) + return false; + break; + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + if (!usbfilterMatchNumExpression(usbfilterGetString(pFilter, (USBFILTERIDX)i), u16Value)) + return false; + break; + } + break; + } + + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + { + const char *psz; + switch (i) + { + case USBFILTERIDX_MANUFACTURER_STR: psz = pDevice->pszManufacturer; break; + case USBFILTERIDX_PRODUCT_STR: psz = pDevice->pszProduct; break; + case USBFILTERIDX_SERIAL_NUMBER_STR: psz = pDevice->pszSerialNumber; break; + default: psz = NULL; break; + } + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_STR_EXACT: + if ( !psz + || strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_EXACT_NP: + if ( psz + && strcmp(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_PATTERN: + if ( !psz + || !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + + case USBFILTERMATCH_STR_PATTERN_NP: + if ( psz + && !usbfilterMatchStringPattern(usbfilterGetString(pFilter, (USBFILTERIDX)i), psz)) + return false; + break; + } + break; + } + + default: + AssertMsgFailed(("#%d: %d\n", i, pFilter->aFields[i].enmMatch)); + return false; + } + } + + return true; +} + + +/** + * Checks if the two filters are identical. + * + * @returns true if the are identical, false if they aren't. + * @param pFilter The first filter. + * @param pFilter2 The second filter. + */ +USBLIB_DECL(bool) USBFilterIsIdentical(PCUSBFILTER pFilter, PCUSBFILTER pFilter2) +{ + /* Lazy works here because we're darn strict with zero padding and such elsewhere. */ + return memcmp(pFilter, pFilter2, sizeof(*pFilter)) == 0; +} + + + +/** + * Sets the filter type. + * + * @returns VBox status code. + * @retval VERR_INVALID_PARAMETER if the filter type is invalid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmType The new filter type. + */ +USBLIB_DECL(int) USBFilterSetFilterType(PUSBFILTER pFilter, USBFILTERTYPE enmType) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(enmType > USBFILTERTYPE_INVALID && enmType < USBFILTERTYPE_END, VERR_INVALID_PARAMETER); + + pFilter->enmType = enmType; + return VINF_SUCCESS; +} + + +/** + * Replaces the string value of a field. + * + * This will remove any existing string value current held by the field from the + * string table and then attempt to add the new value. This function can be used + * to delete any assigned string before changing the type to numeric by passing + * in an empty string. This works because the first byte in the string table is + * reserved for the empty (NULL) string. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_POINTER if pszString isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pszString The string to add. + * @param fPurge Purge invalid UTF-8 encoding and control characters + * before setting it. + */ +static int usbfilterSetString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszString, bool fPurge) +{ + /* + * Validate input. + */ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszString, VERR_INVALID_POINTER); + + Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab)); + Assert(pFilter->achStrTab[pFilter->offCurEnd] == '\0'); + + /* + * Remove old string value if any. + */ + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch) + && pFilter->aFields[enmFieldIdx].u16Value != 0) + { + uint32_t off = pFilter->aFields[enmFieldIdx].u16Value; + pFilter->aFields[enmFieldIdx].u16Value = 0; /* Assign it to the NULL string. */ + + unsigned cchShift = (unsigned)strlen(&pFilter->achStrTab[off]) + 1; + ssize_t cchToMove = (pFilter->offCurEnd + 1) - (off + cchShift); + Assert(cchToMove >= 0); + if (cchToMove > 0) + { + /* We're not last - must shift the strings. */ + memmove(&pFilter->achStrTab[off], &pFilter->achStrTab[off + cchShift], cchToMove); + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + if ( pFilter->aFields[i].u16Value >= off + && USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[i].enmMatch)) + pFilter->aFields[i].u16Value -= cchShift; + } + pFilter->offCurEnd -= cchShift; + Assert(pFilter->offCurEnd < sizeof(pFilter->achStrTab)); + Assert(pFilter->offCurEnd + cchShift <= sizeof(pFilter->achStrTab)); + + /* zero the unused string table (to allow lazyness/strictness elsewhere). */ + memset(&pFilter->achStrTab[pFilter->offCurEnd], '\0', cchShift); + } + + /* + * Make a special case for the empty string. + * (This also makes the delete logical above work correctly for the last string.) + */ + if (!*pszString) + pFilter->aFields[enmFieldIdx].u16Value = 0; + else + { + size_t cch = strlen(pszString); + if (pFilter->offCurEnd + cch + 2 > sizeof(pFilter->achStrTab)) + return VERR_BUFFER_OVERFLOW; + + pFilter->aFields[enmFieldIdx].u16Value = pFilter->offCurEnd + 1; + memcpy(&pFilter->achStrTab[pFilter->offCurEnd + 1], pszString, cch + 1); + if (fPurge) + cch = USBLibPurgeEncoding(&pFilter->achStrTab[pFilter->offCurEnd + 1]); + pFilter->offCurEnd += (uint32_t)cch + 1; + } + + return VINF_SUCCESS; +} + +/** + * Wrapper around usbfilterSetString() that deletes any string value + * currently assigned to a field. + * + * Upon successful return the field contains a null string, nothing or a number. + * + * This function will validate the field index if there isn't any string + * value to delete, thus preventing any extra validating of the index. + * + * @returns VBox status code. See usbfilterSetString. + * @param pFilter The filter. + * @param enmFieldIdx The index of the field which string value should be deleted. + */ +static int usbfilterDeleteAnyStringValue(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = VINF_SUCCESS; + if ( USBFilterIsMethodUsingStringValue((USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch) + && pFilter->aFields[enmFieldIdx].u16Value != 0) + rc = usbfilterSetString(pFilter, enmFieldIdx, "", false /*fPurge*/); + else if ((unsigned)enmFieldIdx >= (unsigned)USBFILTERIDX_END) + rc = VERR_INVALID_PARAMETER; + return rc; +} + + +/** + * Sets a field to always match (ignore whatever is thrown at it). + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + */ +USBLIB_DECL(int) USBFilterSetIgnore(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_IGNORE; + pFilter->aFields[enmFieldIdx].u16Value = 0; + } + return rc; +} + + +/** + * Sets a field to match on device field present only. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + */ +USBLIB_DECL(int) USBFilterSetPresentOnly(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + int rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].enmMatch = USBFILTERMATCH_PRESENT; + pFilter->aFields[enmFieldIdx].u16Value = 0; + } + return rc; +} + + +/** + * Sets a field to exactly match a number. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param u16Value The string pattern. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetNumExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t u16Value, bool fMustBePresent) +{ + int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterDeleteAnyStringValue(pFilter, enmFieldIdx); + if (RT_SUCCESS(rc)) + { + pFilter->aFields[enmFieldIdx].u16Value = u16Value; + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXACT : USBFILTERMATCH_NUM_EXACT_NP; + } + } + + return rc; +} + + +/** + * Sets a field to match a numeric expression. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or the numeric expression aren't valid. + * @retval VERR_INVALID_POINTER if pszExpression isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszExpression The numeric expression. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetNumExpression(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszExpression, bool fMustBePresent) +{ + int rc = USBFilterIsNumericField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + /* Strip leading spaces and empty sub expressions (||). */ + while (*pszExpression && (RT_C_IS_BLANK(*pszExpression) || *pszExpression == '|')) + pszExpression++; + + rc = usbfilterValidateNumExpression(pszExpression); + if (RT_SUCCESS(rc)) + { + /* We could optimize the expression further (stripping spaces, convert numbers), + but it's more work than what it's worth and it could upset some users. */ + rc = usbfilterSetString(pFilter, enmFieldIdx, pszExpression, false /*fPurge*/); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_NUM_EXPRESSION : USBFILTERMATCH_NUM_EXPRESSION_NP; + else if (rc == VERR_NO_DIGITS) + rc = VERR_INVALID_PARAMETER; + } + } + return rc; +} + + +/** + * Sets a field to exactly match a string. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszValue The string value. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + * @param fPurge Purge invalid UTF-8 encoding and control + * characters before setting it. + */ +USBLIB_DECL(int) USBFilterSetStringExact(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszValue, + bool fMustBePresent, bool fPurge) +{ + int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterSetString(pFilter, enmFieldIdx, pszValue, fPurge); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_EXACT : USBFILTERMATCH_STR_EXACT_NP; + } + return rc; +} + + +/** + * Sets a field to match a string pattern. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the string table is full. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. This must be a string field. + * @param pszPattern The string pattern. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetStringPattern(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, const char *pszPattern, bool fMustBePresent) +{ + int rc = USBFilterIsStringField(enmFieldIdx) ? VINF_SUCCESS : VERR_INVALID_PARAMETER; + if (RT_SUCCESS(rc)) + { + rc = usbfilterValidateStringPattern(pszPattern); + if (RT_SUCCESS(rc)) + { + rc = usbfilterSetString(pFilter, enmFieldIdx, pszPattern, false /*fPurge*/); + if (RT_SUCCESS(rc)) + pFilter->aFields[enmFieldIdx].enmMatch = fMustBePresent ? USBFILTERMATCH_STR_PATTERN : USBFILTERMATCH_STR_PATTERN_NP; + } + } + return rc; +} + + +/** + * Sets the must-be-present part of a field. + * + * This only works on field which already has matching criteria. This means + * that field marked 'ignore' will not be processed and will result in a + * warning status code. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VWRN_INVALID_PARAMETER if the field is marked 'ignore'. No assertions. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx or pattern aren't valid. + * @retval VERR_INVALID_POINTER if pszPattern isn't a valid pointer. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param fMustBePresent If set, a non-present field on the device will result in a mismatch. + * If clear, a non-present field on the device will match. + */ +USBLIB_DECL(int) USBFilterSetMustBePresent(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, bool fMustBePresent) +{ + AssertPtrReturn(pFilter, VERR_INVALID_POINTER); + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + AssertReturn((unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END, VERR_INVALID_PARAMETER); + + USBFILTERMATCH enmMatch = (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch; + if (fMustBePresent) + { + switch (enmMatch) + { + case USBFILTERMATCH_IGNORE: + return VWRN_INVALID_PARAMETER; + + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_PATTERN: + break; + + case USBFILTERMATCH_NUM_EXACT_NP: + enmMatch = USBFILTERMATCH_NUM_EXACT; + break; + case USBFILTERMATCH_NUM_EXPRESSION_NP: + enmMatch = USBFILTERMATCH_NUM_EXPRESSION; + break; + case USBFILTERMATCH_STR_EXACT_NP: + enmMatch = USBFILTERMATCH_STR_EXACT; + break; + case USBFILTERMATCH_STR_PATTERN_NP: + enmMatch = USBFILTERMATCH_STR_PATTERN; + break; + default: + AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC); + } + } + else + { + switch (enmMatch) + { + case USBFILTERMATCH_IGNORE: + return VWRN_INVALID_PARAMETER; + + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN_NP: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + break; + + case USBFILTERMATCH_PRESENT: + enmMatch = USBFILTERMATCH_IGNORE; + break; + case USBFILTERMATCH_NUM_EXACT: + enmMatch = USBFILTERMATCH_NUM_EXACT_NP; + break; + case USBFILTERMATCH_NUM_EXPRESSION: + enmMatch = USBFILTERMATCH_NUM_EXPRESSION_NP; + break; + case USBFILTERMATCH_STR_EXACT: + enmMatch = USBFILTERMATCH_STR_EXACT_NP; + break; + case USBFILTERMATCH_STR_PATTERN: + enmMatch = USBFILTERMATCH_STR_PATTERN_NP; + break; + + default: + AssertMsgFailedReturn(("%p: enmFieldIdx=%d enmMatch=%d\n", pFilter, enmFieldIdx, enmMatch), VERR_INVALID_MAGIC); + } + } + + pFilter->aFields[enmFieldIdx].enmMatch = enmMatch; + return VINF_SUCCESS; +} + + +/** + * Gets the filter type. + * + * @returns The filter type. + * USBFILTERTYPE_INVALID if the filter is invalid. + * @param pFilter The filter. + */ +USBLIB_DECL(USBFILTERTYPE) USBFilterGetFilterType(PCUSBFILTER pFilter) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, USBFILTERTYPE_INVALID); + return pFilter->enmType; +} + + +/** + * Gets the matching method for a field. + * + * @returns The matching method on success, UBFILTERMATCH_INVALID on invalid field index. + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(USBFILTERMATCH) USBFilterGetMatchingMethod(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if ( pFilter->u32Magic == USBFILTER_MAGIC + && (unsigned)enmFieldIdx < (unsigned)USBFILTERIDX_END) + return (USBFILTERMATCH)pFilter->aFields[enmFieldIdx].enmMatch; + return USBFILTERMATCH_INVALID; +} + + +/** + * Gets the numeric value of a field. + * + * The field must contain a number, we're not doing any conversions for you. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a number. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pu16Value Where to store the value. + */ +USBLIB_DECL(int) USBFilterQueryNum(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, uint16_t *pu16Value) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + int iValue = usbfilterGetNum(pFilter, enmFieldIdx); + if (iValue == -1) + return VERR_INVALID_PARAMETER; + *pu16Value = (uint16_t)iValue; + return VINF_SUCCESS; +} + + +/** + * Gets the numeric value of a field. + * + * The field must contain a number, we're not doing any conversions for you. + * + * @returns The field value on success, -1 on failure (invalid input / not numeric). + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(int) USBFilterGetNum(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, -1); + return usbfilterGetNum(pFilter, enmFieldIdx); +} + + +/** + * Gets the string value of a field. + * + * The field must contain a string, we're not doing any conversions for you. + * + * @returns VBox status code. + * @retval VINF_SUCCESS on success. + * @retval VERR_BUFFER_OVERFLOW if the buffer isn't sufficient to hold the string. The buffer + * will be filled with as much of the string that'll fit. + * @retval VERR_INVALID_PARAMETER if the enmFieldIdx isn't valid or if the field doesn't contain a string. + * @retval VERR_INVALID_MAGIC if pFilter is invalid. + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + * @param pszBuf Where to store the string. + * @param cchBuf The size of the buffer. + */ +USBLIB_DECL(int) USBFilterQueryString(PUSBFILTER pFilter, USBFILTERIDX enmFieldIdx, char *pszBuf, size_t cchBuf) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, VERR_INVALID_MAGIC); + + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_UNLIKELY(!psz)) + return VERR_INVALID_PARAMETER; + + int rc = VINF_SUCCESS; + size_t cch = strlen(psz); + if (cch < cchBuf) + memcpy(pszBuf, psz, cch + 1); + else + { + rc = VERR_BUFFER_OVERFLOW; + if (cchBuf) + { + memcpy(pszBuf, psz, cchBuf - 1); + pszBuf[cchBuf - 1] = '\0'; + } + } + + return rc; +} + + +/** + * Gets the string table entry for a field. + * + * @returns Pointer to the string. (readonly!) + * + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(const char *) USBFilterGetString(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, NULL); + + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_UNLIKELY(!psz)) + return NULL; + return psz; +} + + +/** + * Gets the string length of a field containing a string. + * + * @returns String length on success, -1 on failure (not a string, bad filter). + * @param pFilter The filter. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(ssize_t) USBFilterGetStringLen(PCUSBFILTER pFilter, USBFILTERIDX enmFieldIdx) +{ + if (RT_LIKELY(pFilter->u32Magic == USBFILTER_MAGIC)) + { + const char *psz = usbfilterGetString(pFilter, enmFieldIdx); + if (RT_LIKELY(psz)) + return strlen(psz); + } + return -1; +} + + +/** + * Check if any of the fields are set to something substatial. + * + * Consider the fileter a wildcard if this returns false. + * + * @returns true / false. + * @param pFilter The filter. + */ +USBLIB_DECL(bool) USBFilterHasAnySubstatialCriteria(PCUSBFILTER pFilter) +{ + AssertReturn(pFilter->u32Magic == USBFILTER_MAGIC, false); + + for (unsigned i = 0; i < RT_ELEMENTS(pFilter->aFields); i++) + { + switch (pFilter->aFields[i].enmMatch) + { + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + break; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + return true; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + { + const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i); + if (psz) + { + while (*psz && (*psz == '|' || RT_C_IS_BLANK(*psz))) + psz++; + if (*psz) + return true; + } + break; + } + + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + { + const char *psz = usbfilterGetString(pFilter, (USBFILTERIDX)i); + if (psz) + { + while (*psz && (*psz == '*' || *psz == '?')) + psz++; + if (*psz) + return true; + } + break; + } + } + } + + return false; +} + + + +/** + * Checks whether the specified field is a numeric field or not. + * + * @returns true / false. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(bool) USBFilterIsNumericField(USBFILTERIDX enmFieldIdx) +{ + switch (enmFieldIdx) + { + case USBFILTERIDX_VENDOR_ID: + case USBFILTERIDX_PRODUCT_ID: + case USBFILTERIDX_DEVICE: + case USBFILTERIDX_DEVICE_CLASS: + case USBFILTERIDX_DEVICE_SUB_CLASS: + case USBFILTERIDX_DEVICE_PROTOCOL: + case USBFILTERIDX_BUS: + case USBFILTERIDX_PORT: + return true; + + default: + AssertMsgFailed(("%d\n", enmFieldIdx)); + RT_FALL_THRU(); + case USBFILTERIDX_MANUFACTURER_STR: + case USBFILTERIDX_PRODUCT_STR: + case USBFILTERIDX_SERIAL_NUMBER_STR: + return false; + } +} + + +/** + * Checks whether the specified field is a string field or not. + * + * @returns true / false. + * @param enmFieldIdx The field index. + */ +USBLIB_DECL(bool) USBFilterIsStringField(USBFILTERIDX enmFieldIdx) +{ + switch (enmFieldIdx) + { + default: + AssertMsgFailed(("%d\n", enmFieldIdx)); + RT_FALL_THRU(); + case USBFILTERIDX_VENDOR_ID: + case USBFILTERIDX_PRODUCT_ID: + case USBFILTERIDX_DEVICE: + case USBFILTERIDX_DEVICE_CLASS: + case USBFILTERIDX_DEVICE_SUB_CLASS: + case USBFILTERIDX_DEVICE_PROTOCOL: + case USBFILTERIDX_BUS: + case USBFILTERIDX_PORT: + return false; + + case USBFILTERIDX_MANUFACTURER_STR: + case USBFILTERIDX_PRODUCT_STR: + case USBFILTERIDX_SERIAL_NUMBER_STR: + return true; + } +} + + +/** + * Checks whether the specified matching method uses a numeric value or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodUsingNumericValue(USBFILTERMATCH enmMatchingMethod) +{ + switch (enmMatchingMethod) + { + default: + AssertMsgFailed(("%d\n", enmMatchingMethod)); + RT_FALL_THRU(); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + return false; + + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return true; + } +} + + +/** + * Checks whether the specified matching method uses a string value or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodUsingStringValue(USBFILTERMATCH enmMatchingMethod) +{ + switch (enmMatchingMethod) + { + default: + AssertMsgFailed(("%d\n", enmMatchingMethod)); + RT_FALL_THRU(); + case USBFILTERMATCH_IGNORE: + case USBFILTERMATCH_PRESENT: + case USBFILTERMATCH_NUM_EXACT: + case USBFILTERMATCH_NUM_EXACT_NP: + return false; + + case USBFILTERMATCH_NUM_EXPRESSION: + case USBFILTERMATCH_NUM_EXPRESSION_NP: + case USBFILTERMATCH_STR_EXACT: + case USBFILTERMATCH_STR_EXACT_NP: + case USBFILTERMATCH_STR_PATTERN: + case USBFILTERMATCH_STR_PATTERN_NP: + return true; + } +} + + +/** + * Checks if a matching method is for numeric fields or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodNumeric(USBFILTERMATCH enmMatchingMethod) +{ + return enmMatchingMethod >= USBFILTERMATCH_NUM_FIRST + && enmMatchingMethod <= USBFILTERMATCH_NUM_LAST; +} + +/** + * Checks if a matching method is for string fields or not. + * + * @returns true / false. + * @param enmMatchingMethod The matching method. + */ +USBLIB_DECL(bool) USBFilterIsMethodString(USBFILTERMATCH enmMatchingMethod) +{ + return enmMatchingMethod >= USBFILTERMATCH_STR_FIRST + && enmMatchingMethod <= USBFILTERMATCH_STR_LAST; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp b/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp new file mode 100644 index 00000000..85c17691 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/USBLib.cpp @@ -0,0 +1,61 @@ +/* $Id: USBLib.cpp $ */ +/** @file + * VirtualBox USB Library, Common Bits. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usblib.h> + + +/** + * Calculate the hash of the serial string. + * + * 64bit FNV1a, chosen because it is designed to hash in to a power of two + * space, and is much quicker and simpler than, say, a half MD4. + * + * @returns the hash. + * @param pszSerial The serial string. + */ +USBLIB_DECL(uint64_t) USBLibHashSerial(const char *pszSerial) +{ + if (!pszSerial) + pszSerial = ""; + + const uint8_t *pu8 = (const uint8_t *)pszSerial; + uint64_t u64 = UINT64_C(14695981039346656037); + for (;;) + { + uint8_t u8 = *pu8; + if (!u8) + break; + u64 = (u64 * UINT64_C(1099511628211)) ^ u8; + pu8++; + } + + return u64; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp new file mode 100644 index 00000000..ab44cb24 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.cpp @@ -0,0 +1,561 @@ +/* $Id: VBoxUSBFilterMgr.cpp $ */ +/** @file + * VirtualBox Ring-0 USB Filter Manager. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usbfilter.h> +#include "VBoxUSBFilterMgr.h" + +#include <iprt/err.h> +#include <iprt/handletable.h> +#include <iprt/mem.h> +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK +# include <iprt/spinlock.h> +#else +# include <iprt/semaphore.h> +#endif +#include <iprt/string.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + +/** @def VBOXUSBFILTERMGR_LOCK + * Locks the filter list. Careful with scoping since this may + * create a temporary variable. Don't call twice in the same function. + */ + +/** @def VBOXUSBFILTERMGR_UNLOCK + * Unlocks the filter list. + */ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + +# define VBOXUSBFILTERMGR_LOCK() \ + RTSpinlockAcquire(g_Spinlock) + +# define VBOXUSBFILTERMGR_UNLOCK() \ + RTSpinlockRelease(g_Spinlock) + +#else + +# define VBOXUSBFILTERMGR_LOCK() \ + do { int rc2 = RTSemFastMutexRequest(g_Mtx); AssertRC(rc2); } while (0) + +# define VBOXUSBFILTERMGR_UNLOCK() \ + do { int rc2 = RTSemFastMutexRelease(g_Mtx); AssertRC(rc2); } while (0) + +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Pointer to an VBoxUSB filter. */ +typedef struct VBOXUSBFILTER *PVBOXUSBFILTER; +/** Pointer to PVBOXUSBFILTER. */ +typedef PVBOXUSBFILTER *PPVBOXUSBFILTER; + +/** + * VBoxUSB internal filter representation. + */ +typedef struct VBOXUSBFILTER +{ + /** The core filter. */ + USBFILTER Core; + /** The filter owner. */ + VBOXUSBFILTER_CONTEXT Owner; + /** The filter Id. */ + uint32_t uHnd; + /** Pointer to the next filter in the list. */ + PVBOXUSBFILTER pNext; +} VBOXUSBFILTER; + +/** + * VBoxUSB filter list. + */ +typedef struct VBOXUSBFILTERLIST +{ + /** The head pointer. */ + PVBOXUSBFILTER pHead; + /** The tail pointer. */ + PVBOXUSBFILTER pTail; +} VBOXUSBFILTERLIST; +/** Pointer to a VBOXUSBFILTERLIST. */ +typedef VBOXUSBFILTERLIST *PVBOXUSBFILTERLIST; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK +/** Spinlock protecting the filter lists. */ +static RTSPINLOCK g_Spinlock = NIL_RTSPINLOCK; +#else +/** Mutex protecting the filter lists. */ +static RTSEMFASTMUTEX g_Mtx = NIL_RTSEMFASTMUTEX; +#endif +/** The per-type filter lists. + * @remark The first entry is empty (USBFILTERTYPE_INVALID). */ +static VBOXUSBFILTERLIST g_aLists[USBFILTERTYPE_END]; +/** The handle table to match handles to the right filter. */ +static RTHANDLETABLE g_hHndTableFilters = NIL_RTHANDLETABLE; + + + +/** + * Initializes the VBoxUSB filter manager. + * + * @returns IPRT status code. + */ +int VBoxUSBFilterInit(void) +{ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + int rc = RTSpinlockCreate(&g_Spinlock, RTSPINLOCK_FLAGS_INTERRUPT_SAFE, "VBoxUSBFilter"); +#else + int rc = RTSemFastMutexCreate(&g_Mtx); +#endif + if (RT_SUCCESS(rc)) + { + uint32_t fFlags; +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + fFlags = RTHANDLETABLE_FLAGS_LOCKED_IRQ_SAFE; +#else + fFlags = RTHANDLETABLE_FLAGS_LOCKED; +#endif + rc = RTHandleTableCreateEx(&g_hHndTableFilters, fFlags, 1 /* uBase */, 8192 /* cMax */, + NULL, NULL); + if (RT_SUCCESS(rc)) + { + /* not really required, but anyway... */ + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + g_aLists[i].pHead = g_aLists[i].pTail = NULL; + } + else + { +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; +#else + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; +#endif + } + } + return rc; +} + + +/** + * Internal worker that frees a filter. + * + * @param pFilter The filter to free. + */ +static void vboxUSBFilterFree(PVBOXUSBFILTER pFilter) +{ + USBFilterDelete(&pFilter->Core); + pFilter->Owner = VBOXUSBFILTER_CONTEXT_NIL; + pFilter->pNext = NULL; + RTMemFree(pFilter); +} + + +/** + * Terminates the VBoxUSB filter manager. + */ +void VBoxUSBFilterTerm(void) +{ +#ifdef VBOXUSBFILTERMGR_USB_SPINLOCK + RTSpinlockDestroy(g_Spinlock); + g_Spinlock = NIL_RTSPINLOCK; +#else + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; +#endif + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + g_aLists[i].pHead = g_aLists[i].pTail = NULL; + while (pCur) + { + PVBOXUSBFILTER pNext = pCur->pNext; + RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + vboxUSBFilterFree(pCur); + pCur = pNext; + } + } + + RTHandleTableDestroy(g_hHndTableFilters, NULL, NULL); +} + + +/** + * Adds a new filter. + * + * The filter will be validate, duplicated and added. + * + * @returns IPRT status code. + * @param pFilter The filter. + * @param Owner The filter owner. Must be non-zero. + * @param puId Where to store the filter ID. + */ +int VBoxUSBFilterAdd(PCUSBFILTER pFilter, VBOXUSBFILTER_CONTEXT Owner, uintptr_t *puId) +{ + /* + * Validate input. + */ + int rc = USBFilterValidate(pFilter); + if (RT_FAILURE(rc)) + return rc; + if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL) + return VERR_INVALID_PARAMETER; + if (!VALID_PTR(puId)) + return VERR_INVALID_POINTER; + + /* + * Allocate a new filter. + */ + PVBOXUSBFILTER pNew = (PVBOXUSBFILTER)RTMemAlloc(sizeof(*pNew)); + if (!pNew) + return VERR_NO_MEMORY; + memcpy(&pNew->Core, pFilter, sizeof(pNew->Core)); + pNew->Owner = Owner; + pNew->pNext = NULL; + + rc = RTHandleTableAlloc(g_hHndTableFilters, pNew, &pNew->uHnd); + if (RT_SUCCESS(rc)) + { + *puId = pNew->uHnd; + + /* + * Insert it. + */ + PVBOXUSBFILTERLIST pList = &g_aLists[pFilter->enmType]; + + VBOXUSBFILTERMGR_LOCK(); + + if (pList->pTail) + pList->pTail->pNext = pNew; + else + pList->pHead = pNew; + pList->pTail = pNew; + + VBOXUSBFILTERMGR_UNLOCK(); + } + else + RTMemFree(pNew); + + return rc; +} + + +/** + * Removes an existing filter. + * + * The filter will be validate, duplicated and added. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if successfully removed. + * @retval VERR_FILE_NOT_FOUND if the specified filter/owner cannot be found. + * + * @param Owner The filter owner. + * @param uId The ID of the filter that's to be removed. + * Returned by VBoxUSBFilterAdd(). + */ +int VBoxUSBFilterRemove(VBOXUSBFILTER_CONTEXT Owner, uintptr_t uId) +{ + /* + * Validate input. + */ + if (!uId || uId != (uint32_t)uId) + return VERR_INVALID_PARAMETER; + if (!Owner || Owner == VBOXUSBFILTER_CONTEXT_NIL) + return VERR_INVALID_PARAMETER; + + /* + * Locate and unlink it. + */ + uint32_t uHnd = (uint32_t)uId; + PVBOXUSBFILTER pCur = NULL; + + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; !pCur && i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + pCur = g_aLists[i].pHead; + while (pCur) + { + if ( pCur->uHnd == uHnd + && pCur->Owner == Owner) + { + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + break; + } + + pPrev = pCur; + pCur = pCur->pNext; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + /* + * Free it (if found). + */ + if (pCur) + { + void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + Assert(pv == pCur); NOREF(pv); + vboxUSBFilterFree(pCur); + return VINF_SUCCESS; + } + + return VERR_FILE_NOT_FOUND; +} + +VBOXUSBFILTER_CONTEXT VBoxUSBFilterGetOwner(uintptr_t uId) +{ + Assert(uId); + /* + * Validate input. + */ + if (!uId || uId != (uint32_t)uId) + return VBOXUSBFILTER_CONTEXT_NIL; + + /* + * Result. + */ + VBOXUSBFILTER_CONTEXT Owner = VBOXUSBFILTER_CONTEXT_NIL; + + VBOXUSBFILTERMGR_LOCK(); + + PVBOXUSBFILTER pCur = (PVBOXUSBFILTER)RTHandleTableLookup(g_hHndTableFilters, (uint32_t)uId); + if (pCur) + Owner = pCur->Owner; + + Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL); + + VBOXUSBFILTERMGR_UNLOCK(); + + return Owner; +} + +/** + * Removes all filters belonging to the specified owner. + * + * This is typically called when an owner disconnects or + * terminates unexpectedly. + * + * @param Owner The owner + */ +void VBoxUSBFilterRemoveOwner(VBOXUSBFILTER_CONTEXT Owner) +{ + /* + * Collect the filters that should be freed. + */ + PVBOXUSBFILTER pToFree = NULL; + + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + while (pCur) + { + if (pCur->Owner == Owner) + { + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + + pCur->pNext = pToFree; + pToFree = pCur; + + pCur = pNext; + } + else + { + pPrev = pCur; + pCur = pCur->pNext; + } + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + /* + * Free any filters we've found. + */ + while (pToFree) + { + PVBOXUSBFILTER pNext = pToFree->pNext; + void *pv = RTHandleTableFree(g_hHndTableFilters, pToFree->uHnd); + Assert(pv == pToFree); NOREF(pv); + vboxUSBFilterFree(pToFree); + pToFree = pNext; + } +} + +/** + * Match the specified device against the filters. + * Unlike the VBoxUSBFilterMatch, returns Owner also if exclude filter is matched + * + * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched. + * @param pDevice The device data as a filter structure. + * See USBFilterMatch for how to construct this. + * @param puId Where to store the filter id (optional). + * @param fRemoveFltIfOneShot Whether or not to remove one-shot filters on + * match. + * @param pfFilter Where to store whether the device must be filtered or not + * @param pfIsOneShot Where to return whetehr the match was a one-shot + * filter or not. Optional. + * + */ +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatchEx(PCUSBFILTER pDevice, uintptr_t *puId, + bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot) +{ + /* + * Validate input. + */ + int rc = USBFilterValidate(pDevice); + AssertRCReturn(rc, VBOXUSBFILTER_CONTEXT_NIL); + + *pfFilter = false; + if (puId) + *puId = 0; + + /* + * Search the lists for a match. + * (The lists are ordered by priority.) + */ + VBOXUSBFILTERMGR_LOCK(); + + for (unsigned i = USBFILTERTYPE_FIRST; i < RT_ELEMENTS(g_aLists); i++) + { + PVBOXUSBFILTER pPrev = NULL; + PVBOXUSBFILTER pCur = g_aLists[i].pHead; + while (pCur) + { + if (USBFilterMatch(&pCur->Core, pDevice)) + { + /* + * Take list specific actions and return. + * + * The code does NOT implement the case where there are two or more + * filter clients, and one of them is releasing a device that's + * requested by some of the others. It's just too much work for a + * situation that noone will encounter. + */ + if (puId) + *puId = pCur->uHnd; + VBOXUSBFILTER_CONTEXT Owner = pCur->Owner; + *pfFilter = !!(i != USBFILTERTYPE_IGNORE + && i != USBFILTERTYPE_ONESHOT_IGNORE); + + if ( i == USBFILTERTYPE_ONESHOT_IGNORE + || i == USBFILTERTYPE_ONESHOT_CAPTURE) + { + if (fRemoveFltIfOneShot) + { + /* unlink */ + PVBOXUSBFILTER pNext = pCur->pNext; + if (pPrev) + pPrev->pNext = pNext; + else + g_aLists[i].pHead = pNext; + if (!pNext) + g_aLists[i].pTail = pPrev; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + + if ( i == USBFILTERTYPE_ONESHOT_IGNORE + || i == USBFILTERTYPE_ONESHOT_CAPTURE) + { + if (fRemoveFltIfOneShot) + { + void *pv = RTHandleTableFree(g_hHndTableFilters, pCur->uHnd); + Assert(pv == pCur); NOREF(pv); + vboxUSBFilterFree(pCur); + } + if (pfIsOneShot) + *pfIsOneShot = true; + } + else + { + if (pfIsOneShot) + *pfIsOneShot = false; + } + return Owner; + } + + pPrev = pCur; + pCur = pCur->pNext; + } + } + + VBOXUSBFILTERMGR_UNLOCK(); + return VBOXUSBFILTER_CONTEXT_NIL; +} + +/** + * Match the specified device against the filters. + * + * @returns Owner on if matched, VBOXUSBFILTER_CONTEXT_NIL it not matched. + * @param pDevice The device data as a filter structure. + * See USBFilterMatch for how to construct this. + * @param puId Where to store the filter id (optional). + */ +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatch(PCUSBFILTER pDevice, uintptr_t *puId) +{ + bool fFilter = false; + VBOXUSBFILTER_CONTEXT Owner = VBoxUSBFilterMatchEx(pDevice, puId, + true, /* remove filter is it's a one-shot*/ + &fFilter, NULL /* bool * fIsOneShot */); + if (fFilter) + { + Assert(Owner != VBOXUSBFILTER_CONTEXT_NIL); + return Owner; + } + return VBOXUSBFILTER_CONTEXT_NIL; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h new file mode 100644 index 00000000..6839b3df --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/VBoxUSBFilterMgr.h @@ -0,0 +1,58 @@ +/* $Id: VBoxUSBFilterMgr.h $ */ +/** @file + * VirtualBox Ring-0 USB Filter Manager. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_VBoxUSBFilterMgr_h +#define VBOX_INCLUDED_SRC_VBoxUSB_VBoxUSBFilterMgr_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/usbfilter.h> + +RT_C_DECLS_BEGIN + +/** @todo r=bird: VBOXUSBFILTER_CONTEXT isn't following the coding + * guildlines. Don't know which clueless dude did this... */ +#if defined(RT_OS_WINDOWS) +typedef struct VBOXUSBFLTCTX *VBOXUSBFILTER_CONTEXT; +#define VBOXUSBFILTER_CONTEXT_NIL NULL +#else +typedef RTPROCESS VBOXUSBFILTER_CONTEXT; +#define VBOXUSBFILTER_CONTEXT_NIL NIL_RTPROCESS +#endif + +int VBoxUSBFilterInit(void); +void VBoxUSBFilterTerm(void); +void VBoxUSBFilterRemoveOwner(VBOXUSBFILTER_CONTEXT Owner); +int VBoxUSBFilterAdd(PCUSBFILTER pFilter, VBOXUSBFILTER_CONTEXT Owner, uintptr_t *puId); +int VBoxUSBFilterRemove(VBOXUSBFILTER_CONTEXT Owner, uintptr_t uId); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatch(PCUSBFILTER pDevice, uintptr_t *puId); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterMatchEx(PCUSBFILTER pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot); +VBOXUSBFILTER_CONTEXT VBoxUSBFilterGetOwner(uintptr_t uId); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_VBoxUSBFilterMgr_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist b/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist new file mode 100644 index 00000000..16aac548 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/Info.plist @@ -0,0 +1,89 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> <string>English</string> + <key>CFBundleExecutable</key> <string>VBoxUSB</string> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>CFBundleInfoDictionaryVersion</key> <string>6.0</string> + <key>CFBundleName</key> <string>VBoxUSB</string> + <key>CFBundlePackageType</key> <string>KEXT</string> + <key>CFBundleSignature</key> <string>????</string> + <key>CFBundleVersion</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleShortVersionString</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + <key>CFBundleGetInfoString</key> <string>@VBOX_PRODUCT@ @VBOX_VERSION_STRING@, © 2007-@VBOX_C_YEAR@ @VBOX_VENDOR@</string> + <key>IOKitPersonalities</key> + <dict> + <key>VBoxUSB</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSB</string> + <key>IOMatchCategory</key> <string>org_virtualbox_VBoxUSB</string> + <key>IOProviderClass</key> <string>IOResources</string> + <key>IOResourceMatch</key> <string>IOKit</string> + <key>IOUserClientClass</key> <string>org_virtualbox_VBoxUSBClient</string> + </dict> + <key>VBoxUSBDevice</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSBDevice</string> + <key>IOProviderClass</key> <string>IOUSBDevice</string> + <key>idVendor</key> <string>*</string> + <key>idProduct</key> <string>*</string> + <key>bcdDevice</key> <string>*</string> + <key>IOProbeScore</key> <integer>9942</integer> + <key>IOProviderMergeProperties</key> + <dict> + <key>IOCFPlugInTypes</key> + <dict> + <key>9dc7b780-9ec0-11d4-a54f-000a27052861</key> <string>IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle</string> + </dict> + <key>IOUserClientClass</key> <string>IOUSBDeviceUserClientV2</string> + </dict> + </dict> + <key>VBoxUSBInterface</key> + <dict> + <key>CFBundleIdentifier</key> <string>org.virtualbox.kext.VBoxUSB</string> + <key>IOClass</key> <string>org_virtualbox_VBoxUSBInterface</string> + <key>IOProviderClass</key> <string>IOUSBInterface</string> + <key>idVendor</key> <string>*</string> + <key>idProduct</key> <string>*</string> + <key>bcdDevice</key> <string>*</string> + <key>bConfigurationValue</key> <string>*</string> + <key>bInterfaceNumber</key> <string>*</string> + <key>IOProbeScore</key> <integer>9942</integer> + <key>IOProviderMergeProperties</key> + <dict> + <key>IOCFPlugInTypes</key> + <dict> + <key>2d9786c6-9ef3-11d4-ad51-000a27052861</key> <string>IOUSBFamily.kext/Contents/PlugIns/IOUSBLib.bundle</string> + </dict> + <key>IOUserClientClass</key> <string>IOUSBInterfaceUserClientV2</string> + </dict> + </dict> + </dict> + <key>OSBundleLibraries</key> + <dict> + <key>com.apple.iokit.IOUSBFamily</key> <string>3.0.0</string> + <key>com.apple.iokit.IOUSBUserClient</key> <string>3.0.0</string> + <key>com.apple.kpi.iokit</key> <string>9.0.0</string> + <key>com.apple.kpi.bsd</key> <string>9.0.0</string> + <key>com.apple.kpi.mach</key> <string>9.0.0</string> + <key>com.apple.kpi.libkern</key> <string>9.0.0</string> + <key>com.apple.kpi.unsupported</key> <string>9.0.0</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> + <key>OSBundleLibraries_x86_64</key> + <dict> + <key>com.apple.iokit.IOUSBFamily</key> <string>3.5.0a25</string> + <key>com.apple.iokit.IOUSBUserClient</key> <string>3.5.0a25</string> + <key>com.apple.kpi.iokit</key> <string>10.0.0d3</string> + <key>com.apple.kpi.bsd</key> <string>10.0.0d3</string> + <key>com.apple.kpi.mach</key> <string>10.0.0d3</string> + <key>com.apple.kpi.libkern</key> <string>10.0.0d3</string> + <key>com.apple.kpi.unsupported</key> <string>10.0.0d3</string> + <key>org.virtualbox.kext.VBoxDrv</key> <string>@VBOX_VERSION_MAJOR@.@VBOX_VERSION_MINOR@.@VBOX_VERSION_BUILD@</string> + </dict> +</dict> +</plist> + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk new file mode 100644 index 00000000..1c7eae4a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/Makefile.kmk @@ -0,0 +1,87 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Darwin VBoxUSB kernel extension. +# + +# +# Copyright (C) 2006-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# VBoxUSB.kext - The Darwin Kernel Extension. +# + +# Leopard (x86) and Snow Leopard (x86/amd64) +SYSMODS.darwin += VBoxUSB +VBoxUSB_TEMPLATE = VBOXR0DRV +VBoxUSB_INST = $(INST_VBOXUSB)Contents/MacOS/ +VBoxUSB_DEBUG_INST.darwin = $(patsubst %/,%,$(INST_VBOXUSB)) +VBoxUSB_INCS = \ + . \ + .. +#VBoxUSB_LDFLAGS = -v -Wl,-whyload -Wl,-v -Wl,-whatsloaded +VBoxUSB_SOURCES := \ + VBoxUSB.cpp \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp + +INSTALLS += VBoxUSB.kext +VBoxUSB.kext_INST = $(INST_VBOXUSB)Contents/ +VBoxUSB.kext_SOURCES = $(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist +VBoxUSB.kext_CLEAN = $(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist +VBoxUSB.kext_BLDDIRS = $(VBoxUSB.kext_0_OUTDIR)/Contents/ + +$$(VBoxUSB.kext_0_OUTDIR)/Contents/Info.plist: $(PATH_SUB_CURRENT)/Info.plist $(VBOX_VERSION_MK) | $$(dir $$@) + $(call MSG_GENERATE,VBoxUSB,$@,$<) + $(QUIET)$(RM) -f $@ + $(QUIET)$(SED) \ + -e 's/@VBOX_VERSION_STRING@/$(VBOX_VERSION_STRING)/g' \ + -e 's/@VBOX_VERSION_MAJOR@/$(VBOX_VERSION_MAJOR)/g' \ + -e 's/@VBOX_VERSION_MINOR@/$(VBOX_VERSION_MINOR)/g' \ + -e 's/@VBOX_VERSION_BUILD@/$(VBOX_VERSION_BUILD)/g' \ + -e 's/@VBOX_VENDOR@/$(VBOX_VENDOR)/g' \ + -e 's/@VBOX_PRODUCT@/$(VBOX_PRODUCT)/g' \ + -e 's/@VBOX_C_YEAR@/$(VBOX_C_YEAR)/g' \ + --output $@ \ + $< + +$(evalcall2 VBOX_TEST_SIGN_KEXT,VBoxUSB) + +# Common manual loader script. +INSTALLS += ScriptsUSB +ScriptsUSB_INST = $(INST_DIST) +ScriptsUSB_EXEC_SOURCES = \ + loadusb.sh + +ifdef VBOX_WITH_TESTCASES +# +# Testcase for doing some manual driver testing... +# +PROGRAMS += tstOpenUSBDev +tstOpenUSBDev_TEMPLATE = VBOXR3TSTEXE +tstOpenUSBDev_SOURCES = testcase/tstOpenUSBDev.cpp +tstOpenUSBDev_LDFLAGS = -framework CoreFoundation -framework IOKit +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp new file mode 100644 index 00000000..14bd1974 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/USBLib-darwin.cpp @@ -0,0 +1,201 @@ +/** $Id: USBLib-darwin.cpp $ */ +/** @file + * USBLib - Library for wrapping up the VBoxUSB functionality, Darwin flavor. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usblib.h> +#include <iprt/errcore.h> +#include <VBox/log.h> +#include "VBoxUSBInterface.h" + +#include <iprt/assert.h> +#include <iprt/asm.h> + +#include <mach/mach_port.h> +#include <IOKit/IOKitLib.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The IOClass key of the service (see VBoxUSB.cpp / Info.plist). */ +#define IOCLASS_NAME "org_virtualbox_VBoxUSB" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Reference counter. */ +static uint32_t volatile g_cUsers = 0; +/** The IOMasterPort. */ +static mach_port_t g_MasterPort = 0; +/** The current service connection. */ +static io_connect_t g_Connection = 0; + + + +USBLIB_DECL(int) USBLibInit(void) +{ + /* + * Already open? + * This isn't properly serialized, but we'll be fine with the current usage. + */ + if (g_cUsers) + { + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; + } + + /* + * Finding the VBoxUSB service. + */ + mach_port_t MasterPort; + kern_return_t kr = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOMasterPort -> %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + CFDictionaryRef ClassToMatch = IOServiceMatching(IOCLASS_NAME); + if (!ClassToMatch) + { + LogRel(("USBLib: IOServiceMatching(\"%s\") failed.\n", IOCLASS_NAME)); + return VERR_GENERAL_FAILURE; + } + + /* Create an io_iterator_t for all instances of our drivers class that exist in the IORegistry. */ + io_iterator_t Iterator; + kr = IOServiceGetMatchingServices(g_MasterPort, ClassToMatch, &Iterator); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOServiceGetMatchingServices returned %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + /* Get the first item in the iterator and release it. */ + io_service_t ServiceObject = IOIteratorNext(Iterator); + IOObjectRelease(Iterator); + if (!ServiceObject) + { + LogRel(("USBLib: Couldn't find any matches.\n")); + return VERR_GENERAL_FAILURE; + } + + /* + * Open the service. + * This will cause the user client class in VBoxUSB.cpp to be instantiated. + */ + kr = IOServiceOpen(ServiceObject, mach_task_self(), VBOXUSB_DARWIN_IOSERVICE_COOKIE, &g_Connection); + IOObjectRelease(ServiceObject); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: IOServiceOpen returned %#x\n", kr)); + return RTErrConvertFromDarwinKern(kr); + } + + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; +} + + +USBLIB_DECL(int) USBLibTerm(void) +{ + if (!g_cUsers) + return VERR_WRONG_ORDER; + if (ASMAtomicDecU32(&g_cUsers) != 0) + return VINF_SUCCESS; + + /* + * We're the last guy, close down the connection. + */ + kern_return_t kr = IOServiceClose(g_Connection); + if (kr != kIOReturnSuccess) + { + LogRel(("USBLib: Warning: IOServiceClose(%p) returned %#x\n", g_Connection, kr)); + AssertMsgFailed(("%#x\n", kr)); + } + g_Connection = 0; + + return VINF_SUCCESS; +} + + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + VBOXUSBADDFILTEROUT Out = { 0, VERR_WRONG_ORDER }; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + IOByteCount cbOut = sizeof(Out); + kern_return_t kr = IOConnectMethodStructureIStructureO(g_Connection, + VBOXUSBMETHOD_ADD_FILTER, + sizeof(*pFilter), + &cbOut, + (void *)pFilter, + &Out); +#else /* 10.5 and later */ + size_t cbOut = sizeof(Out); + kern_return_t kr = IOConnectCallStructMethod(g_Connection, + VBOXUSBMETHOD_ADD_FILTER, + (void *)pFilter, sizeof(*pFilter), + &Out, &cbOut); +#endif /* 10.5 and later */ + if ( kr == kIOReturnSuccess + && RT_SUCCESS(Out.rc)) + { + Assert(cbOut == sizeof(Out)); + Assert((void *)Out.uId != NULL); + return (void *)Out.uId; + } + + AssertMsgFailed(("kr=%#x Out.rc=%Rrc\n", kr, Out.rc)); + return NULL; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + int rc = VERR_WRONG_ORDER; +#if MAC_OS_X_VERSION_MIN_REQUIRED < 1050 + IOByteCount cbOut = sizeof(rc); + kern_return_t kr = IOConnectMethodStructureIStructureO(g_Connection, + VBOXUSBMETHOD_REMOVE_FILTER, + sizeof(pvId), + &cbOut, + &pvId, + &rc); +#else /* 10.5 and later */ + size_t cbOut = sizeof(rc); + kern_return_t kr = IOConnectCallStructMethod(g_Connection, + VBOXUSBMETHOD_REMOVE_FILTER, + (void *)&pvId, sizeof(pvId), + &rc, &cbOut); +#endif /* 10.5 and later */ + AssertMsg(kr == kIOReturnSuccess && RT_SUCCESS(rc), ("kr=%#x rc=%Rrc\n", kr, rc)); + NOREF(kr); +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp new file mode 100644 index 00000000..b0f8d434 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSB.cpp @@ -0,0 +1,1890 @@ +/* $Id: VBoxUSB.cpp $ */ +/** @file + * VirtualBox USB driver for Darwin. + * + * This driver is responsible for hijacking USB devices when any of the + * VBoxSVC daemons requests it. It is also responsible for arbitrating + * access to hijacked USB devices. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_USB_DRV +/* Deal with conflicts first. + * (This is mess inherited from BSD. The *BSDs has clean this up long ago.) */ +#include <sys/param.h> +#undef PVM +#include <IOKit/IOLib.h> /* Assert as function */ + +#include "VBoxUSBInterface.h" +#include "VBoxUSBFilterMgr.h" +#include <VBox/version.h> +#include <VBox/usblib-darwin.h> +#include <VBox/log.h> +#include <iprt/types.h> +#include <iprt/initterm.h> +#include <iprt/assert.h> +#include <iprt/semaphore.h> +#include <iprt/process.h> +#include <iprt/alloc.h> +#include <iprt/errcore.h> +#include <iprt/asm.h> + +#include <mach/kmod.h> +#include <miscfs/devfs/devfs.h> +#include <sys/conf.h> +#include <sys/errno.h> +#include <sys/ioccom.h> +#include <sys/malloc.h> +#include <sys/proc.h> +#include <kern/task.h> +#include <IOKit/IOService.h> +#include <IOKit/IOUserClient.h> +#include <IOKit/IOKitKeys.h> +#include <IOKit/usb/USB.h> +#include <IOKit/usb/IOUSBDevice.h> +#include <IOKit/usb/IOUSBInterface.h> +#include <IOKit/usb/IOUSBUserClient.h> + +/* private: */ +RT_C_DECLS_BEGIN +extern void *get_bsdtask_info(task_t); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Locks the lists. */ +#define VBOXUSB_LOCK() do { int rc = RTSemFastMutexRequest(g_Mtx); AssertRC(rc); } while (0) +/** Unlocks the lists. */ +#define VBOXUSB_UNLOCK() do { int rc = RTSemFastMutexRelease(g_Mtx); AssertRC(rc); } while (0) + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +RT_C_DECLS_BEGIN +static kern_return_t VBoxUSBStart(struct kmod_info *pKModInfo, void *pvData); +static kern_return_t VBoxUSBStop(struct kmod_info *pKModInfo, void *pvData); +RT_C_DECLS_END + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * The service class. + * + * This is the management service that VBoxSVC and the VMs speak to. + * + * @remark The method prototypes are ordered somewhat after their order of + * invocation, while the implementation is ordered by pair. + */ +class org_virtualbox_VBoxUSB : public IOService +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSB); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual bool start(IOService *pProvider); + virtual bool open(IOService *pForClient, IOOptionBits fOptions = 0, void *pvArg = 0); + virtual bool terminate(IOOptionBits fOptions); + virtual void close(IOService *pForClient, IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + /** @} */ + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSB, IOService); + + +/** + * The user client class that pairs up with org_virtualbox_VBoxUSB. + */ +class org_virtualbox_VBoxUSBClient : public IOUserClient +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBClient); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService & IOUserClient + * @{ */ + virtual bool initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type); + virtual bool start(IOService *pProvider); + virtual IOReturn clientClose(void); + virtual IOReturn clientDied(void); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual bool finalize(IOOptionBits fOptions); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOExternalMethod *getTargetAndMethodForIndex(IOService **ppService, UInt32 iMethod); + /** @} */ + + /** @name User client methods + * @{ */ + IOReturn addFilter(PUSBFILTER pFilter, PVBOXUSBADDFILTEROUT pOut, IOByteCount cbFilter, IOByteCount *pcbOut); + IOReturn removeFilter(uintptr_t *puId, int *prc, IOByteCount cbIn, IOByteCount *pcbOut); + /** @} */ + + static bool isClientTask(task_t ClientTask); + +private: + /** Guard against the parent class growing and us using outdated headers. */ + uint8_t m_abSafetyPadding[256]; + /** The service provider. */ + org_virtualbox_VBoxUSB *m_pProvider; + /** The client task. */ + task_t m_Task; + /** The client process. */ + RTPROCESS m_Process; + /** Pointer to the next user client. */ + org_virtualbox_VBoxUSBClient * volatile m_pNext; + /** List of user clients. Protected by g_Mtx. */ + static org_virtualbox_VBoxUSBClient * volatile s_pHead; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBClient, IOUserClient); + + +/** + * The IOUSBDevice driver class. + * + * The main purpose of this is hijack devices matching current filters. + * + * @remarks This is derived from IOUSBUserClientInit instead of IOService because we must make + * sure IOUSBUserClientInit::start() gets invoked for this provider. The problem is that + * there is some kind of magic that prevents this from happening if we boost the probe + * score to high. With the result that we don't have the required plugin entry for + * user land and consequently cannot open it. + * + * So, to avoid having to write a lot of code we just inherit from IOUSBUserClientInit + * and make some possibly bold assumptions about it not changing. This just means + * we'll have to keep an eye on the source apple releases or only call + * IOUSBUserClientInit::start() and hand the rest of the super calls to IOService. For + * now we're doing it by the C++ book. + */ +class org_virtualbox_VBoxUSBDevice : public IOUSBUserClientInit +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBDevice); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool start(IOService *pProvider); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOReturn message(UInt32 enmMsg, IOService *pProvider, void *pvArg = 0); + /** @} */ + + static void scheduleReleaseByOwner(RTPROCESS Owner); +private: + /** Padding to guard against parent class expanding (see class remarks). */ + uint8_t m_abSafetyPadding[256]; + /** The interface we're driving (aka. the provider). */ + IOUSBDevice *m_pDevice; + /** The owner process, meaning the VBoxSVC process. */ + RTPROCESS volatile m_Owner; + /** The client process, meaning the VM process. */ + RTPROCESS volatile m_Client; + /** The ID of the matching filter. */ + uintptr_t m_uId; + /** Have we opened the device or not? */ + bool volatile m_fOpen; + /** Should be open the device on the next close notification message? */ + bool volatile m_fOpenOnWasClosed; + /** Whether to re-enumerate this device when the client closes it. + * This is something we'll do when the filter owner dies. */ + bool volatile m_fReleaseOnClose; + /** Whether we're being unloaded or not. + * Only valid in stop(). */ + bool m_fBeingUnloaded; + /** Pointer to the next device in the list. */ + org_virtualbox_VBoxUSBDevice * volatile m_pNext; + /** Pointer to the list head. Protected by g_Mtx. */ + static org_virtualbox_VBoxUSBDevice * volatile s_pHead; + +#ifdef DEBUG + /** The interest notifier. */ + IONotifier *m_pNotifier; + + static IOReturn MyInterestHandler(void *pvTarget, void *pvRefCon, UInt32 enmMsgType, + IOService *pProvider, void * pvMsgArg, vm_size_t cbMsgArg); +#endif +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBDevice, IOUSBUserClientInit); + + +/** + * The IOUSBInterface driver class. + * + * The main purpose of this is hijack interfaces which device is driven + * by org_virtualbox_VBoxUSBDevice. + * + * @remarks See org_virtualbox_VBoxUSBDevice for why we use IOUSBUserClientInit. + */ +class org_virtualbox_VBoxUSBInterface : public IOUSBUserClientInit +{ + OSDeclareDefaultStructors(org_virtualbox_VBoxUSBInterface); + +public: + RTR0MEMEF_NEW_AND_DELETE_OPERATORS_IOKIT(); + + /** @name IOService + * @{ */ + virtual bool init(OSDictionary *pDictionary = 0); + virtual IOService *probe(IOService *pProvider, SInt32 *pi32Score); + virtual bool start(IOService *pProvider); + virtual bool terminate(IOOptionBits fOptions = 0); + virtual void stop(IOService *pProvider); + virtual void free(); + virtual IOReturn message(UInt32 enmMsg, IOService *pProvider, void *pvArg = 0); + /** @} */ + +private: + /** Padding to guard against parent class expanding (see class remarks). */ + uint8_t m_abSafetyPadding[256]; + /** The interface we're driving (aka. the provider). */ + IOUSBInterface *m_pInterface; + /** Have we opened the device or not? */ + bool volatile m_fOpen; + /** Should be open the device on the next close notification message? */ + bool volatile m_fOpenOnWasClosed; +}; +OSDefineMetaClassAndStructors(org_virtualbox_VBoxUSBInterface, IOUSBUserClientInit); + + + + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/* + * Declare the module stuff. + */ +RT_C_DECLS_BEGIN +extern kern_return_t _start(struct kmod_info *pKModInfo, void *pvData); +extern kern_return_t _stop(struct kmod_info *pKModInfo, void *pvData); + +KMOD_EXPLICIT_DECL(VBoxDrv, VBOX_VERSION_STRING, _start, _stop) +DECLHIDDEN(kmod_start_func_t *) _realmain = VBoxUSBStart; +DECLHIDDEN(kmod_stop_func_t *) _antimain = VBoxUSBStop; +DECLHIDDEN(int) _kext_apple_cc = __APPLE_CC__; +RT_C_DECLS_END + +/** Mutex protecting the lists. */ +static RTSEMFASTMUTEX g_Mtx = NIL_RTSEMFASTMUTEX; +org_virtualbox_VBoxUSBClient * volatile org_virtualbox_VBoxUSBClient::s_pHead = NULL; +org_virtualbox_VBoxUSBDevice * volatile org_virtualbox_VBoxUSBDevice::s_pHead = NULL; + +/** Global instance count - just for checking proving that everything is destroyed correctly. */ +static volatile uint32_t g_cInstances = 0; + + +/** + * Start the kernel module. + */ +static kern_return_t VBoxUSBStart(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + int rc; + Log(("VBoxUSBStart\n")); + + /* + * Initialize IPRT. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Create the spinlock. + */ + rc = RTSemFastMutexCreate(&g_Mtx); + if (RT_SUCCESS(rc)) + { + rc = VBoxUSBFilterInit(); + if (RT_SUCCESS(rc)) + { +#if 0 /* testing */ + USBFILTER Flt; + USBFilterInit(&Flt, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&Flt, USBFILTERIDX_VENDOR_ID, 0x096e, true); + uintptr_t uId; + rc = VBoxUSBFilterAdd(&Flt, 1, &uId); + printf("VBoxUSB: VBoxUSBFilterAdd #1 -> %d + %p\n", rc, uId); + + USBFilterInit(&Flt, USBFILTERTYPE_CAPTURE); + USBFilterSetStringPattern(&Flt, USBFILTERIDX_PRODUCT_STR, "*DISK*", true); + rc = VBoxUSBFilterAdd(&Flt, 2, &uId); + printf("VBoxUSB: VBoxUSBFilterAdd #2 -> %d + %p\n", rc, uId); +#endif + return KMOD_RETURN_SUCCESS; + } + printf("VBoxUSB: VBoxUSBFilterInit failed (rc=%d)\n", rc); + RTSemFastMutexDestroy(g_Mtx); + g_Mtx = NIL_RTSEMFASTMUTEX; + } + else + printf("VBoxUSB: RTSemFastMutexCreate failed (rc=%d)\n", rc); + RTR0Term(); + } + else + printf("VBoxUSB: failed to initialize IPRT (rc=%d)\n", rc); + + return KMOD_RETURN_FAILURE; +} + + +/** + * Stop the kernel module. + */ +static kern_return_t VBoxUSBStop(struct kmod_info *pKModInfo, void *pvData) +{ + RT_NOREF(pKModInfo, pvData); + Log(("VBoxUSBStop: g_cInstances=%d\n", g_cInstances)); + + /** @todo Fix problem with crashing when unloading a driver that's in use. */ + + /* + * Undo the work done during start (in reverse order). + */ + VBoxUSBFilterTerm(); + + int rc = RTSemFastMutexDestroy(g_Mtx); + AssertRC(rc); + g_Mtx = NIL_RTSEMFASTMUTEX; + + RTR0Term(); + + Log(("VBoxUSBStop - done\n")); + return KMOD_RETURN_SUCCESS; +} + + + + + +#ifdef LOG_ENABLED +/** + * Gets the name of a IOKit message. + * + * @returns Message name (read only). + * @param enmMsg The message. + */ +DECLINLINE(const char *) DbgGetIOKitMessageName(UInt32 enmMsg) +{ + switch (enmMsg) + { +# define MY_CASE(enm) case enm: return #enm; break + MY_CASE(kIOMessageServiceIsTerminated); + MY_CASE(kIOMessageServiceIsSuspended); + MY_CASE(kIOMessageServiceIsResumed); + MY_CASE(kIOMessageServiceIsRequestingClose); + MY_CASE(kIOMessageServiceIsAttemptingOpen); + MY_CASE(kIOMessageServiceWasClosed); + MY_CASE(kIOMessageServiceBusyStateChange); + MY_CASE(kIOMessageServicePropertyChange); + MY_CASE(kIOMessageCanDevicePowerOff); + MY_CASE(kIOMessageDeviceWillPowerOff); + MY_CASE(kIOMessageDeviceWillNotPowerOff); + MY_CASE(kIOMessageDeviceHasPoweredOn); + MY_CASE(kIOMessageCanSystemPowerOff); + MY_CASE(kIOMessageSystemWillPowerOff); + MY_CASE(kIOMessageSystemWillNotPowerOff); + MY_CASE(kIOMessageCanSystemSleep); + MY_CASE(kIOMessageSystemWillSleep); + MY_CASE(kIOMessageSystemWillNotSleep); + MY_CASE(kIOMessageSystemHasPoweredOn); + MY_CASE(kIOMessageSystemWillRestart); + MY_CASE(kIOMessageSystemWillPowerOn); + MY_CASE(kIOUSBMessageHubResetPort); + MY_CASE(kIOUSBMessageHubSuspendPort); + MY_CASE(kIOUSBMessageHubResumePort); + MY_CASE(kIOUSBMessageHubIsDeviceConnected); + MY_CASE(kIOUSBMessageHubIsPortEnabled); + MY_CASE(kIOUSBMessageHubReEnumeratePort); + MY_CASE(kIOUSBMessagePortHasBeenReset); + MY_CASE(kIOUSBMessagePortHasBeenResumed); + MY_CASE(kIOUSBMessageHubPortClearTT); + MY_CASE(kIOUSBMessagePortHasBeenSuspended); + MY_CASE(kIOUSBMessageFromThirdParty); + MY_CASE(kIOUSBMessagePortWasNotSuspended); + MY_CASE(kIOUSBMessageExpressCardCantWake); +// MY_CASE(kIOUSBMessageCompositeDriverReconfigured); +# undef MY_CASE + } + return "unknown"; +} +#endif /* LOG_ENABLED */ + + + + + +/* + * + * org_virtualbox_VBoxUSB + * + */ + + +/** + * Initialize the object. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSB::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + if (IOService::init(pDictionary)) + { + /* init members. */ + return true; + } + ASMAtomicDecU32(&g_cInstances); + return false; +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSB::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOService::free(); +} + + +/** + * Start this service. + */ +bool +org_virtualbox_VBoxUSB::start(IOService *pProvider) +{ + Log(("VBoxUSB::start([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + if (IOService::start(pProvider)) + { + /* register the service. */ + registerService(); + return true; + } + return false; +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::stop(IOService *pProvider) +{ + Log(("VBoxUSB::stop([%p], %p (%s))\n", this, pProvider, pProvider->getName())); + IOService::stop(pProvider); +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::open(IOService *pForClient, IOOptionBits fOptions/* = 0*/, void *pvArg/* = 0*/) +{ + Log(("VBoxUSB::open([%p], %p, %#x, %p)\n", this, pForClient, fOptions, pvArg)); + bool fRc = IOService::open(pForClient, fOptions, pvArg); + Log(("VBoxUSB::open([%p], %p, %#x, %p) -> %d\n", this, pForClient, fOptions, pvArg, fRc)); + return fRc; +} + + +/** + * Stop this service. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSB::close(IOService *pForClient, IOOptionBits fOptions/* = 0*/) +{ + Log(("VBoxUSB::close([%p], %p, %#x)\n", this, pForClient, fOptions)); + IOService::close(pForClient, fOptions); +} + + +/** + * Terminate request. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSB::terminate(IOOptionBits fOptions) +{ + Log(("VBoxUSB::terminate([%p], %#x): g_cInstances=%d\n", this, fOptions, g_cInstances)); + bool fRc = IOService::terminate(fOptions); + Log(("VBoxUSB::terminate([%p], %#x): returns %d\n", this, fOptions, fRc)); + return fRc; +} + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBClient + * + */ + + +/** + * Initializer called when the client opens the service. + */ +bool +org_virtualbox_VBoxUSBClient::initWithTask(task_t OwningTask, void *pvSecurityId, UInt32 u32Type) +{ + if (!OwningTask) + { + Log(("VBoxUSBClient::initWithTask([%p], %p, %p, %#x) -> false (no task)\n", this, OwningTask, pvSecurityId, u32Type)); + return false; + } + if (u32Type != VBOXUSB_DARWIN_IOSERVICE_COOKIE) + { + Log(("VBoxUSBClient::initWithTask: Bade cookie %#x\n", u32Type)); + return false; + } + + proc_t pProc = (proc_t)get_bsdtask_info(OwningTask); /* we need the pid */ + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x)\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type)); + + if (IOUserClient::initWithTask(OwningTask, pvSecurityId , u32Type)) + { + /* + * In theory we have to call task_reference() to make sure that the task is + * valid during the lifetime of this object. The pointer is only used to check + * for the context this object is called in though and never dereferenced + * or passed to anything which might, so we just skip this step. + */ + m_pProvider = NULL; + m_Task = OwningTask; + m_Process = pProc ? proc_pid(pProc) : NIL_RTPROCESS; + m_pNext = NULL; + + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x) -> true; new g_cInstances=%d\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type, cInstances)); + RT_NOREF_PV(cInstances); + return true; + } + + Log(("VBoxUSBClient::initWithTask([%p], %p(->%p:{.pid=%d}, %p, %#x) -> false\n", + this, OwningTask, pProc, pProc ? proc_pid(pProc) : -1, pvSecurityId, u32Type)); + return false; +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBClient::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBClient::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUserClient::free(); +} + + +/** + * Start the client service. + */ +bool +org_virtualbox_VBoxUSBClient::start(IOService *pProvider) +{ + Log(("VBoxUSBClient::start([%p], %p)\n", this, pProvider)); + if (IOUserClient::start(pProvider)) + { + m_pProvider = OSDynamicCast(org_virtualbox_VBoxUSB, pProvider); + if (m_pProvider) + { + /* + * Add ourselves to the list of user clients. + */ + VBOXUSB_LOCK(); + + m_pNext = s_pHead; + s_pHead = this; + + VBOXUSB_UNLOCK(); + + return true; + } + Log(("VBoxUSBClient::start: %p isn't org_virtualbox_VBoxUSB\n", pProvider)); + } + return false; +} + + +/** + * Client exits normally. + */ +IOReturn +org_virtualbox_VBoxUSBClient::clientClose(void) +{ + Log(("VBoxUSBClient::clientClose([%p:{.m_Process=%d}])\n", this, (int)m_Process)); + + /* + * Remove this process from the client list. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBClient *pPrev = NULL; + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); + + /* + * Drop all filters owned by this client. + */ + if (m_Process != NIL_RTPROCESS) + VBoxUSBFilterRemoveOwner(m_Process); + + /* + * Schedule all devices owned (filtered) by this process for + * immediate release or release upon close. + */ + if (m_Process != NIL_RTPROCESS) + org_virtualbox_VBoxUSBDevice::scheduleReleaseByOwner(m_Process); + + /* + * Initiate termination. + */ + m_pProvider = NULL; + terminate(); + + return kIOReturnSuccess; +} + + +/** + * The client exits abnormally / forgets to do cleanups. + * @remark Only for logging. + */ +IOReturn +org_virtualbox_VBoxUSBClient::clientDied(void) +{ + Log(("VBoxUSBClient::clientDied([%p]) m_Task=%p R0Process=%p Process=%d\n", + this, m_Task, RTR0ProcHandleSelf(), RTProcSelf())); + + /* IOUserClient::clientDied() calls clientClose... */ + return IOUserClient::clientDied(); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBClient::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBClient::terminate([%p], %#x)\n", this, fOptions)); + return IOUserClient::terminate(fOptions); +} + + +/** + * The final stage of the client service destruction. + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBClient::finalize(IOOptionBits fOptions) +{ + Log(("VBoxUSBClient::finalize([%p], %#x)\n", this, fOptions)); + return IOUserClient::finalize(fOptions); +} + + +/** + * Stop the client service. + */ +void +org_virtualbox_VBoxUSBClient::stop(IOService *pProvider) +{ + Log(("VBoxUSBClient::stop([%p])\n", this)); + IOUserClient::stop(pProvider); + + /* + * Paranoia. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBClient *pPrev = NULL; + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); +} + + +/** + * Translate a user method index into a service object and an external method structure. + * + * @returns Pointer to external method structure descripting the method. + * NULL if the index isn't valid. + * @param ppService Where to store the service object on success. + * @param iMethod The method index. + */ +IOExternalMethod * +org_virtualbox_VBoxUSBClient::getTargetAndMethodForIndex(IOService **ppService, UInt32 iMethod) +{ + static IOExternalMethod s_aMethods[VBOXUSBMETHOD_END] = + { + /*[VBOXUSBMETHOD_ADD_FILTER] = */ + { + (IOService *)0, /* object */ + (IOMethod)&org_virtualbox_VBoxUSBClient::addFilter, /* func */ + kIOUCStructIStructO, /* flags - struct input (count0) and struct output (count1) */ + sizeof(USBFILTER), /* count0 - size of the input struct. */ + sizeof(VBOXUSBADDFILTEROUT) /* count1 - size of the return struct. */ + }, + /* [VBOXUSBMETHOD_FILTER_REMOVE] = */ + { + (IOService *)0, /* object */ + (IOMethod)&org_virtualbox_VBoxUSBClient::removeFilter, /* func */ + kIOUCStructIStructO, /* flags - struct input (count0) and struct output (count1) */ + sizeof(uintptr_t), /* count0 - size of the input (id) */ + sizeof(int) /* count1 - size of the output (rc) */ + }, + }; + + if (RT_UNLIKELY(iMethod >= RT_ELEMENTS(s_aMethods))) + return NULL; + + *ppService = this; + return &s_aMethods[iMethod]; +} + + +/** + * Add filter user request. + * + * @returns IOKit status code. + * @param pFilter The filter to add. + * @param pOut Pointer to the output structure. + * @param cbFilter Size of the filter structure. + * @param pcbOut In/Out - sizeof(*pOut). + */ +IOReturn +org_virtualbox_VBoxUSBClient::addFilter(PUSBFILTER pFilter, PVBOXUSBADDFILTEROUT pOut, IOByteCount cbFilter, IOByteCount *pcbOut) +{ + Log(("VBoxUSBClient::addFilter: [%p:{.m_Process=%d}] pFilter=%p pOut=%p\n", this, (int)m_Process, pFilter, pOut)); + + /* + * Validate input. + */ + if (RT_UNLIKELY( cbFilter != sizeof(*pFilter) + || *pcbOut != sizeof(*pOut))) + { + printf("VBoxUSBClient::addFilter: cbFilter=%#x expected %#x; *pcbOut=%#x expected %#x\n", + (int)cbFilter, (int)sizeof(*pFilter), (int)*pcbOut, (int)sizeof(*pOut)); + return kIOReturnBadArgument; + } + + /* + * Log the filter details. + */ +#ifdef DEBUG + Log2(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT))); + Log2(("VBoxUSBClient::addFilter: 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>")); +#endif + + /* + * Since we cannot query the bus number, make sure the filter + * isn't requiring that field to be present. + */ + int rc = USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false /* fMustBePresent */); AssertRC(rc); + + /* + * Add the filter. + */ + pOut->uId = 0; + pOut->rc = VBoxUSBFilterAdd(pFilter, m_Process, &pOut->uId); + + Log(("VBoxUSBClient::addFilter: returns *pOut={.rc=%d, .uId=%p}\n", pOut->rc, (void *)pOut->uId)); + return kIOReturnSuccess; +} + + +/** + * Removes filter user request. + * + * @returns IOKit status code. + * @param puId Where to get the filter ID. + * @param prc Where to store the return code. + * @param cbIn sizeof(*puId). + * @param pcbOut In/Out - sizeof(*prc). + */ +IOReturn +org_virtualbox_VBoxUSBClient::removeFilter(uintptr_t *puId, int *prc, IOByteCount cbIn, IOByteCount *pcbOut) +{ + Log(("VBoxUSBClient::removeFilter: [%p:{.m_Process=%d}] *puId=%p m_Proc\n", this, (int)m_Process, *puId)); + + /* + * Validate input. + */ + if (RT_UNLIKELY( cbIn != sizeof(*puId) + || *pcbOut != sizeof(*prc))) + { + printf("VBoxUSBClient::removeFilter: cbIn=%#x expected %#x; *pcbOut=%#x expected %#x\n", + (int)cbIn, (int)sizeof(*puId), (int)*pcbOut, (int)sizeof(*prc)); + return kIOReturnBadArgument; + } + + /* + * Remove the filter. + */ + *prc = VBoxUSBFilterRemove(m_Process, *puId); + + Log(("VBoxUSBClient::removeFilter: returns *prc=%d\n", *prc)); + return kIOReturnSuccess; +} + + +/** + * Checks whether the specified task is a VBoxUSB client task or not. + * + * This is used to validate clients trying to open any of the device + * or interfaces that we've hijacked. + * + * @returns true / false. + * @param ClientTask The task. + * + * @remark This protecting against other user clients is not currently implemented + * as it turned out to be more bothersome than first imagined. + */ +/* static*/ bool +org_virtualbox_VBoxUSBClient::isClientTask(task_t ClientTask) +{ + VBOXUSB_LOCK(); + + for (org_virtualbox_VBoxUSBClient *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + if (pCur->m_Task == ClientTask) + { + VBOXUSB_UNLOCK(); + return true; + } + + VBOXUSB_UNLOCK(); + return false; +} + + + + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBDevice + * + */ + +/** + * Initialize instance data. + * + * @returns Success indicator. + * @param pDictionary The dictionary that will become the registry entry's + * property table, or NULL. Hand it up to our parents. + */ +bool +org_virtualbox_VBoxUSBDevice::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBDevice::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + m_pDevice = NULL; + m_Owner = NIL_RTPROCESS; + m_Client = NIL_RTPROCESS; + m_uId = ~(uintptr_t)0; + m_fOpen = false; + m_fOpenOnWasClosed = false; + m_fReleaseOnClose = false; + m_fBeingUnloaded = false; + m_pNext = NULL; +#ifdef DEBUG + m_pNotifier = NULL; +#endif + + return IOUSBUserClientInit::init(pDictionary); +} + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBDevice::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBDevice::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUSBUserClientInit::free(); +} + + +/** + * The device/driver probing. + * + * I/O Kit will iterate all device drivers suitable for this kind of device + * (this is something it figures out from the property file) and call their + * probe() method in order to try determine which is the best match for the + * device. We will match the device against the registered filters and set + * a ridiculously high score if we find it, thus making it extremely likely + * that we'll be the first driver to be started. We'll also set a couple of + * attributes so that it's not necessary to do a rematch in init to find + * the appropriate filter (might not be necessary..., see todo). + * + * @returns Service instance to be started and *pi32Score if matching. + * NULL if not a device suitable for this driver. + * + * @param pProvider The provider instance. + * @param pi32Score Where to store the probe score. + */ +IOService * +org_virtualbox_VBoxUSBDevice::probe(IOService *pProvider, SInt32 *pi32Score) +{ + Log(("VBoxUSBDevice::probe([%p], %p {%s}, %p={%d})\n", this, + pProvider, pProvider->getName(), pi32Score, pi32Score ? *pi32Score : 0)); + + /* + * Check against filters. + */ + USBFILTER Device; + USBFilterInit(&Device, USBFILTERTYPE_CAPTURE); + + static const struct + { + const char *pszName; + USBFILTERIDX enmIdx; + bool fNumeric; + } s_aProps[] = + { + { kUSBVendorID, USBFILTERIDX_VENDOR_ID, true }, + { kUSBProductID, USBFILTERIDX_PRODUCT_ID, true }, + { kUSBDeviceReleaseNumber, USBFILTERIDX_DEVICE_REV, true }, + { kUSBDeviceClass, USBFILTERIDX_DEVICE_CLASS, true }, + { kUSBDeviceSubClass, USBFILTERIDX_DEVICE_SUB_CLASS, true }, + { kUSBDeviceProtocol, USBFILTERIDX_DEVICE_PROTOCOL, true }, + { "PortNum", USBFILTERIDX_PORT, true }, + /// @todo { , USBFILTERIDX_BUS, true }, - must be derived :-/ + /// Seems to be the upper byte of locationID and our "grand parent" has a USBBusNumber prop. + { "USB Vendor Name", USBFILTERIDX_MANUFACTURER_STR, false }, + { "USB Product Name", USBFILTERIDX_PRODUCT_STR, false }, + { "USB Serial Number", USBFILTERIDX_SERIAL_NUMBER_STR, false }, + }; + for (unsigned i = 0; i < RT_ELEMENTS(s_aProps); i++) + { + OSObject *pObj = pProvider->getProperty(s_aProps[i].pszName); + if (!pObj) + continue; + if (s_aProps[i].fNumeric) + { + OSNumber *pNum = OSDynamicCast(OSNumber, pObj); + if (pNum) + { + uint16_t u16 = pNum->unsigned16BitValue(); + Log2(("VBoxUSBDevice::probe: %d/%s - %#x (32bit=%#x)\n", i, s_aProps[i].pszName, u16, pNum->unsigned32BitValue())); + int vrc = USBFilterSetNumExact(&Device, s_aProps[i].enmIdx, u16, true); + if (RT_FAILURE(vrc)) + Log(("VBoxUSBDevice::probe: pObj=%p pNum=%p - %d/%s - rc=%d!\n", pObj, pNum, i, s_aProps[i].pszName, vrc)); + } + else + Log(("VBoxUSBDevice::probe: pObj=%p pNum=%p - %d/%s!\n", pObj, pNum, i, s_aProps[i].pszName)); + } + else + { + OSString *pStr = OSDynamicCast(OSString, pObj); + if (pStr) + { + Log2(("VBoxUSBDevice::probe: %d/%s - %s\n", i, s_aProps[i].pszName, pStr->getCStringNoCopy())); + int vrc = USBFilterSetStringExact(&Device, s_aProps[i].enmIdx, pStr->getCStringNoCopy(), + true /*fMustBePresent*/, true /*fPurge*/); + if (RT_FAILURE(vrc)) + Log(("VBoxUSBDevice::probe: pObj=%p pStr=%p - %d/%s - rc=%d!\n", pObj, pStr, i, s_aProps[i].pszName, vrc)); + } + else + Log(("VBoxUSBDevice::probe: pObj=%p pStr=%p - %d/%s\n", pObj, pStr, i, s_aProps[i].pszName)); + } + } + /** @todo try figure the blasted bus number */ + + /* + * Run filters on it. + */ + uintptr_t uId = 0; + RTPROCESS Owner = VBoxUSBFilterMatch(&Device, &uId); + USBFilterDelete(&Device); + if (Owner == NIL_RTPROCESS) + { + Log(("VBoxUSBDevice::probe: returns NULL uId=%d\n", uId)); + return NULL; + } + + /* + * It matched. Save the owner in the provider registry (hope that works). + */ + /*IOService *pRet = IOUSBUserClientInit::probe(pProvider, pi32Score); - call always returns NULL on 10.11+ */ + /*AssertMsg(pRet == this, ("pRet=%p this=%p *pi32Score=%d \n", pRet, this, pi32Score ? *pi32Score : 0)); - call always returns NULL on 10.11+ */ + IOService *pRet = this; + m_Owner = Owner; + m_uId = uId; + Log(("%p: m_Owner=%d m_uId=%d\n", this, (int)m_Owner, (int)m_uId)); + *pi32Score = _1G; + Log(("VBoxUSBDevice::probe: returns %p and *pi32Score=%d\n", pRet, *pi32Score)); + return pRet; +} + + +/** + * Try start the device driver. + * + * We will do device linking, copy the filter and owner properties from the provider, + * set the client property, retain the device, and try open (seize) the device. + * + * @returns Success indicator. + * @param pProvider The provider instance. + */ +bool +org_virtualbox_VBoxUSBDevice::start(IOService *pProvider) +{ + Log(("VBoxUSBDevice::start([%p:{.m_Owner=%d, .m_uId=%p}], %p {%s})\n", + this, m_Owner, m_uId, pProvider, pProvider->getName())); + + m_pDevice = OSDynamicCast(IOUSBDevice, pProvider); + if (!m_pDevice) + { + printf("VBoxUSBDevice::start([%p], %p {%s}): failed!\n", this, pProvider, pProvider->getName()); + return false; + } + +#ifdef DEBUG + /* for some extra log messages */ + m_pNotifier = pProvider->registerInterest(gIOGeneralInterest, + &org_virtualbox_VBoxUSBDevice::MyInterestHandler, + this, /* pvTarget */ + NULL); /* pvRefCon */ +#endif + + /* + * Exploit IOUSBUserClientInit to process IOProviderMergeProperties. + */ + IOUSBUserClientInit::start(pProvider); /* returns false */ + + /* + * Link ourselves into the list of hijacked device. + */ + VBOXUSB_LOCK(); + + m_pNext = s_pHead; + s_pHead = this; + + VBOXUSB_UNLOCK(); + + /* + * Set the VBoxUSB properties. + */ + if (!setProperty(VBOXUSB_OWNER_KEY, (unsigned long long)m_Owner, sizeof(m_Owner) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_OWNER_KEY "' property!\n")); + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + if (!setProperty(VBOXUSB_FILTER_KEY, (unsigned long long)m_uId, sizeof(m_uId) * 8 /* bits */)) + Log(("VBoxUSBDevice::start: failed to set the '" VBOXUSB_FILTER_KEY "' property!\n")); + + /* + * Retain and open the device. + */ + m_pDevice->retain(); + m_fOpen = m_pDevice->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBDevice::start: failed to open the device!\n")); + m_fOpenOnWasClosed = !m_fOpen; + + Log(("VBoxUSBDevice::start: returns %d\n", true)); + return true; +} + + +/** + * Stop the device driver. + * + * We'll unlink the device, start device re-enumeration and close it. And call + * the parent stop method of course. + * + * @param pProvider The provider instance. + */ +void +org_virtualbox_VBoxUSBDevice::stop(IOService *pProvider) +{ + Log(("VBoxUSBDevice::stop([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Remove ourselves from the list of device. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBDevice *pPrev = NULL; + for (org_virtualbox_VBoxUSBDevice *pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + if (pCur == this) + { + if (pPrev) + pPrev->m_pNext = m_pNext; + else + s_pHead = m_pNext; + m_pNext = NULL; + break; + } + pPrev = pCur; + } + + VBOXUSB_UNLOCK(); + + /* + * Should we release the device? + */ + if (m_fBeingUnloaded) + { + if (m_pDevice) + { + IOReturn irc = m_pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): m_pDevice=%p unload & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), m_pDevice, irc)); + } + else + { + IOUSBDevice *pDevice = OSDynamicCast(IOUSBDevice, pProvider); + if (pDevice) + { + IOReturn irc = pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): pDevice=%p unload & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), pDevice, irc)); + } + else + Log(("VBoxUSBDevice::stop([%p], %p {%s}): failed to cast provider to IOUSBDevice\n", + this, pProvider, pProvider->getName())); + } + } + else if (m_fReleaseOnClose) + { + ASMAtomicWriteBool(&m_fReleaseOnClose, false); + if (m_pDevice) + { + IOReturn irc = m_pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::stop([%p], %p {%s}): m_pDevice=%p close & ReEnumerateDevice -> %#x\n", + this, pProvider, pProvider->getName(), m_pDevice, irc)); + } + } + + /* + * Close and release the IOUSBDevice if didn't do that already in message(). + */ + if (m_pDevice) + { + /* close it */ + if (m_fOpen) + { + m_fOpenOnWasClosed = false; + m_fOpen = false; + m_pDevice->close(this, 0); + } + + /* release it (see start()) */ + m_pDevice->release(); + m_pDevice = NULL; + } + +#ifdef DEBUG + /* avoid crashing on unload. */ + if (m_pNotifier) + { + m_pNotifier->release(); + m_pNotifier = NULL; + } +#endif + + IOUSBUserClientInit::stop(pProvider); + Log(("VBoxUSBDevice::stop: returns void\n")); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBDevice::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBDevice::terminate([%p], %#x)\n", this, fOptions)); + + /* + * There aren't too many reasons why we gets terminated. + * The most common one is that the device is being unplugged. Another is + * that we've triggered reenumeration. In both cases we'll get a + * kIOMessageServiceIsTerminated message before we're stopped. + * + * But, when we're unloaded the provider service isn't terminated, and + * for some funny reason we're frequently causing kernel panics when the + * device is detached (after we're unloaded). So, for now, let's try + * re-enumerate it in stop. + * + * To avoid creating unnecessary trouble we'll try guess if we're being + * unloaded from the option bit mask. (kIOServiceRecursing is private btw.) + */ + /** @todo would be nice if there was a documented way of doing the unload detection this, or + * figure out what exactly we're doing wrong in the unload scenario. */ + if ((fOptions & 0xffff) == (kIOServiceRequired | kIOServiceSynchronous)) + m_fBeingUnloaded = true; + + return IOUSBUserClientInit::terminate(fOptions); +} + + +/** + * Intercept open requests and only let Mr. Right (the VM process) open the device. + * This is where it all gets a bit complicated... + * + * @return Status code. + * + * @param enmMsg The message number. + * @param pProvider Pointer to the provider instance. + * @param pvArg Message argument. + */ +IOReturn +org_virtualbox_VBoxUSBDevice::message(UInt32 enmMsg, IOService *pProvider, void *pvArg) +{ + Log(("VBoxUSBDevice::message([%p], %#x {%s}, %p {%s}, %p) - pid=%d\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, RTProcSelf())); + + IOReturn irc; + switch (enmMsg) + { + /* + * This message is send to the current IOService client from IOService::handleOpen(), + * expecting it to call pProvider->close() if it agrees to the other party seizing + * the service. It is also called in IOService::didTerminate() and perhaps some other + * odd places. The way to find out is to examin the pvArg, which would be including + * kIOServiceSeize if it's the handleOpen case. + * + * How to validate that the other end is actually our VM process? Well, IOKit doesn't + * provide any clue about the new client really. But fortunately, it seems like the + * calling task/process context when the VM tries to open the device is the VM process. + * We'll ASSUME this'll remain like this for now... + */ + case kIOMessageServiceIsRequestingClose: + irc = kIOReturnExclusiveAccess; + /* If it's not a seize request, assume it's didTerminate and pray that it isn't a rouge driver. + ... weird, doesn't seem to match for the post has-terminated messages. */ + if (!((uintptr_t)pvArg & kIOServiceSeize)) + { + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d: not seize - closing...\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pDevice) + m_pDevice->close(this, 0); + m_Client = NIL_RTPROCESS; + irc = kIOReturnSuccess; + } + else + { + if (org_virtualbox_VBoxUSBClient::isClientTask(current_task())) + { + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d task=%p: client process, closing.\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pDevice) + m_pDevice->close(this, 0); + m_fOpenOnWasClosed = true; + m_Client = RTProcSelf(); + irc = kIOReturnSuccess; + } + else + Log(("VBoxUSBDevice::message([%p],%p {%s}, %p) - pid=%d task=%p: not client process!\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + } + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::message: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + break; + + /* + * The service was closed by the current client. + * Update the client property, check for scheduled re-enumeration and re-open. + * + * Note that we will not be called if we're doing the closing. (Even if we was + * called in that case, the code should be able to handle it.) + */ + case kIOMessageServiceWasClosed: + /* + * Update the client property value. + */ + if (m_Client != NIL_RTPROCESS) + { + m_Client = NIL_RTPROCESS; + if (!setProperty(VBOXUSB_CLIENT_KEY, (unsigned long long)m_Client, sizeof(m_Client) * 8 /* bits */)) + Log(("VBoxUSBDevice::message: failed to set the '" VBOXUSB_CLIENT_KEY "' property!\n")); + } + + if (m_pDevice) + { + /* + * Should we release the device? + */ + if (ASMAtomicXchgBool(&m_fReleaseOnClose, false)) + { + m_fOpenOnWasClosed = false; + irc = m_pDevice->ReEnumerateDevice(0); + Log(("VBoxUSBDevice::message([%p], %p {%s}) - ReEnumerateDevice() -> %#x\n", + this, pProvider, pProvider->getName(), irc)); + } + /* + * Should we attempt to re-open the device? + */ + else if (m_fOpenOnWasClosed) + { + Log(("VBoxUSBDevice::message: attempting to re-open the device...\n")); + m_fOpenOnWasClosed = false; + m_fOpen = m_pDevice->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBDevice::message: failed to open the device!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + /* + * The IOUSBDevice is shutting down, so close it if we've opened it. + */ + case kIOMessageServiceIsTerminated: + m_fBeingUnloaded = false; + ASMAtomicWriteBool(&m_fReleaseOnClose, false); + if (m_pDevice) + { + /* close it */ + if (m_fOpen) + { + m_fOpen = false; + m_fOpenOnWasClosed = false; + Log(("VBoxUSBDevice::message: closing the device (%p)...\n", m_pDevice)); + m_pDevice->close(this, 0); + } + + /* release it (see start()) */ + Log(("VBoxUSBDevice::message: releasing the device (%p)...\n", m_pDevice)); + m_pDevice->release(); + m_pDevice = NULL; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + default: + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + } + + Log(("VBoxUSBDevice::message([%p], %#x {%s}, %p {%s}, %p) -> %#x\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, irc)); + return irc; +} + + +/** + * Schedule all devices belonging to the specified process for release. + * + * Devices that aren't currently in use will be released immediately. + * + * @param Owner The owner process. + */ +/* static */ void +org_virtualbox_VBoxUSBDevice::scheduleReleaseByOwner(RTPROCESS Owner) +{ + Log2(("VBoxUSBDevice::scheduleReleaseByOwner: Owner=%d\n", Owner)); + AssertReturnVoid(Owner && Owner != NIL_RTPROCESS); + + /* + * Walk the list of devices looking for device belonging to this process. + * + * If we release a device, we have to lave the spinlock and will therefore + * have to restart the search. + */ + VBOXUSB_LOCK(); + + org_virtualbox_VBoxUSBDevice *pCur; + do + { + for (pCur = s_pHead; pCur; pCur = pCur->m_pNext) + { + Log2(("VBoxUSBDevice::scheduleReleaseByOwner: pCur=%p m_Owner=%d (%s) m_fReleaseOnClose=%d\n", + pCur, pCur->m_Owner, pCur->m_Owner == Owner ? "match" : "mismatch", pCur->m_fReleaseOnClose)); + if (pCur->m_Owner == Owner) + { + /* make sure we won't hit it again. */ + pCur->m_Owner = NIL_RTPROCESS; + IOUSBDevice *pDevice = pCur->m_pDevice; + if ( pDevice + && !pCur->m_fReleaseOnClose) + { + pCur->m_fOpenOnWasClosed = false; + if (pCur->m_Client != NIL_RTPROCESS) + { + /* It's currently open, so just schedule it for re-enumeration on close. */ + ASMAtomicWriteBool(&pCur->m_fReleaseOnClose, true); + Log(("VBoxUSBDevice::scheduleReleaseByOwner: %p {%s} - used by %d\n", + pDevice, pDevice->getName(), pCur->m_Client)); + } + else + { + /* + * Get the USBDevice object and do the re-enumeration now. + * Retain the device so we don't run into any trouble. + */ + pDevice->retain(); + VBOXUSB_UNLOCK(); + + IOReturn irc = pDevice->ReEnumerateDevice(0); NOREF(irc); + Log(("VBoxUSBDevice::scheduleReleaseByOwner: %p {%s} - ReEnumerateDevice -> %#x\n", + pDevice, pDevice->getName(), irc)); + + pDevice->release(); + VBOXUSB_LOCK(); + break; + } + } + } + } + } while (pCur); + + VBOXUSB_UNLOCK(); +} + + +#ifdef DEBUG +/*static*/ IOReturn +org_virtualbox_VBoxUSBDevice::MyInterestHandler(void *pvTarget, void *pvRefCon, UInt32 enmMsgType, + IOService *pProvider, void * pvMsgArg, vm_size_t cbMsgArg) +{ + org_virtualbox_VBoxUSBDevice *pThis = (org_virtualbox_VBoxUSBDevice *)pvTarget; + if (!pThis) + return kIOReturnError; + + switch (enmMsgType) + { + case kIOMessageServiceIsAttemptingOpen: + /* pvMsgArg == the open() fOptions, so we could check for kIOServiceSeize if we care. + We'll also get a kIIOServiceRequestingClose message() for that... */ + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceIsAttemptingOpen - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOMessageServiceWasClosed: + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceWasClosed - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOMessageServiceIsTerminated: + Log(("VBoxUSBDevice::MyInterestHandler: kIOMessageServiceIsTerminated - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + case kIOUSBMessagePortHasBeenReset: + Log(("VBoxUSBDevice::MyInterestHandler: kIOUSBMessagePortHasBeenReset - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + + default: + Log(("VBoxUSBDevice::MyInterestHandler: %#x (%s) - pvRefCon=%p pProvider=%p pvMsgArg=%p cbMsgArg=%d\n", + enmMsgType, DbgGetIOKitMessageName(enmMsgType), pvRefCon, pProvider, pvMsgArg, cbMsgArg)); + break; + } + + return kIOReturnSuccess; +} +#endif /* DEBUG */ + + + + + + + + + + + + + + +/* + * + * org_virtualbox_VBoxUSBInterface + * + */ + +/** + * Initialize our data members. + */ +bool +org_virtualbox_VBoxUSBInterface::init(OSDictionary *pDictionary) +{ + uint32_t cInstances = ASMAtomicIncU32(&g_cInstances); + Log(("VBoxUSBInterface::init([%p], %p) new g_cInstances=%d\n", this, pDictionary, cInstances)); + RT_NOREF_PV(cInstances); + + m_pInterface = NULL; + m_fOpen = false; + m_fOpenOnWasClosed = false; + + return IOUSBUserClientInit::init(pDictionary); +} + + +/** + * Free the object. + * @remark Only for logging. + */ +void +org_virtualbox_VBoxUSBInterface::free() +{ + uint32_t cInstances = ASMAtomicDecU32(&g_cInstances); NOREF(cInstances); + Log(("VBoxUSBInterfaces::free([%p]) new g_cInstances=%d\n", this, cInstances)); + IOUSBUserClientInit::free(); +} + + +/** + * Probe the interface to see if we're the right driver for it. + * + * We implement this similarly to org_virtualbox_VBoxUSBDevice, except that + * we don't bother matching filters but instead just check if the parent is + * handled by org_virtualbox_VBoxUSBDevice or not. + */ +IOService * +org_virtualbox_VBoxUSBInterface::probe(IOService *pProvider, SInt32 *pi32Score) +{ + Log(("VBoxUSBInterface::probe([%p], %p {%s}, %p={%d})\n", this, + pProvider, pProvider->getName(), pi32Score, pi32Score ? *pi32Score : 0)); + + /* + * Check if VBoxUSBDevice is the parent's driver. + */ + bool fHijackIt = false; + const IORegistryPlane *pServicePlane = getPlane(kIOServicePlane); + IORegistryEntry *pParent = pProvider->getParentEntry(pServicePlane); + if (pParent) + { + Log(("VBoxUSBInterface::probe: pParent=%p {%s}\n", pParent, pParent->getName())); + + OSIterator *pSiblings = pParent->getChildIterator(pServicePlane); + if (pSiblings) + { + IORegistryEntry *pSibling; + while ( (pSibling = OSDynamicCast(IORegistryEntry, pSiblings->getNextObject())) ) + { + const OSMetaClass *pMetaClass = pSibling->getMetaClass(); + Log2(("sibling: %p - %s - %s\n", pMetaClass, pSibling->getName(), pMetaClass->getClassName())); + if (pMetaClass == &org_virtualbox_VBoxUSBDevice::gMetaClass) + { + fHijackIt = true; + break; + } + } + pSiblings->release(); + } + } + if (!fHijackIt) + { + Log(("VBoxUSBInterface::probe: returns NULL\n")); + return NULL; + } + + /* IOService *pRet = IOUSBUserClientInit::probe(pProvider, pi32Score); - call always returns NULL on 10.11+ */ + IOService *pRet = this; + *pi32Score = _1G; + Log(("VBoxUSBInterface::probe: returns %p and *pi32Score=%d - hijack it.\n", pRet, *pi32Score)); + return pRet; +} + + +/** + * Start the driver (this), retain and open the USB interface object (pProvider). + */ +bool +org_virtualbox_VBoxUSBInterface::start(IOService *pProvider) +{ + Log(("VBoxUSBInterface::start([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Exploit IOUSBUserClientInit to process IOProviderMergeProperties. + */ + IOUSBUserClientInit::start(pProvider); /* returns false */ + + /* + * Retain the and open the interface (stop() or message() cleans up). + */ + bool fRc = true; + m_pInterface = OSDynamicCast(IOUSBInterface, pProvider); + if (m_pInterface) + { + m_pInterface->retain(); + m_fOpen = m_pInterface->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBInterface::start: failed to open the interface!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + else + { + printf("VBoxUSBInterface::start([%p], %p {%s}): failed!\n", this, pProvider, pProvider->getName()); + fRc = false; + } + + Log(("VBoxUSBInterface::start: returns %d\n", fRc)); + return fRc; +} + + +/** + * Close and release the USB interface object (pProvider) and stop the driver (this). + */ +void +org_virtualbox_VBoxUSBInterface::stop(IOService *pProvider) +{ + Log(("org_virtualbox_VBoxUSBInterface::stop([%p], %p {%s})\n", this, pProvider, pProvider->getName())); + + /* + * Close and release the IOUSBInterface if didn't do that already in message(). + */ + if (m_pInterface) + { + /* close it */ + if (m_fOpen) + { + m_fOpenOnWasClosed = false; + m_fOpen = false; + m_pInterface->close(this, 0); + } + + /* release it (see start()) */ + m_pInterface->release(); + m_pInterface = NULL; + } + + IOUSBUserClientInit::stop(pProvider); + Log(("VBoxUSBInterface::stop: returns void\n")); +} + + +/** + * Terminate the service (initiate the destruction). + * @remark Only for logging. + */ +bool +org_virtualbox_VBoxUSBInterface::terminate(IOOptionBits fOptions) +{ + /* kIOServiceRecursing, kIOServiceRequired, kIOServiceTerminate, kIOServiceSynchronous - interesting option bits */ + Log(("VBoxUSBInterface::terminate([%p], %#x)\n", this, fOptions)); + return IOUSBUserClientInit::terminate(fOptions); +} + + +/** + * @copydoc org_virtualbox_VBoxUSBDevice::message + */ +IOReturn +org_virtualbox_VBoxUSBInterface::message(UInt32 enmMsg, IOService *pProvider, void *pvArg) +{ + Log(("VBoxUSBInterface::message([%p], %#x {%s}, %p {%s}, %p)\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg)); + + IOReturn irc; + switch (enmMsg) + { + /* + * See explanation in org_virtualbox_VBoxUSBDevice::message. + */ + case kIOMessageServiceIsRequestingClose: + irc = kIOReturnExclusiveAccess; + if (!((uintptr_t)pvArg & kIOServiceSeize)) + { + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d: not seize - closing...\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pInterface) + m_pInterface->close(this, 0); + irc = kIOReturnSuccess; + } + else + { + if (org_virtualbox_VBoxUSBClient::isClientTask(current_task())) + { + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d task=%p: client process, closing.\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + m_fOpen = false; + m_fOpenOnWasClosed = false; + if (m_pInterface) + m_pInterface->close(this, 0); + m_fOpenOnWasClosed = true; + irc = kIOReturnSuccess; + } + else + Log(("VBoxUSBInterface::message([%p],%p {%s}, %p) - pid=%d task=%p: not client process!\n", + this, pProvider, pProvider->getName(), pvArg, RTProcSelf(), current_task())); + } + break; + + /* + * The service was closed by the current client, check for re-open. + */ + case kIOMessageServiceWasClosed: + if (m_pInterface && m_fOpenOnWasClosed) + { + Log(("VBoxUSBInterface::message: attempting to re-open the interface...\n")); + m_fOpenOnWasClosed = false; + m_fOpen = m_pInterface->open(this, kIOServiceSeize, 0); + if (!m_fOpen) + Log(("VBoxUSBInterface::message: failed to open the interface!\n")); + m_fOpenOnWasClosed = !m_fOpen; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + /* + * The IOUSBInterface/Device is shutting down, so close and release. + */ + case kIOMessageServiceIsTerminated: + if (m_pInterface) + { + /* close it */ + if (m_fOpen) + { + m_fOpen = false; + m_fOpenOnWasClosed = false; + m_pInterface->close(this, 0); + } + + /* release it (see start()) */ + m_pInterface->release(); + m_pInterface = NULL; + } + + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + + default: + irc = IOUSBUserClientInit::message(enmMsg, pProvider, pvArg); + break; + } + + Log(("VBoxUSBInterface::message([%p], %#x {%s}, %p {%s}, %p) -> %#x\n", + this, enmMsg, DbgGetIOKitMessageName(enmMsg), pProvider, pProvider->getName(), pvArg, irc)); + return irc; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h new file mode 100644 index 00000000..4d61525c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/VBoxUSBInterface.h @@ -0,0 +1,65 @@ +/** $Id: VBoxUSBInterface.h $ */ +/** @file + * VirtualBox USB Driver User<->Kernel Interface. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h +#define VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/usbfilter.h> + +/** + * org_virtualbox_VBoxUSBClient method indexes. + */ +typedef enum VBOXUSBMETHOD +{ + /** org_virtualbox_VBoxUSBClient::addFilter */ + VBOXUSBMETHOD_ADD_FILTER = 0, + /** org_virtualbox_VBoxUSBClient::removeFilter */ + VBOXUSBMETHOD_REMOVE_FILTER, + /** End/max. */ + VBOXUSBMETHOD_END +} VBOXUSBMETHOD; + +/** + * Output from a VBOXUSBMETHOD_ADD_FILTER call. + */ +typedef struct VBOXUSBADDFILTEROUT +{ + /** The ID. */ + uintptr_t uId; + /** The return code. */ + int rc; +} VBOXUSBADDFILTEROUT; +/** Pointer to a VBOXUSBADDFILTEROUT. */ +typedef VBOXUSBADDFILTEROUT *PVBOXUSBADDFILTEROUT; + +/** Cookie used to fend off some unwanted clients to the IOService. */ +#define VBOXUSB_DARWIN_IOSERVICE_COOKIE UINT32_C(0x62735556) /* 'VUsb' */ + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_darwin_VBoxUSBInterface_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh b/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh new file mode 100755 index 00000000..34858aee --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/loadusb.sh @@ -0,0 +1,123 @@ +#!/bin/bash +## @file +# For development. +# + +# +# Copyright (C) 2006-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +SCRIPT_NAME="loadusb" +XNU_VERSION=`LC_ALL=C uname -r | LC_ALL=C cut -d . -f 1` + +DRVNAME="VBoxUSB.kext" +BUNDLE="org.virtualbox.kext.VBoxUSB" + +DEP_DRVNAME="VBoxDrv.kext" +DEP_BUNDLE="org.virtualbox.kext.VBoxDrv" + + +DIR=`dirname "$0"` +DIR=`cd "$DIR" && pwd` +DEP_DIR="$DIR/$DEP_DRVNAME" +DIR="$DIR/$DRVNAME" +if [ ! -d "$DIR" ]; then + echo "Cannot find $DIR or it's not a directory..." + exit 1; +fi +if [ ! -d "$DEP_DIR" ]; then + echo "Cannot find $DEP_DIR or it's not a directory... (dependency)" + exit 1; +fi +if [ -n "$*" ]; then + OPTS="$*" +else + OPTS="-t" +fi + +trap "sudo chown -R `whoami` $DIR $DEP_DIR; exit 1" INT + +# Try unload any existing instance first. +LOADED=`kextstat -b $BUNDLE -l` +if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: Unloading $BUNDLE..." + sudo kextunload -v 6 -b $BUNDLE + LOADED=`kextstat -b $BUNDLE -l` + if test -n "$LOADED"; then + echo "${SCRIPT_NAME}.sh: failed to unload $BUNDLE, see above..." + exit 1; + fi + echo "${SCRIPT_NAME}.sh: Successfully unloaded $BUNDLE" +fi + +set -e + +# Copy the .kext to the symbols directory and tweak the kextload options. +if test -n "$VBOX_DARWIN_SYMS"; then + echo "${SCRIPT_NAME}.sh: copying the extension the symbol area..." + rm -Rf "$VBOX_DARWIN_SYMS/$DRVNAME" + mkdir -p "$VBOX_DARWIN_SYMS" + cp -R "$DIR" "$VBOX_DARWIN_SYMS/" + OPTS="$OPTS -s $VBOX_DARWIN_SYMS/ " + sync +fi + +# On smbfs, this might succeed just fine but make no actual changes, +# so we might have to temporarily copy the driver to a local directory. +if sudo chown -R root:wheel "$DIR" "$DEP_DIR"; then + OWNER=`/usr/bin/stat -f "%u" "$DIR"` +else + OWNER=1000 +fi +if test "$OWNER" -ne 0; then + TMP_DIR=/tmp/${SCRIPT_NAME}.tmp + echo "${SCRIPT_NAME}.sh: chown didn't work on $DIR, using temp location $TMP_DIR/$DRVNAME" + + # clean up first (no sudo rm) + if test -e "$TMP_DIR"; then + sudo chown -R `whoami` "$TMP_DIR" + rm -Rf "$TMP_DIR" + fi + + # make a copy and switch over DIR + mkdir -p "$TMP_DIR/" + sudo cp -Rp "$DIR" "$TMP_DIR/" + DIR="$TMP_DIR/$DRVNAME" + + # load.sh puts it here. + DEP_DIR="/tmp/loaddrv.tmp/$DEP_DRVNAME" + + # retry + sudo chown -R root:wheel "$DIR" "$DEP_DIR" +fi + +sudo chmod -R o-rwx "$DIR" +sync +if [ "$XNU_VERSION" -ge "10" ]; then + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextutil $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextutil $OPTS -d "$DEP_DIR" "$DIR" +else + echo "${SCRIPT_NAME}.sh: loading $DIR... (kextload $OPTS -d \"$DEP_DIR\" \"$DIR\")" + sudo kextload $OPTS -d "$DEP_DIR" "$DIR" +fi +sync +sudo chown -R `whoami` "$DIR" "$DEP_DIR" +kextstat | grep org.virtualbox.kext + diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp new file mode 100644 index 00000000..541a1995 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/darwin/testcase/tstOpenUSBDev.cpp @@ -0,0 +1,295 @@ +/* $Id: tstOpenUSBDev.cpp $ */ +/** @file + * Testcase that attempts to locate and open the specified device. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <mach/mach.h> +#include <Carbon/Carbon.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/storage/IOStorageDeviceCharacteristics.h> +#include <IOKit/scsi/SCSITaskLib.h> +#include <mach/mach_error.h> +#include <IOKit/usb/IOUSBLib.h> +#include <IOKit/IOCFPlugIn.h> + +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/thread.h> +#include <iprt/getopt.h> +#include <iprt/initterm.h> +#include <iprt/stream.h> + + +/** + * Gets an unsigned 32-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu32 Where to store the key value. + */ +static bool tstDictGetU32(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint32_t *pu32) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt32Type, pu32)) + return true; + } + *pu32 = 0; + return false; +} + + +/** + * Gets an unsigned 64-bit integer value. + * + * @returns Success indicator (true/false). + * @param DictRef The dictionary. + * @param KeyStrRef The key name. + * @param pu64 Where to store the key value. + */ +static bool tstDictGetU64(CFMutableDictionaryRef DictRef, CFStringRef KeyStrRef, uint64_t *pu64) +{ + CFTypeRef ValRef = CFDictionaryGetValue(DictRef, KeyStrRef); + if (ValRef) + { + if (CFNumberGetValue((CFNumberRef)ValRef, kCFNumberSInt64Type, pu64)) + return true; + } + *pu64 = 0; + return false; +} + + +static int tstDoWork(io_object_t USBDevice, const char *argv0) +{ + /* + * Create a plugin interface for the device and query its IOUSBDeviceInterface. + */ + int vrc = VINF_SUCCESS; + SInt32 Score = 0; + IOCFPlugInInterface **ppPlugInInterface = NULL; + IOReturn irc = IOCreatePlugInInterfaceForService(USBDevice, kIOUSBDeviceUserClientTypeID, + kIOCFPlugInInterfaceID, &ppPlugInInterface, &Score); + if (irc == kIOReturnSuccess) + { + IOUSBDeviceInterface245 **ppDevI = NULL; + HRESULT hrc = (*ppPlugInInterface)->QueryInterface(ppPlugInInterface, + CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID245), + (LPVOID *)&ppDevI); + irc = IODestroyPlugInInterface(ppPlugInInterface); Assert(irc == kIOReturnSuccess); + ppPlugInInterface = NULL; + if (hrc == S_OK) + { + /* + * Try open the device for exclusive access. + */ + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + if (irc == kIOReturnExclusiveAccess) + { + RTThreadSleep(20); + irc = (*ppDevI)->USBDeviceOpenSeize(ppDevI); + } + if (irc == kIOReturnSuccess) + { +#if 0 + /* + * Re-enumerate the device and bail out. + */ + irc = (*ppDevI)->USBDeviceReEnumerate(ppDevI, 0); + if (irc != kIOReturnSuccess) + { + vrc = RTErrConvertFromDarwinIO(irc); + RTPrintf("%s: Failed to re-enumerate the device, irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } +#endif + + (*ppDevI)->USBDeviceClose(ppDevI); + } + else if (irc == kIOReturnExclusiveAccess) + { + vrc = VERR_SHARING_VIOLATION; + RTPrintf("%s: The device is being used by another process (irc=kIOReturnExclusiveAccess)\n", argv0); + } + else + { + vrc = VERR_OPEN_FAILED; + RTPrintf("%s: Failed to open the device, irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } + } + else + { + vrc = VERR_OPEN_FAILED; + RTPrintf("%s: Failed to create plugin interface for the device, hrc=%#x (vrc=%Rrc).\n", argv0, hrc, vrc); + } + + (*ppDevI)->Release(ppDevI); + } + else + { + vrc = RTErrConvertFromDarwinIO(irc); + RTPrintf("%s: Failed to open the device, plug-in creation failed with irc=%#x (vrc=%Rrc).\n", argv0, irc, vrc); + } + + return vrc; +} + + +static int tstSyntax(const char *argv0) +{ + RTPrintf("syntax: %s [criteria]\n" + "\n" + "Criteria:\n" + " -l <location>\n" + " -s <session>\n" + , argv0); + return 1; +} + + +int main(int argc, char **argv) +{ + RTR3InitExe(argc, &argv, 0); + + /* + * Show help if not arguments. + */ + if (argc <= 1) + return tstSyntax(argv[0]); + + /* + * Parse arguments. + */ + static const RTGETOPTDEF g_aOptions[] = + { + { "--location", 'l', RTGETOPT_REQ_UINT32 }, + { "--session", 's', RTGETOPT_REQ_UINT64 }, + }; + + kern_return_t krc; + uint64_t u64SessionId = 0; + uint32_t u32LocationId = 0; + + int ch; + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aOptions, RT_ELEMENTS(g_aOptions), 1, 0 /* fFlags */); + while ((ch = RTGetOpt(&GetState, &ValueUnion))) + { + switch (ch) + { + case 'l': + u32LocationId = ValueUnion.u32; + break; + case 's': + u64SessionId = ValueUnion.u64; + break; + case 'h': + return tstSyntax(argv[0]); + case 'V': + RTPrintf("$Revision: 135976 $\n"); + return 0; + + default: + return RTGetOptPrintError(ch, &ValueUnion); + } + } + + /* + * Open the master port. + */ + mach_port_t MasterPort = MACH_PORT_NULL; + krc = IOMasterPort(MACH_PORT_NULL, &MasterPort); + if (krc != KERN_SUCCESS) + { + RTPrintf("%s: IOMasterPort -> %x\n", argv[0], krc); + return 1; + } + + /* + * Iterate the USB devices and find all that matches. + */ + CFMutableDictionaryRef RefMatchingDict = IOServiceMatching(kIOUSBDeviceClassName); + if (!RefMatchingDict) + { + RTPrintf("%s: IOServiceMatching failed\n", argv[0]); + return 1; + } + + io_iterator_t USBDevices = IO_OBJECT_NULL; + IOReturn irc = IOServiceGetMatchingServices(MasterPort, RefMatchingDict, &USBDevices); + if (irc != kIOReturnSuccess) + { + RTPrintf("%s: IOServiceGetMatchingServices -> %#x\n", argv[0], irc); + return 1; + } + RefMatchingDict = NULL; /* the reference is consumed by IOServiceGetMatchingServices. */ + + unsigned cDevices = 0; + unsigned cMatches = 0; + io_object_t USBDevice; + while ((USBDevice = IOIteratorNext(USBDevices))) + { + cDevices++; + CFMutableDictionaryRef PropsRef = 0; + krc = IORegistryEntryCreateCFProperties(USBDevice, &PropsRef, kCFAllocatorDefault, kNilOptions); + if (krc == KERN_SUCCESS) + { + uint64_t u64CurSessionId; + uint32_t u32CurLocationId; + if ( ( !u64SessionId + || ( tstDictGetU64(PropsRef, CFSTR("sessionID"), &u64CurSessionId) + && u64CurSessionId == u64SessionId)) + && ( !u32LocationId + || ( tstDictGetU32(PropsRef, CFSTR(kUSBDevicePropertyLocationID), &u32CurLocationId) + && u32CurLocationId == u32LocationId)) + ) + { + cMatches++; + CFRelease(PropsRef); + tstDoWork(USBDevice, argv[0]); + } + else + CFRelease(PropsRef); + } + IOObjectRelease(USBDevice); + } + IOObjectRelease(USBDevices); + + /* + * Bitch if we didn't find anything matching the criteria. + */ + if (!cMatches) + RTPrintf("%s: No matching devices found from a total of %d.\n", argv[0], cDevices); + return !cMatches; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/os2/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c new file mode 100644 index 00000000..5950cc6f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.c @@ -0,0 +1,1278 @@ +#define INCL_DOSERRORS +#define INCL_DOSMEMMGR +#define INCL_DOSSEMAPHORES +#define INCL_DOSDEVICES +#define INCL_DOSDEVIOCTL +#define INCL_DOSMODULEMGR +#include <os2.h> + +#if !defined(__GNUC__) || defined(STATIC_USBCALLS) +#include <string.h> +#else +#define memcpy __builtin_memcpy +#endif +#include <stdlib.h> +#include <stdio.h> + +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <VBox/log.h> + +#ifdef __GNUC__ +# define APIEXPORT __declspec(dllexport) +#else +# define APIEXPORT +#endif + +#ifndef ERROR_USER_DEFINED_BASE +/*#define ERROR_USER_DEFINED_BASE 0xFF00 */ + +#define ERROR_I24_WRITE_PROTECT 0 +#define ERROR_I24_BAD_UNIT 1 +#define ERROR_I24_NOT_READY 2 +#define ERROR_I24_BAD_COMMAND 3 +#define ERROR_I24_CRC 4 +#define ERROR_I24_BAD_LENGTH 5 +#define ERROR_I24_SEEK 6 +#define ERROR_I24_NOT_DOS_DISK 7 +#define ERROR_I24_SECTOR_NOT_FOUND 8 +#define ERROR_I24_OUT_OF_PAPER 9 +#define ERROR_I24_WRITE_FAULT 10 +#define ERROR_I24_READ_FAULT 11 +#define ERROR_I24_GEN_FAILURE 12 +#define ERROR_I24_DISK_CHANGE 13 +#define ERROR_I24_WRONG_DISK 15 +#define ERROR_I24_UNCERTAIN_MEDIA 16 +#define ERROR_I24_CHAR_CALL_INTERRUPTED 17 +#define ERROR_I24_NO_MONITOR_SUPPORT 18 +#define ERROR_I24_INVALID_PARAMETER 19 +#define ERROR_I24_DEVICE_IN_USE 20 +#define ERROR_I24_QUIET_INIT_FAIL 21 +#endif + +#include "usbcalls.h" + +#define IOCAT_USBRES 0x000000A0 /* USB Resource device control */ +#define IOCTLF_NUMDEVICE 0x00000031 /* Get Number of plugged in Devices */ +#define IOCTLF_GETINFO 0x00000032 /* Get Info About a device */ +#define IOCTLF_AQUIREDEVICE 0x00000033 +#define IOCTLF_RELEASEDEVICE 0x00000034 +#define IOCTLF_GETSTRING 0x00000035 +#define IOCTLF_SENDCONTROLURB 0x00000036 +#define IOCTLF_SENDBULKURB 0x00000037 /* Send */ +#define IOCTLF_START_IRQ_PROC 0x00000038 /* Start IRQ polling in a buffer */ +#define IOCTLF_GETDEVINFO 0x00000039 /* Get information about device */ +#define IOCTLF_STOP_IRQ_PROC 0x0000003A /* Stop IRQ Polling */ +#define IOCTLF_START_ISO_PROC 0x0000003B /* Start ISO buffering in a Ringbuffer */ +#define IOCTLF_STOP_ISO_PROC 0x0000003C /* Stop ISO buffering */ +#define IOCTLF_CANCEL_IORB 0x0000003D /* Abort an IORB; */ +#define IOCTLF_SELECT_BULKPIPE 0x0000003E /* Select which Bulk endpoints can be used via Read/Write */ +#define IOCTLF_SENDIRQURB 0x0000003F /* Start IRQ polling in a buffer */ +#define IOCTLF_FIXUPDEVUCE 0x00000040 /* Fixup USB device configuration data */ +#define IOCTLF_REG_STATUSSEM 0x00000041 /* Register Semaphore for general Statuschange */ +#define IOCTLF_DEREG_STATUSSEM 0x00000042 /* Deregister Semaphore */ +#define IOCTLF_REG_DEVICESEM 0x00000043 /* Register Semaphore for a vendor&deviceID */ +#define IOCTLF_DEREG_DEVICESEM 0x00000044 /* Deregister Semaphore */ + + +#define NOTIFY_FREE 0 +#define NOTIFY_CHANGE 1 +#define NOTIFY_DEVICE 2 +#define MAX_NOTIFICATIONS 256 + +#pragma pack(1) + +typedef struct +{ + HEV hDeviceAdded; + HEV hDeviceRemoved; + USHORT usFlags; + USHORT usVendor; + USHORT usProduct; + USHORT usBCDDevice; +} NOTIFYENTRY, *PNOTIFYENTRY; + +#define DEV_SEM_ADD 0x00000001 +#define DEV_SEM_REMOVE 0x00000002 +#define DEV_SEM_MASK 0x00000003 +#define DEV_SEM_VENDORID 0x00000004 +#define DEV_SEM_PRODUCTID 0x00000008 +#define DEV_SEM_BCDDEVICE 0x00000010 + +typedef struct{ + ULONG ulSize; + ULONG ulCaps; + ULONG ulSemDeviceAdd; + ULONG ulSemDeviceRemove; +} STATUSEVENTSET, * PSTATUSEVENTSET; + + +typedef struct{ + ULONG ulSize; + ULONG ulCaps; + ULONG ulSemDeviceAdd; + ULONG ulSemDeviceRemove; + USHORT usVendorID; + USHORT usProductID; + USHORT usBCDDevice; + USHORT usStatus; +} DEVEVENTSET, * PDEVEVENTSET; + +typedef struct +{ + USHORT usVendorID; + USHORT usProductID; + USHORT usBCDDevice; + USHORT usDeviceNumber; /* Get the usDeviceNumber device in the system fi. if 2 acquire the 2nd device + 0 means first not acquired device. */ +} AQUIREDEV, *PAQUIREDEV; + +typedef struct +{ + UCHAR bRequestType; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + ULONG ulTimeout; /* in milliseconds */ +} SETUPPACKET, *PSETUPPACKET; + +typedef struct +{ + ULONG ulHandle; + UCHAR bRequestType; + UCHAR bRequest; + USHORT wValue; + USHORT wIndex; + USHORT wLength; + ULONG ulTimeout; /* in milliseconds */ + USHORT usStatus; +} USBCALLS_CTRL_REQ, *PUSBCALLS_CTRL_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; + ULONG ulID; +} USBCALLS_ISO_START, *NPUSBCALLS_ISO_START, FAR *PUSBCALLS_ISO_START, + USBCALLS_IRQ_START, *NPUSBCALLS_IRQ_START, FAR *PUSBCALLS_IRQ_START, + USBCALLS_CANCEL_REQ, *NPUSBCALLS_CANCEL_REQ, FAR *PUSBCALLS_CANCEL_REQ; + +#define ISO_DIRMASK 0x80 +typedef struct +{ + ULONG hSemAccess; /* Synchronise access to the Pos values */ + ULONG hDevice; + USHORT usPosWrite; + USHORT usPosRead; + USHORT usBufSize; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + UCHAR ucBuffer[16*1023]; +} ISORINGBUFFER, *PISORINGBUFFER; + +typedef USBCALLS_ISO_START USBCALLS_ISO_STOP, * NPUSBCALLS_ISO_STOP, FAR *PUSBCALLS_ISO_STOP; +typedef USBCALLS_ISO_START USBCALLS_IRQ_STOP, * NPUSBCALLS_IRQ_STOP, FAR *PUSBCALLS_IRQ_STOP; + +#define USB_TRANSFER_FULL_SIZE 0x01 + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; +/* ULONG ulID; - yeah, right */ + ULONG ulTimeout; + USHORT usDataProcessed; + USHORT usDataRemain; + USHORT usFlags; +} USBCALLS_BULK_REQ, *PUSBCALLS_BULK_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucEndpoint; + UCHAR ucAltInterface; + USHORT usStatus; + ULONG ulEvent; + ULONG ulID; + ULONG ulTimeout; + USHORT usDataLen; +} LIBUSB_IRQ_REQ, *NPLIBUSB_IRQ_REQ, FAR *PLIBUSB_IRQ_REQ; + +typedef struct +{ + ULONG ulDevHandle; + UCHAR ucConfiguration; + UCHAR ucAltInterface; + USHORT usStatus; +} LIBUSB_FIXUP, *NPLIBUSB_FIXUP, FAR *PLIBUSB_FIXUP; + +#pragma pack() + +/******************************************************************************/ + +HFILE g_hUSBDrv; +ULONG g_cInit; +ULONG g_ulFreeNotifys; +HMTX g_hSemNotifytable; +NOTIFYENTRY g_Notifications[MAX_NOTIFICATIONS]; + +HMTX g_hSemRingBuffers; +PISORINGBUFFER g_pIsoRingBuffers; +ULONG g_ulNumIsoRingBuffers; + +APIEXPORT APIRET APIENTRY +InitUsbCalls(void) +{ + int i; + ULONG ulAction; + APIRET rc; + + if (++g_cInit > 1) + return NO_ERROR; + + rc = DosOpen( (PCSZ)"USBRESM$", + &g_hUSBDrv, + &ulAction, + 0, + FILE_NORMAL, + OPEN_ACTION_OPEN_IF_EXISTS, + OPEN_ACCESS_READWRITE | + OPEN_FLAGS_NOINHERIT | + OPEN_SHARE_DENYNONE, + 0 ); + if(rc) + { + g_hUSBDrv = 0; + g_cInit = 0; + + } + else + { + /* @@ToDO Add EnvVar or INI for dynamically setting the number */ + g_ulNumIsoRingBuffers = 8; + for(i=0;i<MAX_NOTIFICATIONS;i++) + { + g_Notifications[i].usFlags = NOTIFY_FREE; + g_Notifications[i].hDeviceAdded = 0; + g_Notifications[i].hDeviceRemoved = 0; + g_Notifications[i].usVendor = 0; + g_Notifications[i].usProduct = 0; + g_Notifications[i].usBCDDevice = 0; + } + rc = DosAllocMem( (PPVOID)&g_pIsoRingBuffers, + g_ulNumIsoRingBuffers * sizeof(ISORINGBUFFER), + PAG_WRITE | PAG_COMMIT | OBJ_TILE); + if(!rc) + { + PISORINGBUFFER pIter = g_pIsoRingBuffers; + for(i=0;i< g_ulNumIsoRingBuffers;i++,pIter++) + { + pIter->hDevice = 0; + pIter->hSemAccess = 0; /* Synchronise access to the Pos values */ + pIter->usPosWrite = 0; + pIter->usPosRead = 0; + pIter->usBufSize = 16*1023; + pIter->ucEndpoint = 0; + pIter->ucAltInterface = 0; + /*pIter->ucBuffer */ + } + rc=DosCreateMutexSem(NULL,&g_hSemRingBuffers,DC_SEM_SHARED,FALSE); + if(!rc) + { + rc=DosCreateMutexSem(NULL,&g_hSemNotifytable,DC_SEM_SHARED,FALSE); + if(rc) + { + DosCloseMutexSem(g_hSemRingBuffers); + DosFreeMem(g_pIsoRingBuffers); + } + } + else + { + DosFreeMem(g_pIsoRingBuffers); + } + } + + if(rc) + { + DosClose(g_hUSBDrv); + g_hUSBDrv = 0; + g_cInit = 0; + } + } + return g_cInit ? NO_ERROR : rc ? rc : ERROR_GEN_FAILURE; +} + +APIEXPORT APIRET APIENTRY +TermUsbCalls(void) +{ + if (!g_cInit) + return ERROR_GEN_FAILURE; + if(!--g_cInit) + { + int i; + for(i=0;i<MAX_NOTIFICATIONS;i++) + if( g_Notifications[i].usFlags != NOTIFY_FREE); + UsbDeregisterNotification((USBNOTIFY)(&g_Notifications[i])); + + DosClose(g_hUSBDrv); + g_hUSBDrv = NULLHANDLE; + + if (g_pIsoRingBuffers) + DosFreeMem(g_pIsoRingBuffers); + + DosCloseMutexSem(g_hSemRingBuffers); + g_hSemRingBuffers = NULLHANDLE; + DosCloseMutexSem(g_hSemNotifytable); + g_hSemNotifytable = NULLHANDLE; + } + return NO_ERROR; +} + + +#ifdef VBOX /* complete wast of time */ +# define IsBadReadPointer(pBase, ulSize) (FALSE) +# define IsBadWritePointer(pBase, ulSize) (FALSE) +#else +static BOOL IsBadReadPointer(PVOID pBase, ULONG ulSize) +{ + APIRET rc; + ULONG ulFlags; + rc = DosQueryMem(pBase, &ulSize, &ulFlags); + + return rc!=0?TRUE:(ulFlags&PAG_READ)&&(ulFlags&PAG_COMMIT)?FALSE:TRUE; +} + +static BOOL IsBadWritePointer(PVOID pBase, ULONG ulSize) +{ + APIRET rc; + ULONG ulFlags; + rc = DosQueryMem(pBase, &ulSize, &ulFlags); + + return rc!=0?TRUE:((ulFlags&PAG_WRITE)==PAG_WRITE&&(ulFlags&PAG_COMMIT)==PAG_COMMIT)?FALSE:TRUE; +} +#endif + +APIEXPORT APIRET APIENTRY +UsbQueryNumberDevices(ULONG *pulNumDev) +{ + APIRET rc; + ULONG ulLength; + if(!g_cInit) + return USB_NOT_INIT; + + if( IsBadWritePointer(pulNumDev,sizeof(ULONG)) ) + return ERROR_INVALID_PARAMETER; + ulLength=sizeof(ULONG); + *pulNumDev = 0; + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_NUMDEVICE, + NULL, 0, NULL, + pulNumDev, ulLength, &ulLength); + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbQueryDeviceReport(ULONG ulDevNumber, ULONG *pulBufLen, PVOID pData) +{ + APIRET rc; + ULONG ulParmLen; + + if(!g_cInit) + return USB_NOT_INIT; + + if( IsBadWritePointer(pulBufLen, sizeof(ULONG)) ) + return ERROR_INVALID_PARAMETER; + + if( pData!=NULL && IsBadWritePointer(pData,*pulBufLen) ) + return ERROR_INVALID_PARAMETER; + if(pData==NULL) + *pulBufLen = 0; + ulParmLen = sizeof(ulDevNumber); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_GETINFO, + (PVOID)&ulDevNumber, ulParmLen, &ulParmLen, + pData, *pulBufLen, pulBufLen); + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbRegisterChangeNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved) +{ + APIRET rc; + int i; + STATUSEVENTSET EventSet; + ULONG ulSize; + + if(!g_cInit) + return USB_NOT_INIT; + + if( IsBadWritePointer(pNotifyID, sizeof(ULONG)) || + (hDeviceAdded==0 && hDeviceRemoved==0) ) + return ERROR_INVALID_PARAMETER; + + ulSize = sizeof(EventSet); + EventSet.ulSize = ulSize; + EventSet.ulCaps = 0; + + if(hDeviceAdded!=0) + { + ULONG ulCnt; + rc = DosQueryEventSem(hDeviceAdded,&ulCnt); + if(rc) + return rc; + EventSet.ulCaps |= DEV_SEM_ADD; + EventSet.ulSemDeviceAdd = hDeviceAdded; + } + + if(hDeviceRemoved!=0) + { + ULONG ulCnt; + rc = DosQueryEventSem(hDeviceRemoved,&ulCnt); + if(rc) + return rc; + EventSet.ulCaps |= DEV_SEM_REMOVE; + EventSet.ulSemDeviceRemove = hDeviceRemoved; + } + + rc = DosRequestMutexSem(g_hSemNotifytable,SEM_INDEFINITE_WAIT); + if(rc) + return rc; + + for(i=0;i<MAX_NOTIFICATIONS;i++) + { + if( g_Notifications[i].usFlags == NOTIFY_FREE) + { + g_Notifications[i].usFlags = NOTIFY_CHANGE; + g_Notifications[i].hDeviceAdded = hDeviceAdded; + g_Notifications[i].hDeviceRemoved = hDeviceRemoved; + g_Notifications[i].usVendor = 0; + g_Notifications[i].usProduct = 0; + g_Notifications[i].usBCDDevice = 0; + break; + } + } + DosReleaseMutexSem(g_hSemNotifytable); + if(i==MAX_NOTIFICATIONS) + return USB_ERROR_NO_MORE_NOTIFICATIONS; + + /* @@ToDo come up with a better way to generate IDs */ + *pNotifyID = (USBNOTIFY) (&g_Notifications[i]); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_REG_STATUSSEM, + NULL, 0, NULL, + &EventSet,ulSize, &ulSize); + if(rc) + { + g_Notifications[i].usFlags = NOTIFY_FREE; + *pNotifyID = 0; + } + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbRegisterDeviceNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDVersion) +{ + DEVEVENTSET EventSet; + ULONG ulCnt,ulSize; + int i; + APIRET rc; + + if(!g_cInit) + return USB_NOT_INIT; + + if( IsBadWritePointer(pNotifyID, sizeof(ULONG)) || + hDeviceAdded==0 || hDeviceRemoved==0 || + usVendor == 0 || usVendor == 0xFFFF || + usProduct == 0 || usProduct == 0xFFFF ) + return ERROR_INVALID_PARAMETER; + + + rc = DosQueryEventSem(hDeviceAdded,&ulCnt); + if(rc) + return rc; + rc = DosQueryEventSem(hDeviceRemoved,&ulCnt); + if(rc) + return rc; + + ulSize = sizeof(EventSet); + EventSet.ulSize = ulSize; + EventSet.ulCaps = DEV_SEM_ADD | DEV_SEM_REMOVE | + DEV_SEM_VENDORID | DEV_SEM_PRODUCTID | + DEV_SEM_BCDDEVICE ; + EventSet.ulSemDeviceAdd = hDeviceAdded; + EventSet.ulSemDeviceRemove = hDeviceRemoved; + EventSet.usVendorID = usVendor; + EventSet.usProductID = usProduct; + EventSet.usBCDDevice = usBCDVersion; + EventSet.usStatus = 0; + + rc = DosRequestMutexSem(g_hSemNotifytable,SEM_INDEFINITE_WAIT); + + if(rc) + return rc; + + for(i=0;i<MAX_NOTIFICATIONS;i++) + { + if( g_Notifications[i].usFlags == NOTIFY_FREE) + { + g_Notifications[i].usFlags = NOTIFY_DEVICE; + g_Notifications[i].hDeviceAdded = hDeviceAdded; + g_Notifications[i].hDeviceRemoved = hDeviceRemoved; + g_Notifications[i].usVendor = usVendor; + g_Notifications[i].usProduct = usProduct; + g_Notifications[i].usBCDDevice = usBCDVersion; + break; + } + } + DosReleaseMutexSem(g_hSemNotifytable); + if(i==MAX_NOTIFICATIONS) + return USB_ERROR_NO_MORE_NOTIFICATIONS; + + /* @@ToDo come up with a better way to generate IDs */ + *pNotifyID = (USBNOTIFY) (&g_Notifications[i]); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_REG_DEVICESEM, + NULL, 0, NULL, + &EventSet,ulSize, &ulSize); + if(rc) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= EventSet.usStatus; + + g_Notifications[i].usFlags = NOTIFY_FREE; + *pNotifyID = 0; + } + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbDeregisterNotification( USBNOTIFY NotifyID ) +{ + DEVEVENTSET EventSet; + USBNOTIFY MinID,MaxID; + ULONG Index, ulFunction, ulSize; + APIRET rc; + + if(!g_cInit) + return USB_NOT_INIT; + + MinID = (USBNOTIFY) (&g_Notifications[0]); + MaxID = (USBNOTIFY) (&g_Notifications[MAX_NOTIFICATIONS-1]); + + if(NotifyID<MinID || NotifyID>MaxID) + return ERROR_INVALID_PARAMETER; + + Index = NotifyID - MinID; + + if(Index % sizeof(NOTIFYENTRY)) + return ERROR_INVALID_PARAMETER; + + Index /= sizeof(NOTIFYENTRY); + + rc = DosRequestMutexSem(g_hSemNotifytable,SEM_INDEFINITE_WAIT); + + switch(g_Notifications[Index].usFlags) + { + case NOTIFY_FREE: + DosReleaseMutexSem(g_hSemNotifytable); + return ERROR_INVALID_PARAMETER; + case NOTIFY_CHANGE: + ulFunction = IOCTLF_DEREG_STATUSSEM; + ulSize = sizeof(STATUSEVENTSET); + EventSet.ulSize = ulSize; + EventSet.ulCaps = DEV_SEM_ADD | DEV_SEM_REMOVE; + EventSet.ulSemDeviceAdd = g_Notifications[Index].hDeviceAdded; + EventSet.ulSemDeviceRemove = g_Notifications[Index].hDeviceRemoved; + break; + case NOTIFY_DEVICE: + ulFunction = IOCTLF_DEREG_DEVICESEM; + ulSize = sizeof(DEVEVENTSET); + EventSet.ulSize = ulSize; + EventSet.ulCaps = DEV_SEM_ADD | DEV_SEM_REMOVE | + DEV_SEM_VENDORID | DEV_SEM_PRODUCTID | + DEV_SEM_BCDDEVICE ; + EventSet.ulSemDeviceAdd = g_Notifications[Index].hDeviceAdded; + EventSet.ulSemDeviceRemove = g_Notifications[Index].hDeviceRemoved; + EventSet.usVendorID = g_Notifications[Index].usVendor; + EventSet.usProductID = g_Notifications[Index].usProduct; + EventSet.usBCDDevice = g_Notifications[Index].usBCDDevice; + EventSet.usStatus = 0; + break; + default: + DosReleaseMutexSem(g_hSemNotifytable); + return ERROR_GEN_FAILURE; + } + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, ulFunction, + NULL, 0, NULL, + &EventSet,ulSize, &ulSize); + if(0==rc) + { + g_Notifications[Index].usFlags = NOTIFY_FREE; + g_Notifications[Index].hDeviceAdded = 0; + g_Notifications[Index].hDeviceRemoved = 0; + g_Notifications[Index].usVendor = 0; + g_Notifications[Index].usProduct = 0; + g_Notifications[Index].usBCDDevice = 0; + } else + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= EventSet.usStatus; + } + DosReleaseMutexSem(g_hSemNotifytable); + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbOpen( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice) +{ + ULONG ulCat, ulFunc; + ULONG ulParmLen, ulDataLen; + AQUIREDEV Aquire; + APIRET rc; + + if(!g_cInit) + return USB_NOT_INIT; + if(IsBadWritePointer(pHandle,sizeof(USBHANDLE)) ) + return ERROR_INVALID_PARAMETER; + + Aquire.usVendorID = usVendor; + Aquire.usProductID = usProduct; + Aquire.usBCDDevice = usBCDDevice; + Aquire.usDeviceNumber = usEnumDevice; + ulCat = 0xA0; + ulFunc = 0x33; + ulParmLen = sizeof(Aquire); + ulDataLen = sizeof(USBHANDLE); + rc = DosDevIOCtl( g_hUSBDrv, + ulCat,ulFunc, /*IOCAT_USBRES, IOCTLF_AQUIREDEVICE, */ + &Aquire, ulParmLen, &ulParmLen, + pHandle, ulDataLen, &ulDataLen); + + /* @@ ToDO maybe gether some info about device here (endpoints etc for safety checks) */ + return rc; + +} + +APIEXPORT APIRET APIENTRY +UsbClose( USBHANDLE Handle) +{ + APIRET rc; + ULONG ulDataLen,ulParmLen; + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(USBHANDLE); + ulDataLen = 0; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_RELEASEDEVICE, + (PVOID)&Handle, ulParmLen, &ulParmLen, + NULL, ulDataLen, &ulDataLen); + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbCtrlMessage( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pData, + ULONG ulTimeout) +{ + APIRET rc; + USBCALLS_CTRL_REQ CtrlRequest; + ULONG ulParmLen, ulDataLen; + + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(USBCALLS_CTRL_REQ); + CtrlRequest.ulHandle = Handle; + CtrlRequest.bRequestType = ucRequestType; + CtrlRequest.bRequest = ucRequest; + CtrlRequest.wValue = usValue; + CtrlRequest.wIndex = usIndex; + CtrlRequest.wLength = usLength; + CtrlRequest.ulTimeout = ulTimeout; + ulDataLen = usLength; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDCONTROLURB, + (PVOID)&CtrlRequest, ulParmLen, &ulParmLen, + ulDataLen>0?(PVOID)pData:NULL, + ulDataLen, + ulDataLen>0?&ulDataLen:NULL); + if( rc != NO_ERROR ) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= CtrlRequest.usStatus; + } + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbBulkRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + return UsbBulkRead2(Handle, Endpoint, AltInterface, TRUE /* fShortOk */, ulNumBytes, pvData, ulTimeout); +} + +APIEXPORT APIRET APIENTRY +UsbBulkRead2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen, ulToProcess, ulTotalProcessed; + USBCALLS_BULK_REQ BulkRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + if(*ulNumBytes==0) + return 0; + + /* just require this */ + if ((ULONG)pvData & 0xfff) + return ERROR_INVALID_ADDRESS; + if ((ULONG)pvData >= 0x20000000) + return ERROR_INVALID_ADDRESS; + + ulToProcess = *ulNumBytes; + ulTotalProcessed = 0; + + do + { + /* Process up to 64k, making sure we're working on segments. */ + ulDataLen = 0x10000 - ((ULONG)pvData & 0xffff); + if (ulDataLen > ulToProcess) + ulDataLen = ulToProcess; + + ulParmLen = sizeof(USBCALLS_BULK_REQ); + + memset(&BulkRequest, 0, sizeof(BulkRequest)); + BulkRequest.ulDevHandle = Handle; + BulkRequest.ucEndpoint = Endpoint; + BulkRequest.ucAltInterface = AltInterface; + BulkRequest.usStatus = 0; + BulkRequest.ulEvent = 0; + //BulkRequest.ulID = (ULONG)pvData; + BulkRequest.ulTimeout = ulTimeout; + BulkRequest.usDataProcessed = 0; + BulkRequest.usDataRemain = ulDataLen; + BulkRequest.usFlags = fShortOk && ulDataLen == ulToProcess ? 0 : USB_TRANSFER_FULL_SIZE; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDBULKURB, + (PVOID)&BulkRequest, ulParmLen, &ulParmLen, + pvData, ulDataLen, &ulDataLen); + Log(("BulkRead: usStatus=%d rc=%ld usDataProcessed=%d usDataRemain=%d ulDataLen=%ld\n", + BulkRequest.usStatus, rc, BulkRequest.usDataProcessed, BulkRequest.usDataRemain, ulDataLen)); + + if (rc != NO_ERROR) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= BulkRequest.usStatus; + break; + } + + /* Adjust count and source pointer */ + ulToProcess -= ulDataLen; + pvData = (PBYTE)pvData + ulDataLen; + ulTotalProcessed += BulkRequest.usDataProcessed; + + if (BulkRequest.usDataProcessed != ulDataLen) + { + /* Transferred less than we wanted? so something is wrong, + or device doesn't wish to send more, exit loop */ + rc = USB_ERROR_LESSTRANSFERED; + break; + } + } while( ulToProcess>0 ); + + *ulNumBytes = ulTotalProcessed; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbBulkWrite( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + return UsbBulkWrite2(Handle, Endpoint, AltInterface, FALSE /* fShortOk */, ulNumBytes, pvData, ulTimeout); +} + +APIEXPORT APIRET APIENTRY +UsbBulkWrite2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + USBCALLS_BULK_REQ BulkRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + /* just require this */ + if ((ULONG)pvData & 0xfff) + return ERROR_INVALID_ADDRESS; + if ((ULONG)pvData >= 0x20000000) + return ERROR_INVALID_ADDRESS; + + do + { + /* Process up to 64k, making sure we're working on segments. */ + ulDataLen = 0x10000 - ((ULONG)pvData & 0xffff); + if (ulDataLen > ulNumBytes) + ulDataLen = ulNumBytes; + + ulParmLen = sizeof(USBCALLS_BULK_REQ); + + memset(&BulkRequest, 0, sizeof(BulkRequest)); + BulkRequest.ulDevHandle = Handle; + BulkRequest.ucEndpoint = Endpoint; + BulkRequest.ucAltInterface = AltInterface; + BulkRequest.usStatus = 0; + BulkRequest.ulEvent = 0; + //BulkRequest.ulID = (ULONG)pvData; + BulkRequest.ulTimeout = ulTimeout; + BulkRequest.usDataProcessed = 0; + BulkRequest.usDataRemain = ulDataLen; + BulkRequest.usFlags = fShortOk && ulDataLen == ulNumBytes ? 0 : USB_TRANSFER_FULL_SIZE; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDBULKURB, + &BulkRequest, ulParmLen, &ulParmLen, + pvData, ulDataLen, &ulDataLen ); + Log(("BulkWrite: usStatus=%d rc=%ld usDataProcessed=%d usDataRemain=%d ulDataLen=%ld\n", + BulkRequest.usStatus, rc, BulkRequest.usDataProcessed, BulkRequest.usDataRemain, ulDataLen)); + if (rc != NO_ERROR) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= BulkRequest.usStatus; + break; + } + /* Adjust count and source pointer */ + ulNumBytes -= ulDataLen; + pvData = (PBYTE)pvData + ulDataLen; + } while( ulNumBytes > 0 ); + + return rc; +} + +APIRET APIENTRY +UsbIrqStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + USHORT ulNumBytes, + PVOID pData, + PHEV pHevModified) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + USBCALLS_IRQ_START IrqStart; + HEV hEvent; + + if(!g_cInit) + return USB_NOT_INIT; + + if(0==ulNumBytes || IsBadWritePointer(pData, ulNumBytes)) + return ERROR_INVALID_PARAMETER; + + rc = DosCreateEventSem( NULL, + &hEvent, + DC_SEM_SHARED, + FALSE); + if(rc) + return rc; + + IrqStart.ulDevHandle = Handle; + IrqStart.ucEndpoint = Endpoint; + IrqStart.ucAltInterface = AltInterface; + IrqStart.usStatus = 0; + IrqStart.ulEvent = hEvent; + ulParmLen = sizeof(IrqStart); + ulDataLen = ulNumBytes; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_START_IRQ_PROC, + (PVOID)&IrqStart, ulParmLen, &ulParmLen, + pData, ulDataLen,&ulDataLen); + if(rc) + DosCloseEventSem(hEvent); + else + *pHevModified = hEvent; + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIrqStop( USBHANDLE Handle, + HEV HevModified) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + + if(!g_cInit) + return USB_NOT_INIT; + + ulParmLen = sizeof(Handle); + ulDataLen = sizeof(HevModified); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_STOP_IRQ_PROC, + (PVOID)&Handle, ulParmLen, &ulParmLen, + &HevModified, ulDataLen, &ulDataLen); + if(!rc) + DosCloseEventSem(HevModified); + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ISOHANDLE *phIso) +{ + APIRET rc; + PISORINGBUFFER pIter = g_pIsoRingBuffers; + USBCALLS_ISO_START IsoStart; + ULONG ulParmLen, ulDataLen; + int i; + + if(!g_cInit) + return USB_NOT_INIT; + + rc = DosRequestMutexSem(g_hSemRingBuffers,SEM_INDEFINITE_WAIT); + if(rc) + return rc; + + for(i=0;i< g_ulNumIsoRingBuffers;i++,pIter++) + { + if (pIter->hDevice==0) + { + pIter->hDevice = Handle; + break; + } + } + DosReleaseMutexSem(g_hSemRingBuffers); + + if(i==g_ulNumIsoRingBuffers) + return USB_ERROR_OUTOF_RESOURCES; + + IsoStart.ulDevHandle = Handle; + IsoStart.ucEndpoint = Endpoint; + IsoStart.ucAltInterface = AltInterface; + ulParmLen = sizeof(IsoStart); + ulDataLen = sizeof(ISORINGBUFFER); + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_STOP_IRQ_PROC, + (PVOID)&IsoStart, ulParmLen, &ulParmLen, + pIter, ulDataLen, &ulDataLen); + if(rc) + { + pIter->hDevice = 0; + *phIso = 0; + } + else + { + pIter->ucEndpoint = Endpoint; + pIter->ucAltInterface = AltInterface; + } + return rc; +} + +static APIRET IsInvalidIsoHandle(const ISOHANDLE hIso) +{ + PISORINGBUFFER pIter; + ULONG i; + pIter = g_pIsoRingBuffers; + + for(i=0;i<g_ulNumIsoRingBuffers;i++,pIter++) + { + if(pIter==(PISORINGBUFFER)hIso && pIter->hDevice) + return 0; + } + return ERROR_INVALID_PARAMETER; +} + +APIEXPORT APIRET APIENTRY +UsbIsoStop( ISOHANDLE hIso) +{ + + APIRET rc = NO_ERROR; + if(!g_cInit) + return USB_NOT_INIT; + +/* rc = DosDevIOCtl( g_hUSBDrv, */ + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoDequeue( ISOHANDLE hIso, + PVOID pBuffer, + ULONG ulNumBytes) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(!(pRB->ucEndpoint & ISO_DIRMASK)) + return ERROR_INVALID_PARAMETER; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoPeekQueue( ISOHANDLE hIso, + UCHAR * pByte, + ULONG ulOffset) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(!(pRB->ucEndpoint & ISO_DIRMASK)) + return ERROR_INVALID_PARAMETER; + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoEnqueue( ISOHANDLE hIso, + const UCHAR * pBuffer, + ULONG ulNumBytes) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER)hIso; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + if(pRB->ucEndpoint & ISO_DIRMASK) + return ERROR_INVALID_PARAMETER; + + return rc; +} + +APIEXPORT APIRET APIENTRY +UsbIsoGetLength( ISOHANDLE hIso, + ULONG *pulLength) +{ + APIRET rc; + PISORINGBUFFER pRB = (PISORINGBUFFER) hIso; + USHORT ri,wi; + + rc = IsInvalidIsoHandle(hIso); + if(rc) + return rc; + wi = pRB->usPosWrite; + ri = pRB->usPosRead; + + if (ri == wi) + *pulLength = 0; + else if (ri < wi) + *pulLength = wi - ri; + else + *pulLength = wi + (pRB->usBufSize - ri); + + return 0; +} + +APIEXPORT APIRET APIENTRY +UsbIrqRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + ULONG *ulNumBytes, + PVOID pData, + ULONG ulTimeout) +{ + APIRET rc; + ULONG ulParmLen, ulDataLen; + LIBUSB_IRQ_REQ IrqRequest; + + if(!g_cInit) + return USB_NOT_INIT; + + /* 10 01 2003 - KIEWITZ -> Still @@ToDo Add Endpoint check based on descriptors + We currently only allow Endpoint-addresses 80h->8Fh here */ + if ((Endpoint<0x80) || (Endpoint>0x8F)) + return USB_ERROR_INVALID_ENDPOINT; + + if(*ulNumBytes==0) + return 0; + + IrqRequest.ulDevHandle = Handle; + IrqRequest.ucEndpoint = Endpoint; + IrqRequest.ucAltInterface = AltInterface; + IrqRequest.usStatus = 0; + IrqRequest.ulEvent = 0; + IrqRequest.ulTimeout = ulTimeout; + ulParmLen = sizeof(LIBUSB_IRQ_REQ); + ulDataLen = *ulNumBytes; + + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_SENDIRQURB, + (PVOID)&IrqRequest, ulParmLen, &ulParmLen, + pData, ulDataLen, &ulDataLen); + + if( rc == NO_ERROR ) + { + *ulNumBytes = IrqRequest.usDataLen; + } else + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= IrqRequest.usStatus; + } + return rc; +} + + +APIEXPORT APIRET APIENTRY +UsbFixupDevice( USBHANDLE Handle, + UCHAR ucConfiguration, + UCHAR *pucConfigurationData, + ULONG ulConfigurationLen ) +{ + LIBUSB_FIXUP request; + ULONG ulParmLen; + APIRET rc; + + request.ulDevHandle= Handle; + request.ucConfiguration= ucConfiguration; + request.usStatus= 0; + ulParmLen= sizeof(LIBUSB_FIXUP); + rc = DosDevIOCtl( g_hUSBDrv, + IOCAT_USBRES, IOCTLF_FIXUPDEVUCE, + (PVOID)&request, ulParmLen, &ulParmLen, + pucConfigurationData, ulConfigurationLen, &ulConfigurationLen); + if( rc != NO_ERROR ) + { + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_INVALID_PARAMETER) ) + rc= ERROR_INVALID_PARAMETER; + if( rc == (ERROR_USER_DEFINED_BASE|ERROR_I24_GEN_FAILURE) ) + rc= request.usStatus; + } + return rc; +} + +#ifndef STATIC_USBCALLS + /*+-------------------------------------------------------------------+*/ + /*| _CRT_init is the C run-time environment initialization function. |*/ + /*|It will return 0 to indicate success and -1 to indicate failure. |*/ + /*+-------------------------------------------------------------------+*/ + +/* int _CRT_init (void); */ + + /*+-------------------------------------------------------------------+*/ + /*| _CRT_term is the C run-time environment termination function. |*/ + /*+-------------------------------------------------------------------+*/ + +/* void _CRT_term (unsigned long);*/ + + /*+-------------------------------------------------------------------+*/ + /*| _DLL_InitTerm is the function that gets called by the operating |*/ + /*| system loader when it loads and frees this DLL for each process |*/ + /*| that accesses this DLL. However, it only gets called the first |*/ + /*| time the DLL is loaded and the last time it is freed for a |*/ + /*| particular process. The system linkage convention must be used |*/ + /*| because the operating system loader is calling this function. |*/ + /*+-------------------------------------------------------------------+*/ + +#ifdef STATIC_LINK +int _CRT_init (void); +void _CRT_term(0UL); +#endif + +unsigned long _System _DLL_InitTerm (unsigned long modhandle, unsigned long flag) +{ + + /* If flag is zero then the DLL is being loaded so initialization */ + /* should be performed. If flag is 1 then the DLL is being freed */ + /* so termination should be performed. */ + + switch (flag) + { + case 0: + /* The C run-time environment initialization function must */ + /* be called before any calls to C run-time functions that */ + /* are not inlined. */ + +#ifdef STATIC_LINK + if (_CRT_init () == -1) + return 0UL; +#endif + InitUsbCalls(); + break; + + case 1: + TermUsbCalls(); +#ifdef STATIC_LINK + _CRT_term(0UL); +#endif + break; + + default: + return 0UL; + + } + + /* A nonzero value must be returned to indicate success. */ + return 1UL; +} +#endif /* !STATIC_USBCALLS */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h new file mode 100644 index 00000000..151a5bc1 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/os2/usbcalls.h @@ -0,0 +1,242 @@ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h +#define VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifdef __cplusplus + extern "C" { +#endif + +typedef ULONG USBHANDLE, *PUSBHANDLE; +typedef ULONG USBNOTIFY, *PUSBNOTIFY; +typedef ULONG ISOHANDLE, *PISOHANDLE; + +#define USB_NOT_INIT 7000 +#define USB_ERROR_NO_MORE_NOTIFICATIONS 7001 +#define USB_ERROR_OUTOF_RESOURCES 7002 +#define USB_ERROR_INVALID_ENDPOINT 7003 +#define USB_ERROR_LESSTRANSFERED 7004 + +/* IORB status codes */ +#define USB_IORB_DONE 0x0000 +#define USB_IORB_FAILED 0x8000 + +#define USB_ANY_PRODUCTVERSION 0xFFFF +#define USB_OPEN_FIRST_UNUSED 0 + +#ifdef USB_BIND_DYNAMIC + typedef APIRET APIENTRY USBREGISTERDEVICENOTIFICATION( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDVersion); + typedef USBREGISTERDEVICENOTIFICATION *PUSBREGISTERDEVICENOTIFICATION; + + typedef APIRET APIENTRY USBDEREGISTERNOTIFICATION( USBNOTIFY NotifyID); + + typedef USBDEREGISTERNOTIFICATION *PUSBDEREGISTERNOTIFICATION; + + typedef APIRET APIENTRY USBOPEN( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice); + typedef USBOPEN *PUSBOPEN; + typedef APIRET APIENTRY USBCLOSE( USBHANDLE Handle); + typedef USBCLOSE *PUSBCLOSE; + + typedef APIRET APIENTRY USBCTRLMESSAGE( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pvData, + ULONG ulTimeout); + typedef USBCTRLMESSAGE *PUSBCTRLMESSAGE; +#else + APIRET APIENTRY UsbQueryNumberDevices( ULONG *pulNumDev); + + APIRET APIENTRY UsbQueryDeviceReport( ULONG ulDevNumber, + ULONG *ulBufLen, + PVOID pvData); + APIRET APIENTRY UsbRegisterChangeNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved); + + APIRET APIENTRY UsbRegisterDeviceNotification( PUSBNOTIFY pNotifyID, + HEV hDeviceAdded, + HEV hDeviceRemoved, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDVersion); + + APIRET APIENTRY UsbDeregisterNotification( USBNOTIFY NotifyID); + + APIRET APIENTRY UsbOpen( PUSBHANDLE pHandle, + USHORT usVendor, + USHORT usProduct, + USHORT usBCDDevice, + USHORT usEnumDevice); + + APIRET APIENTRY UsbClose( USBHANDLE Handle); + + APIRET APIENTRY UsbCtrlMessage( USBHANDLE Handle, + UCHAR ucRequestType, + UCHAR ucRequest, + USHORT usValue, + USHORT usIndex, + USHORT usLength, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkRead2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + BOOL fShortOk, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkWrite( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbBulkWrite2( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR AltInterface, + BOOL fShortOk, + ULONG ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbIrqStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + USHORT usNumBytes, + PVOID pvData, + PHEV pHevModified); + APIRET APIENTRY UsbIrqStop( USBHANDLE Handle, + HEV HevModified); + + APIRET APIENTRY UsbIsoStart( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ISOHANDLE *phIso); + APIRET APIENTRY UsbIsoStop( ISOHANDLE hIso); + + APIRET APIENTRY UsbIsoDequeue( ISOHANDLE hIso, + PVOID pBuffer, + ULONG ulNumBytes); + APIRET APIENTRY UsbIsoEnqueue( ISOHANDLE hIso, + const UCHAR * pBuffer, + ULONG ulNumBytes); + APIRET APIENTRY UsbIsoPeekQueue( ISOHANDLE hIso, + UCHAR * pByte, + ULONG ulOffset); + APIRET APIENTRY UsbIsoGetLength( ISOHANDLE hIso, + ULONG *pulLength); + + APIRET APIENTRY UsbIrqRead( USBHANDLE Handle, + UCHAR Endpoint, + UCHAR Interface, + ULONG *ulNumBytes, + PVOID pvData, + ULONG ulTimeout); + + APIRET APIENTRY UsbFixupDevice( USBHANDLE Handle, + UCHAR ucConfiguration, + UCHAR *pucConfigurationData, + ULONG ulConfigurationLen ); + + APIRET APIENTRY InitUsbCalls(void); + APIRET APIENTRY TermUsbCalls(void); + + /* Standard USB Requests See 9.4. in USB 1.1. spec. */ + + /* 09 01 2003 - KIEWITZ */ + #define UsbDeviceClearFeature(HANDLE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x00, 0x01, FEAT, 0, 0, NULL, 0) + #define UsbDeviceSetFeature(HANDLE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, 0, 0, NULL, 0) + #define UsbInterfaceClearFeature(HANDLE, IFACE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x01, 0x01, FEAT, IFACE, 0, NULL, 0) + #define UsbInterfaceSetFeature(HANDLE, IFACE, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, IFACE, 0, NULL, 0) + #define UsbEndpointClearFeature(HANDLE, ENDPOINT, FEAT) \ + UsbCtrlMessage(HANDLE, 0x02, 0x01, FEAT, ENDPOINT, 0, NULL, 0) + #define UsbEndpointSetFeature(HANDLE, ENDPOINT, FEAT) \ + UsbCtrlMessage(HANDLE, 0x80, 0x03, FEAT, ENDPOINT, 0, NULL, 0) + #define FEATURE_DEVICE_REMOTE_WAKEUP 1 + #define FEATURE_ENDPOINT_HALT 0 + #define UsbEndpointClearHalt(HANDLE, ENDPOINT) \ + UsbEndpointClearFeature(HANDLE, ENDPOINT, FEATURE_ENDPOINT_HALT) + + #define UsbDeviceGetConfiguration(HANDLE, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x08, 0, 0, 1, DATA, 0) + #define UsbDeviceSetConfiguration(HANDLE, CONFIG) \ + UsbCtrlMessage(HANDLE, 0x00, 0x09, CONFIG, 0, 0, NULL, 0) + + #define UsbDeviceGetStatus(HANDLE, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, 0, 2, STATUS, 0) + #define UsbInterfaceGetStatus(HANDLE, IFACE, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, IFACE, 2, STATUS, 0) + #define UsbEndpointGetStatus(HANDLE, ENDPOINT, STATUS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x00, 0, ENDPOINT, 2, STATUS, 0) + + #define STATUS_ENDPOINT_HALT 0x0001 + #define STATUS_DEVICE_SELFPOWERD 0x0001 + #define STATUS_DEVICE_REMOTEWAKEUP 0x0002 + + #define UsbDeviceSetAddress(HANDLE, ADDRESS) \ + UsbCtrlMessage(HANDLE, 0x80, 0x05, ADDRESS, 0, 0, NULL, 0) + + #define UsbDeviceGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0100|INDEX), LID, LEN, DATA, 0) + #define UsbDeviceSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0100|INDEX), LID, LEN, DATA, 0) + #define UsbConfigurationGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0200|INDEX), LID, LEN, DATA, 0) + #define UsbConfigurationSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0200|INDEX), LID, LEN, DATA, 0) + #define UsbStringGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0300|INDEX), LID, LEN, DATA, 0) + #define UsbStringSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0300|INDEX), LID, LEN, DATA, 0) + #define UsbInterfaceGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0400|INDEX), LID, LEN, DATA, 0) + #define UsbInterfaceSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0400|INDEX), LID, LEN, DATA, 0) + #define UsbEndpointGetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x06, (0x0500|INDEX), LID, LEN, DATA, 0) + #define UsbEndpointSetDescriptor(HANDLE, INDEX, LID, LEN, DATA) \ + UsbCtrlMessage(HANDLE, 0x80, 0x07, (0x0500|INDEX), LID, LEN, DATA, 0) + + #define UsbInterfaceGetAltSetting(HANDLE, IFACE, SETTING) \ + UsbCtrlMessage(HANDLE, 0x81, 0x0A, 0, IFACE, 1, SETTING, 0) + #define UsbInterfaceSetAltSetting(HANDLE, IFACE, ALTSET) \ + UsbCtrlMessage(HANDLE, 0x01, 0x0B, ALTSET, IFACE, 0, NULL, 0) + + #define UsbEndpointSynchFrame(HANDLE, ENDPOINT, FRAMENUM) \ + UsbCtrlMessage(HANDLE, 0x82, 0x0B, 0, ENDPOINT, 2, FRAMENUM, 0) +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_os2_usbcalls_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk new file mode 100644 index 00000000..9a758402 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/Makefile.kmk @@ -0,0 +1,57 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Solaris VBoxUSB kernel extension. +# + +# +# Copyright (C) 2008-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# vboxusbmon - The Solaris USB Monitor Kernel module. +# +SYSMODS.solaris += vboxusbmon +vboxusbmon_TEMPLATE = VBOXR0DRV +vboxusbmon_DEFS = IN_RT_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxusbmon_DEPS += $(VBOX_SVN_REV_KMK) +vboxusbmon_INCS := . .. include +vboxusbmon_LDFLAGS += -N drv/vboxdrv -N misc/usba +vboxusbmon_SOURCES = \ + VBoxUSBMon-solaris.c \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp + +# +# vboxusb - The Solaris Generic USB Client Kernel module. +# +SYSMODS.solaris += vboxusb +vboxusb_TEMPLATE = VBOXR0DRV +vboxusb_DEFS = IN_RT_R0 IN_SUP_R0 VBOX_SVN_REV=$(VBOX_SVN_REV) +vboxusb_DEPS += $(VBOX_SVN_REV_KMK) +vboxusb_INCS := . include/ +vboxusb_LDFLAGS += -N drv/vboxdrv -N drv/vboxusbmon -N misc/usba +vboxusb_SOURCES = \ + VBoxUSB-solaris.c + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp b/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp new file mode 100644 index 00000000..1b90dec5 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/USBLib-solaris.cpp @@ -0,0 +1,269 @@ +/** $Id: USBLib-solaris.cpp $ */ +/** @file + * USBLib - Library for wrapping up the VBoxUSB functionality, Solaris flavor. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usblib.h> +#include <VBox/err.h> +#include <VBox/log.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/file.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/env.h> +#include <iprt/path.h> +#include <iprt/string.h> + +# include <sys/types.h> +# include <sys/stat.h> +# include <errno.h> +# include <unistd.h> +# include <string.h> +# include <limits.h> +# include <strings.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Logging class. */ +#define USBLIBR3 "USBLibR3" + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Reference counter. */ +static uint32_t volatile g_cUsers = 0; +/** VBoxUSB Device handle. */ +static RTFILE g_hFile = NIL_RTFILE; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int usblibDoIOCtl(unsigned iFunction, void *pvData, size_t cbData); + + +USBLIB_DECL(int) USBLibInit(void) +{ + LogFlow((USBLIBR3 ":USBLibInit\n")); + + /* + * Already open? + * This isn't properly serialized, but we'll be fine with the current usage. + */ + if (g_cUsers) + { + ASMAtomicIncU32(&g_cUsers); + return VINF_SUCCESS; + } + + RTFILE File; + int rc = RTFileOpen(&File, VBOXUSB_DEVICE_NAME, RTFILE_O_READWRITE | RTFILE_O_OPEN | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + LogRel((USBLIBR3 ":failed to open the VBoxUSB monitor device node '%s' rc=%Rrc\n", VBOXUSB_DEVICE_NAME, rc)); + return rc; + } + g_hFile = File; + + ASMAtomicIncU32(&g_cUsers); + /* + * Check the USBMonitor version. + */ + VBOXUSBREQ_GET_VERSION Req; + bzero(&Req, sizeof(Req)); + rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_GET_VERSION, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + { + if ( Req.u32Major != VBOXUSBMON_VERSION_MAJOR + || Req.u32Minor < VBOXUSBMON_VERSION_MINOR) + { + rc = VERR_VERSION_MISMATCH; + LogRel((USBLIBR3 ":USBMonitor version mismatch! driver v%d.%d, expecting ~v%d.%d\n", + Req.u32Major, Req.u32Minor, VBOXUSBMON_VERSION_MAJOR, VBOXUSBMON_VERSION_MINOR)); + + RTFileClose(File); + g_hFile = NIL_RTFILE; + ASMAtomicDecU32(&g_cUsers); + return rc; + } + } + else + { + LogRel((USBLIBR3 ":USBMonitor driver version query failed. rc=%Rrc\n", rc)); + RTFileClose(File); + g_hFile = NIL_RTFILE; + ASMAtomicDecU32(&g_cUsers); + return rc; + } + + return VINF_SUCCESS; +} + + +USBLIB_DECL(int) USBLibTerm(void) +{ + LogFlow((USBLIBR3 ":USBLibTerm\n")); + + if (!g_cUsers) + return VERR_WRONG_ORDER; + if (ASMAtomicDecU32(&g_cUsers) != 0) + return VINF_SUCCESS; + + /* + * We're the last guy, close down the connection. + */ + RTFILE File = g_hFile; + g_hFile = NIL_RTFILE; + if (File == NIL_RTFILE) + return VERR_INTERNAL_ERROR; + + int rc = RTFileClose(File); + AssertRC(rc); + return rc; +} + + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + LogFlow((USBLIBR3 ":USBLibAddFilter pFilter=%p\n", pFilter)); + + VBOXUSBREQ_ADD_FILTER Req; + Req.Filter = *pFilter; + Req.uId = 0; + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_ADD_FILTER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + return (void *)Req.uId; + + AssertMsgFailed((USBLIBR3 ":VBOXUSBMON_IOCTL_ADD_FILTER failed! rc=%Rrc\n", rc)); + return NULL; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + LogFlow((USBLIBR3 ":USBLibRemoveFilter pvId=%p\n", pvId)); + + VBOXUSBREQ_REMOVE_FILTER Req; + Req.uId = (uintptr_t)pvId; + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_REMOVE_FILTER, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + return; + + AssertMsgFailed((USBLIBR3 ":VBOXUSBMON_IOCTL_REMOVE_FILTER failed! rc=%Rrc\n", rc)); +} + + +USBLIB_DECL(int) USBLibGetClientInfo(char *pszDeviceIdent, char **ppszClientPath, int *pInstance) +{ + LogFlow((USBLIBR3 ":USBLibGetClientInfo pszDeviceIdent=%s ppszClientPath=%p pInstance=%p\n", + pszDeviceIdent, ppszClientPath, pInstance)); + + AssertPtrReturn(pInstance, VERR_INVALID_PARAMETER); + AssertPtrReturn(ppszClientPath, VERR_INVALID_PARAMETER); + AssertPtrReturn(pszDeviceIdent, VERR_INVALID_PARAMETER); + + VBOXUSBREQ_CLIENT_INFO Req; + bzero(&Req, sizeof(Req)); + RTStrPrintf(Req.szDeviceIdent, sizeof(Req.szDeviceIdent), "%s", pszDeviceIdent); + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_CLIENT_INFO, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + { + *pInstance = Req.Instance; + rc = RTStrDupEx(ppszClientPath, Req.szClientPath); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + + LogRel((USBLIBR3 ":USBLibGetClientInfo RTStrDupEx failed! rc=%Rrc szClientPath=%s\n", rc, Req.szClientPath)); + } + else + LogRel((USBLIBR3 ":USBLibGetClientInfo VBOXUSBMON_IOCTL_CLIENTPATH failed! rc=%Rrc\n", rc)); + + return rc; +} + + +USBLIB_DECL(int) USBLibResetDevice(char *pszDevicePath, bool fReattach) +{ + LogFlow((USBLIBR3 ":USBLibResetDevice pszDevicePath=%s\n", pszDevicePath)); + + size_t cbPath = strlen(pszDevicePath) + 1; + size_t cbReq = sizeof(VBOXUSBREQ_RESET_DEVICE) + cbPath; + VBOXUSBREQ_RESET_DEVICE *pReq = (VBOXUSBREQ_RESET_DEVICE *)RTMemTmpAllocZ(cbReq); + if (RT_UNLIKELY(!pReq)) + return VERR_NO_MEMORY; + + pReq->fReattach = fReattach; + if (strlcpy(pReq->szDevicePath, pszDevicePath, cbPath) >= cbPath) + { + LogRel((USBLIBR3 ":USBLibResetDevice buffer overflow. cbPath=%u pszDevicePath=%s\n", cbPath, pszDevicePath)); + return VERR_BUFFER_OVERFLOW; + } + + int rc = usblibDoIOCtl(VBOXUSBMON_IOCTL_RESET_DEVICE, pReq, cbReq); + if (RT_FAILURE(rc)) + LogRel((USBLIBR3 ":VBOXUSBMON_IOCTL_RESET_DEVICE failed! rc=%Rrc\n", rc)); + + RTMemFree(pReq); + return rc; +} + + +static int usblibDoIOCtl(unsigned iFunction, void *pvData, size_t cbData) +{ + if (g_hFile == NIL_RTFILE) + { + LogRel((USBLIBR3 ":IOCtl failed, device not open.\n")); + return VERR_FILE_NOT_FOUND; + } + + VBOXUSBREQ Hdr; + Hdr.u32Magic = VBOXUSBMON_MAGIC; + Hdr.cbData = cbData; /* Don't include full size because the header size is fixed. */ + Hdr.pvDataR3 = pvData; + + int rc = ioctl(RTFileToNative(g_hFile), iFunction, &Hdr); + if (rc < 0) + { + rc = errno; + LogRel((USBLIBR3 ":IOCtl failed iFunction=%x errno=%d g_file=%d\n", iFunction, rc, RTFileToNative(g_hFile))); + return RTErrConvertFromErrno(rc); + } + + rc = Hdr.rc; + if (RT_UNLIKELY(RT_FAILURE(rc))) + LogRel((USBLIBR3 ":Function (%x) failed. rc=%Rrc\n", iFunction, rc)); + + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c new file mode 100644 index 00000000..263f1199 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSB-solaris.c @@ -0,0 +1,4046 @@ +/* $Id: VBoxUSB-solaris.c $ */ +/** @file + * VirtualBox USB Client Driver, Solaris Hosts. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_USB_DRV +#include <VBox/version.h> +#include <VBox/log.h> +#include <VBox/err.h> +#include <VBox/cdefs.h> +#include <VBox/sup.h> +#include <VBox/usblib-solaris.h> + +#include <iprt/assert.h> +#include <iprt/initterm.h> +#include <iprt/semaphore.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/path.h> +#include <iprt/thread.h> +#include <iprt/dbg.h> + +#define USBDRV_MAJOR_VER 2 +#define USBDRV_MINOR_VER 0 +#include <sys/usb/usba.h> +#include <sys/strsun.h> +#include "usbai_private.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxusb" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox USB" + +/** -=-=-=-=-=-=- Standard Specifics -=-=-=-=-=-=- */ +/** Max. supported endpoints. */ +#define VBOXUSB_MAX_ENDPOINTS 32 +/** Size of USB Ctrl Xfer Header in bytes. */ +#define VBOXUSB_CTRL_XFER_SIZE 8 +/** + * USB2.0 (Sec. 9-13) Bits 10..0 is the max packet size; for high speed Isoc/Intr, bits 12..11 is + * number of additional transaction opportunities per microframe. + */ +#define VBOXUSB_PKT_SIZE(pkt) (pkt & 0x07FF) * (1 + ((pkt >> 11) & 3)) +/** Endpoint Xfer Type. */ +#define VBOXUSB_XFER_TYPE(endp) ((endp)->EpDesc.bmAttributes & USB_EP_ATTR_MASK) +/** Endpoint Xfer Direction. */ +#define VBOXUSB_XFER_DIR(endp) ((endp)->EpDesc.bEndpointAddress & USB_EP_DIR_IN) +/** Create an Endpoint index from an Endpoint address. */ +#define VBOXUSB_GET_EP_INDEX(epaddr) (((epaddr) & USB_EP_NUM_MASK) + \ + (((epaddr) & USB_EP_DIR_MASK) ? 16 : 0)) + + +/** -=-=-=-=-=-=- Tunable Parameters -=-=-=-=-=-=- */ +/** Time to wait while draining inflight UBRs on suspend, in seconds. */ +#define VBOXUSB_DRAIN_TIME 20 +/** Ctrl Xfer timeout in seconds. */ +#define VBOXUSB_CTRL_XFER_TIMEOUT 15 +/** Maximum URB queue length. */ +#define VBOXUSB_URB_QUEUE_SIZE 512 +/** Maximum asynchronous requests per pipe. */ +#define VBOXUSB_MAX_PIPE_ASYNC_REQS 2 + +/** For enabling global symbols while debugging. **/ +#if defined(DEBUG_ramshankar) +# define LOCAL +#else +# define LOCAL static +#endif + + +/********************************************************************************************************************************* +* Kernel Entry Hooks * +*********************************************************************************************************************************/ +int VBoxUSBSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +int VBoxUSBSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +int VBoxUSBSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +int VBoxUSBSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +int VBoxUSBSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal); +int VBoxUSBSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead); +int VBoxUSBSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pArg, void **ppResult); +int VBoxUSBSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +int VBoxUSBSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); +int VBoxUSBSolarisPower(dev_info_t *pDip, int Component, int Level); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxUSBSolarisCbOps = +{ + VBoxUSBSolarisOpen, + VBoxUSBSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + VBoxUSBSolarisRead, + VBoxUSBSolarisWrite, + VBoxUSBSolarisIOCtl, + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + VBoxUSBSolarisPoll, + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV, /* revision */ + nodev, /* c aread */ + nodev /* c awrite */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_VBoxUSBSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxUSBSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxUSBSolarisAttach, + VBoxUSBSolarisDetach, + nodev, /* reset */ + &g_VBoxUSBSolarisCbOps, + NULL, /* bus ops */ + VBoxUSBSolarisPower, + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxUSBSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxUSBSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxUSBSolarisModLinkage = +{ + MODREV_1, + &g_VBoxUSBSolarisModule, + NULL, +}; + +/** + * vboxusb_ep_t: Endpoint structure with info. for managing an endpoint. + */ +typedef struct vboxusb_ep_t +{ + bool fInitialized; /* Whether this Endpoint is initialized */ + usb_ep_descr_t EpDesc; /* Endpoint descriptor */ + usb_pipe_handle_t pPipe; /* Endpoint pipe handle */ + usb_pipe_policy_t PipePolicy; /* Endpoint policy */ + bool fIsocPolling; /* Whether Isoc. IN polling is enabled */ + list_t hIsocInUrbs; /* Isoc. IN inflight URBs */ + uint16_t cIsocInUrbs; /* Number of Isoc. IN inflight URBs */ + list_t hIsocInLandedReqs; /* Isoc. IN landed requests */ + uint16_t cbIsocInLandedReqs; /* Cumulative size of landed Isoc. IN requests */ + size_t cbMaxIsocData; /* Maximum size of Isoc. IN landed buffer */ +} vboxusb_ep_t; + +/** + * vboxusb_isoc_req_t: Isoc IN. requests queued from device till they are reaped. + */ +typedef struct vboxusb_isoc_req_t +{ + mblk_t *pMsg; /* Pointer to the data buffer */ + uint32_t cIsocPkts; /* Number of Isoc pkts */ + VUSBISOC_PKT_DESC aIsocPkts[8]; /* Array of Isoc pkt descriptors */ + list_node_t hListLink; +} vboxusb_isoc_req_t; + +/** + * VBOXUSB_URB_STATE: Internal USB URB state. + */ +typedef enum VBOXUSB_URB_STATE +{ + VBOXUSB_URB_STATE_FREE = 0x00, + VBOXUSB_URB_STATE_INFLIGHT = 0x04, + VBOXUSB_URB_STATE_LANDED = 0x08 +} VBOXUSB_URB_STATE; + +/** + * vboxusb_urb_t: kernel URB representation. + */ +typedef struct vboxusb_urb_t +{ + void *pvUrbR3; /* Userspace URB address (untouched, returned while reaping) */ + uint8_t bEndpoint; /* Endpoint address */ + VUSBXFERTYPE enmType; /* Xfer type */ + VUSBDIRECTION enmDir; /* Xfer direction */ + VUSBSTATUS enmStatus; /* URB status */ + bool fShortOk; /* Whether receiving less data than requested is acceptable */ + RTR3PTR pvDataR3; /* Userspace address of the original data buffer */ + size_t cbDataR3; /* Size of the data buffer */ + mblk_t *pMsg; /* Pointer to the data buffer */ + uint32_t cIsocPkts; /* Number of Isoc pkts */ + VUSBISOC_PKT_DESC aIsocPkts[8]; /* Array of Isoc pkt descriptors */ + VBOXUSB_URB_STATE enmState; /* URB state (free/in-flight/landed). */ + struct vboxusb_state_t *pState; /* Pointer to the device instance */ + list_node_t hListLink; /* List node link handle */ +} vboxusb_urb_t; + +/** + * vboxusb_power_t: Per Device Power Management info. + */ +typedef struct vboxusb_power_t +{ + uint_t PowerStates; /* Bit mask of the power states */ + int PowerBusy; /* Busy reference counter */ + bool fPowerWakeup; /* Whether remote power wakeup is enabled */ + bool fPowerRaise; /* Whether to raise the power level */ + uint8_t PowerLevel; /* Current power level */ +} vboxusb_power_t; + +/** + * vboxusb_state_t: Per Device instance state info. + */ +typedef struct vboxusb_state_t +{ + dev_info_t *pDip; /* Per instance device info. */ + usb_client_dev_data_t *pDevDesc; /* Parsed & complete device descriptor */ + uint8_t DevState; /* Current USB Device state */ + bool fDefaultPipeOpen; /* Whether the device (default control pipe) is closed */ + bool fPollPending; /* Whether the userland process' poll is pending */ + kmutex_t Mtx; /* Mutex state protection */ + usb_serialization_t StateMulti; /* State serialization */ + size_t cbMaxBulkXfer; /* Maximum bulk xfer size */ + vboxusb_ep_t aEps[VBOXUSB_MAX_ENDPOINTS]; /* Array of all endpoints structures */ + list_t hFreeUrbs; /* List of free URBs */ + list_t hInflightUrbs; /* List of inflight URBs */ + list_t hLandedUrbs; /* List of landed URBs */ + uint32_t cFreeUrbs; /* Number of free URBs */ + uint32_t cInflightUrbs; /* Number of inflight URBs */ + uint32_t cLandedUrbs; /* Number of landed URBs */ + pollhead_t PollHead; /* Handle to pollhead for waking polling processes */ + RTPROCESS Process; /* The process (pid) of the user session */ + VBOXUSBREQ_CLIENT_INFO ClientInfo; /* Registration data */ + vboxusb_power_t *pPower; /* Power Management */ + char szMfg[255]; /* Parsed manufacturer string */ + char szProduct[255]; /* Parsed product string */ +} vboxusb_state_t; +AssertCompileMemberSize(vboxusb_state_t, szMfg, USB_MAXSTRINGLEN); +AssertCompileMemberSize(vboxusb_state_t, szProduct, USB_MAXSTRINGLEN); + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +LOCAL int vboxUsbSolarisInitEp(vboxusb_state_t *pState, usb_ep_data_t *pEpData); +LOCAL int vboxUsbSolarisInitEpsForCfg(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisInitEpsForIfAlt(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt); +LOCAL void vboxUsbSolarisDestroyAllEps(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDestroyEp(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL void vboxUsbSolarisCloseAllPipes(vboxusb_state_t *pState, bool fControlPipe); +LOCAL int vboxUsbSolarisOpenPipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL void vboxUsbSolarisClosePipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp); +LOCAL int vboxUsbSolarisCtrlXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisCtrlXferCompleted(usb_pipe_handle_t pPipe, usb_ctrl_req_t *pReq); +LOCAL int vboxUsbSolarisBulkXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *purb); +LOCAL void vboxUsbSolarisBulkXferCompleted(usb_pipe_handle_t pPipe, usb_bulk_req_t *pReq); +LOCAL int vboxUsbSolarisIntrXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisIntrXferCompleted(usb_pipe_handle_t pPipe, usb_intr_req_t *pReq); +LOCAL int vboxUsbSolarisIsocXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisIsocInXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL void vboxUsbSolarisIsocInXferError(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL void vboxUsbSolarisIsocOutXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq); +LOCAL vboxusb_urb_t *vboxUsbSolarisGetIsocInUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq); +LOCAL vboxusb_urb_t *vboxUsbSolarisQueueUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, mblk_t *pMsg); +LOCAL VUSBSTATUS vboxUsbSolarisGetUrbStatus(usb_cr_t Status); +LOCAL void vboxUsbSolarisConcatMsg(vboxusb_urb_t *pUrb); +LOCAL void vboxUsbSolarisDeQueueUrb(vboxusb_urb_t *pUrb, int URBStatus); +LOCAL void vboxUsbSolarisNotifyComplete(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisProcessIOCtl(int iFunction, void *pvState, int Mode, PVBOXUSBREQ pUSBReq, void *pvBuf, + size_t *pcbDataOut); +LOCAL bool vboxUsbSolarisIsUSBDevice(dev_info_t *pDip); + +/** @name Device Operation Hooks + * @{ */ +LOCAL int vboxUsbSolarisSendUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode); +LOCAL int vboxUsbSolarisReapUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode); +LOCAL int vboxUsbSolarisClearEndPoint(vboxusb_state_t *pState, uint8_t bEndpoint); +LOCAL int vboxUsbSolarisSetConfig(vboxusb_state_t *pState, uint8_t bCfgValue); +LOCAL int vboxUsbSolarisGetConfig(vboxusb_state_t *pState, uint8_t *pCfgValue); +LOCAL int vboxUsbSolarisSetInterface(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt); +LOCAL int vboxUsbSolarisCloseDevice(vboxusb_state_t *pState, VBOXUSB_RESET_LEVEL enmReset); +LOCAL int vboxUsbSolarisAbortPipe(vboxusb_state_t *pState, uint8_t bEndpoint); +LOCAL int vboxUsbSolarisGetConfigIndex(vboxusb_state_t *pState, uint_t bCfgValue); +/** @} */ + +/** @name Hotplug & Power Management Hooks + * @{ */ +LOCAL void vboxUsbSolarisNotifyUnplug(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisDeviceDisconnected(dev_info_t *pDip); +LOCAL int vboxUsbSolarisDeviceReconnected(dev_info_t *pDip); + +LOCAL int vboxUsbSolarisInitPower(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDestroyPower(vboxusb_state_t *pState); +LOCAL int vboxUsbSolarisDeviceSuspend(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDeviceResume(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisDeviceRestore(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisPowerBusy(vboxusb_state_t *pState); +LOCAL void vboxUsbSolarisPowerIdle(vboxusb_state_t *pState); +/** @} */ + +/** @name Monitor Hooks + * @{ */ +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip); +/** @} */ + +/** @name Callbacks from Monitor + * @{ */ +LOCAL int vboxUsbSolarisSetConsumerCredentials(RTPROCESS Process, int Instance, void *pvReserved); +/** @} */ + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global list of all device instances. */ +static void *g_pVBoxUSBSolarisState; + +/** The default endpoint descriptor */ +static usb_ep_descr_t g_VBoxUSBSolarisDefaultEpDesc = { 7, 5, 0, USB_EP_ATTR_CONTROL, 8, 0 }; + +/** Size of the usb_ep_data_t struct (used to index into data). */ +static size_t g_cbUsbEpData = ~0UL; + +/** The offset of usb_ep_data_t::ep_desc. */ +static size_t g_offUsbEpDataDescr = ~0UL; + + +#ifdef LOG_ENABLED +/** + * Gets the description of an Endpoint's transfer type. + * + * @param pEp The Endpoint. + * @returns The type of the Endpoint. + */ +static const char *vboxUsbSolarisEpType(vboxusb_ep_t *pEp) +{ + uint8_t uType = VBOXUSB_XFER_TYPE(pEp); + switch (uType) + { + case 0: return "CTRL"; + case 1: return "ISOC"; + case 2: return "BULK"; + default: return "INTR"; + } +} + + +/** + * Gets the description of an Endpoint's direction. + * + * @param pEp The Endpoint. + * @returns The direction of the Endpoint. + */ +static const char *vboxUsbSolarisEpDir(vboxusb_ep_t *pEp) +{ + return VBOXUSB_XFER_DIR(pEp) == USB_EP_DIR_IN ? "IN " : "OUT"; +} +#endif + + +/** + * Caches device strings from the parsed device descriptors. + * + * @param pState The USB device instance. + * + * @remarks Must only be called after usb_get_dev_data(). + */ +static void vboxUsbSolarisGetDeviceStrings(vboxusb_state_t *pState) +{ + AssertReturnVoid(pState); + AssertReturnVoid(pState->pDevDesc); + + if (pState->pDevDesc->dev_product) + strlcpy(&pState->szMfg[0], pState->pDevDesc->dev_mfg, sizeof(pState->szMfg)); + else + strlcpy(&pState->szMfg[0], "<Unknown Manufacturer>", sizeof(pState->szMfg)); + + if (pState->pDevDesc->dev_product) + strlcpy(&pState->szProduct[0], pState->pDevDesc->dev_product, sizeof(pState->szProduct)); + else + strlcpy(&pState->szProduct[0], "<Unnamed USB device>", sizeof(pState->szProduct)); +} + + +/** + * Queries the necessary symbols at runtime. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisQuerySymbols(void) +{ + RTDBGKRNLINFO hKrnlDbgInfo; + int rc = RTR0DbgKrnlInfoOpen(&hKrnlDbgInfo, 0 /* fFlags */); + if (RT_SUCCESS(rc)) + { + /* + * Query and sanitize the size of usb_ep_data_t struct. + */ + size_t cbPrevUsbEpData = g_cbUsbEpData; + rc = RTR0DbgKrnlInfoQuerySize(hKrnlDbgInfo, "usba", "usb_ep_data_t", &g_cbUsbEpData); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to query size of \"usb_ep_data_t\" in the \"usba\" module, rc=%Rrc\n", rc)); + return rc; + } + if (g_cbUsbEpData > _4K) + { + LogRel(("Size of \"usb_ep_data_t\" (%u bytes) seems implausible, too paranoid to continue\n", g_cbUsbEpData)); + return VERR_MISMATCH; + } + + /* + * Query and sanitizie the offset of usb_ep_data_t::ep_descr. + */ + size_t offPrevUsbEpDataDescr = g_offUsbEpDataDescr; + rc = RTR0DbgKrnlInfoQueryMember(hKrnlDbgInfo, "usba", "usb_ep_data_t", "ep_descr", &g_offUsbEpDataDescr); + if (RT_FAILURE(rc)) + { + LogRel(("Failed to query offset of usb_ep_data_t::ep_descr, rc=%Rrc\n", rc)); + return rc; + } + if (g_offUsbEpDataDescr > _4K - sizeof(usb_ep_descr_t)) + { + LogRel(("Offset of \"ep_desrc\" (%u) seems implausible, too paranoid to continue\n", g_offUsbEpDataDescr)); + return VERR_MISMATCH; + } + + /* + * Log only when it changes / first time, since _init() seems to be called often (e.g. on failed attaches). + * cmn_err, CE_CONT and '!' is used to not show the message on console during boot each time. + */ + if ( cbPrevUsbEpData != g_cbUsbEpData + || offPrevUsbEpDataDescr != g_offUsbEpDataDescr) + { + cmn_err(CE_CONT, "!usba_ep_data_t is %lu bytes\n", g_cbUsbEpData); + cmn_err(CE_CONT, "!usba_ep_data_t::ep_descr @ 0x%lx (%ld)\n", g_offUsbEpDataDescr, g_offUsbEpDataDescr); + } + + RTR0DbgKrnlInfoRelease(hKrnlDbgInfo); + } + + return rc; +} + + +/** + * Kernel entry points + */ +int _init(void) +{ + LogFunc((DEVICE_NAME ": _init\n")); + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxUSBSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": _init: failed to disable autounloading!\n")); + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + int rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + rc = vboxUsbSolarisQuerySymbols(); + if (RT_FAILURE(rc)) + { + RTR0Term(); + return EINVAL; + } + + rc = ddi_soft_state_init(&g_pVBoxUSBSolarisState, sizeof(vboxusb_state_t), 4 /* pre-alloc */); + if (!rc) + { + rc = mod_install(&g_VBoxUSBSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ": _init: mod_install failed! rc=%d\n", rc)); + ddi_soft_state_fini(&g_pVBoxUSBSolarisState); + } + else + LogRel((DEVICE_NAME ": _init: failed to initialize soft state\n")); + + RTR0Term(); + } + else + LogRel((DEVICE_NAME ": _init: RTR0Init failed! rc=%d\n", rc)); + return RTErrConvertToErrno(rc); +} + + +int _fini(void) +{ + int rc; + + LogFunc((DEVICE_NAME ": _fini\n")); + + rc = mod_remove(&g_VBoxUSBSolarisModLinkage); + if (!rc) + { + ddi_soft_state_fini(&g_pVBoxUSBSolarisState); + RTR0Term(); + } + + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ": _info\n")); + + return mod_info(&g_VBoxUSBSolarisModLinkage, pModInfo); +} + + +/** + * 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 Solaris error code. + */ +int VBoxUSBSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisAttach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + int rc; + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = NULL; + + switch (enmCmd) + { + case DDI_ATTACH: + { + rc = ddi_soft_state_zalloc(g_pVBoxUSBSolarisState, instance); + if (rc == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_LIKELY(pState)) + { + pState->pDip = pDip; + pState->pDevDesc = NULL; + pState->fPollPending = false; + pState->cInflightUrbs = 0; + pState->cFreeUrbs = 0; + pState->cLandedUrbs = 0; + pState->Process = NIL_RTPROCESS; + pState->pPower = NULL; + bzero(pState->aEps, sizeof(pState->aEps)); + list_create(&pState->hFreeUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + list_create(&pState->hInflightUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + list_create(&pState->hLandedUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + + /* + * There is a bug in usb_client_attach() as of Nevada 120 which panics when we bind to + * a non-USB device. So check if we are really binding to a USB device or not. + */ + if (vboxUsbSolarisIsUSBDevice(pState->pDip)) + { + /* + * Here starts the USB specifics. + */ + rc = usb_client_attach(pState->pDip, USBDRV_VERSION, 0); + if (rc == USB_SUCCESS) + { + pState->fDefaultPipeOpen = true; + + /* + * Parse out the entire descriptor. + */ + rc = usb_get_dev_data(pState->pDip, &pState->pDevDesc, USB_PARSE_LVL_ALL, 0 /* Unused */); + if (rc == USB_SUCCESS) + { + /* + * Cache some device descriptor strings. + */ + vboxUsbSolarisGetDeviceStrings(pState); +#ifdef DEBUG_ramshankar + usb_print_descr_tree(pState->pDip, pState->pDevDesc); +#endif + + /* + * Initialize state locks. + */ + mutex_init(&pState->Mtx, NULL, MUTEX_DRIVER, pState->pDevDesc->dev_iblock_cookie); + pState->StateMulti = usb_init_serialization(pState->pDip, USB_INIT_SER_CHECK_SAME_THREAD); + + /* + * Get maximum bulk transfer size supported by the HCD. + */ + rc = usb_pipe_get_max_bulk_transfer_size(pState->pDip, &pState->cbMaxBulkXfer); + if (rc == USB_SUCCESS) + { + Log((DEVICE_NAME ": VBoxUSBSolarisAttach: cbMaxBulkXfer=%d\n", pState->cbMaxBulkXfer)); + + /* + * Initialize the default endpoint. + */ + rc = vboxUsbSolarisInitEp(pState, NULL /* pEp */); + if (RT_SUCCESS(rc)) + { + /* + * Set the device state. + */ + pState->DevState = USB_DEV_ONLINE; + + /* + * Initialize power management for the device. + */ + rc = vboxUsbSolarisInitPower(pState); + if (RT_SUCCESS(rc)) + { + /* + * Initialize endpoints for the current config. + */ + rc = vboxUsbSolarisInitEpsForCfg(pState); + AssertRC(rc); + + /* + * Publish the minor node. + */ + rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0, + "none", "none", 0666); + if (RT_LIKELY(rc == DDI_SUCCESS)) + { + /* + * Register hotplug callbacks. + */ + rc = usb_register_hotplug_cbs(pState->pDip, &vboxUsbSolarisDeviceDisconnected, + &vboxUsbSolarisDeviceReconnected); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + /* + * Register with our monitor driver. + */ + bzero(&pState->ClientInfo, sizeof(pState->ClientInfo)); + char szDevicePath[MAXPATHLEN]; + ddi_pathname(pState->pDip, szDevicePath); + RTStrPrintf(pState->ClientInfo.szClientPath, + sizeof(pState->ClientInfo.szClientPath), + "/devices%s:%s", szDevicePath, DEVICE_NAME); + RTStrPrintf(pState->ClientInfo.szDeviceIdent, + sizeof(pState->ClientInfo.szDeviceIdent), + "%#x:%#x:%d:%s", + pState->pDevDesc->dev_descr->idVendor, + pState->pDevDesc->dev_descr->idProduct, + pState->pDevDesc->dev_descr->bcdDevice, szDevicePath); + pState->ClientInfo.Instance = instance; + pState->ClientInfo.pfnSetConsumerCredentials = &vboxUsbSolarisSetConsumerCredentials; + rc = VBoxUSBMonSolarisRegisterClient(pState->pDip, &pState->ClientInfo); + if (RT_SUCCESS(rc)) + { +#if 0 + LogRel((DEVICE_NAME ": Captured %s %s (Ident=%s)\n", pState->szMfg, + pState->szProduct, pState->ClientInfo.szDeviceIdent)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Captured %s %s (Ident=%s)\n", pState->szMfg, + pState->szProduct, pState->ClientInfo.szDeviceIdent); +#endif + return DDI_SUCCESS; + } + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisRegisterClient failed! rc=%d " + "path=%s instance=%d\n", rc, pState->ClientInfo.szClientPath, + instance)); + + usb_unregister_hotplug_cbs(pState->pDip); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to register hotplug callbacks! rc=%d\n", rc)); + + ddi_remove_minor_node(pState->pDip, NULL); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: ddi_create_minor_node failed! rc=%d\n", rc)); + + mutex_enter(&pState->Mtx); + vboxUsbSolarisDestroyPower(pState); + mutex_exit(&pState->Mtx); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to init power management! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: vboxUsbSolarisInitEp failed! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: usb_pipe_get_max_bulk_transfer_size failed! rc=%d\n", rc)); + + usb_fini_serialization(pState->StateMulti); + mutex_destroy(&pState->Mtx); + usb_free_dev_data(pState->pDip, pState->pDevDesc); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to get device descriptor. rc=%d\n", rc)); + + usb_client_detach(pState->pDip, NULL); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: usb_client_attach failed! rc=%d\n", rc)); + } + else + { + /* This would appear on every boot if it were LogRel() */ + Log((DEVICE_NAME ": VBoxUSBSolarisAttach: Not a USB device\n")); + } + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to get soft state\n", sizeof(*pState))); + + ddi_soft_state_free(g_pVBoxUSBSolarisState, instance); + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: Failed to alloc soft state. rc=%d\n", rc)); + + return DDI_FAILURE; + } + + case DDI_RESUME: + { + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisAttach: DDI_RESUME failed to get soft state on detach\n")); + return DDI_FAILURE; + } + + vboxUsbSolarisDeviceResume(pState); + 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 Solaris error code. + */ +int VBoxUSBSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisDetach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisDetach: Failed to get soft state on detach\n")); + return DDI_FAILURE; + } + + switch (enmCmd) + { + case DDI_DETACH: + { + /* + * At this point it must be assumed that the default control pipe has + * already been closed by userland (via VBoxUSBSolarisClose() entry point). + * Once it's closed we can no longer open or reference the device here. + */ + + /* + * Notify userland if any that we're gone (while resetting device held by us). + */ + mutex_enter(&pState->Mtx); + pState->DevState = USB_DEV_DISCONNECTED; + vboxUsbSolarisNotifyUnplug(pState); + mutex_exit(&pState->Mtx); + + + /* + * Unregister hotplug callback events first without holding the mutex as the callbacks + * would otherwise block on the mutex. + */ + usb_unregister_hotplug_cbs(pDip); + + /* + * Serialize: paranoid; drain other driver activity. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0 /* timeout */); + usb_release_access(pState->StateMulti); + mutex_enter(&pState->Mtx); + + /* + * Close all pipes. + */ + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + Assert(!pState->fDefaultPipeOpen); + + /* + * Deinitialize power, destroy all endpoints. + */ + vboxUsbSolarisDestroyPower(pState); + vboxUsbSolarisDestroyAllEps(pState); + + /* + * Free up all URB lists. + */ + vboxusb_urb_t *pUrb = NULL; + while ((pUrb = list_remove_head(&pState->hFreeUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + while ((pUrb = list_remove_head(&pState->hInflightUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + while ((pUrb = list_remove_head(&pState->hLandedUrbs)) != NULL) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + } + pState->cFreeUrbs = 0; + pState->cLandedUrbs = 0; + pState->cInflightUrbs = 0; + list_destroy(&pState->hFreeUrbs); + list_destroy(&pState->hInflightUrbs); + list_destroy(&pState->hLandedUrbs); + + /* + * Destroy locks, free up descriptor and detach from USBA. + */ + mutex_exit(&pState->Mtx); + usb_fini_serialization(pState->StateMulti); + mutex_destroy(&pState->Mtx); + + usb_free_dev_data(pState->pDip, pState->pDevDesc); + usb_client_detach(pState->pDip, NULL); + + /* + * Deregister with our Monitor driver. + */ + VBoxUSBMonSolarisUnregisterClient(pState->pDip); + + ddi_remove_minor_node(pState->pDip, NULL); + +#if 0 + LogRel((DEVICE_NAME ": Released %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Released %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, pState->ClientInfo.szDeviceIdent); +#endif + + ddi_soft_state_free(g_pVBoxUSBSolarisState, instance); + pState = NULL; + return DDI_SUCCESS; + } + + case DDI_SUSPEND: + { + int rc = vboxUsbSolarisDeviceSuspend(pState); + if (RT_SUCCESS(rc)) + return DDI_SUCCESS; + + return DDI_FAILURE; + } + + 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 Solaris error code. + */ +int VBoxUSBSolarisGetInfo(dev_info_t *pDip, ddi_info_cmd_t enmCmd, void *pvArg, void **ppvResult) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisGetInfo\n")); + + vboxusb_state_t *pState = NULL; + int instance = getminor((dev_t)pvArg); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + { + /* + * One is to one mapping of instance & minor number as we publish only one minor node per device. + */ + pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (pState) + { + *ppvResult = (void *)pState->pDip; + return DDI_SUCCESS; + } + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisGetInfo: Failed to get device state\n")); + return DDI_FAILURE; + } + + case DDI_INFO_DEVT2INSTANCE: + { + *ppvResult = (void *)(uintptr_t)instance; + return DDI_SUCCESS; + } + + default: + return DDI_FAILURE; + } +} + + +/** + * Callback invoked from the VirtualBox USB Monitor driver when a VM process + * tries to access this USB client instance. + * + * This determines which VM process will be allowed to open and access this USB + * device. + * + * @returns VBox status code. + * + * @param Process The VM process performing the client info. query. + * @param Instance This client instance (the one set while we register + * ourselves to the Monitor driver) + * @param pvReserved Reserved for future, unused. + */ +LOCAL int vboxUsbSolarisSetConsumerCredentials(RTPROCESS Process, int Instance, void *pvReserved) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Process=%u Instance=%d\n", Process, Instance)); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, Instance); + if (!pState) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Failed to get device state for instance %d\n", Instance)); + return VERR_INVALID_STATE; + } + + int rc = VINF_SUCCESS; + mutex_enter(&pState->Mtx); + + if (pState->Process == NIL_RTPROCESS) + pState->Process = Process; + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConsumerCredentials: Failed! Process %u already has client open\n", + pState->Process)); + rc = VERR_RESOURCE_BUSY; + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +int VBoxUSBSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisOpen: pDev=%p fFlag=%d fType=%d pCred=%p\n", pDev, fFlag, fType, pCred)); + + /* + * Verify we are being opened as a character device + */ + if (fType != OTYP_CHR) + return EINVAL; + + /* + * One is to one mapping. (Minor<=>Instance). + */ + int instance = getminor((dev_t)*pDev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: Failed to get device state for instance %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + + /* + * Only one user process can open a device instance at a time. + */ + if (pState->Process != RTProcSelf()) + { + if (pState->Process == NIL_RTPROCESS) + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: No prior information about authorized process\n")); + else + LogRel((DEVICE_NAME ": VBoxUSBSolarisOpen: Process %u is already using this device instance\n", pState->Process)); + + mutex_exit(&pState->Mtx); + return EPERM; + } + + mutex_exit(&pState->Mtx); + + NOREF(fFlag); + NOREF(pCred); + + return 0; +} + + +int VBoxUSBSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisClose: Dev=%d fFlag=%d fType=%d pCred=%p\n", Dev, fFlag, fType, pCred)); + + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisClose: Failed to get device state for instance %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + pState->fPollPending = false; + pState->Process = NIL_RTPROCESS; + mutex_exit(&pState->Mtx); + + return 0; +} + + +int VBoxUSBSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisRead\n")); + return ENOTSUP; +} + + +int VBoxUSBSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisWrite\n")); + return ENOTSUP; +} + + +int VBoxUSBSolarisPoll(dev_t Dev, short fEvents, int fAnyYet, short *pReqEvents, struct pollhead **ppPollHead) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisPoll: Dev=%d fEvents=%d fAnyYet=%d pReqEvents=%p\n", Dev, fEvents, fAnyYet, pReqEvents)); + + /* + * Get the device state (one to one mapping). + */ + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisPoll: No state data for %d\n", instance)); + return ENXIO; + } + + mutex_enter(&pState->Mtx); + + /* + * Disconnect event (POLLHUP) is invalid in "fEvents". + */ + if (pState->DevState == USB_DEV_DISCONNECTED) + *pReqEvents |= POLLHUP; + else if (pState->cLandedUrbs) + *pReqEvents |= POLLIN; + else + { + *pReqEvents = 0; + if (!fAnyYet) + { + *ppPollHead = &pState->PollHead; + pState->fPollPending = true; + } + } + + mutex_exit(&pState->Mtx); + + return 0; +} + + +int VBoxUSBSolarisPower(dev_info_t *pDip, int Component, int Level) +{ + LogFunc((DEVICE_NAME ": VBoxUSBSolarisPower: pDip=%p Component=%d Level=%d\n", pDip, Component, Level)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisPower: Failed! State Gone\n")); + return DDI_FAILURE; + } + + if (!pState->pPower) + return DDI_SUCCESS; + + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + int rc = USB_FAILURE; + if (pState->DevState == USB_DEV_ONLINE) + { + /* + * Check if we are transitioning to a valid power state. + */ + if (!USB_DEV_PWRSTATE_OK(pState->pPower->PowerStates, Level)) + { + switch (Level) + { + case USB_DEV_OS_PWR_OFF: + { + if (pState->pPower->PowerBusy) + break; + + /* + * USB D3 command. + */ + pState->pPower->PowerLevel = USB_DEV_OS_PWR_OFF; + mutex_exit(&pState->Mtx); + rc = USB_SUCCESS; /* usb_set_device_pwrlvl3(pDip); */ + mutex_enter(&pState->Mtx); + break; + } + + case USB_DEV_OS_FULL_PWR: + { + /* + * Can happen during shutdown of the OS. + */ + pState->pPower->PowerLevel = USB_DEV_OS_FULL_PWR; + mutex_exit(&pState->Mtx); + rc = USB_SUCCESS; /* usb_set_device_pwrlvl0(pDip); */ + mutex_enter(&pState->Mtx); + break; + } + + default: /* Power levels 1, 2 not implemented */ + break; + } + } + else + Log((DEVICE_NAME ": VBoxUSBSolarisPower: USB_DEV_PWRSTATE_OK failed\n")); + } + else + rc = USB_SUCCESS; + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + return rc == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE; +} + + +/** @def IOCPARM_LEN + * Gets the length from the ioctl number. + * This is normally defined by sys/ioccom.h on BSD systems... + */ +#ifndef IOCPARM_LEN +# define IOCPARM_LEN(Code) (((Code) >> 16) & IOCPARM_MASK) +#endif + +int VBoxUSBSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal) +{ + /* LogFunc((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Dev=%d Cmd=%d pArg=%p Mode=%d\n", Dev, Cmd, pArg)); */ + + /* + * Get the device state (one to one mapping). + */ + int instance = getminor((dev_t)Dev); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + if (RT_UNLIKELY(!pState)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: No state data for %d\n", instance)); + return EINVAL; + } + + /* + * Read the request wrapper. + */ + VBOXUSBREQ ReqWrap; + if (IOCPARM_LEN(Cmd) != sizeof(ReqWrap)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad request %#x size=%d expected=%d\n", Cmd, IOCPARM_LEN(Cmd), + sizeof(ReqWrap))); + return ENOTTY; + } + + int rc = ddi_copyin((void *)pArg, &ReqWrap, sizeof(ReqWrap), Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyin failed to read header pArg=%p Cmd=%d. rc=%d\n", pArg, Cmd, rc)); + return EINVAL; + } + + if (ReqWrap.u32Magic != VBOXUSB_MAGIC) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad magic %#x; pArg=%p Cmd=%d\n", ReqWrap.u32Magic, pArg, Cmd)); + return EINVAL; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || ReqWrap.cbData > _1M*16)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Bad size %#x; pArg=%p Cmd=%d\n", ReqWrap.cbData, pArg, Cmd)); + return EINVAL; + } + + /* + * Read the request. + */ + void *pvBuf = RTMemTmpAlloc(ReqWrap.cbData); + if (RT_UNLIKELY(!pvBuf)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: RTMemTmpAlloc failed to alloc %d bytes\n", ReqWrap.cbData)); + return ENOMEM; + } + + rc = ddi_copyin((void *)(uintptr_t)ReqWrap.pvDataR3, pvBuf, ReqWrap.cbData, Mode); + if (RT_UNLIKELY(rc)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyin failed! pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, rc)); + return EFAULT; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || pvBuf == NULL)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Invalid request! pvBuf=%p cbData=%d\n", pvBuf, ReqWrap.cbData)); + return EINVAL; + } + + /* + * Process the IOCtl. + */ + size_t cbDataOut = 0; + rc = vboxUsbSolarisProcessIOCtl(Cmd, pState, Mode, &ReqWrap, pvBuf, &cbDataOut); + ReqWrap.rc = rc; + rc = 0; + + if (RT_UNLIKELY(cbDataOut > ReqWrap.cbData)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: Too much output data %d expected %d Truncating!\n", cbDataOut, + ReqWrap.cbData)); + cbDataOut = ReqWrap.cbData; + } + + ReqWrap.cbData = cbDataOut; + + /* + * Copy VBOXUSBREQ back to userspace (which contains rc for USB operation). + */ + rc = ddi_copyout(&ReqWrap, (void *)pArg, sizeof(ReqWrap), Mode); + if (RT_LIKELY(!rc)) + { + /* + * Copy payload (if any) back to userspace. + */ + if (cbDataOut > 0) + { + rc = ddi_copyout(pvBuf, (void *)(uintptr_t)ReqWrap.pvDataR3, cbDataOut, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyout failed! pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, + Cmd, rc)); + rc = EFAULT; + } + } + } + else + { + LogRel((DEVICE_NAME ": VBoxUSBSolarisIOCtl: ddi_copyout(1)failed! pReqWrap=%p pArg=%p Cmd=%d. rc=%d\n", &ReqWrap, pArg, + Cmd, rc)); + rc = EFAULT; + } + + *pVal = rc; + RTMemTmpFree(pvBuf); + return rc; +} + + +/** + * IOCtl processor for user to kernel and kernel to kernel communication. + * + * @returns VBox status code. + * + * @param iFunction The requested function. + * @param pvState The USB device instance. + * @param Mode The IOCtl mode. + * @param pUSBReq Pointer to the VBOXUSB request. + * @param pvBuf Pointer to the ring-3 URB. + * @param pcbDataOut Where to store the IOCtl OUT data size. + */ +LOCAL int vboxUsbSolarisProcessIOCtl(int iFunction, void *pvState, int Mode, PVBOXUSBREQ pUSBReq, void *pvBuf, + size_t *pcbDataOut) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: iFunction=%d pvState=%p pUSBReq=%p\n", iFunction, pvState, pUSBReq)); */ + + AssertPtrReturn(pvState, VERR_INVALID_PARAMETER); + vboxusb_state_t *pState = (vboxusb_state_t *)pvState; + size_t cbData = pUSBReq->cbData; + int rc; + +#define CHECKRET_MIN_SIZE(mnemonic, cbMin) \ + do { \ + if (cbData < (cbMin)) \ + { \ + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: " mnemonic ": cbData=%#zx (%zu) min is %#zx (%zu)\n", \ + cbData, cbData, (size_t)(cbMin), (size_t)(cbMin))); \ + return VERR_BUFFER_OVERFLOW; \ + } \ + if ((cbMin) != 0 && !VALID_PTR(pvBuf)) \ + { \ + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: " mnemonic ": Invalid pointer %p\n", pvBuf)); \ + return VERR_INVALID_PARAMETER; \ + } \ + } while (0) + + switch (iFunction) + { + case VBOXUSB_IOCTL_SEND_URB: + { + CHECKRET_MIN_SIZE("SEND_URB", sizeof(VBOXUSBREQ_URB)); + + PVBOXUSBREQ_URB pUrbReq = (PVBOXUSBREQ_URB)pvBuf; + rc = vboxUsbSolarisSendUrb(pState, pUrbReq, Mode); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SEND_URB returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_REAP_URB: + { + CHECKRET_MIN_SIZE("REAP_URB", sizeof(VBOXUSBREQ_URB)); + + PVBOXUSBREQ_URB pUrbReq = (PVBOXUSBREQ_URB)pvBuf; + rc = vboxUsbSolarisReapUrb(pState, pUrbReq, Mode); + *pcbDataOut = sizeof(VBOXUSBREQ_URB); + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: REAP_URB returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_CLEAR_EP: + { + CHECKRET_MIN_SIZE("CLEAR_EP", sizeof(VBOXUSBREQ_CLEAR_EP)); + + PVBOXUSBREQ_CLEAR_EP pClearEpReq = (PVBOXUSBREQ_CLEAR_EP)pvBuf; + rc = vboxUsbSolarisClearEndPoint(pState, pClearEpReq->bEndpoint); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: CLEAR_EP returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_SET_CONFIG: + { + CHECKRET_MIN_SIZE("SET_CONFIG", sizeof(VBOXUSBREQ_SET_CONFIG)); + + PVBOXUSBREQ_SET_CONFIG pSetCfgReq = (PVBOXUSBREQ_SET_CONFIG)pvBuf; + rc = vboxUsbSolarisSetConfig(pState, pSetCfgReq->bConfigValue); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SET_CONFIG returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_SET_INTERFACE: + { + CHECKRET_MIN_SIZE("SET_INTERFACE", sizeof(VBOXUSBREQ_SET_INTERFACE)); + + PVBOXUSBREQ_SET_INTERFACE pSetInterfaceReq = (PVBOXUSBREQ_SET_INTERFACE)pvBuf; + rc = vboxUsbSolarisSetInterface(pState, pSetInterfaceReq->bInterface, pSetInterfaceReq->bAlternate); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: SET_INTERFACE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_CLOSE_DEVICE: + { + CHECKRET_MIN_SIZE("CLOSE_DEVICE", sizeof(VBOXUSBREQ_CLOSE_DEVICE)); + + PVBOXUSBREQ_CLOSE_DEVICE pCloseDeviceReq = (PVBOXUSBREQ_CLOSE_DEVICE)pvBuf; + if ( pCloseDeviceReq->ResetLevel != VBOXUSB_RESET_LEVEL_REATTACH + || (Mode & FKIOCTL)) + { + rc = vboxUsbSolarisCloseDevice(pState, pCloseDeviceReq->ResetLevel); + } + else + { + /* Userland IOCtls are not allowed to perform a reattach of the device. */ + rc = VERR_NOT_SUPPORTED; + } + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: CLOSE_DEVICE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_ABORT_PIPE: + { + CHECKRET_MIN_SIZE("ABORT_PIPE", sizeof(VBOXUSBREQ_ABORT_PIPE)); + + PVBOXUSBREQ_ABORT_PIPE pAbortPipeReq = (PVBOXUSBREQ_ABORT_PIPE)pvBuf; + rc = vboxUsbSolarisAbortPipe(pState, pAbortPipeReq->bEndpoint); + *pcbDataOut = 0; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: ABORT_PIPE returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_GET_CONFIG: + { + CHECKRET_MIN_SIZE("GET_CONFIG", sizeof(VBOXUSBREQ_GET_CONFIG)); + + PVBOXUSBREQ_GET_CONFIG pGetCfgReq = (PVBOXUSBREQ_GET_CONFIG)pvBuf; + rc = vboxUsbSolarisGetConfig(pState, &pGetCfgReq->bConfigValue); + *pcbDataOut = sizeof(VBOXUSBREQ_GET_CONFIG); + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: GET_CONFIG returned %d\n", rc)); + break; + } + + case VBOXUSB_IOCTL_GET_VERSION: + { + CHECKRET_MIN_SIZE("GET_VERSION", sizeof(VBOXUSBREQ_GET_VERSION)); + + PVBOXUSBREQ_GET_VERSION pGetVersionReq = (PVBOXUSBREQ_GET_VERSION)pvBuf; + pGetVersionReq->u32Major = VBOXUSB_VERSION_MAJOR; + pGetVersionReq->u32Minor = VBOXUSB_VERSION_MINOR; + *pcbDataOut = sizeof(VBOXUSBREQ_GET_VERSION); + rc = VINF_SUCCESS; + Log((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: GET_VERSION returned %d\n", rc)); + break; + } + + default: + { + LogRel((DEVICE_NAME ": vboxUsbSolarisProcessIOCtl: Unknown request %#x\n", iFunction)); + rc = VERR_NOT_SUPPORTED; + *pcbDataOut = 0; + break; + } + } + + pUSBReq->cbData = *pcbDataOut; + return rc; +} + + +/** + * Initializes device power management. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitPower(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitPower: pState=%p\n", pState)); + + int rc = usb_handle_remote_wakeup(pState->pDip, USB_REMOTE_WAKEUP_ENABLE); + if (rc == USB_SUCCESS) + { + vboxusb_power_t *pPower = RTMemAllocZ(sizeof(vboxusb_power_t)); + if (RT_LIKELY(pPower)) + { + mutex_enter(&pState->Mtx); + pState->pPower = pPower; + pState->pPower->fPowerWakeup = false; + mutex_exit(&pState->Mtx); + + uint_t PowerStates; + rc = usb_create_pm_components(pState->pDip, &PowerStates); + if (rc == USB_SUCCESS) + { + pState->pPower->fPowerWakeup = true; + pState->pPower->PowerLevel = USB_DEV_OS_FULL_PWR; + pState->pPower->PowerStates = PowerStates; + + rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + + if (rc != DDI_SUCCESS) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to raise power level usb(%#x,%#x)\n", + pState->pDevDesc->dev_descr->idVendor, pState->pDevDesc->dev_descr->idProduct)); + } + } + else + Log((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to create power components\n")); + + return VINF_SUCCESS; + } + else + rc = VERR_NO_MEMORY; + } + else + { + Log((DEVICE_NAME ": vboxUsbSolarisInitPower: Failed to enable remote wakeup, No PM!\n")); + rc = VINF_SUCCESS; + } + + return rc; +} + + +/** + * Destroys device power management. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + * + * @returns VBox status code. + */ +LOCAL void vboxUsbSolarisDestroyPower(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyPower: pState=%p\n", pState)); + + if (pState->pPower) + { + mutex_exit(&pState->Mtx); + vboxUsbSolarisPowerBusy(pState); + mutex_enter(&pState->Mtx); + + int rc = -1; + if ( pState->pPower->fPowerWakeup + && pState->DevState != USB_DEV_DISCONNECTED) + { + mutex_exit(&pState->Mtx); + rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Raising power failed! rc=%d\n", rc)); + + rc = usb_handle_remote_wakeup(pState->pDip, USB_REMOTE_WAKEUP_DISABLE); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Failed to disable remote wakeup\n")); + } + else + mutex_exit(&pState->Mtx); + + rc = pm_lower_power(pState->pDip, 0 /* component */, USB_DEV_OS_PWR_OFF); + if (rc != DDI_SUCCESS) + Log((DEVICE_NAME ": vboxUsbSolarisDestroyPower: Lowering power failed! rc=%d\n", rc)); + + vboxUsbSolarisPowerIdle(pState); + mutex_enter(&pState->Mtx); + RTMemFree(pState->pPower); + pState->pPower = NULL; + } +} + + +/** + * Converts Solaris' USBA URB status to VBox's USB URB status. + * + * @param Status Solaris USBA USB URB status. + * + * @returns VBox USB URB status. + */ +LOCAL VUSBSTATUS vboxUsbSolarisGetUrbStatus(usb_cr_t Status) +{ + switch (Status) + { + case USB_CR_OK: return VUSBSTATUS_OK; + case USB_CR_CRC: return VUSBSTATUS_CRC; + case USB_CR_DEV_NOT_RESP: return VUSBSTATUS_DNR; + case USB_CR_DATA_UNDERRUN: return VUSBSTATUS_DATA_UNDERRUN; + case USB_CR_DATA_OVERRUN: return VUSBSTATUS_DATA_OVERRUN; + case USB_CR_STALL: return VUSBSTATUS_STALL; + /* + case USB_CR_BITSTUFFING: + case USB_CR_DATA_TOGGLE_MM: + case USB_CR_PID_CHECKFAILURE: + case USB_CR_UNEXP_PID: + case USB_CR_BUFFER_OVERRUN: + case USB_CR_BUFFER_UNDERRUN: + case USB_CR_TIMEOUT: + case USB_CR_NOT_ACCESSED: + case USB_CR_NO_RESOURCES: + case USB_CR_UNSPECIFIED_ERR: + case USB_CR_STOPPED_POLLING: + case USB_CR_PIPE_CLOSING: + case USB_CR_PIPE_RESET: + case USB_CR_NOT_SUPPORTED: + case USB_CR_FLUSHED: + case USB_CR_HC_HARDWARE_ERR: + */ + default: return VUSBSTATUS_INVALID; + } +} + + +/** + * Converts Solaris' USBA error code to VBox's error code. + * + * @param UsbRc Solaris USBA error code. + * + * @returns VBox error code. + */ +static int vboxUsbSolarisToVBoxRC(int UsbRc) +{ + switch (UsbRc) + { + case USB_SUCCESS: return VINF_SUCCESS; + case USB_INVALID_ARGS: return VERR_INVALID_PARAMETER; + case USB_INVALID_PIPE: return VERR_BAD_PIPE; + case USB_INVALID_CONTEXT: return VERR_INVALID_CONTEXT; + case USB_BUSY: return VERR_PIPE_BUSY; + case USB_PIPE_ERROR: return VERR_PIPE_IO_ERROR; + /* + case USB_FAILURE: + case USB_NO_RESOURCES: + case USB_NO_BANDWIDTH: + case USB_NOT_SUPPORTED: + case USB_PIPE_ERROR: + case USB_NO_FRAME_NUMBER: + case USB_INVALID_START_FRAME: + case USB_HC_HARDWARE_ERROR: + case USB_INVALID_REQUEST: + case USB_INVALID_VERSION: + case USB_INVALID_PERM: + */ + default: return VERR_GENERAL_FAILURE; + } +} + + +/** + * Converts Solaris' USBA device state to VBox's error code. + * + * @param uDeviceState The USB device state to convert. + * + * @returns VBox error code. + */ +static int vboxUsbSolarisDeviceState(uint8_t uDeviceState) +{ + switch (uDeviceState) + { + case USB_DEV_ONLINE: return VINF_SUCCESS; + case USB_DEV_SUSPENDED: return VERR_VUSB_DEVICE_IS_SUSPENDED; + case USB_DEV_DISCONNECTED: + case USB_DEV_PWRED_DOWN: return VERR_VUSB_DEVICE_NOT_ATTACHED; + default: return VERR_GENERAL_FAILURE; + } +} + + +/** + * Checks if the device is a USB device. + * + * @param pDip Pointer to this device info. structure. + * + * @returns If this is really a USB device returns true, otherwise false. + */ +LOCAL bool vboxUsbSolarisIsUSBDevice(dev_info_t *pDip) +{ + int rc = DDI_FAILURE; + + /* + * Check device for "usb" compatible property, root hubs->device would likely mean parent has no "usb" property. + */ + char **ppszCompatible = NULL; + uint_t cCompatible; + rc = ddi_prop_lookup_string_array(DDI_DEV_T_ANY, pDip, DDI_PROP_DONTPASS, "compatible", &ppszCompatible, &cCompatible); + if (RT_LIKELY(rc == DDI_PROP_SUCCESS)) + { + while (cCompatible--) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Compatible[%d]=%s\n", cCompatible, ppszCompatible[cCompatible])); + if (!strncmp(ppszCompatible[cCompatible], "usb", 3)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Verified device as USB. pszCompatible=%s\n", + ppszCompatible[cCompatible])); + ddi_prop_free(ppszCompatible); + return true; + } + } + + ddi_prop_free(ppszCompatible); + ppszCompatible = NULL; + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: USB property lookup failed, rc=%d\n", rc)); + + /* + * Check parent for "usb" compatible property. + */ + dev_info_t *pParentDip = ddi_get_parent(pDip); + if (pParentDip) + { + rc = ddi_prop_lookup_string_array(DDI_DEV_T_ANY, pParentDip, DDI_PROP_DONTPASS, "compatible", &ppszCompatible, + &cCompatible); + if (RT_LIKELY(rc == DDI_PROP_SUCCESS)) + { + while (cCompatible--) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Parent compatible[%d]=%s\n", cCompatible, + ppszCompatible[cCompatible])); + if (!strncmp(ppszCompatible[cCompatible], "usb", 3)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Verified device as USB. parent pszCompatible=%s\n", + ppszCompatible[cCompatible])); + ddi_prop_free(ppszCompatible); + return true; + } + } + + ddi_prop_free(ppszCompatible); + ppszCompatible = NULL; + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: USB parent property lookup failed. rc=%d\n", rc)); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsUSBDevice: Failed to obtain parent device for property lookup\n")); + + return false; +} + + +/** + * Submits a URB. + * + * @param pState The USB device instance. + * @param pUrbReq Pointer to the VBox USB URB. + * @param Mode The IOCtl mode. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSendUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode) +{ + int iEpIndex = VBOXUSB_GET_EP_INDEX(pUrbReq->bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + AssertPtrReturn(pEp, VERR_INVALID_POINTER); + Assert(pUrbReq); + +#if 0 + LogFunc((DEVICE_NAME ": vboxUsbSolarisSendUrb: pState=%p pUrbReq=%p bEndpoint=%#x[%d] enmDir=%#x enmType=%#x " + "cbData=%d pvData=%p\n", pState, pUrbReq, pUrbReq->bEndpoint, iEpIndex, pUrbReq->enmDir, + pUrbReq->enmType, pUrbReq->cbData, pUrbReq->pvData)); +#endif + + if (RT_UNLIKELY(!pUrbReq->pvData)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Invalid request - No data\n")); + return VERR_INVALID_POINTER; + } + + /* + * Allocate message block & copy userspace buffer for host to device Xfers and for + * Control Xfers (since input has Setup header that needs copying). + */ + mblk_t *pMsg = NULL; + int rc = VINF_SUCCESS; + if ( pUrbReq->enmDir == VUSBDIRECTION_OUT + || pUrbReq->enmType == VUSBXFERTYPE_MSG) + { + pMsg = allocb(pUrbReq->cbData, BPRI_HI); + if (RT_UNLIKELY(!pMsg)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Failed to allocate %u bytes\n", pUrbReq->cbData)); + return VERR_NO_MEMORY; + } + + rc = ddi_copyin(pUrbReq->pvData, pMsg->b_wptr, pUrbReq->cbData, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: ddi_copyin failed! rc=%d\n", rc)); + freemsg(pMsg); + return VERR_NO_MEMORY; + } + + pMsg->b_wptr += pUrbReq->cbData; + } + + mutex_enter(&pState->Mtx); + rc = vboxUsbSolarisDeviceState(pState->DevState); + if (!pState->fDefaultPipeOpen) /* Required for Isoc. IN Xfers which don't Xfer through the pipe after polling starts */ + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + if (RT_SUCCESS(rc)) + { + /* + * Open the pipe if needed. + */ + rc = vboxUsbSolarisOpenPipe(pState, pEp); + if (RT_UNLIKELY(RT_FAILURE(rc))) + { + mutex_exit(&pState->Mtx); + freemsg(pMsg); + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: OpenPipe failed! pState=%p pUrbReq=%p bEndpoint=%#x enmDir=%#x " + "enmType=%#x cbData=%d pvData=%p rc=%d\n", pState, pUrbReq, pUrbReq->bEndpoint, pUrbReq->enmDir, + pUrbReq->enmType, pUrbReq->cbData, pUrbReq->pvData, rc)); + return VERR_BAD_PIPE; + } + + mutex_exit(&pState->Mtx); + + vboxusb_urb_t *pUrb = NULL; + if ( pUrbReq->enmType == VUSBXFERTYPE_ISOC + && pUrbReq->enmDir == VUSBDIRECTION_IN) + pUrb = vboxUsbSolarisGetIsocInUrb(pState, pUrbReq); + else + pUrb = vboxUsbSolarisQueueUrb(pState, pUrbReq, pMsg); + + if (RT_LIKELY(pUrb)) + { + switch (pUrb->enmType) + { + case VUSBXFERTYPE_MSG: + { + rc = vboxUsbSolarisCtrlXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_BULK: + { + rc = vboxUsbSolarisBulkXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_INTR: + { + rc = vboxUsbSolarisIntrXfer(pState, pEp, pUrb); + break; + } + + case VUSBXFERTYPE_ISOC: + { + rc = vboxUsbSolarisIsocXfer(pState, pEp, pUrb); + break; + } + + default: + { + LogRelMax(5, (DEVICE_NAME ": vboxUsbSolarisSendUrb: URB type unsupported %d\n", pUrb->enmType)); + rc = VERR_NOT_SUPPORTED; + break; + } + } + + if (RT_FAILURE(rc)) + { + mutex_enter(&pState->Mtx); + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + pMsg = NULL; + + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + RTMemFree(pUrb); + pUrb = NULL; + } + else + { + /* + * Xfer failed, move URB back to the free list. + */ + list_remove(&pState->hInflightUrbs, pUrb); + Assert(pState->cInflightUrbs > 0); + --pState->cInflightUrbs; + + pUrb->enmState = VBOXUSB_URB_STATE_FREE; + Assert(!pUrb->pMsg); + list_insert_head(&pState->hFreeUrbs, pUrb); + ++pState->cFreeUrbs; + } + mutex_exit(&pState->Mtx); + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSendUrb: Failed to queue URB\n")); + rc = VERR_NO_MEMORY; + freemsg(pMsg); + } + } + else + { + mutex_exit(&pState->Mtx); + freemsg(pMsg); + } + + return rc; +} + + +/** + * Reaps a completed URB. + * + * @param pState The USB device instance. + * @param pUrbReq Pointer to the VBox USB URB. + * @param Mode The IOCtl mode. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisReapUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, int Mode) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisReapUrb: pState=%p pUrbReq=%p\n", pState, pUrbReq)); */ + + AssertPtrReturn(pUrbReq, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + mutex_enter(&pState->Mtx); + rc = vboxUsbSolarisDeviceState(pState->DevState); + if (!pState->fDefaultPipeOpen) + rc = VERR_VUSB_DEVICE_NOT_ATTACHED; + if (RT_SUCCESS(rc)) + { + vboxusb_urb_t *pUrb = list_remove_head(&pState->hLandedUrbs); + if (pUrb) + { + Assert(pState->cLandedUrbs > 0); + --pState->cLandedUrbs; + } + + /* + * It is safe to access pUrb->pMsg outside the state mutex because this is from the landed URB list + * and not the inflight URB list. + */ + mutex_exit(&pState->Mtx); + if (pUrb) + { + /* + * Copy the URB which will then be copied to user-space. + */ + pUrbReq->pvUrbR3 = pUrb->pvUrbR3; + pUrbReq->bEndpoint = pUrb->bEndpoint; + pUrbReq->enmType = pUrb->enmType; + pUrbReq->enmDir = pUrb->enmDir; + pUrbReq->enmStatus = pUrb->enmStatus; + pUrbReq->pvData = (void *)pUrb->pvDataR3; + pUrbReq->cbData = pUrb->cbDataR3; + + if (RT_LIKELY(pUrb->pMsg)) + { + /* + * Copy the message back into the user buffer. + */ + if (RT_LIKELY(pUrb->pvDataR3 != NIL_RTR3PTR)) + { + Assert(!pUrb->pMsg->b_cont); /* We really should have a single message block always. */ + size_t cbData = RT_MIN(MBLKL(pUrb->pMsg), pUrb->cbDataR3); + pUrbReq->cbData = cbData; + + if (RT_LIKELY(cbData)) + { + rc = ddi_copyout(pUrb->pMsg->b_rptr, (void *)pUrbReq->pvData, cbData, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisReapUrb: ddi_copyout failed! rc=%d\n", rc)); + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + } + } + + Log((DEVICE_NAME ": vboxUsbSolarisReapUrb: pvUrbR3=%p pvDataR3=%p cbData=%d\n", pUrbReq->pvUrbR3, + pUrbReq->pvData, pUrbReq->cbData)); + } + else + { + pUrbReq->cbData = 0; + rc = VERR_INVALID_POINTER; + LogRel((DEVICE_NAME ": vboxUsbSolarisReapUrb: Missing pvDataR3!!\n")); + } + + /* + * Free buffer allocated in vboxUsbSolarisSendUrb or vboxUsbSolaris[Ctrl|Bulk|Intr]Xfer(). + */ + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + } + else + { + if ( pUrb->enmType == VUSBXFERTYPE_ISOC + && pUrb->enmDir == VUSBDIRECTION_IN) + { + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + pUrbReq->cbData = 0; + } + } + + /* + * Copy Isoc packet descriptors. + */ + if (pUrb->enmType == VUSBXFERTYPE_ISOC) + { + AssertCompile(sizeof(pUrbReq->aIsocPkts) == sizeof(pUrb->aIsocPkts)); + pUrbReq->cIsocPkts = pUrb->cIsocPkts; + + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + { + pUrbReq->aIsocPkts[i].cbPkt = pUrb->aIsocPkts[i].cbPkt; + pUrbReq->aIsocPkts[i].cbActPkt = pUrb->aIsocPkts[i].cbActPkt; + pUrbReq->aIsocPkts[i].enmStatus = pUrb->aIsocPkts[i].enmStatus; + } + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + RTMemFree(pUrb); + pUrb = NULL; + } + } + + if (pUrb) + { + /* + * Add URB back to the free list. + */ + Assert(!pUrb->pMsg); + pUrb->cbDataR3 = 0; + pUrb->pvDataR3 = NIL_RTR3PTR; + pUrb->enmState = VBOXUSB_URB_STATE_FREE; + mutex_enter(&pState->Mtx); + list_insert_head(&pState->hFreeUrbs, pUrb); + ++pState->cFreeUrbs; + mutex_exit(&pState->Mtx); + } + } + else + { + pUrbReq->pvUrbR3 = NULL; + pUrbReq->cbData = 0; + pUrbReq->pvData = NULL; + pUrbReq->enmStatus = VUSBSTATUS_INVALID; + } + } + else + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Clears a pipe (CLEAR_FEATURE). + * + * @param pState The USB device instance. + * @param bEndpoint The Endpoint address. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisClearEndPoint(vboxusb_state_t *pState, uint8_t bEndpoint) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: pState=%p bEndpoint=%#x\n", pState, bEndpoint)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + int iEpIndex = VBOXUSB_GET_EP_INDEX(bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + if (RT_LIKELY(pEp)) + { + /* + * Check if the endpoint is open to be cleared. + */ + if (pEp->pPipe) + { + mutex_exit(&pState->Mtx); + + /* + * Synchronous reset pipe. + */ + usb_pipe_reset(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + NULL, /* Completion callback */ + NULL); /* Exception callback */ + + mutex_enter(&pState->Mtx); + + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: bEndpoint=%#x[%d] returns %d\n", bEndpoint, iEpIndex, rc)); + + rc = VINF_SUCCESS; + } + else + { + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Not opened to be cleared. Faking success. bEndpoint=%#x\n", + bEndpoint)); + rc = VINF_SUCCESS; + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Endpoint missing! bEndpoint=%#x[%d]\n", bEndpoint, iEpIndex)); + rc = VERR_GENERAL_FAILURE; + } + } + else + Log((DEVICE_NAME ": vboxUsbSolarisClearEndPoint: Device not online, state=%d\n", pState->DevState)); + + mutex_exit(&pState->Mtx); + return rc; +} + + +/** + * Sets configuration (SET_CONFIGURATION) + * + * @param pState The USB device instance. + * @param bConfig The Configuration. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSetConfig(vboxusb_state_t *pState, uint8_t bConfig) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetConfig: pState=%p bConfig=%u\n", pState, bConfig)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + vboxUsbSolarisCloseAllPipes(pState, false /* ControlPipe */); + int iCfgIndex = vboxUsbSolarisGetConfigIndex(pState, bConfig); + + if ( iCfgIndex >= 0 + && iCfgIndex < pState->pDevDesc->dev_n_cfg) + { + /* + * Switch Config synchronously. + */ + mutex_exit(&pState->Mtx); + rc = usb_set_cfg(pState->pDip, (uint_t)iCfgIndex, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback data */); + mutex_enter(&pState->Mtx); + + if (rc == USB_SUCCESS) + { + int rc2 = vboxUsbSolarisInitEpsForCfg(pState); + AssertRC(rc2); NOREF(rc2); + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConfig: usb_set_cfg failed for iCfgIndex=%#x bConfig=%u rc=%d\n", + iCfgIndex, bConfig, rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetConfig: Invalid iCfgIndex=%d bConfig=%u\n", iCfgIndex, bConfig)); + rc = VERR_OUT_OF_RANGE; + } + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Gets configuration (GET_CONFIGURATION) + * + * @param pState The USB device instance. + * @param pbConfig Where to store the Configuration. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisGetConfig(vboxusb_state_t *pState, uint8_t *pbConfig) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisGetConfig: pState=%p pbConfig=%p\n", pState, pbConfig)); + AssertPtrReturn(pbConfig, VERR_INVALID_POINTER); + + /* + * Get Config synchronously. + */ + uint_t bConfig; + int rc = usb_get_cfg(pState->pDip, &bConfig, USB_FLAGS_SLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + *pbConfig = bConfig; + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisGetConfig: Failed, rc=%d\n", rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + + Log((DEVICE_NAME ": vboxUsbSolarisGetConfig: Returns %d bConfig=%u\n", rc, *pbConfig)); + return rc; +} + + +/** + * Sets interface (SET_INTERFACE) and alternate. + * + * @param pState The USB device instance. + * @param bIf The Interface. + * @param bAlt The Alternate setting. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisSetInterface(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisSetInterface: pState=%p bIf=%#x bAlt=%#x\n", pState, bIf, bAlt)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + /* + * Set Interface & Alt setting synchronously. + */ + mutex_exit(&pState->Mtx); + rc = usb_set_alt_if(pState->pDip, bIf, bAlt, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback data */); + mutex_enter(&pState->Mtx); + + if (rc == USB_SUCCESS) + { + Log((DEVICE_NAME ": vboxUsbSolarisSetInterface: Success, bIf=%#x bAlt=%#x\n", bIf, bAlt, rc)); + int rc2 = vboxUsbSolarisInitEpsForIfAlt(pState, bIf, bAlt); + AssertRC(rc2); NOREF(rc2); + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisSetInterface: usb_set_alt_if failed for bIf=%#x bAlt=%#x rc=%d\n", bIf, bAlt, rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + } + + mutex_exit(&pState->Mtx); + + return rc; +} + + +/** + * Closes the USB device and optionally resets it. + * + * @param pState The USB device instance. + * @param enmReset The reset level. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisCloseDevice(vboxusb_state_t *pState, VBOXUSB_RESET_LEVEL enmReset) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCloseDevice: pState=%p enmReset=%d\n", pState, enmReset)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + + if (enmReset == VBOXUSB_RESET_LEVEL_CLOSE) + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + else + vboxUsbSolarisCloseAllPipes(pState, false /* ControlPipe */); + + mutex_exit(&pState->Mtx); + + if (RT_SUCCESS(rc)) + { + switch (enmReset) + { + case VBOXUSB_RESET_LEVEL_REATTACH: + rc = usb_reset_device(pState->pDip, USB_RESET_LVL_REATTACH); + break; + + case VBOXUSB_RESET_LEVEL_SOFT: + rc = usb_reset_device(pState->pDip, USB_RESET_LVL_DEFAULT); + break; + + default: + rc = USB_SUCCESS; + break; + } + + rc = vboxUsbSolarisToVBoxRC(rc); + } + + Log((DEVICE_NAME ": vboxUsbSolarisCloseDevice: Returns %d\n", rc)); + return rc; +} + + +/** + * Aborts pending requests and reset the pipe. + * + * @param pState The USB device instance. + * @param bEndpoint The Endpoint address. + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisAbortPipe(vboxusb_state_t *pState, uint8_t bEndpoint) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisAbortPipe: pState=%p bEndpoint=%#x\n", pState, bEndpoint)); + + mutex_enter(&pState->Mtx); + int rc = vboxUsbSolarisDeviceState(pState->DevState); + if (RT_SUCCESS(rc)) + { + int iEpIndex = VBOXUSB_GET_EP_INDEX(bEndpoint); + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + vboxusb_ep_t *pEp = &pState->aEps[iEpIndex]; + if (RT_LIKELY(pEp)) + { + if (pEp->pPipe) + { + /* + * Aborting requests not supported for the default control pipe. + */ + if ((pEp->EpDesc.bEndpointAddress & USB_EP_NUM_MASK) == 0) + { + mutex_exit(&pState->Mtx); + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Cannot reset default control pipe\n")); + return VERR_NOT_SUPPORTED; + } + + mutex_exit(&pState->Mtx); + usb_pipe_reset(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + NULL, /* Completion callback */ + NULL); /* Callback's parameter */ + + /* + * Allow pending async requests to complete. + */ + /** @todo this is most likely not required. */ + rc = usb_pipe_drain_reqs(pState->pDip, pEp->pPipe, + USB_FLAGS_SLEEP, /* Synchronous */ + 5, /* Timeout (seconds) */ + NULL, /* Completion callback */ + NULL); /* Callback's parameter */ + + mutex_enter(&pState->Mtx); + + Log((DEVICE_NAME ": vboxUsbSolarisAbortPipe: usb_pipe_drain_reqs returns %d\n", rc)); + rc = vboxUsbSolarisToVBoxRC(rc); + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: pipe not open. bEndpoint=%#x\n", bEndpoint)); + rc = VERR_PIPE_IO_ERROR; + } + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Invalid pipe bEndpoint=%#x[%d]\n", bEndpoint, iEpIndex)); + rc = VERR_INVALID_HANDLE; + } + } + + mutex_exit(&pState->Mtx); + + LogFunc((DEVICE_NAME ": vboxUsbSolarisAbortPipe: Returns %d\n", rc)); + return rc; +} + + +/** + * Initializes an Endpoint. + * + * @param pState The USB device instance. + * @param pEpData The Endpoint data (NULL implies the default + * endpoint). + * + * @returns VBox error code. + */ +LOCAL int vboxUsbSolarisInitEp(vboxusb_state_t *pState, usb_ep_data_t *pEpData) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEp: pState=%p pEpData=%p", pState, pEpData)); + + /* + * Is this the default endpoint? + */ + usb_ep_descr_t *pEpDesc = NULL; + vboxusb_ep_t *pEp = NULL; + int iEpIndex; + if (!pEpData) + { + iEpIndex = 0; + pEpDesc = &g_VBoxUSBSolarisDefaultEpDesc; + } + else + { + iEpIndex = VBOXUSB_GET_EP_INDEX(pEpData->ep_descr.bEndpointAddress); + pEpDesc = (usb_ep_descr_t *)((uint8_t *)pEpData + g_offUsbEpDataDescr); + } + + Assert(iEpIndex >= 0 && iEpIndex < RT_ELEMENTS(pState->aEps)); + pEp = &pState->aEps[iEpIndex]; + + /* + * Initialize the endpoint. + */ + pEp->EpDesc = *pEpDesc; + if (!pEp->fInitialized) + { + pEp->pPipe = NULL; + bzero(&pEp->PipePolicy, sizeof(pEp->PipePolicy)); + pEp->PipePolicy.pp_max_async_reqs = VBOXUSB_MAX_PIPE_ASYNC_REQS; + pEp->fIsocPolling = false; + list_create(&pEp->hIsocInUrbs, sizeof(vboxusb_urb_t), offsetof(vboxusb_urb_t, hListLink)); + pEp->cIsocInUrbs = 0; + list_create(&pEp->hIsocInLandedReqs, sizeof(vboxusb_isoc_req_t), offsetof(vboxusb_isoc_req_t, hListLink)); + pEp->cbIsocInLandedReqs = 0; + pEp->cbMaxIsocData = 0; + pEp->fInitialized = true; + } + + Log((DEVICE_NAME ": vboxUsbSolarisInitEp: Success, %s[%2d] %s %s bEndpoint=%#x\n", !pEpData ? "Default " : "Endpoint", + iEpIndex, vboxUsbSolarisEpType(pEp), vboxUsbSolarisEpDir(pEp), pEp->EpDesc.bEndpointAddress)); + return VINF_SUCCESS; +} + + +/** + * Initializes Endpoints for the current configuration, all interfaces and + * alternate setting 0 for each interface. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitEpsForCfg(vboxusb_state_t *pState) +{ + uint_t uCfgIndex = usb_get_current_cfgidx(pState->pDip); + if (uCfgIndex >= pState->pDevDesc->dev_n_cfg) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: Invalid current config index %u\n", uCfgIndex)); + return VERR_OUT_OF_RANGE; + } + + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[uCfgIndex]; + uchar_t bConfig = pConfig->cfg_descr.bConfigurationValue; + + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: pState=%p bConfig=%u uCfgIndex=%u\n", pState, bConfig, uCfgIndex)); + + const uint_t cIfs = pConfig->cfg_n_if; + for (uchar_t uIf = 0; uIf < cIfs; uIf++) + { + usb_if_data_t *pIf = &pConfig->cfg_if[uIf]; + const uint_t cAlts = pIf->if_n_alt; + for (uchar_t uAlt = 0; uAlt < cAlts; uAlt++) + { + usb_alt_if_data_t *pAlt = &pIf->if_alt[uAlt]; + if (pAlt->altif_descr.bAlternateSetting == 0) /* Refer USB 2.0 spec 9.6.5 "Interface" */ + { + const uint_t cEps = pAlt->altif_n_ep; + for (uchar_t uEp = 0; uEp < cEps; uEp++) + { + uint8_t *pbEpData = (uint8_t *)&pAlt->altif_ep[0]; + usb_ep_data_t *pEpData = (usb_ep_data_t *)(pbEpData + uEp * g_cbUsbEpData); + int rc = vboxUsbSolarisInitEp(pState, pEpData); + if (RT_FAILURE(rc)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForCfg: Failed to init endpoint! " + "bConfig=%u bIf=%#x bAlt=%#x\n", bConfig, pAlt->altif_descr.bInterfaceNumber, + pAlt->altif_descr.bAlternateSetting)); + return rc; + } + } + break; /* move on to next interface. */ + } + } + } + return VINF_SUCCESS; +} + + +/** + * Initializes Endpoints for the given Interface & Alternate setting. + * + * @param pState The USB device instance. + * @param bIf The Interface. + * @param bAlt The Alterate. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisInitEpsForIfAlt(vboxusb_state_t *pState, uint8_t bIf, uint8_t bAlt) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: pState=%p bIf=%d uAlt=%d\n", pState, bIf, bAlt)); + + /* Doesn't hurt to be paranoid */ + uint_t uCfgIndex = usb_get_current_cfgidx(pState->pDip); + if (uCfgIndex >= pState->pDevDesc->dev_n_cfg) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: Invalid current config index %d\n", uCfgIndex)); + return VERR_OUT_OF_RANGE; + } + + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[uCfgIndex]; + for (uchar_t uIf = 0; uIf < pConfig->cfg_n_if; uIf++) + { + usb_if_data_t *pInterface = &pConfig->cfg_if[uIf]; + const uint_t cAlts = pInterface->if_n_alt; + for (uchar_t uAlt = 0; uAlt < cAlts; uAlt++) + { + usb_alt_if_data_t *pAlt = &pInterface->if_alt[uAlt]; + if ( pAlt->altif_descr.bInterfaceNumber == bIf + && pAlt->altif_descr.bAlternateSetting == bAlt) + { + const uint_t cEps = pAlt->altif_n_ep; + for (uchar_t uEp = 0; uEp < cEps; uEp++) + { + uint8_t *pbEpData = (uint8_t *)&pAlt->altif_ep[0]; + usb_ep_data_t *pEpData = (usb_ep_data_t *)(pbEpData + uEp * g_cbUsbEpData); + int rc = vboxUsbSolarisInitEp(pState, pEpData); + if (RT_FAILURE(rc)) + { + uint8_t bCfgValue = pConfig->cfg_descr.bConfigurationValue; + LogRel((DEVICE_NAME ": vboxUsbSolarisInitEpsForIfAlt: Failed to init endpoint! " + "bCfgValue=%u bIf=%#x bAlt=%#x\n", bCfgValue, bIf, bAlt)); + return rc; + } + } + return VINF_SUCCESS; + } + } + } + return VERR_NOT_FOUND; +} + + +/** + * Destroys all Endpoints. + * + * @param pState The USB device instance. + * + * @remarks Requires the state mutex to be held. + * Call only from Detach() or similar as callbacks + */ +LOCAL void vboxUsbSolarisDestroyAllEps(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyAllEps: pState=%p\n", pState)); + + Assert(mutex_owned(&pState->Mtx)); + for (unsigned i = 0; i < VBOXUSB_MAX_ENDPOINTS; i++) + { + vboxusb_ep_t *pEp = &pState->aEps[i]; + if (pEp->fInitialized) + vboxUsbSolarisDestroyEp(pState, pEp); + } +} + + +/** + * Destroys an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * + * @remarks Requires the state mutex to be held. + */ +LOCAL void vboxUsbSolarisDestroyEp(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDestroyEp: pState=%p pEp=%p\n", pState, pEp)); + + Assert(pEp->fInitialized); + Assert(mutex_owned(&pState->Mtx)); + vboxusb_urb_t *pUrb = list_remove_head(&pEp->hIsocInUrbs); + while (pUrb) + { + if (pUrb->pMsg) + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + pUrb = list_remove_head(&pEp->hIsocInUrbs); + } + pEp->cIsocInUrbs = 0; + list_destroy(&pEp->hIsocInUrbs); + + vboxusb_isoc_req_t *pIsocReq = list_remove_head(&pEp->hIsocInLandedReqs); + while (pIsocReq) + { + kmem_free(pIsocReq, sizeof(vboxusb_isoc_req_t)); + pIsocReq = list_remove_head(&pEp->hIsocInLandedReqs); + } + pEp->cbIsocInLandedReqs = 0; + list_destroy(&pEp->hIsocInLandedReqs); + + pEp->fInitialized = false; +} + + +/** + * Closes all non-default pipes and drains the default pipe. + * + * @param pState The USB device instance. + * @param fDefault Whether to close the default control pipe. + * + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisCloseAllPipes(vboxusb_state_t *pState, bool fDefault) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: pState=%p\n", pState)); + + for (int i = 1; i < VBOXUSB_MAX_ENDPOINTS; i++) + { + vboxusb_ep_t *pEp = &pState->aEps[i]; + if ( pEp + && pEp->pPipe) + { + Log((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: Closing[%d]\n", i)); + vboxUsbSolarisClosePipe(pState, pEp); + } + } + + if (fDefault) + { + vboxusb_ep_t *pEp = &pState->aEps[0]; + if ( pEp + && pEp->pPipe) + { + vboxUsbSolarisClosePipe(pState, pEp); + Log((DEVICE_NAME ": vboxUsbSolarisCloseAllPipes: Closed default pipe\n")); + } + } +} + + +/** + * Opens the pipe associated with an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * @remarks Requires the device state mutex to be held. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisOpenPipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + Assert(mutex_owned(&pState->Mtx)); + + /* + * Make sure the Endpoint isn't open already. + */ + if (pEp->pPipe) + return VINF_SUCCESS; + + /* + * Default Endpoint; already opened just copy the pipe handle. + */ + if ((pEp->EpDesc.bEndpointAddress & USB_EP_NUM_MASK) == 0) + { + pEp->pPipe = pState->pDevDesc->dev_default_ph; + Log((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Default pipe opened\n")); + return VINF_SUCCESS; + } + + /* + * Open the non-default pipe for the Endpoint. + */ + mutex_exit(&pState->Mtx); + int rc = usb_pipe_open(pState->pDip, &pEp->EpDesc, &pEp->PipePolicy, USB_FLAGS_NOSLEEP, &pEp->pPipe); + mutex_enter(&pState->Mtx); + if (rc == USB_SUCCESS) + { + LogFunc((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Opened pipe, pState=%p pEp=%p\n", pState, pEp)); + usb_pipe_set_private(pEp->pPipe, (usb_opaque_t)pEp); + + /* + * Determine input buffer size for Isoc. IN transfers. + */ + if ( VBOXUSB_XFER_TYPE(pEp) == VUSBXFERTYPE_ISOC + && VBOXUSB_XFER_DIR(pEp) == VUSB_DIR_TO_HOST) + { + /* + * wMaxPacketSize bits 10..0 specifies maximum packet size which can hold 1024 bytes. + * If bits 12..11 is non-zero, cbMax will be more than 1024 and thus the Endpoint is a + * high-bandwidth Endpoint. + */ + uint16_t cbMax = VBOXUSB_PKT_SIZE(pEp->EpDesc.wMaxPacketSize); + if (cbMax <= 1024) + { + /* Buffer 1 second for highspeed and 8 seconds for fullspeed Endpoints. */ + pEp->cbMaxIsocData = 1000 * cbMax * 8; + } + else + { + /* Buffer about 400 milliseconds of data for highspeed high-bandwidth endpoints. */ + pEp->cbMaxIsocData = 400 * cbMax * 8; + } + Log((DEVICE_NAME ": vboxUsbSolarisOpenPipe: bEndpoint=%#x cbMaxIsocData=%u\n", pEp->EpDesc.bEndpointAddress, + pEp->cbMaxIsocData)); + } + + rc = VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisOpenPipe: Failed! rc=%d pState=%p pEp=%p\n", rc, pState, pEp)); + rc = VERR_BAD_PIPE; + } + + return rc; +} + + +/** + * Closes the pipe associated with an Endpoint. + * + * @param pState The USB device instance. + * @param pEp The Endpoint. + * + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisClosePipe(vboxusb_state_t *pState, vboxusb_ep_t *pEp) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisClosePipe: pState=%p pEp=%p\n", pState, pEp)); + AssertPtr(pEp); + + if (pEp->pPipe) + { + /* + * Default pipe: allow completion of pending requests. + */ + if (pEp->pPipe == pState->pDevDesc->dev_default_ph) + { + mutex_exit(&pState->Mtx); + usb_pipe_drain_reqs(pState->pDip, pEp->pPipe, 0, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback arg. */); + mutex_enter(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Closed default pipe\n")); + pState->fDefaultPipeOpen = false; + } + else + { + /* + * Stop Isoc. IN polling if required. + */ + if (pEp->fIsocPolling) + { + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pEp->pPipe, USB_FLAGS_NOSLEEP); + mutex_enter(&pState->Mtx); + } + + /* + * Non-default pipe: close it. + */ + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Pipe bmAttributes=%#x bEndpoint=%#x\n", pEp->EpDesc.bmAttributes, + pEp->EpDesc.bEndpointAddress)); + mutex_exit(&pState->Mtx); + usb_pipe_close(pState->pDip, pEp->pPipe, USB_FLAGS_SLEEP, NULL /* callback */, NULL /* callback arg. */); + mutex_enter(&pState->Mtx); + } + + /* + * Free the Endpoint data message block and reset pipe handle. + */ + pEp->pPipe = NULL; + + Log((DEVICE_NAME ": vboxUsbSolarisClosePipe: Success, bEndpoint=%#x\n", pEp->EpDesc.bEndpointAddress)); + } + + Assert(pEp->pPipe == NULL); +} + + +/** + * Finds the Configuration index for the passed in Configuration value. + * + * @param pState The USB device instance. + * @param bConfig The Configuration. + * + * @returns The configuration index if found, otherwise -1. + */ +LOCAL int vboxUsbSolarisGetConfigIndex(vboxusb_state_t *pState, uint_t bConfig) +{ + for (int CfgIndex = 0; CfgIndex < pState->pDevDesc->dev_n_cfg; CfgIndex++) + { + usb_cfg_data_t *pConfig = &pState->pDevDesc->dev_cfg[CfgIndex]; + if (pConfig->cfg_descr.bConfigurationValue == bConfig) + return CfgIndex; + } + + return -1; +} + + +/** + * Allocates and initializes an Isoc. In URB from the ring-3 equivalent. + * + * @param pState The USB device instance. + * @param pUrbReq Opaque pointer to the complete request. + * + * @returns The allocated Isoc. In URB to be used. + */ +LOCAL vboxusb_urb_t *vboxUsbSolarisGetIsocInUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq) +{ + /* + * Isoc. In URBs are not queued into the Inflight list like every other URBs. + * For now we allocate each URB which gets queued into the respective Endpoint during Xfer. + */ + vboxusb_urb_t *pUrb = RTMemAllocZ(sizeof(vboxusb_urb_t)); + if (RT_LIKELY(pUrb)) + { + pUrb->enmState = VBOXUSB_URB_STATE_INFLIGHT; + pUrb->pState = pState; + + if (RT_LIKELY(pUrbReq)) + { + pUrb->pvUrbR3 = pUrbReq->pvUrbR3; + pUrb->bEndpoint = pUrbReq->bEndpoint; + pUrb->enmType = pUrbReq->enmType; + pUrb->enmDir = pUrbReq->enmDir; + pUrb->enmStatus = pUrbReq->enmStatus; + pUrb->cbDataR3 = pUrbReq->cbData; + pUrb->pvDataR3 = (RTR3PTR)pUrbReq->pvData; + pUrb->cIsocPkts = pUrbReq->cIsocPkts; + + for (unsigned i = 0; i < pUrbReq->cIsocPkts; i++) + pUrb->aIsocPkts[i].cbPkt = pUrbReq->aIsocPkts[i].cbPkt; + + pUrb->pMsg = NULL; + } + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisGetIsocInUrb: Failed to alloc %d bytes\n", sizeof(vboxusb_urb_t))); + return pUrb; +} + + +/** + * Queues a URB reusing previously allocated URBs as required. + * + * @param pState The USB device instance. + * @param pUrbReq Opaque pointer to the complete request. + * @param pMsg Pointer to the allocated request data. + * + * @returns The allocated URB to be used, or NULL upon failure. + */ +LOCAL vboxusb_urb_t *vboxUsbSolarisQueueUrb(vboxusb_state_t *pState, PVBOXUSBREQ_URB pUrbReq, mblk_t *pMsg) +{ + Assert(pUrbReq); + LogFunc((DEVICE_NAME ": vboxUsbSolarisQueueUrb: pState=%p pUrbReq=%p\n", pState, pUrbReq)); + + mutex_enter(&pState->Mtx); + + /* + * Grab a URB from the free list. + */ + vboxusb_urb_t *pUrb = list_remove_head(&pState->hFreeUrbs); + if (pUrb) + { + Assert(pUrb->enmState == VBOXUSB_URB_STATE_FREE); + Assert(!pUrb->pMsg); + Assert(pState->cFreeUrbs > 0); + --pState->cFreeUrbs; + } + else + { + /* + * We can't discard "old" URBs. For instance, INTR IN URBs that don't complete as + * they don't have a timeout can essentially take arbitrarily long to complete depending + * on the device and it's not safe to discard them in case they -do- complete. However, + * we also have to reasonably assume a device doesn't have too many pending URBs always. + * + * Thus we just use a large queue and simply refuse further transfers. This is not + * a situation which normally ever happens as usually there are at most than 4 or 5 URBs + * in-flight until we reap them. + */ + uint32_t const cTotalUrbs = pState->cInflightUrbs + pState->cFreeUrbs + pState->cLandedUrbs; + if (cTotalUrbs >= VBOXUSB_URB_QUEUE_SIZE) + { + mutex_exit(&pState->Mtx); + LogRelMax(5, (DEVICE_NAME ": vboxUsbSolarisQueueUrb: Max queue size %u reached, refusing further transfers", + cTotalUrbs)); + return NULL; + } + + /* + * Allocate a new URB as we have no free URBs. + */ + mutex_exit(&pState->Mtx); + pUrb = RTMemAllocZ(sizeof(vboxusb_urb_t)); + if (RT_UNLIKELY(!pUrb)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisQueueUrb: Failed to alloc %d bytes\n", sizeof(vboxusb_urb_t))); + return NULL; + } + mutex_enter(&pState->Mtx); + } + + /* + * Add the URB to the inflight list. + */ + list_insert_tail(&pState->hInflightUrbs, pUrb); + ++pState->cInflightUrbs; + + Assert(!pUrb->pMsg); + pUrb->pMsg = pMsg; + pUrb->pState = pState; + pUrb->enmState = VBOXUSB_URB_STATE_INFLIGHT; + pUrb->pvUrbR3 = pUrbReq->pvUrbR3; + pUrb->bEndpoint = pUrbReq->bEndpoint; + pUrb->enmType = pUrbReq->enmType; + pUrb->enmDir = pUrbReq->enmDir; + pUrb->enmStatus = pUrbReq->enmStatus; + pUrb->fShortOk = pUrbReq->fShortOk; + pUrb->pvDataR3 = (RTR3PTR)pUrbReq->pvData; + pUrb->cbDataR3 = pUrbReq->cbData; + pUrb->cIsocPkts = pUrbReq->cIsocPkts; + if (pUrbReq->enmType == VUSBXFERTYPE_ISOC) + { + for (unsigned i = 0; i < pUrbReq->cIsocPkts; i++) + pUrb->aIsocPkts[i].cbPkt = pUrbReq->aIsocPkts[i].cbPkt; + } + + mutex_exit(&pState->Mtx); + return pUrb; +} + + +/** + * Dequeues a completed URB into the landed list and informs user-land. + * + * @param pUrb The URB to move. + * @param URBStatus The Solaris URB completion code. + * + * @remarks All pipes could be closed at this point (e.g. Device disconnected during inflight URBs) + */ +LOCAL void vboxUsbSolarisDeQueueUrb(vboxusb_urb_t *pUrb, int URBStatus) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeQueue: pUrb=%p\n", pUrb)); + AssertPtrReturnVoid(pUrb); + + pUrb->enmStatus = vboxUsbSolarisGetUrbStatus(URBStatus); + if (pUrb->enmStatus != VUSBSTATUS_OK) + Log((DEVICE_NAME ": vboxUsbSolarisDeQueueUrb: URB failed! URBStatus=%d bEndpoint=%#x\n", URBStatus, pUrb->bEndpoint)); + + vboxusb_state_t *pState = pUrb->pState; + if (RT_LIKELY(pState)) + { + mutex_enter(&pState->Mtx); + pUrb->enmState = VBOXUSB_URB_STATE_LANDED; + + /* + * Remove it from the inflight list & move it to the landed list. + */ + list_remove(&pState->hInflightUrbs, pUrb); + Assert(pState->cInflightUrbs > 0); + --pState->cInflightUrbs; + + list_insert_tail(&pState->hLandedUrbs, pUrb); + ++pState->cLandedUrbs; + + vboxUsbSolarisNotifyComplete(pUrb->pState); + mutex_exit(&pState->Mtx); + return; + } + + /* Well, let's at least not leak memory... */ + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + pUrb->enmStatus = VUSBSTATUS_INVALID; + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeQueue: State Gone\n")); +} + + +/** + * Concatenates a chain message block into a single message block if possible. + * + * @param pUrb The URB to move. + */ +LOCAL void vboxUsbSolarisConcatMsg(vboxusb_urb_t *pUrb) +{ + /* + * Concatenate the whole message rather than doing a chained copy while reaping. + */ + if ( pUrb->pMsg + && pUrb->pMsg->b_cont) + { + mblk_t *pFullMsg = msgpullup(pUrb->pMsg, -1 /* all data */); + if (RT_LIKELY(pFullMsg)) + { + freemsg(pUrb->pMsg); + pUrb->pMsg = pFullMsg; + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisConcatMsg: Failed. Expect glitches due to truncated data!\n")); + } +} + + +/** + * Wakes up a user process signalling URB completion. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisNotifyComplete(vboxusb_state_t *pState) +{ + if (pState->fPollPending) + { + pollhead_t *pPollHead = &pState->PollHead; + pState->fPollPending = false; + mutex_exit(&pState->Mtx); + pollwakeup(pPollHead, POLLIN); + mutex_enter(&pState->Mtx); + } +} + + +/** + * Wakes up a user process signalling a device unplug events. + * + * @param pState The USB device instance. + * @remarks Requires the device state mutex to be held. + */ +LOCAL void vboxUsbSolarisNotifyUnplug(vboxusb_state_t *pState) +{ + if (pState->fPollPending) + { + pollhead_t *pPollHead = &pState->PollHead; + pState->fPollPending = false; + mutex_exit(&pState->Mtx); + pollwakeup(pPollHead, POLLHUP); + mutex_enter(&pState->Mtx); + } +} + + +/** + * Performs a Control Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisCtrlXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + AssertPtrReturn(pUrb->pMsg, VERR_INVALID_PARAMETER); + const size_t cbData = pUrb->cbDataR3 > VBOXUSB_CTRL_XFER_SIZE ? pUrb->cbDataR3 - VBOXUSB_CTRL_XFER_SIZE : 0; + + /* + * Allocate a wrapper request. + */ + usb_ctrl_req_t *pReq = usb_alloc_ctrl_req(pState->pDip, cbData, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + uchar_t *pSetupData = pUrb->pMsg->b_rptr; + + /* + * Initialize the Ctrl Xfer Header. + */ + pReq->ctrl_bmRequestType = pSetupData[0]; + pReq->ctrl_bRequest = pSetupData[1]; + pReq->ctrl_wValue = (pSetupData[3] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[2]; + pReq->ctrl_wIndex = (pSetupData[5] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[4]; + pReq->ctrl_wLength = (pSetupData[7] << VBOXUSB_CTRL_XFER_SIZE) | pSetupData[6]; + + if ( pUrb->enmDir == VUSBDIRECTION_OUT + && cbData) + { + bcopy(pSetupData + VBOXUSB_CTRL_XFER_SIZE, pReq->ctrl_data->b_wptr, cbData); + pReq->ctrl_data->b_wptr += cbData; + } + + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + + /* + * Initialize callbacks and timeouts. + */ + pReq->ctrl_cb = vboxUsbSolarisCtrlXferCompleted; + pReq->ctrl_exc_cb = vboxUsbSolarisCtrlXferCompleted; + pReq->ctrl_timeout = VBOXUSB_CTRL_XFER_TIMEOUT; + pReq->ctrl_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_SHORT_XFER_OK; + pReq->ctrl_client_private = (usb_opaque_t)pUrb; + + /* + * Submit the request. + */ + int rc = usb_pipe_ctrl_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: Request failed! bEndpoint=%#x rc=%d\n", pUrb->bEndpoint, rc)); + + usb_free_ctrl_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXfer: Failed to alloc request for %u bytes\n", cbData)); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Control Xfers. + * + * @param pPipe The Ctrl pipe handle. + * @param pReq The Ctrl request. + */ +LOCAL void vboxUsbSolarisCtrlXferCompleted(usb_pipe_handle_t pPipe, usb_ctrl_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + Assert(pReq); + Assert(!(pReq->ctrl_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->ctrl_client_private; + if (RT_LIKELY(pUrb)) + { + /* + * Funky stuff: We need to reconstruct the header for control transfers. + * Let us chain along the data and concatenate the entire message. + */ + mblk_t *pSetupMsg = allocb(sizeof(VUSBSETUP), BPRI_MED); + if (RT_LIKELY(pSetupMsg)) + { + VUSBSETUP SetupData; + SetupData.bmRequestType = pReq->ctrl_bmRequestType; + SetupData.bRequest = pReq->ctrl_bRequest; + SetupData.wValue = pReq->ctrl_wValue; + SetupData.wIndex = pReq->ctrl_wIndex; + SetupData.wLength = pReq->ctrl_wLength; + + bcopy(&SetupData, pSetupMsg->b_wptr, sizeof(VUSBSETUP)); + pSetupMsg->b_wptr += sizeof(VUSBSETUP); + + /* + * Should be safe to update pMsg here without the state mutex as typically nobody else + * touches this URB in the inflight list. + * + * The reason we choose to use vboxUsbSolarisConcatMsg here is that we don't assume the + * message returned by Solaris is one contiguous chunk in 'pMsg->b_rptr'. + */ + Assert(!pUrb->pMsg); + pUrb->pMsg = pSetupMsg; + pUrb->pMsg->b_cont = pReq->ctrl_data; + pReq->ctrl_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: Failed to alloc %u bytes for header\n", sizeof(VUSBSETUP))); + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->ctrl_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisCtrlXferCompleted: Extreme error! missing private data\n")); + + usb_free_ctrl_req(pReq); +} + + +/** + * Performs a Bulk Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisBulkXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisBulkXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + /* + * Allocate a wrapper request. + */ + size_t const cbAlloc = pUrb->enmDir == VUSBDIRECTION_IN ? pUrb->cbDataR3 : 0; + usb_bulk_req_t *pReq = usb_alloc_bulk_req(pState->pDip, cbAlloc, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Bulk Xfer, callbacks and timeouts. + */ + usb_req_attrs_t fAttributes = USB_ATTRS_AUTOCLEARING; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->bulk_data = pUrb->pMsg; + pUrb->pMsg = NULL; + } + else if ( pUrb->enmDir == VUSBDIRECTION_IN + && pUrb->fShortOk) + { + fAttributes |= USB_ATTRS_SHORT_XFER_OK; + } + + Assert(!pUrb->pMsg); + pReq->bulk_len = pUrb->cbDataR3; + pReq->bulk_cb = vboxUsbSolarisBulkXferCompleted; + pReq->bulk_exc_cb = vboxUsbSolarisBulkXferCompleted; + pReq->bulk_timeout = 0; + pReq->bulk_attributes = fAttributes; + pReq->bulk_client_private = (usb_opaque_t)pUrb; + + /* Don't obtain state lock here, we're just reading unchanging data... */ + if (RT_UNLIKELY(pUrb->cbDataR3 > pState->cbMaxBulkXfer)) + { + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Requesting %d bytes when only %d bytes supported by device\n", + pUrb->cbDataR3, pState->cbMaxBulkXfer)); + } + + /* + * Submit the request. + */ + int rc = usb_pipe_bulk_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Request failed! Ep=%#x rc=%d cbData=%u\n", pUrb->bEndpoint, rc, + pReq->bulk_len)); + + usb_free_bulk_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXfer: Failed to alloc bulk request\n")); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Bulk Xfers. + * + * @param pPipe The Bulk pipe handle. + * @param pReq The Bulk request. + */ +LOCAL void vboxUsbSolarisBulkXferCompleted(usb_pipe_handle_t pPipe, usb_bulk_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + Assert(pReq); + Assert(!(pReq->bulk_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_LIKELY(pEp)) + { + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->bulk_client_private; + if (RT_LIKELY(pUrb)) + { + Assert(!pUrb->pMsg); + if ( pUrb->enmDir == VUSBDIRECTION_IN + && pReq->bulk_data) + { + pUrb->pMsg = pReq->bulk_data; + pReq->bulk_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + + /* + * Update the URB and move to tail for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->bulk_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: Extreme error! private request data missing!\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisBulkXferCompleted: Pipe Gone!\n")); + + usb_free_bulk_req(pReq); +} + + +/** + * Performs an Interrupt Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisIntrXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIntrXfer: pState=%p pEp=%p pUrb=%p enmDir=%d cbData=%d\n", pState, pEp, pUrb, + pUrb->enmDir, pUrb->cbDataR3)); + + usb_intr_req_t *pReq = usb_alloc_intr_req(pState->pDip, 0 /* length */, USB_FLAGS_SLEEP); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Intr Xfer, callbacks & timeouts. + */ + usb_req_attrs_t fAttributes = USB_ATTRS_AUTOCLEARING; + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->intr_data = pUrb->pMsg; + pUrb->pMsg = NULL; + } + else + { + Assert(pUrb->enmDir == VUSBDIRECTION_IN); + fAttributes |= USB_ATTRS_ONE_XFER; + if (pUrb->fShortOk) + fAttributes |= USB_ATTRS_SHORT_XFER_OK; + } + + Assert(!pUrb->pMsg); + pReq->intr_len = pUrb->cbDataR3; /* Not pEp->EpDesc.wMaxPacketSize */ + pReq->intr_cb = vboxUsbSolarisIntrXferCompleted; + pReq->intr_exc_cb = vboxUsbSolarisIntrXferCompleted; + pReq->intr_timeout = 0; + pReq->intr_attributes = fAttributes; + pReq->intr_client_private = (usb_opaque_t)pUrb; + + /* + * Submit the request. + */ + int rc = usb_pipe_intr_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + return VINF_SUCCESS; + + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXfer: usb_pipe_intr_xfer failed! rc=%d bEndpoint=%#x\n", rc, pUrb->bEndpoint)); + + usb_free_intr_req(pReq); + return VERR_PIPE_IO_ERROR; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXfer: Failed to alloc intr request\n")); + return VERR_NO_MEMORY; +} + + +/** + * Completion/Exception callback for Intr Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + */ +LOCAL void vboxUsbSolarisIntrXferCompleted(usb_pipe_handle_t pPipe, usb_intr_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIntrXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + Assert(pReq); + Assert(!(pReq->intr_cb_flags & USB_CB_INTR_CONTEXT)); + + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->intr_client_private; + if (RT_LIKELY(pUrb)) + { + if ( pUrb->enmDir == VUSBDIRECTION_IN + && pReq->intr_data) + { + pUrb->pMsg = pReq->intr_data; + pReq->intr_data = NULL; + vboxUsbSolarisConcatMsg(pUrb); + } + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->intr_completion_reason); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIntrXferCompleted: Extreme error! private request data missing\n")); + + usb_free_intr_req(pReq); +} + + +/** + * Performs an Isochronous Xfer. + * + * @param pState The USB device instance. + * @param pEp The Endpoint for the Xfer. + * @param pUrb The VBox USB URB. + * + * @returns VBox status code. + * @remarks Any errors, the caller should free pUrb->pMsg. + */ +LOCAL int vboxUsbSolarisIsocXfer(vboxusb_state_t *pState, vboxusb_ep_t *pEp, vboxusb_urb_t *pUrb) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocXfer: pState=%p pEp=%p pUrb=%p\n", pState, pEp, pUrb)); */ + + /* + * For Isoc. IN transfers we perform one request and USBA polls the device continuously + * and supplies our Xfer callback with input data. We cannot perform one-shot Isoc. In transfers. + */ + size_t cbData = (pUrb->enmDir == VUSBDIRECTION_IN ? pUrb->cIsocPkts * pUrb->aIsocPkts[0].cbPkt : 0); + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Isoc. IN - Queueing\n")); + + mutex_enter(&pState->Mtx); + if (pEp->fIsocPolling) + { + /* + * Queue a maximum of cbMaxIsocData bytes, else fail. + */ + if (pEp->cbIsocInLandedReqs + cbData > pEp->cbMaxIsocData) + { + mutex_exit(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Max Isoc. data %d bytes queued\n", pEp->cbMaxIsocData)); + return VERR_TOO_MUCH_DATA; + } + + list_insert_tail(&pEp->hIsocInUrbs, pUrb); + ++pEp->cIsocInUrbs; + + mutex_exit(&pState->Mtx); + return VINF_SUCCESS; + } + mutex_exit(&pState->Mtx); + } + + int rc = VINF_SUCCESS; + usb_isoc_req_t *pReq = usb_alloc_isoc_req(pState->pDip, pUrb->cIsocPkts, cbData, USB_FLAGS_NOSLEEP); + Log((DEVICE_NAME ": vboxUsbSolarisIsocXfer: enmDir=%#x cIsocPkts=%d aIsocPkts[0]=%d cbDataR3=%d\n", pUrb->enmDir, + pUrb->cIsocPkts, pUrb->aIsocPkts[0].cbPkt, pUrb->cbDataR3)); + if (RT_LIKELY(pReq)) + { + /* + * Initialize Isoc Xfer, callbacks & timeouts. + */ + for (unsigned i = 0; i < pUrb->cIsocPkts; i++) + pReq->isoc_pkt_descr[i].isoc_pkt_length = pUrb->aIsocPkts[i].cbPkt; + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + pReq->isoc_data = pUrb->pMsg; + pReq->isoc_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_ISOC_XFER_ASAP; + pReq->isoc_cb = vboxUsbSolarisIsocOutXferCompleted; + pReq->isoc_exc_cb = vboxUsbSolarisIsocOutXferCompleted; + pReq->isoc_client_private = (usb_opaque_t)pUrb; + } + else + { + pReq->isoc_attributes = USB_ATTRS_AUTOCLEARING | USB_ATTRS_ISOC_XFER_ASAP | USB_ATTRS_SHORT_XFER_OK; + pReq->isoc_cb = vboxUsbSolarisIsocInXferCompleted; + pReq->isoc_exc_cb = vboxUsbSolarisIsocInXferError; + pReq->isoc_client_private = (usb_opaque_t)pState; + } + pReq->isoc_pkts_count = pUrb->cIsocPkts; + pReq->isoc_pkts_length = 0; /* auto compute */ + + /* + * Submit the request. + */ + rc = usb_pipe_isoc_xfer(pEp->pPipe, pReq, USB_FLAGS_NOSLEEP); + if (RT_LIKELY(rc == USB_SUCCESS)) + { + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + /* + * Add the first Isoc. IN URB to the queue as well. + */ + mutex_enter(&pState->Mtx); + list_insert_tail(&pEp->hIsocInUrbs, pUrb); + ++pEp->cIsocInUrbs; + pEp->fIsocPolling = true; + mutex_exit(&pState->Mtx); + } + + return VINF_SUCCESS; + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocXfer: usb_pipe_isoc_xfer failed! rc=%d\n", rc)); + rc = VERR_PIPE_IO_ERROR; + + if (pUrb->enmDir == VUSBDIRECTION_IN) + { + mutex_enter(&pState->Mtx); + vboxusb_urb_t *pIsocFailedUrb = list_remove_tail(&pEp->hIsocInUrbs); + if (pIsocFailedUrb) + { + RTMemFree(pIsocFailedUrb); + --pEp->cIsocInUrbs; + } + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + } + } + + if (pUrb->enmDir == VUSBDIRECTION_OUT) + { + freemsg(pUrb->pMsg); + pUrb->pMsg = NULL; + } + + usb_free_isoc_req(pReq); + } + else + { + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocXfer: Failed to alloc isoc req for %d packets\n", pUrb->cIsocPkts)); + rc = VERR_NO_MEMORY; + } + + return rc; +} + + +/** + * Completion/Exception callback for Isoc IN Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocInXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + /* LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); */ + + vboxusb_state_t *pState = (vboxusb_state_t *)pReq->isoc_client_private; + if (RT_LIKELY(pState)) + { + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if ( pEp + && pEp->pPipe) + { +#if 0 + /* + * Stop polling if all packets failed. + */ + if (pReq->isoc_error_count == pReq->isoc_pkts_count) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Stopping polling! Too many errors\n")); + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pPipe, USB_FLAGS_NOSLEEP); + mutex_enter(&pState->Mtx); + pEp->fIsocPolling = false; + } +#endif + + /** @todo Query and verify this at runtime. */ + AssertCompile(sizeof(VUSBISOC_PKT_DESC) == sizeof(usb_isoc_pkt_descr_t)); + if (RT_LIKELY(pReq->isoc_data)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: cIsocInUrbs=%d cbIsocInLandedReqs=%d\n", pEp->cIsocInUrbs, + pEp->cbIsocInLandedReqs)); + + mutex_enter(&pState->Mtx); + + /* + * If there are waiting URBs, satisfy the oldest one. + */ + if ( pEp->cIsocInUrbs > 0 + && pEp->cbIsocInLandedReqs == 0) + { + vboxusb_urb_t *pUrb = list_remove_head(&pEp->hIsocInUrbs); + if (RT_LIKELY(pUrb)) + { + --pEp->cIsocInUrbs; + mutex_exit(&pState->Mtx); + + for (unsigned i = 0; i < pReq->isoc_pkts_count; i++) + { + pUrb->aIsocPkts[i].cbActPkt = pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].enmStatus = vboxUsbSolarisGetUrbStatus(pReq->isoc_pkt_descr[i].isoc_pkt_status); + } + + pUrb->pMsg = pReq->isoc_data; + pReq->isoc_data = NULL; + + /* + * Move to landed list + */ + mutex_enter(&pState->Mtx); + list_insert_tail(&pState->hLandedUrbs, pUrb); + ++pState->cLandedUrbs; + vboxUsbSolarisNotifyComplete(pState); + } + else + { + /* Huh!? cIsocInUrbs is wrong then! Should never happen unless we decide to decrement cIsocInUrbs in + Reap time */ + pEp->cIsocInUrbs = 0; + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Extreme error! Isoc. counter borked!\n")); + } + + mutex_exit(&pState->Mtx); + usb_free_isoc_req(pReq); + return; + } + + mutex_exit(&pState->Mtx); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Data missing\n")); + } + else + LogRel((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: Pipe Gone\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferCompleted: State Gone\n")); + + usb_free_isoc_req(pReq); +} + + +/** + * Exception callback for Isoc IN Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocInXferError(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: pPipe=%p pReq=%p\n", pPipe, pReq)); + + vboxusb_state_t *pState = (vboxusb_state_t *)pReq->isoc_client_private; + if (RT_UNLIKELY(!pState)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: State Gone\n")); + usb_free_isoc_req(pReq); + return; + } + + mutex_enter(&pState->Mtx); + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_UNLIKELY(!pEp)) + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Pipe Gone\n")); + mutex_exit(&pState->Mtx); + usb_free_isoc_req(pReq); + return; + } + + switch(pReq->isoc_completion_reason) + { + case USB_CR_NO_RESOURCES: + { + /* + * Resubmit the request in case the original request did not complete due to + * immediately unavailable requests + */ + mutex_exit(&pState->Mtx); + usb_pipe_isoc_xfer(pPipe, pReq, USB_FLAGS_NOSLEEP); + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Resubmitted Isoc. IN request due to unavailable resources\n")); + return; + } + + case USB_CR_PIPE_CLOSING: + case USB_CR_STOPPED_POLLING: + case USB_CR_PIPE_RESET: + { + pEp->fIsocPolling = false; + usb_free_isoc_req(pReq); + break; + } + + default: + { + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Stopping Isoc. IN polling due to rc=%d\n", + pReq->isoc_completion_reason)); + pEp->fIsocPolling = false; + mutex_exit(&pState->Mtx); + usb_pipe_stop_isoc_polling(pPipe, USB_FLAGS_NOSLEEP); + usb_free_isoc_req(pReq); + mutex_enter(&pState->Mtx); + break; + } + } + + /* + * Dequeue i.e. delete the last queued Isoc In. URB. as failed. + */ + vboxusb_urb_t *pUrb = list_remove_tail(&pEp->hIsocInUrbs); + if (pUrb) + { + --pEp->cIsocInUrbs; + Log((DEVICE_NAME ": vboxUsbSolarisIsocInXferError: Deleting last queued URB as it failed\n")); + freemsg(pUrb->pMsg); + RTMemFree(pUrb); + vboxUsbSolarisNotifyComplete(pState); + } + + mutex_exit(&pState->Mtx); +} + + +/** + * Completion/Exception callback for Isoc OUT Xfers. + * + * @param pPipe The Intr pipe handle. + * @param pReq The Intr request. + * @remarks Completion callback executes in interrupt context! + */ +LOCAL void vboxUsbSolarisIsocOutXferCompleted(usb_pipe_handle_t pPipe, usb_isoc_req_t *pReq) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: pPipe=%p pReq=%p\n", pPipe, pReq)); + + vboxusb_ep_t *pEp = (vboxusb_ep_t *)usb_pipe_get_private(pPipe); + if (RT_LIKELY(pEp)) + { + vboxusb_urb_t *pUrb = (vboxusb_urb_t *)pReq->isoc_client_private; + if (RT_LIKELY(pUrb)) + { + size_t cbActPkt = 0; + for (int i = 0; i < pReq->isoc_pkts_count; i++) + { + cbActPkt += pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].cbActPkt = pReq->isoc_pkt_descr[i].isoc_pkt_actual_length; + pUrb->aIsocPkts[i].enmStatus = vboxUsbSolarisGetUrbStatus(pReq->isoc_pkt_descr[i].isoc_pkt_status); + } + + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: cIsocPkts=%d cbData=%d cbActPkt=%d\n", pUrb->cIsocPkts, + pUrb->cbDataR3, cbActPkt)); + + if (pReq->isoc_completion_reason == USB_CR_OK) + { + if (RT_UNLIKELY(pUrb->pMsg != pReq->isoc_data)) /* Paranoia */ + { + freemsg(pUrb->pMsg); + pUrb->pMsg = pReq->isoc_data; + } + } + pReq->isoc_data = NULL; + + pUrb->cIsocPkts = pReq->isoc_pkts_count; + pUrb->cbDataR3 = cbActPkt; + + /* + * Update the URB and move to landed list for reaping. + */ + vboxUsbSolarisDeQueueUrb(pUrb, pReq->isoc_completion_reason); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: Missing private data!?! Dropping OUT pUrb\n")); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisIsocOutXferCompleted: Pipe Gone\n")); + + usb_free_isoc_req(pReq); +} + + +/** + * Callback when the device gets disconnected. + * + * @param pDip The module structure instance. + * + * @returns Solaris USB error code. + */ +LOCAL int vboxUsbSolarisDeviceDisconnected(dev_info_t *pDip) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceDisconnected: pDip=%p\n", pDip)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + + if (RT_LIKELY(pState)) + { + /* + * Serialize access: exclusive access to the state. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + pState->DevState = USB_DEV_DISCONNECTED; + + vboxUsbSolarisCloseAllPipes(pState, true /* ControlPipe */); + vboxUsbSolarisNotifyUnplug(pState); + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + return USB_SUCCESS; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceDisconnected: Failed to get device state!\n")); + return USB_FAILURE; +} + + +/** + * Callback when the device gets reconnected. + * + * @param pDip The module structure instance. + * + * @returns Solaris USB error code. + */ +LOCAL int vboxUsbSolarisDeviceReconnected(dev_info_t *pDip) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceReconnected: pDip=%p\n", pDip)); + + int instance = ddi_get_instance(pDip); + vboxusb_state_t *pState = ddi_get_soft_state(g_pVBoxUSBSolarisState, instance); + + if (RT_LIKELY(pState)) + { + vboxUsbSolarisDeviceRestore(pState); + return USB_SUCCESS; + } + + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceReconnected: Failed to get device state!\n")); + return USB_FAILURE; +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisDeviceRestore(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceRestore: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + /* + * Raise device power. + */ + vboxUsbSolarisPowerBusy(pState); + int rc = pm_raise_power(pState->pDip, 0 /* component */, USB_DEV_OS_FULL_PWR); + + /* + * Check if the same device is resumed/reconnected. + */ + rc = usb_check_same_device(pState->pDip, + NULL, /* log handle */ + USB_LOG_L2, /* log level */ + -1, /* log mask */ + USB_CHK_ALL, /* check level */ + NULL); /* device string */ + + if (rc != USB_SUCCESS) + { + mutex_enter(&pState->Mtx); + pState->DevState = USB_DEV_DISCONNECTED; + mutex_exit(&pState->Mtx); + + /* Do we need to inform userland here? */ + vboxUsbSolarisPowerIdle(pState); + Log((DEVICE_NAME ": vboxUsbSolarisDeviceRestore: Not the same device\n")); + return; + } + + /* + * Serialize access to not race with other PM functions. + */ + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + + mutex_enter(&pState->Mtx); + if (pState->DevState == USB_DEV_DISCONNECTED) + pState->DevState = USB_DEV_ONLINE; + else if (pState->DevState == USB_DEV_SUSPENDED) + pState->DevState = USB_DEV_ONLINE; + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + vboxUsbSolarisPowerIdle(pState); +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + * + * @returns VBox status code. + */ +LOCAL int vboxUsbSolarisDeviceSuspend(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: pState=%p\n", pState)); + + int rc = VERR_VUSB_DEVICE_IS_SUSPENDED; + mutex_enter(&pState->Mtx); + + switch (pState->DevState) + { + case USB_DEV_SUSPENDED: + { + LogRel((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: Invalid device state %d\n", pState->DevState)); + break; + } + + case USB_DEV_ONLINE: + case USB_DEV_DISCONNECTED: + case USB_DEV_PWRED_DOWN: + { + int PreviousState = pState->DevState; + pState->DevState = USB_DEV_DISCONNECTED; + + /** @todo this doesn't make sense when for e.g. an INTR IN URB with infinite + * timeout is pending on the device. Fix suspend logic later. */ + /* + * Drain pending URBs. + */ + for (int i = 0; i < VBOXUSB_DRAIN_TIME; i++) + { + if (pState->cInflightUrbs < 1) + break; + + mutex_exit(&pState->Mtx); + delay(drv_usectohz(100000)); + mutex_enter(&pState->Mtx); + } + + /* + * Deny suspend if we still have pending URBs. + */ + if (pState->cInflightUrbs > 0) + { + pState->DevState = PreviousState; + LogRel((DEVICE_NAME ": Cannot suspend %s %s (Ident=%s), %d inflight URBs\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent, pState->cInflightUrbs)); + + mutex_exit(&pState->Mtx); + return VERR_RESOURCE_BUSY; + } + + pState->cInflightUrbs = 0; + + /* + * Serialize access to not race with Open/Detach/Close and + * Close all pipes including the default pipe. + */ + mutex_exit(&pState->Mtx); + usb_serialize_access(pState->StateMulti, USB_WAIT, 0); + mutex_enter(&pState->Mtx); + + vboxUsbSolarisCloseAllPipes(pState, true /* default pipe */); + vboxUsbSolarisNotifyUnplug(pState); + + mutex_exit(&pState->Mtx); + usb_release_access(pState->StateMulti); + + LogRel((DEVICE_NAME ": Suspended %s %s (Ident=%s)\n", pState->szMfg, pState->szProduct, + pState->ClientInfo.szDeviceIdent)); + return VINF_SUCCESS; + } + } + + mutex_exit(&pState->Mtx); + Log((DEVICE_NAME ": vboxUsbSolarisDeviceSuspend: Returns %d\n", rc)); + return rc; +} + + +/** + * Restores device state after a reconnect or resume. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisDeviceResume(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisDeviceResume: pState=%p\n", pState)); + return vboxUsbSolarisDeviceRestore(pState); +} + + +/** + * Flags the PM component as busy so the system will not manage it's power. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisPowerBusy(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisPowerBusy: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + mutex_enter(&pState->Mtx); + if (pState->pPower) + { + pState->pPower->PowerBusy++; + mutex_exit(&pState->Mtx); + + int rc = pm_busy_component(pState->pDip, 0 /* component */); + if (rc != DDI_SUCCESS) + { + Log((DEVICE_NAME ": vboxUsbSolarisPowerBusy: Busy component failed! rc=%d\n", rc)); + mutex_enter(&pState->Mtx); + pState->pPower->PowerBusy--; + mutex_exit(&pState->Mtx); + } + } + else + mutex_exit(&pState->Mtx); +} + + +/** + * Flags the PM component as idle so its power managed by the system. + * + * @param pState The USB device instance. + */ +LOCAL void vboxUsbSolarisPowerIdle(vboxusb_state_t *pState) +{ + LogFunc((DEVICE_NAME ": vboxUsbSolarisPowerIdle: pState=%p\n", pState)); + AssertPtrReturnVoid(pState); + + if (pState->pPower) + { + int rc = pm_idle_component(pState->pDip, 0 /* component */); + if (rc == DDI_SUCCESS) + { + mutex_enter(&pState->Mtx); + Assert(pState->pPower->PowerBusy > 0); + pState->pPower->PowerBusy--; + mutex_exit(&pState->Mtx); + } + else + Log((DEVICE_NAME ": vboxUsbSolarisPowerIdle: Idle component failed! rc=%d\n", rc)); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c new file mode 100644 index 00000000..90663b8c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/VBoxUSBMon-solaris.c @@ -0,0 +1,1048 @@ +/* $Id: VBoxUSBMon-solaris.c $ */ +/** @file + * VirtualBox USB Monitor Driver, Solaris Hosts. + */ + +/* + * Copyright (C) 2008-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_USB_DRV +#include "VBoxUSBFilterMgr.h" +#include <VBox/usblib-solaris.h> +#include <VBox/version.h> +#include <VBox/log.h> +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <VBox/version.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/initterm.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/path.h> +#include <iprt/semaphore.h> +#include <iprt/string.h> + +#define USBDRV_MAJOR_VER 2 +#define USBDRV_MINOR_VER 0 +#include <sys/usb/usba.h> +#include <sys/conf.h> +#include <sys/modctl.h> +#include <sys/mutex.h> +#include <sys/stat.h> +#include <sys/ddi.h> +#include <sys/sunddi.h> +#include <sys/sunndi.h> +#include <sys/open.h> +#include <sys/cmn_err.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The module name. */ +#define DEVICE_NAME "vboxusbmon" +/** The module description as seen in 'modinfo'. */ +#define DEVICE_DESC_DRV "VirtualBox USBMon" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int VBoxUSBMonSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred); +static int VBoxUSBMonSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred); +static int VBoxUSBMonSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred); +static int VBoxUSBMonSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred); +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); +static int VBoxUSBMonSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd); +static int VBoxUSBMonSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd); + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** + * cb_ops: for drivers that support char/block entry points + */ +static struct cb_ops g_VBoxUSBMonSolarisCbOps = +{ + VBoxUSBMonSolarisOpen, + VBoxUSBMonSolarisClose, + nodev, /* b strategy */ + nodev, /* b dump */ + nodev, /* b print */ + VBoxUSBMonSolarisRead, + VBoxUSBMonSolarisWrite, + VBoxUSBMonSolarisIOCtl, + nodev, /* c devmap */ + nodev, /* c mmap */ + nodev, /* c segmap */ + nochpoll, /* c poll */ + ddi_prop_op, /* property ops */ + NULL, /* streamtab */ + D_NEW | D_MP, /* compat. flag */ + CB_REV /* revision */ +}; + +/** + * dev_ops: for driver device operations + */ +static struct dev_ops g_VBoxUSBMonSolarisDevOps = +{ + DEVO_REV, /* driver build revision */ + 0, /* ref count */ + VBoxUSBMonSolarisGetInfo, + nulldev, /* identify */ + nulldev, /* probe */ + VBoxUSBMonSolarisAttach, + VBoxUSBMonSolarisDetach, + nodev, /* reset */ + &g_VBoxUSBMonSolarisCbOps, + (struct bus_ops *)0, + nodev, /* power */ + ddi_quiesce_not_needed +}; + +/** + * modldrv: export driver specifics to the kernel + */ +static struct modldrv g_VBoxUSBMonSolarisModule = +{ + &mod_driverops, /* extern from kernel */ + DEVICE_DESC_DRV " " VBOX_VERSION_STRING "r" RT_XSTR(VBOX_SVN_REV), + &g_VBoxUSBMonSolarisDevOps +}; + +/** + * modlinkage: export install/remove/info to the kernel + */ +static struct modlinkage g_VBoxUSBMonSolarisModLinkage = +{ + MODREV_1, + &g_VBoxUSBMonSolarisModule, + NULL, +}; + +/** + * Client driver info. + */ +typedef struct vboxusbmon_client_t +{ + dev_info_t *pDip; /* Client device info. pointer */ + VBOXUSB_CLIENT_INFO Info; /* Client registration data. */ + struct vboxusbmon_client_t *pNext; /* Pointer to next client */ +} vboxusbmon_client_t; + +/** + * Device state info. + */ +typedef struct +{ + RTPROCESS Process; /* The process (id) of the session */ +} vboxusbmon_state_t; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Global Device handle we only support one instance. */ +static dev_info_t *g_pDip = NULL; +/** Global Mutex. */ +static kmutex_t g_VBoxUSBMonSolarisMtx; +/** Global list of client drivers registered with us. */ +vboxusbmon_client_t *g_pVBoxUSBMonSolarisClients = NULL; +/** 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); +static int vboxUSBMonSolarisResetDevice(char *pszDevicePath, bool fReattach); + + +/********************************************************************************************************************************* +* Monitor Global Hooks * +*********************************************************************************************************************************/ +static int vboxUSBMonSolarisClientInfo(vboxusbmon_state_t *pState, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo); +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip); +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; + + LogFunc((DEVICE_NAME ": _init\n")); + + g_pDip = NULL; + + /* + * Prevent module autounloading. + */ + modctl_t *pModCtl = mod_getctl(&g_VBoxUSBMonSolarisModLinkage); + if (pModCtl) + pModCtl->mod_loadflags |= MOD_NOAUTOUNLOAD; + else + LogRel((DEVICE_NAME ": _init: Failed to disable autounloading!\n")); + + /* + * Initialize IPRT R0 driver, which internally calls OS-specific r0 init. + */ + rc = RTR0Init(0); + if (RT_SUCCESS(rc)) + { + /* + * Initialize global mutex. + */ + mutex_init(&g_VBoxUSBMonSolarisMtx, NULL, MUTEX_DRIVER, NULL); + rc = VBoxUSBFilterInit(); + if (RT_SUCCESS(rc)) + { + rc = ddi_soft_state_init(&g_pVBoxUSBMonSolarisState, sizeof(vboxusbmon_state_t), 1); + if (!rc) + { + rc = mod_install(&g_VBoxUSBMonSolarisModLinkage); + if (!rc) + return rc; + + LogRel((DEVICE_NAME ": _init: mod_install failed! rc=%d\n", rc)); + ddi_soft_state_fini(&g_pVBoxUSBMonSolarisState); + } + else + LogRel((DEVICE_NAME ": _init: ddi_soft_state_init failed! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": _init: VBoxUSBFilterInit failed! rc=%d\n", rc)); + + mutex_destroy(&g_VBoxUSBMonSolarisMtx); + RTR0Term(); + } + else + LogRel((DEVICE_NAME ": _init: RTR0Init failed! rc=%d\n", rc)); + + return -1; +} + + +int _fini(void) +{ + int rc; + + LogFunc((DEVICE_NAME ": _fini\n")); + + rc = mod_remove(&g_VBoxUSBMonSolarisModLinkage); + if (!rc) + { + ddi_soft_state_fini(&g_pVBoxUSBMonSolarisState); + VBoxUSBFilterTerm(); + mutex_destroy(&g_VBoxUSBMonSolarisMtx); + + RTR0Term(); + } + return rc; +} + + +int _info(struct modinfo *pModInfo) +{ + LogFunc((DEVICE_NAME ": _info\n")); + + return mod_info(&g_VBoxUSBMonSolarisModLinkage, pModInfo); +} + + +/** + * 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. + */ +static int VBoxUSBMonSolarisAttach(dev_info_t *pDip, ddi_attach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisAttach: pDip=%p enmCmd=%d\n", pDip, enmCmd)); + switch (enmCmd) + { + case DDI_ATTACH: + { + if (RT_UNLIKELY(g_pDip)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: Global instance already initialized\n")); + return DDI_FAILURE; + } + + g_pDip = pDip; + int instance = ddi_get_instance(pDip); + int rc = ddi_create_priv_minor_node(pDip, DEVICE_NAME, S_IFCHR, instance, DDI_PSEUDO, 0, + "none", "none", 0660); + if (rc == DDI_SUCCESS) + { + rc = usb_register_dev_driver(g_pDip, VBoxUSBMonSolarisElectDriver); + if (rc == DDI_SUCCESS) + { + ddi_report_dev(pDip); + return DDI_SUCCESS; + } + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: Failed to register driver election callback! rc=%d\n", rc)); + } + else + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisAttach: ddi_create_minor_node failed! rc=%d\n", rc)); + 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. + */ +static int VBoxUSBMonSolarisDetach(dev_info_t *pDip, ddi_detach_cmd_t enmCmd) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisDetach\n")); + + switch (enmCmd) + { + case DDI_DETACH: + { + /* + * Free all registered clients' info. + */ + mutex_enter(&g_VBoxUSBMonSolarisMtx); + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + while (pCur) + { + vboxusbmon_client_t *pNext = pCur->pNext; + RTMemFree(pCur); + pCur = pNext; + } + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + usb_unregister_dev_driver(g_pDip); + + ddi_remove_minor_node(pDip, NULL); + g_pDip = NULL; + 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) +{ + int rc = DDI_SUCCESS; + + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisGetInfo\n")); + + switch (enmCmd) + { + case DDI_INFO_DEVT2DEVINFO: + *ppvResult = (void *)g_pDip; + break; + + case DDI_INFO_DEVT2INSTANCE: + *ppvResult = (void *)(uintptr_t)ddi_get_instance(g_pDip); + break; + + default: + rc = DDI_FAILURE; + break; + } + return rc; +} + + +static int VBoxUSBMonSolarisOpen(dev_t *pDev, int fFlag, int fType, cred_t *pCred) +{ + vboxusbmon_state_t *pState = NULL; + unsigned iOpenInstance; + + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisOpen\n")); + + /* + * Verify we are being opened as a character device. + */ + if (fType != OTYP_CHR) + return EINVAL; + + /* + * Verify that we're called after attach. + */ + if (!g_pDip) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisOpen: Invalid state for opening\n")); + return ENXIO; + } + + for (iOpenInstance = 0; iOpenInstance < 4096; iOpenInstance++) + { + if ( !ddi_get_soft_state(g_pVBoxUSBMonSolarisState, iOpenInstance) /* faster */ + && ddi_soft_state_zalloc(g_pVBoxUSBMonSolarisState, iOpenInstance) == DDI_SUCCESS) + { + pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, iOpenInstance); + break; + } + } + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisOpen: Too many open instances")); + return ENXIO; + } + + pState->Process = RTProcSelf(); + *pDev = makedevice(getmajor(*pDev), iOpenInstance); + + NOREF(fFlag); + NOREF(pCred); + + return 0; +} + + +static int VBoxUSBMonSolarisClose(dev_t Dev, int fFlag, int fType, cred_t *pCred) +{ + vboxusbmon_state_t *pState = NULL; + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisClose\n")); + + pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, getminor(Dev)); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisClose: Failed to get state\n")); + return EFAULT; + } + + /* + * Remove all filters for this client process. + */ + VBoxUSBFilterRemoveOwner(pState->Process); + + ddi_soft_state_free(g_pVBoxUSBMonSolarisState, getminor(Dev)); + pState = NULL; + + NOREF(fFlag); + NOREF(fType); + NOREF(pCred); + + return 0; +} + + +static int VBoxUSBMonSolarisRead(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisRead\n")); + return 0; +} + + +static int VBoxUSBMonSolarisWrite(dev_t Dev, struct uio *pUio, cred_t *pCred) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisWrite\n")); + return 0; +} + + +/** @def IOCPARM_LEN + * Gets the length from the ioctl number. + * This is normally defined by sys/ioccom.h on BSD systems... + */ +#ifndef IOCPARM_LEN +# define IOCPARM_LEN(Code) (((Code) >> 16) & IOCPARM_MASK) +#endif + +static int VBoxUSBMonSolarisIOCtl(dev_t Dev, int Cmd, intptr_t pArg, int Mode, cred_t *pCred, int *pVal) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Dev=%d Cmd=%d pArg=%p Mode=%d\n", Dev, Cmd, pArg)); + + /* + * Get the session from the soft state item. + */ + vboxusbmon_state_t *pState = ddi_get_soft_state(g_pVBoxUSBMonSolarisState, getminor(Dev)); + if (!pState) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: No state data for minor instance %d\n", getminor(Dev))); + 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. + */ + VBOXUSBREQ ReqWrap; + if (IOCPARM_LEN(Cmd) != sizeof(ReqWrap)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: bad request %#x size=%d expected=%d\n", Cmd, IOCPARM_LEN(Cmd), + sizeof(ReqWrap))); + return ENOTTY; + } + + int rc = ddi_copyin((void *)pArg, &ReqWrap, sizeof(ReqWrap), Mode); + 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; + } + + if (ReqWrap.u32Magic != VBOXUSBMON_MAGIC) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Bad magic %#x; pArg=%p Cmd=%d\n", ReqWrap.u32Magic, pArg, Cmd)); + return EINVAL; + } + if (RT_UNLIKELY( ReqWrap.cbData == 0 + || ReqWrap.cbData > _1M*16)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Bad size %#x; pArg=%p Cmd=%d\n", ReqWrap.cbData, pArg, Cmd)); + return EINVAL; + } + + /* + * Read the request. + */ + void *pvBuf = RTMemTmpAlloc(ReqWrap.cbData); + if (RT_UNLIKELY(!pvBuf)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: RTMemTmpAlloc failed to alloc %d bytes\n", ReqWrap.cbData)); + return ENOMEM; + } + + rc = ddi_copyin((void *)(uintptr_t)ReqWrap.pvDataR3, pvBuf, ReqWrap.cbData, Mode); + if (RT_UNLIKELY(rc)) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyin failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, pArg, Cmd, + rc)); + return EFAULT; + } + if (RT_UNLIKELY( ReqWrap.cbData != 0 + && !VALID_PTR(pvBuf))) + { + RTMemTmpFree(pvBuf); + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: pvBuf Invalid pointer %p\n", pvBuf)); + return EINVAL; + } + Log((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: pid=%d\n", (int)RTProcSelf())); + + /* + * Process the IOCtl. + */ + size_t cbDataReturned = 0; + rc = vboxUSBMonSolarisProcessIOCtl(Cmd, pState, pvBuf, ReqWrap.cbData, &cbDataReturned); + ReqWrap.rc = rc; + rc = 0; + + if (RT_UNLIKELY(cbDataReturned > ReqWrap.cbData)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: Too much output data %d expected %d\n", cbDataReturned, ReqWrap.cbData)); + cbDataReturned = ReqWrap.cbData; + } + + ReqWrap.cbData = cbDataReturned; + + /* + * Copy the request back to user space. + */ + rc = ddi_copyout(&ReqWrap, (void *)pArg, sizeof(ReqWrap), Mode); + if (RT_LIKELY(!rc)) + { + /* + * Copy the payload (if any) back to user space. + */ + if (cbDataReturned > 0) + { + rc = ddi_copyout(pvBuf, (void *)(uintptr_t)ReqWrap.pvDataR3, cbDataReturned, Mode); + if (RT_UNLIKELY(rc)) + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyout failed; pvBuf=%p pArg=%p Cmd=%d. rc=%d\n", pvBuf, + pArg, Cmd, rc)); + rc = EFAULT; + } + } + } + else + { + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisIOCtl: ddi_copyout(1) failed pArg=%p Cmd=%d\n", pArg, Cmd)); + rc = EFAULT; + } + + *pVal = rc; + RTMemTmpFree(pvBuf); + 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 pvData The input/output data buffer. Can be NULL + * depending on the function. + * @param cbData The max size of the data buffer. + * @param pcbReturnedData 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 ": vboxUSBMonSolarisProcessIOCtl: iFunction=%d pvBuf=%p cbBuf=%zu\n", iFunction, pvData, cbData)); + + AssertPtrReturn(pvState, VERR_INVALID_POINTER); + vboxusbmon_state_t *pState = (vboxusbmon_state_t *)pvState; + int rc; + +#define CHECKRET_MIN_SIZE(mnemonic, cbMin) \ + do { \ + if (cbData < (cbMin)) \ + { \ + LogRel(("vboxUSBSolarisProcessIOCtl: " mnemonic ": cbData=%#zx (%zu) min is %#zx (%zu)\n", \ + cbData, cbData, (size_t)(cbMin), (size_t)(cbMin))); \ + return VERR_BUFFER_OVERFLOW; \ + } \ + if ((cbMin) != 0 && !VALID_PTR(pvData)) \ + { \ + LogRel(("vboxUSBSolarisProcessIOCtl: " mnemonic ": Invalid pointer %p\n", pvData)); \ + return VERR_INVALID_POINTER; \ + } \ + } while (0) + + switch (iFunction) + { + case VBOXUSBMON_IOCTL_ADD_FILTER: + { + CHECKRET_MIN_SIZE("ADD_FILTER", sizeof(VBOXUSBREQ_ADD_FILTER)); + + VBOXUSBREQ_ADD_FILTER *pReq = (VBOXUSBREQ_ADD_FILTER *)pvData; + PUSBFILTER pFilter = (PUSBFILTER)&pReq->Filter; + + Log(("vboxUSBMonSolarisProcessIOCtl: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x " + "bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT))); + 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); + + rc = VBoxUSBFilterAdd(pFilter, pState->Process, &pReq->uId); + *pcbReturnedData = cbData; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: ADD_FILTER (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_REMOVE_FILTER: + { + CHECKRET_MIN_SIZE("REMOVE_FILTER", sizeof(VBOXUSBREQ_REMOVE_FILTER)); + + VBOXUSBREQ_REMOVE_FILTER *pReq = (VBOXUSBREQ_REMOVE_FILTER *)pvData; + rc = VBoxUSBFilterRemove(pState->Process, (uintptr_t)pReq->uId); + *pcbReturnedData = 0; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: REMOVE_FILTER (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_RESET_DEVICE: + { + CHECKRET_MIN_SIZE("RESET_DEVICE", sizeof(VBOXUSBREQ_RESET_DEVICE)); + + VBOXUSBREQ_RESET_DEVICE *pReq = (VBOXUSBREQ_RESET_DEVICE *)pvData; + rc = vboxUSBMonSolarisResetDevice(pReq->szDevicePath, pReq->fReattach); + *pcbReturnedData = 0; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: RESET_DEVICE (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_CLIENT_INFO: + { + CHECKRET_MIN_SIZE("CLIENT_INFO", sizeof(VBOXUSBREQ_CLIENT_INFO)); + + VBOXUSBREQ_CLIENT_INFO *pReq = (VBOXUSBREQ_CLIENT_INFO *)pvData; + rc = vboxUSBMonSolarisClientInfo(pState, pReq); + *pcbReturnedData = cbData; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: CLIENT_INFO (Process:%d) returned %d\n", pState->Process, rc)); + break; + } + + case VBOXUSBMON_IOCTL_GET_VERSION: + { + CHECKRET_MIN_SIZE("GET_VERSION", sizeof(VBOXUSBREQ_GET_VERSION)); + + PVBOXUSBREQ_GET_VERSION pGetVersionReq = (PVBOXUSBREQ_GET_VERSION)pvData; + pGetVersionReq->u32Major = VBOXUSBMON_VERSION_MAJOR; + pGetVersionReq->u32Minor = VBOXUSBMON_VERSION_MINOR; + *pcbReturnedData = sizeof(VBOXUSBREQ_GET_VERSION); + rc = VINF_SUCCESS; + Log((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: GET_VERSION returned %d\n", rc)); + break; + } + + default: + { + LogRel((DEVICE_NAME ": vboxUSBMonSolarisProcessIOCtl: Unknown request (Process:%d) %#x\n", pState->Process, + iFunction)); + *pcbReturnedData = 0; + rc = VERR_NOT_SUPPORTED; + break; + } + } + return rc; +} + + +static int vboxUSBMonSolarisResetDevice(char *pszDevicePath, bool fReattach) +{ + int rc = VERR_GENERAL_FAILURE; + + LogFunc((DEVICE_NAME ": vboxUSBMonSolarisResetDevice: pszDevicePath=%s fReattach=%d\n", pszDevicePath, fReattach)); + + /* + * Try grabbing the dev_info_t. + */ + dev_info_t *pDeviceInfo = e_ddi_hold_devi_by_path(pszDevicePath, 0); + if (pDeviceInfo) + { + ddi_release_devi(pDeviceInfo); + + /* + * Grab the root device node from the parent hub for resetting. + */ + dev_info_t *pTmpDeviceInfo = NULL; + for (;;) + { + pTmpDeviceInfo = ddi_get_parent(pDeviceInfo); + 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; + + pDeviceInfo = pTmpDeviceInfo; + } + + /* + * Try re-enumerating the device. + */ + rc = usb_reset_device(pDeviceInfo, fReattach ? USB_RESET_LVL_REATTACH : USB_RESET_LVL_DEFAULT); + Log((DEVICE_NAME ": vboxUSBMonSolarisResetDevice: usb_reset_device for %s level=%s rc=%d\n", pszDevicePath, + fReattach ? "ReAttach" : "Default", rc)); + + switch (rc) + { + case USB_SUCCESS: rc = VINF_SUCCESS; break; + case USB_INVALID_PERM: rc = VERR_PERMISSION_DENIED; break; + case USB_INVALID_ARGS: rc = VERR_INVALID_PARAMETER; break; + case USB_BUSY: rc = VERR_RESOURCE_BUSY; break; + case USB_INVALID_CONTEXT: rc = VERR_INVALID_CONTEXT; break; + case USB_FAILURE: rc = VERR_GENERAL_FAILURE; break; + + default: rc = VERR_UNRESOLVED_ERROR; break; + } + } + else + { + rc = VERR_INVALID_HANDLE; + 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)); + + AssertPtrReturn(pState, VERR_INVALID_POINTER); + AssertPtrReturn(pClientInfo, VERR_INVALID_POINTER); + + mutex_enter(&g_VBoxUSBMonSolarisMtx); + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + vboxusbmon_client_t *pPrev = NULL; + while (pCur) + { + if (strncmp(pClientInfo->szDeviceIdent, pCur->Info.szDeviceIdent, sizeof(pCur->Info.szDeviceIdent) - 1) == 0) + { + pClientInfo->Instance = pCur->Info.Instance; + 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; + if (pCur->Info.pfnSetConsumerCredentials) + { + rc = pCur->Info.pfnSetConsumerCredentials(pState->Process, pCur->Info.Instance, NULL /* pvReserved */); + if (RT_FAILURE(rc)) + LogRel((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: pfnSetConsumerCredentials failed! rc=%d\n", rc)); + } + else + rc = VERR_INVALID_FUNCTION; + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: Found %s, rc=%d\n", pClientInfo->szDeviceIdent, rc)); + return rc; + } + pPrev = pCur; + pCur = pCur->pNext; + } + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + LogRel((DEVICE_NAME ": vboxUSBMonSolarisClientInfo: Failed to find client %s\n", pClientInfo->szDeviceIdent)); + return VERR_NOT_FOUND; +} + + +/** + * Registers client driver. + * + * @returns VBox status code. + */ +int VBoxUSBMonSolarisRegisterClient(dev_info_t *pClientDip, PVBOXUSB_CLIENT_INFO pClientInfo) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisRegisterClient: pClientDip=%p pClientInfo=%p\n", pClientDip, pClientInfo)); + AssertPtrReturn(pClientInfo, VERR_INVALID_PARAMETER); + + if (RT_LIKELY(g_pDip)) + { + vboxusbmon_client_t *pClient = RTMemAllocZ(sizeof(vboxusbmon_client_t)); + if (RT_LIKELY(pClient)) + { + pClient->Info.Instance = pClientInfo->Instance; + strncpy(pClient->Info.szClientPath, pClientInfo->szClientPath, sizeof(pClient->Info.szClientPath)); + strncpy(pClient->Info.szDeviceIdent, pClientInfo->szDeviceIdent, sizeof(pClient->Info.szDeviceIdent)); + pClient->Info.pfnSetConsumerCredentials = pClientInfo->pfnSetConsumerCredentials; + pClient->pDip = pClientDip; + + mutex_enter(&g_VBoxUSBMonSolarisMtx); + pClient->pNext = g_pVBoxUSBMonSolarisClients; + g_pVBoxUSBMonSolarisClients = pClient; + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": Client registered (ClientPath=%s Ident=%s)\n", pClient->Info.szClientPath, + pClient->Info.szDeviceIdent)); + return VINF_SUCCESS; + } + return VERR_NO_MEMORY; + } + return VERR_INVALID_STATE; +} + + +/** + * Deregisters client driver. + * + * @returns VBox status code. + */ +int VBoxUSBMonSolarisUnregisterClient(dev_info_t *pClientDip) +{ + LogFunc((DEVICE_NAME ": VBoxUSBMonSolarisUnregisterClient: pClientDip=%p\n", pClientDip)); + AssertReturn(pClientDip, VERR_INVALID_PARAMETER); + + if (RT_LIKELY(g_pDip)) + { + mutex_enter(&g_VBoxUSBMonSolarisMtx); + + vboxusbmon_client_t *pCur = g_pVBoxUSBMonSolarisClients; + vboxusbmon_client_t *pPrev = NULL; + while (pCur) + { + if (pCur->pDip == pClientDip) + { + if (pPrev) + pPrev->pNext = pCur->pNext; + else + g_pVBoxUSBMonSolarisClients = pCur->pNext; + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + Log((DEVICE_NAME ": Client unregistered (ClientPath=%s Ident=%s)\n", pCur->Info.szClientPath, + pCur->Info.szDeviceIdent)); + RTMemFree(pCur); + return VINF_SUCCESS; + } + pPrev = pCur; + pCur = pCur->pNext; + } + + mutex_exit(&g_VBoxUSBMonSolarisMtx); + + LogRel((DEVICE_NAME ": VBoxUSBMonSolarisUnregisterClient: Failed to find registered client %p\n", pClientDip)); + return VERR_NOT_FOUND; + } + 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, + pDevStrings, pszDevicePath, Bus, Port)); + + AssertPtrReturn(pDevDesc, USB_FAILURE); + AssertPtrReturn(pDevStrings, USB_FAILURE); + + /* + * Create a filter from the device being attached. + */ + USBFILTER Filter; + USBFilterInit(&Filter, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&Filter, USBFILTERIDX_VENDOR_ID, pDevDesc->idVendor, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_PRODUCT_ID, pDevDesc->idProduct, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_REV, pDevDesc->bcdDevice, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_CLASS, pDevDesc->bDeviceClass, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS, pDevDesc->bDeviceSubClass, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_DEVICE_PROTOCOL, pDevDesc->bDeviceProtocol, true); + USBFilterSetNumExact(&Filter, USBFILTERIDX_BUS, 0x0 /* Bus */, true); /* Use 0x0 as userland initFilterFromDevice function in Main: see comment on "SetMustBePresent" below */ + USBFilterSetNumExact(&Filter, USBFILTERIDX_PORT, Port, true); + USBFilterSetStringExact(&Filter, USBFILTERIDX_MANUFACTURER_STR, pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "", + true /*fMustBePresent*/, true /*fPurge*/); + USBFilterSetStringExact(&Filter, USBFILTERIDX_PRODUCT_STR, pDevStrings->usb_product ? pDevStrings->usb_product : "", + true /*fMustBePresent*/, true /*fPurge*/); + USBFilterSetStringExact(&Filter, USBFILTERIDX_SERIAL_NUMBER_STR, pDevStrings->usb_serialno ? pDevStrings->usb_serialno : "", + true /*fMustBePresent*/, true /*fPurge*/); + + /* This doesn't work like it should (USBFilterMatch fails on matching field (6) i.e. Bus despite this. Investigate later. */ + USBFilterSetMustBePresent(&Filter, USBFILTERIDX_BUS, false /* fMustBePresent */); + + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x " + "bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x\n", + USBFilterGetNum(&Filter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(&Filter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(&Filter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(&Filter, USBFILTERIDX_BUS), + USBFilterGetNum(&Filter, USBFILTERIDX_PORT))); + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: Manufacturer=%s Product=%s Serial=%s\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. + */ + uintptr_t uId = 0; + RTPROCESS Owner = VBoxUSBFilterMatch(&Filter, &uId); + USBFilterDelete(&Filter); + if (Owner == NIL_RTPROCESS) + { + Log((DEVICE_NAME ": VBoxUSBMonSolarisElectDriver: No matching filters, device %#x:%#x uninteresting\n", + pDevDesc->idVendor, pDevDesc->idProduct)); + return USB_FAILURE; + } + + *ppszDrv = ddi_strdup(VBOXUSB_DRIVER_NAME, KM_SLEEP); +#if 0 + LogRel((DEVICE_NAME ": Capturing %s %s %#x:%#x:%s Bus=%d Port=%d\n", + pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "<Unknown Manufacturer>", + pDevStrings->usb_product ? pDevStrings->usb_product : "<Unnamed USB device>", + pDevDesc->idVendor, pDevDesc->idProduct, pszDevicePath, Bus, Port)); +#else + /* Until IPRT R0 logging is fixed. See @bugref{6657#c7} */ + cmn_err(CE_CONT, "Capturing %s %s 0x%x:0x%x:%s Bus=%d Port=%d\n", + pDevStrings->usb_mfg ? pDevStrings->usb_mfg : "<Unknown Manufacturer>", + pDevStrings->usb_product ? pDevStrings->usb_product : "<Unnamed USB device>", + pDevDesc->idVendor, pDevDesc->idProduct, pszDevicePath, Bus, Port); +#endif + return USB_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h b/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h new file mode 100644 index 00000000..2e61d804 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/include/usbai_private.h @@ -0,0 +1,161 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2009-2010 Oracle Corporation. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _SYS_USB_USBA_USBAI_PRIVATE_H +#define _SYS_USB_USBA_USBAI_PRIVATE_H + +/* + * Unstable interfaces not part of USBAI but used by Solaris client drivers. + * These interfaces may not be present in future releases and are highly + * unstable. + * + * Status key: + * C = Remove from Sun client drivers before removing from this file + * D = May be needed by legacy (DDK) drivers. + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * convenience function for getting default config index + * as saved in usba_device structure + * + * Status: C + */ +uint_t usb_get_current_cfgidx(dev_info_t *); + +/* + * Usb logging, debug and console message handling. + */ +typedef struct usb_log_handle *usb_log_handle_t; + +#define USB_LOG_L0 0 /* warnings, console & syslog buffer */ +#define USB_LOG_L1 1 /* errors, syslog buffer */ +#define USB_LOG_L2 2 /* recoverable errors, debug only */ +#define USB_LOG_L3 3 /* interesting data, debug only */ +#define USB_LOG_L4 4 /* tracing, debug only */ + +#define USB_CHK_BASIC 0 /* Empty mask. Basics always done. */ +#define USB_CHK_SERIAL 0x00000001 /* Compare device serial numbers. */ +#define USB_CHK_CFG 0x00000002 /* Compare raw config clouds. */ +#define USB_CHK_VIDPID 0x00000004 /* Compare product and vendor ID. */ +#define USB_CHK_ALL 0xFFFFFFFF /* Perform maximum checking. */ + +int usb_check_same_device(dev_info_t *dip, + usb_log_handle_t log_handle, + int log_level, + int log_mask, + uint_t check_mask, + char *device_string); + +/* + * ************************************************************************** + * Serialization functions remaining Contracted Consolidation Private + * ************************************************************************** + */ + +/* This whole section: status: C and D. */ + +/* + * opaque serialization handle. + * Used by all usb_serialization routines. + * + * This handle is opaque to the client driver. + */ +typedef struct usb_serialization *usb_serialization_t; + +/* + * usb_init_serialization + * setup for serialization + * + * ARGUMENTS: + * s_dip - devinfo pointer + * flag - USB_INIT_SER_CHECK_SAME_THREAD + * when set, usb_release_access() will verify that the same + * thread releases access. If not, a console warning will + * be issued but access will be released anyways. + * + * RETURNS: + * usb_serialization handle + * + */ +usb_serialization_t usb_init_serialization(dev_info_t *s_dip, + uint_t flag); + +#define USB_INIT_SER_CHECK_SAME_THREAD 1 + +/* fini for serialization */ +void usb_fini_serialization(usb_serialization_t usb_serp); + +/* + * Various ways of calling usb_serialize_access. These correspond to + * their cv_*wait* function counterparts for usb_serialize_access. + */ +#define USB_WAIT 0 +#define USB_WAIT_SIG 1 +#define USB_TIMEDWAIT 2 +#define USB_TIMEDWAIT_SIG 3 + +/* + * usb_serialize_access: + * acquire serialized access + * + * ARGUMENTS: + * usb_serp - usb_serialization handle + * how_to_wait - Which cv_*wait* function to wait for condition. + * USB_WAIT: use cv_wait + * USB_WAIT_SIG: use cv_wait_sig + * USB_TIMEDWAIT: use cv_timedwait + * USB_TIMEDWAIT_SIG: use cv_timedwait_sig + * delta_timeout - Time in ms from current time to timeout. Checked + * only if USB_TIMEDWAIT or USB_TIMEDWAIT_SIG + * specified in how_to_wait. + * RETURNS: + * Same as values returned by cv_*wait* functions, + * except for when how_to_wait == USB_WAIT, where 0 is always returned. + * For calls where a timeout or signal could be expected, use this value + * to tell whether a kill(2) signal or timeout occurred. + */ +int usb_serialize_access(usb_serialization_t usb_serp, + uint_t how_to_wait, + uint_t delta_timeout); + +/* + * usb_release_access: + * release serialized access + * + * ARGUMENTS: + * usb_serp - usb_serialization handle + */ +void usb_release_access(usb_serialization_t usb_serp); + +#ifdef __cplusplus +} +#endif + +#endif /* _SYS_USB_USBA_USBAI_PRIVATE_H */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf new file mode 100644 index 00000000..5d010b9b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusb.conf @@ -0,0 +1,27 @@ +# +# Solaris Host USB Client Driver Configuration +# + +# +# Copyright (C) 2010-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +name="vboxusb" parent="pseudo"; + diff --git a/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf new file mode 100644 index 00000000..ddbce6c2 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/solaris/vboxusbmon.conf @@ -0,0 +1,27 @@ +# +# Solaris Host USB Monitor Driver Configuration +# + +# +# Copyright (C) 2010-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +name="vboxusbmon" parent="pseudo" instance=0; + diff --git a/src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/testcase/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp b/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp new file mode 100644 index 00000000..2bbb4573 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/testcase/tstUSBFilter.cpp @@ -0,0 +1,367 @@ +/** $Id: tstUSBFilter.cpp $ */ +/** @file + * VirtualBox USB filter abstraction - testcase. + */ + +/* + * Copyright (C) 2007-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <VBox/usbfilter.h> +#include <iprt/errcore.h> + +#include <iprt/string.h> +#include <iprt/stream.h> +#include <iprt/assert.h> +#include <iprt/initterm.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define TESTCASE "tstUSBFilter" + +#define TST_CHECK_RC(expr) do { \ + int rc = expr; \ + if (RT_FAILURE(rc)) \ + { \ + RTPrintf( TESTCASE "(%d): %Rrc - %s\n", __LINE__, rc, #expr); \ + cErrors++; \ + } \ + } while (0) + +#define TST_CHECK_EXPR(expr) do { \ + int rc = (intptr_t)(expr); \ + if (!rc) \ + { \ + RTPrintf( TESTCASE "(%d): %s -> %d\n", __LINE__, #expr, rc); \ + cErrors++; \ + } \ +} while (0) + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static const char g_szString64[64+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345" +}; + +static const char g_szString128[128+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" +}; + +static const char g_szString256[256+1] = +{ + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" + "abcdefghijklmnopqrstuvwxyz012345abcdefghijklmnopqrstuvwxyz012345" +}; + + +int main() +{ + unsigned cErrors = 0; + RTR3InitExeNoArguments(0); + + /* + * Basic property setting and simple matching. + */ + USBFILTER Flt1; + USBFilterInit(&Flt1, USBFILTERTYPE_CAPTURE); + /* numbers */ + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_VENDOR_ID, 0x1111, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PRODUCT_ID, 0x2222, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_DEVICE_PROTOCOL, 0xff, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_BUS)); + TST_CHECK_RC(USBFilterSetPresentOnly(&Flt1, USBFILTERIDX_BUS)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, false)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 1, false)); + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_PORT)); + /* strings */ + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "foobar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "foobar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString128, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "barbar", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, g_szString64, true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_MANUFACTURER_STR, "vendor", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_SERIAL_NUMBER_STR, "serial", true, false )); + + /* cloning */ + USBFILTER Dev; + USBFilterClone(&Dev, &Flt1); + + TST_CHECK_EXPR(USBFilterIsIdentical(&Dev, &Flt1)); + TST_CHECK_EXPR(USBFilterMatch(&Dev, &Flt1)); + + USBFilterDelete(&Flt1); + USBFilterDelete(&Dev); + + /* make a sample device */ + USBFilterInit(&Dev, USBFILTERTYPE_CAPTURE); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_VENDOR_ID, 0x1111, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_PRODUCT_ID, 0x2222, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_DEVICE_PROTOCOL, 0xff, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_RC(USBFilterSetNumExact(&Dev, USBFILTERIDX_PORT, 2, true)); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_MANUFACTURER_STR, "vendor", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_RC(USBFilterSetStringExact(&Dev, USBFILTERIDX_SERIAL_NUMBER_STR, "serial", true, false )); + + /* do some basic matching tests */ + USBFilterInit(&Flt1, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(!USBFilterHasAnySubstatialCriteria(&Flt1)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev) /* 100% ignore filter */); + + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 3, true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_PORT, 2, true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 2, true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExact(&Flt1, USBFILTERIDX_BUS, 1, true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "no match", true, false )); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringExact(&Flt1, USBFILTERIDX_PRODUCT_STR, "product", true, false )); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + /* string patterns */ + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*product", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "product*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*uct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*uct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*duct", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "pro*x", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*product*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*oduct*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "*produc*", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?*?*?***??t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "?r??u*?*?*?***??", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*d*t", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetStringPattern(&Flt1, USBFILTERIDX_PRODUCT_STR, "p*x*t", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetIgnore(&Flt1, USBFILTERIDX_PRODUCT_STR)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + /* numeric patterns */ + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0X1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4369", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010421", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111-0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4369-4369", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010421-010421", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1110-0x1112", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "4360-4370", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "010420-010422", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1112-0x1110", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0x1f", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-19", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-017", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0xffff", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-65535", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0-177777", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0-0XABCD", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x0EF-0XABCD", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0X0ef-0Xabcd", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "42|1|0x1111", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "42|0x1111|1", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1111|42|1", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x1112|42|1", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "39-59|0x256-0x101f|0xfffff-0xf000|0x1000-0x2000", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "0x000256-0x0101f|0xf000-0xfffff|0x000008000-0x2000|39-59", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "| | \t \t\t| 0x256 - 0x101f | 0xf000 - 0xfeff\t| 0x1000 -\t0x6000 | 1- 0512", true)); + TST_CHECK_EXPR(USBFilterMatch(&Flt1, &Dev)); + TST_CHECK_RC(USBFilterSetNumExpression(&Flt1, USBFILTERIDX_VENDOR_ID, "| | \t \t\t| 0x256 - 0x101f | 0xf000 - 0xfeff\t| 0x1112 -\t0x6000 | 1- 0512", true)); + TST_CHECK_EXPR(!USBFilterMatch(&Flt1, &Dev)); + + + USBFilterDelete(&Flt1); + + /* + * string overflow + */ + struct + { + uint64_t u64Pre; + USBFILTER Flt; + uint64_t u64Post; + } sOf; + sOf.u64Pre = sOf.u64Post = UINT64_C(0x1234567887654321); + + USBFilterInit(&sOf.Flt, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + + AssertCompileMemberSize(USBFILTER, achStrTab, 256); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[0], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[1], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString256[3], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 0 + 1 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, "", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString256[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString256[1], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 0 + 2 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString128[2], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString128[1], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + /* 3 */ + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_SERIAL_NUMBER_STR, &g_szString64[0], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_PRODUCT_STR, &g_szString64[0], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[4], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[4], true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&sOf.Flt, USBFILTERIDX_MANUFACTURER_STR, &g_szString128[3], true, false ) == VERR_BUFFER_OVERFLOW); + TST_CHECK_EXPR(sOf.u64Pre == UINT64_C(0x1234567887654321)); TST_CHECK_EXPR(sOf.u64Post == UINT64_C(0x1234567887654321)); + + /* + * Check for a string replacement bug. + */ + USBFILTER Dev2; + USBFilterInit(&Dev2, USBFILTERTYPE_CAPTURE); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_VENDOR_ID, 0x19b6, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_PRODUCT_ID, 0x1024, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_REV, 0x0141, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_CLASS, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_SUB_CLASS, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_DEVICE_PROTOCOL, 0, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetNumExact(&Dev2, USBFILTERIDX_PORT, 0x1, true) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_MANUFACTURER_STR, "Generic", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_PRODUCT_STR, "Mass Storage Device", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterSetStringExact(&Dev2, USBFILTERIDX_MANUFACTURER_STR, "YBU1PPRS", true, false ) == VINF_SUCCESS); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_VENDOR_ID) == 0x19b6); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_PRODUCT_ID) == 0x1024); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_REV) == 0x0141); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_CLASS) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_SUB_CLASS) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_DEVICE_PROTOCOL) == 0); + TST_CHECK_EXPR(USBFilterGetNum(&Dev2, USBFILTERIDX_PORT) == 1); + + + /* + * Summary. + */ + if (!cErrors) + RTPrintf(TESTCASE ": SUCCESS\n"); + else + RTPrintf(TESTCASE ": FAILURE - %d errors\n", cErrors); + return !!cErrors; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Install/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp new file mode 100644 index 00000000..b906ae6a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBInstall.cpp @@ -0,0 +1,241 @@ +/* $Id: USBInstall.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver installation, Installation code. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/win/setupapi.h> +#include <newdev.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/initterm.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/utf16.h> + +#include <VBox/VBoxDrvCfg-win.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The support service name. */ +#define SERVICE_NAME "VBoxUSBMon" +/** Win32 Device name. */ +#define DEVICE_NAME "\\\\.\\VBoxUSBMon" +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxUSBMon" +/** Win32 Symlink name. */ +#define DEVICE_NAME_DOS L"\\DosDevices\\VBoxUSBMon" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +int usblibOsCreateService(void); + + +static DECLCALLBACK(void) vboxUsbLog(VBOXDRVCFG_LOG_SEVERITY enmSeverity, char *pszMsg, void *pvContext) +{ + RT_NOREF1(pvContext); + switch (enmSeverity) + { + case VBOXDRVCFG_LOG_SEVERITY_FLOW: + case VBOXDRVCFG_LOG_SEVERITY_REGULAR: + break; + case VBOXDRVCFG_LOG_SEVERITY_REL: + RTPrintf("%s", pszMsg); + break; + default: + break; + } +} + +static DECLCALLBACK(void) vboxUsbPanic(void *pvPanic) +{ + RT_NOREF1(pvPanic); +#ifndef DEBUG_bird + AssertFailed(); +#endif +} + + +int __cdecl main(int argc, char **argv) +{ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + VBoxDrvCfgLoggerSet(vboxUsbLog, NULL); + VBoxDrvCfgPanicSet(vboxUsbPanic, NULL); + + RTPrintf("USB installation\n"); + + rc = usblibOsCreateService(); + if (RT_SUCCESS(rc)) + { + /* Build the path to the INF file: */ + char szInfFile[RTPATH_MAX]; + rc = RTProcGetExecutablePath(szInfFile, sizeof(szInfFile)) ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; + if (RT_SUCCESS(rc)) + { + RTPathStripFilename(szInfFile); + rc = RTPathAppend(szInfFile, sizeof(szInfFile), "VBoxUSB.inf"); + } + PRTUTF16 pwszInfFile = NULL; + if (RT_SUCCESS(rc)) + rc = RTStrToUtf16(szInfFile, &pwszInfFile); + if (RT_SUCCESS(rc)) + { + /* Install the INF file: */ + HRESULT hr = VBoxDrvCfgInfInstall(pwszInfFile); + if (hr == S_OK) + RTPrintf("Installation successful.\n"); + else + rc = -1; + + RTUtf16Free(pwszInfFile); + } + else + RTMsgError("Failed to construct INF path: %Rrc", rc); + } + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTEXITCODE_FAILURE; +} + + +/** + * Changes the USB driver service to specified driver path. + * + * @returns 0 on success. + * @returns < 0 on failure. + */ +int usblibOsChangeService(const char *pszDriverPath) +{ + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwLastError = GetLastError(); + int rc = RTErrConvertFromWin32(dwLastError); + AssertPtr(pszDriverPath); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed rc=%d\n", dwLastError)); + if (hSMgrCreate) + { + SC_HANDLE hService = OpenService(hSMgrCreate, + SERVICE_NAME, + GENERIC_ALL); + DWORD dwLastError = GetLastError(); + if (hService == NULL) + { + AssertMsg(hService, ("OpenService failed! LastError=%Rwa, pszDriver=%s\n", dwLastError, pszDriverPath)); + rc = RTErrConvertFromWin32(dwLastError); + } + else + { + /* We only gonna change the driver image path, the rest remains like it already is */ + if (ChangeServiceConfig(hService, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + SERVICE_NO_CHANGE, + pszDriverPath, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL)) + { + RTPrintf("Changed service config to new driver path: %s\n", pszDriverPath); + } + else + { + AssertMsg(hService, ("ChangeServiceConfig failed! LastError=%Rwa, pszDriver=%s\n", dwLastError, pszDriverPath)); + rc = RTErrConvertFromWin32(dwLastError); + } + if (hService != NULL) + CloseServiceHandle(hService); + } + + CloseServiceHandle(hSMgrCreate); + } + return rc; +} + + +/** + * Creates the service. + * + * @returns 0 on success. + * @returns < 0 on failure. + */ +int usblibOsCreateService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + SC_HANDLE hSMgrCreate = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD dwLastError = GetLastError(); + int rc = RTErrConvertFromWin32(dwLastError); + AssertMsg(hSMgrCreate, ("OpenSCManager(,,create) failed rc=%d\n", dwLastError)); + if (hSMgrCreate) + { + char szDriver[RTPATH_MAX]; + int rc = RTPathExecDir(szDriver, sizeof(szDriver) - sizeof("\\VBoxUSBMon.sys")); + if (RT_SUCCESS(rc)) + { + strcat(szDriver, "\\VBoxUSBMon.sys"); + RTPrintf("Creating USB monitor driver service with path %s ...\n", szDriver); + SC_HANDLE hService = CreateService(hSMgrCreate, + SERVICE_NAME, + "VBox USB Monitor Driver", + SERVICE_QUERY_STATUS, + SERVICE_KERNEL_DRIVER, + SERVICE_DEMAND_START, + SERVICE_ERROR_NORMAL, + szDriver, + NULL, NULL, NULL, NULL, NULL); + DWORD dwLastError = GetLastError(); + if (dwLastError == ERROR_SERVICE_EXISTS) + { + RTPrintf("USB monitor driver service already exists, skipping creation.\n"); + rc = usblibOsChangeService(szDriver); + } + else + { + AssertMsg(hService, ("CreateService failed! LastError=%Rwa, szDriver=%s\n", dwLastError, szDriver)); + rc = RTErrConvertFromWin32(dwLastError); + if (hService != NULL) + CloseServiceHandle(hService); + } + } + CloseServiceHandle(hSMgrCreate); + } + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp new file mode 100644 index 00000000..0f9576d0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Install/USBUninstall.cpp @@ -0,0 +1,211 @@ +/* $Id: USBUninstall.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver uninstallation. + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/win/setupapi.h> +#include <newdev.h> + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <iprt/errcore.h> +#include <VBox/VBoxDrvCfg-win.h> +#include <stdio.h> + + +int usblibOsStopService(void); +int usblibOsDeleteService(void); + +static DECLCALLBACK(void) vboxUsbLog(VBOXDRVCFG_LOG_SEVERITY enmSeverity, char *pszMsg, void *pvContext) +{ + RT_NOREF1(pvContext); + switch (enmSeverity) + { + case VBOXDRVCFG_LOG_SEVERITY_FLOW: + case VBOXDRVCFG_LOG_SEVERITY_REGULAR: + break; + case VBOXDRVCFG_LOG_SEVERITY_REL: + printf("%s", pszMsg); + break; + default: + break; + } +} + +static DECLCALLBACK(void) vboxUsbPanic(void *pvPanic) +{ + RT_NOREF1(pvPanic); +#ifndef DEBUG_bird + AssertFailed(); +#endif +} + + +int __cdecl main(int argc, char **argv) +{ + RT_NOREF2(argc, argv); + printf("USB uninstallation\n"); + + VBoxDrvCfgLoggerSet(vboxUsbLog, NULL); + VBoxDrvCfgPanicSet(vboxUsbPanic, NULL); + + usblibOsStopService(); + usblibOsDeleteService(); + + HRESULT hr = VBoxDrvCfgInfUninstallAllF(L"USB", L"USB\\VID_80EE&PID_CAFE", SUOI_FORCEDELETE); + if (hr != S_OK) + { + printf("SetupUninstallOEMInf failed with hr=0x%x\n", hr); + return 1; + } + + printf("USB uninstallation succeeded!\n"); + + return 0; +} + +/** The support service name. */ +#define SERVICE_NAME "VBoxUSBMon" +/** Win32 Device name. */ +#define DEVICE_NAME "\\\\.\\VBoxUSBMon" +/** NT Device name. */ +#define DEVICE_NAME_NT L"\\Device\\VBoxUSBMon" +/** Win32 Symlink name. */ +#define DEVICE_NAME_DOS L"\\DosDevices\\VBoxUSBMon" + +/** + * Stops a possibly running service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +int usblibOsStopService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", LastError)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (hService) + { + /* + * Stop the service. + */ + SERVICE_STATUS Status; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + /* + * Wait for finish about 1 minute. + * It should be enough for work with driver verifier + */ + int iWait = 600; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + else + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("ControlService failed with LastError=%Rwa. status=%d\n", LastError, Status.dwCurrentState)); + } + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hSMgr); + } + return rc; +} + + +/** + * Deletes the service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +int usblibOsDeleteService(void) +{ + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_CHANGE_CONFIG); + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", LastError)); + if (hSMgr) + { + SC_HANDLE hService = OpenService(hSMgr, SERVICE_NAME, DELETE); + if (hService) + { + /* + * Delete the service. + */ + if (DeleteService(hService)) + rc = 0; + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("DeleteService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hSMgr); + } + return rc; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk new file mode 100644 index 00000000..971328ab --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kmk @@ -0,0 +1,203 @@ +# $Id: Makefile.kmk $ +## @file +# Sub-Makefile for the Windows USB drivers. +# + +# +# Copyright (C) 2006-2020 Oracle Corporation +# +# 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. +# +# 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. +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +LIBRARIES.win += usbd +SYSMODS.win += VBoxUSB VBoxUSBMon +PROGRAMS.win += USBInstall USBUninstall USBTest +INSTALLS.win += install-infs + +# +# usbd +# +usbd_TEMPLATE = VBOXR0DRV +usbd_SOURCES = usbd/usbd.def + +# +# VBoxUSB +# +VBoxUSB_TEMPLATE = VBOXR0DRV +ifdef VBOX_SIGNING_MODE + VBoxUSB_INSTTYPE = none + VBoxUSB_DEBUG_INSTTYPE = both +endif +VBoxUSB_SDKS = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK)INCS +VBoxUSB_DEFS = IN_RT_R0 IN_SUP_R0 VBOX_DBG_LOG_NAME=\"USBDev\" +VBoxUSB_LDFLAGS.x86 = -Entry:DriverEntry@8 +VBoxUSB_LDFLAGS.amd64 = -Entry:DriverEntry +VBoxUSB_SOURCES = \ + dev/VBoxUsbDev.cpp \ + dev/VBoxUsbRt.cpp \ + dev/VBoxUsbPnP.cpp \ + dev/VBoxUsbPwr.cpp \ + cmn/VBoxUsbTool.cpp \ + cmn/VBoxDrvTool.cpp \ + dev/VBoxUsbDev.rc +VBoxUSB_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(usbd_1_TARGET) + +# +# VBoxUSBMon +# +VBoxUSBMon_TEMPLATE = VBOXR0DRV +ifdef VBOX_SIGNING_MODE + VBoxUSBMon_INSTTYPE = none + VBoxUSBMon_DEBUG_INSTTYPE = both +endif +VBoxUSBMon_INCS := $(PATH_SUB_CURRENT)/.. +VBoxUSBMon_SDKS = ReorderCompilerIncs $(VBOX_WINDDK) $(VBOX_WINPSDK)INCS +VBoxUSBMon_DEFS = IN_RT_R0 IN_SUP_R0 NTDDI_WINNT=_NTDDI_VISTA \ + VBOXUSBFILTERMGR_USB_SPINLOCK VBOX_DBG_LOG_NAME=\"USBMon\" +VBoxUSBMon_LDFLAGS.x86 = -Entry:DriverEntry@8 +VBoxUSBMon_LDFLAGS.amd64 = -Entry:DriverEntry +ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUSBMon_DEFS += VBOX_USBMON_WITH_FILTER_AUTOAPPLY +endif +VBoxUSBMon_SOURCES = \ + mon/VBoxUsbMon.cpp \ + mon/VBoxUsbFlt.cpp \ + mon/VBoxUsbHook.cpp \ + cmn/VBoxUsbTool.cpp \ + cmn/VBoxDrvTool.cpp \ + ../USBFilter.cpp \ + ../VBoxUSBFilterMgr.cpp \ + mon/VBoxUsbMon.rc +VBoxUSBMon_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/ntoskrnl.lib \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/hal.lib \ + $(PATH_STAGE_LIB)/RuntimeR0Drv$(VBOX_SUFF_LIB) \ + $(usbd_1_TARGET) +if1of ($(KBUILD_TYPE), debug) +VBoxUSBMon_DEFS += LOG_ENABLED VBOX_USB_WITH_VERBOSE_LOGGING +endif + +# +# USBInstall +# +USBInstall_TEMPLATE = VBOXR3EXE +USBInstall_DEFS = IN_RT_R3 +USBInstall_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBOX_NTDLL +USBInstall_CXXFLAGS = -Gz +USBInstall_CFLAGS = -Gz +USBInstall_SOURCES = \ + Install/USBInstall.cpp +USBInstall_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/newdev.lib \ + $(LIB_RUNTIME) \ + $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBoxDrvCfg$(VBOX_SUFF_LIB) + + +# +# USBUninstall +# +USBUninstall_TEMPLATE = VBOXR3EXE +USBUninstall_DEFS = IN_RT_R3 +USBUninstall_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBOX_NTDLL +USBUninstall_CXXFLAGS = -Gz +USBUninstall_CFLAGS = -Gz +USBUninstall_SOURCES = \ + Install/USBUninstall.cpp +USBUninstall_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/newdev.lib \ + $(LIB_RUNTIME) \ + $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBoxDrvCfg$(VBOX_SUFF_LIB) + +# +# USBTest +# +USBTest_TEMPLATE = VBOXR3EXE +USBTest_DEFS = IN_RT_R3 IN_USBLIB +USBTest_SDKS = ReorderCompilerIncs $(VBOX_WINPSDK) $(VBOX_WINDDK) VBOX_NTDLL +USBTest_CXXFLAGS = -Gz +USBTest_CFLAGS = -Gz +USBTest_SOURCES = \ + testcase/USBTest.cpp \ + ../USBFilter.cpp +USBTest_LIBS = \ + $(PATH_SDK_$(VBOX_WINDDK)_LIB)/newdev.lib \ + $(LIB_RUNTIME) \ + $(PATH_STAGE_LIB)/SUPR3$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBoxDrvCfg$(VBOX_SUFF_LIB) + +# +# Install the INF files. +# +install-infs_TEMPLATE = VBoxR0DrvInfCat +install-infs_SOURCES = \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf +install-infs_CLEAN = $(install-infs_SOURCES) +install-infs_BLDDIRS = \ + $(PATH_TARGET)/VBoxUSBCat.dir \ + $(PATH_TARGET)/VBoxUSBMonCat.dir + +$(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf: $(PATH_SUB_CURRENT)/dev/VBoxUSB.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,install-infs,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + +$(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf: $(PATH_SUB_CURRENT)/mon/VBoxUSBMon.inf $(MAKEFILE_CURRENT) | $$(dir $$@) + $(call MSG_GENERATE,install-infs,$@,$<) + $(call VBOX_EDIT_INF_FN,$<,$@) + +ifdef VBOX_SIGNING_MODE +install-infs_SOURCES += \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat=>VBoxUSB-PreW10.cat \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat=>VBoxUSBMon-PreW10.cat \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys + +$(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys: $$(VBoxUSB_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + +$(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.cat: \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.inf \ + $(PATH_TARGET)/VBoxUSBCat.dir/VBoxUSB.sys + $(call MSG_TOOL,Inf2Cat,VBoxUSB-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + +$(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys: $$(VBoxUSBMon_1_TARGET) | $$(dir $$@) + $(INSTALL) -m 644 $< $(@D) + +$(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.cat: \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.inf \ + $(PATH_TARGET)/VBoxUSBMonCat.dir/VBoxUSBMon.sys + $(call MSG_TOOL,Inf2Cat,VBoxUSBMon-inf,$@,$<) + $(call VBOX_MAKE_CAT_FN, $(@D),$@) + +endif # signing + +# generate rules +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp new file mode 100644 index 00000000..e460a50a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.cpp @@ -0,0 +1,237 @@ +/* $Id: VBoxDrvTool.cpp $ */ +/** @file + * Windows Driver R0 Tooling. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#include "VBoxDrvTool.h" + +#include <iprt/assert.h> +#include <VBox/log.h> + +#include "../../../win/VBoxDbgLog.h" + +#define VBOXDRVTOOL_MEMTAG 'TDBV' + +static PVOID vboxDrvToolMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXDRVTOOL_MEMTAG); + Assert(pvMem); + return pvMem; +} + +static PVOID vboxDrvToolMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxDrvToolMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +static VOID vboxDrvToolMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXDRVTOOL_MEMTAG); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKeyU(OUT PHANDLE phKey, IN PUNICODE_STRING pName, IN ACCESS_MASK fAccess) +{ + OBJECT_ATTRIBUTES ObjAttr; + + InitializeObjectAttributes(&ObjAttr, pName, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL); + + return ZwOpenKey(phKey, fAccess, &ObjAttr); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKey(OUT PHANDLE phKey, IN PWCHAR pName, IN ACCESS_MASK fAccess) +{ + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + + return VBoxDrvToolRegOpenKeyU(phKey, &RtlStr, fAccess); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegCloseKey(IN HANDLE hKey) +{ + return ZwClose(hKey); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegQueryValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT PULONG pDword) +{ + struct + { + KEY_VALUE_PARTIAL_INFORMATION Info; + UCHAR Buf[32]; /* should be enough */ + } Buf; + ULONG cbBuf; + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + NTSTATUS Status = ZwQueryValueKey(hKey, + &RtlStr, + KeyValuePartialInformation, + &Buf.Info, + sizeof(Buf), + &cbBuf); + if (Status == STATUS_SUCCESS) + { + if (Buf.Info.Type == REG_DWORD) + { + Assert(Buf.Info.DataLength == 4); + *pDword = *((PULONG)Buf.Info.Data); + return STATUS_SUCCESS; + } + } + + return STATUS_INVALID_PARAMETER; +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegSetValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT ULONG val) +{ + UNICODE_STRING RtlStr; + RtlInitUnicodeString(&RtlStr, pName); + return ZwSetValueKey(hKey, &RtlStr, + NULL, /* IN ULONG TitleIndex OPTIONAL, reserved */ + REG_DWORD, + &val, + sizeof(val)); +} + +static NTSTATUS vboxDrvToolIoCompletionSetEvent(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF2(pDevObj, pIrp); + PKEVENT pEvent = (PKEVENT)pvContext; + KeSetEvent(pEvent, 0, FALSE); + return STATUS_MORE_PROCESSING_REQUIRED; +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostAsync(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent) +{ + IoSetCompletionRoutine(pIrp, vboxDrvToolIoCompletionSetEvent, pEvent, TRUE, TRUE, TRUE); + return IoCallDriver(pDevObj, pIrp); +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSync(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + KEVENT Event; + KeInitializeEvent(&Event, NotificationEvent, FALSE); + NTSTATUS Status = VBoxDrvToolIoPostAsync(pDevObj, pIrp, &Event); + if (Status == STATUS_PENDING) + { + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = pIrp->IoStatus.Status; + } + return Status; +} + +/* !!!NOTE: the caller MUST be the IRP owner!!! * + * !! one can not post threaded IRPs this way!! */ +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSyncWithTimeout(PDEVICE_OBJECT pDevObj, PIRP pIrp, ULONG dwTimeoutMs) +{ + KEVENT Event; + LOG(("post irp (0x%p) to DevObj(0x%p) with timeout (%u)", pIrp, pDevObj, dwTimeoutMs)); + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + NTSTATUS Status = VBoxDrvToolIoPostAsync(pDevObj, pIrp, &Event); + if (Status == STATUS_PENDING) + { + LARGE_INTEGER Interval; + PLARGE_INTEGER pInterval = NULL; + if (dwTimeoutMs != RT_INDEFINITE_WAIT) + { + Interval.QuadPart = -(int64_t) dwTimeoutMs /* ms */ * 10000; + pInterval = &Interval; + } + + Status = KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, pInterval); + if (Status == STATUS_TIMEOUT) + { + WARN(("irp (0x%p) to DevObj(0x%p) was not completed within timeout (%u), cancelling", pIrp, pDevObj, dwTimeoutMs)); + if (!IoCancelIrp(pIrp)) + { + /* this may happen, but this is something the caller with timeout is not expecting */ + WARN(("IoCancelIrp failed")); + } + + /* wait for the IRP to complete */ + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + } + else + { + ASSERT_WARN(Status == STATUS_SUCCESS, ("uunexpected Status (0x%x)", Status)); + } + + /* by this time the IRP is completed */ + Status = pIrp->IoStatus.Status; + LOG(("Pending IRP(0x%p) completed with status(0x%x)", pIrp, Status)); + } + else + { + LOG(("IRP(0x%p) completed with status(0x%x)", pIrp, Status)); + } + return Status; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolRefWaitEqual(PVBOXDRVTOOL_REF pRef, uint32_t u32Val) +{ + LARGE_INTEGER Interval; + Interval.QuadPart = -(int64_t) 2 /* ms */ * 10000; + uint32_t cRefs; + size_t loops = 0; + KTIMER kTimer; + NTSTATUS status = STATUS_SUCCESS; + + KeInitializeTimer(&kTimer); + + while ((cRefs = ASMAtomicReadU32(&pRef->cRefs)) > u32Val && loops < 256) + { + Assert(cRefs >= u32Val); + Assert(cRefs < UINT32_MAX/2); + + KeSetTimer(&kTimer, Interval, NULL); + status = KeWaitForSingleObject(&kTimer, Executive, KernelMode, false, NULL); + Assert(NT_SUCCESS(status)); + loops++; + } +} + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolStrCopy(PUNICODE_STRING pDst, CONST PUNICODE_STRING pSrc) +{ + USHORT cbLength = pSrc->Length + sizeof (pDst->Buffer[0]); + pDst->Buffer = (PWCHAR)vboxDrvToolMemAlloc(cbLength); + Assert(pDst->Buffer); + if (pDst->Buffer) + { + RtlMoveMemory(pDst->Buffer, pSrc->Buffer, pSrc->Length); + pDst->Buffer[pSrc->Length / sizeof (pDst->Buffer[0])] = L'\0'; + pDst->Length = pSrc->Length; + pDst->MaximumLength = cbLength; + return STATUS_SUCCESS; + } + return STATUS_NO_MEMORY; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolStrFree(PUNICODE_STRING pStr) +{ + vboxDrvToolMemFree(pStr->Buffer); +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h new file mode 100644 index 00000000..4b09c28f --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxDrvTool.h @@ -0,0 +1,103 @@ +/* $Id: VBoxDrvTool.h $ */ +/** @file + * Windows Driver R0 Tooling. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <iprt/stdint.h> +#include <iprt/assert.h> +#include <iprt/asm.h> +#include <iprt/nt/wdm.h> + + +RT_C_DECLS_BEGIN + +#if 0 +/* enable this in case we include this in a dll*/ +# ifdef IN_VBOXDRVTOOL +# define VBOXDRVTOOL_DECL(a_Type) DECLEXPORT(a_Type) +# else +# define VBOXDRVTOOL_DECL(a_Type) DECLIMPORT(a_Type) +# endif +#else +/*enable this in case we include this in a static lib*/ +# define VBOXDRVTOOL_DECL(a_Type) a_Type VBOXCALL +#endif + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKeyU(OUT PHANDLE phKey, IN PUNICODE_STRING pName, IN ACCESS_MASK fAccess); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegOpenKey(OUT PHANDLE phKey, IN PWCHAR pName, IN ACCESS_MASK fAccess); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegCloseKey(IN HANDLE hKey); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegQueryValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT PULONG pDword); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolRegSetValueDword(IN HANDLE hKey, IN PWCHAR pName, OUT ULONG val); + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostAsync(PDEVICE_OBJECT pDevObj, PIRP pIrp, PKEVENT pEvent); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSync(PDEVICE_OBJECT pDevObj, PIRP pIrp); +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolIoPostSyncWithTimeout(PDEVICE_OBJECT pDevObj, PIRP pIrp, ULONG dwTimeoutMs); +DECLINLINE(NTSTATUS) VBoxDrvToolIoComplete(PIRP pIrp, NTSTATUS Status, ULONG ulInfo) +{ + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = ulInfo; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + +typedef struct VBOXDRVTOOL_REF +{ + volatile uint32_t cRefs; +} VBOXDRVTOOL_REF, *PVBOXDRVTOOL_REF; + +DECLINLINE(void) VBoxDrvToolRefInit(PVBOXDRVTOOL_REF pRef) +{ + pRef->cRefs = 1; +} + +DECLINLINE(uint32_t) VBoxDrvToolRefRetain(PVBOXDRVTOOL_REF pRef) +{ + Assert(pRef->cRefs); + Assert(pRef->cRefs < UINT32_MAX / 2); + return ASMAtomicIncU32(&pRef->cRefs); +} + +DECLINLINE(uint32_t) VBoxDrvToolRefRelease(PVBOXDRVTOOL_REF pRef) +{ + uint32_t cRefs = ASMAtomicDecU32(&pRef->cRefs); + Assert(cRefs < UINT32_MAX/2); + return cRefs; +} + +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolRefWaitEqual(PVBOXDRVTOOL_REF pRef, uint32_t u32Val); + +VBOXDRVTOOL_DECL(NTSTATUS) VBoxDrvToolStrCopy(PUNICODE_STRING pDst, CONST PUNICODE_STRING pSrc); +VBOXDRVTOOL_DECL(VOID) VBoxDrvToolStrFree(PUNICODE_STRING pStr); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxDrvTool_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h new file mode 100644 index 00000000..280da44d --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbIdc.h @@ -0,0 +1,85 @@ +/* $Id: VBoxUsbIdc.h $ */ +/** @file + * Windows USB Proxy - Monitor Driver communication interface. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#define VBOXUSBIDC_VERSION_MAJOR 1 +#define VBOXUSBIDC_VERSION_MINOR 0 + +#define VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION CTL_CODE(FILE_DEVICE_UNKNOWN, 0x618, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP CTL_CODE(FILE_DEVICE_UNKNOWN, 0x619, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN CTL_CODE(FILE_DEVICE_UNKNOWN, 0x61A, METHOD_NEITHER, FILE_WRITE_ACCESS) +#define VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STATE_CHANGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x61B, METHOD_NEITHER, FILE_WRITE_ACCESS) + +typedef struct +{ + uint32_t u32Major; + uint32_t u32Minor; +} VBOXUSBIDC_VERSION, *PVBOXUSBIDC_VERSION; + +typedef void *HVBOXUSBIDCDEV; + +/* the initial device state is USBDEVICESTATE_HELD_BY_PROXY */ +typedef struct VBOXUSBIDC_PROXY_STARTUP +{ + union + { + /* in: device PDO */ + PDEVICE_OBJECT pPDO; + /* out: device handle to be used for subsequent USBSUP_PROXY_XXX calls */ + HVBOXUSBIDCDEV hDev; + } u; +} VBOXUSBIDC_PROXY_STARTUP, *PVBOXUSBIDC_PROXY_STARTUP; + +typedef struct VBOXUSBIDC_PROXY_TEARDOWN +{ + HVBOXUSBIDCDEV hDev; +} VBOXUSBIDC_PROXY_TEARDOWN, *PVBOXUSBIDC_PROXY_TEARDOWN; + +typedef enum +{ + VBOXUSBIDC_PROXY_STATE_UNKNOWN = 0, + VBOXUSBIDC_PROXY_STATE_IDLE, + VBOXUSBIDC_PROXY_STATE_INITIAL = VBOXUSBIDC_PROXY_STATE_IDLE, + VBOXUSBIDC_PROXY_STATE_USED_BY_GUEST +} VBOXUSBIDC_PROXY_STATE; + +typedef struct VBOXUSBIDC_PROXY_STATE_CHANGE +{ + HVBOXUSBIDCDEV hDev; + VBOXUSBIDC_PROXY_STATE enmState; +} VBOXUSBIDC_PROXY_STATE_CHANGE, *PVBOXUSBIDC_PROXY_STATE_CHANGE; + +NTSTATUS VBoxUsbIdcInit(); +VOID VBoxUsbIdcTerm(); +NTSTATUS VBoxUsbIdcProxyStarted(PDEVICE_OBJECT pPDO, HVBOXUSBIDCDEV *phDev); +NTSTATUS VBoxUsbIdcProxyStopped(HVBOXUSBIDCDEV hDev); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbIdc_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp new file mode 100644 index 00000000..42565fd8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.cpp @@ -0,0 +1,418 @@ +/* $Id: VBoxUsbTool.cpp $ */ +/** @file + * Windows USB R0 Tooling. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#define INITGUID +#include "VBoxUsbTool.h" +#include <usbbusif.h> + +#include <iprt/assert.h> +#include <iprt/string.h> +#include <iprt/utf16.h> +#include <VBox/log.h> +#include <VBox/usblib.h> + +#include "../../../win/VBoxDbgLog.h" + +#define VBOXUSBTOOL_MEMTAG 'TUBV' + +static PVOID vboxUsbToolMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSBTOOL_MEMTAG); + Assert(pvMem); + return pvMem; +} + +static PVOID vboxUsbToolMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxUsbToolMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +static VOID vboxUsbToolMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSBTOOL_MEMTAG); +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAlloc(USHORT u16Function, USHORT cbSize) +{ + PURB pUrb = (PURB)vboxUsbToolMemAlloc(cbSize); + Assert(pUrb); + if (!pUrb) + return NULL; + + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAllocZ(USHORT u16Function, USHORT cbSize) +{ + PURB pUrb = (PURB)vboxUsbToolMemAllocZ(cbSize); + Assert(pUrb); + if (!pUrb) + return NULL; + + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbReinit(PURB pUrb, USHORT cbSize, USHORT u16Function) +{ + Assert(pUrb->UrbHeader.Length == cbSize); + if (pUrb->UrbHeader.Length < cbSize) + return NULL; + pUrb->UrbHeader.Length = cbSize; + pUrb->UrbHeader.Function = u16Function; + return pUrb; +} + +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolUrbFree(PURB pUrb) +{ + vboxUsbToolMemFree(pUrb); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolUrbPost(PDEVICE_OBJECT pDevObj, PURB pUrb, ULONG dwTimeoutMs) +{ + if (dwTimeoutMs == RT_INDEFINITE_WAIT) + return VBoxUsbToolIoInternalCtlSendSync(pDevObj, IOCTL_INTERNAL_USB_SUBMIT_URB, pUrb, NULL); + return VBoxUsbToolIoInternalCtlSendSyncWithTimeout(pDevObj, IOCTL_INTERNAL_USB_SUBMIT_URB, pUrb, NULL, dwTimeoutMs); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDescriptor(PDEVICE_OBJECT pDevObj, void *pvBuffer, int cbBuffer, int Type, int iIndex, int LangId, ULONG dwTimeoutMs) +{ + NTSTATUS Status; + USHORT cbUrb = sizeof (struct _URB_CONTROL_DESCRIPTOR_REQUEST); + PURB pUrb = VBoxUsbToolUrbAllocZ(URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE, cbUrb); + if (!pUrb) + { + WARN(("allocating URB failed")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + PUSB_COMMON_DESCRIPTOR pCmn = (PUSB_COMMON_DESCRIPTOR)pvBuffer; + pCmn->bLength = cbBuffer; + pCmn->bDescriptorType = Type; + + pUrb->UrbHeader.Function = URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE; + pUrb->UrbHeader.Length = sizeof(struct _URB_CONTROL_DESCRIPTOR_REQUEST); + pUrb->UrbControlDescriptorRequest.TransferBufferLength = cbBuffer; + pUrb->UrbControlDescriptorRequest.TransferBuffer = pvBuffer; + pUrb->UrbControlDescriptorRequest.Index = (UCHAR)iIndex; + pUrb->UrbControlDescriptorRequest.DescriptorType = (UCHAR)Type; + pUrb->UrbControlDescriptorRequest.LanguageId = (USHORT)LangId; + + Status = VBoxUsbToolUrbPost(pDevObj, pUrb, dwTimeoutMs); + ASSERT_WARN(Status == STATUS_SUCCESS, ("VBoxUsbToolUrbPost failed Status (0x%x)", Status)); + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolStringDescriptorToUnicodeString(PUSB_STRING_DESCRIPTOR pDr, PUNICODE_STRING pUnicode) +{ + /* for some reason the string dr sometimes contains a non-null terminated string + * although we zeroed up the complete descriptor buffer + * this is why RtlInitUnicodeString won't work + * we need to init the scting length based on dr length */ + pUnicode->Buffer = pDr->bString; + pUnicode->Length = pUnicode->MaximumLength = pDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString); +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetStringDescriptor(PDEVICE_OBJECT pDevObj, char *pszResult, ULONG cbResult, + int iIndex, int LangId, ULONG dwTimeoutMs) +{ + char aBuf[MAXIMUM_USB_STRING_LENGTH]; + AssertCompile(sizeof (aBuf) <= UINT8_MAX); + UCHAR cbBuf = (UCHAR)sizeof (aBuf); + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)&aBuf; + + Assert(pszResult); + *pszResult = 0; + + memset(pDr, 0, cbBuf); + pDr->bLength = cbBuf; + pDr->bDescriptorType = USB_STRING_DESCRIPTOR_TYPE; + + NTSTATUS Status = VBoxUsbToolGetDescriptor(pDevObj, pDr, cbBuf, USB_STRING_DESCRIPTOR_TYPE, iIndex, LangId, dwTimeoutMs); + if (NT_SUCCESS(Status)) + { + if (pDr->bLength >= sizeof (USB_STRING_DESCRIPTOR)) + { + int rc = RTUtf16ToUtf8Ex(pDr->bString, (pDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString)) / sizeof(RTUTF16), + &pszResult, cbResult, NULL /*pcch*/); + if (RT_SUCCESS(rc)) + { + USBLibPurgeEncoding(pszResult); + Status = STATUS_SUCCESS; + } + else + Status = STATUS_UNSUCCESSFUL; + } + else + Status = STATUS_INVALID_PARAMETER; + } + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetLangID(PDEVICE_OBJECT pDevObj, int *pLangId, ULONG dwTimeoutMs) +{ + char aBuf[MAXIMUM_USB_STRING_LENGTH]; + AssertCompile(sizeof (aBuf) <= UINT8_MAX); + UCHAR cbBuf = (UCHAR)sizeof (aBuf); + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)&aBuf; + + Assert(pLangId); + *pLangId = 0; + + memset(pDr, 0, cbBuf); + pDr->bLength = cbBuf; + pDr->bDescriptorType = USB_STRING_DESCRIPTOR_TYPE; + + NTSTATUS Status = VBoxUsbToolGetDescriptor(pDevObj, pDr, cbBuf, USB_STRING_DESCRIPTOR_TYPE, 0, 0, dwTimeoutMs); + if (NT_SUCCESS(Status)) + { + /* Just grab the first lang ID if available. In 99% cases, it will be US English (0x0409).*/ + if (pDr->bLength >= sizeof (USB_STRING_DESCRIPTOR)) + { + AssertCompile(sizeof (pDr->bString[0]) == sizeof (uint16_t)); + *pLangId = pDr->bString[0]; + Status = STATUS_SUCCESS; + } + else + { + Status = STATUS_INVALID_PARAMETER; + } + } + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDeviceSpeed(PDEVICE_OBJECT pDevObj, BOOLEAN *pbIsHigh) +{ + Assert(pbIsHigh); + *pbIsHigh = FALSE; + + PIRP pIrp = IoAllocateIrp(pDevObj->StackSize, FALSE); + Assert(pIrp); + if (!pIrp) + { + return STATUS_INSUFFICIENT_RESOURCES; + } + + USB_BUS_INTERFACE_USBDI_V1 BusIf; + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_PNP; + pSl->MinorFunction = IRP_MN_QUERY_INTERFACE; + pSl->Parameters.QueryInterface.InterfaceType = &USB_BUS_INTERFACE_USBDI_GUID; + pSl->Parameters.QueryInterface.Size = sizeof (BusIf); + pSl->Parameters.QueryInterface.Version = USB_BUSIF_USBDI_VERSION_1; + pSl->Parameters.QueryInterface.Interface = (PINTERFACE)&BusIf; + pSl->Parameters.QueryInterface.InterfaceSpecificData = NULL; + + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevObj, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + if (NT_SUCCESS(Status)) + { + *pbIsHigh = BusIf.IsDeviceHighSpeed(BusIf.BusContext); + BusIf.InterfaceDereference(BusIf.BusContext); + } + IoFreeIrp(pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolPipeClear(PDEVICE_OBJECT pDevObj, HANDLE hPipe, bool fReset) +{ + if (!hPipe) + { + Log(("Resetting the control pipe??\n")); + return STATUS_SUCCESS; + } + USHORT u16Function = fReset ? URB_FUNCTION_RESET_PIPE : URB_FUNCTION_ABORT_PIPE; + PURB pUrb = VBoxUsbToolUrbAlloc(u16Function, sizeof (struct _URB_PIPE_REQUEST)); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed!\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + pUrb->UrbPipeRequest.PipeHandle = hPipe; + pUrb->UrbPipeRequest.Reserved = 0; + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, pUrb, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status) || !USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + AssertMsgFailed((__FUNCTION__": vboxUsbToolRequest failed with %x (%x)\n", Status, pUrb->UrbHeader.Status)); + } + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolCurrentFrame(PDEVICE_OBJECT pDevObj, PIRP pIrp, PULONG piFrame) +{ + struct _URB_GET_CURRENT_FRAME_NUMBER Urb; + Urb.Hdr.Function = URB_FUNCTION_GET_CURRENT_FRAME_NUMBER; + Urb.Hdr.Length = sizeof(Urb); + Urb.FrameNumber = (ULONG)-1; + + Assert(piFrame); + *piFrame = (ULONG)-1; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; + pSl->Parameters.Others.Argument1 = (PVOID)&Urb; + pSl->Parameters.Others.Argument2 = NULL; + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, (PURB)&Urb, RT_INDEFINITE_WAIT); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + { + *piFrame = Urb.FrameNumber; + } + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolDevUnconfigure(PDEVICE_OBJECT pDevObj) +{ + USHORT cbUrb = sizeof (struct _URB_SELECT_CONFIGURATION); + PURB pUrb = VBoxUsbToolUrbAlloc(URB_FUNCTION_SELECT_CONFIGURATION, cbUrb); + Assert(pUrb); + if (!pUrb) + return STATUS_INSUFFICIENT_RESOURCES; + + UsbBuildSelectConfigurationRequest(pUrb, (USHORT)cbUrb, NULL); + + NTSTATUS Status = VBoxUsbToolUrbPost(pDevObj, pUrb, RT_INDEFINITE_WAIT); + Assert(NT_SUCCESS(Status)); + + VBoxUsbToolUrbFree(pUrb); + + return Status; +} + +VBOXUSBTOOL_DECL(PIRP) VBoxUsbToolIoBuildAsyncInternalCtl(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2) +{ + PIRP pIrp = IoAllocateIrp(pDevObj->StackSize, FALSE); + Assert(pIrp); + if (!pIrp) + { + return NULL; + } + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = NULL; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->MinorFunction = 0; + pSl->Parameters.DeviceIoControl.IoControlCode = uCtl; + pSl->Parameters.Others.Argument1 = pvArg1; + pSl->Parameters.Others.Argument2 = pvArg2; + return pIrp; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSyncWithTimeout(PDEVICE_OBJECT pDevObj, ULONG uCtl, + void *pvArg1, void *pvArg2, ULONG dwTimeoutMs) +{ + /* since we're going to cancel the irp on timeout, we should allocate our own IRP rather than using the threaded one + * */ + PIRP pIrp = VBoxUsbToolIoBuildAsyncInternalCtl(pDevObj, uCtl, pvArg1, pvArg2); + if (!pIrp) + { + WARN(("VBoxUsbToolIoBuildAsyncInternalCtl failed")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + NTSTATUS Status = VBoxDrvToolIoPostSyncWithTimeout(pDevObj, pIrp, dwTimeoutMs); + + IoFreeIrp(pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendAsync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, + PKEVENT pEvent, PIO_STATUS_BLOCK pIoStatus) +{ + NTSTATUS Status; + PIRP pIrp; + PIO_STACK_LOCATION pSl; + Assert(KeGetCurrentIrql() == PASSIVE_LEVEL); + + pIrp = IoBuildDeviceIoControlRequest(uCtl, pDevObj, NULL, 0, NULL, 0, TRUE, pEvent, pIoStatus); + if (!pIrp) + { + WARN(("IoBuildDeviceIoControlRequest failed!!\n")); + pIoStatus->Status = STATUS_INSUFFICIENT_RESOURCES; + pIoStatus->Information = 0; + return STATUS_INSUFFICIENT_RESOURCES; + } + + /* Get the next stack location as that is used for the new irp */ + pSl = IoGetNextIrpStackLocation(pIrp); + pSl->Parameters.Others.Argument1 = pvArg1; + pSl->Parameters.Others.Argument2 = pvArg2; + + Status = IoCallDriver(pDevObj, pIrp); + + return Status; +} + +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2) +{ + IO_STATUS_BLOCK IoStatus = {0}; + KEVENT Event; + NTSTATUS Status; + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + LOG(("Sending sync Ctl pDevObj(0x%p), uCtl(0x%x), pvArg1(0x%p), pvArg2(0x%p)", pDevObj, uCtl, pvArg1, pvArg2)); + + Status = VBoxUsbToolIoInternalCtlSendAsync(pDevObj, uCtl, pvArg1, pvArg2, &Event, &IoStatus); + + if (Status == STATUS_PENDING) + { + LOG(("VBoxUsbToolIoInternalCtlSendAsync returned pending for pDevObj(0x%p)", pDevObj)); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatus.Status; + LOG(("Pending VBoxUsbToolIoInternalCtlSendAsync completed with Status (0x%x) for pDevObj(0x%p)", Status, pDevObj)); + } + else + { + LOG(("VBoxUsbToolIoInternalCtlSendAsync completed with Status (0x%x) for pDevObj(0x%p)", Status, pDevObj)); + } + + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h new file mode 100644 index 00000000..1f2433c4 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/cmn/VBoxUsbTool.h @@ -0,0 +1,79 @@ +/* $Id: VBoxUsbTool.h $ */ +/** @file + * Windows USB R0 Tooling. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxDrvTool.h" + +RT_C_DECLS_BEGIN +#pragma warning( disable : 4200 ) +#include <usbdi.h> +#pragma warning( default : 4200 ) +#include <usbdlib.h> + +RT_C_DECLS_END + +#if 0 +/* enable this in case we include this in a dll*/ +# ifdef IN_VBOXUSBTOOL +# define VBOXUSBTOOL_DECL(a_Type) DECLEXPORT(a_Type) +# else +# define VBOXUSBTOOL_DECL(a_Type) DECLIMPORT(a_Type) +# endif +#else +/*enable this in case we include this in a static lib*/ +# define VBOXUSBTOOL_DECL(a_Type) a_Type VBOXCALL +#endif + + +RT_C_DECLS_BEGIN + +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAlloc(USHORT u16Function, USHORT cbSize); +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbAllocZ(USHORT u16Function, USHORT cbSize); +VBOXUSBTOOL_DECL(PURB) VBoxUsbToolUrbReinit(PURB pUrb, USHORT cbSize, USHORT u16Function); +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolUrbFree(PURB pUrb); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolUrbPost(PDEVICE_OBJECT pDevObj, PURB pUrb, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDescriptor(PDEVICE_OBJECT pDevObj, void *pvBuffer, int cbBuffer, int Type, int iIndex, int LangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetStringDescriptor(PDEVICE_OBJECT pDevObj, char *pszResult, ULONG cbResult, int iIndex, int LangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetLangID(PDEVICE_OBJECT pDevObj, int *pLangId, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolGetDeviceSpeed(PDEVICE_OBJECT pDevObj, BOOLEAN *pbIsHigh); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolPipeClear(PDEVICE_OBJECT pDevObj, HANDLE hPipe, bool fReset); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolCurrentFrame(PDEVICE_OBJECT pDevObj, PIRP pIrp, PULONG piFrame); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolDevUnconfigure(PDEVICE_OBJECT pDevObj); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendAsync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, PKEVENT pEvent, PIO_STATUS_BLOCK pIoStatus); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSync(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2); +VBOXUSBTOOL_DECL(PIRP) VBoxUsbToolIoBuildAsyncInternalCtl(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2); +VBOXUSBTOOL_DECL(NTSTATUS) VBoxUsbToolIoInternalCtlSendSyncWithTimeout(PDEVICE_OBJECT pDevObj, ULONG uCtl, void *pvArg1, void *pvArg2, ULONG dwTimeoutMs); +VBOXUSBTOOL_DECL(VOID) VBoxUsbToolStringDescriptorToUnicodeString(PUSB_STRING_DESCRIPTOR pDr, PUNICODE_STRING pUnicode); + + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_cmn_VBoxUsbTool_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf new file mode 100644 index 00000000..d8daf7e3 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUSB.inf @@ -0,0 +1,81 @@ +; $Id: VBoxUSB.inf $ +;; @file +; VBox host drivers - USB drivers - Win32 USB device +; + +; +; Copyright (C) 2011-2020 Oracle Corporation +; +; 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. +; +; 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. +; + +[Version] +Signature="$Windows NT$" +Class=USB +ClassGUID={36FC9E60-C465-11CF-8056-444553540000} +provider=%ORACLE% +;edit-DriverVer=08/26/2008,2.00.0000 +;cat CatalogFile=VBoxUSB.cat + +[SourceDisksNames] +1=%Disk_Description%,,, + +[SourceDisksFiles] +VBoxUSB.sys = 1 + +[Manufacturer] +;x86 %MfgName%=ORACLE +;amd64 %MfgName%=ORACLE, NTamd64 + +;x86 [ORACLE] +;amd64 [ORACLE.NTamd64] +%USB\VID_80EE&PID_CAFE.DeviceDesc%=VBoxUSB.Dev, USB\VID_80EE&PID_CAFE + +[DestinationDirs] +VBoxUSB.Files.Ext = 10,System32\Drivers + +[VBoxUSB.Dev.NT] +CopyFiles=VBoxUSB.Files.Ext + +[VBoxUSB.Dev.NT.Services] +Addservice = VBoxUSB, 0x00000002, VBoxUSB.AddService + +[VBoxUSB.AddService] +DisplayName = %VBoxUSB.SvcDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %10%\System32\Drivers\VBoxUSB.sys +AddReg = VBoxUSB.AddReg +LoadOrderGroup = Base + +[VBoxUSB.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,VBoxUSB.sys + +[VBoxUSB.Files.Ext] +VBoxUSB.sys + +;---------------------------------------------------------------; + +[Strings] +ORACLE="Oracle Corporation" +MfgName="Oracle Corporation" +Disk_Description="VBoxUSB Installation Disk" +USB\VID_80EE&PID_CAFE.DeviceDesc="VirtualBox USB" +VBoxUSB.SvcDesc="VirtualBox USB" diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h new file mode 100644 index 00000000..e90ea3aa --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbCmn.h @@ -0,0 +1,93 @@ +/* $Id: VBoxUsbCmn.h $ */ +/** @file + * VBoxUsmCmn.h - USB device. Common defs + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "../cmn/VBoxDrvTool.h" +#include "../cmn/VBoxUsbTool.h" + +#include <iprt/cdefs.h> +#include <iprt/asm.h> + +#include <VBox/usblib-win.h> + +#define VBOXUSB_CFG_IDLE_TIME_MS 5000 + +typedef struct VBOXUSBDEV_EXT *PVBOXUSBDEV_EXT; + +RT_C_DECLS_BEGIN + +#ifdef _WIN64 +#define DECLSPEC_USBIMPORT DECLSPEC_IMPORT +#else +#define DECLSPEC_USBIMPORT + +#define USBD_ParseDescriptors _USBD_ParseDescriptors +#define USBD_ParseConfigurationDescriptorEx _USBD_ParseConfigurationDescriptorEx +#define USBD_CreateConfigurationRequestEx _USBD_CreateConfigurationRequestEx +#endif + +DECLSPEC_USBIMPORT PUSB_COMMON_DESCRIPTOR +USBD_ParseDescriptors( + IN PVOID DescriptorBuffer, + IN ULONG TotalLength, + IN PVOID StartPosition, + IN LONG DescriptorType + ); + +DECLSPEC_USBIMPORT PUSB_INTERFACE_DESCRIPTOR +USBD_ParseConfigurationDescriptorEx( + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, + IN PVOID StartPosition, + IN LONG InterfaceNumber, + IN LONG AlternateSetting, + IN LONG InterfaceClass, + IN LONG InterfaceSubClass, + IN LONG InterfaceProtocol + ); + +DECLSPEC_USBIMPORT PURB +USBD_CreateConfigurationRequestEx( + IN PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor, + IN PUSBD_INTERFACE_LIST_ENTRY InterfaceList + ); + +RT_C_DECLS_END + +DECLHIDDEN(PVOID) vboxUsbMemAlloc(SIZE_T cbBytes); +DECLHIDDEN(PVOID) vboxUsbMemAllocZ(SIZE_T cbBytes); +DECLHIDDEN(VOID) vboxUsbMemFree(PVOID pvMem); + +#include "VBoxUsbRt.h" +#include "VBoxUsbPnP.h" +#include "VBoxUsbPwr.h" +#include "VBoxUsbDev.h" + + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbCmn_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp new file mode 100644 index 00000000..a2192df6 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.cpp @@ -0,0 +1,347 @@ +/* $Id: VBoxUsbDev.cpp $ */ +/** @file + * VBoxUsbDev.cpp - USB device. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxUsbCmn.h" +#include <iprt/assert.h> +#include <VBox/log.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXUSB_MEMTAG 'bUBV' + + + +DECLHIDDEN(PVOID) vboxUsbMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSB_MEMTAG); + Assert(pvMem); + return pvMem; +} + +DECLHIDDEN(PVOID) vboxUsbMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = vboxUsbMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +DECLHIDDEN(VOID) vboxUsbMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSB_MEMTAG); +} + +VBOXUSB_GLOBALS g_VBoxUsbGlobals = {0}; + +static NTSTATUS vboxUsbDdiAddDevice(PDRIVER_OBJECT pDriverObject, + PDEVICE_OBJECT pPDO) +{ + PDEVICE_OBJECT pFDO = NULL; + NTSTATUS Status = IoCreateDevice(pDriverObject, + sizeof (VBOXUSBDEV_EXT), + NULL, /* IN PUNICODE_STRING pDeviceName OPTIONAL */ + FILE_DEVICE_UNKNOWN, /* IN DEVICE_TYPE DeviceType */ + FILE_AUTOGENERATED_DEVICE_NAME, /* IN ULONG DeviceCharacteristics */ + FALSE, /* IN BOOLEAN fExclusive */ + &pFDO); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pFDO->DeviceExtension; + /* init Device Object bits */ + pFDO->Flags |= DO_DIRECT_IO; + if (pPDO->Flags & DO_POWER_PAGABLE) + pFDO->Flags |= DO_POWER_PAGABLE; + + + /* now init our state bits */ + + pDevExt->cHandles = 0; + + pDevExt->pFDO = pFDO; + pDevExt->pPDO = pPDO; + pDevExt->pLowerDO = IoAttachDeviceToDeviceStack(pFDO, pPDO); + Assert(pDevExt->pLowerDO); + if (pDevExt->pLowerDO) + { + vboxUsbDdiStateInit(pDevExt); + Status = vboxUsbRtInit(pDevExt); + if (Status == STATUS_SUCCESS) + { + /* we're done! */ + pFDO->Flags &= ~DO_DEVICE_INITIALIZING; + return STATUS_SUCCESS; + } + + IoDetachDevice(pDevExt->pLowerDO); + } + else + Status = STATUS_NO_SUCH_DEVICE; + + IoDeleteDevice(pFDO); + } + + return Status; +} + +static VOID vboxUsbDdiUnload(PDRIVER_OBJECT pDriverObject) +{ + RT_NOREF1(pDriverObject); + LogRel(("VBoxUsb::DriverUnload. Built Date (%s) Time (%s)\n", __DATE__, __TIME__)); + VBoxDrvToolStrFree(&g_VBoxUsbGlobals.RegPath); + + vboxUsbRtGlobalsTerm(); + + PRTLOGGER pLogger = RTLogRelSetDefaultInstance(NULL); + if (pLogger) + { + RTLogDestroy(pLogger); + } + pLogger = RTLogSetDefaultInstance(NULL); + if (pLogger) + { + RTLogDestroy(pLogger); + } +} + +static NTSTATUS vboxUsbDispatchCreate(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + NTSTATUS Status = STATUS_INVALID_HANDLE; + do + { + if (vboxUsbPnPStateGet(pDevExt) != ENMVBOXUSB_PNPSTATE_STARTED) + { + Status = STATUS_INVALID_DEVICE_STATE; + break; + } + + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + if (!pFObj) + { + Status = STATUS_INVALID_PARAMETER; + break; + } + + pFObj->FsContext = NULL; + + if (pFObj->FileName.Length) + { + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtCreate(pDevExt, pIrp); + if (!NT_SUCCESS(Status)) + { + AssertFailed(); + break; + } + + ASMAtomicIncU32(&pDevExt->cHandles); + Status = STATUS_SUCCESS; + break; + } while (0); + + Status = VBoxDrvToolIoComplete(pIrp, Status, 0); + return Status; +} + +static NTSTATUS vboxUsbDispatchClose(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + NTSTATUS Status = STATUS_SUCCESS; +#ifdef VBOX_STRICT + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + Assert(pFObj); + Assert(!pFObj->FileName.Length); +#endif + Status = vboxUsbRtClose(pDevExt, pIrp); + if (NT_SUCCESS(Status)) + { + ASMAtomicDecU32(&pDevExt->cHandles); + } + else + { + AssertFailed(); + } + Status = VBoxDrvToolIoComplete(pIrp, Status, 0); + return Status; +} + +static NTSTATUS vboxUsbDispatchDeviceControl(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (vboxUsbDdiStateRetainIfStarted(pDevExt)) + return vboxUsbRtDispatch(pDevExt, pIrp); + return VBoxDrvToolIoComplete(pIrp, STATUS_INVALID_DEVICE_STATE, 0); +} + +static NTSTATUS vboxUsbDispatchCleanup(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + RT_NOREF1(pDeviceObject); + return VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); +} + +static NTSTATUS vboxUsbDevAccessDeviedDispatchStub(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + { + VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + return STATUS_DELETE_PENDING; + } + + NTSTATUS Status = VBoxDrvToolIoComplete(pIrp, STATUS_ACCESS_DENIED, 0); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbDispatchSystemControl(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + { + VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + return STATUS_DELETE_PENDING; + } + + IoSkipCurrentIrpStackLocation(pIrp); + + NTSTATUS Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbDispatchRead(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ +#ifdef DEBUG_misha + AssertFailed(); +#endif + return vboxUsbDevAccessDeviedDispatchStub(pDeviceObject, pIrp); +} + +static NTSTATUS vboxUsbDispatchWrite(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ +#ifdef DEBUG_misha + AssertFailed(); +#endif + return vboxUsbDevAccessDeviedDispatchStub(pDeviceObject, pIrp); +} + +RT_C_DECLS_BEGIN + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath); + +RT_C_DECLS_END + +NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath) +{ + LogRel(("VBoxUsb::DriverEntry. Built Date (%s) Time (%s)\n", __DATE__, __TIME__)); + + NTSTATUS Status = vboxUsbRtGlobalsInit(); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + Status = VBoxDrvToolStrCopy(&g_VBoxUsbGlobals.RegPath, pRegistryPath); + Assert(Status == STATUS_SUCCESS); + if (Status == STATUS_SUCCESS) + { + g_VBoxUsbGlobals.pDrvObj = pDriverObject; + + pDriverObject->DriverExtension->AddDevice = vboxUsbDdiAddDevice; + + pDriverObject->DriverUnload = vboxUsbDdiUnload; + + pDriverObject->MajorFunction[IRP_MJ_CREATE] = vboxUsbDispatchCreate; + pDriverObject->MajorFunction[IRP_MJ_CLOSE] = vboxUsbDispatchClose; + pDriverObject->MajorFunction[IRP_MJ_READ] = vboxUsbDispatchRead; + pDriverObject->MajorFunction[IRP_MJ_WRITE] = vboxUsbDispatchWrite; + pDriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = vboxUsbDispatchDeviceControl; + pDriverObject->MajorFunction[IRP_MJ_CLEANUP] = vboxUsbDispatchCleanup; + pDriverObject->MajorFunction[IRP_MJ_POWER] = vboxUsbDispatchPower; + pDriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL] = vboxUsbDispatchSystemControl; + pDriverObject->MajorFunction[IRP_MJ_PNP] = vboxUsbDispatchPnP; + + return STATUS_SUCCESS; + } + vboxUsbRtGlobalsTerm(); + } + + LogRel(("VBoxUsb::DriverEntry. failed with Status (0x%x)\n", Status)); + + return Status; +} + +#ifdef VBOX_STRICT +DECLHIDDEN(VOID) vboxUsbPnPStateGbgChange(ENMVBOXUSB_PNPSTATE enmOldState, ENMVBOXUSB_PNPSTATE enmNewState) +{ + /* *ensure the state change is valid */ + switch (enmNewState) + { + case ENMVBOXUSB_PNPSTATE_STARTED: + Assert( enmOldState == ENMVBOXUSB_PNPSTATE_START_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_STOPPED + || enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING); + break; + case ENMVBOXUSB_PNPSTATE_STOP_PENDING: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_STOPPED: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING); + break; + case ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_REMOVE_PENDING: + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STARTED); + break; + case ENMVBOXUSB_PNPSTATE_REMOVED: + Assert( enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED); + break; + default: + AssertFailed(); + break; + } + +} +#endif diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h new file mode 100644 index 00000000..d1bfa44c --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.h @@ -0,0 +1,208 @@ +/* $Id: VBoxUsbDev.h $ */ +/** @file + * VBoxUsbDev.h - USB device. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbCmn.h" +#include <VBox/cdefs.h> +#include <iprt/assert.h> + +typedef struct VBOXUSB_GLOBALS +{ + PDRIVER_OBJECT pDrvObj; + UNICODE_STRING RegPath; + VBOXUSBRT_IDC RtIdc; +} VBOXUSB_GLOBALS, *PVBOXUSB_GLOBALS; + +extern VBOXUSB_GLOBALS g_VBoxUsbGlobals; + +/* pnp state decls */ +typedef enum +{ + ENMVBOXUSB_PNPSTATE_UNKNOWN = 0, + ENMVBOXUSB_PNPSTATE_START_PENDING, + ENMVBOXUSB_PNPSTATE_STARTED, + ENMVBOXUSB_PNPSTATE_STOP_PENDING, + ENMVBOXUSB_PNPSTATE_STOPPED, + ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED, + ENMVBOXUSB_PNPSTATE_REMOVE_PENDING, + ENMVBOXUSB_PNPSTATE_REMOVED, + ENMVBOXUSB_PNPSTATE_FORSEDWORD = 0x8fffffff +} ENMVBOXUSB_PNPSTATE; +AssertCompile(sizeof (ENMVBOXUSB_PNPSTATE) == sizeof (uint32_t)); + +#ifdef VBOX_STRICT +DECLHIDDEN(VOID) vboxUsbPnPStateGbgChange(ENMVBOXUSB_PNPSTATE enmOld, ENMVBOXUSB_PNPSTATE enmNew); +# define VBOXUSB_PNP_GBG_STATE_CHANGE(_old, _new) vboxUsbPnPStateGbgChange((_old), (_new)) +#else +# define VBOXUSB_PNP_GBG_STATE_CHANGE(_old, _new) do { } while (0) +#endif + + +typedef struct VBOXUSB_PNPSTATE +{ + /* Current state */ + volatile ENMVBOXUSB_PNPSTATE Curr; + /* Previous state, used to restore state info on cancell stop device */ + ENMVBOXUSB_PNPSTATE Prev; +} VBOXUSB_PNPSTATE, *PVBOXUSB_PNPSTATE; + +typedef struct VBOXUSBDEV_DDISTATE +{ + /* Lock */ + KSPIN_LOCK Lock; + VBOXDRVTOOL_REF Ref; + VBOXUSB_PNPSTATE PnPState; + VBOXUSB_PWRSTATE PwrState; + /* current dev caps */ + DEVICE_CAPABILITIES DevCaps; +} VBOXUSBDEV_DDISTATE, *PVBOXUSBDEV_DDISTATE; + +typedef struct VBOXUSBDEV_EXT +{ + PDEVICE_OBJECT pFDO; + PDEVICE_OBJECT pPDO; + PDEVICE_OBJECT pLowerDO; + + VBOXUSBDEV_DDISTATE DdiState; + + uint32_t cHandles; + + VBOXUSB_RT Rt; + +} VBOXUSBDEV_EXT, *PVBOXUSBDEV_EXT; + +/* pnp state api */ +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateGet(PVBOXUSBDEV_EXT pDevExt) +{ + return (ENMVBOXUSB_PNPSTATE)ASMAtomicUoReadU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr); +} + +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateSet(PVBOXUSBDEV_EXT pDevExt, ENMVBOXUSB_PNPSTATE enmState) +{ + KIRQL Irql; + ENMVBOXUSB_PNPSTATE enmOldState; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &Irql); + pDevExt->DdiState.PnPState.Prev = (ENMVBOXUSB_PNPSTATE)ASMAtomicUoReadU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr); + ASMAtomicWriteU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr, (uint32_t)enmState); + pDevExt->DdiState.PnPState.Curr = enmState; + enmOldState = pDevExt->DdiState.PnPState.Prev; + KeReleaseSpinLock(&pDevExt->DdiState.Lock, Irql); + VBOXUSB_PNP_GBG_STATE_CHANGE(enmOldState, enmState); + return enmState; +} + +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbPnPStateRestore(PVBOXUSBDEV_EXT pDevExt) +{ + ENMVBOXUSB_PNPSTATE enmNewState, enmOldState; + KIRQL Irql; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &Irql); + enmOldState = pDevExt->DdiState.PnPState.Curr; + enmNewState = pDevExt->DdiState.PnPState.Prev; + ASMAtomicWriteU32((volatile uint32_t*)&pDevExt->DdiState.PnPState.Curr, (uint32_t)pDevExt->DdiState.PnPState.Prev); + KeReleaseSpinLock(&pDevExt->DdiState.Lock, Irql); + VBOXUSB_PNP_GBG_STATE_CHANGE(enmOldState, enmNewState); + Assert(enmNewState == ENMVBOXUSB_PNPSTATE_STARTED); + Assert(enmOldState == ENMVBOXUSB_PNPSTATE_STOP_PENDING + || enmOldState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING); + return enmNewState; +} + +DECLINLINE(VOID) vboxUsbPnPStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + pDevExt->DdiState.PnPState.Curr = pDevExt->DdiState.PnPState.Prev = ENMVBOXUSB_PNPSTATE_START_PENDING; +} + +DECLINLINE(VOID) vboxUsbDdiStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + KeInitializeSpinLock(&pDevExt->DdiState.Lock); + VBoxDrvToolRefInit(&pDevExt->DdiState.Ref); + vboxUsbPwrStateInit(pDevExt); + vboxUsbPnPStateInit(pDevExt); +} + +DECLINLINE(bool) vboxUsbDdiStateRetainIfStarted(PVBOXUSBDEV_EXT pDevExt) +{ + KIRQL oldIrql; + bool bRetained = true; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &oldIrql); + if (vboxUsbPnPStateGet(pDevExt) == ENMVBOXUSB_PNPSTATE_STARTED) + { + VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); + } + else + { + bRetained = false; + } + KeReleaseSpinLock(&pDevExt->DdiState.Lock, oldIrql); + return bRetained; +} + +/* if device is removed - does nothing and returns zero, + * otherwise increments a ref counter and returns the current pnp state + * NOTE: never returns ENMVBOXUSB_PNPSTATE_REMOVED + * */ +DECLINLINE(ENMVBOXUSB_PNPSTATE) vboxUsbDdiStateRetainIfNotRemoved(PVBOXUSBDEV_EXT pDevExt) +{ + KIRQL oldIrql; + ENMVBOXUSB_PNPSTATE enmState; + KeAcquireSpinLock(&pDevExt->DdiState.Lock, &oldIrql); + enmState = vboxUsbPnPStateGet(pDevExt); + if (enmState != ENMVBOXUSB_PNPSTATE_REMOVED) + { + VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); + } + KeReleaseSpinLock(&pDevExt->DdiState.Lock, oldIrql); + return enmState != ENMVBOXUSB_PNPSTATE_REMOVED ? enmState : (ENMVBOXUSB_PNPSTATE)0; +} + +DECLINLINE(uint32_t) vboxUsbDdiStateRetain(PVBOXUSBDEV_EXT pDevExt) +{ + return VBoxDrvToolRefRetain(&pDevExt->DdiState.Ref); +} + +DECLINLINE(uint32_t) vboxUsbDdiStateRelease(PVBOXUSBDEV_EXT pDevExt) +{ + return VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); +} + +DECLINLINE(VOID) vboxUsbDdiStateReleaseAndWaitCompleted(PVBOXUSBDEV_EXT pDevExt) +{ + VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); + VBoxDrvToolRefWaitEqual(&pDevExt->DdiState.Ref, 1); +} + +DECLINLINE(VOID) vboxUsbDdiStateReleaseAndWaitRemoved(PVBOXUSBDEV_EXT pDevExt) +{ + VBoxDrvToolRefRelease(&pDevExt->DdiState.Ref); + VBoxDrvToolRefWaitEqual(&pDevExt->DdiState.Ref, 0); +} + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbDev_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc new file mode 100644 index 00000000..d012e97b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbDev.rc @@ -0,0 +1,60 @@ +/* $Id: VBoxUsbDev.rc $ */ +/** @file + * VBoxUSB - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox USB Driver\0" + VALUE "InternalName", "VBoxUSB\0" + VALUE "OriginalFilename", "VBoxUSB.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp new file mode 100644 index 00000000..c3dba580 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.cpp @@ -0,0 +1,264 @@ +/* $Id: VBoxUsbPnP.cpp $ */ +/** @file + * USB PnP Handling + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#include "VBoxUsbCmn.h" + +static NTSTATUS vboxUsbPnPMnStartDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + IoCopyCurrentIrpStackLocationToNext(pIrp); + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbRtStart(pDevExt); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STARTED); + } + } + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STOP_PENDING); + + vboxUsbDdiStateReleaseAndWaitCompleted(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + return IoCallDriver(pDevExt->pLowerDO, pIrp); +} + +static NTSTATUS vboxUsbPnPMnStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_STOPPED); + + vboxUsbRtClear(pDevExt); + + NTSTATUS Status = VBoxUsbToolDevUnconfigure(pDevExt->pLowerDO); + Assert(NT_SUCCESS(Status)); + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPnPMnCancelStopDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + + IoCopyCurrentIrpStackLocationToNext(pIrp); + Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + if (NT_SUCCESS(Status) && enmState == ENMVBOXUSB_PNPSTATE_STOP_PENDING) + { + vboxUsbPnPStateRestore(pDevExt); + } + + Status = STATUS_SUCCESS; + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_REMOVE_PENDING); + + vboxUsbDdiStateReleaseAndWaitCompleted(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + return IoCallDriver(pDevExt->pLowerDO, pIrp); +} + +static NTSTATUS vboxUsbPnPRmDev(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = vboxUsbRtRm(pDevExt); + Assert(Status == STATUS_SUCCESS); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + if (enmState != ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED) + { + Status = vboxUsbPnPRmDev(pDevExt); + Assert(Status == STATUS_SUCCESS); + } + + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_REMOVED); + + vboxUsbDdiStateRelease(pDevExt); + + vboxUsbDdiStateReleaseAndWaitRemoved(pDevExt); + + vboxUsbRtClear(pDevExt); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + IoDetachDevice(pDevExt->pLowerDO); + IoDeleteDevice(pDevExt->pFDO); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnCancelRemoveDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + ENMVBOXUSB_PNPSTATE enmState = vboxUsbPnPStateGet(pDevExt); + NTSTATUS Status = STATUS_SUCCESS; + IoCopyCurrentIrpStackLocationToNext(pIrp); + + Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + + if (NT_SUCCESS(Status) && + enmState == ENMVBOXUSB_PNPSTATE_REMOVE_PENDING) + { + vboxUsbPnPStateRestore(pDevExt); + } + + Status = STATUS_SUCCESS; + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnSurpriseRemoval(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + vboxUsbPnPStateSet(pDevExt, ENMVBOXUSB_PNPSTATE_SURPRISE_REMOVED); + + NTSTATUS Status = vboxUsbPnPRmDev(pDevExt); + Assert(Status == STATUS_SUCCESS); + + pIrp->IoStatus.Status = STATUS_SUCCESS; + pIrp->IoStatus.Information = 0; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnQueryCapabilities(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PDEVICE_CAPABILITIES pDevCaps = pSl->Parameters.DeviceCapabilities.Capabilities; + + if (pDevCaps->Version < 1 || pDevCaps->Size < sizeof (*pDevCaps)) + { + AssertFailed(); + /** @todo return more appropriate status ?? */ + return STATUS_UNSUCCESSFUL; + } + + pDevCaps->SurpriseRemovalOK = TRUE; + pIrp->IoStatus.Status = STATUS_SUCCESS; + + IoCopyCurrentIrpStackLocationToNext(pIrp); + NTSTATUS Status = VBoxDrvToolIoPostSync(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + { + pDevCaps->SurpriseRemovalOK = 1; + pDevExt->DdiState.DevCaps = *pDevCaps; + } + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPnPMnDefault(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + NTSTATUS Status; + IoSkipCurrentIrpStackLocation(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPnP(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + if (!vboxUsbDdiStateRetainIfNotRemoved(pDevExt)) + return VBoxDrvToolIoComplete(pIrp, STATUS_DELETE_PENDING, 0); + + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_START_DEVICE: + return vboxUsbPnPMnStartDevice(pDevExt, pIrp); + + case IRP_MN_QUERY_STOP_DEVICE: + return vboxUsbPnPMnQueryStopDevice(pDevExt, pIrp); + + case IRP_MN_STOP_DEVICE: + return vboxUsbPnPMnStopDevice(pDevExt, pIrp); + + case IRP_MN_CANCEL_STOP_DEVICE: + return vboxUsbPnPMnCancelStopDevice(pDevExt, pIrp); + + case IRP_MN_QUERY_REMOVE_DEVICE: + return vboxUsbPnPMnQueryRemoveDevice(pDevExt, pIrp); + + case IRP_MN_REMOVE_DEVICE: + return vboxUsbPnPMnRemoveDevice(pDevExt, pIrp); + + case IRP_MN_CANCEL_REMOVE_DEVICE: + return vboxUsbPnPMnCancelRemoveDevice(pDevExt, pIrp); + + case IRP_MN_SURPRISE_REMOVAL: + return vboxUsbPnPMnSurpriseRemoval(pDevExt, pIrp); + + case IRP_MN_QUERY_CAPABILITIES: + return vboxUsbPnPMnQueryCapabilities(pDevExt, pIrp); + + default: + return vboxUsbPnPMnDefault(pDevExt, pIrp); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h new file mode 100644 index 00000000..280389f0 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPnP.h @@ -0,0 +1,36 @@ +/* $Id: VBoxUsbPnP.h $ */ +/** @file + * USB PnP Handling + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif +#include "VBoxUsbCmn.h" + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPnP(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPnP_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp new file mode 100644 index 00000000..b335fdcd --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.cpp @@ -0,0 +1,417 @@ +/* $Id: VBoxUsbPwr.cpp $ */ +/** @file + * USB Power state Handling + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#include "VBoxUsbCmn.h" + +#include <iprt/assert.h> + +DECLHIDDEN(VOID) vboxUsbPwrStateInit(PVBOXUSBDEV_EXT pDevExt) +{ + POWER_STATE PowerState; + PowerState.SystemState = PowerSystemWorking; + PowerState.DeviceState = PowerDeviceD0; + PoSetPowerState(pDevExt->pFDO, DevicePowerState, PowerState); + pDevExt->DdiState.PwrState.PowerState = PowerState; + pDevExt->DdiState.PwrState.PowerDownLevel = PowerDeviceUnspecified; +} + +static NTSTATUS vboxUsbPwrMnDefault(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + NTSTATUS Status; + PoStartNextPowerIrp(pIrp); + IoSkipCurrentIrpStackLocation(pIrp); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status) || Status == STATUS_NOT_SUPPORTED); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbPwrMnPowerSequence(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); +} + +typedef struct VBOXUSB_PWRDEV_CTX +{ + PVBOXUSBDEV_EXT pDevExt; + PIRP pIrp; +} VBOXUSB_PWRDEV_CTX, *PVBOXUSB_PWRDEV_CTX; + +static VOID vboxUsbPwrIoDeviceCompletion(IN PDEVICE_OBJECT pDeviceObject, + IN UCHAR MinorFunction, + IN POWER_STATE PowerState, + IN PVOID pvContext, + IN PIO_STATUS_BLOCK pIoStatus) +{ + RT_NOREF3(pDeviceObject, MinorFunction, PowerState); + PVBOXUSB_PWRDEV_CTX pDevCtx = (PVBOXUSB_PWRDEV_CTX)pvContext; + PVBOXUSBDEV_EXT pDevExt = pDevCtx->pDevExt; + PIRP pIrp = pDevCtx->pIrp; + pIrp->IoStatus.Status = pIoStatus->Status; + pIrp->IoStatus.Information = 0; + + PoStartNextPowerIrp(pIrp); + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + vboxUsbDdiStateRelease(pDevExt); + + vboxUsbMemFree(pDevCtx); +} + +static NTSTATUS vboxUsbPwrIoRequestDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + POWER_STATE PwrState; + PwrState.SystemState = pSl->Parameters.Power.State.SystemState; + PwrState.DeviceState = pDevExt->DdiState.DevCaps.DeviceState[PwrState.SystemState]; + + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PVBOXUSB_PWRDEV_CTX pDevCtx = (PVBOXUSB_PWRDEV_CTX)vboxUsbMemAlloc(sizeof (*pDevCtx)); + Assert(pDevCtx); + if (pDevCtx) + { + pDevCtx->pDevExt = pDevExt; + pDevCtx->pIrp = pIrp; + + Status = PoRequestPowerIrp(pDevExt->pPDO, pSl->MinorFunction, PwrState, + vboxUsbPwrIoDeviceCompletion, pDevCtx, NULL); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return STATUS_MORE_PROCESSING_REQUIRED; + + vboxUsbMemFree(pDevCtx); + } + + PoStartNextPowerIrp(pIrp); + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + vboxUsbDdiStateRelease(pDevExt); + + /* the "real" Status is stored in pIrp->IoStatus.Status, + * return success here to complete the Io */ + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostSysCompletion(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF1(pDevObj); + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pvContext; + NTSTATUS Status = pIrp->IoStatus.Status; + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_SET_POWER: + pDevExt->DdiState.PwrState.PowerState.SystemState = pSl->Parameters.Power.State.SystemState; + break; + + default: + break; + } + + return vboxUsbPwrIoRequestDev(pDevExt, pIrp); + } + + PoStartNextPowerIrp(pIrp); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + IoMarkIrpPending(pIrp); + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostSysCompletion, pDevExt, TRUE, TRUE, TRUE); + NTSTATUS Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); NOREF(Status); + return STATUS_PENDING; +} + +static NTSTATUS vboxUsbPwrQueryPowerSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + /*PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + SYSTEM_POWER_STATE enmSysPState = pSl->Parameters.Power.State.SystemState;*/ + + return vboxUsbPwrIoPostSys(pDevExt, pIrp); +} + +static NTSTATUS vboxUsbPwrIoPostDevCompletion(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp, IN PVOID pvContext) +{ + RT_NOREF1(pDevObj); + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pvContext; + + if (pIrp->PendingReturned) + IoMarkIrpPending(pIrp); + + NTSTATUS Status = pIrp->IoStatus.Status; + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->MinorFunction) + { + case IRP_MN_SET_POWER: + pDevExt->DdiState.PwrState.PowerState.DeviceState = pSl->Parameters.Power.State.DeviceState; + PoSetPowerState(pDevExt->pFDO, DevicePowerState, pSl->Parameters.Power.State); + break; + + default: + break; + } + } + + PoStartNextPowerIrp(pIrp); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbPwrIoPostDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + IoMarkIrpPending(pIrp); + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostDevCompletion, pDevExt, TRUE, TRUE, TRUE); + NTSTATUS Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + Assert(NT_SUCCESS(Status)); RT_NOREF_PV(Status); + return STATUS_PENDING; +} + +typedef struct VBOXUSB_IOASYNC_CTX +{ + PIO_WORKITEM pWrkItem; + PIRP pIrp; +} VBOXUSB_IOASYNC_CTX, *PVBOXUSB_IOASYNC_CTX; + +static VOID vboxUsbPwrIoWaitCompletionAndPostAsyncWorker(IN PDEVICE_OBJECT pDeviceObject, IN PVOID pvContext) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + PVBOXUSB_IOASYNC_CTX pCtx = (PVBOXUSB_IOASYNC_CTX)pvContext; + PIRP pIrp = pCtx->pIrp; + + vboxUsbPwrIoPostDev(pDevExt, pIrp); + + IoFreeWorkItem(pCtx->pWrkItem); + vboxUsbMemFree(pCtx); +} + +static NTSTATUS vboxUsbPwrIoWaitCompletionAndPostAsync(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PVBOXUSB_IOASYNC_CTX pCtx = (PVBOXUSB_IOASYNC_CTX)vboxUsbMemAlloc(sizeof (*pCtx)); + Assert(pCtx); + if (pCtx) + { + PIO_WORKITEM pWrkItem = IoAllocateWorkItem(pDevExt->pFDO); + Assert(pWrkItem); + if (pWrkItem) + { + pCtx->pWrkItem = pWrkItem; + pCtx->pIrp = pIrp; + IoMarkIrpPending(pIrp); + IoQueueWorkItem(pWrkItem, vboxUsbPwrIoWaitCompletionAndPostAsyncWorker, DelayedWorkQueue, pCtx); + return STATUS_PENDING; + } + vboxUsbMemFree(pCtx); + } + return Status; +} + +static NTSTATUS vboxUsbPwrQueryPowerDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + DEVICE_POWER_STATE enmDevPState = pSl->Parameters.Power.State.DeviceState; + NTSTATUS Status = STATUS_SUCCESS; + + if (enmDevPState >= pDevExt->DdiState.PwrState.PowerState.DeviceState) + { + Status = vboxUsbPwrIoWaitCompletionAndPostAsync(pDevExt, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return Status; + } + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + + PoStartNextPowerIrp(pIrp); + + if (NT_SUCCESS(Status)) + { + IoSkipCurrentIrpStackLocation(pIrp); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + } + else + { + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + + vboxUsbDdiStateRelease(pDevExt); + + return Status; +} + +static NTSTATUS vboxUsbPwrMnQueryPower(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.Power.Type) + { + case SystemPowerState: + return vboxUsbPwrQueryPowerSys(pDevExt, pIrp); + + case DevicePowerState: + return vboxUsbPwrQueryPowerDev(pDevExt, pIrp); + + default: + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + + } +} + +static NTSTATUS vboxUsbPwrSetPowerSys(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + /*PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + SYSTEM_POWER_STATE enmSysPState = pSl->Parameters.Power.State.SystemState;*/ + + return vboxUsbPwrIoPostSys(pDevExt, pIrp); +} + +static NTSTATUS vboxUsbPwrSetPowerDev(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + DEVICE_POWER_STATE enmDevPState = pSl->Parameters.Power.State.DeviceState; + DEVICE_POWER_STATE enmCurDevPState = pDevExt->DdiState.PwrState.PowerState.DeviceState; + NTSTATUS Status = STATUS_SUCCESS; + + if (enmDevPState > enmCurDevPState && enmCurDevPState == PowerDeviceD0) + { + Status = vboxUsbPwrIoWaitCompletionAndPostAsync(pDevExt, pIrp); + Assert(NT_SUCCESS(Status)); + if (NT_SUCCESS(Status)) + return Status; + } + + PoStartNextPowerIrp(pIrp); + + if (NT_SUCCESS(Status)) + { + IoCopyCurrentIrpStackLocationToNext(pIrp); + IoSetCompletionRoutine(pIrp, vboxUsbPwrIoPostDevCompletion, pDevExt, TRUE, TRUE, TRUE); + Status = PoCallDriver(pDevExt->pLowerDO, pIrp); + } + else + { + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + vboxUsbDdiStateRelease(pDevExt); + } + + return Status; +} + + +static NTSTATUS vboxUsbPwrMnSetPower(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.Power.Type) + { + case SystemPowerState: + return vboxUsbPwrSetPowerSys(pDevExt, pIrp); + + case DevicePowerState: + return vboxUsbPwrSetPowerDev(pDevExt, pIrp); + + default: + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + } +} + +static NTSTATUS vboxUsbPwrMnWaitWake(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); +} + + +static NTSTATUS vboxUsbPwrDispatch(IN PVBOXUSBDEV_EXT pDevExt, IN PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + + switch (pSl->MinorFunction) + { + case IRP_MN_POWER_SEQUENCE: + return vboxUsbPwrMnPowerSequence(pDevExt, pIrp); + + case IRP_MN_QUERY_POWER: + return vboxUsbPwrMnQueryPower(pDevExt, pIrp); + + case IRP_MN_SET_POWER: + return vboxUsbPwrMnSetPower(pDevExt, pIrp); + + case IRP_MN_WAIT_WAKE: + return vboxUsbPwrMnWaitWake(pDevExt, pIrp); + + default: +// AssertFailed(); + return vboxUsbPwrMnDefault(pDevExt, pIrp); + } +} + +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPower(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp) +{ + PVBOXUSBDEV_EXT pDevExt = (PVBOXUSBDEV_EXT)pDeviceObject->DeviceExtension; + ENMVBOXUSB_PNPSTATE enmState = vboxUsbDdiStateRetainIfNotRemoved(pDevExt); + switch (enmState) + { + case ENMVBOXUSB_PNPSTATE_REMOVED: + PoStartNextPowerIrp(pIrp); + + pIrp->IoStatus.Status = STATUS_DELETE_PENDING; + pIrp->IoStatus.Information = 0; + + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + + vboxUsbDdiStateRelease(pDevExt); + + return STATUS_DELETE_PENDING; + + case ENMVBOXUSB_PNPSTATE_START_PENDING: + PoStartNextPowerIrp(pIrp); + IoSkipCurrentIrpStackLocation(pIrp); + + vboxUsbDdiStateRelease(pDevExt); + + return PoCallDriver(pDevExt->pLowerDO, pIrp); + + default: + return vboxUsbPwrDispatch(pDevExt, pIrp); + } +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h new file mode 100644 index 00000000..48f439e3 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbPwr.h @@ -0,0 +1,41 @@ +/* $Id: VBoxUsbPwr.h $ */ +/** @file + * USB Power state Handling + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +typedef struct VBOXUSB_PWRSTATE +{ + POWER_STATE PowerState; + ULONG PowerDownLevel; +} VBOXUSB_PWRSTATE, *PVBOXUSB_PWRSTATE; + +DECLHIDDEN(VOID) vboxUsbPwrStateInit(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbDispatchPower(IN PDEVICE_OBJECT pDeviceObject, IN PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbPwr_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp new file mode 100644 index 00000000..05f7381b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.cpp @@ -0,0 +1,1581 @@ +/* $Id: VBoxUsbRt.cpp $ */ +/** @file + * VBox USB R0 runtime + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxUsbCmn.h" +#include "../cmn/VBoxUsbIdc.h" +#include "../cmn/VBoxUsbTool.h" + +#include <VBox/usblib-win.h> +#include <iprt/assert.h> +#include <VBox/log.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define _USBD_ /** @todo r=bird: What is this?? */ + +#define USBD_DEFAULT_PIPE_TRANSFER 0x00000008 + +#define VBOXUSB_MAGIC 0xABCF1423 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXUSB_URB_CONTEXT +{ + PURB pUrb; + PMDL pMdlBuf; + PVBOXUSBDEV_EXT pDevExt; + PVOID pOut; + ULONG ulTransferType; + ULONG ulMagic; +} VBOXUSB_URB_CONTEXT, * PVBOXUSB_URB_CONTEXT; + +typedef struct VBOXUSB_SETUP +{ + uint8_t bmRequestType; + uint8_t bRequest; + uint16_t wValue; + uint16_t wIndex; + uint16_t wLength; +} VBOXUSB_SETUP, *PVBOXUSB_SETUP; + + + +static bool vboxUsbRtCtxSetOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + bool fRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, pFObj, NULL); + if (fRc) + LogFunc(("pDevExt (0x%x) Owner(0x%x) acquired\n", pFObj)); + else + LogFunc(("pDevExt (0x%x) Owner(0x%x) FAILED!!\n", pFObj)); + return fRc; +} + +static bool vboxUsbRtCtxReleaseOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + bool fRc = ASMAtomicCmpXchgPtr(&pDevExt->Rt.pOwner, NULL, pFObj); + if (fRc) + LogFunc(("pDevExt (0x%x) Owner(0x%x) released\n", pFObj)); + else + LogFunc(("pDevExt (0x%x) Owner(0x%x) release: is NOT an owner\n", pFObj)); + return fRc; +} + +static bool vboxUsbRtCtxIsOwner(PVBOXUSBDEV_EXT pDevExt, PFILE_OBJECT pFObj) +{ + PFILE_OBJECT pOwner = (PFILE_OBJECT)ASMAtomicReadPtr((void *volatile *)(&pDevExt->Rt.pOwner)); + return pOwner == pFObj; +} + +static NTSTATUS vboxUsbRtIdcSubmit(ULONG uCtl, void *pvBuffer) +{ + /* we just reuse the standard usb tooling for simplicity here */ + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(g_VBoxUsbGlobals.RtIdc.pDevice, uCtl, pvBuffer, NULL); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +static NTSTATUS vboxUsbRtIdcInit() +{ + UNICODE_STRING UniName; + RtlInitUnicodeString(&UniName, USBMON_DEVICE_NAME_NT); + NTSTATUS Status = IoGetDeviceObjectPointer(&UniName, FILE_ALL_ACCESS, &g_VBoxUsbGlobals.RtIdc.pFile, &g_VBoxUsbGlobals.RtIdc.pDevice); + if (NT_SUCCESS(Status)) + { + VBOXUSBIDC_VERSION Version; + vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION, &Version); + if (NT_SUCCESS(Status)) + { + if ( Version.u32Major == VBOXUSBIDC_VERSION_MAJOR +#if VBOXUSBIDC_VERSION_MINOR != 0 + && Version.u32Minor >= VBOXUSBIDC_VERSION_MINOR +#endif + ) + return STATUS_SUCCESS; + AssertFailed(); + } + else + { + AssertFailed(); + } + + /* this will as well dereference the dev obj */ + ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile); + } + else + { + AssertFailed(); + } + + memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc)); + return Status; +} + +static VOID vboxUsbRtIdcTerm() +{ + Assert(g_VBoxUsbGlobals.RtIdc.pFile); + Assert(g_VBoxUsbGlobals.RtIdc.pDevice); + ObDereferenceObject(g_VBoxUsbGlobals.RtIdc.pFile); + memset(&g_VBoxUsbGlobals.RtIdc, 0, sizeof (g_VBoxUsbGlobals.RtIdc)); +} + +static NTSTATUS vboxUsbRtIdcReportDevStart(PDEVICE_OBJECT pPDO, HVBOXUSBIDCDEV *phDev) +{ + VBOXUSBIDC_PROXY_STARTUP Start; + Start.u.pPDO = pPDO; + + *phDev = NULL; + + NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP, &Start); + Assert(Status == STATUS_SUCCESS); + if (!NT_SUCCESS(Status)) + return Status; + + *phDev = Start.u.hDev; + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtIdcReportDevStop(HVBOXUSBIDCDEV hDev) +{ + VBOXUSBIDC_PROXY_TEARDOWN Stop; + Stop.hDev = hDev; + + NTSTATUS Status = vboxUsbRtIdcSubmit(VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN, &Stop); + Assert(Status == STATUS_SUCCESS); + return Status; +} + + +DECLHIDDEN(NTSTATUS) vboxUsbRtGlobalsInit() +{ + return vboxUsbRtIdcInit(); +} + +DECLHIDDEN(VOID) vboxUsbRtGlobalsTerm() +{ + vboxUsbRtIdcTerm(); +} + + +DECLHIDDEN(NTSTATUS) vboxUsbRtInit(PVBOXUSBDEV_EXT pDevExt) +{ + RtlZeroMemory(&pDevExt->Rt, sizeof (pDevExt->Rt)); + NTSTATUS Status = IoRegisterDeviceInterface(pDevExt->pPDO, &GUID_CLASS_VBOXUSB, + NULL, /* IN PUNICODE_STRING ReferenceString OPTIONAL */ + &pDevExt->Rt.IfName); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbRtIdcReportDevStart(pDevExt->pPDO, &pDevExt->Rt.hMonDev); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + Assert(pDevExt->Rt.hMonDev); + return STATUS_SUCCESS; + } + + NTSTATUS tmpStatus = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE); + Assert(tmpStatus == STATUS_SUCCESS); + if (NT_SUCCESS(tmpStatus)) + { + RtlFreeUnicodeString(&pDevExt->Rt.IfName); + } + } + return Status; +} + +/** + * Free cached USB device/configuration descriptors + * + * @param pDevExt USB DevExt pointer + */ +static void vboxUsbRtFreeCachedDescriptors(PVBOXUSBDEV_EXT pDevExt) +{ + if (pDevExt->Rt.devdescr) + { + vboxUsbMemFree(pDevExt->Rt.devdescr); + pDevExt->Rt.devdescr = NULL; + } + for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i) + { + if (pDevExt->Rt.cfgdescr[i]) + { + vboxUsbMemFree(pDevExt->Rt.cfgdescr[i]); + pDevExt->Rt.cfgdescr[i] = NULL; + } + } +} + +/** + * Free per-device interface info + * + * @param pDevExt USB DevExt pointer + * @param fAbortPipes If true, also abort any open pipes + */ +static void vboxUsbRtFreeInterfaces(PVBOXUSBDEV_EXT pDevExt, BOOLEAN fAbortPipes) +{ + unsigned i; + unsigned j; + + /* + * Free old interface info + */ + if (pDevExt->Rt.pVBIfaceInfo) + { + for (i=0;i<pDevExt->Rt.uNumInterfaces;i++) + { + if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo) + { + if (fAbortPipes) + { + for (j=0; j<pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + Log(("Aborting Pipe %d handle %x address %x\n", j, + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle, + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress)); + VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle, FALSE); + } + } + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo); + } + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = NULL; + if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo); + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL; + } + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo); + pDevExt->Rt.pVBIfaceInfo = NULL; + } +} + +DECLHIDDEN(VOID) vboxUsbRtClear(PVBOXUSBDEV_EXT pDevExt) +{ + vboxUsbRtFreeCachedDescriptors(pDevExt); + vboxUsbRtFreeInterfaces(pDevExt, FALSE); +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtRm(PVBOXUSBDEV_EXT pDevExt) +{ + if (!pDevExt->Rt.IfName.Buffer) + return STATUS_SUCCESS; + + NTSTATUS Status = vboxUsbRtIdcReportDevStop(pDevExt->Rt.hMonDev); + Assert(Status == STATUS_SUCCESS); + Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, FALSE); + Assert(Status == STATUS_SUCCESS); + if (NT_SUCCESS(Status)) + { + RtlFreeUnicodeString(&pDevExt->Rt.IfName); + pDevExt->Rt.IfName.Buffer = NULL; + } + return Status; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtStart(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = IoSetDeviceInterfaceState(&pDevExt->Rt.IfName, TRUE); + Assert(Status == STATUS_SUCCESS); + return Status; +} + +static NTSTATUS vboxUsbRtCacheDescriptors(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; +// uint32_t uTotalLength; +// unsigned i; + + /* Read device descriptor */ + Assert(!pDevExt->Rt.devdescr); + pDevExt->Rt.devdescr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_DEVICE_DESCRIPTOR)); + if (pDevExt->Rt.devdescr) + { + memset(pDevExt->Rt.devdescr, 0, sizeof (USB_DEVICE_DESCRIPTOR)); + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.devdescr, sizeof (USB_DEVICE_DESCRIPTOR), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + Assert(pDevExt->Rt.devdescr->bNumConfigurations > 0); + PUSB_CONFIGURATION_DESCRIPTOR pDr = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(sizeof (USB_CONFIGURATION_DESCRIPTOR)); + Assert(pDr); + if (pDr) + { + UCHAR i = 0; + for (; i < pDevExt->Rt.devdescr->bNumConfigurations; ++i) + { + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof (USB_CONFIGURATION_DESCRIPTOR), USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status)) + { + break; + } + + USHORT uTotalLength = pDr->wTotalLength; + pDevExt->Rt.cfgdescr[i] = (PUSB_CONFIGURATION_DESCRIPTOR)vboxUsbMemAlloc(uTotalLength); + if (!pDevExt->Rt.cfgdescr[i]) + { + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDevExt->Rt.cfgdescr[i], uTotalLength, USB_CONFIGURATION_DESCRIPTOR_TYPE, i, 0, RT_INDEFINITE_WAIT); + if (!NT_SUCCESS(Status)) + { + break; + } + } + + vboxUsbMemFree(pDr); + + if (NT_SUCCESS(Status)) + return Status; + + /* recources will be freed in vboxUsbRtFreeCachedDescriptors below */ + } + } + + vboxUsbRtFreeCachedDescriptors(pDevExt); + } + + /* shoud be only on fail here */ + Assert(!NT_SUCCESS(Status)); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchClaimDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLAIMDEV pDev = (PUSBSUP_CLAIMDEV)pIrp->AssociatedIrp.SystemBuffer; + ULONG cbOut = 0; + NTSTATUS Status = STATUS_SUCCESS; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if ( !pDev + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pDev) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pDev)) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxSetOwner(pDevExt, pFObj)) + { + AssertFailed(); + pDev->fClaimed = false; + cbOut = sizeof (*pDev); + break; + } + + vboxUsbRtFreeCachedDescriptors(pDevExt); + Status = vboxUsbRtCacheDescriptors(pDevExt); + if (NT_SUCCESS(Status)) + { + pDev->fClaimed = true; + cbOut = sizeof (*pDev); + } + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, cbOut); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchReleaseDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + NTSTATUS Status= STATUS_SUCCESS; + + if (vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + vboxUsbRtFreeCachedDescriptors(pDevExt); + bool fRc = vboxUsbRtCtxReleaseOwner(pDevExt, pFObj); + Assert(fRc); NOREF(fRc); + } + else + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + } + + VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtGetDeviceDescription(PVBOXUSBDEV_EXT pDevExt) +{ + NTSTATUS Status = STATUS_INSUFFICIENT_RESOURCES; + PUSB_DEVICE_DESCRIPTOR pDr = (PUSB_DEVICE_DESCRIPTOR)vboxUsbMemAllocZ(sizeof (USB_DEVICE_DESCRIPTOR)); + if (pDr) + { + Status = VBoxUsbToolGetDescriptor(pDevExt->pLowerDO, pDr, sizeof(*pDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + pDevExt->Rt.idVendor = pDr->idVendor; + pDevExt->Rt.idProduct = pDr->idProduct; + pDevExt->Rt.bcdDevice = pDr->bcdDevice; + pDevExt->Rt.szSerial[0] = 0; + + if (pDr->iSerialNumber +#ifdef DEBUG + || pDr->iProduct || pDr->iManufacturer +#endif + ) + { + int langId; + Status = VBoxUsbToolGetLangID(pDevExt->pLowerDO, &langId, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status)) + { + Status = VBoxUsbToolGetStringDescriptor(pDevExt->pLowerDO, pDevExt->Rt.szSerial, sizeof (pDevExt->Rt.szSerial), + pDr->iSerialNumber, langId, RT_INDEFINITE_WAIT); + } + else + { + Status = STATUS_SUCCESS; + } + } + } + vboxUsbMemFree(pDr); + } + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchGetDevice(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PUSBSUP_GETDEV pDev = (PUSBSUP_GETDEV)pIrp->AssociatedIrp.SystemBuffer; + ULONG cbOut = 0; + + /* don't check for owner since this request is allowed for non-owners as well */ + NTSTATUS Status; + if ( pDev + && pSl->Parameters.DeviceIoControl.InputBufferLength == sizeof(*pDev) + && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof(*pDev)) + { + /* Even if we don't return it, we need to query the HS flag for later use. */ + Status = VBoxUsbToolGetDeviceSpeed(pDevExt->pLowerDO, &pDevExt->Rt.fIsHighSpeed); + if (NT_SUCCESS(Status)) + { + pDev->hDevice = pDevExt->Rt.hMonDev; + cbOut = sizeof (*pDev); + } + } + else + Status = STATUS_INVALID_PARAMETER; + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, cbOut); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbReset(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + NTSTATUS rcNt; + if (pFObj) + { + if (vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + if ( pIrp->AssociatedIrp.SystemBuffer == NULL + && pSl->Parameters.DeviceIoControl.InputBufferLength == 0 + && pSl->Parameters.DeviceIoControl.OutputBufferLength == 0) + { + rcNt = VBoxUsbToolIoInternalCtlSendSync(pDevExt->pLowerDO, IOCTL_INTERNAL_USB_RESET_PORT, NULL, NULL); + Assert(NT_SUCCESS(rcNt)); + } + else + { + AssertFailed(); + rcNt = STATUS_INVALID_PARAMETER; + } + } + else + { + AssertFailed(); + rcNt = STATUS_ACCESS_DENIED; + } + } + else + { + AssertFailed(); + rcNt = STATUS_INVALID_PARAMETER; + } + + Assert(rcNt != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, rcNt, 0); + vboxUsbDdiStateRelease(pDevExt); + return rcNt; +} + +static PUSB_CONFIGURATION_DESCRIPTOR vboxUsbRtFindConfigDesc(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration) +{ + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + + for (ULONG i = 0; i < VBOXUSBRT_MAX_CFGS; ++i) + { + if (pDevExt->Rt.cfgdescr[i]) + { + if (pDevExt->Rt.cfgdescr[i]->bConfigurationValue == uConfiguration) + { + pCfgDr = pDevExt->Rt.cfgdescr[i]; + break; + } + } + } + + return pCfgDr; +} + +static NTSTATUS vboxUsbRtSetConfig(PVBOXUSBDEV_EXT pDevExt, uint8_t uConfiguration) +{ + PURB pUrb = NULL; + NTSTATUS Status = STATUS_SUCCESS; + uint32_t i; + + if (!uConfiguration) + { + pUrb = VBoxUsbToolUrbAllocZ(URB_FUNCTION_SELECT_CONFIGURATION, sizeof (struct _URB_SELECT_CONFIGURATION)); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAlloc failed\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + vboxUsbRtFreeInterfaces(pDevExt, TRUE); + + pUrb->UrbSelectConfiguration.ConfigurationDescriptor = NULL; + + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle; + pDevExt->Rt.uConfigValue = uConfiguration; + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + + VBoxUsbToolUrbFree(pUrb); + + return Status; + } + +/** @todo r=bird: Need to write a script for fixing these kind of clueless use + * of AssertMsgFailed (into AssertMsgReturn). The __FUNCTION__ is just + * the topping it off - the assertion message includes function, file and + * line number. Duh! */ + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, uConfiguration); + if (!pCfgDr) + { + AssertMsgFailed((__FUNCTION__": VBoxUSBFindConfigDesc did not find cfg (%d)\n", uConfiguration)); + return STATUS_INVALID_PARAMETER; + } + + PUSBD_INTERFACE_LIST_ENTRY pIfLe = (PUSBD_INTERFACE_LIST_ENTRY)vboxUsbMemAllocZ((pCfgDr->bNumInterfaces + 1) * sizeof(USBD_INTERFACE_LIST_ENTRY)); + if (!pIfLe) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ for pIfLe failed\n")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + for (i = 0; i < pCfgDr->bNumInterfaces; i++) + { + pIfLe[i].InterfaceDescriptor = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, i, 0, -1, -1, -1); + if (!pIfLe[i].InterfaceDescriptor) + { + AssertMsgFailed((__FUNCTION__": interface %d not found\n", i)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + pIfLe[pCfgDr->bNumInterfaces].InterfaceDescriptor = NULL; + + if (NT_SUCCESS(Status)) + { + pUrb = USBD_CreateConfigurationRequestEx(pCfgDr, pIfLe); + if (pUrb) + { + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + vboxUsbRtFreeInterfaces(pDevExt, FALSE); + + pDevExt->Rt.hConfiguration = pUrb->UrbSelectConfiguration.ConfigurationHandle; + pDevExt->Rt.uConfigValue = uConfiguration; + pDevExt->Rt.uNumInterfaces = pCfgDr->bNumInterfaces; + + pDevExt->Rt.pVBIfaceInfo = (VBOXUSB_IFACE_INFO*)vboxUsbMemAllocZ(pDevExt->Rt.uNumInterfaces * sizeof (VBOXUSB_IFACE_INFO)); + if (pDevExt->Rt.pVBIfaceInfo) + { + Assert(NT_SUCCESS(Status)); + for (i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + size_t uTotalIfaceInfoLength = GET_USBD_INTERFACE_SIZE(pIfLe[i].Interface->NumberOfPipes); + pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength); + if (!pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + if (pIfLe[i].Interface->NumberOfPipes > 0) + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = (VBOXUSB_PIPE_INFO *)vboxUsbMemAlloc(pIfLe[i].Interface->NumberOfPipes * sizeof(VBOXUSB_PIPE_INFO)); + if (!pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_NO_MEMORY; + break; + } + } + else + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo = NULL; + } + + RtlCopyMemory(pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo, pIfLe[i].Interface, uTotalIfaceInfoLength); + + for (ULONG j = 0; j < pIfLe[i].Interface->NumberOfPipes; j++) + { + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress = pIfLe[i].Interface->Pipes[j].EndpointAddress; + pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].NextScheduledFrame = 0; + } + } + +// if (NT_SUCCESS(Status)) +// { +// +// } + } + else + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAllocZ failed\n")); + Status = STATUS_NO_MEMORY; + } + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x), usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + ExFreePool(pUrb); + } + else + { + AssertMsgFailed((__FUNCTION__": USBD_CreateConfigurationRequestEx failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + } + } + + vboxUsbMemFree(pIfLe); + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbSetConfig(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_SET_CONFIG pCfg = (PUSBSUP_SET_CONFIG)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status = STATUS_SUCCESS; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCfg + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCfg) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtSetConfig(pDevExt, pCfg->bConfigurationValue); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtSetInterface(PVBOXUSBDEV_EXT pDevExt, uint32_t InterfaceNumber, int AlternateSetting) +{ + AssertMsgReturn(pDevExt->Rt.uConfigValue, ("Can't select an interface without an active configuration\n"), + STATUS_INVALID_PARAMETER); + AssertMsgReturn(InterfaceNumber < pDevExt->Rt.uNumInterfaces, ("InterfaceNumber %d too high!!\n", InterfaceNumber), + STATUS_INVALID_PARAMETER); + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = vboxUsbRtFindConfigDesc(pDevExt, pDevExt->Rt.uConfigValue); + AssertMsgReturn(pCfgDr, ("configuration %d not found!!\n", pDevExt->Rt.uConfigValue), + STATUS_INVALID_PARAMETER); + PUSB_INTERFACE_DESCRIPTOR pIfDr = USBD_ParseConfigurationDescriptorEx(pCfgDr, pCfgDr, InterfaceNumber, AlternateSetting, -1, -1, -1); + AssertMsgReturn(pIfDr, ("invalid interface %d or alternate setting %d\n", InterfaceNumber, AlternateSetting), + STATUS_UNSUCCESSFUL); + + USHORT uUrbSize = GET_SELECT_INTERFACE_REQUEST_SIZE(pIfDr->bNumEndpoints); + ULONG uTotalIfaceInfoLength = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints); + NTSTATUS Status = STATUS_SUCCESS; + PURB pUrb; + PUSBD_INTERFACE_INFORMATION pNewIFInfo = NULL; + VBOXUSB_PIPE_INFO *pNewPipeInfo = NULL; + + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo) + { + /* Clear pipes associated with the interface, else Windows may hang. */ + for (ULONG i = 0; i < pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->NumberOfPipes; i++) + VBoxUsbToolPipeClear(pDevExt->pLowerDO, pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo->Pipes[i].PipeHandle, FALSE); + } + + do { + /* First allocate all the structures we'll need. */ + pUrb = VBoxUsbToolUrbAllocZ(0, uUrbSize); + if (!pUrb) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbAllocZ failed\n")); + Status = STATUS_NO_MEMORY; + break; + } + + pNewIFInfo = (PUSBD_INTERFACE_INFORMATION)vboxUsbMemAlloc(uTotalIfaceInfoLength); + if (!pNewIFInfo) + { + AssertMsgFailed((__FUNCTION__": Failed allocating interface storage\n")); + Status = STATUS_NO_MEMORY; + break; + } + + if (pIfDr->bNumEndpoints > 0) + { + pNewPipeInfo = (VBOXUSB_PIPE_INFO *)vboxUsbMemAlloc(pIfDr->bNumEndpoints * sizeof(VBOXUSB_PIPE_INFO)); + if (!pNewPipeInfo) + { + AssertMsgFailed((__FUNCTION__": Failed allocating pipe info storage\n")); + Status = STATUS_NO_MEMORY; + break; + } + } + else + pNewPipeInfo = NULL; + + /* Now that we have all the bits, select the interface. */ + UsbBuildSelectInterfaceRequest(pUrb, uUrbSize, pDevExt->Rt.hConfiguration, InterfaceNumber, AlternateSetting); + pUrb->UrbSelectInterface.Interface.Length = GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints); + + Status = VBoxUsbToolUrbPost(pDevExt->pLowerDO, pUrb, RT_INDEFINITE_WAIT); + if (NT_SUCCESS(Status) && USBD_SUCCESS(pUrb->UrbHeader.Status)) + { + /* Free the old memory and put new in. */ + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo); + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo = pNewIFInfo; + if (pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo) + vboxUsbMemFree(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo); + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo = pNewPipeInfo; + pNewPipeInfo = NULL; pNewIFInfo = NULL; /* Don't try to free it again. */ + + USBD_INTERFACE_INFORMATION *pIfInfo = &pUrb->UrbSelectInterface.Interface; + memcpy(pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pInterfaceInfo, pIfInfo, GET_USBD_INTERFACE_SIZE(pIfDr->bNumEndpoints)); + + Assert(pIfInfo->NumberOfPipes == pIfDr->bNumEndpoints); + for (ULONG i = 0; i < pIfInfo->NumberOfPipes; i++) + { + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].EndpointAddress = pIfInfo->Pipes[i].EndpointAddress; + pDevExt->Rt.pVBIfaceInfo[InterfaceNumber].pPipeInfo[i].NextScheduledFrame = 0; + } + } + else + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolUrbPost failed Status (0x%x) usb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); + } + } while (0); + + /* Clean up. */ + if (pUrb) + VBoxUsbToolUrbFree(pUrb); + if (pNewIFInfo) + vboxUsbMemFree(pNewIFInfo); + if (pNewPipeInfo) + vboxUsbMemFree(pNewPipeInfo); + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbSelectInterface(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_SELECT_INTERFACE pIf = (PUSBSUP_SELECT_INTERFACE)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pIf + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pIf) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtSetInterface(pDevExt, pIf->bInterfaceNumber, pIf->bAlternateSetting); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static HANDLE vboxUsbRtGetPipeHandle(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress) +{ + if (EndPointAddress == 0) + return pDevExt->Rt.hPipe0; + + for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + /* Note that bit 7 determines pipe direction, but is still significant + * because endpoints may be numbered like 0x01, 0x81, 0x02, 0x82 etc. + */ + if (pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].EndpointAddress == EndPointAddress) + return pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->Pipes[j].PipeHandle; + } + } + return 0; +} + +static VBOXUSB_PIPE_INFO* vboxUsbRtGetPipeInfo(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress) +{ + for (ULONG i = 0; i < pDevExt->Rt.uNumInterfaces; i++) + { + for (ULONG j = 0; j < pDevExt->Rt.pVBIfaceInfo[i].pInterfaceInfo->NumberOfPipes; j++) + { + if (pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j].EndpointAddress == EndPointAddress) + return &pDevExt->Rt.pVBIfaceInfo[i].pPipeInfo[j]; + } + } + return NULL; +} + + + +static NTSTATUS vboxUsbRtClearEndpoint(PVBOXUSBDEV_EXT pDevExt, uint32_t EndPointAddress, bool fReset) +{ + NTSTATUS Status = VBoxUsbToolPipeClear(pDevExt->pLowerDO, vboxUsbRtGetPipeHandle(pDevExt, EndPointAddress), fReset); + if (!NT_SUCCESS(Status)) + { + AssertMsgFailed((__FUNCTION__": VBoxUsbToolPipeClear failed Status (0x%x)\n", Status)); + } + + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbClearEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCe + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, TRUE); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchUsbAbortEndpoint(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_CLEAR_ENDPOINT pCe = (PUSBSUP_CLEAR_ENDPOINT)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pCe + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pCe) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != 0) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = vboxUsbRtClearEndpoint(pDevExt, pCe->bEndpoint, FALSE); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtUrbSendCompletion(PDEVICE_OBJECT pDevObj, IRP *pIrp, void *pvContext) +{ + RT_NOREF1(pDevObj); + + if (!pvContext) + { + AssertMsgFailed((__FUNCTION__": context is NULL\n")); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + PVBOXUSB_URB_CONTEXT pContext = (PVBOXUSB_URB_CONTEXT)pvContext; + + if (pContext->ulMagic != VBOXUSB_MAGIC) + { + AssertMsgFailed((__FUNCTION__": Invalid context magic\n")); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + PURB pUrb = pContext->pUrb; + PMDL pMdlBuf = pContext->pMdlBuf; + PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pContext->pOut; + PVBOXUSBDEV_EXT pDevExt = pContext->pDevExt; + + if (!pUrb || !pMdlBuf || !pUrbInfo || !pDevExt) + { + AssertMsgFailed((__FUNCTION__": Invalid args\n")); + if (pDevExt) + vboxUsbDdiStateRelease(pDevExt); + pIrp->IoStatus.Information = 0; + return STATUS_CONTINUE_COMPLETION; + } + + NTSTATUS Status = pIrp->IoStatus.Status; + if (Status == STATUS_SUCCESS) + { + switch(pUrb->UrbHeader.Status) + { + case USBD_STATUS_CRC: + pUrbInfo->error = USBSUP_XFER_CRC; + break; + case USBD_STATUS_SUCCESS: + pUrbInfo->error = USBSUP_XFER_OK; + break; + case USBD_STATUS_STALL_PID: + pUrbInfo->error = USBSUP_XFER_STALL; + break; + case USBD_STATUS_INVALID_URB_FUNCTION: + case USBD_STATUS_INVALID_PARAMETER: + AssertMsgFailed((__FUNCTION__": sw error, urb Status (0x%x)\n", pUrb->UrbHeader.Status)); + case USBD_STATUS_DEV_NOT_RESPONDING: + default: + pUrbInfo->error = USBSUP_XFER_DNR; + break; + } + + switch(pContext->ulTransferType) + { + case USBSUP_TRANSFER_TYPE_MSG: + pUrbInfo->len = pUrb->UrbControlTransfer.TransferBufferLength; + /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special + * the first 8 bytes of the buffer is the setup packet so the real + * data length is therefore urb->len - 8 + */ + pUrbInfo->len += sizeof (pUrb->UrbControlTransfer.SetupPacket); + + /* If a control URB was successfully completed on the default control + * pipe, stash away the handle. When submitting the URB, we don't need + * to know (and initially don't have) the handle. If we want to abort + * the default control pipe, we *have* to have a handle. This is how we + * find out what the handle is. + */ + if (!pUrbInfo->ep && (pDevExt->Rt.hPipe0 == NULL)) + { + pDevExt->Rt.hPipe0 = pUrb->UrbControlTransfer.PipeHandle; + } + + break; + case USBSUP_TRANSFER_TYPE_ISOC: + pUrbInfo->len = pUrb->UrbIsochronousTransfer.TransferBufferLength; + break; + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + if (pUrbInfo->dir == USBSUP_DIRECTION_IN && pUrbInfo->error == USBSUP_XFER_OK + && !(pUrbInfo->flags & USBSUP_FLAG_SHORT_OK) + && pUrbInfo->len > pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength + ) + { + /* If we don't use the USBD_SHORT_TRANSFER_OK flag, the returned buffer lengths are + * wrong for short transfers (always a multiple of max packet size?). So we just figure + * out if this was a data underrun on our own. + */ + pUrbInfo->error = USBSUP_XFER_UNDERRUN; + } + pUrbInfo->len = pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength; + break; + default: + break; + } + } + else + { + pUrbInfo->len = 0; + + LogFunc(("URB failed Status (0x%x) urb Status (0x%x)\n", Status, pUrb->UrbHeader.Status)); +#ifdef DEBUG + switch(pContext->ulTransferType) + { + case USBSUP_TRANSFER_TYPE_MSG: + LogRel(("Msg (CTRL) length=%d\n", pUrb->UrbControlTransfer.TransferBufferLength)); + break; + case USBSUP_TRANSFER_TYPE_ISOC: + LogRel(("ISOC length=%d\n", pUrb->UrbIsochronousTransfer.TransferBufferLength)); + break; + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + LogRel(("BULK/INTR length=%d\n", pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength)); + break; + } +#endif + switch(pUrb->UrbHeader.Status) + { + case USBD_STATUS_CRC: + pUrbInfo->error = USBSUP_XFER_CRC; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_STALL_PID: + pUrbInfo->error = USBSUP_XFER_STALL; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_DEV_NOT_RESPONDING: + case USBD_STATUS_DEVICE_GONE: + pUrbInfo->error = USBSUP_XFER_DNR; + Status = STATUS_SUCCESS; + break; + case ((USBD_STATUS)0xC0010000L): // USBD_STATUS_CANCELED - too bad usbdi.h and usb.h aren't consistent! + /// @todo What the heck are we really supposed to do here? + pUrbInfo->error = USBSUP_XFER_STALL; + Status = STATUS_SUCCESS; + break; + case USBD_STATUS_BAD_START_FRAME: // This one really shouldn't happen + case USBD_STATUS_ISOCH_REQUEST_FAILED: + pUrbInfo->error = USBSUP_XFER_NAC; + Status = STATUS_SUCCESS; + break; + default: + AssertMsgFailed((__FUNCTION__": err Status (0x%x) (0x%x)\n", Status, pUrb->UrbHeader.Status)); + pUrbInfo->error = USBSUP_XFER_DNR; + Status = STATUS_SUCCESS; + break; + } + } + // For isochronous transfers, always update the individual packets + if (pContext->ulTransferType == USBSUP_TRANSFER_TYPE_ISOC) + { + Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets); + for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i) + { + Assert(pUrbInfo->aIsoPkts[i].off == pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset); + pUrbInfo->aIsoPkts[i].cb = (uint16_t)pUrb->UrbIsochronousTransfer.IsoPacket[i].Length; + switch (pUrb->UrbIsochronousTransfer.IsoPacket[i].Status) + { + case USBD_STATUS_SUCCESS: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_OK; + break; + case USBD_STATUS_NOT_ACCESSED: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_NAC; + break; + default: + pUrbInfo->aIsoPkts[i].stat = USBSUP_XFER_STALL; + break; + } + } + } + + MmUnlockPages(pMdlBuf); + IoFreeMdl(pMdlBuf); + + vboxUsbMemFree(pContext); + + vboxUsbDdiStateRelease(pDevExt); + + Assert(pIrp->IoStatus.Status != STATUS_IO_TIMEOUT); + pIrp->IoStatus.Information = sizeof(*pUrbInfo); + pIrp->IoStatus.Status = Status; + return STATUS_CONTINUE_COMPLETION; +} + +static NTSTATUS vboxUsbRtUrbSend(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp, PUSBSUP_URB pUrbInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + PVBOXUSB_URB_CONTEXT pContext = NULL; + PMDL pMdlBuf = NULL; + ULONG cbUrb; + + Assert(pUrbInfo); + if (pUrbInfo->type == USBSUP_TRANSFER_TYPE_ISOC) + { + Assert(pUrbInfo->numIsoPkts <= 8); + cbUrb = GET_ISO_URB_SIZE(pUrbInfo->numIsoPkts); + } + else + cbUrb = sizeof (URB); + + do + { + pContext = (PVBOXUSB_URB_CONTEXT)vboxUsbMemAllocZ(cbUrb + sizeof (VBOXUSB_URB_CONTEXT)); + if (!pContext) + { + AssertMsgFailed((__FUNCTION__": vboxUsbMemAlloc failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + PURB pUrb = (PURB)(pContext + 1); + HANDLE hPipe = NULL; + if (pUrbInfo->ep) + { + hPipe = vboxUsbRtGetPipeHandle(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00)); + if (!hPipe) + { + AssertMsgFailed((__FUNCTION__": vboxUsbRtGetPipeHandle failed for endpoint (0x%x)\n", pUrbInfo->ep)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + pMdlBuf = IoAllocateMdl(pUrbInfo->buf, (ULONG)pUrbInfo->len, FALSE, FALSE, NULL); + if (!pMdlBuf) + { + AssertMsgFailed((__FUNCTION__": IoAllocateMdl failed for buffer (0x%p) length (%d)\n", pUrbInfo->buf, pUrbInfo->len)); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + __try + { + MmProbeAndLockPages(pMdlBuf, KernelMode, IoModifyAccess); + } + __except(EXCEPTION_EXECUTE_HANDLER) + { + Status = GetExceptionCode(); + IoFreeMdl(pMdlBuf); + pMdlBuf = NULL; + AssertMsgFailed((__FUNCTION__": Exception Code (0x%x)\n", Status)); + break; + } + + /* For some reason, passing a MDL in the URB does not work reliably. Notably + * the iPhone when used with iTunes fails. + */ + PVOID pBuffer = MmGetSystemAddressForMdlSafe(pMdlBuf, NormalPagePriority); + if (!pBuffer) + { + AssertMsgFailed((__FUNCTION__": MmGetSystemAddressForMdlSafe failed\n")); + Status = STATUS_INSUFFICIENT_RESOURCES; + break; + } + + switch (pUrbInfo->type) + { + case USBSUP_TRANSFER_TYPE_MSG: + { + pUrb->UrbHeader.Function = URB_FUNCTION_CONTROL_TRANSFER; + pUrb->UrbHeader.Length = sizeof (struct _URB_CONTROL_TRANSFER); + pUrb->UrbControlTransfer.PipeHandle = hPipe; + pUrb->UrbControlTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbControlTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + pUrb->UrbControlTransfer.UrbLink = 0; + + if (!hPipe) + pUrb->UrbControlTransfer.TransferFlags |= USBD_DEFAULT_PIPE_TRANSFER; + + /* QUSB_TRANSFER_TYPE_MSG is a control transfer, but it is special + * the first 8 bytes of the buffer is the setup packet so the real + * data length is therefore pUrb->len - 8 + */ + //PVBOXUSB_SETUP pSetup = (PVBOXUSB_SETUP)pUrb->UrbControlTransfer.SetupPacket; + memcpy(pUrb->UrbControlTransfer.SetupPacket, pBuffer, min(sizeof (pUrb->UrbControlTransfer.SetupPacket), pUrbInfo->len)); + + if (pUrb->UrbControlTransfer.TransferBufferLength <= sizeof (pUrb->UrbControlTransfer.SetupPacket)) + pUrb->UrbControlTransfer.TransferBufferLength = 0; + else + pUrb->UrbControlTransfer.TransferBufferLength -= sizeof (pUrb->UrbControlTransfer.SetupPacket); + + pUrb->UrbControlTransfer.TransferBuffer = (uint8_t *)pBuffer + sizeof(pUrb->UrbControlTransfer.SetupPacket); + pUrb->UrbControlTransfer.TransferBufferMDL = 0; + pUrb->UrbControlTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK; + break; + } + case USBSUP_TRANSFER_TYPE_ISOC: + { + Assert(hPipe); + VBOXUSB_PIPE_INFO *pPipeInfo = vboxUsbRtGetPipeInfo(pDevExt, pUrbInfo->ep | ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? 0x80 : 0x00)); + if (pPipeInfo == NULL) + { + /* Can happen if the isoc request comes in too early or late. */ + AssertMsgFailed((__FUNCTION__": pPipeInfo not found\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + pUrb->UrbHeader.Function = URB_FUNCTION_ISOCH_TRANSFER; + pUrb->UrbHeader.Length = (USHORT)cbUrb; + pUrb->UrbIsochronousTransfer.PipeHandle = hPipe; + pUrb->UrbIsochronousTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbIsochronousTransfer.TransferBufferMDL = 0; + pUrb->UrbIsochronousTransfer.TransferBuffer = pBuffer; + pUrb->UrbIsochronousTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + pUrb->UrbIsochronousTransfer.TransferFlags |= USBD_SHORT_TRANSFER_OK; // May be implied already + pUrb->UrbIsochronousTransfer.NumberOfPackets = pUrbInfo->numIsoPkts; + pUrb->UrbIsochronousTransfer.ErrorCount = 0; + pUrb->UrbIsochronousTransfer.UrbLink = 0; + + Assert(pUrbInfo->numIsoPkts == pUrb->UrbIsochronousTransfer.NumberOfPackets); + for (ULONG i = 0; i < pUrbInfo->numIsoPkts; ++i) + { + pUrb->UrbIsochronousTransfer.IsoPacket[i].Offset = pUrbInfo->aIsoPkts[i].off; + pUrb->UrbIsochronousTransfer.IsoPacket[i].Length = pUrbInfo->aIsoPkts[i].cb; + } + + /* We have to schedule the URBs ourselves. There is an ASAP flag but + * that can only be reliably used after pipe creation/reset, ie. it's + * almost completely useless. + */ + ULONG iFrame, iStartFrame; + VBoxUsbToolCurrentFrame(pDevExt->pLowerDO, pIrp, &iFrame); + iFrame += 2; + iStartFrame = pPipeInfo->NextScheduledFrame; + if ((iFrame < iStartFrame) || (iStartFrame > iFrame + 512)) + iFrame = iStartFrame; + /* For full-speed devices, there must be one transfer per frame (Windows USB + * stack requirement), but URBs can contain multiple packets. For high-speed or + * faster transfers, we expect one URB per frame, regardless of the interval. + */ + if (pDevExt->Rt.devdescr->bcdUSB < 0x300 && !pDevExt->Rt.fIsHighSpeed) + pPipeInfo->NextScheduledFrame = iFrame + pUrbInfo->numIsoPkts; + else + pPipeInfo->NextScheduledFrame = iFrame + 1; + pUrb->UrbIsochronousTransfer.StartFrame = iFrame; + break; + } + case USBSUP_TRANSFER_TYPE_BULK: + case USBSUP_TRANSFER_TYPE_INTR: + { + Assert(pUrbInfo->dir != USBSUP_DIRECTION_SETUP); + Assert(pUrbInfo->dir == USBSUP_DIRECTION_IN || pUrbInfo->type == USBSUP_TRANSFER_TYPE_BULK); + Assert(hPipe); + + pUrb->UrbHeader.Function = URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER; + pUrb->UrbHeader.Length = sizeof (struct _URB_BULK_OR_INTERRUPT_TRANSFER); + pUrb->UrbBulkOrInterruptTransfer.PipeHandle = hPipe; + pUrb->UrbBulkOrInterruptTransfer.TransferBufferLength = (ULONG)pUrbInfo->len; + pUrb->UrbBulkOrInterruptTransfer.TransferBufferMDL = 0; + pUrb->UrbBulkOrInterruptTransfer.TransferBuffer = pBuffer; + pUrb->UrbBulkOrInterruptTransfer.TransferFlags = ((pUrbInfo->dir == USBSUP_DIRECTION_IN) ? USBD_TRANSFER_DIRECTION_IN : USBD_TRANSFER_DIRECTION_OUT); + + if (pUrb->UrbBulkOrInterruptTransfer.TransferFlags & USBD_TRANSFER_DIRECTION_IN) + pUrb->UrbBulkOrInterruptTransfer.TransferFlags |= (USBD_SHORT_TRANSFER_OK); + + pUrb->UrbBulkOrInterruptTransfer.UrbLink = 0; + break; + } + default: + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + if (!NT_SUCCESS(Status)) + { + break; + } + + pContext->pDevExt = pDevExt; + pContext->pMdlBuf = pMdlBuf; + pContext->pUrb = pUrb; + pContext->pOut = pUrbInfo; + pContext->ulTransferType = pUrbInfo->type; + pContext->ulMagic = VBOXUSB_MAGIC; + + PIO_STACK_LOCATION pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_INTERNAL_DEVICE_CONTROL; + pSl->Parameters.DeviceIoControl.IoControlCode = IOCTL_INTERNAL_USB_SUBMIT_URB; + pSl->Parameters.Others.Argument1 = pUrb; + pSl->Parameters.Others.Argument2 = NULL; + + IoSetCompletionRoutine(pIrp, vboxUsbRtUrbSendCompletion, pContext, TRUE, TRUE, TRUE); + IoMarkIrpPending(pIrp); + Status = IoCallDriver(pDevExt->pLowerDO, pIrp); + AssertMsg(NT_SUCCESS(Status), (__FUNCTION__": IoCallDriver failed Status (0x%x)\n", Status)); + return STATUS_PENDING; + } while (0); + + Assert(!NT_SUCCESS(Status)); + + if (pMdlBuf) + { + MmUnlockPages(pMdlBuf); + IoFreeMdl(pMdlBuf); + } + + if (pContext) + vboxUsbMemFree(pContext); + + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchSendUrb(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + PUSBSUP_URB pUrbInfo = (PUSBSUP_URB)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status; + + do + { + if (!pFObj) + { + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + break; + } + + if (!vboxUsbRtCtxIsOwner(pDevExt, pFObj)) + { + AssertFailed(); + Status = STATUS_ACCESS_DENIED; + break; + } + + if ( !pUrbInfo + || pSl->Parameters.DeviceIoControl.InputBufferLength != sizeof (*pUrbInfo) + || pSl->Parameters.DeviceIoControl.OutputBufferLength != sizeof (*pUrbInfo)) + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + break; + } + return vboxUsbRtUrbSend(pDevExt, pIrp, pUrbInfo); + } while (0); + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, 0); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchIsOperational(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + VBoxDrvToolIoComplete(pIrp, STATUS_SUCCESS, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbRtDispatchGetVersion(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PUSBSUP_VERSION pVer= (PUSBSUP_VERSION)pIrp->AssociatedIrp.SystemBuffer; + NTSTATUS Status = STATUS_SUCCESS; + + if ( pVer + && pSl->Parameters.DeviceIoControl.InputBufferLength == 0 + && pSl->Parameters.DeviceIoControl.OutputBufferLength == sizeof(*pVer)) + { + pVer->u32Major = USBDRV_MAJOR_VERSION; + pVer->u32Minor = USBDRV_MINOR_VERSION; + } + else + { + AssertMsgFailed((__FUNCTION__": STATUS_INVALID_PARAMETER\n")); + Status = STATUS_INVALID_PARAMETER; + } + + Assert(Status != STATUS_PENDING); + VBoxDrvToolIoComplete(pIrp, Status, sizeof (*pVer)); + vboxUsbDdiStateRelease(pDevExt); + return Status; +} + +static NTSTATUS vboxUsbRtDispatchDefault(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + VBoxDrvToolIoComplete(pIrp, STATUS_INVALID_DEVICE_REQUEST, 0); + vboxUsbDdiStateRelease(pDevExt); + return STATUS_INVALID_DEVICE_REQUEST; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtCreate(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + RT_NOREF1(pDevExt); + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + AssertReturn(pFObj, STATUS_INVALID_PARAMETER); + return STATUS_SUCCESS; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtClose(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFObj = pSl->FileObject; + Assert(pFObj); + + vboxUsbRtCtxReleaseOwner(pDevExt, pFObj); + + return STATUS_SUCCESS; +} + +DECLHIDDEN(NTSTATUS) vboxUsbRtDispatch(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp) +{ + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + switch (pSl->Parameters.DeviceIoControl.IoControlCode) + { + case SUPUSB_IOCTL_USB_CLAIM_DEVICE: + return vboxUsbRtDispatchClaimDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_RELEASE_DEVICE: + return vboxUsbRtDispatchReleaseDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_GET_DEVICE: + return vboxUsbRtDispatchGetDevice(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_RESET: + return vboxUsbRtDispatchUsbReset(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_SET_CONFIG: + return vboxUsbRtDispatchUsbSetConfig(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_SELECT_INTERFACE: + return vboxUsbRtDispatchUsbSelectInterface(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_CLEAR_ENDPOINT: + return vboxUsbRtDispatchUsbClearEndpoint(pDevExt, pIrp); + + case SUPUSB_IOCTL_USB_ABORT_ENDPOINT: + return vboxUsbRtDispatchUsbAbortEndpoint(pDevExt, pIrp); + + case SUPUSB_IOCTL_SEND_URB: + return vboxUsbRtDispatchSendUrb(pDevExt, pIrp); + + case SUPUSB_IOCTL_IS_OPERATIONAL: + return vboxUsbRtDispatchIsOperational(pDevExt, pIrp); + + case SUPUSB_IOCTL_GET_VERSION: + return vboxUsbRtDispatchGetVersion(pDevExt, pIrp); + + default: + return vboxUsbRtDispatchDefault(pDevExt, pIrp); + } +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h new file mode 100644 index 00000000..80185d0a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/dev/VBoxUsbRt.h @@ -0,0 +1,87 @@ +/* $Id: VBoxUsbRt.h $ */ +/** @file + * VBox USB R0 runtime + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbCmn.h" +#include "../cmn/VBoxUsbIdc.h" + +#define VBOXUSBRT_MAX_CFGS 4 + +typedef struct VBOXUSB_PIPE_INFO { + UCHAR EndpointAddress; + ULONG NextScheduledFrame; +} VBOXUSB_PIPE_INFO; + +typedef struct VBOXUSB_IFACE_INFO { + USBD_INTERFACE_INFORMATION *pInterfaceInfo; + VBOXUSB_PIPE_INFO *pPipeInfo; +} VBOXUSB_IFACE_INFO; + +typedef struct VBOXUSB_RT +{ + UNICODE_STRING IfName; + + HANDLE hPipe0; + HANDLE hConfiguration; + uint32_t uConfigValue; + + uint32_t uNumInterfaces; + USB_DEVICE_DESCRIPTOR *devdescr; + USB_CONFIGURATION_DESCRIPTOR *cfgdescr[VBOXUSBRT_MAX_CFGS]; + + VBOXUSB_IFACE_INFO *pVBIfaceInfo; + + uint16_t idVendor, idProduct, bcdDevice; + char szSerial[MAX_USB_SERIAL_STRING]; + BOOLEAN fIsHighSpeed; + + HVBOXUSBIDCDEV hMonDev; + PFILE_OBJECT pOwner; +} VBOXUSB_RT, *PVBOXUSB_RT; + +typedef struct VBOXUSBRT_IDC +{ + PDEVICE_OBJECT pDevice; + PFILE_OBJECT pFile; +} VBOXUSBRT_IDC, *PVBOXUSBRT_IDC; + +DECLHIDDEN(NTSTATUS) vboxUsbRtGlobalsInit(); +DECLHIDDEN(VOID) vboxUsbRtGlobalsTerm(); + +DECLHIDDEN(NTSTATUS) vboxUsbRtInit(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(VOID) vboxUsbRtClear(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbRtRm(PVBOXUSBDEV_EXT pDevExt); +DECLHIDDEN(NTSTATUS) vboxUsbRtStart(PVBOXUSBDEV_EXT pDevExt); + +DECLHIDDEN(NTSTATUS) vboxUsbRtDispatch(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); +DECLHIDDEN(NTSTATUS) vboxUsbRtCreate(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); +DECLHIDDEN(NTSTATUS) vboxUsbRtClose(PVBOXUSBDEV_EXT pDevExt, PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_dev_VBoxUsbRt_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/lib/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp b/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp new file mode 100644 index 00000000..77ffc94a --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/lib/VBoxUsbLib-win.cpp @@ -0,0 +1,2209 @@ +/* $Id: VBoxUsbLib-win.cpp $ */ +/** @file + * VBox USB ring-3 Driver Interface library, Windows. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_DRV_USBPROXY +#include <iprt/win/windows.h> + +#include <VBox/sup.h> +#include <VBox/types.h> +#include <VBox/err.h> +#include <VBox/param.h> +#include <iprt/path.h> +#include <iprt/assert.h> +#include <iprt/alloc.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/utf16.h> +#include <VBox/log.h> +#include <VBox/usblib.h> +#include <VBox/usblib-win.h> +#include <VBox/usb.h> +#include <VBox/VBoxDrvCfg-win.h> +#pragma warning (disable:4200) /* shuts up the empty array member warnings */ +#include <iprt/win/setupapi.h> +#include <usbdi.h> +#include <hidsdi.h> +#include <Dbt.h> + +/* Defined in Windows 8 DDK (through usbdi.h) but we use Windows 7 DDK to build. */ +#define UsbSuperSpeed 3 + +#ifdef VBOX_WITH_NEW_USB_ENUM +# include <cfgmgr32.h> +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct _USB_INTERFACE_DESCRIPTOR2 +{ + UCHAR bLength; + UCHAR bDescriptorType; + UCHAR bInterfaceNumber; + UCHAR bAlternateSetting; + UCHAR bNumEndpoints; + UCHAR bInterfaceClass; + UCHAR bInterfaceSubClass; + UCHAR bInterfaceProtocol; + UCHAR iInterface; + USHORT wNumClasses; +} USB_INTERFACE_DESCRIPTOR2, *PUSB_INTERFACE_DESCRIPTOR2; + +typedef struct VBOXUSBGLOBALSTATE +{ + HANDLE hMonitor; + HANDLE hNotifyEvent; + HANDLE hInterruptEvent; + HANDLE hThread; + HWND hWnd; + HANDLE hTimerQueue; + HANDLE hTimer; +} VBOXUSBGLOBALSTATE, *PVBOXUSBGLOBALSTATE; + +typedef struct VBOXUSB_STRING_DR_ENTRY +{ + struct VBOXUSB_STRING_DR_ENTRY *pNext; + UCHAR iDr; + USHORT idLang; + USB_STRING_DESCRIPTOR StrDr; +} VBOXUSB_STRING_DR_ENTRY, *PVBOXUSB_STRING_DR_ENTRY; + +/** + * This represents VBoxUsb device instance + */ +typedef struct VBOXUSB_DEV +{ + struct VBOXUSB_DEV *pNext; + char szName[512]; + char szDriverRegName[512]; +} VBOXUSB_DEV, *PVBOXUSB_DEV; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static VBOXUSBGLOBALSTATE g_VBoxUsbGlobal; + + +static void usbLibVuFreeDevices(PVBOXUSB_DEV pDevInfos) +{ + while (pDevInfos) + { + PVBOXUSB_DEV pNext = pDevInfos->pNext; + RTMemFree(pDevInfos); + pDevInfos = pNext; + } +} + +/* Check that a proxied device responds the way we expect it to. */ +static int usbLibVuDeviceValidate(PVBOXUSB_DEV pVuDev) +{ + HANDLE hOut = INVALID_HANDLE_VALUE; + DWORD dwErr; + + hOut = CreateFile(pVuDev->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL); + + if (hOut == INVALID_HANDLE_VALUE) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("Failed to open `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + return VERR_GENERAL_FAILURE; + } + + USBSUP_VERSION version = {0}; + DWORD cbReturned = 0; + int rc = VERR_VERSION_MISMATCH; + + do + { + if (!DeviceIoControl(hOut, SUPUSB_IOCTL_GET_VERSION, NULL, 0,&version, sizeof(version), &cbReturned, NULL)) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSB_IOCTL_GET_VERSION failed on `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + break; + } + + if ( version.u32Major != USBDRV_MAJOR_VERSION +#if USBDRV_MINOR_VERSION != 0 + || version.u32Minor < USBDRV_MINOR_VERSION +#endif + ) + { + AssertFailed(); + LogRelFunc(("Invalid version %d:%d (%s) vs %d:%d (library)!\n", version.u32Major, version.u32Minor, pVuDev->szName, USBDRV_MAJOR_VERSION, USBDRV_MINOR_VERSION)); + break; + } + + if (!DeviceIoControl(hOut, SUPUSB_IOCTL_IS_OPERATIONAL, NULL, 0, NULL, NULL, &cbReturned, NULL)) + { + dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSB_IOCTL_IS_OPERATIONAL failed on `%s' (dwErr=%u)!\n", pVuDev->szName, dwErr)); + break; + } + + rc = VINF_SUCCESS; + } while (0); + + CloseHandle(hOut); + return rc; +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibVuDevicePopulate(PVBOXUSB_DEV pVuDev, HDEVINFO hDevInfo, PSP_DEVICE_INTERFACE_DATA pIfData) +{ + DWORD cbIfDetailData; + int rc = VINF_SUCCESS; + + SetupDiGetDeviceInterfaceDetail(hDevInfo, pIfData, + NULL, /* OUT PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData */ + 0, /* IN DWORD DeviceInterfaceDetailDataSize */ + &cbIfDetailData, + NULL + ); + Assert(GetLastError() == ERROR_INSUFFICIENT_BUFFER); + + PSP_DEVICE_INTERFACE_DETAIL_DATA pIfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAllocZ(cbIfDetailData); + if (!pIfDetailData) + { + AssertMsgFailed(("RTMemAllocZ failed\n")); + return VERR_OUT_OF_RESOURCES; + } + + DWORD cbDbgRequired; + SP_DEVINFO_DATA DevInfoData; + DevInfoData.cbSize = sizeof (DevInfoData); + /* the cbSize should contain the sizeof a fixed-size part according to the docs */ + pIfDetailData->cbSize = sizeof (*pIfDetailData); + do + { + if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, pIfData, + pIfDetailData, + cbIfDetailData, + &cbDbgRequired, + &DevInfoData)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetDeviceInterfaceDetail, cbRequired (%d), was (%d), dwErr (%d)\n", cbDbgRequired, cbIfDetailData, dwErr)); + rc = VERR_GENERAL_FAILURE; + break; + } + + strncpy(pVuDev->szName, pIfDetailData->DevicePath, sizeof (pVuDev->szName)); + + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfo, &DevInfoData, SPDRP_DRIVER, + NULL, /* OUT PDWORD PropertyRegDataType */ + (PBYTE)pVuDev->szDriverRegName, + sizeof (pVuDev->szDriverRegName), + &cbDbgRequired)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetDeviceRegistryPropertyA, cbRequired (%d), was (%d), dwErr (%d)\n", cbDbgRequired, sizeof (pVuDev->szDriverRegName), dwErr)); + rc = VERR_GENERAL_FAILURE; + break; + } + + rc = usbLibVuDeviceValidate(pVuDev); + LogRelFunc(("Found VBoxUSB on `%s' (rc=%d)\n", pVuDev->szName, rc)); + AssertRC(rc); + } while (0); + + RTMemFree(pIfDetailData); + return rc; +} + +static int usbLibVuGetDevices(PVBOXUSB_DEV *ppVuDevs, uint32_t *pcVuDevs) +{ + *ppVuDevs = NULL; + *pcVuDevs = 0; + + HDEVINFO hDevInfo = SetupDiGetClassDevs(&GUID_CLASS_VBOXUSB, + NULL, /* IN PCTSTR Enumerator */ + NULL, /* IN HWND hwndParent */ + (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE) /* IN DWORD Flags */ + ); + if (hDevInfo == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetupDiGetClassDevs, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + + for (int i = 0; ; ++i) + { + SP_DEVICE_INTERFACE_DATA IfData; + IfData.cbSize = sizeof (IfData); + if (!SetupDiEnumDeviceInterfaces(hDevInfo, + NULL, /* IN PSP_DEVINFO_DATA DeviceInfoData */ + &GUID_CLASS_VBOXUSB, /* IN LPGUID InterfaceClassGuid */ + i, + &IfData)) + { + DWORD dwErr = GetLastError(); + if (dwErr == ERROR_NO_MORE_ITEMS) + break; + + AssertMsgFailed(("SetupDiEnumDeviceInterfaces, dwErr (%u), resuming\n", dwErr)); + continue; + } + + /* we've now got the IfData */ + PVBOXUSB_DEV pVuDev = (PVBOXUSB_DEV)RTMemAllocZ(sizeof (*pVuDev)); + if (!pVuDev) + { + AssertMsgFailed(("RTMemAllocZ failed, resuming\n")); + continue; + } + + int rc = usbLibVuDevicePopulate(pVuDev, hDevInfo, &IfData); + if (!RT_SUCCESS(rc)) + { + AssertMsgFailed(("usbLibVuDevicePopulate failed, rc (%d), resuming\n", rc)); + continue; + } + + pVuDev->pNext = *ppVuDevs; + *ppVuDevs = pVuDev; + ++*pcVuDevs; + } + + SetupDiDestroyDeviceInfoList(hDevInfo); + + return VINF_SUCCESS; +} + +static int usbLibDevPopulate(PUSBDEVICE pDev, PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo, ULONG iPort, LPCSTR lpszDrvKeyName, LPCSTR lpszHubName, PVBOXUSB_STRING_DR_ENTRY pDrList) +{ + pDev->bcdUSB = pConInfo->DeviceDescriptor.bcdUSB; + pDev->bDeviceClass = pConInfo->DeviceDescriptor.bDeviceClass; + pDev->bDeviceSubClass = pConInfo->DeviceDescriptor.bDeviceSubClass; + pDev->bDeviceProtocol = pConInfo->DeviceDescriptor.bDeviceProtocol; + pDev->idVendor = pConInfo->DeviceDescriptor.idVendor; + pDev->idProduct = pConInfo->DeviceDescriptor.idProduct; + pDev->bcdDevice = pConInfo->DeviceDescriptor.bcdDevice; + pDev->bBus = 0; /** @todo figure out bBus on windows... */ + pDev->bPort = iPort; + /** @todo check which devices are used for primary input (keyboard & mouse) */ + if (!lpszDrvKeyName || *lpszDrvKeyName == 0) + pDev->enmState = USBDEVICESTATE_UNUSED; + else + pDev->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* Determine the speed the device is operating at. */ + switch (pConInfo->Speed) + { + case UsbLowSpeed: pDev->enmSpeed = USBDEVICESPEED_LOW; break; + case UsbFullSpeed: pDev->enmSpeed = USBDEVICESPEED_FULL; break; + case UsbHighSpeed: pDev->enmSpeed = USBDEVICESPEED_HIGH; break; + default: /* If we don't know, most likely it's something new. */ + case UsbSuperSpeed: pDev->enmSpeed = USBDEVICESPEED_SUPER; break; + } + /* Unfortunately USB_NODE_CONNECTION_INFORMATION_EX will not report UsbSuperSpeed, and + * it's not even defined in the Win7 DDK we use. So we go by the USB version, and + * luckily we know that USB3 must mean SuperSpeed. The USB3 spec guarantees this (9.6.1). + */ + if (pDev->bcdUSB >= 0x0300) + pDev->enmSpeed = USBDEVICESPEED_SUPER; + + int rc = RTStrAPrintf((char **)&pDev->pszAddress, "%s", lpszDrvKeyName); + if (rc < 0) + return VERR_NO_MEMORY; + pDev->pszBackend = RTStrDup("host"); + if (!pDev->pszBackend) + { + RTStrFree((char *)pDev->pszAddress); + return VERR_NO_STR_MEMORY; + } + pDev->pszHubName = RTStrDup(lpszHubName); + pDev->bNumConfigurations = 0; + pDev->u64SerialHash = 0; + + for (; pDrList; pDrList = pDrList->pNext) + { + char **ppszString = NULL; + if ( pConInfo->DeviceDescriptor.iManufacturer + && pDrList->iDr == pConInfo->DeviceDescriptor.iManufacturer) + ppszString = (char **)&pDev->pszManufacturer; + else if ( pConInfo->DeviceDescriptor.iProduct + && pDrList->iDr == pConInfo->DeviceDescriptor.iProduct) + ppszString = (char **)&pDev->pszProduct; + else if ( pConInfo->DeviceDescriptor.iSerialNumber + && pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + ppszString = (char **)&pDev->pszSerialNumber; + if (ppszString) + { + rc = RTUtf16ToUtf8((PCRTUTF16)pDrList->StrDr.bString, ppszString); + if (RT_SUCCESS(rc)) + { + Assert(*ppszString); + USBLibPurgeEncoding(*ppszString); + + if (pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + pDev->u64SerialHash = USBLibHashSerial(*ppszString); + } + else + { + AssertMsgFailed(("RTUtf16ToUtf8 failed, rc (%d), resuming\n", rc)); + *ppszString = NULL; + } + } + } + + return VINF_SUCCESS; +} +#else + +static PSP_DEVICE_INTERFACE_DETAIL_DATA usbLibGetDevDetail(HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData, PSP_DEVINFO_DATA DevInfoData); +static void *usbLibGetRegistryProperty(HDEVINFO InfoSet, const PSP_DEVINFO_DATA DevData, DWORD Property); + +/* Populate the data for a single proxied USB device. */ +static int usbLibVUsbDevicePopulate(PVBOXUSB_DEV pVuDev, HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData) +{ + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData = NULL; + SP_DEVINFO_DATA DeviceData; + LPCSTR Location; + int rc = VINF_SUCCESS; + + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + /* The interface detail includes the device path. */ + DetailData = usbLibGetDevDetail(InfoSet, InterfaceData, &DeviceData); + if (DetailData) + { + strncpy(pVuDev->szName, DetailData->DevicePath, sizeof(pVuDev->szName)); + + /* The location is used as a unique identifier for cross-referencing the two lists. */ + Location = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_DRIVER); + if (Location) + { + strncpy(pVuDev->szDriverRegName, Location, sizeof(pVuDev->szDriverRegName)); + rc = usbLibVuDeviceValidate(pVuDev); + LogRelFunc(("Found VBoxUSB on `%s' (rc=%d)\n", pVuDev->szName, rc)); + AssertRC(rc); + + RTMemFree((void *)Location); + } + else + { + /* Errors will be logged by usbLibGetRegistryProperty(). */ + rc = VERR_GENERAL_FAILURE; + } + + RTMemFree(DetailData); + } + else + { + /* Errors will be logged by usbLibGetDevDetail(). */ + rc = VERR_GENERAL_FAILURE; + } + + + return rc; +} + +/* Enumerate proxied USB devices (with VBoxUSB.sys loaded). */ +static int usbLibEnumVUsbDevices(PVBOXUSB_DEV *ppVuDevs, uint32_t *pcVuDevs) +{ + SP_DEVICE_INTERFACE_DATA InterfaceData; + HDEVINFO InfoSet; + DWORD DeviceIndex; + DWORD dwErr; + + *ppVuDevs = NULL; + *pcVuDevs = 0; + + /* Enumerate all present devices which support the GUID_CLASS_VBOXUSB interface. */ + InfoSet = SetupDiGetClassDevs(&GUID_CLASS_VBOXUSB, NULL, NULL, + (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); + if (InfoSet == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("SetupDiGetClassDevs for GUID_CLASS_VBOXUSB failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + DeviceIndex = 0; + + /* Loop over the enumerated list. */ + while (SetupDiEnumDeviceInterfaces(InfoSet, NULL, &GUID_CLASS_VBOXUSB, DeviceIndex, &InterfaceData)) + { + /* we've now got the IfData */ + PVBOXUSB_DEV pVuDev = (PVBOXUSB_DEV)RTMemAllocZ(sizeof (*pVuDev)); + if (!pVuDev) + { + AssertFailed(); + LogRelFunc(("RTMemAllocZ failed\n")); + break; + } + + int rc = usbLibVUsbDevicePopulate(pVuDev, InfoSet, &InterfaceData); + if (RT_SUCCESS(rc)) + { + pVuDev->pNext = *ppVuDevs; + *ppVuDevs = pVuDev; + ++*pcVuDevs; + } + else /* Skip this device but continue enumerating. */ + AssertMsgFailed(("usbLibVuDevicePopulate failed, rc=%d\n", rc)); + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + ++DeviceIndex; + } + + /* Paranoia. */ + dwErr = GetLastError(); + if (dwErr != ERROR_NO_MORE_ITEMS) + { + LogRelFunc(("SetupDiEnumDeviceInterfaces failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + } + + SetupDiDestroyDeviceInfoList(InfoSet); + + return VINF_SUCCESS; +} + +static uint16_t usbLibParseHexNumU16(LPCSTR *ppStr) +{ + const char *pStr = *ppStr; + char c; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 4; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + c = *pStr; + u = c >= 'A' ? c - 'A' + 10 : c - '0'; /* Hex digit to number. */ + num |= u << (12 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static int usbLibDevPopulate(PUSBDEVICE pDev, PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo, ULONG iPort, LPCSTR lpszLocation, LPCSTR lpszDrvKeyName, LPCSTR lpszHubName, PVBOXUSB_STRING_DR_ENTRY pDrList) +{ + pDev->bcdUSB = pConInfo->DeviceDescriptor.bcdUSB; + pDev->bDeviceClass = pConInfo->DeviceDescriptor.bDeviceClass; + pDev->bDeviceSubClass = pConInfo->DeviceDescriptor.bDeviceSubClass; + pDev->bDeviceProtocol = pConInfo->DeviceDescriptor.bDeviceProtocol; + pDev->idVendor = pConInfo->DeviceDescriptor.idVendor; + pDev->idProduct = pConInfo->DeviceDescriptor.idProduct; + pDev->bcdDevice = pConInfo->DeviceDescriptor.bcdDevice; + pDev->bBus = 0; /* The hub numbering is not very useful on Windows. Skip it. */ + pDev->bPort = iPort; + + /* The port path/location uniquely identifies the port. */ + pDev->pszPortPath = RTStrDup(lpszLocation); + if (!pDev->pszPortPath) + return VERR_NO_STR_MEMORY; + + /* If there is no DriverKey, the device is unused because there's no driver. */ + if (!lpszDrvKeyName || *lpszDrvKeyName == 0) + pDev->enmState = USBDEVICESTATE_UNUSED; + else + pDev->enmState = USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* Determine the speed the device is operating at. */ + switch (pConInfo->Speed) + { + case UsbLowSpeed: pDev->enmSpeed = USBDEVICESPEED_LOW; break; + case UsbFullSpeed: pDev->enmSpeed = USBDEVICESPEED_FULL; break; + case UsbHighSpeed: pDev->enmSpeed = USBDEVICESPEED_HIGH; break; + default: /* If we don't know, most likely it's something new. */ + case UsbSuperSpeed: pDev->enmSpeed = USBDEVICESPEED_SUPER; break; + } + /* Unfortunately USB_NODE_CONNECTION_INFORMATION_EX will not report UsbSuperSpeed, and + * it's not even defined in the Win7 DDK we use. So we go by the USB version, and + * luckily we know that USB3 must mean SuperSpeed. The USB3 spec guarantees this (9.6.1). + */ + if (pDev->bcdUSB >= 0x0300) + pDev->enmSpeed = USBDEVICESPEED_SUPER; + + /* If there's no DriverKey, jam in an empty string to avoid NULL pointers. */ + if (!lpszDrvKeyName) + pDev->pszAddress = RTStrDup(""); + else + pDev->pszAddress = RTStrDup(lpszDrvKeyName); + + pDev->pszBackend = RTStrDup("host"); + if (!pDev->pszBackend) + { + RTStrFree((char *)pDev->pszAddress); + return VERR_NO_STR_MEMORY; + } + pDev->pszHubName = RTStrDup(lpszHubName); + pDev->bNumConfigurations = 0; + pDev->u64SerialHash = 0; + + for (; pDrList; pDrList = pDrList->pNext) + { + char **ppszString = NULL; + if ( pConInfo->DeviceDescriptor.iManufacturer + && pDrList->iDr == pConInfo->DeviceDescriptor.iManufacturer) + ppszString = (char **)&pDev->pszManufacturer; + else if ( pConInfo->DeviceDescriptor.iProduct + && pDrList->iDr == pConInfo->DeviceDescriptor.iProduct) + ppszString = (char **)&pDev->pszProduct; + else if ( pConInfo->DeviceDescriptor.iSerialNumber + && pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + ppszString = (char **)&pDev->pszSerialNumber; + if (ppszString) + { + int rc = RTUtf16ToUtf8((PCRTUTF16)pDrList->StrDr.bString, ppszString); + if (RT_SUCCESS(rc)) + { + Assert(*ppszString); + USBLibPurgeEncoding(*ppszString); + + if (pDrList->iDr == pConInfo->DeviceDescriptor.iSerialNumber) + pDev->u64SerialHash = USBLibHashSerial(*ppszString); + } + else + { + AssertMsgFailed(("RTUtf16ToUtf8 failed, rc (%d), resuming\n", rc)); + *ppszString = NULL; + } + } + } + + return VINF_SUCCESS; +} +#endif + +static void usbLibDevStrFree(LPSTR lpszName) +{ + RTStrFree(lpszName); +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibDevStrDriverKeyGet(HANDLE hHub, ULONG iPort, LPSTR* plpszName) +{ + USB_NODE_CONNECTION_DRIVERKEY_NAME Name; + DWORD cbReturned = 0; + Name.ConnectionIndex = iPort; + *plpszName = NULL; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL)) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertMsgFailed(("DeviceIoControl 1 fail dwErr (%u)\n", GetLastError())); +#endif + return VERR_GENERAL_FAILURE; + } + + if (Name.ActualLength < sizeof (Name)) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + PUSB_NODE_CONNECTION_DRIVERKEY_NAME pName = (PUSB_NODE_CONNECTION_DRIVERKEY_NAME)RTMemAllocZ(Name.ActualLength); + if (!pName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + pName->ConnectionIndex = iPort; + if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_DRIVERKEY_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->DriverKeyName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("DeviceIoControl 2 fail dwErr (%u)\n", dwErr)); + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pName); + return rc; +} +#endif + +static int usbLibDevStrHubNameGet(HANDLE hHub, ULONG iPort, LPSTR* plpszName) +{ + USB_NODE_CONNECTION_NAME Name; + DWORD cbReturned = 0; + Name.ConnectionIndex = iPort; + *plpszName = NULL; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, &Name, sizeof (Name), &Name, sizeof (Name), &cbReturned, NULL)) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + if (Name.ActualLength < sizeof (Name)) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + PUSB_NODE_CONNECTION_NAME pName = (PUSB_NODE_CONNECTION_NAME)RTMemAllocZ(Name.ActualLength); + if (!pName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + pName->ConnectionIndex = iPort; + if (DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_NAME, pName, Name.ActualLength, pName, Name.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pName->NodeName, pName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + AssertFailed(); + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pName); + return rc; +} + +static int usbLibDevStrRootHubNameGet(HANDLE hCtl, LPSTR* plpszName) +{ + USB_ROOT_HUB_NAME HubName; + DWORD cbReturned = 0; + *plpszName = NULL; + if (!DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, &HubName, sizeof (HubName), &cbReturned, NULL)) + { + return VERR_GENERAL_FAILURE; + } + PUSB_ROOT_HUB_NAME pHubName = (PUSB_ROOT_HUB_NAME)RTMemAllocZ(HubName.ActualLength); + if (!pHubName) + return VERR_OUT_OF_RESOURCES; + + int rc = VINF_SUCCESS; + if (DeviceIoControl(hCtl, IOCTL_USB_GET_ROOT_HUB_NAME, NULL, 0, pHubName, HubName.ActualLength, &cbReturned, NULL)) + { + rc = RTUtf16ToUtf8Ex((PCRTUTF16)pHubName->RootHubName, pHubName->ActualLength / sizeof (WCHAR), plpszName, 0, NULL); + AssertRC(rc); + if (RT_SUCCESS(rc)) + rc = VINF_SUCCESS; + } + else + { + rc = VERR_GENERAL_FAILURE; + } + RTMemFree(pHubName); + return rc; +} + +static int usbLibDevCfgDrGet(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, PUSB_CONFIGURATION_DESCRIPTOR *ppDr) +{ + *ppDr = NULL; + + char Buf[sizeof (USB_DESCRIPTOR_REQUEST) + sizeof (USB_CONFIGURATION_DESCRIPTOR)]; + memset(&Buf, 0, sizeof (Buf)); + + PUSB_DESCRIPTOR_REQUEST pCfgDrRq = (PUSB_DESCRIPTOR_REQUEST)Buf; + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = (PUSB_CONFIGURATION_DESCRIPTOR)(Buf + sizeof (*pCfgDrRq)); + + pCfgDrRq->ConnectionIndex = iPort; + pCfgDrRq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | iDr; + pCfgDrRq->SetupPacket.wLength = (USHORT)(sizeof (USB_CONFIGURATION_DESCRIPTOR)); + DWORD cbReturned = 0; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pCfgDrRq, sizeof (Buf), + pCfgDrRq, sizeof (Buf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION #1 failed (dwErr=%u) on hub %s port %d\n", dwErr, lpcszHubName, iPort)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return VERR_GENERAL_FAILURE; + } + + if (sizeof (Buf) != cbReturned) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + if (pCfgDr->wTotalLength < sizeof (USB_CONFIGURATION_DESCRIPTOR)) + { + AssertFailed(); + return VERR_GENERAL_FAILURE; + } + + DWORD cbRq = sizeof (USB_DESCRIPTOR_REQUEST) + pCfgDr->wTotalLength; + PUSB_DESCRIPTOR_REQUEST pRq = (PUSB_DESCRIPTOR_REQUEST)RTMemAllocZ(cbRq); + Assert(pRq); + if (!pRq) + return VERR_OUT_OF_RESOURCES; + + int rc = VERR_GENERAL_FAILURE; + do + { + PUSB_CONFIGURATION_DESCRIPTOR pDr = (PUSB_CONFIGURATION_DESCRIPTOR)(pRq + 1); + pRq->ConnectionIndex = iPort; + pRq->SetupPacket.wValue = (USB_CONFIGURATION_DESCRIPTOR_TYPE << 8) | iDr; + pRq->SetupPacket.wLength = (USHORT)(cbRq - sizeof (USB_DESCRIPTOR_REQUEST)); + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pRq, cbRq, + pRq, cbRq, + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION #2 failed (dwErr=%u) on hub %s port %d\n", dwErr, lpcszHubName, iPort)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + break; + } + + if (cbRq != cbReturned) + { + AssertFailed(); + break; + } + + if (pDr->wTotalLength != cbRq - sizeof (USB_DESCRIPTOR_REQUEST)) + { + AssertFailed(); + break; + } + + *ppDr = pDr; + return VINF_SUCCESS; + } while (0); + + RTMemFree(pRq); + return rc; +} + +static void usbLibDevCfgDrFree(PUSB_CONFIGURATION_DESCRIPTOR pDr) +{ + Assert(pDr); + PUSB_DESCRIPTOR_REQUEST pRq = ((PUSB_DESCRIPTOR_REQUEST)pDr)-1; + RTMemFree(pRq); +} + +static int usbLibDevStrDrEntryGet(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, USHORT idLang, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + char szBuf[sizeof (USB_DESCRIPTOR_REQUEST) + MAXIMUM_USB_STRING_LENGTH]; + RT_ZERO(szBuf); + + PUSB_DESCRIPTOR_REQUEST pRq = (PUSB_DESCRIPTOR_REQUEST)szBuf; + PUSB_STRING_DESCRIPTOR pDr = (PUSB_STRING_DESCRIPTOR)(szBuf + sizeof (*pRq)); + RT_BZERO(pDr, sizeof(USB_STRING_DESCRIPTOR)); + + pRq->ConnectionIndex = iPort; + pRq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8) | iDr; + pRq->SetupPacket.wIndex = idLang; + pRq->SetupPacket.wLength = sizeof (szBuf) - sizeof (*pRq); + + DWORD cbReturned = 0; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION, pRq, sizeof (szBuf), + pRq, sizeof(szBuf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + LogRel(("Getting USB descriptor (id %u) failed (dwErr=%u) on hub %s port %d\n", iDr, dwErr, lpcszHubName, iPort)); + return RTErrConvertFromWin32(dwErr); + } + + /* Wrong descriptor type at the requested port index? Bail out. */ + if (pDr->bDescriptorType != USB_STRING_DESCRIPTOR_TYPE) + return VERR_NOT_FOUND; + + /* Some more sanity checks. */ + if ( (cbReturned < sizeof (*pDr) + 2) + || (!!(pDr->bLength % 2)) + || (pDr->bLength != cbReturned - sizeof(*pRq))) + { + AssertMsgFailed(("Sanity check failed for string descriptor: cbReturned=%RI32, cbDevReq=%zu, type=%RU8, len=%RU8, port=%RU32, index=%RU32, lang=%RU32\n", + cbReturned, sizeof(*pRq), pDr->bDescriptorType, pDr->bLength, iPort, iDr, idLang)); + return VERR_INVALID_PARAMETER; + } + + PVBOXUSB_STRING_DR_ENTRY pEntry = + (PVBOXUSB_STRING_DR_ENTRY)RTMemAllocZ(sizeof(VBOXUSB_STRING_DR_ENTRY) + pDr->bLength + 2); + AssertPtr(pEntry); + if (!pEntry) + return VERR_NO_MEMORY; + + pEntry->pNext = *ppList; + pEntry->iDr = iDr; + pEntry->idLang = idLang; + memcpy(&pEntry->StrDr, pDr, pDr->bLength); + + *ppList = pEntry; + + return VINF_SUCCESS; +} + +static void usbLibDevStrDrEntryFree(PVBOXUSB_STRING_DR_ENTRY pDr) +{ + RTMemFree(pDr); +} + +static void usbLibDevStrDrEntryFreeList(PVBOXUSB_STRING_DR_ENTRY pDr) +{ + while (pDr) + { + PVBOXUSB_STRING_DR_ENTRY pNext = pDr->pNext; + usbLibDevStrDrEntryFree(pDr); + pDr = pNext; + } +} + +static int usbLibDevStrDrEntryGetForLangs(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, ULONG iDr, ULONG cIdLang, const USHORT *pIdLang, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + for (ULONG i = 0; i < cIdLang; ++i) + { + usbLibDevStrDrEntryGet(hHub, lpcszHubName, iPort, iDr, pIdLang[i], ppList); + } + return VINF_SUCCESS; +} + +static int usbLibDevStrDrEntryGetAll(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, PUSB_DEVICE_DESCRIPTOR pDevDr, PUSB_CONFIGURATION_DESCRIPTOR pCfgDr, PVBOXUSB_STRING_DR_ENTRY *ppList) +{ + /* Read string descriptor zero to determine what languages are available. */ + int rc = usbLibDevStrDrEntryGet(hHub, lpcszHubName, iPort, 0, 0, ppList); + if (RT_FAILURE(rc)) + return rc; + + PUSB_STRING_DESCRIPTOR pLangStrDr = &(*ppList)->StrDr; + USHORT *pIdLang = pLangStrDr->bString; + ULONG cIdLang = (pLangStrDr->bLength - RT_OFFSETOF(USB_STRING_DESCRIPTOR, bString)) / sizeof (*pIdLang); + + if (pDevDr->iManufacturer) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iManufacturer, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + if (pDevDr->iProduct) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iProduct, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + if (pDevDr->iSerialNumber) + { + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pDevDr->iSerialNumber, cIdLang, pIdLang, ppList); + AssertRC(rc); + } + + PUCHAR pCur = (PUCHAR)pCfgDr; + PUCHAR pEnd = pCur + pCfgDr->wTotalLength; + while (pCur + sizeof (USB_COMMON_DESCRIPTOR) <= pEnd) + { + PUSB_COMMON_DESCRIPTOR pCmnDr = (PUSB_COMMON_DESCRIPTOR)pCur; + if (pCur + pCmnDr->bLength > pEnd) + { + AssertFailed(); + break; + } + + /* This is invalid but was seen with a TerraTec Aureon 7.1 USB sound card. */ + if (!pCmnDr->bLength) + break; + + switch (pCmnDr->bDescriptorType) + { + case USB_CONFIGURATION_DESCRIPTOR_TYPE: + { + if (pCmnDr->bLength != sizeof (USB_CONFIGURATION_DESCRIPTOR)) + { + AssertFailed(); + break; + } + PUSB_CONFIGURATION_DESCRIPTOR pCurCfgDr = (PUSB_CONFIGURATION_DESCRIPTOR)pCmnDr; + if (!pCurCfgDr->iConfiguration) + break; + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pCurCfgDr->iConfiguration, cIdLang, pIdLang, ppList); + AssertRC(rc); + break; + } + case USB_INTERFACE_DESCRIPTOR_TYPE: + { + if (pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR) && pCmnDr->bLength != sizeof (USB_INTERFACE_DESCRIPTOR2)) + { + AssertFailed(); + break; + } + PUSB_INTERFACE_DESCRIPTOR pCurIfDr = (PUSB_INTERFACE_DESCRIPTOR)pCmnDr; + if (!pCurIfDr->iInterface) + break; + rc = usbLibDevStrDrEntryGetForLangs(hHub, lpcszHubName, iPort, pCurIfDr->iInterface, cIdLang, pIdLang, ppList); + AssertRC(rc); + break; + } + default: + break; + } + + pCur = pCur + pCmnDr->bLength; + } + + return VINF_SUCCESS; +} + +#ifndef VBOX_WITH_NEW_USB_ENUM +static int usbLibDevGetHubDevices(LPCSTR lpszName, PUSBDEVICE *ppDevs, uint32_t *pcDevs); + +static int usbLibDevGetHubPortDevices(HANDLE hHub, LPCSTR lpcszHubName, ULONG iPort, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + int rc = VINF_SUCCESS; + char Buf[sizeof (USB_NODE_CONNECTION_INFORMATION_EX) + (sizeof (USB_PIPE_INFO) * 20)]; + PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo = (PUSB_NODE_CONNECTION_INFORMATION_EX)Buf; + //PUSB_PIPE_INFO paPipeInfo = (PUSB_PIPE_INFO)(Buf + sizeof (PUSB_NODE_CONNECTION_INFORMATION_EX)); + DWORD cbReturned = 0; + memset(&Buf, 0, sizeof (Buf)); + pConInfo->ConnectionIndex = iPort; + if (!DeviceIoControl(hHub, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, + pConInfo, sizeof (Buf), + pConInfo, sizeof (Buf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRel(("Getting USB connection information failed (dwErr=%u) on hub %s\n", dwErr, lpcszHubName)); + AssertMsg(dwErr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed (dwErr=%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + + if (pConInfo->ConnectionStatus != DeviceConnected) + { + /* just ignore & return success */ + return VWRN_INVALID_HANDLE; + } + + if (pConInfo->DeviceIsHub) + { + LPSTR lpszChildHubName = NULL; + rc = usbLibDevStrHubNameGet(hHub, iPort, &lpszChildHubName); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibDevGetHubDevices(lpszChildHubName, ppDevs, pcDevs); + usbLibDevStrFree(lpszChildHubName); + AssertRC(rc); + return rc; + } + /* ignore this err */ + return VINF_SUCCESS; + } + + bool fFreeNameBuf = true; + char nameEmptyBuf = '\0'; + LPSTR lpszName = NULL; + rc = usbLibDevStrDriverKeyGet(hHub, iPort, &lpszName); + Assert(!!lpszName == !!RT_SUCCESS(rc)); + if (!lpszName) + { + LogRelFunc(("No DriverKey on hub %s port %d\n", lpcszHubName, iPort)); + lpszName = &nameEmptyBuf; + fFreeNameBuf = false; + } + + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + PVBOXUSB_STRING_DR_ENTRY pList = NULL; + rc = usbLibDevCfgDrGet(hHub, lpcszHubName, iPort, 0, &pCfgDr); + if (pCfgDr) + { + rc = usbLibDevStrDrEntryGetAll(hHub, lpcszHubName, iPort, &pConInfo->DeviceDescriptor, pCfgDr, &pList); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertRC(rc); // this can fail if device suspended +#endif + } + + PUSBDEVICE pDev = (PUSBDEVICE)RTMemAllocZ(sizeof (*pDev)); + if (RT_LIKELY(pDev)) + { + rc = usbLibDevPopulate(pDev, pConInfo, iPort, lpszName, lpcszHubName, pList); + if (RT_SUCCESS(rc)) + { + pDev->pNext = *ppDevs; + *ppDevs = pDev; + ++*pcDevs; + } + else + RTMemFree(pDev); + } + else + rc = VERR_NO_MEMORY; + + if (pCfgDr) + usbLibDevCfgDrFree(pCfgDr); + if (fFreeNameBuf) + { + Assert(lpszName); + usbLibDevStrFree(lpszName); + } + if (pList) + usbLibDevStrDrEntryFreeList(pList); + + return rc; +} + +static int usbLibDevGetHubDevices(LPCSTR lpszName, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + LPSTR lpszDevName = (LPSTR)RTMemAllocZ(strlen(lpszName) + sizeof("\\\\.\\")); + HANDLE hDev = INVALID_HANDLE_VALUE; + Assert(lpszDevName); + if (!lpszDevName) + { + AssertFailed(); + return VERR_OUT_OF_RESOURCES; + } + + int rc = VINF_SUCCESS; + strcpy(lpszDevName, "\\\\.\\"); + strcpy(lpszDevName + sizeof("\\\\.\\") - sizeof (lpszDevName[0]), lpszName); + do + { + DWORD cbReturned = 0; + hDev = CreateFile(lpszDevName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hDev == INVALID_HANDLE_VALUE) + { + AssertFailed(); + break; + } + + USB_NODE_INFORMATION NodeInfo; + memset(&NodeInfo, 0, sizeof (NodeInfo)); + if (!DeviceIoControl(hDev, IOCTL_USB_GET_NODE_INFORMATION, + &NodeInfo, sizeof (NodeInfo), + &NodeInfo, sizeof (NodeInfo), + &cbReturned, NULL)) + { + LogRel(("Getting USB node information failed (dwErr=%u) on hub %s\n", GetLastError(), lpszName)); + AssertFailed(); + break; + } + + for (ULONG i = 1; i <= NodeInfo.u.HubInformation.HubDescriptor.bNumberOfPorts; ++i) + { + /* Just skip devices for which we failed to create the device structure. */ + usbLibDevGetHubPortDevices(hDev, lpszName, i, ppDevs, pcDevs); + } + } while (0); + + if (hDev != INVALID_HANDLE_VALUE) + CloseHandle(hDev); + + RTMemFree(lpszDevName); + + return rc; +} +#endif + +#ifdef VBOX_WITH_NEW_USB_ENUM + +/* Get a registry property for a device given its HDEVINFO + SP_DEVINFO_DATA. */ +static void *usbLibGetRegistryProperty(HDEVINFO InfoSet, const PSP_DEVINFO_DATA DevData, DWORD Property) +{ + BOOL rc; + DWORD dwReqLen; + void *PropertyData; + + /* How large a buffer do we need? */ + rc = SetupDiGetDeviceRegistryProperty(InfoSet, DevData, Property, + NULL, NULL, 0, &dwReqLen); + if (!rc && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + { + LogRelFunc(("Failed to query buffer size, error %ld\n", GetLastError())); + return NULL; + } + + PropertyData = RTMemAlloc(dwReqLen); + if (!PropertyData) + return NULL; + + /* Get the actual property data. */ + rc = SetupDiGetDeviceRegistryProperty(InfoSet, DevData, Property, + NULL, (PBYTE)PropertyData, dwReqLen, &dwReqLen); + if (!rc) + { + LogRelFunc(("Failed to get property data, error %ld\n", GetLastError())); + RTMemFree(PropertyData); + return NULL; + } + return PropertyData; +} + +/* Given a HDEVINFO and SP_DEVICE_INTERFACE_DATA, get the interface detail data and optionally device info data. */ +static PSP_DEVICE_INTERFACE_DETAIL_DATA usbLibGetDevDetail(HDEVINFO InfoSet, PSP_DEVICE_INTERFACE_DATA InterfaceData, PSP_DEVINFO_DATA DevInfoData) +{ + BOOL rc; + DWORD dwReqLen; + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData; + + rc = SetupDiGetDeviceInterfaceDetail(InfoSet, InterfaceData, NULL, 0, &dwReqLen, DevInfoData); + if (!rc && (GetLastError() != ERROR_INSUFFICIENT_BUFFER)) + { + LogRelFunc(("Failed to get interface detail size, error %ld\n", GetLastError())); + return NULL; + } + + DetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)RTMemAlloc(dwReqLen); + if (!DetailData) + return NULL; + + memset(DetailData, 0, dwReqLen); + DetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + rc = SetupDiGetDeviceInterfaceDetail(InfoSet, InterfaceData, DetailData, dwReqLen, &dwReqLen, DevInfoData); + if (!rc) + { + LogRelFunc(("Failed to get interface detail, error %ld\n", GetLastError())); + RTMemFree(DetailData); + } + + return DetailData; +} + +/* Given a hub's PnP device instance, find its device path (file name). */ +static LPCSTR usbLibGetHubPathFromInstanceID(LPCSTR InstanceID) +{ + HDEVINFO InfoSet; + SP_DEVICE_INTERFACE_DATA InterfaceData; + PSP_DEVICE_INTERFACE_DETAIL_DATA DetailData; + BOOL rc; + LPSTR DevicePath = NULL; + + /* Enumerate the DevInst's USB hub interface. */ + InfoSet = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_HUB, InstanceID, NULL, + DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (InfoSet == INVALID_HANDLE_VALUE) + { + LogRelFunc(("Failed to get interface for InstID %se, error %ld\n", InstanceID, GetLastError())); + return NULL; + } + + memset(&InterfaceData, 0, sizeof(InterfaceData)); + InterfaceData.cbSize = sizeof(InterfaceData); + rc = SetupDiEnumDeviceInterfaces(InfoSet, 0, &GUID_DEVINTERFACE_USB_HUB, 0, &InterfaceData); + if (!rc) + { + DWORD dwErr = GetLastError(); + + /* The parent device might not be a hub; that is valid, ignore such errors. */ + if (dwErr != ERROR_NO_MORE_ITEMS) + LogRelFunc(("Failed to get interface data for InstID %s, error %ld\n", InstanceID, dwErr)); + SetupDiDestroyDeviceInfoList(InfoSet); + return NULL; + } + + DetailData = usbLibGetDevDetail(InfoSet, &InterfaceData, NULL); + if (!DetailData) + { + SetupDiDestroyDeviceInfoList(InfoSet); + return NULL; + } + + /* Copy the device path out of the interface detail. */ + DevicePath = RTStrDup(DetailData->DevicePath); + RTMemFree(DetailData); + SetupDiDestroyDeviceInfoList(InfoSet); + + return DevicePath; +} + + +/* Use the Configuration Manager (CM) to get a devices's parent given its DEVINST and + * turn it into a PnP device instance ID string. + */ +static LPCSTR usbLibGetParentInstanceID(DEVINST DevInst) +{ + LPSTR InstanceID; + DEVINST ParentInst; + ULONG ulReqChars; + ULONG ulReqBytes; + CONFIGRET cr; + + /* First get the parent DEVINST. */ + cr = CM_Get_Parent(&ParentInst, DevInst, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get parent instance, error %ld\n", GetLastError())); + return NULL; + } + + /* Then convert it to the instance ID string. */ + cr = CM_Get_Device_ID_Size(&ulReqChars, ParentInst, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get device ID size (DevInst=%X), error %ld\n", DevInst, GetLastError())); + return NULL; + } + + /* CM_Get_Device_ID_Size gives us the size in characters without terminating null. */ + ulReqBytes = (ulReqChars + 1) * sizeof(char); + InstanceID = (LPSTR)RTMemAlloc(ulReqBytes); + if (!InstanceID) + return NULL; + + cr = CM_Get_Device_ID(ParentInst, InstanceID, ulReqBytes, 0); + if (cr != CR_SUCCESS) + { + LogRelFunc(("Failed to get device ID (DevInst=%X), error %ld\n", DevInst, GetLastError())); + RTMemFree(InstanceID); + return NULL; + } + + return InstanceID; +} + +/* Process a single USB device that's being enumerated and grab its hub-specific data. */ +static int usbLibDevGetDevice(LPCSTR lpcszHubFile, ULONG iPort, LPCSTR lpcszLocation, LPCSTR lpcszDriverKey, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + HANDLE HubDevice; + BYTE abConBuf[sizeof(USB_NODE_CONNECTION_INFORMATION_EX)]; + PUSB_NODE_CONNECTION_INFORMATION_EX pConInfo = PUSB_NODE_CONNECTION_INFORMATION_EX(abConBuf); + int rc = VINF_SUCCESS; + DWORD cbReturned = 0; + + /* Validate inputs. */ + if ((iPort < 1) || (iPort > 255)) + { + LogRelFunc(("Port index out of range (%u)\n", iPort)); + return VERR_INVALID_PARAMETER; + } + if (!lpcszHubFile) + { + LogRelFunc(("Hub path is NULL!\n")); + return VERR_INVALID_PARAMETER; + } + if (!lpcszLocation) + { + LogRelFunc(("Location NULL!\n")); + return VERR_INVALID_PARAMETER; + } + + /* Try opening the hub file so we can send IOCTLs to it. */ + HubDevice = CreateFile(lpcszHubFile, GENERIC_WRITE, FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, 0, NULL); + if (HubDevice == INVALID_HANDLE_VALUE) + { + LogRelFunc(("Failed to open hub `%s' (dwErr=%u)\n", lpcszHubFile, GetLastError())); + return VERR_FILE_NOT_FOUND; + } + + /* The shenanigans with abConBuf are due to USB_NODE_CONNECTION_INFORMATION_EX + * containing a zero-sized array, triggering compiler warnings. + */ + memset(pConInfo, 0, sizeof(abConBuf)); + pConInfo->ConnectionIndex = iPort; + + /* We expect that IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX is always available + * on any supported Windows version and hardware. + * NB: IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX_V2 is Win8 and later only. + */ + if (!DeviceIoControl(HubDevice, IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX, + pConInfo, sizeof(abConBuf), pConInfo, sizeof(abConBuf), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRel(("IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX failed (dwErr=%u) on hub %s, port %d\n", dwErr, lpcszHubFile, iPort)); + AssertMsg(dwErr == ERROR_DEVICE_NOT_CONNECTED, (__FUNCTION__": DeviceIoControl failed dwErr (%u)\n", dwErr)); + CloseHandle(HubDevice); + return VERR_GENERAL_FAILURE; + } + + if (pConInfo->ConnectionStatus != DeviceConnected) + { + /* Ignore this, can't do anything with it. */ + LogFunc(("Device is not connected, skipping.\n")); + CloseHandle(HubDevice); + return VINF_SUCCESS; + } + + if (pConInfo->DeviceIsHub) + { + /* We're ignoring hubs, just skip this. */ + LogFunc(("Device is a hub, skipping.\n")); + CloseHandle(HubDevice); + return VINF_SUCCESS; + } + + PUSB_CONFIGURATION_DESCRIPTOR pCfgDr = NULL; + PVBOXUSB_STRING_DR_ENTRY pList = NULL; + rc = usbLibDevCfgDrGet(HubDevice, lpcszHubFile, iPort, 0, &pCfgDr); + if (pCfgDr) + { + rc = usbLibDevStrDrEntryGetAll(HubDevice, lpcszHubFile, iPort, &pConInfo->DeviceDescriptor, pCfgDr, &pList); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertRC(rc); // this can fail if device suspended +#endif + } + + /* At this point we're done with the hub device. */ + CloseHandle(HubDevice); + + PUSBDEVICE pDev = (PUSBDEVICE)RTMemAllocZ(sizeof (*pDev)); + if (RT_LIKELY(pDev)) + { + rc = usbLibDevPopulate(pDev, pConInfo, iPort, lpcszLocation, lpcszDriverKey, lpcszHubFile, pList); + if (RT_SUCCESS(rc)) + { + pDev->pNext = *ppDevs; + *ppDevs = pDev; + ++*pcDevs; + } + else + RTMemFree(pDev); + } + else + rc = VERR_NO_MEMORY; + + if (pCfgDr) + usbLibDevCfgDrFree(pCfgDr); + if (pList) + usbLibDevStrDrEntryFreeList(pList); + + return rc; +} + + +/* + * Enumerate the USB devices in the host system. Since we do not care about the hierarchical + * structure of root hubs, other hubs, and devices, we just ask the USB PnP enumerator to + * give us all it has. This includes hubs (though not root hubs), as well as multiple child + * interfaces of multi-interface USB devices, which we filter out. It also includes USB + * devices with no driver, which is notably something we cannot get by enumerating via + * GUID_DEVINTERFACE_USB_DEVICE. + * + * This approach also saves us some trouble relative to enumerating devices via hub IOCTLs and + * then hunting through the PnP manager to find them. Instead, we look up the device's parent + * which (for devices we're interested in) is always a hub, and that allows us to obtain + * USB-specific data (descriptors, speeds, etc.) when combined with the devices PnP "address" + * (USB port on parent hub). + * + * NB: Every USB device known to the Windows PnP Manager will have a device instance ID. Typically + * it also has a DriverKey but only if it has a driver installed. Hence we ignore the DriverKey, at + * least prior to capturing (once VBoxUSB.sys is installed, a DriverKey must by definition be + * present). Also note that the device instance ID changes for captured devices since we change + * their USB VID/PID, though it is unique at any given point. + * + * The location information should be a reliable way of identifying a device and does not change + * with driver installs, capturing, etc. USB device location information is only available on + * Windows Vista and later; earlier Windows version had no reliable way of cross-referencing the + * USB IOCTL and PnP Manager data. + */ +static int usbLibEnumDevices(PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + HDEVINFO InfoSet; + DWORD DeviceIndex; + LPDWORD Address; + SP_DEVINFO_DATA DeviceData; + LPCSTR ParentInstID; + LPCSTR HubPath = NULL; + LPCSTR Location; + LPCSTR DriverKey; + + /* Ask the USB PnP enumerator for all it has. */ + InfoSet = SetupDiGetClassDevs(NULL, "USB", NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT); + + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + DeviceIndex = 0; + + /* Enumerate everything in the info set. */ + while (SetupDiEnumDeviceInfo(InfoSet, DeviceIndex, &DeviceData)) + { + /* Use the CM API to get the parent instance ID. */ + ParentInstID = usbLibGetParentInstanceID(DeviceData.DevInst); + + /* Now figure out the hub's file path fron the instance ID, if there is one. */ + if (ParentInstID) + HubPath = usbLibGetHubPathFromInstanceID(ParentInstID); + + /* If there's no hub interface on the parent, then this might be a child + * device of a multi-interface device. Either way, we're not interested. + */ + if (HubPath) + { + /* The location information uniquely identifies the USB device, (hub/port). */ + Location = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_LOCATION_PATHS); + + /* The software key aka DriverKey. This will be NULL for devices with no driver + * and allows us to distinguish between 'busy' (driver installed) and 'available' + * (no driver) devices. + */ + DriverKey = (LPCSTR)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_DRIVER); + + /* The device's PnP Manager "address" is the port number on the parent hub. */ + Address = (LPDWORD)usbLibGetRegistryProperty(InfoSet, &DeviceData, SPDRP_ADDRESS); + if (Address && Location) /* NB: DriverKey may be NULL! */ + { + usbLibDevGetDevice(HubPath, *Address, Location, DriverKey, ppDevs, pcDevs); + } + RTMemFree((void *)HubPath); + + if (Location) + RTMemFree((void *)Location); + if (DriverKey) + RTMemFree((void *)DriverKey); + if (Address) + RTMemFree((void *)Address); + } + + /* Clean up after this device. */ + if (ParentInstID) + RTMemFree((void *)ParentInstID); + + ++DeviceIndex; + memset(&DeviceData, 0, sizeof(DeviceData)); + DeviceData.cbSize = sizeof(DeviceData); + } + + if (InfoSet) + SetupDiDestroyDeviceInfoList(InfoSet); + + return VINF_SUCCESS; +} + +#else +static int usbLibDevGetDevices(PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + char CtlName[16]; + int rc = VINF_SUCCESS; + + for (int i = 0; i < 10; ++i) + { + RTStrPrintf(CtlName, sizeof(CtlName), "\\\\.\\HCD%d", i); + HANDLE hCtl = CreateFile(CtlName, GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (hCtl != INVALID_HANDLE_VALUE) + { + char* lpszName; + rc = usbLibDevStrRootHubNameGet(hCtl, &lpszName); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibDevGetHubDevices(lpszName, ppDevs, pcDevs); + AssertRC(rc); + usbLibDevStrFree(lpszName); + } + CloseHandle(hCtl); + if (RT_FAILURE(rc)) + break; + } + } + return VINF_SUCCESS; +} +#endif + +static int usbLibMonDevicesCmp(PUSBDEVICE pDev, PVBOXUSB_DEV pDevInfo) +{ + int iDiff; + iDiff = strcmp(pDev->pszAddress, pDevInfo->szDriverRegName); + return iDiff; +} + +static int usbLibMonDevicesUpdate(PVBOXUSBGLOBALSTATE pGlobal, PUSBDEVICE pDevs, PVBOXUSB_DEV pDevInfos) +{ + + PUSBDEVICE pDevsHead = pDevs; + for (; pDevInfos; pDevInfos = pDevInfos->pNext) + { + for (pDevs = pDevsHead; pDevs; pDevs = pDevs->pNext) + { + if (usbLibMonDevicesCmp(pDevs, pDevInfos)) + continue; + + if (!pDevInfos->szDriverRegName[0]) + { + AssertFailed(); + break; + } + + USBSUP_GETDEV Dev = {0}; + HANDLE hDev = CreateFile(pDevInfos->szName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_SYSTEM, NULL); + if (hDev == INVALID_HANDLE_VALUE) + { + AssertFailed(); + break; + } + + DWORD cbReturned = 0; + if (!DeviceIoControl(hDev, SUPUSB_IOCTL_GET_DEVICE, &Dev, sizeof (Dev), &Dev, sizeof (Dev), &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + /* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */ + AsserFailed(); +#endif + LogRelFunc(("SUPUSB_IOCTL_GET_DEVICE failed on '%s' (dwErr=%u)!\n", pDevInfos->szName, dwErr)); + CloseHandle(hDev); + break; + } + + /* we must not close the handle until we request for the device state from the monitor to ensure + * the device handle returned by the device driver does not disappear */ + Assert(Dev.hDevice); + USBSUP_GETDEV_MON MonInfo; + HVBOXUSBDEVUSR hDevice = Dev.hDevice; + if (!DeviceIoControl(pGlobal->hMonitor, SUPUSBFLT_IOCTL_GET_DEVICE, &hDevice, sizeof (hDevice), &MonInfo, sizeof (MonInfo), &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + /* ERROR_DEVICE_NOT_CONNECTED -> device was removed just now */ + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_GET_DEVICE failed for '%s' (hDevice=%p, dwErr=%u)!\n", pDevInfos->szName, hDevice, dwErr)); + CloseHandle(hDev); + break; + } + + CloseHandle(hDev); + + /* success!! update device info */ + /* ensure the state returned is valid */ + Assert( MonInfo.enmState == USBDEVICESTATE_USED_BY_HOST + || MonInfo.enmState == USBDEVICESTATE_USED_BY_HOST_CAPTURABLE + || MonInfo.enmState == USBDEVICESTATE_UNUSED + || MonInfo.enmState == USBDEVICESTATE_HELD_BY_PROXY + || MonInfo.enmState == USBDEVICESTATE_USED_BY_GUEST); + pDevs->enmState = MonInfo.enmState; + + if (pDevs->enmState != USBDEVICESTATE_USED_BY_HOST) + { + /* only set the interface name if device can be grabbed */ + RTStrFree(pDevs->pszAltAddress); + pDevs->pszAltAddress = (char*)pDevs->pszAddress; + pDevs->pszAddress = RTStrDup(pDevInfos->szName); + } +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + else + { + /* dbg breakpoint */ + AssertFailed(); + } +#endif + + /* we've found the device, break in any way */ + break; + } + } + + return VINF_SUCCESS; +} + +static int usbLibGetDevices(PVBOXUSBGLOBALSTATE pGlobal, PUSBDEVICE *ppDevs, uint32_t *pcDevs) +{ + *ppDevs = NULL; + *pcDevs = 0; + + LogRelFunc(("Starting USB device enumeration\n")); +#ifdef VBOX_WITH_NEW_USB_ENUM + int rc = usbLibEnumDevices(ppDevs, pcDevs); +#else + int rc = usbLibDevGetDevices(ppDevs, pcDevs); +#endif + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + PVBOXUSB_DEV pDevInfos = NULL; + uint32_t cDevInfos = 0; +#ifdef VBOX_WITH_NEW_USB_ENUM + rc = usbLibEnumVUsbDevices(&pDevInfos, &cDevInfos); +#else + rc = usbLibVuGetDevices(&pDevInfos, &cDevInfos); +#endif + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + rc = usbLibMonDevicesUpdate(pGlobal, *ppDevs, pDevInfos); + AssertRC(rc); + usbLibVuFreeDevices(pDevInfos); + } + + LogRelFunc(("Found %u USB devices, %u captured\n", *pcDevs, cDevInfos)); + return VINF_SUCCESS; + } + return rc; +} + +AssertCompile(INFINITE == RT_INDEFINITE_WAIT); +static int usbLibStateWaitChange(PVBOXUSBGLOBALSTATE pGlobal, RTMSINTERVAL cMillies) +{ + HANDLE ahEvents[] = {pGlobal->hNotifyEvent, pGlobal->hInterruptEvent}; + DWORD dwResult = WaitForMultipleObjects(RT_ELEMENTS(ahEvents), ahEvents, + FALSE, /* BOOL bWaitAll */ + cMillies); + + switch (dwResult) + { + case WAIT_OBJECT_0: + return VINF_SUCCESS; + case WAIT_OBJECT_0 + 1: + return VERR_INTERRUPTED; + case WAIT_TIMEOUT: + return VERR_TIMEOUT; + default: + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("WaitForMultipleObjects failed, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + } +} + +AssertCompile(RT_INDEFINITE_WAIT == INFINITE); +AssertCompile(sizeof (RTMSINTERVAL) == sizeof (DWORD)); +USBLIB_DECL(int) USBLibWaitChange(RTMSINTERVAL msWaitTimeout) +{ + return usbLibStateWaitChange(&g_VBoxUsbGlobal, msWaitTimeout); +} + +static int usbLibInterruptWaitChange(PVBOXUSBGLOBALSTATE pGlobal) +{ + BOOL fRc = SetEvent(pGlobal->hInterruptEvent); + if (!fRc) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("SetEvent failed, dwErr (%u)\n", dwErr)); + return VERR_GENERAL_FAILURE; + } + return VINF_SUCCESS; +} + +USBLIB_DECL(int) USBLibInterruptWaitChange(void) +{ + return usbLibInterruptWaitChange(&g_VBoxUsbGlobal); +} + +/* +USBLIB_DECL(bool) USBLibHasPendingDeviceChanges(void) +{ + int rc = USBLibWaitChange(0); + return rc == VINF_SUCCESS; +} +*/ + +USBLIB_DECL(int) USBLibGetDevices(PUSBDEVICE *ppDevices, uint32_t *pcbNumDevices) +{ + Assert(g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE); + return usbLibGetDevices(&g_VBoxUsbGlobal, ppDevices, pcbNumDevices); +} + +USBLIB_DECL(void *) USBLibAddFilter(PCUSBFILTER pFilter) +{ + USBSUP_FLTADDOUT FltAddRc; + DWORD cbReturned = 0; + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return NULL; + } + + Log(("usblibInsertFilter: 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>")); + + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_ADD_FILTER, + (LPVOID)pFilter, sizeof(*pFilter), + &FltAddRc, sizeof(FltAddRc), + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_ADD_FILTER failed (dwErr=%u)!\n", dwErr)); + return NULL; + } + + if (RT_FAILURE(FltAddRc.rc)) + { + AssertFailed(); + LogRelFunc(("Adding a USB filter failed with rc=%d!\n", FltAddRc.rc)); + return NULL; + } + + LogRel(("Added USB filter (ID=%u, type=%d) for device %04X:%04X rev %04X, c/s/p %02X/%02X/%02X, Manufacturer=`%s' Product=`%s' Serial=`%s'\n", FltAddRc.uId, USBFilterGetFilterType(pFilter), + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + 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>")); + + return (void *)FltAddRc.uId; +} + + +USBLIB_DECL(void) USBLibRemoveFilter(void *pvId) +{ + uintptr_t uId; + DWORD cbReturned = 0; + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + return; + } + + Log(("usblibRemoveFilter %p\n", pvId)); + + uId = (uintptr_t)pvId; + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_REMOVE_FILTER, &uId, sizeof(uId), NULL, 0,&cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_REMOVE_FILTER failed (dwErr=%u)!\n", dwErr)); + } + else + LogRel(("Removed USB filter ID=%u\n", uId)); +} + +USBLIB_DECL(int) USBLibRunFilters(void) +{ + DWORD cbReturned = 0; + + Assert(g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE); + + if (!DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_RUN_FILTERS, + NULL, 0, + NULL, 0, + &cbReturned, NULL)) + { + DWORD dwErr = GetLastError(); + AssertFailed(); + LogRelFunc(("SUPUSBFLT_IOCTL_RUN_FILTERS failed (dwErr=%u)!\n", dwErr)); + return RTErrConvertFromWin32(dwErr); + } + + return VINF_SUCCESS; +} + + +static VOID CALLBACK usbLibTimerCallback(__in PVOID lpParameter, __in BOOLEAN TimerOrWaitFired) +{ + RT_NOREF2(lpParameter, TimerOrWaitFired); + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); +} + +static void usbLibOnDeviceChange(void) +{ + /* we're getting series of events like that especially on device re-attach + * (i.e. first for device detach and then for device attach) + * unfortunately the event does not tell us what actually happened. + * To avoid extra notifications, we delay the SetEvent via a timer + * and update the timer if additional notification comes before the timer fires + * */ + if (g_VBoxUsbGlobal.hTimer) + { + if (!DeleteTimerQueueTimer(g_VBoxUsbGlobal.hTimerQueue, g_VBoxUsbGlobal.hTimer, NULL)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(dwErr == ERROR_IO_PENDING, ("DeleteTimerQueueTimer failed, dwErr (%u)\n", dwErr)); + } + } + + if (!CreateTimerQueueTimer(&g_VBoxUsbGlobal.hTimer, g_VBoxUsbGlobal.hTimerQueue, + usbLibTimerCallback, + NULL, + 500, /* ms*/ + 0, + WT_EXECUTEONLYONCE)) + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateTimerQueueTimer failed, dwErr (%u)\n", dwErr)); + + /* call it directly */ + usbLibTimerCallback(NULL, FALSE); + } +} + +static LRESULT CALLBACK usbLibWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_DEVICECHANGE: + if (wParam == DBT_DEVNODES_CHANGED) + { + /* we notify change any device arivals/removals on the system + * and let the client decide whether the usb change actually happened + * so far this is more clean than reporting events from the Monitor + * because monitor sees only PDO arrivals/removals, + * and by the time PDO is created, device can not + * be yet started and fully functional, + * so usblib won't be able to pick it up + * */ + + usbLibOnDeviceChange(); + } + break; + case WM_DESTROY: + { + PostQuitMessage(0); + return 0; + } + } + return DefWindowProc (hwnd, uMsg, wParam, lParam); +} + +/** @todo r=bird: Use an IPRT thread? */ +static DWORD WINAPI usbLibMsgThreadProc(__in LPVOID lpParameter) +{ + static LPCSTR s_szVBoxUsbWndClassName = "VBoxUsbLibClass"; + const HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL); + RT_NOREF1(lpParameter); + + Assert(g_VBoxUsbGlobal.hWnd == NULL); + g_VBoxUsbGlobal.hWnd = NULL; + + /* + * Register the Window Class and create the hidden window. + */ + WNDCLASS wc; + wc.style = 0; + wc.lpfnWndProc = usbLibWndProc; + wc.cbClsExtra = 0; + wc.cbWndExtra = sizeof(void *); + wc.hInstance = hInstance; + wc.hIcon = NULL; + wc.hCursor = NULL; + wc.hbrBackground = (HBRUSH)(COLOR_BACKGROUND + 1); + wc.lpszMenuName = NULL; + wc.lpszClassName = s_szVBoxUsbWndClassName; + ATOM atomWindowClass = RegisterClass(&wc); + if (atomWindowClass != 0) + g_VBoxUsbGlobal.hWnd = CreateWindowEx(WS_EX_TOOLWINDOW | WS_EX_TRANSPARENT | WS_EX_TOPMOST, + s_szVBoxUsbWndClassName, s_szVBoxUsbWndClassName, + WS_POPUPWINDOW, + -200, -200, 100, 100, NULL, NULL, hInstance, NULL); + else + AssertMsgFailed(("RegisterClass failed, last error %u\n", GetLastError())); + + /* + * Signal the creator thread. + */ + ASMCompilerBarrier(); + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); + + if (g_VBoxUsbGlobal.hWnd) + { + /* Make sure it's really hidden. */ + SetWindowPos(g_VBoxUsbGlobal.hWnd, HWND_TOPMOST, -200, -200, 0, 0, + SWP_NOACTIVATE | SWP_HIDEWINDOW | SWP_NOCOPYBITS | SWP_NOREDRAW | SWP_NOSIZE); + + /* + * The message pump. + */ + MSG msg; + BOOL fRet; + while ((fRet = GetMessage(&msg, NULL, 0, 0)) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + Assert(fRet >= 0); + } + + if (atomWindowClass != NULL) + UnregisterClass(s_szVBoxUsbWndClassName, hInstance); + + return 0; +} + + +/** + * Initialize the USB library + * + * @returns VBox status code. + */ +USBLIB_DECL(int) USBLibInit(void) +{ + int rc = VERR_GENERAL_FAILURE; + + Log(("usbproxy: usbLibInit\n")); + + RT_ZERO(g_VBoxUsbGlobal); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + + /* + * Create the notification and interrupt event before opening the device. + */ + g_VBoxUsbGlobal.hNotifyEvent = CreateEvent(NULL, /* LPSECURITY_ATTRIBUTES lpEventAttributes */ + FALSE, /* BOOL bManualReset */ + FALSE, /* set to false since it will be initially used for notification thread startup sync */ + NULL /* LPCTSTR lpName */); + if (g_VBoxUsbGlobal.hNotifyEvent) + { + g_VBoxUsbGlobal.hInterruptEvent = CreateEvent(NULL, /* LPSECURITY_ATTRIBUTES lpEventAttributes */ + FALSE, /* BOOL bManualReset */ + FALSE, /* BOOL bInitialState */ + NULL /* LPCTSTR lpName */); + if (g_VBoxUsbGlobal.hInterruptEvent) + { + /* + * Open the USB monitor device, starting if needed. + */ + g_VBoxUsbGlobal.hMonitor = CreateFile(USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM, + NULL); + + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + HRESULT hr = VBoxDrvCfgSvcStart(USBMON_SERVICE_NAME_W); + if (hr == S_OK) + { + g_VBoxUsbGlobal.hMonitor = CreateFile(USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_SYSTEM, + NULL); + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + DWORD dwErr = GetLastError(); + LogRelFunc(("CreateFile failed (dwErr=%u) for `%s'\n", dwErr, USBMON_DEVICE_NAME)); + rc = VERR_FILE_NOT_FOUND; + } + } + } + + if (g_VBoxUsbGlobal.hMonitor != INVALID_HANDLE_VALUE) + { + /* + * Check the USB monitor version. + * + * Drivers are backwards compatible within the same major + * number. We consider the minor version number this library + * is compiled with to be the minimum required by the driver. + * This is by reasoning that the library uses the full feature + * set of the driver it's written for. + */ + USBSUP_VERSION Version = {0}; + DWORD cbReturned = 0; + if (DeviceIoControl(g_VBoxUsbGlobal.hMonitor, SUPUSBFLT_IOCTL_GET_VERSION, + NULL, 0, + &Version, sizeof (Version), + &cbReturned, NULL)) + { + if ( Version.u32Major == USBMON_MAJOR_VERSION +#if USBMON_MINOR_VERSION != 0 + && Version.u32Minor >= USBMON_MINOR_VERSION +#endif + ) + { + /* + * We can not use USB Mon for reliable device add/remove tracking + * since once USB Mon is notified about PDO creation and/or IRP_MN_START_DEVICE, + * the function device driver may still do some initialization, which might result in + * notifying too early. + * Instead we use WM_DEVICECHANGE + DBT_DEVNODES_CHANGED to make Windows notify us about + * device arivals/removals. + * Since WM_DEVICECHANGE is a window message, create a dedicated thread to be used for WndProc and stuff. + * The thread would create a window, track windows messages and call usbLibOnDeviceChange on WM_DEVICECHANGE arrival. + * See comments in usbLibOnDeviceChange function for detail about using the timer queue. + */ + g_VBoxUsbGlobal.hTimerQueue = CreateTimerQueue(); + if (g_VBoxUsbGlobal.hTimerQueue) + { + g_VBoxUsbGlobal.hThread = CreateThread( + NULL, /*__in_opt LPSECURITY_ATTRIBUTES lpThreadAttributes, */ + 0, /*__in SIZE_T dwStackSize, */ + usbLibMsgThreadProc, /*__in LPTHREAD_START_ROUTINE lpStartAddress,*/ + NULL, /*__in_opt LPVOID lpParameter,*/ + 0, /*__in DWORD dwCreationFlags,*/ + NULL /*__out_opt LPDWORD lpThreadId*/ + ); + if (g_VBoxUsbGlobal.hThread) + { + DWORD dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hNotifyEvent, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); + if (g_VBoxUsbGlobal.hWnd) + { + /* + * We're DONE! + * + * Just ensure that the event is set so the + * first "wait change" request is processed. + */ + SetEvent(g_VBoxUsbGlobal.hNotifyEvent); + return VINF_SUCCESS; + } + + dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hThread, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); + BOOL fRc = CloseHandle(g_VBoxUsbGlobal.hThread); NOREF(fRc); + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsg(fRc, ("CloseHandle for hThread failed (dwErr=%u)\n", dwErr)); + g_VBoxUsbGlobal.hThread = INVALID_HANDLE_VALUE; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateThread failed, (dwErr=%u)\n", dwErr)); + rc = VERR_GENERAL_FAILURE; + } + + DeleteTimerQueueEx(g_VBoxUsbGlobal.hTimerQueue, INVALID_HANDLE_VALUE /* see term */); + g_VBoxUsbGlobal.hTimerQueue = NULL; + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + AssertMsgFailed(("CreateTimerQueue failed (dwErr=%u)\n", dwErr)); + } + } + else + { + LogRelFunc(("USB Monitor driver version mismatch! driver=%u.%u library=%u.%u\n", + Version.u32Major, Version.u32Minor, USBMON_MAJOR_VERSION, USBMON_MINOR_VERSION)); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + rc = VERR_VERSION_MISMATCH; + } + } + else + { + DWORD dwErr = GetLastError(); NOREF(dwErr); + LogRelFunc(("SUPUSBFLT_IOCTL_GET_VERSION failed (dwErr=%u)\n", dwErr)); + AssertFailed(); + rc = VERR_VERSION_MISMATCH; + } + + CloseHandle(g_VBoxUsbGlobal.hMonitor); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + } + else + { + LogRelFunc(("USB Service not found\n")); +#ifdef VBOX_WITH_ANNOYING_USB_ASSERTIONS + AssertFailed(); +#endif + rc = VERR_FILE_NOT_FOUND; + } + + CloseHandle(g_VBoxUsbGlobal.hInterruptEvent); + g_VBoxUsbGlobal.hInterruptEvent = NULL; + } + else + { + AssertMsgFailed(("CreateEvent for InterruptEvent failed (dwErr=%u)\n", GetLastError())); + rc = VERR_GENERAL_FAILURE; + } + + CloseHandle(g_VBoxUsbGlobal.hNotifyEvent); + g_VBoxUsbGlobal.hNotifyEvent = NULL; + } + else + { + AssertMsgFailed(("CreateEvent for NotifyEvent failed (dwErr=%u)\n", GetLastError())); + rc = VERR_GENERAL_FAILURE; + } + + /* since main calls us even if USBLibInit fails, + * we use hMonitor == INVALID_HANDLE_VALUE as a marker to indicate whether the lib is inited */ + + Assert(RT_FAILURE(rc)); + return rc; +} + + +/** + * Terminate the USB library + * + * @returns VBox status code. + */ +USBLIB_DECL(int) USBLibTerm(void) +{ + if (g_VBoxUsbGlobal.hMonitor == INVALID_HANDLE_VALUE) + { + Assert(g_VBoxUsbGlobal.hInterruptEvent == NULL); + Assert(g_VBoxUsbGlobal.hNotifyEvent == NULL); + return VINF_SUCCESS; + } + + BOOL fRc; + fRc = PostMessage(g_VBoxUsbGlobal.hWnd, WM_CLOSE, 0, 0); + AssertMsg(fRc, ("PostMessage for hWnd failed (dwErr=%u)\n", GetLastError())); + + if (g_VBoxUsbGlobal.hThread != NULL) + { + DWORD dwResult = WaitForSingleObject(g_VBoxUsbGlobal.hThread, INFINITE); + Assert(dwResult == WAIT_OBJECT_0); NOREF(dwResult); + fRc = CloseHandle(g_VBoxUsbGlobal.hThread); + AssertMsg(fRc, ("CloseHandle for hThread failed (dwErr=%u)\n", GetLastError())); + } + + if (g_VBoxUsbGlobal.hTimer) + { + fRc = DeleteTimerQueueTimer(g_VBoxUsbGlobal.hTimerQueue, g_VBoxUsbGlobal.hTimer, + INVALID_HANDLE_VALUE); /* <-- to block until the timer is completed */ + AssertMsg(fRc, ("DeleteTimerQueueTimer failed (dwErr=%u)\n", GetLastError())); + } + + if (g_VBoxUsbGlobal.hTimerQueue) + { + fRc = DeleteTimerQueueEx(g_VBoxUsbGlobal.hTimerQueue, + INVALID_HANDLE_VALUE); /* <-- to block until all timers are completed */ + AssertMsg(fRc, ("DeleteTimerQueueEx failed (dwErr=%u)\n", GetLastError())); + } + + fRc = CloseHandle(g_VBoxUsbGlobal.hMonitor); + AssertMsg(fRc, ("CloseHandle for hMonitor failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hMonitor = INVALID_HANDLE_VALUE; + + fRc = CloseHandle(g_VBoxUsbGlobal.hInterruptEvent); + AssertMsg(fRc, ("CloseHandle for hInterruptEvent failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hInterruptEvent = NULL; + + fRc = CloseHandle(g_VBoxUsbGlobal.hNotifyEvent); + AssertMsg(fRc, ("CloseHandle for hNotifyEvent failed (dwErr=%u)\n", GetLastError())); + g_VBoxUsbGlobal.hNotifyEvent = NULL; + + return VINF_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf new file mode 100644 index 00000000..81adcf25 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUSBMon.inf @@ -0,0 +1,88 @@ +; $Id: VBoxUSBMon.inf $ +;; @file +; VBox USB Monitor driver - Installation file +; + +; +; Copyright (C) 2011-2020 Oracle Corporation +; +; 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. +; +; 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. +; + +[Version] +Signature="$Windows NT$" +Class=System +ClassGuid={4d36e97d-e325-11ce-bfc1-08002be10318} +Provider=%ORACLE% +;edit-DriverVer=08/26/2008,2.00.0000 +DriverPackageType=KernelService +;cat CatalogFile=VBoxUSBMon.cat + +[DestinationDirs] +DefaultDestDir = 12 + +[DefaultInstall@DOT-NT-ARCH@] +CopyFiles=VBoxUSBMon_CopyFiles + +[DefaultInstall@DOT-NT-ARCH@.Services] +AddService=VBoxUSBMon,0x00000002,VBoxUSBMon_Service,VBoxUSBMon_AddEventLog + +;; Cannot get this to work (same works fine for VBoxDrv): +;; [Manufacturer] +;; %ORACLE%=Oracle@COMMA-NT-ARCH@ +;; +;; ; Models section (referenced by [Manufacturer]). +;; [Oracle@DOT-NT-ARCH@] +;; %VBoxUSBMon.DRVDESC%=VBoxUSBMonInstall,root\VBoxUSBMon +;; +;; [VBoxUSBMonInstall@DOT-NT-ARCH@] +;; CopyFiles=VBoxUSBMon_CopyFiles +;; +;; [VBoxUSBMonInstall@DOT-NT-ARCH@.Services] +;; AddService=VBoxUSBMon,0x00000002,VBoxUSBMon_Service,VBoxUSBMon_AddEventLog + +[SourceDisksFiles] +VBoxUSBMon.sys=1 + +[SourceDisksNames] +1=%VBoxUSBMon.DSKDESC%, + +[VBoxUSBMon_CopyFiles] +VBoxUSBMon.sys + +[VBoxUSBMon_Service] +DisplayName = %VBoxUSBMon.SVCDESC% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +;StartType = 3 ; SERVICE_DEMAND_START +StartType = 1 ; autostart to fix Vista problem +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\VBoxUSBMon.sys + +[VBoxUSBMon_AddEventLog] +AddReg = VBoxUSBMon_AddEventLogRegistry + +[VBoxUSBMon_AddEventLogRegistry] +HKR,,EventMessageFile,0x00020000,"%%SystemRoot%%\System32\IoLogMsg.dll" +HKR,,TypesSupported,0x00010001,7 + +[Strings] +ORACLE = "Oracle Corporation" +VBoxUSBMon.SVCDESC = "VirtualBox USB Monitor Service" +VBoxUSBMon.DRVDESC = "VirtualBox USB Monitor Driver" +VBoxUSBMon.DSKDESC = "VirtualBox USB Monitor Driver Installation Disk" + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp new file mode 100644 index 00000000..f09bab58 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.cpp @@ -0,0 +1,1753 @@ +/* $Id: VBoxUsbFlt.cpp $ */ +/** @file + * VBox USB Monitor Device Filtering functionality + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" +#include "../cmn/VBoxUsbTool.h" + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <iprt/process.h> +#include <iprt/assert.h> +#include <iprt/errcore.h> + +#include <iprt/assert.h> + +#pragma warning(disable : 4200) +#include "usbdi.h" +#pragma warning(default : 4200) +#include "usbdlib.h" +#include "VBoxUSBFilterMgr.h" +#include <VBox/usblib.h> +#include <devguid.h> +#include <devpkey.h> + + +/* We should be including ntifs.h but that's not as easy as it sounds. */ +extern "C" { +NTKERNELAPI PDEVICE_OBJECT IoGetDeviceAttachmentBaseRef(__in PDEVICE_OBJECT DeviceObject); +} + +/* + * state transitions: + * + * (we are not filtering this device ) + * ADDED --> UNCAPTURED ------------------------------->- + * | | + * | (we are filtering this device, | (the device is being + * | waiting for our device driver | re-plugged to perform + * | to pick it up) | capture-uncapture transition) + * |-> CAPTURING -------------------------------->|---> REPLUGGING ----- + * ^ | (device driver picked | | + * | | up the device) | (remove cased | (device is removed + * | ->---> CAPTURED ---------------------->| by "real" removal | the device info is removed form the list) + * | | |------------------->->--> REMOVED + * | | | + * |-----------<->---> USED_BY_GUEST ------->| + * | | + * |------------------------<- + * + * NOTE: the order of enums DOES MATTER!! + * Do not blindly modify!! as the code assumes the state is ordered this way. + */ +typedef enum +{ + VBOXUSBFLT_DEVSTATE_UNKNOWN = 0, + VBOXUSBFLT_DEVSTATE_REMOVED, + VBOXUSBFLT_DEVSTATE_REPLUGGING, + VBOXUSBFLT_DEVSTATE_ADDED, + VBOXUSBFLT_DEVSTATE_UNCAPTURED, + VBOXUSBFLT_DEVSTATE_CAPTURING, + VBOXUSBFLT_DEVSTATE_CAPTURED, + VBOXUSBFLT_DEVSTATE_USED_BY_GUEST, + VBOXUSBFLT_DEVSTATE_32BIT_HACK = 0x7fffffff +} VBOXUSBFLT_DEVSTATE; + +typedef struct VBOXUSBFLT_DEVICE +{ + LIST_ENTRY GlobalLe; + /* auxiliary list to be used for gathering devices to be re-plugged + * only thread that puts the device to the REPLUGGING state can use this list */ + LIST_ENTRY RepluggingLe; + /* Owning session. Each matched device has an owning session. */ + struct VBOXUSBFLTCTX *pOwner; + /* filter id - if NULL AND device has an owner - the filter is destroyed */ + uintptr_t uFltId; + /* true iff device is filtered with a one-shot filter */ + bool fIsFilterOneShot; + /* true if descriptors could not be read and only inferred from PnP Manager data */ + bool fInferredDesc; + /* The device state. If the non-owner session is requesting the state while the device is grabbed, + * the USBDEVICESTATE_USED_BY_HOST is returned. */ + VBOXUSBFLT_DEVSTATE enmState; + volatile uint32_t cRefs; + PDEVICE_OBJECT Pdo; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint16_t bPort; + uint8_t bClass; + uint8_t bSubClass; + uint8_t bProtocol; + char szSerial[MAX_USB_SERIAL_STRING]; + char szMfgName[MAX_USB_SERIAL_STRING]; + char szProduct[MAX_USB_SERIAL_STRING]; + WCHAR szLocationPath[768]; +#if 0 + char szDrvKeyName[512]; + BOOLEAN fHighSpeed; +#endif +} VBOXUSBFLT_DEVICE, *PVBOXUSBFLT_DEVICE; + +#define PVBOXUSBFLT_DEVICE_FROM_LE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, GlobalLe) ) ) +#define PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(_pLe) ( (PVBOXUSBFLT_DEVICE)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_DEVICE, RepluggingLe) ) ) +#define PVBOXUSBFLTCTX_FROM_LE(_pLe) ( (PVBOXUSBFLTCTX)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLTCTX, ListEntry) ) ) + +typedef struct VBOXUSBFLT_LOCK +{ + KSPIN_LOCK Lock; + KIRQL OldIrql; +} VBOXUSBFLT_LOCK, *PVBOXUSBFLT_LOCK; + +#define VBOXUSBFLT_LOCK_INIT() \ + KeInitializeSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock) +#define VBOXUSBFLT_LOCK_TERM() do { } while (0) +#define VBOXUSBFLT_LOCK_ACQUIRE() \ + KeAcquireSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, &g_VBoxUsbFltGlobals.Lock.OldIrql); +#define VBOXUSBFLT_LOCK_RELEASE() \ + KeReleaseSpinLock(&g_VBoxUsbFltGlobals.Lock.Lock, g_VBoxUsbFltGlobals.Lock.OldIrql); + + +typedef struct VBOXUSBFLT_BLDEV +{ + LIST_ENTRY ListEntry; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; +} VBOXUSBFLT_BLDEV, *PVBOXUSBFLT_BLDEV; + +#define PVBOXUSBFLT_BLDEV_FROM_LE(_pLe) ( (PVBOXUSBFLT_BLDEV)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBFLT_BLDEV, ListEntry) ) ) + +typedef struct VBOXUSBFLTGLOBALS +{ + LIST_ENTRY DeviceList; + LIST_ENTRY ContextList; + /* devices known to misbehave */ + LIST_ENTRY BlackDeviceList; + VBOXUSBFLT_LOCK Lock; + /** Flag whether to force replugging a device we can't query descirptors from. + * Short term workaround for @bugref{9479}. */ + ULONG dwForceReplugWhenDevPopulateFails; +} VBOXUSBFLTGLOBALS, *PVBOXUSBFLTGLOBALS; +static VBOXUSBFLTGLOBALS g_VBoxUsbFltGlobals; + +static bool vboxUsbFltBlDevMatchLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice) +{ + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_BLDEV pDev = PVBOXUSBFLT_BLDEV_FROM_LE(pEntry); + if (pDev->idVendor != idVendor) + continue; + if (pDev->idProduct != idProduct) + continue; + if (pDev->bcdDevice != bcdDevice) + continue; + + return true; + } + return false; +} + +static NTSTATUS vboxUsbFltBlDevAddLocked(uint16_t idVendor, uint16_t idProduct, uint16_t bcdDevice) +{ + if (vboxUsbFltBlDevMatchLocked(idVendor, idProduct, bcdDevice)) + return STATUS_SUCCESS; + PVBOXUSBFLT_BLDEV pDev = (PVBOXUSBFLT_BLDEV)VBoxUsbMonMemAllocZ(sizeof (*pDev)); + if (!pDev) + { + AssertFailed(); + return STATUS_INSUFFICIENT_RESOURCES; + } + + pDev->idVendor = idVendor; + pDev->idProduct = idProduct; + pDev->bcdDevice = bcdDevice; + InsertHeadList(&g_VBoxUsbFltGlobals.BlackDeviceList, &pDev->ListEntry); + return STATUS_SUCCESS; +} + +static void vboxUsbFltBlDevClearLocked() +{ + PLIST_ENTRY pNext; + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.BlackDeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.BlackDeviceList; + pEntry = pNext) + { + pNext = pEntry->Flink; + VBoxUsbMonMemFree(pEntry); + } +} + +static void vboxUsbFltBlDevPopulateWithKnownLocked() +{ + /* this one halts when trying to get string descriptors from it */ + vboxUsbFltBlDevAddLocked(0x5ac, 0x921c, 0x115); +} + + +DECLINLINE(void) vboxUsbFltDevRetain(PVBOXUSBFLT_DEVICE pDevice) +{ + Assert(pDevice->cRefs); + ASMAtomicIncU32(&pDevice->cRefs); +} + +static void vboxUsbFltDevDestroy(PVBOXUSBFLT_DEVICE pDevice) +{ + Assert(!pDevice->cRefs); + Assert(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED); + VBoxUsbMonMemFree(pDevice); +} + +DECLINLINE(void) vboxUsbFltDevRelease(PVBOXUSBFLT_DEVICE pDevice) +{ + uint32_t cRefs = ASMAtomicDecU32(&pDevice->cRefs); + Assert(cRefs < UINT32_MAX/2); + if (!cRefs) + { + vboxUsbFltDevDestroy(pDevice); + } +} + +static void vboxUsbFltDevOwnerSetLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot) +{ + ASSERT_WARN(!pDevice->pOwner, ("device 0x%p has an owner(0x%p)", pDevice, pDevice->pOwner)); + ++pContext->cActiveFilters; + pDevice->pOwner = pContext; + pDevice->uFltId = uFltId; + pDevice->fIsFilterOneShot = fIsOneShot; +} + +static void vboxUsbFltDevOwnerClearLocked(PVBOXUSBFLT_DEVICE pDevice) +{ + ASSERT_WARN(pDevice->pOwner, ("no owner for device 0x%p", pDevice)); + --pDevice->pOwner->cActiveFilters; + ASSERT_WARN(pDevice->pOwner->cActiveFilters < UINT32_MAX/2, ("cActiveFilters (%d)", pDevice->pOwner->cActiveFilters)); + pDevice->pOwner = NULL; + pDevice->uFltId = 0; +} + +static void vboxUsbFltDevOwnerUpdateLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext, uintptr_t uFltId, bool fIsOneShot) +{ + if (pDevice->pOwner != pContext) + { + if (pDevice->pOwner) + vboxUsbFltDevOwnerClearLocked(pDevice); + if (pContext) + vboxUsbFltDevOwnerSetLocked(pDevice, pContext, uFltId, fIsOneShot); + } + else if (pContext) + { + pDevice->uFltId = uFltId; + pDevice->fIsFilterOneShot = fIsOneShot; + } +} + +static PVBOXUSBFLT_DEVICE vboxUsbFltDevGetLocked(PDEVICE_OBJECT pPdo) +{ +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + for (PLIST_ENTRY pEntry2 = pEntry->Flink; + pEntry2 != &g_VBoxUsbFltGlobals.DeviceList; + pEntry2 = pEntry2->Flink) + { + PVBOXUSBFLT_DEVICE pDevice2 = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry2); + ASSERT_WARN( pDevice->idVendor != pDevice2->idVendor + || pDevice->idProduct != pDevice2->idProduct + || pDevice->bcdDevice != pDevice2->bcdDevice, ("duplicate devices in a list!!")); + } + } +#endif + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + ASSERT_WARN( pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST, + ("Invalid device state(%d) for device(0x%p) PDO(0x%p)", pDevice->enmState, pDevice, pDevice->Pdo)); + if (pDevice->Pdo == pPdo) + return pDevice; + } + return NULL; +} + +static NTSTATUS vboxUsbFltPdoReplug(PDEVICE_OBJECT pDo) +{ + LOG(("Replugging PDO(0x%p)", pDo)); + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_CYCLE_PORT, NULL, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("replugging PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Replugging PDO(0x%p) done with Status(0x%x)", pDo, Status)); + return Status; +} + +static bool vboxUsbFltDevCanBeCaptured(PVBOXUSBFLT_DEVICE pDevice) +{ + if (pDevice->bClass == USB_DEVICE_CLASS_HUB) + { + LOG(("device (0x%p), pdo (0x%p) is a hub, can not be captured", pDevice, pDevice->Pdo)); + return false; + } + return true; +} + +static PVBOXUSBFLTCTX vboxUsbFltDevMatchLocked(PVBOXUSBFLT_DEVICE pDevice, uintptr_t *puId, bool fRemoveFltIfOneShot, bool *pfFilter, bool *pfIsOneShot) +{ + *puId = 0; + *pfFilter = false; + *pfIsOneShot = false; + if (!vboxUsbFltDevCanBeCaptured(pDevice)) + { + LOG(("vboxUsbFltDevCanBeCaptured returned false")); + return NULL; + } + + USBFILTER DevFlt; + USBFilterInit(&DevFlt, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_VENDOR_ID, pDevice->idVendor, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PRODUCT_ID, pDevice->idProduct, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_REV, pDevice->bcdDevice, true); + + /* If we could not read a string descriptor, don't set the filter item at all. */ + if (pDevice->szMfgName[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_MANUFACTURER_STR, pDevice->szMfgName, true /*fMustBePresent*/, true /*fPurge*/); + if (pDevice->szProduct[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_PRODUCT_STR, pDevice->szProduct, true /*fMustBePresent*/, true /*fPurge*/); + if (pDevice->szSerial[0]) + USBFilterSetStringExact(&DevFlt, USBFILTERIDX_SERIAL_NUMBER_STR, pDevice->szSerial, true /*fMustBePresent*/, true /*fPurge*/); + + /* If device descriptor had to be inferred from PnP Manager data, the class/subclass/protocol may be wrong. + * When Windows reports CompatibleIDs 'USB\Class_03&SubClass_00&Prot_00', the device descriptor might be + * reporting class 3 (HID), *or* the device descriptor might be reporting class 0 (specified by interface) + * and the device's interface reporting class 3. Ignore the class/subclass/protocol in such case, since + * we are more or less guaranteed to rely on VID/PID anyway. + * See @bugref{9479}. + */ + if (pDevice->fInferredDesc) + { + LOG(("Device descriptor was not read, only inferred; ignoring class/subclass/protocol!")); + } + else + { + LOG(("Setting filter class/subclass/protocol %02X/%02X/%02X\n", pDevice->bClass, pDevice->bSubClass, pDevice->bProtocol)); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_CLASS, pDevice->bClass, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_SUB_CLASS, pDevice->bSubClass, true); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_DEVICE_PROTOCOL, pDevice->bProtocol, true); + } + + /* If the port number looks valid, add it to the filter. */ + if (pDevice->bPort) + { + LOG(("Setting filter port %04X\n", pDevice->bPort)); + USBFilterSetNumExact(&DevFlt, USBFILTERIDX_PORT, pDevice->bPort, true); + } + else + LOG(("Port number not known, ignoring!")); + + /* Run filters on the thing. */ + PVBOXUSBFLTCTX pOwner = VBoxUSBFilterMatchEx(&DevFlt, puId, fRemoveFltIfOneShot, pfFilter, pfIsOneShot); + USBFilterDelete(&DevFlt); + return pOwner; +} + +static void vboxUsbFltDevStateMarkReplugLocked(PVBOXUSBFLT_DEVICE pDevice) +{ + vboxUsbFltDevOwnerUpdateLocked(pDevice, NULL, 0, false); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REPLUGGING; +} + +static bool vboxUsbFltDevStateIsNotFiltered(PVBOXUSBFLT_DEVICE pDevice) +{ + return pDevice->enmState == VBOXUSBFLT_DEVSTATE_UNCAPTURED; +} + +static bool vboxUsbFltDevStateIsFiltered(PVBOXUSBFLT_DEVICE pDevice) +{ + return pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; +} + +static uint16_t vboxUsbParseHexNumU16(WCHAR **ppStr) +{ + WCHAR *pStr = *ppStr; + WCHAR wc; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 4; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + wc = *pStr; + u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */ + num |= u << (12 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static uint8_t vboxUsbParseHexNumU8(WCHAR **ppStr) +{ + WCHAR *pStr = *ppStr; + WCHAR wc; + uint16_t num = 0; + unsigned u; + + for (int i = 0; i < 2; ++i) + { + if (!*pStr) /* Just in case the string is too short. */ + break; + + wc = *pStr; + u = wc >= 'A' ? wc - 'A' + 10 : wc - '0'; /* Hex digit to number. */ + num |= u << (4 - 4 * i); + pStr++; + } + *ppStr = pStr; + + return num; +} + +static bool vboxUsbParseHardwareID(WCHAR *pchIdStr, uint16_t *pVid, uint16_t *pPid, uint16_t *pRev) +{ +#define VID_PREFIX L"USB\\VID_" +#define PID_PREFIX L"&PID_" +#define REV_PREFIX L"&REV_" + + *pVid = *pPid = *pRev = 0xFFFF; + + /* The Hardware ID is in the format USB\VID_xxxx&PID_xxxx&REV_xxxx, with 'xxxx' + * being 16-bit hexadecimal numbers. The string is coming from the + * Windows PnP manager so OEMs should have no opportunity to mess it up. + */ + + if (wcsncmp(pchIdStr, VID_PREFIX, wcslen(VID_PREFIX))) + return false; + /* Point to the start of the vendor ID number and parse it. */ + pchIdStr += wcslen(VID_PREFIX); + *pVid = vboxUsbParseHexNumU16(&pchIdStr); + + if (wcsncmp(pchIdStr, PID_PREFIX, wcslen(PID_PREFIX))) + return false; + /* Point to the start of the product ID number and parse it. */ + pchIdStr += wcslen(PID_PREFIX); + *pPid = vboxUsbParseHexNumU16(&pchIdStr); + + /* The revision might not be there; the Windows documentation is not + * entirely clear if it will be always present for USB devices or not. + * If it's not there, still consider this a success. */ + if (wcsncmp(pchIdStr, REV_PREFIX, wcslen(REV_PREFIX))) + return true; + + /* Point to the start of the revision number and parse it. */ + pchIdStr += wcslen(REV_PREFIX); + *pRev = vboxUsbParseHexNumU16(&pchIdStr); + + return true; +#undef VID_PREFIX +#undef PID_PREFIX +#undef REV_PREFIX +} + +static bool vboxUsbParseCompatibleIDs(WCHAR *pchIdStr, uint8_t *pClass, uint8_t *pSubClass, uint8_t *pProt) +{ +#define CLS_PREFIX L"USB\\Class_" +#define SUB_PREFIX L"&SubClass_" +#define PRO_PREFIX L"&Prot_" + + *pClass = *pSubClass = *pProt = 0xFF; + + /* The Compatible IDs string is in the format USB\Class_xx&SubClass_xx&Prot_xx, + * with 'xx' being 8-bit hexadecimal numbers. Since this string is provided by the + * PnP manager and USB devices always report these as part of the basic USB device + * descriptor, we assume all three must be present. + */ + + if (wcsncmp(pchIdStr, CLS_PREFIX, wcslen(CLS_PREFIX))) + return false; + /* Point to the start of the device class and parse it. */ + pchIdStr += wcslen(CLS_PREFIX); + *pClass = vboxUsbParseHexNumU8(&pchIdStr); + + if (wcsncmp(pchIdStr, SUB_PREFIX, wcslen(SUB_PREFIX))) + return false; + + /* Point to the start of the subclass and parse it. */ + pchIdStr += wcslen(SUB_PREFIX); + *pSubClass = vboxUsbParseHexNumU8(&pchIdStr); + + if (wcsncmp(pchIdStr, PRO_PREFIX, wcslen(PRO_PREFIX))) + return false; + + /* Point to the start of the protocol and parse it. */ + pchIdStr += wcslen(PRO_PREFIX); + *pProt = vboxUsbParseHexNumU8(&pchIdStr); + + return true; +#undef CLS_PREFIX +#undef SUB_PREFIX +#undef PRO_PREFIX +} + +#define VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS 10000 + +static NTSTATUS vboxUsbFltDevPopulate(PVBOXUSBFLT_DEVICE pDevice, PDEVICE_OBJECT pDo /*, BOOLEAN bPopulateNonFilterProps*/) +{ + NTSTATUS Status; + USB_TOPOLOGY_ADDRESS TopoAddr; + PUSB_DEVICE_DESCRIPTOR pDevDr = 0; + ULONG ulResultLen; + DEVPROPTYPE type; + WCHAR wchPropBuf[256]; + uint16_t port; + bool rc; + + pDevice->Pdo = pDo; + + LOG(("Populating Device(0x%p) for PDO(0x%p)", pDevice, pDo)); + + pDevDr = (PUSB_DEVICE_DESCRIPTOR)VBoxUsbMonMemAllocZ(sizeof(*pDevDr)); + if (pDevDr == NULL) + { + WARN(("Failed to alloc mem for urb")); + return STATUS_INSUFFICIENT_RESOURCES; + } + + do + { + pDevice->fInferredDesc = false; + Status = VBoxUsbToolGetDescriptor(pDo, pDevDr, sizeof(*pDevDr), USB_DEVICE_DESCRIPTOR_TYPE, 0, 0, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + uint16_t vid, pid, rev; + uint8_t cls, sub, prt; + + WARN(("getting device descriptor failed, Status (0x%x); falling back to IoGetDeviceProperty", Status)); + + /* Try falling back to IoGetDevicePropertyData. */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_HardwareIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* This just isn't our day. We have no idea what the device is. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_HardwareIds, Status (0x%x)", Status)); + break; + } + rc = vboxUsbParseHardwareID(wchPropBuf, &vid, &pid, &rev); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + break; + } + + /* Now grab the Compatible IDs to get the class/subclass/protocol. */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_CompatibleIds, LOCALE_NEUTRAL, 0, sizeof(wchPropBuf), wchPropBuf, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* We really kind of need these. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_CompatibleIds, Status (0x%x)", Status)); + break; + } + rc = vboxUsbParseCompatibleIDs(wchPropBuf, &cls, &sub, &prt); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + break; + } + + LOG(("Parsed HardwareID: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt)); + if (vid == 0xFFFF || pid == 0xFFFF) + break; + + LOG(("Successfully fell back to IoGetDeviceProperty result")); + pDevDr->idVendor = vid; + pDevDr->idProduct = pid; + pDevDr->bcdDevice = rev; + pDevDr->bDeviceClass = cls; + pDevDr->bDeviceSubClass = sub; + pDevDr->bDeviceProtocol = prt; + + /* The USB device class/subclass/protocol may not be accurate. We have to be careful when comparing + * and not take mismatches too seriously. + */ + pDevice->fInferredDesc = true; + } + + /* Query the location path. The path is purely a function of the physical device location + * and does not change if the device changes, and also does not change depending on + * whether the device is captured or not. + * NB: We ignore any additional strings and only look at the first one. + */ + Status = IoGetDevicePropertyData(pDo, &DEVPKEY_Device_LocationPaths, LOCALE_NEUTRAL, 0, sizeof(pDevice->szLocationPath), pDevice->szLocationPath, &ulResultLen, &type); + if (!NT_SUCCESS(Status)) + { + /* We do need this, but not critically. On Windows 7, we may get STATUS_OBJECT_NAME_NOT_FOUND. */ + WARN(("IoGetDevicePropertyData failed for DEVPKEY_Device_LocationPaths, Status (0x%x)", Status)); + } + else + { + LOG_STRW(pDevice->szLocationPath); + } + + // Disabled, but could be used as a fallback instead of IoGetDevicePropertyData; it should work even + // when this code is entered from the PnP IRP processing path. +#if 0 + { + HUB_DEVICE_CONFIG_INFO HubInfo; + + memset(&HubInfo, 0, sizeof(HubInfo)); + HubInfo.Version = 1; + HubInfo.Length = sizeof(HubInfo); + + NTSTATUS Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_DEVICE_CONFIG_INFO, &HubInfo, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_DEVICE_CONFIG_INFO for PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Querying hub device config info for PDO(0x%p) done with Status(0x%x)", pDo, Status)); + + if (Status == STATUS_SUCCESS) + { + uint16_t vid, pid, rev; + uint8_t cls, sub, prt; + + LOG(("Hub flags: %X\n", HubInfo.HubFlags)); + LOG_STRW(HubInfo.HardwareIds.Buffer); + LOG_STRW(HubInfo.CompatibleIds.Buffer); + if (HubInfo.DeviceDescription.Buffer) + LOG_STRW(HubInfo.DeviceDescription.Buffer); + + rc = vboxUsbParseHardwareID(HubInfo.HardwareIds.Buffer, &pid, &vid, &rev); + if (!rc) + { + /* This *really* should not happen. */ + WARN(("Failed to parse Hardware ID")); + } + + /* The CompatibleID the IOCTL gives is not always the same as what the PnP Manager uses + * (thanks, Microsoft). It might look like "USB\DevClass_00&SubClass_00&Prot_00" or like + * "USB\USB30_HUB". In such cases, we must consider the class/subclass/protocol + * information simply unavailable. + */ + rc = vboxUsbParseCompatibleIDs(HubInfo.CompatibleIds.Buffer, &cls, &sub, &prt); + if (!rc) + { + /* This is unfortunate but not fatal. */ + WARN(("Failed to parse Compatible ID")); + } + LOG(("Parsed HardwareID from IOCTL: vid=%04X, pid=%04X, rev=%04X, class=%02X, subcls=%02X, prot=%02X", vid, pid, rev, cls, sub, prt)); + + ExFreePool(HubInfo.HardwareIds.Buffer); + ExFreePool(HubInfo.CompatibleIds.Buffer); + if (HubInfo.DeviceDescription.Buffer) + ExFreePool(HubInfo.DeviceDescription.Buffer); + } + } +#endif + + /* Query the topology address from the hub driver. This is not trivial to translate to the location + * path, but at least we can get the port number this way. + */ + memset(&TopoAddr, 0, sizeof(TopoAddr)); + Status = VBoxUsbToolIoInternalCtlSendSync(pDo, IOCTL_INTERNAL_USB_GET_TOPOLOGY_ADDRESS, &TopoAddr, NULL); + ASSERT_WARN(Status == STATUS_SUCCESS, ("GET_TOPOLOGY_ADDRESS for PDO(0x%p) failed Status(0x%x)", pDo, Status)); + LOG(("Querying topology address for PDO(0x%p) done with Status(0x%x)", pDo, Status)); + + port = 0; + if (Status == STATUS_SUCCESS) + { + uint16_t *pPort = &TopoAddr.RootHubPortNumber; + + /* The last non-zero port number is the one we're looking for. It might be on the + * root hub directly, or on some downstream hub. + */ + for (int i = 0; i < RT_ELEMENTS(TopoAddr.HubPortNumber) + 1; ++i) { + if (*pPort) + port = *pPort; + pPort++; + } + LOG(("PCI bus/dev/fn: %02X:%02X:%02X, parsed port: %u\n", TopoAddr.PciBusNumber, TopoAddr.PciDeviceNumber, TopoAddr.PciFunctionNumber, port)); + LOG(("RH port: %u, hub ports: %u/%u/%u/%u/%u/%u\n", TopoAddr.RootHubPortNumber, TopoAddr.HubPortNumber[0], + TopoAddr.HubPortNumber[1], TopoAddr.HubPortNumber[2], TopoAddr.HubPortNumber[3], TopoAddr.HubPortNumber[4], TopoAddr.HubPortNumber[5])); + + /* In the extremely unlikely case that the port number does not fit into 8 bits, force + * it to zero to indicate that we can't use it. + */ + if (port > 255) + port = 0; + } + + if (vboxUsbFltBlDevMatchLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)) + { + WARN(("found a known black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + Status = STATUS_UNSUCCESSFUL; + break; + } + + LOG(("Device pid=%x vid=%x rev=%x port=%x", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice, port)); + pDevice->bPort = port; + pDevice->idVendor = pDevDr->idVendor; + pDevice->idProduct = pDevDr->idProduct; + pDevice->bcdDevice = pDevDr->bcdDevice; + pDevice->bClass = pDevDr->bDeviceClass; + pDevice->bSubClass = pDevDr->bDeviceSubClass; + pDevice->bProtocol = pDevDr->bDeviceProtocol; + pDevice->szSerial[0] = 0; + pDevice->szMfgName[0] = 0; + pDevice->szProduct[0] = 0; + + /* If there are no strings, don't even try to get any string descriptors. */ + if (pDevDr->iSerialNumber || pDevDr->iManufacturer || pDevDr->iProduct) + { + int langId; + + Status = VBoxUsbToolGetLangID(pDo, &langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading language ID failed")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + } + break; + } + + if (pDevDr->iSerialNumber) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szSerial, sizeof (pDevice->szSerial), pDevDr->iSerialNumber, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading serial number failed")); + ASSERT_WARN(pDevice->szSerial[0] == '\0', ("serial is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + if (pDevDr->iManufacturer) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szMfgName, sizeof (pDevice->szMfgName), pDevDr->iManufacturer, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading manufacturer name failed")); + ASSERT_WARN(pDevice->szMfgName[0] == '\0', ("szMfgName is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + if (pDevDr->iProduct) + { + Status = VBoxUsbToolGetStringDescriptor(pDo, pDevice->szProduct, sizeof (pDevice->szProduct), pDevDr->iProduct, langId, VBOXUSBMON_POPULATE_REQUEST_TIMEOUT_MS); + if (!NT_SUCCESS(Status)) + { + WARN(("reading product name failed")); + ASSERT_WARN(pDevice->szProduct[0] == '\0', ("szProduct is not zero!!")); + if (Status == STATUS_CANCELLED) + { + WARN(("found a new black list device, vid(0x%x), pid(0x%x), rev(0x%x)", pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice)); + vboxUsbFltBlDevAddLocked(pDevDr->idVendor, pDevDr->idProduct, pDevDr->bcdDevice); + Status = STATUS_UNSUCCESSFUL; + break; + } + LOG(("pretending success..")); + Status = STATUS_SUCCESS; + } + } + + LOG((": strings: '%s':'%s':'%s' (lang ID %x)", + pDevice->szMfgName, pDevice->szProduct, pDevice->szSerial, langId)); + } + + LOG(("Populating Device(0x%p) for PDO(0x%p) Succeeded", pDevice, pDo)); + Status = STATUS_SUCCESS; + } while (0); + + VBoxUsbMonMemFree(pDevDr); + LOG(("Populating Device(0x%p) for PDO(0x%p) Done, Status (0x%x)", pDevice, pDo, Status)); + return Status; +} + +static bool vboxUsbFltDevCheckReplugLocked(PVBOXUSBFLT_DEVICE pDevice, PVBOXUSBFLTCTX pContext) +{ + ASSERT_WARN(pContext, ("context is NULL!")); + + LOG(("Current context is (0x%p)", pContext)); + LOG(("Current Device owner is (0x%p)", pDevice->pOwner)); + + /* check if device is already replugging */ + if (pDevice->enmState <= VBOXUSBFLT_DEVSTATE_ADDED) + { + LOG(("Device (0x%p) is already replugging, return..", pDevice)); + /* it is, do nothing */ + ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING, + ("Device (0x%p) state is NOT REPLUGGING (%d)", pDevice, pDevice->enmState)); + return false; + } + + if (pDevice->pOwner && pContext != pDevice->pOwner) + { + LOG(("Device (0x%p) is owned by another context(0x%p), current is(0x%p)", pDevice, pDevice->pOwner, pContext)); + /* this device is owned by another context, we're not allowed to do anything */ + return false; + } + + uintptr_t uId = 0; + bool bNeedReplug = false; + bool fFilter = false; + bool fIsOneShot = false; + PVBOXUSBFLTCTX pNewOwner = vboxUsbFltDevMatchLocked(pDevice, &uId, + false, /* do not remove a one-shot filter */ + &fFilter, &fIsOneShot); + LOG(("Matching Info: Filter (0x%p), NewOwner(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pNewOwner, (int)fFilter, (int)fIsOneShot)); + if (pDevice->pOwner && pNewOwner && pDevice->pOwner != pNewOwner) + { + LOG(("Matching: Device (0x%p) is requested another owner(0x%p), current is(0x%p)", pDevice, pNewOwner, pDevice->pOwner)); + /* the device is owned by another owner, we can not change the owner here */ + return false; + } + + if (!fFilter) + { + LOG(("Matching: Device (0x%p) should NOT be filtered", pDevice)); + /* the device should NOT be filtered, check the current state */ + if (vboxUsbFltDevStateIsNotFiltered(pDevice)) + { + LOG(("Device (0x%p) is NOT filtered", pDevice)); + /* no changes */ + if (fIsOneShot) + { + ASSERT_WARN(pNewOwner, ("no new owner")); + LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId)); + /* remove a one-shot filter and keep the original filter data */ + int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId); + ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc)); + if (!pDevice->pOwner) + { + LOG(("Matching: updating the one-shot owner to (0x%p), fltId(0x%p)", pNewOwner, uId)); + /* update owner for one-shot if the owner is changed (i.e. assigned) */ + vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, true); + } + else + { + LOG(("Matching: device already has owner (0x%p) assigned", pDevice->pOwner)); + } + } + else + { + LOG(("Matching: This is NOT a one-shot filter (0x%p), newOwner(0x%p)", uId, pNewOwner)); + if (pNewOwner) + { + vboxUsbFltDevOwnerUpdateLocked(pDevice, pNewOwner, uId, false); + } + } + } + else + { + LOG(("Device (0x%p) IS filtered", pDevice)); + /* the device is currently filtered, we should release it only if + * 1. device does not have an owner + * or + * 2. it should be released bue to a one-shot filter + * or + * 3. it is NOT grabbed by a one-shot filter */ + if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot) + { + LOG(("Matching: Need replug")); + bNeedReplug = true; + } + } + } + else + { + LOG(("Matching: Device (0x%p) SHOULD be filtered", pDevice)); + /* the device should be filtered, check the current state */ + ASSERT_WARN(uId, ("zero uid")); + ASSERT_WARN(pNewOwner, ("zero pNewOwner")); + if (vboxUsbFltDevStateIsFiltered(pDevice)) + { + LOG(("Device (0x%p) IS filtered", pDevice)); + /* the device is filtered */ + if (pNewOwner == pDevice->pOwner) + { + LOG(("Device owner match")); + /* no changes */ + if (fIsOneShot) + { + LOG(("Matching: This is a one-shot filter (0x%p), removing..", uId)); + /* remove a one-shot filter and keep the original filter data */ + int tmpRc = VBoxUSBFilterRemove(pNewOwner, uId); + ASSERT_WARN(RT_SUCCESS(tmpRc), ("remove filter failed, rc (%d)", tmpRc)); + } + else + { + LOG(("Matching: This is NOT a one-shot filter (0x%p), Owner(0x%p)", uId, pDevice->pOwner)); + vboxUsbFltDevOwnerUpdateLocked(pDevice, pDevice->pOwner, uId, false); + } + } + else + { + ASSERT_WARN(!pDevice->pOwner, ("device should NOT have owner")); + LOG(("Matching: Need replug")); + /* the device needs to be filtered, but the owner changes, replug needed */ + bNeedReplug = true; + } + } + else + { + /* the device is currently NOT filtered, + * we should replug it only if + * 1. device does not have an owner + * or + * 2. it should be captured due to a one-shot filter + * or + * 3. it is NOT released by a one-shot filter */ + if (!pDevice->pOwner || fIsOneShot || !pDevice->fIsFilterOneShot) + { + bNeedReplug = true; + LOG(("Matching: Need replug")); + } + } + } + + if (bNeedReplug) + { + LOG(("Matching: Device needs replugging, marking as such")); + vboxUsbFltDevStateMarkReplugLocked(pDevice); + } + else + { + LOG(("Matching: Device does NOT need replugging")); + } + + return bNeedReplug; +} + +static void vboxUsbFltReplugList(PLIST_ENTRY pList) +{ + PLIST_ENTRY pNext; + for (PLIST_ENTRY pEntry = pList->Flink; + pEntry != pList; + pEntry = pNext) + { + pNext = pEntry->Flink; + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_REPLUGGINGLE(pEntry); + LOG(("replugging matched PDO(0x%p), pDevice(0x%p)", pDevice->Pdo, pDevice)); + ASSERT_WARN(pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_REMOVED, + ("invalid state(0x%x) for device(0x%p)", pDevice->enmState, pDevice)); + + vboxUsbFltPdoReplug(pDevice->Pdo); + ObDereferenceObject(pDevice->Pdo); + vboxUsbFltDevRelease(pDevice); + } +} + +typedef struct VBOXUSBFLTCHECKWALKER +{ + PVBOXUSBFLTCTX pContext; +} VBOXUSBFLTCHECKWALKER, *PVBOXUSBFLTCHECKWALKER; + +static DECLCALLBACK(BOOLEAN) vboxUsbFltFilterCheckWalker(PFILE_OBJECT pHubFile, + PDEVICE_OBJECT pHubDo, PVOID pvContext) +{ + PVBOXUSBFLTCHECKWALKER pData = (PVBOXUSBFLTCHECKWALKER)pvContext; + PVBOXUSBFLTCTX pContext = pData->pContext; + + LOG(("Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext)); + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql)); + + PDEVICE_RELATIONS pDevRelations = NULL; + + NTSTATUS Status = VBoxUsbMonQueryBusRelations(pHubDo, pHubFile, &pDevRelations); + if (Status == STATUS_SUCCESS && pDevRelations) + { + ULONG cReplugPdos = pDevRelations->Count; + LIST_ENTRY ReplugDevList; + InitializeListHead(&ReplugDevList); + for (ULONG k = 0; k < pDevRelations->Count; ++k) + { + PDEVICE_OBJECT pDevObj; + + /* Grab the PDO+reference. We won't need the upper layer device object + * anymore, so dereference that right here, and drop the PDO ref later. + */ + pDevObj = IoGetDeviceAttachmentBaseRef(pDevRelations->Objects[k]); + LOG(("DevObj=%p, PDO=%p\n", pDevRelations->Objects[k], pDevObj)); + ObDereferenceObject(pDevRelations->Objects[k]); + pDevRelations->Objects[k] = pDevObj; + + LOG(("Found existing USB PDO 0x%p", pDevObj)); + VBOXUSBFLT_LOCK_ACQUIRE(); + PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pDevObj); + if (pDevice) + { + LOG(("Found existing device info (0x%p) for PDO 0x%p", pDevice, pDevObj)); + bool bReplug = vboxUsbFltDevCheckReplugLocked(pDevice, pContext); + if (bReplug) + { + LOG(("Replug needed for device (0x%p)", pDevice)); + InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe); + vboxUsbFltDevRetain(pDevice); + /* do not dereference object since we will use it later */ + } + else + { + LOG(("Replug NOT needed for device (0x%p)", pDevice)); + ObDereferenceObject(pDevObj); + } + + VBOXUSBFLT_LOCK_RELEASE(); + + pDevRelations->Objects[k] = NULL; + --cReplugPdos; + ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos(%d) state broken", cReplugPdos)); + continue; + } + VBOXUSBFLT_LOCK_RELEASE(); + + LOG(("NO device info found for PDO 0x%p", pDevObj)); + VBOXUSBFLT_DEVICE Device; + Status = vboxUsbFltDevPopulate(&Device, pDevObj /*, FALSE /* only need filter properties */); + if (NT_SUCCESS(Status)) + { + uintptr_t uId = 0; + bool fFilter = false; + bool fIsOneShot = false; + VBOXUSBFLT_LOCK_ACQUIRE(); + PVBOXUSBFLTCTX pCtx = vboxUsbFltDevMatchLocked(&Device, &uId, + false, /* do not remove a one-shot filter */ + &fFilter, &fIsOneShot); + VBOXUSBFLT_LOCK_RELEASE(); + NOREF(pCtx); + LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot)); + if (fFilter) + { + LOG(("Matching: This device SHOULD be filtered")); + /* this device needs to be filtered, but it's not, + * leave the PDO in array to issue a replug request for it + * later on */ + continue; + } + } + else + { + WARN(("vboxUsbFltDevPopulate for PDO 0x%p failed with Status 0x%x", pDevObj, Status)); + if ( Status == STATUS_CANCELLED + && g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails) + { + /* + * This can happen if the device got suspended and is in D3 state where we can't query any strings. + * There is no known way to set the power state of the device, especially if there is no driver attached yet. + * The sledgehammer approach is to just replug the device to force it out of suspend, see bugref @{9479}. + */ + continue; + } + } + + LOG(("Matching: This device should NOT be filtered")); + /* this device should not be filtered, and it's not */ + ObDereferenceObject(pDevObj); + pDevRelations->Objects[k] = NULL; + --cReplugPdos; + ASSERT_WARN((uint32_t)cReplugPdos < UINT32_MAX/2, ("cReplugPdos is %d", cReplugPdos)); + } + + LOG(("(%d) non-matched PDOs to be replugged", cReplugPdos)); + + if (cReplugPdos) + { + for (ULONG k = 0; k < pDevRelations->Count; ++k) + { + if (!pDevRelations->Objects[k]) + continue; + + Status = vboxUsbFltPdoReplug(pDevRelations->Objects[k]); + ASSERT_WARN(Status == STATUS_SUCCESS, ("vboxUsbFltPdoReplug failed! Status(0x%x)", Status)); + ObDereferenceObject(pDevRelations->Objects[k]); + if (!--cReplugPdos) + break; + } + + ASSERT_WARN(!cReplugPdos, ("cReplugPdos reached zero!")); + } + + vboxUsbFltReplugList(&ReplugDevList); + + ExFreePool(pDevRelations); + } + else + { + WARN(("VBoxUsbMonQueryBusRelations failed for hub DO(0x%p), Status(0x%x), pDevRelations(0x%p)", + pHubDo, Status, pDevRelations)); + } + + LOG(("Done Visiting pHubFile(0x%p), pHubDo(0x%p), oContext(0x%p)", pHubFile, pHubDo, pContext)); + + return TRUE; +} + +NTSTATUS VBoxUsbFltFilterCheck(PVBOXUSBFLTCTX pContext) +{ + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("unexpected IRQL (%d)", Irql)); + + LOG(("Running filters, Context (0x%p)..", pContext)); + + VBOXUSBFLTCHECKWALKER Data; + Data.pContext = pContext; + vboxUsbMonHubDevWalk(vboxUsbFltFilterCheckWalker, &Data); + + LOG(("DONE Running filters, Context (0x%p)", pContext)); + + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltClose(PVBOXUSBFLTCTX pContext) +{ + LOG(("Closing context(0x%p)", pContext)); + LIST_ENTRY ReplugDevList; + InitializeListHead(&ReplugDevList); + + ASSERT_WARN(pContext, ("null context")); + + KIRQL Irql = KeGetCurrentIrql(); + ASSERT_WARN(Irql == PASSIVE_LEVEL, ("irql==(%d)", Irql)); + + VBOXUSBFLT_LOCK_ACQUIRE(); + + pContext->bRemoved = TRUE; + RemoveEntryList(&pContext->ListEntry); + + LOG(("removing owner filters")); + /* now re-arrange the filters */ + /* 1. remove filters */ + VBoxUSBFilterRemoveOwner(pContext); + + LOG(("enumerating devices..")); + /* 2. check if there are devices owned */ + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + if (pDevice->pOwner != pContext) + continue; + + LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)", + pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + + vboxUsbFltDevOwnerClearLocked(pDevice); + + if (vboxUsbFltDevCheckReplugLocked(pDevice, pContext)) + { + LOG(("device needs replug")); + InsertHeadList(&ReplugDevList, &pDevice->RepluggingLe); + /* retain to ensure the device is not removed before we issue a replug */ + vboxUsbFltDevRetain(pDevice); + /* keep the PDO alive */ + ObReferenceObject(pDevice->Pdo); + } + else + { + LOG(("device does NOT need replug")); + } + } + + VBOXUSBFLT_LOCK_RELEASE(); + + /* this should replug all devices that were either skipped or grabbed due to the context's */ + vboxUsbFltReplugList(&ReplugDevList); + + LOG(("SUCCESS done context(0x%p)", pContext)); + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltCreate(PVBOXUSBFLTCTX pContext) +{ + LOG(("Creating context(0x%p)", pContext)); + memset(pContext, 0, sizeof (*pContext)); + pContext->Process = RTProcSelf(); + VBOXUSBFLT_LOCK_ACQUIRE(); + InsertHeadList(&g_VBoxUsbFltGlobals.ContextList, &pContext->ListEntry); + VBOXUSBFLT_LOCK_RELEASE(); + LOG(("SUCCESS context(0x%p)", pContext)); + return STATUS_SUCCESS; +} + +int VBoxUsbFltAdd(PVBOXUSBFLTCTX pContext, PUSBFILTER pFilter, uintptr_t *pId) +{ + LOG(("adding filter, Context (0x%p)..", pContext)); + *pId = 0; + /* LOG the filter details. */ + LOG((__FUNCTION__": %s %s %s", + 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>")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("VBoxUSBClient::addFilter: idVendor=%#x idProduct=%#x bcdDevice=%#x bDeviceClass=%#x bDeviceSubClass=%#x bDeviceProtocol=%#x bBus=%#x bPort=%#x Type%#x", + USBFilterGetNum(pFilter, USBFILTERIDX_VENDOR_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_PRODUCT_ID), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_REV), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS), + USBFilterGetNum(pFilter, USBFILTERIDX_DEVICE_PROTOCOL), + USBFilterGetNum(pFilter, USBFILTERIDX_BUS), + USBFilterGetNum(pFilter, USBFILTERIDX_PORT), + USBFilterGetFilterType(pFilter))); +#endif + + /* We can't get the bus/port numbers. Ignore them while matching. */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_BUS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PORT, false); + + /* We may not be able to reconstruct the class/subclass/protocol if we aren't able to + * read the device descriptor. Don't require these to be present. See also the fInferredDesc flag. + */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_CLASS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_SUB_CLASS, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_DEVICE_PROTOCOL, false); + + /* We may also be unable to read string descriptors. Often the userland can't read the + * string descriptors either because the device is in a low-power state, but it can happen + * that the userland gets lucky and reads the strings, but by the time we get to read them + * they're inaccessible due to power management. So, don't require the strings to be present. + */ + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_MANUFACTURER_STR, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_PRODUCT_STR, false); + USBFilterSetMustBePresent(pFilter, USBFILTERIDX_SERIAL_NUMBER_STR, false); + + uintptr_t uId = 0; + VBOXUSBFLT_LOCK_ACQUIRE(); + /* Add the filter. */ + int rc = VBoxUSBFilterAdd(pFilter, pContext, &uId); + VBOXUSBFLT_LOCK_RELEASE(); + if (RT_SUCCESS(rc)) + { + LOG(("ADDED filter id 0x%p", uId)); + ASSERT_WARN(uId, ("uid is NULL")); +#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUsbFltFilterCheck(); +#endif + } + else + { + WARN(("VBoxUSBFilterAdd failed rc (%d)", rc)); + ASSERT_WARN(!uId, ("uid is not NULL")); + } + + *pId = uId; + return rc; +} + +int VBoxUsbFltRemove(PVBOXUSBFLTCTX pContext, uintptr_t uId) +{ + LOG(("removing filter id(0x%p), Context (0x%p)..", pContext, uId)); + Assert(uId); + + VBOXUSBFLT_LOCK_ACQUIRE(); + int rc = VBoxUSBFilterRemove(pContext, uId); + if (!RT_SUCCESS(rc)) + { + WARN(("VBoxUSBFilterRemove failed rc (%d)", rc)); + VBOXUSBFLT_LOCK_RELEASE(); + return rc; + } + + LOG(("enumerating devices..")); + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + if (pDevice->fIsFilterOneShot) + { + ASSERT_WARN(!pDevice->uFltId, ("oneshot filter on device(0x%p): unexpected uFltId(%d)", pDevice, pDevice->uFltId)); + } + + if (pDevice->uFltId != uId) + continue; + + ASSERT_WARN(pDevice->pOwner == pContext, ("Device(0x%p) owner(0x%p) not match to (0x%p)", pDevice, pDevice->pOwner, pContext)); + if (pDevice->pOwner != pContext) + continue; + + LOG(("found device(0x%p), pdo(0x%p), state(%d), filter id(0x%p), oneshot(%d)", + pDevice, pDevice->Pdo, pDevice->enmState, pDevice->uFltId, (int)pDevice->fIsFilterOneShot)); + ASSERT_WARN(!pDevice->fIsFilterOneShot, ("device(0x%p) is filtered with a oneshot filter", pDevice)); + pDevice->uFltId = 0; + /* clear the fIsFilterOneShot flag to ensure the device is replugged on the next VBoxUsbFltFilterCheck call */ + pDevice->fIsFilterOneShot = false; + } + VBOXUSBFLT_LOCK_RELEASE(); + + LOG(("done enumerating devices")); + + if (RT_SUCCESS(rc)) + { +#ifdef VBOX_USBMON_WITH_FILTER_AUTOAPPLY + VBoxUsbFltFilterCheck(); +#endif + } + return rc; +} + +static USBDEVICESTATE vboxUsbDevGetUserState(PVBOXUSBFLTCTX pContext, PVBOXUSBFLT_DEVICE pDevice) +{ + if (vboxUsbFltDevStateIsNotFiltered(pDevice)) + return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + + /* the device is filtered, or replugging */ + if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_REPLUGGING) + { + ASSERT_WARN(!pDevice->pOwner, ("replugging device(0x%p) still has an owner(0x%p)", pDevice, pDevice->pOwner)); + ASSERT_WARN(!pDevice->uFltId, ("replugging device(0x%p) still has filter(0x%p)", pDevice, pDevice->uFltId)); + /* no user state for this, we should not return it tu the user */ + return USBDEVICESTATE_USED_BY_HOST; + } + + /* the device is filtered, if owner differs from the context, return as USED_BY_HOST */ + ASSERT_WARN(pDevice->pOwner, ("device(0x%p) has noowner", pDevice)); + /* the id can be null if a filter is removed */ +// Assert(pDevice->uFltId); + + if (pDevice->pOwner != pContext) + { + LOG(("Device owner differs from the current context, returning used by host")); + return USBDEVICESTATE_USED_BY_HOST; + } + + switch (pDevice->enmState) + { + case VBOXUSBFLT_DEVSTATE_UNCAPTURED: + case VBOXUSBFLT_DEVSTATE_CAPTURING: + return USBDEVICESTATE_USED_BY_HOST_CAPTURABLE; + case VBOXUSBFLT_DEVSTATE_CAPTURED: + return USBDEVICESTATE_HELD_BY_PROXY; + case VBOXUSBFLT_DEVSTATE_USED_BY_GUEST: + return USBDEVICESTATE_USED_BY_GUEST; + default: + WARN(("unexpected device state(%d) for device(0x%p)", pDevice->enmState, pDevice)); + return USBDEVICESTATE_UNSUPPORTED; + } +} + +NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo) +{ + if (!hDevice) + return STATUS_INVALID_PARAMETER; + + memset (pInfo, 0, sizeof (*pInfo)); + VBOXUSBFLT_LOCK_ACQUIRE(); + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pEntry->Flink) + { + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED); + Assert(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED); + + if (pDevice != hDevice) + continue; + + USBDEVICESTATE enmUsrState = vboxUsbDevGetUserState(pContext, pDevice); + pInfo->enmState = enmUsrState; + VBOXUSBFLT_LOCK_RELEASE(); + return STATUS_SUCCESS; + } + + VBOXUSBFLT_LOCK_RELEASE(); + + /* We should not get this far with valid input. */ + return STATUS_INVALID_PARAMETER; +} + +NTSTATUS VBoxUsbFltPdoAdd(PDEVICE_OBJECT pPdo, BOOLEAN *pbFiltered) +{ + *pbFiltered = FALSE; + PVBOXUSBFLT_DEVICE pDevice; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + /* first check if device is in the a already */ + VBOXUSBFLT_LOCK_ACQUIRE(); + pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + { + LOG(("found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + *pbFiltered = pDevice->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + return STATUS_SUCCESS; + } + VBOXUSBFLT_LOCK_RELEASE(); + pDevice = (PVBOXUSBFLT_DEVICE)VBoxUsbMonMemAllocZ(sizeof (*pDevice)); + if (!pDevice) + { + WARN(("VBoxUsbMonMemAllocZ failed")); + ObDereferenceObject(pPdo); + return STATUS_NO_MEMORY; + } + + pDevice->enmState = VBOXUSBFLT_DEVSTATE_ADDED; + pDevice->cRefs = 1; + NTSTATUS Status = vboxUsbFltDevPopulate(pDevice, pPdo /* , TRUE /* need all props */); + if (!NT_SUCCESS(Status)) + { + WARN(("vboxUsbFltDevPopulate failed, Status 0x%x", Status)); + ObDereferenceObject(pPdo); + VBoxUsbMonMemFree(pDevice); + return Status; + } + + uintptr_t uId; + bool fFilter = false; + bool fIsOneShot = false; + PVBOXUSBFLTCTX pCtx; + PVBOXUSBFLT_DEVICE pTmpDev; + VBOXUSBFLT_LOCK_ACQUIRE(); + /* (paranoia) re-check the device is still not here */ + pTmpDev = vboxUsbFltDevGetLocked(pPdo); + + /* Drop the PDO ref, now we won't need it anymore. */ + ObDereferenceObject(pPdo); + + if (pTmpDev) + { + LOG(("second try: found device (0x%p), state(%d) for PDO(0x%p)", pDevice, pDevice->enmState, pPdo)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_ADDED, ("second try: VBOXUSBFLT_DEVSTATE_ADDED state for device(0x%p)", pDevice)); + ASSERT_WARN(pDevice->enmState != VBOXUSBFLT_DEVSTATE_REMOVED, ("second try: VBOXUSBFLT_DEVSTATE_REMOVED state for device(0x%p)", pDevice)); + *pbFiltered = pTmpDev->enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; + VBOXUSBFLT_LOCK_RELEASE(); + VBoxUsbMonMemFree(pDevice); + return STATUS_SUCCESS; + } + + LOG(("Created Device 0x%p for PDO 0x%p", pDevice, pPdo)); + + pCtx = vboxUsbFltDevMatchLocked(pDevice, &uId, + true, /* remove a one-shot filter */ + &fFilter, &fIsOneShot); + LOG(("Matching Info: Filter (0x%p), pCtx(0x%p), fFilter(%d), fIsOneShot(%d)", uId, pCtx, (int)fFilter, (int)fIsOneShot)); + if (fFilter) + { + LOG(("Created Device 0x%p should be filtered", pDevice)); + ASSERT_WARN(pCtx, ("zero ctx")); + ASSERT_WARN(uId, ("zero uId")); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING; + } + else + { + LOG(("Created Device 0x%p should NOT be filtered", pDevice)); + ASSERT_WARN(!uId == !pCtx, ("invalid uid(0x%p) - ctx(0x%p) pair", uId, pCtx)); /* either both zero or both not */ + pDevice->enmState = VBOXUSBFLT_DEVSTATE_UNCAPTURED; + } + + if (pCtx) + vboxUsbFltDevOwnerSetLocked(pDevice, pCtx, fIsOneShot ? 0 : uId, fIsOneShot); + + InsertHeadList(&g_VBoxUsbFltGlobals.DeviceList, &pDevice->GlobalLe); + + /* do not need to signal anything here - + * going to do that once the proxy device object starts */ + VBOXUSBFLT_LOCK_RELEASE(); + + *pbFiltered = fFilter; + + return STATUS_SUCCESS; +} + +BOOLEAN VBoxUsbFltPdoIsFiltered(PDEVICE_OBJECT pPdo) +{ + VBOXUSBFLT_DEVSTATE enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + VBOXUSBFLT_LOCK_ACQUIRE(); + + PVBOXUSBFLT_DEVICE pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + enmState = pDevice->enmState; + + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + + return enmState >= VBOXUSBFLT_DEVSTATE_CAPTURING; +} + +NTSTATUS VBoxUsbFltPdoRemove(PDEVICE_OBJECT pPdo) +{ + PVBOXUSBFLT_DEVICE pDevice; + VBOXUSBFLT_DEVSTATE enmOldState; + + /* Find the real PDO+reference. Dereference when we're done with it. Note that + * the input pPdo was not explicitly referenced so we're not dropping its ref. + */ + PDEVICE_OBJECT pDevObj = IoGetDeviceAttachmentBaseRef(pPdo); + LOG(("DevObj=%p, real PDO=%p\n", pPdo, pDevObj)); + pPdo = pDevObj; + + VBOXUSBFLT_LOCK_ACQUIRE(); + pDevice = vboxUsbFltDevGetLocked(pPdo); + if (pDevice) + { + RemoveEntryList(&pDevice->GlobalLe); + enmOldState = pDevice->enmState; + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + } + VBOXUSBFLT_LOCK_RELEASE(); + ObDereferenceObject(pPdo); + if (pDevice) + vboxUsbFltDevRelease(pDevice); + return STATUS_SUCCESS; +} + +HVBOXUSBFLTDEV VBoxUsbFltProxyStarted(PDEVICE_OBJECT pPdo) +{ + PVBOXUSBFLT_DEVICE pDevice; + VBOXUSBFLT_LOCK_ACQUIRE(); + + /* NB: The USB proxy (VBoxUSB.sys) passes us the real PDO, not anything above that. */ + pDevice = vboxUsbFltDevGetLocked(pPdo); + /* + * Prevent a host crash when vboxUsbFltDevGetLocked fails to locate the matching PDO + * in g_VBoxUsbFltGlobals.DeviceList (see @bugref{6509}). + */ + if (pDevice == NULL) + { + WARN(("failed to get device for PDO(0x%p)", pPdo)); + } + else if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURING) + { + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURED; + LOG(("The proxy notified proxy start for the captured device 0x%p", pDevice)); + vboxUsbFltDevRetain(pDevice); + } + else + { + WARN(("invalid state, %d", pDevice->enmState)); + pDevice = NULL; + } + VBOXUSBFLT_LOCK_RELEASE(); + return pDevice; +} + +void VBoxUsbFltProxyStopped(HVBOXUSBFLTDEV hDev) +{ + PVBOXUSBFLT_DEVICE pDevice = (PVBOXUSBFLT_DEVICE)hDev; + /* + * Prevent a host crash when VBoxUsbFltProxyStarted fails, returning NULL. + * See @bugref{6509}. + */ + if (pDevice == NULL) + { + WARN(("VBoxUsbFltProxyStopped called with NULL device pointer")); + return; + } + VBOXUSBFLT_LOCK_ACQUIRE(); + if (pDevice->enmState == VBOXUSBFLT_DEVSTATE_CAPTURED + || pDevice->enmState == VBOXUSBFLT_DEVSTATE_USED_BY_GUEST) + { + /* this is due to devie was physically removed */ + LOG(("The proxy notified proxy stop for the captured device 0x%p, current state %d", pDevice, pDevice->enmState)); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_CAPTURING; + } + else + { + if (pDevice->enmState != VBOXUSBFLT_DEVSTATE_REPLUGGING) + { + WARN(("invalid state, %d", pDevice->enmState)); + } + } + VBOXUSBFLT_LOCK_RELEASE(); + + vboxUsbFltDevRelease(pDevice); +} + + +static NTSTATUS vboxUsbFltRegKeyQuery(PWSTR ValueName, ULONG ValueType, PVOID ValueData, ULONG ValueLength, PVOID Context, PVOID EntryContext) +{ + NTSTATUS Status = STATUS_SUCCESS; + + RT_NOREF(ValueName, Context); + if ( ValueType == REG_DWORD + && ValueLength == sizeof(ULONG)) + *(ULONG *)EntryContext = *(ULONG *)ValueData; + else + Status = STATUS_OBJECT_TYPE_MISMATCH; + + return Status; +} + + +NTSTATUS VBoxUsbFltInit() +{ + int rc = VBoxUSBFilterInit(); + if (RT_FAILURE(rc)) + { + WARN(("VBoxUSBFilterInit failed, rc (%d)", rc)); + return STATUS_UNSUCCESSFUL; + } + + memset(&g_VBoxUsbFltGlobals, 0, sizeof (g_VBoxUsbFltGlobals)); + InitializeListHead(&g_VBoxUsbFltGlobals.DeviceList); + InitializeListHead(&g_VBoxUsbFltGlobals.ContextList); + InitializeListHead(&g_VBoxUsbFltGlobals.BlackDeviceList); + vboxUsbFltBlDevPopulateWithKnownLocked(); + VBOXUSBFLT_LOCK_INIT(); + + /* + * Check whether the setting to force replugging USB devices when + * querying string descriptors fail is set in the registry, + * see @bugref{9479}. + */ + RTL_QUERY_REGISTRY_TABLE aParams[] = + { + {vboxUsbFltRegKeyQuery, 0, L"ForceReplugWhenDevPopulateFails", &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, REG_DWORD, &g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails, sizeof(ULONG) }, + { NULL, 0, NULL, NULL, 0, 0, 0 } + }; + UNICODE_STRING UnicodePath = RTL_CONSTANT_STRING(L"\\VBoxUSB"); + + NTSTATUS Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, UnicodePath.Buffer, &aParams[0], NULL, NULL); + if (Status == STATUS_SUCCESS) + { + if (g_VBoxUsbFltGlobals.dwForceReplugWhenDevPopulateFails) + LOG(("Forcing replug of USB devices where querying the descriptors fail\n")); + } + else + LOG(("RtlQueryRegistryValues() -> %#x, assuming defaults\n", Status)); + + return STATUS_SUCCESS; +} + +NTSTATUS VBoxUsbFltTerm() +{ + bool bBusy = false; + VBOXUSBFLT_LOCK_ACQUIRE(); + do + { + if (!IsListEmpty(&g_VBoxUsbFltGlobals.ContextList)) + { + AssertFailed(); + bBusy = true; + break; + } + + PLIST_ENTRY pNext = NULL; + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = pNext) + { + pNext = pEntry->Flink; + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + Assert(!pDevice->uFltId); + Assert(!pDevice->pOwner); + if (pDevice->cRefs != 1) + { + AssertFailed(); + bBusy = true; + break; + } + } + } while (0); + + VBOXUSBFLT_LOCK_RELEASE() + + if (bBusy) + { + return STATUS_DEVICE_BUSY; + } + + for (PLIST_ENTRY pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink; + pEntry != &g_VBoxUsbFltGlobals.DeviceList; + pEntry = g_VBoxUsbFltGlobals.DeviceList.Flink) + { + RemoveEntryList(pEntry); + PVBOXUSBFLT_DEVICE pDevice = PVBOXUSBFLT_DEVICE_FROM_LE(pEntry); + pDevice->enmState = VBOXUSBFLT_DEVSTATE_REMOVED; + vboxUsbFltDevRelease(pDevice); + } + + vboxUsbFltBlDevClearLocked(); + + VBOXUSBFLT_LOCK_TERM(); + + VBoxUSBFilterTerm(); + + return STATUS_SUCCESS; +} + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h new file mode 100644 index 00000000..4c3767e2 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbFlt.h @@ -0,0 +1,65 @@ +/* $Id: VBoxUsbFlt.h $ */ +/** @file + * VBox USB Monitor Device Filtering functionality + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbMon.h" +#include <VBoxUSBFilterMgr.h> + +#include <VBox/usblib-win.h> + +typedef struct VBOXUSBFLTCTX +{ + LIST_ENTRY ListEntry; + RTPROCESS Process; // Purely informational, no function? + uint32_t cActiveFilters; + BOOLEAN bRemoved; // For debugging only? +} VBOXUSBFLTCTX, *PVBOXUSBFLTCTX; + +NTSTATUS VBoxUsbFltInit(); +NTSTATUS VBoxUsbFltTerm(); +NTSTATUS VBoxUsbFltCreate(PVBOXUSBFLTCTX pContext); +NTSTATUS VBoxUsbFltClose(PVBOXUSBFLTCTX pContext); +int VBoxUsbFltAdd(PVBOXUSBFLTCTX pContext, PUSBFILTER pFilter, uintptr_t *pId); +int VBoxUsbFltRemove(PVBOXUSBFLTCTX pContext, uintptr_t uId); +NTSTATUS VBoxUsbFltFilterCheck(PVBOXUSBFLTCTX pContext); + +NTSTATUS VBoxUsbFltGetDevice(PVBOXUSBFLTCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo); + +typedef void* HVBOXUSBFLTDEV; +HVBOXUSBFLTDEV VBoxUsbFltProxyStarted(PDEVICE_OBJECT pPdo); +void VBoxUsbFltProxyStopped(HVBOXUSBFLTDEV hDev); + +NTSTATUS VBoxUsbFltPdoAdd(PDEVICE_OBJECT pPdo, BOOLEAN *pbFiltered); +NTSTATUS VBoxUsbFltPdoRemove(PDEVICE_OBJECT pPdo); +BOOLEAN VBoxUsbFltPdoIsFiltered(PDEVICE_OBJECT pPdo); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbFlt_h */ + diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp new file mode 100644 index 00000000..9d574814 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.cpp @@ -0,0 +1,208 @@ +/* $Id: VBoxUsbHook.cpp $ */ +/** @file + * Driver Dispatch Table Hooking API + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +#define VBOXUSBHOOK_MEMTAG 'HUBV' + + +NTSTATUS VBoxUsbHookInstall(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (pHook->fIsInstalled) + { + WARN(("hook is marked installed, returning failure")); + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_UNSUCCESSFUL; + } + + pHook->pfnOldHandler = (PDRIVER_DISPATCH)InterlockedExchangePointer((PVOID*)&pHook->pDrvObj->MajorFunction[pHook->iMjFunction], pHook->pfnHook); + Assert(pHook->pfnOldHandler); + Assert(pHook->pfnHook != pHook->pfnOldHandler); + pHook->fIsInstalled = TRUE; + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_SUCCESS; + +} +NTSTATUS VBoxUsbHookUninstall(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (!pHook->fIsInstalled) + { + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_SUCCESS; + } + + PDRIVER_DISPATCH pfnOldVal = (PDRIVER_DISPATCH)InterlockedCompareExchangePointer((PVOID*)&pHook->pDrvObj->MajorFunction[pHook->iMjFunction], pHook->pfnOldHandler, pHook->pfnHook); + Assert(pfnOldVal == pHook->pfnHook); + if (pfnOldVal != pHook->pfnHook) + { + AssertMsgFailed(("unhook failed!!!\n")); + /* this is bad! this could happen if someone else has chained another hook, + * or (which is even worse) restored the "initial" entry value it saved when doing a hooking before us + * return the failure and don't do anything else + * the best thing to do if this happens is to leave everything as is + * and to prevent the driver from being unloaded to ensure no one references our unloaded hook routine */ + KeReleaseSpinLock(&pHook->Lock, Irql); + return STATUS_UNSUCCESSFUL; + } + + pHook->fIsInstalled = FALSE; + KeReleaseSpinLock(&pHook->Lock, Irql); + + /* wait for the current handlers to exit */ + VBoxDrvToolRefWaitEqual(&pHook->HookRef, 1); + + return STATUS_SUCCESS; +} + +BOOLEAN VBoxUsbHookIsInstalled(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + BOOLEAN fIsInstalled; + KeAcquireSpinLock(&pHook->Lock, &Irql); + fIsInstalled = pHook->fIsInstalled; + KeReleaseSpinLock(&pHook->Lock, Irql); + return fIsInstalled; +} + +VOID VBoxUsbHookInit(PVBOXUSBHOOK_ENTRY pHook, PDRIVER_OBJECT pDrvObj, UCHAR iMjFunction, PDRIVER_DISPATCH pfnHook) +{ + Assert(pDrvObj); + Assert(iMjFunction <= IRP_MJ_MAXIMUM_FUNCTION); + Assert(pfnHook); + memset(pHook, 0, sizeof (*pHook)); + InitializeListHead(&pHook->RequestList); + KeInitializeSpinLock(&pHook->Lock); + VBoxDrvToolRefInit(&pHook->HookRef); + pHook->pDrvObj = pDrvObj; + pHook->iMjFunction = iMjFunction; + pHook->pfnHook = pfnHook; + Assert(!pHook->pfnOldHandler); + Assert(!pHook->fIsInstalled); + +} + +static void vboxUsbHookRequestRegisterCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest) +{ + Assert(pfnCompletion); + Assert(pRequest); + Assert(pDevObj); + Assert(pIrp); + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + memset(pRequest, 0, sizeof (*pRequest)); + pRequest->pHook = pHook; + pRequest->OldLocation = *pSl; + pRequest->pDevObj = pDevObj; + pRequest->pIrp = pIrp; + pRequest->bCompletionStopped = FALSE; + pSl->CompletionRoutine = pfnCompletion; + pSl->Context = pRequest; + pSl->Control = SL_INVOKE_ON_SUCCESS | SL_INVOKE_ON_ERROR | SL_INVOKE_ON_CANCEL; + + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + InsertTailList(&pHook->RequestList, &pRequest->ListEntry); + KeReleaseSpinLock(&pHook->Lock, oldIrql); +} + +NTSTATUS VBoxUsbHookRequestPassDownHookCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest) +{ + Assert(pfnCompletion); + vboxUsbHookRequestRegisterCompletion(pHook, pDevObj, pIrp, pfnCompletion, pRequest); + return pHook->pfnOldHandler(pDevObj, pIrp); +} + +NTSTATUS VBoxUsbHookRequestPassDownHookSkip(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + return pHook->pfnOldHandler(pDevObj, pIrp); +} + +NTSTATUS VBoxUsbHookRequestMoreProcessingRequired(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, + PVBOXUSBHOOK_REQUEST pRequest) +{ + RT_NOREF3(pHook, pDevObj, pIrp); + Assert(!pRequest->bCompletionStopped); + pRequest->bCompletionStopped = TRUE; + return STATUS_MORE_PROCESSING_REQUIRED; +} + +NTSTATUS VBoxUsbHookRequestComplete(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest) +{ + NTSTATUS Status = STATUS_SUCCESS; + + if (pRequest->OldLocation.CompletionRoutine && pRequest->OldLocation.Control) + { + Status = pRequest->OldLocation.CompletionRoutine(pDevObj, pIrp, pRequest->OldLocation.Context); + } + + if (Status != STATUS_MORE_PROCESSING_REQUIRED) + { + if (pRequest->bCompletionStopped) + { + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + } + } + /* + * else - in case driver returned STATUS_MORE_PROCESSING_REQUIRED, + * it will call IoCompleteRequest itself + */ + + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + RemoveEntryList(&pRequest->ListEntry); + KeReleaseSpinLock(&pHook->Lock, oldIrql); + return Status; +} + +#define PVBOXUSBHOOK_REQUEST_FROM_LE(_pLe) ( (PVBOXUSBHOOK_REQUEST)( ((uint8_t*)(_pLe)) - RT_OFFSETOF(VBOXUSBHOOK_REQUEST, ListEntry) ) ) + +VOID VBoxUsbHookVerifyCompletion(PVBOXUSBHOOK_ENTRY pHook, PVBOXUSBHOOK_REQUEST pRequest, PIRP pIrp) +{ + KIRQL oldIrql; + KeAcquireSpinLock(&pHook->Lock, &oldIrql); + for (PLIST_ENTRY pLe = pHook->RequestList.Flink; pLe != &pHook->RequestList; pLe = pLe->Flink) + { + PVBOXUSBHOOK_REQUEST pCur = PVBOXUSBHOOK_REQUEST_FROM_LE(pLe); + if (pCur != pRequest) + continue; + if (pCur->pIrp != pIrp) + continue; + WARN(("found pending IRP(0x%p) when it should not be", pIrp)); + } + KeReleaseSpinLock(&pHook->Lock, oldIrql); + +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h new file mode 100644 index 00000000..5276abba --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbHook.h @@ -0,0 +1,86 @@ +/* $Id: VBoxUsbHook.h $ */ +/** @file + * Driver Dispatch Table Hooking API impl + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "VBoxUsbMon.h" + +typedef struct VBOXUSBHOOK_ENTRY +{ + LIST_ENTRY RequestList; + KSPIN_LOCK Lock; + BOOLEAN fIsInstalled; + PDRIVER_DISPATCH pfnOldHandler; + VBOXDRVTOOL_REF HookRef; + PDRIVER_OBJECT pDrvObj; + UCHAR iMjFunction; + PDRIVER_DISPATCH pfnHook; +} VBOXUSBHOOK_ENTRY, *PVBOXUSBHOOK_ENTRY; + +typedef struct VBOXUSBHOOK_REQUEST +{ + LIST_ENTRY ListEntry; + PVBOXUSBHOOK_ENTRY pHook; + IO_STACK_LOCATION OldLocation; + PDEVICE_OBJECT pDevObj; + PIRP pIrp; + BOOLEAN bCompletionStopped; +} VBOXUSBHOOK_REQUEST, *PVBOXUSBHOOK_REQUEST; + +DECLINLINE(BOOLEAN) VBoxUsbHookRetain(PVBOXUSBHOOK_ENTRY pHook) +{ + KIRQL Irql; + KeAcquireSpinLock(&pHook->Lock, &Irql); + if (!pHook->fIsInstalled) + { + KeReleaseSpinLock(&pHook->Lock, Irql); + return FALSE; + } + + VBoxDrvToolRefRetain(&pHook->HookRef); + KeReleaseSpinLock(&pHook->Lock, Irql); + return TRUE; +} + +DECLINLINE(VOID) VBoxUsbHookRelease(PVBOXUSBHOOK_ENTRY pHook) +{ + VBoxDrvToolRefRelease(&pHook->HookRef); +} + +VOID VBoxUsbHookInit(PVBOXUSBHOOK_ENTRY pHook, PDRIVER_OBJECT pDrvObj, UCHAR iMjFunction, PDRIVER_DISPATCH pfnHook); +NTSTATUS VBoxUsbHookInstall(PVBOXUSBHOOK_ENTRY pHook); +NTSTATUS VBoxUsbHookUninstall(PVBOXUSBHOOK_ENTRY pHook); +BOOLEAN VBoxUsbHookIsInstalled(PVBOXUSBHOOK_ENTRY pHook); +NTSTATUS VBoxUsbHookRequestPassDownHookCompletion(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PIO_COMPLETION_ROUTINE pfnCompletion, PVBOXUSBHOOK_REQUEST pRequest); +NTSTATUS VBoxUsbHookRequestPassDownHookSkip(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp); +NTSTATUS VBoxUsbHookRequestMoreProcessingRequired(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest); +NTSTATUS VBoxUsbHookRequestComplete(PVBOXUSBHOOK_ENTRY pHook, PDEVICE_OBJECT pDevObj, PIRP pIrp, PVBOXUSBHOOK_REQUEST pRequest); +VOID VBoxUsbHookVerifyCompletion(PVBOXUSBHOOK_ENTRY pHook, PVBOXUSBHOOK_REQUEST pRequest, PIRP pIrp); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbHook_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp new file mode 100644 index 00000000..e4484877 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.cpp @@ -0,0 +1,1566 @@ +/* $Id: VBoxUsbMon.cpp $ */ +/** @file + * VBox USB Monitor + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + + +/* + * + * Theory of Operation + * - or - + * The Document I Wish The Original Author Had Written + * + * + * The USB Monitor (VBoxUSBMon.sys) serves to capture and uncapture USB + * devices. Its job is to ensure that the USB proxy (VBoxUSB.sys) gets installed + * for captured devices and removed again when not needed, restoring the regular + * driver (if any). + * + * The USB Monitor does not handle any actual USB traffic; that is the role of + * VBoxUSB.sys, the USB proxy. A typical solution for installing such USB proxy + * is using a filter driver, but that approach was rejected because filter drivers + * cannot be dynamically added and removed. What VBoxUSBMon does instead is hook + * into the dispatch routine of the bus driver, i.e. USB hub driver, and alter + * the PnP information returned by the bus driver. + * + * The key functionality for capturing is cycling a USB port (which causes a USB + * device reset and triggers re-enumeration in the Windows USB driver stack), and + * then modifying IRP_MN_QUERY_ID / BusQueryHardwareIDs and related requests so + * that they return the synthetic USB VID/PID that VBoxUSB.sys handles rather than + * the true hardware VID/PID. That causes Windows to install VBoxUSB.sys for the + * device. + * + * Uncapturing again cycles the USB port but returns unmodified hardware IDs, + * causing Windows to load the normal driver for the device. + * + * Identifying devices to capture or release (uncapture) is done through USB filters, + * a cross-platform concept which matches USB device based on their VID/PID, class, + * and other criteria. + * + * There is an IOCTL interface for adding/removing USB filters and applying them. + * The IOCTLs are normally issued by VBoxSVC. + * + * USB devices are enumerated by finding all USB hubs (GUID_DEVINTERFACE_USB_HUB) + * and querying their child devices (i.e. USB devices or other hubs) by sending + * IRP_MJ_PNP / IRP_MN_QUERY_DEVICE_RELATIONS / BusRelations. This is done when + * applying existing filters. + * + * Newly arrived USB devices are intercepted early in their PnP enumeration + * through the hooked bus driver dispatch routine. Devices which satisty the + * filter matching criteria are morphed (see above) such that VBoxUSB.sys loads + * for them before any default driver does. + * + * There is an IDC interface to VBoxUSB.sys which allows the USB proxy to report + * that it's installed for a given USB device, and also report when the USB proxy + * is unloaded (typically caused by either unplugging the device or uncapturing + * and cycling the port). VBoxUSBMon.sys relies on these IDC calls to track + * captured devices and be informed when VBoxUSB.sys unloads. + * + * Windows 8+ complicates the USB Monitor's life by automatically putting some + * USB devices to a low-power state where they are unable to respond to any USB + * requests and VBoxUSBMon can't read any of their descriptors (note that in + * userland, the device descriptor can always be read, but string descriptors + * can't). Such devices' USB VID/PID/revision is recovered using the Windows + * PnP Manager from their DevicePropertyHardwareID, but their USB class/subclass + * and protocol unfortunately cannot be unambiguously recovered from their + * DevicePropertyCompatibleIDs. + * + * Filter drivers add another complication. With filter drivers in place, the + * device objects returned by the BusRelations query (or passing through the PnP + * hooks) may not be PDOs but rather filter DOs higher in the stack. To avoid + * confusion, we flatten the references to their base, i.e. the real PDO, which + * should remain the same for the lifetime of a device. Note that VBoxUSB.sys + * always passes its own PDO in the proxy startup IOCTL. + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include "VBoxUsbMon.h" +#include "../cmn/VBoxUsbIdc.h" +#include <iprt/errcore.h> +#include <VBox/usblib.h> +#include <excpt.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/* + * Note: Must match the VID & PID in the USB driver .inf file!! + */ +/* + BusQueryDeviceID USB\Vid_80EE&Pid_CAFE + BusQueryInstanceID 2 + BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE&Rev_0100 + BusQueryHardwareIDs USB\Vid_80EE&Pid_CAFE + BusQueryCompatibleIDs USB\Class_ff&SubClass_00&Prot_00 + BusQueryCompatibleIDs USB\Class_ff&SubClass_00 + BusQueryCompatibleIDs USB\Class_ff +*/ + +#define szBusQueryDeviceId L"USB\\Vid_80EE&Pid_CAFE" +#define szBusQueryHardwareIDs L"USB\\Vid_80EE&Pid_CAFE&Rev_0100\0USB\\Vid_80EE&Pid_CAFE\0\0" +#define szBusQueryCompatibleIDs L"USB\\Class_ff&SubClass_00&Prot_00\0USB\\Class_ff&SubClass_00\0USB\\Class_ff\0\0" + +#define szDeviceTextDescription L"VirtualBox USB" + + +#define VBOXUSBMON_MEMTAG 'MUBV' + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +typedef struct VBOXUSBMONINS +{ + void * pvDummy; +} VBOXUSBMONINS, *PVBOXUSBMONINS; + +typedef struct VBOXUSBMONCTX +{ + VBOXUSBFLTCTX FltCtx; +} VBOXUSBMONCTX, *PVBOXUSBMONCTX; + +typedef struct VBOXUSBHUB_PNPHOOK +{ + VBOXUSBHOOK_ENTRY Hook; + bool fUninitFailed; +} VBOXUSBHUB_PNPHOOK, *PVBOXUSBHUB_PNPHOOK; + +typedef struct VBOXUSBHUB_PNPHOOK_COMPLETION +{ + VBOXUSBHOOK_REQUEST Rq; +} VBOXUSBHUB_PNPHOOK_COMPLETION, *PVBOXUSBHUB_PNPHOOK_COMPLETION; + +#define VBOXUSBMON_MAXDRIVERS 5 +typedef struct VBOXUSB_PNPDRIVER +{ + PDRIVER_OBJECT DriverObject; + VBOXUSBHUB_PNPHOOK UsbHubPnPHook; + PDRIVER_DISPATCH pfnHookStub; +} VBOXUSB_PNPDRIVER, *PVBOXUSB_PNPDRIVER; + +typedef struct VBOXUSBMONGLOBALS +{ + PDEVICE_OBJECT pDevObj; + VBOXUSB_PNPDRIVER pDrivers[VBOXUSBMON_MAXDRIVERS]; + KEVENT OpenSynchEvent; + IO_REMOVE_LOCK RmLock; + uint32_t cOpens; + volatile LONG ulPreventUnloadOn; + PFILE_OBJECT pPreventUnloadFileObj; +} VBOXUSBMONGLOBALS, *PVBOXUSBMONGLOBALS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +static VBOXUSBMONGLOBALS g_VBoxUsbMonGlobals; + + + +PVOID VBoxUsbMonMemAlloc(SIZE_T cbBytes) +{ + PVOID pvMem = ExAllocatePoolWithTag(NonPagedPool, cbBytes, VBOXUSBMON_MEMTAG); + Assert(pvMem); + return pvMem; +} + +PVOID VBoxUsbMonMemAllocZ(SIZE_T cbBytes) +{ + PVOID pvMem = VBoxUsbMonMemAlloc(cbBytes); + if (pvMem) + { + RtlZeroMemory(pvMem, cbBytes); + } + return pvMem; +} + +VOID VBoxUsbMonMemFree(PVOID pvMem) +{ + ExFreePoolWithTag(pvMem, VBOXUSBMON_MEMTAG); +} + +#define VBOXUSBDBG_STRCASE(_t) \ + case _t: return #_t +#define VBOXUSBDBG_STRCASE_UNKNOWN(_v) \ + default: LOG((__FUNCTION__": Unknown Value (0n%d), (0x%x)", _v, _v)); return "Unknown" + +/* These minor code are semi-undocumented. */ +#ifndef IRP_MN_QUERY_LEGACY_BUS_INFORMATION +#define IRP_MN_QUERY_LEGACY_BUS_INFORMATION 0x18 +#endif +#ifndef IRP_MN_DEVICE_ENUMERATED +#define IRP_MN_DEVICE_ENUMERATED 0x19 +#endif + +static const char* vboxUsbDbgStrPnPMn(UCHAR uMn) +{ + switch (uMn) + { + VBOXUSBDBG_STRCASE(IRP_MN_START_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_CANCEL_REMOVE_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_CANCEL_STOP_DEVICE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_DEVICE_RELATIONS); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_INTERFACE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_CAPABILITIES); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_RESOURCES); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_RESOURCE_REQUIREMENTS); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_DEVICE_TEXT); + VBOXUSBDBG_STRCASE(IRP_MN_FILTER_RESOURCE_REQUIREMENTS); + VBOXUSBDBG_STRCASE(IRP_MN_READ_CONFIG); + VBOXUSBDBG_STRCASE(IRP_MN_WRITE_CONFIG); + VBOXUSBDBG_STRCASE(IRP_MN_EJECT); + VBOXUSBDBG_STRCASE(IRP_MN_SET_LOCK); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_ID); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_PNP_DEVICE_STATE); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_BUS_INFORMATION); + VBOXUSBDBG_STRCASE(IRP_MN_DEVICE_USAGE_NOTIFICATION); + VBOXUSBDBG_STRCASE(IRP_MN_SURPRISE_REMOVAL); + VBOXUSBDBG_STRCASE(IRP_MN_QUERY_LEGACY_BUS_INFORMATION); + VBOXUSBDBG_STRCASE(IRP_MN_DEVICE_ENUMERATED); + VBOXUSBDBG_STRCASE_UNKNOWN(uMn); + } +} + +/** + * Send IRP_MN_QUERY_DEVICE_RELATIONS + * + * @returns NT Status + * @param pDevObj USB device pointer + * @param pFileObj Valid file object pointer + * @param pDevRelations Pointer to DEVICE_RELATIONS pointer (out) + */ +NTSTATUS VBoxUsbMonQueryBusRelations(PDEVICE_OBJECT pDevObj, PFILE_OBJECT pFileObj, PDEVICE_RELATIONS *pDevRelations) +{ + IO_STATUS_BLOCK IoStatus; + KEVENT Event; + NTSTATUS Status; + PIRP pIrp; + PIO_STACK_LOCATION pSl; + + KeInitializeEvent(&Event, NotificationEvent, FALSE); + + Assert(pDevRelations); + *pDevRelations = NULL; + + pIrp = IoBuildSynchronousFsdRequest(IRP_MJ_PNP, pDevObj, NULL, 0, NULL, &Event, &IoStatus); + if (!pIrp) + { + WARN(("IoBuildDeviceIoControlRequest failed!!")); + return STATUS_INSUFFICIENT_RESOURCES; + } + pIrp->IoStatus.Status = STATUS_NOT_SUPPORTED; + + pSl = IoGetNextIrpStackLocation(pIrp); + pSl->MajorFunction = IRP_MJ_PNP; + pSl->MinorFunction = IRP_MN_QUERY_DEVICE_RELATIONS; + pSl->Parameters.QueryDeviceRelations.Type = BusRelations; + pSl->FileObject = pFileObj; + + Status = IoCallDriver(pDevObj, pIrp); + if (Status == STATUS_PENDING) + { + LOG(("IoCallDriver returned STATUS_PENDING!!")); + KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, NULL); + Status = IoStatus.Status; + } + + if (Status == STATUS_SUCCESS) + { + PDEVICE_RELATIONS pRel = (PDEVICE_RELATIONS)IoStatus.Information; + LOG(("pRel = %p", pRel)); + if (VALID_PTR(pRel)) + { + *pDevRelations = pRel; + } + else + { + WARN(("Invalid pointer %p", pRel)); + } + } + else + { + WARN(("IRP_MN_QUERY_DEVICE_RELATIONS failed Status(0x%x)", Status)); + } + + LOG(("IoCallDriver returned %x", Status)); + return Status; +} + +VOID vboxUsbMonHubDevWalk(PFNVBOXUSBMONDEVWALKER pfnWalker, PVOID pvWalker) +{ + NTSTATUS Status = STATUS_UNSUCCESSFUL; + PWSTR szwHubList; + Status = IoGetDeviceInterfaces(&GUID_DEVINTERFACE_USB_HUB, NULL, 0, &szwHubList); + if (Status != STATUS_SUCCESS) + { + LOG(("IoGetDeviceInterfaces failed with %d\n", Status)); + return; + } + if (szwHubList) + { + UNICODE_STRING UnicodeName; + PDEVICE_OBJECT pHubDevObj; + PFILE_OBJECT pHubFileObj; + PWSTR szwHubName = szwHubList; + while (*szwHubName != UNICODE_NULL) + { + RtlInitUnicodeString(&UnicodeName, szwHubName); + Status = IoGetDeviceObjectPointer(&UnicodeName, FILE_READ_DATA, &pHubFileObj, &pHubDevObj); + if (Status == STATUS_SUCCESS) + { + /* We could not log hub name here. + * It is the paged memory and we cannot use it in logger cause it increases the IRQL + */ + LOG(("IoGetDeviceObjectPointer returned %p %p", pHubDevObj, pHubFileObj)); + if (!pfnWalker(pHubFileObj, pHubDevObj, pvWalker)) + { + LOG(("the walker said to stop")); + ObDereferenceObject(pHubFileObj); + break; + } + + LOG(("going forward..")); + ObDereferenceObject(pHubFileObj); + } + szwHubName += wcslen(szwHubName) + 1; + } + ExFreePool(szwHubList); + } +} + +/* NOTE: the stack location data is not the "actual" IRP stack location, + * but a copy being preserved on the IRP way down. + * See the note in VBoxUsbPnPCompletion for detail */ +static NTSTATUS vboxUsbMonHandlePnPIoctl(PDEVICE_OBJECT pDevObj, PIO_STACK_LOCATION pSl, PIO_STATUS_BLOCK pIoStatus) +{ + LOG(("IRQL = %d", KeGetCurrentIrql())); + switch(pSl->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_TEXT: + { + LOG(("IRP_MN_QUERY_DEVICE_TEXT: pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS) + { + WCHAR *pId = (WCHAR *)pIoStatus->Information; + if (VALID_PTR(pId)) + { + KIRQL Iqrl = KeGetCurrentIrql(); + /* IRQL should be always passive here */ + ASSERT_WARN(Iqrl == PASSIVE_LEVEL, ("irql is not PASSIVE")); + switch(pSl->Parameters.QueryDeviceText.DeviceTextType) + { + case DeviceTextLocationInformation: + LOG(("DeviceTextLocationInformation")); + LOG_STRW(pId); + break; + + case DeviceTextDescription: + LOG(("DeviceTextDescription")); + LOG_STRW(pId); + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + { + LOG(("PDO (0x%p) is filtered", pDevObj)); + WCHAR *pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(szDeviceTextDescription)); + if (!pId) + { + AssertFailed(); + break; + } + memcpy(pId, szDeviceTextDescription, sizeof(szDeviceTextDescription)); + LOG(("NEW szDeviceTextDescription")); + LOG_STRW(pId); + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId; + } + else + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + break; + default: + LOG(("DeviceText %d", pSl->Parameters.QueryDeviceText.DeviceTextType)); + break; + } + } + else + LOG(("Invalid pointer %p", pId)); + } + break; + } + + case IRP_MN_QUERY_ID: + { + LOG(("IRP_MN_QUERY_ID: Irp->pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS && pDevObj) + { + WCHAR *pId = (WCHAR *)pIoStatus->Information; +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + WCHAR *pTmp; +#endif + if (VALID_PTR(pId)) + { + KIRQL Iqrl = KeGetCurrentIrql(); + /* IRQL should be always passive here */ + ASSERT_WARN(Iqrl == PASSIVE_LEVEL, ("irql is not PASSIVE")); + + switch (pSl->Parameters.QueryId.IdType) + { + case BusQueryInstanceID: + LOG(("BusQueryInstanceID")); + LOG_STRW(pId); + break; + + case BusQueryDeviceID: + { + LOG(("BusQueryDeviceID")); + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(szBusQueryDeviceId)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + + BOOLEAN bFiltered = FALSE; + NTSTATUS Status = VBoxUsbFltPdoAdd(pDevObj, &bFiltered); + if (Status != STATUS_SUCCESS || !bFiltered) + { + if (Status == STATUS_SUCCESS) + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + else + { + WARN(("VBoxUsbFltPdoAdd for PDO (0x%p) failed Status 0x%x", pDevObj, Status)); + } + ExFreePool(pId); + break; + } + + LOG(("PDO (0x%p) is filtered", pDevObj)); + ExFreePool((PVOID)pIoStatus->Information); + memcpy(pId, szBusQueryDeviceId, sizeof(szBusQueryDeviceId)); + pIoStatus->Information = (ULONG_PTR)pId; + break; + } + case BusQueryHardwareIDs: + { + LOG(("BusQueryHardwareIDs")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + while (*pId) //MULTI_SZ + { + LOG_STRW(pId); + while (*pId) pId++; + pId++; + } +#endif + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(szBusQueryHardwareIDs)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + + BOOLEAN bFiltered = FALSE; + NTSTATUS Status = VBoxUsbFltPdoAdd(pDevObj, &bFiltered); + if (Status != STATUS_SUCCESS || !bFiltered) + { + if (Status == STATUS_SUCCESS) + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + else + { + WARN(("VBoxUsbFltPdoAdd for PDO (0x%p) failed Status 0x%x", pDevObj, Status)); + } + ExFreePool(pId); + break; + } + + LOG(("PDO (0x%p) is filtered", pDevObj)); + + memcpy(pId, szBusQueryHardwareIDs, sizeof(szBusQueryHardwareIDs)); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("NEW BusQueryHardwareIDs")); + pTmp = pId; + while (*pTmp) //MULTI_SZ + { + + LOG_STRW(pTmp); + while (*pTmp) pTmp++; + pTmp++; + } +#endif + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId; + break; + } + case BusQueryCompatibleIDs: + LOG(("BusQueryCompatibleIDs")); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + while (*pId) //MULTI_SZ + { + LOG_STRW(pId); + while (*pId) pId++; + pId++; + } +#endif + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + { + LOG(("PDO (0x%p) is filtered", pDevObj)); + pId = (WCHAR *)ExAllocatePool(PagedPool, sizeof(szBusQueryCompatibleIDs)); + if (!pId) + { + WARN(("ExAllocatePool failed")); + break; + } + memcpy(pId, szBusQueryCompatibleIDs, sizeof(szBusQueryCompatibleIDs)); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + LOG(("NEW BusQueryCompatibleIDs")); + pTmp = pId; + while (*pTmp) //MULTI_SZ + { + LOG_STRW(pTmp); + while (*pTmp) pTmp++; + pTmp++; + } +#endif + ExFreePool((PVOID)pIoStatus->Information); + pIoStatus->Information = (ULONG_PTR)pId; + } + else + { + LOG(("PDO (0x%p) is NOT filtered", pDevObj)); + } + break; + + default: + /** @todo r=bird: handle BusQueryContainerID and whatever else we might see */ + break; + } + } + else + { + LOG(("Invalid pointer %p", pId)); + } + } + break; + } + +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + case IRP_MN_QUERY_DEVICE_RELATIONS: + { + switch(pSl->Parameters.QueryDeviceRelations.Type) + { + case BusRelations: + LOG(("BusRelations")); + + if (pIoStatus->Status == STATUS_SUCCESS) + { + PDEVICE_RELATIONS pRel = (PDEVICE_RELATIONS)pIoStatus->Information; + LOG(("pRel = %p", pRel)); + if (VALID_PTR(pRel)) + { + for (unsigned i=0;i<pRel->Count;i++) + { + if (VBoxUsbFltPdoIsFiltered(pDevObj)) + LOG(("New PDO %p", pRel->Objects[i])); + } + } + else + LOG(("Invalid pointer %p", pRel)); + } + break; + case TargetDeviceRelation: + LOG(("TargetDeviceRelation")); + break; + case RemovalRelations: + LOG(("RemovalRelations")); + break; + case EjectionRelations: + LOG(("EjectionRelations")); + break; + default: + LOG(("QueryDeviceRelations.Type=%d", pSl->Parameters.QueryDeviceRelations.Type)); + } + break; + } + + case IRP_MN_QUERY_CAPABILITIES: + { + LOG(("IRP_MN_QUERY_CAPABILITIES: pIoStatus->Status = %x", pIoStatus->Status)); + if (pIoStatus->Status == STATUS_SUCCESS) + { + PDEVICE_CAPABILITIES pCaps = pSl->Parameters.DeviceCapabilities.Capabilities; + if (VALID_PTR(pCaps)) + { + LOG(("Caps.SilentInstall = %d", pCaps->SilentInstall)); + LOG(("Caps.UniqueID = %d", pCaps->UniqueID )); + LOG(("Caps.Address = %d", pCaps->Address )); + LOG(("Caps.UINumber = %d", pCaps->UINumber )); + } + else + LOG(("Invalid pointer %p", pCaps)); + } + break; + } + + default: + break; +#endif + } /*switch */ + + LOG(("Done returns %x (IRQL = %d)", pIoStatus->Status, KeGetCurrentIrql())); + return pIoStatus->Status; +} + +NTSTATUS _stdcall VBoxUsbPnPCompletion(DEVICE_OBJECT *pDevObj, IRP *pIrp, void *pvContext) +{ + LOG(("Completion PDO(0x%p), IRP(0x%p), Status(0x%x)", pDevObj, pIrp, pIrp->IoStatus.Status)); + ASSERT_WARN(pvContext, ("zero context")); + + PVBOXUSBHOOK_REQUEST pRequest = (PVBOXUSBHOOK_REQUEST)pvContext; + /* NOTE: despite a regular IRP processing the stack location in our completion + * differs from those of the PnP hook since the hook is invoked in the "context" of the calle, + * while the completion is in the "coller" context in terms of IRP, + * so the completion stack location is one level "up" here. + * + * Moreover we CAN NOT access irp stack location in the completion because we might not have one at all + * in case the hooked driver is at the top of the irp call stack + * + * This is why we use the stack location we saved on IRP way down. + * */ + PIO_STACK_LOCATION pSl = &pRequest->OldLocation; + ASSERT_WARN(pIrp == pRequest->pIrp, ("completed IRP(0x%x) not match request IRP(0x%x)", pIrp, pRequest->pIrp)); + /* NOTE: we can not rely on pDevObj passed in IoCompletion since it may be zero + * in case IRP was created with extra stack locations and the caller did not initialize + * the IO_STACK_LOCATION::DeviceObject */ + DEVICE_OBJECT *pRealDevObj = pRequest->pDevObj; +// Assert(!pDevObj || pDevObj == pRealDevObj); +// Assert(pSl->DeviceObject == pDevObj); + + switch(pSl->MinorFunction) + { + case IRP_MN_QUERY_DEVICE_TEXT: + case IRP_MN_QUERY_ID: +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + case IRP_MN_QUERY_DEVICE_RELATIONS: + case IRP_MN_QUERY_CAPABILITIES: +#endif + if (NT_SUCCESS(pIrp->IoStatus.Status)) + { + vboxUsbMonHandlePnPIoctl(pRealDevObj, pSl, &pIrp->IoStatus); + } + else + { + ASSERT_WARN(pIrp->IoStatus.Status == STATUS_NOT_SUPPORTED, ("Irp failed with status(0x%x)", pIrp->IoStatus.Status)); + } + break; + + case IRP_MN_SURPRISE_REMOVAL: + case IRP_MN_REMOVE_DEVICE: + if (NT_SUCCESS(pIrp->IoStatus.Status)) + { + VBoxUsbFltPdoRemove(pRealDevObj); + } + else + { + AssertFailed(); + } + break; + + /* These two IRPs are received when the PnP subsystem has determined the id of the newly arrived device */ + /* IRP_MN_START_DEVICE only arrives if it's a USB device of a known class or with a present host driver */ + case IRP_MN_QUERY_RESOURCE_REQUIREMENTS: + case IRP_MN_QUERY_RESOURCES: + /* There used to be code to support SUPUSBFLT_IOCTL_SET_NOTIFY_EVENT but it was not reliable. */ + + default: + break; + } + + LOG(("<==PnP: Mn(%s), PDO(0x%p), IRP(0x%p), Status(0x%x), Sl PDO(0x%p), Compl PDO(0x%p)", + vboxUsbDbgStrPnPMn(pSl->MinorFunction), + pRealDevObj, pIrp, pIrp->IoStatus.Status, + pSl->DeviceObject, pDevObj)); +#ifdef DEBUG_misha + NTSTATUS tmpStatus = pIrp->IoStatus.Status; +#endif + PVBOXUSBHOOK_ENTRY pHook = pRequest->pHook; + NTSTATUS Status = VBoxUsbHookRequestComplete(pHook, pDevObj, pIrp, pRequest); + VBoxUsbMonMemFree(pRequest); +#ifdef DEBUG_misha + if (Status != STATUS_MORE_PROCESSING_REQUIRED) + { + Assert(pIrp->IoStatus.Status == tmpStatus); + } +#endif + VBoxUsbHookRelease(pHook); + return Status; +} + +/** + * Device PnP hook + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +static NTSTATUS vboxUsbMonPnPHook(IN PVBOXUSBHOOK_ENTRY pHook, IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) +{ + LOG(("==>PnP: Mn(%s), PDO(0x%p), IRP(0x%p), Status(0x%x)", vboxUsbDbgStrPnPMn(IoGetCurrentIrpStackLocation(pIrp)->MinorFunction), pDevObj, pIrp, pIrp->IoStatus.Status)); + + if (!VBoxUsbHookRetain(pHook)) + { + WARN(("VBoxUsbHookRetain failed")); + return VBoxUsbHookRequestPassDownHookSkip(pHook, pDevObj, pIrp); + } + + PVBOXUSBHUB_PNPHOOK_COMPLETION pCompletion = (PVBOXUSBHUB_PNPHOOK_COMPLETION)VBoxUsbMonMemAlloc(sizeof (*pCompletion)); + if (!pCompletion) + { + WARN(("VBoxUsbMonMemAlloc failed")); + VBoxUsbHookRelease(pHook); + pIrp->IoStatus.Status = STATUS_INSUFFICIENT_RESOURCES; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return STATUS_INSUFFICIENT_RESOURCES; + } + + NTSTATUS Status = VBoxUsbHookRequestPassDownHookCompletion(pHook, pDevObj, pIrp, VBoxUsbPnPCompletion, &pCompletion->Rq); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + if (Status != STATUS_PENDING) + { + LOG(("Request completed, Status(0x%x)", Status)); + VBoxUsbHookVerifyCompletion(pHook, &pCompletion->Rq, pIrp); + } + else + { + LOG(("Request pending")); + } +#endif + return Status; +} + +/** + * Device PnP hook stubs. + * + * @param pDevObj Device object. + * @param pIrp Request packet. + */ +#define VBOX_PNPHOOKSTUB(n) NTSTATUS _stdcall VBoxUsbMonPnPHook##n(IN PDEVICE_OBJECT pDevObj, IN PIRP pIrp) \ +{ \ + return vboxUsbMonPnPHook(&g_VBoxUsbMonGlobals.pDrivers[n].UsbHubPnPHook.Hook, pDevObj, pIrp); \ +} + +#define VBOX_PNPHOOKSTUB_INIT(n) g_VBoxUsbMonGlobals.pDrivers[n].pfnHookStub = VBoxUsbMonPnPHook##n + +VBOX_PNPHOOKSTUB(0) +VBOX_PNPHOOKSTUB(1) +VBOX_PNPHOOKSTUB(2) +VBOX_PNPHOOKSTUB(3) +VBOX_PNPHOOKSTUB(4) +AssertCompile(VBOXUSBMON_MAXDRIVERS == 5); + +typedef struct VBOXUSBMONHOOKDRIVERWALKER +{ + PDRIVER_OBJECT pDrvObj; +} VBOXUSBMONHOOKDRIVERWALKER, *PVBOXUSBMONHOOKDRIVERWALKER; + +/** + * Logs an error to the system event log. + * + * @param ErrCode Error to report to event log. + * @param ReturnedStatus Error that was reported by the driver to the caller. + * @param uErrId Unique error id representing the location in the driver. + * @param cbDumpData Number of bytes at pDumpData. + * @param pDumpData Pointer to data that will be added to the message (see 'details' tab). + * + * NB: We only use IoLogMsg.dll as the message file, limiting + * ErrCode to status codes and messages defined in ntiologc.h + */ +static void vboxUsbMonLogError(NTSTATUS ErrCode, NTSTATUS ReturnedStatus, ULONG uErrId, USHORT cbDumpData, PVOID pDumpData) +{ + PIO_ERROR_LOG_PACKET pErrEntry; + + + /* Truncate dumps that do not fit into IO_ERROR_LOG_PACKET. */ + if (FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData > ERROR_LOG_MAXIMUM_SIZE) + cbDumpData = ERROR_LOG_MAXIMUM_SIZE - FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData); + + pErrEntry = (PIO_ERROR_LOG_PACKET)IoAllocateErrorLogEntry(g_VBoxUsbMonGlobals.pDevObj, + FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData); + if (pErrEntry) + { + uint8_t *pDump = (uint8_t *)pErrEntry->DumpData; + if (cbDumpData) + memcpy(pDump, pDumpData, cbDumpData); + pErrEntry->MajorFunctionCode = 0; + pErrEntry->RetryCount = 0; + pErrEntry->DumpDataSize = cbDumpData; + pErrEntry->NumberOfStrings = 0; + pErrEntry->StringOffset = 0; + pErrEntry->ErrorCode = ErrCode; + pErrEntry->UniqueErrorValue = uErrId; + pErrEntry->FinalStatus = ReturnedStatus; + pErrEntry->IoControlCode = 0; + IoWriteErrorLogEntry(pErrEntry); + } + else + { + LOG(("Failed to allocate error log entry (cb=%d)\n", FIELD_OFFSET(IO_ERROR_LOG_PACKET, DumpData) + cbDumpData)); + } +} + +static DECLCALLBACK(BOOLEAN) vboxUsbMonHookDrvObjWalker(PFILE_OBJECT pHubFile, PDEVICE_OBJECT pHubDo, PVOID pvContext) +{ + RT_NOREF2(pHubFile, pvContext); + PDRIVER_OBJECT pDrvObj = pHubDo->DriverObject; + + /* First we try to figure out if we are already hooked to this driver. */ + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + if (pDrvObj == g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + LOG(("Found %p at pDrivers[%d]\n", pDrvObj, i)); + /* We've already hooked to this one -- nothing to do. */ + return TRUE; + } + /* We are not hooked yet, find an empty slot. */ + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + { + if (!g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + /* Found an emtpy slot, use it. */ + g_VBoxUsbMonGlobals.pDrivers[i].DriverObject = pDrvObj; + ObReferenceObject(pDrvObj); + LOG(("pDrivers[%d] = %p, installing the hook...\n", i, pDrvObj)); + VBoxUsbHookInit(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook, + pDrvObj, + IRP_MJ_PNP, + g_VBoxUsbMonGlobals.pDrivers[i].pfnHookStub); + VBoxUsbHookInstall(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook); + return TRUE; /* Must continue to find all drivers. */ + } + if (pDrvObj == g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + LOG(("Found %p at pDrivers[%d]\n", pDrvObj, i)); + /* We've already hooked to this one -- nothing to do. */ + return TRUE; + } + } + /* No empty slots! No reason to continue. */ + LOG(("No empty slots!\n")); + ANSI_STRING ansiDrvName; + NTSTATUS Status = RtlUnicodeStringToAnsiString(&ansiDrvName, &pDrvObj->DriverName, true); + if (Status != STATUS_SUCCESS) + { + ansiDrvName.Length = 0; + LOG(("RtlUnicodeStringToAnsiString failed with 0x%x", Status)); + } + vboxUsbMonLogError(IO_ERR_INSUFFICIENT_RESOURCES, STATUS_SUCCESS, 1, ansiDrvName.Length, ansiDrvName.Buffer); + if (Status == STATUS_SUCCESS) + RtlFreeAnsiString(&ansiDrvName); + return FALSE; +} + +/** + * Finds all USB drivers in the system and installs hooks if haven't done already. + */ +static NTSTATUS vboxUsbMonInstallAllHooks() +{ + vboxUsbMonHubDevWalk(vboxUsbMonHookDrvObjWalker, NULL); + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbMonHookCheckInit() +{ + static bool fIsHookInited = false; + if (fIsHookInited) + { + LOG(("hook inited already, success")); + return STATUS_SUCCESS; + } + return vboxUsbMonInstallAllHooks(); +} + +static NTSTATUS vboxUsbMonHookInstall() +{ + /* Nothing to do here as we have already installed all hooks in vboxUsbMonHookCheckInit(). */ + return STATUS_SUCCESS; +} + +static NTSTATUS vboxUsbMonHookUninstall() +{ +#ifdef VBOXUSBMON_DBG_NO_PNPHOOK + return STATUS_SUCCESS; +#else + NTSTATUS Status = STATUS_SUCCESS; + for (int i = 0; i < VBOXUSBMON_MAXDRIVERS; i++) + { + if (g_VBoxUsbMonGlobals.pDrivers[i].DriverObject) + { + Assert(g_VBoxUsbMonGlobals.pDrivers[i].DriverObject == g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook.pDrvObj); + LOG(("Unhooking from %p...\n", g_VBoxUsbMonGlobals.pDrivers[i].DriverObject)); + Status = VBoxUsbHookUninstall(&g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.Hook); + if (!NT_SUCCESS(Status)) + { + /* + * We failed to uninstall the hook, so we keep the reference to the driver + * in order to prevent another driver re-using this slot because we are + * going to mark this hook as fUninitFailed. + */ + //AssertMsgFailed(("usbhub pnp unhook failed, setting the fUninitFailed flag, the current value of fUninitFailed (%d)", g_VBoxUsbMonGlobals.UsbHubPnPHook.fUninitFailed)); + LOG(("usbhub pnp unhook failed, setting the fUninitFailed flag, the current value of fUninitFailed (%d)", g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.fUninitFailed)); + g_VBoxUsbMonGlobals.pDrivers[i].UsbHubPnPHook.fUninitFailed = true; + } + else + { + /* The hook was removed successfully, now we can forget about this driver. */ + ObDereferenceObject(g_VBoxUsbMonGlobals.pDrivers[i].DriverObject); + g_VBoxUsbMonGlobals.pDrivers[i].DriverObject = NULL; + } + } + } + return Status; +#endif +} + + +static NTSTATUS vboxUsbMonCheckTermStuff() +{ + NTSTATUS Status = KeWaitForSingleObject(&g_VBoxUsbMonGlobals.OpenSynchEvent, + Executive, KernelMode, + FALSE, /* BOOLEAN Alertable */ + NULL /* IN PLARGE_INTEGER Timeout */ + ); + AssertRelease(Status == STATUS_SUCCESS); + + do + { + if (--g_VBoxUsbMonGlobals.cOpens) + break; + + Status = vboxUsbMonHookUninstall(); + + NTSTATUS tmpStatus = VBoxUsbFltTerm(); + if (!NT_SUCCESS(tmpStatus)) + { + /* this means a driver state is screwed up, KeBugCheckEx here ? */ + AssertReleaseFailed(); + } + } while (0); + + KeSetEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, 0, FALSE); + + return Status; +} + +static NTSTATUS vboxUsbMonCheckInitStuff() +{ + NTSTATUS Status = KeWaitForSingleObject(&g_VBoxUsbMonGlobals.OpenSynchEvent, + Executive, KernelMode, + FALSE, /* BOOLEAN Alertable */ + NULL /* IN PLARGE_INTEGER Timeout */ + ); + if (Status == STATUS_SUCCESS) + { + do + { + if (g_VBoxUsbMonGlobals.cOpens++) + { + LOG(("opens: %d, success", g_VBoxUsbMonGlobals.cOpens)); + break; + } + + Status = VBoxUsbFltInit(); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbMonHookCheckInit(); + if (NT_SUCCESS(Status)) + { + Status = vboxUsbMonHookInstall(); + if (NT_SUCCESS(Status)) + { + Status = STATUS_SUCCESS; + LOG(("succeded!!")); + break; + } + else + { + WARN(("vboxUsbMonHookInstall failed, Status (0x%x)", Status)); + } + } + else + { + WARN(("vboxUsbMonHookCheckInit failed, Status (0x%x)", Status)); + } + VBoxUsbFltTerm(); + } + else + { + WARN(("VBoxUsbFltInit failed, Status (0x%x)", Status)); + } + + --g_VBoxUsbMonGlobals.cOpens; + Assert(!g_VBoxUsbMonGlobals.cOpens); + } while (0); + + KeSetEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, 0, FALSE); + } + else + { + WARN(("KeWaitForSingleObject failed, Status (0x%x)", Status)); + } + return Status; +} + +static NTSTATUS vboxUsbMonContextCreate(PVBOXUSBMONCTX *ppCtx) +{ + NTSTATUS Status; + *ppCtx = NULL; + PVBOXUSBMONCTX pFileCtx = (PVBOXUSBMONCTX)VBoxUsbMonMemAllocZ(sizeof (*pFileCtx)); + if (pFileCtx) + { + Status = vboxUsbMonCheckInitStuff(); + if (Status == STATUS_SUCCESS) + { + Status = VBoxUsbFltCreate(&pFileCtx->FltCtx); + if (Status == STATUS_SUCCESS) + { + *ppCtx = pFileCtx; + LOG(("succeeded!!")); + return STATUS_SUCCESS; + } + else + { + WARN(("VBoxUsbFltCreate failed")); + } + vboxUsbMonCheckTermStuff(); + } + else + { + WARN(("vboxUsbMonCheckInitStuff failed")); + } + VBoxUsbMonMemFree(pFileCtx); + } + else + { + WARN(("VBoxUsbMonMemAllocZ failed")); + Status = STATUS_NO_MEMORY; + } + + return Status; +} + +static NTSTATUS vboxUsbMonContextClose(PVBOXUSBMONCTX pCtx) +{ + NTSTATUS Status = VBoxUsbFltClose(&pCtx->FltCtx); + if (Status == STATUS_SUCCESS) + { + Status = vboxUsbMonCheckTermStuff(); + Assert(Status == STATUS_SUCCESS); + /* ignore the failure */ + VBoxUsbMonMemFree(pCtx); + } + + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonClose(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + Assert(pFileObj->FsContext); + PVBOXUSBMONCTX pCtx = (PVBOXUSBMONCTX)pFileObj->FsContext; + + LOG(("VBoxUsbMonClose")); + + NTSTATUS Status = vboxUsbMonContextClose(pCtx); + if (Status != STATUS_SUCCESS) + { + WARN(("vboxUsbMonContextClose failed, Status (0x%x), prevent unload", Status)); + if (!InterlockedExchange(&g_VBoxUsbMonGlobals.ulPreventUnloadOn, 1)) + { + LOGREL(("ulPreventUnloadOn not set, preventing unload")); + UNICODE_STRING UniName; + PDEVICE_OBJECT pTmpDevObj; + RtlInitUnicodeString(&UniName, USBMON_DEVICE_NAME_NT); + NTSTATUS tmpStatus = IoGetDeviceObjectPointer(&UniName, FILE_ALL_ACCESS, &g_VBoxUsbMonGlobals.pPreventUnloadFileObj, &pTmpDevObj); + AssertRelease(NT_SUCCESS(tmpStatus)); + AssertRelease(pTmpDevObj == pDevObj); + } + else + { + WARN(("ulPreventUnloadOn already set")); + } + LOG(("success!!")); + Status = STATUS_SUCCESS; + } + pFileObj->FsContext = NULL; + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + + +static NTSTATUS _stdcall VBoxUsbMonCreate(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + RT_NOREF1(pDevObj); + PIO_STACK_LOCATION pStack = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pStack->FileObject; + NTSTATUS Status; + + LOG(("VBoxUSBMonCreate")); + + if (pStack->Parameters.Create.Options & FILE_DIRECTORY_FILE) + { + WARN(("trying to open as a directory")); + pIrp->IoStatus.Status = STATUS_NOT_A_DIRECTORY; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return STATUS_NOT_A_DIRECTORY; + } + + pFileObj->FsContext = NULL; + PVBOXUSBMONCTX pCtx = NULL; + Status = vboxUsbMonContextCreate(&pCtx); + if (Status == STATUS_SUCCESS) + { + Assert(pCtx); + pFileObj->FsContext = pCtx; + } + else + { + WARN(("vboxUsbMonContextCreate failed Status (0x%x)", Status)); + } + + pIrp->IoStatus.Status = Status; + pIrp->IoStatus.Information = 0; + IoCompleteRequest(pIrp, IO_NO_INCREMENT); + return Status; +} + +static int VBoxUsbMonFltAdd(PVBOXUSBMONCTX pContext, PUSBFILTER pFilter, uintptr_t *pId) +{ +#ifdef VBOXUSBMON_DBG_NO_FILTERS + static uintptr_t idDummy = 1; + *pId = idDummy; + ++idDummy; + return VINF_SUCCESS; +#else + int rc = VBoxUsbFltAdd(&pContext->FltCtx, pFilter, pId); + return rc; +#endif +} + +static int VBoxUsbMonFltRemove(PVBOXUSBMONCTX pContext, uintptr_t uId) +{ +#ifdef VBOXUSBMON_DBG_NO_FILTERS + return VINF_SUCCESS; +#else + int rc = VBoxUsbFltRemove(&pContext->FltCtx, uId); + return rc; +#endif +} + +static NTSTATUS VBoxUsbMonRunFilters(PVBOXUSBMONCTX pContext) +{ + NTSTATUS Status = VBoxUsbFltFilterCheck(&pContext->FltCtx); + return Status; +} + +static NTSTATUS VBoxUsbMonGetDevice(PVBOXUSBMONCTX pContext, HVBOXUSBDEVUSR hDevice, PUSBSUP_GETDEV_MON pInfo) +{ + NTSTATUS Status = VBoxUsbFltGetDevice(&pContext->FltCtx, hDevice, pInfo); + return Status; +} + +static NTSTATUS vboxUsbMonIoctlDispatch(PVBOXUSBMONCTX pContext, ULONG Ctl, PVOID pvBuffer, ULONG cbInBuffer, + ULONG cbOutBuffer, ULONG_PTR *pInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + ULONG_PTR Info = 0; + switch (Ctl) + { + case SUPUSBFLT_IOCTL_GET_VERSION: + { + PUSBSUP_VERSION pOut = (PUSBSUP_VERSION)pvBuffer; + + LOG(("SUPUSBFLT_IOCTL_GET_VERSION")); + if (!pvBuffer || cbOutBuffer != sizeof(*pOut) || cbInBuffer != 0) + { + WARN(("SUPUSBFLT_IOCTL_GET_VERSION: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, 0, cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + pOut->u32Major = USBMON_MAJOR_VERSION; + pOut->u32Minor = USBMON_MINOR_VERSION; + Info = sizeof (*pOut); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_ADD_FILTER: + { + PUSBFILTER pFilter = (PUSBFILTER)pvBuffer; + PUSBSUP_FLTADDOUT pOut = (PUSBSUP_FLTADDOUT)pvBuffer; + uintptr_t uId = 0; + int rc; + if (RT_UNLIKELY(!pvBuffer || cbInBuffer != sizeof (*pFilter) || cbOutBuffer != sizeof (*pOut))) + { + WARN(("SUPUSBFLT_IOCTL_ADD_FILTER: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, sizeof (*pFilter), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + + rc = VBoxUsbMonFltAdd(pContext, pFilter, &uId); + pOut->rc = rc; + pOut->uId = uId; + Info = sizeof (*pOut); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_REMOVE_FILTER: + { + uintptr_t *pIn = (uintptr_t *)pvBuffer; + int *pRc = (int *)pvBuffer; + + if (!pvBuffer || cbInBuffer != sizeof (*pIn) || (cbOutBuffer && cbOutBuffer != sizeof (*pRc))) + { + WARN(("SUPUSBFLT_IOCTL_REMOVE_FILTER: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, sizeof (*pIn), cbOutBuffer, 0)); + Status = STATUS_INVALID_PARAMETER; + break; + } + LOG(("SUPUSBFLT_IOCTL_REMOVE_FILTER %x", *pIn)); + int rc = VBoxUsbMonFltRemove(pContext, *pIn); + if (cbOutBuffer) + { + /* we've validated that already */ + Assert(cbOutBuffer == (ULONG)*pRc); + *pRc = rc; + Info = sizeof (*pRc); + } + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case SUPUSBFLT_IOCTL_RUN_FILTERS: + { + if (pvBuffer || cbInBuffer || cbOutBuffer) + { + WARN(("SUPUSBFLT_IOCTL_RUN_FILTERS: Invalid input/output sizes. cbIn=%d expected %d. cbOut=%d expected %d.", + cbInBuffer, 0, cbOutBuffer, 0)); + Status = STATUS_INVALID_PARAMETER; + break; + } + LOG(("SUPUSBFLT_IOCTL_RUN_FILTERS ")); + Status = VBoxUsbMonRunFilters(pContext); + ASSERT_WARN(Status != STATUS_PENDING, ("status pending!")); + break; + } + + case SUPUSBFLT_IOCTL_GET_DEVICE: + { + HVBOXUSBDEVUSR hDevice; + PUSBSUP_GETDEV_MON pOut = (PUSBSUP_GETDEV_MON)pvBuffer; + if (!pvBuffer || cbInBuffer != sizeof (hDevice) || cbOutBuffer < sizeof (*pOut)) + { + WARN(("SUPUSBFLT_IOCTL_GET_DEVICE: Invalid input/output sizes! cbIn=%d expected %d. cbOut=%d expected >= %d.", + cbInBuffer, sizeof (hDevice), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + hDevice = *(HVBOXUSBDEVUSR*)pvBuffer; + if (!hDevice) + { + WARN(("SUPUSBFLT_IOCTL_GET_DEVICE: hDevice is NULL!", + cbInBuffer, sizeof (hDevice), cbOutBuffer, sizeof (*pOut))); + Status = STATUS_INVALID_PARAMETER; + break; + } + + Status = VBoxUsbMonGetDevice(pContext, hDevice, pOut); + + if (NT_SUCCESS(Status)) + { + Info = sizeof (*pOut); + } + else + { + WARN(("VBoxUsbMonGetDevice fail 0x%x", Status)); + } + break; + } + + default: + WARN(("Unknown code 0x%x", Ctl)); + Status = STATUS_INVALID_PARAMETER; + break; + } + + ASSERT_WARN(Status != STATUS_PENDING, ("Status pending!")); + + *pInfo = Info; + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + ULONG_PTR Info = 0; + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + PFILE_OBJECT pFileObj = pSl->FileObject; + Assert(pFileObj); + Assert(pFileObj->FsContext); + PVBOXUSBMONCTX pCtx = (PVBOXUSBMONCTX)pFileObj->FsContext; + Assert(pCtx); + Status = vboxUsbMonIoctlDispatch(pCtx, + pSl->Parameters.DeviceIoControl.IoControlCode, + pIrp->AssociatedIrp.SystemBuffer, + pSl->Parameters.DeviceIoControl.InputBufferLength, + pSl->Parameters.DeviceIoControl.OutputBufferLength, + &Info); + ASSERT_WARN(Status != STATUS_PENDING, ("Status pending")); + + IoReleaseRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + } + else + { + WARN(("IoAcquireRemoveLock failed Status (0x%x)", Status)); + } + + pIrp->IoStatus.Information = Info; + pIrp->IoStatus.Status = Status; + IoCompleteRequest (pIrp, IO_NO_INCREMENT); + return Status; +} + +static NTSTATUS vboxUsbMonInternalIoctlDispatch(ULONG Ctl, PVOID pvBuffer, ULONG_PTR *pInfo) +{ + NTSTATUS Status = STATUS_SUCCESS; + *pInfo = 0; + switch (Ctl) + { + case VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION: + { + PVBOXUSBIDC_VERSION pOut = (PVBOXUSBIDC_VERSION)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_GET_VERSION: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + pOut->u32Major = VBOXUSBIDC_VERSION_MAJOR; + pOut->u32Minor = VBOXUSBIDC_VERSION_MINOR; + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP: + { + PVBOXUSBIDC_PROXY_STARTUP pOut = (PVBOXUSBIDC_PROXY_STARTUP)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_STARTUP: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + PDEVICE_OBJECT pDevObj = pOut->u.pPDO; + pOut->u.hDev = VBoxUsbFltProxyStarted(pDevObj); + + /* If we couldn't find the PDO in our list, that's a real problem and + * the capturing will not really work. Log an error. + */ + if (!pOut->u.hDev) + vboxUsbMonLogError(IO_ERR_DRIVER_ERROR, STATUS_SUCCESS, 2, sizeof("INTERNAL_IOCTL_PROXY_STARTUP"), "INTERNAL_IOCTL_PROXY_STARTUP"); + + ASSERT_WARN(pOut->u.hDev, ("zero hDev")); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + case VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN: + { + PVBOXUSBIDC_PROXY_TEARDOWN pOut = (PVBOXUSBIDC_PROXY_TEARDOWN)pvBuffer; + + LOG(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN")); + if (!pvBuffer) + { + WARN(("VBOXUSBIDC_INTERNAL_IOCTL_PROXY_TEARDOWN: Buffer is NULL")); + Status = STATUS_INVALID_PARAMETER; + break; + } + + ASSERT_WARN(pOut->hDev, ("zero hDev")); + VBoxUsbFltProxyStopped(pOut->hDev); + ASSERT_WARN(Status == STATUS_SUCCESS, ("unexpected status, 0x%x", Status)); + break; + } + + default: + { + WARN(("Unknown code 0x%x", Ctl)); + Status = STATUS_INVALID_PARAMETER; + break; + } + } + + return Status; +} + +static NTSTATUS _stdcall VBoxUsbMonInternalDeviceControl(PDEVICE_OBJECT pDevObj, PIRP pIrp) +{ + ULONG_PTR Info = 0; + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + if (NT_SUCCESS(Status)) + { + PIO_STACK_LOCATION pSl = IoGetCurrentIrpStackLocation(pIrp); + Status = vboxUsbMonInternalIoctlDispatch(pSl->Parameters.DeviceIoControl.IoControlCode, + pSl->Parameters.Others.Argument1, + &Info); + Assert(Status != STATUS_PENDING); + + IoReleaseRemoveLock(&g_VBoxUsbMonGlobals.RmLock, pDevObj); + } + + pIrp->IoStatus.Information = Info; + pIrp->IoStatus.Status = Status; + IoCompleteRequest (pIrp, IO_NO_INCREMENT); + return Status; +} + +/** + * Unload the driver. + * + * @param pDrvObj Driver object. + */ +static void _stdcall VBoxUsbMonUnload(PDRIVER_OBJECT pDrvObj) +{ + RT_NOREF1(pDrvObj); + LOG(("VBoxUSBMonUnload pDrvObj (0x%p)", pDrvObj)); + + IoReleaseRemoveLockAndWait(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + + Assert(!g_VBoxUsbMonGlobals.cOpens); + + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, USBMON_DEVICE_NAME_DOS); + IoDeleteSymbolicLink(&DosName); + + IoDeleteDevice(g_VBoxUsbMonGlobals.pDevObj); + + /* cleanup the logger */ + PRTLOGGER pLogger = RTLogRelSetDefaultInstance(NULL); + if (pLogger) + RTLogDestroy(pLogger); + pLogger = RTLogSetDefaultInstance(NULL); + if (pLogger) + RTLogDestroy(pLogger); +} + +RT_C_DECLS_BEGIN +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath); +RT_C_DECLS_END + +/** + * Driver entry point. + * + * @returns appropriate status code. + * @param pDrvObj Pointer to driver object. + * @param pRegPath Registry base path. + */ +NTSTATUS _stdcall DriverEntry(PDRIVER_OBJECT pDrvObj, PUNICODE_STRING pRegPath) +{ + RT_NOREF1(pRegPath); +#ifdef VBOX_USB_WITH_VERBOSE_LOGGING + RTLogGroupSettings(0, "+default.e.l.f.l2.l3"); + RTLogDestinations(0, "debugger"); +#endif + + LOGREL(("Built %s %s", __DATE__, __TIME__)); + + memset (&g_VBoxUsbMonGlobals, 0, sizeof (g_VBoxUsbMonGlobals)); + + VBOX_PNPHOOKSTUB_INIT(0); + VBOX_PNPHOOKSTUB_INIT(1); + VBOX_PNPHOOKSTUB_INIT(2); + VBOX_PNPHOOKSTUB_INIT(3); + VBOX_PNPHOOKSTUB_INIT(4); + AssertCompile(VBOXUSBMON_MAXDRIVERS == 5); + + KeInitializeEvent(&g_VBoxUsbMonGlobals.OpenSynchEvent, SynchronizationEvent, TRUE /* signaled */); + IoInitializeRemoveLock(&g_VBoxUsbMonGlobals.RmLock, VBOXUSBMON_MEMTAG, 1, 100); + UNICODE_STRING DevName; + PDEVICE_OBJECT pDevObj; + /* create the device */ + RtlInitUnicodeString(&DevName, USBMON_DEVICE_NAME_NT); + NTSTATUS Status = IoAcquireRemoveLock(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + if (NT_SUCCESS(Status)) + { + Status = IoCreateDevice(pDrvObj, sizeof (VBOXUSBMONINS), &DevName, FILE_DEVICE_UNKNOWN, 0, FALSE, &pDevObj); + if (NT_SUCCESS(Status)) + { + UNICODE_STRING DosName; + RtlInitUnicodeString(&DosName, USBMON_DEVICE_NAME_DOS); + Status = IoCreateSymbolicLink(&DosName, &DevName); + if (NT_SUCCESS(Status)) + { + PVBOXUSBMONINS pDevExt = (PVBOXUSBMONINS)pDevObj->DeviceExtension; + memset(pDevExt, 0, sizeof(*pDevExt)); + + pDrvObj->DriverUnload = VBoxUsbMonUnload; + pDrvObj->MajorFunction[IRP_MJ_CREATE] = VBoxUsbMonCreate; + pDrvObj->MajorFunction[IRP_MJ_CLOSE] = VBoxUsbMonClose; + pDrvObj->MajorFunction[IRP_MJ_DEVICE_CONTROL] = VBoxUsbMonDeviceControl; + pDrvObj->MajorFunction[IRP_MJ_INTERNAL_DEVICE_CONTROL] = VBoxUsbMonInternalDeviceControl; + + g_VBoxUsbMonGlobals.pDevObj = pDevObj; + LOG(("VBoxUSBMon::DriverEntry returning STATUS_SUCCESS")); + return STATUS_SUCCESS; + } + IoDeleteDevice(pDevObj); + } + IoReleaseRemoveLockAndWait(&g_VBoxUsbMonGlobals.RmLock, &g_VBoxUsbMonGlobals); + } + + return Status; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h new file mode 100644 index 00000000..342ab9d8 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.h @@ -0,0 +1,67 @@ +/* $Id: VBoxUsbMon.h $ */ +/** @file + * VBox USB Monitor + */ +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#ifndef VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h +#define VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <VBox/cdefs.h> +#include <VBox/types.h> +#include <iprt/assert.h> +#include <VBox/sup.h> +#include <iprt/asm.h> +#include <VBox/log.h> + +#ifdef DEBUG +/* disables filters */ +//#define VBOXUSBMON_DBG_NO_FILTERS +/* disables pnp hooking */ +//#define VBOXUSBMON_DBG_NO_PNPHOOK +#endif + +#include "../../../win/VBoxDbgLog.h" +#include "../cmn/VBoxDrvTool.h" +#include "../cmn/VBoxUsbTool.h" + +#include "VBoxUsbHook.h" +#include "VBoxUsbFlt.h" + +PVOID VBoxUsbMonMemAlloc(SIZE_T cbBytes); +PVOID VBoxUsbMonMemAllocZ(SIZE_T cbBytes); +VOID VBoxUsbMonMemFree(PVOID pvMem); + +NTSTATUS VBoxUsbMonGetDescriptor(PDEVICE_OBJECT pDevObj, void *buffer, int size, int type, int index, int language_id); +NTSTATUS VBoxUsbMonQueryBusRelations(PDEVICE_OBJECT pDevObj, PFILE_OBJECT pFileObj, PDEVICE_RELATIONS *pDevRelations); + +void vboxUsbDbgPrintUnicodeString(PUNICODE_STRING pUnicodeString); + +typedef DECLCALLBACK(BOOLEAN) FNVBOXUSBMONDEVWALKER(PFILE_OBJECT pHubFile, PDEVICE_OBJECT pHubDo, PVOID pvContext); +typedef FNVBOXUSBMONDEVWALKER *PFNVBOXUSBMONDEVWALKER; + +VOID vboxUsbMonHubDevWalk(PFNVBOXUSBMONDEVWALKER pfnWalker, PVOID pvWalker); + +#endif /* !VBOX_INCLUDED_SRC_VBoxUSB_win_mon_VBoxUsbMon_h */ diff --git a/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc new file mode 100644 index 00000000..61009982 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/mon/VBoxUsbMon.rc @@ -0,0 +1,60 @@ +/* $Id: VBoxUsbMon.rc $ */ +/** @file + * VBoxUSBMon - Resource file containing version info and icon. + */ + +/* + * Copyright (C) 2011-2020 Oracle Corporation + * + * 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. + * + * 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. + */ + +#include <windows.h> +#include <VBox/version.h> + +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VBOX_RC_FILE_VERSION + PRODUCTVERSION VBOX_RC_FILE_VERSION + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK + FILEFLAGS VBOX_RC_FILE_FLAGS + FILEOS VBOX_RC_FILE_OS + FILETYPE VBOX_RC_TYPE_DRV + FILESUBTYPE VFT2_DRV_SYSTEM +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" // Lang=US English, CharSet=Unicode + BEGIN + VALUE "FileDescription", "VirtualBox USB Monitor Driver\0" + VALUE "InternalName", "VBoxUSBMon\0" + VALUE "OriginalFilename", "VBoxUSBMon.sys\0" + VALUE "CompanyName", VBOX_RC_COMPANY_NAME + VALUE "FileVersion", VBOX_RC_FILE_VERSION_STR + VALUE "LegalCopyright", VBOX_RC_LEGAL_COPYRIGHT + VALUE "ProductName", VBOX_RC_PRODUCT_NAME_STR + VALUE "ProductVersion", VBOX_RC_PRODUCT_VERSION_STR + VBOX_RC_MORE_STRINGS + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END diff --git a/src/VBox/HostDrivers/VBoxUSB/win/testcase/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/testcase/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/testcase/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp b/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp new file mode 100644 index 00000000..0a9fdb60 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/testcase/USBTest.cpp @@ -0,0 +1,373 @@ +/* $Id: USBTest.cpp $ */ +/** @file + * VBox host drivers - USB drivers - Filter & driver installation + */ + +/* + * Copyright (C) 2006-2020 Oracle Corporation + * + * 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. + * + * 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 * +*********************************************************************************************************************************/ +#include <iprt/win/windows.h> +#include <iprt/win/setupapi.h> +#include <newdev.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/string.h> +#include <VBox/err.h> +#include <stdio.h> +#include <VBox/usblib.h> +#include <VBox/VBoxDrvCfg-win.h> + +/** Handle to the open device. */ +static HANDLE g_hUSBMonitor = INVALID_HANDLE_VALUE; +/** Flags whether or not we started the service. */ +static bool g_fStartedService = false; + +/** + * Attempts to start the service, creating it if necessary. + * + * @returns 0 on success. + * @returns -1 on failure. + * @param fRetry Indicates retry call. + */ +int usbMonStartService(void) +{ + HRESULT hr = VBoxDrvCfgSvcStart(USBMON_SERVICE_NAME_W); + if (hr != S_OK) + { + AssertMsgFailed(("couldn't start service, hr (0x%x)\n", hr)); + return -1; + } + return 0; +} + +/** + * Stops a possibly running service. + * + * @returns 0 on success. + * @returns -1 on failure. + */ +int usbMonStopService(void) +{ + printf("usbMonStopService\n"); + /* + * Assume it didn't exist, so we'll create the service. + */ + int rc = -1; + SC_HANDLE hSMgr = OpenSCManager(NULL, NULL, SERVICE_STOP | SERVICE_QUERY_STATUS); + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsg(hSMgr, ("OpenSCManager(,,delete) failed rc=%d\n", LastError)); + if (hSMgr) + { + SC_HANDLE hService = OpenServiceW(hSMgr, USBMON_SERVICE_NAME_W, SERVICE_STOP | SERVICE_QUERY_STATUS); + if (hService) + { + /* + * Stop the service. + */ + SERVICE_STATUS Status; + QueryServiceStatus(hService, &Status); + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + else if (ControlService(hService, SERVICE_CONTROL_STOP, &Status)) + { + int iWait = 100; + while (Status.dwCurrentState == SERVICE_STOP_PENDING && iWait-- > 0) + { + Sleep(100); + QueryServiceStatus(hService, &Status); + } + if (Status.dwCurrentState == SERVICE_STOPPED) + rc = 0; + else + AssertMsgFailed(("Failed to stop service. status=%d\n", Status.dwCurrentState)); + } + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("ControlService failed with LastError=%Rwa. status=%d\n", LastError, Status.dwCurrentState)); + } + CloseServiceHandle(hService); + } + else if (GetLastError() == ERROR_SERVICE_DOES_NOT_EXIST) + rc = 0; + else + { + DWORD LastError = GetLastError(); NOREF(LastError); + AssertMsgFailed(("OpenService failed LastError=%Rwa\n", LastError)); + } + CloseServiceHandle(hSMgr); + } + return rc; +} +/** + * Release specified USB device to the host. + * + * @returns VBox status code + * @param usVendorId Vendor id + * @param usProductId Product id + * @param usRevision Revision + */ +int usbMonReleaseDevice(USHORT usVendorId, USHORT usProductId, USHORT usRevision) +{ + USBSUP_RELEASE release; + DWORD cbReturned = 0; + + printf("usbLibReleaseDevice %x %x %x\n", usVendorId, usProductId, usRevision); + + release.usVendorId = usVendorId; + release.usProductId = usProductId; + release.usRevision = usRevision; + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_RELEASE_DEVICE, &release, sizeof(release), NULL, 0, &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + + return VINF_SUCCESS; +} + + +/** + * Add USB device filter + * + * @returns VBox status code. + * @param usVendorId Vendor id + * @param usProductId Product id + * @param usRevision Revision + * @param ppID Pointer to filter id + */ +int usbMonInsertFilter(USHORT usVendorId, USHORT usProductId, USHORT usRevision, void **ppID) +{ + USBFILTER filter; + USBSUP_FLTADDOUT flt_add; + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + printf("usblibInsertFilter %04X %04X %04X\n", usVendorId, usProductId, usRevision); + + USBFilterInit(&filter, USBFILTERTYPE_CAPTURE); + USBFilterSetNumExact(&filter, USBFILTERIDX_VENDOR_ID, usVendorId, true); + USBFilterSetNumExact(&filter, USBFILTERIDX_PRODUCT_ID, usProductId, true); + USBFilterSetNumExact(&filter, USBFILTERIDX_DEVICE_REV, usRevision, true); + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_ADD_FILTER, &filter, sizeof(filter), &flt_add, sizeof(flt_add), &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + *ppID = (void *)flt_add.uId; + return VINF_SUCCESS; +} + +/** + * Applies existing filters to currently plugged-in USB devices + * + * @returns VBox status code. + */ +int usbMonRunFilters(void) +{ + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_RUN_FILTERS, NULL, 0, NULL, 0, &cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + return VINF_SUCCESS; +} + +/** + * Remove USB device filter + * + * @returns VBox status code. + * @param aID Filter id + */ +int usbMonRemoveFilter (void *aID) +{ + uintptr_t uId; + DWORD cbReturned = 0; + + Assert(g_hUSBMonitor); + + printf("usblibRemoveFilter %p\n", aID); + + uId = (uintptr_t)aID; + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_REMOVE_FILTER, &uId, sizeof(uId), NULL, 0,&cbReturned, NULL)) + { + AssertMsgFailed(("DeviceIoControl failed with %d\n", GetLastError())); + return RTErrConvertFromWin32(GetLastError()); + } + return VINF_SUCCESS; +} + +/** + * Initialize the USB monitor + * + * @returns VBox status code. + */ +int usbMonitorInit() +{ + int rc; + USBSUP_VERSION version = {0}; + DWORD cbReturned; + + printf("usbproxy: usbLibInit\n"); + + g_hUSBMonitor = CreateFile (USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM, + NULL); // No template file + + if (g_hUSBMonitor == INVALID_HANDLE_VALUE) + { + usbMonStartService(); + + g_hUSBMonitor = CreateFile (USBMON_DEVICE_NAME, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, // no SECURITY_ATTRIBUTES structure + OPEN_EXISTING, // No special create flags + FILE_ATTRIBUTE_SYSTEM, + NULL); // No template file + + if (g_hUSBMonitor == INVALID_HANDLE_VALUE) + { + /* AssertFailed(); */ + printf("usbproxy: Unable to open filter driver!! (rc=%d)\n", GetLastError()); + rc = VERR_FILE_NOT_FOUND; + goto failure; + } + } + + /* + * Check the version + */ + cbReturned = 0; + if (!DeviceIoControl(g_hUSBMonitor, SUPUSBFLT_IOCTL_GET_VERSION, NULL, 0,&version, sizeof(version), &cbReturned, NULL)) + { + printf("usbproxy: Unable to query filter version!! (rc=%d)\n", GetLastError()); + rc = VERR_VERSION_MISMATCH; + goto failure; + } + + if ( version.u32Major != USBMON_MAJOR_VERSION +#if USBMON_MINOR_VERSION != 0 + || version.u32Minor < USBMON_MINOR_VERSION +#endif + ) + { + printf("usbproxy: Filter driver version mismatch!!\n"); + rc = VERR_VERSION_MISMATCH; + goto failure; + } + + return VINF_SUCCESS; + +failure: + if (g_hUSBMonitor != INVALID_HANDLE_VALUE) + { + CloseHandle(g_hUSBMonitor); + g_hUSBMonitor = INVALID_HANDLE_VALUE; + } + return rc; +} + + + +/** + * Terminate the USB monitor + * + * @returns VBox status code. + */ +int usbMonitorTerm() +{ + if (g_hUSBMonitor != INVALID_HANDLE_VALUE) + { + CloseHandle(g_hUSBMonitor); + g_hUSBMonitor = INVALID_HANDLE_VALUE; + } + /* + * If we started the service we might consider stopping it too. + * + * Since this won't work unless the process starting it is the + * last user we might wanna skip this... + */ + if (g_fStartedService) + { + usbMonStopService(); + g_fStartedService = false; + } + + return VINF_SUCCESS; +} + + +int __cdecl main(int argc, char **argv) +{ + int rc; + int c; + RT_NOREF2(argc, argv); + + printf("USB test\n"); + + rc = usbMonitorInit(); + AssertRC(rc); + + void *pId1, *pId2, *pId3; + + usbMonInsertFilter(0x0529, 0x0514, 0x0100, &pId1); + usbMonInsertFilter(0x0A16, 0x2499, 0x0100, &pId2); + usbMonInsertFilter(0x80EE, 0x0030, 0x0110, &pId3); + + printf("Waiting to capture devices... enter 'r' to run filters\n"); + c = getchar(); + if (c == 'r') + { + usbMonRunFilters(); + printf("Waiting to capture devices...\n"); + getchar(); /* eat the '\n' */ + getchar(); /* wait for more input */ + } + + printf("Releasing device\n"); + usbMonReleaseDevice(0xA16, 0x2499, 0x100); + + usbMonRemoveFilter(pId1); + usbMonRemoveFilter(pId2); + usbMonRemoveFilter(pId3); + + rc = usbMonitorTerm(); + + return 0; +} diff --git a/src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup b/src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/usbd/Makefile.kup diff --git a/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def b/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def new file mode 100644 index 00000000..e5664e15 --- /dev/null +++ b/src/VBox/HostDrivers/VBoxUSB/win/usbd/usbd.def @@ -0,0 +1,64 @@ +; $Id: usbd.def $ +;; @file +; Export definitions +; + +; +; Copyright (C) 2006-2020 Oracle Corporation +; +; 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. +; +; 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. +; + +LIBRARY USBD.SYS + +EXPORTS +USBD_AllocateDeviceName +USBD_CalculateUsbBandwidth +USBD_CompleteRequest +USBD_CreateConfigurationRequest +USBD_CreateDevice +USBD_Debug_GetHeap +USBD_Debug_LogEntry +USBD_Debug_RetHeap +USBD_Dispatch +USBD_FreeDeviceMutex +USBD_FreeDeviceName +USBD_GetDeviceInformation +USBD_GetInterfaceLength +USBD_GetPdoRegistryParameter +USBD_GetSuspendPowerState +USBD_GetUSBDIVersion +USBD_InitializeDevice +USBD_MakePdoName +USBD_ParseConfigurationDescriptor +USBD_QueryBusTime +USBD_RegisterHcDeviceCapabilities +USBD_RegisterHcFilter +USBD_RegisterHostController +USBD_RemoveDevice +USBD_RestoreDevice +USBD_SetSuspendPowerState +USBD_WaitDeviceMutex +_USBD_CreateConfigurationRequestEx@8 +_USBD_ParseDescriptors@16 +_USBD_ParseConfigurationDescriptorEx@28 + +; win64 strips the _stdcall postfix +USBD_CreateConfigurationRequestEx +USBD_ParseConfigurationDescriptorEx +USBD_ParseDescriptors |