Changeset View
Changeset View
Standalone View
Standalone View
sys/dev/hid/hidmap.c
- This file was added.
/*- | |||||
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD | |||||
* | |||||
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org> | |||||
* | |||||
* Redistribution and use in source and binary forms, with or without | |||||
* modification, are permitted provided that the following conditions | |||||
* are met: | |||||
* 1. Redistributions of source code must retain the above copyright | |||||
* notice, this list of conditions and the following disclaimer. | |||||
* 2. Redistributions in binary form must reproduce the above copyright | |||||
* notice, this list of conditions and the following disclaimer in the | |||||
* documentation and/or other materials provided with the distribution. | |||||
* | |||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND | |||||
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE | |||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |||||
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |||||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |||||
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |||||
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |||||
* SUCH DAMAGE. | |||||
*/ | |||||
#include <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
/* | |||||
* Abstract 1 to 1 HID input usage to evdev event mapper driver. | |||||
*/ | |||||
#include "opt_hid.h" | |||||
#include <sys/param.h> | |||||
#include <sys/bus.h> | |||||
#include <sys/kernel.h> | |||||
#include <sys/lock.h> | |||||
#include <sys/malloc.h> | |||||
#include <sys/module.h> | |||||
#include <sys/sysctl.h> | |||||
#include <sys/systm.h> | |||||
#include <dev/evdev/input.h> | |||||
#include <dev/evdev/evdev.h> | |||||
#include <dev/hid/hid.h> | |||||
#include <dev/hid/hidbus.h> | |||||
#include <dev/hid/hidmap.h> | |||||
#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, | |||||
&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); |