Better uwacom(4) support for Intuos S and One S tablets; most of the work
authormiod <miod@openbsd.org>
Sat, 12 Aug 2023 20:47:06 +0000 (20:47 +0000)
committermiod <miod@openbsd.org>
Sat, 12 Aug 2023 20:47:06 +0000 (20:47 +0000)
done by Vladimir Meshcheriakov (first name dot last name at epita somewhere in
frogland), thanks!

Tested by Peter J. Philipp on Intuos Draw and by espie@ on Intuos S.

sys/dev/hid/hid.c
sys/dev/hid/hid.h
sys/dev/hid/hidms.c
sys/dev/hid/hidmsvar.h
sys/dev/usb/uhidev.c
sys/dev/usb/uwacom.c

index c758764..435e547 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: hid.c,v 1.5 2022/05/20 05:03:45 anton Exp $ */
+/*     $OpenBSD: hid.c,v 1.6 2023/08/12 20:47:06 miod Exp $ */
 /*     $NetBSD: hid.c,v 1.23 2002/07/11 21:14:25 augustss Exp $        */
 /*     $FreeBSD: src/sys/dev/usb/hid.c,v 1.11 1999/11/17 22:33:39 n_hibma Exp $ */
 
@@ -657,3 +657,52 @@ hid_is_collection(const void *desc, int size, uint8_t id, int32_t usage)
        hid_end_parse(hd);
        return (0);
 }
+
+struct hid_data *
+hid_get_collection_data(const void *desc, int size, int32_t usage,
+    uint32_t collection)
+{
+       struct hid_data *hd;
+       struct hid_item hi;
+
+       hd = hid_start_parse(desc, size, hid_all);
+
+       DPRINTF("%s: usage=0x%x\n", __func__, usage);
+       while (hid_get_item(hd, &hi)) {
+               DPRINTF("%s: kind=%d id=%d usage=0x%x(0x%x)\n", __func__,
+                   hi.kind, hi.report_ID, hi.usage, usage);
+               if (hi.kind == hid_collection &&
+                   hi.collection == collection && hi.usage == usage) {
+                       DPRINTF("%s: found\n", __func__);
+                       return hd;
+               }
+       }
+       DPRINTF("%s: not found\n", __func__);
+       hid_end_parse(hd);
+       return NULL;
+}
+
+int
+hid_get_id_of_collection(const void *desc, int size, int32_t usage,
+    uint32_t collection)
+{
+       struct hid_data *hd;
+       struct hid_item hi;
+
+       hd = hid_start_parse(desc, size, hid_all);
+
+       DPRINTF("%s: id=%d usage=0x%x\n", __func__, id, usage);
+       while (hid_get_item(hd, &hi)) {
+               DPRINTF("%s: kind=%d id=%d usage=0x%x(0x%x)\n", __func__,
+                   hi.kind, hi.report_ID, hi.usage, usage);
+               if (hi.kind == hid_collection &&
+                   hi.collection == collection && hi.usage == usage) {
+                       DPRINTF("%s: found\n", __func__);
+                       hid_end_parse(hd);
+                       return hi.report_ID;
+               }
+       }
+       DPRINTF("%s: not found\n", __func__);
+       hid_end_parse(hd);
+       return -1;
+}
index 7400e92..3c5cc2a 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: hid.h,v 1.10 2022/05/20 05:03:45 anton Exp $ */
+/*     $OpenBSD: hid.h,v 1.11 2023/08/12 20:47:06 miod Exp $ */
 /*     $NetBSD: hid.h,v 1.8 2002/07/11 21:14:25 augustss Exp $ */
 /*     $FreeBSD: src/sys/dev/usb/hid.h,v 1.7 1999/11/17 22:33:40 n_hibma Exp $ */
 
