xref: /openbsd-src/sys/dev/hid/hidms.c (revision de8cc8edbc71bd3e3bc7fbffa27ba0e564c37d8b)
1 /*	$OpenBSD: hidms.c,v 1.6 2021/01/10 16:32:48 thfr Exp $ */
2 /*	$NetBSD: ums.c,v 1.60 2003/03/11 16:44:00 augustss Exp $	*/
3 
4 /*
5  * Copyright (c) 1998 The NetBSD Foundation, Inc.
6  * All rights reserved.
7  *
8  * This code is derived from software contributed to The NetBSD Foundation
9  * by Lennart Augustsson (lennart@augustsson.net) at
10  * Carlstedt Research & Technology.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
23  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
24  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
25  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31  * POSSIBILITY OF SUCH DAMAGE.
32  */
33 
34 /*
35  * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
36  */
37 
38 #include <sys/param.h>
39 #include <sys/systm.h>
40 #include <sys/kernel.h>
41 #include <sys/device.h>
42 #include <sys/ioctl.h>
43 
44 #include <dev/wscons/wsconsio.h>
45 #include <dev/wscons/wsmousevar.h>
46 
47 #include <dev/hid/hid.h>
48 #include <dev/hid/hidmsvar.h>
49 
50 #ifdef HIDMS_DEBUG
51 #define DPRINTF(x)	do { if (hidmsdebug) printf x; } while (0)
52 #define DPRINTFN(n,x)	do { if (hidmsdebug>(n)) printf x; } while (0)
53 int	hidmsdebug = 0;
54 #else
55 #define DPRINTF(x)
56 #define DPRINTFN(n,x)
57 #endif
58 
59 #define HIDMS_BUT(i)	((i) == 1 || (i) == 2 ? 3 - (i) : i)
60 
61 #define MOUSE_FLAGS_MASK	(HIO_CONST | HIO_RELATIVE)
62 #define NOTMOUSE(f)		(((f) & MOUSE_FLAGS_MASK) != HIO_RELATIVE)
63 
64 int
65 hidms_setup(struct device *self, struct hidms *ms, uint32_t quirks,
66     int id, void *desc, int dlen)
67 {
68 	struct hid_item h;
69 	struct hid_data *d;
70 	uint32_t flags;
71 	int i, wheel, twheel;
72 
73 	ms->sc_device = self;
74 	ms->sc_rawmode = 1;
75 
76 	ms->sc_flags = quirks;
77 
78 	if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), id,
79 	    hid_input, &ms->sc_loc_x, &flags)) {
80 		printf("\n%s: mouse has no X report\n", self->dv_xname);
81 		return ENXIO;
82 	}
83 	switch(flags & MOUSE_FLAGS_MASK) {
84 	case 0:
85 		ms->sc_flags |= HIDMS_ABSX;
86 		break;
87 	case HIO_RELATIVE:
88 		break;
89 	default:
90 		printf("\n%s: X report 0x%04x not supported\n",
91 		    self->dv_xname, flags);
92 		return ENXIO;
93 	}
94 
95 	if (!hid_locate(desc, dlen, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), id,
96 	    hid_input, &ms->sc_loc_y, &flags)) {
97 		printf("\n%s: mouse has no Y report\n", self->dv_xname);
98 		return ENXIO;
99 	}
100 	switch(flags & MOUSE_FLAGS_MASK) {
101 	case 0:
102 		ms->sc_flags |= HIDMS_ABSY;
103 		break;
104 	case HIO_RELATIVE:
105 		break;
106 	default:
107 		printf("\n%s: Y report 0x%04x not supported\n",
108 		    self->dv_xname, flags);
109 		return ENXIO;
110 	}
111 
112 	/*
113 	 * Try to guess the Z activator: check WHEEL, TWHEEL, and Z,
114 	 * in that order.
115 	 */
116 
117 	wheel = hid_locate(desc, dlen,
118 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL), id,
119 	    hid_input, &ms->sc_loc_z, &flags);
120 	if (wheel == 0)
121 		twheel = hid_locate(desc, dlen,
122 		    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL), id,
123 		    hid_input, &ms->sc_loc_z, &flags);
124 	else
125 		twheel = 0;
126 
127 	if (wheel || twheel) {
128 		if (NOTMOUSE(flags)) {
129 			DPRINTF(("\n%s: Wheel report 0x%04x not supported\n",
130 			    self->dv_xname, flags));
131 			ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */
132 		} else {
133 			ms->sc_flags |= HIDMS_Z;
134 			/* Wheels need the Z axis reversed. */
135 			ms->sc_flags ^= HIDMS_REVZ;
136 		}
137 		/*
138 		 * We might have both a wheel and Z direction; in this case,
139 		 * report the Z direction on the W axis.
140 		 *
141 		 * Otherwise, check for a W direction as an AC Pan input used
142 		 * on some newer mice.
143 		 */
144 		if (hid_locate(desc, dlen,
145 		    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id,
146 		    hid_input, &ms->sc_loc_w, &flags)) {
147 			if (NOTMOUSE(flags)) {
148 				DPRINTF(("\n%s: Z report 0x%04x not supported\n",
149 				    self->dv_xname, flags));
150 				/* Bad Z coord, ignore it */
151 				ms->sc_loc_w.size = 0;
152 			}
153 			else
154 				ms->sc_flags |= HIDMS_W;
155 		} else if (hid_locate(desc, dlen,
156 		    HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN), id, hid_input,
157 		    &ms->sc_loc_w, &flags)) {
158 			ms->sc_flags |= HIDMS_W;
159 		}
160 	} else if (hid_locate(desc, dlen,
161 	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z), id,
162 	    hid_input, &ms->sc_loc_z, &flags)) {
163 		if (NOTMOUSE(flags)) {
164 			DPRINTF(("\n%s: Z report 0x%04x not supported\n",
165 			    self->dv_xname, flags));
166 			ms->sc_loc_z.size = 0; /* Bad Z coord, ignore it */
167 		} else {
168 			ms->sc_flags |= HIDMS_Z;
169 		}
170 	}
171 
172 	/*
173 	 * The Microsoft Wireless Intellimouse 2.0 reports its wheel
174 	 * using 0x0048 (I've called it HUG_TWHEEL) and seems to expect
175 	 * us to know that the byte after the wheel is the tilt axis.
176 	 * There are no other HID axis descriptors other than X, Y and
177 	 * TWHEEL, so we report TWHEEL on the W axis.
178 	 */
179 	if (twheel) {
180 		ms->sc_loc_w = ms->sc_loc_z;
181 		ms->sc_loc_w.pos = ms->sc_loc_w.pos + 8;
182 		ms->sc_flags |= HIDMS_W | HIDMS_LEADINGBYTE;
183 		/* Wheels need their axis reversed. */
184 		ms->sc_flags ^= HIDMS_REVW;
185 	}
186 
187 	/* figure out the number of buttons */
188 	for (i = 1; i <= MAX_BUTTONS; i++)
189 		if (!hid_locate(desc, dlen, HID_USAGE2(HUP_BUTTON, i), id,
190 		    hid_input, &ms->sc_loc_btn[i - 1], NULL))
191 			break;
192 	ms->sc_num_buttons = i - 1;
193 
194 	/*
195 	 * The Kensington Slimblade reports some of its buttons as binary
196 	 * inputs in the first vendor usage page (0xff00). Add such inputs
197 	 * as buttons if the device has this quirk.
198 	 */
199 	if (ms->sc_flags & HIDMS_VENDOR_BUTTONS) {
200 		const int b = ms->sc_num_buttons;
201 		for (i = 1; b + i <= MAX_BUTTONS; i++)
202 			if (!hid_locate(desc, dlen,
203 			    HID_USAGE2(HUP_MICROSOFT, i),
204 			    id, hid_input, &ms->sc_loc_btn[b + i - 1], NULL))
205 				break;
206 		ms->sc_num_buttons += i;
207 	}
208 
209 	if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS,
210 	    HUD_TIP_SWITCH), id, hid_input,
211 	    &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){
212 		ms->sc_flags |= HIDMS_TIP;
213 		ms->sc_num_buttons++;
214 	}
215 
216 	if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS,
217 	    HUD_ERASER), id, hid_input,
218 	    &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){
219 		ms->sc_flags |= HIDMS_ERASER;
220 		ms->sc_num_buttons++;
221 	}
222 
223 	if (hid_locate(desc, dlen, HID_USAGE2(HUP_DIGITIZERS,
224 	    HUD_BARREL_SWITCH), id, hid_input,
225 	    &ms->sc_loc_btn[ms->sc_num_buttons], NULL)){
226 		ms->sc_flags |= HIDMS_BARREL;
227 		ms->sc_num_buttons++;
228 	}
229 
230 	/*
231 	 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse
232 	 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and
233 	 * all of its other button positions are all off. It also reports that
234 	 * it has two addional buttons and a tilt wheel.
235 	 */
236 	if (ms->sc_flags & HIDMS_MS_BAD_CLASS) {
237 		/* HIDMS_LEADINGBYTE cleared on purpose */
238 		ms->sc_flags = HIDMS_Z | HIDMS_SPUR_BUT_UP;
239 		ms->sc_num_buttons = 3;
240 		/* XXX change sc_hdev isize to 5? */
241 		/* 1st byte of descriptor report contains garbage */
242 		ms->sc_loc_x.pos = 16;
243 		ms->sc_loc_y.pos = 24;
244 		ms->sc_loc_z.pos = 32;
245 		ms->sc_loc_btn[0].pos = 8;
246 		ms->sc_loc_btn[1].pos = 9;
247 		ms->sc_loc_btn[2].pos = 10;
248 	}
249 	/* Parse descriptors to get touch panel bounds */
250 	d = hid_start_parse(desc, dlen, hid_input);
251 	while (hid_get_item(d, &h)) {
252 		if (h.kind != hid_input ||
253 		    HID_GET_USAGE_PAGE(h.usage) != HUP_GENERIC_DESKTOP)
254 			continue;
255 		DPRINTF(("hidms: usage=0x%x range %d..%d\n",
256 			h.usage, h.logical_minimum, h.logical_maximum));
257 		switch (HID_GET_USAGE(h.usage)) {
258 		case HUG_X:
259 			if (ms->sc_flags & HIDMS_ABSX) {
260 				ms->sc_tsscale.minx = h.logical_minimum;
261 				ms->sc_tsscale.maxx = h.logical_maximum;
262 			}
263 			break;
264 		case HUG_Y:
265 			if (ms->sc_flags & HIDMS_ABSY) {
266 				ms->sc_tsscale.miny = h.logical_minimum;
267 				ms->sc_tsscale.maxy = h.logical_maximum;
268 			}
269 			break;
270 		}
271 	}
272 	hid_end_parse(d);
273 	return 0;
274 }
275 
276 void
277 hidms_attach(struct hidms *ms, const struct wsmouse_accessops *ops)
278 {
279 	struct wsmousedev_attach_args a;
280 #ifdef HIDMS_DEBUG
281 	int i;
282 #endif
283 
284 	printf(": %d button%s",
285 	    ms->sc_num_buttons, ms->sc_num_buttons <= 1 ? "" : "s");
286 	switch (ms->sc_flags & (HIDMS_Z | HIDMS_W)) {
287 	case HIDMS_Z:
288 		printf(", Z dir");
289 		break;
290 	case HIDMS_W:
291 		printf(", W dir");
292 		break;
293 	case HIDMS_Z | HIDMS_W:
294 		printf(", Z and W dir");
295 		break;
296 	}
297 
298 	if (ms->sc_flags & HIDMS_TIP)
299 		printf(", tip");
300 	if (ms->sc_flags & HIDMS_BARREL)
301 		printf(", barrel");
302 	if (ms->sc_flags & HIDMS_ERASER)
303 		printf(", eraser");
304 
305 	printf("\n");
306 
307 #ifdef HIDMS_DEBUG
308 	DPRINTF(("hidms_attach: ms=%p\n", ms));
309 	DPRINTF(("hidms_attach: X\t%d/%d\n",
310 	     ms->sc_loc_x.pos, ms->sc_loc_x.size));
311 	DPRINTF(("hidms_attach: Y\t%d/%d\n",
312 	    ms->sc_loc_y.pos, ms->sc_loc_y.size));
313 	if (ms->sc_flags & HIDMS_Z)
314 		DPRINTF(("hidms_attach: Z\t%d/%d\n",
315 		    ms->sc_loc_z.pos, ms->sc_loc_z.size));
316 	if (ms->sc_flags & HIDMS_W)
317 		DPRINTF(("hidms_attach: W\t%d/%d\n",
318 		    ms->sc_loc_w.pos, ms->sc_loc_w.size));
319 	for (i = 1; i <= ms->sc_num_buttons; i++) {
320 		DPRINTF(("hidms_attach: B%d\t%d/%d\n",
321 		    i, ms->sc_loc_btn[i - 1].pos, ms->sc_loc_btn[i - 1].size));
322 	}
323 #endif
324 
325 	a.accessops = ops;
326 	a.accesscookie = ms->sc_device;
327 	ms->sc_wsmousedev = config_found(ms->sc_device, &a, wsmousedevprint);
328 }
329 
330 int
331 hidms_detach(struct hidms *ms, int flags)
332 {
333 	int rv = 0;
334 
335 	DPRINTF(("hidms_detach: ms=%p flags=%d\n", ms, flags));
336 
337 	/* No need to do reference counting of hidms, wsmouse has all the goo */
338 	if (ms->sc_wsmousedev != NULL)
339 		rv = config_detach(ms->sc_wsmousedev, flags);
340 
341 	return (rv);
342 }
343 
344 void
345 hidms_input(struct hidms *ms, uint8_t *data, u_int len)
346 {
347 	int dx, dy, dz, dw;
348 	u_int32_t buttons = 0;
349 	int i, s;
350 
351 	DPRINTFN(5,("hidms_input: len=%d\n", len));
352 
353 	/*
354 	 * The Microsoft Wireless Intellimouse 2.0 sends one extra leading
355 	 * byte of data compared to most USB mice.  This byte frequently
356 	 * switches from 0x01 (usual state) to 0x02.  It may be used to
357 	 * report non-standard events (such as battery life).  However,
358 	 * at the same time, it generates a left click event on the
359 	 * button byte, where there shouldn't be any.  We simply discard
360 	 * the packet in this case.
361 	 *
362 	 * This problem affects the MS Wireless Notebook Optical Mouse, too.
363 	 * However, the leading byte for this mouse is normally 0x11, and
364 	 * the phantom mouse click occurs when it's 0x14.
365 	 */
366 	if (ms->sc_flags & HIDMS_LEADINGBYTE) {
367 		if (*data++ == 0x02)
368 			return;
369 		/* len--; */
370 	} else if (ms->sc_flags & HIDMS_SPUR_BUT_UP) {
371 		if (*data == 0x14 || *data == 0x15)
372 			return;
373 	}
374 
375 	dx =  hid_get_data(data, len, &ms->sc_loc_x);
376 	dy = -hid_get_data(data, len, &ms->sc_loc_y);
377 	dz =  hid_get_data(data, len, &ms->sc_loc_z);
378 	dw =  hid_get_data(data, len, &ms->sc_loc_w);
379 
380 	if (ms->sc_flags & HIDMS_ABSY)
381 		dy = -dy;
382 	if (ms->sc_flags & HIDMS_REVZ)
383 		dz = -dz;
384 	if (ms->sc_flags & HIDMS_REVW)
385 		dw = -dw;
386 
387 	if (ms->sc_tsscale.swapxy && !ms->sc_rawmode) {
388 		int tmp = dx;
389 		dx = dy;
390 		dy = tmp;
391 	}
392 
393 	if (!ms->sc_rawmode &&
394 	    (ms->sc_tsscale.maxx - ms->sc_tsscale.minx) != 0 &&
395 	    (ms->sc_tsscale.maxy - ms->sc_tsscale.miny) != 0) {
396 		/* Scale down to the screen resolution. */
397 		dx = ((dx - ms->sc_tsscale.minx) * ms->sc_tsscale.resx) /
398 		    (ms->sc_tsscale.maxx - ms->sc_tsscale.minx);
399 		dy = ((dy - ms->sc_tsscale.miny) * ms->sc_tsscale.resy) /
400 		    (ms->sc_tsscale.maxy - ms->sc_tsscale.miny);
401 	}
402 
403 	for (i = 0; i < ms->sc_num_buttons; i++)
404 		if (hid_get_data(data, len, &ms->sc_loc_btn[i]))
405 			buttons |= (1 << HIDMS_BUT(i));
406 
407 	if (dx != 0 || dy != 0 || dz != 0 || dw != 0 ||
408 	    buttons != ms->sc_buttons) {
409 		DPRINTFN(10, ("hidms_input: x:%d y:%d z:%d w:%d buttons:0x%x\n",
410 			dx, dy, dz, dw, buttons));
411 		ms->sc_buttons = buttons;
412 		if (ms->sc_wsmousedev != NULL) {
413 			s = spltty();
414 			if (ms->sc_flags & HIDMS_ABSX) {
415 				wsmouse_set(ms->sc_wsmousedev,
416 				    WSMOUSE_ABS_X, dx, 0);
417 				dx = 0;
418 			}
419 			if (ms->sc_flags & HIDMS_ABSY) {
420 				wsmouse_set(ms->sc_wsmousedev,
421 				    WSMOUSE_ABS_Y, dy, 0);
422 				dy = 0;
423 			}
424 			WSMOUSE_INPUT(ms->sc_wsmousedev,
425 			    buttons, dx, dy, dz, dw);
426 			splx(s);
427 		}
428 	}
429 }
430 
431 int
432 hidms_enable(struct hidms *ms)
433 {
434 	if (ms->sc_enabled)
435 		return EBUSY;
436 
437 	ms->sc_enabled = 1;
438 	ms->sc_buttons = 0;
439 	return 0;
440 }
441 
442 int
443 hidms_ioctl(struct hidms *ms, u_long cmd, caddr_t data, int flag,
444     struct proc *p)
445 {
446 	struct wsmouse_calibcoords *wsmc = (struct wsmouse_calibcoords *)data;
447 
448 	switch (cmd) {
449 	case WSMOUSEIO_SCALIBCOORDS:
450 		if (!(wsmc->minx >= -32768 && wsmc->maxx >= -32768 &&
451 		    wsmc->miny >= -32768 && wsmc->maxy >= -32768 &&
452 		    wsmc->resx >= 0 && wsmc->resy >= 0 &&
453 		    wsmc->minx < 32768 && wsmc->maxx < 32768 &&
454 		    wsmc->miny < 32768 && wsmc->maxy < 32768 &&
455 		    (wsmc->maxx - wsmc->minx) != 0 &&
456 		    (wsmc->maxy - wsmc->miny) != 0 &&
457 		    wsmc->resx < 32768 && wsmc->resy < 32768 &&
458 		    wsmc->swapxy >= 0 && wsmc->swapxy <= 1 &&
459 		    wsmc->samplelen >= 0 && wsmc->samplelen <= 1))
460 			return (EINVAL);
461 
462 		ms->sc_tsscale.minx = wsmc->minx;
463 		ms->sc_tsscale.maxx = wsmc->maxx;
464 		ms->sc_tsscale.miny = wsmc->miny;
465 		ms->sc_tsscale.maxy = wsmc->maxy;
466 		ms->sc_tsscale.swapxy = wsmc->swapxy;
467 		ms->sc_tsscale.resx = wsmc->resx;
468 		ms->sc_tsscale.resy = wsmc->resy;
469 		ms->sc_rawmode = wsmc->samplelen;
470 		return 0;
471 	case WSMOUSEIO_GCALIBCOORDS:
472 		wsmc->minx = ms->sc_tsscale.minx;
473 		wsmc->maxx = ms->sc_tsscale.maxx;
474 		wsmc->miny = ms->sc_tsscale.miny;
475 		wsmc->maxy = ms->sc_tsscale.maxy;
476 		wsmc->swapxy = ms->sc_tsscale.swapxy;
477 		wsmc->resx = ms->sc_tsscale.resx;
478 		wsmc->resy = ms->sc_tsscale.resy;
479 		wsmc->samplelen = ms->sc_rawmode;
480 		return 0;
481 	case WSMOUSEIO_GTYPE:
482 		if (ms->sc_flags & HIDMS_ABSX && ms->sc_flags & HIDMS_ABSY) {
483 			*(u_int *)data = WSMOUSE_TYPE_TPANEL;
484 			return 0;
485 		}
486 		/* FALLTHROUGH */
487 	default:
488 		return -1;
489 	}
490 }
491 
492 void
493 hidms_disable(struct hidms *ms)
494 {
495 	ms->sc_enabled = 0;
496 }
497