root/sys/dev/bluetooth/btmagic.c
/*      $NetBSD: btmagic.c,v 1.21 2021/08/07 16:19:09 thorpej 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.21 2021/08/07 16:19:09 thorpej 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/hid/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;       /* active IDs */
        int                     sc_nfingers;    /* number of active IDs */
        int                     sc_ax[16];
        int                     sc_ay[16];

        /* previous mouse buttons */
        int                     sc_mb_id; /* which ID selects the button */
        uint32_t                sc_mb;
        /* button emulation with tap */
        int                     sc_tapmb_id; /* which ID selects the button */
        struct timeval          sc_taptime;
        int                     sc_taptimeout;
        callout_t               sc_tapcallout;
};

/* 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);
static int  btmagic_tap(struct btmagic_softc *, int);
static int  btmagic_sysctl_taptimeout(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_magicm(struct btmagic_softc *, uint8_t *, size_t);
static void  btmagic_input_magict(struct btmagic_softc *, uint8_t *, size_t);
static void  btmagic_tapcallout(void *);

/* report types (data[1]) */
#define BASIC_REPORT_ID         0x10
#define TRACKPAD_REPORT_ID      0x28
#define MOUSE_REPORT_ID         0x29
#define BATT_STAT_REPORT_ID     0x30
#define BATT_STRENGTH_REPORT_ID 0x47
#define SURFACE_REPORT_ID       0x61

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 ||
                p == USB_PRODUCT_APPLE_MAGICTRACKPAD))
                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;
        int err;

        /*
         * Init softc
         */
        sc->sc_dev = self;
        sc->sc_state = BTMAGIC_CLOSED;
        sc->sc_mb_id = -1;
        sc->sc_tapmb_id = -1;
        callout_init(&sc->sc_timeout, 0);
        callout_setfunc(&sc->sc_timeout, btmagic_timeout, sc);
        callout_init(&sc->sc_tapcallout, 0);
        callout_setfunc(&sc->sc_tapcallout, btmagic_tapcallout, 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_value(obj));

        obj = prop_dictionary_get(aux, BTDEVraddr);
        bdaddr_copy(&sc->sc_raddr, prop_data_value(obj));

        obj = prop_dictionary_get(aux, BTDEVmode);
        if (prop_object_type(obj) == PROP_TYPE_STRING) {
                if (prop_string_equals_string(obj, BTDEVauth))
                        sockopt_setint(&sc->sc_mode, L2CAP_LM_AUTH);
                else if (prop_string_equals_string(obj, BTDEVencrypt))
                        sockopt_setint(&sc->sc_mode, L2CAP_LM_ENCRYPT);
                else if (prop_string_equals_string(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_value(obj));
        } else
                sockopt_setint(&sc->sc_mode, 0);

        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;
        sc->sc_taptimeout = 100;

        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,
                        (void *)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,
                        (void *)sc, 0,
                        CTL_HW, node->sysctl_num,
                        CTL_CREATE, CTL_EOL);
                sysctl_createv(&sc->sc_log, 0, NULL, NULL,
                        CTLFLAG_READWRITE,
                        CTLTYPE_INT, "taptimeout",
                        "timeout for tap detection in milliseconds",
                        btmagic_sysctl_taptimeout, 0,
                        (void *)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, CFARGS_NONE);
        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);
        if ((err = btmagic_listen(sc)) != 0)
                aprint_error_dev(self, "failed to listen (%d)\n", err);
        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_pcb(&sc->sc_int_l);
                sc->sc_int_l = NULL;
        }

        /* release control listen */
        if (sc->sc_ctl_l != NULL) {
                l2cap_detach_pcb(&sc->sc_ctl_l);
                sc->sc_ctl_l = NULL;
        }

        /* close interrupt channel */
        if (sc->sc_int != NULL) {
                l2cap_disconnect_pcb(sc->sc_int, 0);
                l2cap_detach_pcb(&sc->sc_int);
                sc->sc_int = NULL;
        }

        /* close control channel */
        if (sc->sc_ctl != NULL) {
                l2cap_disconnect_pcb(sc->sc_ctl, 0);
                l2cap_detach_pcb(&sc->sc_ctl);
                sc->sc_ctl = NULL;
        }

        callout_halt(&sc->sc_tapcallout, bt_lock);
        callout_destroy(&sc->sc_tapcallout);
        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_pcb(&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_pcb(sc->sc_ctl_l, &sa);
        if (err)
                return err;

        err = l2cap_listen_pcb(sc->sc_ctl_l);
        if (err)
                return err;

        /*
         * Listen on interrupt PSM
         */
        err = l2cap_attach_pcb(&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_pcb(sc->sc_int_l, &sa);
        if (err)
                return err;

        err = l2cap_listen_pcb(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_pcb(&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) {
                printf("%s: l2cap_setopt failed (%d)\n",
                    device_xname(sc->sc_dev), err);
                return err;
        }

        bdaddr_copy(&sa.bt_bdaddr, &sc->sc_laddr);
        err = l2cap_bind_pcb(sc->sc_ctl, &sa);
        if (err) {
                printf("%s: l2cap_bind_pcb 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_pcb(sc->sc_ctl, &sa);
        if (err) {
                printf("%s: l2cap_connect_pcb 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;
}

/* validate tap timeout */
static int
btmagic_sysctl_taptimeout(SYSCTLFN_ARGS)
{
        struct sysctlnode node;
        struct btmagic_softc *sc;
        int t, error;

        node = *rnode;
        sc = node.sysctl_data;

        t = sc->sc_taptimeout;
        node.sysctl_data = &t;
        error = sysctl_lookup(SYSCTLFN_CALL(&node));
        if (error || newp == NULL)
                return error;

        if (t < uimax(1000 / hz, 1) || t > 999)
                return EINVAL;

        sc->sc_taptimeout = t;
        DPRINTF(sc, "taptimeout = %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_pcb(sc->sc_int, 0);
                        break;
                }

                if (sc->sc_ctl != NULL) {
                        l2cap_disconnect_pcb(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_pcb(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_pcb(&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_pcb(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_pcb(sc->sc_int, &sa);
                if (err)
                        goto fail;
        }

        sc->sc_state = BTMAGIC_WAIT_INT;
        return;

fail:
        l2cap_detach_pcb(&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_pcb(&sc->sc_ctl);
                sc->sc_ctl = NULL;
        }

        if (sc->sc_int == NULL) {
                printf("%s: disconnected (%d)\n", device_xname(sc->sc_dev), err);
                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_pcb(&sc->sc_int);
                sc->sc_int = NULL;
        }

        if (sc->sc_ctl == NULL) {
                printf("%s: disconnected (%d)\n", device_xname(sc->sc_dev), err);
                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_pcb(&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_pcb(&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_pcb(sc->sc_int, 0);

        if (sc->sc_ctl != NULL)
                l2cap_disconnect_pcb(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 BASIC_REPORT_ID: /* Basic mouse (input) */
                        btmagic_input_basic(sc, data + 2, len - 2);
                        break;

                case TRACKPAD_REPORT_ID: /* Magic trackpad (input) */
                        btmagic_input_magict(sc, data + 2, len - 2);
                        break;

                case MOUSE_REPORT_ID: /* Magic touch (input) */
                        btmagic_input_magicm(sc, data + 2, len - 2);
                        break;

                case BATT_STAT_REPORT_ID: /* 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 BATT_STRENGTH_REPORT_ID: /* Battery strength (feature) */
                        if (len != 3)
                                break;

                        printf("%s: Battery %d%%\n", device_xname(sc->sc_dev),
                            data[2]);
                        break;

                case SURFACE_REPORT_ID: /* 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_magicm(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:
#define sc_az sc_ay
#define sc_aw sc_ax
                        tz = az - sc->sc_az[id];
                        tw = aw - sc->sc_aw[id];

                        if (ISSET(sc->sc_smask, __BIT(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, __BIT(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, __BIT(id));
                        break;
                }

                sc->sc_az[id] = az;
                sc->sc_aw[id] = aw;
#undef sc_az
#undef sc_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);
        }
}

/*
 * the Magic touch trackpad report (0x28), according to the Linux driver
 * written by Michael Poole and Chase Douglas, is variable length starting
 * with the fixed 24-bit header
 *
 *      button 1        1-bit
 *      unknown         5-bits
 *      timestamp       18-bits
 *
 * followed by (up to 5?) touch reports of 72-bits each
 *
 *      abs X           13-bits (signed)
 *      abs Y           13-bits (signed)
 *      unknown         6-bits
 *      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 button;
        struct hid_location timestamp;
} magict = {
        .button = { .pos =  0, .size = 1 },
        .timestamp = { .pos = 6, .size = 18 },
};

static const struct {
        struct hid_location aX;
        struct hid_location aY;
        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;
} toucht = {
        .aX = { .pos = 0, .size = 13 },
        .aY = { .pos = 13, .size = 13 },
        .major = { .pos = 32, .size = 8 },
        .minor = { .pos = 40, .size = 8 },
        .pressure = { .pos = 48, .size = 6 },
        .id = { .pos = 54, .size = 4 },
        .angle = { .pos = 58, .size = 6 },
        .unknown = { .pos = 64, .size = 4 },
        .phase = { .pos = 68, .size = 4 },
};

/*
 * as for btmagic_input_magicm, 
 * 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.
 */

/* min and max values reported */
#define MAGICT_X_MIN    (-2910)
#define MAGICT_X_MAX    (3170)
#define MAGICT_Y_MIN    (-2565)
#define MAGICT_Y_MAX    (2455)

/*
 * area for detecting the buttons: divide in 3 areas on X, 
 * below -1900 on y
 */
#define MAGICT_B_YMAX   (-1900)
#define MAGICT_B_XSIZE  ((MAGICT_X_MAX - MAGICT_X_MIN) / 3)
#define MAGICT_B_X1MAX  (MAGICT_X_MIN + MAGICT_B_XSIZE)
#define MAGICT_B_X2MAX  (MAGICT_X_MIN + MAGICT_B_XSIZE * 2)

static void
btmagic_input_magict(struct btmagic_softc *sc, uint8_t *data, size_t len)
{
        bool bpress;
        uint32_t mb;
        int id, ax, ay, tx, ty;
        int dx, dy, dz, dw;
        int s;

        if (((len - 3) % 9) != 0)
                return;

        bpress = 0;
        if (hid_get_udata(data, &magict.button))
                bpress = 1;

        dx = dy = dz = dw = 0;
        mb = 0;

        len = (len - 3) / 9;
        for (data += 3; len-- > 0; data += 9) {
                id = hid_get_udata(data, &toucht.id);
                ax = hid_get_data(data, &toucht.aX);
                ay = hid_get_data(data, &toucht.aY);

                DPRINTF(sc,
                    "btmagic_input_magict: id %d ax %d ay %d phase %ld %s\n",
                    id, ax, ay, hid_get_udata(data, &toucht.phase),
                    bpress ? "button pressed" : "");

                /*
                 * a single touch is interpreted as a mouse move.
                 * If a button is pressed, the touch in the button area
                 * defined above defines the button; a second touch is
                 * interpreted as a mouse move.
                 */

                switch (hid_get_udata(data, &toucht.phase)) {
                case BTMAGIC_PHASE_CONT:
                        if (bpress) {
                                if (sc->sc_mb == 0 && ay < MAGICT_B_YMAX) {
                                        /*
                                         * we have a new button press,
                                         * and this id tells which one
                                         */
                                        if (ax < MAGICT_B_X1MAX)
                                                mb = __BIT(0);
                                        else if (ax > MAGICT_B_X2MAX)
                                                mb = __BIT(2);
                                        else
                                                mb = __BIT(1);
                                        sc->sc_mb_id = id;
                                } else {
                                        /* keep previous state */
                                        mb = sc->sc_mb;
                                }
                        } else {
                                /* no button pressed */
                                mb = 0;
                                sc->sc_mb_id = -1;
                        }
                        if (id == sc->sc_mb_id) {
                                /*
                                 * this id selects the button
                                 * ignore for move/scroll
                                 */
                                 continue;
                        }
                        if (id >= __arraycount(sc->sc_ax))
                                continue;
                                        
                        tx = ax - sc->sc_ax[id];
                        ty = ay - sc->sc_ay[id];

                        if (ISSET(sc->sc_smask, __BIT(id))) {
                                struct timeval now_tv;
                                getmicrotime(&now_tv);
                                if (sc->sc_nfingers == 1 && mb == 0 &&
                                    timercmp(&sc->sc_taptime, &now_tv, >)) {
                                        /* still detecting a tap */
                                        continue;
                                }

                                if (sc->sc_nfingers == 1 || mb != 0) {
                                        /* single finger moving */
                                        dx += btmagic_scale(tx, &sc->sc_rx,
                                            sc->sc_resolution);
                                        dy += btmagic_scale(ty, &sc->sc_ry,
                                            sc->sc_resolution);
                                } else {
                                        /* scrolling fingers */
                                        dz += btmagic_scale(ty, &sc->sc_rz,
                                            sc->sc_resolution / sc->sc_scale);
                                        dw += btmagic_scale(tx, &sc->sc_rw,
                                            sc->sc_resolution / sc->sc_scale);
                                }
                        } else if (ay > MAGICT_B_YMAX) { /* new finger */
                                sc->sc_rx = 0;
                                sc->sc_ry = 0;
                                sc->sc_rz = 0;
                                sc->sc_rw = 0;
                                KASSERT(!ISSET(sc->sc_smask, __BIT(id)));
                                SET(sc->sc_smask, __BIT(id));
                                sc->sc_nfingers++;
                                if (sc->sc_tapmb_id == -1 &&
                                    mb == 0 && sc->sc_mb == 0) {
                                        sc->sc_tapmb_id = id;
                                        getmicrotime(&sc->sc_taptime);
                                        sc->sc_taptime.tv_usec +=
                                            sc->sc_taptimeout * 1000;
                                        if (sc->sc_taptime.tv_usec > 1000000) {
                                                sc->sc_taptime.tv_usec -=
                                                    1000000;
                                                sc->sc_taptime.tv_sec++;
                                        }
                                }
                                        
                        }

                        break;
                default:
                        if (ISSET(sc->sc_smask, __BIT(id))) {
                                CLR(sc->sc_smask, __BIT(id));
                                sc->sc_nfingers--;
                                KASSERT(sc->sc_nfingers >= 0);
                                if (id == sc->sc_tapmb_id) {
                                        mb = btmagic_tap(sc, id);
                                }
                        }
                        break;
                }

                if (id >= __arraycount(sc->sc_ax))
                        continue;

                sc->sc_ax[id] = ax;
                sc->sc_ay[id] = ay;
        }

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

static int
btmagic_tap(struct btmagic_softc *sc, int id)
{
        struct timeval now_tv;

        sc->sc_tapmb_id = -1;
        getmicrotime(&now_tv);
        if (timercmp(&sc->sc_taptime, &now_tv, >)) {
                /* got a tap */
                callout_schedule(
                    &sc->sc_tapcallout,
                    mstohz(sc->sc_taptimeout));
                return __BIT(0);
        }
        return 0;
}

static void
btmagic_tapcallout(void *arg)
{
        struct btmagic_softc *sc = arg;
        int s;

        mutex_enter(bt_lock);
        callout_ack(&sc->sc_tapcallout);
        if ((sc->sc_mb & __BIT(0)) != 0) {
                sc->sc_mb &= ~__BIT(0);
                s = spltty();
                wsmouse_input(sc->sc_wsmouse, sc->sc_mb,
                    0, 0, 0, 0, WSMOUSE_INPUT_DELTA);
                splx(s);
        }
        mutex_exit(bt_lock);
}