diff options
Diffstat (limited to '')
17 files changed, 6564 insertions, 0 deletions
diff --git a/src/VBox/ValidationKit/utils/usb/Makefile.kmk b/src/VBox/ValidationKit/utils/usb/Makefile.kmk new file mode 100644 index 00000000..e8dc1923 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/Makefile.kmk @@ -0,0 +1,74 @@ +# $Id: Makefile.kmk $ +## @file +# VirtualBox Validation Kit - USB test helpers. +# + +# +# Copyright (C) 2014-2023 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see <https://www.gnu.org/licenses>. +# +# The contents of this file may alternatively be used under the terms +# of the Common Development and Distribution License Version 1.0 +# (CDDL), a copy of it is provided in the "COPYING.CDDL" file included +# in the VirtualBox 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. +# +# SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 +# + +SUB_DEPTH = ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +# +# USB Linux test frontend. +# +ifeq ($(KBUILD_TARGET),linux) + PROGRAMS += UsbTest + UsbTest_TEMPLATE = VBoxValidationKitR3 + UsbTest_SOURCES = UsbTest.cpp +endif + +PROGRAMS += UsbTestService +UsbTestService_TEMPLATE = VBoxValidationKitR3 +ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + UsbTestService_DEFS = \ + KBUILD_TARGET="$(KBUILD_TARGET)" \ + KBUILD_TARGET_ARCH="$(KBUILD_TARGET_ARCH)" +else + UsbTestService_DEFS = \ + KBUILD_TARGET=\"$(KBUILD_TARGET)\" \ + KBUILD_TARGET_ARCH=\"$(KBUILD_TARGET_ARCH)\" +endif +UsbTestService_SOURCES = \ + UsbTestService.cpp \ + UsbTestServiceGadgetCfg.cpp \ + UsbTestServiceGadgetClassTest.cpp \ + UsbTestServiceGadgetHost.cpp \ + UsbTestServiceGadgetHostUsbIp.cpp \ + UsbTestServiceGadget.cpp \ + UsbTestServiceProtocol.cpp \ + UsbTestServiceTcp.cpp +UsbTestService_SOURCES.linux = \ + UsbTestServicePlatform-linux.cpp + +$(evalcall def_vbox_validationkit_process_python_sources) +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp new file mode 100644 index 00000000..809e3fe1 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTest.cpp @@ -0,0 +1,667 @@ +/* $Id: UsbTest.cpp $ */ +/** @file + * UsbTest - User frontend for the Linux usbtest USB test and benchmarking module. + * Integrates with our test framework for nice outputs. + */ + +/* + * Copyright (C) 2014-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/file.h> +#include <iprt/getopt.h> +#include <iprt/path.h> +#include <iprt/param.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/test.h> + +#include <iprt/linux/sysfs.h> + +#include <unistd.h> +#include <errno.h> +#include <limits.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <sys/ioctl.h> +#include <linux/usbdevice_fs.h> + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * USB test request data. + * There is no public header with this information so we define it ourself here. + */ +typedef struct USBTESTPARMS +{ + /** Specifies the test to run. */ + uint32_t idxTest; + /** How many iterations the test should be executed. */ + uint32_t cIterations; + /** Size of the data packets. */ + uint32_t cbData; + /** Size of */ + uint32_t cbVariation; + /** Length of the S/G list for the test. */ + uint32_t cSgLength; + /** Returned time data after completing the test. */ + struct timeval TimeTest; +} USBTESTPARAMS; +/** Pointer to a test parameter structure. */ +typedef USBTESTPARAMS *PUSBTESTPARAMS; + +/** + * USB device descriptor. Used to search for the test device based + * on the vendor and product id. + */ +#pragma pack(1) +typedef struct USBDEVDESC +{ + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdUSB; + uint8_t bDeviceClass; + uint8_t bDeviceSubClass; + uint8_t bDeviceProtocol; + uint8_t bMaxPacketSize0; + uint16_t idVendor; + uint16_t idProduct; + uint16_t bcdDevice; + uint8_t iManufacturer; + uint8_t iProduct; + uint8_t iSerialNumber; + uint8_t bNumConfigurations; +} USBDEVDESC; +#pragma pack() + +#define USBTEST_REQUEST _IOWR('U', 100, USBTESTPARMS) + +/** + * Callback to set up the test parameters for a specific test. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if setting the parameters up succeeded. Any other error code + * otherwise indicating the kind of error. + * @param idxTest The test index. + * @param pszTest Test name. + * @param pParams The USB test parameters to set up. + */ +typedef DECLCALLBACKTYPE(int, FNUSBTESTPARAMSSETUP,(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams)); +/** Pointer to a USB test parameters setup callback. */ +typedef FNUSBTESTPARAMSSETUP *PFNUSBTESTPARAMSSETUP; + +/** + * USB test descriptor. + */ +typedef struct USBTESTDESC +{ + /** (Sort of) Descriptive test name. */ + const char *pszName; + /** Flag whether the test is excluded. */ + bool fExcluded; + /** The parameter setup callback. */ + PFNUSBTESTPARAMSSETUP pfnParamsSetup; +} USBTESTDESC; +/** Pointer a USB test descriptor. */ +typedef USBTESTDESC *PUSBTESTDESC; + +/** + * USB speed values. + */ +typedef enum USBTESTSPEED +{ + USBTESTSPEED_ANY = 0, + USBTESTSPEED_UNKNOWN, + USBTESTSPEED_LOW, + USBTESTSPEED_FULL, + USBTESTSPEED_HIGH, + USBTESTSPEED_SUPER +} USBTESTSPEED; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Some forward method declarations. */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams); + +/** Command line parameters */ +static const RTGETOPTDEF g_aCmdOptions[] = +{ + {"--device", 'd', RTGETOPT_REQ_STRING }, + {"--help", 'h', RTGETOPT_REQ_NOTHING}, + {"--exclude", 'e', RTGETOPT_REQ_UINT32}, + {"--exclude-all", 'a', RTGETOPT_REQ_NOTHING}, + {"--include", 'i', RTGETOPT_REQ_UINT32}, + {"--expected-speed", 's', RTGETOPT_REQ_STRING } +}; + +static USBTESTDESC g_aTests[] = +{ + /* pszTest fExcluded pfnParamsSetup */ + {"NOP", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Non-queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk write variabe size", false, usbTestParamsSetupReadWrite}, + {"Queued Bulk read variabe size", false, usbTestParamsSetupReadWrite}, + {"Chapter 9 Control Test", false, usbTestParamsSetupReadWrite}, + {"Queued control messaging", false, usbTestParamsSetupReadWrite}, + {"Unlink reads", false, usbTestParamsSetupReadWrite}, + {"Unlink writes", false, usbTestParamsSetupReadWrite}, + {"Set/Clear halts", false, usbTestParamsSetupReadWrite}, + {"Control writes", false, usbTestParamsSetupControlWrites}, + {"Isochronous write", false, usbTestParamsSetupReadWrite}, + {"Isochronous read", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk write unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Bulk read unaligned (no DMA)", false, usbTestParamsSetupReadWrite}, + {"Control writes unaligned", false, usbTestParamsSetupControlWrites}, + {"Isochronous write unaligned", false, usbTestParamsSetupReadWrite}, + {"Isochronous read unaligned", false, usbTestParamsSetupReadWrite}, + {"Unlink queued Bulk", false, usbTestParamsSetupReadWrite} +}; + +/** The test handle. */ +static RTTEST g_hTest; +/** The expected device speed. */ +static USBTESTSPEED g_enmSpeed = USBTESTSPEED_ANY; + +/** + * Setup callback for basic read/write (bulk, isochronous) tests. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupReadWrite(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + pParams->cbVariation = 512; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Setup callback for the control writes test. + * + * @copydoc FNUSBTESTPARAMSSETUP + */ +static DECLCALLBACK(int) usbTestParamsSetupControlWrites(unsigned idxTest, const char *pszTest, PUSBTESTPARAMS pParams) +{ + NOREF(idxTest); + NOREF(pszTest); + + pParams->cIterations = 1000; + pParams->cbData = 512; + /* + * Must be smaller than cbData or the parameter check in the usbtest module fails, + * no idea yet why it must be this. + */ + pParams->cbVariation = 256; + pParams->cSgLength = 32; + + return VINF_SUCCESS; +} + +/** + * Shows tool usage text. + */ +static void usbTestUsage(PRTSTREAM pStrm) +{ + char szExec[RTPATH_MAX]; + RTStrmPrintf(pStrm, "usage: %s [options]\n", + RTPathFilename(RTProcGetExecutablePath(szExec, sizeof(szExec)))); + RTStrmPrintf(pStrm, "\n"); + RTStrmPrintf(pStrm, "options: \n"); + + + for (unsigned i = 0; i < RT_ELEMENTS(g_aCmdOptions); i++) + { + const char *pszHelp; + switch (g_aCmdOptions[i].iShort) + { + case 'h': + pszHelp = "Displays this help and exit"; + break; + case 'd': + pszHelp = "Use the specified test device"; + break; + case 'e': + pszHelp = "Exclude the given test id from the list"; + break; + case 'a': + pszHelp = "Exclude all tests from the list (useful to enable single tests later with --include)"; + break; + case 'i': + pszHelp = "Include the given test id in the list"; + break; + case 's': + pszHelp = "The device speed to expect"; + break; + default: + pszHelp = "Option undocumented"; + break; + } + char szOpt[256]; + RTStrPrintf(szOpt, sizeof(szOpt), "%s, -%c", g_aCmdOptions[i].pszLong, g_aCmdOptions[i].iShort); + RTStrmPrintf(pStrm, " %-30s%s\n", szOpt, pszHelp); + } +} + +/** + * Searches for a USB test device and returns the bus and device ID and the device speed. + */ +static int usbTestDeviceQueryBusAndDevId(uint16_t *pu16BusId, uint16_t *pu16DevId, USBTESTSPEED *penmSpeed) +{ + bool fFound = false; + +#define USBTEST_USB_DEV_SYSFS "/sys/bus/usb/devices/" + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, USBTEST_USB_DEV_SYSFS); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if ( RT_SUCCESS(rc) + && RTStrNCmp(DirUsbBus.szName, "usb", 3) + && RTLinuxSysFsExists(USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName)) + { + int64_t idVendor = 0; + int64_t idProduct = 0; + int64_t iBusId = 0; + int64_t iDevId = 0; + char aszSpeed[20]; + + rc = RTLinuxSysFsReadIntFile(16, &idVendor, USBTEST_USB_DEV_SYSFS "%s/idVendor", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &idProduct, USBTEST_USB_DEV_SYSFS "%s/idProduct", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iBusId, USBTEST_USB_DEV_SYSFS "%s/busnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadIntFile(16, &iDevId, USBTEST_USB_DEV_SYSFS "%s/devnum", DirUsbBus.szName); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsReadStrFile(&aszSpeed[0], sizeof(aszSpeed), NULL, USBTEST_USB_DEV_SYSFS "%s/speed", DirUsbBus.szName); + + if ( RT_SUCCESS(rc) + && idVendor == 0x0525 + && idProduct == 0xa4a0) + { + if (penmSpeed) + { + /* Parse the speed. */ + if (!RTStrCmp(&aszSpeed[0], "1.5")) + *penmSpeed = USBTESTSPEED_LOW; + else if (!RTStrCmp(&aszSpeed[0], "12")) + *penmSpeed = USBTESTSPEED_FULL; + else if (!RTStrCmp(&aszSpeed[0], "480")) + *penmSpeed = USBTESTSPEED_HIGH; + else if ( !RTStrCmp(&aszSpeed[0], "5000") + || !RTStrCmp(&aszSpeed[0], "10000")) + *penmSpeed = USBTESTSPEED_SUPER; + else + *penmSpeed = USBTESTSPEED_UNKNOWN; + } + + if (pu16BusId) + *pu16BusId = (uint16_t)iBusId; + if (pu16DevId) + *pu16DevId = (uint16_t)iDevId; + fFound = true; + break; + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !fFound); + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDirUsb); + } + + if (RT_SUCCESS(rc) && !fFound) + rc = VERR_NOT_FOUND; + + return rc; +} + +/** + * Search for a USB test device and return the device path. + * + * @returns Path to the USB test device or NULL if none was found. + */ +static char *usbTestFindDevice(void) +{ + /* + * Very crude and quick way to search for the correct test device. + * Assumption is that the path looks like /dev/bus/usb/%3d/%3d. + */ + char *pszDevPath = NULL; + + RTDIR hDirUsb = NULL; + int rc = RTDirOpen(&hDirUsb, "/dev/bus/usb"); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbBus; + rc = RTDirRead(hDirUsb, &DirUsbBus, NULL); + if (RT_SUCCESS(rc)) + { + char aszPath[RTPATH_MAX + 1]; + RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), "/dev/bus/usb/%s", DirUsbBus.szName); + + RTDIR hDirUsbBus = NULL; + rc = RTDirOpen(&hDirUsbBus, &aszPath[0]); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirUsbDev; + rc = RTDirRead(hDirUsbBus, &DirUsbDev, NULL); + if (RT_SUCCESS(rc)) + { + char aszPathDev[RTPATH_MAX + 1]; + RTStrPrintf(&aszPathDev[0], RT_ELEMENTS(aszPathDev), "/dev/bus/usb/%s/%s", + DirUsbBus.szName, DirUsbDev.szName); + + RTFILE hFileDev; + rc = RTFileOpen(&hFileDev, aszPathDev, RTFILE_O_OPEN | RTFILE_O_READ | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + USBDEVDESC DevDesc; + + rc = RTFileRead(hFileDev, &DevDesc, sizeof(DevDesc), NULL); + RTFileClose(hFileDev); + + if ( RT_SUCCESS(rc) + && DevDesc.idVendor == 0x0525 + && DevDesc.idProduct == 0xa4a0) + pszDevPath = RTStrDup(aszPathDev); + } + + rc = VINF_SUCCESS; + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + rc = VINF_SUCCESS; + RTDirClose(hDirUsbBus); + } + } + else if (rc != VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + } while ( RT_SUCCESS(rc) + && !pszDevPath); + + RTDirClose(hDirUsb); + } + + return pszDevPath; +} + +static int usbTestIoctl(int iDevFd, int iInterface, PUSBTESTPARAMS pParams) +{ + struct usbdevfs_ioctl IoCtlData; + + IoCtlData.ifno = iInterface; + IoCtlData.ioctl_code = (int)USBTEST_REQUEST; + IoCtlData.data = pParams; + return ioctl(iDevFd, USBDEVFS_IOCTL, &IoCtlData); +} + +/** + * Test execution worker. + * + * @param pszDevice The device to use for testing. + */ +static void usbTestExec(const char *pszDevice) +{ + int iDevFd; + + RTTestSub(g_hTest, "Opening device"); + iDevFd = open(pszDevice, O_RDWR); + if (iDevFd != -1) + { + USBTESTPARAMS Params; + + RTTestPassed(g_hTest, "Opening device successful\n"); + + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + { + RTTestSub(g_hTest, g_aTests[i].pszName); + + if (g_aTests[i].fExcluded) + { + RTTestSkipped(g_hTest, "Excluded from list"); + continue; + } + + int rc = g_aTests[i].pfnParamsSetup(i, g_aTests[i].pszName, &Params); + if (RT_SUCCESS(rc)) + { + Params.idxTest = i; + + /* Assume the test interface has the number 0 for now. */ + int rcPosix = usbTestIoctl(iDevFd, 0, &Params); + if (rcPosix < 0 && errno == EOPNOTSUPP) + { + RTTestSkipped(g_hTest, "Not supported"); + continue; + } + + if (rcPosix < 0) + { + /* + * The error status code of the unlink testcase is + * offset by 2000 for the sync and 1000 for the sync code path + * (see drivers/usb/misc/usbtest.c in the Linux kernel sources). + * + * Adjust to the actual status code so converting doesn't assert. + */ + int iTmpErrno = errno; + if (iTmpErrno >= 2000) + iTmpErrno -= 2000; + else if (iTmpErrno >= 1000) + iTmpErrno -= 1000; + RTTestFailed(g_hTest, "Test failed with %Rrc\n", RTErrConvertFromErrno(iTmpErrno)); + } + else + { + uint64_t u64Ns = Params.TimeTest.tv_sec * RT_NS_1SEC + Params.TimeTest.tv_usec * RT_NS_1US; + RTTestValue(g_hTest, "Runtime", u64Ns, RTTESTUNIT_NS); + } + } + else + RTTestFailed(g_hTest, "Setting up test parameters failed with %Rrc\n", rc); + RTTestSubDone(g_hTest); + } + + close(iDevFd); + } + else + RTTestFailed(g_hTest, "Opening device failed with %Rrc\n", RTErrConvertFromErrno(errno)); + +} + +int main(int argc, char *argv[]) +{ + /* + * Init IPRT and globals. + */ + int rc = RTTestInitAndCreate("UsbTest", &g_hTest); + if (rc) + return rc; + + /* + * Default values. + */ + const char *pszDevice = NULL; + + RTGETOPTUNION ValueUnion; + RTGETOPTSTATE GetState; + RTGetOptInit(&GetState, argc, argv, g_aCmdOptions, RT_ELEMENTS(g_aCmdOptions), 1, 0 /* fFlags */); + while ((rc = RTGetOpt(&GetState, &ValueUnion))) + { + switch (rc) + { + case 'h': + usbTestUsage(g_pStdOut); + return RTEXITCODE_SUCCESS; + case 'd': + pszDevice = ValueUnion.psz; + break; + case 's': + if (!RTStrICmp(ValueUnion.psz, "Low")) + g_enmSpeed = USBTESTSPEED_LOW; + else if (!RTStrICmp(ValueUnion.psz, "Full")) + g_enmSpeed = USBTESTSPEED_FULL; + else if (!RTStrICmp(ValueUnion.psz, "High")) + g_enmSpeed = USBTESTSPEED_HIGH; + else if (!RTStrICmp(ValueUnion.psz, "Super")) + g_enmSpeed = USBTESTSPEED_SUPER; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid speed passed to --expected-speed\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'e': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = true; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --exclude\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + case 'a': + for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) + g_aTests[i].fExcluded = true; + break; + case 'i': + if (ValueUnion.u32 < RT_ELEMENTS(g_aTests)) + g_aTests[ValueUnion.u32].fExcluded = false; + else + { + RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "Invalid test number passed to --include\n"); + RTTestErrorInc(g_hTest); + return RTGetOptPrintError(VERR_INVALID_PARAMETER, &ValueUnion); + } + break; + default: + return RTGetOptPrintError(rc, &ValueUnion); + } + } + + /* + * Start testing. + */ + RTTestBanner(g_hTest); + + /* Find the first test device if none was given. */ + if (!pszDevice) + { + RTTestSub(g_hTest, "Detecting device"); + pszDevice = usbTestFindDevice(); + if (!pszDevice) + RTTestFailed(g_hTest, "Failed to find suitable device\n"); + + RTTestSubDone(g_hTest); + } + + if (pszDevice) + { + /* First check that the requested speed matches. */ + if (g_enmSpeed != USBTESTSPEED_ANY) + { + RTTestSub(g_hTest, "Checking correct device speed"); + + USBTESTSPEED enmSpeed = USBTESTSPEED_UNKNOWN; + rc = usbTestDeviceQueryBusAndDevId(NULL, NULL, &enmSpeed); + if (RT_SUCCESS(rc)) + { + if (enmSpeed == g_enmSpeed) + RTTestPassed(g_hTest, "Reported device speed matches requested speed\n"); + else + RTTestFailed(g_hTest, "Reported device speed doesn'match requested speed (%u vs %u)\n", + enmSpeed, g_enmSpeed); + } + else + RTTestFailed(g_hTest, "Failed to query device speed with rc=%Rrc\n", rc); + + RTTestSubDone(g_hTest); + } + usbTestExec(pszDevice); + } + + RTEXITCODE rcExit = RTTestSummaryAndDestroy(g_hTest); + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp new file mode 100644 index 00000000..0912ff51 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestService.cpp @@ -0,0 +1,1658 @@ +/* $Id: UsbTestService.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/alloca.h> +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/crc.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/err.h> +#include <iprt/getopt.h> +#include <iprt/handle.h> +#include <iprt/initterm.h> +#include <iprt/json.h> +#include <iprt/list.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/param.h> +#include <iprt/path.h> +#include <iprt/pipe.h> +#include <iprt/poll.h> +#include <iprt/process.h> +#include <iprt/stream.h> +#include <iprt/string.h> +#include <iprt/thread.h> + +#include "UsbTestServiceInternal.h" +#include "UsbTestServiceGadget.h" +#include "UsbTestServicePlatform.h" + + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +#define UTS_USBIP_PORT_FIRST 3240 +#define UTS_USBIP_PORT_LAST 3340 + +/** + * UTS client state. + */ +typedef enum UTSCLIENTSTATE +{ + /** Invalid client state. */ + UTSCLIENTSTATE_INVALID = 0, + /** Client is initialising, only the HOWDY and BYE packets are allowed. */ + UTSCLIENTSTATE_INITIALISING, + /** Client is in fully cuntional state and ready to process all requests. */ + UTSCLIENTSTATE_READY, + /** Client is destroying. */ + UTSCLIENTSTATE_DESTROYING, + /** 32bit hack. */ + UTSCLIENTSTATE_32BIT_HACK = 0x7fffffff +} UTSCLIENTSTATE; + +/** + * UTS client instance. + */ +typedef struct UTSCLIENT +{ + /** List node for new clients. */ + RTLISTNODE NdLst; + /** The current client state. */ + UTSCLIENTSTATE enmState; + /** Transport backend specific data. */ + PUTSTRANSPORTCLIENT pTransportClient; + /** Client hostname. */ + char *pszHostname; + /** Gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Handle fo the current configured gadget. */ + UTSGADGET hGadget; +} UTSCLIENT; +/** Pointer to a UTS client instance. */ +typedef UTSCLIENT *PUTSCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** + * Transport layers. + */ +static const PCUTSTRANSPORT g_apTransports[] = +{ + &g_TcpTransport, + //&g_SerialTransport, + //&g_FileSysTransport, + //&g_GuestPropTransport, + //&g_TestDevTransport, +}; + +/** The select transport layer. */ +static PCUTSTRANSPORT g_pTransport; +/** The config path. */ +static char g_szCfgPath[RTPATH_MAX]; +/** The scratch path. */ +static char g_szScratchPath[RTPATH_MAX]; +/** The default scratch path. */ +static char g_szDefScratchPath[RTPATH_MAX]; +/** The CD/DVD-ROM path. */ +static char g_szCdRomPath[RTPATH_MAX]; +/** The default CD/DVD-ROM path. */ +static char g_szDefCdRomPath[RTPATH_MAX]; +/** The operating system short name. */ +static char g_szOsShortName[16]; +/** The CPU architecture short name. */ +static char g_szArchShortName[16]; +/** The combined "OS.arch" name. */ +static char g_szOsDotArchShortName[32]; +/** The combined "OS/arch" name. */ +static char g_szOsSlashArchShortName[32]; +/** The executable suffix. */ +static char g_szExeSuff[8]; +/** The shell script suffix. */ +static char g_szScriptSuff[8]; +/** Whether to display the output of the child process or not. */ +static bool g_fDisplayOutput = true; +/** Whether to terminate or not. + * @todo implement signals and stuff. */ +static bool volatile g_fTerminate = false; +/** Configuration AST. */ +static RTJSONVAL g_hCfgJson = NIL_RTJSONVAL; +/** Pipe for communicating with the serving thread about new clients. - read end */ +static RTPIPE g_hPipeR; +/** Pipe for communicating with the serving thread about new clients. - write end */ +static RTPIPE g_hPipeW; +/** Thread serving connected clients. */ +static RTTHREAD g_hThreadServing; +/** Critical section protecting the list of new clients. */ +static RTCRITSECT g_CritSectClients; +/** List of new clients waiting to be picked up by the client worker thread. */ +static RTLISTANCHOR g_LstClientsNew; +/** First USB/IP port we can use. */ +static uint16_t g_uUsbIpPortFirst = UTS_USBIP_PORT_FIRST; +/** Last USB/IP port we can use. */ +static uint16_t g_uUsbIpPortLast = UTS_USBIP_PORT_LAST; +/** Next free port. */ +static uint16_t g_uUsbIpPortNext = UTS_USBIP_PORT_FIRST; + + + +/** + * Returns the string represenation of the given state. + */ +static const char *utsClientStateStringify(UTSCLIENTSTATE enmState) +{ + switch (enmState) + { + case UTSCLIENTSTATE_INVALID: + return "INVALID"; + case UTSCLIENTSTATE_INITIALISING: + return "INITIALISING"; + case UTSCLIENTSTATE_READY: + return "READY"; + case UTSCLIENTSTATE_DESTROYING: + return "DESTROYING"; + case UTSCLIENTSTATE_32BIT_HACK: + default: + break; + } + + AssertMsgFailed(("Unknown state %#x\n", enmState)); + return "UNKNOWN"; +} + +/** + * Calculates the checksum value, zero any padding space and send the packet. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPkt The packet to send. Must point to a correctly + * aligned buffer. + */ +static int utsSendPkt(PUTSCLIENT pClient, PUTSPKTHDR pPkt) +{ + Assert(pPkt->cb >= sizeof(*pPkt)); + pPkt->uCrc32 = RTCrc32(pPkt->achOpcode, pPkt->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)); + if (pPkt->cb != RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT)) + memset((uint8_t *)pPkt + pPkt->cb, '\0', RT_ALIGN_32(pPkt->cb, UTSPKT_ALIGNMENT) - pPkt->cb); + + Log(("utsSendPkt: cb=%#x opcode=%.8s\n", pPkt->cb, pPkt->achOpcode)); + Log2(("%.*Rhxd\n", RT_MIN(pPkt->cb, 256), pPkt)); + int rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + while (RT_UNLIKELY(rc == VERR_INTERRUPTED) && !g_fTerminate) + rc = g_pTransport->pfnSendPkt(pClient->pTransportClient, pPkt); + if (RT_FAILURE(rc)) + Log(("utsSendPkt: rc=%Rrc\n", rc)); + + return rc; +} + +/** + * Sends a babble reply and disconnects the client (if applicable). + * + * @param pClient The UTS client structure. + * @param pszOpcode The BABBLE opcode. + */ +static void utsReplyBabble(PUTSCLIENT pClient, const char *pszOpcode) +{ + UTSPKTHDR Reply; + Reply.cb = sizeof(Reply); + Reply.uCrc32 = 0; + memcpy(Reply.achOpcode, pszOpcode, sizeof(Reply.achOpcode)); + + g_pTransport->pfnBabble(pClient->pTransportClient, &Reply, 20*1000); +} + +/** + * Receive and validate a packet. + * + * Will send bable responses to malformed packets that results in a error status + * code. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param ppPktHdr Where to return the packet on success. Free + * with RTMemFree. + * @param fAutoRetryOnFailure Whether to retry on error. + */ +static int utsRecvPkt(PUTSCLIENT pClient, PPUTSPKTHDR ppPktHdr, bool fAutoRetryOnFailure) +{ + for (;;) + { + PUTSPKTHDR pPktHdr; + int rc = g_pTransport->pfnRecvPkt(pClient->pTransportClient, &pPktHdr); + if (RT_SUCCESS(rc)) + { + /* validate the packet. */ + if ( pPktHdr->cb >= sizeof(UTSPKTHDR) + && pPktHdr->cb < UTSPKT_MAX_SIZE) + { + Log2(("utsRecvPkt: pPktHdr=%p cb=%#x crc32=%#x opcode=%.8s\n" + "%.*Rhxd\n", + pPktHdr, pPktHdr->cb, pPktHdr->uCrc32, pPktHdr->achOpcode, RT_MIN(pPktHdr->cb, 256), pPktHdr)); + uint32_t uCrc32Calc = pPktHdr->uCrc32 != 0 + ? RTCrc32(&pPktHdr->achOpcode[0], pPktHdr->cb - RT_UOFFSETOF(UTSPKTHDR, achOpcode)) + : 0; + if (pPktHdr->uCrc32 == uCrc32Calc) + { + AssertCompileMemberSize(UTSPKTHDR, achOpcode, 8); + if ( RT_C_IS_UPPER(pPktHdr->achOpcode[0]) + && RT_C_IS_UPPER(pPktHdr->achOpcode[1]) + && (RT_C_IS_UPPER(pPktHdr->achOpcode[2]) || pPktHdr->achOpcode[2] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[3]) || pPktHdr->achOpcode[3] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[4]) || pPktHdr->achOpcode[4] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[5]) || pPktHdr->achOpcode[5] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[6]) || pPktHdr->achOpcode[6] == ' ') + && (RT_C_IS_PRINT(pPktHdr->achOpcode[7]) || pPktHdr->achOpcode[7] == ' ') + ) + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s\n", pPktHdr->cb, pPktHdr->achOpcode)); + *ppPktHdr = pPktHdr; + return rc; + } + + rc = VERR_IO_BAD_COMMAND; + } + else + { + Log(("utsRecvPkt: cb=%#x opcode=%.8s crc32=%#x actual=%#x\n", + pPktHdr->cb, pPktHdr->achOpcode, pPktHdr->uCrc32, uCrc32Calc)); + rc = VERR_IO_CRC; + } + } + else + rc = VERR_IO_BAD_LENGTH; + + /* Send babble reply and disconnect the client if the transport is + connection oriented. */ + if (rc == VERR_IO_BAD_LENGTH) + utsReplyBabble(pClient, "BABBLE L"); + else if (rc == VERR_IO_CRC) + utsReplyBabble(pClient, "BABBLE C"); + else if (rc == VERR_IO_BAD_COMMAND) + utsReplyBabble(pClient, "BABBLE O"); + else + utsReplyBabble(pClient, "BABBLE "); + RTMemFree(pPktHdr); + } + + /* Try again or return failure? */ + if ( g_fTerminate + || rc != VERR_INTERRUPTED + || !fAutoRetryOnFailure + ) + { + Log(("utsRecvPkt: rc=%Rrc\n", rc)); + return rc; + } + } +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pReply The reply packet. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param cbExtra Bytes in addition to the header. + */ +static int utsReplyInternal(PUTSCLIENT pClient, PUTSPKTSTS pReply, const char *pszOpcode, size_t cbExtra) +{ + /* copy the opcode, don't be too strict in case of a padding screw up. */ + size_t cchOpcode = strlen(pszOpcode); + if (RT_LIKELY(cchOpcode == sizeof(pReply->Hdr.achOpcode))) + memcpy(pReply->Hdr.achOpcode, pszOpcode, sizeof(pReply->Hdr.achOpcode)); + else + { + Assert(cchOpcode == sizeof(pReply->Hdr.achOpcode)); + while (cchOpcode > 0 && pszOpcode[cchOpcode - 1] == ' ') + cchOpcode--; + AssertMsgReturn(cchOpcode < sizeof(pReply->Hdr.achOpcode), ("%d/'%.8s'\n", cchOpcode, pszOpcode), VERR_INTERNAL_ERROR_4); + memcpy(pReply->Hdr.achOpcode, pszOpcode, cchOpcode); + memset(&pReply->Hdr.achOpcode[cchOpcode], ' ', sizeof(pReply->Hdr.achOpcode) - cchOpcode); + } + + pReply->Hdr.cb = (uint32_t)sizeof(UTSPKTSTS) + (uint32_t)cbExtra; + pReply->Hdr.uCrc32 = 0; + + return utsSendPkt(pClient, &pReply->Hdr); +} + +/** + * Make a simple reply, only status opcode. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + */ +static int utsReplySimple(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode) +{ + UTSPKTSTS Pkt; + + RT_ZERO(Pkt); + Pkt.rcReq = VINF_SUCCESS; + Pkt.cchStsMsg = 0; + NOREF(pPktHdr); + return utsReplyInternal(pClient, &Pkt, pszOpcode, 0); +} + +/** + * Acknowledges a packet with success. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + */ +static int utsReplyAck(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplySimple(pClient, pPktHdr, "ACK "); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param rcReq Status code. + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq The status code of the request. + * @param pszDetailFmt Longer description of the problem (format string). + * @param va Format arguments. + */ +static int utsReplyFailureV(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, va_list va) +{ + NOREF(pPktHdr); + union + { + UTSPKTSTS Hdr; + char ach[256]; + } uPkt; + + RT_ZERO(uPkt); + size_t cchDetail = RTStrPrintfV(&uPkt.ach[sizeof(UTSPKTSTS)], + sizeof(uPkt) - sizeof(UTSPKTSTS), + pszDetailFmt, va); + uPkt.Hdr.rcReq = rcReq; + uPkt.Hdr.cchStsMsg = cchDetail; + return utsReplyInternal(pClient, &uPkt.Hdr, pszOpcode, cchDetail + 1); +} + +/** + * Replies with a failure. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The original packet (for future use). + * @param pszOpcode The status opcode. Exactly 8 chars long, padd + * with space. + * @param rcReq Status code. + * @param pszDetailFmt Longer description of the problem (format string). + * @param ... Format arguments. + */ +static int utsReplyFailure(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, const char *pszOpcode, int rcReq, const char *pszDetailFmt, ...) +{ + va_list va; + va_start(va, pszDetailFmt); + int rc = utsReplyFailureV(pClient, pPktHdr, pszOpcode, rcReq, pszDetailFmt, va); + va_end(va); + return rc; +} + +/** + * Replies according to the return code. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param rcOperation The status code to report. + * @param pszOperationFmt The operation that failed. Typically giving the + * function call with important arguments. + * @param ... Arguments to the format string. + */ +static int utsReplyRC(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, int rcOperation, const char *pszOperationFmt, ...) +{ + if (RT_SUCCESS(rcOperation)) + return utsReplyAck(pClient, pPktHdr); + + char szOperation[128]; + va_list va; + va_start(va, pszOperationFmt); + RTStrPrintfV(szOperation, sizeof(szOperation), pszOperationFmt, va); + va_end(va); + + return utsReplyFailure(pClient, pPktHdr, "FAILED ", rcOperation, "%s failed with rc=%Rrc (opcode '%.8s')", + szOperation, rcOperation, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Signal a bad packet minum size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cbMin The minimum size. + */ +static int utsReplyBadMinSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cbMin) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at least %zu bytes, got %u (opcode '%.8s')", + cbMin, pPktHdr->cb, pPktHdr->achOpcode); +} +#endif + +/** + * Signal a bad packet exact size. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + * @param cb The wanted size. + */ +static int utsReplyBadSize(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr, size_t cb) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD SIZE", VERR_INVALID_PARAMETER, "Expected at %zu bytes, got %u (opcode '%.8s')", + cb, pPktHdr->cb, pPktHdr->achOpcode); +} + +#if 0 /* unused */ +/** + * Deals with a command that isn't implemented yet. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet which opcode isn't implemented. + */ +static int utsReplyNotImplemented(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "NOT IMPL", VERR_NOT_IMPLEMENTED, "Opcode '%.8s' is not implemented", pPktHdr->achOpcode); +} +#endif + +/** + * Deals with a unknown command. + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet to reply to. + */ +static int utsReplyUnknown(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "UNKNOWN ", VERR_NOT_FOUND, "Opcode '%.8s' is not known", pPktHdr->achOpcode); +} + +/** + * Deals with a command which contains an unterminated string. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyBadStrTermination(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "BAD TERM", VERR_INVALID_PARAMETER, "Opcode '%.8s' contains an unterminated string", pPktHdr->achOpcode); +} + +/** + * Deals with a command sent in an invalid client state. + * + * @returns IPRT status code of the send. + * @param pClient The UTS client structure. + * @param pPktHdr The packet containing the unterminated string. + */ +static int utsReplyInvalidState(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + return utsReplyFailure(pClient, pPktHdr, "INVSTATE", VERR_INVALID_STATE, "Opcode '%.8s' is not supported at client state '%s", + pPktHdr->achOpcode, utsClientStateStringify(pClient->enmState)); +} + +/** + * Parses an unsigned integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given maximum. + * @param pszVal The value string. + * @param uMax The maximum value. + * @param pu64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseUInt(const char *pszVal, uint64_t uMax, uint64_t *pu64) +{ + int rc = RTStrToUInt64Ex(pszVal, NULL, 0, pu64); + if (RT_SUCCESS(rc)) + { + if (*pu64 > uMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses a signed integer from the given value string. + * + * @returns IPRT status code. + * @retval VERR_OUT_OF_RANGE if the parsed value exceeds the given range. + * @param pszVal The value string. + * @param iMin The minimum value. + * @param iMax The maximum value. + * @param pi64 Where to store the parsed number on success. + */ +static int utsDoGadgetCreateCfgParseInt(const char *pszVal, int64_t iMin, int64_t iMax, int64_t *pi64) +{ + int rc = RTStrToInt64Ex(pszVal, NULL, 0, pi64); + if (RT_SUCCESS(rc)) + { + if ( *pi64 < iMin + || *pi64 > iMax) + rc = VERR_OUT_OF_RANGE; + } + + return rc; +} + +/** + * Parses the given config item and fills in the value according to the given type. + * + * @returns IPRT status code. + * @param pCfgItem The config item to parse. + * @param u32Type The config type. + * @param pszVal The value encoded as a string. + */ +static int utsDoGadgetCreateCfgParseItem(PUTSGADGETCFGITEM pCfgItem, uint32_t u32Type, + const char *pszVal) +{ + int rc = VINF_SUCCESS; + + switch (u32Type) + { + case UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_BOOLEAN; + if ( RTStrICmp(pszVal, "enabled") + || RTStrICmp(pszVal, "1") + || RTStrICmp(pszVal, "true")) + pCfgItem->Val.u.f = true; + else if ( RTStrICmp(pszVal, "disabled") + || RTStrICmp(pszVal, "0") + || RTStrICmp(pszVal, "false")) + pCfgItem->Val.u.f = false; + else + rc = VERR_INVALID_PARAMETER; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_STRING: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_STRING; + pCfgItem->Val.u.psz = RTStrDup(pszVal); + if (!pCfgItem->Val.u.psz) + rc = VERR_NO_STR_MEMORY; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT8; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT8_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u8 = (uint8_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT16; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT16_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u16 = (uint16_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT32; + + uint64_t u64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT32_MAX, &u64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.u32 = (uint32_t)u64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_UINT64; + rc = utsDoGadgetCreateCfgParseUInt(pszVal, UINT64_MAX, &pCfgItem->Val.u.u64); + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT8: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT8; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT8_MIN, INT8_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i8 = (int8_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT16: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT16; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT16_MIN, INT16_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i16 = (int16_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT32: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT32; + + int64_t i64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT32_MIN, INT32_MAX, &i64); + if (RT_SUCCESS(rc)) + pCfgItem->Val.u.i32 = (int32_t)i64; + break; + } + case UTSPKT_GDGT_CFG_ITEM_TYPE_INT64: + { + pCfgItem->Val.enmType = UTSGADGETCFGTYPE_INT64; + rc = utsDoGadgetCreateCfgParseInt(pszVal, INT64_MIN, INT64_MAX, &pCfgItem->Val.u.i64); + break; + } + default: + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Creates the configuration from the given GADGET CREATE packet. + * + * @returns IPRT status code. + * @param pCfgItem The first config item header in the request packet. + * @param cCfgItems Number of config items in the packet to parse. + * @param cbPkt Number of bytes left in the packet for the config data. + * @param paCfg The array of configuration items to fill. + */ +static int utsDoGadgetCreateFillCfg(PUTSPKTREQGDGTCTORCFGITEM pCfgItem, unsigned cCfgItems, + size_t cbPkt, PUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + unsigned idxCfg = 0; + + while ( RT_SUCCESS(rc) + && cCfgItems + && cbPkt) + { + if (cbPkt >= sizeof(UTSPKTREQGDGTCTORCFGITEM)) + { + cbPkt -= sizeof(UTSPKTREQGDGTCTORCFGITEM); + if (pCfgItem->u32KeySize + pCfgItem->u32ValSize >= cbPkt) + { + const char *pszKey = (const char *)(pCfgItem + 1); + const char *pszVal = pszKey + pCfgItem->u32KeySize; + + /* Validate termination. */ + if ( *(pszKey + pCfgItem->u32KeySize - 1) != '\0' + || *(pszVal + pCfgItem->u32ValSize - 1) != '\0') + rc = VERR_INVALID_PARAMETER; + else + { + paCfg[idxCfg].pszKey = RTStrDup(pszKey); + + rc = utsDoGadgetCreateCfgParseItem(&paCfg[idxCfg], pCfgItem->u32Type, pszVal); + if (RT_SUCCESS(rc)) + { + cbPkt -= pCfgItem->u32KeySize + pCfgItem->u32ValSize; + cCfgItems--; + idxCfg++; + pCfgItem = (PUTSPKTREQGDGTCTORCFGITEM)(pszVal + pCfgItem->u32ValSize); + } + } + } + else + rc = VERR_INVALID_PARAMETER; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + +/** + * Verifies and acknowledges a "BYE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoBye(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc; + if (pPktHdr->cb == sizeof(UTSPKTHDR)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTHDR)); + return rc; +} + +/** + * Verifies and acknowledges a "HOWDY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The howdy packet. + */ +static int utsDoHowdy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb != sizeof(UTSPKTREQHOWDY)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQHOWDY)); + + if (pClient->enmState != UTSCLIENTSTATE_INITIALISING) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQHOWDY pReq = (PUTSPKTREQHOWDY)pPktHdr; + + if (pReq->uVersion != UTS_PROTOCOL_VS) + return utsReplyRC(pClient, pPktHdr, VERR_VERSION_MISMATCH, "The given version %#x is not supported", pReq->uVersion); + + /* Verify hostname string. */ + if (pReq->cchHostname >= sizeof(pReq->achHostname)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(pReq->achHostname) - 1); + + if (pReq->achHostname[pReq->cchHostname] != '\0') + return utsReplyBadStrTermination(pClient, pPktHdr); + + /* Extract string. */ + pClient->pszHostname = RTStrDup(&pReq->achHostname[0]); + if (!pClient->pszHostname) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for the hostname string"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_PHYSICAL) + return utsReplyRC(pClient, pPktHdr, VERR_NOT_SUPPORTED, "Physical connections are not yet supported"); + + if (pReq->fUsbConn & UTSPKT_HOWDY_CONN_F_USBIP) + { + /* Set up the USB/IP server, find an unused port we can start the server on. */ + UTSGADGETCFGITEM aCfg[2]; + + uint16_t uPort = g_uUsbIpPortNext; + + if (g_uUsbIpPortNext == g_uUsbIpPortLast) + g_uUsbIpPortNext = g_uUsbIpPortFirst; + else + g_uUsbIpPortNext++; + + aCfg[0].pszKey = "UsbIp/Port"; + aCfg[0].Val.enmType = UTSGADGETCFGTYPE_UINT16; + aCfg[0].Val.u.u16 = uPort; + aCfg[1].pszKey = NULL; + + rc = utsGadgetHostCreate(UTSGADGETHOSTTYPE_USBIP, &aCfg[0], &pClient->hGadgetHost); + if (RT_SUCCESS(rc)) + { + /* Send the reply with the configured USB/IP port. */ + UTSPKTREPHOWDY Rep; + + RT_ZERO(Rep); + + Rep.uVersion = UTS_PROTOCOL_VS; + Rep.fUsbConn = UTSPKT_HOWDY_CONN_F_USBIP; + Rep.uUsbIpPort = uPort; + Rep.cUsbIpDevices = 1; + Rep.cPhysicalDevices = 0; + + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + if (RT_SUCCESS(rc)) + { + g_pTransport->pfnNotifyHowdy(pClient->pTransportClient); + pClient->enmState = UTSCLIENTSTATE_READY; + RTDirRemoveRecursive(g_szScratchPath, RTDIRRMREC_F_CONTENT_ONLY); + } + } + else + return utsReplyRC(pClient, pPktHdr, rc, "Creating the USB/IP gadget host failed"); + } + else + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "No access method requested"); + + return rc; +} + +/** + * Verifies and processes a "GADGET CREATE" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget create packet. + */ +static int utsDoGadgetCreate(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + int rc = VINF_SUCCESS; + + if (pPktHdr->cb < sizeof(UTSPKTREQGDGTCTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCTOR pReq = (PUTSPKTREQGDGTCTOR)pPktHdr; + + if (pReq->u32GdgtType != UTSPKT_GDGT_CREATE_TYPE_TEST) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget type is not supported"); + + if (pReq->u32GdgtAccess != UTSPKT_GDGT_CREATE_ACCESS_USBIP) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_PARAMETER, "The given gadget access method is not supported"); + + PUTSGADGETCFGITEM paCfg = NULL; + if (pReq->u32CfgItems > 0) + { + paCfg = (PUTSGADGETCFGITEM)RTMemAllocZ((pReq->u32CfgItems + 1) * sizeof(UTSGADGETCFGITEM)); + if (RT_UNLIKELY(!paCfg)) + return utsReplyRC(pClient, pPktHdr, VERR_NO_MEMORY, "Failed to allocate memory for configration items"); + + rc = utsDoGadgetCreateFillCfg((PUTSPKTREQGDGTCTORCFGITEM)(pReq + 1), pReq->u32CfgItems, + pPktHdr->cb - sizeof(UTSPKTREQGDGTCTOR), paCfg); + if (RT_FAILURE(rc)) + { + RTMemFree(paCfg); + return utsReplyRC(pClient, pPktHdr, rc, "Failed to parse configuration"); + } + } + + rc = utsGadgetCreate(pClient->hGadgetHost, UTSGADGETCLASS_TEST, paCfg, &pClient->hGadget); + if (RT_SUCCESS(rc)) + { + UTSPKTREPGDGTCTOR Rep; + RT_ZERO(Rep); + + Rep.idGadget = 0; + Rep.u32BusId = utsGadgetGetBusId(pClient->hGadget); + Rep.u32DevId = utsGadgetGetDevId(pClient->hGadget); + rc = utsReplyInternal(pClient, &Rep.Sts, "ACK ", sizeof(Rep) - sizeof(UTSPKTSTS)); + } + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to create gadget with %Rrc\n", rc); + + return rc; +} + +/** + * Verifies and processes a "GADGET DESTROY" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget destroy packet. + */ +static int utsDoGadgetDestroy(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDTOR)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDTOR)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDTOR pReq = (PUTSPKTREQGDGTDTOR)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + utsGadgetRelease(pClient->hGadget); + pClient->hGadget = NIL_UTSGADGET; + + return utsReplyAck(pClient, pPktHdr); +} + +/** + * Verifies and processes a "GADGET CONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget connect packet. + */ +static int utsDoGadgetConnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTCNCT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTCNCT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTCNCT pReq = (PUTSPKTREQGDGTCNCT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetConnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to connect the gadget"); + + return rc; +} + +/** + * Verifies and processes a "GADGET DISCONNECT" request. + * + * @returns IPRT status code. + * @param pClient The UTS client structure. + * @param pPktHdr The gadget disconnect packet. + */ +static int utsDoGadgetDisconnect(PUTSCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + if (pPktHdr->cb != sizeof(UTSPKTREQGDGTDCNT)) + return utsReplyBadSize(pClient, pPktHdr, sizeof(UTSPKTREQGDGTDCNT)); + + if ( pClient->enmState != UTSCLIENTSTATE_READY + || pClient->hGadgetHost == NIL_UTSGADGETHOST) + return utsReplyInvalidState(pClient, pPktHdr); + + PUTSPKTREQGDGTDCNT pReq = (PUTSPKTREQGDGTDCNT)pPktHdr; + + if (pReq->idGadget != 0) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_HANDLE, "The given gadget handle is invalid"); + if (pClient->hGadget == NIL_UTSGADGET) + return utsReplyRC(pClient, pPktHdr, VERR_INVALID_STATE, "The gadget is not set up"); + + int rc = utsGadgetDisconnect(pClient->hGadget); + if (RT_SUCCESS(rc)) + rc = utsReplyAck(pClient, pPktHdr); + else + rc = utsReplyRC(pClient, pPktHdr, rc, "Failed to disconnect the gadget"); + + return rc; +} + +/** + * Main request processing routine for each client. + * + * @returns IPRT status code. + * @param pClient The UTS client structure sending the request. + */ +static int utsClientReqProcess(PUTSCLIENT pClient) +{ + /* + * Read client command packet and process it. + */ + PUTSPKTHDR pPktHdr = NULL; + int rc = utsRecvPkt(pClient, &pPktHdr, true /*fAutoRetryOnFailure*/); + if (RT_FAILURE(rc)) + return rc; + + /* + * Do a string switch on the opcode bit. + */ + /* Connection: */ + if ( utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_HOWDY)) + rc = utsDoHowdy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_BYE)) + rc = utsDoBye(pClient, pPktHdr); + /* Gadget API. */ + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CREATE)) + rc = utsDoGadgetCreate(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DESTROY)) + rc = utsDoGadgetDestroy(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_CONNECT)) + rc = utsDoGadgetConnect(pClient, pPktHdr); + else if (utsIsSameOpcode(pPktHdr, UTSPKT_OPCODE_GADGET_DISCONNECT)) + rc = utsDoGadgetDisconnect(pClient, pPktHdr); + /* Misc: */ + else + rc = utsReplyUnknown(pClient, pPktHdr); + + RTMemFree(pPktHdr); + + return rc; +} + +/** + * Destroys a client instance. + * + * @param pClient The UTS client structure. + */ +static void utsClientDestroy(PUTSCLIENT pClient) +{ + if (pClient->pszHostname) + RTStrFree(pClient->pszHostname); + if (pClient->hGadget != NIL_UTSGADGET) + utsGadgetRelease(pClient->hGadget); + if (pClient->hGadgetHost != NIL_UTSGADGETHOST) + utsGadgetHostRelease(pClient->hGadgetHost); + RTMemFree(pClient); +} + +/** + * The main thread worker serving the clients. + */ +static DECLCALLBACK(int) utsClientWorker(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF2(hThread, pvUser); + unsigned cClientsMax = 0; + unsigned cClientsCur = 0; + PUTSCLIENT *papClients = NULL; + RTPOLLSET hPollSet; + + int rc = RTPollSetCreate(&hPollSet); + if (RT_FAILURE(rc)) + return rc; + + /* Add the pipe to the poll set. */ + rc = RTPollSetAddPipe(hPollSet, g_hPipeR, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, 0); + if (RT_SUCCESS(rc)) + { + while (!g_fTerminate) + { + uint32_t fEvts; + uint32_t uId; + rc = RTPoll(hPollSet, RT_INDEFINITE_WAIT, &fEvts, &uId); + if (RT_SUCCESS(rc)) + { + if (uId == 0) + { + if (fEvts & RTPOLL_EVT_ERROR) + break; + + /* We got woken up because of a new client. */ + Assert(fEvts & RTPOLL_EVT_READ); + + uint8_t bRead; + size_t cbRead = 0; + rc = RTPipeRead(g_hPipeR, &bRead, 1, &cbRead); + AssertRC(rc); + + RTCritSectEnter(&g_CritSectClients); + /* Walk the list and add all new clients. */ + PUTSCLIENT pIt, pItNext; + RTListForEachSafe(&g_LstClientsNew, pIt, pItNext, UTSCLIENT, NdLst) + { + RTListNodeRemove(&pIt->NdLst); + Assert(cClientsCur <= cClientsMax); + if (cClientsCur == cClientsMax) + { + /* Realloc to accommodate for the new clients. */ + PUTSCLIENT *papClientsNew = (PUTSCLIENT *)RTMemReallocZ(papClients, cClientsMax * sizeof(PUTSCLIENT), (cClientsMax + 10) * sizeof(PUTSCLIENT)); + if (RT_LIKELY(papClientsNew)) + { + cClientsMax += 10; + papClients = papClientsNew; + } + } + + if (cClientsCur < cClientsMax) + { + /* Find a free slot in the client array. */ + unsigned idxSlt = 0; + while ( idxSlt < cClientsMax + && papClients[idxSlt] != NULL) + idxSlt++; + + rc = g_pTransport->pfnPollSetAdd(hPollSet, pIt->pTransportClient, idxSlt + 1); + if (RT_SUCCESS(rc)) + { + cClientsCur++; + papClients[idxSlt] = pIt; + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + else + { + g_pTransport->pfnNotifyBye(pIt->pTransportClient); + utsClientDestroy(pIt); + } + } + RTCritSectLeave(&g_CritSectClients); + } + else + { + /* Client sends a request, pick the right client and process it. */ + PUTSCLIENT pClient = papClients[uId - 1]; + AssertPtr(pClient); + if (fEvts & RTPOLL_EVT_READ) + rc = utsClientReqProcess(pClient); + + if ( (fEvts & RTPOLL_EVT_ERROR) + || RT_FAILURE(rc)) + { + /* Close connection and remove client from array. */ + rc = g_pTransport->pfnPollSetRemove(hPollSet, pClient->pTransportClient, uId); + AssertRC(rc); + + g_pTransport->pfnNotifyBye(pClient->pTransportClient); + papClients[uId - 1] = NULL; + cClientsCur--; + utsClientDestroy(pClient); + } + } + } + } + } + + RTPollSetDestroy(hPollSet); + + return rc; +} + +/** + * The main loop. + * + * @returns exit code. + */ +static RTEXITCODE utsMainLoop(void) +{ + RTEXITCODE enmExitCode = RTEXITCODE_SUCCESS; + while (!g_fTerminate) + { + /* + * Wait for new connection and spin off a new thread + * for every new client. + */ + PUTSTRANSPORTCLIENT pTransportClient; + int rc = g_pTransport->pfnWaitForConnect(&pTransportClient); + if (RT_FAILURE(rc)) + continue; + + /* + * New connection, create new client structure and spin of + * the request handling thread. + */ + PUTSCLIENT pClient = (PUTSCLIENT)RTMemAllocZ(sizeof(UTSCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->enmState = UTSCLIENTSTATE_INITIALISING; + pClient->pTransportClient = pTransportClient; + pClient->pszHostname = NULL; + pClient->hGadgetHost = NIL_UTSGADGETHOST; + pClient->hGadget = NIL_UTSGADGET; + + /* Add client to the new list and inform the worker thread. */ + RTCritSectEnter(&g_CritSectClients); + RTListAppend(&g_LstClientsNew, &pClient->NdLst); + RTCritSectLeave(&g_CritSectClients); + + size_t cbWritten = 0; + rc = RTPipeWrite(g_hPipeW, "", 1, &cbWritten); + if (RT_FAILURE(rc)) + RTMsgError("Failed to inform worker thread of a new client"); + } + else + { + RTMsgError("Creating new client structure failed with out of memory error\n"); + g_pTransport->pfnNotifyBye(pTransportClient); + } + + + } + + return enmExitCode; +} + +/** + * Initializes the global UTS state. + * + * @returns IPRT status code. + */ +static int utsInit(void) +{ + int rc = VINF_SUCCESS; + PRTERRINFO pErrInfo = NULL; + + RTListInit(&g_LstClientsNew); + + rc = RTJsonParseFromFile(&g_hCfgJson, g_szCfgPath, pErrInfo); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformInit(); + if (RT_SUCCESS(rc)) + { + rc = RTCritSectInit(&g_CritSectClients); + if (RT_SUCCESS(rc)) + { + rc = RTPipeCreate(&g_hPipeR, &g_hPipeW, 0); + if (RT_SUCCESS(rc)) + { + /* Spin off the thread serving connections. */ + rc = RTThreadCreate(&g_hThreadServing, utsClientWorker, NULL, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, + "USBTSTSRV"); + if (RT_SUCCESS(rc)) + return VINF_SUCCESS; + else + RTMsgError("Creating the client worker thread failed with %Rrc\n", rc); + + RTPipeClose(g_hPipeR); + RTPipeClose(g_hPipeW); + } + else + RTMsgError("Creating communications pipe failed with %Rrc\n", rc); + + RTCritSectDelete(&g_CritSectClients); + } + else + RTMsgError("Creating global critical section failed with %Rrc\n", rc); + + RTJsonValueRelease(g_hCfgJson); + } + else + RTMsgError("Initializing the platform failed with %Rrc\n", rc); + } + else + { + if (RTErrInfoIsSet(pErrInfo)) + { + RTMsgError("Failed to parse config with detailed error: %s (%Rrc)\n", + pErrInfo->pszMsg, pErrInfo->rc); + RTErrInfoFree(pErrInfo); + } + else + RTMsgError("Failed to parse config with unknown error (%Rrc)\n", rc); + } + + return rc; +} + +/** + * Determines the default configuration. + */ +static void utsSetDefaults(void) +{ + /* + * OS and ARCH. + */ + AssertCompile(sizeof(KBUILD_TARGET) <= sizeof(g_szOsShortName)); + strcpy(g_szOsShortName, KBUILD_TARGET); + + AssertCompile(sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szArchShortName)); + strcpy(g_szArchShortName, KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsDotArchShortName)); + strcpy(g_szOsDotArchShortName, KBUILD_TARGET); + g_szOsDotArchShortName[sizeof(KBUILD_TARGET) - 1] = '.'; + strcpy(&g_szOsDotArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + + AssertCompile(sizeof(KBUILD_TARGET) + sizeof(KBUILD_TARGET_ARCH) <= sizeof(g_szOsSlashArchShortName)); + strcpy(g_szOsSlashArchShortName, KBUILD_TARGET); + g_szOsSlashArchShortName[sizeof(KBUILD_TARGET) - 1] = '/'; + strcpy(&g_szOsSlashArchShortName[sizeof(KBUILD_TARGET)], KBUILD_TARGET_ARCH); + +#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) + strcpy(g_szExeSuff, ".exe"); + strcpy(g_szScriptSuff, ".cmd"); +#else + strcpy(g_szExeSuff, ""); + strcpy(g_szScriptSuff, ".sh"); +#endif + + /* + * The CD/DVD-ROM location. + */ + /** @todo do a better job here :-) */ +#ifdef RT_OS_WINDOWS + strcpy(g_szDefCdRomPath, "D:/"); +#elif defined(RT_OS_OS2) + strcpy(g_szDefCdRomPath, "D:/"); +#else + if (RTDirExists("/media")) + strcpy(g_szDefCdRomPath, "/media/cdrom"); + else + strcpy(g_szDefCdRomPath, "/mnt/cdrom"); +#endif + strcpy(g_szCdRomPath, g_szDefCdRomPath); + + /* + * Temporary directory. + */ + int rc = RTPathTemp(g_szDefScratchPath, sizeof(g_szDefScratchPath)); + if (RT_SUCCESS(rc)) +#if defined(RT_OS_OS2) || defined(RT_OS_WINDOWS) || defined(RT_OS_DOS) + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXX.tmp"); +#else + rc = RTPathAppend(g_szDefScratchPath, sizeof(g_szDefScratchPath), "uts-XXXXXXXXX.tmp"); +#endif + if (RT_FAILURE(rc)) + { + RTMsgError("RTPathTemp/Append failed when constructing scratch path: %Rrc\n", rc); + strcpy(g_szDefScratchPath, "/tmp/uts-XXXX.tmp"); + } + strcpy(g_szScratchPath, g_szDefScratchPath); + + /* + * Config file location. + */ + /** @todo Improve */ +#if !defined(RT_OS_WINDOWS) + strcpy(g_szCfgPath, "/etc/uts.conf"); +#else + strcpy(g_szCfgPath, ""); +#endif + + /* + * The default transporter is the first one. + */ + g_pTransport = g_apTransports[0]; +} + +/** + * Prints the usage. + * + * @param pStrm Where to print it. + * @param pszArgv0 The program name (argv[0]). + */ +static void utsUsage(PRTSTREAM pStrm, const char *pszArgv0) +{ + RTStrmPrintf(pStrm, + "Usage: %Rbn [options]\n" + "\n" + "Options:\n" + " --config <path>\n" + " Where to load the config from\n" + " --cdrom <path>\n" + " Where the CD/DVD-ROM will be mounted.\n" + " Default: %s\n" + " --scratch <path>\n" + " Where to put scratch files.\n" + " Default: %s \n" + , + pszArgv0, + g_szDefCdRomPath, + g_szDefScratchPath); + RTStrmPrintf(pStrm, + " --transport <name>\n" + " Use the specified transport layer, one of the following:\n"); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + RTStrmPrintf(pStrm, " %s - %s\n", g_apTransports[i]->szName, g_apTransports[i]->pszDesc); + RTStrmPrintf(pStrm, " Default: %s\n", g_pTransport->szName); + RTStrmPrintf(pStrm, + " --display-output, --no-display-output\n" + " Display the output and the result of all child processes.\n"); + RTStrmPrintf(pStrm, + " --foreground\n" + " Don't daemonize, run in the foreground.\n"); + RTStrmPrintf(pStrm, + " --help, -h, -?\n" + " Display this message and exit.\n" + " --version, -V\n" + " Display the version and exit.\n"); + + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + RTStrmPrintf(pStrm, + "\n" + "Options for %s:\n", g_apTransports[i]->szName); + g_apTransports[i]->pfnUsage(g_pStdOut); + } +} + +/** + * Parses the arguments. + * + * @returns Exit code. Exit if this is non-zero or @a *pfExit is set. + * @param argc The number of arguments. + * @param argv The argument vector. + * @param pfExit For indicating exit when the exit code is zero. + */ +static RTEXITCODE utsParseArgv(int argc, char **argv, bool *pfExit) +{ + *pfExit = false; + + /* + * Storage for locally handled options. + */ + bool fDaemonize = true; + bool fDaemonized = false; + + /* + * Combine the base and transport layer option arrays. + */ + static const RTGETOPTDEF s_aBaseOptions[] = + { + { "--config", 'C', RTGETOPT_REQ_STRING }, + { "--transport", 't', RTGETOPT_REQ_STRING }, + { "--cdrom", 'c', RTGETOPT_REQ_STRING }, + { "--scratch", 's', RTGETOPT_REQ_STRING }, + { "--display-output", 'd', RTGETOPT_REQ_NOTHING }, + { "--no-display-output",'D', RTGETOPT_REQ_NOTHING }, + { "--foreground", 'f', RTGETOPT_REQ_NOTHING }, + { "--daemonized", 'Z', RTGETOPT_REQ_NOTHING }, + }; + + size_t cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + cOptions += g_apTransports[i]->cOpts; + + PRTGETOPTDEF paOptions = (PRTGETOPTDEF)alloca(cOptions * sizeof(RTGETOPTDEF)); + if (!paOptions) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "alloca failed\n"); + + memcpy(paOptions, s_aBaseOptions, sizeof(s_aBaseOptions)); + cOptions = RT_ELEMENTS(s_aBaseOptions); + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + { + memcpy(&paOptions[cOptions], g_apTransports[i]->paOpts, g_apTransports[i]->cOpts * sizeof(RTGETOPTDEF)); + cOptions += g_apTransports[i]->cOpts; + } + + /* + * Parse the arguments. + */ + RTGETOPTSTATE GetState; + int rc = RTGetOptInit(&GetState, argc, argv, paOptions, cOptions, 1, 0 /* fFlags */); + AssertRC(rc); + + int ch; + RTGETOPTUNION Val; + while ((ch = RTGetOpt(&GetState, &Val))) + { + switch (ch) + { + case 'C': + rc = RTStrCopy(g_szCfgPath, sizeof(g_szCfgPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "Config file path is path too long (%Rrc)\n", rc); + break; + + case 'c': + rc = RTStrCopy(g_szCdRomPath, sizeof(g_szCdRomPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "CD/DVD-ROM is path too long (%Rrc)\n", rc); + break; + + case 'd': + g_fDisplayOutput = true; + break; + + case 'D': + g_fDisplayOutput = false; + break; + + case 'f': + fDaemonize = false; + break; + + case 'h': + utsUsage(g_pStdOut, argv[0]); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 's': + rc = RTStrCopy(g_szScratchPath, sizeof(g_szScratchPath), Val.psz); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "scratch path is too long (%Rrc)\n", rc); + break; + + case 't': + { + PCUTSTRANSPORT pTransport = NULL; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (!strcmp(g_apTransports[i]->szName, Val.psz)) + { + pTransport = g_apTransports[i]; + break; + } + if (!pTransport) + return RTMsgErrorExit(RTEXITCODE_SYNTAX, "Unknown transport layer name '%s'\n", Val.psz); + g_pTransport = pTransport; + break; + } + + case 'V': + RTPrintf("$Revision: 157380 $\n"); + *pfExit = true; + return RTEXITCODE_SUCCESS; + + case 'Z': + fDaemonized = true; + fDaemonize = false; + break; + + default: + { + rc = VERR_TRY_AGAIN; + for (size_t i = 0; i < RT_ELEMENTS(g_apTransports); i++) + if (g_apTransports[i]->cOpts) + { + rc = g_apTransports[i]->pfnOption(ch, &Val); + if (RT_SUCCESS(rc)) + break; + if (rc != VERR_TRY_AGAIN) + { + *pfExit = true; + return RTEXITCODE_SYNTAX; + } + } + if (rc == VERR_TRY_AGAIN) + { + *pfExit = true; + return RTGetOptPrintError(ch, &Val); + } + break; + } + } + } + + /* + * Daemonize ourselves if asked to. + */ + if (fDaemonize && !*pfExit) + { + rc = RTProcDaemonize(argv, "--daemonized"); + if (RT_FAILURE(rc)) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "RTProcDaemonize: %Rrc\n", rc); + *pfExit = true; + } + + return RTEXITCODE_SUCCESS; +} + + +int main(int argc, char **argv) +{ + /* + * Initialize the runtime. + */ + int rc = RTR3InitExe(argc, &argv, 0); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + /* + * Determine defaults and parse the arguments. + */ + utsSetDefaults(); + bool fExit; + RTEXITCODE rcExit = utsParseArgv(argc, argv, &fExit); + if (rcExit != RTEXITCODE_SUCCESS || fExit) + return rcExit; + + /* + * Initialize global state. + */ + rc = utsInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Initialize the transport layer. + */ + rc = g_pTransport->pfnInit(); + if (RT_FAILURE(rc)) + return RTEXITCODE_FAILURE; + + /* + * Ok, start working + */ + rcExit = utsMainLoop(); + + /* + * Cleanup. + */ + g_pTransport->pfnTerm(); + + utsPlatformTerm(); + + return rcExit; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp new file mode 100644 index 00000000..05b4c569 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.cpp @@ -0,0 +1,211 @@ +/* $Id: UsbTestServiceGadget.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadgetInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget class callback table. */ + PCUTSGADGETCLASSIF pClassIf; + /** The gadget host handle. */ + UTSGADGETHOST hGadgetHost; + /** Class specific instance data - variable in size. */ + uint8_t abClassInst[1]; +} UTSGADGETINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETINT *PUTSGADGETINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETCLASSIF g_apUtsGadgetClass[] = +{ + &g_UtsGadgetClassTest +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget instance. + * + * @param pThis The gadget instance. + */ +static void utsGadgetDestroy(PUTSGADGETINT pThis) +{ + pThis->pClassIf->pfnTerm((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETCLASSIF pClassIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetClass); i++) + { + if (g_apUtsGadgetClass[i]->enmClass == enmClass) + { + pClassIf = g_apUtsGadgetClass[i]; + break; + } + } + + if (RT_LIKELY(pClassIf)) + { + PUTSGADGETINT pThis = (PUTSGADGETINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETINT, abClassInst[pClassIf->cbClass])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->hGadgetHost = hGadgetHost; + pThis->pClassIf = pClassIf; + rc = pClassIf->pfnInit((PUTSGADGETCLASSINT)&pThis->abClassInst[0], paCfg); + if (RT_SUCCESS(rc)) + { + /* Connect the gadget to the host. */ + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, pThis); + if (RT_SUCCESS(rc)) + *phGadget = pThis; + } + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pClassIf->pfnGetBusId((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); +} + + +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return 1; /** @todo Current assumption which is true on Linux with dummy_hcd. */ +} + + +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = pThis->pClassIf->pfnConnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + if (RT_SUCCESS(rc)) + rc = utsGadgetHostGadgetConnect(pThis->hGadgetHost, hGadget); + + return rc; +} + + +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget) +{ + PUTSGADGETINT pThis = hGadget; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + int rc = utsGadgetHostGadgetDisconnect(pThis->hGadgetHost, hGadget); + if (RT_SUCCESS(rc)) + rc = pThis->pClassIf->pfnDisconnect((PUTSGADGETCLASSINT)&pThis->abClassInst[0]); + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h new file mode 100644 index 00000000..19d2603a --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadget.h @@ -0,0 +1,546 @@ +/* $Id: UsbTestServiceGadget.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** Opaque gadget host handle. */ +typedef struct UTSGADGETHOSTINT *UTSGADGETHOST; +/** Pointer to a gadget host handle. */ +typedef UTSGADGETHOST *PUTSGADGETHOST; + +/** NIL gadget host handle. */ +#define NIL_UTSGADGETHOST ((UTSGADGETHOST)0) + +/** Opaque USB gadget handle. */ +typedef struct UTSGADGETINT *UTSGADGET; +/** Pointer to a USB gadget handle. */ +typedef UTSGADGET *PUTSGADET; + +/** NIL gadget handle. */ +#define NIL_UTSGADGET ((UTSGADGET)0) + +/** + * Gadget/Gadget host configuration item type. + */ +typedef enum UTSGADGETCFGTYPE +{ + /** Don't use! */ + UTSGADGETCFGTYPE_INVALID = 0, + /** Boolean type. */ + UTSGADGETCFGTYPE_BOOLEAN, + /** UTF-8 string. */ + UTSGADGETCFGTYPE_STRING, + /** Unsigned 8bit integer. */ + UTSGADGETCFGTYPE_UINT8, + /** Unsigned 16bit integer. */ + UTSGADGETCFGTYPE_UINT16, + /** Unsigned 32bit integer. */ + UTSGADGETCFGTYPE_UINT32, + /** Unsigned 64bit integer. */ + UTSGADGETCFGTYPE_UINT64, + /** Signed 8bit integer. */ + UTSGADGETCFGTYPE_INT8, + /** Signed 16bit integer. */ + UTSGADGETCFGTYPE_INT16, + /** Signed 32bit integer. */ + UTSGADGETCFGTYPE_INT32, + /** Signed 64bit integer. */ + UTSGADGETCFGTYPE_INT64, + /** 32bit hack. */ + UTSGADGETCFGTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETCFGTYPE; + +/** + * Gadget configuration value. + */ +typedef struct UTSGADGETCFGVAL +{ + /** Value type */ + UTSGADGETCFGTYPE enmType; + /** Value based on the type. */ + union + { + bool f; + const char *psz; + uint8_t u8; + uint16_t u16; + uint32_t u32; + uint64_t u64; + int8_t i8; + int16_t i16; + int32_t i32; + int64_t i64; + } u; +} UTSGADGETCFGVAL; +/** Pointer to a gadget configuration value. */ +typedef UTSGADGETCFGVAL *PUTSGADGETCFGVAL; +/** Pointer to a const gadget configuration value. */ +typedef const UTSGADGETCFGVAL *PCUTSGADGETCFGVAL; + +/** + * Gadget configuration item. + */ +typedef struct UTSGADGETCFGITEM +{ + /** Item key. */ + const char *pszKey; + /** Item value. */ + UTSGADGETCFGVAL Val; +} UTSGADGETCFGITEM; +/** Pointer to a gadget configuration item. */ +typedef UTSGADGETCFGITEM *PUTSGADGETCFGITEM; +/** Pointer to a const gadget configuration item. */ +typedef const UTSGADGETCFGITEM *PCUTSGADGETCFGITEM; + +/** + * Type for the gadget host. + */ +typedef enum UTSGADGETHOSTTYPE +{ + /** Invalid type, don't use. */ + UTSGADGETHOSTTYPE_INVALID = 0, + /** USB/IP host, gadgets are exported using a USB/IP server. */ + UTSGADGETHOSTTYPE_USBIP, + /** Physical connection using a device or OTG port. */ + UTSGADGETHOSTTYPE_PHYSICAL, + /** 32bit hack. */ + UTSGADGETHOSTTYPE_32BIT_HACK = 0x7fffffff +} UTSGADGETHOSTTYPE; + +/** + * USB gadget class. + */ +typedef enum UTSGADGETCLASS +{ + /** Invalid class, don't use. */ + UTSGADGETCLASS_INVALID = 0, + /** Special test device class. */ + UTSGADGETCLASS_TEST, + /** MSD device. */ + UTSGADGETCLASS_MSD, + /** 32bit hack. */ + UTSGADGETCLASS_32BIT_HACK = 0x7fffffff +} UTSGADGETCLASS; + +/** + * Queries the value of a given boolean key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf); + +/** + * Queries the value of a given boolean key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pf Where to store the value on success. + * @param fDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef); + +/** + * Queries the string value of a given key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + */ +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal); + +/** + * Queries the string value of a given key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param ppszVal Where to store the pointer to the string on success, + * must be freed with RTStrFree(). + * @param pszDef The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef); + +/** + * Queries the value of a given uint8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8); + +/** + * Queries the value of a given uint8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu8 Where to store the value on success. + * @param u8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def); + +/** + * Queries the value of a given uint16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16); + +/** + * Queries the value of a given uint16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu16 Where to store the value on success. + * @param u16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def); + +/** + * Queries the value of a given uint32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32); + +/** + * Queries the value of a given uint32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu32 Where to store the value on success. + * @param u32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def); + +/** + * Queries the value of a given uint64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64); + +/** + * Queries the value of a given uint64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pu64 Where to store the value on success. + * @param u64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def); + +/** + * Queries the value of a given int8_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8); + +/** + * Queries the value of a given int8_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi8 Where to store the value on success. + * @param i8Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def); + +/** + * Queries the value of a given int16_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16); + +/** + * Queries the value of a given int16_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi16 Where to store the value on success. + * @param i16Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def); + +/** + * Queries the value of a given int32_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32); + +/** + * Queries the value of a given int32_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi32 Where to store the value on success. + * @param i32Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def); + +/** + * Queries the value of a given int64_t key from the given configuration array. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64); + +/** + * Queries the value of a given int64_t key from the given configuration array, + * setting a default if not found. + * + * @returns IPRT status code. + * @param paCfg The configuration items. + * @param pszKey The key query the value for. + * @param pi64 Where to store the value on success. + * @param i64Def The default value to assign if the key is not found. + */ +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def); + +/** + * Creates a new USB gadget host. + * + * @returns IPRT status code. + * @param enmType The host type. + * @param paCfg Additional configuration parameters - optional. + * The array must be terminated with a NULL entry. + * @param phGadgetHost Where to store the handle to the gadget host on success. + */ +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost); + +/** + * Retains the given gadget host handle. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost); + +/** + * Releases the given gadget host handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadgetHost The gadget host handle to release. + */ +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost); + +/** + * Returns the current config of the given gadget host. + * + * @returns Pointer to a constant array of configuration items for the given gadget host. + * @param hGadgetHost The gadget host handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetHostGetCfg(UTSGADGETHOST hGadgetHost); + +/** + * Connects the given gadget to the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Disconnects the given gadget from the host. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host handle. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget); + +/** + * Creates a new USB gadget based the class. + * + * @returns IPRT status code. + * @param hGadgetHost The gadget host the gadget is part of. + * @param enmClass The gadget class. + * @param paCfg Array of optional configuration items for the gadget. + * @param phGadget Where to store the gadget handle on success. + */ +DECLHIDDEN(int) utsGadgetCreate(UTSGADGETHOST hGadgetHost, UTSGADGETCLASS enmClass, + PCUTSGADGETCFGITEM paCfg, PUTSGADET phGadget); + +/** + * Retains the given gadget handle. + * + * @returns New reference count. + * @param hGadget The gadget handle to retain. + */ +DECLHIDDEN(uint32_t) utsGadgetRetain(UTSGADGET hGadget); + +/** + * Releases the given gadget handle, destroying it if the reference + * count reaches 0. + * + * @returns New reference count. + * @param hGadget The gadget handle to destroy. + */ +DECLHIDDEN(uint32_t) utsGadgetRelease(UTSGADGET hGadget); + +/** + * Returns the current config of the given gadget. + * + * @returns Pointer to a constant array of configuration items for the given gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(PCUTSGADGETCFGITEM) utsGadgetGetCfg(UTSGADGET hGadget); + +/** + * Returns the path of the given gadget from which it can be accessed. + * + * @returns Access path. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(const char *) utsGadgetGetAccessPath(UTSGADGET hGadget); + +/** + * Returns the bus ID the gadget is on. + * + * @returns Bus ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetBusId(UTSGADGET hGadget); + +/** + * Returns the device ID of the gagdet. + * + * @returns Device ID of the gadget. + * @param hGadget The gadget handle. + */ +DECLHIDDEN(uint32_t) utsGadgetGetDevId(UTSGADGET hGadget); + +/** + * Mark the gadget as connected to the host. Depending + * on the host type it will be appear as physically attached + * or will appear in the exported USB device list. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to connect. + */ +DECLHIDDEN(int) utsGadgetConnect(UTSGADGET hGadget); + +/** + * Mark the gadget as disconnected from the host. + * + * @returns IPRT status code. + * @param hGadget The gadget handle to disconnect. + */ +DECLHIDDEN(int) utsGadgetDisconnect(UTSGADGET hGadget); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadget_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp new file mode 100644 index 00000000..7ae631a5 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetCfg.cpp @@ -0,0 +1,462 @@ +/* $Id: UsbTestServiceGadgetCfg.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget Cfg API. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/cdefs.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Returns the gadget configuration item matching the given key. + * + * @returns Pointer to the configuration item on success or NULL if not found. + * @param paCfg The configuration item array. + * @param pszKey The key to look for. + */ +static PCUTSGADGETCFGITEM utsGadgetCfgGetItemFromKey(PCUTSGADGETCFGITEM paCfg, const char *pszKey) +{ + while ( paCfg + && paCfg->pszKey) + { + if (!RTStrCmp(paCfg->pszKey, pszKey)) + return paCfg; + + paCfg++; + } + return NULL; +} + + + +DECLHIDDEN(int) utsGadgetCfgQueryBool(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem->Val.u.f; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryBoolDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + bool *pf, bool fDef) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_BOOLEAN) + { + *pf = pCfgItem ? pCfgItem->Val.u.f : fDef; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryString(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem->Val.u.psz); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryStringDef(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + char **ppszVal, const char *pszDef) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_STRING) + { + *ppszVal = RTStrDup(pCfgItem ? pCfgItem->Val.u.psz : pszDef); + if (*ppszVal) + rc = VINF_SUCCESS; + else + rc = VERR_NO_STR_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem->Val.u.u8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint8_t *pu8, uint8_t u8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT8) + { + *pu8 = pCfgItem ? pCfgItem->Val.u.u8 : u8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem->Val.u.u16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pu16, uint16_t u16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT16) + { + *pu16 = pCfgItem ? pCfgItem->Val.u.u16 : u16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem->Val.u.u32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pu32, uint32_t u32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT32) + { + *pu32 = pCfgItem ? pCfgItem->Val.u.u32 : u32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem->Val.u.u64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryU64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pu64, uint64_t u64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_UINT64) + { + *pu64 = pCfgItem ? pCfgItem->Val.u.u64 : u64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem->Val.u.i8; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS8Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + int8_t *pi8, uint8_t i8Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT8) + { + *pi8 = pCfgItem ? pCfgItem->Val.u.i8 : i8Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem->Val.u.i16; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS16Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint16_t *pi16, uint16_t i16Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT16) + { + *pi16 = pCfgItem ? pCfgItem->Val.u.i16 : i16Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem->Val.u.i32; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS32Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint32_t *pi32, uint32_t i32Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT32) + { + *pi32 = pCfgItem ? pCfgItem->Val.u.i32 : i32Def; + rc = VINF_SUCCESS; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64) +{ + int rc = VERR_NOT_FOUND; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if (pCfgItem) + { + if (pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem->Val.u.i64; + rc = VINF_SUCCESS; + } + else + rc = VERR_INVALID_PARAMETER; + } + + return rc; +} + + +DECLHIDDEN(int) utsGadgetCfgQueryS64Def(PCUTSGADGETCFGITEM paCfg, const char *pszKey, + uint64_t *pi64, uint64_t i64Def) +{ + int rc = VERR_INVALID_PARAMETER; + PCUTSGADGETCFGITEM pCfgItem = utsGadgetCfgGetItemFromKey(paCfg, pszKey); + + if ( !pCfgItem + || pCfgItem->Val.enmType == UTSGADGETCFGTYPE_INT64) + { + *pi64 = pCfgItem ? pCfgItem->Val.u.i64 : i64Def; + rc = VINF_SUCCESS; + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp new file mode 100644 index 00000000..6a2f3626 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetClassTest.cpp @@ -0,0 +1,470 @@ +/* $Id: UsbTestServiceGadgetClassTest.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget class + * for the test device. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/dir.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/symlink.h> +#include <iprt/thread.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServiceGadgetInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Default configfs mount point. */ +#define UTS_GADGET_CLASS_CONFIGFS_MNT_DEF "/sys/kernel/config/usb_gadget" +/** Gadget template name */ +#define UTS_GADGET_TEMPLATE_NAME "gadget_test" + +/** Default vendor ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_VENDOR_ID_DEF UINT16_C(0x0525) +/** Default product ID which is recognized by the usbtest driver. */ +#define UTS_GADGET_TEST_PRODUCT_ID_DEF UINT16_C(0xa4a0) +/** Default device class. */ +#define UTS_GADGET_TEST_DEVICE_CLASS_DEF UINT8_C(0xff) +/** Default serial number string. */ +#define UTS_GADGET_TEST_SERIALNUMBER_DEF "0123456789" +/** Default manufacturer string. */ +#define UTS_GADGET_TEST_MANUFACTURER_DEF "Oracle Inc." +/** Default product string. */ +#define UTS_GADGET_TEST_PRODUCT_DEF "USB test device" + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETCLASSINT +{ + /** Gadget template path. */ + char *pszGadgetPath; + /** The UDC this gadget is connected to. */ + char *pszUdc; + /** Bus identifier for the used UDC. */ + uint32_t uBusId; + /** Device identifier. */ + uint32_t uDevId; +} UTSGADGETCLASSINT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Number of already created gadgets, used for the template name. */ +static volatile uint32_t g_cGadgets = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirCreateV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirCreateFullPath(aszPath, 0700); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Creates a new directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirCreate(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirCreateV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param va The arguments. + */ +static int utsGadgetClassTestDirRemoveV(const char *pszFormat, va_list va) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintfV(&aszPath[0], sizeof(aszPath), pszFormat, va); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTDirRemove(aszPath); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Removes a directory pointed to by the given format string. + * + * @returns IPRT status code. + * @param pszFormat The format string. + * @param ... The arguments. + */ +static int utsGadgetClassTestDirRemove(const char *pszFormat, ...) +{ + va_list va; + va_start(va, pszFormat); + int rc = utsGadgetClassTestDirRemoveV(pszFormat, va); + va_end(va); + return rc; +} + + +/** + * Links the given function to the given config. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to link. + * @param pszCfg The configuration which the function will be part of. + */ +static int utsGadgetClassTestLinkFuncToCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPathFunc[RTPATH_MAX + 1]; + char aszPathCfg[RTPATH_MAX + 1]; + + size_t cbStr = RTStrPrintf(&aszPathFunc[0], sizeof(aszPathFunc), "%s/functions/%s", + pClass->pszGadgetPath, pszFunc); + if (cbStr <= sizeof(aszPathFunc) - 1) + { + cbStr = RTStrPrintf(&aszPathCfg[0], sizeof(aszPathCfg), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPathCfg) - 1) + rc = RTSymlinkCreate(&aszPathCfg[0], &aszPathFunc[0], RTSYMLINKTYPE_DIR, 0); + else + rc = VERR_BUFFER_OVERFLOW; + } + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Unlinks the given function from the given configuration. + * + * @returns IPRT status code. + * @param pClass The gadget class instance data. + * @param pszFunc The function to unlink. + * @param pszCfg The configuration which the function is currently part of. + */ +static int utsGadgetClassTestUnlinkFuncFromCfg(PUTSGADGETCLASSINT pClass, const char *pszFunc, const char *pszCfg) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + size_t cbStr = RTStrPrintf(&aszPath[0], sizeof(aszPath), "%s/configs/%s/%s", + pClass->pszGadgetPath, pszCfg, pszFunc); + if (cbStr <= sizeof(aszPath) - 1) + rc = RTSymlinkDelete(&aszPath[0], 0); + else + rc = VERR_BUFFER_OVERFLOW; + + return rc; +} + + +/** + * Cleans up any leftover configurations from the gadget class. + * + * @param pClass The gadget class instance data. + */ +static void utsGadgetClassTestCleanup(PUTSGADGETCLASSINT pClass) +{ + /* Unbind the gadget from the currently assigned UDC first. */ + int rc = RTLinuxSysFsWriteStrFile("", 0, NULL, "%s/UDC", pClass->pszGadgetPath); + AssertRC(rc); + + /* Delete the symlinks, ignore any errors. */ + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "Loopback.0", "c.2"); + utsGadgetClassTestUnlinkFuncFromCfg(pClass, "SourceSink.0", "c.1"); + + /* Delete configuration strings and then the configuration directories. */ + utsGadgetClassTestDirRemove("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + + utsGadgetClassTestDirRemove("%s/configs/c.2", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/configs/c.1", pClass->pszGadgetPath); + + /* Delete the functions. */ + utsGadgetClassTestDirRemove("%s/functions/Loopback.0", pClass->pszGadgetPath); + utsGadgetClassTestDirRemove("%s/functions/SourceSink.0", pClass->pszGadgetPath); + + /* Delete the english strings. */ + utsGadgetClassTestDirRemove("%s/strings/0x409", pClass->pszGadgetPath); + + /* Finally delete the gadget template. */ + utsGadgetClassTestDirRemove(pClass->pszGadgetPath); + + /* Release the UDC. */ + if (pClass->pszUdc) + { + rc = utsPlatformLnxReleaseUDC(pClass->pszUdc); + AssertRC(rc); + RTStrFree(pClass->pszUdc); + } +} + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetClassTestInit(PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + + if (RTLinuxSysFsExists(UTS_GADGET_CLASS_CONFIGFS_MNT_DEF)) + { + /* Create the gadget template */ + unsigned idx = ASMAtomicIncU32(&g_cGadgets); + + int rcStr = RTStrAPrintf(&pClass->pszGadgetPath, "%s/%s%u", UTS_GADGET_CLASS_CONFIGFS_MNT_DEF, + UTS_GADGET_TEMPLATE_NAME, idx); + if (rcStr == -1) + return VERR_NO_STR_MEMORY; + + rc = utsGadgetClassTestDirCreate(pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + { + uint16_t idVendor = 0; + uint16_t idProduct = 0; + uint8_t bDeviceClass = 0; + char *pszSerial = NULL; + char *pszManufacturer = NULL; + char *pszProduct = NULL; + bool fSuperSpeed = false; + + /* Get basic device config. */ + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idVendor", &idVendor, UTS_GADGET_TEST_VENDOR_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU16Def(paCfg, "Gadget/idProduct", &idProduct, UTS_GADGET_TEST_PRODUCT_ID_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryU8Def(paCfg, "Gadget/bDeviceClass", &bDeviceClass, UTS_GADGET_TEST_DEVICE_CLASS_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/SerialNumber", &pszSerial, UTS_GADGET_TEST_SERIALNUMBER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Manufacturer", &pszManufacturer, UTS_GADGET_TEST_MANUFACTURER_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryStringDef(paCfg, "Gadget/Product", &pszProduct, UTS_GADGET_TEST_PRODUCT_DEF); + if (RT_SUCCESS(rc)) + rc = utsGadgetCfgQueryBoolDef(paCfg, "Gadget/SuperSpeed", &fSuperSpeed, false); + + if (RT_SUCCESS(rc)) + { + /* Write basic attributes. */ + rc = RTLinuxSysFsWriteU16File(16, idVendor, "%s/idVendor", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, idProduct, "%s/idProduct", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteU16File(16, bDeviceClass, "%s/bDeviceClass", pClass->pszGadgetPath); + + /* Create english language strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszSerial, 0, NULL, "%s/strings/0x409/serialnumber", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszManufacturer, 0, NULL, "%s/strings/0x409/manufacturer", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pszProduct, 0, NULL, "%s/strings/0x409/product", pClass->pszGadgetPath); + + /* Create the gadget functions. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/SourceSink.0", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/functions/Loopback.0", pClass->pszGadgetPath); + + /* Create the device configs. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2", pClass->pszGadgetPath); + + /* Write configuration strings. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.1/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestDirCreate("%s/configs/c.2/strings/0x409", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("source and sink data", 0, NULL, "%s/configs/c.1/strings/0x409/configuration", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile("loop input to output", 0, NULL, "%s/configs/c.2/strings/0x409/configuration", pClass->pszGadgetPath); + + /* Link the functions into the configurations. */ + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "SourceSink.0", "c.1"); + if (RT_SUCCESS(rc)) + rc = utsGadgetClassTestLinkFuncToCfg(pClass, "Loopback.0", "c.2"); + + /* Finally enable the gadget by attaching it to a UDC. */ + if (RT_SUCCESS(rc)) + { + pClass->pszUdc = NULL; + + rc = utsPlatformLnxAcquireUDC(fSuperSpeed, &pClass->pszUdc, &pClass->uBusId); + if (RT_SUCCESS(rc)) + rc = RTLinuxSysFsWriteStrFile(pClass->pszUdc, 0, NULL, "%s/UDC", pClass->pszGadgetPath); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + } + } + + if (pszSerial) + RTStrFree(pszSerial); + if (pszManufacturer) + RTStrFree(pszManufacturer); + if (pszProduct) + RTStrFree(pszProduct); + } + } + else + rc = VERR_NOT_FOUND; + + if (RT_FAILURE(rc)) + utsGadgetClassTestCleanup(pClass); + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetClassTestTerm(PUTSGADGETCLASSINT pClass) +{ + utsGadgetClassTestCleanup(pClass); + + if (pClass->pszGadgetPath) + RTStrFree(pClass->pszGadgetPath); +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnGetBusId} + */ +static DECLCALLBACK(uint32_t) utsGadgetClassTestGetBusId(PUTSGADGETCLASSINT pClass) +{ + return pClass->uBusId; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnConnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestConnect(PUTSGADGETCLASSINT pClass) +{ + int rc = RTLinuxSysFsWriteStrFile("connect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc); + if (RT_SUCCESS(rc)) + RTThreadSleep(500); /* Fudge: Sleep a bit to give the device a chance to appear on the host so binding succeeds. */ + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETCLASSIF,pfnDisconnect} + */ +static DECLCALLBACK(int) utsGadgetClassTestDisconnect(PUTSGADGETCLASSINT pClass) +{ + return RTLinuxSysFsWriteStrFile("disconnect", 0, NULL, "/sys/class/udc/%s/soft_connect", pClass->pszUdc);} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETCLASSIF g_UtsGadgetClassTest = +{ + /** enmType */ + UTSGADGETCLASS_TEST, + /** pszDesc */ + "UTS test device gadget class", + /** cbIf */ + sizeof(UTSGADGETCLASSINT), + /** pfnInit */ + utsGadgetClassTestInit, + /** pfnTerm */ + utsGadgetClassTestTerm, + /** pfnGetBusId */ + utsGadgetClassTestGetBusId, + /** pfnConnect */ + utsGadgetClassTestConnect, + /** pfnDisconnect. */ + utsGadgetClassTestDisconnect +}; + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp new file mode 100644 index 00000000..1f8cc53b --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHost.cpp @@ -0,0 +1,179 @@ +/* $Id: UsbTestServiceGadgetHost.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host API. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/errcore.h> +#include <iprt/mem.h> +#include <iprt/string.h> + +#include "UsbTestServiceGadget.h" +#include "UsbTestServiceGadgetHostInternal.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTINT +{ + /** Reference counter. */ + volatile uint32_t cRefs; + /** Pointer to the gadget host callback table. */ + PCUTSGADGETHOSTIF pHstIf; + /** Interface specific instance data - variable in size. */ + uint8_t abIfInst[1]; +} UTSGADGETHOSTINT; +/** Pointer to the internal gadget host instance data. */ +typedef UTSGADGETHOSTINT *PUTSGADGETHOSTINT; + + +/********************************************************************************************************************************* +* Global variables * +*********************************************************************************************************************************/ + +/** Known gadget host interfaces. */ +static const PCUTSGADGETHOSTIF g_apUtsGadgetHostIf[] = +{ + &g_UtsGadgetHostIfUsbIp, +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Destroys a gadget host instance. + * + * @param pThis The gadget host instance. + */ +static void utsGadgetHostDestroy(PUTSGADGETHOSTINT pThis) +{ + /** @todo Remove all gadgets. */ + pThis->pHstIf->pfnTerm((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0]); + RTMemFree(pThis); +} + + +DECLHIDDEN(int) utsGadgetHostCreate(UTSGADGETHOSTTYPE enmType, PCUTSGADGETCFGITEM paCfg, + PUTSGADGETHOST phGadgetHost) +{ + int rc = VINF_SUCCESS; + PCUTSGADGETHOSTIF pIf = NULL; + + /* Get the interface. */ + for (unsigned i = 0; i < RT_ELEMENTS(g_apUtsGadgetHostIf); i++) + { + if (g_apUtsGadgetHostIf[i]->enmType == enmType) + { + pIf = g_apUtsGadgetHostIf[i]; + break; + } + } + + if (RT_LIKELY(pIf)) + { + PUTSGADGETHOSTINT pThis = (PUTSGADGETHOSTINT)RTMemAllocZ(RT_UOFFSETOF_DYN(UTSGADGETHOSTINT, abIfInst[pIf->cbIf])); + if (RT_LIKELY(pThis)) + { + pThis->cRefs = 1; + pThis->pHstIf = pIf; + rc = pIf->pfnInit((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], paCfg); + if (RT_SUCCESS(rc)) + *phGadgetHost = pThis; + else + RTMemFree(pThis); + } + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + + return rc; +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRetain(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + return ASMAtomicIncU32(&pThis->cRefs); +} + + +DECLHIDDEN(uint32_t) utsGadgetHostRelease(UTSGADGETHOST hGadgetHost) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, 0); + + uint32_t cRefs = ASMAtomicDecU32(&pThis->cRefs); + if (!cRefs) + utsGadgetHostDestroy(pThis); + + return cRefs; +} + + +DECLHIDDEN(int) utsGadgetHostGadgetConnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetConnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + + +DECLHIDDEN(int) utsGadgetHostGadgetDisconnect(UTSGADGETHOST hGadgetHost, UTSGADGET hGadget) +{ + PUTSGADGETHOSTINT pThis = hGadgetHost; + + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + return pThis->pHstIf->pfnGadgetDisconnect((PUTSGADGETHOSTTYPEINT)&pThis->abIfInst[0], hGadget); +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h new file mode 100644 index 00000000..6ae11fd0 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostInternal.h @@ -0,0 +1,132 @@ +/* $Id: UsbTestServiceGadgetHostInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Gadget API. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETHOSTTYPEINT *PUTSGADGETHOSTTYPEINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETHOSTTYPEINT *PPUTSGADETHOSTTYPEINT; + +/** + * Gadget host interface. + */ +typedef struct UTSGADGETHOSTIF +{ + /** The gadget host type implemented. */ + UTSGADGETHOSTTYPE enmType; + /** Description. */ + const char *pszDesc; + /** Size of the interface specific instance data. */ + size_t cbIf; + + /** + * Initializes the gadget host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget host interface. + * + * @param pIf The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETHOSTTYPEINT pIf)); + + /** + * Adds the given gadget to the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetAdd, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Removes the given gadget from the host interface. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetRemove, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Connects the given gadget to the host interface so it appears as connected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetConnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + + /** + * Disconnects the given gadget from the host interface so it appears as disconnected to the client + * of the gadget host. + * + * @returns IPRT status code. + * @param pIf The interface specific instance data. + * @param hGadget The gadget handle to add. + */ + DECLR3CALLBACKMEMBER(int, pfnGadgetDisconnect, (PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget)); + +} UTSGADGETHOSTIF; +/** Pointer to a gadget host callback table. */ +typedef UTSGADGETHOSTIF *PUTSGADGETHOSTIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETHOSTIF *PCUTSGADGETHOSTIF; + +extern UTSGADGETHOSTIF const g_UtsGadgetHostIfUsbIp; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetHostInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp new file mode 100644 index 00000000..a0f183ca --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetHostUsbIp.cpp @@ -0,0 +1,263 @@ +/* $Id: UsbTestServiceGadgetHostUsbIp.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, USB gadget host interface + * for USB/IP. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/process.h> +#include <iprt/string.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceGadgetHostInternal.h" +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * Internal UTS gadget host instance data. + */ +typedef struct UTSGADGETHOSTTYPEINT +{ + /** Handle to the USB/IP daemon process. */ + RTPROCESS hProcUsbIp; +} UTSGADGETHOSTTYPEINT; + +/** Default port of the USB/IP server. */ +#define UTS_GADGET_HOST_USBIP_PORT_DEF 3240 + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Worker for binding/unbinding the given gadget from the USB/IP server. + * + * @returns IPRT status code. + * @param pThis The gadget host instance. + * @param hGadget The gadget handle. + * @param fBind Flag whether to do a bind or unbind. + */ +static int usbGadgetHostUsbIpBindUnbind(PUTSGADGETHOSTTYPEINT pThis, UTSGADGET hGadget, bool fBind) +{ + RT_NOREF1(pThis); + uint32_t uBusId, uDevId; + char aszBus[32]; + + uBusId = utsGadgetGetBusId(hGadget); + uDevId = utsGadgetGetDevId(hGadget); + + /* Create the busid argument string. */ + size_t cbRet = RTStrPrintf(&aszBus[0], RT_ELEMENTS(aszBus), "%u-%u", uBusId, uDevId); + if (cbRet == RT_ELEMENTS(aszBus)) + return VERR_BUFFER_OVERFLOW; + + /* Bind to the USB/IP server. */ + RTPROCESS hProcUsbIp = NIL_RTPROCESS; + const char *apszArgv[5]; + + apszArgv[0] = "usbip"; + apszArgv[1] = fBind ? "bind" : "unbind"; + apszArgv[2] = "-b"; + apszArgv[3] = &aszBus[0]; + apszArgv[4] = NULL; + + int rc = RTProcCreate("usbip", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcUsbIp); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnInit} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpInit(PUTSGADGETHOSTTYPEINT pIf, PCUTSGADGETCFGITEM paCfg) +{ + int rc = VINF_SUCCESS; + uint16_t uPort = 0; + + pIf->hProcUsbIp = NIL_RTPROCESS; + + rc = utsGadgetCfgQueryU16Def(paCfg, "UsbIp/Port", &uPort, UTS_GADGET_HOST_USBIP_PORT_DEF); + if (RT_SUCCESS(rc)) + { + /* Make sure the kernel drivers are loaded. */ + rc = utsPlatformModuleLoad("usbip-core", NULL, 0); + if (RT_SUCCESS(rc)) + { + rc = utsPlatformModuleLoad("usbip-host", NULL, 0); + if (RT_SUCCESS(rc)) + { + char aszPort[10]; + char aszPidFile[64]; + const char *apszArgv[6]; + + RTStrPrintf(aszPort, RT_ELEMENTS(aszPort), "%u", uPort); + RTStrPrintf(aszPidFile, RT_ELEMENTS(aszPidFile), "/var/run/usbipd-%u.pid", uPort); + /* Start the USB/IP server process. */ + apszArgv[0] = "usbipd"; + apszArgv[1] = "--tcp-port"; + apszArgv[2] = aszPort; + apszArgv[3] = "--pid"; + apszArgv[4] = aszPidFile; + apszArgv[5] = NULL; + rc = RTProcCreate("usbipd", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &pIf->hProcUsbIp); + if (RT_SUCCESS(rc)) + { + /* Wait for a bit to make sure the server started up successfully. */ + uint64_t tsStart = RTTimeMilliTS(); + do + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_NOBLOCK, &ProcSts); + if (rc != VERR_PROCESS_RUNNING) + { + rc = VERR_INVALID_HANDLE; + break; + } + RTThreadSleep(1); + rc = VINF_SUCCESS; + } while (RTTimeMilliTS() - tsStart < 2 * 1000); /* 2 seconds. */ + } + } + } + } + + return rc; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnTerm} + */ +static DECLCALLBACK(void) utsGadgetHostUsbIpTerm(PUTSGADGETHOSTTYPEINT pIf) +{ + /* Kill the process and wait for it to terminate. */ + RTProcTerminate(pIf->hProcUsbIp); + + RTPROCSTATUS ProcSts; + RTProcWait(pIf->hProcUsbIp, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetAdd} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetAdd(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetRemove} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetRemove(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + /* Nothing to do so far. */ + RT_NOREF2(pIf, hGadget); + return VINF_SUCCESS; +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetConnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetConnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, true /* fBind */); +} + + +/** + * @interface_method_impl{UTSGADGETHOSTIF,pfnGadgetDisconnect} + */ +static DECLCALLBACK(int) utsGadgetHostUsbIpGadgetDisconnect(PUTSGADGETHOSTTYPEINT pIf, UTSGADGET hGadget) +{ + return usbGadgetHostUsbIpBindUnbind(pIf, hGadget, false /* fBind */); +} + + + +/** + * The gadget host interface callback table. + */ +const UTSGADGETHOSTIF g_UtsGadgetHostIfUsbIp = +{ + /** enmType */ + UTSGADGETHOSTTYPE_USBIP, + /** pszDesc */ + "UTS USB/IP gadget host", + /** cbIf */ + sizeof(UTSGADGETHOSTTYPEINT), + /** pfnInit */ + utsGadgetHostUsbIpInit, + /** pfnTerm */ + utsGadgetHostUsbIpTerm, + /** pfnGadgetAdd */ + utsGadgetHostUsbIpGadgetAdd, + /** pfnGadgetRemove */ + utsGadgetHostUsbIpGadgetRemove, + /** pfnGadgetConnect */ + utsGadgetHostUsbIpGadgetConnect, + /** pfnGadgetDisconnect */ + utsGadgetHostUsbIpGadgetDisconnect +}; diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h new file mode 100644 index 00000000..dce2719f --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceGadgetInternal.h @@ -0,0 +1,118 @@ +/* $Id: UsbTestServiceGadgetInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Interal gadget interfaces. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +#include "UsbTestServiceGadget.h" + +RT_C_DECLS_BEGIN + +/** Pointer to an opaque type dependent gadget host instance data. */ +typedef struct UTSGADGETCLASSINT *PUTSGADGETCLASSINT; +/** Pointer to a gadget host instance pointer. */ +typedef PUTSGADGETCLASSINT *PPUTSGADGETCLASSINT; + +/** + * Gadget class interface. + */ +typedef struct UTSGADGETCLASSIF +{ + /** The gadget class type implemented. */ + UTSGADGETCLASS enmClass; + /** Description. */ + const char *pszDesc; + /** Size of the class specific instance data. */ + size_t cbClass; + + /** + * Initializes the gadget class instance. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + * @param paCfg The configuration of the interface. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (PUTSGADGETCLASSINT pClass, PCUTSGADGETCFGITEM paCfg)); + + /** + * Terminates the gadget class instance. + * + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (PUTSGADGETCLASSINT pClass)); + + /** + * Returns the bus ID of the class instance. + * + * @returns Bus ID. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(uint32_t, pfnGetBusId, (PUTSGADGETCLASSINT pClass)); + + /** + * Connects the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnConnect, (PUTSGADGETCLASSINT pClass)); + + /** + * Disconnect the gadget. + * + * @returns IPRT status code. + * @param pClass The interface specific instance data. + */ + DECLR3CALLBACKMEMBER(int, pfnDisconnect, (PUTSGADGETCLASSINT pClass)); + +} UTSGADGETCLASSIF; +/** Pointer to a gadget class callback table. */ +typedef UTSGADGETCLASSIF *PUTSGADGETCLASSIF; +/** Pointer to a const gadget host callback table. */ +typedef const struct UTSGADGETCLASSIF *PCUTSGADGETCLASSIF; + +extern UTSGADGETCLASSIF const g_UtsGadgetClassTest; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceGadgetInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h new file mode 100644 index 00000000..71e2d961 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceInternal.h @@ -0,0 +1,226 @@ +/* $Id: UsbTestServiceInternal.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Internal Header. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/getopt.h> +#include <iprt/stream.h> + +#include "UsbTestServiceProtocol.h" + +RT_C_DECLS_BEGIN + +/** Opaque UTS transport layer specific client data. */ +typedef struct UTSTRANSPORTCLIENT *PUTSTRANSPORTCLIENT; +typedef PUTSTRANSPORTCLIENT *PPUTSTRANSPORTCLIENT; + +/** + * Transport layer descriptor. + */ +typedef struct UTSTRANSPORT +{ + /** The name. */ + char szName[16]; + /** The description. */ + const char *pszDesc; + /** Pointer to an array of options. */ + PCRTGETOPTDEF paOpts; + /** The number of options in the array. */ + size_t cOpts; + + /** + * Print the usage information for this transport layer. + * + * @param pStream The stream to print the usage info to. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(void, pfnUsage, (PRTSTREAM pStream)); + + /** + * Handle an option. + * + * When encountering an options that is not part of the base options, we'll call + * this method for each transport layer until one handles it. + * + * @retval VINF_SUCCESS if handled. + * @retval VERR_TRY_AGAIN if not handled. + * @retval VERR_INVALID_PARAMETER if we should exit with a non-zero status. + * + * @param ch The short option value. + * @param pVal Pointer to the value union. + * + * @remarks This is only required if TXSTRANSPORT::cOpts is greater than 0. + */ + DECLR3CALLBACKMEMBER(int, pfnOption, (int ch, PCRTGETOPTUNION pVal)); + + /** + * Initializes the transport layer. + * + * @returns IPRT status code. On errors, the transport layer shall call + * RTMsgError to display the error details to the user. + */ + DECLR3CALLBACKMEMBER(int, pfnInit, (void)); + + /** + * Terminate the transport layer, closing and freeing resources. + * + * On errors, the transport layer shall call RTMsgError to display the error + * details to the user. + */ + DECLR3CALLBACKMEMBER(void, pfnTerm, (void)); + + /** + * Waits for a new client to connect and returns the client specific data on + * success. + */ + DECLR3CALLBACKMEMBER(int, pfnWaitForConnect, (PPUTSTRANSPORTCLIENT ppClientNew)); + + /** + * Polls for incoming packets. + * + * @returns true if there are pending packets, false if there isn't. + * @param pClient The client to poll for data. + */ + DECLR3CALLBACKMEMBER(bool, pfnPollIn, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Adds any pollable handles to the poll set. + * + * @returns IPRT status code. + * @param hPollSet The poll set to add them to. + * @param pClient The transport client structure. + * @param idStart The handle ID to start at. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetAdd, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Removes the given client frmo the given pollset. + * + * @returns IPRT status code. + * @param hPollSet The poll set to remove from. + * @param pClient The transport client structure. + * @param idStart The handle ID to remove. + */ + DECLR3CALLBACKMEMBER(int, pfnPollSetRemove, (RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart)); + + /** + * Receives an incoming packet. + * + * This will block until the data becomes available or we're interrupted by a + * signal or something. + * + * @returns IPRT status code. On error conditions other than VERR_INTERRUPTED, + * the current operation will be aborted when applicable. When + * interrupted, the transport layer will store the data until the next + * receive call. + * + * @param pClient The transport client structure. + * @param ppPktHdr Where to return the pointer to the packet we've + * read. This is allocated from the heap using + * RTMemAlloc (w/ UTSPKT_ALIGNMENT) and must be + * free by calling RTMemFree. + */ + DECLR3CALLBACKMEMBER(int, pfnRecvPkt, (PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr)); + + /** + * Sends an outgoing packet. + * + * This will block until the data has been written. + * + * @returns IPRT status code. + * @retval VERR_INTERRUPTED if interrupted before anything was sent. + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + */ + DECLR3CALLBACKMEMBER(int, pfnSendPkt, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr)); + + /** + * Sends a babble packet and disconnects the client (if applicable). + * + * @param pClient The transport client structure. + * @param pPktHdr The packet to send. The size is given by + * aligning the size in the header by + * UTSPKT_ALIGNMENT. + * @param cMsSendTimeout The send timeout measured in milliseconds. + */ + DECLR3CALLBACKMEMBER(void, pfnBabble, (PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout)); + + /** + * Notification about a client HOWDY. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyHowdy, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a client BYE. + * + * For connection oriented transport layers, it would be good to disconnect the + * client at this point. + * + * @param pClient The transport client structure. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyBye, (PUTSTRANSPORTCLIENT pClient)); + + /** + * Notification about a REBOOT or SHUTDOWN. + * + * For connection oriented transport layers, stop listening for and + * accepting at this point. + */ + DECLR3CALLBACKMEMBER(void, pfnNotifyReboot, (void)); + + /** Non-zero end marker. */ + uint32_t u32EndMarker; +} UTSTRANSPORT; +/** Pointer to a const transport layer descriptor. */ +typedef const struct UTSTRANSPORT *PCUTSTRANSPORT; + + +extern UTSTRANSPORT const g_TcpTransport; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceInternal_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp new file mode 100644 index 00000000..0f422479 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp @@ -0,0 +1,448 @@ +/* $Id: UsbTestServicePlatform-linux.cpp $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform + * specific helpers - Linux version. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include <iprt/asm.h> +#include <iprt/ctype.h> +#include <iprt/err.h> +#include <iprt/dir.h> +#include <iprt/env.h> +#include <iprt/mem.h> +#include <iprt/path.h> +#include <iprt/process.h> +#include <iprt/string.h> + +#include <iprt/linux/sysfs.h> + +#include "UsbTestServicePlatform.h" + + +/********************************************************************************************************************************* +* Constants And Macros, Structures and Typedefs * +*********************************************************************************************************************************/ + +/** Where the dummy_hcd.* and dummy_udc.* entries are stored. */ +#define UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/sys/devices/platform" + +/** + * A USB bus provided by the dummy HCD. + */ +typedef struct UTSPLATFORMLNXDUMMYHCDBUS +{ + /** The bus ID on the host the dummy HCD is serving. */ + uint32_t uBusId; + /** Flag whether this is a super speed bus. */ + bool fSuperSpeed; +} UTSPLATFORMLNXDUMMYHCDBUS; +/** Pointer to a Dummy HCD bus. */ +typedef UTSPLATFORMLNXDUMMYHCDBUS *PUTSPLATFORMLNXDUMMYHCDBUS; + +/** + * A dummy UDC descriptor. + */ +typedef struct UTSPLATFORMLNXDUMMYHCD +{ + /** Index of the dummy hcd entry. */ + uint32_t idxDummyHcd; + /** Name for the dummy HCD. */ + const char *pszHcdName; + /** Name for the accompanying dummy HCD. */ + const char *pszUdcName; + /** Flag whether this HCD is free for use. */ + bool fAvailable; + /** Flag whether this HCD contains a super speed capable bus. */ + bool fSuperSpeed; + /** Number of busses this HCD instance serves. */ + unsigned cBusses; + /** Bus structures the HCD serves.*/ + PUTSPLATFORMLNXDUMMYHCDBUS paBusses; +} UTSPLATFORMLNXDUMMYHCD; +/** Pointer to a dummy HCD entry. */ +typedef UTSPLATFORMLNXDUMMYHCD *PUTSPLATFORMLNXDUMMYHCD; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ + +/** Array of dummy HCD entries. */ +static PUTSPLATFORMLNXDUMMYHCD g_paDummyHcd = NULL; +/** Number of Dummy hCD entries in the array. */ +static unsigned g_cDummyHcd = 0; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ + + +/** + * Queries the assigned busses for the given dummy HCD instance. + * + * @returns IPRT status code. + * @param pHcd The dummy HCD bus instance. + * @param pszHcdName The base HCD name. + */ +static int utsPlatformLnxDummyHcdQueryBusses(PUTSPLATFORMLNXDUMMYHCD pHcd, const char *pszHcdName) +{ + int rc = VINF_SUCCESS; + char aszPath[RTPATH_MAX + 1]; + unsigned idxBusCur = 0; + unsigned idxBusMax = 0; + + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/usb*", + pszHcdName, pHcd->idxDummyHcd); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + RTDIR hDir = NULL; + rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + uint32_t uBusId = 0; + + /* Extract the bus number - it is after "usb", i.e. "usb9" indicates a bus ID of 9. */ + rc = RTStrToUInt32Ex(&DirFolderContent.szName[3], NULL, 10, &uBusId); + if (RT_SUCCESS(rc)) + { + /* Check whether this is a super speed bus. */ + int64_t iSpeed = 0; + bool fSuperSpeed = false; + rc = RTLinuxSysFsReadIntFile(10, &iSpeed, UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.%u/%s/speed", + pszHcdName, pHcd->idxDummyHcd, DirFolderContent.szName); + if ( RT_SUCCESS(rc) + && (iSpeed == 5000 || iSpeed == 10000)) + { + fSuperSpeed = true; + pHcd->fSuperSpeed = true; + } + + /* Add to array of available busses for this HCD. */ + if (idxBusCur == idxBusMax) + { + size_t cbNew = (idxBusMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCDBUS); + PUTSPLATFORMLNXDUMMYHCDBUS pNew = (PUTSPLATFORMLNXDUMMYHCDBUS)RTMemRealloc(pHcd->paBusses, cbNew); + if (pNew) + { + idxBusMax += 10; + pHcd->paBusses = pNew; + } + } + + if (idxBusCur < idxBusMax) + { + pHcd->paBusses[idxBusCur].uBusId = uBusId; + pHcd->paBusses[idxBusCur].fSuperSpeed = fSuperSpeed; + idxBusCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } while (RT_SUCCESS(rc)); + + pHcd->cBusses = idxBusCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + + +/** + * Scans all available HCDs with the given name. + * + * @returns IPRT status code. + * @param pszHcdName The base HCD name. + * @param pszUdcName The base UDC name. + */ +static int utsPlatformLnxHcdScanByName(const char *pszHcdName, const char *pszUdcName) +{ + char aszPath[RTPATH_MAX + 1]; + size_t cchPath = RTStrPrintf(&aszPath[0], RT_ELEMENTS(aszPath), + UTS_PLATFORM_LNX_DUMMY_HCD_PATH "/%s.*", pszHcdName); + if (cchPath == RT_ELEMENTS(aszPath)) + return VERR_BUFFER_OVERFLOW; + + /* Enumerate the available HCD and their bus numbers. */ + RTDIR hDir = NULL; + int rc = RTDirOpenFiltered(&hDir, aszPath, RTDIRFILTER_WINNT, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + { + unsigned idxHcdCur = g_cDummyHcd; + unsigned idxHcdMax = g_cDummyHcd; + + do + { + RTDIRENTRY DirFolderContent; + rc = RTDirRead(hDir, &DirFolderContent, NULL); + if (RT_SUCCESS(rc)) + { + /* + * Get the HCD index and assigned bus number form the sysfs entries, + * Any error here is silently ignored and results in the HCD not being + * added to the list of available controllers. + */ + const char *pszIdx = RTStrStr(DirFolderContent.szName, "."); + if (pszIdx) + { + /* Skip the separator and convert number to index. */ + pszIdx++; + + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + /* Add to array of available HCDs. */ + if (idxHcdCur == idxHcdMax) + { + size_t cbNew = (idxHcdMax + 10) * sizeof(UTSPLATFORMLNXDUMMYHCD); + PUTSPLATFORMLNXDUMMYHCD pNew = (PUTSPLATFORMLNXDUMMYHCD)RTMemRealloc(g_paDummyHcd, cbNew); + if (pNew) + { + idxHcdMax += 10; + g_paDummyHcd = pNew; + } + } + + if (idxHcdCur < idxHcdMax) + { + g_paDummyHcd[idxHcdCur].idxDummyHcd = idxHcd; + g_paDummyHcd[idxHcdCur].pszHcdName = pszHcdName; + g_paDummyHcd[idxHcdCur].pszUdcName = pszUdcName; + g_paDummyHcd[idxHcdCur].fAvailable = true; + g_paDummyHcd[idxHcdCur].fSuperSpeed = false; + g_paDummyHcd[idxHcdCur].cBusses = 0; + g_paDummyHcd[idxHcdCur].paBusses = NULL; + rc = utsPlatformLnxDummyHcdQueryBusses(&g_paDummyHcd[idxHcdCur], pszHcdName); + if (RT_SUCCESS(rc)) + idxHcdCur++; + } + else + rc = VERR_NO_MEMORY; + } + } + } + } while (RT_SUCCESS(rc)); + + g_cDummyHcd = idxHcdCur; + + if (rc == VERR_NO_MORE_FILES) + rc = VINF_SUCCESS; + + RTDirClose(hDir); + } + + return rc; +} + +DECLHIDDEN(int) utsPlatformInit(void) +{ + /* Load the modules required for setting up USB/IP testing. */ + int rc = utsPlatformModuleLoad("libcomposite", NULL, 0); + if (RT_SUCCESS(rc)) + { + const char *apszArg[] = { "num=20" }; /** @todo Make configurable from config. */ + rc = utsPlatformModuleLoad("dummy_hcd", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformModuleLoad("dummy_hcd_ss", &apszArg[0], RT_ELEMENTS(apszArg)); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd", "dummy_udc"); + if (RT_SUCCESS(rc)) + rc = utsPlatformLnxHcdScanByName("dummy_hcd_ss", "dummy_udc_ss"); + } + + return rc; +} + + +DECLHIDDEN(void) utsPlatformTerm(void) +{ + /* Unload dummy HCD. */ + utsPlatformModuleUnload("dummy_hcd"); + utsPlatformModuleUnload("dummy_hcd_ss"); + + RTMemFree(g_paDummyHcd); +} + + +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char **papszArgs = (const char **)RTMemAllocZ((3 + cArgv) * sizeof(const char *)); + if (RT_UNLIKELY(!papszArgs)) + return VERR_NO_MEMORY; + + papszArgs[0] = "modprobe"; + papszArgs[1] = pszModule; + + unsigned idx; + for (idx = 0; idx < cArgv; idx++) + papszArgs[2+idx] = papszArgv[idx]; + papszArgs[2+idx] = NULL; + + int rc = RTProcCreate("modprobe", papszArgs, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + RTMemFree(papszArgs); + return rc; +} + + +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule) +{ + RTPROCESS hProcModprobe = NIL_RTPROCESS; + const char *apszArgv[3]; + + apszArgv[0] = "rmmod"; + apszArgv[1] = pszModule; + apszArgv[2] = NULL; + + int rc = RTProcCreate("rmmod", apszArgv, RTENV_DEFAULT, RTPROC_FLAGS_SEARCH_PATH, &hProcModprobe); + if (RT_SUCCESS(rc)) + { + RTPROCSTATUS ProcSts; + rc = RTProcWait(hProcModprobe, RTPROCWAIT_FLAGS_BLOCK, &ProcSts); + if (RT_SUCCESS(rc)) + { + /* Evaluate the process status. */ + if ( ProcSts.enmReason != RTPROCEXITREASON_NORMAL + || ProcSts.iStatus != 0) + rc = VERR_UNRESOLVED_ERROR; /** @todo Log and give finer grained status code. */ + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId) +{ + int rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + PUTSPLATFORMLNXDUMMYHCD pHcd = &g_paDummyHcd[i]; + + /* + * We can't use a super speed capable UDC for gadgets with lower speeds + * because they hardcode the maximum speed to SuperSpeed most of the time + * which will make it unusable for lower speeds. + */ + if ( pHcd->fAvailable + && pHcd->fSuperSpeed == fSuperSpeed) + { + /* Check all assigned busses for a speed match. */ + for (unsigned idxBus = 0; idxBus < pHcd->cBusses; idxBus++) + { + if (pHcd->paBusses[idxBus].fSuperSpeed == fSuperSpeed) + { + rc = VINF_SUCCESS; + int cbRet = RTStrAPrintf(ppszUdc, "%s.%u", pHcd->pszUdcName, pHcd->idxDummyHcd); + if (cbRet == -1) + rc = VERR_NO_STR_MEMORY; + *puBusId = pHcd->paBusses[idxBus].uBusId; + pHcd->fAvailable = false; + break; + } + } + + if (rc != VERR_NOT_FOUND) + break; + } + } + + return rc; +} + + +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc) +{ + int rc = VERR_INVALID_PARAMETER; + const char *pszIdx = RTStrStr(pszUdc, "."); + if (pszIdx) + { + size_t cchUdcName = pszIdx - pszUdc; + pszIdx++; + uint32_t idxHcd = 0; + rc = RTStrToUInt32Ex(pszIdx, NULL, 10, &idxHcd); + if (RT_SUCCESS(rc)) + { + rc = VERR_NOT_FOUND; + + for (unsigned i = 0; i < g_cDummyHcd; i++) + { + if ( g_paDummyHcd[i].idxDummyHcd == idxHcd + && !RTStrNCmp(g_paDummyHcd[i].pszUdcName, pszUdc, cchUdcName)) + { + AssertReturn(!g_paDummyHcd[i].fAvailable, VERR_INVALID_PARAMETER); + g_paDummyHcd[i].fAvailable = true; + rc = VINF_SUCCESS; + break; + } + } + } + } + + return rc; +} + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h new file mode 100644 index 00000000..cf5eed26 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform.h @@ -0,0 +1,105 @@ +/* $Id: UsbTestServicePlatform.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Platform specific helpers. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> + +RT_C_DECLS_BEGIN + +/** + * Initializes the platform specific structures for UTS. + * + * @returns IPRT status code. + */ +DECLHIDDEN(int) utsPlatformInit(void); + +/** + * Frees all platform specific structures for UTS. + */ +DECLHIDDEN(void) utsPlatformTerm(void); + +/** + * Loads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to load. + * @param papszArgv Array of arguments to pass to the module. + * @param cArgv Number of argument array entries. + */ +DECLHIDDEN(int) utsPlatformModuleLoad(const char *pszModule, const char **papszArgv, + unsigned cArgv); + +/** + * Unloads the specified kernel module on the platform. + * + * @returns IPRT status code. + * @param pszModule The module to unload. + */ +DECLHIDDEN(int) utsPlatformModuleUnload(const char *pszModule); + +#ifdef RT_OS_LINUX + +/** + * Acquires a free UDC to attach a gadget to. + * + * @returns IPRT status code. + * @param fSuperSpeed Flag whether a super speed bus is required. + * @param ppszUdc Where to store the pointer to the name of the UDC on success. + * Free with RTStrFree(). + * @param puBusId Where to store the bus ID the UDC is attached to on the host side. + */ +DECLHIDDEN(int) utsPlatformLnxAcquireUDC(bool fSuperSpeed, char **ppszUdc, uint32_t *puBusId); + +/** + * Releases the given UDC for other use. + * + * @returns IPRT status code. + * @param pszUdc The UDC to release. + */ +DECLHIDDEN(int) utsPlatformLnxReleaseUDC(const char *pszUdc); + +#endif + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServicePlatform_h */ + diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp new file mode 100644 index 00000000..a32e8a3e --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.cpp @@ -0,0 +1,120 @@ +/* $Id: UsbTestServiceProtocol.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, Protocol helpers. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/cdefs.h> + +#include "UsbTestServiceProtocol.h" + + + +/** + * Converts a UTS packet header from host to network byte order. + * + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrH2N(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_H2N_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_H2N_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS packet header from network to host byte order. + * + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolPktHdrN2H(PUTSPKTHDR pPktHdr) +{ + pPktHdr->cb = RT_N2H_U32(pPktHdr->cb); + pPktHdr->uCrc32 = RT_N2H_U32(pPktHdr->uCrc32); +} + + +/** + * Converts a UTS status header from host to network byte order. + * + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrH2N(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrH2N(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_H2N_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_H2N_U32(pPktHdr->cchStsMsg); +} + + +/** + * Converts a UTS status header from network to host byte order. + * + * @param pPktHdr The packet header to convert. + */ +DECLINLINE(void) utsProtocolStsHdrN2H(PUTSPKTSTS pPktHdr) +{ + utsProtocolPktHdrN2H(&pPktHdr->Hdr); + pPktHdr->rcReq = RT_N2H_U32(pPktHdr->rcReq); + pPktHdr->cchStsMsg = RT_N2H_U32(pPktHdr->cchStsMsg); +} + + +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr) +{ + utsProtocolPktHdrH2N(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} + + +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTSTS pPktHdr) +{ + RT_NOREF1(pPktHdr); +} diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h new file mode 100644 index 00000000..5378493b --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceProtocol.h @@ -0,0 +1,373 @@ +/* $Id: UsbTestServiceProtocol.h $ */ +/** @file + * UsbTestServ - Remote USB test configuration and execution server, Protocol Header. + */ + +/* + * Copyright (C) 2016-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#define VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> + +RT_C_DECLS_BEGIN + +/** + * Common Packet header (for requests and replies). + */ +typedef struct UTSPKTHDR +{ + /** The unpadded packet length. This include this header. */ + uint32_t cb; + /** The CRC-32 for the packet starting from the opcode field. 0 if the packet + * hasn't been CRCed. */ + uint32_t uCrc32; + /** Packet opcode, an unterminated ASCII string. */ + uint8_t achOpcode[8]; +} UTSPKTHDR; +AssertCompileSize(UTSPKTHDR, 16); +/** Pointer to a packet header. */ +typedef UTSPKTHDR *PUTSPKTHDR; +/** Pointer to a packet header. */ +typedef UTSPKTHDR const *PCUTSPKTHDR; +/** Pointer to a packet header pointer. */ +typedef PUTSPKTHDR *PPUTSPKTHDR; + +/** Packet alignment. */ +#define UTSPKT_ALIGNMENT 16 +/** Max packet size. */ +#define UTSPKT_MAX_SIZE _256K + +/** + * Status packet. + */ +typedef struct UTSPKTSTS +{ + /** Embedded common packet header. */ + UTSPKTHDR Hdr; + /** The IPRT status code of the request. */ + int32_t rcReq; + /** Size of the optional status message following this structure - + * only for errors. */ + uint32_t cchStsMsg; + /** Padding - reserved. */ + uint8_t au8Padding[8]; +} UTSPKTSTS; +AssertCompileSizeAlignment(UTSPKTSTS, UTSPKT_ALIGNMENT); +/** Pointer to a status packet header. */ +typedef UTSPKTSTS *PUTSPKTSTS; + +#define UTSPKT_OPCODE_HOWDY "HOWDY " + +/** 32bit protocol version consisting of a 16bit major and 16bit minor part. */ +#define UTS_PROTOCOL_VS (UTS_PROTOCOL_VS_MAJOR | UTS_PROTOCOL_VS_MINOR) +/** The major version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MAJOR (1 << 16) +/** The minor version part of the protocol version. */ +#define UTS_PROTOCOL_VS_MINOR (0) + +/** + * The HOWDY request structure. + */ +typedef struct UTSPKTREQHOWDY +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Version of the protocol the client wants to use. */ + uint32_t uVersion; + /** Mask of USB device connections the client wants to use. */ + uint32_t fUsbConn; + /** The number of characters for the hostname. */ + uint32_t cchHostname; + /** The client host name as terminated ASCII string. */ + char achHostname[68]; +} UTSPKTREQHOWDY; +AssertCompileSizeAlignment(UTSPKTREQHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY request structure. */ +typedef UTSPKTREQHOWDY *PUTSPKTREQHOWDY; + +/** + * The HOWDY reply structure. + */ +typedef struct UTSPKTREPHOWDY +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** Version to use for the established connection. */ + uint32_t uVersion; + /** Mask of supported USB device connections for this connection. */ + uint32_t fUsbConn; + /** Port number the USB/IP server is listening on if + * the client requested USB/IP support and the server can + * deliver it. */ + uint32_t uUsbIpPort; + /** Maximum number of devices supported over USB/IP + * at the same time. */ + uint32_t cUsbIpDevices; + /** Maximum number of physical devices supported for this client + * if a physical connection is present. */ + uint32_t cPhysicalDevices; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREPHOWDY; +AssertCompileSizeAlignment(UTSPKTREPHOWDY, UTSPKT_ALIGNMENT); +/** Pointer to a HOWDY reply structure. */ +typedef UTSPKTREPHOWDY *PUTSPKTREPHOWDY; + +/** Connections over USB/IP are supported. */ +#define UTSPKT_HOWDY_CONN_F_USBIP RT_BIT_32(0) +/** The server has a physical connection available to the client + * which can be used for testing. */ +#define UTSPKT_HOWDY_CONN_F_PHYSICAL RT_BIT_32(1) + + +#define UTSPKT_OPCODE_BYE "BYE " + +/* No additional structures for BYE. */ + +#define UTSPKT_OPCODE_GADGET_CREATE "GDGTCRT " + +/** + * The GADGET CREATE request structure. + */ +typedef struct UTSPKTREQGDGTCTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget type. */ + uint32_t u32GdgtType; + /** Access methods. */ + uint32_t u32GdgtAccess; + /** Number of config items - following this structure. */ + uint32_t u32CfgItems; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREQGDGTCTOR *PUTSPKTREQGDGTCTOR; + +/** Gadget type - Test device. */ +#define UTSPKT_GDGT_CREATE_TYPE_TEST UINT32_C(0x1) + +/** Gadget acess method - USB/IP. */ +#define UTSPKT_GDGT_CREATE_ACCESS_USBIP UINT32_C(0x1) + +/** + * Configuration item. + */ +typedef struct UTSPKTREQGDGTCTORCFGITEM +{ + /** Size of the key incuding termination in bytes. */ + uint32_t u32KeySize; + /** Item type. */ + uint32_t u32Type; + /** Size of the value string including termination in bytes. */ + uint32_t u32ValSize; + /** Reserved. */ + uint32_t u32Rsvd0; +} UTSPKTREQGDGTCTORCFGITEM; +AssertCompileSizeAlignment(UTSPKTREQGDGTCTORCFGITEM, UTSPKT_ALIGNMENT); +/** Pointer to a configuration item. */ +typedef UTSPKTREQGDGTCTORCFGITEM *PUTSPKTREQGDGTCTORCFGITEM; + +/** Boolean configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_BOOLEAN UINT32_C(1) +/** String configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_STRING UINT32_C(2) +/** Unsigned 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT8 UINT32_C(3) +/** Unsigned 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT16 UINT32_C(4) +/** Unsigned 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT32 UINT32_C(5) +/** Unsigned 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_UINT64 UINT32_C(6) +/** Signed 8-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT8 UINT32_C(7) +/** Signed 16-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT16 UINT32_C(8) +/** Signed 32-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT32 UINT32_C(9) +/** Signed 64-bit integer configuration item type. */ +#define UTSPKT_GDGT_CFG_ITEM_TYPE_INT64 UINT32_C(10) + +/** + * The GADGET CREATE reply structure. + */ +typedef struct UTSPKTREPGDGTCTOR +{ + /** Status packet. */ + UTSPKTSTS Sts; + /** The gadget ID on success. */ + uint32_t idGadget; + /** Bus ID the gadget is attached to */ + uint32_t u32BusId; + /** Device ID of the gadget on the bus. */ + uint32_t u32DevId; + /** Padding - reserved. */ + uint8_t au8Padding[4]; +} UTSPKTREPGDGTCTOR; +AssertCompileSizeAlignment(UTSPKTREPGDGTCTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CREATE structure. */ +typedef UTSPKTREPGDGTCTOR *PUTSPKTREPGDGTCTOR; + + +#define UTSPKT_OPCODE_GADGET_DESTROY "GDGTDTOR" + +/** + * The GADGET DESTROY request structure. + */ +typedef struct UTSPKTREQGDGTDTOR +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDTOR; +AssertCompileSizeAlignment(UTSPKTREQGDGTDTOR, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET DESTROY structure. */ +typedef UTSPKTREQGDGTDTOR *PUTSPKTREQGDGTDTOR; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_CONNECT "GDGTCNCT" + +/** + * The GADGET CONNECT request structure. + */ +typedef struct UTSPKTREQGDGTCNCT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTCNCT; +AssertCompileSizeAlignment(UTSPKTREQGDGTCNCT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTCNCT *PUTSPKTREQGDGTCNCT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +#define UTSPKT_OPCODE_GADGET_DISCONNECT "GDGTDCNT" + +/** + * The GADGET DISCONNECT request structure. + */ +typedef struct UTSPKTREQGDGTDCNT +{ + /** Embedded packet header. */ + UTSPKTHDR Hdr; + /** Gadget ID as returned from the GADGET CREATE request on success. */ + uint32_t idGadget; + /** Padding - reserved. */ + uint8_t au8Padding[12]; +} UTSPKTREQGDGTDCNT; +AssertCompileSizeAlignment(UTSPKTREQGDGTDCNT, UTSPKT_ALIGNMENT); +/** Pointer to a GADGET CONNECT request structure. */ +typedef UTSPKTREQGDGTDCNT *PUTSPKTREQGDGTDCNT; + +/* No additional structure for the reply (just standard STATUS packet). */ + +/** + * Checks if the two opcodes match. + * + * @returns true on match, false on mismatch. + * @param pPktHdr The packet header. + * @param pszOpcode2 The opcode we're comparing with. Does not have + * to be the whole 8 chars long. + */ +DECLINLINE(bool) utsIsSameOpcode(PCUTSPKTHDR pPktHdr, const char *pszOpcode2) +{ + if (pPktHdr->achOpcode[0] != pszOpcode2[0]) + return false; + if (pPktHdr->achOpcode[1] != pszOpcode2[1]) + return false; + + unsigned i = 2; + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] != '\0') + { + if (pPktHdr->achOpcode[i] != pszOpcode2[i]) + break; + i++; + } + + if ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pszOpcode2[i] == '\0') + { + while ( i < RT_SIZEOFMEMB(UTSPKTHDR, achOpcode) + && pPktHdr->achOpcode[i] == ' ') + i++; + } + + return i == RT_SIZEOFMEMB(UTSPKTHDR, achOpcode); +} + +/** + * Converts a UTS request packet from host to network byte ordering. + * + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS request packet from network to host byte ordering. + * + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolReqN2H(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from host to network byte ordering. + * + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepH2N(PUTSPKTHDR pPktHdr); + +/** + * Converts a UTS reply packet from network to host byte ordering. + * + * @param pPktHdr The packet to convert. + */ +DECLHIDDEN(void) utsProtocolRepN2H(PUTSPKTHDR pPktHdr); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_usb_UsbTestServiceProtocol_h */ diff --git a/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp new file mode 100644 index 00000000..4d9013f7 --- /dev/null +++ b/src/VBox/ValidationKit/utils/usb/UsbTestServiceTcp.cpp @@ -0,0 +1,512 @@ +/* $Id: UsbTestServiceTcp.cpp $ */ +/** @file + * UsbTestService - Remote USB test configuration and execution server, TCP/IP Transport Layer. + */ + +/* + * Copyright (C) 2010-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox 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. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP RTLOGGROUP_DEFAULT +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/critsect.h> +#include <iprt/err.h> +#include <iprt/log.h> +#include <iprt/mem.h> +#include <iprt/message.h> +#include <iprt/poll.h> +#include <iprt/string.h> +#include <iprt/tcp.h> +#include <iprt/thread.h> +#include <iprt/time.h> + +#include "UsbTestServiceInternal.h" + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** The default server port. */ +#define UTS_TCP_DEF_BIND_PORT 6042 +/** The default server bind address. */ +#define UTS_TCP_DEF_BIND_ADDRESS "" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ + +/** + * TCP specific client data. + */ +typedef struct UTSTRANSPORTCLIENT +{ + /** Socket of the current client. */ + RTSOCKET hTcpClient; + /** The size of the stashed data. */ + size_t cbTcpStashed; + /** The size of the stashed data allocation. */ + size_t cbTcpStashedAlloced; + /** The stashed data. */ + uint8_t *pbTcpStashed; +} UTSTRANSPORTCLIENT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** @name TCP Parameters + * @{ */ +/** The addresses to bind to. Empty string means any. */ +static char g_szTcpBindAddr[256] = UTS_TCP_DEF_BIND_ADDRESS; +/** The TCP port to listen to. */ +static uint32_t g_uTcpBindPort = UTS_TCP_DEF_BIND_PORT; +/** @} */ + +/** Pointer to the TCP server instance. */ +static PRTTCPSERVER g_pTcpServer = NULL; +#if 0 /* unused */ +/** Stop connecting attempts when set. */ +static bool g_fTcpStopConnecting = false; +#endif + + + +/** + * Disconnects the current client and frees all stashed data. + */ +static void utsTcpDisconnectClient(PUTSTRANSPORTCLIENT pClient) +{ + if (pClient->hTcpClient != NIL_RTSOCKET) + { + int rc = RTTcpServerDisconnectClient2(pClient->hTcpClient); + pClient->hTcpClient = NIL_RTSOCKET; + AssertRCSuccess(rc); + } + + if (pClient->pbTcpStashed) + { + RTMemFree(pClient->pbTcpStashed); + pClient->pbTcpStashed = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnWaitForConnect} + */ +static DECLCALLBACK(int) utsTcpWaitForConnect(PPUTSTRANSPORTCLIENT ppClientNew) +{ + int rc; + RTSOCKET hClientNew; + + rc = RTTcpServerListen2(g_pTcpServer, &hClientNew); + Log(("utsTcpWaitForConnect: RTTcpServerListen2 -> %Rrc\n", rc)); + + if (RT_SUCCESS(rc)) + { + PUTSTRANSPORTCLIENT pClient = (PUTSTRANSPORTCLIENT)RTMemAllocZ(sizeof(UTSTRANSPORTCLIENT)); + if (RT_LIKELY(pClient)) + { + pClient->hTcpClient = hClientNew; + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + *ppClientNew = pClient; + } + else + { + RTTcpServerDisconnectClient2(hClientNew); + rc = VERR_NO_MEMORY; + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyReboot} + */ +static DECLCALLBACK(void) utsTcpNotifyReboot(void) +{ + Log(("utsTcpNotifyReboot: RTTcpServerDestroy(%p)\n", g_pTcpServer)); + if (g_pTcpServer) + { + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpNotifyReboot: %Rrc", rc); + g_pTcpServer = NULL; + } +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyBye} + */ +static DECLCALLBACK(void) utsTcpNotifyBye(PUTSTRANSPORTCLIENT pClient) +{ + Log(("utsTcpNotifyBye: utsTcpDisconnectClient %RTsock\n", pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + RTMemFree(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnNotifyHowdy} + */ +static DECLCALLBACK(void) utsTcpNotifyHowdy(PUTSTRANSPORTCLIENT pClient) +{ + /* nothing to do here */ + RT_NOREF1(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnBabble} + */ +static DECLCALLBACK(void) utsTcpBabble(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr, RTMSINTERVAL cMsSendTimeout) +{ + /* + * Try send the babble reply. + */ + NOREF(cMsSendTimeout); /** @todo implement the timeout here; non-blocking write + select-on-write. */ + int rc; + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + do rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + while (rc == VERR_INTERRUPTED); + + /* + * Disconnect the client. + */ + Log(("utsTcpBabble: utsTcpDisconnectClient(%RTsock) (RTTcpWrite rc=%Rrc)\n", pClient->hTcpClient, rc)); + utsTcpDisconnectClient(pClient); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnSendPkt} + */ +static DECLCALLBACK(int) utsTcpSendPkt(PUTSTRANSPORTCLIENT pClient, PCUTSPKTHDR pPktHdr) +{ + Assert(pPktHdr->cb >= sizeof(UTSPKTHDR)); + + /* + * Write it. + */ + size_t cbToSend = RT_ALIGN_Z(pPktHdr->cb, UTSPKT_ALIGNMENT); + int rc = RTTcpWrite(pClient->hTcpClient, pPktHdr, cbToSend); + if ( RT_FAILURE(rc) + && rc != VERR_INTERRUPTED) + { + /* assume fatal connection error. */ + Log(("RTTcpWrite -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnRecvPkt} + */ +static DECLCALLBACK(int) utsTcpRecvPkt(PUTSTRANSPORTCLIENT pClient, PPUTSPKTHDR ppPktHdr) +{ + int rc = VINF_SUCCESS; + *ppPktHdr = NULL; + + /* + * Read state. + */ + size_t offData = 0; + size_t cbData = 0; + size_t cbDataAlloced; + uint8_t *pbData = NULL; + + /* + * Any stashed data? + */ + if (pClient->cbTcpStashedAlloced) + { + offData = pClient->cbTcpStashed; + cbDataAlloced = pClient->cbTcpStashedAlloced; + pbData = pClient->pbTcpStashed; + + pClient->cbTcpStashed = 0; + pClient->cbTcpStashedAlloced = 0; + pClient->pbTcpStashed = NULL; + } + else + { + cbDataAlloced = RT_ALIGN_Z(64, UTSPKT_ALIGNMENT); + pbData = (uint8_t *)RTMemAlloc(cbDataAlloced); + if (!pbData) + return VERR_NO_MEMORY; + } + + /* + * Read and valid the length. + */ + while (offData < sizeof(uint32_t)) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, sizeof(uint32_t) - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#1)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + if (RT_SUCCESS(rc)) + { + ASMCompilerBarrier(); /* paranoia^3 */ + cbData = *(uint32_t volatile *)pbData; + if (cbData >= sizeof(UTSPKTHDR) && cbData <= UTSPKT_MAX_SIZE) + { + /* + * Align the length and reallocate the return packet it necessary. + */ + cbData = RT_ALIGN_Z(cbData, UTSPKT_ALIGNMENT); + if (cbData > cbDataAlloced) + { + void *pvNew = RTMemRealloc(pbData, cbData); + if (pvNew) + { + pbData = (uint8_t *)pvNew; + cbDataAlloced = cbData; + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + /* + * Read the remainder of the data. + */ + while (offData < cbData) + { + size_t cbRead; + rc = RTTcpRead(pClient->hTcpClient, pbData + offData, cbData - offData, &cbRead); + if (RT_FAILURE(rc)) + break; + if (cbRead == 0) + { + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc / cbRead=0 -> VERR_NET_NOT_CONNECTED (#2)\n", rc)); + rc = VERR_NET_NOT_CONNECTED; + break; + } + offData += cbRead; + } + } + } + else + rc = VERR_NET_PROTOCOL_ERROR; + } + if (RT_SUCCESS(rc)) + *ppPktHdr = (PUTSPKTHDR)pbData; + else + { + /* + * Deal with errors. + */ + if (rc == VERR_INTERRUPTED) + { + /* stash it away for the next call. */ + pClient->cbTcpStashed = cbData; + pClient->cbTcpStashedAlloced = cbDataAlloced; + pClient->pbTcpStashed = pbData; + } + else + { + RTMemFree(pbData); + + /* assume fatal connection error. */ + Log(("utsTcpRecvPkt: RTTcpRead -> %Rrc -> utsTcpDisconnectClient(%RTsock)\n", rc, pClient->hTcpClient)); + utsTcpDisconnectClient(pClient); + } + } + + return rc; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetAdd} + */ +static DECLCALLBACK(int) utsTcpPollSetAdd(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + return RTPollSetAddSocket(hPollSet, pClient->hTcpClient, RTPOLL_EVT_READ | RTPOLL_EVT_ERROR, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollSetRemove} + */ +static DECLCALLBACK(int) utsTcpPollSetRemove(RTPOLLSET hPollSet, PUTSTRANSPORTCLIENT pClient, uint32_t idStart) +{ + RT_NOREF1(pClient); + return RTPollSetRemove(hPollSet, idStart); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnPollIn} + */ +static DECLCALLBACK(bool) utsTcpPollIn(PUTSTRANSPORTCLIENT pClient) +{ + int rc = RTTcpSelectOne(pClient->hTcpClient, 0/*cMillies*/); + return RT_SUCCESS(rc); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnTerm} + */ +static DECLCALLBACK(void) utsTcpTerm(void) +{ + /* Shut down the server (will wake up thread). */ + if (g_pTcpServer) + { + Log(("utsTcpTerm: Destroying server...\n")); + int rc = RTTcpServerDestroy(g_pTcpServer); + if (RT_FAILURE(rc)) + RTMsgInfo("RTTcpServerDestroy failed in utsTcpTerm: %Rrc", rc); + g_pTcpServer = NULL; + } + + Log(("utsTcpTerm: done\n")); +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnInit} + */ +static DECLCALLBACK(int) utsTcpInit(void) +{ + int rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + if (RT_FAILURE(rc)) + { + if (rc == VERR_NET_DOWN) + { + RTMsgInfo("RTTcpServerCreateEx(%s, %u,) failed: %Rrc, retrying for 20 seconds...\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + uint64_t StartMs = RTTimeMilliTS(); + do + { + RTThreadSleep(1000); + rc = RTTcpServerCreateEx(g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, &g_pTcpServer); + } while ( rc == VERR_NET_DOWN + && RTTimeMilliTS() - StartMs < 20000); + if (RT_SUCCESS(rc)) + RTMsgInfo("RTTcpServerCreateEx succceeded.\n"); + } + if (RT_FAILURE(rc)) + { + g_pTcpServer = NULL; + RTMsgError("RTTcpServerCreateEx(%s, %u,) failed: %Rrc\n", + g_szTcpBindAddr[0] ? g_szTcpBindAddr : NULL, g_uTcpBindPort, rc); + } + } + + return rc; +} + +/** Options */ +enum UTSTCPOPT +{ + UTSTCPOPT_BIND_ADDRESS = 1000, + UTSTCPOPT_BIND_PORT +}; + +/** + * @interface_method_impl{UTSTRANSPORT,pfnOption} + */ +static DECLCALLBACK(int) utsTcpOption(int ch, PCRTGETOPTUNION pVal) +{ + int rc; + + switch (ch) + { + case UTSTCPOPT_BIND_ADDRESS: + rc = RTStrCopy(g_szTcpBindAddr, sizeof(g_szTcpBindAddr), pVal->psz); + if (RT_FAILURE(rc)) + return RTMsgErrorRc(VERR_INVALID_PARAMETER, "TCP bind address is too long (%Rrc)", rc); + return VINF_SUCCESS; + + case UTSTCPOPT_BIND_PORT: + g_uTcpBindPort = pVal->u16 == 0 ? UTS_TCP_DEF_BIND_PORT : pVal->u16; + return VINF_SUCCESS; + } + return VERR_TRY_AGAIN; +} + +/** + * @interface_method_impl{UTSTRANSPORT,pfnUsage} + */ +DECLCALLBACK(void) utsTcpUsage(PRTSTREAM pStream) +{ + RTStrmPrintf(pStream, + " --tcp-bind-address <address>\n" + " The address(es) to listen to TCP connection on. Empty string\n" + " means any address, this is the default.\n" + " --tcp-bind-port <port>\n" + " The port to listen to TCP connections on.\n" + " Default: %u\n" + , UTS_TCP_DEF_BIND_PORT); +} + +/** Command line options for the TCP/IP transport layer. */ +static const RTGETOPTDEF g_TcpOpts[] = +{ + { "--tcp-bind-address", UTSTCPOPT_BIND_ADDRESS, RTGETOPT_REQ_STRING }, + { "--tcp-bind-port", UTSTCPOPT_BIND_PORT, RTGETOPT_REQ_UINT16 } +}; + +/** TCP/IP transport layer. */ +const UTSTRANSPORT g_TcpTransport = +{ + /* .szName = */ "tcp", + /* .pszDesc = */ "TCP/IP", + /* .cOpts = */ &g_TcpOpts[0], + /* .paOpts = */ RT_ELEMENTS(g_TcpOpts), + /* .pfnUsage = */ utsTcpUsage, + /* .pfnOption = */ utsTcpOption, + /* .pfnInit = */ utsTcpInit, + /* .pfnTerm = */ utsTcpTerm, + /* .pfnWaitForConnect = */ utsTcpWaitForConnect, + /* .pfnPollIn = */ utsTcpPollIn, + /* .pfnPollSetAdd = */ utsTcpPollSetAdd, + /* .pfnPollSetRemove = */ utsTcpPollSetRemove, + /* .pfnRecvPkt = */ utsTcpRecvPkt, + /* .pfnSendPkt = */ utsTcpSendPkt, + /* .pfnBabble = */ utsTcpBabble, + /* .pfnNotifyHowdy = */ utsTcpNotifyHowdy, + /* .pfnNotifyBye = */ utsTcpNotifyBye, + /* .pfnNotifyReboot = */ utsTcpNotifyReboot, + /* .u32EndMarker = */ UINT32_C(0x12345678) +}; |