/* Serial interface for local (hardwired) serial ports on Windows systems
- Copyright (C) 2006
- Free Software Foundation, Inc.
+ Copyright (C) 2006, 2007 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
+ the Free Software Foundation; either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
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. */
+ along with this program. If not, see <http://www.gnu.org/licenses/>. */
#include "defs.h"
#include "serial.h"
#include "ser-tcp.h"
#include <windows.h>
+#include <conio.h>
#include <fcntl.h>
#include <unistd.h>
HANDLE start_select;
HANDLE stop_select;
+ HANDLE exit_select;
+ HANDLE have_stopped;
+
+ HANDLE thread;
};
static DWORD WINAPI
console_select_thread (void *arg)
{
struct serial *scb = arg;
- struct ser_console_state *state, state_copy;
- int event_index, fd;
+ struct ser_console_state *state;
+ int event_index;
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);
+ state = scb->state;
+ h = (HANDLE) _get_osfhandle (scb->fd);
while (1)
{
INPUT_RECORD record;
DWORD n_records;
+ SetEvent (state->have_stopped);
+
wait_events[0] = state->start_select;
- wait_events[1] = state->stop_select;
+ wait_events[1] = state->exit_select;
if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
- {
- CloseHandle (state->stop_select);
- return 0;
- }
+ return 0;
+
+ ResetEvent (state->have_stopped);
retry:
wait_events[0] = state->stop_select;
if (event_index == WAIT_OBJECT_0
|| WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
- {
- CloseHandle (state->stop_select);
- return 0;
- }
+ continue;
if (event_index != WAIT_OBJECT_0 + 1)
{
if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown)
{
- /* This is really a keypress. */
- SetEvent (state->read_event);
- continue;
+ WORD keycode = record.Event.KeyEvent.wVirtualKeyCode;
+
+ /* Ignore events containing only control keys. We must
+ recognize "enhanced" keys which we are interested in
+ reading via getch, if they do not map to ASCII. But we
+ do not want to report input available for e.g. the
+ control key alone. */
+
+ if (record.Event.KeyEvent.uChar.AsciiChar != 0
+ || keycode == VK_PRIOR
+ || keycode == VK_NEXT
+ || keycode == VK_END
+ || keycode == VK_HOME
+ || keycode == VK_LEFT
+ || keycode == VK_UP
+ || keycode == VK_RIGHT
+ || keycode == VK_DOWN
+ || keycode == VK_INSERT
+ || keycode == VK_DELETE)
+ {
+ /* This is really a keypress. */
+ SetEvent (state->read_event);
+ continue;
+ }
}
/* Otherwise discard it and wait again. */
return 0;
}
+static int
+fd_is_file (int fd)
+{
+ if (GetFileType ((HANDLE) _get_osfhandle (fd)) == FILE_TYPE_DISK)
+ 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;
+ struct ser_console_state *state;
+ int event_index;
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);
+ state = scb->state;
+ h = (HANDLE) _get_osfhandle (scb->fd);
while (1)
{
HANDLE wait_events[2];
DWORD n_avail;
+ SetEvent (state->have_stopped);
+
wait_events[0] = state->start_select;
- wait_events[1] = state->stop_select;
+ wait_events[1] = state->exit_select;
if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
- {
- CloseHandle (state->stop_select);
- return 0;
- }
+ return 0;
+
+ ResetEvent (state->have_stopped);
retry:
if (!PeekNamedPipe (h, NULL, 0, NULL, &n_avail, NULL))
continue;
}
- if (WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
+ /* Delay 10ms before checking again, but allow the stop event
+ to wake us. */
+ if (WaitForSingleObject (state->stop_select, 10) == WAIT_OBJECT_0)
+ continue;
+
+ goto retry;
+ }
+}
+
+static DWORD WINAPI
+file_select_thread (void *arg)
+{
+ struct serial *scb = arg;
+ struct ser_console_state *state;
+ int event_index;
+ HANDLE h;
+
+ state = scb->state;
+ h = (HANDLE) _get_osfhandle (scb->fd);
+
+ while (1)
+ {
+ HANDLE wait_events[2];
+ DWORD n_avail;
+
+ SetEvent (state->have_stopped);
+
+ wait_events[0] = state->start_select;
+ wait_events[1] = state->exit_select;
+
+ if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
+ return 0;
+
+ ResetEvent (state->have_stopped);
+
+ if (SetFilePointer (h, 0, NULL, FILE_CURRENT) == INVALID_SET_FILE_POINTER)
{
- CloseHandle (state->stop_select);
- return 0;
+ SetEvent (state->except_event);
+ continue;
}
- Sleep (10);
- goto retry;
+ SetEvent (state->read_event);
}
}
int is_tty;
is_tty = isatty (scb->fd);
- if (!is_tty && !fd_is_pipe (scb->fd))
+ if (!is_tty && !fd_is_file (scb->fd) && !fd_is_pipe (scb->fd))
{
*read = NULL;
*except = NULL;
memset (state, 0, sizeof (struct ser_console_state));
scb->state = state;
- /* Create auto reset events to wake and terminate the select thread. */
+ /* Create auto reset events to wake, stop, and exit the select
+ thread. */
state->start_select = CreateEvent (0, FALSE, FALSE, 0);
state->stop_select = CreateEvent (0, FALSE, FALSE, 0);
+ state->exit_select = CreateEvent (0, FALSE, FALSE, 0);
- /* Create our own events to report read and exceptions separately.
- The exception event is currently never used. */
+ /* Create a manual reset event to signal whether the thread is
+ stopped. This must be manual reset, because we may wait on
+ it multiple times without ever starting the thread. */
+ state->have_stopped = CreateEvent (0, TRUE, FALSE, 0);
+
+ /* Create our own events to report read and exceptions separately. */
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);
+ state->thread = CreateThread (NULL, 0, console_select_thread, scb, 0,
+ &threadId);
+ else if (fd_is_pipe (scb->fd))
+ state->thread = CreateThread (NULL, 0, pipe_select_thread, scb, 0,
+ &threadId);
else
- CreateThread (NULL, 0, pipe_select_thread, scb, 0, &threadId);
+ state->thread = CreateThread (NULL, 0, file_select_thread, scb, 0,
+ &threadId);
}
+ *read = state->read_event;
+ *except = state->except_event;
+
+ /* Start from a blank state. */
ResetEvent (state->read_event);
ResetEvent (state->except_event);
+ ResetEvent (state->stop_select);
+
+ /* First check for a key already in the buffer. If there is one,
+ we don't need a thread. This also catches the second key of
+ multi-character returns from getch, for instance for arrow
+ keys. The second half is in a C library internal buffer,
+ and PeekConsoleInput will not find it. */
+ if (_kbhit ())
+ {
+ SetEvent (state->read_event);
+ return;
+ }
+ /* Otherwise, start the select thread. */
SetEvent (state->start_select);
+}
- *read = state->read_event;
- *except = state->except_event;
+static void
+ser_console_done_wait_handle (struct serial *scb)
+{
+ struct ser_console_state *state = scb->state;
+
+ if (state == NULL)
+ return;
+
+ SetEvent (state->stop_select);
+ WaitForSingleObject (state->have_stopped, INFINITE);
}
static void
if (scb->state)
{
- SetEvent (state->stop_select);
+ SetEvent (state->exit_select);
+
+ WaitForSingleObject (state->thread, INFINITE);
+
+ CloseHandle (state->start_select);
+ CloseHandle (state->stop_select);
+ CloseHandle (state->exit_select);
+ CloseHandle (state->have_stopped);
CloseHandle (state->read_event);
CloseHandle (state->except_event);
return NULL;
}
+struct pipe_state
+{
+ /* Since we use the pipe_select_thread for our select emulation,
+ we need to place the state structure it requires at the front
+ of our state. */
+ struct ser_console_state wait;
+
+ /* The pex obj for our (one-stage) pipeline. */
+ struct pex_obj *pex;
+
+ /* Streams for the pipeline's input and output. */
+ FILE *input, *output;
+};
+
+static struct pipe_state *
+make_pipe_state (void)
+{
+ struct pipe_state *ps = XMALLOC (struct pipe_state);
+
+ memset (ps, 0, sizeof (*ps));
+ ps->wait.read_event = INVALID_HANDLE_VALUE;
+ ps->wait.except_event = INVALID_HANDLE_VALUE;
+ ps->wait.start_select = INVALID_HANDLE_VALUE;
+ ps->wait.stop_select = INVALID_HANDLE_VALUE;
+
+ return ps;
+}
+
+static void
+free_pipe_state (struct pipe_state *ps)
+{
+ int saved_errno = errno;
+
+ if (ps->wait.read_event != INVALID_HANDLE_VALUE)
+ {
+ SetEvent (ps->wait.exit_select);
+
+ WaitForSingleObject (ps->wait.thread, INFINITE);
+
+ CloseHandle (ps->wait.start_select);
+ CloseHandle (ps->wait.stop_select);
+ CloseHandle (ps->wait.exit_select);
+ CloseHandle (ps->wait.have_stopped);
+
+ CloseHandle (ps->wait.read_event);
+ CloseHandle (ps->wait.except_event);
+ }
+
+ /* Close the pipe to the child. We must close the pipe before
+ calling pex_free because pex_free will wait for the child to exit
+ and the child will not exit until the pipe is closed. */
+ if (ps->input)
+ fclose (ps->input);
+ if (ps->pex)
+ pex_free (ps->pex);
+ /* pex_free closes ps->output. */
+
+ xfree (ps);
+
+ errno = saved_errno;
+}
+
+static void
+cleanup_pipe_state (void *untyped)
+{
+ struct pipe_state *ps = untyped;
+
+ free_pipe_state (ps);
+}
+
+static int
+pipe_windows_open (struct serial *scb, const char *name)
+{
+ struct pipe_state *ps;
+ FILE *pex_stderr;
+
+ char **argv = buildargv (name);
+ struct cleanup *back_to = make_cleanup_freeargv (argv);
+ if (! argv[0] || argv[0][0] == '\0')
+ error ("missing child command");
+
+
+ ps = make_pipe_state ();
+ make_cleanup (cleanup_pipe_state, ps);
+
+ ps->pex = pex_init (PEX_USE_PIPES, "target remote pipe", NULL);
+ if (! ps->pex)
+ goto fail;
+ ps->input = pex_input_pipe (ps->pex, 1);
+ if (! ps->input)
+ goto fail;
+
+ {
+ int err;
+ const char *err_msg
+ = pex_run (ps->pex, PEX_SEARCH | PEX_BINARY_INPUT | PEX_BINARY_OUTPUT
+ | PEX_STDERR_TO_PIPE,
+ argv[0], argv, NULL, NULL,
+ &err);
+
+ if (err_msg)
+ {
+ /* Our caller expects us to return -1, but all they'll do with
+ it generally is print the message based on errno. We have
+ all the same information here, plus err_msg provided by
+ pex_run, so we just raise the error here. */
+ if (err)
+ error ("error starting child process '%s': %s: %s",
+ name, err_msg, safe_strerror (err));
+ else
+ error ("error starting child process '%s': %s",
+ name, err_msg);
+ }
+ }
+
+ ps->output = pex_read_output (ps->pex, 1);
+ if (! ps->output)
+ goto fail;
+ scb->fd = fileno (ps->output);
+
+ pex_stderr = pex_read_err (ps->pex, 1);
+ if (! pex_stderr)
+ goto fail;
+ scb->error_fd = fileno (pex_stderr);
+
+ scb->state = (void *) ps;
+
+ discard_cleanups (back_to);
+ return 0;
+
+ fail:
+ do_cleanups (back_to);
+ return -1;
+}
+
+
+static void
+pipe_windows_close (struct serial *scb)
+{
+ struct pipe_state *ps = scb->state;
+
+ /* In theory, we should try to kill the subprocess here, but the pex
+ interface doesn't give us enough information to do that. Usually
+ closing the input pipe will get the message across. */
+
+ free_pipe_state (ps);
+}
+
+
+static int
+pipe_windows_read (struct serial *scb, size_t count)
+{
+ HANDLE pipeline_out = (HANDLE) _get_osfhandle (scb->fd);
+ DWORD available;
+ DWORD bytes_read;
+
+ if (pipeline_out == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (! PeekNamedPipe (pipeline_out, NULL, 0, NULL, &available, NULL))
+ return -1;
+
+ if (count > available)
+ count = available;
+
+ if (! ReadFile (pipeline_out, scb->buf, count, &bytes_read, NULL))
+ return -1;
+
+ return bytes_read;
+}
+
+
+static int
+pipe_windows_write (struct serial *scb, const void *buf, size_t count)
+{
+ struct pipe_state *ps = scb->state;
+ HANDLE pipeline_in;
+ DWORD written;
+
+ int pipeline_in_fd = fileno (ps->input);
+ if (pipeline_in_fd < 0)
+ return -1;
+
+ pipeline_in = (HANDLE) _get_osfhandle (pipeline_in_fd);
+ if (pipeline_in == INVALID_HANDLE_VALUE)
+ return -1;
+
+ if (! WriteFile (pipeline_in, buf, count, &written, NULL))
+ return -1;
+
+ return written;
+}
+
+
+static void
+pipe_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except)
+{
+ struct pipe_state *ps = scb->state;
+
+ /* Have we allocated our events yet? */
+ if (ps->wait.read_event == INVALID_HANDLE_VALUE)
+ {
+ DWORD threadId;
+
+ /* Create auto reset events to wake, stop, and exit the select
+ thread. */
+ ps->wait.start_select = CreateEvent (0, FALSE, FALSE, 0);
+ ps->wait.stop_select = CreateEvent (0, FALSE, FALSE, 0);
+ ps->wait.exit_select = CreateEvent (0, FALSE, FALSE, 0);
+
+ /* Create a manual reset event to signal whether the thread is
+ stopped. This must be manual reset, because we may wait on
+ it multiple times without ever starting the thread. */
+ ps->wait.have_stopped = CreateEvent (0, TRUE, FALSE, 0);
+
+ /* Create our own events to report read and exceptions separately.
+ The exception event is currently never used. */
+ ps->wait.read_event = CreateEvent (0, FALSE, FALSE, 0);
+ ps->wait.except_event = CreateEvent (0, FALSE, FALSE, 0);
+
+ /* Start the select thread. */
+ CreateThread (NULL, 0, pipe_select_thread, scb, 0, &threadId);
+ }
+
+ *read = ps->wait.read_event;
+ *except = ps->wait.except_event;
+
+ /* Start from a blank state. */
+ ResetEvent (ps->wait.read_event);
+ ResetEvent (ps->wait.except_event);
+ ResetEvent (ps->wait.stop_select);
+
+ /* Start the select thread. */
+ SetEvent (ps->wait.start_select);
+}
+
+static void
+pipe_done_wait_handle (struct serial *scb)
+{
+ struct pipe_state *ps = scb->state;
+
+ /* Have we allocated our events yet? */
+ if (ps->wait.read_event == INVALID_HANDLE_VALUE)
+ return;
+
+ SetEvent (ps->wait.stop_select);
+ WaitForSingleObject (ps->wait.have_stopped, INFINITE);
+}
+
+static int
+pipe_avail (struct serial *scb, int fd)
+{
+ HANDLE h = (HANDLE) _get_osfhandle (fd);
+ DWORD numBytes;
+ BOOL r = PeekNamedPipe (h, NULL, 0, NULL, &numBytes, NULL);
+ if (r == FALSE)
+ numBytes = 0;
+ return numBytes;
+}
+
struct net_windows_state
{
HANDLE read_event;
HANDLE start_select;
HANDLE stop_select;
+ HANDLE exit_select;
+ HANDLE have_stopped;
+
HANDLE sock_event;
+
+ HANDLE thread;
};
static DWORD WINAPI
{
struct serial *scb = arg;
struct net_windows_state *state, state_copy;
- int event_index, fd;
+ int event_index;
- /* 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;
+ state = scb->state;
while (1)
{
HANDLE wait_events[2];
WSANETWORKEVENTS events;
+ SetEvent (state->have_stopped);
+
wait_events[0] = state->start_select;
- wait_events[1] = state->stop_select;
+ wait_events[1] = state->exit_select;
if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) != WAIT_OBJECT_0)
- {
- CloseHandle (state->stop_select);
- return 0;
- }
+ return 0;
+
+ ResetEvent (state->have_stopped);
wait_events[0] = state->stop_select;
wait_events[1] = state->sock_event;
if (event_index == WAIT_OBJECT_0
|| WaitForSingleObject (state->stop_select, 0) == WAIT_OBJECT_0)
- {
- CloseHandle (state->stop_select);
- return 0;
- }
+ continue;
if (event_index != WAIT_OBJECT_0 + 1)
{
/* Enumerate the internal network events, and reset the object that
signalled us to catch the next event. */
- WSAEnumNetworkEvents (fd, state->sock_event, &events);
+ WSAEnumNetworkEvents (scb->fd, state->sock_event, &events);
+
+ gdb_assert (events.lNetworkEvents & (FD_READ | FD_CLOSE));
if (events.lNetworkEvents & FD_READ)
SetEvent (state->read_event);
{
struct net_windows_state *state = scb->state;
+ /* Start from a clean slate. */
ResetEvent (state->read_event);
ResetEvent (state->except_event);
-
- SetEvent (state->start_select);
+ ResetEvent (state->stop_select);
*read = state->read_event;
*except = state->except_event;
+
+ /* Check any pending events. This both avoids starting the thread
+ unnecessarily, and handles stray FD_READ events (see below). */
+ if (WaitForSingleObject (state->sock_event, 0) == WAIT_OBJECT_0)
+ {
+ WSANETWORKEVENTS events;
+ int any = 0;
+
+ /* Enumerate the internal network events, and reset the object that
+ signalled us to catch the next event. */
+ WSAEnumNetworkEvents (scb->fd, state->sock_event, &events);
+
+ /* You'd think that FD_READ or FD_CLOSE would be set here. But,
+ sometimes, neither is. I suspect that the FD_READ is set and
+ the corresponding event signalled while recv is running, and
+ the FD_READ is then lowered when recv consumes all the data,
+ but there's no way to un-signal the event. This isn't a
+ problem for the call in net_select_thread, since any new
+ events after this point will not have been drained by recv.
+ It just means that we can't have the obvious assert here. */
+
+ /* If there is a read event, it might be still valid, or it might
+ not be - it may have been signalled before we last called
+ recv. Double-check that there is data. */
+ if (events.lNetworkEvents & FD_READ)
+ {
+ unsigned long available;
+
+ if (ioctlsocket (scb->fd, FIONREAD, &available) == 0
+ && available > 0)
+ {
+ SetEvent (state->read_event);
+ any = 1;
+ }
+ else
+ /* Oops, no data. This call to recv will cause future
+ data to retrigger the event, e.g. while we are
+ in net_select_thread. */
+ recv (scb->fd, NULL, 0, 0);
+ }
+
+ /* If there's a close event, then record it - it is obviously
+ still valid, and it will not be resignalled. */
+ if (events.lNetworkEvents & FD_CLOSE)
+ {
+ SetEvent (state->except_event);
+ any = 1;
+ }
+
+ /* If we set either handle, there's no need to wake the thread. */
+ if (any)
+ return;
+ }
+
+ /* Start the select thread. */
+ SetEvent (state->start_select);
+}
+
+static void
+net_windows_done_wait_handle (struct serial *scb)
+{
+ struct net_windows_state *state = scb->state;
+
+ SetEvent (state->stop_select);
+ WaitForSingleObject (state->have_stopped, INFINITE);
}
static int
memset (state, 0, sizeof (struct net_windows_state));
scb->state = state;
- /* Create auto reset events to wake and terminate the select thread. */
+ /* Create auto reset events to wake, stop, and exit the select
+ thread. */
state->start_select = CreateEvent (0, FALSE, FALSE, 0);
state->stop_select = CreateEvent (0, FALSE, FALSE, 0);
+ state->exit_select = CreateEvent (0, FALSE, FALSE, 0);
+
+ /* Create a manual reset event to signal whether the thread is
+ stopped. This must be manual reset, because we may wait on
+ it multiple times without ever starting the thread. */
+ state->have_stopped = CreateEvent (0, TRUE, FALSE, 0);
/* Associate an event with the socket. */
state->sock_event = CreateEvent (0, TRUE, 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);
+ state->thread = CreateThread (NULL, 0, net_windows_select_thread, scb, 0,
+ &threadId);
return 0;
}
{
struct net_windows_state *state = scb->state;
- SetEvent (state->stop_select);
+ SetEvent (state->exit_select);
+ WaitForSingleObject (state->thread, INFINITE);
CloseHandle (state->read_event);
CloseHandle (state->except_event);
+
CloseHandle (state->start_select);
+ CloseHandle (state->stop_select);
+ CloseHandle (state->exit_select);
+ CloseHandle (state->have_stopped);
+
CloseHandle (state->sock_event);
xfree (scb->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;
+ ops->done_wait_handle = ser_console_done_wait_handle;
+
+ serial_add_interface (ops);
+
+ /* The pipe interface. */
+
+ ops = XMALLOC (struct serial_ops);
+ memset (ops, 0, sizeof (struct serial_ops));
+ ops->name = "pipe";
+ ops->next = 0;
+ ops->open = pipe_windows_open;
+ ops->close = pipe_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 = pipe_windows_read;
+ ops->write_prim = pipe_windows_write;
+ ops->wait_handle = pipe_wait_handle;
+ ops->done_wait_handle = pipe_done_wait_handle;
+ ops->avail = pipe_avail;
serial_add_interface (ops);
ops->read_prim = net_read_prim;
ops->write_prim = net_write_prim;
ops->wait_handle = net_windows_wait_handle;
+ ops->done_wait_handle = net_windows_done_wait_handle;
serial_add_interface (ops);
}