@@ -93,6 +93,8 @@ int   hid_locate(const void *, int, int32_t, uint8_t, enum hid_kind,
 int32_t        hid_get_data(const uint8_t *buf, int, struct hid_location *);
 uint32_t hid_get_udata(const uint8_t *buf, int, struct hid_location *);
 int    hid_is_collection(const void *, int, uint8_t, int32_t);
+struct hid_data *hid_get_collection_data(const void *, int, int32_t, uint32_t);
+int    hid_get_id_of_collection(const void *, int, int32_t, uint32_t);
 
 #endif /* _KERNEL */
 
@@ -353,6 +355,7 @@ int hid_is_collection(const void *, int, uint8_t, int32_t);
 #define HUD_TOUCHSCREEN                0x0004
 #define HUD_TOUCHPAD           0x0005
 #define HUD_CONFIG             0x000e
+#define HUD_STYLUS             0x0020
 #define HUD_FINGER             0x0022
 #define HUD_TIP_PRESSURE       0x0030
 #define HUD_BARREL_PRESSURE    0x0031
@@ -387,6 +390,12 @@ int        hid_is_collection(const void *, int, uint8_t, int32_t);
 #define HUD_CONTACT_MAX                0x0055
 #define HUD_SCAN_TIME          0x0056
 #define HUD_BUTTON_TYPE                0x0059
+#define HUD_SECONDARY_BARREL_SWITCH    0x005A
+#define HUD_WACOM_X            0x0130
+#define HUD_WACOM_Y            0x0131
+#define HUD_WACOM_DISTANCE             0x0132
+#define HUD_WACOM_PAD_BUTTONS00                0x0910
+#define HUD_WACOM_BATTERY              0x1013
 
 /* Usages, LED */
 #define HUL_NUM_LOCK           0x0001
index 622d5d9..aa86575 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: hidms.c,v 1.9 2022/06/16 20:52:38 bru Exp $ */
+/*     $OpenBSD: hidms.c,v 1.10 2023/08/12 20:47:06 miod Exp $ */
 /*     $NetBSD: ums.c,v 1.60 2003/03/11 16:44:00 augustss Exp $        */
 
 /*
@@ -61,6 +61,188 @@ int hidmsdebug = 0;
 #define MOUSE_FLAGS_MASK       (HIO_CONST | HIO_RELATIVE)
 #define NOTMOUSE(f)            (((f) & MOUSE_FLAGS_MASK) != HIO_RELATIVE)
 
+void
+hidms_stylus_hid_parse(struct hidms *ms, struct hid_data *d,
+    struct hid_location *loc_stylus_btn)
+{
+       struct hid_item h;
+
+       while (hid_get_item(d, &h)) {
+               if (h.kind == hid_endcollection)
+                       break;
+               if (h.kind != hid_input || (h.flags & HIO_CONST) != 0)
+                       continue;
+               /* All the possible stylus reported usages go here */
+#ifdef HIDMS_DEBUG
+               printf("stylus usage: 0x%x\n", h.usage);
+#endif
+               switch (h.usage) {
+               /* Buttons */
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_TIP_SWITCH):
+                       DPRINTF("Stylus usage tip set\n");
+                       if (ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_stylus_btn[ms->sc_num_stylus_buttons++] = h.loc;
+                       ms->sc_flags |= HIDMS_TIP;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_BARREL_SWITCH):
+                       DPRINTF("Stylus usage barrel set\n");
+                       if (ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_stylus_btn[ms->sc_num_stylus_buttons++] = h.loc;
+                       ms->sc_flags |= HIDMS_BARREL;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS,
+                   HUD_SECONDARY_BARREL_SWITCH):
+                       DPRINTF("Stylus usage secondary barrel set\n");
+                       if (ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_stylus_btn[ms->sc_num_stylus_buttons++] = h.loc;
+                       ms->sc_flags |= HIDMS_SEC_BARREL;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_IN_RANGE):
+                       DPRINTF("Stylus usage in range set\n");
+                       if (ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_stylus_btn[ms->sc_num_stylus_buttons++] = h.loc;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_QUALITY):
+                       DPRINTF("Stylus usage quality set\n");
+                       if (ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_stylus_btn[ms->sc_num_stylus_buttons++] = h.loc;
+                       break;
+               /* Axes */
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_WACOM_X):
+                       DPRINTF("Stylus usage x set\n");
+                       ms->sc_loc_x = h.loc;
+                       ms->sc_tsscale.minx = h.logical_minimum;
+                       ms->sc_tsscale.maxx = h.logical_maximum;
+                       ms->sc_flags |= HIDMS_ABSX;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_WACOM_Y):
+                       DPRINTF("Stylus usage y set\n");
+                       ms->sc_loc_y = h.loc;
+                       ms->sc_tsscale.miny = h.logical_minimum;
+                       ms->sc_tsscale.maxy = h.logical_maximum;
+                       ms->sc_flags |= HIDMS_ABSY;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_TIP_PRESSURE):
+                       DPRINTF("Stylus usage pressure set\n");
+                       ms->sc_loc_z = h.loc;
+                       ms->sc_tsscale.minz = h.logical_minimum;
+                       ms->sc_tsscale.maxz = h.logical_maximum;
+                       ms->sc_flags |= HIDMS_Z;
+                       break;
+               case HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_WACOM_DISTANCE):
+                       DPRINTF("Stylus usage distance set\n");
+                       ms->sc_loc_w = h.loc;
+                       ms->sc_tsscale.minw = h.logical_minimum;
+                       ms->sc_tsscale.maxw = h.logical_maximum;
+                       ms->sc_flags |= HIDMS_W;
+                       break;
+               default:
+#ifdef HIDMS_DEBUG
+                       printf("Unknown stylus usage: 0x%x\n",
+                           h.usage);
+#endif
+                       break;
+               }
+       }
+}
+
+void
+hidms_pad_buttons_hid_parse(struct hidms *ms, struct hid_data *d,
+    struct hid_location *loc_pad_btn)
+{
+       struct hid_item h;
+
+       while (hid_get_item(d, &h)) {
+               if (h.kind == hid_endcollection)
+                       break;
+               if (h.kind == hid_input && (h.flags & HIO_CONST) != 0 &&
+                   h.usage == HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS,
+                   HUD_WACOM_PAD_BUTTONS00 | ms->sc_num_pad_buttons)) {
+                       if (ms->sc_num_pad_buttons >= MAX_BUTTONS)
+                               break;
+                       loc_pad_btn[ms->sc_num_pad_buttons++] = h.loc;
+               }
+       }
+}
+
+int
+hidms_wacom_setup(struct device *self, struct hidms *ms, void *desc, int dlen)
+{
+       struct hid_data *hd;
+       int i;
+       struct hid_location loc_pad_btn[MAX_BUTTONS];
+       struct hid_location loc_stylus_btn[MAX_BUTTONS];
+
+       ms->sc_flags = 0;
+
+       /* Set x,y,z and w to zero by default */
+       ms->sc_loc_x.size = 0;
+       ms->sc_loc_y.size = 0;
+       ms->sc_loc_z.size = 0;
+       ms->sc_loc_w.size = 0;
+
+       if ((hd = hid_get_collection_data(desc, dlen,
+            HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_DIGITIZER),
+            HCOLL_APPLICATION))) {
+               DPRINTF("found the global collection\n");
+               hid_end_parse(hd);
+               if ((hd = hid_get_collection_data(desc, dlen,
+                    HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_STYLUS),
+                    HCOLL_PHYSICAL))) {
+                       DPRINTF("found stylus collection\n");
+                       hidms_stylus_hid_parse(ms, hd, loc_stylus_btn);
+                       hid_end_parse(hd);
+               }
+               if ((hd = hid_get_collection_data(desc, dlen,
+                    HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_TABLET_FKEYS),
+                    HCOLL_PHYSICAL))) {
+                       DPRINTF("found tablet keys collection\n");
+                       hidms_pad_buttons_hid_parse(ms, hd, loc_pad_btn);
+                       hid_end_parse(hd);
+               }
+#ifdef notyet
+               if ((hd = hid_get_collection_data(desc, dlen,
+                    HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_WACOM_BATTERY),
+                    HCOLL_PHYSICAL))) {
+                       DPRINTF("found battery collection\n");
+                       /* parse and set the battery info */
+                       /* not yet used */
+                       hid_end_parse(hd);
+               }
+#endif
+               /*
+                * Ignore the device config, it's not really needed;
+                * Ignore the usage 0x10AC which is the debug collection, and
+                * ignore firmware collection and other collections for now.
+                */
+       }
+
+       /* Map the pad and stylus buttons to mouse buttons */
+       for (i = 0; i < ms->sc_num_stylus_buttons; i++)
+               memcpy(&ms->sc_loc_btn[i], &loc_stylus_btn[i],
+                   sizeof(struct hid_location));
+       if (ms->sc_num_pad_buttons + ms->sc_num_stylus_buttons >= MAX_BUTTONS)
+               ms->sc_num_pad_buttons =
+                   MAX_BUTTONS - ms->sc_num_stylus_buttons;
+       for (; i < ms->sc_num_pad_buttons + ms->sc_num_stylus_buttons; i++)
+               memcpy(&ms->sc_loc_btn[i], &loc_pad_btn[i],
+                   sizeof(struct hid_location));
+       ms->sc_num_buttons = i;
+       DPRINTF("Button information\n");
+#ifdef HIDMS_DEBUG
+       for (i = 0; i < ms->sc_num_buttons; i++)
+               printf("size: 0x%x, pos: 0x%x, count: 0x%x\n",
+                   ms->sc_loc_btn[i].size, ms->sc_loc_btn[i].pos,
+                   ms->sc_loc_btn[i].count);
+#endif
+       return 0;
+}
+
 int
 hidms_setup(struct device *self, struct hidms *ms, uint32_t quirks,
     int id, void *desc, int dlen)
