|  | /* Main file for COMM support | 
|  | * | 
|  | * DEC 93 Erik Bos <erik@xs4all.nl> | 
|  | * 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 <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 | 
|  | #ifdef HAVE_TERMIOS_H | 
|  | #include <termios.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 | 
|  | #include <linux/serial.h> | 
|  | #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; | 
|  | } |