xref: /openbmc/qemu/ui/console-vc.c (revision 7d87775f)
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 *
122 qemu_text_console_get_label(QemuTextConsole *c)
123 {
124     return c->chr ? c->chr->label : NULL;
125 }
126 
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) */
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 
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 
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 
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 
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 
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 
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 */
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 
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 
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 
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  */
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 
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 
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 
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 
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 */
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 
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"
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 
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
888 cursor_timer_cb(void *opaque)
889 {
890     qemu_text_console_update_cursor();
891 }
892 
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
904 qemu_text_console_finalize(Object *obj)
905 {
906 }
907 
908 static void
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
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
933 qemu_fixed_text_console_finalize(Object *obj)
934 {
935 }
936 
937 static void
938 qemu_fixed_text_console_class_init(ObjectClass *oc, void *data)
939 {
940 }
941 
942 static void
943 qemu_fixed_text_console_init(Object *obj)
944 {
945 }
946 
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 
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 
961 void qemu_text_console_update_size(QemuTextConsole *c)
962 {
963     dpy_text_resize(QEMU_CONSOLE(c), c->width, c->height);
964 }
965 
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 
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 
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 
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