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
hidmt_get_resolution(struct hid_item * h)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
hidmt_setup(struct device * self,struct hidmt * mt,void * desc,int dlen)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
hidmt_configure(struct hidmt * mt)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
hidmt_attach(struct hidmt * mt,const struct wsmouse_accessops * ops)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
hidmt_detach(struct hidmt * mt,int flags)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
hidmt_set_input_mode(struct hidmt * mt,uint16_t mode)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
hidmt_input(struct hidmt * mt,uint8_t * data,u_int len)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
hidmt_enable(struct hidmt * mt)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
hidmt_ioctl(struct hidmt * mt,u_long cmd,caddr_t data,int flag,struct proc * p)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
hidmt_disable(struct hidmt * mt)530 hidmt_disable(struct hidmt *mt)
531 {
532 mt->sc_enabled = 0;
533 }
534