@@ -75,11 +257,15 @@ hidms_setup(struct device *self, struct hidms *ms, uint32_t quirks,
 
        ms->sc_flags = quirks;
 
+       /* We are setting up a Wacom tablet, not a regular mouse */
+       if (quirks & HIDMS_WACOM_SETUP)
+               return hidms_wacom_setup(self, ms, desc, dlen);
+
        if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), id,
            hid_input, &ms->sc_loc_x, &flags))
                ms->sc_loc_x.size = 0;
 
-       switch(flags & MOUSE_FLAGS_MASK) {
+       switch (flags & MOUSE_FLAGS_MASK) {
        case 0:
                ms->sc_flags |= HIDMS_ABSX;
                break;
@@ -189,7 +375,7 @@ hidms_setup(struct device *self, struct hidms *ms, uint32_t quirks,
                        break;
        ms->sc_num_buttons = i - 1;
 
-       /* 
+       /*
         * The Kensington Slimblade reports some of its buttons as binary
         * inputs in the first vendor usage page (0xff00). Add such inputs
         * as buttons if the device has this quirk.
index a430b90..3cd754c 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: hidmsvar.h,v 1.2 2021/01/10 16:32:48 thfr Exp $ */
+/*     $OpenBSD: hidmsvar.h,v 1.3 2023/08/12 20:47:06 miod Exp $ */
 /*     $NetBSD: ums.c,v 1.60 2003/03/11 16:44:00 augustss Exp $        */
 
 /*
 struct tsscale {
        int     minx, maxx;
        int     miny, maxy;
+       int     minz, maxz;
+       int     minw, maxw;
        int     swapxy;
        int     resx, resy;
 };
 
 struct hidms {
+       struct device   *sc_device;
+       struct device   *sc_wsmousedev;
+
        int             sc_enabled;
        int             sc_flags;       /* device configuration */
 #define HIDMS_SPUR_BUT_UP      0x0001  /* spurious button up events */
@@ -51,17 +56,20 @@ struct hidms {
 #define HIDMS_LEADINGBYTE      0x0020  /* Unknown leading byte */
 #define HIDMS_ABSX             0x0040  /* X-axis is absolute */
 #define HIDMS_ABSY             0x0080  /* Y-axis is absolute */
-#define HIDMS_TIP              0x0100   /* Tip switch on a digitiser pen */
+#define HIDMS_TIP              0x0100  /* Tip switch on a digitiser pen */
 #define HIDMS_BARREL           0x0200  /* Barrel switch on a digitiser pen */
-#define HIDMS_ERASER           0x0400   /* Eraser switch on a digitiser pen */
+#define HIDMS_ERASER           0x0400  /* Eraser switch on a digitiser pen */
 #define HIDMS_MS_BAD_CLASS     0x0800  /* Mouse doesn't identify properly */
 #define HIDMS_VENDOR_BUTTONS   0x1000  /* extra buttons in vendor page */
+#define HIDMS_SEC_BARREL       0x2000  /* Secondary Barrel switch on a digitiser pen */
+#define HIDMS_WACOM_SETUP      0x4000  /* Requires Wacom-style setup */
 
        int             sc_num_buttons;
        u_int32_t       sc_buttons;     /* mouse button status */
 
-       struct device   *sc_device;
-       struct device   *sc_wsmousedev;
+       /* Wacom-specific fields */
+       int             sc_num_pad_buttons;
+       int             sc_num_stylus_buttons;
 
        /* locators */
        struct hid_location sc_loc_x;
index 26b5b04..c0664fb 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: uhidev.c,v 1.108 2022/05/20 05:03:45 anton Exp $      */
+/*     $OpenBSD: uhidev.c,v 1.109 2023/08/12 20:47:06 miod Exp $       */
 /*     $NetBSD: uhidev.c,v 1.14 2003/03/11 16:44:00 augustss Exp $     */
 
 /*
@@ -139,6 +139,22 @@ uhidev_match(struct device *parent, void *match, void *aux)
        return (UMATCH_IFACECLASS_GENERIC);
 }
 
+int
+uhidev_attach_repid(struct uhidev_softc *sc, struct uhidev_attach_arg *uha,
+    int repid)
+{
+       struct device *dev;
+
+       /* Could already be assigned by uhidev_set_report_dev(). */
+       if (sc->sc_subdevs[repid] != NULL)
+               return 0;
+
+       uha->reportid = repid;
+       dev = config_found_sm(&sc->sc_dev, uha, uhidevprint, NULL);
+       sc->sc_subdevs[repid] = (struct uhidev *)dev;
+       return 1;
+}
+
 void
 uhidev_attach(struct device *parent, struct device *self, void *aux)
 {
@@ -283,6 +299,35 @@ uhidev_attach(struct device *parent, struct device *self, void *aux)
        free(uha.claimed, M_TEMP, nrepid);
        uha.claimed = NULL;
 
+       /* Special case for Wacom tablets */
+       if (uha.uaa->vendor == USB_VENDOR_WACOM) {
+               int ndigitizers = 0;
+               /*
+                * Get all the needed collections (only 3 seem to be of
+                * interest currently).
+                */
+               repid = hid_get_id_of_collection(desc, size,
+                   HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_STYLUS),
+                   HCOLL_PHYSICAL);
+               if (repid >= 0 && repid < nrepid)
+                       ndigitizers += uhidev_attach_repid(sc, &uha, repid);
+               repid = hid_get_id_of_collection(desc, size,
+                   HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_TABLET_FKEYS),
+                   HCOLL_PHYSICAL);
+               if (repid >= 0 && repid < nrepid)
+                       ndigitizers += uhidev_attach_repid(sc, &uha, repid);
+#ifdef notyet  /* not handled in hidms_wacom_setup() yet */
+               repid = hid_get_id_of_collection(desc, size,
+                   HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_WACOM_BATTERY),
+                   HCOLL_PHYSICAL);
+               if (repid >= 0 && repid < nrepid)
+                       ndigitizers += uhidev_attach_repid(sc, &uha, repid);
+#endif
+
+               if (ndigitizers != 0)
+                       return;
+       }
+
        for (repid = 0; repid < nrepid; repid++) {
                DPRINTF(("%s: try repid=%d\n", __func__, repid));
                if (hid_report_size(desc, size, hid_input, repid) == 0 &&
@@ -290,13 +335,7 @@ uhidev_attach(struct device *parent, struct device *self, void *aux)
                    hid_report_size(desc, size, hid_feature, repid) == 0)
                        continue;
 
-               /* Could already be assigned by uhidev_set_report_dev(). */
-               if (sc->sc_subdevs[repid] != NULL)
-                       continue;
-
-               uha.reportid = repid;
-               dev = config_found_sm(self, &uha, uhidevprint, NULL);
-               sc->sc_subdevs[repid] = (struct uhidev *)dev;
+               uhidev_attach_repid(sc, &uha, repid);
        }
 }
 
