1 /* $OpenBSD: umt.c,v 1.4 2021/03/24 02:49:57 jcs Exp $ */ 2 /* 3 * USB multitouch touchpad driver for devices conforming to 4 * Windows Precision Touchpad standard 5 * 6 * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collections 7 * 8 * Copyright (c) 2016-2018 joshua stein <jcs@openbsd.org> 9 * 10 * Permission to use, copy, modify, and distribute this software for any 11 * purpose with or without fee is hereby granted, provided that the above 12 * copyright notice and this permission notice appear in all copies. 13 * 14 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 15 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 16 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 17 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 18 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 19 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 20 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 21 */ 22 23 #include <sys/param.h> 24 #include <sys/systm.h> 25 #include <sys/kernel.h> 26 #include <sys/device.h> 27 #include <sys/ioctl.h> 28 29 #include <dev/usb/usb.h> 30 #include <dev/usb/usbhid.h> 31 #include <dev/usb/usbdi.h> 32 #include <dev/usb/usbdi_util.h> 33 #include <dev/usb/usbdevs.h> 34 #include <dev/usb/usb_quirks.h> 35 #include <dev/usb/uhidev.h> 36 37 #include <dev/wscons/wsconsio.h> 38 #include <dev/wscons/wsmousevar.h> 39 40 #include <dev/hid/hid.h> 41 #include <dev/hid/hidmtvar.h> 42 43 struct umt_softc { 44 struct uhidev sc_hdev; 45 struct hidmt sc_mt; 46 47 int sc_rep_input; 48 int sc_rep_config; 49 int sc_rep_cap; 50 51 u_int32_t sc_quirks; 52 }; 53 54 int umt_enable(void *); 55 int umt_open(struct uhidev *); 56 void umt_intr(struct uhidev *, void *, u_int); 57 void umt_disable(void *); 58 int umt_ioctl(void *, u_long, caddr_t, int, struct proc *); 59 60 const struct wsmouse_accessops umt_accessops = { 61 umt_enable, 62 umt_ioctl, 63 umt_disable, 64 }; 65 66 int umt_match(struct device *, void *, void *); 67 int umt_find_winptp_reports(struct uhidev_softc *, void *, int, int *, 68 int *, int *); 69 void umt_attach(struct device *, struct device *, void *); 70 int umt_hidev_get_report(struct device *, int, int, void *, int); 71 int umt_hidev_set_report(struct device *, int, int, void *, int); 72 int umt_detach(struct device *, int); 73 74 struct cfdriver umt_cd = { 75 NULL, "umt", DV_DULL 76 }; 77 78 const struct cfattach umt_ca = { 79 sizeof(struct umt_softc), 80 umt_match, 81 umt_attach, 82 umt_detach 83 }; 84 85 int 86 umt_match(struct device *parent, void *match, void *aux) 87 { 88 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; 89 int input = 0, conf = 0, cap = 0; 90 int size; 91 void *desc; 92 93 if (uha->reportid == UHIDEV_CLAIM_MULTIPLE_REPORTID) { 94 uhidev_get_report_desc(uha->parent, &desc, &size); 95 if (umt_find_winptp_reports(uha->parent, desc, size, &input, 96 &conf, &cap)) { 97 uha->claimed[input] = 1; 98 uha->claimed[conf] = 1; 99 uha->claimed[cap] = 1; 100 return (UMATCH_DEVCLASS_DEVSUBCLASS); 101 } 102 } 103 104 return (UMATCH_NONE); 105 } 106 107 int 108 umt_find_winptp_reports(struct uhidev_softc *parent, void *desc, int size, 109 int *input, int *config, int *cap) 110 { 111 int repid; 112 int finput = 0, fconf = 0, fcap = 0; 113 114 if (input != NULL) 115 *input = -1; 116 if (config != NULL) 117 *config = -1; 118 if (cap != NULL) 119 *cap = -1; 120 121 for (repid = 0; repid < parent->sc_nrepid; repid++) { 122 if (hid_report_size(desc, size, hid_input, repid) == 0 && 123 hid_report_size(desc, size, hid_output, repid) == 0 && 124 hid_report_size(desc, size, hid_feature, repid) == 0) 125 continue; 126 127 if (hid_is_collection(desc, size, repid, 128 HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD))) { 129 finput = 1; 130 if (input != NULL && *input == -1) 131 *input = repid; 132 } else if (hid_is_collection(desc, size, repid, 133 HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG))) { 134 fconf = 1; 135 if (config != NULL && *config == -1) 136 *config = repid; 137 } 138 139 /* capabilities report could be anywhere */ 140 if (hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, 141 HUD_CONTACT_MAX), repid, hid_feature, NULL, NULL)) { 142 fcap = 1; 143 if (cap != NULL && *cap == -1) 144 *cap = repid; 145 } 146 } 147 148 return (fconf && finput && fcap); 149 } 150 151 void 152 umt_attach(struct device *parent, struct device *self, void *aux) 153 { 154 struct umt_softc *sc = (struct umt_softc *)self; 155 struct hidmt *mt = &sc->sc_mt; 156 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; 157 struct usb_attach_arg *uaa = uha->uaa; 158 int size; 159 void *desc; 160 161 sc->sc_hdev.sc_intr = umt_intr; 162 sc->sc_hdev.sc_parent = uha->parent; 163 sc->sc_hdev.sc_udev = uaa->device; 164 165 usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0); 166 167 sc->sc_quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags; 168 169 uhidev_get_report_desc(uha->parent, &desc, &size); 170 umt_find_winptp_reports(uha->parent, desc, size, &sc->sc_rep_input, 171 &sc->sc_rep_config, &sc->sc_rep_cap); 172 173 memset(mt, 0, sizeof(sc->sc_mt)); 174 175 /* assume everything has "natural scrolling" where Y axis is reversed */ 176 mt->sc_flags = HIDMT_REVY; 177 178 mt->hidev_report_type_conv = uhidev_report_type_conv; 179 mt->hidev_get_report = umt_hidev_get_report; 180 mt->hidev_set_report = umt_hidev_set_report; 181 mt->sc_rep_input = sc->sc_rep_input; 182 mt->sc_rep_config = sc->sc_rep_config; 183 mt->sc_rep_cap = sc->sc_rep_cap; 184 185 if (hidmt_setup(self, mt, desc, size) != 0) 186 return; 187 188 hidmt_attach(mt, &umt_accessops); 189 190 if (sc->sc_quirks & UQ_ALWAYS_OPEN) { 191 /* open uhidev and keep it open */ 192 umt_enable(sc); 193 /* but mark the hidmt not in use */ 194 umt_disable(sc); 195 } 196 } 197 198 int 199 umt_hidev_get_report(struct device *self, int type, int id, void *data, int len) 200 { 201 struct umt_softc *sc = (struct umt_softc *)self; 202 int ret; 203 204 ret = uhidev_get_report(sc->sc_hdev.sc_parent, type, id, data, len); 205 return (ret < len); 206 } 207 208 int 209 umt_hidev_set_report(struct device *self, int type, int id, void *data, int len) 210 { 211 struct umt_softc *sc = (struct umt_softc *)self; 212 int ret; 213 214 ret = uhidev_set_report(sc->sc_hdev.sc_parent, type, id, data, len); 215 return (ret < len); 216 } 217 218 int 219 umt_detach(struct device *self, int flags) 220 { 221 struct umt_softc *sc = (struct umt_softc *)self; 222 struct hidmt *mt = &sc->sc_mt; 223 224 return hidmt_detach(mt, flags); 225 } 226 227 void 228 umt_intr(struct uhidev *dev, void *buf, u_int len) 229 { 230 struct umt_softc *sc = (struct umt_softc *)dev; 231 struct hidmt *mt = &sc->sc_mt; 232 233 if (!mt->sc_enabled) 234 return; 235 236 hidmt_input(mt, (uint8_t *)buf, len); 237 } 238 239 int 240 umt_enable(void *v) 241 { 242 struct umt_softc *sc = v; 243 struct hidmt *mt = &sc->sc_mt; 244 int rv; 245 246 if ((rv = hidmt_enable(mt)) != 0) 247 return rv; 248 249 if ((sc->sc_quirks & UQ_ALWAYS_OPEN) && 250 (sc->sc_hdev.sc_state & UHIDEV_OPEN)) 251 rv = 0; 252 else 253 rv = uhidev_open(&sc->sc_hdev); 254 255 hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD); 256 257 return rv; 258 } 259 260 void 261 umt_disable(void *v) 262 { 263 struct umt_softc *sc = v; 264 struct hidmt *mt = &sc->sc_mt; 265 266 hidmt_disable(mt); 267 268 if (sc->sc_quirks & UQ_ALWAYS_OPEN) 269 return; 270 271 uhidev_close(&sc->sc_hdev); 272 } 273 274 int 275 umt_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) 276 { 277 struct umt_softc *sc = v; 278 struct hidmt *mt = &sc->sc_mt; 279 int rc; 280 281 rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p); 282 if (rc != -1) 283 return rc; 284 285 return hidmt_ioctl(mt, cmd, data, flag, p); 286 } 287