root/usr/src/uts/common/io/usb/clients/usbinput/usbwcm/usbwcm.c
/*
 * Copyright (c) 2018, Joyent, Inc.
 */

/*
 * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * Copyright (c) 2007, 2008 Bartosz Fabianowski <freebsd@chillt.de>
 * All rights reserved.
 *
 * Financed by the "Irish Research Council for Science, Engineering and
 * Technology: funded by the National Development Plan"
 *
 * 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 AUTHOR 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 AUTHOR 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) 1998 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *        This product includes software developed by the NetBSD
 *        Foundation, Inc. and its contributors.
 * 4. Neither the name of The NetBSD Foundation nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission.
 *
 * 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.
 */

#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/strtty.h>
#include <sys/systm.h>

#include <sys/usb/usba/usbai_version.h>
#include <sys/usb/usba.h>
#include <sys/usb/usba/usbai_private.h>
#include <sys/usb/clients/hid/hid.h>
#include <sys/usb/clients/usbinput/usbwcm/usbwcm.h>

/* debugging information */
uint_t  usbwcm_errmask = (uint_t)PRINT_MASK_ALL;
uint_t  usbwcm_errlevel = USB_LOG_L2;
static usb_log_handle_t usbwcm_log_handle;

static void
uwacom_event(usbwcm_state_t *usbwcmp, uint_t type, uint_t idx, int val)
{
        struct uwacom_softc     *sc = &usbwcmp->usbwcm_softc;
        mblk_t                  *mp;

        switch (type) {
        case EVT_SYN:
                if (sc->sc_sync)
                        return;
                break;

        case EVT_BTN:
                if (sc->sc_btn[idx] == val)
                        return;

                sc->sc_btn[idx] = val;
                break;

        case EVT_ABS:
                if (sc->sc_abs[idx].fuzz) {
                        int dist = abs(val - sc->sc_abs[idx].value);

                        if (dist < sc->sc_abs[idx].fuzz >> 1) {
                                return;
                        } else if (dist < sc->sc_abs[idx].fuzz) {
                                val = (7 * sc->sc_abs[idx].value + val) >> 3;
                        } else if (dist < sc->sc_abs[idx].fuzz << 1) {
                                val = (sc->sc_abs[idx].value + val) >> 1;
                        }
                }
                if (sc->sc_abs[idx].value == val) {
                        return;
                }

                sc->sc_abs[idx].value = val;
                break;

        case EVT_REL:
                if (!val)
                        return;
                break;

        case EVT_MSC:
                break;

        default:
                return;
        }

        if ((mp = allocb(sizeof (struct event_input), BPRI_HI)) != NULL) {
                struct event_input *ev = (struct event_input *)mp->b_wptr;

                ev->type = (uint16_t)type;
                ev->code = (uint16_t)idx;
                ev->value = (int32_t)val;
                uniqtime32(&ev->time);

                mp->b_wptr += sizeof (struct event_input);
                putnext(usbwcmp->usbwcm_rq, mp);
        } else {
                return;
        }

        sc->sc_sync = (type == EVT_SYN);
}

static void
uwacom_pos_events_graphire(usbwcm_state_t *usbwcmp, int x, int y)
{
        uwacom_event(usbwcmp, EVT_ABS, ABS_X, x);
        uwacom_event(usbwcmp, EVT_ABS, ABS_Y, y);
}

static void
uwacom_pen_events_graphire(usbwcm_state_t *usbwcmp, int prs, int stl1, int stl2)
{
        uwacom_event(usbwcmp, EVT_ABS, ABS_PRESSURE, prs);
        uwacom_event(usbwcmp, EVT_BTN, BTN_TIP, prs);
        uwacom_event(usbwcmp, EVT_BTN, BTN_STYLUS_1, stl1);
        uwacom_event(usbwcmp, EVT_BTN, BTN_STYLUS_2, stl2);
}

static void
uwacom_mouse_events_graphire(usbwcm_state_t *usbwcmp, int left, int middle,
    int right, int wheel, int distance)
{
        uwacom_event(usbwcmp, EVT_BTN, BTN_LEFT, left);
        uwacom_event(usbwcmp, EVT_BTN, BTN_MIDDLE, middle);
        uwacom_event(usbwcmp, EVT_BTN, BTN_RIGHT, right);
        uwacom_event(usbwcmp, EVT_REL, REL_WHEEL, wheel);
        uwacom_event(usbwcmp, EVT_ABS, ABS_DISTANCE, distance);
}

static void
uwacom_tool_events_graphire(usbwcm_state_t *usbwcmp, int idx, int proximity)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        uwacom_event(usbwcmp, EVT_BTN, sc->sc_tool[idx], proximity);
        uwacom_event(usbwcmp, EVT_ABS, ABS_MISC, sc->sc_tool_id[idx]);
        if (sc->sc_serial[idx]) {
                uwacom_event(usbwcmp, EVT_MSC, MSC_SERIAL, sc->sc_serial[idx]);
        }

        uwacom_event(usbwcmp, EVT_SYN, SYN_REPORT, 0);
}

static void
uwacom_pad_events_graphire4(usbwcm_state_t *usbwcmp, int b0, int b1, int b4,
    int b5, int rel, int abs)
{
        uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_0, b0);
        uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_1, b1);
        uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_4, b4);
        uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_5, b5);
        uwacom_event(usbwcmp, EVT_REL, REL_WHEEL, rel);
        uwacom_event(usbwcmp, EVT_ABS, ABS_WHEEL, abs);
        uwacom_tool_events_graphire(usbwcmp, 1, b0 | b1 | b4 | b5 | rel | abs);
}

