1/* $NetBSD: ums.c,v 1.90 2016/04/27 19:35:17 jakllsch Exp $ */
2
3/*
4 * Copyright (c) 1998 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Lennart Augustsson (lennart@augustsson.net) at
9 * Carlstedt Research & Technology.
10 *
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
21 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
24 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30 * POSSIBILITY OF SUCH DAMAGE.
31 */
32
33/*
34 * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
35 */
36
37#include <sys/cdefs.h>
38__KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.90 2016/04/27 19:35:17 jakllsch Exp $");
39
40#ifdef _KERNEL_OPT
41#include "opt_usb.h"
42#endif
43
44#include <sys/param.h>
45#include <sys/systm.h>
46#include <sys/kernel.h>
47#include <sys/device.h>
48#include <sys/ioctl.h>
49#include <sys/file.h>
50#include <sys/select.h>
51#include <sys/proc.h>
52#include <sys/vnode.h>
53#include <sys/poll.h>
54
55#include <dev/usb/usb.h>
56#include <dev/usb/usbhid.h>
57
58#include <dev/usb/usbdi.h>
59#include <dev/usb/usbdi_util.h>
60#include <dev/usb/usbdevs.h>
61#include <dev/usb/usb_quirks.h>
62#include <dev/usb/uhidev.h>
63#include <dev/usb/hid.h>
64
65#include <dev/wscons/wsconsio.h>
66#include <dev/wscons/wsmousevar.h>
67
68#ifdef UMS_DEBUG
69#define DPRINTF(x) if (umsdebug) printf x
70#define DPRINTFN(n,x) if (umsdebug>(n)) printf x
71int umsdebug = 0;
72#else
73#define DPRINTF(x)
74#define DPRINTFN(n,x)
75#endif
76
77#define UMS_BUT(i) ((i) == 1 || (i) == 2 ? 3 - (i) : i)
78
79#define UMSUNIT(s) (minor(s))
80
81#define PS2LBUTMASK x01
82#define PS2RBUTMASK x02
83#define PS2MBUTMASK x04
84#define PS2BUTMASK 0x0f
85
86#define MAX_BUTTONS 31 /* must not exceed size of sc_buttons */
87
88struct ums_softc {
89 struct uhidev sc_hdev;
90
91 struct hid_location sc_loc_x, sc_loc_y, sc_loc_z, sc_loc_w;
92 struct hid_location sc_loc_btn[MAX_BUTTONS];
93
94 int sc_enabled;
95
96 u_int flags; /* device configuration */
97#define UMS_Z 0x001 /* z direction available */
98#define UMS_SPUR_BUT_UP 0x002 /* spurious button up events */
99#define UMS_REVZ 0x004 /* Z-axis is reversed */
100#define UMS_W 0x008 /* w direction/tilt available */
101#define UMS_ABS 0x010 /* absolute position, touchpanel */
102#define UMS_TIP_SWITCH 0x020 /* digitizer tip switch */
103#define UMS_SEC_TIP_SWITCH 0x040 /* digitizer secondary tip switch */
104#define UMS_BARREL_SWITCH 0x080 /* digitizer barrel switch */
105#define UMS_ERASER 0x100 /* digitizer eraser */
106
107 int nbuttons;
108
109 uint32_t sc_buttons; /* mouse button status */
110 device_t sc_wsmousedev;
111
112 char sc_dying;
113};
114
115static const struct {
116 u_int feature;
117 u_int flag;
118} digbut[] = {
119 { HUD_TIP_SWITCH, UMS_TIP_SWITCH },
120 { HUD_SEC_TIP_SWITCH, UMS_SEC_TIP_SWITCH },
121 { HUD_BARREL_SWITCH, UMS_BARREL_SWITCH },
122 { HUD_ERASER, UMS_ERASER },
123};
124
125#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
126
127Static void ums_intr(struct uhidev *, void *, u_int);
128
129Static int ums_enable(void *);
130Static void ums_disable(void *);
131Static int ums_ioctl(void *, u_long, void *, int, struct lwp *);
132
133const struct wsmouse_accessops ums_accessops = {
134 ums_enable,
135 ums_ioctl,
136 ums_disable,
137};
138
139int ums_match(device_t, cfdata_t, void *);
140void ums_attach(device_t, device_t, void *);
141void ums_childdet(device_t, device_t);
142int ums_detach(device_t, int);
143int ums_activate(device_t, enum devact);
144extern struct cfdriver ums_cd;
145CFATTACH_DECL2_NEW(ums, sizeof(struct ums_softc), ums_match, ums_attach,
146 ums_detach, ums_activate, NULL, ums_childdet);
147
148int
149ums_match(device_t parent, cfdata_t match, void *aux)
150{
151 struct uhidev_attach_arg *uha = aux;
152 int size;
153 void *desc;
154
155 /*
156 * Some (older) Griffin PowerMate knobs may masquerade as a
157 * mouse, avoid treating them as such, they have only one axis.
158 */
159 if (uha->uiaa->uiaa_vendor == USB_VENDOR_GRIFFIN &&
160 uha->uiaa->uiaa_product == USB_PRODUCT_GRIFFIN_POWERMATE)
161 return UMATCH_NONE;
162
163 uhidev_get_report_desc(uha->parent, &desc, &size);
164 if (!hid_is_collection(desc, size, uha->reportid,
165 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) &&
166 !hid_is_collection(desc, size, uha->reportid,
167 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_POINTER)) &&
168 !hid_is_collection(desc, size, uha->reportid,
169 HID_USAGE2(HUP_DIGITIZERS, 0x0002)))
170 return UMATCH_NONE;
171
172 return UMATCH_IFACECLASS;
173}
174
175void
176ums_attach(device_t parent, device_t self, void *aux)
177{
178 struct ums_softc *sc = device_private(self);
179 struct uhidev_attach_arg *uha = aux;
180 struct wsmousedev_attach_args a;
181 int size;
182 void *desc;
183 uint32_t flags, quirks;
184 int i, hl;
185 struct hid_location *zloc;
186 bool isdigitizer;
187
188 aprint_naive("\n");
189
190 sc->sc_hdev.sc_dev = self;
191 sc->sc_hdev.sc_intr = ums_intr;
192 sc->sc_hdev.sc_parent = uha->parent;
193 sc->sc_hdev.sc_report_id = uha->reportid;
194
195 quirks = usbd_get_quirks(uha->parent->sc_udev)->uq_flags;
196 if (quirks & UQ_MS_REVZ)
197 sc->flags |= UMS_REVZ;
198 if (quirks & UQ_SPUR_BUT_UP)
199 sc->flags |= UMS_SPUR_BUT_UP;
200
201 uhidev_get_report_desc(uha->parent, &desc, &size);
202
203 isdigitizer = hid_is_collection(desc, size, uha->reportid,
204 HID_USAGE2(HUP_DIGITIZERS, 0x0002));
205
206 if (!pmf_device_register(self, NULL, NULL))
207 aprint_error_dev(self, "couldn't establish power handler\n");
208
209 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
210 uha->reportid, hid_input, &sc->sc_loc_x, &flags)) {
211 aprint_error("\n%s: mouse has no X report\n",
212 device_xname(sc->sc_hdev.sc_dev));
213 return;
214 }
215 switch (flags & MOUSE_FLAGS_MASK) {
216 case 0:
217 sc->flags |= UMS_ABS;
218 break;
219 case HIO_RELATIVE:
220 break;
221 default:
222 aprint_error("\n%s: X report 0x%04x not supported\n",
223 device_xname(sc->sc_hdev.sc_dev), flags);
224 return;
225 }
226
227 if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
228 uha->reportid, hid_input, &sc->sc_loc_y, &flags)) {
229 aprint_error("\n%s: mouse has no Y report\n",
230 device_xname(sc->sc_hdev.sc_dev));
231 return;
232 }
233 switch (flags & MOUSE_FLAGS_MASK) {
234 case 0:
235 sc->flags |= UMS_ABS;
236 break;
237 case HIO_RELATIVE:
238 break;
239 default:
240 aprint_error("\n%s: Y report 0x%04x not supported\n",
241 device_xname(sc->sc_hdev.sc_dev), flags);
242 return;
243 }
244
245 /* Try the wheel first as the Z activator since it's tradition. */
246 hl = hid_locate(desc,
247 size,
248 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL),
249 uha->reportid,
250 hid_input,
251 &sc->sc_loc_z,
252 &flags);
253
254 zloc = &sc->sc_loc_z;
255 if (hl) {
256 if ((flags & MOUSE_FLAGS_MASK) != HIO_RELATIVE) {
257 aprint_verbose("\n%s: Wheel report 0x%04x not "
258 "supported\n", device_xname(sc->sc_hdev.sc_dev),
259 flags);
260 sc->sc_loc_z.size = 0; /* Bad Z coord, ignore it */
261 } else {
262 sc->flags |= UMS_Z;
263 /* Wheels need the Z axis reversed. */
264 sc->flags ^= UMS_REVZ;
265 /* Put Z on the W coordinate */
266 zloc = &sc->sc_loc_w;
267 }
268 }
269
270 hl = hid_locate(desc,
271 size,
272 HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z),
273 uha->reportid,
274 hid_input,
275 zloc,
276 &flags);
277
278 /*
279 * The horizontal component of the scrollball can also be given by
280 * Application Control Pan in the Consumer page, so if we didnt see
281 * any Z then check that.
282 */
283 if (!hl) {
284 hl = hid_locate(desc,
285 size,
286 HID_USAGE2(HUP_CONSUMER, HUC_AC_PAN),
287 uha->reportid,
288 hid_input,
289 zloc,
290 &flags);
291 }
292
293 if (hl) {
294 if ((flags & MOUSE_FLAGS_MASK) != HIO_RELATIVE) {
295 aprint_verbose("\n%s: Z report 0x%04x not supported\n",
296 device_xname(sc->sc_hdev.sc_dev), flags);
297 zloc->size = 0; /* Bad Z coord, ignore it */
298 } else {
299 if (sc->flags & UMS_Z)
300 sc->flags |= UMS_W;
301 else
302 sc->flags |= UMS_Z;
303 }
304 }
305
306 if (uha->uiaa->uiaa_vendor == USB_VENDOR_MICROSOFT) {
307 int fixpos;
308 /*
309 * The Microsoft Wireless Laser Mouse 6000 v2.0 and the
310 * Microsoft Comfort Mouse 2.0 report a bad position for
311 * the wheel and wheel tilt controls -- should be in bytes
312 * 3 & 4 of the report. Fix this if necessary.
313 */
314 switch (uha->uiaa->uiaa_product) {
315 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR10:
316 case USB_PRODUCT_MICROSOFT_24GHZ_XCVR20:
317 fixpos = 24;
318 break;
319 case USB_PRODUCT_MICROSOFT_CM6000:
320 fixpos = 40;
321 break;
322 default:
323 fixpos = 0;
324 break;
325 }
326 if (fixpos) {
327 if ((sc->flags & UMS_Z) && sc->sc_loc_z.pos == 0)
328 sc->sc_loc_z.pos = fixpos;
329 if ((sc->flags & UMS_W) && sc->sc_loc_w.pos == 0)
330 sc->sc_loc_w.pos = sc->sc_loc_z.pos + 8;
331 }
332 }
333
334 /* figure out the number of buttons */
335 for (i = 1; i <= MAX_BUTTONS; i++)
336 if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i),
337 uha->reportid, hid_input, &sc->sc_loc_btn[i - 1], 0))
338 break;
339
340 if (isdigitizer) {
341 for (size_t j = 0; j < __arraycount(digbut); j++) {
342 if (hid_locate(desc, size, HID_USAGE2(HUP_DIGITIZERS,
343 digbut[j].feature), uha->reportid, hid_input,
344 &sc->sc_loc_btn[i - 1], 0)) {
345 if (i <= MAX_BUTTONS) {
346 i++;
347 sc->flags |= digbut[j].flag;
348 } else
349 aprint_error_dev(self,
350 "ran out of buttons\n");
351 }
352 }
353 }
354 sc->nbuttons = i - 1;
355
356 aprint_normal(": %d button%s%s%s%s%s%s%s%s%s\n",
357 sc->nbuttons, sc->nbuttons == 1 ? "" : "s",
358 sc->flags & UMS_W ? ", W" : "",
359 sc->flags & UMS_Z ? " and Z dir" : "",
360 sc->flags & UMS_W ? "s" : "",
361 isdigitizer ? " digitizer" : "",
362 sc->flags & UMS_TIP_SWITCH ? ", tip" : "",
363 sc->flags & UMS_SEC_TIP_SWITCH ? ", sec tip" : "",
364 sc->flags & UMS_BARREL_SWITCH ? ", barrel" : "",
365 sc->flags & UMS_ERASER ? ", eraser" : "");
366
367#ifdef UMS_DEBUG
368 DPRINTF(("ums_attach: sc=%p\n", sc));
369 DPRINTF(("ums_attach: X\t%d/%d\n",
370 sc->sc_loc_x.pos, sc->sc_loc_x.size));
371 DPRINTF(("ums_attach: Y\t%d/%d\n",
372 sc->sc_loc_y.pos, sc->sc_loc_y.size));
373 if (sc->flags & UMS_Z)
374 DPRINTF(("ums_attach: Z\t%d/%d\n",
375 sc->sc_loc_z.pos, sc->sc_loc_z.size));
376 if (sc->flags & UMS_W)
377 DPRINTF(("ums_attach: W\t%d/%d\n",
378 sc->sc_loc_w.pos, sc->sc_loc_w.size));
379 for (i = 1; i <= sc->nbuttons; i++) {
380 DPRINTF(("ums_attach: B%d\t%d/%d\n",
381 i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size));
382 }
383#endif
384
385 a.accessops = &ums_accessops;
386 a.accesscookie = sc;
387
388 sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
389
390 return;
391}
392
393int
394ums_activate(device_t self, enum devact act)
395{
396 struct ums_softc *sc = device_private(self);
397
398 switch (act) {
399 case DVACT_DEACTIVATE:
400 sc->sc_dying = 1;
401 return 0;
402 default:
403 return EOPNOTSUPP;
404 }
405}
406
407void
408ums_childdet(device_t self, device_t child)
409{
410 struct ums_softc *sc = device_private(self);
411
412 KASSERT(sc->sc_wsmousedev == child);
413 sc->sc_wsmousedev = NULL;
414}
415
416int
417ums_detach(device_t self, int flags)
418{
419 struct ums_softc *sc = device_private(self);
420 int rv = 0;
421
422 DPRINTF(("ums_detach: sc=%p flags=%d\n", sc, flags));
423
424 /* No need to do reference counting of ums, wsmouse has all the goo. */
425 if (sc->sc_wsmousedev != NULL)
426 rv = config_detach(sc->sc_wsmousedev, flags);
427
428 pmf_device_deregister(self);
429
430 return rv;
431}
432
433void
434ums_intr(struct uhidev *addr, void *ibuf, u_int len)
435{
436 struct ums_softc *sc = (struct ums_softc *)addr;
437 int dx, dy, dz, dw;
438 uint32_t buttons = 0;
439 int i, flags, s;
440
441 DPRINTFN(5,("ums_intr: len=%d\n", len));
442
443 flags = WSMOUSE_INPUT_DELTA; /* equals 0 */
444
445 dx = hid_get_data(ibuf, &sc->sc_loc_x);
446 if (sc->flags & UMS_ABS) {
447 flags |= (WSMOUSE_INPUT_ABSOLUTE_X | WSMOUSE_INPUT_ABSOLUTE_Y);
448 dy = hid_get_data(ibuf, &sc->sc_loc_y);
449 } else
450 dy = -hid_get_data(ibuf, &sc->sc_loc_y);
451 dz = hid_get_data(ibuf, &sc->sc_loc_z);
452 dw = hid_get_data(ibuf, &sc->sc_loc_w);
453
454 if (sc->flags & UMS_REVZ)
455 dz = -dz;
456 for (i = 0; i < sc->nbuttons; i++)
457 if (hid_get_data(ibuf, &sc->sc_loc_btn[i]))
458 buttons |= (1 << UMS_BUT(i));
459
460 if (dx != 0 || dy != 0 || dz != 0 || dw != 0 ||
461 buttons != sc->sc_buttons) {
462 DPRINTFN(10, ("ums_intr: x:%d y:%d z:%d w:%d buttons:0x%x\n",
463 dx, dy, dz, dw, buttons));
464 sc->sc_buttons = buttons;
465 if (sc->sc_wsmousedev != NULL) {
466 s = spltty();
467 wsmouse_input(sc->sc_wsmousedev, buttons, dx, dy, dz,
468 dw, flags);
469 splx(s);
470 }
471 }
472}
473
474Static int
475ums_enable(void *v)
476{
477 struct ums_softc *sc = v;
478 int error;
479
480 DPRINTFN(1,("ums_enable: sc=%p\n", sc));
481
482 if (sc->sc_dying)
483 return EIO;
484
485 if (sc->sc_enabled)
486 return EBUSY;
487
488 sc->sc_enabled = 1;
489 sc->sc_buttons = 0;
490
491 error = uhidev_open(&sc->sc_hdev);
492 if (error)
493 sc->sc_enabled = 0;
494
495 return error;
496}
497
498Static void
499ums_disable(void *v)
500{
501 struct ums_softc *sc = v;
502
503 DPRINTFN(1,("ums_disable: sc=%p\n", sc));
504#ifdef DIAGNOSTIC
505 if (!sc->sc_enabled) {
506 printf("ums_disable: not enabled\n");
507 return;
508 }
509#endif
510
511 if (sc->sc_enabled) {
512 sc->sc_enabled = 0;
513 uhidev_close(&sc->sc_hdev);
514 }
515}
516
517Static int
518ums_ioctl(void *v, u_long cmd, void *data, int flag,
519 struct lwp * p)
520
521{
522 struct ums_softc *sc = v;
523
524 switch (cmd) {
525 case WSMOUSEIO_GTYPE:
526 if (sc->flags & UMS_ABS)
527 *(u_int *)data = WSMOUSE_TYPE_TPANEL;
528 else
529 *(u_int *)data = WSMOUSE_TYPE_USB;
530 return 0;
531 }
532
533 return EPASSTHROUGH;
534}
535