xref: /openbmc/qemu/ui/console-vc.c (revision 55522f72149fbf95ee3b057f1419da0cad46d0dd)
1  /*
2   * SPDX-License-Identifier: MIT
3   * QEMU VC
4   */
5  #include "qemu/osdep.h"
6  
7  #include "chardev/char.h"
8  #include "qapi/error.h"
9  #include "qemu/fifo8.h"
10  #include "qemu/option.h"
11  #include "ui/console.h"
12  
13  #include "trace.h"
14  #include "console-priv.h"
15  
16  #define DEFAULT_BACKSCROLL 512
17  #define CONSOLE_CURSOR_PERIOD 500
18  
19  typedef struct TextAttributes {
20      uint8_t fgcol:4;
21      uint8_t bgcol:4;
22      uint8_t bold:1;
23      uint8_t uline:1;
24      uint8_t blink:1;
25      uint8_t invers:1;
26      uint8_t unvisible:1;
27  } TextAttributes;
28  
29  #define TEXT_ATTRIBUTES_DEFAULT ((TextAttributes) { \
30      .fgcol = QEMU_COLOR_WHITE,                      \
31      .bgcol = QEMU_COLOR_BLACK                       \
32  })
33  
34  typedef struct TextCell {
35      uint8_t ch;
36      TextAttributes t_attrib;
37  } TextCell;
38  
39  #define MAX_ESC_PARAMS 3
40  
41  enum TTYState {
42      TTY_STATE_NORM,
43      TTY_STATE_ESC,
44      TTY_STATE_CSI,
45  };
46  
47  typedef struct QemuTextConsole {
48      QemuConsole parent;
49  
50      int width;
51      int height;
52      int total_height;
53      int backscroll_height;
54      int x, y;
55      int y_displayed;
56      int y_base;
57      TextCell *cells;
58      int text_x[2], text_y[2], cursor_invalidate;
59      int echo;
60  
61      int update_x0;
62      int update_y0;
63      int update_x1;
64      int update_y1;
65  
66      Chardev *chr;
67      /* fifo for key pressed */
68      Fifo8 out_fifo;
69  } QemuTextConsole;
70  
71  typedef QemuConsoleClass QemuTextConsoleClass;
72  
73  OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE)
74  
75  typedef struct QemuFixedTextConsole {
76      QemuTextConsole parent;
77  } QemuFixedTextConsole;
78  
79  typedef QemuTextConsoleClass QemuFixedTextConsoleClass;
80  
81  OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE)
82  
83  struct VCChardev {
84      Chardev parent;
85      QemuTextConsole *console;
86  
87      enum TTYState state;
88      int esc_params[MAX_ESC_PARAMS];
89      int nb_esc_params;
90      TextAttributes t_attrib; /* currently active text attributes */
91      int x_saved, y_saved;
92  };
93  typedef struct VCChardev VCChardev;
94  
95  static const pixman_color_t color_table_rgb[2][8] = {
96      {   /* dark */
97          [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
98          [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa),  /* blue */
99          [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00),  /* green */
100          [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa),  /* cyan */
101          [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00),  /* red */
102          [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa),  /* magenta */
103          [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00),  /* yellow */
104          [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR_GRAY,
105      },
106      {   /* bright */
107          [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
108          [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff),  /* blue */
109          [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00),  /* green */
110          [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff),  /* cyan */
111          [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00),  /* red */
112          [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff),  /* magenta */
113          [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00),  /* yellow */
114          [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff),  /* white */
115      }
116  };
117  
118  static bool cursor_visible_phase;
119  static QEMUTimer *cursor_timer;
120  
121  const char *
qemu_text_console_get_label(QemuTextConsole * c)122  qemu_text_console_get_label(QemuTextConsole *c)
123  {
124      return c->chr ? c->chr->label : NULL;
125  }
126  
qemu_console_fill_rect(QemuConsole * con,int posx,int posy,int width,int height,pixman_color_t color)127  static void qemu_console_fill_rect(QemuConsole *con, int posx, int posy,
128                                     int width, int height, pixman_color_t color)
129  {
130      DisplaySurface *surface = qemu_console_surface(con);
131      pixman_rectangle16_t rect = {
132          .x = posx, .y = posy, .width = width, .height = height
133      };
134  
135      assert(surface);
136      pixman_image_fill_rectangles(PIXMAN_OP_SRC, surface->image,
137                                   &color, 1, &rect);
138  }
139  
140  /* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */
qemu_console_bitblt(QemuConsole * con,int xs,int ys,int xd,int yd,int w,int h)141  static void qemu_console_bitblt(QemuConsole *con,
142                                  int xs, int ys, int xd, int yd, int w, int h)
143  {
144      DisplaySurface *surface = qemu_console_surface(con);
145  
146      assert(surface);
147      pixman_image_composite(PIXMAN_OP_SRC,
148                             surface->image, NULL, surface->image,
149                             xs, ys, 0, 0, xd, yd, w, h);
150  }
151  
vga_putcharxy(QemuConsole * s,int x,int y,int ch,TextAttributes * t_attrib)152  static void vga_putcharxy(QemuConsole *s, int x, int y, int ch,
153                            TextAttributes *t_attrib)
154  {
155      static pixman_image_t *glyphs[256];
156      DisplaySurface *surface = qemu_console_surface(s);
157      pixman_color_t fgcol, bgcol;
158  
159      assert(surface);
160      if (t_attrib->invers) {
161          bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
162          fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
163      } else {
164          fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
165          bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
166      }
167  
168      if (!glyphs[ch]) {
169          glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch);
170      }
171      qemu_pixman_glyph_render(glyphs[ch], surface->image,
172                               &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT);
173  }
174  
invalidate_xy(QemuTextConsole * s,int x,int y)175  static void invalidate_xy(QemuTextConsole *s, int x, int y)
176  {
177      if (!qemu_console_is_visible(QEMU_CONSOLE(s))) {
178          return;
179      }
180      if (s->update_x0 > x * FONT_WIDTH)
181          s->update_x0 = x * FONT_WIDTH;
182      if (s->update_y0 > y * FONT_HEIGHT)
183          s->update_y0 = y * FONT_HEIGHT;
184      if (s->update_x1 < (x + 1) * FONT_WIDTH)
185          s->update_x1 = (x + 1) * FONT_WIDTH;
186      if (s->update_y1 < (y + 1) * FONT_HEIGHT)
187          s->update_y1 = (y + 1) * FONT_HEIGHT;
188  }
189  
console_show_cursor(QemuTextConsole * s,int show)190  static void console_show_cursor(QemuTextConsole *s, int show)
191  {
192      TextCell *c;
193      int y, y1;
194      int x = s->x;
195  
196      s->cursor_invalidate = 1;
197  
198      if (x >= s->width) {
199          x = s->width - 1;
200      }
201      y1 = (s->y_base + s->y) % s->total_height;
202      y = y1 - s->y_displayed;
203      if (y < 0) {
204          y += s->total_height;
205      }
206      if (y < s->height) {
207          c = &s->cells[y1 * s->width + x];
208          if (show && cursor_visible_phase) {
209              TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT;
210              t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */
211              vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &t_attrib);
212          } else {
213              vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch, &(c->t_attrib));
214          }
215          invalidate_xy(s, x, y);
216      }
217  }
218  
console_refresh(QemuTextConsole * s)219  static void console_refresh(QemuTextConsole *s)
220  {
221      DisplaySurface *surface = qemu_console_surface(QEMU_CONSOLE(s));
222      TextCell *c;
223      int x, y, y1;
224  
225      assert(surface);
226      s->text_x[0] = 0;
227      s->text_y[0] = 0;
228      s->text_x[1] = s->width - 1;
229      s->text_y[1] = s->height - 1;
230      s->cursor_invalidate = 1;
231  
232      qemu_console_fill_rect(QEMU_CONSOLE(s), 0, 0, surface_width(surface), surface_height(surface),
233                             color_table_rgb[0][QEMU_COLOR_BLACK]);
234      y1 = s->y_displayed;
235      for (y = 0; y < s->height; y++) {
236          c = s->cells + y1 * s->width;
237          for (x = 0; x < s->width; x++) {
238              vga_putcharxy(QEMU_CONSOLE(s), x, y, c->ch,
239                            &(c->t_attrib));
240              c++;
241          }
242          if (++y1 == s->total_height) {
243              y1 = 0;
244          }
245      }
246      console_show_cursor(s, 1);
247      dpy_gfx_update(QEMU_CONSOLE(s), 0, 0,
248                     surface_width(surface), surface_height(surface));
249  }
250  
console_scroll(QemuTextConsole * s,int ydelta)251  static void console_scroll(QemuTextConsole *s, int ydelta)
252  {
253      int i, y1;
254  
255      if (ydelta > 0) {
256          for(i = 0; i < ydelta; i++) {
257              if (s->y_displayed == s->y_base)
258                  break;
259              if (++s->y_displayed == s->total_height)
260                  s->y_displayed = 0;
261          }
262      } else {
263          ydelta = -ydelta;
264          i = s->backscroll_height;
265          if (i > s->total_height - s->height)
266              i = s->total_height - s->height;
267          y1 = s->y_base - i;
268          if (y1 < 0)
269              y1 += s->total_height;
270          for(i = 0; i < ydelta; i++) {
271              if (s->y_displayed == y1)
272                  break;
273              if (--s->y_displayed < 0)
274                  s->y_displayed = s->total_height - 1;
275          }
276      }
277      console_refresh(s);
278  }
279  
kbd_send_chars(QemuTextConsole * s)280  static void kbd_send_chars(QemuTextConsole *s)
281  {
282      uint32_t len, avail;
283  
284      len = qemu_chr_be_can_write(s->chr);
285      avail = fifo8_num_used(&s->out_fifo);
286      while (len > 0 && avail > 0) {
287          const uint8_t *buf;
288          uint32_t size;
289  
290          buf = fifo8_pop_bufptr(&s->out_fifo, MIN(len, avail), &size);
291          qemu_chr_be_write(s->chr, buf, size);
292          len = qemu_chr_be_can_write(s->chr);
293          avail -= size;
294      }
295  }
296  
297  /* called when an ascii key is pressed */
qemu_text_console_handle_keysym(QemuTextConsole * s,int keysym)298  void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym)
299  {
300      uint8_t buf[16], *q;
301      int c;
302      uint32_t num_free;
303  
304      switch(keysym) {
305      case QEMU_KEY_CTRL_UP:
306          console_scroll(s, -1);
307          break;
308      case QEMU_KEY_CTRL_DOWN:
309          console_scroll(s, 1);
310          break;
311      case QEMU_KEY_CTRL_PAGEUP:
312          console_scroll(s, -10);
313          break;
314      case QEMU_KEY_CTRL_PAGEDOWN:
315          console_scroll(s, 10);
316          break;
317      default:
318          /* convert the QEMU keysym to VT100 key string */
319          q = buf;
320          if (keysym >= 0xe100 && keysym <= 0xe11f) {
321              *q++ = '\033';
322              *q++ = '[';
323              c = keysym - 0xe100;
324              if (c >= 10)
325                  *q++ = '0' + (c / 10);
326              *q++ = '0' + (c % 10);
327              *q++ = '~';
328          } else if (keysym >= 0xe120 && keysym <= 0xe17f) {
329              *q++ = '\033';
330              *q++ = '[';
331              *q++ = keysym & 0xff;
332          } else if (s->echo && (keysym == '\r' || keysym == '\n')) {
333              qemu_chr_write(s->chr, (uint8_t *)"\r", 1, true);
334              *q++ = '\n';
335          } else {
336              *q++ = keysym;
337          }
338          if (s->echo) {
339              qemu_chr_write(s->chr, buf, q - buf, true);
340          }
341          num_free = fifo8_num_free(&s->out_fifo);
342          fifo8_push_all(&s->out_fifo, buf, MIN(num_free, q - buf));
343          kbd_send_chars(s);
344          break;
345      }
346  }
347  
text_console_update(void * opaque,console_ch_t * chardata)348  static void text_console_update(void *opaque, console_ch_t *chardata)
349  {
350      QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
351      int i, j, src;
352  
353      if (s->text_x[0] <= s->text_x[1]) {
354          src = (s->y_base + s->text_y[0]) * s->width;
355          chardata += s->text_y[0] * s->width;
356          for (i = s->text_y[0]; i <= s->text_y[1]; i ++)
357              for (j = 0; j < s->width; j++, src++) {
358                  console_write_ch(chardata ++,
359                                   ATTR2CHTYPE(s->cells[src].ch,
360                                               s->cells[src].t_attrib.fgcol,
361                                               s->cells[src].t_attrib.bgcol,
362                                               s->cells[src].t_attrib.bold));
363              }
364          dpy_text_update(QEMU_CONSOLE(s), s->text_x[0], s->text_y[0],
365                          s->text_x[1] - s->text_x[0], i - s->text_y[0]);
366          s->text_x[0] = s->width;
367          s->text_y[0] = s->height;
368          s->text_x[1] = 0;
369          s->text_y[1] = 0;
370      }
371      if (s->cursor_invalidate) {
372          dpy_text_cursor(QEMU_CONSOLE(s), s->x, s->y);
373          s->cursor_invalidate = 0;
374      }
375  }
376  
text_console_resize(QemuTextConsole * t)377  static void text_console_resize(QemuTextConsole *t)
378  {
379      QemuConsole *s = QEMU_CONSOLE(t);
380      TextCell *cells, *c, *c1;
381      int w1, x, y, last_width, w, h;
382  
383      assert(s->scanout.kind == SCANOUT_SURFACE);
384  
385      w = surface_width(s->surface) / FONT_WIDTH;
386      h = surface_height(s->surface) / FONT_HEIGHT;
387      if (w == t->width && h == t->height) {
388          return;
389      }
390  
391      last_width = t->width;
392      t->width = w;
393      t->height = h;
394  
395      w1 = MIN(t->width, last_width);
396  
397      cells = g_new(TextCell, t->width * t->total_height + 1);
398      for (y = 0; y < t->total_height; y++) {
399          c = &cells[y * t->width];
400          if (w1 > 0) {
401              c1 = &t->cells[y * last_width];
402              for (x = 0; x < w1; x++) {
403                  *c++ = *c1++;
404              }
405          }
406          for (x = w1; x < t->width; x++) {
407              c->ch = ' ';
408              c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
409              c++;
410          }
411      }
412      g_free(t->cells);
413      t->cells = cells;
414  }
415  
vc_put_lf(VCChardev * vc)416  static void vc_put_lf(VCChardev *vc)
417  {
418      QemuTextConsole *s = vc->console;
419      TextCell *c;
420      int x, y1;
421  
422      s->y++;
423      if (s->y >= s->height) {
424          s->y = s->height - 1;
425  
426          if (s->y_displayed == s->y_base) {
427              if (++s->y_displayed == s->total_height)
428                  s->y_displayed = 0;
429          }
430          if (++s->y_base == s->total_height)
431              s->y_base = 0;
432          if (s->backscroll_height < s->total_height)
433              s->backscroll_height++;
434          y1 = (s->y_base + s->height - 1) % s->total_height;
435          c = &s->cells[y1 * s->width];
436          for(x = 0; x < s->width; x++) {
437              c->ch = ' ';
438              c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
439              c++;
440          }
441          if (s->y_displayed == s->y_base) {
442              s->text_x[0] = 0;
443              s->text_y[0] = 0;
444              s->text_x[1] = s->width - 1;
445              s->text_y[1] = s->height - 1;
446  
447              qemu_console_bitblt(QEMU_CONSOLE(s), 0, FONT_HEIGHT, 0, 0,
448                                  s->width * FONT_WIDTH,
449                                  (s->height - 1) * FONT_HEIGHT);
450              qemu_console_fill_rect(QEMU_CONSOLE(s), 0, (s->height - 1) * FONT_HEIGHT,
451                                     s->width * FONT_WIDTH, FONT_HEIGHT,
452                                     color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]);
453              s->update_x0 = 0;
454              s->update_y0 = 0;
455              s->update_x1 = s->width * FONT_WIDTH;
456              s->update_y1 = s->height * FONT_HEIGHT;
457          }
458      }
459  }
460  
461  /* Set console attributes depending on the current escape codes.
462   * NOTE: I know this code is not very efficient (checking every color for it
463   * self) but it is more readable and better maintainable.
464   */
vc_handle_escape(VCChardev * vc)465  static void vc_handle_escape(VCChardev *vc)
466  {
467      int i;
468  
469      for (i = 0; i < vc->nb_esc_params; i++) {
470          switch (vc->esc_params[i]) {
471              case 0: /* reset all console attributes to default */
472                  vc->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
473                  break;
474              case 1:
475                  vc->t_attrib.bold = 1;
476                  break;
477              case 4:
478                  vc->t_attrib.uline = 1;
479                  break;
480              case 5:
481                  vc->t_attrib.blink = 1;
482                  break;
483              case 7:
484                  vc->t_attrib.invers = 1;
485                  break;
486              case 8:
487                  vc->t_attrib.unvisible = 1;
488                  break;
489              case 22:
490                  vc->t_attrib.bold = 0;
491                  break;
492              case 24:
493                  vc->t_attrib.uline = 0;
494                  break;
495              case 25:
496                  vc->t_attrib.blink = 0;
497                  break;
498              case 27:
499                  vc->t_attrib.invers = 0;
500                  break;
501              case 28:
502                  vc->t_attrib.unvisible = 0;
503                  break;
504              /* set foreground color */
505              case 30:
506                  vc->t_attrib.fgcol = QEMU_COLOR_BLACK;
507                  break;
508              case 31:
509                  vc->t_attrib.fgcol = QEMU_COLOR_RED;
510                  break;
511              case 32:
512                  vc->t_attrib.fgcol = QEMU_COLOR_GREEN;
513                  break;
514              case 33:
515                  vc->t_attrib.fgcol = QEMU_COLOR_YELLOW;
516                  break;
517              case 34:
518                  vc->t_attrib.fgcol = QEMU_COLOR_BLUE;
519                  break;
520              case 35:
521                  vc->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
522                  break;
523              case 36:
524                  vc->t_attrib.fgcol = QEMU_COLOR_CYAN;
525                  break;
526              case 37:
527                  vc->t_attrib.fgcol = QEMU_COLOR_WHITE;
528                  break;
529              /* set background color */
530              case 40:
531                  vc->t_attrib.bgcol = QEMU_COLOR_BLACK;
532                  break;
533              case 41:
534                  vc->t_attrib.bgcol = QEMU_COLOR_RED;
535                  break;
536              case 42:
537                  vc->t_attrib.bgcol = QEMU_COLOR_GREEN;
538                  break;
539              case 43:
540                  vc->t_attrib.bgcol = QEMU_COLOR_YELLOW;
541                  break;
542              case 44:
543                  vc->t_attrib.bgcol = QEMU_COLOR_BLUE;
544                  break;
545              case 45:
546                  vc->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
547                  break;
548              case 46:
549                  vc->t_attrib.bgcol = QEMU_COLOR_CYAN;
550                  break;
551              case 47:
552                  vc->t_attrib.bgcol = QEMU_COLOR_WHITE;
553                  break;
554          }
555      }
556  }
557  
vc_update_xy(VCChardev * vc,int x,int y)558  static void vc_update_xy(VCChardev *vc, int x, int y)
559  {
560      QemuTextConsole *s = vc->console;
561      TextCell *c;
562      int y1, y2;
563  
564      s->text_x[0] = MIN(s->text_x[0], x);
565      s->text_x[1] = MAX(s->text_x[1], x);
566      s->text_y[0] = MIN(s->text_y[0], y);
567      s->text_y[1] = MAX(s->text_y[1], y);
568  
569      y1 = (s->y_base + y) % s->total_height;
570      y2 = y1 - s->y_displayed;
571      if (y2 < 0) {
572          y2 += s->total_height;
573      }
574      if (y2 < s->height) {
575          if (x >= s->width) {
576              x = s->width - 1;
577          }
578          c = &s->cells[y1 * s->width + x];
579          vga_putcharxy(QEMU_CONSOLE(s), x, y2, c->ch,
580                        &(c->t_attrib));
581          invalidate_xy(s, x, y2);
582      }
583  }
584  
vc_clear_xy(VCChardev * vc,int x,int y)585  static void vc_clear_xy(VCChardev *vc, int x, int y)
586  {
587      QemuTextConsole *s = vc->console;
588      int y1 = (s->y_base + y) % s->total_height;
589      if (x >= s->width) {
590          x = s->width - 1;
591      }
592      TextCell *c = &s->cells[y1 * s->width + x];
593      c->ch = ' ';
594      c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
595      vc_update_xy(vc, x, y);
596  }
597  
vc_put_one(VCChardev * vc,int ch)598  static void vc_put_one(VCChardev *vc, int ch)
599  {
600      QemuTextConsole *s = vc->console;
601      TextCell *c;
602      int y1;
603      if (s->x >= s->width) {
604          /* line wrap */
605          s->x = 0;
606          vc_put_lf(vc);
607      }
608      y1 = (s->y_base + s->y) % s->total_height;
609      c = &s->cells[y1 * s->width + s->x];
610      c->ch = ch;
611      c->t_attrib = vc->t_attrib;
612      vc_update_xy(vc, s->x, s->y);
613      s->x++;
614  }
615  
vc_respond_str(VCChardev * vc,const char * buf)616  static void vc_respond_str(VCChardev *vc, const char *buf)
617  {
618      while (*buf) {
619          vc_put_one(vc, *buf);
620          buf++;
621      }
622  }
623  
624  /* set cursor, checking bounds */
vc_set_cursor(VCChardev * vc,int x,int y)625  static void vc_set_cursor(VCChardev *vc, int x, int y)
626  {
627      QemuTextConsole *s = vc->console;
628  
629      if (x < 0) {
630          x = 0;
631      }
632      if (y < 0) {
633          y = 0;
634      }
635      if (y >= s->height) {
636          y = s->height - 1;
637      }
638      if (x >= s->width) {
639          x = s->width - 1;
640      }
641  
642      s->x = x;
643      s->y = y;
644  }
645  
vc_putchar(VCChardev * vc,int ch)646  static void vc_putchar(VCChardev *vc, int ch)
647  {
648      QemuTextConsole *s = vc->console;
649      int i;
650      int x, y;
651      g_autofree char *response = NULL;
652  
653      switch(vc->state) {
654      case TTY_STATE_NORM:
655          switch(ch) {
656          case '\r':  /* carriage return */
657              s->x = 0;
658              break;
659          case '\n':  /* newline */
660              vc_put_lf(vc);
661              break;
662          case '\b':  /* backspace */
663              if (s->x > 0)
664                  s->x--;
665              break;
666          case '\t':  /* tabspace */
667              if (s->x + (8 - (s->x % 8)) > s->width) {
668                  s->x = 0;
669                  vc_put_lf(vc);
670              } else {
671                  s->x = s->x + (8 - (s->x % 8));
672              }
673              break;
674          case '\a':  /* alert aka. bell */
675              /* TODO: has to be implemented */
676              break;
677          case 14:
678              /* SI (shift in), character set 0 (ignored) */
679              break;
680          case 15:
681              /* SO (shift out), character set 1 (ignored) */
682              break;
683          case 27:    /* esc (introducing an escape sequence) */
684              vc->state = TTY_STATE_ESC;
685              break;
686          default:
687              vc_put_one(vc, ch);
688              break;
689          }
690          break;
691      case TTY_STATE_ESC: /* check if it is a terminal escape sequence */
692          if (ch == '[') {
693              for(i=0;i<MAX_ESC_PARAMS;i++)
694                  vc->esc_params[i] = 0;
695              vc->nb_esc_params = 0;
696              vc->state = TTY_STATE_CSI;
697          } else {
698              vc->state = TTY_STATE_NORM;
699          }
700          break;
701      case TTY_STATE_CSI: /* handle escape sequence parameters */
702          if (ch >= '0' && ch <= '9') {
703              if (vc->nb_esc_params < MAX_ESC_PARAMS) {
704                  int *param = &vc->esc_params[vc->nb_esc_params];
705                  int digit = (ch - '0');
706  
707                  *param = (*param <= (INT_MAX - digit) / 10) ?
708                           *param * 10 + digit : INT_MAX;
709              }
710          } else {
711              if (vc->nb_esc_params < MAX_ESC_PARAMS)
712                  vc->nb_esc_params++;
713              if (ch == ';' || ch == '?') {
714                  break;
715              }
716              trace_console_putchar_csi(vc->esc_params[0], vc->esc_params[1],
717                                        ch, vc->nb_esc_params);
718              vc->state = TTY_STATE_NORM;
719              switch(ch) {
720              case 'A':
721                  /* move cursor up */
722                  if (vc->esc_params[0] == 0) {
723                      vc->esc_params[0] = 1;
724                  }
725                  vc_set_cursor(vc, s->x, s->y - vc->esc_params[0]);
726                  break;
727              case 'B':
728                  /* move cursor down */
729                  if (vc->esc_params[0] == 0) {
730                      vc->esc_params[0] = 1;
731                  }
732                  vc_set_cursor(vc, s->x, s->y + vc->esc_params[0]);
733                  break;
734              case 'C':
735                  /* move cursor right */
736                  if (vc->esc_params[0] == 0) {
737                      vc->esc_params[0] = 1;
738                  }
739                  vc_set_cursor(vc, s->x + vc->esc_params[0], s->y);
740                  break;
741              case 'D':
742                  /* move cursor left */
743                  if (vc->esc_params[0] == 0) {
744                      vc->esc_params[0] = 1;
745                  }
746                  vc_set_cursor(vc, s->x - vc->esc_params[0], s->y);
747                  break;
748              case 'G':
749                  /* move cursor to column */
750                  vc_set_cursor(vc, vc->esc_params[0] - 1, s->y);
751                  break;
752              case 'f':
753              case 'H':
754                  /* move cursor to row, column */
755                  vc_set_cursor(vc, vc->esc_params[1] - 1, vc->esc_params[0] - 1);
756                  break;
757              case 'J':
758                  switch (vc->esc_params[0]) {
759                  case 0:
760                      /* clear to end of screen */
761                      for (y = s->y; y < s->height; y++) {
762                          for (x = 0; x < s->width; x++) {
763                              if (y == s->y && x < s->x) {
764                                  continue;
765                              }
766                              vc_clear_xy(vc, x, y);
767                          }
768                      }
769                      break;
770                  case 1:
771                      /* clear from beginning of screen */
772                      for (y = 0; y <= s->y; y++) {
773                          for (x = 0; x < s->width; x++) {
774                              if (y == s->y && x > s->x) {
775                                  break;
776                              }
777                              vc_clear_xy(vc, x, y);
778                          }
779                      }
780                      break;
781                  case 2:
782                      /* clear entire screen */
783                      for (y = 0; y <= s->height; y++) {
784                          for (x = 0; x < s->width; x++) {
785                              vc_clear_xy(vc, x, y);
786                          }
787                      }
788                      break;
789                  }
790                  break;
791              case 'K':
792                  switch (vc->esc_params[0]) {
793                  case 0:
794                      /* clear to eol */
795                      for(x = s->x; x < s->width; x++) {
796                          vc_clear_xy(vc, x, s->y);
797                      }
798                      break;
799                  case 1:
800                      /* clear from beginning of line */
801                      for (x = 0; x <= s->x && x < s->width; x++) {
802                          vc_clear_xy(vc, x, s->y);
803                      }
804                      break;
805                  case 2:
806                      /* clear entire line */
807                      for(x = 0; x < s->width; x++) {
808                          vc_clear_xy(vc, x, s->y);
809                      }
810                      break;
811                  }
812                  break;
813              case 'm':
814                  vc_handle_escape(vc);
815                  break;
816              case 'n':
817                  switch (vc->esc_params[0]) {
818                  case 5:
819                      /* report console status (always succeed)*/
820                      vc_respond_str(vc, "\033[0n");
821                      break;
822                  case 6:
823                      /* report cursor position */
824                      response = g_strdup_printf("\033[%d;%dR",
825                             (s->y_base + s->y) % s->total_height + 1,
826                              s->x + 1);
827                      vc_respond_str(vc, response);
828                      break;
829                  }
830                  break;
831              case 's':
832                  /* save cursor position */
833                  vc->x_saved = s->x;
834                  vc->y_saved = s->y;
835                  break;
836              case 'u':
837                  /* restore cursor position */
838                  s->x = vc->x_saved;
839                  s->y = vc->y_saved;
840                  break;
841              default:
842                  trace_console_putchar_unhandled(ch);
843                  break;
844              }
845              break;
846          }
847      }
848  }
849  
850  #define TYPE_CHARDEV_VC "chardev-vc"
DECLARE_INSTANCE_CHECKER(VCChardev,VC_CHARDEV,TYPE_CHARDEV_VC)851  DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV,
852                           TYPE_CHARDEV_VC)
853  
854  static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len)
855  {
856      VCChardev *drv = VC_CHARDEV(chr);
857      QemuTextConsole *s = drv->console;
858      int i;
859  
860      s->update_x0 = s->width * FONT_WIDTH;
861      s->update_y0 = s->height * FONT_HEIGHT;
862      s->update_x1 = 0;
863      s->update_y1 = 0;
864      console_show_cursor(s, 0);
865      for(i = 0; i < len; i++) {
866          vc_putchar(drv, buf[i]);
867      }
868      console_show_cursor(s, 1);
869      if (s->update_x0 < s->update_x1) {
870          dpy_gfx_update(QEMU_CONSOLE(s), s->update_x0, s->update_y0,
871                         s->update_x1 - s->update_x0,
872                         s->update_y1 - s->update_y0);
873      }
874      return len;
875  }
876  
qemu_text_console_update_cursor(void)877  void qemu_text_console_update_cursor(void)
878  {
879      cursor_visible_phase = !cursor_visible_phase;
880  
881      if (qemu_invalidate_text_consoles()) {
882          timer_mod(cursor_timer,
883                    qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2);
884      }
885  }
886  
887  static void
cursor_timer_cb(void * opaque)888  cursor_timer_cb(void *opaque)
889  {
890      qemu_text_console_update_cursor();
891  }
892  
text_console_invalidate(void * opaque)893  static void text_console_invalidate(void *opaque)
894  {
895      QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque);
896  
897      if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) {
898          text_console_resize(QEMU_TEXT_CONSOLE(s));
899      }
900      console_refresh(s);
901  }
902  
903  static void
qemu_text_console_finalize(Object * obj)904  qemu_text_console_finalize(Object *obj)
905  {
906  }
907  
908  static void
qemu_text_console_class_init(ObjectClass * oc,void * data)909  qemu_text_console_class_init(ObjectClass *oc, void *data)
910  {
911      if (!cursor_timer) {
912          cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL);
913      }
914  }
915  
916  static const GraphicHwOps text_console_ops = {
917      .invalidate  = text_console_invalidate,
918      .text_update = text_console_update,
919  };
920  
921  static void
qemu_text_console_init(Object * obj)922  qemu_text_console_init(Object *obj)
923  {
924      QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj);
925  
926      fifo8_create(&c->out_fifo, 16);
927      c->total_height = DEFAULT_BACKSCROLL;
928      QEMU_CONSOLE(c)->hw_ops = &text_console_ops;
929      QEMU_CONSOLE(c)->hw = c;
930  }
931  
932  static void
qemu_fixed_text_console_finalize(Object * obj)933  qemu_fixed_text_console_finalize(Object *obj)
934  {
935  }
936  
937  static void
qemu_fixed_text_console_class_init(ObjectClass * oc,void * data)938  qemu_fixed_text_console_class_init(ObjectClass *oc, void *data)
939  {
940  }
941  
942  static void
qemu_fixed_text_console_init(Object * obj)943  qemu_fixed_text_console_init(Object *obj)
944  {
945  }
946  
vc_chr_accept_input(Chardev * chr)947  static void vc_chr_accept_input(Chardev *chr)
948  {
949      VCChardev *drv = VC_CHARDEV(chr);
950  
951      kbd_send_chars(drv->console);
952  }
953  
vc_chr_set_echo(Chardev * chr,bool echo)954  static void vc_chr_set_echo(Chardev *chr, bool echo)
955  {
956      VCChardev *drv = VC_CHARDEV(chr);
957  
958      drv->console->echo = echo;
959  }
960  
qemu_text_console_update_size(QemuTextConsole * c)961  void qemu_text_console_update_size(QemuTextConsole *c)
962  {
963      dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height);
964  }
965  
vc_chr_open(Chardev * chr,ChardevBackend * backend,bool * be_opened,Error ** errp)966  static void vc_chr_open(Chardev *chr,
967                          ChardevBackend *backend,
968                          bool *be_opened,
969                          Error **errp)
970  {
971      ChardevVC *vc = backend->u.vc.data;
972      VCChardev *drv = VC_CHARDEV(chr);
973      QemuTextConsole *s;
974      unsigned width = 0;
975      unsigned height = 0;
976  
977      if (vc->has_width) {
978          width = vc->width;
979      } else if (vc->has_cols) {
980          width = vc->cols * FONT_WIDTH;
981      }
982  
983      if (vc->has_height) {
984          height = vc->height;
985      } else if (vc->has_rows) {
986          height = vc->rows * FONT_HEIGHT;
987      }
988  
989      trace_console_txt_new(width, height);
990      if (width == 0 || height == 0) {
991          s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE));
992          width = 80 * FONT_WIDTH;
993          height = 24 * FONT_HEIGHT;
994      } else {
995          s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE));
996      }
997  
998      dpy_gfx_replace_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height));
999  
1000      s->chr = chr;
1001      drv->console = s;
1002  
1003      /* set current text attributes to default */
1004      drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
1005      text_console_resize(s);
1006  
1007      if (chr->label) {
1008          char *msg;
1009  
1010          drv->t_attrib.bgcol = QEMU_COLOR_BLUE;
1011          msg = g_strdup_printf("%s console\r\n", chr->label);
1012          qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true);
1013          g_free(msg);
1014          drv->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
1015      }
1016  
1017      *be_opened = true;
1018  }
1019  
vc_chr_parse(QemuOpts * opts,ChardevBackend * backend,Error ** errp)1020  static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp)
1021  {
1022      int val;
1023      ChardevVC *vc;
1024  
1025      backend->type = CHARDEV_BACKEND_KIND_VC;
1026      vc = backend->u.vc.data = g_new0(ChardevVC, 1);
1027      qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc));
1028  
1029      val = qemu_opt_get_number(opts, "width", 0);
1030      if (val != 0) {
1031          vc->has_width = true;
1032          vc->width = val;
1033      }
1034  
1035      val = qemu_opt_get_number(opts, "height", 0);
1036      if (val != 0) {
1037          vc->has_height = true;
1038          vc->height = val;
1039      }
1040  
1041      val = qemu_opt_get_number(opts, "cols", 0);
1042      if (val != 0) {
1043          vc->has_cols = true;
1044          vc->cols = val;
1045      }
1046  
1047      val = qemu_opt_get_number(opts, "rows", 0);
1048      if (val != 0) {
1049          vc->has_rows = true;
1050          vc->rows = val;
1051      }
1052  }
1053  
char_vc_class_init(ObjectClass * oc,void * data)1054  static void char_vc_class_init(ObjectClass *oc, void *data)
1055  {
1056      ChardevClass *cc = CHARDEV_CLASS(oc);
1057  
1058      cc->parse = vc_chr_parse;
1059      cc->open = vc_chr_open;
1060      cc->chr_write = vc_chr_write;
1061      cc->chr_accept_input = vc_chr_accept_input;
1062      cc->chr_set_echo = vc_chr_set_echo;
1063  }
1064  
1065  static const TypeInfo char_vc_type_info = {
1066      .name = TYPE_CHARDEV_VC,
1067      .parent = TYPE_CHARDEV,
1068      .instance_size = sizeof(VCChardev),
1069      .class_init = char_vc_class_init,
1070  };
1071  
qemu_console_early_init(void)1072  void qemu_console_early_init(void)
1073  {
1074      /* set the default vc driver */
1075      if (!object_class_by_name(TYPE_CHARDEV_VC)) {
1076          type_register(&char_vc_type_info);
1077      }
1078  }
1079