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#include "qom/object.h"
  36
  37
  38#define WC_OUTPUT_BUF_MAX_LEN 512
  39#define WC_COMMAND_MAX_LEN 60
  40
  41#define WC_L7(n) ((n) & 127)
  42#define WC_M7(n) (((n) >> 7) & 127)
  43#define WC_H2(n) ((n) >> 14)
  44
  45#define WC_L4(n) ((n) & 15)
  46#define WC_H4(n) (((n) >> 4) & 15)
  47
  48/* Model string and config string */
  49#define WC_MODEL_STRING_LENGTH 18
  50uint8_t WC_MODEL_STRING[WC_MODEL_STRING_LENGTH + 1] = "~#CT-0045R,V1.3-5,";
  51
  52#define WC_CONFIG_STRING_LENGTH 8
  53uint8_t WC_CONFIG_STRING[WC_CONFIG_STRING_LENGTH + 1] = "96,N,8,0";
  54
  55#define WC_FULL_CONFIG_STRING_LENGTH 61
  56uint8_t WC_FULL_CONFIG_STRING[WC_FULL_CONFIG_STRING_LENGTH + 1] = {
  57    0x5c, 0x39, 0x36, 0x2c, 0x4e, 0x2c, 0x38, 0x2c,
  58    0x31, 0x28, 0x01, 0x24, 0x57, 0x41, 0x43, 0x30,
  59    0x30, 0x34, 0x35, 0x5c, 0x5c, 0x50, 0x45, 0x4e, 0x5c,
  60    0x57, 0x41, 0x43, 0x30, 0x30, 0x30, 0x30, 0x5c,
  61    0x54, 0x61, 0x62, 0x6c, 0x65, 0x74, 0x0d, 0x0a,
  62    0x43, 0x54, 0x2d, 0x30, 0x30, 0x34, 0x35, 0x52,
  63    0x2c, 0x56, 0x31, 0x2e, 0x33, 0x2d, 0x35, 0x0d,
  64    0x0a, 0x45, 0x37, 0x29
  65};
  66
  67/* This structure is used to save private info for Wacom Tablet. */
  68struct TabletChardev {
  69    Chardev parent;
  70    QemuInputHandlerState *hs;
  71
  72    /* Query string from serial */
  73    uint8_t query[100];
  74    int query_index;
  75
  76    /* Command to be sent to serial port */
  77    uint8_t outbuf[WC_OUTPUT_BUF_MAX_LEN];
  78    int outlen;
  79
  80    int line_speed;
  81    bool send_events;
  82    int axis[INPUT_AXIS__MAX];
  83    bool btns[INPUT_BUTTON__MAX];
  84
  85};
  86typedef struct TabletChardev TabletChardev;
  87
  88#define TYPE_CHARDEV_WCTABLET "chardev-wctablet"
  89DECLARE_INSTANCE_CHECKER(TabletChardev, WCTABLET_CHARDEV,
  90                         TYPE_CHARDEV_WCTABLET)
  91
  92
  93static void wctablet_chr_accept_input(Chardev *chr);
  94
  95static void wctablet_shift_input(TabletChardev *tablet, int count)
  96{
  97    tablet->query_index -= count;
  98    memmove(tablet->query, tablet->query + count, tablet->query_index);
  99    tablet->query[tablet->query_index] = 0;
 100}
 101
 102static void wctablet_queue_output(TabletChardev *tablet, uint8_t *buf, int count)
 103{
 104    if (tablet->outlen + count > sizeof(tablet->outbuf)) {
 105        return;
 106    }
 107
 108    memcpy(tablet->outbuf + tablet->outlen, buf, count);
 109    tablet->outlen += count;
 110    wctablet_chr_accept_input(CHARDEV(tablet));
 111}
 112
 113static void wctablet_reset(TabletChardev *tablet)
 114{
 115    /* clear buffers */
 116    tablet->query_index = 0;
 117    tablet->outlen = 0;
 118    /* reset state */
 119    tablet->send_events = false;
 120}
 121
 122static void wctablet_queue_event(TabletChardev *tablet)
 123{
 124    uint8_t codes[8] = { 0xe0, 0, 0, 0, 0, 0, 0 };
 125
 126    if (tablet->line_speed != 9600) {
 127        return;
 128    }
 129
 130    int newX = tablet->axis[INPUT_AXIS_X] * 0.1537;
 131    int nexY = tablet->axis[INPUT_AXIS_Y] * 0.1152;
 132
 133    codes[0] = codes[0] | WC_H2(newX);
 134    codes[1] = codes[1] | WC_M7(newX);
 135    codes[2] = codes[2] | WC_L7(newX);
 136
 137    codes[3] = codes[3] | WC_H2(nexY);
 138    codes[4] = codes[4] | WC_M7(nexY);
 139    codes[5] = codes[5] | WC_L7(nexY);
 140
 141    if (tablet->btns[INPUT_BUTTON_LEFT]) {
 142        codes[0] = 0xa0;
 143    }
 144
 145    wctablet_queue_output(tablet, codes, 7);
 146}
 147
 148static void wctablet_input_event(DeviceState *dev, QemuConsole *src,
 149                                InputEvent *evt)
 150{
 151    TabletChardev *tablet = (TabletChardev *)dev;
 152    InputMoveEvent *move;
 153    InputBtnEvent *btn;
 154
 155    switch (evt->type) {
 156    case INPUT_EVENT_KIND_ABS:
 157        move = evt->u.abs.data;
 158        tablet->axis[move->axis] = move->value;
 159        break;
 160
 161    case INPUT_EVENT_KIND_BTN:
 162        btn = evt->u.btn.data;
 163        tablet->btns[btn->button] = btn->down;
 164        break;
 165
 166    default:
 167        /* keep gcc happy */
 168        break;
 169    }
 170}
 171
 172static void wctablet_input_sync(DeviceState *dev)
 173{
 174    TabletChardev *tablet = (TabletChardev *)dev;
 175
 176    if (tablet->send_events) {
 177        wctablet_queue_event(tablet);
 178    }
 179}
 180
 181static QemuInputHandler wctablet_handler = {
 182    .name  = "QEMU Wacom Pen Tablet",
 183    .mask  = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
 184    .event = wctablet_input_event,
 185    .sync  = wctablet_input_sync,
 186};
 187
 188static void wctablet_chr_accept_input(Chardev *chr)
 189{
 190    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 191    int len, canWrite;
 192
 193    canWrite = qemu_chr_be_can_write(chr);
 194    len = canWrite;
 195    if (len > tablet->outlen) {
 196        len = tablet->outlen;
 197    }
 198
 199    if (len) {
 200        qemu_chr_be_write(chr, tablet->outbuf, len);
 201        tablet->outlen -= len;
 202        if (tablet->outlen) {
 203            memmove(tablet->outbuf, tablet->outbuf + len, tablet->outlen);
 204        }
 205    }
 206}
 207
 208static int wctablet_chr_write(struct Chardev *chr,
 209                              const uint8_t *buf, int len)
 210{
 211    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 212    unsigned int i, clen;
 213    char *pos;
 214
 215    if (tablet->line_speed != 9600) {
 216        return len;
 217    }
 218    for (i = 0; i < len && tablet->query_index < sizeof(tablet->query) - 1; i++) {
 219        tablet->query[tablet->query_index++] = buf[i];
 220    }
 221    tablet->query[tablet->query_index] = 0;
 222
 223    while (tablet->query_index > 0 && (tablet->query[0] == '@'  ||
 224                                       tablet->query[0] == '\r' ||
 225                                       tablet->query[0] == '\n')) {
 226        wctablet_shift_input(tablet, 1);
 227    }
 228    if (!tablet->query_index) {
 229        return len;
 230    }
 231
 232    if (strncmp((char *)tablet->query, "~#", 2) == 0) {
 233        /* init / detect sequence */
 234        trace_wct_init();
 235        wctablet_shift_input(tablet, 2);
 236        wctablet_queue_output(tablet, WC_MODEL_STRING,
 237                              WC_MODEL_STRING_LENGTH);
 238        return len;
 239    }
 240
 241    /* detect line */
 242    pos = strchr((char *)tablet->query, '\r');
 243    if (!pos) {
 244        pos = strchr((char *)tablet->query, '\n');
 245    }
 246    if (!pos) {
 247        return len;
 248    }
 249    clen = pos - (char *)tablet->query;
 250
 251    /* process commands */
 252    if (strncmp((char *)tablet->query, "RE", 2) == 0 &&
 253        clen == 2) {
 254        trace_wct_cmd_re();
 255        wctablet_shift_input(tablet, 3);
 256        wctablet_queue_output(tablet, WC_CONFIG_STRING,
 257                              WC_CONFIG_STRING_LENGTH);
 258
 259    } else if (strncmp((char *)tablet->query, "ST", 2) == 0 &&
 260               clen == 2) {
 261        trace_wct_cmd_st();
 262        wctablet_shift_input(tablet, 3);
 263        tablet->send_events = true;
 264        wctablet_queue_event(tablet);
 265
 266    } else if (strncmp((char *)tablet->query, "SP", 2) == 0 &&
 267               clen == 2) {
 268        trace_wct_cmd_sp();
 269        wctablet_shift_input(tablet, 3);
 270        tablet->send_events = false;
 271
 272    } else if (strncmp((char *)tablet->query, "TS", 2) == 0 &&
 273               clen == 3) {
 274        unsigned int input = tablet->query[2];
 275        uint8_t codes[7] = {
 276            0xa3,
 277            ((input & 0x80) == 0) ? 0x7e : 0x7f,
 278            (((WC_H4(input) & 0x7) ^ 0x5) << 4) | (WC_L4(input) ^ 0x7),
 279            0x03,
 280            0x7f,
 281            0x7f,
 282            0x00,
 283        };
 284        trace_wct_cmd_ts(input);
 285        wctablet_shift_input(tablet, 4);
 286        wctablet_queue_output(tablet, codes, 7);
 287
 288    } else {
 289        tablet->query[clen] = 0; /* terminate line for printing */
 290        trace_wct_cmd_other((char *)tablet->query);
 291        wctablet_shift_input(tablet, clen + 1);
 292
 293    }
 294
 295    return len;
 296}
 297
 298static int wctablet_chr_ioctl(Chardev *chr, int cmd, void *arg)
 299{
 300    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 301    QEMUSerialSetParams *ssp;
 302
 303    switch (cmd) {
 304    case CHR_IOCTL_SERIAL_SET_PARAMS:
 305        ssp = arg;
 306        if (tablet->line_speed != ssp->speed) {
 307            trace_wct_speed(ssp->speed);
 308            wctablet_reset(tablet);
 309            tablet->line_speed = ssp->speed;
 310        }
 311        break;
 312    default:
 313        return -ENOTSUP;
 314    }
 315    return 0;
 316}
 317
 318static void wctablet_chr_finalize(Object *obj)
 319{
 320    TabletChardev *tablet = WCTABLET_CHARDEV(obj);
 321
 322    qemu_input_handler_unregister(tablet->hs);
 323}
 324
 325static void wctablet_chr_open(Chardev *chr,
 326                              ChardevBackend *backend,
 327                              bool *be_opened,
 328                              Error **errp)
 329{
 330    TabletChardev *tablet = WCTABLET_CHARDEV(chr);
 331
 332    *be_opened = true;
 333
 334    /* init state machine */
 335    memcpy(tablet->outbuf, WC_FULL_CONFIG_STRING, WC_FULL_CONFIG_STRING_LENGTH);
 336    tablet->outlen = WC_FULL_CONFIG_STRING_LENGTH;
 337    tablet->query_index = 0;
 338
 339    tablet->hs = qemu_input_handler_register((DeviceState *)tablet,
 340                                             &wctablet_handler);
 341}
 342
 343static void wctablet_chr_class_init(ObjectClass *oc, void *data)
 344{
 345    ChardevClass *cc = CHARDEV_CLASS(oc);
 346
 347    cc->open = wctablet_chr_open;
 348    cc->chr_write = wctablet_chr_write;
 349    cc->chr_ioctl = wctablet_chr_ioctl;
 350    cc->chr_accept_input = wctablet_chr_accept_input;
 351}
 352
 353static const TypeInfo wctablet_type_info = {
 354    .name = TYPE_CHARDEV_WCTABLET,
 355    .parent = TYPE_CHARDEV,
 356    .instance_size = sizeof(TabletChardev),
 357    .instance_finalize = wctablet_chr_finalize,
 358    .class_init = wctablet_chr_class_init,
 359};
 360
 361static void register_types(void)
 362{
 363     type_register_static(&wctablet_type_info);
 364}
 365
 366type_init(register_types);
 367