From 4c743de69cca6c4a7f1b471ef399d714eae45853 Mon Sep 17 00:00:00 2001 From: Emmanuel Ledoux Date: Mon, 12 May 2014 17:33:56 +0200 Subject: [PATCH] winpr-comm: got IOCTL_SERIAL_SET_TIMEOUTS / IOCTL_SERIAL_GET_TIMEOUTS serial redirection: use of winpr-comm's functions and not serial_tty.* anymore --- channels/serial/client/CMakeLists.txt | 3 - channels/serial/client/serial_main.c | 269 +++++++++++++------- winpr/include/winpr/comm.h | 16 ++ winpr/libwinpr/comm/CMakeLists.txt | 1 + winpr/libwinpr/comm/comm.c | 89 ++++++- winpr/libwinpr/comm/comm.h | 1 + winpr/libwinpr/comm/comm_io.c | 316 ++++++++++++++++++++++++ winpr/libwinpr/comm/comm_ioctl.c | 65 ++++- winpr/libwinpr/comm/comm_ioctl.h | 18 +- winpr/libwinpr/comm/comm_sercx2_sys.c | 5 + winpr/libwinpr/comm/comm_sercx_sys.c | 7 +- winpr/libwinpr/comm/comm_serial_sys.c | 34 +++ winpr/libwinpr/comm/test/CMakeLists.txt | 1 + winpr/libwinpr/comm/test/TestTimeouts.c | 126 ++++++++++ 14 files changed, 841 insertions(+), 110 deletions(-) create mode 100644 winpr/libwinpr/comm/comm_io.c create mode 100644 winpr/libwinpr/comm/test/TestTimeouts.c diff --git a/channels/serial/client/CMakeLists.txt b/channels/serial/client/CMakeLists.txt index a919a0180..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") diff --git a/channels/serial/client/serial_main.c b/channels/serial/client/serial_main.c index c592f607d..e46da4c73 100644 --- a/channels/serial/client/serial_main.c +++ b/channels/serial/client/serial_main.c @@ -42,9 +42,6 @@ #include #endif -#include "serial_tty.h" -#include "serial_constants.h" - #include #include #include @@ -65,11 +62,7 @@ struct _SERIAL_DEVICE DEVICE device; HANDLE* hComm; - // TMP: TBR - char* path; - SERIAL_TTY* tty; - // - + // TMP: use of log wLog* log; HANDLE thread; wMessageQueue* IrpQueue; @@ -77,13 +70,11 @@ struct _SERIAL_DEVICE static void serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) { - UINT32 FileId; - DWORD DesiredAccess; DWORD SharedAccess; DWORD CreateDisposition; UINT32 PathLength; - + 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) */ @@ -95,16 +86,37 @@ static void serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) assert(PathLength == 0); /* MS-RDPESP 2.2.2.2 */ - /* CommCreateFileA current implementation expects nothing else than that */ - assert(DesiredAccess == (GENERIC_READ | GENERIC_WRITE)); - assert(SharedAccess == 0); - assert(CreateDisposition == OPEN_EXISTING); + +#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 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); + * + */ + + DEBUG_SVC("DesiredAccess: 0x%0.8x, SharedAccess: 0x%0.8x, CreateDisposition: 0x%0.8x", DesiredAccess, SharedAccess, CreateDisposition); + + /* FIXME: As of today only the flags below are supported by CommCreateFileA: */ + DesiredAccess = GENERIC_READ | GENERIC_WRITE; + SharedAccess = 0; + CreateDisposition = OPEN_EXISTING; +#endif serial->hComm = CreateFile(serial->device.name, - DesiredAccess, /* GENERIC_READ | GENERIC_WRITE */ - SharedAccess, /* 0 */ + DesiredAccess, + SharedAccess, NULL, /* SecurityAttributes */ - CreateDisposition, /* OPEN_EXISTING */ + CreateDisposition, 0, /* FlagsAndAttributes */ NULL); /* TemplateFile */ @@ -113,17 +125,36 @@ static void serial_process_irp_create(SERIAL_DEVICE* serial, IRP* irp) DEBUG_WARN("CreateFile failure: %s last-error: Ox%x\n", serial->device.name, GetLastError()); irp->IoStatus = STATUS_UNSUCCESSFUL; - FileId = 0; goto error_handle; } - FileId = irp->devman->id_sequence++; // TMP: !? + /* 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); */ + + // TMP: + COMMTIMEOUTS timeouts; + timeouts.ReadIntervalTimeout = MAXULONG; /* ensures a non blocking state */ + timeouts.ReadTotalTimeoutMultiplier = 0; + timeouts.ReadTotalTimeoutConstant = 0; + timeouts.WriteTotalTimeoutMultiplier = 0; + timeouts.WriteTotalTimeoutConstant = 0; + SetCommTimeouts(serial->hComm, &timeouts); + + assert(irp->FileId == 0); + irp->FileId = irp->devman->id_sequence++; /* FIXME: why not ((WINPR_COMM*)hComm)->fd? */ + irp->IoStatus = STATUS_SUCCESS; - DEBUG_SVC("%s %s (%d) created.", serial->device.name, serial->path, FileId); + DEBUG_SVC("%s (DeviceId: %d, FileId: %d) created.", serial->device.name, irp->device->id, irp->FileId); error_handle: - Stream_Write_UINT32(irp->output, FileId); /* FileId (4 bytes) */ + Stream_Write_UINT32(irp->output, irp->FileId); /* FileId (4 bytes) */ Stream_Write_UINT8(irp->output, 0); /* Information (1 byte) */ irp->Complete(irp); @@ -135,12 +166,12 @@ static void serial_process_irp_close(SERIAL_DEVICE* serial, IRP* irp) if (!CloseHandle(serial->hComm)) { - DEBUG_WARN("CloseHandle failure: %s %s (%d) closed.", serial->device.name, serial->path, irp->device->id); + DEBUG_WARN("CloseHandle failure: %s (%d) closed.", serial->device.name, irp->device->id); irp->IoStatus = STATUS_UNSUCCESSFUL; goto error_handle; } - DEBUG_SVC("%s %s (%d) closed.", serial->device.name, serial->path, irp->device->id); + DEBUG_SVC("%s (DeviceId: %d, FileId: %d) closed.", serial->device.name, irp->device->id, irp->FileId); serial->hComm = NULL; irp->IoStatus = STATUS_SUCCESS; @@ -156,91 +187,146 @@ 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) - { - irp->IoStatus = STATUS_UNSUCCESSFUL; - Length = 0; - DEBUG_WARN("tty not valid."); + buffer = (BYTE*)calloc(Length, sizeof(BYTE)); + if (buffer == NULL) + { + irp->IoStatus = STATUS_NO_MEMORY; + goto error_handle; + } + + + /* MSRDPESP 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 %lu 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=%d, last-error: 0x%0.8x", serial->device.name, nbRead, GetLastError()); - if (!serial_tty_read(tty, buffer, &Length)) + switch(GetLastError()) { - irp->IoStatus = STATUS_UNSUCCESSFUL; - free(buffer); - buffer = NULL; - Length = 0; + case ERROR_INVALID_HANDLE: + irp->IoStatus = STATUS_INVALID_DEVICE_REQUEST; + break; - DEBUG_WARN("read %s(%d) failed.", serial->path, tty->id); - } - else - { - DEBUG_SVC("read %llu-%llu from %d", Offset, Offset + Length, tty->id); + case ERROR_NOT_SUPPORTED: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + + case ERROR_INVALID_PARAMETER: + irp->IoStatus = STATUS_INVALID_PARAMETER; + break; + + case ERROR_IO_DEVICE: + irp->IoStatus = STATUS_IO_DEVICE_ERROR; + break; + + case ERROR_TIMEOUT: + irp->IoStatus = STATUS_TIMEOUT; + break; + + case ERROR_BAD_DEVICE: + irp->IoStatus = STATUS_INVALID_DEVICE_REQUEST; + break; + + default: + DEBUG_SVC("unexpected last-error: 0x%x", GetLastError()); + irp->IoStatus = STATUS_UNSUCCESSFUL; + break; } } - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ + + error_handle: - if (Length > 0) + 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); + + if (buffer) + free(buffer); irp->Complete(irp); } 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) + assert(Offset == 0); /* not implemented otherwise */ + + DEBUG_SVC("writing %lu 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_UNSUCCESSFUL; - Length = 0; + irp->IoStatus = STATUS_SUCCESS; + } + else + { + DEBUG_SVC("write failure to %s, nbWritten=%d, last-error: 0x%0.8x", serial->device.name, nbWritten, GetLastError()); - DEBUG_WARN("tty not valid."); + switch(GetLastError()) + { + case ERROR_INVALID_HANDLE: + irp->IoStatus = STATUS_INVALID_DEVICE_REQUEST; + break; - Stream_Write_UINT32(irp->output, Length); /* Length (4 bytes) */ - Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ - irp->Complete(irp); - return; + case ERROR_NOT_SUPPORTED: + irp->IoStatus = STATUS_NOT_SUPPORTED; + break; + + case ERROR_INVALID_PARAMETER: + irp->IoStatus = STATUS_INVALID_PARAMETER; + break; + + case ERROR_IO_DEVICE: + irp->IoStatus = STATUS_IO_DEVICE_ERROR; + break; + + case ERROR_TIMEOUT: + irp->IoStatus = STATUS_TIMEOUT; + break; + + case ERROR_BAD_DEVICE: + irp->IoStatus = STATUS_INVALID_DEVICE_REQUEST; + break; + + default: + DEBUG_SVC("unexpected last-error: 0x%x", GetLastError()); + irp->IoStatus = STATUS_UNSUCCESSFUL; + break; + } } - status = serial_tty_write(tty, Stream_Pointer(irp->input), Length); - 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); } @@ -253,20 +339,12 @@ static void serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) UINT32 OutputBufferLength; BYTE* OutputBuffer = NULL; DWORD BytesReturned = 0; - SERIAL_TTY* tty = serial->tty; 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) - { - DEBUG_WARN("tty not valid."); - irp->IoStatus = STATUS_UNSUCCESSFUL; - goto error_handle; - } - OutputBuffer = (BYTE*)calloc(OutputBufferLength, sizeof(BYTE)); if (OutputBuffer == NULL) { @@ -280,16 +358,19 @@ static void serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) irp->IoStatus = STATUS_NO_MEMORY; goto error_handle; } + + Stream_Read(irp->input, InputBuffer, InputBufferLength); + + DEBUG_SVC("CommDeviceIoControl: IoControlCode 0x%x", IoControlCode); - /* FIXME: to be replaced by DeviceIoControl() */ + /* FIXME: CommDeviceIoControl to be replaced by DeviceIoControl() */ if (CommDeviceIoControl(serial->hComm, IoControlCode, InputBuffer, InputBufferLength, OutputBuffer, OutputBufferLength, &BytesReturned, NULL)) { - Stream_Write(irp->output, OutputBuffer, BytesReturned); irp->IoStatus = STATUS_SUCCESS; } else { - DEBUG_SVC("CommDeviceIoControl failure: IoControlCode 0x%x last-error: 0x%x", IoControlCode, GetLastError()); + DEBUG_SVC("CommDeviceIoControl failure: IoControlCode 0x%0.8x last-error: 0x%x", IoControlCode, GetLastError()); switch(GetLastError()) { @@ -321,13 +402,25 @@ static void serial_process_irp_device_control(SERIAL_DEVICE* serial, IRP* irp) } error_handle: + + Stream_Write_UINT32(irp->output, BytesReturned); /* OutputBufferLength (4 bytes) */ + + if (BytesReturned > 0) + { + Stream_EnsureRemainingCapacity(irp->output, BytesReturned); + Stream_Write(irp->output, OutputBuffer, BytesReturned); /* OutputBuffer */ + } + else + { + Stream_Write_UINT8(irp->output, 0); /* Padding (1 byte) */ + } + if (InputBuffer != NULL) free(InputBuffer); if (OutputBuffer != NULL) free(OutputBuffer); - // TMP: double check the completion, Information field irp->Complete(irp); } @@ -402,14 +495,15 @@ static void serial_irp_request(DEVICE* device, IRP* irp) static void serial_free(DEVICE* device) { SERIAL_DEVICE* serial = (SERIAL_DEVICE*) device; - + WLog_Print(serial->log, WLOG_DEBUG, "freeing"); MessageQueue_PostQuit(serial->IrpQueue, 0); WaitForSingleObject(serial->thread, INFINITE); CloseHandle(serial->thread); - serial_tty_free(serial->tty); + if (serial->hComm) + CloseHandle(serial->hComm); /* Clean up resources */ Stream_Free(serial->device.data, TRUE); @@ -442,9 +536,11 @@ int DeviceServiceEntry(PDEVICE_SERVICE_ENTRY_POINTS pEntryPoints) if ((name && name[0]) && (path && path[0])) { + DEBUG_SVC("Defining %s as %s", name, path); + if (!DefineCommDevice(name /* eg: COM1 */, path /* eg: /dev/ttyS0 */)) { - DEBUG_SVC("Could not registered: %s as %s", path, name); + DEBUG_SVC("Could not define %s as %s", name, path); return -1; } @@ -463,7 +559,6 @@ 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); WLog_Init(); diff --git a/winpr/include/winpr/comm.h b/winpr/include/winpr/comm.h index 804b8da51..b373fe6f2 100644 --- a/winpr/include/winpr/comm.h +++ b/winpr/include/winpr/comm.h @@ -375,6 +375,11 @@ WINPR_API BOOL WaitCommEvent(HANDLE hFile, PDWORD lpEvtMask, LPOVERLAPPED lpOver /* Extended API */ +/* FIXME: MAXULONG should be defined arround winpr/limits.h */ +#ifndef MAXULONG +#define MAXULONG (4294967295UL) +#endif + /* * About DefineCommDevice() / QueryDosDevice() * @@ -402,6 +407,17 @@ WINPR_API HANDLE CommCreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD 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 } diff --git a/winpr/libwinpr/comm/CMakeLists.txt b/winpr/libwinpr/comm/CMakeLists.txt index 70f0c6d48..c37af7268 100644 --- a/winpr/libwinpr/comm/CMakeLists.txt +++ b/winpr/libwinpr/comm/CMakeLists.txt @@ -21,6 +21,7 @@ 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 diff --git a/winpr/libwinpr/comm/comm.c b/winpr/libwinpr/comm/comm.c index b639c840f..8feb72464 100644 --- a/winpr/libwinpr/comm/comm.c +++ b/winpr/libwinpr/comm/comm.c @@ -522,7 +522,6 @@ BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) if (lpDCB->fBinary) { upcomingTermios.c_lflag &= ~ICANON; - // TMP: complete the raw mode here? } else { @@ -563,22 +562,55 @@ BOOL SetCommState(HANDLE hFile, LPDCB lpDCB) return TRUE; } +/** + * ERRORS: + * ERROR_INVALID_HANDLE + */ BOOL GetCommTimeouts(HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts) { WINPR_COMM* pComm = (WINPR_COMM*) hFile; + DWORD bytesReturned; - if (!pComm) + 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) + 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; } @@ -975,7 +1007,7 @@ HANDLE CommCreateFileA(LPCSTR lpDeviceName, DWORD dwDesiredAccess, DWORD dwShare CHAR devicePath[MAX_PATH]; struct stat deviceStat; WINPR_COMM* pComm = NULL; - + struct termios upcomingTermios; if (dwDesiredAccess != (GENERIC_READ | GENERIC_WRITE)) { @@ -1052,12 +1084,53 @@ HANDLE CommCreateFileA(LPCSTR lpDeviceName, DWORD dwDesiredAccess, DWORD dwShare goto error_handle; } + /* Restore the blocking mode for upcoming read/write operations */ + if (fcntl(pComm->fd, F_SETFL, fcntl(pComm->fd, F_GETFL) & ~O_NONBLOCK) < 0) + { + DEBUG_WARN("failed to open device %s, could not restore the O_NONBLOCK flag", devicePath); + SetLastError(ERROR_BAD_DEVICE); + goto error_handle; + } - /* TMP: TODO: FIXME: this information is at least need for - * get/set baud. Is possible to pull this information? to be - * forced with a comand line argument. + + /* TMP: TODO: FIXME: this information is at least needed for + * get/set baud functions. Is it possible to pull this + * information? Could be a command line argument. */ - pComm->remoteSerialDriverId = RemoteSerialDriverUnknown; + pComm->remoteSerialDriverId = RemoteSerialDriverUnknown; + + + /* 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 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; diff --git a/winpr/libwinpr/comm/comm.h b/winpr/libwinpr/comm/comm.h index f77dad7e7..116f85dc4 100644 --- a/winpr/libwinpr/comm/comm.h +++ b/winpr/libwinpr/comm/comm.h @@ -45,6 +45,7 @@ struct winpr_comm int fd; REMOTE_SERIAL_DRIVER_ID remoteSerialDriverId; + COMMTIMEOUTS timeouts; /* NB: CloseHandle() has to free resources */ }; diff --git a/winpr/libwinpr/comm/comm_io.c b/winpr/libwinpr/comm/comm_io.c new file mode 100644 index 000000000..c25a8b659 --- /dev/null +++ b/winpr/libwinpr/comm/comm_io.c @@ -0,0 +1,316 @@ +/** + * 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 "comm.h" + +/* Computes Tmax in deciseconds (m and Tcare in milliseconds) */ +static UCHAR _tmax(DWORD N, ULONG m, ULONG Tc) +{ + /* Tmax = N * m + Tc */ + + ULONGLONG Tmax = N * m + Tc; + + /* FIXME: look for an equivalent math function otherwise let + * do the compiler do the optimization */ + if (Tmax == 0) + return 0; + else if (Tmax < 100) + return 1; + else if (Tmax > 25500) + return 255; /* 0xFF */ + else + return Tmax/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; + ssize_t nbRead = 0; + COMMTIMEOUTS *pTimeouts; + UCHAR vmin = 0; + UCHAR vtime = 0; + struct termios currentTermios; + + 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 (lpNumberOfBytesRead == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + return FALSE; + } + + *lpNumberOfBytesRead = 0; /* will be ajusted if required ... */ + + if (nNumberOfBytesToRead <= 0) /* N */ + { + return TRUE; /* FIXME: or FALSE? */ + } + + if (tcgetattr(pComm->fd, ¤tTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + if (currentTermios.c_lflag & ICANON) + { + DEBUG_WARN("Canonical mode not supported"); /* the timeout could not be set */ + SetLastError(ERROR_NOT_SUPPORTED); + 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. FIXME: reduce N to 1? + * 0< Ti fd, 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); + return FALSE; + } + + /* VMIN */ + + if ((pTimeouts->ReadIntervalTimeout < MAXULONG) && (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0)) + { + /* should only match the two first cases above */ + vmin = nNumberOfBytesToRead < 256 ? nNumberOfBytesToRead : 255; /* 0xFF */ + } + + + /* VTIME */ + + if ((pTimeouts->ReadIntervalTimeout > 0) && (pTimeouts->ReadTotalTimeoutMultiplier == 0) && (pTimeouts->ReadTotalTimeoutConstant == 0)) + { + /* Ti */ + + vtime = _tmax(0, 0, pTimeouts->ReadIntervalTimeout); + + } + else if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && (pTimeouts->ReadTotalTimeoutMultiplier == MAXULONG) && (pTimeouts->ReadTotalTimeoutConstant < MAXULONG)) + { + /* Tc */ + + vtime = _tmax(0, 0, pTimeouts->ReadTotalTimeoutConstant); + } + else if ((pTimeouts->ReadTotalTimeoutMultiplier > 0) || (pTimeouts->ReadTotalTimeoutConstant > 0)) /* <=> Tmax > 0 */ + { + /* Tmax and Tmax(1) */ + + vtime = _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; + + // TMP: + DEBUG_MSG("Applying timeout VMIN=%u, VTIME=%u", vmin, 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); + return FALSE; + } + } + + // TMP: + DEBUG_MSG("Reading N=%u, VMIN=%u, VTIME=%u", nNumberOfBytesToRead, vmin, vtime); + + nbRead = read(pComm->fd, lpBuffer, nNumberOfBytesToRead); + + if (nbRead < 0) + { + DEBUG_WARN("CommReadFile failed, ReadIntervalTimeout=%d, ReadTotalTimeoutMultiplier=%d, ReadTotalTimeoutConstant=%d VMIN=%d, VTIME=%d", + pTimeouts->ReadIntervalTimeout, pTimeouts->ReadTotalTimeoutMultiplier, pTimeouts->ReadTotalTimeoutConstant, + currentTermios.c_cc[VMIN], currentTermios.c_cc[VTIME]); + DEBUG_WARN("CommReadFile failed, nNumberOfBytesToRead=%d, errno=[%d] %s", nNumberOfBytesToRead, errno, strerror(errno)); + + switch (errno) + { + // TMP: TODO: + case EAGAIN: /* EWOULDBLOCK */ + nbRead = 0; + break; + + case EBADF: + SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */ + return FALSE; + + default: + return FALSE; + } + + } + + // SetLastError(ERROR_TIMEOUT) + + *lpNumberOfBytesRead = nbRead; + 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; + COMMTIMEOUTS *pTimeouts; + + 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 (lpNumberOfBytesWritten == NULL) + { + SetLastError(ERROR_INVALID_PARAMETER); /* since we doesn't suppport lpOverlapped != NULL */ + return FALSE; + } + + *lpNumberOfBytesWritten = 0; /* will be ajusted if required ... */ + + if (nNumberOfBytesToWrite <= 0) + { + return TRUE; /* FIXME: or FALSE? */ + } + + // TMP: TODO: Timeouts + pTimeouts = &(pComm->timeouts); + + while (*lpNumberOfBytesWritten < nNumberOfBytesToWrite) + { + ssize_t nbWritten; + + nbWritten += write(pComm->fd, + lpBuffer + (*lpNumberOfBytesWritten), + nNumberOfBytesToWrite - (*lpNumberOfBytesWritten)); + + if (nbWritten < 0) + { + DEBUG_WARN("CommWriteFile failed after %d bytes written, errno=[%d] %s\n", *lpNumberOfBytesWritten, errno, strerror(errno)); + + switch (errno) + { + case EAGAIN: + /* EWOULDBLOCK */ + // TMP: FIXME: fcntl(tty->fd, F_SETFL, O_NONBLOCK) and manage the timeout here? + break; + case EBADF: + SetLastError(ERROR_BAD_DEVICE); /* STATUS_INVALID_DEVICE_REQUEST */ + return FALSE; + } + + return FALSE; + } + + *lpNumberOfBytesWritten += nbWritten; + } + + + return TRUE; +} + + +#endif /* _WIN32 */ diff --git a/winpr/libwinpr/comm/comm_ioctl.c b/winpr/libwinpr/comm/comm_ioctl.c index 0b8273a20..346d10f77 100644 --- a/winpr/libwinpr/comm/comm_ioctl.c +++ b/winpr/libwinpr/comm/comm_ioctl.c @@ -61,8 +61,10 @@ * * 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) @@ -71,6 +73,12 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe WINPR_COMM* pComm = (WINPR_COMM*) hDevice; REMOTE_SERIAL_DRIVER* pRemoteSerialDriver = NULL; + if (hDevice == INVALID_HANDLE_VALUE) + { + SetLastError(ERROR_INVALID_HANDLE); + return FALSE; + } + if (!pComm || pComm->Type != HANDLE_TYPE_COMM || !pComm->fd ) { SetLastError(ERROR_INVALID_HANDLE); @@ -85,12 +93,14 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe if (lpBytesReturned == NULL) { - SetLastError(ERROR_INVALID_DATA); /* since we doesn't suppport lpOverlapped != 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 @@ -111,7 +121,7 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe case RemoteSerialDriverUnknown: default: - DEBUG_WARN("Unknown remote serial driver (%d), using SerCx2.sys", pComm->remoteSerialDriverId); + DEBUG_MSG("Unknown remote serial driver (%d), using SerCx2.sys", pComm->remoteSerialDriverId); pRemoteSerialDriver = SerCx2Sys_s(); break; } @@ -129,7 +139,7 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe assert(nInBufferSize >= sizeof(SERIAL_BAUD_RATE)); if (nInBufferSize < sizeof(SERIAL_BAUD_RATE)) { - SetLastError(ERROR_INVALID_DATA); + SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } @@ -188,7 +198,7 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe assert(nInBufferSize >= sizeof(SERIAL_CHARS)); if (nInBufferSize < sizeof(SERIAL_CHARS)) { - SetLastError(ERROR_INVALID_DATA); + SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } @@ -226,7 +236,7 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe assert(nInBufferSize >= sizeof(SERIAL_LINE_CONTROL)); if (nInBufferSize < sizeof(SERIAL_LINE_CONTROL)) { - SetLastError(ERROR_INVALID_DATA); + SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } @@ -264,7 +274,7 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe assert(nInBufferSize >= sizeof(SERIAL_HANDFLOW)); if (nInBufferSize < sizeof(SERIAL_HANDFLOW)) { - SetLastError(ERROR_INVALID_DATA); + SetLastError(ERROR_INVALID_PARAMETER); return FALSE; } @@ -293,10 +303,49 @@ BOOL CommDeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, LPVOID lpInBuffe } break; } + case IOCTL_SERIAL_SET_TIMEOUTS: + { + if (pRemoteSerialDriver->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 pRemoteSerialDriver->set_timeouts(pComm, pHandflow); + } + break; + } + case IOCTL_SERIAL_GET_TIMEOUTS: + { + if (pRemoteSerialDriver->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 (!pRemoteSerialDriver->get_timeouts(pComm, pHandflow)) + return FALSE; + + *lpBytesReturned = sizeof(SERIAL_TIMEOUTS); + return TRUE; + } + break; + } } DEBUG_WARN(_T("unsupported IoControlCode: Ox%0.8x (remote serial driver: %s)"), dwIoControlCode, pRemoteSerialDriver->name); + SetLastError(ERROR_CALL_NOT_IMPLEMENTED); return FALSE; } @@ -323,7 +372,7 @@ int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios *te if (memcmp(¤tState, &termios_p, sizeof(struct termios)) != 0) { - DEBUG_WARN("all termios parameters were not set, doing a second attempt..."); + 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); @@ -339,7 +388,7 @@ int _comm_ioctl_tcsetattr(int fd, int optional_actions, const struct termios *te if (memcmp(¤tState, termios_p, sizeof(struct termios)) != 0) { - DEBUG_WARN("Failure: all parameters were not set on a second attempt."); + DEBUG_WARN("Failure: all termios parameters are still not set on a second attempt"); return -1; /* TMP: double-check whether some parameters can differ anyway */ } } diff --git a/winpr/libwinpr/comm/comm_ioctl.h b/winpr/libwinpr/comm/comm_ioctl.h index 5ef8ff407..84261b79b 100644 --- a/winpr/libwinpr/comm/comm_ioctl.h +++ b/winpr/libwinpr/comm/comm_ioctl.h @@ -45,9 +45,8 @@ extern "C" { #define IOCTL_SERIAL_GET_BAUD_RATE 0x001B0050 #define IOCTL_SERIAL_SET_LINE_CONTROL 0x001B000C #define IOCTL_SERIAL_GET_LINE_CONTROL 0x001B0054 -/* IOCTL_SERIAL_SET_TIMEOUTS 0x001B001C */ -/* IOCTL_SERIAL_GET_TIMEOUTS 0x001B0020 */ - +#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 @@ -179,6 +178,17 @@ typedef struct _SERIAL_HANDFLOW #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; + + /** * A function might be NULL if not supported by the underlying remote driver. * @@ -197,6 +207,8 @@ typedef struct _REMOTE_SERIAL_DRIVER 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); } REMOTE_SERIAL_DRIVER; diff --git a/winpr/libwinpr/comm/comm_sercx2_sys.c b/winpr/libwinpr/comm/comm_sercx2_sys.c index 7f657f827..1b870eec5 100644 --- a/winpr/libwinpr/comm/comm_sercx2_sys.c +++ b/winpr/libwinpr/comm/comm_sercx2_sys.c @@ -66,6 +66,8 @@ static REMOTE_SERIAL_DRIVER _SerCx2Sys = .get_line_control = NULL, .set_handflow = NULL, .get_handflow = NULL, + .set_timeouts = NULL, + .get_timeouts = NULL, }; @@ -89,6 +91,9 @@ REMOTE_SERIAL_DRIVER* SerCx2Sys_s() _SerCx2Sys.set_handflow = pSerialSys->set_handflow; _SerCx2Sys.get_handflow = pSerialSys->get_handflow; + _SerCx2Sys.set_timeouts = pSerialSys->set_timeouts; + _SerCx2Sys.get_timeouts = pSerialSys->get_timeouts; + return &_SerCx2Sys; } diff --git a/winpr/libwinpr/comm/comm_sercx_sys.c b/winpr/libwinpr/comm/comm_sercx_sys.c index 8dad85738..d3276fd8b 100644 --- a/winpr/libwinpr/comm/comm_sercx_sys.c +++ b/winpr/libwinpr/comm/comm_sercx_sys.c @@ -188,7 +188,7 @@ static BOOL _set_baud_rate(WINPR_COMM *pComm, const SERIAL_BAUD_RATE *pBaudRate) if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0) { - DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x0.8x", GetLastError()); + DEBUG_WARN("_comm_ioctl_tcsetattr failure: last-error: 0x%0.8x", GetLastError()); return FALSE; } @@ -507,6 +507,8 @@ static REMOTE_SERIAL_DRIVER _SerCxSys = .get_line_control = NULL, .set_handflow = _set_handflow, .get_handflow = _get_handflow, + .set_timeouts = NULL, + .get_timeouts = NULL, }; @@ -521,6 +523,9 @@ REMOTE_SERIAL_DRIVER* SerCxSys_s() _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; + return &_SerCxSys; } diff --git a/winpr/libwinpr/comm/comm_serial_sys.c b/winpr/libwinpr/comm/comm_serial_sys.c index aaf5fe64c..66f95c84d 100644 --- a/winpr/libwinpr/comm/comm_serial_sys.c +++ b/winpr/libwinpr/comm/comm_serial_sys.c @@ -827,6 +827,38 @@ static BOOL _get_handflow(WINPR_COMM *pComm, SERIAL_HANDFLOW *pHandflow) 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 REMOTE_SERIAL_DRIVER _SerialSys = { @@ -841,6 +873,8 @@ static REMOTE_SERIAL_DRIVER _SerialSys = .get_line_control = _get_line_control, .set_handflow = _set_handflow, .get_handflow = _get_handflow, + .set_timeouts = _set_timeouts, + .get_timeouts = _get_timeouts, }; diff --git a/winpr/libwinpr/comm/test/CMakeLists.txt b/winpr/libwinpr/comm/test/CMakeLists.txt index d81c3228c..dcf737f3a 100644 --- a/winpr/libwinpr/comm/test/CMakeLists.txt +++ b/winpr/libwinpr/comm/test/CMakeLists.txt @@ -12,6 +12,7 @@ set(${MODULE_PREFIX}_TESTS TestSerialChars.c TestControlSettings.c TestHandflow.c + TestTimeouts.c TestCommMonitor.c) create_test_sourcelist(${MODULE_PREFIX}_SRCS diff --git a/winpr/libwinpr/comm/test/TestTimeouts.c b/winpr/libwinpr/comm/test/TestTimeouts.c new file mode 100644 index 000000000..4799eb794 --- /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_setRemoteSerialDriver(hComm, RemoteSerialDriverSerialSys); + if (!test_generic(hComm)) + { + fprintf(stderr, "test_SerialSys failure\n"); + return EXIT_FAILURE; + } + + _comm_setRemoteSerialDriver(hComm, RemoteSerialDriverSerCxSys); + if (!test_generic(hComm)) + { + fprintf(stderr, "test_SerCxSys failure\n"); + return EXIT_FAILURE; + } + + _comm_setRemoteSerialDriver(hComm, RemoteSerialDriverSerCx2Sys); + 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; +}