/*-
 * Copyright (c) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Lennart Augustsson (lennart@augustsson.net) at
 * Carlstedt Research & Technology.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. all advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        this product includes software developed by the netbsd
 *        foundation, inc. and its contributors.
 * 4. neither the name of the netbsd foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * this software is provided by the netbsd foundation, inc. and contributors
 * ``as is'' and any express or implied warranties, including, but not limited
 * to, the implied warranties of merchantability and fitness for a particular
 * purpose are disclaimed.  in no event shall the foundation or contributors
 * be liable for any direct, indirect, incidental, special, exemplary, or
 * consequential damages (including, but not limited to, procurement of
 * substitute goods or services; loss of use, data, or profits; or business
 * interruption) however caused and on any theory of liability, whether in
 * contract, strict liability, or tort (including negligence or otherwise)
 * arising in any way out of the use of this software, even if advised of the
 * possibility of such damage.
 */
/* UMSPATCH-dhh-1a 02/24/08 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/dev/usb/ums.c,v 1.77.2.5 2007/06/17 09:38:26 brueffer Exp $");

/*
 * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
 */

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <sys/ioccom.h>
#include <sys/conf.h>
#include <sys/fcntl.h>
#include <sys/tty.h>
#include <sys/file.h>
#include <sys/selinfo.h>
#include <sys/poll.h>
#include <sys/sysctl.h>
#include <sys/uio.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbhid.h>

#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <machine/bus.h>
#include <dev/usb/usbdivar.h>
#include "usbdevs.h"
#include <dev/usb/usb_quirks.h>
#include <dev/usb/hid.h>

#include <sys/mouse.h>

/* Define this while the code is experimental
 * set umsdebug to 1
 * use DPRINTF for enhanced  reporting ###%%%
 *
 * [dhh 02/10/08]
 */
#ifndef USB_DEBUG
#define USB_DEBUG
#endif

#ifdef USB_DEBUG
#define DPRINTF(x)	if (umsdebug) logprintf x
#define DPRINTFN(n,x)	if (umsdebug>(n)) logprintf x
int	umsdebug = 1;
SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums");
SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RW,
	   &umsdebug, 0, "ums debug level");
#else
#define DPRINTF(x)
#define DPRINTFN(n,x)
#endif

#define UMSUNIT(s)	(minor(s)&0x1f)

#define MS_TO_TICKS(ms) ((ms) * hz / 1000)

#define QUEUE_BUFSIZE	960	/* MUST be divisible by 5, 8 _and_ 12 */

struct ums_softc {
	device_t sc_dev;		/* base device */
	usbd_device_handle sc_device;	/* usb device */
	usbd_interface_handle sc_iface;	/* interface */
	usbd_pipe_handle sc_intrpipe;	/* interrupt pipe */
	int sc_ep_addr;

	u_char *sc_ibuf;
	u_int8_t sc_iid;
	int sc_isize;
	struct hid_location sc_loc_x, sc_loc_y, sc_loc_z, sc_loc_t, sc_loc_w;
	struct hid_location *sc_loc_btn;

	void *sc_repdesc;
	int sc_repdesc_size;

	usb_callout_t callout_handle;	/* for spurious button ups */

	int sc_enabled;
	int sc_disconnected;	/* device is gone */

	int flags;		/* device configuration */
#define UMS_Z		0x01	/* z direction available */
#define UMS_W          	0x02	/* wheel data available  */
#define UMS_T		0x04	/* tilt wheel data available */
#define UMS_REVZ	0x08	/* Z-axis is reversed */
#define UMS_SPUR_BUT_UP	0x10	/* spurious button up events */
	int nbuttons;
#define MAX_BUTTONS	31	/* chosen because sc_buttons is int */

	u_char		qbuf[QUEUE_BUFSIZE];	/* must be divisable by 3&4 */
	u_char		dummy[100];	/* XXX just for safety and for now */
	int		qcount, qhead, qtail;
	mousehw_t	hw;
	mousemode_t	mode;
	mousestatus_t	status;

	int		state;
#	  define	UMS_ASLEEP	0x01	/* readFromDevice is waiting */
#	  define	UMS_SELECT	0x02	/* select is waiting */
	struct selinfo	rsel;		/* process waiting in select */

	struct cdev *dev;		/* specfs */
};

#define MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
#define MOUSE_FLAGS (HIO_RELATIVE)

Static void ums_intr(usbd_xfer_handle xfer,
			  usbd_private_handle priv, usbd_status status);

Static void ums_add_to_queue(struct ums_softc *sc,
				int dx, int dy, int dz, int dt, int buttons);
Static void ums_add_to_queue_timeout(void *priv);

Static int  ums_enable(void *);
Static void ums_disable(void *);

Static d_open_t  ums_open;
Static d_close_t ums_close;
Static d_read_t  ums_read;
Static d_ioctl_t ums_ioctl;
Static d_poll_t  ums_poll;


Static struct cdevsw ums_cdevsw = {
	.d_version =	D_VERSION,
	.d_flags =	D_NEEDGIANT,
	.d_open =	ums_open,
	.d_close =	ums_close,
	.d_read =	ums_read,
	.d_ioctl =	ums_ioctl,
	.d_poll =	ums_poll,
	.d_name =	"ums",
};

USB_DECLARE_DRIVER(ums);

USB_MATCH(ums)
{
	USB_MATCH_START(ums, uaa);
	usb_interface_descriptor_t *id;
	int size, ret;
	void *desc;
	usbd_status err;

	if (!uaa->iface)
		return (UMATCH_NONE);
	id = usbd_get_interface_descriptor(uaa->iface);
	if (!id || id->bInterfaceClass != UICLASS_HID)
		return (UMATCH_NONE);

	err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP);
	if (err)
		return (UMATCH_NONE);

	/* 
	 * If class is HID and protocol is MOUSE, attach.  Subclass
	 * should not be relevant. [dhh]
	 */
	if ((id->bInterfaceClass == UICLASS_HID) &&
	    (id->bInterfaceProtocol == UIPROTO_MOUSE))
		ret = UMATCH_IFACECLASS;
	else
		ret = UMATCH_NONE;

	free(desc, M_TEMP);
	return (ret);
}

