xref: /netbsd-src/sys/dev/usb/ums.c (revision 8ac07aec990b9d2e483062509d0a9fa5b4f57cf2)
1 /*	$NetBSD: ums.c,v 1.71 2008/02/18 05:24:24 dyoung 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  * 3. All advertising materials mentioning features or use of this software
20  *    must display the following acknowledgement:
21  *        This product includes software developed by the NetBSD
22  *        Foundation, Inc. and its contributors.
23  * 4. Neither the name of The NetBSD Foundation nor the names of its
24  *    contributors may be used to endorse or promote products derived
25  *    from this software without specific prior written permission.
26  *
27  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
28  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
29  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
30  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
31  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
32  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
33  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
34  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
35  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
37  * POSSIBILITY OF SUCH DAMAGE.
38  */
39 
40 /*
41  * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
42  */
43 
44 #include <sys/cdefs.h>
45 __KERNEL_RCSID(0, "$NetBSD: ums.c,v 1.71 2008/02/18 05:24:24 dyoung Exp $");
46 
47 #include <sys/param.h>
48 #include <sys/systm.h>
49 #include <sys/kernel.h>
50 #include <sys/malloc.h>
51 #include <sys/device.h>
52 #include <sys/ioctl.h>
53 #include <sys/tty.h>
54 #include <sys/file.h>
55 #include <sys/select.h>
56 #include <sys/proc.h>
57 #include <sys/vnode.h>
58 #include <sys/poll.h>
59 
60 #include <dev/usb/usb.h>
61 #include <dev/usb/usbhid.h>
62 
63 #include <dev/usb/usbdi.h>
64 #include <dev/usb/usbdi_util.h>
65 #include <dev/usb/usbdevs.h>
66 #include <dev/usb/usb_quirks.h>
67 #include <dev/usb/uhidev.h>
68 #include <dev/usb/hid.h>
69 
70 #include <dev/wscons/wsconsio.h>
71 #include <dev/wscons/wsmousevar.h>
72 
73 #ifdef USB_DEBUG
74 #define DPRINTF(x)	if (umsdebug) logprintf x
75 #define DPRINTFN(n,x)	if (umsdebug>(n)) logprintf x
76 int	umsdebug = 0;
77 #else
78 #define DPRINTF(x)
79 #define DPRINTFN(n,x)
80 #endif
81 
82 #define UMS_BUT(i) ((i) == 1 || (i) == 2 ? 3 - (i) : i)
83 
84 #define UMSUNIT(s)	(minor(s))
85 
86 #define PS2LBUTMASK	x01
87 #define PS2RBUTMASK	x02
88 #define PS2MBUTMASK	x04
89 #define PS2BUTMASK 0x0f
90 
91 #define MAX_BUTTONS	31	/* must not exceed size of sc_buttons */
92 
93 struct ums_softc {
94 	struct uhidev sc_hdev;
95 
96 	struct hid_location sc_loc_x, sc_loc_y, sc_loc_z, sc_loc_w;
97 	struct hid_location sc_loc_btn[MAX_BUTTONS];
98 
99 	int sc_enabled;
100 
101 	int flags;		/* device configuration */
102 #define UMS_Z		0x01	/* z direction available */
103 #define UMS_SPUR_BUT_UP	0x02	/* spurious button up events */
104 #define UMS_REVZ	0x04	/* Z-axis is reversed */
105 
106 	int nbuttons;
107 
108 	u_int32_t sc_buttons;	/* mouse button status */
109 	device_t sc_wsmousedev;
110 
111 	char			sc_dying;
112 };
113 
114 #define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
115 #define MOUSE_FLAGS (HIO_RELATIVE)
116 
117 Static void ums_intr(struct uhidev *addr, void *ibuf, u_int len);
118 
119 Static int	ums_enable(void *);
120 Static void	ums_disable(void *);
121 Static int	ums_ioctl(void *, u_long, void *, int, struct lwp * );
122 
123 const struct wsmouse_accessops ums_accessops = {
124 	ums_enable,
125 	ums_ioctl,
126 	ums_disable,
127 };
128 
129 int ums_match(device_t, struct cfdata *, void *);
130 void ums_attach(device_t, device_t, void *);
131 void ums_childdet(device_t, device_t);
132 int ums_detach(device_t, int);
133 int ums_activate(device_t, enum devact);
134 extern struct cfdriver ums_cd;
135 CFATTACH_DECL2(ums, sizeof(struct ums_softc), ums_match, ums_attach,
136     ums_detach, ums_activate, NULL, ums_childdet);
137 
138 int
139 ums_match(struct device *parent, struct cfdata *match,
140     void *aux)
141 {
142 	struct uhidev_attach_arg *uha = aux;
143 	int size;
144 	void *desc;
145 
146 	uhidev_get_report_desc(uha->parent, &desc, &size);
147 	if (!hid_is_collection(desc, size, uha->reportid,
148 			       HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
149 		return (UMATCH_NONE);
150 
151 	return (UMATCH_IFACECLASS);
152 }
153 
154 void
155 ums_attach(struct device *parent, struct device *self, void *aux)
156 {
157 	struct ums_softc *sc = (struct ums_softc *)self;
158 	struct uhidev_attach_arg *uha = aux;
159 	struct wsmousedev_attach_args a;
160 	int size;
161 	void *desc;
162 	u_int32_t flags, quirks;
163 	int i, wheel;
164 	struct hid_location loc_btn;
165 
166 	aprint_naive("\n");
167 
168 	sc->sc_hdev.sc_intr = ums_intr;
169 	sc->sc_hdev.sc_parent = uha->parent;
170 	sc->sc_hdev.sc_report_id = uha->reportid;
171 
172 	quirks = usbd_get_quirks(uha->parent->sc_udev)->uq_flags;
173 	if (quirks & UQ_MS_REVZ)
174 		sc->flags |= UMS_REVZ;
175 	if (quirks & UQ_SPUR_BUT_UP)
176 		sc->flags |= UMS_SPUR_BUT_UP;
177 
178 	uhidev_get_report_desc(uha->parent, &desc, &size);
179 
180 	if (!pmf_device_register(self, NULL, NULL))
181 		aprint_error_dev(self, "couldn't establish power handler\n");
182 
183 	if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
184 	       uha->reportid, hid_input, &sc->sc_loc_x, &flags)) {
185 		aprint_error("\n%s: mouse has no X report\n",
186 		       USBDEVNAME(sc->sc_hdev.sc_dev));
187 		USB_ATTACH_ERROR_RETURN;
188 	}
189 	if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
190 		aprint_error("\n%s: X report 0x%04x not supported\n",
191 		       USBDEVNAME(sc->sc_hdev.sc_dev), flags);
192 		USB_ATTACH_ERROR_RETURN;
193 	}
194 
195 	if (!hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
196 	       uha->reportid, hid_input, &sc->sc_loc_y, &flags)) {
197 		aprint_error("\n%s: mouse has no Y report\n",
198 		       USBDEVNAME(sc->sc_hdev.sc_dev));
199 		USB_ATTACH_ERROR_RETURN;
200 	}
201 	if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
202 		aprint_error("\n%s: Y report 0x%04x not supported\n",
203 		       USBDEVNAME(sc->sc_hdev.sc_dev), flags);
204 		USB_ATTACH_ERROR_RETURN;
205 	}
206 
207 	/* Try the wheel first as the Z activator since it's tradition. */
208 	wheel = hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP,
209 						  HUG_WHEEL),
210 			   uha->reportid, hid_input, &sc->sc_loc_z, &flags);
211 	if (wheel) {
212 		if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
213 			aprint_verbose("\n%s: Wheel report 0x%04x not "
214 			    "supported\n", USBDEVNAME(sc->sc_hdev.sc_dev),
215 			    flags);
216 			sc->sc_loc_z.size = 0;	/* Bad Z coord, ignore it */
217 		} else {
218 			sc->flags |= UMS_Z;
219 			/* Wheels need the Z axis reversed. */
220 			sc->flags ^= UMS_REVZ;
221 		}
222 		/*
223 		 * We might have both a wheel and Z direction, if so put
224 		 * put the Z on the W coordinate.
225 		 */
226 		if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP,
227 						      HUG_Z),
228 			uha->reportid, hid_input, &sc->sc_loc_w, &flags)) {
229 			if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
230 				aprint_verbose("\n%s: Z report 0x%04x not "
231 				    "supported\n",
232 				       USBDEVNAME(sc->sc_hdev.sc_dev), flags);
233 				sc->sc_loc_w.size = 0;	/* Bad Z, ignore */
234 			}
235 		}
236 	 } else if (hid_locate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP,
237 						      HUG_Z),
238 		      uha->reportid, hid_input, &sc->sc_loc_z, &flags)) {
239 		if ((flags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
240 			aprint_verbose("\n%s: Z report 0x%04x not supported\n",
241 			       USBDEVNAME(sc->sc_hdev.sc_dev), flags);
242 			sc->sc_loc_z.size = 0;	/* Bad Z coord, ignore it */
243 		} else {
244 			sc->flags |= UMS_Z;
245 		}
246 	}
247 
248 
249 	/* figure out the number of buttons */
250 	for (i = 1; i <= MAX_BUTTONS; i++)
251 		if (!hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i),
252 			uha->reportid, hid_input, &loc_btn, 0))
253 			break;
254 	sc->nbuttons = i - 1;
255 
256 	aprint_normal(": %d button%s%s\n",
257 	    sc->nbuttons, sc->nbuttons == 1 ? "" : "s",
258 	    sc->flags & UMS_Z ? " and Z dir." : "");
259 
260 	for (i = 1; i <= sc->nbuttons; i++)
261 		hid_locate(desc, size, HID_USAGE2(HUP_BUTTON, i),
262 			   uha->reportid, hid_input,
263 			   &sc->sc_loc_btn[i-1], 0);
264 
265 #ifdef USB_DEBUG
266 	DPRINTF(("ums_attach: sc=%p\n", sc));
267 	DPRINTF(("ums_attach: X\t%d/%d\n",
268 		 sc->sc_loc_x.pos, sc->sc_loc_x.size));
269 	DPRINTF(("ums_attach: Y\t%d/%d\n",
270 		 sc->sc_loc_y.pos, sc->sc_loc_y.size));
271 	if (sc->flags & UMS_Z)
272 		DPRINTF(("ums_attach: Z\t%d/%d\n",
273 			 sc->sc_loc_z.pos, sc->sc_loc_z.size));
274 	for (i = 1; i <= sc->nbuttons; i++) {
275 		DPRINTF(("ums_attach: B%d\t%d/%d\n",
276 			 i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size));
277 	}
278 #endif
279 
280 	a.accessops = &ums_accessops;
281 	a.accesscookie = sc;
282 
283 	sc->sc_wsmousedev = config_found(self, &a, wsmousedevprint);
284 
285 	USB_ATTACH_SUCCESS_RETURN;
286 }
287 
288 int
289 ums_activate(device_ptr_t self, enum devact act)
290 {
291 	struct ums_softc *sc = (struct ums_softc *)self;
292 	int rv = 0;
293 
294 	switch (act) {
295 	case DVACT_ACTIVATE:
296 		return (EOPNOTSUPP);
297 
298 	case DVACT_DEACTIVATE:
299 		if (sc->sc_wsmousedev != NULL)
300 			rv = config_deactivate(sc->sc_wsmousedev);
301 		sc->sc_dying = 1;
302 		break;
303 	}
304 	return (rv);
305 }
306 
307 void
308 ums_childdet(device_t self, device_t child)
309 {
310 	struct ums_softc *sc = device_private(self);
311 
312 	KASSERT(sc->sc_wsmousedev == child);
313 	sc->sc_wsmousedev = NULL;
314 }
315 
316 int
317 ums_detach(device_t self, int flags)
318 {
319 	struct ums_softc *sc = device_private(self);
320 	int rv = 0;
321 
322 	DPRINTF(("ums_detach: sc=%p flags=%d\n", sc, flags));
323 
324 	/* No need to do reference counting of ums, wsmouse has all the goo. */
325 	if (sc->sc_wsmousedev != NULL)
326 		rv = config_detach(sc->sc_wsmousedev, flags);
327 
328 	pmf_device_deregister(self);
329 
330 	return (rv);
331 }
332 
333 void
334 ums_intr(struct uhidev *addr, void *ibuf, u_int len)
335 {
336 	struct ums_softc *sc = (struct ums_softc *)addr;
337 	int dx, dy, dz, dw;
338 	u_int32_t buttons = 0;
339 	int i;
340 	int s;
341 
342 	DPRINTFN(5,("ums_intr: len=%d\n", len));
343 
344 	dx =  hid_get_data(ibuf, &sc->sc_loc_x);
345 	dy = -hid_get_data(ibuf, &sc->sc_loc_y);
346 	dz =  hid_get_data(ibuf, &sc->sc_loc_z);
347 	dw =  hid_get_data(ibuf, &sc->sc_loc_w);
348 	if (sc->flags & UMS_REVZ)
349 		dz = -dz;
350 	for (i = 0; i < sc->nbuttons; i++)
351 		if (hid_get_data(ibuf, &sc->sc_loc_btn[i]))
352 			buttons |= (1 << UMS_BUT(i));
353 
354 	if (dx != 0 || dy != 0 || dz != 0 || dw != 0 ||
355 	    buttons != sc->sc_buttons) {
356 		DPRINTFN(10, ("ums_intr: x:%d y:%d z:%d w:%d buttons:0x%x\n",
357 			dx, dy, dz, dw, buttons));
358 		sc->sc_buttons = buttons;
359 		if (sc->sc_wsmousedev != NULL) {
360 			s = spltty();
361 			wsmouse_input(sc->sc_wsmousedev,
362 					buttons,
363 					dx, dy, dz, dw,
364 					WSMOUSE_INPUT_DELTA);
365 			splx(s);
366 		}
367 	}
368 }
369 
370 Static int
371 ums_enable(void *v)
372 {
373 	struct ums_softc *sc = v;
374 
375 	DPRINTFN(1,("ums_enable: sc=%p\n", sc));
376 
377 	if (sc->sc_dying)
378 		return (EIO);
379 
380 	if (sc->sc_enabled)
381 		return (EBUSY);
382 
383 	sc->sc_enabled = 1;
384 	sc->sc_buttons = 0;
385 
386 	return (uhidev_open(&sc->sc_hdev));
387 }
388 
389 Static void
390 ums_disable(void *v)
391 {
392 	struct ums_softc *sc = v;
393 
394 	DPRINTFN(1,("ums_disable: sc=%p\n", sc));
395 #ifdef DIAGNOSTIC
396 	if (!sc->sc_enabled) {
397 		printf("ums_disable: not enabled\n");
398 		return;
399 	}
400 #endif
401 
402 	sc->sc_enabled = 0;
403 	uhidev_close(&sc->sc_hdev);
404 }
405 
406 Static int
407 ums_ioctl(void *v, u_long cmd, void *data, int flag,
408     struct lwp * p)
409 
410 {
411 	switch (cmd) {
412 	case WSMOUSEIO_GTYPE:
413 		*(u_int *)data = WSMOUSE_TYPE_USB;
414 		return (0);
415 	}
416 
417 	return (EPASSTHROUGH);
418 }
419