static void
usbwcm_input_graphire(usbwcm_state_t *usbwcmp, mblk_t *mp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;
        uint8_t *packet = mp->b_rptr;

        if (PACKET_BITS(0, 0, 8) != 0x02) {
                USB_DPRINTF_L1(PRINT_MASK_ALL, usbwcm_log_handle,
                    "unknown report type %02x received\n",
                    PACKET_BITS(0, 0, 8));
                return;
        }

        /* Tool in proximity */
        if (PACKET_BIT(1, 7)) {
                uwacom_pos_events_graphire(usbwcmp,
                    (PACKET_BITS(3, 0, 8) << 8) | PACKET_BITS(2, 0, 8),
                    (PACKET_BITS(5, 0, 8) << 8) | PACKET_BITS(4, 0, 8));

                if (!PACKET_BIT(1, 6)) {
                        if (!PACKET_BIT(1, 5)) {
                                sc->sc_tool[0] = BTN_TOOL_PEN;
                                sc->sc_tool_id[0] = TOOL_ID_PEN;
                        } else {
                                sc->sc_tool[0] = BTN_TOOL_ERASER;
                                sc->sc_tool_id[0] = TOOL_ID_ERASER;
                        }

                        uwacom_pen_events_graphire(usbwcmp,
                            (PACKET_BIT(7, 0) << 8) | PACKET_BITS(6, 0, 8),
                            PACKET_BIT(1, 1), PACKET_BIT(1, 2));
                } else {
                        int wheel, distance;

                        if (sc->sc_type->protocol == GRAPHIRE) {
                                wheel = (PACKET_BIT(1, 5) ?
                                    0 : -(int8_t)PACKET_BITS(6, 0, 8));
                                distance = PACKET_BITS(7, 0, 6);
                        } else {
                                wheel = (PACKET_BIT(7, 2) << 2) -
                                    PACKET_BITS(7, 0, 2);
                                distance = PACKET_BITS(6, 0, 6);
                        }

                        sc->sc_tool[0] = BTN_TOOL_MOUSE;
                        sc->sc_tool_id[0] = TOOL_ID_MOUSE;

                        uwacom_mouse_events_graphire(usbwcmp, PACKET_BIT(1, 0),
                            PACKET_BIT(1, 2), PACKET_BIT(1, 1), wheel,
                            distance);
                }

                uwacom_tool_events_graphire(usbwcmp, 0, 1);

                /* Tool leaving proximity */
        } else if (sc->sc_tool_id[0]) {
                uwacom_pos_events_graphire(usbwcmp, 0, 0);

                if (sc->sc_tool[0] == BTN_TOOL_MOUSE)
                        uwacom_mouse_events_graphire(usbwcmp, 0, 0, 0, 0, 0);
                else
                        uwacom_pen_events_graphire(usbwcmp, 0, 0, 0);

                sc->sc_tool_id[0] = 0;
                uwacom_tool_events_graphire(usbwcmp, 0, 0);
        }

        /* Finger on pad: Graphire4 */
        if ((sc->sc_type->protocol == GRAPHIRE4) && PACKET_BITS(7, 3, 5)) {
                sc->sc_tool_id[1] = TOOL_ID_PAD;
                uwacom_pad_events_graphire4(usbwcmp, PACKET_BIT(7, 6), 0,
                    PACKET_BIT(7, 7), 0,
                    PACKET_BITS(7, 3, 2) - (PACKET_BIT(7, 5) << 2), 0);

        /* Finger on pad: MyOffice */
        } else if ((sc->sc_type->protocol == MYOFFICE) &&
            (PACKET_BITS(7, 3, 4) || PACKET_BITS(8, 0, 8))) {
                sc->sc_tool_id[1] = TOOL_ID_PAD;
                uwacom_pad_events_graphire4(usbwcmp, PACKET_BIT(7, 3),
                    PACKET_BIT(7, 4), PACKET_BIT(7, 5), PACKET_BIT(7, 6), 0,
                    PACKET_BITS(8, 0, 7));

        /* Finger leaving pad */
        } else if (sc->sc_tool_id[1]) {
                sc->sc_tool_id[1] = 0;
                uwacom_pad_events_graphire4(usbwcmp, 0, 0, 0, 0, 0, 0);
        }
}

static void
uwacom_pos_events_intuos(usbwcm_state_t *usbwcmp, int x, int y, int distance)
{
        uwacom_event(usbwcmp, EVT_ABS, ABS_X, x);
        uwacom_event(usbwcmp, EVT_ABS, ABS_Y, y);
        uwacom_event(usbwcmp, EVT_ABS, ABS_DISTANCE, distance);
}

static void
uwacom_pen_events_intuos(usbwcm_state_t *usbwcmp, uint8_t *packet)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;
        int press, tilt_x, tilt_y, stl1, stl2;

        switch (sc->sc_type->protocol) {
        case INTUOS4S:
        case INTUOS4L:
                press = PACKET_BITS(7, 6, 10) << 1 | PACKET_BIT(1, 0);
                break;
        default:
                press = PACKET_BITS(7, 6, 10);
                break;
        }

        tilt_x = PACKET_BITS(8, 7, 7);
        tilt_y = PACKET_BITS(8, 0, 7);
        stl1 = PACKET_BIT(1, 1);
        stl2 = PACKET_BIT(1, 2);

        uwacom_event(usbwcmp, EVT_ABS, ABS_PRESSURE, press);
        uwacom_event(usbwcmp, EVT_ABS, ABS_TILT_X, tilt_x);
        uwacom_event(usbwcmp, EVT_ABS, ABS_TILT_Y, tilt_y);
        uwacom_event(usbwcmp, EVT_BTN, BTN_TIP, press);
        uwacom_event(usbwcmp, EVT_BTN, BTN_STYLUS_1, stl1);
        uwacom_event(usbwcmp, EVT_BTN, BTN_STYLUS_2, stl2);
}

static void
uwacom_mouse_events_intuos(usbwcm_state_t *usbwcmp, uint8_t *packet)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;
        int left, middle, right, extra, side, wheel;

        switch (sc->sc_type->protocol) {
        case INTUOS4S:
        case INTUOS4L:
                left = PACKET_BIT(6, 0);
                middle = PACKET_BIT(6, 1);
                right = PACKET_BIT(6, 2);
                side = PACKET_BIT(6, 3);
                extra = PACKET_BIT(6, 4);
                wheel = PACKET_BIT(7, 7) - PACKET_BIT(7, 6);
                break;

        default:
                left = PACKET_BIT(8, 2);
                middle = PACKET_BIT(8, 3);
                right = PACKET_BIT(8, 4);
                extra = PACKET_BIT(8, 5);
                side = PACKET_BIT(8, 6);
                wheel = PACKET_BIT(8, 0) - PACKET_BIT(8, 1);
                break;
        }

        uwacom_event(usbwcmp, EVT_BTN, BTN_LEFT, left);
        uwacom_event(usbwcmp, EVT_BTN, BTN_MIDDLE, middle);
        uwacom_event(usbwcmp, EVT_BTN, BTN_RIGHT, right);
        uwacom_event(usbwcmp, EVT_BTN, BTN_EXTRA, extra);
        uwacom_event(usbwcmp, EVT_BTN, BTN_SIDE, side);
        uwacom_event(usbwcmp, EVT_REL, REL_WHEEL, wheel);
}

static void
uwacom_tool_events_intuos(usbwcm_state_t *usbwcmp, int idx, int proximity)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        uwacom_event(usbwcmp, EVT_BTN, sc->sc_tool[idx], proximity);
        uwacom_event(usbwcmp, EVT_ABS, ABS_MISC, sc->sc_tool_id[idx]);
        uwacom_event(usbwcmp, EVT_MSC, MSC_SERIAL, sc->sc_serial[idx]);
        uwacom_event(usbwcmp, EVT_SYN, SYN_REPORT, 0);
}

