diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1017,6 +1017,7 @@ 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 --- /dev/null +++ b/share/man/man4/usbhid.4 @@ -0,0 +1,79 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" 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 . diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC --- a/sys/amd64/conf/GENERIC +++ b/sys/amd64/conf/GENERIC @@ -385,3 +385,6 @@ 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 --- a/sys/conf/files +++ b/sys/conf/files @@ -3406,6 +3406,7 @@ 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 --- a/sys/conf/options +++ b/sys/conf/options @@ -669,6 +669,7 @@ 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 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -904,3 +904,4 @@ 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 --- a/sys/dev/usb/input/uhid.c +++ b/sys/dev/usb/input/uhid.c @@ -911,4 +911,6 @@ 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 --- a/sys/dev/usb/input/ukbd.c +++ b/sys/dev/usb/input/ukbd.c @@ -2192,4 +2192,6 @@ 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 --- a/sys/dev/usb/input/ums.c +++ b/sys/dev/usb/input/ums.c @@ -1220,4 +1220,6 @@ 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 --- /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 + * + * 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 +__FBSDID("$FreeBSD$"); + +/* + * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usbhid_debug +#include + +#include + +#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 diff --git a/sys/dev/usb/input/wmt.c b/sys/dev/usb/input/wmt.c --- a/sys/dev/usb/input/wmt.c +++ b/sys/dev/usb/input/wmt.c @@ -1009,11 +1009,13 @@ 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, 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 diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC --- a/sys/i386/conf/GENERIC +++ b/sys/i386/conf/GENERIC @@ -353,3 +353,6 @@ 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/modules/usb/Makefile b/sys/modules/usb/Makefile --- a/sys/modules/usb/Makefile +++ b/sys/modules/usb/Makefile @@ -47,7 +47,8 @@ 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 diff --git a/sys/modules/usb/usbhid/Makefile b/sys/modules/usb/usbhid/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/usb/usbhid/Makefile @@ -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