root/usr.sbin/bhyve/usb_mouse.c
/*-
 * SPDX-License-Identifier: BSD-2-Clause
 *
 * Copyright (c) 2014 Leon Dang <ldang@nahannisys.com>
 * All rights reserved.
 *
 * 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.
 */

#include <sys/cdefs.h>
#include <sys/time.h>

#include <machine/vmm_snapshot.h>

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>

#include "usb_emul.h"
#include "console.h"
#include "bhyvegc.h"
#include "debug.h"

static int umouse_debug = 0;
#define DPRINTF(params) if (umouse_debug) PRINTLN params
#define WPRINTF(params) PRINTLN params

/* USB endpoint context (1-15) for reporting mouse data events*/
#define UMOUSE_INTR_ENDPT       1

#define UMOUSE_REPORT_DESC_TYPE 0x22

#define UMOUSE_GET_REPORT       0x01
#define UMOUSE_GET_IDLE         0x02
#define UMOUSE_GET_PROTOCOL     0x03
#define UMOUSE_SET_REPORT       0x09
#define UMOUSE_SET_IDLE         0x0A
#define UMOUSE_SET_PROTOCOL     0x0B

#define HSETW(ptr, val)   ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }

enum {
        UMSTR_LANG,
        UMSTR_MANUFACTURER,
        UMSTR_PRODUCT,
        UMSTR_SERIAL,
        UMSTR_CONFIG,
        UMSTR_MAX
};

static const char *umouse_desc_strings[] = {
        "\x09\x04",
        "BHYVE",
        "HID Tablet",
        "01",
        "HID Tablet Device",
};

struct umouse_hid_descriptor {
        uint8_t bLength;
        uint8_t bDescriptorType;
        uint8_t bcdHID[2];
        uint8_t bCountryCode;
        uint8_t bNumDescriptors;
        uint8_t bReportDescriptorType;
        uint8_t wItemLength[2];
} __packed;

struct umouse_config_desc {
        struct usb_config_descriptor            confd;
        struct usb_interface_descriptor         ifcd;
        struct umouse_hid_descriptor            hidd;
        struct usb_endpoint_descriptor          endpd;
        struct usb_endpoint_ss_comp_descriptor  sscompd;
} __packed;

#define MOUSE_MAX_X     0x8000
#define MOUSE_MAX_Y     0x8000

static const uint8_t umouse_report_desc[] = {
        0x05, 0x01,             /* USAGE_PAGE (Generic Desktop)         */
        0x09, 0x02,             /* USAGE (Mouse)                        */
        0xa1, 0x01,             /* COLLECTION (Application)             */
        0x09, 0x01,             /*   USAGE (Pointer)                    */
        0xa1, 0x00,             /*   COLLECTION (Physical)              */
        0x05, 0x09,             /*     USAGE_PAGE (Button)              */
        0x19, 0x01,             /*     USAGE_MINIMUM (Button 1)         */
        0x29, 0x03,             /*     USAGE_MAXIMUM (Button 3)         */
        0x15, 0x00,             /*     LOGICAL_MINIMUM (0)              */
        0x25, 0x01,             /*     LOGICAL_MAXIMUM (1)              */
        0x75, 0x01,             /*     REPORT_SIZE (1)                  */
        0x95, 0x03,             /*     REPORT_COUNT (3)                 */
        0x81, 0x02,             /*     INPUT (Data,Var,Abs); 3 buttons  */
        0x75, 0x05,             /*     REPORT_SIZE (5)                  */
        0x95, 0x01,             /*     REPORT_COUNT (1)                 */
        0x81, 0x03,             /*     INPUT (Cnst,Var,Abs); padding    */
        0x05, 0x01,             /*     USAGE_PAGE (Generic Desktop)     */
        0x09, 0x30,             /*     USAGE (X)                        */
        0x09, 0x31,             /*     USAGE (Y)                        */
        0x35, 0x00,             /*     PHYSICAL_MINIMUM (0)             */
        0x46, 0xff, 0x7f,       /*     PHYSICAL_MAXIMUM (0x7fff)        */
        0x15, 0x00,             /*     LOGICAL_MINIMUM (0)              */
        0x26, 0xff, 0x7f,       /*     LOGICAL_MAXIMUM (0x7fff)         */
        0x75, 0x10,             /*     REPORT_SIZE (16)                 */
        0x95, 0x02,             /*     REPORT_COUNT (2)                 */
        0x81, 0x02,             /*     INPUT (Data,Var,Abs)             */
        0x05, 0x01,             /*     USAGE Page (Generic Desktop)     */
        0x09, 0x38,             /*     USAGE (Wheel)                    */
        0x35, 0x00,             /*     PHYSICAL_MINIMUM (0)             */
        0x45, 0x00,             /*     PHYSICAL_MAXIMUM (0)             */
        0x15, 0x81,             /*     LOGICAL_MINIMUM (-127)           */
        0x25, 0x7f,             /*     LOGICAL_MAXIMUM (127)            */
        0x75, 0x08,             /*     REPORT_SIZE (8)                  */
        0x95, 0x01,             /*     REPORT_COUNT (1)                 */
        0x81, 0x06,             /*     INPUT (Data,Var,Rel)             */
        0xc0,                   /*   END_COLLECTION                     */
        0xc0                    /* END_COLLECTION                       */
};

