hid: Import hidbus(4)

This driver provides support for multiple HID driver attachments
to single HID transport backend. This ability existed in Net/OpenBSD
(uhidev and ihidev drivers) but has never been ported to FreeBSD.
Unlike Net/OpenBSD we do not use report number alone to distinct report
source but we follow MS way and use a top level collection (TLC) usage
index that report belongs to as a location key.

The driver performs child device autodiscovery based on HID report
descriptor data, proxying of HID requests from child devices to parent
transport backends and broadcasting of interrupts in backward direction.

Differential revision:	https://reviews.freebsd.org/D27888
This commit is contained in:
Vladimir Kondratyev 2020-10-09 04:10:23 +03:00
parent 961a3535db
commit 2b4464b0b1
7 changed files with 1196 additions and 1 deletions

View File

@ -179,6 +179,7 @@ MAN= aac.4 \
gpioths.4 \
gre.4 \
h_ertt.4 \
hidbus.4 \
hifn.4 \
hpet.4 \
${_hpt27xx.4} \

102
share/man/man4/hidbus.4 Normal file
View File

@ -0,0 +1,102 @@
.\" 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 14, 2020
.Dt HIDBUS 4
.Os
.Sh NAME
.Nm hidbus
.Nd generic HID bus 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 hidbus"
.Cd "device hid"
.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
hidbus_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides support for multiple HID driver attachments to single HID
transport backend.
See
.Xr iichid 4
or
.Xr usbhid 4 .
.Pp
Each HID device can have several components, e.g., a keyboard and
a mouse.
These components use different report identifiers (a byte) combined into
groups called collections to distinguish which one data is coming from.
The
.Nm
driver has other drivers attached that handle particular
kinds of devices and
.Nm
broadcasts data to all of them.
.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.hid.hidbus.debug
Debug output level, where 0 is debugging disabled and larger values increase
debug message verbosity.
Default is 0.
.El
.Sh SEE ALSO
.Xr hconf 4 ,
.Xr hcons 4 ,
.Xr hgame 4 ,
.Xr hidraw 4 ,
.Xr hkbd 4 ,
.Xr hms 4 ,
.Xr hmt 4 ,
.Xr hpen 4 ,
.Xr hsctrl 4 ,
.Xr hskbd 4 ,
.Xr iichid 4 ,
.Xr usbhid 4
.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

@ -1817,6 +1817,7 @@ dev/gpio/gpiopps.c optional gpiopps fdt
dev/gpio/ofw_gpiobus.c optional fdt gpio
dev/hid/hid.c optional hid
dev/hid/hid_if.m optional hid
dev/hid/hidbus.c optional hidbus
dev/hifn/hifn7751.c optional hifn
dev/hptiop/hptiop.c optional hptiop scbus
dev/hwpmc/hwpmc_logging.c optional hwpmc

905
sys/dev/hid/hidbus.c Normal file
View File

