From 6b10e573d15ef82dbc5c5b3726028e6642e134f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Lureau?= Date: Mon, 29 May 2017 12:39:42 +0400 Subject: char: move char devices to chardev/ MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Suggested by Paolo Bonzini during series review. Signed-off-by: Marc-André Lureau Reviewed-by: Philippe Mathieu-Daudé --- chardev/Makefile.objs | 6 + chardev/baum.c | 677 ++++++++++++++++++++++++++++++++++++++++++++++++++ chardev/msmouse.c | 190 ++++++++++++++ chardev/spice.c | 421 +++++++++++++++++++++++++++++++ chardev/testdev.c | 129 ++++++++++ chardev/trace-events | 18 ++ chardev/wctablet.c | 369 +++++++++++++++++++++++++++ 7 files changed, 1810 insertions(+) create mode 100644 chardev/baum.c create mode 100644 chardev/msmouse.c create mode 100644 chardev/spice.c create mode 100644 chardev/testdev.c create mode 100644 chardev/trace-events create mode 100644 chardev/wctablet.c (limited to 'chardev') diff --git a/chardev/Makefile.objs b/chardev/Makefile.objs index e0b37dbfd8..52a8127606 100644 --- a/chardev/Makefile.objs +++ b/chardev/Makefile.objs @@ -16,3 +16,9 @@ chardev-obj-y += char-stdio.o chardev-obj-y += char-udp.o chardev-obj-$(CONFIG_WIN32) += char-win.o chardev-obj-$(CONFIG_WIN32) += char-win-stdio.o + +common-obj-y += msmouse.o wctablet.o testdev.o +common-obj-$(CONFIG_BRLAPI) += baum.o +baum.o-cflags := $(SDL_CFLAGS) + +common-obj-$(CONFIG_SPICE) += spice.o diff --git a/chardev/baum.c b/chardev/baum.c new file mode 100644 index 0000000000..302dd9666c --- /dev/null +++ b/chardev/baum.c @@ -0,0 +1,677 @@ +/* + * QEMU Baum Braille Device + * + * Copyright (c) 2008, 2010-2011, 2016 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 "qemu-common.h" +#include "chardev/char.h" +#include "qemu/timer.h" +#include "hw/usb.h" +#include "ui/console.h" +#include +#include +#include + +#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 + +typedef struct { + 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; +} BaumChardev; + +#define TYPE_CHARDEV_BRAILLE "chardev-braille" +#define BAUM_CHARDEV(obj) OBJECT_CHECK(BaumChardev, (obj), 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; + } + + 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); + 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, +}; + +static void register_types(void) +{ + type_register_static(&char_braille_type_info); +} + +type_init(register_types); diff --git a/chardev/msmouse.c b/chardev/msmouse.c new file mode 100644 index 0000000000..0ffd137ce8 --- /dev/null +++ b/chardev/msmouse.c @@ -0,0 +1,190 @@ +/* + * 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-common.h" +#include "chardev/char.h" +#include "ui/console.h" +#include "ui/input.h" + +#define MSMOUSE_LO6(n) ((n) & 0x3f) +#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6) + +typedef struct { + 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; +} MouseChardev; + +#define TYPE_CHARDEV_MSMOUSE "chardev-msmouse" +#define MOUSE_CHARDEV(obj) \ + OBJECT_CHECK(MouseChardev, (obj), 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 0000000000..a312078812 --- /dev/null +++ b/chardev/spice.c @@ -0,0 +1,421 @@ +#include "qemu/osdep.h" +#include "trace.h" +#include "ui/qemu-spice.h" +#include "chardev/char.h" +#include "qemu/error-report.h" +#include +#include + + +typedef struct SpiceChardev { + Chardev parent; + + SpiceCharDeviceInstance sin; + bool active; + bool blocked; + const uint8_t *datapos; + int datalen; + QLIST_ENTRY(SpiceChardev) next; +} SpiceChardev; + +#define TYPE_CHARDEV_SPICE "chardev-spice" +#define TYPE_CHARDEV_SPICEVMC "chardev-spicevmc" +#define TYPE_CHARDEV_SPICEPORT "chardev-spiceport" + +#define SPICE_CHARDEV(obj) OBJECT_CHECK(SpiceChardev, (obj), TYPE_CHARDEV_SPICE) + +typedef struct SpiceCharSource { + GSource source; + SpiceChardev *scd; +} SpiceCharSource; + +static QLIST_HEAD(, SpiceChardev) spice_chars = + QLIST_HEAD_INITIALIZER(spice_chars); + +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; +} + +#if SPICE_SERVER_VERSION >= 0x000c02 +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); +} +#endif + +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, +#if SPICE_SERVER_VERSION >= 0x000c02 + .event = vmc_event, +#endif +#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; + + *timeout = -1; + + return !src->scd->blocked; +} + +static gboolean spice_char_source_check(GSource *source) +{ + SpiceCharSource *src = (SpiceCharSource *)source; + + return !src->scd->blocked; +} + +static gboolean spice_char_source_dispatch(GSource *source, + GSourceFunc callback, gpointer user_data) +{ + GIOFunc func = (GIOFunc)callback; + + return func(NULL, G_IO_OUT, 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); + 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); + + if (s->next.le_prev) { + QLIST_REMOVE(s, next); + } + + g_free((char *)s->sin.subtype); +#if SPICE_SERVER_VERSION >= 0x000c02 + g_free((char *)s->sin.portname); +#endif +} + +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) +{ +#if SPICE_SERVER_VERSION >= 0x000c02 + 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); + } +#endif +} + +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); + + QLIST_INSERT_HEAD(&spice_chars, s, next); +} + +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; + chr_open(chr, type); +} + +#if SPICE_SERVER_VERSION >= 0x000c02 +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; + } + + chr_open(chr, "port"); + + *be_opened = false; + s = SPICE_CHARDEV(chr); + s->sin.portname = g_strdup(name); +} + +void qemu_spice_register_ports(void) +{ + SpiceChardev *s; + + QLIST_FOREACH(s, &spice_chars, next) { + if (s->sin.portname == NULL) { + continue; + } + vmc_register_interface(s); + } +} +#endif + +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, +}; + +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, +}; + +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, +}; + +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); diff --git a/chardev/testdev.c b/chardev/testdev.c new file mode 100644 index 0000000000..031e9a23e8 --- /dev/null +++ b/chardev/testdev.c @@ -0,0 +1,129 @@ +/* + * QEMU Char Device for testsuite control + * + * Copyright (c) 2014 Red Hat, Inc. + * + * Author: Paolo Bonzini + * + * 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 "chardev/char.h" + +#define BUF_SIZE 32 + +typedef struct { + Chardev parent; + + uint8_t in_buf[32]; + int in_buf_used; +} TestdevChardev; + +#define TYPE_CHARDEV_TESTDEV "chardev-testdev" +#define TESTDEV_CHARDEV(obj) \ + OBJECT_CHECK(TestdevChardev, (obj), 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 0000000000..822dde668b --- /dev/null +++ b/chardev/trace-events @@ -0,0 +1,18 @@ +# See docs/tracing.txt for syntax documentation. + +# chardev/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" + +# chardev/spice.c +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/wctablet.c b/chardev/wctablet.c new file mode 100644 index 0000000000..6c13c2c58a --- /dev/null +++ b/chardev/wctablet.c @@ -0,0 +1,369 @@ +/* + * 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 +#include +#include +#include + +#include "qemu/osdep.h" +#include "qemu-common.h" +#include "chardev/char-serial.h" +#include "ui/console.h" +#include "ui/input.h" +#include "trace.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. */ +typedef struct { + 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]; + +} TabletChardev; + +#define TYPE_CHARDEV_WCTABLET "chardev-wctablet" +#define WCTABLET_CHARDEV(obj) \ + OBJECT_CHECK(TabletChardev, (obj), 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 Wacome 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); + g_free(tablet); +} + +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); -- cgit v1.2.1