diff --git a/share/man/man4/hidquirk.4 b/share/man/man4/hidquirk.4 index aa78e189765c..cbe0e33c934c 100644 --- a/share/man/man4/hidquirk.4 +++ b/share/man/man4/hidquirk.4 @@ -1,143 +1,145 @@ .\" .\" Copyright (c) 2010 AnyWi Technologies .\" All rights reserved. .\" .\" Permission to use, copy, modify, and distribute this software for any .\" purpose with or without fee is hereby granted, provided that the above .\" copyright notice and this permission notice appear in all copies. .\" .\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES .\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF .\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR .\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES .\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" .\" $FreeBSD$ .\" .Dd September 16, 2020 .Dt HIDQUIRK 4 .Os .Sh NAME .Nm hidquirk .Nd HID quirks module .Sh SYNOPSIS To compile this module into the kernel, place the following line in your kernel configuration file: .Bd -ragged -offset indent .Cd "device hid" .Ed .Pp Alternatively, to load the module at boot time, place the following line in .Xr loader.conf 5 : .Bd -literal -offset indent hidquirk_load="YES" .Ed .Sh DESCRIPTION The .Nm module provides support for adding quirks for HID devices .Bl -tag -width Ds .It HQ_HID_IGNORE device should be ignored by hid class .It HQ_KBD_BOOTPROTO device should set the boot protocol .It HQ_MS_BOOTPROTO device should set the boot protocol .It HQ_MS_BAD_CLASS doesn't identify properly .It HQ_MS_LEADING_BYTE mouse sends an unknown leading byte .It HQ_MS_REVZ mouse has Z-axis reversed +.It HQ_MS_VENDOR_BTN +mouse has buttons in vendor usage page .It HQ_SPUR_BUT_UP spurious mouse button up events .It HQ_MT_TIMESTAMP Multitouch device exports HW timestamps .Dv 0x1b5a01 .El .Pp See .Pa /sys/dev/hid/hidquirk.h for the complete list of supported quirks. .Sh LOADER TUNABLE The following tunable can be set at the .Xr loader 8 prompt before booting the kernel, or stored in .Xr loader.conf 5 . .Bl -tag -width indent .It Va hw.hid.quirk.%d The value is a string whose format is: .Bd -literal -offset indent .Qo BusId VendorId ProductId LowRevision HighRevision HQ_QUIRK,... Qc .Ed .Pp Installs the quirks .Ic HQ_QUIRK,... for all HID devices matching .Ic BusId and .Ic VendorId and .Ic ProductId which have a hardware revision between and including .Ic LowRevision and .Ic HighRevision . .Pp .Ic BusId , .Ic VendorId , .Ic ProductId , .Ic LowRevision and .Ic HighRevision are all 16 bits numbers which can be decimal or hexadecimal based. .Pp A maximum of 100 variables .Ic hw.hid.quirk.0, .1, ..., .99 can be defined. .Pp If a matching entry is found in the kernel's internal quirks table, it is replaced by the new definition. .Pp Else a new entry is created given that the quirk table is not full. .Pp The kernel iterates over the .Ic hw.hid.quirk.N variables starting at .Ic N = 0 and stops at .Ic N = 99 or the first non-existing one. .El .Sh EXAMPLES To install a quirk at boot time, place one or several lines like the following in .Xr loader.conf 5 : .Bd -literal -offset indent hw.hid.quirk.0="0x18 0x6cb 0x1941 0 0xffff HQ_MT_TIMESTAMP" .Ed .Sh HISTORY The .Nm module appeared in .Fx 13.0 . .Sh AUTHORS .An -nosplit The .Nm driver was written by .An Hans Petter Selasky Aq Mt hselasky@FreeBSD.org for .Xr usb 4 subsystem and adopted to .Xr hid 4 by .An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . This manual page is based on .Xr usb_quirk 4 manual page written by .An Nick Hibma Aq Mt n_hibma@FreeBSD.org . diff --git a/sys/dev/hid/hidquirk.c b/sys/dev/hid/hidquirk.c index 7c28e240582b..dfb97a3e5597 100644 --- a/sys/dev/hid/hidquirk.c +++ b/sys/dev/hid/hidquirk.c @@ -1,437 +1,439 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-NetBSD * * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. All rights reserved. * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hid_debug #include #include #include "usbdevs.h" MODULE_DEPEND(hidquirk, hid, 1, 1, 1); MODULE_VERSION(hidquirk, 1); #define HID_DEV_QUIRKS_MAX 384 #define HID_SUB_QUIRKS_MAX 8 #define HID_QUIRK_ENVROOT "hw.hid.quirk." struct hidquirk_entry { uint16_t bus; uint16_t vid; uint16_t pid; uint16_t lo_rev; uint16_t hi_rev; uint16_t quirks[HID_SUB_QUIRKS_MAX]; }; static struct mtx hidquirk_mtx; #define HID_QUIRK_VP(b,v,p,l,h,...) \ { .bus = (b), .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), \ .quirks = { __VA_ARGS__ } } #define USB_QUIRK(v,p,l,h,...) \ HID_QUIRK_VP(BUS_USB, USB_VENDOR_##v, USB_PRODUCT_##v##_##p, l, h, __VA_ARGS__) static struct hidquirk_entry hidquirks[HID_DEV_QUIRKS_MAX] = { USB_QUIRK(ASUS, LCM, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(QTRONIX, 980N, 0x110, 0x110, HQ_SPUR_BUT_UP), USB_QUIRK(ALCOR2, KBD_HUB, 0x001, 0x001, HQ_SPUR_BUT_UP), USB_QUIRK(LOGITECH, G510S, 0x0000, 0xFFFF, HQ_KBD_BOOTPROTO), /* Devices which should be ignored by usbhid */ USB_QUIRK(APC, UPS, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6H375USB, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C550AVR, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C1250TWRK, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C1500TWRK, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C900UNV, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C100UNV, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C120UNV, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C800UNV, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(BELKIN, F6C1100UNV, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(CYBERPOWER, BC900D, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(CYBERPOWER, 1500CAVRLCD, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(CYBERPOWER, OR2200LCDRM2U, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(DELL2, VARIOUS_UPS, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(CYPRESS, SILVERSHIELD, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(DELORME, EARTHMATE, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(DREAMLINK, DL100B, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(MICROCHIP, PICOLCD20X2, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(MICROCHIP, PICOLCD4X20, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(LIEBERT, POWERSURE_PXT, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(LIEBERT2, PSI1000, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(LIEBERT2, POWERSURE_PSA, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(MGE, UPS1, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(MGE, UPS2, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(POWERCOM, IMPERIAL_SERIES, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(POWERCOM, SMART_KING_PRO, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(POWERCOM, WOW, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(POWERCOM, VANGUARD, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(POWERCOM, BLACK_KNIGHT_PRO, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, AVR550U, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, AVR750U, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, ECO550UPS, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, T750_INTL, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, RT_2200_INTL, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, OMNI1000LCD, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, OMNI900LCD, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, SMART_2200RMXL2U, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, UPS_3014, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, SU6000RT4U, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA_2, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(APPLE, IPHONE, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(APPLE, IPHONE_3G, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(MEGATEC, UPS, 0x0000, 0xffff, HQ_HID_IGNORE), /* Devices which should be ignored by both ukbd and uhid */ USB_QUIRK(CYPRESS, WISPY1A, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(METAGEEK, WISPY1B, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(METAGEEK, WISPY24X, 0x0000, 0xffff, HQ_HID_IGNORE), USB_QUIRK(METAGEEK2, WISPYDBX, 0x0000, 0xffff, HQ_HID_IGNORE), /* MS keyboards do weird things */ USB_QUIRK(MICROSOFT, NATURAL4000, 0x0000, 0xFFFF, HQ_KBD_BOOTPROTO), USB_QUIRK(MICROSOFT, WLINTELLIMOUSE, 0x0000, 0xffff, HQ_MS_LEADING_BYTE), /* Quirk for Corsair Vengeance K60 keyboard */ USB_QUIRK(CORSAIR, K60, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), /* Quirk for Corsair Gaming K68 keyboard */ USB_QUIRK(CORSAIR, K68, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), /* Quirk for Corsair Vengeance K70 keyboard */ USB_QUIRK(CORSAIR, K70, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), /* Quirk for Corsair K70 RGB keyboard */ USB_QUIRK(CORSAIR, K70_RGB, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), /* Quirk for Corsair STRAFE Gaming keyboard */ USB_QUIRK(CORSAIR, STRAFE, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), USB_QUIRK(CORSAIR, STRAFE2, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), /* Holtek USB gaming keyboard */ USB_QUIRK(HOLTEK, F85, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Quirk for Kensington Slimblade Trackball */ + USB_QUIRK(KENSINGTON, SLIMBLADE, 0x0000, 0xffff, HQ_MS_VENDOR_BTN), }; #undef HID_QUIRK_VP #undef USB_QUIRK /* hidquirk.h exposes only HID_QUIRK_LIST macro when HQ() is defined */ #define HQ(x) [HQ_##x] = "HQ_"#x #include "hidquirk.h" static const char *hidquirk_str[HID_QUIRK_MAX] = { HID_QUIRK_LIST() }; #undef HQ static hid_test_quirk_t hid_test_quirk_by_info; /*------------------------------------------------------------------------* * hidquirkstr * * This function converts an USB quirk code into a string. *------------------------------------------------------------------------*/ static const char * hidquirkstr(uint16_t quirk) { return ((quirk < HID_QUIRK_MAX && hidquirk_str[quirk] != NULL) ? hidquirk_str[quirk] : "HQ_UNKNOWN"); } /*------------------------------------------------------------------------* * hid_strquirk * * This function converts a string into a HID quirk code. * * Returns: * Less than HID_QUIRK_MAX: Quirk code * Else: Quirk code not found *------------------------------------------------------------------------*/ static uint16_t hid_strquirk(const char *str, size_t len) { const char *quirk; uint16_t x; for (x = 0; x != HID_QUIRK_MAX; x++) { quirk = hidquirkstr(x); if (strncmp(str, quirk, len) == 0 && quirk[len] == 0) break; } return (x); } /*------------------------------------------------------------------------* * hid_test_quirk_by_info * * Returns: * false: Quirk not found * true: Quirk found *------------------------------------------------------------------------*/ bool hid_test_quirk_by_info(const struct hid_device_info *info, uint16_t quirk) { uint16_t x; uint16_t y; if (quirk == HQ_NONE) goto done; mtx_lock(&hidquirk_mtx); for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { /* see if quirk information does not match */ if ((hidquirks[x].bus != info->idBus) || (hidquirks[x].vid != info->idVendor) || (hidquirks[x].lo_rev > info->idVersion) || (hidquirks[x].hi_rev < info->idVersion)) { continue; } /* see if quirk only should match vendor ID */ if (hidquirks[x].pid != info->idProduct) { if (hidquirks[x].pid != 0) continue; for (y = 0; y != HID_SUB_QUIRKS_MAX; y++) { if (hidquirks[x].quirks[y] == HQ_MATCH_VENDOR_ONLY) break; } if (y == HID_SUB_QUIRKS_MAX) continue; } /* lookup quirk */ for (y = 0; y != HID_SUB_QUIRKS_MAX; y++) { if (hidquirks[x].quirks[y] == quirk) { mtx_unlock(&hidquirk_mtx); DPRINTF("Found quirk '%s'.\n", hidquirkstr(quirk)); return (true); } } } mtx_unlock(&hidquirk_mtx); done: return (false); /* no quirk match */ } static struct hidquirk_entry * hidquirk_get_entry(uint16_t bus, uint16_t vid, uint16_t pid, uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc) { uint16_t x; mtx_assert(&hidquirk_mtx, MA_OWNED); if ((bus | vid | pid | lo_rev | hi_rev) == 0) { /* all zero - special case */ return (hidquirks + HID_DEV_QUIRKS_MAX - 1); } /* search for an existing entry */ for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { /* see if quirk information does not match */ if ((hidquirks[x].bus != bus) || (hidquirks[x].vid != vid) || (hidquirks[x].pid != pid) || (hidquirks[x].lo_rev != lo_rev) || (hidquirks[x].hi_rev != hi_rev)) { continue; } return (hidquirks + x); } if (do_alloc == 0) { /* no match */ return (NULL); } /* search for a free entry */ for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { /* see if quirk information does not match */ if ((hidquirks[x].bus | hidquirks[x].vid | hidquirks[x].pid | hidquirks[x].lo_rev | hidquirks[x].hi_rev) != 0) { continue; } hidquirks[x].bus = bus; hidquirks[x].vid = vid; hidquirks[x].pid = pid; hidquirks[x].lo_rev = lo_rev; hidquirks[x].hi_rev = hi_rev; return (hidquirks + x); } /* no entry found */ return (NULL); } /*------------------------------------------------------------------------* * usb_quirk_strtou16 * * Helper function to scan a 16-bit integer. *------------------------------------------------------------------------*/ static uint16_t hidquirk_strtou16(const char **pptr, const char *name, const char *what) { unsigned long value; char *end; value = strtoul(*pptr, &end, 0); if (value > 65535 || *pptr == end || (*end != ' ' && *end != '\t')) { printf("%s: %s 16-bit %s value set to zero\n", name, what, *end == 0 ? "incomplete" : "invalid"); return (0); } *pptr = end + 1; return ((uint16_t)value); } /*------------------------------------------------------------------------* * usb_quirk_add_entry_from_str * * Add a USB quirk entry from string. * "VENDOR PRODUCT LO_REV HI_REV QUIRK[,QUIRK[,...]]" *------------------------------------------------------------------------*/ static void hidquirk_add_entry_from_str(const char *name, const char *env) { struct hidquirk_entry entry = { }; struct hidquirk_entry *new; uint16_t quirk_idx; uint16_t quirk; const char *end; /* check for invalid environment variable */ if (name == NULL || env == NULL) return; if (bootverbose) printf("Adding HID QUIRK '%s' = '%s'\n", name, env); /* parse device information */ entry.bus = hidquirk_strtou16(&env, name, "Bus ID"); entry.vid = hidquirk_strtou16(&env, name, "Vendor ID"); entry.pid = hidquirk_strtou16(&env, name, "Product ID"); entry.lo_rev = hidquirk_strtou16(&env, name, "Low revision"); entry.hi_rev = hidquirk_strtou16(&env, name, "High revision"); /* parse quirk information */ quirk_idx = 0; while (*env != 0 && quirk_idx != HID_SUB_QUIRKS_MAX) { /* skip whitespace before quirks */ while (*env == ' ' || *env == '\t') env++; /* look for quirk separation character */ end = strchr(env, ','); if (end == NULL) end = env + strlen(env); /* lookup quirk in string table */ quirk = hid_strquirk(env, end - env); if (quirk < HID_QUIRK_MAX) { entry.quirks[quirk_idx++] = quirk; } else { printf("%s: unknown HID quirk '%.*s' (skipped)\n", name, (int)(end - env), env); } env = end; /* skip quirk delimiter, if any */ if (*env != 0) env++; } /* register quirk */ if (quirk_idx != 0) { if (*env != 0) { printf("%s: Too many HID quirks, only %d allowed!\n", name, HID_SUB_QUIRKS_MAX); } mtx_lock(&hidquirk_mtx); new = hidquirk_get_entry(entry.bus, entry.vid, entry.pid, entry.lo_rev, entry.hi_rev, 1); if (new == NULL) printf("%s: HID quirks table is full!\n", name); else memcpy(new->quirks, entry.quirks, sizeof(entry.quirks)); mtx_unlock(&hidquirk_mtx); } else { printf("%s: No USB quirks found!\n", name); } } static void hidquirk_init(void *arg) { char envkey[sizeof(HID_QUIRK_ENVROOT) + 2]; /* 2 digits max, 0 to 99 */ int i; /* initialize mutex */ mtx_init(&hidquirk_mtx, "HID quirk", NULL, MTX_DEF); /* look for quirks defined by the environment variable */ for (i = 0; i != 100; i++) { snprintf(envkey, sizeof(envkey), HID_QUIRK_ENVROOT "%d", i); /* Stop at first undefined var */ if (!testenv(envkey)) break; /* parse environment variable */ hidquirk_add_entry_from_str(envkey, kern_getenv(envkey)); } /* register our function */ hid_test_quirk_p = &hid_test_quirk_by_info; } static void hidquirk_uninit(void *arg) { hid_quirk_unload(arg); /* destroy mutex */ mtx_destroy(&hidquirk_mtx); } SYSINIT(hidquirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, hidquirk_init, NULL); SYSUNINIT(hidquirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, hidquirk_uninit, NULL); diff --git a/sys/dev/hid/hidquirk.h b/sys/dev/hid/hidquirk.h index 0201f2f0746c..5f0dafb8a8a2 100644 --- a/sys/dev/hid/hidquirk.h +++ b/sys/dev/hid/hidquirk.h @@ -1,74 +1,75 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ /* * Screening of all content of this file except HID_QUIRK_LIST is a kind of * hack that allows multiple HID_QUIRK_LIST inclusion with different HQ() * wrappers. That save us splitting hidquirk.h on two header files. */ #ifndef HQ #ifndef _HID_QUIRK_H_ #define _HID_QUIRK_H_ #endif /* * Keep in sync with share/man/man4/hidquirk.4 */ #define HID_QUIRK_LIST(...) \ HQ(NONE), /* not a valid quirk */ \ \ HQ(MATCH_VENDOR_ONLY), /* match quirk on vendor only */ \ \ /* Autoquirks */ \ HQ(HAS_KBD_BOOTPROTO), /* device supports keyboard boot protocol */ \ HQ(HAS_MS_BOOTPROTO), /* device supports mouse boot protocol */ \ HQ(IS_XBOX360GP), /* device is XBox 360 GamePad */ \ HQ(NOWRITE), /* device does not support writes */ \ HQ(IICHID_SAMPLING), /* IIC backend runs in sampling mode */ \ \ /* Various quirks */ \ HQ(HID_IGNORE), /* device should be ignored by hid class */ \ HQ(KBD_BOOTPROTO), /* device should set the boot protocol */ \ HQ(MS_BOOTPROTO), /* device should set the boot protocol */ \ HQ(MS_BAD_CLASS), /* doesn't identify properly */ \ HQ(MS_LEADING_BYTE), /* mouse sends an unknown leading byte */ \ HQ(MS_REVZ), /* mouse has Z-axis reversed */ \ + HQ(MS_VENDOR_BTN), /* mouse has buttons in vendor usage page */ \ HQ(SPUR_BUT_UP), /* spurious mouse button up events */ \ HQ(MT_TIMESTAMP) /* Multitouch device exports HW timestamps */ #ifndef HQ #define HQ(x) HQ_##x enum { HID_QUIRK_LIST(), HID_QUIRK_MAX }; #undef HQ #endif /* _HID_QUIRK_H_ */ #endif /* HQ */ diff --git a/sys/dev/hid/hms.c b/sys/dev/hid/hms.c index ae0380ecb6ce..2d2b996646c2 100644 --- a/sys/dev/hid/hms.c +++ b/sys/dev/hid/hms.c @@ -1,337 +1,341 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * * Copyright (c) 2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf */ #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static const uint8_t hms_boot_desc[] = { HID_MOUSE_BOOTPROTO_DESCR() }; enum { HMS_REL_X, HMS_REL_Y, HMS_REL_Z, HMS_ABS_X, HMS_ABS_Y, HMS_ABS_Z, HMS_HWHEEL, HMS_BTN, - HMS_BTN_MS1, - HMS_BTN_MS2, HMS_FINAL_CB, }; static hidmap_cb_t hms_final_cb; #ifdef IICHID_SAMPLING static hid_intr_t hms_intr; #endif #define HMS_MAP_BUT_RG(usage_from, usage_to, code) \ { HIDMAP_KEY_RANGE(HUP_BUTTON, usage_from, usage_to, code) } #define HMS_MAP_BUT_MS(usage, code) \ { HIDMAP_KEY(HUP_MICROSOFT, usage, code) } #define HMS_MAP_ABS(usage, code) \ { HIDMAP_ABS(HUP_GENERIC_DESKTOP, usage, code) } #define HMS_MAP_REL(usage, code) \ { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code) } #define HMS_MAP_REL_REV(usage, code) \ { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code), .invert_value = true } #define HMS_MAP_REL_CN(usage, code) \ { HIDMAP_REL(HUP_CONSUMER, usage, code) } #define HMS_FINAL_CB(cb) \ { HIDMAP_FINAL_CB(&cb) } static const struct hidmap_item hms_map[] = { [HMS_REL_X] = HMS_MAP_REL(HUG_X, REL_X), [HMS_REL_Y] = HMS_MAP_REL(HUG_Y, REL_Y), [HMS_REL_Z] = HMS_MAP_REL(HUG_Z, REL_Z), [HMS_ABS_X] = HMS_MAP_ABS(HUG_X, ABS_X), [HMS_ABS_Y] = HMS_MAP_ABS(HUG_Y, ABS_Y), [HMS_ABS_Z] = HMS_MAP_ABS(HUG_Z, ABS_Z), [HMS_HWHEEL] = HMS_MAP_REL_CN(HUC_AC_PAN, REL_HWHEEL), [HMS_BTN] = HMS_MAP_BUT_RG(1, 16, BTN_MOUSE), - [HMS_BTN_MS1] = HMS_MAP_BUT_MS(1, BTN_RIGHT), - [HMS_BTN_MS2] = HMS_MAP_BUT_MS(2, BTN_MIDDLE), [HMS_FINAL_CB] = HMS_FINAL_CB(hms_final_cb), }; static const struct hidmap_item hms_map_wheel[] = { HMS_MAP_REL(HUG_WHEEL, REL_WHEEL), }; static const struct hidmap_item hms_map_wheel_rev[] = { HMS_MAP_REL_REV(HUG_WHEEL, REL_WHEEL), }; +static const struct hidmap_item hms_map_kensington_slimblade[] = { + HMS_MAP_BUT_MS(1, BTN_RIGHT), + HMS_MAP_BUT_MS(2, BTN_MIDDLE), +}; + /* A match on these entries will load hms */ static const struct hid_device_id hms_devs[] = { { HID_TLC(HUP_GENERIC_DESKTOP, HUG_POINTER) }, { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE) }, }; struct hms_softc { struct hidmap hm; HIDMAP_CAPS(caps, hms_map); #ifdef IICHID_SAMPLING bool iichid_sampling; void *last_ir; hid_size_t last_irsize; hid_size_t isize; uint32_t drift_cnt; uint32_t drift_thresh; #endif }; #ifdef IICHID_SAMPLING static void hms_intr(void *context, void *buf, hid_size_t len) { struct hidmap *hm = context; struct hms_softc *sc = device_get_softc(hm->dev); if (len > sc->isize) len = sc->isize; /* * Many I2C "compatibility" mouse devices found on touchpads continue * to return last report data in sampling mode even after touch has * been ended. That results in cursor drift. Filter out such a * reports through comparing with previous one. */ if (len == sc->last_irsize && memcmp(buf, sc->last_ir, len) == 0) { sc->drift_cnt++; if (sc->drift_thresh != 0 && sc->drift_cnt >= sc->drift_thresh) return; } else { sc->drift_cnt = 0; sc->last_irsize = len; bcopy(buf, sc->last_ir, len); } hidmap_intr(context, buf, len); } #endif static int hms_final_cb(HIDMAP_CB_ARGS) { struct hms_softc *sc = HIDMAP_CB_GET_SOFTC(); struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) { if (hidmap_test_cap(sc->caps, HMS_ABS_X) || hidmap_test_cap(sc->caps, HMS_ABS_Y)) evdev_support_prop(evdev, INPUT_PROP_DIRECT); else evdev_support_prop(evdev, INPUT_PROP_POINTER); #ifdef IICHID_SAMPLING /* Overload interrupt handler to skip identical reports */ if (sc->iichid_sampling) hidbus_set_intr(sc->hm.dev, hms_intr, &sc->hm); #endif } /* Do not execute callback at interrupt handler and detach */ return (ENOSYS); } static void hms_identify(driver_t *driver, device_t parent) { const struct hid_device_info *hw = hid_get_device_info(parent); void *d_ptr; hid_size_t d_len; int error; /* * If device claimed boot protocol support but do not have report * descriptor, load one defined in "Appendix B.2" of HID1_11.pdf */ error = hid_get_report_descr(parent, &d_ptr, &d_len); if ((error != 0 && hid_test_quirk(hw, HQ_HAS_MS_BOOTPROTO)) || (error == 0 && hid_test_quirk(hw, HQ_MS_BOOTPROTO) && hid_is_mouse(d_ptr, d_len))) (void)hid_set_report_descr(parent, hms_boot_desc, sizeof(hms_boot_desc)); } static int hms_probe(device_t dev) { struct hms_softc *sc = device_get_softc(dev); int error; error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hms_devs); if (error != 0) return (error); hidmap_set_dev(&sc->hm, dev); /* Check if report descriptor belongs to mouse */ error = HIDMAP_ADD_MAP(&sc->hm, hms_map, sc->caps); if (error != 0) return (error); /* There should be at least one X or Y axis */ if (!hidmap_test_cap(sc->caps, HMS_REL_X) && !hidmap_test_cap(sc->caps, HMS_REL_X) && !hidmap_test_cap(sc->caps, HMS_ABS_X) && !hidmap_test_cap(sc->caps, HMS_ABS_Y)) return (ENXIO); if (hidmap_test_cap(sc->caps, HMS_ABS_X) || hidmap_test_cap(sc->caps, HMS_ABS_Y)) hidbus_set_desc(dev, "Tablet"); else hidbus_set_desc(dev, "Mouse"); return (BUS_PROBE_GENERIC); } static int hms_attach(device_t dev) { struct hms_softc *sc = device_get_softc(dev); const struct hid_device_info *hw = hid_get_device_info(dev); struct hidmap_hid_item *hi; HIDMAP_CAPS(cap_wheel, hms_map_wheel); void *d_ptr; hid_size_t d_len; bool set_report_proto; int error, nbuttons = 0; /* * Set the report (non-boot) protocol if report descriptor has not been * overloaded with boot protocol report descriptor. * * Mice without boot protocol support may choose not to implement * Set_Protocol at all; Ignore any error. */ error = hid_get_report_descr(dev, &d_ptr, &d_len); set_report_proto = !(error == 0 && d_len == sizeof(hms_boot_desc) && memcmp(d_ptr, hms_boot_desc, sizeof(hms_boot_desc)) == 0); (void)hid_set_protocol(dev, set_report_proto ? 1 : 0); if (hid_test_quirk(hw, HQ_MS_REVZ)) HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel_rev, cap_wheel); else HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel, cap_wheel); + if (hid_test_quirk(hw, HQ_MS_VENDOR_BTN)) + HIDMAP_ADD_MAP(&sc->hm, hms_map_kensington_slimblade, NULL); + #ifdef IICHID_SAMPLING if (hid_test_quirk(hw, HQ_IICHID_SAMPLING) && hidmap_test_cap(sc->caps, HMS_REL_X) && hidmap_test_cap(sc->caps, HMS_REL_Y)) { sc->iichid_sampling = true; sc->isize = hid_report_size_max(d_ptr, d_len, hid_input, NULL); sc->last_ir = malloc(sc->isize, M_DEVBUF, M_WAITOK | M_ZERO); sc->drift_thresh = 2; SYSCTL_ADD_U32(device_get_sysctl_ctx(dev), SYSCTL_CHILDREN(device_get_sysctl_tree(dev)), OID_AUTO, "drift_thresh", CTLFLAG_RW, &sc->drift_thresh, 0, "drift detection threshold"); } #endif error = hidmap_attach(&sc->hm); if (error) return (error); /* Count number of input usages of variable type mapped to buttons */ for (hi = sc->hm.hid_items; hi < sc->hm.hid_items + sc->hm.nhid_items; hi++) if (hi->type == HIDMAP_TYPE_VARIABLE && hi->evtype == EV_KEY) nbuttons++; /* announce information about the mouse */ device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n", nbuttons, (hidmap_test_cap(sc->caps, HMS_REL_X) || hidmap_test_cap(sc->caps, HMS_ABS_X)) ? "X" : "", (hidmap_test_cap(sc->caps, HMS_REL_Y) || hidmap_test_cap(sc->caps, HMS_ABS_Y)) ? "Y" : "", (hidmap_test_cap(sc->caps, HMS_REL_Z) || hidmap_test_cap(sc->caps, HMS_ABS_Z)) ? "Z" : "", hidmap_test_cap(cap_wheel, 0) ? "W" : "", hidmap_test_cap(sc->caps, HMS_HWHEEL) ? "H" : "", sc->hm.hid_items[0].id); return (0); } static int hms_detach(device_t dev) { struct hms_softc *sc = device_get_softc(dev); int error; error = hidmap_detach(&sc->hm); #ifdef IICHID_SAMPLING if (error == 0) free(sc->last_ir, M_DEVBUF); #endif return (error); } static devclass_t hms_devclass; static device_method_t hms_methods[] = { DEVMETHOD(device_identify, hms_identify), DEVMETHOD(device_probe, hms_probe), DEVMETHOD(device_attach, hms_attach), DEVMETHOD(device_detach, hms_detach), DEVMETHOD_END }; DEFINE_CLASS_0(hms, hms_driver, hms_methods, sizeof(struct hms_softc)); DRIVER_MODULE(hms, hidbus, hms_driver, hms_devclass, NULL, 0); MODULE_DEPEND(hms, hid, 1, 1, 1); MODULE_DEPEND(hms, hidbus, 1, 1, 1); MODULE_DEPEND(hms, hidmap, 1, 1, 1); MODULE_DEPEND(hms, evdev, 1, 1, 1); MODULE_VERSION(hms, 1); HID_PNP_INFO(hms_devs);