Module Name:    src
Committed By:   plunky
Date:           Sat May 22 18:56:01 UTC 2010

Modified Files:
        src/distrib/sets/lists/man: mi
        src/share/man/man4: Makefile
        src/sys/dev/bluetooth: files.bluetooth
Added Files:
        src/share/man/man4: btmagic.4
        src/sys/dev/bluetooth: btmagic.c

Log Message:
add Magic Mouse driver and manpage btmagic(4)


To generate a diff of this commit:
cvs rdiff -u -r1.1209 -r1.1210 src/distrib/sets/lists/man/mi
cvs rdiff -u -r1.516 -r1.517 src/share/man/man4/Makefile
cvs rdiff -u -r0 -r1.1 src/share/man/man4/btmagic.4
cvs rdiff -u -r0 -r1.1 src/sys/dev/bluetooth/btmagic.c
cvs rdiff -u -r1.13 -r1.14 src/sys/dev/bluetooth/files.bluetooth

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/distrib/sets/lists/man/mi
diff -u src/distrib/sets/lists/man/mi:1.1209 src/distrib/sets/lists/man/mi:1.1210
--- src/distrib/sets/lists/man/mi:1.1209	Sun May 16 15:33:29 2010
+++ src/distrib/sets/lists/man/mi	Sat May 22 18:56:00 2010
@@ -1,4 +1,4 @@
-# $NetBSD: mi,v 1.1209 2010/05/16 15:33:29 jruoho Exp $
+# $NetBSD: mi,v 1.1210 2010/05/22 18:56:00 plunky Exp $
 #
 # Note: don't delete entries from here - mark them as "obsolete" instead.
 #
@@ -819,6 +819,7 @@
 ./usr/share/man/cat4/bthset.0			man-obsolete		obsolete
 ./usr/share/man/cat4/bthub.0			man-sys-catman		.cat
 ./usr/share/man/cat4/btkbd.0			man-sys-catman		.cat
+./usr/share/man/cat4/btmagic.0			man-sys-catman		.cat
 ./usr/share/man/cat4/btms.0			man-sys-catman		.cat
 ./usr/share/man/cat4/btsco.0			man-sys-catman		.cat
 ./usr/share/man/cat4/btuart.0			man-sys-catman		.cat
@@ -3479,6 +3480,7 @@
 ./usr/share/man/html4/bthidev.html		man-sys-htmlman		html
 ./usr/share/man/html4/bthub.html		man-sys-htmlman		html
 ./usr/share/man/html4/btkbd.html		man-sys-htmlman		html
+./usr/share/man/html4/btmagic.html		man-sys-htmlman		html
 ./usr/share/man/html4/btms.html			man-sys-htmlman		html
 ./usr/share/man/html4/btsco.html		man-sys-htmlman		html
 ./usr/share/man/html4/btuart.html		man-sys-htmlman		html
@@ -5915,6 +5917,7 @@
 ./usr/share/man/man4/bthset.4			man-obsolete		obsolete
 ./usr/share/man/man4/bthub.4			man-sys-man		.man
 ./usr/share/man/man4/btkbd.4			man-sys-man		.man
+./usr/share/man/man4/btmagic.4			man-sys-man		.man
 ./usr/share/man/man4/btms.4			man-sys-man		.man
 ./usr/share/man/man4/btsco.4			man-sys-man		.man
 ./usr/share/man/man4/btuart.4			man-sys-man		.man

Index: src/share/man/man4/Makefile
diff -u src/share/man/man4/Makefile:1.516 src/share/man/man4/Makefile:1.517
--- src/share/man/man4/Makefile:1.516	Sat Apr 10 17:55:25 2010
+++ src/share/man/man4/Makefile	Sat May 22 18:56:01 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: Makefile,v 1.516 2010/04/10 17:55:25 jruoho Exp $
+#	$NetBSD: Makefile,v 1.517 2010/05/22 18:56:01 plunky Exp $
 #	@(#)Makefile	8.1 (Berkeley) 6/18/93
 
 MAN=	aac.4 ac97.4 acardide.4 aceride.4 acphy.4 \
@@ -13,7 +13,8 @@
 	auixp.4 autri.4 auvia.4 awi.4 azalia.4 \
 	battery_pmu.4 bba.4 bce.4 bcsp.4 be.4 bge.4 bnx.4 bha.4 \
 	bio.4 bktr.4 bluetooth.4 bmtphy.4 bpf.4 \
-	brgphy.4 bridge.4 bthidev.4 bthub.4 btkbd.4 btms.4 btsco.4 btuart.4 \
+	brgphy.4 bridge.4 bthidev.4 bthub.4 btkbd.4 \
+	btmagic.4 btms.4 btsco.4 btuart.4 \
 	bwi.4 \
 	cac.4 cardbus.4 carp.4 cas.4 ccd.4 cd.4 \
 	cec.4 cgd.4 cfb.4 ch.4 chipsfb.4 ciphy.4 ciss.4 clcs.4 clct.4 clnp.4 \

Index: src/sys/dev/bluetooth/files.bluetooth
diff -u src/sys/dev/bluetooth/files.bluetooth:1.13 src/sys/dev/bluetooth/files.bluetooth:1.14
--- src/sys/dev/bluetooth/files.bluetooth:1.13	Tue Jun 10 12:49:16 2008
+++ src/sys/dev/bluetooth/files.bluetooth	Sat May 22 18:56:01 2010
@@ -1,4 +1,4 @@
-#	$NetBSD: files.bluetooth,v 1.13 2008/06/10 12:49:16 drochner Exp $
+#	$NetBSD: files.bluetooth,v 1.14 2010/05/22 18:56:01 plunky Exp $
 #
 # Config file for machine independent Bluetooth devices
 
@@ -27,6 +27,11 @@
 attach btms at bthidbus
 file dev/bluetooth/btms.c		btms
 
+# Apple MagicMouse
+device btmagic: bluetooth, hid, wsmousedev
+attach btmagic at bthub
+file dev/bluetooth/btmagic.c		btmagic
+
 # SCO Audio
 device btsco: bluetooth, audiobus, auconv, mulaw, aurateconv
 attach btsco at bthub

Added files:

