From e6f7b24ccad366704b2b91f88c0a37e407c99c52 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 1 Jul 2025 08:11:57 +0200 Subject: [PATCH 01/78] Added gadgets --- examples/CMakeLists.txt | 3 + examples/compile.sh | 3 + examples/header.type | 0 examples/xlink_usb_client | Bin 0 -> 542096 bytes examples/xlink_usb_client.cpp | 54 +++++++++ examples/xlink_usb_server.cpp | 62 ++++++++++ include/XLink/XLinkPublicDefines.h | 1 + src/pc/PlatformData.c | 7 ++ src/pc/PlatformDeviceControl.c | 11 ++ src/pc/protocols/usb_host_ep.cpp | 183 +++++++++++++++++++++++++++++ src/pc/protocols/usb_host_ep.h | 24 ++++ src/shared/XLinkDevice.c | 1 + 12 files changed, 349 insertions(+) create mode 100755 examples/compile.sh create mode 100644 examples/header.type create mode 100755 examples/xlink_usb_client create mode 100644 examples/xlink_usb_client.cpp create mode 100644 examples/xlink_usb_server.cpp create mode 100644 src/pc/protocols/usb_host_ep.cpp create mode 100644 src/pc/protocols/usb_host_ep.h diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 65b690e8..2330b091 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -54,3 +54,6 @@ if (UNIX AND !APPLE) add_example(xlink_client_local xlink_client_local.cpp) endif (UNIX AND !APPLE) +# EP example +add_example(xlink_usb_server xlink_usb_server.cpp) +add_example(xlink_usb_client xlink_usb_client.cpp) diff --git a/examples/compile.sh b/examples/compile.sh new file mode 100755 index 00000000..4ba5603c --- /dev/null +++ b/examples/compile.sh @@ -0,0 +1,3 @@ +g++ xlink_usb_server.cpp -g -I../include -L../build -lXLink -lc -lusb-1.0 -o xlink_usb_server +g++ xlink_usb_client.cpp -g -I../include -L../build -lXLink -lc -lusb-1.0 -o xlink_usb_client + diff --git a/examples/header.type b/examples/header.type new file mode 100644 index 00000000..e69de29b diff --git a/examples/xlink_usb_client b/examples/xlink_usb_client new file mode 100755 index 0000000000000000000000000000000000000000..4bf052f46b2640c7a3c1e5f257655180ef3561fc GIT binary patch literal 542096 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "XLink/XLink.h" +#include "XLink/XLinkPublicDefines.h" +#include "XLink/XLinkLog.h" + +// Common constants +const uint8_t DUMMY_DATA[1024*128] = {}; + +int main(int argc, char** argv) { + XLinkGlobalHandler_t gHandler; + XLinkInitialize(&gHandler); + + mvLogDefaultLevelSet(MVLOG_DEBUG); + + deviceDesc_t deviceDesc; + strcpy(deviceDesc.name, "usbdev"); + deviceDesc.protocol = X_LINK_USB_EP; + + printf("Device name: %s\n", deviceDesc.name); + + XLinkHandler_t handler; + handler.devicePath = deviceDesc.name; + handler.protocol = deviceDesc.protocol; + auto connRet = XLinkConnect(&handler); + printf("Connection returned: %s\n", XLinkErrorToStr(connRet)); + if(connRet != X_LINK_SUCCESS) { + return -1; + } + + auto s = XLinkOpenStream(handler.linkId, "test_0", sizeof(DUMMY_DATA) * 2); + if(s == INVALID_STREAM_ID){ + printf("Open stream failed...\n"); + } else { + printf("Open stream OK - id: 0x%08X\n", s); + } + + auto w = XLinkWriteData(s, (uint8_t*) &s, sizeof(s)); + assert(w == X_LINK_SUCCESS); + + return 0; +} diff --git a/examples/xlink_usb_server.cpp b/examples/xlink_usb_server.cpp new file mode 100644 index 00000000..2688e2a4 --- /dev/null +++ b/examples/xlink_usb_server.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "XLink/XLink.h" +#include "XLink/XLinkPublicDefines.h" +#include "XLink/XLinkLog.h" + +// Common constants +constexpr static auto NUM_STREAMS = 16; +constexpr static auto NUM_PACKETS = 120; +const uint8_t DUMMY_DATA[1024*128] = {}; +XLinkGlobalHandler_t xlinkGlobalHandler = {}; + +// Server +// + +int main(int argc, const char** argv){ + + xlinkGlobalHandler.protocol = X_LINK_USB_EP; + + // Initialize and suppress XLink logs + mvLogDefaultLevelSet(MVLOG_DEBUG); + auto status = XLinkInitialize(&xlinkGlobalHandler); + if(X_LINK_SUCCESS != status) { + throw std::runtime_error("Couldn't initialize XLink"); + } + + XLinkHandler_t handler; + handler.devicePath = "/dev/usb-ffs/xlink"; + handler.protocol = X_LINK_USB_EP; + XLinkServer(&handler, "eps", X_LINK_BOOTED, X_LINK_MYRIAD_X); + + + // loop through streams + auto s = XLinkOpenStream(0, "test_0", sizeof(DUMMY_DATA) * 2); + assert(s != INVALID_STREAM_ID); + + // auto w = XLinkWriteData2(s, (uint8_t*) &s, sizeof(s/2), ((uint8_t*) &s) + sizeof(s/2), sizeof(s) - sizeof(s/2)); + // assert(w == X_LINK_SUCCESS); + + auto w = XLinkWriteData(s, (uint8_t*) &s, sizeof(s)); + assert(w == X_LINK_SUCCESS); + + + streamPacketDesc_t p; + w = XLinkReadMoveData(s, &p); + assert(w == X_LINK_SUCCESS); + XLinkDeallocateMoveData(p.data, p.length); + + return 0; +} diff --git a/include/XLink/XLinkPublicDefines.h b/include/XLink/XLinkPublicDefines.h index f459b863..95bafa50 100644 --- a/include/XLink/XLinkPublicDefines.h +++ b/include/XLink/XLinkPublicDefines.h @@ -67,6 +67,7 @@ typedef enum{ X_LINK_TCP_IP, X_LINK_LOCAL_SHDMEM, X_LINK_TCP_IP_OR_LOCAL_SHDMEM, + X_LINK_USB_EP, X_LINK_NMB_OF_PROTOCOLS, X_LINK_ANY_PROTOCOL } XLinkProtocol_t; diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index c56cc899..00b17d72 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -15,6 +15,7 @@ #include "pcie_host.h" #include "tcpip_host.h" #include "local_memshd.h" +#include "usb_host_ep.h" #include "PlatformDeviceFd.h" #include "inttypes.h" @@ -99,6 +100,9 @@ int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) case X_LINK_LOCAL_SHDMEM: return shdmemPlatformWrite(deviceHandle->xLinkFD, data, size); #endif + case X_LINK_USB_EP: + return usbEpPlatformWrite(deviceHandle->xLinkFD, data, size); + case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to write with TCP_IP_OR_LOCAL_SHDMEM\n"); @@ -146,6 +150,9 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l case X_LINK_LOCAL_SHDMEM: return shdmemPlatformRead(deviceHandle->xLinkFD, data, size, fd); #endif + case X_LINK_USB_EP: + return usbEpPlatformRead(deviceHandle->xLinkFD, data, size); + case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to read with TCP_IP_OR_LOCAL_SHDMEM\n"); default: diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 6bd45fd6..d03e0943 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -12,6 +12,7 @@ #include "tcpip_host.h" #include "local_memshd.h" #include "tcpip_memshd.h" +#include "usb_host_ep.h" #include "XLinkStringUtils.h" #include "PlatformDeviceFd.h" @@ -103,6 +104,10 @@ xLinkPlatformErrorCode_t XLinkPlatformInit(XLinkGlobalHandler_t* globalHandler) xlinkSetProtocolInitialized(X_LINK_LOCAL_SHDMEM, 0); } #endif + + if (usbEpInitialize() != 0) { + xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); + } xlinkSetProtocolInitialized(X_LINK_TCP_IP_OR_LOCAL_SHDMEM, 1); @@ -200,6 +205,8 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha case X_LINK_LOCAL_SHDMEM: return shdmemPlatformConnect(devPathRead, devPathWrite, fd); #endif + case X_LINK_USB_EP: + return usbEpPlatformConnect(devPathRead, devPathWrite, fd); default: return X_LINK_PLATFORM_INVALID_PARAMETERS; @@ -220,6 +227,10 @@ xLinkPlatformErrorCode_t XLinkPlatformServer(const char* devPathRead, const char return shdmemPlatformServer(devPathRead, devPathWrite, fd, NULL); #endif + case X_LINK_USB_EP: + return usbEpPlatformServer(devPathRead, devPathWrite, fd); + + default: return X_LINK_PLATFORM_INVALID_PARAMETERS; } diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp new file mode 100644 index 00000000..de74adc5 --- /dev/null +++ b/src/pc/protocols/usb_host_ep.cpp @@ -0,0 +1,183 @@ +// project +#include +#define MVLOG_UNIT_NAME xLinkUsb + +#include "XLink/XLinkLog.h" +#include "XLink/XLinkPlatform.h" +#include "XLink/XLinkPublicDefines.h" +#include "usb_host_ep.h" +#include "../PlatformDeviceFd.h" + +#include +#include +#include + +#include + +/* Vendor ID */ +#define VENDOR_ID 0x03e7 + +/* Product ID */ +#define PRODUCT_ID 0x1234 + +/* Interface number for ffs.xlink */ +#define INTERFACE_XLINK 2 + +/* Base ndpoint address used for output */ +#define ENDPOINT_OUT_BASE 0x01 + +/* Base endpoint address used for input */ +#define ENDPOINT_IN_BASE 0x81 + +/* Transfer timeout */ +#define TIMEOUT 2000 + +static int usbFdRead, usbFdWrite; +static bool isServer; + +static libusb_context *ctx = NULL; +static libusb_device_handle *dev_handle = NULL; + +int usbEpInitialize() { + int error; + + /* Initialize libusb */ + libusb_init(&ctx); + + return 0; +} + +int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) +{ + int error; + isServer = false; + + /* Get our device */ + dev_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); + if (dev_handle == NULL) { + libusb_exit(ctx); + + error = LIBUSB_ERROR_NO_DEVICE; + return error; + } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + if (error != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return error; + } + + /* Now we claim our ffs interfaces */ + error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); + if (error != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return error; + } + + /* We get the first EP_OUT and EP_IN for our interfaces + * In the way we initialized our usb-gadget on our device + * We know ncm is claiming the first 2 interfaces + */ + usbFdWrite = ENDPOINT_OUT_BASE + 1; /* +1 because NCM claims 1 OUT endpoint */ + usbFdRead = ENDPOINT_IN_BASE + 2; /* +2 because NCM claims 2 IN endpoints */ + + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); + + return 0; +} + +int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) +{ + isServer = true; + + int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); + int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); + + if(outfd < 0 || infd < 0) { + return -1; + } + + usbFdRead = infd; + usbFdWrite = outfd; + + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); + + return 0; +} + + +int usbEpPlatformClose(void *fdKey) +{ + int error; + + if (isServer) { + if (usbFdRead != -1){ + close(usbFdRead); + usbFdRead = -1; + } + + if (usbFdWrite != -1){ + close(usbFdWrite); + usbFdWrite = -1; + } + } else { + error = libusb_release_interface(dev_handle, INTERFACE_XLINK); + if (error != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return error; + } + + /* Release the device and exit */ + libusb_close(dev_handle); + } + + libusb_exit(ctx); + + error = EXIT_SUCCESS; + + return EXIT_SUCCESS; +} + +int usbEpPlatformRead(void *fdKey, void *data, int size) +{ + int rc = 0; + + if (isServer) { + if(usbFdRead < 0) + { + return -1; + } + + rc = read(usbFdRead, data, size); + } else { + rc = libusb_bulk_transfer(dev_handle, usbFdRead, (unsigned char*)data, size, &rc, TIMEOUT); + } + + return rc; +} + +int usbEpPlatformWrite(void *fdKey, void *data, int size) +{ + int rc = 0; + + if (isServer) { + if(usbFdWrite < 0) + { + return -1; + } + + rc = write(usbFdWrite, data, size); + + } else { + rc = libusb_bulk_transfer(dev_handle, usbFdWrite, (unsigned char*)data, size, &rc, TIMEOUT); + } + + return rc; +} + diff --git a/src/pc/protocols/usb_host_ep.h b/src/pc/protocols/usb_host_ep.h new file mode 100644 index 00000000..658819d2 --- /dev/null +++ b/src/pc/protocols/usb_host_ep.h @@ -0,0 +1,24 @@ +// Copyright (C) 2018-2021 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 +// + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "XLink/XLink.h" +#include "XLink/XLinkPlatform.h" + +int usbEpInitialize(); + +int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd); +int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd); +int usbEpPlatformClose(void *fd); + +int usbEpPlatformRead(void *fd, void *data, int size); +int usbEpPlatformWrite(void *fd, void *data, int size); + +#ifdef __cplusplus +} +#endif diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index 40101302..a9d338e7 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -700,6 +700,7 @@ const char* XLinkProtocolToStr(XLinkProtocol_t val) { case X_LINK_TCP_IP: return "X_LINK_TCP_IP"; case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: return "X_LINK_TCP_IP_OR_LOCAL_SHDMEM"; + case X_LINK_USB_EP: return "X_LINK_USB_EP"; case X_LINK_NMB_OF_PROTOCOLS: return "X_LINK_NMB_OF_PROTOCOLS"; case X_LINK_ANY_PROTOCOL: return "X_LINK_ANY_PROTOCOL"; default: From 58edff585a9aca884325618af315f80a59d7462b Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 1 Jul 2025 08:12:34 +0200 Subject: [PATCH 02/78] Removed binary files --- examples/compile.sh | 3 --- examples/header.type | 0 examples/xlink_usb_client | Bin 542096 -> 0 bytes 3 files changed, 3 deletions(-) delete mode 100755 examples/compile.sh delete mode 100644 examples/header.type delete mode 100755 examples/xlink_usb_client diff --git a/examples/compile.sh b/examples/compile.sh deleted file mode 100755 index 4ba5603c..00000000 --- a/examples/compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -g++ xlink_usb_server.cpp -g -I../include -L../build -lXLink -lc -lusb-1.0 -o xlink_usb_server -g++ xlink_usb_client.cpp -g -I../include -L../build -lXLink -lc -lusb-1.0 -o xlink_usb_client - diff --git a/examples/header.type b/examples/header.type deleted file mode 100644 index e69de29b..00000000 diff --git a/examples/xlink_usb_client b/examples/xlink_usb_client deleted file mode 100755 index 4bf052f46b2640c7a3c1e5f257655180ef3561fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 542096 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 Date: Tue, 1 Jul 2025 11:29:51 +0200 Subject: [PATCH 03/78] Added comments for EPs offsets --- src/pc/protocols/usb_host_ep.cpp | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index de74adc5..2661b5cc 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -29,6 +29,15 @@ /* Base endpoint address used for input */ #define ENDPOINT_IN_BASE 0x81 +/* The endpoint structure is the following + * - Gate: first endpoint + * - XLink: second endpoint + * - ADB: third endpoint + * - etc + */ +#define ENDPOINT_OUT_OFFSET 0 +#define ENDPOINT_IN_OFFSET 0 + /* Transfer timeout */ #define TIMEOUT 2000 @@ -81,10 +90,9 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void /* We get the first EP_OUT and EP_IN for our interfaces * In the way we initialized our usb-gadget on our device - * We know ncm is claiming the first 2 interfaces */ - usbFdWrite = ENDPOINT_OUT_BASE + 1; /* +1 because NCM claims 1 OUT endpoint */ - usbFdRead = ENDPOINT_IN_BASE + 2; /* +2 because NCM claims 2 IN endpoints */ + usbFdWrite = ENDPOINT_OUT_BASE + ENDPOINT_OUT_OFFSET; + usbFdRead = ENDPOINT_IN_BASE + ENDPOINT_IN_OFFSET; *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); From 6dbba164b41afc07f7dc32190c9f0d7bcf2a765c Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 1 Jul 2025 11:39:31 +0200 Subject: [PATCH 04/78] Set correct offsets. --- src/pc/protocols/usb_host_ep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 2661b5cc..81a2200c 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -35,8 +35,8 @@ * - ADB: third endpoint * - etc */ -#define ENDPOINT_OUT_OFFSET 0 -#define ENDPOINT_IN_OFFSET 0 +#define ENDPOINT_OUT_OFFSET 1 +#define ENDPOINT_IN_OFFSET 1 /* Transfer timeout */ #define TIMEOUT 2000 From 7538a4663c2a467b8c8f1e0397d534ead3a2e232 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 2 Jul 2025 09:30:05 +0200 Subject: [PATCH 05/78] Added discovery --- include/XLink/XLinkPlatform.h | 1 + src/pc/PlatformDeviceSearch.c | 50 +++++++++++++++++++-- src/pc/protocols/usb_host_ep.cpp | 74 +++++++++++++++++++++++++++++++- src/pc/protocols/usb_host_ep.h | 1 + src/shared/XLinkDevice.c | 1 + 5 files changed, 123 insertions(+), 4 deletions(-) diff --git a/include/XLink/XLinkPlatform.h b/include/XLink/XLinkPlatform.h index 77b67e39..75faf6f3 100644 --- a/include/XLink/XLinkPlatform.h +++ b/include/XLink/XLinkPlatform.h @@ -37,6 +37,7 @@ typedef enum { X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED = X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_LOCAL_SHDMEM, X_LINK_PLATFORM_TCP_IP_OR_LOCAL_SHDMEM_DRIVER_NOT_LOADED = X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_TCP_IP_OR_LOCAL_SHDMEM, X_LINK_PLATFORM_PCIE_DRIVER_NOT_LOADED = X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_PCIE, + X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED = X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP, } xLinkPlatformErrorCode_t; // ------------------------------------ diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 40c3eead..819f2475 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -10,6 +10,7 @@ #include "pcie_host.h" #include "tcpip_host.h" #include "local_memshd.h" +#include "usb_host_ep.h" #include "XLinkStringUtils.h" @@ -41,6 +42,10 @@ static xLinkPlatformErrorCode_t getLocalShdmemDevices(const deviceDesc_t in_devi unsigned int *out_amountOfFoundDevices); #endif +static xLinkPlatformErrorCode_t getUSBEPDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices); + // ------------------------------------ // Helpers declaration. End. // ------------------------------------ @@ -58,6 +63,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe xLinkPlatformErrorCode_t PCIe_rc; xLinkPlatformErrorCode_t TCPIP_rc; xLinkPlatformErrorCode_t SHDMEM_rc; + xLinkPlatformErrorCode_t USBEP_rc; unsigned numFoundDevices = 0; *out_amountOfFoundDevices = 0; @@ -69,7 +75,6 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } // Check if protocol is initialized return getUSBDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); - /* TODO(themarpe) - reenable PCIe case X_LINK_PCIE: return getPCIeDeviceName(0, state, in_deviceRequirements, out_foundDevice); @@ -87,6 +92,11 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } return getLocalShdmemDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); #endif + case X_LINK_USB_EP: + if(!XLinkIsProtocolInitialized(in_deviceRequirements.protocol)) { + return X_LINK_PLATFORM_DRIVER_NOT_LOADED+in_deviceRequirements.protocol; + } + return getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); case X_LINK_ANY_PROTOCOL: // If USB protocol is initialized @@ -103,8 +113,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe sizeFoundDevices -= numFoundDevices; } } - - + // TODO(themarpe) - reenable PCIe (void) PCIe_rc; /* @@ -152,6 +161,20 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } } + if(XLinkIsProtocolInitialized(X_LINK_USB_EP)) { + numFoundDevices = 0; + USBEP_rc = getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, &numFoundDevices); + *out_amountOfFoundDevices += numFoundDevices; + out_foundDevices += numFoundDevices; + // Found enough devices, return + if (numFoundDevices >= sizeFoundDevices) { + return X_LINK_PLATFORM_SUCCESS; + } else { + sizeFoundDevices -= numFoundDevices; + } + } + + return X_LINK_PLATFORM_SUCCESS; default: @@ -182,6 +205,7 @@ char* XLinkPlatformErrorToStr(const xLinkPlatformErrorCode_t errorCode) { case X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_TCP_IP_OR_LOCAL_SHDMEM_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_PCIE_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_PCIE_DRIVER_NOT_LOADED"; + case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_INVALID_PARAMETERS: return "X_LINK_PLATFORM_INVALID_PARAMETERS"; default: return ""; } @@ -352,6 +376,26 @@ xLinkPlatformErrorCode_t getLocalShdmemDevices(const deviceDesc_t in_deviceRequi } #endif +xLinkPlatformErrorCode_t getUSBEPDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices) +{ + ASSERT_XLINK_PLATFORM(out_foundDevices); + ASSERT_XLINK_PLATFORM(out_amountOfFoundDevices); + if (in_deviceRequirements.platform == X_LINK_MYRIAD_2) { + return X_LINK_PLATFORM_ERROR; + } + + if(in_deviceRequirements.state == X_LINK_UNBOOTED) { + /** + * There is no condition where unbooted + * state device to be found using usb. + */ + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } + + return usbepGetDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); +} // ------------------------------------ // Helpers implementation. End. // ------------------------------------ diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 81a2200c..a047d65f 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -7,6 +7,13 @@ #include "XLink/XLinkPublicDefines.h" #include "usb_host_ep.h" #include "../PlatformDeviceFd.h" +#include "usb_mx_id.h" +#include "usb_host.h" + +// std +#include +#include +#include #include #include @@ -21,7 +28,7 @@ #define PRODUCT_ID 0x1234 /* Interface number for ffs.xlink */ -#define INTERFACE_XLINK 2 +#define INTERFACE_XLINK 1 /* Base ndpoint address used for output */ #define ENDPOINT_OUT_BASE 0x01 @@ -189,3 +196,68 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) return rc; } +int usbepGetDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices) { + int error = 0; + libusb_device_handle* dev_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); + if (dev_handle == NULL) { + libusb_exit(ctx); + return LIBUSB_ERROR_NO_DEVICE; + } + + error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return error; + } + + // Check if the interface exists + libusb_device* dev = libusb_get_device(dev_handle); + struct libusb_config_descriptor* config; + error = libusb_get_active_config_descriptor(dev, &config); + if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return error; + } + + // Look for INTERFACE_XLINK + bool found = false; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { + found = true; + break; + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(dev_handle); + libusb_exit(ctx); + + *out_amountOfFoundDevices = 0; + + return X_LINK_PLATFORM_SUCCESS; + } + + int numDevicesFound = 0; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; + out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; + out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + + libusb_close(dev_handle); + libusb_exit(ctx); + + return X_LINK_PLATFORM_SUCCESS; +} diff --git a/src/pc/protocols/usb_host_ep.h b/src/pc/protocols/usb_host_ep.h index 658819d2..84d9be71 100644 --- a/src/pc/protocols/usb_host_ep.h +++ b/src/pc/protocols/usb_host_ep.h @@ -19,6 +19,7 @@ int usbEpPlatformClose(void *fd); int usbEpPlatformRead(void *fd, void *data, int size); int usbEpPlatformWrite(void *fd, void *data, int size); +int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices); #ifdef __cplusplus } #endif diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index d78611c8..716db847 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -644,6 +644,7 @@ XLinkError_t parsePlatformError(xLinkPlatformErrorCode_t rc) { case X_LINK_PLATFORM_DEVICE_BUSY: return X_LINK_DEVICE_ALREADY_IN_USE; case X_LINK_PLATFORM_USB_DRIVER_NOT_LOADED: + case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: return X_LINK_INIT_USB_ERROR; case X_LINK_PLATFORM_TCP_IP_DRIVER_NOT_LOADED: return X_LINK_INIT_TCP_IP_ERROR; From 6ab310d74f39fd4cab9a21bc99bb1a8e6c8e7cf2 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 2 Jul 2025 09:41:26 +0200 Subject: [PATCH 06/78] Recognition of the USB EP interface via strings --- src/pc/protocols/usb_host_ep.cpp | 58 ++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index a047d65f..769a999f 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -29,6 +29,7 @@ /* Interface number for ffs.xlink */ #define INTERFACE_XLINK 1 +#define INTERFACE_XLINK_NAME "Luxonis Communication Interface" /* Base ndpoint address used for output */ #define ENDPOINT_OUT_BASE 0x01 @@ -82,11 +83,50 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void */ error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); libusb_exit(ctx); return error; } + libusb_device* dev = libusb_get_device(dev_handle); + struct libusb_config_descriptor* config; + error = libusb_get_active_config_descriptor(dev, &config); + if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return error; + } + + // Look for INTERFACE_XLINK + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { + if(config->interface[i].altsetting[0].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[0].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + break; + } + } + } + } + } + + if (!found) { + libusb_close(dev_handle); + libusb_exit(ctx); + return LIBUSB_ERROR_NO_DEVICE; + } + /* Now we claim our ffs interfaces */ error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); if (error != LIBUSB_SUCCESS) { @@ -225,10 +265,24 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, // Look for INTERFACE_XLINK bool found = false; + unsigned char name_buf[256]; for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { - found = true; - break; + if(config->interface[i].altsetting[0].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[0].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + break; + } + } + } } } From 5c220ed7f6df4bfc658cf38f7bbfc968ed3451a7 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 3 Jul 2025 09:44:58 +0200 Subject: [PATCH 07/78] Set correct PRODUCT_ID in USB EP --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 769a999f..58f7ac33 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -25,7 +25,7 @@ #define VENDOR_ID 0x03e7 /* Product ID */ -#define PRODUCT_ID 0x1234 +#define PRODUCT_ID 0xf63b /* Interface number for ffs.xlink */ #define INTERFACE_XLINK 1 From ca49e538231c2cd2bfac39ff8219e9188d19a7f9 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 3 Jul 2025 10:27:36 +0200 Subject: [PATCH 08/78] Using first unused device in USB EP. --- src/pc/protocols/usb_host_ep.cpp | 88 ++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 21 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 58f7ac33..5348d4e1 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -22,10 +22,10 @@ #include /* Vendor ID */ -#define VENDOR_ID 0x03e7 +#define VENDOR_ID 0x05c6 /* Product ID */ -#define PRODUCT_ID 0xf63b +#define PRODU /* Interface number for ffs.xlink */ #define INTERFACE_XLINK 1 @@ -64,13 +64,57 @@ int usbEpInitialize() { return 0; } +libusb_device_handle *findUnusedDevice() { + libusb_device **devs; + ssize_t cnt = libusb_get_device_list(ctx, &devs); + if (cnt < 0) return NULL; + + libusb_device_handle *handle = NULL; + + for (ssize_t i = 0; i < cnt; i++) { + libusb_device *dev = devs[i]; + struct libusb_device_descriptor desc; + + if (libusb_get_device_descriptor(dev, &desc) != 0) + continue; + + if (desc.idVendor != VENDOR_ID) + continue; + + if (libusb_open(dev, &handle) != 0) + continue; + + for (int iface = 0; iface < desc.bNumConfigurations; iface++) { + int detach_result = libusb_kernel_driver_active(handle, iface); + if (detach_result == 1) { + libusb_close(handle); + handle = NULL; + break; /* This interface is in use by kernel driver */ + } + } + + if (handle) { + if (libusb_claim_interface(handle, INTERFACE_XLINK) == 0) { + libusb_release_interface(handle, INTERFACE_XLINK); + break; /* Found available device */ + } else { + libusb_close(handle); + handle = NULL; + } + } + } + + libusb_free_device_list(devs, 1); + return handle; +} + int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) { int error; isServer = false; /* Get our device */ - dev_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); + dev_handle = findUnusedDevice(); if (dev_handle == NULL) { libusb_exit(ctx); @@ -98,34 +142,36 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void return error; } - // Look for INTERFACE_XLINK + // Try claiming interface 0 to test if it's in use bool found = false; unsigned char name_buf[256]; for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { if(config->interface[i].altsetting[0].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, + int r = libusb_get_string_descriptor_ascii(dev_handle, config->interface[i].altsetting[0].iInterface, name_buf, sizeof(name_buf)); - if (r > 0) { - name_buf[r] = '\0'; - - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - break; - } + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + break; + } } } - } + } } + libusb_free_config_descriptor(config); + if (!found) { libusb_close(dev_handle); libusb_exit(ctx); - return LIBUSB_ERROR_NO_DEVICE; - } + + return LIBUSB_ERROR_NO_DEVICE; + } /* Now we claim our ffs interfaces */ error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); @@ -240,7 +286,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { int error = 0; - libusb_device_handle* dev_handle = libusb_open_device_with_vid_pid(NULL, VENDOR_ID, PRODUCT_ID); + libusb_device_handle* dev_handle = findUnusedDevice(); if (dev_handle == NULL) { libusb_exit(ctx); return LIBUSB_ERROR_NO_DEVICE; @@ -275,12 +321,12 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, sizeof(name_buf)); if (r > 0) { - name_buf[r] = '\0'; + name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - break; - } + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + break; + } } } } From d20978df0ddf39ccd97d8a16efaa099524212120 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 10 Jul 2025 12:25:21 +0200 Subject: [PATCH 09/78] Finalised USB EPs --- examples/xlink_usb_server.cpp | 2 +- src/pc/protocols/usb_host_ep.cpp | 59 +++++++++++++------------------- 2 files changed, 24 insertions(+), 37 deletions(-) diff --git a/examples/xlink_usb_server.cpp b/examples/xlink_usb_server.cpp index 2688e2a4..43ebe035 100644 --- a/examples/xlink_usb_server.cpp +++ b/examples/xlink_usb_server.cpp @@ -39,7 +39,7 @@ int main(int argc, const char** argv){ XLinkHandler_t handler; handler.devicePath = "/dev/usb-ffs/xlink"; handler.protocol = X_LINK_USB_EP; - XLinkServer(&handler, "eps", X_LINK_BOOTED, X_LINK_MYRIAD_X); + XLinkServerOnly(&handler); // loop through streams diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 5348d4e1..78f2306b 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -24,9 +24,6 @@ /* Vendor ID */ #define VENDOR_ID 0x05c6 -/* Product ID */ -#define PRODU - /* Interface number for ffs.xlink */ #define INTERFACE_XLINK 1 #define INTERFACE_XLINK_NAME "Luxonis Communication Interface" @@ -77,30 +74,15 @@ libusb_device_handle *findUnusedDevice() { if (libusb_get_device_descriptor(dev, &desc) != 0) continue; - + if (desc.idVendor != VENDOR_ID) continue; - + if (libusb_open(dev, &handle) != 0) continue; - - for (int iface = 0; iface < desc.bNumConfigurations; iface++) { - int detach_result = libusb_kernel_driver_active(handle, iface); - if (detach_result == 1) { - libusb_close(handle); - handle = NULL; - break; /* This interface is in use by kernel driver */ - } - } - + if (handle) { - if (libusb_claim_interface(handle, INTERFACE_XLINK) == 0) { - libusb_release_interface(handle, INTERFACE_XLINK); - break; /* Found available device */ - } else { - libusb_close(handle); - handle = NULL; - } + break; /* Found available device */ } } @@ -121,7 +103,7 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void error = LIBUSB_ERROR_NO_DEVICE; return error; } - + /* Not strictly necessary, but it is better to use it, * as we're using kernel modules together with our interfaces */ @@ -132,7 +114,7 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void return error; } - + libusb_device* dev = libusb_get_device(dev_handle); struct libusb_config_descriptor* config; error = libusb_get_active_config_descriptor(dev, &config); @@ -144,24 +126,29 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void // Try claiming interface 0 to test if it's in use bool found = false; + bool foundGate = false; unsigned char name_buf[256]; for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { - if(config->interface[i].altsetting[0].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, - config->interface[i].altsetting[0].iInterface, + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[j].iInterface, name_buf, sizeof(name_buf)); - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - break; + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + if(!foundGate) { + foundGate = true; + } else { + found = true; + break; } - } + } } - } + } + } } libusb_free_config_descriptor(config); @@ -205,7 +192,7 @@ int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void usbFdRead = infd; usbFdWrite = outfd; - + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); return 0; From 0e2e7d11f0f128c1363b1ac208fb09a363afb9d8 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 10 Jul 2025 15:32:41 +0200 Subject: [PATCH 10/78] Initialising usb ctx. --- src/pc/protocols/usb_host_ep.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 78f2306b..3595a4f3 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -272,6 +272,8 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { + libusb_init(&ctx); + int error = 0; libusb_device_handle* dev_handle = findUnusedDevice(); if (dev_handle == NULL) { From 8423b90465d9f91d8c3fd108bb8c18a7b27d8a3e Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 10 Jul 2025 16:07:16 +0200 Subject: [PATCH 11/78] Fixed wrong protocol in discovery for USB EP --- src/pc/protocols/usb_host_ep.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 3595a4f3..d5edd973 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -335,8 +335,8 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, int numDevicesFound = 0; // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From 11538f32737839159fa60eccd0c7f171b7815a14 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Fri, 11 Jul 2025 09:34:07 +0200 Subject: [PATCH 12/78] Set priority for USB_EP during discovery --- src/pc/PlatformDeviceSearch.c | 27 +++++++++++++-------------- src/pc/protocols/usb_host_ep.cpp | 1 + 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 819f2475..4cd61f77 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -99,6 +99,19 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe return getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); case X_LINK_ANY_PROTOCOL: + if(XLinkIsProtocolInitialized(X_LINK_USB_EP)) { + numFoundDevices = 0; + USBEP_rc = getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, &numFoundDevices); + *out_amountOfFoundDevices += numFoundDevices; + out_foundDevices += numFoundDevices; + // Found enough devices, return + if (numFoundDevices >= sizeFoundDevices) { + return X_LINK_PLATFORM_SUCCESS; + } else { + sizeFoundDevices -= numFoundDevices; + } + } + // If USB protocol is initialized if(XLinkIsProtocolInitialized(X_LINK_USB_VSC)) { // Find first correct USB Device @@ -161,20 +174,6 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } } - if(XLinkIsProtocolInitialized(X_LINK_USB_EP)) { - numFoundDevices = 0; - USBEP_rc = getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, &numFoundDevices); - *out_amountOfFoundDevices += numFoundDevices; - out_foundDevices += numFoundDevices; - // Found enough devices, return - if (numFoundDevices >= sizeFoundDevices) { - return X_LINK_PLATFORM_SUCCESS; - } else { - sizeFoundDevices -= numFoundDevices; - } - } - - return X_LINK_PLATFORM_SUCCESS; default: diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index d5edd973..0790c862 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -339,6 +339,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); numDevicesFound++; From fdc7ca6699431bab598e366057274c9e215967c0 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Fri, 11 Jul 2025 10:01:35 +0200 Subject: [PATCH 13/78] Removed ctx deinitialisation --- src/pc/protocols/usb_host_ep.cpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 0790c862..c59c1475 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -272,19 +272,15 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { - libusb_init(&ctx); - int error = 0; libusb_device_handle* dev_handle = findUnusedDevice(); if (dev_handle == NULL) { - libusb_exit(ctx); return LIBUSB_ERROR_NO_DEVICE; } error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); if (error != LIBUSB_SUCCESS) { libusb_close(dev_handle); - libusb_exit(ctx); return error; } @@ -294,7 +290,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, error = libusb_get_active_config_descriptor(dev, &config); if (error != LIBUSB_SUCCESS) { libusb_close(dev_handle); - libusb_exit(ctx); return error; } @@ -325,7 +320,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, if (!found) { libusb_close(dev_handle); - libusb_exit(ctx); *out_amountOfFoundDevices = 0; @@ -347,7 +341,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, *out_amountOfFoundDevices = numDevicesFound; libusb_close(dev_handle); - libusb_exit(ctx); return X_LINK_PLATFORM_SUCCESS; } From 9909f6d0801f40634c306c99897854e76433c0b1 Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Tue, 15 Jul 2025 14:19:10 +0200 Subject: [PATCH 14/78] Brainstorm --- src/pc/protocols/usb_host_ep.cpp | 128 +++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 78f2306b..29147808 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -348,3 +348,131 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, return X_LINK_PLATFORM_SUCCESS; } + + +/// TODO - use this for device search. +xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices) { + + // Also protects usb_mx_id_cache + std::lock_guard l(mutex); + + // No RVC3/4 devices on USB now, return 0 + if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + *out_amountOfFoundDevices = 0; + return X_LINK_PLATFORM_SUCCESS; + } + + + // Get list of usb devices + static libusb_device **devs = NULL; + auto numDevices = libusb_get_device_list(context, &devs); + if(numDevices < 0) { + mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); + return X_LINK_PLATFORM_ERROR; + } + + /// NOT NEEDED + // // Initialize mx id cache + // usb_mx_id_cache_init(); + + // Loop over all usb devices, increase count only if myriad device + int numDevicesFound = 0; + for(ssize_t i = 0; i < numDevices; i++) { + if(devs[i] == nullptr) continue; + + if(numDevicesFound >= sizeFoundDevices){ + break; + } + + // Get device descriptor + struct libusb_device_descriptor desc; + auto res = libusb_get_device_descriptor(devs[i], &desc); + if (res < 0) { + mvLog(MVLOG_DEBUG, "Unable to get USB device descriptor: %s", xlink_libusb_strerror(res)); + continue; + } + + /// TODO - modify to use updated VID/PID + VidPid vidpid{desc.idVendor, desc.idProduct}; + + if(vidPidToDeviceState.count(vidpid) > 0){ + // Device found + + // Device status + XLinkError_t status = X_LINK_SUCCESS; + + // Get device state + XLinkDeviceState_t state = vidPidToDeviceState.at(vidpid); + // Check if compare with state + if(in_deviceRequirements.state != X_LINK_ANY_STATE && state != in_deviceRequirements.state){ + // Current device doesn't match the "filter" + continue; + } + + // Get device name + std::string devicePath = getLibusbDevicePath(devs[i]); + // Check if compare with name, if name is only a hint, don't filter + + if(!in_deviceRequirements.nameHintOnly){ + std::string requiredName(in_deviceRequirements.name); + if(requiredName.length() > 0 && requiredName != devicePath){ + // Current device doesn't match the "filter" + continue; + } + } + + // Get device mxid + std::string mxId; + + libusb_error rc = libusb_get_string_descriptor_ascii(handle, pDesc->iSerialNumber, ((uint8_t*) mxId), sizeof(mxId))); + + switch (rc) + { + case LIBUSB_SUCCESS: + status = X_LINK_SUCCESS; + break; + case LIBUSB_ERROR_ACCESS: + status = X_LINK_INSUFFICIENT_PERMISSIONS; + break; + case LIBUSB_ERROR_BUSY: + status = X_LINK_DEVICE_ALREADY_IN_USE; + break; + default: + status = X_LINK_ERROR; + break; + } + + // compare with MxId + std::string requiredMxId(in_deviceRequirements.mxid); + if(requiredMxId.length() > 0 && requiredMxId != mxId){ + // Current device doesn't match the "filter" + continue; + } + + // TODO(themarpe) - check platform + + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; + out_foundDevices[numDevicesFound].state = state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + + } + + } + + // Free list of usb devices + libusb_free_device_list(devs, 1); + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + + return X_LINK_PLATFORM_SUCCESS; +} From 4891a886788fa72a5a2de76bcb110ffb07469013 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 09:18:42 +0200 Subject: [PATCH 15/78] Added GATE read and write functions. --- include/XLink/XLinkPlatform.h | 2 + include/XLink/XLinkPrivateDefines.h | 5 + src/pc/PlatformData.c | 32 +++ src/pc/protocols/usb_host_ep.cpp | 297 +++++++++++++++------------- src/pc/protocols/usb_host_ep.h | 4 + src/shared/XLinkData.c | 64 ++++++ src/shared/XLinkDispatcher.c | 4 + src/shared/XLinkDispatcherImpl.c | 32 +++ 8 files changed, 300 insertions(+), 140 deletions(-) diff --git a/include/XLink/XLinkPlatform.h b/include/XLink/XLinkPlatform.h index 75faf6f3..cc9ebbfa 100644 --- a/include/XLink/XLinkPlatform.h +++ b/include/XLink/XLinkPlatform.h @@ -91,6 +91,8 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); int XLinkPlatformWriteFd(xLinkDeviceHandle_t *deviceHandle, const long fd, void *data2, int size2); int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, long *fd); +int XLinkPlatformGateWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); +int XLinkPlatformGateRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size); void* XLinkPlatformAllocateData(uint32_t size, uint32_t alignment); void XLinkPlatformDeallocateData(void *ptr, uint32_t size, uint32_t alignment); diff --git a/include/XLink/XLinkPrivateDefines.h b/include/XLink/XLinkPrivateDefines.h index 9b9e92f0..db73dd02 100644 --- a/include/XLink/XLinkPrivateDefines.h +++ b/include/XLink/XLinkPrivateDefines.h @@ -116,10 +116,15 @@ typedef enum XLINK_READ_REL_SPEC_REQ, XLINK_WRITE_FD_REQ, // only for the shared mem protocol + XLINK_GATE_WRITE_REQ, + XLINK_GATE_READ_REQ, XLINK_REQUEST_LAST, XLINK_READ_REL_SPEC_RESP, XLINK_WRITE_FD_RESP, // only for the shared mem protocol + XLINK_GATE_WRITE_RESP, + XLINK_GATE_READ_RESP, + XLINK_RESP_LAST, } xLinkEventType_t; diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 00b17d72..5925ceea 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -160,6 +160,38 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l } } +int XLinkPlatformGateWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) +{ + if(!XLinkIsProtocolInitialized(deviceHandle->protocol)) { + return X_LINK_PLATFORM_DRIVER_NOT_LOADED+deviceHandle->protocol; + } + + switch (deviceHandle->protocol) { + case X_LINK_USB_EP: + return usbEpPlatformGateWrite(data, size); + + default: + return X_LINK_PLATFORM_INVALID_PARAMETERS; + } +} + +int XLinkPlatformGateRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size) +{ + if(!XLinkIsProtocolInitialized(deviceHandle->protocol)) { + return X_LINK_PLATFORM_DRIVER_NOT_LOADED+deviceHandle->protocol; + } + + switch (deviceHandle->protocol) { + case X_LINK_USB_EP: + return usbEpPlatformGateRead(data, size); + + default: + return X_LINK_PLATFORM_INVALID_PARAMETERS; + } +} + + + void* XLinkPlatformAllocateData(uint32_t size, uint32_t alignment) { void* ret = NULL; diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 3d198f77..426437b5 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -12,8 +12,8 @@ // std #include -#include #include +#include #include #include @@ -23,6 +23,10 @@ /* Vendor ID */ #define VENDOR_ID 0x05c6 +#define PRODUCT_ID 0x4321 + +/* Interface number for ffs.gate */ +#define INTERFACE_GATE 0 /* Interface number for ffs.xlink */ #define INTERFACE_XLINK 1 @@ -61,42 +65,13 @@ int usbEpInitialize() { return 0; } -libusb_device_handle *findUnusedDevice() { - libusb_device **devs; - ssize_t cnt = libusb_get_device_list(ctx, &devs); - if (cnt < 0) return NULL; - - libusb_device_handle *handle = NULL; - - for (ssize_t i = 0; i < cnt; i++) { - libusb_device *dev = devs[i]; - struct libusb_device_descriptor desc; - - if (libusb_get_device_descriptor(dev, &desc) != 0) - continue; - - if (desc.idVendor != VENDOR_ID) - continue; - - if (libusb_open(dev, &handle) != 0) - continue; - - if (handle) { - break; /* Found available device */ - } - } - - libusb_free_device_list(devs, 1); - return handle; -} - int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) { int error; isServer = false; /* Get our device */ - dev_handle = findUnusedDevice(); + dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); if (dev_handle == NULL) { libusb_exit(ctx); @@ -273,7 +248,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { int error = 0; - libusb_device_handle* dev_handle = findUnusedDevice(); + libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); if (dev_handle == NULL) { return LIBUSB_ERROR_NO_DEVICE; } @@ -326,6 +301,18 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, return X_LINK_PLATFORM_SUCCESS; } + // Get device descriptor + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(dev, &desc); + if (r < 0) { + return X_LINK_PLATFORM_ERROR; + } + + // Get device mxid + char mxId[32] = {'\0'}; + + r = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); + int numDevicesFound = 0; // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; @@ -335,6 +322,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strcpy(out_foundDevices[numDevicesFound].mxid, mxId); numDevicesFound++; // Write the number of found devices @@ -346,129 +334,158 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } -/// TODO - use this for device search. -xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices) { +int usbEpPlatformGateRead(void *data, int size) +{ + int rc = 0; + + /* Get our device */ + libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (dev_handle == NULL) { + libusb_exit(ctx); - // Also protects usb_mx_id_cache - std::lock_guard l(mutex); + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; + } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); - // No RVC3/4 devices on USB now, return 0 - if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ - *out_amountOfFoundDevices = 0; - return X_LINK_PLATFORM_SUCCESS; + return rc; + } + + libusb_device* dev = libusb_get_device(dev_handle); + struct libusb_config_descriptor* config; + rc = libusb_get_active_config_descriptor(dev, &config); + if (rc != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return rc; } + // Try claiming interface 0 to test if it's in use + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[j].iInterface, + name_buf, + sizeof(name_buf)); - // Get list of usb devices - static libusb_device **devs = NULL; - auto numDevices = libusb_get_device_list(context, &devs); - if(numDevices < 0) { - mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); - return X_LINK_PLATFORM_ERROR; + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + } + } + } + } } - /// NOT NEEDED - // // Initialize mx id cache - // usb_mx_id_cache_init(); + libusb_free_config_descriptor(config); - // Loop over all usb devices, increase count only if myriad device - int numDevicesFound = 0; - for(ssize_t i = 0; i < numDevices; i++) { - if(devs[i] == nullptr) continue; + if (!found) { + libusb_close(dev_handle); + libusb_exit(ctx); - if(numDevicesFound >= sizeFoundDevices){ - break; - } + return LIBUSB_ERROR_NO_DEVICE; + } - // Get device descriptor - struct libusb_device_descriptor desc; - auto res = libusb_get_device_descriptor(devs[i], &desc); - if (res < 0) { - mvLog(MVLOG_DEBUG, "Unable to get USB device descriptor: %s", xlink_libusb_strerror(res)); - continue; - } + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(dev_handle, INTERFACE_GATE); + if (rc != LIBUSB_SUCCESS) { + libusb_exit(ctx); - /// TODO - modify to use updated VID/PID - VidPid vidpid{desc.idVendor, desc.idProduct}; - - if(vidPidToDeviceState.count(vidpid) > 0){ - // Device found - - // Device status - XLinkError_t status = X_LINK_SUCCESS; - - // Get device state - XLinkDeviceState_t state = vidPidToDeviceState.at(vidpid); - // Check if compare with state - if(in_deviceRequirements.state != X_LINK_ANY_STATE && state != in_deviceRequirements.state){ - // Current device doesn't match the "filter" - continue; - } - - // Get device name - std::string devicePath = getLibusbDevicePath(devs[i]); - // Check if compare with name, if name is only a hint, don't filter - - if(!in_deviceRequirements.nameHintOnly){ - std::string requiredName(in_deviceRequirements.name); - if(requiredName.length() > 0 && requiredName != devicePath){ - // Current device doesn't match the "filter" - continue; - } - } - - // Get device mxid - std::string mxId; - - libusb_error rc = libusb_get_string_descriptor_ascii(handle, pDesc->iSerialNumber, ((uint8_t*) mxId), sizeof(mxId))); - - switch (rc) - { - case LIBUSB_SUCCESS: - status = X_LINK_SUCCESS; - break; - case LIBUSB_ERROR_ACCESS: - status = X_LINK_INSUFFICIENT_PERMISSIONS; - break; - case LIBUSB_ERROR_BUSY: - status = X_LINK_DEVICE_ALREADY_IN_USE; - break; - default: - status = X_LINK_ERROR; - break; - } - - // compare with MxId - std::string requiredMxId(in_deviceRequirements.mxid); - if(requiredMxId.length() > 0 && requiredMxId != mxId){ - // Current device doesn't match the "filter" - continue; - } - - // TODO(themarpe) - check platform - - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; - out_foundDevices[numDevicesFound].state = state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; + return rc; + } - } + rc = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + + libusb_close(dev_handle); + + return rc; +} +int usbEpPlatformGateWrite(void *data, int size) +{ + int rc = 0; + + /* Get our device */ + libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (dev_handle == NULL) { + libusb_exit(ctx); + + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); - // Free list of usb devices - libusb_free_device_list(devs, 1); + return rc; + } + + libusb_device* dev = libusb_get_device(dev_handle); + struct libusb_config_descriptor* config; + rc = libusb_get_active_config_descriptor(dev, &config); + if (rc != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return rc; + } - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; + // Try claiming interface 0 to test if it's in use + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[j].iInterface, + name_buf, + sizeof(name_buf)); - return X_LINK_PLATFORM_SUCCESS; + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + } + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(dev_handle); + libusb_exit(ctx); + + return LIBUSB_ERROR_NO_DEVICE; + } + + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(dev_handle, INTERFACE_GATE); + if (rc != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return rc; + } + + rc = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + + libusb_close(dev_handle); + + return rc; } diff --git a/src/pc/protocols/usb_host_ep.h b/src/pc/protocols/usb_host_ep.h index 84d9be71..77127b5f 100644 --- a/src/pc/protocols/usb_host_ep.h +++ b/src/pc/protocols/usb_host_ep.h @@ -20,6 +20,10 @@ int usbEpPlatformRead(void *fd, void *data, int size); int usbEpPlatformWrite(void *fd, void *data, int size); int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices); + +int usbEpPlatformGateRead(void *data, int size); +int usbEpPlatformGateWrite(void *data, int size); + #ifdef __cplusplus } #endif diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index bf3a885b..40d4204e 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -501,6 +501,70 @@ XLinkError_t XLinkGetFillLevel(streamId_t const streamId, int isRemote, int* fil return X_LINK_SUCCESS; } +XLinkError_t XLinkGateWriteData_(streamId_t streamId, const uint8_t* buffer, + int size, XLinkTimespec* outTSend) +{ + XLINK_RET_IF(buffer == NULL); + + float opTime = 0.0f; + xLinkDesc_t* link = NULL; + XLINK_RET_IF(getLinkByStreamId(streamId, &link)); + streamId_t streamIdOnly = EXTRACT_STREAM_ID(streamId); + + xLinkEvent_t event = {0}; + XLINK_INIT_EVENT(event, streamIdOnly, XLINK_GATE_WRITE_REQ, + size,(void*)buffer, link->deviceHandle); + + XLINK_RET_IF(addEventWithPerf_(&event, &opTime, XLINK_NO_RW_TIMEOUT, outTSend)); + + if( glHandler->profEnable) { + glHandler->profilingData.totalWriteBytes += size; + glHandler->profilingData.totalWriteTime += opTime; + } + link->profilingData.totalWriteBytes += size; + link->profilingData.totalWriteTime += size; + + return X_LINK_SUCCESS; +} + +XLinkError_t XLinkGateWriteData(streamId_t const streamId, const uint8_t* buffer, + int size) +{ + return XLinkWriteData_(streamId, buffer, size, NULL); +} + +XLinkError_t XLinkGateReadData_(streamId_t streamId, const uint8_t* buffer, + int size, XLinkTimespec* outTSend) +{ + XLINK_RET_IF(buffer == NULL); + + float opTime = 0.0f; + xLinkDesc_t* link = NULL; + XLINK_RET_IF(getLinkByStreamId(streamId, &link)); + streamId_t streamIdOnly = EXTRACT_STREAM_ID(streamId); + + xLinkEvent_t event = {0}; + XLINK_INIT_EVENT(event, streamIdOnly, XLINK_GATE_READ_REQ, + size,(void*)buffer, link->deviceHandle); + + XLINK_RET_IF(addEventWithPerf_(&event, &opTime, XLINK_NO_RW_TIMEOUT, outTSend)); + + if( glHandler->profEnable) { + glHandler->profilingData.totalReadBytes += size; + glHandler->profilingData.totalReadTime += opTime; + } + link->profilingData.totalReadBytes += size; + link->profilingData.totalReadTime += size; + + return X_LINK_SUCCESS; +} + +XLinkError_t XLinkGateReadData(streamId_t const streamId, const uint8_t* buffer, + int size) +{ + return XLinkGateReadData_(streamId, buffer, size, NULL); +} + // ------------------------------------ // Helpers declaration. Begin. // ------------------------------------ diff --git a/src/shared/XLinkDispatcher.c b/src/shared/XLinkDispatcher.c index 95e066f1..3b7c9f1b 100644 --- a/src/shared/XLinkDispatcher.c +++ b/src/shared/XLinkDispatcher.c @@ -520,9 +520,13 @@ char* TypeToStr(int type) case XLINK_STATIC_RESP_LAST: return "XLINK_STATIC_RESP_LAST"; case XLINK_READ_REL_SPEC_REQ: return "XLINK_READ_REL_SPEC_REQ"; case XLINK_WRITE_FD_REQ: return "XLINK_WRITE_FD_REQ"; + case XLINK_GATE_WRITE_REQ: return "XLINK_GATE_WRITE_REQ"; + case XLINK_GATE_READ_REQ: return "XLINK_GATE_READ_REQ"; case XLINK_REQUEST_LAST: return "XLINK_REQUEST_LAST"; case XLINK_READ_REL_SPEC_RESP: return "XLINK_READ_REL_SPEC_RESP"; case XLINK_WRITE_FD_RESP: return "XLINK_WRITE_FD_REQ"; + case XLINK_GATE_WRITE_RESP: return "XLINK_GATE_WRITE_RESP"; + case XLINK_GATE_READ_RESP: return "XLINK_GATE_READ_RESP"; case XLINK_RESP_LAST: return "XLINK_RESP_LAST"; default: diff --git a/src/shared/XLinkDispatcherImpl.c b/src/shared/XLinkDispatcherImpl.c index e0d84288..ab61fd39 100644 --- a/src/shared/XLinkDispatcherImpl.c +++ b/src/shared/XLinkDispatcherImpl.c @@ -335,6 +335,12 @@ int dispatcherEventSend(xLinkEvent_t *event, XLinkTimespec* sendTime) mvLog(MVLOG_ERROR,"Write failed %d\n", rc); return rc; } + } else if (event->header.type == XLINK_GATE_WRITE_REQ) { + rc = XLinkPlatformGateWrite(&event->deviceHandle, event->data, event->header.size); + if(rc < 0) { + mvLog(MVLOG_ERROR,"Write failed %d\n", rc); + return rc; + } } return 0; @@ -386,6 +392,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response, switch (event->header.type){ case XLINK_WRITE_REQ: case XLINK_WRITE_FD_REQ: + case XLINK_GATE_WRITE_REQ: { //in case local tries to write after it issues close (writeSize is zero) stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); @@ -423,6 +430,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response, break; } case XLINK_READ_REQ: + case XLINK_GATE_READ_REQ: { stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); if(!stream) { @@ -596,8 +604,28 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response (int)response->header.streamId, (int)xxx); } break; + case XLINK_GATE_WRITE_REQ: + { + //let remote write immediately as we have a local buffer for the data + response->header.type = XLINK_GATE_WRITE_RESP; + response->header.size = event->header.size; + response->header.streamId = event->header.streamId; + response->deviceHandle = event->deviceHandle; + XLINK_EVENT_ACKNOWLEDGE(response); + + // we got some data. We should unblock a blocked read + int xxx = DispatcherUnblockEvent(-1, + XLINK_GATE_READ_REQ, + response->header.streamId, + event->deviceHandle.xLinkFD); + (void) xxx; + mvLog(MVLOG_DEBUG,"unblocked from stream %d %d\n", + (int)response->header.streamId, (int)xxx); + } case XLINK_READ_REQ: break; + case XLINK_GATE_READ_REQ: + break; case XLINK_READ_REL_SPEC_REQ: XLINK_EVENT_ACKNOWLEDGE(response); response->header.type = XLINK_READ_REL_SPEC_RESP; @@ -741,10 +769,14 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response break; case XLINK_WRITE_RESP: break; + case XLINK_GATE_WRITE_RESP: + break; case XLINK_WRITE_FD_RESP: break; case XLINK_READ_RESP: break; + case XLINK_GATE_READ_RESP: + break; case XLINK_READ_REL_RESP: break; case XLINK_READ_REL_SPEC_RESP: From e519b8698d81722b05b389551e1038006198ef77 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 11:07:11 +0200 Subject: [PATCH 16/78] Changed XLinkGateReadData to behave like XLinkReadMoveData --- include/XLink/XLink.h | 3 +++ src/shared/XLinkData.c | 49 ++++++++++++++++++++++++++++-------------- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/include/XLink/XLink.h b/include/XLink/XLink.h index b55fe236..e6db94ca 100644 --- a/include/XLink/XLink.h +++ b/include/XLink/XLink.h @@ -427,6 +427,9 @@ XLinkError_t XLinkReadDataWithTimeout(streamId_t streamId, streamPacketDesc_t** */ XLinkError_t XLinkWriteDataWithTimeout(streamId_t streamId, const uint8_t* buffer, int size, unsigned int timeoutMs); +XLinkError_t XLinkGateWriteData(streamId_t const streamId, const uint8_t* buffer, int size); +XLinkError_t XLinkGateReadData(streamId_t const streamId, streamPacketDesc_t* const packet); + // ------------------------------------ // Device streams management. End. // ------------------------------------ diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 40d4204e..612e2a15 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -533,36 +533,53 @@ XLinkError_t XLinkGateWriteData(streamId_t const streamId, const uint8_t* buffer return XLinkWriteData_(streamId, buffer, size, NULL); } -XLinkError_t XLinkGateReadData_(streamId_t streamId, const uint8_t* buffer, - int size, XLinkTimespec* outTSend) +XLinkError_t XLinkGateReadData_(streamId_t streamId, streamPacketDesc_t* const packet, XLinkTimespec* outTSend) { - XLINK_RET_IF(buffer == NULL); + XLINK_RET_IF(packet == NULL); - float opTime = 0.0f; - xLinkDesc_t* link = NULL; + float opTime = 0; + xLinkDesc_t *link = NULL; XLINK_RET_IF(getLinkByStreamId(streamId, &link)); streamId_t streamIdOnly = EXTRACT_STREAM_ID(streamId); xLinkEvent_t event = {0}; - XLINK_INIT_EVENT(event, streamIdOnly, XLINK_GATE_READ_REQ, - size,(void*)buffer, link->deviceHandle); + XLINK_INIT_EVENT(event, streamIdOnly, XLINK_READ_REQ, + 0, NULL, link->deviceHandle); + event.header.flags.bitField.moveSemantic = 1; + XLINK_RET_IF(addEventWithPerf(&event, &opTime, XLINK_NO_RW_TIMEOUT)); - XLINK_RET_IF(addEventWithPerf_(&event, &opTime, XLINK_NO_RW_TIMEOUT, outTSend)); + if (!event.data) + { + return X_LINK_ERROR; + } + *packet = *(streamPacketDesc_t *)event.data; - if( glHandler->profEnable) { - glHandler->profilingData.totalReadBytes += size; + // free the allocation from movePacketFromStream() + // done within this same XLink module so the same C runtime is used + free(event.data); + + if (glHandler->profEnable) + { + glHandler->profilingData.totalReadBytes += packet->length; glHandler->profilingData.totalReadTime += opTime; } - link->profilingData.totalReadBytes += size; - link->profilingData.totalReadTime += size; + link->profilingData.totalReadBytes += packet->length; + link->profilingData.totalReadTime += opTime; - return X_LINK_SUCCESS; + + const XLinkError_t retVal = XLinkReleaseData(streamId); + if (retVal != X_LINK_SUCCESS) { + // severe error; deallocate here as the caller might forget to dealloc on errors; or be less able to manage + XLinkPlatformDeallocateData(packet->data, ALIGN_UP_INT32((int32_t)packet->length, __CACHE_LINE_SIZE), __CACHE_LINE_SIZE); + packet->data = NULL; + packet->length = 0; + } + return retVal; } -XLinkError_t XLinkGateReadData(streamId_t const streamId, const uint8_t* buffer, - int size) +XLinkError_t XLinkGateReadData(streamId_t const streamId, streamPacketDesc_t* const packet) { - return XLinkGateReadData_(streamId, buffer, size, NULL); + return XLinkGateReadData_(streamId, packet, NULL); } // ------------------------------------ From 4e42afbe66dc2348db025292a9e29e6f2a08d290 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 12:34:44 +0200 Subject: [PATCH 17/78] Set state to gate --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 426437b5..06b84453 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -318,7 +318,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; + out_foundDevices[numDevicesFound].state = X_LINK_GATE; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From aa476c77d7405169a47a6ac51220fe13ea70bb52 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 12:40:34 +0200 Subject: [PATCH 18/78] Revert "Set state to gate" This reverts commit 4e42afbe66dc2348db025292a9e29e6f2a08d290. --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 06b84453..426437b5 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -318,7 +318,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_GATE; + out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From 59681908bc9f71a5710734d19f78433ac942fb00 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 12:40:44 +0200 Subject: [PATCH 19/78] Revert "Set state to gate" This reverts commit 4e42afbe66dc2348db025292a9e29e6f2a08d290. --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 06b84453..426437b5 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -318,7 +318,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_GATE; + out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From fd7e65fb13705efd3cd3f965c56e7ee0b34c9105 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 16:55:45 +0200 Subject: [PATCH 20/78] Reapply "Set state to gate" This reverts commit aa476c77d7405169a47a6ac51220fe13ea70bb52. --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 426437b5..06b84453 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -318,7 +318,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; + out_foundDevices[numDevicesFound].state = X_LINK_GATE; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From 4126554f5854b8215ef5833af9ca85fd39816d0c Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 16:58:00 +0200 Subject: [PATCH 21/78] Set gate state. --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 426437b5..06b84453 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -318,7 +318,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_BOOTED; + out_foundDevices[numDevicesFound].state = X_LINK_GATE; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From 7d9d6b92725aa78795d6c5d239ceb66cdd0cdf10 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 17:48:09 +0200 Subject: [PATCH 22/78] Changed XLinkPlatformGate functions to not require device handle --- include/XLink/XLinkPlatform.h | 4 ++-- src/pc/PlatformData.c | 28 ++++++++-------------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/include/XLink/XLinkPlatform.h b/include/XLink/XLinkPlatform.h index cc9ebbfa..2b6ae1e0 100644 --- a/include/XLink/XLinkPlatform.h +++ b/include/XLink/XLinkPlatform.h @@ -91,8 +91,8 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); int XLinkPlatformWriteFd(xLinkDeviceHandle_t *deviceHandle, const long fd, void *data2, int size2); int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, long *fd); -int XLinkPlatformGateWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); -int XLinkPlatformGateRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size); +int XLinkPlatformGateWrite(void *data, int size); +int XLinkPlatformGateRead(void *data, int size); void* XLinkPlatformAllocateData(uint32_t size, uint32_t alignment); void XLinkPlatformDeallocateData(void *ptr, uint32_t size, uint32_t alignment); diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 5925ceea..f85b5bd1 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -160,34 +160,22 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l } } -int XLinkPlatformGateWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) +int XLinkPlatformGateWrite(void *data, int size) { - if(!XLinkIsProtocolInitialized(deviceHandle->protocol)) { - return X_LINK_PLATFORM_DRIVER_NOT_LOADED+deviceHandle->protocol; + if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { + return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - switch (deviceHandle->protocol) { - case X_LINK_USB_EP: - return usbEpPlatformGateWrite(data, size); - - default: - return X_LINK_PLATFORM_INVALID_PARAMETERS; - } + return usbEpPlatformGateWrite(data, size); } -int XLinkPlatformGateRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size) +int XLinkPlatformGateRead(void *data, int size) { - if(!XLinkIsProtocolInitialized(deviceHandle->protocol)) { - return X_LINK_PLATFORM_DRIVER_NOT_LOADED+deviceHandle->protocol; + if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { + return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - switch (deviceHandle->protocol) { - case X_LINK_USB_EP: - return usbEpPlatformGateRead(data, size); - - default: - return X_LINK_PLATFORM_INVALID_PARAMETERS; - } + return usbEpPlatformGateRead(data, size); } From d46c049e30d2a56f7bbec2e25e7e2fa5c09ec6d0 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 16 Jul 2025 17:51:41 +0200 Subject: [PATCH 23/78] Removed dispatched implementation. --- include/XLink/XLinkPrivateDefines.h | 4 -- src/shared/XLinkData.c | 81 ----------------------------- src/shared/XLinkDispatcher.c | 4 -- src/shared/XLinkDispatcherImpl.c | 32 ------------ 4 files changed, 121 deletions(-) diff --git a/include/XLink/XLinkPrivateDefines.h b/include/XLink/XLinkPrivateDefines.h index db73dd02..6c0a714f 100644 --- a/include/XLink/XLinkPrivateDefines.h +++ b/include/XLink/XLinkPrivateDefines.h @@ -116,14 +116,10 @@ typedef enum XLINK_READ_REL_SPEC_REQ, XLINK_WRITE_FD_REQ, // only for the shared mem protocol - XLINK_GATE_WRITE_REQ, - XLINK_GATE_READ_REQ, XLINK_REQUEST_LAST, XLINK_READ_REL_SPEC_RESP, XLINK_WRITE_FD_RESP, // only for the shared mem protocol - XLINK_GATE_WRITE_RESP, - XLINK_GATE_READ_RESP, XLINK_RESP_LAST, } xLinkEventType_t; diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 612e2a15..bf3a885b 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -501,87 +501,6 @@ XLinkError_t XLinkGetFillLevel(streamId_t const streamId, int isRemote, int* fil return X_LINK_SUCCESS; } -XLinkError_t XLinkGateWriteData_(streamId_t streamId, const uint8_t* buffer, - int size, XLinkTimespec* outTSend) -{ - XLINK_RET_IF(buffer == NULL); - - float opTime = 0.0f; - xLinkDesc_t* link = NULL; - XLINK_RET_IF(getLinkByStreamId(streamId, &link)); - streamId_t streamIdOnly = EXTRACT_STREAM_ID(streamId); - - xLinkEvent_t event = {0}; - XLINK_INIT_EVENT(event, streamIdOnly, XLINK_GATE_WRITE_REQ, - size,(void*)buffer, link->deviceHandle); - - XLINK_RET_IF(addEventWithPerf_(&event, &opTime, XLINK_NO_RW_TIMEOUT, outTSend)); - - if( glHandler->profEnable) { - glHandler->profilingData.totalWriteBytes += size; - glHandler->profilingData.totalWriteTime += opTime; - } - link->profilingData.totalWriteBytes += size; - link->profilingData.totalWriteTime += size; - - return X_LINK_SUCCESS; -} - -XLinkError_t XLinkGateWriteData(streamId_t const streamId, const uint8_t* buffer, - int size) -{ - return XLinkWriteData_(streamId, buffer, size, NULL); -} - -XLinkError_t XLinkGateReadData_(streamId_t streamId, streamPacketDesc_t* const packet, XLinkTimespec* outTSend) -{ - XLINK_RET_IF(packet == NULL); - - float opTime = 0; - xLinkDesc_t *link = NULL; - XLINK_RET_IF(getLinkByStreamId(streamId, &link)); - streamId_t streamIdOnly = EXTRACT_STREAM_ID(streamId); - - xLinkEvent_t event = {0}; - XLINK_INIT_EVENT(event, streamIdOnly, XLINK_READ_REQ, - 0, NULL, link->deviceHandle); - event.header.flags.bitField.moveSemantic = 1; - XLINK_RET_IF(addEventWithPerf(&event, &opTime, XLINK_NO_RW_TIMEOUT)); - - if (!event.data) - { - return X_LINK_ERROR; - } - *packet = *(streamPacketDesc_t *)event.data; - - // free the allocation from movePacketFromStream() - // done within this same XLink module so the same C runtime is used - free(event.data); - - if (glHandler->profEnable) - { - glHandler->profilingData.totalReadBytes += packet->length; - glHandler->profilingData.totalReadTime += opTime; - } - link->profilingData.totalReadBytes += packet->length; - link->profilingData.totalReadTime += opTime; - - - const XLinkError_t retVal = XLinkReleaseData(streamId); - if (retVal != X_LINK_SUCCESS) { - // severe error; deallocate here as the caller might forget to dealloc on errors; or be less able to manage - XLinkPlatformDeallocateData(packet->data, ALIGN_UP_INT32((int32_t)packet->length, __CACHE_LINE_SIZE), __CACHE_LINE_SIZE); - packet->data = NULL; - packet->length = 0; - } - return retVal; -} - -XLinkError_t XLinkGateReadData(streamId_t const streamId, streamPacketDesc_t* const packet) -{ - return XLinkGateReadData_(streamId, packet, NULL); -} - // ------------------------------------ // Helpers declaration. Begin. // ------------------------------------ diff --git a/src/shared/XLinkDispatcher.c b/src/shared/XLinkDispatcher.c index 3b7c9f1b..95e066f1 100644 --- a/src/shared/XLinkDispatcher.c +++ b/src/shared/XLinkDispatcher.c @@ -520,13 +520,9 @@ char* TypeToStr(int type) case XLINK_STATIC_RESP_LAST: return "XLINK_STATIC_RESP_LAST"; case XLINK_READ_REL_SPEC_REQ: return "XLINK_READ_REL_SPEC_REQ"; case XLINK_WRITE_FD_REQ: return "XLINK_WRITE_FD_REQ"; - case XLINK_GATE_WRITE_REQ: return "XLINK_GATE_WRITE_REQ"; - case XLINK_GATE_READ_REQ: return "XLINK_GATE_READ_REQ"; case XLINK_REQUEST_LAST: return "XLINK_REQUEST_LAST"; case XLINK_READ_REL_SPEC_RESP: return "XLINK_READ_REL_SPEC_RESP"; case XLINK_WRITE_FD_RESP: return "XLINK_WRITE_FD_REQ"; - case XLINK_GATE_WRITE_RESP: return "XLINK_GATE_WRITE_RESP"; - case XLINK_GATE_READ_RESP: return "XLINK_GATE_READ_RESP"; case XLINK_RESP_LAST: return "XLINK_RESP_LAST"; default: diff --git a/src/shared/XLinkDispatcherImpl.c b/src/shared/XLinkDispatcherImpl.c index ab61fd39..e0d84288 100644 --- a/src/shared/XLinkDispatcherImpl.c +++ b/src/shared/XLinkDispatcherImpl.c @@ -335,12 +335,6 @@ int dispatcherEventSend(xLinkEvent_t *event, XLinkTimespec* sendTime) mvLog(MVLOG_ERROR,"Write failed %d\n", rc); return rc; } - } else if (event->header.type == XLINK_GATE_WRITE_REQ) { - rc = XLinkPlatformGateWrite(&event->deviceHandle, event->data, event->header.size); - if(rc < 0) { - mvLog(MVLOG_ERROR,"Write failed %d\n", rc); - return rc; - } } return 0; @@ -392,7 +386,6 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response, switch (event->header.type){ case XLINK_WRITE_REQ: case XLINK_WRITE_FD_REQ: - case XLINK_GATE_WRITE_REQ: { //in case local tries to write after it issues close (writeSize is zero) stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); @@ -430,7 +423,6 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response, break; } case XLINK_READ_REQ: - case XLINK_GATE_READ_REQ: { stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); if(!stream) { @@ -604,28 +596,8 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response (int)response->header.streamId, (int)xxx); } break; - case XLINK_GATE_WRITE_REQ: - { - //let remote write immediately as we have a local buffer for the data - response->header.type = XLINK_GATE_WRITE_RESP; - response->header.size = event->header.size; - response->header.streamId = event->header.streamId; - response->deviceHandle = event->deviceHandle; - XLINK_EVENT_ACKNOWLEDGE(response); - - // we got some data. We should unblock a blocked read - int xxx = DispatcherUnblockEvent(-1, - XLINK_GATE_READ_REQ, - response->header.streamId, - event->deviceHandle.xLinkFD); - (void) xxx; - mvLog(MVLOG_DEBUG,"unblocked from stream %d %d\n", - (int)response->header.streamId, (int)xxx); - } case XLINK_READ_REQ: break; - case XLINK_GATE_READ_REQ: - break; case XLINK_READ_REL_SPEC_REQ: XLINK_EVENT_ACKNOWLEDGE(response); response->header.type = XLINK_READ_REL_SPEC_RESP; @@ -769,14 +741,10 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response break; case XLINK_WRITE_RESP: break; - case XLINK_GATE_WRITE_RESP: - break; case XLINK_WRITE_FD_RESP: break; case XLINK_READ_RESP: break; - case XLINK_GATE_READ_RESP: - break; case XLINK_READ_REL_RESP: break; case XLINK_READ_REL_SPEC_RESP: From 58c5cb8b56a3e814a3c27a937c6e3a1456b36555 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 5 Aug 2025 11:45:54 +0200 Subject: [PATCH 24/78] Added conditional compilation for windows for usb_host_ep --- src/pc/protocols/usb_host_ep.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 06b84453..8403c0f6 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -15,9 +15,11 @@ #include #include +#if defined(__unix__) #include #include #include +#endif #include @@ -158,6 +160,7 @@ int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void { isServer = true; +#if defined(__unix__) int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); @@ -169,6 +172,7 @@ int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void usbFdWrite = outfd; *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); +#endif return 0; } @@ -179,6 +183,7 @@ int usbEpPlatformClose(void *fdKey) int error; if (isServer) { +#if defined(__unix__) if (usbFdRead != -1){ close(usbFdRead); usbFdRead = -1; @@ -188,6 +193,7 @@ int usbEpPlatformClose(void *fdKey) close(usbFdWrite); usbFdWrite = -1; } +#endif } else { error = libusb_release_interface(dev_handle, INTERFACE_XLINK); if (error != LIBUSB_SUCCESS) { @@ -212,12 +218,14 @@ int usbEpPlatformRead(void *fdKey, void *data, int size) int rc = 0; if (isServer) { +#if defined(__unix__) if(usbFdRead < 0) { return -1; } rc = read(usbFdRead, data, size); +#endif } else { rc = libusb_bulk_transfer(dev_handle, usbFdRead, (unsigned char*)data, size, &rc, TIMEOUT); } @@ -230,13 +238,14 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) int rc = 0; if (isServer) { +#if defined(__unix__) if(usbFdWrite < 0) { return -1; } rc = write(usbFdWrite, data, size); - +#endif } else { rc = libusb_bulk_transfer(dev_handle, usbFdWrite, (unsigned char*)data, size, &rc, TIMEOUT); } From 674b295ce8a260c8817ee3396a1403d8697ce226 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 5 Aug 2025 12:08:24 +0200 Subject: [PATCH 25/78] Added wrapper for XLinkPlatformGate functions --- include/XLink/XLink.h | 9 +++++++++ src/shared/XLinkData.c | 10 ++++++++++ 2 files changed, 19 insertions(+) diff --git a/include/XLink/XLink.h b/include/XLink/XLink.h index e6db94ca..68cfc311 100644 --- a/include/XLink/XLink.h +++ b/include/XLink/XLink.h @@ -306,6 +306,15 @@ XLinkError_t XLinkWriteData(streamId_t const streamId, const uint8_t* buffer, in XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, int size, XLinkTimespec* outTSend); +/** + * @brief Sends/Receives a message to Gate via USB + * @param[in] data - Data to be transmitted/collected + * @param[in] size - The data size + * @return Status code of the operation: X_LINK_SUCCESS (0) for success + */ +XLinkError_t XLinkGateWrite(void *data, int size); +XLinkError_t XLinkGateRead(void *data, int size); + /** * @brief Sends a package to initiate the writing of a file descriptor * @warning Actual size of the written data is ALIGN_UP(size, 64) diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index bf3a885b..1a845727 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -117,6 +117,16 @@ XLinkError_t XLinkCloseStream(streamId_t const streamId) return X_LINK_SUCCESS; } +XLinkError_t XLinkGateWrite(void *data, int size) +{ + return XLinkPlatformGateWrite(data, size); +} + +XLinkError_t XLinkGateRead(void *data, int size) +{ + return XLinkPlatformGateRead(data, size); +} + XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, int size, XLinkTimespec* outTSend) { From 798a74ed42c6a5c92d693a8f8af1fbcd0eae6aea Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 3 Sep 2025 14:26:27 +0200 Subject: [PATCH 26/78] Getting state from gate --- src/pc/protocols/usb_host_ep.cpp | 46 ++++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 8403c0f6..9433832c 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -21,6 +21,9 @@ #include #endif +#include +#include + #include /* Vendor ID */ @@ -322,12 +325,51 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, r = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); + + /* Now we claim our ffs interfaces */ + r = libusb_claim_interface(dev_handle, INTERFACE_GATE); + if (r != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return r; + } + + struct USBRequest_t { + uint32_t RequestNum; + uint32_t RequestSize; + } request = { + .RequestNum = 12, + .RequestSize = 0, + }; + + r = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &r, TIMEOUT); + r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&request, sizeof(request), &r, TIMEOUT); + + std::vector respBuffer; + respBuffer.reserve(request.RequestSize + 1); + r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], request.RequestSize, &r, TIMEOUT); + respBuffer[request.RequestSize]= '\0'; + + size_t strLen = strlen((const char*)&respBuffer[0]); + char string[strLen + 1]; + strcpy(string, (const char*)&respBuffer[0]); + string[strLen] = '\0'; + + struct GateResponse_t { + uint32_t state; + uint32_t protocol; + uint32_t platform; + } response; + + memcpy(&response, &respBuffer[strLen], request.RequestSize - strLen - 1); + + libusb_close(dev_handle); int numDevicesFound = 0; // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)response.platform; out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_GATE; + out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)response.state; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); From c72e6701d994b04a5b3f8ebe466c8567c0faea56 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 11 Sep 2025 14:17:31 +0200 Subject: [PATCH 27/78] Made sure the response in usbepGetDevices isn't larger than the struct --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 9433832c..8d0b97aa 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -361,7 +361,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, uint32_t platform; } response; - memcpy(&response, &respBuffer[strLen], request.RequestSize - strLen - 1); + memcpy(&response, &respBuffer[strLen], sizeof(response)); libusb_close(dev_handle); int numDevicesFound = 0; From 8ddeb2b2c37ca85f7af6f3a8196fdea1533ed92d Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Fri, 12 Sep 2025 18:01:30 +0200 Subject: [PATCH 28/78] Separated the bytes transferred variable in bulk transfer for device detection in USB EP --- src/pc/protocols/usb_host_ep.cpp | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 8d0b97aa..4d70af97 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -37,7 +37,7 @@ #define INTERFACE_XLINK 1 #define INTERFACE_XLINK_NAME "Luxonis Communication Interface" -/* Base ndpoint address used for output */ +/* Base endpoint address used for output */ #define ENDPOINT_OUT_BASE 0x01 /* Base endpoint address used for input */ @@ -342,12 +342,17 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, .RequestSize = 0, }; - r = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &r, TIMEOUT); - r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&request, sizeof(request), &r, TIMEOUT); + int transferred; + r = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); + r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); + + if (request.RequestNum != 0) { + return X_LINK_PLATFORM_ERROR; + } std::vector respBuffer; respBuffer.reserve(request.RequestSize + 1); - r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], request.RequestSize, &r, TIMEOUT); + r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], request.RequestSize, &transferred, TIMEOUT); respBuffer[request.RequestSize]= '\0'; size_t strLen = strlen((const char*)&respBuffer[0]); From 2f5db2d8eecb1e8bb72a59fc63ae9ec3d521b04e Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 17 Sep 2025 11:38:44 +0200 Subject: [PATCH 29/78] usb_host_ep: Added checks in Gate functions --- src/pc/protocols/usb_host_ep.cpp | 176 ++++++++++++++++++------------- 1 file changed, 101 insertions(+), 75 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 4d70af97..59a4edea 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -53,7 +53,8 @@ #define ENDPOINT_IN_OFFSET 1 /* Transfer timeout */ -#define TIMEOUT 2000 +//#define TIMEOUT 2000 +#define TIMEOUT 0 static int usbFdRead, usbFdWrite; static bool isServer; @@ -260,23 +261,23 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { int error = 0; - libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (dev_handle == NULL) { + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { return LIBUSB_ERROR_NO_DEVICE; } - error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + error = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); return error; } // Check if the interface exists - libusb_device* dev = libusb_get_device(dev_handle); + libusb_device* dev = libusb_get_device(gate_dev_handle); struct libusb_config_descriptor* config; error = libusb_get_active_config_descriptor(dev, &config); if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); return error; } @@ -286,7 +287,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { if(config->interface[i].altsetting[0].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, config->interface[i].altsetting[0].iInterface, name_buf, sizeof(name_buf)); @@ -306,7 +307,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, libusb_free_config_descriptor(config); if (!found) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); *out_amountOfFoundDevices = 0; @@ -323,11 +324,11 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, // Get device mxid char mxId[32] = {'\0'}; - r = libusb_get_string_descriptor_ascii(dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); + r = libusb_get_string_descriptor_ascii(gate_dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); /* Now we claim our ffs interfaces */ - r = libusb_claim_interface(dev_handle, INTERFACE_GATE); + r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (r != LIBUSB_SUCCESS) { libusb_exit(ctx); @@ -335,57 +336,82 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } struct USBRequest_t { - uint32_t RequestNum; - uint32_t RequestSize; - } request = { - .RequestNum = 12, - .RequestSize = 0, + uint32_t RequestNum; + uint32_t RequestSize; }; int transferred; - r = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); - r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); - - if (request.RequestNum != 0) { + USBRequest_t request = { + .RequestNum = 12, + .RequestSize = 0, + }; + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); + if (transferred < sizeof(request) || r != 0) { return X_LINK_PLATFORM_ERROR; } - std::vector respBuffer; - respBuffer.reserve(request.RequestSize + 1); - r = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], request.RequestSize, &transferred, TIMEOUT); - respBuffer[request.RequestSize]= '\0'; - - size_t strLen = strlen((const char*)&respBuffer[0]); - char string[strLen + 1]; - strcpy(string, (const char*)&respBuffer[0]); - string[strLen] = '\0'; + USBRequest_t response; + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&response, sizeof(response), &transferred, TIMEOUT); + if (transferred < sizeof(request) || r != 0) { + return X_LINK_PLATFORM_ERROR; + } - struct GateResponse_t { - uint32_t state; - uint32_t protocol; - uint32_t platform; - } response; + if (response.RequestNum != 0) { + return X_LINK_PLATFORM_ERROR; + } - memcpy(&response, &respBuffer[strLen], sizeof(response)); - - libusb_close(dev_handle); - int numDevicesFound = 0; - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)response.platform; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)response.state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strcpy(out_foundDevices[numDevicesFound].mxid, mxId); - numDevicesFound++; - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - libusb_close(dev_handle); + if (response.RequestSize != 0) { + std::vector respBuffer; + respBuffer.resize(response.RequestSize + 1); + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); + respBuffer[response.RequestSize]= '\0'; + + size_t strLen = strlen((const char*)&respBuffer[0]); + char string[strLen + 1]; + strcpy(string, (const char*)&respBuffer[0]); + string[strLen] = '\0'; + + struct GateResponse_t { + uint32_t state; + uint32_t protocol; + uint32_t platform; + } gateResponse; + + memcpy(&gateResponse, &respBuffer[strLen], sizeof(response)); + + int numDevicesFound = 0; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; + out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strcpy(out_foundDevices[numDevicesFound].mxid, mxId); + numDevicesFound++; + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + } else { + int numDevicesFound = 0; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; + out_foundDevices[numDevicesFound].state = X_LINK_GATE; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strcpy(out_foundDevices[numDevicesFound].mxid, mxId); + numDevicesFound++; + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + } + + libusb_close(gate_dev_handle); return X_LINK_PLATFORM_SUCCESS; } @@ -395,8 +421,8 @@ int usbEpPlatformGateRead(void *data, int size) int rc = 0; /* Get our device */ - libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (dev_handle == NULL) { + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { libusb_exit(ctx); rc = LIBUSB_ERROR_NO_DEVICE; @@ -406,19 +432,19 @@ int usbEpPlatformGateRead(void *data, int size) /* Not strictly necessary, but it is better to use it, * as we're using kernel modules together with our interfaces */ - rc = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return rc; } - libusb_device* dev = libusb_get_device(dev_handle); + libusb_device* dev = libusb_get_device(gate_dev_handle); struct libusb_config_descriptor* config; rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return rc; } @@ -429,7 +455,7 @@ int usbEpPlatformGateRead(void *data, int size) for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { for (int j = 0; j < config->interface[i].num_altsetting; j++) { if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, config->interface[i].altsetting[j].iInterface, name_buf, sizeof(name_buf)); @@ -447,23 +473,23 @@ int usbEpPlatformGateRead(void *data, int size) libusb_free_config_descriptor(config); if (!found) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return LIBUSB_ERROR_NO_DEVICE; } /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(dev_handle, INTERFACE_GATE); + rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { libusb_exit(ctx); return rc; } - rc = libusb_bulk_transfer(dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - libusb_close(dev_handle); + libusb_close(gate_dev_handle); return rc; } @@ -473,8 +499,8 @@ int usbEpPlatformGateWrite(void *data, int size) int rc = 0; /* Get our device */ - libusb_device_handle *dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (dev_handle == NULL) { + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { libusb_exit(ctx); rc = LIBUSB_ERROR_NO_DEVICE; @@ -484,19 +510,19 @@ int usbEpPlatformGateWrite(void *data, int size) /* Not strictly necessary, but it is better to use it, * as we're using kernel modules together with our interfaces */ - rc = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return rc; } - libusb_device* dev = libusb_get_device(dev_handle); + libusb_device* dev = libusb_get_device(gate_dev_handle); struct libusb_config_descriptor* config; rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return rc; } @@ -507,7 +533,7 @@ int usbEpPlatformGateWrite(void *data, int size) for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { for (int j = 0; j < config->interface[i].num_altsetting; j++) { if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, config->interface[i].altsetting[j].iInterface, name_buf, sizeof(name_buf)); @@ -525,23 +551,23 @@ int usbEpPlatformGateWrite(void *data, int size) libusb_free_config_descriptor(config); if (!found) { - libusb_close(dev_handle); + libusb_close(gate_dev_handle); libusb_exit(ctx); return LIBUSB_ERROR_NO_DEVICE; } /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(dev_handle, INTERFACE_GATE); + rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { libusb_exit(ctx); return rc; } - rc = libusb_bulk_transfer(dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - libusb_close(dev_handle); + libusb_close(gate_dev_handle); return rc; } From 3606f517d56012c4fc72c49b235d42c5f6d260da Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 18 Sep 2025 11:54:56 +0200 Subject: [PATCH 30/78] Fixed string size calculation. --- src/pc/protocols/usb_host_ep.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 59a4edea..43f2028c 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -362,14 +362,8 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, if (response.RequestSize != 0) { std::vector respBuffer; - respBuffer.resize(response.RequestSize + 1); + respBuffer.resize(response.RequestSize); r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); - respBuffer[response.RequestSize]= '\0'; - - size_t strLen = strlen((const char*)&respBuffer[0]); - char string[strLen + 1]; - strcpy(string, (const char*)&respBuffer[0]); - string[strLen] = '\0'; struct GateResponse_t { uint32_t state; @@ -377,6 +371,11 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, uint32_t platform; } gateResponse; + size_t strLen = response.RequestSize - sizeof(gateResponse); + char string[strLen + 1]; + memcpy(string, (const char*)&respBuffer[0], strLen); + string[strLen] = '\0'; + memcpy(&gateResponse, &respBuffer[strLen], sizeof(response)); int numDevicesFound = 0; From d5abd87b44e171bef99245c12334ef8e246a0086 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 18 Sep 2025 11:58:13 +0200 Subject: [PATCH 31/78] usb_host_ep: Fixed typo --- src/pc/protocols/usb_host_ep.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 43f2028c..a5226c77 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -376,7 +376,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, memcpy(string, (const char*)&respBuffer[0], strLen); string[strLen] = '\0'; - memcpy(&gateResponse, &respBuffer[strLen], sizeof(response)); + memcpy(&gateResponse, &respBuffer[strLen], sizeof(gateResponse)); int numDevicesFound = 0; // Everything passed, fillout details of found device From 57b192bed50c08e09b0809f9d08439f033c1a539 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 18 Sep 2025 12:05:23 +0200 Subject: [PATCH 32/78] usb_host_ep: Corrected dynamic platform assignment. --- src/pc/protocols/usb_host_ep.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index a5226c77..622251bf 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -381,7 +381,13 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, int numDevicesFound = 0; // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; + if (gateResponse.platform == 4) { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + } else if (gateResponse.platform == 3) { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; + } else { + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)0; + } out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); From 085e25dc3a55219d7cee042c387e5846e86bf119 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 10:56:29 +0200 Subject: [PATCH 33/78] usb_host_ep: Reinstated timeout --- src/pc/protocols/usb_host_ep.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 622251bf..1ff15547 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -53,8 +53,7 @@ #define ENDPOINT_IN_OFFSET 1 /* Transfer timeout */ -//#define TIMEOUT 2000 -#define TIMEOUT 0 +#define TIMEOUT 2000 static int usbFdRead, usbFdWrite; static bool isServer; From c8fb130ab0fb65f75789098ca9561736a4621255 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:06:25 +0200 Subject: [PATCH 34/78] usb_host_ep: Using async transfers. --- src/pc/protocols/usb_host_ep.cpp | 110 ++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 1ff15547..6483eef5 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -216,6 +216,37 @@ int usbEpPlatformClose(void *fdKey) return EXIT_SUCCESS; } +struct AsyncContext { + int result; + int transferred; + bool done; +}; + +static void bulk_transfer_callback(struct libusb_transfer *transfer) { + AsyncContext* ctx = (AsyncContext*)transfer->user_data; + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + ctx->transferred = transfer->actual_length; + ctx->result = 0; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ctx->transferred = 0; + ctx->result = LIBUSB_ERROR_NO_DEVICE; + break; + default: + ctx->transferred = 0; + ctx->result = LIBUSB_ERROR_OTHER; + break; + } + + ctx->done = true; + + // Free resources + free(transfer->buffer); + libusb_free_transfer(transfer); +} + int usbEpPlatformRead(void *fdKey, void *data, int size) { int rc = 0; @@ -230,7 +261,44 @@ int usbEpPlatformRead(void *fdKey, void *data, int size) rc = read(usbFdRead, data, size); #endif } else { - rc = libusb_bulk_transfer(dev_handle, usbFdRead, (unsigned char*)data, size, &rc, TIMEOUT); + unsigned char* buffer = (unsigned char*)malloc(size); + if (!buffer) return LIBUSB_ERROR_NO_MEM; + + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + free(buffer); + return LIBUSB_ERROR_NO_MEM; + } + + AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; + + libusb_fill_bulk_transfer( + transfer, + dev_handle, + usbFdRead, + buffer, + size, + bulk_transfer_callback, + &ctx, + TIMEOUT + ); + + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { + free(buffer); + libusb_free_transfer(transfer); + return LIBUSB_ERROR_OTHER; + } + + // Wait for transfer completion + while (!asyncCtx.done) { + libusb_handle_events_completed(ctx, NULL); + } + + if (asyncCtx.result == 0 && asyncCtx.transferred > 0) { + memcpy(data, buffer, asyncCtx.transferred); + } + + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } return rc; @@ -250,7 +318,41 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) rc = write(usbFdWrite, data, size); #endif } else { - rc = libusb_bulk_transfer(dev_handle, usbFdWrite, (unsigned char*)data, size, &rc, TIMEOUT); + unsigned char* buffer = (unsigned char*)malloc(size); + if (!buffer) return LIBUSB_ERROR_NO_MEM; + memcpy(buffer, data, size); + + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + free(buffer); + return LIBUSB_ERROR_NO_MEM; + } + + AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; + + libusb_fill_bulk_transfer( + transfer, + dev_handle, + usbFdWrite, + buffer, + size, + bulk_transfer_callback, + &ctx, + TIMEOUT + ); + + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { + free(buffer); + libusb_free_transfer(transfer); + return LIBUSB_ERROR_OTHER; + } + + // Wait for transfer completion + while (!asyncCtx.done) { + libusb_handle_events_completed(ctx, NULL); + } + + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } return rc; @@ -371,7 +473,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } gateResponse; size_t strLen = response.RequestSize - sizeof(gateResponse); - char string[strLen + 1]; + char *string = (char*)malloc(strLen + 1); memcpy(string, (const char*)&respBuffer[0], strLen); string[strLen] = '\0'; @@ -397,6 +499,8 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, // Write the number of found devices *out_amountOfFoundDevices = numDevicesFound; + + free(string); } else { int numDevicesFound = 0; // Everything passed, fillout details of found device From 551b333b1bd86c23178dfb6ebd1521f5d1e5dab4 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:12:15 +0200 Subject: [PATCH 35/78] usb_host_ep: Fixed use after free. --- src/pc/protocols/usb_host_ep.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 6483eef5..e62a3131 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -242,8 +242,6 @@ static void bulk_transfer_callback(struct libusb_transfer *transfer) { ctx->done = true; - // Free resources - free(transfer->buffer); libusb_free_transfer(transfer); } @@ -298,6 +296,8 @@ int usbEpPlatformRead(void *fdKey, void *data, int size) memcpy(data, buffer, asyncCtx.transferred); } + free(buffer); + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } @@ -352,6 +352,8 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) libusb_handle_events_completed(ctx, NULL); } + free(buffer); + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } From c856a7667b6c2f5652517c8c746a174ec728f236 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:20:29 +0200 Subject: [PATCH 36/78] usb_host_ep: Async fixes in handling, buffers and volatile. --- src/pc/protocols/usb_host_ep.cpp | 37 ++++++++------------------------ 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index e62a3131..d2cb9195 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -216,10 +216,10 @@ int usbEpPlatformClose(void *fdKey) return EXIT_SUCCESS; } -struct AsyncContext { +struct AsyncContext { int result; int transferred; - bool done; + volatile bool done; }; static void bulk_transfer_callback(struct libusb_transfer *transfer) { @@ -259,45 +259,34 @@ int usbEpPlatformRead(void *fdKey, void *data, int size) rc = read(usbFdRead, data, size); #endif } else { - unsigned char* buffer = (unsigned char*)malloc(size); - if (!buffer) return LIBUSB_ERROR_NO_MEM; - struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { - free(buffer); return LIBUSB_ERROR_NO_MEM; } - AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; + AsyncContext asyncCtx = { 0 }; libusb_fill_bulk_transfer( transfer, dev_handle, usbFdRead, - buffer, + (unsigned char*)data, size, bulk_transfer_callback, - &ctx, + &asyncCtx, TIMEOUT ); if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - free(buffer); libusb_free_transfer(transfer); return LIBUSB_ERROR_OTHER; } // Wait for transfer completion while (!asyncCtx.done) { - libusb_handle_events_completed(ctx, NULL); - } - - if (asyncCtx.result == 0 && asyncCtx.transferred > 0) { - memcpy(data, buffer, asyncCtx.transferred); + libusb_handle_events(ctx); } - free(buffer); - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } @@ -318,13 +307,8 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) rc = write(usbFdWrite, data, size); #endif } else { - unsigned char* buffer = (unsigned char*)malloc(size); - if (!buffer) return LIBUSB_ERROR_NO_MEM; - memcpy(buffer, data, size); - struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { - free(buffer); return LIBUSB_ERROR_NO_MEM; } @@ -334,26 +318,23 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) transfer, dev_handle, usbFdWrite, - buffer, + (unsigned char*)data, size, bulk_transfer_callback, - &ctx, + &asyncCtx, TIMEOUT ); if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - free(buffer); libusb_free_transfer(transfer); return LIBUSB_ERROR_OTHER; } // Wait for transfer completion while (!asyncCtx.done) { - libusb_handle_events_completed(ctx, NULL); + libusb_handle_events(ctx); } - free(buffer); - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; } From 80e539435aea1137d5b34994e1163df3feb20b3c Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:26:08 +0200 Subject: [PATCH 37/78] usb_host_ep: Added usb disconnection/reconnection callback --- src/pc/protocols/usb_host_ep.cpp | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index d2cb9195..55cd163c 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -55,6 +55,8 @@ /* Transfer timeout */ #define TIMEOUT 2000 +static bool deviceDisconnected = false; + static int usbFdRead, usbFdWrite; static bool isServer; @@ -70,6 +72,19 @@ int usbEpInitialize() { return 0; } +int LIBUSB_CALL hotplug_cb(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data) { + if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + printf("Device disconnected\n"); + deviceDisconnected = true; + } + + if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + deviceDisconnected = false; + } + + return 0; // return 0 to keep callback active +} + int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) { int error; @@ -153,6 +168,20 @@ int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void */ usbFdWrite = ENDPOINT_OUT_BASE + ENDPOINT_OUT_OFFSET; usbFdRead = ENDPOINT_IN_BASE + ENDPOINT_IN_OFFSET; + + deviceDisconnected = false; + + libusb_hotplug_register_callback( + ctx, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_NO_FLAGS, + VENDOR_ID, + PRODUCT_ID, + LIBUSB_HOTPLUG_MATCH_ANY, + hotplug_cb, + NULL, + NULL + ); *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); @@ -259,6 +288,10 @@ int usbEpPlatformRead(void *fdKey, void *data, int size) rc = read(usbFdRead, data, size); #endif } else { + if (deviceDisconnected) { + return LIBUSB_ERROR_NO_DEVICE; + } + struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { return LIBUSB_ERROR_NO_MEM; @@ -307,6 +340,10 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) rc = write(usbFdWrite, data, size); #endif } else { + if (deviceDisconnected) { + return LIBUSB_ERROR_NO_DEVICE; + } + struct libusb_transfer *transfer = libusb_alloc_transfer(0); if (!transfer) { return LIBUSB_ERROR_NO_MEM; From 3010d2a4a36d6766d06fdb3929dbdcb3bde17347 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:40:29 +0200 Subject: [PATCH 38/78] usb_host_ep: Setup gate_ctx for gate transfers. --- src/pc/protocols/usb_host_ep.cpp | 40 +- "\303\271" | 708 +++++++++++++++++++++++++++++++ 2 files changed, 734 insertions(+), 14 deletions(-) create mode 100644 "\303\271" diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 55cd163c..2a40a04c 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -381,8 +381,11 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { + libusb_context *gate_ctx; + libusb_init(&gate_ctx); + int error = 0; - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { return LIBUSB_ERROR_NO_DEVICE; } @@ -451,7 +454,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, /* Now we claim our ffs interfaces */ r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (r != LIBUSB_SUCCESS) { - libusb_exit(ctx); + libusb_exit(gate_ctx); return r; } @@ -540,18 +543,22 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } libusb_close(gate_dev_handle); + libusb_exit(gate_ctx); + return X_LINK_PLATFORM_SUCCESS; } int usbEpPlatformGateRead(void *data, int size) { + libusb_context *gate_ctx; + libusb_init(&gate_ctx); int rc = 0; /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { - libusb_exit(ctx); + libusb_exit(gate_ctx); rc = LIBUSB_ERROR_NO_DEVICE; return rc; @@ -563,7 +570,7 @@ int usbEpPlatformGateRead(void *data, int size) rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -573,7 +580,7 @@ int usbEpPlatformGateRead(void *data, int size) rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -602,7 +609,7 @@ int usbEpPlatformGateRead(void *data, int size) if (!found) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return LIBUSB_ERROR_NO_DEVICE; } @@ -610,7 +617,7 @@ int usbEpPlatformGateRead(void *data, int size) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -618,18 +625,22 @@ int usbEpPlatformGateRead(void *data, int size) rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); libusb_close(gate_dev_handle); + libusb_exit(gate_ctx); return rc; } int usbEpPlatformGateWrite(void *data, int size) { + libusb_context *gate_ctx; + libusb_init(&gate_ctx); + int rc = 0; /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { - libusb_exit(ctx); + libusb_exit(gate_ctx); rc = LIBUSB_ERROR_NO_DEVICE; return rc; @@ -641,7 +652,7 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -651,7 +662,7 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -680,7 +691,7 @@ int usbEpPlatformGateWrite(void *data, int size) if (!found) { libusb_close(gate_dev_handle); - libusb_exit(ctx); + libusb_exit(gate_ctx); return LIBUSB_ERROR_NO_DEVICE; } @@ -688,7 +699,7 @@ int usbEpPlatformGateWrite(void *data, int size) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { - libusb_exit(ctx); + libusb_exit(gate_ctx); return rc; } @@ -696,6 +707,7 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); libusb_close(gate_dev_handle); + libusb_exit(gate_ctx); return rc; } diff --git "a/\303\271" "b/\303\271" new file mode 100644 index 00000000..24d2ec63 --- /dev/null +++ "b/\303\271" @@ -0,0 +1,708 @@ +// project +#include +#define MVLOG_UNIT_NAME xLinkUsb + +#include "XLink/XLinkLog.h" +#include "XLink/XLinkPlatform.h" +#include "XLink/XLinkPublicDefines.h" +#include "usb_host_ep.h" +#include "../PlatformDeviceFd.h" +#include "usb_mx_id.h" +#include "usb_host.h" + +// std +#include +#include +#include + +#if defined(__unix__) +#include +#include +#include +#endif + +#include +#include + +#include + +/* Vendor ID */ +#define VENDOR_ID 0x05c6 +#define PRODUCT_ID 0x4321 + +/* Interface number for ffs.gate */ +#define INTERFACE_GATE 0 + +/* Interface number for ffs.xlink */ +#define INTERFACE_XLINK 1 +#define INTERFACE_XLINK_NAME "Luxonis Communication Interface" + +/* Base endpoint address used for output */ +#define ENDPOINT_OUT_BASE 0x01 + +/* Base endpoint address used for input */ +#define ENDPOINT_IN_BASE 0x81 + +/* The endpoint structure is the following + * - Gate: first endpoint + * - XLink: second endpoint + * - ADB: third endpoint + * - etc + */ +#define ENDPOINT_OUT_OFFSET 1 +#define ENDPOINT_IN_OFFSET 1 + +/* Transfer timeout */ +#define TIMEOUT 2000 + +static bool deviceDisconnected = false; + +static int usbFdRead, usbFdWrite; +static bool isServer; + +static libusb_context *ctx = NULL; +static libusb_device_handle *dev_handle = NULL; + +int usbEpInitialize() { + int error; + + /* Initialize libusb */ + libusb_init(&ctx); + + return 0; +} + +int LIBUSB_CALL hotplug_cb(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data) { + if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { + printf("Device disconnected\n"); + deviceDisconnected = true; + } + + if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { + deviceDisconnected = false; + } + + return 0; // return 0 to keep callback active +} + +int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) +{ + int error; + isServer = false; + + /* Get our device */ + dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (dev_handle == NULL) { + libusb_exit(ctx); + + error = LIBUSB_ERROR_NO_DEVICE; + return error; + } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); + if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + + return error; + } + + libusb_device* dev = libusb_get_device(dev_handle); + struct libusb_config_descriptor* config; + error = libusb_get_active_config_descriptor(dev, &config); + if (error != LIBUSB_SUCCESS) { + libusb_close(dev_handle); + libusb_exit(ctx); + return error; + } + + // Try claiming interface 0 to test if it's in use + bool found = false; + bool foundGate = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(dev_handle, + config->interface[i].altsetting[j].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + if(!foundGate) { + foundGate = true; + } else { + found = true; + break; + } + } + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(dev_handle); + libusb_exit(ctx); + + return LIBUSB_ERROR_NO_DEVICE; + } + + /* Now we claim our ffs interfaces */ + error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); + if (error != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return error; + } + + /* We get the first EP_OUT and EP_IN for our interfaces + * In the way we initialized our usb-gadget on our device + */ + usbFdWrite = ENDPOINT_OUT_BASE + ENDPOINT_OUT_OFFSET; + usbFdRead = ENDPOINT_IN_BASE + ENDPOINT_IN_OFFSET; + + deviceDisconnected = false; + + libusb_hotplug_register_callback( + ctx, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + LIBUSB_HOTPLUG_NO_FLAGS, + VENDOR_ID, + PRODUCT_ID, + LIBUSB_HOTPLUG_MATCH_ANY, + hotplug_cb, + NULL, + NULL + ); + + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); + + return 0; +} + +int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) +{ + isServer = true; + +#if defined(__unix__) + int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); + int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); + + if(outfd < 0 || infd < 0) { + return -1; + } + + usbFdRead = infd; + usbFdWrite = outfd; + + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); +#endif + + return 0; +} + + +int usbEpPlatformClose(void *fdKey) +{ + int error; + + if (isServer) { +#if defined(__unix__) + if (usbFdRead != -1){ + close(usbFdRead); + usbFdRead = -1; + } + + if (usbFdWrite != -1){ + close(usbFdWrite); + usbFdWrite = -1; + } +#endif + } else { + error = libusb_release_interface(dev_handle, INTERFACE_XLINK); + if (error != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return error; + } + + /* Release the device and exit */ + libusb_close(dev_handle); + } + + libusb_exit(ctx); + + error = EXIT_SUCCESS; + + return EXIT_SUCCESS; +} + +struct AsyncContext { + int result; + int transferred; + volatile bool done; +}; + +static void bulk_transfer_callback(struct libusb_transfer *transfer) { + AsyncContext* ctx = (AsyncContext*)transfer->user_data; + + switch (transfer->status) { + case LIBUSB_TRANSFER_COMPLETED: + ctx->transferred = transfer->actual_length; + ctx->result = 0; + break; + case LIBUSB_TRANSFER_NO_DEVICE: + ctx->transferred = 0; + ctx->result = LIBUSB_ERROR_NO_DEVICE; + break; + default: + ctx->transferred = 0; + ctx->result = LIBUSB_ERROR_OTHER; + break; + } + + ctx->done = true; + + libusb_free_transfer(transfer); +} + +int usbEpPlatformRead(void *fdKey, void *data, int size) +{ + int rc = 0; + + if (isServer) { +#if defined(__unix__) + if(usbFdRead < 0) + { + return -1; + } + + rc = read(usbFdRead, data, size); +#endif + } else { + if (deviceDisconnected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + return LIBUSB_ERROR_NO_MEM; + } + + AsyncContext asyncCtx = { 0 }; + + libusb_fill_bulk_transfer( + transfer, + dev_handle, + usbFdRead, + (unsigned char*)data, + size, + bulk_transfer_callback, + &asyncCtx, + TIMEOUT + ); + + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { + libusb_free_transfer(transfer); + return LIBUSB_ERROR_OTHER; + } + + // Wait for transfer completion + while (!asyncCtx.done) { + libusb_handle_events(ctx); + } + + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; + } + + return rc; +} + +int usbEpPlatformWrite(void *fdKey, void *data, int size) +{ + int rc = 0; + + if (isServer) { +#if defined(__unix__) + if(usbFdWrite < 0) + { + return -1; + } + + rc = write(usbFdWrite, data, size); +#endif + } else { + if (deviceDisconnected) { + return LIBUSB_ERROR_NO_DEVICE; + } + + struct libusb_transfer *transfer = libusb_alloc_transfer(0); + if (!transfer) { + return LIBUSB_ERROR_NO_MEM; + } + + AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; + + libusb_fill_bulk_transfer( + transfer, + dev_handle, + usbFdWrite, + (unsigned char*)data, + size, + bulk_transfer_callback, + &asyncCtx, + TIMEOUT + ); + + if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { + libusb_free_transfer(transfer); + return LIBUSB_ERROR_OTHER; + } + + // Wait for transfer completion + while (!asyncCtx.done) { + libusb_handle_events(ctx); + } + + return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; + } + + return rc; +} + +int usbepGetDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices) { + libusb_context gateCtx; + libusb_init(&gateCtx); + + int error = 0; + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { + return LIBUSB_ERROR_NO_DEVICE; + } + + error = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); + if (error != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + return error; + } + + // Check if the interface exists + libusb_device* dev = libusb_get_device(gate_dev_handle); + struct libusb_config_descriptor* config; + error = libusb_get_active_config_descriptor(dev, &config); + if (error != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + return error; + } + + // Look for INTERFACE_XLINK + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { + if(config->interface[i].altsetting[0].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, + config->interface[i].altsetting[0].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + break; + } + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(gate_dev_handle); + + *out_amountOfFoundDevices = 0; + + return X_LINK_PLATFORM_SUCCESS; + } + + // Get device descriptor + struct libusb_device_descriptor desc; + int r = libusb_get_device_descriptor(dev, &desc); + if (r < 0) { + return X_LINK_PLATFORM_ERROR; + } + + // Get device mxid + char mxId[32] = {'\0'}; + + r = libusb_get_string_descriptor_ascii(gate_dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); + + + /* Now we claim our ffs interfaces */ + r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); + if (r != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return r; + } + + struct USBRequest_t { + uint32_t RequestNum; + uint32_t RequestSize; + }; + + int transferred; + USBRequest_t request = { + .RequestNum = 12, + .RequestSize = 0, + }; + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); + if (transferred < sizeof(request) || r != 0) { + return X_LINK_PLATFORM_ERROR; + } + + USBRequest_t response; + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&response, sizeof(response), &transferred, TIMEOUT); + if (transferred < sizeof(request) || r != 0) { + return X_LINK_PLATFORM_ERROR; + } + + if (response.RequestNum != 0) { + return X_LINK_PLATFORM_ERROR; + } + + if (response.RequestSize != 0) { + std::vector respBuffer; + respBuffer.resize(response.RequestSize); + r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); + + struct GateResponse_t { + uint32_t state; + uint32_t protocol; + uint32_t platform; + } gateResponse; + + size_t strLen = response.RequestSize - sizeof(gateResponse); + char *string = (char*)malloc(strLen + 1); + memcpy(string, (const char*)&respBuffer[0], strLen); + string[strLen] = '\0'; + + memcpy(&gateResponse, &respBuffer[strLen], sizeof(gateResponse)); + + int numDevicesFound = 0; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; + if (gateResponse.platform == 4) { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + } else if (gateResponse.platform == 3) { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; + } else { + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)0; + } + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; + out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strcpy(out_foundDevices[numDevicesFound].mxid, mxId); + numDevicesFound++; + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + + free(string); + } else { + int numDevicesFound = 0; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; + out_foundDevices[numDevicesFound].state = X_LINK_GATE; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strcpy(out_foundDevices[numDevicesFound].mxid, mxId); + numDevicesFound++; + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + + } + + libusb_close(gate_dev_handle); + return X_LINK_PLATFORM_SUCCESS; +} + + +int usbEpPlatformGateRead(void *data, int size) +{ + int rc = 0; + + /* Get our device */ + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { + libusb_exit(ctx); + + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; + } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + + return rc; + } + + libusb_device* dev = libusb_get_device(gate_dev_handle); + struct libusb_config_descriptor* config; + rc = libusb_get_active_config_descriptor(dev, &config); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + return rc; + } + + // Try claiming interface 0 to test if it's in use + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, + config->interface[i].altsetting[j].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + } + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + + return LIBUSB_ERROR_NO_DEVICE; + } + + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); + if (rc != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return rc; + } + + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + + libusb_close(gate_dev_handle); + + return rc; +} + +int usbEpPlatformGateWrite(void *data, int size) +{ + if (ctx == NULL) { + return LIBUSB_ERROR; + } + + int rc = 0; + + /* Get our device */ + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); + if (gate_dev_handle == NULL) { + libusb_exit(ctx); + + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; + } + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + + return rc; + } + + libusb_device* dev = libusb_get_device(gate_dev_handle); + struct libusb_config_descriptor* config; + rc = libusb_get_active_config_descriptor(dev, &config); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + return rc; + } + + // Try claiming interface 0 to test if it's in use + bool found = false; + unsigned char name_buf[256]; + for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { + for (int j = 0; j < config->interface[i].num_altsetting; j++) { + if(config->interface[i].altsetting[j].iInterface > 0) { + int r = libusb_get_string_descriptor_ascii(gate_dev_handle, + config->interface[i].altsetting[j].iInterface, + name_buf, + sizeof(name_buf)); + + if (r > 0) { + name_buf[r] = '\0'; + if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { + found = true; + } + } + } + } + } + + libusb_free_config_descriptor(config); + + if (!found) { + libusb_close(gate_dev_handle); + libusb_exit(ctx); + + return LIBUSB_ERROR_NO_DEVICE; + } + + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); + if (rc != LIBUSB_SUCCESS) { + libusb_exit(ctx); + + return rc; + } + + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + + libusb_close(gate_dev_handle); + + return rc; +} From cc65c5ee9da6a5a9904a3c58cbeccede26fccfca Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 23 Sep 2025 13:40:49 +0200 Subject: [PATCH 39/78] Removed file. --- "\303\271" | 708 ----------------------------------------------------- 1 file changed, 708 deletions(-) delete mode 100644 "\303\271" diff --git "a/\303\271" "b/\303\271" deleted file mode 100644 index 24d2ec63..00000000 --- "a/\303\271" +++ /dev/null @@ -1,708 +0,0 @@ -// project -#include -#define MVLOG_UNIT_NAME xLinkUsb - -#include "XLink/XLinkLog.h" -#include "XLink/XLinkPlatform.h" -#include "XLink/XLinkPublicDefines.h" -#include "usb_host_ep.h" -#include "../PlatformDeviceFd.h" -#include "usb_mx_id.h" -#include "usb_host.h" - -// std -#include -#include -#include - -#if defined(__unix__) -#include -#include -#include -#endif - -#include -#include - -#include - -/* Vendor ID */ -#define VENDOR_ID 0x05c6 -#define PRODUCT_ID 0x4321 - -/* Interface number for ffs.gate */ -#define INTERFACE_GATE 0 - -/* Interface number for ffs.xlink */ -#define INTERFACE_XLINK 1 -#define INTERFACE_XLINK_NAME "Luxonis Communication Interface" - -/* Base endpoint address used for output */ -#define ENDPOINT_OUT_BASE 0x01 - -/* Base endpoint address used for input */ -#define ENDPOINT_IN_BASE 0x81 - -/* The endpoint structure is the following - * - Gate: first endpoint - * - XLink: second endpoint - * - ADB: third endpoint - * - etc - */ -#define ENDPOINT_OUT_OFFSET 1 -#define ENDPOINT_IN_OFFSET 1 - -/* Transfer timeout */ -#define TIMEOUT 2000 - -static bool deviceDisconnected = false; - -static int usbFdRead, usbFdWrite; -static bool isServer; - -static libusb_context *ctx = NULL; -static libusb_device_handle *dev_handle = NULL; - -int usbEpInitialize() { - int error; - - /* Initialize libusb */ - libusb_init(&ctx); - - return 0; -} - -int LIBUSB_CALL hotplug_cb(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data) { - if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { - printf("Device disconnected\n"); - deviceDisconnected = true; - } - - if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { - deviceDisconnected = false; - } - - return 0; // return 0 to keep callback active -} - -int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) -{ - int error; - isServer = false; - - /* Get our device */ - dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (dev_handle == NULL) { - libusb_exit(ctx); - - error = LIBUSB_ERROR_NO_DEVICE; - return error; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); - if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); - libusb_exit(ctx); - - return error; - } - - libusb_device* dev = libusb_get_device(dev_handle); - struct libusb_config_descriptor* config; - error = libusb_get_active_config_descriptor(dev, &config); - if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); - libusb_exit(ctx); - return error; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - bool foundGate = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - if(!foundGate) { - foundGate = true; - } else { - found = true; - break; - } - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(dev_handle); - libusb_exit(ctx); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); - if (error != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return error; - } - - /* We get the first EP_OUT and EP_IN for our interfaces - * In the way we initialized our usb-gadget on our device - */ - usbFdWrite = ENDPOINT_OUT_BASE + ENDPOINT_OUT_OFFSET; - usbFdRead = ENDPOINT_IN_BASE + ENDPOINT_IN_OFFSET; - - deviceDisconnected = false; - - libusb_hotplug_register_callback( - ctx, - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - LIBUSB_HOTPLUG_NO_FLAGS, - VENDOR_ID, - PRODUCT_ID, - LIBUSB_HOTPLUG_MATCH_ANY, - hotplug_cb, - NULL, - NULL - ); - - *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); - - return 0; -} - -int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) -{ - isServer = true; - -#if defined(__unix__) - int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); - int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); - - if(outfd < 0 || infd < 0) { - return -1; - } - - usbFdRead = infd; - usbFdWrite = outfd; - - *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); -#endif - - return 0; -} - - -int usbEpPlatformClose(void *fdKey) -{ - int error; - - if (isServer) { -#if defined(__unix__) - if (usbFdRead != -1){ - close(usbFdRead); - usbFdRead = -1; - } - - if (usbFdWrite != -1){ - close(usbFdWrite); - usbFdWrite = -1; - } -#endif - } else { - error = libusb_release_interface(dev_handle, INTERFACE_XLINK); - if (error != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return error; - } - - /* Release the device and exit */ - libusb_close(dev_handle); - } - - libusb_exit(ctx); - - error = EXIT_SUCCESS; - - return EXIT_SUCCESS; -} - -struct AsyncContext { - int result; - int transferred; - volatile bool done; -}; - -static void bulk_transfer_callback(struct libusb_transfer *transfer) { - AsyncContext* ctx = (AsyncContext*)transfer->user_data; - - switch (transfer->status) { - case LIBUSB_TRANSFER_COMPLETED: - ctx->transferred = transfer->actual_length; - ctx->result = 0; - break; - case LIBUSB_TRANSFER_NO_DEVICE: - ctx->transferred = 0; - ctx->result = LIBUSB_ERROR_NO_DEVICE; - break; - default: - ctx->transferred = 0; - ctx->result = LIBUSB_ERROR_OTHER; - break; - } - - ctx->done = true; - - libusb_free_transfer(transfer); -} - -int usbEpPlatformRead(void *fdKey, void *data, int size) -{ - int rc = 0; - - if (isServer) { -#if defined(__unix__) - if(usbFdRead < 0) - { - return -1; - } - - rc = read(usbFdRead, data, size); -#endif - } else { - if (deviceDisconnected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - struct libusb_transfer *transfer = libusb_alloc_transfer(0); - if (!transfer) { - return LIBUSB_ERROR_NO_MEM; - } - - AsyncContext asyncCtx = { 0 }; - - libusb_fill_bulk_transfer( - transfer, - dev_handle, - usbFdRead, - (unsigned char*)data, - size, - bulk_transfer_callback, - &asyncCtx, - TIMEOUT - ); - - if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - libusb_free_transfer(transfer); - return LIBUSB_ERROR_OTHER; - } - - // Wait for transfer completion - while (!asyncCtx.done) { - libusb_handle_events(ctx); - } - - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; - } - - return rc; -} - -int usbEpPlatformWrite(void *fdKey, void *data, int size) -{ - int rc = 0; - - if (isServer) { -#if defined(__unix__) - if(usbFdWrite < 0) - { - return -1; - } - - rc = write(usbFdWrite, data, size); -#endif - } else { - if (deviceDisconnected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - struct libusb_transfer *transfer = libusb_alloc_transfer(0); - if (!transfer) { - return LIBUSB_ERROR_NO_MEM; - } - - AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; - - libusb_fill_bulk_transfer( - transfer, - dev_handle, - usbFdWrite, - (unsigned char*)data, - size, - bulk_transfer_callback, - &asyncCtx, - TIMEOUT - ); - - if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - libusb_free_transfer(transfer); - return LIBUSB_ERROR_OTHER; - } - - // Wait for transfer completion - while (!asyncCtx.done) { - libusb_handle_events(ctx); - } - - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; - } - - return rc; -} - -int usbepGetDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices) { - libusb_context gateCtx; - libusb_init(&gateCtx); - - int error = 0; - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - return LIBUSB_ERROR_NO_DEVICE; - } - - error = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (error != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return error; - } - - // Check if the interface exists - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - error = libusb_get_active_config_descriptor(dev, &config); - if (error != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return error; - } - - // Look for INTERFACE_XLINK - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { - if(config->interface[i].altsetting[0].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[0].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - break; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - - *out_amountOfFoundDevices = 0; - - return X_LINK_PLATFORM_SUCCESS; - } - - // Get device descriptor - struct libusb_device_descriptor desc; - int r = libusb_get_device_descriptor(dev, &desc); - if (r < 0) { - return X_LINK_PLATFORM_ERROR; - } - - // Get device mxid - char mxId[32] = {'\0'}; - - r = libusb_get_string_descriptor_ascii(gate_dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); - - - /* Now we claim our ffs interfaces */ - r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (r != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return r; - } - - struct USBRequest_t { - uint32_t RequestNum; - uint32_t RequestSize; - }; - - int transferred; - USBRequest_t request = { - .RequestNum = 12, - .RequestSize = 0, - }; - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); - if (transferred < sizeof(request) || r != 0) { - return X_LINK_PLATFORM_ERROR; - } - - USBRequest_t response; - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&response, sizeof(response), &transferred, TIMEOUT); - if (transferred < sizeof(request) || r != 0) { - return X_LINK_PLATFORM_ERROR; - } - - if (response.RequestNum != 0) { - return X_LINK_PLATFORM_ERROR; - } - - if (response.RequestSize != 0) { - std::vector respBuffer; - respBuffer.resize(response.RequestSize); - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); - - struct GateResponse_t { - uint32_t state; - uint32_t protocol; - uint32_t platform; - } gateResponse; - - size_t strLen = response.RequestSize - sizeof(gateResponse); - char *string = (char*)malloc(strLen + 1); - memcpy(string, (const char*)&respBuffer[0], strLen); - string[strLen] = '\0'; - - memcpy(&gateResponse, &respBuffer[strLen], sizeof(gateResponse)); - - int numDevicesFound = 0; - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - if (gateResponse.platform == 4) { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; - } else if (gateResponse.platform == 3) { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; - } else { - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)0; - } - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strcpy(out_foundDevices[numDevicesFound].mxid, mxId); - numDevicesFound++; - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - free(string); - } else { - int numDevicesFound = 0; - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_GATE; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strcpy(out_foundDevices[numDevicesFound].mxid, mxId); - numDevicesFound++; - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - } - - libusb_close(gate_dev_handle); - return X_LINK_PLATFORM_SUCCESS; -} - - -int usbEpPlatformGateRead(void *data, int size) -{ - int rc = 0; - - /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - libusb_exit(ctx); - - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - - return rc; - } - - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - rc = libusb_get_active_config_descriptor(dev, &config); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - return rc; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (rc != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return rc; - } - - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - - libusb_close(gate_dev_handle); - - return rc; -} - -int usbEpPlatformGateWrite(void *data, int size) -{ - if (ctx == NULL) { - return LIBUSB_ERROR; - } - - int rc = 0; - - /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - libusb_exit(ctx); - - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - - return rc; - } - - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - rc = libusb_get_active_config_descriptor(dev, &config); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - return rc; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - libusb_exit(ctx); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (rc != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return rc; - } - - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - - libusb_close(gate_dev_handle); - - return rc; -} From 647dcc40ef4911c427a70e674d72c388545beefe Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 24 Sep 2025 11:43:14 +0200 Subject: [PATCH 40/78] usb_host_ep: Initialising gate only once. --- src/pc/protocols/usb_host_ep.cpp | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 2a40a04c..61ef59e6 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -61,6 +61,7 @@ static int usbFdRead, usbFdWrite; static bool isServer; static libusb_context *ctx = NULL; +static libusb_context *gate_ctx = NULL; static libusb_device_handle *dev_handle = NULL; int usbEpInitialize() { @@ -68,6 +69,7 @@ int usbEpInitialize() { /* Initialize libusb */ libusb_init(&ctx); + libusb_init(&gate_ctx); return 0; } @@ -381,9 +383,6 @@ int usbEpPlatformWrite(void *fdKey, void *data, int size) int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { - libusb_context *gate_ctx; - libusb_init(&gate_ctx); - int error = 0; libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { @@ -551,8 +550,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, int usbEpPlatformGateRead(void *data, int size) { - libusb_context *gate_ctx; - libusb_init(&gate_ctx); int rc = 0; /* Get our device */ @@ -632,9 +629,6 @@ int usbEpPlatformGateRead(void *data, int size) int usbEpPlatformGateWrite(void *data, int size) { - libusb_context *gate_ctx; - libusb_init(&gate_ctx); - int rc = 0; /* Get our device */ From 9262cc1fc22c38892b44ad46365e7e443bbdbcfe Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 24 Sep 2025 11:58:16 +0200 Subject: [PATCH 41/78] usb_host_ep: Removed references to libusb_exit for gate_ctx --- src/pc/protocols/usb_host_ep.cpp | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 61ef59e6..6b08a0e6 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -453,8 +453,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, /* Now we claim our ffs interfaces */ r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (r != LIBUSB_SUCCESS) { - libusb_exit(gate_ctx); - return r; } @@ -542,7 +540,6 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return X_LINK_PLATFORM_SUCCESS; } @@ -555,8 +552,6 @@ int usbEpPlatformGateRead(void *data, int size) /* Get our device */ libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { - libusb_exit(gate_ctx); - rc = LIBUSB_ERROR_NO_DEVICE; return rc; } @@ -567,7 +562,6 @@ int usbEpPlatformGateRead(void *data, int size) rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } @@ -577,7 +571,6 @@ int usbEpPlatformGateRead(void *data, int size) rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } @@ -606,7 +599,6 @@ int usbEpPlatformGateRead(void *data, int size) if (!found) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return LIBUSB_ERROR_NO_DEVICE; } @@ -614,15 +606,12 @@ int usbEpPlatformGateRead(void *data, int size) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { - libusb_exit(gate_ctx); - return rc; } rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } @@ -634,8 +623,6 @@ int usbEpPlatformGateWrite(void *data, int size) /* Get our device */ libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); if (gate_dev_handle == NULL) { - libusb_exit(gate_ctx); - rc = LIBUSB_ERROR_NO_DEVICE; return rc; } @@ -646,7 +633,6 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } @@ -656,7 +642,6 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_get_active_config_descriptor(dev, &config); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } @@ -685,7 +670,6 @@ int usbEpPlatformGateWrite(void *data, int size) if (!found) { libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return LIBUSB_ERROR_NO_DEVICE; } @@ -693,7 +677,6 @@ int usbEpPlatformGateWrite(void *data, int size) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { - libusb_exit(gate_ctx); return rc; } @@ -701,7 +684,6 @@ int usbEpPlatformGateWrite(void *data, int size) rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); libusb_close(gate_dev_handle); - libusb_exit(gate_ctx); return rc; } From 7f30c51be0cffd648b8ec0a9eb9a140e9ce53fad Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 25 Sep 2025 10:27:53 +0200 Subject: [PATCH 42/78] Gate: added explicit timeout. --- include/XLink/XLink.h | 8 +++----- include/XLink/XLinkPlatform.h | 4 ++-- src/pc/PlatformData.c | 8 ++++---- src/pc/protocols/usb_host_ep.cpp | 8 ++++---- src/pc/protocols/usb_host_ep.h | 4 ++-- src/shared/XLinkData.c | 8 ++++---- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/include/XLink/XLink.h b/include/XLink/XLink.h index 68cfc311..e658ab22 100644 --- a/include/XLink/XLink.h +++ b/include/XLink/XLink.h @@ -310,10 +310,11 @@ XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, int siz * @brief Sends/Receives a message to Gate via USB * @param[in] data - Data to be transmitted/collected * @param[in] size - The data size + * @param[in] size - USB timeout * @return Status code of the operation: X_LINK_SUCCESS (0) for success */ -XLinkError_t XLinkGateWrite(void *data, int size); -XLinkError_t XLinkGateRead(void *data, int size); +XLinkError_t XLinkGateWrite(void *data, int size, int timeout); +XLinkError_t XLinkGateRead(void *data, int size, int timeout); /** * @brief Sends a package to initiate the writing of a file descriptor @@ -436,9 +437,6 @@ XLinkError_t XLinkReadDataWithTimeout(streamId_t streamId, streamPacketDesc_t** */ XLinkError_t XLinkWriteDataWithTimeout(streamId_t streamId, const uint8_t* buffer, int size, unsigned int timeoutMs); -XLinkError_t XLinkGateWriteData(streamId_t const streamId, const uint8_t* buffer, int size); -XLinkError_t XLinkGateReadData(streamId_t const streamId, streamPacketDesc_t* const packet); - // ------------------------------------ // Device streams management. End. // ------------------------------------ diff --git a/include/XLink/XLinkPlatform.h b/include/XLink/XLinkPlatform.h index 2b6ae1e0..e95be051 100644 --- a/include/XLink/XLinkPlatform.h +++ b/include/XLink/XLinkPlatform.h @@ -91,8 +91,8 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); int XLinkPlatformWriteFd(xLinkDeviceHandle_t *deviceHandle, const long fd, void *data2, int size2); int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, long *fd); -int XLinkPlatformGateWrite(void *data, int size); -int XLinkPlatformGateRead(void *data, int size); +int XLinkPlatformGateWrite(void *data, int size, int timeout); +int XLinkPlatformGateRead(void *data, int size, int timeout); void* XLinkPlatformAllocateData(uint32_t size, uint32_t alignment); void XLinkPlatformDeallocateData(void *ptr, uint32_t size, uint32_t alignment); diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index f85b5bd1..e686a8a4 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -160,22 +160,22 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l } } -int XLinkPlatformGateWrite(void *data, int size) +int XLinkPlatformGateWrite(void *data, int size, int timeout) { if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbEpPlatformGateWrite(data, size); + return usbEpPlatformGateWrite(data, size, timeout); } -int XLinkPlatformGateRead(void *data, int size) +int XLinkPlatformGateRead(void *data, int size, int timeout) { if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbEpPlatformGateRead(data, size); + return usbEpPlatformGateRead(data, size, timeout); } diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp index 6b08a0e6..875b58ce 100644 --- a/src/pc/protocols/usb_host_ep.cpp +++ b/src/pc/protocols/usb_host_ep.cpp @@ -545,7 +545,7 @@ int usbepGetDevices(const deviceDesc_t in_deviceRequirements, } -int usbEpPlatformGateRead(void *data, int size) +int usbEpPlatformGateRead(void *data, int size, int timeout) { int rc = 0; @@ -609,14 +609,14 @@ int usbEpPlatformGateRead(void *data, int size) return rc; } - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, timeout); libusb_close(gate_dev_handle); return rc; } -int usbEpPlatformGateWrite(void *data, int size) +int usbEpPlatformGateWrite(void *data, int size, int timeout) { int rc = 0; @@ -681,7 +681,7 @@ int usbEpPlatformGateWrite(void *data, int size) return rc; } - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); + rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, timeout); libusb_close(gate_dev_handle); diff --git a/src/pc/protocols/usb_host_ep.h b/src/pc/protocols/usb_host_ep.h index 77127b5f..19b46df6 100644 --- a/src/pc/protocols/usb_host_ep.h +++ b/src/pc/protocols/usb_host_ep.h @@ -21,8 +21,8 @@ int usbEpPlatformWrite(void *fd, void *data, int size); int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices); -int usbEpPlatformGateRead(void *data, int size); -int usbEpPlatformGateWrite(void *data, int size); +int usbEpPlatformGateRead(void *data, int size, int timeout); +int usbEpPlatformGateWrite(void *data, int size, int timeout); #ifdef __cplusplus } diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 1a845727..0f84ebd7 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -117,14 +117,14 @@ XLinkError_t XLinkCloseStream(streamId_t const streamId) return X_LINK_SUCCESS; } -XLinkError_t XLinkGateWrite(void *data, int size) +XLinkError_t XLinkGateWrite(void *data, int size, int timeout) { - return XLinkPlatformGateWrite(data, size); + return XLinkPlatformGateWrite(data, size, timeout); } -XLinkError_t XLinkGateRead(void *data, int size) +XLinkError_t XLinkGateRead(void *data, int size, int timeout) { - return XLinkPlatformGateRead(data, size); + return XLinkPlatformGateRead(data, size, timeout); } XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, From 6cc5e927d3515b77fcd09ac26967f572a015d201 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 25 Sep 2025 10:39:22 +0200 Subject: [PATCH 43/78] Gate: instated correct error handling. --- src/shared/XLinkData.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 0f84ebd7..5393e306 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -119,12 +119,22 @@ XLinkError_t XLinkCloseStream(streamId_t const streamId) XLinkError_t XLinkGateWrite(void *data, int size, int timeout) { - return XLinkPlatformGateWrite(data, size, timeout); + int rc = XLinkPlatformGateWrite(data, size, timeout) + if(rc) < 0 { + return X_LINK_ERROR; + } else { + return X_LINK_SUCCESS; + } } XLinkError_t XLinkGateRead(void *data, int size, int timeout) { - return XLinkPlatformGateRead(data, size, timeout); + int rc = XLinkPlatformGateRead(data, size, timeout) + if(rc) < 0 { + return X_LINK_ERROR; + } else { + return X_LINK_SUCCESS; + } } XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, From 24d0be07730a22706a9de6cdeae53fdc8bbb503b Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 25 Sep 2025 10:51:14 +0200 Subject: [PATCH 44/78] XLinkData: fixed missing semicolon. --- src/shared/XLinkData.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 5393e306..08000560 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -119,8 +119,8 @@ XLinkError_t XLinkCloseStream(streamId_t const streamId) XLinkError_t XLinkGateWrite(void *data, int size, int timeout) { - int rc = XLinkPlatformGateWrite(data, size, timeout) - if(rc) < 0 { + int rc = XLinkPlatformGateWrite(data, size, timeout); + if(rc < 0) { return X_LINK_ERROR; } else { return X_LINK_SUCCESS; @@ -129,8 +129,8 @@ XLinkError_t XLinkGateWrite(void *data, int size, int timeout) XLinkError_t XLinkGateRead(void *data, int size, int timeout) { - int rc = XLinkPlatformGateRead(data, size, timeout) - if(rc) < 0 { + int rc = XLinkPlatformGateRead(data, size, timeout); + if(rc < 0) { return X_LINK_ERROR; } else { return X_LINK_SUCCESS; From 85502ee25c5f1ec13b570bec4f1d6db2c266a3b6 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 29 Sep 2025 12:15:57 +0200 Subject: [PATCH 45/78] Merging usb_host_ep into usb_host: fixing discovery. --- src/pc/protocols/usb_host.cpp | 176 +++++++++++++++++++++++++--------- 1 file changed, 132 insertions(+), 44 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index d5b1a5b9..63beb9f8 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -93,10 +93,24 @@ static std::unordered_map vidPidToDeviceS {{0x03E7, 0xf63b}, X_LINK_BOOTED}, {{0x03E7, 0xf63c}, X_LINK_BOOTLOADER}, {{0x03E7, 0xf63d}, X_LINK_FLASH_BOOTED}, + {{0x05C6, 0x4321}, X_LINK_GATE}, +}; + + +struct USBGateRequest { + uint32_t RequestNum; + uint32_t RequestSize; +}; + +struct GateResponse { + uint32_t state; + uint32_t protocol; + uint32_t platform; }; static std::string getLibusbDevicePath(libusb_device *dev); static libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId); +static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial); static const char* xlink_libusb_strerror(int x); #ifdef _WIN32 std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev); @@ -109,13 +123,6 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, // Also protects usb_mx_id_cache std::lock_guard l(mutex); - // No RVC3/4 devices on USB now, return 0 - if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ - *out_amountOfFoundDevices = 0; - return X_LINK_PLATFORM_SUCCESS; - } - - // Get list of usb devices static libusb_device **devs = NULL; auto numDevices = libusb_get_device_list(context, &devs); @@ -172,45 +179,63 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } } - // Get device mxid - std::string mxId; - libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); - mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); - switch (rc) - { - case LIBUSB_SUCCESS: - status = X_LINK_SUCCESS; - break; - case LIBUSB_ERROR_ACCESS: - status = X_LINK_INSUFFICIENT_PERMISSIONS; - break; - case LIBUSB_ERROR_BUSY: - status = X_LINK_DEVICE_ALREADY_IN_USE; - break; - default: - status = X_LINK_ERROR; - break; - } + // Check for RVC3 and RVC4 first + if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + GateResponse gateResponse; + std::string serial; + getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, serial); + + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = gateResponse.platform; + out_foundDevices[numDevicesFound].protocol = gateResponse.protocol; + out_foundDevices[numDevicesFound].state = gateResponse.state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, serial.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + } else { + // Get device mxid + std::string mxId; + libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); + mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); + switch (rc) + { + case LIBUSB_SUCCESS: + status = X_LINK_SUCCESS; + break; + case LIBUSB_ERROR_ACCESS: + status = X_LINK_INSUFFICIENT_PERMISSIONS; + break; + case LIBUSB_ERROR_BUSY: + status = X_LINK_DEVICE_ALREADY_IN_USE; + break; + default: + status = X_LINK_ERROR; + break; + } - // compare with MxId - std::string requiredMxId(in_deviceRequirements.mxid); - if(requiredMxId.length() > 0 && requiredMxId != mxId){ - // Current device doesn't match the "filter" - continue; - } + // compare with MxId + std::string requiredMxId(in_deviceRequirements.mxid); + if(requiredMxId.length() > 0 && requiredMxId != mxId){ + // Current device doesn't match the "filter" + continue; + } - // TODO(themarpe) - check platform + // TODO(themarpe) - check platform - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; - out_foundDevices[numDevicesFound].state = state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; + out_foundDevices[numDevicesFound].state = state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + } } @@ -499,6 +524,69 @@ libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePat } +static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial) { + GateResponse gateResponse = {0}; + std::string serial = ""; + + // get serial from usb descriptor + libusb_device_handle *handle = nullptr; + int libusb_rc = LIBUSB_SUCCESS; + libusb_rc = libusb_open(dev, &handle); + if (libusb_rc != 0){ + return (libusb_error) libusb_rc; + } + + libusb_rc = libusb_claim_interface(handle, INTERFACE_GATE); + if (libusb_rc != 0){ + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + USBGateRequest usbGateRequest = { + .RequestNum = 12, + .RequestSize = 0, + }; + + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + USBGateRequest usbGateResponse = { 0 }; + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + std::vector respBuffer; + respBuffer.resize(response.RequestSize); + libusb_rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return libusb_error::LIBUSB_ERROR; + } + + size_t serialStrLen = response.RequestSize - sizeof(GateResponse); + char *serialStr = (char*)malloc(serialStrLen + 1); + memcpy(serialStr, (const char*)&respBuffer[0], serialStrLen); + serialStr[serialStrLen] = '\0'; + serial = serialStr; + free(serialStr); + outSerial = serial; + + memcpy(&gateResponse, &respBuffer[serialStrLen], sizeof(gateResponse)); + outGateResponse = gateResponse; + + // Close opened device + if(handle != nullptr){ + libusb_close(handle); + } + + return libusb_error::LIBUSB_SUCCESS; +} + const char* xlink_libusb_strerror(int x) { return libusb_strerror((libusb_error) x); } @@ -514,7 +602,7 @@ static libusb_error usb_open_device(libusb_device *dev, uint8_t* endpoint, libus if((res = libusb_open(dev, &h)) < 0) { mvLog(MVLOG_DEBUG, "cannot open device: %s\n", xlink_libusb_strerror(res)); - return (libusb_error) res; +return (libusb_error) res; } // Get configuration first From 2fc723a3b3dd09f1ed339e5741d912ef6bbf0689 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 29 Sep 2025 16:54:33 +0200 Subject: [PATCH 46/78] Merging usb_host_ep into usb_host: added read/write/gate read/gate write --- src/pc/PlatformData.c | 4 + src/pc/PlatformDeviceControl.c | 3 + src/pc/protocols/usb_host.cpp | 163 +++++++++++++++++++++++++++++---- 3 files changed, 152 insertions(+), 18 deletions(-) diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index f85b5bd1..059406dd 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -60,6 +60,10 @@ extern int usbFdWrite; extern int usbFdRead; + +extern int usbGateFdWrite; +extern int usbGateFdRead; + #endif /*USE_USB_VSC*/ // ------------------------------------ diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index d03e0943..dabd844f 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -28,6 +28,9 @@ int usbFdWrite = -1; int usbFdRead = -1; +int usbGateFdWrite = -1; +int usbGateFdRead = -1; + #endif /*USE_USB_VSC*/ #include "XLinkPublicDefines.h" diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 63beb9f8..7cddf3f4 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -23,6 +23,7 @@ #include #include #include +#include constexpr static int MAXIMUM_PORT_NUMBERS = 7; using VidPid = std::pair; @@ -30,6 +31,7 @@ static const int MX_ID_TIMEOUT_MS = 100; static constexpr auto DEFAULT_OPEN_TIMEOUT = std::chrono::seconds(5); static constexpr auto DEFAULT_WRITE_TIMEOUT = 2000; +static constexpr auto DEFAULT_READ_TIMEOUT = 2000; static constexpr std::chrono::milliseconds DEFAULT_CONNECT_TIMEOUT{20000}; static constexpr std::chrono::milliseconds DEFAULT_SEND_FILE_TIMEOUT{10000}; static constexpr auto USB1_CHUNKSZ = 64; @@ -187,9 +189,9 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = gateResponse.platform; - out_foundDevices[numDevicesFound].protocol = gateResponse.protocol; - out_foundDevices[numDevicesFound].state = gateResponse.state; + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; + out_foundDevices[numDevicesFound].protocol = (XLinkProtocol_t)gateResponse.protocol; + out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); @@ -536,7 +538,7 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* return (libusb_error) libusb_rc; } - libusb_rc = libusb_claim_interface(handle, INTERFACE_GATE); + libusb_rc = libusb_claim_interface(handle, 0); if (libusb_rc != 0){ libusb_close(handle); return (libusb_error) libusb_rc; @@ -546,29 +548,31 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* .RequestNum = 12, .RequestSize = 0, }; + + int transferred = 0; - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, TIMEOUT); + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); return (libusb_error) libusb_rc; } USBGateRequest usbGateResponse = { 0 }; - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, TIMEOUT); + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); return (libusb_error) libusb_rc; } std::vector respBuffer; - respBuffer.resize(response.RequestSize); - libusb_rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); + respBuffer.resize(usbGateResponse.RequestSize); + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); - return libusb_error::LIBUSB_ERROR; + return (libusb_error) libusb_rc; } - size_t serialStrLen = response.RequestSize - sizeof(GateResponse); + size_t serialStrLen = usbGateResponse.RequestSize - sizeof(GateResponse); char *serialStr = (char*)malloc(serialStrLen + 1); memcpy(serialStr, (const char*)&respBuffer[0], serialStrLen); serialStr[serialStrLen] = '\0'; @@ -631,6 +635,19 @@ return (libusb_error) res; libusb_close(h); return (libusb_error) res; } + + // TODO(TheMutta): Find a way to enable this conditionally + //#if (!defined(USE_USB_VSC)) + // XLink EPs + if((res = libusb_claim_interface(h, 1)) < 0) + { + mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + + + if((res = libusb_get_config_descriptor(dev, 0, &cdesc)) < 0) { mvLog(MVLOG_DEBUG, "Unable to get USB config descriptor: %s\n", xlink_libusb_strerror(res)); @@ -883,7 +900,7 @@ int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void * } return 0; -#else + #else usbFdRead= open(devPathRead, O_RDWR); if(usbFdRead < 0) { @@ -943,7 +960,7 @@ int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void * return X_LINK_PLATFORM_ERROR; } return 0; -#endif /*USE_LINK_JTAG*/ + #endif /*USE_LINK_JTAG*/ #else libusb_device_handle* usbHandle = nullptr; @@ -1014,7 +1031,7 @@ int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware -int usb_read(libusb_device_handle *f, void *data, size_t size) +int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) { const int chunk_size = DEFAULT_CHUNKSZ; while(size > 0) @@ -1022,7 +1039,7 @@ int usb_read(libusb_device_handle *f, void *data, size_t size) int bt, ss = (int)size; if(ss > chunk_size) ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_IN,(unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + int rc = libusb_bulk_transfer(f, USB_ENDPOINT_IN + offset,(unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); if(rc) return rc; data = ((char *)data) + bt; @@ -1031,7 +1048,7 @@ int usb_read(libusb_device_handle *f, void *data, size_t size) return 0; } -int usb_write(libusb_device_handle *f, const void *data, size_t size) +int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t offset) { const int chunk_size = DEFAULT_CHUNKSZ; while(size > 0) @@ -1039,7 +1056,7 @@ int usb_write(libusb_device_handle *f, const void *data, size_t size) int bt, ss = (int)size; if(ss > chunk_size) ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_OUT, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + int rc = libusb_bulk_transfer(f, USB_ENDPOINT_OUT + offset, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); if(rc) return rc; data = (char *)data + bt; @@ -1096,7 +1113,7 @@ int usbPlatformRead(void* fdKey, void* data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - rc = usb_read(usbHandle, data, size); + rc = usb_read(usbHandle, data, size, 1); #endif /*USE_USB_VSC*/ return rc; } @@ -1152,11 +1169,121 @@ int usbPlatformWrite(void *fdKey, void *data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - rc = usb_write(usbHandle, data, size); + rc = usb_write(usbHandle, data, size, 1); #endif /*USE_USB_VSC*/ return rc; } +int usbPlatformGateRead(void* fdKey, void* data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int nread = 0; +#ifdef USE_LINK_JTAG + while (nread < size){ + nread += read(usbFdWrite, &((char*)data)[nread], size - nread); + printf("read %d %d\n", nread, size); + } +#else + if(usbFdRead < 0) + { + return -1; + } + + while(nread < size) + { + int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ + ? PACKET_LENGTH : size - nread; + + while(toRead > 0) + { + rc = read(usbFdRead, &((char*)data)[nread], toRead); + if ( rc < 0) + { + return -2; + } + toRead -=rc; + nread += rc; + } + unsigned char acknowledge = 0xEF; + int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); + if (wc != sizeof(acknowledge)) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_read(usbHandle, data, size, 0); +#endif /*USE_USB_VSC*/ + return rc; +} + +int usbPlatformGateWrite(void *fdKey, void *data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int byteCount = 0; +#ifdef USE_LINK_JTAG + while (byteCount < size){ + byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); + printf("write %d %d\n", byteCount, size); + } +#else + if(usbFdWrite < 0) + { + return -1; + } + while(byteCount < size) + { + int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ + ? PACKET_LENGTH:size - byteCount; + int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); + + if ( wc != toWrite) + { + return -2; + } + + byteCount += toWrite; + unsigned char acknowledge; + int rc; + rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); + + if ( rc < 0) + { + return -2; + } + + if (acknowledge != 0xEF) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_write(usbHandle, data, size, 0); +#endif /*USE_USB_VSC*/ + return rc; +} + + #ifdef _WIN32 #include #include From 0988394d4d87653e536fc3e6273810eb5d2d5161 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 12:16:52 +0200 Subject: [PATCH 47/78] Merging usb_host_ep into usb_host: merged final quirks and added usb server. --- src/pc/PlatformData.c | 14 +- src/pc/PlatformDeviceControl.c | 23 +- src/pc/protocols/usb_host.cpp | 56 +- src/pc/protocols/usb_host.h | 12 +- src/pc/protocols/usb_host_ep.cpp | 689 -------------- src/pc/protocols/usb_host_ep.h | 29 - "\303\271" | 1429 ++++++++++++++++++++++++++++++ 7 files changed, 1493 insertions(+), 759 deletions(-) delete mode 100644 src/pc/protocols/usb_host_ep.cpp delete mode 100644 src/pc/protocols/usb_host_ep.h create mode 100644 "\303\271" diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 059406dd..0f6b0758 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -57,15 +57,13 @@ #include #include "usb_host.h" +#endif /*USE_USB_VSC*/ extern int usbFdWrite; extern int usbFdRead; - extern int usbGateFdWrite; extern int usbGateFdRead; -#endif /*USE_USB_VSC*/ - // ------------------------------------ // Wrappers declaration. Begin. // ------------------------------------ @@ -92,6 +90,7 @@ int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) switch (deviceHandle->protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: + case X_LINK_USB_EP: return usbPlatformWrite(deviceHandle->xLinkFD, data, size); case X_LINK_PCIE: @@ -104,8 +103,6 @@ int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) case X_LINK_LOCAL_SHDMEM: return shdmemPlatformWrite(deviceHandle->xLinkFD, data, size); #endif - case X_LINK_USB_EP: - return usbEpPlatformWrite(deviceHandle->xLinkFD, data, size); case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to write with TCP_IP_OR_LOCAL_SHDMEM\n"); @@ -142,6 +139,7 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l switch (deviceHandle->protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: + case X_LINK_USB_EP: return usbPlatformRead(deviceHandle->xLinkFD, data, size); case X_LINK_PCIE: @@ -154,8 +152,6 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l case X_LINK_LOCAL_SHDMEM: return shdmemPlatformRead(deviceHandle->xLinkFD, data, size, fd); #endif - case X_LINK_USB_EP: - return usbEpPlatformRead(deviceHandle->xLinkFD, data, size); case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to read with TCP_IP_OR_LOCAL_SHDMEM\n"); @@ -170,7 +166,7 @@ int XLinkPlatformGateWrite(void *data, int size) return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbEpPlatformGateWrite(data, size); + return usbPlatformGateWrite(NULL, data, size); } int XLinkPlatformGateRead(void *data, int size) @@ -179,7 +175,7 @@ int XLinkPlatformGateRead(void *data, int size) return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbEpPlatformGateRead(data, size); + return usbPlatformGateRead(NULL, data, size); } diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index dabd844f..464d6932 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -25,14 +25,13 @@ #include #include #include +#endif /*USE_USB_VSC*/ int usbFdWrite = -1; int usbFdRead = -1; int usbGateFdWrite = -1; int usbGateFdRead = -1; -#endif /*USE_USB_VSC*/ - #include "XLinkPublicDefines.h" #define USB_LINK_SOCKET_PORT 5678 @@ -94,6 +93,7 @@ xLinkPlatformErrorCode_t XLinkPlatformInit(XLinkGlobalHandler_t* globalHandler) // check for failed initialization; LIBUSB_SUCCESS = 0 if (usbInitialize(globalHandler->options) != 0) { xlinkSetProtocolInitialized(X_LINK_USB_VSC, 0); + xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); } // Initialize tcpip protocol if necessary @@ -108,10 +108,6 @@ xLinkPlatformErrorCode_t XLinkPlatformInit(XLinkGlobalHandler_t* globalHandler) } #endif - if (usbEpInitialize() != 0) { - xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); - } - xlinkSetProtocolInitialized(X_LINK_TCP_IP_OR_LOCAL_SHDMEM, 1); return X_LINK_PLATFORM_SUCCESS; @@ -193,7 +189,8 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha switch (*protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - return usbPlatformConnect(devPathRead, devPathWrite, fd); + case X_LINK_USB_EP: + return usbPlatformConnect(*protocol, devPathRead, devPathWrite, fd); case X_LINK_PCIE: return pciePlatformConnect(devPathRead, devPathWrite, fd); @@ -208,9 +205,6 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha case X_LINK_LOCAL_SHDMEM: return shdmemPlatformConnect(devPathRead, devPathWrite, fd); #endif - case X_LINK_USB_EP: - return usbEpPlatformConnect(devPathRead, devPathWrite, fd); - default: return X_LINK_PLATFORM_INVALID_PARAMETERS; } @@ -219,6 +213,11 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha xLinkPlatformErrorCode_t XLinkPlatformServer(const char* devPathRead, const char* devPathWrite, XLinkProtocol_t *protocol, void** fd) { switch (*protocol) { + case X_LINK_USB_VSC: + case X_LINK_USB_CDC: + case X_LINK_USB_EP: + return usbPlatformServer(devPathRead, devPathWrite, fd); + case X_LINK_TCP_IP: return tcpipPlatformServer(devPathRead, devPathWrite, fd, NULL); @@ -230,10 +229,6 @@ xLinkPlatformErrorCode_t XLinkPlatformServer(const char* devPathRead, const char return shdmemPlatformServer(devPathRead, devPathWrite, fd, NULL); #endif - case X_LINK_USB_EP: - return usbEpPlatformServer(devPathRead, devPathWrite, fd); - - default: return X_LINK_PLATFORM_INVALID_PARAMETERS; } diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 7cddf3f4..4f64e36a 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -25,6 +25,12 @@ #include #include +#if defined(__unix__) +#include +#include +#include +#endif + constexpr static int MAXIMUM_PORT_NUMBERS = 7; using VidPid = std::pair; static const int MX_ID_TIMEOUT_MS = 100; @@ -596,7 +602,7 @@ const char* xlink_libusb_strerror(int x) { } -static libusb_error usb_open_device(libusb_device *dev, uint8_t* endpoint, libusb_device_handle*& handle) +static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev, uint8_t* endpoint, libusb_device_handle*& handle) { struct libusb_config_descriptor *cdesc; const struct libusb_interface_descriptor *ifdesc; @@ -636,17 +642,14 @@ return (libusb_error) res; return (libusb_error) res; } - // TODO(TheMutta): Find a way to enable this conditionally - //#if (!defined(USE_USB_VSC)) - // XLink EPs - if((res = libusb_claim_interface(h, 1)) < 0) - { - mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; + if (protocol = X_LINK_USB_EP) { + if((res = libusb_claim_interface(h, 1)) < 0) + { + mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } } - - if((res = libusb_get_config_descriptor(dev, 0, &cdesc)) < 0) { @@ -753,7 +756,7 @@ int usb_boot(const char *addr, const void *mvcmd, unsigned size) auto t2 = steady_clock::now(); do { - if((res = usb_open_device(dev, &endpoint, h)) == LIBUSB_SUCCESS){ + if((res = usb_open_device(X_LINK_USB_VSC, dev, &endpoint, h)) == LIBUSB_SUCCESS){ break; } std::this_thread::sleep_for(milliseconds(100)); @@ -782,7 +785,7 @@ int usb_boot(const char *addr, const void *mvcmd, unsigned size) -xLinkPlatformErrorCode_t usbLinkOpen(const char *path, libusb_device_handle*& h) +xLinkPlatformErrorCode_t usbLinkOpen(XLinkProtocol_t protocol, const char *path, libusb_device_handle*& h) { using namespace std::chrono; if (path == NULL) { @@ -807,7 +810,7 @@ xLinkPlatformErrorCode_t usbLinkOpen(const char *path, libusb_device_handle*& h) } uint8_t ep = 0; - libusb_error libusb_rc = usb_open_device(dev, &ep, h); + libusb_error libusb_rc = usb_open_device(protocol, dev, &ep, h); if(libusb_rc == LIBUSB_SUCCESS) { return X_LINK_PLATFORM_SUCCESS; } else if(libusb_rc == LIBUSB_ERROR_ACCESS) { @@ -867,12 +870,33 @@ xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { void usbLinkClose(libusb_device_handle *f) { libusb_release_interface(f, 0); + libusb_release_interface(f, 1); libusb_close(f); } +extern int usbFdWrite; +extern int usbFdRead; +int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) +{ +#if defined(__unix__) + int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); + int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); + + if(outfd < 0 || infd < 0) { + return -1; + } + + usbFdRead = infd; + usbFdWrite = outfd; + + *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); +#endif + + return 0; +} -int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) +int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd) { #if (!defined(USE_USB_VSC)) #ifdef USE_LINK_JTAG @@ -964,7 +988,7 @@ int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void * #else libusb_device_handle* usbHandle = nullptr; - xLinkPlatformErrorCode_t ret = usbLinkOpen(devPathWrite, usbHandle); + xLinkPlatformErrorCode_t ret = usbLinkOpen(protocol, devPathWrite, usbHandle); if (ret != X_LINK_PLATFORM_SUCCESS) { diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 1103fa49..589554a4 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -47,26 +47,34 @@ int usb_boot(const char *addr, const void *mvcmd, unsigned size); int get_pid_by_name(const char* name); xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* path); -int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd); +int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd); +int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd); int usbPlatformClose(void *fd); int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length); int usbPlatformRead(void *fd, void *data, int size); int usbPlatformWrite(void *fd, void *data, int size); +int usbPlatformGateRead(void *fd, void *data, int size); +int usbPlatformGateWrite(void *fd, void *data, int size); + #else // Error out on these function calls static inline int usbInitialize(void* options) { return -1; } static inline xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* path) { return X_LINK_PLATFORM_USB_DRIVER_NOT_LOADED; } -static inline int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) { return -1; } +static inline int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd) { return -1; } +static inline int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) { return -1; } static inline int usbPlatformClose(void *fd) { return -1; } static inline int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length) { return -1; } static inline int usbPlatformRead(void *fd, void *data, int size) { return -1; } static inline int usbPlatformWrite(void *fd, void *data, int size) { return -1; } +static inline int usbPlatformGateRead(void *fd, void *data, int size) { return -1; } +static inline int usbPlatformGateWrite(void *fd, void *data, int size) { return -1; } + static inline xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices) { diff --git a/src/pc/protocols/usb_host_ep.cpp b/src/pc/protocols/usb_host_ep.cpp deleted file mode 100644 index 6b08a0e6..00000000 --- a/src/pc/protocols/usb_host_ep.cpp +++ /dev/null @@ -1,689 +0,0 @@ -// project -#include -#define MVLOG_UNIT_NAME xLinkUsb - -#include "XLink/XLinkLog.h" -#include "XLink/XLinkPlatform.h" -#include "XLink/XLinkPublicDefines.h" -#include "usb_host_ep.h" -#include "../PlatformDeviceFd.h" -#include "usb_mx_id.h" -#include "usb_host.h" - -// std -#include -#include -#include - -#if defined(__unix__) -#include -#include -#include -#endif - -#include -#include - -#include - -/* Vendor ID */ -#define VENDOR_ID 0x05c6 -#define PRODUCT_ID 0x4321 - -/* Interface number for ffs.gate */ -#define INTERFACE_GATE 0 - -/* Interface number for ffs.xlink */ -#define INTERFACE_XLINK 1 -#define INTERFACE_XLINK_NAME "Luxonis Communication Interface" - -/* Base endpoint address used for output */ -#define ENDPOINT_OUT_BASE 0x01 - -/* Base endpoint address used for input */ -#define ENDPOINT_IN_BASE 0x81 - -/* The endpoint structure is the following - * - Gate: first endpoint - * - XLink: second endpoint - * - ADB: third endpoint - * - etc - */ -#define ENDPOINT_OUT_OFFSET 1 -#define ENDPOINT_IN_OFFSET 1 - -/* Transfer timeout */ -#define TIMEOUT 2000 - -static bool deviceDisconnected = false; - -static int usbFdRead, usbFdWrite; -static bool isServer; - -static libusb_context *ctx = NULL; -static libusb_context *gate_ctx = NULL; -static libusb_device_handle *dev_handle = NULL; - -int usbEpInitialize() { - int error; - - /* Initialize libusb */ - libusb_init(&ctx); - libusb_init(&gate_ctx); - - return 0; -} - -int LIBUSB_CALL hotplug_cb(libusb_context *ctx, libusb_device *dev, libusb_hotplug_event event, void *user_data) { - if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) { - printf("Device disconnected\n"); - deviceDisconnected = true; - } - - if (event & LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED) { - deviceDisconnected = false; - } - - return 0; // return 0 to keep callback active -} - -int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) -{ - int error; - isServer = false; - - /* Get our device */ - dev_handle = libusb_open_device_with_vid_pid(ctx, VENDOR_ID, PRODUCT_ID); - if (dev_handle == NULL) { - libusb_exit(ctx); - - error = LIBUSB_ERROR_NO_DEVICE; - return error; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - error = libusb_set_auto_detach_kernel_driver(dev_handle, 1); - if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); - libusb_exit(ctx); - - return error; - } - - libusb_device* dev = libusb_get_device(dev_handle); - struct libusb_config_descriptor* config; - error = libusb_get_active_config_descriptor(dev, &config); - if (error != LIBUSB_SUCCESS) { - libusb_close(dev_handle); - libusb_exit(ctx); - return error; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - bool foundGate = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - if(!foundGate) { - foundGate = true; - } else { - found = true; - break; - } - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(dev_handle); - libusb_exit(ctx); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - error = libusb_claim_interface(dev_handle, INTERFACE_XLINK); - if (error != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return error; - } - - /* We get the first EP_OUT and EP_IN for our interfaces - * In the way we initialized our usb-gadget on our device - */ - usbFdWrite = ENDPOINT_OUT_BASE + ENDPOINT_OUT_OFFSET; - usbFdRead = ENDPOINT_IN_BASE + ENDPOINT_IN_OFFSET; - - deviceDisconnected = false; - - libusb_hotplug_register_callback( - ctx, - LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, - LIBUSB_HOTPLUG_NO_FLAGS, - VENDOR_ID, - PRODUCT_ID, - LIBUSB_HOTPLUG_MATCH_ANY, - hotplug_cb, - NULL, - NULL - ); - - *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); - - return 0; -} - -int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) -{ - isServer = true; - -#if defined(__unix__) - int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); - int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); - - if(outfd < 0 || infd < 0) { - return -1; - } - - usbFdRead = infd; - usbFdWrite = outfd; - - *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); -#endif - - return 0; -} - - -int usbEpPlatformClose(void *fdKey) -{ - int error; - - if (isServer) { -#if defined(__unix__) - if (usbFdRead != -1){ - close(usbFdRead); - usbFdRead = -1; - } - - if (usbFdWrite != -1){ - close(usbFdWrite); - usbFdWrite = -1; - } -#endif - } else { - error = libusb_release_interface(dev_handle, INTERFACE_XLINK); - if (error != LIBUSB_SUCCESS) { - libusb_exit(ctx); - - return error; - } - - /* Release the device and exit */ - libusb_close(dev_handle); - } - - libusb_exit(ctx); - - error = EXIT_SUCCESS; - - return EXIT_SUCCESS; -} - -struct AsyncContext { - int result; - int transferred; - volatile bool done; -}; - -static void bulk_transfer_callback(struct libusb_transfer *transfer) { - AsyncContext* ctx = (AsyncContext*)transfer->user_data; - - switch (transfer->status) { - case LIBUSB_TRANSFER_COMPLETED: - ctx->transferred = transfer->actual_length; - ctx->result = 0; - break; - case LIBUSB_TRANSFER_NO_DEVICE: - ctx->transferred = 0; - ctx->result = LIBUSB_ERROR_NO_DEVICE; - break; - default: - ctx->transferred = 0; - ctx->result = LIBUSB_ERROR_OTHER; - break; - } - - ctx->done = true; - - libusb_free_transfer(transfer); -} - -int usbEpPlatformRead(void *fdKey, void *data, int size) -{ - int rc = 0; - - if (isServer) { -#if defined(__unix__) - if(usbFdRead < 0) - { - return -1; - } - - rc = read(usbFdRead, data, size); -#endif - } else { - if (deviceDisconnected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - struct libusb_transfer *transfer = libusb_alloc_transfer(0); - if (!transfer) { - return LIBUSB_ERROR_NO_MEM; - } - - AsyncContext asyncCtx = { 0 }; - - libusb_fill_bulk_transfer( - transfer, - dev_handle, - usbFdRead, - (unsigned char*)data, - size, - bulk_transfer_callback, - &asyncCtx, - TIMEOUT - ); - - if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - libusb_free_transfer(transfer); - return LIBUSB_ERROR_OTHER; - } - - // Wait for transfer completion - while (!asyncCtx.done) { - libusb_handle_events(ctx); - } - - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; - } - - return rc; -} - -int usbEpPlatformWrite(void *fdKey, void *data, int size) -{ - int rc = 0; - - if (isServer) { -#if defined(__unix__) - if(usbFdWrite < 0) - { - return -1; - } - - rc = write(usbFdWrite, data, size); -#endif - } else { - if (deviceDisconnected) { - return LIBUSB_ERROR_NO_DEVICE; - } - - struct libusb_transfer *transfer = libusb_alloc_transfer(0); - if (!transfer) { - return LIBUSB_ERROR_NO_MEM; - } - - AsyncContext asyncCtx = { .result = 0, .transferred = 0, .done = false }; - - libusb_fill_bulk_transfer( - transfer, - dev_handle, - usbFdWrite, - (unsigned char*)data, - size, - bulk_transfer_callback, - &asyncCtx, - TIMEOUT - ); - - if (libusb_submit_transfer(transfer) != LIBUSB_SUCCESS) { - libusb_free_transfer(transfer); - return LIBUSB_ERROR_OTHER; - } - - // Wait for transfer completion - while (!asyncCtx.done) { - libusb_handle_events(ctx); - } - - return asyncCtx.result == 0 ? asyncCtx.transferred : asyncCtx.result; - } - - return rc; -} - -int usbepGetDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices) { - int error = 0; - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - return LIBUSB_ERROR_NO_DEVICE; - } - - error = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (error != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return error; - } - - // Check if the interface exists - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - error = libusb_get_active_config_descriptor(dev, &config); - if (error != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return error; - } - - // Look for INTERFACE_XLINK - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - if (config->interface[i].altsetting[0].bInterfaceNumber == INTERFACE_XLINK) { - if(config->interface[i].altsetting[0].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[0].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - break; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - - *out_amountOfFoundDevices = 0; - - return X_LINK_PLATFORM_SUCCESS; - } - - // Get device descriptor - struct libusb_device_descriptor desc; - int r = libusb_get_device_descriptor(dev, &desc); - if (r < 0) { - return X_LINK_PLATFORM_ERROR; - } - - // Get device mxid - char mxId[32] = {'\0'}; - - r = libusb_get_string_descriptor_ascii(gate_dev_handle, desc.iSerialNumber, ((uint8_t*) mxId), 32); - - - /* Now we claim our ffs interfaces */ - r = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (r != LIBUSB_SUCCESS) { - return r; - } - - struct USBRequest_t { - uint32_t RequestNum; - uint32_t RequestSize; - }; - - int transferred; - USBRequest_t request = { - .RequestNum = 12, - .RequestSize = 0, - }; - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)&request, sizeof(request), &transferred, TIMEOUT); - if (transferred < sizeof(request) || r != 0) { - return X_LINK_PLATFORM_ERROR; - } - - USBRequest_t response; - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&response, sizeof(response), &transferred, TIMEOUT); - if (transferred < sizeof(request) || r != 0) { - return X_LINK_PLATFORM_ERROR; - } - - if (response.RequestNum != 0) { - return X_LINK_PLATFORM_ERROR; - } - - if (response.RequestSize != 0) { - std::vector respBuffer; - respBuffer.resize(response.RequestSize); - r = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)&respBuffer[0], response.RequestSize, &transferred, TIMEOUT); - - struct GateResponse_t { - uint32_t state; - uint32_t protocol; - uint32_t platform; - } gateResponse; - - size_t strLen = response.RequestSize - sizeof(gateResponse); - char *string = (char*)malloc(strLen + 1); - memcpy(string, (const char*)&respBuffer[0], strLen); - string[strLen] = '\0'; - - memcpy(&gateResponse, &respBuffer[strLen], sizeof(gateResponse)); - - int numDevicesFound = 0; - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - if (gateResponse.platform == 4) { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; - } else if (gateResponse.platform == 3) { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; - } else { - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)0; - } - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strcpy(out_foundDevices[numDevicesFound].mxid, mxId); - numDevicesFound++; - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - free(string); - } else { - int numDevicesFound = 0; - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = X_LINK_SUCCESS; - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_EP; - out_foundDevices[numDevicesFound].state = X_LINK_GATE; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strcpy(out_foundDevices[numDevicesFound].name, "USB EP"); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strcpy(out_foundDevices[numDevicesFound].mxid, mxId); - numDevicesFound++; - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - } - - libusb_close(gate_dev_handle); - - return X_LINK_PLATFORM_SUCCESS; -} - - -int usbEpPlatformGateRead(void *data, int size) -{ - int rc = 0; - - /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - - return rc; - } - - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - rc = libusb_get_active_config_descriptor(dev, &config); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return rc; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (rc != LIBUSB_SUCCESS) { - return rc; - } - - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_IN_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - - libusb_close(gate_dev_handle); - - return rc; -} - -int usbEpPlatformGateWrite(void *data, int size) -{ - int rc = 0; - - /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(gate_ctx, VENDOR_ID, PRODUCT_ID); - if (gate_dev_handle == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; - } - - /* Not strictly necessary, but it is better to use it, - * as we're using kernel modules together with our interfaces - */ - rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - - return rc; - } - - libusb_device* dev = libusb_get_device(gate_dev_handle); - struct libusb_config_descriptor* config; - rc = libusb_get_active_config_descriptor(dev, &config); - if (rc != LIBUSB_SUCCESS) { - libusb_close(gate_dev_handle); - return rc; - } - - // Try claiming interface 0 to test if it's in use - bool found = false; - unsigned char name_buf[256]; - for (uint8_t i = 0; i < config->bNumInterfaces; ++i) { - for (int j = 0; j < config->interface[i].num_altsetting; j++) { - if(config->interface[i].altsetting[j].iInterface > 0) { - int r = libusb_get_string_descriptor_ascii(gate_dev_handle, - config->interface[i].altsetting[j].iInterface, - name_buf, - sizeof(name_buf)); - - if (r > 0) { - name_buf[r] = '\0'; - if (strcmp((char*)name_buf, INTERFACE_XLINK_NAME) == 0) { - found = true; - } - } - } - } - } - - libusb_free_config_descriptor(config); - - if (!found) { - libusb_close(gate_dev_handle); - - return LIBUSB_ERROR_NO_DEVICE; - } - - /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, INTERFACE_GATE); - if (rc != LIBUSB_SUCCESS) { - - return rc; - } - - rc = libusb_bulk_transfer(gate_dev_handle, ENDPOINT_OUT_BASE, (unsigned char*)data, size, &rc, TIMEOUT); - - libusb_close(gate_dev_handle); - - return rc; -} diff --git a/src/pc/protocols/usb_host_ep.h b/src/pc/protocols/usb_host_ep.h deleted file mode 100644 index 77127b5f..00000000 --- a/src/pc/protocols/usb_host_ep.h +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (C) 2018-2021 Intel Corporation -// SPDX-License-Identifier: Apache-2.0 -// - -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include "XLink/XLink.h" -#include "XLink/XLinkPlatform.h" - -int usbEpInitialize(); - -int usbEpPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd); -int usbEpPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd); -int usbEpPlatformClose(void *fd); - -int usbEpPlatformRead(void *fd, void *data, int size); -int usbEpPlatformWrite(void *fd, void *data, int size); - -int usbepGetDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, unsigned int *out_amountOfFoundDevices); - -int usbEpPlatformGateRead(void *data, int size); -int usbEpPlatformGateWrite(void *data, int size); - -#ifdef __cplusplus -} -#endif diff --git "a/\303\271" "b/\303\271" new file mode 100644 index 00000000..75f7a4e0 --- /dev/null +++ "b/\303\271" @@ -0,0 +1,1429 @@ +// project +#define MVLOG_UNIT_NAME xLinkUsb + +// libraries +#ifdef XLINK_LIBUSB_LOCAL +#include +#else +#include +#endif + +#include "XLink/XLinkLog.h" +#include "XLink/XLinkPlatform.h" +#include "XLink/XLinkPublicDefines.h" +#include "usb_mx_id.h" +#include "usb_host.h" +#include "../PlatformDeviceFd.h" + +// std +#include +#include +#include +#include +#include +#include +#include +#include + +constexpr static int MAXIMUM_PORT_NUMBERS = 7; +using VidPid = std::pair; +static const int MX_ID_TIMEOUT_MS = 100; + +static constexpr auto DEFAULT_OPEN_TIMEOUT = std::chrono::seconds(5); +static constexpr auto DEFAULT_WRITE_TIMEOUT = 2000; +static constexpr auto DEFAULT_READ_TIMEOUT = 2000; +static constexpr std::chrono::milliseconds DEFAULT_CONNECT_TIMEOUT{20000}; +static constexpr std::chrono::milliseconds DEFAULT_SEND_FILE_TIMEOUT{10000}; +static constexpr auto USB1_CHUNKSZ = 64; + +static constexpr int USB_ENDPOINT_IN = 0x81; +static constexpr int USB_ENDPOINT_OUT = 0x01; + +static constexpr int XLINK_USB_DATA_TIMEOUT = 0; + +static unsigned int bulk_chunklen = DEFAULT_CHUNKSZ; +static int write_timeout = DEFAULT_WRITE_TIMEOUT; +static int initialized; + +struct UsbSetupPacket { + uint8_t requestType; + uint8_t request; + uint16_t value; + uint16_t index; + uint16_t length; +}; + +static UsbSetupPacket bootBootloaderPacket{ + 0x00, // bmRequestType: device-directed + 0xF5, // bRequest: custom + 0x0DA1, // wValue: custom + 0x0000, // wIndex + 0 // not used +}; + + + +static std::mutex mutex; +static libusb_context* context; + +int usbInitialize(void* options){ + #ifdef __ANDROID__ + // If Android, set the options as JavaVM (to default context) + if(options != nullptr){ + libusb_set_option(NULL, libusb_option::LIBUSB_OPTION_ANDROID_JAVAVM, options); + } + #endif + + // // Debug + // mvLogLevelSet(MVLOG_DEBUG); + + #if defined(_WIN32) && defined(_MSC_VER) + return usbInitialize_customdir((void**)&context); + #endif + return libusb_init(&context); +} + +struct pair_hash { + template + std::size_t operator() (const std::pair &pair) const { + return std::hash()(pair.first) ^ std::hash()(pair.second); + } +}; + +static std::unordered_map vidPidToDeviceState = { + {{0x03E7, 0x2485}, X_LINK_UNBOOTED}, + {{0x03E7, 0xf63b}, X_LINK_BOOTED}, + {{0x03E7, 0xf63c}, X_LINK_BOOTLOADER}, + {{0x03E7, 0xf63d}, X_LINK_FLASH_BOOTED}, + {{0x05C6, 0x4321}, X_LINK_GATE}, +}; + + +struct USBGateRequest { + uint32_t RequestNum; + uint32_t RequestSize; +}; + +struct GateResponse { + uint32_t state; + uint32_t protocol; + uint32_t platform; +}; + +static std::string getLibusbDevicePath(libusb_device *dev); +static libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId); +static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial); +static const char* xlink_libusb_strerror(int x); +#ifdef _WIN32 +std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev); +#endif + +xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, + deviceDesc_t* out_foundDevices, int sizeFoundDevices, + unsigned int *out_amountOfFoundDevices) { + + // Also protects usb_mx_id_cache + std::lock_guard l(mutex); + + // Get list of usb devices + static libusb_device **devs = NULL; + auto numDevices = libusb_get_device_list(context, &devs); + if(numDevices < 0) { + mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); + return X_LINK_PLATFORM_ERROR; + } + + // Initialize mx id cache + usb_mx_id_cache_init(); + + // Loop over all usb devices, increase count only if myriad device + int numDevicesFound = 0; + for(ssize_t i = 0; i < numDevices; i++) { + if(devs[i] == nullptr) continue; + + if(numDevicesFound >= sizeFoundDevices){ + break; + } + + // Get device descriptor + struct libusb_device_descriptor desc; + auto res = libusb_get_device_descriptor(devs[i], &desc); + if (res < 0) { + mvLog(MVLOG_DEBUG, "Unable to get USB device descriptor: %s", xlink_libusb_strerror(res)); + continue; + } + + VidPid vidpid{desc.idVendor, desc.idProduct}; + + if(vidPidToDeviceState.count(vidpid) > 0){ + // Device found + + // Device status + XLinkError_t status = X_LINK_SUCCESS; + + // Get device state + XLinkDeviceState_t state = vidPidToDeviceState.at(vidpid); + // Check if compare with state + if(in_deviceRequirements.state != X_LINK_ANY_STATE && state != in_deviceRequirements.state){ + // Current device doesn't match the "filter" + continue; + } + + // Get device name + std::string devicePath = getLibusbDevicePath(devs[i]); + // Check if compare with name, if name is only a hint, don't filter + + if(!in_deviceRequirements.nameHintOnly){ + std::string requiredName(in_deviceRequirements.name); + if(requiredName.length() > 0 && requiredName != devicePath){ + // Current device doesn't match the "filter" + continue; + } + } + + // Check for RVC3 and RVC4 first + if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + GateResponse gateResponse; + std::string serial; + getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, serial); + + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; + out_foundDevices[numDevicesFound].protocol = (XLinkProtocol_t)gateResponse.protocol; + out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, serial.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + } else { + // Get device mxid + std::string mxId; + libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); + mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); + switch (rc) + { + case LIBUSB_SUCCESS: + status = X_LINK_SUCCESS; + break; + case LIBUSB_ERROR_ACCESS: + status = X_LINK_INSUFFICIENT_PERMISSIONS; + break; + case LIBUSB_ERROR_BUSY: + status = X_LINK_DEVICE_ALREADY_IN_USE; + break; + default: + status = X_LINK_ERROR; + break; + } + + // compare with MxId + std::string requiredMxId(in_deviceRequirements.mxid); + if(requiredMxId.length() > 0 && requiredMxId != mxId){ + // Current device doesn't match the "filter" + continue; + } + + // TODO(themarpe) - check platform + + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; + out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; + out_foundDevices[numDevicesFound].state = state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; + } + + } + + } + + // Free list of usb devices + libusb_free_device_list(devs, 1); + + // Write the number of found devices + *out_amountOfFoundDevices = numDevicesFound; + + return X_LINK_PLATFORM_SUCCESS; +} + +extern "C" xLinkPlatformErrorCode_t refLibusbDeviceByName(const char* name, libusb_device** pdev) { + + std::lock_guard l(mutex); + + // Get list of usb devices + static libusb_device **devs = NULL; + auto numDevices = libusb_get_device_list(context, &devs); + if(numDevices < 0) { + mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); + return X_LINK_PLATFORM_ERROR; + } + + // Loop over all usb devices, increase count only if myriad device + bool found = false; + for(ssize_t i = 0; i < numDevices; i++) { + if(devs[i] == nullptr) continue; + + // Check path only + std::string devicePath = getLibusbDevicePath(devs[i]); + // Check if compare with name + std::string requiredName(name); + if(requiredName.length() > 0 && requiredName == devicePath){ + // Found, increase ref and exit the loop + libusb_ref_device(devs[i]); + *pdev = devs[i]; + found = true; + break; + } + } + + // Free list of usb devices (unref each) + libusb_free_device_list(devs, 1); + + if(!found){ + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } + + return X_LINK_PLATFORM_SUCCESS; +} + + + +std::string getLibusbDevicePath(libusb_device *dev) { + + std::string devicePath = ""; + + // Add bus number + uint8_t bus = libusb_get_bus_number(dev); + devicePath += std::to_string(bus) + "."; + + // Add all subsequent port numbers + uint8_t portNumbers[MAXIMUM_PORT_NUMBERS]; + int count = libusb_get_port_numbers(dev, portNumbers, MAXIMUM_PORT_NUMBERS); + if (count == LIBUSB_ERROR_OVERFLOW) { + // shouldn't happen! + return ""; + } + if(count == 0){ + // Only bus number is available + return devicePath; + } + + for (int i = 0; i < count - 1; i++){ + devicePath += std::to_string(portNumbers[i]) + "."; + } + devicePath += std::to_string(portNumbers[count - 1]); + + // Return the device path + return devicePath; +} + +libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId) +{ + char mxId[XLINK_MAX_MX_ID_SIZE] = {0}; + + // Default MXID - empty + outMxId = ""; + + // first check if entry already exists in the list (and is still valid) + // if found, it stores it into mx_id variable + bool found = usb_mx_id_cache_get_entry(devicePath.c_str(), mxId); + + if(found){ + mvLog(MVLOG_DEBUG, "Found cached MX ID: %s", mxId); + outMxId = std::string(mxId); + return LIBUSB_SUCCESS; + } else { + // If not found, retrieve mxId + + // get serial from usb descriptor + libusb_device_handle *handle = nullptr; + int libusb_rc = LIBUSB_SUCCESS; + + // Retry getting MX ID for 15ms + const std::chrono::milliseconds RETRY_TIMEOUT{15}; // 15ms + const std::chrono::microseconds SLEEP_BETWEEN_RETRIES{100}; // 100us + + auto t1 = std::chrono::steady_clock::now(); + do { + + // Open device - if not already + if(handle == nullptr){ + libusb_rc = libusb_open(dev, &handle); + if (libusb_rc < 0){ + // Some kind of error, either NO_MEM, ACCESS, NO_DEVICE or other + mvLog(MVLOG_DEBUG, "libusb_open: %s", xlink_libusb_strerror(libusb_rc)); + + // If WIN32, access error and state == BOOTED + #ifdef _WIN32 + if(libusb_rc == LIBUSB_ERROR_ACCESS && state == X_LINK_BOOTED) { + auto winMxId = getWinUsbMxId({pDesc->idVendor, pDesc->idProduct}, dev); + if(winMxId != "") { + strncpy(mxId, winMxId.c_str(), sizeof(mxId) - 1); + libusb_rc = 0; + break; + } + } + #endif + + // retry + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + continue; + } + } + + // if UNBOOTED state, perform mx_id retrieval procedure using small program and a read command + if(state == X_LINK_UNBOOTED){ + + // Get configuration first (From OS cache) + int active_configuration = -1; + if( (libusb_rc = libusb_get_configuration(handle, &active_configuration)) == 0){ + if(active_configuration != 1){ + mvLog(MVLOG_DEBUG, "Setting configuration from %d to 1\n", active_configuration); + if ((libusb_rc = libusb_set_configuration(handle, 1)) < 0) { + mvLog(MVLOG_ERROR, "libusb_set_configuration: %s", xlink_libusb_strerror(libusb_rc)); + + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + } + } else { + // getting config failed... + mvLog(MVLOG_ERROR, "libusb_set_configuration: %s", xlink_libusb_strerror(libusb_rc)); + + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + + + // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) + libusb_set_auto_detach_kernel_driver(handle, 1); + // Claim interface (as we'll be doing IO on endpoints) + if ((libusb_rc = libusb_claim_interface(handle, 0)) < 0) { + if(libusb_rc != LIBUSB_ERROR_BUSY){ + mvLog(MVLOG_ERROR, "libusb_claim_interface: %s", xlink_libusb_strerror(libusb_rc)); + } else { + mvLog(MVLOG_DEBUG, "libusb_claim_interface: %s", xlink_libusb_strerror(libusb_rc)); + } + // retry - most likely device busy by another app + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + + + const int send_ep = 0x01; + int transferred = 0; + + // /////////////////////// + // Start + // WD Protection & MXID Retrieval Command + transferred = 0; + libusb_rc = libusb_bulk_transfer(handle, send_ep, ((uint8_t*) usb_mx_id_get_payload()), usb_mx_id_get_payload_size(), &transferred, MX_ID_TIMEOUT_MS); + if (libusb_rc < 0 || usb_mx_id_get_payload_size() != transferred) { + mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, usb_mx_id_get_payload_size()); + // Mark as error and retry + libusb_rc = -1; + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + + // MXID Read + const int recv_ep = 0x81; + const int expectedMxIdReadSize = 9; + uint8_t rbuf[128]; + transferred = 0; + libusb_rc = libusb_bulk_transfer(handle, recv_ep, rbuf, sizeof(rbuf), &transferred, MX_ID_TIMEOUT_MS); + if (libusb_rc < 0 || expectedMxIdReadSize != transferred) { + mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, expectedMxIdReadSize); + // Mark as error and retry + libusb_rc = -1; + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + + // WD Protection end + transferred = 0; + libusb_rc = libusb_bulk_transfer(handle, send_ep, ((uint8_t*) usb_mx_id_get_payload_end()), usb_mx_id_get_payload_end_size(), &transferred, MX_ID_TIMEOUT_MS); + if (libusb_rc < 0 || usb_mx_id_get_payload_end_size() != transferred) { + mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, usb_mx_id_get_payload_end_size()); + // Mark as error and retry + libusb_rc = -1; + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + // End + /////////////////////// + + // Release claimed interface + // ignore error as it doesn't matter + libusb_release_interface(handle, 0); + + // Parse mxId into HEX presentation + // There's a bug, it should be 0x0F, but setting as in MDK + rbuf[8] &= 0xF0; + + // Convert to HEX presentation and store into mx_id + for (int i = 0; i < expectedMxIdReadSize; i++) { + snprintf(mxId + 2*i, 3, "%02X", rbuf[i]); + } + + // Indicate no error + libusb_rc = 0; + + } else { + + if( (libusb_rc = libusb_get_string_descriptor_ascii(handle, pDesc->iSerialNumber, ((uint8_t*) mxId), sizeof(mxId))) < 0){ + mvLog(MVLOG_WARN, "Failed to get string descriptor"); + + // retry + std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); + continue; + } + + // Indicate no error + libusb_rc = 0; + + } + + } while (libusb_rc != 0 && std::chrono::steady_clock::now() - t1 < RETRY_TIMEOUT); + + // Close opened device + if(handle != nullptr){ + libusb_close(handle); + } + + // if mx_id couldn't be retrieved, exit by returning error + if(libusb_rc != 0){ + return (libusb_error) libusb_rc; + } + + // Cache the retrieved mx_id + // Find empty space and store this entry + // If no empty space, don't cache (possible case: >16 devices) + int cache_index = usb_mx_id_cache_store_entry(mxId, devicePath.c_str()); + if(cache_index >= 0){ + // debug print + mvLog(MVLOG_DEBUG, "Cached MX ID %s at index %d", mxId, cache_index); + } else { + // debug print + mvLog(MVLOG_DEBUG, "Couldn't cache MX ID %s", mxId); + } + + } + + outMxId = std::string(mxId); + return libusb_error::LIBUSB_SUCCESS; + +} + +static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial) { + GateResponse gateResponse = {0}; + std::string serial = ""; + + // get serial from usb descriptor + libusb_device_handle *handle = nullptr; + int libusb_rc = LIBUSB_SUCCESS; + libusb_rc = libusb_open(dev, &handle); + if (libusb_rc != 0){ + return (libusb_error) libusb_rc; + } + + libusb_rc = libusb_claim_interface(handle, 0); + if (libusb_rc != 0){ + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + USBGateRequest usbGateRequest = { + .RequestNum = 12, + .RequestSize = 0, + }; + + int transferred = 0; + + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + USBGateRequest usbGateResponse = { 0 }; + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + std::vector respBuffer; + respBuffer.resize(usbGateResponse.RequestSize); + libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); + if (libusb_rc != 0) { + libusb_close(handle); + return (libusb_error) libusb_rc; + } + + size_t serialStrLen = usbGateResponse.RequestSize - sizeof(GateResponse); + char *serialStr = (char*)malloc(serialStrLen + 1); + memcpy(serialStr, (const char*)&respBuffer[0], serialStrLen); + serialStr[serialStrLen] = '\0'; + serial = serialStr; + free(serialStr); + outSerial = serial; + + memcpy(&gateResponse, &respBuffer[serialStrLen], sizeof(gateResponse)); + outGateResponse = gateResponse; + + // Close opened device + if(handle != nullptr){ + libusb_close(handle); + } + + return libusb_error::LIBUSB_SUCCESS; +} + +const char* xlink_libusb_strerror(int x) { + return libusb_strerror((libusb_error) x); +} + + +static libusb_error usb_open_device(libusb_device *dev, uint8_t* endpoint, libusb_device_handle*& handle) +{ + struct libusb_config_descriptor *cdesc; + const struct libusb_interface_descriptor *ifdesc; + libusb_device_handle *h = NULL; + int res; + + if((res = libusb_open(dev, &h)) < 0) + { + mvLog(MVLOG_DEBUG, "cannot open device: %s\n", xlink_libusb_strerror(res)); +return (libusb_error) res; + } + + // Get configuration first + int active_configuration = -1; + if((res = libusb_get_configuration(h, &active_configuration)) < 0){ + mvLog(MVLOG_DEBUG, "setting config 1 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + + // Check if set configuration call is needed + if(active_configuration != 1){ + mvLog(MVLOG_DEBUG, "Setting configuration from %d to 1\n", active_configuration); + if ((res = libusb_set_configuration(h, 1)) < 0) { + mvLog(MVLOG_ERROR, "libusb_set_configuration: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + } + + // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) + libusb_set_auto_detach_kernel_driver(h, 1); + if((res = libusb_claim_interface(h, 0)) < 0) + { + mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + + // TODO(TheMutta): Find a way to enable this conditionally + //#if (!defined(USE_USB_VSC)) + // XLink EPs + if((res = libusb_claim_interface(h, 1)) < 0) + { + mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + + + + if((res = libusb_get_config_descriptor(dev, 0, &cdesc)) < 0) + { + mvLog(MVLOG_DEBUG, "Unable to get USB config descriptor: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } + ifdesc = cdesc->interface->altsetting; + for(int i=0; ibNumEndpoints; i++) + { + mvLog(MVLOG_DEBUG, "Found EP 0x%02x : max packet size is %u bytes", + ifdesc->endpoint[i].bEndpointAddress, ifdesc->endpoint[i].wMaxPacketSize); + if((ifdesc->endpoint[i].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) != LIBUSB_TRANSFER_TYPE_BULK) + continue; + if( !(ifdesc->endpoint[i].bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ) + { + *endpoint = ifdesc->endpoint[i].bEndpointAddress; + bulk_chunklen = ifdesc->endpoint[i].wMaxPacketSize; + libusb_free_config_descriptor(cdesc); + handle = h; + return LIBUSB_SUCCESS; + } + } + libusb_free_config_descriptor(cdesc); + libusb_close(h); + return LIBUSB_ERROR_ACCESS; +} + +static int send_file(libusb_device_handle* h, uint8_t endpoint, const void* tx_buf, unsigned filesize,uint16_t bcdusb) +{ + using namespace std::chrono; + + uint8_t *p; + int rc; + int wb, twb, wbr; + int bulk_chunklen = DEFAULT_CHUNKSZ; + twb = 0; + p = const_cast((const uint8_t*)tx_buf); + int send_zlp = ((filesize % 512) == 0); + + if(bcdusb < 0x200) { + bulk_chunklen = USB1_CHUNKSZ; + } + + auto t1 = steady_clock::now(); + mvLog(MVLOG_DEBUG, "Performing bulk write of %u bytes...", filesize); + while(((unsigned)twb < filesize) || send_zlp) + { + wb = filesize - twb; + if(wb > bulk_chunklen) + wb = bulk_chunklen; + wbr = 0; + rc = libusb_bulk_transfer(h, endpoint, p, wb, &wbr, write_timeout); + if((rc || (wb != wbr)) && (wb != 0)) // Don't check the return code for ZLP + { + if(rc == LIBUSB_ERROR_NO_DEVICE) + break; + mvLog(MVLOG_WARN, "bulk write: %s (%d bytes written, %d bytes to write)", xlink_libusb_strerror(rc), wbr, wb); + if(rc == LIBUSB_ERROR_TIMEOUT) + return USB_BOOT_TIMEOUT; + else return USB_BOOT_ERROR; + } + if (steady_clock::now() - t1 > DEFAULT_SEND_FILE_TIMEOUT) { + return USB_BOOT_TIMEOUT; + } + if(wb == 0) // ZLP just sent, last packet + break; + twb += wbr; + p += wbr; + } + +#ifndef NDEBUG + double MBpS = ((double)filesize / 1048576.) / (duration_cast>(steady_clock::now() - t1)).count(); + mvLog(MVLOG_DEBUG, "Successfully sent %u bytes of data in %lf ms (%lf MB/s)", filesize, duration_cast(steady_clock::now() - t1).count(), MBpS); +#endif + + return 0; +} + +int usb_boot(const char *addr, const void *mvcmd, unsigned size) +{ + using namespace std::chrono; + + int rc = 0; + uint8_t endpoint; + + libusb_device *dev = nullptr; + libusb_device_handle *h; + uint16_t bcdusb=-1; + libusb_error res = LIBUSB_ERROR_ACCESS; + + + auto t1 = steady_clock::now(); + do { + if(refLibusbDeviceByName(addr, &dev) == X_LINK_PLATFORM_SUCCESS){ + break; + } + std::this_thread::sleep_for(milliseconds(10)); + } while(steady_clock::now() - t1 < DEFAULT_CONNECT_TIMEOUT); + + if(dev == nullptr) { + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } + + auto t2 = steady_clock::now(); + do { + if((res = usb_open_device(dev, &endpoint, h)) == LIBUSB_SUCCESS){ + break; + } + std::this_thread::sleep_for(milliseconds(100)); + } while(steady_clock::now() - t2 < DEFAULT_CONNECT_TIMEOUT); + + if(res == LIBUSB_SUCCESS) { + rc = send_file(h, endpoint, mvcmd, size, bcdusb); + libusb_release_interface(h, 0); + libusb_close(h); + } else { + if(res == LIBUSB_ERROR_ACCESS) { + rc = X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; + } else if(res == LIBUSB_ERROR_BUSY) { + rc = X_LINK_PLATFORM_DEVICE_BUSY; + } else { + rc = X_LINK_PLATFORM_ERROR; + } + } + + if (dev) { + libusb_unref_device(dev); + } + + return rc; +} + + + +xLinkPlatformErrorCode_t usbLinkOpen(const char *path, libusb_device_handle*& h) +{ + using namespace std::chrono; + if (path == NULL) { + return X_LINK_PLATFORM_INVALID_PARAMETERS; + } + + usbBootError_t rc = USB_BOOT_DEVICE_NOT_FOUND; + h = nullptr; + libusb_device *dev = nullptr; + bool found = false; + + auto t1 = steady_clock::now(); + do { + if(refLibusbDeviceByName(path, &dev) == X_LINK_PLATFORM_SUCCESS){ + found = true; + break; + } + } while(steady_clock::now() - t1 < DEFAULT_OPEN_TIMEOUT); + + if(!found) { + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } + + uint8_t ep = 0; + libusb_error libusb_rc = usb_open_device(dev, &ep, h); + if(libusb_rc == LIBUSB_SUCCESS) { + return X_LINK_PLATFORM_SUCCESS; + } else if(libusb_rc == LIBUSB_ERROR_ACCESS) { + return X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; + } else if(libusb_rc == LIBUSB_ERROR_BUSY) { + return X_LINK_PLATFORM_DEVICE_BUSY; + } else { + return X_LINK_PLATFORM_ERROR; + } +} + + +xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { + + libusb_device *dev = nullptr; + auto refErr = refLibusbDeviceByName(path, &dev); + if(refErr != X_LINK_PLATFORM_SUCCESS) { + return refErr; + } + if(dev == NULL){ + return X_LINK_PLATFORM_ERROR; + } + libusb_device_handle *h = NULL; + + + int libusb_rc = libusb_open(dev, &h); + if (libusb_rc < 0) { + libusb_unref_device(dev); + if(libusb_rc == LIBUSB_ERROR_ACCESS) { + return X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; + } + return X_LINK_PLATFORM_ERROR; + } + + // Make control transfer + libusb_rc = libusb_control_transfer(h, + bootBootloaderPacket.requestType, // bmRequestType: device-directed + bootBootloaderPacket.request, // bRequest: custom + bootBootloaderPacket.value, // wValue: custom + bootBootloaderPacket.index, // wIndex + NULL, // data pointer + 0, // data size + 1000 // timeout [ms] + ); + + // Ignore error and close device + libusb_unref_device(dev); + libusb_close(h); + + if(libusb_rc < 0) { + return X_LINK_PLATFORM_ERROR; + } + + return X_LINK_PLATFORM_SUCCESS; +} + +void usbLinkClose(libusb_device_handle *f) +{ + libusb_release_interface(f, 0); + libusb_close(f); +} + + + +int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) +{ +#if (!defined(USE_USB_VSC)) + #ifdef USE_LINK_JTAG + struct sockaddr_in serv_addr; + usbFdWrite = socket(AF_INET, SOCK_STREAM, 0); + usbFdRead = socket(AF_INET, SOCK_STREAM, 0); + assert(usbFdWrite >=0); + assert(usbFdRead >=0); + memset(&serv_addr, '0', sizeof(serv_addr)); + + serv_addr.sin_family = AF_INET; + serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); + serv_addr.sin_port = htons(USB_LINK_SOCKET_PORT); + + if (connect(usbFdWrite, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) + { + mvLog(MVLOG_ERROR, "connect(usbFdWrite,...) returned < 0\n"); + if (usbFdRead >= 0) + close(usbFdRead); + if (usbFdWrite >= 0) + close(usbFdWrite); + usbFdRead = -1; + usbFdWrite = -1; + return X_LINK_PLATFORM_ERROR; + } + return 0; + + #else + + if (strcmp(devPathRead, "/dev/usb-ffs/xlink") { + int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); + int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); + + if(outfd < 0 || infd < 0) { + return X_LINK_PLATFORM_ERROR; + } + + usbFdRead = infd; + usbFdWrite = outfd; + } else { + usbFdRead= open(devPathRead, O_RDWR); + if(usbFdRead < 0) + { + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } + + + // set tty to raw mode + struct termios tty; + speed_t spd; + int rc; + rc = tcgetattr(usbFdRead, &tty); + if (rc < 0) { + close(usbFdRead); + usbFdRead = -1; + return X_LINK_PLATFORM_ERROR; + } + + spd = B115200; + cfsetospeed(&tty, (speed_t)spd); + cfsetispeed(&tty, (speed_t)spd); + + cfmakeraw(&tty); + + rc = tcsetattr(usbFdRead, TCSANOW, &tty); + if (rc < 0) { + close(usbFdRead); + usbFdRead = -1; + return X_LINK_PLATFORM_ERROR; + } + + usbFdWrite= open(devPathWrite, O_RDWR); + if(usbFdWrite < 0) + { + close(usbFdRead); + usbFdWrite = -1; + return X_LINK_PLATFORM_ERROR; + } + // set tty to raw mode + rc = tcgetattr(usbFdWrite, &tty); + if (rc < 0) { + close(usbFdRead); + close(usbFdWrite); + usbFdWrite = -1; + return X_LINK_PLATFORM_ERROR; + } + + spd = B115200; + cfsetospeed(&tty, (speed_t)spd); + cfsetispeed(&tty, (speed_t)spd); + + cfmakeraw(&tty); + + rc = tcsetattr(usbFdWrite, TCSANOW, &tty); + if (rc < 0) { + close(usbFdRead); + close(usbFdWrite); + usbFdWrite = -1; + return X_LINK_PLATFORM_ERROR; + } + return 0; + } + #endif /*USE_LINK_JTAG*/ +#else + + libusb_device_handle* usbHandle = nullptr; + xLinkPlatformErrorCode_t ret = usbLinkOpen(devPathWrite, usbHandle); + + if (ret != X_LINK_PLATFORM_SUCCESS) + { + /* could fail due to port name change */ + return ret; + } + + // Store the usb handle and create a "unique" key instead + // (as file descriptors are reused and can cause a clash with lookups between scheduler and link) + *fd = createPlatformDeviceFdKey(usbHandle); + +#endif /*USE_USB_VSC*/ + + return 0; +} + + +int usbPlatformClose(void *fdKey) +{ + +#ifndef USE_USB_VSC + #ifdef USE_LINK_JTAG + /*Nothing*/ +#else + if (usbFdRead != -1){ + close(usbFdRead); + usbFdRead = -1; + } + if (usbFdWrite != -1){ + close(usbFdWrite); + usbFdWrite = -1; + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find USB Handle by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + usbLinkClose((libusb_device_handle *) tmpUsbHandle); + + if(destroyPlatformDeviceFdKey(fdKey)){ + mvLog(MVLOG_FATAL, "Cannot destroy USB Handle key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + +#endif /*USE_USB_VSC*/ + return -1; +} + + + +int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length){ + + // Boot it + int rc = usb_boot(deviceDesc->name, firmware, (unsigned)length); + + if(!rc) { + mvLog(MVLOG_DEBUG, "Boot successful, device address %s", deviceDesc->name); + } + return rc; +} + + + +int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) +{ + const int chunk_size = DEFAULT_CHUNKSZ; + while(size > 0) + { + int bt, ss = (int)size; + if(ss > chunk_size) + ss = chunk_size; + int rc = libusb_bulk_transfer(f, USB_ENDPOINT_IN + offset,(unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + if(rc) + return rc; + data = ((char *)data) + bt; + size -= bt; + } + return 0; +} + +int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t offset) +{ + const int chunk_size = DEFAULT_CHUNKSZ; + while(size > 0) + { + int bt, ss = (int)size; + if(ss > chunk_size) + ss = chunk_size; + int rc = libusb_bulk_transfer(f, USB_ENDPOINT_OUT + offset, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + if(rc) + return rc; + data = (char *)data + bt; + size -= bt; + } + return 0; +} + +int usbPlatformRead(void* fdKey, void* data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int nread = 0; +#ifdef USE_LINK_JTAG + while (nread < size){ + nread += read(usbFdWrite, &((char*)data)[nread], size - nread); + printf("read %d %d\n", nread, size); + } +#else + if(usbFdRead < 0) + { + return -1; + } + + while(nread < size) + { + int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ + ? PACKET_LENGTH : size - nread; + + while(toRead > 0) + { + rc = read(usbFdRead, &((char*)data)[nread], toRead); + if ( rc < 0) + { + return -2; + } + toRead -=rc; + nread += rc; + } + unsigned char acknowledge = 0xEF; + int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); + if (wc != sizeof(acknowledge)) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_read(usbHandle, data, size, 1); +#endif /*USE_USB_VSC*/ + return rc; +} + +int usbPlatformWrite(void *fdKey, void *data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int byteCount = 0; +#ifdef USE_LINK_JTAG + while (byteCount < size){ + byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); + printf("write %d %d\n", byteCount, size); + } +#else + if(usbFdWrite < 0) + { + return -1; + } + while(byteCount < size) + { + int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ + ? PACKET_LENGTH:size - byteCount; + int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); + + if ( wc != toWrite) + { + return -2; + } + + byteCount += toWrite; + unsigned char acknowledge; + int rc; + rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); + + if ( rc < 0) + { + return -2; + } + + if (acknowledge != 0xEF) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_write(usbHandle, data, size, 1); +#endif /*USE_USB_VSC*/ + return rc; +} + +int usbPlatformGateRead(void* fdKey, void* data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int nread = 0; +#ifdef USE_LINK_JTAG + while (nread < size){ + nread += read(usbFdWrite, &((char*)data)[nread], size - nread); + printf("read %d %d\n", nread, size); + } +#else + if(usbFdRead < 0) + { + return -1; + } + + while(nread < size) + { + int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ + ? PACKET_LENGTH : size - nread; + + while(toRead > 0) + { + rc = read(usbFdRead, &((char*)data)[nread], toRead); + if ( rc < 0) + { + return -2; + } + toRead -=rc; + nread += rc; + } + unsigned char acknowledge = 0xEF; + int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); + if (wc != sizeof(acknowledge)) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_read(usbHandle, data, size, 0); +#endif /*USE_USB_VSC*/ + return rc; +} + +int usbPlatformGateWrite(void *fdKey, void *data, int size) +{ + int rc = 0; +#ifndef USE_USB_VSC + int byteCount = 0; +#ifdef USE_LINK_JTAG + while (byteCount < size){ + byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); + printf("write %d %d\n", byteCount, size); + } +#else + if(usbFdWrite < 0) + { + return -1; + } + while(byteCount < size) + { + int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ + ? PACKET_LENGTH:size - byteCount; + int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); + + if ( wc != toWrite) + { + return -2; + } + + byteCount += toWrite; + unsigned char acknowledge; + int rc; + rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); + + if ( rc < 0) + { + return -2; + } + + if (acknowledge != 0xEF) + { + return -2; + } + } +#endif /*USE_LINK_JTAG*/ +#else + + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + rc = usb_write(usbHandle, data, size, 0); +#endif /*USE_USB_VSC*/ + return rc; +} + + +#ifdef _WIN32 +#include +#include +#pragma comment(lib, "setupapi.lib") +#include +#include + +// get MxId given the vidpid and libusb device (Windows only) +// Uses the Win32 SetupDI* apis. Several cautions: +// - Movidius MyriadX usb devices often change their usb path when they load their bootloader/firmware +// - Since USB is dynamic, it is technically possible for a device to change its path at any time +std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { + if (dev == NULL) return {}; + + // init device info vars + HDEVINFO hDevInfoSet; + SP_DEVINFO_DATA devInfoData{}; + devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); + + // get USB host controllers; each has exactly one root hub + hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hDevInfoSet == INVALID_HANDLE_VALUE) { + return {}; + } + + // iterate over usb host controllers and populate list with their location path for later matching to device paths + std::vector hostControllerLocationPaths; + for(int i = 0; SetupDiEnumDeviceInfo(hDevInfoSet, i, &devInfoData); i++) { + // get location paths as a REG_MULTI_SZ + std::string locationPaths(1023, 0); + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, NULL, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), NULL)) { + continue; + } + + // find PCI path in the multi string and emplace to back of vector + const auto pciPosition = locationPaths.find("PCIROOT"); + if (pciPosition == std::string::npos) { + continue; + } + hostControllerLocationPaths.emplace_back(locationPaths.substr(pciPosition, strnlen_s(locationPaths.c_str() + pciPosition, locationPaths.size() - pciPosition))); + } + + // Free dev info, return if no usb host controllers found + SetupDiDestroyDeviceInfoList(hDevInfoSet); + if (hostControllerLocationPaths.empty()) { + return {}; + } + + // get USB devices + hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (hDevInfoSet == INVALID_HANDLE_VALUE) { + return {}; + } + + // iterate over usb devices and populate with device info + std::string goalPath{getLibusbDevicePath(dev)}; + std::string deviceId; + for(int i = 0; SetupDiEnumDeviceInfo(hDevInfoSet, i, &devInfoData); i++) { + // get device instance id + char instanceId[128] {}; + if(!SetupDiGetDeviceInstanceIdA(hDevInfoSet, &devInfoData, (PSTR)instanceId, sizeof(instanceId), NULL)) { + continue; + } + + // get device vid, pid, and serial id + char serialId[128] {}; + uint16_t vid = 0, pid = 0; + if(sscanf(instanceId, "USB\\VID_%hx&PID_%hx\\%s", &vid, &pid, serialId) != 3) { + continue; + } + + // check if this is the device we are looking for + if(vidpid.first != vid || vidpid.second != pid) { + continue; + } + + // get location paths as a REG_MULTI_SZ + std::string locationPaths(1023, 0); + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, NULL, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), NULL)) { + continue; + } + + // find PCI path in the multi string and isolate that path + const auto pciPosition = locationPaths.find("PCIROOT"); + if (pciPosition == std::string::npos) { + continue; + } + const auto usbPath = locationPaths.substr(pciPosition, strnlen_s(locationPaths.c_str() + pciPosition, locationPaths.size() - pciPosition)); + + // find matching host controller + const auto hostController = std::find_if(hostControllerLocationPaths.begin(), hostControllerLocationPaths.end(), [&usbPath](const std::string& candidateController) noexcept { + // check if the usb path starts with the candidate controller path + return usbPath.find(candidateController) == 0; + }); + if (hostController == hostControllerLocationPaths.end()) { + mvLog(MVLOG_WARN, "Found device with matching vid/pid but no matching USBROOT hub"); + continue; + } + + // initialize pseudo libusb path using the host controller index +1 as the "libusb bus number" + std::string pseudoLibUsbPath = std::to_string(std::distance(hostControllerLocationPaths.begin(), hostController) + 1); + + // there is only one root hub per host controller, it is always on port 0, + // therefore start the search past this known root hub in the usb path + static constexpr auto usbRootLength = sizeof("#USBROOT(0)") - 1; + auto searchPosition{usbPath.c_str() + hostController->size() + usbRootLength}; + + // parse and transform the Windows USB path to the pseudo libusb path + int charsRead = 0; + int port = 0; + while (sscanf(searchPosition, "#USB(%4d)%n", &port, &charsRead) == 1) { + searchPosition += charsRead; + pseudoLibUsbPath += '.' + std::to_string(port); + } + + if(pseudoLibUsbPath == goalPath) { + deviceId = serialId; + break; + } + } + + // Free dev info + SetupDiDestroyDeviceInfoList(hDevInfoSet); + + // Return deviceId if found + return deviceId; +} +#endif From dcc9a91d280fcd24c6a775e16b07b3353e724d7f Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 12:17:02 +0200 Subject: [PATCH 48/78] Removed file. --- "\303\271" | 1429 ---------------------------------------------------- 1 file changed, 1429 deletions(-) delete mode 100644 "\303\271" diff --git "a/\303\271" "b/\303\271" deleted file mode 100644 index 75f7a4e0..00000000 --- "a/\303\271" +++ /dev/null @@ -1,1429 +0,0 @@ -// project -#define MVLOG_UNIT_NAME xLinkUsb - -// libraries -#ifdef XLINK_LIBUSB_LOCAL -#include -#else -#include -#endif - -#include "XLink/XLinkLog.h" -#include "XLink/XLinkPlatform.h" -#include "XLink/XLinkPublicDefines.h" -#include "usb_mx_id.h" -#include "usb_host.h" -#include "../PlatformDeviceFd.h" - -// std -#include -#include -#include -#include -#include -#include -#include -#include - -constexpr static int MAXIMUM_PORT_NUMBERS = 7; -using VidPid = std::pair; -static const int MX_ID_TIMEOUT_MS = 100; - -static constexpr auto DEFAULT_OPEN_TIMEOUT = std::chrono::seconds(5); -static constexpr auto DEFAULT_WRITE_TIMEOUT = 2000; -static constexpr auto DEFAULT_READ_TIMEOUT = 2000; -static constexpr std::chrono::milliseconds DEFAULT_CONNECT_TIMEOUT{20000}; -static constexpr std::chrono::milliseconds DEFAULT_SEND_FILE_TIMEOUT{10000}; -static constexpr auto USB1_CHUNKSZ = 64; - -static constexpr int USB_ENDPOINT_IN = 0x81; -static constexpr int USB_ENDPOINT_OUT = 0x01; - -static constexpr int XLINK_USB_DATA_TIMEOUT = 0; - -static unsigned int bulk_chunklen = DEFAULT_CHUNKSZ; -static int write_timeout = DEFAULT_WRITE_TIMEOUT; -static int initialized; - -struct UsbSetupPacket { - uint8_t requestType; - uint8_t request; - uint16_t value; - uint16_t index; - uint16_t length; -}; - -static UsbSetupPacket bootBootloaderPacket{ - 0x00, // bmRequestType: device-directed - 0xF5, // bRequest: custom - 0x0DA1, // wValue: custom - 0x0000, // wIndex - 0 // not used -}; - - - -static std::mutex mutex; -static libusb_context* context; - -int usbInitialize(void* options){ - #ifdef __ANDROID__ - // If Android, set the options as JavaVM (to default context) - if(options != nullptr){ - libusb_set_option(NULL, libusb_option::LIBUSB_OPTION_ANDROID_JAVAVM, options); - } - #endif - - // // Debug - // mvLogLevelSet(MVLOG_DEBUG); - - #if defined(_WIN32) && defined(_MSC_VER) - return usbInitialize_customdir((void**)&context); - #endif - return libusb_init(&context); -} - -struct pair_hash { - template - std::size_t operator() (const std::pair &pair) const { - return std::hash()(pair.first) ^ std::hash()(pair.second); - } -}; - -static std::unordered_map vidPidToDeviceState = { - {{0x03E7, 0x2485}, X_LINK_UNBOOTED}, - {{0x03E7, 0xf63b}, X_LINK_BOOTED}, - {{0x03E7, 0xf63c}, X_LINK_BOOTLOADER}, - {{0x03E7, 0xf63d}, X_LINK_FLASH_BOOTED}, - {{0x05C6, 0x4321}, X_LINK_GATE}, -}; - - -struct USBGateRequest { - uint32_t RequestNum; - uint32_t RequestSize; -}; - -struct GateResponse { - uint32_t state; - uint32_t protocol; - uint32_t platform; -}; - -static std::string getLibusbDevicePath(libusb_device *dev); -static libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId); -static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial); -static const char* xlink_libusb_strerror(int x); -#ifdef _WIN32 -std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev); -#endif - -xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices) { - - // Also protects usb_mx_id_cache - std::lock_guard l(mutex); - - // Get list of usb devices - static libusb_device **devs = NULL; - auto numDevices = libusb_get_device_list(context, &devs); - if(numDevices < 0) { - mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); - return X_LINK_PLATFORM_ERROR; - } - - // Initialize mx id cache - usb_mx_id_cache_init(); - - // Loop over all usb devices, increase count only if myriad device - int numDevicesFound = 0; - for(ssize_t i = 0; i < numDevices; i++) { - if(devs[i] == nullptr) continue; - - if(numDevicesFound >= sizeFoundDevices){ - break; - } - - // Get device descriptor - struct libusb_device_descriptor desc; - auto res = libusb_get_device_descriptor(devs[i], &desc); - if (res < 0) { - mvLog(MVLOG_DEBUG, "Unable to get USB device descriptor: %s", xlink_libusb_strerror(res)); - continue; - } - - VidPid vidpid{desc.idVendor, desc.idProduct}; - - if(vidPidToDeviceState.count(vidpid) > 0){ - // Device found - - // Device status - XLinkError_t status = X_LINK_SUCCESS; - - // Get device state - XLinkDeviceState_t state = vidPidToDeviceState.at(vidpid); - // Check if compare with state - if(in_deviceRequirements.state != X_LINK_ANY_STATE && state != in_deviceRequirements.state){ - // Current device doesn't match the "filter" - continue; - } - - // Get device name - std::string devicePath = getLibusbDevicePath(devs[i]); - // Check if compare with name, if name is only a hint, don't filter - - if(!in_deviceRequirements.nameHintOnly){ - std::string requiredName(in_deviceRequirements.name); - if(requiredName.length() > 0 && requiredName != devicePath){ - // Current device doesn't match the "filter" - continue; - } - } - - // Check for RVC3 and RVC4 first - if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ - GateResponse gateResponse; - std::string serial; - getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, serial); - - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; - out_foundDevices[numDevicesFound].protocol = (XLinkProtocol_t)gateResponse.protocol; - out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, serial.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; - } else { - // Get device mxid - std::string mxId; - libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); - mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); - switch (rc) - { - case LIBUSB_SUCCESS: - status = X_LINK_SUCCESS; - break; - case LIBUSB_ERROR_ACCESS: - status = X_LINK_INSUFFICIENT_PERMISSIONS; - break; - case LIBUSB_ERROR_BUSY: - status = X_LINK_DEVICE_ALREADY_IN_USE; - break; - default: - status = X_LINK_ERROR; - break; - } - - // compare with MxId - std::string requiredMxId(in_deviceRequirements.mxid); - if(requiredMxId.length() > 0 && requiredMxId != mxId){ - // Current device doesn't match the "filter" - continue; - } - - // TODO(themarpe) - check platform - - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; - out_foundDevices[numDevicesFound].state = state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; - } - - } - - } - - // Free list of usb devices - libusb_free_device_list(devs, 1); - - // Write the number of found devices - *out_amountOfFoundDevices = numDevicesFound; - - return X_LINK_PLATFORM_SUCCESS; -} - -extern "C" xLinkPlatformErrorCode_t refLibusbDeviceByName(const char* name, libusb_device** pdev) { - - std::lock_guard l(mutex); - - // Get list of usb devices - static libusb_device **devs = NULL; - auto numDevices = libusb_get_device_list(context, &devs); - if(numDevices < 0) { - mvLog(MVLOG_DEBUG, "Unable to get USB device list: %s", xlink_libusb_strerror(static_cast(numDevices))); - return X_LINK_PLATFORM_ERROR; - } - - // Loop over all usb devices, increase count only if myriad device - bool found = false; - for(ssize_t i = 0; i < numDevices; i++) { - if(devs[i] == nullptr) continue; - - // Check path only - std::string devicePath = getLibusbDevicePath(devs[i]); - // Check if compare with name - std::string requiredName(name); - if(requiredName.length() > 0 && requiredName == devicePath){ - // Found, increase ref and exit the loop - libusb_ref_device(devs[i]); - *pdev = devs[i]; - found = true; - break; - } - } - - // Free list of usb devices (unref each) - libusb_free_device_list(devs, 1); - - if(!found){ - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; - } - - return X_LINK_PLATFORM_SUCCESS; -} - - - -std::string getLibusbDevicePath(libusb_device *dev) { - - std::string devicePath = ""; - - // Add bus number - uint8_t bus = libusb_get_bus_number(dev); - devicePath += std::to_string(bus) + "."; - - // Add all subsequent port numbers - uint8_t portNumbers[MAXIMUM_PORT_NUMBERS]; - int count = libusb_get_port_numbers(dev, portNumbers, MAXIMUM_PORT_NUMBERS); - if (count == LIBUSB_ERROR_OVERFLOW) { - // shouldn't happen! - return ""; - } - if(count == 0){ - // Only bus number is available - return devicePath; - } - - for (int i = 0; i < count - 1; i++){ - devicePath += std::to_string(portNumbers[i]) + "."; - } - devicePath += std::to_string(portNumbers[count - 1]); - - // Return the device path - return devicePath; -} - -libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId) -{ - char mxId[XLINK_MAX_MX_ID_SIZE] = {0}; - - // Default MXID - empty - outMxId = ""; - - // first check if entry already exists in the list (and is still valid) - // if found, it stores it into mx_id variable - bool found = usb_mx_id_cache_get_entry(devicePath.c_str(), mxId); - - if(found){ - mvLog(MVLOG_DEBUG, "Found cached MX ID: %s", mxId); - outMxId = std::string(mxId); - return LIBUSB_SUCCESS; - } else { - // If not found, retrieve mxId - - // get serial from usb descriptor - libusb_device_handle *handle = nullptr; - int libusb_rc = LIBUSB_SUCCESS; - - // Retry getting MX ID for 15ms - const std::chrono::milliseconds RETRY_TIMEOUT{15}; // 15ms - const std::chrono::microseconds SLEEP_BETWEEN_RETRIES{100}; // 100us - - auto t1 = std::chrono::steady_clock::now(); - do { - - // Open device - if not already - if(handle == nullptr){ - libusb_rc = libusb_open(dev, &handle); - if (libusb_rc < 0){ - // Some kind of error, either NO_MEM, ACCESS, NO_DEVICE or other - mvLog(MVLOG_DEBUG, "libusb_open: %s", xlink_libusb_strerror(libusb_rc)); - - // If WIN32, access error and state == BOOTED - #ifdef _WIN32 - if(libusb_rc == LIBUSB_ERROR_ACCESS && state == X_LINK_BOOTED) { - auto winMxId = getWinUsbMxId({pDesc->idVendor, pDesc->idProduct}, dev); - if(winMxId != "") { - strncpy(mxId, winMxId.c_str(), sizeof(mxId) - 1); - libusb_rc = 0; - break; - } - } - #endif - - // retry - std::this_thread::sleep_for(std::chrono::milliseconds(1)); - continue; - } - } - - // if UNBOOTED state, perform mx_id retrieval procedure using small program and a read command - if(state == X_LINK_UNBOOTED){ - - // Get configuration first (From OS cache) - int active_configuration = -1; - if( (libusb_rc = libusb_get_configuration(handle, &active_configuration)) == 0){ - if(active_configuration != 1){ - mvLog(MVLOG_DEBUG, "Setting configuration from %d to 1\n", active_configuration); - if ((libusb_rc = libusb_set_configuration(handle, 1)) < 0) { - mvLog(MVLOG_ERROR, "libusb_set_configuration: %s", xlink_libusb_strerror(libusb_rc)); - - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - } - } else { - // getting config failed... - mvLog(MVLOG_ERROR, "libusb_set_configuration: %s", xlink_libusb_strerror(libusb_rc)); - - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - - - // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) - libusb_set_auto_detach_kernel_driver(handle, 1); - // Claim interface (as we'll be doing IO on endpoints) - if ((libusb_rc = libusb_claim_interface(handle, 0)) < 0) { - if(libusb_rc != LIBUSB_ERROR_BUSY){ - mvLog(MVLOG_ERROR, "libusb_claim_interface: %s", xlink_libusb_strerror(libusb_rc)); - } else { - mvLog(MVLOG_DEBUG, "libusb_claim_interface: %s", xlink_libusb_strerror(libusb_rc)); - } - // retry - most likely device busy by another app - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - - - const int send_ep = 0x01; - int transferred = 0; - - // /////////////////////// - // Start - // WD Protection & MXID Retrieval Command - transferred = 0; - libusb_rc = libusb_bulk_transfer(handle, send_ep, ((uint8_t*) usb_mx_id_get_payload()), usb_mx_id_get_payload_size(), &transferred, MX_ID_TIMEOUT_MS); - if (libusb_rc < 0 || usb_mx_id_get_payload_size() != transferred) { - mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, usb_mx_id_get_payload_size()); - // Mark as error and retry - libusb_rc = -1; - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - - // MXID Read - const int recv_ep = 0x81; - const int expectedMxIdReadSize = 9; - uint8_t rbuf[128]; - transferred = 0; - libusb_rc = libusb_bulk_transfer(handle, recv_ep, rbuf, sizeof(rbuf), &transferred, MX_ID_TIMEOUT_MS); - if (libusb_rc < 0 || expectedMxIdReadSize != transferred) { - mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, expectedMxIdReadSize); - // Mark as error and retry - libusb_rc = -1; - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - - // WD Protection end - transferred = 0; - libusb_rc = libusb_bulk_transfer(handle, send_ep, ((uint8_t*) usb_mx_id_get_payload_end()), usb_mx_id_get_payload_end_size(), &transferred, MX_ID_TIMEOUT_MS); - if (libusb_rc < 0 || usb_mx_id_get_payload_end_size() != transferred) { - mvLog(MVLOG_ERROR, "libusb_bulk_transfer (%s), transfer: %d, expected: %d", xlink_libusb_strerror(libusb_rc), transferred, usb_mx_id_get_payload_end_size()); - // Mark as error and retry - libusb_rc = -1; - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - // End - /////////////////////// - - // Release claimed interface - // ignore error as it doesn't matter - libusb_release_interface(handle, 0); - - // Parse mxId into HEX presentation - // There's a bug, it should be 0x0F, but setting as in MDK - rbuf[8] &= 0xF0; - - // Convert to HEX presentation and store into mx_id - for (int i = 0; i < expectedMxIdReadSize; i++) { - snprintf(mxId + 2*i, 3, "%02X", rbuf[i]); - } - - // Indicate no error - libusb_rc = 0; - - } else { - - if( (libusb_rc = libusb_get_string_descriptor_ascii(handle, pDesc->iSerialNumber, ((uint8_t*) mxId), sizeof(mxId))) < 0){ - mvLog(MVLOG_WARN, "Failed to get string descriptor"); - - // retry - std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); - continue; - } - - // Indicate no error - libusb_rc = 0; - - } - - } while (libusb_rc != 0 && std::chrono::steady_clock::now() - t1 < RETRY_TIMEOUT); - - // Close opened device - if(handle != nullptr){ - libusb_close(handle); - } - - // if mx_id couldn't be retrieved, exit by returning error - if(libusb_rc != 0){ - return (libusb_error) libusb_rc; - } - - // Cache the retrieved mx_id - // Find empty space and store this entry - // If no empty space, don't cache (possible case: >16 devices) - int cache_index = usb_mx_id_cache_store_entry(mxId, devicePath.c_str()); - if(cache_index >= 0){ - // debug print - mvLog(MVLOG_DEBUG, "Cached MX ID %s at index %d", mxId, cache_index); - } else { - // debug print - mvLog(MVLOG_DEBUG, "Couldn't cache MX ID %s", mxId); - } - - } - - outMxId = std::string(mxId); - return libusb_error::LIBUSB_SUCCESS; - -} - -static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* pDesc, libusb_device *dev, GateResponse& outGateResponse, std::string& outSerial) { - GateResponse gateResponse = {0}; - std::string serial = ""; - - // get serial from usb descriptor - libusb_device_handle *handle = nullptr; - int libusb_rc = LIBUSB_SUCCESS; - libusb_rc = libusb_open(dev, &handle); - if (libusb_rc != 0){ - return (libusb_error) libusb_rc; - } - - libusb_rc = libusb_claim_interface(handle, 0); - if (libusb_rc != 0){ - libusb_close(handle); - return (libusb_error) libusb_rc; - } - - USBGateRequest usbGateRequest = { - .RequestNum = 12, - .RequestSize = 0, - }; - - int transferred = 0; - - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); - if (libusb_rc != 0) { - libusb_close(handle); - return (libusb_error) libusb_rc; - } - - USBGateRequest usbGateResponse = { 0 }; - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); - if (libusb_rc != 0) { - libusb_close(handle); - return (libusb_error) libusb_rc; - } - - std::vector respBuffer; - respBuffer.resize(usbGateResponse.RequestSize); - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); - if (libusb_rc != 0) { - libusb_close(handle); - return (libusb_error) libusb_rc; - } - - size_t serialStrLen = usbGateResponse.RequestSize - sizeof(GateResponse); - char *serialStr = (char*)malloc(serialStrLen + 1); - memcpy(serialStr, (const char*)&respBuffer[0], serialStrLen); - serialStr[serialStrLen] = '\0'; - serial = serialStr; - free(serialStr); - outSerial = serial; - - memcpy(&gateResponse, &respBuffer[serialStrLen], sizeof(gateResponse)); - outGateResponse = gateResponse; - - // Close opened device - if(handle != nullptr){ - libusb_close(handle); - } - - return libusb_error::LIBUSB_SUCCESS; -} - -const char* xlink_libusb_strerror(int x) { - return libusb_strerror((libusb_error) x); -} - - -static libusb_error usb_open_device(libusb_device *dev, uint8_t* endpoint, libusb_device_handle*& handle) -{ - struct libusb_config_descriptor *cdesc; - const struct libusb_interface_descriptor *ifdesc; - libusb_device_handle *h = NULL; - int res; - - if((res = libusb_open(dev, &h)) < 0) - { - mvLog(MVLOG_DEBUG, "cannot open device: %s\n", xlink_libusb_strerror(res)); -return (libusb_error) res; - } - - // Get configuration first - int active_configuration = -1; - if((res = libusb_get_configuration(h, &active_configuration)) < 0){ - mvLog(MVLOG_DEBUG, "setting config 1 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } - - // Check if set configuration call is needed - if(active_configuration != 1){ - mvLog(MVLOG_DEBUG, "Setting configuration from %d to 1\n", active_configuration); - if ((res = libusb_set_configuration(h, 1)) < 0) { - mvLog(MVLOG_ERROR, "libusb_set_configuration: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } - } - - // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) - libusb_set_auto_detach_kernel_driver(h, 1); - if((res = libusb_claim_interface(h, 0)) < 0) - { - mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } - - // TODO(TheMutta): Find a way to enable this conditionally - //#if (!defined(USE_USB_VSC)) - // XLink EPs - if((res = libusb_claim_interface(h, 1)) < 0) - { - mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } - - - - if((res = libusb_get_config_descriptor(dev, 0, &cdesc)) < 0) - { - mvLog(MVLOG_DEBUG, "Unable to get USB config descriptor: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } - ifdesc = cdesc->interface->altsetting; - for(int i=0; ibNumEndpoints; i++) - { - mvLog(MVLOG_DEBUG, "Found EP 0x%02x : max packet size is %u bytes", - ifdesc->endpoint[i].bEndpointAddress, ifdesc->endpoint[i].wMaxPacketSize); - if((ifdesc->endpoint[i].bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) != LIBUSB_TRANSFER_TYPE_BULK) - continue; - if( !(ifdesc->endpoint[i].bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) ) - { - *endpoint = ifdesc->endpoint[i].bEndpointAddress; - bulk_chunklen = ifdesc->endpoint[i].wMaxPacketSize; - libusb_free_config_descriptor(cdesc); - handle = h; - return LIBUSB_SUCCESS; - } - } - libusb_free_config_descriptor(cdesc); - libusb_close(h); - return LIBUSB_ERROR_ACCESS; -} - -static int send_file(libusb_device_handle* h, uint8_t endpoint, const void* tx_buf, unsigned filesize,uint16_t bcdusb) -{ - using namespace std::chrono; - - uint8_t *p; - int rc; - int wb, twb, wbr; - int bulk_chunklen = DEFAULT_CHUNKSZ; - twb = 0; - p = const_cast((const uint8_t*)tx_buf); - int send_zlp = ((filesize % 512) == 0); - - if(bcdusb < 0x200) { - bulk_chunklen = USB1_CHUNKSZ; - } - - auto t1 = steady_clock::now(); - mvLog(MVLOG_DEBUG, "Performing bulk write of %u bytes...", filesize); - while(((unsigned)twb < filesize) || send_zlp) - { - wb = filesize - twb; - if(wb > bulk_chunklen) - wb = bulk_chunklen; - wbr = 0; - rc = libusb_bulk_transfer(h, endpoint, p, wb, &wbr, write_timeout); - if((rc || (wb != wbr)) && (wb != 0)) // Don't check the return code for ZLP - { - if(rc == LIBUSB_ERROR_NO_DEVICE) - break; - mvLog(MVLOG_WARN, "bulk write: %s (%d bytes written, %d bytes to write)", xlink_libusb_strerror(rc), wbr, wb); - if(rc == LIBUSB_ERROR_TIMEOUT) - return USB_BOOT_TIMEOUT; - else return USB_BOOT_ERROR; - } - if (steady_clock::now() - t1 > DEFAULT_SEND_FILE_TIMEOUT) { - return USB_BOOT_TIMEOUT; - } - if(wb == 0) // ZLP just sent, last packet - break; - twb += wbr; - p += wbr; - } - -#ifndef NDEBUG - double MBpS = ((double)filesize / 1048576.) / (duration_cast>(steady_clock::now() - t1)).count(); - mvLog(MVLOG_DEBUG, "Successfully sent %u bytes of data in %lf ms (%lf MB/s)", filesize, duration_cast(steady_clock::now() - t1).count(), MBpS); -#endif - - return 0; -} - -int usb_boot(const char *addr, const void *mvcmd, unsigned size) -{ - using namespace std::chrono; - - int rc = 0; - uint8_t endpoint; - - libusb_device *dev = nullptr; - libusb_device_handle *h; - uint16_t bcdusb=-1; - libusb_error res = LIBUSB_ERROR_ACCESS; - - - auto t1 = steady_clock::now(); - do { - if(refLibusbDeviceByName(addr, &dev) == X_LINK_PLATFORM_SUCCESS){ - break; - } - std::this_thread::sleep_for(milliseconds(10)); - } while(steady_clock::now() - t1 < DEFAULT_CONNECT_TIMEOUT); - - if(dev == nullptr) { - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; - } - - auto t2 = steady_clock::now(); - do { - if((res = usb_open_device(dev, &endpoint, h)) == LIBUSB_SUCCESS){ - break; - } - std::this_thread::sleep_for(milliseconds(100)); - } while(steady_clock::now() - t2 < DEFAULT_CONNECT_TIMEOUT); - - if(res == LIBUSB_SUCCESS) { - rc = send_file(h, endpoint, mvcmd, size, bcdusb); - libusb_release_interface(h, 0); - libusb_close(h); - } else { - if(res == LIBUSB_ERROR_ACCESS) { - rc = X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; - } else if(res == LIBUSB_ERROR_BUSY) { - rc = X_LINK_PLATFORM_DEVICE_BUSY; - } else { - rc = X_LINK_PLATFORM_ERROR; - } - } - - if (dev) { - libusb_unref_device(dev); - } - - return rc; -} - - - -xLinkPlatformErrorCode_t usbLinkOpen(const char *path, libusb_device_handle*& h) -{ - using namespace std::chrono; - if (path == NULL) { - return X_LINK_PLATFORM_INVALID_PARAMETERS; - } - - usbBootError_t rc = USB_BOOT_DEVICE_NOT_FOUND; - h = nullptr; - libusb_device *dev = nullptr; - bool found = false; - - auto t1 = steady_clock::now(); - do { - if(refLibusbDeviceByName(path, &dev) == X_LINK_PLATFORM_SUCCESS){ - found = true; - break; - } - } while(steady_clock::now() - t1 < DEFAULT_OPEN_TIMEOUT); - - if(!found) { - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; - } - - uint8_t ep = 0; - libusb_error libusb_rc = usb_open_device(dev, &ep, h); - if(libusb_rc == LIBUSB_SUCCESS) { - return X_LINK_PLATFORM_SUCCESS; - } else if(libusb_rc == LIBUSB_ERROR_ACCESS) { - return X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; - } else if(libusb_rc == LIBUSB_ERROR_BUSY) { - return X_LINK_PLATFORM_DEVICE_BUSY; - } else { - return X_LINK_PLATFORM_ERROR; - } -} - - -xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { - - libusb_device *dev = nullptr; - auto refErr = refLibusbDeviceByName(path, &dev); - if(refErr != X_LINK_PLATFORM_SUCCESS) { - return refErr; - } - if(dev == NULL){ - return X_LINK_PLATFORM_ERROR; - } - libusb_device_handle *h = NULL; - - - int libusb_rc = libusb_open(dev, &h); - if (libusb_rc < 0) { - libusb_unref_device(dev); - if(libusb_rc == LIBUSB_ERROR_ACCESS) { - return X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; - } - return X_LINK_PLATFORM_ERROR; - } - - // Make control transfer - libusb_rc = libusb_control_transfer(h, - bootBootloaderPacket.requestType, // bmRequestType: device-directed - bootBootloaderPacket.request, // bRequest: custom - bootBootloaderPacket.value, // wValue: custom - bootBootloaderPacket.index, // wIndex - NULL, // data pointer - 0, // data size - 1000 // timeout [ms] - ); - - // Ignore error and close device - libusb_unref_device(dev); - libusb_close(h); - - if(libusb_rc < 0) { - return X_LINK_PLATFORM_ERROR; - } - - return X_LINK_PLATFORM_SUCCESS; -} - -void usbLinkClose(libusb_device_handle *f) -{ - libusb_release_interface(f, 0); - libusb_close(f); -} - - - -int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) -{ -#if (!defined(USE_USB_VSC)) - #ifdef USE_LINK_JTAG - struct sockaddr_in serv_addr; - usbFdWrite = socket(AF_INET, SOCK_STREAM, 0); - usbFdRead = socket(AF_INET, SOCK_STREAM, 0); - assert(usbFdWrite >=0); - assert(usbFdRead >=0); - memset(&serv_addr, '0', sizeof(serv_addr)); - - serv_addr.sin_family = AF_INET; - serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); - serv_addr.sin_port = htons(USB_LINK_SOCKET_PORT); - - if (connect(usbFdWrite, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) - { - mvLog(MVLOG_ERROR, "connect(usbFdWrite,...) returned < 0\n"); - if (usbFdRead >= 0) - close(usbFdRead); - if (usbFdWrite >= 0) - close(usbFdWrite); - usbFdRead = -1; - usbFdWrite = -1; - return X_LINK_PLATFORM_ERROR; - } - return 0; - - #else - - if (strcmp(devPathRead, "/dev/usb-ffs/xlink") { - int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); - int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); - - if(outfd < 0 || infd < 0) { - return X_LINK_PLATFORM_ERROR; - } - - usbFdRead = infd; - usbFdWrite = outfd; - } else { - usbFdRead= open(devPathRead, O_RDWR); - if(usbFdRead < 0) - { - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; - } - - - // set tty to raw mode - struct termios tty; - speed_t spd; - int rc; - rc = tcgetattr(usbFdRead, &tty); - if (rc < 0) { - close(usbFdRead); - usbFdRead = -1; - return X_LINK_PLATFORM_ERROR; - } - - spd = B115200; - cfsetospeed(&tty, (speed_t)spd); - cfsetispeed(&tty, (speed_t)spd); - - cfmakeraw(&tty); - - rc = tcsetattr(usbFdRead, TCSANOW, &tty); - if (rc < 0) { - close(usbFdRead); - usbFdRead = -1; - return X_LINK_PLATFORM_ERROR; - } - - usbFdWrite= open(devPathWrite, O_RDWR); - if(usbFdWrite < 0) - { - close(usbFdRead); - usbFdWrite = -1; - return X_LINK_PLATFORM_ERROR; - } - // set tty to raw mode - rc = tcgetattr(usbFdWrite, &tty); - if (rc < 0) { - close(usbFdRead); - close(usbFdWrite); - usbFdWrite = -1; - return X_LINK_PLATFORM_ERROR; - } - - spd = B115200; - cfsetospeed(&tty, (speed_t)spd); - cfsetispeed(&tty, (speed_t)spd); - - cfmakeraw(&tty); - - rc = tcsetattr(usbFdWrite, TCSANOW, &tty); - if (rc < 0) { - close(usbFdRead); - close(usbFdWrite); - usbFdWrite = -1; - return X_LINK_PLATFORM_ERROR; - } - return 0; - } - #endif /*USE_LINK_JTAG*/ -#else - - libusb_device_handle* usbHandle = nullptr; - xLinkPlatformErrorCode_t ret = usbLinkOpen(devPathWrite, usbHandle); - - if (ret != X_LINK_PLATFORM_SUCCESS) - { - /* could fail due to port name change */ - return ret; - } - - // Store the usb handle and create a "unique" key instead - // (as file descriptors are reused and can cause a clash with lookups between scheduler and link) - *fd = createPlatformDeviceFdKey(usbHandle); - -#endif /*USE_USB_VSC*/ - - return 0; -} - - -int usbPlatformClose(void *fdKey) -{ - -#ifndef USE_USB_VSC - #ifdef USE_LINK_JTAG - /*Nothing*/ -#else - if (usbFdRead != -1){ - close(usbFdRead); - usbFdRead = -1; - } - if (usbFdWrite != -1){ - close(usbFdWrite); - usbFdWrite = -1; - } -#endif /*USE_LINK_JTAG*/ -#else - - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find USB Handle by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - usbLinkClose((libusb_device_handle *) tmpUsbHandle); - - if(destroyPlatformDeviceFdKey(fdKey)){ - mvLog(MVLOG_FATAL, "Cannot destroy USB Handle key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - -#endif /*USE_USB_VSC*/ - return -1; -} - - - -int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length){ - - // Boot it - int rc = usb_boot(deviceDesc->name, firmware, (unsigned)length); - - if(!rc) { - mvLog(MVLOG_DEBUG, "Boot successful, device address %s", deviceDesc->name); - } - return rc; -} - - - -int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) -{ - const int chunk_size = DEFAULT_CHUNKSZ; - while(size > 0) - { - int bt, ss = (int)size; - if(ss > chunk_size) - ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_IN + offset,(unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); - if(rc) - return rc; - data = ((char *)data) + bt; - size -= bt; - } - return 0; -} - -int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t offset) -{ - const int chunk_size = DEFAULT_CHUNKSZ; - while(size > 0) - { - int bt, ss = (int)size; - if(ss > chunk_size) - ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_OUT + offset, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); - if(rc) - return rc; - data = (char *)data + bt; - size -= bt; - } - return 0; -} - -int usbPlatformRead(void* fdKey, void* data, int size) -{ - int rc = 0; -#ifndef USE_USB_VSC - int nread = 0; -#ifdef USE_LINK_JTAG - while (nread < size){ - nread += read(usbFdWrite, &((char*)data)[nread], size - nread); - printf("read %d %d\n", nread, size); - } -#else - if(usbFdRead < 0) - { - return -1; - } - - while(nread < size) - { - int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ - ? PACKET_LENGTH : size - nread; - - while(toRead > 0) - { - rc = read(usbFdRead, &((char*)data)[nread], toRead); - if ( rc < 0) - { - return -2; - } - toRead -=rc; - nread += rc; - } - unsigned char acknowledge = 0xEF; - int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); - if (wc != sizeof(acknowledge)) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ -#else - - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_read(usbHandle, data, size, 1); -#endif /*USE_USB_VSC*/ - return rc; -} - -int usbPlatformWrite(void *fdKey, void *data, int size) -{ - int rc = 0; -#ifndef USE_USB_VSC - int byteCount = 0; -#ifdef USE_LINK_JTAG - while (byteCount < size){ - byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); - printf("write %d %d\n", byteCount, size); - } -#else - if(usbFdWrite < 0) - { - return -1; - } - while(byteCount < size) - { - int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ - ? PACKET_LENGTH:size - byteCount; - int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); - - if ( wc != toWrite) - { - return -2; - } - - byteCount += toWrite; - unsigned char acknowledge; - int rc; - rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); - - if ( rc < 0) - { - return -2; - } - - if (acknowledge != 0xEF) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ -#else - - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_write(usbHandle, data, size, 1); -#endif /*USE_USB_VSC*/ - return rc; -} - -int usbPlatformGateRead(void* fdKey, void* data, int size) -{ - int rc = 0; -#ifndef USE_USB_VSC - int nread = 0; -#ifdef USE_LINK_JTAG - while (nread < size){ - nread += read(usbFdWrite, &((char*)data)[nread], size - nread); - printf("read %d %d\n", nread, size); - } -#else - if(usbFdRead < 0) - { - return -1; - } - - while(nread < size) - { - int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ - ? PACKET_LENGTH : size - nread; - - while(toRead > 0) - { - rc = read(usbFdRead, &((char*)data)[nread], toRead); - if ( rc < 0) - { - return -2; - } - toRead -=rc; - nread += rc; - } - unsigned char acknowledge = 0xEF; - int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); - if (wc != sizeof(acknowledge)) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ -#else - - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_read(usbHandle, data, size, 0); -#endif /*USE_USB_VSC*/ - return rc; -} - -int usbPlatformGateWrite(void *fdKey, void *data, int size) -{ - int rc = 0; -#ifndef USE_USB_VSC - int byteCount = 0; -#ifdef USE_LINK_JTAG - while (byteCount < size){ - byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); - printf("write %d %d\n", byteCount, size); - } -#else - if(usbFdWrite < 0) - { - return -1; - } - while(byteCount < size) - { - int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ - ? PACKET_LENGTH:size - byteCount; - int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); - - if ( wc != toWrite) - { - return -2; - } - - byteCount += toWrite; - unsigned char acknowledge; - int rc; - rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); - - if ( rc < 0) - { - return -2; - } - - if (acknowledge != 0xEF) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ -#else - - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_write(usbHandle, data, size, 0); -#endif /*USE_USB_VSC*/ - return rc; -} - - -#ifdef _WIN32 -#include -#include -#pragma comment(lib, "setupapi.lib") -#include -#include - -// get MxId given the vidpid and libusb device (Windows only) -// Uses the Win32 SetupDI* apis. Several cautions: -// - Movidius MyriadX usb devices often change their usb path when they load their bootloader/firmware -// - Since USB is dynamic, it is technically possible for a device to change its path at any time -std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { - if (dev == NULL) return {}; - - // init device info vars - HDEVINFO hDevInfoSet; - SP_DEVINFO_DATA devInfoData{}; - devInfoData.cbSize = sizeof(SP_DEVINFO_DATA); - - // get USB host controllers; each has exactly one root hub - hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (hDevInfoSet == INVALID_HANDLE_VALUE) { - return {}; - } - - // iterate over usb host controllers and populate list with their location path for later matching to device paths - std::vector hostControllerLocationPaths; - for(int i = 0; SetupDiEnumDeviceInfo(hDevInfoSet, i, &devInfoData); i++) { - // get location paths as a REG_MULTI_SZ - std::string locationPaths(1023, 0); - if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, NULL, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), NULL)) { - continue; - } - - // find PCI path in the multi string and emplace to back of vector - const auto pciPosition = locationPaths.find("PCIROOT"); - if (pciPosition == std::string::npos) { - continue; - } - hostControllerLocationPaths.emplace_back(locationPaths.substr(pciPosition, strnlen_s(locationPaths.c_str() + pciPosition, locationPaths.size() - pciPosition))); - } - - // Free dev info, return if no usb host controllers found - SetupDiDestroyDeviceInfoList(hDevInfoSet); - if (hostControllerLocationPaths.empty()) { - return {}; - } - - // get USB devices - hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (hDevInfoSet == INVALID_HANDLE_VALUE) { - return {}; - } - - // iterate over usb devices and populate with device info - std::string goalPath{getLibusbDevicePath(dev)}; - std::string deviceId; - for(int i = 0; SetupDiEnumDeviceInfo(hDevInfoSet, i, &devInfoData); i++) { - // get device instance id - char instanceId[128] {}; - if(!SetupDiGetDeviceInstanceIdA(hDevInfoSet, &devInfoData, (PSTR)instanceId, sizeof(instanceId), NULL)) { - continue; - } - - // get device vid, pid, and serial id - char serialId[128] {}; - uint16_t vid = 0, pid = 0; - if(sscanf(instanceId, "USB\\VID_%hx&PID_%hx\\%s", &vid, &pid, serialId) != 3) { - continue; - } - - // check if this is the device we are looking for - if(vidpid.first != vid || vidpid.second != pid) { - continue; - } - - // get location paths as a REG_MULTI_SZ - std::string locationPaths(1023, 0); - if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, NULL, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), NULL)) { - continue; - } - - // find PCI path in the multi string and isolate that path - const auto pciPosition = locationPaths.find("PCIROOT"); - if (pciPosition == std::string::npos) { - continue; - } - const auto usbPath = locationPaths.substr(pciPosition, strnlen_s(locationPaths.c_str() + pciPosition, locationPaths.size() - pciPosition)); - - // find matching host controller - const auto hostController = std::find_if(hostControllerLocationPaths.begin(), hostControllerLocationPaths.end(), [&usbPath](const std::string& candidateController) noexcept { - // check if the usb path starts with the candidate controller path - return usbPath.find(candidateController) == 0; - }); - if (hostController == hostControllerLocationPaths.end()) { - mvLog(MVLOG_WARN, "Found device with matching vid/pid but no matching USBROOT hub"); - continue; - } - - // initialize pseudo libusb path using the host controller index +1 as the "libusb bus number" - std::string pseudoLibUsbPath = std::to_string(std::distance(hostControllerLocationPaths.begin(), hostController) + 1); - - // there is only one root hub per host controller, it is always on port 0, - // therefore start the search past this known root hub in the usb path - static constexpr auto usbRootLength = sizeof("#USBROOT(0)") - 1; - auto searchPosition{usbPath.c_str() + hostController->size() + usbRootLength}; - - // parse and transform the Windows USB path to the pseudo libusb path - int charsRead = 0; - int port = 0; - while (sscanf(searchPosition, "#USB(%4d)%n", &port, &charsRead) == 1) { - searchPosition += charsRead; - pseudoLibUsbPath += '.' + std::to_string(port); - } - - if(pseudoLibUsbPath == goalPath) { - deviceId = serialId; - break; - } - } - - // Free dev info - SetupDiDestroyDeviceInfoList(hDevInfoSet); - - // Return deviceId if found - return deviceId; -} -#endif From 2ca735d9fa565335c696b3ad2709b4516c80cdec Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 16:41:36 +0200 Subject: [PATCH 49/78] usb_ep: Removed references to usb_host_ep --- src/pc/PlatformData.c | 1 - src/pc/PlatformDeviceControl.c | 1 - src/pc/PlatformDeviceSearch.c | 33 +-------------------------------- 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 0f6b0758..f45232f0 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -15,7 +15,6 @@ #include "pcie_host.h" #include "tcpip_host.h" #include "local_memshd.h" -#include "usb_host_ep.h" #include "PlatformDeviceFd.h" #include "inttypes.h" diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 464d6932..37b17bcc 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -12,7 +12,6 @@ #include "tcpip_host.h" #include "local_memshd.h" #include "tcpip_memshd.h" -#include "usb_host_ep.h" #include "XLinkStringUtils.h" #include "PlatformDeviceFd.h" diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 4cd61f77..076f6e26 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -10,7 +10,6 @@ #include "pcie_host.h" #include "tcpip_host.h" #include "local_memshd.h" -#include "usb_host_ep.h" #include "XLinkStringUtils.h" @@ -42,10 +41,6 @@ static xLinkPlatformErrorCode_t getLocalShdmemDevices(const deviceDesc_t in_devi unsigned int *out_amountOfFoundDevices); #endif -static xLinkPlatformErrorCode_t getUSBEPDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices); - // ------------------------------------ // Helpers declaration. End. // ------------------------------------ @@ -70,6 +65,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe switch (in_deviceRequirements.protocol){ case X_LINK_USB_CDC: case X_LINK_USB_VSC: + case X_LINK_USB_EP: if(!XLinkIsProtocolInitialized(in_deviceRequirements.protocol)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+in_deviceRequirements.protocol; } @@ -92,12 +88,6 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } return getLocalShdmemDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); #endif - case X_LINK_USB_EP: - if(!XLinkIsProtocolInitialized(in_deviceRequirements.protocol)) { - return X_LINK_PLATFORM_DRIVER_NOT_LOADED+in_deviceRequirements.protocol; - } - return getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); - case X_LINK_ANY_PROTOCOL: if(XLinkIsProtocolInitialized(X_LINK_USB_EP)) { numFoundDevices = 0; @@ -204,7 +194,6 @@ char* XLinkPlatformErrorToStr(const xLinkPlatformErrorCode_t errorCode) { case X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_TCP_IP_OR_LOCAL_SHDMEM_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_LOCAL_SHDMEM_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_PCIE_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_PCIE_DRIVER_NOT_LOADED"; - case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: return "X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED"; case X_LINK_PLATFORM_INVALID_PARAMETERS: return "X_LINK_PLATFORM_INVALID_PARAMETERS"; default: return ""; } @@ -375,26 +364,6 @@ xLinkPlatformErrorCode_t getLocalShdmemDevices(const deviceDesc_t in_deviceRequi } #endif -xLinkPlatformErrorCode_t getUSBEPDevices(const deviceDesc_t in_deviceRequirements, - deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices) -{ - ASSERT_XLINK_PLATFORM(out_foundDevices); - ASSERT_XLINK_PLATFORM(out_amountOfFoundDevices); - if (in_deviceRequirements.platform == X_LINK_MYRIAD_2) { - return X_LINK_PLATFORM_ERROR; - } - - if(in_deviceRequirements.state == X_LINK_UNBOOTED) { - /** - * There is no condition where unbooted - * state device to be found using usb. - */ - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; - } - - return usbepGetDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); -} // ------------------------------------ // Helpers implementation. End. // ------------------------------------ From 87e1e75775846f9f158547d6a1a4f2a8c8cd8602 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 16:42:27 +0200 Subject: [PATCH 50/78] usb_ep: Removed references to usb_host_ep v2 --- src/pc/PlatformDeviceSearch.c | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 076f6e26..3b9b594a 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -58,7 +58,6 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe xLinkPlatformErrorCode_t PCIe_rc; xLinkPlatformErrorCode_t TCPIP_rc; xLinkPlatformErrorCode_t SHDMEM_rc; - xLinkPlatformErrorCode_t USBEP_rc; unsigned numFoundDevices = 0; *out_amountOfFoundDevices = 0; @@ -89,19 +88,6 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe return getLocalShdmemDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); #endif case X_LINK_ANY_PROTOCOL: - if(XLinkIsProtocolInitialized(X_LINK_USB_EP)) { - numFoundDevices = 0; - USBEP_rc = getUSBEPDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, &numFoundDevices); - *out_amountOfFoundDevices += numFoundDevices; - out_foundDevices += numFoundDevices; - // Found enough devices, return - if (numFoundDevices >= sizeFoundDevices) { - return X_LINK_PLATFORM_SUCCESS; - } else { - sizeFoundDevices -= numFoundDevices; - } - } - // If USB protocol is initialized if(XLinkIsProtocolInitialized(X_LINK_USB_VSC)) { // Find first correct USB Device From bc8afd1f2f68aca02ef7ab41457288a0db32c0be Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 17:30:16 +0200 Subject: [PATCH 51/78] usb_host: Fixed bug in discovery for USB EPs --- cmake/XLinkDependencies.cmake | 3 ++- src/pc/protocols/usb_host.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/cmake/XLinkDependencies.cmake b/cmake/XLinkDependencies.cmake index d256b1c3..bc6377e9 100644 --- a/cmake/XLinkDependencies.cmake +++ b/cmake/XLinkDependencies.cmake @@ -12,7 +12,8 @@ if(NOT CONFIG_MODE OR (CONFIG_MODE AND NOT XLINK_INSTALL_PUBLIC_ONLY AND XLINK_E if(XLINK_LIBUSB_LOCAL) add_subdirectory("${XLINK_LIBUSB_LOCAL}" "${CMAKE_CURRENT_BINARY_DIR}/libusb" EXCLUDE_FROM_ALL) elseif(NOT XLINK_LIBUSB_SYSTEM) - find_package(usb-1.0 ${_QUIET} CONFIG REQUIRED HINTS "${CMAKE_CURRENT_LIST_DIR}/libusb") + find_package(PkgConfig REQUIRED) + pkg_check_modules(LIBUSB REQUIRED libusb-1.0) endif() endif() diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 4f64e36a..d680fd96 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -188,7 +188,7 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } // Check for RVC3 and RVC4 first - if(in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + if(state == X_LINK_GATE || in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ GateResponse gateResponse; std::string serial; getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, serial); From 4daffea226daceb81e428a882e6d9f2cf893d48b Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 17:40:04 +0200 Subject: [PATCH 52/78] usb_ep: Fixed platform assignment from gate. --- src/pc/protocols/usb_host.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index d680fd96..94dee081 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -195,7 +195,11 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = (XLinkPlatform_t)gateResponse.platform; + if (gateResponse.platform == 4) { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + } else { + out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; + } out_foundDevices[numDevicesFound].protocol = (XLinkProtocol_t)gateResponse.protocol; out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); From 871076fd48a508c7d1f15ab50c46c45cbd76d64d Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 17:48:53 +0200 Subject: [PATCH 53/78] usb_ep: removed unused function is PlatformGate functions. --- src/pc/protocols/usb_host.cpp | 75 +---------------------------------- 1 file changed, 2 insertions(+), 73 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 94dee081..30b3e337 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1206,41 +1206,7 @@ int usbPlatformGateRead(void* fdKey, void* data, int size) { int rc = 0; #ifndef USE_USB_VSC - int nread = 0; -#ifdef USE_LINK_JTAG - while (nread < size){ - nread += read(usbFdWrite, &((char*)data)[nread], size - nread); - printf("read %d %d\n", nread, size); - } -#else - if(usbFdRead < 0) - { - return -1; - } - - while(nread < size) - { - int toRead = (PACKET_LENGTH && (size - nread > PACKET_LENGTH)) \ - ? PACKET_LENGTH : size - nread; - - while(toRead > 0) - { - rc = read(usbFdRead, &((char*)data)[nread], toRead); - if ( rc < 0) - { - return -2; - } - toRead -=rc; - nread += rc; - } - unsigned char acknowledge = 0xEF; - int wc = write(usbFdRead, &acknowledge, sizeof(acknowledge)); - if (wc != sizeof(acknowledge)) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ + return -1; #else void* tmpUsbHandle = NULL; @@ -1259,44 +1225,7 @@ int usbPlatformGateWrite(void *fdKey, void *data, int size) { int rc = 0; #ifndef USE_USB_VSC - int byteCount = 0; -#ifdef USE_LINK_JTAG - while (byteCount < size){ - byteCount += write(usbFdWrite, &((char*)data)[byteCount], size - byteCount); - printf("write %d %d\n", byteCount, size); - } -#else - if(usbFdWrite < 0) - { - return -1; - } - while(byteCount < size) - { - int toWrite = (PACKET_LENGTH && (size - byteCount > PACKET_LENGTH)) \ - ? PACKET_LENGTH:size - byteCount; - int wc = write(usbFdWrite, ((char*)data) + byteCount, toWrite); - - if ( wc != toWrite) - { - return -2; - } - - byteCount += toWrite; - unsigned char acknowledge; - int rc; - rc = read(usbFdWrite, &acknowledge, sizeof(acknowledge)); - - if ( rc < 0) - { - return -2; - } - - if (acknowledge != 0xEF) - { - return -2; - } - } -#endif /*USE_LINK_JTAG*/ + return -1; #else void* tmpUsbHandle = NULL; From 4dc7073ed878c466cbed40838fd9155879230173 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 18:03:20 +0200 Subject: [PATCH 54/78] usb_host: Reinstated old Gate functions. --- src/pc/PlatformData.c | 4 +- src/pc/protocols/usb_host.cpp | 86 +++++++++++++++++++++++++---------- src/pc/protocols/usb_host.h | 8 ++-- 3 files changed, 69 insertions(+), 29 deletions(-) diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 12403ede..8b384827 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -165,7 +165,7 @@ int XLinkPlatformGateWrite(void *data, int size, int timeout) return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbPlatformGateWrite(NULL, data, size); + return usbPlatformGateWrite(data, size, timeout); } int XLinkPlatformGateRead(void *data, int size, int timeout) @@ -174,7 +174,7 @@ int XLinkPlatformGateRead(void *data, int size, int timeout) return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbPlatformGateRead(NULL, data, size); + return usbPlatformGateRead(data, size, timeout); } diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 30b3e337..3025b608 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1141,6 +1141,7 @@ int usbPlatformRead(void* fdKey, void* data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + // TODO(TheMutta): Fix for VSC rc = usb_read(usbHandle, data, size, 1); #endif /*USE_USB_VSC*/ return rc; @@ -1197,50 +1198,89 @@ int usbPlatformWrite(void *fdKey, void *data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + // TODO(TheMutta) fix for VSC rc = usb_write(usbHandle, data, size, 1); #endif /*USE_USB_VSC*/ return rc; } -int usbPlatformGateRead(void* fdKey, void* data, int size) +int usbEpPlatformGateRead(void *data, int size, int timeout) { + std::lock_guard l(mutex); + int rc = 0; -#ifndef USE_USB_VSC - return -1; -#else - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; + /* Get our device */ + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(context, 0x05C6, 0x4321); + if (gate_dev_handle == NULL) { + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + + return rc; + } + + libusb_device* dev = libusb_get_device(gate_dev_handle); + + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(gate_dev_handle, 0); + if (rc != LIBUSB_SUCCESS) { + return rc; + } + + rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_IN, (unsigned char*)data, size, &rc, timeout); + + libusb_close(gate_dev_handle); - rc = usb_read(usbHandle, data, size, 0); -#endif /*USE_USB_VSC*/ return rc; } -int usbPlatformGateWrite(void *fdKey, void *data, int size) +int usbEpPlatformGateWrite(void *data, int size, int timeout) { + std::lock_guard l(mutex); + int rc = 0; -#ifndef USE_USB_VSC - return -1; -#else - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; + /* Get our device */ + libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(context, 0x05C6, 0x4321); + if (gate_dev_handle == NULL) { + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + /* Not strictly necessary, but it is better to use it, + * as we're using kernel modules together with our interfaces + */ + rc = libusb_set_auto_detach_kernel_driver(gate_dev_handle, 1); + if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + + return rc; + } + + libusb_device* dev = libusb_get_device(gate_dev_handle); + + /* Now we claim our ffs interfaces */ + rc = libusb_claim_interface(gate_dev_handle, 0); + if (rc != LIBUSB_SUCCESS) { + + return rc; + } + + rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_OUT, (unsigned char*)data, size, &rc, timeout); + + libusb_close(gate_dev_handle); - rc = usb_write(usbHandle, data, size, 0); -#endif /*USE_USB_VSC*/ return rc; } - #ifdef _WIN32 #include #include diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 589554a4..5c4b09b6 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -55,8 +55,8 @@ int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware int usbPlatformRead(void *fd, void *data, int size); int usbPlatformWrite(void *fd, void *data, int size); -int usbPlatformGateRead(void *fd, void *data, int size); -int usbPlatformGateWrite(void *fd, void *data, int size); +int usbPlatformGateRead(void *data, int size, int timeout); +int usbPlatformGateWrite(void *data, int size, int timeout); #else @@ -72,8 +72,8 @@ static inline int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const static inline int usbPlatformRead(void *fd, void *data, int size) { return -1; } static inline int usbPlatformWrite(void *fd, void *data, int size) { return -1; } -static inline int usbPlatformGateRead(void *fd, void *data, int size) { return -1; } -static inline int usbPlatformGateWrite(void *fd, void *data, int size) { return -1; } +static inline int usbPlatformGateRead(void *data, int size, int timeout) { return -1; } +static inline int usbPlatformGateWrite(void *data, int size, int timeout) { return -1; } static inline xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, From 52f2419ae8382e166429b7c422bec3e39e81f707 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 18:05:26 +0200 Subject: [PATCH 55/78] usb_host: Fixed naming in EP functions --- src/pc/protocols/usb_host.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 3025b608..b7c67879 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1204,7 +1204,7 @@ int usbPlatformWrite(void *fdKey, void *data, int size) return rc; } -int usbEpPlatformGateRead(void *data, int size, int timeout) +int usbPlatformGateRead(void *data, int size, int timeout) { std::lock_guard l(mutex); @@ -1242,7 +1242,7 @@ int usbEpPlatformGateRead(void *data, int size, int timeout) return rc; } -int usbEpPlatformGateWrite(void *data, int size, int timeout) +int usbPlatformGateWrite(void *data, int size, int timeout) { std::lock_guard l(mutex); From f4dc80ba842ff1ac9199af47cfea294c59152646 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 1 Oct 2025 18:36:19 +0200 Subject: [PATCH 56/78] usb_host: Added lock in usb link opening. --- src/pc/protocols/usb_host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index b7c67879..9abfc8d4 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -814,6 +814,7 @@ xLinkPlatformErrorCode_t usbLinkOpen(XLinkProtocol_t protocol, const char *path, } uint8_t ep = 0; + std::lock_guard l(mutex); libusb_error libusb_rc = usb_open_device(protocol, dev, &ep, h); if(libusb_rc == LIBUSB_SUCCESS) { return X_LINK_PLATFORM_SUCCESS; @@ -990,7 +991,6 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const return 0; #endif /*USE_LINK_JTAG*/ #else - libusb_device_handle* usbHandle = nullptr; xLinkPlatformErrorCode_t ret = usbLinkOpen(protocol, devPathWrite, usbHandle); From a24a3e37bb120a2e29c43834321d3f40f9ea1c15 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Thu, 2 Oct 2025 09:08:20 +0200 Subject: [PATCH 57/78] usb_host: Added mutext usb checks. --- src/pc/protocols/usb_host.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 9abfc8d4..93f8658e 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -903,6 +903,7 @@ int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void ** int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd) { + std::lock_guard l(mutex); #if (!defined(USE_USB_VSC)) #ifdef USE_LINK_JTAG struct sockaddr_in serv_addr; @@ -1012,6 +1013,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const int usbPlatformClose(void *fdKey) { + std::lock_guard l(mutex); #ifndef USE_USB_VSC #ifdef USE_LINK_JTAG @@ -1095,6 +1097,8 @@ int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t off int usbPlatformRead(void* fdKey, void* data, int size) { + std::lock_guard l(mutex); + int rc = 0; #ifndef USE_USB_VSC int nread = 0; @@ -1149,6 +1153,8 @@ int usbPlatformRead(void* fdKey, void* data, int size) int usbPlatformWrite(void *fdKey, void *data, int size) { + std::lock_guard l(mutex); + int rc = 0; #ifndef USE_USB_VSC int byteCount = 0; @@ -1208,6 +1214,8 @@ int usbPlatformGateRead(void *data, int size, int timeout) { std::lock_guard l(mutex); + if (context == nullptr) return -1; + int rc = 0; /* Get our device */ @@ -1245,6 +1253,8 @@ int usbPlatformGateRead(void *data, int size, int timeout) int usbPlatformGateWrite(void *data, int size, int timeout) { std::lock_guard l(mutex); + + if (context == nullptr) return -1; int rc = 0; From 2814d66698752e4c7ccba5a16cbfb9a2f176033a Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 11:33:31 +0200 Subject: [PATCH 58/78] usb_host: Removed excess mutex locks. --- src/pc/protocols/usb_host.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 93f8658e..b0e3f00d 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -263,9 +263,6 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } extern "C" xLinkPlatformErrorCode_t refLibusbDeviceByName(const char* name, libusb_device** pdev) { - - std::lock_guard l(mutex); - // Get list of usb devices static libusb_device **devs = NULL; auto numDevices = libusb_get_device_list(context, &devs); @@ -814,7 +811,6 @@ xLinkPlatformErrorCode_t usbLinkOpen(XLinkProtocol_t protocol, const char *path, } uint8_t ep = 0; - std::lock_guard l(mutex); libusb_error libusb_rc = usb_open_device(protocol, dev, &ep, h); if(libusb_rc == LIBUSB_SUCCESS) { return X_LINK_PLATFORM_SUCCESS; From 7cf6eb7ee349faeca0b4daa184bcf620f565bc67 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 12:07:24 +0200 Subject: [PATCH 59/78] usb_host: Added correct handling in Read/Write for VSC/EP --- src/pc/PlatformData.c | 4 ++-- src/pc/protocols/usb_host.cpp | 18 ++++++++++++------ src/pc/protocols/usb_host.h | 8 ++++---- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 8b384827..40d17327 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -90,7 +90,7 @@ int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) case X_LINK_USB_VSC: case X_LINK_USB_CDC: case X_LINK_USB_EP: - return usbPlatformWrite(deviceHandle->xLinkFD, data, size); + return usbPlatformWrite(deviceHandle->protocol, deviceHandle->xLinkFD, data, size); case X_LINK_PCIE: return pciePlatformWrite(deviceHandle->xLinkFD, data, size); @@ -139,7 +139,7 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l case X_LINK_USB_VSC: case X_LINK_USB_CDC: case X_LINK_USB_EP: - return usbPlatformRead(deviceHandle->xLinkFD, data, size); + return usbPlatformRead(deviceHandle->protocol, deviceHandle->xLinkFD, data, size); case X_LINK_PCIE: return pciePlatformRead(deviceHandle->xLinkFD, data, size); diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index b0e3f00d..8de47642 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1091,7 +1091,7 @@ int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t off return 0; } -int usbPlatformRead(void* fdKey, void* data, int size) +int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) { std::lock_guard l(mutex); @@ -1141,13 +1141,16 @@ int usbPlatformRead(void* fdKey, void* data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - // TODO(TheMutta): Fix for VSC - rc = usb_read(usbHandle, data, size, 1); + if (protocol == X_LINK_USB_EP) { + rc = usb_read(usbHandle, data, size, 1); + } else { + rc = usb_read(usbHandle, data, size, 0); + } #endif /*USE_USB_VSC*/ return rc; } -int usbPlatformWrite(void *fdKey, void *data, int size) +int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size) { std::lock_guard l(mutex); @@ -1200,8 +1203,11 @@ int usbPlatformWrite(void *fdKey, void *data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - // TODO(TheMutta) fix for VSC - rc = usb_write(usbHandle, data, size, 1); + if (protocol == X_LINK_USB_EP) { + rc = usb_write(usbHandle, data, size, 1); + } else { + rc = usb_write(usbHandle, data, size, 0); + } #endif /*USE_USB_VSC*/ return rc; } diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 5c4b09b6..24f400cb 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -52,8 +52,8 @@ int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void ** int usbPlatformClose(void *fd); int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length); -int usbPlatformRead(void *fd, void *data, int size); -int usbPlatformWrite(void *fd, void *data, int size); +int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size); +int usbPlatformWrite(XLinkProtocol_t protocol, void *fd, void *data, int size); int usbPlatformGateRead(void *data, int size, int timeout); int usbPlatformGateWrite(void *data, int size, int timeout); @@ -69,8 +69,8 @@ static inline int usbPlatformServer(const char *devPathRead, const char *devPath static inline int usbPlatformClose(void *fd) { return -1; } static inline int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length) { return -1; } -static inline int usbPlatformRead(void *fd, void *data, int size) { return -1; } -static inline int usbPlatformWrite(void *fd, void *data, int size) { return -1; } +static inline int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size) { return -1; } +static inline int usbPlatformWrite(XLinkProtocol_t protocol, void *fd, void *data, int size) { return -1; } static inline int usbPlatformGateRead(void *data, int size, int timeout) { return -1; } static inline int usbPlatformGateWrite(void *data, int size, int timeout) { return -1; } From 68a9933e49faea6463cd1780fd5378906f8f2628 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 12:11:35 +0200 Subject: [PATCH 60/78] usb_host: Remove read/write mutexes for testing. --- src/pc/protocols/usb_host.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 8de47642..ac431bcd 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1093,8 +1093,6 @@ int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t off int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) { - std::lock_guard l(mutex); - int rc = 0; #ifndef USE_USB_VSC int nread = 0; @@ -1152,8 +1150,6 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size) { - std::lock_guard l(mutex); - int rc = 0; #ifndef USE_USB_VSC int byteCount = 0; From cdeb969609c13e834a12595cb1abbc93a7588191 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 12:20:42 +0200 Subject: [PATCH 61/78] usb_host: Added server setting for EPs. --- src/pc/protocols/usb_host.cpp | 54 ++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 20 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index ac431bcd..17a677dc 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -50,6 +50,7 @@ static constexpr int XLINK_USB_DATA_TIMEOUT = 0; static unsigned int bulk_chunklen = DEFAULT_CHUNKSZ; static int write_timeout = DEFAULT_WRITE_TIMEOUT; static int initialized; +static bool isServer; struct UsbSetupPacket { uint8_t requestType; @@ -889,6 +890,8 @@ int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void ** usbFdRead = infd; usbFdWrite = outfd; + + isServer = true; *fd = createPlatformDeviceFdKey((void*) (uintptr_t) usbFdRead); #endif @@ -1001,6 +1004,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const // (as file descriptors are reused and can cause a clash with lookups between scheduler and link) *fd = createPlatformDeviceFdKey(usbHandle); + isServer = false; #endif /*USE_USB_VSC*/ return 0; @@ -1132,17 +1136,22 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) #endif /*USE_LINK_JTAG*/ #else - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - if (protocol == X_LINK_USB_EP) { - rc = usb_read(usbHandle, data, size, 1); + if (isServer) { + rc = read(usbFdRead, data, size); } else { - rc = usb_read(usbHandle, data, size, 0); + std::lock_guard l(mutex); + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + if (protocol == X_LINK_USB_EP) { + rc = usb_read(usbHandle, data, size, 1); + } else { + rc = usb_read(usbHandle, data, size, 0); + } } #endif /*USE_USB_VSC*/ return rc; @@ -1192,17 +1201,22 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size #endif /*USE_LINK_JTAG*/ #else - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; - } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - if (protocol == X_LINK_USB_EP) { - rc = usb_write(usbHandle, data, size, 1); + if (isServer) { + rc = write(usbFdWrite, data, size); } else { - rc = usb_write(usbHandle, data, size, 0); + std::lock_guard l(mutex); + void* tmpUsbHandle = NULL; + if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return -1; + } + libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; + + if (protocol == X_LINK_USB_EP) { + rc = usb_write(usbHandle, data, size, 1); + } else { + rc = usb_write(usbHandle, data, size, 0); + } } #endif /*USE_USB_VSC*/ return rc; From 2164106a9e9f3eb62352c81cfd5244a21e08498d Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 12:29:25 +0200 Subject: [PATCH 62/78] usb_host: Disabled mutex in read/write due to scheduler. --- src/pc/protocols/usb_host.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 17a677dc..55cb0c64 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1139,7 +1139,7 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) if (isServer) { rc = read(usbFdRead, data, size); } else { - std::lock_guard l(mutex); + //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); @@ -1204,7 +1204,7 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size if (isServer) { rc = write(usbFdWrite, data, size); } else { - std::lock_guard l(mutex); + //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); From 9c0193b77f839a36707589014d128f891e3b391e Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Mon, 6 Oct 2025 13:41:23 +0200 Subject: [PATCH 63/78] Whitespace tweaks --- include/XLink/XLinkPrivateDefines.h | 1 - src/pc/PlatformData.c | 2 -- src/pc/PlatformDeviceControl.c | 4 ++-- src/pc/PlatformDeviceSearch.c | 7 +++++-- src/pc/protocols/usb_host.cpp | 1 + src/shared/XLinkDevice.c | 6 +++--- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/include/XLink/XLinkPrivateDefines.h b/include/XLink/XLinkPrivateDefines.h index 6c0a714f..9b9e92f0 100644 --- a/include/XLink/XLinkPrivateDefines.h +++ b/include/XLink/XLinkPrivateDefines.h @@ -120,7 +120,6 @@ typedef enum XLINK_READ_REL_SPEC_RESP, XLINK_WRITE_FD_RESP, // only for the shared mem protocol - XLINK_RESP_LAST, } xLinkEventType_t; diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index 40d17327..a27e2e87 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -102,7 +102,6 @@ int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size) case X_LINK_LOCAL_SHDMEM: return shdmemPlatformWrite(deviceHandle->xLinkFD, data, size); #endif - case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to write with TCP_IP_OR_LOCAL_SHDMEM\n"); @@ -151,7 +150,6 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l case X_LINK_LOCAL_SHDMEM: return shdmemPlatformRead(deviceHandle->xLinkFD, data, size, fd); #endif - case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: mvLog(MVLOG_ERROR, "Failed to read with TCP_IP_OR_LOCAL_SHDMEM\n"); default: diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 37b17bcc..74d704ad 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -92,7 +92,7 @@ xLinkPlatformErrorCode_t XLinkPlatformInit(XLinkGlobalHandler_t* globalHandler) // check for failed initialization; LIBUSB_SUCCESS = 0 if (usbInitialize(globalHandler->options) != 0) { xlinkSetProtocolInitialized(X_LINK_USB_VSC, 0); - xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); + xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); } // Initialize tcpip protocol if necessary @@ -188,7 +188,7 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha switch (*protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: return usbPlatformConnect(*protocol, devPathRead, devPathWrite, fd); case X_LINK_PCIE: diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 3b9b594a..8169aba5 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -64,12 +64,13 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe switch (in_deviceRequirements.protocol){ case X_LINK_USB_CDC: case X_LINK_USB_VSC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: if(!XLinkIsProtocolInitialized(in_deviceRequirements.protocol)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+in_deviceRequirements.protocol; } // Check if protocol is initialized return getUSBDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); + /* TODO(themarpe) - reenable PCIe case X_LINK_PCIE: return getPCIeDeviceName(0, state, in_deviceRequirements, out_foundDevice); @@ -87,7 +88,8 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } return getLocalShdmemDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); #endif - case X_LINK_ANY_PROTOCOL: + +case X_LINK_ANY_PROTOCOL: // If USB protocol is initialized if(XLinkIsProtocolInitialized(X_LINK_USB_VSC)) { // Find first correct USB Device @@ -102,6 +104,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe sizeFoundDevices -= numFoundDevices; } } + // TODO(themarpe) - reenable PCIe (void) PCIe_rc; diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 55cb0c64..ffeac948 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -991,6 +991,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const return 0; #endif /*USE_LINK_JTAG*/ #else + libusb_device_handle* usbHandle = nullptr; xLinkPlatformErrorCode_t ret = usbLinkOpen(protocol, devPathWrite, usbHandle); diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index 716db847..7e0e0711 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -644,7 +644,7 @@ XLinkError_t parsePlatformError(xLinkPlatformErrorCode_t rc) { case X_LINK_PLATFORM_DEVICE_BUSY: return X_LINK_DEVICE_ALREADY_IN_USE; case X_LINK_PLATFORM_USB_DRIVER_NOT_LOADED: - case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: + case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: return X_LINK_INIT_USB_ERROR; case X_LINK_PLATFORM_TCP_IP_DRIVER_NOT_LOADED: return X_LINK_INIT_TCP_IP_ERROR; @@ -700,9 +700,9 @@ const char* XLinkProtocolToStr(XLinkProtocol_t val) { case X_LINK_PCIE: return "X_LINK_PCIE"; case X_LINK_IPC: return "X_LINK_IPC"; case X_LINK_TCP_IP: return "X_LINK_TCP_IP"; - case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; + case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: return "X_LINK_TCP_IP_OR_LOCAL_SHDMEM"; - case X_LINK_USB_EP: return "X_LINK_USB_EP"; + case X_LINK_USB_EP: return "X_LINK_USB_EP"; case X_LINK_NMB_OF_PROTOCOLS: return "X_LINK_NMB_OF_PROTOCOLS"; case X_LINK_ANY_PROTOCOL: return "X_LINK_ANY_PROTOCOL"; default: From 4e33b5500968ba3f535b51837f458ae88c1e1799 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Mon, 6 Oct 2025 18:26:26 +0200 Subject: [PATCH 64/78] usb_host: Formatting changes. --- cmake/XLinkDependencies.cmake | 4 ++- src/pc/PlatformData.c | 2 +- src/pc/PlatformDeviceControl.c | 11 +++--- src/pc/PlatformDeviceSearch.c | 5 ++- src/pc/protocols/usb_host.cpp | 64 ++++++++++++++++++---------------- src/pc/protocols/usb_host.h | 4 +-- src/shared/XLinkDevice.c | 6 ++-- 7 files changed, 50 insertions(+), 46 deletions(-) diff --git a/cmake/XLinkDependencies.cmake b/cmake/XLinkDependencies.cmake index bc6377e9..c1fcac95 100644 --- a/cmake/XLinkDependencies.cmake +++ b/cmake/XLinkDependencies.cmake @@ -11,9 +11,11 @@ endif() if(NOT CONFIG_MODE OR (CONFIG_MODE AND NOT XLINK_INSTALL_PUBLIC_ONLY AND XLINK_ENABLE_LIBUSB)) if(XLINK_LIBUSB_LOCAL) add_subdirectory("${XLINK_LIBUSB_LOCAL}" "${CMAKE_CURRENT_BINARY_DIR}/libusb" EXCLUDE_FROM_ALL) - elseif(NOT XLINK_LIBUSB_SYSTEM) + elseif(XLINK_LIBUSB_SYSTEM) find_package(PkgConfig REQUIRED) pkg_check_modules(LIBUSB REQUIRED libusb-1.0) + elseif(NOT XLINK_LIBUSB_SYSTEM) + find_package(usb-1.0 ${_QUIET} CONFIG REQUIRED HINTS "${CMAKE_CURRENT_LIST_DIR}/libusb") endif() endif() diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index a27e2e87..e6e27b75 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -137,7 +137,7 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l switch (deviceHandle->protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: return usbPlatformRead(deviceHandle->protocol, deviceHandle->xLinkFD, data, size); case X_LINK_PCIE: diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 74d704ad..c2881acd 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -92,7 +92,7 @@ xLinkPlatformErrorCode_t XLinkPlatformInit(XLinkGlobalHandler_t* globalHandler) // check for failed initialization; LIBUSB_SUCCESS = 0 if (usbInitialize(globalHandler->options) != 0) { xlinkSetProtocolInitialized(X_LINK_USB_VSC, 0); - xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); + xlinkSetProtocolInitialized(X_LINK_USB_EP, 0); } // Initialize tcpip protocol if necessary @@ -188,7 +188,7 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha switch (*protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: return usbPlatformConnect(*protocol, devPathRead, devPathWrite, fd); case X_LINK_PCIE: @@ -212,9 +212,9 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha xLinkPlatformErrorCode_t XLinkPlatformServer(const char* devPathRead, const char* devPathWrite, XLinkProtocol_t *protocol, void** fd) { switch (*protocol) { - case X_LINK_USB_VSC: + case X_LINK_USB_VSC: case X_LINK_USB_CDC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: return usbPlatformServer(devPathRead, devPathWrite, fd); case X_LINK_TCP_IP: @@ -268,7 +268,8 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan switch (deviceHandle->protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - return usbPlatformClose(deviceHandle->xLinkFD); + case X_LINK_USB_EP: + return usbPlatformClose(deviceHandle->protocol, deviceHandle->xLinkFD); case X_LINK_PCIE: return pciePlatformClose(deviceHandle->xLinkFD); diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 8169aba5..4203c398 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -64,7 +64,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe switch (in_deviceRequirements.protocol){ case X_LINK_USB_CDC: case X_LINK_USB_VSC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: if(!XLinkIsProtocolInitialized(in_deviceRequirements.protocol)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+in_deviceRequirements.protocol; } @@ -89,7 +89,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe return getLocalShdmemDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); #endif -case X_LINK_ANY_PROTOCOL: + case X_LINK_ANY_PROTOCOL: // If USB protocol is initialized if(XLinkIsProtocolInitialized(X_LINK_USB_VSC)) { // Find first correct USB Device @@ -105,7 +105,6 @@ case X_LINK_ANY_PROTOCOL: } } - // TODO(themarpe) - reenable PCIe (void) PCIe_rc; /* diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index ffeac948..d072c303 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -188,29 +188,25 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } } + XLinkPlatform_t platform = X_LINK_MYRIAD_X; + XLinkProtocol_t protocol = X_LINK_USB_VSC; + std::string mxId; + // Check for RVC3 and RVC4 first if(state == X_LINK_GATE || in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ GateResponse gateResponse; - std::string serial; - getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, serial); + getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, mxId); // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; if (gateResponse.platform == 4) { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC4; + platform = X_LINK_RVC4; } else { - out_foundDevices[numDevicesFound].platform = X_LINK_RVC3; + platform = X_LINK_RVC3; } - out_foundDevices[numDevicesFound].protocol = (XLinkProtocol_t)gateResponse.protocol; - out_foundDevices[numDevicesFound].state = (XLinkDeviceState_t)gateResponse.state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, serial.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; + protocol = (XLinkProtocol_t)gateResponse.protocol; + state = (XLinkDeviceState_t)gateResponse.state; } else { // Get device mxid - std::string mxId; libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); switch (rc) @@ -237,19 +233,18 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } // TODO(themarpe) - check platform - - // Everything passed, fillout details of found device - out_foundDevices[numDevicesFound].status = status; - out_foundDevices[numDevicesFound].platform = X_LINK_MYRIAD_X; - out_foundDevices[numDevicesFound].protocol = X_LINK_USB_VSC; - out_foundDevices[numDevicesFound].state = state; - memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); - strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); - memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); - strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); - numDevicesFound++; } + // Everything passed, fillout details of found device + out_foundDevices[numDevicesFound].status = status; + out_foundDevices[numDevicesFound].platform = platform; + out_foundDevices[numDevicesFound].protocol = protocol; + out_foundDevices[numDevicesFound].state = state; + memset(out_foundDevices[numDevicesFound].name, 0, sizeof(out_foundDevices[numDevicesFound].name)); + strncpy(out_foundDevices[numDevicesFound].name, devicePath.c_str(), sizeof(out_foundDevices[numDevicesFound].name)); + memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); + strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); + numDevicesFound++; } } @@ -614,7 +609,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev if((res = libusb_open(dev, &h)) < 0) { mvLog(MVLOG_DEBUG, "cannot open device: %s\n", xlink_libusb_strerror(res)); -return (libusb_error) res; + return (libusb_error) res; } // Get configuration first @@ -869,10 +864,14 @@ xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { return X_LINK_PLATFORM_SUCCESS; } -void usbLinkClose(libusb_device_handle *f) +void usbLinkClose(XLinkProtocol_t protocol, libusb_device_handle *f) { libusb_release_interface(f, 0); - libusb_release_interface(f, 1); + + if (protocol = X_LINK_USB_EP) { + libusb_release_interface(f, 1); + } + libusb_close(f); } @@ -929,7 +928,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const } return 0; - #else +#else usbFdRead= open(devPathRead, O_RDWR); if(usbFdRead < 0) { @@ -989,7 +988,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const return X_LINK_PLATFORM_ERROR; } return 0; - #endif /*USE_LINK_JTAG*/ +#endif /*USE_LINK_JTAG*/ #else libusb_device_handle* usbHandle = nullptr; @@ -1012,7 +1011,7 @@ int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const } -int usbPlatformClose(void *fdKey) +int usbPlatformClose(XLinkProtocol_t protocol, void *fdKey) { std::lock_guard l(mutex); @@ -1036,7 +1035,7 @@ int usbPlatformClose(void *fdKey) mvLog(MVLOG_FATAL, "Cannot find USB Handle by key: %" PRIxPTR, (uintptr_t) fdKey); return -1; } - usbLinkClose((libusb_device_handle *) tmpUsbHandle); + usbLinkClose(protocol, (libusb_device_handle *) tmpUsbHandle); if(destroyPlatformDeviceFdKey(fdKey)){ mvLog(MVLOG_FATAL, "Cannot destroy USB Handle key: %" PRIxPTR, (uintptr_t) fdKey); @@ -1253,6 +1252,8 @@ int usbPlatformGateRead(void *data, int size, int timeout) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, 0); if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); + return rc; } @@ -1293,6 +1294,7 @@ int usbPlatformGateWrite(void *data, int size, int timeout) /* Now we claim our ffs interfaces */ rc = libusb_claim_interface(gate_dev_handle, 0); if (rc != LIBUSB_SUCCESS) { + libusb_close(gate_dev_handle); return rc; } diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 24f400cb..12a66920 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -49,7 +49,7 @@ int get_pid_by_name(const char* name); xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* path); int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd); int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd); -int usbPlatformClose(void *fd); +int usbPlatformClose(XLinkProtocol_t protocol, void *fd); int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length); int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size); @@ -66,7 +66,7 @@ static inline xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* path) { static inline int usbPlatformConnect(XLinkProtocol_t protocol, const char *devPathRead, const char *devPathWrite, void **fd) { return -1; } static inline int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) { return -1; } -static inline int usbPlatformClose(void *fd) { return -1; } +static inline int usbPlatformClose(XLinkProtocol_t protocol, void *fd) { return -1; } static inline int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length) { return -1; } static inline int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size) { return -1; } diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index 7e0e0711..945f221f 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -644,7 +644,7 @@ XLinkError_t parsePlatformError(xLinkPlatformErrorCode_t rc) { case X_LINK_PLATFORM_DEVICE_BUSY: return X_LINK_DEVICE_ALREADY_IN_USE; case X_LINK_PLATFORM_USB_DRIVER_NOT_LOADED: - case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: + case X_LINK_PLATFORM_USB_EP_DRIVER_NOT_LOADED: return X_LINK_INIT_USB_ERROR; case X_LINK_PLATFORM_TCP_IP_DRIVER_NOT_LOADED: return X_LINK_INIT_TCP_IP_ERROR; @@ -700,9 +700,9 @@ const char* XLinkProtocolToStr(XLinkProtocol_t val) { case X_LINK_PCIE: return "X_LINK_PCIE"; case X_LINK_IPC: return "X_LINK_IPC"; case X_LINK_TCP_IP: return "X_LINK_TCP_IP"; - case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; + case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: return "X_LINK_TCP_IP_OR_LOCAL_SHDMEM"; - case X_LINK_USB_EP: return "X_LINK_USB_EP"; + case X_LINK_USB_EP: return "X_LINK_USB_EP"; case X_LINK_NMB_OF_PROTOCOLS: return "X_LINK_NMB_OF_PROTOCOLS"; case X_LINK_ANY_PROTOCOL: return "X_LINK_ANY_PROTOCOL"; default: From 18d6dee253b3838b0a7a37f47bb87c5a1abccdae Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 11:58:48 +0200 Subject: [PATCH 65/78] usb_host: Added explicit dev path for Gate Read/Write --- include/XLink/XLink.h | 5 +++-- include/XLink/XLinkPlatform.h | 4 ++-- src/pc/PlatformData.c | 8 ++++---- src/pc/protocols/usb_host.cpp | 26 +++++++++++++++++++++----- src/pc/protocols/usb_host.h | 8 ++++---- src/shared/XLinkData.c | 8 ++++---- 6 files changed, 38 insertions(+), 21 deletions(-) diff --git a/include/XLink/XLink.h b/include/XLink/XLink.h index e658ab22..1774f387 100644 --- a/include/XLink/XLink.h +++ b/include/XLink/XLink.h @@ -308,13 +308,14 @@ XLinkError_t XLinkWriteData_(streamId_t streamId, const uint8_t* buffer, int siz /** * @brief Sends/Receives a message to Gate via USB + * @param[in] name - Device name/path * @param[in] data - Data to be transmitted/collected * @param[in] size - The data size * @param[in] size - USB timeout * @return Status code of the operation: X_LINK_SUCCESS (0) for success */ -XLinkError_t XLinkGateWrite(void *data, int size, int timeout); -XLinkError_t XLinkGateRead(void *data, int size, int timeout); +XLinkError_t XLinkGateWrite(const char *name, void *data, int size, int timeout); +XLinkError_t XLinkGateRead(const char *name, void *data, int size, int timeout); /** * @brief Sends a package to initiate the writing of a file descriptor diff --git a/include/XLink/XLinkPlatform.h b/include/XLink/XLinkPlatform.h index e95be051..c3974a95 100644 --- a/include/XLink/XLinkPlatform.h +++ b/include/XLink/XLinkPlatform.h @@ -91,8 +91,8 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan int XLinkPlatformWrite(xLinkDeviceHandle_t *deviceHandle, void *data, int size); int XLinkPlatformWriteFd(xLinkDeviceHandle_t *deviceHandle, const long fd, void *data2, int size2); int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, long *fd); -int XLinkPlatformGateWrite(void *data, int size, int timeout); -int XLinkPlatformGateRead(void *data, int size, int timeout); +int XLinkPlatformGateWrite(const char *name, void *data, int size, int timeout); +int XLinkPlatformGateRead(const char *name, void *data, int size, int timeout); void* XLinkPlatformAllocateData(uint32_t size, uint32_t alignment); void XLinkPlatformDeallocateData(void *ptr, uint32_t size, uint32_t alignment); diff --git a/src/pc/PlatformData.c b/src/pc/PlatformData.c index e6e27b75..9fb2d56b 100644 --- a/src/pc/PlatformData.c +++ b/src/pc/PlatformData.c @@ -157,22 +157,22 @@ int XLinkPlatformRead(xLinkDeviceHandle_t *deviceHandle, void *data, int size, l } } -int XLinkPlatformGateWrite(void *data, int size, int timeout) +int XLinkPlatformGateWrite(const char *name, void *data, int size, int timeout) { if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbPlatformGateWrite(data, size, timeout); + return usbPlatformGateWrite(name, data, size, timeout); } -int XLinkPlatformGateRead(void *data, int size, int timeout) +int XLinkPlatformGateRead(const char *name, void *data, int size, int timeout) { if(!XLinkIsProtocolInitialized(X_LINK_USB_EP)) { return X_LINK_PLATFORM_DRIVER_NOT_LOADED+X_LINK_USB_EP; } - return usbPlatformGateRead(data, size, timeout); + return usbPlatformGateRead(name, data, size, timeout); } diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index d072c303..522da76f 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -639,7 +639,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev return (libusb_error) res; } - if (protocol = X_LINK_USB_EP) { + if (protocol == X_LINK_USB_EP) { if((res = libusb_claim_interface(h, 1)) < 0) { mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); @@ -1222,7 +1222,7 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size return rc; } -int usbPlatformGateRead(void *data, int size, int timeout) +int usbPlatformGateRead(const char *name, void *data, int size, int timeout) { std::lock_guard l(mutex); @@ -1231,7 +1231,15 @@ int usbPlatformGateRead(void *data, int size, int timeout) int rc = 0; /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(context, 0x05C6, 0x4321); + libusb_device *gate_dev; + refLibusbDeviceByName(name, &gate_dev); + if (gate_dev == NULL) { + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; + } + + libusb_device_handle *gate_dev_handle; + libusb_open(gate_dev, &gate_dev_handle); if (gate_dev_handle == NULL) { rc = LIBUSB_ERROR_NO_DEVICE; return rc; @@ -1264,7 +1272,7 @@ int usbPlatformGateRead(void *data, int size, int timeout) return rc; } -int usbPlatformGateWrite(void *data, int size, int timeout) +int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) { std::lock_guard l(mutex); @@ -1273,7 +1281,15 @@ int usbPlatformGateWrite(void *data, int size, int timeout) int rc = 0; /* Get our device */ - libusb_device_handle *gate_dev_handle = libusb_open_device_with_vid_pid(context, 0x05C6, 0x4321); + libusb_device *gate_dev; + refLibusbDeviceByName(name, &gate_dev); + if (gate_dev == NULL) { + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; + } + + libusb_device_handle *gate_dev_handle; + libusb_open(gate_dev, &gate_dev_handle); if (gate_dev_handle == NULL) { rc = LIBUSB_ERROR_NO_DEVICE; return rc; diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 12a66920..574ddfa2 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -55,8 +55,8 @@ int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size); int usbPlatformWrite(XLinkProtocol_t protocol, void *fd, void *data, int size); -int usbPlatformGateRead(void *data, int size, int timeout); -int usbPlatformGateWrite(void *data, int size, int timeout); +int usbPlatformGateRead(const char *name, void *data, int size, int timeout); +int usbPlatformGateWrite(const char *name, void *data, int size, int timeout); #else @@ -72,8 +72,8 @@ static inline int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const static inline int usbPlatformRead(XLinkProtocol_t protocol, void *fd, void *data, int size) { return -1; } static inline int usbPlatformWrite(XLinkProtocol_t protocol, void *fd, void *data, int size) { return -1; } -static inline int usbPlatformGateRead(void *data, int size, int timeout) { return -1; } -static inline int usbPlatformGateWrite(void *data, int size, int timeout) { return -1; } +static inline int usbPlatformGateRead(const char *name, void *data, int size, int timeout) { return -1; } +static inline int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) { return -1; } static inline xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 08000560..dd935d40 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -117,9 +117,9 @@ XLinkError_t XLinkCloseStream(streamId_t const streamId) return X_LINK_SUCCESS; } -XLinkError_t XLinkGateWrite(void *data, int size, int timeout) +XLinkError_t XLinkGateWrite(const char *name, void *data, int size, int timeout) { - int rc = XLinkPlatformGateWrite(data, size, timeout); + int rc = XLinkPlatformGateWrite(name, data, size, timeout); if(rc < 0) { return X_LINK_ERROR; } else { @@ -127,9 +127,9 @@ XLinkError_t XLinkGateWrite(void *data, int size, int timeout) } } -XLinkError_t XLinkGateRead(void *data, int size, int timeout) +XLinkError_t XLinkGateRead(const char *name, void *data, int size, int timeout) { - int rc = XLinkPlatformGateRead(data, size, timeout); + int rc = XLinkPlatformGateRead(name, data, size, timeout); if(rc < 0) { return X_LINK_ERROR; } else { From 72e6b7e8edf1e2a41b45308466cca2cf7ac71bb4 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:31:35 +0200 Subject: [PATCH 66/78] usb_host: Fixed missing else case. --- src/pc/protocols/usb_host.cpp | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 522da76f..3a6b7367 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -194,15 +194,15 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, // Check for RVC3 and RVC4 first if(state == X_LINK_GATE || in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ - GateResponse gateResponse; - getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, mxId); - - // Everything passed, fillout details of found device - if (gateResponse.platform == 4) { - platform = X_LINK_RVC4; - } else { - platform = X_LINK_RVC3; - } + GateResponse gateResponse; + getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, mxId); + + // Everything passed, fillout details of found device + if (gateResponse.platform == 4) { + platform = X_LINK_RVC4; + } else { + platform = X_LINK_RVC3; + } protocol = (XLinkProtocol_t)gateResponse.protocol; state = (XLinkDeviceState_t)gateResponse.state; } else { @@ -632,12 +632,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) libusb_set_auto_detach_kernel_driver(h, 1); - if((res = libusb_claim_interface(h, 0)) < 0) - { - mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; - } + if (protocol == X_LINK_USB_EP) { if((res = libusb_claim_interface(h, 1)) < 0) @@ -646,6 +641,13 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev libusb_close(h); return (libusb_error) res; } + } else { + if((res = libusb_claim_interface(h, 0)) < 0) + { + mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; + } } if((res = libusb_get_config_descriptor(dev, 0, &cdesc)) < 0) From deaedf5bec1a2c573d53403fb9d1cc4680695b16 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:34:28 +0200 Subject: [PATCH 67/78] usb_host: Explicit ep assignment in read/write --- src/pc/protocols/usb_host.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 3a6b7367..eb88c09d 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -1063,7 +1063,7 @@ int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware -int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) +int usb_read(libusb_device_handle *f, void *data, size_t size, uint8_t ep) { const int chunk_size = DEFAULT_CHUNKSZ; while(size > 0) @@ -1071,7 +1071,7 @@ int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) int bt, ss = (int)size; if(ss > chunk_size) ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_IN + offset,(unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + int rc = libusb_bulk_transfer(f, ep, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); if(rc) return rc; data = ((char *)data) + bt; @@ -1080,7 +1080,7 @@ int usb_read(libusb_device_handle *f, void *data, size_t size, size_t offset) return 0; } -int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t offset) +int usb_write(libusb_device_handle *f, const void *data, size_t size, uint8_t ep) { const int chunk_size = DEFAULT_CHUNKSZ; while(size > 0) @@ -1088,7 +1088,7 @@ int usb_write(libusb_device_handle *f, const void *data, size_t size, size_t off int bt, ss = (int)size; if(ss > chunk_size) ss = chunk_size; - int rc = libusb_bulk_transfer(f, USB_ENDPOINT_OUT + offset, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); + int rc = libusb_bulk_transfer(f, ep, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); if(rc) return rc; data = (char *)data + bt; @@ -1150,9 +1150,9 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if (protocol == X_LINK_USB_EP) { - rc = usb_read(usbHandle, data, size, 1); + rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 1); } else { - rc = usb_read(usbHandle, data, size, 0); + rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 0); } } #endif /*USE_USB_VSC*/ @@ -1215,9 +1215,9 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if (protocol == X_LINK_USB_EP) { - rc = usb_write(usbHandle, data, size, 1); + rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 1); } else { - rc = usb_write(usbHandle, data, size, 0); + rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 0); } } #endif /*USE_USB_VSC*/ From 79d268bd9d444b9ee9144daf379d09830a51aa55 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:37:58 +0200 Subject: [PATCH 68/78] usb_host: Reset VIDPID to pre-testing. --- src/pc/protocols/usb_host.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index eb88c09d..0325e509 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -102,10 +102,9 @@ static std::unordered_map vidPidToDeviceS {{0x03E7, 0xf63b}, X_LINK_BOOTED}, {{0x03E7, 0xf63c}, X_LINK_BOOTLOADER}, {{0x03E7, 0xf63d}, X_LINK_FLASH_BOOTED}, - {{0x05C6, 0x4321}, X_LINK_GATE}, + {{0x05C6, 0x901d}, X_LINK_GATE}, }; - struct USBGateRequest { uint32_t RequestNum; uint32_t RequestSize; @@ -871,7 +870,7 @@ void usbLinkClose(XLinkProtocol_t protocol, libusb_device_handle *f) libusb_release_interface(f, 0); if (protocol = X_LINK_USB_EP) { - libusb_release_interface(f, 1); + libusb_release_interface(f, 1); } libusb_close(f); From e8c3654636200fa0de6cd87c20e570393e61b34a Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:42:03 +0200 Subject: [PATCH 69/78] usb_host: Fixed formatting. --- cmake/XLinkDependencies.cmake | 2 +- src/pc/PlatformDeviceControl.c | 2 +- src/pc/protocols/usb_host.cpp | 7 +++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/cmake/XLinkDependencies.cmake b/cmake/XLinkDependencies.cmake index c1fcac95..cf9f4b2b 100644 --- a/cmake/XLinkDependencies.cmake +++ b/cmake/XLinkDependencies.cmake @@ -13,7 +13,7 @@ if(NOT CONFIG_MODE OR (CONFIG_MODE AND NOT XLINK_INSTALL_PUBLIC_ONLY AND XLINK_E add_subdirectory("${XLINK_LIBUSB_LOCAL}" "${CMAKE_CURRENT_BINARY_DIR}/libusb" EXCLUDE_FROM_ALL) elseif(XLINK_LIBUSB_SYSTEM) find_package(PkgConfig REQUIRED) - pkg_check_modules(LIBUSB REQUIRED libusb-1.0) + pkg_check_modules(LIBUSB REQUIRED libusb-1.0) elseif(NOT XLINK_LIBUSB_SYSTEM) find_package(usb-1.0 ${_QUIET} CONFIG REQUIRED HINTS "${CMAKE_CURRENT_LIST_DIR}/libusb") endif() diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index c2881acd..2e725ac8 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -268,7 +268,7 @@ xLinkPlatformErrorCode_t XLinkPlatformCloseRemote(xLinkDeviceHandle_t* deviceHan switch (deviceHandle->protocol) { case X_LINK_USB_VSC: case X_LINK_USB_CDC: - case X_LINK_USB_EP: + case X_LINK_USB_EP: return usbPlatformClose(deviceHandle->protocol, deviceHandle->xLinkFD); case X_LINK_PCIE: diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 0325e509..f038a53a 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -25,6 +25,7 @@ #include #include +// Used server side only #if defined(__unix__) #include #include @@ -244,6 +245,7 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, memset(out_foundDevices[numDevicesFound].mxid, 0, sizeof(out_foundDevices[numDevicesFound].mxid)); strncpy(out_foundDevices[numDevicesFound].mxid, mxId.c_str(), sizeof(out_foundDevices[numDevicesFound].mxid)); numDevicesFound++; + } } @@ -867,10 +869,11 @@ xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { void usbLinkClose(XLinkProtocol_t protocol, libusb_device_handle *f) { - libusb_release_interface(f, 0); if (protocol = X_LINK_USB_EP) { - libusb_release_interface(f, 1); + libusb_release_interface(f, 1); + } else { + libusb_release_interface(f, 0); } libusb_close(f); From 8db877751f83631ce79af8ee785117265fb65fd4 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:44:40 +0200 Subject: [PATCH 70/78] usb_host: Fixed serial. --- src/pc/protocols/usb_host.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index f038a53a..f8c9714e 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -576,12 +576,13 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* return (libusb_error) libusb_rc; } + size_t serialStrLen = usbGateResponse.RequestSize - sizeof(GateResponse); - char *serialStr = (char*)malloc(serialStrLen + 1); - memcpy(serialStr, (const char*)&respBuffer[0], serialStrLen); - serialStr[serialStrLen] = '\0'; - serial = serialStr; - free(serialStr); + serial.resize(serialStrLen + 1); + for (int i = 0; i < serialStrLen; ++i) { + serial[i] = respBuffer[i]; + } + serial[serialStrLen] = '\0'; outSerial = serial; memcpy(&gateResponse, &respBuffer[serialStrLen], sizeof(gateResponse)); From eadd24d8660e892b63b787a1de580e65858950d8 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:51:30 +0200 Subject: [PATCH 71/78] Fixed formatting. --- src/pc/PlatformDeviceSearch.c | 3 ++- src/pc/protocols/usb_host.cpp | 50 +++++++++++++++++------------------ src/shared/XLinkData.c | 8 +++--- src/shared/XLinkDevice.c | 2 +- 4 files changed, 32 insertions(+), 31 deletions(-) diff --git a/src/pc/PlatformDeviceSearch.c b/src/pc/PlatformDeviceSearch.c index 4203c398..f029ef94 100644 --- a/src/pc/PlatformDeviceSearch.c +++ b/src/pc/PlatformDeviceSearch.c @@ -70,7 +70,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } // Check if protocol is initialized return getUSBDevices(in_deviceRequirements, out_foundDevices, sizeFoundDevices, out_amountOfFoundDevices); - + /* TODO(themarpe) - reenable PCIe case X_LINK_PCIE: return getPCIeDeviceName(0, state, in_deviceRequirements, out_foundDevice); @@ -105,6 +105,7 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevices(const deviceDesc_t in_deviceRe } } + // TODO(themarpe) - reenable PCIe (void) PCIe_rc; /* diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index f8c9714e..743d4524 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -611,7 +611,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev if((res = libusb_open(dev, &h)) < 0) { mvLog(MVLOG_DEBUG, "cannot open device: %s\n", xlink_libusb_strerror(res)); - return (libusb_error) res; + return (libusb_error) res; } // Get configuration first @@ -637,18 +637,18 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev if (protocol == X_LINK_USB_EP) { - if((res = libusb_claim_interface(h, 1)) < 0) - { + if((res = libusb_claim_interface(h, 1)) < 0) + { mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; - } + } } else { if((res = libusb_claim_interface(h, 0)) < 0) { - mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; + mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; } } @@ -1142,7 +1142,7 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) #else if (isServer) { - rc = read(usbFdRead, data, size); + rc = read(usbFdRead, data, size); } else { //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; @@ -1153,9 +1153,9 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if (protocol == X_LINK_USB_EP) { - rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 1); + rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 1); } else { - rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 0); + rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 0); } } #endif /*USE_USB_VSC*/ @@ -1207,7 +1207,7 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size #else if (isServer) { - rc = write(usbFdWrite, data, size); + rc = write(usbFdWrite, data, size); } else { //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; @@ -1218,9 +1218,9 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if (protocol == X_LINK_USB_EP) { - rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 1); + rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 1); } else { - rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 0); + rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 0); } } #endif /*USE_USB_VSC*/ @@ -1239,15 +1239,15 @@ int usbPlatformGateRead(const char *name, void *data, int size, int timeout) libusb_device *gate_dev; refLibusbDeviceByName(name, &gate_dev); if (gate_dev == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } libusb_device_handle *gate_dev_handle; libusb_open(gate_dev, &gate_dev_handle); if (gate_dev_handle == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } /* Not strictly necessary, but it is better to use it, @@ -1257,7 +1257,7 @@ int usbPlatformGateRead(const char *name, void *data, int size, int timeout) if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - return rc; + return rc; } libusb_device* dev = libusb_get_device(gate_dev_handle); @@ -1267,7 +1267,7 @@ int usbPlatformGateRead(const char *name, void *data, int size, int timeout) if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - return rc; + return rc; } rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_IN, (unsigned char*)data, size, &rc, timeout); @@ -1289,15 +1289,15 @@ int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) libusb_device *gate_dev; refLibusbDeviceByName(name, &gate_dev); if (gate_dev == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } libusb_device_handle *gate_dev_handle; libusb_open(gate_dev, &gate_dev_handle); if (gate_dev_handle == NULL) { - rc = LIBUSB_ERROR_NO_DEVICE; - return rc; + rc = LIBUSB_ERROR_NO_DEVICE; + return rc; } /* Not strictly necessary, but it is better to use it, @@ -1307,7 +1307,7 @@ int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - return rc; + return rc; } libusb_device* dev = libusb_get_device(gate_dev_handle); @@ -1317,7 +1317,7 @@ int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); - return rc; + return rc; } rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_OUT, (unsigned char*)data, size, &rc, timeout); diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index dd935d40..2cc93658 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -121,9 +121,9 @@ XLinkError_t XLinkGateWrite(const char *name, void *data, int size, int timeout) { int rc = XLinkPlatformGateWrite(name, data, size, timeout); if(rc < 0) { - return X_LINK_ERROR; + return X_LINK_ERROR; } else { - return X_LINK_SUCCESS; + return X_LINK_SUCCESS; } } @@ -131,9 +131,9 @@ XLinkError_t XLinkGateRead(const char *name, void *data, int size, int timeout) { int rc = XLinkPlatformGateRead(name, data, size, timeout); if(rc < 0) { - return X_LINK_ERROR; + return X_LINK_ERROR; } else { - return X_LINK_SUCCESS; + return X_LINK_SUCCESS; } } diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index 945f221f..dcf5c640 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -700,7 +700,7 @@ const char* XLinkProtocolToStr(XLinkProtocol_t val) { case X_LINK_PCIE: return "X_LINK_PCIE"; case X_LINK_IPC: return "X_LINK_IPC"; case X_LINK_TCP_IP: return "X_LINK_TCP_IP"; - case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; + case X_LINK_LOCAL_SHDMEM: return "X_LINK_LOCAL_SHDMEM"; case X_LINK_TCP_IP_OR_LOCAL_SHDMEM: return "X_LINK_TCP_IP_OR_LOCAL_SHDMEM"; case X_LINK_USB_EP: return "X_LINK_USB_EP"; case X_LINK_NMB_OF_PROTOCOLS: return "X_LINK_NMB_OF_PROTOCOLS"; From b83a8c318f85669e85b4684ae26b049a97bc36ea Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Tue, 7 Oct 2025 12:54:56 +0200 Subject: [PATCH 72/78] Fixed formatting. --- src/pc/PlatformDeviceControl.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 2e725ac8..cce0bd63 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -204,6 +204,7 @@ xLinkPlatformErrorCode_t XLinkPlatformConnect(const char* devPathRead, const cha case X_LINK_LOCAL_SHDMEM: return shdmemPlatformConnect(devPathRead, devPathWrite, fd); #endif + default: return X_LINK_PLATFORM_INVALID_PARAMETERS; } From 7ea6780b313ae33ecf9036b2a80d4d3c914c950e Mon Sep 17 00:00:00 2001 From: Martin Peterlin Date: Wed, 8 Oct 2025 16:15:45 +0200 Subject: [PATCH 73/78] Rearranged some format & fixed an if statement misassignment bug --- src/pc/protocols/usb_host.cpp | 73 ++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 36 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 743d4524..67a4a32a 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -188,24 +188,26 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, } } - XLinkPlatform_t platform = X_LINK_MYRIAD_X; - XLinkProtocol_t protocol = X_LINK_USB_VSC; - std::string mxId; + XLinkPlatform_t platform = X_LINK_MYRIAD_X; + XLinkProtocol_t protocol = X_LINK_USB_VSC; + std::string mxId; - // Check for RVC3 and RVC4 first - if(state == X_LINK_GATE || in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + // Check for RVC3 and RVC4 first + if(state == X_LINK_GATE || in_deviceRequirements.platform == X_LINK_RVC3 || in_deviceRequirements.platform == X_LINK_RVC4){ + GateResponse gateResponse; getLibusbDeviceGateResponse(&desc, devs[i], gateResponse, mxId); - - // Everything passed, fillout details of found device - if (gateResponse.platform == 4) { + + if (gateResponse.platform == 4){ platform = X_LINK_RVC4; } else { platform = X_LINK_RVC3; } + protocol = (XLinkProtocol_t)gateResponse.protocol; state = (XLinkDeviceState_t)gateResponse.state; - } else { + + } else { // Get device mxid libusb_error rc = getLibusbDeviceMxId(state, devicePath, &desc, devs[i], mxId); mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s", xlink_libusb_strerror(rc)); @@ -225,15 +227,20 @@ xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, break; } - // compare with MxId - std::string requiredMxId(in_deviceRequirements.mxid); - if(requiredMxId.length() > 0 && requiredMxId != mxId){ - // Current device doesn't match the "filter" - continue; - } + } - // TODO(themarpe) - check platform - } + // Comparisons / filters + // compare deviceId + std::string requiredMxId(in_deviceRequirements.mxid); + if(requiredMxId.length() > 0 && requiredMxId != mxId){ + // Current device doesn't match the "filter" + continue; + } + // compare platform + if(in_deviceRequirements.platform != X_LINK_ANY_PLATFORM && in_deviceRequirements.platform != platform){ + // Current device doesn't match the "filter" + continue; + } // Everything passed, fillout details of found device out_foundDevices[numDevicesFound].status = status; @@ -635,20 +642,16 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) libusb_set_auto_detach_kernel_driver(h, 1); - - if (protocol == X_LINK_USB_EP) { - if((res = libusb_claim_interface(h, 1)) < 0) - { + if(protocol == X_LINK_USB_EP){ + if((res = libusb_claim_interface(h, 1)) < 0){ mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; - } } else { - if((res = libusb_claim_interface(h, 0)) < 0) - { - mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); - libusb_close(h); - return (libusb_error) res; + if((res = libusb_claim_interface(h, 0)) < 0){ + mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); + libusb_close(h); + return (libusb_error) res; } } @@ -871,7 +874,7 @@ xLinkPlatformErrorCode_t usbLinkBootBootloader(const char *path) { void usbLinkClose(XLinkProtocol_t protocol, libusb_device_handle *f) { - if (protocol = X_LINK_USB_EP) { + if (protocol == X_LINK_USB_EP){ libusb_release_interface(f, 1); } else { libusb_release_interface(f, 0); @@ -1141,10 +1144,9 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) #endif /*USE_LINK_JTAG*/ #else - if (isServer) { - rc = read(usbFdRead, data, size); + if(isServer){ + rc = read(usbFdRead, data, size); } else { - //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); @@ -1152,7 +1154,7 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - if (protocol == X_LINK_USB_EP) { + if(protocol == X_LINK_USB_EP) { rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 1); } else { rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 0); @@ -1206,10 +1208,9 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size #endif /*USE_LINK_JTAG*/ #else - if (isServer) { - rc = write(usbFdWrite, data, size); + if(isServer){ + rc = write(usbFdWrite, data, size); } else { - //std::lock_guard l(mutex); void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); @@ -1217,7 +1218,7 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size } libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - if (protocol == X_LINK_USB_EP) { + if(protocol == X_LINK_USB_EP){ rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 1); } else { rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 0); From b5f63da223bbb3b9fa895ca89ae86bacd7119dc8 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 8 Oct 2025 16:33:41 +0200 Subject: [PATCH 74/78] usb_host: Added atomic bool for isServer. --- src/pc/protocols/usb_host.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 67a4a32a..fbf16ee8 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -17,8 +17,7 @@ // std #include -#include -#include +#include #include #include #include @@ -51,7 +50,7 @@ static constexpr int XLINK_USB_DATA_TIMEOUT = 0; static unsigned int bulk_chunklen = DEFAULT_CHUNKSZ; static int write_timeout = DEFAULT_WRITE_TIMEOUT; static int initialized; -static bool isServer; +static std::atomic isServer { false }; struct UsbSetupPacket { uint8_t requestType; From e56ec8ad10381ef58cde3be430ebf77a470623d2 Mon Sep 17 00:00:00 2001 From: Filippo Mutta Date: Wed, 8 Oct 2025 18:06:59 +0200 Subject: [PATCH 75/78] usb_host: Added missing bracket. --- src/pc/protocols/usb_host.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 7d624ad7..867a886e 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -685,6 +685,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; + } } else { if((res = libusb_claim_interface(h, 0)) < 0){ mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); From fccbec0e780c73da0146b271ce1a6a6808cdcabe Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 2 Feb 2026 06:19:48 +0200 Subject: [PATCH 76/78] usb_host: cleanup a few whitespaces --- src/pc/protocols/usb_host.cpp | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 867a886e..5e40bedf 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -572,22 +572,22 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* } USBGateRequest usbGateRequest = { - .RequestNum = 12, - .RequestSize = 0, + .RequestNum = 12, + .RequestSize = 0, }; - + int transferred = 0; libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { - libusb_close(handle); + libusb_close(handle); return (libusb_error) libusb_rc; } USBGateRequest usbGateResponse = { 0 }; libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { - libusb_close(handle); + libusb_close(handle); return (libusb_error) libusb_rc; } @@ -595,7 +595,7 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* respBuffer.resize(usbGateResponse.RequestSize); libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { - libusb_close(handle); + libusb_close(handle); return (libusb_error) libusb_rc; } @@ -685,7 +685,7 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; - } + } } else { if((res = libusb_claim_interface(h, 0)) < 0){ mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); @@ -931,7 +931,7 @@ int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void ** int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); if(outfd < 0 || infd < 0) { - return -1; + return -1; } usbFdRead = infd; @@ -1184,7 +1184,7 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) #else if(isServer){ - rc = read(usbFdRead, data, size); + rc = read(usbFdRead, data, size); } else { void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ @@ -1248,7 +1248,7 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size #else if(isServer){ - rc = write(usbFdWrite, data, size); + rc = write(usbFdWrite, data, size); } else { void* tmpUsbHandle = NULL; if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ From d061873ddd6316b03d0eaf1150b90e6ba7f2298e Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 2 Feb 2026 06:21:04 +0200 Subject: [PATCH 77/78] usb_host: USB_EP adapt to latest RVC4 OS USB interfaces --- src/pc/protocols/usb_host.cpp | 49 +++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 5e40bedf..94c6fc8d 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -51,8 +51,17 @@ static constexpr std::chrono::milliseconds DEFAULT_CONNECT_TIMEOUT{20000}; static constexpr std::chrono::milliseconds DEFAULT_SEND_FILE_TIMEOUT{10000}; static constexpr auto USB1_CHUNKSZ = 64; -static constexpr int USB_ENDPOINT_IN = 0x81; -static constexpr int USB_ENDPOINT_OUT = 0x01; +static constexpr int USB_VSC_INTERFACE = 0; +static constexpr int USB_VSC_ENDPOINT_IN = 0x81; +static constexpr int USB_VSC_ENDPOINT_OUT = 0x01; + +// TBD could be taken from the USB descriptor, based on strings +static constexpr int USB_EP_INTERFACE_GATE = 2; +static constexpr int USB_EP_INTERFACE_DEVICE = 3; +static constexpr int USB_EP_ENDPOINT_GATE_IN = 0x83; +static constexpr int USB_EP_ENDPOINT_GATE_OUT = 0x03; +static constexpr int USB_EP_ENDPOINT_DEVICE_IN = 0x84; +static constexpr int USB_EP_ENDPOINT_DEVICE_OUT = 0x04; static constexpr int XLINK_USB_DATA_TIMEOUT = 0; @@ -565,7 +574,7 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* return (libusb_error) libusb_rc; } - libusb_rc = libusb_claim_interface(handle, 0); + libusb_rc = libusb_claim_interface(handle, USB_EP_INTERFACE_GATE); if (libusb_rc != 0){ libusb_close(handle); return (libusb_error) libusb_rc; @@ -578,14 +587,14 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* int transferred = 0; - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); + libusb_rc = libusb_bulk_transfer(handle, USB_EP_ENDPOINT_GATE_OUT, (unsigned char*)&usbGateRequest, sizeof(usbGateRequest), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); return (libusb_error) libusb_rc; } USBGateRequest usbGateResponse = { 0 }; - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); + libusb_rc = libusb_bulk_transfer(handle, USB_EP_ENDPOINT_GATE_IN, (unsigned char*)&usbGateResponse, sizeof(usbGateResponse), &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); return (libusb_error) libusb_rc; @@ -593,7 +602,7 @@ static libusb_error getLibusbDeviceGateResponse(const libusb_device_descriptor* std::vector respBuffer; respBuffer.resize(usbGateResponse.RequestSize); - libusb_rc = libusb_bulk_transfer(handle, USB_ENDPOINT_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); + libusb_rc = libusb_bulk_transfer(handle, USB_EP_ENDPOINT_GATE_IN, (unsigned char*)&respBuffer[0], usbGateResponse.RequestSize, &transferred, DEFAULT_WRITE_TIMEOUT); if (libusb_rc != 0) { libusb_close(handle); return (libusb_error) libusb_rc; @@ -681,14 +690,14 @@ static libusb_error usb_open_device(XLinkProtocol_t protocol, libusb_device *dev libusb_set_auto_detach_kernel_driver(h, 1); if(protocol == X_LINK_USB_EP){ - if((res = libusb_claim_interface(h, 1)) < 0){ - mvLog(MVLOG_DEBUG, "claiming interface 1 failed: %s\n", xlink_libusb_strerror(res)); + if((res = libusb_claim_interface(h, USB_EP_INTERFACE_DEVICE)) < 0){ + mvLog(MVLOG_DEBUG, "claiming interface %d failed: %s\n", USB_EP_INTERFACE_DEVICE, xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; } } else { - if((res = libusb_claim_interface(h, 0)) < 0){ - mvLog(MVLOG_DEBUG, "claiming interface 0 failed: %s\n", xlink_libusb_strerror(res)); + if((res = libusb_claim_interface(h, USB_VSC_INTERFACE)) < 0){ + mvLog(MVLOG_DEBUG, "claiming interface %d failed: %s\n", USB_VSC_INTERFACE, xlink_libusb_strerror(res)); libusb_close(h); return (libusb_error) res; } @@ -914,9 +923,9 @@ void usbLinkClose(XLinkProtocol_t protocol, libusb_device_handle *f) { if (protocol == X_LINK_USB_EP){ - libusb_release_interface(f, 1); + libusb_release_interface(f, USB_EP_INTERFACE_DEVICE); } else { - libusb_release_interface(f, 0); + libusb_release_interface(f, USB_VSC_INTERFACE); } libusb_close(f); @@ -1194,9 +1203,9 @@ int usbPlatformRead(XLinkProtocol_t protocol, void* fdKey, void* data, int size) libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if(protocol == X_LINK_USB_EP) { - rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 1); + rc = usb_read(usbHandle, data, size, USB_EP_ENDPOINT_DEVICE_IN); } else { - rc = usb_read(usbHandle, data, size, USB_ENDPOINT_IN + 0); + rc = usb_read(usbHandle, data, size, USB_VSC_ENDPOINT_IN); } } #endif /*USE_USB_VSC*/ @@ -1258,9 +1267,9 @@ int usbPlatformWrite(XLinkProtocol_t protocol, void *fdKey, void *data, int size libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; if(protocol == X_LINK_USB_EP){ - rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 1); + rc = usb_write(usbHandle, data, size, USB_EP_ENDPOINT_DEVICE_OUT); } else { - rc = usb_write(usbHandle, data, size, USB_ENDPOINT_OUT + 0); + rc = usb_write(usbHandle, data, size, USB_VSC_ENDPOINT_OUT); } } #endif /*USE_USB_VSC*/ @@ -1303,14 +1312,14 @@ int usbPlatformGateRead(const char *name, void *data, int size, int timeout) libusb_device* dev = libusb_get_device(gate_dev_handle); /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, 0); + rc = libusb_claim_interface(gate_dev_handle, USB_EP_INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); return rc; } - rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_IN, (unsigned char*)data, size, &rc, timeout); + rc = libusb_bulk_transfer(gate_dev_handle, USB_EP_ENDPOINT_GATE_IN, (unsigned char*)data, size, &rc, timeout); libusb_close(gate_dev_handle); @@ -1353,14 +1362,14 @@ int usbPlatformGateWrite(const char *name, void *data, int size, int timeout) libusb_device* dev = libusb_get_device(gate_dev_handle); /* Now we claim our ffs interfaces */ - rc = libusb_claim_interface(gate_dev_handle, 0); + rc = libusb_claim_interface(gate_dev_handle, USB_EP_INTERFACE_GATE); if (rc != LIBUSB_SUCCESS) { libusb_close(gate_dev_handle); return rc; } - rc = libusb_bulk_transfer(gate_dev_handle, USB_ENDPOINT_OUT, (unsigned char*)data, size, &rc, timeout); + rc = libusb_bulk_transfer(gate_dev_handle, USB_EP_ENDPOINT_GATE_OUT, (unsigned char*)data, size, &rc, timeout); libusb_close(gate_dev_handle); From 32fefff6fb7006b3472da157900d8105de63126e Mon Sep 17 00:00:00 2001 From: alex-luxonis Date: Mon, 2 Feb 2026 08:09:46 +0200 Subject: [PATCH 78/78] Rename /dev/usb-ffs/xlink -> /dev/usb-ffs/device --- examples/xlink_usb_server.cpp | 2 +- src/pc/protocols/usb_host.cpp | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/examples/xlink_usb_server.cpp b/examples/xlink_usb_server.cpp index 43ebe035..fe223960 100644 --- a/examples/xlink_usb_server.cpp +++ b/examples/xlink_usb_server.cpp @@ -37,7 +37,7 @@ int main(int argc, const char** argv){ } XLinkHandler_t handler; - handler.devicePath = "/dev/usb-ffs/xlink"; + handler.devicePath = "/dev/usb-ffs/device"; handler.protocol = X_LINK_USB_EP; XLinkServerOnly(&handler); diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 051d7eba..e25f2efd 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -974,8 +974,9 @@ extern int usbFdRead; int usbPlatformServer(const char *devPathRead, const char *devPathWrite, void **fd) { #if defined(__unix__) - int outfd = open("/dev/usb-ffs/xlink/ep1", O_WRONLY); - int infd = open("/dev/usb-ffs/xlink/ep2", O_RDONLY); + // FIXME: get this info from the caller, don't hardcode here + int outfd = open("/dev/usb-ffs/device/ep1", O_WRONLY); + int infd = open("/dev/usb-ffs/device/ep2", O_RDONLY); if(outfd < 0 || infd < 0) { return -1;