@ -0,0 +1,905 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2019-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.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/ck.h>
#include <sys/epoch.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/libkern.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/mutex.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/sx.h>
#define HID_DEBUG_VAR hid_debug
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidquirk.h>
#include "hid_if.h"
#define INPUT_EPOCH global_epoch_preempt
#define HID_RSIZE_MAX 1024
static hid_intr_t hidbus_intr;
static device_probe_t hidbus_probe;
static device_attach_t hidbus_attach;
static device_detach_t hidbus_detach;
struct hidbus_ivars {
int32_t usage;
uint8_t index;
uint32_t flags;
uintptr_t driver_info; /* for internal use */
struct mtx *mtx; /* child intr mtx */
hid_intr_t *intr_handler; /* executed under mtx*/
void *intr_ctx;
unsigned int refcnt; /* protected by mtx */
struct epoch_context epoch_ctx;
CK_STAILQ_ENTRY(hidbus_ivars) link;
};
struct hidbus_softc {
device_t dev;
struct sx sx;
struct mtx mtx;
bool nowrite;
struct hid_rdesc_info rdesc;
bool overloaded;
int nest; /* Child attach nesting lvl */
int nauto; /* Number of autochildren */
CK_STAILQ_HEAD(, hidbus_ivars) tlcs;
};
static int
hidbus_fill_rdesc_info(struct hid_rdesc_info *hri, const void *data,
hid_size_t len)
{
int error = 0;
hri->data = __DECONST(void *, data);
hri->len = len;
/*
* If report descriptor is not available yet, set maximal
* report sizes high enough to allow hidraw to work.
*/
hri->isize = len == 0 ? HID_RSIZE_MAX :
hid_report_size_max(data, len, hid_input, &hri->iid);
hri->osize = len == 0 ? HID_RSIZE_MAX :
hid_report_size_max(data, len, hid_output, &hri->oid);
hri->fsize = len == 0 ? HID_RSIZE_MAX :
hid_report_size_max(data, len, hid_feature, &hri->fid);
if (hri->isize > HID_RSIZE_MAX) {
DPRINTF("input size is too large, %u bytes (truncating)\n",
hri->isize);
hri->isize = HID_RSIZE_MAX;
error = EOVERFLOW;
}
if (hri->osize > HID_RSIZE_MAX) {
DPRINTF("output size is too large, %u bytes (truncating)\n",
hri->osize);
hri->osize = HID_RSIZE_MAX;
error = EOVERFLOW;
}
if (hri->fsize > HID_RSIZE_MAX) {
DPRINTF("feature size is too large, %u bytes (truncating)\n",
hri->fsize);
hri->fsize = HID_RSIZE_MAX;
error = EOVERFLOW;
}
return (error);
}
int
hidbus_locate(const void *desc, hid_size_t size, int32_t u, enum hid_kind k,
uint8_t tlc_index, uint8_t index, struct hid_location *loc,
uint32_t *flags, uint8_t *id, struct hid_absinfo *ai)
{
struct hid_data *d;
struct hid_item h;
int i;
d = hid_start_parse(desc, size, 1 << k);
HIDBUS_FOREACH_ITEM(d, &h, tlc_index) {
for (i = 0; i < h.nusages; i++) {
if (h.kind == k && h.usages[i] == u) {
if (index--)
break;
if (loc != NULL)
*loc = h.loc;
if (flags != NULL)
*flags = h.flags;
if (id != NULL)
*id = h.report_ID;
if (ai != NULL && (h.flags&HIO_RELATIVE) == 0)
*ai = (struct hid_absinfo) {
.max = h.logical_maximum,
.min = h.logical_minimum,
.res = hid_item_resolution(&h),
};
hid_end_parse(d);
return (1);
}
}
}
if (loc != NULL)
loc->size = 0;
if (flags != NULL)
*flags = 0;
if (id != NULL)
*id = 0;
hid_end_parse(d);
return (0);
}
static device_t
hidbus_add_child(device_t dev, u_int order, const char *name, int unit)
{
struct hidbus_softc *sc = device_get_softc(dev);
struct hidbus_ivars *tlc;
device_t child;
child = device_add_child_ordered(dev, order, name, unit);
if (child == NULL)
return (child);
tlc = malloc(sizeof(struct hidbus_ivars), M_DEVBUF, M_WAITOK | M_ZERO);
tlc->mtx = &sc->mtx;
device_set_ivars(child, tlc);
sx_xlock(&sc->sx);
CK_STAILQ_INSERT_TAIL(&sc->tlcs, tlc, link);
sx_unlock(&sc->sx);
return (child);
}
static int
hidbus_enumerate_children(device_t dev, const void* data, hid_size_t len)
{
struct hidbus_softc *sc = device_get_softc(dev);
struct hid_data *hd;
struct hid_item hi;
device_t child;
uint8_t index = 0;
if (data == NULL || len == 0)
return (ENXIO);
/* Add a child for each top level collection */
hd = hid_start_parse(data, len, 1 << hid_input);
while (hid_get_item(hd, &hi)) {
if (hi.kind != hid_collection || hi.collevel != 1)
continue;
child = BUS_ADD_CHILD(dev, 0, NULL, -1);
if (child == NULL) {
device_printf(dev, "Could not add HID device\n");
continue;
}
hidbus_set_index(child, index);
hidbus_set_usage(child, hi.usage);
hidbus_set_flags(child, HIDBUS_FLAG_AUTOCHILD);
index++;
DPRINTF("Add child TLC: 0x%04x:0x%04x\n",
HID_GET_USAGE_PAGE(hi.usage), HID_GET_USAGE(hi.usage));
}
hid_end_parse(hd);
if (index == 0)
return (ENXIO);
sc->nauto = index;
return (0);
}
static int
hidbus_attach_children(device_t dev)
{
struct hidbus_softc *sc = device_get_softc(dev);
int error;
HID_INTR_SETUP(device_get_parent(dev), hidbus_intr, sc, &sc->rdesc);
error = hidbus_enumerate_children(dev, sc->rdesc.data, sc->rdesc.len);
if (error != 0)
DPRINTF("failed to enumerate children: error %d\n", error);
/*
* hidbus_attach_children() can recurse through device_identify->
* hid_set_report_descr() call sequence. Do not perform children
* attach twice in that case.
*/
sc->nest++;
bus_generic_probe(dev);
sc->nest--;
if (sc->nest != 0)
return (0);
if (hid_is_keyboard(sc->rdesc.data, sc->rdesc.len) != 0)
error = bus_generic_attach(dev);
else
error = bus_delayed_attach_children(dev);
if (error != 0)
device_printf(dev, "failed to attach child: error %d\n", error);
return (error);
}
static int
hidbus_detach_children(device_t dev)
{
device_t *children, bus;
bool is_bus;
int i, error;
error = 0;
is_bus = device_get_devclass(dev) == hidbus_devclass;
bus = is_bus ? dev : device_get_parent(dev);
KASSERT(device_get_devclass(bus) == hidbus_devclass,
("Device is not hidbus or it's child"));
if (is_bus) {
/* If hidbus is passed, delete all children. */
bus_generic_detach(bus);
device_delete_children(bus);
} else {
/*
* If hidbus child is passed, delete all hidbus children
* except caller. Deleting the caller may result in deadlock.
*/
error = device_get_children(bus, &children, &i);
if (error != 0)
return (error);
while (i-- > 0) {
if (children[i] == dev)
continue;
DPRINTF("Delete child. index=%d (%s)\n",
hidbus_get_index(children[i]),
device_get_nameunit(children[i]));
error = device_delete_child(bus, children[i]);
if (error) {
DPRINTF("Failed deleting %s\n",
device_get_nameunit(children[i]));
break;
}
}
free(children, M_TEMP);
}
HID_INTR_UNSETUP(device_get_parent(bus));
return (error);
}
static int
hidbus_probe(device_t dev)
{
device_set_desc(dev, "HID bus");
/* Allow other subclasses to override this driver. */
return (BUS_PROBE_GENERIC);
}
static int
hidbus_attach(device_t dev)
{
struct hidbus_softc *sc = device_get_softc(dev);
struct hid_device_info *devinfo = device_get_ivars(dev);
void *d_ptr = NULL;
hid_size_t d_len;
int error;
sc->dev = dev;
CK_STAILQ_INIT(&sc->tlcs);
mtx_init(&sc->mtx, "hidbus ivar lock", NULL, MTX_DEF);
sx_init(&sc->sx, "hidbus ivar list lock");
/*
* Ignore error. It is possible for non-HID device e.g. XBox360 gamepad
* to emulate HID through overloading of report descriptor.
*/
d_len = devinfo->rdescsize;
if (d_len != 0) {
d_ptr = malloc(d_len, M_DEVBUF, M_ZERO | M_WAITOK);
error = hid_get_rdesc(dev, d_ptr, d_len);
if (error != 0) {
free(d_ptr, M_DEVBUF);
d_len = 0;
d_ptr = NULL;
}
}
hidbus_fill_rdesc_info(&sc->rdesc, d_ptr, d_len);
sc->nowrite = hid_test_quirk(devinfo, HQ_NOWRITE);
error = hidbus_attach_children(dev);
if (error != 0) {
hidbus_detach(dev);
return (ENXIO);
}
return (0);
}
static int
hidbus_detach(device_t dev)
{
struct hidbus_softc *sc = device_get_softc(dev);
hidbus_detach_children(dev);
sx_destroy(&sc->sx);
mtx_destroy(&sc->mtx);
free(sc->rdesc.data, M_DEVBUF);
return (0);
}
static void
hidbus_child_detached(device_t bus, device_t child)
{
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *tlc = device_get_ivars(child);
KASSERT(tlc->refcnt == 0, ("Child device is running"));
tlc->mtx = &sc->mtx;
tlc->intr_handler = NULL;
tlc->flags &= ~HIDBUS_FLAG_CAN_POLL;
}
/*
* Epoch callback indicating tlc is safe to destroy
*/
static void
hidbus_ivar_dtor(epoch_context_t ctx)
{
struct hidbus_ivars *tlc;
tlc = __containerof(ctx, struct hidbus_ivars, epoch_ctx);
free(tlc, M_DEVBUF);
}
static void
hidbus_child_deleted(device_t bus, device_t child)
{
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *tlc = device_get_ivars(child);
sx_xlock(&sc->sx);
KASSERT(tlc->refcnt == 0, ("Child device is running"));
CK_STAILQ_REMOVE(&sc->tlcs, tlc, hidbus_ivars, link);
sx_unlock(&sc->sx);
epoch_call(INPUT_EPOCH, hidbus_ivar_dtor, &tlc->epoch_ctx);
}
static int
hidbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result)
{
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *tlc = device_get_ivars(child);
switch (which) {
case HIDBUS_IVAR_INDEX:
*result = tlc->index;
break;
case HIDBUS_IVAR_USAGE:
*result = tlc->usage;
break;
case HIDBUS_IVAR_FLAGS:
*result = tlc->flags;
break;
case HIDBUS_IVAR_DRIVER_INFO:
*result = tlc->driver_info;
break;
case HIDBUS_IVAR_LOCK:
*result = (uintptr_t)(tlc->mtx == &sc->mtx ? NULL : tlc->mtx);
break;
default:
return (EINVAL);
}
return (0);
}
static int
hidbus_write_ivar(device_t bus, device_t child, int which, uintptr_t value)
{
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *tlc = device_get_ivars(child);
switch (which) {
case HIDBUS_IVAR_INDEX:
tlc->index = value;
break;
case HIDBUS_IVAR_USAGE:
tlc->usage = value;
break;
case HIDBUS_IVAR_FLAGS:
tlc->flags = value;
break;
case HIDBUS_IVAR_DRIVER_INFO:
tlc->driver_info = value;
break;
case HIDBUS_IVAR_LOCK:
tlc->mtx = (struct mtx *)value == NULL ?
&sc->mtx : (struct mtx *)value;
break;
default:
return (EINVAL);
}
return (0);
}
/* Location hint for devctl(8) */
static int
hidbus_child_location_str(device_t bus, device_t child, char *buf,
size_t buflen)
{
struct hidbus_ivars *tlc = device_get_ivars(child);
snprintf(buf, buflen, "index=%hhu", tlc->index);
return (0);
}
/* PnP information for devctl(8) */
static int
hidbus_child_pnpinfo_str(device_t bus, device_t child, char *buf,
size_t buflen)
{
struct hidbus_ivars *tlc = device_get_ivars(child);
struct hid_device_info *devinfo = device_get_ivars(bus);
snprintf(buf, buflen, "page=0x%04x usage=0x%04x bus=0x%02hx "
"vendor=0x%04hx product=0x%04hx version=0x%04hx%s%s",
HID_GET_USAGE_PAGE(tlc->usage), HID_GET_USAGE(tlc->usage),
devinfo->idBus, devinfo->idVendor, devinfo->idProduct,
devinfo->idVersion, devinfo->idPnP[0] == '\0' ? "" : " _HID=",
devinfo->idPnP[0] == '\0' ? "" : devinfo->idPnP);
return (0);
}
void
hidbus_set_desc(device_t child, const char *suffix)
{
device_t bus = device_get_parent(child);
struct hidbus_softc *sc = device_get_softc(bus);
struct hid_device_info *devinfo = device_get_ivars(bus);
struct hidbus_ivars *tlc = device_get_ivars(child);
char buf[80];
/* Do not add NULL suffix or if device name already contains it. */
if (suffix != NULL && strcasestr(devinfo->name, suffix) == NULL &&
(sc->nauto > 1 || (tlc->flags & HIDBUS_FLAG_AUTOCHILD) == 0)) {
snprintf(buf, sizeof(buf), "%s %s", devinfo->name, suffix);
device_set_desc_copy(child, buf);
} else
device_set_desc(child, devinfo->name);
}
device_t
hidbus_find_child(device_t bus, int32_t usage)
{
device_t *children, child;
int ccount, i;
GIANT_REQUIRED;
/* Get a list of all hidbus children */
if (device_get_children(bus, &children, &ccount) != 0)
return (NULL);
/* Scan through to find required TLC */
for (i = 0, child = NULL; i < ccount; i++) {
if (hidbus_get_usage(children[i]) == usage) {
child = children[i];
break;
}
}
free(children, M_TEMP);
return (child);
}
void
hidbus_intr(void *context, void *buf, hid_size_t len)
{
struct hidbus_softc *sc = context;
struct hidbus_ivars *tlc;
struct epoch_tracker et;
/*
* Broadcast input report to all subscribers.
* TODO: Add check for input report ID.
*
* Relock mutex on every TLC item as we can't hold any locks over whole
* TLC list here due to LOR with open()/close() handlers.
*/
if (!HID_IN_POLLING_MODE())
epoch_enter_preempt(INPUT_EPOCH, &et);
CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) {
if (tlc->refcnt == 0 || tlc->intr_handler == NULL)
continue;
if (HID_IN_POLLING_MODE()) {
if ((tlc->flags & HIDBUS_FLAG_CAN_POLL) != 0)
tlc->intr_handler(tlc->intr_ctx, buf, len);
} else {
mtx_lock(tlc->mtx);
tlc->intr_handler(tlc->intr_ctx, buf, len);
mtx_unlock(tlc->mtx);
}
}
if (!HID_IN_POLLING_MODE())
epoch_exit_preempt(INPUT_EPOCH, &et);
}
void
hidbus_set_intr(device_t child, hid_intr_t *handler, void *context)
{
struct hidbus_ivars *tlc = device_get_ivars(child);
tlc->intr_handler = handler;
tlc->intr_ctx = context;
}
int
hidbus_intr_start(device_t child)
{
device_t bus = device_get_parent(child);
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *ivar = device_get_ivars(child);
struct hidbus_ivars *tlc;
int refcnt = 0;
int error;
if (sx_xlock_sig(&sc->sx) != 0)
return (EINTR);
CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) {
refcnt += tlc->refcnt;
if (tlc == ivar) {
mtx_lock(tlc->mtx);
++tlc->refcnt;
mtx_unlock(tlc->mtx);
}
}
error = refcnt != 0 ? 0 : HID_INTR_START(device_get_parent(bus));
sx_unlock(&sc->sx);
return (error);
}
int
hidbus_intr_stop(device_t child)
{
device_t bus = device_get_parent(child);
struct hidbus_softc *sc = device_get_softc(bus);
struct hidbus_ivars *ivar = device_get_ivars(child);
struct hidbus_ivars *tlc;
bool refcnt = 0;
int error;
if (sx_xlock_sig(&sc->sx) != 0)
return (EINTR);
CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) {
if (tlc == ivar) {
mtx_lock(tlc->mtx);
MPASS(tlc->refcnt != 0);
--tlc->refcnt;
mtx_unlock(tlc->mtx);
}
refcnt += tlc->refcnt;
}
error = refcnt != 0 ? 0 : HID_INTR_STOP(device_get_parent(bus));
sx_unlock(&sc->sx);
return (error);
}
void
hidbus_intr_poll(device_t child)
{
device_t bus = device_get_parent(child);
HID_INTR_POLL(device_get_parent(bus));
}
struct hid_rdesc_info *
hidbus_get_rdesc_info(device_t child)
{
device_t bus = device_get_parent(child);
struct hidbus_softc *sc = device_get_softc(bus);
return (&sc->rdesc);
}
/*
* HID interface.
*
* Hidbus as well as any hidbus child can be passed as first arg.
*/
/* Read cached report descriptor */
int
hid_get_report_descr(device_t dev, void **data, hid_size_t *len)
{
device_t bus;
struct hidbus_softc *sc;
bus = device_get_devclass(dev) == hidbus_devclass ?
dev : device_get_parent(dev);
sc = device_get_softc(bus);
/*
* Do not send request to a transport backend.
* Use cached report descriptor instead of it.
*/
if (sc->rdesc.data == NULL || sc->rdesc.len == 0)
return (ENXIO);
if (data != NULL)
*data = sc->rdesc.data;
if (len != NULL)
*len = sc->rdesc.len;
return (0);
}
/*
* Replace cached report descriptor with top level driver provided one.
*
* It deletes all hidbus children except caller and enumerates them again after
* new descriptor has been registered. Currently it can not be called from
* autoenumerated (by report's TLC) child device context as it results in child
* duplication. To overcome this limitation hid_set_report_descr() should be
* called from device_identify driver's handler with hidbus itself passed as
* 'device_t dev' parameter.
*/
int
hid_set_report_descr(device_t dev, const void *data, hid_size_t len)
{
struct hid_rdesc_info rdesc;
device_t bus;
struct hidbus_softc *sc;
bool is_bus;
int error;
GIANT_REQUIRED;
is_bus = device_get_devclass(dev) == hidbus_devclass;
bus = is_bus ? dev : device_get_parent(dev);
sc = device_get_softc(bus);
/*
* Do not overload already overloaded report descriptor in
* device_identify handler. It causes infinite recursion loop.
*/
if (is_bus && sc->overloaded)
return(0);
DPRINTFN(5, "len=%d\n", len);
DPRINTFN(5, "data = %*D\n", len, data, " ");
error = hidbus_fill_rdesc_info(&rdesc, data, len);
if (error != 0)
return (error);
error = hidbus_detach_children(dev);
if (error != 0)
return(error);
/* Make private copy to handle a case of dynamicaly allocated data. */
rdesc.data = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK);
bcopy(data, rdesc.data, len);
sc->overloaded = true;
free(sc->rdesc.data, M_DEVBUF);
bcopy(&rdesc, &sc->rdesc, sizeof(struct hid_rdesc_info));
error = hidbus_attach_children(bus);
return (error);
}
static int
hidbus_write(device_t dev, const void *data, hid_size_t len)
{
struct hidbus_softc *sc;
uint8_t id;
sc = device_get_softc(dev);
/*
* Output interrupt endpoint is often optional. If HID device
* does not provide it, send reports via control pipe.
*/
if (sc->nowrite) {
/* try to extract the ID byte */
id = (sc->rdesc.oid & (len > 0)) ? *(const uint8_t*)data : 0;
return (hid_set_report(dev, data, len, HID_OUTPUT_REPORT, id));
}
return (hid_write(dev, data, len));
}
/*------------------------------------------------------------------------*
* hidbus_lookup_id
*
* This functions takes an array of "struct hid_device_id" and tries
* to match the entries with the information in "struct hid_device_info".
*
* Return values:
* NULL: No match found.
* Else: Pointer to matching entry.
*------------------------------------------------------------------------*/
const struct hid_device_id *
hidbus_lookup_id(device_t dev, const struct hid_device_id *id, int nitems_id)
{
const struct hid_device_id *id_end;
const struct hid_device_info *info;
int32_t usage;
bool is_child;
if (id == NULL) {
goto done;
}
id_end = id + nitems_id;
info = hid_get_device_info(dev);
is_child = device_get_devclass(dev) != hidbus_devclass;
if (is_child)
usage = hidbus_get_usage(dev);
/*
* Keep on matching array entries until we find a match or
* until we reach the end of the matching array:
*/
for (; id != id_end; id++) {
if (is_child && (id->match_flag_page) &&
(id->page != HID_GET_USAGE_PAGE(usage))) {
continue;
}
if (is_child && (id->match_flag_usage) &&
(id->usage != HID_GET_USAGE(usage))) {
continue;
}
if ((id->match_flag_bus) &&
(id->idBus != info->idBus)) {
continue;
}
if ((id->match_flag_vendor) &&
(id->idVendor != info->idVendor)) {
continue;
}
if ((id->match_flag_product) &&
(id->idProduct != info->idProduct)) {
continue;
}
if ((id->match_flag_ver_lo) &&
(id->idVersion_lo > info->idVersion)) {
continue;
}
if ((id->match_flag_ver_hi) &&
(id->idVersion_hi < info->idVersion)) {
continue;
}
if (id->match_flag_pnp &&
strncmp(id->idPnP, info->idPnP, HID_PNP_ID_SIZE) != 0) {
continue;
}
/* We found a match! */
return (id);
}
done:
return (NULL);
}
/*------------------------------------------------------------------------*
* hidbus_lookup_driver_info - factored out code
*
* Return values:
* 0: Success
* Else: Failure
*------------------------------------------------------------------------*/
int
hidbus_lookup_driver_info(device_t child, const struct hid_device_id *id,
int nitems_id)
{
id = hidbus_lookup_id(child, id, nitems_id);
if (id) {
/* copy driver info */
hidbus_set_driver_info(child, id->driver_info);
return (0);
}
return (ENXIO);
}
const struct hid_device_info *
hid_get_device_info(device_t dev)
{
device_t bus;
bus = device_get_devclass(dev) == hidbus_devclass ?
dev : device_get_parent(dev);
return (device_get_ivars(bus));
}
static device_method_t hidbus_methods[] = {
/* device interface */
DEVMETHOD(device_probe, hidbus_probe),
DEVMETHOD(device_attach, hidbus_attach),
DEVMETHOD(device_detach, hidbus_detach),
DEVMETHOD(device_suspend, bus_generic_suspend),
DEVMETHOD(device_resume, bus_generic_resume),
/* bus interface */
DEVMETHOD(bus_add_child, hidbus_add_child),
DEVMETHOD(bus_child_detached, hidbus_child_detached),
DEVMETHOD(bus_child_deleted, hidbus_child_deleted),
DEVMETHOD(bus_read_ivar, hidbus_read_ivar),
DEVMETHOD(bus_write_ivar, hidbus_write_ivar),
DEVMETHOD(bus_child_pnpinfo_str,hidbus_child_pnpinfo_str),
DEVMETHOD(bus_child_location_str,hidbus_child_location_str),
/* hid interface */
DEVMETHOD(hid_get_rdesc, hid_get_rdesc),
DEVMETHOD(hid_read, hid_read),
DEVMETHOD(hid_write, hidbus_write),
DEVMETHOD(hid_get_report, hid_get_report),
DEVMETHOD(hid_set_report, hid_set_report),
DEVMETHOD(hid_set_idle, hid_set_idle),
DEVMETHOD(hid_set_protocol, hid_set_protocol),
DEVMETHOD_END
};
devclass_t hidbus_devclass;
driver_t hidbus_driver = {
"hidbus",
hidbus_methods,
sizeof(struct hidbus_softc),
};
MODULE_DEPEND(hidbus, hid, 1, 1, 1);
MODULE_VERSION(hidbus, 1);

