/* * 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 "sysemu/char.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);