diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c index 96d36c8d191d..683449fca49c 100644 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -1,973 +1,962 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019-2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hid_debug #include #include #include #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 */ + bool active; /* 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, DEVICE_UNIT_ANY); 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_identify_children(dev); sc->nest--; if (sc->nest != 0) return (0); if (hid_is_keyboard(sc->rdesc.data, sc->rdesc.len) != 0) bus_attach_children(dev); else bus_delayed_attach_children(dev); return (0); } 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. */ error = bus_generic_detach(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")); + KASSERT(!tlc->active, ("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")); + KASSERT(!tlc->active, ("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); /* 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)) device_set_descf(child, "%s %s", devinfo->name, suffix); 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) + if (!tlc->active || 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); + mtx_lock(ivar->mtx); + ivar->active = true; + mtx_unlock(ivar->mtx); + error = 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; + bool active = 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); + mtx_lock(ivar->mtx); + ivar->active = false; + mtx_unlock(ivar->mtx); + CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) + active |= tlc->active; + error = active ? 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); diff --git a/sys/dev/hid/ietp.c b/sys/dev/hid/ietp.c index 217585a7948b..73a5cb7414d4 100644 --- a/sys/dev/hid/ietp.c +++ b/sys/dev/hid/ietp.c @@ -1,675 +1,690 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020, 2022 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Elan I2C Touchpad driver. Based on Linux driver. * https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/input/mouse/elan_i2c_core.c */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR ietp_debug #include #include #include #ifdef HID_DEBUG static SYSCTL_NODE(_hw_hid, OID_AUTO, ietp, CTLFLAG_RW, 0, "Elantech Touchpad"); static int ietp_debug = 1; SYSCTL_INT(_hw_hid_ietp, OID_AUTO, debug, CTLFLAG_RWTUN, &ietp_debug, 1, "Debug level"); #endif #define IETP_PATTERN 0x0100 #define IETP_UNIQUEID 0x0101 #define IETP_FW_VERSION 0x0102 #define IETP_IC_TYPE 0x0103 #define IETP_OSM_VERSION 0x0103 #define IETP_NSM_VERSION 0x0104 #define IETP_TRACENUM 0x0105 #define IETP_MAX_X_AXIS 0x0106 #define IETP_MAX_Y_AXIS 0x0107 #define IETP_RESOLUTION 0x0108 #define IETP_PRESSURE 0x010A #define IETP_CONTROL 0x0300 #define IETP_CTRL_ABSOLUTE 0x0001 #define IETP_CTRL_STANDARD 0x0000 #define IETP_REPORT_LEN_LO 32 #define IETP_REPORT_LEN_HI 37 #define IETP_MAX_FINGERS 5 #define IETP_REPORT_ID_LO 0x5D #define IETP_REPORT_ID_HI 0x60 #define IETP_TOUCH_INFO 1 #define IETP_FINGER_DATA 2 #define IETP_FINGER_DATA_LEN 5 #define IETP_HOVER_INFO 28 #define IETP_WH_DATA 31 #define IETP_TOUCH_LMB (1 << 0) #define IETP_TOUCH_RMB (1 << 1) #define IETP_TOUCH_MMB (1 << 2) #define IETP_MAX_PRESSURE 255 #define IETP_FWIDTH_REDUCE 90 #define IETP_FINGER_MAX_WIDTH 15 #define IETP_PRESSURE_BASE 25 struct ietp_softc { device_t dev; struct evdev_dev *evdev; + bool open; uint8_t report_id; hid_size_t report_len; uint16_t product_id; uint16_t ic_type; int32_t pressure_base; uint16_t max_x; uint16_t max_y; uint16_t trace_x; uint16_t trace_y; uint16_t res_x; /* dots per mm */ uint16_t res_y; bool hi_precision; bool is_clickpad; bool has_3buttons; }; static evdev_open_t ietp_ev_open; static evdev_close_t ietp_ev_close; static hid_intr_t ietp_intr; static int ietp_probe(struct ietp_softc *); static int ietp_attach(struct ietp_softc *); static int ietp_detach(struct ietp_softc *); static int32_t ietp_res2dpmm(uint8_t, bool); static device_identify_t ietp_iic_identify; static device_probe_t ietp_iic_probe; static device_attach_t ietp_iic_attach; static device_detach_t ietp_iic_detach; static device_resume_t ietp_iic_resume; static int ietp_iic_read_reg(device_t, uint16_t, size_t, void *); static int ietp_iic_write_reg(device_t, uint16_t, uint16_t); static int ietp_iic_set_absolute_mode(device_t, bool); #define IETP_IIC_DEV(pnp) \ { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE), HID_BUS(BUS_I2C), HID_PNP(pnp) } static const struct hid_device_id ietp_iic_devs[] = { IETP_IIC_DEV("ELAN0000"), IETP_IIC_DEV("ELAN0100"), IETP_IIC_DEV("ELAN0600"), IETP_IIC_DEV("ELAN0601"), IETP_IIC_DEV("ELAN0602"), IETP_IIC_DEV("ELAN0603"), IETP_IIC_DEV("ELAN0604"), IETP_IIC_DEV("ELAN0605"), IETP_IIC_DEV("ELAN0606"), IETP_IIC_DEV("ELAN0607"), IETP_IIC_DEV("ELAN0608"), IETP_IIC_DEV("ELAN0609"), IETP_IIC_DEV("ELAN060B"), IETP_IIC_DEV("ELAN060C"), IETP_IIC_DEV("ELAN060F"), IETP_IIC_DEV("ELAN0610"), IETP_IIC_DEV("ELAN0611"), IETP_IIC_DEV("ELAN0612"), IETP_IIC_DEV("ELAN0615"), IETP_IIC_DEV("ELAN0616"), IETP_IIC_DEV("ELAN0617"), IETP_IIC_DEV("ELAN0618"), IETP_IIC_DEV("ELAN0619"), IETP_IIC_DEV("ELAN061A"), IETP_IIC_DEV("ELAN061B"), IETP_IIC_DEV("ELAN061C"), IETP_IIC_DEV("ELAN061D"), IETP_IIC_DEV("ELAN061E"), IETP_IIC_DEV("ELAN061F"), IETP_IIC_DEV("ELAN0620"), IETP_IIC_DEV("ELAN0621"), IETP_IIC_DEV("ELAN0622"), IETP_IIC_DEV("ELAN0623"), IETP_IIC_DEV("ELAN0624"), IETP_IIC_DEV("ELAN0625"), IETP_IIC_DEV("ELAN0626"), IETP_IIC_DEV("ELAN0627"), IETP_IIC_DEV("ELAN0628"), IETP_IIC_DEV("ELAN0629"), IETP_IIC_DEV("ELAN062A"), IETP_IIC_DEV("ELAN062B"), IETP_IIC_DEV("ELAN062C"), IETP_IIC_DEV("ELAN062D"), IETP_IIC_DEV("ELAN062E"), /* Lenovo V340 Whiskey Lake U */ IETP_IIC_DEV("ELAN062F"), /* Lenovo V340 Comet Lake U */ IETP_IIC_DEV("ELAN0631"), IETP_IIC_DEV("ELAN0632"), IETP_IIC_DEV("ELAN0633"), /* Lenovo S145 */ IETP_IIC_DEV("ELAN0634"), /* Lenovo V340 Ice lake */ IETP_IIC_DEV("ELAN0635"), /* Lenovo V1415-IIL */ IETP_IIC_DEV("ELAN0636"), /* Lenovo V1415-Dali */ IETP_IIC_DEV("ELAN0637"), /* Lenovo V1415-IGLR */ IETP_IIC_DEV("ELAN1000"), }; static uint8_t const ietp_dummy_rdesc[] = { 0x05, HUP_GENERIC_DESKTOP, /* Usage Page (Generic Desktop Ctrls) */ 0x09, HUG_MOUSE, /* Usage (Mouse) */ 0xA1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (0x01) */ 0x95, IETP_REPORT_LEN_LO, /* Report Count (IETP_REPORT_LEN_LO) */ 0x75, 0x08, /* Report Size (8) */ 0x81, 0x02, /* Input (Data,Var,Abs) */ 0xC0, /* End Collection */ }; static const struct evdev_methods ietp_evdev_methods = { .ev_open = &ietp_ev_open, .ev_close = &ietp_ev_close, }; static int ietp_ev_open(struct evdev_dev *evdev) { - return (hid_intr_start(evdev_get_softc(evdev))); + struct ietp_softc *sc = evdev_get_softc(evdev); + int error; + + error = hid_intr_start(sc->dev); + if (error == 0) + sc->open = true; + return (error); } static int ietp_ev_close(struct evdev_dev *evdev) { - return (hid_intr_stop(evdev_get_softc(evdev))); + struct ietp_softc *sc = evdev_get_softc(evdev); + int error; + + error = hid_intr_stop(sc->dev); + if (error == 0) + sc->open = false; + return (error); } static int ietp_probe(struct ietp_softc *sc) { if (hidbus_find_child(device_get_parent(sc->dev), HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD)) != NULL) { DPRINTFN(5, "Ignore HID-compatible touchpad on %s\n", device_get_nameunit(device_get_parent(sc->dev))); return (ENXIO); } device_set_desc(sc->dev, "Elan Touchpad"); return (BUS_PROBE_DEFAULT); } static int ietp_attach(struct ietp_softc *sc) { const struct hid_device_info *hw = hid_get_device_info(sc->dev); void *d_ptr; hid_size_t d_len; int32_t minor, major; int error; sc->report_id = sc->hi_precision ? IETP_REPORT_ID_HI : IETP_REPORT_ID_LO; sc->report_len = sc->hi_precision ? IETP_REPORT_LEN_HI : IETP_REPORT_LEN_LO; /* Try to detect 3-rd button by relative mouse TLC */ if (!sc->is_clickpad) { error = hid_get_report_descr(sc->dev, &d_ptr, &d_len); if (error != 0) { device_printf(sc->dev, "could not retrieve report " "descriptor from device: %d\n", error); return (ENXIO); } if (hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_BUTTON, 3), hid_input, hidbus_get_index(sc->dev), 0, NULL, NULL, NULL, NULL)) sc->has_3buttons = true; } sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(sc->dev)); evdev_set_phys(sc->evdev, device_get_nameunit(sc->dev)); evdev_set_id(sc->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(sc->evdev, hw->serial); - evdev_set_methods(sc->evdev, sc->dev, &ietp_evdev_methods); + evdev_set_methods(sc->evdev, sc, &ietp_evdev_methods); evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT); evdev_set_flag(sc->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_prop(sc->evdev, INPUT_PROP_POINTER); evdev_support_key(sc->evdev, BTN_LEFT); if (sc->is_clickpad) { evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD); } else { evdev_support_key(sc->evdev, BTN_RIGHT); if (sc->has_3buttons) evdev_support_key(sc->evdev, BTN_MIDDLE); } major = IETP_FINGER_MAX_WIDTH * MAX(sc->trace_x, sc->trace_y); minor = IETP_FINGER_MAX_WIDTH * MIN(sc->trace_x, sc->trace_y); evdev_support_abs(sc->evdev, ABS_MT_SLOT, 0, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TRACKING_ID, -1, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_POSITION_X, 0, sc->max_x, 0, 0, sc->res_x); evdev_support_abs(sc->evdev, ABS_MT_POSITION_Y, 0, sc->max_y, 0, 0, sc->res_y); evdev_support_abs(sc->evdev, ABS_MT_PRESSURE, 0, IETP_MAX_PRESSURE, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_ORIENTATION, 0, 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MAJOR, 0, major, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MINOR, 0, minor, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_DISTANCE, 0, 1, 0, 0, 0); error = evdev_register(sc->evdev); if (error != 0) { ietp_detach(sc); return (ENOMEM); } hidbus_set_intr(sc->dev, ietp_intr, sc); device_printf(sc->dev, "[%d:%d], %s\n", sc->max_x, sc->max_y, sc->is_clickpad ? "clickpad" : sc->has_3buttons ? "3 buttons" : "2 buttons"); return (0); } static int ietp_detach(struct ietp_softc *sc) { evdev_free(sc->evdev); return (0); } static void ietp_intr(void *context, void *buf, hid_size_t len) { struct ietp_softc *sc = context; union evdev_mt_slot slot_data; uint8_t *report, *fdata; int32_t finger; int32_t x, y, w, h, wh; /* we seem to get 0 length reports sometimes, ignore them */ if (len == 0) return; if (len != sc->report_len) { DPRINTF("wrong report length (%d vs %d expected)", len, sc->report_len); return; } report = buf; if (*report != sc->report_id) return; evdev_push_key(sc->evdev, BTN_LEFT, report[IETP_TOUCH_INFO] & IETP_TOUCH_LMB); evdev_push_key(sc->evdev, BTN_MIDDLE, report[IETP_TOUCH_INFO] & IETP_TOUCH_MMB); evdev_push_key(sc->evdev, BTN_RIGHT, report[IETP_TOUCH_INFO] & IETP_TOUCH_RMB); evdev_push_abs(sc->evdev, ABS_DISTANCE, (report[IETP_HOVER_INFO] & 0x40) >> 6); for (finger = 0, fdata = report + IETP_FINGER_DATA; finger < IETP_MAX_FINGERS; finger++, fdata += IETP_FINGER_DATA_LEN) { if ((report[IETP_TOUCH_INFO] & (1 << (finger + 3))) != 0) { if (sc->hi_precision) { x = fdata[0] << 8 | fdata[1]; y = fdata[2] << 8 | fdata[3]; wh = report[IETP_WH_DATA + finger]; } else { x = (fdata[0] & 0xf0) << 4 | fdata[1]; y = (fdata[0] & 0x0f) << 8 | fdata[2]; wh = fdata[3]; } if (x > sc->max_x || y > sc->max_y) { DPRINTF("[%d] x=%d y=%d over max (%d, %d)", finger, x, y, sc->max_x, sc->max_y); continue; } /* Reduce trace size to not treat large finger as palm */ w = (wh & 0x0F) * (sc->trace_x - IETP_FWIDTH_REDUCE); h = (wh >> 4) * (sc->trace_y - IETP_FWIDTH_REDUCE); slot_data = (union evdev_mt_slot) { .id = finger, .x = x, .y = sc->max_y - y, .p = MIN((int32_t)fdata[4] + sc->pressure_base, IETP_MAX_PRESSURE), .ori = w > h ? 1 : 0, .maj = MAX(w, h), .min = MIN(w, h), }; evdev_mt_push_slot(sc->evdev, finger, &slot_data); } else { evdev_push_abs(sc->evdev, ABS_MT_SLOT, finger); evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); } } evdev_sync(sc->evdev); } static int32_t ietp_res2dpmm(uint8_t res, bool hi_precision) { int32_t dpi; dpi = hi_precision ? 300 + res * 100 : 790 + res * 10; return (dpi * 10 /254); } static void ietp_iic_identify(driver_t *driver, device_t parent) { void *d_ptr; hid_size_t d_len; int isize; uint8_t iid; if (HIDBUS_LOOKUP_ID(parent, ietp_iic_devs) == NULL) return; if (hid_get_report_descr(parent, &d_ptr, &d_len) != 0) return; /* * Some Elantech trackpads have a mangled HID report descriptor, which * reads as having an incorrect input size (i.e. < IETP_REPORT_LEN_LO). * If the input size is incorrect, load a dummy report descriptor. */ isize = hid_report_size_max(d_ptr, d_len, hid_input, &iid); if (isize >= IETP_REPORT_LEN_LO) return; hid_set_report_descr(parent, ietp_dummy_rdesc, sizeof(ietp_dummy_rdesc)); } static int ietp_iic_probe(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); device_t iichid; int error; error = HIDBUS_LOOKUP_DRIVER_INFO(dev, ietp_iic_devs); if (error != 0) return (error); iichid = device_get_parent(device_get_parent(dev)); if (device_get_devclass(iichid) != devclass_find("iichid")) return (ENXIO); sc->dev = dev; return (ietp_probe(sc)); } static int ietp_iic_attach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); uint16_t buf, reg; uint8_t *buf8; uint8_t pattern; buf8 = (uint8_t *)&buf; if (ietp_iic_read_reg(dev, IETP_UNIQUEID, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading product ID\n"); return (EIO); } sc->product_id = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_PATTERN, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pattern\n"); return (EIO); } pattern = buf == 0xFFFF ? 0 : buf8[1]; sc->hi_precision = pattern >= 0x02; reg = pattern >= 0x01 ? IETP_IC_TYPE : IETP_OSM_VERSION; if (ietp_iic_read_reg(dev, reg, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading IC type\n"); return (EIO); } sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1]; if (ietp_iic_read_reg(dev, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading SM version\n"); return (EIO); } sc->is_clickpad = (buf8[0] & 0x10) != 0; if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(sc->dev, "failed to set absolute mode\n"); return (EIO); } if (ietp_iic_read_reg(dev, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max x\n"); return (EIO); } sc->max_x = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max y\n"); return (EIO); } sc->max_y = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_TRACENUM, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading trace info\n"); return (EIO); } sc->trace_x = sc->max_x / buf8[0]; sc->trace_y = sc->max_y / buf8[1]; if (ietp_iic_read_reg(dev, IETP_PRESSURE, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pressure format\n"); return (EIO); } sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE; if (ietp_iic_read_reg(dev, IETP_RESOLUTION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading resolution\n"); return (EIO); } /* Conversion from internal format to dot per mm */ sc->res_x = ietp_res2dpmm(buf8[0], sc->hi_precision); sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precision); return (ietp_attach(sc)); } static int ietp_iic_detach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); if (ietp_iic_set_absolute_mode(dev, false) != 0) device_printf(dev, "failed setting standard mode\n"); return (ietp_detach(sc)); } static int ietp_iic_resume(device_t dev) { if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(dev, "reset when resuming failed: \n"); return (EIO); } return (0); } static int ietp_iic_set_absolute_mode(device_t dev, bool enable) { struct ietp_softc *sc = device_get_softc(dev); static const struct { uint16_t ic_type; uint16_t product_id; } special_fw[] = { { 0x0E, 0x05 }, { 0x0E, 0x06 }, { 0x0E, 0x07 }, { 0x0E, 0x09 }, { 0x0E, 0x13 }, { 0x08, 0x26 }, }; uint16_t val; int i, error; bool require_wakeup; error = 0; /* * Some ASUS touchpads need to be powered on to enter absolute mode. */ require_wakeup = false; - for (i = 0; i < nitems(special_fw); i++) { - if (sc->ic_type == special_fw[i].ic_type && - sc->product_id == special_fw[i].product_id) { - require_wakeup = true; - break; + if (!sc->open) { + for (i = 0; i < nitems(special_fw); i++) { + if (sc->ic_type == special_fw[i].ic_type && + sc->product_id == special_fw[i].product_id) { + require_wakeup = true; + break; + } } } if (require_wakeup && hid_intr_start(dev) != 0) { device_printf(dev, "failed writing poweron command\n"); return (EIO); } val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD; if (ietp_iic_write_reg(dev, IETP_CONTROL, val) != 0) { device_printf(dev, "failed setting absolute mode\n"); error = EIO; } if (require_wakeup && hid_intr_stop(dev) != 0) { device_printf(dev, "failed writing poweroff command\n"); error = EIO; } return (error); } static int ietp_iic_read_reg(device_t dev, uint16_t reg, size_t len, void *val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[2] = { reg & 0xff, (reg >> 8) & 0xff }; struct iic_msg msgs[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd }, { addr, IIC_M_RD, len, val }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; int error; DPRINTF("Read reg 0x%04x with size %zu\n", reg, len); error = hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird); if (error != 0) return (error); DPRINTF("Response: %*D\n", (int)len, val, " "); return (0); } static int ietp_iic_write_reg(device_t dev, uint16_t reg, uint16_t val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[4] = { reg & 0xff, (reg >> 8) & 0xff, val & 0xff, (val >> 8) & 0xff }; struct iic_msg msgs[1] = { { addr, IIC_M_WR, sizeof(cmd), cmd }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; DPRINTF("Write reg 0x%04x with value 0x%04x\n", reg, val); return (hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird)); } static device_method_t ietp_methods[] = { DEVMETHOD(device_identify, ietp_iic_identify), DEVMETHOD(device_probe, ietp_iic_probe), DEVMETHOD(device_attach, ietp_iic_attach), DEVMETHOD(device_detach, ietp_iic_detach), DEVMETHOD(device_resume, ietp_iic_resume), DEVMETHOD_END }; static driver_t ietp_driver = { .name = "ietp", .methods = ietp_methods, .size = sizeof(struct ietp_softc), }; DRIVER_MODULE(ietp, hidbus, ietp_driver, NULL, NULL); MODULE_DEPEND(ietp, hidbus, 1, 1, 1); MODULE_DEPEND(ietp, hid, 1, 1, 1); MODULE_DEPEND(ietp, evdev, 1, 1, 1); MODULE_VERSION(ietp, 1); HID_PNP_INFO(ietp_iic_devs); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c index 3f1d7a0cefba..fdb4816b8bd9 100644 --- a/sys/dev/iicbus/iichid.c +++ b/sys/dev/iicbus/iichid.c @@ -1,1382 +1,1383 @@ /*- * Copyright (c) 2018-2019 Marc Priggemeyer * Copyright (c) 2019-2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * I2C HID transport backend. */ #include #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid_if.h" #ifdef IICHID_DEBUG static int iichid_debug = 0; static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID"); SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN, &iichid_debug, 1, "Debug level"); #define DPRINTFN(sc, n, ...) do { \ if (iichid_debug >= (n)) \ device_printf((sc)->dev, __VA_ARGS__); \ } while (0) #define DPRINTF(sc, ...) DPRINTFN(sc, 1, __VA_ARGS__) #else #define DPRINTFN(...) do {} while (0) #define DPRINTF(...) do {} while (0) #endif typedef hid_size_t iichid_size_t; #define IICHID_SIZE_MAX (UINT16_MAX - 2) /* 7.2 */ enum { I2C_HID_CMD_DESCR = 0x0, I2C_HID_CMD_RESET = 0x1, I2C_HID_CMD_GET_REPORT = 0x2, I2C_HID_CMD_SET_REPORT = 0x3, I2C_HID_CMD_GET_IDLE = 0x4, I2C_HID_CMD_SET_IDLE = 0x5, I2C_HID_CMD_GET_PROTO = 0x6, I2C_HID_CMD_SET_PROTO = 0x7, I2C_HID_CMD_SET_POWER = 0x8, }; #define I2C_HID_POWER_ON 0x0 #define I2C_HID_POWER_OFF 0x1 #define IICHID_RESET_TIMEOUT 5 /* seconds */ /* * Since interrupt resource acquisition is not always possible (in case of GPIO * interrupts) iichid now supports a sampling_mode. * Set dev.iichid..sampling_rate_slow to a value greater then 0 * to activate sampling. A value of 0 is possible but will not reset the * callout and, thereby, disable further report requests. Do not set the * sampling_rate_fast value too high as it may result in periodical lags of * cursor motion. */ #define IICHID_SAMPLING_RATE_FAST 80 #define IICHID_SAMPLING_RATE_SLOW 10 #define IICHID_SAMPLING_HYSTERESIS 16 /* ~ 2x fast / slow */ /* 5.1.1 - HID Descriptor Format */ struct i2c_hid_desc { uint16_t wHIDDescLength; uint16_t bcdVersion; uint16_t wReportDescLength; uint16_t wReportDescRegister; uint16_t wInputRegister; uint16_t wMaxInputLength; uint16_t wOutputRegister; uint16_t wMaxOutputLength; uint16_t wCommandRegister; uint16_t wDataRegister; uint16_t wVendorID; uint16_t wProductID; uint16_t wVersionID; uint32_t reserved; } __packed; #define IICHID_REG_NONE -1 #define IICHID_REG_ACPI (UINT16_MAX + 1) #define IICHID_REG_ELAN 0x0001 static const struct iichid_id { char *id; int reg; } iichid_ids[] = { { "ELAN0000", IICHID_REG_ELAN }, { "PNP0C50", IICHID_REG_ACPI }, { "ACPI0C50", IICHID_REG_ACPI }, { NULL, 0 }, }; enum iichid_powerstate_how { IICHID_PS_NULL, IICHID_PS_ON, IICHID_PS_OFF, }; /* * Locking: no internal locks are used. To serialize access to shared members, * external iicbus lock should be taken. That allows to make locking greatly * simple at the cost of running front interrupt handlers with locked bus. */ struct iichid_softc { device_t dev; struct mtx mtx; bool probe_done; int probe_result; struct hid_device_info hw; uint16_t addr; /* Shifted left by 1 */ struct i2c_hid_desc desc; hid_intr_t *intr_handler; void *intr_ctx; uint8_t *intr_buf; iichid_size_t intr_bufsize; int irq_rid; struct resource *irq_res; void *irq_cookie; #ifdef IICHID_SAMPLING int sampling_rate_slow; /* iicbus lock */ int sampling_rate_fast; int sampling_hysteresis; int missing_samples; /* iicbus lock */ int dup_samples; /* iicbus lock */ iichid_size_t dup_size; /* iicbus lock */ bool callout_setup; /* iicbus lock */ uint8_t *dup_buf; struct taskqueue *taskqueue; struct timeout_task sampling_task; /* iicbus lock */ #endif struct task suspend_task; bool open; /* iicbus lock */ bool suspend; /* iicbus lock */ bool power_on; /* iicbus lock */ bool reset_acked; /* iichid mtx */ }; static device_probe_t iichid_probe; static device_attach_t iichid_attach; static device_detach_t iichid_detach; static device_resume_t iichid_resume; static device_suspend_t iichid_suspend; static void iichid_suspend_task(void *, int); #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *); static int iichid_reset_callout(struct iichid_softc *); static void iichid_teardown_callout(struct iichid_softc *); #endif static inline int acpi_is_iichid(ACPI_HANDLE handle) { const struct iichid_id *ids; UINT32 sta; int reg; for (ids = iichid_ids; ids->id != NULL; ids++) { if (acpi_MatchHid(handle, ids->id)) { reg = ids->reg; break; } } if (ids->id == NULL) return (IICHID_REG_NONE); /* * If no _STA method or if it failed, then assume that * the device is present. */ if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) || ACPI_DEVICE_PRESENT(sta)) return (reg); return (IICHID_REG_NONE); } static ACPI_STATUS iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg) { ACPI_OBJECT *result; ACPI_BUFFER acpi_buf; ACPI_STATUS status; /* * function (_DSM) to be evaluated to retrieve the address of * the configuration register of the HID device. */ /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */ static uint8_t dsm_guid[ACPI_UUID_LENGTH] = { 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, }; status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf, ACPI_TYPE_INTEGER); if (ACPI_FAILURE(status)) { printf("%s: error evaluating _DSM\n", __func__); return (status); } result = (ACPI_OBJECT *) acpi_buf.Pointer; *config_reg = result->Integer.Value & 0xFFFF; AcpiOsFree(result); return (status); } static int iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, iichid_size_t *actual_len) { /* * 6.1.3 - Retrieval of Input Reports * DEVICE returns the length (2 Bytes) and the entire Input Report. */ memset(buf, 0xaa, 2); // In case nothing gets read struct iic_msg msgs[] = { { sc->addr, IIC_M_RD, maxlen, buf }, }; int error; error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); DPRINTFN(sc, 5, "%*D\n", msgs[0].len, msgs[0].buf, " "); uint16_t actlen = le16dec(buf); if (actlen == 0) { if (!sc->reset_acked) { mtx_lock(&sc->mtx); sc->reset_acked = true; wakeup(&sc->reset_acked); mtx_unlock(&sc->mtx); } } if (actlen <= 2 || actlen > maxlen) { actlen = 0; } if (actual_len != NULL) { *actual_len = actlen; } return (error); } static int iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len) { /* 6.2.3 - Sending Output Reports. */ uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister; uint16_t replen = 2 + len; uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 }; struct iic_msg msgs[] = { {sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd}, {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, }; if (le16toh(sc->desc.wMaxOutputLength) == 0) return (IIC_ENOTSUPP); if (len < 2) return (IIC_ENOTSUPP); DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): " "%*D\n", len, len, buf, " "); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg, struct i2c_hid_desc *hid_desc) { /* * 5.2.2 - HID Descriptor Retrieval * register is passed from the controller. */ uint16_t cmd = htole16(config_reg); struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, { sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc }, }; int error; DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg); error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); DPRINTF(sc, "HID descriptor: %*D\n", (int)sizeof(struct i2c_hid_desc), hid_desc, " "); return (0); } static int iichid_set_power(struct iichid_softc *sc, uint8_t param) { uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER }; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, }; DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_reset(struct iichid_softc *sc) { uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET }; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, }; DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n"); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } static int iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf, iichid_size_t len) { uint16_t cmd = sc->desc.wReportDescRegister; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, { sc->addr, IIC_M_RD, len, buf }, }; int error; DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n", le16toh(cmd), len); error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " "); return (0); } static int iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, iichid_size_t *actual_len, uint8_t type, uint8_t id) { /* * 7.2.2.4 - "The protocol is optimized for Report < 15. If a * report ID >= 15 is necessary, then the Report ID in the Low Byte * must be set to 1111 and a Third Byte is appended to the protocol. * This Third Byte contains the entire/actual report ID." */ uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ cmdreg[0] , cmdreg[1] , (id >= 15 ? 15 | (type << 4): id | (type << 4)), I2C_HID_CMD_GET_REPORT , (id >= 15 ? id : dtareg[0] ), (id >= 15 ? dtareg[0] : dtareg[1] ), (id >= 15 ? dtareg[1] : 0 ), }; int cmdlen = (id >= 15 ? 7 : 6 ); uint8_t actbuf[2] = { 0, 0 }; uint16_t actlen; int d, error; struct iic_msg msgs[] = { { sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd }, { sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf }, { sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf }, }; if (maxlen == 0) return (EINVAL); DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d " "(type %d, len %d)\n", id, type, maxlen); /* * 7.2.2.2 - Response will be a 2-byte length value, the report * id (1 byte, if defined in Report Descriptor), and then the report. */ error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); actlen = actbuf[0] | actbuf[1] << 8; if (actlen != maxlen + 2) DPRINTF(sc, "response size %d != expected length %d\n", actlen, maxlen + 2); if (actlen <= 2 || actlen == 0xFFFF) return (ENOMSG); d = id != 0 ? *(uint8_t *)buf : 0; if (d != id) { DPRINTF(sc, "response report id %d != %d\n", d, id); return (EBADMSG); } actlen -= 2; if (actlen > maxlen) actlen = maxlen; if (actual_len != NULL) *actual_len = actlen; DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " "); return (0); } static int iichid_cmd_set_report(struct iichid_softc* sc, const void *buf, iichid_size_t len, uint8_t type, uint8_t id) { /* * 7.2.2.4 - "The protocol is optimized for Report < 15. If a * report ID >= 15 is necessary, then the Report ID in the Low Byte * must be set to 1111 and a Third Byte is appended to the protocol. * This Third Byte contains the entire/actual report ID." */ uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; uint16_t replen = 2 + len; uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ cmdreg[0] , cmdreg[1] , (id >= 15 ? 15 | (type << 4): id | (type << 4)), I2C_HID_CMD_SET_REPORT , (id >= 15 ? id : dtareg[0] ), (id >= 15 ? dtareg[0] : dtareg[1] ), (id >= 15 ? dtareg[1] : replen & 0xff ), (id >= 15 ? replen & 0xff : replen >> 8 ), (id >= 15 ? replen >> 8 : 0 ), }; int cmdlen = (id >= 15 ? 9 : 8 ); struct iic_msg msgs[] = { {sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd}, {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, }; DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): " "%*D\n", id, type, len, len, buf, " "); return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); } #ifdef IICHID_SAMPLING static void iichid_sampling_task(void *context, int pending) { struct iichid_softc *sc; device_t parent; iichid_size_t actual; bool bus_requested; int error, rate; sc = context; parent = device_get_parent(sc->dev); bus_requested = false; if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0) goto rearm; bus_requested = true; if (!sc->power_on) goto out; error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); if (error == 0) { if (actual > 0) { sc->intr_handler(sc->intr_ctx, sc->intr_buf + 2, actual); sc->missing_samples = 0; if (sc->dup_size != actual || memcmp(sc->dup_buf, sc->intr_buf, actual) != 0) { sc->dup_size = actual; memcpy(sc->dup_buf, sc->intr_buf, actual); sc->dup_samples = 0; } else ++sc->dup_samples; } else { if (++sc->missing_samples == 1) sc->intr_handler(sc->intr_ctx, sc->intr_buf + 2, 0); sc->dup_samples = 0; } } else DPRINTF(sc, "read error occurred: %d\n", error); rearm: if (sc->callout_setup && sc->sampling_rate_slow > 0) { if (sc->missing_samples >= sc->sampling_hysteresis || sc->dup_samples >= sc->sampling_hysteresis) rate = sc->sampling_rate_slow; else rate = sc->sampling_rate_fast; taskqueue_enqueue_timeout_sbt(sc->taskqueue, &sc->sampling_task, SBT_1S / MAX(rate, 1), 0, C_PREL(2)); } out: if (bus_requested) iicbus_release_bus(parent, sc->dev); } #endif /* IICHID_SAMPLING */ static void iichid_intr(void *context) { struct iichid_softc *sc; device_t parent; iichid_size_t actual; int error; sc = context; parent = device_get_parent(sc->dev); /* * Designware(IG4) driver-specific hack. * Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled * mode in the driver, making possible iicbus_transfer execution from * interrupt handlers and callouts. */ if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0) return; /* * Reading of input reports of I2C devices residing in SLEEP state is * not allowed and often returns a garbage. If a HOST needs to * communicate with the DEVICE it MUST issue a SET POWER command * (to ON) before any other command. As some hardware requires reads to * acknowledge interrupts we fetch only length header and discard it. */ THREAD_SLEEPING_OK(); error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); THREAD_NO_SLEEPING(); if (error == 0) { if (sc->power_on && sc->open) { if (actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf + 2, actual); else DPRINTF(sc, "no data received\n"); } } else DPRINTF(sc, "read error occurred: %d\n", error); iicbus_release_bus(parent, sc->dev); } static int iichid_set_power_state(struct iichid_softc *sc, enum iichid_powerstate_how how_open, enum iichid_powerstate_how how_suspend) { device_t parent; int error; int how_request; bool power_on; /* * Request iicbus early as sc->suspend and sc->power_on * are protected by iicbus internal lock. */ parent = device_get_parent(sc->dev); /* Allow to interrupt open()/close() handlers by SIGINT */ how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT; error = iicbus_request_bus(parent, sc->dev, how_request); if (error != 0) return (error); switch (how_open) { case IICHID_PS_ON: sc->open = true; break; case IICHID_PS_OFF: sc->open = false; break; case IICHID_PS_NULL: default: break; } switch (how_suspend) { case IICHID_PS_ON: sc->suspend = false; break; case IICHID_PS_OFF: sc->suspend = true; break; case IICHID_PS_NULL: default: break; } power_on = sc->open & !sc->suspend; if (power_on != sc->power_on) { error = iichid_set_power(sc, power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF); sc->power_on = power_on; #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) { if (power_on) { iichid_setup_callout(sc); iichid_reset_callout(sc); } else iichid_teardown_callout(sc); } #endif } iicbus_release_bus(parent, sc->dev); return (error); } static int iichid_setup_interrupt(struct iichid_softc *sc) { sc->irq_cookie = 0; int error = bus_setup_intr(sc->dev, sc->irq_res, INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie); if (error != 0) DPRINTF(sc, "Could not setup interrupt handler\n"); else DPRINTF(sc, "successfully setup interrupt\n"); return (error); } static void iichid_teardown_interrupt(struct iichid_softc *sc) { if (sc->irq_cookie) bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie); sc->irq_cookie = 0; } #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow < 0) { DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n"); return (EINVAL); } sc->callout_setup = true; DPRINTF(sc, "successfully setup callout\n"); return (0); } static int iichid_reset_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow <= 0) { DPRINTF(sc, "sampling_rate is below or equal to 0, " "can't reset callout\n"); return (EINVAL); } if (!sc->callout_setup) return (EINVAL); /* Start with slow sampling. */ sc->missing_samples = sc->sampling_hysteresis; sc->dup_samples = 0; sc->dup_size = 0; taskqueue_enqueue_timeout(sc->taskqueue, &sc->sampling_task, 0); return (0); } static void iichid_teardown_callout(struct iichid_softc *sc) { sc->callout_setup = false; taskqueue_cancel_timeout(sc->taskqueue, &sc->sampling_task, NULL); DPRINTF(sc, "tore callout down\n"); } static int iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS) { struct iichid_softc *sc; device_t parent; int error, oldval, value; sc = arg1; value = sc->sampling_rate_slow; error = sysctl_handle_int(oidp, &value, 0, req); if (error != 0 || req->newptr == NULL || value == sc->sampling_rate_slow) return (error); /* Can't switch to interrupt mode if it is not supported. */ if (sc->irq_res == NULL && value < 0) return (EINVAL); parent = device_get_parent(sc->dev); error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); if (error != 0) return (iic2errno(error)); oldval = sc->sampling_rate_slow; sc->sampling_rate_slow = value; if (oldval < 0 && value >= 0) { iichid_teardown_interrupt(sc); if (sc->power_on) iichid_setup_callout(sc); } else if (oldval >= 0 && value < 0) { if (sc->power_on) iichid_teardown_callout(sc); iichid_setup_interrupt(sc); } if (sc->power_on && value > 0) iichid_reset_callout(sc); iicbus_release_bus(parent, sc->dev); DPRINTF(sc, "new sampling_rate value: %d\n", value); return (0); } #endif /* IICHID_SAMPLING */ static void iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct iichid_softc *sc; device_t parent; if (intr == NULL) return; sc = device_get_softc(dev); /* * Do not rely just on wMaxInputLength, as some devices (which?) * may set it to a wrong length. Also find the longest input report * in report descriptor, and add two for the length field. */ rdesc->rdsize = 2 + MAX(rdesc->isize, le16toh(sc->desc.wMaxInputLength)); /* Write and get/set_report sizes are limited by I2C-HID protocol. */ rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX; rdesc->wrsize = IICHID_SIZE_MAX; parent = device_get_parent(sc->dev); iicbus_request_bus(parent, sc->dev, IIC_WAIT); sc->intr_handler = intr; sc->intr_ctx = context; sc->intr_bufsize = rdesc->rdsize; sc->intr_buf = realloc(sc->intr_buf, sc->intr_bufsize, M_DEVBUF, M_WAITOK | M_ZERO); #ifdef IICHID_SAMPLING sc->dup_buf = realloc(sc->dup_buf, sc->intr_bufsize, M_DEVBUF, M_WAITOK | M_ZERO); taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, "%s taskq", device_get_nameunit(sc->dev)); #endif iicbus_release_bus(parent, sc->dev); } static void iichid_intr_unsetup(device_t dev, device_t child __unused) { #ifdef IICHID_SAMPLING struct iichid_softc *sc; sc = device_get_softc(dev); taskqueue_drain_all(sc->taskqueue); #endif } static int iichid_intr_start(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device open\n"); - iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL); + if (!sc->open) + iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL); return (0); } static int iichid_intr_stop(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device close\n"); /* * 8.2 - The HOST determines that there are no active applications * that are currently using the specific HID DEVICE. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL); return (0); } static void iichid_intr_poll(device_t dev, device_t child __unused) { struct iichid_softc *sc; iichid_size_t actual; int error; sc = device_get_softc(dev); error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); if (error == 0 && actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf + 2, actual); } /* * HID interface */ static int iichid_get_rdesc(device_t dev, device_t child __unused, void *buf, hid_size_t len) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); error = iichid_cmd_get_report_desc(sc, buf, len); if (error) DPRINTF(sc, "failed to fetch report descriptor: %d\n", error); return (iic2errno(error)); } static int iichid_read(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen) { struct iichid_softc *sc; device_t parent; uint8_t *tmpbuf; int error; if (maxlen > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); parent = device_get_parent(sc->dev); error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); if (error == 0) { tmpbuf = malloc(maxlen + 2, M_DEVBUF, M_WAITOK | M_ZERO); error = iichid_cmd_read(sc, tmpbuf, maxlen + 2, actlen); iicbus_release_bus(parent, sc->dev); if (*actlen > 0) memcpy(buf, tmpbuf + 2, *actlen); free(tmpbuf, M_DEVBUF); } return (iic2errno(error)); } static int iichid_write(device_t dev, device_t child __unused, const void *buf, hid_size_t len) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_write(sc, buf, len))); } static int iichid_get_report(device_t dev, device_t child __unused, void *buf, hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (maxlen > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno( iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id))); } static int iichid_set_report(device_t dev, device_t child __unused, const void *buf, hid_size_t len, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id))); } static int iichid_set_idle(device_t dev, device_t child __unused, uint16_t duration, uint8_t id) { return (ENOTSUP); } static int iichid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) { return (ENOTSUP); } static int iichid_ioctl(device_t dev, device_t child __unused, unsigned long cmd, uintptr_t data) { int error; switch (cmd) { case I2CRDWR: error = iic2errno(iicbus_transfer(dev, ((struct iic_rdwr_data *)data)->msgs, ((struct iic_rdwr_data *)data)->nmsgs)); break; default: error = EINVAL; } return (error); } static int iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle, struct hid_device_info *hw) { ACPI_DEVICE_INFO *device_info; hw->idBus = BUS_I2C; hw->idVendor = le16toh(desc->wVendorID); hw->idProduct = le16toh(desc->wProductID); hw->idVersion = le16toh(desc->wVersionID); /* get ACPI HID. It is a base part of the device name. */ if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info))) return (ENXIO); if (device_info->Valid & ACPI_VALID_HID) strlcpy(hw->idPnP, device_info->HardwareId.String, HID_PNP_ID_SIZE); snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X", (device_info->Valid & ACPI_VALID_HID) ? device_info->HardwareId.String : "Unknown", (device_info->Valid & ACPI_VALID_UID) ? strtoul(device_info->UniqueId.String, NULL, 10) : 0UL, le16toh(desc->wVendorID), le16toh(desc->wProductID)); AcpiOsFree(device_info); strlcpy(hw->serial, "", sizeof(hw->serial)); hw->rdescsize = le16toh(desc->wReportDescLength); if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0) hid_add_dynamic_quirk(hw, HQ_NOWRITE); return (0); } static int iichid_probe(device_t dev) { struct iichid_softc *sc; ACPI_HANDLE handle; uint16_t config_reg; int error, reg; sc = device_get_softc(dev); sc->dev = dev; if (sc->probe_done) goto done; sc->probe_done = true; sc->probe_result = ENXIO; if (acpi_disabled("iichid")) return (ENXIO); sc->addr = iicbus_get_addr(dev) << 1; if (sc->addr == 0) return (ENXIO); handle = acpi_get_handle(dev); if (handle == NULL) return (ENXIO); reg = acpi_is_iichid(handle); if (reg == IICHID_REG_NONE) return (ENXIO); if (reg == IICHID_REG_ACPI) { if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) return (ENXIO); } else config_reg = (uint16_t)reg; DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1); DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg); error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc); if (error) { DPRINTF(sc, "could not retrieve HID descriptor from the " "device: %d\n", error); return (ENXIO); } if (le16toh(sc->desc.wHIDDescLength) != 30 || le16toh(sc->desc.bcdVersion) != 0x100) { DPRINTF(sc, "HID descriptor is broken\n"); return (ENXIO); } /* Setup hid_device_info so we can figure out quirks for the device. */ if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) { DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n"); return (ENXIO); } if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE)) return (ENXIO); sc->probe_result = BUS_PROBE_DEFAULT; done: if (sc->probe_result <= BUS_PROBE_SPECIFIC) device_set_descf(dev, "%s I2C HID device", sc->hw.name); return (sc->probe_result); } static int iichid_attach(device_t dev) { struct iichid_softc *sc; device_t child; int error; sc = device_get_softc(dev); error = iichid_set_power(sc, I2C_HID_POWER_ON); if (error) { device_printf(dev, "failed to power on: %d\n", error); return (ENXIO); } sc->power_on = true; mtx_init(&sc->mtx, device_get_nameunit(dev), NULL, MTX_DEF); sc->intr_bufsize = le16toh(sc->desc.wMaxInputLength) - 2; sc->intr_buf = malloc(sc->intr_bufsize, M_DEVBUF, M_WAITOK | M_ZERO); TASK_INIT(&sc->suspend_task, 0, iichid_suspend_task, sc); #ifdef IICHID_SAMPLING sc->taskqueue = taskqueue_create_fast("iichid_tq", M_WAITOK | M_ZERO, taskqueue_thread_enqueue, &sc->taskqueue); TIMEOUT_TASK_INIT(sc->taskqueue, &sc->sampling_task, 0, iichid_sampling_task, sc); sc->sampling_rate_slow = -1; sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST; sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS; sc->dup_buf = malloc(sc->intr_bufsize, M_DEVBUF, M_WAITOK | M_ZERO); #endif sc->irq_rid = 0; sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &sc->irq_rid, RF_ACTIVE); if (sc->irq_res != NULL) { DPRINTF(sc, "allocated irq at %p and rid %d\n", sc->irq_res, sc->irq_rid); error = iichid_setup_interrupt(sc); } if (sc->irq_res == NULL || error != 0) { #ifdef IICHID_SAMPLING device_printf(sc->dev, "Using sampling mode\n"); sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW; #else device_printf(sc->dev, "Interrupt setup failed\n"); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); iichid_detach(dev); error = ENXIO; goto done; #endif } #ifdef IICHID_SAMPLING SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN, sc, 0, iichid_sysctl_sampling_rate_handler, "I", "idle sampling rate in num/second"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_rate_fast", CTLFLAG_RWTUN, &sc->sampling_rate_fast, 0, "active sampling rate in num/second"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_hysteresis", CTLFLAG_RWTUN, &sc->sampling_hysteresis, 0, "number of missing samples before enabling of slow mode"); hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING); #endif /* IICHID_SAMPLING */ /* * Windows driver sleeps for 1ms between the SET_POWER and RESET * commands. So we too as some devices may depend on this. */ pause("iichid", (hz + 999) / 1000); error = iichid_reset(sc); if (error) { device_printf(dev, "failed to reset hardware: %d\n", error); iichid_detach(dev); error = ENXIO; goto done; } /* Wait for RESET response */ #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow >= 0) { pause("iichid", (hz + 999) / 1000); (void)iichid_cmd_read(sc, sc->intr_buf, 0, NULL); } else #endif /* IICHID_SAMPLING */ { mtx_lock(&sc->mtx); if (!sc->reset_acked && !cold) { error = mtx_sleep(&sc->reset_acked, &sc->mtx, 0, "iichid_reset", hz * IICHID_RESET_TIMEOUT); if (error != 0) device_printf(sc->dev, "Reset timeout expired\n"); } mtx_unlock(&sc->mtx); } child = device_add_child(dev, "hidbus", DEVICE_UNIT_ANY); if (child == NULL) { device_printf(sc->dev, "Could not add I2C device\n"); iichid_detach(dev); error = ENOMEM; goto done; } device_set_ivars(child, &sc->hw); bus_attach_children(dev); error = 0; done: iicbus_request_bus(device_get_parent(dev), dev, IIC_WAIT); if (!sc->open) { (void)iichid_set_power(sc, I2C_HID_POWER_OFF); sc->power_on = false; } iicbus_release_bus(device_get_parent(dev), dev); return (error); } static int iichid_detach(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); error = bus_generic_detach(dev); if (error) return (error); iichid_teardown_interrupt(sc); if (sc->irq_res != NULL) bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, sc->irq_res); #ifdef IICHID_SAMPLING if (sc->taskqueue != NULL) taskqueue_free(sc->taskqueue); sc->taskqueue = NULL; free(sc->dup_buf, M_DEVBUF); #endif free(sc->intr_buf, M_DEVBUF); mtx_destroy(&sc->mtx); return (0); } static void iichid_suspend_task(void *context, int pending) { struct iichid_softc *sc = context; iichid_teardown_interrupt(sc); } static int iichid_suspend(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); (void)bus_generic_suspend(dev); /* * 8.2 - The HOST is going into a deep power optimized state and wishes * to put all the devices into a low power state also. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ DPRINTF(sc, "Suspend called, setting device to power_state 1\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif { /* * bus_teardown_intr can not be executed right here as it wants * to run on certain CPU to interacts with LAPIC while suspend * thread is bound to CPU0. So run it from taskqueue context. */ #ifdef IICHID_SAMPLING #define suspend_thread sc->taskqueue #else #define suspend_thread taskqueue_thread #endif taskqueue_enqueue(suspend_thread, &sc->suspend_task); taskqueue_drain(suspend_thread, &sc->suspend_task); } return (0); } static int iichid_resume(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif iichid_setup_interrupt(sc); DPRINTF(sc, "Resume called, setting device to power_state 0\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); (void)bus_generic_resume(dev); return (0); } static device_method_t iichid_methods[] = { DEVMETHOD(device_probe, iichid_probe), DEVMETHOD(device_attach, iichid_attach), DEVMETHOD(device_detach, iichid_detach), DEVMETHOD(device_suspend, iichid_suspend), DEVMETHOD(device_resume, iichid_resume), DEVMETHOD(hid_intr_setup, iichid_intr_setup), DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup), DEVMETHOD(hid_intr_start, iichid_intr_start), DEVMETHOD(hid_intr_stop, iichid_intr_stop), DEVMETHOD(hid_intr_poll, iichid_intr_poll), /* HID interface */ DEVMETHOD(hid_get_rdesc, iichid_get_rdesc), DEVMETHOD(hid_read, iichid_read), DEVMETHOD(hid_write, iichid_write), DEVMETHOD(hid_get_report, iichid_get_report), DEVMETHOD(hid_set_report, iichid_set_report), DEVMETHOD(hid_set_idle, iichid_set_idle), DEVMETHOD(hid_set_protocol, iichid_set_protocol), DEVMETHOD(hid_ioctl, iichid_ioctl), DEVMETHOD_END }; static driver_t iichid_driver = { .name = "iichid", .methods = iichid_methods, .size = sizeof(struct iichid_softc), }; DRIVER_MODULE(iichid, iicbus, iichid_driver, NULL, NULL); MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_DEPEND(iichid, acpi, 1, 1, 1); MODULE_DEPEND(iichid, hid, 1, 1, 1); MODULE_DEPEND(iichid, hidbus, 1, 1, 1); MODULE_VERSION(iichid, 1); IICBUS_ACPI_PNP_INFO(iichid_ids);