[rtems-libbsd commit] Add FREEBSD USB input device files

Sebastian Huber sebh at rtems.org
Fri May 19 07:28:03 UTC 2017


Module:    rtems-libbsd
Branch:    master
Commit:    3e7de301c1924828cb18cfbe47e4736c46cf7d59
Changeset: http://git.rtems.org/rtems-libbsd/commit/?id=3e7de301c1924828cb18cfbe47e4736c46cf7d59

Author:    Kevin Kirspel <kevin-kirspel at idexx.com>
Date:      Wed May 17 08:40:28 2017 -0400

Add FREEBSD USB input device files

---

 freebsd/sys/dev/usb/input/atp.c       | 2636 +++++++++++++++++++++++++++++++++
 freebsd/sys/dev/usb/input/uep.c       |  446 ++++++
 freebsd/sys/dev/usb/input/uhid.c      |  883 +++++++++++
 freebsd/sys/dev/usb/input/ukbd.c      | 2309 +++++++++++++++++++++++++++++
 freebsd/sys/dev/usb/input/ums.c       | 1231 +++++++++++++++
 freebsd/sys/dev/usb/input/usb_rdesc.h |  276 ++++
 freebsd/sys/dev/usb/input/wsp.c       | 1405 ++++++++++++++++++
 freebsd/sys/sys/mouse.h               |  395 +++++
 8 files changed, 9581 insertions(+)

diff --git a/freebsd/sys/dev/usb/input/atp.c b/freebsd/sys/dev/usb/input/atp.c
new file mode 100644
index 0000000..461d4ff
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/atp.c
@@ -0,0 +1,2636 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * Copyright (c) 2014 Rohit Grover
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+/*
+ * Some tables, structures, definitions and constant values for the
+ * touchpad protocol has been copied from Linux's
+ * "drivers/input/mouse/bcm5974.c" which has the following copyright
+ * holders under GPLv2. All device specific code in this driver has
+ * been written from scratch. The decoding algorithm is based on
+ * output from FreeBSD's usbdump.
+ *
+ * Copyright (C) 2008      Henrik Rydberg (rydberg at euromail.se)
+ * Copyright (C) 2008      Scott Shawcroft (scott.shawcroft at gmail.com)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg at kroah.com)
+ * Copyright (C) 2005      Johannes Berg (johannes at sipsolutions.net)
+ * Copyright (C) 2005      Stelian Pop (stelian at popies.net)
+ * Copyright (C) 2005      Frank Arnold (frank at scirocco-5v-turbo.de)
+ * Copyright (C) 2005      Peter Osterlund (petero2 at telia.com)
+ * Copyright (C) 2005      Michael Hanselmann (linux-kernel at hansmi.ch)
+ * Copyright (C) 2006      Nicolas Boichat (nicolas at boichat.ch)
+ */
+
+/*
+ * Author's note: 'atp' supports two distinct families of Apple trackpad
+ * products: the older Fountain/Geyser and the latest Wellspring trackpads.
+ * The first version made its appearance with FreeBSD 8 and worked only with
+ * the Fountain/Geyser hardware. A fork of this driver for Wellspring was
+ * contributed by Huang Wen Hui. This driver unifies the Wellspring effort
+ * and also improves upon the original work.
+ *
+ * I'm grateful to Stephan Scheunig, Angela Naegele, and Nokia IT-support
+ * for helping me with access to hardware. Thanks also go to Nokia for
+ * giving me an opportunity to do this work.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <rtems/bsd/sys/param.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/malloc.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#include <rtems/bsd/local/usbdevs.h>
+
+#define USB_DEBUG_VAR atp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mouse.h>
+
+#define ATP_DRIVER_NAME "atp"
+
+/*
+ * Driver specific options: the following options may be set by
+ * `options' statements in the kernel configuration file.
+ */
+
+/* The divisor used to translate sensor reported positions to mickeys. */
+#ifndef ATP_SCALE_FACTOR
+#define ATP_SCALE_FACTOR                  16
+#endif
+
+/* Threshold for small movement noise (in mickeys) */
+#ifndef ATP_SMALL_MOVEMENT_THRESHOLD
+#define ATP_SMALL_MOVEMENT_THRESHOLD      30
+#endif
+
+/* Threshold of instantaneous deltas beyond which movement is considered fast.*/
+#ifndef ATP_FAST_MOVEMENT_TRESHOLD
+#define ATP_FAST_MOVEMENT_TRESHOLD        150
+#endif
+
+/*
+ * This is the age in microseconds beyond which a touch is considered
+ * to be a slide; and therefore a tap event isn't registered.
+ */
+#ifndef ATP_TOUCH_TIMEOUT
+#define ATP_TOUCH_TIMEOUT                 125000
+#endif
+
+#ifndef ATP_IDLENESS_THRESHOLD
+#define	ATP_IDLENESS_THRESHOLD 10
+#endif
+
+#ifndef FG_SENSOR_NOISE_THRESHOLD
+#define FG_SENSOR_NOISE_THRESHOLD 2
+#endif
+
+/*
+ * A double-tap followed by a single-finger slide is treated as a
+ * special gesture. The driver responds to this gesture by assuming a
+ * virtual button-press for the lifetime of the slide. The following
+ * threshold is the maximum time gap (in microseconds) between the two
+ * tap events preceding the slide for such a gesture.
+ */
+#ifndef ATP_DOUBLE_TAP_N_DRAG_THRESHOLD
+#define ATP_DOUBLE_TAP_N_DRAG_THRESHOLD   200000
+#endif
+
+/*
+ * The wait duration in ticks after losing a touch contact before
+ * zombied strokes are reaped and turned into button events.
+ */
+#define ATP_ZOMBIE_STROKE_REAP_INTERVAL   (hz / 20)	/* 50 ms */
+
+/* The multiplier used to translate sensor reported positions to mickeys. */
+#define FG_SCALE_FACTOR                   380
+
+/*
+ * The movement threshold for a stroke; this is the maximum difference
+ * in position which will be resolved as a continuation of a stroke
+ * component.
+ */
+#define FG_MAX_DELTA_MICKEYS             ((3 * (FG_SCALE_FACTOR)) >> 1)
+
+/* Distance-squared threshold for matching a finger with a known stroke */
+#ifndef WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ
+#define WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ 1000000
+#endif
+
+/* Ignore pressure spans with cumulative press. below this value. */
+#define FG_PSPAN_MIN_CUM_PRESSURE         10
+
+/* Maximum allowed width for pressure-spans.*/
+#define FG_PSPAN_MAX_WIDTH                4
+
+/* end of driver specific options */
+
+/* Tunables */
+static SYSCTL_NODE(_hw_usb, OID_AUTO, atp, CTLFLAG_RW, 0, "USB ATP");
+
+#ifdef USB_DEBUG
+enum atp_log_level {
+	ATP_LLEVEL_DISABLED = 0,
+	ATP_LLEVEL_ERROR,
+	ATP_LLEVEL_DEBUG,       /* for troubleshooting */
+	ATP_LLEVEL_INFO,        /* for diagnostics */
+};
+static int atp_debug = ATP_LLEVEL_ERROR; /* the default is to only log errors */
+SYSCTL_INT(_hw_usb_atp, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &atp_debug, ATP_LLEVEL_ERROR, "ATP debug level");
+#endif /* USB_DEBUG */
+
+static u_int atp_touch_timeout = ATP_TOUCH_TIMEOUT;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, touch_timeout, CTLFLAG_RWTUN,
+    &atp_touch_timeout, 125000, "age threshold in microseconds for a touch");
+
+static u_int atp_double_tap_threshold = ATP_DOUBLE_TAP_N_DRAG_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, double_tap_threshold, CTLFLAG_RWTUN,
+    &atp_double_tap_threshold, ATP_DOUBLE_TAP_N_DRAG_THRESHOLD,
+    "maximum time in microseconds to allow association between a double-tap and "
+    "drag gesture");
+
+static u_int atp_mickeys_scale_factor = ATP_SCALE_FACTOR;
+static int atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS);
+SYSCTL_PROC(_hw_usb_atp, OID_AUTO, scale_factor, CTLTYPE_UINT | CTLFLAG_RWTUN,
+    &atp_mickeys_scale_factor, sizeof(atp_mickeys_scale_factor),
+    atp_sysctl_scale_factor_handler, "IU", "movement scale factor");
+
+static u_int atp_small_movement_threshold = ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, small_movement, CTLFLAG_RWTUN,
+    &atp_small_movement_threshold, ATP_SMALL_MOVEMENT_THRESHOLD,
+    "the small movement black-hole for filtering noise");
+
+static u_int atp_tap_minimum = 1;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, tap_minimum, CTLFLAG_RWTUN,
+    &atp_tap_minimum, 1, "Minimum number of taps before detection");
+
+/*
+ * Strokes which accumulate at least this amount of absolute movement
+ * from the aggregate of their components are considered as
+ * slides. Unit: mickeys.
+ */
+static u_int atp_slide_min_movement = 2 * ATP_SMALL_MOVEMENT_THRESHOLD;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, slide_min_movement, CTLFLAG_RWTUN,
+    &atp_slide_min_movement, 2 * ATP_SMALL_MOVEMENT_THRESHOLD,
+    "strokes with at least this amt. of movement are considered slides");
+
+/*
+ * The minimum age of a stroke for it to be considered mature; this
+ * helps filter movements (noise) from immature strokes. Units: interrupts.
+ */
+static u_int atp_stroke_maturity_threshold = 4;
+SYSCTL_UINT(_hw_usb_atp, OID_AUTO, stroke_maturity_threshold, CTLFLAG_RWTUN,
+    &atp_stroke_maturity_threshold, 4,
+    "the minimum age of a stroke for it to be considered mature");
+
+typedef enum atp_trackpad_family {
+	TRACKPAD_FAMILY_FOUNTAIN_GEYSER,
+	TRACKPAD_FAMILY_WELLSPRING,
+	TRACKPAD_FAMILY_MAX /* keep this at the tail end of the enumeration */
+} trackpad_family_t;
+
+enum fountain_geyser_product {
+	FOUNTAIN,
+	GEYSER1,
+	GEYSER1_17inch,
+	GEYSER2,
+	GEYSER3,
+	GEYSER4,
+	FOUNTAIN_GEYSER_PRODUCT_MAX /* keep this at the end */
+};
+
+enum wellspring_product {
+	WELLSPRING1,
+	WELLSPRING2,
+	WELLSPRING3,
+	WELLSPRING4,
+	WELLSPRING4A,
+	WELLSPRING5,
+	WELLSPRING6A,
+	WELLSPRING6,
+	WELLSPRING5A,
+	WELLSPRING7,
+	WELLSPRING7A,
+	WELLSPRING8,
+	WELLSPRING_PRODUCT_MAX /* keep this at the end of the enumeration */
+};
+
+/* trackpad header types */
+enum fountain_geyser_trackpad_type {
+	FG_TRACKPAD_TYPE_GEYSER1,
+	FG_TRACKPAD_TYPE_GEYSER2,
+	FG_TRACKPAD_TYPE_GEYSER3,
+	FG_TRACKPAD_TYPE_GEYSER4,
+};
+enum wellspring_trackpad_type {
+	WSP_TRACKPAD_TYPE1,      /* plain trackpad */
+	WSP_TRACKPAD_TYPE2,      /* button integrated in trackpad */
+	WSP_TRACKPAD_TYPE3       /* additional header fields since June 2013 */
+};
+
+/*
+ * Trackpad family and product and family are encoded together in the
+ * driver_info value associated with a trackpad product.
+ */
+#define N_PROD_BITS 8  /* Number of bits used to encode product */
+#define ENCODE_DRIVER_INFO(FAMILY, PROD)      \
+    (((FAMILY) << N_PROD_BITS) | (PROD))
+#define DECODE_FAMILY_FROM_DRIVER_INFO(INFO)  ((INFO) >> N_PROD_BITS)
+#define DECODE_PRODUCT_FROM_DRIVER_INFO(INFO) \
+    ((INFO) & ((1 << N_PROD_BITS) - 1))
+
+#define FG_DRIVER_INFO(PRODUCT)               \
+    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_FOUNTAIN_GEYSER, PRODUCT)
+#define WELLSPRING_DRIVER_INFO(PRODUCT)       \
+    ENCODE_DRIVER_INFO(TRACKPAD_FAMILY_WELLSPRING, PRODUCT)
+
+/*
+ * The following structure captures the state of a pressure span along
+ * an axis. Each contact with the touchpad results in separate
+ * pressure spans along the two axes.
+ */
+typedef struct fg_pspan {
+	u_int width;       /* in units of sensors */
+	u_int cum;         /* cumulative compression (from all sensors) */
+	u_int cog;         /* center of gravity */
+	u_int loc;         /* location (scaled using the mickeys factor) */
+	boolean_t matched; /* to track pspans as they match against strokes. */
+} fg_pspan;
+
+#define FG_MAX_PSPANS_PER_AXIS 3
+#define FG_MAX_STROKES         (2 * FG_MAX_PSPANS_PER_AXIS)
+
+#define WELLSPRING_INTERFACE_INDEX 1
+
+/* trackpad finger data offsets, le16-aligned */
+#define WSP_TYPE1_FINGER_DATA_OFFSET  (13 * 2)
+#define WSP_TYPE2_FINGER_DATA_OFFSET  (15 * 2)
+#define WSP_TYPE3_FINGER_DATA_OFFSET  (19 * 2)
+
+/* trackpad button data offsets */
+#define WSP_TYPE2_BUTTON_DATA_OFFSET   15
+#define WSP_TYPE3_BUTTON_DATA_OFFSET   23
+
+/* list of device capability bits */
+#define HAS_INTEGRATED_BUTTON   1
+
+/* trackpad finger structure - little endian */
+struct wsp_finger_sensor_data {
+	int16_t origin;       /* zero when switching track finger */
+	int16_t abs_x;        /* absolute x coordinate */
+	int16_t abs_y;        /* absolute y coordinate */
+	int16_t rel_x;        /* relative x coordinate */
+	int16_t rel_y;        /* relative y coordinate */
+	int16_t tool_major;   /* tool area, major axis */
+	int16_t tool_minor;   /* tool area, minor axis */
+	int16_t orientation;  /* 16384 when point, else 15 bit angle */
+	int16_t touch_major;  /* touch area, major axis */
+	int16_t touch_minor;  /* touch area, minor axis */
+	int16_t unused[3];    /* zeros */
+	int16_t multi;        /* one finger: varies, more fingers: constant */
+} __packed;
+
+typedef struct wsp_finger {
+	/* to track fingers as they match against strokes. */
+	boolean_t matched;
+
+	/* location (scaled using the mickeys factor) */
+	int x;
+	int y;
+} wsp_finger_t;
+
+#define WSP_MAX_FINGERS               16
+#define WSP_SIZEOF_FINGER_SENSOR_DATA sizeof(struct wsp_finger_sensor_data)
+#define WSP_SIZEOF_ALL_FINGER_DATA    (WSP_MAX_FINGERS * \
+				       WSP_SIZEOF_FINGER_SENSOR_DATA)
+#define WSP_MAX_FINGER_ORIENTATION    16384
+
+#define ATP_SENSOR_DATA_BUF_MAX       1024
+#if (ATP_SENSOR_DATA_BUF_MAX < ((WSP_MAX_FINGERS * 14 * 2) + \
+				WSP_TYPE3_FINGER_DATA_OFFSET))
+/* note: 14 * 2 in the above is based on sizeof(struct wsp_finger_sensor_data)*/
+#error "ATP_SENSOR_DATA_BUF_MAX is too small"
+#endif
+
+#define ATP_MAX_STROKES               MAX(WSP_MAX_FINGERS, FG_MAX_STROKES)
+
+#define FG_MAX_XSENSORS 26
+#define FG_MAX_YSENSORS 16
+
+/* device-specific configuration */
+struct fg_dev_params {
+	u_int                              data_len;   /* for sensor data */
+	u_int                              n_xsensors;
+	u_int                              n_ysensors;
+	enum fountain_geyser_trackpad_type prot;
+};
+struct wsp_dev_params {
+	uint8_t  caps;               /* device capability bitmask */
+	uint8_t  tp_type;            /* type of trackpad interface */
+	uint8_t  finger_data_offset; /* offset to trackpad finger data */
+};
+
+static const struct fg_dev_params fg_dev_params[FOUNTAIN_GEYSER_PRODUCT_MAX] = {
+	[FOUNTAIN] = {
+		.data_len   = 81,
+		.n_xsensors = 16,
+		.n_ysensors = 16,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
+	},
+	[GEYSER1] = {
+		.data_len   = 81,
+		.n_xsensors = 16,
+		.n_ysensors = 16,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
+	},
+	[GEYSER1_17inch] = {
+		.data_len   = 81,
+		.n_xsensors = 26,
+		.n_ysensors = 16,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER1
+	},
+	[GEYSER2] = {
+		.data_len   = 64,
+		.n_xsensors = 15,
+		.n_ysensors = 9,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER2
+	},
+	[GEYSER3] = {
+		.data_len   = 64,
+		.n_xsensors = 20,
+		.n_ysensors = 10,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER3
+	},
+	[GEYSER4] = {
+		.data_len   = 64,
+		.n_xsensors = 20,
+		.n_ysensors = 10,
+		.prot       = FG_TRACKPAD_TYPE_GEYSER4
+	}
+};
+
+static const STRUCT_USB_HOST_ID fg_devs[] = {
+	/* PowerBooks Feb 2005, iBooks G4 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020e, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020f, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0210, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x030a, FG_DRIVER_INFO(FOUNTAIN)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x030b, FG_DRIVER_INFO(GEYSER1)) },
+
+	/* PowerBooks Oct 2005 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0214, FG_DRIVER_INFO(GEYSER2)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0215, FG_DRIVER_INFO(GEYSER2)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0216, FG_DRIVER_INFO(GEYSER2)) },
+
+	/* Core Duo MacBook & MacBook Pro */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0217, FG_DRIVER_INFO(GEYSER3)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0218, FG_DRIVER_INFO(GEYSER3)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0219, FG_DRIVER_INFO(GEYSER3)) },
+
+	/* Core2 Duo MacBook & MacBook Pro */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021a, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021b, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x021c, FG_DRIVER_INFO(GEYSER4)) },
+
+	/* Core2 Duo MacBook3,1 */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x0229, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022a, FG_DRIVER_INFO(GEYSER4)) },
+	{ USB_VPI(USB_VENDOR_APPLE, 0x022b, FG_DRIVER_INFO(GEYSER4)) },
+
+	/* 17 inch PowerBook */
+	{ USB_VPI(USB_VENDOR_APPLE, 0x020d, FG_DRIVER_INFO(GEYSER1_17inch)) },
+};
+
+static const struct wsp_dev_params wsp_dev_params[WELLSPRING_PRODUCT_MAX] = {
+	[WELLSPRING1] = {
+		.caps       = 0,
+		.tp_type    = WSP_TRACKPAD_TYPE1,
+		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING2] = {
+		.caps       = 0,
+		.tp_type    = WSP_TRACKPAD_TYPE1,
+		.finger_data_offset  = WSP_TYPE1_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING3] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING4] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING4A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING5] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING6] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING5A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING6A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING7] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING7A] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE2,
+		.finger_data_offset  = WSP_TYPE2_FINGER_DATA_OFFSET,
+	},
+	[WELLSPRING8] = {
+		.caps       = HAS_INTEGRATED_BUTTON,
+		.tp_type    = WSP_TRACKPAD_TYPE3,
+		.finger_data_offset  = WSP_TYPE3_FINGER_DATA_OFFSET,
+	},
+};
+
+#define ATP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+
+/* TODO: STRUCT_USB_HOST_ID */
+static const struct usb_device_id wsp_devs[] = {
+	/* MacbookAir1.1 */
+	ATP_DEV(APPLE, WELLSPRING_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+	ATP_DEV(APPLE, WELLSPRING_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+	ATP_DEV(APPLE, WELLSPRING_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING1)),
+
+	/* MacbookProPenryn, aka wellspring2 */
+	ATP_DEV(APPLE, WELLSPRING2_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+	ATP_DEV(APPLE, WELLSPRING2_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+	ATP_DEV(APPLE, WELLSPRING2_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING2)),
+
+	/* Macbook5,1 (unibody), aka wellspring3 */
+	ATP_DEV(APPLE, WELLSPRING3_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+	ATP_DEV(APPLE, WELLSPRING3_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+	ATP_DEV(APPLE, WELLSPRING3_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING3)),
+
+	/* MacbookAir3,2 (unibody), aka wellspring4 */
+	ATP_DEV(APPLE, WELLSPRING4_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+	ATP_DEV(APPLE, WELLSPRING4_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+	ATP_DEV(APPLE, WELLSPRING4_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4)),
+
+	/* MacbookAir3,1 (unibody), aka wellspring4 */
+	ATP_DEV(APPLE, WELLSPRING4A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+	ATP_DEV(APPLE, WELLSPRING4A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+	ATP_DEV(APPLE, WELLSPRING4A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING4A)),
+
+	/* Macbook8 (unibody, March 2011) */
+	ATP_DEV(APPLE, WELLSPRING5_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+	ATP_DEV(APPLE, WELLSPRING5_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+	ATP_DEV(APPLE, WELLSPRING5_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5)),
+
+	/* MacbookAir4,1 (unibody, July 2011) */
+	ATP_DEV(APPLE, WELLSPRING6A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+	ATP_DEV(APPLE, WELLSPRING6A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+	ATP_DEV(APPLE, WELLSPRING6A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6A)),
+
+	/* MacbookAir4,2 (unibody, July 2011) */
+	ATP_DEV(APPLE, WELLSPRING6_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+	ATP_DEV(APPLE, WELLSPRING6_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+	ATP_DEV(APPLE, WELLSPRING6_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING6)),
+
+	/* Macbook8,2 (unibody) */
+	ATP_DEV(APPLE, WELLSPRING5A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+	ATP_DEV(APPLE, WELLSPRING5A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+	ATP_DEV(APPLE, WELLSPRING5A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING5A)),
+
+	/* MacbookPro10,1 (unibody, June 2012) */
+	/* MacbookPro11,? (unibody, June 2013) */
+	ATP_DEV(APPLE, WELLSPRING7_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+	ATP_DEV(APPLE, WELLSPRING7_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+	ATP_DEV(APPLE, WELLSPRING7_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7)),
+
+	/* MacbookPro10,2 (unibody, October 2012) */
+	ATP_DEV(APPLE, WELLSPRING7A_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+	ATP_DEV(APPLE, WELLSPRING7A_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+	ATP_DEV(APPLE, WELLSPRING7A_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING7A)),
+
+	/* MacbookAir6,2 (unibody, June 2013) */
+	ATP_DEV(APPLE, WELLSPRING8_ANSI, WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+	ATP_DEV(APPLE, WELLSPRING8_ISO,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+	ATP_DEV(APPLE, WELLSPRING8_JIS,  WELLSPRING_DRIVER_INFO(WELLSPRING8)),
+};
+
+typedef enum atp_stroke_type {
+	ATP_STROKE_TOUCH,
+	ATP_STROKE_SLIDE,
+} atp_stroke_type;
+
+typedef enum atp_axis {
+	X = 0,
+	Y = 1,
+	NUM_AXES
+} atp_axis;
+
+#define ATP_FIFO_BUF_SIZE        8 /* bytes */
+#define ATP_FIFO_QUEUE_MAXLEN   50 /* units */
+
+enum {
+	ATP_INTR_DT,
+	ATP_RESET,
+	ATP_N_TRANSFER,
+};
+
+typedef struct fg_stroke_component {
+	/* Fields encapsulating the pressure-span. */
+	u_int loc;              /* location (scaled) */
+	u_int cum_pressure;     /* cumulative compression */
+	u_int max_cum_pressure; /* max cumulative compression */
+	boolean_t matched; /*to track components as they match against pspans.*/
+
+	int   delta_mickeys;    /* change in location (un-smoothened movement)*/
+} fg_stroke_component_t;
+
+/*
+ * The following structure captures a finger contact with the
+ * touchpad. A stroke comprises two p-span components and some state.
+ */
+typedef struct atp_stroke {
+	TAILQ_ENTRY(atp_stroke) entry;
+
+	atp_stroke_type type;
+	uint32_t        flags; /* the state of this stroke */
+#define ATSF_ZOMBIE 0x1
+	boolean_t       matched;          /* to track match against fingers.*/
+
+	struct timeval  ctime; /* create time; for coincident siblings. */
+
+	/*
+	 * Unit: interrupts; we maintain this value in
+	 * addition to 'ctime' in order to avoid the
+	 * expensive call to microtime() at every
+	 * interrupt.
+	 */
+	uint32_t age;
+
+	/* Location */
+	int x;
+	int y;
+
+	/* Fields containing information about movement. */
+	int   instantaneous_dx; /* curr. change in X location (un-smoothened) */
+	int   instantaneous_dy; /* curr. change in Y location (un-smoothened) */
+	int   pending_dx;       /* cum. of pending short movements */
+	int   pending_dy;       /* cum. of pending short movements */
+	int   movement_dx;      /* interpreted smoothened movement */
+	int   movement_dy;      /* interpreted smoothened movement */
+	int   cum_movement_x;   /* cum. horizontal movement */
+	int   cum_movement_y;   /* cum. vertical movement */
+
+	/*
+	 * The following member is relevant only for fountain-geyser trackpads.
+	 * For these, there is the need to track pressure-spans and cumulative
+	 * pressures for stroke components.
+	 */
+	fg_stroke_component_t components[NUM_AXES];
+} atp_stroke_t;
+
+struct atp_softc; /* forward declaration */
+typedef void (*sensor_data_interpreter_t)(struct atp_softc *sc, u_int len);
+
+struct atp_softc {
+	device_t            sc_dev;
+	struct usb_device  *sc_usb_device;
+	struct mtx          sc_mutex; /* for synchronization */
+	struct usb_fifo_sc  sc_fifo;
+
+#define	MODE_LENGTH 8
+	char                sc_mode_bytes[MODE_LENGTH]; /* device mode */
+
+	trackpad_family_t   sc_family;
+	const void         *sc_params; /* device configuration */
+	sensor_data_interpreter_t sensor_data_interpreter;
+
+	mousehw_t           sc_hw;
+	mousemode_t         sc_mode;
+	mousestatus_t       sc_status;
+
+	u_int               sc_state;
+#define ATP_ENABLED          0x01
+#define ATP_ZOMBIES_EXIST    0x02
+#define ATP_DOUBLE_TAP_DRAG  0x04
+#define ATP_VALID            0x08
+
+	struct usb_xfer    *sc_xfer[ATP_N_TRANSFER];
+
+	u_int               sc_pollrate;
+	int                 sc_fflags;
+
+	atp_stroke_t        sc_strokes_data[ATP_MAX_STROKES];
+	TAILQ_HEAD(,atp_stroke) sc_stroke_free;
+	TAILQ_HEAD(,atp_stroke) sc_stroke_used;
+	u_int               sc_n_strokes;
+
+	struct callout	    sc_callout;
+
+	/*
+	 * button status. Set to non-zero if the mouse-button is physically
+	 * pressed. This state variable is exposed through softc to allow
+	 * reap_sibling_zombies to avoid registering taps while the trackpad
+	 * button is pressed.
+         */
+	uint8_t             sc_ibtn;
+
+	/*
+	 * Time when touch zombies were last reaped; useful for detecting
+	 * double-touch-n-drag.
+	 */
+	struct timeval      sc_touch_reap_time;
+
+	u_int	            sc_idlecount;
+
+	/* Regarding the data transferred from t-pad in USB INTR packets. */
+	u_int   sc_expected_sensor_data_len;
+	uint8_t sc_sensor_data[ATP_SENSOR_DATA_BUF_MAX] __aligned(4);
+
+	int      sc_cur_x[FG_MAX_XSENSORS];      /* current sensor readings */
+	int      sc_cur_y[FG_MAX_YSENSORS];
+	int      sc_base_x[FG_MAX_XSENSORS];     /* base sensor readings */
+	int      sc_base_y[FG_MAX_YSENSORS];
+	int      sc_pressure_x[FG_MAX_XSENSORS]; /* computed pressures */
+	int      sc_pressure_y[FG_MAX_YSENSORS];
+	fg_pspan sc_pspans_x[FG_MAX_PSPANS_PER_AXIS];
+	fg_pspan sc_pspans_y[FG_MAX_PSPANS_PER_AXIS];
+};
+
+/*
+ * The last byte of the fountain-geyser sensor data contains status bits; the
+ * following values define the meanings of these bits.
+ * (only Geyser 3/4)
+ */
+enum geyser34_status_bits {
+	FG_STATUS_BUTTON      = (uint8_t)0x01, /* The button was pressed */
+	FG_STATUS_BASE_UPDATE = (uint8_t)0x04, /* Data from an untouched pad.*/
+};
+
+typedef enum interface_mode {
+	RAW_SENSOR_MODE = (uint8_t)0x01,
+	HID_MODE        = (uint8_t)0x08
+} interface_mode;
+
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t   atp_start_read;
+static usb_fifo_cmd_t   atp_stop_read;
+static usb_fifo_open_t  atp_open;
+static usb_fifo_close_t atp_close;
+static usb_fifo_ioctl_t atp_ioctl;
+
+static struct usb_fifo_methods atp_fifo_methods = {
+	.f_open       = &atp_open,
+	.f_close      = &atp_close,
+	.f_ioctl      = &atp_ioctl,
+	.f_start_read = &atp_start_read,
+	.f_stop_read  = &atp_stop_read,
+	.basename[0]  = ATP_DRIVER_NAME,
+};
+
+/* device initialization and shutdown */
+static usb_error_t   atp_set_device_mode(struct atp_softc *, interface_mode);
+static void	     atp_reset_callback(struct usb_xfer *, usb_error_t);
+static int	     atp_enable(struct atp_softc *);
+static void	     atp_disable(struct atp_softc *);
+
+/* sensor interpretation */
+static void	     fg_interpret_sensor_data(struct atp_softc *, u_int);
+static void	     fg_extract_sensor_data(const int8_t *, u_int, atp_axis,
+    int *, enum fountain_geyser_trackpad_type);
+static void	     fg_get_pressures(int *, const int *, const int *, int);
+static void	     fg_detect_pspans(int *, u_int, u_int, fg_pspan *, u_int *);
+static void	     wsp_interpret_sensor_data(struct atp_softc *, u_int);
+
+/* movement detection */
+static boolean_t     fg_match_stroke_component(fg_stroke_component_t *,
+    const fg_pspan *, atp_stroke_type);
+static void	     fg_match_strokes_against_pspans(struct atp_softc *,
+    atp_axis, fg_pspan *, u_int, u_int);
+static boolean_t     wsp_match_strokes_against_fingers(struct atp_softc *,
+    wsp_finger_t *, u_int);
+static boolean_t     fg_update_strokes(struct atp_softc *, fg_pspan *, u_int,
+    fg_pspan *, u_int);
+static boolean_t     wsp_update_strokes(struct atp_softc *,
+    wsp_finger_t [WSP_MAX_FINGERS], u_int);
+static void fg_add_stroke(struct atp_softc *, const fg_pspan *, const fg_pspan *);
+static void	     fg_add_new_strokes(struct atp_softc *, fg_pspan *,
+    u_int, fg_pspan *, u_int);
+static void wsp_add_stroke(struct atp_softc *, const wsp_finger_t *);
+static void	     atp_advance_stroke_state(struct atp_softc *,
+    atp_stroke_t *, boolean_t *);
+static boolean_t atp_stroke_has_small_movement(const atp_stroke_t *);
+static void	     atp_update_pending_mickeys(atp_stroke_t *);
+static boolean_t     atp_compute_stroke_movement(atp_stroke_t *);
+static void	     atp_terminate_stroke(struct atp_softc *, atp_stroke_t *);
+
+/* tap detection */
+static boolean_t atp_is_horizontal_scroll(const atp_stroke_t *);
+static boolean_t atp_is_vertical_scroll(const atp_stroke_t *);
+static void	     atp_reap_sibling_zombies(void *);
+static void	     atp_convert_to_slide(struct atp_softc *, atp_stroke_t *);
+
+/* updating fifo */
+static void	     atp_reset_buf(struct atp_softc *);
+static void	     atp_add_to_queue(struct atp_softc *, int, int, int, uint32_t);
+
+/* Device methods. */
+static device_probe_t  atp_probe;
+static device_attach_t atp_attach;
+static device_detach_t atp_detach;
+static usb_callback_t  atp_intr;
+
+static const struct usb_config atp_xfer_config[ATP_N_TRANSFER] = {
+	[ATP_INTR_DT] = {
+		.type      = UE_INTERRUPT,
+		.endpoint  = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {
+			.pipe_bof = 1, /* block pipe on failure */
+			.short_xfer_ok = 1,
+		},
+		.bufsize   = ATP_SENSOR_DATA_BUF_MAX,
+		.callback  = &atp_intr,
+	},
+	[ATP_RESET] = {
+		.type      = UE_CONTROL,
+		.endpoint  = 0, /* Control pipe */
+		.direction = UE_DIR_ANY,
+		.bufsize   = sizeof(struct usb_device_request) + MODE_LENGTH,
+		.callback  = &atp_reset_callback,
+		.interval  = 0,  /* no pre-delay */
+	},
+};
+
+static atp_stroke_t *
+atp_alloc_stroke(struct atp_softc *sc)
+{
+	atp_stroke_t *pstroke;
+
+	pstroke = TAILQ_FIRST(&sc->sc_stroke_free);
+	if (pstroke == NULL)
+		goto done;
+
+	TAILQ_REMOVE(&sc->sc_stroke_free, pstroke, entry);
+	memset(pstroke, 0, sizeof(*pstroke));
+	TAILQ_INSERT_TAIL(&sc->sc_stroke_used, pstroke, entry);
+
+	sc->sc_n_strokes++;
+done:
+	return (pstroke);
+}
+
+static void
+atp_free_stroke(struct atp_softc *sc, atp_stroke_t *pstroke)
+{
+	if (pstroke == NULL)
+		return;
+
+	sc->sc_n_strokes--;
+
+	TAILQ_REMOVE(&sc->sc_stroke_used, pstroke, entry);
+	TAILQ_INSERT_TAIL(&sc->sc_stroke_free, pstroke, entry);
+}
+
+static void
+atp_init_stroke_pool(struct atp_softc *sc)
+{
+	u_int x;
+
+	TAILQ_INIT(&sc->sc_stroke_free);
+	TAILQ_INIT(&sc->sc_stroke_used);
+
+	sc->sc_n_strokes = 0;
+
+	memset(&sc->sc_strokes_data, 0, sizeof(sc->sc_strokes_data));
+
+	for (x = 0; x != ATP_MAX_STROKES; x++) {
+		TAILQ_INSERT_TAIL(&sc->sc_stroke_free, &sc->sc_strokes_data[x],
+		    entry);
+	}
+}
+
+static usb_error_t
+atp_set_device_mode(struct atp_softc *sc, interface_mode newMode)
+{
+	uint8_t mode_value;
+	usb_error_t err;
+
+	if ((newMode != RAW_SENSOR_MODE) && (newMode != HID_MODE))
+		return (USB_ERR_INVAL);
+
+	if ((newMode == RAW_SENSOR_MODE) &&
+	    (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER))
+		mode_value = (uint8_t)0x04;
+	else
+		mode_value = newMode;
+
+	err = usbd_req_get_report(sc->sc_usb_device, NULL /* mutex */,
+	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+	    0x03 /* type */, 0x00 /* id */);
+	if (err != USB_ERR_NORMAL_COMPLETION) {
+		DPRINTF("Failed to read device mode (%d)\n", err);
+		return (err);
+	}
+
+	if (sc->sc_mode_bytes[0] == mode_value)
+		return (err);
+
+	/*
+	 * XXX Need to wait at least 250ms for hardware to get
+	 * ready. The device mode handling appears to be handled
+	 * asynchronously and we should not issue these commands too
+	 * quickly.
+	 */
+	pause("WHW", hz / 4);
+
+	sc->sc_mode_bytes[0] = mode_value;
+	return (usbd_req_set_report(sc->sc_usb_device, NULL /* mutex */,
+	    sc->sc_mode_bytes, sizeof(sc->sc_mode_bytes), 0 /* interface idx */,
+	    0x03 /* type */, 0x00 /* id */));
+}
+
+static void
+atp_reset_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	usb_device_request_t   req;
+	struct usb_page_cache *pc;
+	struct atp_softc      *sc = usbd_xfer_softc(xfer);
+
+	uint8_t mode_value;
+	if (sc->sc_family == TRACKPAD_FAMILY_FOUNTAIN_GEYSER)
+		mode_value = 0x04;
+	else
+		mode_value = RAW_SENSOR_MODE;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_SETUP:
+		sc->sc_mode_bytes[0] = mode_value;
+		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+		req.bRequest = UR_SET_REPORT;
+		USETW2(req.wValue,
+		    (uint8_t)0x03 /* type */, (uint8_t)0x00 /* id */);
+		USETW(req.wIndex, 0);
+		USETW(req.wLength, MODE_LENGTH);
+
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_in(pc, 0, &req, sizeof(req));
+		pc = usbd_xfer_get_frame(xfer, 1);
+		usbd_copy_in(pc, 0, sc->sc_mode_bytes, MODE_LENGTH);
+
+		usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+		usbd_xfer_set_frame_len(xfer, 1, MODE_LENGTH);
+		usbd_xfer_set_frames(xfer, 2);
+		usbd_transfer_submit(xfer);
+		break;
+
+	case USB_ST_TRANSFERRED:
+	default:
+		break;
+	}
+}
+
+static int
+atp_enable(struct atp_softc *sc)
+{
+	if (sc->sc_state & ATP_ENABLED)
+		return (0);
+
+	/* reset status */
+	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+
+	atp_init_stroke_pool(sc);
+
+	sc->sc_state |= ATP_ENABLED;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "enabled atp\n");
+	return (0);
+}
+
+static void
+atp_disable(struct atp_softc *sc)
+{
+	sc->sc_state &= ~(ATP_ENABLED | ATP_VALID);
+	DPRINTFN(ATP_LLEVEL_INFO, "disabled atp\n");
+}
+
+static void
+fg_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+	u_int n_xpspans = 0;
+	u_int n_ypspans = 0;
+	uint8_t status_bits;
+
+	const struct fg_dev_params *params =
+	    (const struct fg_dev_params *)sc->sc_params;
+
+	fg_extract_sensor_data(sc->sc_sensor_data, params->n_xsensors, X,
+	    sc->sc_cur_x, params->prot);
+	fg_extract_sensor_data(sc->sc_sensor_data, params->n_ysensors, Y,
+	    sc->sc_cur_y, params->prot);
+
+	/*
+	 * If this is the initial update (from an untouched
+	 * pad), we should set the base values for the sensor
+	 * data; deltas with respect to these base values can
+	 * be used as pressure readings subsequently.
+	 */
+	status_bits = sc->sc_sensor_data[params->data_len - 1];
+	if (((params->prot == FG_TRACKPAD_TYPE_GEYSER3) ||
+	     (params->prot == FG_TRACKPAD_TYPE_GEYSER4))  &&
+	    ((sc->sc_state & ATP_VALID) == 0)) {
+		if (status_bits & FG_STATUS_BASE_UPDATE) {
+			memcpy(sc->sc_base_x, sc->sc_cur_x,
+			    params->n_xsensors * sizeof(*sc->sc_base_x));
+			memcpy(sc->sc_base_y, sc->sc_cur_y,
+			    params->n_ysensors * sizeof(*sc->sc_base_y));
+			sc->sc_state |= ATP_VALID;
+			return;
+		}
+	}
+
+	/* Get pressure readings and detect p-spans for both axes. */
+	fg_get_pressures(sc->sc_pressure_x, sc->sc_cur_x, sc->sc_base_x,
+	    params->n_xsensors);
+	fg_detect_pspans(sc->sc_pressure_x, params->n_xsensors,
+	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_x, &n_xpspans);
+	fg_get_pressures(sc->sc_pressure_y, sc->sc_cur_y, sc->sc_base_y,
+	    params->n_ysensors);
+	fg_detect_pspans(sc->sc_pressure_y, params->n_ysensors,
+	    FG_MAX_PSPANS_PER_AXIS, sc->sc_pspans_y, &n_ypspans);
+
+	/* Update strokes with new pspans to detect movements. */
+	if (fg_update_strokes(sc, sc->sc_pspans_x, n_xpspans, sc->sc_pspans_y, n_ypspans))
+		sc->sc_status.flags |= MOUSE_POSCHANGED;
+
+	sc->sc_ibtn = (status_bits & FG_STATUS_BUTTON) ? MOUSE_BUTTON1DOWN : 0;
+	sc->sc_status.button = sc->sc_ibtn;
+
+	/*
+	 * The Fountain/Geyser device continues to trigger interrupts
+	 * at a fast rate even after touchpad activity has
+	 * stopped. Upon detecting that the device has remained idle
+	 * beyond a threshold, we reinitialize it to silence the
+	 * interrupts.
+	 */
+	if ((sc->sc_status.flags  == 0) && (sc->sc_n_strokes == 0)) {
+		sc->sc_idlecount++;
+		if (sc->sc_idlecount >= ATP_IDLENESS_THRESHOLD) {
+			/*
+			 * Use the last frame before we go idle for
+			 * calibration on pads which do not send
+			 * calibration frames.
+			 */
+			const struct fg_dev_params *params =
+			    (const struct fg_dev_params *)sc->sc_params;
+
+			DPRINTFN(ATP_LLEVEL_INFO, "idle\n");
+
+			if (params->prot < FG_TRACKPAD_TYPE_GEYSER3) {
+				memcpy(sc->sc_base_x, sc->sc_cur_x,
+				    params->n_xsensors * sizeof(*(sc->sc_base_x)));
+				memcpy(sc->sc_base_y, sc->sc_cur_y,
+				    params->n_ysensors * sizeof(*(sc->sc_base_y)));
+			}
+
+			sc->sc_idlecount = 0;
+			usbd_transfer_start(sc->sc_xfer[ATP_RESET]);
+		}
+	} else {
+		sc->sc_idlecount = 0;
+	}
+}
+
+/*
+ * Interpret the data from the X and Y pressure sensors. This function
+ * is called separately for the X and Y sensor arrays. The data in the
+ * USB packet is laid out in the following manner:
+ *
+ * sensor_data:
+ *            --,--,Y1,Y2,--,Y3,Y4,--,Y5,...,Y10, ... X1,X2,--,X3,X4
+ *  indices:   0  1  2  3  4  5  6  7  8 ...  15  ... 20 21 22 23 24
+ *
+ * '--' (in the above) indicates that the value is unimportant.
+ *
+ * Information about the above layout was obtained from the
+ * implementation of the AppleTouch driver in Linux.
+ *
+ * parameters:
+ *   sensor_data
+ *       raw sensor data from the USB packet.
+ *   num
+ *       The number of elements in the array 'arr'.
+ *   axis
+ *       Axis of data to fetch
+ *   arr
+ *       The array to be initialized with the readings.
+ *   prot
+ *       The protocol to use to interpret the data
+ */
+static void
+fg_extract_sensor_data(const int8_t *sensor_data, u_int num, atp_axis axis,
+    int	*arr, enum fountain_geyser_trackpad_type prot)
+{
+	u_int i;
+	u_int di;   /* index into sensor data */
+
+	switch (prot) {
+	case FG_TRACKPAD_TYPE_GEYSER1:
+		/*
+		 * For Geyser 1, the sensors are laid out in pairs
+		 * every 5 bytes.
+		 */
+		for (i = 0, di = (axis == Y) ? 1 : 2; i < 8; di += 5, i++) {
+			arr[i] = sensor_data[di];
+			arr[i+8] = sensor_data[di+2];
+			if ((axis == X) && (num > 16))
+				arr[i+16] = sensor_data[di+40];
+		}
+
+		break;
+	case FG_TRACKPAD_TYPE_GEYSER2:
+		for (i = 0, di = (axis == Y) ? 1 : 19; i < num; /* empty */ ) {
+			arr[i++] = sensor_data[di++];
+			arr[i++] = sensor_data[di++];
+			di++;
+		}
+		break;
+	case FG_TRACKPAD_TYPE_GEYSER3:
+	case FG_TRACKPAD_TYPE_GEYSER4:
+		for (i = 0, di = (axis == Y) ? 2 : 20; i < num; /* empty */ ) {
+			arr[i++] = sensor_data[di++];
+			arr[i++] = sensor_data[di++];
+			di++;
+		}
+		break;
+	default:
+		break;
+	}
+}
+
+static void
+fg_get_pressures(int *p, const int *cur, const int *base, int n)
+{
+	int i;
+
+	for (i = 0; i < n; i++) {
+		p[i] = cur[i] - base[i];
+		if (p[i] > 127)
+			p[i] -= 256;
+		if (p[i] < -127)
+			p[i] += 256;
+		if (p[i] < 0)
+			p[i] = 0;
+
+		/*
+		 * Shave off pressures below the noise-pressure
+		 * threshold; this will reduce the contribution from
+		 * lower pressure readings.
+		 */
+		if ((u_int)p[i] <= FG_SENSOR_NOISE_THRESHOLD)
+			p[i] = 0; /* filter away noise */
+		else
+			p[i] -= FG_SENSOR_NOISE_THRESHOLD;
+	}
+}
+
+static void
+fg_detect_pspans(int *p, u_int num_sensors,
+    u_int      max_spans, /* max # of pspans permitted */
+    fg_pspan  *spans,     /* finger spans */
+    u_int     *nspans_p)  /* num spans detected */
+{
+	u_int i;
+	int   maxp;             /* max pressure seen within a span */
+	u_int num_spans = 0;
+
+	enum fg_pspan_state {
+		ATP_PSPAN_INACTIVE,
+		ATP_PSPAN_INCREASING,
+		ATP_PSPAN_DECREASING,
+	} state; /* state of the pressure span */
+
+	/*
+	 * The following is a simple state machine to track
+	 * the phase of the pressure span.
+	 */
+	memset(spans, 0, max_spans * sizeof(fg_pspan));
+	maxp = 0;
+	state = ATP_PSPAN_INACTIVE;
+	for (i = 0; i < num_sensors; i++) {
+		if (num_spans >= max_spans)
+			break;
+
+		if (p[i] == 0) {
+			if (state == ATP_PSPAN_INACTIVE) {
+				/*
+				 * There is no pressure information for this
+				 * sensor, and we aren't tracking a finger.
+				 */
+				continue;
+			} else {
+				state = ATP_PSPAN_INACTIVE;
+				maxp = 0;
+				num_spans++;
+			}
+		} else {
+			switch (state) {
+			case ATP_PSPAN_INACTIVE:
+				state = ATP_PSPAN_INCREASING;
+				maxp  = p[i];
+				break;
+
+			case ATP_PSPAN_INCREASING:
+				if (p[i] > maxp)
+					maxp = p[i];
+				else if (p[i] <= (maxp >> 1))
+					state = ATP_PSPAN_DECREASING;
+				break;
+
+			case ATP_PSPAN_DECREASING:
+				if (p[i] > p[i - 1]) {
+					/*
+					 * This is the beginning of
+					 * another span; change state
+					 * to give the appearance that
+					 * we're starting from an
+					 * inactive span, and then
+					 * re-process this reading in
+					 * the next iteration.
+					 */
+					num_spans++;
+					state = ATP_PSPAN_INACTIVE;
+					maxp  = 0;
+					i--;
+					continue;
+				}
+				break;
+			}
+
+			/* Update the finger span with this reading. */
+			spans[num_spans].width++;
+			spans[num_spans].cum += p[i];
+			spans[num_spans].cog += p[i] * (i + 1);
+		}
+	}
+	if (state != ATP_PSPAN_INACTIVE)
+		num_spans++;    /* close the last finger span */
+
+	/* post-process the spans */
+	for (i = 0; i < num_spans; i++) {
+		/* filter away unwanted pressure spans */
+		if ((spans[i].cum < FG_PSPAN_MIN_CUM_PRESSURE) ||
+		    (spans[i].width > FG_PSPAN_MAX_WIDTH)) {
+			if ((i + 1) < num_spans) {
+				memcpy(&spans[i], &spans[i + 1],
+				    (num_spans - i - 1) * sizeof(fg_pspan));
+				i--;
+			}
+			num_spans--;
+			continue;
+		}
+
+		/* compute this span's representative location */
+		spans[i].loc = spans[i].cog * FG_SCALE_FACTOR /
+			spans[i].cum;
+
+		spans[i].matched = false; /* not yet matched against a stroke */
+	}
+
+	*nspans_p = num_spans;
+}
+
+static void
+wsp_interpret_sensor_data(struct atp_softc *sc, u_int data_len)
+{
+	const struct wsp_dev_params *params = sc->sc_params;
+	wsp_finger_t fingers[WSP_MAX_FINGERS];
+	struct wsp_finger_sensor_data *source_fingerp;
+	u_int n_source_fingers;
+	u_int n_fingers;
+	u_int i;
+
+	/* validate sensor data length */
+	if ((data_len < params->finger_data_offset) ||
+	    ((data_len - params->finger_data_offset) %
+	     WSP_SIZEOF_FINGER_SENSOR_DATA) != 0)
+		return;
+
+	/* compute number of source fingers */
+	n_source_fingers = (data_len - params->finger_data_offset) /
+	    WSP_SIZEOF_FINGER_SENSOR_DATA;
+
+	if (n_source_fingers > WSP_MAX_FINGERS)
+		n_source_fingers = WSP_MAX_FINGERS;
+
+	/* iterate over the source data collecting useful fingers */
+	n_fingers = 0;
+	source_fingerp = (struct wsp_finger_sensor_data *)(sc->sc_sensor_data +
+	     params->finger_data_offset);
+
+	for (i = 0; i < n_source_fingers; i++, source_fingerp++) {
+		/* swap endianness, if any */
+		if (le16toh(0x1234) != 0x1234) {
+			source_fingerp->origin      = le16toh((uint16_t)source_fingerp->origin);
+			source_fingerp->abs_x       = le16toh((uint16_t)source_fingerp->abs_x);
+			source_fingerp->abs_y       = le16toh((uint16_t)source_fingerp->abs_y);
+			source_fingerp->rel_x       = le16toh((uint16_t)source_fingerp->rel_x);
+			source_fingerp->rel_y       = le16toh((uint16_t)source_fingerp->rel_y);
+			source_fingerp->tool_major  = le16toh((uint16_t)source_fingerp->tool_major);
+			source_fingerp->tool_minor  = le16toh((uint16_t)source_fingerp->tool_minor);
+			source_fingerp->orientation = le16toh((uint16_t)source_fingerp->orientation);
+			source_fingerp->touch_major = le16toh((uint16_t)source_fingerp->touch_major);
+			source_fingerp->touch_minor = le16toh((uint16_t)source_fingerp->touch_minor);
+			source_fingerp->multi       = le16toh((uint16_t)source_fingerp->multi);
+		}
+
+		/* check for minium threshold */
+		if (source_fingerp->touch_major == 0)
+			continue;
+
+		fingers[n_fingers].matched = false;
+		fingers[n_fingers].x       = source_fingerp->abs_x;
+		fingers[n_fingers].y       = -source_fingerp->abs_y;
+
+		n_fingers++;
+	}
+
+	if ((sc->sc_n_strokes == 0) && (n_fingers == 0))
+		return;
+
+	if (wsp_update_strokes(sc, fingers, n_fingers))
+		sc->sc_status.flags |= MOUSE_POSCHANGED;
+
+	switch(params->tp_type) {
+	case WSP_TRACKPAD_TYPE2:
+		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE2_BUTTON_DATA_OFFSET];
+		break;
+	case WSP_TRACKPAD_TYPE3:
+		sc->sc_ibtn = sc->sc_sensor_data[WSP_TYPE3_BUTTON_DATA_OFFSET];
+		break;
+	default:
+		break;
+	}
+	sc->sc_status.button = sc->sc_ibtn ? MOUSE_BUTTON1DOWN : 0;
+}
+
+/*
+ * Match a pressure-span against a stroke-component. If there is a
+ * match, update the component's state and return true.
+ */
+static boolean_t
+fg_match_stroke_component(fg_stroke_component_t *component,
+    const fg_pspan *pspan, atp_stroke_type stroke_type)
+{
+	int   delta_mickeys;
+	u_int min_pressure;
+
+	delta_mickeys = pspan->loc - component->loc;
+
+	if (abs(delta_mickeys) > (int)FG_MAX_DELTA_MICKEYS)
+		return (false); /* the finger span is too far out; no match */
+
+	component->loc = pspan->loc;
+
+	/*
+	 * A sudden and significant increase in a pspan's cumulative
+	 * pressure indicates the incidence of a new finger
+	 * contact. This usually revises the pspan's
+	 * centre-of-gravity, and hence the location of any/all
+	 * matching stroke component(s). But such a change should
+	 * *not* be interpreted as a movement.
+	 */
+	if (pspan->cum > ((3 * component->cum_pressure) >> 1))
+		delta_mickeys = 0;
+
+	component->cum_pressure = pspan->cum;
+	if (pspan->cum > component->max_cum_pressure)
+		component->max_cum_pressure = pspan->cum;
+
+	/*
+	 * Disregard the component's movement if its cumulative
+	 * pressure drops below a fraction of the maximum; this
+	 * fraction is determined based on the stroke's type.
+	 */
+	if (stroke_type == ATP_STROKE_TOUCH)
+		min_pressure = (3 * component->max_cum_pressure) >> 2;
+	else
+		min_pressure = component->max_cum_pressure >> 2;
+	if (component->cum_pressure < min_pressure)
+		delta_mickeys = 0;
+
+	component->delta_mickeys = delta_mickeys;
+	return (true);
+}
+
+static void
+fg_match_strokes_against_pspans(struct atp_softc *sc, atp_axis axis,
+    fg_pspan *pspans, u_int n_pspans, u_int repeat_count)
+{
+	atp_stroke_t *strokep;
+	u_int repeat_index = 0;
+	u_int i;
+
+	/* Determine the index of the multi-span. */
+	if (repeat_count) {
+		for (i = 0; i < n_pspans; i++) {
+			if (pspans[i].cum > pspans[repeat_index].cum)
+				repeat_index = i;
+		}
+	}
+
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+		if (strokep->components[axis].matched)
+			continue; /* skip matched components */
+
+		for (i = 0; i < n_pspans; i++) {
+			if (pspans[i].matched)
+				continue; /* skip matched pspans */
+
+			if (fg_match_stroke_component(
+			    &strokep->components[axis], &pspans[i],
+			    strokep->type)) {
+
+				/* There is a match. */
+				strokep->components[axis].matched = true;
+
+				/* Take care to repeat at the multi-span. */
+				if ((repeat_count > 0) && (i == repeat_index))
+					repeat_count--;
+				else
+					pspans[i].matched = true;
+
+				break; /* skip to the next strokep */
+			}
+		} /* loop over pspans */
+	} /* loop over strokes */
+}
+
+static boolean_t
+wsp_match_strokes_against_fingers(struct atp_softc *sc,
+    wsp_finger_t *fingers, u_int n_fingers)
+{
+	boolean_t movement = false;
+	atp_stroke_t *strokep;
+	u_int i;
+
+	/* reset the matched status for all strokes */
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry)
+		strokep->matched = false;
+
+	for (i = 0; i != n_fingers; i++) {
+		u_int least_distance_sq = WSP_MAX_ALLOWED_MATCH_DISTANCE_SQ;
+		atp_stroke_t *strokep_best = NULL;
+
+		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+			int instantaneous_dx;
+			int instantaneous_dy;
+			u_int d_squared;
+
+			if (strokep->matched)
+				continue;
+
+			instantaneous_dx = fingers[i].x - strokep->x;
+			instantaneous_dy = fingers[i].y - strokep->y;
+
+			/* skip strokes which are far away */
+			d_squared =
+			    (instantaneous_dx * instantaneous_dx) +
+			    (instantaneous_dy * instantaneous_dy);
+
+			if (d_squared < least_distance_sq) {
+				least_distance_sq = d_squared;
+				strokep_best = strokep;
+			}
+		}
+
+		strokep = strokep_best;
+
+		if (strokep != NULL) {
+			fingers[i].matched = true;
+
+			strokep->matched          = true;
+			strokep->instantaneous_dx = fingers[i].x - strokep->x;
+			strokep->instantaneous_dy = fingers[i].y - strokep->y;
+			strokep->x                = fingers[i].x;
+			strokep->y                = fingers[i].y;
+
+			atp_advance_stroke_state(sc, strokep, &movement);
+		}
+	}
+	return (movement);
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return true if any movement is detected.
+ */
+static boolean_t
+fg_update_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+	atp_stroke_t *strokep;
+	atp_stroke_t *strokep_next;
+	boolean_t movement = false;
+	u_int repeat_count = 0;
+	u_int i;
+	u_int j;
+
+	/* Reset X and Y components of all strokes as unmatched. */
+	TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+		strokep->components[X].matched = false;
+		strokep->components[Y].matched = false;
+	}
+
+	/*
+	 * Usually, the X and Y pspans come in pairs (the common case
+	 * being a single pair). It is possible, however, that
+	 * multiple contacts resolve to a single pspan along an
+	 * axis, as illustrated in the following:
+	 *
+	 *   F = finger-contact
+	 *
+	 *                pspan  pspan
+	 *        +-----------------------+
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *        |         .      .      |
+	 *  pspan |.........F......F      |
+	 *        |                       |
+	 *        |                       |
+	 *        |                       |
+	 *        +-----------------------+
+	 *
+	 *
+	 * The above case can be detected by a difference in the
+	 * number of X and Y pspans. When this happens, X and Y pspans
+	 * aren't easy to pair or match against strokes.
+	 *
+	 * When X and Y pspans differ in number, the axis with the
+	 * smaller number of pspans is regarded as having a repeating
+	 * pspan (or a multi-pspan)--in the above illustration, the
+	 * Y-axis has a repeating pspan. Our approach is to try to
+	 * match the multi-pspan repeatedly against strokes. The
+	 * difference between the number of X and Y pspans gives us a
+	 * crude repeat_count for matching multi-pspans--i.e. the
+	 * multi-pspan along the Y axis (above) has a repeat_count of 1.
+	 */
+	repeat_count = abs(n_xpspans - n_ypspans);
+
+	fg_match_strokes_against_pspans(sc, X, pspans_x, n_xpspans,
+	    (((repeat_count != 0) && ((n_xpspans < n_ypspans))) ?
+		repeat_count : 0));
+	fg_match_strokes_against_pspans(sc, Y, pspans_y, n_ypspans,
+	    (((repeat_count != 0) && (n_ypspans < n_xpspans)) ?
+		repeat_count : 0));
+
+	/* Update the state of strokes based on the above pspan matches. */
+	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+
+		if (strokep->components[X].matched &&
+		    strokep->components[Y].matched) {
+			strokep->matched = true;
+			strokep->instantaneous_dx =
+			    strokep->components[X].delta_mickeys;
+			strokep->instantaneous_dy =
+			    strokep->components[Y].delta_mickeys;
+			atp_advance_stroke_state(sc, strokep, &movement);
+		} else {
+			/*
+			 * At least one component of this stroke
+			 * didn't match against current pspans;
+			 * terminate it.
+			 */
+			atp_terminate_stroke(sc, strokep);
+		}
+	}
+
+	/* Add new strokes for pairs of unmatched pspans */
+	for (i = 0; i < n_xpspans; i++) {
+		if (pspans_x[i].matched == false) break;
+	}
+	for (j = 0; j < n_ypspans; j++) {
+		if (pspans_y[j].matched == false) break;
+	}
+	if ((i < n_xpspans) && (j < n_ypspans)) {
+#ifdef USB_DEBUG
+		if (atp_debug >= ATP_LLEVEL_INFO) {
+			printf("unmatched pspans:");
+			for (; i < n_xpspans; i++) {
+				if (pspans_x[i].matched)
+					continue;
+				printf(" X:[loc:%u,cum:%u]",
+				    pspans_x[i].loc, pspans_x[i].cum);
+			}
+			for (; j < n_ypspans; j++) {
+				if (pspans_y[j].matched)
+					continue;
+				printf(" Y:[loc:%u,cum:%u]",
+				    pspans_y[j].loc, pspans_y[j].cum);
+			}
+			printf("\n");
+		}
+#endif /* USB_DEBUG */
+		if ((n_xpspans == 1) && (n_ypspans == 1))
+			/* The common case of a single pair of new pspans. */
+			fg_add_stroke(sc, &pspans_x[0], &pspans_y[0]);
+		else
+			fg_add_new_strokes(sc, pspans_x, n_xpspans,
+			    pspans_y, n_ypspans);
+	}
+
+#ifdef USB_DEBUG
+	if (atp_debug >= ATP_LLEVEL_INFO) {
+		TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+			printf(" %s%clc:%u,dm:%d,cum:%d,max:%d,%c"
+			    ",%clc:%u,dm:%d,cum:%d,max:%d,%c",
+			    (strokep->flags & ATSF_ZOMBIE) ? "zomb:" : "",
+			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    strokep->components[X].loc,
+			    strokep->components[X].delta_mickeys,
+			    strokep->components[X].cum_pressure,
+			    strokep->components[X].max_cum_pressure,
+			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>',
+			    (strokep->type == ATP_STROKE_TOUCH) ? '[' : '<',
+			    strokep->components[Y].loc,
+			    strokep->components[Y].delta_mickeys,
+			    strokep->components[Y].cum_pressure,
+			    strokep->components[Y].max_cum_pressure,
+			    (strokep->type == ATP_STROKE_TOUCH) ? ']' : '>');
+		}
+		if (TAILQ_FIRST(&sc->sc_stroke_used) != NULL)
+			printf("\n");
+	}
+#endif /* USB_DEBUG */
+	return (movement);
+}
+
+/*
+ * Update strokes by matching against current pressure-spans.
+ * Return true if any movement is detected.
+ */
+static boolean_t
+wsp_update_strokes(struct atp_softc *sc, wsp_finger_t *fingers, u_int n_fingers)
+{
+	boolean_t movement = false;
+	atp_stroke_t *strokep_next;
+	atp_stroke_t *strokep;
+	u_int i;
+
+	if (sc->sc_n_strokes > 0) {
+		movement = wsp_match_strokes_against_fingers(
+		    sc, fingers, n_fingers);
+
+		/* handle zombie strokes */
+		TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+			if (strokep->matched)
+				continue;
+			atp_terminate_stroke(sc, strokep);
+		}
+	}
+
+	/* initialize unmatched fingers as strokes */
+	for (i = 0; i != n_fingers; i++) {
+		if (fingers[i].matched)
+			continue;
+
+		wsp_add_stroke(sc, fingers + i);
+	}
+	return (movement);
+}
+
+/* Initialize a stroke using a pressure-span. */
+static void
+fg_add_stroke(struct atp_softc *sc, const fg_pspan *pspan_x,
+    const fg_pspan *pspan_y)
+{
+	atp_stroke_t *strokep;
+
+	strokep = atp_alloc_stroke(sc);
+	if (strokep == NULL)
+		return;
+
+	/*
+	 * Strokes begin as potential touches. If a stroke survives
+	 * longer than a threshold, or if it records significant
+	 * cumulative movement, then it is considered a 'slide'.
+	 */
+	strokep->type    = ATP_STROKE_TOUCH;
+	strokep->matched = false;
+	microtime(&strokep->ctime);
+	strokep->age     = 1;		/* number of interrupts */
+	strokep->x       = pspan_x->loc;
+	strokep->y       = pspan_y->loc;
+
+	strokep->components[X].loc              = pspan_x->loc;
+	strokep->components[X].cum_pressure     = pspan_x->cum;
+	strokep->components[X].max_cum_pressure = pspan_x->cum;
+	strokep->components[X].matched          = true;
+
+	strokep->components[Y].loc              = pspan_y->loc;
+	strokep->components[Y].cum_pressure     = pspan_y->cum;
+	strokep->components[Y].max_cum_pressure = pspan_y->cum;
+	strokep->components[Y].matched          = true;
+
+	if (sc->sc_n_strokes > 1) {
+		/* Reset double-tap-n-drag if we have more than one strokes. */
+		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+	}
+
+	DPRINTFN(ATP_LLEVEL_INFO, "[%u,%u], time: %u,%ld\n",
+	    strokep->components[X].loc,
+	    strokep->components[Y].loc,
+	    (u_int)strokep->ctime.tv_sec,
+	    (unsigned long int)strokep->ctime.tv_usec);
+}
+
+static void
+fg_add_new_strokes(struct atp_softc *sc, fg_pspan *pspans_x,
+    u_int n_xpspans, fg_pspan *pspans_y, u_int n_ypspans)
+{
+	fg_pspan spans[2][FG_MAX_PSPANS_PER_AXIS];
+	u_int nspans[2];
+	u_int i;
+	u_int j;
+
+	/* Copy unmatched pspans into the local arrays. */
+	for (i = 0, nspans[X] = 0; i < n_xpspans; i++) {
+		if (pspans_x[i].matched == false) {
+			spans[X][nspans[X]] = pspans_x[i];
+			nspans[X]++;
+		}
+	}
+	for (j = 0, nspans[Y] = 0; j < n_ypspans; j++) {
+		if (pspans_y[j].matched == false) {
+			spans[Y][nspans[Y]] = pspans_y[j];
+			nspans[Y]++;
+		}
+	}
+
+	if (nspans[X] == nspans[Y]) {
+		/* Create new strokes from pairs of unmatched pspans */
+		for (i = 0, j = 0; (i < nspans[X]) && (j < nspans[Y]); i++, j++)
+			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+	} else {
+		u_int    cum = 0;
+		atp_axis repeat_axis;      /* axis with multi-pspans */
+		u_int    repeat_count;     /* repeat count for the multi-pspan*/
+		u_int    repeat_index = 0; /* index of the multi-span */
+
+		repeat_axis  = (nspans[X] > nspans[Y]) ? Y : X;
+		repeat_count = abs(nspans[X] - nspans[Y]);
+		for (i = 0; i < nspans[repeat_axis]; i++) {
+			if (spans[repeat_axis][i].cum > cum) {
+				repeat_index = i;
+				cum = spans[repeat_axis][i].cum;
+			}
+		}
+
+		/* Create new strokes from pairs of unmatched pspans */
+		i = 0, j = 0;
+		for (; (i < nspans[X]) && (j < nspans[Y]); i++, j++) {
+			fg_add_stroke(sc, &spans[X][i], &spans[Y][j]);
+
+			/* Take care to repeat at the multi-pspan. */
+			if (repeat_count > 0) {
+				if ((repeat_axis == X) &&
+				    (repeat_index == i)) {
+					i--; /* counter loop increment */
+					repeat_count--;
+				} else if ((repeat_axis == Y) &&
+				    (repeat_index == j)) {
+					j--; /* counter loop increment */
+					repeat_count--;
+				}
+			}
+		}
+	}
+}
+
+/* Initialize a stroke from an unmatched finger. */
+static void
+wsp_add_stroke(struct atp_softc *sc, const wsp_finger_t *fingerp)
+{
+	atp_stroke_t *strokep;
+
+	strokep = atp_alloc_stroke(sc);
+	if (strokep == NULL)
+		return;
+
+	/*
+	 * Strokes begin as potential touches. If a stroke survives
+	 * longer than a threshold, or if it records significant
+	 * cumulative movement, then it is considered a 'slide'.
+	 */
+	strokep->type    = ATP_STROKE_TOUCH;
+	strokep->matched = true;
+	microtime(&strokep->ctime);
+	strokep->age = 1;	/* number of interrupts */
+	strokep->x = fingerp->x;
+	strokep->y = fingerp->y;
+
+	/* Reset double-tap-n-drag if we have more than one strokes. */
+	if (sc->sc_n_strokes > 1)
+		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "[%d,%d]\n", strokep->x, strokep->y);
+}
+
+static void
+atp_advance_stroke_state(struct atp_softc *sc, atp_stroke_t *strokep,
+    boolean_t *movementp)
+{
+	/* Revitalize stroke if it had previously been marked as a zombie. */
+	if (strokep->flags & ATSF_ZOMBIE)
+		strokep->flags &= ~ATSF_ZOMBIE;
+
+	strokep->age++;
+	if (strokep->age <= atp_stroke_maturity_threshold) {
+		/* Avoid noise from immature strokes. */
+		strokep->instantaneous_dx = 0;
+		strokep->instantaneous_dy = 0;
+	}
+
+	if (atp_compute_stroke_movement(strokep))
+		*movementp = true;
+
+	if (strokep->type != ATP_STROKE_TOUCH)
+		return;
+
+	/* Convert touch strokes to slides upon detecting movement or age. */
+	if ((abs(strokep->cum_movement_x) > atp_slide_min_movement) ||
+	    (abs(strokep->cum_movement_y) > atp_slide_min_movement))
+		atp_convert_to_slide(sc, strokep);
+	else {
+		/* Compute the stroke's age. */
+		struct timeval tdiff;
+		getmicrotime(&tdiff);
+		if (timevalcmp(&tdiff, &strokep->ctime, >)) {
+			timevalsub(&tdiff, &strokep->ctime);
+
+			if ((tdiff.tv_sec > (atp_touch_timeout / 1000000)) ||
+			    ((tdiff.tv_sec == (atp_touch_timeout / 1000000)) &&
+			     (tdiff.tv_usec >= (atp_touch_timeout % 1000000))))
+				atp_convert_to_slide(sc, strokep);
+		}
+	}
+}
+
+static boolean_t
+atp_stroke_has_small_movement(const atp_stroke_t *strokep)
+{
+	return (((u_int)abs(strokep->instantaneous_dx) <=
+		 atp_small_movement_threshold) &&
+		((u_int)abs(strokep->instantaneous_dy) <=
+		 atp_small_movement_threshold));
+}
+
+/*
+ * Accumulate instantaneous changes into the stroke's 'pending' bucket; if
+ * the aggregate exceeds the small_movement_threshold, then retain
+ * instantaneous changes for later.
+ */
+static void
+atp_update_pending_mickeys(atp_stroke_t *strokep)
+{
+	/* accumulate instantaneous movement */
+	strokep->pending_dx += strokep->instantaneous_dx;
+	strokep->pending_dy += strokep->instantaneous_dy;
+
+#define UPDATE_INSTANTANEOUS_AND_PENDING(I, P)                          \
+	if (abs((P)) <= atp_small_movement_threshold)                   \
+		(I) = 0; /* clobber small movement */                   \
+	else {                                                          \
+		if ((I) > 0) {                                          \
+			/*                                              \
+			 * Round up instantaneous movement to the nearest \
+			 * ceiling. This helps preserve small mickey    \
+			 * movements from being lost in following scaling \
+			 * operation.                                   \
+			 */                                             \
+			(I) = (((I) + (atp_mickeys_scale_factor - 1)) / \
+			       atp_mickeys_scale_factor) *              \
+			      atp_mickeys_scale_factor;                 \
+									\
+			/*                                              \
+			 * Deduct the rounded mickeys from pending mickeys. \
+			 * Note: we multiply by 2 to offset the previous \
+			 * accumulation of instantaneous movement into  \
+			 * pending.                                     \
+			 */                                             \
+			(P) -= ((I) << 1);                              \
+									\
+			/* truncate pending to 0 if it becomes negative. */ \
+			(P) = imax((P), 0);                             \
+		} else {                                                \
+			/*                                              \
+			 * Round down instantaneous movement to the nearest \
+			 * ceiling. This helps preserve small mickey    \
+			 * movements from being lost in following scaling \
+			 * operation.                                   \
+			 */                                             \
+			(I) = (((I) - (atp_mickeys_scale_factor - 1)) / \
+			       atp_mickeys_scale_factor) *              \
+			      atp_mickeys_scale_factor;                 \
+									\
+			/*                                              \
+			 * Deduct the rounded mickeys from pending mickeys. \
+			 * Note: we multiply by 2 to offset the previous \
+			 * accumulation of instantaneous movement into  \
+			 * pending.                                     \
+			 */                                             \
+			(P) -= ((I) << 1);                              \
+									\
+			/* truncate pending to 0 if it becomes positive. */ \
+			(P) = imin((P), 0);                             \
+		}                                                       \
+	}
+
+	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dx,
+	    strokep->pending_dx);
+	UPDATE_INSTANTANEOUS_AND_PENDING(strokep->instantaneous_dy,
+	    strokep->pending_dy);
+}
+
+/*
+ * Compute a smoothened value for the stroke's movement from
+ * instantaneous changes in the X and Y components.
+ */
+static boolean_t
+atp_compute_stroke_movement(atp_stroke_t *strokep)
+{
+	/*
+	 * Short movements are added first to the 'pending' bucket,
+	 * and then acted upon only when their aggregate exceeds a
+	 * threshold. This has the effect of filtering away movement
+	 * noise.
+	 */
+	if (atp_stroke_has_small_movement(strokep))
+		atp_update_pending_mickeys(strokep);
+	else {                /* large movement */
+		/* clear away any pending mickeys if there are large movements*/
+		strokep->pending_dx = 0;
+		strokep->pending_dy = 0;
+	}
+
+	/* scale movement */
+	strokep->movement_dx = (strokep->instantaneous_dx) /
+	    (int)atp_mickeys_scale_factor;
+	strokep->movement_dy = (strokep->instantaneous_dy) /
+	    (int)atp_mickeys_scale_factor;
+
+	if ((abs(strokep->instantaneous_dx) >= ATP_FAST_MOVEMENT_TRESHOLD) ||
+	    (abs(strokep->instantaneous_dy) >= ATP_FAST_MOVEMENT_TRESHOLD)) {
+		strokep->movement_dx <<= 1;
+		strokep->movement_dy <<= 1;
+	}
+
+	strokep->cum_movement_x += strokep->movement_dx;
+	strokep->cum_movement_y += strokep->movement_dy;
+
+	return ((strokep->movement_dx != 0) || (strokep->movement_dy != 0));
+}
+
+/*
+ * Terminate a stroke. Aside from immature strokes, a slide or touch is
+ * retained as a zombies so as to reap all their termination siblings
+ * together; this helps establish the number of fingers involved at the
+ * end of a multi-touch gesture.
+ */
+static void
+atp_terminate_stroke(struct atp_softc *sc, atp_stroke_t *strokep)
+{
+	if (strokep->flags & ATSF_ZOMBIE)
+		return;
+
+	/* Drop immature strokes rightaway. */
+	if (strokep->age <= atp_stroke_maturity_threshold) {
+		atp_free_stroke(sc, strokep);
+		return;
+	}
+
+	strokep->flags |= ATSF_ZOMBIE;
+	sc->sc_state |= ATP_ZOMBIES_EXIST;
+
+	callout_reset(&sc->sc_callout, ATP_ZOMBIE_STROKE_REAP_INTERVAL,
+	    atp_reap_sibling_zombies, sc);
+
+	/*
+	 * Reset the double-click-n-drag at the termination of any
+	 * slide stroke.
+	 */
+	if (strokep->type == ATP_STROKE_SLIDE)
+		sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+}
+
+static boolean_t
+atp_is_horizontal_scroll(const atp_stroke_t *strokep)
+{
+	if (abs(strokep->cum_movement_x) < atp_slide_min_movement)
+		return (false);
+	if (strokep->cum_movement_y == 0)
+		return (true);
+	return (abs(strokep->cum_movement_x / strokep->cum_movement_y) >= 4);
+}
+
+static boolean_t
+atp_is_vertical_scroll(const atp_stroke_t *strokep)
+{
+	if (abs(strokep->cum_movement_y) < atp_slide_min_movement)
+		return (false);
+	if (strokep->cum_movement_x == 0)
+		return (true);
+	return (abs(strokep->cum_movement_y / strokep->cum_movement_x) >= 4);
+}
+
+static void
+atp_reap_sibling_zombies(void *arg)
+{
+	struct atp_softc *sc = (struct atp_softc *)arg;
+	u_int8_t n_touches_reaped = 0;
+	u_int8_t n_slides_reaped = 0;
+	u_int8_t n_horizontal_scrolls = 0;
+	u_int8_t n_vertical_scrolls = 0;
+	int horizontal_scroll = 0;
+	int vertical_scroll = 0;
+	atp_stroke_t *strokep;
+	atp_stroke_t *strokep_next;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "\n");
+
+	TAILQ_FOREACH_SAFE(strokep, &sc->sc_stroke_used, entry, strokep_next) {
+		if ((strokep->flags & ATSF_ZOMBIE) == 0)
+			continue;
+
+		if (strokep->type == ATP_STROKE_TOUCH) {
+			n_touches_reaped++;
+		} else {
+			n_slides_reaped++;
+
+			if (atp_is_horizontal_scroll(strokep)) {
+				n_horizontal_scrolls++;
+				horizontal_scroll += strokep->cum_movement_x;
+			} else if (atp_is_vertical_scroll(strokep)) {
+				n_vertical_scrolls++;
+				vertical_scroll +=  strokep->cum_movement_y;
+			}
+		}
+
+		atp_free_stroke(sc, strokep);
+	}
+
+	DPRINTFN(ATP_LLEVEL_INFO, "reaped %u zombies\n",
+	    n_touches_reaped + n_slides_reaped);
+	sc->sc_state &= ~ATP_ZOMBIES_EXIST;
+
+	/* No further processing necessary if physical button is depressed. */
+	if (sc->sc_ibtn != 0)
+		return;
+
+	if ((n_touches_reaped == 0) && (n_slides_reaped == 0))
+		return;
+
+	/* Add a pair of virtual button events (button-down and button-up) if
+	 * the physical button isn't pressed. */
+	if (n_touches_reaped != 0) {
+		if (n_touches_reaped < atp_tap_minimum)
+			return;
+
+		switch (n_touches_reaped) {
+		case 1:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
+			microtime(&sc->sc_touch_reap_time); /* remember this time */
+			break;
+		case 2:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
+			break;
+		case 3:
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
+			break;
+		default:
+			/* we handle taps of only up to 3 fingers */
+			return;
+		}
+		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+
+	} else if ((n_slides_reaped == 2) && (n_horizontal_scrolls == 2)) {
+		if (horizontal_scroll < 0)
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON4DOWN);
+		else
+			atp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON5DOWN);
+		atp_add_to_queue(sc, 0, 0, 0, 0); /* button release */
+	}
+}
+
+/* Switch a given touch stroke to being a slide. */
+static void
+atp_convert_to_slide(struct atp_softc *sc, atp_stroke_t *strokep)
+{
+	strokep->type = ATP_STROKE_SLIDE;
+
+	/* Are we at the beginning of a double-click-n-drag? */
+	if ((sc->sc_n_strokes == 1) &&
+	    ((sc->sc_state & ATP_ZOMBIES_EXIST) == 0) &&
+	    timevalcmp(&strokep->ctime, &sc->sc_touch_reap_time, >)) {
+		struct timeval delta;
+		struct timeval window = {
+			atp_double_tap_threshold / 1000000,
+			atp_double_tap_threshold % 1000000
+		};
+
+		delta = strokep->ctime;
+		timevalsub(&delta, &sc->sc_touch_reap_time);
+		if (timevalcmp(&delta, &window, <=))
+			sc->sc_state |= ATP_DOUBLE_TAP_DRAG;
+	}
+}
+
+static void
+atp_reset_buf(struct atp_softc *sc)
+{
+	/* reset read queue */
+	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+static void
+atp_add_to_queue(struct atp_softc *sc, int dx, int dy, int dz,
+    uint32_t buttons_in)
+{
+	uint32_t buttons_out;
+	uint8_t  buf[8];
+
+	dx = imin(dx,  254); dx = imax(dx, -256);
+	dy = imin(dy,  254); dy = imax(dy, -256);
+	dz = imin(dz,  126); dz = imax(dz, -128);
+
+	buttons_out = MOUSE_MSC_BUTTONS;
+	if (buttons_in & MOUSE_BUTTON1DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
+	else if (buttons_in & MOUSE_BUTTON2DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
+	else if (buttons_in & MOUSE_BUTTON3DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "dx=%d, dy=%d, buttons=%x\n",
+	    dx, dy, buttons_out);
+
+	/* Encode the mouse data in standard format; refer to mouse(4) */
+	buf[0] = sc->sc_mode.syncmask[1];
+	buf[0] |= buttons_out;
+	buf[1] = dx >> 1;
+	buf[2] = dy >> 1;
+	buf[3] = dx - (dx >> 1);
+	buf[4] = dy - (dy >> 1);
+	/* Encode extra bytes for level 1 */
+	if (sc->sc_mode.level == 1) {
+		buf[5] = dz >> 1;
+		buf[6] = dz - (dz >> 1);
+		buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
+	}
+
+	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+	    sc->sc_mode.packetsize, 1);
+}
+
+static int
+atp_probe(device_t self)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(self);
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+
+	if (uaa->info.bInterfaceClass != UICLASS_HID)
+		return (ENXIO);
+	/*
+	 * Note: for some reason, the check
+	 * (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) doesn't hold true
+	 * for wellspring trackpads, so we've removed it from the common path.
+	 */
+
+	if ((usbd_lookup_id_by_uaa(fg_devs, sizeof(fg_devs), uaa)) == 0)
+		return ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) ?
+			0 : ENXIO);
+
+	if ((usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa)) == 0)
+		if (uaa->info.bIfaceIndex == WELLSPRING_INTERFACE_INDEX)
+			return (0);
+
+	return (ENXIO);
+}
+
+static int
+atp_attach(device_t dev)
+{
+	struct atp_softc      *sc  = device_get_softc(dev);
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	usb_error_t            err;
+	void *descriptor_ptr = NULL;
+	uint16_t descriptor_len;
+	unsigned long di;
+
+	DPRINTFN(ATP_LLEVEL_INFO, "sc=%p\n", sc);
+
+	sc->sc_dev        = dev;
+	sc->sc_usb_device = uaa->device;
+
+	/* Get HID descriptor */
+	if (usbd_req_get_hid_desc(uaa->device, NULL, &descriptor_ptr,
+	    &descriptor_len, M_TEMP, uaa->info.bIfaceIndex) !=
+	    USB_ERR_NORMAL_COMPLETION)
+		return (ENXIO);
+
+	/* Get HID report descriptor length */
+	sc->sc_expected_sensor_data_len = hid_report_size(descriptor_ptr,
+	    descriptor_len, hid_input, NULL);
+	free(descriptor_ptr, M_TEMP);
+
+	if ((sc->sc_expected_sensor_data_len <= 0) ||
+	    (sc->sc_expected_sensor_data_len > ATP_SENSOR_DATA_BUF_MAX)) {
+		DPRINTF("atp_attach: datalength invalid or too large: %d\n",
+			sc->sc_expected_sensor_data_len);
+		return (ENXIO);
+	}
+
+	/*
+	 * By default the touchpad behaves like an HID device, sending
+	 * packets with reportID = 2. Such reports contain only
+	 * limited information--they encode movement deltas and button
+	 * events,--but do not include data from the pressure
+	 * sensors. The device input mode can be switched from HID
+	 * reports to raw sensor data using vendor-specific USB
+	 * control commands.
+	 */
+	if ((err = atp_set_device_mode(sc, RAW_SENSOR_MODE)) != 0) {
+		DPRINTF("failed to set mode to 'RAW_SENSOR' (%d)\n", err);
+		return (ENXIO);
+	}
+
+	mtx_init(&sc->sc_mutex, "atpmtx", NULL, MTX_DEF | MTX_RECURSE);
+
+	di = USB_GET_DRIVER_INFO(uaa);
+
+	sc->sc_family = DECODE_FAMILY_FROM_DRIVER_INFO(di);
+
+	switch(sc->sc_family) {
+	case TRACKPAD_FAMILY_FOUNTAIN_GEYSER:
+		sc->sc_params =
+		    &fg_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+		sc->sensor_data_interpreter = fg_interpret_sensor_data;
+		break;
+	case TRACKPAD_FAMILY_WELLSPRING:
+		sc->sc_params =
+		    &wsp_dev_params[DECODE_PRODUCT_FROM_DRIVER_INFO(di)];
+		sc->sensor_data_interpreter = wsp_interpret_sensor_data;
+		break;
+	default:
+		goto detach;
+	}
+
+	err = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, atp_xfer_config,
+	    ATP_N_TRANSFER, sc, &sc->sc_mutex);
+	if (err) {
+		DPRINTF("error=%s\n", usbd_errstr(err));
+		goto detach;
+	}
+
+	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
+	    &atp_fifo_methods, &sc->sc_fifo,
+	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+	    UID_ROOT, GID_OPERATOR, 0644)) {
+		goto detach;
+	}
+
+	device_set_usb_desc(dev);
+
+	sc->sc_hw.buttons       = 3;
+	sc->sc_hw.iftype        = MOUSE_IF_USB;
+	sc->sc_hw.type          = MOUSE_PAD;
+	sc->sc_hw.model         = MOUSE_MODEL_GENERIC;
+	sc->sc_hw.hwid          = 0;
+	sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
+	sc->sc_mode.rate        = -1;
+	sc->sc_mode.resolution  = MOUSE_RES_UNKNOWN;
+	sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
+	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+	sc->sc_mode.accelfactor = 0;
+	sc->sc_mode.level       = 0;
+
+	sc->sc_state            = 0;
+	sc->sc_ibtn             = 0;
+
+	callout_init_mtx(&sc->sc_callout, &sc->sc_mutex, 0);
+
+	return (0);
+
+detach:
+	atp_detach(dev);
+	return (ENOMEM);
+}
+
+static int
+atp_detach(device_t dev)
+{
+	struct atp_softc *sc;
+
+	sc = device_get_softc(dev);
+	atp_set_device_mode(sc, HID_MODE);
+
+	mtx_lock(&sc->sc_mutex);
+	callout_drain(&sc->sc_callout);
+	if (sc->sc_state & ATP_ENABLED)
+		atp_disable(sc);
+	mtx_unlock(&sc->sc_mutex);
+
+	usb_fifo_detach(&sc->sc_fifo);
+
+	usbd_transfer_unsetup(sc->sc_xfer, ATP_N_TRANSFER);
+
+	mtx_destroy(&sc->sc_mutex);
+
+	return (0);
+}
+
+static void
+atp_intr(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct atp_softc      *sc = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	int len;
+
+	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_out(pc, 0, sc->sc_sensor_data, len);
+		if (len < sc->sc_expected_sensor_data_len) {
+			/* make sure we don't process old data */
+			memset(sc->sc_sensor_data + len, 0,
+			    sc->sc_expected_sensor_data_len - len);
+		}
+
+		sc->sc_status.flags &= ~(MOUSE_STDBUTTONSCHANGED |
+		    MOUSE_POSCHANGED);
+		sc->sc_status.obutton = sc->sc_status.button;
+
+		(sc->sensor_data_interpreter)(sc, len);
+
+		if (sc->sc_status.button != 0) {
+			/* Reset DOUBLE_TAP_N_DRAG if the button is pressed. */
+			sc->sc_state &= ~ATP_DOUBLE_TAP_DRAG;
+		} else if (sc->sc_state & ATP_DOUBLE_TAP_DRAG) {
+			/* Assume a button-press with DOUBLE_TAP_N_DRAG. */
+			sc->sc_status.button = MOUSE_BUTTON1DOWN;
+		}
+
+		sc->sc_status.flags |=
+		    sc->sc_status.button ^ sc->sc_status.obutton;
+		if (sc->sc_status.flags & MOUSE_STDBUTTONSCHANGED) {
+		    DPRINTFN(ATP_LLEVEL_INFO, "button %s\n",
+			((sc->sc_status.button & MOUSE_BUTTON1DOWN) ?
+			"pressed" : "released"));
+		}
+
+		if (sc->sc_status.flags & (MOUSE_POSCHANGED |
+		    MOUSE_STDBUTTONSCHANGED)) {
+
+			atp_stroke_t *strokep;
+			u_int8_t n_movements = 0;
+			int dx = 0;
+			int dy = 0;
+			int dz = 0;
+
+			TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+				if (strokep->flags & ATSF_ZOMBIE)
+					continue;
+
+				dx += strokep->movement_dx;
+				dy += strokep->movement_dy;
+				if (strokep->movement_dx ||
+				    strokep->movement_dy)
+					n_movements++;
+			}
+
+			/* average movement if multiple strokes record motion.*/
+			if (n_movements > 1) {
+				dx /= (int)n_movements;
+				dy /= (int)n_movements;
+			}
+
+			/* detect multi-finger vertical scrolls */
+			if (n_movements >= 2) {
+				boolean_t all_vertical_scrolls = true;
+				TAILQ_FOREACH(strokep, &sc->sc_stroke_used, entry) {
+					if (strokep->flags & ATSF_ZOMBIE)
+						continue;
+
+					if (!atp_is_vertical_scroll(strokep))
+						all_vertical_scrolls = false;
+				}
+				if (all_vertical_scrolls) {
+					dz = dy;
+					dy = dx = 0;
+				}
+			}
+
+			sc->sc_status.dx += dx;
+			sc->sc_status.dy += dy;
+			sc->sc_status.dz += dz;
+			atp_add_to_queue(sc, dx, -dy, -dz, sc->sc_status.button);
+		}
+
+	case USB_ST_SETUP:
+	tr_setup:
+		/* check if we can put more data into the FIFO */
+		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+			usbd_xfer_set_frame_len(xfer, 0,
+			    sc->sc_expected_sensor_data_len);
+			usbd_transfer_submit(xfer);
+		}
+		break;
+
+	default:                        /* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		break;
+	}
+}
+
+static void
+atp_start_read(struct usb_fifo *fifo)
+{
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+	int rate;
+
+	/* Check if we should override the default polling interval */
+	rate = sc->sc_pollrate;
+	/* Range check rate */
+	if (rate > 1000)
+		rate = 1000;
+	/* Check for set rate */
+	if ((rate > 0) && (sc->sc_xfer[ATP_INTR_DT] != NULL)) {
+		/* Stop current transfer, if any */
+		usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
+		/* Set new interval */
+		usbd_xfer_set_interval(sc->sc_xfer[ATP_INTR_DT], 1000 / rate);
+		/* Only set pollrate once */
+		sc->sc_pollrate = 0;
+	}
+
+	usbd_transfer_start(sc->sc_xfer[ATP_INTR_DT]);
+}
+
+static void
+atp_stop_read(struct usb_fifo *fifo)
+{
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+	usbd_transfer_stop(sc->sc_xfer[ATP_INTR_DT]);
+}
+
+static int
+atp_open(struct usb_fifo *fifo, int fflags)
+{
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+
+	/* check for duplicate open, should not happen */
+	if (sc->sc_fflags & fflags)
+		return (EBUSY);
+
+	/* check for first open */
+	if (sc->sc_fflags == 0) {
+		int rc;
+		if ((rc = atp_enable(sc)) != 0)
+			return (rc);
+	}
+
+	if (fflags & FREAD) {
+		if (usb_fifo_alloc_buffer(fifo,
+		    ATP_FIFO_BUF_SIZE, ATP_FIFO_QUEUE_MAXLEN)) {
+			return (ENOMEM);
+		}
+	}
+
+	sc->sc_fflags |= (fflags & (FREAD | FWRITE));
+	return (0);
+}
+
+static void
+atp_close(struct usb_fifo *fifo, int fflags)
+{
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+	if (fflags & FREAD)
+		usb_fifo_free_buffer(fifo);
+
+	sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+	if (sc->sc_fflags == 0) {
+		atp_disable(sc);
+	}
+}
+
+static int
+atp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+	struct atp_softc *sc = usb_fifo_softc(fifo);
+	mousemode_t mode;
+	int error = 0;
+
+	mtx_lock(&sc->sc_mutex);
+
+	switch(cmd) {
+	case MOUSE_GETHWINFO:
+		*(mousehw_t *)addr = sc->sc_hw;
+		break;
+	case MOUSE_GETMODE:
+		*(mousemode_t *)addr = sc->sc_mode;
+		break;
+	case MOUSE_SETMODE:
+		mode = *(mousemode_t *)addr;
+
+		if (mode.level == -1)
+			/* Don't change the current setting */
+			;
+		else if ((mode.level < 0) || (mode.level > 1)) {
+			error = EINVAL;
+			break;
+		}
+		sc->sc_mode.level = mode.level;
+		sc->sc_pollrate   = mode.rate;
+		sc->sc_hw.buttons = 3;
+
+		if (sc->sc_mode.level == 0) {
+			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		atp_reset_buf(sc);
+		break;
+	case MOUSE_GETLEVEL:
+		*(int *)addr = sc->sc_mode.level;
+		break;
+	case MOUSE_SETLEVEL:
+		if ((*(int *)addr < 0) || (*(int *)addr > 1)) {
+			error = EINVAL;
+			break;
+		}
+		sc->sc_mode.level = *(int *)addr;
+		sc->sc_hw.buttons = 3;
+
+		if (sc->sc_mode.level == 0) {
+			sc->sc_mode.protocol    = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize  = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			sc->sc_mode.protocol    = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize  = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		atp_reset_buf(sc);
+		break;
+	case MOUSE_GETSTATUS: {
+		mousestatus_t *status = (mousestatus_t *)addr;
+
+		*status = sc->sc_status;
+		sc->sc_status.obutton = sc->sc_status.button;
+		sc->sc_status.button  = 0;
+		sc->sc_status.dx      = 0;
+		sc->sc_status.dy      = 0;
+		sc->sc_status.dz      = 0;
+
+		if (status->dx || status->dy || status->dz)
+			status->flags |= MOUSE_POSCHANGED;
+		if (status->button != status->obutton)
+			status->flags |= MOUSE_BUTTONSCHANGED;
+		break;
+	}
+
+	default:
+		error = ENOTTY;
+		break;
+	}
+
+	mtx_unlock(&sc->sc_mutex);
+	return (error);
+}
+
+static int
+atp_sysctl_scale_factor_handler(SYSCTL_HANDLER_ARGS)
+{
+	int error;
+	u_int tmp;
+
+	tmp = atp_mickeys_scale_factor;
+	error = sysctl_handle_int(oidp, &tmp, 0, req);
+	if (error != 0 || req->newptr == NULL)
+		return (error);
+
+	if (tmp == atp_mickeys_scale_factor)
+		return (0);     /* no change */
+	if ((tmp == 0) || (tmp > (10 * ATP_SCALE_FACTOR)))
+		return (EINVAL);
+
+	atp_mickeys_scale_factor = tmp;
+	DPRINTFN(ATP_LLEVEL_INFO, "%s: resetting mickeys_scale_factor to %u\n",
+	    ATP_DRIVER_NAME, tmp);
+
+	return (0);
+}
+
+static devclass_t atp_devclass;
+
+static device_method_t atp_methods[] = {
+	DEVMETHOD(device_probe,  atp_probe),
+	DEVMETHOD(device_attach, atp_attach),
+	DEVMETHOD(device_detach, atp_detach),
+
+	DEVMETHOD_END
+};
+
+static driver_t atp_driver = {
+	.name    = ATP_DRIVER_NAME,
+	.methods = atp_methods,
+	.size    = sizeof(struct atp_softc)
+};
+
+DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0);
+MODULE_DEPEND(atp, usb, 1, 1, 1);
+MODULE_VERSION(atp, 1);
+USB_PNP_HOST_INFO(fg_devs);
+USB_PNP_HOST_INFO(wsp_devs);
diff --git a/freebsd/sys/dev/usb/input/uep.c b/freebsd/sys/dev/usb/input/uep.c
new file mode 100644
index 0000000..9344197
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/uep.c
@@ -0,0 +1,446 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * Copyright 2010, Gleb Smirnoff <glebius at FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ */
+
+/*
+ *  http://www.eeti.com.tw/pdf/Software%20Programming%20Guide_v2.0.pdf
+ */
+
+#include <rtems/bsd/sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/conf.h>
+#include <sys/kernel.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include <rtems/bsd/local/usbdevs.h>
+
+#include <sys/ioccom.h>
+#include <sys/fcntl.h>
+#include <sys/tty.h>
+
+#define USB_DEBUG_VAR uep_debug
+#include <dev/usb/usb_debug.h>
+
+#ifdef USB_DEBUG
+static int uep_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uep, CTLFLAG_RW, 0, "USB uep");
+SYSCTL_INT(_hw_usb_uep, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &uep_debug, 0, "Debug level");
+#endif
+
+#define UEP_MAX_X		2047
+#define UEP_MAX_Y		2047
+
+#define UEP_DOWN		0x01
+#define UEP_PACKET_LEN_MAX	16
+#define UEP_PACKET_LEN_REPORT	5
+#define UEP_PACKET_LEN_REPORT2	6
+#define UEP_PACKET_DIAG		0x0a
+#define UEP_PACKET_REPORT_MASK		0xe0
+#define UEP_PACKET_REPORT		0x80
+#define UEP_PACKET_REPORT_PRESSURE	0xc0
+#define UEP_PACKET_REPORT_PLAYER	0xa0
+#define	UEP_PACKET_LEN_MASK	
+
+#define UEP_FIFO_BUF_SIZE	8	/* bytes */
+#define UEP_FIFO_QUEUE_MAXLEN	50	/* units */
+
+enum {
+	UEP_INTR_DT,
+	UEP_N_TRANSFER,
+};
+
+struct uep_softc {
+	struct mtx mtx;
+
+	struct usb_xfer *xfer[UEP_N_TRANSFER];
+	struct usb_fifo_sc fifo;
+
+	u_int		pollrate;
+	u_int		state;
+#define UEP_ENABLED	0x01
+
+	/* Reassembling buffer. */
+	u_char		buf[UEP_PACKET_LEN_MAX];
+	uint8_t		buf_len;
+};
+
+static usb_callback_t uep_intr_callback;
+
+static device_probe_t	uep_probe;
+static device_attach_t	uep_attach;
+static device_detach_t	uep_detach;
+
+static usb_fifo_cmd_t	uep_start_read;
+static usb_fifo_cmd_t	uep_stop_read;
+static usb_fifo_open_t	uep_open;
+static usb_fifo_close_t	uep_close;
+
+static void uep_put_queue(struct uep_softc *, u_char *);
+
+static struct usb_fifo_methods uep_fifo_methods = {
+	.f_open = &uep_open,
+	.f_close = &uep_close,
+	.f_start_read = &uep_start_read,
+	.f_stop_read = &uep_stop_read,
+	.basename[0] = "uep",
+};
+
+static int
+get_pkt_len(u_char *buf)
+{
+	if (buf[0] == UEP_PACKET_DIAG) {
+		int len;
+
+		len = buf[1] + 2;
+		if (len > UEP_PACKET_LEN_MAX) {
+			DPRINTF("bad packet len %u\n", len);
+			return (UEP_PACKET_LEN_MAX);
+		}
+
+		return (len);
+	}
+
+	switch (buf[0] & UEP_PACKET_REPORT_MASK) {
+	case UEP_PACKET_REPORT:
+		return (UEP_PACKET_LEN_REPORT);
+	case UEP_PACKET_REPORT_PRESSURE:
+	case UEP_PACKET_REPORT_PLAYER:
+	case UEP_PACKET_REPORT_PRESSURE | UEP_PACKET_REPORT_PLAYER:
+		return (UEP_PACKET_LEN_REPORT2);
+	default:
+		DPRINTF("bad packet len 0\n");
+		return (0);
+	}
+}
+
+static void
+uep_process_pkt(struct uep_softc *sc, u_char *buf)
+{
+	int32_t x, y;
+
+	if ((buf[0] & 0xFE) != 0x80) {
+		DPRINTF("bad input packet format 0x%.2x\n", buf[0]);
+		return;
+	}
+
+	/*
+	 * Packet format is 5 bytes:
+	 *
+	 * 1000000T
+	 * 0000AAAA
+	 * 0AAAAAAA
+	 * 0000BBBB
+	 * 0BBBBBBB
+	 *
+	 * T: 1=touched 0=not touched
+	 * A: bits of axis A position, MSB to LSB
+	 * B: bits of axis B position, MSB to LSB
+	 *
+	 * For the unit I have, which is CTF1020-S from CarTFT.com,
+	 * A = X and B = Y. But in NetBSD uep(4) it is other way round :)
+	 *
+	 * The controller sends a stream of T=1 events while the
+	 * panel is touched, followed by a single T=0 event.
+	 *
+	 */
+
+	x = (buf[1] << 7) | buf[2];
+	y = (buf[3] << 7) | buf[4];
+
+	DPRINTFN(2, "x %u y %u\n", x, y);
+
+	uep_put_queue(sc, buf);
+}
+
+static void
+uep_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct uep_softc *sc = usbd_xfer_softc(xfer);
+	int len;
+
+	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+	    {
+		struct usb_page_cache *pc;
+		u_char buf[17], *p;
+		int pkt_len;
+
+		if (len > (int)sizeof(buf)) {
+			DPRINTF("bad input length %d\n", len);
+			goto tr_setup;
+		}
+
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_out(pc, 0, buf, len);
+
+		/*
+		 * The below code mimics Linux a lot. I don't know
+		 * why NetBSD reads complete packets, but we need
+		 * to reassamble 'em like Linux does (tries?).
+		 */
+		if (sc->buf_len > 0) {
+			int res;
+
+			if (sc->buf_len == 1)
+				sc->buf[1] = buf[0];
+
+			if ((pkt_len = get_pkt_len(sc->buf)) == 0)
+				goto tr_setup;
+
+			res = pkt_len - sc->buf_len;
+			memcpy(sc->buf + sc->buf_len, buf, res);
+			uep_process_pkt(sc, sc->buf);
+			sc->buf_len = 0;
+ 
+			p = buf + res;
+			len -= res;
+		} else
+			p = buf;
+
+		if (len == 1) {
+			sc->buf[0] = buf[0];
+			sc->buf_len = 1;
+
+			goto tr_setup;
+		}
+
+		while (len > 0) {
+			if ((pkt_len = get_pkt_len(p)) == 0)
+				goto tr_setup;
+
+			/* full packet: process */
+			if (pkt_len <= len) {
+				uep_process_pkt(sc, p);
+			} else {
+				/* incomplete packet: save in buffer */
+				memcpy(sc->buf, p, len);
+				sc->buf_len = len;
+			}
+			p += pkt_len;
+			len -= pkt_len;
+		}
+	    }
+	case USB_ST_SETUP:
+	tr_setup:
+		/* check if we can put more data into the FIFO */
+		if (usb_fifo_put_bytes_max(sc->fifo.fp[USB_FIFO_RX]) != 0) {
+			usbd_xfer_set_frame_len(xfer, 0,
+			    usbd_xfer_max_len(xfer));
+			usbd_transfer_submit(xfer);
+                }
+		break;
+
+	default:
+		if (error != USB_ERR_CANCELLED) {
+			/* try clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		break;
+	}
+}
+
+static const struct usb_config uep_config[UEP_N_TRANSFER] = {
+	[UEP_INTR_DT] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+		.bufsize = 0,   /* use wMaxPacketSize */
+		.callback = &uep_intr_callback,
+	},
+};
+
+static const STRUCT_USB_HOST_ID uep_devs[] = {
+	{USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL, 0)},
+	{USB_VPI(USB_VENDOR_EGALAX, USB_PRODUCT_EGALAX_TPANEL2, 0)},
+	{USB_VPI(USB_VENDOR_EGALAX2, USB_PRODUCT_EGALAX2_TPANEL, 0)},
+};
+
+static int
+uep_probe(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+	if (uaa->info.bConfigIndex != 0)
+		return (ENXIO);
+	if (uaa->info.bIfaceIndex != 0)
+		return (ENXIO);
+
+	return (usbd_lookup_id_by_uaa(uep_devs, sizeof(uep_devs), uaa));
+}
+
+static int
+uep_attach(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	struct uep_softc *sc = device_get_softc(dev);
+	int error;
+
+	device_set_usb_desc(dev);
+
+	mtx_init(&sc->mtx, "uep lock", NULL, MTX_DEF);
+
+	error = usbd_transfer_setup(uaa->device, &uaa->info.bIfaceIndex,
+	    sc->xfer, uep_config, UEP_N_TRANSFER, sc, &sc->mtx);
+
+	if (error) {
+		DPRINTF("usbd_transfer_setup error=%s\n", usbd_errstr(error));
+		goto detach;
+	}
+
+	error = usb_fifo_attach(uaa->device, sc, &sc->mtx, &uep_fifo_methods,
+	    &sc->fifo, device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+	    UID_ROOT, GID_OPERATOR, 0644);
+
+        if (error) {
+		DPRINTF("usb_fifo_attach error=%s\n", usbd_errstr(error));
+                goto detach;
+        }
+
+	sc->buf_len = 0;
+
+	return (0);	
+
+detach:
+	uep_detach(dev);
+
+	return (ENOMEM); /* XXX */
+}
+
+static int
+uep_detach(device_t dev)
+{
+	struct uep_softc *sc = device_get_softc(dev);
+
+	usb_fifo_detach(&sc->fifo);
+
+	usbd_transfer_unsetup(sc->xfer, UEP_N_TRANSFER);
+
+	mtx_destroy(&sc->mtx);
+
+	return (0);
+}
+
+static void
+uep_start_read(struct usb_fifo *fifo)
+{
+	struct uep_softc *sc = usb_fifo_softc(fifo);
+	u_int rate;
+
+	if ((rate = sc->pollrate) > 1000)
+		rate = 1000;
+
+	if (rate > 0 && sc->xfer[UEP_INTR_DT] != NULL) {
+		usbd_transfer_stop(sc->xfer[UEP_INTR_DT]);
+		usbd_xfer_set_interval(sc->xfer[UEP_INTR_DT], 1000 / rate);
+		sc->pollrate = 0;
+	}
+
+	usbd_transfer_start(sc->xfer[UEP_INTR_DT]);
+}
+
+static void
+uep_stop_read(struct usb_fifo *fifo)
+{
+	struct uep_softc *sc = usb_fifo_softc(fifo);
+
+	usbd_transfer_stop(sc->xfer[UEP_INTR_DT]);
+}
+
+static void
+uep_put_queue(struct uep_softc *sc, u_char *buf)
+{
+	usb_fifo_put_data_linear(sc->fifo.fp[USB_FIFO_RX], buf,
+	    UEP_PACKET_LEN_REPORT, 1);
+}
+
+static int
+uep_open(struct usb_fifo *fifo, int fflags)
+{
+	if (fflags & FREAD) {
+		struct uep_softc *sc = usb_fifo_softc(fifo);
+
+		if (sc->state & UEP_ENABLED)
+			return (EBUSY);
+		if (usb_fifo_alloc_buffer(fifo, UEP_FIFO_BUF_SIZE,
+		    UEP_FIFO_QUEUE_MAXLEN))
+			return (ENOMEM);
+
+		sc->state |= UEP_ENABLED;
+	}
+
+	return (0);
+}
+
+static void
+uep_close(struct usb_fifo *fifo, int fflags)
+{
+	if (fflags & FREAD) {
+		struct uep_softc *sc = usb_fifo_softc(fifo);
+
+		sc->state &= ~(UEP_ENABLED);
+		usb_fifo_free_buffer(fifo);
+	}
+}
+
+static devclass_t uep_devclass;
+
+static device_method_t uep_methods[] = {
+	DEVMETHOD(device_probe, uep_probe),
+       	DEVMETHOD(device_attach, uep_attach),
+	DEVMETHOD(device_detach, uep_detach),
+	{ 0, 0 },
+};
+
+static driver_t uep_driver = {
+	.name = "uep",
+	.methods = uep_methods,
+	.size = sizeof(struct uep_softc),
+};
+
+DRIVER_MODULE(uep, uhub, uep_driver, uep_devclass, NULL, NULL);
+MODULE_DEPEND(uep, usb, 1, 1, 1);
+MODULE_VERSION(uep, 1);
+USB_PNP_HOST_INFO(uep_devs);
diff --git a/freebsd/sys/dev/usb/input/uhid.c b/freebsd/sys/dev/usb/input/uhid.c
new file mode 100644
index 0000000..d3812d0
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/uhid.c
@@ -0,0 +1,883 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*	$NetBSD: uhid.c,v 1.46 2001/11/13 06:24:55 lukem Exp $	*/
+
+/* Also already merged from NetBSD:
+ *	$NetBSD: uhid.c,v 1.54 2002/09/23 05:51:21 simonb Exp $
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*-
+ * 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 at 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.
+ *
+ * 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.
+ */
+
+/*
+ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <rtems/bsd/sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <rtems/bsd/sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <rtems/bsd/local/usbdevs.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/usb_ioctl.h>
+
+#define	USB_DEBUG_VAR uhid_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/input/usb_rdesc.h>
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef USB_DEBUG
+static int uhid_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, uhid, CTLFLAG_RW, 0, "USB uhid");
+SYSCTL_INT(_hw_usb_uhid, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &uhid_debug, 0, "Debug level");
+#endif
+
+#define	UHID_BSIZE	1024		/* bytes, buffer size */
+#define	UHID_FRAME_NUM 	  50		/* bytes, frame number */
+
+enum {
+	UHID_INTR_DT_WR,
+	UHID_INTR_DT_RD,
+	UHID_CTRL_DT_WR,
+	UHID_CTRL_DT_RD,
+	UHID_N_TRANSFER,
+};
+
+struct uhid_softc {
+	struct usb_fifo_sc sc_fifo;
+	struct mtx sc_mtx;
+
+	struct usb_xfer *sc_xfer[UHID_N_TRANSFER];
+	struct usb_device *sc_udev;
+	void   *sc_repdesc_ptr;
+
+	uint32_t sc_isize;
+	uint32_t sc_osize;
+	uint32_t sc_fsize;
+
+	uint16_t sc_repdesc_size;
+
+	uint8_t	sc_iface_no;
+	uint8_t	sc_iface_index;
+	uint8_t	sc_iid;
+	uint8_t	sc_oid;
+	uint8_t	sc_fid;
+	uint8_t	sc_flags;
+#define	UHID_FLAG_IMMED        0x01	/* set if read should be immediate */
+#define	UHID_FLAG_STATIC_DESC  0x04	/* set if report descriptors are
+					 * static */
+};
+
+static const uint8_t uhid_xb360gp_report_descr[] = {UHID_XB360GP_REPORT_DESCR()};
+static const uint8_t uhid_graphire_report_descr[] = {UHID_GRAPHIRE_REPORT_DESCR()};
+static const uint8_t uhid_graphire3_4x5_report_descr[] = {UHID_GRAPHIRE3_4X5_REPORT_DESCR()};
+
+/* prototypes */
+
+static device_probe_t uhid_probe;
+static device_attach_t uhid_attach;
+static device_detach_t uhid_detach;
+
+static usb_callback_t uhid_intr_write_callback;
+static usb_callback_t uhid_intr_read_callback;
+static usb_callback_t uhid_write_callback;
+static usb_callback_t uhid_read_callback;
+
+static usb_fifo_cmd_t uhid_start_read;
+static usb_fifo_cmd_t uhid_stop_read;
+static usb_fifo_cmd_t uhid_start_write;
+static usb_fifo_cmd_t uhid_stop_write;
+static usb_fifo_open_t uhid_open;
+static usb_fifo_close_t uhid_close;
+static usb_fifo_ioctl_t uhid_ioctl;
+
+static struct usb_fifo_methods uhid_fifo_methods = {
+	.f_open = &uhid_open,
+	.f_close = &uhid_close,
+	.f_ioctl = &uhid_ioctl,
+	.f_start_read = &uhid_start_read,
+	.f_stop_read = &uhid_stop_read,
+	.f_start_write = &uhid_start_write,
+	.f_stop_write = &uhid_stop_write,
+	.basename[0] = "uhid",
+};
+
+static void
+uhid_intr_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct uhid_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	int actlen;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+	case USB_ST_SETUP:
+tr_setup:
+		pc = usbd_xfer_get_frame(xfer, 0);
+		if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+		    0, usbd_xfer_max_len(xfer), &actlen, 0)) {
+			usbd_xfer_set_frame_len(xfer, 0, actlen);
+			usbd_transfer_submit(xfer);
+		}
+		return;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		return;
+	}
+}
+
+static void
+uhid_intr_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct uhid_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	int actlen;
+
+	usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		DPRINTF("transferred!\n");
+
+		pc = usbd_xfer_get_frame(xfer, 0);
+
+		/* 
+		 * If the ID byte is non zero we allow descriptors
+		 * having multiple sizes:
+		 */
+		if ((actlen >= (int)sc->sc_isize) ||
+		    ((actlen > 0) && (sc->sc_iid != 0))) {
+			/* limit report length to the maximum */
+			if (actlen > (int)sc->sc_isize)
+				actlen = sc->sc_isize;
+			usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc,
+			    0, actlen, 1);
+		} else {
+			/* ignore it */
+			DPRINTF("ignored transfer, %d bytes\n", actlen);
+		}
+
+	case USB_ST_SETUP:
+re_submit:
+		if (usb_fifo_put_bytes_max(
+		    sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+			usbd_xfer_set_frame_len(xfer, 0, sc->sc_isize);
+			usbd_transfer_submit(xfer);
+		}
+		return;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto re_submit;
+		}
+		return;
+	}
+}
+
+static void
+uhid_fill_set_report(struct usb_device_request *req, uint8_t iface_no,
+    uint8_t type, uint8_t id, uint16_t size)
+{
+	req->bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req->bRequest = UR_SET_REPORT;
+	USETW2(req->wValue, type, id);
+	req->wIndex[0] = iface_no;
+	req->wIndex[1] = 0;
+	USETW(req->wLength, size);
+}
+
+static void
+uhid_fill_get_report(struct usb_device_request *req, uint8_t iface_no,
+    uint8_t type, uint8_t id, uint16_t size)
+{
+	req->bmRequestType = UT_READ_CLASS_INTERFACE;
+	req->bRequest = UR_GET_REPORT;
+	USETW2(req->wValue, type, id);
+	req->wIndex[0] = iface_no;
+	req->wIndex[1] = 0;
+	USETW(req->wLength, size);
+}
+
+static void
+uhid_write_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct uhid_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_device_request req;
+	struct usb_page_cache *pc;
+	uint32_t size = sc->sc_osize;
+	uint32_t actlen;
+	uint8_t id;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+	case USB_ST_SETUP:
+		/* try to extract the ID byte */
+		if (sc->sc_oid) {
+			pc = usbd_xfer_get_frame(xfer, 0);
+			if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+			    0, 1, &actlen, 0)) {
+				if (actlen != 1) {
+					goto tr_error;
+				}
+				usbd_copy_out(pc, 0, &id, 1);
+
+			} else {
+				return;
+			}
+			if (size) {
+				size--;
+			}
+		} else {
+			id = 0;
+		}
+
+		pc = usbd_xfer_get_frame(xfer, 1);
+		if (usb_fifo_get_data(sc->sc_fifo.fp[USB_FIFO_TX], pc,
+		    0, UHID_BSIZE, &actlen, 1)) {
+			if (actlen != size) {
+				goto tr_error;
+			}
+			uhid_fill_set_report
+			    (&req, sc->sc_iface_no,
+			    UHID_OUTPUT_REPORT, id, size);
+
+			pc = usbd_xfer_get_frame(xfer, 0);
+			usbd_copy_in(pc, 0, &req, sizeof(req));
+
+			usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+			usbd_xfer_set_frame_len(xfer, 1, size);
+			usbd_xfer_set_frames(xfer, size ? 2 : 1);
+			usbd_transfer_submit(xfer);
+		}
+		return;
+
+	default:
+tr_error:
+		/* bomb out */
+		usb_fifo_get_data_error(sc->sc_fifo.fp[USB_FIFO_TX]);
+		return;
+	}
+}
+
+static void
+uhid_read_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct uhid_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_device_request req;
+	struct usb_page_cache *pc;
+
+	pc = usbd_xfer_get_frame(xfer, 0);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		usb_fifo_put_data(sc->sc_fifo.fp[USB_FIFO_RX], pc, sizeof(req),
+		    sc->sc_isize, 1);
+		return;
+
+	case USB_ST_SETUP:
+
+		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) > 0) {
+
+			uhid_fill_get_report
+			    (&req, sc->sc_iface_no, UHID_INPUT_REPORT,
+			    sc->sc_iid, sc->sc_isize);
+
+			usbd_copy_in(pc, 0, &req, sizeof(req));
+
+			usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+			usbd_xfer_set_frame_len(xfer, 1, sc->sc_isize);
+			usbd_xfer_set_frames(xfer, sc->sc_isize ? 2 : 1);
+			usbd_transfer_submit(xfer);
+		}
+		return;
+
+	default:			/* Error */
+		/* bomb out */
+		usb_fifo_put_data_error(sc->sc_fifo.fp[USB_FIFO_RX]);
+		return;
+	}
+}
+
+static const struct usb_config uhid_config[UHID_N_TRANSFER] = {
+
+	[UHID_INTR_DT_WR] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_OUT,
+		.flags = {.pipe_bof = 1,.no_pipe_ok = 1, },
+		.bufsize = UHID_BSIZE,
+		.callback = &uhid_intr_write_callback,
+	},
+
+	[UHID_INTR_DT_RD] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+		.bufsize = UHID_BSIZE,
+		.callback = &uhid_intr_read_callback,
+	},
+
+	[UHID_CTRL_DT_WR] = {
+		.type = UE_CONTROL,
+		.endpoint = 0x00,	/* Control pipe */
+		.direction = UE_DIR_ANY,
+		.bufsize = sizeof(struct usb_device_request) + UHID_BSIZE,
+		.callback = &uhid_write_callback,
+		.timeout = 1000,	/* 1 second */
+	},
+
+	[UHID_CTRL_DT_RD] = {
+		.type = UE_CONTROL,
+		.endpoint = 0x00,	/* Control pipe */
+		.direction = UE_DIR_ANY,
+		.bufsize = sizeof(struct usb_device_request) + UHID_BSIZE,
+		.callback = &uhid_read_callback,
+		.timeout = 1000,	/* 1 second */
+	},
+};
+
+static void
+uhid_start_read(struct usb_fifo *fifo)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+	if (sc->sc_flags & UHID_FLAG_IMMED) {
+		usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_RD]);
+	} else {
+		usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_RD]);
+	}
+}
+
+static void
+uhid_stop_read(struct usb_fifo *fifo)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+	usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_RD]);
+	usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_RD]);
+}
+
+static void
+uhid_start_write(struct usb_fifo *fifo)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+	if ((sc->sc_flags & UHID_FLAG_IMMED) ||
+	    sc->sc_xfer[UHID_INTR_DT_WR] == NULL) {
+		usbd_transfer_start(sc->sc_xfer[UHID_CTRL_DT_WR]);
+	} else {
+		usbd_transfer_start(sc->sc_xfer[UHID_INTR_DT_WR]);
+	}
+}
+
+static void
+uhid_stop_write(struct usb_fifo *fifo)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+	usbd_transfer_stop(sc->sc_xfer[UHID_CTRL_DT_WR]);
+	usbd_transfer_stop(sc->sc_xfer[UHID_INTR_DT_WR]);
+}
+
+static int
+uhid_get_report(struct uhid_softc *sc, uint8_t type,
+    uint8_t id, void *kern_data, void *user_data,
+    uint16_t len)
+{
+	int err;
+	uint8_t free_data = 0;
+
+	if (kern_data == NULL) {
+		kern_data = malloc(len, M_USBDEV, M_WAITOK);
+		if (kern_data == NULL) {
+			err = ENOMEM;
+			goto done;
+		}
+		free_data = 1;
+	}
+	err = usbd_req_get_report(sc->sc_udev, NULL, kern_data,
+	    len, sc->sc_iface_index, type, id);
+	if (err) {
+		err = ENXIO;
+		goto done;
+	}
+	if (user_data) {
+		/* dummy buffer */
+		err = copyout(kern_data, user_data, len);
+		if (err) {
+			goto done;
+		}
+	}
+done:
+	if (free_data) {
+		free(kern_data, M_USBDEV);
+	}
+	return (err);
+}
+
+static int
+uhid_set_report(struct uhid_softc *sc, uint8_t type,
+    uint8_t id, void *kern_data, void *user_data,
+    uint16_t len)
+{
+	int err;
+	uint8_t free_data = 0;
+
+	if (kern_data == NULL) {
+		kern_data = malloc(len, M_USBDEV, M_WAITOK);
+		if (kern_data == NULL) {
+			err = ENOMEM;
+			goto done;
+		}
+		free_data = 1;
+		err = copyin(user_data, kern_data, len);
+		if (err) {
+			goto done;
+		}
+	}
+	err = usbd_req_set_report(sc->sc_udev, NULL, kern_data,
+	    len, sc->sc_iface_index, type, id);
+	if (err) {
+		err = ENXIO;
+		goto done;
+	}
+done:
+	if (free_data) {
+		free(kern_data, M_USBDEV);
+	}
+	return (err);
+}
+
+static int
+uhid_open(struct usb_fifo *fifo, int fflags)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+
+	/*
+	 * The buffers are one byte larger than maximum so that one
+	 * can detect too large read/writes and short transfers:
+	 */
+	if (fflags & FREAD) {
+		/* reset flags */
+		mtx_lock(&sc->sc_mtx);
+		sc->sc_flags &= ~UHID_FLAG_IMMED;
+		mtx_unlock(&sc->sc_mtx);
+
+		if (usb_fifo_alloc_buffer(fifo,
+		    sc->sc_isize + 1, UHID_FRAME_NUM)) {
+			return (ENOMEM);
+		}
+	}
+	if (fflags & FWRITE) {
+		if (usb_fifo_alloc_buffer(fifo,
+		    sc->sc_osize + 1, UHID_FRAME_NUM)) {
+			return (ENOMEM);
+		}
+	}
+	return (0);
+}
+
+static void
+uhid_close(struct usb_fifo *fifo, int fflags)
+{
+	if (fflags & (FREAD | FWRITE)) {
+		usb_fifo_free_buffer(fifo);
+	}
+}
+
+static int
+uhid_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr,
+    int fflags)
+{
+	struct uhid_softc *sc = usb_fifo_softc(fifo);
+	struct usb_gen_descriptor *ugd;
+	uint32_t size;
+	int error = 0;
+	uint8_t id;
+
+	switch (cmd) {
+	case USB_GET_REPORT_DESC:
+		ugd = addr;
+		if (sc->sc_repdesc_size > ugd->ugd_maxlen) {
+			size = ugd->ugd_maxlen;
+		} else {
+			size = sc->sc_repdesc_size;
+		}
+		ugd->ugd_actlen = size;
+		if (ugd->ugd_data == NULL)
+			break;		/* descriptor length only */
+		error = copyout(sc->sc_repdesc_ptr, ugd->ugd_data, size);
+		break;
+
+	case USB_SET_IMMED:
+		if (!(fflags & FREAD)) {
+			error = EPERM;
+			break;
+		}
+		if (*(int *)addr) {
+
+			/* do a test read */
+
+			error = uhid_get_report(sc, UHID_INPUT_REPORT,
+			    sc->sc_iid, NULL, NULL, sc->sc_isize);
+			if (error) {
+				break;
+			}
+			mtx_lock(&sc->sc_mtx);
+			sc->sc_flags |= UHID_FLAG_IMMED;
+			mtx_unlock(&sc->sc_mtx);
+		} else {
+			mtx_lock(&sc->sc_mtx);
+			sc->sc_flags &= ~UHID_FLAG_IMMED;
+			mtx_unlock(&sc->sc_mtx);
+		}
+		break;
+
+	case USB_GET_REPORT:
+		if (!(fflags & FREAD)) {
+			error = EPERM;
+			break;
+		}
+		ugd = addr;
+		switch (ugd->ugd_report_type) {
+		case UHID_INPUT_REPORT:
+			size = sc->sc_isize;
+			id = sc->sc_iid;
+			break;
+		case UHID_OUTPUT_REPORT:
+			size = sc->sc_osize;
+			id = sc->sc_oid;
+			break;
+		case UHID_FEATURE_REPORT:
+			size = sc->sc_fsize;
+			id = sc->sc_fid;
+			break;
+		default:
+			return (EINVAL);
+		}
+		if (id != 0)
+			copyin(ugd->ugd_data, &id, 1);
+		error = uhid_get_report(sc, ugd->ugd_report_type, id,
+		    NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
+		break;
+
+	case USB_SET_REPORT:
+		if (!(fflags & FWRITE)) {
+			error = EPERM;
+			break;
+		}
+		ugd = addr;
+		switch (ugd->ugd_report_type) {
+		case UHID_INPUT_REPORT:
+			size = sc->sc_isize;
+			id = sc->sc_iid;
+			break;
+		case UHID_OUTPUT_REPORT:
+			size = sc->sc_osize;
+			id = sc->sc_oid;
+			break;
+		case UHID_FEATURE_REPORT:
+			size = sc->sc_fsize;
+			id = sc->sc_fid;
+			break;
+		default:
+			return (EINVAL);
+		}
+		if (id != 0)
+			copyin(ugd->ugd_data, &id, 1);
+		error = uhid_set_report(sc, ugd->ugd_report_type, id,
+		    NULL, ugd->ugd_data, imin(ugd->ugd_maxlen, size));
+		break;
+
+	case USB_GET_REPORT_ID:
+		*(int *)addr = 0;	/* XXX: we only support reportid 0? */
+		break;
+
+	default:
+		error = EINVAL;
+		break;
+	}
+	return (error);
+}
+
+static const STRUCT_USB_HOST_ID uhid_devs[] = {
+	/* generic HID class */
+	{USB_IFACE_CLASS(UICLASS_HID),},
+	/* the Xbox 360 gamepad doesn't use the HID class */
+	{USB_IFACE_CLASS(UICLASS_VENDOR),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER),
+	 USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),},
+};
+
+static int
+uhid_probe(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	int error;
+
+	DPRINTFN(11, "\n");
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+
+	error = usbd_lookup_id_by_uaa(uhid_devs, sizeof(uhid_devs), uaa);
+	if (error)
+		return (error);
+
+	if (usb_test_quirk(uaa, UQ_HID_IGNORE))
+		return (ENXIO);
+
+	/*
+	 * Don't attach to mouse and keyboard devices, hence then no
+	 * "nomatch" event is generated and then ums and ukbd won't
+	 * attach properly when loaded.
+	 */
+	if ((uaa->info.bInterfaceClass == UICLASS_HID) &&
+	    (uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+	    (((uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD) &&
+	      !usb_test_quirk(uaa, UQ_KBD_IGNORE)) ||
+	     ((uaa->info.bInterfaceProtocol == UIPROTO_MOUSE) &&
+	      !usb_test_quirk(uaa, UQ_UMS_IGNORE))))
+		return (ENXIO);
+
+	return (BUS_PROBE_GENERIC);
+}
+
+static int
+uhid_attach(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	struct uhid_softc *sc = device_get_softc(dev);
+	int unit = device_get_unit(dev);
+	int error = 0;
+
+	DPRINTFN(10, "sc=%p\n", sc);
+
+	device_set_usb_desc(dev);
+
+	mtx_init(&sc->sc_mtx, "uhid lock", NULL, MTX_DEF | MTX_RECURSE);
+
+	sc->sc_udev = uaa->device;
+
+	sc->sc_iface_no = uaa->info.bIfaceNum;
+	sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+	error = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, uhid_config,
+	    UHID_N_TRANSFER, sc, &sc->sc_mtx);
+
+	if (error) {
+		DPRINTF("error=%s\n", usbd_errstr(error));
+		goto detach;
+	}
+	if (uaa->info.idVendor == USB_VENDOR_WACOM) {
+
+		/* the report descriptor for the Wacom Graphire is broken */
+
+		if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE) {
+
+			sc->sc_repdesc_size = sizeof(uhid_graphire_report_descr);
+			sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire_report_descr);
+			sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+
+		} else if (uaa->info.idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) {
+
+			static uint8_t reportbuf[] = {2, 2, 2};
+
+			/*
+			 * The Graphire3 needs 0x0202 to be written to
+			 * feature report ID 2 before it'll start
+			 * returning digitizer data.
+			 */
+			error = usbd_req_set_report(uaa->device, NULL,
+			    reportbuf, sizeof(reportbuf),
+			    uaa->info.bIfaceIndex, UHID_FEATURE_REPORT, 2);
+
+			if (error) {
+				DPRINTF("set report failed, error=%s (ignored)\n",
+				    usbd_errstr(error));
+			}
+			sc->sc_repdesc_size = sizeof(uhid_graphire3_4x5_report_descr);
+			sc->sc_repdesc_ptr = __DECONST(void *, &uhid_graphire3_4x5_report_descr);
+			sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+		}
+	} else if ((uaa->info.bInterfaceClass == UICLASS_VENDOR) &&
+	    (uaa->info.bInterfaceSubClass == UISUBCLASS_XBOX360_CONTROLLER) &&
+	    (uaa->info.bInterfaceProtocol == UIPROTO_XBOX360_GAMEPAD)) {
+		static const uint8_t reportbuf[3] = {1, 3, 0};
+		/*
+		 * Turn off the four LEDs on the gamepad which
+		 * are blinking by default:
+		 */
+		error = usbd_req_set_report(uaa->device, NULL,
+		    __DECONST(void *, reportbuf), sizeof(reportbuf),
+		    uaa->info.bIfaceIndex, UHID_OUTPUT_REPORT, 0);
+		if (error) {
+			DPRINTF("set output report failed, error=%s (ignored)\n",
+			    usbd_errstr(error));
+		}
+		/* the Xbox 360 gamepad has no report descriptor */
+		sc->sc_repdesc_size = sizeof(uhid_xb360gp_report_descr);
+		sc->sc_repdesc_ptr = __DECONST(void *, &uhid_xb360gp_report_descr);
+		sc->sc_flags |= UHID_FLAG_STATIC_DESC;
+	}
+	if (sc->sc_repdesc_ptr == NULL) {
+
+		error = usbd_req_get_hid_desc(uaa->device, NULL,
+		    &sc->sc_repdesc_ptr, &sc->sc_repdesc_size,
+		    M_USBDEV, uaa->info.bIfaceIndex);
+
+		if (error) {
+			device_printf(dev, "no report descriptor\n");
+			goto detach;
+		}
+	}
+	error = usbd_req_set_idle(uaa->device, NULL,
+	    uaa->info.bIfaceIndex, 0, 0);
+
+	if (error) {
+		DPRINTF("set idle failed, error=%s (ignored)\n",
+		    usbd_errstr(error));
+	}
+	sc->sc_isize = hid_report_size
+	    (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_input, &sc->sc_iid);
+
+	sc->sc_osize = hid_report_size
+	    (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_output, &sc->sc_oid);
+
+	sc->sc_fsize = hid_report_size
+	    (sc->sc_repdesc_ptr, sc->sc_repdesc_size, hid_feature, &sc->sc_fid);
+
+	if (sc->sc_isize > UHID_BSIZE) {
+		DPRINTF("input size is too large, "
+		    "%d bytes (truncating)\n",
+		    sc->sc_isize);
+		sc->sc_isize = UHID_BSIZE;
+	}
+	if (sc->sc_osize > UHID_BSIZE) {
+		DPRINTF("output size is too large, "
+		    "%d bytes (truncating)\n",
+		    sc->sc_osize);
+		sc->sc_osize = UHID_BSIZE;
+	}
+	if (sc->sc_fsize > UHID_BSIZE) {
+		DPRINTF("feature size is too large, "
+		    "%d bytes (truncating)\n",
+		    sc->sc_fsize);
+		sc->sc_fsize = UHID_BSIZE;
+	}
+
+	error = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+	    &uhid_fifo_methods, &sc->sc_fifo,
+	    unit, -1, uaa->info.bIfaceIndex,
+	    UID_ROOT, GID_OPERATOR, 0644);
+	if (error) {
+		goto detach;
+	}
+	return (0);			/* success */
+
+detach:
+	uhid_detach(dev);
+	return (ENOMEM);
+}
+
+static int
+uhid_detach(device_t dev)
+{
+	struct uhid_softc *sc = device_get_softc(dev);
+
+	usb_fifo_detach(&sc->sc_fifo);
+
+	usbd_transfer_unsetup(sc->sc_xfer, UHID_N_TRANSFER);
+
+	if (sc->sc_repdesc_ptr) {
+		if (!(sc->sc_flags & UHID_FLAG_STATIC_DESC)) {
+			free(sc->sc_repdesc_ptr, M_USBDEV);
+		}
+	}
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+static devclass_t uhid_devclass;
+
+static device_method_t uhid_methods[] = {
+	DEVMETHOD(device_probe, uhid_probe),
+	DEVMETHOD(device_attach, uhid_attach),
+	DEVMETHOD(device_detach, uhid_detach),
+
+	DEVMETHOD_END
+};
+
+static driver_t uhid_driver = {
+	.name = "uhid",
+	.methods = uhid_methods,
+	.size = sizeof(struct uhid_softc),
+};
+
+DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, NULL, 0);
+MODULE_DEPEND(uhid, usb, 1, 1, 1);
+MODULE_VERSION(uhid, 1);
+USB_PNP_HOST_INFO(uhid_devs);
diff --git a/freebsd/sys/dev/usb/input/ukbd.c b/freebsd/sys/dev/usb/input/ukbd.c
new file mode 100644
index 0000000..5c6f558
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/ukbd.c
@@ -0,0 +1,2309 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+
+/*-
+ * 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 at 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.
+ *
+ * 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.
+ *
+ */
+
+/*
+ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
+ */
+
+#include <rtems/bsd/local/opt_compat.h>
+#include <rtems/bsd/local/opt_kbd.h>
+#include <rtems/bsd/local/opt_ukbd.h>
+#include <rtems/bsd/local/opt_evdev.h>
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <rtems/bsd/sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <rtems/bsd/sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define	USB_DEBUG_VAR ukbd_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/tty.h>
+#include <sys/kbio.h>
+
+#include <dev/kbd/kbdreg.h>
+
+/* the initial key map, accent map and fkey strings */
+#if defined(UKBD_DFLT_KEYMAP) && !defined(KLD_MODULE)
+#define	KBD_DFLT_KEYMAP
+#include "ukbdmap.h"
+#endif
+
+/* the following file must be included after "ukbdmap.h" */
+#include <dev/kbd/kbdtables.h>
+
+#ifdef USB_DEBUG
+static int ukbd_debug = 0;
+static int ukbd_no_leds = 0;
+static int ukbd_pollrate = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ukbd, CTLFLAG_RW, 0, "USB keyboard");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &ukbd_debug, 0, "Debug level");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, no_leds, CTLFLAG_RWTUN,
+    &ukbd_no_leds, 0, "Disables setting of keyboard leds");
+SYSCTL_INT(_hw_usb_ukbd, OID_AUTO, pollrate, CTLFLAG_RWTUN,
+    &ukbd_pollrate, 0, "Force this polling rate, 1-1000Hz");
+#endif
+
+#define	UKBD_EMULATE_ATSCANCODE	       1
+#define	UKBD_DRIVER_NAME          "ukbd"
+#define	UKBD_NMOD                     8	/* units */
+#define	UKBD_NKEYCODE                 6	/* units */
+#define	UKBD_IN_BUF_SIZE  (2*(UKBD_NMOD + (2*UKBD_NKEYCODE)))	/* bytes */
+#define	UKBD_IN_BUF_FULL  ((UKBD_IN_BUF_SIZE / 2) - 1)	/* bytes */
+#define	UKBD_NFKEY        (sizeof(fkey_tab)/sizeof(fkey_tab[0]))	/* units */
+#define	UKBD_BUFFER_SIZE	      64	/* bytes */
+
+struct ukbd_data {
+	uint16_t	modifiers;
+#define	MOD_CONTROL_L	0x01
+#define	MOD_CONTROL_R	0x10
+#define	MOD_SHIFT_L	0x02
+#define	MOD_SHIFT_R	0x20
+#define	MOD_ALT_L	0x04
+#define	MOD_ALT_R	0x40
+#define	MOD_WIN_L	0x08
+#define	MOD_WIN_R	0x80
+/* internal */
+#define	MOD_EJECT	0x0100
+#define	MOD_FN		0x0200
+	uint8_t	keycode[UKBD_NKEYCODE];
+};
+
+enum {
+	UKBD_INTR_DT_0,
+	UKBD_INTR_DT_1,
+	UKBD_CTRL_LED,
+	UKBD_N_TRANSFER,
+};
+
+struct ukbd_softc {
+	keyboard_t sc_kbd;
+	keymap_t sc_keymap;
+	accentmap_t sc_accmap;
+	fkeytab_t sc_fkeymap[UKBD_NFKEY];
+	struct hid_location sc_loc_apple_eject;
+	struct hid_location sc_loc_apple_fn;
+	struct hid_location sc_loc_ctrl_l;
+	struct hid_location sc_loc_ctrl_r;
+	struct hid_location sc_loc_shift_l;
+	struct hid_location sc_loc_shift_r;
+	struct hid_location sc_loc_alt_l;
+	struct hid_location sc_loc_alt_r;
+	struct hid_location sc_loc_win_l;
+	struct hid_location sc_loc_win_r;
+	struct hid_location sc_loc_events;
+	struct hid_location sc_loc_numlock;
+	struct hid_location sc_loc_capslock;
+	struct hid_location sc_loc_scrolllock;
+	struct usb_callout sc_callout;
+	struct ukbd_data sc_ndata;
+	struct ukbd_data sc_odata;
+
+	struct thread *sc_poll_thread;
+	struct usb_device *sc_udev;
+	struct usb_interface *sc_iface;
+	struct usb_xfer *sc_xfer[UKBD_N_TRANSFER];
+#ifdef EVDEV_SUPPORT
+	struct evdev_dev *sc_evdev;
+#endif
+
+	sbintime_t sc_co_basetime;
+	int	sc_delay;
+	uint32_t sc_ntime[UKBD_NKEYCODE];
+	uint32_t sc_otime[UKBD_NKEYCODE];
+	uint32_t sc_input[UKBD_IN_BUF_SIZE];	/* input buffer */
+	uint32_t sc_time_ms;
+	uint32_t sc_composed_char;	/* composed char code, if non-zero */
+#ifdef UKBD_EMULATE_ATSCANCODE
+	uint32_t sc_buffered_char[2];
+#endif
+	uint32_t sc_flags;		/* flags */
+#define	UKBD_FLAG_COMPOSE	0x00000001
+#define	UKBD_FLAG_POLLING	0x00000002
+#define	UKBD_FLAG_SET_LEDS	0x00000004
+#define	UKBD_FLAG_ATTACHED	0x00000010
+#define	UKBD_FLAG_GONE		0x00000020
+
+#define	UKBD_FLAG_HID_MASK	0x003fffc0
+#define	UKBD_FLAG_APPLE_EJECT	0x00000040
+#define	UKBD_FLAG_APPLE_FN	0x00000080
+#define	UKBD_FLAG_APPLE_SWAP	0x00000100
+#define	UKBD_FLAG_CTRL_L	0x00000400
+#define	UKBD_FLAG_CTRL_R	0x00000800
+#define	UKBD_FLAG_SHIFT_L	0x00001000
+#define	UKBD_FLAG_SHIFT_R	0x00002000
+#define	UKBD_FLAG_ALT_L		0x00004000
+#define	UKBD_FLAG_ALT_R		0x00008000
+#define	UKBD_FLAG_WIN_L		0x00010000
+#define	UKBD_FLAG_WIN_R		0x00020000
+#define	UKBD_FLAG_EVENTS	0x00040000
+#define	UKBD_FLAG_NUMLOCK	0x00080000
+#define	UKBD_FLAG_CAPSLOCK	0x00100000
+#define	UKBD_FLAG_SCROLLLOCK 	0x00200000
+
+	int	sc_mode;		/* input mode (K_XLATE,K_RAW,K_CODE) */
+	int	sc_state;		/* shift/lock key state */
+	int	sc_accents;		/* accent key index (> 0) */
+	int	sc_polling;		/* polling recursion count */
+	int	sc_led_size;
+	int	sc_kbd_size;
+
+	uint16_t sc_inputs;
+	uint16_t sc_inputhead;
+	uint16_t sc_inputtail;
+	uint16_t sc_modifiers;
+
+	uint8_t	sc_leds;		/* store for async led requests */
+	uint8_t	sc_iface_index;
+	uint8_t	sc_iface_no;
+	uint8_t sc_id_apple_eject;
+	uint8_t sc_id_apple_fn;
+	uint8_t sc_id_ctrl_l;
+	uint8_t sc_id_ctrl_r;
+	uint8_t sc_id_shift_l;
+	uint8_t sc_id_shift_r;
+	uint8_t sc_id_alt_l;
+	uint8_t sc_id_alt_r;
+	uint8_t sc_id_win_l;
+	uint8_t sc_id_win_r;
+	uint8_t sc_id_event;
+	uint8_t sc_id_numlock;
+	uint8_t sc_id_capslock;
+	uint8_t sc_id_scrolllock;
+	uint8_t sc_id_events;
+	uint8_t sc_kbd_id;
+
+	uint8_t sc_buffer[UKBD_BUFFER_SIZE];
+};
+
+#define	KEY_ERROR	  0x01
+
+#define	KEY_PRESS	  0
+#define	KEY_RELEASE	  0x400
+#define	KEY_INDEX(c)	  ((c) & 0xFF)
+
+#define	SCAN_PRESS	  0
+#define	SCAN_RELEASE	  0x80
+#define	SCAN_PREFIX_E0	  0x100
+#define	SCAN_PREFIX_E1	  0x200
+#define	SCAN_PREFIX_CTL	  0x400
+#define	SCAN_PREFIX_SHIFT 0x800
+#define	SCAN_PREFIX	(SCAN_PREFIX_E0  | SCAN_PREFIX_E1 | \
+			 SCAN_PREFIX_CTL | SCAN_PREFIX_SHIFT)
+#define	SCAN_CHAR(c)	((c) & 0x7f)
+
+#define	UKBD_LOCK()	USB_MTX_LOCK(&Giant)
+#define	UKBD_UNLOCK()	USB_MTX_UNLOCK(&Giant)
+#define	UKBD_LOCK_ASSERT()	USB_MTX_ASSERT(&Giant, MA_OWNED)
+
+struct ukbd_mods {
+	uint32_t mask, key;
+};
+
+static const struct ukbd_mods ukbd_mods[UKBD_NMOD] = {
+	{MOD_CONTROL_L, 0xe0},
+	{MOD_CONTROL_R, 0xe4},
+	{MOD_SHIFT_L, 0xe1},
+	{MOD_SHIFT_R, 0xe5},
+	{MOD_ALT_L, 0xe2},
+	{MOD_ALT_R, 0xe6},
+	{MOD_WIN_L, 0xe3},
+	{MOD_WIN_R, 0xe7},
+};
+
+#define	NN 0				/* no translation */
+/*
+ * Translate USB keycodes to AT keyboard scancodes.
+ */
+/*
+ * FIXME: Mac USB keyboard generates:
+ * 0x53: keypad NumLock/Clear
+ * 0x66: Power
+ * 0x67: keypad =
+ * 0x68: F13
+ * 0x69: F14
+ * 0x6a: F15
+ * 
+ * USB Apple Keyboard JIS generates:
+ * 0x90: Kana
+ * 0x91: Eisu
+ */
+static const uint8_t ukbd_trtab[256] = {
+	0, 0, 0, 0, 30, 48, 46, 32,	/* 00 - 07 */
+	18, 33, 34, 35, 23, 36, 37, 38,	/* 08 - 0F */
+	50, 49, 24, 25, 16, 19, 31, 20,	/* 10 - 17 */
+	22, 47, 17, 45, 21, 44, 2, 3,	/* 18 - 1F */
+	4, 5, 6, 7, 8, 9, 10, 11,	/* 20 - 27 */
+	28, 1, 14, 15, 57, 12, 13, 26,	/* 28 - 2F */
+	27, 43, 43, 39, 40, 41, 51, 52,	/* 30 - 37 */
+	53, 58, 59, 60, 61, 62, 63, 64,	/* 38 - 3F */
+	65, 66, 67, 68, 87, 88, 92, 70,	/* 40 - 47 */
+	104, 102, 94, 96, 103, 99, 101, 98,	/* 48 - 4F */
+	97, 100, 95, 69, 91, 55, 74, 78,/* 50 - 57 */
+	89, 79, 80, 81, 75, 76, 77, 71,	/* 58 - 5F */
+	72, 73, 82, 83, 86, 107, 122, NN,	/* 60 - 67 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* 68 - 6F */
+	NN, NN, NN, NN, 115, 108, 111, 113,	/* 70 - 77 */
+	109, 110, 112, 118, 114, 116, 117, 119,	/* 78 - 7F */
+	121, 120, NN, NN, NN, NN, NN, 123,	/* 80 - 87 */
+	124, 125, 126, 127, 128, NN, NN, NN,	/* 88 - 8F */
+	129, 130, NN, NN, NN, NN, NN, NN,	/* 90 - 97 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* 98 - 9F */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* A0 - A7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* A8 - AF */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* B0 - B7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* B8 - BF */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* C0 - C7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* C8 - CF */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* D0 - D7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* D8 - DF */
+	29, 42, 56, 105, 90, 54, 93, 106,	/* E0 - E7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* E8 - EF */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* F0 - F7 */
+	NN, NN, NN, NN, NN, NN, NN, NN,	/* F8 - FF */
+};
+
+static const uint8_t ukbd_boot_desc[] = {
+	0x05, 0x01, 0x09, 0x06, 0xa1,
+	0x01, 0x05, 0x07, 0x19, 0xe0,
+	0x29, 0xe7, 0x15, 0x00, 0x25,
+	0x01, 0x75, 0x01, 0x95, 0x08,
+	0x81, 0x02, 0x95, 0x01, 0x75,
+	0x08, 0x81, 0x01, 0x95, 0x03,
+	0x75, 0x01, 0x05, 0x08, 0x19,
+	0x01, 0x29, 0x03, 0x91, 0x02,
+	0x95, 0x05, 0x75, 0x01, 0x91,
+	0x01, 0x95, 0x06, 0x75, 0x08,
+	0x15, 0x00, 0x26, 0xff, 0x00,
+	0x05, 0x07, 0x19, 0x00, 0x2a,
+	0xff, 0x00, 0x81, 0x00, 0xc0
+};
+
+/* prototypes */
+static void	ukbd_timeout(void *);
+static void	ukbd_set_leds(struct ukbd_softc *, uint8_t);
+static int	ukbd_set_typematic(keyboard_t *, int);
+#ifdef UKBD_EMULATE_ATSCANCODE
+static uint32_t	ukbd_atkeycode(int, int);
+static int	ukbd_key2scan(struct ukbd_softc *, int, int, int);
+#endif
+static uint32_t	ukbd_read_char(keyboard_t *, int);
+static void	ukbd_clear_state(keyboard_t *);
+static int	ukbd_ioctl(keyboard_t *, u_long, caddr_t);
+static int	ukbd_enable(keyboard_t *);
+static int	ukbd_disable(keyboard_t *);
+static void	ukbd_interrupt(struct ukbd_softc *);
+static void	ukbd_event_keyinput(struct ukbd_softc *);
+
+static device_probe_t ukbd_probe;
+static device_attach_t ukbd_attach;
+static device_detach_t ukbd_detach;
+static device_resume_t ukbd_resume;
+
+#ifdef EVDEV_SUPPORT
+static const struct evdev_methods ukbd_evdev_methods = {
+	.ev_event = evdev_ev_kbd_event,
+};
+#endif
+
+static uint8_t
+ukbd_any_key_pressed(struct ukbd_softc *sc)
+{
+	uint8_t i;
+	uint8_t j;
+
+	for (j = i = 0; i < UKBD_NKEYCODE; i++)
+		j |= sc->sc_odata.keycode[i];
+
+	return (j ? 1 : 0);
+}
+
+static void
+ukbd_start_timer(struct ukbd_softc *sc)
+{
+	sbintime_t delay, prec;
+
+	delay = SBT_1MS * sc->sc_delay;
+	sc->sc_co_basetime += delay;
+	/* This is rarely called, so prefer precision to efficiency. */
+	prec = qmin(delay >> 7, SBT_1MS * 10);
+	usb_callout_reset_sbt(&sc->sc_callout, sc->sc_co_basetime, prec,
+	    ukbd_timeout, sc, C_ABSOLUTE);
+}
+
+static void
+ukbd_put_key(struct ukbd_softc *sc, uint32_t key)
+{
+
+	UKBD_LOCK_ASSERT();
+
+	DPRINTF("0x%02x (%d) %s\n", key, key,
+	    (key & KEY_RELEASE) ? "released" : "pressed");
+
+#ifdef EVDEV_SUPPORT
+	if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL) {
+		evdev_push_event(sc->sc_evdev, EV_KEY,
+		    evdev_hid2key(KEY_INDEX(key)), !(key & KEY_RELEASE));
+		evdev_sync(sc->sc_evdev);
+	}
+#endif
+
+	if (sc->sc_inputs < UKBD_IN_BUF_SIZE) {
+		sc->sc_input[sc->sc_inputtail] = key;
+		++(sc->sc_inputs);
+		++(sc->sc_inputtail);
+		if (sc->sc_inputtail >= UKBD_IN_BUF_SIZE) {
+			sc->sc_inputtail = 0;
+		}
+	} else {
+		DPRINTF("input buffer is full\n");
+	}
+}
+
+static void
+ukbd_do_poll(struct ukbd_softc *sc, uint8_t wait)
+{
+
+	UKBD_LOCK_ASSERT();
+	KASSERT((sc->sc_flags & UKBD_FLAG_POLLING) != 0,
+	    ("ukbd_do_poll called when not polling\n"));
+	DPRINTFN(2, "polling\n");
+
+	if (USB_IN_POLLING_MODE_FUNC() == 0) {
+		/*
+		 * In this context the kernel is polling for input,
+		 * but the USB subsystem works in normal interrupt-driven
+		 * mode, so we just wait on the USB threads to do the job.
+		 * Note that we currently hold the Giant, but it's also used
+		 * as the transfer mtx, so we must release it while waiting.
+		 */
+		while (sc->sc_inputs == 0) {
+			/*
+			 * Give USB threads a chance to run.  Note that
+			 * kern_yield performs DROP_GIANT + PICKUP_GIANT.
+			 */
+			kern_yield(PRI_UNCHANGED);
+			if (!wait)
+				break;
+		}
+		return;
+	}
+
+	while (sc->sc_inputs == 0) {
+
+		usbd_transfer_poll(sc->sc_xfer, UKBD_N_TRANSFER);
+
+		/* Delay-optimised support for repetition of keys */
+		if (ukbd_any_key_pressed(sc)) {
+			/* a key is pressed - need timekeeping */
+			DELAY(1000);
+
+			/* 1 millisecond has passed */
+			sc->sc_time_ms += 1;
+		}
+
+		ukbd_interrupt(sc);
+
+		if (!wait)
+			break;
+	}
+}
+
+static int32_t
+ukbd_get_key(struct ukbd_softc *sc, uint8_t wait)
+{
+	int32_t c;
+
+	UKBD_LOCK_ASSERT();
+	KASSERT((USB_IN_POLLING_MODE_FUNC() == 0) ||
+	    (sc->sc_flags & UKBD_FLAG_POLLING) != 0,
+	    ("not polling in kdb or panic\n"));
+
+	if (sc->sc_inputs == 0 &&
+	    (sc->sc_flags & UKBD_FLAG_GONE) == 0) {
+		/* start transfer, if not already started */
+		usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]);
+		usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]);
+	}
+
+	if (sc->sc_flags & UKBD_FLAG_POLLING)
+		ukbd_do_poll(sc, wait);
+
+	if (sc->sc_inputs == 0) {
+		c = -1;
+	} else {
+		c = sc->sc_input[sc->sc_inputhead];
+		--(sc->sc_inputs);
+		++(sc->sc_inputhead);
+		if (sc->sc_inputhead >= UKBD_IN_BUF_SIZE) {
+			sc->sc_inputhead = 0;
+		}
+	}
+	return (c);
+}
+
+static void
+ukbd_interrupt(struct ukbd_softc *sc)
+{
+	struct timeval ctv;
+	uint32_t n_mod;
+	uint32_t o_mod;
+	uint32_t now = sc->sc_time_ms;
+	int32_t dtime;
+	uint8_t key;
+	uint8_t i;
+	uint8_t j;
+
+	UKBD_LOCK_ASSERT();
+
+	if (sc->sc_ndata.keycode[0] == KEY_ERROR)
+		return;
+
+	n_mod = sc->sc_ndata.modifiers;
+	o_mod = sc->sc_odata.modifiers;
+	if (n_mod != o_mod) {
+		for (i = 0; i < UKBD_NMOD; i++) {
+			if ((n_mod & ukbd_mods[i].mask) !=
+			    (o_mod & ukbd_mods[i].mask)) {
+				ukbd_put_key(sc, ukbd_mods[i].key |
+				    ((n_mod & ukbd_mods[i].mask) ?
+				    KEY_PRESS : KEY_RELEASE));
+			}
+		}
+	}
+	/* Check for released keys. */
+	for (i = 0; i < UKBD_NKEYCODE; i++) {
+		key = sc->sc_odata.keycode[i];
+		if (key == 0) {
+			continue;
+		}
+		for (j = 0; j < UKBD_NKEYCODE; j++) {
+			if (sc->sc_ndata.keycode[j] == 0) {
+				continue;
+			}
+			if (key == sc->sc_ndata.keycode[j]) {
+				goto rfound;
+			}
+		}
+		ukbd_put_key(sc, key | KEY_RELEASE);
+rfound:	;
+	}
+
+	/* Check for pressed keys. */
+	for (i = 0; i < UKBD_NKEYCODE; i++) {
+		key = sc->sc_ndata.keycode[i];
+		if (key == 0) {
+			continue;
+		}
+		sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay1;
+		for (j = 0; j < UKBD_NKEYCODE; j++) {
+			if (sc->sc_odata.keycode[j] == 0) {
+				continue;
+			}
+			if (key == sc->sc_odata.keycode[j]) {
+
+				/* key is still pressed */
+
+				sc->sc_ntime[i] = sc->sc_otime[j];
+				dtime = (sc->sc_otime[j] - now);
+
+				if (dtime > 0) {
+					/* time has not elapsed */
+					goto pfound;
+				}
+				sc->sc_ntime[i] = now + sc->sc_kbd.kb_delay2;
+				break;
+			}
+		}
+		if (j < UKBD_NKEYCODE) {
+			/* Old key repeating. */
+			sc->sc_delay = sc->sc_kbd.kb_delay2;
+		} else {
+			/* New key. */
+			microuptime(&ctv);
+			sc->sc_co_basetime = tvtosbt(ctv);
+			sc->sc_delay = sc->sc_kbd.kb_delay1;
+		}
+		ukbd_put_key(sc, key | KEY_PRESS);
+
+		/*
+                 * If any other key is presently down, force its repeat to be
+                 * well in the future (100s).  This makes the last key to be
+                 * pressed do the autorepeat.
+                 */
+		for (j = 0; j != UKBD_NKEYCODE; j++) {
+			if (j != i)
+				sc->sc_ntime[j] = now + (100 * 1000);
+		}
+pfound:	;
+	}
+
+	sc->sc_odata = sc->sc_ndata;
+
+	memcpy(sc->sc_otime, sc->sc_ntime, sizeof(sc->sc_otime));
+
+	ukbd_event_keyinput(sc);
+}
+
+static void
+ukbd_event_keyinput(struct ukbd_softc *sc)
+{
+	int c;
+
+	UKBD_LOCK_ASSERT();
+
+	if ((sc->sc_flags & UKBD_FLAG_POLLING) != 0)
+		return;
+
+	if (sc->sc_inputs == 0)
+		return;
+
+	if (KBD_IS_ACTIVE(&sc->sc_kbd) &&
+	    KBD_IS_BUSY(&sc->sc_kbd)) {
+		/* let the callback function process the input */
+		(sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT,
+		    sc->sc_kbd.kb_callback.kc_arg);
+	} else {
+		/* read and discard the input, no one is waiting for it */
+		do {
+			c = ukbd_read_char(&sc->sc_kbd, 0);
+		} while (c != NOKEY);
+	}
+}
+
+static void
+ukbd_timeout(void *arg)
+{
+	struct ukbd_softc *sc = arg;
+
+	UKBD_LOCK_ASSERT();
+
+	sc->sc_time_ms += sc->sc_delay;
+	sc->sc_delay = 0;
+
+	ukbd_interrupt(sc);
+
+	/* Make sure any leftover key events gets read out */
+	ukbd_event_keyinput(sc);
+
+	if (ukbd_any_key_pressed(sc) || (sc->sc_inputs != 0)) {
+		ukbd_start_timer(sc);
+	}
+}
+
+static uint8_t
+ukbd_apple_fn(uint8_t keycode) {
+	switch (keycode) {
+	case 0x28: return 0x49; /* RETURN -> INSERT */
+	case 0x2a: return 0x4c; /* BACKSPACE -> DEL */
+	case 0x50: return 0x4a; /* LEFT ARROW -> HOME */
+	case 0x4f: return 0x4d; /* RIGHT ARROW -> END */
+	case 0x52: return 0x4b; /* UP ARROW -> PGUP */
+	case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */
+	default: return keycode;
+	}
+}
+
+static uint8_t
+ukbd_apple_swap(uint8_t keycode) {
+	switch (keycode) {
+	case 0x35: return 0x64;
+	case 0x64: return 0x35;
+	default: return keycode;
+	}
+}
+
+static void
+ukbd_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct ukbd_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	uint8_t i;
+	uint8_t offset;
+	uint8_t id;
+	int len;
+
+	UKBD_LOCK_ASSERT();
+
+	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+	pc = usbd_xfer_get_frame(xfer, 0);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		DPRINTF("actlen=%d bytes\n", len);
+
+		if (len == 0) {
+			DPRINTF("zero length data\n");
+			goto tr_setup;
+		}
+
+		if (sc->sc_kbd_id != 0) {
+			/* check and remove HID ID byte */
+			usbd_copy_out(pc, 0, &id, 1);
+			offset = 1;
+			len--;
+			if (len == 0) {
+				DPRINTF("zero length data\n");
+				goto tr_setup;
+			}
+		} else {
+			offset = 0;
+			id = 0;
+		}
+
+		if (len > UKBD_BUFFER_SIZE)
+			len = UKBD_BUFFER_SIZE;
+
+		/* get data */
+		usbd_copy_out(pc, offset, sc->sc_buffer, len);
+
+		/* clear temporary storage */
+		memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+
+		/* scan through HID data */
+		if ((sc->sc_flags & UKBD_FLAG_APPLE_EJECT) &&
+		    (id == sc->sc_id_apple_eject)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_eject))
+				sc->sc_modifiers |= MOD_EJECT;
+			else
+				sc->sc_modifiers &= ~MOD_EJECT;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_APPLE_FN) &&
+		    (id == sc->sc_id_apple_fn)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_apple_fn))
+				sc->sc_modifiers |= MOD_FN;
+			else
+				sc->sc_modifiers &= ~MOD_FN;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_CTRL_L) &&
+		    (id == sc->sc_id_ctrl_l)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_ctrl_l))
+			  sc->	sc_modifiers |= MOD_CONTROL_L;
+			else
+			  sc->	sc_modifiers &= ~MOD_CONTROL_L;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_CTRL_R) &&
+		    (id == sc->sc_id_ctrl_r)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_ctrl_r))
+				sc->sc_modifiers |= MOD_CONTROL_R;
+			else
+				sc->sc_modifiers &= ~MOD_CONTROL_R;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_SHIFT_L) &&
+		    (id == sc->sc_id_shift_l)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_shift_l))
+				sc->sc_modifiers |= MOD_SHIFT_L;
+			else
+				sc->sc_modifiers &= ~MOD_SHIFT_L;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_SHIFT_R) &&
+		    (id == sc->sc_id_shift_r)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_shift_r))
+				sc->sc_modifiers |= MOD_SHIFT_R;
+			else
+				sc->sc_modifiers &= ~MOD_SHIFT_R;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_ALT_L) &&
+		    (id == sc->sc_id_alt_l)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_alt_l))
+				sc->sc_modifiers |= MOD_ALT_L;
+			else
+				sc->sc_modifiers &= ~MOD_ALT_L;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_ALT_R) &&
+		    (id == sc->sc_id_alt_r)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_alt_r))
+				sc->sc_modifiers |= MOD_ALT_R;
+			else
+				sc->sc_modifiers &= ~MOD_ALT_R;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_WIN_L) &&
+		    (id == sc->sc_id_win_l)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_win_l))
+				sc->sc_modifiers |= MOD_WIN_L;
+			else
+				sc->sc_modifiers &= ~MOD_WIN_L;
+		}
+		if ((sc->sc_flags & UKBD_FLAG_WIN_R) &&
+		    (id == sc->sc_id_win_r)) {
+			if (hid_get_data(sc->sc_buffer, len, &sc->sc_loc_win_r))
+				sc->sc_modifiers |= MOD_WIN_R;
+			else
+				sc->sc_modifiers &= ~MOD_WIN_R;
+		}
+
+		sc->sc_ndata.modifiers = sc->sc_modifiers;
+
+		if ((sc->sc_flags & UKBD_FLAG_EVENTS) &&
+		    (id == sc->sc_id_events)) {
+			i = sc->sc_loc_events.count;
+			if (i > UKBD_NKEYCODE)
+				i = UKBD_NKEYCODE;
+			if (i > len)
+				i = len;
+			while (i--) {
+				sc->sc_ndata.keycode[i] =
+				    hid_get_data(sc->sc_buffer + i, len - i,
+				    &sc->sc_loc_events);
+			}
+		}
+
+#ifdef USB_DEBUG
+		DPRINTF("modifiers = 0x%04x\n", (int)sc->sc_modifiers);
+		for (i = 0; i < UKBD_NKEYCODE; i++) {
+			if (sc->sc_ndata.keycode[i]) {
+				DPRINTF("[%d] = 0x%02x\n",
+				    (int)i, (int)sc->sc_ndata.keycode[i]);
+			}
+		}
+#endif
+		if (sc->sc_modifiers & MOD_FN) {
+			for (i = 0; i < UKBD_NKEYCODE; i++) {
+				sc->sc_ndata.keycode[i] = 
+				    ukbd_apple_fn(sc->sc_ndata.keycode[i]);
+			}
+		}
+
+		if (sc->sc_flags & UKBD_FLAG_APPLE_SWAP) {
+			for (i = 0; i < UKBD_NKEYCODE; i++) {
+				sc->sc_ndata.keycode[i] = 
+				    ukbd_apple_swap(sc->sc_ndata.keycode[i]);
+			}
+		}
+
+		ukbd_interrupt(sc);
+
+		if (ukbd_any_key_pressed(sc) != 0) {
+			ukbd_start_timer(sc);
+		}
+
+	case USB_ST_SETUP:
+tr_setup:
+		if (sc->sc_inputs < UKBD_IN_BUF_FULL) {
+			usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+			usbd_transfer_submit(xfer);
+		} else {
+			DPRINTF("input queue is full!\n");
+		}
+		break;
+
+	default:			/* Error */
+		DPRINTF("error=%s\n", usbd_errstr(error));
+
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		break;
+	}
+}
+
+static void
+ukbd_set_leds_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct ukbd_softc *sc = usbd_xfer_softc(xfer);
+	struct usb_device_request req;
+	struct usb_page_cache *pc;
+	uint8_t id;
+	uint8_t any;
+	int len;
+
+	UKBD_LOCK_ASSERT();
+
+#ifdef USB_DEBUG
+	if (ukbd_no_leds)
+		return;
+#endif
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+	case USB_ST_SETUP:
+		if (!(sc->sc_flags & UKBD_FLAG_SET_LEDS))
+			break;
+		sc->sc_flags &= ~UKBD_FLAG_SET_LEDS;
+
+		req.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+		req.bRequest = UR_SET_REPORT;
+		USETW2(req.wValue, UHID_OUTPUT_REPORT, 0);
+		req.wIndex[0] = sc->sc_iface_no;
+		req.wIndex[1] = 0;
+		req.wLength[1] = 0;
+
+		memset(sc->sc_buffer, 0, UKBD_BUFFER_SIZE);
+
+		id = 0;
+		any = 0;
+
+		/* Assumption: All led bits must be in the same ID. */
+
+		if (sc->sc_flags & UKBD_FLAG_NUMLOCK) {
+			if (sc->sc_leds & NLKED) {
+				hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+				    &sc->sc_loc_numlock, 1);
+			}
+			id = sc->sc_id_numlock;
+			any = 1;
+		}
+
+		if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK) {
+			if (sc->sc_leds & SLKED) {
+				hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+				    &sc->sc_loc_scrolllock, 1);
+			}
+			id = sc->sc_id_scrolllock;
+			any = 1;
+		}
+
+		if (sc->sc_flags & UKBD_FLAG_CAPSLOCK) {
+			if (sc->sc_leds & CLKED) {
+				hid_put_data_unsigned(sc->sc_buffer + 1, UKBD_BUFFER_SIZE - 1,
+				    &sc->sc_loc_capslock, 1);
+			}
+			id = sc->sc_id_capslock;
+			any = 1;
+		}
+
+		/* if no leds, nothing to do */
+		if (!any)
+			break;
+
+#ifdef EVDEV_SUPPORT
+		if (sc->sc_evdev != NULL)
+			evdev_push_leds(sc->sc_evdev, sc->sc_leds);
+#endif
+
+		/* range check output report length */
+		len = sc->sc_led_size;
+		if (len > (UKBD_BUFFER_SIZE - 1))
+			len = (UKBD_BUFFER_SIZE - 1);
+
+		/* check if we need to prefix an ID byte */
+		sc->sc_buffer[0] = id;
+
+		pc = usbd_xfer_get_frame(xfer, 1);
+		if (id != 0) {
+			len++;
+			usbd_copy_in(pc, 0, sc->sc_buffer, len);
+		} else {
+			usbd_copy_in(pc, 0, sc->sc_buffer + 1, len);
+		}
+		req.wLength[0] = len;
+		usbd_xfer_set_frame_len(xfer, 1, len);
+
+		DPRINTF("len=%d, id=%d\n", len, id);
+
+		/* setup control request last */
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_in(pc, 0, &req, sizeof(req));
+		usbd_xfer_set_frame_len(xfer, 0, sizeof(req));
+
+		/* start data transfer */
+		usbd_xfer_set_frames(xfer, 2);
+		usbd_transfer_submit(xfer);
+		break;
+
+	default:			/* Error */
+		DPRINTFN(1, "error=%s\n", usbd_errstr(error));
+		break;
+	}
+}
+
+static const struct usb_config ukbd_config[UKBD_N_TRANSFER] = {
+
+	[UKBD_INTR_DT_0] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+		.bufsize = 0,	/* use wMaxPacketSize */
+		.callback = &ukbd_intr_callback,
+	},
+
+	[UKBD_INTR_DT_1] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+		.bufsize = 0,	/* use wMaxPacketSize */
+		.callback = &ukbd_intr_callback,
+	},
+
+	[UKBD_CTRL_LED] = {
+		.type = UE_CONTROL,
+		.endpoint = 0x00,	/* Control pipe */
+		.direction = UE_DIR_ANY,
+		.bufsize = sizeof(struct usb_device_request) + UKBD_BUFFER_SIZE,
+		.callback = &ukbd_set_leds_callback,
+		.timeout = 1000,	/* 1 second */
+	},
+};
+
+/* A match on these entries will load ukbd */
+static const STRUCT_USB_HOST_ID __used ukbd_devs[] = {
+	{USB_IFACE_CLASS(UICLASS_HID),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+	 USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),},
+};
+
+static int
+ukbd_probe(device_t dev)
+{
+	keyboard_switch_t *sw = kbd_get_switch(UKBD_DRIVER_NAME);
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	void *d_ptr;
+	int error;
+	uint16_t d_len;
+
+	UKBD_LOCK_ASSERT();
+	DPRINTFN(11, "\n");
+
+	if (sw == NULL) {
+		return (ENXIO);
+	}
+	if (uaa->usb_mode != USB_MODE_HOST) {
+		return (ENXIO);
+	}
+
+	if (uaa->info.bInterfaceClass != UICLASS_HID)
+		return (ENXIO);
+
+	if (usb_test_quirk(uaa, UQ_KBD_IGNORE))
+		return (ENXIO);
+
+	if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+	    (uaa->info.bInterfaceProtocol == UIPROTO_BOOT_KEYBOARD))
+		return (BUS_PROBE_DEFAULT);
+
+	error = usbd_req_get_hid_desc(uaa->device, NULL,
+	    &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+	if (error)
+		return (ENXIO);
+
+	if (hid_is_keyboard(d_ptr, d_len)) {
+		if (hid_is_mouse(d_ptr, d_len)) {
+			/*
+			 * NOTE: We currently don't support USB mouse
+			 * and USB keyboard on the same USB endpoint.
+			 * Let "ums" driver win.
+			 */
+			error = ENXIO;
+		} else {
+			error = BUS_PROBE_DEFAULT;
+		}
+	} else {
+		error = ENXIO;
+	}
+	free(d_ptr, M_TEMP);
+	return (error);
+}
+
+static void
+ukbd_parse_hid(struct ukbd_softc *sc, const uint8_t *ptr, uint32_t len)
+{
+	uint32_t flags;
+
+	/* reset detected bits */
+	sc->sc_flags &= ~UKBD_FLAG_HID_MASK;
+
+	/* check if there is an ID byte */
+	sc->sc_kbd_size = hid_report_size(ptr, len,
+	    hid_input, &sc->sc_kbd_id);
+
+	/* investigate if this is an Apple Keyboard */
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT),
+	    hid_input, 0, &sc->sc_loc_apple_eject, &flags,
+	    &sc->sc_id_apple_eject)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_APPLE_EJECT | 
+			    UKBD_FLAG_APPLE_SWAP;
+		DPRINTFN(1, "Found Apple eject-key\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(0xFFFF, 0x0003),
+	    hid_input, 0, &sc->sc_loc_apple_fn, &flags,
+	    &sc->sc_id_apple_fn)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_APPLE_FN;
+		DPRINTFN(1, "Found Apple FN-key\n");
+	}
+	/* figure out some keys */
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE0),
+	    hid_input, 0, &sc->sc_loc_ctrl_l, &flags,
+	    &sc->sc_id_ctrl_l)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_CTRL_L;
+		DPRINTFN(1, "Found left control\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE4),
+	    hid_input, 0, &sc->sc_loc_ctrl_r, &flags,
+	    &sc->sc_id_ctrl_r)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_CTRL_R;
+		DPRINTFN(1, "Found right control\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE1),
+	    hid_input, 0, &sc->sc_loc_shift_l, &flags,
+	    &sc->sc_id_shift_l)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_SHIFT_L;
+		DPRINTFN(1, "Found left shift\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE5),
+	    hid_input, 0, &sc->sc_loc_shift_r, &flags,
+	    &sc->sc_id_shift_r)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_SHIFT_R;
+		DPRINTFN(1, "Found right shift\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE2),
+	    hid_input, 0, &sc->sc_loc_alt_l, &flags,
+	    &sc->sc_id_alt_l)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_ALT_L;
+		DPRINTFN(1, "Found left alt\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE6),
+	    hid_input, 0, &sc->sc_loc_alt_r, &flags,
+	    &sc->sc_id_alt_r)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_ALT_R;
+		DPRINTFN(1, "Found right alt\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE3),
+	    hid_input, 0, &sc->sc_loc_win_l, &flags,
+	    &sc->sc_id_win_l)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_WIN_L;
+		DPRINTFN(1, "Found left GUI\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0xE7),
+	    hid_input, 0, &sc->sc_loc_win_r, &flags,
+	    &sc->sc_id_win_r)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_WIN_R;
+		DPRINTFN(1, "Found right GUI\n");
+	}
+	/* figure out event buffer */
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_KEYBOARD, 0x00),
+	    hid_input, 0, &sc->sc_loc_events, &flags,
+	    &sc->sc_id_events)) {
+		if (flags & HIO_VARIABLE) {
+			DPRINTFN(1, "Ignoring keyboard event control\n");
+		} else {
+			sc->sc_flags |= UKBD_FLAG_EVENTS;
+			DPRINTFN(1, "Found keyboard event array\n");
+		}
+	}
+
+	/* figure out leds on keyboard */
+	sc->sc_led_size = hid_report_size(ptr, len,
+	    hid_output, NULL);
+
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_LEDS, 0x01),
+	    hid_output, 0, &sc->sc_loc_numlock, &flags,
+	    &sc->sc_id_numlock)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_NUMLOCK;
+		DPRINTFN(1, "Found keyboard numlock\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_LEDS, 0x02),
+	    hid_output, 0, &sc->sc_loc_capslock, &flags,
+	    &sc->sc_id_capslock)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_CAPSLOCK;
+		DPRINTFN(1, "Found keyboard capslock\n");
+	}
+	if (hid_locate(ptr, len,
+	    HID_USAGE2(HUP_LEDS, 0x03),
+	    hid_output, 0, &sc->sc_loc_scrolllock, &flags,
+	    &sc->sc_id_scrolllock)) {
+		if (flags & HIO_VARIABLE)
+			sc->sc_flags |= UKBD_FLAG_SCROLLLOCK;
+		DPRINTFN(1, "Found keyboard scrolllock\n");
+	}
+}
+
+static int
+ukbd_attach(device_t dev)
+{
+	struct ukbd_softc *sc = device_get_softc(dev);
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	int unit = device_get_unit(dev);
+	keyboard_t *kbd = &sc->sc_kbd;
+	void *hid_ptr = NULL;
+	usb_error_t err;
+	uint16_t n;
+	uint16_t hid_len;
+#ifdef EVDEV_SUPPORT
+	struct evdev_dev *evdev;
+	int i;
+#endif
+#ifdef USB_DEBUG
+	int rate;
+#endif
+	UKBD_LOCK_ASSERT();
+
+	kbd_init_struct(kbd, UKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0);
+
+	kbd->kb_data = (void *)sc;
+
+	device_set_usb_desc(dev);
+
+	sc->sc_udev = uaa->device;
+	sc->sc_iface = uaa->iface;
+	sc->sc_iface_index = uaa->info.bIfaceIndex;
+	sc->sc_iface_no = uaa->info.bIfaceNum;
+	sc->sc_mode = K_XLATE;
+
+	usb_callout_init_mtx(&sc->sc_callout, &Giant, 0);
+
+#ifdef UKBD_NO_POLLING
+	err = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, ukbd_config,
+	    UKBD_N_TRANSFER, sc, &Giant);
+#else
+	/*
+	 * Setup the UKBD USB transfers one by one, so they are memory
+	 * independent which allows for handling panics triggered by
+	 * the keyboard driver itself, typically via CTRL+ALT+ESC
+	 * sequences. Or if the USB keyboard driver was processing a
+	 * key at the moment of panic.
+	 */
+	for (n = 0; n != UKBD_N_TRANSFER; n++) {
+		err = usbd_transfer_setup(uaa->device,
+		    &uaa->info.bIfaceIndex, sc->sc_xfer + n, ukbd_config + n,
+		    1, sc, &Giant);
+		if (err)
+			break;
+	}
+#endif
+
+	if (err) {
+		DPRINTF("error=%s\n", usbd_errstr(err));
+		goto detach;
+	}
+	/* setup default keyboard maps */
+
+	sc->sc_keymap = key_map;
+	sc->sc_accmap = accent_map;
+	for (n = 0; n < UKBD_NFKEY; n++) {
+		sc->sc_fkeymap[n] = fkey_tab[n];
+	}
+
+	kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap,
+	    sc->sc_fkeymap, UKBD_NFKEY);
+
+	KBD_FOUND_DEVICE(kbd);
+
+	ukbd_clear_state(kbd);
+
+	/*
+	 * FIXME: set the initial value for lock keys in "sc_state"
+	 * according to the BIOS data?
+	 */
+	KBD_PROBE_DONE(kbd);
+
+	/* get HID descriptor */
+	err = usbd_req_get_hid_desc(uaa->device, NULL, &hid_ptr,
+	    &hid_len, M_TEMP, uaa->info.bIfaceIndex);
+
+	if (err == 0) {
+		DPRINTF("Parsing HID descriptor of %d bytes\n",
+		    (int)hid_len);
+
+		ukbd_parse_hid(sc, hid_ptr, hid_len);
+
+		free(hid_ptr, M_TEMP);
+	}
+
+	/* check if we should use the boot protocol */
+	if (usb_test_quirk(uaa, UQ_KBD_BOOTPROTO) ||
+	    (err != 0) || (!(sc->sc_flags & UKBD_FLAG_EVENTS))) {
+
+		DPRINTF("Forcing boot protocol\n");
+
+		err = usbd_req_set_protocol(sc->sc_udev, NULL, 
+			sc->sc_iface_index, 0);
+
+		if (err != 0) {
+			DPRINTF("Set protocol error=%s (ignored)\n",
+			    usbd_errstr(err));
+		}
+
+		ukbd_parse_hid(sc, ukbd_boot_desc, sizeof(ukbd_boot_desc));
+	}
+
+	/* ignore if SETIDLE fails, hence it is not crucial */
+	usbd_req_set_idle(sc->sc_udev, NULL, sc->sc_iface_index, 0, 0);
+
+	ukbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state);
+
+	KBD_INIT_DONE(kbd);
+
+	if (kbd_register(kbd) < 0) {
+		goto detach;
+	}
+	KBD_CONFIG_DONE(kbd);
+
+	ukbd_enable(kbd);
+
+#ifdef KBD_INSTALL_CDEV
+	if (kbd_attach(kbd)) {
+		goto detach;
+	}
+#endif
+
+#ifdef EVDEV_SUPPORT
+	evdev = evdev_alloc();
+	evdev_set_name(evdev, device_get_desc(dev));
+	evdev_set_phys(evdev, device_get_nameunit(dev));
+	evdev_set_id(evdev, BUS_USB, uaa->info.idVendor,
+	   uaa->info.idProduct, 0);
+	evdev_set_serial(evdev, usb_get_serial(uaa->device));
+	evdev_set_methods(evdev, kbd, &ukbd_evdev_methods);
+	evdev_support_event(evdev, EV_SYN);
+	evdev_support_event(evdev, EV_KEY);
+	if (sc->sc_flags & (UKBD_FLAG_NUMLOCK | UKBD_FLAG_CAPSLOCK |
+			    UKBD_FLAG_SCROLLLOCK))
+		evdev_support_event(evdev, EV_LED);
+	evdev_support_event(evdev, EV_REP);
+
+	for (i = 0x00; i <= 0xFF; i++)
+		evdev_support_key(evdev, evdev_hid2key(i));
+	if (sc->sc_flags & UKBD_FLAG_NUMLOCK)
+		evdev_support_led(evdev, LED_NUML);
+	if (sc->sc_flags & UKBD_FLAG_CAPSLOCK)
+		evdev_support_led(evdev, LED_CAPSL);
+	if (sc->sc_flags & UKBD_FLAG_SCROLLLOCK)
+		evdev_support_led(evdev, LED_SCROLLL);
+
+	if (evdev_register(evdev))
+		evdev_free(evdev);
+	else
+		sc->sc_evdev = evdev;
+#endif
+
+	sc->sc_flags |= UKBD_FLAG_ATTACHED;
+
+	if (bootverbose) {
+		genkbd_diag(kbd, bootverbose);
+	}
+
+#ifdef USB_DEBUG
+	/* check for polling rate override */
+	rate = ukbd_pollrate;
+	if (rate > 0) {
+		if (rate > 1000)
+			rate = 1;
+		else
+			rate = 1000 / rate;
+
+		/* set new polling interval in ms */
+		usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_0], rate);
+		usbd_xfer_set_interval(sc->sc_xfer[UKBD_INTR_DT_1], rate);
+	}
+#endif
+	/* start the keyboard */
+	usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_0]);
+	usbd_transfer_start(sc->sc_xfer[UKBD_INTR_DT_1]);
+
+	return (0);			/* success */
+
+detach:
+	ukbd_detach(dev);
+	return (ENXIO);			/* error */
+}
+
+static int
+ukbd_detach(device_t dev)
+{
+	struct ukbd_softc *sc = device_get_softc(dev);
+	int error;
+
+	UKBD_LOCK_ASSERT();
+
+	DPRINTF("\n");
+
+	sc->sc_flags |= UKBD_FLAG_GONE;
+
+	usb_callout_stop(&sc->sc_callout);
+
+	/* kill any stuck keys */
+	if (sc->sc_flags & UKBD_FLAG_ATTACHED) {
+		/* stop receiving events from the USB keyboard */
+		usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_0]);
+		usbd_transfer_stop(sc->sc_xfer[UKBD_INTR_DT_1]);
+
+		/* release all leftover keys, if any */
+		memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+
+		/* process releasing of all keys */
+		ukbd_interrupt(sc);
+	}
+
+	ukbd_disable(&sc->sc_kbd);
+
+#ifdef KBD_INSTALL_CDEV
+	if (sc->sc_flags & UKBD_FLAG_ATTACHED) {
+		error = kbd_detach(&sc->sc_kbd);
+		if (error) {
+			/* usb attach cannot return an error */
+			device_printf(dev, "WARNING: kbd_detach() "
+			    "returned non-zero! (ignored)\n");
+		}
+	}
+#endif
+
+#ifdef EVDEV_SUPPORT
+	evdev_free(sc->sc_evdev);
+#endif
+
+	if (KBD_IS_CONFIGURED(&sc->sc_kbd)) {
+		error = kbd_unregister(&sc->sc_kbd);
+		if (error) {
+			/* usb attach cannot return an error */
+			device_printf(dev, "WARNING: kbd_unregister() "
+			    "returned non-zero! (ignored)\n");
+		}
+	}
+	sc->sc_kbd.kb_flags = 0;
+
+	usbd_transfer_unsetup(sc->sc_xfer, UKBD_N_TRANSFER);
+
+	usb_callout_drain(&sc->sc_callout);
+
+	DPRINTF("%s: disconnected\n",
+	    device_get_nameunit(dev));
+
+	return (0);
+}
+
+static int
+ukbd_resume(device_t dev)
+{
+	struct ukbd_softc *sc = device_get_softc(dev);
+
+	UKBD_LOCK_ASSERT();
+
+	ukbd_clear_state(&sc->sc_kbd);
+
+	return (0);
+}
+
+/* early keyboard probe, not supported */
+static int
+ukbd_configure(int flags)
+{
+	return (0);
+}
+
+/* detect a keyboard, not used */
+static int
+ukbd__probe(int unit, void *arg, int flags)
+{
+	return (ENXIO);
+}
+
+/* reset and initialize the device, not used */
+static int
+ukbd_init(int unit, keyboard_t **kbdp, void *arg, int flags)
+{
+	return (ENXIO);
+}
+
+/* test the interface to the device, not used */
+static int
+ukbd_test_if(keyboard_t *kbd)
+{
+	return (0);
+}
+
+/* finish using this keyboard, not used */
+static int
+ukbd_term(keyboard_t *kbd)
+{
+	return (ENXIO);
+}
+
+/* keyboard interrupt routine, not used */
+static int
+ukbd_intr(keyboard_t *kbd, void *arg)
+{
+	return (0);
+}
+
+/* lock the access to the keyboard, not used */
+static int
+ukbd_lock(keyboard_t *kbd, int lock)
+{
+	return (1);
+}
+
+/*
+ * Enable the access to the device; until this function is called,
+ * the client cannot read from the keyboard.
+ */
+static int
+ukbd_enable(keyboard_t *kbd)
+{
+
+	UKBD_LOCK();
+	KBD_ACTIVATE(kbd);
+	UKBD_UNLOCK();
+
+	return (0);
+}
+
+/* disallow the access to the device */
+static int
+ukbd_disable(keyboard_t *kbd)
+{
+
+	UKBD_LOCK();
+	KBD_DEACTIVATE(kbd);
+	UKBD_UNLOCK();
+
+	return (0);
+}
+
+/* check if data is waiting */
+/* Currently unused. */
+static int
+ukbd_check(keyboard_t *kbd)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+
+	UKBD_LOCK_ASSERT();
+
+	if (!KBD_IS_ACTIVE(kbd))
+		return (0);
+
+	if (sc->sc_flags & UKBD_FLAG_POLLING)
+		ukbd_do_poll(sc, 0);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+	if (sc->sc_buffered_char[0]) {
+		return (1);
+	}
+#endif
+	if (sc->sc_inputs > 0) {
+		return (1);
+	}
+	return (0);
+}
+
+/* check if char is waiting */
+static int
+ukbd_check_char_locked(keyboard_t *kbd)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+
+	UKBD_LOCK_ASSERT();
+
+	if (!KBD_IS_ACTIVE(kbd))
+		return (0);
+
+	if ((sc->sc_composed_char > 0) &&
+	    (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) {
+		return (1);
+	}
+	return (ukbd_check(kbd));
+}
+
+static int
+ukbd_check_char(keyboard_t *kbd)
+{
+	int result;
+
+	UKBD_LOCK();
+	result = ukbd_check_char_locked(kbd);
+	UKBD_UNLOCK();
+
+	return (result);
+}
+
+/* read one byte from the keyboard if it's allowed */
+/* Currently unused. */
+static int
+ukbd_read(keyboard_t *kbd, int wait)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+	int32_t usbcode;
+#ifdef UKBD_EMULATE_ATSCANCODE
+	uint32_t keycode;
+	uint32_t scancode;
+
+#endif
+
+	UKBD_LOCK_ASSERT();
+
+	if (!KBD_IS_ACTIVE(kbd))
+		return (-1);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+	if (sc->sc_buffered_char[0]) {
+		scancode = sc->sc_buffered_char[0];
+		if (scancode & SCAN_PREFIX) {
+			sc->sc_buffered_char[0] &= ~SCAN_PREFIX;
+			return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+		}
+		sc->sc_buffered_char[0] = sc->sc_buffered_char[1];
+		sc->sc_buffered_char[1] = 0;
+		return (scancode);
+	}
+#endif					/* UKBD_EMULATE_ATSCANCODE */
+
+	/* XXX */
+	usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1);
+	if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1))
+		return (-1);
+
+	++(kbd->kb_count);
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+	keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.modifiers);
+	if (keycode == NN) {
+		return -1;
+	}
+	return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers,
+	    (usbcode & KEY_RELEASE)));
+#else					/* !UKBD_EMULATE_ATSCANCODE */
+	return (usbcode);
+#endif					/* UKBD_EMULATE_ATSCANCODE */
+}
+
+/* read char from the keyboard */
+static uint32_t
+ukbd_read_char_locked(keyboard_t *kbd, int wait)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+	uint32_t action;
+	uint32_t keycode;
+	int32_t usbcode;
+#ifdef UKBD_EMULATE_ATSCANCODE
+	uint32_t scancode;
+#endif
+
+	UKBD_LOCK_ASSERT();
+
+	if (!KBD_IS_ACTIVE(kbd))
+		return (NOKEY);
+
+next_code:
+
+	/* do we have a composed char to return ? */
+
+	if ((sc->sc_composed_char > 0) &&
+	    (!(sc->sc_flags & UKBD_FLAG_COMPOSE))) {
+
+		action = sc->sc_composed_char;
+		sc->sc_composed_char = 0;
+
+		if (action > 0xFF) {
+			goto errkey;
+		}
+		goto done;
+	}
+#ifdef UKBD_EMULATE_ATSCANCODE
+
+	/* do we have a pending raw scan code? */
+
+	if (sc->sc_mode == K_RAW) {
+		scancode = sc->sc_buffered_char[0];
+		if (scancode) {
+			if (scancode & SCAN_PREFIX) {
+				sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX);
+				return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+			}
+			sc->sc_buffered_char[0] = sc->sc_buffered_char[1];
+			sc->sc_buffered_char[1] = 0;
+			return (scancode);
+		}
+	}
+#endif					/* UKBD_EMULATE_ATSCANCODE */
+
+	/* see if there is something in the keyboard port */
+	/* XXX */
+	usbcode = ukbd_get_key(sc, (wait == FALSE) ? 0 : 1);
+	if (usbcode == -1) {
+		return (NOKEY);
+	}
+	++kbd->kb_count;
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+	/* USB key index -> key code -> AT scan code */
+	keycode = ukbd_atkeycode(usbcode, sc->sc_ndata.modifiers);
+	if (keycode == NN) {
+		return (NOKEY);
+	}
+	/* return an AT scan code for the K_RAW mode */
+	if (sc->sc_mode == K_RAW) {
+		return (ukbd_key2scan(sc, keycode, sc->sc_ndata.modifiers,
+		    (usbcode & KEY_RELEASE)));
+	}
+#else					/* !UKBD_EMULATE_ATSCANCODE */
+
+	/* return the byte as is for the K_RAW mode */
+	if (sc->sc_mode == K_RAW) {
+		return (usbcode);
+	}
+	/* USB key index -> key code */
+	keycode = ukbd_trtab[KEY_INDEX(usbcode)];
+	if (keycode == NN) {
+		return (NOKEY);
+	}
+#endif					/* UKBD_EMULATE_ATSCANCODE */
+
+	switch (keycode) {
+	case 0x38:			/* left alt (compose key) */
+		if (usbcode & KEY_RELEASE) {
+			if (sc->sc_flags & UKBD_FLAG_COMPOSE) {
+				sc->sc_flags &= ~UKBD_FLAG_COMPOSE;
+
+				if (sc->sc_composed_char > 0xFF) {
+					sc->sc_composed_char = 0;
+				}
+			}
+		} else {
+			if (!(sc->sc_flags & UKBD_FLAG_COMPOSE)) {
+				sc->sc_flags |= UKBD_FLAG_COMPOSE;
+				sc->sc_composed_char = 0;
+			}
+		}
+		break;
+	}
+
+	/* return the key code in the K_CODE mode */
+	if (usbcode & KEY_RELEASE) {
+		keycode |= SCAN_RELEASE;
+	}
+	if (sc->sc_mode == K_CODE) {
+		return (keycode);
+	}
+	/* compose a character code */
+	if (sc->sc_flags & UKBD_FLAG_COMPOSE) {
+		switch (keycode) {
+			/* key pressed, process it */
+		case 0x47:
+		case 0x48:
+		case 0x49:		/* keypad 7,8,9 */
+			sc->sc_composed_char *= 10;
+			sc->sc_composed_char += keycode - 0x40;
+			goto check_composed;
+
+		case 0x4B:
+		case 0x4C:
+		case 0x4D:		/* keypad 4,5,6 */
+			sc->sc_composed_char *= 10;
+			sc->sc_composed_char += keycode - 0x47;
+			goto check_composed;
+
+		case 0x4F:
+		case 0x50:
+		case 0x51:		/* keypad 1,2,3 */
+			sc->sc_composed_char *= 10;
+			sc->sc_composed_char += keycode - 0x4E;
+			goto check_composed;
+
+		case 0x52:		/* keypad 0 */
+			sc->sc_composed_char *= 10;
+			goto check_composed;
+
+			/* key released, no interest here */
+		case SCAN_RELEASE | 0x47:
+		case SCAN_RELEASE | 0x48:
+		case SCAN_RELEASE | 0x49:	/* keypad 7,8,9 */
+		case SCAN_RELEASE | 0x4B:
+		case SCAN_RELEASE | 0x4C:
+		case SCAN_RELEASE | 0x4D:	/* keypad 4,5,6 */
+		case SCAN_RELEASE | 0x4F:
+		case SCAN_RELEASE | 0x50:
+		case SCAN_RELEASE | 0x51:	/* keypad 1,2,3 */
+		case SCAN_RELEASE | 0x52:	/* keypad 0 */
+			goto next_code;
+
+		case 0x38:		/* left alt key */
+			break;
+
+		default:
+			if (sc->sc_composed_char > 0) {
+				sc->sc_flags &= ~UKBD_FLAG_COMPOSE;
+				sc->sc_composed_char = 0;
+				goto errkey;
+			}
+			break;
+		}
+	}
+	/* keycode to key action */
+	action = genkbd_keyaction(kbd, SCAN_CHAR(keycode),
+	    (keycode & SCAN_RELEASE),
+	    &sc->sc_state, &sc->sc_accents);
+	if (action == NOKEY) {
+		goto next_code;
+	}
+done:
+	return (action);
+
+check_composed:
+	if (sc->sc_composed_char <= 0xFF) {
+		goto next_code;
+	}
+errkey:
+	return (ERRKEY);
+}
+
+/* Currently wait is always false. */
+static uint32_t
+ukbd_read_char(keyboard_t *kbd, int wait)
+{
+	uint32_t keycode;
+
+	UKBD_LOCK();
+	keycode = ukbd_read_char_locked(kbd, wait);
+	UKBD_UNLOCK();
+
+	return (keycode);
+}
+
+/* some useful control functions */
+static int
+ukbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+	int i;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+    defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+	int ival;
+
+#endif
+
+	UKBD_LOCK_ASSERT();
+
+	switch (cmd) {
+	case KDGKBMODE:		/* get keyboard mode */
+		*(int *)arg = sc->sc_mode;
+		break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+    defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+	case _IO('K', 7):
+		ival = IOCPARM_IVAL(arg);
+		arg = (caddr_t)&ival;
+		/* FALLTHROUGH */
+#endif
+	case KDSKBMODE:		/* set keyboard mode */
+		switch (*(int *)arg) {
+		case K_XLATE:
+			if (sc->sc_mode != K_XLATE) {
+				/* make lock key state and LED state match */
+				sc->sc_state &= ~LOCK_MASK;
+				sc->sc_state |= KBD_LED_VAL(kbd);
+			}
+			/* FALLTHROUGH */
+		case K_RAW:
+		case K_CODE:
+			if (sc->sc_mode != *(int *)arg) {
+				if ((sc->sc_flags & UKBD_FLAG_POLLING) == 0)
+					ukbd_clear_state(kbd);
+				sc->sc_mode = *(int *)arg;
+			}
+			break;
+		default:
+			return (EINVAL);
+		}
+		break;
+
+	case KDGETLED:			/* get keyboard LED */
+		*(int *)arg = KBD_LED_VAL(kbd);
+		break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+    defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+	case _IO('K', 66):
+		ival = IOCPARM_IVAL(arg);
+		arg = (caddr_t)&ival;
+		/* FALLTHROUGH */
+#endif
+	case KDSETLED:			/* set keyboard LED */
+		/* NOTE: lock key state in "sc_state" won't be changed */
+		if (*(int *)arg & ~LOCK_MASK)
+			return (EINVAL);
+
+		i = *(int *)arg;
+
+		/* replace CAPS LED with ALTGR LED for ALTGR keyboards */
+		if (sc->sc_mode == K_XLATE &&
+		    kbd->kb_keymap->n_keys > ALTGR_OFFSET) {
+			if (i & ALKED)
+				i |= CLKED;
+			else
+				i &= ~CLKED;
+		}
+		if (KBD_HAS_DEVICE(kbd))
+			ukbd_set_leds(sc, i);
+
+		KBD_LED_VAL(kbd) = *(int *)arg;
+		break;
+	case KDGKBSTATE:		/* get lock key state */
+		*(int *)arg = sc->sc_state & LOCK_MASK;
+		break;
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+    defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+	case _IO('K', 20):
+		ival = IOCPARM_IVAL(arg);
+		arg = (caddr_t)&ival;
+		/* FALLTHROUGH */
+#endif
+	case KDSKBSTATE:		/* set lock key state */
+		if (*(int *)arg & ~LOCK_MASK) {
+			return (EINVAL);
+		}
+		sc->sc_state &= ~LOCK_MASK;
+		sc->sc_state |= *(int *)arg;
+
+		/* set LEDs and quit */
+		return (ukbd_ioctl(kbd, KDSETLED, arg));
+
+	case KDSETREPEAT:		/* set keyboard repeat rate (new
+					 * interface) */
+		if (!KBD_HAS_DEVICE(kbd)) {
+			return (0);
+		}
+		/*
+		 * Convert negative, zero and tiny args to the same limits
+		 * as atkbd.  We could support delays of 1 msec, but
+		 * anything much shorter than the shortest atkbd value
+		 * of 250.34 is almost unusable as well as incompatible.
+		 */
+		kbd->kb_delay1 = imax(((int *)arg)[0], 250);
+		kbd->kb_delay2 = imax(((int *)arg)[1], 34);
+#ifdef EVDEV_SUPPORT
+		if (sc->sc_evdev != NULL)
+			evdev_push_repeats(sc->sc_evdev, kbd);
+#endif
+		return (0);
+
+#if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \
+    defined(COMPAT_FREEBSD4) || defined(COMPAT_43)
+	case _IO('K', 67):
+		ival = IOCPARM_IVAL(arg);
+		arg = (caddr_t)&ival;
+		/* FALLTHROUGH */
+#endif
+	case KDSETRAD:			/* set keyboard repeat rate (old
+					 * interface) */
+		return (ukbd_set_typematic(kbd, *(int *)arg));
+
+	case PIO_KEYMAP:		/* set keyboard translation table */
+	case OPIO_KEYMAP:		/* set keyboard translation table
+					 * (compat) */
+	case PIO_KEYMAPENT:		/* set keyboard translation table
+					 * entry */
+	case PIO_DEADKEYMAP:		/* set accent key translation table */
+		sc->sc_accents = 0;
+		/* FALLTHROUGH */
+	default:
+		return (genkbd_commonioctl(kbd, cmd, arg));
+	}
+
+	return (0);
+}
+
+static int
+ukbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
+{
+	int result;
+
+	/*
+	 * XXX Check if someone is calling us from a critical section:
+	 */
+	if (curthread->td_critnest != 0)
+		return (EDEADLK);
+
+	/*
+	 * XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any
+	 * context where printf(9) can be called, which among other things
+	 * includes interrupt filters and threads with any kinds of locks
+	 * already held.  For this reason it would be dangerous to acquire
+	 * the Giant here unconditionally.  On the other hand we have to
+	 * have it to handle the ioctl.
+	 * So we make our best effort to auto-detect whether we can grab
+	 * the Giant or not.  Blame syscons(4) for this.
+	 */
+	switch (cmd) {
+	case KDGKBSTATE:
+	case KDSKBSTATE:
+	case KDSETLED:
+		if (!mtx_owned(&Giant) && !USB_IN_POLLING_MODE_FUNC())
+			return (EDEADLK);	/* best I could come up with */
+		/* FALLTHROUGH */
+	default:
+		UKBD_LOCK();
+		result = ukbd_ioctl_locked(kbd, cmd, arg);
+		UKBD_UNLOCK();
+		return (result);
+	}
+}
+
+
+/* clear the internal state of the keyboard */
+static void
+ukbd_clear_state(keyboard_t *kbd)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+
+	UKBD_LOCK_ASSERT();
+
+	sc->sc_flags &= ~(UKBD_FLAG_COMPOSE | UKBD_FLAG_POLLING);
+	sc->sc_state &= LOCK_MASK;	/* preserve locking key state */
+	sc->sc_accents = 0;
+	sc->sc_composed_char = 0;
+#ifdef UKBD_EMULATE_ATSCANCODE
+	sc->sc_buffered_char[0] = 0;
+	sc->sc_buffered_char[1] = 0;
+#endif
+	memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata));
+	memset(&sc->sc_odata, 0, sizeof(sc->sc_odata));
+	memset(&sc->sc_ntime, 0, sizeof(sc->sc_ntime));
+	memset(&sc->sc_otime, 0, sizeof(sc->sc_otime));
+}
+
+/* save the internal state, not used */
+static int
+ukbd_get_state(keyboard_t *kbd, void *buf, size_t len)
+{
+	return (len == 0) ? 1 : -1;
+}
+
+/* set the internal state, not used */
+static int
+ukbd_set_state(keyboard_t *kbd, void *buf, size_t len)
+{
+	return (EINVAL);
+}
+
+static int
+ukbd_poll(keyboard_t *kbd, int on)
+{
+	struct ukbd_softc *sc = kbd->kb_data;
+
+	UKBD_LOCK();
+	/*
+	 * Keep a reference count on polling to allow recursive
+	 * cngrab() during a panic for example.
+	 */
+	if (on)
+		sc->sc_polling++;
+	else if (sc->sc_polling > 0)
+		sc->sc_polling--;
+
+	if (sc->sc_polling != 0) {
+		sc->sc_flags |= UKBD_FLAG_POLLING;
+		sc->sc_poll_thread = curthread;
+	} else {
+		sc->sc_flags &= ~UKBD_FLAG_POLLING;
+		sc->sc_delay = 0;
+	}
+	UKBD_UNLOCK();
+
+	return (0);
+}
+
+/* local functions */
+
+static void
+ukbd_set_leds(struct ukbd_softc *sc, uint8_t leds)
+{
+
+	UKBD_LOCK_ASSERT();
+	DPRINTF("leds=0x%02x\n", leds);
+
+	sc->sc_leds = leds;
+	sc->sc_flags |= UKBD_FLAG_SET_LEDS;
+
+	/* start transfer, if not already started */
+
+	usbd_transfer_start(sc->sc_xfer[UKBD_CTRL_LED]);
+}
+
+static int
+ukbd_set_typematic(keyboard_t *kbd, int code)
+{
+#ifdef EVDEV_SUPPORT
+	struct ukbd_softc *sc = kbd->kb_data;
+#endif
+	static const int delays[] = {250, 500, 750, 1000};
+	static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63,
+		68, 76, 84, 92, 100, 110, 118, 126,
+		136, 152, 168, 184, 200, 220, 236, 252,
+	272, 304, 336, 368, 400, 440, 472, 504};
+
+	if (code & ~0x7f) {
+		return (EINVAL);
+	}
+	kbd->kb_delay1 = delays[(code >> 5) & 3];
+	kbd->kb_delay2 = rates[code & 0x1f];
+#ifdef EVDEV_SUPPORT
+	if (sc->sc_evdev != NULL)
+		evdev_push_repeats(sc->sc_evdev, kbd);
+#endif
+	return (0);
+}
+
+#ifdef UKBD_EMULATE_ATSCANCODE
+static uint32_t
+ukbd_atkeycode(int usbcode, int shift)
+{
+	uint32_t keycode;
+
+	keycode = ukbd_trtab[KEY_INDEX(usbcode)];
+	/*
+	 * Translate Alt-PrintScreen to SysRq.
+	 *
+	 * Some or all AT keyboards connected through USB have already
+	 * mapped Alted PrintScreens to an unusual usbcode (0x8a).
+	 * ukbd_trtab translates this to 0x7e, and key2scan() would
+	 * translate that to 0x79 (Intl' 4).  Assume that if we have
+	 * an Alted 0x7e here then it actually is an Alted PrintScreen.
+	 *
+	 * The usual usbcode for all PrintScreens is 0x46.  ukbd_trtab
+	 * translates this to 0x5c, so the Alt check to classify 0x5c
+	 * is routine.
+	 */
+	if ((keycode == 0x5c || keycode == 0x7e) &&
+	    shift & (MOD_ALT_L | MOD_ALT_R))
+		return (0x54);
+	return (keycode);
+}
+
+static int
+ukbd_key2scan(struct ukbd_softc *sc, int code, int shift, int up)
+{
+	static const int scan[] = {
+		/* 89 */
+		0x11c,	/* Enter */
+		/* 90-99 */
+		0x11d,	/* Ctrl-R */
+		0x135,	/* Divide */
+		0x137,	/* PrintScreen */
+		0x138,	/* Alt-R */
+		0x147,	/* Home */
+		0x148,	/* Up */
+		0x149,	/* PageUp */
+		0x14b,	/* Left */
+		0x14d,	/* Right */
+		0x14f,	/* End */
+		/* 100-109 */
+		0x150,	/* Down */
+		0x151,	/* PageDown */
+		0x152,	/* Insert */
+		0x153,	/* Delete */
+		0x146,	/* Pause/Break */
+		0x15b,	/* Win_L(Super_L) */
+		0x15c,	/* Win_R(Super_R) */
+		0x15d,	/* Application(Menu) */
+
+		/* SUN TYPE 6 USB KEYBOARD */
+		0x168,	/* Sun Type 6 Help */
+		0x15e,	/* Sun Type 6 Stop */
+		/* 110 - 119 */
+		0x15f,	/* Sun Type 6 Again */
+		0x160,	/* Sun Type 6 Props */
+		0x161,	/* Sun Type 6 Undo */
+		0x162,	/* Sun Type 6 Front */
+		0x163,	/* Sun Type 6 Copy */
+		0x164,	/* Sun Type 6 Open */
+		0x165,	/* Sun Type 6 Paste */
+		0x166,	/* Sun Type 6 Find */
+		0x167,	/* Sun Type 6 Cut */
+		0x125,	/* Sun Type 6 Mute */
+		/* 120 - 130 */
+		0x11f,	/* Sun Type 6 VolumeDown */
+		0x11e,	/* Sun Type 6 VolumeUp */
+		0x120,	/* Sun Type 6 PowerDown */
+
+		/* Japanese 106/109 keyboard */
+		0x73,	/* Keyboard Intl' 1 (backslash / underscore) */
+		0x70,	/* Keyboard Intl' 2 (Katakana / Hiragana) */
+		0x7d,	/* Keyboard Intl' 3 (Yen sign) (Not using in jp106/109) */
+		0x79,	/* Keyboard Intl' 4 (Henkan) */
+		0x7b,	/* Keyboard Intl' 5 (Muhenkan) */
+		0x5c,	/* Keyboard Intl' 6 (Keypad ,) (For PC-9821 layout) */
+		0x71,   /* Apple Keyboard JIS (Kana) */
+		0x72,   /* Apple Keyboard JIS (Eisu) */
+	};
+
+	if ((code >= 89) && (code < (int)(89 + nitems(scan)))) {
+		code = scan[code - 89];
+	}
+	/* PrintScreen */
+	if (code == 0x137 && (!(shift & (MOD_CONTROL_L | MOD_CONTROL_R |
+	    MOD_SHIFT_L | MOD_SHIFT_R)))) {
+		code |= SCAN_PREFIX_SHIFT;
+	}
+	/* Pause/Break */
+	if ((code == 0x146) && (!(shift & (MOD_CONTROL_L | MOD_CONTROL_R)))) {
+		code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL);
+	}
+	code |= (up ? SCAN_RELEASE : SCAN_PRESS);
+
+	if (code & SCAN_PREFIX) {
+		if (code & SCAN_PREFIX_CTL) {
+			/* Ctrl */
+			sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE));
+			sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX);
+		} else if (code & SCAN_PREFIX_SHIFT) {
+			/* Shift */
+			sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE));
+			sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT);
+		} else {
+			sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX);
+			sc->sc_buffered_char[1] = 0;
+		}
+		return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1);
+	}
+	return (code);
+
+}
+
+#endif					/* UKBD_EMULATE_ATSCANCODE */
+
+static keyboard_switch_t ukbdsw = {
+	.probe = &ukbd__probe,
+	.init = &ukbd_init,
+	.term = &ukbd_term,
+	.intr = &ukbd_intr,
+	.test_if = &ukbd_test_if,
+	.enable = &ukbd_enable,
+	.disable = &ukbd_disable,
+	.read = &ukbd_read,
+	.check = &ukbd_check,
+	.read_char = &ukbd_read_char,
+	.check_char = &ukbd_check_char,
+	.ioctl = &ukbd_ioctl,
+	.lock = &ukbd_lock,
+	.clear_state = &ukbd_clear_state,
+	.get_state = &ukbd_get_state,
+	.set_state = &ukbd_set_state,
+	.get_fkeystr = &genkbd_get_fkeystr,
+	.poll = &ukbd_poll,
+	.diag = &genkbd_diag,
+};
+
+KEYBOARD_DRIVER(ukbd, ukbdsw, ukbd_configure);
+
+static int
+ukbd_driver_load(module_t mod, int what, void *arg)
+{
+	switch (what) {
+	case MOD_LOAD:
+		kbd_add_driver(&ukbd_kbd_driver);
+		break;
+	case MOD_UNLOAD:
+		kbd_delete_driver(&ukbd_kbd_driver);
+		break;
+	}
+	return (0);
+}
+
+static devclass_t ukbd_devclass;
+
+static device_method_t ukbd_methods[] = {
+	DEVMETHOD(device_probe, ukbd_probe),
+	DEVMETHOD(device_attach, ukbd_attach),
+	DEVMETHOD(device_detach, ukbd_detach),
+	DEVMETHOD(device_resume, ukbd_resume),
+
+	DEVMETHOD_END
+};
+
+static driver_t ukbd_driver = {
+	.name = "ukbd",
+	.methods = ukbd_methods,
+	.size = sizeof(struct ukbd_softc),
+};
+
+DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0);
+MODULE_DEPEND(ukbd, usb, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(ukbd, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(ukbd, 1);
+USB_PNP_HOST_INFO(ukbd_devs);
diff --git a/freebsd/sys/dev/usb/input/ums.c b/freebsd/sys/dev/usb/input/ums.c
new file mode 100644
index 0000000..1af6d63
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/ums.c
@@ -0,0 +1,1231 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * 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 at 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.
+ *
+ * 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf
+ */
+
+#include <rtems/bsd/local/opt_evdev.h>
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <rtems/bsd/sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <rtems/bsd/sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/sbuf.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+#include <rtems/bsd/local/usbdevs.h>
+
+#define	USB_DEBUG_VAR ums_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/tty.h>
+#include <sys/mouse.h>
+
+#ifdef USB_DEBUG
+static int ums_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, ums, CTLFLAG_RW, 0, "USB ums");
+SYSCTL_INT(_hw_usb_ums, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &ums_debug, 0, "Debug level");
+#endif
+
+#define	MOUSE_FLAGS_MASK (HIO_CONST|HIO_RELATIVE)
+#define	MOUSE_FLAGS (HIO_RELATIVE)
+
+#define	UMS_BUF_SIZE      8		/* bytes */
+#define	UMS_IFQ_MAXLEN   50		/* units */
+#define	UMS_BUTTON_MAX   31		/* exclusive, must be less than 32 */
+#define	UMS_BUT(i) ((i) < 3 ? (((i) + 2) % 3) : (i))
+#define	UMS_INFO_MAX	  2		/* maximum number of HID sets */
+
+enum {
+	UMS_INTR_DT,
+	UMS_N_TRANSFER,
+};
+
+struct ums_info {
+	struct hid_location sc_loc_w;
+	struct hid_location sc_loc_x;
+	struct hid_location sc_loc_y;
+	struct hid_location sc_loc_z;
+	struct hid_location sc_loc_t;
+	struct hid_location sc_loc_btn[UMS_BUTTON_MAX];
+
+	uint32_t sc_flags;
+#define	UMS_FLAG_X_AXIS     0x0001
+#define	UMS_FLAG_Y_AXIS     0x0002
+#define	UMS_FLAG_Z_AXIS     0x0004
+#define	UMS_FLAG_T_AXIS     0x0008
+#define	UMS_FLAG_SBU        0x0010	/* spurious button up events */
+#define	UMS_FLAG_REVZ	    0x0020	/* Z-axis is reversed */
+#define	UMS_FLAG_W_AXIS     0x0040
+
+	uint8_t	sc_iid_w;
+	uint8_t	sc_iid_x;
+	uint8_t	sc_iid_y;
+	uint8_t	sc_iid_z;
+	uint8_t	sc_iid_t;
+	uint8_t	sc_iid_btn[UMS_BUTTON_MAX];
+	uint8_t	sc_buttons;
+};
+
+struct ums_softc {
+	struct usb_fifo_sc sc_fifo;
+	struct mtx sc_mtx;
+	struct usb_callout sc_callout;
+	struct ums_info sc_info[UMS_INFO_MAX];
+
+	mousehw_t sc_hw;
+	mousemode_t sc_mode;
+	mousestatus_t sc_status;
+
+	struct usb_xfer *sc_xfer[UMS_N_TRANSFER];
+
+	int sc_pollrate;
+	int sc_fflags;
+#ifdef EVDEV_SUPPORT
+	int sc_evflags;
+#define	UMS_EVDEV_OPENED	1
+#endif
+
+	uint8_t	sc_buttons;
+	uint8_t	sc_iid;
+	uint8_t	sc_temp[64];
+
+#ifdef EVDEV_SUPPORT
+	struct evdev_dev *sc_evdev;
+#endif
+};
+
+static void ums_put_queue_timeout(void *__sc);
+
+static usb_callback_t ums_intr_callback;
+
+static device_probe_t ums_probe;
+static device_attach_t ums_attach;
+static device_detach_t ums_detach;
+
+static usb_fifo_cmd_t ums_fifo_start_read;
+static usb_fifo_cmd_t ums_fifo_stop_read;
+static usb_fifo_open_t ums_fifo_open;
+static usb_fifo_close_t ums_fifo_close;
+static usb_fifo_ioctl_t ums_fifo_ioctl;
+
+#ifdef EVDEV_SUPPORT
+static evdev_open_t ums_ev_open;
+static evdev_close_t ums_ev_close;
+static void ums_evdev_push(struct ums_softc *, int32_t, int32_t,
+    int32_t, int32_t, int32_t);
+#endif
+
+static void	ums_start_rx(struct ums_softc *);
+static void	ums_stop_rx(struct ums_softc *);
+static void	ums_put_queue(struct ums_softc *, int32_t, int32_t,
+		    int32_t, int32_t, int32_t);
+static int	ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS);
+
+static struct usb_fifo_methods ums_fifo_methods = {
+	.f_open = &ums_fifo_open,
+	.f_close = &ums_fifo_close,
+	.f_ioctl = &ums_fifo_ioctl,
+	.f_start_read = &ums_fifo_start_read,
+	.f_stop_read = &ums_fifo_stop_read,
+	.basename[0] = "ums",
+};
+
+#ifdef EVDEV_SUPPORT
+static const struct evdev_methods ums_evdev_methods = {
+	.ev_open = &ums_ev_open,
+	.ev_close = &ums_ev_close,
+};
+#endif
+
+static void
+ums_put_queue_timeout(void *__sc)
+{
+	struct ums_softc *sc = __sc;
+
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	ums_put_queue(sc, 0, 0, 0, 0, 0);
+#ifdef EVDEV_SUPPORT
+	ums_evdev_push(sc, 0, 0, 0, 0, 0);
+#endif
+}
+
+static void
+ums_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct ums_softc *sc = usbd_xfer_softc(xfer);
+	struct ums_info *info = &sc->sc_info[0];
+	struct usb_page_cache *pc;
+	uint8_t *buf = sc->sc_temp;
+	int32_t buttons = 0;
+	int32_t buttons_found = 0;
+#ifdef EVDEV_SUPPORT
+	int32_t buttons_reported = 0;
+#endif
+	int32_t dw = 0;
+	int32_t dx = 0;
+	int32_t dy = 0;
+	int32_t dz = 0;
+	int32_t dt = 0;
+	uint8_t i;
+	uint8_t id;
+	int len;
+
+	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		DPRINTFN(6, "sc=%p actlen=%d\n", sc, len);
+
+		if (len > (int)sizeof(sc->sc_temp)) {
+			DPRINTFN(6, "truncating large packet to %zu bytes\n",
+			    sizeof(sc->sc_temp));
+			len = sizeof(sc->sc_temp);
+		}
+		if (len == 0)
+			goto tr_setup;
+
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_out(pc, 0, buf, len);
+
+		DPRINTFN(6, "data = %02x %02x %02x %02x "
+		    "%02x %02x %02x %02x\n",
+		    (len > 0) ? buf[0] : 0, (len > 1) ? buf[1] : 0,
+		    (len > 2) ? buf[2] : 0, (len > 3) ? buf[3] : 0,
+		    (len > 4) ? buf[4] : 0, (len > 5) ? buf[5] : 0,
+		    (len > 6) ? buf[6] : 0, (len > 7) ? buf[7] : 0);
+
+		if (sc->sc_iid) {
+			id = *buf;
+
+			len--;
+			buf++;
+
+		} else {
+			id = 0;
+			if (sc->sc_info[0].sc_flags & UMS_FLAG_SBU) {
+				if ((*buf == 0x14) || (*buf == 0x15)) {
+					goto tr_setup;
+				}
+			}
+		}
+
+	repeat:
+		if ((info->sc_flags & UMS_FLAG_W_AXIS) &&
+		    (id == info->sc_iid_w))
+			dw += hid_get_data(buf, len, &info->sc_loc_w);
+
+		if ((info->sc_flags & UMS_FLAG_X_AXIS) && 
+		    (id == info->sc_iid_x))
+			dx += hid_get_data(buf, len, &info->sc_loc_x);
+
+		if ((info->sc_flags & UMS_FLAG_Y_AXIS) &&
+		    (id == info->sc_iid_y))
+			dy -= hid_get_data(buf, len, &info->sc_loc_y);
+
+		if ((info->sc_flags & UMS_FLAG_Z_AXIS) &&
+		    (id == info->sc_iid_z)) {
+			int32_t temp;
+			temp = hid_get_data(buf, len, &info->sc_loc_z);
+			if (info->sc_flags & UMS_FLAG_REVZ)
+				temp = -temp;
+			dz -= temp;
+		}
+
+		if ((info->sc_flags & UMS_FLAG_T_AXIS) &&
+		    (id == info->sc_iid_t)) {
+			dt -= hid_get_data(buf, len, &info->sc_loc_t);
+			/* T-axis is translated into button presses */
+			buttons_found |= (1UL << 5) | (1UL << 6);
+		}
+
+		for (i = 0; i < info->sc_buttons; i++) {
+			uint32_t mask;
+			mask = 1UL << UMS_BUT(i);
+			/* check for correct button ID */
+			if (id != info->sc_iid_btn[i])
+				continue;
+			/* check for button pressed */
+			if (hid_get_data(buf, len, &info->sc_loc_btn[i]))
+				buttons |= mask;
+			/* register button mask */
+			buttons_found |= mask;
+		}
+
+		if (++info != &sc->sc_info[UMS_INFO_MAX])
+			goto repeat;
+
+#ifdef EVDEV_SUPPORT
+		buttons_reported = buttons;
+#endif
+		/* keep old button value(s) for non-detected buttons */
+		buttons |= sc->sc_status.button & ~buttons_found;
+
+		if (dx || dy || dz || dt || dw ||
+		    (buttons != sc->sc_status.button)) {
+
+			DPRINTFN(6, "x:%d y:%d z:%d t:%d w:%d buttons:0x%08x\n",
+			    dx, dy, dz, dt, dw, buttons);
+
+			/* translate T-axis into button presses until further */
+			if (dt > 0) {
+				ums_put_queue(sc, 0, 0, 0, 0, buttons);
+				buttons |= 1UL << 5;
+			} else if (dt < 0) {
+				ums_put_queue(sc, 0, 0, 0, 0, buttons);
+				buttons |= 1UL << 6;
+			}
+
+			sc->sc_status.button = buttons;
+			sc->sc_status.dx += dx;
+			sc->sc_status.dy += dy;
+			sc->sc_status.dz += dz;
+			/*
+			 * sc->sc_status.dt += dt;
+			 * no way to export this yet
+			 */
+
+			/*
+		         * 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->sc_info[0].sc_flags & UMS_FLAG_SBU) &&
+			    (dx == 0) && (dy == 0) && (dz == 0) && (dt == 0) &&
+			    (dw == 0) && (buttons == 0)) {
+
+				usb_callout_reset(&sc->sc_callout, hz / 20,
+				    &ums_put_queue_timeout, sc);
+			} else {
+
+				usb_callout_stop(&sc->sc_callout);
+
+				ums_put_queue(sc, dx, dy, dz, dt, buttons);
+#ifdef EVDEV_SUPPORT
+				ums_evdev_push(sc, dx, dy, dz, dt,
+				    buttons_reported);
+#endif
+
+			}
+		}
+	case USB_ST_SETUP:
+tr_setup:
+		/* check if we can put more data into the FIFO */
+		if (usb_fifo_put_bytes_max(sc->sc_fifo.fp[USB_FIFO_RX]) == 0) {
+#ifdef EVDEV_SUPPORT
+			if (sc->sc_evflags == 0)
+				break;
+#else
+			break;
+#endif
+		}
+
+		usbd_xfer_set_frame_len(xfer, 0, usbd_xfer_max_len(xfer));
+		usbd_transfer_submit(xfer);
+		break;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		break;
+	}
+}
+
+static const struct usb_config ums_config[UMS_N_TRANSFER] = {
+
+	[UMS_INTR_DT] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,},
+		.bufsize = 0,	/* use wMaxPacketSize */
+		.callback = &ums_intr_callback,
+	},
+};
+
+/* A match on these entries will load ums */
+static const STRUCT_USB_HOST_ID __used ums_devs[] = {
+	{USB_IFACE_CLASS(UICLASS_HID),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+	 USB_IFACE_PROTOCOL(UIPROTO_MOUSE),},
+};
+
+static int
+ums_probe(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	void *d_ptr;
+	int error;
+	uint16_t d_len;
+
+	DPRINTFN(11, "\n");
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+
+	if (uaa->info.bInterfaceClass != UICLASS_HID)
+		return (ENXIO);
+
+	if (usb_test_quirk(uaa, UQ_UMS_IGNORE))
+		return (ENXIO);
+
+	if ((uaa->info.bInterfaceSubClass == UISUBCLASS_BOOT) &&
+	    (uaa->info.bInterfaceProtocol == UIPROTO_MOUSE))
+		return (BUS_PROBE_DEFAULT);
+
+	error = usbd_req_get_hid_desc(uaa->device, NULL,
+	    &d_ptr, &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+	if (error)
+		return (ENXIO);
+
+	if (hid_is_mouse(d_ptr, d_len))
+		error = BUS_PROBE_DEFAULT;
+	else
+		error = ENXIO;
+
+	free(d_ptr, M_TEMP);
+	return (error);
+}
+
+static void
+ums_hid_parse(struct ums_softc *sc, device_t dev, const uint8_t *buf,
+    uint16_t len, uint8_t index)
+{
+	struct ums_info *info = &sc->sc_info[index];
+	uint32_t flags;
+	uint8_t i;
+	uint8_t j;
+
+	if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X),
+	    hid_input, index, &info->sc_loc_x, &flags, &info->sc_iid_x)) {
+
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+			info->sc_flags |= UMS_FLAG_X_AXIS;
+		}
+	}
+	if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y),
+	    hid_input, index, &info->sc_loc_y, &flags, &info->sc_iid_y)) {
+
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+			info->sc_flags |= UMS_FLAG_Y_AXIS;
+		}
+	}
+	/* Try the wheel first as the Z activator since it's tradition. */
+	if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+	    HUG_WHEEL), hid_input, index, &info->sc_loc_z, &flags,
+	    &info->sc_iid_z) ||
+	    hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+	    HUG_TWHEEL), hid_input, index, &info->sc_loc_z, &flags,
+	    &info->sc_iid_z)) {
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+			info->sc_flags |= UMS_FLAG_Z_AXIS;
+		}
+		/*
+		 * We might have both a wheel and Z direction, if so put
+		 * put the Z on the W coordinate.
+		 */
+		if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+		    HUG_Z), hid_input, index, &info->sc_loc_w, &flags,
+		    &info->sc_iid_w)) {
+
+			if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+				info->sc_flags |= UMS_FLAG_W_AXIS;
+			}
+		}
+	} else if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+	    HUG_Z), hid_input, index, &info->sc_loc_z, &flags, 
+	    &info->sc_iid_z)) {
+
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+			info->sc_flags |= UMS_FLAG_Z_AXIS;
+		}
+	}
+	/*
+	 * The Microsoft Wireless Intellimouse 2.0 reports it's wheel
+	 * using 0x0048, which is HUG_TWHEEL, and seems to expect you
+	 * to know that the byte after the wheel is the tilt axis.
+	 * There are no other HID axis descriptors other than X,Y and
+	 * TWHEEL
+	 */
+	if (hid_locate(buf, len, HID_USAGE2(HUP_GENERIC_DESKTOP,
+	    HUG_TWHEEL), hid_input, index, &info->sc_loc_t, 
+	    &flags, &info->sc_iid_t)) {
+
+		info->sc_loc_t.pos += 8;
+
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS) {
+			info->sc_flags |= UMS_FLAG_T_AXIS;
+		}
+	} else if (hid_locate(buf, len, HID_USAGE2(HUP_CONSUMER,
+		HUC_AC_PAN), hid_input, index, &info->sc_loc_t,
+		&flags, &info->sc_iid_t)) {
+
+		if ((flags & MOUSE_FLAGS_MASK) == MOUSE_FLAGS)
+			info->sc_flags |= UMS_FLAG_T_AXIS;
+	}
+	/* figure out the number of buttons */
+
+	for (i = 0; i < UMS_BUTTON_MAX; i++) {
+		if (!hid_locate(buf, len, HID_USAGE2(HUP_BUTTON, (i + 1)),
+		    hid_input, index, &info->sc_loc_btn[i], NULL, 
+		    &info->sc_iid_btn[i])) {
+			break;
+		}
+	}
+
+	/* detect other buttons */
+
+	for (j = 0; (i < UMS_BUTTON_MAX) && (j < 2); i++, j++) {
+		if (!hid_locate(buf, len, HID_USAGE2(HUP_MICROSOFT, (j + 1)),
+		    hid_input, index, &info->sc_loc_btn[i], NULL, 
+		    &info->sc_iid_btn[i])) {
+			break;
+		}
+	}
+
+	info->sc_buttons = i;
+
+	if (i > sc->sc_buttons)
+		sc->sc_buttons = i;
+
+	if (info->sc_flags == 0)
+		return;
+
+	/* announce information about the mouse */
+	device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n",
+	    (info->sc_buttons),
+	    (info->sc_flags & UMS_FLAG_X_AXIS) ? "X" : "",
+	    (info->sc_flags & UMS_FLAG_Y_AXIS) ? "Y" : "",
+	    (info->sc_flags & UMS_FLAG_Z_AXIS) ? "Z" : "",
+	    (info->sc_flags & UMS_FLAG_T_AXIS) ? "T" : "",
+	    (info->sc_flags & UMS_FLAG_W_AXIS) ? "W" : "",
+	    info->sc_iid_x);
+}
+
+static int
+ums_attach(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	struct ums_softc *sc = device_get_softc(dev);
+	struct ums_info *info;
+	void *d_ptr = NULL;
+	int isize;
+	int err;
+	uint16_t d_len;
+	uint8_t i;
+#ifdef USB_DEBUG
+	uint8_t j;
+#endif
+
+	DPRINTFN(11, "sc=%p\n", sc);
+
+	device_set_usb_desc(dev);
+
+	mtx_init(&sc->sc_mtx, "ums lock", NULL, MTX_DEF | MTX_RECURSE);
+
+	usb_callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0);
+
+	/*
+         * Force the report (non-boot) protocol.
+         *
+         * Mice without boot protocol support may choose not to implement
+         * Set_Protocol at all; Ignore any error.
+         */
+	err = usbd_req_set_protocol(uaa->device, NULL,
+	    uaa->info.bIfaceIndex, 1);
+
+	err = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, ums_config,
+	    UMS_N_TRANSFER, sc, &sc->sc_mtx);
+
+	if (err) {
+		DPRINTF("error=%s\n", usbd_errstr(err));
+		goto detach;
+	}
+
+	/* Get HID descriptor */
+	err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
+	    &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+	if (err) {
+		device_printf(dev, "error reading report description\n");
+		goto detach;
+	}
+
+	isize = hid_report_size(d_ptr, d_len, hid_input, &sc->sc_iid);
+
+	/*
+	 * The Microsoft Wireless Notebook Optical Mouse seems to be in worse
+	 * shape than the Wireless Intellimouse 2.0, as its X, Y, wheel, and
+	 * all of its other button positions are all off. It also reports that
+	 * it has two additional buttons and a tilt wheel.
+	 */
+	if (usb_test_quirk(uaa, UQ_MS_BAD_CLASS)) {
+
+		sc->sc_iid = 0;
+
+		info = &sc->sc_info[0];
+		info->sc_flags = (UMS_FLAG_X_AXIS |
+		    UMS_FLAG_Y_AXIS |
+		    UMS_FLAG_Z_AXIS |
+		    UMS_FLAG_SBU);
+		info->sc_buttons = 3;
+		isize = 5;
+		/* 1st byte of descriptor report contains garbage */
+		info->sc_loc_x.pos = 16;
+		info->sc_loc_x.size = 8;
+		info->sc_loc_y.pos = 24;
+		info->sc_loc_y.size = 8;
+		info->sc_loc_z.pos = 32;
+		info->sc_loc_z.size = 8;
+		info->sc_loc_btn[0].pos = 8;
+		info->sc_loc_btn[0].size = 1;
+		info->sc_loc_btn[1].pos = 9;
+		info->sc_loc_btn[1].size = 1;
+		info->sc_loc_btn[2].pos = 10;
+		info->sc_loc_btn[2].size = 1;
+
+		/* Announce device */
+		device_printf(dev, "3 buttons and [XYZ] "
+		    "coordinates ID=0\n");
+
+	} else {
+		/* Search the HID descriptor and announce device */
+		for (i = 0; i < UMS_INFO_MAX; i++) {
+			ums_hid_parse(sc, dev, d_ptr, d_len, i);
+		}
+	}
+
+	if (usb_test_quirk(uaa, UQ_MS_REVZ)) {
+		info = &sc->sc_info[0];
+		/* Some wheels need the Z axis reversed. */
+		info->sc_flags |= UMS_FLAG_REVZ;
+	}
+	if (isize > (int)usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT])) {
+		DPRINTF("WARNING: report size, %d bytes, is larger "
+		    "than interrupt size, %d bytes!\n", isize,
+		    usbd_xfer_max_framelen(sc->sc_xfer[UMS_INTR_DT]));
+	}
+	free(d_ptr, M_TEMP);
+	d_ptr = NULL;
+
+#ifdef USB_DEBUG
+	for (j = 0; j < UMS_INFO_MAX; j++) {
+		info = &sc->sc_info[j];
+
+		DPRINTF("sc=%p, index=%d\n", sc, j);
+		DPRINTF("X\t%d/%d id=%d\n", info->sc_loc_x.pos,
+		    info->sc_loc_x.size, info->sc_iid_x);
+		DPRINTF("Y\t%d/%d id=%d\n", info->sc_loc_y.pos,
+		    info->sc_loc_y.size, info->sc_iid_y);
+		DPRINTF("Z\t%d/%d id=%d\n", info->sc_loc_z.pos,
+		    info->sc_loc_z.size, info->sc_iid_z);
+		DPRINTF("T\t%d/%d id=%d\n", info->sc_loc_t.pos,
+		    info->sc_loc_t.size, info->sc_iid_t);
+		DPRINTF("W\t%d/%d id=%d\n", info->sc_loc_w.pos,
+		    info->sc_loc_w.size, info->sc_iid_w);
+
+		for (i = 0; i < info->sc_buttons; i++) {
+			DPRINTF("B%d\t%d/%d id=%d\n",
+			    i + 1, info->sc_loc_btn[i].pos,
+			    info->sc_loc_btn[i].size, info->sc_iid_btn[i]);
+		}
+	}
+	DPRINTF("size=%d, id=%d\n", isize, sc->sc_iid);
+#endif
+
+	err = usb_fifo_attach(uaa->device, sc, &sc->sc_mtx,
+	    &ums_fifo_methods, &sc->sc_fifo,
+	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+  	    UID_ROOT, GID_OPERATOR, 0644);
+	if (err)
+		goto detach;
+
+#ifdef EVDEV_SUPPORT
+	sc->sc_evdev = evdev_alloc();
+	evdev_set_name(sc->sc_evdev, device_get_desc(dev));
+	evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev));
+	evdev_set_id(sc->sc_evdev, BUS_USB, uaa->info.idVendor,
+	    uaa->info.idProduct, 0);
+	evdev_set_serial(sc->sc_evdev, usb_get_serial(uaa->device));
+	evdev_set_methods(sc->sc_evdev, sc, &ums_evdev_methods);
+	evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER);
+	evdev_support_event(sc->sc_evdev, EV_SYN);
+	evdev_support_event(sc->sc_evdev, EV_REL);
+	evdev_support_event(sc->sc_evdev, EV_KEY);
+
+	info = &sc->sc_info[0];
+
+	if (info->sc_flags & UMS_FLAG_X_AXIS)
+		evdev_support_rel(sc->sc_evdev, REL_X);
+
+	if (info->sc_flags & UMS_FLAG_Y_AXIS)
+		evdev_support_rel(sc->sc_evdev, REL_Y);
+
+	if (info->sc_flags & UMS_FLAG_Z_AXIS)
+		evdev_support_rel(sc->sc_evdev, REL_WHEEL);
+
+	if (info->sc_flags & UMS_FLAG_T_AXIS)
+		evdev_support_rel(sc->sc_evdev, REL_HWHEEL);
+
+	for (i = 0; i < info->sc_buttons; i++)
+		evdev_support_key(sc->sc_evdev, BTN_MOUSE + i);
+
+	err = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx);
+	if (err)
+		goto detach;
+#endif
+
+	SYSCTL_ADD_PROC(device_get_sysctl_ctx(dev),
+	    SYSCTL_CHILDREN(device_get_sysctl_tree(dev)),
+	    OID_AUTO, "parseinfo", CTLTYPE_STRING|CTLFLAG_RD,
+	    sc, 0, ums_sysctl_handler_parseinfo,
+	    "", "Dump of parsed HID report descriptor");
+
+	return (0);
+
+detach:
+	if (d_ptr) {
+		free(d_ptr, M_TEMP);
+	}
+	ums_detach(dev);
+	return (ENOMEM);
+}
+
+static int
+ums_detach(device_t self)
+{
+	struct ums_softc *sc = device_get_softc(self);
+
+	DPRINTF("sc=%p\n", sc);
+
+	usb_fifo_detach(&sc->sc_fifo);
+
+#ifdef EVDEV_SUPPORT
+	evdev_free(sc->sc_evdev);
+#endif
+
+	usbd_transfer_unsetup(sc->sc_xfer, UMS_N_TRANSFER);
+
+	usb_callout_drain(&sc->sc_callout);
+
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+static void
+ums_reset(struct ums_softc *sc)
+{
+
+	/* reset all USB mouse parameters */
+
+	if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+		sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+	else
+		sc->sc_hw.buttons = sc->sc_buttons;
+
+	sc->sc_hw.iftype = MOUSE_IF_USB;
+	sc->sc_hw.type = MOUSE_MOUSE;
+	sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+	sc->sc_hw.hwid = 0;
+
+	sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+	sc->sc_mode.rate = -1;
+	sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+	sc->sc_mode.accelfactor = 0;
+	sc->sc_mode.level = 0;
+	sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+
+	/* reset status */
+
+	sc->sc_status.flags = 0;
+	sc->sc_status.button = 0;
+	sc->sc_status.obutton = 0;
+	sc->sc_status.dx = 0;
+	sc->sc_status.dy = 0;
+	sc->sc_status.dz = 0;
+	/* sc->sc_status.dt = 0; */
+}
+
+static void
+ums_start_rx(struct ums_softc *sc)
+{
+	int rate;
+
+	/* Check if we should override the default polling interval */
+	rate = sc->sc_pollrate;
+	/* Range check rate */
+	if (rate > 1000)
+		rate = 1000;
+	/* Check for set rate */
+	if ((rate > 0) && (sc->sc_xfer[UMS_INTR_DT] != NULL)) {
+		DPRINTF("Setting pollrate = %d\n", rate);
+		/* Stop current transfer, if any */
+		usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]);
+		/* Set new interval */
+		usbd_xfer_set_interval(sc->sc_xfer[UMS_INTR_DT], 1000 / rate);
+		/* Only set pollrate once */
+		sc->sc_pollrate = 0;
+	}
+
+	usbd_transfer_start(sc->sc_xfer[UMS_INTR_DT]);
+}
+
+static void
+ums_stop_rx(struct ums_softc *sc)
+{
+	usbd_transfer_stop(sc->sc_xfer[UMS_INTR_DT]);
+	usb_callout_stop(&sc->sc_callout);
+}
+
+static void
+ums_fifo_start_read(struct usb_fifo *fifo)
+{
+	struct ums_softc *sc = usb_fifo_softc(fifo);
+
+	ums_start_rx(sc);
+}
+
+static void
+ums_fifo_stop_read(struct usb_fifo *fifo)
+{
+	struct ums_softc *sc = usb_fifo_softc(fifo);
+
+	ums_stop_rx(sc);
+}
+
+
+#if ((MOUSE_SYS_PACKETSIZE != 8) || \
+     (MOUSE_MSC_PACKETSIZE != 5))
+#error "Software assumptions are not met. Please update code."
+#endif
+
+static void
+ums_put_queue(struct ums_softc *sc, int32_t dx, int32_t dy,
+    int32_t dz, int32_t dt, int32_t buttons)
+{
+	uint8_t buf[8];
+
+	if (1) {
+
+		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;
+
+		buf[0] = sc->sc_mode.syncmask[1];
+		buf[0] |= (~buttons) & MOUSE_MSC_BUTTONS;
+		buf[1] = dx >> 1;
+		buf[2] = dy >> 1;
+		buf[3] = dx - (dx >> 1);
+		buf[4] = dy - (dy >> 1);
+
+		if (sc->sc_mode.level == 1) {
+			buf[5] = dz >> 1;
+			buf[6] = dz - (dz >> 1);
+			buf[7] = (((~buttons) >> 3) & MOUSE_SYS_EXTBUTTONS);
+		}
+		usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+		    sc->sc_mode.packetsize, 1);
+	} else {
+		DPRINTF("Buffer full, discarded packet\n");
+	}
+}
+
+#ifdef EVDEV_SUPPORT
+static void
+ums_evdev_push(struct ums_softc *sc, int32_t dx, int32_t dy,
+    int32_t dz, int32_t dt, int32_t buttons)
+{
+	if (evdev_rcpt_mask & EVDEV_RCPT_HW_MOUSE) {
+		/* Push evdev event */
+		evdev_push_rel(sc->sc_evdev, REL_X, dx);
+		evdev_push_rel(sc->sc_evdev, REL_Y, -dy);
+		evdev_push_rel(sc->sc_evdev, REL_WHEEL, -dz);
+		evdev_push_rel(sc->sc_evdev, REL_HWHEEL, dt);
+		evdev_push_mouse_btn(sc->sc_evdev,
+		    (buttons & ~MOUSE_STDBUTTONS) |
+		    (buttons & (1 << 2) ? MOUSE_BUTTON1DOWN : 0) |
+		    (buttons & (1 << 1) ? MOUSE_BUTTON2DOWN : 0) |
+		    (buttons & (1 << 0) ? MOUSE_BUTTON3DOWN : 0));
+		evdev_sync(sc->sc_evdev);
+	}
+}
+#endif
+
+static void
+ums_reset_buf(struct ums_softc *sc)
+{
+	/* reset read queue, must be called locked */
+	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+#ifdef EVDEV_SUPPORT
+static int
+ums_ev_open(struct evdev_dev *evdev, void *ev_softc)
+{
+	struct ums_softc *sc = (struct ums_softc *)ev_softc;
+
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	sc->sc_evflags = UMS_EVDEV_OPENED;
+
+	if (sc->sc_fflags == 0) {
+		ums_reset(sc);
+		ums_start_rx(sc);
+	}
+
+	return (0);
+}
+
+static void
+ums_ev_close(struct evdev_dev *evdev, void *ev_softc)
+{
+	struct ums_softc *sc = (struct ums_softc *)ev_softc;
+
+	mtx_assert(&sc->sc_mtx, MA_OWNED);
+
+	sc->sc_evflags = 0;
+
+	if (sc->sc_fflags == 0)
+		ums_stop_rx(sc);
+}
+#endif
+
+static int
+ums_fifo_open(struct usb_fifo *fifo, int fflags)
+{
+	struct ums_softc *sc = usb_fifo_softc(fifo);
+
+	DPRINTFN(2, "\n");
+
+	/* check for duplicate open, should not happen */
+	if (sc->sc_fflags & fflags)
+		return (EBUSY);
+
+	/* check for first open */
+#ifdef EVDEV_SUPPORT
+	if (sc->sc_fflags == 0 && sc->sc_evflags == 0)
+		ums_reset(sc);
+#else
+	if (sc->sc_fflags == 0)
+		ums_reset(sc);
+#endif
+
+	if (fflags & FREAD) {
+		/* allocate RX buffer */
+		if (usb_fifo_alloc_buffer(fifo,
+		    UMS_BUF_SIZE, UMS_IFQ_MAXLEN)) {
+			return (ENOMEM);
+		}
+	}
+
+	sc->sc_fflags |= fflags & (FREAD | FWRITE);
+	return (0);
+}
+
+static void
+ums_fifo_close(struct usb_fifo *fifo, int fflags)
+{
+	struct ums_softc *sc = usb_fifo_softc(fifo);
+
+	DPRINTFN(2, "\n");
+
+	if (fflags & FREAD)
+		usb_fifo_free_buffer(fifo);
+
+	sc->sc_fflags &= ~(fflags & (FREAD | FWRITE));
+}
+
+static int
+ums_fifo_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+	struct ums_softc *sc = usb_fifo_softc(fifo);
+	mousemode_t mode;
+	int error = 0;
+
+	DPRINTFN(2, "\n");
+
+	mtx_lock(&sc->sc_mtx);
+
+	switch (cmd) {
+	case MOUSE_GETHWINFO:
+		*(mousehw_t *)addr = sc->sc_hw;
+		break;
+
+	case MOUSE_GETMODE:
+		*(mousemode_t *)addr = sc->sc_mode;
+		break;
+
+	case MOUSE_SETMODE:
+		mode = *(mousemode_t *)addr;
+
+		if (mode.level == -1) {
+			/* don't change the current setting */
+		} else if ((mode.level < 0) || (mode.level > 1)) {
+			error = EINVAL;
+			break;
+		} else {
+			sc->sc_mode.level = mode.level;
+		}
+
+		/* store polling rate */
+		sc->sc_pollrate = mode.rate;
+
+		if (sc->sc_mode.level == 0) {
+			if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+				sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+			else
+				sc->sc_hw.buttons = sc->sc_buttons;
+			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
+				sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+			else
+				sc->sc_hw.buttons = sc->sc_buttons;
+			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		ums_reset_buf(sc);
+		break;
+
+	case MOUSE_GETLEVEL:
+		*(int *)addr = sc->sc_mode.level;
+		break;
+
+	case MOUSE_SETLEVEL:
+		if (*(int *)addr < 0 || *(int *)addr > 1) {
+			error = EINVAL;
+			break;
+		}
+		sc->sc_mode.level = *(int *)addr;
+
+		if (sc->sc_mode.level == 0) {
+			if (sc->sc_buttons > MOUSE_MSC_MAXBUTTON)
+				sc->sc_hw.buttons = MOUSE_MSC_MAXBUTTON;
+			else
+				sc->sc_hw.buttons = sc->sc_buttons;
+			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			if (sc->sc_buttons > MOUSE_SYS_MAXBUTTON)
+				sc->sc_hw.buttons = MOUSE_SYS_MAXBUTTON;
+			else
+				sc->sc_hw.buttons = sc->sc_buttons;
+			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		ums_reset_buf(sc);
+		break;
+
+	case MOUSE_GETSTATUS:{
+			mousestatus_t *status = (mousestatus_t *)addr;
+
+			*status = sc->sc_status;
+			sc->sc_status.obutton = sc->sc_status.button;
+			sc->sc_status.button = 0;
+			sc->sc_status.dx = 0;
+			sc->sc_status.dy = 0;
+			sc->sc_status.dz = 0;
+			/* sc->sc_status.dt = 0; */
+
+			if (status->dx || status->dy || status->dz /* || status->dt */ ) {
+				status->flags |= MOUSE_POSCHANGED;
+			}
+			if (status->button != status->obutton) {
+				status->flags |= MOUSE_BUTTONSCHANGED;
+			}
+			break;
+		}
+	default:
+		error = ENOTTY;
+		break;
+	}
+
+	mtx_unlock(&sc->sc_mtx);
+	return (error);
+}
+
+static int
+ums_sysctl_handler_parseinfo(SYSCTL_HANDLER_ARGS)
+{
+	struct ums_softc *sc = arg1;
+	struct ums_info *info;
+	struct sbuf *sb;
+	int i, j, err, had_output;
+
+	sb = sbuf_new_auto();
+	for (i = 0, had_output = 0; i < UMS_INFO_MAX; i++) {
+		info = &sc->sc_info[i];
+
+		/* Don't emit empty info */
+		if ((info->sc_flags &
+		    (UMS_FLAG_X_AXIS | UMS_FLAG_Y_AXIS | UMS_FLAG_Z_AXIS |
+		     UMS_FLAG_T_AXIS | UMS_FLAG_W_AXIS)) == 0 &&
+		    info->sc_buttons == 0)
+			continue;
+
+		if (had_output)
+			sbuf_printf(sb, "\n");
+		had_output = 1;
+		sbuf_printf(sb, "i%d:", i + 1);
+		if (info->sc_flags & UMS_FLAG_X_AXIS)
+			sbuf_printf(sb, " X:r%d, p%d, s%d;",
+			    (int)info->sc_iid_x,
+			    (int)info->sc_loc_x.pos,
+			    (int)info->sc_loc_x.size);
+		if (info->sc_flags & UMS_FLAG_Y_AXIS)
+			sbuf_printf(sb, " Y:r%d, p%d, s%d;",
+			    (int)info->sc_iid_y,
+			    (int)info->sc_loc_y.pos,
+			    (int)info->sc_loc_y.size);
+		if (info->sc_flags & UMS_FLAG_Z_AXIS)
+			sbuf_printf(sb, " Z:r%d, p%d, s%d;",
+			    (int)info->sc_iid_z,
+			    (int)info->sc_loc_z.pos,
+			    (int)info->sc_loc_z.size);
+		if (info->sc_flags & UMS_FLAG_T_AXIS)
+			sbuf_printf(sb, " T:r%d, p%d, s%d;",
+			    (int)info->sc_iid_t,
+			    (int)info->sc_loc_t.pos,
+			    (int)info->sc_loc_t.size);
+		if (info->sc_flags & UMS_FLAG_W_AXIS)
+			sbuf_printf(sb, " W:r%d, p%d, s%d;",
+			    (int)info->sc_iid_w,
+			    (int)info->sc_loc_w.pos,
+			    (int)info->sc_loc_w.size);
+
+		for (j = 0; j < info->sc_buttons; j++) {
+			sbuf_printf(sb, " B%d:r%d, p%d, s%d;", j + 1,
+			    (int)info->sc_iid_btn[j],
+			    (int)info->sc_loc_btn[j].pos,
+			    (int)info->sc_loc_btn[j].size);
+		}
+	}
+	sbuf_finish(sb);
+	err = SYSCTL_OUT(req, sbuf_data(sb), sbuf_len(sb) + 1);
+	sbuf_delete(sb);
+
+	return (err);
+}
+
+static devclass_t ums_devclass;
+
+static device_method_t ums_methods[] = {
+	DEVMETHOD(device_probe, ums_probe),
+	DEVMETHOD(device_attach, ums_attach),
+	DEVMETHOD(device_detach, ums_detach),
+
+	DEVMETHOD_END
+};
+
+static driver_t ums_driver = {
+	.name = "ums",
+	.methods = ums_methods,
+	.size = sizeof(struct ums_softc),
+};
+
+DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, NULL, 0);
+MODULE_DEPEND(ums, usb, 1, 1, 1);
+#ifdef EVDEV_SUPPORT
+MODULE_DEPEND(ums, evdev, 1, 1, 1);
+#endif
+MODULE_VERSION(ums, 1);
+USB_PNP_HOST_INFO(ums_devs);
diff --git a/freebsd/sys/dev/usb/input/usb_rdesc.h b/freebsd/sys/dev/usb/input/usb_rdesc.h
new file mode 100644
index 0000000..23ea620
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/usb_rdesc.h
@@ -0,0 +1,276 @@
+/*-
+ * Copyright (c) 2000 Nick Hibma <n_hibma at FreeBSD.org>
+ * All rights reserved.
+ *
+ * Copyright (c) 2005 Ed Schouten <ed at FreeBSD.org>
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ *
+ * $FreeBSD$
+ *
+ * This file contains replacements for broken HID report descriptors.
+ */
+
+#define	UHID_GRAPHIRE_REPORT_DESCR(...) \
+    0x05, 0x0d,                    /*  USAGE_PAGE (Digitizers)		*/\
+    0x09, 0x01,                    /*  USAGE (Digitizer)		*/\
+    0xa1, 0x01,                    /*  COLLECTION (Application)		*/\
+    0x85, 0x02,                    /*    REPORT_ID (2)			*/\
+    0x05, 0x0d,                    /*    USAGE_PAGE (Digitizers)	*/\
+    0x09, 0x01,                    /*    USAGE (Digitizer)		*/\
+    0xa1, 0x00,                    /*    COLLECTION (Physical)		*/\
+    0x15, 0x00,                    /*      LOGICAL_MINIMUM (0)		*/\
+    0x25, 0x01,                    /*      LOGICAL_MAXIMUM (1)		*/\
+    0x09, 0x33,                    /*      USAGE (Touch)		*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x09, 0x44,                    /*      USAGE (Barrel Switch)	*/\
+    0x95, 0x02,                    /*      REPORT_COUNT (2)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x09, 0x00,                    /*      USAGE (Undefined)		*/\
+    0x95, 0x02,                    /*      REPORT_COUNT (2)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x03,                    /*      INPUT (Cnst,Var,Abs)		*/\
+    0x09, 0x3c,                    /*      USAGE (Invert)		*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x09, 0x38,                    /*      USAGE (Transducer Index)	*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x09, 0x32,                    /*      USAGE (In Range)		*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x01,                    /*      REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x05, 0x01,                    /*      USAGE_PAGE (Generic Desktop)	*/\
+    0x09, 0x30,                    /*      USAGE (X)			*/\
+    0x15, 0x00,                    /*      LOGICAL_MINIMUM (0)		*/\
+    0x26, 0xde, 0x27,              /*      LOGICAL_MAXIMUM (10206)	*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x10,                    /*      REPORT_SIZE (16)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x09, 0x31,                    /*      USAGE (Y)			*/\
+    0x26, 0xfe, 0x1c,              /*      LOGICAL_MAXIMUM (7422)	*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x10,                    /*      REPORT_SIZE (16)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0x05, 0x0d,                    /*      USAGE_PAGE (Digitizers)	*/\
+    0x09, 0x30,                    /*      USAGE (Tip Pressure)		*/\
+    0x26, 0xff, 0x01,              /*      LOGICAL_MAXIMUM (511)	*/\
+    0x95, 0x01,                    /*      REPORT_COUNT (1)		*/\
+    0x75, 0x10,                    /*      REPORT_SIZE (16)		*/\
+    0x81, 0x02,                    /*      INPUT (Data,Var,Abs)		*/\
+    0xc0,                          /*    END_COLLECTION			*/\
+    0x05, 0x0d,                    /*    USAGE_PAGE (Digitizers)	*/\
+    0x09, 0x00,                    /*    USAGE (Undefined)		*/\
+    0x85, 0x02,                    /*    REPORT_ID (2)			*/\
+    0x95, 0x01,                    /*    REPORT_COUNT (1)		*/\
+    0xb1, 0x02,                    /*    FEATURE (Data,Var,Abs)		*/\
+    0x09, 0x00,                    /*    USAGE (Undefined)		*/\
+    0x85, 0x03,                    /*    REPORT_ID (3)			*/\
+    0x95, 0x01,                    /*    REPORT_COUNT (1)		*/\
+    0xb1, 0x02,                    /*    FEATURE (Data,Var,Abs)		*/\
+    0xc0,                          /*  END_COLLECTION			*/\
+
+#define	UHID_GRAPHIRE3_4X5_REPORT_DESCR(...) \
+    0x05, 0x01,                    /* USAGE_PAGE (Generic Desktop)	*/\
+    0x09, 0x02,                    /* USAGE (Mouse)			*/\
+    0xa1, 0x01,                    /* COLLECTION (Application)		*/\
+    0x85, 0x01,                    /*   REPORT_ID (1)			*/\
+    0x09, 0x01,                    /*   USAGE (Pointer)			*/\
+    0xa1, 0x00,                    /*   COLLECTION (Physical)		*/\
+    0x05, 0x09,                    /*     USAGE_PAGE (Button)		*/\
+    0x19, 0x01,                    /*     USAGE_MINIMUM (Button 1)	*/\
+    0x29, 0x03,                    /*     USAGE_MAXIMUM (Button 3)	*/\
+    0x15, 0x00,                    /*     LOGICAL_MINIMUM (0)		*/\
+    0x25, 0x01,                    /*     LOGICAL_MAXIMUM (1)		*/\
+    0x95, 0x03,                    /*     REPORT_COUNT (3)		*/\
+    0x75, 0x01,                    /*     REPORT_SIZE (1)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0x95, 0x01,                    /*     REPORT_COUNT (1)		*/\
+    0x75, 0x05,                    /*     REPORT_SIZE (5)		*/\
+    0x81, 0x01,                    /*     INPUT (Cnst,Ary,Abs)		*/\
+    0x05, 0x01,                    /*     USAGE_PAGE (Generic Desktop)	*/\
+    0x09, 0x30,                    /*     USAGE (X)			*/\
+    0x09, 0x31,                    /*     USAGE (Y)			*/\
+    0x09, 0x38,                    /*     USAGE (Wheel)			*/\
+    0x15, 0x81,                    /*     LOGICAL_MINIMUM (-127)	*/\
+    0x25, 0x7f,                    /*     LOGICAL_MAXIMUM (127)		*/\
+    0x75, 0x08,                    /*     REPORT_SIZE (8)		*/\
+    0x95, 0x03,                    /*     REPORT_COUNT (3)		*/\
+    0x81, 0x06,                    /*     INPUT (Data,Var,Rel)		*/\
+    0xc0,                          /*   END_COLLECTION			*/\
+    0xc0,                          /* END_COLLECTION			*/\
+    0x05, 0x0d,                    /* USAGE_PAGE (Digitizers)		*/\
+    0x09, 0x01,                    /* USAGE (Pointer)			*/\
+    0xa1, 0x01,                    /* COLLECTION (Applicaption)		*/\
+    0x85, 0x02,                    /*   REPORT_ID (2)			*/\
+    0x05, 0x0d,                    /*   USAGE_PAGE (Digitizers)		*/\
+    0x09, 0x01,                    /*   USAGE (Digitizer)		*/\
+    0xa1, 0x00,                    /*   COLLECTION (Physical)		*/\
+    0x09, 0x33,                    /*     USAGE (Touch)			*/\
+    0x09, 0x44,                    /*     USAGE (Barrel Switch)		*/\
+    0x09, 0x44,                    /*     USAGE (Barrel Switch)		*/\
+    0x15, 0x00,                    /*     LOGICAL_MINIMUM (0)		*/\
+    0x25, 0x01,                    /*     LOGICAL_MAXIMUM (1)		*/\
+    0x75, 0x01,                    /*     REPORT_SIZE (1)		*/\
+    0x95, 0x03,                    /*     REPORT_COUNT (3)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0x75, 0x01,                    /*     REPORT_SIZE (1)		*/\
+    0x95, 0x02,                    /*     REPORT_COUNT (2)		*/\
+    0x81, 0x01,                    /*     INPUT (Cnst,Ary,Abs)		*/\
+    0x09, 0x3c,                    /*     USAGE (Invert)		*/\
+    0x09, 0x38,                    /*     USAGE (Transducer Index)	*/\
+    0x09, 0x32,                    /*     USAGE (In Range)		*/\
+    0x75, 0x01,                    /*     REPORT_SIZE (1)		*/\
+    0x95, 0x03,                    /*     REPORT_COUNT (3)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0x05, 0x01,                    /*     USAGE_PAGE (Generic Desktop)	*/\
+    0x09, 0x30,                    /*     USAGE (X)			*/\
+    0x15, 0x00,                    /*     LOGICAL_MINIMUM (0)		*/\
+    0x26, 0xde, 0x27,              /*     LOGICAL_MAXIMUM (10206)	*/\
+    0x75, 0x10,                    /*     REPORT_SIZE (16)		*/\
+    0x95, 0x01,                    /*     REPORT_COUNT (1)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0x09, 0x31,                    /*     USAGE (Y)			*/\
+    0x26, 0xfe, 0x1c,              /*     LOGICAL_MAXIMUM (7422)	*/\
+    0x75, 0x10,                    /*     REPORT_SIZE (16)		*/\
+    0x95, 0x01,                    /*     REPORT_COUNT (1)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0x05, 0x0d,                    /*     USAGE_PAGE (Digitizers)	*/\
+    0x09, 0x30,                    /*     USAGE (Tip Pressure)		*/\
+    0x26, 0xff, 0x01,              /*     LOGICAL_MAXIMUM (511)		*/\
+    0x75, 0x10,                    /*     REPORT_SIZE (16)		*/\
+    0x95, 0x01,                    /*     REPORT_COUNT (1)		*/\
+    0x81, 0x02,                    /*     INPUT (Data,Var,Abs)		*/\
+    0xc0,                          /*   END_COLLECTION			*/\
+    0x05, 0x0d,                    /*   USAGE_PAGE (Digitizers)		*/\
+    0x09, 0x00,                    /*   USAGE (Undefined)		*/\
+    0x85, 0x02,                    /*   REPORT_ID (2)			*/\
+    0x95, 0x01,                    /*   REPORT_COUNT (1)		*/\
+    0xb1, 0x02,                    /*   FEATURE (Data,Var,Abs)		*/\
+    0x09, 0x00,                    /*   USAGE (Undefined)		*/\
+    0x85, 0x03,                    /*   REPORT_ID (3)			*/\
+    0x95, 0x01,                    /*   REPORT_COUNT (1)		*/\
+    0xb1, 0x02,                    /*   FEATURE (Data,Var,Abs)		*/\
+    0xc0                           /* END_COLLECTION			*/\
+
+/*
+ * The descriptor has no output report format, thus preventing you from
+ * controlling the LEDs and the built-in rumblers.
+ */
+#define	UHID_XB360GP_REPORT_DESCR(...) \
+    0x05, 0x01,		/* USAGE PAGE (Generic Desktop)		*/\
+    0x09, 0x05,		/* USAGE (Gamepad)			*/\
+    0xa1, 0x01,		/* COLLECTION (Application)		*/\
+    /* Unused */\
+    0x75, 0x08,		/*  REPORT SIZE (8)			*/\
+    0x95, 0x01,		/*  REPORT COUNT (1)			*/\
+    0x81, 0x01,		/*  INPUT (Constant)			*/\
+    /* Byte count */\
+    0x75, 0x08,		/*  REPORT SIZE (8)			*/\
+    0x95, 0x01,		/*  REPORT COUNT (1)			*/\
+    0x05, 0x01,		/*  USAGE PAGE (Generic Desktop)	*/\
+    0x09, 0x3b,		/*  USAGE (Byte Count)			*/\
+    0x81, 0x01,		/*  INPUT (Constant)			*/\
+    /* D-Pad */\
+    0x05, 0x01,		/*  USAGE PAGE (Generic Desktop)	*/\
+    0x09, 0x01,		/*  USAGE (Pointer)			*/\
+    0xa1, 0x00,		/*  COLLECTION (Physical)		*/\
+    0x75, 0x01,		/*   REPORT SIZE (1)			*/\
+    0x15, 0x00,		/*   LOGICAL MINIMUM (0)		*/\
+    0x25, 0x01,		/*   LOGICAL MAXIMUM (1)		*/\
+    0x35, 0x00,		/*   PHYSICAL MINIMUM (0)		*/\
+    0x45, 0x01,		/*   PHYSICAL MAXIMUM (1)		*/\
+    0x95, 0x04,		/*   REPORT COUNT (4)			*/\
+    0x05, 0x01,		/*   USAGE PAGE (Generic Desktop)	*/\
+    0x09, 0x90,		/*   USAGE (D-Pad Up)			*/\
+    0x09, 0x91,		/*   USAGE (D-Pad Down)			*/\
+    0x09, 0x93,		/*   USAGE (D-Pad Left)			*/\
+    0x09, 0x92,		/*   USAGE (D-Pad Right)		*/\
+    0x81, 0x02,		/*   INPUT (Data, Variable, Absolute)	*/\
+    0xc0,		/*  END COLLECTION			*/\
+    /* Buttons 5-11 */\
+    0x75, 0x01,		/*  REPORT SIZE (1)			*/\
+    0x15, 0x00,		/*  LOGICAL MINIMUM (0)			*/\
+    0x25, 0x01,		/*  LOGICAL MAXIMUM (1)			*/\
+    0x35, 0x00,		/*  PHYSICAL MINIMUM (0)		*/\
+    0x45, 0x01,		/*  PHYSICAL MAXIMUM (1)		*/\
+    0x95, 0x07,		/*  REPORT COUNT (7)			*/\
+    0x05, 0x09,		/*  USAGE PAGE (Button)			*/\
+    0x09, 0x08,		/*  USAGE (Button 8)			*/\
+    0x09, 0x07,		/*  USAGE (Button 7)			*/\
+    0x09, 0x09,		/*  USAGE (Button 9)			*/\
+    0x09, 0x0a,		/*  USAGE (Button 10)			*/\
+    0x09, 0x05,		/*  USAGE (Button 5)			*/\
+    0x09, 0x06,		/*  USAGE (Button 6)			*/\
+    0x09, 0x0b,		/*  USAGE (Button 11)			*/\
+    0x81, 0x02,		/*  INPUT (Data, Variable, Absolute)	*/\
+    /* Unused */\
+    0x75, 0x01,		/*  REPORT SIZE (1)			*/\
+    0x95, 0x01,		/*  REPORT COUNT (1)			*/\
+    0x81, 0x01,		/*  INPUT (Constant)			*/\
+    /* Buttons 1-4 */\
+    0x75, 0x01,		/*  REPORT SIZE (1)			*/\
+    0x15, 0x00,		/*  LOGICAL MINIMUM (0)			*/\
+    0x25, 0x01,		/*  LOGICAL MAXIMUM (1)			*/\
+    0x35, 0x00,		/*  PHYSICAL MINIMUM (0)		*/\
+    0x45, 0x01,		/*  PHYSICAL MAXIMUM (1)		*/\
+    0x95, 0x04,		/*  REPORT COUNT (4)			*/\
+    0x05, 0x09,		/*  USAGE PAGE (Button)			*/\
+    0x19, 0x01,		/*  USAGE MINIMUM (Button 1)		*/\
+    0x29, 0x04,		/*  USAGE MAXIMUM (Button 4)		*/\
+    0x81, 0x02,		/*  INPUT (Data, Variable, Absolute)	*/\
+    /* Triggers */\
+    0x75, 0x08,		/*  REPORT SIZE (8)			*/\
+    0x15, 0x00,		/*  LOGICAL MINIMUM (0)			*/\
+    0x26, 0xff, 0x00,	/*  LOGICAL MAXIMUM (255)		*/\
+    0x35, 0x00,		/*  PHYSICAL MINIMUM (0)		*/\
+    0x46, 0xff, 0x00,	/*  PHYSICAL MAXIMUM (255)		*/\
+    0x95, 0x02,		/*  REPORT SIZE (2)			*/\
+    0x05, 0x01,		/*  USAGE PAGE (Generic Desktop)	*/\
+    0x09, 0x32,		/*  USAGE (Z)				*/\
+    0x09, 0x35,		/*  USAGE (Rz)				*/\
+    0x81, 0x02,		/*  INPUT (Data, Variable, Absolute)	*/\
+    /* Sticks */\
+    0x75, 0x10,		/*  REPORT SIZE (16)			*/\
+    0x16, 0x00, 0x80,	/*  LOGICAL MINIMUM (-32768)		*/\
+    0x26, 0xff, 0x7f,	/*  LOGICAL MAXIMUM (32767)		*/\
+    0x36, 0x00, 0x80,	/*  PHYSICAL MINIMUM (-32768)		*/\
+    0x46, 0xff, 0x7f,	/*  PHYSICAL MAXIMUM (32767)		*/\
+    0x95, 0x04,		/*  REPORT COUNT (4)			*/\
+    0x05, 0x01,		/*  USAGE PAGE (Generic Desktop)	*/\
+    0x09, 0x30,		/*  USAGE (X)				*/\
+    0x09, 0x31,		/*  USAGE (Y)				*/\
+    0x09, 0x33,		/*  USAGE (Rx)				*/\
+    0x09, 0x34,		/*  USAGE (Ry)				*/\
+    0x81, 0x02,		/*  INPUT (Data, Variable, Absolute)	*/\
+    /* Unused */\
+    0x75, 0x30,		/*  REPORT SIZE (48)			*/\
+    0x95, 0x01,		/*  REPORT COUNT (1)			*/\
+    0x81, 0x01,		/*  INPUT (Constant)			*/\
+    0xc0		/* END COLLECTION			*/\
+
diff --git a/freebsd/sys/dev/usb/input/wsp.c b/freebsd/sys/dev/usb/input/wsp.c
new file mode 100644
index 0000000..cff2972
--- /dev/null
+++ b/freebsd/sys/dev/usb/input/wsp.c
@@ -0,0 +1,1405 @@
+#include <machine/rtems-bsd-kernel-space.h>
+
+/*-
+ * Copyright (c) 2012 Huang Wen Hui
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <rtems/bsd/sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <rtems/bsd/sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/file.h>
+#include <sys/selinfo.h>
+#include <sys/poll.h>
+#include <sys/sysctl.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#include <rtems/bsd/local/usbdevs.h>
+
+#define	USB_DEBUG_VAR wsp_debug
+#include <dev/usb/usb_debug.h>
+
+#include <sys/mouse.h>
+
+#define	WSP_DRIVER_NAME "wsp"
+#define	WSP_BUFFER_MAX	1024
+
+#define	WSP_CLAMP(x,low,high) do {		\
+	if ((x) < (low))			\
+		(x) = (low);			\
+	else if ((x) > (high))			\
+		(x) = (high);			\
+} while (0)
+
+/* Tunables */
+static	SYSCTL_NODE(_hw_usb, OID_AUTO, wsp, CTLFLAG_RW, 0, "USB wsp");
+
+#ifdef USB_DEBUG
+enum wsp_log_level {
+	WSP_LLEVEL_DISABLED = 0,
+	WSP_LLEVEL_ERROR,
+	WSP_LLEVEL_DEBUG,		/* for troubleshooting */
+	WSP_LLEVEL_INFO,		/* for diagnostics */
+};
+static int wsp_debug = WSP_LLEVEL_ERROR;/* the default is to only log errors */
+
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &wsp_debug, WSP_LLEVEL_ERROR, "WSP debug level");
+#endif					/* USB_DEBUG */
+
+static struct wsp_tuning {
+	int	scale_factor;
+	int	z_factor;
+	int	pressure_touch_threshold;
+	int	pressure_untouch_threshold;
+	int	pressure_tap_threshold;
+	int	scr_hor_threshold;
+	int	enable_single_tap_clicks;
+}
+	wsp_tuning =
+{
+	.scale_factor = 12,
+	.z_factor = 5,
+	.pressure_touch_threshold = 50,
+	.pressure_untouch_threshold = 10,
+	.pressure_tap_threshold = 120,
+	.scr_hor_threshold = 20,
+	.enable_single_tap_clicks = 1,
+};
+
+static void
+wsp_runing_rangecheck(struct wsp_tuning *ptun)
+{
+	WSP_CLAMP(ptun->scale_factor, 1, 63);
+	WSP_CLAMP(ptun->z_factor, 1, 63);
+	WSP_CLAMP(ptun->pressure_touch_threshold, 1, 255);
+	WSP_CLAMP(ptun->pressure_untouch_threshold, 1, 255);
+	WSP_CLAMP(ptun->pressure_tap_threshold, 1, 255);
+	WSP_CLAMP(ptun->scr_hor_threshold, 1, 255);
+	WSP_CLAMP(ptun->enable_single_tap_clicks, 0, 1);
+}
+
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scale_factor, CTLFLAG_RWTUN,
+    &wsp_tuning.scale_factor, 0, "movement scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, z_factor, CTLFLAG_RWTUN,
+    &wsp_tuning.z_factor, 0, "Z-axis scale factor");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_touch_threshold, CTLFLAG_RWTUN,
+    &wsp_tuning.pressure_touch_threshold, 0, "touch pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_untouch_threshold, CTLFLAG_RWTUN,
+    &wsp_tuning.pressure_untouch_threshold, 0, "untouch pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, pressure_tap_threshold, CTLFLAG_RWTUN,
+    &wsp_tuning.pressure_tap_threshold, 0, "tap pressure threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, scr_hor_threshold, CTLFLAG_RWTUN,
+    &wsp_tuning.scr_hor_threshold, 0, "horizontal scrolling threshold");
+SYSCTL_INT(_hw_usb_wsp, OID_AUTO, enable_single_tap_clicks, CTLFLAG_RWTUN,
+    &wsp_tuning.enable_single_tap_clicks, 0, "enable single tap clicks");
+
+/*
+ * Some tables, structures, definitions and constant values for the
+ * touchpad protocol has been copied from Linux's
+ * "drivers/input/mouse/bcm5974.c" which has the following copyright
+ * holders under GPLv2. All device specific code in this driver has
+ * been written from scratch. The decoding algorithm is based on
+ * output from FreeBSD's usbdump.
+ *
+ * Copyright (C) 2008      Henrik Rydberg (rydberg at euromail.se)
+ * Copyright (C) 2008      Scott Shawcroft (scott.shawcroft at gmail.com)
+ * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg at kroah.com)
+ * Copyright (C) 2005      Johannes Berg (johannes at sipsolutions.net)
+ * Copyright (C) 2005      Stelian Pop (stelian at popies.net)
+ * Copyright (C) 2005      Frank Arnold (frank at scirocco-5v-turbo.de)
+ * Copyright (C) 2005      Peter Osterlund (petero2 at telia.com)
+ * Copyright (C) 2005      Michael Hanselmann (linux-kernel at hansmi.ch)
+ * Copyright (C) 2006      Nicolas Boichat (nicolas at boichat.ch)
+ */
+
+/* button data structure */
+struct bt_data {
+	uint8_t	unknown1;		/* constant */
+	uint8_t	button;			/* left button */
+	uint8_t	rel_x;			/* relative x coordinate */
+	uint8_t	rel_y;			/* relative y coordinate */
+} __packed;
+
+/* trackpad header types */
+enum tp_type {
+	TYPE1,			/* plain trackpad */
+	TYPE2,			/* button integrated in trackpad */
+	TYPE3,			/* additional header fields since June 2013 */
+	TYPE4                   /* additional header field for pressure data */
+};
+
+/* trackpad finger data offsets, le16-aligned */
+#define	FINGER_TYPE1		(13 * 2)
+#define	FINGER_TYPE2		(15 * 2)
+#define	FINGER_TYPE3		(19 * 2)
+#define	FINGER_TYPE4		(23 * 2)
+
+/* trackpad button data offsets */
+#define	BUTTON_TYPE2		15
+#define	BUTTON_TYPE3		23
+#define	BUTTON_TYPE4		31
+
+/* list of device capability bits */
+#define	HAS_INTEGRATED_BUTTON	1
+
+/* trackpad finger data block size */
+#define FSIZE_TYPE1             (14 * 2)
+#define FSIZE_TYPE2             (14 * 2)
+#define FSIZE_TYPE3             (14 * 2)
+#define FSIZE_TYPE4             (15 * 2)
+
+/* trackpad finger header - little endian */
+struct tp_header {
+	uint8_t	flag;
+	uint8_t	sn0;
+	uint16_t wFixed0;
+	uint32_t dwSn1;
+	uint32_t dwFixed1;
+	uint16_t wLength;
+	uint8_t	nfinger;
+	uint8_t	ibt;
+	int16_t	wUnknown[6];
+	uint8_t	q1;
+	uint8_t	q2;
+} __packed;
+
+/* trackpad finger structure - little endian */
+struct tp_finger {
+	int16_t	origin;			/* zero when switching track finger */
+	int16_t	abs_x;			/* absolute x coodinate */
+	int16_t	abs_y;			/* absolute y coodinate */
+	int16_t	rel_x;			/* relative x coodinate */
+	int16_t	rel_y;			/* relative y coodinate */
+	int16_t	tool_major;		/* tool area, major axis */
+	int16_t	tool_minor;		/* tool area, minor axis */
+	int16_t	orientation;		/* 16384 when point, else 15 bit angle */
+	int16_t	touch_major;		/* touch area, major axis */
+	int16_t	touch_minor;		/* touch area, minor axis */
+	int16_t	unused[2];		/* zeros */
+	int16_t pressure;		/* pressure on forcetouch touchpad */
+	int16_t	multi;			/* one finger: varies, more fingers:
+				 	 * constant */
+} __packed;
+
+/* trackpad finger data size, empirically at least ten fingers */
+#define	MAX_FINGERS		16
+#define	SIZEOF_FINGER		sizeof(struct tp_finger)
+#define	SIZEOF_ALL_FINGERS	(MAX_FINGERS * SIZEOF_FINGER)
+
+#if (WSP_BUFFER_MAX < ((MAX_FINGERS * FSIZE_TYPE4) + FINGER_TYPE4))
+#error "WSP_BUFFER_MAX is too small"
+#endif
+
+enum {
+	WSP_FLAG_WELLSPRING1,
+	WSP_FLAG_WELLSPRING2,
+	WSP_FLAG_WELLSPRING3,
+	WSP_FLAG_WELLSPRING4,
+	WSP_FLAG_WELLSPRING4A,
+	WSP_FLAG_WELLSPRING5,
+	WSP_FLAG_WELLSPRING6A,
+	WSP_FLAG_WELLSPRING6,
+	WSP_FLAG_WELLSPRING5A,
+	WSP_FLAG_WELLSPRING7,
+	WSP_FLAG_WELLSPRING7A,
+	WSP_FLAG_WELLSPRING8,
+	WSP_FLAG_WELLSPRING9,
+	WSP_FLAG_MAX,
+};
+
+/* device-specific configuration */
+struct wsp_dev_params {
+	uint8_t	caps;			/* device capability bitmask */
+	uint8_t	tp_type;		/* type of trackpad interface */
+	uint8_t	tp_button;		/* offset to button data */
+	uint8_t	tp_offset;		/* offset to trackpad finger data */
+	uint8_t tp_fsize;		/* bytes in single finger block */
+	uint8_t tp_delta;		/* offset from header to finger struct */
+	uint8_t iface_index;
+	uint8_t um_size;		/* usb control message length */
+	uint8_t um_req_val;		/* usb control message value */
+	uint8_t um_req_idx;		/* usb control message index */
+	uint8_t um_switch_idx;		/* usb control message mode switch index */
+	uint8_t um_switch_on;		/* usb control message mode switch on */
+	uint8_t um_switch_off;		/* usb control message mode switch off */
+};
+
+static const struct wsp_dev_params wsp_dev_params[WSP_FLAG_MAX] = {
+	[WSP_FLAG_WELLSPRING1] = {
+		.caps = 0,
+		.tp_type = TYPE1,
+		.tp_button = 0,
+		.tp_offset = FINGER_TYPE1,
+		.tp_fsize = FSIZE_TYPE1,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING2] = {
+		.caps = 0,
+		.tp_type = TYPE1,
+		.tp_button = 0,
+		.tp_offset = FINGER_TYPE1,
+		.tp_fsize = FSIZE_TYPE1,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING3] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING4] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING4A] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING5] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING6] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING5A] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING6A] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING7] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING7A] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE2,
+		.tp_button = BUTTON_TYPE2,
+		.tp_offset = FINGER_TYPE2,
+		.tp_fsize = FSIZE_TYPE2,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING8] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE3,
+		.tp_button = BUTTON_TYPE3,
+		.tp_offset = FINGER_TYPE3,
+		.tp_fsize = FSIZE_TYPE3,
+		.tp_delta = 0,
+		.iface_index = 0,
+		.um_size = 8,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x00,
+		.um_switch_idx = 0,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x08,
+	},
+	[WSP_FLAG_WELLSPRING9] = {
+		.caps = HAS_INTEGRATED_BUTTON,
+		.tp_type = TYPE4,
+		.tp_button = BUTTON_TYPE4,
+		.tp_offset = FINGER_TYPE4,
+		.tp_fsize = FSIZE_TYPE4,
+		.tp_delta = 2,
+		.iface_index = 2,
+		.um_size = 2,
+		.um_req_val = 0x03,
+		.um_req_idx = 0x02,
+		.um_switch_idx = 1,
+		.um_switch_on = 0x01,
+		.um_switch_off = 0x00,
+	},
+};
+
+#define	WSP_DEV(v,p,i) { USB_VPI(USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i) }
+
+static const STRUCT_USB_HOST_ID wsp_devs[] = {
+	/* MacbookAir1.1 */
+	WSP_DEV(APPLE, WELLSPRING_ANSI, WSP_FLAG_WELLSPRING1),
+	WSP_DEV(APPLE, WELLSPRING_ISO, WSP_FLAG_WELLSPRING1),
+	WSP_DEV(APPLE, WELLSPRING_JIS, WSP_FLAG_WELLSPRING1),
+
+	/* MacbookProPenryn, aka wellspring2 */
+	WSP_DEV(APPLE, WELLSPRING2_ANSI, WSP_FLAG_WELLSPRING2),
+	WSP_DEV(APPLE, WELLSPRING2_ISO, WSP_FLAG_WELLSPRING2),
+	WSP_DEV(APPLE, WELLSPRING2_JIS, WSP_FLAG_WELLSPRING2),
+
+	/* Macbook5,1 (unibody), aka wellspring3 */
+	WSP_DEV(APPLE, WELLSPRING3_ANSI, WSP_FLAG_WELLSPRING3),
+	WSP_DEV(APPLE, WELLSPRING3_ISO, WSP_FLAG_WELLSPRING3),
+	WSP_DEV(APPLE, WELLSPRING3_JIS, WSP_FLAG_WELLSPRING3),
+
+	/* MacbookAir3,2 (unibody), aka wellspring4 */
+	WSP_DEV(APPLE, WELLSPRING4_ANSI, WSP_FLAG_WELLSPRING4),
+	WSP_DEV(APPLE, WELLSPRING4_ISO, WSP_FLAG_WELLSPRING4),
+	WSP_DEV(APPLE, WELLSPRING4_JIS, WSP_FLAG_WELLSPRING4),
+
+	/* MacbookAir3,1 (unibody), aka wellspring4 */
+	WSP_DEV(APPLE, WELLSPRING4A_ANSI, WSP_FLAG_WELLSPRING4A),
+	WSP_DEV(APPLE, WELLSPRING4A_ISO, WSP_FLAG_WELLSPRING4A),
+	WSP_DEV(APPLE, WELLSPRING4A_JIS, WSP_FLAG_WELLSPRING4A),
+
+	/* Macbook8 (unibody, March 2011) */
+	WSP_DEV(APPLE, WELLSPRING5_ANSI, WSP_FLAG_WELLSPRING5),
+	WSP_DEV(APPLE, WELLSPRING5_ISO, WSP_FLAG_WELLSPRING5),
+	WSP_DEV(APPLE, WELLSPRING5_JIS, WSP_FLAG_WELLSPRING5),
+
+	/* MacbookAir4,1 (unibody, July 2011) */
+	WSP_DEV(APPLE, WELLSPRING6A_ANSI, WSP_FLAG_WELLSPRING6A),
+	WSP_DEV(APPLE, WELLSPRING6A_ISO, WSP_FLAG_WELLSPRING6A),
+	WSP_DEV(APPLE, WELLSPRING6A_JIS, WSP_FLAG_WELLSPRING6A),
+
+	/* MacbookAir4,2 (unibody, July 2011) */
+	WSP_DEV(APPLE, WELLSPRING6_ANSI, WSP_FLAG_WELLSPRING6),
+	WSP_DEV(APPLE, WELLSPRING6_ISO, WSP_FLAG_WELLSPRING6),
+	WSP_DEV(APPLE, WELLSPRING6_JIS, WSP_FLAG_WELLSPRING6),
+
+	/* Macbook8,2 (unibody) */
+	WSP_DEV(APPLE, WELLSPRING5A_ANSI, WSP_FLAG_WELLSPRING5A),
+	WSP_DEV(APPLE, WELLSPRING5A_ISO, WSP_FLAG_WELLSPRING5A),
+	WSP_DEV(APPLE, WELLSPRING5A_JIS, WSP_FLAG_WELLSPRING5A),
+
+	/* MacbookPro10,1 (unibody, June 2012) */
+	/* MacbookPro11,1-3 (unibody, June 2013) */
+	WSP_DEV(APPLE, WELLSPRING7_ANSI, WSP_FLAG_WELLSPRING7),
+	WSP_DEV(APPLE, WELLSPRING7_ISO, WSP_FLAG_WELLSPRING7),
+	WSP_DEV(APPLE, WELLSPRING7_JIS, WSP_FLAG_WELLSPRING7),
+
+	/* MacbookPro10,2 (unibody, October 2012) */
+	WSP_DEV(APPLE, WELLSPRING7A_ANSI, WSP_FLAG_WELLSPRING7A),
+	WSP_DEV(APPLE, WELLSPRING7A_ISO, WSP_FLAG_WELLSPRING7A),
+	WSP_DEV(APPLE, WELLSPRING7A_JIS, WSP_FLAG_WELLSPRING7A),
+
+	/* MacbookAir6,2 (unibody, June 2013) */
+	WSP_DEV(APPLE, WELLSPRING8_ANSI, WSP_FLAG_WELLSPRING8),
+	WSP_DEV(APPLE, WELLSPRING8_ISO, WSP_FLAG_WELLSPRING8),
+	WSP_DEV(APPLE, WELLSPRING8_JIS, WSP_FLAG_WELLSPRING8),
+
+	/* MacbookPro12,1 MacbookPro11,4 */
+	WSP_DEV(APPLE, WELLSPRING9_ANSI, WSP_FLAG_WELLSPRING9),
+	WSP_DEV(APPLE, WELLSPRING9_ISO, WSP_FLAG_WELLSPRING9),
+	WSP_DEV(APPLE, WELLSPRING9_JIS, WSP_FLAG_WELLSPRING9),
+};
+
+#define	WSP_FIFO_BUF_SIZE	 8	/* bytes */
+#define	WSP_FIFO_QUEUE_MAXLEN	50	/* units */
+
+enum {
+	WSP_INTR_DT,
+	WSP_N_TRANSFER,
+};
+
+struct wsp_softc {
+	struct usb_device *sc_usb_device;
+	struct mtx sc_mutex;		/* for synchronization */
+	struct usb_xfer *sc_xfer[WSP_N_TRANSFER];
+	struct usb_fifo_sc sc_fifo;
+
+	const struct wsp_dev_params *sc_params;	/* device configuration */
+
+	mousehw_t sc_hw;
+	mousemode_t sc_mode;
+	u_int	sc_pollrate;
+	mousestatus_t sc_status;
+	u_int	sc_state;
+#define	WSP_ENABLED	       0x01
+
+	struct tp_finger *index[MAX_FINGERS];	/* finger index data */
+	int16_t	pos_x[MAX_FINGERS];	/* position array */
+	int16_t	pos_y[MAX_FINGERS];	/* position array */
+	u_int	sc_touch;		/* touch status */
+#define	WSP_UNTOUCH		0x00
+#define	WSP_FIRST_TOUCH		0x01
+#define	WSP_SECOND_TOUCH	0x02
+#define	WSP_TOUCHING		0x04
+	int16_t	pre_pos_x;		/* previous position array */
+	int16_t	pre_pos_y;		/* previous position array */
+	int	dx_sum;			/* x axis cumulative movement */
+	int	dy_sum;			/* y axis cumulative movement */
+	int	dz_sum;			/* z axis cumulative movement */
+	int	dz_count;
+#define	WSP_DZ_MAX_COUNT	32
+	int	dt_sum;			/* T-axis cumulative movement */
+	int	rdx;			/* x axis remainder of divide by scale_factor */
+	int	rdy;			/* y axis remainder of divide by scale_factor */
+	int	rdz;			/* z axis remainder of divide by scale_factor */
+	int	tp_datalen;
+	uint8_t o_ntouch;		/* old touch finger status */
+	uint8_t	finger;			/* 0 or 1 *, check which finger moving */
+	uint16_t intr_count;
+#define	WSP_TAP_THRESHOLD	3
+#define	WSP_TAP_MAX_COUNT	20
+	int	distance;		/* the distance of 2 fingers */
+#define	MAX_DISTANCE		2500	/* the max allowed distance */
+	uint8_t	ibtn;			/* button status in tapping */
+	uint8_t	ntaps;			/* finger status in tapping */
+	uint8_t	scr_mode;		/* scroll status in movement */
+#define	WSP_SCR_NONE		0
+#define	WSP_SCR_VER		1
+#define	WSP_SCR_HOR		2
+	uint8_t tp_data[WSP_BUFFER_MAX] __aligned(4);		/* trackpad transferred data */
+};
+
+/*
+ * function prototypes
+ */
+static usb_fifo_cmd_t wsp_start_read;
+static usb_fifo_cmd_t wsp_stop_read;
+static usb_fifo_open_t wsp_open;
+static usb_fifo_close_t wsp_close;
+static usb_fifo_ioctl_t wsp_ioctl;
+
+static struct usb_fifo_methods wsp_fifo_methods = {
+	.f_open = &wsp_open,
+	.f_close = &wsp_close,
+	.f_ioctl = &wsp_ioctl,
+	.f_start_read = &wsp_start_read,
+	.f_stop_read = &wsp_stop_read,
+	.basename[0] = WSP_DRIVER_NAME,
+};
+
+/* device initialization and shutdown */
+static int wsp_enable(struct wsp_softc *sc);
+static void wsp_disable(struct wsp_softc *sc);
+
+/* updating fifo */
+static void wsp_reset_buf(struct wsp_softc *sc);
+static void wsp_add_to_queue(struct wsp_softc *, int, int, int, uint32_t);
+
+/* Device methods. */
+static device_probe_t wsp_probe;
+static device_attach_t wsp_attach;
+static device_detach_t wsp_detach;
+static usb_callback_t wsp_intr_callback;
+
+static const struct usb_config wsp_config[WSP_N_TRANSFER] = {
+	[WSP_INTR_DT] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {
+			.pipe_bof = 0,
+			.short_xfer_ok = 1,
+		},
+		.bufsize = WSP_BUFFER_MAX,
+		.callback = &wsp_intr_callback,
+	},
+};
+
+static usb_error_t
+wsp_set_device_mode(struct wsp_softc *sc, uint8_t on)
+{
+	const struct wsp_dev_params *params = sc->sc_params;
+	uint8_t	mode_bytes[8];
+	usb_error_t err;
+
+	/* Type 3 does not require a mode switch */
+	if (params->tp_type == TYPE3)
+		return 0;
+
+	err = usbd_req_get_report(sc->sc_usb_device, NULL,
+	    mode_bytes, params->um_size, params->iface_index,
+	    params->um_req_val, params->um_req_idx);
+
+	if (err != USB_ERR_NORMAL_COMPLETION) {
+		DPRINTF("Failed to read device mode (%d)\n", err);
+		return (err);
+	}
+
+	/*
+	 * XXX Need to wait at least 250ms for hardware to get
+	 * ready. The device mode handling appears to be handled
+	 * asynchronously and we should not issue these commands too
+	 * quickly.
+	 */
+	pause("WHW", hz / 4);
+
+	mode_bytes[params->um_switch_idx] = 
+	    on ? params->um_switch_on : params->um_switch_off;
+
+	return (usbd_req_set_report(sc->sc_usb_device, NULL,
+	    mode_bytes, params->um_size, params->iface_index, 
+	    params->um_req_val, params->um_req_idx));
+}
+
+static int
+wsp_enable(struct wsp_softc *sc)
+{
+	/* reset status */
+	memset(&sc->sc_status, 0, sizeof(sc->sc_status));
+	sc->sc_state |= WSP_ENABLED;
+
+	DPRINTFN(WSP_LLEVEL_INFO, "enabled wsp\n");
+	return (0);
+}
+
+static void
+wsp_disable(struct wsp_softc *sc)
+{
+	sc->sc_state &= ~WSP_ENABLED;
+	DPRINTFN(WSP_LLEVEL_INFO, "disabled wsp\n");
+}
+
+static int
+wsp_probe(device_t self)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(self);
+	struct usb_interface_descriptor *id;
+	struct usb_interface *iface;
+	uint8_t i;
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+
+	/* figure out first interface matching */
+	for (i = 1;; i++) {
+		iface = usbd_get_iface(uaa->device, i);
+		if (iface == NULL || i == 3)
+			return (ENXIO);
+		id = iface->idesc;
+		if ((id == NULL) ||
+		    (id->bInterfaceClass != UICLASS_HID) ||
+		    (id->bInterfaceProtocol != 0 &&
+		    id->bInterfaceProtocol != UIPROTO_MOUSE))
+			continue;
+		break;
+	}
+	/* check if we are attaching to the first match */
+	if (uaa->info.bIfaceIndex != i)
+		return (ENXIO);
+	return (usbd_lookup_id_by_uaa(wsp_devs, sizeof(wsp_devs), uaa));
+}
+
+static int
+wsp_attach(device_t dev)
+{
+	struct wsp_softc *sc = device_get_softc(dev);
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	usb_error_t err;
+	void *d_ptr = NULL;
+	uint16_t d_len;
+
+	DPRINTFN(WSP_LLEVEL_INFO, "sc=%p\n", sc);
+
+	/* Get HID descriptor */
+	err = usbd_req_get_hid_desc(uaa->device, NULL, &d_ptr,
+	    &d_len, M_TEMP, uaa->info.bIfaceIndex);
+
+	if (err == USB_ERR_NORMAL_COMPLETION) {
+		/* Get HID report descriptor length */
+		sc->tp_datalen = hid_report_size(d_ptr, d_len, hid_input, NULL);
+		free(d_ptr, M_TEMP);
+
+		if (sc->tp_datalen <= 0 || sc->tp_datalen > WSP_BUFFER_MAX) {
+			DPRINTF("Invalid datalength or too big "
+			    "datalength: %d\n", sc->tp_datalen);
+			return (ENXIO);
+		}
+	} else {
+		return (ENXIO);
+	}
+
+	sc->sc_usb_device = uaa->device;
+
+	/* get device specific configuration */
+	sc->sc_params = wsp_dev_params + USB_GET_DRIVER_INFO(uaa);
+
+	/*
+	 * By default the touchpad behaves like a HID device, sending
+	 * packets with reportID = 8. Such reports contain only
+	 * limited information. They encode movement deltas and button
+	 * events, but do not include data from the pressure
+	 * sensors. The device input mode can be switched from HID
+	 * reports to raw sensor data using vendor-specific USB
+	 * control commands:
+	 */
+
+	/*
+	 * During re-enumeration of the device we need to force the
+	 * device back into HID mode before switching it to RAW
+	 * mode. Else the device does not work like expected.
+	 */
+	err = wsp_set_device_mode(sc, 0);
+	if (err != USB_ERR_NORMAL_COMPLETION) {
+		DPRINTF("Failed to set mode to HID MODE (%d)\n", err);
+		return (ENXIO);
+	}
+
+	err = wsp_set_device_mode(sc, 1);
+	if (err != USB_ERR_NORMAL_COMPLETION) {
+		DPRINTF("failed to set mode to RAW MODE (%d)\n", err);
+		return (ENXIO);
+	}
+
+	mtx_init(&sc->sc_mutex, "wspmtx", NULL, MTX_DEF | MTX_RECURSE);
+
+	err = usbd_transfer_setup(uaa->device,
+	    &uaa->info.bIfaceIndex, sc->sc_xfer, wsp_config,
+	    WSP_N_TRANSFER, sc, &sc->sc_mutex);
+	if (err) {
+		DPRINTF("error=%s\n", usbd_errstr(err));
+		goto detach;
+	}
+	if (usb_fifo_attach(sc->sc_usb_device, sc, &sc->sc_mutex,
+	    &wsp_fifo_methods, &sc->sc_fifo,
+	    device_get_unit(dev), -1, uaa->info.bIfaceIndex,
+	    UID_ROOT, GID_OPERATOR, 0644)) {
+		goto detach;
+	}
+	device_set_usb_desc(dev);
+
+	sc->sc_hw.buttons = 3;
+	sc->sc_hw.iftype = MOUSE_IF_USB;
+	sc->sc_hw.type = MOUSE_PAD;
+	sc->sc_hw.model = MOUSE_MODEL_GENERIC;
+	sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+	sc->sc_mode.rate = -1;
+	sc->sc_mode.resolution = MOUSE_RES_UNKNOWN;
+	sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+	sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+	sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+
+	sc->sc_touch = WSP_UNTOUCH;
+	sc->scr_mode = WSP_SCR_NONE;
+
+	return (0);
+
+detach:
+	wsp_detach(dev);
+	return (ENOMEM);
+}
+
+static int
+wsp_detach(device_t dev)
+{
+	struct wsp_softc *sc = device_get_softc(dev);
+
+	(void) wsp_set_device_mode(sc, 0);
+
+	mtx_lock(&sc->sc_mutex);
+	if (sc->sc_state & WSP_ENABLED)
+		wsp_disable(sc);
+	mtx_unlock(&sc->sc_mutex);
+
+	usb_fifo_detach(&sc->sc_fifo);
+
+	usbd_transfer_unsetup(sc->sc_xfer, WSP_N_TRANSFER);
+
+	mtx_destroy(&sc->sc_mutex);
+
+	return (0);
+}
+
+static void
+wsp_intr_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct wsp_softc *sc = usbd_xfer_softc(xfer);
+	const struct wsp_dev_params *params = sc->sc_params;
+	struct usb_page_cache *pc;
+	struct tp_finger *f;
+	struct tp_header *h;
+	struct wsp_tuning tun = wsp_tuning;
+	int ntouch = 0;			/* the finger number in touch */
+	int ibt = 0;			/* button status */
+	int dx = 0;
+	int dy = 0;
+	int dz = 0;
+	int rdx = 0;
+	int rdy = 0;
+	int rdz = 0;
+	int len;
+	int i;
+
+	wsp_runing_rangecheck(&tun);
+
+	if (sc->dz_count == 0)
+		sc->dz_count = WSP_DZ_MAX_COUNT;
+
+	usbd_xfer_status(xfer, &len, NULL, NULL, NULL);
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+
+		/* copy out received data */
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_out(pc, 0, sc->tp_data, len);
+
+		if ((len < params->tp_offset + params->tp_fsize) ||
+		    ((len - params->tp_offset) % params->tp_fsize) != 0) {
+			DPRINTFN(WSP_LLEVEL_INFO, "Invalid length: %d, %x, %x\n",
+			    len, sc->tp_data[0], sc->tp_data[1]);
+			goto tr_setup;
+		}
+
+		if (len < sc->tp_datalen) {
+			/* make sure we don't process old data */
+			memset(sc->tp_data + len, 0, sc->tp_datalen - len);
+		}
+
+		h = (struct tp_header *)(sc->tp_data);
+
+		if (params->tp_type >= TYPE2) {
+			ibt = sc->tp_data[params->tp_button];
+			ntouch = sc->tp_data[params->tp_button - 1];
+		}
+		/* range check */
+		if (ntouch < 0)
+			ntouch = 0;
+		else if (ntouch > MAX_FINGERS)
+			ntouch = MAX_FINGERS;
+
+		for (i = 0; i != ntouch; i++) {
+			f = (struct tp_finger *)(sc->tp_data + params->tp_offset + params->tp_delta + i * params->tp_fsize);
+			/* swap endianness, if any */
+			if (le16toh(0x1234) != 0x1234) {
+				f->origin = le16toh((uint16_t)f->origin);
+				f->abs_x = le16toh((uint16_t)f->abs_x);
+				f->abs_y = le16toh((uint16_t)f->abs_y);
+				f->rel_x = le16toh((uint16_t)f->rel_x);
+				f->rel_y = le16toh((uint16_t)f->rel_y);
+				f->tool_major = le16toh((uint16_t)f->tool_major);
+				f->tool_minor = le16toh((uint16_t)f->tool_minor);
+				f->orientation = le16toh((uint16_t)f->orientation);
+				f->touch_major = le16toh((uint16_t)f->touch_major);
+				f->touch_minor = le16toh((uint16_t)f->touch_minor);
+				f->pressure = le16toh((uint16_t)f->pressure);
+				f->multi = le16toh((uint16_t)f->multi);
+			}
+			DPRINTFN(WSP_LLEVEL_INFO, 
+			    "[%d]ibt=%d, taps=%d, o=%4d, ax=%5d, ay=%5d, "
+			    "rx=%5d, ry=%5d, tlmaj=%4d, tlmin=%4d, ot=%4x, "
+			    "tchmaj=%4d, tchmin=%4d, presure=%4d, m=%4x\n",
+			    i, ibt, ntouch, f->origin, f->abs_x, f->abs_y,
+			    f->rel_x, f->rel_y, f->tool_major, f->tool_minor, f->orientation,
+			    f->touch_major, f->touch_minor, f->pressure, f->multi);
+			sc->pos_x[i] = f->abs_x;
+			sc->pos_y[i] = -f->abs_y;
+			sc->index[i] = f;
+		}
+
+		sc->sc_status.flags &= ~MOUSE_POSCHANGED;
+		sc->sc_status.flags &= ~MOUSE_STDBUTTONSCHANGED;
+		sc->sc_status.obutton = sc->sc_status.button;
+		sc->sc_status.button = 0;
+
+		if (ibt != 0) {
+			sc->sc_status.button |= MOUSE_BUTTON1DOWN;
+			sc->ibtn = 1;
+		}
+		sc->intr_count++;
+
+		if (sc->ntaps < ntouch) {
+			switch (ntouch) {
+			case 1:
+				if (sc->index[0]->touch_major > tun.pressure_tap_threshold &&
+				    sc->index[0]->tool_major <= 1200)
+					sc->ntaps = 1;
+				break;
+			case 2:
+				if (sc->index[0]->touch_major > tun.pressure_tap_threshold-30 &&
+				    sc->index[1]->touch_major > tun.pressure_tap_threshold-30)
+					sc->ntaps = 2;
+				break;
+			case 3:
+				if (sc->index[0]->touch_major > tun.pressure_tap_threshold-40 &&
+				    sc->index[1]->touch_major > tun.pressure_tap_threshold-40 &&
+				    sc->index[2]->touch_major > tun.pressure_tap_threshold-40)
+					sc->ntaps = 3;
+				break;
+			default:
+				break;
+			}
+		}
+		if (ntouch == 2) {
+			sc->distance = max(sc->distance, max(
+			    abs(sc->pos_x[0] - sc->pos_x[1]),
+			    abs(sc->pos_y[0] - sc->pos_y[1])));
+		}
+		if (sc->index[0]->touch_major < tun.pressure_untouch_threshold &&
+		    sc->sc_status.button == 0) {
+			sc->sc_touch = WSP_UNTOUCH;
+			if (sc->intr_count < WSP_TAP_MAX_COUNT &&
+			    sc->intr_count > WSP_TAP_THRESHOLD &&
+			    sc->ntaps && sc->ibtn == 0) {
+				/*
+				 * Add a pair of events (button-down and
+				 * button-up).
+				 */
+				switch (sc->ntaps) {
+				case 1:
+					if (!(params->caps & HAS_INTEGRATED_BUTTON) || tun.enable_single_tap_clicks) {
+						wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON1DOWN);
+						DPRINTFN(WSP_LLEVEL_INFO, "LEFT CLICK!\n");
+					}
+					break;
+				case 2:
+					DPRINTFN(WSP_LLEVEL_INFO, "sum_x=%5d, sum_y=%5d\n",
+					    sc->dx_sum, sc->dy_sum);
+					if (sc->distance < MAX_DISTANCE && abs(sc->dx_sum) < 5 &&
+					    abs(sc->dy_sum) < 5) {
+						wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON3DOWN);
+						DPRINTFN(WSP_LLEVEL_INFO, "RIGHT CLICK!\n");
+					}
+					break;
+				case 3:
+					wsp_add_to_queue(sc, 0, 0, 0, MOUSE_BUTTON2DOWN);
+					break;
+				default:
+					/* we don't handle taps of more than three fingers */
+					break;
+				}
+				wsp_add_to_queue(sc, 0, 0, 0, 0);	/* button release */
+			}
+			if ((sc->dt_sum / tun.scr_hor_threshold) != 0 &&
+			    sc->ntaps == 2 && sc->scr_mode == WSP_SCR_HOR) {
+
+				/*
+				 * translate T-axis into button presses
+				 * until further
+				 */
+				if (sc->dt_sum > 0)
+					wsp_add_to_queue(sc, 0, 0, 0, 1UL << 3);
+				else if (sc->dt_sum < 0)
+					wsp_add_to_queue(sc, 0, 0, 0, 1UL << 4);
+			}
+			sc->dz_count = WSP_DZ_MAX_COUNT;
+			sc->dz_sum = 0;
+			sc->intr_count = 0;
+			sc->ibtn = 0;
+			sc->ntaps = 0;
+			sc->finger = 0;
+			sc->distance = 0;
+			sc->dt_sum = 0;
+			sc->dx_sum = 0;
+			sc->dy_sum = 0;
+			sc->rdx = 0;
+			sc->rdy = 0;
+			sc->rdz = 0;
+			sc->scr_mode = WSP_SCR_NONE;
+		} else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold &&
+		    sc->sc_touch == WSP_UNTOUCH) {	/* ignore first touch */
+			sc->sc_touch = WSP_FIRST_TOUCH;
+		} else if (sc->index[0]->touch_major >= tun.pressure_touch_threshold &&
+		    sc->sc_touch == WSP_FIRST_TOUCH) {	/* ignore second touch */
+			sc->sc_touch = WSP_SECOND_TOUCH;
+			DPRINTFN(WSP_LLEVEL_INFO, "Fist pre_x=%5d, pre_y=%5d\n",
+			    sc->pre_pos_x, sc->pre_pos_y);
+		} else {
+			if (sc->sc_touch == WSP_SECOND_TOUCH)
+				sc->sc_touch = WSP_TOUCHING;
+
+			if (ntouch != 0 &&
+			    sc->index[0]->touch_major >= tun.pressure_touch_threshold) {
+				dx = sc->pos_x[0] - sc->pre_pos_x;
+				dy = sc->pos_y[0] - sc->pre_pos_y;
+
+				/* Ignore movement during button is releasing */
+				if (sc->ibtn != 0 && sc->sc_status.button == 0)
+					dx = dy = 0;
+
+				/* Ignore movement if ntouch changed */
+				if (sc->o_ntouch != ntouch)
+					dx = dy = 0;
+
+				/* Ignore unexpeted movement when typing */
+				if (ntouch == 1 && sc->index[0]->tool_major > 1200)
+					dx = dy = 0;
+
+				if (sc->ibtn != 0 && ntouch == 1 && 
+				    sc->intr_count < WSP_TAP_MAX_COUNT && 
+				    abs(sc->dx_sum) < 1 && abs(sc->dy_sum) < 1 )
+					dx = dy = 0;
+
+				if (ntouch == 2 && sc->sc_status.button != 0) {
+					dx = sc->pos_x[sc->finger] - sc->pre_pos_x;
+					dy = sc->pos_y[sc->finger] - sc->pre_pos_y;
+					
+					/*
+					 * Ignore movement of switch finger or
+					 * movement from ibt=0 to ibt=1
+					 */
+					if (sc->index[0]->origin == 0 || sc->index[1]->origin == 0 ||
+					    sc->sc_status.obutton != sc->sc_status.button) {
+						dx = dy = 0;
+						sc->finger = 0;
+					}
+					if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) <
+					    (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) &&
+					    sc->finger == 0) {
+						sc->sc_touch = WSP_SECOND_TOUCH;
+						dx = dy = 0;
+						sc->finger = 1;
+					}
+					if ((abs(sc->index[0]->rel_x) + abs(sc->index[0]->rel_y)) >=
+					    (abs(sc->index[1]->rel_x) + abs(sc->index[1]->rel_y)) &&
+					    sc->finger == 1) {
+						sc->sc_touch = WSP_SECOND_TOUCH;
+						dx = dy = 0;
+						sc->finger = 0;
+					}
+					DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, mov=%5d\n",
+					    dx, dy, sc->finger);
+				}
+				if (sc->dz_count--) {
+					rdz = (dy + sc->rdz) % tun.scale_factor;
+					sc->dz_sum -= (dy + sc->rdz) / tun.scale_factor;
+					sc->rdz = rdz;
+				}
+				if ((sc->dz_sum / tun.z_factor) != 0)
+					sc->dz_count = 0;
+			}
+			rdx = (dx + sc->rdx) % tun.scale_factor;
+			dx = (dx + sc->rdx) / tun.scale_factor;
+			sc->rdx = rdx;
+
+			rdy = (dy + sc->rdy) % tun.scale_factor;
+			dy = (dy + sc->rdy) / tun.scale_factor;
+			sc->rdy = rdy;
+
+			sc->dx_sum += dx;
+			sc->dy_sum += dy;
+
+			if (ntouch == 2 && sc->sc_status.button == 0) {
+				if (sc->scr_mode == WSP_SCR_NONE &&
+				    abs(sc->dx_sum) + abs(sc->dy_sum) > tun.scr_hor_threshold)
+					sc->scr_mode = abs(sc->dx_sum) >
+					    abs(sc->dy_sum) * 2 ? WSP_SCR_HOR : WSP_SCR_VER;
+				DPRINTFN(WSP_LLEVEL_INFO, "scr_mode=%5d, count=%d, dx_sum=%d, dy_sum=%d\n",
+				    sc->scr_mode, sc->intr_count, sc->dx_sum, sc->dy_sum);
+				if (sc->scr_mode == WSP_SCR_HOR)
+					sc->dt_sum += dx;
+				else
+					sc->dt_sum = 0;
+
+				dx = dy = 0;
+				if (sc->dz_count == 0)
+					dz = sc->dz_sum / tun.z_factor;
+				if (sc->scr_mode == WSP_SCR_HOR || 
+				    abs(sc->pos_x[0] - sc->pos_x[1]) > MAX_DISTANCE ||
+				    abs(sc->pos_y[0] - sc->pos_y[1]) > MAX_DISTANCE)
+					dz = 0;
+			}
+			if (ntouch == 3)
+				dx = dy = dz = 0;
+			if (sc->intr_count < WSP_TAP_MAX_COUNT &&
+			    abs(dx) < 3 && abs(dy) < 3 && abs(dz) < 3)
+				dx = dy = dz = 0;
+			else
+				sc->intr_count = WSP_TAP_MAX_COUNT;
+			if (dx || dy || dz)
+				sc->sc_status.flags |= MOUSE_POSCHANGED;
+			DPRINTFN(WSP_LLEVEL_INFO, "dx=%5d, dy=%5d, dz=%5d, sc_touch=%x, btn=%x\n",
+			    dx, dy, dz, sc->sc_touch, sc->sc_status.button);
+			sc->sc_status.dx += dx;
+			sc->sc_status.dy += dy;
+			sc->sc_status.dz += dz;
+
+			wsp_add_to_queue(sc, dx, -dy, dz, sc->sc_status.button);
+			if (sc->dz_count == 0) {
+				sc->dz_sum = 0;
+				sc->rdz = 0;
+			}
+
+		}
+		sc->pre_pos_x = sc->pos_x[0];
+		sc->pre_pos_y = sc->pos_y[0];
+
+		if (ntouch == 2 && sc->sc_status.button != 0) {
+			sc->pre_pos_x = sc->pos_x[sc->finger];
+			sc->pre_pos_y = sc->pos_y[sc->finger];
+		}
+		sc->o_ntouch = ntouch;
+
+	case USB_ST_SETUP:
+tr_setup:
+		/* check if we can put more data into the FIFO */
+		if (usb_fifo_put_bytes_max(
+		    sc->sc_fifo.fp[USB_FIFO_RX]) != 0) {
+			usbd_xfer_set_frame_len(xfer, 0,
+			    sc->tp_datalen);
+			usbd_transfer_submit(xfer);
+		}
+		break;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		break;
+	}
+}
+
+static void
+wsp_add_to_queue(struct wsp_softc *sc, int dx, int dy, int dz,
+    uint32_t buttons_in)
+{
+	uint32_t buttons_out;
+	uint8_t buf[8];
+
+	dx = imin(dx, 254);
+	dx = imax(dx, -256);
+	dy = imin(dy, 254);
+	dy = imax(dy, -256);
+	dz = imin(dz, 126);
+	dz = imax(dz, -128);
+
+	buttons_out = MOUSE_MSC_BUTTONS;
+	if (buttons_in & MOUSE_BUTTON1DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON1UP;
+	else if (buttons_in & MOUSE_BUTTON2DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON2UP;
+	else if (buttons_in & MOUSE_BUTTON3DOWN)
+		buttons_out &= ~MOUSE_MSC_BUTTON3UP;
+
+	/* Encode the mouse data in standard format; refer to mouse(4) */
+	buf[0] = sc->sc_mode.syncmask[1];
+	buf[0] |= buttons_out;
+	buf[1] = dx >> 1;
+	buf[2] = dy >> 1;
+	buf[3] = dx - (dx >> 1);
+	buf[4] = dy - (dy >> 1);
+	/* Encode extra bytes for level 1 */
+	if (sc->sc_mode.level == 1) {
+		buf[5] = dz >> 1;	/* dz / 2 */
+		buf[6] = dz - (dz >> 1);/* dz - (dz / 2) */
+		buf[7] = (((~buttons_in) >> 3) & MOUSE_SYS_EXTBUTTONS);
+	}
+	usb_fifo_put_data_linear(sc->sc_fifo.fp[USB_FIFO_RX], buf,
+	    sc->sc_mode.packetsize, 1);
+}
+
+static void
+wsp_reset_buf(struct wsp_softc *sc)
+{
+	/* reset read queue */
+	usb_fifo_reset(sc->sc_fifo.fp[USB_FIFO_RX]);
+}
+
+static void
+wsp_start_read(struct usb_fifo *fifo)
+{
+	struct wsp_softc *sc = usb_fifo_softc(fifo);
+	int rate;
+
+	/* Check if we should override the default polling interval */
+	rate = sc->sc_pollrate;
+	/* Range check rate */
+	if (rate > 1000)
+		rate = 1000;
+	/* Check for set rate */
+	if ((rate > 0) && (sc->sc_xfer[WSP_INTR_DT] != NULL)) {
+		/* Stop current transfer, if any */
+		usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]);
+		/* Set new interval */
+		usbd_xfer_set_interval(sc->sc_xfer[WSP_INTR_DT], 1000 / rate);
+		/* Only set pollrate once */
+		sc->sc_pollrate = 0;
+	}
+	usbd_transfer_start(sc->sc_xfer[WSP_INTR_DT]);
+}
+
+static void
+wsp_stop_read(struct usb_fifo *fifo)
+{
+	struct wsp_softc *sc = usb_fifo_softc(fifo);
+
+	usbd_transfer_stop(sc->sc_xfer[WSP_INTR_DT]);
+}
+
+
+static int
+wsp_open(struct usb_fifo *fifo, int fflags)
+{
+	DPRINTFN(WSP_LLEVEL_INFO, "\n");
+
+	if (fflags & FREAD) {
+		struct wsp_softc *sc = usb_fifo_softc(fifo);
+		int rc;
+
+		if (sc->sc_state & WSP_ENABLED)
+			return (EBUSY);
+
+		if (usb_fifo_alloc_buffer(fifo,
+		    WSP_FIFO_BUF_SIZE, WSP_FIFO_QUEUE_MAXLEN)) {
+			return (ENOMEM);
+		}
+		rc = wsp_enable(sc);
+		if (rc != 0) {
+			usb_fifo_free_buffer(fifo);
+			return (rc);
+		}
+	}
+	return (0);
+}
+
+static void
+wsp_close(struct usb_fifo *fifo, int fflags)
+{
+	if (fflags & FREAD) {
+		struct wsp_softc *sc = usb_fifo_softc(fifo);
+
+		wsp_disable(sc);
+		usb_fifo_free_buffer(fifo);
+	}
+}
+
+int
+wsp_ioctl(struct usb_fifo *fifo, u_long cmd, void *addr, int fflags)
+{
+	struct wsp_softc *sc = usb_fifo_softc(fifo);
+	mousemode_t mode;
+	int error = 0;
+
+	mtx_lock(&sc->sc_mutex);
+
+	switch (cmd) {
+	case MOUSE_GETHWINFO:
+		*(mousehw_t *)addr = sc->sc_hw;
+		break;
+	case MOUSE_GETMODE:
+		*(mousemode_t *)addr = sc->sc_mode;
+		break;
+	case MOUSE_SETMODE:
+		mode = *(mousemode_t *)addr;
+
+		if (mode.level == -1)
+			/* Don't change the current setting */
+			;
+		else if ((mode.level < 0) || (mode.level > 1)) {
+			error = EINVAL;
+			goto done;
+		}
+		sc->sc_mode.level = mode.level;
+		sc->sc_pollrate = mode.rate;
+		sc->sc_hw.buttons = 3;
+
+		if (sc->sc_mode.level == 0) {
+			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		wsp_reset_buf(sc);
+		break;
+	case MOUSE_GETLEVEL:
+		*(int *)addr = sc->sc_mode.level;
+		break;
+	case MOUSE_SETLEVEL:
+		if (*(int *)addr < 0 || *(int *)addr > 1) {
+			error = EINVAL;
+			goto done;
+		}
+		sc->sc_mode.level = *(int *)addr;
+		sc->sc_hw.buttons = 3;
+
+		if (sc->sc_mode.level == 0) {
+			sc->sc_mode.protocol = MOUSE_PROTO_MSC;
+			sc->sc_mode.packetsize = MOUSE_MSC_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_MSC_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_MSC_SYNC;
+		} else if (sc->sc_mode.level == 1) {
+			sc->sc_mode.protocol = MOUSE_PROTO_SYSMOUSE;
+			sc->sc_mode.packetsize = MOUSE_SYS_PACKETSIZE;
+			sc->sc_mode.syncmask[0] = MOUSE_SYS_SYNCMASK;
+			sc->sc_mode.syncmask[1] = MOUSE_SYS_SYNC;
+		}
+		wsp_reset_buf(sc);
+		break;
+	case MOUSE_GETSTATUS:{
+			mousestatus_t *status = (mousestatus_t *)addr;
+
+			*status = sc->sc_status;
+			sc->sc_status.obutton = sc->sc_status.button;
+			sc->sc_status.button = 0;
+			sc->sc_status.dx = 0;
+			sc->sc_status.dy = 0;
+			sc->sc_status.dz = 0;
+
+			if (status->dx || status->dy || status->dz)
+				status->flags |= MOUSE_POSCHANGED;
+			if (status->button != status->obutton)
+				status->flags |= MOUSE_BUTTONSCHANGED;
+			break;
+		}
+	default:
+		error = ENOTTY;
+	}
+
+done:
+	mtx_unlock(&sc->sc_mutex);
+	return (error);
+}
+
+static device_method_t wsp_methods[] = {
+	/* Device interface */
+	DEVMETHOD(device_probe, wsp_probe),
+	DEVMETHOD(device_attach, wsp_attach),
+	DEVMETHOD(device_detach, wsp_detach),
+	DEVMETHOD_END
+};
+
+static driver_t wsp_driver = {
+	.name = WSP_DRIVER_NAME,
+	.methods = wsp_methods,
+	.size = sizeof(struct wsp_softc)
+};
+
+static devclass_t wsp_devclass;
+
+DRIVER_MODULE(wsp, uhub, wsp_driver, wsp_devclass, NULL, 0);
+MODULE_DEPEND(wsp, usb, 1, 1, 1);
+MODULE_VERSION(wsp, 1);
+USB_PNP_HOST_INFO(wsp_devs);
diff --git a/freebsd/sys/sys/mouse.h b/freebsd/sys/sys/mouse.h
new file mode 100644
index 0000000..9fd1d6d
--- /dev/null
+++ b/freebsd/sys/sys/mouse.h
@@ -0,0 +1,395 @@
+/*-
+ * Copyright (c) 1992, 1993 Erik Forsberg.
+ * Copyright (c) 1996, 1997 Kazutaka YOKOTA
+ * All rights reserved.
+ *
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ``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 I 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.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _SYS_MOUSE_H_
+#define _SYS_MOUSE_H_
+
+#include <sys/types.h>
+#include <sys/ioccom.h>
+
+/* ioctls */
+#define MOUSE_GETSTATUS		_IOR('M', 0, mousestatus_t)
+#define MOUSE_GETHWINFO		_IOR('M', 1, mousehw_t)
+#define MOUSE_GETMODE		_IOR('M', 2, mousemode_t)
+#define MOUSE_SETMODE		_IOW('M', 3, mousemode_t)
+#define MOUSE_GETLEVEL		_IOR('M', 4, int)
+#define MOUSE_SETLEVEL		_IOW('M', 5, int)
+#define MOUSE_GETVARS		_IOR('M', 6, mousevar_t)
+#define MOUSE_SETVARS		_IOW('M', 7, mousevar_t)
+#define MOUSE_READSTATE		_IOWR('M', 8, mousedata_t)
+#define MOUSE_READDATA		_IOWR('M', 9, mousedata_t)
+
+#ifdef notyet
+#define MOUSE_SETRESOLUTION	_IOW('M', 10, int)
+#define MOUSE_SETSCALING	_IOW('M', 11, int)
+#define MOUSE_SETRATE		_IOW('M', 12, int)
+#define MOUSE_GETHWID		_IOR('M', 13, int)
+#endif
+
+#define MOUSE_SYN_GETHWINFO	_IOR('M', 100, synapticshw_t)
+
+/* mouse status block */
+typedef struct mousestatus {
+    int     flags;		/* state change flags */
+    int     button;		/* button status */
+    int     obutton;		/* previous button status */
+    int     dx;			/* x movement */
+    int     dy;			/* y movement */
+    int     dz;			/* z movement */
+} mousestatus_t;
+
+/* button */
+#define MOUSE_BUTTON1DOWN	0x0001	/* left */
+#define MOUSE_BUTTON2DOWN	0x0002	/* middle */
+#define MOUSE_BUTTON3DOWN	0x0004	/* right */
+#define MOUSE_BUTTON4DOWN	0x0008
+#define MOUSE_BUTTON5DOWN	0x0010
+#define MOUSE_BUTTON6DOWN	0x0020
+#define MOUSE_BUTTON7DOWN	0x0040
+#define MOUSE_BUTTON8DOWN	0x0080
+#define MOUSE_MAXBUTTON		31
+#define MOUSE_STDBUTTONS	0x0007		/* buttons 1-3 */
+#define MOUSE_EXTBUTTONS	0x7ffffff8	/* the others (28 of them!) */
+#define MOUSE_BUTTONS		(MOUSE_STDBUTTONS | MOUSE_EXTBUTTONS)
+
+/* flags */
+#define MOUSE_STDBUTTONSCHANGED	MOUSE_STDBUTTONS
+#define MOUSE_EXTBUTTONSCHANGED	MOUSE_EXTBUTTONS
+#define MOUSE_BUTTONSCHANGED	MOUSE_BUTTONS
+#define MOUSE_POSCHANGED	0x80000000
+
+typedef struct mousehw {
+	int buttons;		/* -1 if unknown */
+	int iftype;		/* MOUSE_IF_XXX */
+	int type;		/* mouse/track ball/pad... */
+	int model;		/* I/F dependent model ID: MOUSE_MODEL_XXX */
+	int hwid;		/* I/F dependent hardware ID
+				 * for the PS/2 mouse, it will be PSM_XXX_ID
+				 */
+} mousehw_t;
+
+typedef struct synapticshw {
+	int infoMajor;
+	int infoMinor;
+	int infoRot180;
+	int infoPortrait;
+	int infoSensor;
+	int infoHardware;
+	int infoNewAbs;
+	int capPen;
+	int infoSimplC;
+	int infoGeometry;
+	int capExtended;
+	int capSleep;
+	int capFourButtons;
+	int capMultiFinger;
+	int capPalmDetect;
+	int capPassthrough;
+	int capMiddle;
+	int capLowPower;
+	int capMultiFingerReport;
+	int capBallistics;
+	int nExtendedButtons;
+	int nExtendedQueries;
+	int capClickPad;
+	int capDeluxeLEDs;
+	int noAbsoluteFilter;
+	int capReportsV;
+	int capUniformClickPad;
+	int capReportsMin;
+	int capInterTouch;
+	int capReportsMax;
+	int capClearPad;
+	int capAdvancedGestures;
+	int multiFingerMode;
+	int capCoveredPad;
+	int verticalScroll;
+	int horizontalScroll;
+	int verticalWheel;
+	int capEWmode;
+	int minimumXCoord;
+	int minimumYCoord;
+	int maximumXCoord;
+	int maximumYCoord;
+	int infoXupmm;
+	int infoYupmm;
+} synapticshw_t;
+
+/* iftype */
+#define MOUSE_IF_UNKNOWN	(-1)
+#define MOUSE_IF_SERIAL		0
+#define MOUSE_IF_BUS		1
+#define MOUSE_IF_INPORT		2
+#define MOUSE_IF_PS2		3
+#define MOUSE_IF_SYSMOUSE	4
+#define MOUSE_IF_USB		5
+
+/* type */
+#define MOUSE_UNKNOWN		(-1)	/* should be treated as a mouse */
+#define MOUSE_MOUSE		0
+#define MOUSE_TRACKBALL		1
+#define MOUSE_STICK		2
+#define MOUSE_PAD		3
+
+/* model */
+#define MOUSE_MODEL_UNKNOWN		(-1)
+#define MOUSE_MODEL_GENERIC		0
+#define MOUSE_MODEL_GLIDEPOINT		1
+#define MOUSE_MODEL_NETSCROLL		2
+#define MOUSE_MODEL_NET			3
+#define MOUSE_MODEL_INTELLI		4
+#define MOUSE_MODEL_THINK		5
+#define MOUSE_MODEL_EASYSCROLL		6
+#define MOUSE_MODEL_MOUSEMANPLUS	7
+#define MOUSE_MODEL_KIDSPAD		8
+#define MOUSE_MODEL_VERSAPAD		9
+#define MOUSE_MODEL_EXPLORER		10
+#define MOUSE_MODEL_4D			11
+#define MOUSE_MODEL_4DPLUS		12
+#define MOUSE_MODEL_SYNAPTICS		13
+#define	MOUSE_MODEL_TRACKPOINT		14
+#define	MOUSE_MODEL_ELANTECH		15
+
+typedef struct mousemode {
+	int protocol;		/* MOUSE_PROTO_XXX */
+	int rate;		/* report rate (per sec), -1 if unknown */
+	int resolution;		/* MOUSE_RES_XXX, -1 if unknown */
+	int accelfactor;	/* accelation factor (must be 1 or greater) */
+	int level;		/* driver operation level */
+	int packetsize;		/* the length of the data packet */
+	unsigned char syncmask[2]; /* sync. data bits in the header byte */
+} mousemode_t;
+
+/* protocol */
+/*
+ * Serial protocols:
+ *   Microsoft, MouseSystems, Logitech, MM series, MouseMan, Hitachi Tablet,
+ *   GlidePoint, IntelliMouse, Thinking Mouse, MouseRemote, Kidspad,
+ *   VersaPad
+ * Bus mouse protocols:
+ *   bus, InPort
+ * PS/2 mouse protocol:
+ *   PS/2
+ */
+#define MOUSE_PROTO_UNKNOWN	(-1)
+#define MOUSE_PROTO_MS		0	/* Microsoft Serial, 3 bytes */
+#define MOUSE_PROTO_MSC		1	/* Mouse Systems, 5 bytes */
+#define MOUSE_PROTO_LOGI	2	/* Logitech, 3 bytes */
+#define MOUSE_PROTO_MM		3	/* MM series, 3 bytes */
+#define MOUSE_PROTO_LOGIMOUSEMAN 4	/* Logitech MouseMan 3/4 bytes */
+#define MOUSE_PROTO_BUS		5	/* MS/Logitech bus mouse */
+#define MOUSE_PROTO_INPORT	6	/* MS/ATI InPort mouse */
+#define MOUSE_PROTO_PS2		7	/* PS/2 mouse, 3 bytes */
+#define MOUSE_PROTO_HITTAB	8	/* Hitachi Tablet 3 bytes */
+#define MOUSE_PROTO_GLIDEPOINT	9	/* ALPS GlidePoint, 3/4 bytes */
+#define MOUSE_PROTO_INTELLI	10	/* MS IntelliMouse, 4 bytes */
+#define MOUSE_PROTO_THINK	11	/* Kensington Thinking Mouse, 3/4 bytes */
+#define MOUSE_PROTO_SYSMOUSE	12	/* /dev/sysmouse */
+#define MOUSE_PROTO_X10MOUSEREM	13	/* X10 MouseRemote, 3 bytes */
+#define MOUSE_PROTO_KIDSPAD	14	/* Genius Kidspad */
+#define MOUSE_PROTO_VERSAPAD	15	/* Interlink VersaPad, 6 bytes */
+#define MOUSE_PROTO_JOGDIAL	16	/* Vaio's JogDial */
+#define MOUSE_PROTO_GTCO_DIGIPAD	17
+
+#define MOUSE_RES_UNKNOWN	(-1)
+#define MOUSE_RES_DEFAULT	0
+#define MOUSE_RES_LOW		(-2)
+#define MOUSE_RES_MEDIUMLOW	(-3)
+#define MOUSE_RES_MEDIUMHIGH	(-4)
+#define MOUSE_RES_HIGH		(-5)
+
+typedef struct mousedata {
+	int len;		/* # of data in the buffer */
+	int buf[16];		/* data buffer */
+} mousedata_t;
+
+#if (defined(MOUSE_GETVARS))
+
+typedef struct mousevar {
+	int var[16];
+} mousevar_t;
+
+/* magic numbers in var[0] */
+#define MOUSE_VARS_PS2_SIG	0x00325350	/* 'PS2' */
+#define MOUSE_VARS_BUS_SIG	0x00535542	/* 'BUS' */
+#define MOUSE_VARS_INPORT_SIG	0x00504e49	/* 'INP' */
+
+#endif /* MOUSE_GETVARS */
+
+/* Synaptics Touchpad */
+#define MOUSE_SYNAPTICS_PACKETSIZE	6	/* '3' works better */
+
+/* Elantech Touchpad */
+#define MOUSE_ELANTECH_PACKETSIZE	6
+
+/* Microsoft Serial mouse data packet */
+#define MOUSE_MSS_PACKETSIZE	3
+#define MOUSE_MSS_SYNCMASK	0x40
+#define MOUSE_MSS_SYNC		0x40
+#define MOUSE_MSS_BUTTONS	0x30
+#define MOUSE_MSS_BUTTON1DOWN	0x20	/* left */
+#define MOUSE_MSS_BUTTON2DOWN	0x00	/* no middle button */
+#define MOUSE_MSS_BUTTON3DOWN	0x10	/* right */
+
+/* Logitech MouseMan data packet (M+ protocol) */
+#define MOUSE_LMAN_BUTTON2DOWN	0x20	/* middle button, the 4th byte */
+
+/* ALPS GlidePoint extension (variant of M+ protocol) */
+#define MOUSE_ALPS_BUTTON2DOWN	0x20	/* middle button, the 4th byte */
+#define MOUSE_ALPS_TAP		0x10	/* `tapping' action, the 4th byte */
+
+/* Kinsington Thinking Mouse extension (variant of M+ protocol) */
+#define MOUSE_THINK_BUTTON2DOWN 0x20	/* lower-left button, the 4th byte */
+#define MOUSE_THINK_BUTTON4DOWN 0x10	/* lower-right button, the 4th byte */
+
+/* MS IntelliMouse (variant of MS Serial) */
+#define MOUSE_INTELLI_PACKETSIZE 4
+#define MOUSE_INTELLI_BUTTON2DOWN 0x10	/* middle button in the 4th byte */
+
+/* Mouse Systems Corp. mouse data packet */
+#define MOUSE_MSC_PACKETSIZE	5
+#define MOUSE_MSC_SYNCMASK	0xf8
+#define MOUSE_MSC_SYNC		0x80
+#define MOUSE_MSC_BUTTONS	0x07
+#define MOUSE_MSC_BUTTON1UP	0x04	/* left */
+#define MOUSE_MSC_BUTTON2UP	0x02	/* middle */
+#define MOUSE_MSC_BUTTON3UP	0x01	/* right */
+#define MOUSE_MSC_MAXBUTTON	3
+
+/* MM series mouse data packet */
+#define MOUSE_MM_PACKETSIZE	3
+#define MOUSE_MM_SYNCMASK	0xe0
+#define MOUSE_MM_SYNC		0x80
+#define MOUSE_MM_BUTTONS	0x07
+#define MOUSE_MM_BUTTON1DOWN	0x04	/* left */
+#define MOUSE_MM_BUTTON2DOWN	0x02	/* middle */
+#define MOUSE_MM_BUTTON3DOWN	0x01	/* right */
+#define MOUSE_MM_XPOSITIVE	0x10
+#define MOUSE_MM_YPOSITIVE	0x08
+
+/* PS/2 mouse data packet */
+#define MOUSE_PS2_PACKETSIZE	3
+#define MOUSE_PS2_SYNCMASK	0xc8
+#define MOUSE_PS2_SYNC		0x08
+#define MOUSE_PS2_BUTTONS	0x07	/* 0x03 for 2 button mouse */
+#define MOUSE_PS2_BUTTON1DOWN	0x01	/* left */
+#define MOUSE_PS2_BUTTON2DOWN	0x04	/* middle */
+#define MOUSE_PS2_BUTTON3DOWN	0x02	/* right */
+#define MOUSE_PS2_TAP		MOUSE_PS2_SYNC /* GlidePoint (PS/2) `tapping'
+					        * Yes! this is the same bit
+						* as SYNC!
+					 	*/
+
+#define MOUSE_PS2_XNEG		0x10
+#define MOUSE_PS2_YNEG		0x20
+#define MOUSE_PS2_XOVERFLOW	0x40
+#define MOUSE_PS2_YOVERFLOW	0x80
+
+/* Logitech MouseMan+ (PS/2) data packet (PS/2++ protocol) */
+#define MOUSE_PS2PLUS_SYNCMASK	0x48
+#define MOUSE_PS2PLUS_SYNC	0x48
+#define MOUSE_PS2PLUS_ZNEG	0x08	/* sign bit */
+#define MOUSE_PS2PLUS_BUTTON4DOWN 0x10	/* 4th button on MouseMan+ */
+#define MOUSE_PS2PLUS_BUTTON5DOWN 0x20
+
+/* IBM ScrollPoint (PS/2) also uses PS/2++ protocol */
+#define MOUSE_SPOINT_ZNEG	0x80	/* sign bits */
+#define MOUSE_SPOINT_WNEG	0x08
+
+/* MS IntelliMouse (PS/2) data packet */
+#define MOUSE_PS2INTELLI_PACKETSIZE 4
+/* some compatible mice have additional buttons */
+#define MOUSE_PS2INTELLI_BUTTON4DOWN 0x40
+#define MOUSE_PS2INTELLI_BUTTON5DOWN 0x80
+
+/* MS IntelliMouse Explorer (PS/2) data packet (variation of IntelliMouse) */
+#define MOUSE_EXPLORER_ZNEG	0x08	/* sign bit */
+/* IntelliMouse Explorer has additional button data in the fourth byte */
+#define MOUSE_EXPLORER_BUTTON4DOWN 0x10
+#define MOUSE_EXPLORER_BUTTON5DOWN 0x20
+
+/* Interlink VersaPad (serial I/F) data packet */
+#define MOUSE_VERSA_PACKETSIZE	6
+#define MOUSE_VERSA_IN_USE	0x04
+#define MOUSE_VERSA_SYNCMASK	0xc3
+#define MOUSE_VERSA_SYNC	0xc0
+#define MOUSE_VERSA_BUTTONS	0x30
+#define MOUSE_VERSA_BUTTON1DOWN	0x20	/* left */
+#define MOUSE_VERSA_BUTTON2DOWN	0x00	/* middle */
+#define MOUSE_VERSA_BUTTON3DOWN	0x10	/* right */
+#define MOUSE_VERSA_TAP		0x08
+
+/* Interlink VersaPad (PS/2 I/F) data packet */
+#define MOUSE_PS2VERSA_PACKETSIZE	6
+#define MOUSE_PS2VERSA_IN_USE		0x10
+#define MOUSE_PS2VERSA_SYNCMASK		0xe8
+#define MOUSE_PS2VERSA_SYNC		0xc8
+#define MOUSE_PS2VERSA_BUTTONS		0x05
+#define MOUSE_PS2VERSA_BUTTON1DOWN	0x04	/* left */
+#define MOUSE_PS2VERSA_BUTTON2DOWN	0x00	/* middle */
+#define MOUSE_PS2VERSA_BUTTON3DOWN	0x01	/* right */
+#define MOUSE_PS2VERSA_TAP		0x02
+
+/* A4 Tech 4D Mouse (PS/2) data packet */
+#define MOUSE_4D_PACKETSIZE		3
+#define MOUSE_4D_WHEELBITS		0xf0
+
+/* A4 Tech 4D+ Mouse (PS/2) data packet */
+#define MOUSE_4DPLUS_PACKETSIZE		3
+#define MOUSE_4DPLUS_ZNEG		0x04	/* sign bit */
+#define MOUSE_4DPLUS_BUTTON4DOWN	0x08
+
+/* sysmouse extended data packet */
+/*
+ * /dev/sysmouse sends data in two formats, depending on the protocol
+ * level.  At the level 0, format is exactly the same as MousSystems'
+ * five byte packet.  At the level 1, the first five bytes are the same
+ * as at the level 0.  There are additional three bytes which shows
+ * `dz' and the states of additional buttons.  `dz' is expressed as the
+ * sum of the byte 5 and 6 which contain signed seven bit values.
+ * The states of the button 4 though 10 are in the bit 0 though 6 in
+ * the byte 7 respectively: 1 indicates the button is up.
+ */
+#define MOUSE_SYS_PACKETSIZE	8
+#define MOUSE_SYS_SYNCMASK	0xf8
+#define MOUSE_SYS_SYNC		0x80
+#define MOUSE_SYS_BUTTON1UP	0x04	/* left, 1st byte */
+#define MOUSE_SYS_BUTTON2UP	0x02	/* middle, 1st byte */
+#define MOUSE_SYS_BUTTON3UP	0x01	/* right, 1st byte */
+#define MOUSE_SYS_BUTTON4UP	0x0001	/* 7th byte */
+#define MOUSE_SYS_BUTTON5UP	0x0002
+#define MOUSE_SYS_BUTTON6UP	0x0004
+#define MOUSE_SYS_BUTTON7UP	0x0008
+#define MOUSE_SYS_BUTTON8UP	0x0010
+#define MOUSE_SYS_BUTTON9UP	0x0020
+#define MOUSE_SYS_BUTTON10UP	0x0040
+#define MOUSE_SYS_MAXBUTTON	10
+#define MOUSE_SYS_STDBUTTONS	0x07
+#define MOUSE_SYS_EXTBUTTONS	0x7f	/* the others */
+
+/* Mouse remote socket */
+#define _PATH_MOUSEREMOTE	"/var/run/MouseRemote"
+
+#endif /* _SYS_MOUSE_H_ */




More information about the vc mailing list