ums.c revision 189110
1184610Salfred/*-
2184610Salfred * Copyright (c) 1998 The NetBSD Foundation, Inc.
3184610Salfred * All rights reserved.
4184610Salfred *
5184610Salfred * This code is derived from software contributed to The NetBSD Foundation
6184610Salfred * by Lennart Augustsson (lennart@augustsson.net) at
7184610Salfred * Carlstedt Research & Technology.
8184610Salfred *
9184610Salfred * Redistribution and use in source and binary forms, with or without
10184610Salfred * modification, are permitted provided that the following conditions
11184610Salfred * are met:
12184610Salfred * 1. Redistributions of source code must retain the above copyright
13184610Salfred *    notice, this list of conditions and the following disclaimer.
14184610Salfred * 2. Redistributions in binary form must reproduce the above copyright
15184610Salfred *    notice, this list of conditions and the following disclaimer in the
16184610Salfred *    documentation and/or other materials provided with the distribution.
17184610Salfred * 3. All advertising materials mentioning features or use of this software
18184610Salfred *    must display the following acknowledgement:
19184610Salfred *        This product includes software developed by the NetBSD
20184610Salfred *        Foundation, Inc. and its contributors.
21184610Salfred * 4. Neither the name of The NetBSD Foundation nor the names of its
22184610Salfred *    contributors may be used to endorse or promote products derived
23184610Salfred *    from this software without specific prior written permission.
24184610Salfred *
25184610Salfred * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
26184610Salfred * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
27184610Salfred * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
28184610Salfred * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
29184610Salfred * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
30184610Salfred * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
31184610Salfred * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
32184610Salfred * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
33184610Salfred * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34184610Salfred * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
35184610Salfred * POSSIBILITY OF SUCH DAMAGE.
36184610Salfred */
37184610Salfred
38184610Salfred#include <sys/cdefs.h>
39184610Salfred__FBSDID("$FreeBSD: head/sys/dev/usb/input/ums.c 189110 2009-02-27 17:27:16Z thompsa $");
40184610Salfred
41184610Salfred/*
42184610Salfred * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
43184610Salfred */
44184610Salfred
45188746Sthompsa#include "usbdevs.h"
46188942Sthompsa#include <dev/usb/usb.h>
47188942Sthompsa#include <dev/usb/usb_mfunc.h>
48188942Sthompsa#include <dev/usb/usb_error.h>
49188942Sthompsa#include <dev/usb/usbhid.h>
50184610Salfred
51184610Salfred#define	USB_DEBUG_VAR ums_debug
52184610Salfred
53188942Sthompsa#include <dev/usb/usb_core.h>
54188942Sthompsa#include <dev/usb/usb_util.h>
55188942Sthompsa#include <dev/usb/usb_debug.h>
56188942Sthompsa#include <dev/usb/usb_busdma.h>
57188942Sthompsa#include <dev/usb/usb_process.h>
58188942Sthompsa#include <dev/usb/usb_transfer.h>
59188942Sthompsa#include <dev/usb/usb_request.h>
60188942Sthompsa#include <dev/usb/usb_dynamic.h>
61188942Sthompsa#include <dev/usb/usb_mbuf.h>
62188942Sthompsa#include <dev/usb/usb_dev.h>
63188942Sthompsa#include <dev/usb/usb_hid.h>
64184610Salfred
65188942Sthompsa#include <dev/usb/quirk/usb_quirk.h>
66184610Salfred
67184610Salfred#include <sys/ioccom.h>
68184610Salfred#include <sys/filio.h>
69184610Salfred#include <sys/tty.h>
70184610Salfred#include <sys/mouse.h>
71184610Salfred
72184610Salfred#if USB_DEBUG
73184610Salfredstatic int ums_debug = 0;
74184610Salfred
75184610SalfredSYSCTL_NODE(_hw_usb2, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums");
76184610SalfredSYSCTL_INT(_hw_usb2_ums, OID_AUTO, debug, CTLFLAG_RW,
77184610Salfred    &ums_debug, 0, "Debug level");
78184610Salfred#endif
79184610Salfred
80184610Salfred#define	MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
81184610Salfred#define	MOUSE_FLAGS (HIO_RELATIVE)
82184610Salfred
83184610Salfred#define	UMS_BUF_SIZE      8		/* bytes */
84184610Salfred#define	UMS_IFQ_MAXLEN   50		/* units */
85184610Salfred#define	UMS_BUTTON_MAX   31		/* exclusive, must be less than 32 */
86184610Salfred#define	UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i))
87184610Salfred
88187259Sthompsaenum {
89187259Sthompsa	UMS_INTR_DT,
90188981Sthompsa	UMS_N_TRANSFER,
91187259Sthompsa};
92187259Sthompsa
93184610Salfredstruct ums_softc {
94184610Salfred	struct usb2_fifo_sc sc_fifo;
95184610Salfred	struct mtx sc_mtx;
96184610Salfred	struct usb2_callout sc_callout;
97184610Salfred	struct hid_location sc_loc_w;
98184610Salfred	struct hid_location sc_loc_x;
99184610Salfred	struct hid_location sc_loc_y;
100184610Salfred	struct hid_location sc_loc_z;
101184610Salfred	struct hid_location sc_loc_t;
102184610Salfred	struct hid_location sc_loc_btn[UMS_BUTTON_MAX];
103184610Salfred	mousehw_t sc_hw;
104184610Salfred	mousemode_t sc_mode;
105184610Salfred	mousestatus_t sc_status;
106184610Salfred
107184610Salfred	struct usb2_xfer *sc_xfer[UMS_N_TRANSFER];
108184610Salfred
109184610Salfred	uint32_t sc_flags;
110184610Salfred#define	UMS_FLAG_X_AXIS     0x0001
111184610Salfred#define	UMS_FLAG_Y_AXIS     0x0002
112184610Salfred#define	UMS_FLAG_Z_AXIS     0x0004
113184610Salfred#define	UMS_FLAG_T_AXIS     0x0008
114184610Salfred#define	UMS_FLAG_SBU        0x0010	/* spurious button up events */
115188981Sthompsa#define	UMS_FLAG_REVZ	    0x0020	/* Z-axis is reversed */
116188981Sthompsa#define	UMS_FLAG_W_AXIS     0x0040
117184610Salfred
118184610Salfred	uint8_t	sc_buttons;
119184610Salfred	uint8_t	sc_iid;
120184610Salfred	uint8_t	sc_temp[64];
121184610Salfred};
122184610Salfred
123184610Salfredstatic void ums_put_queue_timeout(void *__sc);
124184610Salfred
125184610Salfredstatic usb2_callback_t ums_intr_callback;
126184610Salfred
127184610Salfredstatic device_probe_t ums_probe;
128184610Salfredstatic device_attach_t ums_attach;
129184610Salfredstatic device_detach_t ums_detach;
130184610Salfred
131184610Salfredstatic usb2_fifo_cmd_t ums_start_read;
132184610Salfredstatic usb2_fifo_cmd_t ums_stop_read;
133184610Salfredstatic usb2_fifo_open_t ums_open;
134184610Salfredstatic usb2_fifo_close_t ums_close;
135184610Salfredstatic usb2_fifo_ioctl_t ums_ioctl;
136184610Salfred
137184610Salfredstatic void ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy, int32_t dz, int32_t dt, int32_t buttons);
138184610Salfred
139184610Salfredstatic struct usb2_fifo_methods ums_fifo_methods = {
140184610Salfred	.f_open = &ums_open,
141184610Salfred	.f_close = &ums_close,
142184610Salfred	.f_ioctl = &ums_ioctl,
143184610Salfred	.f_start_read = &ums_start_read,
144184610Salfred	.f_stop_read = &ums_stop_read,
145184610Salfred	.basename[0] = "ums",
146184610Salfred};
147184610Salfred
148184610Salfredstatic void
149184610Salfredums_put_queue_timeout(void *__sc)
150184610Salfred{
151184610Salfred	struct ums_softc *sc = __sc;
152184610Salfred
153184610Salfred	mtx_assert(&sc->sc_mtx, MA_OWNED);
154184610Salfred
155184610Salfred	ums_put_queue(sc, 0, 0, 0, 0, 0);
156184610Salfred}
157184610Salfred
158184610Salfredstatic void
159184610Salfredums_intr_callback(struct usb2_xfer *xfer)
160184610Salfred{
161184610Salfred	struct ums_softc *sc = xfer->priv_sc;
162184610Salfred	uint8_t *buf = sc->sc_temp;
163184610Salfred	uint16_t len = xfer->actlen;
164184610Salfred	int32_t buttons = 0;
165184610Salfred	int32_t dw;
166184610Salfred	int32_t dx;
167184610Salfred	int32_t dy;
168184610Salfred	int32_t dz;
169184610Salfred	int32_t dt;
170184610Salfred	uint8_t i;
171184610Salfred
172184610Salfred	switch (USB_GET_STATE(xfer)) {
173184610Salfred	case USB_ST_TRANSFERRED:
174184610Salfred		DPRINTFN(6, "sc=%p actlen=%d\n", sc, len);
175184610Salfred
176184610Salfred		if (len > sizeof(sc->sc_temp)) {
177184610Salfred			DPRINTFN(6, "truncating large packet to %zu bytes\n",
178184610Salfred			    sizeof(sc->sc_temp));
179184610Salfred			len = sizeof(sc->sc_temp);
180184610Salfred		}
181188981Sthompsa		if (len == 0)
182184610Salfred			goto tr_setup;
183188981Sthompsa
184184610Salfred		usb2_copy_out(xfer->frbuffers, 0, buf, len);
185184610Salfred
186184610Salfred		DPRINTFN(6, "data = %02x %02x %02x %02x "
187184610Salfred		    "%02x %02x %02x %02x\n",
188184610Salfred		    (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0,
189184610Salfred		    (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0,
190184610Salfred		    (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0,
191184610Salfred		    (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0);
192184610Salfred
193184610Salfred		/*
194188981Sthompsa		 * The M$ Wireless Intellimouse 2.0 sends 1 extra
195188981Sthompsa		 * leading byte of data compared to most USB
196188981Sthompsa		 * mice. This byte frequently switches from 0x01
197188981Sthompsa		 * (usual state) to 0x02. I assume it is to allow
198188981Sthompsa		 * extra, non-standard, reporting (say battery-life).
199184610Salfred		 *
200188981Sthompsa		 * However at the same time it generates a left-click
201188981Sthompsa		 * message on the button byte which causes spurious
202188981Sthompsa		 * left-click's where there shouldn't be.  This should
203188981Sthompsa		 * sort that.  Currently it's the only user of
204188981Sthompsa		 * UMS_FLAG_T_AXIS so use it as an identifier.
205184610Salfred		 *
206184610Salfred		 *
207188981Sthompsa		 * UPDATE: This problem affects the M$ Wireless
208188981Sthompsa		 * Notebook Optical Mouse, too. However, the leading
209188981Sthompsa		 * byte for this mouse is normally 0x11, and the
210188981Sthompsa		 * phantom mouse click occurs when its 0x14.
211184610Salfred		 *
212184610Salfred		 * We probably should switch to some more official quirk.
213184610Salfred		 */
214184610Salfred		if (sc->sc_iid) {
215184610Salfred			if (sc->sc_flags & UMS_FLAG_T_AXIS) {
216184610Salfred				if (*buf == 0x02) {
217184610Salfred					goto tr_setup;
218184610Salfred				}
219184610Salfred			} else {
220184610Salfred				if (*buf != sc->sc_iid) {
221184610Salfred					goto tr_setup;
222184610Salfred				}
223184610Salfred			}
224184610Salfred
225184610Salfred			len--;
226184610Salfred			buf++;
227184610Salfred
228184610Salfred		} else {
229184610Salfred			if (sc->sc_flags & UMS_FLAG_SBU) {
230184610Salfred				if ((*buf == 0x14) || (*buf == 0x15)) {
231184610Salfred					goto tr_setup;
232184610Salfred				}
233184610Salfred			}
234184610Salfred		}
235184610Salfred
236184610Salfred		dw = (sc->sc_flags & UMS_FLAG_W_AXIS) ?
237184610Salfred		    hid_get_data(buf, len, &sc->sc_loc_w) : 0;
238184610Salfred
239184610Salfred		dx = (sc->sc_flags & UMS_FLAG_X_AXIS) ?
240184610Salfred		    hid_get_data(buf, len, &sc->sc_loc_x) : 0;
241184610Salfred
242184610Salfred		dy = (sc->sc_flags & UMS_FLAG_Y_AXIS) ?
243184610Salfred		    -hid_get_data(buf, len, &sc->sc_loc_y) : 0;
244184610Salfred
245184610Salfred		dz = (sc->sc_flags & UMS_FLAG_Z_AXIS) ?
246184610Salfred		    -hid_get_data(buf, len, &sc->sc_loc_z) : 0;
247184610Salfred
248184610Salfred		if (sc->sc_flags & UMS_FLAG_REVZ) {
249184610Salfred			dz = -dz;
250184610Salfred		}
251184610Salfred		dt = (sc->sc_flags & UMS_FLAG_T_AXIS) ?
252184610Salfred		    -hid_get_data(buf, len, &sc->sc_loc_t): 0;
253184610Salfred
254184610Salfred		for (i = 0; i < sc->sc_buttons; i++) {
255184610Salfred			if (hid_get_data(buf, len, &sc->sc_loc_btn[i])) {
256184610Salfred				buttons |= (1 << UMS_BUT(i));
257184610Salfred			}
258184610Salfred		}
259184610Salfred
260184610Salfred		if (dx || dy || dz || dt || dw ||
261184610Salfred		    (buttons != sc->sc_status.button)) {
262184610Salfred
263184610Salfred			DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n",
264184610Salfred			    dx, dy, dz, dt, dw, buttons);
265184610Salfred
266184610Salfred			sc->sc_status.button = buttons;
267184610Salfred			sc->sc_status.dx += dx;
268184610Salfred			sc->sc_status.dy += dy;
269184610Salfred			sc->sc_status.dz += dz;
270184610Salfred			/*
271184610Salfred			 * sc->sc_status.dt += dt;
272184610Salfred			 * no way to export this yet
273184610Salfred			 */
274184610Salfred
275184610Salfred			/*
276188981Sthompsa		         * The Qtronix keyboard has a built in PS/2
277188981Sthompsa		         * port for a mouse.  The firmware once in a
278188981Sthompsa		         * while posts a spurious button up
279188981Sthompsa		         * event. This event we ignore by doing a
280188981Sthompsa		         * timeout for 50 msecs.  If we receive
281188981Sthompsa		         * dx=dy=dz=buttons=0 before we add the event
282188981Sthompsa		         * to the queue.  In any other case we delete
283188981Sthompsa		         * the timeout event.
284184610Salfred		         */
285184610Salfred			if ((sc->sc_flags & UMS_FLAG_SBU) &&
286184610Salfred			    (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) &&
287184610Salfred			    (dw == 0) && (buttons == 0)) {
288184610Salfred
289184610Salfred				usb2_callout_reset(&sc->sc_callout, hz / 20,
290184610Salfred				    &ums_put_queue_timeout, sc);
291184610Salfred			} else {
292184610Salfred
293184610Salfred				usb2_callout_stop(&sc->sc_callout);
294184610Salfred
295184610Salfred				ums_put_queue(sc, dx, dy, dz, dt, buttons);
296184610Salfred			}
297184610Salfred		}
298184610Salfred	case USB_ST_SETUP:
299184610Salfredtr_setup:
300188981Sthompsa		/* check if we can put more data into the FIFO */
301188981Sthompsa		if (usb2_fifo_put_bytes_max(
302188981Sthompsa		    sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
303188981Sthompsa			xfer->frlengths[0] = xfer->max_data_length;
304188981Sthompsa			usb2_start_hardware(xfer);
305184610Salfred		}
306188981Sthompsa		break;
307184610Salfred
308184610Salfred	default:			/* Error */
309184610Salfred		if (xfer->error != USB_ERR_CANCELLED) {
310188981Sthompsa			/* try clear stall first */
311188981Sthompsa			xfer->flags.stall_pipe = 1;
312188981Sthompsa			goto tr_setup;
313184610Salfred		}
314188981Sthompsa		break;
315184610Salfred	}
316184610Salfred}
317184610Salfred
318184610Salfredstatic const struct usb2_config ums_config[UMS_N_TRANSFER] = {
319184610Salfred
320187259Sthompsa	[UMS_INTR_DT] = {
321184610Salfred		.type = UE_INTERRUPT,
322184610Salfred		.endpoint = UE_ADDR_ANY,
323184610Salfred		.direction = UE_DIR_IN,
324184610Salfred		.mh.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
325184610Salfred		.mh.bufsize = 0,	/* use wMaxPacketSize */
326184610Salfred		.mh.callback = &ums_intr_callback,
327184610Salfred	},
328184610Salfred};
329184610Salfred
330184610Salfredstatic int
331184610Salfredums_probe(device_t dev)
332184610Salfred{
333184610Salfred	struct usb2_attach_arg *uaa = device_get_ivars(dev);
334184610Salfred	struct usb2_interface_descriptor *id;
335184610Salfred	void *d_ptr;
336188981Sthompsa	int error;
337184610Salfred	uint16_t d_len;
338184610Salfred
339184610Salfred	DPRINTFN(11, "\n");
340184610Salfred
341188981Sthompsa	if (uaa->usb2_mode != USB_MODE_HOST)
342184610Salfred		return (ENXIO);
343188981Sthompsa
344184610Salfred	id = usb2_get_interface_descriptor(uaa->iface);
345184610Salfred
346184610Salfred	if ((id == NULL) ||
347188981Sthompsa	    (id->bInterfaceClass != UICLASS_HID))
348184610Salfred		return (ENXIO);
349188981Sthompsa
350188981Sthompsa	error = usb2_req_get_hid_desc(uaa->device, &Giant,
351184610Salfred	    &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
352184610Salfred
353188981Sthompsa	if (error)
354184610Salfred		return (ENXIO);
355188981Sthompsa
356184610Salfred	if (hid_is_collection(d_ptr, d_len,
357188981Sthompsa	    HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)))
358184610Salfred		error = 0;
359188981Sthompsa	else if ((id->bInterfaceSubClass == UISUBCLASS_BOOT) &&
360188981Sthompsa	    (id->bInterfaceProtocol == UIPROTO_MOUSE))
361184610Salfred		error = 0;
362188981Sthompsa	else
363184610Salfred		error = ENXIO;
364184610Salfred
365184610Salfred	free(d_ptr, M_TEMP);
366184610Salfred	return (error);
367184610Salfred}
368184610Salfred
369184610Salfredstatic int
370184610Salfredums_attach(device_t dev)
371184610Salfred{
372184610Salfred	struct usb2_attach_arg *uaa = device_get_ivars(dev);
373184610Salfred	struct ums_softc *sc = device_get_softc(dev);
374184610Salfred	void *d_ptr = NULL;
375184610Salfred	int unit = device_get_unit(dev);
376188981Sthompsa	int isize;
377188981Sthompsa	int isizebits;
378188981Sthompsa	int err;
379184610Salfred	uint32_t flags;
380184610Salfred	uint16_t d_len;
381184610Salfred	uint8_t i;
382184610Salfred
383184610Salfred	DPRINTFN(11, "sc=%p\n", sc);
384184610Salfred
385184610Salfred	device_set_usb2_desc(dev);
386184610Salfred
387184610Salfred	mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE);
388184610Salfred
389186454Sthompsa	usb2_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
390184610Salfred
391184610Salfred	/*
392184610Salfred         * Force the report (non-boot) protocol.
393184610Salfred         *
394184610Salfred         * Mice without boot protocol support may choose not to implement
395184610Salfred         * Set_Protocol at all; Ignore any error.
396184610Salfred         */
397184610Salfred	err = usb2_req_set_protocol(uaa->device, NULL, uaa->info.bIfaceIndex, 1);
398184610Salfred
399184610Salfred	err = usb2_transfer_setup(uaa->device,
400184610Salfred	    &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config,
401184610Salfred	    UMS_N_TRANSFER, sc, &sc->sc_mtx);
402184610Salfred
403184610Salfred	if (err) {
404184610Salfred		DPRINTF("error=%s\n", usb2_errstr(err));
405184610Salfred		goto detach;
406184610Salfred	}
407184610Salfred	err = usb2_req_get_hid_desc
408184610Salfred	    (uaa->device, &Giant, &d_ptr,
409184610Salfred	    &d_len, M_TEMP, uaa->info.bIfaceIndex);
410184610Salfred
411184610Salfred	if (err) {
412184610Salfred		device_printf(dev, "error reading report description\n");
413184610Salfred		goto detach;
414184610Salfred	}
415184610Salfred	if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
416188981Sthompsa	    hid_input, &sc->sc_loc_x, &flags, &sc->sc_iid)) {
417184610Salfred
418184610Salfred		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
419184610Salfred			sc->sc_flags |= UMS_FLAG_X_AXIS;
420184610Salfred		}
421184610Salfred	}
422184610Salfred	if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
423188981Sthompsa	    hid_input, &sc->sc_loc_y, &flags, &sc->sc_iid)) {
424184610Salfred
425184610Salfred		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
426184610Salfred			sc->sc_flags |= UMS_FLAG_Y_AXIS;
427184610Salfred		}
428184610Salfred	}
429184610Salfred	/* Try the wheel first as the Z activator since it's tradition. */
430184610Salfred	if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP,
431188981Sthompsa	    HUG_WHEEL), hid_input, &sc->sc_loc_z, &flags, &sc->sc_iid) ||
432184610Salfred	    hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP,
433188981Sthompsa	    HUG_TWHEEL), hid_input, &sc->sc_loc_z, &flags, &sc->sc_iid)) {
434184610Salfred		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
435184610Salfred			sc->sc_flags |= UMS_FLAG_Z_AXIS;
436184610Salfred		}
437184610Salfred		/*
438184610Salfred		 * We might have both a wheel and Z direction, if so put
439184610Salfred		 * put the Z on the W coordinate.
440184610Salfred		 */
441184610Salfred		if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP,
442188981Sthompsa		    HUG_Z), hid_input, &sc->sc_loc_w, &flags, &sc->sc_iid)) {
443184610Salfred
444184610Salfred			if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
445184610Salfred				sc->sc_flags |= UMS_FLAG_W_AXIS;
446184610Salfred			}
447184610Salfred		}
448184610Salfred	} else if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP,
449188981Sthompsa	    HUG_Z), hid_input, &sc->sc_loc_z, &flags, &sc->sc_iid)) {
450184610Salfred
451184610Salfred		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
452184610Salfred			sc->sc_flags |= UMS_FLAG_Z_AXIS;
453184610Salfred		}
454184610Salfred	}
455184610Salfred	/*
456184610Salfred	 * The Microsoft Wireless Intellimouse 2.0 reports it's wheel
457184610Salfred	 * using 0x0048, which is HUG_TWHEEL, and seems to expect you
458184610Salfred	 * to know that the byte after the wheel is the tilt axis.
459184610Salfred	 * There are no other HID axis descriptors other than X,Y and
460184610Salfred	 * TWHEEL
461184610Salfred	 */
462184610Salfred	if (hid_locate(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL),
463188981Sthompsa	    hid_input, &sc->sc_loc_t, &flags, &sc->sc_iid)) {
464184610Salfred
465184610Salfred		sc->sc_loc_t.pos += 8;
466184610Salfred
467184610Salfred		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
468184610Salfred			sc->sc_flags |= UMS_FLAG_T_AXIS;
469184610Salfred		}
470184610Salfred	}
471184610Salfred	/* figure out the number of buttons */
472184610Salfred
473184610Salfred	for (i = 0; i < UMS_BUTTON_MAX; i++) {
474184610Salfred		if (!hid_locate(d_ptr, d_len, HID_USAGE2(HUP_BUTTON, (i + 1)),
475188981Sthompsa			hid_input, &sc->sc_loc_btn[i], NULL, &sc->sc_iid)) {
476184610Salfred			break;
477184610Salfred		}
478184610Salfred	}
479184610Salfred
480184610Salfred	sc->sc_buttons = i;
481184610Salfred
482188981Sthompsa	isize = hid_report_size(d_ptr, d_len, hid_input, NULL);
483184610Salfred
484184610Salfred	/*
485184610Salfred	 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse
486184610Salfred	 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and
487184610Salfred	 * all of its other button positions are all off. It also reports that
488184610Salfred	 * it has two addional buttons and a tilt wheel.
489184610Salfred	 */
490184610Salfred	if (usb2_test_quirk(uaa, UQ_MS_BAD_CLASS)) {
491184610Salfred		sc->sc_flags = (UMS_FLAG_X_AXIS |
492184610Salfred		    UMS_FLAG_Y_AXIS |
493184610Salfred		    UMS_FLAG_Z_AXIS |
494184610Salfred		    UMS_FLAG_SBU);
495184610Salfred		sc->sc_buttons = 3;
496184610Salfred		isize = 5;
497184610Salfred		sc->sc_iid = 0;
498184610Salfred		/* 1st byte of descriptor report contains garbage */
499184610Salfred		sc->sc_loc_x.pos = 16;
500184610Salfred		sc->sc_loc_y.pos = 24;
501184610Salfred		sc->sc_loc_z.pos = 32;
502184610Salfred		sc->sc_loc_btn[0].pos = 8;
503184610Salfred		sc->sc_loc_btn[1].pos = 9;
504184610Salfred		sc->sc_loc_btn[2].pos = 10;
505184610Salfred	}
506188981Sthompsa
507184610Salfred	/*
508188981Sthompsa	 * Some Microsoft devices have incorrectly high location
509188981Sthompsa	 * positions. Correct this:
510184610Salfred	 */
511188981Sthompsa	isizebits = isize * 8;
512188981Sthompsa	if ((sc->sc_iid != 0) && (isizebits > 8)) {
513188981Sthompsa		isizebits -= 8;	/* remove size of report ID */
514188981Sthompsa		sc->sc_loc_w.pos %= isizebits;
515188981Sthompsa		sc->sc_loc_x.pos %= isizebits;
516188981Sthompsa		sc->sc_loc_y.pos %= isizebits;
517188981Sthompsa		sc->sc_loc_z.pos %= isizebits;
518188981Sthompsa		sc->sc_loc_t.pos %= isizebits;
519188981Sthompsa		for (i = 0; i != UMS_BUTTON_MAX; i++)
520188981Sthompsa			sc->sc_loc_btn[i].pos %= isizebits;
521184610Salfred	}
522188981Sthompsa
523184610Salfred	if (usb2_test_quirk(uaa, UQ_MS_REVZ)) {
524184610Salfred		/* Some wheels need the Z axis reversed. */
525184610Salfred		sc->sc_flags |= UMS_FLAG_REVZ;
526184610Salfred	}
527187259Sthompsa	if (isize > sc->sc_xfer[UMS_INTR_DT]->max_frame_size) {
528184610Salfred		DPRINTF("WARNING: report size, %d bytes, is larger "
529184610Salfred		    "than interrupt size, %d bytes!\n",
530187259Sthompsa		    isize, sc->sc_xfer[UMS_INTR_DT]->max_frame_size);
531184610Salfred	}
532184610Salfred	/* announce information about the mouse */
533184610Salfred
534184610Salfred	device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates\n",
535184610Salfred	    (sc->sc_buttons),
536184610Salfred	    (sc->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "",
537184610Salfred	    (sc->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "",
538184610Salfred	    (sc->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "",
539184610Salfred	    (sc->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "",
540184610Salfred	    (sc->sc_flags & UMS_FLAG_W_AXIS) ? "W" : "");
541184610Salfred
542184610Salfred	free(d_ptr, M_TEMP);
543184610Salfred	d_ptr = NULL;
544184610Salfred
545184610Salfred#if USB_DEBUG
546184610Salfred	DPRINTF("sc=%p\n", sc);
547184610Salfred	DPRINTF("X\t%d/%d\n", sc->sc_loc_x.pos, sc->sc_loc_x.size);
548184610Salfred	DPRINTF("Y\t%d/%d\n", sc->sc_loc_y.pos, sc->sc_loc_y.size);
549184610Salfred	DPRINTF("Z\t%d/%d\n", sc->sc_loc_z.pos, sc->sc_loc_z.size);
550184610Salfred	DPRINTF("T\t%d/%d\n", sc->sc_loc_t.pos, sc->sc_loc_t.size);
551184610Salfred	DPRINTF("W\t%d/%d\n", sc->sc_loc_w.pos, sc->sc_loc_w.size);
552184610Salfred
553184610Salfred	for (i = 0; i < sc->sc_buttons; i++) {
554184610Salfred		DPRINTF("B%d\t%d/%d\n",
555184610Salfred		    i + 1, sc->sc_loc_btn[i].pos, sc->sc_loc_btn[i].size);
556184610Salfred	}
557184610Salfred	DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid);
558184610Salfred#endif
559184610Salfred
560184610Salfred	if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
561184610Salfred		sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
562184610Salfred	else
563184610Salfred		sc->sc_hw.buttons = sc->sc_buttons;
564184610Salfred
565184610Salfred	sc->sc_hw.iftype = MOUSE_IF_USB;
566184610Salfred	sc->sc_hw.type = MOUSE_MOUSE;
567184610Salfred	sc->sc_hw.model = MOUSE_MODEL_GENERIC;
568184610Salfred	sc->sc_hw.hwid = 0;
569184610Salfred
570184610Salfred	sc->sc_mode.protocol = MOUSE_PROTO_MSC;
571184610Salfred	sc->sc_mode.rate = -1;
572184610Salfred	sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
573184610Salfred	sc->sc_mode.accelfactor = 0;
574184610Salfred	sc->sc_mode.level = 0;
575184610Salfred	sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
576184610Salfred	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
577184610Salfred	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
578184610Salfred
579184610Salfred	sc->sc_status.flags = 0;
580184610Salfred	sc->sc_status.button = 0;
581184610Salfred	sc->sc_status.obutton = 0;
582184610Salfred	sc->sc_status.dx = 0;
583184610Salfred	sc->sc_status.dy = 0;
584184610Salfred	sc->sc_status.dz = 0;
585184610Salfred
586184610Salfred	err = usb2_fifo_attach(uaa->device, sc, &sc->sc_mtx,
587184610Salfred	    &ums_fifo_methods, &sc->sc_fifo,
588189110Sthompsa	    unit, 0 - 1, uaa->info.bIfaceIndex,
589189110Sthompsa  	    UID_ROOT, GID_OPERATOR, 0644);
590184610Salfred	if (err) {
591184610Salfred		goto detach;
592184610Salfred	}
593184610Salfred	return (0);
594184610Salfred
595184610Salfreddetach:
596184610Salfred	if (d_ptr) {
597184610Salfred		free(d_ptr, M_TEMP);
598184610Salfred	}
599184610Salfred	ums_detach(dev);
600184610Salfred	return (ENOMEM);
601184610Salfred}
602184610Salfred
603184610Salfredstatic int
604184610Salfredums_detach(device_t self)
605184610Salfred{
606184610Salfred	struct ums_softc *sc = device_get_softc(self);
607184610Salfred
608184610Salfred	DPRINTF("sc=%p\n", sc);
609184610Salfred
610184610Salfred	usb2_fifo_detach(&sc->sc_fifo);
611184610Salfred
612184610Salfred	usb2_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER);
613184610Salfred
614184610Salfred	usb2_callout_drain(&sc->sc_callout);
615184610Salfred
616184610Salfred	mtx_destroy(&sc->sc_mtx);
617184610Salfred
618184610Salfred	return (0);
619184610Salfred}
620184610Salfred
621184610Salfredstatic void
622184610Salfredums_start_read(struct usb2_fifo *fifo)
623184610Salfred{
624184610Salfred	struct ums_softc *sc = fifo->priv_sc0;
625184610Salfred
626187259Sthompsa	usb2_transfer_start(sc->sc_xfer[UMS_INTR_DT]);
627184610Salfred}
628184610Salfred
629184610Salfredstatic void
630184610Salfredums_stop_read(struct usb2_fifo *fifo)
631184610Salfred{
632184610Salfred	struct ums_softc *sc = fifo->priv_sc0;
633184610Salfred
634187259Sthompsa	usb2_transfer_stop(sc->sc_xfer[UMS_INTR_DT]);
635184610Salfred	usb2_callout_stop(&sc->sc_callout);
636184610Salfred}
637184610Salfred
638184610Salfred
639184610Salfred#if ((MOUSE_SYS_PACKETSIZE != 8) || \
640184610Salfred     (MOUSE_MSC_PACKETSIZE != 5))
641184610Salfred#error "Software assumptions are not met. Please update code."
642184610Salfred#endif
643184610Salfred
644184610Salfredstatic void
645184610Salfredums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy,
646184610Salfred    int32_t dz, int32_t dt, int32_t buttons)
647184610Salfred{
648184610Salfred	uint8_t buf[8];
649184610Salfred
650184610Salfred	if (1) {
651184610Salfred
652184610Salfred		if (dx > 254)
653184610Salfred			dx = 254;
654184610Salfred		if (dx < -256)
655184610Salfred			dx = -256;
656184610Salfred		if (dy > 254)
657184610Salfred			dy = 254;
658184610Salfred		if (dy < -256)
659184610Salfred			dy = -256;
660184610Salfred		if (dz > 126)
661184610Salfred			dz = 126;
662184610Salfred		if (dz < -128)
663184610Salfred			dz = -128;
664184610Salfred		if (dt > 126)
665184610Salfred			dt = 126;
666184610Salfred		if (dt < -128)
667184610Salfred			dt = -128;
668184610Salfred
669184610Salfred		buf[0] = sc->sc_mode.syncmask[1];
670184610Salfred		buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS;
671184610Salfred		buf[1] = dx >> 1;
672184610Salfred		buf[2] = dy >> 1;
673184610Salfred		buf[3] = dx - (dx >> 1);
674184610Salfred		buf[4] = dy - (dy >> 1);
675184610Salfred
676184610Salfred		if (sc->sc_mode.level == 1) {
677184610Salfred			buf[5] = dz >> 1;
678184610Salfred			buf[6] = dz - (dz >> 1);
679184610Salfred			buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS);
680184610Salfred		}
681184610Salfred		usb2_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
682184610Salfred		    sc->sc_mode.packetsize, 1);
683184610Salfred
684184610Salfred	} else {
685184610Salfred		DPRINTF("Buffer full, discarded packet\n");
686184610Salfred	}
687184610Salfred}
688184610Salfred
689184610Salfredstatic void
690184610Salfredums_reset_buf(struct ums_softc *sc)
691184610Salfred{
692184610Salfred	/* reset read queue */
693184610Salfred	usb2_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
694184610Salfred}
695184610Salfred
696184610Salfredstatic int
697189110Sthompsaums_open(struct usb2_fifo *fifo, int fflags)
698184610Salfred{
699184610Salfred	struct ums_softc *sc = fifo->priv_sc0;
700184610Salfred
701184610Salfred	DPRINTFN(2, "\n");
702184610Salfred
703184610Salfred	if (fflags & FREAD) {
704184610Salfred
705184610Salfred		/* reset status */
706184610Salfred
707184610Salfred		sc->sc_status.flags = 0;
708184610Salfred		sc->sc_status.button = 0;
709184610Salfred		sc->sc_status.obutton = 0;
710184610Salfred		sc->sc_status.dx = 0;
711184610Salfred		sc->sc_status.dy = 0;
712184610Salfred		sc->sc_status.dz = 0;
713184610Salfred		/* sc->sc_status.dt = 0; */
714184610Salfred
715184610Salfred		if (usb2_fifo_alloc_buffer(fifo,
716184610Salfred		    UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) {
717184610Salfred			return (ENOMEM);
718184610Salfred		}
719184610Salfred	}
720184610Salfred	return (0);
721184610Salfred}
722184610Salfred
723184610Salfredstatic void
724189110Sthompsaums_close(struct usb2_fifo *fifo, int fflags)
725184610Salfred{
726184610Salfred	if (fflags & FREAD) {
727184610Salfred		usb2_fifo_free_buffer(fifo);
728184610Salfred	}
729184610Salfred}
730184610Salfred
731184610Salfredstatic int
732189110Sthompsaums_ioctl(struct usb2_fifo *fifo, u_long cmd, void *addr, int fflags)
733184610Salfred{
734184610Salfred	struct ums_softc *sc = fifo->priv_sc0;
735184610Salfred	mousemode_t mode;
736184610Salfred	int error = 0;
737184610Salfred
738184610Salfred	DPRINTFN(2, "\n");
739184610Salfred
740184610Salfred	mtx_lock(&sc->sc_mtx);
741184610Salfred
742184610Salfred	switch (cmd) {
743184610Salfred	case MOUSE_GETHWINFO:
744184610Salfred		*(mousehw_t *)addr = sc->sc_hw;
745184610Salfred		break;
746184610Salfred
747184610Salfred	case MOUSE_GETMODE:
748184610Salfred		*(mousemode_t *)addr = sc->sc_mode;
749184610Salfred		break;
750184610Salfred
751184610Salfred	case MOUSE_SETMODE:
752184610Salfred		mode = *(mousemode_t *)addr;
753184610Salfred
754184610Salfred		if (mode.level == -1) {
755184610Salfred			/* don't change the current setting */
756184610Salfred		} else if ((mode.level < 0) || (mode.level > 1)) {
757184610Salfred			error = EINVAL;
758184610Salfred			goto done;
759184610Salfred		} else {
760184610Salfred			sc->sc_mode.level = mode.level;
761184610Salfred		}
762184610Salfred
763184610Salfred		if (sc->sc_mode.level == 0) {
764184610Salfred			if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
765184610Salfred				sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
766184610Salfred			else
767184610Salfred				sc->sc_hw.buttons = sc->sc_buttons;
768184610Salfred			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
769184610Salfred			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
770184610Salfred			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
771184610Salfred			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
772184610Salfred		} else if (sc->sc_mode.level == 1) {
773184610Salfred			if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
774184610Salfred				sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
775184610Salfred			else
776184610Salfred				sc->sc_hw.buttons = sc->sc_buttons;
777184610Salfred			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
778184610Salfred			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
779184610Salfred			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
780184610Salfred			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
781184610Salfred		}
782184610Salfred		ums_reset_buf(sc);
783184610Salfred		break;
784184610Salfred
785184610Salfred	case MOUSE_GETLEVEL:
786184610Salfred		*(int *)addr = sc->sc_mode.level;
787184610Salfred		break;
788184610Salfred
789184610Salfred	case MOUSE_SETLEVEL:
790184610Salfred		if (*(int *)addr < 0 || *(int *)addr > 1) {
791184610Salfred			error = EINVAL;
792184610Salfred			goto done;
793184610Salfred		}
794184610Salfred		sc->sc_mode.level = *(int *)addr;
795184610Salfred
796184610Salfred		if (sc->sc_mode.level == 0) {
797184610Salfred			if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
798184610Salfred				sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
799184610Salfred			else
800184610Salfred				sc->sc_hw.buttons = sc->sc_buttons;
801184610Salfred			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
802184610Salfred			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
803184610Salfred			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
804184610Salfred			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
805184610Salfred		} else if (sc->sc_mode.level == 1) {
806184610Salfred			if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
807184610Salfred				sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
808184610Salfred			else
809184610Salfred				sc->sc_hw.buttons = sc->sc_buttons;
810184610Salfred			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
811184610Salfred			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
812184610Salfred			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
813184610Salfred			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
814184610Salfred		}
815184610Salfred		ums_reset_buf(sc);
816184610Salfred		break;
817184610Salfred
818184610Salfred	case MOUSE_GETSTATUS:{
819184610Salfred			mousestatus_t *status = (mousestatus_t *)addr;
820184610Salfred
821184610Salfred			*status = sc->sc_status;
822184610Salfred			sc->sc_status.obutton = sc->sc_status.button;
823184610Salfred			sc->sc_status.button = 0;
824184610Salfred			sc->sc_status.dx = 0;
825184610Salfred			sc->sc_status.dy = 0;
826184610Salfred			sc->sc_status.dz = 0;
827184610Salfred			/* sc->sc_status.dt = 0; */
828184610Salfred
829184610Salfred			if (status->dx || status->dy || status->dz /* || status->dt */ ) {
830184610Salfred				status->flags |= MOUSE_POSCHANGED;
831184610Salfred			}
832184610Salfred			if (status->button != status->obutton) {
833184610Salfred				status->flags |= MOUSE_BUTTONSCHANGED;
834184610Salfred			}
835184610Salfred			break;
836184610Salfred		}
837184610Salfred	default:
838184610Salfred		error = ENOTTY;
839184610Salfred	}
840184610Salfred
841184610Salfreddone:
842184610Salfred	mtx_unlock(&sc->sc_mtx);
843184610Salfred	return (error);
844184610Salfred}
845184610Salfred
846184610Salfredstatic devclass_t ums_devclass;
847184610Salfred
848184610Salfredstatic device_method_t ums_methods[] = {
849184610Salfred	DEVMETHOD(device_probe, ums_probe),
850184610Salfred	DEVMETHOD(device_attach, ums_attach),
851184610Salfred	DEVMETHOD(device_detach, ums_detach),
852184610Salfred	{0, 0}
853184610Salfred};
854184610Salfred
855184610Salfredstatic driver_t ums_driver = {
856184610Salfred	.name = "ums",
857184610Salfred	.methods = ums_methods,
858184610Salfred	.size = sizeof(struct ums_softc),
859184610Salfred};
860184610Salfred
861184610SalfredDRIVER_MODULE(ums, ushub, ums_driver, ums_devclass, NULL, 0);
862188942SthompsaMODULE_DEPEND(ums, usb, 1, 1, 1);
863