summaryrefslogtreecommitdiff
path: root/sys/dev/bluetooth
diff options
context:
space:
mode:
authorplunky <plunky@NetBSD.org>2010-05-22 18:56:00 +0000
committerplunky <plunky@NetBSD.org>2010-05-22 18:56:00 +0000
commitb1c545a2cfacfbe95e35319182b53c5cc37cbc4a (patch)
treeefcfa9f8dbed9f7f1dc60488031dd66377b735be /sys/dev/bluetooth
parent3368a77b7c48673fd28574b3b1c8f3654c105057 (diff)
add Magic Mouse driver and manpage btmagic(4)
Diffstat (limited to 'sys/dev/bluetooth')
-rw-r--r--sys/dev/bluetooth/btmagic.c1355
-rw-r--r--sys/dev/bluetooth/files.bluetooth7
2 files changed, 1361 insertions, 1 deletions
diff --git a/sys/dev/bluetooth/btmagic.c b/sys/dev/bluetooth/btmagic.c
new file mode 100644
index 00000000000..02310e827aa
--- /dev/null
+++ b/sys/dev/bluetooth/btmagic.c
@@ -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 (lennart@augustsson.net) at
+ * Carlstedt Research & Technology.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * 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);
+ }
+}
diff --git a/sys/dev/bluetooth/files.bluetooth b/sys/dev/bluetooth/files.bluetooth
index 2ea4e0b6ecc..189773bb884 100644
--- a/sys/dev/bluetooth/files.bluetooth
+++ b/sys/dev/bluetooth/files.bluetooth
@@ -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 @@ device btms: hid, wsmousedev
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