static void
uwacom_pad_events_intuos(usbwcm_state_t *usbwcmp, uint8_t *packet)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;
        int b0, b1, b2, b3, b4, b5, b6, b7;
        int rx, ry, prox;
        int b8, whl, rot;

        switch (sc->sc_type->protocol) {
        case INTUOS4L:
                b7 = PACKET_BIT(3, 6);
                b8 = PACKET_BIT(3, 7);

                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_7, b7);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_8, b8);
        /*FALLTHRU*/
        case INTUOS4S:
                b0 = PACKET_BIT(2, 0);
                b1 = PACKET_BIT(3, 0);
                b2 = PACKET_BIT(3, 1);
                b3 = PACKET_BIT(3, 2);
                b4 = PACKET_BIT(3, 3);
                b5 = PACKET_BIT(3, 4);
                b6 = PACKET_BIT(3, 5);

                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_0, b0);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_1, b1);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_2, b2);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_3, b3);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_4, b4);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_5, b5);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_6, b6);

                whl = PACKET_BIT(1, 7);
                if (whl) {
                        rot = PACKET_BITS(1, 0, 7);
                        uwacom_event(usbwcmp, EVT_ABS, ABS_WHEEL, rot);
                }

                prox = b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8 | whl;
                uwacom_tool_events_intuos(usbwcmp, 1, prox);

                break;

        default:
                b0 = PACKET_BIT(5, 0);
                b1 = PACKET_BIT(5, 1);
                b2 = PACKET_BIT(5, 2);
                b3 = PACKET_BIT(5, 3);
                b4 = PACKET_BIT(6, 0);
                b5 = PACKET_BIT(6, 1);
                b6 = PACKET_BIT(6, 2);
                b7 = PACKET_BIT(6, 3);
                rx = PACKET_BITS(2, 0, 13);
                ry = PACKET_BITS(4, 0, 13);

                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_0, b0);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_1, b1);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_2, b2);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_3, b3);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_4, b4);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_5, b5);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_6, b6);
                uwacom_event(usbwcmp, EVT_BTN, BTN_MISC_7, b7);
                uwacom_event(usbwcmp, EVT_ABS, ABS_RX, rx);
                uwacom_event(usbwcmp, EVT_ABS, ABS_RY, ry);

                prox = b0 | b1 | b2 | b3 | b4 | b5 | b6 | b7 | rx | ry;
                uwacom_tool_events_intuos(usbwcmp, 1, prox);

                break;
        }
}

static void
usbwcm_input_intuos(usbwcm_state_t *usbwcmp, mblk_t *mp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;
        uint8_t *packet = mp->b_rptr;

        switch (PACKET_BITS(0, 0, 8)) {
        case 0x02:
                switch (PACKET_BITS(1, 5, 2)) {
                /* Tool entering proximity */
                case 0x2:
                        sc->sc_tool_id[0] = PACKET_BITS(3, 4, 12);
                        sc->sc_serial[0] =
                            (PACKET_BIT(1, 1) ? PACKET_BITS(7, 4, 32) : 0);

                        switch (sc->sc_tool_id[0]) {
                        case 0x802: /* Intuos4 Grip Pen */
                        case 0x804: /* Intuos4 Art Marker */
                        case 0x823: /* Intuos3 Grip Pen */
                        case 0x885: /* Intuos3 Art Marker */
                                sc->sc_tool[0] = BTN_TOOL_PEN;
                                break;
                        case 0x80a: /* Intuos4 Grip Pen eraser */
                        case 0x82b: /* Intuos3 Grip Pen eraser */
                                sc->sc_tool[0] = BTN_TOOL_ERASER;
                                break;
                        case 0x017: /* Intuos3 2D mouse */
                        case 0x806: /* Intuos4 2D mouse */
                                sc->sc_tool[0] = BTN_TOOL_MOUSE;
                                break;
                        default:
                                USB_DPRINTF_L1(PRINT_MASK_ALL,
                                    usbwcm_log_handle,
                                    "unknown tool ID %03x seen\n",
                                    sc->sc_tool_id[0]);
                                sc->sc_tool[0] = BTN_TOOL_PEN;
                        }
                        break;

                /* Tool leaving proximity */
                case 0x0:
                        uwacom_pos_events_intuos(usbwcmp, 0, 0, 0);

                        if (sc->sc_tool[0] == BTN_TOOL_MOUSE)
                                uwacom_mouse_events_intuos(usbwcmp, packet);
                        else
                                uwacom_pen_events_intuos(usbwcmp, packet);

                        sc->sc_tool_id[0] = 0;
                        uwacom_tool_events_intuos(usbwcmp, 0, 0);
                        break;

                /* Tool motion, outbound */
                case 0x1:
                /* Outbound tracking is unreliable on the Cintiq */
                        if (sc->sc_type->protocol == CINTIQ)
                                break;

                /* Tool motion */
                /*FALLTHRU*/
                case 0x3:
                        uwacom_pos_events_intuos(usbwcmp,
                            (PACKET_BITS(3, 0, 16) << 1) | PACKET_BIT(9, 1),
                            (PACKET_BITS(5, 0, 16) << 1) | PACKET_BIT(9, 0),
                            PACKET_BITS(9, 2, 6));

                        if (PACKET_BITS(1, 3, 2) == 0) {
                                uwacom_pen_events_intuos(usbwcmp, packet);

                        } else if (PACKET_BITS(1, 1, 4) == 0x5) {
                                int angle = 450 - PACKET_BITS(7, 6, 10);

                                if (PACKET_BIT(7, 5)) {
                                        angle = (angle > 0 ? 900 : -900) -
                                            angle;
                                }

                                uwacom_event(usbwcmp, EVT_ABS, ABS_Z, angle);
                                break;
                        } else if (PACKET_BITS(1, 1, 4) == 0x8) {
                                uwacom_mouse_events_intuos(usbwcmp, packet);
                        } else {
                                USB_DPRINTF_L1(PRINT_MASK_ALL,
                                    usbwcm_log_handle,
                                    "unsupported motion packet type %x "
                                    "received\n", PACKET_BITS(1, 1, 4));
                        }

                        uwacom_tool_events_intuos(usbwcmp, 0, 1);
                        break;
                }

                break;

        case 0x0c:
                uwacom_pad_events_intuos(usbwcmp, packet);
                break;

        default:
                USB_DPRINTF_L1(PRINT_MASK_ALL, usbwcm_log_handle,
                    "unknown report type %02x received\n",
                    PACKET_BITS(0, 0, 8));
        }
}

static void
uwacom_init_abs(usbwcm_state_t *usbwcmp, int axis, int32_t min, int32_t max,
    int32_t fuzz, int32_t flat)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        sc->sc_abs[axis].min = min;
        sc->sc_abs[axis].max = max;
        sc->sc_abs[axis].fuzz = fuzz;
        sc->sc_abs[axis].flat = flat;
}

static void
uwacom_init_graphire4(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[0], EVT_MSC);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_0);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_4);
        BM_SET_BIT(sc->sc_bm[1], BTN_TOOL_PAD);
        BM_SET_BIT(sc->sc_bm[4], MSC_SERIAL);

        sc->sc_tool[1] = BTN_TOOL_PAD;
        sc->sc_serial[1] = SERIAL_PAD_GRAPHIRE4;
}

