git: 01f2e864f795 - main - hid: Import usbhid - USB transport backend for HID subsystem.

Vladimir Kondratyev wulf at FreeBSD.org
Thu Jan 7 23:20:55 UTC 2021


The branch main has been updated by wulf:

URL: https://cgit.FreeBSD.org/src/commit/?id=01f2e864f79584c0cd250a8e7cfb501a9985768a

commit 01f2e864f79584c0cd250a8e7cfb501a9985768a
Author:     Vladimir Kondratyev <wulf at FreeBSD.org>
AuthorDate: 2020-10-08 21:32:12 +0000
Commit:     Vladimir Kondratyev <wulf at FreeBSD.org>
CommitDate: 2021-01-07 23:18:43 +0000

    hid: Import usbhid - USB transport backend for HID subsystem.
    
    This change implements hid_if.m methods for HID-over-USB protocol [1].
    
    Also, this change adds USBHID_ENABLED kernel option which changes
    device_probe() priority and adds/removes PnP records to prefer usbhid
    over ums, ukbd, wmt and other USB HID device drivers and vice-versa.
    
    The module is based on uhid(4) driver.  It is disabled by default for
    now due to conflicts with existing USB HID drivers.
    
    [1] https://www.usb.org/sites/default/files/hid1_11.pdf
    
    Reviewed by:    hselasky
    Differential revision:  https://reviews.freebsd.org/D27893
---
 share/man/man4/Makefile         |   1 +
 share/man/man4/usbhid.4         |  79 ++++
 sys/amd64/conf/GENERIC          |   3 +
 sys/conf/files                  |   1 +
 sys/conf/options                |   1 +
 sys/dev/hid/hidbus.c            |   1 +
 sys/dev/usb/input/uhid.c        |   2 +
 sys/dev/usb/input/ukbd.c        |   2 +
 sys/dev/usb/input/ums.c         |   2 +
 sys/dev/usb/input/usbhid.c      | 786 ++++++++++++++++++++++++++++++++++++++++
 sys/dev/usb/input/wmt.c         |   4 +
 sys/i386/conf/GENERIC           |   3 +
 sys/modules/usb/Makefile        |   3 +-
 sys/modules/usb/usbhid/Makefile |  10 +
 14 files changed, 897 insertions(+), 1 deletion(-)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index 5b0b55968afc..f18e93152353 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -1017,6 +1017,7 @@ MAN+=	\
 	usb.4 \
 	usb_quirk.4 \
 	usb_template.4 \
+	usbhid.4 \
 	usfs.4 \
 	uslcom.4 \
 	uvisor.4 \
diff --git a/share/man/man4/usbhid.4 b/share/man/man4/usbhid.4
new file mode 100644
index 000000000000..a8905dc76ea8
--- /dev/null
+++ b/share/man/man4/usbhid.4
@@ -0,0 +1,79 @@
+.\" Copyright (c) 2020 Vladimir Kondratyev <wulf at FreeBSD.org>
+.\"
+.\" 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.
+.\"
+.\" $FreeBSD$
+.\"
+.Dd September 21, 2020
+.Dt USBHID 4
+.Os
+.Sh NAME
+.Nm usbhid
+.Nd USB HID transport driver
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device usbhid"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+usbhid_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm
+driver provides a interface to USB Human Interface Devices (HIDs).
+.Sh SYSCTL VARIABLES
+The following variables are available as both
+.Xr sysctl 8
+variables and
+.Xr loader 8
+tunables:
+.Bl -tag -width indent
+.It Va hw.usb.usbhid.debug
+Debug output level, where 0 is debugging disabled and larger values increase
+debug message verbosity.
+Default is 0.
+.El
+.Sh SEE ALSO
+.Xr ehci 4 ,
+.Xr ohci 4 ,
+.Xr uhci 4 ,
+.Xr usb 4 ,
+.Xr xhci 4 ,
+.Xr usbconfig 8
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 13.0.
+.Sh AUTHORS
+.An -nosplit
+The
+.Nm
+driver was written by
+.An Vladimir Kondratyev Aq Mt wulf at FreeBSD.org .
diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC
index ee2a972d8302..94f184334648 100644
--- a/sys/amd64/conf/GENERIC
+++ b/sys/amd64/conf/GENERIC
@@ -385,3 +385,6 @@ device		uinput			# install /dev/uinput cdev
 options 	HID_DEBUG		# enable debug msgs
 device		hid			# Generic HID support
 options 	IICHID_SAMPLING		# Workaround missing GPIO INTR support
