/* Serial interface for local (hardwired) serial ports on Windows systems

   Copyright (C) 2006
   Free Software Foundation, Inc.

   This file is part of GDB.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program 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 General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 51 Franklin Street, Fifth Floor,
   Boston, MA 02110-1301, USA.  */

#include "defs.h"
#include "serial.h"
#include "ser-base.h"
#include "ser-tcp.h"

#include <windows.h>

#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>

#include "gdb_assert.h"
#include "gdb_string.h"

void _initialize_ser_windows (void);

struct ser_windows_state
{
  int in_progress;
  OVERLAPPED ov;
  DWORD lastCommMask;
  HANDLE except_event;
};

/* Open up a real live device for serial I/O.  */

static int
ser_windows_open (struct serial *scb, const char *name)
{
  HANDLE h;
  struct ser_windows_state *state;
  COMMTIMEOUTS timeouts;

  /* Only allow COM ports.  */
  if (strncmp (name, "COM", 3) != 0)
    {
      errno = ENOENT;
      return -1;
    }

  h = CreateFile (name, GENERIC_READ | GENERIC_WRITE, 0, NULL,
		  OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
  if (h == INVALID_HANDLE_VALUE)
    {
      errno = ENOENT;
      return -1;
    }

  scb->fd = _open_osfhandle ((long) h, O_RDWR);
  if (scb->fd < 0)
    {
      errno = ENOENT;
      return -1;
    }

  if (!SetCommMask (h, EV_RXCHAR))
    {
      errno = EINVAL;
      return -1;
    }

  timeouts.ReadIntervalTimeout = MAXDWORD;
  timeouts.ReadTotalTimeoutConstant = 0;
  timeouts.ReadTotalTimeoutMultiplier = 0;
  timeouts.WriteTotalTimeoutConstant = 0;
  timeouts.WriteTotalTimeoutMultiplier = 0;
  if (!SetCommTimeouts (h, &timeouts))
    {
      errno = EINVAL;
      return -1;
    }

  state = xmalloc (sizeof (struct ser_windows_state));
  memset (state, 0, sizeof (struct ser_windows_state));
  scb->state = state;

  /* Create a manual reset event to watch the input buffer.  */
  state->ov.hEvent = CreateEvent (0, TRUE, FALSE, 0);

  /* Create a (currently unused) handle to record exceptions.  */
  state->except_event = CreateEvent (0, TRUE, FALSE, 0);

  return 0;
}

/* Wait for the output to drain away, as opposed to flushing (discarding)
   it.  */

static int
ser_windows_drain_output (struct serial *scb)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);

  return (FlushFileBuffers (h) != 0) ? 0 : -1;
}

static int
ser_windows_flush_output (struct serial *scb)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);

  return (PurgeComm (h, PURGE_TXCLEAR) != 0) ? 0 : -1;
}

static int
ser_windows_flush_input (struct serial *scb)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);

  return (PurgeComm (h, PURGE_RXCLEAR) != 0) ? 0 : -1;
}

static int
ser_windows_send_break (struct serial *scb)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);

  if (SetCommBreak (h) == 0)
    return -1;

  /* Delay for 250 milliseconds.  */
  Sleep (250);

  if (ClearCommBreak (h))
    return -1;

  return 0;
}

static void
ser_windows_raw (struct serial *scb)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);
  DCB state;

  if (GetCommState (h, &state) == 0)
    return;

  state.fParity = FALSE;
  state.fOutxCtsFlow = FALSE;
  state.fOutxDsrFlow = FALSE;
  state.fDtrControl = DTR_CONTROL_ENABLE;
  state.fDsrSensitivity = FALSE;
  state.fOutX = FALSE;
  state.fInX = FALSE;
  state.fNull = FALSE;
  state.fAbortOnError = FALSE;
  state.ByteSize = 8;
  state.Parity = NOPARITY;

  scb->current_timeout = 0;

  if (SetCommState (h, &state) == 0)
    warning (_("SetCommState failed\n"));
}

