From b2f67f7ce1b55835cbc598e33b2b62800e4fa8fa Mon Sep 17 00:00:00 2001 From: Kyle Johannes Date: Tue, 14 Nov 2023 14:09:56 -0500 Subject: [PATCH] wip: android driver --- CMakeLists.txt | 22 +- api/icsneocpp/icsneocpp.cpp | 3 +- device/devicefinder.cpp | 8 + examples/java/src/icsneojava.java | 1 + include/icsneo/api/event.h | 9 +- include/icsneo/platform/android/androidusb.h | 56 +++++ platform/posix/android/androidusb.cpp | 238 +++++++++++++++++++ platform/posix/android/cdcacmandroid.cpp | 0 platform/posix/cdcacm.cpp | 4 + 9 files changed, 332 insertions(+), 9 deletions(-) create mode 100644 include/icsneo/platform/android/androidusb.h create mode 100644 platform/posix/android/androidusb.cpp create mode 100644 platform/posix/android/cdcacmandroid.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3be4b0e..5514064 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,10 +5,10 @@ cmake_policy(SET CMP0074 NEW) option(LIBICSNEO_BUILD_TESTS "Build all tests." OFF) option(LIBICSNEO_BUILD_DOCS "Build documentation. Don't use in Visual Studio." OFF) -option(LIBICSNEO_BUILD_EXAMPLES "Build examples." ON) +option(LIBICSNEO_BUILD_EXAMPLES "Build examples." OFF) option(LIBICSNEO_BUILD_ICSNEOC "Build dynamic C library" ON) option(LIBICSNEO_BUILD_ICSNEOC_STATIC "Build static C library" ON) -option(LIBICSNEO_BUILD_ICSNEOLEGACY "Build icsnVC40 compatibility library" ON) +option(LIBICSNEO_BUILD_ICSNEOLEGACY "Build icsnVC40 compatibility library" OFF) set(LIBICSNEO_NPCAP_INCLUDE_DIR "" CACHE STRING "Npcap include directory; set to build with Npcap") # Device Drivers @@ -16,11 +16,12 @@ set(LIBICSNEO_NPCAP_INCLUDE_DIR "" CACHE STRING "Npcap include directory; set to # it is only relevant for communication between Linux and # CoreMini from the onboard processor of the device. option(LIBICSNEO_ENABLE_FIRMIO "Enable communication between Linux and CoreMini within the same device" OFF) -option(LIBICSNEO_ENABLE_RAW_ETHERNET "Enable devices which communicate over raw ethernet" ON) -option(LIBICSNEO_ENABLE_CDCACM "Enable devices which communicate over USB CDC ACM" ON) -option(LIBICSNEO_ENABLE_FTDI "Enable devices which communicate over USB FTDI2XX" ON) +option(LIBICSNEO_ENABLE_RAW_ETHERNET "Enable devices which communicate over raw ethernet" OFF) +option(LIBICSNEO_ENABLE_CDCACM "Enable devices which communicate over USB CDC ACM" OFF) +option(LIBICSNEO_ENABLE_ANDROIDUSB "Enable devices which communicate over USB CDC ACM on Android" ON) +option(LIBICSNEO_ENABLE_FTDI "Enable devices which communicate over USB FTDI2XX" OFF) option(LIBICSNEO_ENABLE_TCP "Enable devices which communicate over TCP" OFF) -option(LIBICSNEO_ENABLE_FTD3XX "Enable devices which communicate over USB FTD3XX" ON) +option(LIBICSNEO_ENABLE_FTD3XX "Enable devices which communicate over USB FTD3XX" OFF) if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 17) @@ -159,6 +160,12 @@ else() # Darwin or Linux endif() endif() endif() + + if(LIBICSNEO_ENABLE_ANDROIDUSB) + list(APPEND PLATFORM_SRC + platform/posix/android/androidusb.cpp + ) + endif() endif() if(LIBICSNEO_ENABLE_FTD3XX) @@ -360,6 +367,9 @@ if(LIBICSNEO_ENABLE_TCP) endif() endif() +if(ANDROID) + #target_link_libraries(icsneocpp PRIVATE android log) +endif() # fatfs add_subdirectory(third-party/fatfs) set_property(TARGET fatfs PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/api/icsneocpp/icsneocpp.cpp b/api/icsneocpp/icsneocpp.cpp index e3200aa..ce7e35d 100644 --- a/api/icsneocpp/icsneocpp.cpp +++ b/api/icsneocpp/icsneocpp.cpp @@ -1,5 +1,6 @@ #include "icsneo/icsneocpp.h" #include "icsneo/device/devicefinder.h" +#include using namespace icsneo; @@ -45,4 +46,4 @@ void icsneo::SetEventLimit(size_t newLimit) { size_t icsneo::GetEventLimit() { return EventManager::GetInstance().getEventLimit(); -} \ No newline at end of file +} diff --git a/device/devicefinder.cpp b/device/devicefinder.cpp index a94b2b7..a2e7017 100644 --- a/device/devicefinder.cpp +++ b/device/devicefinder.cpp @@ -15,6 +15,10 @@ #include "icsneo/platform/cdcacm.h" #endif +#ifdef ICSNEO_ENABLE_ANDROIDUSB +#include "icsneo/platform/cdcacm.h" +#endif + #ifdef ICSNEO_ENABLE_FTDI #include "icsneo/platform/ftdi.h" #endif @@ -65,6 +69,10 @@ std::vector> DeviceFinder::FindAll() { #ifdef ICSNEO_ENABLE_CDCACM CDCACM::Find(newDriverFoundDevices); + #endif + + #ifdef ICSNEO_ENABLE_ANDROIDUSB + ANDROIDUSB::Find(newDriverFoundDevices): #endif #ifdef ICSNEO_ENABLE_FTDI diff --git a/examples/java/src/icsneojava.java b/examples/java/src/icsneojava.java index 8d431d3..e661c2f 100644 --- a/examples/java/src/icsneojava.java +++ b/examples/java/src/icsneojava.java @@ -1,3 +1,4 @@ +package com.example.icsneojava; /* ---------------------------------------------------------------------------- * This file was automatically generated by SWIG (http://www.swig.org). * Version 4.0.0 diff --git a/include/icsneo/api/event.h b/include/icsneo/api/event.h index 5e40b79..2bd01a4 100644 --- a/include/icsneo/api/event.h +++ b/include/icsneo/api/event.h @@ -122,6 +122,11 @@ public: GetIfAddrsError = 0x3108, SendToError = 0x3109, MDIOMessageExceedsMaxLength = 0x3110, + DriverTTYPathEmpty = 0x3120, + DriverWasNotNegOne = 0x3121, + DriverTCGetAddrFail = 0x3122, + DriverTCSetAddrFail= 0x3123, + // FTD3XX FTOK = 0x4000, // placeholder @@ -160,8 +165,8 @@ public: NoErrorFound = 0xFFFFFFFD, TooManyEvents = 0xFFFFFFFE, - Unknown = 0xFFFFFFFF - }; + Unknown = 0xFFFFFFFF, + }; enum class Severity : uint8_t { Any = 0, // Used for filtering, should not appear in data EventInfo = 0x10, diff --git a/include/icsneo/platform/android/androidusb.h b/include/icsneo/platform/android/androidusb.h new file mode 100644 index 0000000..3155221 --- /dev/null +++ b/include/icsneo/platform/android/androidusb.h @@ -0,0 +1,56 @@ +#ifndef __CDCACM_ANDROID_H_ +#define __CDCACM_ANDROID_H_ + +#ifdef __cplusplus + +#include "icsneo/communication/driver.h" +#include "icsneo/device/neodevice.h" +#include "icsneo/api/eventmanager.h" +#include +#include +#include +#include +#include + +namespace icsneo { + +class ANDROIDUSB : public Driver { +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(); + static void Find(std::vector& found); + + bool open() override; + bool isOpen() override; + bool close() override; + + void modeChangeIncoming() override; + void awaitModeChangeComplete() override; + + static void addSystemFD(int fd); + static void 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; + + static std::string HandleToTTY(neodevice_handle_t handle); + + void readTask() override; + void writeTask() override; + bool fdIsValid(); +}; + +} + +#endif // __cplusplus + +#endif //LIBICSNEO_TEST_ANDROIDUSB_H diff --git a/platform/posix/android/androidusb.cpp b/platform/posix/android/androidusb.cpp new file mode 100644 index 0000000..344e905 --- /dev/null +++ b/platform/posix/android/androidusb.cpp @@ -0,0 +1,238 @@ +#include "icsneo/platform/android/androidusb.h" +#include "icsneo/device/founddevice.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace icsneo; + +ANDROIDUSB::~ANDROIDUSB() { + awaitModeChangeComplete(); + if(isOpen()) + close(); +} + +bool ANDROIDUSB::open() { + 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 fd >= 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; + + if(modeChanging) { + // We're expecting this inode to go away after we close the device + // In order to block waiting for this to happen, we first need to + // get the inode. + struct stat fileStat = {}; + if(fstat(fd, &fileStat) >= 0) + disallowedInode = fileStat.st_ino; + } + + int ret = ::close(fd); + fd = -1; + + uint8_t flush; + WriteOperation flushop; + while (readQueue.try_dequeue(flush)) {} + while (writeQueue.try_dequeue(flushop)) {} + + if(modeChanging) { + modeChanging = false; + return open(); // Reopen the reenumerated device + } + + if(ret == 0) { + return true; + } else { + report(APIEvent::Type::DriverFailedToClose, APIEvent::Severity::Error); + return false; + } +} + + +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); + auto bytesRead = ::read(fd, readbuf, READ_BUFFER_SIZE); + 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(modeChanging) { + // We were expecting a disconnect for reenumeration + modeChangeThread = std::thread([this] { + modeChangeCV.notify_all(); + // Requesting thread is responsible for calling close. This allows for more flexibility + }); + break; + } 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 = ::write(fd, writeOp.bytes.data() + totalWritten, writeSize); + 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() { + struct termios tty = {}; + return tcgetattr(fd, &tty) == 0 ? true : false; +} + +void ANDROIDUSB::Find(std::vector& found) { + for (auto& each: systemFDs) { + FoundDevice device; + device.handle = 0; + device.productId = "ttyPid"; + //device.serial = {}; + //device.serial[getter.getSerial().copy(device.serial, sizeof(device.serial)-1)] = '\0'; + + // 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 + } +} diff --git a/platform/posix/android/cdcacmandroid.cpp b/platform/posix/android/cdcacmandroid.cpp new file mode 100644 index 0000000..e69de29 diff --git a/platform/posix/cdcacm.cpp b/platform/posix/cdcacm.cpp index 0cf7bed..a4eb5ed 100644 --- a/platform/posix/cdcacm.cpp +++ b/platform/posix/cdcacm.cpp @@ -29,6 +29,7 @@ bool CDCACM::open() { const std::string& ttyPath = HandleToTTY(device.handle); if(ttyPath.empty()) { report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + report(APIEvent::Type::DriverTTYPathEmpty, APIEvent::Severity::Error); return false; } @@ -60,6 +61,7 @@ bool CDCACM::open() { if(!isOpen()) { //std::cout << "Open of " << ttyPath.c_str() << " failed with " << strerror(errno) << ' '; report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + report(APIEvent::Type::DriverWasNotNegOne, APIEvent::Severity::Error); return false; } @@ -69,6 +71,7 @@ bool CDCACM::open() { if(tcgetattr(fd, &tty) != 0) { close(); report(APIEvent::Type::DriverFailedToOpen, APIEvent::Severity::Error); + report(APIEvent::Type::DriverTCGetAddrFail, APIEvent::Severity::Error); return false; } @@ -92,6 +95,7 @@ bool CDCACM::open() { 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; }