Edit

IABSD.fr/src/sys/dev/usb/uhidpp.c

Branch :

  • Show log

    Commit

  • Author : jsg
    Date : 2024-05-23 03:21:08
    Hash : 81508fe3
    Message : remove unneeded includes; ok mpi@

  • sys/dev/usb/uhidpp.c
  • /*	$OpenBSD: uhidpp.c,v 1.44 2024/05/23 03:21:09 jsg Exp $	*/
    
    /*
     * Copyright (c) 2021 Anton Lindqvist <anton@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    #include <sys/param.h>
    #include <sys/systm.h>
    #include <sys/device.h>
    #include <sys/mutex.h>
    #include <sys/sensors.h>
    
    #include <dev/usb/usb.h>
    #include <dev/usb/usbhid.h>
    #include <dev/usb/usbdi.h>
    #include <dev/usb/usbdevs.h>
    #include <dev/usb/uhidev.h>
    
    /* #define UHIDPP_DEBUG */
    #ifdef UHIDPP_DEBUG
    
    #define DPRINTF(x...) do {						\
    	if (uhidpp_debug)						\
    		printf(x);						\
    } while (0)
    
    #define DREPORT(prefix, repid, buf, len) do {				\
    	if (uhidpp_debug)						\
    		uhidd_dump_report((prefix), (repid), (buf), (len));	\
    } while (0)
    
    void uhidd_dump_report(const char *, uint8_t, const unsigned char *, u_int);
    
    int uhidpp_debug = 1;
    
    #else
    
    #define DPRINTF(x...)
    #define DREPORT(prefix, repid, buf, len)
    
    #endif
    
    #define HIDPP_REPORT_ID_SHORT			0x10
    #define HIDPP_REPORT_ID_LONG			0x11
    
    /*
     * Length of reports. Note that the effective length is always +1 as
     * uhidev_set_report() prepends the report ID.
     */
    #define HIDPP_REPORT_SHORT_LENGTH		(7 - 1)
    #define HIDPP_REPORT_LONG_LENGTH		(20 - 1)
    
    /*
     * Maximum number of allowed parameters for reports. Note, the parameters always
     * starts at offset 3 for both RAP and FAP reports.
     */
    #define HIDPP_REPORT_SHORT_PARAMS_MAX		(HIDPP_REPORT_SHORT_LENGTH - 3)
    #define HIDPP_REPORT_LONG_PARAMS_MAX		(HIDPP_REPORT_LONG_LENGTH - 3)
    
    #define HIDPP_DEVICE_ID_RECEIVER		0xff
    
    #define HIDPP_FEAT_ROOT_ID			0x00
    #define HIDPP_FEAT_ROOT_PING_FUNC		0x01
    #define  HIDPP_PING_DATA			0x5a
    
    #define HIDPP_SET_REGISTER			0x80
    #define HIDPP_GET_REGISTER			0x81
    #define HIDPP_SET_LONG_REGISTER			0x82
    #define HIDPP_GET_LONG_REGISTER			0x83
    
    #define HIDPP_REG_ENABLE_REPORTS			0x00
    #define  HIDPP_ENABLE_REPORTS_DEVICE_BATTERY_STATUS	0x10
    #define  HIDPP_ENABLE_REPORTS_RECEIVER_WIRELESS		0x01
    #define  HIDPP_ENABLE_REPORTS_RECEIVER_SOFTWARE_PRESENT	0x08
    
    #define HIDPP_REG_PAIRING_INFORMATION		0xb5
    
    /* HID++ 1.0 error codes. */
    #define HIDPP_ERROR				0x8f
    #define HIDPP_ERROR_SUCCESS			0x00
    #define HIDPP_ERROR_INVALID_SUBID		0x01
    #define HIDPP_ERROR_INVALID_ADRESS		0x02
    #define HIDPP_ERROR_INVALID_VALUE		0x03
    #define HIDPP_ERROR_CONNECT_FAIL		0x04
    #define HIDPP_ERROR_TOO_MANY_DEVICES		0x05
    #define HIDPP_ERROR_ALREADY_EXISTS		0x06
    #define HIDPP_ERROR_BUSY			0x07
    #define HIDPP_ERROR_UNKNOWN_DEVICE		0x08
    #define HIDPP_ERROR_RESOURCE_ERROR		0x09
    #define HIDPP_ERROR_REQUEST_UNAVAILABLE		0x0a
    #define HIDPP_ERROR_INVALID_PARAM_VALUE		0x0b
    #define HIDPP_ERROR_WRONG_PIN_CODE		0x0c
    
    /*
     * The software ID is added to feature access reports (FAP) and used to
     * distinguish responses from notifications. Note, the software ID must be
     * greater than zero which is reserved for notifications.
     * The effective software ID round robins within its allowed interval [1, 15]
     * making it easier to correlate requests and responses.
     */
    #define HIDPP_SOFTWARE_ID_MIN			1
    #define HIDPP_SOFTWARE_ID_MAX			15
    #define HIDPP_SOFTWARE_ID_LEN			4
    
    #define HIDPP20_FEAT_ROOT_ID			0x0000
    #define HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC	0x0000
    
    #define HIDPP20_FEAT_FEATURE_ID			0x0001
    #define HIDPP20_FEAT_FEATURE_COUNT_FUNC		0x0000
    #define HIDPP20_FEAT_FEATURE_ID_FUNC		0x0001
    
    #define HIDPP20_FEAT_BATTERY_ID			0x1000
    #define HIDPP20_FEAT_BATTERY_LEVEL_STATUS_FUNC	0x0000
    #define  HIDPP20_LEVEL_STATUS_CHARGING_DONE	0x0003
    #define HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC	0x0001
    #define  HIDPP20_CAPABILITY_RECHARGEABLE	0x0004
    
    #define HIDPP20_FEAT_UNIFIED_BATTERY_ID			0x1004
    #define HIDPP20_FEAT_UNIFIED_BATTERY_CAPABILITIES_FUNC	0x0000
    #define  HIDPP20_CAPABILITES_RECHARGEABLE		0x0002
    #define HIDPP20_FEAT_UNIFIED_BATTERY_STATUS_FUNC	0x0001
    #define  HIDPP20_BATTERY_STATUS_CRITICAL		0x0001
    #define  HIDPP20_BATTERY_STATUS_LOW			0x0002
    #define  HIDPP20_BATTERY_STATUS_GOOD			0x0004
    #define  HIDPP20_BATTERY_STATUS_FULL			0x0008
    
    /* HID++ 2.0 error codes. */
    #define HIDPP20_ERROR				0xff
    #define HIDPP20_ERROR_NO_ERROR			0x00
    #define HIDPP20_ERROR_UNKNOWN			0x01
    #define HIDPP20_ERROR_INVALID_ARGUMENT		0x02
    #define HIDPP20_ERROR_OUT_OF_RANGE		0x03
    #define HIDPP20_ERROR_HARDWARE_ERROR		0x04
    #define HIDPP20_ERROR_LOGITECH_INTERNAL		0x05
    #define HIDPP20_ERROR_INVALID_FEATURE_INDEX	0x06
    #define HIDPP20_ERROR_INVALID_FUNCTION_ID	0x07
    #define HIDPP20_ERROR_BUSY			0x08
    #define HIDPP20_ERROR_UNSUPPORTED		0x09
    
    /*
     * Sentinels used for interrupt response synchronization. The values must be
     * disjoint from existing report IDs.
     */
    #define UHIDPP_RESP_NONE			0
    #define UHIDPP_RESP_WAIT			1
    #define UHIDPP_RESP_ERROR			2
    
    /* Maximum number of devices associated with a single receiver. */
    #define UHIDPP_NDEVICES				6
    
    /* Maximum number of pending notifications. */
    #define UHIDPP_NNOTIFICATIONS			4
    
    /* Number of sensors per paired device. */
    #define UHIDPP_NSENSORS				3
    
    /* Feature access report used by the HID++ 2.0 (and greater) protocol. */
    struct fap {
    	uint8_t feature_idx;
    	uint8_t funcidx_swid;
    	uint8_t params[HIDPP_REPORT_LONG_PARAMS_MAX];
    };
    
    /*
     * Register access report used by the HID++ 1.0 protocol. Receivers always uses
     * this type of report.
     */
    struct rap {
    	uint8_t sub_id;
    	uint8_t reg_address;
    	uint8_t params[HIDPP_REPORT_LONG_PARAMS_MAX];
    };
    
    struct uhidpp_report {
    	uint8_t device_id;
    	union {
    		struct fap fap;
    		struct rap rap;
    	};
    } __packed;
    
    struct uhidpp_notification {
    	struct uhidpp_report n_rep;
    	unsigned int n_id;
    };
    
    struct uhidpp_device {
    	uint8_t d_id;
    	uint8_t d_connected;
    	uint8_t d_major;
    	uint8_t d_minor;
    	uint8_t d_features;
    #define UHIDPP_DEVICE_FEATURE_ROOT		0x01
    #define UHIDPP_DEVICE_FEATURE_BATTERY		0x02
    #define UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY	0x04
    
    	struct {
    		struct ksensor sens[UHIDPP_NSENSORS];
    		uint8_t feature_idx;
    		uint8_t nlevels;
    		uint8_t unified_level_mask;
    		uint8_t rechargeable;
    	} d_battery;
    };
    
    /*
     * Locking:
     *	[I]	immutable
     *	[m]	sc_mtx
     */
    struct uhidpp_softc {
    	struct uhidev sc_hdev;
    	struct usbd_device *sc_udev;
    
    	struct mutex sc_mtx;
    
    	struct uhidpp_device sc_devices[UHIDPP_NDEVICES];
    					/* [m] connected devices */
    
    	struct uhidpp_notification sc_notifications[UHIDPP_NNOTIFICATIONS];
    					/* [m] pending notifications */
    
    	struct usb_task sc_task;	/* [m] notification task */
    
    	struct ksensordev sc_sensdev;	/* [m] */
    	struct sensor_task *sc_senstsk;	/* [m] */
    
    	struct uhidpp_report *sc_req;	/* [m] synchronous request buffer */
    	struct uhidpp_report *sc_resp;	/* [m] synchronous response buffer */
    	u_int sc_resp_state;		/* [m] synchronous response state */
    	u_int sc_swid;			/* [m] request software id */
    
    	enum {
    		UHIDPP_RECEIVER_UNIFYING,
    		UHIDPP_RECEIVER_BOLT,
    	} sc_receiver;			/* [I] */
    };
    
    int uhidpp_match(struct device *, void *, void *);
    void uhidpp_attach(struct device *, struct device *, void *);
    int uhidpp_detach(struct device *, int flags);
    void uhidpp_intr(struct uhidev *addr, void *ibuf, u_int len);
    void uhidpp_refresh(void *);
    void uhidpp_task(void *);
    int uhidpp_sleep(struct uhidpp_softc *, uint64_t);
    
    void uhidpp_device_connect(struct uhidpp_softc *, struct uhidpp_device *);
    void uhidpp_device_refresh(struct uhidpp_softc *, struct uhidpp_device *);
    int uhidpp_device_features(struct uhidpp_softc *, struct uhidpp_device *);
    
    struct uhidpp_notification *uhidpp_claim_notification(struct uhidpp_softc *);
    int uhidpp_consume_notification(struct uhidpp_softc *, struct uhidpp_report *);
    int uhidpp_is_notification(struct uhidpp_softc *, struct uhidpp_report *);
    
    static int uhidpp_has_sensors(struct uhidpp_softc *);
    
    int hidpp_get_protocol_version(struct uhidpp_softc *, uint8_t, uint8_t *,
        uint8_t *);
    
    int hidpp10_get_name(struct uhidpp_softc *, uint8_t, char *, size_t);
    int hidpp10_get_type(struct uhidpp_softc *, uint8_t, const char **);
    int hidpp10_enable_notifications(struct uhidpp_softc *, uint8_t);
    
    int hidpp20_root_get_feature(struct uhidpp_softc *, uint8_t, uint16_t,
        uint8_t *, uint8_t *);
    int hidpp20_feature_get_count(struct uhidpp_softc *, uint8_t, uint8_t,
        uint8_t *);
    int hidpp20_feature_get_id(struct uhidpp_softc *, uint8_t, uint8_t, uint8_t,
        uint16_t *, uint8_t *);
    int hidpp20_battery_get_level_status(struct uhidpp_softc *,
        struct uhidpp_device *);
    int hidpp20_battery_get_capability(struct uhidpp_softc *,
        struct uhidpp_device *);
    int hidpp20_battery_status_is_charging(uint8_t);
    int hidpp20_unified_battery_get_capabilities(struct uhidpp_softc *,
        struct uhidpp_device *);
    int hidpp20_unified_battery_get_status(struct uhidpp_softc *,
        struct uhidpp_device *);
    int hidpp20_unified_battery_status_is_charging(uint8_t);
    
    int hidpp_send_validate(uint8_t, int);
    int hidpp_send_rap_report(struct uhidpp_softc *, uint8_t, uint8_t, uint8_t,
        uint8_t, uint8_t *, int, struct uhidpp_report *);
    int hidpp_send_fap_report(struct uhidpp_softc *, uint8_t, uint8_t, uint8_t,
        uint8_t, uint8_t *, int, struct uhidpp_report *);
    int hidpp_send_report(struct uhidpp_softc *, uint8_t, struct uhidpp_report *,
        struct uhidpp_report *);
    
    static uint8_t
    nlevels(uint8_t mask)
    {
    	uint8_t nbits = 0;
    
    	for (; mask > 0; mask >>= 1) {
    		if (mask & 1)
    			nbits++;
    	}
    	return nbits;
    }
    
    struct cfdriver uhidpp_cd = {
    	NULL, "uhidpp", DV_DULL
    };
    
    const struct cfattach uhidpp_ca = {
    	sizeof(struct uhidpp_softc),
    	uhidpp_match,
    	uhidpp_attach,
    	uhidpp_detach,
    };
    
    static const struct usb_devno uhidpp_devs[] = {
    	{ USB_VENDOR_LOGITECH,	USB_PRODUCT_ANY },
    };
    
    int
    uhidpp_match(struct device *parent, void *match, void *aux)
    {
    	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
    	void *desc;
    	int descsiz, siz;
    
    	if (!UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
    		return UMATCH_NONE;
    
    	if (usb_lookup(uhidpp_devs,
    	    uha->uaa->vendor, uha->uaa->product) == NULL)
    		return UMATCH_NONE;
    
    	uhidev_get_report_desc(uha->parent, &desc, &descsiz);
    	siz = hid_report_size(desc, descsiz, hid_output, HIDPP_REPORT_ID_SHORT);
    	if (siz != HIDPP_REPORT_SHORT_LENGTH)
    		return UMATCH_NONE;
    	siz = hid_report_size(desc, descsiz, hid_output, HIDPP_REPORT_ID_LONG);
    	if (siz != HIDPP_REPORT_LONG_LENGTH)
    		return UMATCH_NONE;
    
    	uha->claimed[HIDPP_REPORT_ID_SHORT] = 1;
    	uha->claimed[HIDPP_REPORT_ID_LONG] = 1;
    	return UMATCH_VENDOR_PRODUCT;
    }
    
    void
    uhidpp_attach(struct device *parent, struct device *self, void *aux)
    {
    	struct uhidpp_softc *sc = (struct uhidpp_softc *)self;
    	struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux;
    	struct usb_attach_arg *uaa = uha->uaa;
    	int npaired = 0;
    	int error, i;
    
    	sc->sc_hdev.sc_intr = uhidpp_intr;
    	sc->sc_hdev.sc_udev = uaa->device;
    	sc->sc_hdev.sc_parent = uha->parent;
    	sc->sc_hdev.sc_report_id = uha->reportid;
    	/* The largest supported report dictates the sizes. */
    	sc->sc_hdev.sc_isize = HIDPP_REPORT_LONG_LENGTH;
    	sc->sc_hdev.sc_osize = HIDPP_REPORT_LONG_LENGTH;
    
    	sc->sc_udev = uaa->device;
    
    	mtx_init(&sc->sc_mtx, IPL_USB);
    
    	sc->sc_resp = NULL;
    	sc->sc_resp_state = UHIDPP_RESP_NONE;
    	sc->sc_swid = HIDPP_SOFTWARE_ID_MIN;
    
    	error = uhidev_open(&sc->sc_hdev);
    	if (error) {
    		printf(" open error %d\n", error);
    		return;
    	}
    
    	usb_init_task(&sc->sc_task, uhidpp_task, sc, USB_TASK_TYPE_GENERIC);
    
    	mtx_enter(&sc->sc_mtx);
    
    	/*
    	 * Wire up report device handlers before issuing commands to the device
    	 * in order to receive responses. Necessary as uhidev by default
    	 * performs the wiring after the attach routine has returned.
    	 */
    	error = uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev,
    	    HIDPP_REPORT_ID_SHORT);
    	if (error) {
    		mtx_leave(&sc->sc_mtx);
    		printf(" short report error %d\n", error);
    		return;
    	}
    	error = uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev,
    	    HIDPP_REPORT_ID_LONG);
    	if (error) {
    		mtx_leave(&sc->sc_mtx);
    		printf(" long report error %d\n", error);
    		return;
    	}
    
    	if (uaa->product == 0xc548)
    		sc->sc_receiver = UHIDPP_RECEIVER_BOLT;
    	else
    		sc->sc_receiver = UHIDPP_RECEIVER_UNIFYING;
    
    	/* Probe paired devices. */
    	for (i = 0; i < UHIDPP_NDEVICES; i++) {
    		char name[16];
    		struct uhidpp_device *dev = &sc->sc_devices[i];
    		const char *type;
    		uint8_t device_id = i + 1;
    
    		dev->d_id = device_id;
    
    		if (hidpp10_get_type(sc, device_id, &type) ||
    		    hidpp10_get_name(sc, device_id, name, sizeof(name)))
    			continue;
    
    		if (npaired > 0)
    			printf(",");
    		printf(" device %d", device_id);
    		printf(" %s", type);
    		printf(" \"%s\"", name);
    		npaired++;
    	}
    	if (npaired == 0)
    		goto out;
    
    	/* Enable notifications for the receiver. */
    	error = hidpp10_enable_notifications(sc, HIDPP_DEVICE_ID_RECEIVER);
    	if (error)
    		printf(" error %d", error);
    
    out:
    	mtx_leave(&sc->sc_mtx);
    	printf("\n");
    }
    
    int
    uhidpp_detach(struct device *self, int flags)
    {
    	struct uhidpp_softc *sc = (struct uhidpp_softc *)self;
    	int i;
    
    	usb_rem_wait_task(sc->sc_udev, &sc->sc_task);
    
    	if (uhidpp_has_sensors(sc))
    		sensor_task_unregister(sc->sc_senstsk);
    
    	KASSERT(sc->sc_resp_state == UHIDPP_RESP_NONE);
    
    	if (uhidpp_has_sensors(sc))
    		sensordev_deinstall(&sc->sc_sensdev);
    
    	for (i = 0; i < UHIDPP_NDEVICES; i++) {
    		struct uhidpp_device *dev = &sc->sc_devices[i];
    		struct ksensor *sens = dev->d_battery.sens;
    
    		if (!dev->d_connected)
    			continue;
    
    		sensor_detach(&sc->sc_sensdev, &sens[0]);
    		sensor_detach(&sc->sc_sensdev, &sens[1]);
    		if (dev->d_battery.rechargeable)
    			sensor_detach(&sc->sc_sensdev, &sens[2]);
    	}
    
    	uhidev_close(&sc->sc_hdev);
    
    	return 0;
    }
    
    void
    uhidpp_intr(struct uhidev *addr, void *buf, u_int len)
    {
    	struct uhidpp_softc *sc = (struct uhidpp_softc *)addr;
    	struct uhidpp_report *rep = buf;
    	int dowake = 0;
    	uint8_t repid;
    
    	/*
    	 * Ugliness ahead as the report ID is stripped of by uhidev_intr() but
    	 * needed to determine if an error occurred.
    	 * Note that an error response is always a short report even if the
    	 * command that caused the error is a long report.
    	 */
    	repid = ((uint8_t *)buf)[-1];
    
    	DREPORT(__func__, repid, buf, len);
    
    	mtx_enter(&sc->sc_mtx);
    	if (uhidpp_is_notification(sc, rep)) {
    		struct uhidpp_notification *ntf;
    
    		ntf = uhidpp_claim_notification(sc);
    		if (ntf != NULL) {
    			memcpy(&ntf->n_rep, buf, len);
    			usb_add_task(sc->sc_udev, &sc->sc_task);
    		} else {
    			DPRINTF("%s: too many notifications", __func__);
    		}
    	} else {
    		KASSERT(sc->sc_resp_state == UHIDPP_RESP_WAIT);
    		dowake = 1;
    		sc->sc_resp_state = repid;
    		memcpy(sc->sc_resp, buf, len);
    	}
    	mtx_leave(&sc->sc_mtx);
    	if (dowake)
    		wakeup(sc);
    }
    
    void
    uhidpp_refresh(void *arg)
    {
    	struct uhidpp_softc *sc = arg;
    	int i;
    
    	mtx_enter(&sc->sc_mtx);
    	for (i = 0; i < UHIDPP_NDEVICES; i++) {
    		struct uhidpp_device *dev = &sc->sc_devices[i];
    
    		if (dev->d_connected)
    			uhidpp_device_refresh(sc, dev);
    	}
    	mtx_leave(&sc->sc_mtx);
    }
    
    void
    uhidpp_task(void *arg)
    {
    	struct uhidpp_softc *sc = arg;
    
    	mtx_enter(&sc->sc_mtx);
    	for (;;) {
    		struct uhidpp_report rep;
    		struct uhidpp_device *dev;
    
    		if (uhidpp_consume_notification(sc, &rep))
    			break;
    
    		DPRINTF("%s: device_id=%d, sub_id=%02x\n",
    		    __func__, rep.device_id, rep.rap.sub_id);
    
    		if (rep.device_id == 0 || rep.device_id > UHIDPP_NDEVICES) {
    			DPRINTF("%s: invalid device\n", __func__);
    			continue;
    		}
    		dev = &sc->sc_devices[rep.device_id - 1];
    
    		switch (rep.rap.sub_id) {
    		case 0x0e:	/* leds */
    		case 0x40:	/* disconnect */
    		case 0x4b:	/* pairing accepted */
    			break;
    		case 0x41:	/* connect */
    			uhidpp_device_connect(sc, dev);
    			break;
    		}
    	}
    	mtx_leave(&sc->sc_mtx);
    }
    
    int
    uhidpp_sleep(struct uhidpp_softc *sc, uint64_t nsecs)
    {
    	return msleep_nsec(sc, &sc->sc_mtx, PZERO, "uhidpp", nsecs);
    }
    
    void
    uhidpp_device_connect(struct uhidpp_softc *sc, struct uhidpp_device *dev)
    {
    	struct ksensor *sens;
    	int error;
    
    	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
    
    	/* A connected device will continuously send connect events. */
    	if (dev->d_connected)
    		return;
    
    	/*
    	 * If features are already present, it must be a device lacking battery
    	 * support.
    	 */
    	if (dev->d_features)
    		return;
    
    	error = hidpp_get_protocol_version(sc, dev->d_id,
    	    &dev->d_major, &dev->d_minor);
    	if (error) {
    		DPRINTF("%s: protocol version failure: device_id=%d, "
    		    "error=%d\n",
    		    __func__, dev->d_id, error);
    		return;
    	}
    	DPRINTF("%s: device_id=%d, version=%d.%d\n",
    	    __func__, dev->d_id, dev->d_major, dev->d_minor);
    	if (dev->d_major <= 1)
    		return;
    
    	error = uhidpp_device_features(sc, dev);
    	if (error) {
    		DPRINTF("%s: features failure: device_id=%d, "
    		    "error=%d\n",
    		    __func__, dev->d_id, error);
    		return;
    	}
    
    	if (dev->d_features & UHIDPP_DEVICE_FEATURE_BATTERY)
    		error = hidpp20_battery_get_capability(sc, dev);
    	else if (dev->d_features & UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY)
    		error = hidpp20_unified_battery_get_capabilities(sc, dev);
    	if (error) {
    		DPRINTF("%s: battery capability failure: device_id=%d, "
    		    "error=%d\n", __func__, dev->d_id, error);
    		return;
    	}
    
    	dev->d_connected = 1;
    
    	sens = &dev->d_battery.sens[0];
    	strlcpy(sens->desc, "battery level", sizeof(sens->desc));
    	sens->type = SENSOR_PERCENT;
    	sens->flags = SENSOR_FUNKNOWN;
    	sensor_attach(&sc->sc_sensdev, sens);
    
    	sens = &dev->d_battery.sens[1];
    	strlcpy(sens->desc, "number of battery levels", sizeof(sens->desc));
    	sens->type = SENSOR_INTEGER;
    	sens->value = dev->d_battery.nlevels;
    	sensor_attach(&sc->sc_sensdev, sens);
    
    	if (dev->d_battery.rechargeable) {
    		sens = &dev->d_battery.sens[2];
    		strlcpy(sens->desc, "charger", sizeof(sens->desc));
    		sens->type = SENSOR_INDICATOR;
    		sens->value = 0;
    		sensor_attach(&sc->sc_sensdev, sens);
    	}
    
    	uhidpp_device_refresh(sc, dev);
    
    	/*
    	 * There could be many devices connected to the same receiver, therefore
    	 * only install the sensors once.
    	 */
    	if (uhidpp_has_sensors(sc))
    		return;
    
    	strlcpy(sc->sc_sensdev.xname, sc->sc_hdev.sc_dev.dv_xname,
    	    sizeof(sc->sc_sensdev.xname));
    	sensordev_install(&sc->sc_sensdev);
    
    	/*
    	 * The mutex must be temporarily released while calling
    	 * sensor_task_register() as it might end up sleeping.
    	 */
    	KASSERT(sc->sc_senstsk == NULL);
    	mtx_leave(&sc->sc_mtx);
    	sc->sc_senstsk = sensor_task_register(sc, uhidpp_refresh, 60);
    	mtx_enter(&sc->sc_mtx);
    }
    
    void
    uhidpp_device_refresh(struct uhidpp_softc *sc, struct uhidpp_device *dev)
    {
    	int error;
    
    	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
    
    	if (dev->d_major <= 1)
    		return;
    
    	if (dev->d_features & UHIDPP_DEVICE_FEATURE_BATTERY)
    		error = hidpp20_battery_get_level_status(sc, dev);
    	else if (dev->d_features & UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY)
    		error = hidpp20_unified_battery_get_status(sc, dev);
    	else
    		error = -ENOTSUP;
    	if (error) {
    		DPRINTF("%s: battery status failure: device_id=%d, error=%d\n",
    		    __func__, dev->d_id, error);
    	}
    }
    
    /*
     * Enumerate all supported HID++ 2.0 features for the given device.
     */
    int
    uhidpp_device_features(struct uhidpp_softc *sc, struct uhidpp_device *dev)
    {
    	int error;
    	uint8_t count, feature_idx, feature_type, i;
    
    	/* All devices support the root feature. */
    	dev->d_features |= UHIDPP_DEVICE_FEATURE_ROOT;
    
    	error = hidpp20_root_get_feature(sc, dev->d_id,
    	    HIDPP20_FEAT_FEATURE_ID,
    	    &feature_idx, &feature_type);
    	if (error) {
    		DPRINTF("%s: feature index failure: device_id=%d, error=%d\n",
    		    __func__, dev->d_id, error);
    		return error;
    	}
    
    	error = hidpp20_feature_get_count(sc, dev->d_id, feature_idx, &count);
    	if (error) {
    		DPRINTF("%s: feature count failure: device_id=%d, error=%d\n",
    		    __func__, dev->d_id, error);
    		return error;
    	}
    
    	for (i = 1; i <= count; i++) {
    		uint16_t id;
    		uint8_t type;
    
    		error = hidpp20_feature_get_id(sc, dev->d_id, feature_idx, i,
    		    &id, &type);
    		if (error)
    			continue;
    
    		if (id == HIDPP20_FEAT_BATTERY_ID) {
    			dev->d_features |= UHIDPP_DEVICE_FEATURE_BATTERY;
    			dev->d_battery.feature_idx = i;
    		} else if (id == HIDPP20_FEAT_UNIFIED_BATTERY_ID) {
    			dev->d_features |= UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY;
    			dev->d_battery.feature_idx = i;
    		}
    
    		DPRINTF("%s: idx=%d, id=%x, type=%x device_id=%d\n",
    		    __func__, i, id, type, dev->d_id);
    	}
    	DPRINTF("%s: device_id=%d, count=%d, features=%x\n",
    	    __func__, dev->d_id, count, dev->d_features);
    
    	if ((dev->d_features & ~UHIDPP_DEVICE_FEATURE_ROOT) == 0)
    		return -ENODEV;
    	return 0;
    }
    
    /*
     * Returns the next available notification slot, if available.
     */
    struct uhidpp_notification *
    uhidpp_claim_notification(struct uhidpp_softc *sc)
    {
    	struct uhidpp_notification *ntf = NULL;
    	int nclaimed = 0;
    	int i;
    
    	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
    
    	for (i = 0; i < UHIDPP_NNOTIFICATIONS; i++) {
    		struct uhidpp_notification *tmp = &sc->sc_notifications[i];
    
    		if (tmp->n_id > 0)
    			nclaimed++;
    		else if (ntf == NULL)
    			ntf = tmp;
    	}
    
    	if (ntf == NULL)
    		return NULL;
    	ntf->n_id = nclaimed + 1;
    	return ntf;
    }
    
    /*
     * Consume the first unhandled notification, if present.
     */
    int
    uhidpp_consume_notification(struct uhidpp_softc *sc, struct uhidpp_report *rep)
    {
    	struct uhidpp_notification *ntf = NULL;
    	int i;
    
    	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
    
    	for (i = 0; i < UHIDPP_NNOTIFICATIONS; i++) {
    		struct uhidpp_notification *tmp = &sc->sc_notifications[i];
    
    		if (tmp->n_id > 0 && (ntf == NULL || tmp->n_id < ntf->n_id))
    			ntf = tmp;
    	}
    	if (ntf == NULL)
    		return 1;
    
    	memcpy(rep, &ntf->n_rep, sizeof(*rep));
    	ntf->n_id = 0;
    	return 0;
    }
    
    /*
     * Returns non-zero if the given report is a notification. Otherwise, it must be
     * a response.
     */
    int
    uhidpp_is_notification(struct uhidpp_softc *sc, struct uhidpp_report *rep)
    {
    	/* Not waiting for a response. */
    	if (sc->sc_req == NULL)
    		return 1;
    
    	/* Everything except the parameters must be repeated in a response. */
    	if (sc->sc_req->device_id == rep->device_id &&
    	    sc->sc_req->rap.sub_id == rep->rap.sub_id &&
    	    sc->sc_req->rap.reg_address == rep->rap.reg_address)
    		return 0;
    
    	/* An error must always be a response. */
    	if ((rep->rap.sub_id == HIDPP_ERROR ||
    	    rep->fap.feature_idx == HIDPP20_ERROR) &&
    	    rep->fap.funcidx_swid == sc->sc_req->fap.feature_idx &&
    	    rep->fap.params[0] == sc->sc_req->fap.funcidx_swid)
    		return 0;
    
    	return 1;
    }
    
    static int
    uhidpp_has_sensors(struct uhidpp_softc *sc)
    {
    	return sc->sc_sensdev.xname[0] != '\0';
    }
    
    int
    hidpp_get_protocol_version(struct uhidpp_softc *sc, uint8_t device_id,
        uint8_t *major, uint8_t *minor)
    {
    	struct uhidpp_report resp;
    	uint8_t params[3] = { 0, 0, HIDPP_PING_DATA };
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_SHORT,
    	    device_id,
    	    HIDPP_FEAT_ROOT_ID,
    	    HIDPP_FEAT_ROOT_PING_FUNC,
    	    params, sizeof(params), &resp);
    	if (error == HIDPP_ERROR_INVALID_SUBID) {
    		*major = 1;
    		*minor = 0;
    		return 0;
    	}
    	if (error)
    		return error;
    	if (resp.rap.params[2] != HIDPP_PING_DATA)
    		return -EPROTO;
    
    	*major = resp.fap.params[0];
    	*minor = resp.fap.params[1];
    	return 0;
    }
    
    int
    hidpp10_get_name(struct uhidpp_softc *sc, uint8_t device_id,
        char *buf, size_t bufsiz)
    {
    	struct uhidpp_report resp;
    	int error;
    	const uint8_t *name;
    	uint8_t len;
    
    	if (sc->sc_receiver == UHIDPP_RECEIVER_BOLT) {
    		uint8_t params[2] = { 0x60 + device_id, 0x01 };
    
    		error = hidpp_send_rap_report(sc,
    		    HIDPP_REPORT_ID_SHORT,
    		    HIDPP_DEVICE_ID_RECEIVER,
    		    HIDPP_GET_LONG_REGISTER,
    		    HIDPP_REG_PAIRING_INFORMATION,
    		    params, sizeof(params), &resp);
    		if (error)
    			return error;
    		len = resp.rap.params[2];
    		name = &resp.rap.params[3];
    	} else {
    		uint8_t params[1] = { 0x40 + (device_id - 1) };
    
    		error = hidpp_send_rap_report(sc,
    		    HIDPP_REPORT_ID_SHORT,
    		    HIDPP_DEVICE_ID_RECEIVER,
    		    HIDPP_GET_LONG_REGISTER,
    		    HIDPP_REG_PAIRING_INFORMATION,
    		    params, sizeof(params), &resp);
    		if (error)
    			return error;
    		len = resp.rap.params[1];
    		name = &resp.rap.params[2];
    	}
    
    	if (len > bufsiz - 1)
    		len = bufsiz - 1;
    	memcpy(buf, name, len);
    	buf[len] = '\0';
    	return 0;
    }
    
    int
    hidpp10_get_type(struct uhidpp_softc *sc, uint8_t device_id, const char **buf)
    {
    	struct uhidpp_report resp;
    	int error;
    	uint8_t type;
    
    	if (sc->sc_receiver == UHIDPP_RECEIVER_BOLT) {
    		uint8_t params[1] = { 0x50 + device_id };
    
    		error = hidpp_send_rap_report(sc,
    		    HIDPP_REPORT_ID_SHORT,
    		    HIDPP_DEVICE_ID_RECEIVER,
    		    HIDPP_GET_LONG_REGISTER,
    		    HIDPP_REG_PAIRING_INFORMATION,
    		    params, sizeof(params), &resp);
    		if (error)
    			return error;
    		type = resp.rap.params[1] & 0xf;
    	} else {
    		uint8_t params[1] = { 0x20 + (device_id - 1) };
    
    		error = hidpp_send_rap_report(sc,
    		    HIDPP_REPORT_ID_SHORT,
    		    HIDPP_DEVICE_ID_RECEIVER,
    		    HIDPP_GET_LONG_REGISTER,
    		    HIDPP_REG_PAIRING_INFORMATION,
    		    params, sizeof(params), &resp);
    		if (error)
    			return error;
    		type = resp.rap.params[7];
    	}
    
    	switch (type) {
    	case 0x00:
    		*buf = "unknown";
    		return 0;
    	case 0x01:
    		*buf = "keyboard";
    		return 0;
    	case 0x02:
    		*buf = "mouse";
    		return 0;
    	case 0x03:
    		*buf = "numpad";
    		return 0;
    	case 0x04:
    		*buf = "presenter";
    		return 0;
    	case 0x08:
    		*buf = "trackball";
    		return 0;
    	case 0x09:
    		*buf = "touchpad";
    		return 0;
    	}
    	return -ENOENT;
    }
    
    int
    hidpp10_enable_notifications(struct uhidpp_softc *sc, uint8_t device_id)
    {
    	struct uhidpp_report resp;
    	uint8_t params[3];
    
    	/* Device reporting flags. */
    	params[0] = HIDPP_ENABLE_REPORTS_DEVICE_BATTERY_STATUS;
    	/* Receiver reporting flags. */
    	params[1] = HIDPP_ENABLE_REPORTS_RECEIVER_WIRELESS |
    	    HIDPP_ENABLE_REPORTS_RECEIVER_SOFTWARE_PRESENT;
    	/* Device reporting flags (continued). */
    	params[2] = 0;
    
    	return hidpp_send_rap_report(sc,
    	    HIDPP_REPORT_ID_SHORT,
    	    device_id,
    	    HIDPP_SET_REGISTER,
    	    HIDPP_REG_ENABLE_REPORTS,
    	    params, sizeof(params), &resp);
    }
    
    int
    hidpp20_root_get_feature(struct uhidpp_softc *sc, uint8_t device_id,
        uint16_t feature, uint8_t *feature_idx, uint8_t *feature_type)
    {
    	struct uhidpp_report resp;
    	uint8_t params[2] = { feature >> 8, feature & 0xff };
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    device_id,
    	    HIDPP20_FEAT_ROOT_ID,
    	    HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC,
    	    params, sizeof(params), &resp);
    	if (error)
    		return error;
    
    	if (resp.fap.params[0] == 0)
    		return -ENOENT;
    
    	*feature_idx = resp.fap.params[0];
    	*feature_type = resp.fap.params[1];
    	return 0;
    }
    
    int
    hidpp20_feature_get_count(struct uhidpp_softc *sc, uint8_t device_id,
        uint8_t feature_idx, uint8_t *count)
    {
    	struct uhidpp_report resp;
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    device_id,
    	    feature_idx,
    	    HIDPP20_FEAT_FEATURE_COUNT_FUNC,
    	    NULL, 0, &resp);
    	if (error)
    		return error;
    
    	*count = resp.fap.params[0];
    	return 0;
    }
    
    int
    hidpp20_feature_get_id(struct uhidpp_softc *sc, uint8_t device_id,
        uint8_t feature_idx, uint8_t idx, uint16_t *id, uint8_t *type)
    {
    	struct uhidpp_report resp;
    	uint8_t params[1] = { idx };
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    device_id,
    	    feature_idx,
    	    HIDPP20_FEAT_FEATURE_ID_FUNC,
    	    params, sizeof(params), &resp);
    	if (error)
    		return error;
    
    	*id = bemtoh16(resp.fap.params);
    	*type = resp.fap.params[2];
    	return 0;
    }
    
    int
    hidpp20_battery_get_level_status(struct uhidpp_softc *sc,
        struct uhidpp_device *dev)
    {
    	struct uhidpp_report resp;
    	int charging, error;
    	uint8_t level, status;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    dev->d_id,
    	    dev->d_battery.feature_idx,
    	    HIDPP20_FEAT_BATTERY_LEVEL_STATUS_FUNC,
    	    NULL, 0, &resp);
    	if (error)
    		return error;
    
    	level = resp.fap.params[0];
    	/* next_level = resp.fap.params[1]; */
    	status = resp.fap.params[2];
    	/*
    	 * While charging, the reported level cannot be trusted. However, fake
    	 * the battery state once the charging is done.
    	 */
    	if (status == HIDPP20_LEVEL_STATUS_CHARGING_DONE) {
    		level = 100;
    		status = 0;
    	}
    
    	charging = hidpp20_battery_status_is_charging(status);
    
    	dev->d_battery.sens[0].value = level * 1000;
    	dev->d_battery.sens[0].flags &= ~SENSOR_FUNKNOWN;
    	if (dev->d_battery.nlevels < 10) {
    		/*
    		 * According to the HID++ 2.0 specification, less than
    		 * 10 levels should be mapped to the following 4 levels:
    		 *
    		 * [0, 10]   critical
    		 * [11, 30]  low
    		 * [31, 80]  good
    		 * [81, 100] full
    		 *
    		 * Since sensors are limited to 3 valid statuses, clamp
    		 * it even further. Unless the battery is charging in
    		 * which the level cannot be trusted.
    		 */
    		if (charging)
    			dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
    		else if (level <= 10)
    			dev->d_battery.sens[0].status = SENSOR_S_CRIT;
    		else if (level <= 30)
    			dev->d_battery.sens[0].status = SENSOR_S_WARN;
    		else
    			dev->d_battery.sens[0].status = SENSOR_S_OK;
    	} else {
    		/*
    		 * XXX the device supports battery mileage. The current
    		 * level must be checked against resp.fap.params[3]
    		 * given by hidpp20_battery_get_capability().
    		 */
    		dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
    	}
    
    	if (dev->d_battery.rechargeable)
    		dev->d_battery.sens[2].value = charging;
    
    	return 0;
    }
    
    int
    hidpp20_battery_get_capability(struct uhidpp_softc *sc,
        struct uhidpp_device *dev)
    {
    	struct uhidpp_report resp;
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    dev->d_id,
    	    dev->d_battery.feature_idx,
    	    HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC,
    	    NULL, 0, &resp);
    	if (error)
    		return error;
    	dev->d_battery.nlevels = resp.fap.params[0];
    	dev->d_battery.rechargeable = resp.fap.params[1] &
    	    HIDPP20_CAPABILITY_RECHARGEABLE;
    	return 0;
    }
    
    int
    hidpp20_unified_battery_get_capabilities(struct uhidpp_softc *sc,
        struct uhidpp_device *dev)
    {
    	struct uhidpp_report resp;
    	int error;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    dev->d_id,
    	    dev->d_battery.feature_idx,
    	    HIDPP20_FEAT_UNIFIED_BATTERY_CAPABILITIES_FUNC,
    	    NULL, 0, &resp);
    	if (error)
    		return error;
    	dev->d_battery.nlevels = nlevels(resp.fap.params[0]);
    	dev->d_battery.unified_level_mask = resp.fap.params[0];
    	dev->d_battery.rechargeable = resp.fap.params[1] &
    	    HIDPP20_CAPABILITES_RECHARGEABLE;
    	return 0;
    }
    
    int
    hidpp20_unified_battery_get_status(struct uhidpp_softc *sc,
        struct uhidpp_device *dev)
    {
    	struct uhidpp_report resp;
    	int charging, error;
    	uint8_t level, percentage, status;
    
    	error = hidpp_send_fap_report(sc,
    	    HIDPP_REPORT_ID_LONG,
    	    dev->d_id,
    	    dev->d_battery.feature_idx,
    	    HIDPP20_FEAT_UNIFIED_BATTERY_STATUS_FUNC,
    	    NULL, 0, &resp);
    	if (error)
    		return error;
    	percentage = resp.fap.params[0];
    	level = resp.fap.params[1] & dev->d_battery.unified_level_mask;
    	status = resp.fap.params[2];
    	/* external_power_status = resp.fap.params[3]; */
    
    	charging = hidpp20_unified_battery_status_is_charging(status);
    	dev->d_battery.sens[0].value = percentage * 1000;
    	dev->d_battery.sens[0].flags &= ~SENSOR_FUNKNOWN;
    	dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
    	/* Do not trust the level while charging. */
    	if (!charging) {
    		if (level & HIDPP20_BATTERY_STATUS_CRITICAL)
    			dev->d_battery.sens[0].status = SENSOR_S_CRIT;
    		else if (level & HIDPP20_BATTERY_STATUS_LOW)
    			dev->d_battery.sens[0].status = SENSOR_S_WARN;
    		else if (level & HIDPP20_BATTERY_STATUS_GOOD)
    			dev->d_battery.sens[0].status = SENSOR_S_OK;
    		else if (level & HIDPP20_BATTERY_STATUS_FULL)
    			dev->d_battery.sens[0].status = SENSOR_S_OK;
    	}
    	if (dev->d_battery.rechargeable)
    		dev->d_battery.sens[2].value = charging;
    	return 0;
    }
    
    int
    hidpp20_unified_battery_status_is_charging(uint8_t status)
    {
    	switch (status) {
    	case 1: /* charging */
    	case 2: /* charging slow */
    		return 1;
    	default:
    		return 0;
    	}
    }
    
    int
    hidpp20_battery_status_is_charging(uint8_t status)
    {
    	switch (status) {
    	case 1:	/* recharging */
    	case 2:	/* charge in final stage */
    	case 4:	/* recharging below optimal speed */
    		return 1;
    
    	case 3:	/* charge complete */
    		return 1;
    
    	case 0:	/* discharging */
    	case 5:	/* invalid battery type */
    	case 6:	/* thermal error */
    	case 7:	/* other charging error */
    	default:
    		return 0;
    	}
    }
    
    int
    hidpp_send_validate(uint8_t report_id, int nparams)
    {
    	if (report_id == HIDPP_REPORT_ID_SHORT) {
    		if (nparams > HIDPP_REPORT_SHORT_PARAMS_MAX)
    			return -EMSGSIZE;
    	} else if (report_id == HIDPP_REPORT_ID_LONG) {
    		if (nparams > HIDPP_REPORT_LONG_PARAMS_MAX)
    			return -EMSGSIZE;
    	} else {
    		return -EINVAL;
    	}
    	return 0;
    }
    
    int
    hidpp_send_fap_report(struct uhidpp_softc *sc, uint8_t report_id,
        uint8_t device_id, uint8_t feature_idx, uint8_t func_idx,
        uint8_t *params, int nparams, struct uhidpp_report *resp)
    {
    	struct uhidpp_report req;
    	int error;
    
    	error = hidpp_send_validate(report_id, nparams);
    	if (error)
    		return error;
    
    	memset(&req, 0, sizeof(req));
    	req.device_id = device_id;
    	req.fap.feature_idx = feature_idx;
    	sc->sc_swid = sc->sc_swid == HIDPP_SOFTWARE_ID_MAX ?
    	    HIDPP_SOFTWARE_ID_MIN : sc->sc_swid + 1;
    	req.fap.funcidx_swid =
    	    (func_idx << HIDPP_SOFTWARE_ID_LEN) | sc->sc_swid;
    	memcpy(req.fap.params, params, nparams);
    	return hidpp_send_report(sc, report_id, &req, resp);
    }
    
    int
    hidpp_send_rap_report(struct uhidpp_softc *sc, uint8_t report_id,
        uint8_t device_id, uint8_t sub_id, uint8_t reg_address,
        uint8_t *params, int nparams, struct uhidpp_report *resp)
    {
    	struct uhidpp_report req;
    	int error;
    
    	error = hidpp_send_validate(report_id, nparams);
    	if (error)
    		return error;
    
    	memset(&req, 0, sizeof(req));
    	req.device_id = device_id;
    	req.rap.sub_id = sub_id;
    	req.rap.reg_address = reg_address;
    	memcpy(req.rap.params, params, nparams);
    	return hidpp_send_report(sc, report_id, &req, resp);
    }
    
    int
    hidpp_send_report(struct uhidpp_softc *sc, uint8_t report_id,
        struct uhidpp_report *req, struct uhidpp_report *resp)
    {
    	int error = 0;
    	int len, n;
    
    	MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
    
    	if (report_id == HIDPP_REPORT_ID_SHORT)
    		len = HIDPP_REPORT_SHORT_LENGTH;
    	else if (report_id == HIDPP_REPORT_ID_LONG)
    		len = HIDPP_REPORT_LONG_LENGTH;
    	else
    		return -EINVAL;
    
    	DREPORT(__func__, report_id, (const unsigned char *)req, len);
    
    	/* Wait until any ongoing command has completed. */
    	while (sc->sc_resp_state != UHIDPP_RESP_NONE)
    		uhidpp_sleep(sc, INFSLP);
    	sc->sc_req = req;
    	sc->sc_resp = resp;
    	sc->sc_resp_state = UHIDPP_RESP_WAIT;
    	/*
    	 * The mutex must be temporarily released while calling
    	 * uhidev_set_report() as it might end up sleeping.
    	 */
    	mtx_leave(&sc->sc_mtx);
    
    	n = uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT,
    	    report_id, req, len);
    
    	mtx_enter(&sc->sc_mtx);
    	if (len != n) {
    		error = -EBUSY;
    		goto out;
    	}
    	/*
    	 * The interrupt could already have been received while the mutex was
    	 * released. Otherwise, wait for it.
    	 */
    	if (sc->sc_resp_state == UHIDPP_RESP_WAIT) {
    		/* Timeout taken from the hid-logitech-hidpp Linux driver. */
    		error = uhidpp_sleep(sc, SEC_TO_NSEC(5));
    		if (error) {
    			error = -error;
    			goto out;
    		}
    	}
    
    	if (sc->sc_resp_state == UHIDPP_RESP_ERROR)
    		error = -EIO;
    	else if (sc->sc_resp_state == HIDPP_REPORT_ID_SHORT &&
    	    resp->rap.sub_id == HIDPP_ERROR)
    		error = resp->rap.params[1];
    	else if (sc->sc_resp_state == HIDPP_REPORT_ID_LONG &&
    	    resp->fap.feature_idx == HIDPP20_ERROR)
    		error = resp->fap.params[1];
    
    out:
    	sc->sc_req = NULL;
    	sc->sc_resp = NULL;
    	sc->sc_resp_state = UHIDPP_RESP_NONE;
    	wakeup(sc);
    	return error;
    }
    
    #ifdef UHIDPP_DEBUG
    
    void
    uhidd_dump_report(const char *prefix, uint8_t repid, const unsigned char *buf,
        u_int buflen)
    {
    	u_int i;
    
    	printf("%s: %02x ", prefix, repid);
    	for (i = 0; i < buflen; i++) {
    		printf("%02x%s", buf[i],
    		    i == 2 ? " [" : (i + 1 < buflen ? " " : ""));
    	}
    	printf("]\n");
    }
    
    #endif