struct umouse_report {
        uint8_t buttons;        /* bits: 0 left, 1 right, 2 middle */
        int16_t x;              /* x position */
        int16_t y;              /* y position */
        int8_t  z;              /* z wheel position */
} __packed;


#define MSETW(ptr, val) ptr = { (uint8_t)(val), (uint8_t)((val) >> 8) }

static struct usb_device_descriptor umouse_dev_desc = {
        .bLength = sizeof(umouse_dev_desc),
        .bDescriptorType = UDESC_DEVICE,
        MSETW(.bcdUSB, UD_USB_3_0),
        .bMaxPacketSize = 9,                    /* max pkt size, 2^9 = 512 */
        MSETW(.idVendor, 0xFB5D),               /* vendor */
        MSETW(.idProduct, 0x0001),              /* product */
        MSETW(.bcdDevice, 0),                   /* device version */
        .iManufacturer = UMSTR_MANUFACTURER,
        .iProduct = UMSTR_PRODUCT,
        .iSerialNumber = UMSTR_SERIAL,
        .bNumConfigurations = 1,
};

static struct umouse_config_desc umouse_confd = {
        .confd = {
                .bLength = sizeof(umouse_confd.confd),
                .bDescriptorType = UDESC_CONFIG,
                .wTotalLength[0] = sizeof(umouse_confd),
                .bNumInterface = 1,
                .bConfigurationValue = 1,
                .iConfiguration = UMSTR_CONFIG,
                .bmAttributes = UC_BUS_POWERED | UC_REMOTE_WAKEUP,
                .bMaxPower = 0,
        },
        .ifcd = {
                .bLength = sizeof(umouse_confd.ifcd),
                .bDescriptorType = UDESC_INTERFACE,
                .bNumEndpoints = 1,
                .bInterfaceClass = UICLASS_HID,
                .bInterfaceSubClass = UISUBCLASS_BOOT,
                .bInterfaceProtocol = UIPROTO_MOUSE,
        },
        .hidd = {
                .bLength = sizeof(umouse_confd.hidd),
                .bDescriptorType = 0x21,
                .bcdHID = { 0x01, 0x10 },
                .bCountryCode = 0,
                .bNumDescriptors = 1,
                .bReportDescriptorType = UMOUSE_REPORT_DESC_TYPE,
                .wItemLength = { sizeof(umouse_report_desc), 0 },
        },
        .endpd = {
                .bLength = sizeof(umouse_confd.endpd),
                .bDescriptorType = UDESC_ENDPOINT,
                .bEndpointAddress = UE_DIR_IN | UMOUSE_INTR_ENDPT,
                .bmAttributes = UE_INTERRUPT,
                .wMaxPacketSize[0] = 8,
                .bInterval = 0xA,
        },
        .sscompd = {
                .bLength = sizeof(umouse_confd.sscompd),
                .bDescriptorType = UDESC_ENDPOINT_SS_COMP,
                .bMaxBurst = 0,
                .bmAttributes = 0,
                MSETW(.wBytesPerInterval, 0),
        },
};