Index: src/share/man/man4/btmagic.4
diff -u /dev/null src/share/man/man4/btmagic.4:1.1
--- /dev/null	Sat May 22 18:56:01 2010
+++ src/share/man/man4/btmagic.4	Sat May 22 18:56:01 2010
@@ -0,0 +1,113 @@
+.\" $NetBSD: btmagic.4,v 1.1 2010/05/22 18:56:01 plunky Exp $
+.\"
+.\" Copyright (c) 2010 The NetBSD Foundation, Inc.
+.\" All rights reserved.
+.\"
+.\" This code is derived from software contributed to The NetBSD Foundation
+.\" by Iain Hibbert.
+.\"
+.\" 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.
+.\"
+.Dd May 22, 2010
+.Dt BTMAGIC 4
+.Os
+.Sh NAME
+.Nm btmagic
+.Nd Apple Magic Mouse
+.Sh SYNOPSIS
+.Cd "btmagic*	at bthub?"
+.Cd "wsmouse*	at btmagic?"
+.Sh DESCRIPTION
+The
+.Nm
+driver provides support for the
+.Tn Bluetooth
+.Dq Magic Mouse
+from
+.Tn Apple, Inc .
+.Pp
+The Magic Mouse uses the standard
+.Tn USB
+Human Interface Device protocol to communicate, but does not provide a
+proper HID Descriptor, and requires specific initializations to enable
+the proprietary touch reports.
+.Pp
+The Magic Mouse provides basic mouse functionality with two buttons,
+and the
+.Nm
+driver additionally interprets the touch reports to emulate a middle
+mouse button when more than one firm touch is detected during a click
+event, plus horizontal and vertical scrolling for touch movements
+greater than a certain distance.
+The mouse has a base resolution of 1300dpi, which the driver scales
+by default to a less sensitive 650dpi, but this is adjustable with
+.Xr sysctl 8
+along with the pressure needed to discern a firm touch, the minimum
+distance necessary to trigger scrolling and the additional downscale
+factor applied to scroll movements.
+.Pp
+The Magic Mouse should be configured with the
+.Xr btdevctl 8
+program and
+.Nm
+interfaces to the system as usual through the
+.Xr wscons 4
+driver.
+The following properties are used during autoconfiguration:
+.Bl -tag -width ".It remote-bdaddr"
+.It vendor-id
+Must be 0x05ac.
+.It product-id
+Must be 0x030d.
+.It local-bdaddr
+Local device address.
+.It remote-bdaddr
+Remote device address.
+.It link-mode
+This optional string represents the link mode of the baseband link, and
+may be one of
+.Sq auth ,
+.Sq encrypt ,
+or
+.Sq secure .
+.El
+.Pp
+When the
+.Nm
+driver has configured, it will attempt to open a connection to the mouse
+and, if this fails or the connection is lost, will wait for the
+mouse to initiate connections.
+.Sh SEE ALSO
+.Xr bluetooth 4 ,
+.Xr bthub 4 ,
+.Xr wsmouse 4 ,
+.Xr btdevctl 8 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm
+driver was written by
+.An Iain Hibbert
+with reference to the
+.Tn Linux
+driver written by
+.An Michael Poole .

