1 /* $OpenBSD: hidmt.c,v 1.11 2020/02/10 14:35:08 patrick 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 * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/touchscreen-packet-reporting-modes 8 * 9 * Copyright (c) 2016 joshua stein <jcs@openbsd.org> 10 * 11 * Permission to use, copy, modify, and distribute this software for any 12 * purpose with or without fee is hereby granted, provided that the above 13 * copyright notice and this permission notice appear in all copies. 14 * 15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 22 */ 23 24 #include <sys/param.h> 25 #include <sys/systm.h> 26 #include <sys/kernel.h> 27 #include <sys/device.h> 28 #include <sys/ioctl.h> 29 #include <sys/malloc.h> 30 31 #include <dev/wscons/wsconsio.h> 32 #include <dev/wscons/wsmousevar.h> 33 34 #include <dev/hid/hid.h> 35 #include <dev/hid/hidmtvar.h> 36 37 /* #define HIDMT_DEBUG */ 38 39 #ifdef HIDMT_DEBUG 40 #define DPRINTF(x) printf x 41 #else 42 #define DPRINTF(x) 43 #endif 44 45 #define HID_UNIT_CM 0x11 46 #define HID_UNIT_INCH 0x13 47 48 /* 49 * Calculate the horizontal or vertical resolution, in device units per 50 * millimeter. 51 * 52 * With the length unit specified by the descriptor (centimeter or inch), 53 * the result is: 54 * (logical_maximum - logical_minimum) / ((physical_maximum - 55 * physical_minimum) * 10^unit_exponent) 56 * 57 * The descriptors should encode the unit exponent as a signed half-byte. 58 * However, this function accepts the values from -8 to -1 in both the 59 * 4-bit format and the usual encoding. Other values beyond the 4-bit 60 * range are treated as undefined. Possibly a misinterpretation of 61 * section 6.2.2.7 of the HID specification (v1.11) has been turned into 62 * a standard here, see (from www.usb.org) 63 * HUTRR39: "HID Sensor Usage Tables", sect. 3.9, 3.10, 4.2.1 64 * for an official exegesis and 65 * https://patchwork.kernel.org/patch/3033191 66 * for details and a different view. 67 */ 68 int 69 hidmt_get_resolution(struct hid_item *h) 70 { 71 int log_extent, phy_extent, exponent; 72 73 if (h->unit != HID_UNIT_CM && h->unit != HID_UNIT_INCH) 74 return (0); 75 76 log_extent = h->logical_maximum - h->logical_minimum; 77 phy_extent = h->physical_maximum - h->physical_minimum; 78 if (log_extent <= 0 || phy_extent <= 0) 79 return (0); 80 81 exponent = h->unit_exponent; 82 if (exponent < -8 || exponent > 15) /* See above. */ 83 return (0); 84 if (exponent > 7) 85 exponent -= 16; 86 87 for (; exponent < 0 && log_extent <= INT_MAX / 10; exponent++) 88 log_extent *= 10; 89 for (; exponent > 0 && phy_extent <= INT_MAX / 10; exponent--) 90 phy_extent *= 10; 91 if (exponent != 0) 92 return (0); 93 94 if (h->unit == HID_UNIT_INCH) { /* Map inches to mm. */ 95 if ((phy_extent > INT_MAX / 127) 96 || (log_extent > INT_MAX / 5)) 97 return (0); 98 log_extent *= 5; 99 phy_extent *= 127; 100 } else { /* Map cm to mm. */ 101 if (phy_extent > INT_MAX / 10) 102 return (0); 103 phy_extent *= 10; 104 } 105 106 return (log_extent / phy_extent); 107 } 108 109 int 110 hidmt_setup(struct device *self, struct hidmt *mt, void *desc, int dlen) 111 { 112 struct hid_location cap; 113 int32_t d; 114 uint8_t *rep; 115 int capsize; 116 117 struct hid_data *hd; 118 struct hid_item h; 119 120 mt->sc_device = self; 121 mt->sc_rep_input_size = hid_report_size(desc, dlen, hid_input, 122 mt->sc_rep_input); 123 124 mt->sc_minx = mt->sc_miny = mt->sc_maxx = mt->sc_maxy = 0; 125 126 capsize = hid_report_size(desc, dlen, hid_feature, mt->sc_rep_cap); 127 rep = malloc(capsize, M_DEVBUF, M_NOWAIT | M_ZERO); 128 129 if (mt->hidev_report_type_conv == NULL) 130 panic("no report type conversion function"); 131 132 if (mt->hidev_get_report(mt->sc_device, 133 mt->hidev_report_type_conv(hid_feature), mt->sc_rep_cap, 134 rep, capsize)) { 135 printf("\n%s: failed getting capability report\n", 136 self->dv_xname); 137 return 1; 138 } 139 140 /* find maximum number of contacts being reported per input report */ 141 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), 142 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 143 printf("\n%s: can't find maximum contacts\n", self->dv_xname); 144 return 1; 145 } 146 147 d = hid_get_udata(rep, capsize, &cap); 148 if (d > HIDMT_MAX_CONTACTS) { 149 printf("\n%s: contacts %d > max %d\n", self->dv_xname, d, 150 HIDMT_MAX_CONTACTS); 151 return 1; 152 } 153 else 154 mt->sc_num_contacts = d; 155 156 /* find whether this is a clickpad or not */ 157 if (!hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE), 158 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 159 printf("\n%s: can't find button type\n", self->dv_xname); 160 return 1; 161 } 162 163 d = hid_get_udata(rep, capsize, &cap); 164 mt->sc_clickpad = (d == 0); 165 166 /* 167 * Walk HID descriptor and store usages we care about to know what to 168 * pluck out of input reports. 169 */ 170 171 SIMPLEQ_INIT(&mt->sc_inputs); 172 173 hd = hid_start_parse(desc, dlen, hid_input); 174 while (hid_get_item(hd, &h)) { 175 struct hidmt_data *input; 176 177 if (h.report_ID != mt->sc_rep_input) 178 continue; 179 if (h.kind != hid_input) 180 continue; 181 182 switch (h.usage) { 183 /* contact level usages */ 184 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 185 if (h.logical_maximum - h.logical_minimum) { 186 mt->sc_minx = h.logical_minimum; 187 mt->sc_maxx = h.logical_maximum; 188 mt->sc_resx = hidmt_get_resolution(&h); 189 } 190 break; 191 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 192 if (h.logical_maximum - h.logical_minimum) { 193 mt->sc_miny = h.logical_minimum; 194 mt->sc_maxy = h.logical_maximum; 195 mt->sc_resy = hidmt_get_resolution(&h); 196 } 197 break; 198 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 199 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 200 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 201 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 202 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 203 204 /* report level usages */ 205 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 206 case HID_USAGE2(HUP_BUTTON, 0x01): 207 case HID_USAGE2(HUP_BUTTON, 0x02): 208 case HID_USAGE2(HUP_BUTTON, 0x03): 209 break; 210 default: 211 continue; 212 } 213 214 input = malloc(sizeof(*input), M_DEVBUF, M_NOWAIT | M_ZERO); 215 memcpy(&input->loc, &h.loc, sizeof(struct hid_location)); 216 input->usage = h.usage; 217 218 SIMPLEQ_INSERT_TAIL(&mt->sc_inputs, input, entry); 219 } 220 hid_end_parse(hd); 221 222 if (mt->sc_maxx <= 0 || mt->sc_maxy <= 0) { 223 printf("\n%s: invalid max X/Y %d/%d\n", self->dv_xname, 224 mt->sc_maxx, mt->sc_maxy); 225 return 1; 226 } 227 228 if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD)) { 229 printf("\n%s: switch to multitouch mode failed\n", 230 self->dv_xname); 231 return 1; 232 } 233 234 return 0; 235 } 236 237 void 238 hidmt_configure(struct hidmt *mt) 239 { 240 struct wsmousehw *hw; 241 242 if (mt->sc_wsmousedev == NULL) 243 return; 244 245 hw = wsmouse_get_hw(mt->sc_wsmousedev); 246 hw->type = WSMOUSE_TYPE_TOUCHPAD; 247 hw->hw_type = (mt->sc_clickpad 248 ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD); 249 hw->x_min = mt->sc_minx; 250 hw->x_max = mt->sc_maxx; 251 hw->y_min = mt->sc_miny; 252 hw->y_max = mt->sc_maxy; 253 hw->h_res = mt->sc_resx; 254 hw->v_res = mt->sc_resy; 255 hw->mt_slots = HIDMT_MAX_CONTACTS; 256 257 wsmouse_configure(mt->sc_wsmousedev, NULL, 0); 258 } 259 260 void 261 hidmt_attach(struct hidmt *mt, const struct wsmouse_accessops *ops) 262 { 263 struct wsmousedev_attach_args a; 264 265 printf(": %spad, %d contact%s\n", 266 (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, 267 (mt->sc_num_contacts == 1 ? "" : "s")); 268 269 a.accessops = ops; 270 a.accesscookie = mt->sc_device; 271 mt->sc_wsmousedev = config_found(mt->sc_device, &a, wsmousedevprint); 272 hidmt_configure(mt); 273 } 274 275 int 276 hidmt_detach(struct hidmt *mt, int flags) 277 { 278 int rv = 0; 279 280 if (mt->sc_wsmousedev != NULL) 281 rv = config_detach(mt->sc_wsmousedev, flags); 282 283 return (rv); 284 } 285 286 int 287 hidmt_set_input_mode(struct hidmt *mt, uint16_t mode) 288 { 289 if (mt->hidev_report_type_conv == NULL) 290 panic("no report type conversion function"); 291 292 return mt->hidev_set_report(mt->sc_device, 293 mt->hidev_report_type_conv(hid_feature), 294 mt->sc_rep_config, &mode, sizeof(mode)); 295 } 296 297 void 298 hidmt_input(struct hidmt *mt, uint8_t *data, u_int len) 299 { 300 struct hidmt_data *hi; 301 struct hidmt_contact hc; 302 int32_t d, firstu = 0; 303 int contactcount = 0, seencontacts = 0, tips = 0, buttons = 0, i, s, z; 304 305 if (len != mt->sc_rep_input_size) { 306 DPRINTF(("%s: %s: length %d not %d, ignoring\n", 307 mt->sc_device->dv_xname, __func__, len, 308 mt->sc_rep_input_size)); 309 return; 310 } 311 312 /* 313 * "In Parallel mode, devices report all contact information in a 314 * single packet. Each physical contact is represented by a logical 315 * collection that is embedded in the top-level collection." 316 * 317 * Since additional contacts that were not present will still be in the 318 * report with contactid=0 but contactids are zero-based, find 319 * contactcount first. 320 */ 321 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 322 if (hi->usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) 323 contactcount = hid_get_udata(data, len, &hi->loc); 324 } 325 326 if (contactcount) 327 mt->sc_cur_contactcount = contactcount; 328 else { 329 /* 330 * "In Hybrid mode, the number of contacts that can be reported 331 * in one report is less than the maximum number of contacts 332 * that the device supports. For example, a device that supports 333 * a maximum of 4 concurrent physical contacts, can set up its 334 * top-level collection to deliver a maximum of two contacts in 335 * one report. If four contact points are present, the device 336 * can break these up into two serial reports that deliver two 337 * contacts each. 338 * 339 * "When a device delivers data in this manner, the Contact 340 * Count usage value in the first report should reflect the 341 * total number of contacts that are being delivered in the 342 * hybrid reports. The other serial reports should have a 343 * contact count of zero (0)." 344 */ 345 contactcount = mt->sc_cur_contactcount; 346 } 347 348 if (!contactcount) { 349 DPRINTF(("%s: %s: no contactcount in report\n", 350 mt->sc_device->dv_xname, __func__)); 351 return; 352 } 353 354 /* 355 * Walk through each input we know about and fetch its data from the 356 * report, storing it in a temporary contact. Once we see our first 357 * usage again, we'll know we saw all usages being presented for that 358 * contact. 359 */ 360 bzero(&hc, sizeof(struct hidmt_contact)); 361 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 362 d = hid_get_udata(data, len, &hi->loc); 363 364 if (firstu && hi->usage == firstu) { 365 if (seencontacts < contactcount) { 366 hc.seen = 1; 367 i = wsmouse_id_to_slot( 368 mt->sc_wsmousedev, hc.contactid); 369 if (i >= 0) 370 memcpy(&mt->sc_contacts[i], &hc, 371 sizeof(struct hidmt_contact)); 372 seencontacts++; 373 } 374 375 bzero(&hc, sizeof(struct hidmt_contact)); 376 } 377 else if (!firstu) 378 firstu = hi->usage; 379 380 switch (hi->usage) { 381 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 382 hc.x = d; 383 break; 384 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 385 if (mt->sc_flags & HIDMT_REVY) 386 hc.y = mt->sc_maxy - d; 387 else 388 hc.y = d; 389 break; 390 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 391 hc.tip = d; 392 if (d) 393 tips++; 394 break; 395 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 396 hc.confidence = d; 397 break; 398 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 399 hc.width = d; 400 break; 401 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 402 hc.height = d; 403 break; 404 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 405 hc.contactid = d; 406 break; 407 408 /* these will only appear once per report */ 409 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 410 if (d) 411 contactcount = d; 412 break; 413 case HID_USAGE2(HUP_BUTTON, 0x01): 414 case HID_USAGE2(HUP_BUTTON, 0x02): 415 if (d != 0) 416 buttons |= 1; 417 break; 418 case HID_USAGE2(HUP_BUTTON, 0x03): 419 if (d != 0) 420 buttons |= 1 << 2; 421 break; 422 } 423 } 424 if (seencontacts < contactcount) { 425 hc.seen = 1; 426 i = wsmouse_id_to_slot(mt->sc_wsmousedev, hc.contactid); 427 if (i >= 0) 428 memcpy(&mt->sc_contacts[i], &hc, 429 sizeof(struct hidmt_contact)); 430 seencontacts++; 431 } 432 433 s = spltty(); 434 if (mt->sc_buttons != buttons) { 435 wsmouse_buttons(mt->sc_wsmousedev, buttons); 436 mt->sc_buttons = buttons; 437 } 438 for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { 439 if (!mt->sc_contacts[i].seen) 440 continue; 441 442 mt->sc_contacts[i].seen = 0; 443 444 DPRINTF(("%s: %s: contact %d of %d: id %d, x %d, y %d, " 445 "touch %d, confidence %d, width %d, height %d " 446 "(button 0x%x)\n", 447 mt->sc_device->dv_xname, __func__, 448 i + 1, contactcount, 449 mt->sc_contacts[i].contactid, 450 mt->sc_contacts[i].x, 451 mt->sc_contacts[i].y, 452 mt->sc_contacts[i].tip, 453 mt->sc_contacts[i].confidence, 454 mt->sc_contacts[i].width, 455 mt->sc_contacts[i].height, 456 mt->sc_buttons)); 457 458 if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) 459 continue; 460 461 /* Report width as pressure. */ 462 z = (mt->sc_contacts[i].tip 463 ? imax(mt->sc_contacts[i].width, 50) : 0); 464 465 wsmouse_mtstate(mt->sc_wsmousedev, 466 i, mt->sc_contacts[i].x, mt->sc_contacts[i].y, z); 467 } 468 wsmouse_input_sync(mt->sc_wsmousedev); 469 470 splx(s); 471 } 472 473 int 474 hidmt_enable(struct hidmt *mt) 475 { 476 if (mt->sc_enabled) 477 return EBUSY; 478 479 mt->sc_enabled = 1; 480 481 return 0; 482 } 483 484 int 485 hidmt_ioctl(struct hidmt *mt, u_long cmd, caddr_t data, int flag, 486 struct proc *p) 487 { 488 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 489 int wsmode; 490 491 switch (cmd) { 492 case WSMOUSEIO_GTYPE: { 493 struct wsmousehw *hw = wsmouse_get_hw(mt->sc_wsmousedev); 494 *(u_int *)data = hw->type; 495 break; 496 } 497 498 case WSMOUSEIO_GCALIBCOORDS: 499 wsmc->minx = mt->sc_minx; 500 wsmc->maxx = mt->sc_maxx; 501 wsmc->miny = mt->sc_miny; 502 wsmc->maxy = mt->sc_maxy; 503 wsmc->swapxy = 0; 504 wsmc->resx = mt->sc_resx; 505 wsmc->resy = mt->sc_resy; 506 break; 507 508 case WSMOUSEIO_SETMODE: 509 wsmode = *(u_int *)data; 510 if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) { 511 printf("%s: invalid mode %d\n", 512 mt->sc_device->dv_xname, wsmode); 513 return (EINVAL); 514 } 515 516 DPRINTF(("%s: changing mode to %s\n", mt->sc_device->dv_xname, 517 (wsmode == WSMOUSE_COMPAT ? "compat" : "native"))); 518 519 wsmouse_set_mode(mt->sc_wsmousedev, wsmode); 520 521 break; 522 523 default: 524 return -1; 525 } 526 527 return 0; 528 } 529 530 void 531 hidmt_disable(struct hidmt *mt) 532 { 533 mt->sc_enabled = 0; 534 } 535