| /******************************************************************************* |
| * Copyright (c) 2015 QNX Software Systems and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * QNX Software Systems - initial API and implementation |
| * STMicroelectronics |
| *******************************************************************************/ |
| #ifdef __APPLE__ |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <sys/uio.h> |
| #endif |
| #ifndef __MINGW32__ |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <termios.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <strings.h> |
| #include <errno.h> |
| #include <sys/ioctl.h> |
| #ifndef __APPLE__ |
| #include <linux/serial.h> |
| #endif |
| #else |
| #define WIN32_LEAN_AND_MEAN |
| #define UNICODE |
| #include <windows.h> |
| #endif |
| #include <jni.h> |
| #include <org_eclipse_cdt_serial_SerialPort.h> |
| |
| #define FUNC(x) Java_org_eclipse_cdt_serial_SerialPort_##x |
| |
| /** |
| * Use this method to throw an exception when open fails after the OS open |
| * stage. This method obtains the last error from OS to include in the |
| * IOException |
| */ |
| #ifndef __APPLE__ |
| #ifndef __MINGW32__ |
| static void closeAndthrowIOException(int fd, JNIEnv *env, const char *msg) { |
| #else |
| static void closeAndthrowIOException(HANDLE handle, JNIEnv *env, const char *msg) { |
| #endif |
| char buff[256]; |
| #ifndef __MINGW32__ |
| snprintf(buff, sizeof(buff), "%s: %s", msg, strerror(errno)); |
| close(fd); |
| #else |
| sprintf_s(buff, sizeof(buff), "%s (%d)", msg, GetLastError()); |
| CloseHandle(handle); |
| #endif |
| jclass cls = (*env)->FindClass(env, "java/io/IOException"); |
| (*env)->ThrowNew(env, cls, buff); |
| } |
| #endif |
| |
| static void throwIOException(JNIEnv *env, const char *msg) { |
| char buff[256]; |
| #ifndef __MINGW32__ |
| snprintf(buff, sizeof(buff), "%s: %s", msg, strerror(errno)); |
| #else |
| sprintf_s(buff, sizeof(buff), "%s (%d)", msg, GetLastError()); |
| #endif |
| jclass cls = (*env)->FindClass(env, "java/io/IOException"); |
| (*env)->ThrowNew(env, cls, buff); |
| } |
| |
| JNIEXPORT jlong JNICALL FUNC(open0)(JNIEnv *env, jobject jobj, jstring portName, jint baudRate, jint byteSize, |
| jint parity, jint stopBits) { |
| #ifndef __MINGW32__ |
| const char *cportName = (*env)->GetStringUTFChars(env, portName, NULL); |
| int fd = open(cportName, O_RDWR | O_NOCTTY | O_NDELAY); |
| if (fd < 0) { |
| char msg[256]; |
| snprintf(msg, sizeof(msg), "Error opening %s", cportName); |
| (*env)->ReleaseStringUTFChars(env, portName, cportName); |
| throwIOException(env, msg); |
| return fd; |
| } |
| (*env)->ReleaseStringUTFChars(env, portName, cportName); |
| |
| // Turn off all flags |
| fcntl(fd, F_SETFL, 0); |
| |
| struct termios options; |
| tcgetattr(fd, &options); |
| options.c_cflag |= (CLOCAL | CREAD); |
| |
| #ifndef __APPLE__ |
| speed_t baud; |
| switch (baudRate) { |
| case 110: |
| baud = B110; |
| break; |
| case 300: |
| baud = B300; |
| break; |
| case 600: |
| baud = B600; |
| break; |
| case 1200: |
| baud = B1200; |
| break; |
| case 2400: |
| baud = B2400; |
| break; |
| case 4800: |
| baud = B4800; |
| break; |
| case 9600: |
| baud = B9600; |
| break; |
| case 19200: |
| baud = B19200; |
| break; |
| case 38400: |
| baud = B38400; |
| break; |
| case 57600: |
| baud = B57600; |
| break; |
| case 115200: |
| baud = B115200; |
| break; |
| case 230400: |
| baud = B230400; |
| break; |
| case 460800: |
| baud = B460800; |
| break; |
| case 500000: |
| baud = B500000; |
| break; |
| case 576000: |
| baud = B576000; |
| break; |
| case 921600: |
| baud = B921600; |
| break; |
| case 1000000: |
| baud = B1000000; |
| break; |
| case 1152000: |
| baud = B1152000; |
| break; |
| case 1500000: |
| baud = B1500000; |
| break; |
| case 2000000: |
| baud = B2000000; |
| break; |
| case 2500000: |
| baud = B2500000; |
| break; |
| case 3000000: |
| baud = B3000000; |
| break; |
| case 3500000: |
| baud = B3500000; |
| break; |
| case 4000000: |
| baud = B4000000; |
| break; |
| default: |
| baud = B0; |
| break; |
| } |
| |
| if (baud == B0) { |
| // Use custom linux baud rates if possible: https://bugs.eclipse.org/bugs/show_bug.cgi?id=543122#c8 |
| struct serial_struct serial_options; |
| options.c_cflag |= B38400; |
| |
| if (ioctl(fd, TIOCGSERIAL, &serial_options) != 0) { |
| closeAndthrowIOException(fd, env, "Failed to use custom baud rate. Error using TIOCGSERIAL"); |
| return -1; |
| } |
| serial_options.custom_divisor = serial_options.baud_base / baudRate; |
| if (serial_options.custom_divisor == 0) { |
| serial_options.custom_divisor = 1; |
| } |
| |
| serial_options.flags &= ~ASYNC_SPD_MASK; |
| serial_options.flags |= ASYNC_SPD_CUST; |
| |
| if (ioctl(fd, TIOCSSERIAL, &serial_options) != 0) { |
| closeAndthrowIOException(fd, env, "Failed to use custom baud rate. Error using TIOCSSERIAL"); |
| return -1; |
| } |
| } else { |
| // Set baud rate |
| cfsetispeed(&options, baud); |
| cfsetospeed(&options, baud); |
| } |
| |
| #else |
| // On OSX speed_t is simply the baud rate: |
| // https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/cfsetispeed.3.html |
| cfsetispeed(&options, baudRate); |
| cfsetospeed(&options, baudRate); |
| #endif |
| |
| // set data size |
| options.c_cflag &= ~CSIZE; |
| switch (byteSize) { |
| case 5: |
| options.c_cflag |= CS5; |
| break; |
| case 6: |
| options.c_cflag |= CS6; |
| break; |
| case 7: |
| options.c_cflag |= CS7; |
| break; |
| case 8: |
| options.c_cflag |= CS8; |
| break; |
| } |
| |
| // set parity |
| switch (parity) { |
| case 0: // None |
| options.c_cflag &= ~PARENB; |
| break; |
| case 1: // Even |
| options.c_cflag |= PARENB; |
| options.c_cflag &= ~PARODD; |
| break; |
| case 2: // Odd |
| options.c_cflag |= (PARENB | PARODD); |
| break; |
| } |
| |
| switch (stopBits) { |
| case 0: // 1 |
| options.c_cflag &= ~CSTOPB; |
| break; |
| case 1: // 2 |
| options.c_cflag |= CSTOPB; |
| break; |
| } |
| |
| // raw input |
| options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); |
| |
| // ignore parity |
| options.c_iflag |= IGNPAR; |
| |
| // turn off those bits in the input flag that fiddle with CR and NL |
| options.c_iflag &= ~(ICRNL | INLCR | IGNCR); |
| |
| options.c_cc[VMIN] = 0; // min chars to read |
| options.c_cc[VTIME] = 2; // 10ths second timeout |
| |
| tcflush(fd, TCIFLUSH); |
| tcsetattr(fd, TCSANOW, &options); |
| |
| return fd; |
| #else // __MINGW32__ |
| const wchar_t *cportName = (const wchar_t *)(*env)->GetStringChars(env, portName, NULL); |
| HANDLE handle = |
| CreateFile(cportName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); |
| (*env)->ReleaseStringChars(env, portName, cportName); |
| |
| if (handle == INVALID_HANDLE_VALUE) { |
| char msg[256]; |
| const char *name = (*env)->GetStringUTFChars(env, portName, NULL); |
| sprintf_s(msg, sizeof(msg), "Error opening %s", name); |
| (*env)->ReleaseStringUTFChars(env, portName, name); |
| throwIOException(env, msg); |
| return -1; |
| } |
| |
| DCB dcb = {0}; |
| |
| if (!GetCommState(handle, &dcb)) { |
| closeAndthrowIOException(handle, env, "Error getting DCB"); |
| return -1; |
| } |
| |
| dcb.BaudRate = baudRate; |
| dcb.ByteSize = (BYTE)byteSize; |
| |
| switch (parity) { |
| case 0: // None |
| dcb.fParity = FALSE; |
| dcb.Parity = NOPARITY; |
| break; |
| case 1: // Even |
| dcb.fParity = TRUE; |
| dcb.Parity = EVENPARITY; |
| break; |
| case 2: // Odd |
| dcb.fParity = TRUE; |
| dcb.Parity = ODDPARITY; |
| break; |
| } |
| |
| switch (stopBits) { |
| case 0: |
| dcb.StopBits = ONESTOPBIT; |
| break; |
| case 1: |
| dcb.StopBits = TWOSTOPBITS; |
| break; |
| } |
| |
| if (!SetCommState(handle, &dcb)) { |
| closeAndthrowIOException(handle, env, "Error setting DCB"); |
| return -1; |
| } |
| |
| COMMTIMEOUTS timeouts = {0}; |
| timeouts.ReadIntervalTimeout = MAXDWORD; |
| timeouts.ReadTotalTimeoutMultiplier = MAXDWORD; |
| timeouts.ReadTotalTimeoutConstant = 200; |
| if (!SetCommTimeouts(handle, &timeouts)) { |
| closeAndthrowIOException(handle, env, "Error setting timeouts"); |
| return -1; |
| } |
| |
| #ifdef _WIN64 |
| return (jlong)handle; |
| #else |
| return (jlong)(unsigned)handle; |
| #endif |
| #endif // __MINGW32__ |
| } |
| |
| JNIEXPORT void JNICALL FUNC(close0)(JNIEnv *env, jobject jobj, jlong handle) { |
| #ifndef __MINGW32__ |
| close(handle); |
| #else |
| #ifdef _WIN64 |
| CloseHandle((HANDLE)handle); |
| #else |
| CloseHandle((HANDLE)(unsigned)handle); |
| #endif |
| #endif |
| } |
| |
| JNIEXPORT jint JNICALL FUNC(available0)(JNIEnv *env, jobject jobj, jlong jhandle) { |
| #ifndef __MINGW32__ |
| int result = 0; |
| if (ioctl(jhandle, FIONREAD, &result) < 0) { |
| throwIOException(env, "Error calling ioctl"); |
| return 0; |
| } |
| return result; |
| #else |
| COMSTAT stat; |
| DWORD errCode; |
| #ifdef _WIN64 |
| HANDLE handle = (HANDLE)jhandle; |
| #else |
| HANDLE handle = (HANDLE)(unsigned)jhandle; |
| #endif |
| |
| if (ClearCommError(handle, &errCode, &stat) == 0) { |
| throwIOException(env, "Error calling ClearCommError"); |
| return -1; |
| } |
| return (int)stat.cbInQue; |
| #endif |
| } |
| |
| JNIEXPORT jint JNICALL FUNC(read1)(JNIEnv *env, jobject jobj, jlong jhandle, jbyteArray bytes, jint offset, jint size) { |
| #ifndef __MINGW32__ |
| jbyte buff[256]; |
| int n = size < sizeof(buff) ? size : sizeof(buff); |
| n = read(jhandle, buff, n); |
| if (n > 0) { |
| (*env)->SetByteArrayRegion(env, bytes, offset, n, buff); |
| } |
| return n; |
| #else |
| OVERLAPPED olp = {0}; |
| |
| olp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (olp.hEvent == NULL) { |
| throwIOException(env, "Error creating event"); |
| return -1; |
| } |
| |
| char buff[256]; |
| DWORD nread = sizeof(buff) < size ? sizeof(buff) : size; |
| #ifdef _WIN64 |
| HANDLE handle = (HANDLE)jhandle; |
| #else |
| HANDLE handle = (HANDLE)(unsigned)jhandle; |
| #endif |
| |
| if (!ReadFile(handle, buff, sizeof(buff), &nread, &olp)) { |
| if (GetLastError() != ERROR_IO_PENDING) { |
| throwIOException(env, "Error reading from port"); |
| CloseHandle(olp.hEvent); |
| return -1; |
| } else { |
| switch (WaitForSingleObject(olp.hEvent, INFINITE)) { |
| case WAIT_OBJECT_0: |
| if (!GetOverlappedResult(handle, &olp, &nread, FALSE)) { |
| if (GetLastError() != ERROR_OPERATION_ABORTED) { |
| throwIOException(env, "Error waiting for read"); |
| } |
| CloseHandle(olp.hEvent); |
| return -1; |
| } |
| break; |
| } |
| } |
| } |
| |
| if (nread > 0) { |
| (*env)->SetByteArrayRegion(env, bytes, offset, nread, (jbyte *)buff); |
| } |
| CloseHandle(olp.hEvent); |
| return nread; |
| #endif |
| } |
| |
| JNIEXPORT void JNICALL FUNC(write0)(JNIEnv *env, jobject jobj, jlong jhandle, jint b) { |
| #ifndef __MINGW32__ |
| char buff = b; |
| write(jhandle, &buff, 1); |
| #else |
| OVERLAPPED olp = {0}; |
| |
| olp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (olp.hEvent == NULL) { |
| throwIOException(env, "Error creating event"); |
| return; |
| } |
| |
| char buff = (char)b; |
| DWORD nwritten; |
| #ifdef _WIN64 |
| HANDLE handle = (HANDLE)jhandle; |
| #else |
| HANDLE handle = (HANDLE)(unsigned)jhandle; |
| #endif |
| |
| if (!WriteFile(handle, &buff, sizeof(buff), &nwritten, &olp)) { |
| if (GetLastError() != ERROR_IO_PENDING) { |
| throwIOException(env, "Error writing to port"); |
| } else { |
| switch (WaitForSingleObject(olp.hEvent, INFINITE)) { |
| case WAIT_OBJECT_0: |
| if (!GetOverlappedResult(handle, &olp, &nwritten, FALSE)) { |
| throwIOException(env, "Error waiting for write"); |
| } |
| } |
| } |
| } |
| |
| CloseHandle(olp.hEvent); |
| #endif |
| } |
| |
| JNIEXPORT void JNICALL FUNC(write1)(JNIEnv *env, jobject jobj, jlong jhandle, jbyteArray bytes, jint offset, |
| jint size) { |
| #ifndef __MINGW32__ |
| while (size > 0) { |
| jbyte buff[256]; |
| int n = size < sizeof(buff) ? size : sizeof(buff); |
| (*env)->GetByteArrayRegion(env, bytes, offset, n, buff); |
| n = write(jhandle, buff, n); |
| if (n < 0) { |
| return; |
| } |
| size -= n; |
| offset += n; |
| } |
| #else |
| OVERLAPPED olp = {0}; |
| |
| olp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); |
| if (olp.hEvent == NULL) { |
| throwIOException(env, "Error creating event"); |
| return; |
| } |
| |
| while (size > 0) { |
| char buff[256]; |
| DWORD nwritten = sizeof(buff) < size ? sizeof(buff) : size; |
| (*env)->GetByteArrayRegion(env, bytes, offset, nwritten, (jbyte *)buff); |
| #ifdef _WIN64 |
| HANDLE handle = (HANDLE)jhandle; |
| #else |
| HANDLE handle = (HANDLE)(unsigned)jhandle; |
| #endif |
| |
| if (!WriteFile(handle, buff, nwritten, &nwritten, &olp)) { |
| if (GetLastError() != ERROR_IO_PENDING) { |
| throwIOException(env, "Error writing to port"); |
| return; |
| } else { |
| switch (WaitForSingleObject(olp.hEvent, INFINITE)) { |
| case WAIT_OBJECT_0: |
| if (!GetOverlappedResult(handle, &olp, &nwritten, FALSE)) { |
| throwIOException(env, "Error waiting for write"); |
| return; |
| } |
| } |
| } |
| } |
| size -= nwritten; |
| offset += nwritten; |
| } |
| |
| CloseHandle(olp.hEvent); |
| #endif |
| } |
| |
| #ifdef __MINGW32__ |
| JNIEXPORT jstring FUNC(getPortName)(JNIEnv *env, jclass cls, jint i) { |
| HKEY key; |
| |
| if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, L"HARDWARE\\DEVICEMAP\\SERIALCOMM", 0, KEY_READ, &key) != ERROR_SUCCESS) { |
| // There are none |
| return NULL; |
| } |
| |
| wchar_t name[256]; |
| DWORD len = sizeof(name); |
| LONG rc = RegEnumValue(key, (DWORD)i, name, &len, NULL, NULL, NULL, NULL); |
| if (rc != ERROR_SUCCESS) { |
| if (rc != ERROR_NO_MORE_ITEMS) { |
| throwIOException(env, "Can not enum value"); |
| } |
| RegCloseKey(key); |
| return NULL; |
| } |
| |
| wchar_t value[256]; |
| DWORD type; |
| len = sizeof(value); |
| if (RegQueryValueEx(key, name, NULL, &type, (BYTE *)value, &len) != ERROR_SUCCESS) { |
| throwIOException(env, "Can not query value"); |
| RegCloseKey(key); |
| return NULL; |
| } |
| |
| jstring result = (*env)->NewString(env, (jchar *)value, (jsize)wcslen(value)); |
| RegCloseKey(key); |
| return result; |
| } |
| #endif |