diff options
Diffstat (limited to '')
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, ¤tState) < 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, ¤tTermios) < 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, ¤tTermios) < 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, ¤tState)) < 0) + { + CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, 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(¤tState, sizeof(struct termios)); + if ((result = tcgetattr(fd, ¤tState)) < 0) + { + CommLog_Print(WLOG_WARN, "tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, 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(¤tState, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tState) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + currentSpeed = cfgetispeed(¤tState); + + 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(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 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(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 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(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 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(¤tCounters, sizeof(struct serial_icounter_struct)); + if (ioctl(pComm->fd, TIOCGICOUNT, ¤tCounters) < 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(¤tCounters, 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, ¤tTermios) < 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; +} |