static void
uwacom_init_myoffice(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_1);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_5);
        BM_SET_BIT(sc->sc_bm[3], ABS_WHEEL);

        uwacom_init_abs(usbwcmp, ABS_WHEEL, 0, 71, 0, 0);
}

static void
uwacom_init_intuos(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[0], EVT_MSC);

        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_0);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_1);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_2);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_3);
        BM_SET_BIT(sc->sc_bm[1], BTN_SIDE);
        BM_SET_BIT(sc->sc_bm[1], BTN_EXTRA);
        BM_SET_BIT(sc->sc_bm[1], BTN_TOOL_PAD);

        BM_SET_BIT(sc->sc_bm[3], ABS_TILT_X);
        BM_SET_BIT(sc->sc_bm[3], ABS_TILT_Y);

        BM_SET_BIT(sc->sc_bm[4], MSC_SERIAL);

        sc->sc_tool[1] = BTN_TOOL_PAD;
        sc->sc_tool_id[1] = TOOL_ID_PAD;
        sc->sc_serial[1] = SERIAL_PAD_INTUOS;
}

static void
uwacom_init_intuos3(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[3], ABS_Z);
        BM_SET_BIT(sc->sc_bm[3], ABS_RX);

        uwacom_init_abs(usbwcmp, ABS_Z, -900,  899, 0, 0);
        uwacom_init_abs(usbwcmp, ABS_RX, 0, 4096, 0, 0);
}

static void
uwacom_init_intuos3_large(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_4);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_5);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_6);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_7);

        BM_SET_BIT(sc->sc_bm[3], ABS_RY);

        uwacom_init_abs(usbwcmp, ABS_RY, 0, 4096, 0, 0);
}

static void
uwacom_init_intuos4(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_4);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_5);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_6);

        BM_SET_BIT(sc->sc_bm[3], ABS_Z);

        uwacom_init_abs(usbwcmp, ABS_Z, -900,  899, 0, 0);
}
static void
uwacom_init_intuos4_large(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_7);
        BM_SET_BIT(sc->sc_bm[1], BTN_MISC_8);
}

static int
uwacom_init(usbwcm_state_t *usbwcmp)
{
        struct uwacom_softc *sc = &usbwcmp->usbwcm_softc;

        sc->sc_id.bus = ID_BUS_USB;
        sc->sc_id.vendor = usbwcmp->usbwcm_devid.VendorId;
        sc->sc_id.product = usbwcmp->usbwcm_devid.ProductId;

        sc->sc_id.version = 0;

        for (int i = 0; i < EVT_USED; ++i)
                sc->sc_bm[i] = kmem_zalloc(bm_size[i], KM_SLEEP);

        sc->sc_btn = kmem_zalloc(BTN_USED * sizeof (int), KM_SLEEP);
        sc->sc_abs = kmem_zalloc(ABS_USED * sizeof (struct event_abs_axis),
            KM_SLEEP);

        BM_SET_BIT(sc->sc_bm[0], EVT_SYN);
        BM_SET_BIT(sc->sc_bm[0], EVT_BTN);
        BM_SET_BIT(sc->sc_bm[0], EVT_REL);
        BM_SET_BIT(sc->sc_bm[0], EVT_ABS);

        BM_SET_BIT(sc->sc_bm[1], BTN_LEFT);
        BM_SET_BIT(sc->sc_bm[1], BTN_RIGHT);
        BM_SET_BIT(sc->sc_bm[1], BTN_MIDDLE);
        BM_SET_BIT(sc->sc_bm[1], BTN_TOOL_PEN);
        BM_SET_BIT(sc->sc_bm[1], BTN_TOOL_ERASER);
        BM_SET_BIT(sc->sc_bm[1], BTN_TOOL_MOUSE);
        BM_SET_BIT(sc->sc_bm[1], BTN_TIP);
        BM_SET_BIT(sc->sc_bm[1], BTN_STYLUS_1);
        BM_SET_BIT(sc->sc_bm[1], BTN_STYLUS_2);

        BM_SET_BIT(sc->sc_bm[2], REL_WHEEL);

        BM_SET_BIT(sc->sc_bm[3], ABS_X);
        BM_SET_BIT(sc->sc_bm[3], ABS_Y);
        BM_SET_BIT(sc->sc_bm[3], ABS_PRESSURE);
        BM_SET_BIT(sc->sc_bm[3], ABS_DISTANCE);
        BM_SET_BIT(sc->sc_bm[3], ABS_MISC);

        uwacom_init_abs(usbwcmp, ABS_X, 0, sc->sc_type->x_max, 4, 0);
        uwacom_init_abs(usbwcmp, ABS_Y, 0, sc->sc_type->y_max, 4, 0);
        uwacom_init_abs(usbwcmp, ABS_PRESSURE, 0, sc->sc_type->pressure_max,
            0, 0);
        uwacom_init_abs(usbwcmp, ABS_DISTANCE, 0,
            uwacom_protocols[sc->sc_type->protocol].distance_max, 0, 0);

        switch (sc->sc_type->protocol) {
                case CINTIQ:
                case INTUOS3L:
                        uwacom_init_intuos3_large(usbwcmp);
                /*FALLTHRU*/
                case INTUOS3S:
                        uwacom_init_intuos3(usbwcmp);
                        uwacom_init_intuos(usbwcmp);
                        break;

                case INTUOS4L:
                        uwacom_init_intuos4_large(usbwcmp);
                /*FALLTHRU*/
                case INTUOS4S:
                        uwacom_init_intuos4(usbwcmp);
                        uwacom_init_intuos(usbwcmp);
                        break;
                case MYOFFICE:
                        uwacom_init_myoffice(usbwcmp);
                /*FALLTHRU*/
                case GRAPHIRE4:
                        uwacom_init_graphire4(usbwcmp);
                /*FALLTHRU*/
                case GRAPHIRE:
                        break;
        }

        return (0);
}

/*
 * usbwcm_match() :
 *      Match device with it's parameters.
 */
static const struct uwacom_type *
usbwcm_match(uint16_t vid, uint16_t pid)
{
        const struct uwacom_type *dev;

        dev = uwacom_devs;
        while (dev->devno.vid != 0 && dev->devno.pid != 0) {
                if (dev->devno.vid == vid && dev->devno.pid == pid) {
                        return (dev);
                }
                dev++;
        }

        return (NULL);
}

/*
 * usbwcm_probe() :
 *      Check the device type and protocol.
 */
