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:
parent
961a3535db
commit
2b4464b0b1
@ -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
102
share/man/man4/hidbus.4
Normal 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 .
|
@ -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
905
sys/dev/hid/hidbus.c
Normal 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
176
sys/dev/hid/hidbus.h
Normal 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_ */
|
@ -1,6 +1,7 @@
|
||||
# $FreeBSD$
|
||||
|
||||
SUBDIR = \
|
||||
hid
|
||||
hid \
|
||||
hidbus
|
||||
|
||||
.include <bsd.subdir.mk>
|
||||
|
9
sys/modules/hid/hidbus/Makefile
Normal file
9
sys/modules/hid/hidbus/Makefile
Normal 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>
|
Loading…
Reference in New Issue
Block a user