diff --git a/.gitignore b/.gitignore index 415c5441b..732874953 100755 --- a/.gitignore +++ b/.gitignore @@ -122,3 +122,6 @@ default.log *.txt.user *.autosave + +# etags +TAGS diff --git a/channels/serial/client/CMakeLists.txt b/channels/serial/client/CMakeLists.txt index d170689b5..7d0fe8897 100644 --- a/channels/serial/client/CMakeLists.txt +++ b/channels/serial/client/CMakeLists.txt @@ -18,9 +18,6 @@ define_channel_client("serial") set(${MODULE_PREFIX}_SRCS - serial_tty.c - serial_tty.h - serial_constants.h serial_main.c) add_channel_client_library(${MODULE_PREFIX} ${MODULE_NAME} ${CHANNEL_NAME} TRUE "DeviceServiceEntry") @@ -32,6 +29,11 @@ set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS MODULE freerdp MODULES freerdp-utils) +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} + MODULE winpr + MODULES winpr-comm) + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) install(TARGETS ${MODULE_NAME} DESTINATION ${FREERDP_ADDIN_PATH} EXPORT FreeRDPTargets) diff --git a/channels/serial/client/serial_constants.h b/channels/serial/client/serial_constants.h deleted file mode 100644 index a42c4f001..000000000 --- a/channels/serial/client/serial_constants.h +++ /dev/null @@ -1,154 +0,0 @@ -/** - * FreeRDP: A Remote Desktop Protocol Implementation - * Serial Port Device Service Virtual Channel - * - * Copyright 2011 O.S. Systems Software Ltda. - * Copyright 2011 Eduardo Fiss Beloni - * - * 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 __SERIAL_CONSTANTS_H -#define __SERIAL_CONSTANTS_H - -/* http://www.codeproject.com/KB/system/chaiyasit_t.aspx */ -#define SERIAL_TIMEOUT_MAX 4294967295u - -/* DR_CONTROL_REQ.IoControlCode */ -enum DR_PORT_CONTROL_REQ -{ - IOCTL_SERIAL_SET_BAUD_RATE = 0x001B0004, - IOCTL_SERIAL_GET_BAUD_RATE = 0x001B0050, - IOCTL_SERIAL_SET_LINE_CONTROL = 0x001B000C, - IOCTL_SERIAL_GET_LINE_CONTROL = 0x001B0054, - IOCTL_SERIAL_SET_TIMEOUTS = 0x001B001C, - IOCTL_SERIAL_GET_TIMEOUTS = 0x001B0020, - -/* GET_CHARS and SET_CHARS are swapped in the RDP docs [MS-RDPESP] */ - IOCTL_SERIAL_GET_CHARS = 0x001B0058, - IOCTL_SERIAL_SET_CHARS = 0x001B005C, - - IOCTL_SERIAL_SET_DTR = 0x001B0024, - IOCTL_SERIAL_CLR_DTR = 0x001B0028, - IOCTL_SERIAL_RESET_DEVICE = 0x001B002C, - IOCTL_SERIAL_SET_RTS = 0x001B0030, - IOCTL_SERIAL_CLR_RTS = 0x001B0034, - IOCTL_SERIAL_SET_XOFF = 0x001B0038, - IOCTL_SERIAL_SET_XON = 0x001B003C, - IOCTL_SERIAL_SET_BREAK_ON = 0x001B0010, - IOCTL_SERIAL_SET_BREAK_OFF = 0x001B0014, - IOCTL_SERIAL_SET_QUEUE_SIZE = 0x001B0008, - IOCTL_SERIAL_GET_WAIT_MASK = 0x001B0040, - IOCTL_SERIAL_SET_WAIT_MASK = 0x001B0044, - IOCTL_SERIAL_WAIT_ON_MASK = 0x001B0048, - IOCTL_SERIAL_IMMEDIATE_CHAR = 0x001B0018, - IOCTL_SERIAL_PURGE = 0x001B004C, - IOCTL_SERIAL_GET_HANDFLOW = 0x001B0060, - IOCTL_SERIAL_SET_HANDFLOW = 0x001B0064, - IOCTL_SERIAL_GET_MODEMSTATUS = 0x001B0068, - IOCTL_SERIAL_GET_DTRRTS = 0x001B0078, - -/* according to [MS-RDPESP] it should be 0x001B0084, but servers send 0x001B006C */ - IOCTL_SERIAL_GET_COMMSTATUS = 0x001B006C, - - IOCTL_SERIAL_GET_PROPERTIES = 0x001B0074, - IOCTL_SERIAL_XOFF_COUNTER = 0x001B0070, - IOCTL_SERIAL_LSRMST_INSERT = 0x001B007C, - IOCTL_SERIAL_CONFIG_SIZE = 0x001B0080, - IOCTL_SERIAL_GET_STATS = 0x001B008C, - IOCTL_SERIAL_CLEAR_STATS = 0x001B0090, - IOCTL_SERIAL_GET_MODEM_CONTROL = 0x001B0094, - IOCTL_SERIAL_SET_MODEM_CONTROL = 0x001B0098, - IOCTL_SERIAL_SET_FIFO_CONTROL = 0x001B009C, -}; - -enum SERIAL_PURGE_MASK -{ - SERIAL_PURGE_TXABORT = 0x00000001, - SERIAL_PURGE_RXABORT = 0x00000002, - SERIAL_PURGE_TXCLEAR = 0x00000004, - SERIAL_PURGE_RXCLEAR = 0x00000008, -}; - -enum SERIAL_WAIT_MASK -{ - SERIAL_EV_RXCHAR = 0x0001, /* Any Character received */ - SERIAL_EV_RXFLAG = 0x0002, /* Received certain character */ - SERIAL_EV_TXEMPTY = 0x0004, /* Transmitt Queue Empty */ - SERIAL_EV_CTS = 0x0008, /* CTS changed state */ - SERIAL_EV_DSR = 0x0010, /* DSR changed state */ - SERIAL_EV_RLSD = 0x0020, /* RLSD changed state */ - SERIAL_EV_BREAK = 0x0040, /* BREAK received */ - SERIAL_EV_ERR = 0x0080, /* Line status error occurred */ - SERIAL_EV_RING = 0x0100, /* Ring signal detected */ - SERIAL_EV_PERR = 0x0200, /* Printer error occured */ - SERIAL_EV_RX80FULL = 0x0400,/* Receive buffer is 80 percent full */ - SERIAL_EV_EVENT1 = 0x0800, /* Provider specific event 1 */ - SERIAL_EV_EVENT2 = 0x1000, /* Provider specific event 2 */ -}; - -enum SERIAL_MODEM_STATUS -{ - SERIAL_MS_DTR = 0x01, - SERIAL_MS_RTS = 0x02, - SERIAL_MS_CTS = 0x10, - SERIAL_MS_DSR = 0x20, - SERIAL_MS_RNG = 0x40, - SERIAL_MS_CAR = 0x80, -}; - -enum SERIAL_HANDFLOW -{ - SERIAL_DTR_CONTROL = 0x01, - SERIAL_CTS_HANDSHAKE = 0x08, - SERIAL_ERROR_ABORT = 0x80000000, -}; - -enum SERIAL_FLOW_CONTROL -{ - SERIAL_XON_HANDSHAKE = 0x01, - SERIAL_XOFF_HANDSHAKE = 0x02, - SERIAL_DSR_SENSITIVITY = 0x40, -}; - -enum SERIAL_CHARS -{ - SERIAL_CHAR_EOF = 0, - SERIAL_CHAR_ERROR = 1, - SERIAL_CHAR_BREAK = 2, - SERIAL_CHAR_EVENT = 3, - SERIAL_CHAR_XON = 4, - SERIAL_CHAR_XOFF = 5, -}; - -enum SERIAL_ABORT_IO -{ - SERIAL_ABORT_IO_NONE = 0, - SERIAL_ABORT_IO_WRITE = 1, - SERIAL_ABORT_IO_READ = 2, -}; - -enum SERIAL_STOP_BITS -{ - SERIAL_STOP_BITS_1 = 0, - SERIAL_STOP_BITS_2 = 2, -}; - -enum SERIAL_PARITY -{ - SERIAL_NO_PARITY = 0, - SERIAL_ODD_PARITY = 1, - SERIAL_EVEN_PARITY = 2, -}; - -#endif diff --git a/channels/serial/client/serial_main.c b/channels/serial/client/serial_main.c index de78217bd..d4d9b0738 100644 --- a/channels/serial/client/serial_main.c +++ b/channels/serial/client/serial_main.c @@ -4,6 +4,7 @@ * * Copyright 2011 O.S. Systems Software Ltda. * Copyright 2011 Eduardo Fiss Beloni + * 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. @@ -41,97 +42,201 @@ #include #endif -#include "serial_tty.h" -#include "serial_constants.h" - +#include +#include #include -#include +#include #include #include -#include -#include +/* #include */ #include #include +#define MAX_IRP_THREADS 5 + typedef struct _SERIAL_DEVICE SERIAL_DEVICE; struct _SERIAL_DEVICE { DEVICE device; + SERIAL_DRIVER_ID ServerSerialDriverId; + HANDLE* hComm; - char* path; - SERIAL_TTY* tty; + /* TODO: use of log (prefered the old fashion DEBUG_SVC and + * DEBUG_WARN macros for backward compatibility resaons) + */ + /* wLog* log; */ + HANDLE MainThread; + wMessageQueue* MainIrpQueue; - wLog* log; - HANDLE thread; - wMessageQueue* IrpQueue; + /* one thread per pending IRP and indexed according their CompletionId */ + wListDictionary *IrpThreads; + UINT32 IrpThreadToBeTerminatedCount; + CRITICAL_SECTION TerminatingIrpThreadsLock; }; +typedef struct _IRP_THREAD_DATA IRP_THREAD_DATA; + +struct _IRP_THREAD_DATA +{ + SERIAL_DEVICE *serial; + IRP *irp; +}; + +static UINT32 _GetLastErrorToIoStatus() +{ + /* http://msdn.microsoft.com/en-us/library/ff547466%28v=vs.85%29.aspx#generic_status_values_for_serial_device_control_requests */ + + switch(GetLastError()) + { + case ERROR_BAD_DEVICE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_CALL_NOT_IMPLEMENTED: + return STATUS_NOT_IMPLEMENTED; + + case ERROR_CANCELLED: + return STATUS_CANCELLED; + + case ERROR_INSUFFICIENT_BUFFER: + return STATUS_BUFFER_TOO_SMALL; /* NB: STATUS_BUFFER_SIZE_TOO_SMALL not defined */ + + case ERROR_INVALID_DEVICE_OBJECT_PARAMETER: /* eg: SerCx2.sys' _purge() */ + return STATUS_INVALID_DEVICE_STATE; + + case ERROR_INVALID_HANDLE: + return STATUS_INVALID_DEVICE_REQUEST; + + case ERROR_INVALID_PARAMETER: + return STATUS_INVALID_PARAMETER; + + case ERROR_IO_DEVICE: + return STATUS_IO_DEVICE_ERROR; + + case ERROR_IO_PENDING: + return STATUS_PENDING; + + case ERROR_NOT_SUPPORTED: + return STATUS_NOT_SUPPORTED; + + case ERROR_TIMEOUT: + return STATUS_TIMEOUT; + + /* no default */ + } + + DEBUG_SVC("unexpected last-error: 0x%lx", GetLastError()); + return STATUS_UNSUCCESSFUL; +} + static void serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) { - int status; - UINT32 FileId; + DWORD DesiredAccess; + DWORD SharedAccess; + DWORD CreateDisposition; UINT32 PathLength; - char* path = NULL; - SERIAL_TTY* tty; - Stream_Seek_UINT32(irp->input); /* DesiredAccess (4 bytes) */ - Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */ - Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */ - Stream_Seek_UINT32(irp->input); /* SharedAccess (4 bytes) */ - Stream_Seek_UINT32(irp->input); /* CreateDisposition (4 bytes) */ - Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */ + Stream_Read_UINT32(irp->input, DesiredAccess); /* DesiredAccess (4 bytes) */ + Stream_Seek_UINT64(irp->input); /* AllocationSize (8 bytes) */ + Stream_Seek_UINT32(irp->input); /* FileAttributes (4 bytes) */ + Stream_Read_UINT32(irp->input, SharedAccess); /* SharedAccess (4 bytes) */ + Stream_Read_UINT32(irp->input, CreateDisposition); /* CreateDisposition (4 bytes) */ + Stream_Seek_UINT32(irp->input); /* CreateOptions (4 bytes) */ + Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */ + Stream_Seek(irp->input, PathLength); /* Path (variable) */ - Stream_Read_UINT32(irp->input, PathLength); /* PathLength (4 bytes) */ + assert(PathLength == 0); /* MS-RDPESP 2.2.2.2 */ - status = ConvertFromUnicode(CP_UTF8, 0, (WCHAR*) Stream_Pointer(irp->input), - PathLength / 2, &path, 0, NULL, NULL); - if (status < 1) - path = (char*) calloc(1, 1); +#ifndef _WIN32 + /* Windows 2012 server sends on a first call : + * DesiredAccess = 0x00100080: SYNCHRONIZE | FILE_READ_ATTRIBUTES + * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ + * CreateDisposition = 0x00000001: CREATE_NEW + * + * then Windows 2012 sends : + * DesiredAccess = 0x00120089: SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES | FILE_READ_EA | FILE_READ_DATA + * SharedAccess = 0x00000007: FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ + * CreateDisposition = 0x00000001: CREATE_NEW + * + * assert(DesiredAccess == (GENERIC_READ | GENERIC_WRITE)); + * assert(SharedAccess == 0); + * assert(CreateDisposition == OPEN_EXISTING); + * + */ - FileId = irp->devman->id_sequence++; + DEBUG_SVC("DesiredAccess: 0x%lX, SharedAccess: 0x%lX, CreateDisposition: 0x%lX", DesiredAccess, SharedAccess, CreateDisposition); - tty = serial_tty_new(serial->path, FileId); + /* FIXME: As of today only the flags below are supported by CommCreateFileA: */ + DesiredAccess = GENERIC_READ | GENERIC_WRITE; + SharedAccess = 0; + CreateDisposition = OPEN_EXISTING; +#endif - if (!tty) + serial->hComm = CreateFile(serial->device.name, + DesiredAccess, + SharedAccess, + NULL, /* SecurityAttributes */ + CreateDisposition, + 0, /* FlagsAndAttributes */ + NULL); /* TemplateFile */ + + if (!serial->hComm || (serial->hComm == INVALID_HANDLE_VALUE)) { + DEBUG_WARN("CreateFile failure: %s last-error: Ox%lX\n", serial->device.name, GetLastError()); + irp->IoStatus = STATUS_UNSUCCESSFUL; - FileId = 0; - } - else - { - serial->tty = tty; + goto error_handle; } - Stream_Write_UINT32(irp->output, FileId); /* FileId (4 bytes) */ - Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */ + _comm_setServerSerialDriver(serial->hComm, serial->ServerSerialDriverId); - free(path); + /* FIXME: Appeared to be useful to setup some devices. Guess + * the device driver asked to setup some unsupported feature + * that were not eventually used. TODO: collecting more + * details, a command line argument? */ + /* _comm_set_permissive(serial->hComm, TRUE); */ - irp->Complete(irp); + /* NOTE: binary mode/raw mode required for the redirection. On + * Linux, CommCreateFileA forces this setting. + */ + /* ZeroMemory(&dcb, sizeof(DCB)); */ + /* dcb.DCBlength = sizeof(DCB); */ + /* GetCommState(serial->hComm, &dcb); */ + /* dcb.fBinary = TRUE; */ + /* SetCommState(serial->hComm, &dcb); */ + + assert(irp->FileId == 0); + irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */ + + irp->IoStatus = STATUS_SUCCESS; + + DEBUG_SVC("%s (DeviceId: %d, FileId: %d) created.", serial->device.name, irp->device->id, irp->FileId); + + error_handle: + Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */ + Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */ } static void serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp) { - SERIAL_TTY* tty = serial->tty; - Stream_Seek(irp->input, 32); /* Padding (32 bytes) */ - if (!tty) + if (!CloseHandle(serial->hComm)) { + DEBUG_WARN("CloseHandle failure: %s (%d) closed.", serial->device.name, irp->device->id); irp->IoStatus = STATUS_UNSUCCESSFUL; - } - else - { - serial_tty_free(tty); - serial->tty = NULL; + goto error_handle; } + DEBUG_SVC("%s (DeviceId: %d, FileId: %d) closed.", serial->device.name, irp->device->id, irp->FileId); + + serial->hComm = NULL; + irp->IoStatus = STATUS_SUCCESS; + + error_handle: Stream_Zero(irp->output, 5); /* Padding (5 bytes) */ - - irp->Complete(irp); } static void serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp) @@ -139,119 +244,175 @@ static void serial_process_irp_read(SERIAL_DEVICE* serial, IRP* irp) UINT32 Length; UINT64 Offset; BYTE* buffer = NULL; - SERIAL_TTY* tty = serial->tty; + DWORD nbRead = 0; Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ - if (!tty) + + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + if (buffer == NULL) { - irp->IoStatus = STATUS_UNSUCCESSFUL; - Length = 0; + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + + /* MS-RDPESP 3.2.5.1.4: If the Offset field is not set to 0, the value MUST be ignored + * assert(Offset == 0); + */ + + + DEBUG_SVC("reading %d bytes from %s", Length, serial->device.name); + + /* FIXME: CommReadFile to be replaced by ReadFile */ + if (CommReadFile(serial->hComm, buffer, Length, &nbRead, NULL)) + { + irp->IoStatus = STATUS_SUCCESS; } else { - buffer = (BYTE*) malloc(Length); + DEBUG_SVC("read failure to %s, nbRead=%ld, last-error: 0x%lX", serial->device.name, nbRead, GetLastError()); - if (!serial_tty_read(tty, buffer, &Length)) - { - irp->IoStatus = STATUS_UNSUCCESSFUL; - free(buffer); - buffer = NULL; - Length = 0; - } - else - { - - } + irp->IoStatus = _GetLastErrorToIoStatus(); } - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ + DEBUG_SVC("%lu bytes read from %s", nbRead, serial->device.name); - if (Length > 0) + error_handle: + + Stream_Write_UINT32(irp->output, nbRead); /* Length (4 bytes) */ + + if (nbRead > 0) { - Stream_EnsureRemainingCapacity(irp->output, Length); - Stream_Write(irp->output, buffer, Length); + Stream_EnsureRemainingCapacity(irp->output, nbRead); + Stream_Write(irp->output, buffer, nbRead); /* ReadData */ } - free(buffer); - - irp->Complete(irp); + if (buffer) + free(buffer); } static void serial_process_irp_write(SERIAL_DEVICE* serial, IRP* irp) { - int status; UINT32 Length; UINT64 Offset; - SERIAL_TTY* tty = serial->tty; + DWORD nbWritten = 0; Stream_Read_UINT32(irp->input, Length); /* Length (4 bytes) */ Stream_Read_UINT64(irp->input, Offset); /* Offset (8 bytes) */ Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ - if (!tty) - { - irp->IoStatus = STATUS_UNSUCCESSFUL; - Length = 0; + /* MS-RDPESP 3.2.5.1.5: The Offset field is ignored + * assert(Offset == 0); + * + * Using a serial printer, noticed though this field could be + * set. + */ - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ - Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ - irp->Complete(irp); - return; + DEBUG_SVC("writing %d bytes to %s", Length, serial->device.name); + + /* FIXME: CommWriteFile to be replaced by WriteFile */ + if (CommWriteFile(serial->hComm, Stream_Pointer(irp->input), Length, &nbWritten, NULL)) + { + irp->IoStatus = STATUS_SUCCESS; + } + else + { + DEBUG_SVC("write failure to %s, nbWritten=%ld, last-error: 0x%lX", serial->device.name, nbWritten, GetLastError()); + + irp->IoStatus = _GetLastErrorToIoStatus(); } - status = serial_tty_write(tty, Stream_Pointer(irp->input), Length); + DEBUG_SVC("%lu bytes written to %s", nbWritten, serial->device.name); - if (status < 0) - { - irp->IoStatus = STATUS_UNSUCCESSFUL; - Length = 0; - - printf("serial_tty_write failure: status: %d, errno: %d\n", status, errno); - - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ - Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ - irp->Complete(irp); - return; - } - - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ + Stream_Write_UINT32(irp->output, nbWritten); /* Length (4 bytes) */ Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ - irp->Complete(irp); } + static void serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) { UINT32 IoControlCode; UINT32 InputBufferLength; + BYTE* InputBuffer = NULL; UINT32 OutputBufferLength; - UINT32 abortIo = SERIAL_ABORT_IO_NONE; - SERIAL_TTY* tty = serial->tty; + BYTE* OutputBuffer = NULL; + DWORD BytesReturned = 0; Stream_Read_UINT32(irp->input, OutputBufferLength); /* OutputBufferLength (4 bytes) */ Stream_Read_UINT32(irp->input, InputBufferLength); /* InputBufferLength (4 bytes) */ Stream_Read_UINT32(irp->input, IoControlCode); /* IoControlCode (4 bytes) */ Stream_Seek(irp->input, 20); /* Padding (20 bytes) */ - if (!tty) + OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE)); + if (OutputBuffer == NULL) { - irp->IoStatus = STATUS_UNSUCCESSFUL; - OutputBufferLength = 0; + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + InputBuffer = (BYTE*)calloc(InputBufferLength, sizeof(BYTE)); + if (InputBuffer == NULL) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + Stream_Read(irp->input, InputBuffer, InputBufferLength); + + DEBUG_SVC("CommDeviceIoControl: CompletionId=%d, IoControlCode=[0x%X] %s", irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode)); + + /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */ + if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength, &BytesReturned, NULL)) + { + /* DEBUG_SVC("CommDeviceIoControl: CompletionId=%d, IoControlCode=[0x%X] %s done", irp->CompletionId, IoControlCode, _comm_serial_ioctl_name(IoControlCode)); */ + + irp->IoStatus = STATUS_SUCCESS; } else { - irp->IoStatus = serial_tty_control(tty, IoControlCode, irp->input, irp->output, &abortIo); + DEBUG_SVC("CommDeviceIoControl failure: IoControlCode=[0x%X] %s, last-error: 0x%lX", + IoControlCode, _comm_serial_ioctl_name(IoControlCode), GetLastError()); + + irp->IoStatus = _GetLastErrorToIoStatus(); } - irp->Complete(irp); + error_handle: + + /* FIXME: find out whether it's required or not to get + * BytesReturned == OutputBufferLength when + * CommDeviceIoControl returns FALSE */ + assert(OutputBufferLength == BytesReturned); + + Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */ + + if (BytesReturned > 0) + { + Stream_EnsureRemainingCapacity(irp->output, BytesReturned); + Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */ + } + /* FIXME: Why at least Windows 2008R2 gets lost with this + * extra byte and likely on a IOCTL_SERIAL_SET_BAUD_RATE? The + * extra byte is well required according MS-RDPEFS + * 2.2.1.5.5 */ + /* else */ + /* { */ + /* Stream_Write_UINT8(irp->output, 0); /\* Padding (1 byte) *\/ */ + /* } */ + + if (InputBuffer != NULL) + free(InputBuffer); + + if (OutputBuffer != NULL) + free(OutputBuffer); } static void serial_process_irp(SERIAL_DEVICE* serial, IRP* irp) { - WLog_Print(serial->log, WLOG_DEBUG, "IRP MajorFunction: 0x%04X MinorFunction: 0x%04X\n", - irp->MajorFunction, irp->MinorFunction); + /* WLog_Print(serial->log, WLOG_DEBUG, "IRP MajorFunction: 0x%04X MinorFunction: 0x%04X\n", */ + /* irp->MajorFunction, irp->MinorFunction); */ switch (irp->MajorFunction) { @@ -277,59 +438,300 @@ static void serial_process_irp(SERIAL_DEVICE* serial, IRP* irp) default: irp->IoStatus = STATUS_NOT_SUPPORTED; - irp->Complete(irp); break; } } + +static void* irp_thread_func(void* arg) +{ + IRP_THREAD_DATA *data = (IRP_THREAD_DATA*)arg; + + /* blocks until the end of the request */ + serial_process_irp(data->serial, data->irp); + + EnterCriticalSection(&data->serial->TerminatingIrpThreadsLock); + data->serial->IrpThreadToBeTerminatedCount++; + + data->irp->Complete(data->irp); + + LeaveCriticalSection(&data->serial->TerminatingIrpThreadsLock); + + /* NB: At this point, the server might already being reusing + * the CompletionId whereas the thread is not yet + * terminated */ + + free(data); + + ExitThread(0); + return NULL; +} + + +static void create_irp_thread(SERIAL_DEVICE *serial, IRP *irp) +{ + IRP_THREAD_DATA *data = NULL; + HANDLE irpThread = INVALID_HANDLE_VALUE; + HANDLE previousIrpThread; + + /* for a test/debug purpose, uncomment the code below to get a + * single thread for all IRPs. NB: two IRPs could not be + * processed at the same time, typically two concurent + * Read/Write operations could block each other. */ + /* serial_process_irp(serial, irp); */ + /* irp->Complete(irp); */ + /* return; */ + + + /* NOTE: for good or bad, this implementation relies on the + * server to avoid a flooding of requests. see also _purge(). + */ + + EnterCriticalSection(&serial->TerminatingIrpThreadsLock); + while (serial->IrpThreadToBeTerminatedCount > 0) + { + /* Cleaning up termitating and pending irp + * threads. See also: irp_thread_func() */ + + HANDLE irpThread; + ULONG_PTR *ids; + int i, nbIds; + + nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids); + for (i=0; iIrpThreads, (void*)id); + + /* FIXME: not quite sure a zero timeout is a good thing to check whether a thread is stil alived or not */ + waitResult = WaitForSingleObject(irpThread, 0); + if (waitResult == WAIT_OBJECT_0) + { + /* terminating thread */ + + /* DEBUG_SVC("IRP thread with CompletionId=%d naturally died", id); */ + + CloseHandle(irpThread); + ListDictionary_Remove(serial->IrpThreads, (void*)id); + + serial->IrpThreadToBeTerminatedCount--; + } + else if (waitResult != WAIT_TIMEOUT) + { + /* unexpected thread state */ + + DEBUG_WARN("WaitForSingleObject, got an unexpected result=0x%lX\n", waitResult); + assert(FALSE); + } + /* pending thread (but not yet terminating thread) if waitResult == WAIT_TIMEOUT */ + } + + + if (serial->IrpThreadToBeTerminatedCount > 0) + { + DEBUG_SVC("%d IRP thread(s) not yet terminated", serial->IrpThreadToBeTerminatedCount); + Sleep(1); /* 1 ms */ + } + } + LeaveCriticalSection(&serial->TerminatingIrpThreadsLock); + + /* NB: At this point and thanks to the synchronization we're + * sure that the incoming IRP uses well a recycled + * CompletionId or the server sent again an IRP already posted + * which didn't get yet a response (this later server behavior + * at least observed with IOCTL_SERIAL_WAIT_ON_MASK and + * mstsc.exe). + * + * FIXME: behavior documented somewhere? behavior not yet + * observed with FreeRDP). + */ + + previousIrpThread = ListDictionary_GetItemValue(serial->IrpThreads, (void*)irp->CompletionId); + if (previousIrpThread) + { + /* Thread still alived <=> Request still pending */ + + DEBUG_SVC("IRP recall: IRP with the CompletionId=%d not yet completed!", irp->CompletionId); + + assert(FALSE); /* unimplemented */ + + /* TODO: asserts that previousIrpThread handles well + * the same request by checking more details. Need an + * access to the IRP object used by previousIrpThread + */ + + /* TODO: taking over the pending IRP or sending a kind + * of wake up signal to accelerate the pending + * request + * + * To be considered: + * if (IoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) { + * pComm->PendingEvents |= SERIAL_EV_FREERDP_*; + * } + */ + + irp->Discard(irp); + return; + } + + + if (ListDictionary_Count(serial->IrpThreads) >= MAX_IRP_THREADS) + { + DEBUG_WARN("Number of IRP threads threshold reached: %d, keep on anyway", ListDictionary_Count(serial->IrpThreads)); + + assert(FALSE); /* unimplemented */ + + /* TODO: MAX_IRP_THREADS has been thought to avoid a + * flooding of pending requests. Use + * WaitForMultipleObjects() when available in winpr + * for threads. + */ + } + + + /* error_handle to be used ... */ + + data = (IRP_THREAD_DATA*)calloc(1, sizeof(IRP_THREAD_DATA)); + if (data == NULL) + { + DEBUG_WARN("Could not allocate a new IRP_THREAD_DATA."); + goto error_handle; + } + + data->serial = serial; + data->irp = irp; + + /* data freed by irp_thread_func */ + + irpThread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE)irp_thread_func, + (void*)data, + 0, + NULL); + + if (irpThread == INVALID_HANDLE_VALUE) + { + DEBUG_WARN("Could not allocate a new IRP thread."); + goto error_handle; + } + + + + ListDictionary_Add(serial->IrpThreads, (void*)irp->CompletionId, irpThread); + + return; + + error_handle: + + irp->IoStatus = STATUS_NO_MEMORY; + irp->Complete(irp); + + if (data) + free(data); +} + + +static void terminate_pending_irp_threads(SERIAL_DEVICE *serial) +{ + ULONG_PTR *ids; + int i, nbIds; + + nbIds = ListDictionary_GetKeys(serial->IrpThreads, &ids); + + DEBUG_SVC("Terminating %d IRP thread(s)", nbIds); + + for (i=0; iIrpThreads, (void*)id); + + TerminateThread(irpThread, 0); + + WaitForSingleObject(irpThread, INFINITE); + + CloseHandle(irpThread); + + DEBUG_SVC("IRP thread terminated, CompletionId %d", id); + } + + ListDictionary_Clear(serial->IrpThreads); +} + + static void* serial_thread_func(void* arg) { IRP* irp; wMessage message; - SERIAL_DEVICE* drive = (SERIAL_DEVICE*) arg; + SERIAL_DEVICE* serial = (SERIAL_DEVICE*) arg; while (1) { - if (!MessageQueue_Wait(drive->IrpQueue)) + if (!MessageQueue_Wait(serial->MainIrpQueue)) break; - if (!MessageQueue_Peek(drive->IrpQueue, &message, TRUE)) + if (!MessageQueue_Peek(serial->MainIrpQueue, &message, TRUE)) break; if (message.id == WMQ_QUIT) + { + terminate_pending_irp_threads(serial); break; + } irp = (IRP*) message.wParam; if (irp) - serial_process_irp(drive, irp); + create_irp_thread(serial, irp); } ExitThread(0); return NULL; } + static void serial_irp_request(DEVICE* device, IRP* irp) { SERIAL_DEVICE* serial = (SERIAL_DEVICE*) device; - MessageQueue_Post(serial->IrpQueue, NULL, 0, (void*) irp, NULL); + + assert(irp != NULL); + + if (irp == NULL) + return; + + /* NB: ENABLE_ASYNCIO is set, (MS-RDPEFS 2.2.2.7.2) this + * allows the server to send multiple simultaneous read or + * write requests. + */ + + MessageQueue_Post(serial->MainIrpQueue, NULL, 0, (void*) irp, NULL); } + static void serial_free(DEVICE* device) { SERIAL_DEVICE* serial = (SERIAL_DEVICE*) device; - WLog_Print(serial->log, WLOG_DEBUG, "freeing"); + /* WLog_Print(serial->log, WLOG_DEBUG, "freeing"); */ - MessageQueue_PostQuit(serial->IrpQueue, 0); - WaitForSingleObject(serial->thread, INFINITE); - CloseHandle(serial->thread); + MessageQueue_PostQuit(serial->MainIrpQueue, 0); + WaitForSingleObject(serial->MainThread, INFINITE); + CloseHandle(serial->MainThread); - serial_tty_free(serial->tty); + if (serial->hComm) + CloseHandle(serial->hComm); /* Clean up resources */ Stream_Free(serial->device.data, TRUE); - MessageQueue_Free(serial->IrpQueue); + MessageQueue_Free(serial->MainIrpQueue); + ListDictionary_Free(serial->IrpThreads); + DeleteCriticalSection(&serial->TerminatingIrpThreadsLock); free(serial); } @@ -343,23 +745,32 @@ int DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) int i, len; char* name; char* path; + char* driver; RDPDR_SERIAL* device; SERIAL_DEVICE* serial; device = (RDPDR_SERIAL*) pEntryPoints->device; name = device->Name; path = device->Path; + driver = device->Driver; if (!name || (name[0] == '*')) { - /* TODO: implement auto detection of parallel ports */ + /* TODO: implement auto detection of serial ports */ return 0; } if ((name && name[0]) && (path && path[0])) { - serial = (SERIAL_DEVICE*) calloc(1, sizeof(SERIAL_DEVICE)); + DEBUG_SVC("Defining %s as %s", name, path); + if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */)) + { + DEBUG_SVC("Could not define %s as %s", name, path); + return -1; + } + + serial = (SERIAL_DEVICE*) calloc(1, sizeof(SERIAL_DEVICE)); if (!serial) return -1; @@ -374,17 +785,50 @@ int DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) for (i = 0; i <= len; i++) Stream_Write_UINT8(serial->device.data, name[i] < 0 ? '_' : name[i]); - serial->path = path; - serial->IrpQueue = MessageQueue_New(NULL); + if (driver != NULL) + { + if (_stricmp(driver, "Serial") == 0) + serial->ServerSerialDriverId = SerialDriverSerialSys; + else if (_stricmp(driver, "SerCx") == 0) + serial->ServerSerialDriverId = SerialDriverSerCxSys; + else if (_stricmp(driver, "SerCx2") == 0) + serial->ServerSerialDriverId = SerialDriverSerCx2Sys; + else + { + assert(FALSE); - WLog_Init(); - serial->log = WLog_Get("com.freerdp.channel.serial.client"); - WLog_Print(serial->log, WLOG_DEBUG, "initializing"); + DEBUG_SVC("Unknown server's serial driver: %s. SerCx2 will be used", driver); + serial->ServerSerialDriverId = SerialDriverSerCx2Sys; + } + } + else + { + /* default driver */ + serial->ServerSerialDriverId = SerialDriverSerCx2Sys; + } + + DEBUG_SVC("Server's serial driver: %s (id: %d)", driver, serial->ServerSerialDriverId); + /* TODO: implement auto detection of the server's serial driver */ + + serial->MainIrpQueue = MessageQueue_New(NULL); + + /* IrpThreads content only modified by create_irp_thread() */ + serial->IrpThreads = ListDictionary_New(FALSE); + serial->IrpThreadToBeTerminatedCount = 0; + InitializeCriticalSection(&serial->TerminatingIrpThreadsLock); + + /* WLog_Init(); */ + /* serial->log = WLog_Get("com.freerdp.channel.serial.client"); */ + /* WLog_Print(serial->log, WLOG_DEBUG, "initializing"); */ pEntryPoints->RegisterDevice(pEntryPoints->devman, (DEVICE*) serial); - serial->thread = CreateThread(NULL, 0, - (LPTHREAD_START_ROUTINE) serial_thread_func, (void*) serial, 0, NULL); + serial->MainThread = CreateThread(NULL, + 0, + (LPTHREAD_START_ROUTINE) serial_thread_func, + (void*) serial, + 0, + NULL); } return 0; diff --git a/channels/serial/client/serial_tty.c b/channels/serial/client/serial_tty.c deleted file mode 100644 index 9e65a71ef..000000000 --- a/channels/serial/client/serial_tty.c +++ /dev/null @@ -1,1013 +0,0 @@ -/** - * FreeRDP: A Remote Desktop Protocol Implementation - * Serial Port Device Service Virtual Channel - * - * Copyright 2011 O.S. Systems Software Ltda. - * Copyright 2011 Eduardo Fiss Beloni - * - * 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. - */ - -#ifdef HAVE_CONFIG_H -#include "config.h" -#endif - -#include -#include -#include - -#include -#include -#include - -#include - -#ifndef _WIN32 -#include -#include -#include -#include -#endif - -#include -#include -#include -#include - -#include "serial_tty.h" -#include "serial_constants.h" - -#ifdef HAVE_SYS_MODEM_H -#include -#endif -#ifdef HAVE_SYS_FILIO_H -#include -#endif -#ifdef HAVE_SYS_STRTIO_H -#include -#endif - -#ifndef CRTSCTS -#define CRTSCTS 0 -#endif - -/* FIONREAD should really do the same thing as TIOCINQ, where it is not available */ - -#if !defined(TIOCINQ) && defined(FIONREAD) -#define TIOCINQ FIONREAD -#endif - -#if !defined(TIOCOUTQ) && defined(FIONWRITE) -#define TIOCOUTQ FIONWRITE -#endif - -/** - * Refer to ReactOS's ntddser.h (public domain) for constant definitions - */ - -static UINT32 tty_write_data(SERIAL_TTY* tty, BYTE* data, int len); -static void tty_set_termios(SERIAL_TTY* tty); -static BOOL tty_get_termios(SERIAL_TTY* tty); -static int tty_get_error_status(); - -UINT32 serial_tty_control(SERIAL_TTY* tty, UINT32 IoControlCode, wStream* input, wStream* output, UINT32* abortIo) -{ - UINT32 result; - BYTE immediate; - int purge_mask; - UINT32 modemstate; - UINT32 begPos, endPos; - UINT32 OutputBufferLength; - UINT32 status = STATUS_SUCCESS; - UINT32 IoCtlDeviceType; - UINT32 IoCtlFunction; - UINT32 IoCtlMethod; - UINT32 IoCtlAccess; - - IoCtlMethod = (IoControlCode & 0x3); - IoCtlFunction = ((IoControlCode >> 2) & 0xFFF); - IoCtlAccess = ((IoControlCode >> 14) & 0x3); - IoCtlDeviceType = ((IoControlCode >> 16) & 0xFFFF); - - /** - * FILE_DEVICE_SERIAL_PORT 0x0000001B - * FILE_DEVICE_UNKNOWN 0x00000022 - */ - - if (IoCtlDeviceType == 0x00000022) - { - IoControlCode &= 0xFFFF; - IoControlCode |= (0x0000001B << 16); - } - - Stream_Seek_UINT32(output); /* OutputBufferLength (4 bytes) */ - begPos = (UINT32) Stream_GetPosition(output); - - switch (IoControlCode) - { - case IOCTL_SERIAL_SET_BAUD_RATE: - Stream_Read_UINT32(input, tty->baud_rate); - tty_set_termios(tty); - break; - - case IOCTL_SERIAL_GET_BAUD_RATE: - OutputBufferLength = 4; - Stream_Write_UINT32(output, tty->baud_rate); - break; - - case IOCTL_SERIAL_SET_QUEUE_SIZE: - Stream_Read_UINT32(input, tty->queue_in_size); - Stream_Read_UINT32(input, tty->queue_out_size); - break; - - case IOCTL_SERIAL_SET_LINE_CONTROL: - Stream_Read_UINT8(input, tty->stop_bits); - Stream_Read_UINT8(input, tty->parity); - Stream_Read_UINT8(input, tty->word_length); - tty_set_termios(tty); - break; - - case IOCTL_SERIAL_GET_LINE_CONTROL: - OutputBufferLength = 3; - Stream_Write_UINT8(output, tty->stop_bits); - Stream_Write_UINT8(output, tty->parity); - Stream_Write_UINT8(output, tty->word_length); - break; - - case IOCTL_SERIAL_IMMEDIATE_CHAR: - Stream_Read_UINT8(input, immediate); - tty_write_data(tty, &immediate, 1); - break; - - case IOCTL_SERIAL_CONFIG_SIZE: - OutputBufferLength = 4; - Stream_Write_UINT32(output, 0); - break; - - case IOCTL_SERIAL_GET_CHARS: - OutputBufferLength = 6; - Stream_Write(output, tty->chars, 6); - break; - - case IOCTL_SERIAL_SET_CHARS: - Stream_Read(input, tty->chars, 6); - tty_set_termios(tty); - break; - - case IOCTL_SERIAL_GET_HANDFLOW: - OutputBufferLength = 16; - tty_get_termios(tty); - Stream_Write_UINT32(output, tty->control); - Stream_Write_UINT32(output, tty->xonoff); - Stream_Write_UINT32(output, tty->onlimit); - Stream_Write_UINT32(output, tty->offlimit); - break; - - case IOCTL_SERIAL_SET_HANDFLOW: - Stream_Read_UINT32(input, tty->control); - Stream_Read_UINT32(input, tty->xonoff); - Stream_Read_UINT32(input, tty->onlimit); - Stream_Read_UINT32(input, tty->offlimit); - tty_set_termios(tty); - break; - - case IOCTL_SERIAL_SET_TIMEOUTS: - Stream_Read_UINT32(input, tty->read_interval_timeout); - Stream_Read_UINT32(input, tty->read_total_timeout_multiplier); - Stream_Read_UINT32(input, tty->read_total_timeout_constant); - Stream_Read_UINT32(input, tty->write_total_timeout_multiplier); - Stream_Read_UINT32(input, tty->write_total_timeout_constant); - - /* http://www.codeproject.com/KB/system/chaiyasit_t.aspx, see 'ReadIntervalTimeout' section - http://msdn.microsoft.com/en-us/library/ms885171.aspx */ - if (tty->read_interval_timeout == SERIAL_TIMEOUT_MAX) - { - tty->read_interval_timeout = 0; - tty->read_total_timeout_multiplier = 0; - } - - break; - - case IOCTL_SERIAL_GET_TIMEOUTS: - OutputBufferLength = 20; - Stream_Write_UINT32(output, tty->read_interval_timeout); - Stream_Write_UINT32(output, tty->read_total_timeout_multiplier); - Stream_Write_UINT32(output, tty->read_total_timeout_constant); - Stream_Write_UINT32(output, tty->write_total_timeout_multiplier); - Stream_Write_UINT32(output, tty->write_total_timeout_constant); - break; - - case IOCTL_SERIAL_GET_WAIT_MASK: - OutputBufferLength = 4; - Stream_Write_UINT32(output, tty->wait_mask); - break; - - case IOCTL_SERIAL_SET_WAIT_MASK: - Stream_Read_UINT32(input, tty->wait_mask); - break; - - case IOCTL_SERIAL_SET_DTR: - ioctl(tty->fd, TIOCMGET, &result); - result |= TIOCM_DTR; - ioctl(tty->fd, TIOCMSET, &result); - tty->dtr = 1; - break; - - case IOCTL_SERIAL_CLR_DTR: - ioctl(tty->fd, TIOCMGET, &result); - result &= ~TIOCM_DTR; - ioctl(tty->fd, TIOCMSET, &result); - tty->dtr = 0; - break; - - case IOCTL_SERIAL_SET_RTS: - ioctl(tty->fd, TIOCMGET, &result); - result |= TIOCM_RTS; - ioctl(tty->fd, TIOCMSET, &result); - tty->rts = 1; - break; - - case IOCTL_SERIAL_CLR_RTS: - ioctl(tty->fd, TIOCMGET, &result); - result &= ~TIOCM_RTS; - ioctl(tty->fd, TIOCMSET, &result); - tty->rts = 0; - break; - - case IOCTL_SERIAL_GET_MODEMSTATUS: - modemstate = 0; -#ifdef TIOCMGET - ioctl(tty->fd, TIOCMGET, &result); - if (result & TIOCM_CTS) - modemstate |= SERIAL_MS_CTS; - if (result & TIOCM_DSR) - modemstate |= SERIAL_MS_DSR; - if (result & TIOCM_RNG) - modemstate |= SERIAL_MS_RNG; - if (result & TIOCM_CAR) - modemstate |= SERIAL_MS_CAR; - if (result & TIOCM_DTR) - modemstate |= SERIAL_MS_DTR; - if (result & TIOCM_RTS) - modemstate |= SERIAL_MS_RTS; -#endif - OutputBufferLength = 4; - Stream_Write_UINT32(output, modemstate); - break; - - case IOCTL_SERIAL_GET_COMMSTATUS: - OutputBufferLength = 18; - Stream_Write_UINT32(output, 0); /* Errors */ - Stream_Write_UINT32(output, 0); /* Hold reasons */ - - result = 0; -#ifdef TIOCINQ - ioctl(tty->fd, TIOCINQ, &result); -#endif - Stream_Write_UINT32(output, result); /* Amount in in queue */ - - result = 0; -#ifdef TIOCOUTQ - ioctl(tty->fd, TIOCOUTQ, &result); -#endif - Stream_Write_UINT32(output, result); /* Amount in out queue */ - Stream_Write_UINT8(output, 0); /* EofReceived */ - Stream_Write_UINT8(output, 0); /* WaitForImmediate */ - break; - - case IOCTL_SERIAL_PURGE: - Stream_Read_UINT32(input, purge_mask); - - /* See http://msdn.microsoft.com/en-us/library/ms901431.aspx - PURGE_TXCLEAR Clears the output buffer, if the driver has one. - PURGE_RXCLEAR Clears the input buffer, if the driver has one. - - It clearly states to clear the *driver* buffer, not the port buffer - */ - - if (purge_mask & SERIAL_PURGE_TXABORT) - *abortIo |= SERIAL_ABORT_IO_WRITE; - if (purge_mask & SERIAL_PURGE_RXABORT) - *abortIo |= SERIAL_ABORT_IO_READ; - break; - case IOCTL_SERIAL_WAIT_ON_MASK: - tty->event_pending = 1; - OutputBufferLength = 4; - if (serial_tty_get_event(tty, &result)) - { - Stream_Write_UINT32(output, result); - break; - } - status = STATUS_PENDING; - break; - - case IOCTL_SERIAL_SET_BREAK_ON: - tcsendbreak(tty->fd, 0); - break; - - case IOCTL_SERIAL_RESET_DEVICE: - break; - - case IOCTL_SERIAL_SET_BREAK_OFF: - break; - - case IOCTL_SERIAL_SET_XOFF: - break; - - case IOCTL_SERIAL_SET_XON: - tcflow(tty->fd, TCION); - break; - - default: - return STATUS_INVALID_PARAMETER; - } - - endPos = (UINT32) Stream_GetPosition(output); - OutputBufferLength = endPos - begPos; - - if (OutputBufferLength < 1) - { - Stream_Write_UINT8(output, 0); /* Padding (1 byte) */ - endPos = (UINT32) Stream_GetPosition(output); - OutputBufferLength = endPos - begPos; - } - - Stream_SealLength(output); - - Stream_SetPosition(output, 16); - Stream_Write_UINT32(output, OutputBufferLength); /* OutputBufferLength (4 bytes) */ - Stream_SetPosition(output, endPos); - - return status; -} - -BOOL serial_tty_read(SERIAL_TTY* tty, BYTE* buffer, UINT32* Length) -{ - ssize_t status; - long timeout = 90; - - /* Set timeouts kind of like the windows serial timeout parameters. Multiply timeout - with requested read size */ - if (tty->read_total_timeout_multiplier | tty->read_total_timeout_constant) - { - timeout = - (tty->read_total_timeout_multiplier * (*Length) + - tty->read_total_timeout_constant + 99) / 100; - } - else if (tty->read_interval_timeout) - { - timeout = (tty->read_interval_timeout * (*Length) + 99) / 100; - } - - if (tty->timeout != timeout) - { - struct termios* ptermios; - - ptermios = (struct termios*) calloc(1, sizeof(struct termios)); - - if (tcgetattr(tty->fd, ptermios) < 0) { - free(ptermios); - return FALSE; - } - - /** - * If a timeout is set, do a blocking read, which times out after some time. - * It will make FreeRDP less responsive, but it will improve serial performance, - * by not reading one character at a time. - */ - - if (timeout == 0) - { - ptermios->c_cc[VTIME] = 0; - ptermios->c_cc[VMIN] = 0; - } - else - { - ptermios->c_cc[VTIME] = timeout; - ptermios->c_cc[VMIN] = 1; - } - - tcsetattr(tty->fd, TCSANOW, ptermios); - tty->timeout = timeout; - free(ptermios); - } - - ZeroMemory(buffer, *Length); - - status = read(tty->fd, buffer, *Length); - - if (status < 0) - return FALSE; - - tty->event_txempty = status; - *Length = status; - - return TRUE; -} - -int serial_tty_write(SERIAL_TTY* tty, BYTE* buffer, UINT32 Length) -{ - ssize_t status = 0; - UINT32 event_txempty = Length; - - while (Length > 0) - { - status = write(tty->fd, buffer, Length); - - if (status < 0) - { - if (errno == EAGAIN) - status = 0; - else - return status; - } - - Length -= status; - buffer += status; - } - - tty->event_txempty = event_txempty; - - return status; -} - -/** - * This function is used to deallocated a SERIAL_TTY structure. - * - * @param tty [in] - pointer to the SERIAL_TTY structure to deallocate. - * This will typically be allocated by a call to serial_tty_new(). - * On return, this pointer is invalid. - */ -void serial_tty_free(SERIAL_TTY* tty) -{ - if (!tty) - return; - - if (tty->fd >= 0) - { - if (tty->pold_termios) - tcsetattr(tty->fd, TCSANOW, tty->pold_termios); - - close(tty->fd); - } - - free(tty->ptermios); - free(tty->pold_termios); - free(tty); -} - -SERIAL_TTY* serial_tty_new(const char* path, UINT32 id) -{ - SERIAL_TTY* tty; - - tty = (SERIAL_TTY*) calloc(1, sizeof(SERIAL_TTY)); - - if (!tty) - return NULL; - - tty->id = id; - tty->fd = open(path, O_RDWR | O_NOCTTY | O_NONBLOCK); - - if (tty->fd < 0) - { - perror("open"); - serial_tty_free(tty); - return NULL; - } - else - { - - } - - tty->ptermios = (struct termios*) calloc(1, sizeof(struct termios)); - - if (!tty->ptermios) - { - serial_tty_free(tty); - return NULL; - } - - tty->pold_termios = (struct termios*) calloc(1, sizeof(struct termios)); - - if (!tty->pold_termios) - { - serial_tty_free(tty); - return NULL; - } - tcgetattr(tty->fd, tty->pold_termios); - - if (!tty_get_termios(tty)) - { - fflush(stdout); - serial_tty_free(tty); - return NULL; - } - - tty->ptermios->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON); - tty->ptermios->c_oflag &= ~OPOST; - tty->ptermios->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN); - tty->ptermios->c_cflag &= ~(CSIZE | PARENB); - tty->ptermios->c_cflag |= CS8; - - tty->ptermios->c_iflag = IGNPAR; - tty->ptermios->c_cflag |= CLOCAL | CREAD; - - tcsetattr(tty->fd, TCSANOW, tty->ptermios); - - tty->event_txempty = 0; - tty->event_cts = 0; - tty->event_dsr = 0; - tty->event_rlsd = 0; - tty->event_pending = 0; - - /* all read and writes should be non-blocking */ - if (fcntl(tty->fd, F_SETFL, O_NONBLOCK) == -1) - { - perror("fcntl"); - serial_tty_free(tty) ; - return NULL; - } - - tty->read_total_timeout_constant = 5; - - return tty; -} - -BOOL serial_tty_get_event(SERIAL_TTY* tty, UINT32* result) -{ - int bytes; - BOOL status = FALSE; - - *result = 0; - -#ifdef TIOCINQ - /* When wait_mask is set to zero we ought to cancel it all - For reference: http://msdn.microsoft.com/en-us/library/aa910487.aspx */ - if (tty->wait_mask == 0) - { - tty->event_pending = 0; - return TRUE; - } - - ioctl(tty->fd, TIOCINQ, &bytes); - - if (bytes > 0) - { - if (bytes > tty->event_rlsd) - { - tty->event_rlsd = bytes; - - if (tty->wait_mask & SERIAL_EV_RLSD) - { - *result |= SERIAL_EV_RLSD; - status = TRUE; - } - } - - if ((bytes > 1) && (tty->wait_mask & SERIAL_EV_RXFLAG)) - { - *result |= SERIAL_EV_RXFLAG; - status = TRUE; - } - - if ((tty->wait_mask & SERIAL_EV_RXCHAR)) - { - *result |= SERIAL_EV_RXCHAR; - status = TRUE; - } - } - else - { - tty->event_rlsd = 0; - } -#endif - -#ifdef TIOCOUTQ - ioctl(tty->fd, TIOCOUTQ, &bytes); - if ((bytes == 0) && (tty->event_txempty > 0) && (tty->wait_mask & SERIAL_EV_TXEMPTY)) - { - *result |= SERIAL_EV_TXEMPTY; - status = TRUE; - } - tty->event_txempty = bytes; -#endif - - ioctl(tty->fd, TIOCMGET, &bytes); - if ((bytes & TIOCM_DSR) != tty->event_dsr) - { - tty->event_dsr = bytes & TIOCM_DSR; - - if (tty->wait_mask & SERIAL_EV_DSR) - { - *result |= SERIAL_EV_DSR; - status = TRUE; - } - } - - if ((bytes & TIOCM_CTS) != tty->event_cts) - { - tty->event_cts = bytes & TIOCM_CTS; - - if (tty->wait_mask & SERIAL_EV_CTS) - { - *result |= SERIAL_EV_CTS; - status = TRUE; - } - } - - if (status) - tty->event_pending = 0; - - return status; -} - -static BOOL tty_get_termios(SERIAL_TTY* tty) -{ - speed_t speed; - struct termios* ptermios; - ptermios = tty->ptermios; - - if (tcgetattr(tty->fd, ptermios) < 0) - return FALSE; - - speed = cfgetispeed(ptermios); - - switch (speed) - { -#ifdef B75 - case B75: - tty->baud_rate = 75; - break; -#endif -#ifdef B110 - case B110: - tty->baud_rate = 110; - break; -#endif -#ifdef B134 - case B134: - tty->baud_rate = 134; - break; -#endif -#ifdef B150 - case B150: - tty->baud_rate = 150; - break; -#endif -#ifdef B300 - case B300: - tty->baud_rate = 300; - break; -#endif -#ifdef B600 - case B600: - tty->baud_rate = 600; - break; -#endif -#ifdef B1200 - case B1200: - tty->baud_rate = 1200; - break; -#endif -#ifdef B1800 - case B1800: - tty->baud_rate = 1800; - break; -#endif -#ifdef B2400 - case B2400: - tty->baud_rate = 2400; - break; -#endif -#ifdef B4800 - case B4800: - tty->baud_rate = 4800; - break; -#endif -#ifdef B9600 - case B9600: - tty->baud_rate = 9600; - break; -#endif -#ifdef B19200 - case B19200: - tty->baud_rate = 19200; - break; -#endif -#ifdef B38400 - case B38400: - tty->baud_rate = 38400; - break; -#endif -#ifdef B57600 - case B57600: - tty->baud_rate = 57600; - break; -#endif -#ifdef B115200 - case B115200: - tty->baud_rate = 115200; - break; -#endif -#ifdef B230400 - case B230400: - tty->baud_rate = 230400; - break; -#endif -#ifdef B460800 - case B460800: - tty->baud_rate = 460800; - break; -#endif - default: - tty->baud_rate = 9600; - break; - } - - speed = cfgetospeed(ptermios); - tty->dtr = (speed == B0) ? 0 : 1; - - tty->stop_bits = (ptermios->c_cflag & CSTOPB) ? SERIAL_STOP_BITS_2 : SERIAL_STOP_BITS_1; - tty->parity = - (ptermios->c_cflag & PARENB) ? ((ptermios->c_cflag & PARODD) ? SERIAL_ODD_PARITY : - SERIAL_EVEN_PARITY) : SERIAL_NO_PARITY; - switch (ptermios->c_cflag & CSIZE) - { - case CS5: - tty->word_length = 5; - break; - case CS6: - tty->word_length = 6; - break; - case CS7: - tty->word_length = 7; - break; - default: - tty->word_length = 8; - break; - } - - if (ptermios->c_cflag & CRTSCTS) - { - tty->control = SERIAL_DTR_CONTROL | SERIAL_CTS_HANDSHAKE | SERIAL_ERROR_ABORT; - } - else - { - tty->control = SERIAL_DTR_CONTROL | SERIAL_ERROR_ABORT; - } - - tty->xonoff = SERIAL_DSR_SENSITIVITY; - - if (ptermios->c_iflag & IXON) - tty->xonoff |= SERIAL_XON_HANDSHAKE; - - if (ptermios->c_iflag & IXOFF) - tty->xonoff |= SERIAL_XOFF_HANDSHAKE; - - tty->chars[SERIAL_CHAR_XON] = ptermios->c_cc[VSTART]; - tty->chars[SERIAL_CHAR_XOFF] = ptermios->c_cc[VSTOP]; - tty->chars[SERIAL_CHAR_EOF] = ptermios->c_cc[VEOF]; - tty->chars[SERIAL_CHAR_BREAK] = ptermios->c_cc[VINTR]; - tty->chars[SERIAL_CHAR_ERROR] = ptermios->c_cc[VKILL]; - - tty->timeout = ptermios->c_cc[VTIME]; - - return TRUE; -} - -static void tty_set_termios(SERIAL_TTY* tty) -{ - speed_t speed; - struct termios* ptermios; - - ptermios = tty->ptermios; - - switch (tty->baud_rate) - { -#ifdef B75 - case 75: - speed = B75; - break; -#endif -#ifdef B110 - case 110: - speed = B110; - break; -#endif -#ifdef B134 - case 134: - speed = B134; - break; -#endif -#ifdef B150 - case 150: - speed = B150; - break; -#endif -#ifdef B300 - case 300: - speed = B300; - break; -#endif -#ifdef B600 - case 600: - speed = B600; - break; -#endif -#ifdef B1200 - case 1200: - speed = B1200; - break; -#endif -#ifdef B1800 - case 1800: - speed = B1800; - break; -#endif -#ifdef B2400 - case 2400: - speed = B2400; - break; -#endif -#ifdef B4800 - case 4800: - speed = B4800; - break; -#endif -#ifdef B9600 - case 9600: - speed = B9600; - break; -#endif -#ifdef B19200 - case 19200: - speed = B19200; - break; -#endif -#ifdef B38400 - case 38400: - speed = B38400; - break; -#endif -#ifdef B57600 - case 57600: - speed = B57600; - break; -#endif -#ifdef B115200 - case 115200: - speed = B115200; - break; -#endif -#ifdef B230400 - case 230400: - speed = B115200; - break; -#endif -#ifdef B460800 - case 460800: - speed = B115200; - break; -#endif - default: - speed = B9600; - break; - } - -#ifdef CBAUD - ptermios->c_cflag &= ~CBAUD; - ptermios->c_cflag |= speed; -#else - /* on systems with separate ispeed and ospeed, we can remember the speed - in ispeed while changing DTR with ospeed */ - cfsetispeed(tty->ptermios, speed); - cfsetospeed(tty->ptermios, tty->dtr ? speed : 0); -#endif - - ptermios->c_cflag &= ~(CSTOPB | PARENB | PARODD | CSIZE | CRTSCTS); - - switch (tty->stop_bits) - { - case SERIAL_STOP_BITS_2: - ptermios->c_cflag |= CSTOPB; - break; - - default: - ptermios->c_cflag &= ~CSTOPB; - break; - } - - switch (tty->parity) - { - case SERIAL_EVEN_PARITY: - ptermios->c_cflag |= PARENB; - break; - - case SERIAL_ODD_PARITY: - ptermios->c_cflag |= PARENB | PARODD; - break; - - case SERIAL_NO_PARITY: - ptermios->c_cflag &= ~(PARENB | PARODD); - break; - } - - switch (tty->word_length) - { - case 5: - ptermios->c_cflag |= CS5; - break; - case 6: - ptermios->c_cflag |= CS6; - break; - case 7: - ptermios->c_cflag |= CS7; - break; - default: - ptermios->c_cflag |= CS8; - break; - } - -#if 0 - if (tty->rts) - ptermios->c_cflag |= CRTSCTS; - else - ptermios->c_cflag &= ~CRTSCTS; -#endif - - if (tty->control & SERIAL_CTS_HANDSHAKE) - { - ptermios->c_cflag |= CRTSCTS; - } - else - { - ptermios->c_cflag &= ~CRTSCTS; - } - - - if (tty->xonoff & SERIAL_XON_HANDSHAKE) - { - ptermios->c_iflag |= IXON | IMAXBEL; - } - if (tty->xonoff & SERIAL_XOFF_HANDSHAKE) - { - ptermios->c_iflag |= IXOFF | IMAXBEL; - } - - if ((tty->xonoff & (SERIAL_XOFF_HANDSHAKE | SERIAL_XON_HANDSHAKE)) == 0) - { - ptermios->c_iflag &= ~IXON; - ptermios->c_iflag &= ~IXOFF; - } - - ptermios->c_cc[VSTART] = tty->chars[SERIAL_CHAR_XON]; - ptermios->c_cc[VSTOP] = tty->chars[SERIAL_CHAR_XOFF]; - ptermios->c_cc[VEOF] = tty->chars[SERIAL_CHAR_EOF]; - ptermios->c_cc[VINTR] = tty->chars[SERIAL_CHAR_BREAK]; - ptermios->c_cc[VKILL] = tty->chars[SERIAL_CHAR_ERROR]; - - tcsetattr(tty->fd, TCSANOW, ptermios); -} - -static UINT32 tty_write_data(SERIAL_TTY* tty, BYTE* data, int len) -{ - ssize_t status; - - status = write(tty->fd, data, len); - - if (status < 0) - return tty_get_error_status(); - - tty->event_txempty = status; - - return STATUS_SUCCESS; -} - -static int tty_get_error_status() -{ - switch (errno) - { - case EACCES: - case ENOTDIR: - case ENFILE: - return STATUS_ACCESS_DENIED; - case EISDIR: - return STATUS_FILE_IS_A_DIRECTORY; - case EEXIST: - return STATUS_OBJECT_NAME_COLLISION; - case EBADF: - return STATUS_INVALID_HANDLE; - default: - return STATUS_NO_SUCH_FILE; - } -} diff --git a/channels/serial/client/serial_tty.h b/channels/serial/client/serial_tty.h deleted file mode 100644 index 840c448ec..000000000 --- a/channels/serial/client/serial_tty.h +++ /dev/null @@ -1,80 +0,0 @@ -/** - * FreeRDP: A Remote Desktop Protocol Implementation - * Serial Port Device Service Virtual Channel - * - * Copyright 2011 O.S. Systems Software Ltda. - * Copyright 2011 Eduardo Fiss Beloni - * - * 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 __SERIAL_TTY_H -#define __SERIAL_TTY_H - -#include -#include - -#ifndef _WIN32 -#include -#endif - -#include - -#include - -typedef struct _SERIAL_TTY SERIAL_TTY; - -struct _SERIAL_TTY -{ - UINT32 id; - int fd; - - int dtr; - int rts; - UINT32 control; - UINT32 xonoff; - UINT32 onlimit; - UINT32 offlimit; - UINT32 baud_rate; - UINT32 queue_in_size; - UINT32 queue_out_size; - UINT32 wait_mask; - UINT32 read_interval_timeout; - UINT32 read_total_timeout_multiplier; - UINT32 read_total_timeout_constant; - UINT32 write_total_timeout_multiplier; - UINT32 write_total_timeout_constant; - BYTE stop_bits; - BYTE parity; - BYTE word_length; - BYTE chars[6]; - struct termios* ptermios; - struct termios* pold_termios; - int event_txempty; - int event_cts; - int event_dsr; - int event_rlsd; - int event_pending; - long timeout; -}; - -SERIAL_TTY* serial_tty_new(const char* path, UINT32 id); -void serial_tty_free(SERIAL_TTY* tty); - -BOOL serial_tty_read(SERIAL_TTY* tty, BYTE* buffer, UINT32* Length); -int serial_tty_write(SERIAL_TTY* tty, BYTE* buffer, UINT32 Length); -UINT32 serial_tty_control(SERIAL_TTY* tty, UINT32 IoControlCode, wStream* input, wStream* output, UINT32* abort_io); - -BOOL serial_tty_get_event(SERIAL_TTY* tty, UINT32* result); - -#endif /* __SERIAL_TTY_H */ diff --git a/client/common/cmdline.c b/client/common/cmdline.c index 34bbd0b6b..f80ea1841 100644 --- a/client/common/cmdline.c +++ b/client/common/cmdline.c @@ -400,6 +400,9 @@ int freerdp_client_add_device_channel(rdpSettings* settings, int count, char** p if (count > 2) serial->Path = _strdup(params[2]); + if (count > 3) + serial->Driver = _strdup(params[3]); + freerdp_device_collection_add(settings, (RDPDR_DEVICE*) serial); return 1; diff --git a/include/freerdp/settings.h b/include/freerdp/settings.h index cb9e25cb7..b807141f2 100644 --- a/include/freerdp/settings.h +++ b/include/freerdp/settings.h @@ -466,6 +466,7 @@ struct _RDPDR_SERIAL UINT32 Type; char* Name; char* Path; + char* Driver; }; typedef struct _RDPDR_SERIAL RDPDR_SERIAL; diff --git a/libfreerdp/common/settings.c b/libfreerdp/common/settings.c index e04230f37..1cb47932a 100644 --- a/libfreerdp/common/settings.c +++ b/libfreerdp/common/settings.c @@ -287,8 +287,17 @@ out_smartc_name_error: goto out_serial_path_error; } + if (serial->Driver) + { + _serial->Driver = _strdup(serial->Driver); + if (!_serial->Driver) + goto out_serial_driver_error; + } + return (RDPDR_DEVICE*) _serial; +out_serial_driver_error: + free(_serial->Path); out_serial_path_error: free(_serial->Name); out_serial_name_error: @@ -360,6 +369,7 @@ void freerdp_device_collection_free(rdpSettings* settings) else if (settings->DeviceArray[index]->Type == RDPDR_DTYP_SERIAL) { free(((RDPDR_SERIAL*) device)->Path); + free(((RDPDR_SERIAL*) device)->Driver); } else if (settings->DeviceArray[index]->Type == RDPDR_DTYP_PARALLEL) { diff --git a/winpr/include/winpr/comm.h b/winpr/include/winpr/comm.h new file mode 100644 index 000000000..3398906d1 --- /dev/null +++ b/winpr/include/winpr/comm.h @@ -0,0 +1,589 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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_H +#define WINPR_COMM_H + +#include +#include +#include +#include + + +#ifndef _WIN32 + +#define NOPARITY 0 +#define ODDPARITY 1 +#define EVENPARITY 2 +#define MARKPARITY 3 +#define SPACEPARITY 4 + +#define ONESTOPBIT 0 +#define ONE5STOPBITS 1 +#define TWOSTOPBITS 2 + +#ifndef IGNORE +#define IGNORE 0 +#endif + +#define CBR_110 110 +#define CBR_300 300 +#define CBR_600 600 +#define CBR_1200 1200 +#define CBR_2400 2400 +#define CBR_4800 4800 +#define CBR_9600 9600 +#define CBR_14400 14400 +#define CBR_19200 19200 +#define CBR_38400 38400 +#define CBR_56000 56000 +#define CBR_57600 57600 +#define CBR_115200 115200 +#define CBR_128000 128000 +#define CBR_256000 256000 + +#define CE_RXOVER 0x0001 +#define CE_OVERRUN 0x0002 +#define CE_RXPARITY 0x0004 +#define CE_FRAME 0x0008 +#define CE_BREAK 0x0010 +#define CE_TXFULL 0x0100 +#define CE_PTO 0x0200 +#define CE_IOE 0x0400 +#define CE_DNS 0x0800 +#define CE_OOP 0x1000 +#define CE_MODE 0x8000 + +#define IE_BADID (-1) +#define IE_OPEN (-2) +#define IE_NOPEN (-3) +#define IE_MEMORY (-4) +#define IE_DEFAULT (-5) +#define IE_HARDWARE (-10) +#define IE_BYTESIZE (-11) +#define IE_BAUDRATE (-12) + +#define EV_RXCHAR 0x0001 +#define EV_RXFLAG 0x0002 +#define EV_TXEMPTY 0x0004 +#define EV_CTS 0x0008 +#define EV_DSR 0x0010 +#define EV_RLSD 0x0020 +#define EV_BREAK 0x0040 +#define EV_ERR 0x0080 +#define EV_RING 0x0100 +#define EV_PERR 0x0200 +#define EV_RX80FULL 0x0400 +#define EV_EVENT1 0x0800 +#define EV_EVENT2 0x1000 + +#define SETXOFF 1 +#define SETXON 2 +#define SETRTS 3 +#define CLRRTS 4 +#define SETDTR 5 +#define CLRDTR 6 +#define RESETDEV 7 +#define SETBREAK 8 +#define CLRBREAK 9 + +#define PURGE_TXABORT 0x0001 +#define PURGE_RXABORT 0x0002 +#define PURGE_TXCLEAR 0x0004 +#define PURGE_RXCLEAR 0x0008 + +#define LPTx 0x80 + +#define MS_CTS_ON ((DWORD)0x0010) +#define MS_DSR_ON ((DWORD)0x0020) +#define MS_RING_ON ((DWORD)0x0040) +#define MS_RLSD_ON ((DWORD)0x0080) + +#define SP_SERIALCOMM ((DWORD)0x00000001) + +#define PST_UNSPECIFIED ((DWORD)0x00000000) +#define PST_RS232 ((DWORD)0x00000001) +#define PST_PARALLELPORT ((DWORD)0x00000002) +#define PST_RS422 ((DWORD)0x00000003) +#define PST_RS423 ((DWORD)0x00000004) +#define PST_RS449 ((DWORD)0x00000005) +#define PST_MODEM ((DWORD)0x00000006) +#define PST_FAX ((DWORD)0x00000021) +#define PST_SCANNER ((DWORD)0x00000022) +#define PST_NETWORK_BRIDGE ((DWORD)0x00000100) +#define PST_LAT ((DWORD)0x00000101) +#define PST_TCPIP_TELNET ((DWORD)0x00000102) +#define PST_X25 ((DWORD)0x00000103) + +#define PCF_DTRDSR ((DWORD)0x0001) +#define PCF_RTSCTS ((DWORD)0x0002) +#define PCF_RLSD ((DWORD)0x0004) +#define PCF_PARITY_CHECK ((DWORD)0x0008) +#define PCF_XONXOFF ((DWORD)0x0010) +#define PCF_SETXCHAR ((DWORD)0x0020) +#define PCF_TOTALTIMEOUTS ((DWORD)0x0040) +#define PCF_INTTIMEOUTS ((DWORD)0x0080) +#define PCF_SPECIALCHARS ((DWORD)0x0100) +#define PCF_16BITMODE ((DWORD)0x0200) + +#define SP_PARITY ((DWORD)0x0001) +#define SP_BAUD ((DWORD)0x0002) +#define SP_DATABITS ((DWORD)0x0004) +#define SP_STOPBITS ((DWORD)0x0008) +#define SP_HANDSHAKING ((DWORD)0x0010) +#define SP_PARITY_CHECK ((DWORD)0x0020) +#define SP_RLSD ((DWORD)0x0040) + +#define BAUD_075 ((DWORD)0x00000001) +#define BAUD_110 ((DWORD)0x00000002) +#define BAUD_134_5 ((DWORD)0x00000004) +#define BAUD_150 ((DWORD)0x00000008) +#define BAUD_300 ((DWORD)0x00000010) +#define BAUD_600 ((DWORD)0x00000020) +#define BAUD_1200 ((DWORD)0x00000040) +#define BAUD_1800 ((DWORD)0x00000080) +#define BAUD_2400 ((DWORD)0x00000100) +#define BAUD_4800 ((DWORD)0x00000200) +#define BAUD_7200 ((DWORD)0x00000400) +#define BAUD_9600 ((DWORD)0x00000800) +#define BAUD_14400 ((DWORD)0x00001000) +#define BAUD_19200 ((DWORD)0x00002000) +#define BAUD_38400 ((DWORD)0x00004000) +#define BAUD_56K ((DWORD)0x00008000) +#define BAUD_128K ((DWORD)0x00010000) +#define BAUD_115200 ((DWORD)0x00020000) +#define BAUD_57600 ((DWORD)0x00040000) +#define BAUD_USER ((DWORD)0x10000000) + +/* Ntddser.h: http://msdn.microsoft.com/en-us/cc308432.aspx */ +#define SERIAL_BAUD_075 ((ULONG)0x00000001) +#define SERIAL_BAUD_110 ((ULONG)0x00000002) +#define SERIAL_BAUD_134_5 ((ULONG)0x00000004) +#define SERIAL_BAUD_150 ((ULONG)0x00000008) +#define SERIAL_BAUD_300 ((ULONG)0x00000010) +#define SERIAL_BAUD_600 ((ULONG)0x00000020) +#define SERIAL_BAUD_1200 ((ULONG)0x00000040) +#define SERIAL_BAUD_1800 ((ULONG)0x00000080) +#define SERIAL_BAUD_2400 ((ULONG)0x00000100) +#define SERIAL_BAUD_4800 ((ULONG)0x00000200) +#define SERIAL_BAUD_7200 ((ULONG)0x00000400) +#define SERIAL_BAUD_9600 ((ULONG)0x00000800) +#define SERIAL_BAUD_14400 ((ULONG)0x00001000) +#define SERIAL_BAUD_19200 ((ULONG)0x00002000) +#define SERIAL_BAUD_38400 ((ULONG)0x00004000) +#define SERIAL_BAUD_56K ((ULONG)0x00008000) +#define SERIAL_BAUD_128K ((ULONG)0x00010000) +#define SERIAL_BAUD_115200 ((ULONG)0x00020000) +#define SERIAL_BAUD_57600 ((ULONG)0x00040000) +#define SERIAL_BAUD_USER ((ULONG)0x10000000) + + +#define DATABITS_5 ((WORD)0x0001) +#define DATABITS_6 ((WORD)0x0002) +#define DATABITS_7 ((WORD)0x0004) +#define DATABITS_8 ((WORD)0x0008) +#define DATABITS_16 ((WORD)0x0010) +#define DATABITS_16X ((WORD)0x0020) + +#define STOPBITS_10 ((WORD)0x0001) +#define STOPBITS_15 ((WORD)0x0002) +#define STOPBITS_20 ((WORD)0x0004) + +#define PARITY_NONE ((WORD)0x0100) +#define PARITY_ODD ((WORD)0x0200) +#define PARITY_EVEN ((WORD)0x0400) +#define PARITY_MARK ((WORD)0x0800) +#define PARITY_SPACE ((WORD)0x1000) + +#define COMMPROP_INITIALIZED ((DWORD)0xE73CF52E) + +#define DTR_CONTROL_DISABLE 0x00 +#define DTR_CONTROL_ENABLE 0x01 +#define DTR_CONTROL_HANDSHAKE 0x02 + +#define RTS_CONTROL_DISABLE 0x00 +#define RTS_CONTROL_ENABLE 0x01 +#define RTS_CONTROL_HANDSHAKE 0x02 +#define RTS_CONTROL_TOGGLE 0x03 + +// http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx +typedef struct _DCB +{ + DWORD DCBlength; + DWORD BaudRate; + DWORD fBinary:1; + DWORD fParity:1; + DWORD fOutxCtsFlow:1; + DWORD fOutxDsrFlow:1; + DWORD fDtrControl:2; + DWORD fDsrSensitivity:1; + DWORD fTXContinueOnXoff:1; + DWORD fOutX:1; + DWORD fInX:1; + DWORD fErrorChar:1; + DWORD fNull:1; + DWORD fRtsControl:2; + DWORD fAbortOnError:1; + DWORD fDummy2:17; + WORD wReserved; + WORD XonLim; + WORD XoffLim; + BYTE ByteSize; + BYTE Parity; + BYTE StopBits; + char XonChar; + char XoffChar; + char ErrorChar; + char EofChar; + char EvtChar; + WORD wReserved1; +} DCB, *LPDCB; + +typedef struct _COMM_CONFIG +{ + DWORD dwSize; + WORD wVersion; + WORD wReserved; + DCB dcb; + DWORD dwProviderSubType; + DWORD dwProviderOffset; + DWORD dwProviderSize; + WCHAR wcProviderData[1]; +} COMMCONFIG, *LPCOMMCONFIG; + +typedef struct _COMMPROP +{ + WORD wPacketLength; + WORD wPacketVersion; + DWORD dwServiceMask; + DWORD dwReserved1; + DWORD dwMaxTxQueue; + DWORD dwMaxRxQueue; + DWORD dwMaxBaud; + DWORD dwProvSubType; + DWORD dwProvCapabilities; + DWORD dwSettableParams; + DWORD dwSettableBaud; + WORD wSettableData; + WORD wSettableStopParity; + DWORD dwCurrentTxQueue; + DWORD dwCurrentRxQueue; + DWORD dwProvSpec1; + DWORD dwProvSpec2; + WCHAR wcProvChar[1]; +} COMMPROP, *LPCOMMPROP; + +typedef struct _COMMTIMEOUTS +{ + DWORD ReadIntervalTimeout; + DWORD ReadTotalTimeoutMultiplier; + DWORD ReadTotalTimeoutConstant; + DWORD WriteTotalTimeoutMultiplier; + DWORD WriteTotalTimeoutConstant; +} COMMTIMEOUTS, *LPCOMMTIMEOUTS; + +typedef struct _COMSTAT +{ + DWORD fCtsHold:1; + DWORD fDsrHold:1; + DWORD fRlsdHold:1; + DWORD fXoffHold:1; + DWORD fXoffSent:1; + DWORD fEof:1; + DWORD fTxim:1; + DWORD fReserved:25; + DWORD cbInQue; + DWORD cbOutQue; +} COMSTAT, *LPCOMSTAT; + +#ifdef __cplusplus +extern "C" { +#endif + +WINPR_API BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB); +WINPR_API BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB); + +WINPR_API BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts); +WINPR_API BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts); + +WINPR_API BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC); +WINPR_API BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC); + +WINPR_API BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize); +WINPR_API BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize); + +WINPR_API BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask); +WINPR_API BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask); + +WINPR_API BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat); +WINPR_API BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp); + +WINPR_API BOOL GetCommState(HANDLE hFile, LPDCB lpDCB); +WINPR_API BOOL SetCommState(HANDLE hFile, LPDCB lpDCB); + +WINPR_API BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts); +WINPR_API BOOL SetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts); + +WINPR_API BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize); +WINPR_API BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize); + +WINPR_API BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize); +WINPR_API BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize); + +WINPR_API BOOL SetCommBreak(HANDLE hFile); +WINPR_API BOOL ClearCommBreak(HANDLE hFile); +WINPR_API BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat); + +WINPR_API BOOL PurgeComm(HANDLE hFile, DWORD dwFlags); +WINPR_API BOOL SetupComm(HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue); + +WINPR_API BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc); + +WINPR_API BOOL TransmitCommChar(HANDLE hFile, char cChar); + +WINPR_API BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped); + +#ifdef UNICODE +#define BuildCommDCB BuildCommDCBW +#define BuildCommDCBAndTimeouts BuildCommDCBAndTimeoutsW +#define CommConfigDialog CommConfigDialogW +#define GetDefaultCommConfig GetDefaultCommConfigW +#define SetDefaultCommConfig SetDefaultCommConfigW +#else +#define BuildCommDCB BuildCommDCBA +#define BuildCommDCBAndTimeouts BuildCommDCBAndTimeoutsA +#define CommConfigDialog CommConfigDialogA +#define GetDefaultCommConfig GetDefaultCommConfigA +#define SetDefaultCommConfig SetDefaultCommConfigA +#endif + +/* Extended API */ + +/* FIXME: MAXULONG should be defined arround winpr/limits.h */ +#ifndef MAXULONG +#define MAXULONG (4294967295UL) +#endif + + +/** + * IOCTLs table according the server's serial driver: + * http://msdn.microsoft.com/en-us/library/windows/hardware/dn265347%28v=vs.85%29.aspx + */ +typedef enum _SERIAL_DRIVER_ID +{ + SerialDriverUnknown = 0, + SerialDriverSerialSys, + SerialDriverSerCxSys, + SerialDriverSerCx2Sys /* default fallback, see also CommDeviceIoControl() */ +} SERIAL_DRIVER_ID; + + +/* + * About DefineCommDevice() / QueryDosDevice() + * + * Did something close to QueryDosDevice() and DefineDosDevice() but with + * folowing constraints: + * - mappings are stored in a static array. + * - QueryCommDevice returns only the mappings that have been defined through DefineCommDevice() + */ +WINPR_API BOOL DefineCommDevice(/* DWORD dwFlags,*/ LPCTSTR lpDeviceName, LPCTSTR lpTargetPath); +WINPR_API DWORD QueryCommDevice(LPCTSTR lpDeviceName, LPTSTR lpTargetPath, DWORD ucchMax); +WINPR_API BOOL IsCommDevice(LPCTSTR lpDeviceName); + +/** + * A handle can only be created on defined devices with DefineCommDevice(). This + * also ensures that CommCreateFileA() has been registered through + * RegisterHandleCreator(). + */ +WINPR_API HANDLE CommCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + + + +#define IOCTL_SERIAL_SET_BAUD_RATE 0x001B0004 +#define IOCTL_SERIAL_GET_BAUD_RATE 0x001B0050 +#define IOCTL_SERIAL_SET_LINE_CONTROL 0x001B000C +#define IOCTL_SERIAL_GET_LINE_CONTROL 0x001B0054 +#define IOCTL_SERIAL_SET_TIMEOUTS 0x001B001C +#define IOCTL_SERIAL_GET_TIMEOUTS 0x001B0020 +/* GET_CHARS and SET_CHARS are swapped in the RDP docs [MS-RDPESP] */ +#define IOCTL_SERIAL_GET_CHARS 0x001B0058 +#define IOCTL_SERIAL_SET_CHARS 0x001B005C + +#define IOCTL_SERIAL_SET_DTR 0x001B0024 +#define IOCTL_SERIAL_CLR_DTR 0x001B0028 +#define IOCTL_SERIAL_RESET_DEVICE 0x001B002C +#define IOCTL_SERIAL_SET_RTS 0x001B0030 +#define IOCTL_SERIAL_CLR_RTS 0x001B0034 +#define IOCTL_SERIAL_SET_XOFF 0x001B0038 +#define IOCTL_SERIAL_SET_XON 0x001B003C +#define IOCTL_SERIAL_SET_BREAK_ON 0x001B0010 +#define IOCTL_SERIAL_SET_BREAK_OFF 0x001B0014 +#define IOCTL_SERIAL_SET_QUEUE_SIZE 0x001B0008 +#define IOCTL_SERIAL_GET_WAIT_MASK 0x001B0040 +#define IOCTL_SERIAL_SET_WAIT_MASK 0x001B0044 +#define IOCTL_SERIAL_WAIT_ON_MASK 0x001B0048 +#define IOCTL_SERIAL_IMMEDIATE_CHAR 0x001B0018 +#define IOCTL_SERIAL_PURGE 0x001B004C +#define IOCTL_SERIAL_GET_HANDFLOW 0x001B0060 +#define IOCTL_SERIAL_SET_HANDFLOW 0x001B0064 +#define IOCTL_SERIAL_GET_MODEMSTATUS 0x001B0068 +#define IOCTL_SERIAL_GET_DTRRTS 0x001B0078 + +/* according to [MS-RDPESP] it should be 0x001B0084, but servers send 0x001B006C */ +#define IOCTL_SERIAL_GET_COMMSTATUS 0x001B006C + +#define IOCTL_SERIAL_GET_PROPERTIES 0x001B0074 +/* IOCTL_SERIAL_XOFF_COUNTER 0x001B0070 */ +/* IOCTL_SERIAL_LSRMST_INSERT 0x001B007C */ +#define IOCTL_SERIAL_CONFIG_SIZE 0x001B0080 +/* IOCTL_SERIAL_GET_STATS 0x001B008C */ +/* IOCTL_SERIAL_CLEAR_STATS 0x001B0090 */ +/* IOCTL_SERIAL_GET_MODEM_CONTROL 0x001B0094 */ +/* IOCTL_SERIAL_SET_MODEM_CONTROL 0x001B0098 */ +/* IOCTL_SERIAL_SET_FIFO_CONTROL 0x001B009C */ + +/* IOCTL_PAR_QUERY_INFORMATION 0x00160004 */ +/* IOCTL_PAR_SET_INFORMATION 0x00160008 */ +/* IOCTL_PAR_QUERY_DEVICE_ID 0x0016000C */ +/* IOCTL_PAR_QUERY_DEVICE_ID_SIZE 0x00160010 */ +/* IOCTL_IEEE1284_GET_MODE 0x00160014 */ +/* IOCTL_IEEE1284_NEGOTIATE 0x00160018 */ +/* IOCTL_PAR_SET_WRITE_ADDRESS 0x0016001C */ +/* IOCTL_PAR_SET_READ_ADDRESS 0x00160020 */ +/* IOCTL_PAR_GET_DEVICE_CAPS 0x00160024 */ +/* IOCTL_PAR_GET_DEFAULT_MODES 0x00160028 */ +/* IOCTL_PAR_QUERY_RAW_DEVICE_ID 0x00160030 */ +/* IOCTL_PAR_IS_PORT_FREE 0x00160054 */ + +/* http://msdn.microsoft.com/en-us/library/windows/hardware/ff551803(v=vs.85).aspx */ +#define IOCTL_USBPRINT_GET_1284_ID 0x220034 + + +typedef struct __SERIAL_IOCTL_NAME +{ + ULONG number; + const char* name; +} _SERIAL_IOCTL_NAME; + +static const _SERIAL_IOCTL_NAME _SERIAL_IOCTL_NAMES[] = +{ + {IOCTL_SERIAL_SET_BAUD_RATE, "IOCTL_SERIAL_SET_BAUD_RATE"}, + {IOCTL_SERIAL_GET_BAUD_RATE, "IOCTL_SERIAL_GET_BAUD_RATE"}, + {IOCTL_SERIAL_SET_LINE_CONTROL, "IOCTL_SERIAL_SET_LINE_CONTROL"}, + {IOCTL_SERIAL_GET_LINE_CONTROL, "IOCTL_SERIAL_GET_LINE_CONTROL"}, + {IOCTL_SERIAL_SET_TIMEOUTS, "IOCTL_SERIAL_SET_TIMEOUTS"}, + {IOCTL_SERIAL_GET_TIMEOUTS, "IOCTL_SERIAL_GET_TIMEOUTS"}, + {IOCTL_SERIAL_GET_CHARS, "IOCTL_SERIAL_GET_CHARS"}, + {IOCTL_SERIAL_SET_CHARS, "IOCTL_SERIAL_SET_CHARS"}, + {IOCTL_SERIAL_SET_DTR, "IOCTL_SERIAL_SET_DTR"}, + {IOCTL_SERIAL_CLR_DTR, "IOCTL_SERIAL_CLR_DTR"}, + {IOCTL_SERIAL_RESET_DEVICE, "IOCTL_SERIAL_RESET_DEVICE"}, + {IOCTL_SERIAL_SET_RTS, "IOCTL_SERIAL_SET_RTS"}, + {IOCTL_SERIAL_CLR_RTS, "IOCTL_SERIAL_CLR_RTS"}, + {IOCTL_SERIAL_SET_XOFF, "IOCTL_SERIAL_SET_XOFF"}, + {IOCTL_SERIAL_SET_XON, "IOCTL_SERIAL_SET_XON"}, + {IOCTL_SERIAL_SET_BREAK_ON, "IOCTL_SERIAL_SET_BREAK_ON"}, + {IOCTL_SERIAL_SET_BREAK_OFF, "IOCTL_SERIAL_SET_BREAK_OFF"}, + {IOCTL_SERIAL_SET_QUEUE_SIZE, "IOCTL_SERIAL_SET_QUEUE_SIZE"}, + {IOCTL_SERIAL_GET_WAIT_MASK, "IOCTL_SERIAL_GET_WAIT_MASK"}, + {IOCTL_SERIAL_SET_WAIT_MASK, "IOCTL_SERIAL_SET_WAIT_MASK"}, + {IOCTL_SERIAL_WAIT_ON_MASK, "IOCTL_SERIAL_WAIT_ON_MASK"}, + {IOCTL_SERIAL_IMMEDIATE_CHAR, "IOCTL_SERIAL_IMMEDIATE_CHAR"}, + {IOCTL_SERIAL_PURGE, "IOCTL_SERIAL_PURGE"}, + {IOCTL_SERIAL_GET_HANDFLOW, "IOCTL_SERIAL_GET_HANDFLOW"}, + {IOCTL_SERIAL_SET_HANDFLOW, "IOCTL_SERIAL_SET_HANDFLOW"}, + {IOCTL_SERIAL_GET_MODEMSTATUS, "IOCTL_SERIAL_GET_MODEMSTATUS"}, + {IOCTL_SERIAL_GET_DTRRTS, "IOCTL_SERIAL_GET_DTRRTS"}, + {IOCTL_SERIAL_GET_COMMSTATUS, "IOCTL_SERIAL_GET_COMMSTATUS"}, + {IOCTL_SERIAL_GET_PROPERTIES, "IOCTL_SERIAL_GET_PROPERTIES"}, + // {IOCTL_SERIAL_XOFF_COUNTER, "IOCTL_SERIAL_XOFF_COUNTER"}, + // {IOCTL_SERIAL_LSRMST_INSERT, "IOCTL_SERIAL_LSRMST_INSERT"}, + {IOCTL_SERIAL_CONFIG_SIZE, "IOCTL_SERIAL_CONFIG_SIZE"}, + // {IOCTL_SERIAL_GET_STATS, "IOCTL_SERIAL_GET_STATS"}, + // {IOCTL_SERIAL_CLEAR_STATS, "IOCTL_SERIAL_CLEAR_STATS"}, + // {IOCTL_SERIAL_GET_MODEM_CONTROL,"IOCTL_SERIAL_GET_MODEM_CONTROL"}, + // {IOCTL_SERIAL_SET_MODEM_CONTROL,"IOCTL_SERIAL_SET_MODEM_CONTROL"}, + // {IOCTL_SERIAL_SET_FIFO_CONTROL, "IOCTL_SERIAL_SET_FIFO_CONTROL"}, + + // {IOCTL_PAR_QUERY_INFORMATION, "IOCTL_PAR_QUERY_INFORMATION"}, + // {IOCTL_PAR_SET_INFORMATION, "IOCTL_PAR_SET_INFORMATION"}, + // {IOCTL_PAR_QUERY_DEVICE_ID, "IOCTL_PAR_QUERY_DEVICE_ID"}, + // {IOCTL_PAR_QUERY_DEVICE_ID_SIZE,"IOCTL_PAR_QUERY_DEVICE_ID_SIZE"}, + // {IOCTL_IEEE1284_GET_MODE, "IOCTL_IEEE1284_GET_MODE"}, + // {IOCTL_IEEE1284_NEGOTIATE, "IOCTL_IEEE1284_NEGOTIATE"}, + // {IOCTL_PAR_SET_WRITE_ADDRESS, "IOCTL_PAR_SET_WRITE_ADDRESS"}, + // {IOCTL_PAR_SET_READ_ADDRESS, "IOCTL_PAR_SET_READ_ADDRESS"}, + // {IOCTL_PAR_GET_DEVICE_CAPS, "IOCTL_PAR_GET_DEVICE_CAPS"}, + // {IOCTL_PAR_GET_DEFAULT_MODES, "IOCTL_PAR_GET_DEFAULT_MODES"}, + // {IOCTL_PAR_QUERY_RAW_DEVICE_ID, "IOCTL_PAR_QUERY_RAW_DEVICE_ID"}, + // {IOCTL_PAR_IS_PORT_FREE, "IOCTL_PAR_IS_PORT_FREE"}, + + {IOCTL_USBPRINT_GET_1284_ID, "IOCTL_USBPRINT_GET_1284_ID"}, + + {0, NULL} +}; + +/** + * FIXME: got a proper function name and place + */ +const char* _comm_serial_ioctl_name(ULONG number); + +/** + * FIXME: got a proper function name and place + */ +void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID); + +/** + * FIXME: got a proper function name and place + * + * permissive mode is disabled by default. + */ +BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive); + + +/** + * FIXME: to be moved in comm_ioctl.h + */ +BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, + LPVOID lpOutBuffer, DWORD nOutBufferSize, LPDWORD lpBytesReturned, LPOVERLAPPED lpOverlapped); + +/** + * FIXME: to be moved in comm_io.h + */ +BOOL CommReadFile(HANDLE hDevice, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, + LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped); + +/** + * FIXME: to be moved in comm_io.h + */ +BOOL CommWriteFile(HANDLE hDevice, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, + LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped); + +#ifdef __cplusplus +} +#endif + +#endif /* _WIN32 */ + +#endif /* WINPR_COMM_H */ + diff --git a/winpr/include/winpr/file.h b/winpr/include/winpr/file.h index 59979b6ce..80f89507f 100644 --- a/winpr/include/winpr/file.h +++ b/winpr/include/winpr/file.h @@ -311,10 +311,23 @@ WINPR_API BOOL CreateDirectoryW(LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecu #define CreateDirectory CreateDirectoryA #endif -#endif /* Extra Functions */ +typedef BOOL (*pcIsFileHandled)(LPCSTR lpFileName); +typedef HANDLE (*pcCreateFileA)(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile); + +typedef struct _HANDLE_CREATOR +{ + pcIsFileHandled IsHandled; + pcCreateFileA CreateFileA; +} HANDLE_CREATOR, *PHANDLE_CREATOR, *LPHANDLE_CREATOR; + +BOOL RegisterHandleCreator(PHANDLE_CREATOR pHandleCreator); + +#endif /* _WIN32 */ + #define WILDCARD_STAR 0x00000001 #define WILDCARD_QM 0x00000002 #define WILDCARD_DOS 0x00000100 @@ -331,6 +344,7 @@ WINPR_API LPSTR FilePatternFindNextWildcardA(LPCSTR lpPattern, DWORD* pFlags); WINPR_API int UnixChangeFileMode(const char* filename, int flags); +WINPR_API BOOL IsNamedPipeFileNameA(LPCSTR lpName); WINPR_API char* GetNamedPipeNameWithoutPrefixA(LPCSTR lpName); WINPR_API char* GetNamedPipeUnixDomainSocketBaseFilePathA(void); WINPR_API char* GetNamedPipeUnixDomainSocketFilePathA(LPCSTR lpName); @@ -342,4 +356,3 @@ WINPR_API int GetNamePipeFileDescriptor(HANDLE hNamedPipe); #endif #endif /* WINPR_FILE_H */ - diff --git a/winpr/include/winpr/tchar.h b/winpr/include/winpr/tchar.h index 7390ed5f3..f3525bc66 100644 --- a/winpr/include/winpr/tchar.h +++ b/winpr/include/winpr/tchar.h @@ -40,6 +40,7 @@ typedef CHAR TCHAR; #define _tcslen _wcslen #define _tcsdup _wcsdup #define _tcscmp wcscmp +#define _tcsncmp wcsncmp #define _tcscpy wcscpy #define _tcscat wcscat #define _tcschr wcschr @@ -51,6 +52,7 @@ typedef CHAR TCHAR; #define _tcslen strlen #define _tcsdup _strdup #define _tcscmp strcmp +#define _tcsncmp strncmp #define _tcscpy strcpy #define _tcscat strcat #define _tcschr strchr diff --git a/winpr/libwinpr/comm/CMakeLists.txt b/winpr/libwinpr/comm/CMakeLists.txt new file mode 100644 index 000000000..c37af7268 --- /dev/null +++ b/winpr/libwinpr/comm/CMakeLists.txt @@ -0,0 +1,61 @@ +# WinPR: Windows Portable Runtime +# libwinpr-comm cmake build script +# +# Copyright 2014 Marc-Andre Moreau +# +# 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") + +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) + +if(MSVC AND (NOT MONOLITHIC_BUILD)) + set(${MODULE_PREFIX}_SRCS ${${MODULE_PREFIX}_SRCS} module.def) +endif() + +add_complex_library(MODULE ${MODULE_NAME} TYPE "OBJECT" + MONOLITHIC ${MONOLITHIC_BUILD} + SOURCES ${${MODULE_PREFIX}_SRCS}) + +set_target_properties(${MODULE_NAME} PROPERTIES VERSION ${WINPR_VERSION_FULL} SOVERSION ${WINPR_VERSION} PREFIX "lib") + +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} INTERNAL + MODULE winpr + MODULES winpr-crt winpr-file winpr-utils) + +if(MONOLITHIC_BUILD) + set(WINPR_LIBS ${WINPR_LIBS} ${${MODULE_PREFIX}_LIBS} PARENT_SCOPE) +else() + target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + install(TARGETS ${MODULE_NAME} DESTINATION ${CMAKE_INSTALL_LIBDIR} EXPORT WinPRTargets) +endif() + +set_property(TARGET ${MODULE_NAME} PROPERTY FOLDER "WinPR") + +if(BUILD_TESTING) + add_subdirectory(test) +endif() + diff --git a/winpr/libwinpr/comm/ModuleOptions.cmake b/winpr/libwinpr/comm/ModuleOptions.cmake new file mode 100644 index 000000000..40955892a --- /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 000000000..dea659353 --- /dev/null +++ b/winpr/libwinpr/comm/comm.c @@ -0,0 +1,1312 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include "comm_ioctl.h" + + +/** + * Communication Resources: + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363196/ + */ + +#include "comm.h" + +BOOL BuildCommDCBA(LPCSTR lpDef, LPDCB lpDCB) +{ + return TRUE; +} + +BOOL BuildCommDCBW(LPCWSTR lpDef, LPDCB lpDCB) +{ + return TRUE; +} + +BOOL BuildCommDCBAndTimeoutsA(LPCSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts) +{ + return TRUE; +} + +BOOL BuildCommDCBAndTimeoutsW(LPCWSTR lpDef, LPDCB lpDCB, LPCOMMTIMEOUTS lpCommTimeouts) +{ + return TRUE; +} + +BOOL CommConfigDialogA(LPCSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC) +{ + return TRUE; +} + +BOOL CommConfigDialogW(LPCWSTR lpszName, HWND hWnd, LPCOMMCONFIG lpCC) +{ + return TRUE; +} + +BOOL GetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hCommDev; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL SetCommConfig(HANDLE hCommDev, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hCommDev; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL GetCommMask(HANDLE hFile, PDWORD lpEvtMask) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL SetCommMask(HANDLE hFile, DWORD dwEvtMask) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL GetCommModemStatus(HANDLE hFile, PDWORD lpModemStat) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +/** + * ERRORS: + * ERROR_INVALID_HANDLE + */ +BOOL GetCommProperties(HANDLE hFile, LPCOMMPROP lpCommProp) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + DWORD bytesReturned; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_GET_PROPERTIES, NULL, 0, lpCommProp, sizeof(COMMPROP), &bytesReturned, NULL)) + { + DEBUG_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; + struct termios currentState; + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + DWORD bytesReturned; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + 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)) + { + DEBUG_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) + { + DEBUG_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)) + { + DEBUG_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)) + { + DEBUG_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)) + { + DEBUG_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); + return TRUE; + + + error_handle: + if (lpLocalDcb) + 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; + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + DWORD bytesReturned; + + /* FIXME: validate changes according GetCommProperties? */ + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + 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)) + { + DEBUG_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 */ + { + DEBUG_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)) + { + DEBUG_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)) + { + DEBUG_WARN("SetCommState failure: could not set the control settings."); + return FALSE; + } + + + SERIAL_HANDFLOW handflow; + ZeroMemory(&handflow, sizeof(SERIAL_HANDFLOW)); + + 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: + DEBUG_WARN("Unexpected fDtrControl value: %d\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: + DEBUG_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: + DEBUG_WARN("Unexpected fRtsControl value: %d\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)) + { + DEBUG_WARN("SetCommState failure: could not set the handflow settings."); + return FALSE; + } + + + /** upcomingTermios stage **/ + + + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + 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; + DEBUG_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; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + 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)) + { + DEBUG_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; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + 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)) + { + DEBUG_WARN("SetCommTimeouts failure."); + return FALSE; + } + + return TRUE; +} + +BOOL GetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + return TRUE; +} + +BOOL GetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, LPDWORD lpdwSize) +{ + return TRUE; +} + +BOOL SetDefaultCommConfigA(LPCSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + return TRUE; +} + +BOOL SetDefaultCommConfigW(LPCWSTR lpszName, LPCOMMCONFIG lpCC, DWORD dwSize) +{ + return TRUE; +} + +BOOL SetCommBreak(HANDLE hFile) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL ClearCommBreak(HANDLE hFile) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL ClearCommError(HANDLE hFile, PDWORD lpErrors, LPCOMSTAT lpStat) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + + +BOOL PurgeComm(HANDLE hFile, DWORD dwFlags) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + DWORD bytesReturned = 0; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!CommDeviceIoControl(pComm, IOCTL_SERIAL_PURGE, &dwFlags, sizeof(DWORD), NULL, 0, &bytesReturned, NULL)) + { + DEBUG_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 (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + 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)) + { + DEBUG_WARN("SetCommTimeouts failure."); + return FALSE; + } + + return TRUE; +} + +BOOL EscapeCommFunction(HANDLE hFile, DWORD dwFunc) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL TransmitCommChar(HANDLE hFile, char cChar) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + +BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOverlapped) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hFile; + + if (!pComm) + return FALSE; + + return TRUE; +} + + +/* Extended API */ + +typedef struct _COMM_DEVICE +{ + LPTSTR name; + LPTSTR path; +} 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 HANDLE_CREATOR *_CommHandleCreator = NULL; +static HANDLE_CLOSE_CB *_CommHandleCloseCb = NULL; + +static pthread_once_t _CommInitialized = PTHREAD_ONCE_INIT; +static void _CommInit() +{ + /* NB: error management to be done outside of this function */ + + assert(_CommDevices == NULL); + assert(_CommHandleCreator == NULL); + assert(_CommHandleCloseCb == NULL); + + _CommDevices = (COMM_DEVICE**)calloc(COMM_DEVICE_MAX+1, sizeof(COMM_DEVICE*)); + + _CommHandleCreator = (HANDLE_CREATOR*)malloc(sizeof(HANDLE_CREATOR)); + if (_CommHandleCreator) + { + _CommHandleCreator->IsHandled = IsCommDevice; + _CommHandleCreator->CreateFileA = CommCreateFileA; + + RegisterHandleCreator(_CommHandleCreator); + } + + _CommHandleCloseCb = (HANDLE_CLOSE_CB*)malloc(sizeof(HANDLE_CLOSE_CB)); + if (_CommHandleCloseCb) + { + _CommHandleCloseCb->IsHandled = CommIsHandled; + _CommHandleCloseCb->CloseHandle = CommCloseHandle; + + RegisterHandleCloseCb(_CommHandleCloseCb); + } + + assert(_CommDevices != NULL); + assert(_CommHandleCreator != NULL); + assert(_CommHandleCloseCb != NULL); +} + + +static BOOL _IsReservedCommDeviceName(LPCTSTR lpName) +{ + int i; + + /* Serial ports, COM1-9 */ + for (i=1; i<10; i++) + { + TCHAR genericName[5]; + if (_stprintf_s(genericName, 5, "COM%d", i) < 0) + { + return FALSE; + } + + if (_tcscmp(genericName, lpName) == 0) + return TRUE; + } + + /* Parallel ports, LPT1-9 */ + for (i=1; i<10; i++) + { + TCHAR genericName[5]; + if (_stprintf_s(genericName, 5, "LPT%d", i) < 0) + { + return FALSE; + } + + if (_tcscmp(genericName, lpName) == 0) + return TRUE; + } + + /* FIXME: what about PRN ? */ + + 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) +{ + int i = 0; + LPTSTR storedDeviceName = NULL; + LPTSTR storedTargetPath = NULL; + + if (pthread_once(&_CommInitialized, _CommInit) != 0) + { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error_handle; + } + + if (_CommDevices == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + goto error_handle; + } + + if (_tcsncmp(lpDeviceName, _T("\\\\.\\"), 4) != 0) + { + if (!_IsReservedCommDeviceName(lpDeviceName)) + { + SetLastError(ERROR_INVALID_DATA); + 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; + } + + for (i=0; iname, 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; + } + + return TRUE; + + + error_handle: + if (storedDeviceName != NULL) + free(storedDeviceName); + + if (storedTargetPath != NULL) + free(storedTargetPath); + + 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) +{ + int i; + LPTSTR storedTargetPath; + + SetLastError(ERROR_SUCCESS); + + if (pthread_once(&_CommInitialized, _CommInit) != 0) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return 0; + } + + if (_CommDevices == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return 0; + } + + if (lpDeviceName == NULL || lpTargetPath == NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); + return 0; + } + + storedTargetPath = NULL; + for (i=0; iname, lpDeviceName) == 0) + { + storedTargetPath = _CommDevices[i]->path; + break; + } + + continue; + } + + break; + } + + if (storedTargetPath == NULL) + { + SetLastError(ERROR_INVALID_DATA); + return 0; + } + + if (_tcslen(storedTargetPath) + 2 > ucchMax) + { + SetLastError(ERROR_INSUFFICIENT_BUFFER); + return 0; + } + + _tcscpy(lpTargetPath, storedTargetPath); + _tcscat(lpTargetPath, ""); /* 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 (QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH) > 0) + { + return TRUE; + } + + return FALSE; +} + + +/** + * Sets + */ +void _comm_setServerSerialDriver(HANDLE hComm, SERIAL_DRIVER_ID driverId) +{ + ULONG Type; + PVOID Object; + WINPR_COMM* pComm; + + if (!winpr_Handle_GetInfo(hComm, &Type, &Object)) + { + DEBUG_WARN("_comm_setServerSerialDriver failure"); + return; + } + + pComm = (WINPR_COMM*)Object; + pComm->serverSerialDriverId = driverId; +} + + +/** + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363198%28v=vs.85%29.aspx + * + * @param lpDeviceName e.g. COM1, \\.\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 (dwDesiredAccess != (GENERIC_READ | GENERIC_WRITE)) + { + DEBUG_WARN("unexpected access to the device: 0x%lX", 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) + { + DEBUG_WARN("unexpected security attributes, nLength=%lu", 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) + { + DEBUG_WARN("device not found %s", devicePath); + SetLastError(ERROR_FILE_NOT_FOUND); + return INVALID_HANDLE_VALUE; + } + + if (!S_ISCHR(deviceStat.st_mode)) + { + DEBUG_WARN("bad device %s", devicePath); + SetLastError(ERROR_BAD_DEVICE); + return INVALID_HANDLE_VALUE; + } + + if (dwFlagsAndAttributes != 0) + { + DEBUG_WARN("unexpected flags and attributes: 0x%lX", 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(pComm, HANDLE_TYPE_COMM); + + /* error_handle */ + + pComm->fd = open(devicePath, O_RDWR | O_NOCTTY | O_NONBLOCK); + if (pComm->fd < 0) + { + DEBUG_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) + { + DEBUG_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) + { + DEBUG_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) + { + DEBUG_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) + { + DEBUG_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) + { + DEBUG_WARN("TIOCGICOUNT ioctl failed, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + goto error_handle; + } + + + /* 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: + if (pComm != NULL) + { + CloseHandle(pComm); + } + + + return INVALID_HANDLE_VALUE; +} + + +BOOL CommIsHandled(HANDLE handle) +{ + WINPR_COMM *pComm; + + pComm = (WINPR_COMM*)handle; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + return TRUE; +} + + +BOOL CommCloseHandle(HANDLE handle) +{ + WINPR_COMM *pComm; + + pComm = (WINPR_COMM*)handle; + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (pComm->PendingEvents & SERIAL_EV_FREERDP_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)) + { + DEBUG_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; +} + + +#endif /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm.h b/winpr/libwinpr/comm/comm.h new file mode 100644 index 000000000..800dcb5c7 --- /dev/null +++ b/winpr/libwinpr/comm/comm.h @@ -0,0 +1,94 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2014 Marc-Andre Moreau + * 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 + +#ifndef _WIN32 + +#include +#include + +#include + +#include "../handle/handle.h" + +struct winpr_comm +{ + WINPR_HANDLE_DEF(); + + 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). + * + * Since not all features are supported, some devices and applications + * can still be functional on such errors. + * + * TODO: command line switch or getting rid of it. + */ + 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; + + /* 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_FREERDP_WAITING 0x4000 /* bit today unused by other SERIAL_EV_* */ +#define SERIAL_EV_FREERDP_STOP 0x8000 /* bit today unused by other SERIAL_EV_* */ + +#define FREERDP_PURGE_TXABORT 0x00000001 /* abort pending transmission */ +#define FREERDP_PURGE_RXABORT 0x00000002 /* abort pending reception */ + +BOOL CommIsHandled(HANDLE handle); +BOOL CommCloseHandle(HANDLE handle); + +#endif /* _WIN32 */ + +#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 000000000..53fe67135 --- /dev/null +++ b/winpr/libwinpr/comm/comm_io.c @@ -0,0 +1,551 @@ +/** + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 + +#include +#include +#include +#include + +#include + +#include +#include + +#include "comm.h" + +BOOL _comm_set_permissive(HANDLE hDevice, BOOL permissive) +{ + WINPR_COMM* pComm = (WINPR_COMM*) hDevice; + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + pComm->permissive = permissive; + return TRUE; +} + + +/* Computes VMIN 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; + COMMTIMEOUTS *pTimeouts; + UCHAR vmin = 0; + UCHAR vtime = 0; + ULONGLONG Tmax = 0; + struct timeval tmaxTimeout, *pTmaxTimeout; + struct termios currentTermios; + + EnterCriticalSection(&pComm->ReadLock); /* KISSer by the function's beginning */ + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + goto return_false; + } + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM) + { + SetLastError(ERROR_INVALID_HANDLE); + 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) + { + DEBUG_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 | 0 | Blocks for N bytes available. + * 0< Ti fd_read_event, O_NONBLOCK) doesn't conflict with above use cases */ + + pTimeouts = &(pComm->timeouts); + + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && (pTimeouts->ReadTotalTimeoutConstant == MAXULONG)) + { + DEBUG_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 */ + + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG)) + { + /* Tc */ + Tmax = pTimeouts->ReadTotalTimeoutConstant; + } + else + { + /* Tmax */ + Tmax = nNumberOfBytesToRead * pTimeouts->ReadTotalTimeoutMultiplier + pTimeouts->ReadTotalTimeoutConstant; + } + + 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) + { + DEBUG_WARN("CommReadFile failure, could not apply new timeout values: VMIN=%u, VTIME=%u", vmin, vtime); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + } + + pTmaxTimeout = NULL; /* no timeout if Tmax == 0 */ + if (Tmax > 0) + { + ZeroMemory(&tmaxTimeout, sizeof(struct timeval)); + + tmaxTimeout.tv_sec = Tmax / 1000; /* s */ + tmaxTimeout.tv_usec = (Tmax % 1000) * 1000; /* us */ + + pTmaxTimeout = &tmaxTimeout; + } + + + /* 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); + + assert(pComm->fd_read_event < FD_SETSIZE); + 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) + { + DEBUG_WARN("select() failure, errno=[%d] %s\n", errno, strerror(errno)); + 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) + { + assert(FALSE); /* not quite sure this should ever happen */ + /* keep on */ + } + else + { + DEBUG_WARN("unexpected error on reading fd_read_event, errno=[%d] %s\n", errno, strerror(errno)); + /* FIXME: goto return_false ? */ + } + + assert(errno == EAGAIN); + } + + if (event == FREERDP_PURGE_RXABORT) + { + SetLastError(ERROR_CANCELLED); + goto return_false; + } + + assert(event == FREERDP_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) + { + DEBUG_WARN("CommReadFile failed, ReadIntervalTimeout=%lu, ReadTotalTimeoutMultiplier=%lu, ReadTotalTimeoutConstant=%lu VMIN=%u, VTIME=%u", + pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier, pTimeouts->ReadTotalTimeoutConstant, + currentTermios.c_cc[VMIN], currentTermios.c_cc[VTIME]); + DEBUG_WARN("CommReadFile failed, nNumberOfBytesToRead=%lu, errno=[%d] %s", nNumberOfBytesToRead, errno, strerror(errno)); + + 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 + { + assert(FALSE); + SetLastError(ERROR_IO_DEVICE); + goto return_false; + } + } + + if (nbRead == 0) + { + /* termios timeout */ + SetLastError(ERROR_TIMEOUT); + goto return_false; + } + + *lpNumberOfBytesRead = nbRead; + goto return_true; + } + + 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, *pTmaxTimeout; + + EnterCriticalSection(&pComm->WriteLock); /* KISSer by the function's beginning */ + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + goto return_false; + } + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM) + { + SetLastError(ERROR_INVALID_HANDLE); + 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 = NULL; /* no timeout if Tmax == 0 */ + if (Tmax > 0) + { + ZeroMemory(&tmaxTimeout, sizeof(struct timeval)); + + tmaxTimeout.tv_sec = Tmax / 1000; /* s */ + tmaxTimeout.tv_usec = (Tmax % 1000) * 1000; /* us */ + + pTmaxTimeout = &tmaxTimeout; + } + + while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite) + { + int biggestFd = -1; + fd_set event_set, write_set; + int nbFds; + + biggestFd = pComm->fd_write; + if (pComm->fd_write_event > biggestFd) + biggestFd = pComm->fd_write_event; + + FD_ZERO(&event_set); + FD_ZERO(&write_set); + + assert(pComm->fd_write_event < FD_SETSIZE); + 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) + { + DEBUG_WARN("select() failure, errno=[%d] %s\n", errno, strerror(errno)); + 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) + { + assert(FALSE); /* not quite sure this should ever happen */ + /* keep on */ + } + else + { + DEBUG_WARN("unexpected error on reading fd_write_event, errno=[%d] %s\n", errno, strerror(errno)); + /* FIXME: goto return_false ? */ + } + + assert(errno == EAGAIN); + } + + if (event == FREERDP_PURGE_TXABORT) + { + SetLastError(ERROR_CANCELLED); + goto return_false; + } + + assert(event == FREERDP_PURGE_TXABORT); /* no other expected event so far */ + } + + + /* write_set */ + + if (FD_ISSET(pComm->fd_write, &write_set)) + { + ssize_t nbWritten; + + nbWritten = write(pComm->fd_write, + lpBuffer + (*lpNumberOfBytesWritten), + nNumberOfBytesToWrite - (*lpNumberOfBytesWritten)); + + if (nbWritten < 0) + { + DEBUG_WARN("CommWriteFile failed after %lu bytes written, errno=[%d] %s\n", *lpNumberOfBytesWritten, errno, strerror(errno)); + + if (errno == EAGAIN) + { + /* keep on */ + continue; + } + else if (errno == EBADF) + { + SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */ + goto return_false; + } + else + { + 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 oppened 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 /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_ioctl.c b/winpr/libwinpr/comm/comm_ioctl.c new file mode 100644 index 000000000..11f608552 --- /dev/null +++ b/winpr/libwinpr/comm/comm_ioctl.c @@ -0,0 +1,739 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifndef _WIN32 + + +#include +#include + +#include + +#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) +{ + int i; + + for (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 _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; + + /* clear any previous last error */ + SetLastError(ERROR_SUCCESS); + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (lpOverlapped != NULL) + { + SetLastError(ERROR_NOT_SUPPORTED); + return FALSE; + } + + if (lpBytesReturned == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + return FALSE; + } + + *lpBytesReturned = 0; /* will be ajusted if required ... */ + + DEBUG_MSG("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: + DEBUG_MSG("Unknown remote serial driver (%d), using SerCx2.sys", pComm->serverSerialDriverId); + pServerSerialDriver = SerCx2Sys_s(); + break; + } + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + + 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; + } + } + + DEBUG_WARN(_T("unsupported IoControlCode=[0x%lX] %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; + + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + + result = _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 */ + DEBUG_WARN("lpBytesReturned=%ld and nOutBufferSize=%ld are different!", *lpBytesReturned, nOutBufferSize); + } + + if (pComm->permissive) + { + if (!result) + { + DEBUG_WARN("[permissive]: whereas it failed, made to succeed IoControlCode=[0x%lX] %s, last-error: 0x%lX", + 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; + struct termios currentState; + + if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0) + { + DEBUG_WARN("tcsetattr failure, errno: %d", errno); + return result; + } + + /* NB: tcsetattr() can succeed even if not all changes have been applied. */ + ZeroMemory(¤tState, sizeof(struct termios)); + if ((result = tcgetattr(fd, ¤tState)) < 0) + { + DEBUG_WARN("tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, &termios_p, sizeof(struct termios)) != 0) + { + DEBUG_MSG("all termios parameters are not set yet, doing a second attempt..."); + if ((result = tcsetattr(fd, optional_actions, termios_p)) < 0) + { + DEBUG_WARN("2nd tcsetattr failure, errno: %d", errno); + return result; + } + + ZeroMemory(¤tState, sizeof(struct termios)); + if ((result = tcgetattr(fd, ¤tState)) < 0) + { + DEBUG_WARN("tcgetattr failure, errno: %d", errno); + return result; + } + + if (memcmp(¤tState, termios_p, sizeof(struct termios)) != 0) + { + DEBUG_WARN("Failure: all termios parameters are still not set on a second attempt"); + return -1; + } + } + + return 0; +} + + +#endif /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_ioctl.h b/winpr/libwinpr/comm/comm_ioctl.h new file mode 100644 index 000000000..bb1e3b3d4 --- /dev/null +++ b/winpr/libwinpr/comm/comm_ioctl.h @@ -0,0 +1,244 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * 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_ + +#ifndef _WIN32 + +#include + +#include +#include +#include + +#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 _SERIAL_BAUD_RATE +{ + ULONG BaudRate; +} SERIAL_BAUD_RATE, *PSERIAL_BAUD_RATE; + + +typedef struct _SERIAL_CHARS +{ + UCHAR EofChar; + UCHAR ErrorChar; + UCHAR BreakChar; + UCHAR EventChar; + UCHAR XonChar; + UCHAR XoffChar; +} SERIAL_CHARS, *PSERIAL_CHARS; + + +typedef struct _SERIAL_LINE_CONTROL +{ + UCHAR StopBits; + UCHAR Parity; + UCHAR WordLength; +} SERIAL_LINE_CONTROL, *PSERIAL_LINE_CONTROL; + + +typedef struct _SERIAL_HANDFLOW +{ + 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 _SERIAL_TIMEOUTS +{ + 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 _SERIAL_QUEUE_SIZE +{ + 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 _SERIAL_STATUS +{ + 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 +{ + 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 /* _WIN32 */ + +#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 000000000..9d09e8c98 --- /dev/null +++ b/winpr/libwinpr/comm/comm_sercx2_sys.c @@ -0,0 +1,207 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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 _WIN32 + +#include + +#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) +{ + 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; + SERIAL_DRIVER* pSerialSys = SerialSys_s(); + + possibleMask = *pWaitMask & _SERCX2_SYS_SUPPORTED_EV_MASK; + + if (possibleMask != *pWaitMask) + { + DEBUG_WARN("Not all wait events supported (SerCx2.sys), requested events= 0X%lX, possible events= 0X%lX", *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)) + { + DEBUG_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)) + { + DEBUG_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() +{ + /* _SerCx2Sys completed with inherited functions from SerialSys or SerCxSys */ + SERIAL_DRIVER* pSerialSys = SerialSys_s(); + SERIAL_DRIVER* pSerCxSys = SerCxSys_s(); + + _SerCx2Sys.set_baud_rate = pSerCxSys->set_baud_rate; + _SerCx2Sys.get_baud_rate = pSerCxSys->get_baud_rate; + + _SerCx2Sys.get_properties = pSerCxSys->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 /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.h b/winpr/libwinpr/comm/comm_sercx2_sys.h new file mode 100644 index 000000000..a37df299a --- /dev/null +++ b/winpr/libwinpr/comm/comm_sercx2_sys.h @@ -0,0 +1,39 @@ +/** + * 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 + +#ifndef _WIN32 + +#include "comm_ioctl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SERIAL_DRIVER* SerCx2Sys_s(); + +#ifdef __cplusplus +} +#endif + +#endif /* _WIN32 */ + +#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 000000000..db5faeb60 --- /dev/null +++ b/winpr/libwinpr/comm/comm_sercx_sys.c @@ -0,0 +1,455 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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 _WIN32 + +#include +#include + +#include + +#include "comm_serial_sys.h" + + +/* 0: B* (Linux termios) + * 1: CBR_* or actual baud rate + * 2: BAUD_* (similar to SERIAL_BAUD_*) + */ +static const speed_t _SERCX_SYS_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 +}; + + +static BOOL _get_properties(WINPR_COMM *pComm, COMMPROP *pProperties) +{ + int i; + SERIAL_DRIVER* pSerialSys = SerialSys_s(); + + if (!pSerialSys->get_properties(pComm, pProperties)) + { + return FALSE; + } + + /* override some of the inherited properties from SerialSys ... */ + + pProperties->dwMaxBaud = BAUD_USER; + + pProperties->dwSettableBaud = 0; + for (i=0; _SERCX_SYS_BAUD_TABLE[i][0]<=__MAX_BAUD; i++) + { + pProperties->dwSettableBaud |= _SERCX_SYS_BAUD_TABLE[i][2]; + } + + return TRUE; +} + + +static BOOL _set_baud_rate(WINPR_COMM *pComm, const SERIAL_BAUD_RATE *pBaudRate) +{ + int i; + speed_t newSpeed; + 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 (i=0; _SERCX_SYS_BAUD_TABLE[i][0]<=__MAX_BAUD; i++) + { + if (_SERCX_SYS_BAUD_TABLE[i][1] == pBaudRate->BaudRate) + { + newSpeed = _SERCX_SYS_BAUD_TABLE[i][0]; + if (cfsetspeed(&futureState, newSpeed) < 0) + { + DEBUG_WARN("failed to set speed 0x%x (%lu)", newSpeed, pBaudRate->BaudRate); + return FALSE; + } + + assert(cfgetispeed(&futureState) == newSpeed); + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0) + { + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%lX", GetLastError()); + return FALSE; + } + + return TRUE; + } + } + + DEBUG_WARN("could not find a matching speed for the baud rate %lu", pBaudRate->BaudRate); + SetLastError(ERROR_INVALID_DATA); + return FALSE; +} + + +static BOOL _get_baud_rate(WINPR_COMM *pComm, SERIAL_BAUD_RATE *pBaudRate) +{ + int i; + speed_t currentSpeed; + struct termios currentState; + + ZeroMemory(¤tState, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tState) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + currentSpeed = cfgetispeed(¤tState); + + for (i=0; _SERCX_SYS_BAUD_TABLE[i][0]<=__MAX_BAUD; i++) + { + if (_SERCX_SYS_BAUD_TABLE[i][0] == currentSpeed) + { + pBaudRate->BaudRate = _SERCX_SYS_BAUD_TABLE[i][1]; + return TRUE; + } + } + + DEBUG_WARN("could not find a matching baud rate for the speed 0x%x", currentSpeed); + SetLastError(ERROR_INVALID_DATA); + return FALSE; +} + +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) + { + DEBUG_WARN("SERIAL_DCD_HANDSHAKE not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY) + { + DEBUG_WARN("SERIAL_DSR_SENSITIVITY not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT) + { + DEBUG_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) + { + DEBUG_WARN("SERIAL_AUTO_TRANSMIT not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_AUTO_RECEIVE) + { + DEBUG_WARN("SERIAL_AUTO_RECEIVE not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_ERROR_CHAR) + { + DEBUG_WARN("SERIAL_ERROR_CHAR not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_NULL_STRIPPING) + { + DEBUG_WARN("SERIAL_NULL_STRIPPING not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_BREAK_CHAR) + { + DEBUG_WARN("SERIAL_BREAK_CHAR not supposed to be implemented by SerCx.sys"); + } + + if (pHandflow->ControlHandShake & SERIAL_XOFF_CONTINUE) + { + DEBUG_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; + 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; + SERIAL_DRIVER* pSerialSys = SerialSys_s(); + + possibleMask = *pWaitMask & _SERCX_SYS_SUPPORTED_EV_MASK; + + if (possibleMask != *pWaitMask) + { + DEBUG_WARN("Not all wait events supported (SerCx.sys), requested events= 0x%lX, possible events= 0x%lX", *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 = _set_baud_rate, + .get_baud_rate = _get_baud_rate, + .get_properties = _get_properties, + .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() +{ + /* _SerCxSys completed with inherited functions from SerialSys */ + SERIAL_DRIVER* pSerialSys = SerialSys_s(); + + _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 /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_sercx_sys.h b/winpr/libwinpr/comm/comm_sercx_sys.h new file mode 100644 index 000000000..a232f4b9e --- /dev/null +++ b/winpr/libwinpr/comm/comm_sercx_sys.h @@ -0,0 +1,41 @@ +/** + * 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 + +#ifndef _WIN32 + +#include "comm_ioctl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SERIAL_DRIVER* SerCxSys_s(); + +#ifdef __cplusplus +} +#endif + + +#endif /* _WIN32 */ + + +#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 000000000..813c419f7 --- /dev/null +++ b/winpr/libwinpr/comm/comm_serial_sys.c @@ -0,0 +1,1549 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni + * Copyright 2014 Marc-Andre Moreau + * 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 _WIN32 + +#include +#include +#include +#include +#include +#include + +#include + +#include "comm_serial_sys.h" + +#include + +/* 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 + + + +/* + * Linux, Windows speeds + * + */ +static const speed_t _SERIAL_SYS_BAUD_TABLE[][2] = { +#ifdef B0 + {B0, 0}, /* hang up */ +#endif +/* #ifdef B50 */ +/* {B50, }, /\* undefined by serial.sys *\/ */ +/* #endif */ +#ifdef B75 + {B75, SERIAL_BAUD_075}, +#endif +#ifdef B110 + {B110, SERIAL_BAUD_110}, +#endif +/* #ifdef B134 */ +/* {B134, SERIAL_BAUD_134_5}, /\* TODO: might be the same? *\/ */ +/* #endif */ +#ifdef B150 + {B150, SERIAL_BAUD_150}, +#endif +/* #ifdef B200 */ +/* {B200, }, /\* undefined by serial.sys *\/ */ +/* #endif */ +#ifdef B300 + {B300, SERIAL_BAUD_300}, +#endif +#ifdef B600 + {B600, SERIAL_BAUD_600}, +#endif +#ifdef B1200 + {B1200, SERIAL_BAUD_1200}, +#endif +#ifdef B1800 + {B1800, SERIAL_BAUD_1800}, +#endif +#ifdef B2400 + {B2400, SERIAL_BAUD_2400}, +#endif +#ifdef B4800 + {B4800, SERIAL_BAUD_4800}, +#endif + /* {, SERIAL_BAUD_7200} /\* undefined on Linux *\/ */ +#ifdef B9600 + {B9600, SERIAL_BAUD_9600}, +#endif + /* {, SERIAL_BAUD_14400} /\* undefined on Linux *\/ */ +#ifdef B19200 + {B19200, SERIAL_BAUD_19200}, +#endif +#ifdef B38400 + {B38400, SERIAL_BAUD_38400}, +#endif +/* {, SERIAL_BAUD_56K}, /\* undefined on Linux *\/ */ +#ifdef B57600 + {B57600, SERIAL_BAUD_57600}, +#endif + /* {, SERIAL_BAUD_128K} /\* undefined on Linux *\/ */ +#ifdef B115200 + {B115200, SERIAL_BAUD_115200}, /* _SERIAL_MAX_BAUD */ +#endif + + /* no greater speed defined by serial.sys */ +}; + +#define _SERIAL_MAX_BAUD B115200 + + +static BOOL _get_properties(WINPR_COMM *pComm, COMMPROP *pProperties) +{ + int i; + + /* 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. + */ + + 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; + + pProperties->dwMaxBaud = SERIAL_BAUD_115200; /* _SERIAL_MAX_BAUD */ + + /* 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 (i=0; _SERIAL_SYS_BAUD_TABLE[i][0]<=_SERIAL_MAX_BAUD; i++) + { + pProperties->dwSettableBaud |= _SERIAL_SYS_BAUD_TABLE[i][1]; + } + + 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) +{ + int i; + speed_t newSpeed; + struct termios upcomingTermios; + + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + for (i=0; _SERIAL_SYS_BAUD_TABLE[i][0]<=_SERIAL_MAX_BAUD; i++) + { + if (_SERIAL_SYS_BAUD_TABLE[i][1] == pBaudRate->BaudRate) + { + newSpeed = _SERIAL_SYS_BAUD_TABLE[i][0]; + if (cfsetspeed(&upcomingTermios, newSpeed) < 0) + { + DEBUG_WARN("failed to set speed %u (%lu)", newSpeed, pBaudRate->BaudRate); + return FALSE; + } + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%lX", GetLastError()); + return FALSE; + } + + return TRUE; + } + } + + DEBUG_WARN("could not find a matching speed for the baud rate %lu", pBaudRate->BaudRate); + SetLastError(ERROR_INVALID_DATA); + return FALSE; +} + + +static BOOL _get_baud_rate(WINPR_COMM *pComm, SERIAL_BAUD_RATE *pBaudRate) +{ + int i; + speed_t currentSpeed; + struct termios currentState; + + ZeroMemory(¤tState, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tState) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + currentSpeed = cfgetispeed(¤tState); + + for (i=0; _SERIAL_SYS_BAUD_TABLE[i][0]<=_SERIAL_MAX_BAUD; i++) + { + if (_SERIAL_SYS_BAUD_TABLE[i][0] == currentSpeed) + { + pBaudRate->BaudRate = _SERIAL_SYS_BAUD_TABLE[i][1]; + return TRUE; + } + } + + DEBUG_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) + { + /* http://msdn.microsoft.com/en-us/library/windows/hardware/ff546688%28v=vs.85%29.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') + { + DEBUG_WARN("EofChar='%c' 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') + { + DEBUG_WARN("ErrorChar='%c' (0x%x) cannot be set (unsupported).\n", pSerialChars->ErrorChar, pSerialChars->ErrorChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* FIXME: see also: _set_handflow() */ + if (pSerialChars->BreakChar != '\0') + { + DEBUG_WARN("BreakChar='%c' (0x%x) cannot be set (unsupported).\n", pSerialChars->BreakChar, pSerialChars->BreakChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* FIXME: could be implemented during read/write I/O. What about ISIG? */ + if (pSerialChars->EventChar != '\0') + { + DEBUG_WARN("EventChar='%c' (0x%x) cannot be set\n", pSerialChars->EventChar, pSerialChars->EventChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + upcomingTermios.c_cc[VSTART] = pSerialChars->XonChar; + + upcomingTermios.c_cc[VSTOP] = pSerialChars->XoffChar; + + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%lX", 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: + DEBUG_WARN("Unsupported one and a half stop bits."); + break; + + case STOP_BITS_2: + upcomingTermios.c_cflag |= CSTOPB; + break; + + default: + DEBUG_WARN("unexpected number of stop bits: %d\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: + DEBUG_WARN("unexpected type of parity: %d\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: + DEBUG_WARN("unexpected number od data bits per character: %d\n", pLineControl->WordLength); + result = FALSE; /* but keep on */ + break; + } + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%lX", 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))) + { + DEBUG_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))) + { + DEBUG_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 */ + DEBUG_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 */ + DEBUG_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 */ + DEBUG_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 */ + DEBUG_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 */ + DEBUG_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) + { + DEBUG_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 */ + DEBUG_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) + { + DEBUG_WARN("Attempt to set XonLimit with an unsupported value: %lu", 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) + { + DEBUG_WARN("Attempt to set XoffLimit with an unsupported value: %lu", pHandflow->XoffLimit); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%lX", 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)) + { + DEBUG_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; + + 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) + { + DEBUG_WARN("TIOCMBIS ioctl failed, lines=0x%X, errno=[%d] %s", lines, errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + + +static BOOL _clear_lines(WINPR_COMM *pComm, UINT32 lines) +{ + if (ioctl(pComm->fd, TIOCMBIC, &lines) < 0) + { + DEBUG_WARN("TIOCMBIC ioctl failed, lines=0x%X, errno=[%d] %s", lines, errno, strerror(errno)); + 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 */ + 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 */ + 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) + { + DEBUG_WARN("TIOCMGET ioctl failed, errno=[%d] %s", errno, strerror(errno)); + 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; + + + /* 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_FREERDP_WAITING) + { + /* FIXME: any doubt on reading PendingEvents out of a critical section? */ + + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents |= SERIAL_EV_FREERDP_STOP; + LeaveCriticalSection(&pComm->EventsLock); + + /* waiting the end of the pending _wait_on_mask() */ + while (pComm->PendingEvents & SERIAL_EV_FREERDP_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) + { + DEBUG_WARN("TIOCGICOUNT ioctl failed, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + pComm->PendingEvents = 0; + } + + possibleMask = *pWaitMask & _SERIAL_SYS_SUPPORTED_EV_MASK; + + if (possibleMask != *pWaitMask) + { + DEBUG_WARN("Not all wait events supported (Serial.sys), requested events= 0X%lX, possible events= 0X%lX", *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) + DEBUG_WARN("Requested an incompatible input buffer size: %lu, keeping on with a %d bytes buffer.", pQueueSize->InSize, N_TTY_BUF_SIZE); + + if (pQueueSize->OutSize > N_TTY_BUF_SIZE) + DEBUG_WARN("Requested an incompatible output buffer size: %lu, keeping on with a %d 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) + { + DEBUG_WARN("Invalid purge mask: 0x%lX\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, FREERDP_PURGE_TXABORT) < 0) + { + if (errno != EAGAIN) + { + DEBUG_WARN("eventfd_write failed, errno=[%d] %s", errno, strerror(errno)); + } + + 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, FREERDP_PURGE_RXABORT) < 0) + { + if (errno != EAGAIN) + { + DEBUG_WARN("eventfd_write failed, errno=[%d] %s", errno, strerror(errno)); + } + + 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) + { + DEBUG_WARN("tcflush(TCOFLUSH) failure, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_CANCELLED); + return FALSE; + } + } + + + if (*pPurgeMask & SERIAL_PURGE_RXCLEAR) + { + /* Purges the receive buffer, if one exists. */ + + if (tcflush(pComm->fd, TCIFLUSH) < 0) + { + DEBUG_WARN("tcflush(TCIFLUSH) failure, errno=[%d] %s", errno, strerror(errno)); + 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) + { + DEBUG_WARN("TIOCGICOUNT ioctl failed, errno=[%d] %s", errno, strerror(errno)); + 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) + { + DEBUG_WARN("TIOCINQ ioctl failed, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + + /* AmountInOutQueue */ + + if (ioctl(pComm->fd, TIOCOUTQ, &(pCommstatus->AmountInOutQueue)) < 0) + { + DEBUG_WARN("TIOCOUTQ ioctl failed, errno=[%d] %s", errno, strerror(errno)); + 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_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 occured? */ + 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) +{ + assert(*pOutputMask == 0); + + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents |= SERIAL_EV_FREERDP_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + + + while (TRUE) + { + /* NB: EventsLock also used by _refresh_PendingEvents() */ + if (!_refresh_PendingEvents(pComm)) + { + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents &= ~SERIAL_EV_FREERDP_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + /* NB: ensure to leave the critical section before to return */ + EnterCriticalSection(&pComm->EventsLock); + + if (pComm->PendingEvents & SERIAL_EV_FREERDP_STOP) + { + pComm->PendingEvents &= ~SERIAL_EV_FREERDP_STOP; + + /* pOutputMask must remain empty but should + * not have been modified. + * + * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx + */ + assert(*pOutputMask == 0); + + pComm->PendingEvents &= ~SERIAL_EV_FREERDP_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_FREERDP_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() whithout the burden of the + * semaphore initialization and destroying. + */ + + Sleep(100); /* 100 ms */ + } + + DEBUG_WARN("_wait_on_mask, unexpected return, WaitEventMask=0X%lX", pComm->WaitEventMask); + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents &= ~SERIAL_EV_FREERDP_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + assert(FALSE); + return FALSE; +} + +static BOOL _set_break_on(WINPR_COMM *pComm) +{ + if (ioctl(pComm->fd, TIOCSBRK, NULL) < 0) + { + DEBUG_WARN("TIOCSBRK ioctl failed, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + + +static BOOL _set_break_off(WINPR_COMM *pComm) +{ + if (ioctl(pComm->fd, TIOCCBRK, NULL) < 0) + { + DEBUG_WARN("TIOCSBRK ioctl failed, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + + +static BOOL _set_xoff(WINPR_COMM *pComm) +{ + if (tcflow(pComm->fd, TCIOFF) < 0) + { + DEBUG_WARN("TCIOFF failure, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + + +static BOOL _set_xon(WINPR_COMM *pComm) +{ + if (tcflow(pComm->fd, TCION) < 0) + { + DEBUG_WARN("TCION failure, errno=[%d] %s", errno, strerror(errno)); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + + +BOOL _get_dtrrts(WINPR_COMM *pComm, ULONG *pMask) +{ + UINT32 lines=0; + if (ioctl(pComm->fd, TIOCMGET, &lines) < 0) + { + DEBUG_WARN("TIOCMGET ioctl failed, errno=[%d] %s", errno, strerror(errno)); + 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; +} + + +BOOL _config_size(WINPR_COMM *pComm, ULONG *pSize) +{ + /* http://msdn.microsoft.com/en-us/library/ff546548%28v=vs.85%29.aspx */ + pSize = 0; + return TRUE; +} + + +BOOL _immediate_char(WINPR_COMM *pComm, const UCHAR *pChar) +{ + BOOL result; + 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); + + assert(nbBytesWritten == 1); + + return result; +} + + +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() +{ + return &_SerialSys; +} + +#endif /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_serial_sys.h b/winpr/libwinpr/comm/comm_serial_sys.h new file mode 100644 index 000000000..e99c716d2 --- /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 + +#ifndef _WIN32 + +#include "comm_ioctl.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SERIAL_DRIVER* SerialSys_s(); + +#ifdef __cplusplus +} +#endif + + +#endif /* _WIN32 */ + +#endif /* COMM_SERIAL_SYS_H */ diff --git a/winpr/libwinpr/comm/module.def b/winpr/libwinpr/comm/module.def new file mode 100644 index 000000000..fc26e7287 --- /dev/null +++ b/winpr/libwinpr/comm/module.def @@ -0,0 +1,3 @@ +LIBRARY "libwinpr-comm" +EXPORTS + diff --git a/winpr/libwinpr/comm/test/.gitignore b/winpr/libwinpr/comm/test/.gitignore new file mode 100644 index 000000000..78bb24b78 --- /dev/null +++ b/winpr/libwinpr/comm/test/.gitignore @@ -0,0 +1,2 @@ +TestComm +TestComm.c diff --git a/winpr/libwinpr/comm/test/CMakeLists.txt b/winpr/libwinpr/comm/test/CMakeLists.txt new file mode 100644 index 000000000..dcf737f3a --- /dev/null +++ b/winpr/libwinpr/comm/test/CMakeLists.txt @@ -0,0 +1,39 @@ + +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}) + +set_complex_link_libraries(VARIABLE ${MODULE_PREFIX}_LIBS + MONOLITHIC ${MONOLITHIC_BUILD} + MODULE winpr + MODULES winpr-comm winpr-crt winpr-file) + +target_link_libraries(${MODULE_NAME} ${${MODULE_PREFIX}_LIBS}) + +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}) +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 000000000..edb48bd93 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestCommConfig.c @@ -0,0 +1,141 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2014 Marc-Andre Moreau + * 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 +#include +#include +#include +#include + +int TestCommConfig(int argc, char* argv[]) +{ + DCB dcb; + HANDLE hComm; + BOOL success; + LPCSTR lpFileName = "\\\\.\\COM1"; + COMMPROP commProp; + + 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; + } + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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%0.8x\n", lpFileName, GetLastError()); + return EXIT_FAILURE; + } + + /* TODO: a second call to CreateFileA should failed and + * GetLastError should return ERROR_SHARING_VIOLATION */ + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + success = GetCommState(hComm, &dcb); + if (!success) + { + fprintf(stderr, "GetCommState failure: GetLastError() = Ox%x\n", (int) GetLastError()); + return EXIT_FAILURE; + } + + fprintf(stderr, "BaudRate: %d ByteSize: %d Parity: %d StopBits: %d\n", + (int) dcb.BaudRate, (int) dcb.ByteSize, (int) dcb.Parity, (int) dcb.StopBits); + + ZeroMemory(&commProp, sizeof(COMMPROP)); + if (!GetCommProperties(hComm, &commProp)) + { + fprintf(stderr, "GetCommProperties failure: GetLastError(): 0x0.8x\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", (int) GetLastError()); + return EXIT_FAILURE; + } + + success = GetCommState(hComm, &dcb); + + if (!success) + { + fprintf(stderr, "GetCommState failure: GetLastError() = 0x%x\n", (int) 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: %d ByteSize: %d Parity: %d StopBits: %d\n", + (int) dcb.BaudRate, (int) dcb.ByteSize, (int) dcb.Parity, (int) 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 000000000..a99424bd7 --- /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 + +#include +#include + +static int test_CommDevice(LPCTSTR lpDeviceName, BOOL expectedResult) +{ + BOOL result; + TCHAR lpTargetPath[MAX_PATH]; + DWORD 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 = QueryCommDevice(lpDeviceName, lpTargetPath, MAX_PATH); + if (expectedResult) + { + if (tcslen <= _tcslen(lpDeviceName)) /* at least 2 more TCHAR are expected */ + { + _tprintf(_T("QueryCommDevice failure: didn't found 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 missing at the end of the buffer\n"), lpDeviceName); + return FALSE; + } + } + else + { + if (tcslen > 0) + { + _tprintf(_T("QueryCommDevice failure: device name: %s, expected result: , result: %d %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 000000000..1154b2789 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestCommMonitor.c @@ -0,0 +1,69 @@ + +#include +#include +#include +#include +#include + +int TestCommMonitor(int argc, char* argv[]) +{ + HANDLE hComm; + DWORD dwError; + BOOL fSuccess; + DWORD dwEvtMask; + OVERLAPPED overlapped; + 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 0; + } + + fSuccess = SetCommMask(hComm, EV_CTS | EV_DSR); + + if (!fSuccess) + { + printf("SetCommMask failure: GetLastError() = %d\n", (int) GetLastError()); + return 0; + } + + ZeroMemory(&overlapped, sizeof(OVERLAPPED)); + overlapped.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + 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() = %d\n", (int) dwError); + return 0; + } + } + + 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 000000000..dde4c982c --- /dev/null +++ b/winpr/libwinpr/comm/test/TestControlSettings.c @@ -0,0 +1,121 @@ +/** + * 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 + +#include +#include + +#include "../comm.h" + +int TestControlSettings(int argc, char* argv[]) +{ + BOOL result; + HANDLE hComm; + DCB dcb; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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(): %0.8x\n", GetLastError()); + return FALSE; + } + + /* Test 1 */ + + dcb.ByteSize = 5; + dcb.StopBits = ONESTOPBIT; + dcb.Parity = MARKPARITY; + + if (!SetCommState(hComm, &dcb)) + { + fprintf(stderr, "SetCommState failure; GetLastError(): %0.8x\n", GetLastError()); + return FALSE; + } + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + if (!GetCommState(hComm, &dcb)) + { + fprintf(stderr, "GetCommState failure; GetLastError(): %0.8x\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(): %0.8x\n", GetLastError()); + return FALSE; + } + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + if (!GetCommState(hComm, &dcb)) + { + fprintf(stderr, "GetCommState failure; GetLastError(): %0.8x\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()=%0.8x\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 000000000..1a4df956f --- /dev/null +++ b/winpr/libwinpr/comm/test/TestGetCommState.c @@ -0,0 +1,129 @@ +/** + * 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 + +#include +#include + +#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(1, sizeof(DCB) * 2); + 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[]) +{ + BOOL result; + HANDLE hComm; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + result = DefineCommDevice("COM1", "/dev/ttyS0"); + if (!result) + { + printf("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) + { + 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()=%0.8x\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 000000000..098b8b0e6 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestHandflow.c @@ -0,0 +1,86 @@ +/** + * 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 +#include + +#include +#include + +#include "../comm.h" + +static BOOL test_SerialSys(HANDLE hComm) +{ + // TMP: TODO: + return TRUE; +} + + +int TestHandflow(int argc, char* argv[]) +{ + BOOL result; + HANDLE hComm; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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()=%0.8x\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 000000000..d17c2f844 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestSerialChars.c @@ -0,0 +1,173 @@ +/** + * 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 +#include + +#include +#include + +#include "../comm.h" + +static BOOL test_SerCxSys(HANDLE hComm) +{ + DCB dcb; + UCHAR XonChar, XoffChar; + + struct termios currentTermios; + + ZeroMemory(¤tTermios, sizeof(struct termios)); + if (tcgetattr(((WINPR_COMM*)hComm)->fd, ¤tTermios) < 0) + { + fprintf(stderr, "tcgetattr failure.\n"); + return FALSE; + } + + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + if (!GetCommState(hComm, &dcb)) + { + fprintf(stderr, "GetCommState failure, GetLastError(): 0x%0.8x\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%0.8x\n", GetLastError()); + return FALSE; + } + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + if (!GetCommState(hComm, &dcb)) + { + fprintf(stderr, "GetCommState failure, GetLastError(): 0x%0.8x\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; + + ZeroMemory(&dcb, sizeof(DCB)); + dcb.DCBlength = sizeof(DCB); + if (!GetCommState(hComm, &dcb)) + { + fprintf(stderr, "GetCommState failure; GetLastError(): %0.8x\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[]) +{ + BOOL result; + HANDLE hComm; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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()=%0.8x\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 000000000..9c06263c6 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestSetCommState.c @@ -0,0 +1,390 @@ +/** + * 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 + +#include +#include + +#include "../comm.h" + +static void init_empty_dcb(DCB *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%x\n", GetLastError()); + return FALSE; + } + + /* test 1 */ + dcb.fParity = TRUE; + 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.fParity) + { + fprintf(stderr, "unexpected fParity: %d instead of TRUE\n", dcb.fParity); + return FALSE; + } + + /* test 2 */ + dcb.fParity = FALSE; + 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.fParity) + { + fprintf(stderr, "unexpected fParity: %d 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%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.fParity) + { + fprintf(stderr, "unexpected fParity: %d 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 = SERIAL_BAUD_115200; + result = SetCommState(hComm, &dcb); + if (!result) + { + fprintf(stderr, "SetCommState failure: 0x%0.8x\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 != SERIAL_BAUD_115200) + { + fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (SERIAL_BAUD_115200)\n", SERIAL_BAUD_115200); + return FALSE; + } + + /* Test 2 using a defferent baud rate */ + + dcb.BaudRate = SERIAL_BAUD_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 != SERIAL_BAUD_57600) + { + fprintf(stderr, "SetCommState failure: could not set BaudRate=%d (SERIAL_BAUD_57600)\n", SERIAL_BAUD_57600); + return FALSE; + } + + /* Test 3 using an unsupported baud rate on Linux */ + dcb.BaudRate = SERIAL_BAUD_128K; + result = SetCommState(hComm, &dcb); + if (result) + { + fprintf(stderr, "SetCommState failure: unexpected support of BaudRate=%d (SERIAL_BAUD_128K)\n", SERIAL_BAUD_128K); + return FALSE; + } + + return TRUE; +} + +static BOOL test_SerCxSys(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%0.8x\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_SerCx2Sys(HANDLE hComm) +{ + /* as of today there is no difference */ + return test_SerCxSys(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%0.8x\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[]) +{ + BOOL result; + HANDLE hComm; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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()=%0.8x\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 000000000..4f6c89424 --- /dev/null +++ b/winpr/libwinpr/comm/test/TestTimeouts.c @@ -0,0 +1,126 @@ +/** + * 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 +#include + +#include +#include + +#include "../comm.h" + +static BOOL test_generic(HANDLE hComm) +{ + COMMTIMEOUTS timeouts, timeouts2; + + 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%0.8x\n", GetLastError()); + return FALSE; + } + + ZeroMemory(&timeouts2, sizeof(COMMTIMEOUTS)); + if (!GetCommTimeouts(hComm, &timeouts2)) + { + fprintf(stderr, "GetCommTimeouts failure, GetLastError: 0x%0.8x\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%0.8x\n", GetLastError()); + return FALSE; + } + + if (GetLastError() != ERROR_INVALID_PARAMETER) + { + fprintf(stderr, "SetCommTimeouts failure, expected GetLastError to return ERROR_INVALID_PARAMETER and got: 0x%0.8x\n", GetLastError()); + return FALSE; + } + + return TRUE; +} + + +int TestTimeouts(int argc, char* argv[]) +{ + BOOL result; + HANDLE hComm; + + // TMP: FIXME: check if we can proceed with tests on the actual device, skip and warn otherwise but don't fail + 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()=%0.8x\n", GetLastError()); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} diff --git a/winpr/libwinpr/file/file.c b/winpr/libwinpr/file/file.c index 1d339fe8c..143d5b136 100644 --- a/winpr/libwinpr/file/file.c +++ b/winpr/libwinpr/file/file.c @@ -3,6 +3,7 @@ * File Functions * * Copyright 2012 Marc-Andre Moreau + * 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. @@ -27,6 +28,7 @@ #include #include #include +#include #include @@ -40,7 +42,7 @@ /** * api-ms-win-core-file-l1-2-0.dll: - * + * * CreateFileA * CreateFileW * CreateFile2 @@ -148,8 +150,10 @@ #include #endif +#include #include #include +#include #include #include #include @@ -184,6 +188,64 @@ #include "../pipe/pipe.h" +/* TODO: FIXME: use of a wArrayList and split winpr-utils with + * winpr-collections to avoid a circular dependency + * _HandleCreators = ArrayList_New(TRUE); + */ +/* _HandleCreators is a NULL-terminated array with a maximun of HANDLE_CREATOR_MAX HANDLE_CREATOR */ +#define HANDLE_CREATOR_MAX 128 +static HANDLE_CREATOR **_HandleCreators = NULL; + +static pthread_once_t _HandleCreatorsInitialized = PTHREAD_ONCE_INIT; +static void _HandleCreatorsInit() +{ + /* NB: error management to be done outside of this function */ + + assert(_HandleCreators == NULL); + + _HandleCreators = (HANDLE_CREATOR**)calloc(HANDLE_CREATOR_MAX+1, sizeof(HANDLE_CREATOR*)); + + assert(_HandleCreators != NULL); +} + +/** + * Returns TRUE on success, FALSE otherwise. + * + * ERRORS: + * ERROR_DLL_INIT_FAILED + * ERROR_INSUFFICIENT_BUFFER _HandleCreators full + */ +BOOL RegisterHandleCreator(PHANDLE_CREATOR pHandleCreator) +{ + int i; + + if (pthread_once(&_HandleCreatorsInitialized, _HandleCreatorsInit) != 0) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + if (_HandleCreators == NULL) + { + SetLastError(ERROR_DLL_INIT_FAILED); + return FALSE; + } + + + for (i=0; iIsHandled(lpFileName)) + { + return creator->CreateFileA(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, + dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile); + } + } + + /* TODO: use of a HANDLE_CREATOR for named pipes as well */ + + if (!IsNamedPipeFileNameA(lpFileName)) + return INVALID_HANDLE_VALUE; + name = GetNamedPipeNameWithoutPrefixA(lpFileName); if (!name) @@ -821,6 +911,14 @@ BOOL CreateDirectoryW(LPCWSTR lpPathName, LPSECURITY_ATTRIBUTES lpSecurityAttrib #define NAMED_PIPE_PREFIX_PATH "\\\\.\\pipe\\" +BOOL IsNamedPipeFileNameA(LPCSTR lpName) +{ + if (strncmp(lpName, NAMED_PIPE_PREFIX_PATH, sizeof(NAMED_PIPE_PREFIX_PATH) - 1) != 0) + return FALSE; + + return TRUE; +} + char* GetNamedPipeNameWithoutPrefixA(LPCSTR lpName) { char* lpFileName; @@ -828,7 +926,7 @@ char* GetNamedPipeNameWithoutPrefixA(LPCSTR lpName) if (!lpName) return NULL; - if (strncmp(lpName, NAMED_PIPE_PREFIX_PATH, sizeof(NAMED_PIPE_PREFIX_PATH) - 1) != 0) + if (!IsNamedPipeFileNameA(lpName)) return NULL; lpFileName = _strdup(&lpName[strlen(NAMED_PIPE_PREFIX_PATH)]); diff --git a/winpr/libwinpr/handle/handle.c b/winpr/libwinpr/handle/handle.c index a8b9d3fa4..dd4dedaa0 100644 --- a/winpr/libwinpr/handle/handle.c +++ b/winpr/libwinpr/handle/handle.c @@ -25,9 +25,13 @@ #ifndef _WIN32 +#include +#include + #include "../synch/synch.h" #include "../thread/thread.h" #include "../pipe/pipe.h" +#include "../comm/comm.h" #include "../security/security.h" #ifdef HAVE_UNISTD_H @@ -38,14 +42,83 @@ #include "../handle/handle.h" +/* _HandleCreators is a NULL-terminated array with a maximun of HANDLE_CREATOR_MAX HANDLE_CREATOR */ +#define HANDLE_CLOSE_CB_MAX 128 +static HANDLE_CLOSE_CB **_HandleCloseCbs = NULL; + +static pthread_once_t _HandleCloseCbsInitialized = PTHREAD_ONCE_INIT; +static void _HandleCloseCbsInit() +{ + /* NB: error management to be done outside of this function */ + + assert(_HandleCloseCbs == NULL); + + _HandleCloseCbs = (HANDLE_CLOSE_CB**)calloc(HANDLE_CLOSE_CB_MAX+1, sizeof(HANDLE_CLOSE_CB*)); + + assert(_HandleCloseCbs != NULL); +} + +/** + * Returns TRUE on success, FALSE otherwise. + */ +BOOL RegisterHandleCloseCb(HANDLE_CLOSE_CB *pHandleCloseCb) +{ + int i; + + if (pthread_once(&_HandleCloseCbsInitialized, _HandleCloseCbsInit) != 0) + { + return FALSE; + } + + if (_HandleCloseCbs == NULL) + { + return FALSE; + } + + + for (i=0; iIsHandled(hObject)) + { + return close_cb->CloseHandle(hObject); + } + } + + if (Type == HANDLE_TYPE_THREAD) { WINPR_THREAD* thread; @@ -199,6 +272,8 @@ BOOL CloseHandle(HANDLE hObject) free(token->Domain); free(token); + + return TRUE; } return FALSE; diff --git a/winpr/libwinpr/handle/handle.h b/winpr/libwinpr/handle/handle.h index 2537a5e13..819e7b3db 100644 --- a/winpr/libwinpr/handle/handle.h +++ b/winpr/libwinpr/handle/handle.h @@ -36,6 +36,7 @@ #define HANDLE_TYPE_FILE 10 #define HANDLE_TYPE_TIMER_QUEUE 11 #define HANDLE_TYPE_TIMER_QUEUE_TIMER 12 +#define HANDLE_TYPE_COMM 13 #define WINPR_HANDLE_DEF() \ ULONG Type @@ -64,4 +65,16 @@ static inline BOOL winpr_Handle_GetInfo(HANDLE handle, ULONG* pType, PVOID* pObj return TRUE; } + +typedef BOOL (*pcIsHandled)(HANDLE handle); +typedef BOOL (*pcCloseHandle)(HANDLE handle); + +typedef struct _HANDLE_CLOSE_CB +{ + pcIsHandled IsHandled; + pcCloseHandle CloseHandle; +} HANDLE_CLOSE_CB; + +BOOL RegisterHandleCloseCb(HANDLE_CLOSE_CB *pHandleClose); + #endif /* WINPR_HANDLE_PRIVATE_H */