diff --git a/CMakeLists.txt b/CMakeLists.txt index 2160770..07f6890 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # With additions of Luxonis -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.3) include("cmake/HunterGate.cmake") HunterGate( @@ -82,7 +82,7 @@ else() endif() if(WIN32) - target_compile_definitions(${TARGET_NAME} PRIVATE WIN32_LEAN_AND_MEAN) + target_compile_definitions(${TARGET_NAME} PRIVATE WIN32_LEAN_AND_MEAN NOMINMAX) else() find_package(Threads REQUIRED) target_link_libraries(${TARGET_NAME} PRIVATE Threads::Threads) @@ -140,18 +140,19 @@ endif() # Set C99 standard set_property(TARGET ${TARGET_NAME} PROPERTY C_STANDARD 99) -# Set compiler features (c++11), and disables extensions (g++11) -set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 11) +# Set compiler features (c++14), and disables extensions (g++14) +set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD 14) set_property(TARGET ${TARGET_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET ${TARGET_NAME} PROPERTY CXX_EXTENSIONS OFF) -# Add interface transitive property (C++11) to public library +# Add interface transitive property (C++14) to public library if(${CMAKE_VERSION} VERSION_LESS "3.8.0") - target_compile_features(${TARGET_PUBLIC_NAME} INTERFACE cxx_range_for) + target_compile_features(${TARGET_PUBLIC_NAME} INTERFACE cxx_generic_lambdas) else() - target_compile_features(${TARGET_PUBLIC_NAME} INTERFACE cxx_std_11) + target_compile_features(${TARGET_PUBLIC_NAME} INTERFACE cxx_std_14) endif() # Add flags +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) add_default_flags(${TARGET_NAME}) # Check if pthread_getname_np exists diff --git a/cmake/Flags.cmake b/cmake/Flags.cmake index 08c1e2f..41b2f75 100644 --- a/cmake/Flags.cmake +++ b/cmake/Flags.cmake @@ -1,13 +1,19 @@ ## setup compilation flags # conditionally applies flag. If flag is supported by current compiler, it will be added to compile options. include(CheckCCompilerFlag) +include(CheckCXXCompilerFlag) function(add_flag target flag) check_c_compiler_flag(${flag} FLAG_${flag}) if (FLAG_${flag} EQUAL 1) target_compile_options(${target} PRIVATE $<$:${flag}>) endif () + check_cxx_compiler_flag(${flag} FLAGXX_${flag}) + if (FLAGXX_${flag} EQUAL 1) + target_compile_options(${target} PRIVATE $<$:${flag}>) + endif () endfunction() +# only works for C source files function(add_flag_source source flag) check_c_compiler_flag(${flag} FLAG_${flag}) if (FLAG_${flag} EQUAL 1) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index aa68432..1e7b1f5 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -2,7 +2,7 @@ macro(add_example example_name example_src) add_executable(${example_name} ${example_src}) target_link_libraries(${example_name} ${TARGET_NAME}) - set_property(TARGET ${example_name} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${example_name} PROPERTY CXX_STANDARD 14) set_property(TARGET ${example_name} PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET ${example_name} PROPERTY CXX_EXTENSIONS OFF) diff --git a/include/XLink/XLinkErrorUtils.h b/include/XLink/XLinkErrorUtils.h index af65913..70eccd9 100644 --- a/include/XLink/XLinkErrorUtils.h +++ b/include/XLink/XLinkErrorUtils.h @@ -15,7 +15,7 @@ extern "C" #ifndef ASSERT_XLINK #define ASSERT_XLINK(condition) do { \ if(!(condition)) { \ - mvLog(MVLOG_ERROR, "Assertion Failed: %s \n", #condition); \ + mvLog(MVLOG_ERROR, "Assertion Failed: %s", #condition); \ return X_LINK_ERROR; \ } \ } while(0) @@ -26,7 +26,7 @@ extern "C" #ifndef ASSERT_XLINK #define ASSERT_XLINK(condition) do { \ if(!(condition)) { \ - mvLog(MVLOG_ERROR, "Assertion Failed: %s \n", #condition); \ + mvLog(MVLOG_ERROR, "Assertion Failed: %s", #condition); \ exit(EXIT_FAILURE); \ } \ } while(0) @@ -88,7 +88,7 @@ extern "C" #define XLINK_OUT_IF(condition) do { \ \ XLINK_OUT_WITH_LOG_IF((condition), \ - mvLog(MVLOG_ERROR, "Condition failed: %s \n", #condition));\ + mvLog(MVLOG_ERROR, "Condition failed: %s", #condition));\ \ } while(0) #endif // XLINK_OUT_IF diff --git a/include/XLink/XLinkLog.h b/include/XLink/XLinkLog.h index e487c71..759b709 100644 --- a/include/XLink/XLinkLog.h +++ b/include/XLink/XLinkLog.h @@ -44,12 +44,18 @@ typedef enum mvLog_t{ MVLOG_LAST, } mvLog_t; -// Windows-only -#if (defined (WINNT) || defined(_WIN32) || defined(_WIN64) ) -#define __attribute__(x) -#define FUNCATTR_WEAK static +// attribute directive support test +#if defined(__has_attribute) +# define MVLOG_ATTRIBUTE(x) __attribute__(x) +// weak function support test +# if __has_attribute(weak) +# define FUNCATTR_WEAK +# else +# define FUNCATTR_WEAK static +# endif #else -#define FUNCATTR_WEAK +# define MVLOG_ATTRIBUTE(x) +# define FUNCATTR_WEAK static #endif #define _MVLOGLEVEL(UNIT_NAME) mvLogLevel_ ## UNIT_NAME @@ -61,7 +67,7 @@ typedef enum mvLog_t{ #ifndef MVLOG_UNIT_NAME #define MVLOG_UNIT_NAME global #else -FUNCATTR_WEAK mvLog_t __attribute__ ((weak)) MVLOGLEVEL(MVLOG_UNIT_NAME) = MVLOG_LAST; +FUNCATTR_WEAK mvLog_t MVLOG_ATTRIBUTE((weak)) MVLOGLEVEL(MVLOG_UNIT_NAME) = MVLOG_LAST; #endif @@ -75,7 +81,7 @@ FUNCATTR_WEAK mvLog_t __attribute__ ((weak)) MVLOGLEVEL(MVLOG_UNIT_NAME) = MVLOG extern mvLog_t MVLOGLEVEL(global); extern mvLog_t MVLOGLEVEL(default); -int __attribute__ ((unused)) logprintf(mvLog_t curLogLvl, mvLog_t lvl, const char * func, const int line, const char * format, ...); +int MVLOG_ATTRIBUTE((unused)) logprintf(mvLog_t curLogLvl, mvLog_t lvl, const char * func, const int line, const char * format, ...); #define mvLog(lvl, format, ...) \ logprintf(MVLOGLEVEL(MVLOG_UNIT_NAME), lvl, __func__, __LINE__, format, ##__VA_ARGS__) diff --git a/include/XLink/XLinkPlatformErrorUtils.h b/include/XLink/XLinkPlatformErrorUtils.h index 6c7853c..7af8c97 100644 --- a/include/XLink/XLinkPlatformErrorUtils.h +++ b/include/XLink/XLinkPlatformErrorUtils.h @@ -15,7 +15,7 @@ extern "C" #ifndef ASSERT_XLINK_PLATFORM_R #define ASSERT_XLINK_PLATFORM_R(condition, err) do { \ if(!(condition)) { \ - mvLog(MVLOG_ERROR, "Assertion Failed: %s \n", #condition); \ + mvLog(MVLOG_ERROR, "Assertion Failed: %s", #condition); \ return (err); \ } \ } while(0) @@ -34,7 +34,7 @@ extern "C" #ifndef ASSERT_XLINK_PLATFORM_R #define ASSERT_XLINK_PLATFORM_R(condition, err) do { \ if(!(condition)) { \ - mvLog(MVLOG_ERROR, "Assertion Failed: %s \n", #condition); \ + mvLog(MVLOG_ERROR, "Assertion Failed: %s", #condition); \ exit(EXIT_FAILURE); \ } \ } while(0) diff --git a/src/pc/PlatformDeviceControl.c b/src/pc/PlatformDeviceControl.c index 1b1092f..bc81c53 100644 --- a/src/pc/PlatformDeviceControl.c +++ b/src/pc/PlatformDeviceControl.c @@ -30,7 +30,7 @@ int usbFdRead = -1; #include "XLinkPublicDefines.h" #define USB_LINK_SOCKET_PORT 5678 -#define UNUSED __attribute__((unused)) +#define UNUSED MVLOG_ATTRIBUTE((unused)) static UsbSpeed_t usb_speed_enum = X_LINK_USB_SPEED_UNKNOWN; diff --git a/src/pc/PlatformDeviceFd.cpp b/src/pc/PlatformDeviceFd.cpp index 360cc17..d08243e 100644 --- a/src/pc/PlatformDeviceFd.cpp +++ b/src/pc/PlatformDeviceFd.cpp @@ -1,44 +1,102 @@ +#define MVLOG_UNIT_NAME xLinkUsb + #include "PlatformDeviceFd.h" +#include "XLink/XLinkLog.h" -#include #include -#include #include +#include +#include static std::mutex mutex; static std::unordered_map map; static std::uintptr_t uniqueFdKey{0x55}; -int getPlatformDeviceFdFromKey(void* fdKeyRaw, void** fd){ - if(fd == nullptr) return -1; - std::unique_lock lock(mutex); - - std::uintptr_t fdKey = reinterpret_cast(fdKeyRaw); - if(map.count(fdKey) > 0){ - *fd = map.at(fdKey); +// Returns the mapped fd value of the element with key equivalent to fdKeyRaw +// Non-zero return value indicates failure +int getPlatformDeviceFdFromKey(void* const fdKeyRaw, void** const fd) noexcept { + if(fd == nullptr) { + mvLog(MVLOG_ERROR, "getPlatformDeviceFdFromKey/Simple(%p) failed", fdKeyRaw); + return -1; + } + try { + const std::lock_guard lock(mutex); + const auto result = map.find(reinterpret_cast(fdKeyRaw)); + if(result == map.end()) { + mvLog(MVLOG_ERROR, "getPlatformDeviceFdFromKey/Simple(%p) failed", fdKeyRaw); + return 1; + } + *fd = result->second; + //mvLog(MVLOG_DEBUG, "getPlatformDeviceFdFromKey(%p) result %p", fdKeyRaw, *fd); return 0; - } else { - return 1; + } catch(std::exception&) { + mvLog(MVLOG_ERROR, "getPlatformDeviceFdFromKey/Simple(%p) failed", fdKeyRaw); + return -1; } } -void* createPlatformDeviceFdKey(void* fd){ - std::unique_lock lock(mutex); - - // Get uniqueFdKey - std::uintptr_t fdKey = uniqueFdKey++; - map[fdKey] = fd; - return reinterpret_cast(fdKey); +// Returns the mapped fd value of the element with key equivalent to fdKeyRaw +// returns nullptr when not found, failure, or createPlatformDeviceFdKey(nullptr) was used +void* getPlatformDeviceFdFromKeySimple(void* const fdKeyRaw) noexcept { + void* fd = nullptr; + getPlatformDeviceFdFromKey(fdKeyRaw, &fd); + return fd; } -int destroyPlatformDeviceFdKey(void* fdKeyRaw){ - std::unique_lock lock(mutex); +// Inserts a copy of value fd into an associative container with key fdKeyRaw +// nullptr return value indicates failure +void* createPlatformDeviceFdKey(void* const fd) noexcept { + try { + const std::lock_guard lock(mutex); + const std::uintptr_t fdKey = uniqueFdKey++; // Get a unique key + map[fdKey] = fd; + mvLog(MVLOG_DEBUG, "createPlatformDeviceFdKey(%p) result %p", fd, reinterpret_cast(fdKey)); + return reinterpret_cast(fdKey); + } catch(std::exception&) { + mvLog(MVLOG_ERROR, "createPlatformDeviceFdKey(%p) failed", fd); + return nullptr; + } +} - std::uintptr_t fdKey = reinterpret_cast(fdKeyRaw); - if(map.count(fdKey) > 0){ - map.erase(fdKey); - return 0; - } else { +// Removes the element (if one exists) with the key equivalent to fdKeyRaw +// Non-zero return value indicates failure +int destroyPlatformDeviceFdKey(void* const fdKeyRaw) noexcept { + try { + std::lock_guard lock(mutex); + const auto result = map.erase(reinterpret_cast(fdKeyRaw)); + if (result == 0) + mvLog(MVLOG_ERROR, "destroyPlatformDeviceFdKey(%p) failed", fdKeyRaw); + else + mvLog(MVLOG_DEBUG, "destroyPlatformDeviceFdKey(%p) success", fdKeyRaw); + return !result; + } catch(std::exception&) { + mvLog(MVLOG_ERROR, "destroyPlatformDeviceFdKey(%p) failed", fdKeyRaw); return -1; } } + +// Extracts (finds and removes) the element (if one exists) with the key equivalent to fdKeyRaw +// nullptr return value indicates failure +// Only one mutex lock is taken compared to separate getPlatformDeviceFdFromKey() then destroyPlatformDeviceFdKey(). +// Additionally, this atomic operation prevents a set of race conditions of two threads when +// each get() and/or destroy() keys in unpredictable orders. +void* extractPlatformDeviceFdKey(void* const fdKeyRaw) noexcept { + try { + std::lock_guard lock(mutex); + const auto result = map.find(reinterpret_cast(fdKeyRaw)); + if(result == map.end()) { + mvLog(MVLOG_ERROR, "extractPlatformDeviceFdKey(%p) failed", fdKeyRaw); + return nullptr; + } + const auto fd = result->second; + map.erase(result); + mvLog(MVLOG_DEBUG, "extractPlatformDeviceFdKey(%p) success", fdKeyRaw); + return fd; + } catch(std::exception&) { + mvLog(MVLOG_ERROR, "extractPlatformDeviceFdKey(%p) failed", fdKeyRaw); + return nullptr; + } +} + +// TODO consider storing the device_handle in the fdKey store with std::map so that +// it can automatically handle refcounting, interfaces, and closing diff --git a/src/pc/PlatformDeviceFd.h b/src/pc/PlatformDeviceFd.h index 224ca2c..6461d4a 100644 --- a/src/pc/PlatformDeviceFd.h +++ b/src/pc/PlatformDeviceFd.h @@ -2,13 +2,17 @@ #define _PLATFORM_DEVICE_FD_H_ #ifdef __cplusplus -extern "C" -{ +#define NOEXCEPT noexcept +extern "C" { +#else +#define NOEXCEPT #endif -int getPlatformDeviceFdFromKey(void* fdKeyRaw, void** fd); -void* createPlatformDeviceFdKey(void* fd); -int destroyPlatformDeviceFdKey(void* fdKeyRaw); +int getPlatformDeviceFdFromKey(void* fdKeyRaw, void** fd) NOEXCEPT; +void* getPlatformDeviceFdFromKeySimple(void* fdKeyRaw) NOEXCEPT; +void* createPlatformDeviceFdKey(void* fd) NOEXCEPT; +int destroyPlatformDeviceFdKey(void* fdKeyRaw) NOEXCEPT; +void* extractPlatformDeviceFdKey(void* fdKeyRaw) NOEXCEPT; #ifdef __cplusplus } diff --git a/src/pc/PlatformDeviceSearchDynamic.cpp b/src/pc/PlatformDeviceSearchDynamic.cpp index f8fcd87..c9e785c 100644 --- a/src/pc/PlatformDeviceSearchDynamic.cpp +++ b/src/pc/PlatformDeviceSearchDynamic.cpp @@ -19,7 +19,7 @@ extern "C" xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, int sizeFoundDevices, - unsigned int *out_amountOfFoundDevices); + unsigned int *out_amountOfFoundDevices) noexcept; xLinkPlatformErrorCode_t XLinkPlatformFindDevicesDynamic(const deviceDesc_t in_deviceRequirements, deviceDesc_t* out_foundDevices, unsigned sizeFoundDevices, @@ -138,9 +138,9 @@ xLinkPlatformErrorCode_t XLinkPlatformFindDevicesDynamic(const deviceDesc_t in_d { deviceDesc_t* devices = out_foundDevices; int write_index = 0; - for(int i = 0; i < *out_amountOfFoundDevices; i++){ + for(int i = 0, amountBound = static_cast(*out_amountOfFoundDevices); i < amountBound; ++i){ bool duplicate = false; - for(int j = i - 1; j >= 0; j--){ + for(int j = i - 1; j >= 0; --j){ // Check if duplicate if(devices[i].protocol == devices[j].protocol && strcmp(devices[i].name, devices[j].name) == 0 && strcmp(devices[i].mxid, devices[j].mxid) == 0){ duplicate = true; diff --git a/src/pc/Win/src/win_usb_host.cpp b/src/pc/Win/src/win_usb_host.cpp index 2f0215e..e688ecf 100755 --- a/src/pc/Win/src/win_usb_host.cpp +++ b/src/pc/Win/src/win_usb_host.cpp @@ -18,7 +18,8 @@ #include #include -int usbInitialize_customdir(void** hContext) { +// support Visual C++ compiler for Windows to load libusb DLL from the same directory as the XLink code +libusb_error usbInitializeCustomdir(libusb_context ** hContext) noexcept { // get handle to the module containing a XLink static function/var // can not use GetModuleFileNameW(nullptr) because when the main depthai app is a DLL (e.g. plugin), // then it returns the main EXE which is usually wrong @@ -57,9 +58,9 @@ int usbInitialize_customdir(void** hContext) { } // initialize libusb - int initResult = LIBUSB_SUCCESS; + libusb_error initResult = LIBUSB_SUCCESS; __try { - initResult = libusb_init((libusb_context**)hContext); + initResult = static_cast(libusb_init(hContext)); } __except (EXCEPTION_EXECUTE_HANDLER) { initResult = LIBUSB_ERROR_OVERFLOW; diff --git a/src/pc/protocols/pcie_host.c b/src/pc/protocols/pcie_host.c index 586d12e..7190256 100644 --- a/src/pc/protocols/pcie_host.c +++ b/src/pc/protocols/pcie_host.c @@ -157,7 +157,7 @@ int pcie_write(HANDLE fd, void * buf, size_t bufSize) Event = CreateEvent(NULL, TRUE, FALSE, NULL); if (Event == NULL) { - mvLog(MVLOG_INFO, "Error creating I/O event for pcie_write - 0x%x\n", GetLastError()); + mvLog(MVLOG_INFO, "Error creating I/O event for pcie_write - 0x%x", GetLastError()); return PCIE_HOST_ERROR; } @@ -174,7 +174,7 @@ int pcie_write(HANDLE fd, void * buf, size_t bufSize) mvLog(MVLOG_DEBUG, "WriteFile GetOverlappedResult failed"); } } else { - mvLog(MVLOG_DEBUG, "WriteFile failed with error code = 0x%x \n", GetLastError()); + mvLog(MVLOG_DEBUG, "WriteFile failed with error code = 0x%x", GetLastError()); } } else { mvLog(MVLOG_DEBUG, "fOverlapped - operation complete immediately"); @@ -233,7 +233,7 @@ int pcie_read(HANDLE fd, void * buf, size_t bufSize) Event = CreateEvent(NULL, TRUE, FALSE, NULL); if (Event == NULL) { - mvLog(MVLOG_ERROR, "Error creating I/O event for pcie_read - 0x%x\n", GetLastError()); + mvLog(MVLOG_ERROR, "Error creating I/O event for pcie_read - 0x%x", GetLastError()); return PCIE_HOST_ERROR; } @@ -251,7 +251,7 @@ int pcie_read(HANDLE fd, void * buf, size_t bufSize) mvLog(MVLOG_DEBUG, "ReadFile GetOverlappedResult failed" ); } } else { - mvLog(MVLOG_DEBUG, "ReadFile failed with error code = 0x%x \n", GetLastError()); + mvLog(MVLOG_DEBUG, "ReadFile failed with error code = 0x%x", GetLastError()); } } else { mvLog(MVLOG_DEBUG, "fOverlapped - operation complete immediately"); @@ -559,7 +559,7 @@ pcieHostError_t pcie_boot_device(HANDLE fd, const char *buffer, size_t length) int resetDeviceRC = pcie_reset_device(fd); if (resetDeviceRC) { - mvLog(MVLOG_ERROR, "Device resetting failed with error: %d\n", resetDeviceRC); + mvLog(MVLOG_ERROR, "Device resetting failed with error: %d", resetDeviceRC); return resetDeviceRC; } } diff --git a/src/pc/protocols/usb_host.cpp b/src/pc/protocols/usb_host.cpp index 32bc245..fd77c25 100644 --- a/src/pc/protocols/usb_host.cpp +++ b/src/pc/protocols/usb_host.cpp @@ -14,20 +14,32 @@ #include "usb_mx_id.h" #include "usb_host.h" #include "../PlatformDeviceFd.h" +#include "wrap_libusb.hpp" // std #include -#include +#include #include #include #include +#include #include #include - -constexpr static int MAXIMUM_PORT_NUMBERS = 7; +#include + +using dp::libusb::config_descriptor; +using dp::libusb::device_handle; +using dp::libusb::device_list; +using dp::libusb::out_param; +using dp::libusb::out_param_ptr; +using dp::libusb::span; +using dp::libusb::usb_context; +using dp::libusb::usb_device; +using dp::libusb::usb_error; using VidPid = std::pair; -static const int MX_ID_TIMEOUT_MS = 100; +static constexpr int MAXIMUM_PORT_NUMBERS = 7; +static constexpr 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 std::chrono::milliseconds DEFAULT_CONNECT_TIMEOUT{20000}; @@ -39,8 +51,6 @@ 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 { @@ -59,257 +69,263 @@ static UsbSetupPacket bootBootloaderPacket{ 0 // not used }; +// transform a libusb error code into a XLinkPlatformErrorCode_t +xLinkPlatformErrorCode_t parseLibusbError(libusb_error) noexcept; +#if defined(_WIN32) && defined(_MSC_VER) +// support Visual C++ compiler for Windows to load libusb DLL from the same directory as the XLink code +libusb_error usbInitializeCustomdir(libusb_context** hContext) noexcept; +#endif -static std::mutex mutex; -static libusb_context* context; +static std::mutex cacheMutex; // protects usb_mx_id_cache_* functions +static usb_context context; + +// initialize libusb and set the context +// the return is a xLinkPlatformErrorCode_t cast to an int +int usbInitialize(void* options) noexcept { + try { + #ifdef __ANDROID__ + // If Android, set the options as JavaVM (to default context) + if(options != nullptr){ + libusb_set_option(nullptr, libusb_option::LIBUSB_OPTION_ANDROID_JAVAVM, options); + } + #else + (void)options; + #endif -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); - // // Debug - // mvLogLevelSet(MVLOG_DEBUG); + // Initialize mx id cache + { + std::lock_guard l(cacheMutex); + usb_mx_id_cache_init(); + } - #if defined(_WIN32) && defined(_MSC_VER) - return usbInitialize_customdir((void**)&context); - #endif - return libusb_init(&context); + #if defined(_WIN32) && defined(_MSC_VER) + return parseLibusbError(usbInitializeCustomdir(out_param(context))); + #else + return parseLibusbError(static_cast(libusb_init(out_param(context)))); + #endif + } catch (const std::exception& ex) { + mvLog(MVLOG_FATAL, "usbInitialize() failed: %s", ex.what()); + return X_LINK_PLATFORM_ERROR; + } } -struct pair_hash { - template - std::size_t operator() (const std::pair &pair) const { - return std::hash()(pair.first) ^ std::hash()(pair.second); - } -}; +static bool operator==(const std::pair& entry, const VidPid& vidpid) noexcept { + return entry.first.first == vidpid.first && entry.first.second == vidpid.second; +} -static std::unordered_map vidPidToDeviceState = { +static constexpr std::array, 4> VID_PID_TO_DEVICE_STATE = {{ {{0x03E7, 0x2485}, X_LINK_UNBOOTED}, {{0x03E7, 0xf63b}, X_LINK_BOOTED}, {{0x03E7, 0xf63c}, X_LINK_BOOTLOADER}, {{0x03E7, 0xf63d}, X_LINK_FLASH_BOOTED}, -}; +}}; -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 const char* xlink_libusb_strerror(int x); +static std::string getLibusbDevicePath(const usb_device&); +static libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, const std::string& devicePath, const libusb_device_descriptor& descriptor, const usb_device& device, std::string& outMxId); +static const char* xlink_libusb_strerror(ssize_t); #ifdef _WIN32 -std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev); +std::string getWinUsbMxId(const VidPid&, const usb_device&); #endif -extern "C" 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(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; +extern "C" xLinkPlatformErrorCode_t getUSBDevices(const deviceDesc_t deviceRequirements, + deviceDesc_t* const foundDevicesBuffer, const int sizeFoundDevicesBufferEntities, + unsigned int* const resultCountFoundDevices) noexcept { + try { + // validate parameters + if (foundDevicesBuffer == nullptr || resultCountFoundDevices == nullptr || sizeFoundDevicesBufferEntities <= 0) { + return X_LINK_PLATFORM_INVALID_PARAMETERS; } - VidPid vidpid{desc.idVendor, desc.idProduct}; + // Get list of usb devices; e.g. size() == 10 when 3 xlink devices attached to three separate USB controllers + // If this is called immediately after usb_boot succeeds, then libusb can complain at loglevel=3 that... + // libusb: warning [init_device] could not get node connection information for device 'USB\VID_03E7&PID_2485\03E72485': [87] The parameter is incorrect. + // libusb: warning [winusb_get_device_list] failed to initialize device 'USB\VID_03E7&PID_2485\03E72485' + // libusb: warning [winusb_get_device_list] could not detect installation state of driver for 'USB\VID_03E7&PID_2485\03E72485': [3758096907] The device instance does not exist in the hardware tree. + const device_list deviceList{context}; + + // Loop over all usb devices, persist devices only if they are known/myriad devices + const std::string requiredPath(deviceRequirements.name); + const std::string requiredMxId(deviceRequirements.mxid); + span foundDevices{foundDevicesBuffer, static_cast(sizeFoundDevicesBufferEntities)}; + auto* foundDevice = foundDevices.begin(); + for (const auto candidateDevice : deviceList) { + // disallow invalid devices https://github.com/libusb/libusb/issues/1287 + assert(candidateDevice); + + // setup device i/o and query device + try { + // Get candidate device descriptor + const auto candidateDescriptor = candidateDevice.get_device_descriptor(); + + // Filter device by known vid/pid pairs + const auto vidpid = std::find(VID_PID_TO_DEVICE_STATE.begin(), VID_PID_TO_DEVICE_STATE.end(), VidPid{candidateDescriptor.idVendor, candidateDescriptor.idProduct}); + if(vidpid == VID_PID_TO_DEVICE_STATE.end()) { + // Not a known vid/pid pair + continue; + } - if(vidPidToDeviceState.count(vidpid) > 0){ - // Device found + // Compare candidate device state with requirement state + const XLinkDeviceState_t state = vidpid->second; + if(deviceRequirements.state != X_LINK_ANY_STATE && state != deviceRequirements.state) { + // Current device doesn't match the requirement state "filter" + continue; + } - // Device status - XLinkError_t status = X_LINK_SUCCESS; + // Get device path + const std::string devicePath = getLibusbDevicePath(candidateDevice); - // 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; - } + // Compare paths. If name (i.e. path) is only a hint then don't filter + if(!deviceRequirements.nameHintOnly) { + if(!requiredPath.empty() && requiredPath != devicePath) { + // 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 + // Get device mxid + std::string candidateMxId; + const libusb_error rc = getLibusbDeviceMxId(state, devicePath, candidateDescriptor, candidateDevice, candidateMxId); + mvLog(MVLOG_DEBUG, "getLibusbDeviceMxId returned: %s and mxid %s", xlink_libusb_strerror(rc), candidateMxId.c_str()); + + // convert device usb status -> xlink status + XLinkError_t status = X_LINK_SUCCESS; + 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; + } - if(!in_deviceRequirements.nameHintOnly){ - std::string requiredName(in_deviceRequirements.name); - if(requiredName.length() > 0 && requiredName != devicePath){ + // Compare with MxId + if(!requiredMxId.empty() && requiredMxId != candidateMxId){ // Current device doesn't match the "filter" continue; } - } - // 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; - } + // TODO(themarpe) - check platform - // compare with MxId - std::string requiredMxId(in_deviceRequirements.mxid); - if(requiredMxId.length() > 0 && requiredMxId != mxId){ - // Current device doesn't match the "filter" + // Everything passed, fillout details of found device + foundDevice->status = status; + foundDevice->platform = X_LINK_MYRIAD_X; + foundDevice->protocol = X_LINK_USB_VSC; + foundDevice->state = state; + foundDevice->nameHintOnly = false; // BUGBUG or should it be the input parameter? + strncpy(foundDevice->name, devicePath.c_str(), sizeof(foundDevice->name)); + strncpy(foundDevice->mxid, candidateMxId.c_str(), sizeof(foundDevice->mxid)); + + // increment to the next available device entry and check if we have enough space + if(++foundDevice == foundDevices.end()) break; + } + catch(const usb_error&) { 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); + // Write the number of found devices to the out parameter + *resultCountFoundDevices = static_cast(foundDevice - foundDevices.begin()); + 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(numDevices)); + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception& e) { + mvLog(MVLOG_ERROR, "Unexpected exception: %s", e.what()); 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; +// Search for usb device by libusb path; returns device if found, otherwise empty device. +// All usb errors are caught and instead of throwing, returns empty device. Other exceptions are thrown. +usb_device acquireDeviceByPath(const char* const path) { + // validate params + if(path == nullptr || *path == '\0') throw std::invalid_argument{"path cannot be null or empty"}; + + try { + // Get list of usb devices + const device_list deviceList{context}; + + // Loop over all usb devices + const std::string requiredPath(path); + for(auto candidateDevice : deviceList) { + // disallow invalid devices https://github.com/libusb/libusb/issues/1287 + assert(candidateDevice); + + // compare device path with required path + if(requiredPath == getLibusbDevicePath(candidateDevice)) { + return candidateDevice; + } } } - - // Free list of usb devices (unref each) - libusb_free_device_list(devs, 1); - - if(!found){ - return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + catch(const usb_error&) { + // Ignore all usb errors } - - return X_LINK_PLATFORM_SUCCESS; + return {}; } - - -std::string getLibusbDevicePath(libusb_device *dev) { - - std::string devicePath = ""; - +std::string getLibusbDevicePath(const usb_device& device) { // 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 ""; + std::string devicePath{std::to_string(device.get_bus_number())}; + + // Get and append all subsequent port numbers + const auto portNumbers = device.get_port_numbers(); + if (portNumbers.empty()) { + // likely a host controller/root hub directly on PCIE on Windows; otherwise shouldn't happen + // https://github.com/libusb/libusb/blob/cc498ded18fb2c6e4506c546d0351c4ae91ef2cc/libusb/core.c#L955 + // Previous code appended only a dot + devicePath += ".0"; } - if(count == 0){ - // Only bus number is available - return devicePath; + else { + // Add port numbers to path separated by a dot + for (const auto number : portNumbers) { + devicePath.append(1, '.').append(std::to_string(number)); + } } + return devicePath; +} - for (int i = 0; i < count - 1; i++){ - devicePath += std::to_string(portNumbers[i]) + "."; - } - devicePath += std::to_string(portNumbers[count - 1]); +// get mxId for device path from cache with thread-safety +inline bool safeGetCachedMxid(const char* devicePath, char* mxId) { + std::lock_guard l(cacheMutex); + return usb_mx_id_cache_get_entry(devicePath, mxId); +} - // Return the device path - return devicePath; +// store mxID for device path to cache with thread-safety +inline int safeStoreCachedMxid(const char* devicePath, const char* mxId) { + std::lock_guard l(cacheMutex); + return usb_mx_id_cache_store_entry(mxId, devicePath); } -libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePath, const libusb_device_descriptor* pDesc, libusb_device *dev, std::string& outMxId) +libusb_error getLibusbDeviceMxId(const XLinkDeviceState_t state, const std::string& devicePath, const libusb_device_descriptor& deviceDescriptor, const usb_device& device, std::string& outMxId) { char mxId[XLINK_MAX_MX_ID_SIZE] = {0}; // Default MXID - empty - outMxId = ""; + outMxId.clear(); // 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); + const bool found = safeGetCachedMxid(devicePath.c_str(), mxId); - if(found){ + 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; + device_handle handle; + libusb_error libusb_rc = LIBUSB_SUCCESS; // Retry getting MX ID for 15ms const std::chrono::milliseconds RETRY_TIMEOUT{15}; // 15ms @@ -317,22 +333,28 @@ libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePat 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){ + if(!handle) { + try { + handle = device.open(); + } + catch(const usb_error& e) { // Some kind of error, either NO_MEM, ACCESS, NO_DEVICE or other - mvLog(MVLOG_DEBUG, "libusb_open: %s", xlink_libusb_strerror(libusb_rc)); + libusb_rc = static_cast(e.code().value()); // 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; + try { + const auto winMxId = getWinUsbMxId({deviceDescriptor.idVendor, deviceDescriptor.idProduct}, device); + if(!winMxId.empty()) { + strncpy(mxId, winMxId.c_str(), sizeof(mxId) - 1); + libusb_rc = LIBUSB_SUCCESS; + break; + } + } + catch(const std::exception&) { + //ignore } } #endif @@ -341,429 +363,254 @@ libusb_error getLibusbDeviceMxId(XLinkDeviceState_t state, std::string devicePat std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } + catch(const std::exception&) { + return LIBUSB_ERROR_OTHER; + } } // 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(state == X_LINK_UNBOOTED) { + try { + // Get configuration first (From OS cache), Check if set configuration call is needed + // TODO consider sharing this whole block of code with openConfigClaimDevice() + const auto active_configuration = handle.get_configuration(); 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; - } + mvLog(MVLOG_DEBUG, "Setting configuration from %d to 1", active_configuration); + handle.set_configuration(1); } - } 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) + handle.set_auto_detach_kernel_driver(true); - // 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)); - } + // Claim interface (as we'll be doing IO on endpoints) + // TODO consider that previous C code logged with (libusb_rc == LIBUSB_ERROR_BUSY ? MVLOG_DEBUG : MVLOG_ERROR) + handle.claim_interface(0); + } + catch(const usb_error& e) { // retry - most likely device busy by another app + libusb_rc = static_cast(e.code().value()); std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); continue; } - - - const int send_ep = 0x01; - int transferred = 0; + catch(const std::exception&) { + return LIBUSB_ERROR_OTHER; + } // /////////////////////// // 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; + // /////////////////////// + static constexpr int send_ep = 0x01; + static constexpr int recv_ep = 0x81; + static constexpr int expectedMxIdReadSize = 9; + std::array receiveBuffer; + try { + // WD Protection & MXID Retrieval Command + handle.bulk_transfer(send_ep, usb_mx_id_get_payload(), usb_mx_id_get_payload_size()); + // MXID Read + handle.bulk_transfer(recv_ep, receiveBuffer); + // WD Protection end + handle.bulk_transfer(send_ep, usb_mx_id_get_payload_end(), usb_mx_id_get_payload_end_size()); } - - // 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()); + catch(const usb_error& e) { // Mark as error and retry - libusb_rc = -1; - // retry + libusb_rc = static_cast(e.code().value()); 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; + receiveBuffer[8] &= 0xF0; // Convert to HEX presentation and store into mx_id for (int i = 0; i < expectedMxIdReadSize; i++) { - sprintf(mxId + 2*i, "%02X", rbuf[i]); + sprintf(mxId + 2*i, "%02X", receiveBuffer[i]); } + libusb_rc = LIBUSB_SUCCESS; + } - // 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 + // when not X_LINK_UNBOOTED state, get mx_id from the device's serial number string descriptor + else { + try { + // TODO refactor try/catch broader + // TODO refactor to save a string directly to outMxId + const auto serialNumber = handle.get_string_descriptor_ascii(deviceDescriptor.iSerialNumber); + serialNumber.copy(mxId, sizeof(mxId) - 1); + mxId[serialNumber.size()] = '\0'; + } + catch(const usb_error& e) { + libusb_rc = static_cast(e.code().value()); std::this_thread::sleep_for(SLEEP_BETWEEN_RETRIES); continue; } - - // Indicate no error - libusb_rc = 0; - + catch(const std::exception& e) { + mvLog(MVLOG_ERROR, "Unexpected exception: %s", e.what()); + return LIBUSB_ERROR_OTHER; + } + libusb_rc = LIBUSB_SUCCESS; } } 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; + return 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){ + const int cacheIndex = safeStoreCachedMxid(devicePath.c_str(), mxId); + if(cacheIndex >= 0){ // debug print - mvLog(MVLOG_DEBUG, "Cached MX ID %s at index %d", mxId, cache_index); + mvLog(MVLOG_DEBUG, "Cached MX ID %s at index %d", mxId, cacheIndex); } else { // debug print mvLog(MVLOG_DEBUG, "Couldn't cache MX ID %s", mxId); } - } - outMxId = std::string(mxId); - return libusb_error::LIBUSB_SUCCESS; - + outMxId = mxId; + return LIBUSB_SUCCESS; } -const char* xlink_libusb_strerror(int x) { +const char* xlink_libusb_strerror(ssize_t 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; +// get usb device handle, set configuration, claim interface, and return the first bulk OUT endpoint +// All usb errors are caught and instead of throwing, return libusb_error codes. Other exceptions are thrown. +static libusb_error openConfigClaimDevice(const usb_device& device, uint8_t& endpoint, device_handle& handle) { + try { + // open usb device and get candidate handle + auto candidate = device.open(); + + // Set configuration to 1; optimize to check if the device is already configured + candidate.set_configuration(1); + + // Set to auto detach & reattach kernel driver, and ignore result (success or not supported) + candidate.set_auto_detach_kernel_driver(true); + + // claim interface 0; it is automatically released when handle is destructed + candidate.claim_interface(0); + + // Get device config descriptor + const auto cdesc = device.get_config_descriptor(0); + + // find the first bulk OUT endpoint, persist its max packet size, then return the endpoint number and candidate handle + const span endpoints{cdesc->interface->altsetting->endpoint, + cdesc->interface->altsetting->bNumEndpoints}; + for(const auto& endpointDesc : endpoints) { + mvLog(MVLOG_DEBUG, "Found EP 0x%02x : max packet size is %u bytes", endpointDesc.bEndpointAddress, endpointDesc.wMaxPacketSize); + if((endpointDesc.bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK + && !(endpointDesc.bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK)) { + endpoint = endpointDesc.bEndpointAddress; + candidate.set_max_packet_size(endpoint, endpointDesc.wMaxPacketSize); + handle = std::move(candidate); + return LIBUSB_SUCCESS; + } } + return LIBUSB_ERROR_ACCESS; } - - // 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((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; - } + catch(const usb_error& e) { + // intentionally only catching usb_error, not std::exception, to avoid catching unrelated exceptions + return static_cast(e.code().value()); } - 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; +// Find device by its libusb path name, open and config, return the first bulk OUT (host to device) endpoint and device handle +// * timeout: milliseconds for the entire operation, not for each individual step, timeout=0 means all steps are only tried once +// * retryOpen: boolean to retry the allocation+claim of usb resources while still within the timeout period +std::pair usbSharedOpen(const char* const devicePathName, const std::chrono::milliseconds timeout, const bool retryOpen = false) { + using std::chrono::milliseconds; + using std::chrono::steady_clock; + // validate parameters + if (devicePathName == nullptr || timeout.count() < 0) throw usb_error(LIBUSB_ERROR_INVALID_PARAM); + // get usb device by its libusb path name + usb_device device; auto t1 = steady_clock::now(); - do { - if(refLibusbDeviceByName(addr, &dev) == X_LINK_PLATFORM_SUCCESS){ - break; - } + while(!(device = acquireDeviceByPath(devicePathName)) && steady_clock::now() - t1 < timeout) { 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; } + if(!device) throw usb_error(LIBUSB_ERROR_NOT_FOUND); - auto t2 = steady_clock::now(); - do { - if((res = usb_open_device(dev, &endpoint, h)) == LIBUSB_SUCCESS){ - break; - } + // open usb device and get first bulk OUT (host to device) endpoint + device_handle handle; + uint8_t endpoint = 0; + libusb_error result = LIBUSB_ERROR_ACCESS; + while((result = openConfigClaimDevice(device, endpoint, handle)) != LIBUSB_SUCCESS && steady_clock::now() - t1 < timeout && retryOpen) { 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; + if(result != LIBUSB_SUCCESS) throw usb_error(result); + return {std::move(handle), endpoint}; } +// opens usb device by its libusb path name with retries, sends the boot binary, and returns the result +// a success result only means the boot binary was sent, not that the device booted successfully +// note: result is an xLinkPlatformErrorCode_t cast to an int +int usb_boot(const char* addr, const void* mvcmd, unsigned size) noexcept { + try { + // open usb device; get handle to device and first bulk OUT (host to device) endpoint + const auto handleEndpoint = usbSharedOpen(addr, DEFAULT_CONNECT_TIMEOUT, true); - -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) { + // transfer boot binary to device + handleEndpoint.first.bulk_transfer(handleEndpoint.second, mvcmd, size); 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; + } catch(const usb_error& e) { + return static_cast(parseLibusbError(static_cast(e.code().value()))); + } catch(const std::exception&) { + return static_cast(X_LINK_PLATFORM_ERROR); } } +// tries one time to open a usb device by its libusb path name and returns a device handle +device_handle usbLinkOpen(const char* const path) { + return usbSharedOpen(path, DEFAULT_OPEN_TIMEOUT, false).first; +} -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) { +xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* const pathName) noexcept { + try { + // Get device by path + const auto device = acquireDeviceByPath(pathName); + if(!device) return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + + // Open device to get an i/o device handle + // Make control transfer and take no action if errors occur + device.open().control_transfer(bootBootloaderPacket.requestType, // bmRequestType: device-directed + bootBootloaderPacket.request, // bRequest: custom + bootBootloaderPacket.value, // wValue: custom + bootBootloaderPacket.index, // wIndex + nullptr, // data pointer + 0, // data size + std::chrono::milliseconds(1000)); + return X_LINK_PLATFORM_SUCCESS; + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception&) { return X_LINK_PLATFORM_ERROR; } - - return X_LINK_PLATFORM_SUCCESS; } -void usbLinkClose(libusb_device_handle *f) +// deprecated since apis like usbPlatformClose() use RAII +void usbLinkClose(libusb_device_handle *h) { - libusb_release_interface(f, 0); - libusb_close(f); + // BUGBUG debugger correctly shows ref=1 when entering this function. + // when env LIBUSB_DEBUG=3, on app exit usually get... + // libusb: warning [libusb_exit] device 2.0 still referenced + // libusb: warning [libusb_exit] device 3.0 still referenced + if (h == nullptr) return; + libusb_release_interface(h, 0); + libusb_close(h); } - - -int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) +int usbPlatformConnect(const char* const devPathRead, const char* const devPathWrite, void **fd) noexcept { #if (!defined(USE_USB_VSC)) - #ifdef USE_LINK_JTAG +#ifdef USE_LINK_JTAG struct sockaddr_in serv_addr; usbFdWrite = socket(AF_INET, SOCK_STREAM, 0); usbFdRead = socket(AF_INET, SOCK_STREAM, 0); @@ -777,7 +624,7 @@ int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void * if (connect(usbFdWrite, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) { - mvLog(MVLOG_ERROR, "connect(usbFdWrite,...) returned < 0\n"); + mvLog(MVLOG_ERROR, "connect(usbFdWrite,...) returned < 0"); if (usbFdRead >= 0) close(usbFdRead); if (usbFdWrite >= 0) @@ -849,32 +696,37 @@ int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void * } return 0; #endif /*USE_LINK_JTAG*/ -#else - libusb_device_handle* usbHandle = nullptr; - xLinkPlatformErrorCode_t ret = usbLinkOpen(devPathWrite, usbHandle); +#else + (void)devPathRead; + try { + // make device_handle safely on the heap + auto handle = std::make_unique(usbLinkOpen(devPathWrite)); + + // store device_handle in fd lookup container + // (as file descriptors are reused and can cause a clash with lookups between scheduler and link). + auto* const newKey = createPlatformDeviceFdKey(handle.get()); + if (newKey == nullptr) { + // failed transfer of ownership to fd lookup container + // handle will close on destruction + return X_LINK_PLATFORM_ERROR; + } - if (ret != X_LINK_PLATFORM_SUCCESS) - { - /* could fail due to port name change */ - return ret; + // successful transfer of ownership to fd lookup container + std::ignore = handle.release(); + *fd = newKey; + return X_LINK_PLATFORM_SUCCESS; + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception&) { + return X_LINK_PLATFORM_ERROR; } - - // 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) -{ - +int usbPlatformClose(void* const fdKey) noexcept { #ifndef USE_USB_VSC - #ifdef USE_LINK_JTAG +#ifdef USE_LINK_JTAG /*Nothing*/ #else if (usbFdRead != -1){ @@ -886,77 +738,85 @@ int usbPlatformClose(void *fdKey) usbFdWrite = -1; } #endif /*USE_LINK_JTAG*/ -#else + return -1; - 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); +#else + try { + // extract and transfer ownership of device_handle from the fd lookup container + std::unique_ptr handle(static_cast(extractPlatformDeviceFdKey(fdKey))); + if (!handle) { + mvLog(MVLOG_FATAL, "Cannot find and destroy USB Handle by key: %" PRIxPTR, (uintptr_t)fdKey); + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } - if(destroyPlatformDeviceFdKey(fdKey)){ - mvLog(MVLOG_FATAL, "Cannot destroy USB Handle key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; + // close device_handle and release resources normally on destruction + // therefore usbLinkClose(handle) is not needed + return X_LINK_PLATFORM_SUCCESS; + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception& e) { + mvLog(MVLOG_ERROR, "Unexpected exception: %s", e.what()); + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; } - #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); - } +// Boot it via USB +int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length) noexcept { + const int rc = usb_boot(deviceDesc->name, firmware, (unsigned)length); + if(!rc) mvLog(MVLOG_DEBUG, "Boot binary successfully sent to device address %s", deviceDesc->name); return rc; } - - -int usb_read(libusb_device_handle *f, void *data, size_t size) -{ - const int chunk_size = DEFAULT_CHUNKSZ; - while(size > 0) +// transform a libusb error code into a XLinkPlatformErrorCode_t +xLinkPlatformErrorCode_t parseLibusbError(libusb_error rc) noexcept { + xLinkPlatformErrorCode_t platformResult{X_LINK_PLATFORM_SUCCESS}; + switch (rc) { - 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); - if(rc) - return rc; - data = ((char *)data) + bt; - size -= bt; + case LIBUSB_SUCCESS: + break; + case LIBUSB_ERROR_INVALID_PARAM: + platformResult = X_LINK_PLATFORM_INVALID_PARAMETERS; + break; + case LIBUSB_ERROR_ACCESS: + platformResult = X_LINK_PLATFORM_INSUFFICIENT_PERMISSIONS; + break; + case LIBUSB_ERROR_NO_DEVICE: + case LIBUSB_ERROR_NOT_FOUND: + platformResult = X_LINK_PLATFORM_DEVICE_NOT_FOUND; + break; + case LIBUSB_ERROR_BUSY: + platformResult = X_LINK_PLATFORM_DEVICE_BUSY; + break; + case LIBUSB_ERROR_TIMEOUT: + platformResult = X_LINK_PLATFORM_TIMEOUT; + break; + case LIBUSB_ERROR_IO: + case LIBUSB_ERROR_OVERFLOW: + case LIBUSB_ERROR_PIPE: + case LIBUSB_ERROR_INTERRUPTED: + case LIBUSB_ERROR_NO_MEM: + case LIBUSB_ERROR_NOT_SUPPORTED: + case LIBUSB_ERROR_OTHER: + default: + platformResult = X_LINK_PLATFORM_ERROR; + break; } - return 0; + return platformResult; } -int usb_write(libusb_device_handle *f, const void *data, size_t size) -{ - 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, (unsigned char *)data, ss, &bt, XLINK_USB_DATA_TIMEOUT); - if(rc) - return rc; - data = (char *)data + bt; - size -= bt; - } - return 0; +inline intmax_t usb_read(const device_handle& f, void* const data, const size_t size) { + return f.bulk_transfer(USB_ENDPOINT_IN, data, size).second; +} + +inline intmax_t usb_write(const device_handle& f, const void* const data, const size_t size) { + return f.bulk_transfer(USB_ENDPOINT_OUT, data, size).second; } -int usbPlatformRead(void* fdKey, void* data, int size) +int usbPlatformRead(void* const fdKey, void* data, int size) noexcept { - int rc = 0; #ifndef USE_USB_VSC + int rc = 0; int nread = 0; #ifdef USE_LINK_JTAG while (nread < size){ @@ -992,24 +852,31 @@ int usbPlatformRead(void* fdKey, void* data, int size) } } #endif /*USE_LINK_JTAG*/ +return rc; + #else + try { + // get non-owning device_handle* from the fd lookup container + auto* const handle = static_cast(getPlatformDeviceFdFromKeySimple(fdKey)); + if (handle == nullptr) { + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; + usb_read(*handle, data, size); + return X_LINK_PLATFORM_SUCCESS; + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception&) { + return X_LINK_PLATFORM_ERROR; } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_read(usbHandle, data, size); #endif /*USE_USB_VSC*/ - return rc; } -int usbPlatformWrite(void *fdKey, void *data, int size) +int usbPlatformWrite(void* const fdKey, void *data, int size) noexcept { - int rc = 0; #ifndef USE_USB_VSC + int rc = 0; int byteCount = 0; #ifdef USE_LINK_JTAG while (byteCount < size){ @@ -1048,18 +915,25 @@ int usbPlatformWrite(void *fdKey, void *data, int size) } } #endif /*USE_LINK_JTAG*/ + return rc; + #else + try { + // get non-owning device_handle* from the fd lookup container + auto* const handle = static_cast(getPlatformDeviceFdFromKeySimple(fdKey)); + if (handle == nullptr) { + mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); + return X_LINK_PLATFORM_DEVICE_NOT_FOUND; + } - void* tmpUsbHandle = NULL; - if(getPlatformDeviceFdFromKey(fdKey, &tmpUsbHandle)){ - mvLog(MVLOG_FATAL, "Cannot find file descriptor by key: %" PRIxPTR, (uintptr_t) fdKey); - return -1; + usb_write(*handle, data, size); + return X_LINK_PLATFORM_SUCCESS; + } catch(const usb_error& e) { + return parseLibusbError(static_cast(e.code().value())); + } catch(const std::exception&) { + return X_LINK_PLATFORM_ERROR; } - libusb_device_handle* usbHandle = (libusb_device_handle*) tmpUsbHandle; - - rc = usb_write(usbHandle, data, size); #endif /*USE_USB_VSC*/ - return rc; } #ifdef _WIN32 @@ -1073,8 +947,8 @@ int usbPlatformWrite(void *fdKey, void *data, int size) // 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 {}; +std::string getWinUsbMxId(const VidPid& vidpid, const usb_device& device) { + if (!device) return {}; // init device info vars HDEVINFO hDevInfoSet; @@ -1082,7 +956,7 @@ std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { 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); + hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_HOST_CONTROLLER, nullptr, nullptr, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); if (hDevInfoSet == INVALID_HANDLE_VALUE) { return {}; } @@ -1092,7 +966,7 @@ std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { 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)) { + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, nullptr, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), nullptr)) { continue; } @@ -1104,25 +978,25 @@ std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { 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 + // Free device 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); + hDevInfoSet = SetupDiGetClassDevsA(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, 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 goalPath{getLibusbDevicePath(device)}; 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)) { + if(!SetupDiGetDeviceInstanceIdA(hDevInfoSet, &devInfoData, (PSTR)instanceId, sizeof(instanceId), nullptr)) { continue; } @@ -1140,7 +1014,7 @@ std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { // 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)) { + if (!SetupDiGetDeviceRegistryPropertyA(hDevInfoSet, &devInfoData, SPDRP_LOCATION_PATHS, nullptr, (PBYTE)locationPaths.c_str(), (DWORD)locationPaths.size(), nullptr)) { continue; } @@ -1183,7 +1057,7 @@ std::string getWinUsbMxId(VidPid vidpid, libusb_device* dev) { } } - // Free dev info + // Free device info SetupDiDestroyDeviceInfoList(hDevInfoSet); // Return deviceId if found diff --git a/src/pc/protocols/usb_host.h b/src/pc/protocols/usb_host.h index 0ec906f..d7f79c3 100644 --- a/src/pc/protocols/usb_host.h +++ b/src/pc/protocols/usb_host.h @@ -3,7 +3,10 @@ // #ifdef __cplusplus +#define NOEXCEPT noexcept extern "C" { +#else +#define NOEXCEPT #endif #include @@ -35,19 +38,18 @@ typedef enum usbBootError { USB_BOOT_TIMEOUT } usbBootError_t; -int usbInitialize(void* options); -int usbInitialize_customdir(void** hContext); +int usbInitialize(void* options) NOEXCEPT; -int usb_boot(const char *addr, const void *mvcmd, unsigned size); +int usb_boot(const char *addr, const void *mvcmd, unsigned size) NOEXCEPT; 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 usbPlatformClose(void *fd); -int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length); +xLinkPlatformErrorCode_t usbLinkBootBootloader(const char* path) NOEXCEPT; +int usbPlatformConnect(const char *devPathRead, const char *devPathWrite, void **fd) NOEXCEPT; +int usbPlatformClose(void *fd) NOEXCEPT; +int usbPlatformBootFirmware(const deviceDesc_t* deviceDesc, const char* firmware, size_t length) NOEXCEPT; -int usbPlatformRead(void *fd, void *data, int size); -int usbPlatformWrite(void *fd, void *data, int size); +int usbPlatformRead(void *fd, void *data, int size) NOEXCEPT; +int usbPlatformWrite(void *fd, void *data, int size) NOEXCEPT; #ifdef __cplusplus } diff --git a/src/pc/protocols/usb_mx_id.cpp b/src/pc/protocols/usb_mx_id.cpp index ef3d13f..19f6ad8 100644 --- a/src/pc/protocols/usb_mx_id.cpp +++ b/src/pc/protocols/usb_mx_id.cpp @@ -9,10 +9,8 @@ #endif #include "string.h" -#include -#include -static double steady_seconds() +static double steady_seconds() noexcept { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -22,7 +20,7 @@ static double steady_seconds() #define ADDRESS_BUFF_SIZE 35 // Commands and executable to read serial number from unbooted MX device -static const uint8_t mxid_read_cmd[] = { +static constexpr uint8_t mxid_read_cmd[] = { // Header 0x4d, 0x41, 0x32, 0x78, // WD Protection - start @@ -54,26 +52,26 @@ static const uint8_t mxid_read_cmd[] = { }; // WD Protection - end -static const uint8_t mxid_read_end_cmd[] = { +static constexpr uint8_t mxid_read_end_cmd[] = { 0x9A, 0xA8, 0x00, 0x32, 0x20, 0xAD, 0xDE, 0xD0, 0xF1, 0x9A, 0xA4, 0x00, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x9A, 0xA8, 0x00, 0x32, 0x20, 0xAD, 0xDE, 0xD0, 0xF1, 0x9A, 0x9C, 0x00, 0x32, 0x20, 0xFF, 0xFF, 0xFF, 0xFF, }; -const uint8_t* usb_mx_id_get_payload() { +const uint8_t* usb_mx_id_get_payload() noexcept { return mxid_read_cmd; } -int usb_mx_id_get_payload_size() { +int usb_mx_id_get_payload_size() noexcept { return sizeof(mxid_read_cmd); } -const uint8_t* usb_mx_id_get_payload_end() { +const uint8_t* usb_mx_id_get_payload_end() noexcept { return mxid_read_end_cmd; } -int usb_mx_id_get_payload_end_size() { +int usb_mx_id_get_payload_end_size() noexcept { return sizeof(mxid_read_end_cmd); } @@ -87,10 +85,8 @@ typedef struct { double timestamp; } MxIdListEntry; static MxIdListEntry list_mx_id[MX_ID_LIST_SIZE] = { 0 }; -static bool list_initialized = false; -static std::mutex list_mutex; -static bool list_mx_id_is_entry_valid(MxIdListEntry* entry) { +static bool list_mx_id_is_entry_valid(MxIdListEntry* entry) noexcept { if (entry == NULL) return false; if (entry->compat_name[0] == 0 || steady_seconds() - entry->timestamp >= LIST_ENTRY_TIMEOUT_SEC) return false; @@ -98,19 +94,16 @@ static bool list_mx_id_is_entry_valid(MxIdListEntry* entry) { return true; } -void usb_mx_id_cache_init() { +void usb_mx_id_cache_init() noexcept { // initialize list - if (!list_initialized) { - for (int i = 0; i < MX_ID_LIST_SIZE; i++) { - list_mx_id[i].timestamp = 0; - list_mx_id[i].mx_id[0] = 0; - list_mx_id[i].compat_name[0] = 0; - } - list_initialized = true; + for (int i = 0; i < MX_ID_LIST_SIZE; i++) { + list_mx_id[i].timestamp = 0; + list_mx_id[i].mx_id[0] = 0; + list_mx_id[i].compat_name[0] = 0; } } -int usb_mx_id_cache_store_entry(const char* mx_id, const char* compat_addr) { +int usb_mx_id_cache_store_entry(const char* mx_id, const char* compat_addr) noexcept { for (int i = 0; i < MX_ID_LIST_SIZE; i++) { // If entry an invalid (timedout - default) if (!list_mx_id_is_entry_valid(&list_mx_id[i])) { @@ -123,7 +116,7 @@ int usb_mx_id_cache_store_entry(const char* mx_id, const char* compat_addr) { return -1; } -bool usb_mx_id_cache_get_entry(const char* compat_addr, char* mx_id) { +bool usb_mx_id_cache_get_entry(const char* compat_addr, char* mx_id) noexcept { for (int i = 0; i < MX_ID_LIST_SIZE; i++) { // If entry still valid if (list_mx_id_is_entry_valid(&list_mx_id[i])) { diff --git a/src/pc/protocols/usb_mx_id.h b/src/pc/protocols/usb_mx_id.h index 8e47e34..7f2a7ee 100644 --- a/src/pc/protocols/usb_mx_id.h +++ b/src/pc/protocols/usb_mx_id.h @@ -2,23 +2,27 @@ #define _USB_MX_ID_H_ #ifdef __cplusplus +#define XLINK_NOEXCEPT noexcept extern "C" { +#else +#define XLINK_NOEXCEPT #endif #include #include -const uint8_t* usb_mx_id_get_payload(); -int usb_mx_id_get_payload_size(); -const uint8_t* usb_mx_id_get_payload_end(); -int usb_mx_id_get_payload_end_size(); +const uint8_t* usb_mx_id_get_payload() XLINK_NOEXCEPT; +int usb_mx_id_get_payload_size() XLINK_NOEXCEPT; +const uint8_t* usb_mx_id_get_payload_end() XLINK_NOEXCEPT; +int usb_mx_id_get_payload_end_size() XLINK_NOEXCEPT; -int usb_mx_id_cache_store_entry(const char* mx_id, const char* compat_addr); -bool usb_mx_id_cache_get_entry(const char* compat_addr, char* mx_id); -void usb_mx_id_cache_init(); +int usb_mx_id_cache_store_entry(const char* mx_id, const char* compat_addr) XLINK_NOEXCEPT; +bool usb_mx_id_cache_get_entry(const char* compat_addr, char* mx_id) XLINK_NOEXCEPT; +void usb_mx_id_cache_init() XLINK_NOEXCEPT; #ifdef __cplusplus } #endif +#undef XLINK_NOEXCEPT #endif \ No newline at end of file diff --git a/src/pc/protocols/wrap_libusb.cpp b/src/pc/protocols/wrap_libusb.cpp new file mode 100755 index 0000000..ef7a9b6 --- /dev/null +++ b/src/pc/protocols/wrap_libusb.cpp @@ -0,0 +1,38 @@ +/* + dp::libusb - C++ wrapper for libusb-1.0 (focused on use with the XLink protocol) + + Copyright 2023 Dale Phurrough + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// project +#define MVLOG_UNIT_NAME xLinkUsb +#include "XLink/XLinkLog.h" + +// libraries +#ifdef XLINK_LIBUSB_LOCAL +#include +#else +#include +#endif + +#include "wrap_libusb.hpp" + +std::mutex dp::libusb::device_list::mtx; + +// definition outside class to workaround Clang constexpr failures +constexpr int dp::libusb::device_handle::DEFAULT_CHUNK_SIZE; +constexpr int dp::libusb::device_handle::DEFAULT_CHUNK_SIZE_USB1; +constexpr decltype(libusb_endpoint_descriptor::wMaxPacketSize) dp::libusb::device_handle::DEFAULT_MAX_PACKET_SIZE; +constexpr std::array dp::libusb::device_handle::DEFAULT_MAX_PACKET_ARRAY; diff --git a/src/pc/protocols/wrap_libusb.hpp b/src/pc/protocols/wrap_libusb.hpp new file mode 100755 index 0000000..0118ba0 --- /dev/null +++ b/src/pc/protocols/wrap_libusb.hpp @@ -0,0 +1,972 @@ +/* + dp::libusb - C++ wrapper for libusb-1.0 (focused on use with the XLink protocol) + + Copyright 2023 Dale Phurrough + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _WRAP_LIBUSB_HPP_ +#define _WRAP_LIBUSB_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "wrap_libusb_details.hpp" + +/// @brief dp namespace +namespace dp { + +/////////////////////////////// +// Helper functions and macros +/////////////////////////////// + +/// @brief unique_resource_ptr deleter used to call native API to release resources +/// @tparam Resource native API resource type; the type of the resource disposed/released by this deleter +/// @tparam Dispose native API function used to release the resource; must be a function pointer +template +struct unique_resource_deleter { + inline void operator()(Resource* const ptr) noexcept { + if (ptr != nullptr) + Dispose(ptr); + } +}; + +/// @brief base unique_resource_ptr class to wrap native API resources; behavior is undefined +/// using operator->() or operator*() when get() == nullptr +/// @tparam Resource native API resource type; the type of the resource managed by this unique_resource_ptr +/// @tparam Dispose native API function used to release the resource; must be a function pointer +template +class unique_resource_ptr : public std::unique_ptr> { +private: + using _base = std::unique_ptr>; + +public: + // inherit base constructors, long form due to Clang fail using _base + using std::unique_ptr>::unique_ptr; + + // delete base unique_resource_ptr constructors that would conflict with ref counts + unique_resource_ptr(typename _base::pointer, const typename _base::deleter_type &) = delete; + unique_resource_ptr(typename _base::pointer, typename _base::deleter_type &&) = delete; +}; + + +/// @brief libusb resources, structs, and functions with RAII resource management and exceptions +namespace libusb { + +/////////////////////////////////// +// libusb error exception wrappers +/////////////////////////////////// + +/// @brief exception error class for libusb errors +class usb_error : public std::system_error { +public: + explicit usb_error(int libusbErrorCode) noexcept : + std::system_error{std::error_code(libusbErrorCode, std::system_category()), libusb_strerror(libusbErrorCode)} {} + usb_error(int libusbErrorCode, const std::string& what) noexcept : + std::system_error{std::error_code(libusbErrorCode, std::system_category()), what} {} + usb_error(int libusbErrorCode, const char* what) noexcept : + std::system_error{std::error_code(libusbErrorCode, std::system_category()), what} {} +}; + +/// @brief exception error class for libusb transfer errors +class transfer_error : public usb_error { +public: + explicit transfer_error(int libusbErrorCode, intmax_t transferred) noexcept : + usb_error{libusbErrorCode}, transferred{transferred} {} + transfer_error(int libusbErrorCode, const std::string& what, intmax_t transferred) noexcept : + usb_error{libusbErrorCode, what}, transferred{transferred} {} + transfer_error(int libusbErrorCode, const char* what, intmax_t transferred) noexcept : + usb_error{libusbErrorCode, what}, transferred{transferred} {} +private: + intmax_t transferred{}; // number of bytes transferred +}; + +// tag dispatch for throwing or not throwing exceptions +inline void throw_conditional_usb_error(int libusbErrorCode, std::true_type) noexcept(false) { + throw usb_error(libusbErrorCode); +} +inline void throw_conditional_usb_error(int, std::false_type) noexcept(true) { + // do nothing +} +inline void throw_conditional_transfer_error(int libusbErrorCode, intmax_t transferred, std::true_type) noexcept(false) { + throw transfer_error(libusbErrorCode, transferred); +} +inline void throw_conditional_transfer_error(int, intmax_t, std::false_type) noexcept(true) { + // do nothing +} + +/// @brief template function that can call any libusb function passed to it +/// @tparam Loglevel log level used when errors occur +/// @tparam Throw throw exceptions on errors or only return error codes; when false, return codes must be handled by the caller +/// @tparam Func auto-deduced libusb function pointer type +/// @tparam ...Args auto-deduced variadic template parameter pack for the function parameter(s) +/// @param funcWithin name of the function calling this function; prepended to the log message +/// @param lineNumber line number of the function calling this function; prepended to the log message +/// @param func libusb function pointer +/// @param ...args libusb function parameter(s) +/// @return return code of the libusb function +template +inline auto call_log_throw(const char* funcWithin, const int lineNumber, Func&& func, Args&&... args) noexcept(!Throw) + -> decltype(func(std::forward(args)...)) { + const auto rcNum = func(std::forward(args)...); + if (rcNum < 0) { + logprintf(MVLOGLEVEL(MVLOG_UNIT_NAME), + Loglevel, + funcWithin, + lineNumber, + "dp::libusb failed %s(): %s", + funcWithin, + libusb_strerror(static_cast(rcNum))); + throw_conditional_usb_error(static_cast(rcNum), std::integral_constant{}); + } + return rcNum; +} +#define CALL_LOG_ERROR_THROW(...) call_log_throw(__func__, __LINE__, __VA_ARGS__) + +/////////////////////////////// +// libusb resource wrappers +/////////////////////////////// + +/// @brief RAII wrapper for libusb_context; automatically calls libusb_exit() on destruction +using usb_context = unique_resource_ptr; + +/// @brief RAII wrapper for libusb_device; automatically calls libusb_unref_device() on destruction +class usb_device; + +/// @brief RAII wrapper for libusb_config_descriptor; automatically calls libusb_free_config_descriptor() on destruction +class config_descriptor : public unique_resource_ptr { +public: + using unique_resource_ptr::unique_resource_ptr; + + config_descriptor(libusb_device* dev, uint8_t configIndex) noexcept(false) { + CALL_LOG_ERROR_THROW(libusb_get_config_descriptor, dev, configIndex, out_param(*this)); + } +}; + +/// @brief RAII wrapper for libusb_device_handle to allow I/O on device; automatically calls libusb_close() on destruction +/// @note Can also create with usb_device::open() +class device_handle : public unique_resource_ptr { +private: + using _base = unique_resource_ptr; + + static constexpr int DEFAULT_CHUNK_SIZE = 1024 * 1024; // must be multiple of endpoint max packet size + static constexpr int DEFAULT_CHUNK_SIZE_USB1 = 64; // must be multiple of endpoint max packet size + static constexpr decltype(libusb_endpoint_descriptor::wMaxPacketSize) DEFAULT_MAX_PACKET_SIZE = 512; // USB 1.1 = 64, USB 2.0 = 512, USB 3+ = 1024 + static constexpr std::array DEFAULT_MAX_PACKET_ARRAY{ + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, DEFAULT_MAX_PACKET_SIZE, + }; + + int chunkSize = DEFAULT_CHUNK_SIZE; // persisted for quick access by transfer methods + std::array maxPacketSize = DEFAULT_MAX_PACKET_ARRAY; + std::vector claimedInterfaces; + + // exchange DEFAULT_MAX_PACKET_ARRAY -> val -> return + static decltype(maxPacketSize) exchange_maxPacketSize(decltype(maxPacketSize)& val) { + auto old = std::move(val); + val = DEFAULT_MAX_PACKET_ARRAY; + return old; + } + + // get chunk size in bytes given device bcdUSB version + static int get_chunk_size(const uint16_t bcdUSB) noexcept { + return bcdUSB >= 0x200 ? DEFAULT_CHUNK_SIZE : DEFAULT_CHUNK_SIZE_USB1; + } + + // given an endpoint address, return true if that address is for an IN endpoint, false for OUT + // endpoint bit 7 = 0 is OUT host to device, bit 7 = 1 is IN device to host (Ignored for Control Endpoints) + static bool is_direction_in(uint8_t endpoint) noexcept { + // alternate calculation: (endpoint >> 7) & 0x01 + return static_cast(endpoint & 0x80); + } + +public: + //////////////// + // constructors + //////////////// + + // inherit base constructors, long form due to Clang fail using _base + using unique_resource_ptr::unique_resource_ptr; + + /// @brief Create a device_handle from a raw libusb_device_handle* + /// @param handle raw libusb_device_handle* to wrap and manage ownership + /// @note This constructor will not manage previously claimed interfaces and + /// will default to DEFAULT_MAX_PACKET_SIZE for all endpoints + explicit device_handle(pointer handle) noexcept(false) + : _base{handle}, chunkSize{handle ? get_chunk_size(get_device_descriptor().bcdUSB) : DEFAULT_CHUNK_SIZE} {} + + // move constructor + device_handle(device_handle &&other) noexcept : + _base{std::move(other)}, + chunkSize{std::exchange(other.chunkSize, DEFAULT_CHUNK_SIZE)}, + maxPacketSize{exchange_maxPacketSize(other.maxPacketSize)}, + claimedInterfaces{std::move(other.claimedInterfaces)} + {} + + // move assign + device_handle &operator=(device_handle &&other) noexcept { + if (this != &other) { + _base::operator=(std::move(other)); + chunkSize = std::exchange(other.chunkSize, DEFAULT_CHUNK_SIZE); + maxPacketSize = exchange_maxPacketSize(other.maxPacketSize); + claimedInterfaces = std::move(other.claimedInterfaces); + } + return *this; + } + + // destructor + ~device_handle() noexcept { + reset(); + } + + /// @brief Create a device_handle from a raw libusb_device* + /// @param device raw libusb_device* from which to open a new handle and manage its ownership + explicit device_handle(libusb_device* device) noexcept(false) { + if(device == nullptr) throw std::invalid_argument("device == nullptr"); + CALL_LOG_ERROR_THROW(libusb_open, device, out_param(static_cast<_base&>(*this))); + + // cache the device's bcdUSB version for use in transfer methods + // call libusb_get_device_descriptor() directly since already have the raw libusb_device* + libusb_device_descriptor descriptor{}; + CALL_LOG_ERROR_THROW(libusb_get_device_descriptor, device, &descriptor); + chunkSize = get_chunk_size(descriptor.bcdUSB); + } + + /// @brief Create a device_handle from a platform-specific system device handle + /// @param ctx raw libusb_context* to use for wrapping the platform-specific system device handle + /// @param sysDevHandle platform-specific system device handle to wrap + /// @note Never use usb_device::open() or libusb_open() on this wrapped handle's underlying USB device + device_handle(libusb_context* ctx, intptr_t sysDevHandle) noexcept(false) { + if(ctx == nullptr || sysDevHandle == 0) throw std::invalid_argument("ctx == nullptr || sysDevHandle == 0"); + CALL_LOG_ERROR_THROW(libusb_wrap_sys_device, ctx, sysDevHandle, out_param(static_cast<_base&>(*this))); + + // cache the device's bcdUSB version + chunkSize = get_chunk_size(get_device_descriptor().bcdUSB); + } + + /// @brief Create a device_handle from a usb_device wrapper + /// @param device usb_device from which to open a new handle and manage its ownership + explicit device_handle(const usb_device& device) noexcept(noexcept(device_handle{std::declval()})); + + /////////// + // methods + /////////// + + /// @brief Release handle and its claimed interfaces, then replace with ptr + /// @param ptr raw resource pointer used to replace the currently managed resource + /// @note This method does not automatically manage previously claimed interfaces of ptr and + /// will default to DEFAULT_MAX_PACKET_SIZE for all endpoints of ptr + void reset(pointer ptr = pointer{}) noexcept { + // release all claimed interfaces and resources + for (const auto interfaceNumber : claimedInterfaces) { + call_log_throw(__func__, __LINE__, libusb_release_interface, get(), interfaceNumber); + } + _base::reset(ptr); + + // reset defaults + // do not know what interfaces or endpoints are in use, therefore don't know their max packet size + chunkSize = ptr == nullptr ? DEFAULT_CHUNK_SIZE : get_chunk_size(get_device_descriptor().bcdUSB); + maxPacketSize = DEFAULT_MAX_PACKET_ARRAY; + claimedInterfaces.clear(); + } + + /// @brief Release ownership of the managed device_handle and all device interfaces + /// @return raw libusb_device_handle* that was managed by this device_handle + /// @note Caller is responsible for calling libusb_release_interface() and libusb_close() + device_handle::pointer release() noexcept { + chunkSize = DEFAULT_CHUNK_SIZE; + maxPacketSize = DEFAULT_MAX_PACKET_ARRAY; + claimedInterfaces.clear(); + return _base::release(); + } + + /// @brief Get a usb_device for the device_handle's underlying USB device + /// @return usb_device for the device_handle's underlying USB device + usb_device get_device() const noexcept; + + /// @brief Claim a USB interface on the device_handle's device + /// @param interfaceNumber USB interface number to claim + /// @note Repeat claims of an interface with libusb are generally allowed; the interface + /// will be released when the device_handle is destroyed + void claim_interface(int interfaceNumber) noexcept(false) { + if(std::find(claimedInterfaces.begin(), claimedInterfaces.end(), interfaceNumber) != claimedInterfaces.end()) return; + CALL_LOG_ERROR_THROW(libusb_claim_interface, get(), interfaceNumber); + claimedInterfaces.emplace_back(interfaceNumber); + } + + /// @brief Release a USB interface on the device_handle's device + /// @param interfaceNumber USB interface number to release + void release_interface(int interfaceNumber) noexcept(false) { + auto candidate = std::find(claimedInterfaces.begin(), claimedInterfaces.end(), interfaceNumber); + if (candidate == claimedInterfaces.end()) + return; + CALL_LOG_ERROR_THROW(libusb_release_interface, get(), interfaceNumber); + claimedInterfaces.erase(candidate); + } + + /// @brief Get the active configuration value of the device_handle's device + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @return active configuration value of the device_handle's device; error returns libusb_error cast to int + template + int get_configuration() const noexcept(!Throw) { + int configuration{}; + const int rc = call_log_throw(__func__, __LINE__, libusb_get_configuration, get(), &configuration); + return rc >= LIBUSB_SUCCESS ? configuration : rc; + } + + /// @brief Set the active configuration of the device_handle's device + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @tparam Force force configuration to be set even if it is already active; may cause a lightweight device reset + /// @param configuration value of the configuration to set; put the device in unconfigured state with -1 + /// @return libusb_error result code + /// @note Recommended to set the configuration before claiming interfaces + template + libusb_error set_configuration(int configuration) noexcept(!Throw) { + if(!Force) { + const auto active = get_configuration(); + if(active == configuration) + return LIBUSB_SUCCESS; + mvLog(MVLOG_DEBUG, "Setting configuration from %d to %d", active, configuration); + } + return static_cast(call_log_throw(__func__, __LINE__, libusb_set_configuration, get(), configuration)); + } + + // wrapper for libusb_get_string_descriptor_ascii() + // return string is size of actual number of ascii chars in descriptor + + /// @brief Get a string descriptor from the device_handle's device and convert to ASCII + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return empty string + /// @param descriptorIndex index of the string descriptor to get + /// @return string descriptor from the device_handle's device converted to ASCII; empty on error + template + std::string get_string_descriptor_ascii(uint8_t descriptorIndex) const noexcept(!Throw) { + // String descriptors use UNICODE UTF16LE encodings where a bLength byte field declares the sizeBytes of the string + 2 + // and the string is not NULL-terminated. However, internet searches show some devices do null terminate. + // The libusb api converts to ASCII + // Therefore, the max ascii string length is: (bLength - 2) / 2 = (255 - 2) / 2 = 126.5 + std::string descriptor(127, 0); + const auto result = call_log_throw( + __func__, __LINE__, libusb_get_string_descriptor_ascii, get(), descriptorIndex, (unsigned char*)(descriptor.data()), 127); + if (Throw || result >= 0) { + // when throwing enabled, then throw occurs on negative/error results before this resize(), + // so don't need to check result and compiler can optimize this away. + // when throwing disabled, then resize on non-error + descriptor.resize(result); + } + else { + // when throwing disabled, then return empty string on error + descriptor.clear(); + } + return descriptor; + } + + /// @brief Set whether the device_handle's device should automatically detach the kernel driver when claiming interfaces + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @param enable true to automatically detach kernel driver when claiming interface then attach it when releasing interface + /// @return libusb_error result code + template + libusb_error set_auto_detach_kernel_driver(bool enable) noexcept(!Throw) { + return static_cast(call_log_throw(__func__, __LINE__, libusb_set_auto_detach_kernel_driver, get(), enable)); + } + + /// @brief Set the maximum packet size for an endpoint + /// @param endpoint USB endpoint address + /// @param size maximum packet size for the endpoint + void set_max_packet_size(uint8_t endpoint, decltype(libusb_endpoint_descriptor::wMaxPacketSize) size) noexcept { + // keep endpoint bits 0-3, then move bit 7 to bit 4 + // this creates a 0-31 number representing all possible endpoint addresses + maxPacketSize[(endpoint & 0x0F) | ((endpoint & 0x80) >> 3)] = size; + } + + /// @brief Get the maximum packet size for an endpoint + /// @param endpoint USB endpoint address + /// @return value previously stored with set_max_packet_size(endpoint, value) + decltype(libusb_endpoint_descriptor::wMaxPacketSize) get_max_packet_size(uint8_t endpoint) const noexcept { + // keep endpoint bits 0-3, then move bit 7 to bit 4 + // this creates a 0-31 number representing all possible endpoint addresses + return maxPacketSize[(endpoint & 0x0F) | ((endpoint & 0x80) >> 3)]; + } + + /// @brief Get the device descriptor for the device_handle's device + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return a value initialized libusb_device_descriptor + /// @return device descriptor for the device_handle's device; value initialized on error + /// @note Faster than calling get_device() then get_device_descriptor() + template + libusb_device_descriptor get_device_descriptor() const noexcept(!Throw) { + libusb_device_descriptor descriptor{}; + call_log_throw(__func__, __LINE__, libusb_get_device_descriptor, libusb_get_device(get()), &descriptor); + return descriptor; + } + + /// @brief transfer with libusb_bulk_transfer(); transfers IN continue until bufferSizeBytes is full or timeout/error occurs + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @tparam ChunkTimeoutMs timeout in milliseconds for transfer of each chunk of data; 0 = unlimited + /// @tparam ZeroLengthPacketEnding end transfers OUT having (bufferSizeBytes % maxPacketSize(ep) == 0) with zero length packet + /// @tparam TimeoutMs timeout in milliseconds for entire transfer of data; 0 = unlimited + /// @param endpoint USB endpoint address + /// @param buffer buffer for transfer + /// @param bufferSizeBytes size of buffer in bytes + /// @return std::pair with error code and number of bytes actually transferred + template + std::pair bulk_transfer(unsigned char endpoint, BufferValueType* buffer, intmax_t bufferSizeBytes) const noexcept(!Throw); + + /// @brief transfer with libusb_bulk_transfer(); transfers IN continue until bufferSizeBytes is full or timeout/error occurs + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @tparam ChunkTimeoutMs timeout in milliseconds for transfer of each chunk of data; 0 = unlimited + /// @tparam ZeroLengthPacketEnding end transfers OUT having (bufferSizeBytes % maxPacketSize(ep) == 0) with zero length packet + /// @tparam TimeoutMs timeout in milliseconds for entire transfer of data; 0 = unlimited + /// @param endpoint USB endpoint address + /// @param buffer buffer for transfer; contiguous storage container having data() and size() methods + /// @return std::pair with error code and number of bytes actually transferred + template + std::pair bulk_transfer(const unsigned char endpoint, ContainerType& buffer) const noexcept(!Throw) { + return bulk_transfer( + endpoint, buffer.data(), buffer.size() * sizeof(typename ContainerType::value_type)); + } + + /// @brief basic wrapper for libusb_bulk_transfer() + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @return libusb_error result code + template + libusb_error bulk_transfer(unsigned char endpoint, void *data, int length, int *transferred, std::chrono::milliseconds timeout) const noexcept { + return static_cast(call_log_throw(__func__, __LINE__, libusb_bulk_transfer, get(), endpoint, static_cast(data), length, transferred, static_cast(timeout.count()))); + } + + /// @brief basic wrapper for libusb_control_transfer() + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @return libusb_error result code + template + libusb_error control_transfer(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, void *data, uint16_t length, std::chrono::milliseconds timeout) const noexcept(!Throw) { + return static_cast(call_log_throw(__func__, __LINE__, libusb_control_transfer, get(), requestType, request, value, index, static_cast(data), length, static_cast(timeout.count()))); + } + + /// @brief basic wrapper for libusb_interrupt_transfer() + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return error codes + /// @return libusb_error result code + template + libusb_error interrupt_transfer(unsigned char endpoint, void *data, int length, int *transferred, std::chrono::milliseconds timeout) const noexcept { + return static_cast(call_log_throw(__func__, __LINE__, libusb_interrupt_transfer, get(), endpoint, static_cast(data), length, transferred, static_cast(timeout.count()))); + } +}; + +/// @brief RAII wrapper for libusb_device; automatically calls libusb_unref_device() on destruction +class usb_device : public unique_resource_ptr { +private: + using _base = unique_resource_ptr; + +public: + // inherit base constructors, long form due to Clang fail using _base + using unique_resource_ptr::unique_resource_ptr; + + /// @brief construct a usb_device from a raw libusb_device* pointer and shares ownership + /// @param ptr raw libusb_device* pointer + explicit usb_device(pointer ptr) noexcept : _base{ptr ? libusb_ref_device(ptr) : nullptr} {} + + /// @brief open a device_handle for i/o on the device + /// @return device_handle for i/o on the device + device_handle open() const noexcept(false) { + return device_handle{get()}; + } + + /// @brief Release shared ownership of usb device, then replace with ptr and take shared ownership + /// @param ptr raw resource pointer used to replace the currently managed resource + /// @note No exceptions are thrown. No errors are logged. + void reset(pointer ptr = pointer{}) noexcept { + _base::reset(ptr ? libusb_ref_device(ptr) : nullptr); + } + + /// @brief get the USB config descriptor given the index + /// @param configIndex index of the config descriptor to get + /// @return config_descriptor for the USB device + config_descriptor get_config_descriptor(uint8_t configIndex) const noexcept(noexcept(config_descriptor{get(), 0})) { + return config_descriptor{get(), configIndex}; + } + + /// @brief get the USB device descriptor + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return a value initialized libusb_device_descriptor + /// @return device descriptor for the device; value initialized on error + template + libusb_device_descriptor get_device_descriptor() const noexcept(!Throw) { + libusb_device_descriptor descriptor{}; + call_log_throw(__func__, __LINE__, libusb_get_device_descriptor, get(), &descriptor); + return descriptor; + } + + /// @brief get the USB bus number to which the device is connected + /// @return bus number to which the device is connected + uint8_t get_bus_number() const noexcept(false) { + return CALL_LOG_ERROR_THROW(libusb_get_bus_number, get()); + } + + /// @brief get the USB port numbers to which the device is connected + /// @tparam Loglevel log level used when errors occur + /// @tparam Throw throw exceptions on errors or only return empty vector + /// @tparam Len maximum number of port numbers to read from usb + /// @return port numbers to which device is connected, resized to actual number read; empty on error + template + std::vector get_port_numbers() const noexcept(!Throw) { + std::vector numbers(Len, 0); + const auto result = call_log_throw(__func__, __LINE__, libusb_get_port_numbers, get(), numbers.data(), Len); + if (Throw || result >= 0) { + // when throwing enabled, then throw occurs on error results before this resize(), + // so don't need to check result and compiler can optimize this if chain away. + // when throwing disabled, then always prevent resize of negative/error code result + numbers.resize(result); + } + else { + // when throwing disabled, then return empty vector on error + numbers.clear(); + } + return numbers; + } +}; + +/// @brief RAII wrapper for generated list of usb_device; automatically releases shared ownership of devices and frees device list on destruction +class device_list { +private: + /// @brief iterator template internally used by device_list + /// @tparam Val value type of the iterator + /// @tparam Ref reference type of the iterator; non-reference transformative type returns an r-value + /// to prevent dangling references with operator*() and operator[]() + template + class iterator_xform { + public: + using iterator_category = std::random_access_iterator_tag; + using value_type = Val; + using difference_type = std::ptrdiff_t; + using reference = Ref; + using pointer = value_type*; + + explicit iterator_xform(pointer ptr) noexcept : ptr_{ptr} {} + + // until C++17, deref operators must return non-const reference since usb_device derives + // from std::unique_ptr and a const unique_ptr can not be moved or copied as needed by a function return + reference operator*() const noexcept { + return reference{*ptr_}; + } + reference operator[](difference_type n) const noexcept { + return reference{*(ptr_ + n)}; + } + // no operator->() because it would require persisting a usb_device object in the iterator_xform + // itself and then returning a pointer to it + // pointer operator->() const { + // return ptr_; + // } + + iterator_xform& operator++() noexcept { + ++ptr_; + return *this; + } + iterator_xform operator++(int) noexcept { + auto tmp = *this; + operator++(); + return tmp; + } + iterator_xform& operator--() noexcept { + --ptr_; + return *this; + } + iterator_xform operator--(int) noexcept { + auto tmp = *this; + operator--(); + return tmp; + } + + bool operator==(const iterator_xform& rhs) const noexcept { + return ptr_ == rhs.ptr_; + } + bool operator!=(const iterator_xform& rhs) const noexcept { + return !(*this == rhs); + } + bool operator<(const iterator_xform& rhs) const noexcept { + return ptr_ < rhs.ptr_; + } + bool operator>(const iterator_xform& rhs) const noexcept { + return rhs < *this; + } + bool operator<=(const iterator_xform& rhs) const noexcept { + return !(*this > rhs); + } + bool operator>=(const iterator_xform& rhs) const noexcept { + return !(*this < rhs); + } + + iterator_xform& operator+=(difference_type n) noexcept { + ptr_ += n; + return *this; + } + iterator_xform operator+(difference_type n) const noexcept { + auto tmp = *this; + tmp += n; + return tmp; + } + iterator_xform& operator-=(difference_type n) noexcept { + ptr_ -= n; + return *this; + } + iterator_xform operator-(difference_type n) const noexcept { + auto tmp = *this; + tmp -= n; + return tmp; + } + difference_type operator-(const iterator_xform& rhs) const noexcept { + return ptr_ - rhs.ptr_; + } + private: + pointer ptr_ = nullptr; + }; + +public: + using value_type = libusb_device*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = usb_device; + using const_reference = usb_device; // unable to be const usb_device until c++17 due to unique_ptr base + using pointer = value_type*; + using const_pointer = const value_type*; + using iterator = iterator_xform; + using const_iterator = iterator_xform; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + + // default constructors, destructor, copy, move + device_list() noexcept = default; + ~device_list() noexcept { + // both libapi apis return when param == nullptr + // workaround libusb bug https://github.com/libusb/libusb/issues/1287 + for (size_type i = 0; i < countDevices; ++i) { + libusb_unref_device(deviceList[i]); + } + libusb_free_device_list(deviceList, 0); + } + device_list(const device_list&) = delete; + device_list& operator=(const device_list&) = delete; + device_list(device_list&& other) noexcept : + countDevices{std::exchange(other.countDevices, 0)}, + deviceList{std::exchange(other.deviceList, nullptr)} {}; + device_list& operator=(device_list&& other) noexcept { + if (this == &other) + return *this; + countDevices = std::exchange(other.countDevices, 0); + deviceList = std::exchange(other.deviceList, nullptr); + return *this; + } + + /// @brief Construct a device_list from a raw libusb_context* + /// @param context raw libusb_context* from which to generate the device list + explicit device_list(libusb_context* context) noexcept(false) { + // libusb_get_device_list() is not thread safe! + // https://github.com/libusb/libusb/wiki/FAQ#what-are-the-extra-considerations-to-be-applied-to-applications-which-interact-with-libusb-from-multiple-threads + // libusb will crash or have memory violations when multiple threads simultaneously generate device lists + // due to errant libusb ref count handling, wrongly deleted devices, etc. + // Testing confirmed crashes occurred when libusb internally called libusb_ref_device() or + // called usbi_get_device_priv(dev) and then operated on the pointers + // line in file libusb/os/windows_winusb.c in winusb_get_device_list() line 1741; + // later code using libusb can crash when they call libusb_unref_device(). + std::lock_guard lock(mtx); + countDevices = static_cast(CALL_LOG_ERROR_THROW(libusb_get_device_list, context, &deviceList)); + } + + /// @brief Construct a device_list from a usb_context + /// @param context usb_context from which to generate the device list + explicit device_list(const usb_context& context) noexcept(false) : device_list{context.get()} {} + + /// @brief Wrap an existing libusb_device** list and its count, then take ownership of it + /// @param deviceList raw libusb_device** list + /// @param countDevices count of libusb_device* device pointers in the list + device_list(pointer deviceList, size_type countDevices) noexcept : deviceList{deviceList}, countDevices{countDevices} {} + + //////////////////// + // container methods + //////////////////// + + size_type size() const noexcept { + return countDevices; + } + constexpr size_type max_size() const noexcept { + constexpr auto MAX = std::numeric_limits::max() / sizeof(value_type); + return MAX; + } + bool empty() const noexcept { + return countDevices == 0; + } + pointer data() noexcept { + return deviceList; + } + const_pointer data() const noexcept { + return deviceList; + } + iterator begin() noexcept { + return iterator{deviceList}; + } + const_iterator begin() const noexcept { + return const_iterator{deviceList}; + } + const_iterator cbegin() const noexcept { + return const_iterator{deviceList}; + } + reverse_iterator rbegin() noexcept { + return std::reverse_iterator{end()}; + } + const_reverse_iterator rbegin() const noexcept { + return std::reverse_iterator{cend()}; + } + iterator end() noexcept { + return begin() + countDevices; + } + const_iterator end() const noexcept { + return cbegin() + countDevices; + } + const_iterator cend() const noexcept { + return cbegin() + countDevices; + } + reverse_iterator rend() noexcept { + return std::reverse_iterator{begin()}; + } + const_reverse_iterator rend() const noexcept { + return std::reverse_iterator{cbegin()}; + } + reference front() noexcept { + return *begin(); + } + const_reference front() const noexcept { + return *cbegin(); + } + reference back() noexcept { + return *(begin() + size() - 1); + } + const_reference back() const noexcept { + return *(cbegin() + size() - 1); + } + reference operator[](size_type index) noexcept { + return *(begin() + index); + } + const_reference operator[](size_type index) const noexcept { + return *(cbegin() + index); + } + reference at(size_type index) { + if (index >= size()) { + throw std::out_of_range("device_list::at"); + } + return *(begin() + index); + } + const_reference at(size_type index) const { + if (index >= size()) { + throw std::out_of_range("device_list::at"); + } + return *(cbegin() + index); + } + void swap(device_list& other) noexcept { + std::swap(countDevices, other.countDevices); + std::swap(deviceList, other.deviceList); + } + friend void swap(device_list& left, device_list& right) noexcept { + left.swap(right); + } + bool operator==(const device_list& other) const noexcept { + // short circuit if same pointer list + return countDevices == other.countDevices && deviceList == other.deviceList; + } + bool operator!=(const device_list& other) const noexcept { + return !(*this == other); + } + +private: + static std::mutex mtx; + size_type countDevices{}; + pointer deviceList{}; +}; + +inline device_handle::device_handle(const usb_device& device) noexcept(noexcept(device_handle{device.get()})) : device_handle{device.get()} {} + +// wrap libusb_get_device() and return a usb_device +inline usb_device device_handle::get_device() const noexcept { + return usb_device{libusb_get_device(get())}; +} + +// TODO should a short packet on IN endpoint indicate the device is finished and this function quickly return vs. wait for timeout/error? +template +inline std::pair device_handle::bulk_transfer(const unsigned char endpoint, + BufferValueType* const buffer, + intmax_t bufferSizeBytes) const noexcept(!Throw) { + static constexpr auto FAIL_FORMAT = "dp::libusb failed bulk_transfer(%u %s): %td/%jd bytes transmit; %s"; + static constexpr auto START_FORMAT = "dp::libusb starting bulk_transfer(%u %s): 0/%jd bytes transmit"; + static constexpr auto ZLP_FORMAT = "dp::libusb zerolp bulk_transfer(%u %s): %td/%jd bytes transmit"; + static constexpr auto SUCCESS_FORMAT = "dp::libusb success bulk_transfer(%u %s): %td/%jd bytes transmit in %lf ms (%lf MB/s)"; + static constexpr std::array DIRECTION_TEXT{"out", "in"}; + static constexpr bool BUFFER_IS_CONST = static_cast(std::is_const::value); + static constexpr auto CHUNK_TIMEOUT = (TimeoutMs == 0) ? ChunkTimeoutMs : std::min(ChunkTimeoutMs, TimeoutMs); + + std::pair result{LIBUSB_ERROR_INVALID_PARAM, 0}; + + // verify parameters and that buffer for specific endpoint is non-const for IN endpoints + if((BUFFER_IS_CONST && is_direction_in(endpoint)) || buffer == nullptr || bufferSizeBytes < 0) { + mvLog(MVLOG_FATAL, FAIL_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], 0, bufferSizeBytes, "invalid buffer const, pointer, or size"); + throw_conditional_transfer_error(LIBUSB_ERROR_INVALID_PARAM, 0, std::integral_constant{}); + } + + // start transfer + else { + const bool transmitZeroLengthPacket = ZeroLengthPacketEnding && !is_direction_in(endpoint) && (bufferSizeBytes % get_max_packet_size(endpoint) == 0); + const auto completeSizeBytes = bufferSizeBytes; + auto& rcNum = result.first; + auto& transferredBytes = result.second; + mvLog(MVLOG_DEBUG, START_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], bufferSizeBytes); + + // loop until all data is transferred + const auto t1 = std::chrono::steady_clock::now(); + auto *iterationBuffer = reinterpret_cast(const_cast::type*>(buffer)); + while(bufferSizeBytes || transmitZeroLengthPacket) { + // calculate the number of bytes to transfer in this iteration; never more than chunkSize + int iterationBytesToTransfer = static_cast(std::min(bufferSizeBytes, chunkSize)); + + // Low-level usb packets sized at the endpoint's max packet size will prevent overflow errors. + // This must be handled during the last chunk. If that chunk isn't exactly a multiple of the + // endpoint's max packet size, then need to transfer the largest multiple of max packet size + // and then create a overflow buffer to receive the final "partial" packet of that final chunk + // and then copy that into the outgoing buffer. + std::vector overflowBuffer; + if (is_direction_in(endpoint) && (iterationBytesToTransfer % get_max_packet_size(endpoint) != 0)) { + // adjust down the iterationBytesToTransfer to the largest multiple of max packet size + // which lets this iteration complete without overflow errors, and setup the next + // iteration to handle the final "partial" packet + iterationBytesToTransfer -= iterationBytesToTransfer % get_max_packet_size(endpoint); + + // if this is the final packet, then use the local overflow buffer to receive + // the final "partial" packet and copy the transmitted bytes into the outgoing buffer + if (iterationBytesToTransfer == 0) { + overflowBuffer.resize(get_max_packet_size(endpoint)); + iterationBuffer = overflowBuffer.data(); + iterationBytesToTransfer = static_cast(overflowBuffer.size()); + } + } + + // transfer the data + int iterationTransferredBytes{0}; + rcNum = static_cast(libusb_bulk_transfer(get(), endpoint, iterationBuffer, iterationBytesToTransfer, &iterationTransferredBytes, CHUNK_TIMEOUT)); + + // update the transferred bytes in most cases since some error codes can still transfer data. + // LIBUSB_ERROR_OVERFLOW is special case and libusb docs writes behavior is undefined and + // actual_length out variable is unreliable, data may or may not have been transferred. + // http://billauer.co.il/blog/2019/12/usb-bulk-overflow-halt-reset/ + if(rcNum != LIBUSB_ERROR_OVERFLOW) { + + // adjust this iteration's transferred bytes to minimum of: + // * number of bytes actually transmitted (IN transfers often larger than requested, overflow buffer use, etc.) + // * number of bytes remining for storage in the outgoing buffer + iterationTransferredBytes = static_cast(std::min(iterationTransferredBytes, bufferSizeBytes)); + + // did the transfer happen with the overflow buffer? + if(iterationBuffer == overflowBuffer.data()) { + // copy the "partial" packet from the overflow buffer into the outgoing buffer + std::copy_n(overflowBuffer.data(), + iterationTransferredBytes, + reinterpret_cast(const_cast::type*>(buffer)) + transferredBytes); + } + + // increment the total count of transferred bytes + transferredBytes += iterationTransferredBytes; + } + + // handle error codes, or if the number of bytes transferred != number bytes requested + // note: Scenarios of receiving data from an IN endpoint, and the usb device sends less data than the bufferSizeBytes are + // specifically handled. An example is getLibusbDeviceMxId(). Older code call this function would send a too large + // buffer which causes this scenario. When too large then this while loop would keep looping until LIBUSB_ERROR_TIMEOUT. + // However, if the caller provides a buffer that is exactly the size needed, (getLibusbDeviceMxId() does know this size, + // then this loop will quickly exit. + // note: previous logic ORd (iterationTransferredBytes != iterationBytesToTransfer) with (rcNum < 0) + if(transferredBytes != completeSizeBytes /* logic is superset of ZLP: (iterationBytesToTransfer != 0) */ && rcNum < 0) { + mvLog(Loglevel, FAIL_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], transferredBytes, completeSizeBytes, libusb_strerror(static_cast(rcNum))); + throw_conditional_transfer_error(static_cast(rcNum), transferredBytes, std::integral_constant{}); + break; + } + + // handle zero length packet transfer (the last packet) + if(iterationBytesToTransfer == 0) { + mvLog(MVLOG_DEBUG, ZLP_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], transferredBytes, completeSizeBytes); + rcNum = LIBUSB_SUCCESS; // simulating behavior in previous send_file() function + break; + } + + // successfully and fully transferred this iteration's buffer + bufferSizeBytes -= iterationTransferredBytes; + iterationBuffer += iterationTransferredBytes; + + // check for timeout; when TimeoutMs is non-zero, and bufferSizeBytes is non-zero (all bytes not yet transferred), + // and the duration since started has exceeded TimeoutMs milliseconds + if(static_cast(TimeoutMs) && bufferSizeBytes && (std::chrono::steady_clock::now() - t1 > std::chrono::milliseconds{TimeoutMs})) { + rcNum = LIBUSB_ERROR_TIMEOUT; + mvLog(Loglevel, FAIL_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], transferredBytes, completeSizeBytes, libusb_strerror(static_cast(rcNum))); + throw_conditional_transfer_error(static_cast(rcNum), transferredBytes, std::integral_constant{}); + break; + } + } +#ifndef NDEBUG + if(rcNum == LIBUSB_SUCCESS) { + // calculate the transfer rate (MB/s) and log the result + using double_msec = std::chrono::duration; + const double elapsedMs = std::chrono::duration_cast(std::chrono::steady_clock::now() - t1).count(); + const double MBpS = (static_cast(transferredBytes) / 1048576.) / (elapsedMs * 0.001); + mvLog(MVLOG_DEBUG, SUCCESS_FORMAT, endpoint, DIRECTION_TEXT[is_direction_in(endpoint)], transferredBytes, completeSizeBytes, elapsedMs, MBpS); + } +#endif + } + return result; +} + +} // namespace libusb + +#undef CALL_LOG_ERROR_THROW + +} // namespace dp +#endif // _WRAP_LIBUSB_HPP_ diff --git a/src/pc/protocols/wrap_libusb.md b/src/pc/protocols/wrap_libusb.md new file mode 100755 index 0000000..6368e82 --- /dev/null +++ b/src/pc/protocols/wrap_libusb.md @@ -0,0 +1,152 @@ +# `dp::libusb` - C++ wrap for libusb-1.0 + +Copyright 2023 Dale Phurrough +Licensed under the Apache License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +## Overview + +`dp::libusb` wraps the C apis of [libusb](https://libusb.info/) with C++ unique resource classes +that follow RAII semantics for ownership and automatic disposal of resources. A simple class +`dp::unique_resource_ptr` derives from `std::unique_ptr` with an additional template +parameter for a C-function pointer to dispose the resource. Most `dp::libusb` classes derive +from this base resource type. + +`dp::libusb` is currently focused on the needs of XLink; the protocol to communicate with +Intel Movidius/Myriad devices. A `std::shared_ptr` base for resources was considered for +some use cases. It is not implemented due to the limited needs of XLink; a single owner. + +Many raw libusb types, e.g. `libusb_device_descriptor`, are used in their simple POD form. +Some libusb resources are not implemented, e.g. asynchronous device I/O. + +## Rules + +Most behaviors of `dp::libusb` resources derive from `std::unique_ptr`. + +1. `dp::libusb` resources permit empty/nullptr contents. + `dp::libusb::device_handle handle{nullptr};` and `handle.reset()` are both valid code. +2. Behavior is undefined when code uses or operates on an empty resource. + `handle.claim_interface(1);` and `*handle` would both likely crash your application. +3. Behavior is undefined when code transforms an empty resource. + `libusb_device* device = nullptr; dp::libusb::device_handle handle{device};` will likely crash your application. +4. `dp::libusb::device_list` follows the behaviors and apis of STL containers. It can + be used in places an STL container can be used. +5. Raw libusb return codes for failures are transformed into `dp::libusb::usb_error` exceptions + with error text sourced from `libusb_strerror()`. +6. Raw resource pointers can be retrieved with `get()` to use with libusb C apis. + +## Object Model and Types + +All libusb wrapper classes are derived from `unique_resource_ptr` +where `libusb_****` is the native libusb POD struct and `disposeFunc` is the native libusb disposing function. + +`unique_resource_ptr<>` is a helper class that inherits from `std::unique_ptr>`. It uses the normal `std::unique_ptr` custom deleter feature to automatically call `*disposeFunc(unique_ptr::get())`. + +* `usb_context` enhances raw `libusb_context*` +* `device_list` stl container generates and grants access to all `usb_device` on the host +* `usb_device` enhances raw `libusb_device*` +* `device_handle` enhances raw `libusb_device_handle*` +* `config_descriptor` enhances raw `libusb_config_descriptor*` +* all remaining libusb classes and structs are used unchanged + +The typical process is + +1. Create a `usb_context` +2. From that context get a `device_list` +3. From that list choose a `usb_device` +4. From that device get a `device_handle` +5. Operate and transfer on that handle +6. All resource lifetimes are RAII managed including resource release/free/exiting. + +### Class diagram + +In this diagram, the inheritance `public unique_resource_ptr` is abbreviated `resource`. + +```mermaid +classDiagram +direction TB + + usb_context --> device_list + device_list --> usb_device + usb_device --> device_handle + usb_device --> config_descriptor + + class usb_context { + resource~libusb_context#44;#32;libusb_exit~ + } + + class device_list { + stl#32;container#32;of#32;usb_device + device_list(usb_context) libusb_get_device_list() + ~device_list() libusb_free_device_list() + operator[]() usb_device + begin() usb_device_iterator + size() + standard_container_methods_and_iterators...() + } + + class usb_device { + resource~libusb_device#44;#32;libusb_unref_device~ + usb_device(libusb_device*) libusb_ref_device(ptr) + open() device_handle(libusb_open(get())) + get_config_descriptor(configIndex) + get_device_descriptor() + get_bus_number() + get_port_numbers() + } + + class device_handle { + resource~libusb_device_handle#44;#32;libusb_close~ + device_handle(usb_device) libusb_open() + device_handle(usb_context, sysDevHandle) libusb_wrap_sys_device() + get_device() libusb_get_device(get()) + claim_interface(interfaceNumber) + release_interface(interfaceNumber) + get_configuration() + set_configuration(configurationInt) + get_string_descriptor_ascii(descriptorIndex) + set_auto_detach_kernel_driver(bool) + set_max_packet_size(endpoint, size) + get_max_packet_size(endpoint) + get_device_descriptor() + bulk_transfer(endpoint, *buffer, bufferSize) + bulk_transfer(endpoint, containerBuffer) + bulk_transfer(endpoint, *buffer, bufferSize, *transferred, timeout) + control_transfer(requestType, request, value, index, *buffer, bufferSize, timeout) + interrupt_transfer(endpoint, *buffer, bufferSize, *transferred, timeout) + } + + class config_descriptor { + resource~libusb_config_descriptor#44;#32;libusb_free_config_descriptor~ + config_descriptor(usb_device, configIndex) libusb_get_config_descriptor() + } + + transfer_error --|> usb_error + usb_error --|> system_error + unique_resource_ptr --|> unique_ptr + + namespace helpers { + + class system_error ["std::system_error"] { + } + + class usb_error { + usb_error(errorCode) + usb_error(errorCode, what) + usb_error(errorCode, what) + } + + class transfer_error { + transfer_error(errorCode, transferredBytes) + transfer_error(errorCode, what, transferredBytes) + transfer_error(errorCode, what, transferredBytes) + } + + class unique_ptr ["std::unique_ptr<libusb_***, disposeFunc>"] { + ~unique_ptr() calls *disposeFunc(get()) + } + + class unique_resource_ptr ["unique_resource_ptr<libusb_***, disposeFunc>"] { + } + + } +``` diff --git a/src/pc/protocols/wrap_libusb_details.hpp b/src/pc/protocols/wrap_libusb_details.hpp new file mode 100755 index 0000000..cdb2cad --- /dev/null +++ b/src/pc/protocols/wrap_libusb_details.hpp @@ -0,0 +1,334 @@ +/* + dp::libusb - C++ wrapper for libusb-1.0 (focused on use with the XLink protocol) + + Copyright 2023 Dale Phurrough + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +#ifndef _WRAP_LIBUSB_DETAILS_HPP_ +#define _WRAP_LIBUSB_DETAILS_HPP_ + +// _MSVC_LANG is the more accurate way to get the C++ version in MSVC +#if defined(_MSVC_LANG) && (_MSVC_LANG > __cplusplus) + #define WRAP_CPLUSPLUS _MSVC_LANG +#else + #define WRAP_CPLUSPLUS __cplusplus +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace dp { + +namespace libusb { + +namespace details { + +//********************************************************* +// Portions of this code are from the Microsoft WIL project +// https://github.com/microsoft/wil/wiki/RAII-resource-wrappers#wilout_param +// +// Copyright (c) Microsoft. All rights reserved. +// This code is licensed under the MIT License. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +// ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +// PARTICULAR PURPOSE AND NONINFRINGEMENT. +// +//********************************************************* + +//! Type traits class that identifies the inner type of any smart pointer. +template +struct smart_pointer_details { + typedef typename Ptr::pointer pointer; +}; + +template +struct out_param_t { + typedef typename smart_pointer_details::pointer pointer; + T& wrapper; + pointer pRaw; + bool replace = true; + + explicit out_param_t(T& output) noexcept : wrapper(output), pRaw(nullptr) {} + + out_param_t(out_param_t&& other) noexcept(noexcept(std::declval().reset(std::declval()))) : wrapper(other.wrapper), pRaw(other.pRaw) { + assert(other.replace); + other.replace = false; + } + + operator pointer*() noexcept { + assert(replace); + return &pRaw; + } + + operator pointer&() noexcept { + assert(replace); + return pRaw; + } + + ~out_param_t() noexcept(noexcept(std::declval().reset(std::declval()))) { + if(replace) { + wrapper.reset(pRaw); + } + } + + out_param_t(out_param_t const& other) = delete; + out_param_t& operator=(out_param_t const& other) = delete; +}; + +template +struct out_param_ptr_t { + typedef typename smart_pointer_details::pointer pointer; + T& wrapper; + pointer pRaw; + bool replace = true; + + explicit out_param_ptr_t(T& output) noexcept : wrapper(output), pRaw(nullptr) {} + + out_param_ptr_t(out_param_ptr_t&& other) noexcept(noexcept(std::declval().reset(std::declval()))) : wrapper(other.wrapper), pRaw(other.pRaw) { + assert(other.replace); + other.replace = false; + } + + operator Tcast() noexcept { + assert(replace); + return reinterpret_cast(&pRaw); + } + + ~out_param_ptr_t() noexcept(noexcept(std::declval().reset(std::declval()))) { + if(replace) { + wrapper.reset(pRaw); + } + } + + out_param_ptr_t(out_param_ptr_t const& other) = delete; + out_param_ptr_t& operator=(out_param_ptr_t const& other) = delete; +}; + +} // namespace details + +/** Use to retrieve raw out parameter pointers into smart pointers that do not support the '&' operator. +This avoids multi-step handling of a raw resource to establish the smart pointer. +Example: `GetFoo(out_param(foo));` */ +template +details::out_param_t out_param(T& p) noexcept(noexcept(details::out_param_t(p))) { + return details::out_param_t(p); +} + +/** Use to retrieve raw out parameter pointers (with a required cast) into smart pointers that do not support the '&' operator. +Use only when the smart pointer's &handle is not equal to the output type a function requires, necessitating a cast. +Example: `dp::out_param_ptr(securityDescriptor)` */ +template +details::out_param_ptr_t out_param_ptr(T& p) noexcept(noexcept(details::out_param_ptr_t(p))) { + return details::out_param_ptr_t(p); +} + +#if WRAP_CPLUSPLUS < 202002L + +// simple implementation of std::span for compilers older than C++20 +// dpes not include every feature of C++20 std::span +template +class span { + T* ptr_ = nullptr; + size_t size_ = 0; + static constexpr const char* const throwText = "span index out of range"; + static constexpr auto dynamic_extent = static_cast(-1); + + public: + using element_type = T; + using value_type = typename std::remove_cv::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = element_type*; + using const_pointer = const element_type*; + using reference = element_type&; + using const_reference = const element_type&; + using iterator = pointer; + using reverse_iterator = std::reverse_iterator; + // no constant iterators in c++20 span https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2278r4.html + //using const_iterator = const_pointer; + //using const_reverse_iterator = std::reverse_iterator; + + //// constructors, copy, move, assignment //// + + span() noexcept = default; + ~span() noexcept = default; + span(const span& other) noexcept : ptr_(other.data()), size_(other.size()) {} + span& operator=(const span& other) noexcept { + if(this == &other) return *this; + ptr_ = other.ptr_; + size_ = other.size_; + return *this; + } + span(span&& other) = delete; + span& operator=(span&& other) = delete; + + span(T* ptr, std::size_t size) noexcept : ptr_(ptr), size_(size) {} + + template + span(std::array& arr) noexcept : ptr_(arr.data()), size_(N) {} + + template + span(const std::array& arr) noexcept : ptr_(arr.data()), size_(N) {} + + template + span(Iterator begin, Iterator end) noexcept : ptr_(&(*begin)), size_(std::distance(begin, end)) {} + + //////////////////////// + //// element access //// + //////////////////////// + + pointer data() noexcept { + return ptr_; + } + const_pointer data() const noexcept { + return ptr_; + } + reference operator[](size_type index) noexcept { + return *(begin() + index); + } + const_reference operator[](size_type index) const noexcept { + return *(begin() + index); + } + reference front() noexcept { + return *begin(); + } + const_reference front() const noexcept { + return *begin(); + } + reference back() noexcept { + return *(end() - 1); + } + const_reference back() const noexcept { + return *(end() - 1); + } + // caution: this is non-standard and not available when compiled with C++20 or newer + reference at(size_type index) { + if(index >= size_) { + throw std::out_of_range(throwText); + } + return *(begin() + index); + } + // caution: this is non-standard and not available when compiled with C++20 or newer + const_reference at(size_type index) const { + if(index >= size_) { + throw std::out_of_range(throwText); + } + return *(begin() + index); + } + + /////////////////// + //// iterators //// + /////////////////// + + iterator begin() const noexcept { + return ptr_; + } + iterator end() const noexcept { + return ptr_ + size_; + } + reverse_iterator rbegin() const noexcept { + return reverse_iterator(end()); + } + reverse_iterator rend() const noexcept { + return reverse_iterator(begin()); + } + // no constant iterators in c++20 span https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/p2278r4.html + //const_iterator cbegin() const noexcept { + // return ptr_; + //} + //const_iterator cend() const noexcept { + // return ptr_ + size_; + //} + //const_reverse_iterator crbegin() const noexcept { + // return const_reverse_iterator(cend()); + //} + //const_reverse_iterator crend() const noexcept { + // return const_reverse_iterator(cbegin()); + //} + + /////////////////// + //// observers //// + /////////////////// + + size_type size() const noexcept { + return size_; + } + size_type size_bytes() const noexcept { + return size_ * sizeof(T); + } + bool empty() const noexcept { + return size_ == 0; + } + + ////////////////// + //// subviews //// + ////////////////// + + span subspan(size_type offset, size_type count = dynamic_extent) const noexcept { + return span(ptr_ + offset, count == dynamic_extent ? size_ - offset : count); + } + + template + span subspan() const noexcept { + return span(ptr_ + offset, count == dynamic_extent ? size_ - offset : count); + } + + span first(size_type count) const noexcept { + return subspan(0, count); + } + + template + span first() const noexcept { + return subspan<0, count>(); + } + + span last(size_type count) const noexcept { + return subspan(size_ - count, count); + } + + template + span last() const noexcept { + return span{ptr_ + (size_ - count), count}; + } + + // caution: this is non-standard and not available when compiled with C++20 or newer + span as_bytes() const noexcept { + return {reinterpret_cast(ptr_), size_ * sizeof(T)}; + } + + // caution: this is non-standard and not available when compiled with C++20 or newer + span as_writable_bytes() noexcept { + return {reinterpret_cast(ptr_), size_ * sizeof(T)}; + } +}; +#else + +#include +using std::span; + +#endif + +} // namespace libusb + +} // namespace dp + +#undef WRAP_CPLUSPLUS +#endif // _WRAP_LIBUSB_DETAILS_HPP_ diff --git a/src/shared/XLinkData.c b/src/shared/XLinkData.c index 5f52a27..e173595 100644 --- a/src/shared/XLinkData.c +++ b/src/shared/XLinkData.c @@ -49,7 +49,7 @@ streamId_t XLinkOpenStream(linkId_t id, const char* name, int stream_write_size) XLINK_RET_ERR_IF(stream_write_size < 0, INVALID_STREAM_ID); xLinkDesc_t* link = getLinkById(id); - mvLog(MVLOG_DEBUG,"%s() id %d link %p\n", __func__, id, link); + mvLog(MVLOG_DEBUG,"%s() id %d link %p", __func__, id, link); XLINK_RET_ERR_IF(link == NULL, INVALID_STREAM_ID); XLINK_RET_ERR_IF(getXLinkState(link) != XLINK_UP, INVALID_STREAM_ID); XLINK_RET_ERR_IF(strlen(name) >= MAX_STREAM_NAME_LENGTH, INVALID_STREAM_ID); @@ -189,7 +189,7 @@ XLinkError_t XLinkWriteDataWithTimeout(streamId_t const streamId, const uint8_t* XLINK_INIT_EVENT(event, streamIdOnly, XLINK_WRITE_REQ, size,(void*)buffer, link->deviceHandle); - mvLog(MVLOG_WARN,"XLinkWriteDataWithTimeout is not fully supported yet. The XLinkWriteData method is called instead. Desired timeout = %d\n", timeoutMs); + mvLog(MVLOG_WARN,"XLinkWriteDataWithTimeout is not fully supported yet. The XLinkWriteData method is called instead. Desired timeout = %d", timeoutMs); XLINK_RET_IF_FAIL(addEventWithPerf(&event, &opTime, timeoutMs)); if( glHandler->profEnable) { @@ -423,7 +423,7 @@ XLinkError_t addEvent(xLinkEvent_t *event, unsigned int timeoutMs) xLinkEvent_t* ev = DispatcherAddEvent(EVENT_LOCAL, event); if(ev == NULL) { - mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s\n", + mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s", TypeToStr(event->header.type), event->header.id, event->header.streamName); return X_LINK_ERROR; } @@ -488,7 +488,7 @@ XLinkError_t addEventTimeout(xLinkEvent_t *event, struct timespec abstime) xLinkEvent_t* ev = DispatcherAddEvent(EVENT_LOCAL, event); if(ev == NULL) { - mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s\n", + mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s", TypeToStr(event->header.type), event->header.id, event->header.streamName); return X_LINK_ERROR; } diff --git a/src/shared/XLinkDevice.c b/src/shared/XLinkDevice.c index 24c483e..1f6e91e 100644 --- a/src/shared/XLinkDevice.c +++ b/src/shared/XLinkDevice.c @@ -90,7 +90,7 @@ XLinkError_t XLinkInitialize(XLinkGlobalHandler_t* globalHandler) ASSERT_XLINK(XLINK_MAX_STREAMS <= MAX_POOLS_ALLOC); glHandler = globalHandler; if (sem_init(&pingSem,0,0)) { - mvLog(MVLOG_ERROR, "Can't create semaphore\n"); + mvLog(MVLOG_ERROR, "Can't create semaphore"); } int i; @@ -235,7 +235,7 @@ XLinkError_t XLinkConnect(XLinkHandler_t* handler) xLinkDesc_t* link = getNextAvailableLink(); XLINK_RET_IF(link == NULL); - mvLog(MVLOG_DEBUG,"%s() device name %s glHandler %p protocol %d\n", __func__, handler->devicePath, glHandler, handler->protocol); + mvLog(MVLOG_DEBUG,"%s() device name %s glHandler %p protocol %d", __func__, handler->devicePath, glHandler, handler->protocol); link->deviceHandle.protocol = handler->protocol; int connectStatus = XLinkPlatformConnect(handler->devicePath2, handler->devicePath, @@ -330,7 +330,7 @@ XLinkError_t XLinkResetRemote(linkId_t id) xLinkEvent_t event = {0}; event.header.type = XLINK_RESET_REQ; event.deviceHandle = link->deviceHandle; - mvLog(MVLOG_DEBUG, "sending reset remote event\n"); + mvLog(MVLOG_DEBUG, "sending reset remote event"); DispatcherAddEvent(EVENT_LOCAL, &event); XLINK_RET_ERR_IF(DispatcherWaitEventComplete(&link->deviceHandle, XLINK_NO_RW_TIMEOUT), X_LINK_TIMEOUT); @@ -339,7 +339,7 @@ XLinkError_t XLinkResetRemote(linkId_t id) while(((rc = XLink_sem_wait(&link->dispatcherClosedSem)) == -1) && errno == EINTR) continue; if(rc) { - mvLog(MVLOG_ERROR,"can't wait dispatcherClosedSem\n"); + mvLog(MVLOG_ERROR,"can't wait dispatcherClosedSem"); return X_LINK_ERROR; } @@ -361,7 +361,7 @@ XLinkError_t XLinkResetRemoteTimeout(linkId_t id, int timeoutMs) xLinkEvent_t event = {0}; event.header.type = XLINK_RESET_REQ; event.deviceHandle = link->deviceHandle; - mvLog(MVLOG_DEBUG, "sending reset remote event\n"); + mvLog(MVLOG_DEBUG, "sending reset remote event"); struct timespec start; clock_gettime(CLOCK_REALTIME, &start); @@ -376,7 +376,7 @@ XLinkError_t XLinkResetRemoteTimeout(linkId_t id, int timeoutMs) xLinkEvent_t* ev = DispatcherAddEvent(EVENT_LOCAL, &event); if(ev == NULL) { - mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s\n", + mvLog(MVLOG_ERROR, "Dispatcher failed on adding event. type: %s, id: %d, stream name: %s", TypeToStr(event.header.type), event.header.id, event.header.streamName); return X_LINK_ERROR; } @@ -391,7 +391,7 @@ XLinkError_t XLinkResetRemoteTimeout(linkId_t id, int timeoutMs) // Wait for dispatcher to be closed if(XLink_sem_wait(&link->dispatcherClosedSem)) { - mvLog(MVLOG_ERROR,"can't wait dispatcherClosedSem\n"); + mvLog(MVLOG_ERROR,"can't wait dispatcherClosedSem"); return X_LINK_ERROR; } @@ -412,7 +412,7 @@ XLinkError_t XLinkResetAll() for (stream = 0; stream < XLINK_MAX_STREAMS; stream++) { if (link->availableStreams[stream].id != INVALID_STREAM_ID) { streamId_t streamId = link->availableStreams[stream].id; - mvLog(MVLOG_DEBUG,"%s() Closing stream (stream = %d) %d on link %d\n", + mvLog(MVLOG_DEBUG,"%s() Closing stream (stream = %d) %d on link %d", __func__, stream, (int) streamId, (int) link->id); COMBINE_IDS(streamId, link->id); if (XLinkCloseStream(streamId) != X_LINK_SUCCESS) { @@ -543,7 +543,7 @@ static linkId_t getNextAvailableLinkUniqueId() nextUniqueLinkId = 0; } } while (start != nextUniqueLinkId); - mvLog(MVLOG_ERROR, "%s():- no next available unique link id!\n", __func__); + mvLog(MVLOG_ERROR, "%s():- no next available unique link id!", __func__); return INVALID_LINK_ID; } @@ -565,7 +565,7 @@ static xLinkDesc_t* getNextAvailableLink() { } if(i >= MAX_LINKS) { - mvLog(MVLOG_ERROR,"%s():- no next available link!\n", __func__); + mvLog(MVLOG_ERROR,"%s():- no next available link!", __func__); XLINK_RET_ERR_IF(pthread_mutex_unlock(&availableXLinksMutex) != 0, NULL); return NULL; } @@ -573,7 +573,7 @@ static xLinkDesc_t* getNextAvailableLink() { xLinkDesc_t* link = &availableXLinks[i]; if (XLink_sem_init(&link->dispatcherClosedSem, 0 ,0)) { - mvLog(MVLOG_ERROR, "Cannot initialize semaphore\n"); + mvLog(MVLOG_ERROR, "Cannot initialize semaphore"); XLINK_RET_ERR_IF(pthread_mutex_unlock(&availableXLinksMutex) != 0, NULL); return NULL; } @@ -587,13 +587,13 @@ static xLinkDesc_t* getNextAvailableLink() { static void freeGivenLink(xLinkDesc_t* link) { if(pthread_mutex_lock(&availableXLinksMutex) != 0){ - mvLog(MVLOG_ERROR, "Cannot lock mutex\n"); + mvLog(MVLOG_ERROR, "Cannot lock mutex"); return; } link->id = INVALID_LINK_ID; if (XLink_sem_destroy(&link->dispatcherClosedSem)) { - mvLog(MVLOG_ERROR, "Cannot destroy semaphore\n"); + mvLog(MVLOG_ERROR, "Cannot destroy semaphore"); } pthread_mutex_unlock(&availableXLinksMutex); diff --git a/src/shared/XLinkDispatcher.c b/src/shared/XLinkDispatcher.c index ce49e39..290f73a 100644 --- a/src/shared/XLinkDispatcher.c +++ b/src/shared/XLinkDispatcher.c @@ -199,7 +199,7 @@ XLinkError_t DispatcherInitialize(DispatcherControlFunctions *controlFunc) { numSchedulers = 0; if (sem_init(&addSchedulerSem, 0, 1)) { - mvLog(MVLOG_ERROR, "Can't create semaphore\n"); + mvLog(MVLOG_ERROR, "Can't create semaphore"); return X_LINK_ERROR; } @@ -221,12 +221,12 @@ XLinkError_t DispatcherStart(xLinkDeviceHandle_t *deviceHandle) int eventIdx; if (numSchedulers >= MAX_SCHEDULERS) { - mvLog(MVLOG_ERROR,"Max number Schedulers reached!\n"); + mvLog(MVLOG_ERROR,"Max number Schedulers reached!"); return -1; } int idx = findAvailableScheduler(); if (idx == -1) { - mvLog(MVLOG_ERROR,"Max number Schedulers reached!\n"); + mvLog(MVLOG_ERROR,"Max number Schedulers reached!"); return -1; } @@ -304,7 +304,7 @@ XLinkError_t DispatcherStart(xLinkDeviceHandle_t *deviceHandle) while(((sem_wait(&addSchedulerSem) == -1) && errno == EINTR)) continue; - mvLog(MVLOG_DEBUG,"%s() starting a new thread - schedulerId %d \n", __func__, idx); + mvLog(MVLOG_DEBUG,"%s() starting a new thread - schedulerId %d", __func__, idx); int sc = pthread_create(&schedulerState[idx].xLinkThreadId, &attr, eventSchedulerRun, @@ -364,12 +364,12 @@ xLinkEvent_t* DispatcherAddEvent(xLinkEventOrigin_t origin, xLinkEvent_t *event) if(curr->resetXLink) { return NULL; } - mvLog(MVLOG_DEBUG, "Receiving event %s %d\n", TypeToStr(event->header.type), origin); + mvLog(MVLOG_DEBUG, "Receiving event %s %d", TypeToStr(event->header.type), origin); int rc; while(((rc = XLink_sem_wait(&curr->addEventSem)) == -1) && errno == EINTR) continue; if (rc) { - mvLog(MVLOG_ERROR,"can't wait semaphore\n"); + mvLog(MVLOG_ERROR,"can't wait semaphore"); return NULL; } @@ -382,9 +382,9 @@ xLinkEvent_t* DispatcherAddEvent(xLinkEventOrigin_t origin, xLinkEvent_t *event) sem = createSem(curr); } if (!sem) { - mvLog(MVLOG_WARN,"No more semaphores. Increase XLink or OS resources\n"); + mvLog(MVLOG_WARN,"No more semaphores. Increase XLink or OS resources"); if (XLink_sem_post(&curr->addEventSem)) { - mvLog(MVLOG_ERROR,"can't post semaphore\n"); + mvLog(MVLOG_ERROR,"can't post semaphore"); } return NULL; @@ -397,10 +397,10 @@ xLinkEvent_t* DispatcherAddEvent(xLinkEventOrigin_t origin, xLinkEvent_t *event) ev = addNextQueueElemToProc(curr, &curr->rQueue, event, NULL, origin); } if (XLink_sem_post(&curr->addEventSem)) { - mvLog(MVLOG_ERROR,"can't post semaphore\n"); + mvLog(MVLOG_ERROR,"can't post semaphore"); } if (XLink_sem_post(&curr->notifyDispatcherSem)) { - mvLog(MVLOG_ERROR, "can't post semaphore\n"); + mvLog(MVLOG_ERROR, "can't post semaphore"); } return ev; } @@ -527,7 +527,7 @@ int DispatcherUnblockEvent(eventId_t id, xLinkEventType_t type, streamId_t strea xLinkSchedulerState_t* curr = findCorrespondingScheduler(xlinkFD); ASSERT_XLINK(curr != NULL); - mvLog(MVLOG_DEBUG,"unblock\n"); + mvLog(MVLOG_DEBUG,"unblock"); xLinkEventPriv_t* blockedEvent; XLINK_RET_ERR_IF(pthread_mutex_lock(&(curr->queueMutex)) != 0, 1); @@ -540,17 +540,17 @@ int DispatcherUnblockEvent(eventId_t id, xLinkEventType_t type, streamId_t strea && blockedEvent->packet.header.type == type && blockedEvent->packet.header.streamId == stream)) { - mvLog(MVLOG_DEBUG,"unblocked**************** %d %s\n", + mvLog(MVLOG_DEBUG,"unblocked**************** %d %s", (int)blockedEvent->packet.header.id, TypeToStr((int)blockedEvent->packet.header.type)); blockedEvent->isServed = EVENT_READY; if (XLink_sem_post(&curr->notifyDispatcherSem)){ - mvLog(MVLOG_ERROR, "can't post semaphore\n"); + mvLog(MVLOG_ERROR, "can't post semaphore"); } XLINK_RET_ERR_IF(pthread_mutex_unlock(&(curr->queueMutex)) != 0, 1); return 1; } else { - mvLog(MVLOG_DEBUG,"%d %s\n", + mvLog(MVLOG_DEBUG,"%d %s", (int)blockedEvent->packet.header.id, TypeToStr((int)blockedEvent->packet.header.type)); } @@ -574,7 +574,7 @@ int DispatcherServeEvent(eventId_t id, xLinkEventType_t type, streamId_t stream, && event->packet.header.type == type && event->packet.header.streamId == stream)) { - mvLog(MVLOG_DEBUG,"served**************** %d %s\n", + mvLog(MVLOG_DEBUG,"served**************** %d %s", (int)event->packet.header.id, TypeToStr((int)event->packet.header.type)); event->isServed = EVENT_SERVED; @@ -639,7 +639,7 @@ static XLink_sem_t* createSem(xLinkSchedulerState_t* curr) if (refs == -1) { sem = &temp->sem; if (XLink_sem_init(sem, 0, 0)){ - mvLog(MVLOG_ERROR, "Error: Can't create semaphore\n"); + mvLog(MVLOG_ERROR, "Error: Can't create semaphore"); return NULL; } curr->semaphores++; @@ -695,7 +695,7 @@ static void* eventReader(void* ctx) while (!curr->resetXLink) { int sc = glControlFunc->eventReceive(&event); - mvLog(MVLOG_DEBUG,"Reading %s (scheduler %d, fd %p, event id %d, event stream_id %u, event size %u)\n", + mvLog(MVLOG_DEBUG,"Reading %s (scheduler %d, fd %p, event id %d, event stream_id %u, event size %u)", TypeToStr(event.header.type), curr->schedulerId, event.deviceHandle.xLinkFD, event.header.id, event.header.streamId, event.header.size); if (sc) { @@ -725,7 +725,7 @@ static void* eventSchedulerRun(void* ctx) #endif { int schedulerId = *((int*) ctx); - mvLog(MVLOG_DEBUG,"%s() schedulerId %d\n", __func__, schedulerId); + mvLog(MVLOG_DEBUG,"%s() schedulerId %d", __func__, schedulerId); XLINK_RET_ERR_IF(schedulerId >= MAX_SCHEDULERS, NULL); xLinkSchedulerState_t* curr = &schedulerState[schedulerId]; @@ -823,7 +823,7 @@ static void postAndMarkEventServed(xLinkEventPriv_t *event) } if(event->sem){ if (XLink_sem_post(event->sem)) { - mvLog(MVLOG_ERROR,"can't post semaphore\n"); + mvLog(MVLOG_ERROR,"can't post semaphore"); } } @@ -891,7 +891,7 @@ static int dispatcherRequestServe(xLinkEventPriv_t * event, xLinkSchedulerState_ } else if (header->flags.bitField.ack == 1 && header->flags.bitField.nack == 0){ event->isServed = EVENT_PENDING; - mvLog(MVLOG_DEBUG,"------------------------UNserved %s\n", + mvLog(MVLOG_DEBUG,"------------------------UNserved %s", TypeToStr(event->packet.header.type)); }else{ return 1; @@ -913,7 +913,7 @@ static int dispatcherResponseServe(xLinkEventPriv_t * event, xLinkSchedulerState header->id == evHeader->id && header->type == evHeader->type - XLINK_REQUEST_LAST -1) { - mvLog(MVLOG_DEBUG,"----------------------ISserved %s\n", + mvLog(MVLOG_DEBUG,"----------------------ISserved %s", TypeToStr(header->type)); //propagate back flags header->flags = evHeader->flags; @@ -922,13 +922,13 @@ static int dispatcherResponseServe(xLinkEventPriv_t * event, xLinkSchedulerState } } if (i == MAX_EVENTS) { - mvLog(MVLOG_FATAL,"no request for this response: %s %d\n", TypeToStr(event->packet.header.type), event->origin); - mvLog(MVLOG_DEBUG,"#### (i == MAX_EVENTS) %s %d %d\n", TypeToStr(event->packet.header.type), event->origin, (int)event->packet.header.id); + mvLog(MVLOG_FATAL,"no request for this response: %s %d", TypeToStr(event->packet.header.type), event->origin); + mvLog(MVLOG_DEBUG,"#### (i == MAX_EVENTS) %s %d %d", TypeToStr(event->packet.header.type), event->origin, (int)event->packet.header.id); for (i = 0; i < MAX_EVENTS; i++) { xLinkEventHeader_t *header = &curr->lQueue.q[i].packet.header; - mvLog(MVLOG_DEBUG,"%d) header->id %i, header->type %s(%i), curr->lQueue.q[i].isServed %i, EVENT_PENDING %i\n", i, (int)header->id + mvLog(MVLOG_DEBUG,"%d) header->id %i, header->type %s(%i), curr->lQueue.q[i].isServed %i, EVENT_PENDING %i", i, (int)header->id , TypeToStr(header->type), header->type, curr->lQueue.q[i].isServed, EVENT_PENDING); } @@ -960,7 +960,7 @@ static xLinkEventPriv_t* searchForReadyEvent(xLinkSchedulerState_t* curr) ev = getNextElementWithState(curr->lQueue.base, curr->lQueue.end, curr->lQueue.base, EVENT_READY); if(ev){ - mvLog(MVLOG_DEBUG,"ready %s %d \n", + mvLog(MVLOG_DEBUG,"ready %s %d", TypeToStr((int)ev->packet.header.type), (int)ev->packet.header.id); } @@ -1019,7 +1019,7 @@ static xLinkEventPriv_t* dispatcherGetNextEvent(xLinkSchedulerState_t* curr) while(((rc = XLink_sem_wait(&curr->notifyDispatcherSem)) == -1) && errno == EINTR) continue; if (rc) { - mvLog(MVLOG_ERROR,"can't post semaphore\n"); + mvLog(MVLOG_ERROR,"can't post semaphore"); } xLinkEventPriv_t* event = NULL; @@ -1060,11 +1060,11 @@ static int dispatcherClean(xLinkSchedulerState_t* curr) mvLog(MVLOG_INFO, "Start Clean Dispatcher..."); if (XLink_sem_post(&curr->notifyDispatcherSem)) { - mvLog(MVLOG_ERROR,"can't post semaphore\n"); //to allow us to get a NULL event + mvLog(MVLOG_ERROR,"can't post semaphore"); //to allow us to get a NULL event } xLinkEventPriv_t* event = dispatcherGetNextEvent(curr); while (event != NULL) { - mvLog(MVLOG_INFO, "dropped event is %s, status %d\n", + mvLog(MVLOG_INFO, "dropped event is %s, status %d", TypeToStr(event->packet.header.type), event->isServed); XLINK_RET_ERR_IF(pthread_mutex_lock(&(curr->queueMutex)) != 0, 1); @@ -1150,14 +1150,14 @@ static int dispatcherReset(xLinkSchedulerState_t* curr) xLinkDesc_t* link = getLink(curr->deviceHandle.xLinkFD); if(link == NULL || XLink_sem_post(&link->dispatcherClosedSem)) { - mvLog(MVLOG_DEBUG,"can't post dispatcherClosedSem\n"); + mvLog(MVLOG_DEBUG,"can't post dispatcherClosedSem"); } glControlFunc->closeLink(curr->deviceHandle.xLinkFD, 1); // Set dispatcher link state "down", to disallow resetting again curr->dispatcherLinkDown = 1; - mvLog(MVLOG_DEBUG,"Reset Successfully\n"); + mvLog(MVLOG_DEBUG,"Reset Successfully"); if(pthread_mutex_unlock(&reset_mutex) != 0) { mvLog(MVLOG_ERROR, "Failed to unlock clean_mutex after clearing dispatcher"); @@ -1279,7 +1279,7 @@ static void dispatcherFreeEvents(eventQueueHandler_t *queue, xLinkEventState_t s xLinkEventPriv_t* event = getNextElementWithState(queue->base, queue->end, queue->base, state); while (event != NULL) { - mvLog(MVLOG_DEBUG, "Event is %s, size is %d, Mark it served\n", TypeToStr(event->packet.header.type), event->packet.header.size); + mvLog(MVLOG_DEBUG, "Event is %s, size is %d, Mark it served", TypeToStr(event->packet.header.type), event->packet.header.size); postAndMarkEventServed(event); event = getNextElementWithState(queue->base, queue->end, queue->base, state); } diff --git a/src/shared/XLinkDispatcherImpl.c b/src/shared/XLinkDispatcherImpl.c index 3d2bb2e..8bfeeab 100644 --- a/src/shared/XLinkDispatcherImpl.c +++ b/src/shared/XLinkDispatcherImpl.c @@ -46,14 +46,14 @@ static int handleIncomingEvent(xLinkEvent_t* event); //adds a new event with parameters and returns event id int dispatcherEventSend(xLinkEvent_t *event) { - mvLog(MVLOG_DEBUG, "Send event: %s, size %d, streamId %ld.\n", + mvLog(MVLOG_DEBUG, "Send event: %s, size %d, streamId %ld.", TypeToStr(event->header.type), event->header.size, event->header.streamId); int rc = XLinkPlatformWrite(&event->deviceHandle, &event->header, sizeof(event->header)); if(rc < 0) { - mvLog(MVLOG_ERROR,"Write failed (header) (err %d) | event %s\n", rc, TypeToStr(event->header.type)); + mvLog(MVLOG_ERROR,"Write failed (header) (err %d) | event %s", rc, TypeToStr(event->header.type)); return rc; } @@ -61,7 +61,7 @@ int dispatcherEventSend(xLinkEvent_t *event) rc = XLinkPlatformWrite(&event->deviceHandle, event->data, event->header.size); if(rc < 0) { - mvLog(MVLOG_ERROR,"Write failed %d\n", rc); + mvLog(MVLOG_ERROR,"Write failed %d", rc); return rc; } } @@ -74,7 +74,7 @@ int dispatcherEventReceive(xLinkEvent_t* event){ int rc = XLinkPlatformRead(&event->deviceHandle, &event->header, sizeof(event->header)); - // mvLog(MVLOG_DEBUG,"Incoming event %p: %s %d %p prevEvent: %s %d %p\n", + // mvLog(MVLOG_DEBUG,"Incoming event %p: %s %d %p prevEvent: %s %d %p", // event, // TypeToStr(event->header.type), // (int)event->header.id, @@ -84,7 +84,7 @@ int dispatcherEventReceive(xLinkEvent_t* event){ // prevEvent.deviceHandle.xLinkFD); if(rc < 0) { - mvLog(MVLOG_WARN,"%s() Read failed %d\n", __func__, (int)rc); + mvLog(MVLOG_WARN,"%s() Read failed %d", __func__, (int)rc); return rc; } @@ -92,7 +92,7 @@ int dispatcherEventReceive(xLinkEvent_t* event){ // if (prevEvent.header.id == event->header.id && // prevEvent.header.type == event->header.type && // prevEvent.deviceHandle.xLinkFD == event->deviceHandle.xLinkFD) { - // mvLog(MVLOG_FATAL,"Duplicate id detected. \n"); + // mvLog(MVLOG_FATAL,"Duplicate id detected."); // } // prevEvent = *event; @@ -104,7 +104,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) { streamDesc_t* stream; response->header.id = event->header.id; - mvLog(MVLOG_DEBUG, "%s\n",TypeToStr(event->header.type)); + mvLog(MVLOG_DEBUG, "%s",TypeToStr(event->header.type)); switch (event->header.type){ case XLINK_WRITE_REQ: { @@ -112,7 +112,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); if(!stream) { - mvLog(MVLOG_DEBUG, "stream %d has been closed!\n", event->header.streamId); + mvLog(MVLOG_DEBUG, "stream %d has been closed!", event->header.streamId); XLINK_SET_EVENT_FAILED_AND_SERVE(event); break; } @@ -128,16 +128,16 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) event->header.flags.bitField.localServe = 0; if(!isStreamSpaceEnoughFor(stream, event->header.size)){ - mvLog(MVLOG_DEBUG,"local NACK RTS. stream '%s' is full (event %d)\n", stream->name, event->header.id); + mvLog(MVLOG_DEBUG,"local NACK RTS. stream '%s' is full (event %d)", stream->name, event->header.id); event->header.flags.bitField.block = 1; event->header.flags.bitField.localServe = 1; // TODO: easy to implement non-blocking read here, just return nack - mvLog(MVLOG_WARN, "Blocked event would cause dispatching thread to wait on semaphore infinitely\n"); + mvLog(MVLOG_WARN, "Blocked event would cause dispatching thread to wait on semaphore infinitely"); }else{ event->header.flags.bitField.block = 0; stream->remoteFillLevel += event->header.size; stream->remoteFillPacketLevel++; - mvLog(MVLOG_DEBUG,"S%d: Got local write of %ld , remote fill level %ld out of %ld %ld\n", + mvLog(MVLOG_DEBUG,"S%d: Got local write of %ld , remote fill level %ld out of %ld %ld", event->header.streamId, event->header.size, stream->remoteFillLevel, stream->writeSize, stream->readSize); } releaseStream(stream); @@ -147,7 +147,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) { stream = getStreamById(event->deviceHandle.xLinkFD, event->header.streamId); if(!stream) { - mvLog(MVLOG_DEBUG, "stream %d has been closed!\n", event->header.streamId); + mvLog(MVLOG_DEBUG, "stream %d has been closed!", event->header.streamId); XLINK_SET_EVENT_FAILED_AND_SERVE(event); break; } @@ -205,11 +205,11 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) event->header.streamName, event->header.size, 0, INVALID_STREAM_ID); - mvLog(MVLOG_DEBUG, "XLINK_CREATE_STREAM_REQ - stream has been just opened with id %ld\n", + mvLog(MVLOG_DEBUG, "XLINK_CREATE_STREAM_REQ - stream has been just opened with id %ld", event->header.streamId); #else mvLog(MVLOG_DEBUG, "XLINK_CREATE_STREAM_REQ - do nothing. Stream will be " - "opened with forced id accordingly to response from the host\n"); + "opened with forced id accordingly to response from the host"); #endif break; } @@ -233,13 +233,13 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) case XLINK_RESET_REQ: { XLINK_EVENT_ACKNOWLEDGE(event); - mvLog(MVLOG_DEBUG,"XLINK_RESET_REQ - do nothing\n"); + mvLog(MVLOG_DEBUG,"XLINK_RESET_REQ - do nothing"); break; } case XLINK_PING_REQ: { XLINK_EVENT_ACKNOWLEDGE(event); - mvLog(MVLOG_DEBUG,"XLINK_PING_REQ - do nothing\n"); + mvLog(MVLOG_DEBUG,"XLINK_PING_REQ - do nothing"); break; } case XLINK_WRITE_RESP: @@ -257,7 +257,7 @@ int dispatcherLocalEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response) default: { mvLog(MVLOG_ERROR, - "Fail to get response for local event. type: %d, stream name: %s\n", + "Fail to get response for local event. type: %d, stream name: %s", event->header.type, event->header.streamName); ASSERT_XLINK(0); } @@ -270,7 +270,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response { response->header.id = event->header.id; response->header.flags.raw = 0; - mvLog(MVLOG_DEBUG, "%s\n",TypeToStr(event->header.type)); + mvLog(MVLOG_DEBUG, "%s",TypeToStr(event->header.type)); switch (event->header.type) { @@ -289,7 +289,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response response->header.streamId, event->deviceHandle.xLinkFD); (void) xxx; - mvLog(MVLOG_DEBUG,"unblocked from stream %d %d\n", + mvLog(MVLOG_DEBUG,"unblocked from stream %d %d", (int)response->header.streamId, (int)xxx); } break; @@ -305,7 +305,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response stream->remoteFillLevel -= event->header.size; stream->remoteFillPacketLevel--; - mvLog(MVLOG_DEBUG,"S%d: Got remote release of %ld, remote fill level %ld out of %ld %ld\n", + mvLog(MVLOG_DEBUG,"S%d: Got remote release of %ld, remote fill level %ld out of %ld %ld", event->header.streamId, event->header.size, stream->remoteFillLevel, stream->writeSize, stream->readSize); releaseStream(stream); @@ -314,7 +314,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response //with every released packet check if the stream is already marked for close if (stream->closeStreamInitiated && stream->localFillLevel == 0) { - mvLog(MVLOG_DEBUG,"%s() Unblock close STREAM\n", __func__); + mvLog(MVLOG_DEBUG,"%s() Unblock close STREAM", __func__); DispatcherUnblockEvent(-1, XLINK_CLOSE_STREAM_REQ, event->header.streamId, @@ -332,7 +332,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response stream->remoteFillLevel -= event->header.size; stream->remoteFillPacketLevel--; - mvLog(MVLOG_DEBUG,"S%d: Got remote release of %ld, remote fill level %ld out of %ld %ld\n", + mvLog(MVLOG_DEBUG,"S%d: Got remote release of %ld, remote fill level %ld out of %ld %ld", event->header.streamId, event->header.size, stream->remoteFillLevel, stream->writeSize, stream->readSize); releaseStream(stream); @@ -341,7 +341,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response //with every released packet check if the stream is already marked for close if (stream->closeStreamInitiated && stream->localFillLevel == 0) { - mvLog(MVLOG_DEBUG,"%s() Unblock close STREAM\n", __func__); + mvLog(MVLOG_DEBUG,"%s() Unblock close STREAM", __func__); int xxx = DispatcherUnblockEvent(-1, XLINK_CLOSE_STREAM_REQ, event->header.streamId, @@ -375,7 +375,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response mv_strncpy(response->header.streamName, MAX_STREAM_NAME_LENGTH, event->header.streamName, MAX_STREAM_NAME_LENGTH - 1); response->header.size = event->header.size; - mvLog(MVLOG_DEBUG,"creating stream %x\n", (int)response->header.streamId); + mvLog(MVLOG_DEBUG,"creating stream %x", (int)response->header.streamId); break; case XLINK_CLOSE_STREAM_REQ: { @@ -389,7 +389,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response //if we have sent a NACK before, when the event gets unblocked //the stream might already be unavailable XLINK_EVENT_ACKNOWLEDGE(response); - mvLog(MVLOG_DEBUG,"%s() got a close stream on aready closed stream\n", __func__); + mvLog(MVLOG_DEBUG,"%s() got a close stream on aready closed stream", __func__); } else { if (stream->localFillLevel == 0) { @@ -412,7 +412,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response } else { - mvLog(MVLOG_DEBUG,"%s():fifo is NOT empty returning NACK \n", __func__); + mvLog(MVLOG_DEBUG,"%s():fifo is NOT empty returning NACK", __func__); XLINK_EVENT_NOT_ACKNOWLEDGE(response); stream->closeStreamInitiated = 1; } @@ -428,7 +428,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response sem_post(&pingSem); break; case XLINK_RESET_REQ: - mvLog(MVLOG_DEBUG,"reset request - received! Sending ACK *****\n"); + mvLog(MVLOG_DEBUG,"reset request - received! Sending ACK *****"); XLINK_EVENT_ACKNOWLEDGE(response); response->header.type = XLINK_RESET_RESP; response->deviceHandle = event->deviceHandle; @@ -453,7 +453,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response XLINK_RET_IF(response->header.streamId == INVALID_STREAM_ID); mvLog(MVLOG_DEBUG, "XLINK_CREATE_STREAM_REQ - stream has been just opened " - "with forced id=%ld accordingly to response from the host\n", + "with forced id=%ld accordingly to response from the host", response->header.streamId); #endif response->deviceHandle = event->deviceHandle; @@ -485,7 +485,7 @@ int dispatcherRemoteEventGetResponse(xLinkEvent_t* event, xLinkEvent_t* response default: { mvLog(MVLOG_ERROR, - "Fail to get response for remote event. type: %d, stream name: %s\n", + "Fail to get response for remote event. type: %d, stream name: %s", event->header.type, event->header.streamName); ASSERT_XLINK(0); } @@ -537,7 +537,7 @@ void dispatcherCloseLink(void* fd, int fullClose) } if(XLink_sem_destroy(&link->dispatcherClosedSem)) { - mvLog(MVLOG_DEBUG, "Cannot destroy dispatcherClosedSem\n"); + mvLog(MVLOG_DEBUG, "Cannot destroy dispatcherClosedSem"); } } @@ -560,7 +560,7 @@ int isStreamSpaceEnoughFor(streamDesc_t* stream, uint32_t size) { if(stream->remoteFillPacketLevel >= XLINK_MAX_PACKETS_PER_STREAM || stream->remoteFillLevel + size > stream->writeSize){ - mvLog(MVLOG_DEBUG, "S%d: Not enough space in stream '%s' for %ld: PKT %ld, FILL %ld SIZE %ld\n", + mvLog(MVLOG_DEBUG, "S%d: Not enough space in stream '%s' for %ld: PKT %ld, FILL %ld SIZE %ld", stream->id, stream->name, size, stream->remoteFillPacketLevel, stream->remoteFillLevel, stream->writeSize); return 0; } @@ -592,7 +592,7 @@ streamPacketDesc_t* movePacketFromStream(streamDesc_t* stream) ret = malloc(sizeof(streamPacketDesc_t)); if (!ret) { - mvLog(MVLOG_FATAL, "out of memory to move packet from stream\n"); + mvLog(MVLOG_FATAL, "out of memory to move packet from stream"); return NULL; } ret->data = NULL; @@ -617,12 +617,12 @@ int releasePacketFromStream(streamDesc_t* stream, uint32_t* releasedSize) { streamPacketDesc_t* currPack = &stream->packets[stream->firstPacket]; if(stream->blockedPackets == 0){ - mvLog(MVLOG_ERROR,"There is no packet to release\n"); + mvLog(MVLOG_ERROR,"There is no packet to release"); return 0; // ignore this, although this is a big problem on application side } stream->localFillLevel -= currPack->length; - mvLog(MVLOG_DEBUG, "S%d: Got release of %ld , current local fill level is %ld out of %ld %ld\n", + mvLog(MVLOG_DEBUG, "S%d: Got release of %ld , current local fill level is %ld out of %ld %ld", stream->id, currPack->length, stream->localFillLevel, stream->readSize, stream->writeSize); XLinkPlatformDeallocateData(currPack->data, @@ -638,7 +638,7 @@ int releasePacketFromStream(streamDesc_t* stream, uint32_t* releasedSize) int releaseSpecificPacketFromStream(streamDesc_t* stream, uint32_t* releasedSize, uint8_t* data) { if (stream->blockedPackets == 0) { - mvLog(MVLOG_ERROR,"There is no packet to release\n"); + mvLog(MVLOG_ERROR,"There is no packet to release"); return 0; // ignore this, although this is a big problem on application side } @@ -655,12 +655,12 @@ int releaseSpecificPacketFromStream(streamDesc_t* stream, uint32_t* releasedSize streamPacketDesc_t* currPack = &stream->packets[packetId]; if (currPack->length == 0) { - mvLog(MVLOG_ERROR, "Packet with ID %d is empty\n", packetId); + mvLog(MVLOG_ERROR, "Packet with ID %d is empty", packetId); } stream->localFillLevel -= currPack->length; - mvLog(MVLOG_DEBUG, "S%d: Got release of %ld , current local fill level is %ld out of %ld %ld\n", + mvLog(MVLOG_DEBUG, "S%d: Got release of %ld , current local fill level is %ld out of %ld %ld", stream->id, currPack->length, stream->localFillLevel, stream->readSize, stream->writeSize); XLinkPlatformDeallocateData(currPack->data, ALIGN_UP_INT32((int32_t) currPack->length, __CACHE_LINE_SIZE), __CACHE_LINE_SIZE); @@ -703,7 +703,7 @@ int addNewPacketToStream(streamDesc_t* stream, void* buffer, uint32_t size) { int handleIncomingEvent(xLinkEvent_t* event) { //this function will be dependent whether this is a client or a Remote //specific actions to this peer - mvLog(MVLOG_DEBUG, "%s, size %u, streamId %u.\n", TypeToStr(event->header.type), event->header.size, event->header.streamId); + mvLog(MVLOG_DEBUG, "%s, size %u, streamId %u.", TypeToStr(event->header.type), event->header.size, event->header.streamId); ASSERT_XLINK(event->header.type >= XLINK_WRITE_REQ && event->header.type != XLINK_REQUEST_LAST @@ -719,19 +719,19 @@ int handleIncomingEvent(xLinkEvent_t* event) { ASSERT_XLINK(stream); stream->localFillLevel += event->header.size; - mvLog(MVLOG_DEBUG,"S%u: Got write of %u, current local fill level is %u out of %u %u\n", + mvLog(MVLOG_DEBUG,"S%u: Got write of %u, current local fill level is %u out of %u %u", event->header.streamId, event->header.size, stream->localFillLevel, stream->readSize, stream->writeSize); void* buffer = XLinkPlatformAllocateData(ALIGN_UP(event->header.size, __CACHE_LINE_SIZE), __CACHE_LINE_SIZE); XLINK_OUT_WITH_LOG_IF(buffer == NULL, - mvLog(MVLOG_FATAL,"out of memory to receive data of size = %zu\n", event->header.size)); + mvLog(MVLOG_FATAL,"out of memory to receive data of size = %zu", event->header.size)); const int sc = XLinkPlatformRead(&event->deviceHandle, buffer, event->header.size); - XLINK_OUT_WITH_LOG_IF(sc < 0, mvLog(MVLOG_ERROR,"%s() Read failed %d\n", __func__, sc)); + XLINK_OUT_WITH_LOG_IF(sc < 0, mvLog(MVLOG_ERROR,"%s() Read failed %d", __func__, sc)); event->data = buffer; XLINK_OUT_WITH_LOG_IF(addNewPacketToStream(stream, buffer, event->header.size), - mvLog(MVLOG_WARN,"No more place in stream. release packet\n")); + mvLog(MVLOG_WARN,"No more place in stream. release packet")); rc = 0; XLINK_OUT: diff --git a/src/shared/XLinkLog.c b/src/shared/XLinkLog.c index ba5c002..cc1ac15 100644 --- a/src/shared/XLinkLog.c +++ b/src/shared/XLinkLog.c @@ -124,7 +124,7 @@ void XLinkLogGetThreadName(char *buf, size_t len){ #ifdef __shave__ __attribute__((section(".laststage"))) #endif -int __attribute__ ((unused)) +int MVLOG_ATTRIBUTE((unused)) logprintf(mvLog_t curLogLvl, mvLog_t lvl, const char * func, const int line, const char * format, ...) { diff --git a/src/shared/XLinkPrivateDefines.c b/src/shared/XLinkPrivateDefines.c index 910992a..2df4644 100644 --- a/src/shared/XLinkPrivateDefines.c +++ b/src/shared/XLinkPrivateDefines.c @@ -37,7 +37,7 @@ static streamId_t getNextStreamUniqueId(xLinkDesc_t *link); streamId_t XLinkAddOrUpdateStream(void *fd, const char *name, uint32_t writeSize, uint32_t readSize, streamId_t forcedId) { - mvLog(MVLOG_DEBUG, "name: %s, writeSize: %ld, readSize: %ld, forcedId: %ld\n", + mvLog(MVLOG_DEBUG, "name: %s, writeSize: %ld, readSize: %ld, forcedId: %ld", name, writeSize, readSize, forcedId); streamId_t retStreamId = INVALID_STREAM_ID; @@ -63,7 +63,7 @@ streamId_t XLinkAddOrUpdateStream(void *fd, const char *name, } // XLINK_OUT_WITH_LOG_IF(streamAlreadyExists, - // mvLog(MVLOG_ERROR, "Stream with name:%s already exists: id=%ld\n", name, stream->id)); + // mvLog(MVLOG_ERROR, "Stream with name:%s already exists: id=%ld", name, stream->id)); // if(streamAlreadyExists){ // //stream->writeSize = writeSize; @@ -104,7 +104,7 @@ streamId_t XLinkAddOrUpdateStream(void *fd, const char *name, } retStreamId = stream->id; - mvLog(MVLOG_DEBUG, "The stream \"%s\" created, id = %u, writeSize = %d, readSize = %d\n", + mvLog(MVLOG_DEBUG, "The stream \"%s\" created, id = %u, writeSize = %d, readSize = %d", stream->name, stream->id, stream->writeSize, stream->readSize); XLINK_OUT: @@ -136,7 +136,7 @@ XLinkError_t getNextAvailableStreamIndex(xLinkDesc_t* link, int* out_id) } } - mvLog(MVLOG_DEBUG,"No next available stream!\n"); + mvLog(MVLOG_DEBUG,"No next available stream!"); return X_LINK_ERROR; } @@ -166,7 +166,7 @@ streamId_t getNextStreamUniqueId(xLinkDesc_t *link) curr = 0; } } while (start != curr); - mvLog(MVLOG_ERROR, "%s():- no next available stream unique id!\n", __func__); + mvLog(MVLOG_ERROR, "%s():- no next available stream unique id!", __func__); return INVALID_STREAM_ID; } diff --git a/src/shared/XLinkPrivateFields.c b/src/shared/XLinkPrivateFields.c index 064b8a0..b9ee8c4 100644 --- a/src/shared/XLinkPrivateFields.c +++ b/src/shared/XLinkPrivateFields.c @@ -76,7 +76,7 @@ streamDesc_t* getStreamById(void* fd, streamId_t id) while(((rc = XLink_sem_wait(&link->availableStreams[stream].sem)) == -1) && errno == EINTR) continue; if (rc) { - mvLog(MVLOG_ERROR,"can't wait semaphore\n"); + mvLog(MVLOG_ERROR,"can't wait semaphore"); return NULL; } return &link->availableStreams[stream]; @@ -96,7 +96,7 @@ streamDesc_t* getStreamByName(xLinkDesc_t* link, const char* name) while(((rc = XLink_sem_wait(&link->availableStreams[stream].sem)) == -1) && errno == EINTR) continue; if (rc) { - mvLog(MVLOG_ERROR,"can't wait semaphore\n"); + mvLog(MVLOG_ERROR,"can't wait semaphore"); return NULL; } return &link->availableStreams[stream]; @@ -111,13 +111,13 @@ void releaseStream(streamDesc_t* stream) XLink_sem_post(&stream->sem); } else { - mvLog(MVLOG_DEBUG,"trying to release a semaphore for a released stream\n"); + mvLog(MVLOG_DEBUG,"trying to release a semaphore for a released stream"); } } xLinkState_t getXLinkState(xLinkDesc_t* link) { XLINK_RET_ERR_IF(link == NULL, XLINK_NOT_INIT); - mvLog(MVLOG_DEBUG,"%s() link %p link->peerState %d\n", __func__,link, link->peerState); + mvLog(MVLOG_DEBUG,"%s() link %p link->peerState %d", __func__,link, link->peerState); return link->peerState; } diff --git a/src/shared/XLinkStream.c b/src/shared/XLinkStream.c index 0d4d495..5c89e44 100644 --- a/src/shared/XLinkStream.c +++ b/src/shared/XLinkStream.c @@ -17,13 +17,13 @@ XLinkError_t XLinkStreamInitialize( streamDesc_t* stream, streamId_t id, const char* name) { - mvLog(MVLOG_DEBUG, "name: %s, id: %ld\n", name, id); + mvLog(MVLOG_DEBUG, "name: %s, id: %ld", name, id); ASSERT_XLINK(stream); memset(stream, 0, sizeof(*stream)); if (XLink_sem_init(&stream->sem, 0, 0)) { - mvLog(MVLOG_ERROR, "Cannot initialize semaphore\n"); + mvLog(MVLOG_ERROR, "Cannot initialize semaphore"); return X_LINK_ERROR; } @@ -40,7 +40,7 @@ void XLinkStreamReset(streamDesc_t* stream) { } if(XLink_sem_destroy(&stream->sem)) { - mvLog(MVLOG_DEBUG, "Cannot destroy semaphore\n"); + mvLog(MVLOG_DEBUG, "Cannot destroy semaphore"); } // sets all stream fields, including the packets circular buffer to NULL diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ec8dcbd..e23d2bb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,7 @@ macro(add_test test_name test_src) add_executable(${test_name} ${test_src}) target_link_libraries(${test_name} ${TARGET_NAME}) - set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 11) + set_property(TARGET ${test_name} PROPERTY CXX_STANDARD 14) set_property(TARGET ${test_name} PROPERTY CXX_STANDARD_REQUIRED ON) set_property(TARGET ${test_name} PROPERTY CXX_EXTENSIONS OFF) diff --git a/tests/multiple_open_stream.cpp b/tests/multiple_open_stream.cpp index 9772985..cc6af92 100644 --- a/tests/multiple_open_stream.cpp +++ b/tests/multiple_open_stream.cpp @@ -37,7 +37,7 @@ int main() { XLinkInitialize(&gHandler); // Search for booted device - deviceDesc_t deviceDesc, inDeviceDesc; + deviceDesc_t deviceDesc = {}, inDeviceDesc = {}; inDeviceDesc.protocol = X_LINK_ANY_PROTOCOL; inDeviceDesc.state = X_LINK_BOOTED; if(X_LINK_SUCCESS != XLinkFindFirstSuitableDevice(inDeviceDesc, &deviceDesc)){