1 /* $OpenBSD: hidmt.c,v 1.2 2016/03/30 23:34:12 bru Exp $ */ 2 /* 3 * HID multitouch driver for devices conforming to Windows Precision Touchpad 4 * standard 5 * 6 * https://msdn.microsoft.com/en-us/library/windows/hardware/dn467314%28v=vs.85%29.aspx 7 * 8 * Copyright (c) 2016 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 #include <sys/malloc.h> 29 30 #include <dev/wscons/wsconsio.h> 31 #include <dev/wscons/wsmousevar.h> 32 33 #include <dev/hid/hid.h> 34 #include <dev/hid/hidmtvar.h> 35 36 /* #define HIDMT_DEBUG */ 37 38 #ifdef HIDMT_DEBUG 39 #define DPRINTF(x) printf x 40 #else 41 #define DPRINTF(x) 42 #endif 43 44 int 45 hidmt_setup(struct device *self, struct hidmt *mt, void *desc, int dlen) 46 { 47 struct hid_location cap; 48 int32_t d; 49 uint8_t *rep; 50 int capsize; 51 52 struct hid_data *hd; 53 struct hid_item h; 54 55 mt->sc_device = self; 56 mt->sc_rep_input_size = hid_report_size(desc, dlen, hid_input, 57 mt->sc_rep_input); 58 59 mt->sc_minx = mt->sc_miny = mt->sc_maxx = mt->sc_maxy = 0; 60 61 capsize = hid_report_size(desc, dlen, hid_feature, mt->sc_rep_cap); 62 rep = malloc(capsize, M_DEVBUF, M_NOWAIT | M_ZERO); 63 64 if (mt->hidev_get_report(mt->sc_device, hid_feature, mt->sc_rep_cap, 65 rep, capsize)) { 66 printf("\n%s: failed getting capability report\n", 67 self->dv_xname); 68 return 1; 69 } 70 71 /* find maximum number of contacts being reported per input report */ 72 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), 73 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 74 printf("\n%s: can't find maximum contacts\n", self->dv_xname); 75 return 1; 76 } 77 78 d = hid_get_udata(rep, capsize, &cap); 79 if (d > HIDMT_MAX_CONTACTS) { 80 printf("\n%s: contacts %d > max %d\n", self->dv_xname, d, 81 HIDMT_MAX_CONTACTS); 82 return 1; 83 } 84 else 85 mt->sc_num_contacts = d; 86 87 /* find whether this is a clickpad or not */ 88 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE), 89 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 90 printf("\n%s: can't find button type\n", self->dv_xname); 91 return 1; 92 } 93 94 d = hid_get_udata(rep, capsize, &cap); 95 mt->sc_clickpad = (d == 0); 96 97 /* 98 * Walk HID descriptor and store usages we care about to know what to 99 * pluck out of input reports. 100 */ 101 102 SIMPLEQ_INIT(&mt->sc_inputs); 103 104 hd = hid_start_parse(desc, dlen, hid_input); 105 while (hid_get_item(hd, &h)) { 106 struct hidmt_input *input; 107 108 if (h.report_ID != mt->sc_rep_input) 109 continue; 110 111 switch (h.usage) { 112 /* contact level usages */ 113 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 114 if (h.physical_minimum < mt->sc_minx) 115 mt->sc_minx = h.physical_minimum; 116 if (h.physical_maximum > mt->sc_maxx) 117 mt->sc_maxx = h.physical_maximum; 118 119 break; 120 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 121 if (h.physical_minimum < mt->sc_miny) 122 mt->sc_miny = h.physical_minimum; 123 if (h.physical_maximum > mt->sc_maxy) 124 mt->sc_maxy = h.physical_maximum; 125 126 break; 127 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 128 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 129 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 130 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 131 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 132 133 /* report level usages */ 134 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 135 case HID_USAGE2(HUP_BUTTON, 0x01): 136 break; 137 default: 138 continue; 139 } 140 141 input = malloc(sizeof(*input), M_DEVBUF, M_NOWAIT | M_ZERO); 142 memcpy(&input->loc, &h.loc, sizeof(struct hid_location)); 143 input->usage = h.usage; 144 145 SIMPLEQ_INSERT_TAIL(&mt->sc_inputs, input, entry); 146 } 147 hid_end_parse(hd); 148 149 if (mt->sc_maxx <= 0 || mt->sc_maxy <= 0) { 150 printf("\n%s: invalid max X/Y %d/%d\n", self->dv_xname, 151 mt->sc_maxx, mt->sc_maxy); 152 return 1; 153 } 154 155 if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT)) { 156 printf("\n%s: switch to multitouch mode failed\n", 157 self->dv_xname); 158 return 1; 159 } 160 161 return 0; 162 } 163 164 void 165 hidmt_attach(struct hidmt *mt, const struct wsmouse_accessops *ops) 166 { 167 struct wsmousedev_attach_args a; 168 169 printf(": %spad, %d contact%s\n", 170 (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, 171 (mt->sc_num_contacts == 1 ? "" : "s")); 172 173 a.accessops = ops; 174 a.accesscookie = mt->sc_device; 175 mt->sc_wsmousedev = config_found(mt->sc_device, &a, wsmousedevprint); 176 } 177 178 int 179 hidmt_detach(struct hidmt *mt, int flags) 180 { 181 int rv = 0; 182 183 if (mt->sc_wsmousedev != NULL) 184 rv = config_detach(mt->sc_wsmousedev, flags); 185 186 return (rv); 187 } 188 189 int 190 hidmt_set_input_mode(struct hidmt *mt, int mode) 191 { 192 return mt->hidev_set_report(mt->sc_device, hid_feature, 193 mt->sc_rep_config, &mode, 1); 194 } 195 196 void 197 hidmt_input(struct hidmt *mt, uint8_t *data, u_int len) 198 { 199 struct hidmt_input *hi; 200 struct hidmt_contact hc; 201 int32_t d, firstu = 0; 202 int contactcount = 0, seencontacts = 0, tips = 0, i, s; 203 204 if (len != mt->sc_rep_input_size) { 205 DPRINTF(("%s: %s: length %d not %d, ignoring\n", 206 mt->sc_device->dv_xname, __func__, len, 207 mt->sc_rep_input_size)); 208 return; 209 } 210 211 /* 212 * "In Parallel mode, devices report all contact information in a 213 * single packet. Each physical contact is represented by a logical 214 * collection that is embedded in the top-level collection." 215 * 216 * Since additional contacts that were not present will still be in the 217 * report with contactid=0 but contactids are zero-based, find 218 * contactcount first. 219 */ 220 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 221 if (hi->usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) 222 contactcount = hid_get_udata(data, len, 223 &hi->loc); 224 } 225 226 if (!contactcount) { 227 DPRINTF(("%s: %s: no contactcount in report\n", 228 mt->sc_device->dv_xname, __func__)); 229 return; 230 } 231 232 /* 233 * Walk through each input we know about and fetch its data from the 234 * report, storing it in a temporary contact. Once we see our first 235 * usage again, we'll know we saw all usages being presented for that 236 * contact. 237 */ 238 bzero(&hc, sizeof(struct hidmt_contact)); 239 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 240 d = hid_get_udata(data, len, &hi->loc); 241 242 if (firstu && hi->usage == firstu) { 243 if (seencontacts < contactcount && 244 hc.contactid < HIDMT_MAX_CONTACTS) { 245 hc.seen = 1; 246 memcpy(&mt->sc_contacts[hc.contactid], &hc, 247 sizeof(struct hidmt_contact)); 248 seencontacts++; 249 } 250 251 bzero(&hc, sizeof(struct hidmt_contact)); 252 } 253 else if (!firstu) 254 firstu = hi->usage; 255 256 switch (hi->usage) { 257 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 258 hc.x = d; 259 break; 260 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 261 if (mt->sc_flags & HIDMT_REVY) 262 hc.y = mt->sc_maxy - d; 263 else 264 hc.y = d; 265 break; 266 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 267 hc.tip = d; 268 if (d) 269 tips++; 270 break; 271 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 272 hc.confidence = d; 273 break; 274 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 275 hc.width = d; 276 break; 277 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 278 hc.height = d; 279 break; 280 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 281 hc.contactid = d; 282 break; 283 284 /* these will only appear once per report */ 285 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 286 contactcount = d; 287 break; 288 case HID_USAGE2(HUP_BUTTON, 0x01): 289 mt->sc_button = (d != 0); 290 break; 291 } 292 } 293 if (seencontacts < contactcount && hc.contactid < HIDMT_MAX_CONTACTS) { 294 hc.seen = 1; 295 memcpy(&mt->sc_contacts[hc.contactid], &hc, 296 sizeof(struct hidmt_contact)); 297 seencontacts++; 298 } 299 300 s = spltty(); 301 for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { 302 if (!mt->sc_contacts[i].seen) 303 continue; 304 305 mt->sc_contacts[i].seen = 0; 306 307 DPRINTF(("%s: %s: contact %d of %d: id %d, x %d, y %d, " 308 "touch %d, confidence %d, width %d, height %d " 309 "(button %d)\n", 310 mt->sc_device->dv_xname, __func__, 311 i + 1, contactcount, 312 mt->sc_contacts[i].contactid, 313 mt->sc_contacts[i].x, 314 mt->sc_contacts[i].y, 315 mt->sc_contacts[i].tip, 316 mt->sc_contacts[i].confidence, 317 mt->sc_contacts[i].width, 318 mt->sc_contacts[i].height, 319 mt->sc_button)); 320 321 if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) 322 continue; 323 324 if (mt->sc_wsmode == WSMOUSE_NATIVE) { 325 int width = 0; 326 if (mt->sc_contacts[i].tip) { 327 width = mt->sc_contacts[i].width; 328 if (width < 50) 329 width = 50; 330 } 331 332 WSMOUSE_TOUCH(mt->sc_wsmousedev, mt->sc_button, 333 (mt->last_x = mt->sc_contacts[i].x), 334 (mt->last_y = mt->sc_contacts[i].y), 335 width, tips); 336 } else { 337 WSMOUSE_INPUT(mt->sc_wsmousedev, mt->sc_button, 338 (mt->last_x - mt->sc_contacts[i].x), 339 (mt->last_y - mt->sc_contacts[i].y), 340 0, 0); 341 mt->last_x = mt->sc_contacts[i].x; 342 mt->last_y = mt->sc_contacts[i].y; 343 } 344 345 /* 346 * XXX: wscons can only handle one finger of data 347 */ 348 break; 349 } 350 351 splx(s); 352 } 353 354 int 355 hidmt_enable(struct hidmt *mt) 356 { 357 if (mt->sc_enabled) 358 return EBUSY; 359 360 mt->sc_enabled = 1; 361 362 return 0; 363 } 364 365 int 366 hidmt_ioctl(struct hidmt *mt, u_long cmd, caddr_t data, int flag, 367 struct proc *p) 368 { 369 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 370 int wsmode; 371 372 switch (cmd) { 373 case WSMOUSEIO_GTYPE: 374 /* 375 * So we can specify our own finger/w values to the 376 * xf86-input-synaptics driver like pms(4) 377 */ 378 *(u_int *)data = WSMOUSE_TYPE_ELANTECH; 379 break; 380 381 case WSMOUSEIO_GCALIBCOORDS: 382 wsmc->minx = mt->sc_minx; 383 wsmc->maxx = mt->sc_maxx; 384 wsmc->miny = mt->sc_miny; 385 wsmc->maxy = mt->sc_maxy; 386 wsmc->swapxy = 0; 387 wsmc->resx = 0; 388 wsmc->resy = 0; 389 break; 390 391 case WSMOUSEIO_SETMODE: 392 wsmode = *(u_int *)data; 393 if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) { 394 printf("%s: invalid mode %d\n", 395 mt->sc_device->dv_xname, wsmode); 396 return (EINVAL); 397 } 398 399 DPRINTF(("%s: changing mode to %s\n", mt->sc_device->dv_xname, 400 (wsmode == WSMOUSE_COMPAT ? "compat" : "native"))); 401 402 mt->sc_wsmode = wsmode; 403 break; 404 405 default: 406 return -1; 407 } 408 409 return 0; 410 } 411 412 void 413 hidmt_disable(struct hidmt *mt) 414 { 415 mt->sc_enabled = 0; 416 } 417