/*
 * The most basic mouse device supported by this driver is a
 * mouse providing data for motion along the X and Y axes, with
 * two or three buttons.  Buttons are reported in one byte,
 * and each motion axis is reported in one byte.  Mouse up/down
 * events are represented by 1 bit per button in the button byte). 

 * When operating in this most basic mode mouse reports 3 bytes
 * of input data for each event:
 *      [button, X, Y]
 *
 * Some USB mice make this basic protocol available to the bios for
 * use before the OS is loaded.  This is "boot protocol".
 *
 * XFree86 , and hence Xorg, has for some time supported mice with
 * a wheel or roller, where the wheel data is presented as a third
 * (Z) axis, and the wheel is also used as the middle button.  This style
 * of mouse is common for modern USB mice, and the X server mouse driver
 * typically maps the Z axis to pseudo-buttons, and the display driver
 * subsequently uses the data for vertical scrolling. 
 *
 * Some wheel mice also generate data when the wheel axis is pushed
 * sideways or "tilted".  Xorg and XFree permit this data to be mapped for
 * horizontal scrolling, but the current X drivers do not actually
 * read the tilt data, and earlier versions of this driver read it but
 * did not write it (the "sysmouse" protocol packet size is too small to
 * handle more data).  This driver writes tilt data if "mode level 3" is
 * set via ioctl.  An X driver (or modified version of moused) which
 * handles the required extended protocol and sets protocol level 3
 * must be in place.  If the extended X driver is not in place, this
 * driver will operate at mode level 1 (as requested by the X driver)
 * and the wheel will work but not the "tilt".  For either version,
 * the display driver (X) must be specifically configured (in the X
 * config file) to accept Z and/or TILT data. (see ...../README.mouse 
 * somewhere in your X sources for a description of ZAxisMapping).
 * 
 * Wheel mice, when properly initialized, will communicate in a
 * "report protocol" which may carry more information than the basic
 * boot protocol. If "report protocol" is set, the report descriptor
 * may include one or more report descriptions. If only one report is
 * available it will not have a report id, and a mouse report will deliver
 * 4 data bytes per event:
 *      [button, X, Y, Z]
 *
 * If a tilt capability is present, a fifth byte may appear:
 *      [button, X, Y, Z, Tilt]
 *
 * Mice with multiple report IDs will have an extra "id" byte prepended,
 * yielding
 *      [id, button, X, Y, Z]
 * or
 *      [id, button, X, Y, Z, Tilt]
 *
 * This driver sets up wheel mice to deliver wheel data on the
 * Z channel, and tilt data, if present, is delivered on the byte
 * following the Z channel, as shown above.  Again, tilt data will be
 * written only if the extended SYSMOUSE protocol is defined, and the
 * X mouse driver modified to accept it.  Moused and the sysmouse/syscons
 * driver must also be modified for the extended protocol, and the mode
 * level set to level 3.  The PS/2 driver might also want to get involved.
 *
 * In this driver, mice with a wheel are expected to report the
 * presence of extra data in response to one of HUG_Z, HUG_WHEEL, 
 * or HUG_TWHEEL (tilt wheel) [see usbhid.h].
 *
 * The mouse will be initialized to use report protocol, and to deliver
 * reports only when changed data is available.  The mouse is queried
 * to discover the size of interrupt reports, and the report ID if 
 * multiple report IDs are in use.  This is sufficient to configure
 * "conforming" mice without quirks.  Only mice which do not behave
 * according to the HID specification should require "quirks".
 * 
 * Yeah...right...
 *
 * [dhh 02/10/08]
 */