static int
ser_windows_setstopbits (struct serial *scb, int num)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);
  DCB state;

  if (GetCommState (h, &state) == 0)
    return -1;

  switch (num)
    {
    case SERIAL_1_STOPBITS:
      state.StopBits = ONESTOPBIT;
      break;
    case SERIAL_1_AND_A_HALF_STOPBITS:
      state.StopBits = ONE5STOPBITS;
      break;
    case SERIAL_2_STOPBITS:
      state.StopBits = TWOSTOPBITS;
      break;
    default:
      return 1;
    }

  return (SetCommState (h, &state) != 0) ? 0 : -1;
}

static int
ser_windows_setbaudrate (struct serial *scb, int rate)
{
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);
  DCB state;

  if (GetCommState (h, &state) == 0)
    return -1;

  state.BaudRate = rate;

  return (SetCommState (h, &state) != 0) ? 0 : -1;
}

static void
ser_windows_close (struct serial *scb)
{
  struct ser_windows_state *state;

  /* Stop any pending selects.  */
  CancelIo ((HANDLE) _get_osfhandle (scb->fd));
  state = scb->state;
  CloseHandle (state->ov.hEvent);
  CloseHandle (state->except_event);

  if (scb->fd < 0)
    return;

  close (scb->fd);
  scb->fd = -1;

  xfree (scb->state);
}

static void
ser_windows_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
{
  struct ser_windows_state *state;
  COMSTAT status;
  DWORD errors;
  HANDLE h = (HANDLE) _get_osfhandle (scb->fd);

  state = scb->state;

  *except = state->except_event;
  *read = state->ov.hEvent;

  if (state->in_progress)
    return;

  /* Reset the mask - we are only interested in any characters which
     arrive after this point, not characters which might have arrived
     and already been read.  */

  /* This really, really shouldn't be necessary - just the second one.
     But otherwise an internal flag for EV_RXCHAR does not get
     cleared, and we get a duplicated event, if the last batch
     of characters included at least two arriving close together.  */
  if (!SetCommMask (h, 0))
    warning (_("ser_windows_wait_handle: reseting mask failed"));

  if (!SetCommMask (h, EV_RXCHAR))
    warning (_("ser_windows_wait_handle: reseting mask failed (2)"));

  /* There's a potential race condition here; we must check cbInQue
     and not wait if that's nonzero.  */

  ClearCommError (h, &errors, &status);
  if (status.cbInQue > 0)
    {
      SetEvent (state->ov.hEvent);
      return;
    }

  state->in_progress = 1;
  ResetEvent (state->ov.hEvent);
  state->lastCommMask = -2;
  if (WaitCommEvent (h, &state->lastCommMask, &state->ov))
    {
      gdb_assert (state->lastCommMask & EV_RXCHAR);
      SetEvent (state->ov.hEvent);
    }
  else
    gdb_assert (GetLastError () == ERROR_IO_PENDING);
}

static int
ser_windows_read_prim (struct serial *scb, size_t count)
{
  struct ser_windows_state *state;
  OVERLAPPED ov;
  DWORD bytes_read, bytes_read_tmp;
  HANDLE h;
  gdb_byte *p;

  state = scb->state;
  if (state->in_progress)
    {
      WaitForSingleObject (state->ov.hEvent, INFINITE);
      state->in_progress = 0;
      ResetEvent (state->ov.hEvent);
    }

  memset (&ov, 0, sizeof (OVERLAPPED));
  ov.hEvent = CreateEvent (0, FALSE, FALSE, 0);
  h = (HANDLE) _get_osfhandle (scb->fd);

  if (!ReadFile (h, scb->buf, /* count */ 1, &bytes_read, &ov))
    {
      if (GetLastError () != ERROR_IO_PENDING
	  || !GetOverlappedResult (h, &ov, &bytes_read, TRUE))
	bytes_read = -1;
    }

  CloseHandle (ov.hEvent);
  return bytes_read;
}

