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