diff options
Diffstat (limited to 'src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp')
-rw-r--r-- | src/VBox/ValidationKit/utils/usb/UsbTestServicePlatform-linux.cpp | 448 |
1 files changed, 448 insertions, 0 deletions
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; +} + |