static int
ser_windows_write_prim (struct serial *scb, const void *buf, size_t len)
{
  struct ser_windows_state *state;
  OVERLAPPED ov;
  DWORD bytes_written;
  HANDLE h;

  memset (&ov, 0, sizeof (OVERLAPPED));
  ov.hEvent = CreateEvent (0, FALSE, FALSE, 0);
  h = (HANDLE) _get_osfhandle (scb->fd);
  if (!WriteFile (h, buf, len, &bytes_written, &ov))
    {
      if (GetLastError () != ERROR_IO_PENDING
	  || !GetOverlappedResult (h, &ov, &bytes_written, TRUE))
	bytes_written = -1;
    }

  CloseHandle (ov.hEvent);
  return bytes_written;
}

struct ser_console_state
{
  HANDLE read_event;
  HANDLE except_event;

  HANDLE start_select;
  HANDLE stop_select;
};

static DWORD WINAPI
console_select_thread (void *arg)
{
  struct serial *scb = arg;
  struct ser_console_state *state, state_copy;
  int event_index, fd;
  HANDLE h;

  /* Copy useful information out of the control block, to make sure
     that we do not race with freeing it.  */
  state_copy = *(struct ser_console_state *) scb->state;
  state = &state_copy;
  fd = scb->fd;

  h = (HANDLE) _get_osfhandle (fd);

  while (1)
    {
      HANDLE wait_events[2];
      INPUT_RECORD record;
      DWORD n_records;

      wait_events[0] = state->start_select;
      wait_events[1] = state->stop_select;

      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

    retry:
      wait_events[0] = state->stop_select;
      wait_events[1] = h;

      event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE);

      if (event_index == WAIT_OBJECT_0
	  || WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

      if (event_index != WAIT_OBJECT_0 + 1)
	{
	  /* Wait must have failed; assume an error has occured, e.g.
	     the handle has been closed.  */
	  SetEvent (state->except_event);
	  continue;
	}

      /* We've got a pending event on the console.  See if it's
	 of interest.  */
      if (!PeekConsoleInput (h, &record, 1, &n_records) || n_records != 1)
	{
	  /* Something went wrong.  Maybe the console is gone.  */
	  SetEvent (state->except_event);
	  continue;
	}

      if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
	{
	  /* This is really a keypress.  */
	  SetEvent (state->read_event);
	  continue;
	}

      /* Otherwise discard it and wait again.  */
      ReadConsoleInput (h, &record, 1, &n_records);
      goto retry;
    }
}

static int
fd_is_pipe (int fd)
{
  if (PeekNamedPipe ((HANDLE) _get_osfhandle (fd), NULL, 0, NULL, NULL, NULL))
    return 1;
  else
    return 0;
}

