diff --git a/sys/dev/hid/hidmap.c b/sys/dev/hid/hidmap.c index 163d63c20232..46e789fa7cec 100644 --- a/sys/dev/hid/hidmap.c +++ b/sys/dev/hid/hidmap.c @@ -1,832 +1,832 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 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 __FBSDID("$FreeBSD$"); /* * Abstract 1 to 1 HID input usage to evdev event mapper driver. */ #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HID_DEBUG #define DPRINTFN(hm, n, fmt, ...) do { \ if ((hm)->debug_var != NULL && *(hm)->debug_var >= (n)) { \ device_printf((hm)->dev, "%s: " fmt, \ __FUNCTION__ ,##__VA_ARGS__); \ } \ } while (0) #define DPRINTF(hm, ...) DPRINTFN(hm, 1, __VA_ARGS__) #else #define DPRINTF(...) do { } while (0) #define DPRINTFN(...) do { } while (0) #endif static evdev_open_t hidmap_ev_open; static evdev_close_t hidmap_ev_close; #define HIDMAP_WANT_MERGE_KEYS(hm) ((hm)->key_rel != NULL) #define HIDMAP_FOREACH_ITEM(hm, mi, uoff) \ for (u_int _map = 0, _item = 0, _uoff_priv = -1; \ ((mi) = hidmap_get_next_map_item( \ (hm), &_map, &_item, &_uoff_priv, &(uoff))) != NULL;) static inline bool hidmap_get_next_map_index(const struct hidmap_item *map, int nmap_items, uint32_t *index, uint16_t *usage_offset) { ++*usage_offset; if ((*index != 0 || *usage_offset != 0) && *usage_offset >= map[*index].nusages) { ++*index; *usage_offset = 0; } return (*index < nmap_items); } static inline const struct hidmap_item * hidmap_get_next_map_item(struct hidmap *hm, u_int *map, u_int *item, u_int *uoff_priv, uint16_t *uoff) { *uoff = *uoff_priv; while (!hidmap_get_next_map_index( hm->map[*map], hm->nmap_items[*map], item, uoff)) { ++*map; *item = 0; *uoff = -1; if (*map >= hm->nmaps) return (NULL); } *uoff_priv = *uoff; return (hm->map[*map] + *item); } void _hidmap_set_debug_var(struct hidmap *hm, int *debug_var) { #ifdef HID_DEBUG hm->debug_var = debug_var; #endif } static int hidmap_ev_close(struct evdev_dev *evdev) { return (hidbus_intr_stop(evdev_get_softc(evdev))); } static int hidmap_ev_open(struct evdev_dev *evdev) { return (hidbus_intr_start(evdev_get_softc(evdev))); } void hidmap_support_key(struct hidmap *hm, uint16_t key) { if (hm->key_press == NULL) { hm->key_press = malloc(howmany(KEY_CNT, 8), M_DEVBUF, M_ZERO | M_WAITOK); evdev_support_event(hm->evdev, EV_KEY); hm->key_min = key; hm->key_max = key; } hm->key_min = MIN(hm->key_min, key); hm->key_max = MAX(hm->key_max, key); if (isset(hm->key_press, key)) { if (hm->key_rel == NULL) hm->key_rel = malloc(howmany(KEY_CNT, 8), M_DEVBUF, M_ZERO | M_WAITOK); } else { setbit(hm->key_press, key); evdev_support_key(hm->evdev, key); } } void hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value) { if (HIDMAP_WANT_MERGE_KEYS(hm)) setbit(value != 0 ? hm->key_press : hm->key_rel, key); else evdev_push_key(hm->evdev, key, value); } static void hidmap_sync_keys(struct hidmap *hm) { int i, j; bool press, rel; for (j = hm->key_min / 8; j <= hm->key_max / 8; j++) { if (hm->key_press[j] != hm->key_rel[j]) { for (i = j * 8; i < j * 8 + 8; i++) { press = isset(hm->key_press, i); rel = isset(hm->key_rel, i); if (press != rel) evdev_push_key(hm->evdev, i, press); } } } bzero(hm->key_press, howmany(KEY_CNT, 8)); bzero(hm->key_rel, howmany(KEY_CNT, 8)); } void hidmap_intr(void *context, void *buf, hid_size_t len) { struct hidmap *hm = context; struct hidmap_hid_item *hi; const struct hidmap_item *mi; int32_t usage; int32_t data; uint16_t key, uoff; uint8_t id = 0; bool found, do_sync = false; DPRINTFN(hm, 6, "hm=%p len=%d\n", hm, len); DPRINTFN(hm, 6, "data = %*D\n", len, buf, " "); /* Strip leading "report ID" byte */ if (hm->hid_items[0].id) { id = *(uint8_t *)buf; len--; buf = (uint8_t *)buf + 1; } hm->intr_buf = buf; hm->intr_len = len; for (hi = hm->hid_items; hi < hm->hid_items + hm->nhid_items; hi++) { /* At first run callbacks that not tied to HID items */ if (hi->type == HIDMAP_TYPE_FINALCB) { DPRINTFN(hm, 6, "type=%d item=%*D\n", hi->type, (int)sizeof(hi->cb), &hi->cb, " "); if (hi->cb(hm, hi, (union hidmap_cb_ctx){.rid = id}) == 0) do_sync = true; continue; } /* Ignore irrelevant reports */ if (id != hi->id) continue; /* * 5.8. If Logical Minimum and Logical Maximum are both * positive values then the contents of a field can be assumed * to be an unsigned value. Otherwise, all integer values are * signed values represented in 2’s complement format. */ data = hi->lmin < 0 || hi->lmax < 0 ? hid_get_data(buf, len, &hi->loc) : hid_get_udata(buf, len, &hi->loc); DPRINTFN(hm, 6, "type=%d data=%d item=%*D\n", hi->type, data, (int)sizeof(hi->cb), &hi->cb, " "); if (hi->invert_value && hi->type < HIDMAP_TYPE_ARR_LIST) data = hi->evtype == EV_REL ? -data : hi->lmin + hi->lmax - data; switch (hi->type) { case HIDMAP_TYPE_CALLBACK: if (hi->cb(hm, hi, (union hidmap_cb_ctx){.data = data}) != 0) continue; break; case HIDMAP_TYPE_VAR_NULLST: /* * 5.10. If the host or the device receives an * out-of-range value then the current value for the * respective control will not be modified. */ if (data < hi->lmin || data > hi->lmax) continue; /* FALLTHROUGH */ case HIDMAP_TYPE_VARIABLE: /* * Ignore reports for absolute data if the data did not * change and for relative data if data is 0. * Evdev layer filters out them anyway. */ if (data == (hi->evtype == EV_REL ? 0 : hi->last_val)) continue; if (hi->evtype == EV_KEY) hidmap_push_key(hm, hi->code, data); else evdev_push_event(hm->evdev, hi->evtype, hi->code, data); hi->last_val = data; break; case HIDMAP_TYPE_ARR_LIST: key = KEY_RESERVED; /* * 6.2.2.5. An out-of range value in an array field * is considered no controls asserted. */ if (data < hi->lmin || data > hi->lmax) goto report_key; /* * 6.2.2.5. Rather than returning a single bit for each * button in the group, an array returns an index in * each field that corresponds to the pressed button. */ key = hi->codes[data - hi->lmin]; if (key == KEY_RESERVED) DPRINTF(hm, "Can not map unknown HID " "array index: %08x\n", data); goto report_key; case HIDMAP_TYPE_ARR_RANGE: key = KEY_RESERVED; /* * 6.2.2.5. An out-of range value in an array field * is considered no controls asserted. */ if (data < hi->lmin || data > hi->lmax) goto report_key; /* * When the input field is an array and the usage is * specified with a range instead of an ID, we have to * derive the actual usage by using the item value as * an index in the usage range list. */ usage = data - hi->lmin + hi->umin; found = false; HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (usage == mi->usage + uoff && mi->type == EV_KEY && !mi->has_cb) { key = mi->code; found = true; break; } } if (!found) DPRINTF(hm, "Can not map unknown HID " "usage: %08x\n", usage); report_key: if (key == HIDMAP_KEY_NULL || key == hi->last_key) continue; if (hi->last_key != KEY_RESERVED) hidmap_push_key(hm, hi->last_key, 0); if (key != KEY_RESERVED) hidmap_push_key(hm, key, 1); hi->last_key = key; break; default: KASSERT(0, ("Unknown map type (%d)", hi->type)); } do_sync = true; } if (do_sync) { if (HIDMAP_WANT_MERGE_KEYS(hm)) hidmap_sync_keys(hm); evdev_sync(hm->evdev); } } static inline bool can_map_callback(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return (mi->has_cb && !mi->final_cb && hi->usage == mi->usage + usage_offset && (mi->relabs == HIDMAP_RELABS_ANY || !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); } static inline bool can_map_variable(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) != 0 && !mi->has_cb && hi->usage == mi->usage + usage_offset && (mi->relabs == HIDMAP_RELABS_ANY || !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); } static inline bool can_map_arr_range(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && hi->usage_minimum <= mi->usage + usage_offset && hi->usage_maximum >= mi->usage + usage_offset && mi->type == EV_KEY && (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); } static inline bool can_map_arr_list(struct hid_item *hi, const struct hidmap_item *mi, uint32_t usage, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && usage == mi->usage + usage_offset && mi->type == EV_KEY && (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); } static bool hidmap_probe_hid_item(struct hid_item *hi, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { u_int i, j; uint16_t uoff; bool found = false; #define HIDMAP_FOREACH_INDEX(map, nitems, idx, uoff) \ for ((idx) = 0, (uoff) = -1; \ hidmap_get_next_map_index((map), (nitems), &(idx), &(uoff));) HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_callback(hi, map + i, uoff)) { if (map[i].cb(NULL, NULL, (union hidmap_cb_ctx){.hi = hi}) != 0) break; setbit(caps, i); return (true); } } if (hi->flags & HIO_VARIABLE) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_variable(hi, map + i, uoff)) { KASSERT(map[i].type == EV_KEY || map[i].type == EV_REL || map[i].type == EV_ABS || map[i].type == EV_SW, ("Unsupported event type")); setbit(caps, i); return (true); } } return (false); } if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_arr_range(hi, map + i, uoff)) { setbit(caps, i); found = true; } } return (found); } for (j = 0; j < hi->nusages; j++) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_arr_list(hi, map+i, hi->usages[j], uoff)) { setbit(caps, i); found = true; } } } return (found); } static uint32_t hidmap_probe_hid_descr(void *d_ptr, hid_size_t d_len, uint8_t tlc_index, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { struct hid_data *hd; struct hid_item hi; uint32_t i, items = 0; bool do_free = false; if (caps == NULL) { caps = malloc(HIDMAP_CAPS_SZ(nitems_map), M_DEVBUF, M_WAITOK); do_free = true; } else bzero (caps, HIDMAP_CAPS_SZ(nitems_map)); /* Parse inputs */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { if (hi.kind != hid_input) continue; if (hi.flags & HIO_CONST) continue; for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) if (hidmap_probe_hid_item(&hi, map, nitems_map, caps)) items++; } hid_end_parse(hd); /* Take finalizing callbacks in to account */ for (i = 0; i < nitems_map; i++) { if (map[i].has_cb && map[i].final_cb && map[i].cb(NULL, NULL, (union hidmap_cb_ctx){}) == 0) { setbit(caps, i); items++; } } /* Check that all mandatory usages are present in report descriptor */ if (items != 0) { for (i = 0; i < nitems_map; i++) { if (map[i].required && isclr(caps, i)) { items = 0; break; } } } if (do_free) free(caps, M_DEVBUF); return (items); } uint32_t hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { void *d_ptr; uint32_t items; int i, error; hid_size_t d_len; uint8_t tlc_index = hidbus_get_index(hm->dev); /* Avoid double-adding of map in probe() handler */ for (i = 0; i < hm->nmaps; i++) if (hm->map[i] == map) return (0); error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); if (error != 0) { device_printf(hm->dev, "could not retrieve report descriptor " "from device: %d\n", error); return (error); } hm->cb_state = HIDMAP_CB_IS_PROBING; items = hidmap_probe_hid_descr(d_ptr, d_len, tlc_index, map, nitems_map, caps); if (items == 0) return (ENXIO); KASSERT(hm->nmaps < HIDMAP_MAX_MAPS, ("Not more than %d maps is supported", HIDMAP_MAX_MAPS)); hm->nhid_items += items; hm->map[hm->nmaps] = map; hm->nmap_items[hm->nmaps] = nitems_map; hm->nmaps++; return (0); } static bool hidmap_parse_hid_item(struct hidmap *hm, struct hid_item *hi, struct hidmap_hid_item *item) { const struct hidmap_item *mi; struct hidmap_hid_item hi_temp; uint32_t i; uint16_t uoff; bool found = false; HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_callback(hi, mi, uoff)) { bzero(&hi_temp, sizeof(hi_temp)); hi_temp.cb = mi->cb; hi_temp.type = HIDMAP_TYPE_CALLBACK; /* * Values returned by probe- and attach-stage * callbacks MUST be identical. */ if (mi->cb(hm, &hi_temp, (union hidmap_cb_ctx){.hi = hi}) != 0) break; bcopy(&hi_temp, item, sizeof(hi_temp)); goto mapped; } } if (hi->flags & HIO_VARIABLE) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_variable(hi, mi, uoff)) { item->evtype = mi->type; item->code = mi->code + uoff; item->type = hi->flags & HIO_NULLSTATE ? HIDMAP_TYPE_VAR_NULLST : HIDMAP_TYPE_VARIABLE; item->last_val = 0; item->invert_value = mi->invert_value; switch (mi->type) { case EV_KEY: hidmap_support_key(hm, item->code); break; case EV_REL: evdev_support_event(hm->evdev, EV_REL); evdev_support_rel(hm->evdev, item->code); break; case EV_ABS: evdev_support_event(hm->evdev, EV_ABS); evdev_support_abs(hm->evdev, item->code, hi->logical_minimum, hi->logical_maximum, mi->fuzz, mi->flat, hid_item_resolution(hi)); break; case EV_SW: evdev_support_event(hm->evdev, EV_SW); evdev_support_sw(hm->evdev, item->code); break; default: KASSERT(0, ("Unsupported event type")); } goto mapped; } } return (false); } if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_arr_range(hi, mi, uoff)) { hidmap_support_key(hm, mi->code + uoff); found = true; } } if (!found) return (false); item->umin = hi->usage_minimum; item->type = HIDMAP_TYPE_ARR_RANGE; item->last_key = KEY_RESERVED; goto mapped; } for (i = 0; i < hi->nusages; i++) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_arr_list(hi, mi, hi->usages[i], uoff)) { hidmap_support_key(hm, mi->code + uoff); if (item->codes == NULL) item->codes = malloc( hi->nusages * sizeof(uint16_t), M_DEVBUF, M_WAITOK | M_ZERO); item->codes[i] = mi->code + uoff; found = true; break; } } } if (!found) return (false); item->type = HIDMAP_TYPE_ARR_LIST; item->last_key = KEY_RESERVED; mapped: item->id = hi->report_ID; item->loc = hi->loc; item->loc.count = 1; item->lmin = hi->logical_minimum; item->lmax = hi->logical_maximum; DPRINTFN(hm, 6, "usage=%04x id=%d loc=%u/%u type=%d item=%*D\n", hi->usage, hi->report_ID, hi->loc.pos, hi->loc.size, item->type, (int)sizeof(item->cb), &item->cb, " "); return (true); } static int hidmap_parse_hid_descr(struct hidmap *hm, uint8_t tlc_index) { const struct hidmap_item *map; struct hidmap_hid_item *item = hm->hid_items; void *d_ptr; struct hid_data *hd; struct hid_item hi; int i, error; hid_size_t d_len; error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); if (error != 0) { DPRINTF(hm, "could not retrieve report descriptor from " "device: %d\n", error); return (error); } /* Parse inputs */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { if (hi.kind != hid_input) continue; if (hi.flags & HIO_CONST) continue; for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) if (hidmap_parse_hid_item(hm, &hi, item)) item++; KASSERT(item <= hm->hid_items + hm->nhid_items, ("Parsed HID item array overflow")); } hid_end_parse(hd); /* Add finalizing callbacks to the end of list */ for (i = 0; i < hm->nmaps; i++) { for (map = hm->map[i]; map < hm->map[i] + hm->nmap_items[i]; map++) { if (map->has_cb && map->final_cb && map->cb(hm, item, (union hidmap_cb_ctx){}) == 0) { item->cb = map->cb; item->type = HIDMAP_TYPE_FINALCB; item++; } } } /* * Resulting number of parsed HID items can be less than expected as * map items might be duplicated in different maps. Save real number. */ if (hm->nhid_items != item - hm->hid_items) DPRINTF(hm, "Parsed HID item number mismatch: expected=%u " "result=%td\n", hm->nhid_items, item - hm->hid_items); hm->nhid_items = item - hm->hid_items; if (HIDMAP_WANT_MERGE_KEYS(hm)) bzero(hm->key_press, howmany(KEY_CNT, 8)); return (0); } int hidmap_probe(struct hidmap* hm, device_t dev, const struct hid_device_id *id, int nitems_id, const struct hidmap_item *map, int nitems_map, const char *suffix, hidmap_caps_t caps) { int error; error = hidbus_lookup_driver_info(dev, id, nitems_id); if (error != 0) return (error); hidmap_set_dev(hm, dev); error = hidmap_add_map(hm, map, nitems_map, caps); if (error != 0) return (error); hidbus_set_desc(dev, suffix); return (BUS_PROBE_DEFAULT); } int hidmap_attach(struct hidmap* hm) { const struct hid_device_info *hw = hid_get_device_info(hm->dev); #ifdef HID_DEBUG char tunable[40]; #endif int error; #ifdef HID_DEBUG if (hm->debug_var == NULL) { hm->debug_var = &hm->debug_level; snprintf(tunable, sizeof(tunable), "hw.hid.%s.debug", device_get_name(hm->dev)); TUNABLE_INT_FETCH(tunable, &hm->debug_level); SYSCTL_ADD_INT(device_get_sysctl_ctx(hm->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(hm->dev)), - OID_AUTO, "debug", CTLTYPE_INT | CTLFLAG_RWTUN, + OID_AUTO, "debug", CTLFLAG_RWTUN, &hm->debug_level, 0, "Verbosity level"); } #endif DPRINTFN(hm, 11, "hm=%p\n", hm); hm->cb_state = HIDMAP_CB_IS_ATTACHING; hm->hid_items = malloc(hm->nhid_items * sizeof(struct hid_item), M_DEVBUF, M_WAITOK | M_ZERO); hidbus_set_intr(hm->dev, hidmap_intr, hm); hm->evdev_methods = (struct evdev_methods) { .ev_open = &hidmap_ev_open, .ev_close = &hidmap_ev_close, }; hm->evdev = evdev_alloc(); evdev_set_name(hm->evdev, device_get_desc(hm->dev)); evdev_set_phys(hm->evdev, device_get_nameunit(hm->dev)); evdev_set_id(hm->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(hm->evdev, hw->serial); evdev_set_flag(hm->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(hm->evdev, EV_SYN); error = hidmap_parse_hid_descr(hm, hidbus_get_index(hm->dev)); if (error) { DPRINTF(hm, "error=%d\n", error); hidmap_detach(hm); return (ENXIO); } evdev_set_methods(hm->evdev, hm->dev, &hm->evdev_methods); hm->cb_state = HIDMAP_CB_IS_RUNNING; error = evdev_register(hm->evdev); if (error) { DPRINTF(hm, "error=%d\n", error); hidmap_detach(hm); return (ENXIO); } return (0); } int hidmap_detach(struct hidmap* hm) { struct hidmap_hid_item *hi; DPRINTFN(hm, 11, "\n"); hm->cb_state = HIDMAP_CB_IS_DETACHING; evdev_free(hm->evdev); if (hm->hid_items != NULL) { for (hi = hm->hid_items; hi < hm->hid_items + hm->nhid_items; hi++) if (hi->type == HIDMAP_TYPE_FINALCB || hi->type == HIDMAP_TYPE_CALLBACK) hi->cb(hm, hi, (union hidmap_cb_ctx){}); else if (hi->type == HIDMAP_TYPE_ARR_LIST) free(hi->codes, M_DEVBUF); free(hm->hid_items, M_DEVBUF); } free(hm->key_press, M_DEVBUF); free(hm->key_rel, M_DEVBUF); return (0); } MODULE_DEPEND(hidmap, hid, 1, 1, 1); MODULE_DEPEND(hidmap, hidbus, 1, 1, 1); MODULE_DEPEND(hidmap, evdev, 1, 1, 1); MODULE_VERSION(hidmap, 1); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c index c4bc2d3cee1f..fe949f113984 100644 --- a/sys/dev/iicbus/iichid.c +++ b/sys/dev/iicbus/iichid.c @@ -1,1252 +1,1252 @@ /*- * 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 __FBSDID("$FreeBSD$"); #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 "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 /* * 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 60 #define IICHID_SAMPLING_RATE_SLOW 10 #define IICHID_SAMPLING_HYSTERESIS 1 /* 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; static char *iichid_ids[] = { "PNP0C50", "ACPI0C50", NULL }; 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; 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 */ struct timeout_task periodic_task; /* iicbus lock */ bool callout_setup; /* iicbus lock */ struct taskqueue *taskqueue; struct task event_task; #endif bool open; /* iicbus lock */ bool suspend; /* iicbus lock */ bool power_on; /* iicbus lock */ }; 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; #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 bool acpi_is_iichid(ACPI_HANDLE handle) { char **ids; UINT32 sta; for (ids = iichid_ids; *ids != NULL; ids++) { if (acpi_MatchHid(handle, *ids)) break; } if (*ids == NULL) return (false); /* * 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 (true); return (false); } 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. */ uint8_t actbuf[2] = { 0, 0 }; /* Read actual input report length. */ struct iic_msg msgs[] = { { sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf }, }; uint16_t actlen; int error; error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); if (error != 0) return (error); actlen = actbuf[0] | actbuf[1] << 8; if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) { /* Read and discard 1 byte to send I2C STOP condition. */ msgs[0] = (struct iic_msg) { sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf }; actlen = 0; } else { actlen -= 2; if (actlen > maxlen) { DPRINTF(sc, "input report too big. requested=%d " "received=%d\n", maxlen, actlen); actlen = maxlen; } /* Read input report itself. */ msgs[0] = (struct iic_msg) { sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf }; } error = iicbus_transfer(sc->dev, msgs, 1); if (error == 0 && actual_len != NULL) *actual_len = actlen; DPRINTFN(sc, 5, "%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " "); 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_event_task(void *context, int pending) { struct iichid_softc *sc; device_t parent; iichid_size_t actual; bool bus_requested; int error; 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, actual); sc->missing_samples = 0; } else ++sc->missing_samples; } else DPRINTF(sc, "read error occured: %d\n", error); rearm: if (sc->callout_setup && sc->sampling_rate_slow > 0) { if (sc->missing_samples == sc->sampling_hysteresis) sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0); taskqueue_enqueue_timeout(sc->taskqueue, &sc->periodic_task, hz / MAX(sc->missing_samples >= sc->sampling_hysteresis ? sc->sampling_rate_slow : sc->sampling_rate_fast, 1)); } 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 maxlen, 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 * acknoledge interrupts we fetch only length header and discard it. */ maxlen = sc->power_on ? sc->intr_bufsize : 0; error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual); if (error == 0) { if (sc->power_on) { if (actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); else DPRINTF(sc, "no data received\n"); } } else DPRINTF(sc, "read error occured: %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; taskqueue_enqueue(sc->taskqueue, &sc->event_task); return (0); } static void iichid_teardown_callout(struct iichid_softc *sc) { sc->callout_setup = false; taskqueue_cancel_timeout(sc->taskqueue, &sc->periodic_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, hid_intr_t intr, void *context, struct hid_rdesc_info *rdesc) { struct iichid_softc *sc; sc = device_get_softc(dev); /* * Do not rely on wMaxInputLength, as some devices may set it to * a wrong length. Find the longest input report in report descriptor. */ rdesc->rdsize = rdesc->isize; /* Write and get/set_report sizes are limited by I2C-HID protocol. */ rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX; rdesc->wrsize = IICHID_SIZE_MAX; sc->intr_handler = intr; sc->intr_ctx = context; sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO); sc->intr_bufsize = rdesc->rdsize; #ifdef IICHID_SAMPLING taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, "%s taskq", device_get_nameunit(sc->dev)); #endif } static void iichid_intr_unsetup(device_t dev) { struct iichid_softc *sc; sc = device_get_softc(dev); #ifdef IICHID_SAMPLING taskqueue_drain_all(sc->taskqueue); #endif free(sc->intr_buf, M_DEVBUF); } static int iichid_intr_start(device_t dev) { 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); return (0); } static int iichid_intr_stop(device_t dev) { 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) { 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, actual); } /* * HID interface */ static int iichid_get_rdesc(device_t dev, 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, void *buf, hid_size_t maxlen, hid_size_t *actlen) { struct iichid_softc *sc; device_t parent; 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) { error = iichid_cmd_read(sc, buf, maxlen, actlen); iicbus_release_bus(parent, sc->dev); } return (iic2errno(error)); } static int iichid_write(device_t dev, 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, 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, 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, uint16_t duration, uint8_t id) { return (ENOTSUP); } static int iichid_set_protocol(device_t dev, uint16_t protocol) { return (ENOTSUP); } 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; char buf[80]; uint16_t config_reg; int error; 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); if (!acpi_is_iichid(handle)) return (ENXIO); if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) return (ENXIO); 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) { snprintf(buf, sizeof(buf), "%s I2C HID device", sc->hw.name); device_set_desc_copy(dev, buf); } 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); } /* * 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); return (ENXIO); } sc->power_on = false; #ifdef IICHID_SAMPLING TASK_INIT(&sc->event_task, 0, iichid_event_task, sc); /* taskqueue_create can't fail with M_WAITOK mflag passed. */ sc->taskqueue = taskqueue_create("hmt_tq", M_WAITOK | M_ZERO, taskqueue_thread_enqueue, &sc->taskqueue); TIMEOUT_TASK_INIT(sc->taskqueue, &sc->periodic_task, 0, iichid_event_task, sc); sc->sampling_rate_slow = -1; sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST; sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS; #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, "Interrupt setup failed. Fallback to sampling\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); 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", CTLTYPE_INT | CTLFLAG_RWTUN, + 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", CTLTYPE_INT | CTLFLAG_RWTUN, + 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 */ child = device_add_child(dev, "hidbus", -1); 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); error = bus_generic_attach(dev); if (error) { device_printf(dev, "failed to attach child: error %d\n", error); iichid_detach(dev); } done: (void)iichid_set_power(sc, I2C_HID_POWER_OFF); return (error); } static int iichid_detach(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); error = device_delete_children(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; #endif return (0); } static int iichid_suspend(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); DPRINTF(sc, "Suspend called, setting device to power_state 1\n"); (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. */ 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"); return (0); } static int iichid_resume(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); 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 devclass_t iichid_devclass; 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_END }; static driver_t iichid_driver = { .name = "iichid", .methods = iichid_methods, .size = sizeof(struct iichid_softc), }; DRIVER_MODULE(iichid, iicbus, iichid_driver, iichid_devclass, NULL, 0); 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);