Index: src/sys/dev/bluetooth/btmagic.c
diff -u /dev/null src/sys/dev/bluetooth/btmagic.c:1.1
--- /dev/null	Sat May 22 18:56:01 2010
+++ src/sys/dev/bluetooth/btmagic.c	Sat May 22 18:56:01 2010
@@ -0,0 +1,1355 @@
+/*	$NetBSD: btmagic.c,v 1.1 2010/05/22 18:56:01 plunky Exp $	*/
+
+/*-
+ * Copyright (c) 2010 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Iain Hibbert.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lenn...@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.
+ */
+/*-
+ * Copyright (c) 2006 Itronix Inc.
+ * All rights reserved.
+ *
+ * Written by Iain Hibbert for Itronix Inc.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ * 3. The name of Itronix Inc. may not be used to endorse
+ *    or promote products derived from this software without specific
+ *    prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY ITRONIX INC. ``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 ITRONIX INC. 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.
+ */
+
+
+/*****************************************************************************
+ *
+ *		Apple Bluetooth Magic Mouse driver
+ *
+ * The Apple Magic Mouse is a HID device but it doesn't provide a proper HID
+ * descriptor, and requires extra initializations to enable the proprietary
+ * touch reports. We match against the vendor-id and product-id and provide
+ * our own Bluetooth connection handling as the bthidev driver does not cater
+ * for such complications.
+ *
+ * This driver interprets the touch reports only as far as emulating a
+ * middle mouse button and providing horizontal and vertical scroll action.
+ * Full gesture support would be more complicated and is left as an exercise
+ * for the reader.
+ *
+ * Credit for decoding the proprietary touch reports goes to Michael Poole
+ * who wrote the Linux hid-magicmouse input driver.
+ *
+ *****************************************************************************/
+
+#include <sys/cdefs.h>
+__KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.1 2010/05/22 18:56:01 plunky Exp $");
+
+#include <sys/param.h>
+#include <sys/conf.h>
+#include <sys/device.h>
+#include <sys/fcntl.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/mbuf.h>
+#include <sys/proc.h>
+#include <sys/socketvar.h>
+#include <sys/systm.h>
+#include <sys/sysctl.h>
+
+#include <prop/proplib.h>
+
+#include <netbt/bluetooth.h>
+#include <netbt/l2cap.h>
+
+#include <dev/bluetooth/btdev.h>
+#include <dev/bluetooth/bthid.h>
+#include <dev/bluetooth/bthidev.h>
+
+#include <dev/usb/hid.h>
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdevs.h>
+
+#include <dev/wscons/wsconsio.h>
+#include <dev/wscons/wsmousevar.h>
+
+#undef	DPRINTF
+#ifdef	BTMAGIC_DEBUG
+#define	DPRINTF(sc, ...) do {				\
+	printf("%s: ", device_xname((sc)->sc_dev));	\
+	printf(__VA_ARGS__);				\
+	printf("\n");					\
+} while (/*CONSTCOND*/0)
+#else
+#define	DPRINTF(...)	(void)0
+#endif
+
+struct btmagic_softc {
+	bdaddr_t		sc_laddr;	/* local address */
+	bdaddr_t		sc_raddr;	/* remote address */
+	struct sockopt		sc_mode;	/* link mode */
+
+	device_t		sc_dev;
+	uint16_t		sc_state;
+	uint16_t		sc_flags;
+
+	callout_t		sc_timeout;
+
+	/* control */
+	struct l2cap_channel	*sc_ctl;
+	struct l2cap_channel	*sc_ctl_l;
+
+	/* interrupt */
+	struct l2cap_channel	*sc_int;
+	struct l2cap_channel	*sc_int_l;
+
+	/* wsmouse child */
+	device_t		sc_wsmouse;
+	int			sc_enabled;
+
+	/* config */
+	int			sc_resolution;	/* for soft scaling */
+	int			sc_firm;	/* firm touch threshold */
+	int			sc_dist;	/* scroll distance threshold */
+	int			sc_scale;	/* scroll descaling */
+	struct sysctllog	*sc_log;	/* sysctl teardown log */
+
+	/* remainders */
+	int			sc_rx;
+	int			sc_ry;
+	int			sc_rz;
+	int			sc_rw;
+
+	/* previous touches */
+	uint32_t		sc_smask;	/* scrolling */
+	int			sc_az[16];
+	int			sc_aw[16];
+
+	/* previous mouse buttons */
+	uint32_t		sc_mb;
+};
+
+/* sc_flags */
+#define BTMAGIC_CONNECTING	__BIT(0) /* we are connecting */
+#define BTMAGIC_ENABLED		__BIT(1) /* touch reports enabled */
+
+/* sc_state */
+#define BTMAGIC_CLOSED		0
+#define BTMAGIC_WAIT_CTL	1
+#define BTMAGIC_WAIT_INT	2
+#define BTMAGIC_OPEN		3
+
+/* autoconf(9) glue */
+static int  btmagic_match(device_t, cfdata_t, void *);
+static void btmagic_attach(device_t, device_t, void *);
+static int  btmagic_detach(device_t, int);
+static int  btmagic_listen(struct btmagic_softc *);
+static int  btmagic_connect(struct btmagic_softc *);
+static int  btmagic_sysctl_resolution(SYSCTLFN_PROTO);
+static int  btmagic_sysctl_scale(SYSCTLFN_PROTO);
+
+CFATTACH_DECL_NEW(btmagic, sizeof(struct btmagic_softc),
+    btmagic_match, btmagic_attach, btmagic_detach, NULL);
+
+/* wsmouse(4) accessops */
+static int btmagic_wsmouse_enable(void *);
+static int btmagic_wsmouse_ioctl(void *, unsigned long, void *, int, struct lwp *);
+static void btmagic_wsmouse_disable(void *);
+
+static const struct wsmouse_accessops btmagic_wsmouse_accessops = {
+	btmagic_wsmouse_enable,
+	btmagic_wsmouse_ioctl,
+	btmagic_wsmouse_disable,
+};
+
+/* bluetooth(9) protocol methods for L2CAP */
+static void  btmagic_connecting(void *);
+static void  btmagic_ctl_connected(void *);
+static void  btmagic_int_connected(void *);
+static void  btmagic_ctl_disconnected(void *, int);
+static void  btmagic_int_disconnected(void *, int);
+static void *btmagic_ctl_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void *btmagic_int_newconn(void *, struct sockaddr_bt *, struct sockaddr_bt *);
+static void  btmagic_complete(void *, int);
+static void  btmagic_linkmode(void *, int);
+static void  btmagic_input(void *, struct mbuf *);
+static void  btmagic_input_basic(struct btmagic_softc *, uint8_t *, size_t);
+static void  btmagic_input_magic(struct btmagic_softc *, uint8_t *, size_t);
+
+static const struct btproto btmagic_ctl_proto = {
+	btmagic_connecting,
+	btmagic_ctl_connected,
+	btmagic_ctl_disconnected,
+	btmagic_ctl_newconn,
+	btmagic_complete,
+	btmagic_linkmode,
+	btmagic_input,
+};
+
+static const struct btproto btmagic_int_proto = {
+	btmagic_connecting,
+	btmagic_int_connected,
+	btmagic_int_disconnected,
+	btmagic_int_newconn,
+	btmagic_complete,
+	btmagic_linkmode,
+	btmagic_input,
+};
+
+/* btmagic internals */
+static void btmagic_timeout(void *);
+static int  btmagic_ctl_send(struct btmagic_softc *, const uint8_t *, size_t);
+static void btmagic_enable(struct btmagic_softc *);
+static void btmagic_check_battery(struct btmagic_softc *);
+static int  btmagic_scale(int, int *, int);
+
+
+/*****************************************************************************
+ *
+ *	Magic Mouse autoconf(9) routines
+ */
+
+static int
+btmagic_match(device_t self, cfdata_t cfdata, void *aux)
+{
+	uint16_t v, p;
+
+	if (prop_dictionary_get_uint16(aux, BTDEVvendor, &v)
+	    && prop_dictionary_get_uint16(aux, BTDEVproduct, &p)
+	    && v == USB_VENDOR_APPLE
+	    && p == USB_PRODUCT_APPLE_MAGICMOUSE)
+		return 2;	/* trump bthidev(4) */
+
+	return 0;
+}
+
+static void
+btmagic_attach(device_t parent, device_t self, void *aux)
+{
+	struct btmagic_softc *sc = device_private(self);
+	struct wsmousedev_attach_args wsma;
+	const struct sysctlnode *node;
+	prop_object_t obj;
+
+	/*
+	 * Init softc
+	 */
+	sc->sc_dev = self;
+	sc->sc_state = BTMAGIC_CLOSED;
+	callout_init(&sc->sc_timeout, 0);
+	callout_setfunc(&sc->sc_timeout, btmagic_timeout, sc);
+	sockopt_init(&sc->sc_mode, BTPROTO_L2CAP, SO_L2CAP_LM, 0);
+
+	/*
+	 * extract config from proplist
+	 */
+	obj = prop_dictionary_get(aux, BTDEVladdr);
+	bdaddr_copy(&sc->sc_laddr, prop_data_data_nocopy(obj));
+
+	obj = prop_dictionary_get(aux, BTDEVraddr);
+	bdaddr_copy(&sc->sc_raddr, prop_data_data_nocopy(obj));
+
+	obj = prop_dictionary_get(aux, BTDEVmode);
+	if (prop_object_type(obj) == PROP_TYPE_STRING) {
+		if (prop_string_equals_cstring(obj, BTDEVauth))
+			sockopt_setint(&sc->sc_mode, L2CAP_LM_AUTH);
+		else if (prop_string_equals_cstring(obj, BTDEVencrypt))
+			sockopt_setint(&sc->sc_mode, L2CAP_LM_ENCRYPT);
+		else if (prop_string_equals_cstring(obj, BTDEVsecure))
+			sockopt_setint(&sc->sc_mode, L2CAP_LM_SECURE);
+		else  {
+			aprint_error(" unknown %s\n", BTDEVmode);
+			return;
+		}
+
+		aprint_verbose(" %s %s", BTDEVmode,
+		    prop_string_cstring_nocopy(obj));
+	}
+
+	aprint_normal(": 3 buttons, W and Z dirs\n");
+	aprint_naive("\n");
+
+	/*
+	 * set defaults
+	 */
+	sc->sc_resolution = 650;
+	sc->sc_firm = 6;
+	sc->sc_dist = 130;
+	sc->sc_scale = 20;
+
+	sysctl_createv(&sc->sc_log, 0, NULL, NULL,
+		CTLFLAG_PERMANENT,
+		CTLTYPE_NODE, "hw",
+		NULL,
+		NULL, 0,
+		NULL, 0,
+		CTL_HW, CTL_EOL);
+
+	sysctl_createv(&sc->sc_log, 0, NULL, &node,
+		0,
+		CTLTYPE_NODE, device_xname(self),
+		NULL,
+		NULL, 0,
+		NULL, 0,
+		CTL_HW,
+		CTL_CREATE, CTL_EOL);
+
+	if (node != NULL) {
+		sysctl_createv(&sc->sc_log, 0, NULL, NULL,
+			CTLFLAG_READWRITE,
+			CTLTYPE_INT, "soft_resolution",
+			NULL,
+			btmagic_sysctl_resolution, 0,
+			sc, 0,
+			CTL_HW, node->sysctl_num,
+			CTL_CREATE, CTL_EOL);
+
+		sysctl_createv(&sc->sc_log, 0, NULL, NULL,
+			CTLFLAG_READWRITE,
+			CTLTYPE_INT, "firm_touch_threshold",
+			NULL,
+			NULL, 0,
+			&sc->sc_firm, sizeof(sc->sc_firm),
+			CTL_HW, node->sysctl_num,
+			CTL_CREATE, CTL_EOL);
+
+		sysctl_createv(&sc->sc_log, 0, NULL, NULL,
+			CTLFLAG_READWRITE,
+			CTLTYPE_INT, "scroll_distance_threshold",
+			NULL,
+			NULL, 0,
+			&sc->sc_dist, sizeof(sc->sc_dist),
+			CTL_HW, node->sysctl_num,
+			CTL_CREATE, CTL_EOL);
+
+		sysctl_createv(&sc->sc_log, 0, NULL, NULL,
+			CTLFLAG_READWRITE,
+			CTLTYPE_INT, "scroll_downscale_factor",
+			NULL,
+			btmagic_sysctl_scale, 0,
+			sc, 0,
+			CTL_HW, node->sysctl_num,
+			CTL_CREATE, CTL_EOL);
+	}
+
+	/*
+	 * attach the wsmouse
+	 */
+	wsma.accessops = &btmagic_wsmouse_accessops;
+	wsma.accesscookie = self;
+	sc->sc_wsmouse = config_found(self, &wsma, wsmousedevprint);
+	if (sc->sc_wsmouse == NULL) {
+		aprint_error_dev(self, "failed to attach wsmouse\n");
+		return;
+	}
+
+	/*
+	 * start bluetooth connections
+	 */
+	mutex_enter(bt_lock);
+	btmagic_listen(sc);
+	btmagic_connect(sc);
+	mutex_exit(bt_lock);
+}
+
+static int
+btmagic_detach(device_t self, int flags)
+{
+	struct btmagic_softc *sc = device_private(self);
+	int err = 0;
+
+	mutex_enter(bt_lock);
+
+	/* release interrupt listen */
+	if (sc->sc_int_l != NULL) {
+		l2cap_detach(&sc->sc_int_l);
+		sc->sc_int_l = NULL;
+	}
+
+	/* release control listen */
+	if (sc->sc_ctl_l != NULL) {
+		l2cap_detach(&sc->sc_ctl_l);
+		sc->sc_ctl_l = NULL;
+	}
+
+	/* close interrupt channel */
+	if (sc->sc_int != NULL) {
+		l2cap_disconnect(sc->sc_int, 0);
+		l2cap_detach(&sc->sc_int);
+		sc->sc_int = NULL;
+	}
+
+	/* close control channel */
+	if (sc->sc_ctl != NULL) {
+		l2cap_disconnect(sc->sc_ctl, 0);
+		l2cap_detach(&sc->sc_ctl);
+		sc->sc_ctl = NULL;
+	}
+
+	callout_halt(&sc->sc_timeout, bt_lock);
+	callout_destroy(&sc->sc_timeout);
+
+	mutex_exit(bt_lock);
+
+	sockopt_destroy(&sc->sc_mode);
+
+	sysctl_teardown(&sc->sc_log);
+
+	if (sc->sc_wsmouse != NULL) {
+		err = config_detach(sc->sc_wsmouse, flags);
+		sc->sc_wsmouse = NULL;
+	}
+
+	return err;
+}
+
+/*
+ * listen for our device
+ *
+ * bt_lock is held
+ */
+static int
+btmagic_listen(struct btmagic_softc *sc)
+{
+	struct sockaddr_bt sa;
+	int err;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.bt_len = sizeof(sa);
+	sa.bt_family = AF_BLUETOOTH;
+	bdaddr_copy(&sa.bt_bdaddr, &sc->sc_laddr);
+
+	/*
+	 * Listen on control PSM
+	 */
+	err = l2cap_attach(&sc->sc_ctl_l, &btmagic_ctl_proto, sc);
+	if (err)
+		return err;
+
+	err = l2cap_setopt(sc->sc_ctl_l, &sc->sc_mode);
+	if (err)
+		return err;
+
+	sa.bt_psm = L2CAP_PSM_HID_CNTL;
+	err = l2cap_bind(sc->sc_ctl_l, &sa);
+	if (err)
+		return err;
+
+	err = l2cap_listen(sc->sc_ctl_l);
+	if (err)
+		return err;
+
+	/*
+	 * Listen on interrupt PSM
+	 */
+	err = l2cap_attach(&sc->sc_int_l, &btmagic_int_proto, sc);
+	if (err)
+		return err;
+
+	err = l2cap_setopt(sc->sc_int_l, &sc->sc_mode);
+	if (err)
+		return err;
+
+	sa.bt_psm = L2CAP_PSM_HID_INTR;
+	err = l2cap_bind(sc->sc_int_l, &sa);
+	if (err)
+		return err;
+
+	err = l2cap_listen(sc->sc_int_l);
+	if (err)
+		return err;
+
+	sc->sc_state = BTMAGIC_WAIT_CTL;
+	return 0;
+}
+
+/*
+ * start connecting to our device
+ *
+ * bt_lock is held
+ */
+static int
+btmagic_connect(struct btmagic_softc *sc)
+{
+	struct sockaddr_bt sa;
+	int err;
+
+	memset(&sa, 0, sizeof(sa));
+	sa.bt_len = sizeof(sa);
+	sa.bt_family = AF_BLUETOOTH;
+
+	err = l2cap_attach(&sc->sc_ctl, &btmagic_ctl_proto, sc);
+	if (err) {
+		printf("%s: l2cap_attach failed (%d)\n",
+		    device_xname(sc->sc_dev), err);
+		return err;
+	}
+
+	err = l2cap_setopt(sc->sc_ctl, &sc->sc_mode);
+	if (err)
+		return err;
+
+	bdaddr_copy(&sa.bt_bdaddr, &sc->sc_laddr);
+	err = l2cap_bind(sc->sc_ctl, &sa);
+	if (err) {
+		printf("%s: l2cap_bind failed (%d)\n",
+		    device_xname(sc->sc_dev), err);
+		return err;
+	}
+
+	sa.bt_psm = L2CAP_PSM_HID_CNTL;
+	bdaddr_copy(&sa.bt_bdaddr, &sc->sc_raddr);
+	err = l2cap_connect(sc->sc_ctl, &sa);
+	if (err) {
+		printf("%s: l2cap_connect failed (%d)\n",
+		    device_xname(sc->sc_dev), err);
+		return err;
+	}
+
+	SET(sc->sc_flags, BTMAGIC_CONNECTING);
+	sc->sc_state = BTMAGIC_WAIT_CTL;
+	return 0;
+}
+
+/* validate soft_resolution */
+static int
+btmagic_sysctl_resolution(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node;
+	struct btmagic_softc *sc;
+	int t, error;
+
+	node = *rnode;
+	sc = node.sysctl_data;
+
+	t = sc->sc_resolution;
+	node.sysctl_data = &t;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	if (t < 100 || t > 4000 || (t / sc->sc_scale) == 0)
+		return EINVAL;
+
+	sc->sc_resolution = t;
+	DPRINTF(sc, "sc_resolution = %u", t);
+	return 0;
+}
+
+/* validate scroll_downscale_factor */
+static int
+btmagic_sysctl_scale(SYSCTLFN_ARGS)
+{
+	struct sysctlnode node;
+	struct btmagic_softc *sc;
+	int t, error;
+
+	node = *rnode;
+	sc = node.sysctl_data;
+
+	t = sc->sc_scale;
+	node.sysctl_data = &t;
+	error = sysctl_lookup(SYSCTLFN_CALL(&node));
+	if (error || newp == NULL)
+		return error;
+
+	if (t < 1 || t > 40 || (sc->sc_resolution / t) == 0)
+		return EINVAL;
+
+	sc->sc_scale = t;
+	DPRINTF(sc, "sc_scale = %u", t);
+	return 0;
+}
+
+/*****************************************************************************
+ *
+ *	wsmouse(4) accessops
+ */
+
+static int
+btmagic_wsmouse_enable(void *self)
+{
+	struct btmagic_softc *sc = device_private(self);
+
+	if (sc->sc_enabled)
+		return EBUSY;
+
+	sc->sc_enabled = 1;
+	DPRINTF(sc, "enable");
+	return 0;
+}
+
+static int
+btmagic_wsmouse_ioctl(void *self, unsigned long cmd, void *data,
+    int flag, struct lwp *l)
+{
+	/* struct btmagic_softc *sc = device_private(self); */
+	int err;
+
+	switch (cmd) {
+	case WSMOUSEIO_GTYPE:
+		*(uint *)data = WSMOUSE_TYPE_BLUETOOTH;
+		err = 0;
+		break;
+
+	default:
+		err = EPASSTHROUGH;
+		break;
+	}
+
+	return err;
+}
+
+static void
+btmagic_wsmouse_disable(void *self)
+{
+	struct btmagic_softc *sc = device_private(self);
+
+	DPRINTF(sc, "disable");
+	sc->sc_enabled = 0;
+}
+
+
+/*****************************************************************************
+ *
+ *	setup routines
+ */
+
+static void
+btmagic_timeout(void *arg)
+{
+	struct btmagic_softc *sc = arg;
+
+	mutex_enter(bt_lock);
+	callout_ack(&sc->sc_timeout);
+
+	switch (sc->sc_state) {
+	case BTMAGIC_CLOSED:
+		if (sc->sc_int != NULL) {
+			l2cap_disconnect(sc->sc_int, 0);
+			break;
+		}
+
+		if (sc->sc_ctl != NULL) {
+			l2cap_disconnect(sc->sc_ctl, 0);
+			break;
+		}
+		break;
+
+	case BTMAGIC_OPEN:
+		if (!ISSET(sc->sc_flags, BTMAGIC_ENABLED)) {
+			btmagic_enable(sc);
+			break;
+		}
+
+		btmagic_check_battery(sc);
+		break;
+
+	case BTMAGIC_WAIT_CTL:
+	case BTMAGIC_WAIT_INT:
+	default:
+		break;
+	}
+	mutex_exit(bt_lock);
+}
+
+/*
+ * Send report on control channel
+ *
+ * bt_lock is held
+ */
+static int
+btmagic_ctl_send(struct btmagic_softc *sc, const uint8_t *data, size_t len)
+{
+	struct mbuf *m;
+
+	if (len > MLEN)
+		return EINVAL;
+
+	m = m_gethdr(M_DONTWAIT, MT_DATA);
+	if (m == NULL)
+		return ENOMEM;
+
+#ifdef BTMAGIC_DEBUG
+	printf("%s: send", device_xname(sc->sc_dev));
+	for (size_t i = 0; i < len; i++)
+		printf(" 0x%02x", data[i]);
+	printf("\n");
+#endif
+
+	memcpy(mtod(m, uint8_t *), data, len);
+	m->m_pkthdr.len = m->m_len = len;
+	return l2cap_send(sc->sc_ctl, m);
+}
+
+/*
+ * Enable touch reports by sending the following report
+ *
+ *	 SET_REPORT(FEATURE, 0xd7) = 0x01
+ *
+ * bt_lock is held
+ */
+static void
+btmagic_enable(struct btmagic_softc *sc)
+{
+	static const uint8_t rep[] = { 0x53, 0xd7, 0x01 };
+
+	if (btmagic_ctl_send(sc, rep, sizeof(rep)) != 0) {
+		printf("%s: cannot enable touch reports\n",
+		    device_xname(sc->sc_dev));
+
+		return;
+	}
+
+	SET(sc->sc_flags, BTMAGIC_ENABLED);
+}
+
+/*
+ * Request the battery level by sending the following report
+ *
+ *	GET_REPORT(FEATURE, 0x47)
+ *
+ * bt_lock is held
+ */
+static void
+btmagic_check_battery(struct btmagic_softc *sc)
+{
+	static const uint8_t rep[] = { 0x43, 0x47 };
+
+	if (btmagic_ctl_send(sc, rep, sizeof(rep)) != 0)
+		printf("%s: cannot request battery level\n",
+		    device_xname(sc->sc_dev));
+}
+
+/*
+ * the Magic Mouse has a base resolution of 1300dpi which is rather flighty. We
+ * scale the output to the requested resolution, taking care to account for the
+ * remainders to prevent loss of small deltas.
+ */
+static int
+btmagic_scale(int delta, int *remainder, int resolution)
+{
+	int new;
+
+	delta += *remainder;
+	new = delta * resolution / 1300;
+	*remainder = delta - new * 1300 / resolution;
+	return new;
+}
+
+
+/*****************************************************************************
+ *
+ *	bluetooth(9) callback methods for L2CAP
+ *
+ *	All these are called from Bluetooth Protocol code, holding bt_lock.
+ */
+
+static void
+btmagic_connecting(void *arg)
+{
+
+	/* dont care */
+}
+
+static void
+btmagic_ctl_connected(void *arg)
+{
+	struct sockaddr_bt sa;
+	struct btmagic_softc *sc = arg;
+	int err;
+
+	if (sc->sc_state != BTMAGIC_WAIT_CTL)
+		return;
+
+	KASSERT(sc->sc_ctl != NULL);
+	KASSERT(sc->sc_int == NULL);
+
+	if (ISSET(sc->sc_flags, BTMAGIC_CONNECTING)) {
+		/* initiate connect on interrupt PSM */
+		err = l2cap_attach(&sc->sc_int, &btmagic_int_proto, sc);
+		if (err)
+			goto fail;
+
+		err = l2cap_setopt(sc->sc_int, &sc->sc_mode);
+		if (err)
+			goto fail;
+
+		memset(&sa, 0, sizeof(sa));
+		sa.bt_len = sizeof(sa);
+		sa.bt_family = AF_BLUETOOTH;
+		bdaddr_copy(&sa.bt_bdaddr, &sc->sc_laddr);
+
+		err = l2cap_bind(sc->sc_int, &sa);
+		if (err)
+			goto fail;
+
+		sa.bt_psm = L2CAP_PSM_HID_INTR;
+		bdaddr_copy(&sa.bt_bdaddr, &sc->sc_raddr);
+		err = l2cap_connect(sc->sc_int, &sa);
+		if (err)
+			goto fail;
+	}
+
+	sc->sc_state = BTMAGIC_WAIT_INT;
+	return;
+
+fail:
+	l2cap_detach(&sc->sc_ctl);
+	sc->sc_ctl = NULL;
+
+	printf("%s: connect failed (%d)\n", device_xname(sc->sc_dev), err);
+}
+
+static void
+btmagic_int_connected(void *arg)
+{
+	struct btmagic_softc *sc = arg;
+
+	if (sc->sc_state != BTMAGIC_WAIT_INT)
+		return;
+
+	KASSERT(sc->sc_ctl != NULL);
+	KASSERT(sc->sc_int != NULL);
+
+	printf("%s: connected\n", device_xname(sc->sc_dev));
+	CLR(sc->sc_flags, BTMAGIC_CONNECTING);
+	sc->sc_state = BTMAGIC_OPEN;
+
+	/* trigger the setup */
+	CLR(sc->sc_flags, BTMAGIC_ENABLED);
+	callout_schedule(&sc->sc_timeout, hz);
+}
+
+/*
+ * Disconnected
+ *
+ * Depending on our state, this could mean several things, but essentially
+ * we are lost. If both channels are closed, schedule another connection.
+ */
+static void
+btmagic_ctl_disconnected(void *arg, int err)
+{
+	struct btmagic_softc *sc = arg;
+
+	if (sc->sc_ctl != NULL) {
+		l2cap_detach(&sc->sc_ctl);
+		sc->sc_ctl = NULL;
+	}
+
+	if (sc->sc_int == NULL) {
+		printf("%s: disconnected\n", device_xname(sc->sc_dev));
+		CLR(sc->sc_flags, BTMAGIC_CONNECTING);
+		sc->sc_state = BTMAGIC_WAIT_CTL;
+	} else {
+		/*
+		 * The interrupt channel should have been closed first,
+		 * but its potentially unsafe to detach that from here.
+		 * Give them a second to do the right thing or let the
+		 * callout handle it.
+		 */
+		sc->sc_state = BTMAGIC_CLOSED;
+		callout_schedule(&sc->sc_timeout, hz);
+	}
+}
+
+static void
+btmagic_int_disconnected(void *arg, int err)
+{
+	struct btmagic_softc *sc = arg;
+
+	if (sc->sc_int != NULL) {
+		l2cap_detach(&sc->sc_int);
+		sc->sc_int = NULL;
+	}
+
+	if (sc->sc_ctl == NULL) {
+		printf("%s: disconnected\n", device_xname(sc->sc_dev));
+		CLR(sc->sc_flags, BTMAGIC_CONNECTING);
+		sc->sc_state = BTMAGIC_WAIT_CTL;
+	} else {
+		/*
+		 * The control channel should be closing also, allow
+		 * them a chance to do that before we force it.
+		 */
+		sc->sc_state = BTMAGIC_CLOSED;
+		callout_schedule(&sc->sc_timeout, hz);
+	}
+}
+
+/*
+ * New Connections
+ *
+ * We give a new L2CAP handle back if this matches the BDADDR we are
+ * listening for and we are in the right state. btmagic_connected will
+ * be called when the connection is open, so nothing else to do here
+ */
+static void *
+btmagic_ctl_newconn(void *arg, struct sockaddr_bt *laddr,
+    struct sockaddr_bt *raddr)
+{
+	struct btmagic_softc *sc = arg;
+
+	if (bdaddr_same(&raddr->bt_bdaddr, &sc->sc_raddr) == 0)
+		return NULL;
+
+	if (sc->sc_state != BTMAGIC_WAIT_CTL
+	    || ISSET(sc->sc_flags, BTMAGIC_CONNECTING)
+	    || sc->sc_ctl != NULL
+	    || sc->sc_int != NULL) {
+		DPRINTF(sc, "reject ctl newconn %s%s%s%s",
+		    (sc->sc_state == BTMAGIC_WAIT_CTL) ? " (WAITING)": "",
+		    ISSET(sc->sc_flags, BTMAGIC_CONNECTING) ? " (CONNECTING)" : "",
+		    (sc->sc_ctl != NULL) ? " (GOT CONTROL)" : "",
+		    (sc->sc_int != NULL) ? " (GOT INTERRUPT)" : "");
+
+		return NULL;
+	}
+
+	l2cap_attach(&sc->sc_ctl, &btmagic_ctl_proto, sc);
+	return sc->sc_ctl;
+}
+
+static void *
+btmagic_int_newconn(void *arg, struct sockaddr_bt *laddr,
+    struct sockaddr_bt *raddr)
+{
+	struct btmagic_softc *sc = arg;
+
+	if (bdaddr_same(&raddr->bt_bdaddr, &sc->sc_raddr) == 0)
+		return NULL;
+
+	if (sc->sc_state != BTMAGIC_WAIT_INT
+	    || ISSET(sc->sc_flags, BTMAGIC_CONNECTING)
+	    || sc->sc_ctl == NULL
+	    || sc->sc_int != NULL) {
+		DPRINTF(sc, "reject int newconn %s%s%s%s",
+		    (sc->sc_state == BTMAGIC_WAIT_INT) ? " (WAITING)": "",
+		    ISSET(sc->sc_flags, BTMAGIC_CONNECTING) ? " (CONNECTING)" : "",
+		    (sc->sc_ctl == NULL) ? " (NO CONTROL)" : "",
+		    (sc->sc_int != NULL) ? " (GOT INTERRUPT)" : "");
+
+		return NULL;
+	}
+
+	l2cap_attach(&sc->sc_int, &btmagic_int_proto, sc);
+	return sc->sc_int;
+}
+
+static void
+btmagic_complete(void *arg, int count)
+{
+
+	/* dont care */
+}
+
+static void
+btmagic_linkmode(void *arg, int new)
+{
+	struct btmagic_softc *sc = arg;
+	int mode;
+
+	(void)sockopt_getint(&sc->sc_mode, &mode);
+
+	if (ISSET(mode, L2CAP_LM_AUTH) && !ISSET(new, L2CAP_LM_AUTH))
+		printf("%s: auth failed\n", device_xname(sc->sc_dev));
+	else if (ISSET(mode, L2CAP_LM_ENCRYPT) && !ISSET(new, L2CAP_LM_ENCRYPT))
+		printf("%s: encrypt off\n", device_xname(sc->sc_dev));
+	else if (ISSET(mode, L2CAP_LM_SECURE) && !ISSET(new, L2CAP_LM_SECURE))
+		printf("%s: insecure\n", device_xname(sc->sc_dev));
+	else
+		return;
+
+	if (sc->sc_int != NULL)
+		l2cap_disconnect(sc->sc_int, 0);
+
+	if (sc->sc_ctl != NULL)
+		l2cap_disconnect(sc->sc_ctl, 0);
+}
+
+/*
+ * Receive transaction from the mouse. We don't differentiate between
+ * interrupt and control channel here, there is no need.
+ */
+static void
+btmagic_input(void *arg, struct mbuf *m)
+{
+	struct btmagic_softc *sc = arg;
+	uint8_t *data;
+	size_t len;
+
+	if (sc->sc_state != BTMAGIC_OPEN
+	    || sc->sc_wsmouse == NULL
+	    || sc->sc_enabled == 0)
+		goto release;
+
+	if (m->m_pkthdr.len > m->m_len)
+		printf("%s: truncating input\n", device_xname(sc->sc_dev));
+
+	data = mtod(m, uint8_t *);
+	len = m->m_len;
+
+	if (len < 1)
+		goto release;
+
+	switch (BTHID_TYPE(data[0])) {
+	case BTHID_HANDSHAKE:
+		DPRINTF(sc, "Handshake: 0x%x", BTHID_HANDSHAKE_PARAM(data[0]));
+		callout_schedule(&sc->sc_timeout, hz);
+		break;
+
+	case BTHID_DATA:
+		if (len < 2)
+			break;
+
+		switch (data[1]) {
+		case 0x10: /* Basic mouse (input) */
+			btmagic_input_basic(sc, data + 2, len - 2);
+			break;
+
+		case 0x29: /* Magic touch (input) */
+			btmagic_input_magic(sc, data + 2, len - 2);
+			break;
+
+		case 0x30: /* Battery status (input) */
+			if (len != 3)
+				break;
+
+			printf("%s: Battery ", device_xname(sc->sc_dev));
+			switch (data[2]) {
+			case 0:	printf("Ok\n");			break;
+			case 1:	printf("Warning\n");		break;
+			case 2:	printf("Critical\n");		break;
+			default: printf("0x%02x\n", data[2]);	break;
+			}
+			break;
+
+		case 0x47: /* Battery strength (feature) */
+			if (len != 3)
+				break;
+
+			printf("%s: Battery %d%%\n", device_xname(sc->sc_dev),
+			    data[2]);
+			break;
+
+		case 0x61: /* Surface detection (input) */
+			if (len != 3)
+				break;
+
+			DPRINTF(sc, "Mouse %s",
+			    (data[2] == 0 ? "lowered" : "raised"));
+			break;
+
+		case 0x60: /* unknown (input) */
+		case 0xf0: /* unknown (feature) */
+		case 0xf1: /* unknown (feature) */
+		default:
+#if BTMAGIC_DEBUG
+			printf("%s: recv", device_xname(sc->sc_dev));
+			for (size_t i = 0; i < len; i++)
+				printf(" 0x%02x", data[i]);
+			printf("\n");
+#endif
+			break;
+		}
+		break;
+
+	default:
+		DPRINTF(sc, "transaction (type 0x%x)", BTHID_TYPE(data[0]));
+		break;
+	}
+
+release:
+	m_freem(m);
+}
+
+/*
+ * parse the Basic report (0x10), which according to the provided
+ * HID descriptor is in the following format
+ *
+ *	button 1	1-bit
+ *	button 2	1-bit
+ *	padding		6-bits
+ *	dX		16-bits (signed)
+ *	dY		16-bits (signed)
+ *
+ * Even when the magic touch reports are enabled, the basic report is
+ * sent for mouse move events where no touches are detected.
+ */
+static const struct {
+	struct hid_location button1;
+	struct hid_location button2;
+	struct hid_location dX;
+	struct hid_location dY;
+} basic = {
+	.button1 = { .pos =  0, .size = 1 },
+	.button2 = { .pos =  1, .size = 1 },
+	.dX = { .pos =  8, .size = 16 },
+	.dY = { .pos = 24, .size = 16 },
+};
+
+static void
+btmagic_input_basic(struct btmagic_softc *sc, uint8_t *data, size_t len)
+{
+	int dx, dy;
+	uint32_t mb;
+	int s;
+
+	if (len != 5)
+		return;
+
+	dx = hid_get_data(data, &basic.dX);
+	dx = btmagic_scale(dx, &sc->sc_rx, sc->sc_resolution);
+
+	dy = hid_get_data(data, &basic.dY);
+	dy = btmagic_scale(dy, &sc->sc_ry, sc->sc_resolution);
+
+	mb = 0;
+	if (hid_get_udata(data, &basic.button1))
+		mb |= __BIT(0);
+	if (hid_get_udata(data, &basic.button2))
+		mb |= __BIT(2);
+
+	if (dx != 0 || dy != 0 || mb != sc->sc_mb) {
+		sc->sc_mb = mb;
+
+		s = spltty();
+		wsmouse_input(sc->sc_wsmouse, mb,
+		    dx, -dy, 0, 0, WSMOUSE_INPUT_DELTA);
+		splx(s);
+	}
+}
+
+/*
+ * the Magic touch report (0x29), according to the Linux driver
+ * written by Michael Poole, is variable length starting with the
+ * fixed 40-bit header
+ *
+ *	dX lsb		8-bits (signed)
+ *	dY lsb		8-bits (signed)
+ *	button 1	1-bit
+ *	button 2	1-bit
+ *	dX msb		2-bits (signed)
+ *	dY msb		2-bits (signed)
+ *	timestamp	18-bits
+ *
+ * followed by (up to 5?) touch reports of 64-bits each
+ *
+ *	abs W		12-bits (signed)
+ *	abs Z		12-bits (signed)
+ *	axis major	8-bits
+ *	axis minor	8-bits
+ *	pressure	6-bits
+ *	id		4-bits
+ *	angle		6-bits	(from E(0)->N(32)->W(64))
+ *	unknown		4-bits
+ *	phase		4-bits
+ */
+
+static const struct {
+	struct hid_location dXl;
+	struct hid_location dYl;
+	struct hid_location button1;
+	struct hid_location button2;
+	struct hid_location dXm;
+	struct hid_location dYm;
+	struct hid_location timestamp;
+} magic = {
+	.dXl = { .pos = 0, .size = 8 },
+	.dYl = { .pos = 8, .size = 8 },
+	.button1 = { .pos = 16, .size = 1 },
+	.button2 = { .pos = 17, .size = 1 },
+	.dXm = { .pos = 18, .size = 2 },
+	.dYm = { .pos = 20, .size = 2 },
+	.timestamp = { .pos = 22, .size = 18 },
+};
+
+static const struct {
+	struct hid_location aW;
+	struct hid_location aZ;
+	struct hid_location major;
+	struct hid_location minor;
+	struct hid_location pressure;
+	struct hid_location id;
+	struct hid_location angle;
+	struct hid_location unknown;
+	struct hid_location phase;
+} touch = {
+	.aW = { .pos = 0, .size = 12 },
+	.aZ = { .pos = 12, .size = 12 },
+	.major = { .pos = 24, .size = 8 },
+	.minor = { .pos = 32, .size = 8 },
+	.pressure = { .pos = 40, .size = 6 },
+	.id = { .pos = 46, .size = 4 },
+	.angle = { .pos = 50, .size = 6 },
+	.unknown = { .pos = 56, .size = 4 },
+	.phase = { .pos = 60, .size = 4 },
+};
+
+/*
+ * the phase of the touch starts at 0x01 as the finger is first detected
+ * approaching the mouse, increasing to 0x04 while the finger is touching,
+ * then increases towards 0x07 as the finger is lifted, and we get 0x00
+ * when the touch is cancelled. The values below seem to be produced for
+ * every touch, the others less consistently depending on how fast the
+ * approach or departure is.
+ *
+ * In fact we ignore touches unless they are in the steady 0x04 phase.
+ */
+#define BTMAGIC_PHASE_START	0x3
+#define BTMAGIC_PHASE_CONT	0x4
+#define BTMAGIC_PHASE_END	0x7
+#define BTMAGIC_PHASE_CANCEL	0x0
+
+static void
+btmagic_input_magic(struct btmagic_softc *sc, uint8_t *data, size_t len)
+{
+	uint32_t mb;
+	int dx, dy, dz, dw;
+	int id, nf, az, aw, tz, tw;
+	int s;
+
+	if (((len - 5) % 8) != 0)
+		return;
+
+	dx = (hid_get_data(data, &magic.dXm) << 8)
+	    | (hid_get_data(data, &magic.dXl) & 0xff);
+	dx = btmagic_scale(dx, &sc->sc_rx, sc->sc_resolution);
+
+	dy = (hid_get_data(data, &magic.dYm) << 8)
+	    | (hid_get_data(data, &magic.dYl) & 0xff);
+	dy = btmagic_scale(dy, &sc->sc_ry, sc->sc_resolution);
+
+	mb = 0;
+	if (hid_get_udata(data, &magic.button1))
+		mb |= __BIT(0);
+	if (hid_get_udata(data, &magic.button2))
+		mb |= __BIT(2);
+
+	nf = 0;
+	dz = 0;
+	dw = 0;
+	len = (len - 5) / 8;
+	for (data += 5; len-- > 0; data += 8) {
+		id = hid_get_udata(data, &touch.id);
+		az = hid_get_data(data, &touch.aZ);
+		aw = hid_get_data(data, &touch.aW);
+
+		/*
+		 * scrolling is triggered by an established touch moving
+		 * beyond a minimum distance from its start point and is
+		 * cancelled as the touch starts to fade.
+		 *
+		 * Multiple touches may be scrolling simultaneously, the
+		 * effect is cumulative.
+		 */
+
+		switch (hid_get_udata(data, &touch.phase)) {
+		case BTMAGIC_PHASE_CONT:
+			tz = az - sc->sc_az[id];
+			tw = aw - sc->sc_aw[id];
+
+			if (ISSET(sc->sc_smask, id)) {
+				/* scrolling finger */
+				dz += btmagic_scale(tz, &sc->sc_rz,
+				    sc->sc_resolution / sc->sc_scale);
+				dw += btmagic_scale(tw, &sc->sc_rw,
+				    sc->sc_resolution / sc->sc_scale);
+			} else if (abs(tz) > sc->sc_dist
+			    || abs(tw) > sc->sc_dist) {
+				/* new scrolling finger */
+				if (sc->sc_smask == 0) {
+					sc->sc_rz = 0;
+					sc->sc_rw = 0;
+				}
+
+				SET(sc->sc_smask, id);
+			} else {
+				/* not scrolling finger */
+				az = sc->sc_az[id];
+				aw = sc->sc_aw[id];
+			}
+
+			/* count firm touches for middle-click */
+			if (hid_get_udata(data, &touch.pressure) > sc->sc_firm)
+				nf++;
+
+			break;
+
+		default:
+			CLR(sc->sc_smask, id);
+			break;
+		}
+
+		sc->sc_az[id] = az;
+		sc->sc_aw[id] = aw;
+	}
+
+	/*
+	 * The mouse only has one click detector, and says left or right but
+	 * never both. We convert multiple firm touches while clicking into
+	 * a middle button press, and cancel any scroll effects while click
+	 * is active.
+	 */
+	if (mb != 0) {
+		if (sc->sc_mb != 0)
+			mb = sc->sc_mb;
+		else if (nf > 1)
+			mb = __BIT(1);
+
+		sc->sc_smask = 0;
+		dz = 0;
+		dw = 0;
+	}
+
+	if (dx != 0 || dy != 0 || dz != 0 || dw != 0 || mb != sc->sc_mb) {
+		sc->sc_mb = mb;
+
+		s = spltty();
+		wsmouse_input(sc->sc_wsmouse, mb,
+		    dx, -dy, -dz, dw, WSMOUSE_INPUT_DELTA);
+		splx(s);
+	}
+}

Reply via email to