static DWORD WINAPI
pipe_select_thread (void *arg)
{
  struct serial *scb = arg;
  struct ser_console_state *state, state_copy;
  int event_index, fd;
  HANDLE h;

  /* Copy useful information out of the control block, to make sure
     that we do not race with freeing it.  */
  state_copy = *(struct ser_console_state *) scb->state;
  state = &state_copy;
  fd = scb->fd;

  h = (HANDLE) _get_osfhandle (fd);

  while (1)
    {
      HANDLE wait_events[2];
      DWORD n_avail;

      wait_events[0] = state->start_select;
      wait_events[1] = state->stop_select;

      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

    retry:
      if (!PeekNamedPipe (h, NULL, 0, NULL, &n_avail, NULL))
	{
	  SetEvent (state->except_event);
	  continue;
	}

      if (n_avail > 0)
	{
	  SetEvent (state->read_event);
	  continue;
	}

      if (WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

      Sleep (10);
      goto retry;
    }
}

static void
ser_console_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
{
  struct ser_console_state *state = scb->state;

  if (state == NULL)
    {
      DWORD threadId;
      int is_tty;

      is_tty = isatty (scb->fd);
      if (!is_tty && !fd_is_pipe (scb->fd))
	{
	  *read = NULL;
	  *except = NULL;
	  return;
	}

      state = xmalloc (sizeof (struct ser_console_state));
      memset (state, 0, sizeof (struct ser_console_state));
      scb->state = state;

      /* Create auto reset events to wake and terminate the select thread.  */
      state->start_select = CreateEvent (0, FALSE, FALSE, 0);
      state->stop_select = CreateEvent (0, FALSE, FALSE, 0);

      /* Create our own events to report read and exceptions separately.
	 The exception event is currently never used.  */
      state->read_event = CreateEvent (0, FALSE, FALSE, 0);
      state->except_event = CreateEvent (0, FALSE, FALSE, 0);

      /* And finally start the select thread.  */
      if (is_tty)
	CreateThread (NULL, 0, console_select_thread, scb, 0, &threadId);
      else
	CreateThread (NULL, 0, pipe_select_thread, scb, 0, &threadId);
    }

  ResetEvent (state->read_event);
  ResetEvent (state->except_event);

  SetEvent (state->start_select);

  *read = state->read_event;
  *except = state->except_event;
}

static void
ser_console_close (struct serial *scb)
{
  struct ser_console_state *state = scb->state;

  if (scb->state)
    {
      SetEvent (state->stop_select);

      CloseHandle (state->read_event);
      CloseHandle (state->except_event);

      xfree (scb->state);
    }
}

struct ser_console_ttystate
{
  int is_a_tty;
};

static serial_ttystate
ser_console_get_tty_state (struct serial *scb)
{
  if (isatty (scb->fd))
    {
      struct ser_console_ttystate *state;
      state = (struct ser_console_ttystate *) xmalloc (sizeof *state);
      state->is_a_tty = 1;
      return state;
    }
  else
    return NULL;
}

struct net_windows_state
{
  HANDLE read_event;
  HANDLE except_event;

  HANDLE start_select;
  HANDLE stop_select;
  HANDLE sock_event;
};

static DWORD WINAPI
net_windows_select_thread (void *arg)
{
  struct serial *scb = arg;
  struct net_windows_state *state, state_copy;
  int event_index, fd;

  /* Copy useful information out of the control block, to make sure
     that we do not race with freeing it.  */
  state_copy = *(struct net_windows_state *) scb->state;
  state = &state_copy;
  fd = scb->fd;

  while (1)
    {
      HANDLE wait_events[2];
      WSANETWORKEVENTS events;

      wait_events[0] = state->start_select;
      wait_events[1] = state->stop_select;

      if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

      wait_events[0] = state->stop_select;
      wait_events[1] = state->sock_event;

      event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE);

      if (event_index == WAIT_OBJECT_0
	  || WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
	{
	  CloseHandle (state->stop_select);
	  return 0;
	}

      if (event_index != WAIT_OBJECT_0 + 1)
	{
	  /* Some error has occured.  Assume that this is an error
	     condition.  */
	  SetEvent (state->except_event);
	  continue;
	}

      /* Enumerate the internal network events, and reset the object that
	 signalled us to catch the next event.  */
      WSAEnumNetworkEvents (fd, state->sock_event, &events);

      if (events.lNetworkEvents & FD_READ)
	SetEvent (state->read_event);

      if (events.lNetworkEvents & FD_CLOSE)
	SetEvent (state->except_event);
    }
}

static void
net_windows_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
{
  struct net_windows_state *state = scb->state;

  ResetEvent (state->read_event);
  ResetEvent (state->except_event);

  SetEvent (state->start_select);

  *read = state->read_event;
  *except = state->except_event;
}

static int
net_windows_open (struct serial *scb, const char *name)
{
  struct net_windows_state *state;
  int ret;
  DWORD threadId;

  ret = net_open (scb, name);
  if (ret != 0)
    return ret;

  state = xmalloc (sizeof (struct net_windows_state));
  memset (state, 0, sizeof (struct net_windows_state));
  scb->state = state;

  /* Create auto reset events to wake and terminate the select thread.  */
  state->start_select = CreateEvent (0, FALSE, FALSE, 0);
  state->stop_select = CreateEvent (0, FALSE, FALSE, 0);

  /* Associate an event with the socket.  */
  state->sock_event = CreateEvent (0, TRUE, FALSE, 0);
  WSAEventSelect (scb->fd, state->sock_event, FD_READ | FD_CLOSE);

  /* Create our own events to report read and close separately.  */
  state->read_event = CreateEvent (0, FALSE, FALSE, 0);
  state->except_event = CreateEvent (0, FALSE, FALSE, 0);

  /* And finally start the select thread.  */
  CreateThread (NULL, 0, net_windows_select_thread, scb, 0, &threadId);

  return 0;
}


static void
net_windows_close (struct serial *scb)
{
  struct net_windows_state *state = scb->state;

  SetEvent (state->stop_select);

  CloseHandle (state->read_event);
  CloseHandle (state->except_event);
  CloseHandle (state->start_select);
  CloseHandle (state->sock_event);

  xfree (scb->state);

  net_close (scb);
}

void
_initialize_ser_windows (void)
{
  WSADATA wsa_data;
  struct serial_ops *ops;

  /* First register the serial port driver.  */

  ops = XMALLOC (struct serial_ops);
  memset (ops, 0, sizeof (struct serial_ops));
  ops->name = "hardwire";
  ops->next = 0;
  ops->open = ser_windows_open;
  ops->close = ser_windows_close;

  ops->flush_output = ser_windows_flush_output;
  ops->flush_input = ser_windows_flush_input;
  ops->send_break = ser_windows_send_break;

  /* These are only used for stdin; we do not need them for serial
     ports, so supply the standard dummies.  */
  ops->get_tty_state = ser_base_get_tty_state;
  ops->set_tty_state = ser_base_set_tty_state;
  ops->print_tty_state = ser_base_print_tty_state;
  ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;

  ops->go_raw = ser_windows_raw;
  ops->setbaudrate = ser_windows_setbaudrate;
  ops->setstopbits = ser_windows_setstopbits;
  ops->drain_output = ser_windows_drain_output;
  ops->readchar = ser_base_readchar;
  ops->write = ser_base_write;
  ops->async = ser_base_async;
  ops->read_prim = ser_windows_read_prim;
  ops->write_prim = ser_windows_write_prim;
  ops->wait_handle = ser_windows_wait_handle;

  serial_add_interface (ops);

  /* Next create the dummy serial driver used for terminals.  We only
     provide the TTY-related methods.  */

  ops = XMALLOC (struct serial_ops);
  memset (ops, 0, sizeof (struct serial_ops));

  ops->name = "terminal";
  ops->next = 0;

  ops->close = ser_console_close;
  ops->get_tty_state = ser_console_get_tty_state;
  ops->set_tty_state = ser_base_set_tty_state;
  ops->print_tty_state = ser_base_print_tty_state;
  ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;
  ops->drain_output = ser_base_drain_output;
  ops->wait_handle = ser_console_wait_handle;

  serial_add_interface (ops);

  /* If WinSock works, register the TCP/UDP socket driver.  */

  if (WSAStartup (MAKEWORD (1, 0), &wsa_data) != 0)
    /* WinSock is unavailable.  */
    return;

  ops = XMALLOC (struct serial_ops);
  memset (ops, 0, sizeof (struct serial_ops));
  ops->name = "tcp";
  ops->next = 0;
  ops->open = net_windows_open;
  ops->close = net_windows_close;
  ops->readchar = ser_base_readchar;
  ops->write = ser_base_write;
  ops->flush_output = ser_base_flush_output;
  ops->flush_input = ser_base_flush_input;
  ops->send_break = ser_base_send_break;
  ops->go_raw = ser_base_raw;
  ops->get_tty_state = ser_base_get_tty_state;
  ops->set_tty_state = ser_base_set_tty_state;
  ops->print_tty_state = ser_base_print_tty_state;
  ops->noflush_set_tty_state = ser_base_noflush_set_tty_state;
  ops->setbaudrate = ser_base_setbaudrate;
  ops->setstopbits = ser_base_setstopbits;
  ops->drain_output = ser_base_drain_output;
  ops->async = ser_base_async;
  ops->read_prim = net_read_prim;
  ops->write_prim = net_write_prim;
  ops->wait_handle = net_windows_wait_handle;
  serial_add_interface (ops);
}