static int
usbwcm_probe(usbwcm_state_t *usbwcmp)
{
        queue_t         *q = usbwcmp->usbwcm_rq;
        mblk_t          *mctl_ptr;
        struct iocblk   mctlmsg;
        hid_req_t       *featr;

        /* check device IDs */
        mctlmsg.ioc_cmd = HID_GET_VID_PID;
        mctlmsg.ioc_count = 0;

        mctl_ptr = usba_mk_mctl(mctlmsg, NULL, 0);
        if (mctl_ptr == NULL) {
                return (ENOMEM);
        }

        putnext(usbwcmp->usbwcm_wq, mctl_ptr);
        usbwcmp->usbwcm_flags |= USBWCM_QWAIT;

        while (usbwcmp->usbwcm_flags & USBWCM_QWAIT) {
                if (qwait_sig(q) == 0) {
                        usbwcmp->usbwcm_flags = 0;
                        return (EINTR);
                }
        }

        usbwcmp->usbwcm_softc.sc_type =
            usbwcm_match(usbwcmp->usbwcm_devid.VendorId,
            usbwcmp->usbwcm_devid.ProductId);
        if (!usbwcmp->usbwcm_softc.sc_type) {
                USB_DPRINTF_L1(PRINT_MASK_ALL, usbwcm_log_handle,
                    "unsupported tablet model\n");
                return (ENXIO);
        }

        if (uwacom_init(usbwcmp) != 0) {
                return (ENXIO);
        }

        /* set feature: tablet mode */
        featr = kmem_zalloc(sizeof (hid_req_t), KM_SLEEP);
        featr->hid_req_version_no = HID_VERSION_V_0;
        featr->hid_req_wValue = REPORT_TYPE_FEATURE | 2;
        featr->hid_req_wLength = sizeof (uint8_t) * 2;
        featr->hid_req_data[0] = 2;
        featr->hid_req_data[1] = 2;

        mctlmsg.ioc_cmd = HID_SET_REPORT;
        mctlmsg.ioc_count = sizeof (featr);
        mctl_ptr = usba_mk_mctl(mctlmsg, featr, sizeof (hid_req_t));
        if (mctl_ptr != NULL) {
                putnext(usbwcmp->usbwcm_wq, mctl_ptr);

                /*
                 * Waiting for response of HID_SET_REPORT
                 * mctl for setting the feature.
                 */
                usbwcmp->usbwcm_flags |= USBWCM_QWAIT;
                while (usbwcmp->usbwcm_flags & USBWCM_QWAIT) {
                        qwait(q);
                }
        } else {
                USB_DPRINTF_L1(PRINT_MASK_ALL, usbwcm_log_handle,
                    "enable tablet mode failed\n");
        }

        kmem_free(featr, sizeof (hid_req_t));

        return (0);
}

/*
 * usbwcm_copyreq() :
 *      helper function for usbwcm ioctls
 */
static int
usbwcm_copyreq(mblk_t *mp, uint_t pvtsize, uint_t state, uint_t reqsize,
    uint_t contsize, uint_t copytype)
{
        usbwcm_copyin_t *copystat;
        mblk_t          *iocmp, *contmp;
        struct copyreq  *cq;
        struct copyresp *cr;

        if ((pvtsize == 0) && (state != 0)) {
                cr = (struct copyresp *)mp->b_rptr;
                iocmp = cr->cp_private;
        }

        cq = (struct copyreq *)mp->b_rptr;
        if (mp->b_cont == NULL) {

                return (EINVAL);
        }

        cq->cq_addr = *((caddr_t *)mp->b_cont->b_rptr);
        cq->cq_size = reqsize;
        cq->cq_flag = 0;

        if (pvtsize) {
                iocmp = (mblk_t *)allocb(pvtsize, BPRI_MED);
                if (iocmp == NULL) {

                        return (EAGAIN);
                }
                cq->cq_private = iocmp;
                iocmp = cq->cq_private;
        } else {
                /*
                 * Here we need to set cq_private even if there's
                 * no private data, otherwise its value will be
                 * TRANSPARENT (-1) on 64bit systems because it
                 * overlaps iocp->ioc_count. If user address (cq_addr)
                 * is invalid, it would cause panic later in
                 * usbwcm_copyin:
                 *      freemsg((mblk_t *)copyresp->cp_private);
                 */
                cq->cq_private = NULL;
        }

        if (state) {
                copystat = (usbwcm_copyin_t *)iocmp->b_rptr;
                copystat->state = state;
                if (pvtsize) {  /* M_COPYIN */
                        copystat->addr = cq->cq_addr;
                } else {
                        cq->cq_addr = copystat->addr;
                        cq->cq_private = iocmp;
                }
                iocmp->b_wptr = iocmp->b_rptr + sizeof (usbwcm_copyin_t);
        }

        if (contsize) {
                contmp = (mblk_t *)allocb(contsize, BPRI_MED);
                if (contmp == NULL) {

                        return (EAGAIN);
                }
                if (mp->b_cont) {
                        freemsg(mp->b_cont);
                        mp->b_cont = contmp;
                }
        }

        mp->b_datap->db_type = (unsigned char)copytype;
        mp->b_wptr = mp->b_rptr + sizeof (struct copyreq);

        return (0);
}

static void
usbwcm_miocack(queue_t *q, mblk_t *mp, int rval)
{
        struct iocblk   *iocbp = (struct iocblk *)mp->b_rptr;

        mp->b_datap->db_type = M_IOCACK;
        mp->b_wptr = mp->b_rptr + sizeof (struct iocblk);

        iocbp->ioc_error = 0;
        iocbp->ioc_count = 0;
        iocbp->ioc_rval = rval;

        if (mp->b_cont != NULL) {
                freemsg(mp->b_cont);
                mp->b_cont = NULL;
        }

        qreply(q, mp);
}

/*
 * usbwcm_iocpy() :
 * M_IOCDATA processing for IOCTL's
 */