struct umouse_bos_desc {
        struct usb_bos_descriptor               bosd;
        struct usb_devcap_ss_descriptor         usbssd;
} __packed;


static struct umouse_bos_desc umouse_bosd = {
        .bosd = {
                .bLength = sizeof(umouse_bosd.bosd),
                .bDescriptorType = UDESC_BOS,
                HSETW(.wTotalLength, sizeof(umouse_bosd)),
                .bNumDeviceCaps = 1,
        },
        .usbssd = {
                .bLength = sizeof(umouse_bosd.usbssd),
                .bDescriptorType = UDESC_DEVICE_CAPABILITY,
                .bDevCapabilityType = 3,
                .bmAttributes = 0,
                HSETW(.wSpeedsSupported, 0x08),
                .bFunctionalitySupport = 3,
                .bU1DevExitLat = 0xa,   /* dummy - not used */
                .wU2DevExitLat = { 0x20, 0x00 },
        }
};


struct umouse_softc {
        struct usb_hci *hci;

        struct umouse_report um_report;
        int     newdata;
        struct {
                uint8_t idle;
                uint8_t protocol;
                uint8_t feature;
        } hid;

        pthread_mutex_t mtx;
        pthread_mutex_t ev_mtx;
        int             polling;
        struct timeval  prev_evt;
};

static void
umouse_event(uint8_t button, int x, int y, void *arg)
{
        struct umouse_softc *sc;
        struct bhyvegc_image *gc;

        gc = console_get_image();
        if (gc == NULL) {
                /* not ready */
                return;
        }

        sc = arg;

        pthread_mutex_lock(&sc->mtx);

        sc->um_report.buttons = 0;
        sc->um_report.z = 0;

        if (button & 0x01)
                sc->um_report.buttons |= 0x01;  /* left */
        if (button & 0x02)
                sc->um_report.buttons |= 0x04;  /* middle */
        if (button & 0x04)
                sc->um_report.buttons |= 0x02;  /* right */
        if (button & 0x8)
                sc->um_report.z = 1;
        if (button & 0x10)
                sc->um_report.z = -1;

        /* scale coords to mouse resolution */
        sc->um_report.x = MOUSE_MAX_X * x / gc->width;
        sc->um_report.y = MOUSE_MAX_Y * y / gc->height;
        sc->newdata = 1;
        pthread_mutex_unlock(&sc->mtx);

        pthread_mutex_lock(&sc->ev_mtx);
        sc->hci->hci_intr(sc->hci, UE_DIR_IN | UMOUSE_INTR_ENDPT);
        pthread_mutex_unlock(&sc->ev_mtx);
}

static void *
umouse_probe(struct usb_hci *hci, nvlist_t *nvl __unused)
{
        struct umouse_softc *sc;

        sc = calloc(1, sizeof(struct umouse_softc));
        sc->hci = hci;

        return (sc);
}

static int
umouse_init(void *scarg)
{
        struct umouse_softc *sc = (struct umouse_softc *)scarg;

        sc->hid.protocol = 1;   /* REPORT protocol */
        pthread_mutex_init(&sc->mtx, NULL);
        pthread_mutex_init(&sc->ev_mtx, NULL);

        console_ptr_register(umouse_event, sc, 10);

        return (0);
}

#define UREQ(x,y)       ((x) | ((y) << 8))

