xref: /qemu/hw/usb/dev-wacom.c (revision 12d1a768bdfea6e27a3a829228840d72507613a1)
1  /*
2   * Wacom PenPartner USB tablet emulation.
3   *
4   * Copyright (c) 2006 Openedhand Ltd.
5   * Author: Andrzej Zaborowski <balrog@zabor.org>
6   *
7   * Based on hw/usb-hid.c:
8   * Copyright (c) 2005 Fabrice Bellard
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 deal
12   * in the Software without restriction, including without limitation the rights
13   * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14   * copies of the Software, and to permit persons to whom the Software is
15   * 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 "ui/console.h"
31  #include "hw/usb.h"
32  #include "hw/usb/hid.h"
33  #include "migration/vmstate.h"
34  #include "qemu/module.h"
35  #include "desc.h"
36  #include "qom/object.h"
37  
38  /* Interface requests */
39  #define WACOM_GET_REPORT    0x2101
40  #define WACOM_SET_REPORT    0x2109
41  
42  struct USBWacomState {
43      USBDevice dev;
44      USBEndpoint *intr;
45      QEMUPutMouseEntry *eh_entry;
46      int dx, dy, dz, buttons_state;
47      int x, y;
48      int mouse_grabbed;
49      enum {
50          WACOM_MODE_HID = 1,
51          WACOM_MODE_WACOM = 2,
52      } mode;
53      uint8_t idle;
54      int changed;
55  };
56  
57  #define TYPE_USB_WACOM "usb-wacom-tablet"
58  OBJECT_DECLARE_SIMPLE_TYPE(USBWacomState, USB_WACOM)
59  
60  enum {
61      STR_MANUFACTURER = 1,
62      STR_PRODUCT,
63      STR_SERIALNUMBER,
64  };
65  
66  static const USBDescStrings desc_strings = {
67      [STR_MANUFACTURER]     = "QEMU",
68      [STR_PRODUCT]          = "Wacom PenPartner",
69      [STR_SERIALNUMBER]     = "1",
70  };
71  
72  static const uint8_t qemu_wacom_hid_report_descriptor[] = {
73      0x05, 0x01,      /* Usage Page (Desktop) */
74      0x09, 0x02,      /* Usage (Mouse) */
75      0xa1, 0x01,      /* Collection (Application) */
76      0x85, 0x01,      /*    Report ID (1) */
77      0x09, 0x01,      /*    Usage (Pointer) */
78      0xa1, 0x00,      /*    Collection (Physical) */
79      0x05, 0x09,      /*       Usage Page (Button) */
80      0x19, 0x01,      /*       Usage Minimum (01h) */
81      0x29, 0x03,      /*       Usage Maximum (03h) */
82      0x15, 0x00,      /*       Logical Minimum (0) */
83      0x25, 0x01,      /*       Logical Maximum (1) */
84      0x95, 0x03,      /*       Report Count (3) */
85      0x75, 0x01,      /*       Report Size (1) */
86      0x81, 0x02,      /*       Input (Data, Variable, Absolute) */
87      0x95, 0x01,      /*       Report Count (1) */
88      0x75, 0x05,      /*       Report Size (5) */
89      0x81, 0x01,      /*       Input (Constant) */
90      0x05, 0x01,      /*       Usage Page (Desktop) */
91      0x09, 0x30,      /*       Usage (X) */
92      0x09, 0x31,      /*       Usage (Y) */
93      0x09, 0x38,      /*       Usage (Wheel) */
94      0x15, 0x81,      /*       Logical Minimum (-127) */
95      0x25, 0x7f,      /*       Logical Maximum (127) */
96      0x75, 0x08,      /*       Report Size (8) */
97      0x95, 0x03,      /*       Report Count (3) */
98      0x81, 0x06,      /*       Input (Data, Variable, Relative) */
99      0x95, 0x03,      /*       Report Count (3) */
100      0x81, 0x01,      /*       Input (Constant) */
101      0xc0,            /*    End Collection */
102      0xc0,            /* End Collection */
103      0x05, 0x0d,      /* Usage Page (Digitizer) */
104      0x09, 0x01,      /* Usage (Digitizer) */
105      0xa1, 0x01,      /* Collection (Application) */
106      0x85, 0x02,      /*    Report ID (2) */
107      0xa1, 0x00,      /*    Collection (Physical) */
108      0x06, 0x00, 0xff,/*       Usage Page (ff00h), vendor-defined */
109      0x09, 0x01,      /*       Usage (01h) */
110      0x15, 0x00,      /*       Logical Minimum (0) */
111      0x26, 0xff, 0x00,/*       Logical Maximum (255) */
112      0x75, 0x08,      /*       Report Size (8) */
113      0x95, 0x07,      /*       Report Count (7) */
114      0x81, 0x02,      /*       Input (Data, Variable, Absolute) */
115      0xc0,            /*    End Collection */
116      0x09, 0x01,      /*    Usage (01h) */
117      0x85, 0x63,      /*    Report ID (99) */
118      0x95, 0x07,      /*    Report Count (7) */
119      0x81, 0x02,      /*    Input (Data, Variable, Absolute) */
120      0x09, 0x01,      /*    Usage (01h) */
121      0x85, 0x02,      /*    Report ID (2) */
122      0x95, 0x01,      /*    Report Count (1) */
123      0xb1, 0x02,      /*    Feature (Variable) */
124      0x09, 0x01,      /*    Usage (01h) */
125      0x85, 0x03,      /*    Report ID (3) */
126      0x95, 0x01,      /*    Report Count (1) */
127      0xb1, 0x02,      /*    Feature (Variable) */
128      0xc0             /* End Collection */
129  };
130  
131  static const USBDescIface desc_iface_wacom = {
132      .bInterfaceNumber              = 0,
133      .bNumEndpoints                 = 1,
134      .bInterfaceClass               = USB_CLASS_HID,
135      .bInterfaceSubClass            = 0x01, /* boot */
136      .bInterfaceProtocol            = 0x02,
137      .ndesc                         = 1,
138      .descs = (USBDescOther[]) {
139          {
140              /* HID descriptor */
141              .data = (uint8_t[]) {
142                  0x09,          /*  u8  bLength */
143                  USB_DT_HID,    /*  u8  bDescriptorType */
144                  0x01, 0x10,    /*  u16 HID_class */
145                  0x00,          /*  u8  country_code */
146                  0x01,          /*  u8  num_descriptors */
147                  USB_DT_REPORT, /*  u8  type: Report */
148                  sizeof(qemu_wacom_hid_report_descriptor), 0, /*  u16 len */
149              },
150          },
151      },
152      .eps = (USBDescEndpoint[]) {
153          {
154              .bEndpointAddress      = USB_DIR_IN | 0x01,
155              .bmAttributes          = USB_ENDPOINT_XFER_INT,
156              .wMaxPacketSize        = 8,
157              .bInterval             = 0x0a,
158          },
159      },
160  };
161  
162  static const USBDescDevice desc_device_wacom = {
163      .bcdUSB                        = 0x0110,
164      .bMaxPacketSize0               = 8,
165      .bNumConfigurations            = 1,
166      .confs = (USBDescConfig[]) {
167          {
168              .bNumInterfaces        = 1,
169              .bConfigurationValue   = 1,
170              .bmAttributes          = USB_CFG_ATT_ONE,
171              .bMaxPower             = 40,
172              .nif = 1,
173              .ifs = &desc_iface_wacom,
174          },
175      },
176  };
177  
178  static const USBDesc desc_wacom = {
179      .id = {
180          .idVendor          = 0x056a,
181          .idProduct         = 0x0000,
182          .bcdDevice         = 0x4210,
183          .iManufacturer     = STR_MANUFACTURER,
184          .iProduct          = STR_PRODUCT,
185          .iSerialNumber     = STR_SERIALNUMBER,
186      },
187      .full = &desc_device_wacom,
188      .str  = desc_strings,
189  };
190  
191  static void usb_mouse_event(void *opaque,
192                              int dx1, int dy1, int dz1, int buttons_state)
193  {
194      USBWacomState *s = opaque;
195  
196      s->dx += dx1;
197      s->dy += dy1;
198      s->dz += dz1;
199      s->buttons_state = buttons_state;
200      s->changed = 1;
201      usb_wakeup(s->intr, 0);
202  }
203  
204  static void usb_wacom_event(void *opaque,
205                              int x, int y, int dz, int buttons_state)
206  {
207      USBWacomState *s = opaque;
208  
209      /* scale to Penpartner resolution */
210      s->x = (x * 5040 / 0x7FFF);
211      s->y = (y * 3780 / 0x7FFF);
212      s->dz += dz;
213      s->buttons_state = buttons_state;
214      s->changed = 1;
215      usb_wakeup(s->intr, 0);
216  }
217  
218  static inline int int_clamp(int val, int vmin, int vmax)
219  {
220      if (val < vmin)
221          return vmin;
222      else if (val > vmax)
223          return vmax;
224      else
225          return val;
226  }
227  
228  static int usb_mouse_poll(USBWacomState *s, uint8_t *buf, int len)
229  {
230      int dx, dy, dz, b, l;
231  
232      if (!s->mouse_grabbed) {
233          s->eh_entry = qemu_add_mouse_event_handler(usb_mouse_event, s, 0,
234                          "QEMU PenPartner tablet");
235          qemu_activate_mouse_event_handler(s->eh_entry);
236          s->mouse_grabbed = 1;
237      }
238  
239      dx = int_clamp(s->dx, -128, 127);
240      dy = int_clamp(s->dy, -128, 127);
241      dz = int_clamp(s->dz, -128, 127);
242  
243      s->dx -= dx;
244      s->dy -= dy;
245      s->dz -= dz;
246  
247      b = 0;
248      if (s->buttons_state & MOUSE_EVENT_LBUTTON)
249          b |= 0x01;
250      if (s->buttons_state & MOUSE_EVENT_RBUTTON)
251          b |= 0x02;
252      if (s->buttons_state & MOUSE_EVENT_MBUTTON)
253          b |= 0x04;
254  
255      buf[0] = b;
256      buf[1] = dx;
257      buf[2] = dy;
258      l = 3;
259      if (len >= 4) {
260          buf[3] = dz;
261          l = 4;
262      }
263      return l;
264  }
265  
266  static int usb_wacom_poll(USBWacomState *s, uint8_t *buf, int len)
267  {
268      int b;
269  
270      if (!s->mouse_grabbed) {
271          s->eh_entry = qemu_add_mouse_event_handler(usb_wacom_event, s, 1,
272                          "QEMU PenPartner tablet");
273          qemu_activate_mouse_event_handler(s->eh_entry);
274          s->mouse_grabbed = 1;
275      }
276  
277      b = 0;
278      if (s->buttons_state & MOUSE_EVENT_LBUTTON)
279          b |= 0x01;
280      if (s->buttons_state & MOUSE_EVENT_RBUTTON)
281          b |= 0x40;
282      if (s->buttons_state & MOUSE_EVENT_MBUTTON)
283          b |= 0x20; /* eraser */
284  
285      if (len < 7)
286          return 0;
287  
288      buf[0] = s->mode;
289      buf[5] = 0x00 | (b & 0xf0);
290      buf[1] = s->x & 0xff;
291      buf[2] = s->x >> 8;
292      buf[3] = s->y & 0xff;
293      buf[4] = s->y >> 8;
294      if (b & 0x3f) {
295          buf[6] = 0;
296      } else {
297          buf[6] = (unsigned char) -127;
298      }
299  
300      return 7;
301  }
302  
303  static void usb_wacom_handle_reset(USBDevice *dev)
304  {
305      USBWacomState *s = (USBWacomState *) dev;
306  
307      s->dx = 0;
308      s->dy = 0;
309      s->dz = 0;
310      s->x = 0;
311      s->y = 0;
312      s->buttons_state = 0;
313      s->mode = WACOM_MODE_HID;
314  }
315  
316  static void usb_wacom_handle_control(USBDevice *dev, USBPacket *p,
317                 int request, int value, int index, int length, uint8_t *data)
318  {
319      USBWacomState *s = (USBWacomState *) dev;
320      int ret;
321  
322      ret = usb_desc_handle_control(dev, p, request, value, index, length, data);
323      if (ret >= 0) {
324          return;
325      }
326  
327      switch (request) {
328      case InterfaceRequest | USB_REQ_GET_DESCRIPTOR:
329          switch (value >> 8) {
330          case 0x22:
331                  memcpy(data, qemu_wacom_hid_report_descriptor,
332                         sizeof(qemu_wacom_hid_report_descriptor));
333                  p->actual_length = sizeof(qemu_wacom_hid_report_descriptor);
334              break;
335          default:
336              return;
337          }
338          break;
339      case WACOM_SET_REPORT:
340          if (s->mouse_grabbed) {
341              qemu_remove_mouse_event_handler(s->eh_entry);
342              s->mouse_grabbed = 0;
343          }
344          s->mode = data[0];
345          break;
346      case WACOM_GET_REPORT:
347          data[0] = 0;
348          data[1] = s->mode;
349          p->actual_length = 2;
350          break;
351      /* USB HID requests */
352      case HID_GET_REPORT:
353          if (s->mode == WACOM_MODE_HID)
354              p->actual_length = usb_mouse_poll(s, data, length);
355          else if (s->mode == WACOM_MODE_WACOM)
356              p->actual_length = usb_wacom_poll(s, data, length);
357          break;
358      case HID_GET_IDLE:
359          data[0] = s->idle;
360          p->actual_length = 1;
361          break;
362      case HID_SET_IDLE:
363          s->idle = (uint8_t) (value >> 8);
364          break;
365      default:
366          p->status = USB_RET_STALL;
367          break;
368      }
369  }
370  
371  static void usb_wacom_handle_data(USBDevice *dev, USBPacket *p)
372  {
373      USBWacomState *s = (USBWacomState *) dev;
374      g_autofree uint8_t *buf = g_malloc(p->iov.size);
375      int len = 0;
376  
377      switch (p->pid) {
378      case USB_TOKEN_IN:
379          if (p->ep->nr == 1) {
380              if (!(s->changed || s->idle)) {
381                  p->status = USB_RET_NAK;
382                  return;
383              }
384              s->changed = 0;
385              if (s->mode == WACOM_MODE_HID)
386                  len = usb_mouse_poll(s, buf, p->iov.size);
387              else if (s->mode == WACOM_MODE_WACOM)
388                  len = usb_wacom_poll(s, buf, p->iov.size);
389              usb_packet_copy(p, buf, len);
390              break;
391          }
392          /* Fall through.  */
393      case USB_TOKEN_OUT:
394      default:
395          p->status = USB_RET_STALL;
396      }
397  }
398  
399  static void usb_wacom_unrealize(USBDevice *dev)
400  {
401      USBWacomState *s = (USBWacomState *) dev;
402  
403      if (s->mouse_grabbed) {
404          qemu_remove_mouse_event_handler(s->eh_entry);
405          s->mouse_grabbed = 0;
406      }
407  }
408  
409  static void usb_wacom_realize(USBDevice *dev, Error **errp)
410  {
411      USBWacomState *s = USB_WACOM(dev);
412      usb_desc_create_serial(dev);
413      usb_desc_init(dev);
414      s->intr = usb_ep_get(dev, USB_TOKEN_IN, 1);
415      s->changed = 1;
416  }
417  
418  static const VMStateDescription vmstate_usb_wacom = {
419      .name = "usb-wacom",
420      .unmigratable = 1,
421  };
422  
423  static void usb_wacom_class_init(ObjectClass *klass, const void *data)
424  {
425      DeviceClass *dc = DEVICE_CLASS(klass);
426      USBDeviceClass *uc = USB_DEVICE_CLASS(klass);
427  
428      uc->product_desc   = "QEMU PenPartner Tablet";
429      uc->usb_desc       = &desc_wacom;
430      uc->realize        = usb_wacom_realize;
431      uc->handle_reset   = usb_wacom_handle_reset;
432      uc->handle_control = usb_wacom_handle_control;
433      uc->handle_data    = usb_wacom_handle_data;
434      uc->unrealize      = usb_wacom_unrealize;
435      set_bit(DEVICE_CATEGORY_INPUT, dc->categories);
436      dc->desc = "QEMU PenPartner Tablet";
437      dc->vmsd = &vmstate_usb_wacom;
438  }
439  
440  static const TypeInfo wacom_info = {
441      .name          = TYPE_USB_WACOM,
442      .parent        = TYPE_USB_DEVICE,
443      .instance_size = sizeof(USBWacomState),
444      .class_init    = usb_wacom_class_init,
445  };
446  
447  static void usb_wacom_register_types(void)
448  {
449      type_register_static(&wacom_info);
450      usb_legacy_register(TYPE_USB_WACOM, "wacom-tablet", NULL);
451  }
452  
453  type_init(usb_wacom_register_types)
454