static void
usbwcm_iocpy(queue_t *q, mblk_t *mp)
{
        usbwcm_state_t          *usbwcmp = (usbwcm_state_t *)q->q_ptr;
        struct uwacom_softc     *sc = &usbwcmp->usbwcm_softc;
        struct copyresp         *copyresp;
        usbwcm_copyin_t         *copystat;
        mblk_t                  *datap, *ioctmp;
        struct iocblk           *iocbp;
        int                     err = 0;

        copyresp = (struct copyresp *)mp->b_rptr;
        iocbp = (struct iocblk *)mp->b_rptr;
        if (copyresp->cp_rval) {
                err = EAGAIN;

                goto out;
        }

        switch (copyresp->cp_cmd) {
        default: {
                int num = copyresp->cp_cmd & 0xff;
                int len = IOCPARM_MASK & (copyresp->cp_cmd >> 16);

                if (((copyresp->cp_cmd >> 8) & 0xFF) != 'E') {
                        putnext(q, mp); /* pass it down the line */
                        return;

                } else if ((copyresp->cp_cmd & IOC_INOUT) != IOC_OUT) {
                        err = EINVAL;
                        break;
                }

                switch (num) {
                case EUWACOMGETVERSION:
                        ioctmp = copyresp->cp_private;
                        copystat = (usbwcm_copyin_t *)ioctmp->b_rptr;
                        if (copystat->state == USBWCM_GETSTRUCT) {
                                if (mp->b_cont == NULL) {
                                        err = EINVAL;

                                        break;
                                }
                                datap = (mblk_t *)mp->b_cont;

                                *(int *)datap->b_rptr = 0x00010000;

                                if (err = usbwcm_copyreq(mp, 0,
                                    USBWCM_GETRESULT, sizeof (int), 0,
                                    M_COPYOUT)) {

                                        goto out;
                                }
                        } else if (copystat->state == USBWCM_GETRESULT) {
                                freemsg(ioctmp);
                                usbwcm_miocack(q, mp, 0);
                                return;
                        }
                        break;

                case EUWACOMGETID:
                        ioctmp = copyresp->cp_private;
                        copystat = (usbwcm_copyin_t *)ioctmp->b_rptr;
                        if (copystat->state == USBWCM_GETSTRUCT) {
                                if (mp->b_cont == NULL) {
                                        err = EINVAL;

                                        break;
                                }
                                datap = (mblk_t *)mp->b_cont;

                                bcopy(&sc->sc_id, datap->b_rptr,
                                    sizeof (struct event_dev_id));

                                if (err = usbwcm_copyreq(mp, 0,
                                    USBWCM_GETRESULT,
                                    sizeof (struct event_dev_id), 0,
                                    M_COPYOUT)) {

                                        goto out;
                                }
                        } else if (copystat->state == USBWCM_GETRESULT) {
                                freemsg(ioctmp);
                                usbwcm_miocack(q, mp, 0);
                                return;
                        }
                        break;

                default:
                        if (num >= EUWACOMGETBM &&
                            num < EUWACOMGETBM + EVT_USED) {
                                int idx = num - EUWACOMGETBM;
                                size_t length = min(bm_size[idx], len);

                                ioctmp = copyresp->cp_private;
                                copystat = (usbwcm_copyin_t *)ioctmp->b_rptr;
                                if (copystat->state == USBWCM_GETSTRUCT) {
                                        if (mp->b_cont == NULL) {
                                                err = EINVAL;

                                                break;
                                        }
                                        datap = (mblk_t *)mp->b_cont;

                                        bcopy(sc->sc_bm[idx], datap->b_rptr,
                                            length);

                                        if (err = usbwcm_copyreq(mp, 0,
                                            USBWCM_GETRESULT, length, 0,
                                            M_COPYOUT)) {

                                                goto out;
                                        }

                                } else if (copystat->state ==
                                    USBWCM_GETRESULT) {
                                        freemsg(ioctmp);
                                        usbwcm_miocack(q, mp, length);
                                        return;
                                }
                                break;

                        } else if (num >= EUWACOMGETABS &&
                            num < EUWACOMGETABS + ABS_USED) {
                                int idx = num - EUWACOMGETABS;

                                ioctmp = copyresp->cp_private;
                                copystat = (usbwcm_copyin_t *)ioctmp->b_rptr;
                                if (copystat->state == USBWCM_GETSTRUCT) {
                                        if (mp->b_cont == NULL) {
                                                err = EINVAL;

                                                break;
                                        }
                                        datap = (mblk_t *)mp->b_cont;

                                        bcopy(&sc->sc_abs[idx], datap->b_rptr,
                                            sizeof (struct event_abs_axis));

                                        if (err = usbwcm_copyreq(mp, 0,
                                            USBWCM_GETRESULT,
                                            sizeof (struct event_abs_axis), 0,
                                            M_COPYOUT)) {

                                                goto out;
                                        }

                                } else if (copystat->state ==
                                    USBWCM_GETRESULT) {
                                        freemsg(ioctmp);
                                        usbwcm_miocack(q, mp, 0);
                                        return;
                                }
                                break;

                        } else {
                                err = EINVAL;
                                break;
                        }
                }
        }
        }

out:
        if (err) {
                mp->b_datap->db_type = M_IOCNAK;
                if (mp->b_cont) {
                        freemsg(mp->b_cont);
                        mp->b_cont = (mblk_t *)NULL;
                }
                if (copyresp->cp_private) {
                        freemsg((mblk_t *)copyresp->cp_private);
                        copyresp->cp_private = (mblk_t *)NULL;
                }
                iocbp->ioc_count = 0;
                iocbp->ioc_error = err;
        }

        qreply(q, mp);
}

/*
 * usbwcm_ioctl() :
 *      Process ioctls we recognize and own.  Otherwise, NAK.
 */