static int
umouse_request(void *scarg, struct usb_data_xfer *xfer)
{
        struct umouse_softc *sc;
        struct usb_data_xfer_block *data;
        const char *str;
        uint16_t value;
        uint16_t index;
        uint16_t len;
        uint16_t slen;
        uint8_t *udata;
        int     err;
        int     i, idx;
        int     eshort;

        sc = scarg;

        data = NULL;
        udata = NULL;
        idx = xfer->head;
        for (i = 0; i < xfer->ndata; i++) {
                xfer->data[idx].bdone = 0;
                if (data == NULL && USB_DATA_OK(xfer, idx)) {
                        data = &xfer->data[idx];
                        udata = data->buf;
                }

                xfer->data[idx].processed = 1;
                idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
        }

        err = USB_ERR_NORMAL_COMPLETION;
        eshort = 0;

        if (!xfer->ureq) {
                DPRINTF(("umouse_request: port %d", sc->hci->hci_port));
                goto done;
        }

        value = UGETW(xfer->ureq->wValue);
        index = UGETW(xfer->ureq->wIndex);
        len = UGETW(xfer->ureq->wLength);

        DPRINTF(("umouse_request: port %d, type 0x%x, req 0x%x, val 0x%x, "
                 "idx 0x%x, len %u",
                 sc->hci->hci_port, xfer->ureq->bmRequestType,
                 xfer->ureq->bRequest, value, index, len));

        switch (UREQ(xfer->ureq->bRequest, xfer->ureq->bmRequestType)) {
        case UREQ(UR_GET_CONFIG, UT_READ_DEVICE):
                DPRINTF(("umouse: (UR_GET_CONFIG, UT_READ_DEVICE)"));
                if (!data)
                        break;

                *udata = umouse_confd.confd.bConfigurationValue;
                data->blen = len > 0 ? len - 1 : 0;
                eshort = data->blen > 0;
                data->bdone += 1;
                break;

        case UREQ(UR_GET_DESCRIPTOR, UT_READ_DEVICE):
                DPRINTF(("umouse: (UR_GET_DESCRIPTOR, UT_READ_DEVICE) val %x",
                        value >> 8));
                if (!data)
                        break;

                switch (value >> 8) {
                case UDESC_DEVICE:
                        DPRINTF(("umouse: (->UDESC_DEVICE) len %u ?= "
                                 "sizeof(umouse_dev_desc) %lu",
                                 len, sizeof(umouse_dev_desc)));
                        if ((value & 0xFF) != 0) {
                                err = USB_ERR_STALLED;
                                goto done;
                        }
                        if (len > sizeof(umouse_dev_desc)) {
                                data->blen = len - sizeof(umouse_dev_desc);
                                len = sizeof(umouse_dev_desc);
                        } else
                                data->blen = 0;
                        memcpy(data->buf, &umouse_dev_desc, len);
                        data->bdone += len;
                        break;

                case UDESC_CONFIG:
                        DPRINTF(("umouse: (->UDESC_CONFIG)"));
                        if ((value & 0xFF) != 0) {
                                err = USB_ERR_STALLED;
                                goto done;
                        }
                        if (len > sizeof(umouse_confd)) {
                                data->blen = len - sizeof(umouse_confd);
                                len = sizeof(umouse_confd);
                        } else
                                data->blen = 0;

                        memcpy(data->buf, &umouse_confd, len);
                        data->bdone += len;
                        break;

                case UDESC_STRING:
                        DPRINTF(("umouse: (->UDESC_STRING)"));
                        str = NULL;
                        if ((value & 0xFF) < UMSTR_MAX)
                                str = umouse_desc_strings[value & 0xFF];
                        else
                                goto done;

                        if ((value & 0xFF) == UMSTR_LANG) {
                                udata[0] = 4;
                                udata[1] = UDESC_STRING;
                                data->blen = len - 2;
                                len -= 2;
                                data->bdone += 2;

                                if (len >= 2) {
                                        udata[2] = str[0];
                                        udata[3] = str[1];
                                        data->blen -= 2;
                                        data->bdone += 2;
                                } else
                                        data->blen = 0;

                                goto done;
                        }

                        slen = 2 + strlen(str) * 2;
                        udata[0] = slen;
                        udata[1] = UDESC_STRING;

                        if (len > slen) {
                                data->blen = len - slen;
                                len = slen;
                        } else
                                data->blen = 0;
                        for (i = 2; i < len; i += 2) {
                                udata[i] = *str++;
                                udata[i+1] = '\0';
                        }
                        data->bdone += slen;

                        break;

                case UDESC_BOS:
                        DPRINTF(("umouse: USB3 BOS"));
                        if (len > sizeof(umouse_bosd)) {
                                data->blen = len - sizeof(umouse_bosd);
                                len = sizeof(umouse_bosd);
                        } else
                                data->blen = 0;
                        memcpy(udata, &umouse_bosd, len);
                        data->bdone += len;
                        break;

                default:
                        DPRINTF(("umouse: unknown(%d)->ERROR", value >> 8));
                        err = USB_ERR_STALLED;
                        goto done;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UR_GET_DESCRIPTOR, UT_READ_INTERFACE):
                DPRINTF(("umouse: (UR_GET_DESCRIPTOR, UT_READ_INTERFACE) "
                         "0x%x", (value >> 8)));
                if (!data)
                        break;

                switch (value >> 8) {
                case UMOUSE_REPORT_DESC_TYPE:
                        if (len > sizeof(umouse_report_desc)) {
                                data->blen = len - sizeof(umouse_report_desc);
                                len = sizeof(umouse_report_desc);
                        } else
                                data->blen = 0;
                        memcpy(data->buf, umouse_report_desc, len);
                        data->bdone += len;
                        break;
                default:
                        DPRINTF(("umouse: IO ERROR"));
                        err = USB_ERR_STALLED;
                        goto done;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UR_GET_INTERFACE, UT_READ_INTERFACE):
                DPRINTF(("umouse: (UR_GET_INTERFACE, UT_READ_INTERFACE)"));
                if (index != 0) {
                        DPRINTF(("umouse get_interface, invalid index %d",
                                index));
                        err = USB_ERR_STALLED;
                        goto done;
                }

                if (!data)
                        break;

                if (len > 0) {
                        *udata = 0;
                        data->blen = len - 1;
                }
                eshort = data->blen > 0;
                data->bdone += 1;
                break;

        case UREQ(UR_GET_STATUS, UT_READ_DEVICE):
                DPRINTF(("umouse: (UR_GET_STATUS, UT_READ_DEVICE)"));
                if (data == NULL)
                        break;
                if (len > 1) {
                        if (sc->hid.feature == UF_DEVICE_REMOTE_WAKEUP)
                                USETW(udata, UDS_REMOTE_WAKEUP);
                        else
                                USETW(udata, 0);
                        data->blen = len - 2;
                        data->bdone += 2;
                }

                eshort = data->blen > 0;
                break;

        case UREQ(UR_GET_STATUS, UT_READ_INTERFACE):
        case UREQ(UR_GET_STATUS, UT_READ_ENDPOINT):
                DPRINTF(("umouse: (UR_GET_STATUS, UT_READ_INTERFACE)"));
                if (data == NULL)
                        break;
                if (len > 1) {
                        USETW(udata, 0);
                        data->blen = len - 2;
                        data->bdone += 2;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UR_SET_ADDRESS, UT_WRITE_DEVICE):
                /* XXX Controller should've handled this */
                DPRINTF(("umouse set address %u", value));
                break;

        case UREQ(UR_SET_CONFIG, UT_WRITE_DEVICE):
                DPRINTF(("umouse set config %u", value));
                break;

        case UREQ(UR_SET_DESCRIPTOR, UT_WRITE_DEVICE):
                DPRINTF(("umouse set descriptor %u", value));
                break;


        case UREQ(UR_CLEAR_FEATURE, UT_WRITE_DEVICE):
                DPRINTF(("umouse: (UR_SET_FEATURE, UT_WRITE_DEVICE) %x", value));
                if (value == UF_DEVICE_REMOTE_WAKEUP)
                        sc->hid.feature = 0;
                break;

        case UREQ(UR_SET_FEATURE, UT_WRITE_DEVICE):
                DPRINTF(("umouse: (UR_SET_FEATURE, UT_WRITE_DEVICE) %x", value));
                if (value == UF_DEVICE_REMOTE_WAKEUP)
                        sc->hid.feature = UF_DEVICE_REMOTE_WAKEUP;
                break;

        case UREQ(UR_CLEAR_FEATURE, UT_WRITE_INTERFACE):
        case UREQ(UR_CLEAR_FEATURE, UT_WRITE_ENDPOINT):
        case UREQ(UR_SET_FEATURE, UT_WRITE_INTERFACE):
        case UREQ(UR_SET_FEATURE, UT_WRITE_ENDPOINT):
                DPRINTF(("umouse: (UR_CLEAR_FEATURE, UT_WRITE_INTERFACE)"));
                err = USB_ERR_STALLED;
                goto done;

        case UREQ(UR_SET_INTERFACE, UT_WRITE_INTERFACE):
                DPRINTF(("umouse set interface %u", value));
                break;

        case UREQ(UR_ISOCH_DELAY, UT_WRITE_DEVICE):
                DPRINTF(("umouse set isoch delay %u", value));
                break;

        case UREQ(UR_SET_SEL, 0):
                DPRINTF(("umouse set sel"));
                break;

        case UREQ(UR_SYNCH_FRAME, UT_WRITE_ENDPOINT):
                DPRINTF(("umouse synch frame"));
                break;

        /* HID device requests */

        case UREQ(UMOUSE_GET_REPORT, UT_READ_CLASS_INTERFACE):
                DPRINTF(("umouse: (UMOUSE_GET_REPORT, UT_READ_CLASS_INTERFACE) "
                         "0x%x", (value >> 8)));
                if (!data)
                        break;

                if ((value >> 8) == 0x01 && len >= sizeof(sc->um_report)) {
                        /* TODO read from backend */

                        if (len > sizeof(sc->um_report)) {
                                data->blen = len - sizeof(sc->um_report);
                                len = sizeof(sc->um_report);
                        } else
                                data->blen = 0;

                        memcpy(data->buf, &sc->um_report, len);
                        data->bdone += len;
                } else {
                        err = USB_ERR_STALLED;
                        goto done;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UMOUSE_GET_IDLE, UT_READ_CLASS_INTERFACE):
                if (data == NULL)
                        break;
                if (len > 0) {
                        *udata = sc->hid.idle;
                        data->blen = len - 1;
                        data->bdone += 1;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UMOUSE_GET_PROTOCOL, UT_READ_CLASS_INTERFACE):
                if (data == NULL)
                        break;
                if (len > 0) {
                        *udata = sc->hid.protocol;
                        data->blen = len - 1;
                        data->bdone += 1;
                }
                eshort = data->blen > 0;
                break;

        case UREQ(UMOUSE_SET_REPORT, UT_WRITE_CLASS_INTERFACE):
                DPRINTF(("umouse: (UMOUSE_SET_REPORT, UT_WRITE_CLASS_INTERFACE) ignored"));
                break;

        case UREQ(UMOUSE_SET_IDLE, UT_WRITE_CLASS_INTERFACE):
                sc->hid.idle = UGETW(xfer->ureq->wValue) >> 8;
                DPRINTF(("umouse: (UMOUSE_SET_IDLE, UT_WRITE_CLASS_INTERFACE) %x",
                        sc->hid.idle));
                break;

        case UREQ(UMOUSE_SET_PROTOCOL, UT_WRITE_CLASS_INTERFACE):
                sc->hid.protocol = UGETW(xfer->ureq->wValue) >> 8;
                DPRINTF(("umouse: (UR_CLEAR_FEATURE, UT_WRITE_CLASS_INTERFACE) %x",
                        sc->hid.protocol));
                break;

        default:
                DPRINTF(("**** umouse request unhandled"));
                err = USB_ERR_STALLED;
                break;
        }

done:
        if (xfer->ureq && (xfer->ureq->bmRequestType & UT_WRITE) &&
            (err == USB_ERR_NORMAL_COMPLETION) && (data != NULL))
                data->blen = 0;
        else if (eshort)
                err = USB_ERR_SHORT_XFER;

        DPRINTF(("umouse request error code %d (0=ok), blen %u txlen %u",
                err, (data ? data->blen : 0), (data ? data->bdone : 0)));

        return (err);
}