+#device		usbhid			# USB transport support.
+#device		hidbus			# HID bus (required by usbhid/iichid)
+#options 	USBHID_ENABLED		# Prefer usbhid to other USB drivers
diff --git a/sys/conf/files b/sys/conf/files
index 87b56eb0e8ae..6dd4e95815d1 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -3406,6 +3406,7 @@ dev/usb/input/uhid.c		optional uhid
 dev/usb/input/uhid_snes.c	optional uhid_snes
 dev/usb/input/ukbd.c		optional ukbd
 dev/usb/input/ums.c		optional ums
+dev/usb/input/usbhid.c		optional usbhid
 dev/usb/input/wmt.c		optional wmt
 dev/usb/input/wsp.c		optional wsp
 #
diff --git a/sys/conf/options b/sys/conf/options
index 1c0b643df899..48b7f362a80a 100644
--- a/sys/conf/options
+++ b/sys/conf/options
@@ -669,6 +669,7 @@ UKBD_DFLT_KEYMAP	opt_ukbd.h
 UPLCOM_INTR_INTERVAL	opt_uplcom.h
 UVSCOM_DEFAULT_OPKTSIZE	opt_uvscom.h
 UVSCOM_INTR_INTERVAL	opt_uvscom.h
+USBHID_ENABLED		opt_usb.h
 
 # options for the Realtek rtwn driver
 RTWN_DEBUG		opt_rtwn.h
diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c
index dfe1081c8888..be3564842988 100644
--- a/sys/dev/hid/hidbus.c
+++ b/sys/dev/hid/hidbus.c
@@ -904,3 +904,4 @@ driver_t hidbus_driver = {
 MODULE_DEPEND(hidbus, hid, 1, 1, 1);
 MODULE_VERSION(hidbus, 1);
 DRIVER_MODULE(hidbus, iichid, hidbus_driver, hidbus_devclass, 0, 0);
+DRIVER_MODULE(hidbus, usbhid, hidbus_driver, hidbus_devclass, 0, 0);
diff --git a/sys/dev/usb/input/uhid.c b/sys/dev/usb/input/uhid.c
index 0f87264b7033..dd463dd7de8c 100644
--- a/sys/dev/usb/input/uhid.c
+++ b/sys/dev/usb/input/uhid.c
@@ -911,4 +911,6 @@ DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, NULL, 0);
 MODULE_DEPEND(uhid, usb, 1, 1, 1);
 MODULE_DEPEND(uhid, hid, 1, 1, 1);
 MODULE_VERSION(uhid, 1);
+#ifndef USBHID_ENABLED
 USB_PNP_HOST_INFO(uhid_devs);
+#endif
diff --git a/sys/dev/usb/input/ukbd.c b/sys/dev/usb/input/ukbd.c
index 3314a3b3e70f..0edacbefcbfc 100644
--- a/sys/dev/usb/input/ukbd.c
+++ b/sys/dev/usb/input/ukbd.c
@@ -2192,4 +2192,6 @@ MODULE_DEPEND(ukbd, hid, 1, 1, 1);
 MODULE_DEPEND(ukbd, evdev, 1, 1, 1);
 #endif
 MODULE_VERSION(ukbd, 1);
+#ifndef USBHID_ENABLED
 USB_PNP_HOST_INFO(ukbd_devs);
+#endif
diff --git a/sys/dev/usb/input/ums.c b/sys/dev/usb/input/ums.c
index 6b7c3d526ad0..248478770942 100644
--- a/sys/dev/usb/input/ums.c
+++ b/sys/dev/usb/input/ums.c
@@ -1220,4 +1220,6 @@ MODULE_DEPEND(ums, hid, 1, 1, 1);
 MODULE_DEPEND(ums, evdev, 1, 1, 1);
 #endif
 MODULE_VERSION(ums, 1);