USB_ATTACH(ums)
{
	USB_ATTACH_START(ums, sc, uaa);
	usbd_interface_handle iface = uaa->iface;
	usb_interface_descriptor_t *id;
	usb_endpoint_descriptor_t *ed;
	struct hid_item h;
	int size;
	void *desc;
	usbd_status err;
	char devinfo[1024];
	u_int32_t xflags,yflags,zflags,wflags,tflags;
	int i;
	int item_id = 0;
	int no_report = 0;
	int no_support = 0;
	int id_offset = 0;
	u_int8_t protocol = 0xff;
	usbd_status ustatus;

	sc->sc_disconnected = 1;
	sc->sc_device = uaa->device;
	sc->sc_iface = iface;
	id = usbd_get_interface_descriptor(iface);
	usbd_devinfo(uaa->device, USBD_SHOW_DEVICE_CLASS|USBD_SHOW_INTERFACE_CLASS, devinfo);
	USB_ATTACH_SETUP;
	ed = usbd_interface2endpoint_descriptor(iface, 0);
	if (!ed) {
		printf("%s: could not read endpoint descriptor\n",
		       USBDEVNAME(sc->sc_dev));
		USB_ATTACH_ERROR_RETURN;
	}

	DPRINTFN(10,("%s: attach: bLength=%d bDescriptorType=%d "
		     "bEndpointAddress=%d-%s bmAttributes=%d wMaxPacketSize=%d"
		     " bInterval=%d\n",USBDEVNAME(sc->sc_dev),
		     ed->bLength, ed->bDescriptorType,
		     UE_GET_ADDR(ed->bEndpointAddress),
		     UE_GET_DIR(ed->bEndpointAddress) == UE_DIR_IN ? "in":"out",
		     UE_GET_XFERTYPE(ed->bmAttributes),
		     UGETW(ed->wMaxPacketSize), ed->bInterval));

	if (UE_GET_DIR(ed->bEndpointAddress) != UE_DIR_IN ||
	    UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) {
		printf("%s: unexpected endpoint\n",
		       USBDEVNAME(sc->sc_dev));
		USB_ATTACH_ERROR_RETURN;
	}

	/*
	 * This is for information only.
	 */
	if(usbd_get_protocol(sc->sc_iface, &protocol))
	{
	    DPRINTF(("%s: attach: can't get initial protocol\n",
		    USBDEVNAME(sc->sc_dev)));
	}
	else
	{
	    DPRINTF(("%s: attach: initial protocol = %d\n",
		    USBDEVNAME(sc->sc_dev),protocol));
	}

	/* Initialize the mouse to report protocol and infinite idle rate.
	 * These should be defaults, and usually are... but not always.
	 *
	 * Some devices default to report protocol, some don't, so unilaterally
	 * attempt to set report protocol.  This may fail, especially if the
	 * mouse does not implement SET_PROTOCOL, which is permissible if it
	 * does not implement a boot protocol.  Failure of set_protocol is taken
	 * to imply that the protocol we want is the only one available, and
	 * we just shouldn't worry about it.
	 *
	 * If report protocol can't be set, the 3 byte boot protocol will be
	 * established below.  The output protocol won't change (and will be
	 * selected by the application by setting mode level).  Wheels don't
	 * work in this mode.  This should happen only in the strikingly
	 * unusual case that set_protocol fails but get_protocol succeeds and
	 * claims the protocol is 0 (boot protocol).
	 *
	 * [dhh 02/10/08]
	 */
	if(usbd_set_protocol(sc->sc_iface, 1))
	{
		/*
		 * If it fails, assume the only protocol available is 
		 * report protocol, but check again below.
		 */
		protocol = 1;
		DPRINTF(("%s: attach: could not set input protocol to REPORT\n",
			USBDEVNAME(sc->sc_dev)));
	}
	else
		DPRINTF(("%s: attach: set input protocol to REPORT (1)\n",
			USBDEVNAME(sc->sc_dev)));

	/*
	 * This should verify that the protocol is "report" protocol (1)
	 * or just fail and leave it set that way.  If it succeeds, but
	 * claims the protocol is set to 0, despite our best efforts,
	 * we can do naught but believe it. 
	 */

	if(usbd_get_protocol(sc->sc_iface, &protocol))
	{
		DPRINTF(("%s: attach: couldn't get final input protocol",
			USBDEVNAME(sc->sc_dev)));
		DPRINTF(("...using %d (%s)\n",protocol,protocol ? "REPORT" : "BOOT"));
	}
	else
		DPRINTF(("%s: attach: final input protocol = %d (%s)\n",
			USBDEVNAME(sc->sc_dev),protocol,protocol ? "REPORT" : "BOOT"));

	/*
	 * The hid spec recommends that mice be set to report only upon data
	 * change.  Some folks don't seem to agree.  I have an MS Notebook Optical
	 * Mouse 3000 that reports continuously on ID 0x15, even though its
	 * position reports are on 0x11.  This call attempts to force spec behavior.
	 * (It doesn't work for the 3000). 
	 * [dhh 02/10/08]
	 */

	if((ustatus = usbd_set_idle(sc->sc_iface,0x0,0)))
	{
		printf("%s: attach: usbd_set_idle(interface,0,0) FAILED, status %d\n",
			USBDEVNAME(sc->sc_dev),ustatus);
	}
	else
		printf("%s: attach: usbd_set_idle(interface,0,0) SUCCEEDED, status %d\n",
			USBDEVNAME(sc->sc_dev),ustatus);

	/*
	 * Locate the mouse data items. X and Y axes are expected
	 * (or the mouse is useless, or not a mouse).
	 * W(heel) and T(ilt wheel) may or may not be present.  Wheel data,
	 * if present, will be assigned to the Z axis.  
	 * Up to 8 buttons may be reported by the device and recorded as bits
	 * in the button byte.  
	 * [dhh 02/10/08]
	 */

	err = usbd_read_report_desc(uaa->iface, &desc, &size, M_TEMP);
	DPRINTF(("%s: attach: report descriptor size = %d\n",
			USBDEVNAME(sc->sc_dev),size));
	if (err)
		USB_ATTACH_ERROR_RETURN;

	/*
	 * sc->sc_iid is taken from the hid_item structure, which is filled in by
	 * hid_hlocate().  Each of the following calls should get the same report id.
	 * This allows hid_reportID_size() to compute the correct data report size
	 * when there is more than one report id in the descriptor.
	 * [dhh 02/10/08]
	 */
	if (!hid_hlocate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
		       hid_input, &h)) {
		printf("%s: mouse has no X report\n", USBDEVNAME(sc->sc_dev));
		USB_ATTACH_ERROR_RETURN;
	}
	else
	{
		sc->sc_loc_x = h.loc;
		sc->sc_iid = item_id = h.report_ID;	/* The report ID */
		xflags = h.flags;

		if ((xflags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
			printf("%s: non-relative X report 0x%04x not supported\n",
			       USBDEVNAME(sc->sc_dev), xflags);
			USB_ATTACH_ERROR_RETURN;
		}
		DPRINTF(("%s: attach: X (flags 0x%02x) item_id=%d, sc_loc_x.pos=%d/%d\n",
			USBDEVNAME(sc->sc_dev),xflags,item_id,
			sc->sc_loc_x.pos,sc->sc_loc_x.size));
	}

	if (!hid_hlocate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
		       hid_input, &h)) {
		printf("%s: mouse has no Y report\n", USBDEVNAME(sc->sc_dev));
		USB_ATTACH_ERROR_RETURN;
	}
	else
	{
		sc->sc_loc_y = h.loc;
		yflags = h.flags;
		if(item_id != h.report_ID)	/* all item_ids should be the same */
		{
			DPRINTF(("%s: attach: WARNING: report ID changed for Y\n",
			       USBDEVNAME(sc->sc_dev)));
			item_id = h.report_ID;
		}

		if ((yflags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
			printf("%s: non-relative Y report 0x%04x not supported\n",
			       USBDEVNAME(sc->sc_dev), yflags);
			USB_ATTACH_ERROR_RETURN;
		}
		DPRINTF(("%s: attach: Y (flags 0x%02x) item_id=%d, sc_loc_y.pos=%d/%d\n",
			USBDEVNAME(sc->sc_dev),yflags,item_id,
			sc->sc_loc_y.pos,sc->sc_loc_y.size));
	}

	if (!hid_hlocate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z),
		       hid_input, &h)) {
		no_report |= UMS_Z;
	}
	else
	{
		sc->sc_loc_z = h.loc;
		zflags = h.flags;
		if(item_id != h.report_ID)	/* all item_ids should be the same */
		{
			DPRINTF(("%s: attach: WARNING: report ID changed for Z\n",
			       USBDEVNAME(sc->sc_dev)));
			item_id = h.report_ID;
		}
		if ((zflags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
			no_support |= UMS_Z;
		}
		DPRINTF(("%s: attach: Z (flags 0x%02x) item_id=%d, sc_loc_z.pos=[0x%x]%d/%d\n",
			USBDEVNAME(sc->sc_dev),zflags,item_id,
			sc->sc_loc_z.pos,sc->sc_loc_z.pos,sc->sc_loc_z.size));
	}

	if (!hid_hlocate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_WHEEL),
		       hid_input, &h))
	{
		no_report |= UMS_W;
	}
	else
	{
		sc->sc_loc_w = h.loc;
		wflags = h.flags;
		if(item_id != h.report_ID)	/* all item_ids should be the same */
		{
			DPRINTF(("%s: attach: WARNING: report ID changed for W\n",
			       USBDEVNAME(sc->sc_dev)));
			item_id = h.report_ID;
		}
		if ((wflags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
			no_support |= UMS_W;
		}
		DPRINTF(("%s: attach: W (flags 0x%02x) item_id=%d, sc_loc_w.pos=%d/%d\n",
			USBDEVNAME(sc->sc_dev),wflags,item_id,
			sc->sc_loc_w.pos,sc->sc_loc_w.size));
	}

	if (!hid_hlocate(desc, size, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_TWHEEL),
		       hid_input, &h)) {
		no_report |= UMS_T;
	}
	else
	{
		sc->sc_loc_t = h.loc;
		tflags = h.flags;
		if(item_id != h.report_ID)	/* all item_ids should be the same */
		{
			DPRINTF(("%s: attach: WARNING: report ID changed for T\n",
			       USBDEVNAME(sc->sc_dev)));
			item_id = h.report_ID;
		}
		if ((tflags & MOUSE_FLAGS_MASK) != MOUSE_FLAGS) {
			no_support |= UMS_T;
		}
		DPRINTF(("%s: attach: T (flags 0x%02x) item_id=%d, sc_loc_t.pos=%d/%d\n",
			USBDEVNAME(sc->sc_dev),tflags,item_id,
			sc->sc_loc_t.pos,sc->sc_loc_t.size));
	}


	/* This driver supports mice which deliver relative data (each reported value
	 * is relative to its last value).  Devices which deliver absolute data are
	 * in the wrong place.  
	 */
	if(no_report & UMS_Z)
		printf("%s: mouse has no Z report\n", USBDEVNAME(sc->sc_dev));
	if(no_support & UMS_Z)
		printf("%s: non-relative Z report not supported by this driver\n",
			USBDEVNAME(sc->sc_dev));
	if(no_report & UMS_T)
		printf("%s: mouse has no T report\n", USBDEVNAME(sc->sc_dev));
	if(no_support & UMS_T)
		printf("%s: non-relative T report not supported by this driver\n",
			USBDEVNAME(sc->sc_dev));
	if(no_report & UMS_W)
		printf("%s: mouse has no W report\n", USBDEVNAME(sc->sc_dev));
	if(no_support & UMS_W)
		printf("%s: non-relative W report not supported by this driver\n",
			USBDEVNAME(sc->sc_dev));

	/* figure out the number of buttons */
	for (i = 1; i <= MAX_BUTTONS; i++)
		if (!hid_hlocate(desc, size, HID_USAGE2(HUP_BUTTON, i),
				hid_input, &h))
			break;
	sc->nbuttons = i - 1;
	sc->sc_loc_btn = malloc(sizeof(struct hid_location)*sc->nbuttons,
				M_USBDEV, M_NOWAIT);
	if (!sc->sc_loc_btn) {
		printf("%s: no memory\n", USBDEVNAME(sc->sc_dev));
		USB_ATTACH_ERROR_RETURN;
	}

	/* Locate all of the button bits the mouse claims */
	for (i = 1; i <= sc->nbuttons; i++)
	{
		hid_hlocate(desc, size, HID_USAGE2(HUP_BUTTON, i),
				hid_input, &h);
		sc->sc_loc_btn[i-1] = h.loc;
	}

	/* get the report size for the id filled in by hid_hlocate() */
	sc->sc_isize = hid_reportID_size(desc, size, hid_input, sc->sc_iid);
	printf("%s: input report ID=%d/0x%x, report size %d.\n", 
		USBDEVNAME(sc->sc_dev), sc->sc_iid,sc->sc_iid,sc->sc_isize);
	sc->sc_ibuf = malloc(sc->sc_isize, M_USB, M_NOWAIT);
	if (!sc->sc_ibuf) {
		printf("%s: no memory\n", USBDEVNAME(sc->sc_dev));
		free(sc->sc_loc_btn, M_USB);
		USB_ATTACH_ERROR_RETURN;
	}

	/*
	 * If multiple reports are in the descriptor, the bit positions of data
	 * items within the input report may be computed by simply subtracting
	 * the starting position of the identified report ID from the starting
	 * position of the input report generated from the descriptor string.
	 * The first mouse button is always the start of the report id within the
	 * descriptor, but data reports with non-zero IDs are always preceded by
	 * an id byte, so the button byte will start at bit 8.
	 *
	 * If the report id is 0, there is only one input data report, with no
	 * report ID and with no leading id byte in the report data.  In that
	 * case, position data is correct as reported.
	 *
	 * [dhh 02/10/08]
	 */

	if(sc->sc_iid)
	{
		/* This should be correct even if our iid is first in the
		 * list, because then we need to *add* 8 to account for the
		 * report ID.  (Subtracting -8 is just as good).
		 */
		id_offset = sc->sc_loc_btn[0].pos - 8;
DPRINTF(("%s: attach: id_offset == %d\n",USBDEVNAME(sc->sc_dev),id_offset));
		sc->sc_loc_x.pos -= id_offset;
		sc->sc_loc_y.pos -= id_offset;
		if(sc->sc_loc_z.pos > id_offset)
			sc->sc_loc_z.pos -= id_offset;
		if(sc->sc_loc_t.pos > id_offset)
			sc->sc_loc_t.pos -= id_offset;
		if(sc->sc_loc_w.pos > id_offset)
			sc->sc_loc_w.pos -= id_offset;
		for(i = 0; i < sc->nbuttons; ++i)
			if(sc->sc_loc_btn[i].pos > id_offset)
				sc->sc_loc_btn[i].pos -= id_offset;

		for(i = 0; i < sc->nbuttons; ++i)
			DPRINTF(("%s: attach: fixed B%d ==> %2d/%d\n",
				USBDEVNAME(sc->sc_dev), i,
				sc->sc_loc_btn[i].pos, sc->sc_loc_btn[i].size));
DPRINTF(("%s: attach: fixed X  ==> %2d/%d\n",
	USBDEVNAME(sc->sc_dev), sc->sc_loc_x.pos, sc->sc_loc_x.size));
DPRINTF(("%s: attach: fixed Y  ==> %2d/%d\n",
	USBDEVNAME(sc->sc_dev), sc->sc_loc_y.pos, sc->sc_loc_y.size));
DPRINTF(("%s: attach: fixed Z  ==> %2d/%d\n",
	USBDEVNAME(sc->sc_dev), sc->sc_loc_z.pos, sc->sc_loc_z.size));
DPRINTF(("%s: attach: fixed T  ==> %2d/%d\n",
	USBDEVNAME(sc->sc_dev), sc->sc_loc_t.pos, sc->sc_loc_t.size));
DPRINTF(("%s: attach: fixed W  ==> %2d/%d\n",
	USBDEVNAME(sc->sc_dev), sc->sc_loc_w.pos, sc->sc_loc_w.size));
	}

	/* 
	 * Wheel and tilt wheel data, if present, should be written as
	 * Z axis data.  If there is tilt data (the mouse responded
	 * on HUG_TWHEEL rather than HUG_WHEEL) the tilt information
	 * should appear on the next byte after Z.
	 *
	 * [dhh 02/10/08]
	 */

	if(sc->sc_loc_z.size == 0)
	{
		/*
		 * This assumes there will be W OR T, not both.
		 */
		if(sc->sc_loc_w.size)
		{
			/* Move the wheel data to the Z output axis */
	DPRINTF(("%s: attach: W.pos => Z.pos\n",USBDEVNAME(sc->sc_dev)));
			sc->sc_loc_z.pos = sc->sc_loc_w.pos;
			sc->sc_loc_z.size = sc->sc_loc_w.size;
			sc->flags = UMS_Z|UMS_W;
			sc->sc_loc_w.pos = sc->sc_loc_w.size = 0;
		}
		else if(sc->sc_loc_t.size)
		{
			/* Move the wheel data to the Z output axis */
	DPRINTF(("%s: attach: T.pos => Z.pos\n",USBDEVNAME(sc->sc_dev)));
			sc->sc_loc_z.pos = sc->sc_loc_t.pos;
			sc->sc_loc_z.size = sc->sc_loc_t.size;
			sc->sc_loc_w.pos = sc->sc_loc_w.size = 0;
			sc->flags = UMS_Z|UMS_T;
			sc->sc_loc_t.pos += 8;
	DPRINTF(("%s: attach: T.pos = Z.pos + 8, sc_isize=%d\n",
		USBDEVNAME(sc->sc_dev),sc->sc_isize));
		}
	}
	else
	{
		/* Reports are not expected on the Z *and* wheel usages.
		 * Note the wheel usages and report them, leaving Z alone.
		 * If this happens, someone should be told...
		 */
		if(sc->sc_loc_w.size)
		{
			printf("%s: attach: found Z *and* W data...",
				USBDEVNAME(sc->sc_dev));
			printf("using Z data only\n");
			sc->sc_loc_w.pos =sc->sc_loc_w.size = 0;
		}
		if(sc->sc_loc_t.size)
		{
			printf("%s: attach: found Z *and* T data...",
				USBDEVNAME(sc->sc_dev));
			printf("using Z data only\n");
			sc->sc_loc_t.pos =sc->sc_loc_t.size = 0;
		}
		sc->flags = UMS_Z;
	}

	/*
	 * Now check for quirks.
	 */
	if (usbd_get_quirks(uaa->device)->uq_flags & UQ_MS_REVZ) {
		/* Some wheels need the Z axis reversed. */
		if(sc->flags & UMS_Z)
		{
			sc->flags |= UMS_REVZ;
			DPRINTF(("attach: add UMS_REVZ to sc->flags for wheel -> 0x%x\n",sc->flags));
		}
	}
	if (usbd_get_quirks(uaa->device)->uq_flags & UQ_SPUR_BUT_UP) {
		DPRINTF(("%s: Spurious button up events\n",
			USBDEVNAME(sc->sc_dev)));
		sc->flags |= UMS_SPUR_BUT_UP;
	}

	/*
	 * If get_protocol claimed protocol 0, and it is true, then the exploration
	 * above should have found the truth in the report descriptor and the report
	 * size should be 3.  If it's greater than 3, then get_protocol() lied and it
	 * seems best to believe the report descriptor.  If size is less than 3 in
	 * in the report descriptor, we can try assuming that the protocol really is
	 * BOOT, but in fact this is a "firewall" which is not likely to be taken,
	 * or to succeed if it is taken.  It may be useful for a non-conforming mouse.
	 * [dhh 02/10/08]
	 */
	if((protocol == 0) && (sc->sc_isize < 3))
	{
		DPRINTF(("Configuring BOOT input protocol\n"));
		sc->flags = 0;
		sc->sc_loc_z.pos = sc->sc_loc_t.pos = sc->sc_loc_w.pos =  0;
		sc->sc_loc_z.size = sc->sc_loc_t.size = sc->sc_loc_w.size =  0;
		sc->sc_isize = 3;
		sc->sc_loc_x.pos = 8;
		sc->sc_loc_y.pos = 16;
		sc->sc_loc_btn[0].pos = 0;
		sc->sc_loc_btn[1].pos = 1;
		sc->sc_loc_btn[2].pos = 2;
		sc->sc_loc_btn[0].size = 1;
		sc->sc_loc_btn[1].size = 1;
		sc->sc_loc_btn[2].size = 1;
		sc->nbuttons = 3;
		sc->sc_iid = 0;
	}

	printf("%s: %d buttons%s%s%s%s.\n", USBDEVNAME(sc->sc_dev),
	       sc->nbuttons, sc->flags & UMS_Z? " and Z dir" : " and X,Y only", 
	       sc->flags & UMS_REVZ? " (reversed)" : "",
	       sc->flags & UMS_W? " (wheel)" : "",
	       sc->flags & UMS_T?" (TILT wheel)": "");


	sc->sc_ep_addr = ed->bEndpointAddress;
	sc->sc_disconnected = 0;
	sc->sc_repdesc = desc;
	sc->sc_repdesc_size = size;

