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
This commit is contained in:
Vladimir Kondratyev 2020-10-09 00:32:12 +03:00
parent b1f1b07f6d
commit 01f2e864f7
14 changed files with 897 additions and 1 deletions

View File

@ -1017,6 +1017,7 @@ MAN+= \
usb.4 \
usb_quirk.4 \
usb_template.4 \
usbhid.4 \
usfs.4 \
uslcom.4 \
uvisor.4 \

79
share/man/man4/usbhid.4 Normal file
View File

@ -0,0 +1,79 @@
.\" Copyright (c) 2020 Vladimir Kondratyev <wulf@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@FreeBSD.org .

View File

@ -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

View File

@ -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
#

View File

@ -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

View File

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

View File

@ -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

View File

@ -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

View File

@ -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

786
sys/dev/usb/input/usbhid.c Normal file
View File

@ -0,0 +1,786 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-NetBSD
*
* Copyright (c) 1998 The NetBSD Foundation, Inc.
* Copyright (c) 2019 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* This code is derived from software contributed to The NetBSD Foundation
* by Lennart Augustsson (lennart@augustsson.net) at
* Carlstedt Research & Technology.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#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),
DEVMETHOD(hid_set_report, usbhid_set_report),
DEVMETHOD(hid_set_idle, usbhid_set_idle),
DEVMETHOD(hid_set_protocol, usbhid_set_protocol),
DEVMETHOD_END
};
static driver_t usbhid_driver = {
.name = "usbhid",
.methods = usbhid_methods,
.size = sizeof(struct usbhid_softc),
};
DRIVER_MODULE(usbhid, uhub, usbhid_driver, usbhid_devclass, NULL, 0);
MODULE_DEPEND(usbhid, usb, 1, 1, 1);
MODULE_DEPEND(usbhid, hid, 1, 1, 1);
MODULE_DEPEND(usbhid, hidbus, 1, 1, 1);
MODULE_VERSION(usbhid, 1);
#ifdef USBHID_ENABLED
USB_PNP_HOST_INFO(usbhid_devs);
#endif

View File

@ -1009,11 +1009,13 @@ wmt_set_input_mode(struct wmt_softc *sc, enum wmt_input_mode mode)
return (err);
}
#ifndef USBHID_ENABLED
static const STRUCT_USB_HOST_ID wmt_devs[] = {
/* generic HID class w/o boot interface */
{USB_IFACE_CLASS(UICLASS_HID),
USB_IFACE_SUBCLASS(0),},
};
#endif
static devclass_t wmt_devclass;
@ -1036,4 +1038,6 @@ MODULE_DEPEND(wmt, usb, 1, 1, 1);
MODULE_DEPEND(wmt, hid, 1, 1, 1);
MODULE_DEPEND(wmt, evdev, 1, 1, 1);
MODULE_VERSION(wmt, 1);
#ifndef USBHID_ENABLED
USB_PNP_HOST_INFO(wmt_devs);
#endif

View File

@ -353,3 +353,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

View File

@ -47,7 +47,8 @@ SUBDIR = usb
SUBDIR += ${_dwc_otg} ehci ${_musb} ohci uhci xhci ${_uss820dci} \
${_atmegadci} ${_avr32dci} ${_rsu} ${_rsufw} ${_saf1761otg}
SUBDIR += ${_rum} ${_run} ${_runfw} ${_uath} upgt usie ural ${_zyd} ${_urtw}
SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled
SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled \
usbhid
SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \
umct umcs umodem umoscom uplcom uslcom uvisor uvscom
SUBDIR += cp2112

View File

@ -0,0 +1,10 @@
# $FreeBSD$
S= ${SRCTOP}/sys
.PATH: $S/dev/usb/input
KMOD= usbhid
SRCS= opt_usb.h bus_if.h device_if.h hid_if.h usb_if.h usbhid.c
.include <bsd.kmod.mk>