index f9af276..4fbc8f9 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: uwacom.c,v 1.7 2022/10/08 06:53:06 mglocker Exp $     */
+/*     $OpenBSD: uwacom.c,v 1.8 2023/08/12 20:47:06 miod Exp $ */
 
 /*
  * Copyright (c) 2016 Frank Groeneveld <frank@frankgroeneveld.nl>
@@ -43,22 +43,25 @@ struct uwacom_softc {
        struct hidms            sc_ms;
        struct hid_location     sc_loc_tip_press;
        int                     sc_flags;
+       int                     sc_x, sc_y, sc_z, sc_w;
+       int                     sc_moved;
 };
 
 struct cfdriver uwacom_cd = {
        NULL, "uwacom", DV_DULL
 };
 
-
 const struct usb_devno uwacom_devs[] = {
        { USB_VENDOR_WACOM, USB_PRODUCT_WACOM_INTUOS_DRAW },
        { USB_VENDOR_WACOM, USB_PRODUCT_WACOM_ONE_S },
-       { USB_VENDOR_WACOM, USB_PRODUCT_WACOM_ONE_M }
+       { USB_VENDOR_WACOM, USB_PRODUCT_WACOM_ONE_M },
+       { USB_VENDOR_WACOM, USB_PRODUCT_WACOM_INTUOS_S }
 };
 
 int    uwacom_match(struct device *, void *, void *);
 void   uwacom_attach(struct device *, struct device *, void *);
 int    uwacom_detach(struct device *, int);
+void   uwacom_intr_legacy(struct uhidev *, void *, u_int);
 void   uwacom_intr(struct uhidev *, void *, u_int);
 int    uwacom_enable(void *);
 void   uwacom_disable(void *);
@@ -90,6 +93,9 @@ uwacom_match(struct device *parent, void *match, void *aux)
 
        uhidev_get_report_desc(uha->parent, &desc, &size);
 
+       if (hid_is_collection(desc, size, uha->reportid,
+           HID_USAGE2(HUP_WACOM | HUP_DIGITIZERS, HUD_DIGITIZER)))
+               return (UMATCH_IFACECLASS);
        if (!hid_locate(desc, size, HID_USAGE2(HUP_WACOM, HUG_POINTER),
            uha->reportid, hid_input, NULL, NULL))
                return (UMATCH_NONE);
@@ -104,10 +110,11 @@ uwacom_attach(struct device *parent, struct device *self, void *aux)
        struct hidms *ms = &sc->sc_ms;
        struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
        struct usb_attach_arg *uaa = uha->uaa;
+       static uByte wacom_report_buf[2] = { 0x02, 0x02 };
        int size, repid;
        void *desc;
 
-       sc->sc_hdev.sc_intr = uwacom_intr;
+       sc->sc_hdev.sc_intr = uwacom_intr_legacy;
        sc->sc_hdev.sc_parent = uha->parent;
        sc->sc_hdev.sc_udev = uaa->device;
        sc->sc_hdev.sc_report_id = uha->reportid;
@@ -141,20 +148,23 @@ uwacom_attach(struct device *parent, struct device *self, void *aux)
        ms->sc_loc_btn[2].pos = 2;
        ms->sc_loc_btn[2].size = 1;
 
-       if (uha->uaa->product == USB_PRODUCT_WACOM_ONE_S) {
-               static uByte reportbuf[2] = { 0x02, 0x02 };
-               uhidev_set_report(uha->parent, UHID_FEATURE_REPORT, 2,
-                   &reportbuf, 2);
-               ms->sc_tsscale.maxx = 15200;
-               ms->sc_tsscale.maxy = 9500;
-       }
-
-       if (uha->uaa->product == USB_PRODUCT_WACOM_INTUOS_DRAW) {
+       switch (uha->uaa->product) {
+       case USB_PRODUCT_WACOM_ONE_S:
+       case USB_PRODUCT_WACOM_INTUOS_S:
+               uhidev_set_report(uha->parent, UHID_FEATURE_REPORT,
+                   sc->sc_hdev.sc_report_id, &wacom_report_buf,
+                   sizeof(wacom_report_buf));
+               sc->sc_hdev.sc_intr = uwacom_intr;
+               hidms_setup((struct device *)sc, ms, HIDMS_WACOM_SETUP,
+                   repid, desc, size);
+               break;
+       case USB_PRODUCT_WACOM_INTUOS_DRAW:
                sc->sc_flags = UWACOM_USE_PRESSURE | UWACOM_BIG_ENDIAN;
                sc->sc_loc_tip_press.pos = 43;
                sc->sc_loc_tip_press.size = 8;
                ms->sc_tsscale.maxx = 7600;
                ms->sc_tsscale.maxy = 4750;
+               break;
        }
 
        hidms_attach(ms, &uwacom_accessops);
@@ -170,7 +180,7 @@ uwacom_detach(struct device *self, int flags)
 }
 
 void
-uwacom_intr(struct uhidev *addr, void *buf, u_int len)
+uwacom_intr_legacy(struct uhidev *addr, void *buf, u_int len)
 {
        struct uwacom_softc *sc = (struct uwacom_softc *)addr;
        struct hidms *ms = &sc->sc_ms;
@@ -195,7 +205,7 @@ uwacom_intr(struct uhidev *addr, void *buf, u_int len)
 
        for (i = 0; i < ms->sc_num_buttons; i++)
                if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
-                       buttons |= (1 << i);
+                       buttons |= 1 << i;
 
        if (sc->sc_flags & UWACOM_USE_PRESSURE) {
                pressure = hid_get_data(data, len, &sc->sc_loc_tip_press);
@@ -212,6 +222,63 @@ uwacom_intr(struct uhidev *addr, void *buf, u_int len)
        }
 }
 
+void
+uwacom_intr(struct uhidev *addr, void *buf, u_int len)
+{
+       struct uwacom_softc *sc = (struct uwacom_softc *)addr;
+       struct hidms *ms = &sc->sc_ms;
+       u_int32_t buttons = 0;
+       uint8_t *data = (uint8_t *)buf;
+       int i, j, x, y, dx, dy, dz, dw, pressure, distance;
+
+       if (ms->sc_enabled == 0)
+               return;
+
+       x = hid_get_data(data, len, &ms->sc_loc_x);
+       y = hid_get_data(data, len, &ms->sc_loc_y);
+       pressure = hid_get_data(data, len, &ms->sc_loc_z);
+       distance = hid_get_data(data, len, &ms->sc_loc_w);
+
+       if (!sc->sc_moved) {
+               sc->sc_x = x;
+               sc->sc_y = y;
+               sc->sc_z = pressure;
+               sc->sc_w = distance;
+               sc->sc_moved = 1;
+       }
+
+       dx = sc->sc_x - x;
+       dy = sc->sc_y - y;
+       /* Clamp sensitivity to +/-127 */
+       dz = sc->sc_z / 32 - pressure / 32;
+       dw = sc->sc_w - distance;
+
+       sc->sc_x = x;
+       sc->sc_y = y;
+       sc->sc_z = pressure;
+       sc->sc_w = distance;
+
+       if (sc->sc_flags & UWACOM_BIG_ENDIAN) {
+               x = be16toh(x);
+               y = be16toh(y);
+       }
+
+       for (i = 0; i < ms->sc_num_stylus_buttons; i++)
+               if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
+                       buttons |= 1 << i;
+
+       for (j = 0; i < ms->sc_num_buttons; i++, j++)
+               if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
+                       buttons |= 1 << j;
+
+       if (x != 0 || y != 0 || pressure != 0 || distance != 0 ||
+           buttons != ms->sc_buttons) {
+               wsmouse_motion(ms->sc_wsmousedev, -dx, dy, dz, dw);
+               wsmouse_buttons(ms->sc_wsmousedev, buttons);
+               wsmouse_input_sync(ms->sc_wsmousedev);
+       }
+}
+
 int
 uwacom_enable(void *v)
 {