static int
umouse_data_handler(void *scarg, struct usb_data_xfer *xfer, int dir,
     int epctx)
{
        struct umouse_softc *sc;
        struct usb_data_xfer_block *data;
        uint8_t *udata;
        int len, i, idx;
        int err;

        DPRINTF(("umouse handle data - DIR=%s|EP=%d, blen %d",
                dir ? "IN" : "OUT", epctx, xfer->data[0].blen));


        /* find buffer to add data */
        udata = NULL;
        err = USB_ERR_NORMAL_COMPLETION;

        /* handle xfer at first unprocessed item with buffer */
        data = NULL;
        idx = xfer->head;
        for (i = 0; i < xfer->ndata; i++) {
                data = &xfer->data[idx];
                if (data->buf != NULL && data->blen != 0) {
                        break;
                } else {
                        data->processed = 1;
                        data = NULL;
                }
                idx = (idx + 1) % USB_MAX_XFER_BLOCKS;
        }
        if (!data)
                goto done;

        udata = data->buf;
        len = data->blen;

        if (udata == NULL) {
                DPRINTF(("umouse no buffer provided for input"));
                err = USB_ERR_NOMEM;
                goto done;
        }

        sc = scarg;

        if (dir) {

                pthread_mutex_lock(&sc->mtx);

                if (!sc->newdata) {
                        err = USB_ERR_CANCELLED;
                        USB_DATA_SET_ERRCODE(&xfer->data[xfer->head], USB_NAK);
                        pthread_mutex_unlock(&sc->mtx);
                        goto done;
                }

                if (sc->polling) {
                        err = USB_ERR_STALLED;
                        USB_DATA_SET_ERRCODE(data, USB_STALL);
                        pthread_mutex_unlock(&sc->mtx);
                        goto done;
                }
                sc->polling = 1;

                if (len > 0) {
                        sc->newdata = 0;

                        data->processed = 1;
                        data->bdone += 6;
                        memcpy(udata, &sc->um_report, 6);
                        data->blen = len - 6;
                        if (data->blen > 0)
                                err = USB_ERR_SHORT_XFER;
                }

                sc->polling = 0;
                pthread_mutex_unlock(&sc->mtx);
        } else {
                USB_DATA_SET_ERRCODE(data, USB_STALL);
                err = USB_ERR_STALLED;
        }

done:
        return (err);
}

