qemu/chardev/wctablet.c
<<
>>
Prefs
   1/*
   2 * QEMU Wacom Penpartner serial tablet emulation
   3 *
   4 * some protocol details:
   5 *   http://linuxwacom.sourceforge.net/wiki/index.php/Serial_Protocol_IV
   6 *
   7 * Copyright (c) 2016 Anatoli Huseu1
   8 * Copyright (c) 2016,17 Gerd Hoffmann
   9 *
  10 * Permission is hereby granted, free of charge, to any person obtaining a copy
  11 * of this software and associated documentation files (the "Software"), to
  12 * deal in the Software without restriction, including without limitation
  13 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
  14 * and/or sell copies of the Software, and to permit persons to whom the
  15 * Software is furnished to do so, subject to the following conditions:
  16 *
  17 * The above copyright notice and this permission notice shall be included in
  18 * all copies or substantial portions of the Software.
  19 *
  20 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  21 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  22 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  23 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  24 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM
  25 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  26 * THE SOFTWARE.
  27 */
  28
  29#include "qemu/osdep.h"
  30#include "qemu/module.h"
  31#include "chardev/char-serial.h"
  32#include "ui/console.h"
  33#include "ui/input.h"
  34#include "trace.h"
  35
  36
  37#define WC_OUTPUT_BUF_MAX_LEN 512
  38#define WC_COMMAND_MAX_LEN 60
  39
  40#define WC_L7(n) ((n) & 127)
  41#define WC_M7(n) (((n) >> 7) & 127)
  42#define WC_H2(n) ((n) >> 14)
  43
  44#define WC_L4(n) ((n) & 15)
  45#define WC_H4(n) (((n) >> 4) & 15)
  46
  47/* Model string and config string */
  48#define WC_MODEL_STRING_LENGTH 18
  49uint8_t WC_MODEL_STRING[WC_MODEL_STRING_LENGTH + 1] = "~#CT-0045R,V1.3-5,";
  50
  51#define WC_CONFIG_STRING_LENGTH 8
  52uint8_t WC_CONFIG_STRING[WC_CONFIG_STRING_LENGTH + 1] = "96,N,8,0";
  53
  54#define WC_FULL_CONFIG_STRING_LENGTH 61
  55uint8_t WC_FULL_CONFIG_STRING[WC_FULL_CONFIG_STRING_LENGTH + 1] = {
  56    0x5c, 0x39, 0x36, 0x2c, 0x4e, 0x2c, 0x38, 0x2c,
  57    0x31, 0x28, 0x01, 0x24, 0x57, 0x41, 0x43, 0x30,
  58    0x30, 0x34, 0x35, 0x5c, 0x5c, 0x50, 0x45, 0x4e, 0x5c,
  59    0x57, 0x41, 0x43, 0x30, 0x30, 0x30, 0x30, 0x5c,
  60    0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x0d, 0x0a,
  61    0x43, 0x54, 0x2d, 0x30, 0x30, 0x34, 0x35, 0x52,
  62    0x2c, 0x56, 0x31, 0x2e, 0x33, 0x2d, 0x35, 0x0d,
  63    0x0a, 0x45, 0x37, 0x29
  64};
  65
  66/* This structure is used to save private info for Wacom Tablet. */
  67typedef struct {
  68    Chardev parent;
  69    QemuInputHandlerState *hs;
  70
  71    /* Query string from serial */
  72    uint8_t query[100];
  73    int query_index;
  74
  75    /* Command to be sent to serial port */
  76    uint8_t outbuf[WC_OUTPUT_BUF_MAX_LEN];
  77    int outlen;
  78
  79    int line_speed;
  80    bool send_events;
  81    int axis[INPUT_AXIS__MAX];
  82    bool btns[INPUT_BUTTON__MAX];
  83
  84} TabletChardev;
  85
  86#define TYPE_CHARDEV_WCTABLET "chardev-wctablet"
  87#define WCTABLET_CHARDEV(obj)                                      \
  88    OBJECT_CHECK(TabletChardev, (obj), TYPE_CHARDEV_WCTABLET)
  89
  90
  91static void wctablet_chr_accept_input(Chardev *chr);
  92
  93static void wctablet_shift_input(TabletChardev *tablet, int count)
  94{
  95    tablet->query_index -= count;
  96    memmove(tablet->query, tablet->query + count, tablet->query_index);
  97    tablet->query[tablet->query_index] = 0;
  98}
  99
 100static void wctablet_queue_output(TabletChardev *tablet, uint8_t *buf, int count)
 101{
 102    if (tablet->outlen + count > sizeof(tablet->outbuf)) {
 103        return;
 104    }
 105
 106    memcpy(tablet->outbuf + tablet->outlen, buf, count);
 107    tablet->outlen += count;
 108    wctablet_chr_accept_input(CHARDEV(tablet));
 109}
 110
 111static void wctablet_reset(TabletChardev *tablet)
 112{
 113    /* clear buffers */
 114    tablet->query_index = 0;
 115    tablet->outlen = 0;
 116    /* reset state */
 117    tablet->send_events = false;
 118}
 119
 120static void wctablet_queue_event(TabletChardev *tablet)
 121{
 122    uint8_t codes[8] = { 0xe0, 0, 0, 0, 0, 0, 0 };
 123
 124    if (tablet->line_speed != 9600) {
 125        return;
 126    }
 127
 128    int newX = tablet->axis[INPUT_AXIS_X] * 0.1537;
 129    int nexY = tablet->axis[INPUT_AXIS_Y] * 0.1152;
 130
 131    codes[0] = codes[0] | WC_H2(newX);
 132    codes[1] = codes[1] | WC_M7(newX);
 133    codes[2] = codes[2] | WC_L7(newX);
 134
 135    codes[3] = codes[3] | WC_H2(nexY);
 136    codes[4] = codes[4] | WC_M7(nexY);
 137    codes[5] = codes[5] | WC_L7(nexY);
 138
 139    if (tablet->btns[INPUT_BUTTON_LEFT]) {
 140        codes[0] = 0xa0;
 141    }
 142
 143    wctablet_queue_output(tablet, codes, 7);
 144}
 145
 146static void wctablet_input_event(DeviceState *dev, QemuConsole *src,
 147                                InputEvent *evt)
 148{
 149    TabletChardev *tablet = (TabletChardev *)dev;
 150    InputMoveEvent *move;
 151    InputBtnEvent *btn;
 152
 153    switch (evt->type) {
 154    case INPUT_EVENT_KIND_ABS:
 155        move = evt->u.abs.data;
 156        tablet->axis[move->axis] = move->value;
 157        break;
 158
 159    case INPUT_EVENT_KIND_BTN:
 160        btn = evt->u.btn.data;
 161        tablet->btns[btn->button] = btn->down;
 162        break;
 163
 164    default:
 165        /* keep gcc happy */
 166        break;
 167    }
 168}
 169
 170static void wctablet_input_sync(DeviceState *dev)
 171{
 172    TabletChardev *tablet = (TabletChardev *)dev;
 173
 174    if (tablet->send_events) {
 175        wctablet_queue_event(tablet);
 176    }
 177}
 178
 179static QemuInputHandler wctablet_handler = {
 180    .name  = "QEMU Wacom Pen Tablet",
 181    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
 182    .event = wctablet_input_event,
 183    .sync  = wctablet_input_sync,
 184};
 185
 186static void wctablet_chr_accept_input(Chardev *chr)
 187{
 188    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 189    int len, canWrite;
 190
 191    canWrite = qemu_chr_be_can_write(chr);
 192    len = canWrite;
 193    if (len > tablet->outlen) {
 194        len = tablet->outlen;
 195    }
 196
 197    if (len) {
 198        qemu_chr_be_write(chr, tablet->outbuf, len);
 199        tablet->outlen -= len;
 200        if (tablet->outlen) {
 201            memmove(tablet->outbuf, tablet->outbuf + len, tablet->outlen);
 202        }
 203    }
 204}
 205
 206static int wctablet_chr_write(struct Chardev *chr,
 207                              const uint8_t *buf, int len)
 208{
 209    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 210    unsigned int i, clen;
 211    char *pos;
 212
 213    if (tablet->line_speed != 9600) {
 214        return len;
 215    }
 216    for (i = 0; i < len && tablet->query_index < sizeof(tablet->query) - 1; i++) {
 217        tablet->query[tablet->query_index++] = buf[i];
 218    }
 219    tablet->query[tablet->query_index] = 0;
 220
 221    while (tablet->query_index > 0 && (tablet->query[0] == '@'  ||
 222                                       tablet->query[0] == '\r' ||
 223                                       tablet->query[0] == '\n')) {
 224        wctablet_shift_input(tablet, 1);
 225    }
 226    if (!tablet->query_index) {
 227        return len;
 228    }
 229
 230    if (strncmp((char *)tablet->query, "~#", 2) == 0) {
 231        /* init / detect sequence */
 232        trace_wct_init();
 233        wctablet_shift_input(tablet, 2);
 234        wctablet_queue_output(tablet, WC_MODEL_STRING,
 235                              WC_MODEL_STRING_LENGTH);
 236        return len;
 237    }
 238
 239    /* detect line */
 240    pos = strchr((char *)tablet->query, '\r');
 241    if (!pos) {
 242        pos = strchr((char *)tablet->query, '\n');
 243    }
 244    if (!pos) {
 245        return len;
 246    }
 247    clen = pos - (char *)tablet->query;
 248
 249    /* process commands */
 250    if (strncmp((char *)tablet->query, "RE", 2) == 0 &&
 251        clen == 2) {
 252        trace_wct_cmd_re();
 253        wctablet_shift_input(tablet, 3);
 254        wctablet_queue_output(tablet, WC_CONFIG_STRING,
 255                              WC_CONFIG_STRING_LENGTH);
 256
 257    } else if (strncmp((char *)tablet->query, "ST", 2) == 0 &&
 258               clen == 2) {
 259        trace_wct_cmd_st();
 260        wctablet_shift_input(tablet, 3);
 261        tablet->send_events = true;
 262        wctablet_queue_event(tablet);
 263
 264    } else if (strncmp((char *)tablet->query, "SP", 2) == 0 &&
 265               clen == 2) {
 266        trace_wct_cmd_sp();
 267        wctablet_shift_input(tablet, 3);
 268        tablet->send_events = false;
 269
 270    } else if (strncmp((char *)tablet->query, "TS", 2) == 0 &&
 271               clen == 3) {
 272        unsigned int input = tablet->query[2];
 273        uint8_t codes[7] = {
 274            0xa3,
 275            ((input & 0x80) == 0) ? 0x7e : 0x7f,
 276            (((WC_H4(input) & 0x7) ^ 0x5) << 4) | (WC_L4(input) ^ 0x7),
 277            0x03,
 278            0x7f,
 279            0x7f,
 280            0x00,
 281        };
 282        trace_wct_cmd_ts(input);
 283        wctablet_shift_input(tablet, 4);
 284        wctablet_queue_output(tablet, codes, 7);
 285
 286    } else {
 287        tablet->query[clen] = 0; /* terminate line for printing */
 288        trace_wct_cmd_other((char *)tablet->query);
 289        wctablet_shift_input(tablet, clen + 1);
 290
 291    }
 292
 293    return len;
 294}
 295
 296static int wctablet_chr_ioctl(Chardev *chr, int cmd, void *arg)
 297{
 298    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 299    QEMUSerialSetParams *ssp;
 300
 301    switch (cmd) {
 302    case CHR_IOCTL_SERIAL_SET_PARAMS:
 303        ssp = arg;
 304        if (tablet->line_speed != ssp->speed) {
 305            trace_wct_speed(ssp->speed);
 306            wctablet_reset(tablet);
 307            tablet->line_speed = ssp->speed;
 308        }
 309        break;
 310    default:
 311        return -ENOTSUP;
 312    }
 313    return 0;
 314}
 315
 316static void wctablet_chr_finalize(Object *obj)
 317{
 318    TabletChardev *tablet = WCTABLET_CHARDEV(obj);
 319
 320    qemu_input_handler_unregister(tablet->hs);
 321    g_free(tablet);
 322}
 323
 324static void wctablet_chr_open(Chardev *chr,
 325                              ChardevBackend *backend,
 326                              bool *be_opened,
 327                              Error **errp)
 328{
 329    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 330
 331    *be_opened = true;
 332
 333    /* init state machine */
 334    memcpy(tablet->outbuf, WC_FULL_CONFIG_STRING, WC_FULL_CONFIG_STRING_LENGTH);
 335    tablet->outlen = WC_FULL_CONFIG_STRING_LENGTH;
 336    tablet->query_index = 0;
 337
 338    tablet->hs = qemu_input_handler_register((DeviceState *)tablet,
 339                                             &wctablet_handler);
 340}
 341
 342static void wctablet_chr_class_init(ObjectClass *oc, void *data)
 343{
 344    ChardevClass *cc = CHARDEV_CLASS(oc);
 345
 346    cc->open = wctablet_chr_open;
 347    cc->chr_write = wctablet_chr_write;
 348    cc->chr_ioctl = wctablet_chr_ioctl;
 349    cc->chr_accept_input = wctablet_chr_accept_input;
 350}
 351
 352static const TypeInfo wctablet_type_info = {
 353    .name = TYPE_CHARDEV_WCTABLET,
 354    .parent = TYPE_CHARDEV,
 355    .instance_size = sizeof(TabletChardev),
 356    .instance_finalize = wctablet_chr_finalize,
 357    .class_init = wctablet_chr_class_init,
 358};
 359
 360static void register_types(void)
 361{
 362     type_register_static(&wctablet_type_info);
 363}
 364
 365type_init(register_types);
 366