| /* Serial interface for local (hardwired) serial ports on Windows systems |
| |
| Copyright (C) 2006-2024 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 3 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, see <http://www.gnu.org/licenses/>. */ |
| |
| #include "serial.h" |
| #include "ser-base.h" |
| #include "ser-tcp.h" |
| |
| #include <windows.h> |
| #include <conio.h> |
| |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <sys/types.h> |
| |
| #include "command.h" |
| #include "gdbsupport/buildargv.h" |
| |
| struct ser_windows_state |
| { |
| int in_progress; |
| OVERLAPPED ov; |
| DWORD lastCommMask; |
| HANDLE except_event; |
| }; |
| |
| /* CancelIo is not available for Windows 95 OS, so we need to use |
| LoadLibrary/GetProcAddress to avoid a startup failure. */ |
| #define CancelIo dyn_CancelIo |
| typedef BOOL WINAPI (CancelIo_ftype) (HANDLE); |
| static CancelIo_ftype *CancelIo; |
| |
| /* Open up a real live device for serial I/O. */ |
| |
| static void |
| ser_windows_open (struct serial *scb, const char *name) |
| { |
| HANDLE h; |
| struct ser_windows_state *state; |
| COMMTIMEOUTS timeouts; |
| |
| h = CreateFile (name, GENERIC_READ | GENERIC_WRITE, 0, NULL, |
| OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); |
| if (h == INVALID_HANDLE_VALUE) |
| { |
| std::string msg = string_printf(_("could not open file: %s"), |
| name); |
| throw_winerror_with_name (msg.c_str (), GetLastError ()); |
| } |
| |
| scb->fd = _open_osfhandle ((intptr_t) h, O_RDWR); |
| if (scb->fd < 0) |
| error (_("could not get underlying file descriptor")); |
| |
| if (!SetCommMask (h, EV_RXCHAR)) |
| throw_winerror_with_name (_("error calling SetCommMask"), |
| GetLastError ()); |
| |
| timeouts.ReadIntervalTimeout = MAXDWORD; |
| timeouts.ReadTotalTimeoutConstant = 0; |
| timeouts.ReadTotalTimeoutMultiplier = 0; |
| timeouts.WriteTotalTimeoutConstant = 0; |
| timeouts.WriteTotalTimeoutMultiplier = 0; |
| if (!SetCommTimeouts (h, &timeouts)) |
| throw_winerror_with_name (_("error calling SetCommTimeouts"), |
| GetLastError ()); |
| |
| state = XCNEW (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); |
| } |
| |
| /* 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 void |
| ser_windows_send_break (struct serial *scb) |
| { |
| HANDLE h = (HANDLE) _get_osfhandle (scb->fd); |
| |
| if (SetCommBreak (h) == 0) |
| throw_winerror_with_name ("error calling SetCommBreak", |
| GetLastError ()); |
| |
| /* Delay for 250 milliseconds. */ |
| Sleep (250); |
| |
| if (ClearCommBreak (h) == 0) |
| throw_winerror_with_name ("error calling ClearCommBreak", |
| GetLastError ()); |
| } |
| |
| static void |
| ser_windows_raw (struct serial *scb) |
| { |
| HANDLE h = (HANDLE) _get_osfhandle (scb->fd); |
| DCB state; |
| |
| if (GetCommState (h, &state) == 0) |
| return; |
| |
| 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; |
| |
| if (SetCommState (h, &state) == 0) |
| warning (_("SetCommState failed")); |
| } |
| |
| 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; |
| } |
| |
| /* Implement the "setparity" serial_ops callback. */ |
| |
| static int |
| ser_windows_setparity (struct serial *scb, int parity) |
| { |
| HANDLE h = (HANDLE) _get_osfhandle (scb->fd); |
| DCB state; |
| |
| if (GetCommState (h, &state) == 0) |
| return -1; |
| |
| switch (parity) |
| { |
| case GDBPARITY_NONE: |
| state.Parity = NOPARITY; |
| state.fParity = FALSE; |
| break; |
| case GDBPARITY_ODD: |
| state.Parity = ODDPARITY; |
| state.fParity = TRUE; |
| break; |
| case GDBPARITY_EVEN: |
| state.Parity = EVENPARITY; |
| state.fParity = TRUE; |
| break; |
| default: |
| internal_warning ("Incorrect parity value: %d", parity); |
| return -1; |
| } |
| |
| return (SetCommState (h, &state) != 0) ? 0 : -1; |
| } |
| |
| static void |
| ser_windows_setbaudrate (struct serial *scb, int rate) |
| { |
| HANDLE h = (HANDLE) _get_osfhandle (scb->fd); |
| DCB state; |
| |
| if (GetCommState (h, &state) == 0) |
| throw_winerror_with_name ("call to GetCommState failed", GetLastError ()); |
| |
| state.BaudRate = rate; |
| |
| if (SetCommState (h, &state) == 0) |
| throw_winerror_with_name ("call to SetCommState failed", GetLastError ()); |
| } |
| |
| static void |
| ser_windows_close (struct serial *scb) |
| { |
| struct ser_windows_state *state; |
| |
| /* Stop any pending selects. On Windows 95 OS, CancelIo function does |
| not exist. In that case, it can be replaced by a call to CloseHandle, |
| but this is not necessary here as we do close the Windows handle |
| by calling close (scb->fd) below. */ |
| if (CancelIo) |
| CancelIo ((HANDLE) _get_osfhandle (scb->fd)); |
| state = (struct ser_windows_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 = (struct ser_windows_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; |
| HANDLE h; |
| |
| state = (struct ser_windows_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)) |
| { |
| ULONGEST err = GetLastError (); |
| CloseHandle (ov.hEvent); |
| throw_winerror_with_name (_("error while reading"), err); |
| } |
| } |
| |
| CloseHandle (ov.hEvent); |
| return bytes_read; |
| } |
| |
| static int |
| ser_windows_write_prim (struct serial *scb, const void *buf, size_t len) |
| { |
| 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)) |
| throw_winerror_with_name ("error while writing", GetLastError ()); |
| } |
| |
| CloseHandle (ov.hEvent); |
| return bytes_written; |
| } |
| |
| /* On Windows, gdb_select is implemented using WaitForMulpleObjects. |
| A "select thread" is created for each file descriptor. These |
| threads looks for activity on the corresponding descriptor, using |
| whatever techniques are appropriate for the descriptor type. When |
| that activity occurs, the thread signals an appropriate event, |
| which wakes up WaitForMultipleObjects. |
| |
| Each select thread is in one of two states: stopped or started. |
| Select threads begin in the stopped state. When gdb_select is |
| called, threads corresponding to the descriptors of interest are |
| started by calling a wait_handle function. Each thread that |
| notices activity signals the appropriate event and then reenters |
| the stopped state. Before gdb_select returns it calls the |
| wait_handle_done functions, which return the threads to the stopped |
| state. */ |
| |
| enum select_thread_state { |
| STS_STARTED, |
| STS_STOPPED |
| }; |
| |
| struct ser_console_state |
| { |
| /* Signaled by the select thread to indicate that data is available |
| on the file descriptor. */ |
| HANDLE read_event; |
| /* Signaled by the select thread to indicate that an exception has |
| occurred on the file descriptor. */ |
| HANDLE except_event; |
| /* Signaled by the select thread to indicate that it has entered the |
| started state. HAVE_STARTED and HAVE_STOPPED are never signaled |
| simultaneously. */ |
| HANDLE have_started; |
| /* Signaled by the select thread to indicate that it has stopped, |
| either because data is available (and READ_EVENT is signaled), |
| because an exception has occurred (and EXCEPT_EVENT is signaled), |
| or because STOP_SELECT was signaled. */ |
| HANDLE have_stopped; |
| |
| /* Signaled by the main program to tell the select thread to enter |
| the started state. */ |
| HANDLE start_select; |
| /* Signaled by the main program to tell the select thread to enter |
| the stopped state. */ |
| HANDLE stop_select; |
| /* Signaled by the main program to tell the select thread to |
| exit. */ |
| HANDLE exit_select; |
| |
| /* The handle for the select thread. */ |
| HANDLE thread; |
| /* The state of the select thread. This field is only accessed in |
| the main program, never by the select thread itself. */ |
| enum select_thread_state thread_state; |
| }; |
| |
| /* Called by a select thread to enter the stopped state. This |
| function does not return until the thread has re-entered the |
| started state. */ |
| static void |
| select_thread_wait (struct ser_console_state *state) |
| { |
| HANDLE wait_events[2]; |
| |
| /* There are two things that can wake us up: a request that we enter |
| the started state, or that we exit this thread. */ |
| wait_events[0] = state->start_select; |
| wait_events[1] = state->exit_select; |
| if (WaitForMultipleObjects (2, wait_events, FALSE, INFINITE) |
| != WAIT_OBJECT_0) |
| /* Either the EXIT_SELECT event was signaled (requesting that the |
| thread exit) or an error has occurred. In either case, we exit |
| the thread. */ |
| ExitThread (0); |
| |
| /* We are now in the started state. */ |
| SetEvent (state->have_started); |
| } |
| |
| typedef DWORD WINAPI (*thread_fn_type)(void *); |
| |
| /* Create a new select thread for SCB executing THREAD_FN. The STATE |
| will be filled in by this function before return. */ |
| static void |
| create_select_thread (thread_fn_type thread_fn, |
| struct serial *scb, |
| struct ser_console_state *state) |
| { |
| DWORD threadId; |
| |
| /* Create all of the events. These are all auto-reset events. */ |
| state->read_event = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->except_event = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->have_started = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->have_stopped = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->start_select = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->stop_select = CreateEvent (NULL, FALSE, FALSE, NULL); |
| state->exit_select = CreateEvent (NULL, FALSE, FALSE, NULL); |
| |
| state->thread = CreateThread (NULL, 0, thread_fn, scb, 0, &threadId); |
| /* The thread begins in the stopped state. */ |
| state->thread_state = STS_STOPPED; |
| } |
| |
| /* Destroy the select thread indicated by STATE. */ |
| static void |
| destroy_select_thread (struct ser_console_state *state) |
| { |
| /* Ask the thread to exit. */ |
| SetEvent (state->exit_select); |
| /* Wait until it does. */ |
| WaitForSingleObject (state->thread, INFINITE); |
| |
| /* Destroy the events. */ |
| CloseHandle (state->read_event); |
| CloseHandle (state->except_event); |
| CloseHandle (state->have_started); |
| CloseHandle (state->have_stopped); |
| CloseHandle (state->start_select); |
| CloseHandle (state->stop_select); |
| CloseHandle (state->exit_select); |
| } |
| |
| /* Called by gdb_select to start the select thread indicated by STATE. |
| This function does not return until the thread has started. */ |
| static void |
| start_select_thread (struct ser_console_state *state) |
| { |
| /* Ask the thread to start. */ |
| SetEvent (state->start_select); |
| /* Wait until it does. */ |
| WaitForSingleObject (state->have_started, INFINITE); |
| /* The thread is now started. */ |
| state->thread_state = STS_STARTED; |
| } |
| |
| /* Called by gdb_select to stop the select thread indicated by STATE. |
| This function does not return until the thread has stopped. */ |
| static void |
| stop_select_thread (struct ser_console_state *state) |
| { |
| /* If the thread is already in the stopped state, we have nothing to |
| do. Some of the wait_handle functions avoid calling |
| start_select_thread if they notice activity on the relevant file |
| descriptors. The wait_handle_done functions still call |
| stop_select_thread -- but it is already stopped. */ |
| if (state->thread_state != STS_STARTED) |
| return; |
| /* Ask the thread to stop. */ |
| SetEvent (state->stop_select); |
| /* Wait until it does. */ |
| WaitForSingleObject (state->have_stopped, INFINITE); |
| /* The thread is now stopped. */ |
| state->thread_state = STS_STOPPED; |
| } |
| |
| static DWORD WINAPI |
| console_select_thread (void *arg) |
| { |
| struct serial *scb = (struct serial *) arg; |
| struct ser_console_state *state; |
| int event_index; |
| HANDLE h; |
| |
| state = (struct ser_console_state *) scb->state; |
| h = (HANDLE) _get_osfhandle (scb->fd); |
| |
| while (1) |
| { |
| HANDLE wait_events[2]; |
| INPUT_RECORD record; |
| DWORD n_records; |
| |
| select_thread_wait (state); |
| |
| while (1) |
| { |
| 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) |
| break; |
| |
| if (event_index != WAIT_OBJECT_0 + 1) |
| { |
| /* Wait must have failed; assume an error has occurred, e.g. |
| the handle has been closed. */ |
| SetEvent (state->except_event); |
| break; |
| } |
| |
| /* 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); |
| break; |
| } |
| |
| if (record.EventType == KEY_EVENT && record.Event.KeyEvent.bKeyDown) |
| { |
| 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); |
| break; |
| } |
| } |
| else if (record.EventType == MOUSE_EVENT) |
| { |
| SetEvent (state->read_event); |
| break; |
| } |
| |
| /* Otherwise discard it and wait again. */ |
| ReadConsoleInput (h, &record, 1, &n_records); |
| } |
| |
| SetEvent(state->have_stopped); |
| } |
| return 0; |
| } |
| |
| static int |
| fd_is_pipe (int fd) |
| { |
| if (PeekNamedPipe ((HANDLE) _get_osfhandle (fd), NULL, 0, NULL, NULL, NULL)) |
| return 1; |
| else |
| 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 = (struct serial *) arg; |
| struct ser_console_state *state; |
| HANDLE h; |
| |
| state = (struct ser_console_state *) scb->state; |
| h = (HANDLE) _get_osfhandle (scb->fd); |
| |
| while (1) |
| { |
| DWORD n_avail; |
| |
| select_thread_wait (state); |
| |
| /* Wait for something to happen on the pipe. */ |
| while (1) |
| { |
| if (!PeekNamedPipe (h, NULL, 0, NULL, &n_avail, NULL)) |
| { |
| SetEvent (state->except_event); |
| break; |
| } |
| |
| if (n_avail > 0) |
| { |
| SetEvent (state->read_event); |
| break; |
| } |
| |
| /* Delay 10ms before checking again, but allow the stop |
| event to wake us. */ |
| if (WaitForSingleObject (state->stop_select, 10) == WAIT_OBJECT_0) |
| break; |
| } |
| |
| SetEvent (state->have_stopped); |
| } |
| return 0; |
| } |
| |
| static DWORD WINAPI |
| file_select_thread (void *arg) |
| { |
| struct serial *scb = (struct serial *) arg; |
| struct ser_console_state *state; |
| HANDLE h; |
| |
| state = (struct ser_console_state *) scb->state; |
| h = (HANDLE) _get_osfhandle (scb->fd); |
| |
| while (1) |
| { |
| select_thread_wait (state); |
| |
| if (SetFilePointer (h, 0, NULL, FILE_CURRENT) |
| == INVALID_SET_FILE_POINTER) |
| SetEvent (state->except_event); |
| else |
| SetEvent (state->read_event); |
| |
| SetEvent (state->have_stopped); |
| } |
| return 0; |
| } |
| |
| static void |
| ser_console_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except) |
| { |
| struct ser_console_state *state = (struct ser_console_state *) scb->state; |
| |
| if (state == NULL) |
| { |
| thread_fn_type thread_fn; |
| int is_tty; |
| |
| is_tty = isatty (scb->fd); |
| if (!is_tty && !fd_is_file (scb->fd) && !fd_is_pipe (scb->fd)) |
| { |
| *read = NULL; |
| *except = NULL; |
| return; |
| } |
| |
| state = XCNEW (struct ser_console_state); |
| scb->state = state; |
| |
| if (is_tty) |
| thread_fn = console_select_thread; |
| else if (fd_is_pipe (scb->fd)) |
| thread_fn = pipe_select_thread; |
| else |
| thread_fn = file_select_thread; |
| |
| create_select_thread (thread_fn, scb, state); |
| } |
| |
| *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. */ |
| start_select_thread (state); |
| } |
| |
| static void |
| ser_console_done_wait_handle (struct serial *scb) |
| { |
| struct ser_console_state *state = (struct ser_console_state *) scb->state; |
| |
| if (state == NULL) |
| return; |
| |
| stop_select_thread (state); |
| } |
| |
| static void |
| ser_console_close (struct serial *scb) |
| { |
| struct ser_console_state *state = (struct ser_console_state *) scb->state; |
| |
| if (scb->state) |
| { |
| destroy_select_thread (state); |
| 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 = XNEW (struct ser_console_ttystate); |
| state->is_a_tty = 1; |
| return state; |
| } |
| else |
| 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 = XCNEW (struct pipe_state); |
| |
| 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) |
| destroy_select_thread (&ps->wait); |
| |
| /* 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. */ |
| } |
| else if (ps->output) |
| fclose (ps->output); |
| |
| xfree (ps); |
| |
| errno = saved_errno; |
| } |
| |
| struct pipe_state_destroyer |
| { |
| void operator() (pipe_state *ps) const |
| { |
| free_pipe_state (ps); |
| } |
| }; |
| |
| typedef std::unique_ptr<pipe_state, pipe_state_destroyer> pipe_state_up; |
| |
| static void |
| pipe_windows_open (struct serial *scb, const char *name) |
| { |
| FILE *pex_stderr; |
| |
| if (name == NULL) |
| error_no_arg (_("child command")); |
| |
| if (*name == '|') |
| { |
| name++; |
| name = skip_spaces (name); |
| } |
| |
| gdb_argv argv (name); |
| |
| if (! argv[0] || argv[0][0] == '\0') |
| error (_("missing child command")); |
| |
| pipe_state_up ps (make_pipe_state ()); |
| |
| ps->pex = pex_init (PEX_USE_PIPES, "target remote pipe", NULL); |
| if (! ps->pex) |
| error (_("could not start pipeline")); |
| ps->input = pex_input_pipe (ps->pex, 1); |
| if (! ps->input) |
| error (_("could not find input pipe")); |
| |
| { |
| 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.get (), 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) |
| error (_("could not find output pipe")); |
| scb->fd = fileno (ps->output); |
| |
| pex_stderr = pex_read_err (ps->pex, 1); |
| if (! pex_stderr) |
| error (_("could not find error pipe")); |
| scb->error_fd = fileno (pex_stderr); |
| |
| scb->state = ps.release (); |
| } |
| |
| static int |
| pipe_windows_fdopen (struct serial *scb, int fd) |
| { |
| struct pipe_state *ps; |
| |
| ps = make_pipe_state (); |
| |
| ps->input = fdopen (fd, "r+"); |
| if (! ps->input) |
| goto fail; |
| |
| ps->output = fdopen (fd, "r+"); |
| if (! ps->output) |
| goto fail; |
| |
| scb->fd = fd; |
| scb->state = (void *) ps; |
| |
| return 0; |
| |
| fail: |
| free_pipe_state (ps); |
| return -1; |
| } |
| |
| static void |
| pipe_windows_close (struct serial *scb) |
| { |
| struct pipe_state *ps = (struct pipe_state *) 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) |
| error (_("could not find file number for pipe")); |
| |
| if (! PeekNamedPipe (pipeline_out, NULL, 0, NULL, &available, NULL)) |
| throw_winerror_with_name (_("could not peek into pipe"), GetLastError ()); |
| |
| if (count > available) |
| count = available; |
| |
| if (! ReadFile (pipeline_out, scb->buf, count, &bytes_read, NULL)) |
| throw_winerror_with_name (_("could not read from pipe"), GetLastError ()); |
| |
| return bytes_read; |
| } |
| |
| |
| static int |
| pipe_windows_write (struct serial *scb, const void *buf, size_t count) |
| { |
| struct pipe_state *ps = (struct pipe_state *) scb->state; |
| HANDLE pipeline_in; |
| DWORD written; |
| |
| int pipeline_in_fd = fileno (ps->input); |
| if (pipeline_in_fd < 0) |
| error (_("could not find file number for pipe")); |
| |
| pipeline_in = (HANDLE) _get_osfhandle (pipeline_in_fd); |
| if (pipeline_in == INVALID_HANDLE_VALUE) |
| error (_("could not find handle for pipe")); |
| |
| if (! WriteFile (pipeline_in, buf, count, &written, NULL)) |
| throw_winerror_with_name (_("could not write to pipe"), GetLastError ()); |
| |
| return written; |
| } |
| |
| |
| static void |
| pipe_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except) |
| { |
| struct pipe_state *ps = (struct pipe_state *) scb->state; |
| |
| /* Have we allocated our events yet? */ |
| if (ps->wait.read_event == INVALID_HANDLE_VALUE) |
| /* Start the thread. */ |
| create_select_thread (pipe_select_thread, scb, &ps->wait); |
| |
| *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_select_thread (&ps->wait); |
| } |
| |
| static void |
| pipe_done_wait_handle (struct serial *scb) |
| { |
| struct pipe_state *ps = (struct pipe_state *) scb->state; |
| |
| /* Have we allocated our events yet? */ |
| if (ps->wait.read_event == INVALID_HANDLE_VALUE) |
| return; |
| |
| stop_select_thread (&ps->wait); |
| } |
| |
| 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; |
| } |
| |
| int |
| gdb_pipe (int pdes[2]) |
| { |
| if (_pipe (pdes, 512, _O_BINARY | _O_NOINHERIT) == -1) |
| return -1; |
| return 0; |
| } |
| |
| struct net_windows_state |
| { |
| struct ser_console_state base; |
| |
| HANDLE sock_event; |
| }; |
| |
| /* Check whether the socket has any pending data to be read. If so, |
| set the select thread's read event. On error, set the select |
| thread's except event. If any event was set, return true, |
| otherwise return false. */ |
| |
| static int |
| net_windows_socket_check_pending (struct serial *scb) |
| { |
| struct net_windows_state *state = (struct net_windows_state *) scb->state; |
| unsigned long available; |
| |
| if (ioctlsocket (scb->fd, FIONREAD, &available) != 0) |
| { |
| /* The socket closed, or some other error. */ |
| SetEvent (state->base.except_event); |
| return 1; |
| } |
| else if (available > 0) |
| { |
| SetEvent (state->base.read_event); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| static DWORD WINAPI |
| net_windows_select_thread (void *arg) |
| { |
| struct serial *scb = (struct serial *) arg; |
| struct net_windows_state *state; |
| int event_index; |
| |
| state = (struct net_windows_state *) scb->state; |
| |
| while (1) |
| { |
| HANDLE wait_events[2]; |
| WSANETWORKEVENTS events; |
| |
| select_thread_wait (&state->base); |
| |
| wait_events[0] = state->base.stop_select; |
| wait_events[1] = state->sock_event; |
| |
| /* Wait for something to happen on the socket. */ |
| while (1) |
| { |
| event_index = WaitForMultipleObjects (2, wait_events, FALSE, INFINITE); |
| |
| if (event_index == WAIT_OBJECT_0 |
| || WaitForSingleObject (state->base.stop_select, 0) == WAIT_OBJECT_0) |
| { |
| /* We have been requested to stop. */ |
| break; |
| } |
| |
| if (event_index != WAIT_OBJECT_0 + 1) |
| { |
| /* Some error has occurred. Assume that this is an error |
| condition. */ |
| SetEvent (state->base.except_event); |
| break; |
| } |
| |
| /* Enumerate the internal network events, and reset the |
| object that signalled us to catch the next event. */ |
| if (WSAEnumNetworkEvents (scb->fd, state->sock_event, &events) != 0) |
| { |
| /* Something went wrong. Maybe the socket is gone. */ |
| SetEvent (state->base.except_event); |
| break; |
| } |
| |
| if (events.lNetworkEvents & FD_READ) |
| { |
| if (net_windows_socket_check_pending (scb)) |
| break; |
| |
| /* Spurious wakeup. That is, the socket's event was |
| signalled before we last called recv. */ |
| } |
| |
| if (events.lNetworkEvents & FD_CLOSE) |
| { |
| SetEvent (state->base.except_event); |
| break; |
| } |
| } |
| |
| SetEvent (state->base.have_stopped); |
| } |
| return 0; |
| } |
| |
| static void |
| net_windows_wait_handle (struct serial *scb, HANDLE *read, HANDLE *except) |
| { |
| struct net_windows_state *state = (struct net_windows_state *) scb->state; |
| |
| /* Start from a clean slate. */ |
| ResetEvent (state->base.read_event); |
| ResetEvent (state->base.except_event); |
| ResetEvent (state->base.stop_select); |
| |
| *read = state->base.read_event; |
| *except = state->base.except_event; |
| |
| /* Check any pending events. Otherwise, start the select |
| thread. */ |
| if (!net_windows_socket_check_pending (scb)) |
| start_select_thread (&state->base); |
| } |
| |
| static void |
| net_windows_done_wait_handle (struct serial *scb) |
| { |
| struct net_windows_state *state = (struct net_windows_state *) scb->state; |
| |
| stop_select_thread (&state->base); |
| } |
| |
| static void |
| net_windows_open (struct serial *scb, const char *name) |
| { |
| struct net_windows_state *state; |
| |
| net_open (scb, name); |
| |
| state = XCNEW (struct net_windows_state); |
| scb->state = state; |
| |
| /* Associate an event with the socket. */ |
| state->sock_event = CreateEvent (0, TRUE, FALSE, 0); |
| WSAEventSelect (scb->fd, state->sock_event, FD_READ | FD_CLOSE); |
| |
| /* Start the thread. */ |
| create_select_thread (net_windows_select_thread, scb, &state->base); |
| } |
| |
| |
| static void |
| net_windows_close (struct serial *scb) |
| { |
| struct net_windows_state *state = (struct net_windows_state *) scb->state; |
| |
| destroy_select_thread (&state->base); |
| CloseHandle (state->sock_event); |
| |
| xfree (scb->state); |
| |
| net_close (scb); |
| } |
| |
| /* The serial port driver. */ |
| |
| static const struct serial_ops hardwire_ops = |
| { |
| "hardwire", |
| ser_windows_open, |
| ser_windows_close, |
| NULL, |
| ser_base_readchar, |
| ser_base_write, |
| ser_windows_flush_output, |
| ser_windows_flush_input, |
| ser_windows_send_break, |
| ser_windows_raw, |
| /* These are only used for stdin; we do not need them for serial |
| ports, so supply the standard dummies. */ |
| ser_base_get_tty_state, |
| ser_base_copy_tty_state, |
| ser_base_set_tty_state, |
| ser_base_print_tty_state, |
| ser_windows_setbaudrate, |
| ser_windows_setstopbits, |
| ser_windows_setparity, |
| ser_windows_drain_output, |
| ser_base_async, |
| ser_windows_read_prim, |
| ser_windows_write_prim, |
| NULL, |
| ser_windows_wait_handle |
| }; |
| |
| /* The dummy serial driver used for terminals. We only provide the |
| TTY-related methods. */ |
| |
| static const struct serial_ops tty_ops = |
| { |
| "terminal", |
| NULL, |
| ser_console_close, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| ser_console_get_tty_state, |
| ser_base_copy_tty_state, |
| ser_base_set_tty_state, |
| ser_base_print_tty_state, |
| NULL, |
| NULL, |
| NULL, |
| ser_base_drain_output, |
| NULL, |
| NULL, |
| NULL, |
| NULL, |
| ser_console_wait_handle, |
| ser_console_done_wait_handle |
| }; |
| |
| /* The pipe interface. */ |
| |
| static const struct serial_ops pipe_ops = |
| { |
| "pipe", |
| pipe_windows_open, |
| pipe_windows_close, |
| pipe_windows_fdopen, |
| ser_base_readchar, |
| ser_base_write, |
| ser_base_flush_output, |
| ser_base_flush_input, |
| ser_base_send_break, |
| ser_base_raw, |
| ser_base_get_tty_state, |
| ser_base_copy_tty_state, |
| ser_base_set_tty_state, |
| ser_base_print_tty_state, |
| ser_base_setbaudrate, |
| ser_base_setstopbits, |
| ser_base_setparity, |
| ser_base_drain_output, |
| ser_base_async, |
| pipe_windows_read, |
| pipe_windows_write, |
| pipe_avail, |
| pipe_wait_handle, |
| pipe_done_wait_handle |
| }; |
| |
| /* The TCP/UDP socket driver. */ |
| |
| static const struct serial_ops tcp_ops = |
| { |
| "tcp", |
| net_windows_open, |
| net_windows_close, |
| NULL, |
| ser_base_readchar, |
| ser_base_write, |
| ser_base_flush_output, |
| ser_base_flush_input, |
| ser_tcp_send_break, |
| ser_base_raw, |
| ser_base_get_tty_state, |
| ser_base_copy_tty_state, |
| ser_base_set_tty_state, |
| ser_base_print_tty_state, |
| ser_base_setbaudrate, |
| ser_base_setstopbits, |
| ser_base_setparity, |
| ser_base_drain_output, |
| ser_base_async, |
| net_read_prim, |
| net_write_prim, |
| NULL, |
| net_windows_wait_handle, |
| net_windows_done_wait_handle |
| }; |
| |
| void _initialize_ser_windows (); |
| void |
| _initialize_ser_windows () |
| { |
| WSADATA wsa_data; |
| |
| HMODULE hm = NULL; |
| |
| /* First find out if kernel32 exports CancelIo function. */ |
| hm = LoadLibrary ("kernel32.dll"); |
| if (hm) |
| { |
| CancelIo = (CancelIo_ftype *) GetProcAddress (hm, "CancelIo"); |
| FreeLibrary (hm); |
| } |
| else |
| CancelIo = NULL; |
| |
| serial_add_interface (&hardwire_ops); |
| serial_add_interface (&tty_ops); |
| serial_add_interface (&pipe_ops); |
| |
| /* If WinSock works, register the TCP/UDP socket driver. */ |
| |
| if (WSAStartup (MAKEWORD (1, 0), &wsa_data) != 0) |
| /* WinSock is unavailable. */ |
| return; |
| |
| serial_add_interface (&tcp_ops); |
| } |