summaryrefslogtreecommitdiffstats
path: root/winpr/libwinpr/comm
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--winpr/libwinpr/comm/CMakeLists.txt40
-rw-r--r--winpr/libwinpr/comm/ModuleOptions.cmake9
-rw-r--r--winpr/libwinpr/comm/comm.c1426
-rw-r--r--winpr/libwinpr/comm/comm.h111
-rw-r--r--winpr/libwinpr/comm/comm_io.c549
-rw-r--r--winpr/libwinpr/comm/comm_ioctl.c731
-rw-r--r--winpr/libwinpr/comm/comm_ioctl.h236
-rw-r--r--winpr/libwinpr/comm/comm_sercx2_sys.c200
-rw-r--r--winpr/libwinpr/comm/comm_sercx2_sys.h40
-rw-r--r--winpr/libwinpr/comm/comm_sercx_sys.c266
-rw-r--r--winpr/libwinpr/comm/comm_sercx_sys.h40
-rw-r--r--winpr/libwinpr/comm/comm_serial_sys.c1637
-rw-r--r--winpr/libwinpr/comm/comm_serial_sys.h40
-rw-r--r--winpr/libwinpr/comm/test/CMakeLists.txt35
-rw-r--r--winpr/libwinpr/comm/test/TestCommConfig.c148
-rw-r--r--winpr/libwinpr/comm/test/TestCommDevice.c115
-rw-r--r--winpr/libwinpr/comm/test/TestCommMonitor.c70
-rw-r--r--winpr/libwinpr/comm/test/TestControlSettings.c123
-rw-r--r--winpr/libwinpr/comm/test/TestGetCommState.c138
-rw-r--r--winpr/libwinpr/comm/test/TestHandflow.c92
-rw-r--r--winpr/libwinpr/comm/test/TestSerialChars.c178
-rw-r--r--winpr/libwinpr/comm/test/TestSetCommState.c332
-rw-r--r--winpr/libwinpr/comm/test/TestTimeouts.c138
23 files changed, 6694 insertions, 0 deletions
diff --git a/winpr/libwinpr/comm/CMakeLists.txt b/winpr/libwinpr/comm/CMakeLists.txt
new file mode 100644
index 0000000..cc73741
--- /dev/null
+++ b/winpr/libwinpr/comm/CMakeLists.txt
@@ -0,0 +1,40 @@
+# WinPR: Windows Portable Runtime
+# libwinpr-comm cmake build script
+#
+# Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+#
+# 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.
+
+set(MODULE_NAME "winpr-comm")
+set(MODULE_PREFIX "WINPR_COMM")
+
+if(UNIX AND NOT WIN32 AND NOT APPLE)
+ set(${MODULE_PREFIX}_SRCS
+ comm.c
+ comm.h
+ comm_io.c
+ comm_ioctl.c
+ comm_ioctl.h
+ comm_serial_sys.c
+ comm_serial_sys.h
+ comm_sercx_sys.c
+ comm_sercx_sys.h
+ comm_sercx2_sys.c
+ comm_sercx2_sys.h)
+
+ winpr_module_add(${${MODULE_PREFIX}_SRCS})
+
+ if(BUILD_TESTING AND BUILD_COMM_TESTS)
+ add_subdirectory(test)
+ endif()
+endif()
diff --git a/winpr/libwinpr/comm/ModuleOptions.cmake b/winpr/libwinpr/comm/ModuleOptions.cmake
new file mode 100644
index 0000000..4095589
--- /dev/null
+++ b/winpr/libwinpr/comm/ModuleOptions.cmake
@@ -0,0 +1,9 @@
+
+set(MINWIN_LAYER "1")
+set(MINWIN_GROUP "core")
+set(MINWIN_MAJOR_VERSION "1")
+set(MINWIN_MINOR_VERSION "0")
+set(MINWIN_SHORT_NAME "comm")
+set(MINWIN_LONG_NAME "Serial Communication API")
+set(MODULE_LIBRARY_NAME "api-ms-win-${MINWIN_GROUP}-${MINWIN_SHORT_NAME}-l${MINWIN_LAYER}-${MINWIN_MAJOR_VERSION}-${MINWIN_MINOR_VERSION}")
+
diff --git a/winpr/libwinpr/comm/comm.c b/winpr/libwinpr/comm/comm.c
new file mode 100644
index 0000000..c4e828e
--- /dev/null
+++ b/winpr/libwinpr/comm/comm.c
@@ -0,0 +1,1426 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/tchar.h>
+#include <winpr/wlog.h>
+#include <winpr/handle.h>
+
+#include "comm_ioctl.h"
+
+/**
+ * Communication Resources:
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363196/
+ */
+
+#include "comm.h"
+
+static wLog* _Log = NULL;
+
+struct comm_device
+{
+ LPTSTR name;
+ LPTSTR path;
+};
+
+typedef struct comm_device COMM_DEVICE;
+
+/* FIXME: get a clever data structure, see also io.h functions */
+/* _CommDevices is a NULL-terminated array with a maximun of COMM_DEVICE_MAX COMM_DEVICE */
+#define COMM_DEVICE_MAX 128
+static COMM_DEVICE** _CommDevices = NULL;
+static CRITICAL_SECTION _CommDevicesLock;
+
+static HANDLE_CREATOR _CommHandleCreator;
+
+static pthread_once_t _CommInitialized = PTHREAD_ONCE_INIT;
+
+static int CommGetFd(HANDLE handle)
+{
+ WINPR_COMM* comm = (WINPR_COMM*)handle;
+
+ if (!CommIsHandled(handle))
+ return -1;
+
+ return comm->fd;
+}
+
+HANDLE_CREATOR* GetCommHandleCreator(void)
+{
+ _CommHandleCreator.IsHandled = IsCommDevice;
+ _CommHandleCreator.CreateFileA = CommCreateFileA;
+ return &_CommHandleCreator;
+}
+
+static void _CommInit(void)
+{
+ /* NB: error management to be done outside of this function */
+ WINPR_ASSERT(_Log == NULL);
+ WINPR_ASSERT(_CommDevices == NULL);
+ _CommDevices = (COMM_DEVICE**)calloc(COMM_DEVICE_MAX + 1, sizeof(COMM_DEVICE*));
+
+ if (!_CommDevices)
+ return;
+
+ if (!InitializeCriticalSectionEx(&_CommDevicesLock, 0, 0))
+ {
+ free(_CommDevices);
+ _CommDevices = NULL;
+ return;
+ }
+
+ _Log = WLog_Get("com.winpr.comm");
+ WINPR_ASSERT(_Log != NULL);
+}
+
+/**
+ * Returns TRUE when the comm module is correctly intialized, FALSE otherwise
+ * with ERROR_DLL_INIT_FAILED set as the last error.
+ */
+static BOOL CommInitialized(void)
+{
+ if (pthread_once(&_CommInitialized, _CommInit) != 0)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+void CommLog_Print(DWORD level, ...)
+{
+ if (!CommInitialized())
+ return;
+
+ va_list ap;
+ va_start(ap, level);
+ WLog_PrintVA(_Log, level, ap);
+ va_end(ap);
+}
+
+BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hCommDev;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hCommDev;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_INVALID_HANDLE
+ */
+BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_PROPERTIES, NULL, 0, lpCommProp,
+ sizeof(COMMPROP), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommProperties failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ *
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_INVALID_DATA
+ * ERROR_IO_DEVICE
+ * ERROR_OUTOFMEMORY
+ */
+BOOL GetCommState(HANDLE hFile, LPDCB lpDCB)
+{
+ DCB* lpLocalDcb = NULL;
+ struct termios currentState;
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!lpDCB)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ if (lpDCB->DCBlength < sizeof(DCB))
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ if (tcgetattr(pComm->fd, &currentState) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ lpLocalDcb = (DCB*)calloc(1, lpDCB->DCBlength);
+
+ if (lpLocalDcb == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return FALSE;
+ }
+
+ /* error_handle */
+ lpLocalDcb->DCBlength = lpDCB->DCBlength;
+ SERIAL_BAUD_RATE baudRate;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_BAUD_RATE, NULL, 0, &baudRate,
+ sizeof(SERIAL_BAUD_RATE), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the baud rate.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->BaudRate = baudRate.BaudRate;
+ lpLocalDcb->fBinary = (currentState.c_cflag & ICANON) == 0;
+
+ if (!lpLocalDcb->fBinary)
+ {
+ CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag.");
+ }
+
+ lpLocalDcb->fParity = (currentState.c_iflag & INPCK) != 0;
+ SERIAL_HANDFLOW handflow;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_HANDFLOW, NULL, 0, &handflow,
+ sizeof(SERIAL_HANDFLOW), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the handflow settings.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->fOutxCtsFlow = (handflow.ControlHandShake & SERIAL_CTS_HANDSHAKE) != 0;
+ lpLocalDcb->fOutxDsrFlow = (handflow.ControlHandShake & SERIAL_DSR_HANDSHAKE) != 0;
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_HANDSHAKE;
+ }
+ else if (handflow.ControlHandShake & SERIAL_DTR_CONTROL)
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_ENABLE;
+ }
+ else
+ {
+ lpLocalDcb->fDtrControl = DTR_CONTROL_DISABLE;
+ }
+
+ lpLocalDcb->fDsrSensitivity = (handflow.ControlHandShake & SERIAL_DSR_SENSITIVITY) != 0;
+ lpLocalDcb->fTXContinueOnXoff = (handflow.FlowReplace & SERIAL_XOFF_CONTINUE) != 0;
+ lpLocalDcb->fOutX = (handflow.FlowReplace & SERIAL_AUTO_TRANSMIT) != 0;
+ lpLocalDcb->fInX = (handflow.FlowReplace & SERIAL_AUTO_RECEIVE) != 0;
+ lpLocalDcb->fErrorChar = (handflow.FlowReplace & SERIAL_ERROR_CHAR) != 0;
+ lpLocalDcb->fNull = (handflow.FlowReplace & SERIAL_NULL_STRIPPING) != 0;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_HANDSHAKE;
+ }
+ else if (handflow.FlowReplace & SERIAL_RTS_CONTROL)
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_ENABLE;
+ }
+ else
+ {
+ lpLocalDcb->fRtsControl = RTS_CONTROL_DISABLE;
+ }
+
+ // FIXME: how to get the RTS_CONTROL_TOGGLE state? Does it match the UART 16750's Autoflow
+ // Control Enabled bit in its Modem Control Register (MCR)
+ lpLocalDcb->fAbortOnError = (handflow.ControlHandShake & SERIAL_ERROR_ABORT) != 0;
+ /* lpLocalDcb->fDummy2 not used */
+ lpLocalDcb->wReserved = 0; /* must be zero */
+ lpLocalDcb->XonLim = handflow.XonLimit;
+ lpLocalDcb->XoffLim = handflow.XoffLimit;
+ SERIAL_LINE_CONTROL lineControl;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_LINE_CONTROL, NULL, 0, &lineControl,
+ sizeof(SERIAL_LINE_CONTROL), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the control settings.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->ByteSize = lineControl.WordLength;
+ lpLocalDcb->Parity = lineControl.Parity;
+ lpLocalDcb->StopBits = lineControl.StopBits;
+ SERIAL_CHARS serialChars;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars,
+ sizeof(SERIAL_CHARS), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommState failure: could not get the serial chars.");
+ goto error_handle;
+ }
+
+ lpLocalDcb->XonChar = serialChars.XonChar;
+ lpLocalDcb->XoffChar = serialChars.XoffChar;
+ lpLocalDcb->ErrorChar = serialChars.ErrorChar;
+ lpLocalDcb->EofChar = serialChars.EofChar;
+ lpLocalDcb->EvtChar = serialChars.EventChar;
+ memcpy(lpDCB, lpLocalDcb, lpDCB->DCBlength);
+ free(lpLocalDcb);
+ return TRUE;
+error_handle:
+ free(lpLocalDcb);
+ return FALSE;
+}
+
+/**
+ * @return TRUE on success, FALSE otherwise.
+ *
+ * As of today, SetCommState() can fail half-way with some settings
+ * applied and some others not. SetCommState() returns on the first
+ * failure met. FIXME: or is it correct?
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_IO_DEVICE
+ */
+BOOL SetCommState(HANDLE hFile, LPDCB lpDCB)
+{
+ struct termios upcomingTermios = { 0 };
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ /* FIXME: validate changes according GetCommProperties? */
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!lpDCB)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+ }
+
+ /* NB: did the choice to call ioctls first when available and
+ then to setup upcomingTermios. Don't mix both stages. */
+ /** ioctl calls stage **/
+ SERIAL_BAUD_RATE baudRate;
+ baudRate.BaudRate = lpDCB->BaudRate;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_BAUD_RATE, &baudRate, sizeof(SERIAL_BAUD_RATE),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the baud rate.");
+ return FALSE;
+ }
+
+ SERIAL_CHARS serialChars;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_CHARS, NULL, 0, &serialChars,
+ sizeof(SERIAL_CHARS), &bytesReturned,
+ NULL)) /* as of today, required for BreakChar */
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not get the initial serial chars.");
+ return FALSE;
+ }
+
+ serialChars.XonChar = lpDCB->XonChar;
+ serialChars.XoffChar = lpDCB->XoffChar;
+ serialChars.ErrorChar = lpDCB->ErrorChar;
+ serialChars.EofChar = lpDCB->EofChar;
+ serialChars.EventChar = lpDCB->EvtChar;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_CHARS, &serialChars, sizeof(SERIAL_CHARS),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the serial chars.");
+ return FALSE;
+ }
+
+ SERIAL_LINE_CONTROL lineControl;
+ lineControl.StopBits = lpDCB->StopBits;
+ lineControl.Parity = lpDCB->Parity;
+ lineControl.WordLength = lpDCB->ByteSize;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_LINE_CONTROL, &lineControl,
+ sizeof(SERIAL_LINE_CONTROL), NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the control settings.");
+ return FALSE;
+ }
+
+ SERIAL_HANDFLOW handflow = { 0 };
+
+ if (lpDCB->fOutxCtsFlow)
+ {
+ handflow.ControlHandShake |= SERIAL_CTS_HANDSHAKE;
+ }
+
+ if (lpDCB->fOutxDsrFlow)
+ {
+ handflow.ControlHandShake |= SERIAL_DSR_HANDSHAKE;
+ }
+
+ switch (lpDCB->fDtrControl)
+ {
+ case SERIAL_DTR_HANDSHAKE:
+ handflow.ControlHandShake |= DTR_CONTROL_HANDSHAKE;
+ break;
+
+ case SERIAL_DTR_CONTROL:
+ handflow.ControlHandShake |= DTR_CONTROL_ENABLE;
+ break;
+
+ case DTR_CONTROL_DISABLE:
+ /* do nothing since handflow is init-zeroed */
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "Unexpected fDtrControl value: %" PRIu32 "\n",
+ lpDCB->fDtrControl);
+ return FALSE;
+ }
+
+ if (lpDCB->fDsrSensitivity)
+ {
+ handflow.ControlHandShake |= SERIAL_DSR_SENSITIVITY;
+ }
+
+ if (lpDCB->fTXContinueOnXoff)
+ {
+ handflow.FlowReplace |= SERIAL_XOFF_CONTINUE;
+ }
+
+ if (lpDCB->fOutX)
+ {
+ handflow.FlowReplace |= SERIAL_AUTO_TRANSMIT;
+ }
+
+ if (lpDCB->fInX)
+ {
+ handflow.FlowReplace |= SERIAL_AUTO_RECEIVE;
+ }
+
+ if (lpDCB->fErrorChar)
+ {
+ handflow.FlowReplace |= SERIAL_ERROR_CHAR;
+ }
+
+ if (lpDCB->fNull)
+ {
+ handflow.FlowReplace |= SERIAL_NULL_STRIPPING;
+ }
+
+ switch (lpDCB->fRtsControl)
+ {
+ case RTS_CONTROL_TOGGLE:
+ CommLog_Print(WLOG_WARN, "Unsupported RTS_CONTROL_TOGGLE feature");
+ // FIXME: see also GetCommState()
+ return FALSE;
+
+ case RTS_CONTROL_HANDSHAKE:
+ handflow.FlowReplace |= SERIAL_RTS_HANDSHAKE;
+ break;
+
+ case RTS_CONTROL_ENABLE:
+ handflow.FlowReplace |= SERIAL_RTS_CONTROL;
+ break;
+
+ case RTS_CONTROL_DISABLE:
+ /* do nothing since handflow is init-zeroed */
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "Unexpected fRtsControl value: %" PRIu32 "\n",
+ lpDCB->fRtsControl);
+ return FALSE;
+ }
+
+ if (lpDCB->fAbortOnError)
+ {
+ handflow.ControlHandShake |= SERIAL_ERROR_ABORT;
+ }
+
+ /* lpDCB->fDummy2 not used */
+ /* lpLocalDcb->wReserved ignored */
+ handflow.XonLimit = lpDCB->XonLim;
+ handflow.XoffLimit = lpDCB->XoffLim;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_HANDFLOW, &handflow, sizeof(SERIAL_HANDFLOW),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommState failure: could not set the handflow settings.");
+ return FALSE;
+ }
+
+ /** upcomingTermios stage **/
+
+ if (tcgetattr(pComm->fd, &upcomingTermios) <
+ 0) /* NB: preserves current settings not directly handled by the Communication Functions */
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ if (lpDCB->fBinary)
+ {
+ upcomingTermios.c_lflag &= ~ICANON;
+ }
+ else
+ {
+ upcomingTermios.c_lflag |= ICANON;
+ CommLog_Print(WLOG_WARN, "Unexpected nonbinary mode, consider to unset the ICANON flag.");
+ }
+
+ if (lpDCB->fParity)
+ {
+ upcomingTermios.c_iflag |= INPCK;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~INPCK;
+ }
+
+ /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363423%28v=vs.85%29.aspx
+ *
+ * The SetCommState function reconfigures the communications
+ * resource, but it does not affect the internal output and
+ * input buffers of the specified driver. The buffers are not
+ * flushed, and pending read and write operations are not
+ * terminated prematurely.
+ *
+ * TCSANOW matches the best this definition
+ */
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ */
+BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_TIMEOUTS, NULL, 0, lpCommTimeouts,
+ sizeof(COMMTIMEOUTS), &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "GetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ */
+BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ /* as of today, SERIAL_TIMEOUTS and COMMTIMEOUTS structures are identical */
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_TIMEOUTS, lpCommTimeouts, sizeof(COMMTIMEOUTS),
+ NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL SetCommBreak(HANDLE hFile)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL ClearCommBreak(HANDLE hFile)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL PurgeComm(HANDLE hFile, DWORD dwFlags)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_PURGE, &dwFlags, sizeof(DWORD), NULL, 0,
+ &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "PurgeComm failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+ SERIAL_QUEUE_SIZE queueSize;
+ DWORD bytesReturned = 0;
+
+ if (!CommIsHandleValid(hFile))
+ return FALSE;
+
+ queueSize.InSize = dwInQueue;
+ queueSize.OutSize = dwOutQueue;
+
+ if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_SET_QUEUE_SIZE, &queueSize,
+ sizeof(SERIAL_QUEUE_SIZE), NULL, 0, &bytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "SetCommTimeouts failure.");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL TransmitCommChar(HANDLE hFile, char cChar)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hFile;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ /* TODO: not implemented */
+
+ if (!pComm)
+ return FALSE;
+
+ CommLog_Print(WLOG_ERROR, "Not implemented");
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+}
+
+/**
+ * Returns TRUE on success, FALSE otherwise. To get extended error
+ * information, call GetLastError.
+ *
+ * ERRORS:
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_OUTOFMEMORY was not possible to get mappings.
+ * ERROR_INVALID_DATA was not possible to add the device.
+ */
+BOOL DefineCommDevice(/* DWORD dwFlags,*/ LPCTSTR lpDeviceName, LPCTSTR lpTargetPath)
+{
+ LPTSTR storedDeviceName = NULL;
+ LPTSTR storedTargetPath = NULL;
+
+ if (!CommInitialized())
+ return FALSE;
+
+ EnterCriticalSection(&_CommDevicesLock);
+
+ if (_CommDevices == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ goto error_handle;
+ }
+
+ storedDeviceName = _tcsdup(lpDeviceName);
+
+ if (storedDeviceName == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ storedTargetPath = _tcsdup(lpTargetPath);
+
+ if (storedTargetPath == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ int i = 0;
+ for (; i < COMM_DEVICE_MAX; i++)
+ {
+ if (_CommDevices[i] != NULL)
+ {
+ if (_tcscmp(_CommDevices[i]->name, storedDeviceName) == 0)
+ {
+ /* take over the emplacement */
+ free(_CommDevices[i]->name);
+ free(_CommDevices[i]->path);
+ _CommDevices[i]->name = storedDeviceName;
+ _CommDevices[i]->path = storedTargetPath;
+ break;
+ }
+ }
+ else
+ {
+ /* new emplacement */
+ _CommDevices[i] = (COMM_DEVICE*)calloc(1, sizeof(COMM_DEVICE));
+
+ if (_CommDevices[i] == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ _CommDevices[i]->name = storedDeviceName;
+ _CommDevices[i]->path = storedTargetPath;
+ break;
+ }
+ }
+
+ if (i == COMM_DEVICE_MAX)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ goto error_handle;
+ }
+
+ LeaveCriticalSection(&_CommDevicesLock);
+ return TRUE;
+error_handle:
+ free(storedDeviceName);
+ free(storedTargetPath);
+ LeaveCriticalSection(&_CommDevicesLock);
+ return FALSE;
+}
+
+/**
+ * Returns the number of target paths in the buffer pointed to by
+ * lpTargetPath.
+ *
+ * The current implementation returns in any case 0 and 1 target
+ * path. A NULL lpDeviceName is not supported yet to get all the
+ * paths.
+ *
+ * ERRORS:
+ * ERROR_SUCCESS
+ * ERROR_DLL_INIT_FAILED
+ * ERROR_OUTOFMEMORY was not possible to get mappings.
+ * ERROR_NOT_SUPPORTED equivalent QueryDosDevice feature not supported.
+ * ERROR_INVALID_DATA was not possible to retrieve any device information.
+ * ERROR_INSUFFICIENT_BUFFER too small lpTargetPath
+ */
+DWORD QueryCommDevice(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax)
+{
+ LPTSTR storedTargetPath = NULL;
+ SetLastError(ERROR_SUCCESS);
+
+ if (!CommInitialized())
+ return 0;
+
+ if (_CommDevices == NULL)
+ {
+ SetLastError(ERROR_DLL_INIT_FAILED);
+ return 0;
+ }
+
+ if (lpDeviceName == NULL || lpTargetPath == NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return 0;
+ }
+
+ EnterCriticalSection(&_CommDevicesLock);
+ storedTargetPath = NULL;
+
+ for (int i = 0; i < COMM_DEVICE_MAX; i++)
+ {
+ if (_CommDevices[i] != NULL)
+ {
+ if (_tcscmp(_CommDevices[i]->name, lpDeviceName) == 0)
+ {
+ storedTargetPath = _CommDevices[i]->path;
+ break;
+ }
+
+ continue;
+ }
+
+ break;
+ }
+
+ LeaveCriticalSection(&_CommDevicesLock);
+
+ if (storedTargetPath == NULL)
+ {
+ SetLastError(ERROR_INVALID_DATA);
+ return 0;
+ }
+
+ if (_tcslen(storedTargetPath) + 2 > ucchMax)
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return 0;
+ }
+
+ _tcscpy(lpTargetPath, storedTargetPath);
+ lpTargetPath[_tcslen(storedTargetPath) + 1] = '\0'; /* 2nd final '\0' */
+ return _tcslen(lpTargetPath) + 2;
+}
+
+/**
+ * Checks whether lpDeviceName is a valid and registered Communication device.
+ */
+BOOL IsCommDevice(LPCTSTR lpDeviceName)
+{
+ TCHAR lpTargetPath[MAX_PATH];
+
+ if (!CommInitialized())
+ return FALSE;
+
+ if (QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH) > 0)
+ {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/**
+ * Sets
+ */
+void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID driverId)
+{
+ ULONG Type = 0;
+ WINPR_HANDLE* Object = NULL;
+ WINPR_COMM* pComm = NULL;
+
+ if (!CommInitialized())
+ return;
+
+ if (!winpr_Handle_GetInfo(hComm, &Type, &Object))
+ {
+ CommLog_Print(WLOG_WARN, "_comm_setServerSerialDriver failure");
+ return;
+ }
+
+ pComm = (WINPR_COMM*)Object;
+ pComm->serverSerialDriverId = driverId;
+}
+
+static HANDLE_OPS ops = { CommIsHandled, CommCloseHandle,
+ CommGetFd, NULL, /* CleanupHandle */
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL, NULL,
+ NULL };
+
+/**
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363198%28v=vs.85%29.aspx
+ *
+ * @param lpDeviceName e.g. COM1, ...
+ *
+ * @param dwDesiredAccess expects GENERIC_READ | GENERIC_WRITE, a
+ * warning message is printed otherwise. TODO: better support.
+ *
+ * @param dwShareMode must be zero, INVALID_HANDLE_VALUE is returned
+ * otherwise and GetLastError() should return ERROR_SHARING_VIOLATION.
+ *
+ * @param lpSecurityAttributes NULL expected, a warning message is printed
+ * otherwise. TODO: better support.
+ *
+ * @param dwCreationDisposition must be OPEN_EXISTING. If the
+ * communication device doesn't exist INVALID_HANDLE_VALUE is returned
+ * and GetLastError() returns ERROR_FILE_NOT_FOUND.
+ *
+ * @param dwFlagsAndAttributes zero expected, a warning message is
+ * printed otherwise.
+ *
+ * @param hTemplateFile must be NULL.
+ *
+ * @return INVALID_HANDLE_VALUE on error.
+ */
+HANDLE CommCreateFileA(LPCSTR lpDeviceName, DWORD dwDesiredAccess, DWORD dwShareMode,
+ LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition,
+ DWORD dwFlagsAndAttributes, HANDLE hTemplateFile)
+{
+ CHAR devicePath[MAX_PATH];
+ struct stat deviceStat;
+ WINPR_COMM* pComm = NULL;
+ struct termios upcomingTermios;
+
+ if (!CommInitialized())
+ return INVALID_HANDLE_VALUE;
+
+ if (dwDesiredAccess != (GENERIC_READ | GENERIC_WRITE))
+ {
+ CommLog_Print(WLOG_WARN, "unexpected access to the device: 0x%08" PRIX32 "",
+ dwDesiredAccess);
+ }
+
+ if (dwShareMode != 0)
+ {
+ SetLastError(ERROR_SHARING_VIOLATION);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ /* TODO: Prevents other processes from opening a file or
+ * device if they request delete, read, or write access. */
+
+ if (lpSecurityAttributes != NULL)
+ {
+ CommLog_Print(WLOG_WARN, "unexpected security attributes, nLength=%" PRIu32 "",
+ lpSecurityAttributes->nLength);
+ }
+
+ if (dwCreationDisposition != OPEN_EXISTING)
+ {
+ SetLastError(ERROR_FILE_NOT_FOUND); /* FIXME: ERROR_NOT_SUPPORTED better? */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (QueryCommDevice(lpDeviceName, devicePath, MAX_PATH) <= 0)
+ {
+ /* SetLastError(GetLastError()); */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (stat(devicePath, &deviceStat) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "device not found %s", devicePath);
+ SetLastError(ERROR_FILE_NOT_FOUND);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!S_ISCHR(deviceStat.st_mode))
+ {
+ CommLog_Print(WLOG_WARN, "bad device %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (dwFlagsAndAttributes != 0)
+ {
+ CommLog_Print(WLOG_WARN, "unexpected flags and attributes: 0x%08" PRIX32 "",
+ dwFlagsAndAttributes);
+ }
+
+ if (hTemplateFile != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED); /* FIXME: other proper error? */
+ return INVALID_HANDLE_VALUE;
+ }
+
+ pComm = (WINPR_COMM*)calloc(1, sizeof(WINPR_COMM));
+
+ if (pComm == NULL)
+ {
+ SetLastError(ERROR_OUTOFMEMORY);
+ return INVALID_HANDLE_VALUE;
+ }
+
+ WINPR_HANDLE_SET_TYPE_AND_MODE(pComm, HANDLE_TYPE_COMM, WINPR_FD_READ);
+ pComm->common.ops = &ops;
+ /* error_handle */
+ pComm->fd = open(devicePath, O_RDWR | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open device %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_read = open(devicePath, O_RDONLY | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd_read < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_read, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_read_event = eventfd(
+ 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */
+
+ if (pComm->fd_read_event < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_read_event, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ InitializeCriticalSection(&pComm->ReadLock);
+ pComm->fd_write = open(devicePath, O_WRONLY | O_NOCTTY | O_NONBLOCK);
+
+ if (pComm->fd_write < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_write, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ pComm->fd_write_event = eventfd(
+ 0, EFD_NONBLOCK); /* EFD_NONBLOCK required because a read() is not always expected */
+
+ if (pComm->fd_write_event < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to open fd_write_event, device: %s", devicePath);
+ SetLastError(ERROR_BAD_DEVICE);
+ goto error_handle;
+ }
+
+ InitializeCriticalSection(&pComm->WriteLock);
+ /* can also be setup later on with _comm_setServerSerialDriver() */
+ pComm->serverSerialDriverId = SerialDriverUnknown;
+ InitializeCriticalSection(&pComm->EventsLock);
+
+ if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ CommLog_Print(WLOG_WARN, "could not read counters.");
+ /* could not initialize counters but keep on.
+ *
+ * Not all drivers, especially for USB to serial
+ * adapters (e.g. those based on pl2303), does support
+ * this call.
+ */
+ ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct));
+ }
+
+ /* The binary/raw mode is required for the redirection but
+ * only flags that are not handle somewhere-else, except
+ * ICANON, are forced here. */
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto error_handle;
+ }
+
+ upcomingTermios.c_iflag &=
+ ~(/*IGNBRK |*/ BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL /*| IXON*/);
+ upcomingTermios.c_oflag = 0; /* <=> &= ~OPOST */
+ upcomingTermios.c_lflag = 0; /* <=> &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); */
+ /* upcomingTermios.c_cflag &= ~(CSIZE | PARENB); */
+ /* upcomingTermios.c_cflag |= CS8; */
+ /* About missing flags recommended by termios(3):
+ *
+ * IGNBRK and IXON, see: IOCTL_SERIAL_SET_HANDFLOW
+ * CSIZE, PARENB and CS8, see: IOCTL_SERIAL_SET_LINE_CONTROL
+ */
+ /* a few more settings required for the redirection */
+ upcomingTermios.c_cflag |= CLOCAL | CREAD;
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto error_handle;
+ }
+
+ return (HANDLE)pComm;
+error_handle:
+ CloseHandle(pComm);
+ return INVALID_HANDLE_VALUE;
+}
+
+BOOL CommIsHandled(HANDLE handle)
+{
+ if (!CommInitialized())
+ return FALSE;
+
+ return WINPR_HANDLE_IS_HANDLED(handle, HANDLE_TYPE_COMM, TRUE);
+}
+
+BOOL CommIsHandleValid(HANDLE handle)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)handle;
+ if (!CommIsHandled(handle))
+ return FALSE;
+ if (pComm->fd <= 0)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+BOOL CommCloseHandle(HANDLE handle)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)handle;
+
+ if (!CommIsHandled(handle))
+ return FALSE;
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ ULONG WaitMask = 0;
+ DWORD BytesReturned = 0;
+
+ /* ensures to gracefully stop the WAIT_ON_MASK's loop */
+ if (!CommDeviceIoControl(handle, IOCTL_SERIAL_SET_WAIT_MASK, &WaitMask, sizeof(ULONG), NULL,
+ 0, &BytesReturned, NULL))
+ {
+ CommLog_Print(WLOG_WARN, "failure to WAIT_ON_MASK's loop!");
+ }
+ }
+
+ DeleteCriticalSection(&pComm->ReadLock);
+ DeleteCriticalSection(&pComm->WriteLock);
+ DeleteCriticalSection(&pComm->EventsLock);
+
+ if (pComm->fd > 0)
+ close(pComm->fd);
+
+ if (pComm->fd_write > 0)
+ close(pComm->fd_write);
+
+ if (pComm->fd_write_event > 0)
+ close(pComm->fd_write_event);
+
+ if (pComm->fd_read > 0)
+ close(pComm->fd_read);
+
+ if (pComm->fd_read_event > 0)
+ close(pComm->fd_read_event);
+
+ free(pComm);
+ return TRUE;
+}
+
+#ifndef WITH_EVENTFD_READ_WRITE
+int eventfd_read(int fd, eventfd_t* value)
+{
+ return (read(fd, value, sizeof(*value)) == sizeof(*value)) ? 0 : -1;
+}
+
+int eventfd_write(int fd, eventfd_t value)
+{
+ return (write(fd, &value, sizeof(value)) == sizeof(value)) ? 0 : -1;
+}
+#endif
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm.h b/winpr/libwinpr/comm/comm.h
new file mode 100644
index 0000000..c7884cb
--- /dev/null
+++ b/winpr/libwinpr/comm/comm.h
@@ -0,0 +1,111 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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 WINPR_COMM_PRIVATE_H
+#define WINPR_COMM_PRIVATE_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include <linux/serial.h>
+#include <sys/eventfd.h>
+
+#include <winpr/comm.h>
+
+#include "../handle/handle.h"
+#include <winpr/config.h>
+
+struct winpr_comm
+{
+ WINPR_HANDLE common;
+
+ int fd;
+
+ int fd_read;
+ int fd_read_event; /* as of today, only used by _purge() */
+ CRITICAL_SECTION ReadLock;
+
+ int fd_write;
+ int fd_write_event; /* as of today, only used by _purge() */
+ CRITICAL_SECTION WriteLock;
+
+ /* permissive mode on errors. If TRUE (default is FALSE)
+ * CommDeviceIoControl always return TRUE.
+ *
+ * Not all features are supported yet and an error is then returned when
+ * an application turns them on (e.g: i/o buffers > 4096). It appeared
+ * though that devices and applications can be still functional on such
+ * errors.
+ *
+ * see also: comm_ioctl.c
+ *
+ * FIXME: getting rid of this flag once all features supported.
+ */
+ BOOL permissive;
+
+ SERIAL_DRIVER_ID serverSerialDriverId;
+
+ COMMTIMEOUTS timeouts;
+
+ CRITICAL_SECTION
+ EventsLock; /* protects counters, WaitEventMask and PendingEvents */
+ struct serial_icounter_struct counters;
+ ULONG WaitEventMask;
+ ULONG PendingEvents;
+
+ char eventChar;
+ /* NB: CloseHandle() has to free resources */
+};
+
+typedef struct winpr_comm WINPR_COMM;
+
+#define SERIAL_EV_RXCHAR 0x0001
+#define SERIAL_EV_RXFLAG 0x0002
+#define SERIAL_EV_TXEMPTY 0x0004
+#define SERIAL_EV_CTS 0x0008
+#define SERIAL_EV_DSR 0x0010
+#define SERIAL_EV_RLSD 0x0020
+#define SERIAL_EV_BREAK 0x0040
+#define SERIAL_EV_ERR 0x0080
+#define SERIAL_EV_RING 0x0100
+#define SERIAL_EV_PERR 0x0200
+#define SERIAL_EV_RX80FULL 0x0400
+#define SERIAL_EV_EVENT1 0x0800
+#define SERIAL_EV_EVENT2 0x1000
+#define SERIAL_EV_WINPR_WAITING 0x4000 /* bit today unused by other SERIAL_EV_* */
+#define SERIAL_EV_WINPR_STOP 0x8000 /* bit today unused by other SERIAL_EV_* */
+
+#define WINPR_PURGE_TXABORT 0x00000001 /* abort pending transmission */
+#define WINPR_PURGE_RXABORT 0x00000002 /* abort pending reception */
+
+void CommLog_Print(DWORD wlog_level, ...);
+
+BOOL CommIsHandled(HANDLE handle);
+BOOL CommIsHandleValid(HANDLE handle);
+BOOL CommCloseHandle(HANDLE handle);
+HANDLE_CREATOR* GetCommHandleCreator(void);
+
+#ifndef WITH_EVENTFD_READ_WRITE
+int eventfd_read(int fd, eventfd_t* value);
+int eventfd_write(int fd, eventfd_t value);
+#endif
+
+#endif /* __linux__ */
+
+#endif /* WINPR_COMM_PRIVATE_H */
diff --git a/winpr/libwinpr/comm/comm_io.c b/winpr/libwinpr/comm/comm_io.c
new file mode 100644
index 0000000..9904eab
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_io.c
@@ -0,0 +1,549 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include <winpr/io.h>
+#include <winpr/wlog.h>
+#include <winpr/wtypes.h>
+
+#include "comm.h"
+
+BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+
+ if (!CommIsHandled(hDevice))
+ return FALSE;
+
+ pComm->permissive = permissive;
+ return TRUE;
+}
+
+/* Computes VTIME in deciseconds from Ti in milliseconds */
+static UCHAR _vtime(ULONG Ti)
+{
+ /* FIXME: look for an equivalent math function otherwise let
+ * do the compiler do the optimization */
+ if (Ti == 0)
+ return 0;
+ else if (Ti < 100)
+ return 1;
+ else if (Ti > 25500)
+ return 255; /* 0xFF */
+ else
+ return Ti / 100;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_NOT_SUPPORTED
+ * ERROR_INVALID_PARAMETER
+ * ERROR_TIMEOUT
+ * ERROR_IO_DEVICE
+ * ERROR_BAD_DEVICE
+ */
+BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
+ LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ int biggestFd = -1;
+ fd_set read_set;
+ int nbFds = 0;
+ COMMTIMEOUTS* pTimeouts = NULL;
+ UCHAR vmin = 0;
+ UCHAR vtime = 0;
+ ULONGLONG Tmax = 0;
+ struct timeval tmaxTimeout;
+ struct timeval* pTmaxTimeout = NULL;
+ struct termios currentTermios;
+ EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */
+
+ if (!CommIsHandled(hDevice))
+ goto return_false;
+
+ if (lpOverlapped != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ if (lpNumberOfBytesRead == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ goto return_false;
+ }
+
+ *lpNumberOfBytesRead = 0; /* will be ajusted if required ... */
+
+ if (nNumberOfBytesToRead <= 0) /* N */
+ {
+ goto return_true; /* FIXME: or FALSE? */
+ }
+
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (currentTermios.c_lflag & ICANON)
+ {
+ CommLog_Print(WLOG_WARN, "Canonical mode not supported"); /* the timeout could not be set */
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ /* http://msdn.microsoft.com/en-us/library/hh439614%28v=vs.85%29.aspx
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx
+ *
+ * ReadIntervalTimeout | ReadTotalTimeoutMultiplier | ReadTotalTimeoutConstant | VMIN | VTIME |
+ * TMAX | 0 | 0 | 0 | N | 0 |
+ * INDEF | Blocks for N bytes available. 0< Ti <MAXULONG | 0 | 0 |
+ * N | Ti | INDEF | Blocks on first byte, then use Ti between bytes. MAXULONG | 0 | 0
+ * | 0 | 0 | 0 | Returns immediately with bytes available (don't block) MAXULONG |
+ * MAXULONG | 0< Tc <MAXULONG | N | 0 | Tc | Blocks on first byte
+ * during Tc or returns immediately whith bytes available MAXULONG | m |
+ * MAXULONG | | Invalid 0 | m | 0< Tc
+ * <MAXULONG | N | 0 | Tmax | Blocks on first byte during Tmax or returns
+ * immediately whith bytes available 0< Ti <MAXULONG | m | 0<
+ * Tc <MAXULONG | N | Ti | Tmax | Blocks on first byte, then use Ti between bytes.
+ * Tmax is used for the whole system call.
+ */
+ /* NB: timeouts are in milliseconds, VTIME are in deciseconds and is an unsigned char */
+ /* FIXME: double check whether open(pComm->fd_read_event, O_NONBLOCK) doesn't conflict with
+ * above use cases */
+ pTimeouts = &(pComm->timeouts);
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ goto return_false;
+ }
+
+ /* VMIN */
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0))
+ {
+ vmin = 0;
+ }
+ else
+ {
+ /* N */
+ /* vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255;*/ /* 0xFF */
+ /* NB: we might wait endlessly with vmin=N, prefer to
+ * force vmin=1 and return with bytes
+ * available. FIXME: is a feature disarded here? */
+ vmin = 1;
+ }
+
+ /* VTIME */
+
+ if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG))
+ {
+ /* Ti */
+ vtime = _vtime(pTimeouts->ReadIntervalTimeout);
+ }
+
+ /* TMAX */
+ pTmaxTimeout = &tmaxTimeout;
+
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG))
+ {
+ /* Tc */
+ Tmax = pTimeouts->ReadTotalTimeoutConstant;
+ }
+ else
+ {
+ /* Tmax */
+ Tmax = nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier +
+ pTimeouts->ReadTotalTimeoutConstant;
+
+ /* INDEFinitely */
+ if ((Tmax == 0) && (pTimeouts->ReadIntervalTimeout < MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutMultiplier == 0))
+ pTmaxTimeout = NULL;
+ }
+
+ if ((currentTermios.c_cc[VMIN] != vmin) || (currentTermios.c_cc[VTIME] != vtime))
+ {
+ currentTermios.c_cc[VMIN] = vmin;
+ currentTermios.c_cc[VTIME] = vtime;
+
+ if (tcsetattr(pComm->fd, TCSANOW, &currentTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN,
+ "CommReadFile failure, could not apply new timeout values: VMIN=%" PRIu8
+ ", VTIME=%" PRIu8 "",
+ vmin, vtime);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ /* wait indefinitely if pTmaxTimeout is NULL */
+
+ if (pTmaxTimeout != NULL)
+ {
+ ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
+
+ if (Tmax > 0) /* return immdiately if Tmax == 0 */
+ {
+ pTmaxTimeout->tv_sec = Tmax / 1000; /* s */
+ pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
+ }
+ }
+
+ /* FIXME: had expected eventfd_write() to return EAGAIN when
+ * there is no eventfd_read() but this not the case. */
+ /* discard a possible and no more relevant event */
+ eventfd_read(pComm->fd_read_event, NULL);
+ biggestFd = pComm->fd_read;
+
+ if (pComm->fd_read_event > biggestFd)
+ biggestFd = pComm->fd_read_event;
+
+ FD_ZERO(&read_set);
+ WINPR_ASSERT(pComm->fd_read_event < FD_SETSIZE);
+ WINPR_ASSERT(pComm->fd_read < FD_SETSIZE);
+ FD_SET(pComm->fd_read_event, &read_set);
+ FD_SET(pComm->fd_read, &read_set);
+ nbFds = select(biggestFd + 1, &read_set, NULL, NULL, pTmaxTimeout);
+
+ if (nbFds < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (nbFds == 0)
+ {
+ /* timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ /* read_set */
+
+ if (FD_ISSET(pComm->fd_read_event, &read_set))
+ {
+ eventfd_t event = 0;
+
+ if (eventfd_read(pComm->fd_read_event, &event) < 0)
+ {
+ if (errno == EAGAIN)
+ {
+ WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
+ /* keep on */
+ }
+ else
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "unexpected error on reading fd_read_event, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ /* FIXME: goto return_false ? */
+ }
+
+ WINPR_ASSERT(errno == EAGAIN);
+ }
+
+ if (event == WINPR_PURGE_RXABORT)
+ {
+ SetLastError(ERROR_CANCELLED);
+ goto return_false;
+ }
+
+ WINPR_ASSERT(event == WINPR_PURGE_RXABORT); /* no other expected event so far */
+ }
+
+ if (FD_ISSET(pComm->fd_read, &read_set))
+ {
+ ssize_t nbRead = 0;
+ nbRead = read(pComm->fd_read, lpBuffer, nNumberOfBytesToRead);
+
+ if (nbRead < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "CommReadFile failed, ReadIntervalTimeout=%" PRIu32
+ ", ReadTotalTimeoutMultiplier=%" PRIu32
+ ", ReadTotalTimeoutConstant=%" PRIu32 " VMIN=%u, VTIME=%u",
+ pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier,
+ pTimeouts->ReadTotalTimeoutConstant, currentTermios.c_cc[VMIN],
+ currentTermios.c_cc[VTIME]);
+ CommLog_Print(
+ WLOG_WARN, "CommReadFile failed, nNumberOfBytesToRead=%" PRIu32 ", errno=[%d] %s",
+ nNumberOfBytesToRead, errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (errno == EAGAIN)
+ {
+ /* keep on */
+ goto return_true; /* expect a read-loop to be implemented on the server side */
+ }
+ else if (errno == EBADF)
+ {
+ SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
+ goto return_false;
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ if (nbRead == 0)
+ {
+ /* termios timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ *lpNumberOfBytesRead = nbRead;
+
+ EnterCriticalSection(&pComm->EventsLock);
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ if (pComm->eventChar != '\0' && memchr(lpBuffer, pComm->eventChar, nbRead))
+ pComm->PendingEvents |= SERIAL_EV_RXCHAR;
+ }
+ LeaveCriticalSection(&pComm->EventsLock);
+ goto return_true;
+ }
+
+ WINPR_ASSERT(FALSE);
+ *lpNumberOfBytesRead = 0;
+return_false:
+ LeaveCriticalSection(&pComm->ReadLock);
+ return FALSE;
+return_true:
+ LeaveCriticalSection(&pComm->ReadLock);
+ return TRUE;
+}
+
+/**
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_NOT_SUPPORTED
+ * ERROR_INVALID_PARAMETER
+ * ERROR_BAD_DEVICE
+ */
+BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
+ LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ struct timeval tmaxTimeout;
+ struct timeval* pTmaxTimeout = NULL;
+ EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */
+
+ if (!CommIsHandled(hDevice))
+ goto return_false;
+
+ if (lpOverlapped != NULL)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ goto return_false;
+ }
+
+ if (lpNumberOfBytesWritten == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ goto return_false;
+ }
+
+ *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */
+
+ if (nNumberOfBytesToWrite <= 0)
+ {
+ goto return_true; /* FIXME: or FALSE? */
+ }
+
+ /* FIXME: had expected eventfd_write() to return EAGAIN when
+ * there is no eventfd_read() but this not the case. */
+ /* discard a possible and no more relevant event */
+ eventfd_read(pComm->fd_write_event, NULL);
+ /* ms */
+ ULONGLONG Tmax = nNumberOfBytesToWrite * pComm->timeouts.WriteTotalTimeoutMultiplier +
+ pComm->timeouts.WriteTotalTimeoutConstant;
+ /* NB: select() may update the timeout argument to indicate
+ * how much time was left. Keep the timeout variable out of
+ * the while() */
+ pTmaxTimeout = &tmaxTimeout;
+ ZeroMemory(pTmaxTimeout, sizeof(struct timeval));
+
+ if (Tmax > 0)
+ {
+ pTmaxTimeout->tv_sec = Tmax / 1000; /* s */
+ pTmaxTimeout->tv_usec = (Tmax % 1000) * 1000; /* us */
+ }
+ else if ((pComm->timeouts.WriteTotalTimeoutMultiplier == 0) &&
+ (pComm->timeouts.WriteTotalTimeoutConstant == 0))
+ {
+ pTmaxTimeout = NULL;
+ }
+
+ /* else return immdiately */
+
+ while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite)
+ {
+ int biggestFd = -1;
+ fd_set event_set;
+ fd_set write_set;
+ int nbFds = 0;
+ biggestFd = pComm->fd_write;
+
+ if (pComm->fd_write_event > biggestFd)
+ biggestFd = pComm->fd_write_event;
+
+ FD_ZERO(&event_set);
+ FD_ZERO(&write_set);
+ WINPR_ASSERT(pComm->fd_write_event < FD_SETSIZE);
+ WINPR_ASSERT(pComm->fd_write < FD_SETSIZE);
+ FD_SET(pComm->fd_write_event, &event_set);
+ FD_SET(pComm->fd_write, &write_set);
+ nbFds = select(biggestFd + 1, &event_set, &write_set, NULL, pTmaxTimeout);
+
+ if (nbFds < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "select() failure, errno=[%d] %s\n", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+
+ if (nbFds == 0)
+ {
+ /* timeout */
+ SetLastError(ERROR_TIMEOUT);
+ goto return_false;
+ }
+
+ /* event_set */
+
+ if (FD_ISSET(pComm->fd_write_event, &event_set))
+ {
+ eventfd_t event = 0;
+
+ if (eventfd_read(pComm->fd_write_event, &event) < 0)
+ {
+ if (errno == EAGAIN)
+ {
+ WINPR_ASSERT(FALSE); /* not quite sure this should ever happen */
+ /* keep on */
+ }
+ else
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "unexpected error on reading fd_write_event, errno=[%d] %s\n",
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ /* FIXME: goto return_false ? */
+ }
+
+ WINPR_ASSERT(errno == EAGAIN);
+ }
+
+ if (event == WINPR_PURGE_TXABORT)
+ {
+ SetLastError(ERROR_CANCELLED);
+ goto return_false;
+ }
+
+ WINPR_ASSERT(event == WINPR_PURGE_TXABORT); /* no other expected event so far */
+ }
+
+ /* write_set */
+
+ if (FD_ISSET(pComm->fd_write, &write_set))
+ {
+ ssize_t nbWritten = 0;
+ nbWritten = write(pComm->fd_write, ((const BYTE*)lpBuffer) + (*lpNumberOfBytesWritten),
+ nNumberOfBytesToWrite - (*lpNumberOfBytesWritten));
+
+ if (nbWritten < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN,
+ "CommWriteFile failed after %" PRIu32
+ " bytes written, errno=[%d] %s\n",
+ *lpNumberOfBytesWritten, errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (errno == EAGAIN)
+ {
+ /* keep on */
+ continue;
+ }
+ else if (errno == EBADF)
+ {
+ SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */
+ goto return_false;
+ }
+ else
+ {
+ WINPR_ASSERT(FALSE);
+ SetLastError(ERROR_IO_DEVICE);
+ goto return_false;
+ }
+ }
+
+ *lpNumberOfBytesWritten += nbWritten;
+ }
+ } /* while */
+
+ /* FIXME: this call to tcdrain() doesn't look correct and
+ * might hide a bug but was required while testing a serial
+ * printer. Its driver was expecting the modem line status
+ * SERIAL_MSR_DSR true after the sending which was never
+ * happenning otherwise. A purge was also done before each
+ * Write operation. The serial port was opened with:
+ * DesiredAccess=0x0012019F. The printer worked fine with
+ * mstsc. */
+ tcdrain(pComm->fd_write);
+
+return_true:
+ LeaveCriticalSection(&pComm->WriteLock);
+ return TRUE;
+
+return_false:
+ LeaveCriticalSection(&pComm->WriteLock);
+ return FALSE;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_ioctl.c b/winpr/libwinpr/comm/comm_ioctl.c
new file mode 100644
index 0000000..4f208ef
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_ioctl.c
@@ -0,0 +1,731 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <winpr/config.h>
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+
+#include <winpr/wlog.h>
+
+#include "comm.h"
+#include "comm_ioctl.h"
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+#include "comm_sercx2_sys.h"
+
+/* NB: MS-RDPESP's recommendation:
+ *
+ * <2> Section 3.2.5.1.6: Windows Implementations use IOCTL constants
+ * for IoControlCode values. The content and values of the IOCTLs are
+ * opaque to the protocol. On the server side, the data contained in
+ * an IOCTL is simply packaged and sent to the client side. For
+ * maximum compatibility between the different versions of the Windows
+ * operating system, the client implementation only singles out
+ * critical IOCTLs and invokes the applicable Win32 port API. The
+ * other IOCTLS are passed directly to the client-side driver, and the
+ * processing of this value depends on the drivers installed on the
+ * client side. The values and parameters for these IOCTLS can be
+ * found in [MSFT-W2KDDK] Volume 2, Part 2—Serial and Parallel
+ * Drivers, and in [MSDN-PORTS].
+ */
+
+const char* _comm_serial_ioctl_name(ULONG number)
+{
+ for (int i = 0; _SERIAL_IOCTL_NAMES[i].number != 0; i++)
+ {
+ if (_SERIAL_IOCTL_NAMES[i].number == number)
+ {
+ return _SERIAL_IOCTL_NAMES[i].name;
+ }
+ }
+
+ return "(unknown ioctl name)";
+}
+
+static BOOL s_CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ SERIAL_DRIVER* pServerSerialDriver = NULL;
+
+ if (!CommIsHandleValid(hDevice))
+ return FALSE;
+
+ if (lpOverlapped)
+ {
+ SetLastError(ERROR_NOT_SUPPORTED);
+ return FALSE;
+ }
+
+ if (lpBytesReturned == NULL)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */
+ return FALSE;
+ }
+
+ /* clear any previous last error */
+ SetLastError(ERROR_SUCCESS);
+
+ *lpBytesReturned = 0; /* will be ajusted if required ... */
+
+ CommLog_Print(WLOG_DEBUG, "CommDeviceIoControl: IoControlCode: 0x%0.8x", dwIoControlCode);
+
+ /* remoteSerialDriver to be use ...
+ *
+ * FIXME: might prefer to use an automatic rather than static structure
+ */
+ switch (pComm->serverSerialDriverId)
+ {
+ case SerialDriverSerialSys:
+ pServerSerialDriver = SerialSys_s();
+ break;
+
+ case SerialDriverSerCxSys:
+ pServerSerialDriver = SerCxSys_s();
+ break;
+
+ case SerialDriverSerCx2Sys:
+ pServerSerialDriver = SerCx2Sys_s();
+ break;
+
+ case SerialDriverUnknown:
+ default:
+ CommLog_Print(WLOG_DEBUG, "Unknown remote serial driver (%d), using SerCx2.sys",
+ pComm->serverSerialDriverId);
+ pServerSerialDriver = SerCx2Sys_s();
+ break;
+ }
+
+ WINPR_ASSERT(pServerSerialDriver != NULL);
+
+ switch (dwIoControlCode)
+ {
+ case IOCTL_USBPRINT_GET_1284_ID:
+ {
+ /* FIXME:
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */
+ *lpBytesReturned = nOutBufferSize; /* an empty OutputBuffer will be returned */
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ return FALSE;
+ }
+ case IOCTL_SERIAL_SET_BAUD_RATE:
+ {
+ if (pServerSerialDriver->set_baud_rate)
+ {
+ SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_BAUD_RATE));
+ if (nInBufferSize < sizeof(SERIAL_BAUD_RATE))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_baud_rate(pComm, pBaudRate);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_BAUD_RATE:
+ {
+ if (pServerSerialDriver->get_baud_rate)
+ {
+ SERIAL_BAUD_RATE* pBaudRate = (SERIAL_BAUD_RATE*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_BAUD_RATE));
+ if (nOutBufferSize < sizeof(SERIAL_BAUD_RATE))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_baud_rate(pComm, pBaudRate))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_BAUD_RATE);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_PROPERTIES:
+ {
+ if (pServerSerialDriver->get_properties)
+ {
+ COMMPROP* pProperties = (COMMPROP*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(COMMPROP));
+ if (nOutBufferSize < sizeof(COMMPROP))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_properties(pComm, pProperties))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(COMMPROP);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_CHARS:
+ {
+ if (pServerSerialDriver->set_serial_chars)
+ {
+ SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_CHARS));
+ if (nInBufferSize < sizeof(SERIAL_CHARS))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_serial_chars(pComm, pSerialChars);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_CHARS:
+ {
+ if (pServerSerialDriver->get_serial_chars)
+ {
+ SERIAL_CHARS* pSerialChars = (SERIAL_CHARS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_CHARS));
+ if (nOutBufferSize < sizeof(SERIAL_CHARS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_serial_chars(pComm, pSerialChars))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_CHARS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_LINE_CONTROL:
+ {
+ if (pServerSerialDriver->set_line_control)
+ {
+ SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL));
+ if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_line_control(pComm, pLineControl);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_LINE_CONTROL:
+ {
+ if (pServerSerialDriver->get_line_control)
+ {
+ SERIAL_LINE_CONTROL* pLineControl = (SERIAL_LINE_CONTROL*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_LINE_CONTROL));
+ if (nOutBufferSize < sizeof(SERIAL_LINE_CONTROL))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_line_control(pComm, pLineControl))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_LINE_CONTROL);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_HANDFLOW:
+ {
+ if (pServerSerialDriver->set_handflow)
+ {
+ SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_HANDFLOW));
+ if (nInBufferSize < sizeof(SERIAL_HANDFLOW))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_handflow(pComm, pHandflow);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_HANDFLOW:
+ {
+ if (pServerSerialDriver->get_handflow)
+ {
+ SERIAL_HANDFLOW* pHandflow = (SERIAL_HANDFLOW*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_HANDFLOW));
+ if (nOutBufferSize < sizeof(SERIAL_HANDFLOW))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_handflow(pComm, pHandflow))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_HANDFLOW);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_TIMEOUTS:
+ {
+ if (pServerSerialDriver->set_timeouts)
+ {
+ SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_TIMEOUTS));
+ if (nInBufferSize < sizeof(SERIAL_TIMEOUTS))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_timeouts(pComm, pHandflow);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_TIMEOUTS:
+ {
+ if (pServerSerialDriver->get_timeouts)
+ {
+ SERIAL_TIMEOUTS* pHandflow = (SERIAL_TIMEOUTS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_TIMEOUTS));
+ if (nOutBufferSize < sizeof(SERIAL_TIMEOUTS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_timeouts(pComm, pHandflow))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_TIMEOUTS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_DTR:
+ {
+ if (pServerSerialDriver->set_dtr)
+ {
+ return pServerSerialDriver->set_dtr(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CLR_DTR:
+ {
+ if (pServerSerialDriver->clear_dtr)
+ {
+ return pServerSerialDriver->clear_dtr(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_RTS:
+ {
+ if (pServerSerialDriver->set_rts)
+ {
+ return pServerSerialDriver->set_rts(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CLR_RTS:
+ {
+ if (pServerSerialDriver->clear_rts)
+ {
+ return pServerSerialDriver->clear_rts(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_MODEMSTATUS:
+ {
+ if (pServerSerialDriver->get_modemstatus)
+ {
+ ULONG* pRegister = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_modemstatus(pComm, pRegister))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_WAIT_MASK:
+ {
+ if (pServerSerialDriver->set_wait_mask)
+ {
+ ULONG* pWaitMask = (ULONG*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
+ if (nInBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_wait_mask(pComm, pWaitMask);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_WAIT_MASK:
+ {
+ if (pServerSerialDriver->get_wait_mask)
+ {
+ ULONG* pWaitMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_wait_mask(pComm, pWaitMask))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_WAIT_ON_MASK:
+ {
+ if (pServerSerialDriver->wait_on_mask)
+ {
+ ULONG* pOutputMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->wait_on_mask(pComm, pOutputMask))
+ {
+ *lpBytesReturned = sizeof(ULONG);
+ return FALSE;
+ }
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_QUEUE_SIZE:
+ {
+ if (pServerSerialDriver->set_queue_size)
+ {
+ SERIAL_QUEUE_SIZE* pQueueSize = (SERIAL_QUEUE_SIZE*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(SERIAL_QUEUE_SIZE));
+ if (nInBufferSize < sizeof(SERIAL_QUEUE_SIZE))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->set_queue_size(pComm, pQueueSize);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_PURGE:
+ {
+ if (pServerSerialDriver->purge)
+ {
+ ULONG* pPurgeMask = (ULONG*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(ULONG));
+ if (nInBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->purge(pComm, pPurgeMask);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_COMMSTATUS:
+ {
+ if (pServerSerialDriver->get_commstatus)
+ {
+ SERIAL_STATUS* pCommstatus = (SERIAL_STATUS*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(SERIAL_STATUS));
+ if (nOutBufferSize < sizeof(SERIAL_STATUS))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_commstatus(pComm, pCommstatus))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(SERIAL_STATUS);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_BREAK_ON:
+ {
+ if (pServerSerialDriver->set_break_on)
+ {
+ return pServerSerialDriver->set_break_on(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_BREAK_OFF:
+ {
+ if (pServerSerialDriver->set_break_off)
+ {
+ return pServerSerialDriver->set_break_off(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_XOFF:
+ {
+ if (pServerSerialDriver->set_xoff)
+ {
+ return pServerSerialDriver->set_xoff(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_SET_XON:
+ {
+ if (pServerSerialDriver->set_xon)
+ {
+ return pServerSerialDriver->set_xon(pComm);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_GET_DTRRTS:
+ {
+ if (pServerSerialDriver->get_dtrrts)
+ {
+ ULONG* pMask = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->get_dtrrts(pComm, pMask))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_CONFIG_SIZE:
+ {
+ if (pServerSerialDriver->config_size)
+ {
+ ULONG* pSize = (ULONG*)lpOutBuffer;
+
+ WINPR_ASSERT(nOutBufferSize >= sizeof(ULONG));
+ if (nOutBufferSize < sizeof(ULONG))
+ {
+ SetLastError(ERROR_INSUFFICIENT_BUFFER);
+ return FALSE;
+ }
+
+ if (!pServerSerialDriver->config_size(pComm, pSize))
+ return FALSE;
+
+ *lpBytesReturned = sizeof(ULONG);
+ return TRUE;
+ }
+ break;
+ }
+ case IOCTL_SERIAL_IMMEDIATE_CHAR:
+ {
+ if (pServerSerialDriver->immediate_char)
+ {
+ UCHAR* pChar = (UCHAR*)lpInBuffer;
+
+ WINPR_ASSERT(nInBufferSize >= sizeof(UCHAR));
+ if (nInBufferSize < sizeof(UCHAR))
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return pServerSerialDriver->immediate_char(pComm, pChar);
+ }
+ break;
+ }
+ case IOCTL_SERIAL_RESET_DEVICE:
+ {
+ if (pServerSerialDriver->reset_device)
+ {
+ return pServerSerialDriver->reset_device(pComm);
+ }
+ break;
+ }
+ }
+
+ CommLog_Print(
+ WLOG_WARN, _T("unsupported IoControlCode=[0x%08" PRIX32 "] %s (remote serial driver: %s)"),
+ dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), pServerSerialDriver->name);
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED); /* => STATUS_NOT_IMPLEMENTED */
+ return FALSE;
+}
+
+/**
+ * FIXME: to be used through winpr-io's DeviceIoControl
+ *
+ * Any previous error as returned by GetLastError is cleared.
+ *
+ * ERRORS:
+ * ERROR_INVALID_HANDLE
+ * ERROR_INVALID_PARAMETER
+ * ERROR_NOT_SUPPORTED lpOverlapped is not supported
+ * ERROR_INSUFFICIENT_BUFFER
+ * ERROR_CALL_NOT_IMPLEMENTED unimplemented ioctl
+ */
+BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer,
+ DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize,
+ LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped)
+{
+ WINPR_COMM* pComm = (WINPR_COMM*)hDevice;
+ BOOL result = 0;
+
+ if (hDevice == INVALID_HANDLE_VALUE)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+
+ if (!CommIsHandled(hDevice))
+ return FALSE;
+
+ if (!pComm->fd)
+ {
+ SetLastError(ERROR_INVALID_HANDLE);
+ return FALSE;
+ }
+
+ result = s_CommDeviceIoControl(hDevice, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer,
+ nOutBufferSize, lpBytesReturned, lpOverlapped);
+
+ if (lpBytesReturned && *lpBytesReturned != nOutBufferSize)
+ {
+ /* This might be a hint for a bug, especially when result==TRUE */
+ CommLog_Print(WLOG_WARN,
+ "lpBytesReturned=%" PRIu32 " and nOutBufferSize=%" PRIu32 " are different!",
+ *lpBytesReturned, nOutBufferSize);
+ }
+
+ if (pComm->permissive)
+ {
+ if (!result)
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "[permissive]: whereas it failed, made to succeed IoControlCode=[0x%08" PRIX32
+ "] %s, last-error: 0x%08" PRIX32 "",
+ dwIoControlCode, _comm_serial_ioctl_name(dwIoControlCode), GetLastError());
+ }
+
+ return TRUE; /* always! */
+ }
+
+ return result;
+}
+
+int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p)
+{
+ int result = 0;
+ struct termios currentState = { 0 };
+
+ if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcsetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ /* NB: tcsetattr() can succeed even if not all changes have been applied. */
+ if ((result = tcgetattr(fd, &currentState)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
+ {
+ CommLog_Print(WLOG_DEBUG,
+ "all termios parameters are not set yet, doing a second attempt...");
+ if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "2nd tcsetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ ZeroMemory(&currentState, sizeof(struct termios));
+ if ((result = tcgetattr(fd, &currentState)) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno);
+ return result;
+ }
+
+ if (memcmp(&currentState, termios_p, sizeof(struct termios)) != 0)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Failure: all termios parameters are still not set on a second attempt");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_ioctl.h b/winpr/libwinpr/comm/comm_ioctl.h
new file mode 100644
index 0000000..6467a43
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_ioctl.h
@@ -0,0 +1,236 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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 WINPR_COMM_IOCTL_H_
+#define WINPR_COMM_IOCTL_H_
+
+#if defined __linux__ && !defined ANDROID
+
+#include <termios.h>
+
+#include <winpr/io.h>
+#include <winpr/tchar.h>
+#include <winpr/wtypes.h>
+
+#include "comm.h"
+
+/* Serial I/O Request Interface: http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx
+ * Ntddser.h http://msdn.microsoft.com/en-us/cc308432.aspx
+ * Ntddpar.h http://msdn.microsoft.com/en-us/cc308431.aspx
+ */
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ /* TODO: defines and types below are very similar to those in comm.h, keep only
+ * those that differ more than the names */
+
+#define STOP_BIT_1 0
+#define STOP_BITS_1_5 1
+#define STOP_BITS_2 2
+
+#define NO_PARITY 0
+#define ODD_PARITY 1
+#define EVEN_PARITY 2
+#define MARK_PARITY 3
+#define SPACE_PARITY 4
+
+ typedef struct
+ {
+ ULONG BaudRate;
+ } SERIAL_BAUD_RATE, *PSERIAL_BAUD_RATE;
+
+ typedef struct
+ {
+ UCHAR EofChar;
+ UCHAR ErrorChar;
+ UCHAR BreakChar;
+ UCHAR EventChar;
+ UCHAR XonChar;
+ UCHAR XoffChar;
+ } SERIAL_CHARS, *PSERIAL_CHARS;
+
+ typedef struct
+ {
+ UCHAR StopBits;
+ UCHAR Parity;
+ UCHAR WordLength;
+ } SERIAL_LINE_CONTROL, *PSERIAL_LINE_CONTROL;
+
+ typedef struct
+ {
+ ULONG ControlHandShake;
+ ULONG FlowReplace;
+ LONG XonLimit;
+ LONG XoffLimit;
+ } SERIAL_HANDFLOW, *PSERIAL_HANDFLOW;
+
+#define SERIAL_DTR_MASK ((ULONG)0x03)
+#define SERIAL_DTR_CONTROL ((ULONG)0x01)
+#define SERIAL_DTR_HANDSHAKE ((ULONG)0x02)
+#define SERIAL_CTS_HANDSHAKE ((ULONG)0x08)
+#define SERIAL_DSR_HANDSHAKE ((ULONG)0x10)
+#define SERIAL_DCD_HANDSHAKE ((ULONG)0x20)
+#define SERIAL_OUT_HANDSHAKEMASK ((ULONG)0x38)
+#define SERIAL_DSR_SENSITIVITY ((ULONG)0x40)
+#define SERIAL_ERROR_ABORT ((ULONG)0x80000000)
+#define SERIAL_CONTROL_INVALID ((ULONG)0x7fffff84)
+#define SERIAL_AUTO_TRANSMIT ((ULONG)0x01)
+#define SERIAL_AUTO_RECEIVE ((ULONG)0x02)
+#define SERIAL_ERROR_CHAR ((ULONG)0x04)
+#define SERIAL_NULL_STRIPPING ((ULONG)0x08)
+#define SERIAL_BREAK_CHAR ((ULONG)0x10)
+#define SERIAL_RTS_MASK ((ULONG)0xc0)
+#define SERIAL_RTS_CONTROL ((ULONG)0x40)
+#define SERIAL_RTS_HANDSHAKE ((ULONG)0x80)
+#define SERIAL_TRANSMIT_TOGGLE ((ULONG)0xc0)
+#define SERIAL_XOFF_CONTINUE ((ULONG)0x80000000)
+#define SERIAL_FLOW_INVALID ((ULONG)0x7fffff20)
+
+#define SERIAL_SP_SERIALCOMM ((ULONG)0x00000001)
+
+#define SERIAL_SP_UNSPECIFIED ((ULONG)0x00000000)
+#define SERIAL_SP_RS232 ((ULONG)0x00000001)
+#define SERIAL_SP_PARALLEL ((ULONG)0x00000002)
+#define SERIAL_SP_RS422 ((ULONG)0x00000003)
+#define SERIAL_SP_RS423 ((ULONG)0x00000004)
+#define SERIAL_SP_RS449 ((ULONG)0x00000005)
+#define SERIAL_SP_MODEM ((ULONG)0x00000006)
+#define SERIAL_SP_FAX ((ULONG)0x00000021)
+#define SERIAL_SP_SCANNER ((ULONG)0x00000022)
+#define SERIAL_SP_BRIDGE ((ULONG)0x00000100)
+#define SERIAL_SP_LAT ((ULONG)0x00000101)
+#define SERIAL_SP_TELNET ((ULONG)0x00000102)
+#define SERIAL_SP_X25 ((ULONG)0x00000103)
+
+ typedef struct
+ {
+ ULONG ReadIntervalTimeout;
+ ULONG ReadTotalTimeoutMultiplier;
+ ULONG ReadTotalTimeoutConstant;
+ ULONG WriteTotalTimeoutMultiplier;
+ ULONG WriteTotalTimeoutConstant;
+ } SERIAL_TIMEOUTS, *PSERIAL_TIMEOUTS;
+
+#define SERIAL_MSR_DCTS 0x01
+#define SERIAL_MSR_DDSR 0x02
+#define SERIAL_MSR_TERI 0x04
+#define SERIAL_MSR_DDCD 0x08
+#define SERIAL_MSR_CTS 0x10
+#define SERIAL_MSR_DSR 0x20
+#define SERIAL_MSR_RI 0x40
+#define SERIAL_MSR_DCD 0x80
+
+ typedef struct
+ {
+ ULONG InSize;
+ ULONG OutSize;
+ } SERIAL_QUEUE_SIZE, *PSERIAL_QUEUE_SIZE;
+
+#define SERIAL_PURGE_TXABORT 0x00000001
+#define SERIAL_PURGE_RXABORT 0x00000002
+#define SERIAL_PURGE_TXCLEAR 0x00000004
+#define SERIAL_PURGE_RXCLEAR 0x00000008
+
+ typedef struct
+ {
+ ULONG Errors;
+ ULONG HoldReasons;
+ ULONG AmountInInQueue;
+ ULONG AmountInOutQueue;
+ BOOLEAN EofReceived;
+ BOOLEAN WaitForImmediate;
+ } SERIAL_STATUS, *PSERIAL_STATUS;
+
+#define SERIAL_TX_WAITING_FOR_CTS ((ULONG)0x00000001)
+#define SERIAL_TX_WAITING_FOR_DSR ((ULONG)0x00000002)
+#define SERIAL_TX_WAITING_FOR_DCD ((ULONG)0x00000004)
+#define SERIAL_TX_WAITING_FOR_XON ((ULONG)0x00000008)
+#define SERIAL_TX_WAITING_XOFF_SENT ((ULONG)0x00000010)
+#define SERIAL_TX_WAITING_ON_BREAK ((ULONG)0x00000020)
+#define SERIAL_RX_WAITING_FOR_DSR ((ULONG)0x00000040)
+
+#define SERIAL_ERROR_BREAK ((ULONG)0x00000001)
+#define SERIAL_ERROR_FRAMING ((ULONG)0x00000002)
+#define SERIAL_ERROR_OVERRUN ((ULONG)0x00000004)
+#define SERIAL_ERROR_QUEUEOVERRUN ((ULONG)0x00000008)
+#define SERIAL_ERROR_PARITY ((ULONG)0x00000010)
+
+#define SERIAL_DTR_STATE ((ULONG)0x00000001)
+#define SERIAL_RTS_STATE ((ULONG)0x00000002)
+#define SERIAL_CTS_STATE ((ULONG)0x00000010)
+#define SERIAL_DSR_STATE ((ULONG)0x00000020)
+#define SERIAL_RI_STATE ((ULONG)0x00000040)
+#define SERIAL_DCD_STATE ((ULONG)0x00000080)
+
+ /**
+ * A function might be NULL if not supported by the underlying driver.
+ *
+ * FIXME: better have to use input and output buffers for all functions?
+ */
+ typedef struct
+ {
+ SERIAL_DRIVER_ID id;
+ TCHAR* name;
+ BOOL (*set_baud_rate)(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate);
+ BOOL (*get_baud_rate)(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate);
+ BOOL (*get_properties)(WINPR_COMM* pComm, COMMPROP* pProperties);
+ BOOL (*set_serial_chars)(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars);
+ BOOL (*get_serial_chars)(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars);
+ BOOL (*set_line_control)(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl);
+ BOOL (*get_line_control)(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl);
+ BOOL (*set_handflow)(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow);
+ BOOL (*get_handflow)(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow);
+ BOOL (*set_timeouts)(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts);
+ BOOL (*get_timeouts)(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts);
+ BOOL (*set_dtr)(WINPR_COMM* pComm);
+ BOOL (*clear_dtr)(WINPR_COMM* pComm);
+ BOOL (*set_rts)(WINPR_COMM* pComm);
+ BOOL (*clear_rts)(WINPR_COMM* pComm);
+ BOOL (*get_modemstatus)(WINPR_COMM* pComm, ULONG* pRegister);
+ BOOL (*set_wait_mask)(WINPR_COMM* pComm, const ULONG* pWaitMask);
+ BOOL (*get_wait_mask)(WINPR_COMM* pComm, ULONG* pWaitMask);
+ BOOL (*wait_on_mask)(WINPR_COMM* pComm, ULONG* pOutputMask);
+ BOOL (*set_queue_size)(WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize);
+ BOOL (*purge)(WINPR_COMM* pComm, const ULONG* pPurgeMask);
+ BOOL (*get_commstatus)(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus);
+ BOOL (*set_break_on)(WINPR_COMM* pComm);
+ BOOL (*set_break_off)(WINPR_COMM* pComm);
+ BOOL (*set_xoff)(WINPR_COMM* pComm);
+ BOOL (*set_xon)(WINPR_COMM* pComm);
+ BOOL (*get_dtrrts)(WINPR_COMM* pComm, ULONG* pMask);
+ BOOL (*config_size)(WINPR_COMM* pComm, ULONG* pSize);
+ BOOL (*immediate_char)(WINPR_COMM* pComm, const UCHAR* pChar);
+ BOOL (*reset_device)(WINPR_COMM* pComm);
+
+ } SERIAL_DRIVER;
+
+ int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios* termios_p);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* WINPR_COMM_IOCTL_H_ */
diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.c b/winpr/libwinpr/comm/comm_sercx2_sys.c
new file mode 100644
index 0000000..d636124
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx2_sys.c
@@ -0,0 +1,200 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/wlog.h>
+
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+
+#include "comm_sercx2_sys.h"
+
+/* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx
+ *
+ * SerCx2 does not support special characters. SerCx2 always completes
+ * an IOCTL_SERIAL_SET_CHARS request with a STATUS_SUCCESS status
+ * code, but does not set any special characters or perform any other
+ * operation in response to this request. For an
+ * IOCTL_SERIAL_GET_CHARS request, SerCx2 sets all the character
+ * values in the SERIAL_CHARS structure to null, and completes the
+ * request with a STATUS_SUCCESS status code.
+ */
+
+static BOOL _set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars)
+{
+ return TRUE;
+}
+
+static BOOL _get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars)
+{
+ WINPR_ASSERT(pSerialChars);
+ ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS));
+ return TRUE;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+/* FIXME: only using the Serial.sys' events, complete the support of the remaining events */
+static const ULONG _SERCX2_SYS_SUPPORTED_EV_MASK =
+ SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR |
+ SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING |
+ /* SERIAL_EV_PERR | */
+ SERIAL_EV_RX80FULL /*|
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+/* use Serial.sys for basis (not SerCx.sys) */
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ possibleMask = *pWaitMask & _SERCX2_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (SerCx2.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+ return FALSE;
+ }
+
+ /* NB: All events that are supported by SerCx.sys are supported by Serial.sys*/
+ return pSerialSys->set_wait_mask(pComm, pWaitMask);
+}
+
+static BOOL _purge(WINPR_COMM* pComm, const ULONG* pPurgeMask)
+{
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/ff546655%28v=vs.85%29.aspx */
+
+ if ((*pPurgeMask & SERIAL_PURGE_RXCLEAR) && !(*pPurgeMask & SERIAL_PURGE_RXABORT))
+ {
+ CommLog_Print(WLOG_WARN,
+ "Expecting SERIAL_PURGE_RXABORT since SERIAL_PURGE_RXCLEAR is set");
+ SetLastError(ERROR_INVALID_DEVICE_OBJECT_PARAMETER);
+ return FALSE;
+ }
+
+ if ((*pPurgeMask & SERIAL_PURGE_TXCLEAR) && !(*pPurgeMask & SERIAL_PURGE_TXABORT))
+ {
+ CommLog_Print(WLOG_WARN,
+ "Expecting SERIAL_PURGE_TXABORT since SERIAL_PURGE_TXCLEAR is set");
+ SetLastError(ERROR_INVALID_DEVICE_OBJECT_PARAMETER);
+ return FALSE;
+ }
+
+ return pSerialSys->purge(pComm, pPurgeMask);
+}
+
+/* specific functions only */
+static SERIAL_DRIVER _SerCx2Sys = {
+ .id = SerialDriverSerCx2Sys,
+ .name = _T("SerCx2.sys"),
+ .set_baud_rate = NULL,
+ .get_baud_rate = NULL,
+ .get_properties = NULL,
+ .set_serial_chars = _set_serial_chars,
+ .get_serial_chars = _get_serial_chars,
+ .set_line_control = NULL,
+ .get_line_control = NULL,
+ .set_handflow = NULL,
+ .get_handflow = NULL,
+ .set_timeouts = NULL,
+ .get_timeouts = NULL,
+ .set_dtr = NULL,
+ .clear_dtr = NULL,
+ .set_rts = NULL,
+ .clear_rts = NULL,
+ .get_modemstatus = NULL,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = NULL,
+ .wait_on_mask = NULL,
+ .set_queue_size = NULL,
+ .purge = _purge,
+ .get_commstatus = NULL,
+ .set_break_on = NULL,
+ .set_break_off = NULL,
+ .set_xoff = NULL, /* not supported by SerCx2.sys */
+ .set_xon = NULL, /* not supported by SerCx2.sys */
+ .get_dtrrts = NULL,
+ .config_size = NULL, /* not supported by SerCx2.sys */
+ .immediate_char = NULL, /* not supported by SerCx2.sys */
+ .reset_device = NULL, /* not supported by SerCx2.sys */
+};
+
+SERIAL_DRIVER* SerCx2Sys_s(void)
+{
+ /* _SerCx2Sys completed with inherited functions from SerialSys or SerCxSys */
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+ SERIAL_DRIVER* pSerCxSys = SerCxSys_s();
+ if (!pSerialSys || !pSerCxSys)
+ return NULL;
+
+ _SerCx2Sys.set_baud_rate = pSerialSys->set_baud_rate;
+ _SerCx2Sys.get_baud_rate = pSerialSys->get_baud_rate;
+
+ _SerCx2Sys.get_properties = pSerialSys->get_properties;
+
+ _SerCx2Sys.set_line_control = pSerCxSys->set_line_control;
+ _SerCx2Sys.get_line_control = pSerCxSys->get_line_control;
+
+ /* Only SERIAL_CTS_HANDSHAKE, SERIAL_RTS_CONTROL and SERIAL_RTS_HANDSHAKE flags are really
+ * required by SerCx2.sys http://msdn.microsoft.com/en-us/library/jj680685%28v=vs.85%29.aspx
+ */
+ _SerCx2Sys.set_handflow = pSerialSys->set_handflow;
+ _SerCx2Sys.get_handflow = pSerialSys->get_handflow;
+
+ _SerCx2Sys.set_timeouts = pSerialSys->set_timeouts;
+ _SerCx2Sys.get_timeouts = pSerialSys->get_timeouts;
+
+ _SerCx2Sys.set_dtr = pSerialSys->set_dtr;
+ _SerCx2Sys.clear_dtr = pSerialSys->clear_dtr;
+
+ _SerCx2Sys.set_rts = pSerialSys->set_rts;
+ _SerCx2Sys.clear_rts = pSerialSys->clear_rts;
+
+ _SerCx2Sys.get_modemstatus = pSerialSys->get_modemstatus;
+
+ _SerCx2Sys.set_wait_mask = pSerialSys->set_wait_mask;
+ _SerCx2Sys.get_wait_mask = pSerialSys->get_wait_mask;
+ _SerCx2Sys.wait_on_mask = pSerialSys->wait_on_mask;
+
+ _SerCx2Sys.set_queue_size = pSerialSys->set_queue_size;
+
+ _SerCx2Sys.get_commstatus = pSerialSys->get_commstatus;
+
+ _SerCx2Sys.set_break_on = pSerialSys->set_break_on;
+ _SerCx2Sys.set_break_off = pSerialSys->set_break_off;
+
+ _SerCx2Sys.get_dtrrts = pSerialSys->get_dtrrts;
+
+ return &_SerCx2Sys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.h b/winpr/libwinpr/comm/comm_sercx2_sys.h
new file mode 100644
index 0000000..a290653
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx2_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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 COMM_SERCX2_SYS_H
+#define COMM_SERCX2_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerCx2Sys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERCX2_SYS_H */
diff --git a/winpr/libwinpr/comm/comm_sercx_sys.c b/winpr/libwinpr/comm/comm_sercx_sys.c
new file mode 100644
index 0000000..ece456f
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx_sys.c
@@ -0,0 +1,266 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <termios.h>
+
+#include <winpr/wlog.h>
+
+#include "comm_serial_sys.h"
+#include "comm_sercx_sys.h"
+
+static BOOL _set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow)
+{
+ SERIAL_HANDFLOW SerCxHandflow;
+ BOOL result = TRUE;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ memcpy(&SerCxHandflow, pHandflow, sizeof(SERIAL_HANDFLOW));
+
+ /* filter out unsupported bits by SerCx.sys
+ *
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/jj680685%28v=vs.85%29.aspx
+ */
+
+ SerCxHandflow.ControlHandShake =
+ pHandflow->ControlHandShake &
+ (SERIAL_DTR_CONTROL | SERIAL_DTR_HANDSHAKE | SERIAL_CTS_HANDSHAKE | SERIAL_DSR_HANDSHAKE);
+ SerCxHandflow.FlowReplace =
+ pHandflow->FlowReplace & (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE);
+
+ if (SerCxHandflow.ControlHandShake != pHandflow->ControlHandShake)
+ {
+ if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DCD_HANDSHAKE not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DSR_SENSITIVITY not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_ERROR_ABORT not supposed to be implemented by SerCx.sys");
+ }
+
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ result = FALSE;
+ }
+
+ if (SerCxHandflow.FlowReplace != pHandflow->FlowReplace)
+ {
+ if (pHandflow->ControlHandShake & SERIAL_AUTO_TRANSMIT)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_AUTO_TRANSMIT not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_AUTO_RECEIVE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_AUTO_RECEIVE not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_CHAR)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_ERROR_CHAR not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_NULL_STRIPPING)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_NULL_STRIPPING not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_BREAK_CHAR)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_BREAK_CHAR not supposed to be implemented by SerCx.sys");
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_XOFF_CONTINUE)
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_XOFF_CONTINUE not supposed to be implemented by SerCx.sys");
+ }
+
+ SetLastError(ERROR_CALL_NOT_IMPLEMENTED);
+ result = FALSE;
+ }
+
+ if (!pSerialSys->set_handflow(pComm, &SerCxHandflow))
+ return FALSE;
+
+ return result;
+}
+
+static BOOL _get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow)
+{
+ BOOL result = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ result = pSerialSys->get_handflow(pComm, pHandflow);
+
+ /* filter out unsupported bits by SerCx.sys
+ *
+ * http://msdn.microsoft.com/en-us/library/windows/hardware/jj680685%28v=vs.85%29.aspx
+ */
+
+ pHandflow->ControlHandShake =
+ pHandflow->ControlHandShake &
+ (SERIAL_DTR_CONTROL | SERIAL_DTR_HANDSHAKE | SERIAL_CTS_HANDSHAKE | SERIAL_DSR_HANDSHAKE);
+ pHandflow->FlowReplace = pHandflow->FlowReplace & (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE);
+
+ return result;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+static const ULONG _SERCX_SYS_SUPPORTED_EV_MASK = SERIAL_EV_RXCHAR |
+ /* SERIAL_EV_RXFLAG | */
+ SERIAL_EV_TXEMPTY | SERIAL_EV_CTS |
+ SERIAL_EV_DSR | SERIAL_EV_RLSD | SERIAL_EV_BREAK |
+ SERIAL_EV_ERR | SERIAL_EV_RING /* |
+ SERIAL_EV_PERR |
+ SERIAL_EV_RX80FULL |
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+
+ possibleMask = *pWaitMask & _SERCX_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (SerCx.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+ return FALSE;
+ }
+
+ /* NB: All events that are supported by SerCx.sys are supported by Serial.sys*/
+ return pSerialSys->set_wait_mask(pComm, pWaitMask);
+}
+
+/* specific functions only */
+static SERIAL_DRIVER _SerCxSys = {
+ .id = SerialDriverSerCxSys,
+ .name = _T("SerCx.sys"),
+ .set_baud_rate = NULL,
+ .get_baud_rate = NULL,
+ .get_properties = NULL,
+ .set_serial_chars = NULL,
+ .get_serial_chars = NULL,
+ .set_line_control = NULL,
+ .get_line_control = NULL,
+ .set_handflow = _set_handflow,
+ .get_handflow = _get_handflow,
+ .set_timeouts = NULL,
+ .get_timeouts = NULL,
+ .set_dtr = NULL,
+ .clear_dtr = NULL,
+ .set_rts = NULL,
+ .clear_rts = NULL,
+ .get_modemstatus = NULL,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = NULL,
+ .wait_on_mask = NULL,
+ .set_queue_size = NULL,
+ .purge = NULL,
+ .get_commstatus = NULL,
+ .set_break_on = NULL,
+ .set_break_off = NULL,
+ .set_xoff = NULL,
+ .set_xon = NULL,
+ .get_dtrrts = NULL,
+ .config_size = NULL, /* not supported by SerCx.sys */
+ .immediate_char = NULL,
+ .reset_device = NULL, /* not supported by SerCx.sys */
+};
+
+SERIAL_DRIVER* SerCxSys_s(void)
+{
+ /* _SerCxSys completed with inherited functions from SerialSys */
+ SERIAL_DRIVER* pSerialSys = SerialSys_s();
+ if (!pSerialSys)
+ return NULL;
+
+ _SerCxSys.set_baud_rate = pSerialSys->set_baud_rate;
+ _SerCxSys.get_baud_rate = pSerialSys->get_baud_rate;
+
+ _SerCxSys.get_properties = pSerialSys->get_properties;
+
+ _SerCxSys.set_serial_chars = pSerialSys->set_serial_chars;
+ _SerCxSys.get_serial_chars = pSerialSys->get_serial_chars;
+ _SerCxSys.set_line_control = pSerialSys->set_line_control;
+ _SerCxSys.get_line_control = pSerialSys->get_line_control;
+
+ _SerCxSys.set_timeouts = pSerialSys->set_timeouts;
+ _SerCxSys.get_timeouts = pSerialSys->get_timeouts;
+
+ _SerCxSys.set_dtr = pSerialSys->set_dtr;
+ _SerCxSys.clear_dtr = pSerialSys->clear_dtr;
+
+ _SerCxSys.set_rts = pSerialSys->set_rts;
+ _SerCxSys.clear_rts = pSerialSys->clear_rts;
+
+ _SerCxSys.get_modemstatus = pSerialSys->get_modemstatus;
+
+ _SerCxSys.set_wait_mask = pSerialSys->set_wait_mask;
+ _SerCxSys.get_wait_mask = pSerialSys->get_wait_mask;
+ _SerCxSys.wait_on_mask = pSerialSys->wait_on_mask;
+
+ _SerCxSys.set_queue_size = pSerialSys->set_queue_size;
+
+ _SerCxSys.purge = pSerialSys->purge;
+
+ _SerCxSys.get_commstatus = pSerialSys->get_commstatus;
+
+ _SerCxSys.set_break_on = pSerialSys->set_break_on;
+ _SerCxSys.set_break_off = pSerialSys->set_break_off;
+
+ _SerCxSys.set_xoff = pSerialSys->set_xoff;
+ _SerCxSys.set_xon = pSerialSys->set_xon;
+
+ _SerCxSys.get_dtrrts = pSerialSys->get_dtrrts;
+
+ _SerCxSys.immediate_char = pSerialSys->immediate_char;
+
+ return &_SerCxSys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_sercx_sys.h b/winpr/libwinpr/comm/comm_sercx_sys.h
new file mode 100644
index 0000000..2268c0c
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_sercx_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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 COMM_SERCX_SYS_H
+#define COMM_SERCX_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerCxSys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERCX_SYS_H */
diff --git a/winpr/libwinpr/comm/comm_serial_sys.c b/winpr/libwinpr/comm/comm_serial_sys.c
new file mode 100644
index 0000000..cae653c
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_serial_sys.c
@@ -0,0 +1,1637 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2011 O.S. Systems Software Ltda.
+ * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br>
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#if defined __linux__ && !defined ANDROID
+
+#include <winpr/assert.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "comm_serial_sys.h"
+#ifdef __UCLIBC__
+#include "comm.h"
+#endif
+
+#include <winpr/crt.h>
+#include <winpr/wlog.h>
+
+/* Undocumented flag, not supported everywhere.
+ * Provide a sensible fallback to avoid compilation problems. */
+#ifndef CMSPAR
+#define CMSPAR 010000000000
+#endif
+
+/* hard-coded in N_TTY */
+#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */
+#define TTY_THRESHOLD_UNTHROTTLE 128
+#define N_TTY_BUF_SIZE 4096
+
+#define BAUD_TABLE_END 0010020 /* __MAX_BAUD + 1 */
+
+/* 0: B* (Linux termios)
+ * 1: CBR_* or actual baud rate
+ * 2: BAUD_* (identical to SERIAL_BAUD_*)
+ */
+static const speed_t _BAUD_TABLE[][3] = {
+#ifdef B0
+ { B0, 0, 0 }, /* hang up */
+#endif
+#ifdef B50
+ { B50, 50, 0 },
+#endif
+#ifdef B75
+ { B75, 75, BAUD_075 },
+#endif
+#ifdef B110
+ { B110, CBR_110, BAUD_110 },
+#endif
+#ifdef B134
+ { B134, 134, 0 /*BAUD_134_5*/ },
+#endif
+#ifdef B150
+ { B150, 150, BAUD_150 },
+#endif
+#ifdef B200
+ { B200, 200, 0 },
+#endif
+#ifdef B300
+ { B300, CBR_300, BAUD_300 },
+#endif
+#ifdef B600
+ { B600, CBR_600, BAUD_600 },
+#endif
+#ifdef B1200
+ { B1200, CBR_1200, BAUD_1200 },
+#endif
+#ifdef B1800
+ { B1800, 1800, BAUD_1800 },
+#endif
+#ifdef B2400
+ { B2400, CBR_2400, BAUD_2400 },
+#endif
+#ifdef B4800
+ { B4800, CBR_4800, BAUD_4800 },
+#endif
+/* {, ,BAUD_7200} */
+#ifdef B9600
+ { B9600, CBR_9600, BAUD_9600 },
+#endif
+/* {, CBR_14400, BAUD_14400}, /\* unsupported on Linux *\/ */
+#ifdef B19200
+ { B19200, CBR_19200, BAUD_19200 },
+#endif
+#ifdef B38400
+ { B38400, CBR_38400, BAUD_38400 },
+#endif
+/* {, CBR_56000, BAUD_56K}, /\* unsupported on Linux *\/ */
+#ifdef B57600
+ { B57600, CBR_57600, BAUD_57600 },
+#endif
+#ifdef B115200
+ { B115200, CBR_115200, BAUD_115200 },
+#endif
+/* {, CBR_128000, BAUD_128K}, /\* unsupported on Linux *\/ */
+/* {, CBR_256000, BAUD_USER}, /\* unsupported on Linux *\/ */
+#ifdef B230400
+ { B230400, 230400, BAUD_USER },
+#endif
+#ifdef B460800
+ { B460800, 460800, BAUD_USER },
+#endif
+#ifdef B500000
+ { B500000, 500000, BAUD_USER },
+#endif
+#ifdef B576000
+ { B576000, 576000, BAUD_USER },
+#endif
+#ifdef B921600
+ { B921600, 921600, BAUD_USER },
+#endif
+#ifdef B1000000
+ { B1000000, 1000000, BAUD_USER },
+#endif
+#ifdef B1152000
+ { B1152000, 1152000, BAUD_USER },
+#endif
+#ifdef B1500000
+ { B1500000, 1500000, BAUD_USER },
+#endif
+#ifdef B2000000
+ { B2000000, 2000000, BAUD_USER },
+#endif
+#ifdef B2500000
+ { B2500000, 2500000, BAUD_USER },
+#endif
+#ifdef B3000000
+ { B3000000, 3000000, BAUD_USER },
+#endif
+#ifdef B3500000
+ { B3500000, 3500000, BAUD_USER },
+#endif
+#ifdef B4000000
+ { B4000000, 4000000, BAUD_USER }, /* __MAX_BAUD */
+#endif
+ { BAUD_TABLE_END, 0, 0 }
+};
+
+static BOOL _get_properties(WINPR_COMM* pComm, COMMPROP* pProperties)
+{
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/jj680684%28v=vs.85%29.aspx
+ * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363189%28v=vs.85%29.aspx
+ */
+
+ /* FIXME: properties should be better probed. The current
+ * implementation just relies on the Linux' implementation.
+ */
+ WINPR_ASSERT(pProperties);
+ if (pProperties->dwProvSpec1 != COMMPROP_INITIALIZED)
+ {
+ ZeroMemory(pProperties, sizeof(COMMPROP));
+ pProperties->wPacketLength = sizeof(COMMPROP);
+ }
+
+ pProperties->wPacketVersion = 2;
+
+ pProperties->dwServiceMask = SERIAL_SP_SERIALCOMM;
+
+ /* pProperties->Reserved1; not used */
+
+ /* FIXME: could be implemented on top of N_TTY */
+ pProperties->dwMaxTxQueue = N_TTY_BUF_SIZE;
+ pProperties->dwMaxRxQueue = N_TTY_BUF_SIZE;
+
+ /* FIXME: to be probe on the device? */
+ pProperties->dwMaxBaud = BAUD_USER;
+
+ /* FIXME: what about PST_RS232? see also: serial_struct */
+ pProperties->dwProvSubType = PST_UNSPECIFIED;
+
+ /* TODO: to be finalized */
+ pProperties->dwProvCapabilities =
+ /*PCF_16BITMODE |*/ PCF_DTRDSR | PCF_INTTIMEOUTS | PCF_PARITY_CHECK | /*PCF_RLSD |*/
+ PCF_RTSCTS | PCF_SETXCHAR | /*PCF_SPECIALCHARS |*/ PCF_TOTALTIMEOUTS | PCF_XONXOFF;
+
+ /* TODO: double check SP_RLSD */
+ pProperties->dwSettableParams = SP_BAUD | SP_DATABITS | SP_HANDSHAKING | SP_PARITY |
+ SP_PARITY_CHECK | /*SP_RLSD |*/ SP_STOPBITS;
+
+ pProperties->dwSettableBaud = 0;
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ pProperties->dwSettableBaud |= _BAUD_TABLE[i][2];
+ }
+
+ pProperties->wSettableData =
+ DATABITS_5 | DATABITS_6 | DATABITS_7 | DATABITS_8 /*| DATABITS_16 | DATABITS_16X*/;
+
+ pProperties->wSettableStopParity = STOPBITS_10 | /*STOPBITS_15 |*/ STOPBITS_20 | PARITY_NONE |
+ PARITY_ODD | PARITY_EVEN | PARITY_MARK | PARITY_SPACE;
+
+ /* FIXME: additional input and output buffers could be implemented on top of N_TTY */
+ pProperties->dwCurrentTxQueue = N_TTY_BUF_SIZE;
+ pProperties->dwCurrentRxQueue = N_TTY_BUF_SIZE;
+
+ /* pProperties->ProvSpec1; see above */
+ /* pProperties->ProvSpec2; ignored */
+ /* pProperties->ProvChar[1]; ignored */
+
+ return TRUE;
+}
+
+static BOOL _set_baud_rate(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate)
+{
+ speed_t newSpeed = 0;
+ struct termios futureState;
+
+ ZeroMemory(&futureState, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &futureState) <
+ 0) /* NB: preserves current settings not directly handled by the Communication Functions */
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ if (_BAUD_TABLE[i][1] == pBaudRate->BaudRate)
+ {
+ newSpeed = _BAUD_TABLE[i][0];
+ if (cfsetspeed(&futureState, newSpeed) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "failed to set speed 0x%x (%" PRIu32 ")", newSpeed,
+ pBaudRate->BaudRate);
+ return FALSE;
+ }
+
+ WINPR_ASSERT(cfgetispeed(&futureState) == newSpeed);
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+ }
+ }
+
+ CommLog_Print(WLOG_WARN, "could not find a matching speed for the baud rate %" PRIu32 "",
+ pBaudRate->BaudRate);
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+}
+
+static BOOL _get_baud_rate(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate)
+{
+ speed_t currentSpeed = 0;
+ struct termios currentState;
+
+ ZeroMemory(&currentState, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentState) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ currentSpeed = cfgetispeed(&currentState);
+
+ for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++)
+ {
+ if (_BAUD_TABLE[i][0] == currentSpeed)
+ {
+ pBaudRate->BaudRate = _BAUD_TABLE[i][1];
+ return TRUE;
+ }
+ }
+
+ CommLog_Print(WLOG_WARN, "could not find a matching baud rate for the speed 0x%x",
+ currentSpeed);
+ SetLastError(ERROR_INVALID_DATA);
+ return FALSE;
+}
+
+/**
+ * NOTE: Only XonChar and XoffChar are plenty supported with the Linux
+ * N_TTY line discipline.
+ *
+ * ERRORS:
+ * ERROR_IO_DEVICE
+ * ERROR_INVALID_PARAMETER when Xon and Xoff chars are the same;
+ * ERROR_NOT_SUPPORTED
+ */
+static BOOL _set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ if (pSerialChars->XonChar == pSerialChars->XoffChar)
+ {
+ /* https://msdn.microsoft.com/en-us/library/windows/hardware/ff546688?v=vs.85.aspx */
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ /* termios(3): (..) above symbolic subscript values are all
+ * different, except that VTIME, VMIN may have the same value
+ * as VEOL, VEOF, respectively. In noncanonical mode the
+ * special character meaning is replaced by the timeout
+ * meaning.
+ *
+ * EofChar and c_cc[VEOF] are not quite the same, prefer to
+ * don't use c_cc[VEOF] at all.
+ *
+ * FIXME: might be implemented during read/write I/O
+ */
+ if (pSerialChars->EofChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "EofChar %02" PRIX8 " cannot be set\n", pSerialChars->EofChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* According the Linux's n_tty discipline, charaters with a
+ * parity error can only be let unchanged, replaced by \0 or
+ * get the prefix the prefix \377 \0
+ */
+
+ /* FIXME: see also: _set_handflow() */
+ if (pSerialChars->ErrorChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "ErrorChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
+ pSerialChars->ErrorChar, (char)pSerialChars->ErrorChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* FIXME: see also: _set_handflow() */
+ if (pSerialChars->BreakChar != '\0')
+ {
+ CommLog_Print(WLOG_WARN, "BreakChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n",
+ pSerialChars->BreakChar, (char)pSerialChars->BreakChar);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pSerialChars->EventChar != '\0')
+ {
+ pComm->eventChar = pSerialChars->EventChar;
+ }
+
+ upcomingTermios.c_cc[VSTART] = pSerialChars->XonChar;
+
+ upcomingTermios.c_cc[VSTOP] = pSerialChars->XoffChar;
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS));
+
+ /* EofChar unsupported */
+
+ /* ErrorChar unsupported */
+
+ /* BreakChar unsupported */
+
+ /* FIXME: see also: _set_serial_chars() */
+ /* EventChar */
+
+ pSerialChars->XonChar = currentTermios.c_cc[VSTART];
+
+ pSerialChars->XoffChar = currentTermios.c_cc[VSTOP];
+
+ return TRUE;
+}
+
+static BOOL _set_line_control(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx
+ *
+ * The use of 5 data bits with 2 stop bits is an invalid
+ * combination, as is 6, 7, or 8 data bits with 1.5 stop bits.
+ *
+ * FIXME: prefered to let the underlying driver to deal with
+ * this issue. At least produce a warning message?
+ */
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* FIXME: use of a COMMPROP to validate new settings? */
+
+ switch (pLineControl->StopBits)
+ {
+ case STOP_BIT_1:
+ upcomingTermios.c_cflag &= ~CSTOPB;
+ break;
+
+ case STOP_BITS_1_5:
+ CommLog_Print(WLOG_WARN, "Unsupported one and a half stop bits.");
+ break;
+
+ case STOP_BITS_2:
+ upcomingTermios.c_cflag |= CSTOPB;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected number of stop bits: %" PRIu8 "\n",
+ pLineControl->StopBits);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ switch (pLineControl->Parity)
+ {
+ case NO_PARITY:
+ upcomingTermios.c_cflag &= ~(PARENB | PARODD | CMSPAR);
+ break;
+
+ case ODD_PARITY:
+ upcomingTermios.c_cflag &= ~CMSPAR;
+ upcomingTermios.c_cflag |= PARENB | PARODD;
+ break;
+
+ case EVEN_PARITY:
+ upcomingTermios.c_cflag &= ~(PARODD | CMSPAR);
+ upcomingTermios.c_cflag |= PARENB;
+ break;
+
+ case MARK_PARITY:
+ upcomingTermios.c_cflag |= PARENB | PARODD | CMSPAR;
+ break;
+
+ case SPACE_PARITY:
+ upcomingTermios.c_cflag &= ~PARODD;
+ upcomingTermios.c_cflag |= PARENB | CMSPAR;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected type of parity: %" PRIu8 "\n",
+ pLineControl->Parity);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ switch (pLineControl->WordLength)
+ {
+ case 5:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS5;
+ break;
+
+ case 6:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS6;
+ break;
+
+ case 7:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS7;
+ break;
+
+ case 8:
+ upcomingTermios.c_cflag &= ~CSIZE;
+ upcomingTermios.c_cflag |= CS8;
+ break;
+
+ default:
+ CommLog_Print(WLOG_WARN, "unexpected number od data bits per character: %" PRIu8 "\n",
+ pLineControl->WordLength);
+ result = FALSE; /* but keep on */
+ break;
+ }
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_line_control(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ pLineControl->StopBits = (currentTermios.c_cflag & CSTOPB) ? STOP_BITS_2 : STOP_BIT_1;
+
+ if (!(currentTermios.c_cflag & PARENB))
+ {
+ pLineControl->Parity = NO_PARITY;
+ }
+ else if (currentTermios.c_cflag & CMSPAR)
+ {
+ pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? MARK_PARITY : SPACE_PARITY;
+ }
+ else
+ {
+ /* PARENB is set */
+ pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? ODD_PARITY : EVEN_PARITY;
+ }
+
+ switch (currentTermios.c_cflag & CSIZE)
+ {
+ case CS5:
+ pLineControl->WordLength = 5;
+ break;
+ case CS6:
+ pLineControl->WordLength = 6;
+ break;
+ case CS7:
+ pLineControl->WordLength = 7;
+ break;
+ default:
+ pLineControl->WordLength = 8;
+ break;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow)
+{
+ BOOL result = TRUE;
+ struct termios upcomingTermios;
+
+ ZeroMemory(&upcomingTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &upcomingTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* HUPCL */
+
+ /* logical XOR */
+ if ((!(pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL)) ||
+ ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) &&
+ !(pHandflow->FlowReplace & SERIAL_RTS_CONTROL)))
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_DTR_CONTROL:%s and SERIAL_RTS_CONTROL:%s cannot be different, HUPCL "
+ "will be set since it is claimed for one of the both lines.",
+ (pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ? "ON" : "OFF",
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL) ? "ON" : "OFF");
+ }
+
+ if ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ||
+ (pHandflow->FlowReplace & SERIAL_RTS_CONTROL))
+ {
+ upcomingTermios.c_cflag |= HUPCL;
+ }
+ else
+ {
+ upcomingTermios.c_cflag &= ~HUPCL;
+
+ /* FIXME: is the DTR line also needs to be forced to a disable state according
+ * SERIAL_DTR_CONTROL? */
+ /* FIXME: is the RTS line also needs to be forced to a disable state according
+ * SERIAL_RTS_CONTROL? */
+ }
+
+ /* CRTSCTS */
+
+ /* logical XOR */
+ if ((!(pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)) ||
+ ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) &&
+ !(pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)))
+ {
+ CommLog_Print(WLOG_WARN,
+ "SERIAL_CTS_HANDSHAKE:%s and SERIAL_RTS_HANDSHAKE:%s cannot be different, "
+ "CRTSCTS will be set since it is claimed for one of the both lines.",
+ (pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ? "ON" : "OFF",
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE) ? "ON" : "OFF");
+ }
+
+ if ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ||
+ (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE))
+ {
+ upcomingTermios.c_cflag |= CRTSCTS;
+ }
+ else
+ {
+ upcomingTermios.c_cflag &= ~CRTSCTS;
+ }
+
+ /* ControlHandShake */
+
+ if (pHandflow->ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ /* DTR/DSR flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DTR_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DSR_HANDSHAKE)
+ {
+ /* DTR/DSR flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE)
+ {
+ /* DCD flow control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DCD_HANDSHAKE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY)
+ {
+ /* DSR line control not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_SENSITIVITY feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT)
+ {
+ /* Aborting operations on error not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_ERROR_ABORT feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* FlowReplace */
+
+ if (pHandflow->FlowReplace & SERIAL_AUTO_TRANSMIT)
+ {
+ upcomingTermios.c_iflag |= IXON;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IXON;
+ }
+
+ if (pHandflow->FlowReplace & SERIAL_AUTO_RECEIVE)
+ {
+ upcomingTermios.c_iflag |= IXOFF;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IXOFF;
+ }
+
+ // FIXME: could be implemented during read/write I/O, as of today ErrorChar is necessary '\0'
+ if (pHandflow->FlowReplace & SERIAL_ERROR_CHAR)
+ {
+ /* errors will be replaced by the character '\0'. */
+ upcomingTermios.c_iflag &= ~IGNPAR;
+ }
+ else
+ {
+ upcomingTermios.c_iflag |= IGNPAR;
+ }
+
+ if (pHandflow->FlowReplace & SERIAL_NULL_STRIPPING)
+ {
+ upcomingTermios.c_iflag |= IGNBRK;
+ }
+ else
+ {
+ upcomingTermios.c_iflag &= ~IGNBRK;
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->FlowReplace & SERIAL_BREAK_CHAR)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_BREAK_CHAR feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->FlowReplace & SERIAL_XOFF_CONTINUE)
+ {
+ /* not supported on Linux */
+ CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_XOFF_CONTINUE feature.");
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* XonLimit */
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->XonLimit != TTY_THRESHOLD_UNTHROTTLE)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to set XonLimit with an unsupported value: %" PRId32 "",
+ pHandflow->XonLimit);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ /* XoffChar */
+
+ // FIXME: could be implemented during read/write I/O
+ if (pHandflow->XoffLimit != TTY_THRESHOLD_THROTTLE)
+ {
+ CommLog_Print(WLOG_WARN, "Attempt to set XoffLimit with an unsupported value: %" PRId32 "",
+ pHandflow->XoffLimit);
+ SetLastError(ERROR_NOT_SUPPORTED);
+ result = FALSE; /* but keep on */
+ }
+
+ if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0)
+ {
+ CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "",
+ GetLastError());
+ return FALSE;
+ }
+
+ return result;
+}
+
+static BOOL _get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow)
+{
+ struct termios currentTermios;
+
+ ZeroMemory(&currentTermios, sizeof(struct termios));
+ if (tcgetattr(pComm->fd, &currentTermios) < 0)
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ /* ControlHandShake */
+
+ pHandflow->ControlHandShake = 0;
+
+ if (currentTermios.c_cflag & HUPCL)
+ pHandflow->ControlHandShake |= SERIAL_DTR_CONTROL;
+
+ /* SERIAL_DTR_HANDSHAKE unsupported */
+
+ if (currentTermios.c_cflag & CRTSCTS)
+ pHandflow->ControlHandShake |= SERIAL_CTS_HANDSHAKE;
+
+ /* SERIAL_DSR_HANDSHAKE unsupported */
+
+ /* SERIAL_DCD_HANDSHAKE unsupported */
+
+ /* SERIAL_DSR_SENSITIVITY unsupported */
+
+ /* SERIAL_ERROR_ABORT unsupported */
+
+ /* FlowReplace */
+
+ pHandflow->FlowReplace = 0;
+
+ if (currentTermios.c_iflag & IXON)
+ pHandflow->FlowReplace |= SERIAL_AUTO_TRANSMIT;
+
+ if (currentTermios.c_iflag & IXOFF)
+ pHandflow->FlowReplace |= SERIAL_AUTO_RECEIVE;
+
+ if (!(currentTermios.c_iflag & IGNPAR))
+ pHandflow->FlowReplace |= SERIAL_ERROR_CHAR;
+
+ if (currentTermios.c_iflag & IGNBRK)
+ pHandflow->FlowReplace |= SERIAL_NULL_STRIPPING;
+
+ /* SERIAL_BREAK_CHAR unsupported */
+
+ if (currentTermios.c_cflag & HUPCL)
+ pHandflow->FlowReplace |= SERIAL_RTS_CONTROL;
+
+ if (currentTermios.c_cflag & CRTSCTS)
+ pHandflow->FlowReplace |= SERIAL_RTS_HANDSHAKE;
+
+ /* SERIAL_XOFF_CONTINUE unsupported */
+
+ /* XonLimit */
+
+ pHandflow->XonLimit = TTY_THRESHOLD_UNTHROTTLE;
+
+ /* XoffLimit */
+
+ pHandflow->XoffLimit = TTY_THRESHOLD_THROTTLE;
+
+ return TRUE;
+}
+
+static BOOL _set_timeouts(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts)
+{
+ /* NB: timeouts are applied on system during read/write I/O */
+
+ /* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx */
+ if ((pTimeouts->ReadIntervalTimeout == MAXULONG) &&
+ (pTimeouts->ReadTotalTimeoutConstant == MAXULONG))
+ {
+ CommLog_Print(
+ WLOG_WARN,
+ "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG");
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ pComm->timeouts.ReadIntervalTimeout = pTimeouts->ReadIntervalTimeout;
+ pComm->timeouts.ReadTotalTimeoutMultiplier = pTimeouts->ReadTotalTimeoutMultiplier;
+ pComm->timeouts.ReadTotalTimeoutConstant = pTimeouts->ReadTotalTimeoutConstant;
+ pComm->timeouts.WriteTotalTimeoutMultiplier = pTimeouts->WriteTotalTimeoutMultiplier;
+ pComm->timeouts.WriteTotalTimeoutConstant = pTimeouts->WriteTotalTimeoutConstant;
+
+ CommLog_Print(WLOG_DEBUG, "ReadIntervalTimeout %" PRIu32 "",
+ pComm->timeouts.ReadIntervalTimeout);
+ CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutMultiplier %" PRIu32 "",
+ pComm->timeouts.ReadTotalTimeoutMultiplier);
+ CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutConstant %" PRIu32 "",
+ pComm->timeouts.ReadTotalTimeoutConstant);
+ CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutMultiplier %" PRIu32 "",
+ pComm->timeouts.WriteTotalTimeoutMultiplier);
+ CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutConstant %" PRIu32 "",
+ pComm->timeouts.WriteTotalTimeoutConstant);
+
+ return TRUE;
+}
+
+static BOOL _get_timeouts(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts)
+{
+ pTimeouts->ReadIntervalTimeout = pComm->timeouts.ReadIntervalTimeout;
+ pTimeouts->ReadTotalTimeoutMultiplier = pComm->timeouts.ReadTotalTimeoutMultiplier;
+ pTimeouts->ReadTotalTimeoutConstant = pComm->timeouts.ReadTotalTimeoutConstant;
+ pTimeouts->WriteTotalTimeoutMultiplier = pComm->timeouts.WriteTotalTimeoutMultiplier;
+ pTimeouts->WriteTotalTimeoutConstant = pComm->timeouts.WriteTotalTimeoutConstant;
+
+ return TRUE;
+}
+
+static BOOL _set_lines(WINPR_COMM* pComm, UINT32 lines)
+{
+ if (ioctl(pComm->fd, TIOCMBIS, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMBIS ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines,
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _clear_lines(WINPR_COMM* pComm, UINT32 lines)
+{
+ if (ioctl(pComm->fd, TIOCMBIC, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMBIC ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines,
+ errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_dtr(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ /* SERIAL_DTR_HANDSHAKE not supported as of today */
+ WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _set_lines(pComm, TIOCM_DTR);
+}
+
+static BOOL _clear_dtr(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ /* SERIAL_DTR_HANDSHAKE not supported as of today */
+ WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0);
+
+ if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _clear_lines(pComm, TIOCM_DTR);
+}
+
+static BOOL _set_rts(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _set_lines(pComm, TIOCM_RTS);
+}
+
+static BOOL _clear_rts(WINPR_COMM* pComm)
+{
+ SERIAL_HANDFLOW handflow;
+ if (!_get_handflow(pComm, &handflow))
+ return FALSE;
+
+ if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE)
+ {
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ return _clear_lines(pComm, TIOCM_RTS);
+}
+
+static BOOL _get_modemstatus(WINPR_COMM* pComm, ULONG* pRegister)
+{
+ UINT32 lines = 0;
+ if (ioctl(pComm->fd, TIOCMGET, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ ZeroMemory(pRegister, sizeof(ULONG));
+
+ /* FIXME: Is the last read of the MSR register available or
+ * cached somewhere? Not quite sure we need to return the 4
+ * LSBits anyway. A direct access to the register -- which
+ * would reset the register -- is likely not expected from
+ * this function.
+ */
+
+ /* #define SERIAL_MSR_DCTS 0x01 */
+ /* #define SERIAL_MSR_DDSR 0x02 */
+ /* #define SERIAL_MSR_TERI 0x04 */
+ /* #define SERIAL_MSR_DDCD 0x08 */
+
+ if (lines & TIOCM_CTS)
+ *pRegister |= SERIAL_MSR_CTS;
+ if (lines & TIOCM_DSR)
+ *pRegister |= SERIAL_MSR_DSR;
+ if (lines & TIOCM_RI)
+ *pRegister |= SERIAL_MSR_RI;
+ if (lines & TIOCM_CD)
+ *pRegister |= SERIAL_MSR_DCD;
+
+ return TRUE;
+}
+
+/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */
+static const ULONG _SERIAL_SYS_SUPPORTED_EV_MASK =
+ SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR |
+ SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING |
+ /* SERIAL_EV_PERR | */
+ SERIAL_EV_RX80FULL /*|
+ SERIAL_EV_EVENT1 |
+ SERIAL_EV_EVENT2*/
+ ;
+
+static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask)
+{
+ ULONG possibleMask = 0;
+
+ /* Stops pending IOCTL_SERIAL_WAIT_ON_MASK
+ * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
+ */
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ {
+ /* FIXME: any doubt on reading PendingEvents out of a critical section? */
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents |= SERIAL_EV_WINPR_STOP;
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ /* waiting the end of the pending _wait_on_mask() */
+ while (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING)
+ Sleep(10); /* 10ms */
+ }
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ if (*pWaitMask == 0)
+ {
+ /* clearing pending events */
+
+ if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+
+ if (pComm->permissive)
+ {
+ /* counters could not be reset but keep on */
+ ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct));
+ }
+ else
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+ }
+
+ pComm->PendingEvents = 0;
+ }
+
+ possibleMask = *pWaitMask & _SERIAL_SYS_SUPPORTED_EV_MASK;
+
+ if (possibleMask != *pWaitMask)
+ {
+ CommLog_Print(WLOG_WARN,
+ "Not all wait events supported (Serial.sys), requested events= 0x%08" PRIX32
+ ", possible events= 0x%08" PRIX32 "",
+ *pWaitMask, possibleMask);
+
+ /* FIXME: shall we really set the possibleMask and return FALSE? */
+ pComm->WaitEventMask = possibleMask;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ pComm->WaitEventMask = possibleMask;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+}
+
+static BOOL _get_wait_mask(WINPR_COMM* pComm, ULONG* pWaitMask)
+{
+ *pWaitMask = pComm->WaitEventMask;
+ return TRUE;
+}
+
+static BOOL _set_queue_size(WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize)
+{
+ if ((pQueueSize->InSize <= N_TTY_BUF_SIZE) && (pQueueSize->OutSize <= N_TTY_BUF_SIZE))
+ return TRUE; /* nothing to do */
+
+ /* FIXME: could be implemented on top of N_TTY */
+
+ if (pQueueSize->InSize > N_TTY_BUF_SIZE)
+ CommLog_Print(WLOG_WARN,
+ "Requested an incompatible input buffer size: %" PRIu32
+ ", keeping on with a %" PRIu32 " bytes buffer.",
+ pQueueSize->InSize, N_TTY_BUF_SIZE);
+
+ if (pQueueSize->OutSize > N_TTY_BUF_SIZE)
+ CommLog_Print(WLOG_WARN,
+ "Requested an incompatible output buffer size: %" PRIu32
+ ", keeping on with a %" PRIu32 " bytes buffer.",
+ pQueueSize->OutSize, N_TTY_BUF_SIZE);
+
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+}
+
+static BOOL _purge(WINPR_COMM* pComm, const ULONG* pPurgeMask)
+{
+ if ((*pPurgeMask & ~(SERIAL_PURGE_TXABORT | SERIAL_PURGE_RXABORT | SERIAL_PURGE_TXCLEAR |
+ SERIAL_PURGE_RXCLEAR)) > 0)
+ {
+ CommLog_Print(WLOG_WARN, "Invalid purge mask: 0x%" PRIX32 "\n", *pPurgeMask);
+ SetLastError(ERROR_INVALID_PARAMETER);
+ return FALSE;
+ }
+
+ /* FIXME: currently relying too much on the fact the server
+ * sends a single IRP_MJ_WRITE or IRP_MJ_READ at a time
+ * (taking care though that one IRP_MJ_WRITE and one
+ * IRP_MJ_READ can be sent simultaneously) */
+
+ if (*pPurgeMask & SERIAL_PURGE_TXABORT)
+ {
+ /* Purges all write (IRP_MJ_WRITE) requests. */
+
+ if (eventfd_write(pComm->fd_write_event, WINPR_PURGE_TXABORT) < 0)
+ {
+ if (errno != EAGAIN)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+
+ WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_WRITE */
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_RXABORT)
+ {
+ /* Purges all read (IRP_MJ_READ) requests. */
+
+ if (eventfd_write(pComm->fd_read_event, WINPR_PURGE_RXABORT) < 0)
+ {
+ if (errno != EAGAIN)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ }
+
+ WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_READ */
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_TXCLEAR)
+ {
+ /* Purges the transmit buffer, if one exists. */
+
+ if (tcflush(pComm->fd, TCOFLUSH) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "tcflush(TCOFLUSH) failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+ }
+ }
+
+ if (*pPurgeMask & SERIAL_PURGE_RXCLEAR)
+ {
+ /* Purges the receive buffer, if one exists. */
+
+ if (tcflush(pComm->fd, TCIFLUSH) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "tcflush(TCIFLUSH) failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_CANCELLED);
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/* NB: _get_commstatus also produces most of the events consumed by _wait_on_mask(). Exceptions:
+ * - SERIAL_EV_RXFLAG: FIXME: once EventChar supported
+ *
+ */
+static BOOL _get_commstatus(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus)
+{
+ /* http://msdn.microsoft.com/en-us/library/jj673022%28v=vs.85%29.aspx */
+
+ struct serial_icounter_struct currentCounters;
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ ZeroMemory(pCommstatus, sizeof(SERIAL_STATUS));
+
+ ZeroMemory(&currentCounters, sizeof(struct serial_icounter_struct));
+ if (ioctl(pComm->fd, TIOCGICOUNT, &currentCounters) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ CommLog_Print(WLOG_WARN, " could not read counters.");
+
+ if (pComm->permissive)
+ {
+ /* Errors and events based on counters could not be
+ * detected but keep on.
+ */
+ ZeroMemory(&currentCounters, sizeof(struct serial_icounter_struct));
+ }
+ else
+ {
+ SetLastError(ERROR_IO_DEVICE);
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+ }
+
+ /* NB: preferred below (currentCounters.* != pComm->counters.*) over (currentCounters.* >
+ * pComm->counters.*) thinking the counters can loop */
+
+ /* Errors */
+
+ if (currentCounters.buf_overrun != pComm->counters.buf_overrun)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_QUEUEOVERRUN;
+ }
+
+ if (currentCounters.overrun != pComm->counters.overrun)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_OVERRUN;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ if (currentCounters.brk != pComm->counters.brk)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_BREAK;
+ pComm->PendingEvents |= SERIAL_EV_BREAK;
+ }
+
+ if (currentCounters.parity != pComm->counters.parity)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_PARITY;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ if (currentCounters.frame != pComm->counters.frame)
+ {
+ pCommstatus->Errors |= SERIAL_ERROR_FRAMING;
+ pComm->PendingEvents |= SERIAL_EV_ERR;
+ }
+
+ /* HoldReasons */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_CTS */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_DSR */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_DCD */
+
+ /* TODO: SERIAL_TX_WAITING_FOR_XON */
+
+ /* TODO: SERIAL_TX_WAITING_ON_BREAK, see LCR's bit 6 */
+
+ /* TODO: SERIAL_TX_WAITING_XOFF_SENT */
+
+ /* AmountInInQueue */
+
+ if (ioctl(pComm->fd, TIOCINQ, &(pCommstatus->AmountInInQueue)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCINQ ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* AmountInOutQueue */
+
+ if (ioctl(pComm->fd, TIOCOUTQ, &(pCommstatus->AmountInOutQueue)) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCOUTQ ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* BOOLEAN EofReceived; FIXME: once EofChar supported */
+
+ /* BOOLEAN WaitForImmediate; TODO: once IOCTL_SERIAL_IMMEDIATE_CHAR fully supported */
+
+ /* other events based on counters */
+
+ if (currentCounters.rx != pComm->counters.rx)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR;
+ }
+
+ if ((currentCounters.tx != pComm->counters.tx) && /* at least a transmission occurred AND ...*/
+ (pCommstatus->AmountInOutQueue == 0)) /* output bufer is now empty */
+ {
+ pComm->PendingEvents |= SERIAL_EV_TXEMPTY;
+ }
+ else
+ {
+ /* FIXME: "now empty" from the specs is ambiguous, need to track previous completed
+ * transmission? */
+ pComm->PendingEvents &= ~SERIAL_EV_TXEMPTY;
+ }
+
+ if (currentCounters.cts != pComm->counters.cts)
+ {
+ pComm->PendingEvents |= SERIAL_EV_CTS;
+ }
+
+ if (currentCounters.dsr != pComm->counters.dsr)
+ {
+ pComm->PendingEvents |= SERIAL_EV_DSR;
+ }
+
+ if (currentCounters.dcd != pComm->counters.dcd)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RLSD;
+ }
+
+ if (currentCounters.rng != pComm->counters.rng)
+ {
+ pComm->PendingEvents |= SERIAL_EV_RING;
+ }
+
+ if (pCommstatus->AmountInInQueue > (0.8 * N_TTY_BUF_SIZE))
+ {
+ pComm->PendingEvents |= SERIAL_EV_RX80FULL;
+ }
+ else
+ {
+ /* FIXME: "is 80 percent full" from the specs is ambiguous, need to track when it previously
+ * * occurred? */
+ pComm->PendingEvents &= ~SERIAL_EV_RX80FULL;
+ }
+
+ pComm->counters = currentCounters;
+
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+}
+
+static BOOL _refresh_PendingEvents(WINPR_COMM* pComm)
+{
+ SERIAL_STATUS serialStatus;
+
+ /* NB: also ensures PendingEvents to be up to date */
+ ZeroMemory(&serialStatus, sizeof(SERIAL_STATUS));
+ if (!_get_commstatus(pComm, &serialStatus))
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void _consume_event(WINPR_COMM* pComm, ULONG* pOutputMask, ULONG event)
+{
+ if ((pComm->WaitEventMask & event) && (pComm->PendingEvents & event))
+ {
+ pComm->PendingEvents &= ~event; /* consumed */
+ *pOutputMask |= event;
+ }
+}
+
+/*
+ * NB: see also: _set_wait_mask()
+ */
+static BOOL _wait_on_mask(WINPR_COMM* pComm, ULONG* pOutputMask)
+{
+ WINPR_ASSERT(*pOutputMask == 0);
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents |= SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ while (TRUE)
+ {
+ /* NB: EventsLock also used by _refresh_PendingEvents() */
+ if (!_refresh_PendingEvents(pComm))
+ {
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return FALSE;
+ }
+
+ /* NB: ensure to leave the critical section before to return */
+ EnterCriticalSection(&pComm->EventsLock);
+
+ if (pComm->PendingEvents & SERIAL_EV_WINPR_STOP)
+ {
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_STOP;
+
+ /* pOutputMask must remain empty but should
+ * not have been modified.
+ *
+ * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx
+ */
+ WINPR_ASSERT(*pOutputMask == 0);
+
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+ }
+
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RXCHAR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RXFLAG);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_TXEMPTY);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_CTS);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_DSR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RLSD);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_BREAK);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_ERR);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RING);
+ _consume_event(pComm, pOutputMask, SERIAL_EV_RX80FULL);
+
+ LeaveCriticalSection(&pComm->EventsLock);
+
+ /* NOTE: PendingEvents can be modified from now on but
+ * not pOutputMask */
+
+ if (*pOutputMask != 0)
+ {
+ /* at least an event occurred */
+
+ EnterCriticalSection(&pComm->EventsLock);
+ pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING;
+ LeaveCriticalSection(&pComm->EventsLock);
+ return TRUE;
+ }
+
+ /* waiting for a modification of PendingEvents.
+ *
+ * NOTE: previously used a semaphore but used
+ * sem_timedwait() anyway. Finally preferred a simpler
+ * solution with Sleep() without the burden of the
+ * semaphore initialization and destroying.
+ */
+
+ Sleep(100); /* 100 ms */
+ }
+}
+
+static BOOL _set_break_on(WINPR_COMM* pComm)
+{
+ if (ioctl(pComm->fd, TIOCSBRK, NULL) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_break_off(WINPR_COMM* pComm)
+{
+ if (ioctl(pComm->fd, TIOCCBRK, NULL) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_xoff(WINPR_COMM* pComm)
+{
+ if (tcflow(pComm->fd, TCIOFF) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TCIOFF failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _set_xon(WINPR_COMM* pComm)
+{
+ if (tcflow(pComm->fd, TCION) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TCION failure, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL _get_dtrrts(WINPR_COMM* pComm, ULONG* pMask)
+{
+ UINT32 lines = 0;
+ if (ioctl(pComm->fd, TIOCMGET, &lines) < 0)
+ {
+ char ebuffer[256] = { 0 };
+ CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno,
+ winpr_strerror(errno, ebuffer, sizeof(ebuffer)));
+ SetLastError(ERROR_IO_DEVICE);
+ return FALSE;
+ }
+
+ *pMask = 0;
+
+ if (!(lines & TIOCM_DTR))
+ *pMask |= SERIAL_DTR_STATE;
+ if (!(lines & TIOCM_RTS))
+ *pMask |= SERIAL_RTS_STATE;
+
+ return TRUE;
+}
+
+static BOOL _config_size(WINPR_COMM* pComm, ULONG* pSize)
+{
+ /* http://msdn.microsoft.com/en-us/library/ff546548%28v=vs.85%29.aspx */
+ if (!pSize)
+ return FALSE;
+
+ *pSize = 0;
+ return TRUE;
+}
+
+static BOOL _immediate_char(WINPR_COMM* pComm, const UCHAR* pChar)
+{
+ BOOL result = 0;
+ DWORD nbBytesWritten = -1;
+
+ /* FIXME: CommWriteFile uses a critical section, shall it be
+ * interrupted?
+ *
+ * FIXME: see also _get_commstatus()'s WaitForImmediate boolean
+ */
+
+ result = CommWriteFile(pComm, pChar, 1, &nbBytesWritten, NULL);
+
+ WINPR_ASSERT(nbBytesWritten == 1);
+
+ return result;
+}
+
+static BOOL _reset_device(WINPR_COMM* pComm)
+{
+ /* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx */
+ return TRUE;
+}
+
+static SERIAL_DRIVER _SerialSys = {
+ .id = SerialDriverSerialSys,
+ .name = _T("Serial.sys"),
+ .set_baud_rate = _set_baud_rate,
+ .get_baud_rate = _get_baud_rate,
+ .get_properties = _get_properties,
+ .set_serial_chars = _set_serial_chars,
+ .get_serial_chars = _get_serial_chars,
+ .set_line_control = _set_line_control,
+ .get_line_control = _get_line_control,
+ .set_handflow = _set_handflow,
+ .get_handflow = _get_handflow,
+ .set_timeouts = _set_timeouts,
+ .get_timeouts = _get_timeouts,
+ .set_dtr = _set_dtr,
+ .clear_dtr = _clear_dtr,
+ .set_rts = _set_rts,
+ .clear_rts = _clear_rts,
+ .get_modemstatus = _get_modemstatus,
+ .set_wait_mask = _set_wait_mask,
+ .get_wait_mask = _get_wait_mask,
+ .wait_on_mask = _wait_on_mask,
+ .set_queue_size = _set_queue_size,
+ .purge = _purge,
+ .get_commstatus = _get_commstatus,
+ .set_break_on = _set_break_on,
+ .set_break_off = _set_break_off,
+ .set_xoff = _set_xoff,
+ .set_xon = _set_xon,
+ .get_dtrrts = _get_dtrrts,
+ .config_size = _config_size,
+ .immediate_char = _immediate_char,
+ .reset_device = _reset_device,
+};
+
+SERIAL_DRIVER* SerialSys_s(void)
+{
+ return &_SerialSys;
+}
+
+#endif /* __linux__ */
diff --git a/winpr/libwinpr/comm/comm_serial_sys.h b/winpr/libwinpr/comm/comm_serial_sys.h
new file mode 100644
index 0000000..abb3f01
--- /dev/null
+++ b/winpr/libwinpr/comm/comm_serial_sys.h
@@ -0,0 +1,40 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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 COMM_SERIAL_SYS_H
+#define COMM_SERIAL_SYS_H
+
+#if defined __linux__ && !defined ANDROID
+
+#include "comm_ioctl.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ SERIAL_DRIVER* SerialSys_s(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __linux__ */
+
+#endif /* COMM_SERIAL_SYS_H */
diff --git a/winpr/libwinpr/comm/test/CMakeLists.txt b/winpr/libwinpr/comm/test/CMakeLists.txt
new file mode 100644
index 0000000..f5ae406
--- /dev/null
+++ b/winpr/libwinpr/comm/test/CMakeLists.txt
@@ -0,0 +1,35 @@
+
+set(MODULE_NAME "TestComm")
+set(MODULE_PREFIX "TEST_COMM")
+
+set(${MODULE_PREFIX}_DRIVER ${MODULE_NAME}.c)
+
+set(${MODULE_PREFIX}_TESTS
+ TestCommDevice.c
+ TestCommConfig.c
+ TestGetCommState.c
+ TestSetCommState.c
+ TestSerialChars.c
+ TestControlSettings.c
+ TestHandflow.c
+ TestTimeouts.c
+ TestCommMonitor.c)
+
+create_test_sourcelist(${MODULE_PREFIX}_SRCS
+ ${${MODULE_PREFIX}_DRIVER}
+ ${${MODULE_PREFIX}_TESTS})
+
+add_executable(${MODULE_NAME} ${${MODULE_PREFIX}_SRCS})
+
+target_link_libraries(${MODULE_NAME} winpr)
+
+set_target_properties(${MODULE_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${TESTING_OUTPUT_DIRECTORY}")
+
+foreach(test ${${MODULE_PREFIX}_TESTS})
+ get_filename_component(TestName ${test} NAME_WE)
+ add_test(${TestName} ${TESTING_OUTPUT_DIRECTORY}/${MODULE_NAME} ${TestName})
+ set_tests_properties(${TestName} PROPERTIES LABELS "comm" )
+endforeach()
+
+set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR/Test")
+
diff --git a/winpr/libwinpr/comm/test/TestCommConfig.c b/winpr/libwinpr/comm/test/TestCommConfig.c
new file mode 100644
index 0000000..c405f14
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommConfig.c
@@ -0,0 +1,148 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com>
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <sys/stat.h>
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+int TestCommConfig(int argc, char* argv[])
+{
+ DCB dcb = { 0 };
+ HANDLE hComm;
+ BOOL success;
+ LPCSTR lpFileName = "\\\\.\\COM1";
+ COMMPROP commProp = { 0 };
+ struct stat statbuf = { 0 };
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hComm && (hComm != INVALID_HANDLE_VALUE))
+ {
+ fprintf(stderr,
+ "CreateFileA failure: could create a handle on a not yet defined device: %s\n",
+ lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ success = DefineCommDevice(lpFileName, "/dev/ttyS0");
+ if (!success)
+ {
+ fprintf(stderr, "DefineCommDevice failure: %s\n", lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_WRITE, /* invalid parmaeter */
+ NULL, CREATE_NEW, /* invalid parameter */
+ 0, (HANDLE)1234); /* invalid parmaeter */
+ if (hComm != INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr,
+ "CreateFileA failure: could create a handle with some invalid parameters %s\n",
+ lpFileName);
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (!hComm || (hComm == INVALID_HANDLE_VALUE))
+ {
+ fprintf(stderr, "CreateFileA failure: %s GetLastError() = 0x%08x\n", lpFileName,
+ GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ /* TODO: a second call to CreateFileA should failed and
+ * GetLastError should return ERROR_SHARING_VIOLATION */
+
+ dcb.DCBlength = sizeof(DCB);
+ success = GetCommState(hComm, &dcb);
+ if (!success)
+ {
+ fprintf(stderr, "GetCommState failure: GetLastError() = Ox%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ fprintf(stderr,
+ "BaudRate: %" PRIu32 " ByteSize: %" PRIu8 " Parity: %" PRIu8 " StopBits: %" PRIu8 "\n",
+ dcb.BaudRate, dcb.ByteSize, dcb.Parity, dcb.StopBits);
+
+ if (!GetCommProperties(hComm, &commProp))
+ {
+ fprintf(stderr, "GetCommProperties failure: GetLastError(): 0x%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if ((commProp.dwSettableBaud & BAUD_57600) <= 0)
+ {
+ fprintf(stderr, "BAUD_57600 unsupported!\n");
+ return EXIT_FAILURE;
+ }
+
+ if ((commProp.dwSettableBaud & BAUD_14400) > 0)
+ {
+ fprintf(stderr, "BAUD_14400 supported!\n");
+ return EXIT_FAILURE;
+ }
+
+ dcb.BaudRate = CBR_57600;
+ dcb.ByteSize = 8;
+ dcb.Parity = NOPARITY;
+ dcb.StopBits = ONESTOPBIT;
+
+ success = SetCommState(hComm, &dcb);
+
+ if (!success)
+ {
+ fprintf(stderr, "SetCommState failure: GetLastError() = 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ success = GetCommState(hComm, &dcb);
+
+ if (!success)
+ {
+ fprintf(stderr, "GetCommState failure: GetLastError() = 0x%x\n", GetLastError());
+ return 0;
+ }
+
+ if ((dcb.BaudRate != CBR_57600) || (dcb.ByteSize != 8) || (dcb.Parity != NOPARITY) ||
+ (dcb.StopBits != ONESTOPBIT))
+ {
+ fprintf(stderr,
+ "Got an unexpeted value among: BaudRate: %" PRIu32 " ByteSize: %" PRIu8
+ " Parity: %" PRIu8 " StopBits: %" PRIu8 "\n",
+ dcb.BaudRate, dcb.ByteSize, dcb.Parity, dcb.StopBits);
+ }
+
+ CloseHandle(hComm);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestCommDevice.c b/winpr/libwinpr/comm/test/TestCommDevice.c
new file mode 100644
index 0000000..09eb1c2
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommDevice.c
@@ -0,0 +1,115 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+
+#include <winpr/comm.h>
+#include <winpr/tchar.h>
+
+static int test_CommDevice(LPCTSTR lpDeviceName, BOOL expectedResult)
+{
+ BOOL result;
+ TCHAR lpTargetPath[MAX_PATH];
+ size_t tcslen;
+
+ result = DefineCommDevice(lpDeviceName, _T("/dev/test"));
+ if ((!expectedResult && result) || (expectedResult && !result)) /* logical XOR */
+ {
+ _tprintf(_T("DefineCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, (expectedResult ? "TRUE" : "FALSE"), (result ? "TRUE" : "FALSE"));
+
+ return FALSE;
+ }
+
+ result = IsCommDevice(lpDeviceName);
+ if ((!expectedResult && result) || (expectedResult && !result)) /* logical XOR */
+ {
+ _tprintf(_T("IsCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, (expectedResult ? "TRUE" : "FALSE"), (result ? "TRUE" : "FALSE"));
+
+ return FALSE;
+ }
+
+ tcslen = (size_t)QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH);
+ if (expectedResult)
+ {
+ if (tcslen <= _tcslen(lpTargetPath)) /* at least 2 more TCHAR are expected */
+ {
+ _tprintf(_T("QueryCommDevice failure: didn't find the device name: %s\n"),
+ lpDeviceName);
+ return FALSE;
+ }
+
+ if (_tcscmp(_T("/dev/test"), lpTargetPath) != 0)
+ {
+ _tprintf(
+ _T("QueryCommDevice failure: device name: %s, expected result: %s, result: %s\n"),
+ lpDeviceName, _T("/dev/test"), lpTargetPath);
+
+ return FALSE;
+ }
+
+ if (lpTargetPath[_tcslen(lpTargetPath) + 1] != 0)
+ {
+ _tprintf(_T("QueryCommDevice failure: device name: %s, the second NULL character is ")
+ _T("missing at the end of the buffer\n"),
+ lpDeviceName);
+ return FALSE;
+ }
+ }
+ else
+ {
+ if (tcslen > 0)
+ {
+ _tprintf(_T("QueryCommDevice failure: device name: %s, expected result: <none>, ")
+ _T("result: %") _T(PRIuz) _T(" %s\n"),
+ lpDeviceName, tcslen, lpTargetPath);
+
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+int TestCommDevice(int argc, char* argv[])
+{
+ if (!test_CommDevice(_T("COM0"), FALSE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM1"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM1"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("COM10"), FALSE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.\\COM5"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.\\COM10"), TRUE))
+ return EXIT_FAILURE;
+
+ if (!test_CommDevice(_T("\\\\.COM10"), FALSE))
+ return EXIT_FAILURE;
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestCommMonitor.c b/winpr/libwinpr/comm/test/TestCommMonitor.c
new file mode 100644
index 0000000..fe28a86
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestCommMonitor.c
@@ -0,0 +1,70 @@
+
+#include <winpr/crt.h>
+#include <winpr/comm.h>
+#include <winpr/file.h>
+#include <winpr/synch.h>
+#include <winpr/handle.h>
+
+int TestCommMonitor(int argc, char* argv[])
+{
+ HANDLE hComm;
+ DWORD dwError;
+ BOOL fSuccess;
+ DWORD dwEvtMask;
+ OVERLAPPED overlapped = { 0 };
+ LPCSTR lpFileName = "\\\\.\\COM1";
+
+ hComm = CreateFileA(lpFileName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING,
+ FILE_FLAG_OVERLAPPED, NULL);
+
+ if (!hComm || (hComm == INVALID_HANDLE_VALUE))
+ {
+ printf("CreateFileA failure: %s\n", lpFileName);
+ return -1;
+ }
+
+ fSuccess = SetCommMask(hComm, EV_CTS | EV_DSR);
+
+ if (!fSuccess)
+ {
+ printf("SetCommMask failure: GetLastError() = %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ if (!(overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
+ {
+ printf("CreateEvent failed: GetLastError() = %" PRIu32 "\n", GetLastError());
+ return -1;
+ }
+
+ if (WaitCommEvent(hComm, &dwEvtMask, &overlapped))
+ {
+ if (dwEvtMask & EV_DSR)
+ {
+ printf("EV_DSR\n");
+ }
+
+ if (dwEvtMask & EV_CTS)
+ {
+ printf("EV_CTS\n");
+ }
+ }
+ else
+ {
+ dwError = GetLastError();
+
+ if (dwError == ERROR_IO_PENDING)
+ {
+ printf("ERROR_IO_PENDING\n");
+ }
+ else
+ {
+ printf("WaitCommEvent failure: GetLastError() = %" PRIu32 "\n", dwError);
+ return -1;
+ }
+ }
+
+ CloseHandle(hComm);
+
+ return 0;
+}
diff --git a/winpr/libwinpr/comm/test/TestControlSettings.c b/winpr/libwinpr/comm/test/TestControlSettings.c
new file mode 100644
index 0000000..7611dbb
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestControlSettings.c
@@ -0,0 +1,123 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+int TestControlSettings(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+ DCB dcb;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Test 1 */
+
+ dcb.ByteSize = 5;
+ dcb.StopBits = ONESTOPBIT;
+ dcb.Parity = MARKPARITY;
+
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ByteSize != 5) || (dcb.StopBits != ONESTOPBIT) || (dcb.Parity != MARKPARITY))
+ {
+ fprintf(stderr, "test1 failed.\n");
+ return FALSE;
+ }
+
+ /* Test 2 */
+
+ dcb.ByteSize = 8;
+ dcb.StopBits = ONESTOPBIT;
+ dcb.Parity = NOPARITY;
+
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ByteSize != 8) || (dcb.StopBits != ONESTOPBIT) || (dcb.Parity != NOPARITY))
+ {
+ fprintf(stderr, "test2 failed.\n");
+ return FALSE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestGetCommState.c b/winpr/libwinpr/comm/test/TestGetCommState.c
new file mode 100644
index 0000000..909e61a
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestGetCommState.c
@@ -0,0 +1,138 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_generic(HANDLE hComm)
+{
+ DCB dcb, *pDcb;
+ BOOL result;
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ result = GetCommState(hComm, &dcb);
+ if (result)
+ {
+ printf("GetCommState failure, should have returned false because dcb.DCBlength has been "
+ "let uninitialized\n");
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB) / 2; /* improper value */
+ result = GetCommState(hComm, &dcb);
+ if (result)
+ {
+ printf("GetCommState failure, should have return false because dcb.DCBlength was not "
+ "correctly initialized\n");
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ printf("GetCommState failure: Ox%x, with adjusted DCBlength\n", GetLastError());
+ return FALSE;
+ }
+
+ pDcb = (DCB*)calloc(2, sizeof(DCB));
+ if (!pDcb)
+ return FALSE;
+ pDcb->DCBlength = sizeof(DCB) * 2;
+ result = GetCommState(hComm, pDcb);
+ result = result && (pDcb->DCBlength == sizeof(DCB) * 2);
+ free(pDcb);
+ if (!result)
+ {
+ printf("GetCommState failure: 0x%x, with bigger DCBlength\n", GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestGetCommState(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ printf("DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFileA("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ printf("CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverUnknown)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerialSys)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerCxSys)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ printf("test_generic failure (SerialDriverSerCx2Sys)\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestHandflow.c b/winpr/libwinpr/comm/test/TestHandflow.c
new file mode 100644
index 0000000..ad7fe3c
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestHandflow.c
@@ -0,0 +1,92 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_SerialSys(HANDLE hComm)
+{
+ // TMP: TODO:
+ return TRUE;
+}
+
+int TestHandflow(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_SerialSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ /* _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys); */
+ /* if (!test_SerCxSys(hComm)) */
+ /* { */
+ /* fprintf(stderr, "test_SerCxSys failure\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ /* _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys); */
+ /* if (!test_SerCx2Sys(hComm)) */
+ /* { */
+ /* fprintf(stderr, "test_SerCxSys failure\n"); */
+ /* return EXIT_FAILURE; */
+ /* } */
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestSerialChars.c b/winpr/libwinpr/comm/test/TestSerialChars.c
new file mode 100644
index 0000000..b235321
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestSerialChars.c
@@ -0,0 +1,178 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_SerCxSys(HANDLE hComm)
+{
+ DCB dcb = { 0 };
+ UCHAR XonChar, XoffChar;
+
+ struct termios currentTermios = { 0 };
+
+ if (tcgetattr(((WINPR_COMM*)hComm)->fd, &currentTermios) < 0)
+ {
+ fprintf(stderr, "tcgetattr failure.\n");
+ return FALSE;
+ }
+
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.XonChar == '\0') || (dcb.XoffChar == '\0'))
+ {
+ fprintf(stderr, "test_SerCxSys failure, expected XonChar and XoffChar to be set\n");
+ return FALSE;
+ }
+
+ /* retrieve Xon/Xoff chars */
+ if ((dcb.XonChar != currentTermios.c_cc[VSTART]) ||
+ (dcb.XoffChar != currentTermios.c_cc[VSTOP]))
+ {
+ fprintf(stderr, "test_SerCxSys failure, could not retrieve XonChar and XoffChar\n");
+ return FALSE;
+ }
+
+ /* swap XonChar/XoffChar */
+
+ XonChar = dcb.XonChar;
+ XoffChar = dcb.XoffChar;
+ dcb.XonChar = XoffChar;
+ dcb.XoffChar = XonChar;
+ if (!SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "SetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ ZeroMemory(&dcb, sizeof(DCB));
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure, GetLastError(): 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.XonChar != XoffChar) || (dcb.XoffChar != XonChar))
+ {
+ fprintf(stderr, "test_SerCxSys, expected XonChar and XoffChar to be swapped\n");
+ return FALSE;
+ }
+
+ /* same XonChar / XoffChar */
+ dcb.XonChar = dcb.XoffChar;
+ if (SetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "test_SerCxSys failure, SetCommState() was supposed to failed because "
+ "XonChar and XoffChar are the same\n");
+ return FALSE;
+ }
+ if (GetLastError() != ERROR_INVALID_PARAMETER)
+ {
+ fprintf(stderr, "test_SerCxSys failure, SetCommState() was supposed to failed with "
+ "GetLastError()=ERROR_INVALID_PARAMETER\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerCx2Sys(HANDLE hComm)
+{
+ DCB dcb = { 0 };
+
+ dcb.DCBlength = sizeof(DCB);
+ if (!GetCommState(hComm, &dcb))
+ {
+ fprintf(stderr, "GetCommState failure; GetLastError(): %08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if ((dcb.ErrorChar != '\0') || (dcb.EofChar != '\0') || (dcb.EvtChar != '\0') ||
+ (dcb.XonChar != '\0') || (dcb.XoffChar != '\0'))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure, expected all characters to be: '\\0'\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSerialChars(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_SerCxSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_SerCx2Sys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestSetCommState.c b/winpr/libwinpr/comm/test/TestSetCommState.c
new file mode 100644
index 0000000..0204058
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestSetCommState.c
@@ -0,0 +1,332 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static void init_empty_dcb(DCB* pDcb)
+{
+ WINPR_ASSERT(pDcb);
+
+ ZeroMemory(pDcb, sizeof(DCB));
+ pDcb->DCBlength = sizeof(DCB);
+ pDcb->XonChar = 1;
+ pDcb->XoffChar = 2;
+}
+
+static BOOL test_fParity(HANDLE hComm)
+{
+ DCB dcb;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ /* test 1 */
+ dcb.fParity = TRUE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of TRUE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ /* test 2 */
+ dcb.fParity = FALSE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of FALSE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ /* test 3 (redo test 1) */
+ dcb.fParity = TRUE;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%08" PRIx32 "\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!dcb.fParity)
+ {
+ fprintf(stderr, "unexpected fParity: %" PRIu32 " instead of TRUE\n", dcb.fParity);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerialSys(HANDLE hComm)
+{
+ DCB dcb;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Test 1 */
+ dcb.BaudRate = CBR_115200;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+ if (dcb.BaudRate != CBR_115200)
+ {
+ fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (CBR_115200)\n",
+ CBR_115200);
+ return FALSE;
+ }
+
+ /* Test 2 using a defferent baud rate */
+
+ dcb.BaudRate = CBR_57600;
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+ if (dcb.BaudRate != CBR_57600)
+ {
+ fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (CBR_57600)\n", CBR_57600);
+ return FALSE;
+ }
+
+ /* Test 3 using an unsupported baud rate on Linux */
+ dcb.BaudRate = CBR_128000;
+ result = SetCommState(hComm, &dcb);
+ if (result)
+ {
+ fprintf(stderr, "SetCommState failure: unexpected support of BaudRate=%d (CBR_128000)\n",
+ CBR_128000);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static BOOL test_SerCxSys(HANDLE hComm)
+{
+ /* as of today there is no difference */
+ return test_SerialSys(hComm);
+}
+
+static BOOL test_SerCx2Sys(HANDLE hComm)
+{
+ /* as of today there is no difference */
+ return test_SerialSys(hComm);
+}
+
+static BOOL test_generic(HANDLE hComm)
+{
+ DCB dcb, dcb2;
+ BOOL result;
+
+ init_empty_dcb(&dcb);
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ /* Checks whether we get the same information before and after SetCommState */
+ memcpy(&dcb2, &dcb, sizeof(DCB));
+ result = SetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "SetCommState failure: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ result = GetCommState(hComm, &dcb);
+ if (!result)
+ {
+ fprintf(stderr, "GetCommState failure: 0x%x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (memcmp(&dcb, &dcb2, sizeof(DCB)) != 0)
+ {
+ fprintf(stderr,
+ "DCB is different after SetCommState() whereas it should have not changed\n");
+ return FALSE;
+ }
+
+ // TODO: a more complete and generic test using GetCommProperties()
+
+ /* TMP: TODO: fBinary tests */
+
+ /* fParity tests */
+ if (!test_fParity(hComm))
+ {
+ fprintf(stderr, "test_fParity failure\n");
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestSetCommState(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverUnknown)\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerialSys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerialSys(hComm))
+ {
+ fprintf(stderr, "test_SerialSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerCxSys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerCxSys(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_generic failure (SerialDriverSerCx2Sys)\n");
+ return EXIT_FAILURE;
+ }
+ if (!test_SerCx2Sys(hComm))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/winpr/libwinpr/comm/test/TestTimeouts.c b/winpr/libwinpr/comm/test/TestTimeouts.c
new file mode 100644
index 0000000..c2d3be8
--- /dev/null
+++ b/winpr/libwinpr/comm/test/TestTimeouts.c
@@ -0,0 +1,138 @@
+/**
+ * WinPR: Windows Portable Runtime
+ * Serial Communication API
+ *
+ * Copyright 2014 Hewlett-Packard Development Company, L.P.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <sys/stat.h>
+
+#ifndef _WIN32
+#include <termios.h>
+#endif
+
+#include <winpr/comm.h>
+#include <winpr/crt.h>
+
+#include "../comm.h"
+
+static BOOL test_generic(HANDLE hComm)
+{
+ COMMTIMEOUTS timeouts = { 0 }, timeouts2 = { 0 };
+
+ timeouts.ReadIntervalTimeout = 1;
+ timeouts.ReadTotalTimeoutMultiplier = 2;
+ timeouts.ReadTotalTimeoutConstant = 3;
+ timeouts.WriteTotalTimeoutMultiplier = 4;
+ timeouts.WriteTotalTimeoutConstant = 5;
+
+ if (!SetCommTimeouts(hComm, &timeouts))
+ {
+ fprintf(stderr, "SetCommTimeouts failure, GetLastError: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (!GetCommTimeouts(hComm, &timeouts2))
+ {
+ fprintf(stderr, "GetCommTimeouts failure, GetLastError: 0x%08x\n", GetLastError());
+ return FALSE;
+ }
+
+ if (memcmp(&timeouts, &timeouts2, sizeof(COMMTIMEOUTS)) != 0)
+ {
+ fprintf(stderr, "TestTimeouts failure, didn't get back the same timeouts.\n");
+ return FALSE;
+ }
+
+ /* not supported combination */
+ timeouts.ReadIntervalTimeout = MAXULONG;
+ timeouts.ReadTotalTimeoutConstant = MAXULONG;
+ if (SetCommTimeouts(hComm, &timeouts))
+ {
+ fprintf(stderr,
+ "SetCommTimeouts succeeded with ReadIntervalTimeout and ReadTotalTimeoutConstant "
+ "set to MAXULONG. GetLastError: 0x%08x\n",
+ GetLastError());
+ return FALSE;
+ }
+
+ if (GetLastError() != ERROR_INVALID_PARAMETER)
+ {
+ fprintf(stderr,
+ "SetCommTimeouts failure, expected GetLastError to return ERROR_INVALID_PARAMETER "
+ "and got: 0x%08x\n",
+ GetLastError());
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+int TestTimeouts(int argc, char* argv[])
+{
+ struct stat statbuf;
+ BOOL result;
+ HANDLE hComm;
+
+ if (stat("/dev/ttyS0", &statbuf) < 0)
+ {
+ fprintf(stderr, "/dev/ttyS0 not available, making the test to succeed though\n");
+ return EXIT_SUCCESS;
+ }
+
+ result = DefineCommDevice("COM1", "/dev/ttyS0");
+ if (!result)
+ {
+ fprintf(stderr, "DefineCommDevice failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ hComm = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
+ if (hComm == INVALID_HANDLE_VALUE)
+ {
+ fprintf(stderr, "CreateFileA failure: 0x%x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerialSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerialSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCxSys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerCxSys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ _comm_setServerSerialDriver(hComm, SerialDriverSerCx2Sys);
+ if (!test_generic(hComm))
+ {
+ fprintf(stderr, "test_SerCx2Sys failure\n");
+ return EXIT_FAILURE;
+ }
+
+ if (!CloseHandle(hComm))
+ {
+ fprintf(stderr, "CloseHandle failure, GetLastError()=%08x\n", GetLastError());
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}