static void
usbwcm_ioctl(queue_t *q, mblk_t *mp)
{
        usbwcm_state_t          *usbwcmp = (usbwcm_state_t *)q->q_ptr;
        struct uwacom_softc     *sc;
        mblk_t                  *datap;
        struct iocblk           *iocp;
        int                     err = 0;

        if (usbwcmp == NULL) {
                miocnak(q, mp, 0, EINVAL);
                return;
        }

        sc = &usbwcmp->usbwcm_softc;
        iocp = (struct iocblk *)mp->b_rptr;

        switch (iocp->ioc_cmd) {
        default: {
                int num = iocp->ioc_cmd & 0xff;
                int len = IOCPARM_MASK & (iocp->ioc_cmd >> 16);

                if (((iocp->ioc_cmd >> 8) & 0xFF) != 'E') {
                        putnext(q, mp); /* pass it down the line */
                        return;

                } else if ((iocp->ioc_cmd & IOC_INOUT) != IOC_OUT) {
                        err = EINVAL;
                        break;
                }

                switch (num) {
                case EUWACOMGETVERSION:
                        if (iocp->ioc_count == TRANSPARENT) {
                                if (err = usbwcm_copyreq(mp,
                                    sizeof (usbwcm_copyin_t), USBWCM_GETSTRUCT,
                                    sizeof (int), 0, M_COPYIN)) {
                                        break;
                                }
                                freemsg(mp->b_cont);
                                mp->b_cont = (mblk_t *)NULL;

                                qreply(q, mp);
                                return;
                        }

                        if (mp->b_cont == NULL ||
                            iocp->ioc_count != sizeof (int)) {
                                err = EINVAL;
                                break;
                        }
                        datap = mp->b_cont;

                        *(int *)datap->b_rptr = 0x00010000;

                        break;

                case EUWACOMGETID:
                        if (iocp->ioc_count == TRANSPARENT) {
                                if (err = usbwcm_copyreq(mp,
                                    sizeof (usbwcm_copyin_t), USBWCM_GETSTRUCT,
                                    sizeof (struct event_dev_id), 0,
                                    M_COPYIN)) {
                                        break;
                                }
                                freemsg(mp->b_cont);
                                mp->b_cont = (mblk_t *)NULL;

                                qreply(q, mp);
                                return;
                        }

                        if (mp->b_cont == NULL ||
                            iocp->ioc_count != sizeof (struct event_dev_id)) {
                                err = EINVAL;
                                break;
                        }
                        datap = mp->b_cont;

                        bcopy(&sc->sc_id, datap->b_rptr,
                            sizeof (struct event_dev_id));

                        break;

                default:
                        if (num >= EUWACOMGETBM &&
                            num < EUWACOMGETBM + EVT_USED) {
                                int idx = num - EUWACOMGETBM;
                                size_t length = min(bm_size[idx], len);

                                if (iocp->ioc_count == TRANSPARENT) {
                                        if (err = usbwcm_copyreq(mp,
                                            sizeof (usbwcm_copyin_t),
                                            USBWCM_GETSTRUCT, length, 0,
                                            M_COPYIN)) {
                                                break;
                                        }
                                        freemsg(mp->b_cont);
                                        mp->b_cont = (mblk_t *)NULL;

                                        qreply(q, mp);
                                        return;
                                }

                                if (mp->b_cont == NULL ||
                                    iocp->ioc_count != length) {
                                        err = EINVAL;
                                        break;
                                }
                                datap = mp->b_cont;

                                bcopy(sc->sc_bm[idx], datap->b_rptr, length);

                                break;

                        } else if (num >= EUWACOMGETABS &&
                            num < EUWACOMGETABS + ABS_USED) {
                                int idx = num - EUWACOMGETABS;

                                if (iocp->ioc_count == TRANSPARENT) {
                                        if (err = usbwcm_copyreq(mp,
                                            sizeof (usbwcm_copyin_t),
                                            USBWCM_GETSTRUCT,
                                            sizeof (struct event_abs_axis), 0,
                                            M_COPYIN)) {
                                                break;
                                        }
                                        freemsg(mp->b_cont);
                                        mp->b_cont = (mblk_t *)NULL;

                                        qreply(q, mp);
                                        return;
                                }

                                if (mp->b_cont == NULL ||
                                    iocp->ioc_count !=
                                    sizeof (struct event_abs_axis)) {
                                        err = EINVAL;
                                        break;
                                }
                                datap = mp->b_cont;

                                bcopy(&sc->sc_abs[idx], datap->b_rptr,
                                    sizeof (struct event_abs_axis));

                                break;

                        } else {
                                err = EINVAL;
                                break;
                        }
                }
        }
        }

        if (err != 0)
                miocnak(q, mp, 0, err);
        else {
                iocp->ioc_rval = 0;
                iocp->ioc_error = 0;
                mp->b_datap->db_type = M_IOCACK;
                qreply(q, mp);
                /* REMOVE */
        }

        return;

}

/*
 * usbwcm_input() :
 *
 *      Wacom input routine; process data received from a device and
 *      assemble into a input event for the window system.
 *
 *      Watch out for overflow!
 */
static void
usbwcm_input(usbwcm_state_t *usbwcmp, mblk_t *mp)
{
        struct uwacom_softc     *sc = &usbwcmp->usbwcm_softc;

        switch (sc->sc_type->protocol) {
        case GRAPHIRE:
        case GRAPHIRE4:
        case MYOFFICE:
                usbwcm_input_graphire(usbwcmp, mp);
                break;

        case INTUOS3S:
        case INTUOS3L:
        case INTUOS4S:
        case INTUOS4L:
        case CINTIQ:
                usbwcm_input_intuos(usbwcmp, mp);
                break;
        }
}

/*
 * usbwcm_flush() :
 *      Resets the soft state to default values
 *      and sends M_FLUSH above.
 */
static void
usbwcm_flush(usbwcm_state_t *usbwcmp)
{
        queue_t                 *q;

        if ((q = usbwcmp->usbwcm_rq) != NULL && q->q_next != NULL) {
                (void) putnextctl1(q, M_FLUSH, FLUSHR);
        }
}

/*
 * usbwcm_mctl() :
 *      Handle M_CTL messages from hid.  If
 *      we don't understand the command, free message.
 */
static void
usbwcm_mctl(queue_t *q, mblk_t *mp)
{
        usbwcm_state_t  *usbwcmp = (usbwcm_state_t *)q->q_ptr;
        struct iocblk   *iocp;
        caddr_t         data = NULL;
        struct iocblk   mctlmsg;
        mblk_t          *mctl_ptr;
        hid_req_t       *featr;

        iocp = (struct iocblk *)mp->b_rptr;
        if (mp->b_cont != NULL)
                data = (caddr_t)mp->b_cont->b_rptr;

        switch (iocp->ioc_cmd) {
        case HID_GET_VID_PID:
                if ((data != NULL) &&
                    (iocp->ioc_count == sizeof (hid_vid_pid_t)) &&
                    (MBLKL(mp->b_cont) == iocp->ioc_count)) {
                        bcopy(data, &usbwcmp->usbwcm_devid, iocp->ioc_count);
                }

                freemsg(mp);
                usbwcmp->usbwcm_flags &= ~USBWCM_QWAIT;
                break;

        case HID_CONNECT_EVENT:
                /* set feature: tablet mode */
                featr = kmem_zalloc(sizeof (hid_req_t), KM_SLEEP);
                featr->hid_req_version_no = HID_VERSION_V_0;
                featr->hid_req_wValue = REPORT_TYPE_FEATURE | 2;
                featr->hid_req_wLength = sizeof (uint8_t) * 2;
                featr->hid_req_data[0] = 2;
                featr->hid_req_data[1] = 2;

                mctlmsg.ioc_cmd = HID_SET_REPORT;
                mctlmsg.ioc_count = sizeof (featr);
                mctl_ptr = usba_mk_mctl(mctlmsg, featr, sizeof (hid_req_t));
                if (mctl_ptr != NULL) {
                        putnext(usbwcmp->usbwcm_wq, mctl_ptr);
                } else {
                USB_DPRINTF_L1(PRINT_MASK_ALL, usbwcm_log_handle,
                    "enable tablet mode failed\n");
                }

                kmem_free(featr, sizeof (hid_req_t));
                freemsg(mp);
                break;

        case HID_SET_REPORT:
                /* FALLTHRU */

        case HID_SET_PROTOCOL:
                usbwcmp->usbwcm_flags &= ~USBWCM_QWAIT;
                /* FALLTHRU */

        default:
                freemsg(mp);
        }
}

/*
 * usbwcm_open() :
 *      open() entry point for the USB wacom module.
 */
/*ARGSUSED*/
static int
usbwcm_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
{
        usbwcm_state_t  *usbwcmp;

        /* Clone opens are not allowed */
        if (sflag != MODOPEN)
                return (EINVAL);

        /* If the module is already open, just return */
        if (q->q_ptr) {
                return (0);
        }

        /* allocate usbwcm state structure */
        usbwcmp = kmem_zalloc(sizeof (usbwcm_state_t), KM_SLEEP);

        q->q_ptr = usbwcmp;
        WR(q)->q_ptr = usbwcmp;

        usbwcmp->usbwcm_rq = q;
        usbwcmp->usbwcm_wq = WR(q);

        qprocson(q);

        if (usbwcm_probe(usbwcmp) != 0) {

                qprocsoff(q);
                kmem_free(usbwcmp, sizeof (usbwcm_state_t));

                return (EINVAL);
        }

        usbwcm_flush(usbwcmp);

        usbwcmp->usbwcm_flags |= USBWCM_OPEN;
        return (0);
}


