aboutsummaryrefslogtreecommitdiffstats
path: root/chardev
diff options
context:
space:
mode:
authorTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
committerTimos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>2023-10-10 11:40:56 +0000
commite02cda008591317b1625707ff8e115a4841aa889 (patch)
treeaee302e3cf8b59ec2d32ec481be3d1afddfc8968 /chardev
parentcc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff)
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback design to work with QEMU and rust-vmm vhost-user backend without require any changes. Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com> Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'chardev')
-rw-r--r--chardev/baum.c690
-rw-r--r--chardev/char-console.c55
-rw-r--r--chardev/char-fd.c265
-rw-r--r--chardev/char-fe.c386
-rw-r--r--chardev/char-file.c141
-rw-r--r--chardev/char-io.c147
-rw-r--r--chardev/char-mux.c431
-rw-r--r--chardev/char-null.c56
-rw-r--r--chardev/char-parallel.c319
-rw-r--r--chardev/char-pipe.c196
-rw-r--r--chardev/char-pty.c255
-rw-r--r--chardev/char-ringbuf.c255
-rw-r--r--chardev/char-serial.c327
-rw-r--r--chardev/char-socket.c1611
-rw-r--r--chardev/char-stdio.c166
-rw-r--r--chardev/char-udp.c246
-rw-r--r--chardev/char-win-stdio.c271
-rw-r--r--chardev/char-win.c244
-rw-r--r--chardev/char.c1230
-rw-r--r--chardev/chardev-internal.h67
-rw-r--r--chardev/meson.build44
-rw-r--r--chardev/msmouse.c193
-rw-r--r--chardev/spice.c412
-rw-r--r--chardev/testdev.c132
-rw-r--r--chardev/trace-events19
-rw-r--r--chardev/trace.h1
-rw-r--r--chardev/wctablet.c366
27 files changed, 8525 insertions, 0 deletions
diff --git a/chardev/baum.c b/chardev/baum.c
new file mode 100644
index 000000000..79d618e35
--- /dev/null
+++ b/chardev/baum.c
@@ -0,0 +1,690 @@
+/*
+ * QEMU Baum Braille Device
+ *
+ * Copyright (c) 2008, 2010-2011, 2016-2017 Samuel Thibault
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "chardev/char.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "hw/usb.h"
+#include "ui/console.h"
+#include <brlapi.h>
+#include <brlapi_constants.h>
+#include <brlapi_keycodes.h>
+#include "qom/object.h"
+
+#if 0
+#define DPRINTF(fmt, ...) \
+ printf(fmt, ## __VA_ARGS__)
+#else
+#define DPRINTF(fmt, ...)
+#endif
+
+#define ESC 0x1B
+
+#define BAUM_REQ_DisplayData 0x01
+#define BAUM_REQ_GetVersionNumber 0x05
+#define BAUM_REQ_GetKeys 0x08
+#define BAUM_REQ_SetMode 0x12
+#define BAUM_REQ_SetProtocol 0x15
+#define BAUM_REQ_GetDeviceIdentity 0x84
+#define BAUM_REQ_GetSerialNumber 0x8A
+
+#define BAUM_RSP_CellCount 0x01
+#define BAUM_RSP_VersionNumber 0x05
+#define BAUM_RSP_ModeSetting 0x11
+#define BAUM_RSP_CommunicationChannel 0x16
+#define BAUM_RSP_PowerdownSignal 0x17
+#define BAUM_RSP_HorizontalSensors 0x20
+#define BAUM_RSP_VerticalSensors 0x21
+#define BAUM_RSP_RoutingKeys 0x22
+#define BAUM_RSP_Switches 0x23
+#define BAUM_RSP_TopKeys 0x24
+#define BAUM_RSP_HorizontalSensor 0x25
+#define BAUM_RSP_VerticalSensor 0x26
+#define BAUM_RSP_RoutingKey 0x27
+#define BAUM_RSP_FrontKeys6 0x28
+#define BAUM_RSP_BackKeys6 0x29
+#define BAUM_RSP_CommandKeys 0x2B
+#define BAUM_RSP_FrontKeys10 0x2C
+#define BAUM_RSP_BackKeys10 0x2D
+#define BAUM_RSP_EntryKeys 0x33
+#define BAUM_RSP_JoyStick 0x34
+#define BAUM_RSP_ErrorCode 0x40
+#define BAUM_RSP_InfoBlock 0x42
+#define BAUM_RSP_DeviceIdentity 0x84
+#define BAUM_RSP_SerialNumber 0x8A
+#define BAUM_RSP_BluetoothName 0x8C
+
+#define BAUM_TL1 0x01
+#define BAUM_TL2 0x02
+#define BAUM_TL3 0x04
+#define BAUM_TR1 0x08
+#define BAUM_TR2 0x10
+#define BAUM_TR3 0x20
+
+#define BUF_SIZE 256
+
+struct BaumChardev {
+ Chardev parent;
+
+ brlapi_handle_t *brlapi;
+ int brlapi_fd;
+ unsigned int x, y;
+ bool deferred_init;
+
+ uint8_t in_buf[BUF_SIZE];
+ uint8_t in_buf_used;
+ uint8_t out_buf[BUF_SIZE];
+ uint8_t out_buf_used, out_buf_ptr;
+
+ QEMUTimer *cellCount_timer;
+};
+typedef struct BaumChardev BaumChardev;
+
+#define TYPE_CHARDEV_BRAILLE "chardev-braille"
+DECLARE_INSTANCE_CHECKER(BaumChardev, BAUM_CHARDEV,
+ TYPE_CHARDEV_BRAILLE)
+
+/* Let's assume NABCC by default */
+enum way {
+ DOTS2ASCII,
+ ASCII2DOTS
+};
+static const uint8_t nabcc_translation[2][256] = {
+#ifndef BRLAPI_DOTS
+#define BRLAPI_DOTS(d1,d2,d3,d4,d5,d6,d7,d8) \
+ ((d1?BRLAPI_DOT1:0)|\
+ (d2?BRLAPI_DOT2:0)|\
+ (d3?BRLAPI_DOT3:0)|\
+ (d4?BRLAPI_DOT4:0)|\
+ (d5?BRLAPI_DOT5:0)|\
+ (d6?BRLAPI_DOT6:0)|\
+ (d7?BRLAPI_DOT7:0)|\
+ (d8?BRLAPI_DOT8:0))
+#endif
+#define DO(dots, ascii) \
+ [DOTS2ASCII][dots] = ascii, \
+ [ASCII2DOTS][ascii] = dots
+ DO(0, ' '),
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 0, 0), 'a'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 0, 0), 'b'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 0, 0), 'c'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 0, 0), 'd'),
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 0, 0), 'e'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 0, 0), 'f'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 0, 0), 'g'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 0, 0), 'h'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 0, 0), 'i'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 0, 0), 'j'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 0, 0), 'k'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 0, 0), 'l'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 0, 0), 'm'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 0, 0), 'n'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 0, 0), 'o'),
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 0, 0), 'p'),
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 0, 0), 'q'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 0, 0), 'r'),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 0, 0), 's'),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 0, 0), 't'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 0, 0), 'u'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 0, 0), 'v'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 0, 0), 'w'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 0, 0), 'x'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 0, 0), 'y'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 0, 0), 'z'),
+
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 0, 1, 0), 'A'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 0, 1, 0), 'B'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 0, 1, 0), 'C'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 0, 1, 0), 'D'),
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 0, 1, 0), 'E'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 0, 1, 0), 'F'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 0, 1, 0), 'G'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 0, 1, 0), 'H'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 0, 1, 0), 'I'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 0, 1, 0), 'J'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 0, 1, 0), 'K'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 0, 1, 0), 'L'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 0, 1, 0), 'M'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 0, 1, 0), 'N'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 0, 1, 0), 'O'),
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 0, 1, 0), 'P'),
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 0, 1, 0), 'Q'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 0, 1, 0), 'R'),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 0, 1, 0), 'S'),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 0, 1, 0), 'T'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 0, 1, 1, 0), 'U'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 0, 1, 1, 0), 'V'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 1, 1, 1, 0), 'W'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 0, 1, 1, 0), 'X'),
+ DO(BRLAPI_DOTS(1, 0, 1, 1, 1, 1, 1, 0), 'Y'),
+ DO(BRLAPI_DOTS(1, 0, 1, 0, 1, 1, 1, 0), 'Z'),
+
+ DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 1, 0, 0), '0'),
+ DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 0, 0, 0), '1'),
+ DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 0, 0, 0), '2'),
+ DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 0, 0, 0), '3'),
+ DO(BRLAPI_DOTS(0, 1, 0, 0, 1, 1, 0, 0), '4'),
+ DO(BRLAPI_DOTS(0, 1, 0, 0, 0, 1, 0, 0), '5'),
+ DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 0, 0, 0), '6'),
+ DO(BRLAPI_DOTS(0, 1, 1, 0, 1, 1, 0, 0), '7'),
+ DO(BRLAPI_DOTS(0, 1, 1, 0, 0, 1, 0, 0), '8'),
+ DO(BRLAPI_DOTS(0, 0, 1, 0, 1, 0, 0, 0), '9'),
+
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 1, 0, 0), '.'),
+ DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 1, 0, 0), '+'),
+ DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 1, 0, 0), '-'),
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 0, 1, 0, 0), '*'),
+ DO(BRLAPI_DOTS(0, 0, 1, 1, 0, 0, 0, 0), '/'),
+ DO(BRLAPI_DOTS(1, 1, 1, 0, 1, 1, 0, 0), '('),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 1, 1, 0, 0), ')'),
+
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 0, 1, 0, 0), '&'),
+ DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 1, 0, 0), '#'),
+
+ DO(BRLAPI_DOTS(0, 0, 0, 0, 0, 1, 0, 0), ','),
+ DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 1, 0, 0), ';'),
+ DO(BRLAPI_DOTS(1, 0, 0, 0, 1, 1, 0, 0), ':'),
+ DO(BRLAPI_DOTS(0, 1, 1, 1, 0, 1, 0, 0), '!'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 1, 1, 0, 0), '?'),
+ DO(BRLAPI_DOTS(0, 0, 0, 0, 1, 0, 0, 0), '"'),
+ DO(BRLAPI_DOTS(0, 0, 1, 0, 0, 0, 0, 0), '\''),
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 0, 0), '`'),
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 1, 0), '^'),
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 0, 0, 0), '~'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 1, 0), '['),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 1, 0), ']'),
+ DO(BRLAPI_DOTS(0, 1, 0, 1, 0, 1, 0, 0), '{'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 1, 1, 0, 0), '}'),
+ DO(BRLAPI_DOTS(1, 1, 1, 1, 1, 1, 0, 0), '='),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 0, 1, 0, 0), '<'),
+ DO(BRLAPI_DOTS(0, 0, 1, 1, 1, 0, 0, 0), '>'),
+ DO(BRLAPI_DOTS(1, 1, 0, 1, 0, 1, 0, 0), '$'),
+ DO(BRLAPI_DOTS(1, 0, 0, 1, 0, 1, 0, 0), '%'),
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 0, 0, 1, 0), '@'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 0, 0), '|'),
+ DO(BRLAPI_DOTS(1, 1, 0, 0, 1, 1, 1, 0), '\\'),
+ DO(BRLAPI_DOTS(0, 0, 0, 1, 1, 1, 0, 0), '_'),
+};
+
+/* The guest OS has started discussing with us, finish initializing BrlAPI */
+static int baum_deferred_init(BaumChardev *baum)
+{
+ int tty = BRLAPI_TTY_DEFAULT;
+ QemuConsole *con;
+
+ if (baum->deferred_init) {
+ return 1;
+ }
+
+ if (brlapi__getDisplaySize(baum->brlapi, &baum->x, &baum->y) == -1) {
+ brlapi_perror("baum: brlapi__getDisplaySize");
+ return 0;
+ }
+ if (baum->y > 1) {
+ baum->y = 1;
+ }
+ if (baum->x > 84) {
+ baum->x = 84;
+ }
+
+ con = qemu_console_lookup_by_index(0);
+ if (con && qemu_console_is_graphic(con)) {
+ tty = qemu_console_get_window_id(con);
+ if (tty == -1)
+ tty = BRLAPI_TTY_DEFAULT;
+ }
+
+ if (brlapi__enterTtyMode(baum->brlapi, tty, NULL) == -1) {
+ brlapi_perror("baum: brlapi__enterTtyMode");
+ return 0;
+ }
+ baum->deferred_init = 1;
+ return 1;
+}
+
+/* The serial port can receive more of our data */
+static void baum_chr_accept_input(struct Chardev *chr)
+{
+ BaumChardev *baum = BAUM_CHARDEV(chr);
+ int room, first;
+
+ if (!baum->out_buf_used)
+ return;
+ room = qemu_chr_be_can_write(chr);
+ if (!room)
+ return;
+ if (room > baum->out_buf_used)
+ room = baum->out_buf_used;
+
+ first = BUF_SIZE - baum->out_buf_ptr;
+ if (room > first) {
+ qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, first);
+ baum->out_buf_ptr = 0;
+ baum->out_buf_used -= first;
+ room -= first;
+ }
+ qemu_chr_be_write(chr, baum->out_buf + baum->out_buf_ptr, room);
+ baum->out_buf_ptr += room;
+ baum->out_buf_used -= room;
+}
+
+/* We want to send a packet */
+static void baum_write_packet(BaumChardev *baum, const uint8_t *buf, int len)
+{
+ Chardev *chr = CHARDEV(baum);
+ uint8_t io_buf[1 + 2 * len], *cur = io_buf;
+ int room;
+ *cur++ = ESC;
+ while (len--)
+ if ((*cur++ = *buf++) == ESC)
+ *cur++ = ESC;
+ room = qemu_chr_be_can_write(chr);
+ len = cur - io_buf;
+ if (len <= room) {
+ /* Fits */
+ qemu_chr_be_write(chr, io_buf, len);
+ } else {
+ int first;
+ uint8_t out;
+ /* Can't fit all, send what can be, and store the rest. */
+ qemu_chr_be_write(chr, io_buf, room);
+ len -= room;
+ cur = io_buf + room;
+ if (len > BUF_SIZE - baum->out_buf_used) {
+ /* Can't even store it, drop the previous data... */
+ assert(len <= BUF_SIZE);
+ baum->out_buf_used = 0;
+ baum->out_buf_ptr = 0;
+ }
+ out = baum->out_buf_ptr;
+ baum->out_buf_used += len;
+ first = BUF_SIZE - baum->out_buf_ptr;
+ if (len > first) {
+ memcpy(baum->out_buf + out, cur, first);
+ out = 0;
+ len -= first;
+ cur += first;
+ }
+ memcpy(baum->out_buf + out, cur, len);
+ }
+}
+
+/* Called when the other end seems to have a wrong idea of our display size */
+static void baum_cellCount_timer_cb(void *opaque)
+{
+ BaumChardev *baum = BAUM_CHARDEV(opaque);
+ uint8_t cell_count[] = { BAUM_RSP_CellCount, baum->x * baum->y };
+ DPRINTF("Timeout waiting for DisplayData, sending cell count\n");
+ baum_write_packet(baum, cell_count, sizeof(cell_count));
+}
+
+/* Try to interpret a whole incoming packet */
+static int baum_eat_packet(BaumChardev *baum, const uint8_t *buf, int len)
+{
+ const uint8_t *cur = buf;
+ uint8_t req = 0;
+
+ if (!len--)
+ return 0;
+ if (*cur++ != ESC) {
+ while (*cur != ESC) {
+ if (!len--)
+ return 0;
+ cur++;
+ }
+ DPRINTF("Dropped %td bytes!\n", cur - buf);
+ }
+
+#define EAT(c) do {\
+ if (!len--) \
+ return 0; \
+ if ((c = *cur++) == ESC) { \
+ if (!len--) \
+ return 0; \
+ if (*cur++ != ESC) { \
+ DPRINTF("Broken packet %#2x, tossing\n", req); \
+ if (timer_pending(baum->cellCount_timer)) { \
+ timer_del(baum->cellCount_timer); \
+ baum_cellCount_timer_cb(baum); \
+ } \
+ return (cur - 2 - buf); \
+ } \
+ } \
+} while (0)
+
+ EAT(req);
+ switch (req) {
+ case BAUM_REQ_DisplayData:
+ {
+ uint8_t cells[baum->x * baum->y], c;
+ uint8_t text[baum->x * baum->y];
+ uint8_t zero[baum->x * baum->y];
+ int cursor = BRLAPI_CURSOR_OFF;
+ int i;
+
+ /* Allow 100ms to complete the DisplayData packet */
+ timer_mod(baum->cellCount_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
+ NANOSECONDS_PER_SECOND / 10);
+ for (i = 0; i < baum->x * baum->y ; i++) {
+ EAT(c);
+ cells[i] = c;
+ if ((c & (BRLAPI_DOT7|BRLAPI_DOT8))
+ == (BRLAPI_DOT7|BRLAPI_DOT8)) {
+ cursor = i + 1;
+ c &= ~(BRLAPI_DOT7|BRLAPI_DOT8);
+ }
+ c = nabcc_translation[DOTS2ASCII][c];
+ if (!c) {
+ c = '?';
+ }
+ text[i] = c;
+ }
+ timer_del(baum->cellCount_timer);
+
+ memset(zero, 0, sizeof(zero));
+
+ brlapi_writeArguments_t wa = {
+ .displayNumber = BRLAPI_DISPLAY_DEFAULT,
+ .regionBegin = 1,
+ .regionSize = baum->x * baum->y,
+ .text = (char *)text,
+ .textSize = baum->x * baum->y,
+ .andMask = zero,
+ .orMask = cells,
+ .cursor = cursor,
+ .charset = (char *)"ISO-8859-1",
+ };
+
+ if (brlapi__write(baum->brlapi, &wa) == -1)
+ brlapi_perror("baum brlapi_write");
+ break;
+ }
+ case BAUM_REQ_SetMode:
+ {
+ uint8_t mode, setting;
+ DPRINTF("SetMode\n");
+ EAT(mode);
+ EAT(setting);
+ /* ignore */
+ break;
+ }
+ case BAUM_REQ_SetProtocol:
+ {
+ uint8_t protocol;
+ DPRINTF("SetProtocol\n");
+ EAT(protocol);
+ /* ignore */
+ break;
+ }
+ case BAUM_REQ_GetDeviceIdentity:
+ {
+ uint8_t identity[17] = { BAUM_RSP_DeviceIdentity,
+ 'B','a','u','m',' ','V','a','r','i','o' };
+ DPRINTF("GetDeviceIdentity\n");
+ identity[11] = '0' + baum->x / 10;
+ identity[12] = '0' + baum->x % 10;
+ baum_write_packet(baum, identity, sizeof(identity));
+ break;
+ }
+ case BAUM_REQ_GetVersionNumber:
+ {
+ uint8_t version[] = { BAUM_RSP_VersionNumber, 1 }; /* ? */
+ DPRINTF("GetVersionNumber\n");
+ baum_write_packet(baum, version, sizeof(version));
+ break;
+ }
+ case BAUM_REQ_GetSerialNumber:
+ {
+ uint8_t serial[] = { BAUM_RSP_SerialNumber,
+ '0','0','0','0','0','0','0','0' };
+ DPRINTF("GetSerialNumber\n");
+ baum_write_packet(baum, serial, sizeof(serial));
+ break;
+ }
+ case BAUM_REQ_GetKeys:
+ {
+ DPRINTF("Get%0#2x\n", req);
+ /* ignore */
+ break;
+ }
+ default:
+ DPRINTF("unrecognized request %0#2x\n", req);
+ do
+ if (!len--)
+ return 0;
+ while (*cur++ != ESC);
+ cur--;
+ break;
+ }
+ return cur - buf;
+}
+
+/* The other end is writing some data. Store it and try to interpret */
+static int baum_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ BaumChardev *baum = BAUM_CHARDEV(chr);
+ int tocopy, cur, eaten, orig_len = len;
+
+ if (!len)
+ return 0;
+ if (!baum->brlapi)
+ return len;
+ if (!baum_deferred_init(baum))
+ return len;
+
+ while (len) {
+ /* Complete our buffer as much as possible */
+ tocopy = len;
+ if (tocopy > BUF_SIZE - baum->in_buf_used)
+ tocopy = BUF_SIZE - baum->in_buf_used;
+
+ memcpy(baum->in_buf + baum->in_buf_used, buf, tocopy);
+ baum->in_buf_used += tocopy;
+ buf += tocopy;
+ len -= tocopy;
+
+ /* Interpret it as much as possible */
+ cur = 0;
+ while (cur < baum->in_buf_used &&
+ (eaten = baum_eat_packet(baum, baum->in_buf + cur, baum->in_buf_used - cur)))
+ cur += eaten;
+
+ /* Shift the remainder */
+ if (cur) {
+ memmove(baum->in_buf, baum->in_buf + cur, baum->in_buf_used - cur);
+ baum->in_buf_used -= cur;
+ }
+
+ /* And continue if any data left */
+ }
+ return orig_len;
+}
+
+/* Send the key code to the other end */
+static void baum_send_key(BaumChardev *baum, uint8_t type, uint8_t value)
+{
+ uint8_t packet[] = { type, value };
+ DPRINTF("writing key %x %x\n", type, value);
+ baum_write_packet(baum, packet, sizeof(packet));
+}
+
+static void baum_send_key2(BaumChardev *baum, uint8_t type, uint8_t value,
+ uint8_t value2)
+{
+ uint8_t packet[] = { type, value, value2 };
+ DPRINTF("writing key %x %x\n", type, value);
+ baum_write_packet(baum, packet, sizeof(packet));
+}
+
+/* We got some data on the BrlAPI socket */
+static void baum_chr_read(void *opaque)
+{
+ BaumChardev *baum = BAUM_CHARDEV(opaque);
+ brlapi_keyCode_t code;
+ int ret;
+ if (!baum->brlapi)
+ return;
+ if (!baum_deferred_init(baum))
+ return;
+ while ((ret = brlapi__readKey(baum->brlapi, 0, &code)) == 1) {
+ DPRINTF("got key %"BRLAPI_PRIxKEYCODE"\n", code);
+ /* Emulate */
+ switch (code & BRLAPI_KEY_TYPE_MASK) {
+ case BRLAPI_KEY_TYPE_CMD:
+ switch (code & BRLAPI_KEY_CMD_BLK_MASK) {
+ case BRLAPI_KEY_CMD_ROUTE:
+ baum_send_key(baum, BAUM_RSP_RoutingKey, (code & BRLAPI_KEY_CMD_ARG_MASK)+1);
+ baum_send_key(baum, BAUM_RSP_RoutingKey, 0);
+ break;
+ case 0:
+ switch (code & BRLAPI_KEY_CMD_ARG_MASK) {
+ case BRLAPI_KEY_CMD_FWINLT:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_FWINRT:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR2);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_LNUP:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR1);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_LNDN:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TR3);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_TOP:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TR1);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_BOT:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL3|BAUM_TR3);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_TOP_LEFT:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_BOT_LEFT:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR3);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_HOME:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL2|BAUM_TR1|BAUM_TR3);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ case BRLAPI_KEY_CMD_PREFMENU:
+ baum_send_key(baum, BAUM_RSP_TopKeys, BAUM_TL1|BAUM_TL3|BAUM_TR1);
+ baum_send_key(baum, BAUM_RSP_TopKeys, 0);
+ break;
+ }
+ }
+ break;
+ case BRLAPI_KEY_TYPE_SYM:
+ {
+ brlapi_keyCode_t keysym = code & BRLAPI_KEY_CODE_MASK;
+ if (keysym < 0x100) {
+ uint8_t dots = nabcc_translation[ASCII2DOTS][keysym];
+ if (dots) {
+ baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, dots);
+ baum_send_key2(baum, BAUM_RSP_EntryKeys, 0, 0);
+ }
+ }
+ break;
+ }
+ }
+ }
+ if (ret == -1 && (brlapi_errno != BRLAPI_ERROR_LIBCERR || errno != EINTR)) {
+ brlapi_perror("baum: brlapi_readKey");
+ brlapi__closeConnection(baum->brlapi);
+ g_free(baum->brlapi);
+ baum->brlapi = NULL;
+ }
+}
+
+static void char_braille_finalize(Object *obj)
+{
+ BaumChardev *baum = BAUM_CHARDEV(obj);
+
+ timer_free(baum->cellCount_timer);
+ if (baum->brlapi) {
+ brlapi__closeConnection(baum->brlapi);
+ g_free(baum->brlapi);
+ }
+}
+
+static void baum_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ BaumChardev *baum = BAUM_CHARDEV(chr);
+ brlapi_handle_t *handle;
+
+ handle = g_malloc0(brlapi_getHandleSize());
+ baum->brlapi = handle;
+
+ baum->brlapi_fd = brlapi__openConnection(handle, NULL, NULL);
+ if (baum->brlapi_fd == -1) {
+ error_setg(errp, "brlapi__openConnection: %s",
+ brlapi_strerror(brlapi_error_location()));
+ g_free(handle);
+ baum->brlapi = NULL;
+ return;
+ }
+ baum->deferred_init = 0;
+
+ baum->cellCount_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, baum_cellCount_timer_cb, baum);
+
+ qemu_set_fd_handler(baum->brlapi_fd, baum_chr_read, NULL, baum);
+}
+
+static void char_braille_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = baum_chr_open;
+ cc->chr_write = baum_chr_write;
+ cc->chr_accept_input = baum_chr_accept_input;
+}
+
+static const TypeInfo char_braille_type_info = {
+ .name = TYPE_CHARDEV_BRAILLE,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(BaumChardev),
+ .instance_finalize = char_braille_finalize,
+ .class_init = char_braille_class_init,
+};
+module_obj(TYPE_CHARDEV_BRAILLE);
+
+static void register_types(void)
+{
+ type_register_static(&char_braille_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-console.c b/chardev/char-console.c
new file mode 100644
index 000000000..6c4ce5dbc
--- /dev/null
+++ b/chardev/char-console.c
@@ -0,0 +1,55 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char-win.h"
+#include "qemu/module.h"
+
+static void qemu_chr_open_win_con(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ win_chr_set_file(chr, GetStdHandle(STD_OUTPUT_HANDLE), true);
+}
+
+static void char_console_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = qemu_chr_open_win_con;
+}
+
+static const TypeInfo char_console_type_info = {
+ .name = TYPE_CHARDEV_CONSOLE,
+ .parent = TYPE_CHARDEV_WIN,
+ .class_init = char_console_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_console_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-fd.c b/chardev/char-fd.c
new file mode 100644
index 000000000..93c56913b
--- /dev/null
+++ b/chardev/char-fd.c
@@ -0,0 +1,265 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/module.h"
+#include "qemu/sockets.h"
+#include "qapi/error.h"
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+#include "io/channel-file.h"
+
+#include "chardev/char-fd.h"
+#include "chardev/char-io.h"
+
+/* Called with chr_write_lock held. */
+static int fd_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ FDChardev *s = FD_CHARDEV(chr);
+
+ if (!s->ioc_out) {
+ return -1;
+ }
+
+ return io_channel_send(s->ioc_out, buf, len);
+}
+
+static gboolean fd_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ FDChardev *s = FD_CHARDEV(opaque);
+ int len;
+ uint8_t buf[CHR_READ_BUF_LEN];
+ ssize_t ret;
+
+ len = sizeof(buf);
+ if (len > s->max_size) {
+ len = s->max_size;
+ }
+ if (len == 0) {
+ return TRUE;
+ }
+
+ ret = qio_channel_read(
+ chan, (gchar *)buf, len, NULL);
+ if (ret == 0) {
+ remove_fd_in_watch(chr);
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ return FALSE;
+ }
+ if (ret > 0) {
+ qemu_chr_be_write(chr, buf, ret);
+ }
+
+ return TRUE;
+}
+
+static int fd_chr_read_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ FDChardev *s = FD_CHARDEV(opaque);
+
+ s->max_size = qemu_chr_be_can_write(chr);
+ return s->max_size;
+}
+
+typedef struct FDSource {
+ GSource parent;
+
+ GIOCondition cond;
+} FDSource;
+
+static gboolean
+fd_source_prepare(GSource *source,
+ gint *timeout_)
+{
+ FDSource *src = (FDSource *)source;
+
+ return src->cond != 0;
+}
+
+static gboolean
+fd_source_check(GSource *source)
+{
+ FDSource *src = (FDSource *)source;
+
+ return src->cond != 0;
+}
+
+static gboolean
+fd_source_dispatch(GSource *source, GSourceFunc callback,
+ gpointer user_data)
+{
+ FDSource *src = (FDSource *)source;
+ FEWatchFunc func = (FEWatchFunc)callback;
+ gboolean ret = G_SOURCE_CONTINUE;
+
+ if (src->cond) {
+ ret = func(NULL, src->cond, user_data);
+ src->cond = 0;
+ }
+
+ return ret;
+}
+
+static GSourceFuncs fd_source_funcs = {
+ fd_source_prepare,
+ fd_source_check,
+ fd_source_dispatch,
+ NULL, NULL, NULL
+};
+
+static GSource *fd_source_new(FDChardev *chr)
+{
+ return g_source_new(&fd_source_funcs, sizeof(FDSource));
+}
+
+static gboolean child_func(GIOChannel *source,
+ GIOCondition condition,
+ gpointer data)
+{
+ FDSource *parent = data;
+
+ parent->cond |= condition;
+
+ return G_SOURCE_CONTINUE;
+}
+
+static GSource *fd_chr_add_watch(Chardev *chr, GIOCondition cond)
+{
+ FDChardev *s = FD_CHARDEV(chr);
+ g_autoptr(GSource) source = fd_source_new(s);
+
+ if (s->ioc_out) {
+ g_autoptr(GSource) child = qio_channel_create_watch(s->ioc_out, cond & ~G_IO_IN);
+ g_source_set_callback(child, (GSourceFunc)child_func, source, NULL);
+ g_source_add_child_source(source, child);
+ }
+ if (s->ioc_in) {
+ g_autoptr(GSource) child = qio_channel_create_watch(s->ioc_in, cond & ~G_IO_OUT);
+ g_source_set_callback(child, (GSourceFunc)child_func, source, NULL);
+ g_source_add_child_source(source, child);
+ }
+
+ return g_steal_pointer(&source);
+}
+
+static void fd_chr_update_read_handler(Chardev *chr)
+{
+ FDChardev *s = FD_CHARDEV(chr);
+
+ remove_fd_in_watch(chr);
+ if (s->ioc_in) {
+ chr->gsource = io_add_watch_poll(chr, s->ioc_in,
+ fd_chr_read_poll,
+ fd_chr_read, chr,
+ chr->gcontext);
+ }
+}
+
+static void char_fd_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ FDChardev *s = FD_CHARDEV(obj);
+
+ remove_fd_in_watch(chr);
+ if (s->ioc_in) {
+ object_unref(OBJECT(s->ioc_in));
+ }
+ if (s->ioc_out) {
+ object_unref(OBJECT(s->ioc_out));
+ }
+
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+int qmp_chardev_open_file_source(char *src, int flags, Error **errp)
+{
+ int fd = -1;
+
+ TFR(fd = qemu_open_old(src, flags, 0666));
+ if (fd == -1) {
+ error_setg_file_open(errp, errno, src);
+ }
+ return fd;
+}
+
+/* open a character device to a unix fd */
+void qemu_chr_open_fd(Chardev *chr,
+ int fd_in, int fd_out)
+{
+ FDChardev *s = FD_CHARDEV(chr);
+ g_autofree char *name = NULL;
+
+ if (fd_out >= 0) {
+ qemu_set_nonblock(fd_out);
+ }
+
+ if (fd_out == fd_in && fd_in >= 0) {
+ s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in));
+ name = g_strdup_printf("chardev-file-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name);
+ s->ioc_out = QIO_CHANNEL(object_ref(s->ioc_in));
+ return;
+ }
+
+ if (fd_in >= 0) {
+ s->ioc_in = QIO_CHANNEL(qio_channel_file_new_fd(fd_in));
+ name = g_strdup_printf("chardev-file-in-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(s->ioc_in), name);
+ }
+
+ if (fd_out >= 0) {
+ s->ioc_out = QIO_CHANNEL(qio_channel_file_new_fd(fd_out));
+ g_free(name);
+ name = g_strdup_printf("chardev-file-out-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(s->ioc_out), name);
+ }
+}
+
+static void char_fd_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->chr_add_watch = fd_chr_add_watch;
+ cc->chr_write = fd_chr_write;
+ cc->chr_update_read_handler = fd_chr_update_read_handler;
+}
+
+static const TypeInfo char_fd_type_info = {
+ .name = TYPE_CHARDEV_FD,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(FDChardev),
+ .instance_finalize = char_fd_finalize,
+ .class_init = char_fd_class_init,
+ .abstract = true,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_fd_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-fe.c b/chardev/char-fe.c
new file mode 100644
index 000000000..7789f7be9
--- /dev/null
+++ b/chardev/char-fe.c
@@ -0,0 +1,386 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "qapi/qmp/qerror.h"
+#include "sysemu/replay.h"
+
+#include "chardev/char-fe.h"
+#include "chardev/char-io.h"
+#include "chardev-internal.h"
+
+int qemu_chr_fe_write(CharBackend *be, const uint8_t *buf, int len)
+{
+ Chardev *s = be->chr;
+
+ if (!s) {
+ return 0;
+ }
+
+ return qemu_chr_write(s, buf, len, false);
+}
+
+int qemu_chr_fe_write_all(CharBackend *be, const uint8_t *buf, int len)
+{
+ Chardev *s = be->chr;
+
+ if (!s) {
+ return 0;
+ }
+
+ return qemu_chr_write(s, buf, len, true);
+}
+
+int qemu_chr_fe_read_all(CharBackend *be, uint8_t *buf, int len)
+{
+ Chardev *s = be->chr;
+ int offset = 0;
+ int res;
+
+ if (!s || !CHARDEV_GET_CLASS(s)->chr_sync_read) {
+ return 0;
+ }
+
+ if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_PLAY) {
+ return replay_char_read_all_load(buf);
+ }
+
+ while (offset < len) {
+ retry:
+ res = CHARDEV_GET_CLASS(s)->chr_sync_read(s, buf + offset,
+ len - offset);
+ if (res == -1 && errno == EAGAIN) {
+ g_usleep(100);
+ goto retry;
+ }
+
+ if (res == 0) {
+ break;
+ }
+
+ if (res < 0) {
+ if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
+ replay_char_read_all_save_error(res);
+ }
+ return res;
+ }
+
+ offset += res;
+ }
+
+ if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
+ replay_char_read_all_save_buf(buf, offset);
+ }
+ return offset;
+}
+
+int qemu_chr_fe_ioctl(CharBackend *be, int cmd, void *arg)
+{
+ Chardev *s = be->chr;
+ int res;
+
+ if (!s || !CHARDEV_GET_CLASS(s)->chr_ioctl || qemu_chr_replay(s)) {
+ res = -ENOTSUP;
+ } else {
+ res = CHARDEV_GET_CLASS(s)->chr_ioctl(s, cmd, arg);
+ }
+
+ return res;
+}
+
+int qemu_chr_fe_get_msgfd(CharBackend *be)
+{
+ Chardev *s = be->chr;
+ int fd;
+ int res = (qemu_chr_fe_get_msgfds(be, &fd, 1) == 1) ? fd : -1;
+ if (s && qemu_chr_replay(s)) {
+ error_report("Replay: get msgfd is not supported "
+ "for serial devices yet");
+ exit(1);
+ }
+ return res;
+}
+
+int qemu_chr_fe_get_msgfds(CharBackend *be, int *fds, int len)
+{
+ Chardev *s = be->chr;
+
+ if (!s) {
+ return -1;
+ }
+
+ return CHARDEV_GET_CLASS(s)->get_msgfds ?
+ CHARDEV_GET_CLASS(s)->get_msgfds(s, fds, len) : -1;
+}
+
+int qemu_chr_fe_set_msgfds(CharBackend *be, int *fds, int num)
+{
+ Chardev *s = be->chr;
+
+ if (!s) {
+ return -1;
+ }
+
+ return CHARDEV_GET_CLASS(s)->set_msgfds ?
+ CHARDEV_GET_CLASS(s)->set_msgfds(s, fds, num) : -1;
+}
+
+void qemu_chr_fe_accept_input(CharBackend *be)
+{
+ Chardev *s = be->chr;
+
+ if (!s) {
+ return;
+ }
+
+ if (CHARDEV_GET_CLASS(s)->chr_accept_input) {
+ CHARDEV_GET_CLASS(s)->chr_accept_input(s);
+ }
+ qemu_notify_event();
+}
+
+void qemu_chr_fe_printf(CharBackend *be, const char *fmt, ...)
+{
+ char buf[CHR_READ_BUF_LEN];
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof(buf), fmt, ap);
+ /* XXX this blocks entire thread. Rewrite to use
+ * qemu_chr_fe_write and background I/O callbacks */
+ qemu_chr_fe_write_all(be, (uint8_t *)buf, strlen(buf));
+ va_end(ap);
+}
+
+Chardev *qemu_chr_fe_get_driver(CharBackend *be)
+{
+ /* this is unsafe for the users that support chardev hotswap */
+ assert(be->chr_be_change == NULL);
+ return be->chr;
+}
+
+bool qemu_chr_fe_backend_connected(CharBackend *be)
+{
+ return !!be->chr;
+}
+
+bool qemu_chr_fe_backend_open(CharBackend *be)
+{
+ return be->chr && be->chr->be_open;
+}
+
+bool qemu_chr_fe_init(CharBackend *b, Chardev *s, Error **errp)
+{
+ int tag = 0;
+
+ if (s) {
+ if (CHARDEV_IS_MUX(s)) {
+ MuxChardev *d = MUX_CHARDEV(s);
+
+ if (d->mux_cnt >= MAX_MUX) {
+ goto unavailable;
+ }
+
+ d->backends[d->mux_cnt] = b;
+ tag = d->mux_cnt++;
+ } else if (s->be) {
+ goto unavailable;
+ } else {
+ s->be = b;
+ }
+ }
+
+ b->fe_open = false;
+ b->tag = tag;
+ b->chr = s;
+ return true;
+
+unavailable:
+ error_setg(errp, QERR_DEVICE_IN_USE, s->label);
+ return false;
+}
+
+void qemu_chr_fe_deinit(CharBackend *b, bool del)
+{
+ assert(b);
+
+ if (b->chr) {
+ qemu_chr_fe_set_handlers(b, NULL, NULL, NULL, NULL, NULL, NULL, true);
+ if (b->chr->be == b) {
+ b->chr->be = NULL;
+ }
+ if (CHARDEV_IS_MUX(b->chr)) {
+ MuxChardev *d = MUX_CHARDEV(b->chr);
+ d->backends[b->tag] = NULL;
+ }
+ if (del) {
+ Object *obj = OBJECT(b->chr);
+ if (obj->parent) {
+ object_unparent(obj);
+ } else {
+ object_unref(obj);
+ }
+ }
+ b->chr = NULL;
+ }
+}
+
+void qemu_chr_fe_set_handlers_full(CharBackend *b,
+ IOCanReadHandler *fd_can_read,
+ IOReadHandler *fd_read,
+ IOEventHandler *fd_event,
+ BackendChangeHandler *be_change,
+ void *opaque,
+ GMainContext *context,
+ bool set_open,
+ bool sync_state)
+{
+ Chardev *s;
+ int fe_open;
+
+ s = b->chr;
+ if (!s) {
+ return;
+ }
+
+ if (!opaque && !fd_can_read && !fd_read && !fd_event) {
+ fe_open = 0;
+ remove_fd_in_watch(s);
+ } else {
+ fe_open = 1;
+ }
+ b->chr_can_read = fd_can_read;
+ b->chr_read = fd_read;
+ b->chr_event = fd_event;
+ b->chr_be_change = be_change;
+ b->opaque = opaque;
+
+ qemu_chr_be_update_read_handlers(s, context);
+
+ if (set_open) {
+ qemu_chr_fe_set_open(b, fe_open);
+ }
+
+ if (fe_open) {
+ qemu_chr_fe_take_focus(b);
+ /* We're connecting to an already opened device, so let's make sure we
+ also get the open event */
+ if (sync_state && s->be_open) {
+ qemu_chr_be_event(s, CHR_EVENT_OPENED);
+ }
+ }
+}
+
+void qemu_chr_fe_set_handlers(CharBackend *b,
+ IOCanReadHandler *fd_can_read,
+ IOReadHandler *fd_read,
+ IOEventHandler *fd_event,
+ BackendChangeHandler *be_change,
+ void *opaque,
+ GMainContext *context,
+ bool set_open)
+{
+ qemu_chr_fe_set_handlers_full(b, fd_can_read, fd_read, fd_event, be_change,
+ opaque, context, set_open,
+ true);
+}
+
+void qemu_chr_fe_take_focus(CharBackend *b)
+{
+ if (!b->chr) {
+ return;
+ }
+
+ if (CHARDEV_IS_MUX(b->chr)) {
+ mux_set_focus(b->chr, b->tag);
+ }
+}
+
+int qemu_chr_fe_wait_connected(CharBackend *be, Error **errp)
+{
+ if (!be->chr) {
+ error_setg(errp, "missing associated backend");
+ return -1;
+ }
+
+ return qemu_chr_wait_connected(be->chr, errp);
+}
+
+void qemu_chr_fe_set_echo(CharBackend *be, bool echo)
+{
+ Chardev *chr = be->chr;
+
+ if (chr && CHARDEV_GET_CLASS(chr)->chr_set_echo) {
+ CHARDEV_GET_CLASS(chr)->chr_set_echo(chr, echo);
+ }
+}
+
+void qemu_chr_fe_set_open(CharBackend *be, int fe_open)
+{
+ Chardev *chr = be->chr;
+
+ if (!chr) {
+ return;
+ }
+
+ if (be->fe_open == fe_open) {
+ return;
+ }
+ be->fe_open = fe_open;
+ if (CHARDEV_GET_CLASS(chr)->chr_set_fe_open) {
+ CHARDEV_GET_CLASS(chr)->chr_set_fe_open(chr, fe_open);
+ }
+}
+
+guint qemu_chr_fe_add_watch(CharBackend *be, GIOCondition cond,
+ FEWatchFunc func, void *user_data)
+{
+ Chardev *s = be->chr;
+ GSource *src;
+ guint tag;
+
+ if (!s || CHARDEV_GET_CLASS(s)->chr_add_watch == NULL) {
+ return 0;
+ }
+
+ src = CHARDEV_GET_CLASS(s)->chr_add_watch(s, cond);
+ if (!src) {
+ return 0;
+ }
+
+ g_source_set_callback(src, (GSourceFunc)func, user_data, NULL);
+ tag = g_source_attach(src, s->gcontext);
+ g_source_unref(src);
+
+ return tag;
+}
+
+void qemu_chr_fe_disconnect(CharBackend *be)
+{
+ Chardev *chr = be->chr;
+
+ if (chr && CHARDEV_GET_CLASS(chr)->chr_disconnect) {
+ CHARDEV_GET_CLASS(chr)->chr_disconnect(chr);
+ }
+}
diff --git a/chardev/char-file.c b/chardev/char-file.c
new file mode 100644
index 000000000..2fd80707e
--- /dev/null
+++ b/chardev/char-file.c
@@ -0,0 +1,141 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "chardev/char.h"
+
+#ifdef _WIN32
+#include "chardev/char-win.h"
+#else
+#include "chardev/char-fd.h"
+#endif
+
+static void qmp_chardev_open_file(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevFile *file = backend->u.file.data;
+#ifdef _WIN32
+ HANDLE out;
+ DWORD accessmode;
+ DWORD flags;
+
+ if (file->has_in) {
+ error_setg(errp, "input file not supported");
+ return;
+ }
+
+ if (file->has_append && file->append) {
+ /* Append to file if it already exists. */
+ accessmode = FILE_GENERIC_WRITE & ~FILE_WRITE_DATA;
+ flags = OPEN_ALWAYS;
+ } else {
+ /* Truncate file if it already exists. */
+ accessmode = GENERIC_WRITE;
+ flags = CREATE_ALWAYS;
+ }
+
+ out = CreateFile(file->out, accessmode, FILE_SHARE_READ, NULL, flags,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ if (out == INVALID_HANDLE_VALUE) {
+ error_setg(errp, "open %s failed", file->out);
+ return;
+ }
+
+ win_chr_set_file(chr, out, false);
+#else
+ int flags, in = -1, out;
+
+ flags = O_WRONLY | O_CREAT | O_BINARY;
+ if (file->has_append && file->append) {
+ flags |= O_APPEND;
+ } else {
+ flags |= O_TRUNC;
+ }
+
+ out = qmp_chardev_open_file_source(file->out, flags, errp);
+ if (out < 0) {
+ return;
+ }
+
+ if (file->has_in) {
+ flags = O_RDONLY;
+ in = qmp_chardev_open_file_source(file->in, flags, errp);
+ if (in < 0) {
+ qemu_close(out);
+ return;
+ }
+ }
+
+ qemu_chr_open_fd(chr, in, out);
+#endif
+}
+
+static void qemu_chr_parse_file_out(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *path = qemu_opt_get(opts, "path");
+ ChardevFile *file;
+
+ backend->type = CHARDEV_BACKEND_KIND_FILE;
+ if (path == NULL) {
+ error_setg(errp, "chardev: file: no filename given");
+ return;
+ }
+ file = backend->u.file.data = g_new0(ChardevFile, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevFile_base(file));
+ file->out = g_strdup(path);
+
+ file->has_append = true;
+ file->append = qemu_opt_get_bool(opts, "append", false);
+}
+
+static void char_file_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_file_out;
+ cc->open = qmp_chardev_open_file;
+}
+
+static const TypeInfo char_file_type_info = {
+ .name = TYPE_CHARDEV_FILE,
+#ifdef _WIN32
+ .parent = TYPE_CHARDEV_WIN,
+#else
+ .parent = TYPE_CHARDEV_FD,
+#endif
+ .class_init = char_file_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_file_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-io.c b/chardev/char-io.c
new file mode 100644
index 000000000..8ced18416
--- /dev/null
+++ b/chardev/char-io.c
@@ -0,0 +1,147 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "qemu/osdep.h"
+#include "chardev/char-io.h"
+
+typedef struct IOWatchPoll {
+ GSource parent;
+
+ QIOChannel *ioc;
+ GSource *src;
+
+ IOCanReadHandler *fd_can_read;
+ GSourceFunc fd_read;
+ void *opaque;
+} IOWatchPoll;
+
+static IOWatchPoll *io_watch_poll_from_source(GSource *source)
+{
+ return container_of(source, IOWatchPoll, parent);
+}
+
+static gboolean io_watch_poll_prepare(GSource *source,
+ gint *timeout)
+{
+ IOWatchPoll *iwp = io_watch_poll_from_source(source);
+ bool now_active = iwp->fd_can_read(iwp->opaque) > 0;
+ bool was_active = iwp->src != NULL;
+ if (was_active == now_active) {
+ return FALSE;
+ }
+
+ if (now_active) {
+ iwp->src = qio_channel_create_watch(
+ iwp->ioc, G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL);
+ g_source_set_callback(iwp->src, iwp->fd_read, iwp->opaque, NULL);
+ g_source_add_child_source(source, iwp->src);
+ g_source_unref(iwp->src);
+ } else {
+ g_source_remove_child_source(source, iwp->src);
+ iwp->src = NULL;
+ }
+ return FALSE;
+}
+
+static gboolean io_watch_poll_dispatch(GSource *source, GSourceFunc callback,
+ gpointer user_data)
+{
+ return G_SOURCE_CONTINUE;
+}
+
+static GSourceFuncs io_watch_poll_funcs = {
+ .prepare = io_watch_poll_prepare,
+ .dispatch = io_watch_poll_dispatch,
+};
+
+GSource *io_add_watch_poll(Chardev *chr,
+ QIOChannel *ioc,
+ IOCanReadHandler *fd_can_read,
+ QIOChannelFunc fd_read,
+ gpointer user_data,
+ GMainContext *context)
+{
+ IOWatchPoll *iwp;
+ char *name;
+
+ iwp = (IOWatchPoll *) g_source_new(&io_watch_poll_funcs,
+ sizeof(IOWatchPoll));
+ iwp->fd_can_read = fd_can_read;
+ iwp->opaque = user_data;
+ iwp->ioc = ioc;
+ iwp->fd_read = (GSourceFunc) fd_read;
+ iwp->src = NULL;
+
+ name = g_strdup_printf("chardev-iowatch-%s", chr->label);
+ g_source_set_name((GSource *)iwp, name);
+ g_free(name);
+
+ g_source_attach(&iwp->parent, context);
+ g_source_unref(&iwp->parent);
+ return (GSource *)iwp;
+}
+
+void remove_fd_in_watch(Chardev *chr)
+{
+ if (chr->gsource) {
+ g_source_destroy(chr->gsource);
+ chr->gsource = NULL;
+ }
+}
+
+int io_channel_send_full(QIOChannel *ioc,
+ const void *buf, size_t len,
+ int *fds, size_t nfds)
+{
+ size_t offset = 0;
+
+ while (offset < len) {
+ ssize_t ret = 0;
+ struct iovec iov = { .iov_base = (char *)buf + offset,
+ .iov_len = len - offset };
+
+ ret = qio_channel_writev_full(
+ ioc, &iov, 1,
+ fds, nfds, NULL);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ if (offset) {
+ return offset;
+ }
+
+ errno = EAGAIN;
+ return -1;
+ } else if (ret < 0) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ offset += ret;
+ }
+
+ return offset;
+}
+
+int io_channel_send(QIOChannel *ioc, const void *buf, size_t len)
+{
+ return io_channel_send_full(ioc, buf, len, NULL, 0);
+}
diff --git a/chardev/char-mux.c b/chardev/char-mux.c
new file mode 100644
index 000000000..ee2d47b20
--- /dev/null
+++ b/chardev/char-mux.c
@@ -0,0 +1,431 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "chardev/char.h"
+#include "sysemu/block-backend.h"
+#include "qapi/qapi-commands-control.h"
+#include "chardev-internal.h"
+
+/* MUX driver for serial I/O splitting */
+
+/*
+ * Set to false by suspend_mux_open. Open events are delayed until
+ * resume_mux_open. Usually suspend_mux_open is called before
+ * command line processing and resume_mux_open afterwards.
+ */
+static bool muxes_opened = true;
+
+/* Called with chr_write_lock held. */
+static int mux_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+ int ret;
+ if (!d->timestamps) {
+ ret = qemu_chr_fe_write(&d->chr, buf, len);
+ } else {
+ int i;
+
+ ret = 0;
+ for (i = 0; i < len; i++) {
+ if (d->linestart) {
+ char buf1[64];
+ int64_t ti;
+ int secs;
+
+ ti = qemu_clock_get_ms(QEMU_CLOCK_REALTIME);
+ if (d->timestamps_start == -1) {
+ d->timestamps_start = ti;
+ }
+ ti -= d->timestamps_start;
+ secs = ti / 1000;
+ snprintf(buf1, sizeof(buf1),
+ "[%02d:%02d:%02d.%03d] ",
+ secs / 3600,
+ (secs / 60) % 60,
+ secs % 60,
+ (int)(ti % 1000));
+ /* XXX this blocks entire thread. Rewrite to use
+ * qemu_chr_fe_write and background I/O callbacks */
+ qemu_chr_fe_write_all(&d->chr,
+ (uint8_t *)buf1, strlen(buf1));
+ d->linestart = 0;
+ }
+ ret += qemu_chr_fe_write(&d->chr, buf + i, 1);
+ if (buf[i] == '\n') {
+ d->linestart = 1;
+ }
+ }
+ }
+ return ret;
+}
+
+static const char * const mux_help[] = {
+ "% h print this help\n\r",
+ "% x exit emulator\n\r",
+ "% s save disk data back to file (if -snapshot)\n\r",
+ "% t toggle console timestamps\n\r",
+ "% b send break (magic sysrq)\n\r",
+ "% c switch between console and monitor\n\r",
+ "% % sends %\n\r",
+ NULL
+};
+
+int term_escape_char = 0x01; /* ctrl-a is used for escape */
+static void mux_print_help(Chardev *chr)
+{
+ int i, j;
+ char ebuf[15] = "Escape-Char";
+ char cbuf[50] = "\n\r";
+
+ if (term_escape_char > 0 && term_escape_char < 26) {
+ snprintf(cbuf, sizeof(cbuf), "\n\r");
+ snprintf(ebuf, sizeof(ebuf), "C-%c", term_escape_char - 1 + 'a');
+ } else {
+ snprintf(cbuf, sizeof(cbuf),
+ "\n\rEscape-Char set to Ascii: 0x%02x\n\r\n\r",
+ term_escape_char);
+ }
+ /* XXX this blocks entire thread. Rewrite to use
+ * qemu_chr_fe_write and background I/O callbacks */
+ qemu_chr_write_all(chr, (uint8_t *)cbuf, strlen(cbuf));
+ for (i = 0; mux_help[i] != NULL; i++) {
+ for (j = 0; mux_help[i][j] != '\0'; j++) {
+ if (mux_help[i][j] == '%') {
+ qemu_chr_write_all(chr, (uint8_t *)ebuf, strlen(ebuf));
+ } else {
+ qemu_chr_write_all(chr, (uint8_t *)&mux_help[i][j], 1);
+ }
+ }
+ }
+}
+
+static void mux_chr_send_event(MuxChardev *d, int mux_nr, QEMUChrEvent event)
+{
+ CharBackend *be = d->backends[mux_nr];
+
+ if (be && be->chr_event) {
+ be->chr_event(be->opaque, event);
+ }
+}
+
+static void mux_chr_be_event(Chardev *chr, QEMUChrEvent event)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+
+ if (d->focus != -1) {
+ mux_chr_send_event(d, d->focus, event);
+ }
+}
+
+static int mux_proc_byte(Chardev *chr, MuxChardev *d, int ch)
+{
+ if (d->term_got_escape) {
+ d->term_got_escape = 0;
+ if (ch == term_escape_char) {
+ goto send_char;
+ }
+ switch (ch) {
+ case '?':
+ case 'h':
+ mux_print_help(chr);
+ break;
+ case 'x':
+ {
+ const char *term = "QEMU: Terminated\n\r";
+ qemu_chr_write_all(chr, (uint8_t *)term, strlen(term));
+ qmp_quit(NULL);
+ break;
+ }
+ case 's':
+ blk_commit_all();
+ break;
+ case 'b':
+ qemu_chr_be_event(chr, CHR_EVENT_BREAK);
+ break;
+ case 'c':
+ assert(d->mux_cnt > 0); /* handler registered with first fe */
+ /* Switch to the next registered device */
+ mux_set_focus(chr, (d->focus + 1) % d->mux_cnt);
+ break;
+ case 't':
+ d->timestamps = !d->timestamps;
+ d->timestamps_start = -1;
+ d->linestart = 0;
+ break;
+ }
+ } else if (ch == term_escape_char) {
+ d->term_got_escape = 1;
+ } else {
+ send_char:
+ return 1;
+ }
+ return 0;
+}
+
+static void mux_chr_accept_input(Chardev *chr)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+ int m = d->focus;
+ CharBackend *be = d->backends[m];
+
+ while (be && d->prod[m] != d->cons[m] &&
+ be->chr_can_read && be->chr_can_read(be->opaque)) {
+ be->chr_read(be->opaque,
+ &d->buffer[m][d->cons[m]++ & MUX_BUFFER_MASK], 1);
+ }
+}
+
+static int mux_chr_can_read(void *opaque)
+{
+ MuxChardev *d = MUX_CHARDEV(opaque);
+ int m = d->focus;
+ CharBackend *be = d->backends[m];
+
+ if ((d->prod[m] - d->cons[m]) < MUX_BUFFER_SIZE) {
+ return 1;
+ }
+
+ if (be && be->chr_can_read) {
+ return be->chr_can_read(be->opaque);
+ }
+
+ return 0;
+}
+
+static void mux_chr_read(void *opaque, const uint8_t *buf, int size)
+{
+ Chardev *chr = CHARDEV(opaque);
+ MuxChardev *d = MUX_CHARDEV(opaque);
+ int m = d->focus;
+ CharBackend *be = d->backends[m];
+ int i;
+
+ mux_chr_accept_input(opaque);
+
+ for (i = 0; i < size; i++)
+ if (mux_proc_byte(chr, d, buf[i])) {
+ if (d->prod[m] == d->cons[m] &&
+ be && be->chr_can_read &&
+ be->chr_can_read(be->opaque)) {
+ be->chr_read(be->opaque, &buf[i], 1);
+ } else {
+ d->buffer[m][d->prod[m]++ & MUX_BUFFER_MASK] = buf[i];
+ }
+ }
+}
+
+void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+ int i;
+
+ if (!muxes_opened) {
+ return;
+ }
+
+ /* Send the event to all registered listeners */
+ for (i = 0; i < d->mux_cnt; i++) {
+ mux_chr_send_event(d, i, event);
+ }
+}
+
+static void mux_chr_event(void *opaque, QEMUChrEvent event)
+{
+ mux_chr_send_all_event(CHARDEV(opaque), event);
+}
+
+static GSource *mux_chr_add_watch(Chardev *s, GIOCondition cond)
+{
+ MuxChardev *d = MUX_CHARDEV(s);
+ Chardev *chr = qemu_chr_fe_get_driver(&d->chr);
+ ChardevClass *cc = CHARDEV_GET_CLASS(chr);
+
+ if (!cc->chr_add_watch) {
+ return NULL;
+ }
+
+ return cc->chr_add_watch(chr, cond);
+}
+
+static void char_mux_finalize(Object *obj)
+{
+ MuxChardev *d = MUX_CHARDEV(obj);
+ int i;
+
+ for (i = 0; i < d->mux_cnt; i++) {
+ CharBackend *be = d->backends[i];
+ if (be) {
+ be->chr = NULL;
+ }
+ }
+ qemu_chr_fe_deinit(&d->chr, false);
+}
+
+static void mux_chr_update_read_handlers(Chardev *chr)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+
+ /* Fix up the real driver with mux routines */
+ qemu_chr_fe_set_handlers_full(&d->chr,
+ mux_chr_can_read,
+ mux_chr_read,
+ mux_chr_event,
+ NULL,
+ chr,
+ chr->gcontext, true, false);
+}
+
+void mux_set_focus(Chardev *chr, int focus)
+{
+ MuxChardev *d = MUX_CHARDEV(chr);
+
+ assert(focus >= 0);
+ assert(focus < d->mux_cnt);
+
+ if (d->focus != -1) {
+ mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_OUT);
+ }
+
+ d->focus = focus;
+ chr->be = d->backends[focus];
+ mux_chr_send_event(d, d->focus, CHR_EVENT_MUX_IN);
+}
+
+static void qemu_chr_open_mux(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevMux *mux = backend->u.mux.data;
+ Chardev *drv;
+ MuxChardev *d = MUX_CHARDEV(chr);
+
+ drv = qemu_chr_find(mux->chardev);
+ if (drv == NULL) {
+ error_setg(errp, "mux: base chardev %s not found", mux->chardev);
+ return;
+ }
+
+ d->focus = -1;
+ /* only default to opened state if we've realized the initial
+ * set of muxes
+ */
+ *be_opened = muxes_opened;
+ qemu_chr_fe_init(&d->chr, drv, errp);
+}
+
+static void qemu_chr_parse_mux(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *chardev = qemu_opt_get(opts, "chardev");
+ ChardevMux *mux;
+
+ if (chardev == NULL) {
+ error_setg(errp, "chardev: mux: no chardev given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_MUX;
+ mux = backend->u.mux.data = g_new0(ChardevMux, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevMux_base(mux));
+ mux->chardev = g_strdup(chardev);
+}
+
+/**
+ * Called after processing of default and command-line-specified
+ * chardevs to deliver CHR_EVENT_OPENED events to any FEs attached
+ * to a mux chardev. This is done here to ensure that
+ * output/prompts/banners are only displayed for the FE that has
+ * focus when initial command-line processing/machine init is
+ * completed.
+ *
+ * After this point, any new FE attached to any new or existing
+ * mux will receive CHR_EVENT_OPENED notifications for the BE
+ * immediately.
+ */
+static void open_muxes(Chardev *chr)
+{
+ /* send OPENED to all already-attached FEs */
+ mux_chr_send_all_event(chr, CHR_EVENT_OPENED);
+
+ /*
+ * mark mux as OPENED so any new FEs will immediately receive
+ * OPENED event
+ */
+ chr->be_open = 1;
+}
+
+void suspend_mux_open(void)
+{
+ muxes_opened = false;
+}
+
+static int chardev_options_parsed_cb(Object *child, void *opaque)
+{
+ Chardev *chr = (Chardev *)child;
+
+ if (!chr->be_open && CHARDEV_IS_MUX(chr)) {
+ open_muxes(chr);
+ }
+
+ return 0;
+}
+
+void resume_mux_open(void)
+{
+ muxes_opened = true;
+ object_child_foreach(get_chardevs_root(),
+ chardev_options_parsed_cb, NULL);
+}
+
+static void char_mux_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_mux;
+ cc->open = qemu_chr_open_mux;
+ cc->chr_write = mux_chr_write;
+ cc->chr_accept_input = mux_chr_accept_input;
+ cc->chr_add_watch = mux_chr_add_watch;
+ cc->chr_be_event = mux_chr_be_event;
+ cc->chr_update_read_handler = mux_chr_update_read_handlers;
+}
+
+static const TypeInfo char_mux_type_info = {
+ .name = TYPE_CHARDEV_MUX,
+ .parent = TYPE_CHARDEV,
+ .class_init = char_mux_class_init,
+ .instance_size = sizeof(MuxChardev),
+ .instance_finalize = char_mux_finalize,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_mux_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-null.c b/chardev/char-null.c
new file mode 100644
index 000000000..1c6a2900f
--- /dev/null
+++ b/chardev/char-null.c
@@ -0,0 +1,56 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char.h"
+#include "qemu/module.h"
+
+static void null_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ *be_opened = false;
+}
+
+static void char_null_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = null_chr_open;
+}
+
+static const TypeInfo char_null_type_info = {
+ .name = TYPE_CHARDEV_NULL,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(Chardev),
+ .class_init = char_null_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_null_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-parallel.c b/chardev/char-parallel.c
new file mode 100644
index 000000000..05e7efbd6
--- /dev/null
+++ b/chardev/char-parallel.c
@@ -0,0 +1,319 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include <sys/ioctl.h>
+
+#ifdef CONFIG_BSD
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+#include <dev/ppbus/ppi.h>
+#include <dev/ppbus/ppbconf.h>
+#elif defined(__DragonFly__)
+#include <dev/misc/ppi/ppi.h>
+#include <bus/ppbus/ppbconf.h>
+#endif
+#else
+#ifdef __linux__
+#include <linux/ppdev.h>
+#include <linux/parport.h>
+#endif
+#endif
+
+#include "chardev/char-fd.h"
+#include "chardev/char-parallel.h"
+
+#if defined(__linux__)
+
+typedef struct {
+ Chardev parent;
+ int fd;
+ int mode;
+} ParallelChardev;
+
+#define PARALLEL_CHARDEV(obj) \
+ OBJECT_CHECK(ParallelChardev, (obj), TYPE_CHARDEV_PARALLEL)
+
+static int pp_hw_mode(ParallelChardev *s, uint16_t mode)
+{
+ if (s->mode != mode) {
+ int m = mode;
+ if (ioctl(s->fd, PPSETMODE, &m) < 0) {
+ return 0;
+ }
+ s->mode = mode;
+ }
+ return 1;
+}
+
+static int pp_ioctl(Chardev *chr, int cmd, void *arg)
+{
+ ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+ int fd = drv->fd;
+ uint8_t b;
+
+ switch (cmd) {
+ case CHR_IOCTL_PP_READ_DATA:
+ if (ioctl(fd, PPRDATA, &b) < 0) {
+ return -ENOTSUP;
+ }
+ *(uint8_t *)arg = b;
+ break;
+ case CHR_IOCTL_PP_WRITE_DATA:
+ b = *(uint8_t *)arg;
+ if (ioctl(fd, PPWDATA, &b) < 0) {
+ return -ENOTSUP;
+ }
+ break;
+ case CHR_IOCTL_PP_READ_CONTROL:
+ if (ioctl(fd, PPRCONTROL, &b) < 0) {
+ return -ENOTSUP;
+ }
+ /* Linux gives only the lowest bits, and no way to know data
+ direction! For better compatibility set the fixed upper
+ bits. */
+ *(uint8_t *)arg = b | 0xc0;
+ break;
+ case CHR_IOCTL_PP_WRITE_CONTROL:
+ b = *(uint8_t *)arg;
+ if (ioctl(fd, PPWCONTROL, &b) < 0) {
+ return -ENOTSUP;
+ }
+ break;
+ case CHR_IOCTL_PP_READ_STATUS:
+ if (ioctl(fd, PPRSTATUS, &b) < 0) {
+ return -ENOTSUP;
+ }
+ *(uint8_t *)arg = b;
+ break;
+ case CHR_IOCTL_PP_DATA_DIR:
+ if (ioctl(fd, PPDATADIR, (int *)arg) < 0) {
+ return -ENOTSUP;
+ }
+ break;
+ case CHR_IOCTL_PP_EPP_READ_ADDR:
+ if (pp_hw_mode(drv, IEEE1284_MODE_EPP | IEEE1284_ADDR)) {
+ struct ParallelIOArg *parg = arg;
+ int n = read(fd, parg->buffer, parg->count);
+ if (n != parg->count) {
+ return -EIO;
+ }
+ }
+ break;
+ case CHR_IOCTL_PP_EPP_READ:
+ if (pp_hw_mode(drv, IEEE1284_MODE_EPP)) {
+ struct ParallelIOArg *parg = arg;
+ int n = read(fd, parg->buffer, parg->count);
+ if (n != parg->count) {
+ return -EIO;
+ }
+ }
+ break;
+ case CHR_IOCTL_PP_EPP_WRITE_ADDR:
+ if (pp_hw_mode(drv, IEEE1284_MODE_EPP | IEEE1284_ADDR)) {
+ struct ParallelIOArg *parg = arg;
+ int n = write(fd, parg->buffer, parg->count);
+ if (n != parg->count) {
+ return -EIO;
+ }
+ }
+ break;
+ case CHR_IOCTL_PP_EPP_WRITE:
+ if (pp_hw_mode(drv, IEEE1284_MODE_EPP)) {
+ struct ParallelIOArg *parg = arg;
+ int n = write(fd, parg->buffer, parg->count);
+ if (n != parg->count) {
+ return -EIO;
+ }
+ }
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void qemu_chr_open_pp_fd(Chardev *chr,
+ int fd,
+ bool *be_opened,
+ Error **errp)
+{
+ ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+
+ if (ioctl(fd, PPCLAIM) < 0) {
+ error_setg_errno(errp, errno, "not a parallel port");
+ close(fd);
+ return;
+ }
+
+ drv->fd = fd;
+ drv->mode = IEEE1284_MODE_COMPAT;
+}
+#endif /* __linux__ */
+
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || defined(__DragonFly__)
+
+typedef struct {
+ Chardev parent;
+ int fd;
+} ParallelChardev;
+
+#define PARALLEL_CHARDEV(obj) \
+ OBJECT_CHECK(ParallelChardev, (obj), TYPE_CHARDEV_PARALLEL)
+
+static int pp_ioctl(Chardev *chr, int cmd, void *arg)
+{
+ ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+ uint8_t b;
+
+ switch (cmd) {
+ case CHR_IOCTL_PP_READ_DATA:
+ if (ioctl(drv->fd, PPIGDATA, &b) < 0) {
+ return -ENOTSUP;
+ }
+ *(uint8_t *)arg = b;
+ break;
+ case CHR_IOCTL_PP_WRITE_DATA:
+ b = *(uint8_t *)arg;
+ if (ioctl(drv->fd, PPISDATA, &b) < 0) {
+ return -ENOTSUP;
+ }
+ break;
+ case CHR_IOCTL_PP_READ_CONTROL:
+ if (ioctl(drv->fd, PPIGCTRL, &b) < 0) {
+ return -ENOTSUP;
+ }
+ *(uint8_t *)arg = b;
+ break;
+ case CHR_IOCTL_PP_WRITE_CONTROL:
+ b = *(uint8_t *)arg;
+ if (ioctl(drv->fd, PPISCTRL, &b) < 0) {
+ return -ENOTSUP;
+ }
+ break;
+ case CHR_IOCTL_PP_READ_STATUS:
+ if (ioctl(drv->fd, PPIGSTATUS, &b) < 0) {
+ return -ENOTSUP;
+ }
+ *(uint8_t *)arg = b;
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void qemu_chr_open_pp_fd(Chardev *chr,
+ int fd,
+ bool *be_opened,
+ Error **errp)
+{
+ ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+ drv->fd = fd;
+ *be_opened = false;
+}
+#endif
+
+#ifdef HAVE_CHARDEV_PARPORT
+static void qmp_chardev_open_parallel(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevHostdev *parallel = backend->u.parallel.data;
+ int fd;
+
+ fd = qmp_chardev_open_file_source(parallel->device, O_RDWR, errp);
+ if (fd < 0) {
+ return;
+ }
+ qemu_chr_open_pp_fd(chr, fd, be_opened, errp);
+}
+
+static void qemu_chr_parse_parallel(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *device = qemu_opt_get(opts, "path");
+ ChardevHostdev *parallel;
+
+ if (device == NULL) {
+ error_setg(errp, "chardev: parallel: no device path given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_PARALLEL;
+ parallel = backend->u.parallel.data = g_new0(ChardevHostdev, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(parallel));
+ parallel->device = g_strdup(device);
+}
+
+static void char_parallel_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_parallel;
+ cc->open = qmp_chardev_open_parallel;
+#if defined(__linux__)
+ cc->chr_ioctl = pp_ioctl;
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+ defined(__DragonFly__)
+ cc->chr_ioctl = pp_ioctl;
+#endif
+}
+
+static void char_parallel_finalize(Object *obj)
+{
+#if defined(__linux__)
+ Chardev *chr = CHARDEV(obj);
+ ParallelChardev *drv = PARALLEL_CHARDEV(chr);
+ int fd = drv->fd;
+
+ pp_hw_mode(drv, IEEE1284_MODE_COMPAT);
+ ioctl(fd, PPRELEASE);
+ close(fd);
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+#elif defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \
+ defined(__DragonFly__)
+ /* FIXME: close fd? */
+#endif
+}
+
+static const TypeInfo char_parallel_type_info = {
+ .name = TYPE_CHARDEV_PARALLEL,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(ParallelChardev),
+ .instance_finalize = char_parallel_finalize,
+ .class_init = char_parallel_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_parallel_type_info);
+}
+
+type_init(register_types);
+
+#endif
diff --git a/chardev/char-pipe.c b/chardev/char-pipe.c
new file mode 100644
index 000000000..7eca5d9a5
--- /dev/null
+++ b/chardev/char-pipe.c
@@ -0,0 +1,196 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "chardev/char.h"
+
+#ifdef _WIN32
+#include "chardev/char-win.h"
+#else
+#include "chardev/char-fd.h"
+#endif
+
+#ifdef _WIN32
+#define MAXCONNECT 1
+#define NTIMEOUT 5000
+
+static int win_chr_pipe_init(Chardev *chr, const char *filename,
+ Error **errp)
+{
+ WinChardev *s = WIN_CHARDEV(chr);
+ OVERLAPPED ov;
+ int ret;
+ DWORD size;
+ char *openname;
+
+ s->fpipe = TRUE;
+
+ s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!s->hsend) {
+ error_setg(errp, "Failed CreateEvent");
+ goto fail;
+ }
+ s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!s->hrecv) {
+ error_setg(errp, "Failed CreateEvent");
+ goto fail;
+ }
+
+ openname = g_strdup_printf("\\\\.\\pipe\\%s", filename);
+ s->file = CreateNamedPipe(openname,
+ PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
+ PIPE_TYPE_BYTE | PIPE_READMODE_BYTE |
+ PIPE_WAIT,
+ MAXCONNECT, NSENDBUF, NRECVBUF, NTIMEOUT, NULL);
+ g_free(openname);
+ if (s->file == INVALID_HANDLE_VALUE) {
+ error_setg_win32(errp, GetLastError(), "Failed CreateNamedPipe");
+ s->file = NULL;
+ goto fail;
+ }
+
+ ZeroMemory(&ov, sizeof(ov));
+ ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ ret = ConnectNamedPipe(s->file, &ov);
+ if (ret) {
+ error_setg(errp, "Failed ConnectNamedPipe");
+ goto fail;
+ }
+
+ ret = GetOverlappedResult(s->file, &ov, &size, TRUE);
+ if (!ret) {
+ error_setg(errp, "Failed GetOverlappedResult");
+ if (ov.hEvent) {
+ CloseHandle(ov.hEvent);
+ ov.hEvent = NULL;
+ }
+ goto fail;
+ }
+
+ if (ov.hEvent) {
+ CloseHandle(ov.hEvent);
+ ov.hEvent = NULL;
+ }
+ qemu_add_polling_cb(win_chr_pipe_poll, chr);
+ return 0;
+
+ fail:
+ return -1;
+}
+
+static void qemu_chr_open_pipe(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevHostdev *opts = backend->u.pipe.data;
+ const char *filename = opts->device;
+
+ if (win_chr_pipe_init(chr, filename, errp) < 0) {
+ return;
+ }
+}
+
+#else
+
+static void qemu_chr_open_pipe(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevHostdev *opts = backend->u.pipe.data;
+ int fd_in, fd_out;
+ char *filename_in;
+ char *filename_out;
+ const char *filename = opts->device;
+
+ filename_in = g_strdup_printf("%s.in", filename);
+ filename_out = g_strdup_printf("%s.out", filename);
+ TFR(fd_in = qemu_open_old(filename_in, O_RDWR | O_BINARY));
+ TFR(fd_out = qemu_open_old(filename_out, O_RDWR | O_BINARY));
+ g_free(filename_in);
+ g_free(filename_out);
+ if (fd_in < 0 || fd_out < 0) {
+ if (fd_in >= 0) {
+ close(fd_in);
+ }
+ if (fd_out >= 0) {
+ close(fd_out);
+ }
+ TFR(fd_in = fd_out = qemu_open_old(filename, O_RDWR | O_BINARY));
+ if (fd_in < 0) {
+ error_setg_file_open(errp, errno, filename);
+ return;
+ }
+ }
+ qemu_chr_open_fd(chr, fd_in, fd_out);
+}
+
+#endif /* !_WIN32 */
+
+static void qemu_chr_parse_pipe(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *device = qemu_opt_get(opts, "path");
+ ChardevHostdev *dev;
+
+ if (device == NULL) {
+ error_setg(errp, "chardev: pipe: no device path given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_PIPE;
+ dev = backend->u.pipe.data = g_new0(ChardevHostdev, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(dev));
+ dev->device = g_strdup(device);
+}
+
+static void char_pipe_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_pipe;
+ cc->open = qemu_chr_open_pipe;
+}
+
+static const TypeInfo char_pipe_type_info = {
+ .name = TYPE_CHARDEV_PIPE,
+#ifdef _WIN32
+ .parent = TYPE_CHARDEV_WIN,
+#else
+ .parent = TYPE_CHARDEV_FD,
+#endif
+ .class_init = char_pipe_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_pipe_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-pty.c b/chardev/char-pty.c
new file mode 100644
index 000000000..a2d1e7c98
--- /dev/null
+++ b/chardev/char-pty.c
@@ -0,0 +1,255 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "chardev/char.h"
+#include "io/channel-file.h"
+#include "qemu/sockets.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/qemu-print.h"
+
+#include "chardev/char-io.h"
+#include "qom/object.h"
+
+struct PtyChardev {
+ Chardev parent;
+ QIOChannel *ioc;
+ int read_bytes;
+
+ int connected;
+ GSource *timer_src;
+};
+typedef struct PtyChardev PtyChardev;
+
+DECLARE_INSTANCE_CHECKER(PtyChardev, PTY_CHARDEV,
+ TYPE_CHARDEV_PTY)
+
+static void pty_chr_state(Chardev *chr, int connected);
+
+static void pty_chr_timer_cancel(PtyChardev *s)
+{
+ if (s->timer_src) {
+ g_source_destroy(s->timer_src);
+ g_source_unref(s->timer_src);
+ s->timer_src = NULL;
+ }
+}
+
+static gboolean pty_chr_timer(gpointer opaque)
+{
+ struct Chardev *chr = CHARDEV(opaque);
+ PtyChardev *s = PTY_CHARDEV(opaque);
+
+ pty_chr_timer_cancel(s);
+ if (!s->connected) {
+ /* Next poll ... */
+ qemu_chr_be_update_read_handlers(chr, chr->gcontext);
+ }
+ return FALSE;
+}
+
+static void pty_chr_rearm_timer(Chardev *chr, int ms)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+ char *name;
+
+ pty_chr_timer_cancel(s);
+ name = g_strdup_printf("pty-timer-%s", chr->label);
+ s->timer_src = qemu_chr_timeout_add_ms(chr, ms, pty_chr_timer, chr);
+ g_source_set_name(s->timer_src, name);
+ g_free(name);
+}
+
+static void pty_chr_update_read_handler(Chardev *chr)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+ GPollFD pfd;
+ int rc;
+ QIOChannelFile *fioc = QIO_CHANNEL_FILE(s->ioc);
+
+ pfd.fd = fioc->fd;
+ pfd.events = G_IO_OUT;
+ pfd.revents = 0;
+ do {
+ rc = g_poll(&pfd, 1, 0);
+ } while (rc == -1 && errno == EINTR);
+ assert(rc >= 0);
+
+ if (pfd.revents & G_IO_HUP) {
+ pty_chr_state(chr, 0);
+ } else {
+ pty_chr_state(chr, 1);
+ }
+}
+
+static int char_pty_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+
+ if (!s->connected) {
+ return len;
+ }
+ return io_channel_send(s->ioc, buf, len);
+}
+
+static GSource *pty_chr_add_watch(Chardev *chr, GIOCondition cond)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+ if (!s->connected) {
+ return NULL;
+ }
+ return qio_channel_create_watch(s->ioc, cond);
+}
+
+static int pty_chr_read_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ PtyChardev *s = PTY_CHARDEV(opaque);
+
+ s->read_bytes = qemu_chr_be_can_write(chr);
+ return s->read_bytes;
+}
+
+static gboolean pty_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ PtyChardev *s = PTY_CHARDEV(opaque);
+ gsize len;
+ uint8_t buf[CHR_READ_BUF_LEN];
+ ssize_t ret;
+
+ len = sizeof(buf);
+ if (len > s->read_bytes) {
+ len = s->read_bytes;
+ }
+ if (len == 0) {
+ return TRUE;
+ }
+ ret = qio_channel_read(s->ioc, (char *)buf, len, NULL);
+ if (ret <= 0) {
+ pty_chr_state(chr, 0);
+ return FALSE;
+ } else {
+ pty_chr_state(chr, 1);
+ qemu_chr_be_write(chr, buf, ret);
+ }
+ return TRUE;
+}
+
+static void pty_chr_state(Chardev *chr, int connected)
+{
+ PtyChardev *s = PTY_CHARDEV(chr);
+
+ if (!connected) {
+ remove_fd_in_watch(chr);
+ s->connected = 0;
+ /* (re-)connect poll interval for idle guests: once per second.
+ * We check more frequently in case the guests sends data to
+ * the virtual device linked to our pty. */
+ pty_chr_rearm_timer(chr, 1000);
+ } else {
+ pty_chr_timer_cancel(s);
+ if (!s->connected) {
+ s->connected = 1;
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+ }
+ if (!chr->gsource) {
+ chr->gsource = io_add_watch_poll(chr, s->ioc,
+ pty_chr_read_poll,
+ pty_chr_read,
+ chr, chr->gcontext);
+ }
+ }
+}
+
+static void char_pty_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ PtyChardev *s = PTY_CHARDEV(obj);
+
+ pty_chr_state(chr, 0);
+ object_unref(OBJECT(s->ioc));
+ pty_chr_timer_cancel(s);
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+static void char_pty_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ PtyChardev *s;
+ int master_fd, slave_fd;
+ char pty_name[PATH_MAX];
+ char *name;
+
+ master_fd = qemu_openpty_raw(&slave_fd, pty_name);
+ if (master_fd < 0) {
+ error_setg_errno(errp, errno, "Failed to create PTY");
+ return;
+ }
+
+ close(slave_fd);
+ qemu_set_nonblock(master_fd);
+
+ chr->filename = g_strdup_printf("pty:%s", pty_name);
+ qemu_printf("char device redirected to %s (label %s)\n",
+ pty_name, chr->label);
+
+ s = PTY_CHARDEV(chr);
+ s->ioc = QIO_CHANNEL(qio_channel_file_new_fd(master_fd));
+ name = g_strdup_printf("chardev-pty-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(s->ioc), name);
+ g_free(name);
+ s->timer_src = NULL;
+ *be_opened = false;
+}
+
+static void char_pty_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = char_pty_open;
+ cc->chr_write = char_pty_chr_write;
+ cc->chr_update_read_handler = pty_chr_update_read_handler;
+ cc->chr_add_watch = pty_chr_add_watch;
+}
+
+static const TypeInfo char_pty_type_info = {
+ .name = TYPE_CHARDEV_PTY,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(PtyChardev),
+ .instance_finalize = char_pty_finalize,
+ .class_init = char_pty_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_pty_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-ringbuf.c b/chardev/char-ringbuf.c
new file mode 100644
index 000000000..d40d21d3c
--- /dev/null
+++ b/chardev/char-ringbuf.c
@@ -0,0 +1,255 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-char.h"
+#include "qemu/base64.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qom/object.h"
+
+/* Ring buffer chardev */
+
+struct RingBufChardev {
+ Chardev parent;
+ size_t size;
+ size_t prod;
+ size_t cons;
+ uint8_t *cbuf;
+};
+typedef struct RingBufChardev RingBufChardev;
+
+DECLARE_INSTANCE_CHECKER(RingBufChardev, RINGBUF_CHARDEV,
+ TYPE_CHARDEV_RINGBUF)
+
+static size_t ringbuf_count(const Chardev *chr)
+{
+ const RingBufChardev *d = RINGBUF_CHARDEV(chr);
+
+ return d->prod - d->cons;
+}
+
+static int ringbuf_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ RingBufChardev *d = RINGBUF_CHARDEV(chr);
+ int i;
+
+ if (!buf || (len < 0)) {
+ return -1;
+ }
+
+ for (i = 0; i < len; i++) {
+ d->cbuf[d->prod++ & (d->size - 1)] = buf[i];
+ if (d->prod - d->cons > d->size) {
+ d->cons = d->prod - d->size;
+ }
+ }
+
+ return len;
+}
+
+static int ringbuf_chr_read(Chardev *chr, uint8_t *buf, int len)
+{
+ RingBufChardev *d = RINGBUF_CHARDEV(chr);
+ int i;
+
+ qemu_mutex_lock(&chr->chr_write_lock);
+ for (i = 0; i < len && d->cons != d->prod; i++) {
+ buf[i] = d->cbuf[d->cons++ & (d->size - 1)];
+ }
+ qemu_mutex_unlock(&chr->chr_write_lock);
+
+ return i;
+}
+
+static void char_ringbuf_finalize(Object *obj)
+{
+ RingBufChardev *d = RINGBUF_CHARDEV(obj);
+
+ g_free(d->cbuf);
+}
+
+static void qemu_chr_open_ringbuf(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevRingbuf *opts = backend->u.ringbuf.data;
+ RingBufChardev *d = RINGBUF_CHARDEV(chr);
+
+ d->size = opts->has_size ? opts->size : 65536;
+
+ /* The size must be power of 2 */
+ if (d->size & (d->size - 1)) {
+ error_setg(errp, "size of ringbuf chardev must be power of two");
+ return;
+ }
+
+ d->prod = 0;
+ d->cons = 0;
+ d->cbuf = g_malloc0(d->size);
+}
+
+void qmp_ringbuf_write(const char *device, const char *data,
+ bool has_format, enum DataFormat format,
+ Error **errp)
+{
+ Chardev *chr;
+ const uint8_t *write_data;
+ int ret;
+ gsize write_count;
+
+ chr = qemu_chr_find(device);
+ if (!chr) {
+ error_setg(errp, "Device '%s' not found", device);
+ return;
+ }
+
+ if (!CHARDEV_IS_RINGBUF(chr)) {
+ error_setg(errp, "%s is not a ringbuf device", device);
+ return;
+ }
+
+ if (has_format && (format == DATA_FORMAT_BASE64)) {
+ write_data = qbase64_decode(data, -1,
+ &write_count,
+ errp);
+ if (!write_data) {
+ return;
+ }
+ } else {
+ write_data = (uint8_t *)data;
+ write_count = strlen(data);
+ }
+
+ ret = ringbuf_chr_write(chr, write_data, write_count);
+
+ if (write_data != (uint8_t *)data) {
+ g_free((void *)write_data);
+ }
+
+ if (ret < 0) {
+ error_setg(errp, "Failed to write to device %s", device);
+ return;
+ }
+}
+
+char *qmp_ringbuf_read(const char *device, int64_t size,
+ bool has_format, enum DataFormat format,
+ Error **errp)
+{
+ Chardev *chr;
+ uint8_t *read_data;
+ size_t count;
+ char *data;
+
+ chr = qemu_chr_find(device);
+ if (!chr) {
+ error_setg(errp, "Device '%s' not found", device);
+ return NULL;
+ }
+
+ if (!CHARDEV_IS_RINGBUF(chr)) {
+ error_setg(errp, "%s is not a ringbuf device", device);
+ return NULL;
+ }
+
+ if (size <= 0) {
+ error_setg(errp, "size must be greater than zero");
+ return NULL;
+ }
+
+ count = ringbuf_count(chr);
+ size = size > count ? count : size;
+ read_data = g_malloc(size + 1);
+
+ ringbuf_chr_read(chr, read_data, size);
+
+ if (has_format && (format == DATA_FORMAT_BASE64)) {
+ data = g_base64_encode(read_data, size);
+ g_free(read_data);
+ } else {
+ /*
+ * FIXME should read only complete, valid UTF-8 characters up
+ * to @size bytes. Invalid sequences should be replaced by a
+ * suitable replacement character. Except when (and only
+ * when) ring buffer lost characters since last read, initial
+ * continuation characters should be dropped.
+ */
+ read_data[size] = 0;
+ data = (char *)read_data;
+ }
+
+ return data;
+}
+
+static void qemu_chr_parse_ringbuf(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ int val;
+ ChardevRingbuf *ringbuf;
+
+ backend->type = CHARDEV_BACKEND_KIND_RINGBUF;
+ ringbuf = backend->u.ringbuf.data = g_new0(ChardevRingbuf, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevRingbuf_base(ringbuf));
+
+ val = qemu_opt_get_size(opts, "size", 0);
+ if (val != 0) {
+ ringbuf->has_size = true;
+ ringbuf->size = val;
+ }
+}
+
+static void char_ringbuf_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_ringbuf;
+ cc->open = qemu_chr_open_ringbuf;
+ cc->chr_write = ringbuf_chr_write;
+}
+
+static const TypeInfo char_ringbuf_type_info = {
+ .name = TYPE_CHARDEV_RINGBUF,
+ .parent = TYPE_CHARDEV,
+ .class_init = char_ringbuf_class_init,
+ .instance_size = sizeof(RingBufChardev),
+ .instance_finalize = char_ringbuf_finalize,
+};
+
+/* Bug-compatibility: */
+static const TypeInfo char_memory_type_info = {
+ .name = TYPE_CHARDEV_MEMORY,
+ .parent = TYPE_CHARDEV_RINGBUF,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_ringbuf_type_info);
+ type_register_static(&char_memory_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-serial.c b/chardev/char-serial.c
new file mode 100644
index 000000000..7c3d84ae2
--- /dev/null
+++ b/chardev/char-serial.c
@@ -0,0 +1,327 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/sockets.h"
+#include "io/channel-file.h"
+#include "qapi/error.h"
+
+#ifdef _WIN32
+#include "chardev/char-win.h"
+#else
+#include <sys/ioctl.h>
+#include <termios.h>
+#include "chardev/char-fd.h"
+#endif
+
+#include "chardev/char-serial.h"
+
+#ifdef _WIN32
+
+static void qmp_chardev_open_serial(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevHostdev *serial = backend->u.serial.data;
+
+ win_chr_serial_init(chr, serial->device, errp);
+}
+
+#elif defined(__linux__) || defined(__sun__) || defined(__FreeBSD__) \
+ || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) \
+ || defined(__GLIBC__) || defined(__APPLE__)
+
+static void tty_serial_init(int fd, int speed,
+ int parity, int data_bits, int stop_bits)
+{
+ struct termios tty = {0};
+ speed_t spd;
+
+#if 0
+ printf("tty_serial_init: speed=%d parity=%c data=%d stop=%d\n",
+ speed, parity, data_bits, stop_bits);
+#endif
+ tcgetattr(fd, &tty);
+
+#define check_speed(val) \
+ if (speed <= val) { \
+ spd = B##val; \
+ goto done; \
+ }
+
+ speed = speed * 10 / 11;
+ check_speed(50);
+ check_speed(75);
+ check_speed(110);
+ check_speed(134);
+ check_speed(150);
+ check_speed(200);
+ check_speed(300);
+ check_speed(600);
+ check_speed(1200);
+ check_speed(1800);
+ check_speed(2400);
+ check_speed(4800);
+ check_speed(9600);
+ check_speed(19200);
+ check_speed(38400);
+ /* Non-Posix values follow. They may be unsupported on some systems. */
+ check_speed(57600);
+ check_speed(115200);
+#ifdef B230400
+ check_speed(230400);
+#endif
+#ifdef B460800
+ check_speed(460800);
+#endif
+#ifdef B500000
+ check_speed(500000);
+#endif
+#ifdef B576000
+ check_speed(576000);
+#endif
+#ifdef B921600
+ check_speed(921600);
+#endif
+#ifdef B1000000
+ check_speed(1000000);
+#endif
+#ifdef B1152000
+ check_speed(1152000);
+#endif
+#ifdef B1500000
+ check_speed(1500000);
+#endif
+#ifdef B2000000
+ check_speed(2000000);
+#endif
+#ifdef B2500000
+ check_speed(2500000);
+#endif
+#ifdef B3000000
+ check_speed(3000000);
+#endif
+#ifdef B3500000
+ check_speed(3500000);
+#endif
+#ifdef B4000000
+ check_speed(4000000);
+#endif
+ spd = B115200;
+
+#undef check_speed
+ done:
+ cfsetispeed(&tty, spd);
+ cfsetospeed(&tty, spd);
+
+ tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ tty.c_oflag &= ~OPOST;
+ tty.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN | ISIG);
+ tty.c_cflag &= ~(CSIZE | PARENB | PARODD | CRTSCTS | CSTOPB);
+ switch (data_bits) {
+ default:
+ case 8:
+ tty.c_cflag |= CS8;
+ break;
+ case 7:
+ tty.c_cflag |= CS7;
+ break;
+ case 6:
+ tty.c_cflag |= CS6;
+ break;
+ case 5:
+ tty.c_cflag |= CS5;
+ break;
+ }
+ switch (parity) {
+ default:
+ case 'N':
+ break;
+ case 'E':
+ tty.c_cflag |= PARENB;
+ break;
+ case 'O':
+ tty.c_cflag |= PARENB | PARODD;
+ break;
+ }
+ if (stop_bits == 2) {
+ tty.c_cflag |= CSTOPB;
+ }
+
+ tcsetattr(fd, TCSANOW, &tty);
+}
+
+static int tty_serial_ioctl(Chardev *chr, int cmd, void *arg)
+{
+ FDChardev *s = FD_CHARDEV(chr);
+ QIOChannelFile *fioc = QIO_CHANNEL_FILE(s->ioc_in);
+
+ switch (cmd) {
+ case CHR_IOCTL_SERIAL_SET_PARAMS:
+ {
+ QEMUSerialSetParams *ssp = arg;
+ tty_serial_init(fioc->fd,
+ ssp->speed, ssp->parity,
+ ssp->data_bits, ssp->stop_bits);
+ }
+ break;
+ case CHR_IOCTL_SERIAL_SET_BREAK:
+ {
+ int enable = *(int *)arg;
+ if (enable) {
+ tcsendbreak(fioc->fd, 1);
+ }
+ }
+ break;
+ case CHR_IOCTL_SERIAL_GET_TIOCM:
+ {
+ int sarg = 0;
+ int *targ = (int *)arg;
+ ioctl(fioc->fd, TIOCMGET, &sarg);
+ *targ = 0;
+ if (sarg & TIOCM_CTS) {
+ *targ |= CHR_TIOCM_CTS;
+ }
+ if (sarg & TIOCM_CAR) {
+ *targ |= CHR_TIOCM_CAR;
+ }
+ if (sarg & TIOCM_DSR) {
+ *targ |= CHR_TIOCM_DSR;
+ }
+ if (sarg & TIOCM_RI) {
+ *targ |= CHR_TIOCM_RI;
+ }
+ if (sarg & TIOCM_DTR) {
+ *targ |= CHR_TIOCM_DTR;
+ }
+ if (sarg & TIOCM_RTS) {
+ *targ |= CHR_TIOCM_RTS;
+ }
+ }
+ break;
+ case CHR_IOCTL_SERIAL_SET_TIOCM:
+ {
+ int sarg = *(int *)arg;
+ int targ = 0;
+ ioctl(fioc->fd, TIOCMGET, &targ);
+ targ &= ~(CHR_TIOCM_CTS | CHR_TIOCM_CAR | CHR_TIOCM_DSR
+ | CHR_TIOCM_RI | CHR_TIOCM_DTR | CHR_TIOCM_RTS);
+ if (sarg & CHR_TIOCM_CTS) {
+ targ |= TIOCM_CTS;
+ }
+ if (sarg & CHR_TIOCM_CAR) {
+ targ |= TIOCM_CAR;
+ }
+ if (sarg & CHR_TIOCM_DSR) {
+ targ |= TIOCM_DSR;
+ }
+ if (sarg & CHR_TIOCM_RI) {
+ targ |= TIOCM_RI;
+ }
+ if (sarg & CHR_TIOCM_DTR) {
+ targ |= TIOCM_DTR;
+ }
+ if (sarg & CHR_TIOCM_RTS) {
+ targ |= TIOCM_RTS;
+ }
+ ioctl(fioc->fd, TIOCMSET, &targ);
+ }
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void qmp_chardev_open_serial(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevHostdev *serial = backend->u.serial.data;
+ int fd;
+
+ fd = qmp_chardev_open_file_source(serial->device, O_RDWR | O_NONBLOCK,
+ errp);
+ if (fd < 0) {
+ return;
+ }
+ qemu_set_nonblock(fd);
+ tty_serial_init(fd, 115200, 'N', 8, 1);
+
+ qemu_chr_open_fd(chr, fd, fd);
+}
+#endif /* __linux__ || __sun__ */
+
+#ifdef HAVE_CHARDEV_SERIAL
+static void qemu_chr_parse_serial(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *device = qemu_opt_get(opts, "path");
+ ChardevHostdev *serial;
+
+ if (device == NULL) {
+ error_setg(errp, "chardev: serial/tty: no device path given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_SERIAL;
+ serial = backend->u.serial.data = g_new0(ChardevHostdev, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevHostdev_base(serial));
+ serial->device = g_strdup(device);
+}
+
+static void char_serial_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_serial;
+ cc->open = qmp_chardev_open_serial;
+#ifndef _WIN32
+ cc->chr_ioctl = tty_serial_ioctl;
+#endif
+}
+
+
+static const TypeInfo char_serial_type_info = {
+ .name = TYPE_CHARDEV_SERIAL,
+#ifdef _WIN32
+ .parent = TYPE_CHARDEV_WIN,
+#else
+ .parent = TYPE_CHARDEV_FD,
+#endif
+ .class_init = char_serial_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_serial_type_info);
+}
+
+type_init(register_types);
+
+#endif
diff --git a/chardev/char-socket.c b/chardev/char-socket.c
new file mode 100644
index 000000000..836cfa0bc
--- /dev/null
+++ b/chardev/char-socket.c
@@ -0,0 +1,1611 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char.h"
+#include "io/channel-socket.h"
+#include "io/channel-tls.h"
+#include "io/channel-websock.h"
+#include "io/net-listener.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qapi/error.h"
+#include "qapi/clone-visitor.h"
+#include "qapi/qapi-visit-sockets.h"
+#include "qemu/yank.h"
+
+#include "chardev/char-io.h"
+#include "qom/object.h"
+
+/***********************************************************/
+/* TCP Net console */
+
+#define TCP_MAX_FDS 16
+
+typedef struct {
+ char buf[21];
+ size_t buflen;
+} TCPChardevTelnetInit;
+
+typedef enum {
+ TCP_CHARDEV_STATE_DISCONNECTED,
+ TCP_CHARDEV_STATE_CONNECTING,
+ TCP_CHARDEV_STATE_CONNECTED,
+} TCPChardevState;
+
+struct SocketChardev {
+ Chardev parent;
+ QIOChannel *ioc; /* Client I/O channel */
+ QIOChannelSocket *sioc; /* Client master channel */
+ QIONetListener *listener;
+ GSource *hup_source;
+ QCryptoTLSCreds *tls_creds;
+ char *tls_authz;
+ TCPChardevState state;
+ int max_size;
+ int do_telnetopt;
+ int do_nodelay;
+ int *read_msgfds;
+ size_t read_msgfds_num;
+ int *write_msgfds;
+ size_t write_msgfds_num;
+ bool registered_yank;
+
+ SocketAddress *addr;
+ bool is_listen;
+ bool is_telnet;
+ bool is_tn3270;
+ GSource *telnet_source;
+ TCPChardevTelnetInit *telnet_init;
+
+ bool is_websock;
+
+ GSource *reconnect_timer;
+ int64_t reconnect_time;
+ bool connect_err_reported;
+
+ QIOTask *connect_task;
+};
+typedef struct SocketChardev SocketChardev;
+
+DECLARE_INSTANCE_CHECKER(SocketChardev, SOCKET_CHARDEV,
+ TYPE_CHARDEV_SOCKET)
+
+static gboolean socket_reconnect_timeout(gpointer opaque);
+static void tcp_chr_telnet_init(Chardev *chr);
+
+static void tcp_chr_change_state(SocketChardev *s, TCPChardevState state)
+{
+ switch (state) {
+ case TCP_CHARDEV_STATE_DISCONNECTED:
+ break;
+ case TCP_CHARDEV_STATE_CONNECTING:
+ assert(s->state == TCP_CHARDEV_STATE_DISCONNECTED);
+ break;
+ case TCP_CHARDEV_STATE_CONNECTED:
+ assert(s->state == TCP_CHARDEV_STATE_CONNECTING);
+ break;
+ }
+ s->state = state;
+}
+
+static void tcp_chr_reconn_timer_cancel(SocketChardev *s)
+{
+ if (s->reconnect_timer) {
+ g_source_destroy(s->reconnect_timer);
+ g_source_unref(s->reconnect_timer);
+ s->reconnect_timer = NULL;
+ }
+}
+
+static void qemu_chr_socket_restart_timer(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ char *name;
+
+ assert(s->state == TCP_CHARDEV_STATE_DISCONNECTED);
+ assert(!s->reconnect_timer);
+ name = g_strdup_printf("chardev-socket-reconnect-%s", chr->label);
+ s->reconnect_timer = qemu_chr_timeout_add_ms(chr,
+ s->reconnect_time * 1000,
+ socket_reconnect_timeout,
+ chr);
+ g_source_set_name(s->reconnect_timer, name);
+ g_free(name);
+}
+
+static void check_report_connect_error(Chardev *chr,
+ Error *err)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (!s->connect_err_reported) {
+ error_reportf_err(err,
+ "Unable to connect character device %s: ",
+ chr->label);
+ s->connect_err_reported = true;
+ } else {
+ error_free(err);
+ }
+ qemu_chr_socket_restart_timer(chr);
+}
+
+static void tcp_chr_accept(QIONetListener *listener,
+ QIOChannelSocket *cioc,
+ void *opaque);
+
+static int tcp_chr_read_poll(void *opaque);
+static void tcp_chr_disconnect_locked(Chardev *chr);
+
+/* Called with chr_write_lock held. */
+static int tcp_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (s->state == TCP_CHARDEV_STATE_CONNECTED) {
+ int ret = io_channel_send_full(s->ioc, buf, len,
+ s->write_msgfds,
+ s->write_msgfds_num);
+
+ /* free the written msgfds in any cases
+ * other than ret < 0 && errno == EAGAIN
+ */
+ if (!(ret < 0 && EAGAIN == errno)
+ && s->write_msgfds_num) {
+ g_free(s->write_msgfds);
+ s->write_msgfds = 0;
+ s->write_msgfds_num = 0;
+ }
+
+ if (ret < 0 && errno != EAGAIN) {
+ if (tcp_chr_read_poll(chr) <= 0) {
+ /* Perform disconnect and return error. */
+ tcp_chr_disconnect_locked(chr);
+ } /* else let the read handler finish it properly */
+ }
+
+ return ret;
+ } else {
+ /* Indicate an error. */
+ errno = EIO;
+ return -1;
+ }
+}
+
+static int tcp_chr_read_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(opaque);
+ if (s->state != TCP_CHARDEV_STATE_CONNECTED) {
+ return 0;
+ }
+ s->max_size = qemu_chr_be_can_write(chr);
+ return s->max_size;
+}
+
+static void tcp_chr_process_IAC_bytes(Chardev *chr,
+ SocketChardev *s,
+ uint8_t *buf, int *size)
+{
+ /* Handle any telnet or tn3270 client's basic IAC options.
+ * For telnet options, it satisfies char by char mode with no echo.
+ * For tn3270 options, it satisfies binary mode with EOR.
+ * All IAC options will be removed from the buf and the do_opt
+ * pointer will be used to track the state of the width of the
+ * IAC information.
+ *
+ * RFC854: "All TELNET commands consist of at least a two byte sequence.
+ * The commands dealing with option negotiation are three byte sequences,
+ * the third byte being the code for the option referenced."
+ * "IAC BREAK", "IAC IP", "IAC NOP" and the double IAC are two bytes.
+ * "IAC SB", "IAC SE" and "IAC EOR" are saved to split up data boundary
+ * for tn3270.
+ * NOP, Break and Interrupt Process(IP) might be encountered during a TN3270
+ * session, and NOP and IP need to be done later.
+ */
+
+ int i;
+ int j = 0;
+
+ for (i = 0; i < *size; i++) {
+ if (s->do_telnetopt > 1) {
+ if ((unsigned char)buf[i] == IAC && s->do_telnetopt == 2) {
+ /* Double IAC means send an IAC */
+ if (j != i) {
+ buf[j] = buf[i];
+ }
+ j++;
+ s->do_telnetopt = 1;
+ } else {
+ if ((unsigned char)buf[i] == IAC_BREAK
+ && s->do_telnetopt == 2) {
+ /* Handle IAC break commands by sending a serial break */
+ qemu_chr_be_event(chr, CHR_EVENT_BREAK);
+ s->do_telnetopt++;
+ } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_EOR
+ || (unsigned char)buf[i] == IAC_SB
+ || (unsigned char)buf[i] == IAC_SE)
+ && s->do_telnetopt == 2) {
+ buf[j++] = IAC;
+ buf[j++] = buf[i];
+ s->do_telnetopt++;
+ } else if (s->is_tn3270 && ((unsigned char)buf[i] == IAC_IP
+ || (unsigned char)buf[i] == IAC_NOP)
+ && s->do_telnetopt == 2) {
+ /* TODO: IP and NOP need to be implemented later. */
+ s->do_telnetopt++;
+ }
+ s->do_telnetopt++;
+ }
+ if (s->do_telnetopt >= 4) {
+ s->do_telnetopt = 1;
+ }
+ } else {
+ if ((unsigned char)buf[i] == IAC) {
+ s->do_telnetopt = 2;
+ } else {
+ if (j != i) {
+ buf[j] = buf[i];
+ }
+ j++;
+ }
+ }
+ }
+ *size = j;
+}
+
+static int tcp_get_msgfds(Chardev *chr, int *fds, int num)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ int to_copy = (s->read_msgfds_num < num) ? s->read_msgfds_num : num;
+
+ assert(num <= TCP_MAX_FDS);
+
+ if (to_copy) {
+ int i;
+
+ memcpy(fds, s->read_msgfds, to_copy * sizeof(int));
+
+ /* Close unused fds */
+ for (i = to_copy; i < s->read_msgfds_num; i++) {
+ close(s->read_msgfds[i]);
+ }
+
+ g_free(s->read_msgfds);
+ s->read_msgfds = 0;
+ s->read_msgfds_num = 0;
+ }
+
+ return to_copy;
+}
+
+static int tcp_set_msgfds(Chardev *chr, int *fds, int num)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ /* clear old pending fd array */
+ g_free(s->write_msgfds);
+ s->write_msgfds = NULL;
+ s->write_msgfds_num = 0;
+
+ if ((s->state != TCP_CHARDEV_STATE_CONNECTED) ||
+ !qio_channel_has_feature(s->ioc,
+ QIO_CHANNEL_FEATURE_FD_PASS)) {
+ return -1;
+ }
+
+ if (num) {
+ s->write_msgfds = g_new(int, num);
+ memcpy(s->write_msgfds, fds, num * sizeof(int));
+ }
+
+ s->write_msgfds_num = num;
+
+ return 0;
+}
+
+static ssize_t tcp_chr_recv(Chardev *chr, char *buf, size_t len)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ struct iovec iov = { .iov_base = buf, .iov_len = len };
+ int ret;
+ size_t i;
+ int *msgfds = NULL;
+ size_t msgfds_num = 0;
+
+ if (qio_channel_has_feature(s->ioc, QIO_CHANNEL_FEATURE_FD_PASS)) {
+ ret = qio_channel_readv_full(s->ioc, &iov, 1,
+ &msgfds, &msgfds_num,
+ NULL);
+ } else {
+ ret = qio_channel_readv_full(s->ioc, &iov, 1,
+ NULL, NULL,
+ NULL);
+ }
+
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ errno = EAGAIN;
+ ret = -1;
+ } else if (ret == -1) {
+ errno = EIO;
+ }
+
+ if (msgfds_num) {
+ /* close and clean read_msgfds */
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ close(s->read_msgfds[i]);
+ }
+
+ if (s->read_msgfds_num) {
+ g_free(s->read_msgfds);
+ }
+
+ s->read_msgfds = msgfds;
+ s->read_msgfds_num = msgfds_num;
+ }
+
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ int fd = s->read_msgfds[i];
+ if (fd < 0) {
+ continue;
+ }
+
+ /* O_NONBLOCK is preserved across SCM_RIGHTS so reset it */
+ qemu_set_block(fd);
+
+#ifndef MSG_CMSG_CLOEXEC
+ qemu_set_cloexec(fd);
+#endif
+ }
+
+ return ret;
+}
+
+static GSource *tcp_chr_add_watch(Chardev *chr, GIOCondition cond)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ if (!s->ioc) {
+ return NULL;
+ }
+ return qio_channel_create_watch(s->ioc, cond);
+}
+
+static void remove_hup_source(SocketChardev *s)
+{
+ if (s->hup_source != NULL) {
+ g_source_destroy(s->hup_source);
+ g_source_unref(s->hup_source);
+ s->hup_source = NULL;
+ }
+}
+
+static void char_socket_yank_iochannel(void *opaque)
+{
+ QIOChannel *ioc = QIO_CHANNEL(opaque);
+
+ qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
+}
+
+static void tcp_chr_free_connection(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ int i;
+
+ if (s->read_msgfds_num) {
+ for (i = 0; i < s->read_msgfds_num; i++) {
+ close(s->read_msgfds[i]);
+ }
+ g_free(s->read_msgfds);
+ s->read_msgfds = NULL;
+ s->read_msgfds_num = 0;
+ }
+
+ remove_hup_source(s);
+
+ tcp_set_msgfds(chr, NULL, 0);
+ remove_fd_in_watch(chr);
+ if (s->registered_yank &&
+ (s->state == TCP_CHARDEV_STATE_CONNECTING
+ || s->state == TCP_CHARDEV_STATE_CONNECTED)) {
+ yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(s->sioc));
+ }
+ object_unref(OBJECT(s->sioc));
+ s->sioc = NULL;
+ object_unref(OBJECT(s->ioc));
+ s->ioc = NULL;
+ g_free(chr->filename);
+ chr->filename = NULL;
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
+}
+
+static const char *qemu_chr_socket_protocol(SocketChardev *s)
+{
+ if (s->is_telnet) {
+ return "telnet";
+ }
+ return s->is_websock ? "websocket" : "tcp";
+}
+
+static char *qemu_chr_socket_address(SocketChardev *s, const char *prefix)
+{
+ switch (s->addr->type) {
+ case SOCKET_ADDRESS_TYPE_INET:
+ return g_strdup_printf("%s%s:%s:%s%s", prefix,
+ qemu_chr_socket_protocol(s),
+ s->addr->u.inet.host,
+ s->addr->u.inet.port,
+ s->is_listen ? ",server=on" : "");
+ break;
+ case SOCKET_ADDRESS_TYPE_UNIX:
+ {
+ const char *tight = "", *abstract = "";
+ UnixSocketAddress *sa = &s->addr->u.q_unix;
+
+#ifdef CONFIG_LINUX
+ if (sa->has_abstract && sa->abstract) {
+ abstract = ",abstract=on";
+ if (sa->has_tight && sa->tight) {
+ tight = ",tight=on";
+ }
+ }
+#endif
+
+ return g_strdup_printf("%sunix:%s%s%s%s", prefix, sa->path,
+ abstract, tight,
+ s->is_listen ? ",server=on" : "");
+ break;
+ }
+ case SOCKET_ADDRESS_TYPE_FD:
+ return g_strdup_printf("%sfd:%s%s", prefix, s->addr->u.fd.str,
+ s->is_listen ? ",server=on" : "");
+ break;
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ return g_strdup_printf("%svsock:%s:%s", prefix,
+ s->addr->u.vsock.cid,
+ s->addr->u.vsock.port);
+ default:
+ abort();
+ }
+}
+
+static void update_disconnected_filename(SocketChardev *s)
+{
+ Chardev *chr = CHARDEV(s);
+
+ g_free(chr->filename);
+ if (s->addr) {
+ chr->filename = qemu_chr_socket_address(s, "disconnected:");
+ } else {
+ chr->filename = g_strdup("disconnected:socket");
+ }
+}
+
+/* NB may be called even if tcp_chr_connect has not been
+ * reached, due to TLS or telnet initialization failure,
+ * so can *not* assume s->state == TCP_CHARDEV_STATE_CONNECTED
+ * This must be called with chr->chr_write_lock held.
+ */
+static void tcp_chr_disconnect_locked(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ bool emit_close = s->state == TCP_CHARDEV_STATE_CONNECTED;
+
+ tcp_chr_free_connection(chr);
+
+ if (s->listener) {
+ qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept,
+ chr, NULL, chr->gcontext);
+ }
+ update_disconnected_filename(s);
+ if (emit_close) {
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ }
+ if (s->reconnect_time && !s->reconnect_timer) {
+ qemu_chr_socket_restart_timer(chr);
+ }
+}
+
+static void tcp_chr_disconnect(Chardev *chr)
+{
+ qemu_mutex_lock(&chr->chr_write_lock);
+ tcp_chr_disconnect_locked(chr);
+ qemu_mutex_unlock(&chr->chr_write_lock);
+}
+
+static gboolean tcp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(opaque);
+ uint8_t buf[CHR_READ_BUF_LEN];
+ int len, size;
+
+ if ((s->state != TCP_CHARDEV_STATE_CONNECTED) ||
+ s->max_size <= 0) {
+ return TRUE;
+ }
+ len = sizeof(buf);
+ if (len > s->max_size) {
+ len = s->max_size;
+ }
+ size = tcp_chr_recv(chr, (void *)buf, len);
+ if (size == 0 || (size == -1 && errno != EAGAIN)) {
+ /* connection closed */
+ tcp_chr_disconnect(chr);
+ } else if (size > 0) {
+ if (s->do_telnetopt) {
+ tcp_chr_process_IAC_bytes(chr, s, buf, &size);
+ }
+ if (size > 0) {
+ qemu_chr_be_write(chr, buf, size);
+ }
+ }
+
+ return TRUE;
+}
+
+static gboolean tcp_chr_hup(QIOChannel *channel,
+ GIOCondition cond,
+ void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ tcp_chr_disconnect(chr);
+ return G_SOURCE_REMOVE;
+}
+
+static int tcp_chr_sync_read(Chardev *chr, const uint8_t *buf, int len)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ int size;
+
+ if (s->state != TCP_CHARDEV_STATE_CONNECTED) {
+ return 0;
+ }
+
+ qio_channel_set_blocking(s->ioc, true, NULL);
+ size = tcp_chr_recv(chr, (void *) buf, len);
+ if (s->state != TCP_CHARDEV_STATE_DISCONNECTED) {
+ qio_channel_set_blocking(s->ioc, false, NULL);
+ }
+ if (size == 0) {
+ /* connection closed */
+ tcp_chr_disconnect(chr);
+ }
+
+ return size;
+}
+
+static char *qemu_chr_compute_filename(SocketChardev *s)
+{
+ struct sockaddr_storage *ss = &s->sioc->localAddr;
+ struct sockaddr_storage *ps = &s->sioc->remoteAddr;
+ socklen_t ss_len = s->sioc->localAddrLen;
+ socklen_t ps_len = s->sioc->remoteAddrLen;
+ char shost[NI_MAXHOST], sserv[NI_MAXSERV];
+ char phost[NI_MAXHOST], pserv[NI_MAXSERV];
+ const char *left = "", *right = "";
+
+ switch (ss->ss_family) {
+#ifndef _WIN32
+ case AF_UNIX:
+ return g_strdup_printf("unix:%s%s",
+ ((struct sockaddr_un *)(ss))->sun_path,
+ s->is_listen ? ",server=on" : "");
+#endif
+ case AF_INET6:
+ left = "[";
+ right = "]";
+ /* fall through */
+ case AF_INET:
+ getnameinfo((struct sockaddr *) ss, ss_len, shost, sizeof(shost),
+ sserv, sizeof(sserv), NI_NUMERICHOST | NI_NUMERICSERV);
+ getnameinfo((struct sockaddr *) ps, ps_len, phost, sizeof(phost),
+ pserv, sizeof(pserv), NI_NUMERICHOST | NI_NUMERICSERV);
+ return g_strdup_printf("%s:%s%s%s:%s%s <-> %s%s%s:%s",
+ qemu_chr_socket_protocol(s),
+ left, shost, right, sserv,
+ s->is_listen ? ",server=on" : "",
+ left, phost, right, pserv);
+
+ default:
+ return g_strdup_printf("unknown");
+ }
+}
+
+static void update_ioc_handlers(SocketChardev *s)
+{
+ Chardev *chr = CHARDEV(s);
+
+ if (s->state != TCP_CHARDEV_STATE_CONNECTED) {
+ return;
+ }
+
+ remove_fd_in_watch(chr);
+ chr->gsource = io_add_watch_poll(chr, s->ioc,
+ tcp_chr_read_poll,
+ tcp_chr_read, chr,
+ chr->gcontext);
+
+ remove_hup_source(s);
+ s->hup_source = qio_channel_create_watch(s->ioc, G_IO_HUP);
+ g_source_set_callback(s->hup_source, (GSourceFunc)tcp_chr_hup,
+ chr, NULL);
+ g_source_attach(s->hup_source, chr->gcontext);
+}
+
+static void tcp_chr_connect(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(opaque);
+
+ g_free(chr->filename);
+ chr->filename = qemu_chr_compute_filename(s);
+
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTED);
+ update_ioc_handlers(s);
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+}
+
+static void tcp_chr_telnet_destroy(SocketChardev *s)
+{
+ if (s->telnet_source) {
+ g_source_destroy(s->telnet_source);
+ g_source_unref(s->telnet_source);
+ s->telnet_source = NULL;
+ }
+}
+
+static void tcp_chr_update_read_handler(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (s->listener && s->state == TCP_CHARDEV_STATE_DISCONNECTED) {
+ /*
+ * It's possible that chardev context is changed in
+ * qemu_chr_be_update_read_handlers(). Reset it for QIO net
+ * listener if there is.
+ */
+ qio_net_listener_set_client_func_full(s->listener, tcp_chr_accept,
+ chr, NULL, chr->gcontext);
+ }
+
+ if (s->telnet_source) {
+ tcp_chr_telnet_init(CHARDEV(s));
+ }
+
+ update_ioc_handlers(s);
+}
+
+static gboolean tcp_chr_telnet_init_io(QIOChannel *ioc,
+ GIOCondition cond G_GNUC_UNUSED,
+ gpointer user_data)
+{
+ SocketChardev *s = user_data;
+ Chardev *chr = CHARDEV(s);
+ TCPChardevTelnetInit *init = s->telnet_init;
+ ssize_t ret;
+
+ assert(init);
+
+ ret = qio_channel_write(ioc, init->buf, init->buflen, NULL);
+ if (ret < 0) {
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ ret = 0;
+ } else {
+ tcp_chr_disconnect(chr);
+ goto end;
+ }
+ }
+ init->buflen -= ret;
+
+ if (init->buflen == 0) {
+ tcp_chr_connect(chr);
+ goto end;
+ }
+
+ memmove(init->buf, init->buf + ret, init->buflen);
+
+ return G_SOURCE_CONTINUE;
+
+end:
+ g_free(s->telnet_init);
+ s->telnet_init = NULL;
+ g_source_unref(s->telnet_source);
+ s->telnet_source = NULL;
+ return G_SOURCE_REMOVE;
+}
+
+static void tcp_chr_telnet_init(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ TCPChardevTelnetInit *init;
+ size_t n = 0;
+
+ /* Destroy existing task */
+ tcp_chr_telnet_destroy(s);
+
+ if (s->telnet_init) {
+ /* We are possibly during a handshake already */
+ goto cont;
+ }
+
+ s->telnet_init = g_new0(TCPChardevTelnetInit, 1);
+ init = s->telnet_init;
+
+#define IACSET(x, a, b, c) \
+ do { \
+ x[n++] = a; \
+ x[n++] = b; \
+ x[n++] = c; \
+ } while (0)
+
+ if (!s->is_tn3270) {
+ init->buflen = 12;
+ /* Prep the telnet negotion to put telnet in binary,
+ * no echo, single char mode */
+ IACSET(init->buf, 0xff, 0xfb, 0x01); /* IAC WILL ECHO */
+ IACSET(init->buf, 0xff, 0xfb, 0x03); /* IAC WILL Suppress go ahead */
+ IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL Binary */
+ IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO Binary */
+ } else {
+ init->buflen = 21;
+ /* Prep the TN3270 negotion based on RFC1576 */
+ IACSET(init->buf, 0xff, 0xfd, 0x19); /* IAC DO EOR */
+ IACSET(init->buf, 0xff, 0xfb, 0x19); /* IAC WILL EOR */
+ IACSET(init->buf, 0xff, 0xfd, 0x00); /* IAC DO BINARY */
+ IACSET(init->buf, 0xff, 0xfb, 0x00); /* IAC WILL BINARY */
+ IACSET(init->buf, 0xff, 0xfd, 0x18); /* IAC DO TERMINAL TYPE */
+ IACSET(init->buf, 0xff, 0xfa, 0x18); /* IAC SB TERMINAL TYPE */
+ IACSET(init->buf, 0x01, 0xff, 0xf0); /* SEND IAC SE */
+ }
+
+#undef IACSET
+
+cont:
+ s->telnet_source = qio_channel_add_watch_source(s->ioc, G_IO_OUT,
+ tcp_chr_telnet_init_io,
+ s, NULL,
+ chr->gcontext);
+}
+
+
+static void tcp_chr_websock_handshake(QIOTask *task, gpointer user_data)
+{
+ Chardev *chr = user_data;
+ SocketChardev *s = user_data;
+
+ if (qio_task_propagate_error(task, NULL)) {
+ tcp_chr_disconnect(chr);
+ } else {
+ if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+ }
+}
+
+
+static void tcp_chr_websock_init(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ QIOChannelWebsock *wioc = NULL;
+ gchar *name;
+
+ wioc = qio_channel_websock_new_server(s->ioc);
+
+ name = g_strdup_printf("chardev-websocket-server-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(wioc), name);
+ g_free(name);
+ object_unref(OBJECT(s->ioc));
+ s->ioc = QIO_CHANNEL(wioc);
+
+ qio_channel_websock_handshake(wioc, tcp_chr_websock_handshake, chr, NULL);
+}
+
+
+static void tcp_chr_tls_handshake(QIOTask *task,
+ gpointer user_data)
+{
+ Chardev *chr = user_data;
+ SocketChardev *s = user_data;
+
+ if (qio_task_propagate_error(task, NULL)) {
+ tcp_chr_disconnect(chr);
+ } else {
+ if (s->is_websock) {
+ tcp_chr_websock_init(chr);
+ } else if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+ }
+}
+
+
+static void tcp_chr_tls_init(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ QIOChannelTLS *tioc;
+ gchar *name;
+
+ if (s->is_listen) {
+ tioc = qio_channel_tls_new_server(
+ s->ioc, s->tls_creds,
+ s->tls_authz,
+ NULL);
+ } else {
+ tioc = qio_channel_tls_new_client(
+ s->ioc, s->tls_creds,
+ s->addr->u.inet.host,
+ NULL);
+ }
+ if (tioc == NULL) {
+ tcp_chr_disconnect(chr);
+ return;
+ }
+ name = g_strdup_printf("chardev-tls-%s-%s",
+ s->is_listen ? "server" : "client",
+ chr->label);
+ qio_channel_set_name(QIO_CHANNEL(tioc), name);
+ g_free(name);
+ object_unref(OBJECT(s->ioc));
+ s->ioc = QIO_CHANNEL(tioc);
+
+ qio_channel_tls_handshake(tioc,
+ tcp_chr_tls_handshake,
+ chr,
+ NULL,
+ chr->gcontext);
+}
+
+
+static void tcp_chr_set_client_ioc_name(Chardev *chr,
+ QIOChannelSocket *sioc)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ char *name;
+ name = g_strdup_printf("chardev-tcp-%s-%s",
+ s->is_listen ? "server" : "client",
+ chr->label);
+ qio_channel_set_name(QIO_CHANNEL(sioc), name);
+ g_free(name);
+
+}
+
+static int tcp_chr_new_client(Chardev *chr, QIOChannelSocket *sioc)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (s->state != TCP_CHARDEV_STATE_CONNECTING) {
+ return -1;
+ }
+
+ s->ioc = QIO_CHANNEL(sioc);
+ object_ref(OBJECT(sioc));
+ s->sioc = sioc;
+ object_ref(OBJECT(sioc));
+
+ qio_channel_set_blocking(s->ioc, false, NULL);
+
+ if (s->do_nodelay) {
+ qio_channel_set_delay(s->ioc, false);
+ }
+ if (s->listener) {
+ qio_net_listener_set_client_func_full(s->listener, NULL, NULL,
+ NULL, chr->gcontext);
+ }
+
+ if (s->tls_creds) {
+ tcp_chr_tls_init(chr);
+ } else if (s->is_websock) {
+ tcp_chr_websock_init(chr);
+ } else if (s->do_telnetopt) {
+ tcp_chr_telnet_init(chr);
+ } else {
+ tcp_chr_connect(chr);
+ }
+
+ return 0;
+}
+
+
+static int tcp_chr_add_client(Chardev *chr, int fd)
+{
+ int ret;
+ QIOChannelSocket *sioc;
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (s->state != TCP_CHARDEV_STATE_DISCONNECTED) {
+ return -1;
+ }
+
+ sioc = qio_channel_socket_new_fd(fd, NULL);
+ if (!sioc) {
+ return -1;
+ }
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
+ tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
+ ret = tcp_chr_new_client(chr, sioc);
+ object_unref(OBJECT(sioc));
+ return ret;
+}
+
+static void tcp_chr_accept(QIONetListener *listener,
+ QIOChannelSocket *cioc,
+ void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
+ tcp_chr_set_client_ioc_name(chr, cioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(cioc));
+ }
+ tcp_chr_new_client(chr, cioc);
+}
+
+
+static int tcp_chr_connect_client_sync(Chardev *chr, Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ QIOChannelSocket *sioc = qio_channel_socket_new();
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
+ tcp_chr_set_client_ioc_name(chr, sioc);
+ if (qio_channel_socket_connect_sync(sioc, s->addr, errp) < 0) {
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
+ object_unref(OBJECT(sioc));
+ return -1;
+ }
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
+ tcp_chr_new_client(chr, sioc);
+ object_unref(OBJECT(sioc));
+ return 0;
+}
+
+
+static void tcp_chr_accept_server_sync(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ QIOChannelSocket *sioc;
+ info_report("QEMU waiting for connection on: %s",
+ chr->filename);
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
+ sioc = qio_net_listener_wait_client(s->listener);
+ tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
+ tcp_chr_new_client(chr, sioc);
+ object_unref(OBJECT(sioc));
+}
+
+
+static int tcp_chr_wait_connected(Chardev *chr, Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ const char *opts[] = { "telnet", "tn3270", "websock", "tls-creds" };
+ bool optset[] = { s->is_telnet, s->is_tn3270, s->is_websock, s->tls_creds };
+ size_t i;
+
+ QEMU_BUILD_BUG_ON(G_N_ELEMENTS(opts) != G_N_ELEMENTS(optset));
+ for (i = 0; i < G_N_ELEMENTS(opts); i++) {
+ if (optset[i]) {
+ error_setg(errp,
+ "'%s' option is incompatible with waiting for "
+ "connection completion", opts[i]);
+ return -1;
+ }
+ }
+
+ tcp_chr_reconn_timer_cancel(s);
+
+ /*
+ * We expect states to be as follows:
+ *
+ * - server
+ * - wait -> CONNECTED
+ * - nowait -> DISCONNECTED
+ * - client
+ * - reconnect == 0 -> CONNECTED
+ * - reconnect != 0 -> CONNECTING
+ *
+ */
+ if (s->state == TCP_CHARDEV_STATE_CONNECTING) {
+ if (!s->connect_task) {
+ error_setg(errp,
+ "Unexpected 'connecting' state without connect task "
+ "while waiting for connection completion");
+ return -1;
+ }
+ /*
+ * tcp_chr_wait_connected should only ever be run from the
+ * main loop thread associated with chr->gcontext, otherwise
+ * qio_task_wait_thread has a dangerous race condition with
+ * free'ing of the s->connect_task object.
+ *
+ * Acquiring the main context doesn't 100% prove we're in
+ * the main loop thread, but it does at least guarantee
+ * that the main loop won't be executed by another thread
+ * avoiding the race condition with the task idle callback.
+ */
+ g_main_context_acquire(chr->gcontext);
+ qio_task_wait_thread(s->connect_task);
+ g_main_context_release(chr->gcontext);
+
+ /*
+ * The completion callback (qemu_chr_socket_connected) for
+ * s->connect_task should have set this to NULL by the time
+ * qio_task_wait_thread has returned.
+ */
+ assert(!s->connect_task);
+
+ /*
+ * NB we are *not* guaranteed to have "s->state == ..CONNECTED"
+ * at this point as this first connect may be failed, so
+ * allow the next loop to run regardless.
+ */
+ }
+
+ while (s->state != TCP_CHARDEV_STATE_CONNECTED) {
+ if (s->is_listen) {
+ tcp_chr_accept_server_sync(chr);
+ } else {
+ Error *err = NULL;
+ if (tcp_chr_connect_client_sync(chr, &err) < 0) {
+ if (s->reconnect_time) {
+ error_free(err);
+ g_usleep(s->reconnect_time * 1000ULL * 1000ULL);
+ } else {
+ error_propagate(errp, err);
+ return -1;
+ }
+ }
+ }
+ }
+
+ return 0;
+}
+
+static void char_socket_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ SocketChardev *s = SOCKET_CHARDEV(obj);
+
+ tcp_chr_free_connection(chr);
+ tcp_chr_reconn_timer_cancel(s);
+ qapi_free_SocketAddress(s->addr);
+ tcp_chr_telnet_destroy(s);
+ g_free(s->telnet_init);
+ if (s->listener) {
+ qio_net_listener_set_client_func_full(s->listener, NULL, NULL,
+ NULL, chr->gcontext);
+ object_unref(OBJECT(s->listener));
+ }
+ if (s->tls_creds) {
+ object_unref(OBJECT(s->tls_creds));
+ }
+ g_free(s->tls_authz);
+ if (s->registered_yank) {
+ /*
+ * In the chardev-change special-case, we shouldn't unregister the yank
+ * instance, as it still may be needed.
+ */
+ if (!chr->handover_yank_instance) {
+ yank_unregister_instance(CHARDEV_YANK_INSTANCE(chr->label));
+ }
+ }
+
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+static void qemu_chr_socket_connected(QIOTask *task, void *opaque)
+{
+ QIOChannelSocket *sioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ Error *err = NULL;
+
+ s->connect_task = NULL;
+
+ if (qio_task_propagate_error(task, &err)) {
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_DISCONNECTED);
+ if (s->registered_yank) {
+ yank_unregister_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
+ check_report_connect_error(chr, err);
+ goto cleanup;
+ }
+
+ s->connect_err_reported = false;
+ tcp_chr_new_client(chr, sioc);
+
+cleanup:
+ object_unref(OBJECT(sioc));
+}
+
+
+static void tcp_chr_connect_client_task(QIOTask *task,
+ gpointer opaque)
+{
+ QIOChannelSocket *ioc = QIO_CHANNEL_SOCKET(qio_task_get_source(task));
+ SocketAddress *addr = opaque;
+ Error *err = NULL;
+
+ qio_channel_socket_connect_sync(ioc, addr, &err);
+
+ qio_task_set_error(task, err);
+}
+
+
+static void tcp_chr_connect_client_async(Chardev *chr)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ QIOChannelSocket *sioc;
+
+ tcp_chr_change_state(s, TCP_CHARDEV_STATE_CONNECTING);
+ sioc = qio_channel_socket_new();
+ tcp_chr_set_client_ioc_name(chr, sioc);
+ if (s->registered_yank) {
+ yank_register_function(CHARDEV_YANK_INSTANCE(chr->label),
+ char_socket_yank_iochannel,
+ QIO_CHANNEL(sioc));
+ }
+ /*
+ * Normally code would use the qio_channel_socket_connect_async
+ * method which uses a QIOTask + qio_task_set_error internally
+ * to avoid blocking. The tcp_chr_wait_connected method, however,
+ * needs a way to synchronize with completion of the background
+ * connect task which can't be done with the QIOChannelSocket
+ * async APIs. Thus we must use QIOTask directly to implement
+ * the non-blocking concept locally.
+ */
+ s->connect_task = qio_task_new(OBJECT(sioc),
+ qemu_chr_socket_connected,
+ object_ref(OBJECT(chr)),
+ (GDestroyNotify)object_unref);
+ qio_task_run_in_thread(s->connect_task,
+ tcp_chr_connect_client_task,
+ s->addr,
+ NULL,
+ chr->gcontext);
+}
+
+static gboolean socket_reconnect_timeout(gpointer opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ SocketChardev *s = SOCKET_CHARDEV(opaque);
+
+ qemu_mutex_lock(&chr->chr_write_lock);
+ g_source_unref(s->reconnect_timer);
+ s->reconnect_timer = NULL;
+ qemu_mutex_unlock(&chr->chr_write_lock);
+
+ if (chr->be_open) {
+ return false;
+ }
+
+ tcp_chr_connect_client_async(chr);
+
+ return false;
+}
+
+
+static int qmp_chardev_open_socket_server(Chardev *chr,
+ bool is_telnet,
+ bool is_waitconnect,
+ Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ char *name;
+ if (is_telnet) {
+ s->do_telnetopt = 1;
+ }
+ s->listener = qio_net_listener_new();
+
+ name = g_strdup_printf("chardev-tcp-listener-%s", chr->label);
+ qio_net_listener_set_name(s->listener, name);
+ g_free(name);
+
+ if (qio_net_listener_open_sync(s->listener, s->addr, 1, errp) < 0) {
+ object_unref(OBJECT(s->listener));
+ s->listener = NULL;
+ return -1;
+ }
+
+ qapi_free_SocketAddress(s->addr);
+ s->addr = socket_local_address(s->listener->sioc[0]->fd, errp);
+ update_disconnected_filename(s);
+
+ if (is_waitconnect) {
+ tcp_chr_accept_server_sync(chr);
+ } else {
+ qio_net_listener_set_client_func_full(s->listener,
+ tcp_chr_accept,
+ chr, NULL,
+ chr->gcontext);
+ }
+
+ return 0;
+}
+
+
+static int qmp_chardev_open_socket_client(Chardev *chr,
+ int64_t reconnect,
+ Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+
+ if (reconnect > 0) {
+ s->reconnect_time = reconnect;
+ tcp_chr_connect_client_async(chr);
+ return 0;
+ } else {
+ return tcp_chr_connect_client_sync(chr, errp);
+ }
+}
+
+
+static bool qmp_chardev_validate_socket(ChardevSocket *sock,
+ SocketAddress *addr,
+ Error **errp)
+{
+ /* Validate any options which have a dependency on address type */
+ switch (addr->type) {
+ case SOCKET_ADDRESS_TYPE_FD:
+ if (sock->has_reconnect) {
+ error_setg(errp,
+ "'reconnect' option is incompatible with "
+ "'fd' address type");
+ return false;
+ }
+ if (sock->has_tls_creds &&
+ !(sock->has_server && sock->server)) {
+ error_setg(errp,
+ "'tls_creds' option is incompatible with "
+ "'fd' address type as client");
+ return false;
+ }
+ break;
+
+ case SOCKET_ADDRESS_TYPE_UNIX:
+ if (sock->has_tls_creds) {
+ error_setg(errp,
+ "'tls_creds' option is incompatible with "
+ "'unix' address type");
+ return false;
+ }
+ break;
+
+ case SOCKET_ADDRESS_TYPE_INET:
+ break;
+
+ case SOCKET_ADDRESS_TYPE_VSOCK:
+ if (sock->has_tls_creds) {
+ error_setg(errp,
+ "'tls_creds' option is incompatible with "
+ "'vsock' address type");
+ return false;
+ }
+
+ default:
+ break;
+ }
+
+ if (sock->has_tls_authz && !sock->has_tls_creds) {
+ error_setg(errp, "'tls_authz' option requires 'tls_creds' option");
+ return false;
+ }
+
+ /* Validate any options which have a dependancy on client vs server */
+ if (!sock->has_server || sock->server) {
+ if (sock->has_reconnect) {
+ error_setg(errp,
+ "'reconnect' option is incompatible with "
+ "socket in server listen mode");
+ return false;
+ }
+ } else {
+ if (sock->has_websocket && sock->websocket) {
+ error_setg(errp, "%s", "Websocket client is not implemented");
+ return false;
+ }
+ if (sock->has_wait) {
+ error_setg(errp, "%s",
+ "'wait' option is incompatible with "
+ "socket in client connect mode");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+
+static void qmp_chardev_open_socket(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(chr);
+ ChardevSocket *sock = backend->u.socket.data;
+ bool do_nodelay = sock->has_nodelay ? sock->nodelay : false;
+ bool is_listen = sock->has_server ? sock->server : true;
+ bool is_telnet = sock->has_telnet ? sock->telnet : false;
+ bool is_tn3270 = sock->has_tn3270 ? sock->tn3270 : false;
+ bool is_waitconnect = sock->has_wait ? sock->wait : false;
+ bool is_websock = sock->has_websocket ? sock->websocket : false;
+ int64_t reconnect = sock->has_reconnect ? sock->reconnect : 0;
+ SocketAddress *addr;
+
+ s->is_listen = is_listen;
+ s->is_telnet = is_telnet;
+ s->is_tn3270 = is_tn3270;
+ s->is_websock = is_websock;
+ s->do_nodelay = do_nodelay;
+ if (sock->tls_creds) {
+ Object *creds;
+ creds = object_resolve_path_component(
+ object_get_objects_root(), sock->tls_creds);
+ if (!creds) {
+ error_setg(errp, "No TLS credentials with id '%s'",
+ sock->tls_creds);
+ return;
+ }
+ s->tls_creds = (QCryptoTLSCreds *)
+ object_dynamic_cast(creds,
+ TYPE_QCRYPTO_TLS_CREDS);
+ if (!s->tls_creds) {
+ error_setg(errp, "Object with id '%s' is not TLS credentials",
+ sock->tls_creds);
+ return;
+ }
+ object_ref(OBJECT(s->tls_creds));
+ if (!qcrypto_tls_creds_check_endpoint(s->tls_creds,
+ is_listen
+ ? QCRYPTO_TLS_CREDS_ENDPOINT_SERVER
+ : QCRYPTO_TLS_CREDS_ENDPOINT_CLIENT,
+ errp)) {
+ return;
+ }
+ }
+ s->tls_authz = g_strdup(sock->tls_authz);
+
+ s->addr = addr = socket_address_flatten(sock->addr);
+
+ if (!qmp_chardev_validate_socket(sock, addr, errp)) {
+ return;
+ }
+
+ qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_RECONNECTABLE);
+ /* TODO SOCKET_ADDRESS_FD where fd has AF_UNIX */
+ if (addr->type == SOCKET_ADDRESS_TYPE_UNIX) {
+ qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_FD_PASS);
+ }
+
+ /*
+ * In the chardev-change special-case, we shouldn't register a new yank
+ * instance, as there already may be one.
+ */
+ if (!chr->handover_yank_instance) {
+ if (!yank_register_instance(CHARDEV_YANK_INSTANCE(chr->label), errp)) {
+ return;
+ }
+ }
+ s->registered_yank = true;
+
+ /* be isn't opened until we get a connection */
+ *be_opened = false;
+
+ update_disconnected_filename(s);
+
+ if (s->is_listen) {
+ if (qmp_chardev_open_socket_server(chr, is_telnet || is_tn3270,
+ is_waitconnect, errp) < 0) {
+ return;
+ }
+ } else {
+ if (qmp_chardev_open_socket_client(chr, reconnect, errp) < 0) {
+ return;
+ }
+ }
+}
+
+static void qemu_chr_parse_socket(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *path = qemu_opt_get(opts, "path");
+ const char *host = qemu_opt_get(opts, "host");
+ const char *port = qemu_opt_get(opts, "port");
+ const char *fd = qemu_opt_get(opts, "fd");
+#ifdef CONFIG_LINUX
+ bool tight = qemu_opt_get_bool(opts, "tight", true);
+ bool abstract = qemu_opt_get_bool(opts, "abstract", false);
+#endif
+ SocketAddressLegacy *addr;
+ ChardevSocket *sock;
+
+ if ((!!path + !!fd + !!host) != 1) {
+ error_setg(errp,
+ "Exactly one of 'path', 'fd' or 'host' required");
+ return;
+ }
+
+ if (host && !port) {
+ error_setg(errp, "chardev: socket: no port given");
+ return;
+ }
+
+ backend->type = CHARDEV_BACKEND_KIND_SOCKET;
+ sock = backend->u.socket.data = g_new0(ChardevSocket, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevSocket_base(sock));
+
+ if (qemu_opt_get(opts, "delay") && qemu_opt_get(opts, "nodelay")) {
+ error_setg(errp, "'delay' and 'nodelay' are mutually exclusive");
+ return;
+ }
+ sock->has_nodelay =
+ qemu_opt_get(opts, "delay") ||
+ qemu_opt_get(opts, "nodelay");
+ sock->nodelay =
+ !qemu_opt_get_bool(opts, "delay", true) ||
+ qemu_opt_get_bool(opts, "nodelay", false);
+
+ /*
+ * We have different default to QMP for 'server', hence
+ * we can't just check for existence of 'server'
+ */
+ sock->has_server = true;
+ sock->server = qemu_opt_get_bool(opts, "server", false);
+ sock->has_telnet = qemu_opt_get(opts, "telnet");
+ sock->telnet = qemu_opt_get_bool(opts, "telnet", false);
+ sock->has_tn3270 = qemu_opt_get(opts, "tn3270");
+ sock->tn3270 = qemu_opt_get_bool(opts, "tn3270", false);
+ sock->has_websocket = qemu_opt_get(opts, "websocket");
+ sock->websocket = qemu_opt_get_bool(opts, "websocket", false);
+ /*
+ * We have different default to QMP for 'wait' when 'server'
+ * is set, hence we can't just check for existence of 'wait'
+ */
+ sock->has_wait = qemu_opt_find(opts, "wait") || sock->server;
+ sock->wait = qemu_opt_get_bool(opts, "wait", true);
+ sock->has_reconnect = qemu_opt_find(opts, "reconnect");
+ sock->reconnect = qemu_opt_get_number(opts, "reconnect", 0);
+ sock->has_tls_creds = qemu_opt_get(opts, "tls-creds");
+ sock->tls_creds = g_strdup(qemu_opt_get(opts, "tls-creds"));
+ sock->has_tls_authz = qemu_opt_get(opts, "tls-authz");
+ sock->tls_authz = g_strdup(qemu_opt_get(opts, "tls-authz"));
+
+ addr = g_new0(SocketAddressLegacy, 1);
+ if (path) {
+ UnixSocketAddress *q_unix;
+ addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ q_unix = addr->u.q_unix.data = g_new0(UnixSocketAddress, 1);
+ q_unix->path = g_strdup(path);
+#ifdef CONFIG_LINUX
+ q_unix->has_tight = true;
+ q_unix->tight = tight;
+ q_unix->has_abstract = true;
+ q_unix->abstract = abstract;
+#endif
+ } else if (host) {
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
+ addr->u.inet.data = g_new(InetSocketAddress, 1);
+ *addr->u.inet.data = (InetSocketAddress) {
+ .host = g_strdup(host),
+ .port = g_strdup(port),
+ .has_to = qemu_opt_get(opts, "to"),
+ .to = qemu_opt_get_number(opts, "to", 0),
+ .has_ipv4 = qemu_opt_get(opts, "ipv4"),
+ .ipv4 = qemu_opt_get_bool(opts, "ipv4", 0),
+ .has_ipv6 = qemu_opt_get(opts, "ipv6"),
+ .ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
+ };
+ } else if (fd) {
+ addr->type = SOCKET_ADDRESS_TYPE_FD;
+ addr->u.fd.data = g_new(String, 1);
+ addr->u.fd.data->str = g_strdup(fd);
+ } else {
+ g_assert_not_reached();
+ }
+ sock->addr = addr;
+}
+
+static void
+char_socket_get_addr(Object *obj, Visitor *v, const char *name,
+ void *opaque, Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(obj);
+
+ visit_type_SocketAddress(v, name, &s->addr, errp);
+}
+
+static bool
+char_socket_get_connected(Object *obj, Error **errp)
+{
+ SocketChardev *s = SOCKET_CHARDEV(obj);
+
+ return s->state == TCP_CHARDEV_STATE_CONNECTED;
+}
+
+static void char_socket_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->supports_yank = true;
+
+ cc->parse = qemu_chr_parse_socket;
+ cc->open = qmp_chardev_open_socket;
+ cc->chr_wait_connected = tcp_chr_wait_connected;
+ cc->chr_write = tcp_chr_write;
+ cc->chr_sync_read = tcp_chr_sync_read;
+ cc->chr_disconnect = tcp_chr_disconnect;
+ cc->get_msgfds = tcp_get_msgfds;
+ cc->set_msgfds = tcp_set_msgfds;
+ cc->chr_add_client = tcp_chr_add_client;
+ cc->chr_add_watch = tcp_chr_add_watch;
+ cc->chr_update_read_handler = tcp_chr_update_read_handler;
+
+ object_class_property_add(oc, "addr", "SocketAddress",
+ char_socket_get_addr, NULL,
+ NULL, NULL);
+
+ object_class_property_add_bool(oc, "connected", char_socket_get_connected,
+ NULL);
+}
+
+static const TypeInfo char_socket_type_info = {
+ .name = TYPE_CHARDEV_SOCKET,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(SocketChardev),
+ .instance_finalize = char_socket_finalize,
+ .class_init = char_socket_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_socket_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-stdio.c b/chardev/char-stdio.c
new file mode 100644
index 000000000..403da308c
--- /dev/null
+++ b/chardev/char-stdio.c
@@ -0,0 +1,166 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/sockets.h"
+#include "qapi/error.h"
+#include "chardev/char.h"
+
+#ifdef _WIN32
+#include "chardev/char-win.h"
+#include "chardev/char-win-stdio.h"
+#else
+#include <termios.h>
+#include "chardev/char-fd.h"
+#endif
+
+#ifndef _WIN32
+/* init terminal so that we can grab keys */
+static struct termios oldtty;
+static int old_fd0_flags;
+static bool stdio_in_use;
+static bool stdio_allow_signal;
+static bool stdio_echo_state;
+
+static void term_exit(void)
+{
+ if (stdio_in_use) {
+ tcsetattr(0, TCSANOW, &oldtty);
+ fcntl(0, F_SETFL, old_fd0_flags);
+ }
+}
+
+static void qemu_chr_set_echo_stdio(Chardev *chr, bool echo)
+{
+ struct termios tty;
+
+ stdio_echo_state = echo;
+ tty = oldtty;
+ if (!echo) {
+ tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
+ | INLCR | IGNCR | ICRNL | IXON);
+ tty.c_oflag |= OPOST;
+ tty.c_lflag &= ~(ECHO | ECHONL | ICANON | IEXTEN);
+ tty.c_cflag &= ~(CSIZE | PARENB);
+ tty.c_cflag |= CS8;
+ tty.c_cc[VMIN] = 1;
+ tty.c_cc[VTIME] = 0;
+ }
+ if (!stdio_allow_signal) {
+ tty.c_lflag &= ~ISIG;
+ }
+
+ tcsetattr(0, TCSANOW, &tty);
+}
+
+static void term_stdio_handler(int sig)
+{
+ /* restore echo after resume from suspend. */
+ qemu_chr_set_echo_stdio(NULL, stdio_echo_state);
+}
+
+static void qemu_chr_open_stdio(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevStdio *opts = backend->u.stdio.data;
+ struct sigaction act;
+
+ if (is_daemonized()) {
+ error_setg(errp, "cannot use stdio with -daemonize");
+ return;
+ }
+
+ if (stdio_in_use) {
+ error_setg(errp, "cannot use stdio by multiple character devices");
+ return;
+ }
+
+ stdio_in_use = true;
+ old_fd0_flags = fcntl(0, F_GETFL);
+ tcgetattr(0, &oldtty);
+ qemu_set_nonblock(0);
+ atexit(term_exit);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = term_stdio_handler;
+ sigaction(SIGCONT, &act, NULL);
+
+ qemu_chr_open_fd(chr, 0, 1);
+
+ stdio_allow_signal = !opts->has_signal || opts->signal;
+ qemu_chr_set_echo_stdio(chr, false);
+}
+#endif
+
+static void qemu_chr_parse_stdio(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ ChardevStdio *stdio;
+
+ backend->type = CHARDEV_BACKEND_KIND_STDIO;
+ stdio = backend->u.stdio.data = g_new0(ChardevStdio, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevStdio_base(stdio));
+ stdio->has_signal = true;
+ stdio->signal = qemu_opt_get_bool(opts, "signal", true);
+}
+
+static void char_stdio_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_stdio;
+#ifndef _WIN32
+ cc->open = qemu_chr_open_stdio;
+ cc->chr_set_echo = qemu_chr_set_echo_stdio;
+#endif
+}
+
+static void char_stdio_finalize(Object *obj)
+{
+#ifndef _WIN32
+ term_exit();
+#endif
+}
+
+static const TypeInfo char_stdio_type_info = {
+ .name = TYPE_CHARDEV_STDIO,
+#ifdef _WIN32
+ .parent = TYPE_CHARDEV_WIN_STDIO,
+#else
+ .parent = TYPE_CHARDEV_FD,
+#endif
+ .instance_finalize = char_stdio_finalize,
+ .class_init = char_stdio_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_stdio_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-udp.c b/chardev/char-udp.c
new file mode 100644
index 000000000..6756e6992
--- /dev/null
+++ b/chardev/char-udp.c
@@ -0,0 +1,246 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "chardev/char.h"
+#include "io/channel-socket.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+
+#include "chardev/char-io.h"
+#include "qom/object.h"
+
+/***********************************************************/
+/* UDP Net console */
+
+struct UdpChardev {
+ Chardev parent;
+ QIOChannel *ioc;
+ uint8_t buf[CHR_READ_BUF_LEN];
+ int bufcnt;
+ int bufptr;
+ int max_size;
+};
+typedef struct UdpChardev UdpChardev;
+
+DECLARE_INSTANCE_CHECKER(UdpChardev, UDP_CHARDEV,
+ TYPE_CHARDEV_UDP)
+
+/* Called with chr_write_lock held. */
+static int udp_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ UdpChardev *s = UDP_CHARDEV(chr);
+
+ return qio_channel_write(
+ s->ioc, (const char *)buf, len, NULL);
+}
+
+static void udp_chr_flush_buffer(UdpChardev *s)
+{
+ Chardev *chr = CHARDEV(s);
+
+ while (s->max_size > 0 && s->bufptr < s->bufcnt) {
+ int n = MIN(s->max_size, s->bufcnt - s->bufptr);
+ qemu_chr_be_write(chr, &s->buf[s->bufptr], n);
+ s->bufptr += n;
+ s->max_size = qemu_chr_be_can_write(chr);
+ }
+}
+
+static int udp_chr_read_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ UdpChardev *s = UDP_CHARDEV(opaque);
+
+ s->max_size = qemu_chr_be_can_write(chr);
+
+ /* If there were any stray characters in the queue process them
+ * first
+ */
+ udp_chr_flush_buffer(s);
+
+ return s->max_size;
+}
+
+static gboolean udp_chr_read(QIOChannel *chan, GIOCondition cond, void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ UdpChardev *s = UDP_CHARDEV(opaque);
+ ssize_t ret;
+
+ if (s->max_size == 0) {
+ return TRUE;
+ }
+ ret = qio_channel_read(
+ s->ioc, (char *)s->buf, sizeof(s->buf), NULL);
+ if (ret <= 0) {
+ remove_fd_in_watch(chr);
+ return FALSE;
+ }
+ s->bufcnt = ret;
+ s->bufptr = 0;
+ udp_chr_flush_buffer(s);
+
+ return TRUE;
+}
+
+static void udp_chr_update_read_handler(Chardev *chr)
+{
+ UdpChardev *s = UDP_CHARDEV(chr);
+
+ remove_fd_in_watch(chr);
+ if (s->ioc) {
+ chr->gsource = io_add_watch_poll(chr, s->ioc,
+ udp_chr_read_poll,
+ udp_chr_read, chr,
+ chr->gcontext);
+ }
+}
+
+static void char_udp_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ UdpChardev *s = UDP_CHARDEV(obj);
+
+ remove_fd_in_watch(chr);
+ if (s->ioc) {
+ object_unref(OBJECT(s->ioc));
+ }
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+static void qemu_chr_parse_udp(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *host = qemu_opt_get(opts, "host");
+ const char *port = qemu_opt_get(opts, "port");
+ const char *localaddr = qemu_opt_get(opts, "localaddr");
+ const char *localport = qemu_opt_get(opts, "localport");
+ bool has_local = false;
+ SocketAddressLegacy *addr;
+ ChardevUdp *udp;
+
+ backend->type = CHARDEV_BACKEND_KIND_UDP;
+ if (host == NULL || strlen(host) == 0) {
+ host = "localhost";
+ }
+ if (port == NULL || strlen(port) == 0) {
+ error_setg(errp, "chardev: udp: remote port not specified");
+ return;
+ }
+ if (localport == NULL || strlen(localport) == 0) {
+ localport = "0";
+ } else {
+ has_local = true;
+ }
+ if (localaddr == NULL || strlen(localaddr) == 0) {
+ localaddr = "";
+ } else {
+ has_local = true;
+ }
+
+ udp = backend->u.udp.data = g_new0(ChardevUdp, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevUdp_base(udp));
+
+ addr = g_new0(SocketAddressLegacy, 1);
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
+ addr->u.inet.data = g_new(InetSocketAddress, 1);
+ *addr->u.inet.data = (InetSocketAddress) {
+ .host = g_strdup(host),
+ .port = g_strdup(port),
+ .has_ipv4 = qemu_opt_get(opts, "ipv4"),
+ .ipv4 = qemu_opt_get_bool(opts, "ipv4", 0),
+ .has_ipv6 = qemu_opt_get(opts, "ipv6"),
+ .ipv6 = qemu_opt_get_bool(opts, "ipv6", 0),
+ };
+ udp->remote = addr;
+
+ if (has_local) {
+ udp->has_local = true;
+ addr = g_new0(SocketAddressLegacy, 1);
+ addr->type = SOCKET_ADDRESS_TYPE_INET;
+ addr->u.inet.data = g_new(InetSocketAddress, 1);
+ *addr->u.inet.data = (InetSocketAddress) {
+ .host = g_strdup(localaddr),
+ .port = g_strdup(localport),
+ };
+ udp->local = addr;
+ }
+}
+
+static void qmp_chardev_open_udp(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevUdp *udp = backend->u.udp.data;
+ SocketAddress *local_addr = socket_address_flatten(udp->local);
+ SocketAddress *remote_addr = socket_address_flatten(udp->remote);
+ QIOChannelSocket *sioc = qio_channel_socket_new();
+ char *name;
+ UdpChardev *s = UDP_CHARDEV(chr);
+ int ret;
+
+ ret = qio_channel_socket_dgram_sync(sioc, local_addr, remote_addr, errp);
+ qapi_free_SocketAddress(local_addr);
+ qapi_free_SocketAddress(remote_addr);
+ if (ret < 0) {
+ object_unref(OBJECT(sioc));
+ return;
+ }
+
+ name = g_strdup_printf("chardev-udp-%s", chr->label);
+ qio_channel_set_name(QIO_CHANNEL(sioc), name);
+ g_free(name);
+
+ s->ioc = QIO_CHANNEL(sioc);
+ /* be isn't opened until we get a connection */
+ *be_opened = false;
+}
+
+static void char_udp_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_udp;
+ cc->open = qmp_chardev_open_udp;
+ cc->chr_write = udp_chr_write;
+ cc->chr_update_read_handler = udp_chr_update_read_handler;
+}
+
+static const TypeInfo char_udp_type_info = {
+ .name = TYPE_CHARDEV_UDP,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(UdpChardev),
+ .instance_finalize = char_udp_finalize,
+ .class_init = char_udp_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_udp_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-win-stdio.c b/chardev/char-win-stdio.c
new file mode 100644
index 000000000..a4771ab82
--- /dev/null
+++ b/chardev/char-win-stdio.c
@@ -0,0 +1,271 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qapi/error.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "chardev/char-win.h"
+#include "chardev/char-win-stdio.h"
+#include "qom/object.h"
+
+struct WinStdioChardev {
+ Chardev parent;
+ HANDLE hStdIn;
+ HANDLE hInputReadyEvent;
+ HANDLE hInputDoneEvent;
+ HANDLE hInputThread;
+ uint8_t win_stdio_buf;
+};
+typedef struct WinStdioChardev WinStdioChardev;
+
+DECLARE_INSTANCE_CHECKER(WinStdioChardev, WIN_STDIO_CHARDEV,
+ TYPE_CHARDEV_WIN_STDIO)
+
+static void win_stdio_wait_func(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
+ INPUT_RECORD buf[4];
+ int ret;
+ DWORD dwSize;
+ int i;
+
+ ret = ReadConsoleInput(stdio->hStdIn, buf, ARRAY_SIZE(buf), &dwSize);
+
+ if (!ret) {
+ /* Avoid error storm */
+ qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
+ return;
+ }
+
+ for (i = 0; i < dwSize; i++) {
+ KEY_EVENT_RECORD *kev = &buf[i].Event.KeyEvent;
+
+ if (buf[i].EventType == KEY_EVENT && kev->bKeyDown) {
+ int j;
+ if (kev->uChar.AsciiChar != 0) {
+ for (j = 0; j < kev->wRepeatCount; j++) {
+ if (qemu_chr_be_can_write(chr)) {
+ uint8_t c = kev->uChar.AsciiChar;
+ qemu_chr_be_write(chr, &c, 1);
+ }
+ }
+ }
+ }
+ }
+}
+
+static DWORD WINAPI win_stdio_thread(LPVOID param)
+{
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(param);
+ int ret;
+ DWORD dwSize;
+
+ while (1) {
+
+ /* Wait for one byte */
+ ret = ReadFile(stdio->hStdIn, &stdio->win_stdio_buf, 1, &dwSize, NULL);
+
+ /* Exit in case of error, continue if nothing read */
+ if (!ret) {
+ break;
+ }
+ if (!dwSize) {
+ continue;
+ }
+
+ /* Some terminal emulator returns \r\n for Enter, just pass \n */
+ if (stdio->win_stdio_buf == '\r') {
+ continue;
+ }
+
+ /* Signal the main thread and wait until the byte was eaten */
+ if (!SetEvent(stdio->hInputReadyEvent)) {
+ break;
+ }
+ if (WaitForSingleObject(stdio->hInputDoneEvent, INFINITE)
+ != WAIT_OBJECT_0) {
+ break;
+ }
+ }
+
+ qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
+ return 0;
+}
+
+static void win_stdio_thread_wait_func(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(opaque);
+
+ if (qemu_chr_be_can_write(chr)) {
+ qemu_chr_be_write(chr, &stdio->win_stdio_buf, 1);
+ }
+
+ SetEvent(stdio->hInputDoneEvent);
+}
+
+static void qemu_chr_set_echo_win_stdio(Chardev *chr, bool echo)
+{
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
+ DWORD dwMode = 0;
+
+ GetConsoleMode(stdio->hStdIn, &dwMode);
+
+ if (echo) {
+ SetConsoleMode(stdio->hStdIn, dwMode | ENABLE_ECHO_INPUT);
+ } else {
+ SetConsoleMode(stdio->hStdIn, dwMode & ~ENABLE_ECHO_INPUT);
+ }
+}
+
+static void qemu_chr_open_stdio(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(chr);
+ DWORD dwMode;
+ int is_console = 0;
+
+ stdio->hStdIn = GetStdHandle(STD_INPUT_HANDLE);
+ if (stdio->hStdIn == INVALID_HANDLE_VALUE) {
+ error_setg(errp, "cannot open stdio: invalid handle");
+ return;
+ }
+
+ is_console = GetConsoleMode(stdio->hStdIn, &dwMode) != 0;
+
+ if (is_console) {
+ if (qemu_add_wait_object(stdio->hStdIn,
+ win_stdio_wait_func, chr)) {
+ error_setg(errp, "qemu_add_wait_object: failed");
+ goto err1;
+ }
+ } else {
+ DWORD dwId;
+
+ stdio->hInputReadyEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ stdio->hInputDoneEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+ if (stdio->hInputReadyEvent == INVALID_HANDLE_VALUE
+ || stdio->hInputDoneEvent == INVALID_HANDLE_VALUE) {
+ error_setg(errp, "cannot create event");
+ goto err2;
+ }
+ if (qemu_add_wait_object(stdio->hInputReadyEvent,
+ win_stdio_thread_wait_func, chr)) {
+ error_setg(errp, "qemu_add_wait_object: failed");
+ goto err2;
+ }
+ stdio->hInputThread = CreateThread(NULL, 0, win_stdio_thread,
+ chr, 0, &dwId);
+
+ if (stdio->hInputThread == INVALID_HANDLE_VALUE) {
+ error_setg(errp, "cannot create stdio thread");
+ goto err3;
+ }
+ }
+
+ dwMode |= ENABLE_LINE_INPUT;
+
+ if (is_console) {
+ /* set the terminal in raw mode */
+ /* ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS */
+ dwMode |= ENABLE_PROCESSED_INPUT;
+ }
+
+ SetConsoleMode(stdio->hStdIn, dwMode);
+
+ qemu_chr_set_echo_win_stdio(chr, false);
+
+ return;
+
+err3:
+ qemu_del_wait_object(stdio->hInputReadyEvent, NULL, NULL);
+err2:
+ CloseHandle(stdio->hInputReadyEvent);
+ CloseHandle(stdio->hInputDoneEvent);
+err1:
+ qemu_del_wait_object(stdio->hStdIn, NULL, NULL);
+}
+
+static void char_win_stdio_finalize(Object *obj)
+{
+ WinStdioChardev *stdio = WIN_STDIO_CHARDEV(obj);
+
+ if (stdio->hInputReadyEvent != INVALID_HANDLE_VALUE) {
+ CloseHandle(stdio->hInputReadyEvent);
+ }
+ if (stdio->hInputDoneEvent != INVALID_HANDLE_VALUE) {
+ CloseHandle(stdio->hInputDoneEvent);
+ }
+ if (stdio->hInputThread != INVALID_HANDLE_VALUE) {
+ TerminateThread(stdio->hInputThread, 0);
+ }
+}
+
+static int win_stdio_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
+ DWORD dwSize;
+ int len1;
+
+ len1 = len;
+
+ while (len1 > 0) {
+ if (!WriteFile(hStdOut, buf, len1, &dwSize, NULL)) {
+ break;
+ }
+ buf += dwSize;
+ len1 -= dwSize;
+ }
+
+ return len - len1;
+}
+
+static void char_win_stdio_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = qemu_chr_open_stdio;
+ cc->chr_write = win_stdio_write;
+ cc->chr_set_echo = qemu_chr_set_echo_win_stdio;
+}
+
+static const TypeInfo char_win_stdio_type_info = {
+ .name = TYPE_CHARDEV_WIN_STDIO,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(WinStdioChardev),
+ .instance_finalize = char_win_stdio_finalize,
+ .class_init = char_win_stdio_class_init,
+ .abstract = true,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_win_stdio_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char-win.c b/chardev/char-win.c
new file mode 100644
index 000000000..d4fb44c4d
--- /dev/null
+++ b/chardev/char-win.c
@@ -0,0 +1,244 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "qemu/module.h"
+#include "qapi/error.h"
+#include "chardev/char-win.h"
+
+static void win_chr_read(Chardev *chr, DWORD len)
+{
+ WinChardev *s = WIN_CHARDEV(chr);
+ int max_size = qemu_chr_be_can_write(chr);
+ int ret, err;
+ uint8_t buf[CHR_READ_BUF_LEN];
+ DWORD size;
+
+ if (len > max_size) {
+ len = max_size;
+ }
+ if (len == 0) {
+ return;
+ }
+
+ ZeroMemory(&s->orecv, sizeof(s->orecv));
+ s->orecv.hEvent = s->hrecv;
+ ret = ReadFile(s->file, buf, len, &size, &s->orecv);
+ if (!ret) {
+ err = GetLastError();
+ if (err == ERROR_IO_PENDING) {
+ ret = GetOverlappedResult(s->file, &s->orecv, &size, TRUE);
+ }
+ }
+
+ if (size > 0) {
+ qemu_chr_be_write(chr, buf, size);
+ }
+}
+
+static int win_chr_serial_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ WinChardev *s = WIN_CHARDEV(opaque);
+ COMSTAT status;
+ DWORD comerr;
+
+ ClearCommError(s->file, &comerr, &status);
+ if (status.cbInQue > 0) {
+ win_chr_read(chr, status.cbInQue);
+ return 1;
+ }
+ return 0;
+}
+
+int win_chr_serial_init(Chardev *chr, const char *filename, Error **errp)
+{
+ WinChardev *s = WIN_CHARDEV(chr);
+ COMMCONFIG comcfg;
+ COMMTIMEOUTS cto = { 0, 0, 0, 0, 0};
+ COMSTAT comstat;
+ DWORD size;
+ DWORD err;
+
+ s->hsend = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!s->hsend) {
+ error_setg(errp, "Failed CreateEvent");
+ goto fail;
+ }
+ s->hrecv = CreateEvent(NULL, TRUE, FALSE, NULL);
+ if (!s->hrecv) {
+ error_setg(errp, "Failed CreateEvent");
+ goto fail;
+ }
+
+ s->file = CreateFile(filename, GENERIC_READ | GENERIC_WRITE, 0, NULL,
+ OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
+ if (s->file == INVALID_HANDLE_VALUE) {
+ error_setg_win32(errp, GetLastError(), "Failed CreateFile");
+ s->file = NULL;
+ goto fail;
+ }
+
+ if (!SetupComm(s->file, NRECVBUF, NSENDBUF)) {
+ error_setg(errp, "Failed SetupComm");
+ goto fail;
+ }
+
+ ZeroMemory(&comcfg, sizeof(COMMCONFIG));
+ size = sizeof(COMMCONFIG);
+ GetDefaultCommConfig(filename, &comcfg, &size);
+ comcfg.dcb.DCBlength = sizeof(DCB);
+ CommConfigDialog(filename, NULL, &comcfg);
+
+ if (!SetCommState(s->file, &comcfg.dcb)) {
+ error_setg(errp, "Failed SetCommState");
+ goto fail;
+ }
+
+ if (!SetCommMask(s->file, EV_ERR)) {
+ error_setg(errp, "Failed SetCommMask");
+ goto fail;
+ }
+
+ cto.ReadIntervalTimeout = MAXDWORD;
+ if (!SetCommTimeouts(s->file, &cto)) {
+ error_setg(errp, "Failed SetCommTimeouts");
+ goto fail;
+ }
+
+ if (!ClearCommError(s->file, &err, &comstat)) {
+ error_setg(errp, "Failed ClearCommError");
+ goto fail;
+ }
+ qemu_add_polling_cb(win_chr_serial_poll, chr);
+ return 0;
+
+ fail:
+ return -1;
+}
+
+int win_chr_pipe_poll(void *opaque)
+{
+ Chardev *chr = CHARDEV(opaque);
+ WinChardev *s = WIN_CHARDEV(opaque);
+ DWORD size;
+
+ PeekNamedPipe(s->file, NULL, 0, NULL, &size, NULL);
+ if (size > 0) {
+ win_chr_read(chr, size);
+ return 1;
+ }
+ return 0;
+}
+
+/* Called with chr_write_lock held. */
+static int win_chr_write(Chardev *chr, const uint8_t *buf, int len1)
+{
+ WinChardev *s = WIN_CHARDEV(chr);
+ DWORD len, ret, size, err;
+
+ len = len1;
+ ZeroMemory(&s->osend, sizeof(s->osend));
+ s->osend.hEvent = s->hsend;
+ while (len > 0) {
+ if (s->hsend) {
+ ret = WriteFile(s->file, buf, len, &size, &s->osend);
+ } else {
+ ret = WriteFile(s->file, buf, len, &size, NULL);
+ }
+ if (!ret) {
+ err = GetLastError();
+ if (err == ERROR_IO_PENDING) {
+ ret = GetOverlappedResult(s->file, &s->osend, &size, TRUE);
+ if (ret) {
+ buf += size;
+ len -= size;
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ } else {
+ buf += size;
+ len -= size;
+ }
+ }
+ return len1 - len;
+}
+
+static void char_win_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+ WinChardev *s = WIN_CHARDEV(chr);
+
+ if (s->hsend) {
+ CloseHandle(s->hsend);
+ }
+ if (s->hrecv) {
+ CloseHandle(s->hrecv);
+ }
+ if (!s->keep_open && s->file) {
+ CloseHandle(s->file);
+ }
+ if (s->fpipe) {
+ qemu_del_polling_cb(win_chr_pipe_poll, chr);
+ } else {
+ qemu_del_polling_cb(win_chr_serial_poll, chr);
+ }
+
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+}
+
+void win_chr_set_file(Chardev *chr, HANDLE file, bool keep_open)
+{
+ WinChardev *s = WIN_CHARDEV(chr);
+
+ s->keep_open = keep_open;
+ s->file = file;
+}
+
+static void char_win_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->chr_write = win_chr_write;
+}
+
+static const TypeInfo char_win_type_info = {
+ .name = TYPE_CHARDEV_WIN,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(WinChardev),
+ .instance_finalize = char_win_finalize,
+ .class_init = char_win_class_init,
+ .abstract = true,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_win_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/char.c b/chardev/char.c
new file mode 100644
index 000000000..0169d8dde
--- /dev/null
+++ b/chardev/char.c
@@ -0,0 +1,1230 @@
+/*
+ * QEMU System Emulator
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/cutils.h"
+#include "monitor/monitor.h"
+#include "qemu/config-file.h"
+#include "qemu/error-report.h"
+#include "qemu/qemu-print.h"
+#include "chardev/char.h"
+#include "qapi/error.h"
+#include "qapi/qapi-commands-char.h"
+#include "qapi/qmp/qerror.h"
+#include "sysemu/replay.h"
+#include "qemu/help_option.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include "qemu/id.h"
+#include "qemu/coroutine.h"
+#include "qemu/yank.h"
+
+#include "chardev-internal.h"
+
+/***********************************************************/
+/* character device */
+
+Object *get_chardevs_root(void)
+{
+ return container_get(object_get_root(), "/chardevs");
+}
+
+static void chr_be_event(Chardev *s, QEMUChrEvent event)
+{
+ CharBackend *be = s->be;
+
+ if (!be || !be->chr_event) {
+ return;
+ }
+
+ be->chr_event(be->opaque, event);
+}
+
+void qemu_chr_be_event(Chardev *s, QEMUChrEvent event)
+{
+ /* Keep track if the char device is open */
+ switch (event) {
+ case CHR_EVENT_OPENED:
+ s->be_open = 1;
+ break;
+ case CHR_EVENT_CLOSED:
+ s->be_open = 0;
+ break;
+ case CHR_EVENT_BREAK:
+ case CHR_EVENT_MUX_IN:
+ case CHR_EVENT_MUX_OUT:
+ /* Ignore */
+ break;
+ }
+
+ CHARDEV_GET_CLASS(s)->chr_be_event(s, event);
+}
+
+/* Not reporting errors from writing to logfile, as logs are
+ * defined to be "best effort" only */
+static void qemu_chr_write_log(Chardev *s, const uint8_t *buf, size_t len)
+{
+ size_t done = 0;
+ ssize_t ret;
+
+ if (s->logfd < 0) {
+ return;
+ }
+
+ while (done < len) {
+ retry:
+ ret = write(s->logfd, buf + done, len - done);
+ if (ret == -1 && errno == EAGAIN) {
+ g_usleep(100);
+ goto retry;
+ }
+
+ if (ret <= 0) {
+ return;
+ }
+ done += ret;
+ }
+}
+
+static int qemu_chr_write_buffer(Chardev *s,
+ const uint8_t *buf, int len,
+ int *offset, bool write_all)
+{
+ ChardevClass *cc = CHARDEV_GET_CLASS(s);
+ int res = 0;
+ *offset = 0;
+
+ qemu_mutex_lock(&s->chr_write_lock);
+ while (*offset < len) {
+ retry:
+ res = cc->chr_write(s, buf + *offset, len - *offset);
+ if (res < 0 && errno == EAGAIN && write_all) {
+ if (qemu_in_coroutine()) {
+ qemu_co_sleep_ns(QEMU_CLOCK_REALTIME, 100000);
+ } else {
+ g_usleep(100);
+ }
+ goto retry;
+ }
+
+ if (res <= 0) {
+ break;
+ }
+
+ *offset += res;
+ if (!write_all) {
+ break;
+ }
+ }
+ if (*offset > 0) {
+ /*
+ * If some data was written by backend, we should
+ * only log what was actually written. This method
+ * may be invoked again to write the remaining
+ * method, thus we'll log the remainder at that time.
+ */
+ qemu_chr_write_log(s, buf, *offset);
+ } else if (res < 0) {
+ /*
+ * If a fatal error was reported by the backend,
+ * assume this method won't be invoked again with
+ * this buffer, so log it all right away.
+ */
+ qemu_chr_write_log(s, buf, len);
+ }
+ qemu_mutex_unlock(&s->chr_write_lock);
+
+ return res;
+}
+
+int qemu_chr_write(Chardev *s, const uint8_t *buf, int len, bool write_all)
+{
+ int offset = 0;
+ int res;
+
+ if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_PLAY) {
+ replay_char_write_event_load(&res, &offset);
+ assert(offset <= len);
+ qemu_chr_write_buffer(s, buf, offset, &offset, true);
+ return res;
+ }
+
+ res = qemu_chr_write_buffer(s, buf, len, &offset, write_all);
+
+ if (qemu_chr_replay(s) && replay_mode == REPLAY_MODE_RECORD) {
+ replay_char_write_event_save(res, offset);
+ }
+
+ if (res < 0) {
+ return res;
+ }
+ return offset;
+}
+
+int qemu_chr_be_can_write(Chardev *s)
+{
+ CharBackend *be = s->be;
+
+ if (!be || !be->chr_can_read) {
+ return 0;
+ }
+
+ return be->chr_can_read(be->opaque);
+}
+
+void qemu_chr_be_write_impl(Chardev *s, uint8_t *buf, int len)
+{
+ CharBackend *be = s->be;
+
+ if (be && be->chr_read) {
+ be->chr_read(be->opaque, buf, len);
+ }
+}
+
+void qemu_chr_be_write(Chardev *s, uint8_t *buf, int len)
+{
+ if (qemu_chr_replay(s)) {
+ if (replay_mode == REPLAY_MODE_PLAY) {
+ return;
+ }
+ replay_chr_be_write(s, buf, len);
+ } else {
+ qemu_chr_be_write_impl(s, buf, len);
+ }
+}
+
+void qemu_chr_be_update_read_handlers(Chardev *s,
+ GMainContext *context)
+{
+ ChardevClass *cc = CHARDEV_GET_CLASS(s);
+
+ assert(qemu_chr_has_feature(s, QEMU_CHAR_FEATURE_GCONTEXT)
+ || !context);
+ s->gcontext = context;
+ if (cc->chr_update_read_handler) {
+ cc->chr_update_read_handler(s);
+ }
+}
+
+int qemu_chr_add_client(Chardev *s, int fd)
+{
+ return CHARDEV_GET_CLASS(s)->chr_add_client ?
+ CHARDEV_GET_CLASS(s)->chr_add_client(s, fd) : -1;
+}
+
+static void qemu_char_open(Chardev *chr, ChardevBackend *backend,
+ bool *be_opened, Error **errp)
+{
+ ChardevClass *cc = CHARDEV_GET_CLASS(chr);
+ /* Any ChardevCommon member would work */
+ ChardevCommon *common = backend ? backend->u.null.data : NULL;
+
+ if (common && common->has_logfile) {
+ int flags = O_WRONLY;
+ if (common->has_logappend &&
+ common->logappend) {
+ flags |= O_APPEND;
+ } else {
+ flags |= O_TRUNC;
+ }
+ chr->logfd = qemu_create(common->logfile, flags, 0666, errp);
+ if (chr->logfd < 0) {
+ return;
+ }
+ }
+
+ if (cc->open) {
+ cc->open(chr, backend, be_opened, errp);
+ }
+}
+
+static void char_init(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+
+ chr->handover_yank_instance = false;
+ chr->logfd = -1;
+ qemu_mutex_init(&chr->chr_write_lock);
+
+ /*
+ * Assume if chr_update_read_handler is implemented it will
+ * take the updated gcontext into account.
+ */
+ if (CHARDEV_GET_CLASS(chr)->chr_update_read_handler) {
+ qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_GCONTEXT);
+ }
+
+}
+
+static int null_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ return len;
+}
+
+static void char_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->chr_write = null_chr_write;
+ cc->chr_be_event = chr_be_event;
+}
+
+static void char_finalize(Object *obj)
+{
+ Chardev *chr = CHARDEV(obj);
+
+ if (chr->be) {
+ chr->be->chr = NULL;
+ }
+ g_free(chr->filename);
+ g_free(chr->label);
+ if (chr->logfd != -1) {
+ close(chr->logfd);
+ }
+ qemu_mutex_destroy(&chr->chr_write_lock);
+}
+
+static const TypeInfo char_type_info = {
+ .name = TYPE_CHARDEV,
+ .parent = TYPE_OBJECT,
+ .instance_size = sizeof(Chardev),
+ .instance_init = char_init,
+ .instance_finalize = char_finalize,
+ .abstract = true,
+ .class_size = sizeof(ChardevClass),
+ .class_init = char_class_init,
+};
+
+static bool qemu_chr_is_busy(Chardev *s)
+{
+ if (CHARDEV_IS_MUX(s)) {
+ MuxChardev *d = MUX_CHARDEV(s);
+ return d->mux_cnt >= 0;
+ } else {
+ return s->be != NULL;
+ }
+}
+
+int qemu_chr_wait_connected(Chardev *chr, Error **errp)
+{
+ ChardevClass *cc = CHARDEV_GET_CLASS(chr);
+
+ if (cc->chr_wait_connected) {
+ return cc->chr_wait_connected(chr, errp);
+ }
+
+ return 0;
+}
+
+QemuOpts *qemu_chr_parse_compat(const char *label, const char *filename,
+ bool permit_mux_mon)
+{
+ char host[65], port[33], width[8], height[8];
+ int pos;
+ const char *p;
+ QemuOpts *opts;
+ Error *local_err = NULL;
+
+ opts = qemu_opts_create(qemu_find_opts("chardev"), label, 1, &local_err);
+ if (local_err) {
+ error_report_err(local_err);
+ return NULL;
+ }
+
+ if (strstart(filename, "mon:", &p)) {
+ if (!permit_mux_mon) {
+ error_report("mon: isn't supported in this context");
+ return NULL;
+ }
+ filename = p;
+ qemu_opt_set(opts, "mux", "on", &error_abort);
+ if (strcmp(filename, "stdio") == 0) {
+ /* Monitor is muxed to stdio: do not exit on Ctrl+C by default
+ * but pass it to the guest. Handle this only for compat syntax,
+ * for -chardev syntax we have special option for this.
+ * This is what -nographic did, redirecting+muxing serial+monitor
+ * to stdio causing Ctrl+C to be passed to guest. */
+ qemu_opt_set(opts, "signal", "off", &error_abort);
+ }
+ }
+
+ if (strcmp(filename, "null") == 0 ||
+ strcmp(filename, "pty") == 0 ||
+ strcmp(filename, "msmouse") == 0 ||
+ strcmp(filename, "wctablet") == 0 ||
+ strcmp(filename, "braille") == 0 ||
+ strcmp(filename, "testdev") == 0 ||
+ strcmp(filename, "stdio") == 0) {
+ qemu_opt_set(opts, "backend", filename, &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "vc", &p)) {
+ qemu_opt_set(opts, "backend", "vc", &error_abort);
+ if (*p == ':') {
+ if (sscanf(p+1, "%7[0-9]x%7[0-9]", width, height) == 2) {
+ /* pixels */
+ qemu_opt_set(opts, "width", width, &error_abort);
+ qemu_opt_set(opts, "height", height, &error_abort);
+ } else if (sscanf(p+1, "%7[0-9]Cx%7[0-9]C", width, height) == 2) {
+ /* chars */
+ qemu_opt_set(opts, "cols", width, &error_abort);
+ qemu_opt_set(opts, "rows", height, &error_abort);
+ } else {
+ goto fail;
+ }
+ }
+ return opts;
+ }
+ if (strcmp(filename, "con:") == 0) {
+ qemu_opt_set(opts, "backend", "console", &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "COM", NULL)) {
+ qemu_opt_set(opts, "backend", "serial", &error_abort);
+ qemu_opt_set(opts, "path", filename, &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "file:", &p)) {
+ qemu_opt_set(opts, "backend", "file", &error_abort);
+ qemu_opt_set(opts, "path", p, &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "pipe:", &p)) {
+ qemu_opt_set(opts, "backend", "pipe", &error_abort);
+ qemu_opt_set(opts, "path", p, &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "tcp:", &p) ||
+ strstart(filename, "telnet:", &p) ||
+ strstart(filename, "tn3270:", &p) ||
+ strstart(filename, "websocket:", &p)) {
+ if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) {
+ host[0] = 0;
+ if (sscanf(p, ":%32[^,]%n", port, &pos) < 1)
+ goto fail;
+ }
+ qemu_opt_set(opts, "backend", "socket", &error_abort);
+ qemu_opt_set(opts, "host", host, &error_abort);
+ qemu_opt_set(opts, "port", port, &error_abort);
+ if (p[pos] == ',') {
+ if (!qemu_opts_do_parse(opts, p + pos + 1, NULL, &local_err)) {
+ error_report_err(local_err);
+ goto fail;
+ }
+ }
+ if (strstart(filename, "telnet:", &p)) {
+ qemu_opt_set(opts, "telnet", "on", &error_abort);
+ } else if (strstart(filename, "tn3270:", &p)) {
+ qemu_opt_set(opts, "tn3270", "on", &error_abort);
+ } else if (strstart(filename, "websocket:", &p)) {
+ qemu_opt_set(opts, "websocket", "on", &error_abort);
+ }
+ return opts;
+ }
+ if (strstart(filename, "udp:", &p)) {
+ qemu_opt_set(opts, "backend", "udp", &error_abort);
+ if (sscanf(p, "%64[^:]:%32[^@,]%n", host, port, &pos) < 2) {
+ host[0] = 0;
+ if (sscanf(p, ":%32[^@,]%n", port, &pos) < 1) {
+ goto fail;
+ }
+ }
+ qemu_opt_set(opts, "host", host, &error_abort);
+ qemu_opt_set(opts, "port", port, &error_abort);
+ if (p[pos] == '@') {
+ p += pos + 1;
+ if (sscanf(p, "%64[^:]:%32[^,]%n", host, port, &pos) < 2) {
+ host[0] = 0;
+ if (sscanf(p, ":%32[^,]%n", port, &pos) < 1) {
+ goto fail;
+ }
+ }
+ qemu_opt_set(opts, "localaddr", host, &error_abort);
+ qemu_opt_set(opts, "localport", port, &error_abort);
+ }
+ return opts;
+ }
+ if (strstart(filename, "unix:", &p)) {
+ qemu_opt_set(opts, "backend", "socket", &error_abort);
+ if (!qemu_opts_do_parse(opts, p, "path", &local_err)) {
+ error_report_err(local_err);
+ goto fail;
+ }
+ return opts;
+ }
+ if (strstart(filename, "/dev/parport", NULL) ||
+ strstart(filename, "/dev/ppi", NULL)) {
+ qemu_opt_set(opts, "backend", "parallel", &error_abort);
+ qemu_opt_set(opts, "path", filename, &error_abort);
+ return opts;
+ }
+ if (strstart(filename, "/dev/", NULL)) {
+ qemu_opt_set(opts, "backend", "serial", &error_abort);
+ qemu_opt_set(opts, "path", filename, &error_abort);
+ return opts;
+ }
+
+ error_report("'%s' is not a valid char driver", filename);
+
+fail:
+ qemu_opts_del(opts);
+ return NULL;
+}
+
+void qemu_chr_parse_common(QemuOpts *opts, ChardevCommon *backend)
+{
+ const char *logfile = qemu_opt_get(opts, "logfile");
+
+ backend->has_logfile = logfile != NULL;
+ backend->logfile = g_strdup(logfile);
+
+ backend->has_logappend = true;
+ backend->logappend = qemu_opt_get_bool(opts, "logappend", false);
+}
+
+static const ChardevClass *char_get_class(const char *driver, Error **errp)
+{
+ ObjectClass *oc;
+ const ChardevClass *cc;
+ char *typename = g_strdup_printf("chardev-%s", driver);
+
+ oc = module_object_class_by_name(typename);
+ g_free(typename);
+
+ if (!object_class_dynamic_cast(oc, TYPE_CHARDEV)) {
+ error_setg(errp, "'%s' is not a valid char driver name", driver);
+ return NULL;
+ }
+
+ if (object_class_is_abstract(oc)) {
+ error_setg(errp, QERR_INVALID_PARAMETER_VALUE, "driver",
+ "an abstract device type");
+ return NULL;
+ }
+
+ cc = CHARDEV_CLASS(oc);
+ if (cc->internal) {
+ error_setg(errp, "'%s' is not a valid char driver name", driver);
+ return NULL;
+ }
+
+ return cc;
+}
+
+static struct ChardevAlias {
+ const char *typename;
+ const char *alias;
+ bool deprecation_warning_printed;
+} chardev_alias_table[] = {
+#ifdef HAVE_CHARDEV_PARPORT
+ { "parallel", "parport" },
+#endif
+#ifdef HAVE_CHARDEV_SERIAL
+ { "serial", "tty" },
+#endif
+};
+
+typedef struct ChadevClassFE {
+ void (*fn)(const char *name, void *opaque);
+ void *opaque;
+} ChadevClassFE;
+
+static void
+chardev_class_foreach(ObjectClass *klass, void *opaque)
+{
+ ChadevClassFE *fe = opaque;
+
+ assert(g_str_has_prefix(object_class_get_name(klass), "chardev-"));
+ if (CHARDEV_CLASS(klass)->internal) {
+ return;
+ }
+
+ fe->fn(object_class_get_name(klass) + 8, fe->opaque);
+}
+
+static void
+chardev_name_foreach(void (*fn)(const char *name, void *opaque),
+ void *opaque)
+{
+ ChadevClassFE fe = { .fn = fn, .opaque = opaque };
+
+ object_class_foreach(chardev_class_foreach, TYPE_CHARDEV, false, &fe);
+}
+
+static void
+help_string_append(const char *name, void *opaque)
+{
+ GString *str = opaque;
+
+ g_string_append_printf(str, "\n %s", name);
+}
+
+static const char *chardev_alias_translate(const char *name)
+{
+ int i;
+ for (i = 0; i < (int)ARRAY_SIZE(chardev_alias_table); i++) {
+ if (g_strcmp0(chardev_alias_table[i].alias, name) == 0) {
+ if (!chardev_alias_table[i].deprecation_warning_printed) {
+ warn_report("The alias '%s' is deprecated, use '%s' instead",
+ name, chardev_alias_table[i].typename);
+ chardev_alias_table[i].deprecation_warning_printed = true;
+ }
+ return chardev_alias_table[i].typename;
+ }
+ }
+ return name;
+}
+
+ChardevBackend *qemu_chr_parse_opts(QemuOpts *opts, Error **errp)
+{
+ Error *local_err = NULL;
+ const ChardevClass *cc;
+ ChardevBackend *backend = NULL;
+ const char *name = chardev_alias_translate(qemu_opt_get(opts, "backend"));
+
+ if (name == NULL) {
+ error_setg(errp, "chardev: \"%s\" missing backend",
+ qemu_opts_id(opts));
+ return NULL;
+ }
+
+ cc = char_get_class(name, errp);
+ if (cc == NULL) {
+ return NULL;
+ }
+
+ backend = g_new0(ChardevBackend, 1);
+ backend->type = CHARDEV_BACKEND_KIND_NULL;
+
+ if (cc->parse) {
+ cc->parse(opts, backend, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ qapi_free_ChardevBackend(backend);
+ return NULL;
+ }
+ } else {
+ ChardevCommon *ccom = g_new0(ChardevCommon, 1);
+ qemu_chr_parse_common(opts, ccom);
+ backend->u.null.data = ccom; /* Any ChardevCommon member would work */
+ }
+
+ return backend;
+}
+
+Chardev *qemu_chr_new_from_opts(QemuOpts *opts, GMainContext *context,
+ Error **errp)
+{
+ const ChardevClass *cc;
+ Chardev *chr = NULL;
+ ChardevBackend *backend = NULL;
+ const char *name = chardev_alias_translate(qemu_opt_get(opts, "backend"));
+ const char *id = qemu_opts_id(opts);
+ char *bid = NULL;
+
+ if (name && is_help_option(name)) {
+ GString *str = g_string_new("");
+
+ chardev_name_foreach(help_string_append, str);
+
+ qemu_printf("Available chardev backend types: %s\n", str->str);
+ g_string_free(str, true);
+ return NULL;
+ }
+
+ if (id == NULL) {
+ error_setg(errp, "chardev: no id specified");
+ return NULL;
+ }
+
+ backend = qemu_chr_parse_opts(opts, errp);
+ if (backend == NULL) {
+ return NULL;
+ }
+
+ cc = char_get_class(name, errp);
+ if (cc == NULL) {
+ goto out;
+ }
+
+ if (qemu_opt_get_bool(opts, "mux", 0)) {
+ bid = g_strdup_printf("%s-base", id);
+ }
+
+ chr = qemu_chardev_new(bid ? bid : id,
+ object_class_get_name(OBJECT_CLASS(cc)),
+ backend, context, errp);
+
+ if (chr == NULL) {
+ goto out;
+ }
+
+ if (bid) {
+ Chardev *mux;
+ qapi_free_ChardevBackend(backend);
+ backend = g_new0(ChardevBackend, 1);
+ backend->type = CHARDEV_BACKEND_KIND_MUX;
+ backend->u.mux.data = g_new0(ChardevMux, 1);
+ backend->u.mux.data->chardev = g_strdup(bid);
+ mux = qemu_chardev_new(id, TYPE_CHARDEV_MUX, backend, context, errp);
+ if (mux == NULL) {
+ object_unparent(OBJECT(chr));
+ chr = NULL;
+ goto out;
+ }
+ chr = mux;
+ }
+
+out:
+ qapi_free_ChardevBackend(backend);
+ g_free(bid);
+ return chr;
+}
+
+Chardev *qemu_chr_new_noreplay(const char *label, const char *filename,
+ bool permit_mux_mon, GMainContext *context)
+{
+ const char *p;
+ Chardev *chr;
+ QemuOpts *opts;
+ Error *err = NULL;
+
+ if (strstart(filename, "chardev:", &p)) {
+ return qemu_chr_find(p);
+ }
+
+ opts = qemu_chr_parse_compat(label, filename, permit_mux_mon);
+ if (!opts)
+ return NULL;
+
+ chr = qemu_chr_new_from_opts(opts, context, &err);
+ if (!chr) {
+ error_report_err(err);
+ goto out;
+ }
+
+ if (qemu_opt_get_bool(opts, "mux", 0)) {
+ assert(permit_mux_mon);
+ monitor_init_hmp(chr, true, &err);
+ if (err) {
+ error_report_err(err);
+ object_unparent(OBJECT(chr));
+ chr = NULL;
+ goto out;
+ }
+ }
+
+out:
+ qemu_opts_del(opts);
+ return chr;
+}
+
+static Chardev *qemu_chr_new_permit_mux_mon(const char *label,
+ const char *filename,
+ bool permit_mux_mon,
+ GMainContext *context)
+{
+ Chardev *chr;
+ chr = qemu_chr_new_noreplay(label, filename, permit_mux_mon, context);
+ if (chr) {
+ if (replay_mode != REPLAY_MODE_NONE) {
+ qemu_chr_set_feature(chr, QEMU_CHAR_FEATURE_REPLAY);
+ }
+ if (qemu_chr_replay(chr) && CHARDEV_GET_CLASS(chr)->chr_ioctl) {
+ error_report("Replay: ioctl is not supported "
+ "for serial devices yet");
+ }
+ replay_register_char_driver(chr);
+ }
+ return chr;
+}
+
+Chardev *qemu_chr_new(const char *label, const char *filename,
+ GMainContext *context)
+{
+ return qemu_chr_new_permit_mux_mon(label, filename, false, context);
+}
+
+Chardev *qemu_chr_new_mux_mon(const char *label, const char *filename,
+ GMainContext *context)
+{
+ return qemu_chr_new_permit_mux_mon(label, filename, true, context);
+}
+
+static int qmp_query_chardev_foreach(Object *obj, void *data)
+{
+ Chardev *chr = CHARDEV(obj);
+ ChardevInfoList **list = data;
+ ChardevInfo *value = g_malloc0(sizeof(*value));
+
+ value->label = g_strdup(chr->label);
+ value->filename = g_strdup(chr->filename);
+ value->frontend_open = chr->be && chr->be->fe_open;
+
+ QAPI_LIST_PREPEND(*list, value);
+
+ return 0;
+}
+
+ChardevInfoList *qmp_query_chardev(Error **errp)
+{
+ ChardevInfoList *chr_list = NULL;
+
+ object_child_foreach(get_chardevs_root(),
+ qmp_query_chardev_foreach, &chr_list);
+
+ return chr_list;
+}
+
+static void
+qmp_prepend_backend(const char *name, void *opaque)
+{
+ ChardevBackendInfoList **list = opaque;
+ ChardevBackendInfo *value;
+
+ value = g_new0(ChardevBackendInfo, 1);
+ value->name = g_strdup(name);
+ QAPI_LIST_PREPEND(*list, value);
+}
+
+ChardevBackendInfoList *qmp_query_chardev_backends(Error **errp)
+{
+ ChardevBackendInfoList *backend_list = NULL;
+
+ chardev_name_foreach(qmp_prepend_backend, &backend_list);
+
+ return backend_list;
+}
+
+Chardev *qemu_chr_find(const char *name)
+{
+ Object *obj = object_resolve_path_component(get_chardevs_root(), name);
+
+ return obj ? CHARDEV(obj) : NULL;
+}
+
+QemuOptsList qemu_chardev_opts = {
+ .name = "chardev",
+ .implied_opt_name = "backend",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_chardev_opts.head),
+ .desc = {
+ {
+ .name = "backend",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "path",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "host",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "port",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "fd",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "localaddr",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "localport",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "to",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "ipv4",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "ipv6",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "wait",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "server",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "delay",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "nodelay",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "reconnect",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "telnet",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "tn3270",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "tls-creds",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "tls-authz",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "websocket",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "width",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "height",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "cols",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "rows",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "mux",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "signal",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "name",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "debug",
+ .type = QEMU_OPT_NUMBER,
+ },{
+ .name = "size",
+ .type = QEMU_OPT_SIZE,
+ },{
+ .name = "chardev",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "append",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "logfile",
+ .type = QEMU_OPT_STRING,
+ },{
+ .name = "logappend",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "mouse",
+ .type = QEMU_OPT_BOOL,
+ },{
+ .name = "clipboard",
+ .type = QEMU_OPT_BOOL,
+#ifdef CONFIG_LINUX
+ },{
+ .name = "tight",
+ .type = QEMU_OPT_BOOL,
+ .def_value_str = "on",
+ },{
+ .name = "abstract",
+ .type = QEMU_OPT_BOOL,
+#endif
+ },
+ { /* end of list */ }
+ },
+};
+
+bool qemu_chr_has_feature(Chardev *chr,
+ ChardevFeature feature)
+{
+ return test_bit(feature, chr->features);
+}
+
+void qemu_chr_set_feature(Chardev *chr,
+ ChardevFeature feature)
+{
+ return set_bit(feature, chr->features);
+}
+
+static Chardev *chardev_new(const char *id, const char *typename,
+ ChardevBackend *backend,
+ GMainContext *gcontext,
+ bool handover_yank_instance,
+ Error **errp)
+{
+ Object *obj;
+ Chardev *chr = NULL;
+ Error *local_err = NULL;
+ bool be_opened = true;
+
+ assert(g_str_has_prefix(typename, "chardev-"));
+ assert(id);
+
+ obj = object_new(typename);
+ chr = CHARDEV(obj);
+ chr->handover_yank_instance = handover_yank_instance;
+ chr->label = g_strdup(id);
+ chr->gcontext = gcontext;
+
+ qemu_char_open(chr, backend, &be_opened, &local_err);
+ if (local_err) {
+ error_propagate(errp, local_err);
+ object_unref(obj);
+ return NULL;
+ }
+
+ if (!chr->filename) {
+ chr->filename = g_strdup(typename + 8);
+ }
+ if (be_opened) {
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+ }
+
+ return chr;
+}
+
+Chardev *qemu_chardev_new(const char *id, const char *typename,
+ ChardevBackend *backend,
+ GMainContext *gcontext,
+ Error **errp)
+{
+ Chardev *chr;
+ g_autofree char *genid = NULL;
+
+ if (!id) {
+ genid = id_generate(ID_CHR);
+ id = genid;
+ }
+
+ chr = chardev_new(id, typename, backend, gcontext, false, errp);
+ if (!chr) {
+ return NULL;
+ }
+
+ if (!object_property_try_add_child(get_chardevs_root(), id, OBJECT(chr),
+ errp)) {
+ object_unref(OBJECT(chr));
+ return NULL;
+ }
+ object_unref(OBJECT(chr));
+
+ return chr;
+}
+
+ChardevReturn *qmp_chardev_add(const char *id, ChardevBackend *backend,
+ Error **errp)
+{
+ ERRP_GUARD();
+ const ChardevClass *cc;
+ ChardevReturn *ret;
+ g_autoptr(Chardev) chr = NULL;
+
+ if (qemu_chr_find(id)) {
+ error_setg(errp, "Chardev with id '%s' already exists", id);
+ return NULL;
+ }
+
+ cc = char_get_class(ChardevBackendKind_str(backend->type), errp);
+ if (!cc) {
+ goto err;
+ }
+
+ chr = chardev_new(id, object_class_get_name(OBJECT_CLASS(cc)),
+ backend, NULL, false, errp);
+ if (!chr) {
+ goto err;
+ }
+
+ if (!object_property_try_add_child(get_chardevs_root(), id, OBJECT(chr),
+ errp)) {
+ goto err;
+ }
+
+ ret = g_new0(ChardevReturn, 1);
+ if (CHARDEV_IS_PTY(chr)) {
+ ret->pty = g_strdup(chr->filename + 4);
+ ret->has_pty = true;
+ }
+
+ return ret;
+
+err:
+ error_prepend(errp, "Failed to add chardev '%s': ", id);
+ return NULL;
+}
+
+ChardevReturn *qmp_chardev_change(const char *id, ChardevBackend *backend,
+ Error **errp)
+{
+ CharBackend *be;
+ const ChardevClass *cc, *cc_new;
+ Chardev *chr, *chr_new;
+ bool closed_sent = false;
+ bool handover_yank_instance;
+ ChardevReturn *ret;
+
+ chr = qemu_chr_find(id);
+ if (!chr) {
+ error_setg(errp, "Chardev '%s' does not exist", id);
+ return NULL;
+ }
+
+ if (CHARDEV_IS_MUX(chr)) {
+ error_setg(errp, "Mux device hotswap not supported yet");
+ return NULL;
+ }
+
+ if (qemu_chr_replay(chr)) {
+ error_setg(errp,
+ "Chardev '%s' cannot be changed in record/replay mode", id);
+ return NULL;
+ }
+
+ be = chr->be;
+ if (!be) {
+ /* easy case */
+ object_unparent(OBJECT(chr));
+ return qmp_chardev_add(id, backend, errp);
+ }
+
+ if (!be->chr_be_change) {
+ error_setg(errp, "Chardev user does not support chardev hotswap");
+ return NULL;
+ }
+
+ cc = CHARDEV_GET_CLASS(chr);
+ cc_new = char_get_class(ChardevBackendKind_str(backend->type), errp);
+ if (!cc_new) {
+ return NULL;
+ }
+
+ /*
+ * The new chardev should not register a yank instance if the current
+ * chardev has registered one already.
+ */
+ handover_yank_instance = cc->supports_yank && cc_new->supports_yank;
+
+ chr_new = chardev_new(id, object_class_get_name(OBJECT_CLASS(cc_new)),
+ backend, chr->gcontext, handover_yank_instance, errp);
+ if (!chr_new) {
+ return NULL;
+ }
+
+ if (chr->be_open && !chr_new->be_open) {
+ qemu_chr_be_event(chr, CHR_EVENT_CLOSED);
+ closed_sent = true;
+ }
+
+ chr->be = NULL;
+ qemu_chr_fe_init(be, chr_new, &error_abort);
+
+ if (be->chr_be_change(be->opaque) < 0) {
+ error_setg(errp, "Chardev '%s' change failed", chr_new->label);
+ chr_new->be = NULL;
+ qemu_chr_fe_init(be, chr, &error_abort);
+ if (closed_sent) {
+ qemu_chr_be_event(chr, CHR_EVENT_OPENED);
+ }
+ object_unref(OBJECT(chr_new));
+ return NULL;
+ }
+
+ /* change successfull, clean up */
+ chr_new->handover_yank_instance = false;
+
+ /*
+ * When the old chardev is freed, it should not unregister the yank
+ * instance if the new chardev needs it.
+ */
+ chr->handover_yank_instance = handover_yank_instance;
+
+ object_unparent(OBJECT(chr));
+ object_property_add_child(get_chardevs_root(), chr_new->label,
+ OBJECT(chr_new));
+ object_unref(OBJECT(chr_new));
+
+ ret = g_new0(ChardevReturn, 1);
+ if (CHARDEV_IS_PTY(chr_new)) {
+ ret->pty = g_strdup(chr_new->filename + 4);
+ ret->has_pty = true;
+ }
+
+ return ret;
+}
+
+void qmp_chardev_remove(const char *id, Error **errp)
+{
+ Chardev *chr;
+
+ chr = qemu_chr_find(id);
+ if (chr == NULL) {
+ error_setg(errp, "Chardev '%s' not found", id);
+ return;
+ }
+ if (qemu_chr_is_busy(chr)) {
+ error_setg(errp, "Chardev '%s' is busy", id);
+ return;
+ }
+ if (qemu_chr_replay(chr)) {
+ error_setg(errp,
+ "Chardev '%s' cannot be unplugged in record/replay mode", id);
+ return;
+ }
+ object_unparent(OBJECT(chr));
+}
+
+void qmp_chardev_send_break(const char *id, Error **errp)
+{
+ Chardev *chr;
+
+ chr = qemu_chr_find(id);
+ if (chr == NULL) {
+ error_setg(errp, "Chardev '%s' not found", id);
+ return;
+ }
+ qemu_chr_be_event(chr, CHR_EVENT_BREAK);
+}
+
+/*
+ * Add a timeout callback for the chardev (in milliseconds), return
+ * the GSource object created. Please use this to add timeout hook for
+ * chardev instead of g_timeout_add() and g_timeout_add_seconds(), to
+ * make sure the gcontext that the task bound to is correct.
+ */
+GSource *qemu_chr_timeout_add_ms(Chardev *chr, guint ms,
+ GSourceFunc func, void *private)
+{
+ GSource *source = g_timeout_source_new(ms);
+
+ assert(func);
+ g_source_set_callback(source, func, private, NULL);
+ g_source_attach(source, chr->gcontext);
+
+ return source;
+}
+
+void qemu_chr_cleanup(void)
+{
+ object_unparent(get_chardevs_root());
+}
+
+static void register_types(void)
+{
+ type_register_static(&char_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/chardev-internal.h b/chardev/chardev-internal.h
new file mode 100644
index 000000000..aba024075
--- /dev/null
+++ b/chardev/chardev-internal.h
@@ -0,0 +1,67 @@
+/*
+ * QEMU Character device internals
+ *
+ * Copyright (c) 2003-2008 Fabrice Bellard
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#ifndef CHARDEV_INTERNAL_H
+#define CHARDEV_INTERNAL_H
+
+#include "chardev/char.h"
+#include "chardev/char-fe.h"
+#include "qom/object.h"
+
+#define MAX_MUX 4
+#define MUX_BUFFER_SIZE 32 /* Must be a power of 2. */
+#define MUX_BUFFER_MASK (MUX_BUFFER_SIZE - 1)
+
+struct MuxChardev {
+ Chardev parent;
+ CharBackend *backends[MAX_MUX];
+ CharBackend chr;
+ int focus;
+ int mux_cnt;
+ int term_got_escape;
+ int max_size;
+ /* Intermediate input buffer catches escape sequences even if the
+ currently active device is not accepting any input - but only until it
+ is full as well. */
+ unsigned char buffer[MAX_MUX][MUX_BUFFER_SIZE];
+ int prod[MAX_MUX];
+ int cons[MAX_MUX];
+ int timestamps;
+
+ /* Protected by the Chardev chr_write_lock. */
+ int linestart;
+ int64_t timestamps_start;
+};
+typedef struct MuxChardev MuxChardev;
+
+DECLARE_INSTANCE_CHECKER(MuxChardev, MUX_CHARDEV,
+ TYPE_CHARDEV_MUX)
+#define CHARDEV_IS_MUX(chr) \
+ object_dynamic_cast(OBJECT(chr), TYPE_CHARDEV_MUX)
+
+void mux_set_focus(Chardev *chr, int focus);
+void mux_chr_send_all_event(Chardev *chr, QEMUChrEvent event);
+
+Object *get_chardevs_root(void);
+
+#endif /* CHAR_MUX_H */
diff --git a/chardev/meson.build b/chardev/meson.build
new file mode 100644
index 000000000..325ba2bdb
--- /dev/null
+++ b/chardev/meson.build
@@ -0,0 +1,44 @@
+chardev_ss.add(files(
+ 'char-fe.c',
+ 'char-file.c',
+ 'char-io.c',
+ 'char-mux.c',
+ 'char-null.c',
+ 'char-pipe.c',
+ 'char-ringbuf.c',
+ 'char-serial.c',
+ 'char-socket.c',
+ 'char-stdio.c',
+ 'char-udp.c',
+ 'char.c',
+))
+chardev_ss.add(when: 'CONFIG_POSIX', if_true: files(
+ 'char-fd.c',
+ 'char-parallel.c',
+ 'char-pty.c',
+))
+chardev_ss.add(when: 'CONFIG_WIN32', if_true: files(
+ 'char-console.c',
+ 'char-win-stdio.c',
+ 'char-win.c',
+))
+
+chardev_ss = chardev_ss.apply(config_host, strict: false)
+
+softmmu_ss.add(files('msmouse.c', 'wctablet.c', 'testdev.c'))
+
+chardev_modules = {}
+
+if brlapi.found()
+ module_ss = ss.source_set()
+ module_ss.add(when: [brlapi], if_true: [files('baum.c'), pixman])
+ chardev_modules += { 'baum': module_ss }
+endif
+
+if spice.found()
+ module_ss = ss.source_set()
+ module_ss.add(when: [spice], if_true: files('spice.c'))
+ chardev_modules += { 'spice': module_ss }
+endif
+
+modules += { 'chardev': chardev_modules }
diff --git a/chardev/msmouse.c b/chardev/msmouse.c
new file mode 100644
index 000000000..eb9231dcd
--- /dev/null
+++ b/chardev/msmouse.c
@@ -0,0 +1,193 @@
+/*
+ * QEMU Microsoft serial mouse emulation
+ *
+ * Copyright (c) 2008 Lubomir Rintel
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "chardev/char.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "qom/object.h"
+
+#define MSMOUSE_LO6(n) ((n) & 0x3f)
+#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6)
+
+struct MouseChardev {
+ Chardev parent;
+
+ QemuInputHandlerState *hs;
+ int axis[INPUT_AXIS__MAX];
+ bool btns[INPUT_BUTTON__MAX];
+ bool btnc[INPUT_BUTTON__MAX];
+ uint8_t outbuf[32];
+ int outlen;
+};
+typedef struct MouseChardev MouseChardev;
+
+#define TYPE_CHARDEV_MSMOUSE "chardev-msmouse"
+DECLARE_INSTANCE_CHECKER(MouseChardev, MOUSE_CHARDEV,
+ TYPE_CHARDEV_MSMOUSE)
+
+static void msmouse_chr_accept_input(Chardev *chr)
+{
+ MouseChardev *mouse = MOUSE_CHARDEV(chr);
+ int len;
+
+ len = qemu_chr_be_can_write(chr);
+ if (len > mouse->outlen) {
+ len = mouse->outlen;
+ }
+ if (!len) {
+ return;
+ }
+
+ qemu_chr_be_write(chr, mouse->outbuf, len);
+ mouse->outlen -= len;
+ if (mouse->outlen) {
+ memmove(mouse->outbuf, mouse->outbuf + len, mouse->outlen);
+ }
+}
+
+static void msmouse_queue_event(MouseChardev *mouse)
+{
+ unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 };
+ int dx, dy, count = 3;
+
+ dx = mouse->axis[INPUT_AXIS_X];
+ mouse->axis[INPUT_AXIS_X] = 0;
+
+ dy = mouse->axis[INPUT_AXIS_Y];
+ mouse->axis[INPUT_AXIS_Y] = 0;
+
+ /* Movement deltas */
+ bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx);
+ bytes[1] |= MSMOUSE_LO6(dx);
+ bytes[2] |= MSMOUSE_LO6(dy);
+
+ /* Buttons */
+ bytes[0] |= (mouse->btns[INPUT_BUTTON_LEFT] ? 0x20 : 0x00);
+ bytes[0] |= (mouse->btns[INPUT_BUTTON_RIGHT] ? 0x10 : 0x00);
+ if (mouse->btns[INPUT_BUTTON_MIDDLE] ||
+ mouse->btnc[INPUT_BUTTON_MIDDLE]) {
+ bytes[3] |= (mouse->btns[INPUT_BUTTON_MIDDLE] ? 0x20 : 0x00);
+ mouse->btnc[INPUT_BUTTON_MIDDLE] = false;
+ count = 4;
+ }
+
+ if (mouse->outlen <= sizeof(mouse->outbuf) - count) {
+ memcpy(mouse->outbuf + mouse->outlen, bytes, count);
+ mouse->outlen += count;
+ } else {
+ /* queue full -> drop event */
+ }
+}
+
+static void msmouse_input_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ MouseChardev *mouse = MOUSE_CHARDEV(dev);
+ InputMoveEvent *move;
+ InputBtnEvent *btn;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_REL:
+ move = evt->u.rel.data;
+ mouse->axis[move->axis] += move->value;
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ mouse->btns[btn->button] = btn->down;
+ mouse->btnc[btn->button] = true;
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void msmouse_input_sync(DeviceState *dev)
+{
+ MouseChardev *mouse = MOUSE_CHARDEV(dev);
+ Chardev *chr = CHARDEV(dev);
+
+ msmouse_queue_event(mouse);
+ msmouse_chr_accept_input(chr);
+}
+
+static int msmouse_chr_write(struct Chardev *s, const uint8_t *buf, int len)
+{
+ /* Ignore writes to mouse port */
+ return len;
+}
+
+static void char_msmouse_finalize(Object *obj)
+{
+ MouseChardev *mouse = MOUSE_CHARDEV(obj);
+
+ qemu_input_handler_unregister(mouse->hs);
+}
+
+static QemuInputHandler msmouse_handler = {
+ .name = "QEMU Microsoft Mouse",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
+ .event = msmouse_input_event,
+ .sync = msmouse_input_sync,
+};
+
+static void msmouse_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ MouseChardev *mouse = MOUSE_CHARDEV(chr);
+
+ *be_opened = false;
+ mouse->hs = qemu_input_handler_register((DeviceState *)mouse,
+ &msmouse_handler);
+}
+
+static void char_msmouse_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = msmouse_chr_open;
+ cc->chr_write = msmouse_chr_write;
+ cc->chr_accept_input = msmouse_chr_accept_input;
+}
+
+static const TypeInfo char_msmouse_type_info = {
+ .name = TYPE_CHARDEV_MSMOUSE,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(MouseChardev),
+ .instance_finalize = char_msmouse_finalize,
+ .class_init = char_msmouse_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_msmouse_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/spice.c b/chardev/spice.c
new file mode 100644
index 000000000..bbffef491
--- /dev/null
+++ b/chardev/spice.c
@@ -0,0 +1,412 @@
+#include "qemu/osdep.h"
+#include "trace.h"
+#include "ui/qemu-spice.h"
+#include "chardev/char.h"
+#include "chardev/spice.h"
+#include "qapi/error.h"
+#include "qemu/error-report.h"
+#include "qemu/module.h"
+#include "qemu/option.h"
+#include <spice/protocol.h>
+
+typedef struct SpiceCharSource {
+ GSource source;
+ SpiceChardev *scd;
+} SpiceCharSource;
+
+static int vmc_write(SpiceCharDeviceInstance *sin, const uint8_t *buf, int len)
+{
+ SpiceChardev *scd = container_of(sin, SpiceChardev, sin);
+ Chardev *chr = CHARDEV(scd);
+ ssize_t out = 0;
+ ssize_t last_out;
+ uint8_t* p = (uint8_t*)buf;
+
+ while (len > 0) {
+ int can_write = qemu_chr_be_can_write(chr);
+ last_out = MIN(len, can_write);
+ if (last_out <= 0) {
+ break;
+ }
+ qemu_chr_be_write(chr, p, last_out);
+ out += last_out;
+ len -= last_out;
+ p += last_out;
+ }
+
+ trace_spice_vmc_write(out, len + out);
+ return out;
+}
+
+static int vmc_read(SpiceCharDeviceInstance *sin, uint8_t *buf, int len)
+{
+ SpiceChardev *scd = container_of(sin, SpiceChardev, sin);
+ int bytes = MIN(len, scd->datalen);
+
+ if (bytes > 0) {
+ memcpy(buf, scd->datapos, bytes);
+ scd->datapos += bytes;
+ scd->datalen -= bytes;
+ assert(scd->datalen >= 0);
+ }
+ if (scd->datalen == 0) {
+ scd->datapos = 0;
+ scd->blocked = false;
+ }
+ trace_spice_vmc_read(bytes, len);
+ return bytes;
+}
+
+static void vmc_event(SpiceCharDeviceInstance *sin, uint8_t event)
+{
+ SpiceChardev *scd = container_of(sin, SpiceChardev, sin);
+ Chardev *chr = CHARDEV(scd);
+ int chr_event;
+
+ switch (event) {
+ case SPICE_PORT_EVENT_BREAK:
+ chr_event = CHR_EVENT_BREAK;
+ break;
+ default:
+ return;
+ }
+
+ trace_spice_vmc_event(chr_event);
+ qemu_chr_be_event(chr, chr_event);
+}
+
+static void vmc_state(SpiceCharDeviceInstance *sin, int connected)
+{
+ SpiceChardev *scd = container_of(sin, SpiceChardev, sin);
+ Chardev *chr = CHARDEV(scd);
+
+ if ((chr->be_open && connected) ||
+ (!chr->be_open && !connected)) {
+ return;
+ }
+
+ qemu_chr_be_event(chr,
+ connected ? CHR_EVENT_OPENED : CHR_EVENT_CLOSED);
+}
+
+static SpiceCharDeviceInterface vmc_interface = {
+ .base.type = SPICE_INTERFACE_CHAR_DEVICE,
+ .base.description = "spice virtual channel char device",
+ .base.major_version = SPICE_INTERFACE_CHAR_DEVICE_MAJOR,
+ .base.minor_version = SPICE_INTERFACE_CHAR_DEVICE_MINOR,
+ .state = vmc_state,
+ .write = vmc_write,
+ .read = vmc_read,
+ .event = vmc_event,
+#if SPICE_SERVER_VERSION >= 0x000c06
+ .flags = SPICE_CHAR_DEVICE_NOTIFY_WRITABLE,
+#endif
+};
+
+
+static void vmc_register_interface(SpiceChardev *scd)
+{
+ if (scd->active) {
+ return;
+ }
+ scd->sin.base.sif = &vmc_interface.base;
+ qemu_spice.add_interface(&scd->sin.base);
+ scd->active = true;
+ trace_spice_vmc_register_interface(scd);
+}
+
+static void vmc_unregister_interface(SpiceChardev *scd)
+{
+ if (!scd->active) {
+ return;
+ }
+ spice_server_remove_interface(&scd->sin.base);
+ scd->active = false;
+ trace_spice_vmc_unregister_interface(scd);
+}
+
+static gboolean spice_char_source_prepare(GSource *source, gint *timeout)
+{
+ SpiceCharSource *src = (SpiceCharSource *)source;
+ Chardev *chr = CHARDEV(src->scd);
+
+ *timeout = -1;
+
+ if (!chr->be_open) {
+ return true;
+ }
+
+ return !src->scd->blocked;
+}
+
+static gboolean spice_char_source_check(GSource *source)
+{
+ SpiceCharSource *src = (SpiceCharSource *)source;
+ Chardev *chr = CHARDEV(src->scd);
+
+ if (!chr->be_open) {
+ return true;
+ }
+
+ return !src->scd->blocked;
+}
+
+static gboolean spice_char_source_dispatch(GSource *source,
+ GSourceFunc callback, gpointer user_data)
+{
+ SpiceCharSource *src = (SpiceCharSource *)source;
+ Chardev *chr = CHARDEV(src->scd);
+ GIOFunc func = (GIOFunc)callback;
+ GIOCondition cond = chr->be_open ? G_IO_OUT : G_IO_HUP;
+
+ return func(NULL, cond, user_data);
+}
+
+static GSourceFuncs SpiceCharSourceFuncs = {
+ .prepare = spice_char_source_prepare,
+ .check = spice_char_source_check,
+ .dispatch = spice_char_source_dispatch,
+};
+
+static GSource *spice_chr_add_watch(Chardev *chr, GIOCondition cond)
+{
+ SpiceChardev *scd = SPICE_CHARDEV(chr);
+ SpiceCharSource *src;
+
+ assert(cond & G_IO_OUT);
+
+ src = (SpiceCharSource *)g_source_new(&SpiceCharSourceFuncs,
+ sizeof(SpiceCharSource));
+ src->scd = scd;
+
+ return (GSource *)src;
+}
+
+static int spice_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ SpiceChardev *s = SPICE_CHARDEV(chr);
+ int read_bytes;
+
+ assert(s->datalen == 0);
+
+ if (!chr->be_open) {
+ trace_spice_chr_discard_write(len);
+ return len;
+ }
+
+ s->datapos = buf;
+ s->datalen = len;
+ spice_server_char_device_wakeup(&s->sin);
+ read_bytes = len - s->datalen;
+ if (read_bytes != len) {
+ /* We'll get passed in the unconsumed data with the next call */
+ s->datalen = 0;
+ s->datapos = NULL;
+ s->blocked = true;
+ }
+ return read_bytes;
+}
+
+static void char_spice_finalize(Object *obj)
+{
+ SpiceChardev *s = SPICE_CHARDEV(obj);
+
+ vmc_unregister_interface(s);
+
+ g_free((char *)s->sin.subtype);
+ g_free((char *)s->sin.portname);
+}
+
+static void spice_vmc_set_fe_open(struct Chardev *chr, int fe_open)
+{
+ SpiceChardev *s = SPICE_CHARDEV(chr);
+ if (fe_open) {
+ vmc_register_interface(s);
+ } else {
+ vmc_unregister_interface(s);
+ }
+}
+
+static void spice_port_set_fe_open(struct Chardev *chr, int fe_open)
+{
+ SpiceChardev *s = SPICE_CHARDEV(chr);
+
+ if (fe_open) {
+ spice_server_port_event(&s->sin, SPICE_PORT_EVENT_OPENED);
+ } else {
+ spice_server_port_event(&s->sin, SPICE_PORT_EVENT_CLOSED);
+ }
+}
+
+static void spice_chr_accept_input(struct Chardev *chr)
+{
+ SpiceChardev *s = SPICE_CHARDEV(chr);
+
+ spice_server_char_device_wakeup(&s->sin);
+}
+
+static void chr_open(Chardev *chr, const char *subtype)
+{
+ SpiceChardev *s = SPICE_CHARDEV(chr);
+
+ s->active = false;
+ s->sin.subtype = g_strdup(subtype);
+}
+
+static void qemu_chr_open_spice_vmc(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevSpiceChannel *spicevmc = backend->u.spicevmc.data;
+ const char *type = spicevmc->type;
+ const char **psubtype = spice_server_char_device_recognized_subtypes();
+
+ for (; *psubtype != NULL; ++psubtype) {
+ if (strcmp(type, *psubtype) == 0) {
+ break;
+ }
+ }
+ if (*psubtype == NULL) {
+ char *subtypes = g_strjoinv(", ",
+ (gchar **)spice_server_char_device_recognized_subtypes());
+
+ error_setg(errp, "unsupported type name: %s", type);
+ error_append_hint(errp, "allowed spice char type names: %s\n",
+ subtypes);
+
+ g_free(subtypes);
+ return;
+ }
+
+ *be_opened = false;
+#if SPICE_SERVER_VERSION < 0x000e02
+ /* Spice < 0.14.2 doesn't explicitly open smartcard chardev */
+ if (strcmp(type, "smartcard") == 0) {
+ *be_opened = true;
+ }
+#endif
+ chr_open(chr, type);
+}
+
+static void qemu_chr_open_spice_port(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ ChardevSpicePort *spiceport = backend->u.spiceport.data;
+ const char *name = spiceport->fqdn;
+ SpiceChardev *s;
+
+ if (name == NULL) {
+ error_setg(errp, "missing name parameter");
+ return;
+ }
+
+ if (!using_spice) {
+ error_setg(errp, "spice not enabled");
+ return;
+ }
+
+ chr_open(chr, "port");
+
+ *be_opened = false;
+ s = SPICE_CHARDEV(chr);
+ s->sin.portname = g_strdup(name);
+
+ vmc_register_interface(s);
+}
+
+static void qemu_chr_parse_spice_vmc(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *name = qemu_opt_get(opts, "name");
+ ChardevSpiceChannel *spicevmc;
+
+ if (name == NULL) {
+ error_setg(errp, "chardev: spice channel: no name given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_SPICEVMC;
+ spicevmc = backend->u.spicevmc.data = g_new0(ChardevSpiceChannel, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevSpiceChannel_base(spicevmc));
+ spicevmc->type = g_strdup(name);
+}
+
+static void qemu_chr_parse_spice_port(QemuOpts *opts, ChardevBackend *backend,
+ Error **errp)
+{
+ const char *name = qemu_opt_get(opts, "name");
+ ChardevSpicePort *spiceport;
+
+ if (name == NULL) {
+ error_setg(errp, "chardev: spice port: no name given");
+ return;
+ }
+ backend->type = CHARDEV_BACKEND_KIND_SPICEPORT;
+ spiceport = backend->u.spiceport.data = g_new0(ChardevSpicePort, 1);
+ qemu_chr_parse_common(opts, qapi_ChardevSpicePort_base(spiceport));
+ spiceport->fqdn = g_strdup(name);
+}
+
+static void char_spice_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->chr_write = spice_chr_write;
+ cc->chr_add_watch = spice_chr_add_watch;
+ cc->chr_accept_input = spice_chr_accept_input;
+}
+
+static const TypeInfo char_spice_type_info = {
+ .name = TYPE_CHARDEV_SPICE,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(SpiceChardev),
+ .instance_finalize = char_spice_finalize,
+ .class_init = char_spice_class_init,
+ .abstract = true,
+};
+module_obj(TYPE_CHARDEV_SPICE);
+
+static void char_spicevmc_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_spice_vmc;
+ cc->open = qemu_chr_open_spice_vmc;
+ cc->chr_set_fe_open = spice_vmc_set_fe_open;
+}
+
+static const TypeInfo char_spicevmc_type_info = {
+ .name = TYPE_CHARDEV_SPICEVMC,
+ .parent = TYPE_CHARDEV_SPICE,
+ .class_init = char_spicevmc_class_init,
+};
+module_obj(TYPE_CHARDEV_SPICEVMC);
+
+static void char_spiceport_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->parse = qemu_chr_parse_spice_port;
+ cc->open = qemu_chr_open_spice_port;
+ cc->chr_set_fe_open = spice_port_set_fe_open;
+}
+
+static const TypeInfo char_spiceport_type_info = {
+ .name = TYPE_CHARDEV_SPICEPORT,
+ .parent = TYPE_CHARDEV_SPICE,
+ .class_init = char_spiceport_class_init,
+};
+module_obj(TYPE_CHARDEV_SPICEPORT);
+
+static void register_types(void)
+{
+ type_register_static(&char_spice_type_info);
+ type_register_static(&char_spicevmc_type_info);
+ type_register_static(&char_spiceport_type_info);
+}
+
+type_init(register_types);
+
+module_dep("ui-spice-core");
diff --git a/chardev/testdev.c b/chardev/testdev.c
new file mode 100644
index 000000000..a92caca3c
--- /dev/null
+++ b/chardev/testdev.c
@@ -0,0 +1,132 @@
+/*
+ * QEMU Char Device for testsuite control
+ *
+ * Copyright (c) 2014 Red Hat, Inc.
+ *
+ * Author: Paolo Bonzini <pbonzini@redhat.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "chardev/char.h"
+#include "qom/object.h"
+
+#define BUF_SIZE 32
+
+struct TestdevChardev {
+ Chardev parent;
+
+ uint8_t in_buf[32];
+ int in_buf_used;
+};
+typedef struct TestdevChardev TestdevChardev;
+
+#define TYPE_CHARDEV_TESTDEV "chardev-testdev"
+DECLARE_INSTANCE_CHECKER(TestdevChardev, TESTDEV_CHARDEV,
+ TYPE_CHARDEV_TESTDEV)
+
+/* Try to interpret a whole incoming packet */
+static int testdev_eat_packet(TestdevChardev *testdev)
+{
+ const uint8_t *cur = testdev->in_buf;
+ int len = testdev->in_buf_used;
+ uint8_t c;
+ int arg;
+
+#define EAT(c) do { \
+ if (!len--) { \
+ return 0; \
+ } \
+ c = *cur++; \
+} while (0)
+
+ EAT(c);
+
+ while (isspace(c)) {
+ EAT(c);
+ }
+
+ arg = 0;
+ while (isdigit(c)) {
+ arg = arg * 10 + c - '0';
+ EAT(c);
+ }
+
+ while (isspace(c)) {
+ EAT(c);
+ }
+
+ switch (c) {
+ case 'q':
+ exit((arg << 1) | 1);
+ break;
+ default:
+ break;
+ }
+ return cur - testdev->in_buf;
+}
+
+/* The other end is writing some data. Store it and try to interpret */
+static int testdev_chr_write(Chardev *chr, const uint8_t *buf, int len)
+{
+ TestdevChardev *testdev = TESTDEV_CHARDEV(chr);
+ int tocopy, eaten, orig_len = len;
+
+ while (len) {
+ /* Complete our buffer as much as possible */
+ tocopy = MIN(len, BUF_SIZE - testdev->in_buf_used);
+
+ memcpy(testdev->in_buf + testdev->in_buf_used, buf, tocopy);
+ testdev->in_buf_used += tocopy;
+ buf += tocopy;
+ len -= tocopy;
+
+ /* Interpret it as much as possible */
+ while (testdev->in_buf_used > 0 &&
+ (eaten = testdev_eat_packet(testdev)) > 0) {
+ memmove(testdev->in_buf, testdev->in_buf + eaten,
+ testdev->in_buf_used - eaten);
+ testdev->in_buf_used -= eaten;
+ }
+ }
+ return orig_len;
+}
+
+static void char_testdev_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->chr_write = testdev_chr_write;
+}
+
+static const TypeInfo char_testdev_type_info = {
+ .name = TYPE_CHARDEV_TESTDEV,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(TestdevChardev),
+ .class_init = char_testdev_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&char_testdev_type_info);
+}
+
+type_init(register_types);
diff --git a/chardev/trace-events b/chardev/trace-events
new file mode 100644
index 000000000..027107b0c
--- /dev/null
+++ b/chardev/trace-events
@@ -0,0 +1,19 @@
+# See docs/devel/tracing.rst for syntax documentation.
+
+# wctablet.c
+wct_init(void) ""
+wct_cmd_re(void) ""
+wct_cmd_st(void) ""
+wct_cmd_sp(void) ""
+wct_cmd_ts(int input) "0x%02x"
+wct_cmd_other(const char *cmd) "%s"
+wct_speed(int speed) "%d"
+
+# spice.c
+spice_chr_discard_write(int len) "spice chr write discarded %d"
+spice_vmc_write(ssize_t out, int len) "spice wrote %zd of requested %d"
+spice_vmc_read(int bytes, int len) "spice read %d of requested %d"
+spice_vmc_register_interface(void *scd) "spice vmc registered interface %p"
+spice_vmc_unregister_interface(void *scd) "spice vmc unregistered interface %p"
+spice_vmc_event(int event) "spice vmc event %d"
+
diff --git a/chardev/trace.h b/chardev/trace.h
new file mode 100644
index 000000000..eb4f9027a
--- /dev/null
+++ b/chardev/trace.h
@@ -0,0 +1 @@
+#include "trace/trace-chardev.h"
diff --git a/chardev/wctablet.c b/chardev/wctablet.c
new file mode 100644
index 000000000..e8b292c43
--- /dev/null
+++ b/chardev/wctablet.c
@@ -0,0 +1,366 @@
+/*
+ * QEMU Wacom Penpartner serial tablet emulation
+ *
+ * some protocol details:
+ * http://linuxwacom.sourceforge.net/wiki/index.php/Serial_Protocol_IV
+ *
+ * Copyright (c) 2016 Anatoli Huseu1
+ * Copyright (c) 2016,17 Gerd Hoffmann
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/module.h"
+#include "chardev/char-serial.h"
+#include "ui/console.h"
+#include "ui/input.h"
+#include "trace.h"
+#include "qom/object.h"
+
+
+#define WC_OUTPUT_BUF_MAX_LEN 512
+#define WC_COMMAND_MAX_LEN 60
+
+#define WC_L7(n) ((n) & 127)
+#define WC_M7(n) (((n) >> 7) & 127)
+#define WC_H2(n) ((n) >> 14)
+
+#define WC_L4(n) ((n) & 15)
+#define WC_H4(n) (((n) >> 4) & 15)
+
+/* Model string and config string */
+#define WC_MODEL_STRING_LENGTH 18
+uint8_t WC_MODEL_STRING[WC_MODEL_STRING_LENGTH + 1] = "~#CT-0045R,V1.3-5,";
+
+#define WC_CONFIG_STRING_LENGTH 8
+uint8_t WC_CONFIG_STRING[WC_CONFIG_STRING_LENGTH + 1] = "96,N,8,0";
+
+#define WC_FULL_CONFIG_STRING_LENGTH 61
+uint8_t WC_FULL_CONFIG_STRING[WC_FULL_CONFIG_STRING_LENGTH + 1] = {
+ 0x5c, 0x39, 0x36, 0x2c, 0x4e, 0x2c, 0x38, 0x2c,
+ 0x31, 0x28, 0x01, 0x24, 0x57, 0x41, 0x43, 0x30,
+ 0x30, 0x34, 0x35, 0x5c, 0x5c, 0x50, 0x45, 0x4e, 0x5c,
+ 0x57, 0x41, 0x43, 0x30, 0x30, 0x30, 0x30, 0x5c,
+ 0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x0d, 0x0a,
+ 0x43, 0x54, 0x2d, 0x30, 0x30, 0x34, 0x35, 0x52,
+ 0x2c, 0x56, 0x31, 0x2e, 0x33, 0x2d, 0x35, 0x0d,
+ 0x0a, 0x45, 0x37, 0x29
+};
+
+/* This structure is used to save private info for Wacom Tablet. */
+struct TabletChardev {
+ Chardev parent;
+ QemuInputHandlerState *hs;
+
+ /* Query string from serial */
+ uint8_t query[100];
+ int query_index;
+
+ /* Command to be sent to serial port */
+ uint8_t outbuf[WC_OUTPUT_BUF_MAX_LEN];
+ int outlen;
+
+ int line_speed;
+ bool send_events;
+ int axis[INPUT_AXIS__MAX];
+ bool btns[INPUT_BUTTON__MAX];
+
+};
+typedef struct TabletChardev TabletChardev;
+
+#define TYPE_CHARDEV_WCTABLET "chardev-wctablet"
+DECLARE_INSTANCE_CHECKER(TabletChardev, WCTABLET_CHARDEV,
+ TYPE_CHARDEV_WCTABLET)
+
+
+static void wctablet_chr_accept_input(Chardev *chr);
+
+static void wctablet_shift_input(TabletChardev *tablet, int count)
+{
+ tablet->query_index -= count;
+ memmove(tablet->query, tablet->query + count, tablet->query_index);
+ tablet->query[tablet->query_index] = 0;
+}
+
+static void wctablet_queue_output(TabletChardev *tablet, uint8_t *buf, int count)
+{
+ if (tablet->outlen + count > sizeof(tablet->outbuf)) {
+ return;
+ }
+
+ memcpy(tablet->outbuf + tablet->outlen, buf, count);
+ tablet->outlen += count;
+ wctablet_chr_accept_input(CHARDEV(tablet));
+}
+
+static void wctablet_reset(TabletChardev *tablet)
+{
+ /* clear buffers */
+ tablet->query_index = 0;
+ tablet->outlen = 0;
+ /* reset state */
+ tablet->send_events = false;
+}
+
+static void wctablet_queue_event(TabletChardev *tablet)
+{
+ uint8_t codes[8] = { 0xe0, 0, 0, 0, 0, 0, 0 };
+
+ if (tablet->line_speed != 9600) {
+ return;
+ }
+
+ int newX = tablet->axis[INPUT_AXIS_X] * 0.1537;
+ int nexY = tablet->axis[INPUT_AXIS_Y] * 0.1152;
+
+ codes[0] = codes[0] | WC_H2(newX);
+ codes[1] = codes[1] | WC_M7(newX);
+ codes[2] = codes[2] | WC_L7(newX);
+
+ codes[3] = codes[3] | WC_H2(nexY);
+ codes[4] = codes[4] | WC_M7(nexY);
+ codes[5] = codes[5] | WC_L7(nexY);
+
+ if (tablet->btns[INPUT_BUTTON_LEFT]) {
+ codes[0] = 0xa0;
+ }
+
+ wctablet_queue_output(tablet, codes, 7);
+}
+
+static void wctablet_input_event(DeviceState *dev, QemuConsole *src,
+ InputEvent *evt)
+{
+ TabletChardev *tablet = (TabletChardev *)dev;
+ InputMoveEvent *move;
+ InputBtnEvent *btn;
+
+ switch (evt->type) {
+ case INPUT_EVENT_KIND_ABS:
+ move = evt->u.abs.data;
+ tablet->axis[move->axis] = move->value;
+ break;
+
+ case INPUT_EVENT_KIND_BTN:
+ btn = evt->u.btn.data;
+ tablet->btns[btn->button] = btn->down;
+ break;
+
+ default:
+ /* keep gcc happy */
+ break;
+ }
+}
+
+static void wctablet_input_sync(DeviceState *dev)
+{
+ TabletChardev *tablet = (TabletChardev *)dev;
+
+ if (tablet->send_events) {
+ wctablet_queue_event(tablet);
+ }
+}
+
+static QemuInputHandler wctablet_handler = {
+ .name = "QEMU Wacom Pen Tablet",
+ .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
+ .event = wctablet_input_event,
+ .sync = wctablet_input_sync,
+};
+
+static void wctablet_chr_accept_input(Chardev *chr)
+{
+ TabletChardev *tablet = WCTABLET_CHARDEV(chr);
+ int len, canWrite;
+
+ canWrite = qemu_chr_be_can_write(chr);
+ len = canWrite;
+ if (len > tablet->outlen) {
+ len = tablet->outlen;
+ }
+
+ if (len) {
+ qemu_chr_be_write(chr, tablet->outbuf, len);
+ tablet->outlen -= len;
+ if (tablet->outlen) {
+ memmove(tablet->outbuf, tablet->outbuf + len, tablet->outlen);
+ }
+ }
+}
+
+static int wctablet_chr_write(struct Chardev *chr,
+ const uint8_t *buf, int len)
+{
+ TabletChardev *tablet = WCTABLET_CHARDEV(chr);
+ unsigned int i, clen;
+ char *pos;
+
+ if (tablet->line_speed != 9600) {
+ return len;
+ }
+ for (i = 0; i < len && tablet->query_index < sizeof(tablet->query) - 1; i++) {
+ tablet->query[tablet->query_index++] = buf[i];
+ }
+ tablet->query[tablet->query_index] = 0;
+
+ while (tablet->query_index > 0 && (tablet->query[0] == '@' ||
+ tablet->query[0] == '\r' ||
+ tablet->query[0] == '\n')) {
+ wctablet_shift_input(tablet, 1);
+ }
+ if (!tablet->query_index) {
+ return len;
+ }
+
+ if (strncmp((char *)tablet->query, "~#", 2) == 0) {
+ /* init / detect sequence */
+ trace_wct_init();
+ wctablet_shift_input(tablet, 2);
+ wctablet_queue_output(tablet, WC_MODEL_STRING,
+ WC_MODEL_STRING_LENGTH);
+ return len;
+ }
+
+ /* detect line */
+ pos = strchr((char *)tablet->query, '\r');
+ if (!pos) {
+ pos = strchr((char *)tablet->query, '\n');
+ }
+ if (!pos) {
+ return len;
+ }
+ clen = pos - (char *)tablet->query;
+
+ /* process commands */
+ if (strncmp((char *)tablet->query, "RE", 2) == 0 &&
+ clen == 2) {
+ trace_wct_cmd_re();
+ wctablet_shift_input(tablet, 3);
+ wctablet_queue_output(tablet, WC_CONFIG_STRING,
+ WC_CONFIG_STRING_LENGTH);
+
+ } else if (strncmp((char *)tablet->query, "ST", 2) == 0 &&
+ clen == 2) {
+ trace_wct_cmd_st();
+ wctablet_shift_input(tablet, 3);
+ tablet->send_events = true;
+ wctablet_queue_event(tablet);
+
+ } else if (strncmp((char *)tablet->query, "SP", 2) == 0 &&
+ clen == 2) {
+ trace_wct_cmd_sp();
+ wctablet_shift_input(tablet, 3);
+ tablet->send_events = false;
+
+ } else if (strncmp((char *)tablet->query, "TS", 2) == 0 &&
+ clen == 3) {
+ unsigned int input = tablet->query[2];
+ uint8_t codes[7] = {
+ 0xa3,
+ ((input & 0x80) == 0) ? 0x7e : 0x7f,
+ (((WC_H4(input) & 0x7) ^ 0x5) << 4) | (WC_L4(input) ^ 0x7),
+ 0x03,
+ 0x7f,
+ 0x7f,
+ 0x00,
+ };
+ trace_wct_cmd_ts(input);
+ wctablet_shift_input(tablet, 4);
+ wctablet_queue_output(tablet, codes, 7);
+
+ } else {
+ tablet->query[clen] = 0; /* terminate line for printing */
+ trace_wct_cmd_other((char *)tablet->query);
+ wctablet_shift_input(tablet, clen + 1);
+
+ }
+
+ return len;
+}
+
+static int wctablet_chr_ioctl(Chardev *chr, int cmd, void *arg)
+{
+ TabletChardev *tablet = WCTABLET_CHARDEV(chr);
+ QEMUSerialSetParams *ssp;
+
+ switch (cmd) {
+ case CHR_IOCTL_SERIAL_SET_PARAMS:
+ ssp = arg;
+ if (tablet->line_speed != ssp->speed) {
+ trace_wct_speed(ssp->speed);
+ wctablet_reset(tablet);
+ tablet->line_speed = ssp->speed;
+ }
+ break;
+ default:
+ return -ENOTSUP;
+ }
+ return 0;
+}
+
+static void wctablet_chr_finalize(Object *obj)
+{
+ TabletChardev *tablet = WCTABLET_CHARDEV(obj);
+
+ qemu_input_handler_unregister(tablet->hs);
+}
+
+static void wctablet_chr_open(Chardev *chr,
+ ChardevBackend *backend,
+ bool *be_opened,
+ Error **errp)
+{
+ TabletChardev *tablet = WCTABLET_CHARDEV(chr);
+
+ *be_opened = true;
+
+ /* init state machine */
+ memcpy(tablet->outbuf, WC_FULL_CONFIG_STRING, WC_FULL_CONFIG_STRING_LENGTH);
+ tablet->outlen = WC_FULL_CONFIG_STRING_LENGTH;
+ tablet->query_index = 0;
+
+ tablet->hs = qemu_input_handler_register((DeviceState *)tablet,
+ &wctablet_handler);
+}
+
+static void wctablet_chr_class_init(ObjectClass *oc, void *data)
+{
+ ChardevClass *cc = CHARDEV_CLASS(oc);
+
+ cc->open = wctablet_chr_open;
+ cc->chr_write = wctablet_chr_write;
+ cc->chr_ioctl = wctablet_chr_ioctl;
+ cc->chr_accept_input = wctablet_chr_accept_input;
+}
+
+static const TypeInfo wctablet_type_info = {
+ .name = TYPE_CHARDEV_WCTABLET,
+ .parent = TYPE_CHARDEV,
+ .instance_size = sizeof(TabletChardev),
+ .instance_finalize = wctablet_chr_finalize,
+ .class_init = wctablet_chr_class_init,
+};
+
+static void register_types(void)
+{
+ type_register_static(&wctablet_type_info);
+}
+
+type_init(register_types);