static int
umouse_reset(void *scarg)
{
        struct umouse_softc *sc;

        sc = scarg;

        sc->newdata = 0;

        return (0);
}

static int
umouse_remove(void *scarg __unused)
{
        return (0);
}

static int
umouse_stop(void *scarg __unused)
{
        return (0);
}

#ifdef BHYVE_SNAPSHOT
static int
umouse_snapshot(void *scarg, struct vm_snapshot_meta *meta)
{
        int ret;
        struct umouse_softc *sc;

        sc = scarg;

        SNAPSHOT_VAR_OR_LEAVE(sc->um_report, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->newdata, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->hid.idle, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->hid.protocol, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->hid.feature, meta, ret, done);

        SNAPSHOT_VAR_OR_LEAVE(sc->polling, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->prev_evt.tv_sec, meta, ret, done);
        SNAPSHOT_VAR_OR_LEAVE(sc->prev_evt.tv_usec, meta, ret, done);

done:
        return (ret);
}
#endif

static struct usb_devemu ue_mouse = {
        .ue_emu =       "tablet",
        .ue_usbver =    3,
        .ue_usbspeed =  USB_SPEED_HIGH,
        .ue_probe =     umouse_probe,
        .ue_init =      umouse_init,
        .ue_request =   umouse_request,
        .ue_data =      umouse_data_handler,
        .ue_reset =     umouse_reset,
        .ue_remove =    umouse_remove,
        .ue_stop =      umouse_stop,
#ifdef BHYVE_SNAPSHOT
        .ue_snapshot =  umouse_snapshot,
#endif
};
USB_EMUL_SET(ue_mouse);