diff --git a/CMakeLists.txt b/CMakeLists.txt index 5514064..7ef5838 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -118,6 +118,12 @@ if(WIN32) platform/windows/vcp.cpp ) endif() + +elseif(LIBICSNEO_ENABLE_ANDROIDUSB) + list(APPEND PLATFORM_SRC + platform/android/androidusb.cpp + ) + else() # Darwin or Linux set(PLATFORM_SRC) @@ -168,6 +174,7 @@ else() # Darwin or Linux endif() endif() + if(LIBICSNEO_ENABLE_FTD3XX) if(NOT FTD3XX_ROOT) # allow system override include(FetchContent) @@ -367,9 +374,14 @@ if(LIBICSNEO_ENABLE_TCP) endif() endif() -if(ANDROID) - #target_link_libraries(icsneocpp PRIVATE android log) +if(LIBICSNEO_ENABLE_ANDROIDUSB) + add_library(libusb SHARED IMPORTED) + set_target_properties(libusb PROPERTIES IMPORTED_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/third-party/libusb/android/libs/${ANDROID_ABI}/libusb1.0.so) + target_include_directories(icsneocpp PRIVATE third-party/libusb/libusb) + target_compile_definitions(icsneocpp PRIVATE ICSNEO_ENABLE_ANDROIDUSB) + target_link_libraries(icsneocpp PRIVATE android log libusb) endif() + # fatfs add_subdirectory(third-party/fatfs) set_property(TARGET fatfs PROPERTY POSITION_INDEPENDENT_CODE ON) @@ -424,6 +436,11 @@ if(LIBICSNEO_BUILD_ICSNEOC) ) target_link_libraries(icsneoc PRIVATE icsneocpp) target_compile_features(icsneoc PRIVATE cxx_auto_type cxx_constexpr cxx_lambdas cxx_nullptr cxx_range_for cxx_rvalue_references cxx_sizeof_member cxx_strong_enums) + if(LIBICSNEO_ENABLE_ANDROIDUSB) + target_link_libraries(icsneoc PRIVATE android log) + target_include_directories(icsneoc PRIVATE third-party/libusb/libusb) + target_compile_definitions(icsneoc PRIVATE ICSNEO_ENABLE_ANDROIDUSB) + endif() endif() if(LIBICSNEO_BUILD_ICSNEOC_STATIC) diff --git a/api/icsneoc/icsneoc.cpp b/api/icsneoc/icsneoc.cpp index 5717750..b235b6b 100644 --- a/api/icsneoc/icsneoc.cpp +++ b/api/icsneoc/icsneoc.cpp @@ -4,6 +4,10 @@ #define ICSNEOC_MAKEDLL +#ifdef ICSNEO_ENABLE_ANDROIDUSB +#include "icsneo/platform/android/androidusb.h" +#endif + #include "icsneo/icsneoc.h" #include "icsneo/icsneocpp.h" #include "icsneo/platform/dynamiclib.h" @@ -740,4 +744,13 @@ int icsneo_getDeviceStatus(const neodevice_t* device, void* status, size_t* size *size = rawMessage->data.size(); return true; -} \ No newline at end of file +} +#ifdef ICSNEO_ENABLE_ANDROIDUSB +bool icsneo_addSysFileDescriptor(int sysFd) { + return icsneo::ANDROIDUSB::addSystemFD(sysFd); + +} +bool icsneo_removeSysFileDescriptor(int sysFd) { + return icsneo::ANDROIDUSB::removeSystemFD(sysFd); +} +#endif \ No newline at end of file diff --git a/device/devicefinder.cpp b/device/devicefinder.cpp index a2e7017..e6e67b7 100644 --- a/device/devicefinder.cpp +++ b/device/devicefinder.cpp @@ -16,7 +16,7 @@ #endif #ifdef ICSNEO_ENABLE_ANDROIDUSB -#include "icsneo/platform/cdcacm.h" +#include "icsneo/platform/android/androidusb.h" #endif #ifdef ICSNEO_ENABLE_FTDI @@ -72,7 +72,7 @@ std::vector> DeviceFinder::FindAll() { #endif #ifdef ICSNEO_ENABLE_ANDROIDUSB - ANDROIDUSB::Find(newDriverFoundDevices): + ANDROIDUSB::Find(newDriverFoundDevices); #endif #ifdef ICSNEO_ENABLE_FTDI diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 2bd01a4..1afcac1 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -125,7 +125,7 @@ public: DriverTTYPathEmpty = 0x3120, DriverWasNotNegOne = 0x3121, DriverTCGetAddrFail = 0x3122, - DriverTCSetAddrFail= 0x3123, + DriverTCSetAddrFail = 0x3123, // FTD3XX @@ -163,10 +163,15 @@ public: FTIncorrectDevicePath = FTOK + 31, FTOtherError = FTOK + 32, + // Android USB + AndroidUSBLibusbInitFailed = 0x5000, + AndroidUSBFDWrapFailed = 0x5001, + NoErrorFound = 0xFFFFFFFD, TooManyEvents = 0xFFFFFFFE, Unknown = 0xFFFFFFFF, - }; + }; + enum class Severity : uint8_t { Any = 0, // Used for filtering, should not appear in data EventInfo = 0x10, diff --git a/include/icsneo/icsneoc.h b/include/icsneo/icsneoc.h index 58da0f1..3435e2f 100644 --- a/include/icsneo/icsneoc.h +++ b/include/icsneo/icsneoc.h @@ -837,6 +837,11 @@ extern bool icsneo_getRTC(const neodevice_t* device, uint64_t* output); */ extern bool icsneo_setRTC(const neodevice_t* device, uint64_t input); +#ifdef ICSNEO_ENABLE_ANDROIDUSB +extern bool icsneo_addSysFileDescriptor(int sysFd); +extern bool icsneo_removeSysFileDescriptor(int sysFd); +#endif + #ifdef __cplusplus } // extern "C" #endif diff --git a/include/icsneo/platform/android/androidusb.h b/include/icsneo/platform/android/androidusb.h index 3155221..906560f 100644 --- a/include/icsneo/platform/android/androidusb.h +++ b/include/icsneo/platform/android/androidusb.h @@ -1,11 +1,12 @@ -#ifndef __CDCACM_ANDROID_H_ -#define __CDCACM_ANDROID_H_ +#ifndef __ANDROIDUSB_H_ +#define __ANDROIDUSB_H_ #ifdef __cplusplus #include "icsneo/communication/driver.h" #include "icsneo/device/neodevice.h" #include "icsneo/api/eventmanager.h" +#include "libusb.h" #include #include #include @@ -19,7 +20,11 @@ public: /** * Note: This is a driver for all devices which use Android CDC_ACM */ - ANDROIDUSB(const device_eventhandler_t& err, neodevice_t& forDevice) : Driver(err), device(forDevice) {} + ANDROIDUSB(const device_eventhandler_t& err, neodevice_t& forDevice) + : Driver(err), device(forDevice) { + if(auto key = systemFDs.find(device.handle); key != systemFDs.end()) + libusbDeviceHandle = key->second; + } ~ANDROIDUSB(); static void Find(std::vector& found); @@ -27,22 +32,15 @@ public: bool isOpen() override; bool close() override; - void modeChangeIncoming() override; - void awaitModeChangeComplete() override; - - static void addSystemFD(int fd); - static void removeSystemFD(int fd); + static bool addSystemFD(int fd); + static bool removeSystemFD(int fd); private: neodevice_t& device; - static std::unordered_map systemFDs; - std::optional disallowedInode; - std::atomic modeChanging{false}; - std::thread modeChangeThread; - std::mutex modeChangeMutex; - std::condition_variable modeChangeCV; + libusb_context *ctx = nullptr; + libusb_device_handle *libusbDeviceHandle = nullptr; - static std::string HandleToTTY(neodevice_handle_t handle); + inline static std::unordered_map systemFDs; //android FD, libusb handle void readTask() override; void writeTask() override; diff --git a/platform/android/androidusb.cpp b/platform/android/androidusb.cpp new file mode 100644 index 0000000..d91b8f2 --- /dev/null +++ b/platform/android/androidusb.cpp @@ -0,0 +1,279 @@ +#include "icsneo/platform/android/androidusb.h" +#include "icsneo/device/founddevice.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "libusb.h" +#include +#define LOG_TAG "libicsneo_test_androidusb" +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) + +using namespace icsneo; + +ANDROIDUSB::~ANDROIDUSB() { + if (isOpen()) + close(); +} + +bool ANDROIDUSB::addSystemFD(int sysFd) { + if ((sysFd != -1) && (systemFDs[sysFd] == nullptr)) { + return true; + } + return false; +} + +bool ANDROIDUSB::removeSystemFD(int sysFd) { + if (sysFd != -1) { + return systemFDs.erase(sysFd); + } + return false; +} + +bool ANDROIDUSB::open() { + int ret = 0; + if (!isOpen()) { + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + return false; + } + + /* + struct termios tty = {}; + struct termios compare = {}; + + if(tcgetattr(fd, &tty) != 0) { + close(); + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + report(APIEvent::Type::DriverTCGetAddrFail, APIEvent::Severity::Error); + return false; + } + + tty.c_cflag |= (CLOCAL | CREAD); // Ignore modem controls + tty.c_cflag &= ~CSIZE; + tty.c_cflag |= CS8; // 8-bit characters + tty.c_cflag &= ~PARENB; // No parity bit + tty.c_cflag &= ~CSTOPB; // One stop bit + tty.c_cflag &= ~CRTSCTS; // No hardware flow control + + // Non-canonical mode + tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); + tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); + tty.c_oflag &= ~OPOST; + + // Fetch bytes as they become available + // See http://man7.org/linux/man-pages/man3/termios.3.html + tty.c_cc[VMIN] = 0; + tty.c_cc[VTIME] = 1; // 100ms timeout (1 decisecond, what?) + + if(tcsetattr(fd, TCSAFLUSH, &tty) != 0) { // Flushes input and output buffers as well as setting settings + close(); + report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + report(APIEvent::Type::DriverTCSetAddrFail, APIEvent::Severity::Error); + return false; + } + + if(tcgetattr(fd, &compare) != 0 || memcmp(&tty, &compare, sizeof(struct termios)) != 0) { + close(); + return false; + } + */ + + // Create threads + readThread = std::thread(&ANDROIDUSB::readTask, this); + writeThread = std::thread(&ANDROIDUSB::writeTask, this); + + return true; +} + +bool ANDROIDUSB::isOpen() { + return device.handle >= 0; // Negative fd indicates error or not opened yet +} + +bool ANDROIDUSB::close() { + if(!isOpen() && !isDisconnected()) { + report(APIEvent::Type::DeviceCurrentlyClosed, APIEvent::Severity::Error); + return false; + } + + closing = true; + + if(readThread.joinable()) + readThread.join(); + + if(writeThread.joinable()) + writeThread.join(); + + closing = false; + disconnected = false; + + systemFDs[device.handle] = nullptr; + device.handle = -1; + + uint8_t flush; + WriteOperation flushop; + while (readQueue.try_dequeue(flush)) {} + while (writeQueue.try_dequeue(flushop)) {} + + return true; +} + +void ANDROIDUSB::readTask() { + //constexpr size_t READ_BUFFER_SIZE = 2048; + //uint8_t readbuf[READ_BUFFER_SIZE]; + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + while(!closing && !isDisconnected()) { + //fd_set rfds = {0}; + //struct timeval tv = {0}; + //FD_SET(fd, &rfds); + //tv.tv_usec = 50000; // 50ms + //::select(fd + 1, &rfds, NULL, NULL, &tv); + ssize_t bytesRead = 0; // ::read(fd, readbuf, READ_BUFFER_SIZE); + + //libusb bulk transfer? + //todo! + + if(bytesRead > 0) { +#if 0 // Perhaps helpful for debugging :) + std::cout << "Read data: (" << bytesRead << ')' << std::hex << std::endl; + for(int i = 0; i < bytesRead; i += 16) { + for(int j = 0; j < std::min(bytesRead - i, 16); j++) + std::cout << std::setw(2) << std::setfill('0') << uint32_t(readbuf[i+j]) << ' '; + std::cout << std::endl; + } + std::cout << std::dec << std::endl; +#endif + + //readQueue.enqueue_bulk(readbuf, bytesRead); + } else { + if(!closing && !fdIsValid() && !isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } + } +} + +void ANDROIDUSB::writeTask() { + WriteOperation writeOp; + EventManager::GetInstance().downgradeErrorsOnCurrentThread(); + while(!closing && !isDisconnected()) { + if(!writeQueue.wait_dequeue_timed(writeOp, std::chrono::milliseconds(100))) + continue; + + const ssize_t totalWriteSize = (ssize_t)writeOp.bytes.size(); + ssize_t totalWritten = 0; + while(totalWritten < totalWriteSize) { + const ssize_t writeSize = totalWriteSize - totalWritten; + ssize_t actualWritten = writeSize; //::write(fd, writeOp.bytes.data() + totalWritten, writeSize); + + //libusb bulk transfer? + //todo! + + if(actualWritten != writeSize) { + // If we partially wrote, it's probably EAGAIN but it won't have been set + // so don't signal an error unless it's < 0, we'll come back around and + // get a -1 to see the real error. + if(errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { + // We filled the TX FIFO, use select to wait for it to become available again + //fd_set wfds = {0}; + //struct timeval tv = {0}; + //FD_SET(fd, &wfds); + //tv.tv_usec = 50000; // 50ms + //::select(fd + 1, nullptr, &wfds, nullptr, &tv); + } else if (actualWritten < 0) { + if(!fdIsValid()) { + if(!isDisconnected()) { + disconnected = true; + report(APIEvent::Type::DeviceDisconnected, APIEvent::Severity::Error); + } + } else + report(APIEvent::Type::FailedToWrite, APIEvent::Severity::Error); + break; + } + } + if(actualWritten > 0) { +#if 0 // Perhaps helpful for debugging :) + std::cout << "Wrote data: (" << actualWritten << ')' << std::hex << std::endl; + for(int i = 0; i < actualWritten; i += 16) { + for(int j = 0; j < std::min(actualWritten - i, 16); j++) + std::cout << std::setw(2) << std::setfill('0') << uint32_t(writeOp.bytes[totalWritten+i+j]) << ' '; + std::cout << std::endl; + } + std::cout << std::dec << std::endl; +#endif + + totalWritten += actualWritten; + } + } + } +} + +bool ANDROIDUSB::fdIsValid() { + //libusb validate FD + return false; +} + +void ANDROIDUSB::Find(std::vector& found) { + int ret = 0; + libusb_context *ctx = NULL; + libusb_device_handle *libusbDeviceHandle = NULL; + ret = libusb_set_option(ctx, LIBUSB_OPTION_NO_DEVICE_DISCOVERY, NULL); + if (ret != LIBUSB_SUCCESS) { + LOGD("libusb_init failed: %d\n", ret); + return; + } + ret = libusb_init(&ctx); + if (ret < 0) { + LOGD("libusb_init failed: %d\n", ret); + return; + } + LOGD("libusb_init completed: %d\n", ret); + LOGD("SYSTEM FDs size: %d\n", systemFDs.size()); + for (auto & [fd, libUsbHandle]: systemFDs) { + ret = libusb_wrap_sys_device(ctx, (intptr_t) fd, &libusbDeviceHandle); + LOGD("Wrapping system FD: %d, return: %s\n", fd, libusb_strerror(ret)); + if (ret == 0) { + FoundDevice device = {}; + struct libusb_device_descriptor desc; + unsigned char outString[255] = {}; + if(libusbDeviceHandle) { + ret = libusb_get_device_descriptor(libusb_get_device(libusbDeviceHandle), &desc); + if (ret == 0) { + device.productId = desc.idProduct; + LOGD("ProductID: %x\n", device.productId); + ret = libusb_get_string_descriptor_ascii(libusbDeviceHandle, desc.iSerialNumber, + outString, sizeof(outString)); + + if (ret > 0) { + std::strncpy(device.serial, reinterpret_cast(outString), + sizeof(device.serial)); + LOGD("Serial Number: %s\n", device.serial); + } else { + LOGD("Serial number fetch failed!\n"); + } + } else { + LOGD("Fetch device descriptor failed for FD: %d, %s\n", fd, libusb_strerror(ret)); + } + } else { + LOGD("LIBUSB Device handle was invalid!\n"); + } + device.handle = fd; + + // Add a factory to make the driver + device.makeDriver = [&](const device_eventhandler_t &report, neodevice_t &device) { + return std::unique_ptr(new ANDROIDUSB(report, device)); + }; + + found.push_back(device); // Finally, add device to search results + libUsbHandle = libusbDeviceHandle; + } + } +} \ No newline at end of file diff --git a/third-party/libusb b/third-party/libusb new file mode 160000 index 0000000..4239bc3 --- /dev/null +++ b/third-party/libusb @@ -0,0 +1 @@ +Subproject commit 4239bc3a50014b8e6a5a2a59df1fff3b7469543b