1 /* $OpenBSD: umt.c,v 1.8 2024/05/26 20:06:27 mglocker 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/device.h> 26 27 #include <dev/usb/usb.h> 28 #include <dev/usb/usbhid.h> 29 #include <dev/usb/usbdi.h> 30 #include <dev/usb/usbdi_util.h> 31 #include <dev/usb/usb_quirks.h> 32 #include <dev/usb/uhidev.h> 33 34 #include <dev/wscons/wsconsio.h> 35 #include <dev/wscons/wsmousevar.h> 36 37 #include <dev/hid/hid.h> 38 #include <dev/hid/hidmtvar.h> 39 40 struct umt_softc { 41 struct uhidev sc_hdev; 42 struct hidmt sc_mt; 43 44 int sc_rep_input; 45 int sc_rep_config; 46 int sc_rep_cap; 47 48 u_int32_t sc_quirks; 49 }; 50 51 int umt_enable(void *); 52 void umt_intr(struct uhidev *, void *, u_int); 53 void umt_disable(void *); 54 int umt_ioctl(void *, u_long, caddr_t, int, struct proc *); 55 56 const struct wsmouse_accessops umt_accessops = { 57 umt_enable, 58 umt_ioctl, 59 umt_disable, 60 }; 61 62 int umt_match(struct device *, void *, void *); 63 int umt_find_winptp_reports(struct uhidev_softc *, void *, int, int *, 64 int *, int *); 65 void umt_attach(struct device *, struct device *, void *); 66 int umt_hidev_get_report(struct device *, int, int, void *, int); 67 int umt_hidev_set_report(struct device *, int, int, void *, int); 68 int umt_detach(struct device *, int); 69 70 struct cfdriver umt_cd = { 71 NULL, "umt", DV_DULL 72 }; 73 74 const struct cfattach umt_ca = { 75 sizeof(struct umt_softc), 76 umt_match, 77 umt_attach, 78 umt_detach 79 }; 80 81 int 82 umt_match(struct device *parent, void *match, void *aux) 83 { 84 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; 85 int input = 0, conf = 0, cap = 0; 86 int size; 87 void *desc; 88 89 if (UHIDEV_CLAIM_MULTIPLE_REPORTID(uha)) { 90 uhidev_get_report_desc(uha->parent, &desc, &size); 91 if (umt_find_winptp_reports(uha->parent, desc, size, &input, 92 &conf, &cap)) { 93 uha->claimed[input] = 1; 94 uha->claimed[conf] = 1; 95 uha->claimed[cap] = 1; 96 return (UMATCH_DEVCLASS_DEVSUBCLASS); 97 } 98 } 99 100 return (UMATCH_NONE); 101 } 102 103 int 104 umt_find_winptp_reports(struct uhidev_softc *parent, void *desc, int size, 105 int *input, int *config, int *cap) 106 { 107 int repid; 108 int finput = 0, fconf = 0, fcap = 0; 109 110 if (input != NULL) 111 *input = -1; 112 if (config != NULL) 113 *config = -1; 114 if (cap != NULL) 115 *cap = -1; 116 117 for (repid = 0; repid < parent->sc_nrepid; repid++) { 118 if (hid_report_size(desc, size, hid_input, repid) == 0 && 119 hid_report_size(desc, size, hid_output, repid) == 0 && 120 hid_report_size(desc, size, hid_feature, repid) == 0) 121 continue; 122 123 if (hid_is_collection(desc, size, repid, 124 HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD))) { 125 finput = 1; 126 if (input != NULL && *input == -1) 127 *input = repid; 128 } else if (hid_is_collection(desc, size, repid, 129 HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG))) { 130 fconf = 1; 131 if (config != NULL && *config == -1) 132 *config = repid; 133 } 134 135 /* capabilities report could be anywhere */ 136 if (hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS, 137 HUD_CONTACT_MAX), repid, hid_feature, NULL, NULL)) { 138 fcap = 1; 139 if (cap != NULL && *cap == -1) 140 *cap = repid; 141 } 142 } 143 144 return (fconf && finput && fcap); 145 } 146 147 void 148 umt_attach(struct device *parent, struct device *self, void *aux) 149 { 150 struct umt_softc *sc = (struct umt_softc *)self; 151 struct hidmt *mt = &sc->sc_mt; 152 struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; 153 struct usb_attach_arg *uaa = uha->uaa; 154 int size; 155 void *desc; 156 157 sc->sc_hdev.sc_intr = umt_intr; 158 sc->sc_hdev.sc_parent = uha->parent; 159 sc->sc_hdev.sc_udev = uaa->device; 160 161 usbd_set_idle(uha->parent->sc_udev, uha->parent->sc_ifaceno, 0, 0); 162 163 sc->sc_quirks = usbd_get_quirks(sc->sc_hdev.sc_udev)->uq_flags; 164 165 uhidev_get_report_desc(uha->parent, &desc, &size); 166 umt_find_winptp_reports(uha->parent, desc, size, &sc->sc_rep_input, 167 &sc->sc_rep_config, &sc->sc_rep_cap); 168 169 memset(mt, 0, sizeof(sc->sc_mt)); 170 171 /* assume everything has "natural scrolling" where Y axis is reversed */ 172 mt->sc_flags = HIDMT_REVY; 173 174 mt->hidev_report_type_conv = uhidev_report_type_conv; 175 mt->hidev_get_report = umt_hidev_get_report; 176 mt->hidev_set_report = umt_hidev_set_report; 177 mt->sc_rep_input = sc->sc_rep_input; 178 mt->sc_rep_config = sc->sc_rep_config; 179 mt->sc_rep_cap = sc->sc_rep_cap; 180 181 if (hidmt_setup(self, mt, desc, size) != 0) 182 return; 183 184 if (sc->sc_quirks & UQ_ALWAYS_OPEN) { 185 /* open uhidev and keep it open */ 186 umt_enable(sc); 187 /* but mark the hidmt not in use */ 188 umt_disable(sc); 189 } 190 191 hidmt_attach(mt, &umt_accessops); 192 } 193 194 int 195 umt_hidev_get_report(struct device *self, int type, int id, void *data, int len) 196 { 197 struct umt_softc *sc = (struct umt_softc *)self; 198 int ret; 199 200 ret = uhidev_get_report(sc->sc_hdev.sc_parent, type, id, data, len); 201 return (ret < len); 202 } 203 204 int 205 umt_hidev_set_report(struct device *self, int type, int id, void *data, int len) 206 { 207 struct umt_softc *sc = (struct umt_softc *)self; 208 int ret; 209 210 ret = uhidev_set_report(sc->sc_hdev.sc_parent, type, id, data, len); 211 return (ret < len); 212 } 213 214 int 215 umt_detach(struct device *self, int flags) 216 { 217 struct umt_softc *sc = (struct umt_softc *)self; 218 struct hidmt *mt = &sc->sc_mt; 219 220 return hidmt_detach(mt, flags); 221 } 222 223 void 224 umt_intr(struct uhidev *dev, void *buf, u_int len) 225 { 226 struct umt_softc *sc = (struct umt_softc *)dev; 227 struct hidmt *mt = &sc->sc_mt; 228 229 if (!mt->sc_enabled) 230 return; 231 232 hidmt_input(mt, (uint8_t *)buf, len); 233 } 234 235 int 236 umt_enable(void *v) 237 { 238 struct umt_softc *sc = v; 239 struct hidmt *mt = &sc->sc_mt; 240 int rv; 241 242 if ((rv = hidmt_enable(mt)) != 0) 243 return rv; 244 245 if ((sc->sc_quirks & UQ_ALWAYS_OPEN) && 246 (sc->sc_hdev.sc_state & UHIDEV_OPEN)) 247 rv = 0; 248 else 249 rv = uhidev_open(&sc->sc_hdev); 250 251 hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD); 252 253 return rv; 254 } 255 256 void 257 umt_disable(void *v) 258 { 259 struct umt_softc *sc = v; 260 struct hidmt *mt = &sc->sc_mt; 261 262 hidmt_disable(mt); 263 264 if (sc->sc_quirks & UQ_ALWAYS_OPEN) 265 return; 266 267 uhidev_close(&sc->sc_hdev); 268 } 269 270 int 271 umt_ioctl(void *v, u_long cmd, caddr_t data, int flag, struct proc *p) 272 { 273 struct umt_softc *sc = v; 274 struct hidmt *mt = &sc->sc_mt; 275 int rc; 276 277 rc = uhidev_ioctl(&sc->sc_hdev, cmd, data, flag, p); 278 if (rc != -1) 279 return rc; 280 281 return hidmt_ioctl(mt, cmd, data, flag, p); 282 } 283