1 /* $OpenBSD: hidmt.c,v 1.13 2022/10/16 20:17:08 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 * 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 mt->sc_num_contacts = HIDMT_MAX_CONTACTS; 142 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), 143 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 144 d = hid_get_udata(rep, capsize, &cap); 145 if (d > HIDMT_MAX_CONTACTS) 146 printf("\n%s: contacts %d > max %d\n", self->dv_xname, 147 d, HIDMT_MAX_CONTACTS); 148 else 149 mt->sc_num_contacts = d; 150 } 151 152 /* find whether this is a clickpad or not */ 153 if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE), 154 mt->sc_rep_cap, hid_feature, &cap, NULL)) { 155 d = hid_get_udata(rep, capsize, &cap); 156 mt->sc_clickpad = (d == 0); 157 } else if (hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, 1), 158 mt->sc_rep_input, hid_input, &cap, NULL) || 159 !hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, 2), 160 mt->sc_rep_input, hid_input, &cap, NULL) || 161 !hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, 3), 162 mt->sc_rep_input, hid_input, &cap, NULL)) { 163 mt->sc_clickpad = 1; 164 } 165 /* 166 * Walk HID descriptor and store usages we care about to know what to 167 * pluck out of input reports. 168 */ 169 170 SIMPLEQ_INIT(&mt->sc_inputs); 171 172 hd = hid_start_parse(desc, dlen, hid_input); 173 while (hid_get_item(hd, &h)) { 174 struct hidmt_data *input; 175 176 if (h.report_ID != mt->sc_rep_input) 177 continue; 178 if (h.kind != hid_input) 179 continue; 180 181 switch (h.usage) { 182 /* contact level usages */ 183 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 184 if (h.logical_maximum - h.logical_minimum) { 185 mt->sc_minx = h.logical_minimum; 186 mt->sc_maxx = h.logical_maximum; 187 mt->sc_resx = hidmt_get_resolution(&h); 188 } 189 break; 190 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 191 if (h.logical_maximum - h.logical_minimum) { 192 mt->sc_miny = h.logical_minimum; 193 mt->sc_maxy = h.logical_maximum; 194 mt->sc_resy = hidmt_get_resolution(&h); 195 } 196 break; 197 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 198 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 199 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 200 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 201 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 202 203 /* report level usages */ 204 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 205 case HID_USAGE2(HUP_BUTTON, 0x01): 206 case HID_USAGE2(HUP_BUTTON, 0x02): 207 case HID_USAGE2(HUP_BUTTON, 0x03): 208 break; 209 default: 210 continue; 211 } 212 213 input = malloc(sizeof(*input), M_DEVBUF, M_NOWAIT | M_ZERO); 214 memcpy(&input->loc, &h.loc, sizeof(struct hid_location)); 215 input->usage = h.usage; 216 217 SIMPLEQ_INSERT_TAIL(&mt->sc_inputs, input, entry); 218 } 219 hid_end_parse(hd); 220 221 if (mt->sc_maxx <= 0 || mt->sc_maxy <= 0) { 222 printf("\n%s: invalid max X/Y %d/%d\n", self->dv_xname, 223 mt->sc_maxx, mt->sc_maxy); 224 return 1; 225 } 226 227 if (hidmt_set_input_mode(mt, HIDMT_INPUT_MODE_MT_TOUCHPAD)) { 228 printf("\n%s: switch to multitouch mode failed\n", 229 self->dv_xname); 230 return 1; 231 } 232 233 return 0; 234 } 235 236 void 237 hidmt_configure(struct hidmt *mt) 238 { 239 struct wsmousehw *hw; 240 241 if (mt->sc_wsmousedev == NULL) 242 return; 243 244 hw = wsmouse_get_hw(mt->sc_wsmousedev); 245 hw->type = WSMOUSE_TYPE_TOUCHPAD; 246 hw->hw_type = (mt->sc_clickpad 247 ? WSMOUSEHW_CLICKPAD : WSMOUSEHW_TOUCHPAD); 248 hw->x_min = mt->sc_minx; 249 hw->x_max = mt->sc_maxx; 250 hw->y_min = mt->sc_miny; 251 hw->y_max = mt->sc_maxy; 252 hw->h_res = mt->sc_resx; 253 hw->v_res = mt->sc_resy; 254 hw->mt_slots = HIDMT_MAX_CONTACTS; 255 256 wsmouse_configure(mt->sc_wsmousedev, NULL, 0); 257 } 258 259 void 260 hidmt_attach(struct hidmt *mt, const struct wsmouse_accessops *ops) 261 { 262 struct wsmousedev_attach_args a; 263 264 printf(": %spad, %d contact%s\n", 265 (mt->sc_clickpad ? "click" : "touch"), mt->sc_num_contacts, 266 (mt->sc_num_contacts == 1 ? "" : "s")); 267 268 a.accessops = ops; 269 a.accesscookie = mt->sc_device; 270 mt->sc_wsmousedev = config_found(mt->sc_device, &a, wsmousedevprint); 271 hidmt_configure(mt); 272 } 273 274 int 275 hidmt_detach(struct hidmt *mt, int flags) 276 { 277 int rv = 0; 278 279 if (mt->sc_wsmousedev != NULL) 280 rv = config_detach(mt->sc_wsmousedev, flags); 281 282 return (rv); 283 } 284 285 int 286 hidmt_set_input_mode(struct hidmt *mt, uint16_t mode) 287 { 288 if (mt->hidev_report_type_conv == NULL) 289 panic("no report type conversion function"); 290 291 return mt->hidev_set_report(mt->sc_device, 292 mt->hidev_report_type_conv(hid_feature), 293 mt->sc_rep_config, &mode, sizeof(mode)); 294 } 295 296 void 297 hidmt_input(struct hidmt *mt, uint8_t *data, u_int len) 298 { 299 struct hidmt_data *hi; 300 struct hidmt_contact hc; 301 int32_t d, firstu = 0; 302 int contactcount = 0, seencontacts = 0, tips = 0, buttons = 0, i, s, z; 303 304 if (len != mt->sc_rep_input_size) { 305 DPRINTF(("%s: %s: length %d not %d, ignoring\n", 306 mt->sc_device->dv_xname, __func__, len, 307 mt->sc_rep_input_size)); 308 return; 309 } 310 311 /* 312 * "In Parallel mode, devices report all contact information in a 313 * single packet. Each physical contact is represented by a logical 314 * collection that is embedded in the top-level collection." 315 * 316 * Since additional contacts that were not present will still be in the 317 * report with contactid=0 but contactids are zero-based, find 318 * contactcount first. 319 */ 320 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 321 if (hi->usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) 322 contactcount = hid_get_udata(data, len, &hi->loc); 323 } 324 325 if (contactcount) 326 mt->sc_cur_contactcount = contactcount; 327 else { 328 /* 329 * "In Hybrid mode, the number of contacts that can be reported 330 * in one report is less than the maximum number of contacts 331 * that the device supports. For example, a device that supports 332 * a maximum of 4 concurrent physical contacts, can set up its 333 * top-level collection to deliver a maximum of two contacts in 334 * one report. If four contact points are present, the device 335 * can break these up into two serial reports that deliver two 336 * contacts each. 337 * 338 * "When a device delivers data in this manner, the Contact 339 * Count usage value in the first report should reflect the 340 * total number of contacts that are being delivered in the 341 * hybrid reports. The other serial reports should have a 342 * contact count of zero (0)." 343 */ 344 contactcount = mt->sc_cur_contactcount; 345 } 346 347 if (!contactcount) { 348 DPRINTF(("%s: %s: no contactcount in report\n", 349 mt->sc_device->dv_xname, __func__)); 350 return; 351 } 352 353 /* 354 * Walk through each input we know about and fetch its data from the 355 * report, storing it in a temporary contact. Once we see our first 356 * usage again, we'll know we saw all usages being presented for that 357 * contact. 358 */ 359 bzero(&hc, sizeof(struct hidmt_contact)); 360 SIMPLEQ_FOREACH(hi, &mt->sc_inputs, entry) { 361 d = hid_get_udata(data, len, &hi->loc); 362 363 if (firstu && hi->usage == firstu) { 364 if (seencontacts < contactcount) { 365 hc.seen = 1; 366 i = wsmouse_id_to_slot( 367 mt->sc_wsmousedev, hc.contactid); 368 if (i >= 0) 369 memcpy(&mt->sc_contacts[i], &hc, 370 sizeof(struct hidmt_contact)); 371 seencontacts++; 372 } 373 374 bzero(&hc, sizeof(struct hidmt_contact)); 375 } 376 else if (!firstu) 377 firstu = hi->usage; 378 379 switch (hi->usage) { 380 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X): 381 hc.x = d; 382 break; 383 case HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y): 384 if (mt->sc_flags & HIDMT_REVY) 385 hc.y = mt->sc_maxy - d; 386 else 387 hc.y = d; 388 break; 389 case HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH): 390 hc.tip = d; 391 if (d) 392 tips++; 393 break; 394 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE): 395 hc.confidence = d; 396 break; 397 case HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH): 398 hc.width = d; 399 break; 400 case HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT): 401 hc.height = d; 402 break; 403 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID): 404 hc.contactid = d; 405 break; 406 407 /* these will only appear once per report */ 408 case HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT): 409 if (d) 410 contactcount = d; 411 break; 412 case HID_USAGE2(HUP_BUTTON, 0x01): 413 case HID_USAGE2(HUP_BUTTON, 0x02): 414 if (d != 0) 415 buttons |= 1; 416 break; 417 case HID_USAGE2(HUP_BUTTON, 0x03): 418 if (d != 0) 419 buttons |= 1 << 2; 420 break; 421 } 422 } 423 if (seencontacts < contactcount) { 424 hc.seen = 1; 425 i = wsmouse_id_to_slot(mt->sc_wsmousedev, hc.contactid); 426 if (i >= 0) 427 memcpy(&mt->sc_contacts[i], &hc, 428 sizeof(struct hidmt_contact)); 429 seencontacts++; 430 } 431 432 s = spltty(); 433 if (mt->sc_buttons != buttons) { 434 wsmouse_buttons(mt->sc_wsmousedev, buttons); 435 mt->sc_buttons = buttons; 436 } 437 for (i = 0; i < HIDMT_MAX_CONTACTS; i++) { 438 if (!mt->sc_contacts[i].seen) 439 continue; 440 441 mt->sc_contacts[i].seen = 0; 442 443 DPRINTF(("%s: %s: contact %d of %d: id %d, x %d, y %d, " 444 "touch %d, confidence %d, width %d, height %d " 445 "(button 0x%x)\n", 446 mt->sc_device->dv_xname, __func__, 447 i + 1, contactcount, 448 mt->sc_contacts[i].contactid, 449 mt->sc_contacts[i].x, 450 mt->sc_contacts[i].y, 451 mt->sc_contacts[i].tip, 452 mt->sc_contacts[i].confidence, 453 mt->sc_contacts[i].width, 454 mt->sc_contacts[i].height, 455 mt->sc_buttons)); 456 457 if (mt->sc_contacts[i].tip && !mt->sc_contacts[i].confidence) 458 continue; 459 460 /* Report width as pressure. */ 461 z = (mt->sc_contacts[i].tip 462 ? imax(mt->sc_contacts[i].width, 50) : 0); 463 464 wsmouse_mtstate(mt->sc_wsmousedev, 465 i, mt->sc_contacts[i].x, mt->sc_contacts[i].y, z); 466 } 467 wsmouse_input_sync(mt->sc_wsmousedev); 468 469 splx(s); 470 } 471 472 int 473 hidmt_enable(struct hidmt *mt) 474 { 475 if (mt->sc_enabled) 476 return EBUSY; 477 478 mt->sc_enabled = 1; 479 480 return 0; 481 } 482 483 int 484 hidmt_ioctl(struct hidmt *mt, u_long cmd, caddr_t data, int flag, 485 struct proc *p) 486 { 487 struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data; 488 int wsmode; 489 490 switch (cmd) { 491 case WSMOUSEIO_GTYPE: { 492 struct wsmousehw *hw = wsmouse_get_hw(mt->sc_wsmousedev); 493 *(u_int *)data = hw->type; 494 break; 495 } 496 497 case WSMOUSEIO_GCALIBCOORDS: 498 wsmc->minx = mt->sc_minx; 499 wsmc->maxx = mt->sc_maxx; 500 wsmc->miny = mt->sc_miny; 501 wsmc->maxy = mt->sc_maxy; 502 wsmc->swapxy = 0; 503 wsmc->resx = mt->sc_resx; 504 wsmc->resy = mt->sc_resy; 505 break; 506 507 case WSMOUSEIO_SETMODE: 508 wsmode = *(u_int *)data; 509 if (wsmode != WSMOUSE_COMPAT && wsmode != WSMOUSE_NATIVE) { 510 printf("%s: invalid mode %d\n", 511 mt->sc_device->dv_xname, wsmode); 512 return (EINVAL); 513 } 514 515 DPRINTF(("%s: changing mode to %s\n", mt->sc_device->dv_xname, 516 (wsmode == WSMOUSE_COMPAT ? "compat" : "native"))); 517 518 wsmouse_set_mode(mt->sc_wsmousedev, wsmode); 519 520 break; 521 522 default: 523 return -1; 524 } 525 526 return 0; 527 } 528 529 void 530 hidmt_disable(struct hidmt *mt) 531 { 532 mt->sc_enabled = 0; 533 } 534