+#ifndef USBHID_ENABLED
 USB_PNP_HOST_INFO(ums_devs);
+#endif
diff --git a/sys/dev/usb/input/usbhid.c b/sys/dev/usb/input/usbhid.c
new file mode 100644
index 000000000000..ab856dc14d75
--- /dev/null
+++ b/sys/dev/usb/input/usbhid.c
@@ -0,0 +1,786 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-NetBSD
+ *
+ * Copyright (c) 1998 The NetBSD Foundation, Inc.
+ * Copyright (c) 2019 Vladimir Kondratyev <wulf at FreeBSD.org>
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Lennart Augustsson (lennart at augustsson.net) at
+ * Carlstedt Research & Technology.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/evdev/input.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define	USB_DEBUG_VAR usbhid_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#include "hid_if.h"
+
+#ifdef USB_DEBUG
+static int usbhid_debug = 0;
+
+static SYSCTL_NODE(_hw_usb, OID_AUTO, usbhid, CTLFLAG_RW, 0, "USB usbhid");
+SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, debug, CTLFLAG_RWTUN,
+    &usbhid_debug, 0, "Debug level");
+#endif
+
+enum {
+	USBHID_INTR_OUT_DT,
+	USBHID_INTR_IN_DT,
+	USBHID_CTRL_DT,
+	USBHID_N_TRANSFER,
+};
+
+struct usbhid_xfer_ctx;
+typedef int usbhid_callback_t(struct usbhid_xfer_ctx *xfer_ctx);
+
+union usbhid_device_request {
+	struct {			/* INTR xfers */
+		uint16_t maxlen;
+		uint16_t actlen;
+	} intr;
+	struct usb_device_request ctrl;	/* CTRL xfers */
+};
+
+/* Syncronous USB transfer context */
+struct usbhid_xfer_ctx {
+	union usbhid_device_request req;
+	uint8_t *buf;
+	int error;
+	usbhid_callback_t *cb;
+	void *cb_ctx;
+	int waiters;
+	bool influx;
+};
+
+struct usbhid_softc {
+	hid_intr_t *sc_intr_handler;
+	void *sc_intr_ctx;
+	void *sc_intr_buf;
+
+	struct hid_device_info sc_hw;
+
+	struct mtx sc_mtx;
+	struct usb_config sc_config[USBHID_N_TRANSFER];
+	struct usb_xfer *sc_xfer[USBHID_N_TRANSFER];
+	struct usbhid_xfer_ctx sc_xfer_ctx[USBHID_N_TRANSFER];
+
+	struct usb_device *sc_udev;
+	uint8_t	sc_iface_no;
+	uint8_t	sc_iface_index;
+};
+
+/* prototypes */
+
+static device_probe_t usbhid_probe;
+static device_attach_t usbhid_attach;
+static device_detach_t usbhid_detach;
+
+static usb_callback_t usbhid_intr_out_callback;
+static usb_callback_t usbhid_intr_in_callback;
+static usb_callback_t usbhid_ctrl_callback;
+
+static usbhid_callback_t usbhid_intr_handler_cb;
+static usbhid_callback_t usbhid_sync_wakeup_cb;
+
+static void
+usbhid_intr_out_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	int len;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+	case USB_ST_SETUP:
+tr_setup:
+		len = xfer_ctx->req.intr.maxlen;
+		if (len == 0) {
+			if (USB_IN_POLLING_MODE_FUNC())
+				xfer_ctx->error = 0;
+			return;
+		}
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_in(pc, 0, xfer_ctx->buf, len);
+		usbd_xfer_set_frame_len(xfer, 0, len);
+		usbd_transfer_submit(xfer);
+		xfer_ctx->req.intr.maxlen = 0;
+		if (USB_IN_POLLING_MODE_FUNC())
+			return;
+		xfer_ctx->error = 0;
+		goto tr_exit;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto tr_setup;
+		}
+		xfer_ctx->error = EIO;
+tr_exit:
+		(void)xfer_ctx->cb(xfer_ctx);
+		return;
+	}
+}
+
+static void
+usbhid_intr_in_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+	struct usb_page_cache *pc;
+	int actlen;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_TRANSFERRED:
+		DPRINTF("transferred!\n");
+
+		usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL);
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_out(pc, 0, xfer_ctx->buf, actlen);
+		xfer_ctx->req.intr.actlen = actlen;
+		if (xfer_ctx->cb(xfer_ctx) != 0)
+			return;
+
+	case USB_ST_SETUP:
+re_submit:
+		usbd_xfer_set_frame_len(xfer, 0, xfer_ctx->req.intr.maxlen);
+		usbd_transfer_submit(xfer);
+		return;
+
+	default:			/* Error */
+		if (error != USB_ERR_CANCELLED) {
+			/* try to clear stall first */
+			usbd_xfer_set_stall(xfer);
+			goto re_submit;
+		}
+		return;
+	}
+}
+
+static void
+usbhid_ctrl_callback(struct usb_xfer *xfer, usb_error_t error)
+{
+	struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer);
+	struct usb_device_request *req = &xfer_ctx->req.ctrl;
+	struct usb_page_cache *pc;
+	int len = UGETW(req->wLength);
+	bool is_rd = (req->bmRequestType & UT_READ) != 0;
+
+	switch (USB_GET_STATE(xfer)) {
+	case USB_ST_SETUP:
+		if (!is_rd && len != 0) {
+			pc = usbd_xfer_get_frame(xfer, 1);
+			usbd_copy_in(pc, 0, xfer_ctx->buf, len);
+		}
+
+		pc = usbd_xfer_get_frame(xfer, 0);
+		usbd_copy_in(pc, 0, req, sizeof(*req));
+		usbd_xfer_set_frame_len(xfer, 0, sizeof(*req));
+		if (len != 0)
+			usbd_xfer_set_frame_len(xfer, 1, len);
+		usbd_xfer_set_frames(xfer, len != 0 ? 2 : 1);
+		usbd_transfer_submit(xfer);
+		return;
+
+	case USB_ST_TRANSFERRED:
+		if (is_rd && len != 0) {
+			pc = usbd_xfer_get_frame(xfer, 0);
+			usbd_copy_out(pc, sizeof(*req), xfer_ctx->buf, len);
+		}
+		xfer_ctx->error = 0;
+		goto tr_exit;
+
+	default:			/* Error */
+		/* bomb out */
+		DPRINTFN(1, "error=%s\n", usbd_errstr(error));
+		xfer_ctx->error = EIO;
+tr_exit:
+		(void)xfer_ctx->cb(xfer_ctx);
+		return;
+	}
+}
+
+static int
+usbhid_intr_handler_cb(struct usbhid_xfer_ctx *xfer_ctx)
+{
+	struct usbhid_softc *sc = xfer_ctx->cb_ctx;
+
+	sc->sc_intr_handler(sc->sc_intr_ctx, xfer_ctx->buf,
+	    xfer_ctx->req.intr.actlen);
+
+	return (0);
+}
+
+static int
+usbhid_sync_wakeup_cb(struct usbhid_xfer_ctx *xfer_ctx)
+{
+
+	if (!USB_IN_POLLING_MODE_FUNC())
+		wakeup(xfer_ctx->cb_ctx);
+
+	return (ECANCELED);
+}
+
+static const struct usb_config usbhid_config[USBHID_N_TRANSFER] = {
+
+	[USBHID_INTR_OUT_DT] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_OUT,
+		.flags = {.pipe_bof = 1,.proxy_buffer = 1},
+		.callback = &usbhid_intr_out_callback,
+	},
+	[USBHID_INTR_IN_DT] = {
+		.type = UE_INTERRUPT,
+		.endpoint = UE_ADDR_ANY,
+		.direction = UE_DIR_IN,
+		.flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1},
+		.callback = &usbhid_intr_in_callback,
+	},
+	[USBHID_CTRL_DT] = {
+		.type = UE_CONTROL,
+		.endpoint = 0x00,	/* Control pipe */
+		.direction = UE_DIR_ANY,
+		.flags = {.proxy_buffer = 1},
+		.callback = &usbhid_ctrl_callback,
+		.timeout = 1000,	/* 1 second */
+	},
+};
+
+static void
+usbhid_intr_setup(device_t dev, hid_intr_t intr, void *context,
+    struct hid_rdesc_info *rdesc)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	uint16_t n;
+	bool nowrite;
+	int error;
+
+	sc->sc_intr_handler = intr;
+	sc->sc_intr_ctx = context;
+	bcopy(usbhid_config, sc->sc_config, sizeof(usbhid_config));
+
+	/* Set buffer sizes to match HID report sizes */
+	sc->sc_config[USBHID_INTR_OUT_DT].bufsize = rdesc->osize;
+	sc->sc_config[USBHID_INTR_IN_DT].bufsize = rdesc->isize;
+	sc->sc_config[USBHID_CTRL_DT].bufsize =
+	    MAX(rdesc->isize, MAX(rdesc->osize, rdesc->fsize));
+
+	nowrite = hid_test_quirk(&sc->sc_hw, HQ_NOWRITE);
+
+	/*
+	 * Setup the USB transfers one by one, so they are memory independent
+	 * which allows for handling panics triggered by the HID drivers
+	 * itself, typically by hkbd via CTRL+ALT+ESC sequences. Or if the HID
+	 * keyboard driver was processing a key at the moment of panic.
+	 */
+	for (n = 0; n != USBHID_N_TRANSFER; n++) {
+		if (nowrite && n == USBHID_INTR_OUT_DT)
+			continue;
+		error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index,
+		    sc->sc_xfer + n, sc->sc_config + n, 1,
+		    (void *)(sc->sc_xfer_ctx + n), &sc->sc_mtx);
+		if (error)
+			break;
+	}
+
+	if (error)
+		DPRINTF("error=%s\n", usbd_errstr(error));
+
+	rdesc->rdsize = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]);
+	rdesc->grsize = usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]);
+	rdesc->srsize = rdesc->grsize;
+	rdesc->wrsize = nowrite ? rdesc->srsize :
+	    usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]);
+
+	sc->sc_intr_buf = malloc(rdesc->rdsize, M_USBDEV, M_ZERO | M_WAITOK);
+}
+
+static void
+usbhid_intr_unsetup(device_t dev)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+
+	usbd_transfer_unsetup(sc->sc_xfer, USBHID_N_TRANSFER);
+	free(sc->sc_intr_buf, M_USBDEV);
+}
+
+static int
+usbhid_intr_start(device_t dev)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+
+	mtx_lock(&sc->sc_mtx);
+	sc->sc_xfer_ctx[USBHID_INTR_IN_DT] = (struct usbhid_xfer_ctx) {
+		.req.intr.maxlen =
+		    usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]),
+		.cb = usbhid_intr_handler_cb,
+		.cb_ctx = sc,
+		.buf = sc->sc_intr_buf,
+	};
+	usbd_transfer_start(sc->sc_xfer[USBHID_INTR_IN_DT]);
+	mtx_unlock(&sc->sc_mtx);
+
+	return (0);
+}
+
+static int
+usbhid_intr_stop(device_t dev)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+
+	usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_IN_DT]);
+	usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_OUT_DT]);
+
+	return (0);
+}
+
+static void
+usbhid_intr_poll(device_t dev)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+
+	usbd_transfer_poll(sc->sc_xfer + USBHID_INTR_IN_DT, 1);
+}
+
+/*
+ * HID interface
+ */
+static int
+usbhid_sync_xfer(struct usbhid_softc* sc, int xfer_idx,
+    union usbhid_device_request *req, void *buf)
+{
+	int error, timeout;
+	struct usbhid_xfer_ctx *xfer_ctx, save;
+
+	xfer_ctx = sc->sc_xfer_ctx + xfer_idx;
+
+	if (USB_IN_POLLING_MODE_FUNC()) {
+		save = *xfer_ctx;
+	} else {
+		mtx_lock(&sc->sc_mtx);
+		++xfer_ctx->waiters;
+		while (xfer_ctx->influx)
+			mtx_sleep(&xfer_ctx->waiters, &sc->sc_mtx, 0,
+			    "usbhid wt", 0);
+		--xfer_ctx->waiters;
+		xfer_ctx->influx = true;
+	}
+
+	xfer_ctx->buf = buf;
+	xfer_ctx->req = *req;
+	xfer_ctx->error = ETIMEDOUT;
+	xfer_ctx->cb = &usbhid_sync_wakeup_cb;
+	xfer_ctx->cb_ctx = xfer_ctx;
+	timeout = USB_DEFAULT_TIMEOUT;
+	usbd_transfer_start(sc->sc_xfer[xfer_idx]);
+
+	if (USB_IN_POLLING_MODE_FUNC())
+		while (timeout > 0 && xfer_ctx->error == ETIMEDOUT) {
+			usbd_transfer_poll(sc->sc_xfer + xfer_idx, 1);
+			DELAY(1000);
+			timeout--;
+		}
+	 else
+		msleep_sbt(xfer_ctx, &sc->sc_mtx, 0, "usbhid io",
+		    SBT_1MS * timeout, 0, C_HARDCLOCK);
+
+	/* Perform usbhid_write() asyncronously to improve pipelining */
+	if (USB_IN_POLLING_MODE_FUNC() || xfer_ctx->error != 0 ||
+	    sc->sc_config[xfer_idx].type != UE_INTERRUPT ||
+	    sc->sc_config[xfer_idx].direction != UE_DIR_OUT)
+		usbd_transfer_stop(sc->sc_xfer[xfer_idx]);
+	error = xfer_ctx->error;
+	if (error == 0)
+		*req = xfer_ctx->req;
+
+	if (USB_IN_POLLING_MODE_FUNC()) {
+		*xfer_ctx = save;
+	} else {
+		xfer_ctx->influx = false;
+		if (xfer_ctx->waiters != 0)
+			wakeup_one(&xfer_ctx->waiters);
+		mtx_unlock(&sc->sc_mtx);
+	}
+
+	if (error)
+		DPRINTF("USB IO error:%d\n", error);
+
+	return (error);
+}
+
+static int
+usbhid_get_rdesc(device_t dev, void *buf, hid_size_t len)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	int error;
+
+	error = usbd_req_get_report_descriptor(sc->sc_udev, NULL,
+	    buf, len, sc->sc_iface_index);
+
+	if (error)
+		DPRINTF("no report descriptor: %s\n", usbd_errstr(error));
+
+	return (error == 0 ? 0 : ENXIO);
+}
+
+static int
+usbhid_get_report(device_t dev, void *buf, hid_size_t maxlen,
+    hid_size_t *actlen, uint8_t type, uint8_t id)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+	int error;
+
+	if (maxlen > usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]))
+		return (ENOBUFS);
+
+	req.ctrl.bmRequestType = UT_READ_CLASS_INTERFACE;
+	req.ctrl.bRequest = UR_GET_REPORT;
+	USETW2(req.ctrl.wValue, type, id);
+	req.ctrl.wIndex[0] = sc->sc_iface_no;
+	req.ctrl.wIndex[1] = 0;
+	USETW(req.ctrl.wLength, maxlen);
+
+	error = usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, buf);
+	if (!error && actlen != NULL)
+		*actlen = maxlen;
+
+	return (error);
+}
+
+static int
+usbhid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type,
+    uint8_t id)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+
+	if (len > usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]))
+		return (ENOBUFS);
+
+	req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.ctrl.bRequest = UR_SET_REPORT;
+	USETW2(req.ctrl.wValue, type, id);
+	req.ctrl.wIndex[0] = sc->sc_iface_no;
+	req.ctrl.wIndex[1] = 0;
+	USETW(req.ctrl.wLength, len);
+
+	return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req,
+	    __DECONST(void *, buf)));
+}
+
+static int
+usbhid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+	int error;
+
+	if (maxlen > usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]))
+		return (ENOBUFS);
+
+	req.intr.maxlen = maxlen;
+	error = usbhid_sync_xfer(sc, USBHID_INTR_IN_DT, &req, buf);
+	if (error == 0 && actlen != NULL)
+		*actlen = req.intr.actlen;
+
+	return (error);
+}
+
+static int
+usbhid_write(device_t dev, const void *buf, hid_size_t len)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+
+	if (len > usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]))
+		return (ENOBUFS);
+
+	req.intr.maxlen = len;
+	return (usbhid_sync_xfer(sc, USBHID_INTR_OUT_DT, &req,
+	    __DECONST(void *, buf)));
+}
+
+static int
+usbhid_set_idle(device_t dev, uint16_t duration, uint8_t id)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+
+	/* Duration is measured in 4 milliseconds per unit. */
+	req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.ctrl.bRequest = UR_SET_IDLE;
+	USETW2(req.ctrl.wValue, (duration + 3) / 4, id);
+	req.ctrl.wIndex[0] = sc->sc_iface_no;
+	req.ctrl.wIndex[1] = 0;
+	USETW(req.ctrl.wLength, 0);
+
+	return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL));
+}
+
+static int
+usbhid_set_protocol(device_t dev, uint16_t protocol)
+{
+	struct usbhid_softc* sc = device_get_softc(dev);
+	union usbhid_device_request req;
+
+	req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE;
+	req.ctrl.bRequest = UR_SET_PROTOCOL;
+	USETW(req.ctrl.wValue, protocol);
+	req.ctrl.wIndex[0] = sc->sc_iface_no;
+	req.ctrl.wIndex[1] = 0;
+	USETW(req.ctrl.wLength, 0);
+
+	return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL));
+}
+
+static void
+usbhid_init_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw)
+{
+
+	hw->idBus = BUS_USB;
+	hw->idVendor = uaa->info.idVendor;
+	hw->idProduct = uaa->info.idProduct;
+	hw->idVersion = uaa->info.bcdDevice;
+
+	/* Set various quirks based on usb_attach_arg */
+	hid_add_dynamic_quirk(hw, USB_GET_DRIVER_INFO(uaa));
+}
+
+static void
+usbhid_fill_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw)
+{
+	struct usb_device *udev = uaa->device;
+	struct usb_interface *iface = uaa->iface;
+	struct usb_hid_descriptor *hid;
+	struct usb_endpoint *ep;
+
+	snprintf(hw->name, sizeof(hw->name), "%s %s",
+	    usb_get_manufacturer(udev), usb_get_product(udev));
+	strlcpy(hw->serial, usb_get_serial(udev), sizeof(hw->serial));
+
+	if (uaa->info.bInterfaceClass == UICLASS_HID &&
+	    iface != NULL && iface->idesc != NULL) {
+		hid = hid_get_descriptor_from_usb(
+		    usbd_get_config_descriptor(udev), iface->idesc);
+		if (hid != NULL)
+			hw->rdescsize =
+			    UGETW(hid->descrs[0].wDescriptorLength);
+	}
+
+	/* See if there is a interrupt out endpoint. */
+	ep = usbd_get_endpoint(udev, uaa->info.bIfaceIndex,
+	    usbhid_config + USBHID_INTR_OUT_DT);
+	if (ep == NULL || ep->methods == NULL)
+		hid_add_dynamic_quirk(hw, HQ_NOWRITE);
+}
+
+static const STRUCT_USB_HOST_ID usbhid_devs[] = {
+	/* the Xbox 360 gamepad doesn't use the HID class */
+	{USB_IFACE_CLASS(UICLASS_VENDOR),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER),
+	 USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),
+	 USB_DRIVER_INFO(HQ_IS_XBOX360GP)},
+	/* HID keyboard with boot protocol support */
+	{USB_IFACE_CLASS(UICLASS_HID),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+	 USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD),
+	 USB_DRIVER_INFO(HQ_HAS_KBD_BOOTPROTO)},
+	/* HID mouse with boot protocol support */
+	{USB_IFACE_CLASS(UICLASS_HID),
+	 USB_IFACE_SUBCLASS(UISUBCLASS_BOOT),
+	 USB_IFACE_PROTOCOL(UIPROTO_MOUSE),
+	 USB_DRIVER_INFO(HQ_HAS_MS_BOOTPROTO)},
+	/* generic HID class */
+	{USB_IFACE_CLASS(UICLASS_HID), USB_DRIVER_INFO(HQ_NONE)},
+};
+
+static int
+usbhid_probe(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	struct usbhid_softc *sc = device_get_softc(dev);
+	int error;
+
+	DPRINTFN(11, "\n");
+
+	if (uaa->usb_mode != USB_MODE_HOST)
+		return (ENXIO);
+
+	error = usbd_lookup_id_by_uaa(usbhid_devs, sizeof(usbhid_devs), uaa);
+	if (error)
+		return (error);
+
+	if (usb_test_quirk(uaa, UQ_HID_IGNORE))
+		return (ENXIO);
+
+	/*
+	 * Setup temporary hid_device_info so that we can figure out some
+	 * basic quirks for this device.
+	 */
+	usbhid_init_device_info(uaa, &sc->sc_hw);
+
+	if (hid_test_quirk(&sc->sc_hw, HQ_HID_IGNORE))
+		return (ENXIO);
+
+#ifdef USBHID_ENABLED
+	return (BUS_PROBE_GENERIC + 1);
+#else
+	return (BUS_PROBE_GENERIC - 1);
+#endif
+}
+
+static int
+usbhid_attach(device_t dev)
+{
+	struct usb_attach_arg *uaa = device_get_ivars(dev);
+	struct usbhid_softc *sc = device_get_softc(dev);
+	device_t child;
+	int error = 0;
+
+	DPRINTFN(10, "sc=%p\n", sc);
+
+	device_set_usb_desc(dev);
+
+	sc->sc_udev = uaa->device;
+	sc->sc_iface_no = uaa->info.bIfaceNum;
+	sc->sc_iface_index = uaa->info.bIfaceIndex;
+
+	usbhid_fill_device_info(uaa, &sc->sc_hw);
+
+	error = usbd_req_set_idle(uaa->device, NULL,
+	    uaa->info.bIfaceIndex, 0, 0);
+	if (error)
+		DPRINTF("set idle failed, error=%s (ignored)\n",
+		    usbd_errstr(error));
+
+	mtx_init(&sc->sc_mtx, "usbhid lock", NULL, MTX_DEF);
+
+	child = device_add_child(dev, "hidbus", -1);
+	if (child == NULL) {
+		device_printf(dev, "Could not add hidbus device\n");
+		usbhid_detach(dev);
+		return (ENOMEM);
+	}
+
+	device_set_ivars(child, &sc->sc_hw);
+	error = bus_generic_attach(dev);
+	if (error) {
+		device_printf(dev, "failed to attach child: %d\n", error);
+		usbhid_detach(dev);
+		return (error);
+	}
+
+	return (0);			/* success */
+}
+
+static int
+usbhid_detach(device_t dev)
+{
+	struct usbhid_softc *sc = device_get_softc(dev);
+
+	device_delete_children(dev);
+	mtx_destroy(&sc->sc_mtx);
+
+	return (0);
+}
+
+static devclass_t usbhid_devclass;
+
+static device_method_t usbhid_methods[] = {
+	DEVMETHOD(device_probe,		usbhid_probe),
+	DEVMETHOD(device_attach,	usbhid_attach),
+	DEVMETHOD(device_detach,	usbhid_detach),
+
+	DEVMETHOD(hid_intr_setup,	usbhid_intr_setup),
+	DEVMETHOD(hid_intr_unsetup,	usbhid_intr_unsetup),
+	DEVMETHOD(hid_intr_start,	usbhid_intr_start),
+	DEVMETHOD(hid_intr_stop,	usbhid_intr_stop),
+	DEVMETHOD(hid_intr_poll,	usbhid_intr_poll),
+
+	/* HID interface */
+	DEVMETHOD(hid_get_rdesc,	usbhid_get_rdesc),
+	DEVMETHOD(hid_read,		usbhid_read),
+	DEVMETHOD(hid_write,		usbhid_write),
+	DEVMETHOD(hid_get_report,	usbhid_get_report),
*** 87 LINES SKIPPED ***


More information about the dev-commits-src-all mailing list