freebsd-dev/sys/dev/hid/hidbus.c
Val Packett 64fbda90da Add atopcase, the Apple HID over SPI input driver
The driver provides support for Human Interface Devices (HID) on
Serial Peripheral Interface (SPI) buses on Apple Intel Macs
produced in 2015-2018.

The driver appears to work more stable after installation of Darwin OSI
in acpi(4) driver.
To install Darwin OSI insert following lines into /boot/loader.conf:

hw.acpi.install_interface="Darwin"
hw.acpi.remove_interface="Windows 2009, Windows 2012"

Reviewed by:	wulf
Differential revision:	https://reviews.freebsd.org/D39863
2023-08-20 12:53:32 +03:00

980 lines
24 KiB
C

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* 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>
#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/sbuf.h>
#include <sys/sx.h>
#include <sys/systm.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);
}
bool
hidbus_is_collection(const void *desc, hid_size_t size, int32_t usage,
uint8_t tlc_index)
{
struct hid_data *d;
struct hid_item h;
bool ret = false;
d = hid_start_parse(desc, size, 0);
HIDBUS_FOREACH_ITEM(d, &h, tlc_index) {
if (h.kind == hid_collection && h.usage == usage) {
ret = true;
break;
}
}
hid_end_parse(d);
return (ret);
}
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), 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) == devclass_find("hidbus");
bus = is_bus ? dev : device_get_parent(dev);
KASSERT(device_get_devclass(bus) == devclass_find("hidbus"),
("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), 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;
if ((value & HIDBUS_FLAG_CAN_POLL) != 0)
HID_INTR_SETUP(
device_get_parent(bus), bus, NULL, NULL, NULL);
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(device_t bus, device_t child, struct sbuf *sb)
{
struct hidbus_ivars *tlc = device_get_ivars(child);
sbuf_printf(sb, "index=%hhu", tlc->index);
return (0);
}
/* PnP information for devctl(8) */
static int
hidbus_child_pnpinfo(device_t bus, device_t child, struct sbuf *sb)
{
struct hidbus_ivars *tlc = device_get_ivars(child);
struct hid_device_info *devinfo = device_get_ivars(bus);
sbuf_printf(sb, "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;
bus_topo_assert();
/* 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;
}
static int
hidbus_intr_start(device_t bus, device_t child)
{
MPASS(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 refcnted = false;
int error;
if (sx_xlock_sig(&sc->sx) != 0)
return (EINTR);
CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) {
refcnted |= (tlc->refcnt != 0);
if (tlc == ivar) {
mtx_lock(tlc->mtx);
++tlc->refcnt;
mtx_unlock(tlc->mtx);
}
}
error = refcnted ? 0 : hid_intr_start(bus);
sx_unlock(&sc->sx);
return (error);
}
static int
hidbus_intr_stop(device_t bus, device_t child)
{
MPASS(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 refcnted = false;
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);
}
refcnted |= (tlc->refcnt != 0);
}
error = refcnted ? 0 : hid_intr_stop(bus);
sx_unlock(&sc->sx);
return (error);
}
static void
hidbus_intr_poll(device_t bus, device_t child __unused)
{
hid_intr_poll(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) == devclass_find("hidbus") ?
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;
bus_topo_assert();
is_bus = device_get_devclass(dev) == devclass_find("hidbus");
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_get_rdesc(device_t dev, device_t child __unused, void *data,
hid_size_t len)
{
return (hid_get_rdesc(dev, data, len));
}
static int
hidbus_read(device_t dev, device_t child __unused, void *data,
hid_size_t maxlen, hid_size_t *actlen)
{
return (hid_read(dev, data, maxlen, actlen));
}
static int
hidbus_write(device_t dev, device_t child __unused, 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));
}
static int
hidbus_get_report(device_t dev, device_t child __unused, void *data,
hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id)
{
return (hid_get_report(dev, data, maxlen, actlen, type, id));
}
static int
hidbus_set_report(device_t dev, device_t child __unused, const void *data,
hid_size_t len, uint8_t type, uint8_t id)
{
return (hid_set_report(dev, data, len, type, id));
}
static int
hidbus_set_idle(device_t dev, device_t child __unused, uint16_t duration,
uint8_t id)
{
return (hid_set_idle(dev, duration, id));
}
static int
hidbus_set_protocol(device_t dev, device_t child __unused, uint16_t protocol)
{
return (hid_set_protocol(dev, protocol));
}
static int
hidbus_ioctl(device_t dev, device_t child __unused, unsigned long cmd,
uintptr_t data)
{
return (hid_ioctl(dev, cmd, data));
}
/*------------------------------------------------------------------------*
* 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) != devclass_find("hidbus");
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) == devclass_find("hidbus") ?
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, hidbus_child_pnpinfo),
DEVMETHOD(bus_child_location, hidbus_child_location),
/* hid interface */
DEVMETHOD(hid_intr_start, hidbus_intr_start),
DEVMETHOD(hid_intr_stop, hidbus_intr_stop),
DEVMETHOD(hid_intr_poll, hidbus_intr_poll),
DEVMETHOD(hid_get_rdesc, hidbus_get_rdesc),
DEVMETHOD(hid_read, hidbus_read),
DEVMETHOD(hid_write, hidbus_write),
DEVMETHOD(hid_get_report, hidbus_get_report),
DEVMETHOD(hid_set_report, hidbus_set_report),
DEVMETHOD(hid_set_idle, hidbus_set_idle),
DEVMETHOD(hid_set_protocol, hidbus_set_protocol),
DEVMETHOD(hid_ioctl, hidbus_ioctl),
DEVMETHOD_END
};
driver_t hidbus_driver = {
"hidbus",
hidbus_methods,
sizeof(struct hidbus_softc),
};
MODULE_DEPEND(hidbus, hid, 1, 1, 1);
MODULE_VERSION(hidbus, 1);
DRIVER_MODULE(hidbus, atopcase, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, hvhid, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, iichid, hidbus_driver, 0, 0);
DRIVER_MODULE(hidbus, usbhid, hidbus_driver, 0, 0);