/*
 * usbwcm_close() :
 *      close() entry point for the USB wacom module.
 */
/*ARGSUSED*/
static int
usbwcm_close(queue_t *q, int flag, cred_t *credp)
{
        usbwcm_state_t          *usbwcmp = q->q_ptr;
        struct uwacom_softc     *sc = &usbwcmp->usbwcm_softc;

        qprocsoff(q);

        if (usbwcmp->usbwcm_bufcall) {
                qunbufcall(q, (bufcall_id_t)(long)usbwcmp->usbwcm_bufcall);
                usbwcmp->usbwcm_bufcall = 0;
        }

        if (usbwcmp->usbwcm_mioctl != NULL) {
                /*
                 * We were holding an "ioctl" response pending the
                 * availability of an "mblk" to hold data to be passed up;
                 * another "ioctl" came through, which means that "ioctl"
                 * must have timed out or been aborted.
                 */
                freemsg(usbwcmp->usbwcm_mioctl);
                usbwcmp->usbwcm_mioctl = NULL;
        }

        for (int i = 0; i < EVT_USED; i++)
                kmem_free(sc->sc_bm[i], bm_size[i]);

        kmem_free(sc->sc_btn, BTN_USED * sizeof (int));
        kmem_free(sc->sc_abs, ABS_USED * sizeof (struct event_abs_axis));
        kmem_free(usbwcmp, sizeof (usbwcm_state_t));

        q->q_ptr = WR(q)->q_ptr = NULL;
        return (0);
}

/*
 * usbwcm_wput() :
 *      wput() routine for the wacom module.
 *      Module below : hid, module above : consms
 */
static int
usbwcm_wput(queue_t *q, mblk_t *mp)
{
        switch (mp->b_datap->db_type) {

        case M_FLUSH:  /* Canonical flush handling */
                if (*mp->b_rptr & FLUSHW) {
                        flushq(q, FLUSHDATA);
                }
                if (*mp->b_rptr & FLUSHR) {
                        flushq(RD(q), FLUSHDATA);
                }
                putnext(q, mp); /* pass it down the line. */
                break;

        case M_IOCTL:
                usbwcm_ioctl(q, mp);
                break;

        case M_IOCDATA:
                usbwcm_iocpy(q, mp);
                break;

        default:
                putnext(q, mp); /* pass it down the line. */
        }

        return (0);
}

/*
 * usbwcm_rput() :
 *      Put procedure for input from driver end of stream (read queue).
 */
static int
usbwcm_rput(queue_t *q, mblk_t *mp)
{
        usbwcm_state_t          *usbwcmp = q->q_ptr;
        struct uwacom_softc     *sc = &usbwcmp->usbwcm_softc;
        mblk_t                  *mp0 = mp;
        int                     limit;

        if (usbwcmp == 0) {
                freemsg(mp);    /* nobody's listening */
                return (0);
        }

        switch (mp->b_datap->db_type) {
        case M_FLUSH:
                if (*mp->b_rptr & FLUSHW)
                        flushq(WR(q), FLUSHDATA);

                if (*mp->b_rptr & FLUSHR)
                        flushq(q, FLUSHDATA);

                freemsg(mp);
                return (0);

        case M_BREAK:
                /*
                 * We don't have to handle this
                 * because nothing is sent from the downstream
                 */
                freemsg(mp);
                return (0);

        case M_DATA:
                if (!(usbwcmp->usbwcm_flags & USBWCM_OPEN)) {
                        freemsg(mp);    /* not ready to listen */

                        return (0);
                }

                /*
                 * Make sure there are at least "limit" number of bytes.
                 */
                limit = uwacom_protocols[sc->sc_type->protocol].packet_size;
                if (MBLKL(mp0) == limit) {      /* REMOVE */
                        do {
                                /* REMOVE */
                                usbwcm_input(usbwcmp, mp0);
                                mp0 = mp0->b_cont;
                        } while (mp0 != NULL);   /* next block, if any */
                }

                freemsg(mp);
                break;

        case M_CTL:
                usbwcm_mctl(q, mp);
                return (0);

        case M_ERROR:
                /* REMOVE */
                usbwcmp->usbwcm_flags &= ~USBWCM_QWAIT;

                freemsg(mp);
                return (0);
        default:
                putnext(q, mp);
                return (0);
        }
        return (0);
}


static struct module_info modinfo;

/* STREAMS entry points */

/* read side queue */
static struct qinit rinit = {
        .qi_putp = usbwcm_rput,
        .qi_srvp = NULL,
        .qi_qopen = usbwcm_open,
        .qi_qclose = usbwcm_close,
        .qi_qadmin = NULL,
        .qi_minfo = &modinfo,
        .qi_mstat = NULL
};

/* write side queue */
static struct qinit winit = {
        .qi_putp = usbwcm_wput,
        .qi_srvp = NULL,
        .qi_qopen = NULL,
        .qi_qclose = NULL,
        .qi_qadmin = NULL,
        .qi_minfo = &modinfo,
        .qi_mstat = NULL
};

/* STREAMS table */
static struct streamtab strtab = {
        &rinit,
        &winit,
        NULL,                   /* not a MUX */
        NULL                    /* not a MUX */
};

/* Module linkage information */

static struct fmodsw modsw = {
        "usbwcm",
        &strtab,
        D_MP | D_MTPERMOD
};


static struct modlstrmod modlstr = {
        &mod_strmodops,
        "USB Wacom STRMOD",
        &modsw
};

static struct modlinkage modlink = {
        MODREV_1,
        (void *)&modlstr,
        NULL
};

static struct module_info modinfo = {
        0x0ffff,                /* module id number */
        "usbwcm",               /* module name */
        0,                      /* min packet size accepted */
        INFPSZ,                 /* max packet size accepted */
        512,                    /* hi-water mark */
        128                     /* lo-water mark */
};


/* Module entry points */

int
_init(void)
{
        int rval = mod_install(&modlink);

        if (rval == 0) {
                usbwcm_log_handle = usb_alloc_log_hdl(NULL, "usbwcm",
                    &usbwcm_errlevel, &usbwcm_errmask, NULL, 0);
        }

        return (rval);
}

int
_fini(void)
{
        int rval = mod_remove(&modlink);

        if (rval == 0) {
                usb_free_log_hdl(usbwcm_log_handle);
        }

        return (rval);
}

int
_info(struct modinfo *modinfop)
{

        return (mod_info(&modlink, modinfop));
}