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); + } +}