Page MenuHomeFreeBSD

D27888.diff
No OneTemporary

D27888.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -179,6 +179,7 @@
gpioths.4 \
gre.4 \
h_ertt.4 \
+ hidbus.4 \
hifn.4 \
hpet.4 \
${_hpt27xx.4} \
diff --git a/share/man/man4/hidbus.4 b/share/man/man4/hidbus.4
new file mode 100644
--- /dev/null
+++ b/share/man/man4/hidbus.4
@@ -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 .
diff --git a/sys/conf/files b/sys/conf/files
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -1817,6 +1817,7 @@
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
diff --git a/sys/dev/hid/hidbus.h b/sys/dev/hid/hidbus.h
new file mode 100644
--- /dev/null
+++ b/sys/dev/hid/hidbus.h
@@ -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_ */
diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/hid/hidbus.c
@@ -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);
diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile
--- a/sys/modules/hid/Makefile
+++ b/sys/modules/hid/Makefile
@@ -1,6 +1,7 @@
# $FreeBSD$
SUBDIR = \
- hid
+ hid \
+ hidbus
.include <bsd.subdir.mk>
diff --git a/sys/modules/hid/hidbus/Makefile b/sys/modules/hid/hidbus/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/hid/hidbus/Makefile
@@ -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>

File Metadata

Mime Type
text/plain
Expires
Sat, Feb 8, 10:01 PM (21 h, 2 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16533752
Default Alt Text
D27888.diff (33 KB)

Event Timeline