/* Main file for COMM support * * DEC 93 Erik Bos * Copyright 1996 Marcus Meissner * Copyright 2005 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "config.h" #include "wine/port.h" #include #include #include #include #include #ifdef HAVE_STRINGS_H # include #endif #ifdef HAVE_TERMIOS_H #include #endif #ifdef HAVE_IO_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #ifdef HAVE_TERMIOS_H #include #endif #include #ifdef HAVE_SYS_STAT_H # include #endif #include #ifdef HAVE_SYS_FILIO_H # include #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_SYS_POLL_H # include #endif #ifdef HAVE_SYS_MODEM_H # include #endif #ifdef HAVE_SYS_STRTIO_H # include #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 #include #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_%ld\n", ioc); return tmp; } } } static NTSTATUS get_modem_status(int fd, DWORD* lpModemStat) { NTSTATUS status = STATUS_SUCCESS; int mstat; #ifdef TIOCMGET if (ioctl(fd, TIOCMGET, &mstat) == -1) { WARN("ioctl failed\n"); status = FILE_GetNtStatus(); } else { *lpModemStat = 0; #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 " : ""); } #else status = STATUS_NOT_SUPPORTED; #endif return status; } 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_wait_mask(HANDLE hDevice, DWORD* mask) { NTSTATUS status; SERVER_START_REQ( get_serial_info ) { req->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_wait_mask(HANDLE hDevice, DWORD mask) { NTSTATUS status; SERVER_START_REQ( set_serial_info ) { req->handle = hDevice; req->flags = SERIALINFO_SET_MASK; req->eventmask = mask; status = wine_server_call( req ); } SERVER_END_REQ; return status; } static NTSTATUS xmit_immediate(HANDLE hDevice, int fd, 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 * * */ 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) { DWORD sz = 0, access = FILE_READ_DATA; NTSTATUS status = STATUS_SUCCESS; int fd; TRACE("%p %s %p %ld %p %ld %p\n", hDevice, iocode2str(dwIoControlCode), lpInBuffer, nInBufferSize, lpOutBuffer, nOutBufferSize, piosb); piosb->Information = 0; if ((status = wine_server_handle_to_fd( hDevice, access, &fd, NULL ))) goto error; switch (dwIoControlCode) { case IOCTL_SERIAL_GET_COMMSTATUS: if (lpOutBuffer && nOutBufferSize == sizeof(SERIAL_STATUS)) { if (!(status = get_status(fd, (SERIAL_STATUS*)lpOutBuffer))) sz = sizeof(SERIAL_STATUS); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_MODEMSTATUS: if (lpOutBuffer && nOutBufferSize == sizeof(DWORD)) { if (!(status = get_modem_status(fd, (DWORD*)lpOutBuffer))) sz = sizeof(DWORD); } else status = STATUS_INVALID_PARAMETER; break; case IOCTL_SERIAL_GET_WAIT_MASK: if (lpOutBuffer && nOutBufferSize == sizeof(DWORD)) { if (!(status = get_wait_mask(hDevice, (DWORD*)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_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_WAIT_MASK: if (lpInBuffer && nInBufferSize == sizeof(DWORD)) { status = set_wait_mask(hDevice, *(DWORD*)lpInBuffer); } else status = STATUS_INVALID_PARAMETER; break; default: FIXME("Unsupported IOCTL %lx (type=%lx access=%lx func=%lx meth=%lx)\n", dwIoControlCode, dwIoControlCode >> 16, (dwIoControlCode >> 14) & 3, (dwIoControlCode >> 2) & 0xFFF, dwIoControlCode & 3); sz = 0; status = STATUS_INVALID_PARAMETER; break; } wine_server_release_fd( hDevice, fd ); error: piosb->u.Status = status; piosb->Information = sz; if (hEvent) NtSetEvent(hEvent, NULL); return status; }