diff options
Diffstat (limited to 'winpr/libwinpr/comm/comm_serial_sys.c')
-rw-r--r-- | winpr/libwinpr/comm/comm_serial_sys.c | 1637 |
1 files changed, 1637 insertions, 0 deletions
diff --git a/winpr/libwinpr/comm/comm_serial_sys.c b/winpr/libwinpr/comm/comm_serial_sys.c new file mode 100644 index 0000000..cae653c --- /dev/null +++ b/winpr/libwinpr/comm/comm_serial_sys.c @@ -0,0 +1,1637 @@ +/** + * WinPR: Windows Portable Runtime + * Serial Communication API + * + * Copyright 2011 O.S. Systems Software Ltda. + * Copyright 2011 Eduardo Fiss Beloni <beloni@ossystems.com.br> + * Copyright 2014 Marc-Andre Moreau <marcandre.moreau@gmail.com> + * Copyright 2014 Hewlett-Packard Development Company, L.P. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#if defined __linux__ && !defined ANDROID + +#include <winpr/assert.h> +#include <errno.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> + +#include "comm_serial_sys.h" +#ifdef __UCLIBC__ +#include "comm.h" +#endif + +#include <winpr/crt.h> +#include <winpr/wlog.h> + +/* Undocumented flag, not supported everywhere. + * Provide a sensible fallback to avoid compilation problems. */ +#ifndef CMSPAR +#define CMSPAR 010000000000 +#endif + +/* hard-coded in N_TTY */ +#define TTY_THRESHOLD_THROTTLE 128 /* now based on remaining room */ +#define TTY_THRESHOLD_UNTHROTTLE 128 +#define N_TTY_BUF_SIZE 4096 + +#define BAUD_TABLE_END 0010020 /* __MAX_BAUD + 1 */ + +/* 0: B* (Linux termios) + * 1: CBR_* or actual baud rate + * 2: BAUD_* (identical to SERIAL_BAUD_*) + */ +static const speed_t _BAUD_TABLE[][3] = { +#ifdef B0 + { B0, 0, 0 }, /* hang up */ +#endif +#ifdef B50 + { B50, 50, 0 }, +#endif +#ifdef B75 + { B75, 75, BAUD_075 }, +#endif +#ifdef B110 + { B110, CBR_110, BAUD_110 }, +#endif +#ifdef B134 + { B134, 134, 0 /*BAUD_134_5*/ }, +#endif +#ifdef B150 + { B150, 150, BAUD_150 }, +#endif +#ifdef B200 + { B200, 200, 0 }, +#endif +#ifdef B300 + { B300, CBR_300, BAUD_300 }, +#endif +#ifdef B600 + { B600, CBR_600, BAUD_600 }, +#endif +#ifdef B1200 + { B1200, CBR_1200, BAUD_1200 }, +#endif +#ifdef B1800 + { B1800, 1800, BAUD_1800 }, +#endif +#ifdef B2400 + { B2400, CBR_2400, BAUD_2400 }, +#endif +#ifdef B4800 + { B4800, CBR_4800, BAUD_4800 }, +#endif +/* {, ,BAUD_7200} */ +#ifdef B9600 + { B9600, CBR_9600, BAUD_9600 }, +#endif +/* {, CBR_14400, BAUD_14400}, /\* unsupported on Linux *\/ */ +#ifdef B19200 + { B19200, CBR_19200, BAUD_19200 }, +#endif +#ifdef B38400 + { B38400, CBR_38400, BAUD_38400 }, +#endif +/* {, CBR_56000, BAUD_56K}, /\* unsupported on Linux *\/ */ +#ifdef B57600 + { B57600, CBR_57600, BAUD_57600 }, +#endif +#ifdef B115200 + { B115200, CBR_115200, BAUD_115200 }, +#endif +/* {, CBR_128000, BAUD_128K}, /\* unsupported on Linux *\/ */ +/* {, CBR_256000, BAUD_USER}, /\* unsupported on Linux *\/ */ +#ifdef B230400 + { B230400, 230400, BAUD_USER }, +#endif +#ifdef B460800 + { B460800, 460800, BAUD_USER }, +#endif +#ifdef B500000 + { B500000, 500000, BAUD_USER }, +#endif +#ifdef B576000 + { B576000, 576000, BAUD_USER }, +#endif +#ifdef B921600 + { B921600, 921600, BAUD_USER }, +#endif +#ifdef B1000000 + { B1000000, 1000000, BAUD_USER }, +#endif +#ifdef B1152000 + { B1152000, 1152000, BAUD_USER }, +#endif +#ifdef B1500000 + { B1500000, 1500000, BAUD_USER }, +#endif +#ifdef B2000000 + { B2000000, 2000000, BAUD_USER }, +#endif +#ifdef B2500000 + { B2500000, 2500000, BAUD_USER }, +#endif +#ifdef B3000000 + { B3000000, 3000000, BAUD_USER }, +#endif +#ifdef B3500000 + { B3500000, 3500000, BAUD_USER }, +#endif +#ifdef B4000000 + { B4000000, 4000000, BAUD_USER }, /* __MAX_BAUD */ +#endif + { BAUD_TABLE_END, 0, 0 } +}; + +static BOOL _get_properties(WINPR_COMM* pComm, COMMPROP* pProperties) +{ + /* http://msdn.microsoft.com/en-us/library/windows/hardware/jj680684%28v=vs.85%29.aspx + * http://msdn.microsoft.com/en-us/library/windows/desktop/aa363189%28v=vs.85%29.aspx + */ + + /* FIXME: properties should be better probed. The current + * implementation just relies on the Linux' implementation. + */ + WINPR_ASSERT(pProperties); + if (pProperties->dwProvSpec1 != COMMPROP_INITIALIZED) + { + ZeroMemory(pProperties, sizeof(COMMPROP)); + pProperties->wPacketLength = sizeof(COMMPROP); + } + + pProperties->wPacketVersion = 2; + + pProperties->dwServiceMask = SERIAL_SP_SERIALCOMM; + + /* pProperties->Reserved1; not used */ + + /* FIXME: could be implemented on top of N_TTY */ + pProperties->dwMaxTxQueue = N_TTY_BUF_SIZE; + pProperties->dwMaxRxQueue = N_TTY_BUF_SIZE; + + /* FIXME: to be probe on the device? */ + pProperties->dwMaxBaud = BAUD_USER; + + /* FIXME: what about PST_RS232? see also: serial_struct */ + pProperties->dwProvSubType = PST_UNSPECIFIED; + + /* TODO: to be finalized */ + pProperties->dwProvCapabilities = + /*PCF_16BITMODE |*/ PCF_DTRDSR | PCF_INTTIMEOUTS | PCF_PARITY_CHECK | /*PCF_RLSD |*/ + PCF_RTSCTS | PCF_SETXCHAR | /*PCF_SPECIALCHARS |*/ PCF_TOTALTIMEOUTS | PCF_XONXOFF; + + /* TODO: double check SP_RLSD */ + pProperties->dwSettableParams = SP_BAUD | SP_DATABITS | SP_HANDSHAKING | SP_PARITY | + SP_PARITY_CHECK | /*SP_RLSD |*/ SP_STOPBITS; + + pProperties->dwSettableBaud = 0; + for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++) + { + pProperties->dwSettableBaud |= _BAUD_TABLE[i][2]; + } + + pProperties->wSettableData = + DATABITS_5 | DATABITS_6 | DATABITS_7 | DATABITS_8 /*| DATABITS_16 | DATABITS_16X*/; + + pProperties->wSettableStopParity = STOPBITS_10 | /*STOPBITS_15 |*/ STOPBITS_20 | PARITY_NONE | + PARITY_ODD | PARITY_EVEN | PARITY_MARK | PARITY_SPACE; + + /* FIXME: additional input and output buffers could be implemented on top of N_TTY */ + pProperties->dwCurrentTxQueue = N_TTY_BUF_SIZE; + pProperties->dwCurrentRxQueue = N_TTY_BUF_SIZE; + + /* pProperties->ProvSpec1; see above */ + /* pProperties->ProvSpec2; ignored */ + /* pProperties->ProvChar[1]; ignored */ + + return TRUE; +} + +static BOOL _set_baud_rate(WINPR_COMM* pComm, const SERIAL_BAUD_RATE* pBaudRate) +{ + speed_t newSpeed = 0; + struct termios futureState; + + ZeroMemory(&futureState, sizeof(struct termios)); + if (tcgetattr(pComm->fd, &futureState) < + 0) /* NB: preserves current settings not directly handled by the Communication Functions */ + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++) + { + if (_BAUD_TABLE[i][1] == pBaudRate->BaudRate) + { + newSpeed = _BAUD_TABLE[i][0]; + if (cfsetspeed(&futureState, newSpeed) < 0) + { + CommLog_Print(WLOG_WARN, "failed to set speed 0x%x (%" PRIu32 ")", newSpeed, + pBaudRate->BaudRate); + return FALSE; + } + + WINPR_ASSERT(cfgetispeed(&futureState) == newSpeed); + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &futureState) < 0) + { + CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "", + GetLastError()); + return FALSE; + } + + return TRUE; + } + } + + CommLog_Print(WLOG_WARN, "could not find a matching speed for the baud rate %" PRIu32 "", + pBaudRate->BaudRate); + SetLastError(ERROR_INVALID_DATA); + return FALSE; +} + +static BOOL _get_baud_rate(WINPR_COMM* pComm, SERIAL_BAUD_RATE* pBaudRate) +{ + speed_t currentSpeed = 0; + struct termios currentState; + + ZeroMemory(¤tState, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tState) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + currentSpeed = cfgetispeed(¤tState); + + for (int i = 0; _BAUD_TABLE[i][0] < BAUD_TABLE_END; i++) + { + if (_BAUD_TABLE[i][0] == currentSpeed) + { + pBaudRate->BaudRate = _BAUD_TABLE[i][1]; + return TRUE; + } + } + + CommLog_Print(WLOG_WARN, "could not find a matching baud rate for the speed 0x%x", + currentSpeed); + SetLastError(ERROR_INVALID_DATA); + return FALSE; +} + +/** + * NOTE: Only XonChar and XoffChar are plenty supported with the Linux + * N_TTY line discipline. + * + * ERRORS: + * ERROR_IO_DEVICE + * ERROR_INVALID_PARAMETER when Xon and Xoff chars are the same; + * ERROR_NOT_SUPPORTED + */ +static BOOL _set_serial_chars(WINPR_COMM* pComm, const SERIAL_CHARS* pSerialChars) +{ + BOOL result = TRUE; + struct termios upcomingTermios; + + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + if (pSerialChars->XonChar == pSerialChars->XoffChar) + { + /* https://msdn.microsoft.com/en-us/library/windows/hardware/ff546688?v=vs.85.aspx */ + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + /* termios(3): (..) above symbolic subscript values are all + * different, except that VTIME, VMIN may have the same value + * as VEOL, VEOF, respectively. In noncanonical mode the + * special character meaning is replaced by the timeout + * meaning. + * + * EofChar and c_cc[VEOF] are not quite the same, prefer to + * don't use c_cc[VEOF] at all. + * + * FIXME: might be implemented during read/write I/O + */ + if (pSerialChars->EofChar != '\0') + { + CommLog_Print(WLOG_WARN, "EofChar %02" PRIX8 " cannot be set\n", pSerialChars->EofChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* According the Linux's n_tty discipline, charaters with a + * parity error can only be let unchanged, replaced by \0 or + * get the prefix the prefix \377 \0 + */ + + /* FIXME: see also: _set_handflow() */ + if (pSerialChars->ErrorChar != '\0') + { + CommLog_Print(WLOG_WARN, "ErrorChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n", + pSerialChars->ErrorChar, (char)pSerialChars->ErrorChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* FIXME: see also: _set_handflow() */ + if (pSerialChars->BreakChar != '\0') + { + CommLog_Print(WLOG_WARN, "BreakChar 0x%02" PRIX8 " ('%c') cannot be set (unsupported).\n", + pSerialChars->BreakChar, (char)pSerialChars->BreakChar); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + if (pSerialChars->EventChar != '\0') + { + pComm->eventChar = pSerialChars->EventChar; + } + + upcomingTermios.c_cc[VSTART] = pSerialChars->XonChar; + + upcomingTermios.c_cc[VSTOP] = pSerialChars->XoffChar; + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "", + GetLastError()); + return FALSE; + } + + return result; +} + +static BOOL _get_serial_chars(WINPR_COMM* pComm, SERIAL_CHARS* pSerialChars) +{ + struct termios currentTermios; + + ZeroMemory(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + ZeroMemory(pSerialChars, sizeof(SERIAL_CHARS)); + + /* EofChar unsupported */ + + /* ErrorChar unsupported */ + + /* BreakChar unsupported */ + + /* FIXME: see also: _set_serial_chars() */ + /* EventChar */ + + pSerialChars->XonChar = currentTermios.c_cc[VSTART]; + + pSerialChars->XoffChar = currentTermios.c_cc[VSTOP]; + + return TRUE; +} + +static BOOL _set_line_control(WINPR_COMM* pComm, const SERIAL_LINE_CONTROL* pLineControl) +{ + BOOL result = TRUE; + struct termios upcomingTermios; + + /* http://msdn.microsoft.com/en-us/library/windows/desktop/aa363214%28v=vs.85%29.aspx + * + * The use of 5 data bits with 2 stop bits is an invalid + * combination, as is 6, 7, or 8 data bits with 1.5 stop bits. + * + * FIXME: prefered to let the underlying driver to deal with + * this issue. At least produce a warning message? + */ + + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + /* FIXME: use of a COMMPROP to validate new settings? */ + + switch (pLineControl->StopBits) + { + case STOP_BIT_1: + upcomingTermios.c_cflag &= ~CSTOPB; + break; + + case STOP_BITS_1_5: + CommLog_Print(WLOG_WARN, "Unsupported one and a half stop bits."); + break; + + case STOP_BITS_2: + upcomingTermios.c_cflag |= CSTOPB; + break; + + default: + CommLog_Print(WLOG_WARN, "unexpected number of stop bits: %" PRIu8 "\n", + pLineControl->StopBits); + result = FALSE; /* but keep on */ + break; + } + + switch (pLineControl->Parity) + { + case NO_PARITY: + upcomingTermios.c_cflag &= ~(PARENB | PARODD | CMSPAR); + break; + + case ODD_PARITY: + upcomingTermios.c_cflag &= ~CMSPAR; + upcomingTermios.c_cflag |= PARENB | PARODD; + break; + + case EVEN_PARITY: + upcomingTermios.c_cflag &= ~(PARODD | CMSPAR); + upcomingTermios.c_cflag |= PARENB; + break; + + case MARK_PARITY: + upcomingTermios.c_cflag |= PARENB | PARODD | CMSPAR; + break; + + case SPACE_PARITY: + upcomingTermios.c_cflag &= ~PARODD; + upcomingTermios.c_cflag |= PARENB | CMSPAR; + break; + + default: + CommLog_Print(WLOG_WARN, "unexpected type of parity: %" PRIu8 "\n", + pLineControl->Parity); + result = FALSE; /* but keep on */ + break; + } + + switch (pLineControl->WordLength) + { + case 5: + upcomingTermios.c_cflag &= ~CSIZE; + upcomingTermios.c_cflag |= CS5; + break; + + case 6: + upcomingTermios.c_cflag &= ~CSIZE; + upcomingTermios.c_cflag |= CS6; + break; + + case 7: + upcomingTermios.c_cflag &= ~CSIZE; + upcomingTermios.c_cflag |= CS7; + break; + + case 8: + upcomingTermios.c_cflag &= ~CSIZE; + upcomingTermios.c_cflag |= CS8; + break; + + default: + CommLog_Print(WLOG_WARN, "unexpected number od data bits per character: %" PRIu8 "\n", + pLineControl->WordLength); + result = FALSE; /* but keep on */ + break; + } + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%08" PRIX32 "", + GetLastError()); + return FALSE; + } + + return result; +} + +static BOOL _get_line_control(WINPR_COMM* pComm, SERIAL_LINE_CONTROL* pLineControl) +{ + struct termios currentTermios; + + ZeroMemory(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + pLineControl->StopBits = (currentTermios.c_cflag & CSTOPB) ? STOP_BITS_2 : STOP_BIT_1; + + if (!(currentTermios.c_cflag & PARENB)) + { + pLineControl->Parity = NO_PARITY; + } + else if (currentTermios.c_cflag & CMSPAR) + { + pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? MARK_PARITY : SPACE_PARITY; + } + else + { + /* PARENB is set */ + pLineControl->Parity = (currentTermios.c_cflag & PARODD) ? ODD_PARITY : EVEN_PARITY; + } + + switch (currentTermios.c_cflag & CSIZE) + { + case CS5: + pLineControl->WordLength = 5; + break; + case CS6: + pLineControl->WordLength = 6; + break; + case CS7: + pLineControl->WordLength = 7; + break; + default: + pLineControl->WordLength = 8; + break; + } + + return TRUE; +} + +static BOOL _set_handflow(WINPR_COMM* pComm, const SERIAL_HANDFLOW* pHandflow) +{ + BOOL result = TRUE; + struct termios upcomingTermios; + + ZeroMemory(&upcomingTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, &upcomingTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + /* HUPCL */ + + /* logical XOR */ + if ((!(pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) && + (pHandflow->FlowReplace & SERIAL_RTS_CONTROL)) || + ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) && + !(pHandflow->FlowReplace & SERIAL_RTS_CONTROL))) + { + CommLog_Print(WLOG_WARN, + "SERIAL_DTR_CONTROL:%s and SERIAL_RTS_CONTROL:%s cannot be different, HUPCL " + "will be set since it is claimed for one of the both lines.", + (pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) ? "ON" : "OFF", + (pHandflow->FlowReplace & SERIAL_RTS_CONTROL) ? "ON" : "OFF"); + } + + if ((pHandflow->ControlHandShake & SERIAL_DTR_CONTROL) || + (pHandflow->FlowReplace & SERIAL_RTS_CONTROL)) + { + upcomingTermios.c_cflag |= HUPCL; + } + else + { + upcomingTermios.c_cflag &= ~HUPCL; + + /* FIXME: is the DTR line also needs to be forced to a disable state according + * SERIAL_DTR_CONTROL? */ + /* FIXME: is the RTS line also needs to be forced to a disable state according + * SERIAL_RTS_CONTROL? */ + } + + /* CRTSCTS */ + + /* logical XOR */ + if ((!(pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) && + (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)) || + ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) && + !(pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE))) + { + CommLog_Print(WLOG_WARN, + "SERIAL_CTS_HANDSHAKE:%s and SERIAL_RTS_HANDSHAKE:%s cannot be different, " + "CRTSCTS will be set since it is claimed for one of the both lines.", + (pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) ? "ON" : "OFF", + (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE) ? "ON" : "OFF"); + } + + if ((pHandflow->ControlHandShake & SERIAL_CTS_HANDSHAKE) || + (pHandflow->FlowReplace & SERIAL_RTS_HANDSHAKE)) + { + upcomingTermios.c_cflag |= CRTSCTS; + } + else + { + upcomingTermios.c_cflag &= ~CRTSCTS; + } + + /* ControlHandShake */ + + if (pHandflow->ControlHandShake & SERIAL_DTR_HANDSHAKE) + { + /* DTR/DSR flow control not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DTR_HANDSHAKE feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + if (pHandflow->ControlHandShake & SERIAL_DSR_HANDSHAKE) + { + /* DTR/DSR flow control not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_HANDSHAKE feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + if (pHandflow->ControlHandShake & SERIAL_DCD_HANDSHAKE) + { + /* DCD flow control not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DCD_HANDSHAKE feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + // FIXME: could be implemented during read/write I/O + if (pHandflow->ControlHandShake & SERIAL_DSR_SENSITIVITY) + { + /* DSR line control not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_DSR_SENSITIVITY feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + // FIXME: could be implemented during read/write I/O + if (pHandflow->ControlHandShake & SERIAL_ERROR_ABORT) + { + /* Aborting operations on error not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_ERROR_ABORT feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* FlowReplace */ + + if (pHandflow->FlowReplace & SERIAL_AUTO_TRANSMIT) + { + upcomingTermios.c_iflag |= IXON; + } + else + { + upcomingTermios.c_iflag &= ~IXON; + } + + if (pHandflow->FlowReplace & SERIAL_AUTO_RECEIVE) + { + upcomingTermios.c_iflag |= IXOFF; + } + else + { + upcomingTermios.c_iflag &= ~IXOFF; + } + + // FIXME: could be implemented during read/write I/O, as of today ErrorChar is necessary '\0' + if (pHandflow->FlowReplace & SERIAL_ERROR_CHAR) + { + /* errors will be replaced by the character '\0'. */ + upcomingTermios.c_iflag &= ~IGNPAR; + } + else + { + upcomingTermios.c_iflag |= IGNPAR; + } + + if (pHandflow->FlowReplace & SERIAL_NULL_STRIPPING) + { + upcomingTermios.c_iflag |= IGNBRK; + } + else + { + upcomingTermios.c_iflag &= ~IGNBRK; + } + + // FIXME: could be implemented during read/write I/O + if (pHandflow->FlowReplace & SERIAL_BREAK_CHAR) + { + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_BREAK_CHAR feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + // FIXME: could be implemented during read/write I/O + if (pHandflow->FlowReplace & SERIAL_XOFF_CONTINUE) + { + /* not supported on Linux */ + CommLog_Print(WLOG_WARN, "Attempt to use the unsupported SERIAL_XOFF_CONTINUE feature."); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* XonLimit */ + + // FIXME: could be implemented during read/write I/O + if (pHandflow->XonLimit != TTY_THRESHOLD_UNTHROTTLE) + { + CommLog_Print(WLOG_WARN, "Attempt to set XonLimit with an unsupported value: %" PRId32 "", + pHandflow->XonLimit); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + /* XoffChar */ + + // FIXME: could be implemented during read/write I/O + if (pHandflow->XoffLimit != TTY_THRESHOLD_THROTTLE) + { + CommLog_Print(WLOG_WARN, "Attempt to set XoffLimit with an unsupported value: %" PRId32 "", + pHandflow->XoffLimit); + SetLastError(ERROR_NOT_SUPPORTED); + result = FALSE; /* but keep on */ + } + + if (_comm_ioctl_tcsetattr(pComm->fd, TCSANOW, &upcomingTermios) < 0) + { + CommLog_Print(WLOG_WARN, "_comm_ioctl_tcsetattr failure: last-error: 0x%" PRIX32 "", + GetLastError()); + return FALSE; + } + + return result; +} + +static BOOL _get_handflow(WINPR_COMM* pComm, SERIAL_HANDFLOW* pHandflow) +{ + struct termios currentTermios; + + ZeroMemory(¤tTermios, sizeof(struct termios)); + if (tcgetattr(pComm->fd, ¤tTermios) < 0) + { + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + /* ControlHandShake */ + + pHandflow->ControlHandShake = 0; + + if (currentTermios.c_cflag & HUPCL) + pHandflow->ControlHandShake |= SERIAL_DTR_CONTROL; + + /* SERIAL_DTR_HANDSHAKE unsupported */ + + if (currentTermios.c_cflag & CRTSCTS) + pHandflow->ControlHandShake |= SERIAL_CTS_HANDSHAKE; + + /* SERIAL_DSR_HANDSHAKE unsupported */ + + /* SERIAL_DCD_HANDSHAKE unsupported */ + + /* SERIAL_DSR_SENSITIVITY unsupported */ + + /* SERIAL_ERROR_ABORT unsupported */ + + /* FlowReplace */ + + pHandflow->FlowReplace = 0; + + if (currentTermios.c_iflag & IXON) + pHandflow->FlowReplace |= SERIAL_AUTO_TRANSMIT; + + if (currentTermios.c_iflag & IXOFF) + pHandflow->FlowReplace |= SERIAL_AUTO_RECEIVE; + + if (!(currentTermios.c_iflag & IGNPAR)) + pHandflow->FlowReplace |= SERIAL_ERROR_CHAR; + + if (currentTermios.c_iflag & IGNBRK) + pHandflow->FlowReplace |= SERIAL_NULL_STRIPPING; + + /* SERIAL_BREAK_CHAR unsupported */ + + if (currentTermios.c_cflag & HUPCL) + pHandflow->FlowReplace |= SERIAL_RTS_CONTROL; + + if (currentTermios.c_cflag & CRTSCTS) + pHandflow->FlowReplace |= SERIAL_RTS_HANDSHAKE; + + /* SERIAL_XOFF_CONTINUE unsupported */ + + /* XonLimit */ + + pHandflow->XonLimit = TTY_THRESHOLD_UNTHROTTLE; + + /* XoffLimit */ + + pHandflow->XoffLimit = TTY_THRESHOLD_THROTTLE; + + return TRUE; +} + +static BOOL _set_timeouts(WINPR_COMM* pComm, const SERIAL_TIMEOUTS* pTimeouts) +{ + /* NB: timeouts are applied on system during read/write I/O */ + + /* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439614%28v=vs.85%29.aspx */ + if ((pTimeouts->ReadIntervalTimeout == MAXULONG) && + (pTimeouts->ReadTotalTimeoutConstant == MAXULONG)) + { + CommLog_Print( + WLOG_WARN, + "ReadIntervalTimeout and ReadTotalTimeoutConstant cannot be both set to MAXULONG"); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + pComm->timeouts.ReadIntervalTimeout = pTimeouts->ReadIntervalTimeout; + pComm->timeouts.ReadTotalTimeoutMultiplier = pTimeouts->ReadTotalTimeoutMultiplier; + pComm->timeouts.ReadTotalTimeoutConstant = pTimeouts->ReadTotalTimeoutConstant; + pComm->timeouts.WriteTotalTimeoutMultiplier = pTimeouts->WriteTotalTimeoutMultiplier; + pComm->timeouts.WriteTotalTimeoutConstant = pTimeouts->WriteTotalTimeoutConstant; + + CommLog_Print(WLOG_DEBUG, "ReadIntervalTimeout %" PRIu32 "", + pComm->timeouts.ReadIntervalTimeout); + CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutMultiplier %" PRIu32 "", + pComm->timeouts.ReadTotalTimeoutMultiplier); + CommLog_Print(WLOG_DEBUG, "ReadTotalTimeoutConstant %" PRIu32 "", + pComm->timeouts.ReadTotalTimeoutConstant); + CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutMultiplier %" PRIu32 "", + pComm->timeouts.WriteTotalTimeoutMultiplier); + CommLog_Print(WLOG_DEBUG, "WriteTotalTimeoutConstant %" PRIu32 "", + pComm->timeouts.WriteTotalTimeoutConstant); + + return TRUE; +} + +static BOOL _get_timeouts(WINPR_COMM* pComm, SERIAL_TIMEOUTS* pTimeouts) +{ + pTimeouts->ReadIntervalTimeout = pComm->timeouts.ReadIntervalTimeout; + pTimeouts->ReadTotalTimeoutMultiplier = pComm->timeouts.ReadTotalTimeoutMultiplier; + pTimeouts->ReadTotalTimeoutConstant = pComm->timeouts.ReadTotalTimeoutConstant; + pTimeouts->WriteTotalTimeoutMultiplier = pComm->timeouts.WriteTotalTimeoutMultiplier; + pTimeouts->WriteTotalTimeoutConstant = pComm->timeouts.WriteTotalTimeoutConstant; + + return TRUE; +} + +static BOOL _set_lines(WINPR_COMM* pComm, UINT32 lines) +{ + if (ioctl(pComm->fd, TIOCMBIS, &lines) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCMBIS ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines, + errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _clear_lines(WINPR_COMM* pComm, UINT32 lines) +{ + if (ioctl(pComm->fd, TIOCMBIC, &lines) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCMBIC ioctl failed, lines=0x%" PRIX32 ", errno=[%d] %s", lines, + errno, winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _set_dtr(WINPR_COMM* pComm) +{ + SERIAL_HANDFLOW handflow; + if (!_get_handflow(pComm, &handflow)) + return FALSE; + + /* SERIAL_DTR_HANDSHAKE not supported as of today */ + WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0); + + if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return _set_lines(pComm, TIOCM_DTR); +} + +static BOOL _clear_dtr(WINPR_COMM* pComm) +{ + SERIAL_HANDFLOW handflow; + if (!_get_handflow(pComm, &handflow)) + return FALSE; + + /* SERIAL_DTR_HANDSHAKE not supported as of today */ + WINPR_ASSERT((handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) == 0); + + if (handflow.ControlHandShake & SERIAL_DTR_HANDSHAKE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return _clear_lines(pComm, TIOCM_DTR); +} + +static BOOL _set_rts(WINPR_COMM* pComm) +{ + SERIAL_HANDFLOW handflow; + if (!_get_handflow(pComm, &handflow)) + return FALSE; + + if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return _set_lines(pComm, TIOCM_RTS); +} + +static BOOL _clear_rts(WINPR_COMM* pComm) +{ + SERIAL_HANDFLOW handflow; + if (!_get_handflow(pComm, &handflow)) + return FALSE; + + if (handflow.FlowReplace & SERIAL_RTS_HANDSHAKE) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + return _clear_lines(pComm, TIOCM_RTS); +} + +static BOOL _get_modemstatus(WINPR_COMM* pComm, ULONG* pRegister) +{ + UINT32 lines = 0; + if (ioctl(pComm->fd, TIOCMGET, &lines) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + ZeroMemory(pRegister, sizeof(ULONG)); + + /* FIXME: Is the last read of the MSR register available or + * cached somewhere? Not quite sure we need to return the 4 + * LSBits anyway. A direct access to the register -- which + * would reset the register -- is likely not expected from + * this function. + */ + + /* #define SERIAL_MSR_DCTS 0x01 */ + /* #define SERIAL_MSR_DDSR 0x02 */ + /* #define SERIAL_MSR_TERI 0x04 */ + /* #define SERIAL_MSR_DDCD 0x08 */ + + if (lines & TIOCM_CTS) + *pRegister |= SERIAL_MSR_CTS; + if (lines & TIOCM_DSR) + *pRegister |= SERIAL_MSR_DSR; + if (lines & TIOCM_RI) + *pRegister |= SERIAL_MSR_RI; + if (lines & TIOCM_CD) + *pRegister |= SERIAL_MSR_DCD; + + return TRUE; +} + +/* http://msdn.microsoft.com/en-us/library/windows/hardware/hh439605%28v=vs.85%29.aspx */ +static const ULONG _SERIAL_SYS_SUPPORTED_EV_MASK = + SERIAL_EV_RXCHAR | SERIAL_EV_RXFLAG | SERIAL_EV_TXEMPTY | SERIAL_EV_CTS | SERIAL_EV_DSR | + SERIAL_EV_RLSD | SERIAL_EV_BREAK | SERIAL_EV_ERR | SERIAL_EV_RING | + /* SERIAL_EV_PERR | */ + SERIAL_EV_RX80FULL /*| + SERIAL_EV_EVENT1 | + SERIAL_EV_EVENT2*/ + ; + +static BOOL _set_wait_mask(WINPR_COMM* pComm, const ULONG* pWaitMask) +{ + ULONG possibleMask = 0; + + /* Stops pending IOCTL_SERIAL_WAIT_ON_MASK + * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx + */ + + if (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING) + { + /* FIXME: any doubt on reading PendingEvents out of a critical section? */ + + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents |= SERIAL_EV_WINPR_STOP; + LeaveCriticalSection(&pComm->EventsLock); + + /* waiting the end of the pending _wait_on_mask() */ + while (pComm->PendingEvents & SERIAL_EV_WINPR_WAITING) + Sleep(10); /* 10ms */ + } + + /* NB: ensure to leave the critical section before to return */ + EnterCriticalSection(&pComm->EventsLock); + + if (*pWaitMask == 0) + { + /* clearing pending events */ + + if (ioctl(pComm->fd, TIOCGICOUNT, &(pComm->counters)) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + + if (pComm->permissive) + { + /* counters could not be reset but keep on */ + ZeroMemory(&(pComm->counters), sizeof(struct serial_icounter_struct)); + } + else + { + SetLastError(ERROR_IO_DEVICE); + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + } + + pComm->PendingEvents = 0; + } + + possibleMask = *pWaitMask & _SERIAL_SYS_SUPPORTED_EV_MASK; + + if (possibleMask != *pWaitMask) + { + CommLog_Print(WLOG_WARN, + "Not all wait events supported (Serial.sys), requested events= 0x%08" PRIX32 + ", possible events= 0x%08" PRIX32 "", + *pWaitMask, possibleMask); + + /* FIXME: shall we really set the possibleMask and return FALSE? */ + pComm->WaitEventMask = possibleMask; + + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + pComm->WaitEventMask = possibleMask; + + LeaveCriticalSection(&pComm->EventsLock); + return TRUE; +} + +static BOOL _get_wait_mask(WINPR_COMM* pComm, ULONG* pWaitMask) +{ + *pWaitMask = pComm->WaitEventMask; + return TRUE; +} + +static BOOL _set_queue_size(WINPR_COMM* pComm, const SERIAL_QUEUE_SIZE* pQueueSize) +{ + if ((pQueueSize->InSize <= N_TTY_BUF_SIZE) && (pQueueSize->OutSize <= N_TTY_BUF_SIZE)) + return TRUE; /* nothing to do */ + + /* FIXME: could be implemented on top of N_TTY */ + + if (pQueueSize->InSize > N_TTY_BUF_SIZE) + CommLog_Print(WLOG_WARN, + "Requested an incompatible input buffer size: %" PRIu32 + ", keeping on with a %" PRIu32 " bytes buffer.", + pQueueSize->InSize, N_TTY_BUF_SIZE); + + if (pQueueSize->OutSize > N_TTY_BUF_SIZE) + CommLog_Print(WLOG_WARN, + "Requested an incompatible output buffer size: %" PRIu32 + ", keeping on with a %" PRIu32 " bytes buffer.", + pQueueSize->OutSize, N_TTY_BUF_SIZE); + + SetLastError(ERROR_CANCELLED); + return FALSE; +} + +static BOOL _purge(WINPR_COMM* pComm, const ULONG* pPurgeMask) +{ + if ((*pPurgeMask & ~(SERIAL_PURGE_TXABORT | SERIAL_PURGE_RXABORT | SERIAL_PURGE_TXCLEAR | + SERIAL_PURGE_RXCLEAR)) > 0) + { + CommLog_Print(WLOG_WARN, "Invalid purge mask: 0x%" PRIX32 "\n", *pPurgeMask); + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + /* FIXME: currently relying too much on the fact the server + * sends a single IRP_MJ_WRITE or IRP_MJ_READ at a time + * (taking care though that one IRP_MJ_WRITE and one + * IRP_MJ_READ can be sent simultaneously) */ + + if (*pPurgeMask & SERIAL_PURGE_TXABORT) + { + /* Purges all write (IRP_MJ_WRITE) requests. */ + + if (eventfd_write(pComm->fd_write_event, WINPR_PURGE_TXABORT) < 0) + { + if (errno != EAGAIN) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_WRITE */ + } + } + + if (*pPurgeMask & SERIAL_PURGE_RXABORT) + { + /* Purges all read (IRP_MJ_READ) requests. */ + + if (eventfd_write(pComm->fd_read_event, WINPR_PURGE_RXABORT) < 0) + { + if (errno != EAGAIN) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "eventfd_write failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + } + + WINPR_ASSERT(errno == EAGAIN); /* no reader <=> no pending IRP_MJ_READ */ + } + } + + if (*pPurgeMask & SERIAL_PURGE_TXCLEAR) + { + /* Purges the transmit buffer, if one exists. */ + + if (tcflush(pComm->fd, TCOFLUSH) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "tcflush(TCOFLUSH) failure, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_CANCELLED); + return FALSE; + } + } + + if (*pPurgeMask & SERIAL_PURGE_RXCLEAR) + { + /* Purges the receive buffer, if one exists. */ + + if (tcflush(pComm->fd, TCIFLUSH) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "tcflush(TCIFLUSH) failure, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_CANCELLED); + return FALSE; + } + } + + return TRUE; +} + +/* NB: _get_commstatus also produces most of the events consumed by _wait_on_mask(). Exceptions: + * - SERIAL_EV_RXFLAG: FIXME: once EventChar supported + * + */ +static BOOL _get_commstatus(WINPR_COMM* pComm, SERIAL_STATUS* pCommstatus) +{ + /* http://msdn.microsoft.com/en-us/library/jj673022%28v=vs.85%29.aspx */ + + struct serial_icounter_struct currentCounters; + + /* NB: ensure to leave the critical section before to return */ + EnterCriticalSection(&pComm->EventsLock); + + ZeroMemory(pCommstatus, sizeof(SERIAL_STATUS)); + + ZeroMemory(¤tCounters, sizeof(struct serial_icounter_struct)); + if (ioctl(pComm->fd, TIOCGICOUNT, ¤tCounters) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCGICOUNT ioctl failed, errno=[%d] %s.", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + CommLog_Print(WLOG_WARN, " could not read counters."); + + if (pComm->permissive) + { + /* Errors and events based on counters could not be + * detected but keep on. + */ + ZeroMemory(¤tCounters, sizeof(struct serial_icounter_struct)); + } + else + { + SetLastError(ERROR_IO_DEVICE); + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + } + + /* NB: preferred below (currentCounters.* != pComm->counters.*) over (currentCounters.* > + * pComm->counters.*) thinking the counters can loop */ + + /* Errors */ + + if (currentCounters.buf_overrun != pComm->counters.buf_overrun) + { + pCommstatus->Errors |= SERIAL_ERROR_QUEUEOVERRUN; + } + + if (currentCounters.overrun != pComm->counters.overrun) + { + pCommstatus->Errors |= SERIAL_ERROR_OVERRUN; + pComm->PendingEvents |= SERIAL_EV_ERR; + } + + if (currentCounters.brk != pComm->counters.brk) + { + pCommstatus->Errors |= SERIAL_ERROR_BREAK; + pComm->PendingEvents |= SERIAL_EV_BREAK; + } + + if (currentCounters.parity != pComm->counters.parity) + { + pCommstatus->Errors |= SERIAL_ERROR_PARITY; + pComm->PendingEvents |= SERIAL_EV_ERR; + } + + if (currentCounters.frame != pComm->counters.frame) + { + pCommstatus->Errors |= SERIAL_ERROR_FRAMING; + pComm->PendingEvents |= SERIAL_EV_ERR; + } + + /* HoldReasons */ + + /* TODO: SERIAL_TX_WAITING_FOR_CTS */ + + /* TODO: SERIAL_TX_WAITING_FOR_DSR */ + + /* TODO: SERIAL_TX_WAITING_FOR_DCD */ + + /* TODO: SERIAL_TX_WAITING_FOR_XON */ + + /* TODO: SERIAL_TX_WAITING_ON_BREAK, see LCR's bit 6 */ + + /* TODO: SERIAL_TX_WAITING_XOFF_SENT */ + + /* AmountInInQueue */ + + if (ioctl(pComm->fd, TIOCINQ, &(pCommstatus->AmountInInQueue)) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCINQ ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + /* AmountInOutQueue */ + + if (ioctl(pComm->fd, TIOCOUTQ, &(pCommstatus->AmountInOutQueue)) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCOUTQ ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + /* BOOLEAN EofReceived; FIXME: once EofChar supported */ + + /* BOOLEAN WaitForImmediate; TODO: once IOCTL_SERIAL_IMMEDIATE_CHAR fully supported */ + + /* other events based on counters */ + + if (currentCounters.rx != pComm->counters.rx) + { + pComm->PendingEvents |= SERIAL_EV_RXFLAG | SERIAL_EV_RXCHAR; + } + + if ((currentCounters.tx != pComm->counters.tx) && /* at least a transmission occurred AND ...*/ + (pCommstatus->AmountInOutQueue == 0)) /* output bufer is now empty */ + { + pComm->PendingEvents |= SERIAL_EV_TXEMPTY; + } + else + { + /* FIXME: "now empty" from the specs is ambiguous, need to track previous completed + * transmission? */ + pComm->PendingEvents &= ~SERIAL_EV_TXEMPTY; + } + + if (currentCounters.cts != pComm->counters.cts) + { + pComm->PendingEvents |= SERIAL_EV_CTS; + } + + if (currentCounters.dsr != pComm->counters.dsr) + { + pComm->PendingEvents |= SERIAL_EV_DSR; + } + + if (currentCounters.dcd != pComm->counters.dcd) + { + pComm->PendingEvents |= SERIAL_EV_RLSD; + } + + if (currentCounters.rng != pComm->counters.rng) + { + pComm->PendingEvents |= SERIAL_EV_RING; + } + + if (pCommstatus->AmountInInQueue > (0.8 * N_TTY_BUF_SIZE)) + { + pComm->PendingEvents |= SERIAL_EV_RX80FULL; + } + else + { + /* FIXME: "is 80 percent full" from the specs is ambiguous, need to track when it previously + * * occurred? */ + pComm->PendingEvents &= ~SERIAL_EV_RX80FULL; + } + + pComm->counters = currentCounters; + + LeaveCriticalSection(&pComm->EventsLock); + return TRUE; +} + +static BOOL _refresh_PendingEvents(WINPR_COMM* pComm) +{ + SERIAL_STATUS serialStatus; + + /* NB: also ensures PendingEvents to be up to date */ + ZeroMemory(&serialStatus, sizeof(SERIAL_STATUS)); + if (!_get_commstatus(pComm, &serialStatus)) + { + return FALSE; + } + + return TRUE; +} + +static void _consume_event(WINPR_COMM* pComm, ULONG* pOutputMask, ULONG event) +{ + if ((pComm->WaitEventMask & event) && (pComm->PendingEvents & event)) + { + pComm->PendingEvents &= ~event; /* consumed */ + *pOutputMask |= event; + } +} + +/* + * NB: see also: _set_wait_mask() + */ +static BOOL _wait_on_mask(WINPR_COMM* pComm, ULONG* pOutputMask) +{ + WINPR_ASSERT(*pOutputMask == 0); + + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents |= SERIAL_EV_WINPR_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + + while (TRUE) + { + /* NB: EventsLock also used by _refresh_PendingEvents() */ + if (!_refresh_PendingEvents(pComm)) + { + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + return FALSE; + } + + /* NB: ensure to leave the critical section before to return */ + EnterCriticalSection(&pComm->EventsLock); + + if (pComm->PendingEvents & SERIAL_EV_WINPR_STOP) + { + pComm->PendingEvents &= ~SERIAL_EV_WINPR_STOP; + + /* pOutputMask must remain empty but should + * not have been modified. + * + * http://msdn.microsoft.com/en-us/library/ff546805%28v=vs.85%29.aspx + */ + WINPR_ASSERT(*pOutputMask == 0); + + pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + return TRUE; + } + + _consume_event(pComm, pOutputMask, SERIAL_EV_RXCHAR); + _consume_event(pComm, pOutputMask, SERIAL_EV_RXFLAG); + _consume_event(pComm, pOutputMask, SERIAL_EV_TXEMPTY); + _consume_event(pComm, pOutputMask, SERIAL_EV_CTS); + _consume_event(pComm, pOutputMask, SERIAL_EV_DSR); + _consume_event(pComm, pOutputMask, SERIAL_EV_RLSD); + _consume_event(pComm, pOutputMask, SERIAL_EV_BREAK); + _consume_event(pComm, pOutputMask, SERIAL_EV_ERR); + _consume_event(pComm, pOutputMask, SERIAL_EV_RING); + _consume_event(pComm, pOutputMask, SERIAL_EV_RX80FULL); + + LeaveCriticalSection(&pComm->EventsLock); + + /* NOTE: PendingEvents can be modified from now on but + * not pOutputMask */ + + if (*pOutputMask != 0) + { + /* at least an event occurred */ + + EnterCriticalSection(&pComm->EventsLock); + pComm->PendingEvents &= ~SERIAL_EV_WINPR_WAITING; + LeaveCriticalSection(&pComm->EventsLock); + return TRUE; + } + + /* waiting for a modification of PendingEvents. + * + * NOTE: previously used a semaphore but used + * sem_timedwait() anyway. Finally preferred a simpler + * solution with Sleep() without the burden of the + * semaphore initialization and destroying. + */ + + Sleep(100); /* 100 ms */ + } +} + +static BOOL _set_break_on(WINPR_COMM* pComm) +{ + if (ioctl(pComm->fd, TIOCSBRK, NULL) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _set_break_off(WINPR_COMM* pComm) +{ + if (ioctl(pComm->fd, TIOCCBRK, NULL) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCSBRK ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _set_xoff(WINPR_COMM* pComm) +{ + if (tcflow(pComm->fd, TCIOFF) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TCIOFF failure, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _set_xon(WINPR_COMM* pComm) +{ + if (tcflow(pComm->fd, TCION) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TCION failure, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + return TRUE; +} + +static BOOL _get_dtrrts(WINPR_COMM* pComm, ULONG* pMask) +{ + UINT32 lines = 0; + if (ioctl(pComm->fd, TIOCMGET, &lines) < 0) + { + char ebuffer[256] = { 0 }; + CommLog_Print(WLOG_WARN, "TIOCMGET ioctl failed, errno=[%d] %s", errno, + winpr_strerror(errno, ebuffer, sizeof(ebuffer))); + SetLastError(ERROR_IO_DEVICE); + return FALSE; + } + + *pMask = 0; + + if (!(lines & TIOCM_DTR)) + *pMask |= SERIAL_DTR_STATE; + if (!(lines & TIOCM_RTS)) + *pMask |= SERIAL_RTS_STATE; + + return TRUE; +} + +static BOOL _config_size(WINPR_COMM* pComm, ULONG* pSize) +{ + /* http://msdn.microsoft.com/en-us/library/ff546548%28v=vs.85%29.aspx */ + if (!pSize) + return FALSE; + + *pSize = 0; + return TRUE; +} + +static BOOL _immediate_char(WINPR_COMM* pComm, const UCHAR* pChar) +{ + BOOL result = 0; + DWORD nbBytesWritten = -1; + + /* FIXME: CommWriteFile uses a critical section, shall it be + * interrupted? + * + * FIXME: see also _get_commstatus()'s WaitForImmediate boolean + */ + + result = CommWriteFile(pComm, pChar, 1, &nbBytesWritten, NULL); + + WINPR_ASSERT(nbBytesWritten == 1); + + return result; +} + +static BOOL _reset_device(WINPR_COMM* pComm) +{ + /* http://msdn.microsoft.com/en-us/library/dn265347%28v=vs.85%29.aspx */ + return TRUE; +} + +static SERIAL_DRIVER _SerialSys = { + .id = SerialDriverSerialSys, + .name = _T("Serial.sys"), + .set_baud_rate = _set_baud_rate, + .get_baud_rate = _get_baud_rate, + .get_properties = _get_properties, + .set_serial_chars = _set_serial_chars, + .get_serial_chars = _get_serial_chars, + .set_line_control = _set_line_control, + .get_line_control = _get_line_control, + .set_handflow = _set_handflow, + .get_handflow = _get_handflow, + .set_timeouts = _set_timeouts, + .get_timeouts = _get_timeouts, + .set_dtr = _set_dtr, + .clear_dtr = _clear_dtr, + .set_rts = _set_rts, + .clear_rts = _clear_rts, + .get_modemstatus = _get_modemstatus, + .set_wait_mask = _set_wait_mask, + .get_wait_mask = _get_wait_mask, + .wait_on_mask = _wait_on_mask, + .set_queue_size = _set_queue_size, + .purge = _purge, + .get_commstatus = _get_commstatus, + .set_break_on = _set_break_on, + .set_break_off = _set_break_off, + .set_xoff = _set_xoff, + .set_xon = _set_xon, + .get_dtrrts = _get_dtrrts, + .config_size = _config_size, + .immediate_char = _immediate_char, + .reset_device = _reset_device, +}; + +SERIAL_DRIVER* SerialSys_s(void) +{ + return &_SerialSys; +} + +#endif /* __linux__ */ |