HardenedBSD/sys/dev/hid/hidmap.c
Warner Losh 685dc743dc sys: Remove $FreeBSD$: one-line .c pattern
Remove /^[\s*]*__FBSDID\("\$FreeBSD\$"\);?\s*\n/
2023-08-16 11:54:36 -06:00

835 lines
21 KiB
C
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*-
* SPDX-License-Identifier: BSD-2-Clause
*
* 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>
/*
* 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 (hid_intr_stop(evdev_get_softc(evdev)));
}
static int
hidmap_ev_open(struct evdev_dev *evdev)
{
return (hid_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 2s 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 | M_ZERO);
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++) {
KASSERT(!(map[i].required && map[i].forbidden),
("both required & forbidden item flags are set"));
if ((map[i].required && isclr(caps, i)) ||
(map[i].forbidden && isset(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", 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);