diff --git a/sys/dev/hid/hid.c b/sys/dev/hid/hid.c index cd7fb6bf8778..7fa6e34be22a 100644 --- a/sys/dev/hid/hid.c +++ b/sys/dev/hid/hid.c @@ -1,1082 +1,1083 @@ /* $FreeBSD$ */ /* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 "opt_hid.h" #include #include #include #include #include #include #include #define HID_DEBUG_VAR hid_debug #include #include #include "hid_if.h" /* * Define this unconditionally in case a kernel module is loaded that * has been compiled with debugging options. */ int hid_debug = 0; SYSCTL_NODE(_hw, OID_AUTO, hid, CTLFLAG_RW, 0, "HID debugging"); SYSCTL_INT(_hw_hid, OID_AUTO, debug, CTLFLAG_RWTUN, &hid_debug, 0, "Debug level"); static void hid_clear_local(struct hid_item *); static uint8_t hid_get_byte(struct hid_data *s, const uint16_t wSize); static hid_test_quirk_t hid_test_quirk_w; hid_test_quirk_t *hid_test_quirk_p = &hid_test_quirk_w; #define MAXUSAGE 64 #define MAXPUSH 4 #define MAXID 16 #define MAXLOCCNT 2048 struct hid_pos_data { int32_t rid; uint32_t pos; }; struct hid_data { const uint8_t *start; const uint8_t *end; const uint8_t *p; struct hid_item cur[MAXPUSH]; struct hid_pos_data last_pos[MAXID]; int32_t usages_min[MAXUSAGE]; int32_t usages_max[MAXUSAGE]; int32_t usage_last; /* last seen usage */ uint32_t loc_size; /* last seen size */ uint32_t loc_count; /* last seen count */ uint32_t ncount; /* end usage item count */ uint32_t icount; /* current usage item count */ uint8_t kindset; /* we have 5 kinds so 8 bits are enough */ uint8_t pushlevel; /* current pushlevel */ uint8_t nusage; /* end "usages_min/max" index */ uint8_t iusage; /* current "usages_min/max" index */ uint8_t ousage; /* current "usages_min/max" offset */ uint8_t susage; /* usage set flags */ }; /*------------------------------------------------------------------------* * hid_clear_local *------------------------------------------------------------------------*/ static void hid_clear_local(struct hid_item *c) { c->loc.count = 0; c->loc.size = 0; c->nusages = 0; memset(c->usages, 0, sizeof(c->usages)); c->usage_minimum = 0; c->usage_maximum = 0; c->designator_index = 0; c->designator_minimum = 0; c->designator_maximum = 0; c->string_index = 0; c->string_minimum = 0; c->string_maximum = 0; c->set_delimiter = 0; } static void hid_switch_rid(struct hid_data *s, struct hid_item *c, int32_t next_rID) { uint8_t i; /* check for same report ID - optimise */ if (c->report_ID == next_rID) return; /* save current position for current rID */ if (c->report_ID == 0) { i = 0; } else { for (i = 1; i != MAXID; i++) { if (s->last_pos[i].rid == c->report_ID) break; if (s->last_pos[i].rid == 0) break; } } if (i != MAXID) { s->last_pos[i].rid = c->report_ID; s->last_pos[i].pos = c->loc.pos; } /* store next report ID */ c->report_ID = next_rID; /* lookup last position for next rID */ if (next_rID == 0) { i = 0; } else { for (i = 1; i != MAXID; i++) { if (s->last_pos[i].rid == next_rID) break; if (s->last_pos[i].rid == 0) break; } } if (i != MAXID) { s->last_pos[i].rid = next_rID; c->loc.pos = s->last_pos[i].pos; } else { DPRINTF("Out of RID entries, position is set to zero!\n"); c->loc.pos = 0; } } /*------------------------------------------------------------------------* * hid_start_parse *------------------------------------------------------------------------*/ struct hid_data * hid_start_parse(const void *d, hid_size_t len, int kindset) { struct hid_data *s; if ((kindset-1) & kindset) { DPRINTFN(0, "Only one bit can be " "set in the kindset\n"); return (NULL); } s = malloc(sizeof *s, M_TEMP, M_WAITOK | M_ZERO); s->start = s->p = d; s->end = ((const uint8_t *)d) + len; s->kindset = kindset; return (s); } /*------------------------------------------------------------------------* * hid_end_parse *------------------------------------------------------------------------*/ void hid_end_parse(struct hid_data *s) { if (s == NULL) return; free(s, M_TEMP); } /*------------------------------------------------------------------------* * get byte from HID descriptor *------------------------------------------------------------------------*/ static uint8_t hid_get_byte(struct hid_data *s, const uint16_t wSize) { const uint8_t *ptr; uint8_t retval; ptr = s->p; /* check if end is reached */ if (ptr == s->end) return (0); /* read out a byte */ retval = *ptr; /* check if data pointer can be advanced by "wSize" bytes */ if ((s->end - ptr) < wSize) ptr = s->end; else ptr += wSize; /* update pointer */ s->p = ptr; return (retval); } /*------------------------------------------------------------------------* * hid_get_item *------------------------------------------------------------------------*/ int hid_get_item(struct hid_data *s, struct hid_item *h) { struct hid_item *c; unsigned int bTag, bType, bSize; uint32_t oldpos; int32_t mask; int32_t dval; if (s == NULL) return (0); c = &s->cur[s->pushlevel]; top: /* check if there is an array of items */ if (s->icount < s->ncount) { /* get current usage */ if (s->iusage < s->nusage) { dval = s->usages_min[s->iusage] + s->ousage; c->usage = dval; s->usage_last = dval; if (dval == s->usages_max[s->iusage]) { s->iusage ++; s->ousage = 0; } else { s->ousage ++; } } else { DPRINTFN(1, "Using last usage\n"); dval = s->usage_last; } c->nusages = 1; /* array type HID item may have multiple usages */ while ((c->flags & HIO_VARIABLE) == 0 && s->ousage == 0 && s->iusage < s->nusage && c->nusages < HID_ITEM_MAXUSAGE) c->usages[c->nusages++] = s->usages_min[s->iusage++]; if ((c->flags & HIO_VARIABLE) == 0 && s->ousage == 0 && s->iusage < s->nusage) DPRINTFN(0, "HID_ITEM_MAXUSAGE should be increased " "up to %hhu to parse the HID report descriptor\n", s->nusage); s->icount ++; /* * Only copy HID item, increment position and return * if correct kindset! */ if (s->kindset & (1 << c->kind)) { *h = *c; DPRINTFN(1, "%u,%u,%u\n", h->loc.pos, h->loc.size, h->loc.count); c->loc.pos += c->loc.size * c->loc.count; return (1); } } /* reset state variables */ s->icount = 0; s->ncount = 0; s->iusage = 0; s->nusage = 0; s->susage = 0; s->ousage = 0; hid_clear_local(c); /* get next item */ while (s->p != s->end) { bSize = hid_get_byte(s, 1); if (bSize == 0xfe) { /* long item */ bSize = hid_get_byte(s, 1); bSize |= hid_get_byte(s, 1) << 8; bTag = hid_get_byte(s, 1); bType = 0xff; /* XXX what should it be */ } else { /* short item */ bTag = bSize >> 4; bType = (bSize >> 2) & 3; bSize &= 3; if (bSize == 3) bSize = 4; } switch (bSize) { case 0: dval = 0; mask = 0; break; case 1: dval = (int8_t)hid_get_byte(s, 1); mask = 0xFF; break; case 2: dval = hid_get_byte(s, 1); dval |= hid_get_byte(s, 1) << 8; dval = (int16_t)dval; mask = 0xFFFF; break; case 4: dval = hid_get_byte(s, 1); dval |= hid_get_byte(s, 1) << 8; dval |= hid_get_byte(s, 1) << 16; dval |= hid_get_byte(s, 1) << 24; mask = 0xFFFFFFFF; break; default: dval = hid_get_byte(s, bSize); DPRINTFN(0, "bad length %u (data=0x%02x)\n", bSize, dval); continue; } switch (bType) { case 0: /* Main */ switch (bTag) { case 8: /* Input */ c->kind = hid_input; ret: c->flags = dval; c->loc.count = s->loc_count; c->loc.size = s->loc_size; if (c->flags & HIO_VARIABLE) { /* range check usage count */ if (c->loc.count > MAXLOCCNT) { DPRINTFN(0, "Number of " "items(%u) truncated to %u\n", (unsigned)(c->loc.count), MAXLOCCNT); s->ncount = MAXLOCCNT; } else s->ncount = c->loc.count; /* * The "top" loop will return * one and one item: */ c->loc.count = 1; } else { s->ncount = 1; } goto top; case 9: /* Output */ c->kind = hid_output; goto ret; case 10: /* Collection */ c->kind = hid_collection; c->collection = dval; c->collevel++; c->usage = s->usage_last; c->nusages = 1; *h = *c; return (1); case 11: /* Feature */ c->kind = hid_feature; goto ret; case 12: /* End collection */ c->kind = hid_endcollection; if (c->collevel == 0) { DPRINTFN(0, "invalid end collection\n"); return (0); } c->collevel--; *h = *c; return (1); default: DPRINTFN(0, "Main bTag=%d\n", bTag); break; } break; case 1: /* Global */ switch (bTag) { case 0: c->_usage_page = dval << 16; break; case 1: c->logical_minimum = dval; break; case 2: c->logical_maximum = dval; break; case 3: c->physical_minimum = dval; break; case 4: c->physical_maximum = dval; break; case 5: c->unit_exponent = dval; break; case 6: c->unit = dval; break; case 7: /* mask because value is unsigned */ s->loc_size = dval & mask; break; case 8: hid_switch_rid(s, c, dval & mask); break; case 9: /* mask because value is unsigned */ s->loc_count = dval & mask; break; case 10: /* Push */ /* stop parsing, if invalid push level */ if ((s->pushlevel + 1) >= MAXPUSH) { DPRINTFN(0, "Cannot push item @ %d\n", s->pushlevel); return (0); } s->pushlevel ++; s->cur[s->pushlevel] = *c; /* store size and count */ c->loc.size = s->loc_size; c->loc.count = s->loc_count; /* update current item pointer */ c = &s->cur[s->pushlevel]; break; case 11: /* Pop */ /* stop parsing, if invalid push level */ if (s->pushlevel == 0) { DPRINTFN(0, "Cannot pop item @ 0\n"); return (0); } s->pushlevel --; /* preserve position */ oldpos = c->loc.pos; c = &s->cur[s->pushlevel]; /* restore size and count */ s->loc_size = c->loc.size; s->loc_count = c->loc.count; /* set default item location */ c->loc.pos = oldpos; c->loc.size = 0; c->loc.count = 0; break; default: DPRINTFN(0, "Global bTag=%d\n", bTag); break; } break; case 2: /* Local */ switch (bTag) { case 0: if (bSize != 4) dval = (dval & mask) | c->_usage_page; /* set last usage, in case of a collection */ s->usage_last = dval; if (s->nusage < MAXUSAGE) { s->usages_min[s->nusage] = dval; s->usages_max[s->nusage] = dval; s->nusage ++; } else { DPRINTFN(0, "max usage reached\n"); } /* clear any pending usage sets */ s->susage = 0; break; case 1: s->susage |= 1; if (bSize != 4) dval = (dval & mask) | c->_usage_page; c->usage_minimum = dval; goto check_set; case 2: s->susage |= 2; if (bSize != 4) dval = (dval & mask) | c->_usage_page; c->usage_maximum = dval; check_set: if (s->susage != 3) break; /* sanity check */ if ((s->nusage < MAXUSAGE) && (c->usage_minimum <= c->usage_maximum)) { /* add usage range */ s->usages_min[s->nusage] = c->usage_minimum; s->usages_max[s->nusage] = c->usage_maximum; s->nusage ++; } else { DPRINTFN(0, "Usage set dropped\n"); } s->susage = 0; break; case 3: c->designator_index = dval; break; case 4: c->designator_minimum = dval; break; case 5: c->designator_maximum = dval; break; case 7: c->string_index = dval; break; case 8: c->string_minimum = dval; break; case 9: c->string_maximum = dval; break; case 10: c->set_delimiter = dval; break; default: DPRINTFN(0, "Local bTag=%d\n", bTag); break; } break; default: DPRINTFN(0, "default bType=%d\n", bType); break; } } return (0); } /*------------------------------------------------------------------------* * hid_report_size *------------------------------------------------------------------------*/ int hid_report_size(const void *buf, hid_size_t len, enum hid_kind k, uint8_t id) { struct hid_data *d; struct hid_item h; uint32_t temp; uint32_t hpos; uint32_t lpos; int report_id = 0; hpos = 0; lpos = 0xFFFFFFFF; for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) { if (h.kind == k && h.report_ID == id) { /* compute minimum */ if (lpos > h.loc.pos) lpos = h.loc.pos; /* compute end position */ temp = h.loc.pos + (h.loc.size * h.loc.count); /* compute maximum */ if (hpos < temp) hpos = temp; if (h.report_ID != 0) report_id = 1; } } hid_end_parse(d); /* safety check - can happen in case of currupt descriptors */ if (lpos > hpos) temp = 0; else temp = hpos - lpos; /* return length in bytes rounded up */ return ((temp + 7) / 8 + report_id); } int hid_report_size_max(const void *buf, hid_size_t len, enum hid_kind k, uint8_t *id) { struct hid_data *d; struct hid_item h; uint32_t temp; uint32_t hpos; uint32_t lpos; uint8_t any_id; any_id = 0; hpos = 0; lpos = 0xFFFFFFFF; for (d = hid_start_parse(buf, len, 1 << k); hid_get_item(d, &h);) { if (h.kind == k) { /* check for ID-byte presence */ if ((h.report_ID != 0) && !any_id) { if (id != NULL) *id = h.report_ID; any_id = 1; } /* compute minimum */ if (lpos > h.loc.pos) lpos = h.loc.pos; /* compute end position */ temp = h.loc.pos + (h.loc.size * h.loc.count); /* compute maximum */ if (hpos < temp) hpos = temp; } } hid_end_parse(d); /* safety check - can happen in case of currupt descriptors */ if (lpos > hpos) temp = 0; else temp = hpos - lpos; /* check for ID byte */ if (any_id) temp += 8; else if (id != NULL) *id = 0; /* return length in bytes rounded up */ return ((temp + 7) / 8); } /*------------------------------------------------------------------------* * hid_locate *------------------------------------------------------------------------*/ int hid_locate(const void *desc, hid_size_t size, int32_t u, enum hid_kind k, uint8_t index, struct hid_location *loc, uint32_t *flags, uint8_t *id) { struct hid_data *d; struct hid_item h; int i; for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { for (i = 0; i < h.nusages; i++) { if (h.kind == k && h.usages[i] == u) { if (index--) break; if (loc != NULL) *loc = h.loc; if (flags != NULL) *flags = h.flags; if (id != NULL) *id = h.report_ID; hid_end_parse(d); return (1); } } } if (loc != NULL) loc->size = 0; if (flags != NULL) *flags = 0; if (id != NULL) *id = 0; hid_end_parse(d); return (0); } /*------------------------------------------------------------------------* * hid_get_data *------------------------------------------------------------------------*/ static uint32_t hid_get_data_sub(const uint8_t *buf, hid_size_t len, struct hid_location *loc, int is_signed) { uint32_t hpos = loc->pos; uint32_t hsize = loc->size; uint32_t data; uint32_t rpos; uint8_t n; DPRINTFN(11, "hid_get_data: loc %d/%d\n", hpos, hsize); /* Range check and limit */ if (hsize == 0) return (0); if (hsize > 32) hsize = 32; /* Get data in a safe way */ data = 0; rpos = (hpos / 8); n = (hsize + 7) / 8; rpos += n; while (n--) { rpos--; if (rpos < len) data |= buf[rpos] << (8 * n); } /* Correctly shift down data */ data = (data >> (hpos % 8)); n = 32 - hsize; /* Mask and sign extend in one */ if (is_signed != 0) data = (int32_t)((int32_t)data << n) >> n; else data = (uint32_t)((uint32_t)data << n) >> n; DPRINTFN(11, "hid_get_data: loc %d/%d = %lu\n", loc->pos, loc->size, (long)data); return (data); } int32_t hid_get_data(const uint8_t *buf, hid_size_t len, struct hid_location *loc) { return (hid_get_data_sub(buf, len, loc, 1)); } uint32_t hid_get_udata(const uint8_t *buf, hid_size_t len, struct hid_location *loc) { return (hid_get_data_sub(buf, len, loc, 0)); } /*------------------------------------------------------------------------* * hid_put_data *------------------------------------------------------------------------*/ void hid_put_udata(uint8_t *buf, hid_size_t len, struct hid_location *loc, unsigned int value) { uint32_t hpos = loc->pos; uint32_t hsize = loc->size; uint64_t data; uint64_t mask; uint32_t rpos; uint8_t n; DPRINTFN(11, "hid_put_data: loc %d/%d = %u\n", hpos, hsize, value); /* Range check and limit */ if (hsize == 0) return; if (hsize > 32) hsize = 32; /* Put data in a safe way */ rpos = (hpos / 8); n = (hsize + 7) / 8; data = ((uint64_t)value) << (hpos % 8); mask = ((1ULL << hsize) - 1ULL) << (hpos % 8); rpos += n; while (n--) { rpos--; if (rpos < len) { buf[rpos] &= ~(mask >> (8 * n)); buf[rpos] |= (data >> (8 * n)); } } } /*------------------------------------------------------------------------* * hid_is_collection *------------------------------------------------------------------------*/ int hid_is_collection(const void *desc, hid_size_t size, int32_t usage) { struct hid_data *hd; struct hid_item hi; int err; hd = hid_start_parse(desc, size, 0); if (hd == NULL) return (0); while ((err = hid_get_item(hd, &hi))) { if (hi.kind == hid_collection && hi.usage == usage) break; } hid_end_parse(hd); return (err); } /*------------------------------------------------------------------------* * calculate HID item resolution. unit/mm for distances, unit/rad for angles *------------------------------------------------------------------------*/ int32_t hid_item_resolution(struct hid_item *hi) { /* * hid unit scaling table according to HID Usage Table Review * Request 39 Tbl 17 http://www.usb.org/developers/hidpage/HUTRR39b.pdf */ static const int64_t scale[0x10][2] = { [0x00] = { 1, 1 }, [0x01] = { 1, 10 }, [0x02] = { 1, 100 }, [0x03] = { 1, 1000 }, [0x04] = { 1, 10000 }, [0x05] = { 1, 100000 }, [0x06] = { 1, 1000000 }, [0x07] = { 1, 10000000 }, [0x08] = { 100000000, 1 }, [0x09] = { 10000000, 1 }, [0x0A] = { 1000000, 1 }, [0x0B] = { 100000, 1 }, [0x0C] = { 10000, 1 }, [0x0D] = { 1000, 1 }, [0x0E] = { 100, 1 }, [0x0F] = { 10, 1 }, }; int64_t logical_size; int64_t physical_size; int64_t multiplier; int64_t divisor; int64_t resolution; switch (hi->unit) { case HUM_CENTIMETER: multiplier = 1; divisor = 10; break; case HUM_INCH: case HUM_INCH_EGALAX: multiplier = 10; divisor = 254; break; case HUM_RADIAN: multiplier = 1; divisor = 1; break; case HUM_DEGREE: multiplier = 573; divisor = 10; break; default: return (0); } if ((hi->logical_maximum <= hi->logical_minimum) || (hi->physical_maximum <= hi->physical_minimum) || (hi->unit_exponent < 0) || (hi->unit_exponent >= nitems(scale))) return (0); logical_size = (int64_t)hi->logical_maximum - (int64_t)hi->logical_minimum; physical_size = (int64_t)hi->physical_maximum - (int64_t)hi->physical_minimum; /* Round to ceiling */ resolution = logical_size * multiplier * scale[hi->unit_exponent][0] / (physical_size * divisor * scale[hi->unit_exponent][1]); if (resolution > INT32_MAX) return (0); return (resolution); } /*------------------------------------------------------------------------* * hid_is_mouse * * This function will decide if a USB descriptor belongs to a USB mouse. * * Return values: * Zero: Not a USB mouse. * Else: Is a USB mouse. *------------------------------------------------------------------------*/ int hid_is_mouse(const void *d_ptr, uint16_t d_len) { struct hid_data *hd; struct hid_item hi; int mdepth; int found; hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); if (hd == NULL) return (0); mdepth = 0; found = 0; while (hid_get_item(hd, &hi)) { switch (hi.kind) { case hid_collection: if (mdepth != 0) mdepth++; else if (hi.collection == 1 && hi.usage == HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) mdepth++; break; case hid_endcollection: if (mdepth != 0) mdepth--; break; case hid_input: if (mdepth == 0) break; if (hi.usage == HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) && (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) found++; if (hi.usage == HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) && (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) found++; break; default: break; } } hid_end_parse(hd); return (found); } /*------------------------------------------------------------------------* * hid_is_keyboard * * This function will decide if a USB descriptor belongs to a USB keyboard. * * Return values: * Zero: Not a USB keyboard. * Else: Is a USB keyboard. *------------------------------------------------------------------------*/ int hid_is_keyboard(const void *d_ptr, uint16_t d_len) { if (hid_is_collection(d_ptr, d_len, HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD))) return (1); return (0); } /*------------------------------------------------------------------------* * hid_test_quirk - test a device for a given quirk * * Return values: * false: The HID device does not have the given quirk. * true: The HID device has the given quirk. *------------------------------------------------------------------------*/ bool hid_test_quirk(const struct hid_device_info *dev_info, uint16_t quirk) { bool found; uint8_t x; if (quirk == HQ_NONE) return (false); /* search the automatic per device quirks first */ for (x = 0; x != HID_MAX_AUTO_QUIRK; x++) { if (dev_info->autoQuirk[x] == quirk) return (true); } /* search global quirk table, if any */ found = (hid_test_quirk_p) (dev_info, quirk); return (found); } static bool hid_test_quirk_w(const struct hid_device_info *dev_info, uint16_t quirk) { return (false); /* no match */ } int hid_add_dynamic_quirk(struct hid_device_info *dev_info, uint16_t quirk) { uint8_t x; for (x = 0; x != HID_MAX_AUTO_QUIRK; x++) { if (dev_info->autoQuirk[x] == 0 || dev_info->autoQuirk[x] == quirk) { dev_info->autoQuirk[x] = quirk; return (0); /* success */ } } return (ENOSPC); } void hid_quirk_unload(void *arg) { /* reset function pointer */ hid_test_quirk_p = &hid_test_quirk_w; #ifdef NOT_YET hidquirk_ioctl_p = &hidquirk_ioctl_w; #endif /* wait for CPU to exit the loaded functions, if any */ /* XXX this is a tradeoff */ pause("WAIT", hz); } int hid_get_rdesc(device_t dev, void *data, hid_size_t len) { - return (HID_GET_RDESC(device_get_parent(dev), data, len)); + return (HID_GET_RDESC(device_get_parent(dev), dev, data, len)); } int hid_read(device_t dev, void *data, hid_size_t maxlen, hid_size_t *actlen) { - return (HID_READ(device_get_parent(dev), data, maxlen, actlen)); + return (HID_READ(device_get_parent(dev), dev, data, maxlen, actlen)); } int hid_write(device_t dev, const void *data, hid_size_t len) { - return (HID_WRITE(device_get_parent(dev), data, len)); + return (HID_WRITE(device_get_parent(dev), dev, data, len)); } int hid_get_report(device_t dev, void *data, hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { - return (HID_GET_REPORT(device_get_parent(dev), data, maxlen, actlen, - type, id)); + return (HID_GET_REPORT(device_get_parent(dev), dev, data, maxlen, + actlen, type, id)); } int hid_set_report(device_t dev, const void *data, hid_size_t len, uint8_t type, uint8_t id) { - return (HID_SET_REPORT(device_get_parent(dev), data, len, type, id)); + return (HID_SET_REPORT(device_get_parent(dev), dev, data, len, type, + id)); } int hid_set_idle(device_t dev, uint16_t duration, uint8_t id) { - return (HID_SET_IDLE(device_get_parent(dev), duration, id)); + return (HID_SET_IDLE(device_get_parent(dev), dev, duration, id)); } int hid_set_protocol(device_t dev, uint16_t protocol) { - return (HID_SET_PROTOCOL(device_get_parent(dev), protocol)); + return (HID_SET_PROTOCOL(device_get_parent(dev), dev, protocol)); } int hid_ioctl(device_t dev, unsigned long cmd, uintptr_t data) { - return (HID_IOCTL(device_get_parent(dev), cmd, data)); + return (HID_IOCTL(device_get_parent(dev), dev, cmd, data)); } MODULE_VERSION(hid, 1); diff --git a/sys/dev/hid/hid_if.m b/sys/dev/hid/hid_if.m index 38a053b8744e..9050620ccea6 100644 --- a/sys/dev/hid/hid_if.m +++ b/sys/dev/hid/hid_if.m @@ -1,176 +1,189 @@ #- # Copyright (c) 2019 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. # # $FreeBSD$ # #include #include #include #include #include # Any function listed here can do unbound sleeps waiting for IO to complete. INTERFACE hid; # Interrupts interface # # Allocate memory and initialise interrupt transfers. # intr callback function which is called if input data is available. # context is the private softc pointer, which will be used to callback. # rdesc is pointer to structire containing requested maximal sizes of input, # output and feature reports. It is used by hardware transport drivers # to determine sizes of internal buffers. # This function can be subsequently called with intr parameter set to NULL # to request intr_poll method support for transport driver. # METHOD void intr_setup { device_t dev; + device_t child; hid_intr_t intr; void *context; struct hid_rdesc_info *rdesc; }; # # Release all allocated resources associated with interrupt transfers. # METHOD void intr_unsetup { device_t dev; + device_t child; }; # # Start the interrupt transfers if not already started. # METHOD int intr_start { device_t dev; + device_t child; }; # # Stop the interrupt transfers if not already stopped. # METHOD int intr_stop { device_t dev; + device_t child; }; # # The following function gets called from the HID keyboard driver when # the system has panicked. intr_setup method with NULL passed as intr parameter # must be called once before to let transport driver to be prepared. # METHOD void intr_poll { device_t dev; + device_t child; }; # HID interface # # Read out an report descriptor from the HID device. # METHOD int get_rdesc { device_t dev; + device_t child; void *data; hid_size_t len; }; # # Get input data from the device. Data should be read in chunks # of the size prescribed by the report descriptor. # This function interferes with interrupt transfers and should not be used. # METHOD int read { device_t dev; + device_t child; void *data; hid_size_t maxlen; hid_size_t *actlen; }; # # Send data to the device. Data should be written in # chunks of the size prescribed by the report descriptor. # METHOD int write { device_t dev; + device_t child; const void *data; hid_size_t len; }; # # Get a report from the device without waiting for data on the interrupt. # Copies a maximum of len bytes of the report data into the memory specified # by data. Upon return actlen is set to the number of bytes copied. The type # field indicates which report is requested. It should be HID_INPUT_REPORT, # HID_OUTPUT_REPORT, or HID_FEATURE_REPORT. This call may fail if the device # does not support this feature. # METHOD int get_report { device_t dev; + device_t child; void *data; hid_size_t maxlen; hid_size_t *actlen; uint8_t type; uint8_t id; }; # # Set a report in the device. The type field indicates which report is to be # set. It should be HID_INPUT_REPORT, HID_OUTPUT_REPORT, or HID_FEATURE_REPORT. # The value of the report is specified by the data and the len fields. # This call may fail if the device does not support this feature. # METHOD int set_report { device_t dev; + device_t child; const void *data; hid_size_t len; uint8_t type; uint8_t id; }; # # Set duration between input reports (in mSec). # METHOD int set_idle { device_t dev; + device_t child; uint16_t duration; uint8_t id; }; # # Switch between the boot protocol and the report protocol (or vice versa). # METHOD int set_protocol { device_t dev; + device_t child; uint16_t protocol; }; # # Executes arbitrary transport backend command. # Format of command is defined by hardware transport driver. # METHOD int ioctl { device_t dev; + device_t child; unsigned long cmd; uintptr_t data; }; diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c index 6ecc7455cc5d..fe6c4df060a3 100644 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -1,929 +1,979 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2019-2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hid_debug #include #include #include #include "hid_if.h" #define INPUT_EPOCH global_epoch_preempt #define HID_RSIZE_MAX 1024 static hid_intr_t hidbus_intr; static device_probe_t hidbus_probe; static device_attach_t hidbus_attach; static device_detach_t hidbus_detach; struct hidbus_ivars { int32_t usage; uint8_t index; uint32_t flags; uintptr_t driver_info; /* for internal use */ struct mtx *mtx; /* child intr mtx */ hid_intr_t *intr_handler; /* executed under mtx*/ void *intr_ctx; unsigned int refcnt; /* protected by mtx */ struct epoch_context epoch_ctx; CK_STAILQ_ENTRY(hidbus_ivars) link; }; struct hidbus_softc { device_t dev; struct sx sx; struct mtx mtx; bool nowrite; struct hid_rdesc_info rdesc; bool overloaded; int nest; /* Child attach nesting lvl */ int nauto; /* Number of autochildren */ CK_STAILQ_HEAD(, hidbus_ivars) tlcs; }; static int hidbus_fill_rdesc_info(struct hid_rdesc_info *hri, const void *data, hid_size_t len) { int error = 0; hri->data = __DECONST(void *, data); hri->len = len; /* * If report descriptor is not available yet, set maximal * report sizes high enough to allow hidraw to work. */ hri->isize = len == 0 ? HID_RSIZE_MAX : hid_report_size_max(data, len, hid_input, &hri->iid); hri->osize = len == 0 ? HID_RSIZE_MAX : hid_report_size_max(data, len, hid_output, &hri->oid); hri->fsize = len == 0 ? HID_RSIZE_MAX : hid_report_size_max(data, len, hid_feature, &hri->fid); if (hri->isize > HID_RSIZE_MAX) { DPRINTF("input size is too large, %u bytes (truncating)\n", hri->isize); hri->isize = HID_RSIZE_MAX; error = EOVERFLOW; } if (hri->osize > HID_RSIZE_MAX) { DPRINTF("output size is too large, %u bytes (truncating)\n", hri->osize); hri->osize = HID_RSIZE_MAX; error = EOVERFLOW; } if (hri->fsize > HID_RSIZE_MAX) { DPRINTF("feature size is too large, %u bytes (truncating)\n", hri->fsize); hri->fsize = HID_RSIZE_MAX; error = EOVERFLOW; } return (error); } int hidbus_locate(const void *desc, hid_size_t size, int32_t u, enum hid_kind k, uint8_t tlc_index, uint8_t index, struct hid_location *loc, uint32_t *flags, uint8_t *id, struct hid_absinfo *ai) { struct hid_data *d; struct hid_item h; int i; d = hid_start_parse(desc, size, 1 << k); HIDBUS_FOREACH_ITEM(d, &h, tlc_index) { for (i = 0; i < h.nusages; i++) { if (h.kind == k && h.usages[i] == u) { if (index--) break; if (loc != NULL) *loc = h.loc; if (flags != NULL) *flags = h.flags; if (id != NULL) *id = h.report_ID; if (ai != NULL && (h.flags&HIO_RELATIVE) == 0) *ai = (struct hid_absinfo) { .max = h.logical_maximum, .min = h.logical_minimum, .res = hid_item_resolution(&h), }; hid_end_parse(d); return (1); } } } if (loc != NULL) loc->size = 0; if (flags != NULL) *flags = 0; if (id != NULL) *id = 0; hid_end_parse(d); return (0); } bool hidbus_is_collection(const void *desc, hid_size_t size, int32_t usage, uint8_t tlc_index) { struct hid_data *d; struct hid_item h; bool ret = false; d = hid_start_parse(desc, size, 0); HIDBUS_FOREACH_ITEM(d, &h, tlc_index) { if (h.kind == hid_collection && h.usage == usage) { ret = true; break; } } hid_end_parse(d); return (ret); } static device_t hidbus_add_child(device_t dev, u_int order, const char *name, int unit) { struct hidbus_softc *sc = device_get_softc(dev); struct hidbus_ivars *tlc; device_t child; child = device_add_child_ordered(dev, order, name, unit); if (child == NULL) return (child); tlc = malloc(sizeof(struct hidbus_ivars), M_DEVBUF, M_WAITOK | M_ZERO); tlc->mtx = &sc->mtx; device_set_ivars(child, tlc); sx_xlock(&sc->sx); CK_STAILQ_INSERT_TAIL(&sc->tlcs, tlc, link); sx_unlock(&sc->sx); return (child); } static int hidbus_enumerate_children(device_t dev, const void* data, hid_size_t len) { struct hidbus_softc *sc = device_get_softc(dev); struct hid_data *hd; struct hid_item hi; device_t child; uint8_t index = 0; if (data == NULL || len == 0) return (ENXIO); /* Add a child for each top level collection */ hd = hid_start_parse(data, len, 1 << hid_input); while (hid_get_item(hd, &hi)) { if (hi.kind != hid_collection || hi.collevel != 1) continue; child = BUS_ADD_CHILD(dev, 0, NULL, -1); if (child == NULL) { device_printf(dev, "Could not add HID device\n"); continue; } hidbus_set_index(child, index); hidbus_set_usage(child, hi.usage); hidbus_set_flags(child, HIDBUS_FLAG_AUTOCHILD); index++; DPRINTF("Add child TLC: 0x%04x:0x%04x\n", HID_GET_USAGE_PAGE(hi.usage), HID_GET_USAGE(hi.usage)); } hid_end_parse(hd); if (index == 0) return (ENXIO); sc->nauto = index; return (0); } static int hidbus_attach_children(device_t dev) { struct hidbus_softc *sc = device_get_softc(dev); int error; - HID_INTR_SETUP(device_get_parent(dev), hidbus_intr, sc, &sc->rdesc); + HID_INTR_SETUP(device_get_parent(dev), dev, hidbus_intr, sc, + &sc->rdesc); error = hidbus_enumerate_children(dev, sc->rdesc.data, sc->rdesc.len); if (error != 0) DPRINTF("failed to enumerate children: error %d\n", error); /* * hidbus_attach_children() can recurse through device_identify-> * hid_set_report_descr() call sequence. Do not perform children * attach twice in that case. */ sc->nest++; bus_generic_probe(dev); sc->nest--; if (sc->nest != 0) return (0); if (hid_is_keyboard(sc->rdesc.data, sc->rdesc.len) != 0) error = bus_generic_attach(dev); else error = bus_delayed_attach_children(dev); if (error != 0) device_printf(dev, "failed to attach child: error %d\n", error); return (error); } static int hidbus_detach_children(device_t dev) { device_t *children, bus; bool is_bus; int i, error; error = 0; is_bus = device_get_devclass(dev) == devclass_find("hidbus"); bus = is_bus ? dev : device_get_parent(dev); KASSERT(device_get_devclass(bus) == devclass_find("hidbus"), ("Device is not hidbus or it's child")); if (is_bus) { /* If hidbus is passed, delete all children. */ bus_generic_detach(bus); device_delete_children(bus); } else { /* * If hidbus child is passed, delete all hidbus children * except caller. Deleting the caller may result in deadlock. */ error = device_get_children(bus, &children, &i); if (error != 0) return (error); while (i-- > 0) { if (children[i] == dev) continue; DPRINTF("Delete child. index=%d (%s)\n", hidbus_get_index(children[i]), device_get_nameunit(children[i])); error = device_delete_child(bus, children[i]); if (error) { DPRINTF("Failed deleting %s\n", device_get_nameunit(children[i])); break; } } free(children, M_TEMP); } - HID_INTR_UNSETUP(device_get_parent(bus)); + HID_INTR_UNSETUP(device_get_parent(bus), bus); return (error); } static int hidbus_probe(device_t dev) { device_set_desc(dev, "HID bus"); /* Allow other subclasses to override this driver. */ return (BUS_PROBE_GENERIC); } static int hidbus_attach(device_t dev) { struct hidbus_softc *sc = device_get_softc(dev); struct hid_device_info *devinfo = device_get_ivars(dev); void *d_ptr = NULL; hid_size_t d_len; int error; sc->dev = dev; CK_STAILQ_INIT(&sc->tlcs); mtx_init(&sc->mtx, "hidbus ivar lock", NULL, MTX_DEF); sx_init(&sc->sx, "hidbus ivar list lock"); /* * Ignore error. It is possible for non-HID device e.g. XBox360 gamepad * to emulate HID through overloading of report descriptor. */ d_len = devinfo->rdescsize; if (d_len != 0) { d_ptr = malloc(d_len, M_DEVBUF, M_ZERO | M_WAITOK); error = hid_get_rdesc(dev, d_ptr, d_len); if (error != 0) { free(d_ptr, M_DEVBUF); d_len = 0; d_ptr = NULL; } } hidbus_fill_rdesc_info(&sc->rdesc, d_ptr, d_len); sc->nowrite = hid_test_quirk(devinfo, HQ_NOWRITE); error = hidbus_attach_children(dev); if (error != 0) { hidbus_detach(dev); return (ENXIO); } return (0); } static int hidbus_detach(device_t dev) { struct hidbus_softc *sc = device_get_softc(dev); hidbus_detach_children(dev); sx_destroy(&sc->sx); mtx_destroy(&sc->mtx); free(sc->rdesc.data, M_DEVBUF); return (0); } static void hidbus_child_detached(device_t bus, device_t child) { struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *tlc = device_get_ivars(child); KASSERT(tlc->refcnt == 0, ("Child device is running")); tlc->mtx = &sc->mtx; tlc->intr_handler = NULL; tlc->flags &= ~HIDBUS_FLAG_CAN_POLL; } /* * Epoch callback indicating tlc is safe to destroy */ static void hidbus_ivar_dtor(epoch_context_t ctx) { struct hidbus_ivars *tlc; tlc = __containerof(ctx, struct hidbus_ivars, epoch_ctx); free(tlc, M_DEVBUF); } static void hidbus_child_deleted(device_t bus, device_t child) { struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *tlc = device_get_ivars(child); sx_xlock(&sc->sx); KASSERT(tlc->refcnt == 0, ("Child device is running")); CK_STAILQ_REMOVE(&sc->tlcs, tlc, hidbus_ivars, link); sx_unlock(&sc->sx); epoch_call(INPUT_EPOCH, hidbus_ivar_dtor, &tlc->epoch_ctx); } static int hidbus_read_ivar(device_t bus, device_t child, int which, uintptr_t *result) { struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *tlc = device_get_ivars(child); switch (which) { case HIDBUS_IVAR_INDEX: *result = tlc->index; break; case HIDBUS_IVAR_USAGE: *result = tlc->usage; break; case HIDBUS_IVAR_FLAGS: *result = tlc->flags; break; case HIDBUS_IVAR_DRIVER_INFO: *result = tlc->driver_info; break; case HIDBUS_IVAR_LOCK: *result = (uintptr_t)(tlc->mtx == &sc->mtx ? NULL : tlc->mtx); break; default: return (EINVAL); } return (0); } static int hidbus_write_ivar(device_t bus, device_t child, int which, uintptr_t value) { struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *tlc = device_get_ivars(child); switch (which) { case HIDBUS_IVAR_INDEX: tlc->index = value; break; case HIDBUS_IVAR_USAGE: tlc->usage = value; break; case HIDBUS_IVAR_FLAGS: tlc->flags = value; if ((value & HIDBUS_FLAG_CAN_POLL) != 0) HID_INTR_SETUP( - device_get_parent(bus), NULL, NULL, NULL); + device_get_parent(bus), bus, NULL, NULL, NULL); break; case HIDBUS_IVAR_DRIVER_INFO: tlc->driver_info = value; break; case HIDBUS_IVAR_LOCK: tlc->mtx = (struct mtx *)value == NULL ? &sc->mtx : (struct mtx *)value; break; default: return (EINVAL); } return (0); } /* Location hint for devctl(8) */ static int hidbus_child_location(device_t bus, device_t child, struct sbuf *sb) { struct hidbus_ivars *tlc = device_get_ivars(child); sbuf_printf(sb, "index=%hhu", tlc->index); return (0); } /* PnP information for devctl(8) */ static int hidbus_child_pnpinfo(device_t bus, device_t child, struct sbuf *sb) { struct hidbus_ivars *tlc = device_get_ivars(child); struct hid_device_info *devinfo = device_get_ivars(bus); sbuf_printf(sb, "page=0x%04x usage=0x%04x bus=0x%02hx " "vendor=0x%04hx product=0x%04hx version=0x%04hx%s%s", HID_GET_USAGE_PAGE(tlc->usage), HID_GET_USAGE(tlc->usage), devinfo->idBus, devinfo->idVendor, devinfo->idProduct, devinfo->idVersion, devinfo->idPnP[0] == '\0' ? "" : " _HID=", devinfo->idPnP[0] == '\0' ? "" : devinfo->idPnP); return (0); } void hidbus_set_desc(device_t child, const char *suffix) { device_t bus = device_get_parent(child); struct hidbus_softc *sc = device_get_softc(bus); struct hid_device_info *devinfo = device_get_ivars(bus); struct hidbus_ivars *tlc = device_get_ivars(child); char buf[80]; /* Do not add NULL suffix or if device name already contains it. */ if (suffix != NULL && strcasestr(devinfo->name, suffix) == NULL && (sc->nauto > 1 || (tlc->flags & HIDBUS_FLAG_AUTOCHILD) == 0)) { snprintf(buf, sizeof(buf), "%s %s", devinfo->name, suffix); device_set_desc_copy(child, buf); } else device_set_desc(child, devinfo->name); } device_t hidbus_find_child(device_t bus, int32_t usage) { device_t *children, child; int ccount, i; bus_topo_assert(); /* Get a list of all hidbus children */ if (device_get_children(bus, &children, &ccount) != 0) return (NULL); /* Scan through to find required TLC */ for (i = 0, child = NULL; i < ccount; i++) { if (hidbus_get_usage(children[i]) == usage) { child = children[i]; break; } } free(children, M_TEMP); return (child); } void hidbus_intr(void *context, void *buf, hid_size_t len) { struct hidbus_softc *sc = context; struct hidbus_ivars *tlc; struct epoch_tracker et; /* * Broadcast input report to all subscribers. * TODO: Add check for input report ID. * * Relock mutex on every TLC item as we can't hold any locks over whole * TLC list here due to LOR with open()/close() handlers. */ if (!HID_IN_POLLING_MODE()) epoch_enter_preempt(INPUT_EPOCH, &et); CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) { if (tlc->refcnt == 0 || tlc->intr_handler == NULL) continue; if (HID_IN_POLLING_MODE()) { if ((tlc->flags & HIDBUS_FLAG_CAN_POLL) != 0) tlc->intr_handler(tlc->intr_ctx, buf, len); } else { mtx_lock(tlc->mtx); tlc->intr_handler(tlc->intr_ctx, buf, len); mtx_unlock(tlc->mtx); } } if (!HID_IN_POLLING_MODE()) epoch_exit_preempt(INPUT_EPOCH, &et); } void hidbus_set_intr(device_t child, hid_intr_t *handler, void *context) { struct hidbus_ivars *tlc = device_get_ivars(child); tlc->intr_handler = handler; tlc->intr_ctx = context; } int hidbus_intr_start(device_t child) { device_t bus = device_get_parent(child); struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *ivar = device_get_ivars(child); struct hidbus_ivars *tlc; bool refcnted = false; int error; if (sx_xlock_sig(&sc->sx) != 0) return (EINTR); CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) { refcnted |= (tlc->refcnt != 0); if (tlc == ivar) { mtx_lock(tlc->mtx); ++tlc->refcnt; mtx_unlock(tlc->mtx); } } - error = refcnted ? 0 : HID_INTR_START(device_get_parent(bus)); + error = refcnted ? 0 : HID_INTR_START(device_get_parent(bus), bus); sx_unlock(&sc->sx); return (error); } int hidbus_intr_stop(device_t child) { device_t bus = device_get_parent(child); struct hidbus_softc *sc = device_get_softc(bus); struct hidbus_ivars *ivar = device_get_ivars(child); struct hidbus_ivars *tlc; bool refcnted = false; int error; if (sx_xlock_sig(&sc->sx) != 0) return (EINTR); CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) { if (tlc == ivar) { mtx_lock(tlc->mtx); MPASS(tlc->refcnt != 0); --tlc->refcnt; mtx_unlock(tlc->mtx); } refcnted |= (tlc->refcnt != 0); } - error = refcnted ? 0 : HID_INTR_STOP(device_get_parent(bus)); + error = refcnted ? 0 : HID_INTR_STOP(device_get_parent(bus), bus); sx_unlock(&sc->sx); return (error); } void hidbus_intr_poll(device_t child) { device_t bus = device_get_parent(child); - HID_INTR_POLL(device_get_parent(bus)); + HID_INTR_POLL(device_get_parent(bus), bus); } struct hid_rdesc_info * hidbus_get_rdesc_info(device_t child) { device_t bus = device_get_parent(child); struct hidbus_softc *sc = device_get_softc(bus); return (&sc->rdesc); } /* * HID interface. * * Hidbus as well as any hidbus child can be passed as first arg. */ /* Read cached report descriptor */ int hid_get_report_descr(device_t dev, void **data, hid_size_t *len) { device_t bus; struct hidbus_softc *sc; bus = device_get_devclass(dev) == devclass_find("hidbus") ? dev : device_get_parent(dev); sc = device_get_softc(bus); /* * Do not send request to a transport backend. * Use cached report descriptor instead of it. */ if (sc->rdesc.data == NULL || sc->rdesc.len == 0) return (ENXIO); if (data != NULL) *data = sc->rdesc.data; if (len != NULL) *len = sc->rdesc.len; return (0); } /* * Replace cached report descriptor with top level driver provided one. * * It deletes all hidbus children except caller and enumerates them again after * new descriptor has been registered. Currently it can not be called from * autoenumerated (by report's TLC) child device context as it results in child * duplication. To overcome this limitation hid_set_report_descr() should be * called from device_identify driver's handler with hidbus itself passed as * 'device_t dev' parameter. */ int hid_set_report_descr(device_t dev, const void *data, hid_size_t len) { struct hid_rdesc_info rdesc; device_t bus; struct hidbus_softc *sc; bool is_bus; int error; bus_topo_assert(); is_bus = device_get_devclass(dev) == devclass_find("hidbus"); bus = is_bus ? dev : device_get_parent(dev); sc = device_get_softc(bus); /* * Do not overload already overloaded report descriptor in * device_identify handler. It causes infinite recursion loop. */ if (is_bus && sc->overloaded) return(0); DPRINTFN(5, "len=%d\n", len); DPRINTFN(5, "data = %*D\n", len, data, " "); error = hidbus_fill_rdesc_info(&rdesc, data, len); if (error != 0) return (error); error = hidbus_detach_children(dev); if (error != 0) return(error); /* Make private copy to handle a case of dynamicaly allocated data. */ rdesc.data = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK); bcopy(data, rdesc.data, len); sc->overloaded = true; free(sc->rdesc.data, M_DEVBUF); bcopy(&rdesc, &sc->rdesc, sizeof(struct hid_rdesc_info)); error = hidbus_attach_children(bus); return (error); } static int -hidbus_write(device_t dev, const void *data, hid_size_t len) +hidbus_get_rdesc(device_t dev, device_t child __unused, void *data, + hid_size_t len) +{ + return (hid_get_rdesc(dev, data, len)); +} + +static int +hidbus_read(device_t dev, device_t child __unused, void *data, + hid_size_t maxlen, hid_size_t *actlen) +{ + return (hid_read(dev, data, maxlen, actlen)); +} + +static int +hidbus_write(device_t dev, device_t child __unused, const void *data, + hid_size_t len) { struct hidbus_softc *sc; uint8_t id; sc = device_get_softc(dev); /* * Output interrupt endpoint is often optional. If HID device * does not provide it, send reports via control pipe. */ if (sc->nowrite) { /* try to extract the ID byte */ id = (sc->rdesc.oid & (len > 0)) ? *(const uint8_t*)data : 0; return (hid_set_report(dev, data, len, HID_OUTPUT_REPORT, id)); } return (hid_write(dev, data, len)); } +static int +hidbus_get_report(device_t dev, device_t child __unused, void *data, + hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) +{ + return (hid_get_report(dev, data, maxlen, actlen, type, id)); +} + +static int +hidbus_set_report(device_t dev, device_t child __unused, const void *data, + hid_size_t len, uint8_t type, uint8_t id) +{ + return (hid_set_report(dev, data, len, type, id)); +} + +static int +hidbus_set_idle(device_t dev, device_t child __unused, uint16_t duration, + uint8_t id) +{ + return (hid_set_idle(dev, duration, id)); +} + +static int +hidbus_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) +{ + return (hid_set_protocol(dev, protocol)); +} + +static int +hidbus_ioctl(device_t dev, device_t child __unused, unsigned long cmd, + uintptr_t data) +{ + return (hid_ioctl(dev, cmd, data)); +} + /*------------------------------------------------------------------------* * hidbus_lookup_id * * This functions takes an array of "struct hid_device_id" and tries * to match the entries with the information in "struct hid_device_info". * * Return values: * NULL: No match found. * Else: Pointer to matching entry. *------------------------------------------------------------------------*/ const struct hid_device_id * hidbus_lookup_id(device_t dev, const struct hid_device_id *id, int nitems_id) { const struct hid_device_id *id_end; const struct hid_device_info *info; int32_t usage; bool is_child; if (id == NULL) { goto done; } id_end = id + nitems_id; info = hid_get_device_info(dev); is_child = device_get_devclass(dev) != devclass_find("hidbus"); if (is_child) usage = hidbus_get_usage(dev); /* * Keep on matching array entries until we find a match or * until we reach the end of the matching array: */ for (; id != id_end; id++) { if (is_child && (id->match_flag_page) && (id->page != HID_GET_USAGE_PAGE(usage))) { continue; } if (is_child && (id->match_flag_usage) && (id->usage != HID_GET_USAGE(usage))) { continue; } if ((id->match_flag_bus) && (id->idBus != info->idBus)) { continue; } if ((id->match_flag_vendor) && (id->idVendor != info->idVendor)) { continue; } if ((id->match_flag_product) && (id->idProduct != info->idProduct)) { continue; } if ((id->match_flag_ver_lo) && (id->idVersion_lo > info->idVersion)) { continue; } if ((id->match_flag_ver_hi) && (id->idVersion_hi < info->idVersion)) { continue; } if (id->match_flag_pnp && strncmp(id->idPnP, info->idPnP, HID_PNP_ID_SIZE) != 0) { continue; } /* We found a match! */ return (id); } done: return (NULL); } /*------------------------------------------------------------------------* * hidbus_lookup_driver_info - factored out code * * Return values: * 0: Success * Else: Failure *------------------------------------------------------------------------*/ int hidbus_lookup_driver_info(device_t child, const struct hid_device_id *id, int nitems_id) { id = hidbus_lookup_id(child, id, nitems_id); if (id) { /* copy driver info */ hidbus_set_driver_info(child, id->driver_info); return (0); } return (ENXIO); } const struct hid_device_info * hid_get_device_info(device_t dev) { device_t bus; bus = device_get_devclass(dev) == devclass_find("hidbus") ? dev : device_get_parent(dev); return (device_get_ivars(bus)); } static device_method_t hidbus_methods[] = { /* device interface */ DEVMETHOD(device_probe, hidbus_probe), DEVMETHOD(device_attach, hidbus_attach), DEVMETHOD(device_detach, hidbus_detach), DEVMETHOD(device_suspend, bus_generic_suspend), DEVMETHOD(device_resume, bus_generic_resume), /* bus interface */ DEVMETHOD(bus_add_child, hidbus_add_child), DEVMETHOD(bus_child_detached, hidbus_child_detached), DEVMETHOD(bus_child_deleted, hidbus_child_deleted), DEVMETHOD(bus_read_ivar, hidbus_read_ivar), DEVMETHOD(bus_write_ivar, hidbus_write_ivar), DEVMETHOD(bus_child_pnpinfo, hidbus_child_pnpinfo), DEVMETHOD(bus_child_location, hidbus_child_location), /* hid interface */ - DEVMETHOD(hid_get_rdesc, hid_get_rdesc), - DEVMETHOD(hid_read, hid_read), + DEVMETHOD(hid_get_rdesc, hidbus_get_rdesc), + DEVMETHOD(hid_read, hidbus_read), DEVMETHOD(hid_write, hidbus_write), - DEVMETHOD(hid_get_report, hid_get_report), - DEVMETHOD(hid_set_report, hid_set_report), - DEVMETHOD(hid_set_idle, hid_set_idle), - DEVMETHOD(hid_set_protocol, hid_set_protocol), - DEVMETHOD(hid_ioctl, hid_ioctl), + DEVMETHOD(hid_get_report, hidbus_get_report), + DEVMETHOD(hid_set_report, hidbus_set_report), + DEVMETHOD(hid_set_idle, hidbus_set_idle), + DEVMETHOD(hid_set_protocol, hidbus_set_protocol), + DEVMETHOD(hid_ioctl, hidbus_ioctl), DEVMETHOD_END }; driver_t hidbus_driver = { "hidbus", hidbus_methods, sizeof(struct hidbus_softc), }; MODULE_DEPEND(hidbus, hid, 1, 1, 1); MODULE_VERSION(hidbus, 1); DRIVER_MODULE(hidbus, hvhid, hidbus_driver, 0, 0); DRIVER_MODULE(hidbus, iichid, hidbus_driver, 0, 0); DRIVER_MODULE(hidbus, usbhid, hidbus_driver, 0, 0); diff --git a/sys/dev/hyperv/input/hv_hid.c b/sys/dev/hyperv/input/hv_hid.c index d576b292e12e..b8fc9605bf67 100644 --- a/sys/dev/hyperv/input/hv_hid.c +++ b/sys/dev/hyperv/input/hv_hid.c @@ -1,564 +1,565 @@ /*- * Copyright (c) 2017 Microsoft Corp. * Copyright (c) 2023 Yuri * * 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 unmodified, 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 ``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 BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hid_if.h" #include "vmbus_if.h" #define HV_HID_VER_MAJOR 2 #define HV_HID_VER_MINOR 0 #define HV_HID_VER (HV_HID_VER_MINOR | (HV_HID_VER_MAJOR) << 16) #define HV_BUFSIZ (4 * PAGE_SIZE) #define HV_HID_RINGBUFF_SEND_SZ (10 * PAGE_SIZE) #define HV_HID_RINGBUFF_RECV_SZ (10 * PAGE_SIZE) typedef struct { device_t dev; struct mtx mtx; /* vmbus */ struct vmbus_channel *hs_chan; struct vmbus_xact_ctx *hs_xact_ctx; uint8_t *buf; int buflen; /* hid */ struct hid_device_info hdi; hid_intr_t *intr; bool intr_on; void *intr_ctx; uint8_t *rdesc; } hv_hid_sc; typedef enum { SH_PROTO_REQ, SH_PROTO_RESP, SH_DEVINFO, SH_DEVINFO_ACK, SH_INPUT_REPORT, } sh_msg_type; typedef struct { sh_msg_type type; uint32_t size; } __packed sh_msg_hdr; typedef struct { sh_msg_hdr hdr; char data[]; } __packed sh_msg; typedef struct { sh_msg_hdr hdr; uint32_t ver; } __packed sh_proto_req; typedef struct { sh_msg_hdr hdr; uint32_t ver; uint32_t app; } __packed sh_proto_resp; typedef struct { u_int size; u_short vendor; u_short product; u_short version; u_short reserved[11]; } __packed sh_devinfo; /* Copied from linux/hid.h */ typedef struct { uint8_t bDescriptorType; uint16_t wDescriptorLength; } __packed sh_hcdesc; typedef struct { uint8_t bLength; uint8_t bDescriptorType; uint16_t bcdHID; uint8_t bCountryCode; uint8_t bNumDescriptors; sh_hcdesc hcdesc[1]; } __packed sh_hdesc; typedef struct { sh_msg_hdr hdr; sh_devinfo devinfo; sh_hdesc hdesc; } __packed sh_devinfo_resp; typedef struct { sh_msg_hdr hdr; uint8_t rsvd; } __packed sh_devinfo_ack; typedef struct { sh_msg_hdr hdr; char buffer[]; } __packed sh_input_report; typedef enum { HV_HID_MSG_INVALID, HV_HID_MSG_DATA, } hv_hid_msg_type; typedef struct { hv_hid_msg_type type; uint32_t size; char data[]; } hv_hid_pmsg; typedef struct { hv_hid_msg_type type; uint32_t size; union { sh_msg msg; sh_proto_req req; sh_proto_resp resp; sh_devinfo_resp dresp; sh_devinfo_ack ack; sh_input_report irep; }; } hv_hid_msg; #define HV_HID_REQ_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_req)) #define HV_HID_RESP_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_proto_resp)) #define HV_HID_ACK_SZ (sizeof(hv_hid_pmsg) + sizeof(sh_devinfo_ack)) /* Somewhat arbitrary, enough to get the devinfo response */ #define HV_HID_REQ_MAX 256 #define HV_HID_RESP_MAX 256 static const struct vmbus_ic_desc vmbus_hid_descs[] = { { .ic_guid = { .hv_guid = { 0x9e, 0xb6, 0xa8, 0xcf, 0x4a, 0x5b, 0xc0, 0x4c, 0xb9, 0x8b, 0x8b, 0xa1, 0xa1, 0xf3, 0xf9, 0x5a} }, .ic_desc = "Hyper-V HID device" }, VMBUS_IC_DESC_END }; /* TODO: add GUID support to devmatch(8) to export vmbus_hid_descs directly */ const struct { char *guid; } vmbus_hid_descs_pnp[] = {{ "cfa8b69e-5b4a-4cc0-b98b-8ba1a1f3f95a" }}; static int hv_hid_attach(device_t dev); static int hv_hid_detach(device_t dev); static int hv_hid_connect_vsp(hv_hid_sc *sc) { struct vmbus_xact *xact; hv_hid_msg *req; const hv_hid_msg *resp; size_t resplen; int ret; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_REQ_SZ); if (xact == NULL) { device_printf(sc->dev, "no xact for init"); return (ENODEV); } req = vmbus_xact_req_data(xact); req->type = HV_HID_MSG_DATA; req->size = sizeof(sh_proto_req); req->req.hdr.type = SH_PROTO_REQ; req->req.hdr.size = sizeof(u_int); req->req.ver = HV_HID_VER; vmbus_xact_activate(xact); ret = vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, VMBUS_CHANPKT_FLAG_RC, req, HV_HID_REQ_SZ, (uint64_t)(uintptr_t)xact); if (ret != 0) { device_printf(sc->dev, "failed to send proto req\n"); vmbus_xact_deactivate(xact); return (ret); } resp = vmbus_chan_xact_wait(sc->hs_chan, xact, &resplen, true); if (resplen != HV_HID_RESP_SZ || !resp->resp.app) { device_printf(sc->dev, "proto req failed\n"); ret = ENODEV; } vmbus_xact_put(xact); return (ret); } static void hv_hid_receive(hv_hid_sc *sc, struct vmbus_chanpkt_hdr *pkt) { const hv_hid_msg *msg; sh_msg_type msg_type; uint32_t msg_len; void *rdesc; msg = VMBUS_CHANPKT_CONST_DATA(pkt); msg_len = VMBUS_CHANPKT_DATALEN(pkt); if (msg->type != HV_HID_MSG_DATA) return; if (msg_len <= sizeof(hv_hid_pmsg)) { device_printf(sc->dev, "invalid packet length\n"); return; } msg_type = msg->msg.hdr.type; switch (msg_type) { case SH_PROTO_RESP: { struct vmbus_xact_ctx *xact_ctx; xact_ctx = sc->hs_xact_ctx; if (xact_ctx != NULL) { vmbus_xact_ctx_wakeup(xact_ctx, VMBUS_CHANPKT_CONST_DATA(pkt), VMBUS_CHANPKT_DATALEN(pkt)); } break; } case SH_DEVINFO: { struct vmbus_xact *xact; struct hid_device_info *hdi; hv_hid_msg ack; const sh_devinfo *devinfo; const sh_hdesc *hdesc; /* Send ack */ ack.type = HV_HID_MSG_DATA; ack.size = sizeof(sh_devinfo_ack); ack.ack.hdr.type = SH_DEVINFO_ACK; ack.ack.hdr.size = 1; ack.ack.rsvd = 0; xact = vmbus_xact_get(sc->hs_xact_ctx, HV_HID_ACK_SZ); if (xact == NULL) break; vmbus_xact_activate(xact); (void) vmbus_chan_send(sc->hs_chan, VMBUS_CHANPKT_TYPE_INBAND, 0, &ack, HV_HID_ACK_SZ, (uint64_t)(uintptr_t)xact); vmbus_xact_deactivate(xact); vmbus_xact_put(xact); /* Check for resume from hibernation */ if (sc->rdesc != NULL) break; /* Parse devinfo response */ devinfo = &msg->dresp.devinfo; hdesc = &msg->dresp.hdesc; if (hdesc->bLength == 0) break; hdi = &sc->hdi; memset(hdi, 0, sizeof(*hdi)); hdi->rdescsize = le16toh(hdesc->hcdesc[0].wDescriptorLength); if (hdi->rdescsize == 0) break; strlcpy(hdi->name, "Hyper-V", sizeof(hdi->name)); hdi->idBus = BUS_VIRTUAL; hdi->idVendor = le16toh(devinfo->vendor); hdi->idProduct = le16toh(devinfo->product); hdi->idVersion = le16toh(devinfo->version); /* Save rdesc copy */ rdesc = malloc(hdi->rdescsize, M_DEVBUF, M_WAITOK | M_ZERO); memcpy(rdesc, (const uint8_t *)hdesc + hdesc->bLength, hdi->rdescsize); mtx_lock(&sc->mtx); sc->rdesc = rdesc; wakeup(sc); mtx_unlock(&sc->mtx); break; } case SH_INPUT_REPORT: { mtx_lock(&sc->mtx); if (sc->intr != NULL && sc->intr_on) sc->intr(sc->intr_ctx, __DECONST(void *, msg->irep.buffer), msg->irep.hdr.size); mtx_unlock(&sc->mtx); break; } default: break; } } static void hv_hid_read_channel(struct vmbus_channel *channel, void *ctx) { hv_hid_sc *sc; uint8_t *buf; int buflen; int ret; sc = ctx; buf = sc->buf; buflen = sc->buflen; for (;;) { struct vmbus_chanpkt_hdr *pkt; int rcvd; pkt = (struct vmbus_chanpkt_hdr *)buf; rcvd = buflen; ret = vmbus_chan_recv_pkt(channel, pkt, &rcvd); if (__predict_false(ret == ENOBUFS)) { buflen = sc->buflen * 2; while (buflen < rcvd) buflen *= 2; buf = malloc(buflen, M_DEVBUF, M_WAITOK | M_ZERO); device_printf(sc->dev, "expand recvbuf %d -> %d\n", sc->buflen, buflen); free(sc->buf, M_DEVBUF); sc->buf = buf; sc->buflen = buflen; continue; } else if (__predict_false(ret == EAGAIN)) { /* No more channel packets; done! */ break; } KASSERT(ret == 0, ("vmbus_chan_recv_pkt failed: %d", ret)); switch (pkt->cph_type) { case VMBUS_CHANPKT_TYPE_COMP: case VMBUS_CHANPKT_TYPE_RXBUF: device_printf(sc->dev, "unhandled event: %d\n", pkt->cph_type); break; case VMBUS_CHANPKT_TYPE_INBAND: hv_hid_receive(sc, pkt); break; default: device_printf(sc->dev, "unknown event: %d\n", pkt->cph_type); break; } } } static int hv_hid_probe(device_t dev) { device_t bus; const struct vmbus_ic_desc *d; if (resource_disabled(device_get_name(dev), 0)) return (ENXIO); bus = device_get_parent(dev); for (d = vmbus_hid_descs; d->ic_desc != NULL; ++d) { if (VMBUS_PROBE_GUID(bus, dev, &d->ic_guid) == 0) { device_set_desc(dev, d->ic_desc); return (BUS_PROBE_DEFAULT); } } return (ENXIO); } static int hv_hid_attach(device_t dev) { device_t child; hv_hid_sc *sc; int ret; sc = device_get_softc(dev); sc->dev = dev; mtx_init(&sc->mtx, "hvhid lock", NULL, MTX_DEF); sc->hs_chan = vmbus_get_channel(dev); sc->hs_xact_ctx = vmbus_xact_ctx_create(bus_get_dma_tag(dev), HV_HID_REQ_MAX, HV_HID_RESP_MAX, 0); if (sc->hs_xact_ctx == NULL) { ret = ENOMEM; goto out; } sc->buflen = HV_BUFSIZ; sc->buf = malloc(sc->buflen, M_DEVBUF, M_WAITOK | M_ZERO); vmbus_chan_set_readbatch(sc->hs_chan, false); ret = vmbus_chan_open(sc->hs_chan, HV_HID_RINGBUFF_SEND_SZ, HV_HID_RINGBUFF_RECV_SZ, NULL, 0, hv_hid_read_channel, sc); if (ret != 0) goto out; ret = hv_hid_connect_vsp(sc); if (ret != 0) goto out; /* Wait until we have devinfo (or arbitrary timeout of 3s) */ mtx_lock(&sc->mtx); if (sc->rdesc == NULL) ret = mtx_sleep(sc, &sc->mtx, 0, "hvhid", hz * 3); mtx_unlock(&sc->mtx); if (ret != 0) { ret = ENODEV; goto out; } child = device_add_child(sc->dev, "hidbus", -1); if (child == NULL) { device_printf(sc->dev, "failed to add hidbus\n"); ret = ENOMEM; goto out; } device_set_ivars(child, &sc->hdi); ret = bus_generic_attach(dev); if (ret != 0) device_printf(sc->dev, "failed to attach hidbus\n"); out: if (ret != 0) hv_hid_detach(dev); return (ret); } static int hv_hid_detach(device_t dev) { hv_hid_sc *sc; int ret; sc = device_get_softc(dev); ret = device_delete_children(dev); if (ret != 0) return (ret); if (sc->hs_xact_ctx != NULL) vmbus_xact_ctx_destroy(sc->hs_xact_ctx); vmbus_chan_close(vmbus_get_channel(dev)); free(sc->buf, M_DEVBUF); free(sc->rdesc, M_DEVBUF); mtx_destroy(&sc->mtx); return (0); } static void -hv_hid_intr_setup(device_t dev, hid_intr_t intr, void *ctx, - struct hid_rdesc_info *rdesc) +hv_hid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, + void *ctx, struct hid_rdesc_info *rdesc) { hv_hid_sc *sc; if (intr == NULL) return; sc = device_get_softc(dev); sc->intr = intr; sc->intr_on = false; sc->intr_ctx = ctx; rdesc->rdsize = rdesc->isize; } static void -hv_hid_intr_unsetup(device_t dev) +hv_hid_intr_unsetup(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); sc->intr = NULL; sc->intr_on = false; sc->intr_ctx = NULL; } static int -hv_hid_intr_start(device_t dev) +hv_hid_intr_start(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = true; mtx_unlock(&sc->mtx); return (0); } static int -hv_hid_intr_stop(device_t dev) +hv_hid_intr_stop(device_t dev, device_t child __unused) { hv_hid_sc *sc; sc = device_get_softc(dev); mtx_lock(&sc->mtx); sc->intr_on = false; mtx_unlock(&sc->mtx); return (0); } static int -hv_hid_get_rdesc(device_t dev, void *buf, hid_size_t len) +hv_hid_get_rdesc(device_t dev, device_t child __unused, void *buf, + hid_size_t len) { hv_hid_sc *sc; sc = device_get_softc(dev); if (len < sc->hdi.rdescsize) return (EMSGSIZE); memcpy(buf, sc->rdesc, len); return (0); } static device_method_t hv_hid_methods[] = { DEVMETHOD(device_probe, hv_hid_probe), DEVMETHOD(device_attach, hv_hid_attach), DEVMETHOD(device_detach, hv_hid_detach), DEVMETHOD(hid_intr_setup, hv_hid_intr_setup), DEVMETHOD(hid_intr_unsetup, hv_hid_intr_unsetup), DEVMETHOD(hid_intr_start, hv_hid_intr_start), DEVMETHOD(hid_intr_stop, hv_hid_intr_stop), DEVMETHOD(hid_get_rdesc, hv_hid_get_rdesc), DEVMETHOD_END, }; static driver_t hv_hid_driver = { .name = "hvhid", .methods = hv_hid_methods, .size = sizeof(hv_hid_sc), }; DRIVER_MODULE(hv_hid, vmbus, hv_hid_driver, NULL, NULL); MODULE_VERSION(hv_hid, 1); MODULE_DEPEND(hv_hid, hidbus, 1, 1, 1); MODULE_DEPEND(hv_hid, hms, 1, 1, 1); MODULE_DEPEND(hv_hid, vmbus, 1, 1, 1); MODULE_PNP_INFO("Z:classid", vmbus, hv_hid, vmbus_hid_descs_pnp, nitems(vmbus_hid_descs_pnp)); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c index 3e481ccd4417..a5da6df5eba3 100644 --- a/sys/dev/iicbus/iichid.c +++ b/sys/dev/iicbus/iichid.c @@ -1,1330 +1,1335 @@ /*- * 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; #define IICHID_REG_NONE -1 #define IICHID_REG_ACPI (UINT16_MAX + 1) #define IICHID_REG_ELAN 0x0001 static const struct iichid_id { char *id; int reg; } iichid_ids[] = { { "ELAN0000", IICHID_REG_ELAN }, { "PNP0C50", IICHID_REG_ACPI }, { "ACPI0C50", IICHID_REG_ACPI }, { NULL, 0 }, }; enum iichid_powerstate_how { IICHID_PS_NULL, IICHID_PS_ON, IICHID_PS_OFF, }; /* * Locking: no internal locks are used. To serialize access to shared members, * external iicbus lock should be taken. That allows to make locking greatly * simple at the cost of running front interrupt handlers with locked bus. */ struct iichid_softc { device_t dev; 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 struct task suspend_task; 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; static void iichid_suspend_task(void *, int); #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *); static int iichid_reset_callout(struct iichid_softc *); static void iichid_teardown_callout(struct iichid_softc *); #endif static inline int acpi_is_iichid(ACPI_HANDLE handle) { const struct iichid_id *ids; UINT32 sta; int reg; for (ids = iichid_ids; ids->id != NULL; ids++) { if (acpi_MatchHid(handle, ids->id)) { reg = ids->reg; break; } } if (ids->id == NULL) return (IICHID_REG_NONE); /* * If no _STA method or if it failed, then assume that * the device is present. */ if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) || ACPI_DEVICE_PRESENT(sta)) return (reg); return (IICHID_REG_NONE); } static ACPI_STATUS iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg) { ACPI_OBJECT *result; ACPI_BUFFER acpi_buf; ACPI_STATUS status; /* * function (_DSM) to be evaluated to retrieve the address of * the configuration register of the HID device. */ /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */ static uint8_t dsm_guid[ACPI_UUID_LENGTH] = { 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, }; status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf, ACPI_TYPE_INTEGER); if (ACPI_FAILURE(status)) { printf("%s: error evaluating _DSM\n", __func__); return (status); } result = (ACPI_OBJECT *) acpi_buf.Pointer; *config_reg = result->Integer.Value & 0xFFFF; AcpiOsFree(result); return (status); } static int iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, iichid_size_t *actual_len) { /* * 6.1.3 - Retrieval of Input Reports * DEVICE returns the length (2 Bytes) and the entire Input Report. */ 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 occurred: %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 * acknowledge 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 occurred: %d\n", error); iicbus_release_bus(parent, sc->dev); } static int iichid_set_power_state(struct iichid_softc *sc, enum iichid_powerstate_how how_open, enum iichid_powerstate_how how_suspend) { device_t parent; int error; int how_request; bool power_on; /* * Request iicbus early as sc->suspend and sc->power_on * are protected by iicbus internal lock. */ parent = device_get_parent(sc->dev); /* Allow to interrupt open()/close() handlers by SIGINT */ how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT; error = iicbus_request_bus(parent, sc->dev, how_request); if (error != 0) return (error); switch (how_open) { case IICHID_PS_ON: sc->open = true; break; case IICHID_PS_OFF: sc->open = false; break; case IICHID_PS_NULL: default: break; } switch (how_suspend) { case IICHID_PS_ON: sc->suspend = false; break; case IICHID_PS_OFF: sc->suspend = true; break; case IICHID_PS_NULL: default: break; } power_on = sc->open & !sc->suspend; if (power_on != sc->power_on) { error = iichid_set_power(sc, power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF); sc->power_on = power_on; #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) { if (power_on) { iichid_setup_callout(sc); iichid_reset_callout(sc); } else iichid_teardown_callout(sc); } #endif } iicbus_release_bus(parent, sc->dev); return (error); } static int iichid_setup_interrupt(struct iichid_softc *sc) { sc->irq_cookie = 0; int error = bus_setup_intr(sc->dev, sc->irq_res, INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie); if (error != 0) DPRINTF(sc, "Could not setup interrupt handler\n"); else DPRINTF(sc, "successfully setup interrupt\n"); return (error); } static void iichid_teardown_interrupt(struct iichid_softc *sc) { if (sc->irq_cookie) bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie); sc->irq_cookie = 0; } #ifdef IICHID_SAMPLING static int iichid_setup_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow < 0) { DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n"); return (EINVAL); } sc->callout_setup = true; DPRINTF(sc, "successfully setup callout\n"); return (0); } static int iichid_reset_callout(struct iichid_softc *sc) { if (sc->sampling_rate_slow <= 0) { DPRINTF(sc, "sampling_rate is below or equal to 0, " "can't reset callout\n"); return (EINVAL); } if (!sc->callout_setup) return (EINVAL); /* Start with slow sampling. */ sc->missing_samples = sc->sampling_hysteresis; 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) +iichid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, + void *context, struct hid_rdesc_info *rdesc) { struct iichid_softc *sc; if (intr == NULL) return; 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) +iichid_intr_unsetup(device_t dev, device_t child __unused) { 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) +iichid_intr_start(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device open\n"); iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL); return (0); } static int -iichid_intr_stop(device_t dev) +iichid_intr_stop(device_t dev, device_t child __unused) { struct iichid_softc *sc; sc = device_get_softc(dev); DPRINTF(sc, "iichid device close\n"); /* * 8.2 - The HOST determines that there are no active applications * that are currently using the specific HID DEVICE. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL); return (0); } static void -iichid_intr_poll(device_t dev) +iichid_intr_poll(device_t dev, device_t child __unused) { struct iichid_softc *sc; iichid_size_t actual; int error; sc = device_get_softc(dev); error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); if (error == 0 && actual != 0) sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); } /* * HID interface */ static int -iichid_get_rdesc(device_t dev, void *buf, hid_size_t len) +iichid_get_rdesc(device_t dev, device_t child __unused, void *buf, + hid_size_t len) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); error = iichid_cmd_get_report_desc(sc, buf, len); if (error) DPRINTF(sc, "failed to fetch report descriptor: %d\n", error); return (iic2errno(error)); } static int -iichid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen) +iichid_read(device_t dev, device_t child __unused, void *buf, + hid_size_t maxlen, hid_size_t *actlen) { struct iichid_softc *sc; device_t parent; 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) +iichid_write(device_t dev, device_t child __unused, const void *buf, + hid_size_t len) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_write(sc, buf, len))); } static int -iichid_get_report(device_t dev, void *buf, hid_size_t maxlen, - hid_size_t *actlen, uint8_t type, uint8_t id) +iichid_get_report(device_t dev, device_t child __unused, void *buf, + hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (maxlen > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno( iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id))); } static int -iichid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type, - uint8_t id) +iichid_set_report(device_t dev, device_t child __unused, const void *buf, + hid_size_t len, uint8_t type, uint8_t id) { struct iichid_softc *sc; if (len > IICHID_SIZE_MAX) return (EMSGSIZE); sc = device_get_softc(dev); return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id))); } static int -iichid_set_idle(device_t dev, uint16_t duration, uint8_t id) +iichid_set_idle(device_t dev, device_t child __unused, + uint16_t duration, uint8_t id) { return (ENOTSUP); } static int -iichid_set_protocol(device_t dev, uint16_t protocol) +iichid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) { return (ENOTSUP); } static int -iichid_ioctl(device_t dev, unsigned long cmd, uintptr_t data) +iichid_ioctl(device_t dev, device_t child __unused, unsigned long cmd, + uintptr_t data) { int error; switch (cmd) { case I2CRDWR: error = iic2errno(iicbus_transfer(dev, ((struct iic_rdwr_data *)data)->msgs, ((struct iic_rdwr_data *)data)->nmsgs)); break; default: error = EINVAL; } return (error); } static int iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle, struct hid_device_info *hw) { ACPI_DEVICE_INFO *device_info; hw->idBus = BUS_I2C; hw->idVendor = le16toh(desc->wVendorID); hw->idProduct = le16toh(desc->wProductID); hw->idVersion = le16toh(desc->wVersionID); /* get ACPI HID. It is a base part of the device name. */ if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info))) return (ENXIO); if (device_info->Valid & ACPI_VALID_HID) strlcpy(hw->idPnP, device_info->HardwareId.String, HID_PNP_ID_SIZE); snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X", (device_info->Valid & ACPI_VALID_HID) ? device_info->HardwareId.String : "Unknown", (device_info->Valid & ACPI_VALID_UID) ? strtoul(device_info->UniqueId.String, NULL, 10) : 0UL, le16toh(desc->wVendorID), le16toh(desc->wProductID)); AcpiOsFree(device_info); strlcpy(hw->serial, "", sizeof(hw->serial)); hw->rdescsize = le16toh(desc->wReportDescLength); if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0) hid_add_dynamic_quirk(hw, HQ_NOWRITE); return (0); } static int iichid_probe(device_t dev) { struct iichid_softc *sc; ACPI_HANDLE handle; char buf[80]; uint16_t config_reg; int error, reg; sc = device_get_softc(dev); sc->dev = dev; if (sc->probe_done) goto done; sc->probe_done = true; sc->probe_result = ENXIO; if (acpi_disabled("iichid")) return (ENXIO); sc->addr = iicbus_get_addr(dev) << 1; if (sc->addr == 0) return (ENXIO); handle = acpi_get_handle(dev); if (handle == NULL) return (ENXIO); reg = acpi_is_iichid(handle); if (reg == IICHID_REG_NONE) return (ENXIO); if (reg == IICHID_REG_ACPI) { if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) return (ENXIO); } else config_reg = (uint16_t)reg; DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1); DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg); error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc); if (error) { DPRINTF(sc, "could not retrieve HID descriptor from the " "device: %d\n", error); return (ENXIO); } if (le16toh(sc->desc.wHIDDescLength) != 30 || le16toh(sc->desc.bcdVersion) != 0x100) { DPRINTF(sc, "HID descriptor is broken\n"); return (ENXIO); } /* Setup hid_device_info so we can figure out quirks for the device. */ if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) { DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n"); return (ENXIO); } if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE)) return (ENXIO); sc->probe_result = BUS_PROBE_DEFAULT; done: if (sc->probe_result <= BUS_PROBE_SPECIFIC) { 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); error = ENXIO; goto done; } sc->power_on = true; TASK_INIT(&sc->suspend_task, 0, iichid_suspend_task, sc); #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("iichid_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", CTLFLAG_RWTUN, &sc->sampling_rate_fast, 0, "active sampling rate in num/second"); SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), OID_AUTO, "sampling_hysteresis", CTLFLAG_RWTUN, &sc->sampling_hysteresis, 0, "number of missing samples before enabling of slow mode"); hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING); if (sc->sampling_rate_slow >= 0) { pause("iichid", (hz + 999) / 1000); (void)iichid_cmd_read(sc, NULL, 0, NULL); } #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); sc->power_on = false; 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 void iichid_suspend_task(void *context, int pending) { struct iichid_softc *sc = context; iichid_teardown_interrupt(sc); } static int iichid_suspend(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); (void)bus_generic_suspend(dev); /* * 8.2 - The HOST is going into a deep power optimized state and wishes * to put all the devices into a low power state also. The HOST * is recommended to issue a HIPO command to the DEVICE to force * the DEVICE in to a lower power state. */ DPRINTF(sc, "Suspend called, setting device to power_state 1\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif { /* * bus_teardown_intr can not be executed right here as it wants * to run on certain CPU to interacts with LAPIC while suspend * thread is bound to CPU0. So run it from taskqueue context. */ #ifdef IICHID_SAMPLING #define suspend_thread sc->taskqueue #else #define suspend_thread taskqueue_thread #endif taskqueue_enqueue(suspend_thread, &sc->suspend_task); taskqueue_drain(suspend_thread, &sc->suspend_task); } return (0); } static int iichid_resume(device_t dev) { struct iichid_softc *sc; int error; sc = device_get_softc(dev); #ifdef IICHID_SAMPLING if (sc->sampling_rate_slow < 0) #endif iichid_setup_interrupt(sc); DPRINTF(sc, "Resume called, setting device to power_state 0\n"); error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON); if (error != 0) DPRINTF(sc, "Could not set power_state, error: %d\n", error); else DPRINTF(sc, "Successfully set power_state\n"); (void)bus_generic_resume(dev); return (0); } static device_method_t iichid_methods[] = { DEVMETHOD(device_probe, iichid_probe), DEVMETHOD(device_attach, iichid_attach), DEVMETHOD(device_detach, iichid_detach), DEVMETHOD(device_suspend, iichid_suspend), DEVMETHOD(device_resume, iichid_resume), DEVMETHOD(hid_intr_setup, iichid_intr_setup), DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup), DEVMETHOD(hid_intr_start, iichid_intr_start), DEVMETHOD(hid_intr_stop, iichid_intr_stop), DEVMETHOD(hid_intr_poll, iichid_intr_poll), /* HID interface */ DEVMETHOD(hid_get_rdesc, iichid_get_rdesc), DEVMETHOD(hid_read, iichid_read), DEVMETHOD(hid_write, iichid_write), DEVMETHOD(hid_get_report, iichid_get_report), DEVMETHOD(hid_set_report, iichid_set_report), DEVMETHOD(hid_set_idle, iichid_set_idle), DEVMETHOD(hid_set_protocol, iichid_set_protocol), DEVMETHOD(hid_ioctl, iichid_ioctl), DEVMETHOD_END }; static driver_t iichid_driver = { .name = "iichid", .methods = iichid_methods, .size = sizeof(struct iichid_softc), }; DRIVER_MODULE(iichid, iicbus, iichid_driver, NULL, NULL); MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); MODULE_DEPEND(iichid, acpi, 1, 1, 1); MODULE_DEPEND(iichid, hid, 1, 1, 1); MODULE_DEPEND(iichid, hidbus, 1, 1, 1); MODULE_VERSION(iichid, 1); IICBUS_ACPI_PNP_INFO(iichid_ids); diff --git a/sys/dev/usb/input/usbhid.c b/sys/dev/usb/input/usbhid.c index 95be0b647da9..a88d2cfac1c2 100644 --- a/sys/dev/usb/input/usbhid.c +++ b/sys/dev/usb/input/usbhid.c @@ -1,898 +1,903 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * Copyright (c) 2019 Vladimir Kondratyev * * This code is derived from software contributed to The NetBSD Foundation * by Lennart Augustsson (lennart@augustsson.net) at * Carlstedt Research & Technology. * * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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$"); /* * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define USB_DEBUG_VAR usbhid_debug #include #include #include "hid_if.h" static SYSCTL_NODE(_hw_usb, OID_AUTO, usbhid, CTLFLAG_RW, 0, "USB usbhid"); static int usbhid_enable = 0; SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, enable, CTLFLAG_RWTUN, &usbhid_enable, 0, "Enable usbhid and prefer it to other USB HID drivers"); #ifdef USB_DEBUG static int usbhid_debug = 0; SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, debug, CTLFLAG_RWTUN, &usbhid_debug, 0, "Debug level"); #endif /* Second set of USB transfers for polling mode */ #define POLL_XFER(xfer) ((xfer) + USBHID_N_TRANSFER) enum { USBHID_INTR_OUT_DT, USBHID_INTR_IN_DT, USBHID_CTRL_DT, USBHID_N_TRANSFER, }; struct usbhid_xfer_ctx; typedef int usbhid_callback_t(struct usbhid_xfer_ctx *xfer_ctx); union usbhid_device_request { struct { /* INTR xfers */ uint16_t maxlen; uint16_t actlen; } intr; struct usb_device_request ctrl; /* CTRL xfers */ }; /* Syncronous USB transfer context */ struct usbhid_xfer_ctx { union usbhid_device_request req; uint8_t *buf; int error; usbhid_callback_t *cb; void *cb_ctx; int waiters; bool influx; }; struct usbhid_softc { hid_intr_t *sc_intr_handler; void *sc_intr_ctx; void *sc_intr_buf; struct hid_device_info sc_hw; struct mtx sc_mtx; struct usb_config sc_config[USBHID_N_TRANSFER]; struct usb_xfer *sc_xfer[POLL_XFER(USBHID_N_TRANSFER)]; struct usbhid_xfer_ctx sc_xfer_ctx[POLL_XFER(USBHID_N_TRANSFER)]; bool sc_can_poll; struct usb_device *sc_udev; uint8_t sc_iface_no; uint8_t sc_iface_index; }; /* prototypes */ static device_probe_t usbhid_probe; static device_attach_t usbhid_attach; static device_detach_t usbhid_detach; static usb_callback_t usbhid_intr_out_callback; static usb_callback_t usbhid_intr_in_callback; static usb_callback_t usbhid_ctrl_callback; static usbhid_callback_t usbhid_intr_handler_cb; static usbhid_callback_t usbhid_sync_wakeup_cb; static void usbhid_intr_out_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int len; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: case USB_ST_SETUP: tr_setup: len = xfer_ctx->req.intr.maxlen; if (len == 0) { if (USB_IN_POLLING_MODE_FUNC()) xfer_ctx->error = 0; return; } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, xfer_ctx->buf, len); usbd_xfer_set_frame_len(xfer, 0, len); usbd_transfer_submit(xfer); xfer_ctx->req.intr.maxlen = 0; if (USB_IN_POLLING_MODE_FUNC()) return; xfer_ctx->error = 0; goto tr_exit; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto tr_setup; } xfer_ctx->error = EIO; tr_exit: (void)xfer_ctx->cb(xfer_ctx); return; } } static void usbhid_intr_in_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_page_cache *pc; int actlen; switch (USB_GET_STATE(xfer)) { case USB_ST_TRANSFERRED: DPRINTF("transferred!\n"); usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, 0, xfer_ctx->buf, actlen); xfer_ctx->req.intr.actlen = actlen; if (xfer_ctx->cb(xfer_ctx) != 0) return; case USB_ST_SETUP: re_submit: usbd_xfer_set_frame_len(xfer, 0, xfer_ctx->req.intr.maxlen); usbd_transfer_submit(xfer); return; default: /* Error */ if (error != USB_ERR_CANCELLED) { /* try to clear stall first */ usbd_xfer_set_stall(xfer); goto re_submit; } return; } } static void usbhid_ctrl_callback(struct usb_xfer *xfer, usb_error_t error) { struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); struct usb_device_request *req = &xfer_ctx->req.ctrl; struct usb_page_cache *pc; int len = UGETW(req->wLength); bool is_rd = (req->bmRequestType & UT_READ) != 0; switch (USB_GET_STATE(xfer)) { case USB_ST_SETUP: if (!is_rd && len != 0) { pc = usbd_xfer_get_frame(xfer, 1); usbd_copy_in(pc, 0, xfer_ctx->buf, len); } pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_in(pc, 0, req, sizeof(*req)); usbd_xfer_set_frame_len(xfer, 0, sizeof(*req)); if (len != 0) usbd_xfer_set_frame_len(xfer, 1, len); usbd_xfer_set_frames(xfer, len != 0 ? 2 : 1); usbd_transfer_submit(xfer); return; case USB_ST_TRANSFERRED: if (is_rd && len != 0) { pc = usbd_xfer_get_frame(xfer, 0); usbd_copy_out(pc, sizeof(*req), xfer_ctx->buf, len); } xfer_ctx->error = 0; goto tr_exit; default: /* Error */ /* bomb out */ DPRINTFN(1, "error=%s\n", usbd_errstr(error)); xfer_ctx->error = EIO; tr_exit: (void)xfer_ctx->cb(xfer_ctx); return; } } static int usbhid_intr_handler_cb(struct usbhid_xfer_ctx *xfer_ctx) { struct usbhid_softc *sc = xfer_ctx->cb_ctx; sc->sc_intr_handler(sc->sc_intr_ctx, xfer_ctx->buf, xfer_ctx->req.intr.actlen); return (0); } static int usbhid_sync_wakeup_cb(struct usbhid_xfer_ctx *xfer_ctx) { if (!USB_IN_POLLING_MODE_FUNC()) wakeup(xfer_ctx->cb_ctx); return (ECANCELED); } static const struct usb_config usbhid_config[USBHID_N_TRANSFER] = { [USBHID_INTR_OUT_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_OUT, .flags = {.pipe_bof = 1,.proxy_buffer = 1}, .callback = &usbhid_intr_out_callback, }, [USBHID_INTR_IN_DT] = { .type = UE_INTERRUPT, .endpoint = UE_ADDR_ANY, .direction = UE_DIR_IN, .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, .callback = &usbhid_intr_in_callback, }, [USBHID_CTRL_DT] = { .type = UE_CONTROL, .endpoint = 0x00, /* Control pipe */ .direction = UE_DIR_ANY, .flags = {.proxy_buffer = 1}, .callback = &usbhid_ctrl_callback, .timeout = 1000, /* 1 second */ }, }; static inline usb_frlength_t usbhid_xfer_max_len(struct usb_xfer *xfer) { return (xfer == NULL ? 0 : usbd_xfer_max_len(xfer)); } static inline int usbhid_xfer_check_len(struct usbhid_softc* sc, int xfer_idx, hid_size_t len) { if (USB_IN_POLLING_MODE_FUNC()) xfer_idx = POLL_XFER(xfer_idx); if (sc->sc_xfer[xfer_idx] == NULL) return (ENODEV); if (len > usbd_xfer_max_len(sc->sc_xfer[xfer_idx])) return (ENOBUFS); return (0); } static void -usbhid_intr_setup(device_t dev, hid_intr_t intr, void *context, - struct hid_rdesc_info *rdesc) +usbhid_intr_setup(device_t dev, device_t child __unused, hid_intr_t intr, + void *context, struct hid_rdesc_info *rdesc) { struct usbhid_softc* sc = device_get_softc(dev); uint16_t n; bool nowrite; int error; nowrite = hid_test_quirk(&sc->sc_hw, HQ_NOWRITE); /* * Setup the USB transfers one by one, so they are memory independent * which allows for handling panics triggered by the HID drivers * itself, typically by hkbd via CTRL+ALT+ESC sequences. Or if the HID * keyboard driver was processing a key at the moment of panic. */ if (intr == NULL) { if (sc->sc_can_poll) return; for (n = 0; n != USBHID_N_TRANSFER; n++) { if (nowrite && n == USBHID_INTR_OUT_DT) continue; error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, sc->sc_xfer + POLL_XFER(n), sc->sc_config + n, 1, (void *)(sc->sc_xfer_ctx + POLL_XFER(n)), &sc->sc_mtx); if (error) DPRINTF("xfer %d setup error=%s\n", n, usbd_errstr(error)); } mtx_lock(&sc->sc_mtx); if (sc->sc_xfer[USBHID_INTR_IN_DT] != NULL && sc->sc_xfer[USBHID_INTR_IN_DT]->flags_int.started) usbd_transfer_start( sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); mtx_unlock(&sc->sc_mtx); sc->sc_can_poll = true; return; } sc->sc_intr_handler = intr; sc->sc_intr_ctx = context; bcopy(usbhid_config, sc->sc_config, sizeof(usbhid_config)); bzero(sc->sc_xfer, sizeof(sc->sc_xfer)); /* Set buffer sizes to match HID report sizes */ sc->sc_config[USBHID_INTR_OUT_DT].bufsize = rdesc->osize; sc->sc_config[USBHID_INTR_IN_DT].bufsize = rdesc->isize; sc->sc_config[USBHID_CTRL_DT].bufsize = MAX(rdesc->isize, MAX(rdesc->osize, rdesc->fsize)); for (n = 0; n != USBHID_N_TRANSFER; n++) { if (nowrite && n == USBHID_INTR_OUT_DT) continue; error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, sc->sc_xfer + n, sc->sc_config + n, 1, (void *)(sc->sc_xfer_ctx + n), &sc->sc_mtx); if (error) DPRINTF("xfer %d setup error=%s\n", n, usbd_errstr(error)); } rdesc->rdsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]); rdesc->grsize = usbhid_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]); rdesc->srsize = rdesc->grsize; rdesc->wrsize = nowrite ? rdesc->srsize : usbhid_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]); sc->sc_intr_buf = malloc(rdesc->rdsize, M_USBDEV, M_ZERO | M_WAITOK); } static void -usbhid_intr_unsetup(device_t dev) +usbhid_intr_unsetup(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); usbd_transfer_unsetup(sc->sc_xfer, USBHID_N_TRANSFER); if (sc->sc_can_poll) usbd_transfer_unsetup( sc->sc_xfer, POLL_XFER(USBHID_N_TRANSFER)); sc->sc_can_poll = false; free(sc->sc_intr_buf, M_USBDEV); } static int -usbhid_intr_start(device_t dev) +usbhid_intr_start(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); if (sc->sc_xfer[USBHID_INTR_IN_DT] == NULL) return (ENODEV); mtx_lock(&sc->sc_mtx); sc->sc_xfer_ctx[USBHID_INTR_IN_DT] = (struct usbhid_xfer_ctx) { .req.intr.maxlen = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]), .cb = usbhid_intr_handler_cb, .cb_ctx = sc, .buf = sc->sc_intr_buf, }; sc->sc_xfer_ctx[POLL_XFER(USBHID_INTR_IN_DT)] = (struct usbhid_xfer_ctx) { .req.intr.maxlen = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]), .cb = usbhid_intr_handler_cb, .cb_ctx = sc, .buf = sc->sc_intr_buf, }; usbd_transfer_start(sc->sc_xfer[USBHID_INTR_IN_DT]); if (sc->sc_can_poll) usbd_transfer_start(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); mtx_unlock(&sc->sc_mtx); return (0); } static int -usbhid_intr_stop(device_t dev) +usbhid_intr_stop(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_IN_DT]); usbd_transfer_drain(sc->sc_xfer[USBHID_INTR_OUT_DT]); if (sc->sc_can_poll) usbd_transfer_drain(sc->sc_xfer[POLL_XFER(USBHID_INTR_IN_DT)]); return (0); } static void -usbhid_intr_poll(device_t dev) +usbhid_intr_poll(device_t dev, device_t child __unused) { struct usbhid_softc* sc = device_get_softc(dev); MPASS(sc->sc_can_poll); usbd_transfer_poll(sc->sc_xfer + USBHID_INTR_IN_DT, 1); usbd_transfer_poll(sc->sc_xfer + POLL_XFER(USBHID_INTR_IN_DT), 1); } /* * HID interface */ static int usbhid_sync_xfer(struct usbhid_softc* sc, int xfer_idx, union usbhid_device_request *req, void *buf) { int error, timeout; struct usbhid_xfer_ctx *xfer_ctx; xfer_ctx = sc->sc_xfer_ctx + xfer_idx; if (USB_IN_POLLING_MODE_FUNC()) { xfer_ctx = POLL_XFER(xfer_ctx); xfer_idx = POLL_XFER(xfer_idx); } else { mtx_lock(&sc->sc_mtx); ++xfer_ctx->waiters; while (xfer_ctx->influx) mtx_sleep(&xfer_ctx->waiters, &sc->sc_mtx, 0, "usbhid wt", 0); --xfer_ctx->waiters; xfer_ctx->influx = true; } xfer_ctx->buf = buf; xfer_ctx->req = *req; xfer_ctx->error = ETIMEDOUT; xfer_ctx->cb = &usbhid_sync_wakeup_cb; xfer_ctx->cb_ctx = xfer_ctx; timeout = USB_DEFAULT_TIMEOUT; usbd_transfer_start(sc->sc_xfer[xfer_idx]); if (USB_IN_POLLING_MODE_FUNC()) while (timeout > 0 && xfer_ctx->error == ETIMEDOUT) { usbd_transfer_poll(sc->sc_xfer + xfer_idx, 1); DELAY(1000); timeout--; } else msleep_sbt(xfer_ctx, &sc->sc_mtx, 0, "usbhid io", SBT_1MS * timeout, 0, C_HARDCLOCK); /* Perform usbhid_write() asyncronously to improve pipelining */ if (USB_IN_POLLING_MODE_FUNC() || xfer_ctx->error != 0 || sc->sc_config[xfer_idx].type != UE_INTERRUPT || sc->sc_config[xfer_idx].direction != UE_DIR_OUT) usbd_transfer_stop(sc->sc_xfer[xfer_idx]); error = xfer_ctx->error; if (error == 0) *req = xfer_ctx->req; if (!USB_IN_POLLING_MODE_FUNC()) { xfer_ctx->influx = false; if (xfer_ctx->waiters != 0) wakeup_one(&xfer_ctx->waiters); mtx_unlock(&sc->sc_mtx); } if (error) DPRINTF("USB IO error:%d\n", error); return (error); } static int -usbhid_get_rdesc(device_t dev, void *buf, hid_size_t len) +usbhid_get_rdesc(device_t dev, device_t child __unused, void *buf, + hid_size_t len) { struct usbhid_softc* sc = device_get_softc(dev); int error; error = usbd_req_get_report_descriptor(sc->sc_udev, NULL, buf, len, sc->sc_iface_index); if (error) DPRINTF("no report descriptor: %s\n", usbd_errstr(error)); return (error == 0 ? 0 : ENXIO); } static int -usbhid_get_report(device_t dev, void *buf, hid_size_t maxlen, - hid_size_t *actlen, uint8_t type, uint8_t id) +usbhid_get_report(device_t dev, device_t child __unused, void *buf, + hid_size_t maxlen, hid_size_t *actlen, uint8_t type, uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, maxlen); if (error) return (error); req.ctrl.bmRequestType = UT_READ_CLASS_INTERFACE; req.ctrl.bRequest = UR_GET_REPORT; USETW2(req.ctrl.wValue, type, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, maxlen); error = usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, buf); if (!error && actlen != NULL) *actlen = maxlen; return (error); } static int -usbhid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type, - uint8_t id) +usbhid_set_report(device_t dev, device_t child __unused, const void *buf, + hid_size_t len, uint8_t type, uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, len); if (error) return (error); req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_REPORT; USETW2(req.ctrl.wValue, type, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, len); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, __DECONST(void *, buf))); } static int -usbhid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen) +usbhid_read(device_t dev, device_t child __unused, void *buf, + hid_size_t maxlen, hid_size_t *actlen) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_INTR_IN_DT, maxlen); if (error) return (error); req.intr.maxlen = maxlen; error = usbhid_sync_xfer(sc, USBHID_INTR_IN_DT, &req, buf); if (error == 0 && actlen != NULL) *actlen = req.intr.actlen; return (error); } static int -usbhid_write(device_t dev, const void *buf, hid_size_t len) +usbhid_write(device_t dev, device_t child __unused, const void *buf, + hid_size_t len) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_INTR_OUT_DT, len); if (error) return (error); req.intr.maxlen = len; return (usbhid_sync_xfer(sc, USBHID_INTR_OUT_DT, &req, __DECONST(void *, buf))); } static int -usbhid_set_idle(device_t dev, uint16_t duration, uint8_t id) +usbhid_set_idle(device_t dev, device_t child __unused, uint16_t duration, + uint8_t id) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0); if (error) return (error); /* Duration is measured in 4 milliseconds per unit. */ req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_IDLE; USETW2(req.ctrl.wValue, (duration + 3) / 4, id); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, 0); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); } static int -usbhid_set_protocol(device_t dev, uint16_t protocol) +usbhid_set_protocol(device_t dev, device_t child __unused, uint16_t protocol) { struct usbhid_softc* sc = device_get_softc(dev); union usbhid_device_request req; int error; error = usbhid_xfer_check_len(sc, USBHID_CTRL_DT, 0); if (error) return (error); req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; req.ctrl.bRequest = UR_SET_PROTOCOL; USETW(req.ctrl.wValue, protocol); req.ctrl.wIndex[0] = sc->sc_iface_no; req.ctrl.wIndex[1] = 0; USETW(req.ctrl.wLength, 0); return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); } static int -usbhid_ioctl(device_t dev, unsigned long cmd, uintptr_t data) +usbhid_ioctl(device_t dev, device_t child __unused, unsigned long cmd, + uintptr_t data) { struct usbhid_softc* sc = device_get_softc(dev); struct usb_ctl_request *ucr; union usbhid_device_request req; int error; switch (cmd) { case USB_REQUEST: ucr = (struct usb_ctl_request *)data; req.ctrl = ucr->ucr_request; error = usbhid_xfer_check_len( sc, USBHID_CTRL_DT, UGETW(req.ctrl.wLength)); if (error) break; error = usb_check_request(sc->sc_udev, &req.ctrl); if (error) break; error = usbhid_sync_xfer( sc, USBHID_CTRL_DT, &req, ucr->ucr_data); if (error == 0) ucr->ucr_actlen = UGETW(req.ctrl.wLength); break; default: error = EINVAL; } return (error); } static void usbhid_init_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) { hw->idBus = BUS_USB; hw->idVendor = uaa->info.idVendor; hw->idProduct = uaa->info.idProduct; hw->idVersion = uaa->info.bcdDevice; /* Set various quirks based on usb_attach_arg */ hid_add_dynamic_quirk(hw, USB_GET_DRIVER_INFO(uaa)); } static void usbhid_fill_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) { struct usb_device *udev = uaa->device; struct usb_interface *iface = uaa->iface; struct usb_hid_descriptor *hid; struct usb_endpoint *ep; snprintf(hw->name, sizeof(hw->name), "%s %s", usb_get_manufacturer(udev), usb_get_product(udev)); strlcpy(hw->serial, usb_get_serial(udev), sizeof(hw->serial)); if (uaa->info.bInterfaceClass == UICLASS_HID && iface != NULL && iface->idesc != NULL) { hid = hid_get_descriptor_from_usb( usbd_get_config_descriptor(udev), iface->idesc); if (hid != NULL) hw->rdescsize = UGETW(hid->descrs[0].wDescriptorLength); } /* See if there is a interrupt out endpoint. */ ep = usbd_get_endpoint(udev, uaa->info.bIfaceIndex, usbhid_config + USBHID_INTR_OUT_DT); if (ep == NULL || ep->methods == NULL) hid_add_dynamic_quirk(hw, HQ_NOWRITE); } static const STRUCT_USB_HOST_ID usbhid_devs[] = { /* the Xbox 360 gamepad doesn't use the HID class */ {USB_IFACE_CLASS(UICLASS_VENDOR), USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD), USB_DRIVER_INFO(HQ_IS_XBOX360GP)}, /* HID keyboard with boot protocol support */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD), USB_DRIVER_INFO(HQ_HAS_KBD_BOOTPROTO)}, /* HID mouse with boot protocol support */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), USB_IFACE_PROTOCOL(UIPROTO_MOUSE), USB_DRIVER_INFO(HQ_HAS_MS_BOOTPROTO)}, /* generic HID class */ {USB_IFACE_CLASS(UICLASS_HID), USB_DRIVER_INFO(HQ_NONE)}, }; static int usbhid_probe(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbhid_softc *sc = device_get_softc(dev); int error; DPRINTFN(11, "\n"); if (usbhid_enable == 0) return (ENXIO); if (uaa->usb_mode != USB_MODE_HOST) return (ENXIO); error = usbd_lookup_id_by_uaa(usbhid_devs, sizeof(usbhid_devs), uaa); if (error) return (error); if (usb_test_quirk(uaa, UQ_HID_IGNORE)) return (ENXIO); /* * Setup temporary hid_device_info so that we can figure out some * basic quirks for this device. */ usbhid_init_device_info(uaa, &sc->sc_hw); if (hid_test_quirk(&sc->sc_hw, HQ_HID_IGNORE)) return (ENXIO); return (BUS_PROBE_DEFAULT + 1); } static int usbhid_attach(device_t dev) { struct usb_attach_arg *uaa = device_get_ivars(dev); struct usbhid_softc *sc = device_get_softc(dev); device_t child; int error = 0; DPRINTFN(10, "sc=%p\n", sc); device_set_usb_desc(dev); sc->sc_udev = uaa->device; sc->sc_iface_no = uaa->info.bIfaceNum; sc->sc_iface_index = uaa->info.bIfaceIndex; usbhid_fill_device_info(uaa, &sc->sc_hw); error = usbd_req_set_idle(uaa->device, NULL, uaa->info.bIfaceIndex, 0, 0); if (error) DPRINTF("set idle failed, error=%s (ignored)\n", usbd_errstr(error)); mtx_init(&sc->sc_mtx, "usbhid lock", NULL, MTX_DEF); child = device_add_child(dev, "hidbus", -1); if (child == NULL) { device_printf(dev, "Could not add hidbus device\n"); usbhid_detach(dev); return (ENOMEM); } device_set_ivars(child, &sc->sc_hw); error = bus_generic_attach(dev); if (error) { device_printf(dev, "failed to attach child: %d\n", error); usbhid_detach(dev); return (error); } return (0); /* success */ } static int usbhid_detach(device_t dev) { struct usbhid_softc *sc = device_get_softc(dev); device_delete_children(dev); mtx_destroy(&sc->sc_mtx); return (0); } static device_method_t usbhid_methods[] = { DEVMETHOD(device_probe, usbhid_probe), DEVMETHOD(device_attach, usbhid_attach), DEVMETHOD(device_detach, usbhid_detach), DEVMETHOD(hid_intr_setup, usbhid_intr_setup), DEVMETHOD(hid_intr_unsetup, usbhid_intr_unsetup), DEVMETHOD(hid_intr_start, usbhid_intr_start), DEVMETHOD(hid_intr_stop, usbhid_intr_stop), DEVMETHOD(hid_intr_poll, usbhid_intr_poll), /* HID interface */ DEVMETHOD(hid_get_rdesc, usbhid_get_rdesc), DEVMETHOD(hid_read, usbhid_read), DEVMETHOD(hid_write, usbhid_write), DEVMETHOD(hid_get_report, usbhid_get_report), DEVMETHOD(hid_set_report, usbhid_set_report), DEVMETHOD(hid_set_idle, usbhid_set_idle), DEVMETHOD(hid_set_protocol, usbhid_set_protocol), DEVMETHOD(hid_ioctl, usbhid_ioctl), DEVMETHOD_END }; static driver_t usbhid_driver = { .name = "usbhid", .methods = usbhid_methods, .size = sizeof(struct usbhid_softc), }; DRIVER_MODULE(usbhid, uhub, usbhid_driver, NULL, NULL); MODULE_DEPEND(usbhid, usb, 1, 1, 1); MODULE_DEPEND(usbhid, hid, 1, 1, 1); MODULE_DEPEND(usbhid, hidbus, 1, 1, 1); MODULE_VERSION(usbhid, 1); USB_PNP_HOST_INFO(usbhid_devs);