/* $NetBSD: btmagic.c,v 1.2 2012/04/03 09:32:53 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 __KERNEL_RCSID(0, "$NetBSD: btmagic.c,v 1.2 2012/04/03 09:32:53 plunky Exp $"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; } pmf_device_register(self, NULL, NULL); /* * 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); pmf_device_deregister(self); 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); } }