176
sys/dev/hid/hidbus.h Normal file
View File

@ -0,0 +1,176 @@
/*-
* Copyright (c) 2019 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.
*/
#ifndef _HID_HIDBUS_H_
#define _HID_HIDBUS_H_
enum {
HIDBUS_IVAR_USAGE,
HIDBUS_IVAR_INDEX,
HIDBUS_IVAR_FLAGS,
#define HIDBUS_FLAG_AUTOCHILD (0<<1) /* Child is autodiscovered */
#define HIDBUS_FLAG_CAN_POLL (1<<1) /* Child can work during panic */
HIDBUS_IVAR_DRIVER_INFO,
HIDBUS_IVAR_LOCK,
};
#define HIDBUS_ACCESSOR(A, B, T) \
__BUS_ACCESSOR(hidbus, A, HIDBUS, B, T)
HIDBUS_ACCESSOR(usage, USAGE, int32_t)
HIDBUS_ACCESSOR(index, INDEX, uint8_t)
HIDBUS_ACCESSOR(flags, FLAGS, uint32_t)
HIDBUS_ACCESSOR(driver_info, DRIVER_INFO, uintptr_t)
HIDBUS_ACCESSOR(lock, LOCK, struct mtx *)
/*
* The following structure is used when looking up an HID driver for
* an HID device. It is inspired by the structure called "usb_device_id".
* which is originated in Linux and ported to FreeBSD.
*/
struct hid_device_id {
/* Select which fields to match against */
#if BYTE_ORDER == LITTLE_ENDIAN
uint16_t
match_flag_page:1,
match_flag_usage:1,
match_flag_bus:1,
match_flag_vendor:1,
match_flag_product:1,
match_flag_ver_lo:1,
match_flag_ver_hi:1,
match_flag_pnp:1,
match_flag_unused:8;
#else
uint16_t
match_flag_unused:8,
match_flag_pnp:1,
match_flag_ver_hi:1,
match_flag_ver_lo:1,
match_flag_product:1,
match_flag_vendor:1,
match_flag_bus:1,
match_flag_usage:1,
match_flag_page:1;
#endif
/* Used for top level collection usage matches */
uint16_t page;
uint16_t usage;
/* Used for product specific matches; the Version range is inclusive */
uint8_t idBus;
uint16_t idVendor;
uint16_t idProduct;
uint16_t idVersion_lo;
uint16_t idVersion_hi;
char *idPnP;
/* Hook for driver specific information */
uintptr_t driver_info;
};
#define HID_STD_PNP_INFO \
"M16:mask;U16:page;U16:usage;U8:bus;U16:vendor;U16:product;" \
"L16:version;G16:version;Z:_HID"
#define HID_PNP_INFO(table) \
MODULE_PNP_INFO(HID_STD_PNP_INFO, hidbus, table, table, nitems(table))
#define HID_TLC(pg,usg) \
.match_flag_page = 1, .match_flag_usage = 1, .page = (pg), .usage = (usg)
#define HID_BUS(bus) \
.match_flag_bus = 1, .idBus = (bus)
#define HID_VENDOR(vend) \
.match_flag_vendor = 1, .idVendor = (vend)
#define HID_PRODUCT(prod) \
.match_flag_product = 1, .idProduct = (prod)
#define HID_VP(vend,prod) \
HID_VENDOR(vend), HID_PRODUCT(prod)
#define HID_BVP(bus,vend,prod) \
HID_BUS(bus), HID_VENDOR(vend), HID_PRODUCT(prod)
#define HID_BVPI(bus,vend,prod,info) \
HID_BUS(bus), HID_VENDOR(vend), HID_PRODUCT(prod), HID_DRIVER_INFO(info)
#define HID_VERSION_GTEQ(lo) /* greater than or equal */ \
.match_flag_ver_lo = 1, .idVersion_lo = (lo)
#define HID_VERSION_LTEQ(hi) /* less than or equal */ \
.match_flag_ver_hi = 1, .idVersion_hi = (hi)
#define HID_PNP(pnp) \
.match_flag_pnp = 1, .idPnP = (pnp)
#define HID_DRIVER_INFO(n) \
.driver_info = (n)
#define HID_GET_DRIVER_INFO(did) \
(did)->driver_info
#define HIDBUS_LOOKUP_ID(d, h) hidbus_lookup_id((d), (h), nitems(h))
#define HIDBUS_LOOKUP_DRIVER_INFO(d, h) \
hidbus_lookup_driver_info((d), (h), nitems(h))
/*
* Walk through all HID items hi belonging Top Level Collection #tlc_index
*/
#define HIDBUS_FOREACH_ITEM(hd, hi, tlc_index) \
for (uint8_t _iter = 0; \
_iter <= (tlc_index) && hid_get_item((hd), (hi)); \
_iter += (hi)->kind == hid_endcollection && (hi)->collevel == 0) \
if (_iter == (tlc_index))
int hidbus_locate(const void *desc, hid_size_t size, int32_t u,
enum hid_kind k, uint8_t tlc_index, uint8_t index,
struct hid_location *loc, uint32_t *flags, uint8_t *id,
struct hid_absinfo *ai);
const struct hid_device_id *hidbus_lookup_id(device_t,
const struct hid_device_id *, int);
struct hid_rdesc_info *hidbus_get_rdesc_info(device_t);
int hidbus_lookup_driver_info(device_t,
const struct hid_device_id *, int);
void hidbus_set_intr(device_t, hid_intr_t*, void *);
int hidbus_intr_start(device_t);
int hidbus_intr_stop(device_t);
void hidbus_intr_poll(device_t);
void hidbus_set_desc(device_t, const char *);
device_t hidbus_find_child(device_t, int32_t);
/* hidbus HID interface */
int hid_get_report_descr(device_t, void **, hid_size_t *);
int hid_set_report_descr(device_t, const void *, hid_size_t);
const struct hid_device_info *hid_get_device_info(device_t);
extern devclass_t hidbus_devclass;
#endif /* _HID_HIDBUS_H_ */

View File

@ -1,6 +1,7 @@
# $FreeBSD$
SUBDIR = \
hid
hid \
hidbus
.include <bsd.subdir.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= hidbus
SRCS= hidbus.c
SRCS+= bus_if.h device_if.h hid_if.h
.include <bsd.kmod.mk>