#include <config.h>
#include <io.h>
#include <stdio.h>
#include "ntp.h"
#include "ntp_tty.h"
#include "lib_strbuf.h"
#include "ntp_assert.h"
#include "win32_io.h"
#include "ntp_iocplmem.h"
#include "ntp_iocpltypes.h"
#define MAX_SERIAL 255
#define MAX_COMDUP 8
typedef struct comhandles_tag {
uint16_t unit;
uint16_t nhnd;
char * comName;
DevCtx_t * devCtx;
HANDLE htab[MAX_COMDUP];
} comhandles;
comhandles ** tab_comh;
size_t num_comh;
size_t max_comh;
static comhandles*
lookup_com_handle(
HANDLE h,
size_t * phidx
)
{
size_t tidx, hidx;
comhandles * slot;
for (tidx = 0; tidx < num_comh; ++tidx) {
slot = tab_comh[tidx];
for (hidx = 0; hidx < slot->nhnd; ++hidx) {
if (slot->htab[hidx] == h) {
if (phidx != NULL)
*phidx = hidx;
return slot;
}
}
}
return NULL;
}
static comhandles*
insert_com_unit(
uint16_t unit
)
{
size_t tidx;
comhandles * slot;
for (tidx = 0; tidx < num_comh; ++tidx)
if (tab_comh[tidx]->unit == unit)
return tab_comh[tidx];
if (num_comh >= max_comh) {
max_comh = (num_comh + 4) & ~(size_t)3;
tab_comh = erealloc(tab_comh, max_comh * sizeof(tab_comh[0]));
}
slot = emalloc_zero(sizeof(comhandles));
LIB_GETBUF(slot->comName);
snprintf(slot->comName, LIB_BUFLENGTH, "\\\\.\\COM%d", unit);
slot->comName = estrdup(slot->comName);
slot->devCtx = DevCtxAlloc();
slot->unit = unit;
tab_comh[num_comh++] = slot;
return slot;
}
static void
remove_com_slot(
comhandles * slot
)
{
size_t tidx;
for (tidx = 0; tidx < num_comh; ++tidx)
if (tab_comh[tidx] == slot) {
tab_comh[tidx] = tab_comh[--num_comh];
break;
}
DevCtxDetach(slot->devCtx);
free(slot->comName);
free(slot);
}
DevCtx_t*
serial_devctx(
HANDLE h
)
{
comhandles * slot = NULL;
if (INVALID_HANDLE_VALUE != h && NULL != h)
slot = lookup_com_handle(h, NULL);
return (NULL != slot) ? slot->devCtx : NULL;
}
HANDLE
common_serial_open(
const char * dev,
const char ** pwindev
)
{
HANDLE handle;
size_t unit;
const char * pch;
comhandles * slot;
TRACE(1, ("common_serial_open given %s\n", dev));
handle = INVALID_HANDLE_VALUE;
pch = NULL;
if ('/' == dev[0]) {
pch = dev + strlen(dev);
while (isdigit((u_char)pch[-1]))
--pch;
TRACE(1, ("common_serial_open skipped to ending digits leaving %s\n", pch));
} else if (0 == _strnicmp("COM", dev, 3)) {
pch = dev + 3;
TRACE(1, ("common_serial_open skipped COM leaving %s\n", pch));
}
if (!pch || !isdigit((u_char)pch[0])) {
TRACE(1, ("not a digit: %s\n", pch ? pch : "[NULL]"));
return INVALID_HANDLE_VALUE;
}
unit = strtoul(pch, (char**)&pch, 10);
if (*pch || unit > MAX_SERIAL) {
TRACE(1, ("conversion failure: unit=%u at '%s'\n", pch));
return INVALID_HANDLE_VALUE;
}
slot = insert_com_unit(unit);
if (slot->nhnd == 0) {
TRACE(1, ("windows device %s\n", slot->comName));
slot->htab[0] = CreateFileA(
slot->comName,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
if (INVALID_HANDLE_VALUE != slot->htab[0]) {
slot->nhnd = 1;
handle = slot->htab[0];
*pwindev = slot->comName;
}
} else if (slot->nhnd >= MAX_COMDUP) {
SetLastError(ERROR_TOO_MANY_OPEN_FILES);
} else if (DuplicateHandle(GetCurrentProcess(), slot->htab[0],
GetCurrentProcess(), &slot->htab[slot->nhnd],
0, FALSE, DUPLICATE_SAME_ACCESS))
{
handle = slot->htab[slot->nhnd++];
*pwindev = slot->comName;
}
return handle;
}
int
closeserial(
int fd
)
{
HANDLE h;
size_t hidx;
comhandles * slot;
h = (HANDLE)_get_osfhandle(fd);
if (INVALID_HANDLE_VALUE == h)
goto onerror;
slot = lookup_com_handle(h, &hidx);
if (NULL == slot)
goto onerror;
slot->htab[hidx] = slot->htab[--slot->nhnd];
if (slot->nhnd == 0)
remove_com_slot(slot);
return close(fd);
onerror:
errno = EBADF;
return -1;
}
int
isserialhandle(
HANDLE h
)
{
if (INVALID_HANDLE_VALUE != h && NULL != h)
return lookup_com_handle(h, NULL) != NULL;
return FALSE;
}
int
tty_open(
const char *dev,
int access,
int mode
)
{
HANDLE Handle;
const char * windev;
windev = dev;
Handle = common_serial_open(dev, &windev);
if (Handle == INVALID_HANDLE_VALUE) {
msyslog(LOG_ERR, "tty_open: device %s CreateFile error: %m", windev);
errno = EMFILE;
return -1;
}
return _open_osfhandle((intptr_t)Handle, _O_TEXT);
}
int
refclock_open(
const char * dev,
u_int speed,
u_int flags
)
{
const char * windev;
HANDLE h;
COMMTIMEOUTS timeouts;
DCB dcb;
DWORD dwEvtMask;
int fd;
int translate;
windev = dev;
h = common_serial_open(dev, &windev);
if (INVALID_HANDLE_VALUE == h) {
SAVE_ERRNO(
msyslog(LOG_ERR, "CreateFile(%s) error: %m",
windev);
)
return -1;
}
if (!SetupComm(h, 1024, 1024)) {
SAVE_ERRNO(
msyslog(LOG_ERR, "SetupComm(%s) error: %m",
windev);
)
return -1;
}
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(h, &dcb)) {
SAVE_ERRNO(
msyslog(LOG_ERR,
"GetCommState(%s) error: %m",
windev);
)
return -1;
}
switch (speed) {
case B300:
dcb.BaudRate = 300;
break;
case B1200:
dcb.BaudRate = 1200;
break;
case B2400:
dcb.BaudRate = 2400;
break;
case B4800:
dcb.BaudRate = 4800;
break;
case B9600:
dcb.BaudRate = 9600;
break;
case B19200:
dcb.BaudRate = 19200;
break;
case B38400:
dcb.BaudRate = 38400;
break;
case B57600:
dcb.BaudRate = 57600;
break;
case B115200:
dcb.BaudRate = 115200;
break;
default:
msyslog(LOG_ERR, "%s unsupported bps code %u", windev,
speed);
SetLastError(ERROR_INVALID_PARAMETER);
return -1;
}
dcb.fBinary = TRUE;
dcb.fParity = TRUE;
dcb.fOutxCtsFlow = 0;
dcb.fOutxDsrFlow = 0;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fDsrSensitivity = 0;
dcb.fTXContinueOnXoff = TRUE;
dcb.fOutX = 0;
dcb.fInX = 0;
dcb.fErrorChar = 0;
dcb.fNull = 0;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fAbortOnError = 0;
dcb.ByteSize = 8;
dcb.StopBits = ONESTOPBIT;
dcb.Parity = NOPARITY;
dcb.ErrorChar = 0;
dcb.EofChar = 0;
if (LDISC_RAW & flags)
dcb.EvtChar = 0;
else
dcb.EvtChar = '\r';
if (!SetCommState(h, &dcb)) {
SAVE_ERRNO(
msyslog(LOG_ERR, "SetCommState(%s) error: %m",
windev);
)
return -1;
}
dwEvtMask = EV_RLSD;
if (LDISC_RAW & flags)
dwEvtMask |= EV_RXCHAR;
else
dwEvtMask |= EV_RXFLAG;
if (!SetCommMask(h, dwEvtMask)) {
SAVE_ERRNO(
msyslog(LOG_ERR, "SetCommMask(%s) error: %m",
windev);
)
return -1;
}
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
if (!SetCommTimeouts(h, &timeouts)) {
SAVE_ERRNO(
msyslog(LOG_ERR,
"Device %s SetCommTimeouts error: %m",
windev);
)
return -1;
}
translate = (LDISC_RAW & flags)
? 0
: _O_TEXT;
fd = _open_osfhandle((intptr_t)h, translate);
if (0 == fd) {
fd = _dup(0);
_close(0);
}
return fd;
}
int
ioctl_tiocmget(
HANDLE h,
int *pi
)
{
DWORD dw;
if (!GetCommModemStatus(h, &dw)) {
errno = ENOTTY;
return -1;
}
*pi = ((dw & MS_CTS_ON) ? TIOCM_CTS : 0)
| ((dw & MS_DSR_ON) ? TIOCM_DSR : 0)
| ((dw & MS_RLSD_ON) ? TIOCM_CAR : 0)
| ((dw & MS_RING_ON) ? TIOCM_RI : 0);
return 0;
}
int
ioctl_tiocmset(
HANDLE h,
int *pi
)
{
BOOL failed;
int result;
failed = !EscapeCommFunction(
h,
(*pi & TIOCM_RTS)
? SETRTS
: CLRRTS
);
if (!failed)
failed = !EscapeCommFunction(
h,
(*pi & TIOCM_DTR)
? SETDTR
: CLRDTR
);
if (failed) {
errno = ENOTTY;
result = -1;
} else
result = 0;
return result;
}
int
ioctl(
int fd,
int op,
void *pv
)
{
HANDLE h;
int result;
int modctl;
int *pi = (int *) pv;
h = (HANDLE)_get_osfhandle(fd);
if (INVALID_HANDLE_VALUE == h) {
return -1;
}
switch (op) {
case TIOCMGET:
result = ioctl_tiocmget(h, pi);
break;
case TIOCMSET:
result = ioctl_tiocmset(h, pi);
break;
case TIOCMBIC:
result = ioctl_tiocmget(h, &modctl);
if (result < 0)
return result;
modctl &= ~(*pi);
result = ioctl_tiocmset(h, &modctl);
break;
case TIOCMBIS:
result = ioctl_tiocmget(h, &modctl);
if (result < 0)
return result;
modctl |= *pi;
result = ioctl_tiocmset(h, &modctl);
break;
default:
errno = EINVAL;
result = -1;
}
return result;
}
int
tcsetattr(
int fd,
int optional_actions,
const struct termios * tios
)
{
DCB dcb;
HANDLE h;
UNUSED_ARG(optional_actions);
h = (HANDLE)_get_osfhandle(fd);
if (INVALID_HANDLE_VALUE == h) {
return -1;
}
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(h, &dcb)) {
errno = ENOTTY;
return -1;
}
switch (max(tios->c_ospeed, tios->c_ispeed)) {
case B300:
dcb.BaudRate = 300;
break;
case B1200:
dcb.BaudRate = 1200;
break;
case B2400:
dcb.BaudRate = 2400;
break;
case B4800:
dcb.BaudRate = 4800;
break;
case B9600:
dcb.BaudRate = 9600;
break;
case B19200:
dcb.BaudRate = 19200;
break;
case B38400:
dcb.BaudRate = 38400;
break;
case B57600:
dcb.BaudRate = 57600;
break;
case B115200:
dcb.BaudRate = 115200;
break;
default:
msyslog(LOG_ERR, "unsupported serial baud rate");
errno = EINVAL;
return -1;
}
switch (tios->c_cflag & CSIZE) {
case CS5:
dcb.ByteSize = 5;
break;
case CS6:
dcb.ByteSize = 6;
break;
case CS7:
dcb.ByteSize = 7;
break;
case CS8:
dcb.ByteSize = 8;
break;
default:
msyslog(LOG_ERR, "unsupported serial word size");
errno = EINVAL;
return FALSE;
}
if (PARENB & tios->c_cflag) {
dcb.fParity = TRUE;
dcb.Parity = (tios->c_cflag & PARODD)
? ODDPARITY
: EVENPARITY;
} else {
dcb.fParity = FALSE;
dcb.Parity = NOPARITY;
}
dcb.StopBits = (CSTOPB & tios->c_cflag)
? TWOSTOPBITS
: ONESTOPBIT;
if (!SetCommState(h, &dcb)) {
errno = ENOTTY;
return -1;
}
return 0;
}
int
tcgetattr(
int fd,
struct termios *tios
)
{
DCB dcb;
HANDLE h;
h = (HANDLE)_get_osfhandle(fd);
if (INVALID_HANDLE_VALUE == h) {
return -1;
}
dcb.DCBlength = sizeof(dcb);
if (!GetCommState(h, &dcb)) {
errno = ENOTTY;
return -1;
}
switch (dcb.BaudRate) {
case 300:
tios->c_ispeed = tios->c_ospeed = B300;
break;
case 1200:
tios->c_ispeed = tios->c_ospeed = B1200;
break;
case 2400:
tios->c_ispeed = tios->c_ospeed = B2400;
break;
case 4800:
tios->c_ispeed = tios->c_ospeed = B4800;
break;
case 9600:
tios->c_ispeed = tios->c_ospeed = B9600;
break;
case 19200:
tios->c_ispeed = tios->c_ospeed = B19200;
break;
case 38400:
tios->c_ispeed = tios->c_ospeed = B38400;
break;
case 57600:
tios->c_ispeed = tios->c_ospeed = B57600;
break;
case 115200:
tios->c_ispeed = tios->c_ospeed = B115200;
break;
default:
tios->c_ispeed = tios->c_ospeed = B9600;
}
switch (dcb.ByteSize) {
case 5:
tios->c_cflag = CS5;
break;
case 6:
tios->c_cflag = CS6;
break;
case 7:
tios->c_cflag = CS7;
break;
case 8:
default:
tios->c_cflag = CS8;
}
if (dcb.fParity) {
tios->c_cflag |= PARENB;
if (ODDPARITY == dcb.Parity)
tios->c_cflag |= PARODD;
}
if (TWOSTOPBITS == dcb.StopBits)
tios->c_cflag |= CSTOPB;
tios->c_iflag = 0;
tios->c_lflag = 0;
tios->c_line = 0;
tios->c_oflag = 0;
return 0;
}
int
tcflush(
int fd,
int mode
)
{
HANDLE h;
BOOL success;
DWORD flags;
int result;
h = (HANDLE)_get_osfhandle(fd);
if (INVALID_HANDLE_VALUE == h) {
return -1;
}
switch (mode) {
case TCIFLUSH:
flags = PURGE_RXCLEAR;
break;
case TCOFLUSH:
flags = PURGE_TXABORT;
break;
case TCIOFLUSH:
flags = PURGE_RXCLEAR | PURGE_TXABORT;
break;
default:
errno = EINVAL;
return -1;
}
success = PurgeComm(h, flags);
if (success)
result = 0;
else {
errno = ENOTTY;
result = -1;
}
return result;
}