From 9e0aab54f2387a1f6f3954719fd813e2c55315bd Mon Sep 17 00:00:00 2001 From: Will Hedgecock Date: Tue, 30 Nov 2021 11:16:12 -0600 Subject: [PATCH] Add C test for separate thread closing --- src/test/c/Makefile | 8 +- src/test/c/testOpenClose.c | 186 +++++++++++++++++++++++++++++++------ 2 files changed, 162 insertions(+), 32 deletions(-) diff --git a/src/test/c/Makefile b/src/test/c/Makefile index d05afb0..af2dbdf 100644 --- a/src/test/c/Makefile +++ b/src/test/c/Makefile @@ -1,9 +1,11 @@ # Compiler tools, commands, and flags COMPILE := gcc BUILD_DIR := build +JDK_HOME := $(shell if [ "`uname`" = "Darwin" ]; then echo "`/usr/libexec/java_home`"; else echo "$$JDK_HOME"; fi) INCLUDES := -I"../../main/c/Posix" -I"$(JDK_HOME)/include" -I"$(JDK_HOME)/include/linux" -I"$(JDK_HOME)/include/darwin" -I"$(JDK_HOME)/include/solaris" -CFLAGS := -fPIC -Os -flto -static-libgcc -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 -fuse-linker-plugin -LDFLAGS := -Os -flto -static-libgcc -shared -fuse-linker-plugin -s +CFLAGS := -fPIC -Os -flto -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=0 $(shell if [ "`uname`" != "Darwin" ]; then echo "-static-libgcc -fuse-linker-plugin"; fi) +LDFLAGS := -Os -flto -fuse-linker-plugin $(shell if [ "`uname`" != "Darwin" ]; then echo "-static-libgcc -s"; fi) +LIBRARIES := $(shell if [ "`uname`" = "Darwin" ]; then echo "-framework Cocoa -framework IOKit"; else echo "-pthread"; fi) DELETE := @rm MKDIR := @mkdir -p COPY := @cp @@ -28,7 +30,7 @@ $(BUILD_DIR) : # Build rules for all tests testOpenClose : $(BUILD_DIR)/testOpenClose.o $(BUILD_DIR)/PosixHelperFunctions.o - $(COMPILE) $(LDFLAGS) -o $@ $< + $(COMPILE) $(LDFLAGS) $(LIBRARIES) -o $@ $^ # Suffix rules to get from *.c -> *.o $(BUILD_DIR)/%.o : %.c diff --git a/src/test/c/testOpenClose.c b/src/test/c/testOpenClose.c index a48891c..706032e 100644 --- a/src/test/c/testOpenClose.c +++ b/src/test/c/testOpenClose.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include @@ -30,8 +31,8 @@ // Global static variables -static long portHandle = -1; -static char* comPort = NULL; +static volatile long portHandle = -1, readBufferLength = 0; +static char *comPort = NULL, *readBuffer = NULL; // JNI functionality @@ -44,36 +45,36 @@ bool configTimeouts(long serialPortFD, int timeoutMode, int readTimeout, int wri tcgetattr(serialPortFD, &options); // Set updated port timeouts - if (((timeoutMode & 0x1) > 0) && (readTimeout > 0)) // Read Semi-blocking with timeout + if (((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_SEMI_BLOCKING) > 0) && (readTimeout > 0)) // Read Semi-blocking with timeout { options.c_cc[VMIN] = 0; options.c_cc[VTIME] = readTimeout / 100; } - else if ((timeoutMode & 0x1) > 0) // Read Semi-blocking without timeout + else if ((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_SEMI_BLOCKING) > 0) // Read Semi-blocking without timeout { - options.c_cc[VMIN] = 1; - options.c_cc[VTIME] = 0; + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 0; } - else if (((timeoutMode & 0x10) > 0) && (readTimeout > 0)) // Read Blocking with timeout + else if (((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING) > 0) && (readTimeout > 0)) // Read Blocking with timeout { - options.c_cc[VMIN] = 0; - options.c_cc[VTIME] = readTimeout / 100; + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = readTimeout / 100; } - else if ((timeoutMode & 0x10) > 0) // Read Blocking without timeout + else if ((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING) > 0) // Read Blocking without timeout { - options.c_cc[VMIN] = 1; - options.c_cc[VTIME] = 0; + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 0; } - else if ((timeoutMode & 0x1000) > 0) // Scanner Mode + else if ((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_SCANNER) > 0) // Scanner Mode { - options.c_cc[VMIN] = 1; - options.c_cc[VTIME] = 1; + options.c_cc[VMIN] = 1; + options.c_cc[VTIME] = 1; } - else // Non-blocking + else // Non-blocking { - flags = O_NONBLOCK; - options.c_cc[VMIN] = 0; - options.c_cc[VTIME] = 0; + flags = O_NONBLOCK; + options.c_cc[VMIN] = 0; + options.c_cc[VTIME] = 0; } // Apply changes @@ -108,10 +109,14 @@ bool configPort(long serialPortFD) unsigned char rs485RxDuringTx = false; unsigned char isDtrEnabled = true; unsigned char isRtsEnabled = true; + char xonStartChar = 17; + char xoffStopChar = 19; // Clear any serial port flags and set up raw non-canonical port parameters struct termios options = { 0 }; tcgetattr(serialPortFD, &options); + options.c_cc[VSTART] = (unsigned char)xonStartChar; + options.c_cc[VSTOP] = (unsigned char)xoffStopChar; options.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | INPCK | IGNPAR | IGNCR | ICRNL | IXON | IXOFF); options.c_oflag &= ~OPOST; options.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); @@ -119,23 +124,23 @@ bool configPort(long serialPortFD) // Update the user-specified port parameters tcflag_t byteSize = (byteSizeInt == 5) ? CS5 : (byteSizeInt == 6) ? CS6 : (byteSizeInt == 7) ? CS7 : CS8; - tcflag_t parity = (parityInt == 0) ? 0 : (parityInt == 1) ? (PARENB | PARODD) : (parityInt == 2) ? PARENB : (parityInt == 3) ? (PARENB | CMSPAR | PARODD) : (PARENB | CMSPAR); + tcflag_t parity = (parityInt == com_fazecast_jSerialComm_SerialPort_NO_PARITY) ? 0 : (parityInt == com_fazecast_jSerialComm_SerialPort_ODD_PARITY) ? (PARENB | PARODD) : (parityInt == com_fazecast_jSerialComm_SerialPort_EVEN_PARITY) ? PARENB : (parityInt == com_fazecast_jSerialComm_SerialPort_MARK_PARITY) ? (PARENB | CMSPAR | PARODD) : (PARENB | CMSPAR); options.c_cflag |= (byteSize | parity | CLOCAL | CREAD); if (!isDtrEnabled || !isRtsEnabled) options.c_cflag &= ~HUPCL; if (!rs485ModeEnabled) options.c_iflag |= BRKINT; - if (stopBitsInt == 3) + if (stopBitsInt == com_fazecast_jSerialComm_SerialPort_TWO_STOP_BITS) options.c_cflag |= CSTOPB; - if (((flowControl & 0x00000010) > 0) || ((flowControl & 0x00000001) > 0)) + if (((flowControl & com_fazecast_jSerialComm_SerialPort_FLOW_CONTROL_CTS_ENABLED) > 0) || ((flowControl & com_fazecast_jSerialComm_SerialPort_FLOW_CONTROL_RTS_ENABLED) > 0)) options.c_cflag |= CRTSCTS; if (byteSizeInt < 8) options.c_iflag |= ISTRIP; if (parityInt != 0) options.c_iflag |= (INPCK | IGNPAR); - if ((flowControl & 0x10000) > 0) + if ((flowControl & com_fazecast_jSerialComm_SerialPort_FLOW_CONTROL_XONXOFF_IN_ENABLED) > 0) options.c_iflag |= IXOFF; - if ((flowControl & 0x100000) > 0) + if ((flowControl & com_fazecast_jSerialComm_SerialPort_FLOW_CONTROL_XONXOFF_OUT_ENABLED) > 0) options.c_iflag |= IXON; // Set baud rate and apply changes @@ -225,7 +230,7 @@ long openPortNative(void) return serialPortFD; } -bool closePortNative(long serialPortFD) +long closePortNative(long serialPortFD) { // Unblock, unlock, and close the port fsync(serialPortFD); @@ -236,10 +241,133 @@ bool closePortNative(long serialPortFD) while (close(serialPortFD) && (errno == EINTR)) errno = 0; serialPortFD = -1; + return -1; +} + +int readBytes(long serialPortFD, char* buffer, long bytesToRead, long offset, int timeoutMode, int readTimeout) +{ + // Ensure that the allocated read buffer is large enough + int numBytesRead, numBytesReadTotal = 0, bytesRemaining = bytesToRead, ioctlResult = 0; + if (bytesToRead > readBufferLength) + { + char *newMemory = (char*)realloc(readBuffer, bytesToRead); + if (!newMemory) + return -1; + readBuffer = newMemory; + readBufferLength = bytesToRead; + } + + // Infinite blocking mode specified, don't return until we have completely finished the read + if (((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING) > 0) && (readTimeout == 0)) + { + // While there are more bytes we are supposed to read + while (bytesRemaining > 0) + { + // Attempt to read some number of bytes from the serial port + do { errno = 0; numBytesRead = read(serialPortFD, readBuffer + numBytesReadTotal, bytesRemaining); } while ((numBytesRead < 0) && (errno == EINTR)); + if ((numBytesRead == -1) || ((numBytesRead == 0) && (ioctl(serialPortFD, FIONREAD, &ioctlResult) == -1))) + break; + + // Fix index variables + numBytesReadTotal += numBytesRead; + bytesRemaining -= numBytesRead; + } + } + else if ((timeoutMode & com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING) > 0) // Blocking mode, but not indefinitely + { + // Get current system time + struct timeval expireTime = { 0 }, currTime = { 0 }; + gettimeofday(&expireTime, NULL); + expireTime.tv_usec += (readTimeout * 1000); + if (expireTime.tv_usec > 1000000) + { + expireTime.tv_sec += (expireTime.tv_usec * 0.000001); + expireTime.tv_usec = (expireTime.tv_usec % 1000000); + } + + // While there are more bytes we are supposed to read and the timeout has not elapsed + do + { + do { errno = 0; numBytesRead = read(serialPortFD, readBuffer + numBytesReadTotal, bytesRemaining); } while ((numBytesRead < 0) && (errno == EINTR)); + if ((numBytesRead == -1) || ((numBytesRead == 0) && (ioctl(serialPortFD, FIONREAD, &ioctlResult) == -1))) + break; + + // Fix index variables + numBytesReadTotal += numBytesRead; + bytesRemaining -= numBytesRead; + + // Get current system time + gettimeofday(&currTime, NULL); + } while ((bytesRemaining > 0) && ((expireTime.tv_sec > currTime.tv_sec) || ((expireTime.tv_sec == currTime.tv_sec) && (expireTime.tv_usec > currTime.tv_usec)))); + } + else // Semi- or non-blocking specified + { + // Read from the port + do { errno = 0; numBytesRead = read(serialPortFD, readBuffer, bytesToRead); } while ((numBytesRead < 0) && (errno == EINTR)); + if (numBytesRead > 0) + numBytesReadTotal = numBytesRead; + } + + // Return number of bytes read if successful + memcpy(buffer, readBuffer, numBytesReadTotal); + return (numBytesRead == -1) ? -1 : numBytesReadTotal; +} + +void* readingThread(void *arg) +{ + // Read forever in a loop while the port is open + char readBuffer[2048]; + while (portHandle > 0) + { + printf("\nBeginning blocking read...\n"); + int numBytesRead = readBytes(portHandle, readBuffer, sizeof(readBuffer), 0, com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING, 0); + printf("Read %d bytes\n", numBytesRead); + } + return NULL; +} +int testCloseSeparateThread(void) +{ + // Open the serial port + if ((portHandle = openPortNative()) <= 0) + { + printf("ERROR: Could not open port: %s\n", comPort); + return -1; + } + printf("Port opened: %s\n", comPort); + + // Configure the serial port for indefinitely blocking reads + if (!configTimeouts(portHandle, com_fazecast_jSerialComm_SerialPort_TIMEOUT_READ_BLOCKING, 0, 0, 0)) + { + printf("ERROR: Could not configure port timeouts\n"); + return -2; + } + printf("Blocking read timeouts successfully configured\n"); + + // Start a new thread to continuously read from the serial port for 5 seconds + pthread_t pid; + if (pthread_create(&pid, NULL, &readingThread, NULL)) + { + printf("ERROR: Could not create a reading thread\n"); + return -3; + } + sleep(5); + + // Close the serial port + printf("\nAttempting to close serial port from a separate thread...\n"); + if ((portHandle = closePortNative(portHandle)) > 0) + { + printf("ERROR: Could not close port: %s\n", comPort); + return -4; + } + printf("Port closed\n"); + + // Wait for the reading thread to return + pthread_join(pid, NULL); + printf("Reading thread successfully returned\n"); return 0; } -int testFull(void) +int testSimpleOpenClose(void) { // Open the serial port if ((portHandle = openPortNative()) <= 0) @@ -250,8 +378,7 @@ int testFull(void) printf("Port opened\n"); // Close the serial port - closePortNative(portHandle); - if (portHandle > 0) + if ((portHandle = closePortNative(portHandle)) > 0) { printf("ERROR: Could not close port: %s\n", comPort); return -3; @@ -271,5 +398,6 @@ int main(int argc, char *argv[]) comPort = argv[1]; // Perform one of the above open/close tests - return testFull(); + return testCloseSeparateThread(); + //return testSimpleOpenClose(); }