xref: /openbmc/qemu/chardev/char-win-stdio.c (revision 9d49b1c9edf829e571093088ddff0b73db3110c6)
1  /*
2   * QEMU System Emulator
3   *
4   * Copyright (c) 2003-2008 Fabrice Bellard
5   *
6   * Permission is hereby granted, free of charge, to any person obtaining a copy
7   * of this software and associated documentation files (the "Software"), to deal
8   * in the Software without restriction, including without limitation the rights
9   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10   * copies of the Software, and to permit persons to whom the Software is
11   * furnished to do so, subject to the following conditions:
12   *
13   * The above copyright notice and this permission notice shall be included in
14   * all copies or substantial portions of the Software.
15   *
16   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17   * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
19   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21   * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22   * THE SOFTWARE.
23   */
24  
25  #include "qemu/osdep.h"
26  #include "qapi/error.h"
27  #include "qemu/main-loop.h"
28  #include "qemu/module.h"
29  #include "chardev/char-win.h"
30  #include "chardev/char-win-stdio.h"
31  #include "qom/object.h"
32  
33  struct WinStdioChardev {
34      Chardev parent;
35      HANDLE  hStdIn;
36      DWORD   dwOldMode;
37      HANDLE  hInputReadyEvent;
38      HANDLE  hInputDoneEvent;
39      HANDLE  hInputThread;
40      uint8_t win_stdio_buf;
41  };
42  typedef struct WinStdioChardev WinStdioChardev;
43  
44  DECLARE_INSTANCE_CHECKER(WinStdioChardev, WIN_STDIO_CHARDEV,
45                           TYPE_CHARDEV_WIN_STDIO)
46  
47  static void win_stdio_wait_func(void *opaque)
48  {
49      Chardev *chr = CHARDEV(opaque);
50      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
51      INPUT_RECORD       buf[4];
52      int                ret;
53      DWORD              dwSize;
54      int                i;
55  
56      ret = ReadConsoleInput(stdio->hStdIn, buf, ARRAY_SIZE(buf), &dwSize);
57  
58      if (!ret) {
59          /* Avoid error storm */
60          qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
61          return;
62      }
63  
64      for (i = 0; i < dwSize; i++) {
65          KEY_EVENT_RECORD *kev = &buf[i].Event.KeyEvent;
66  
67          if (buf[i].EventType == KEY_EVENT && kev->bKeyDown) {
68              int j;
69              if (kev->uChar.AsciiChar != 0) {
70                  for (j = 0; j < kev->wRepeatCount; j++) {
71                      if (qemu_chr_be_can_write(chr)) {
72                          uint8_t c = kev->uChar.AsciiChar;
73                          qemu_chr_be_write(chr, &c, 1);
74                      }
75                  }
76              }
77          }
78      }
79  }
80  
81  static DWORD WINAPI win_stdio_thread(LPVOID param)
82  {
83      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(param);
84      int                ret;
85      DWORD              dwSize;
86  
87      while (1) {
88  
89          /* Wait for one byte */
90          ret = ReadFile(stdio->hStdIn, &stdio->win_stdio_buf, 1, &dwSize, NULL);
91  
92          /* Exit in case of error, continue if nothing read */
93          if (!ret) {
94              break;
95          }
96          if (!dwSize) {
97              continue;
98          }
99  
100          /* Some terminal emulator returns \r\n for Enter, just pass \n */
101          if (stdio->win_stdio_buf == '\r') {
102              continue;
103          }
104  
105          /* Signal the main thread and wait until the byte was eaten */
106          if (!SetEvent(stdio->hInputReadyEvent)) {
107              break;
108          }
109          if (WaitForSingleObject(stdio->hInputDoneEvent, INFINITE)
110              != WAIT_OBJECT_0) {
111              break;
112          }
113      }
114  
115      qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
116      return 0;
117  }
118  
119  static void win_stdio_thread_wait_func(void *opaque)
120  {
121      Chardev *chr = CHARDEV(opaque);
122      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
123  
124      if (qemu_chr_be_can_write(chr)) {
125          qemu_chr_be_write(chr, &stdio->win_stdio_buf, 1);
126      }
127  
128      SetEvent(stdio->hInputDoneEvent);
129  }
130  
131  static void qemu_chr_set_echo_win_stdio(Chardev *chr, bool echo)
132  {
133      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
134      DWORD              dwMode = 0;
135  
136      GetConsoleMode(stdio->hStdIn, &dwMode);
137  
138      if (echo) {
139          SetConsoleMode(stdio->hStdIn, dwMode | ENABLE_ECHO_INPUT);
140      } else {
141          SetConsoleMode(stdio->hStdIn, dwMode & ~ENABLE_ECHO_INPUT);
142      }
143  }
144  
145  static void qemu_chr_open_stdio(Chardev *chr,
146                                  ChardevBackend *backend,
147                                  bool *be_opened,
148                                  Error **errp)
149  {
150      ChardevStdio *opts = backend->u.stdio.data;
151      bool stdio_allow_signal = !opts->has_signal || opts->signal;
152      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
153      DWORD              dwMode;
154      int                is_console = 0;
155  
156      stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
157      if (stdio->hStdIn == INVALID_HANDLE_VALUE) {
158          error_setg(errp, "cannot open stdio: invalid handle");
159          return;
160      }
161  
162      is_console = GetConsoleMode(stdio->hStdIn, &dwMode) != 0;
163      stdio->dwOldMode = dwMode;
164  
165      if (is_console) {
166          if (qemu_add_wait_object(stdio->hStdIn,
167                                   win_stdio_wait_func, chr)) {
168              error_setg(errp, "qemu_add_wait_object: failed");
169              goto err1;
170          }
171      } else {
172          DWORD   dwId;
173  
174          stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
175          stdio->hInputDoneEvent  = CreateEvent(NULL, FALSE, FALSE, NULL);
176          if (stdio->hInputReadyEvent == INVALID_HANDLE_VALUE
177              || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) {
178              error_setg(errp, "cannot create event");
179              goto err2;
180          }
181          if (qemu_add_wait_object(stdio->hInputReadyEvent,
182                                   win_stdio_thread_wait_func, chr)) {
183              error_setg(errp, "qemu_add_wait_object: failed");
184              goto err2;
185          }
186          stdio->hInputThread     = CreateThread(NULL, 0, win_stdio_thread,
187                                                 chr, 0, &dwId);
188  
189          if (stdio->hInputThread == INVALID_HANDLE_VALUE) {
190              error_setg(errp, "cannot create stdio thread");
191              goto err3;
192          }
193      }
194  
195      dwMode |= ENABLE_LINE_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT;
196  
197      if (is_console) {
198          /* set the terminal in raw mode */
199          /* ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS */
200          if (stdio_allow_signal) {
201              dwMode |= ENABLE_PROCESSED_INPUT;
202          } else {
203              dwMode &= ~ENABLE_PROCESSED_INPUT;
204          }
205      }
206  
207      SetConsoleMode(stdio->hStdIn, dwMode);
208  
209      qemu_chr_set_echo_win_stdio(chr, false);
210  
211      return;
212  
213  err3:
214      qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
215  err2:
216      CloseHandle(stdio->hInputReadyEvent);
217      CloseHandle(stdio->hInputDoneEvent);
218  err1:
219      qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
220  }
221  
222  static void char_win_stdio_finalize(Object *obj)
223  {
224      WinStdioChardev *stdio = WIN_STDIO_CHARDEV(obj);
225  
226      if (stdio->hStdIn != INVALID_HANDLE_VALUE) {
227          SetConsoleMode(stdio->hStdIn, stdio->dwOldMode);
228      }
229      if (stdio->hInputReadyEvent != INVALID_HANDLE_VALUE) {
230          CloseHandle(stdio->hInputReadyEvent);
231      }
232      if (stdio->hInputDoneEvent != INVALID_HANDLE_VALUE) {
233          CloseHandle(stdio->hInputDoneEvent);
234      }
235      if (stdio->hInputThread != INVALID_HANDLE_VALUE) {
236          TerminateThread(stdio->hInputThread, 0);
237      }
238  }
239  
240  static int win_stdio_write(Chardev *chr, const uint8_t *buf, int len)
241  {
242      HANDLE  hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
243      DWORD   dwSize;
244      int     len1;
245  
246      len1 = len;
247  
248      while (len1 > 0) {
249          if (!WriteFile(hStdOut, buf, len1, &dwSize, NULL)) {
250              break;
251          }
252          buf  += dwSize;
253          len1 -= dwSize;
254      }
255  
256      return len - len1;
257  }
258  
259  static void char_win_stdio_class_init(ObjectClass *oc, void *data)
260  {
261      ChardevClass *cc = CHARDEV_CLASS(oc);
262  
263      cc->open = qemu_chr_open_stdio;
264      cc->chr_write = win_stdio_write;
265      cc->chr_set_echo = qemu_chr_set_echo_win_stdio;
266  }
267  
268  static const TypeInfo char_win_stdio_type_info = {
269      .name = TYPE_CHARDEV_WIN_STDIO,
270      .parent = TYPE_CHARDEV,
271      .instance_size = sizeof(WinStdioChardev),
272      .instance_finalize = char_win_stdio_finalize,
273      .class_init = char_win_stdio_class_init,
274      .abstract = true,
275  };
276  
277  static void register_types(void)
278  {
279      type_register_static(&char_win_stdio_type_info);
280  }
281  
282  type_init(register_types);
283