/* Main file for COMM support * * DEC 93 Erik Bos <erik@xs4all.nl> * Copyright 1996 Marcus Meissner * Copyright 2005,2006 Eric Pouech * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA */ #include "config.h" #include "wine/port.h" #include <errno.h> #include <string.h> #include <stdlib.h> #include <stdarg.h> #include <stdio.h> #ifdef HAVE_STRINGS_H # include <strings.h> #endif #ifdef HAVE_TERMIOS_H #include <termios.h> #endif #ifdef HAVE_IO_H # include <io.h> #endif #ifdef HAVE_UNISTD_H # include <unistd.h> #endif #include <fcntl.h> #ifdef HAVE_SYS_STAT_H # include <sys/stat.h> #endif #include <sys/types.h> #ifdef HAVE_SYS_FILIO_H # include <sys/filio.h> #endif #ifdef HAVE_SYS_IOCTL_H #include <sys/ioctl.h> #endif #ifdef HAVE_SYS_POLL_H # include <sys/poll.h> #endif #ifdef HAVE_SYS_MODEM_H # include <sys/modem.h> #endif #ifdef HAVE_SYS_STRTIO_H # include <sys/strtio.h> #endif #define NONAMELESSUNION #define NONAMELESSSTRUCT #include "ntstatus.h" #define WIN32_NO_STATUS #include "windef.h" #include "winternl.h" #include "winioctl.h" #include "ddk/ntddser.h" #include "ntdll_misc.h" #include "wine/server.h" #include "wine/library.h" #include "wine/debug.h" #ifdef HAVE_LINUX_SERIAL_H #ifdef HAVE_ASM_TYPES_H #include <asm/types.h> #endif #include <linux/serial.h> #endif #if !defined(TIOCINQ) && defined(FIONREAD) #define TIOCINQ FIONREAD #endif WINE_DEFAULT_DEBUG_CHANNEL(comm); static const char* iocode2str(DWORD ioc) { switch (ioc) { #define X(x) case (x): return #x X(IOCTL_SERIAL_CLEAR_STATS); X(IOCTL_SERIAL_CLR_DTR); X(IOCTL_SERIAL_CLR_RTS); X(IOCTL_SERIAL_CONFIG_SIZE); X(IOCTL_SERIAL_GET_BAUD_RATE); X(IOCTL_SERIAL_GET_CHARS); X(IOCTL_SERIAL_GET_COMMSTATUS); X(IOCTL_SERIAL_GET_DTRRTS); X(IOCTL_SERIAL_GET_HANDFLOW); X(IOCTL_SERIAL_GET_LINE_CONTROL); X(IOCTL_SERIAL_GET_MODEM_CONTROL); X(IOCTL_SERIAL_GET_MODEMSTATUS); X(IOCTL_SERIAL_GET_PROPERTIES); X(IOCTL_SERIAL_GET_STATS); X(IOCTL_SERIAL_GET_TIMEOUTS); X(IOCTL_SERIAL_GET_WAIT_MASK); X(IOCTL_SERIAL_IMMEDIATE_CHAR); X(IOCTL_SERIAL_LSRMST_INSERT); X(IOCTL_SERIAL_PURGE); X(IOCTL_SERIAL_RESET_DEVICE); X(IOCTL_SERIAL_SET_BAUD_RATE); X(IOCTL_SERIAL_SET_BREAK_ON); X(IOCTL_SERIAL_SET_BREAK_OFF); X(IOCTL_SERIAL_SET_CHARS); X(IOCTL_SERIAL_SET_DTR); X(IOCTL_SERIAL_SET_FIFO_CONTROL); X(IOCTL_SERIAL_SET_HANDFLOW); X(IOCTL_SERIAL_SET_LINE_CONTROL); X(IOCTL_SERIAL_SET_MODEM_CONTROL); X(IOCTL_SERIAL_SET_QUEUE_SIZE); X(IOCTL_SERIAL_SET_RTS); X(IOCTL_SERIAL_SET_TIMEOUTS); X(IOCTL_SERIAL_SET_WAIT_MASK); X(IOCTL_SERIAL_SET_XOFF); X(IOCTL_SERIAL_SET_XON); X(IOCTL_SERIAL_WAIT_ON_MASK); X(IOCTL_SERIAL_XOFF_COUNTER); #undef X default: { static char tmp[32]; sprintf(tmp, "IOCTL_SERIAL_%d\n", ioc); return tmp; } } } static NTSTATUS get_baud_rate(int fd, SERIAL_BAUD_RATE* sbr) { struct termios port; int speed; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } #ifndef __EMX__ #ifdef CBAUD speed = port.c_cflag & CBAUD; #else speed = cfgetospeed(&port); #endif switch (speed) { case B0: sbr->BaudRate = 0; break; case B50: sbr->BaudRate = 50; break; case B75: sbr->BaudRate = 75; break; case B110: sbr->BaudRate = 110; break; case B134: sbr->BaudRate = 134; break; case B150: sbr->BaudRate = 150; break; case B200: sbr->BaudRate = 200; break; case B300: sbr->BaudRate = 300; break; case B600: sbr->BaudRate = 600; break; case B1200: sbr->BaudRate = 1200; break; case B1800: sbr->BaudRate = 1800; break; case B2400: sbr->BaudRate = 2400; break; case B4800: sbr->BaudRate = 4800; break; case B9600: sbr->BaudRate = 9600; break; case B19200: sbr->BaudRate = 19200; break; case B38400: sbr->BaudRate = 38400; break; #ifdef B57600 case B57600: sbr->BaudRate = 57600; break; #endif #ifdef B115200 case B115200: sbr->BaudRate = 115200; break; #endif #ifdef B230400 case B230400: sbr->BaudRate = 230400; break; #endif #ifdef B460800 case B460800: sbr->BaudRate = 460800; break; #endif default: ERR("unknown speed %x\n", speed); return STATUS_INVALID_PARAMETER; } #else return STATUS_INVALID_PARAMETER; #endif return STATUS_SUCCESS; } static NTSTATUS get_hand_flow(int fd, SERIAL_HANDFLOW* shf) { int stat = 0; struct termios port; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } /* termios does not support DTR/DSR flow control */ shf->ControlHandShake = 0; shf->FlowReplace = 0; #ifdef TIOCMGET if (ioctl(fd, TIOCMGET, &stat) == -1) { WARN("ioctl error '%s'\n", strerror(errno)); shf->ControlHandShake |= SERIAL_DTR_CONTROL; shf->FlowReplace |= SERIAL_RTS_CONTROL; } #else WARN("Setting DTR/RTS to enabled by default\n"); shf->ControlHandShake |= SERIAL_DTR_CONTROL; shf->FlowReplace |= SERIAL_RTS_CONTROL; #endif #ifdef TIOCM_DTR if (stat & TIOCM_DTR) #endif shf->ControlHandShake |= SERIAL_DTR_CONTROL; #ifdef CRTSCTS if (port.c_cflag & CRTSCTS) { shf->FlowReplace |= SERIAL_RTS_CONTROL; shf->ControlHandShake |= SERIAL_CTS_HANDSHAKE; } else #endif { #ifdef TIOCM_RTS if (stat & TIOCM_RTS) #endif shf->FlowReplace |= SERIAL_RTS_CONTROL; } if (port.c_iflag & IXOFF) shf->FlowReplace |= SERIAL_AUTO_RECEIVE; if (port.c_iflag & IXON) shf->FlowReplace |= SERIAL_AUTO_TRANSMIT; shf->XonLimit = 10; shf->XoffLimit = 10; return STATUS_SUCCESS; } static NTSTATUS get_line_control(int fd, SERIAL_LINE_CONTROL* slc) { struct termios port; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } #ifdef CMSPAR switch (port.c_cflag & (PARENB | PARODD | CMSPAR)) #else switch (port.c_cflag & (PARENB | PARODD)) #endif { case 0: slc->Parity = NOPARITY; break; case PARENB: slc->Parity = EVENPARITY; break; case PARENB|PARODD: slc->Parity = ODDPARITY; break; #ifdef CMSPAR case PARENB|CMSPAR: slc->Parity = MARKPARITY; break; case PARENB|PARODD|CMSPAR: slc->Parity = SPACEPARITY; break; #endif } switch (port.c_cflag & CSIZE) { case CS5: slc->WordLength = 5; break; case CS6: slc->WordLength = 6; break; case CS7: slc->WordLength = 7; break; case CS8: slc->WordLength = 8; break; default: ERR("unknown size %x\n", (UINT)(port.c_cflag & CSIZE)); } if (port.c_cflag & CSTOPB) { if (slc->WordLength == 5) slc->StopBits = ONE5STOPBITS; else slc->StopBits = TWOSTOPBITS; } else slc->StopBits = ONESTOPBIT; return STATUS_SUCCESS; } static NTSTATUS get_modem_status(int fd, DWORD* lpModemStat) { NTSTATUS status = STATUS_NOT_SUPPORTED; int mstat; *lpModemStat = 0; #ifdef TIOCMGET if (!ioctl(fd, TIOCMGET, &mstat)) { #ifdef TIOCM_CTS if (mstat & TIOCM_CTS) *lpModemStat |= MS_CTS_ON; #endif #ifdef TIOCM_DSR if (mstat & TIOCM_DSR) *lpModemStat |= MS_DSR_ON; #endif #ifdef TIOCM_RNG if (mstat & TIOCM_RNG) *lpModemStat |= MS_RING_ON; #endif #ifdef TIOCM_CAR /* FIXME: Not really sure about RLSD UB 990810 */ if (mstat & TIOCM_CAR) *lpModemStat |= MS_RLSD_ON; #endif TRACE("%04x -> %s%s%s%s\n", mstat, (*lpModemStat & MS_RLSD_ON) ? "MS_RLSD_ON " : "", (*lpModemStat & MS_RING_ON) ? "MS_RING_ON " : "", (*lpModemStat & MS_DSR_ON) ? "MS_DSR_ON " : "", (*lpModemStat & MS_CTS_ON) ? "MS_CTS_ON " : ""); return STATUS_SUCCESS; } WARN("ioctl failed\n"); status = FILE_GetNtStatus(); #endif return status; } static NTSTATUS get_special_chars(int fd, SERIAL_CHARS* sc) { struct termios port; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } sc->EofChar = port.c_cc[VEOF]; sc->ErrorChar = 0xFF; sc->BreakChar = 0; /* FIXME */ sc->EventChar = 0; /* FIXME */ sc->XonChar = port.c_cc[VSTART]; sc->XoffChar = port.c_cc[VSTOP]; return STATUS_SUCCESS; } static NTSTATUS get_status(int fd, SERIAL_STATUS* ss) { NTSTATUS status = STATUS_SUCCESS; ss->Errors = 0; ss->HoldReasons = 0; ss->EofReceived = FALSE; ss->WaitForImmediate = FALSE; #ifdef TIOCOUTQ if (ioctl(fd, TIOCOUTQ, &ss->AmountInOutQueue) == -1) { WARN("ioctl returned error\n"); status = FILE_GetNtStatus(); } #else ss->AmountInOutQueue = 0; /* FIXME: find a different way to find out */ #endif #ifdef TIOCINQ if (ioctl(fd, TIOCINQ, &ss->AmountInInQueue)) { WARN("ioctl returned error\n"); status = FILE_GetNtStatus(); } #else ss->AmountInInQueue = 0; /* FIXME: find a different way to find out */ #endif return status; } static NTSTATUS get_timeouts(HANDLE handle, SERIAL_TIMEOUTS* st) { NTSTATUS status; SERVER_START_REQ( get_serial_info ) { req->handle = wine_server_obj_handle( handle ); if (!(status = wine_server_call( req ))) { st->ReadIntervalTimeout = reply->readinterval; st->ReadTotalTimeoutMultiplier = reply->readmult; st->ReadTotalTimeoutConstant = reply->readconst; st->WriteTotalTimeoutMultiplier = reply->writemult; st->WriteTotalTimeoutConstant = reply->writeconst; } } SERVER_END_REQ; return status; } static NTSTATUS get_wait_mask(HANDLE hDevice, DWORD* mask) { NTSTATUS status; SERVER_START_REQ( get_serial_info ) { req->handle = wine_server_obj_handle( hDevice ); if (!(status = wine_server_call( req ))) *mask = reply->eventmask; } SERVER_END_REQ; return status; } static NTSTATUS purge(int fd, DWORD flags) { /* ** not exactly sure how these are different ** Perhaps if we had our own internal queues, one flushes them ** and the other flushes the kernel's buffers. */ if (flags & PURGE_TXABORT) tcflush(fd, TCOFLUSH); if (flags & PURGE_RXABORT) tcflush(fd, TCIFLUSH); if (flags & PURGE_TXCLEAR) tcflush(fd, TCOFLUSH); if (flags & PURGE_RXCLEAR) tcflush(fd, TCIFLUSH); return STATUS_SUCCESS; } static NTSTATUS set_baud_rate(int fd, const SERIAL_BAUD_RATE* sbr) { struct termios port; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } #ifdef CBAUD port.c_cflag &= ~CBAUD; switch (sbr->BaudRate) { case 0: port.c_cflag |= B0; break; case 50: port.c_cflag |= B50; break; case 75: port.c_cflag |= B75; break; case 110: case CBR_110: port.c_cflag |= B110; break; case 134: port.c_cflag |= B134; break; case 150: port.c_cflag |= B150; break; case 200: port.c_cflag |= B200; break; case 300: case CBR_300: port.c_cflag |= B300; break; case 600: case CBR_600: port.c_cflag |= B600; break; case 1200: case CBR_1200: port.c_cflag |= B1200; break; case 1800: port.c_cflag |= B1800; break; case 2400: case CBR_2400: port.c_cflag |= B2400; break; case 4800: case CBR_4800: port.c_cflag |= B4800; break; case 9600: case CBR_9600: port.c_cflag |= B9600; break; case 19200: case CBR_19200: port.c_cflag |= B19200; break; case 38400: case CBR_38400: port.c_cflag |= B38400; break; #ifdef B57600 case 57600: port.c_cflag |= B57600; break; #endif #ifdef B115200 case 115200: port.c_cflag |= B115200;break; #endif #ifdef B230400 case 230400: port.c_cflag |= B230400;break; #endif #ifdef B460800 case 460800: port.c_cflag |= B460800;break; #endif default: #if defined (HAVE_LINUX_SERIAL_H) && defined (TIOCSSERIAL) { struct serial_struct nuts; int arby; ioctl(fd, TIOCGSERIAL, &nuts); nuts.custom_divisor = nuts.baud_base / sbr->BaudRate; if (!(nuts.custom_divisor)) nuts.custom_divisor = 1; arby = nuts.baud_base / nuts.custom_divisor; nuts.flags &= ~ASYNC_SPD_MASK; nuts.flags |= ASYNC_SPD_CUST; WARN("You (or a program acting at your behest) have specified\n" "a non-standard baud rate %d. Wine will set the rate to %d,\n" "which is as close as we can get by our present understanding of your\n" "hardware. I hope you know what you are doing. Any disruption Wine\n" "has caused to your linux system can be undone with setserial\n" "(see man setserial). If you have incapacitated a Hayes type modem,\n" "reset it and it will probably recover.\n", sbr->BaudRate, arby); ioctl(fd, TIOCSSERIAL, &nuts); port.c_cflag |= B38400; } break; #else /* Don't have linux/serial.h or lack TIOCSSERIAL */ ERR("baudrate %d\n", sbr->BaudRate); return STATUS_NOT_SUPPORTED; #endif /* Don't have linux/serial.h or lack TIOCSSERIAL */ } #elif !defined(__EMX__) switch (sbr->BaudRate) { case 0: port.c_ospeed = B0; break; case 50: port.c_ospeed = B50; break; case 75: port.c_ospeed = B75; break; case 110: case CBR_110: port.c_ospeed = B110; break; case 134: port.c_ospeed = B134; break; case 150: port.c_ospeed = B150; break; case 200: port.c_ospeed = B200; break; case 300: case CBR_300: port.c_ospeed = B300; break; case 600: case CBR_600: port.c_ospeed = B600; break; case 1200: case CBR_1200: port.c_ospeed = B1200; break; case 1800: port.c_ospeed = B1800; break; case 2400: case CBR_2400: port.c_ospeed = B2400; break; case 4800: case CBR_4800: port.c_ospeed = B4800; break; case 9600: case CBR_9600: port.c_ospeed = B9600; break; case 19200: case CBR_19200: port.c_ospeed = B19200; break; case 38400: case CBR_38400: port.c_ospeed = B38400; break; #ifdef B57600 case 57600: case CBR_57600: port.c_cflag |= B57600; break; #endif #ifdef B115200 case 115200: case CBR_115200: port.c_cflag |= B115200;break; #endif #ifdef B230400 case 230400: port.c_cflag |= B230400;break; #endif #ifdef B460800 case 460800: port.c_cflag |= B460800;break; #endif default: ERR("baudrate %d\n", sbr->BaudRate); return STATUS_NOT_SUPPORTED; } port.c_ispeed = port.c_ospeed; #endif if (tcsetattr(fd, TCSANOW, &port) == -1) { ERR("tcsetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } return STATUS_SUCCESS; } static int whack_modem(int fd, unsigned int andy, unsigned int orrie) { #ifdef TIOCMGET unsigned int mstat, okay; okay = ioctl(fd, TIOCMGET, &mstat); if (okay) return okay; if (andy) mstat &= andy; mstat |= orrie; return ioctl(fd, TIOCMSET, &mstat); #else return 0; #endif } static NTSTATUS set_handflow(int fd, const SERIAL_HANDFLOW* shf) { struct termios port; if ((shf->FlowReplace & (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE)) == (SERIAL_RTS_CONTROL | SERIAL_RTS_HANDSHAKE)) return STATUS_NOT_SUPPORTED; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } #ifdef CRTSCTS if ((shf->ControlHandShake & SERIAL_CTS_HANDSHAKE) || (shf->FlowReplace & SERIAL_RTS_HANDSHAKE)) { port.c_cflag |= CRTSCTS; TRACE("CRTSCTS\n"); } else port.c_cflag &= ~CRTSCTS; #endif #ifdef TIOCM_DTR if (shf->ControlHandShake & SERIAL_DTR_HANDSHAKE) { WARN("DSR/DTR flow control not supported\n"); } else if (!(shf->ControlHandShake & SERIAL_DTR_CONTROL)) whack_modem(fd, ~TIOCM_DTR, 0); else whack_modem(fd, 0, TIOCM_DTR); #endif #ifdef TIOCM_RTS if (!(shf->ControlHandShake & SERIAL_CTS_HANDSHAKE)) { if ((shf->FlowReplace & (SERIAL_RTS_CONTROL|SERIAL_RTS_HANDSHAKE)) == 0) whack_modem(fd, ~TIOCM_RTS, 0); else whack_modem(fd, 0, TIOCM_RTS); } #endif if (shf->FlowReplace & SERIAL_AUTO_RECEIVE) port.c_iflag |= IXOFF; else port.c_iflag &= ~IXOFF; if (shf->FlowReplace & SERIAL_AUTO_TRANSMIT) port.c_iflag |= IXON; else port.c_iflag &= ~IXON; if (tcsetattr(fd, TCSANOW, &port) == -1) { ERR("tcsetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } return STATUS_SUCCESS; } static NTSTATUS set_line_control(int fd, const SERIAL_LINE_CONTROL* slc) { struct termios port; unsigned bytesize, stopbits; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } #ifdef IMAXBEL port.c_iflag &= ~(ISTRIP|BRKINT|IGNCR|ICRNL|INLCR|PARMRK|IMAXBEL); #else port.c_iflag &= ~(ISTRIP|BRKINT|IGNCR|ICRNL|INLCR|PARMRK); #endif port.c_iflag |= IGNBRK | INPCK; port.c_oflag &= ~(OPOST); port.c_cflag &= ~(HUPCL); port.c_cflag |= CLOCAL | CREAD; /* * on FreeBSD, turning off ICANON does not disable IEXTEN, * so we must turn it off explicitly. No harm done on Linux. */ port.c_lflag &= ~(ICANON|ECHO|ISIG|IEXTEN); port.c_lflag |= NOFLSH; bytesize = slc->WordLength; stopbits = slc->StopBits; #ifdef CMSPAR port.c_cflag &= ~(PARENB | PARODD | CMSPAR); #else port.c_cflag &= ~(PARENB | PARODD); #endif /* make sure that reads don't block */ port.c_cc[VMIN] = 0; port.c_cc[VTIME] = 0; switch (slc->Parity) { case NOPARITY: port.c_iflag &= ~INPCK; break; case ODDPARITY: port.c_cflag |= PARENB | PARODD; break; case EVENPARITY: port.c_cflag |= PARENB; break; #ifdef CMSPAR /* Linux defines mark/space (stick) parity */ case MARKPARITY: port.c_cflag |= PARENB | CMSPAR; break; case SPACEPARITY: port.c_cflag |= PARENB | PARODD | CMSPAR; break; #else /* try the POSIX way */ case MARKPARITY: if (slc->StopBits == ONESTOPBIT) { stopbits = TWOSTOPBITS; port.c_iflag &= ~INPCK; } else { ERR("Cannot set MARK Parity\n"); return STATUS_NOT_SUPPORTED; } break; case SPACEPARITY: if (slc->WordLength < 8) { bytesize +=1; port.c_iflag &= ~INPCK; } else { ERR("Cannot set SPACE Parity\n"); return STATUS_NOT_SUPPORTED; } break; #endif default: ERR("Parity\n"); return STATUS_NOT_SUPPORTED; } port.c_cflag &= ~CSIZE; switch (bytesize) { case 5: port.c_cflag |= CS5; break; case 6: port.c_cflag |= CS6; break; case 7: port.c_cflag |= CS7; break; case 8: port.c_cflag |= CS8; break; default: ERR("ByteSize\n"); return STATUS_NOT_SUPPORTED; } switch (stopbits) { case ONESTOPBIT: port.c_cflag &= ~CSTOPB; break; case ONE5STOPBITS: /* will be selected if bytesize is 5 */ case TWOSTOPBITS: port.c_cflag |= CSTOPB; break; default: ERR("StopBits\n"); return STATUS_NOT_SUPPORTED; } /* otherwise it hangs with pending input*/ if (tcsetattr(fd, TCSANOW, &port) == -1) { ERR("tcsetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } return STATUS_SUCCESS; } static NTSTATUS set_queue_size(int fd, const SERIAL_QUEUE_SIZE* sqs) { FIXME("insize %d outsize %d unimplemented stub\n", sqs->InSize, sqs->OutSize); return STATUS_SUCCESS; } static NTSTATUS set_special_chars(int fd, const SERIAL_CHARS* sc) { struct termios port; if (tcgetattr(fd, &port) == -1) { ERR("tcgetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } port.c_cc[VEOF ] = sc->EofChar; /* FIXME: sc->ErrorChar is not supported */ /* FIXME: sc->BreakChar is not supported */ /* FIXME: sc->EventChar is not supported */ port.c_cc[VSTART] = sc->XonChar; port.c_cc[VSTOP ] = sc->XoffChar; if (tcsetattr(fd, TCSANOW, &port) == -1) { ERR("tcsetattr error '%s'\n", strerror(errno)); return FILE_GetNtStatus(); } return STATUS_SUCCESS; } static NTSTATUS set_timeouts(HANDLE handle, const SERIAL_TIMEOUTS* st) { NTSTATUS status; SERVER_START_REQ( set_serial_info ) { req->handle = wine_server_obj_handle( handle ); req->flags = SERIALINFO_SET_TIMEOUTS; req->readinterval = st->ReadIntervalTimeout ; req->readmult = st->ReadTotalTimeoutMultiplier ; req->readconst = st->ReadTotalTimeoutConstant ; req->writemult = st->WriteTotalTimeoutMultiplier ; req->writeconst = st->WriteTotalTimeoutConstant ; status = wine_server_call( req ); } SERVER_END_REQ; return status; } static NTSTATUS set_wait_mask(HANDLE hDevice, DWORD mask) { NTSTATUS status; SERVER_START_REQ( set_serial_info ) { req->handle = wine_server_obj_handle( hDevice ); req->flags = SERIALINFO_SET_MASK; req->eventmask = mask; status = wine_server_call( req ); } SERVER_END_REQ; return status; } /* * does not change IXOFF but simulates that IXOFF has been received: */ static NTSTATUS set_XOff(int fd) { if (tcflow(fd, TCOOFF)) { return FILE_GetNtStatus(); } return STATUS_SUCCESS; } /* * does not change IXON but simulates that IXON has been received: */ static NTSTATUS set_XOn(int fd) { if (tcflow(fd, TCOON)) { return FILE_GetNtStatus(); } return STATUS_SUCCESS; } /* serial_irq_info * local structure holding the irq values we need for WaitCommEvent() * * Stripped down from struct serial_icounter_struct, which may not be available on some systems * As the modem line interrupts (cts, dsr, rng, dcd) only get updated with TIOCMIWAIT active, * no need to carry them in the internal structure * */ typedef struct serial_irq_info { int rx , tx, frame, overrun, parity, brk, buf_overrun; }serial_irq_info; /*********************************************************************** * Data needed by the thread polling for the changing CommEvent */ typedef struct async_commio { HANDLE hDevice; DWORD* events; IO_STATUS_BLOCK* iosb; HANDLE hEvent; DWORD evtmask; DWORD mstat; serial_irq_info irq_info; } async_commio; /*********************************************************************** * Get extended interrupt count info, needed for wait_on */ static NTSTATUS get_irq_info(int fd, serial_irq_info *irq_info) { NTSTATUS status = STATUS_NOT_IMPLEMENTED; #ifdef TIOCGICOUNT struct serial_icounter_struct einfo; if (!ioctl(fd, TIOCGICOUNT, &einfo)) { irq_info->rx = einfo.rx; irq_info->tx = einfo.tx; irq_info->frame = einfo.frame; irq_info->overrun = einfo.overrun; irq_info->parity = einfo.parity; irq_info->brk = einfo.brk; irq_info->buf_overrun = einfo.buf_overrun; return STATUS_SUCCESS; } TRACE("TIOCGICOUNT err %s\n", strerror(errno)); status = FILE_GetNtStatus(); #endif memset(irq_info,0, sizeof(serial_irq_info)); return status; } static DWORD check_events(int fd, DWORD mask, const serial_irq_info *new, const serial_irq_info *old, DWORD new_mstat, DWORD old_mstat) { DWORD ret = 0, queue; TRACE("mask 0x%08x\n", mask); TRACE("old->rx 0x%08x vs. new->rx 0x%08x\n", old->rx, new->rx); TRACE("old->tx 0x%08x vs. new->tx 0x%08x\n", old->tx, new->tx); TRACE("old->frame 0x%08x vs. new->frame 0x%08x\n", old->frame, new->frame); TRACE("old->overrun 0x%08x vs. new->overrun 0x%08x\n", old->overrun, new->overrun); TRACE("old->parity 0x%08x vs. new->parity 0x%08x\n", old->parity, new->parity); TRACE("old->brk 0x%08x vs. new->brk 0x%08x\n", old->brk, new->brk); TRACE("old->buf_overrun 0x%08x vs. new->buf_overrun 0x%08x\n", old->buf_overrun, new->buf_overrun); if (old->brk != new->brk) ret |= EV_BREAK; if ((old_mstat & MS_CTS_ON ) != (new_mstat & MS_CTS_ON )) ret |= EV_CTS; if ((old_mstat & MS_DSR_ON ) != (new_mstat & MS_DSR_ON )) ret |= EV_DSR; if ((old_mstat & MS_RING_ON) != (new_mstat & MS_RING_ON)) ret |= EV_RING; if ((old_mstat & MS_RLSD_ON) != (new_mstat & MS_RLSD_ON)) ret |= EV_RLSD; if (old->frame != new->frame || old->overrun != new->overrun || old->parity != new->parity) ret |= EV_ERR; if (mask & EV_RXCHAR) { queue = 0; #ifdef TIOCINQ if (ioctl(fd, TIOCINQ, &queue)) WARN("TIOCINQ returned error\n"); #endif if (queue) ret |= EV_RXCHAR; } if (mask & EV_TXEMPTY) { queue = 0; /* We really want to know when all characters have gone out of the transmitter */ #if defined(TIOCSERGETLSR) if (ioctl(fd, TIOCSERGETLSR, &queue)) WARN("TIOCSERGETLSR returned error\n"); if (queue) /* TIOCOUTQ only checks for an empty buffer */ #elif defined(TIOCOUTQ) if (ioctl(fd, TIOCOUTQ, &queue)) WARN("TIOCOUTQ returned error\n"); if (!queue) #endif ret |= EV_TXEMPTY; TRACE("OUTQUEUE %d, Transmitter %sempty\n", queue, (ret & EV_TXEMPTY) ? "" : "not "); } return ret & mask; } /*********************************************************************** * wait_for_event (INTERNAL) * * We need to poll for what is interesting * TIOCMIWAIT only checks modem status line and may not be aborted by a changing mask * */ static DWORD CALLBACK wait_for_event(LPVOID arg) { async_commio *commio = arg; int fd, needs_close; if (!server_get_unix_fd( commio->hDevice, FILE_READ_DATA | FILE_WRITE_DATA, &fd, &needs_close, NULL, NULL )) { serial_irq_info new_irq_info; DWORD new_mstat, new_evtmask; LARGE_INTEGER time; TRACE("device=%p fd=0x%08x mask=0x%08x buffer=%p event=%p irq_info=%p\n", commio->hDevice, fd, commio->evtmask, commio->events, commio->hEvent, &commio->irq_info); time.QuadPart = (ULONGLONG)10000; time.QuadPart = -time.QuadPart; for (;;) { /* * TIOCMIWAIT is not adequate * * FIXME: * We don't handle the EV_RXFLAG (the eventchar) */ NtDelayExecution(FALSE, &time); get_irq_info(fd, &new_irq_info); if (get_modem_status(fd, &new_mstat)) TRACE("get_modem_status failed\n"); *commio->events = check_events(fd, commio->evtmask, &new_irq_info, &commio->irq_info, new_mstat, commio->mstat); if (*commio->events) break; get_wait_mask(commio->hDevice, &new_evtmask); if (commio->evtmask != new_evtmask) { *commio->events = 0; break; } } if (needs_close) close( fd ); } if (commio->iosb) commio->iosb->u.Status = *commio->events ? STATUS_SUCCESS : STATUS_CANCELLED; if (commio->hEvent) NtSetEvent(commio->hEvent, NULL); RtlFreeHeap(GetProcessHeap(), 0, commio); return 0; } static NTSTATUS wait_on(HANDLE hDevice, int fd, HANDLE hEvent, PIO_STATUS_BLOCK piosb, DWORD* events) { async_commio* commio; NTSTATUS status; if ((status = NtResetEvent(hEvent, NULL))) return status; commio = RtlAllocateHeap(GetProcessHeap(), 0, sizeof (async_commio)); if (!commio) return STATUS_NO_MEMORY; commio->hDevice = hDevice; commio->events = events; commio->iosb = piosb; commio->hEvent = hEvent; get_wait_mask(commio->hDevice, &commio->evtmask); /* We may never return, if some capabilities miss * Return error in that case */ #if !defined(TIOCINQ) if (commio->evtmask & EV_RXCHAR) goto error_caps; #endif #if !(defined(TIOCSERGETLSR) && defined(TIOCSER_TEMT)) || !defined(TIOCINQ) if (commio->evtmask & EV_TXEMPTY) goto error_caps; #endif #if !defined(TIOCMGET) if (commio->evtmask & (EV_CTS | EV_DSR| EV_RING| EV_RLSD)) goto error_caps; #endif #if !defined(TIOCM_CTS) if (commio->evtmask & EV_CTS) goto error_caps; #endif #if !defined(TIOCM_DSR) if (commio->evtmask & EV_DSR) goto error_caps; #endif #if !defined(TIOCM_RNG) if (commio->evtmask & EV_RING) goto error_caps; #endif #if !defined(TIOCM_CAR) if (commio->evtmask & EV_RLSD) goto error_caps; #endif if (commio->evtmask & EV_RXFLAG) FIXME("EV_RXFLAG not handled\n"); if ((status = get_irq_info(fd, &commio->irq_info)) && (commio->evtmask & (EV_BREAK | EV_ERR))) goto out_now; if ((status = get_modem_status(fd, &commio->mstat)) && (commio->evtmask & (EV_CTS | EV_DSR| EV_RING| EV_RLSD))) goto out_now; /* We might have received something or the TX buffer is delivered */ *events = check_events(fd, commio->evtmask, &commio->irq_info, &commio->irq_info, commio->mstat, commio->mstat); if (*events) { status = STATUS_SUCCESS; goto out_now; } /* create the worker for the task */ status = RtlQueueWorkItem(wait_for_event, commio, 0 /* FIXME */); if (status != STATUS_SUCCESS) goto out_now; return STATUS_PENDING; #if !defined(TIOCINQ) || (!(defined(TIOCSERGETLSR) && defined(TIOCSER_TEMT)) || !defined(TIOCINQ)) || !defined(TIOCMGET) || !defined(TIOCM_CTS) ||!defined(TIOCM_DSR) || !defined(TIOCM_RNG) || !defined(TIOCM_CAR) error_caps: FIXME("Returning error because of missing capabilities\n"); status = STATUS_INVALID_PARAMETER; #endif out_now: RtlFreeHeap(GetProcessHeap(), 0, commio); return status; } static NTSTATUS xmit_immediate(HANDLE hDevice, int fd, const char* ptr) { /* FIXME: not perfect as it should bypass the in-queue */ WARN("(%p,'%c') not perfect!\n", hDevice, *ptr); if (write(fd, ptr, 1) != 1) return FILE_GetNtStatus(); return STATUS_SUCCESS; } /****************************************************************** * COMM_DeviceIoControl * * */ static inline NTSTATUS io_control(HANDLE hDevice, HANDLE hEvent, PIO_APC_ROUTINE UserApcRoutine, PVOID UserApcContext, PIO_STATUS_BLOCK piosb, ULONG dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) { DWORD sz = 0, access = FILE_READ_DATA; NTSTATUS status = STATUS_SUCCESS; int fd = -1, needs_close = 0; TRACE("%p %s %p %d %p %d %p\n", hDevice, iocode2str(dwIoControlCode), lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, piosb); piosb->Information = 0; if (dwIoControlCode != IOCTL_SERIAL_GET_TIMEOUTS && dwIoControlCode != IOCTL_SERIAL_SET_TIMEOUTS) { enum server_fd_type type; if ((status = server_get_unix_fd( hDevice, access, &fd, &needs_close, &type, NULL ))) goto error; if (type != FD_TYPE_SERIAL) { if (needs_close) close( fd ); status = STATUS_OBJECT_TYPE_MISMATCH; goto error; } } switch (dwIoControlCode) { case IOCTL_SERIAL_CLR_DTR: #ifdef TIOCM_DTR if (whack_modem(fd, ~TIOCM_DTR, 0) == -1) status = FILE_GetNtStatus(); #else status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_CLR_RTS: #ifdef TIOCM_RTS if (whack_modem(fd, ~TIOCM_RTS, 0) == -1) status = FILE_GetNtStatus(); #else status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_GET_BAUD_RATE: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_BAUD_RATE)) { if (!(status = get_baud_rate(fd, lpOutBuffer))) sz = sizeof(SERIAL_BAUD_RATE); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_CHARS: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_CHARS)) { if (!(status = get_special_chars(fd, lpOutBuffer))) sz = sizeof(SERIAL_CHARS); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_COMMSTATUS: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_STATUS)) { if (!(status = get_status(fd, lpOutBuffer))) sz = sizeof(SERIAL_STATUS); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_HANDFLOW: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_HANDFLOW)) { if (!(status = get_hand_flow(fd, lpOutBuffer))) sz = sizeof(SERIAL_HANDFLOW); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_LINE_CONTROL: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_LINE_CONTROL)) { if (!(status = get_line_control(fd, lpOutBuffer))) sz = sizeof(SERIAL_LINE_CONTROL); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_MODEMSTATUS: if (lpOutBuffer && nOutBufferSize == sizeof(DWORD)) { if (!(status = get_modem_status(fd, lpOutBuffer))) sz = sizeof(DWORD); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_TIMEOUTS: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_TIMEOUTS)) { if (!(status = get_timeouts(hDevice, lpOutBuffer))) sz = sizeof(SERIAL_TIMEOUTS); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_WAIT_MASK: if (lpOutBuffer && nOutBufferSize == sizeof(DWORD)) { if (!(status = get_wait_mask(hDevice, lpOutBuffer))) sz = sizeof(DWORD); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_IMMEDIATE_CHAR: if (lpInBuffer && nInBufferSize == sizeof(CHAR)) status = xmit_immediate(hDevice, fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_PURGE: if (lpInBuffer && nInBufferSize == sizeof(DWORD)) status = purge(fd, *(DWORD*)lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_RESET_DEVICE: FIXME("Unsupported\n"); break; case IOCTL_SERIAL_SET_BAUD_RATE: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_BAUD_RATE)) status = set_baud_rate(fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_BREAK_OFF: #if defined(TIOCSBRK) && defined(TIOCCBRK) /* check if available for compilation */ if (ioctl(fd, TIOCCBRK, 0) == -1) { TRACE("ioctl failed\n"); status = FILE_GetNtStatus(); } #else FIXME("ioctl not available\n"); status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_SET_BREAK_ON: #if defined(TIOCSBRK) && defined(TIOCCBRK) /* check if available for compilation */ if (ioctl(fd, TIOCSBRK, 0) == -1) { TRACE("ioctl failed\n"); status = FILE_GetNtStatus(); } #else FIXME("ioctl not available\n"); status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_SET_CHARS: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_CHARS)) status = set_special_chars(fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_DTR: #ifdef TIOCM_DTR if (whack_modem(fd, 0, TIOCM_DTR) == -1) status = FILE_GetNtStatus(); #else status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_SET_HANDFLOW: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_HANDFLOW)) status = set_handflow(fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_LINE_CONTROL: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_LINE_CONTROL)) status = set_line_control(fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_QUEUE_SIZE: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_QUEUE_SIZE)) status = set_queue_size(fd, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_RTS: #ifdef TIOCM_RTS if (whack_modem(fd, 0, TIOCM_RTS) == -1) status = FILE_GetNtStatus(); #else status = STATUS_NOT_SUPPORTED; #endif break; case IOCTL_SERIAL_SET_TIMEOUTS: if (lpInBuffer && nInBufferSize == sizeof(SERIAL_TIMEOUTS)) status = set_timeouts(hDevice, lpInBuffer); else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_WAIT_MASK: if (lpInBuffer && nInBufferSize == sizeof(DWORD)) { status = set_wait_mask(hDevice, *(DWORD*)lpInBuffer); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_SET_XOFF: status = set_XOff(fd); break; case IOCTL_SERIAL_SET_XON: status = set_XOn(fd); break; case IOCTL_SERIAL_WAIT_ON_MASK: if (lpOutBuffer && nOutBufferSize == sizeof(DWORD)) { if (!(status = wait_on(hDevice, fd, hEvent, piosb, lpOutBuffer))) sz = sizeof(DWORD); } else status = STATUS_INVALID_PARAMETER; break; default: FIXME("Unsupported IOCTL %x (type=%x access=%x func=%x meth=%x)\n", dwIoControlCode, dwIoControlCode >> 16, (dwIoControlCode >> 14) & 3, (dwIoControlCode >> 2) & 0xFFF, dwIoControlCode & 3); sz = 0; status = STATUS_INVALID_PARAMETER; break; } if (needs_close) close( fd ); error: piosb->u.Status = status; piosb->Information = sz; if (hEvent && status != STATUS_PENDING) NtSetEvent(hEvent, NULL); return status; } NTSTATUS COMM_DeviceIoControl(HANDLE hDevice, HANDLE hEvent, PIO_APC_ROUTINE UserApcRoutine, PVOID UserApcContext, PIO_STATUS_BLOCK piosb, ULONG dwIoControlCode, LPVOID lpInBuffer, DWORD nInBufferSize, LPVOID lpOutBuffer, DWORD nOutBufferSize) { NTSTATUS status; if (dwIoControlCode == IOCTL_SERIAL_WAIT_ON_MASK) { HANDLE hev = hEvent; /* this is an ioctl we implement in a non blocking way if hEvent is not * null * so we have to explicitly wait if no hEvent is provided */ if (!hev) { OBJECT_ATTRIBUTES attr; attr.Length = sizeof(attr); attr.RootDirectory = 0; attr.ObjectName = NULL; attr.Attributes = OBJ_CASE_INSENSITIVE | OBJ_OPENIF; attr.SecurityDescriptor = NULL; attr.SecurityQualityOfService = NULL; status = NtCreateEvent(&hev, EVENT_ALL_ACCESS, &attr, FALSE, FALSE); if (status) goto done; } status = io_control(hDevice, hev, UserApcRoutine, UserApcContext, piosb, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize); if (hev != hEvent) { if (status == STATUS_PENDING) { NtWaitForSingleObject(hev, FALSE, NULL); status = STATUS_SUCCESS; } NtClose(hev); } } else status = io_control(hDevice, hEvent, UserApcRoutine, UserApcContext, piosb, dwIoControlCode, lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize); done: return status; }