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