#ifdef USB_DEBUG
	DPRINTF(("%s: ATTACH: sc=%p\n",USBDEVNAME(sc->sc_dev), sc));
	if(sc->sc_iid)
		DPRINTF(("%s: ATTACH: ID  0/8\n",USBDEVNAME(sc->sc_dev)));
	for (i = 1; i <= sc->nbuttons; i++) {
		DPRINTF(("%s: ATTACH: B%d %2d/%d\n",USBDEVNAME(sc->sc_dev),
			 i, sc->sc_loc_btn[i-1].pos,sc->sc_loc_btn[i-1].size));
	}
	DPRINTF(("%s: ATTACH: X  %2d/%d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_loc_x.pos, sc->sc_loc_x.size));
	DPRINTF(("%s: ATTACH: Y  %2d/%d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_loc_y.pos, sc->sc_loc_y.size));
	DPRINTF(("%s: ATTACH: Z  %2d/%d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_loc_z.pos, sc->sc_loc_z.size));
	DPRINTF(("%s: ATTACH: T  %2d/%d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_loc_t.pos, sc->sc_loc_t.size));
	DPRINTF(("%s: ATTACH: W  %2d/%d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_loc_w.pos, sc->sc_loc_w.size));
	DPRINTF(("%s: ATTACH: isize = %d, iid = %d\n",USBDEVNAME(sc->sc_dev),
			sc->sc_isize, sc->sc_iid));
#endif

	/* Set up the output protocol.  Defaults to sysmouse level 0,
	 * which uses the 5-byte MSC protocol, and is incapable of
	 * presenting Z or tilt data to the display driver.
	 * The application may override this by issuing an ioctl to
	 * set the level to 1, which selects the "sysmouse" protocol,
	 * which can handle wheel data, or 3, which establishes an
	 * "extended" sysmous protocol with room for tilt data as well.
	 *
	 * [dhh 02/10/08]
	 */
	if (sc->nbuttons > MOUSE_MSC_MAXBUTTON)
		sc->hw.buttons = MOUSE_MSC_MAXBUTTON;
	else
		sc->hw.buttons = sc->nbuttons;
	sc->hw.iftype = MOUSE_IF_USB;
	sc->hw.type = MOUSE_MOUSE;
	sc->hw.model = MOUSE_MODEL_GENERIC;
	sc->hw.hwid = 0;
	sc->mode.protocol = MOUSE_PROTO_MSC;
	sc->mode.rate = -1;
	sc->mode.resolution = MOUSE_RES_UNKNOWN;
	sc->mode.accelfactor = 0;
	sc->mode.level = 0;
	sc->mode.packetsize = MOUSE_MSC_PACKETSIZE;
	sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
	sc->mode.syncmask[1] = MOUSE_MSC_SYNC;

	sc->status.flags = 0;
	sc->status.button = sc->status.obutton = 0;
	sc->status.dx = sc->status.dy = sc->status.dz = 0;
	sc->status.dt = 0;

#ifndef __FreeBSD__
	sc->rsel.si_flags = 0;
	sc->rsel.si_pid = 0;
#endif

	sc->dev = make_dev(&ums_cdevsw, device_get_unit(self),
			UID_ROOT, GID_OPERATOR,
			0644, "ums%d", device_get_unit(self));

	usb_callout_init(sc->callout_handle);

	USB_ATTACH_SUCCESS_RETURN;
}


Static int
ums_detach(device_t self)
{
	struct ums_softc *sc = device_get_softc(self);

	if (sc->sc_enabled)
		ums_disable(sc);

	DPRINTFN(2,("%s: disconnected\n", USBDEVNAME(self)));

	free(sc->sc_loc_btn, M_USB);
	free(sc->sc_ibuf, M_USB);

	/* someone waiting for data */
	/*
	 * XXX If we wakeup the process here, the device will be gone by
	 * the time the process gets a chance to notice. *_close and friends
	 * should be fixed to handle this case.
	 * Or we should do a delayed detach for this.
	 * Does this delay now force tsleep to exit with an error?
	 */
	if (sc->state & UMS_ASLEEP) {
		sc->state &= ~UMS_ASLEEP;
		wakeup(sc);
	}
	if (sc->state & UMS_SELECT) {
		sc->state &= ~UMS_SELECT;
		selwakeuppri(&sc->rsel, PZERO);
	}

	destroy_dev(sc->dev);

	if (sc->sc_repdesc)
		free(sc->sc_repdesc, M_USBDEV);

	return 0;
}

void
ums_intr(usbd_xfer_handle xfer, usbd_private_handle addr, usbd_status status)
{
	struct ums_softc *sc = addr;
	u_char *ibuf;
	int dx, dy, dz, dt;
	int buttons = 0;
	int i;

#define UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i))

	DPRINTFN(2, ("%s: ums_intr: sc=%p id=%d status=%d\n",
			USBDEVNAME(sc->sc_dev), sc, sc->sc_iid, status));
	DPRINTFN(2, ("%s: ums_intr: data =",USBDEVNAME(sc->sc_dev)));
	for (i = 0; i < sc->sc_isize; i++)
		DPRINTFN(2, (" 0x%02x", sc->sc_ibuf[i]));
	DPRINTFN(2,("    sc_iid = %d/0x%x",sc->sc_iid,sc->sc_iid));

	if (status == USBD_CANCELLED)
	{
		DPRINTFN(3, (" CANCELLED\n"));
		return;
	}

	if (status != USBD_NORMAL_COMPLETION) {
		DPRINTFN(3, ("\n"));
		if (status == USBD_STALLED)
		    usbd_clear_endpoint_stall_async(sc->sc_intrpipe);
		if(status != USBD_IOERROR)
			return;
	}

	ibuf = sc->sc_ibuf;

	/* A leading byte giving report_ID is present for devices which have 
	 * multiple report ids (most Microsoft mice, e.g.).  If the
	 * leading byte does not match sc_iid, the data is for some other
	 * report and should be ignored.  Mice which have just a single
	 * report (Logitech...) have no report_ID and the data is always
	 * accepted.
	 *
	 * [dhh 02/10/08]
	 */

	if ((sc->sc_iid) && (*ibuf != sc->sc_iid))
	{
		/*
		 * The Microsoft Wireless Notebook Optical Mouse 3000
		 * (model 1056) fails to respond to set_idle(interface,0,0)
		 * and interrupts continuously at the polling rate with
		 * reports for ID 0x15 (21).  It can be ignored here
		 * (0x11 is the ID which carries the data we want).
		 *
		 * [dhh 02/10/08]
		 */
		DPRINTFN(3,(" IGNORE iid %d/0x%x, sc=%p\n", *ibuf, *ibuf, sc));
		return;
	}
	DPRINTFN(3, ("\n"));

	dx =  hid_get_data(ibuf, &sc->sc_loc_x);
	dy = -hid_get_data(ibuf, &sc->sc_loc_y);
	if(sc->flags & UMS_Z)
		dz = -hid_get_data(ibuf, &sc->sc_loc_z);
	else
		dz = 0;
	if (sc->flags & UMS_REVZ)
		dz = -dz;

	/*
	 * tilt data shows up here as +/- 1, separate from the buttons
	 */
	if (sc->flags & UMS_T)
		dt = -hid_get_data(ibuf, &sc->sc_loc_t);
	else
		dt = 0;
	for (i = 0; i < sc->nbuttons; i++)
		if (hid_get_data(ibuf, &sc->sc_loc_btn[i]))
			buttons |= (1 << UMS_BUT(i));

	if (dx || dy || dz || dt || (sc->flags & UMS_Z)
	    || buttons != sc->status.button) {
		DPRINTFN(2, ("%s: ums_intr: x:%d y:%d z:%d t:%d buttons:0x%x\n",
			USBDEVNAME(sc->sc_dev), dx, dy, dz, dt, buttons));

		sc->status.button = buttons;
		sc->status.dx += dx;
		sc->status.dy += dy;
		sc->status.dz += dz;
		sc->status.dt += dt;
		
		/* Discard data in case of full buffer */
		if (sc->qcount == sizeof(sc->qbuf)) {
			DPRINTF(("%s: ums_intr: buffer full, discarded packet, sc=%p\n",
				USBDEVNAME(sc->sc_dev),sc));
			return;
		}

		/*
		 * The Qtronix keyboard has a built in PS/2 port for a mouse.
		 * The firmware once in a while posts a spurious button up
		 * event. This event we ignore by doing a timeout for 50 msecs.
		 * If we receive dx=dy=dz=buttons=0 before we add the event to
		 * the queue.
		 * In any other case we delete the timeout event.
		 */
		if (sc->flags & UMS_SPUR_BUT_UP &&
		    dx == 0 && dy == 0 && dz == 0 && dt == 0 && buttons == 0) {
			usb_callout(sc->callout_handle, MS_TO_TICKS(50 /*msecs*/),
				    ums_add_to_queue_timeout, (void *) sc);
		} else {
			usb_uncallout(sc->callout_handle,
				      ums_add_to_queue_timeout, (void *) sc);
			ums_add_to_queue(sc, dx, dy, dz, dt, buttons);
		}
	}
}

Static void
ums_add_to_queue_timeout(void *priv)
{
	struct ums_softc *sc = priv;
	int s;

	s = splusb();
	ums_add_to_queue(sc, 0, 0, 0, 0, 0);
	splx(s);
}

Static void
ums_add_to_queue(struct ums_softc *sc, int dx, int dy, int dz, int dt, int buttons)
{
	/* Discard data in case of full buffer */
	if (sc->qhead+sc->mode.packetsize > sizeof(sc->qbuf)) {
		DPRINTF(("ums_add_to_queue: buffer full, discarded packet\n"));
		return;
	}

	if (dx >  254)		dx =  254;
	if (dx < -256)		dx = -256;
	if (dy >  254)		dy =  254;
	if (dy < -256)		dy = -256;
	if (dz >  126)		dz =  126;
	if (dz < -128)		dz = -128;
	if (dt >  126)		dt =  126;
        if (dt < -128)		dt = -128;

	/* This is the sysmouse protocol and is unable to send
	 * tilt data, even at level 1, because it needs 2 bytes
	 * for each axis, and 2 for buttons.
	 */

	sc->qbuf[sc->qhead] = sc->mode.syncmask[1];
	sc->qbuf[sc->qhead] |= ~buttons & MOUSE_MSC_BUTTONS;
	sc->qbuf[sc->qhead+1] = dx >> 1;
	sc->qbuf[sc->qhead+2] = dy >> 1;
	sc->qbuf[sc->qhead+3] = dx - (dx >> 1);
	sc->qbuf[sc->qhead+4] = dy - (dy >> 1);

	DPRINTFN(2,(" %02x %02x %02x %02x %02x",
			sc->qbuf[sc->qhead],
			sc->qbuf[sc->qhead+1],
			sc->qbuf[sc->qhead+2],
			sc->qbuf[sc->qhead+3],
			sc->qbuf[sc->qhead+4]));
	/* level 2 is "native" mode in the PS/2 driver; ignore level 2 */
	if ((sc->mode.level == 1) || (sc->mode.level == 3)) {
		sc->qbuf[sc->qhead+5] = dz >> 1;
		sc->qbuf[sc->qhead+6] = dz - (dz >> 1);
		sc->qbuf[sc->qhead+7] = ((~buttons >> 3)
					 & MOUSE_SYS_EXTBUTTONS);
		DPRINTFN(2,(" %02x %02x %02x",
				sc->qbuf[sc->qhead+5],
				sc->qbuf[sc->qhead+6],
				sc->qbuf[sc->qhead+7]));
		if (sc->mode.level == 3) {
			sc->qbuf[sc->qhead+8] = dt >> 1;
			sc->qbuf[sc->qhead+9] = dt - (dt >> 1);
			sc->qbuf[sc->qhead+10] = 0;
			sc->qbuf[sc->qhead+11] = 0;
			DPRINTFN(2,(" %02x %02x %02x %02x",
					sc->qbuf[sc->qhead+8],
					sc->qbuf[sc->qhead+9],
					sc->qbuf[sc->qhead+10],
					sc->qbuf[sc->qhead+11]));
		}
	}
	DPRINTFN(2,("\n"));

	sc->qhead += sc->mode.packetsize;
	sc->qcount += sc->mode.packetsize;
	/* wrap round at end of buffer */
	if (sc->qhead >= sizeof(sc->qbuf))
		sc->qhead = 0;

	/* someone waiting for data */
	if (sc->state & UMS_ASLEEP) {
		sc->state &= ~UMS_ASLEEP;
		wakeup(sc);
	}
	if (sc->state & UMS_SELECT) {
		sc->state &= ~UMS_SELECT;
		selwakeuppri(&sc->rsel, PZERO);
	}
}

Static int
ums_enable(v)
	void *v;
{
	struct ums_softc *sc = v;

	usbd_status err;

	DPRINTFN(2,("%s: enable\n", USBDEVNAME(sc->sc_dev)));
	if (sc->sc_enabled)
		return EBUSY;

	sc->sc_enabled = 1;
	sc->qcount = 0;
	sc->qhead = sc->qtail = 0;
	sc->status.flags = 0;
	sc->status.button = sc->status.obutton = 0;
	sc->status.dx = sc->status.dy = sc->status.dz = 0;
	sc->status.dt = 0;

	callout_handle_init((struct callout_handle *)&sc->callout_handle);

	/* Set up interrupt pipe. */
	err = usbd_open_pipe_intr(sc->sc_iface, sc->sc_ep_addr,
				USBD_SHORT_XFER_OK, &sc->sc_intrpipe, sc,
				sc->sc_ibuf, sc->sc_isize, ums_intr,
				USBD_DEFAULT_INTERVAL);
	if (err) {
		DPRINTF(("ums_enable: usbd_open_pipe_intr failed, error=%d\n",
			 err));
		sc->sc_enabled = 0;
		return (EIO);
	}
	return (0);
}

Static void
ums_disable(priv)
	void *priv;
{
	struct ums_softc *sc = priv;

	usb_uncallout(sc->callout_handle, ums_add_to_queue_timeout, sc);

	/* Disable interrupts. */
	usbd_abort_pipe(sc->sc_intrpipe);
	usbd_close_pipe(sc->sc_intrpipe);

	sc->sc_enabled = 0;
	DPRINTFN(2,("%s: disabled\n", USBDEVNAME(sc->sc_dev)));

	if (sc->qcount != 0)
		DPRINTF(("ums_disable Discarded %d bytes in queue for %p\n", sc->qcount,sc));
}

Static int
ums_open(struct cdev *dev, int flag, int fmt, usb_proc_ptr p)
{
	struct ums_softc *sc;
	usbd_status ustatus;

	USB_GET_SC_OPEN(ums, UMSUNIT(dev), sc);
	if (sc == NULL)
		return (ENXIO);
	DPRINTFN(2,("%s: open\n", USBDEVNAME(sc->sc_dev)));
	if((ustatus = usbd_set_idle(sc->sc_iface,0,0)))
	{
		printf("%s: usbd_set_idle(interface,0,0) FAILED, status %d\n",
			USBDEVNAME(sc->sc_dev),ustatus);
	}

	return ums_enable(sc);
}

Static int
ums_close(struct cdev *dev, int flag, int fmt, usb_proc_ptr p)
{
	struct ums_softc *sc;

	USB_GET_SC(ums, UMSUNIT(dev), sc);

	if (!sc)
		return 0;
	DPRINTFN(2,("%s: close\n", USBDEVNAME(sc->sc_dev)));

	if (sc->sc_enabled)
		ums_disable(sc);

	return 0;
}

Static int
ums_read(struct cdev *dev, struct uio *uio, int flag)
{
	struct ums_softc *sc;
	int s;
	char buf[sizeof(sc->qbuf)];
	int l = 0;
	int error;

	USB_GET_SC(ums, UMSUNIT(dev), sc);

	s = splusb();
	if (!sc) {
		splx(s);
		return EIO;
	}

	while (sc->qcount == 0 )  {
		if (flag & O_NONBLOCK) {		/* non-blocking I/O */
			splx(s);
			return EWOULDBLOCK;
		}

		sc->state |= UMS_ASLEEP;	/* blocking I/O */
		error = tsleep(sc, PZERO | PCATCH, "umsrea", 0);
		if (error) {
			splx(s);
			return error;
		} else if (!sc->sc_enabled) {
			splx(s);
			return EINTR;
		}
		/* check whether the device is still there */

		sc = devclass_get_softc(ums_devclass, UMSUNIT(dev));
		if (!sc) {
			splx(s);
			return EIO;
		}
	}

	/*
	 * XXX we could optimise the use of splx/splusb somewhat. The writer
	 * process only extends qcount and qtail. We could copy them and use the copies
	 * to do the copying out of the queue.
	 */

	while ((sc->qcount > 0) && (uio->uio_resid > 0)) {
		l = (sc->qcount < uio->uio_resid? sc->qcount:uio->uio_resid);
		if (l > sizeof(buf))
			l = sizeof(buf);
		if (l > sizeof(sc->qbuf) - sc->qtail)		/* transfer till end of buf */
			l = sizeof(sc->qbuf) - sc->qtail;

		splx(s);
		uiomove(&sc->qbuf[sc->qtail], l, uio);
		s = splusb();

		if ( sc->qcount - l < 0 ) {
			DPRINTF(("qcount below 0, count=%d l=%d\n", sc->qcount, l));
			sc->qcount = l;
		}
		sc->qcount -= l;	/* remove the bytes from the buffer */
		sc->qtail = (sc->qtail + l) % sizeof(sc->qbuf);
	}
	splx(s);

	return 0;
}

Static int
ums_poll(struct cdev *dev, int events, usb_proc_ptr p)
{
	struct ums_softc *sc;
	int revents = 0;
	int s;

	USB_GET_SC(ums, UMSUNIT(dev), sc);

	if (!sc)
		return 0;

	s = splusb();
	if (events & (POLLIN | POLLRDNORM)) {
		if (sc->qcount) {
			revents = events & (POLLIN | POLLRDNORM);
		} else {
			sc->state |= UMS_SELECT;
			selrecord(p, &sc->rsel);
		}
	}
	splx(s);

	return revents;
}

/* This function now sports several additional ioctl calls (similar to those
 * supplied by the ugen driver) which allows user level programs read access
 * to information.  It is likely that other ioctls could be useful.
 *
 * Enquiring minds want to know...
 *
 * [dhh 02/10/08]  see "mouse_report"
 */

int
ums_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, usb_proc_ptr p)
{
	struct ums_softc *sc;
	struct usb_ctl_report_desc *rd;
	usb_interface_descriptor_t *iface_desc;
	struct usb_string_desc *string_desc;
	usbd_status err;
	int error = 0;
	int size;
	int s;
	mousemode_t mode;

	USB_GET_SC(ums, UMSUNIT(dev), sc);

	if (!sc)
		return EIO;

	switch(cmd) {
	case MOUSE_GETHWINFO:
		*(mousehw_t *)addr = sc->hw;
		DPRINTFN(1,("ums_ioctl: MOUSE_GETHWINFO\n"));
		break;
	case MOUSE_GETMODE:
		*(mousemode_t *)addr = sc->mode;
		DPRINTFN(1,("ums_ioctl: MOUSE_GETMODE: mode.level is %d for sc=%p\n",sc->mode.level,sc));
		break;
	case MOUSE_SETMODE:
		mode = *(mousemode_t *)addr;

		DPRINTFN(1,("ums_ioctl: MOUSE_SETMODE: set mode.level to %d for sc=%p\n",mode.level,sc));
		/* Level 2 is used by the PS/2 driver for "native mode" */
		if ((mode.level == -1) || (mode.level == 2))
			/* don't change the current setting */
			mode.level = sc->mode.level;
		else if ((mode.level < 0) || (mode.level > 3))
			return (EINVAL);

		s = splusb();
		sc->mode.level = mode.level;

		if (sc->mode.level == 0) {
			if (sc->nbuttons > MOUSE_MSC_MAXBUTTON)
				sc->hw.buttons = MOUSE_MSC_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_MSC;
			sc->mode.packetsize = MOUSE_MSC_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_MSC_SYNC;
		} else if (sc->mode.level == 1) {
			if (sc->nbuttons > MOUSE_SYS_MAXBUTTON)
				sc->hw.buttons = MOUSE_SYS_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_SYSMOUSE;
			sc->mode.packetsize = MOUSE_SYS_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_SYS_SYNC;
		} else if (sc->mode.level == 3) {
			if (sc->nbuttons > MOUSE_SYS_MAXBUTTON)
				sc->hw.buttons = MOUSE_SYS_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_SYSMOUSE_EXT;
			sc->mode.packetsize = MOUSE_SYS_EXT_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_SYS_SYNC;
		}
		DPRINTFN(1,("ums_ioctl: MOUSE_SETMODE: mode.level set to %d for sc=%p\n",sc->mode.level,sc));

		bzero(sc->qbuf, sizeof(sc->qbuf));
		sc->qhead = sc->qtail = sc->qcount = 0;
		splx(s);

		break;
	case MOUSE_GETLEVEL:
		*(int *)addr = sc->mode.level;
		DPRINTFN(1,("ums_ioctl: MOUSE_GETLEVEL: mode.level is %d for sc=%p\n",sc->mode.level,sc));
		break;
	case MOUSE_SETLEVEL:
		DPRINTFN(1,("ums_ioctl: MOUSE_SETLEVEL: set mode.level to %d for sc=%p\n",sc->mode.level,sc));
		if (*(int *)addr < 0 || *(int *)addr > 3)
			return (EINVAL);

		s = splusb();
		sc->mode.level = *(int *)addr;

		if (sc->mode.level == 0) {
			if (sc->nbuttons > MOUSE_MSC_MAXBUTTON)
				sc->hw.buttons = MOUSE_MSC_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_MSC;
			sc->mode.packetsize = MOUSE_MSC_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_MSC_SYNC;
		} else if (sc->mode.level == 1) {
			if (sc->nbuttons > MOUSE_SYS_MAXBUTTON)
				sc->hw.buttons = MOUSE_SYS_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_SYSMOUSE;
			sc->mode.packetsize = MOUSE_SYS_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_SYS_SYNC;
		} else if (sc->mode.level == 3) {
			if (sc->nbuttons > MOUSE_SYS_MAXBUTTON)
				sc->hw.buttons = MOUSE_SYS_MAXBUTTON;
			else
				sc->hw.buttons = sc->nbuttons;
			sc->mode.protocol = MOUSE_PROTO_SYSMOUSE_EXT;
			sc->mode.packetsize = MOUSE_SYS_EXT_PACKETSIZE;
			sc->mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
			sc->mode.syncmask[1] = MOUSE_SYS_SYNC;
		}
		DPRINTFN(1,("ums_ioctl: MOUSE_SETLEVEL: mode.level set to %d for sc=%p\n",sc->mode.level,sc));

		bzero(sc->qbuf, sizeof(sc->qbuf));
		sc->qhead = sc->qtail = sc->qcount = 0;
		splx(s);

		break;
	case MOUSE_GETSTATUS: {
		mousestatus_t *status = (mousestatus_t *) addr;

		s = splusb();
		*status = sc->status;
		sc->status.obutton = sc->status.button;
		sc->status.button = 0;
		sc->status.dx = sc->status.dy = sc->status.dz = 0;
		sc->status.dt = 0;
		splx(s);

		if (status->dx || status->dy || status->dz || status->dt)
			status->flags |= MOUSE_POSCHANGED;
		if (status->button != status->obutton)
			status->flags |= MOUSE_BUTTONSCHANGED;
		break;
		}
	case USB_GET_DEVICEINFO:
		usbd_fill_deviceinfo(sc->sc_device,
				    (struct usb_device_info *)addr, 1);
		break;
	case USB_GET_DEVICE_DESC:
		*(usb_device_descriptor_t *)addr =
			*usbd_get_device_descriptor(sc->sc_device);
		break;
	case USB_GET_INTERFACE_DESC:
		iface_desc = usbd_get_interface_descriptor(sc->sc_iface);
		if(iface_desc)
			*(usb_interface_descriptor_t *)addr = *iface_desc;
		break;
	case USB_GET_REPORT_DESC:
		rd = (struct usb_ctl_report_desc *)addr;
		size = min(sc->sc_repdesc_size, sizeof rd->ucrd_data);
		rd->ucrd_size = size;
		/* The report descriptor is saved at attach */
		memcpy(rd->ucrd_data, sc->sc_repdesc, size);
		break;
	case USB_GET_STRING_DESC: {
		int len;
		string_desc = (struct usb_string_desc *)addr;
		if (usbd_get_quirks(sc->sc_device)->uq_flags & UQ_NO_STRINGS)
			return (USBD_STALLED);
		if (sc->sc_device->langid == -1)
		{
			/* Set up default language */
			err = usbd_get_string_desc(sc->sc_device, USB_LANGUAGE_TABLE, 0,
							&string_desc->usd_desc,
							&size);
			if (err || size < 4) {
				DPRINTFN(-1,("usbd_get_string: getting lang failed, using 0\n"));
				sc->sc_device->langid = 0; /* Well, just pick something then */
			} else {
				/* Pick the first language as the default. */
				sc->sc_device->langid = UGETW(string_desc->usd_desc.bString[0]);
			}
		}
		err = usbd_get_string_desc(sc->sc_device, string_desc->usd_string_index,
				 sc->sc_device->langid, &string_desc->usd_desc, &len);
		if (err)
			return (EINVAL);
		}
		break;
	default:
		error = ENOTTY;
	}

	return error;
}

DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, usbd_driver_load, 0);
