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 ([email protected]) 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);
+ }
+}