/* * SPDX-License-Identifier: MIT * QEMU VC */ #include "qemu/osdep.h" #include "chardev/char.h" #include "qapi/error.h" #include "qemu/option.h" #include "qemu/queue.h" #include "qom/compat-properties.h" #include "ui/console.h" #include "ui/vgafont.h" #include "ui/vt100.h" #include "pixman.h" #include "trace.h" #include "console-priv.h" typedef struct QemuTextConsole { QemuConsole parent; QemuVT100 vt; Chardev *chr; } QemuTextConsole; typedef QemuConsoleClass QemuTextConsoleClass; OBJECT_DEFINE_TYPE(QemuTextConsole, qemu_text_console, QEMU_TEXT_CONSOLE, QEMU_CONSOLE) typedef struct QemuFixedTextConsole { QemuTextConsole parent; } QemuFixedTextConsole; typedef QemuTextConsoleClass QemuFixedTextConsoleClass; OBJECT_DEFINE_TYPE(QemuFixedTextConsole, qemu_fixed_text_console, QEMU_FIXED_TEXT_CONSOLE, QEMU_TEXT_CONSOLE) struct VCChardev { Chardev parent; ChardevVCEncoding encoding; QemuTextConsole *console; }; typedef struct VCChardev VCChardev; static char * qemu_text_console_get_label(const QemuConsole *c) { QemuTextConsole *tc = QEMU_TEXT_CONSOLE(c); return tc->chr ? g_strdup(tc->chr->label) : NULL; } static void qemu_text_console_out_flush(QemuTextConsole *s) { uint32_t len, avail; len = qemu_chr_be_can_write(s->chr); avail = fifo8_num_used(&s->vt.out_fifo); while (len > 0 && avail > 0) { const uint8_t *buf; uint32_t size; buf = fifo8_pop_bufptr(&s->vt.out_fifo, MIN(len, avail), &size); qemu_chr_be_write(s->chr, buf, size); len = qemu_chr_be_can_write(s->chr); avail -= size; } } /* called when an ascii key is pressed */ void qemu_text_console_handle_keysym(QemuTextConsole *s, int keysym) { vt100_keysym(&s->vt, keysym); } static void text_console_update(void *opaque, uint32_t *chardata) { QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); int i, j, src; if (s->vt.text_x[0] <= s->vt.text_x[1]) { src = (s->vt.y_base + s->vt.text_y[0]) * s->vt.width; chardata += s->vt.text_y[0] * s->vt.width; for (i = s->vt.text_y[0]; i <= s->vt.text_y[1]; i ++) for (j = 0; j < s->vt.width; j++, src++) { *chardata++ = ATTR2CHTYPE(s->vt.cells[src].ch, s->vt.cells[src].t_attrib.fgcol, s->vt.cells[src].t_attrib.bgcol, s->vt.cells[src].t_attrib.bold); } qemu_console_text_update(QEMU_CONSOLE(s), s->vt.text_x[0], s->vt.text_y[0], s->vt.text_x[1] - s->vt.text_x[0], i - s->vt.text_y[0]); s->vt.text_x[0] = s->vt.width; s->vt.text_y[0] = s->vt.height; s->vt.text_x[1] = 0; s->vt.text_y[1] = 0; } if (s->vt.cursor_invalidate) { qemu_console_text_set_cursor(QEMU_CONSOLE(s), s->vt.x, s->vt.y); s->vt.cursor_invalidate = 0; } } #define TYPE_CHARDEV_VC "chardev-vc" DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV, TYPE_CHARDEV_VC) static int vc_chr_write(Chardev *chr, const uint8_t *buf, int len) { VCChardev *drv = VC_CHARDEV(chr); QemuTextConsole *s = drv->console; return vt100_input(&s->vt, buf, len); } static void text_console_invalidate(void *opaque) { QemuTextConsole *s = QEMU_TEXT_CONSOLE(opaque); if (!QEMU_IS_FIXED_TEXT_CONSOLE(s)) { vt100_set_image(&s->vt, QEMU_CONSOLE(s)->surface->image); } vt100_refresh(&s->vt); } static void qemu_text_console_finalize(Object *obj) { QemuTextConsole *s = QEMU_TEXT_CONSOLE(obj); vt100_fini(&s->vt); } static void qemu_text_console_class_init(ObjectClass *oc, const void *data) { QemuConsoleClass *cc = QEMU_CONSOLE_CLASS(oc); cc->get_label = qemu_text_console_get_label; } static const GraphicHwOps text_console_ops = { .invalidate = text_console_invalidate, .text_update = text_console_update, }; static void qemu_text_console_init(Object *obj) { QemuTextConsole *c = QEMU_TEXT_CONSOLE(obj); QEMU_CONSOLE(c)->hw_ops = &text_console_ops; QEMU_CONSOLE(c)->hw = c; } static void qemu_fixed_text_console_finalize(Object *obj) { } static void qemu_fixed_text_console_class_init(ObjectClass *oc, const void *data) { } static void qemu_fixed_text_console_init(Object *obj) { } static void vc_chr_accept_input(Chardev *chr) { VCChardev *drv = VC_CHARDEV(chr); qemu_text_console_out_flush(drv->console); } static void vc_chr_set_echo(Chardev *chr, bool echo) { VCChardev *drv = VC_CHARDEV(chr); drv->console->vt.echo = echo; } void qemu_text_console_update_size(QemuTextConsole *c) { qemu_console_text_resize(QEMU_CONSOLE(c), c->vt.width, c->vt.height); } static void text_console_image_update(QemuVT100 *vt, int x, int y, int width, int height) { QemuTextConsole *console = container_of(vt, QemuTextConsole, vt); qemu_console_update(QEMU_CONSOLE(console), x, y, width, height); } static void text_console_out_flush(QemuVT100 *vt) { QemuTextConsole *console = container_of(vt, QemuTextConsole, vt); qemu_text_console_out_flush(console); } static bool vc_chr_open(Chardev *chr, ChardevBackend *backend, Error **errp) { ChardevVC *vc = backend->u.vc.data; VCChardev *drv = VC_CHARDEV(chr); QemuTextConsole *s; unsigned width = 0; unsigned height = 0; if (vc->has_width) { width = vc->width; } else if (vc->has_cols) { width = vc->cols * FONT_WIDTH; } if (vc->has_height) { height = vc->height; } else if (vc->has_rows) { height = vc->rows * FONT_HEIGHT; } trace_console_txt_new(width, height); if (width == 0 || height == 0) { s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_TEXT_CONSOLE)); width = 80 * FONT_WIDTH; height = 24 * FONT_HEIGHT; } else { s = QEMU_TEXT_CONSOLE(object_new(TYPE_QEMU_FIXED_TEXT_CONSOLE)); } qemu_console_set_surface(QEMU_CONSOLE(s), qemu_create_displaysurface(width, height)); if (vc->has_encoding) { drv->encoding = vc->encoding; } vt100_init(&s->vt, QEMU_CONSOLE(s)->surface->image, drv->encoding, text_console_image_update, text_console_out_flush); s->chr = chr; drv->console = s; if (chr->label) { char *msg; s->vt.t_attrib.bgcol = QEMU_COLOR_BLUE; msg = g_strdup_printf("%s console\r\n", chr->label); qemu_chr_write(chr, (uint8_t *)msg, strlen(msg), true); g_free(msg); s->vt.t_attrib = TEXT_ATTRIBUTES_DEFAULT; } qemu_chr_be_event(chr, CHR_EVENT_OPENED); qemu_console_notify(QEMU_CONSOLE_ADDED, QEMU_CONSOLE(s)); return true; } static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) { int val; const char *str; ChardevVC *vc; backend->type = CHARDEV_BACKEND_KIND_VC; vc = backend->u.vc.data = g_new0(ChardevVC, 1); qemu_chr_parse_common(opts, qapi_ChardevVC_base(vc)); val = qemu_opt_get_number(opts, "width", 0); if (val != 0) { vc->has_width = true; vc->width = val; } val = qemu_opt_get_number(opts, "height", 0); if (val != 0) { vc->has_height = true; vc->height = val; } val = qemu_opt_get_number(opts, "cols", 0); if (val != 0) { vc->has_cols = true; vc->cols = val; } val = qemu_opt_get_number(opts, "rows", 0); if (val != 0) { vc->has_rows = true; vc->rows = val; } str = qemu_opt_get(opts, "encoding"); if (str) { int cs = qapi_enum_parse(&ChardevVCEncoding_lookup, str, -1, errp); if (cs < 0) { return; } vc->has_encoding = true; vc->encoding = cs; } } CHARDEV_VC_ENCODING_PROPERTY_DEFINE(VC_CHARDEV) static void char_vc_class_init(ObjectClass *oc, const void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); cc->chr_parse = vc_chr_parse; cc->chr_open = vc_chr_open; cc->chr_write = vc_chr_write; cc->chr_accept_input = vc_chr_accept_input; cc->chr_set_echo = vc_chr_set_echo; cc->supports_size_opts = true; cc->supports_encoding_opts = true; chardev_vc_add_encoding_prop(oc, get_encoding, set_encoding); } static void char_vc_init(Object *obj) { VCChardev *vc = VC_CHARDEV(obj); vc->encoding = CHARDEV_VC_ENCODING_UTF8; } static void char_vc_finalize(Object *obj) { VCChardev *vc = VC_CHARDEV(obj); QemuConsole *con = QEMU_CONSOLE(vc->console); if (con) { qemu_console_notify(QEMU_CONSOLE_REMOVED, con); object_unref(con); } } static const TypeInfo char_vc_type_info = { .name = TYPE_CHARDEV_VC, .parent = TYPE_CHARDEV, .instance_size = sizeof(VCChardev), .instance_init = char_vc_init, .instance_post_init = object_apply_compat_props, .instance_finalize = char_vc_finalize, .class_init = char_vc_class_init, }; void qemu_console_early_init(void) { /* set the default vc driver */ if (!object_class_by_name(TYPE_CHARDEV_VC)) { type_register_static(&char_vc_type_info); } }