diff --git a/sys/dev/hid/bcm5974.c b/sys/dev/hid/bcm5974.c index fdfc29e6b678..9a927aaffc5b 100644 --- a/sys/dev/hid/bcm5974.c +++ b/sys/dev/hid/bcm5974.c @@ -1,886 +1,886 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2012 Huang Wen Hui * Copyright (c) 2021 Vladimir Kondratyev * All rights reserved. * * 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 #define HID_DEBUG_VAR bcm5974_debug #include #include #include #include #include #include #include #include "usbdevs.h" #define BCM5974_BUFFER_MAX (248 * 4) /* 4 Type4 SPI frames */ #define BCM5974_TLC_PAGE HUP_GENERIC_DESKTOP #define BCM5974_TLC_USAGE HUG_MOUSE /* magic to switch device from HID (default) mode into raw */ /* Type1 & Type2 trackpads */ #define BCM5974_USB_IFACE_INDEX 0 #define BCM5974_USB_REPORT_LEN 8 #define BCM5974_USB_REPORT_ID 0 #define BCM5974_USB_MODE_RAW 0x01 #define BCM5974_USB_MODE_HID 0x08 /* Type4 trackpads */ #define BCM5974_HID_REPORT_LEN 2 #define BCM5974_HID_REPORT_ID 2 #define BCM5974_HID_MODE_RAW 0x01 #define BCM5974_HID_MODE_HID 0x00 /* Tunables */ static SYSCTL_NODE(_hw_hid, OID_AUTO, bcm5974, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "HID wellspring touchpad"); #ifdef HID_DEBUG enum wsp_log_level { BCM5974_LLEVEL_DISABLED = 0, BCM5974_LLEVEL_ERROR, BCM5974_LLEVEL_DEBUG, /* for troubleshooting */ BCM5974_LLEVEL_INFO, /* for diagnostics */ }; /* the default is to only log errors */ static int bcm5974_debug = BCM5974_LLEVEL_ERROR; SYSCTL_INT(_hw_hid_bcm5974, OID_AUTO, debug, CTLFLAG_RWTUN, &bcm5974_debug, BCM5974_LLEVEL_ERROR, "BCM5974 debug level"); #endif /* HID_DEBUG */ /* * Some tables, structures, definitions and constant values for the * touchpad protocol has been copied from Linux's * "drivers/input/mouse/bcm5974.c" which has the following copyright * holders under GPLv2. All device specific code in this driver has * been written from scratch. The decoding algorithm is based on * output from FreeBSD's usbdump. * * Copyright (C) 2008 Henrik Rydberg (rydberg@euromail.se) * Copyright (C) 2008 Scott Shawcroft (scott.shawcroft@gmail.com) * Copyright (C) 2001-2004 Greg Kroah-Hartman (greg@kroah.com) * Copyright (C) 2005 Johannes Berg (johannes@sipsolutions.net) * Copyright (C) 2005 Stelian Pop (stelian@popies.net) * Copyright (C) 2005 Frank Arnold (frank@scirocco-5v-turbo.de) * Copyright (C) 2005 Peter Osterlund (petero2@telia.com) * Copyright (C) 2005 Michael Hanselmann (linux-kernel@hansmi.ch) * Copyright (C) 2006 Nicolas Boichat (nicolas@boichat.ch) */ /* trackpad header types */ enum tp_type { TYPE1, /* plain trackpad */ TYPE2, /* button integrated in trackpad */ TYPE3, /* additional header fields since June 2013 */ TYPE4, /* additional header field for pressure data */ TYPE_MT2U, /* Magic Trackpad 2 USB */ TYPE_CNT }; /* list of device capability bits */ #define HAS_INTEGRATED_BUTTON 1 #define USES_COMPACT_REPORT 2 struct tp_type_params { uint8_t caps; /* device capability bitmask */ uint8_t button; /* offset to button data */ uint8_t offset; /* offset to trackpad finger data */ uint8_t delta; /* offset from header to finger struct */ } const static tp[TYPE_CNT] = { [TYPE1] = { .caps = 0, .button = 0, .offset = 13 * 2, .delta = 0, }, [TYPE2] = { .caps = HAS_INTEGRATED_BUTTON, .button = 15, .offset = 15 * 2, .delta = 0, }, [TYPE3] = { .caps = HAS_INTEGRATED_BUTTON, .button = 23, .offset = 19 * 2, .delta = 0, }, [TYPE4] = { .caps = HAS_INTEGRATED_BUTTON, .button = 31, .offset = 23 * 2, .delta = 2, }, [TYPE_MT2U] = { .caps = HAS_INTEGRATED_BUTTON | USES_COMPACT_REPORT, .button = 1, .offset = 12, .delta = 0, }, }; /* trackpad finger structure - compact version for external "Magic" devices */ struct tp_finger_compact { uint32_t coords; /* not struct directly due to endian conversion */ uint8_t touch_major; uint8_t touch_minor; uint8_t size; uint8_t pressure; uint8_t id_ori; } __packed; _Static_assert((sizeof(struct tp_finger_compact) == 9), "tp_finger struct size must be 9"); /* trackpad finger structure - little endian */ struct tp_finger { uint16_t origin; /* zero when switching track finger */ uint16_t abs_x; /* absolute x coodinate */ uint16_t abs_y; /* absolute y coodinate */ uint16_t rel_x; /* relative x coodinate */ uint16_t rel_y; /* relative y coodinate */ uint16_t tool_major; /* tool area, major axis */ uint16_t tool_minor; /* tool area, minor axis */ uint16_t orientation; /* 16384 when point, else 15 bit angle */ uint16_t touch_major; /* touch area, major axis */ uint16_t touch_minor; /* touch area, minor axis */ uint16_t unused[2]; /* zeros */ uint16_t pressure; /* pressure on forcetouch touchpad */ uint16_t multi; /* one finger: varies, more fingers: * constant */ } __packed; #define BCM5974_LE2H(x) ((int32_t)(int16_t)le16toh(x)) /* trackpad finger data size, empirically at least ten fingers */ #define MAX_FINGERS MAX_MT_SLOTS #define MAX_FINGER_ORIENTATION 16384 enum { BCM5974_FLAG_WELLSPRING1, BCM5974_FLAG_WELLSPRING2, BCM5974_FLAG_WELLSPRING3, BCM5974_FLAG_WELLSPRING4, BCM5974_FLAG_WELLSPRING4A, BCM5974_FLAG_WELLSPRING5, BCM5974_FLAG_WELLSPRING6A, BCM5974_FLAG_WELLSPRING6, BCM5974_FLAG_WELLSPRING5A, BCM5974_FLAG_WELLSPRING7, BCM5974_FLAG_WELLSPRING7A, BCM5974_FLAG_WELLSPRING8, BCM5974_FLAG_WELLSPRING9, BCM5974_FLAG_MAGIC_TRACKPAD2_USB, BCM5974_FLAG_MAX, }; /* device-specific parameters */ struct bcm5974_axis { int snratio; /* signal-to-noise ratio */ int min; /* device minimum reading */ int max; /* device maximum reading */ int size; /* physical size, mm */ }; /* device-specific configuration */ struct bcm5974_dev_params { const struct tp_type_params* tp; struct bcm5974_axis p; /* finger pressure limits */ struct bcm5974_axis w; /* finger width limits */ struct bcm5974_axis x; /* horizontal limits */ struct bcm5974_axis y; /* vertical limits */ struct bcm5974_axis o; /* orientation limits */ }; /* logical signal quality */ #define SN_PRESSURE 45 /* pressure signal-to-noise ratio */ #define SN_WIDTH 25 /* width signal-to-noise ratio */ #define SN_COORD 250 /* coordinate signal-to-noise ratio */ #define SN_ORIENT 10 /* orientation signal-to-noise ratio */ static const struct bcm5974_dev_params bcm5974_dev_params[BCM5974_FLAG_MAX] = { [BCM5974_FLAG_WELLSPRING1] = { .tp = tp + TYPE1, .p = { SN_PRESSURE, 0, 256, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4824, 5342, 105 }, .y = { SN_COORD, -172, 5820, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING2] = { .tp = tp + TYPE1, .p = { SN_PRESSURE, 0, 256, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4824, 4824, 105 }, .y = { SN_COORD, -172, 4290, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING3] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4460, 5166, 105 }, .y = { SN_COORD, -75, 6700, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING4] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4620, 5140, 105 }, .y = { SN_COORD, -150, 6600, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING4A] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4616, 5112, 105 }, .y = { SN_COORD, -142, 5234, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING5] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4415, 5050, 105 }, .y = { SN_COORD, -55, 6680, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING6] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4620, 5140, 105 }, .y = { SN_COORD, -150, 6600, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING5A] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4750, 5280, 105 }, .y = { SN_COORD, -150, 6730, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING6A] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4620, 5140, 105 }, .y = { SN_COORD, -150, 6600, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING7] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4750, 5280, 105 }, .y = { SN_COORD, -150, 6730, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING7A] = { .tp = tp + TYPE2, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4750, 5280, 105 }, .y = { SN_COORD, -150, 6730, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_WELLSPRING8] = { .tp = tp + TYPE3, .p = { SN_PRESSURE, 0, 300, 0 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4620, 5140, 105 }, .y = { SN_COORD, -150, 6600, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, /* * NOTE: Actually force-sensitive. Pressure has a "size" equal to the max * so that the "resolution" is 1 (i.e. values will be interpreted as grams). * No scientific measurements have been done :) but a really hard press * results in a value around 3500 on model 4. */ [BCM5974_FLAG_WELLSPRING9] = { .tp = tp + TYPE4, .p = { SN_PRESSURE, 0, 4096, 4096 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -4828, 5345, 105 }, .y = { SN_COORD, -203, 6803, 75 }, .o = { SN_ORIENT, -MAX_FINGER_ORIENTATION, MAX_FINGER_ORIENTATION, 0 }, }, [BCM5974_FLAG_MAGIC_TRACKPAD2_USB] = { .tp = tp + TYPE_MT2U, .p = { SN_PRESSURE, 0, 256, 256 }, .w = { SN_WIDTH, 0, 2048, 0 }, .x = { SN_COORD, -3678, 3934, 48 }, .y = { SN_COORD, -2478, 2587, 44 }, .o = { SN_ORIENT, -3, 4, 0 }, }, }; #define BCM5974_DEV(v,p,i) { \ HID_BVPI(BUS_USB, USB_VENDOR_##v, USB_PRODUCT_##v##_##p, i), \ HID_TLC(BCM5974_TLC_PAGE, BCM5974_TLC_USAGE), \ } static const struct hid_device_id bcm5974_devs[] = { /* MacbookAir1.1 */ BCM5974_DEV(APPLE, WELLSPRING_ANSI, BCM5974_FLAG_WELLSPRING1), BCM5974_DEV(APPLE, WELLSPRING_ISO, BCM5974_FLAG_WELLSPRING1), BCM5974_DEV(APPLE, WELLSPRING_JIS, BCM5974_FLAG_WELLSPRING1), /* MacbookProPenryn, aka wellspring2 */ BCM5974_DEV(APPLE, WELLSPRING2_ANSI, BCM5974_FLAG_WELLSPRING2), BCM5974_DEV(APPLE, WELLSPRING2_ISO, BCM5974_FLAG_WELLSPRING2), BCM5974_DEV(APPLE, WELLSPRING2_JIS, BCM5974_FLAG_WELLSPRING2), /* Macbook5,1 (unibody), aka wellspring3 */ BCM5974_DEV(APPLE, WELLSPRING3_ANSI, BCM5974_FLAG_WELLSPRING3), BCM5974_DEV(APPLE, WELLSPRING3_ISO, BCM5974_FLAG_WELLSPRING3), BCM5974_DEV(APPLE, WELLSPRING3_JIS, BCM5974_FLAG_WELLSPRING3), /* MacbookAir3,2 (unibody), aka wellspring4 */ BCM5974_DEV(APPLE, WELLSPRING4_ANSI, BCM5974_FLAG_WELLSPRING4), BCM5974_DEV(APPLE, WELLSPRING4_ISO, BCM5974_FLAG_WELLSPRING4), BCM5974_DEV(APPLE, WELLSPRING4_JIS, BCM5974_FLAG_WELLSPRING4), /* MacbookAir3,1 (unibody), aka wellspring4 */ BCM5974_DEV(APPLE, WELLSPRING4A_ANSI, BCM5974_FLAG_WELLSPRING4A), BCM5974_DEV(APPLE, WELLSPRING4A_ISO, BCM5974_FLAG_WELLSPRING4A), BCM5974_DEV(APPLE, WELLSPRING4A_JIS, BCM5974_FLAG_WELLSPRING4A), /* Macbook8 (unibody, March 2011) */ BCM5974_DEV(APPLE, WELLSPRING5_ANSI, BCM5974_FLAG_WELLSPRING5), BCM5974_DEV(APPLE, WELLSPRING5_ISO, BCM5974_FLAG_WELLSPRING5), BCM5974_DEV(APPLE, WELLSPRING5_JIS, BCM5974_FLAG_WELLSPRING5), /* MacbookAir4,1 (unibody, July 2011) */ BCM5974_DEV(APPLE, WELLSPRING6A_ANSI, BCM5974_FLAG_WELLSPRING6A), BCM5974_DEV(APPLE, WELLSPRING6A_ISO, BCM5974_FLAG_WELLSPRING6A), BCM5974_DEV(APPLE, WELLSPRING6A_JIS, BCM5974_FLAG_WELLSPRING6A), /* MacbookAir4,2 (unibody, July 2011) */ BCM5974_DEV(APPLE, WELLSPRING6_ANSI, BCM5974_FLAG_WELLSPRING6), BCM5974_DEV(APPLE, WELLSPRING6_ISO, BCM5974_FLAG_WELLSPRING6), BCM5974_DEV(APPLE, WELLSPRING6_JIS, BCM5974_FLAG_WELLSPRING6), /* Macbook8,2 (unibody) */ BCM5974_DEV(APPLE, WELLSPRING5A_ANSI, BCM5974_FLAG_WELLSPRING5A), BCM5974_DEV(APPLE, WELLSPRING5A_ISO, BCM5974_FLAG_WELLSPRING5A), BCM5974_DEV(APPLE, WELLSPRING5A_JIS, BCM5974_FLAG_WELLSPRING5A), /* MacbookPro10,1 (unibody, June 2012) */ /* MacbookPro11,1-3 (unibody, June 2013) */ BCM5974_DEV(APPLE, WELLSPRING7_ANSI, BCM5974_FLAG_WELLSPRING7), BCM5974_DEV(APPLE, WELLSPRING7_ISO, BCM5974_FLAG_WELLSPRING7), BCM5974_DEV(APPLE, WELLSPRING7_JIS, BCM5974_FLAG_WELLSPRING7), /* MacbookPro10,2 (unibody, October 2012) */ BCM5974_DEV(APPLE, WELLSPRING7A_ANSI, BCM5974_FLAG_WELLSPRING7A), BCM5974_DEV(APPLE, WELLSPRING7A_ISO, BCM5974_FLAG_WELLSPRING7A), BCM5974_DEV(APPLE, WELLSPRING7A_JIS, BCM5974_FLAG_WELLSPRING7A), /* MacbookAir6,2 (unibody, June 2013) */ BCM5974_DEV(APPLE, WELLSPRING8_ANSI, BCM5974_FLAG_WELLSPRING8), BCM5974_DEV(APPLE, WELLSPRING8_ISO, BCM5974_FLAG_WELLSPRING8), BCM5974_DEV(APPLE, WELLSPRING8_JIS, BCM5974_FLAG_WELLSPRING8), /* MacbookPro12,1 MacbookPro11,4 */ BCM5974_DEV(APPLE, WELLSPRING9_ANSI, BCM5974_FLAG_WELLSPRING9), BCM5974_DEV(APPLE, WELLSPRING9_ISO, BCM5974_FLAG_WELLSPRING9), BCM5974_DEV(APPLE, WELLSPRING9_JIS, BCM5974_FLAG_WELLSPRING9), /* External "Magic" devices */ BCM5974_DEV(APPLE, MAGIC_TRACKPAD2, BCM5974_FLAG_MAGIC_TRACKPAD2_USB), }; struct bcm5974_softc { device_t sc_dev; struct evdev_dev *sc_evdev; /* device configuration */ const struct bcm5974_dev_params *sc_params; bool sc_saved_mode; }; static const uint8_t bcm5974_rdesc[] = { 0x05, BCM5974_TLC_PAGE, /* Usage Page (BCM5974_TLC_PAGE) */ 0x09, BCM5974_TLC_USAGE,/* Usage (BCM5974_TLC_USAGE) */ 0xA1, 0x01, /* Collection (Application) */ 0x06, 0x00, 0xFF, /* Usage Page (Vendor Defined 0xFF00) */ 0x09, 0x01, /* Usage (0x01) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x26, 0xFF, 0x00, /* Logical Maximum (255) */ 0x75, 0x08, /* Report Size (8) */ 0x96, /* Report Count (BCM5974_BUFFER_MAX) */ BCM5974_BUFFER_MAX & 0xFF, BCM5974_BUFFER_MAX >> 8 & 0xFF, 0x81, 0x02, /* Input (Data,Var,Abs) */ 0xC0, /* End Collection */ }; /* * function prototypes */ static evdev_open_t bcm5974_ev_open; static evdev_close_t bcm5974_ev_close; static const struct evdev_methods bcm5974_evdev_methods = { .ev_open = &bcm5974_ev_open, .ev_close = &bcm5974_ev_close, }; static hid_intr_t bcm5974_intr; /* Device methods. */ static device_identify_t bcm5974_identify; static device_probe_t bcm5974_probe; static device_attach_t bcm5974_attach; static device_detach_t bcm5974_detach; /* * Type1 and Type2 touchpads use keyboard USB interface to switch from HID to * RAW mode. Although it is possible to extend hkbd driver to support such a * mode change requests, it's not wanted due to cross device tree dependencies. * So, find lowest common denominator (struct usb_device of grandparent usbhid * driver) of touchpad and keyboard drivers and issue direct USB requests. */ static int bcm5974_set_device_mode_usb(struct bcm5974_softc *sc, bool on) { uint8_t mode_bytes[BCM5974_USB_REPORT_LEN]; struct usb_ctl_request ucr; int err; ucr.ucr_request.bmRequestType = UT_READ_CLASS_INTERFACE; ucr.ucr_request.bRequest = UR_GET_REPORT; USETW2(ucr.ucr_request.wValue, UHID_FEATURE_REPORT, BCM5974_USB_REPORT_ID); ucr.ucr_request.wIndex[0] = BCM5974_USB_IFACE_INDEX; ucr.ucr_request.wIndex[1] = 0; USETW(ucr.ucr_request.wLength, BCM5974_USB_REPORT_LEN); ucr.ucr_data = mode_bytes; err = hid_ioctl(sc->sc_dev, USB_REQUEST, (uintptr_t)&ucr); if (err != 0) { DPRINTF("Failed to read device mode (%d)\n", err); return (EIO); } #if 0 /* * XXX Need to wait at least 250ms for hardware to get * ready. The device mode handling appears to be handled * asynchronously and we should not issue these commands too * quickly. */ pause("WHW", hz / 4); #endif mode_bytes[0] = on ? BCM5974_USB_MODE_RAW : BCM5974_USB_MODE_HID; ucr.ucr_request.bmRequestType = UT_WRITE_CLASS_INTERFACE; ucr.ucr_request.bRequest = UR_SET_REPORT; err = hid_ioctl(sc->sc_dev, USB_REQUEST, (uintptr_t)&ucr); if (err != 0) { DPRINTF("Failed to write device mode (%d)\n", err); return (EIO); } return (0); } static int bcm5974_set_device_mode_hid(struct bcm5974_softc *sc, bool on) { uint8_t mode_bytes[BCM5974_HID_REPORT_LEN] = { BCM5974_HID_REPORT_ID, on ? BCM5974_HID_MODE_RAW : BCM5974_HID_MODE_HID, }; #if 0 int err; err = hid_get_report(sc->sc_dev, mode_bytes, BCM5974_HID_REPORT_LEN, NULL, HID_FEATURE_REPORT, BCM5974_HID_REPORT_ID); if (err != 0) { DPRINTF("Failed to read device mode (%d)\n", err); return (err); } /* * XXX Need to wait at least 250ms for hardware to get * ready. The device mode handling appears to be handled * asynchronously and we should not issue these commands too * quickly. */ pause("WHW", hz / 4); mode_bytes[1] = on ? BCM5974_HID_MODE_RAW : BCM5974_HID_MODE_HID; #endif return (hid_set_report(sc->sc_dev, mode_bytes, BCM5974_HID_REPORT_LEN, HID_FEATURE_REPORT, BCM5974_HID_REPORT_ID)); } static int bcm5974_set_device_mode(struct bcm5974_softc *sc, bool on) { int err = 0; switch (sc->sc_params->tp - tp) { case TYPE1: case TYPE2: err = bcm5974_set_device_mode_usb(sc, on); break; case TYPE3: /* Type 3 does not require a mode switch */ break; case TYPE4: case TYPE_MT2U: err = bcm5974_set_device_mode_hid(sc, on); break; default: KASSERT(0 == 1, ("Unknown trackpad type")); } if (!err) sc->sc_saved_mode = on; return (err); } static void bcm5974_identify(driver_t *driver, device_t parent) { void *d_ptr; hid_size_t d_len; /* * The bcm5974 touchpad has no stable RAW mode TLC in its report * descriptor. So replace existing HID mode mouse TLC with dummy one * to set proper transport layer buffer sizes, make driver probe * simpler and prevent unwanted hms driver attachment. */ if (HIDBUS_LOOKUP_ID(parent, bcm5974_devs) != NULL && hid_get_report_descr(parent, &d_ptr, &d_len) == 0 && hid_is_mouse(d_ptr, d_len)) hid_set_report_descr(parent, bcm5974_rdesc, sizeof(bcm5974_rdesc)); } static int bcm5974_probe(device_t dev) { int err; err = HIDBUS_LOOKUP_DRIVER_INFO(dev, bcm5974_devs); if (err != 0) return (err); hidbus_set_desc(dev, "Touchpad"); return (BUS_PROBE_DEFAULT); } static int bcm5974_attach(device_t dev) { struct bcm5974_softc *sc = device_get_softc(dev); const struct hid_device_info *hw = hid_get_device_info(dev); int err; DPRINTFN(BCM5974_LLEVEL_INFO, "sc=%p\n", sc); sc->sc_dev = dev; /* get device specific configuration */ sc->sc_params = bcm5974_dev_params + hidbus_get_driver_info(dev); sc->sc_evdev = evdev_alloc(); evdev_set_name(sc->sc_evdev, device_get_desc(dev)); evdev_set_phys(sc->sc_evdev, device_get_nameunit(dev)); evdev_set_id(sc->sc_evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(sc->sc_evdev, hw->serial); evdev_set_methods(sc->sc_evdev, sc, &bcm5974_evdev_methods); evdev_support_prop(sc->sc_evdev, INPUT_PROP_POINTER); evdev_support_event(sc->sc_evdev, EV_SYN); evdev_support_event(sc->sc_evdev, EV_ABS); evdev_support_event(sc->sc_evdev, EV_KEY); evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ #define BCM5974_ABS(evdev, code, param) \ evdev_support_abs((evdev), (code), (param).min, (param).max, \ ((param).max - (param).min) / (param).snratio, 0, \ (param).size != 0 ? ((param).max - (param).min) / (param).size : 0); /* finger position */ BCM5974_ABS(sc->sc_evdev, ABS_MT_POSITION_X, sc->sc_params->x); BCM5974_ABS(sc->sc_evdev, ABS_MT_POSITION_Y, sc->sc_params->y); /* finger pressure */ BCM5974_ABS(sc->sc_evdev, ABS_MT_PRESSURE, sc->sc_params->p); /* finger touch area */ BCM5974_ABS(sc->sc_evdev, ABS_MT_TOUCH_MAJOR, sc->sc_params->w); BCM5974_ABS(sc->sc_evdev, ABS_MT_TOUCH_MINOR, sc->sc_params->w); /* finger approach area */ if ((sc->sc_params->tp->caps & USES_COMPACT_REPORT) == 0) { BCM5974_ABS(sc->sc_evdev, ABS_MT_WIDTH_MAJOR, sc->sc_params->w); BCM5974_ABS(sc->sc_evdev, ABS_MT_WIDTH_MINOR, sc->sc_params->w); } /* finger orientation */ BCM5974_ABS(sc->sc_evdev, ABS_MT_ORIENTATION, sc->sc_params->o); /* button properties */ evdev_support_key(sc->sc_evdev, BTN_LEFT); if ((sc->sc_params->tp->caps & HAS_INTEGRATED_BUTTON) != 0) evdev_support_prop(sc->sc_evdev, INPUT_PROP_BUTTONPAD); /* Enable automatic touch assignment for type B MT protocol */ evdev_support_abs(sc->sc_evdev, ABS_MT_SLOT, 0, MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->sc_evdev, ABS_MT_TRACKING_ID, -1, MAX_FINGERS - 1, 0, 0, 0); if ((sc->sc_params->tp->caps & USES_COMPACT_REPORT) == 0) evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_TRACK); evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_AUTOREL); /* Synaptics compatibility events */ evdev_set_flag(sc->sc_evdev, EVDEV_FLAG_MT_STCOMPAT); err = evdev_register(sc->sc_evdev); if (err) goto detach; hidbus_set_intr(dev, bcm5974_intr, sc); return (0); detach: bcm5974_detach(dev); return (ENOMEM); } static int bcm5974_detach(device_t dev) { struct bcm5974_softc *sc = device_get_softc(dev); evdev_free(sc->sc_evdev); return (0); } static int bcm5974_resume(device_t dev) { struct bcm5974_softc *sc = device_get_softc(dev); bcm5974_set_device_mode(sc, sc->sc_saved_mode); return (0); } static void bcm5974_intr(void *context, void *data, hid_size_t len) { struct bcm5974_softc *sc = context; const struct bcm5974_dev_params *params = sc->sc_params; union evdev_mt_slot slot_data; struct tp_finger *f; struct tp_finger_compact *fc; int coords; int ntouch; /* the finger number in touch */ int ibt; /* button status */ int i; int slot; uint8_t fsize = sizeof(struct tp_finger) + params->tp->delta; if ((params->tp->caps & USES_COMPACT_REPORT) != 0) fsize = sizeof(struct tp_finger_compact) + params->tp->delta; if ((len < params->tp->offset + fsize) || ((len - params->tp->offset) % fsize) != 0) { DPRINTFN(BCM5974_LLEVEL_INFO, "Invalid length: %d, %x, %x\n", len, sc->tp_data[0], sc->tp_data[1]); return; } ibt = ((uint8_t *)data)[params->tp->button]; ntouch = (len - params->tp->offset) / fsize; for (i = 0, slot = 0; i != ntouch; i++) { if ((params->tp->caps & USES_COMPACT_REPORT) != 0) { fc = (struct tp_finger_compact *)(((uint8_t *)data) + params->tp->offset + params->tp->delta + i * fsize); coords = (int)le32toh(fc->coords); DPRINTFN(BCM5974_LLEVEL_INFO, "[%d]ibt=%d, taps=%d, x=%5d, y=%5d, state=%4d, " "tchmaj=%4d, tchmin=%4d, size=%4d, pressure=%4d, " "ot=%4x, id=%4x\n", i, ibt, ntouch, coords << 19 >> 19, coords << 6 >> 19, (u_int)coords >> 30, fc->touch_major, fc->touch_minor, fc->size, fc->pressure, fc->id_ori >> 5, fc->id_ori & 0x0f); if (fc->touch_major == 0) continue; slot_data = (union evdev_mt_slot) { .id = fc->id_ori & 0x0f, .x = coords << 19 >> 19, .y = params->y.min + params->y.max - ((coords << 6) >> 19), .p = fc->pressure, .maj = fc->touch_major << 2, .min = fc->touch_minor << 2, .ori = (int)(fc->id_ori >> 5) - 4, }; evdev_mt_push_slot(sc->sc_evdev, slot, &slot_data); slot++; continue; } f = (struct tp_finger *)(((uint8_t *)data) + params->tp->offset + params->tp->delta + i * fsize); DPRINTFN(BCM5974_LLEVEL_INFO, "[%d]ibt=%d, taps=%d, o=%4d, ax=%5d, ay=%5d, " "rx=%5d, ry=%5d, tlmaj=%4d, tlmin=%4d, ot=%4x, " "tchmaj=%4d, tchmin=%4d, pressure=%4d, m=%4x\n", i, ibt, ntouch, BCM5974_LE2H(f->origin), BCM5974_LE2H(f->abs_x), BCM5974_LE2H(f->abs_y), BCM5974_LE2H(f->rel_x), BCM5974_LE2H(f->rel_y), BCM5974_LE2H(f->tool_major), BCM5974_LE2H(f->tool_minor), BCM5974_LE2H(f->orientation), BCM5974_LE2H(f->touch_major), BCM5974_LE2H(f->touch_minor), BCM5974_LE2H(f->pressure), BCM5974_LE2H(f->multi)); if (BCM5974_LE2H(f->touch_major) == 0) continue; slot_data = (union evdev_mt_slot) { .id = slot, .x = BCM5974_LE2H(f->abs_x), .y = params->y.min + params->y.max - BCM5974_LE2H(f->abs_y), .p = BCM5974_LE2H(f->pressure), .maj = BCM5974_LE2H(f->touch_major) << 1, .min = BCM5974_LE2H(f->touch_minor) << 1, .w_maj = BCM5974_LE2H(f->tool_major) << 1, .w_min = BCM5974_LE2H(f->tool_minor) << 1, .ori = params->o.max - BCM5974_LE2H(f->orientation), }; evdev_mt_push_slot(sc->sc_evdev, slot, &slot_data); slot++; } evdev_push_key(sc->sc_evdev, BTN_LEFT, ibt); evdev_sync(sc->sc_evdev); } static int bcm5974_ev_open(struct evdev_dev *evdev) { struct bcm5974_softc *sc = evdev_get_softc(evdev); int err; /* * By default the touchpad behaves like a HID device, sending * packets with reportID = 8. Such reports contain only * limited information. They encode movement deltas and button * events, but do not include data from the pressure * sensors. The device input mode can be switched from HID * reports to raw sensor data using vendor-specific USB * control commands: */ err = bcm5974_set_device_mode(sc, true); if (err != 0) { DPRINTF("failed to set mode to RAW MODE (%d)\n", err); return (err); } - return (hidbus_intr_start(sc->sc_dev)); + return (hid_intr_start(sc->sc_dev)); } static int bcm5974_ev_close(struct evdev_dev *evdev) { struct bcm5974_softc *sc = evdev_get_softc(evdev); int err; - err = hidbus_intr_stop(sc->sc_dev); + err = hid_intr_stop(sc->sc_dev); if (err != 0) return (err); /* * During re-enumeration of the device we need to force the * device back into HID mode before switching it to RAW * mode. Else the device does not work like expected. */ err = bcm5974_set_device_mode(sc, false); if (err != 0) DPRINTF("Failed to set mode to HID MODE (%d)\n", err); return (err); } static device_method_t bcm5974_methods[] = { /* Device interface */ DEVMETHOD(device_identify, bcm5974_identify), DEVMETHOD(device_probe, bcm5974_probe), DEVMETHOD(device_attach, bcm5974_attach), DEVMETHOD(device_detach, bcm5974_detach), DEVMETHOD(device_resume, bcm5974_resume), DEVMETHOD_END }; static driver_t bcm5974_driver = { .name = "bcm5974", .methods = bcm5974_methods, .size = sizeof(struct bcm5974_softc) }; DRIVER_MODULE(bcm5974, hidbus, bcm5974_driver, NULL, NULL); MODULE_DEPEND(bcm5974, hidbus, 1, 1, 1); MODULE_DEPEND(bcm5974, hid, 1, 1, 1); MODULE_DEPEND(bcm5974, evdev, 1, 1, 1); MODULE_VERSION(bcm5974, 1); HID_PNP_INFO(bcm5974_devs); diff --git a/sys/dev/hid/hid.c b/sys/dev/hid/hid.c index 7fa6e34be22a..6ed72dbe5a00 100644 --- a/sys/dev/hid/hid.c +++ b/sys/dev/hid/hid.c @@ -1,1083 +1,1101 @@ /* $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_intr_start(device_t dev) +{ + return (HID_INTR_START(device_get_parent(dev), dev)); +} + +int +hid_intr_stop(device_t dev) +{ + return (HID_INTR_STOP(device_get_parent(dev), dev)); +} + +void +hid_intr_poll(device_t dev) +{ + HID_INTR_POLL(device_get_parent(dev), dev); +} + int hid_get_rdesc(device_t dev, void *data, hid_size_t 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), dev, data, maxlen, actlen)); } int hid_write(device_t dev, const void *data, hid_size_t 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), 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), 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), dev, duration, id)); } int hid_set_protocol(device_t dev, uint16_t 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), dev, cmd, data)); } MODULE_VERSION(hid, 1); diff --git a/sys/dev/hid/hid.h b/sys/dev/hid/hid.h index 5af470df61d1..a267207e49db 100644 --- a/sys/dev/hid/hid.h +++ b/sys/dev/hid/hid.h @@ -1,346 +1,349 @@ /* $FreeBSD$ */ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. * Copyright (c) 1998 Lennart Augustsson. All rights reserved. * * 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. */ #ifndef _HID_HID_H_ #define _HID_HID_H_ /* Usage pages */ #define HUP_UNDEFINED 0x0000 #define HUP_GENERIC_DESKTOP 0x0001 #define HUP_SIMULATION 0x0002 #define HUP_VR_CONTROLS 0x0003 #define HUP_SPORTS_CONTROLS 0x0004 #define HUP_GAMING_CONTROLS 0x0005 #define HUP_KEYBOARD 0x0007 #define HUP_LEDS 0x0008 #define HUP_BUTTON 0x0009 #define HUP_ORDINALS 0x000a #define HUP_TELEPHONY 0x000b #define HUP_CONSUMER 0x000c #define HUP_DIGITIZERS 0x000d #define HUP_PHYSICAL_IFACE 0x000e #define HUP_UNICODE 0x0010 #define HUP_ALPHANUM_DISPLAY 0x0014 #define HUP_MONITOR 0x0080 #define HUP_MONITOR_ENUM_VAL 0x0081 #define HUP_VESA_VC 0x0082 #define HUP_VESA_CMD 0x0083 #define HUP_POWER 0x0084 #define HUP_BATTERY_SYSTEM 0x0085 #define HUP_BARCODE_SCANNER 0x008b #define HUP_SCALE 0x008c #define HUP_CAMERA_CONTROL 0x0090 #define HUP_ARCADE 0x0091 #define HUP_MICROSOFT 0xff00 /* Usages, generic desktop */ #define HUG_POINTER 0x0001 #define HUG_MOUSE 0x0002 #define HUG_JOYSTICK 0x0004 #define HUG_GAME_PAD 0x0005 #define HUG_KEYBOARD 0x0006 #define HUG_KEYPAD 0x0007 #define HUG_MULTIAXIS_CNTROLLER 0x0008 #define HUG_X 0x0030 #define HUG_Y 0x0031 #define HUG_Z 0x0032 #define HUG_RX 0x0033 #define HUG_RY 0x0034 #define HUG_RZ 0x0035 #define HUG_SLIDER 0x0036 #define HUG_DIAL 0x0037 #define HUG_WHEEL 0x0038 #define HUG_HAT_SWITCH 0x0039 #define HUG_COUNTED_BUFFER 0x003a #define HUG_BYTE_COUNT 0x003b #define HUG_MOTION_WAKEUP 0x003c #define HUG_VX 0x0040 #define HUG_VY 0x0041 #define HUG_VZ 0x0042 #define HUG_VBRX 0x0043 #define HUG_VBRY 0x0044 #define HUG_VBRZ 0x0045 #define HUG_VNO 0x0046 #define HUG_TWHEEL 0x0048 /* M$ Wireless Intellimouse Wheel */ #define HUG_SYSTEM_CONTROL 0x0080 #define HUG_SYSTEM_POWER_DOWN 0x0081 #define HUG_SYSTEM_SLEEP 0x0082 #define HUG_SYSTEM_WAKEUP 0x0083 #define HUG_SYSTEM_CONTEXT_MENU 0x0084 #define HUG_SYSTEM_MAIN_MENU 0x0085 #define HUG_SYSTEM_APP_MENU 0x0086 #define HUG_SYSTEM_MENU_HELP 0x0087 #define HUG_SYSTEM_MENU_EXIT 0x0088 #define HUG_SYSTEM_MENU_SELECT 0x0089 #define HUG_SYSTEM_MENU_RIGHT 0x008a #define HUG_SYSTEM_MENU_LEFT 0x008b #define HUG_SYSTEM_MENU_UP 0x008c #define HUG_SYSTEM_MENU_DOWN 0x008d #define HUG_SYSTEM_POWER_UP 0x008e #define HUG_SYSTEM_RESTART 0x008f #define HUG_D_PAD_UP 0x0090 #define HUG_D_PAD_DOWN 0x0091 #define HUG_D_PAD_RIGHT 0x0092 #define HUG_D_PAD_LEFT 0x0093 #define HUG_APPLE_EJECT 0x00b8 /* Usages Digitizers */ #define HUD_UNDEFINED 0x0000 #define HUD_DIGITIZER 0x0001 #define HUD_PEN 0x0002 #define HUD_TOUCHSCREEN 0x0004 #define HUD_TOUCHPAD 0x0005 #define HUD_CONFIG 0x000e #define HUD_FINGER 0x0022 #define HUD_TIP_PRESSURE 0x0030 #define HUD_BARREL_PRESSURE 0x0031 #define HUD_IN_RANGE 0x0032 #define HUD_TOUCH 0x0033 #define HUD_UNTOUCH 0x0034 #define HUD_TAP 0x0035 #define HUD_QUALITY 0x0036 #define HUD_DATA_VALID 0x0037 #define HUD_TRANSDUCER_INDEX 0x0038 #define HUD_TABLET_FKEYS 0x0039 #define HUD_PROGRAM_CHANGE_KEYS 0x003a #define HUD_BATTERY_STRENGTH 0x003b #define HUD_INVERT 0x003c #define HUD_X_TILT 0x003d #define HUD_Y_TILT 0x003e #define HUD_AZIMUTH 0x003f #define HUD_ALTITUDE 0x0040 #define HUD_TWIST 0x0041 #define HUD_TIP_SWITCH 0x0042 #define HUD_SEC_TIP_SWITCH 0x0043 #define HUD_BARREL_SWITCH 0x0044 #define HUD_ERASER 0x0045 #define HUD_TABLET_PICK 0x0046 #define HUD_CONFIDENCE 0x0047 #define HUD_WIDTH 0x0048 #define HUD_HEIGHT 0x0049 #define HUD_CONTACTID 0x0051 #define HUD_INPUT_MODE 0x0052 #define HUD_DEVICE_INDEX 0x0053 #define HUD_CONTACTCOUNT 0x0054 #define HUD_CONTACT_MAX 0x0055 #define HUD_SCAN_TIME 0x0056 #define HUD_SURFACE_SWITCH 0x0057 #define HUD_BUTTONS_SWITCH 0x0058 #define HUD_BUTTON_TYPE 0x0059 #define HUD_SEC_BARREL_SWITCH 0x005a #define HUD_LATENCY_MODE 0x0060 /* Usages, Consumer */ #define HUC_CONTROL 0x0001 #define HUC_HEADPHONE 0x0005 #define HUC_AC_PAN 0x0238 #define HID_USAGE2(p,u) (((p) << 16) | (u)) #define HID_GET_USAGE(u) ((u) & 0xffff) #define HID_GET_USAGE_PAGE(u) (((u) >> 16) & 0xffff) #define HID_INPUT_REPORT 0x01 #define HID_OUTPUT_REPORT 0x02 #define HID_FEATURE_REPORT 0x03 /* Bits in the input/output/feature items */ #define HIO_CONST 0x001 #define HIO_VARIABLE 0x002 #define HIO_RELATIVE 0x004 #define HIO_WRAP 0x008 #define HIO_NONLINEAR 0x010 #define HIO_NOPREF 0x020 #define HIO_NULLSTATE 0x040 #define HIO_VOLATILE 0x080 #define HIO_BUFBYTES 0x100 /* Units of Measure */ #define HUM_CENTIMETER 0x11 #define HUM_RADIAN 0x12 #define HUM_INCH 0x13 #define HUM_INCH_EGALAX 0x33 #define HUM_DEGREE 0x14 #if defined(_KERNEL) || defined(_STANDALONE) #define HID_ITEM_MAXUSAGE 8 #define HID_MAX_AUTO_QUIRK 8 /* maximum number of dynamic quirks */ #define HID_PNP_ID_SIZE 20 /* includes null terminator */ /* Declare global HID debug variable. */ extern int hid_debug; /* Check if HID debugging is enabled. */ #ifdef HID_DEBUG_VAR #ifdef HID_DEBUG #define DPRINTFN(n,fmt,...) do { \ if ((HID_DEBUG_VAR) >= (n)) { \ printf("%s: " fmt, \ __FUNCTION__ ,##__VA_ARGS__); \ } \ } while (0) #define DPRINTF(...) DPRINTFN(1, __VA_ARGS__) #else #define DPRINTF(...) do { } while (0) #define DPRINTFN(...) do { } while (0) #endif #endif /* Declare parent SYSCTL HID node. */ #ifdef SYSCTL_DECL SYSCTL_DECL(_hw_hid); #endif typedef uint32_t hid_size_t; #define HID_IN_POLLING_MODE() (SCHEDULER_STOPPED() || kdb_active) enum hid_kind { hid_input, hid_output, hid_feature, hid_collection, hid_endcollection }; struct hid_location { uint32_t size; uint32_t count; uint32_t pos; }; struct hid_item { /* Global */ int32_t _usage_page; int32_t logical_minimum; int32_t logical_maximum; int32_t physical_minimum; int32_t physical_maximum; int32_t unit_exponent; int32_t unit; int32_t report_ID; /* Local */ int nusages; union { int32_t usage; int32_t usages[HID_ITEM_MAXUSAGE]; }; int32_t usage_minimum; int32_t usage_maximum; int32_t designator_index; int32_t designator_minimum; int32_t designator_maximum; int32_t string_index; int32_t string_minimum; int32_t string_maximum; int32_t set_delimiter; /* Misc */ int32_t collection; int collevel; enum hid_kind kind; uint32_t flags; /* Location */ struct hid_location loc; }; struct hid_absinfo { int32_t min; int32_t max; int32_t res; }; struct hid_device_info { char name[80]; char serial[80]; char idPnP[HID_PNP_ID_SIZE]; uint16_t idBus; uint16_t idVendor; uint16_t idProduct; uint16_t idVersion; hid_size_t rdescsize; /* Report descriptor size */ uint8_t autoQuirk[HID_MAX_AUTO_QUIRK]; }; struct hid_rdesc_info { void *data; hid_size_t len; hid_size_t isize; hid_size_t osize; hid_size_t fsize; uint8_t iid; uint8_t oid; uint8_t fid; /* Max sizes for HID requests supported by transport backend */ hid_size_t rdsize; hid_size_t wrsize; hid_size_t grsize; hid_size_t srsize; }; typedef void hid_intr_t(void *context, void *data, hid_size_t len); typedef bool hid_test_quirk_t(const struct hid_device_info *dev_info, uint16_t quirk); extern hid_test_quirk_t *hid_test_quirk_p; /* prototypes from "usb_hid.c" */ struct hid_data *hid_start_parse(const void *d, hid_size_t len, int kindset); void hid_end_parse(struct hid_data *s); int hid_get_item(struct hid_data *s, struct hid_item *h); int hid_report_size(const void *buf, hid_size_t len, enum hid_kind k, uint8_t id); int hid_report_size_max(const void *buf, hid_size_t len, enum hid_kind k, uint8_t *id); int hid_locate(const void *desc, hid_size_t size, int32_t usage, enum hid_kind kind, uint8_t index, struct hid_location *loc, uint32_t *flags, uint8_t *id); int32_t hid_get_data(const uint8_t *buf, hid_size_t len, struct hid_location *loc); uint32_t hid_get_udata(const uint8_t *buf, hid_size_t len, struct hid_location *loc); void hid_put_udata(uint8_t *buf, hid_size_t len, struct hid_location *loc, unsigned int value); int hid_is_collection(const void *desc, hid_size_t size, int32_t usage); int32_t hid_item_resolution(struct hid_item *hi); int hid_is_mouse(const void *d_ptr, uint16_t d_len); int hid_is_keyboard(const void *d_ptr, uint16_t d_len); bool hid_test_quirk(const struct hid_device_info *dev_info, uint16_t quirk); int hid_add_dynamic_quirk(struct hid_device_info *dev_info, uint16_t quirk); void hid_quirk_unload(void *arg); +int hid_intr_start(device_t); +int hid_intr_stop(device_t); +void hid_intr_poll(device_t); int hid_get_rdesc(device_t, void *, hid_size_t); int hid_read(device_t, void *, hid_size_t, hid_size_t *); int hid_write(device_t, const void *, hid_size_t); int hid_get_report(device_t, void *, hid_size_t, hid_size_t *, uint8_t, uint8_t); int hid_set_report(device_t, const void *, hid_size_t, uint8_t, uint8_t); int hid_set_idle(device_t, uint16_t, uint8_t); int hid_set_protocol(device_t, uint16_t); int hid_ioctl(device_t, unsigned long, uintptr_t); #endif /* _KERNEL || _STANDALONE */ #endif /* _HID_HID_H_ */ diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c index fe6c4df060a3..6dd8ff9b8e6f 100644 --- a/sys/dev/hid/hidbus.c +++ b/sys/dev/hid/hidbus.c @@ -1,979 +1,980 @@ /*- * 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), 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), 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), 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) +static int +hidbus_intr_start(device_t bus, device_t child) { - device_t bus = device_get_parent(child); + MPASS(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), bus); + error = refcnted ? 0 : hid_intr_start(bus); sx_unlock(&sc->sx); return (error); } -int -hidbus_intr_stop(device_t child) +static int +hidbus_intr_stop(device_t bus, device_t child) { - device_t bus = device_get_parent(child); + MPASS(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), bus); + error = refcnted ? 0 : hid_intr_stop(bus); sx_unlock(&sc->sx); return (error); } -void -hidbus_intr_poll(device_t child) +static void +hidbus_intr_poll(device_t bus, device_t child __unused) { - device_t bus = device_get_parent(child); - - HID_INTR_POLL(device_get_parent(bus), bus); + hid_intr_poll(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_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_intr_start, hidbus_intr_start), + DEVMETHOD(hid_intr_stop, hidbus_intr_stop), + DEVMETHOD(hid_intr_poll, hidbus_intr_poll), DEVMETHOD(hid_get_rdesc, hidbus_get_rdesc), DEVMETHOD(hid_read, hidbus_read), DEVMETHOD(hid_write, hidbus_write), 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/hid/hidbus.h b/sys/dev/hid/hidbus.h index b2744add8658..3de4a6291511 100644 --- a/sys/dev/hid/hidbus.h +++ b/sys/dev/hid/hidbus.h @@ -1,175 +1,172 @@ /*- * 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. */ #ifndef _HID_HIDBUS_H_ #define _HID_HIDBUS_H_ enum { HIDBUS_IVAR_USAGE, HIDBUS_IVAR_INDEX, HIDBUS_IVAR_FLAGS, #define HIDBUS_FLAG_AUTOCHILD (0<<1) /* Child is autodiscovered */ #define HIDBUS_FLAG_CAN_POLL (1<<1) /* Child can work during panic */ HIDBUS_IVAR_DRIVER_INFO, HIDBUS_IVAR_LOCK, }; #define HIDBUS_ACCESSOR(A, B, T) \ __BUS_ACCESSOR(hidbus, A, HIDBUS, B, T) HIDBUS_ACCESSOR(usage, USAGE, int32_t) HIDBUS_ACCESSOR(index, INDEX, uint8_t) HIDBUS_ACCESSOR(flags, FLAGS, uint32_t) HIDBUS_ACCESSOR(driver_info, DRIVER_INFO, uintptr_t) HIDBUS_ACCESSOR(lock, LOCK, struct mtx *) /* * The following structure is used when looking up an HID driver for * an HID device. It is inspired by the structure called "usb_device_id". * which is originated in Linux and ported to FreeBSD. */ struct hid_device_id { /* Select which fields to match against */ #if BYTE_ORDER == LITTLE_ENDIAN uint16_t match_flag_page:1, match_flag_usage:1, match_flag_bus:1, match_flag_vendor:1, match_flag_product:1, match_flag_ver_lo:1, match_flag_ver_hi:1, match_flag_pnp:1, match_flag_unused:8; #else uint16_t match_flag_unused:8, match_flag_pnp:1, match_flag_ver_hi:1, match_flag_ver_lo:1, match_flag_product:1, match_flag_vendor:1, match_flag_bus:1, match_flag_usage:1, match_flag_page:1; #endif /* Used for top level collection usage matches */ uint16_t page; uint16_t usage; /* Used for product specific matches; the Version range is inclusive */ uint8_t idBus; uint16_t idVendor; uint16_t idProduct; uint16_t idVersion_lo; uint16_t idVersion_hi; char *idPnP; /* Hook for driver specific information */ uintptr_t driver_info; }; #define HID_STD_PNP_INFO \ "M16:mask;U16:page;U16:usage;U8:bus;U16:vendor;U16:product;" \ "L16:version;G16:version;Z:_HID" #define HID_PNP_INFO(table) \ MODULE_PNP_INFO(HID_STD_PNP_INFO, hidbus, table, table, nitems(table)) #define HID_TLC(pg,usg) \ .match_flag_page = 1, .match_flag_usage = 1, .page = (pg), .usage = (usg) #define HID_BUS(bus) \ .match_flag_bus = 1, .idBus = (bus) #define HID_VENDOR(vend) \ .match_flag_vendor = 1, .idVendor = (vend) #define HID_PRODUCT(prod) \ .match_flag_product = 1, .idProduct = (prod) #define HID_VP(vend,prod) \ HID_VENDOR(vend), HID_PRODUCT(prod) #define HID_BVP(bus,vend,prod) \ HID_BUS(bus), HID_VENDOR(vend), HID_PRODUCT(prod) #define HID_BVPI(bus,vend,prod,info) \ HID_BUS(bus), HID_VENDOR(vend), HID_PRODUCT(prod), HID_DRIVER_INFO(info) #define HID_VERSION_GTEQ(lo) /* greater than or equal */ \ .match_flag_ver_lo = 1, .idVersion_lo = (lo) #define HID_VERSION_LTEQ(hi) /* less than or equal */ \ .match_flag_ver_hi = 1, .idVersion_hi = (hi) #define HID_PNP(pnp) \ .match_flag_pnp = 1, .idPnP = (pnp) #define HID_DRIVER_INFO(n) \ .driver_info = (n) #define HID_GET_DRIVER_INFO(did) \ (did)->driver_info #define HIDBUS_LOOKUP_ID(d, h) hidbus_lookup_id((d), (h), nitems(h)) #define HIDBUS_LOOKUP_DRIVER_INFO(d, h) \ hidbus_lookup_driver_info((d), (h), nitems(h)) /* * Walk through all HID items hi belonging Top Level Collection #tlc_index */ #define HIDBUS_FOREACH_ITEM(hd, hi, tlc_index) \ for (uint8_t _iter = 0; \ _iter <= (tlc_index) && hid_get_item((hd), (hi)); \ _iter += (hi)->kind == hid_endcollection && (hi)->collevel == 0) \ if (_iter == (tlc_index)) 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); bool hidbus_is_collection(const void *, hid_size_t, int32_t, uint8_t); const struct hid_device_id *hidbus_lookup_id(device_t, const struct hid_device_id *, int); struct hid_rdesc_info *hidbus_get_rdesc_info(device_t); int hidbus_lookup_driver_info(device_t, const struct hid_device_id *, int); void hidbus_set_intr(device_t, hid_intr_t*, void *); -int hidbus_intr_start(device_t); -int hidbus_intr_stop(device_t); -void hidbus_intr_poll(device_t); void hidbus_set_desc(device_t, const char *); device_t hidbus_find_child(device_t, int32_t); /* hidbus HID interface */ int hid_get_report_descr(device_t, void **, hid_size_t *); int hid_set_report_descr(device_t, const void *, hid_size_t); const struct hid_device_info *hid_get_device_info(device_t); #endif /* _HID_HIDBUS_H_ */ diff --git a/sys/dev/hid/hidmap.c b/sys/dev/hid/hidmap.c index 8951f14a2361..b97558ad33ef 100644 --- a/sys/dev/hid/hidmap.c +++ b/sys/dev/hid/hidmap.c @@ -1,836 +1,836 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); /* * Abstract 1 to 1 HID input usage to evdev event mapper driver. */ #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef HID_DEBUG #define DPRINTFN(hm, n, fmt, ...) do { \ if ((hm)->debug_var != NULL && *(hm)->debug_var >= (n)) { \ device_printf((hm)->dev, "%s: " fmt, \ __FUNCTION__ ,##__VA_ARGS__); \ } \ } while (0) #define DPRINTF(hm, ...) DPRINTFN(hm, 1, __VA_ARGS__) #else #define DPRINTF(...) do { } while (0) #define DPRINTFN(...) do { } while (0) #endif static evdev_open_t hidmap_ev_open; static evdev_close_t hidmap_ev_close; #define HIDMAP_WANT_MERGE_KEYS(hm) ((hm)->key_rel != NULL) #define HIDMAP_FOREACH_ITEM(hm, mi, uoff) \ for (u_int _map = 0, _item = 0, _uoff_priv = -1; \ ((mi) = hidmap_get_next_map_item( \ (hm), &_map, &_item, &_uoff_priv, &(uoff))) != NULL;) static inline bool hidmap_get_next_map_index(const struct hidmap_item *map, int nmap_items, uint32_t *index, uint16_t *usage_offset) { ++*usage_offset; if ((*index != 0 || *usage_offset != 0) && *usage_offset >= map[*index].nusages) { ++*index; *usage_offset = 0; } return (*index < nmap_items); } static inline const struct hidmap_item * hidmap_get_next_map_item(struct hidmap *hm, u_int *map, u_int *item, u_int *uoff_priv, uint16_t *uoff) { *uoff = *uoff_priv; while (!hidmap_get_next_map_index( hm->map[*map], hm->nmap_items[*map], item, uoff)) { ++*map; *item = 0; *uoff = -1; if (*map >= hm->nmaps) return (NULL); } *uoff_priv = *uoff; return (hm->map[*map] + *item); } void _hidmap_set_debug_var(struct hidmap *hm, int *debug_var) { #ifdef HID_DEBUG hm->debug_var = debug_var; #endif } static int hidmap_ev_close(struct evdev_dev *evdev) { - return (hidbus_intr_stop(evdev_get_softc(evdev))); + return (hid_intr_stop(evdev_get_softc(evdev))); } static int hidmap_ev_open(struct evdev_dev *evdev) { - return (hidbus_intr_start(evdev_get_softc(evdev))); + return (hid_intr_start(evdev_get_softc(evdev))); } void hidmap_support_key(struct hidmap *hm, uint16_t key) { if (hm->key_press == NULL) { hm->key_press = malloc(howmany(KEY_CNT, 8), M_DEVBUF, M_ZERO | M_WAITOK); evdev_support_event(hm->evdev, EV_KEY); hm->key_min = key; hm->key_max = key; } hm->key_min = MIN(hm->key_min, key); hm->key_max = MAX(hm->key_max, key); if (isset(hm->key_press, key)) { if (hm->key_rel == NULL) hm->key_rel = malloc(howmany(KEY_CNT, 8), M_DEVBUF, M_ZERO | M_WAITOK); } else { setbit(hm->key_press, key); evdev_support_key(hm->evdev, key); } } void hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value) { if (HIDMAP_WANT_MERGE_KEYS(hm)) setbit(value != 0 ? hm->key_press : hm->key_rel, key); else evdev_push_key(hm->evdev, key, value); } static void hidmap_sync_keys(struct hidmap *hm) { int i, j; bool press, rel; for (j = hm->key_min / 8; j <= hm->key_max / 8; j++) { if (hm->key_press[j] != hm->key_rel[j]) { for (i = j * 8; i < j * 8 + 8; i++) { press = isset(hm->key_press, i); rel = isset(hm->key_rel, i); if (press != rel) evdev_push_key(hm->evdev, i, press); } } } bzero(hm->key_press, howmany(KEY_CNT, 8)); bzero(hm->key_rel, howmany(KEY_CNT, 8)); } void hidmap_intr(void *context, void *buf, hid_size_t len) { struct hidmap *hm = context; struct hidmap_hid_item *hi; const struct hidmap_item *mi; int32_t usage; int32_t data; uint16_t key, uoff; uint8_t id = 0; bool found, do_sync = false; DPRINTFN(hm, 6, "hm=%p len=%d\n", hm, len); DPRINTFN(hm, 6, "data = %*D\n", len, buf, " "); /* Strip leading "report ID" byte */ if (hm->hid_items[0].id) { id = *(uint8_t *)buf; len--; buf = (uint8_t *)buf + 1; } hm->intr_buf = buf; hm->intr_len = len; for (hi = hm->hid_items; hi < hm->hid_items + hm->nhid_items; hi++) { /* At first run callbacks that not tied to HID items */ if (hi->type == HIDMAP_TYPE_FINALCB) { DPRINTFN(hm, 6, "type=%d item=%*D\n", hi->type, (int)sizeof(hi->cb), &hi->cb, " "); if (hi->cb(hm, hi, (union hidmap_cb_ctx){.rid = id}) == 0) do_sync = true; continue; } /* Ignore irrelevant reports */ if (id != hi->id) continue; /* * 5.8. If Logical Minimum and Logical Maximum are both * positive values then the contents of a field can be assumed * to be an unsigned value. Otherwise, all integer values are * signed values represented in 2’s complement format. */ data = hi->lmin < 0 || hi->lmax < 0 ? hid_get_data(buf, len, &hi->loc) : hid_get_udata(buf, len, &hi->loc); DPRINTFN(hm, 6, "type=%d data=%d item=%*D\n", hi->type, data, (int)sizeof(hi->cb), &hi->cb, " "); if (hi->invert_value && hi->type < HIDMAP_TYPE_ARR_LIST) data = hi->evtype == EV_REL ? -data : hi->lmin + hi->lmax - data; switch (hi->type) { case HIDMAP_TYPE_CALLBACK: if (hi->cb(hm, hi, (union hidmap_cb_ctx){.data = data}) != 0) continue; break; case HIDMAP_TYPE_VAR_NULLST: /* * 5.10. If the host or the device receives an * out-of-range value then the current value for the * respective control will not be modified. */ if (data < hi->lmin || data > hi->lmax) continue; /* FALLTHROUGH */ case HIDMAP_TYPE_VARIABLE: /* * Ignore reports for absolute data if the data did not * change and for relative data if data is 0. * Evdev layer filters out them anyway. */ if (data == (hi->evtype == EV_REL ? 0 : hi->last_val)) continue; if (hi->evtype == EV_KEY) hidmap_push_key(hm, hi->code, data); else evdev_push_event(hm->evdev, hi->evtype, hi->code, data); hi->last_val = data; break; case HIDMAP_TYPE_ARR_LIST: key = KEY_RESERVED; /* * 6.2.2.5. An out-of range value in an array field * is considered no controls asserted. */ if (data < hi->lmin || data > hi->lmax) goto report_key; /* * 6.2.2.5. Rather than returning a single bit for each * button in the group, an array returns an index in * each field that corresponds to the pressed button. */ key = hi->codes[data - hi->lmin]; if (key == KEY_RESERVED) DPRINTF(hm, "Can not map unknown HID " "array index: %08x\n", data); goto report_key; case HIDMAP_TYPE_ARR_RANGE: key = KEY_RESERVED; /* * 6.2.2.5. An out-of range value in an array field * is considered no controls asserted. */ if (data < hi->lmin || data > hi->lmax) goto report_key; /* * When the input field is an array and the usage is * specified with a range instead of an ID, we have to * derive the actual usage by using the item value as * an index in the usage range list. */ usage = data - hi->lmin + hi->umin; found = false; HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (usage == mi->usage + uoff && mi->type == EV_KEY && !mi->has_cb) { key = mi->code; found = true; break; } } if (!found) DPRINTF(hm, "Can not map unknown HID " "usage: %08x\n", usage); report_key: if (key == HIDMAP_KEY_NULL || key == hi->last_key) continue; if (hi->last_key != KEY_RESERVED) hidmap_push_key(hm, hi->last_key, 0); if (key != KEY_RESERVED) hidmap_push_key(hm, key, 1); hi->last_key = key; break; default: KASSERT(0, ("Unknown map type (%d)", hi->type)); } do_sync = true; } if (do_sync) { if (HIDMAP_WANT_MERGE_KEYS(hm)) hidmap_sync_keys(hm); evdev_sync(hm->evdev); } } static inline bool can_map_callback(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return (mi->has_cb && !mi->final_cb && hi->usage == mi->usage + usage_offset && (mi->relabs == HIDMAP_RELABS_ANY || !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); } static inline bool can_map_variable(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) != 0 && !mi->has_cb && hi->usage == mi->usage + usage_offset && (mi->relabs == HIDMAP_RELABS_ANY || !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); } static inline bool can_map_arr_range(struct hid_item *hi, const struct hidmap_item *mi, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && hi->usage_minimum <= mi->usage + usage_offset && hi->usage_maximum >= mi->usage + usage_offset && mi->type == EV_KEY && (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); } static inline bool can_map_arr_list(struct hid_item *hi, const struct hidmap_item *mi, uint32_t usage, uint16_t usage_offset) { return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && usage == mi->usage + usage_offset && mi->type == EV_KEY && (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); } static bool hidmap_probe_hid_item(struct hid_item *hi, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { u_int i, j; uint16_t uoff; bool found = false; #define HIDMAP_FOREACH_INDEX(map, nitems, idx, uoff) \ for ((idx) = 0, (uoff) = -1; \ hidmap_get_next_map_index((map), (nitems), &(idx), &(uoff));) HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_callback(hi, map + i, uoff)) { if (map[i].cb(NULL, NULL, (union hidmap_cb_ctx){.hi = hi}) != 0) break; setbit(caps, i); return (true); } } if (hi->flags & HIO_VARIABLE) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_variable(hi, map + i, uoff)) { KASSERT(map[i].type == EV_KEY || map[i].type == EV_REL || map[i].type == EV_ABS || map[i].type == EV_SW, ("Unsupported event type")); setbit(caps, i); return (true); } } return (false); } if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_arr_range(hi, map + i, uoff)) { setbit(caps, i); found = true; } } return (found); } for (j = 0; j < hi->nusages; j++) { HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { if (can_map_arr_list(hi, map+i, hi->usages[j], uoff)) { setbit(caps, i); found = true; } } } return (found); } static uint32_t hidmap_probe_hid_descr(void *d_ptr, hid_size_t d_len, uint8_t tlc_index, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { struct hid_data *hd; struct hid_item hi; uint32_t i, items = 0; bool do_free = false; if (caps == NULL) { caps = malloc(HIDMAP_CAPS_SZ(nitems_map), M_DEVBUF, M_WAITOK | M_ZERO); do_free = true; } else bzero (caps, HIDMAP_CAPS_SZ(nitems_map)); /* Parse inputs */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { if (hi.kind != hid_input) continue; if (hi.flags & HIO_CONST) continue; for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) if (hidmap_probe_hid_item(&hi, map, nitems_map, caps)) items++; } hid_end_parse(hd); /* Take finalizing callbacks in to account */ for (i = 0; i < nitems_map; i++) { if (map[i].has_cb && map[i].final_cb && map[i].cb(NULL, NULL, (union hidmap_cb_ctx){}) == 0) { setbit(caps, i); items++; } } /* Check that all mandatory usages are present in report descriptor */ if (items != 0) { for (i = 0; i < nitems_map; i++) { KASSERT(!(map[i].required && map[i].forbidden), ("both required & forbidden item flags are set")); if ((map[i].required && isclr(caps, i)) || (map[i].forbidden && isset(caps, i))) { items = 0; break; } } } if (do_free) free(caps, M_DEVBUF); return (items); } uint32_t hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) { void *d_ptr; uint32_t items; int i, error; hid_size_t d_len; uint8_t tlc_index = hidbus_get_index(hm->dev); /* Avoid double-adding of map in probe() handler */ for (i = 0; i < hm->nmaps; i++) if (hm->map[i] == map) return (0); error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); if (error != 0) { device_printf(hm->dev, "could not retrieve report descriptor " "from device: %d\n", error); return (error); } hm->cb_state = HIDMAP_CB_IS_PROBING; items = hidmap_probe_hid_descr(d_ptr, d_len, tlc_index, map, nitems_map, caps); if (items == 0) return (ENXIO); KASSERT(hm->nmaps < HIDMAP_MAX_MAPS, ("Not more than %d maps is supported", HIDMAP_MAX_MAPS)); hm->nhid_items += items; hm->map[hm->nmaps] = map; hm->nmap_items[hm->nmaps] = nitems_map; hm->nmaps++; return (0); } static bool hidmap_parse_hid_item(struct hidmap *hm, struct hid_item *hi, struct hidmap_hid_item *item) { const struct hidmap_item *mi; struct hidmap_hid_item hi_temp; uint32_t i; uint16_t uoff; bool found = false; HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_callback(hi, mi, uoff)) { bzero(&hi_temp, sizeof(hi_temp)); hi_temp.cb = mi->cb; hi_temp.type = HIDMAP_TYPE_CALLBACK; /* * Values returned by probe- and attach-stage * callbacks MUST be identical. */ if (mi->cb(hm, &hi_temp, (union hidmap_cb_ctx){.hi = hi}) != 0) break; bcopy(&hi_temp, item, sizeof(hi_temp)); goto mapped; } } if (hi->flags & HIO_VARIABLE) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_variable(hi, mi, uoff)) { item->evtype = mi->type; item->code = mi->code + uoff; item->type = hi->flags & HIO_NULLSTATE ? HIDMAP_TYPE_VAR_NULLST : HIDMAP_TYPE_VARIABLE; item->last_val = 0; item->invert_value = mi->invert_value; switch (mi->type) { case EV_KEY: hidmap_support_key(hm, item->code); break; case EV_REL: evdev_support_event(hm->evdev, EV_REL); evdev_support_rel(hm->evdev, item->code); break; case EV_ABS: evdev_support_event(hm->evdev, EV_ABS); evdev_support_abs(hm->evdev, item->code, hi->logical_minimum, hi->logical_maximum, mi->fuzz, mi->flat, hid_item_resolution(hi)); break; case EV_SW: evdev_support_event(hm->evdev, EV_SW); evdev_support_sw(hm->evdev, item->code); break; default: KASSERT(0, ("Unsupported event type")); } goto mapped; } } return (false); } if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_arr_range(hi, mi, uoff)) { hidmap_support_key(hm, mi->code + uoff); found = true; } } if (!found) return (false); item->umin = hi->usage_minimum; item->type = HIDMAP_TYPE_ARR_RANGE; item->last_key = KEY_RESERVED; goto mapped; } for (i = 0; i < hi->nusages; i++) { HIDMAP_FOREACH_ITEM(hm, mi, uoff) { if (can_map_arr_list(hi, mi, hi->usages[i], uoff)) { hidmap_support_key(hm, mi->code + uoff); if (item->codes == NULL) item->codes = malloc( hi->nusages * sizeof(uint16_t), M_DEVBUF, M_WAITOK | M_ZERO); item->codes[i] = mi->code + uoff; found = true; break; } } } if (!found) return (false); item->type = HIDMAP_TYPE_ARR_LIST; item->last_key = KEY_RESERVED; mapped: item->id = hi->report_ID; item->loc = hi->loc; item->loc.count = 1; item->lmin = hi->logical_minimum; item->lmax = hi->logical_maximum; DPRINTFN(hm, 6, "usage=%04x id=%d loc=%u/%u type=%d item=%*D\n", hi->usage, hi->report_ID, hi->loc.pos, hi->loc.size, item->type, (int)sizeof(item->cb), &item->cb, " "); return (true); } static int hidmap_parse_hid_descr(struct hidmap *hm, uint8_t tlc_index) { const struct hidmap_item *map; struct hidmap_hid_item *item = hm->hid_items; void *d_ptr; struct hid_data *hd; struct hid_item hi; int i, error; hid_size_t d_len; error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); if (error != 0) { DPRINTF(hm, "could not retrieve report descriptor from " "device: %d\n", error); return (error); } /* Parse inputs */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { if (hi.kind != hid_input) continue; if (hi.flags & HIO_CONST) continue; for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) if (hidmap_parse_hid_item(hm, &hi, item)) item++; KASSERT(item <= hm->hid_items + hm->nhid_items, ("Parsed HID item array overflow")); } hid_end_parse(hd); /* Add finalizing callbacks to the end of list */ for (i = 0; i < hm->nmaps; i++) { for (map = hm->map[i]; map < hm->map[i] + hm->nmap_items[i]; map++) { if (map->has_cb && map->final_cb && map->cb(hm, item, (union hidmap_cb_ctx){}) == 0) { item->cb = map->cb; item->type = HIDMAP_TYPE_FINALCB; item++; } } } /* * Resulting number of parsed HID items can be less than expected as * map items might be duplicated in different maps. Save real number. */ if (hm->nhid_items != item - hm->hid_items) DPRINTF(hm, "Parsed HID item number mismatch: expected=%u " "result=%td\n", hm->nhid_items, item - hm->hid_items); hm->nhid_items = item - hm->hid_items; if (HIDMAP_WANT_MERGE_KEYS(hm)) bzero(hm->key_press, howmany(KEY_CNT, 8)); return (0); } int hidmap_probe(struct hidmap* hm, device_t dev, const struct hid_device_id *id, int nitems_id, const struct hidmap_item *map, int nitems_map, const char *suffix, hidmap_caps_t caps) { int error; error = hidbus_lookup_driver_info(dev, id, nitems_id); if (error != 0) return (error); hidmap_set_dev(hm, dev); error = hidmap_add_map(hm, map, nitems_map, caps); if (error != 0) return (error); hidbus_set_desc(dev, suffix); return (BUS_PROBE_DEFAULT); } int hidmap_attach(struct hidmap* hm) { const struct hid_device_info *hw = hid_get_device_info(hm->dev); #ifdef HID_DEBUG char tunable[40]; #endif int error; #ifdef HID_DEBUG if (hm->debug_var == NULL) { hm->debug_var = &hm->debug_level; snprintf(tunable, sizeof(tunable), "hw.hid.%s.debug", device_get_name(hm->dev)); TUNABLE_INT_FETCH(tunable, &hm->debug_level); SYSCTL_ADD_INT(device_get_sysctl_ctx(hm->dev), SYSCTL_CHILDREN(device_get_sysctl_tree(hm->dev)), OID_AUTO, "debug", CTLFLAG_RWTUN, &hm->debug_level, 0, "Verbosity level"); } #endif DPRINTFN(hm, 11, "hm=%p\n", hm); hm->cb_state = HIDMAP_CB_IS_ATTACHING; hm->hid_items = malloc(hm->nhid_items * sizeof(struct hid_item), M_DEVBUF, M_WAITOK | M_ZERO); hidbus_set_intr(hm->dev, hidmap_intr, hm); hm->evdev_methods = (struct evdev_methods) { .ev_open = &hidmap_ev_open, .ev_close = &hidmap_ev_close, }; hm->evdev = evdev_alloc(); evdev_set_name(hm->evdev, device_get_desc(hm->dev)); evdev_set_phys(hm->evdev, device_get_nameunit(hm->dev)); evdev_set_id(hm->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(hm->evdev, hw->serial); evdev_set_flag(hm->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(hm->evdev, EV_SYN); error = hidmap_parse_hid_descr(hm, hidbus_get_index(hm->dev)); if (error) { DPRINTF(hm, "error=%d\n", error); hidmap_detach(hm); return (ENXIO); } evdev_set_methods(hm->evdev, hm->dev, &hm->evdev_methods); hm->cb_state = HIDMAP_CB_IS_RUNNING; error = evdev_register(hm->evdev); if (error) { DPRINTF(hm, "error=%d\n", error); hidmap_detach(hm); return (ENXIO); } return (0); } int hidmap_detach(struct hidmap* hm) { struct hidmap_hid_item *hi; DPRINTFN(hm, 11, "\n"); hm->cb_state = HIDMAP_CB_IS_DETACHING; evdev_free(hm->evdev); if (hm->hid_items != NULL) { for (hi = hm->hid_items; hi < hm->hid_items + hm->nhid_items; hi++) if (hi->type == HIDMAP_TYPE_FINALCB || hi->type == HIDMAP_TYPE_CALLBACK) hi->cb(hm, hi, (union hidmap_cb_ctx){}); else if (hi->type == HIDMAP_TYPE_ARR_LIST) free(hi->codes, M_DEVBUF); free(hm->hid_items, M_DEVBUF); } free(hm->key_press, M_DEVBUF); free(hm->key_rel, M_DEVBUF); return (0); } MODULE_DEPEND(hidmap, hid, 1, 1, 1); MODULE_DEPEND(hidmap, hidbus, 1, 1, 1); MODULE_DEPEND(hidmap, evdev, 1, 1, 1); MODULE_VERSION(hidmap, 1); diff --git a/sys/dev/hid/hidraw.c b/sys/dev/hid/hidraw.c index dd20aba3e588..c18d039286ee 100644 --- a/sys/dev/hid/hidraw.c +++ b/sys/dev/hid/hidraw.c @@ -1,994 +1,994 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 1998 The NetBSD Foundation, Inc. * All rights reserved. * Copyright (c) 2020 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. */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include __FBSDID("$FreeBSD$"); #include "opt_hid.h" #include #ifdef COMPAT_FREEBSD32 #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hidraw_debug #include #include #include #ifdef HID_DEBUG static int hidraw_debug = 0; static SYSCTL_NODE(_hw_hid, OID_AUTO, hidraw, CTLFLAG_RW, 0, "HID raw interface"); SYSCTL_INT(_hw_hid_hidraw, OID_AUTO, debug, CTLFLAG_RWTUN, &hidraw_debug, 0, "Debug level"); #endif #define HIDRAW_INDEX 0xFF /* Arbitrary high value */ #define HIDRAW_LOCAL_BUFSIZE 64 /* Size of on-stack buffer. */ #define HIDRAW_LOCAL_ALLOC(local_buf, size) \ (sizeof(local_buf) > (size) ? (local_buf) : \ malloc((size), M_DEVBUF, M_ZERO | M_WAITOK)) #define HIDRAW_LOCAL_FREE(local_buf, buf) \ if ((local_buf) != (buf)) { \ free((buf), M_DEVBUF); \ } struct hidraw_softc { device_t sc_dev; /* base device */ struct mtx sc_mtx; /* hidbus private mutex */ struct hid_rdesc_info *sc_rdesc; const struct hid_device_info *sc_hw; uint8_t *sc_q; hid_size_t *sc_qlen; int sc_head; int sc_tail; int sc_sleepcnt; struct selinfo sc_rsel; struct proc *sc_async; /* process that wants SIGIO */ struct { /* driver state */ bool open:1; /* device is open */ bool aslp:1; /* waiting for device data in read() */ bool sel:1; /* waiting for device data in poll() */ bool quiet:1; /* Ignore input data */ bool immed:1; /* return read data immediately */ bool uhid:1; /* driver switched in to uhid mode */ bool lock:1; /* input queue sleepable lock */ bool flush:1; /* do not wait for data in read() */ } sc_state; int sc_fflags; /* access mode for open lifetime */ struct cdev *dev; }; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor32 { uint32_t hgd_data; /* void * */ uint16_t hgd_lang_id; uint16_t hgd_maxlen; uint16_t hgd_actlen; uint16_t hgd_offset; uint8_t hgd_config_index; uint8_t hgd_string_index; uint8_t hgd_iface_index; uint8_t hgd_altif_index; uint8_t hgd_endpt_index; uint8_t hgd_report_type; uint8_t reserved[8]; }; #define HIDRAW_GET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_GET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_GET_REPORT, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT_DESC32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT_DESC, struct hidraw_gen_descriptor32) #define HIDRAW_SET_REPORT32 \ _IOC_NEWTYPE(HIDRAW_SET_REPORT, struct hidraw_gen_descriptor32) #endif static d_open_t hidraw_open; static d_read_t hidraw_read; static d_write_t hidraw_write; static d_ioctl_t hidraw_ioctl; static d_poll_t hidraw_poll; static d_kqfilter_t hidraw_kqfilter; static d_priv_dtor_t hidraw_dtor; static struct cdevsw hidraw_cdevsw = { .d_version = D_VERSION, .d_open = hidraw_open, .d_read = hidraw_read, .d_write = hidraw_write, .d_ioctl = hidraw_ioctl, .d_poll = hidraw_poll, .d_kqfilter = hidraw_kqfilter, .d_name = "hidraw", }; static hid_intr_t hidraw_intr; static device_identify_t hidraw_identify; static device_probe_t hidraw_probe; static device_attach_t hidraw_attach; static device_detach_t hidraw_detach; static int hidraw_kqread(struct knote *, long); static void hidraw_kqdetach(struct knote *); static void hidraw_notify(struct hidraw_softc *); static struct filterops hidraw_filterops_read = { .f_isfd = 1, .f_detach = hidraw_kqdetach, .f_event = hidraw_kqread, }; static void hidraw_identify(driver_t *driver, device_t parent) { device_t child; if (device_find_child(parent, "hidraw", -1) == NULL) { child = BUS_ADD_CHILD(parent, 0, "hidraw", device_get_unit(parent)); if (child != NULL) hidbus_set_index(child, HIDRAW_INDEX); } } static int hidraw_probe(device_t self) { if (hidbus_get_index(self) != HIDRAW_INDEX) return (ENXIO); hidbus_set_desc(self, "Raw HID Device"); return (BUS_PROBE_GENERIC); } static int hidraw_attach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); struct make_dev_args mda; int error; sc->sc_dev = self; sc->sc_rdesc = hidbus_get_rdesc_info(self); sc->sc_hw = hid_get_device_info(self); /* Hidraw mode does not require report descriptor to work */ if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) device_printf(self, "no report descriptor\n"); mtx_init(&sc->sc_mtx, "hidraw lock", NULL, MTX_DEF); knlist_init_mtx(&sc->sc_rsel.si_note, &sc->sc_mtx); make_dev_args_init(&mda); mda.mda_flags = MAKEDEV_WAITOK; mda.mda_devsw = &hidraw_cdevsw; mda.mda_uid = UID_ROOT; mda.mda_gid = GID_OPERATOR; mda.mda_mode = 0600; mda.mda_si_drv1 = sc; error = make_dev_s(&mda, &sc->dev, "hidraw%d", device_get_unit(self)); if (error) { device_printf(self, "Can not create character device\n"); hidraw_detach(self); return (error); } #ifdef HIDRAW_MAKE_UHID_ALIAS (void)make_dev_alias(sc->dev, "uhid%d", device_get_unit(self)); #endif hidbus_set_lock(self, &sc->sc_mtx); hidbus_set_intr(self, hidraw_intr, sc); return (0); } static int hidraw_detach(device_t self) { struct hidraw_softc *sc = device_get_softc(self); DPRINTF("sc=%p\n", sc); if (sc->dev != NULL) { mtx_lock(&sc->sc_mtx); sc->dev->si_drv1 = NULL; /* Wake everyone */ hidraw_notify(sc); mtx_unlock(&sc->sc_mtx); destroy_dev(sc->dev); } knlist_clear(&sc->sc_rsel.si_note, 0); knlist_destroy(&sc->sc_rsel.si_note); seldrain(&sc->sc_rsel); mtx_destroy(&sc->sc_mtx); return (0); } void hidraw_intr(void *context, void *buf, hid_size_t len) { struct hidraw_softc *sc = context; int next; DPRINTFN(5, "len=%d\n", len); DPRINTFN(5, "data = %*D\n", len, buf, " "); next = (sc->sc_tail + 1) % HIDRAW_BUFFER_SIZE; if (sc->sc_state.quiet || next == sc->sc_head) return; bcopy(buf, sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize, len); /* Make sure we don't process old data */ if (len < sc->sc_rdesc->rdsize) bzero(sc->sc_q + sc->sc_tail * sc->sc_rdesc->rdsize + len, sc->sc_rdesc->isize - len); sc->sc_qlen[sc->sc_tail] = len; sc->sc_tail = next; hidraw_notify(sc); } static inline int hidraw_lock_queue(struct hidraw_softc *sc, bool flush) { int error = 0; mtx_assert(&sc->sc_mtx, MA_OWNED); if (flush) sc->sc_state.flush = true; ++sc->sc_sleepcnt; while (sc->sc_state.lock && error == 0) { /* Flush is requested. Wakeup all readers and forbid sleeps */ if (flush && sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } error = mtx_sleep(&sc->sc_sleepcnt, &sc->sc_mtx, PZERO | PCATCH, "hidrawio", 0); } --sc->sc_sleepcnt; if (flush) sc->sc_state.flush = false; if (error == 0) sc->sc_state.lock = true; return (error); } static inline void hidraw_unlock_queue(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); KASSERT(sc->sc_state.lock, ("input buffer is not locked")); if (sc->sc_sleepcnt != 0) wakeup_one(&sc->sc_sleepcnt); sc->sc_state.lock = false; } static int hidraw_open(struct cdev *dev, int flag, int mode, struct thread *td) { struct hidraw_softc *sc; int error; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); DPRINTF("sc=%p\n", sc); mtx_lock(&sc->sc_mtx); if (sc->sc_state.open) { mtx_unlock(&sc->sc_mtx); return (EBUSY); } sc->sc_state.open = true; mtx_unlock(&sc->sc_mtx); error = devfs_set_cdevpriv(sc, hidraw_dtor); if (error != 0) { mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); return (error); } sc->sc_q = malloc(sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); sc->sc_qlen = malloc(sizeof(hid_size_t) * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Set up interrupt pipe. */ sc->sc_state.immed = false; sc->sc_async = 0; sc->sc_state.uhid = false; /* hidraw mode is default */ sc->sc_state.quiet = false; sc->sc_head = sc->sc_tail = 0; sc->sc_fflags = flag; - hidbus_intr_start(sc->sc_dev); + hid_intr_start(sc->sc_dev); return (0); } static void hidraw_dtor(void *data) { struct hidraw_softc *sc = data; DPRINTF("sc=%p\n", sc); /* Disable interrupts. */ - hidbus_intr_stop(sc->sc_dev); + hid_intr_stop(sc->sc_dev); sc->sc_tail = sc->sc_head = 0; sc->sc_async = 0; free(sc->sc_q, M_DEVBUF); free(sc->sc_qlen, M_DEVBUF); sc->sc_q = NULL; mtx_lock(&sc->sc_mtx); sc->sc_state.open = false; mtx_unlock(&sc->sc_mtx); } static int hidraw_read(struct cdev *dev, struct uio *uio, int flag) { struct hidraw_softc *sc; size_t length; int error; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); mtx_lock(&sc->sc_mtx); error = dev->si_drv1 == NULL ? EIO : hidraw_lock_queue(sc, false); if (error != 0) { mtx_unlock(&sc->sc_mtx); return (error); } if (sc->sc_state.immed) { mtx_unlock(&sc->sc_mtx); DPRINTFN(1, "immed\n"); error = hid_get_report(sc->sc_dev, sc->sc_q, sc->sc_rdesc->isize, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); if (error == 0) error = uiomove(sc->sc_q, sc->sc_rdesc->isize, uio); mtx_lock(&sc->sc_mtx); goto exit; } while (sc->sc_tail == sc->sc_head && !sc->sc_state.flush) { if (flag & O_NONBLOCK) { error = EWOULDBLOCK; goto exit; } sc->sc_state.aslp = true; DPRINTFN(5, "sleep on %p\n", &sc->sc_q); error = mtx_sleep(&sc->sc_q, &sc->sc_mtx, PZERO | PCATCH, "hidrawrd", 0); DPRINTFN(5, "woke, error=%d\n", error); if (dev->si_drv1 == NULL) error = EIO; if (error) { sc->sc_state.aslp = false; goto exit; } } while (sc->sc_tail != sc->sc_head && uio->uio_resid > 0) { length = min(uio->uio_resid, sc->sc_state.uhid ? sc->sc_rdesc->isize : sc->sc_qlen[sc->sc_head]); mtx_unlock(&sc->sc_mtx); /* Copy the data to the user process. */ DPRINTFN(5, "got %lu chars\n", (u_long)length); error = uiomove(sc->sc_q + sc->sc_head * sc->sc_rdesc->rdsize, length, uio); mtx_lock(&sc->sc_mtx); if (error != 0) goto exit; /* Remove a small chunk from the input queue. */ sc->sc_head = (sc->sc_head + 1) % HIDRAW_BUFFER_SIZE; /* * In uhid mode transfer as many chunks as possible. Hidraw * packets are transferred one by one due to different length. */ if (!sc->sc_state.uhid) goto exit; } exit: hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); } static int hidraw_write(struct cdev *dev, struct uio *uio, int flag) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE], *buf; struct hidraw_softc *sc; int error; int size; size_t buf_offset; uint8_t id = 0; DPRINTFN(1, "\n"); sc = dev->si_drv1; if (sc == NULL) return (EIO); if (sc->sc_rdesc->osize == 0) return (EOPNOTSUPP); buf_offset = 0; if (sc->sc_state.uhid) { size = sc->sc_rdesc->osize; if (uio->uio_resid != size) return (EINVAL); } else { size = uio->uio_resid; if (size < 2) return (EINVAL); /* Strip leading 0 if the device doesnt use numbered reports */ error = uiomove(&id, 1, uio); if (error) return (error); if (id != 0) buf_offset++; else size--; /* Check if underlying driver could process this request */ if (size > sc->sc_rdesc->wrsize) return (ENOBUFS); } buf = HIDRAW_LOCAL_ALLOC(local_buf, size); buf[0] = id; error = uiomove(buf + buf_offset, uio->uio_resid, uio); if (error == 0) error = hid_write(sc->sc_dev, buf, size); HIDRAW_LOCAL_FREE(local_buf, buf); return (error); } #ifdef COMPAT_FREEBSD32 static void update_hgd32(const struct hidraw_gen_descriptor *hgd, struct hidraw_gen_descriptor32 *hgd32) { /* Don't update hgd_data pointer */ CP(*hgd, *hgd32, hgd_lang_id); CP(*hgd, *hgd32, hgd_maxlen); CP(*hgd, *hgd32, hgd_actlen); CP(*hgd, *hgd32, hgd_offset); CP(*hgd, *hgd32, hgd_config_index); CP(*hgd, *hgd32, hgd_string_index); CP(*hgd, *hgd32, hgd_iface_index); CP(*hgd, *hgd32, hgd_altif_index); CP(*hgd, *hgd32, hgd_endpt_index); CP(*hgd, *hgd32, hgd_report_type); /* Don't update reserved */ } #endif static int hidraw_ioctl(struct cdev *dev, u_long cmd, caddr_t addr, int flag, struct thread *td) { uint8_t local_buf[HIDRAW_LOCAL_BUFSIZE]; #ifdef COMPAT_FREEBSD32 struct hidraw_gen_descriptor local_hgd; struct hidraw_gen_descriptor32 *hgd32 = NULL; #endif void *buf; struct hidraw_softc *sc; struct hidraw_gen_descriptor *hgd; struct hidraw_report_descriptor *hrd; struct hidraw_devinfo *hdi; const char *devname; uint32_t size; int id, len; int error = 0; DPRINTFN(2, "cmd=%lx\n", cmd); sc = dev->si_drv1; if (sc == NULL) return (EIO); hgd = (struct hidraw_gen_descriptor *)addr; #ifdef COMPAT_FREEBSD32 switch (cmd) { case HIDRAW_GET_REPORT_DESC32: case HIDRAW_GET_REPORT32: case HIDRAW_SET_REPORT_DESC32: case HIDRAW_SET_REPORT32: cmd = _IOC_NEWTYPE(cmd, struct hidraw_gen_descriptor); hgd32 = (struct hidraw_gen_descriptor32 *)addr; hgd = &local_hgd; PTRIN_CP(*hgd32, *hgd, hgd_data); CP(*hgd32, *hgd, hgd_lang_id); CP(*hgd32, *hgd, hgd_maxlen); CP(*hgd32, *hgd, hgd_actlen); CP(*hgd32, *hgd, hgd_offset); CP(*hgd32, *hgd, hgd_config_index); CP(*hgd32, *hgd, hgd_string_index); CP(*hgd32, *hgd, hgd_iface_index); CP(*hgd32, *hgd, hgd_altif_index); CP(*hgd32, *hgd, hgd_endpt_index); CP(*hgd32, *hgd, hgd_report_type); /* Don't copy reserved */ break; } #endif /* fixed-length ioctls handling */ switch (cmd) { case FIONBIO: /* All handled in the upper FS layer. */ return (0); case FIOASYNC: mtx_lock(&sc->sc_mtx); if (*(int *)addr) { if (sc->sc_async == NULL) { sc->sc_async = td->td_proc; DPRINTF("FIOASYNC %p\n", sc->sc_async); } else error = EBUSY; } else sc->sc_async = NULL; mtx_unlock(&sc->sc_mtx); return (error); /* XXX this is not the most general solution. */ case TIOCSPGRP: mtx_lock(&sc->sc_mtx); if (sc->sc_async == NULL) error = EINVAL; else if (*(int *)addr != sc->sc_async->p_pgid) error = EPERM; mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_GET_REPORT_DESC: if (sc->sc_rdesc->data == NULL || sc->sc_rdesc->len == 0) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.uhid = true; mtx_unlock(&sc->sc_mtx); if (sc->sc_rdesc->len > hgd->hgd_maxlen) { size = hgd->hgd_maxlen; } else { size = sc->sc_rdesc->len; } hgd->hgd_actlen = size; #ifdef COMPAT_FREEBSD32 if (hgd32 != NULL) update_hgd32(hgd, hgd32); #endif if (hgd->hgd_data == NULL) return (0); /* descriptor length only */ return (copyout(sc->sc_rdesc->data, hgd->hgd_data, size)); case HIDRAW_SET_REPORT_DESC: if (!(sc->sc_fflags & FWRITE)) return (EPERM); /* check privileges */ error = priv_check(curthread, PRIV_DRIVER); if (error) return (error); /* Stop interrupts and clear input report buffer */ mtx_lock(&sc->sc_mtx); sc->sc_tail = sc->sc_head = 0; error = hidraw_lock_queue(sc, true); if (error == 0) sc->sc_state.quiet = true; mtx_unlock(&sc->sc_mtx); if (error != 0) return(error); buf = HIDRAW_LOCAL_ALLOC(local_buf, hgd->hgd_maxlen); copyin(hgd->hgd_data, buf, hgd->hgd_maxlen); bus_topo_lock(); error = hid_set_report_descr(sc->sc_dev, buf, hgd->hgd_maxlen); bus_topo_unlock(); HIDRAW_LOCAL_FREE(local_buf, buf); /* Realloc hidraw input queue */ if (error == 0) sc->sc_q = realloc(sc->sc_q, sc->sc_rdesc->rdsize * HIDRAW_BUFFER_SIZE, M_DEVBUF, M_ZERO | M_WAITOK); /* Start interrupts again */ mtx_lock(&sc->sc_mtx); sc->sc_state.quiet = false; hidraw_unlock_queue(sc); mtx_unlock(&sc->sc_mtx); return (error); case HIDRAW_SET_IMMED: if (!(sc->sc_fflags & FREAD)) return (EPERM); if (*(int *)addr) { /* XXX should read into ibuf, but does it matter? */ size = sc->sc_rdesc->isize; buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_report(sc->sc_dev, buf, size, NULL, HID_INPUT_REPORT, sc->sc_rdesc->iid); HIDRAW_LOCAL_FREE(local_buf, buf); if (error) return (EOPNOTSUPP); mtx_lock(&sc->sc_mtx); sc->sc_state.immed = true; mtx_unlock(&sc->sc_mtx); } else { mtx_lock(&sc->sc_mtx); sc->sc_state.immed = false; mtx_unlock(&sc->sc_mtx); } return (0); case HIDRAW_GET_REPORT: if (!(sc->sc_fflags & FREAD)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } if (id != 0) copyin(hgd->hgd_data, &id, 1); size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_report(sc->sc_dev, buf, size, NULL, hgd->hgd_report_type, id); if (!error) error = copyout(buf, hgd->hgd_data, size); HIDRAW_LOCAL_FREE(local_buf, buf); #ifdef COMPAT_FREEBSD32 /* * HIDRAW_GET_REPORT is declared _IOWR, but hgd is not written * so we don't call update_hgd32(). */ #endif return (error); case HIDRAW_SET_REPORT: if (!(sc->sc_fflags & FWRITE)) return (EPERM); switch (hgd->hgd_report_type) { case HID_INPUT_REPORT: size = sc->sc_rdesc->isize; id = sc->sc_rdesc->iid; break; case HID_OUTPUT_REPORT: size = sc->sc_rdesc->osize; id = sc->sc_rdesc->oid; break; case HID_FEATURE_REPORT: size = sc->sc_rdesc->fsize; id = sc->sc_rdesc->fid; break; default: return (EINVAL); } size = MIN(hgd->hgd_maxlen, size); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); copyin(hgd->hgd_data, buf, size); if (id != 0) id = *(uint8_t *)buf; error = hid_set_report(sc->sc_dev, buf, size, hgd->hgd_report_type, id); HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDRAW_GET_REPORT_ID: *(int *)addr = 0; /* XXX: we only support reportid 0? */ return (0); case HIDIOCGRDESCSIZE: *(int *)addr = sc->sc_hw->rdescsize; return (0); case HIDIOCGRDESC: hrd = *(struct hidraw_report_descriptor **)addr; error = copyin(&hrd->size, &size, sizeof(uint32_t)); if (error) return (error); /* * HID_MAX_DESCRIPTOR_SIZE-1 is a limit of report descriptor * size in current Linux implementation. */ if (size >= HID_MAX_DESCRIPTOR_SIZE) return (EINVAL); buf = HIDRAW_LOCAL_ALLOC(local_buf, size); error = hid_get_rdesc(sc->sc_dev, buf, size); if (error == 0) { size = MIN(size, sc->sc_rdesc->len); error = copyout(buf, hrd->value, size); } HIDRAW_LOCAL_FREE(local_buf, buf); return (error); case HIDIOCGRAWINFO: hdi = (struct hidraw_devinfo *)addr; hdi->bustype = sc->sc_hw->idBus; hdi->vendor = sc->sc_hw->idVendor; hdi->product = sc->sc_hw->idProduct; return (0); } /* variable-length ioctls handling */ len = IOCPARM_LEN(cmd); switch (IOCBASECMD(cmd)) { case HIDIOCGRAWNAME(0): strlcpy(addr, sc->sc_hw->name, len); td->td_retval[0] = min(strlen(sc->sc_hw->name) + 1, len); return (0); case HIDIOCGRAWPHYS(0): devname = device_get_nameunit(sc->sc_dev); strlcpy(addr, devname, len); td->td_retval[0] = min(strlen(devname) + 1, len); return (0); case HIDIOCSFEATURE(0): if (!(sc->sc_fflags & FWRITE)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } return (hid_set_report(sc->sc_dev, addr, len, HID_FEATURE_REPORT, id)); case HIDIOCGFEATURE(0): if (!(sc->sc_fflags & FREAD)) return (EPERM); if (len < 2) return (EINVAL); id = *(uint8_t *)addr; if (id == 0) { addr = (uint8_t *)addr + 1; len--; } return (hid_get_report(sc->sc_dev, addr, len, NULL, HID_FEATURE_REPORT, id)); case HIDIOCGRAWUNIQ(0): strlcpy(addr, sc->sc_hw->serial, len); td->td_retval[0] = min(strlen(sc->sc_hw->serial) + 1, len); return (0); } return (EINVAL); } static int hidraw_poll(struct cdev *dev, int events, struct thread *td) { struct hidraw_softc *sc; int revents = 0; sc = dev->si_drv1; if (sc == NULL) return (POLLHUP); if (events & (POLLOUT | POLLWRNORM) && (sc->sc_fflags & FWRITE)) revents |= events & (POLLOUT | POLLWRNORM); if (events & (POLLIN | POLLRDNORM) && (sc->sc_fflags & FREAD)) { mtx_lock(&sc->sc_mtx); if (sc->sc_head != sc->sc_tail) revents |= events & (POLLIN | POLLRDNORM); else { sc->sc_state.sel = true; selrecord(td, &sc->sc_rsel); } mtx_unlock(&sc->sc_mtx); } return (revents); } static int hidraw_kqfilter(struct cdev *dev, struct knote *kn) { struct hidraw_softc *sc; sc = dev->si_drv1; if (sc == NULL) return (ENXIO); switch(kn->kn_filter) { case EVFILT_READ: if (sc->sc_fflags & FREAD) { kn->kn_fop = &hidraw_filterops_read; break; } /* FALLTHROUGH */ default: return(EINVAL); } kn->kn_hook = sc; knlist_add(&sc->sc_rsel.si_note, kn, 0); return (0); } static int hidraw_kqread(struct knote *kn, long hint) { struct hidraw_softc *sc; int ret; sc = kn->kn_hook; mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->dev->si_drv1 == NULL) { kn->kn_flags |= EV_EOF; ret = 1; } else ret = (sc->sc_head != sc->sc_tail) ? 1 : 0; return (ret); } static void hidraw_kqdetach(struct knote *kn) { struct hidraw_softc *sc; sc = kn->kn_hook; knlist_remove(&sc->sc_rsel.si_note, kn, 0); } static void hidraw_notify(struct hidraw_softc *sc) { mtx_assert(&sc->sc_mtx, MA_OWNED); if (sc->sc_state.aslp) { sc->sc_state.aslp = false; DPRINTFN(5, "waking %p\n", &sc->sc_q); wakeup(&sc->sc_q); } if (sc->sc_state.sel) { sc->sc_state.sel = false; selwakeuppri(&sc->sc_rsel, PZERO); } if (sc->sc_async != NULL) { DPRINTFN(3, "sending SIGIO %p\n", sc->sc_async); PROC_LOCK(sc->sc_async); kern_psignal(sc->sc_async, SIGIO); PROC_UNLOCK(sc->sc_async); } KNOTE_LOCKED(&sc->sc_rsel.si_note, 0); } static device_method_t hidraw_methods[] = { /* Device interface */ DEVMETHOD(device_identify, hidraw_identify), DEVMETHOD(device_probe, hidraw_probe), DEVMETHOD(device_attach, hidraw_attach), DEVMETHOD(device_detach, hidraw_detach), DEVMETHOD_END }; static driver_t hidraw_driver = { "hidraw", hidraw_methods, sizeof(struct hidraw_softc) }; DRIVER_MODULE(hidraw, hidbus, hidraw_driver, NULL, NULL); MODULE_DEPEND(hidraw, hidbus, 1, 1, 1); MODULE_DEPEND(hidraw, hid, 1, 1, 1); MODULE_VERSION(hidraw, 1); diff --git a/sys/dev/hid/hkbd.c b/sys/dev/hid/hkbd.c index 922256a8f23b..b0aa5be11a1e 100644 --- a/sys/dev/hid/hkbd.c +++ b/sys/dev/hid/hkbd.c @@ -1,2030 +1,2030 @@ #include __FBSDID("$FreeBSD$"); /*- * 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. * */ /* * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ #include "opt_hid.h" #include "opt_kbd.h" #include "opt_hkbd.h" #include "opt_evdev.h" #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 HID_DEBUG_VAR hkbd_debug #include #include #include #include #ifdef EVDEV_SUPPORT #include #include #endif #include #include #include #include /* the initial key map, accent map and fkey strings */ #if defined(HKBD_DFLT_KEYMAP) && !defined(KLD_MODULE) #define KBD_DFLT_KEYMAP #include "ukbdmap.h" #endif /* the following file must be included after "ukbdmap.h" */ #include #ifdef HID_DEBUG static int hkbd_debug = 0; static int hkbd_no_leds = 0; static SYSCTL_NODE(_hw_hid, OID_AUTO, hkbd, CTLFLAG_RW, 0, "USB keyboard"); SYSCTL_INT(_hw_hid_hkbd, OID_AUTO, debug, CTLFLAG_RWTUN, &hkbd_debug, 0, "Debug level"); SYSCTL_INT(_hw_hid_hkbd, OID_AUTO, no_leds, CTLFLAG_RWTUN, &hkbd_no_leds, 0, "Disables setting of keyboard leds"); #endif #define INPUT_EPOCH global_epoch_preempt #define HKBD_EMULATE_ATSCANCODE 1 #define HKBD_DRIVER_NAME "hkbd" #define HKBD_NKEYCODE 256 /* units */ #define HKBD_IN_BUF_SIZE (4 * HKBD_NKEYCODE) /* scancodes */ #define HKBD_IN_BUF_FULL ((HKBD_IN_BUF_SIZE / 2) - 1) /* scancodes */ #define HKBD_NFKEY (sizeof(fkey_tab)/sizeof(fkey_tab[0])) /* units */ #define HKBD_BUFFER_SIZE 64 /* bytes */ #define HKBD_KEY_PRESSED(map, key) ({ \ CTASSERT((key) >= 0 && (key) < HKBD_NKEYCODE); \ bit_test(map, key); \ }) #define MOD_EJECT 0x01 #define MOD_FN 0x02 #define MOD_MIN 0xe0 #define MOD_MAX 0xe7 struct hkbd_softc { device_t sc_dev; keyboard_t sc_kbd; keymap_t sc_keymap; accentmap_t sc_accmap; fkeytab_t sc_fkeymap[HKBD_NFKEY]; bitstr_t bit_decl(sc_loc_key_valid, HKBD_NKEYCODE); struct hid_location sc_loc_apple_eject; struct hid_location sc_loc_apple_fn; struct hid_location sc_loc_key[HKBD_NKEYCODE]; struct hid_location sc_loc_numlock; struct hid_location sc_loc_capslock; struct hid_location sc_loc_scrolllock; struct mtx sc_mtx; struct task sc_task; struct callout sc_callout; /* All reported keycodes */ bitstr_t bit_decl(sc_ndata, HKBD_NKEYCODE); bitstr_t bit_decl(sc_odata, HKBD_NKEYCODE); /* Keycodes reported in array fields only */ bitstr_t bit_decl(sc_ndata0, HKBD_NKEYCODE); bitstr_t bit_decl(sc_odata0, HKBD_NKEYCODE); struct thread *sc_poll_thread; #ifdef EVDEV_SUPPORT struct evdev_dev *sc_evdev; #endif sbintime_t sc_co_basetime; int sc_delay; uint32_t sc_repeat_time; uint32_t sc_input[HKBD_IN_BUF_SIZE]; /* input buffer */ uint32_t sc_time_ms; uint32_t sc_composed_char; /* composed char code, if non-zero */ #ifdef HKBD_EMULATE_ATSCANCODE uint32_t sc_buffered_char[2]; #endif uint32_t sc_flags; /* flags */ #define HKBD_FLAG_COMPOSE 0x00000001 #define HKBD_FLAG_POLLING 0x00000002 #define HKBD_FLAG_ATTACHED 0x00000010 #define HKBD_FLAG_GONE 0x00000020 #define HKBD_FLAG_HID_MASK 0x003fffc0 #define HKBD_FLAG_APPLE_EJECT 0x00000040 #define HKBD_FLAG_APPLE_FN 0x00000080 #define HKBD_FLAG_APPLE_SWAP 0x00000100 #define HKBD_FLAG_NUMLOCK 0x00080000 #define HKBD_FLAG_CAPSLOCK 0x00100000 #define HKBD_FLAG_SCROLLLOCK 0x00200000 int sc_mode; /* input mode (K_XLATE,K_RAW,K_CODE) */ int sc_state; /* shift/lock key state */ int sc_accents; /* accent key index (> 0) */ int sc_polling; /* polling recursion count */ int sc_led_size; int sc_kbd_size; uint32_t sc_inputhead; uint32_t sc_inputtail; uint8_t sc_iface_index; uint8_t sc_iface_no; uint8_t sc_id_apple_eject; uint8_t sc_id_apple_fn; uint8_t sc_id_loc_key[HKBD_NKEYCODE]; uint8_t sc_id_leds; uint8_t sc_kbd_id; uint8_t sc_repeat_key; uint8_t sc_buffer[HKBD_BUFFER_SIZE]; }; #define KEY_NONE 0x00 #define KEY_ERROR 0x01 #define KEY_PRESS 0 #define KEY_RELEASE 0x400 #define KEY_INDEX(c) ((c) & 0xFF) #define SCAN_PRESS 0 #define SCAN_RELEASE 0x80 #define SCAN_PREFIX_E0 0x100 #define SCAN_PREFIX_E1 0x200 #define SCAN_PREFIX_CTL 0x400 #define SCAN_PREFIX_SHIFT 0x800 #define SCAN_PREFIX (SCAN_PREFIX_E0 | SCAN_PREFIX_E1 | \ SCAN_PREFIX_CTL | SCAN_PREFIX_SHIFT) #define SCAN_CHAR(c) ((c) & 0x7f) #define HKBD_LOCK(sc) do { \ if (!HID_IN_POLLING_MODE()) \ mtx_lock(&(sc)->sc_mtx); \ } while (0) #define HKBD_UNLOCK(sc) do { \ if (!HID_IN_POLLING_MODE()) \ mtx_unlock(&(sc)->sc_mtx); \ } while (0) #define HKBD_LOCK_ASSERT(sc) do { \ if (!HID_IN_POLLING_MODE()) \ mtx_assert(&(sc)->sc_mtx, MA_OWNED); \ } while (0) #define SYSCONS_LOCK() do { \ if (!HID_IN_POLLING_MODE()) \ mtx_lock(&Giant); \ } while (0) #define SYSCONS_UNLOCK() do { \ if (!HID_IN_POLLING_MODE()) \ mtx_unlock(&Giant); \ } while (0) #define SYSCONS_LOCK_ASSERT() do { \ if (!HID_IN_POLLING_MODE()) \ mtx_assert(&Giant, MA_OWNED); \ } while (0) #define NN 0 /* no translation */ /* * Translate USB keycodes to AT keyboard scancodes. */ /* * FIXME: Mac USB keyboard generates: * 0x53: keypad NumLock/Clear * 0x66: Power * 0x67: keypad = * 0x68: F13 * 0x69: F14 * 0x6a: F15 * * USB Apple Keyboard JIS generates: * 0x90: Kana * 0x91: Eisu */ static const uint8_t hkbd_trtab[256] = { 0, 0, 0, 0, 30, 48, 46, 32, /* 00 - 07 */ 18, 33, 34, 35, 23, 36, 37, 38, /* 08 - 0F */ 50, 49, 24, 25, 16, 19, 31, 20, /* 10 - 17 */ 22, 47, 17, 45, 21, 44, 2, 3, /* 18 - 1F */ 4, 5, 6, 7, 8, 9, 10, 11, /* 20 - 27 */ 28, 1, 14, 15, 57, 12, 13, 26, /* 28 - 2F */ 27, 43, 43, 39, 40, 41, 51, 52, /* 30 - 37 */ 53, 58, 59, 60, 61, 62, 63, 64, /* 38 - 3F */ 65, 66, 67, 68, 87, 88, 92, 70, /* 40 - 47 */ 104, 102, 94, 96, 103, 99, 101, 98, /* 48 - 4F */ 97, 100, 95, 69, 91, 55, 74, 78,/* 50 - 57 */ 89, 79, 80, 81, 75, 76, 77, 71, /* 58 - 5F */ 72, 73, 82, 83, 86, 107, 122, NN, /* 60 - 67 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 68 - 6F */ NN, NN, NN, NN, 115, 108, 111, 113, /* 70 - 77 */ 109, 110, 112, 118, 114, 116, 117, 119, /* 78 - 7F */ 121, 120, NN, NN, NN, NN, NN, 123, /* 80 - 87 */ 124, 125, 126, 127, 128, NN, NN, NN, /* 88 - 8F */ 129, 130, NN, NN, NN, NN, NN, NN, /* 90 - 97 */ NN, NN, NN, NN, NN, NN, NN, NN, /* 98 - 9F */ NN, NN, NN, NN, NN, NN, NN, NN, /* A0 - A7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* A8 - AF */ NN, NN, NN, NN, NN, NN, NN, NN, /* B0 - B7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* B8 - BF */ NN, NN, NN, NN, NN, NN, NN, NN, /* C0 - C7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* C8 - CF */ NN, NN, NN, NN, NN, NN, NN, NN, /* D0 - D7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* D8 - DF */ 29, 42, 56, 105, 90, 54, 93, 106, /* E0 - E7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* E8 - EF */ NN, NN, NN, NN, NN, NN, NN, NN, /* F0 - F7 */ NN, NN, NN, NN, NN, NN, NN, NN, /* F8 - FF */ }; static const uint8_t hkbd_boot_desc[] = { HID_KBD_BOOTPROTO_DESCR() }; /* prototypes */ static void hkbd_timeout(void *); static int hkbd_set_leds(struct hkbd_softc *, uint8_t); static int hkbd_set_typematic(keyboard_t *, int); #ifdef HKBD_EMULATE_ATSCANCODE static uint32_t hkbd_atkeycode(int, const bitstr_t *); static int hkbd_key2scan(struct hkbd_softc *, int, const bitstr_t *, int); #endif static uint32_t hkbd_read_char(keyboard_t *, int); static void hkbd_clear_state(keyboard_t *); static int hkbd_ioctl(keyboard_t *, u_long, caddr_t); static int hkbd_enable(keyboard_t *); static int hkbd_disable(keyboard_t *); static void hkbd_interrupt(struct hkbd_softc *); static task_fn_t hkbd_event_keyinput; static device_probe_t hkbd_probe; static device_attach_t hkbd_attach; static device_detach_t hkbd_detach; static device_resume_t hkbd_resume; #ifdef EVDEV_SUPPORT static evdev_event_t hkbd_ev_event; static const struct evdev_methods hkbd_evdev_methods = { .ev_event = hkbd_ev_event, }; #endif static bool hkbd_any_key_pressed(struct hkbd_softc *sc) { int result; bit_ffs(sc->sc_odata, HKBD_NKEYCODE, &result); return (result != -1); } static bool hkbd_any_key_valid(struct hkbd_softc *sc) { int result; bit_ffs(sc->sc_loc_key_valid, HKBD_NKEYCODE, &result); return (result != -1); } static bool hkbd_is_modifier_key(uint32_t key) { return (key >= MOD_MIN && key <= MOD_MAX); } static void hkbd_start_timer(struct hkbd_softc *sc) { sbintime_t delay, now, prec; now = sbinuptime(); /* check if initial delay passed and fallback to key repeat delay */ if (sc->sc_delay == 0) sc->sc_delay = sc->sc_kbd.kb_delay2; /* compute timeout */ delay = SBT_1MS * sc->sc_delay; sc->sc_co_basetime += delay; /* check if we are running behind */ if (sc->sc_co_basetime < now) sc->sc_co_basetime = now; /* This is rarely called, so prefer precision to efficiency. */ prec = qmin(delay >> 7, SBT_1MS * 10); if (!HID_IN_POLLING_MODE()) callout_reset_sbt(&sc->sc_callout, sc->sc_co_basetime, prec, hkbd_timeout, sc, C_ABSOLUTE); } static void hkbd_put_key(struct hkbd_softc *sc, uint32_t key) { uint32_t tail; HKBD_LOCK_ASSERT(sc); DPRINTF("0x%02x (%d) %s\n", key, key, (key & KEY_RELEASE) ? "released" : "pressed"); #ifdef EVDEV_SUPPORT if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL) evdev_push_event(sc->sc_evdev, EV_KEY, evdev_hid2key(KEY_INDEX(key)), !(key & KEY_RELEASE)); if (sc->sc_evdev != NULL && evdev_is_grabbed(sc->sc_evdev)) return; #endif tail = (sc->sc_inputtail + 1) % HKBD_IN_BUF_SIZE; if (tail != atomic_load_acq_32(&sc->sc_inputhead)) { sc->sc_input[sc->sc_inputtail] = key; atomic_store_rel_32(&sc->sc_inputtail, tail); } else { DPRINTF("input buffer is full\n"); } } static void hkbd_do_poll(struct hkbd_softc *sc, uint8_t wait) { SYSCONS_LOCK_ASSERT(); KASSERT((sc->sc_flags & HKBD_FLAG_POLLING) != 0, ("hkbd_do_poll called when not polling\n")); DPRINTFN(2, "polling\n"); if (!HID_IN_POLLING_MODE()) { /* * In this context the kernel is polling for input, * but the USB subsystem works in normal interrupt-driven * mode, so we just wait on the USB threads to do the job. * Note that we currently hold the Giant, but it's also used * as the transfer mtx, so we must release it while waiting. */ while (sc->sc_inputhead == atomic_load_acq_32(&sc->sc_inputtail)) { /* * Give USB threads a chance to run. Note that * kern_yield performs DROP_GIANT + PICKUP_GIANT. */ kern_yield(PRI_UNCHANGED); if (!wait) break; } return; } while (sc->sc_inputhead == sc->sc_inputtail) { - hidbus_intr_poll(sc->sc_dev); + hid_intr_poll(sc->sc_dev); /* Delay-optimised support for repetition of keys */ if (hkbd_any_key_pressed(sc)) { /* a key is pressed - need timekeeping */ DELAY(1000); /* 1 millisecond has passed */ sc->sc_time_ms += 1; } hkbd_interrupt(sc); if (!wait) break; } } static int32_t hkbd_get_key(struct hkbd_softc *sc, uint8_t wait) { uint32_t head; int32_t c; SYSCONS_LOCK_ASSERT(); KASSERT(!HID_IN_POLLING_MODE() || (sc->sc_flags & HKBD_FLAG_POLLING) != 0, ("not polling in kdb or panic\n")); if (sc->sc_flags & HKBD_FLAG_POLLING) hkbd_do_poll(sc, wait); head = sc->sc_inputhead; if (head == atomic_load_acq_32(&sc->sc_inputtail)) { c = -1; } else { c = sc->sc_input[head]; head = (head + 1) % HKBD_IN_BUF_SIZE; atomic_store_rel_32(&sc->sc_inputhead, head); } return (c); } static void hkbd_interrupt(struct hkbd_softc *sc) { const uint32_t now = sc->sc_time_ms; unsigned key; HKBD_LOCK_ASSERT(sc); /* * Check for key changes, the order is: * 1. Regular keys up * 2. Modifier keys up * 3. Modifier keys down * 4. Regular keys down * * This allows devices which send events changing the state of * both a modifier key and a regular key, to be correctly * translated. */ bit_foreach(sc->sc_odata, HKBD_NKEYCODE, key) { if (hkbd_is_modifier_key(key) || bit_test(sc->sc_ndata, key)) continue; hkbd_put_key(sc, key | KEY_RELEASE); /* clear repeating key, if any */ if (sc->sc_repeat_key == key) sc->sc_repeat_key = 0; } bit_foreach_at(sc->sc_odata, MOD_MIN, MOD_MAX + 1, key) if (!bit_test(sc->sc_ndata, key)) hkbd_put_key(sc, key | KEY_RELEASE); bit_foreach_at(sc->sc_ndata, MOD_MIN, MOD_MAX + 1, key) if (!bit_test(sc->sc_odata, key)) hkbd_put_key(sc, key | KEY_PRESS); bit_foreach(sc->sc_ndata, HKBD_NKEYCODE, key) { if (hkbd_is_modifier_key(key) || bit_test(sc->sc_odata, key)) continue; hkbd_put_key(sc, key | KEY_PRESS); sc->sc_co_basetime = sbinuptime(); sc->sc_delay = sc->sc_kbd.kb_delay1; hkbd_start_timer(sc); /* set repeat time for last key */ sc->sc_repeat_time = now + sc->sc_kbd.kb_delay1; sc->sc_repeat_key = key; } /* synchronize old data with new data */ memcpy(sc->sc_odata0, sc->sc_ndata0, bitstr_size(HKBD_NKEYCODE)); memcpy(sc->sc_odata, sc->sc_ndata, bitstr_size(HKBD_NKEYCODE)); /* check if last key is still pressed */ if (sc->sc_repeat_key != 0) { const int32_t dtime = (sc->sc_repeat_time - now); /* check if time has elapsed */ if (dtime <= 0) { hkbd_put_key(sc, sc->sc_repeat_key | KEY_PRESS); sc->sc_repeat_time = now + sc->sc_kbd.kb_delay2; } } #ifdef EVDEV_SUPPORT if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && sc->sc_evdev != NULL) evdev_sync(sc->sc_evdev); if (sc->sc_evdev != NULL && evdev_is_grabbed(sc->sc_evdev)) return; #endif /* wakeup keyboard system */ if (!HID_IN_POLLING_MODE()) taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); } static void hkbd_event_keyinput(void *context, int pending) { struct hkbd_softc *sc = context; int c; SYSCONS_LOCK_ASSERT(); if ((sc->sc_flags & HKBD_FLAG_POLLING) != 0) return; if (sc->sc_inputhead == atomic_load_acq_32(&sc->sc_inputtail)) return; if (KBD_IS_ACTIVE(&sc->sc_kbd) && KBD_IS_BUSY(&sc->sc_kbd)) { /* let the callback function process the input */ (sc->sc_kbd.kb_callback.kc_func) (&sc->sc_kbd, KBDIO_KEYINPUT, sc->sc_kbd.kb_callback.kc_arg); } else { /* read and discard the input, no one is waiting for it */ do { c = hkbd_read_char(&sc->sc_kbd, 0); } while (c != NOKEY); } } static void hkbd_timeout(void *arg) { struct hkbd_softc *sc = arg; #ifdef EVDEV_SUPPORT struct epoch_tracker et; #endif HKBD_LOCK_ASSERT(sc); sc->sc_time_ms += sc->sc_delay; sc->sc_delay = 0; #ifdef EVDEV_SUPPORT epoch_enter_preempt(INPUT_EPOCH, &et); #endif hkbd_interrupt(sc); #ifdef EVDEV_SUPPORT epoch_exit_preempt(INPUT_EPOCH, &et); #endif /* Make sure any leftover key events gets read out */ taskqueue_enqueue(taskqueue_swi_giant, &sc->sc_task); if (hkbd_any_key_pressed(sc) || atomic_load_acq_32(&sc->sc_inputhead) != sc->sc_inputtail) { hkbd_start_timer(sc); } } static uint32_t hkbd_apple_fn(uint32_t keycode) { switch (keycode) { case 0x28: return 0x49; /* RETURN -> INSERT */ case 0x2a: return 0x4c; /* BACKSPACE -> DEL */ case 0x50: return 0x4a; /* LEFT ARROW -> HOME */ case 0x4f: return 0x4d; /* RIGHT ARROW -> END */ case 0x52: return 0x4b; /* UP ARROW -> PGUP */ case 0x51: return 0x4e; /* DOWN ARROW -> PGDN */ default: return keycode; } } static uint32_t hkbd_apple_swap(uint32_t keycode) { switch (keycode) { case 0x35: return 0x64; case 0x64: return 0x35; default: return keycode; } } static void hkbd_intr_callback(void *context, void *data, hid_size_t len) { struct hkbd_softc *sc = context; uint8_t *buf = data; uint32_t i; uint8_t id = 0; uint8_t modifiers; HKBD_LOCK_ASSERT(sc); DPRINTF("actlen=%d bytes\n", len); if (len == 0) { DPRINTF("zero length data\n"); return; } if (sc->sc_kbd_id != 0) { /* check and remove HID ID byte */ id = buf[0]; buf++; len--; if (len == 0) { DPRINTF("zero length data\n"); return; } } /* clear temporary storage */ if (bit_test(sc->sc_loc_key_valid, 0) && id == sc->sc_id_loc_key[0]) { bit_foreach(sc->sc_ndata0, HKBD_NKEYCODE, i) bit_clear(sc->sc_ndata, i); memset(&sc->sc_ndata0, 0, bitstr_size(HKBD_NKEYCODE)); } bit_foreach(sc->sc_ndata, HKBD_NKEYCODE, i) if (id == sc->sc_id_loc_key[i]) bit_clear(sc->sc_ndata, i); /* clear modifiers */ modifiers = 0; /* scan through HID data */ if ((sc->sc_flags & HKBD_FLAG_APPLE_EJECT) && (id == sc->sc_id_apple_eject)) { if (hid_get_data(buf, len, &sc->sc_loc_apple_eject)) modifiers |= MOD_EJECT; } if ((sc->sc_flags & HKBD_FLAG_APPLE_FN) && (id == sc->sc_id_apple_fn)) { if (hid_get_data(buf, len, &sc->sc_loc_apple_fn)) modifiers |= MOD_FN; } bit_foreach(sc->sc_loc_key_valid, HKBD_NKEYCODE, i) { if (id != sc->sc_id_loc_key[i]) { continue; /* invalid HID ID */ } else if (i == 0) { struct hid_location tmp_loc = sc->sc_loc_key[0]; /* range check array size */ if (tmp_loc.count > HKBD_NKEYCODE) tmp_loc.count = HKBD_NKEYCODE; while (tmp_loc.count--) { uint32_t key = hid_get_udata(buf, len, &tmp_loc); /* advance to next location */ tmp_loc.pos += tmp_loc.size; if (key == KEY_ERROR) { DPRINTF("KEY_ERROR\n"); memcpy(sc->sc_ndata0, sc->sc_odata0, bitstr_size(HKBD_NKEYCODE)); memcpy(sc->sc_ndata, sc->sc_odata, bitstr_size(HKBD_NKEYCODE)); return; /* ignore */ } if (modifiers & MOD_FN) key = hkbd_apple_fn(key); if (sc->sc_flags & HKBD_FLAG_APPLE_SWAP) key = hkbd_apple_swap(key); if (key == KEY_NONE || key >= HKBD_NKEYCODE) continue; /* set key in bitmap */ bit_set(sc->sc_ndata, key); bit_set(sc->sc_ndata0, key); } } else if (hid_get_data(buf, len, &sc->sc_loc_key[i])) { uint32_t key = i; if (modifiers & MOD_FN) key = hkbd_apple_fn(key); if (sc->sc_flags & HKBD_FLAG_APPLE_SWAP) key = hkbd_apple_swap(key); if (key == KEY_NONE || key == KEY_ERROR || key >= HKBD_NKEYCODE) continue; /* set key in bitmap */ bit_set(sc->sc_ndata, key); } } #ifdef HID_DEBUG DPRINTF("modifiers = 0x%04x\n", modifiers); bit_foreach(sc->sc_ndata, HKBD_NKEYCODE, i) DPRINTF("Key 0x%02x pressed\n", i); #endif hkbd_interrupt(sc); } /* A match on these entries will load ukbd */ static const struct hid_device_id __used hkbd_devs[] = { { HID_TLC(HUP_GENERIC_DESKTOP, HUG_KEYBOARD) }, }; static int hkbd_probe(device_t dev) { keyboard_switch_t *sw = kbd_get_switch(HKBD_DRIVER_NAME); int error; DPRINTFN(11, "\n"); if (sw == NULL) { return (ENXIO); } error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hkbd_devs); if (error != 0) return (error); hidbus_set_desc(dev, "Keyboard"); return (BUS_PROBE_DEFAULT); } static void hkbd_parse_hid(struct hkbd_softc *sc, const uint8_t *ptr, uint32_t len, uint8_t tlc_index) { uint32_t flags; uint32_t key; uint8_t id; /* reset detected bits */ sc->sc_flags &= ~HKBD_FLAG_HID_MASK; /* reset detected keys */ memset(sc->sc_loc_key_valid, 0, bitstr_size(HKBD_NKEYCODE)); /* check if there is an ID byte */ sc->sc_kbd_size = hid_report_size_max(ptr, len, hid_input, &sc->sc_kbd_id); /* investigate if this is an Apple Keyboard */ if (hidbus_locate(ptr, len, HID_USAGE2(HUP_CONSUMER, HUG_APPLE_EJECT), hid_input, tlc_index, 0, &sc->sc_loc_apple_eject, &flags, &sc->sc_id_apple_eject, NULL)) { if (flags & HIO_VARIABLE) sc->sc_flags |= HKBD_FLAG_APPLE_EJECT | HKBD_FLAG_APPLE_SWAP; DPRINTFN(1, "Found Apple eject-key\n"); } if (hidbus_locate(ptr, len, HID_USAGE2(0xFFFF, 0x0003), hid_input, tlc_index, 0, &sc->sc_loc_apple_fn, &flags, &sc->sc_id_apple_fn, NULL)) { if (flags & HIO_VARIABLE) sc->sc_flags |= HKBD_FLAG_APPLE_FN; DPRINTFN(1, "Found Apple FN-key\n"); } /* figure out event buffer */ if (hidbus_locate(ptr, len, HID_USAGE2(HUP_KEYBOARD, 0x00), hid_input, tlc_index, 0, &sc->sc_loc_key[0], &flags, &sc->sc_id_loc_key[0], NULL)) { if (flags & HIO_VARIABLE) { DPRINTFN(1, "Ignoring keyboard event control\n"); } else { bit_set(sc->sc_loc_key_valid, 0); DPRINTFN(1, "Found keyboard event array\n"); } } /* figure out the keys */ for (key = 1; key != HKBD_NKEYCODE; key++) { if (hidbus_locate(ptr, len, HID_USAGE2(HUP_KEYBOARD, key), hid_input, tlc_index, 0, &sc->sc_loc_key[key], &flags, &sc->sc_id_loc_key[key], NULL)) { if (flags & HIO_VARIABLE) { bit_set(sc->sc_loc_key_valid, key); DPRINTFN(1, "Found key 0x%02x\n", key); } } } /* figure out leds on keyboard */ if (hidbus_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x01), hid_output, tlc_index, 0, &sc->sc_loc_numlock, &flags, &sc->sc_id_leds, NULL)) { if (flags & HIO_VARIABLE) sc->sc_flags |= HKBD_FLAG_NUMLOCK; DPRINTFN(1, "Found keyboard numlock\n"); } if (hidbus_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x02), hid_output, tlc_index, 0, &sc->sc_loc_capslock, &flags, &id, NULL)) { if ((sc->sc_flags & HKBD_FLAG_NUMLOCK) == 0) sc->sc_id_leds = id; if (flags & HIO_VARIABLE && sc->sc_id_leds == id) sc->sc_flags |= HKBD_FLAG_CAPSLOCK; DPRINTFN(1, "Found keyboard capslock\n"); } if (hidbus_locate(ptr, len, HID_USAGE2(HUP_LEDS, 0x03), hid_output, tlc_index, 0, &sc->sc_loc_scrolllock, &flags, &id, NULL)) { if ((sc->sc_flags & (HKBD_FLAG_NUMLOCK | HKBD_FLAG_CAPSLOCK)) == 0) sc->sc_id_leds = id; if (flags & HIO_VARIABLE && sc->sc_id_leds == id) sc->sc_flags |= HKBD_FLAG_SCROLLLOCK; DPRINTFN(1, "Found keyboard scrolllock\n"); } if ((sc->sc_flags & (HKBD_FLAG_NUMLOCK | HKBD_FLAG_CAPSLOCK | HKBD_FLAG_SCROLLLOCK)) != 0) sc->sc_led_size = hid_report_size(ptr, len, hid_output, sc->sc_id_leds); } static int hkbd_attach(device_t dev) { struct hkbd_softc *sc = device_get_softc(dev); const struct hid_device_info *hw = hid_get_device_info(dev); int unit = device_get_unit(dev); keyboard_t *kbd = &sc->sc_kbd; void *hid_ptr = NULL; int err; uint16_t n; hid_size_t hid_len; uint8_t tlc_index = hidbus_get_index(dev); #ifdef EVDEV_SUPPORT struct evdev_dev *evdev; int i; #endif sc->sc_dev = dev; SYSCONS_LOCK_ASSERT(); kbd_init_struct(kbd, HKBD_DRIVER_NAME, KB_OTHER, unit, 0, 0, 0); kbd->kb_data = (void *)sc; sc->sc_mode = K_XLATE; mtx_init(&sc->sc_mtx, "hkbd lock", NULL, MTX_DEF); TASK_INIT(&sc->sc_task, 0, hkbd_event_keyinput, sc); callout_init_mtx(&sc->sc_callout, &sc->sc_mtx, 0); hidbus_set_intr(dev, hkbd_intr_callback, sc); /* interrupt handler will be called with hkbd mutex taken */ hidbus_set_lock(dev, &sc->sc_mtx); /* interrupt handler can be called during panic */ hidbus_set_flags(dev, hidbus_get_flags(dev) | HIDBUS_FLAG_CAN_POLL); /* setup default keyboard maps */ sc->sc_keymap = key_map; sc->sc_accmap = accent_map; for (n = 0; n < HKBD_NFKEY; n++) { sc->sc_fkeymap[n] = fkey_tab[n]; } kbd_set_maps(kbd, &sc->sc_keymap, &sc->sc_accmap, sc->sc_fkeymap, HKBD_NFKEY); KBD_FOUND_DEVICE(kbd); hkbd_clear_state(kbd); /* * FIXME: set the initial value for lock keys in "sc_state" * according to the BIOS data? */ KBD_PROBE_DONE(kbd); /* get HID descriptor */ err = hid_get_report_descr(dev, &hid_ptr, &hid_len); if (err == 0) { DPRINTF("Parsing HID descriptor of %d bytes\n", (int)hid_len); hkbd_parse_hid(sc, hid_ptr, hid_len, tlc_index); } /* check if we should use the boot protocol */ if (hid_test_quirk(hw, HQ_KBD_BOOTPROTO) || (err != 0) || hkbd_any_key_valid(sc) == false) { DPRINTF("Forcing boot protocol\n"); err = hid_set_protocol(dev, 0); if (err != 0) { DPRINTF("Set protocol error=%d (ignored)\n", err); } hkbd_parse_hid(sc, hkbd_boot_desc, sizeof(hkbd_boot_desc), 0); } /* ignore if SETIDLE fails, hence it is not crucial */ hid_set_idle(dev, 0, 0); hkbd_ioctl(kbd, KDSETLED, (caddr_t)&sc->sc_state); KBD_INIT_DONE(kbd); if (kbd_register(kbd) < 0) { goto detach; } KBD_CONFIG_DONE(kbd); hkbd_enable(kbd); #ifdef KBD_INSTALL_CDEV if (kbd_attach(kbd)) { goto detach; } #endif #ifdef EVDEV_SUPPORT evdev = evdev_alloc(); evdev_set_name(evdev, device_get_desc(dev)); evdev_set_phys(evdev, device_get_nameunit(dev)); evdev_set_id(evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(evdev, hw->serial); evdev_set_methods(evdev, kbd, &hkbd_evdev_methods); evdev_set_flag(evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(evdev, EV_SYN); evdev_support_event(evdev, EV_KEY); if (sc->sc_flags & (HKBD_FLAG_NUMLOCK | HKBD_FLAG_CAPSLOCK | HKBD_FLAG_SCROLLLOCK)) evdev_support_event(evdev, EV_LED); evdev_support_event(evdev, EV_REP); for (i = 0x00; i <= 0xFF; i++) evdev_support_key(evdev, evdev_hid2key(i)); if (sc->sc_flags & HKBD_FLAG_NUMLOCK) evdev_support_led(evdev, LED_NUML); if (sc->sc_flags & HKBD_FLAG_CAPSLOCK) evdev_support_led(evdev, LED_CAPSL); if (sc->sc_flags & HKBD_FLAG_SCROLLLOCK) evdev_support_led(evdev, LED_SCROLLL); if (evdev_register(evdev)) evdev_free(evdev); else sc->sc_evdev = evdev; #endif sc->sc_flags |= HKBD_FLAG_ATTACHED; if (bootverbose) { kbdd_diag(kbd, bootverbose); } /* start the keyboard */ - hidbus_intr_start(dev); + hid_intr_start(dev); return (0); /* success */ detach: hkbd_detach(dev); return (ENXIO); /* error */ } static int hkbd_detach(device_t dev) { struct hkbd_softc *sc = device_get_softc(dev); #ifdef EVDEV_SUPPORT struct epoch_tracker et; #endif int error; SYSCONS_LOCK_ASSERT(); DPRINTF("\n"); sc->sc_flags |= HKBD_FLAG_GONE; HKBD_LOCK(sc); callout_stop(&sc->sc_callout); HKBD_UNLOCK(sc); /* kill any stuck keys */ if (sc->sc_flags & HKBD_FLAG_ATTACHED) { /* stop receiving events from the USB keyboard */ - hidbus_intr_stop(dev); + hid_intr_stop(dev); /* release all leftover keys, if any */ memset(&sc->sc_ndata, 0, bitstr_size(HKBD_NKEYCODE)); /* process releasing of all keys */ HKBD_LOCK(sc); #ifdef EVDEV_SUPPORT epoch_enter_preempt(INPUT_EPOCH, &et); #endif hkbd_interrupt(sc); #ifdef EVDEV_SUPPORT epoch_exit_preempt(INPUT_EPOCH, &et); #endif HKBD_UNLOCK(sc); taskqueue_drain(taskqueue_swi_giant, &sc->sc_task); } mtx_destroy(&sc->sc_mtx); hkbd_disable(&sc->sc_kbd); #ifdef KBD_INSTALL_CDEV if (sc->sc_flags & HKBD_FLAG_ATTACHED) { error = kbd_detach(&sc->sc_kbd); if (error) { /* usb attach cannot return an error */ device_printf(dev, "WARNING: kbd_detach() " "returned non-zero! (ignored)\n"); } } #endif #ifdef EVDEV_SUPPORT evdev_free(sc->sc_evdev); #endif if (KBD_IS_CONFIGURED(&sc->sc_kbd)) { error = kbd_unregister(&sc->sc_kbd); if (error) { /* usb attach cannot return an error */ device_printf(dev, "WARNING: kbd_unregister() " "returned non-zero! (ignored)\n"); } } sc->sc_kbd.kb_flags = 0; DPRINTF("%s: disconnected\n", device_get_nameunit(dev)); return (0); } static int hkbd_resume(device_t dev) { struct hkbd_softc *sc = device_get_softc(dev); SYSCONS_LOCK_ASSERT(); hkbd_clear_state(&sc->sc_kbd); return (0); } #ifdef EVDEV_SUPPORT static void hkbd_ev_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, int32_t value) { keyboard_t *kbd = evdev_get_softc(evdev); if (evdev_rcpt_mask & EVDEV_RCPT_HW_KBD && (type == EV_LED || type == EV_REP)) { mtx_lock(&Giant); kbd_ev_event(kbd, type, code, value); mtx_unlock(&Giant); } } #endif /* early keyboard probe, not supported */ static int hkbd_configure(int flags) { return (0); } /* detect a keyboard, not used */ static int hkbd__probe(int unit, void *arg, int flags) { return (ENXIO); } /* reset and initialize the device, not used */ static int hkbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) { return (ENXIO); } /* test the interface to the device, not used */ static int hkbd_test_if(keyboard_t *kbd) { return (0); } /* finish using this keyboard, not used */ static int hkbd_term(keyboard_t *kbd) { return (ENXIO); } /* keyboard interrupt routine, not used */ static int hkbd_intr(keyboard_t *kbd, void *arg) { return (0); } /* lock the access to the keyboard, not used */ static int hkbd_lock(keyboard_t *kbd, int lock) { return (1); } /* * Enable the access to the device; until this function is called, * the client cannot read from the keyboard. */ static int hkbd_enable(keyboard_t *kbd) { SYSCONS_LOCK(); KBD_ACTIVATE(kbd); SYSCONS_UNLOCK(); return (0); } /* disallow the access to the device */ static int hkbd_disable(keyboard_t *kbd) { SYSCONS_LOCK(); KBD_DEACTIVATE(kbd); SYSCONS_UNLOCK(); return (0); } /* check if data is waiting */ /* Currently unused. */ static int hkbd_check(keyboard_t *kbd) { struct hkbd_softc *sc = kbd->kb_data; SYSCONS_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (0); if (sc->sc_flags & HKBD_FLAG_POLLING) hkbd_do_poll(sc, 0); #ifdef HKBD_EMULATE_ATSCANCODE if (sc->sc_buffered_char[0]) { return (1); } #endif if (sc->sc_inputhead != atomic_load_acq_32(&sc->sc_inputtail)) { return (1); } return (0); } /* check if char is waiting */ static int hkbd_check_char_locked(keyboard_t *kbd) { struct hkbd_softc *sc = kbd->kb_data; SYSCONS_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (0); if ((sc->sc_composed_char > 0) && (!(sc->sc_flags & HKBD_FLAG_COMPOSE))) { return (1); } return (hkbd_check(kbd)); } static int hkbd_check_char(keyboard_t *kbd) { int result; SYSCONS_LOCK(); result = hkbd_check_char_locked(kbd); SYSCONS_UNLOCK(); return (result); } /* read one byte from the keyboard if it's allowed */ /* Currently unused. */ static int hkbd_read(keyboard_t *kbd, int wait) { struct hkbd_softc *sc = kbd->kb_data; int32_t usbcode; #ifdef HKBD_EMULATE_ATSCANCODE uint32_t keycode; uint32_t scancode; #endif SYSCONS_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (-1); #ifdef HKBD_EMULATE_ATSCANCODE if (sc->sc_buffered_char[0]) { scancode = sc->sc_buffered_char[0]; if (scancode & SCAN_PREFIX) { sc->sc_buffered_char[0] &= ~SCAN_PREFIX; return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; sc->sc_buffered_char[1] = 0; return (scancode); } #endif /* HKBD_EMULATE_ATSCANCODE */ /* XXX */ usbcode = hkbd_get_key(sc, (wait == FALSE) ? 0 : 1); if (!KBD_IS_ACTIVE(kbd) || (usbcode == -1)) return (-1); ++(kbd->kb_count); #ifdef HKBD_EMULATE_ATSCANCODE keycode = hkbd_atkeycode(usbcode, sc->sc_ndata); if (keycode == NN) { return -1; } return (hkbd_key2scan(sc, keycode, sc->sc_ndata, (usbcode & KEY_RELEASE))); #else /* !HKBD_EMULATE_ATSCANCODE */ return (usbcode); #endif /* HKBD_EMULATE_ATSCANCODE */ } /* read char from the keyboard */ static uint32_t hkbd_read_char_locked(keyboard_t *kbd, int wait) { struct hkbd_softc *sc = kbd->kb_data; uint32_t action; uint32_t keycode; int32_t usbcode; #ifdef HKBD_EMULATE_ATSCANCODE uint32_t scancode; #endif SYSCONS_LOCK_ASSERT(); if (!KBD_IS_ACTIVE(kbd)) return (NOKEY); next_code: /* do we have a composed char to return ? */ if ((sc->sc_composed_char > 0) && (!(sc->sc_flags & HKBD_FLAG_COMPOSE))) { action = sc->sc_composed_char; sc->sc_composed_char = 0; if (action > 0xFF) { goto errkey; } goto done; } #ifdef HKBD_EMULATE_ATSCANCODE /* do we have a pending raw scan code? */ if (sc->sc_mode == K_RAW) { scancode = sc->sc_buffered_char[0]; if (scancode) { if (scancode & SCAN_PREFIX) { sc->sc_buffered_char[0] = (scancode & ~SCAN_PREFIX); return ((scancode & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } sc->sc_buffered_char[0] = sc->sc_buffered_char[1]; sc->sc_buffered_char[1] = 0; return (scancode); } } #endif /* HKBD_EMULATE_ATSCANCODE */ /* see if there is something in the keyboard port */ /* XXX */ usbcode = hkbd_get_key(sc, (wait == FALSE) ? 0 : 1); if (usbcode == -1) { return (NOKEY); } ++kbd->kb_count; #ifdef HKBD_EMULATE_ATSCANCODE /* USB key index -> key code -> AT scan code */ keycode = hkbd_atkeycode(usbcode, sc->sc_ndata); if (keycode == NN) { return (NOKEY); } /* return an AT scan code for the K_RAW mode */ if (sc->sc_mode == K_RAW) { return (hkbd_key2scan(sc, keycode, sc->sc_ndata, (usbcode & KEY_RELEASE))); } #else /* !HKBD_EMULATE_ATSCANCODE */ /* return the byte as is for the K_RAW mode */ if (sc->sc_mode == K_RAW) { return (usbcode); } /* USB key index -> key code */ keycode = hkbd_trtab[KEY_INDEX(usbcode)]; if (keycode == NN) { return (NOKEY); } #endif /* HKBD_EMULATE_ATSCANCODE */ switch (keycode) { case 0x38: /* left alt (compose key) */ if (usbcode & KEY_RELEASE) { if (sc->sc_flags & HKBD_FLAG_COMPOSE) { sc->sc_flags &= ~HKBD_FLAG_COMPOSE; if (sc->sc_composed_char > 0xFF) { sc->sc_composed_char = 0; } } } else { if (!(sc->sc_flags & HKBD_FLAG_COMPOSE)) { sc->sc_flags |= HKBD_FLAG_COMPOSE; sc->sc_composed_char = 0; } } break; } /* return the key code in the K_CODE mode */ if (usbcode & KEY_RELEASE) { keycode |= SCAN_RELEASE; } if (sc->sc_mode == K_CODE) { return (keycode); } /* compose a character code */ if (sc->sc_flags & HKBD_FLAG_COMPOSE) { switch (keycode) { /* key pressed, process it */ case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x40; goto check_composed; case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x47; goto check_composed; case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ sc->sc_composed_char *= 10; sc->sc_composed_char += keycode - 0x4E; goto check_composed; case 0x52: /* keypad 0 */ sc->sc_composed_char *= 10; goto check_composed; /* key released, no interest here */ case SCAN_RELEASE | 0x47: case SCAN_RELEASE | 0x48: case SCAN_RELEASE | 0x49: /* keypad 7,8,9 */ case SCAN_RELEASE | 0x4B: case SCAN_RELEASE | 0x4C: case SCAN_RELEASE | 0x4D: /* keypad 4,5,6 */ case SCAN_RELEASE | 0x4F: case SCAN_RELEASE | 0x50: case SCAN_RELEASE | 0x51: /* keypad 1,2,3 */ case SCAN_RELEASE | 0x52: /* keypad 0 */ goto next_code; case 0x38: /* left alt key */ break; default: if (sc->sc_composed_char > 0) { sc->sc_flags &= ~HKBD_FLAG_COMPOSE; sc->sc_composed_char = 0; goto errkey; } break; } } /* keycode to key action */ action = genkbd_keyaction(kbd, SCAN_CHAR(keycode), (keycode & SCAN_RELEASE), &sc->sc_state, &sc->sc_accents); if (action == NOKEY) { goto next_code; } done: return (action); check_composed: if (sc->sc_composed_char <= 0xFF) { goto next_code; } errkey: return (ERRKEY); } /* Currently wait is always false. */ static uint32_t hkbd_read_char(keyboard_t *kbd, int wait) { uint32_t keycode; SYSCONS_LOCK(); keycode = hkbd_read_char_locked(kbd, wait); SYSCONS_UNLOCK(); return (keycode); } /* some useful control functions */ static int hkbd_ioctl_locked(keyboard_t *kbd, u_long cmd, caddr_t arg) { struct hkbd_softc *sc = kbd->kb_data; #ifdef EVDEV_SUPPORT struct epoch_tracker et; #endif int error; int i; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) int ival; #endif SYSCONS_LOCK_ASSERT(); switch (cmd) { case KDGKBMODE: /* get keyboard mode */ *(int *)arg = sc->sc_mode; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 7): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBMODE: /* set keyboard mode */ switch (*(int *)arg) { case K_XLATE: if (sc->sc_mode != K_XLATE) { /* make lock key state and LED state match */ sc->sc_state &= ~LOCK_MASK; sc->sc_state |= KBD_LED_VAL(kbd); } /* FALLTHROUGH */ case K_RAW: case K_CODE: if (sc->sc_mode != *(int *)arg) { if ((sc->sc_flags & HKBD_FLAG_POLLING) == 0) hkbd_clear_state(kbd); sc->sc_mode = *(int *)arg; } break; default: return (EINVAL); } break; case KDGETLED: /* get keyboard LED */ *(int *)arg = KBD_LED_VAL(kbd); break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 66): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETLED: /* set keyboard LED */ /* NOTE: lock key state in "sc_state" won't be changed */ if (*(int *)arg & ~LOCK_MASK) return (EINVAL); i = *(int *)arg; /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ if (sc->sc_mode == K_XLATE && kbd->kb_keymap->n_keys > ALTGR_OFFSET) { if (i & ALKED) i |= CLKED; else i &= ~CLKED; } if (KBD_HAS_DEVICE(kbd)) { error = hkbd_set_leds(sc, i); if (error) return (error); } #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL && !HID_IN_POLLING_MODE()) { epoch_enter_preempt(INPUT_EPOCH, &et); evdev_push_leds(sc->sc_evdev, i); epoch_exit_preempt(INPUT_EPOCH, &et); } #endif KBD_LED_VAL(kbd) = *(int *)arg; break; case KDGKBSTATE: /* get lock key state */ *(int *)arg = sc->sc_state & LOCK_MASK; break; #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 20): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSKBSTATE: /* set lock key state */ if (*(int *)arg & ~LOCK_MASK) { return (EINVAL); } sc->sc_state &= ~LOCK_MASK; sc->sc_state |= *(int *)arg; /* set LEDs and quit */ return (hkbd_ioctl_locked(kbd, KDSETLED, arg)); case KDSETREPEAT: /* set keyboard repeat rate (new * interface) */ if (!KBD_HAS_DEVICE(kbd)) { return (0); } /* * Convert negative, zero and tiny args to the same limits * as atkbd. We could support delays of 1 msec, but * anything much shorter than the shortest atkbd value * of 250.34 is almost unusable as well as incompatible. */ kbd->kb_delay1 = imax(((int *)arg)[0], 250); kbd->kb_delay2 = imax(((int *)arg)[1], 34); #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL && !HID_IN_POLLING_MODE()) { epoch_enter_preempt(INPUT_EPOCH, &et); evdev_push_repeats(sc->sc_evdev, kbd); epoch_exit_preempt(INPUT_EPOCH, &et); } #endif return (0); #if defined(COMPAT_FREEBSD6) || defined(COMPAT_FREEBSD5) || \ defined(COMPAT_FREEBSD4) || defined(COMPAT_43) case _IO('K', 67): ival = IOCPARM_IVAL(arg); arg = (caddr_t)&ival; /* FALLTHROUGH */ #endif case KDSETRAD: /* set keyboard repeat rate (old * interface) */ return (hkbd_set_typematic(kbd, *(int *)arg)); case PIO_KEYMAP: /* set keyboard translation table */ case PIO_KEYMAPENT: /* set keyboard translation table * entry */ case PIO_DEADKEYMAP: /* set accent key translation table */ #ifdef COMPAT_FREEBSD13 case OPIO_KEYMAP: /* set keyboard translation table * (compat) */ case OPIO_DEADKEYMAP: /* set accent key translation table * (compat) */ #endif /* COMPAT_FREEBSD13 */ sc->sc_accents = 0; /* FALLTHROUGH */ default: return (genkbd_commonioctl(kbd, cmd, arg)); } return (0); } static int hkbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) { int result; /* * XXX Check if someone is calling us from a critical section: */ if (curthread->td_critnest != 0) return (EDEADLK); /* * XXX KDGKBSTATE, KDSKBSTATE and KDSETLED can be called from any * context where printf(9) can be called, which among other things * includes interrupt filters and threads with any kinds of locks * already held. For this reason it would be dangerous to acquire * the Giant here unconditionally. On the other hand we have to * have it to handle the ioctl. * So we make our best effort to auto-detect whether we can grab * the Giant or not. Blame syscons(4) for this. */ switch (cmd) { case KDGKBSTATE: case KDSKBSTATE: case KDSETLED: if (!mtx_owned(&Giant) && !HID_IN_POLLING_MODE()) return (EDEADLK); /* best I could come up with */ /* FALLTHROUGH */ default: SYSCONS_LOCK(); result = hkbd_ioctl_locked(kbd, cmd, arg); SYSCONS_UNLOCK(); return (result); } } /* clear the internal state of the keyboard */ static void hkbd_clear_state(keyboard_t *kbd) { struct hkbd_softc *sc = kbd->kb_data; SYSCONS_LOCK_ASSERT(); sc->sc_flags &= ~(HKBD_FLAG_COMPOSE | HKBD_FLAG_POLLING); sc->sc_state &= LOCK_MASK; /* preserve locking key state */ sc->sc_accents = 0; sc->sc_composed_char = 0; #ifdef HKBD_EMULATE_ATSCANCODE sc->sc_buffered_char[0] = 0; sc->sc_buffered_char[1] = 0; #endif memset(&sc->sc_ndata, 0, bitstr_size(HKBD_NKEYCODE)); memset(&sc->sc_odata, 0, bitstr_size(HKBD_NKEYCODE)); memset(&sc->sc_ndata0, 0, bitstr_size(HKBD_NKEYCODE)); memset(&sc->sc_odata0, 0, bitstr_size(HKBD_NKEYCODE)); sc->sc_repeat_time = 0; sc->sc_repeat_key = 0; } /* save the internal state, not used */ static int hkbd_get_state(keyboard_t *kbd, void *buf, size_t len) { return (len == 0) ? 1 : -1; } /* set the internal state, not used */ static int hkbd_set_state(keyboard_t *kbd, void *buf, size_t len) { return (EINVAL); } static int hkbd_poll(keyboard_t *kbd, int on) { struct hkbd_softc *sc = kbd->kb_data; SYSCONS_LOCK(); /* * Keep a reference count on polling to allow recursive * cngrab() during a panic for example. */ if (on) sc->sc_polling++; else if (sc->sc_polling > 0) sc->sc_polling--; if (sc->sc_polling != 0) { sc->sc_flags |= HKBD_FLAG_POLLING; sc->sc_poll_thread = curthread; } else { sc->sc_flags &= ~HKBD_FLAG_POLLING; sc->sc_delay = 0; } SYSCONS_UNLOCK(); return (0); } /* local functions */ static int hkbd_set_leds(struct hkbd_softc *sc, uint8_t leds) { uint8_t id; uint8_t any; uint8_t *buf; int len; int error; SYSCONS_LOCK_ASSERT(); DPRINTF("leds=0x%02x\n", leds); #ifdef HID_DEBUG if (hkbd_no_leds) return (0); #endif memset(sc->sc_buffer, 0, HKBD_BUFFER_SIZE); id = sc->sc_id_leds; any = 0; /* Assumption: All led bits must be in the same ID. */ if (sc->sc_flags & HKBD_FLAG_NUMLOCK) { hid_put_udata(sc->sc_buffer + 1, HKBD_BUFFER_SIZE - 1, &sc->sc_loc_numlock, leds & NLKED ? 1 : 0); any = 1; } if (sc->sc_flags & HKBD_FLAG_SCROLLLOCK) { hid_put_udata(sc->sc_buffer + 1, HKBD_BUFFER_SIZE - 1, &sc->sc_loc_scrolllock, leds & SLKED ? 1 : 0); any = 1; } if (sc->sc_flags & HKBD_FLAG_CAPSLOCK) { hid_put_udata(sc->sc_buffer + 1, HKBD_BUFFER_SIZE - 1, &sc->sc_loc_capslock, leds & CLKED ? 1 : 0); any = 1; } /* if no leds, nothing to do */ if (!any) return (0); /* range check output report length */ len = sc->sc_led_size; if (len > (HKBD_BUFFER_SIZE - 1)) len = (HKBD_BUFFER_SIZE - 1); /* check if we need to prefix an ID byte */ if (id != 0) { sc->sc_buffer[0] = id; buf = sc->sc_buffer; } else { buf = sc->sc_buffer + 1; } DPRINTF("len=%d, id=%d\n", len, id); /* start data transfer */ SYSCONS_UNLOCK(); error = hid_write(sc->sc_dev, buf, len); SYSCONS_LOCK(); return (error); } static int hkbd_set_typematic(keyboard_t *kbd, int code) { #ifdef EVDEV_SUPPORT struct hkbd_softc *sc = kbd->kb_data; #endif if (code & ~0x7f) { return (EINVAL); } kbd->kb_delay1 = kbdelays[(code >> 5) & 3]; kbd->kb_delay2 = kbrates[code & 0x1f]; #ifdef EVDEV_SUPPORT if (sc->sc_evdev != NULL) evdev_push_repeats(sc->sc_evdev, kbd); #endif return (0); } #ifdef HKBD_EMULATE_ATSCANCODE static uint32_t hkbd_atkeycode(int usbcode, const bitstr_t *bitmap) { uint32_t keycode; keycode = hkbd_trtab[KEY_INDEX(usbcode)]; /* * Translate Alt-PrintScreen to SysRq. * * Some or all AT keyboards connected through USB have already * mapped Alted PrintScreens to an unusual usbcode (0x8a). * hkbd_trtab translates this to 0x7e, and key2scan() would * translate that to 0x79 (Intl' 4). Assume that if we have * an Alted 0x7e here then it actually is an Alted PrintScreen. * * The usual usbcode for all PrintScreens is 0x46. hkbd_trtab * translates this to 0x5c, so the Alt check to classify 0x5c * is routine. */ if ((keycode == 0x5c || keycode == 0x7e) && (HKBD_KEY_PRESSED(bitmap, 0xe2 /* ALT-L */) || HKBD_KEY_PRESSED(bitmap, 0xe6 /* ALT-R */))) return (0x54); return (keycode); } static int hkbd_key2scan(struct hkbd_softc *sc, int code, const bitstr_t *bitmap, int up) { static const int scan[] = { /* 89 */ 0x11c, /* Enter */ /* 90-99 */ 0x11d, /* Ctrl-R */ 0x135, /* Divide */ 0x137, /* PrintScreen */ 0x138, /* Alt-R */ 0x147, /* Home */ 0x148, /* Up */ 0x149, /* PageUp */ 0x14b, /* Left */ 0x14d, /* Right */ 0x14f, /* End */ /* 100-109 */ 0x150, /* Down */ 0x151, /* PageDown */ 0x152, /* Insert */ 0x153, /* Delete */ 0x146, /* Pause/Break */ 0x15b, /* Win_L(Super_L) */ 0x15c, /* Win_R(Super_R) */ 0x15d, /* Application(Menu) */ /* SUN TYPE 6 USB KEYBOARD */ 0x168, /* Sun Type 6 Help */ 0x15e, /* Sun Type 6 Stop */ /* 110 - 119 */ 0x15f, /* Sun Type 6 Again */ 0x160, /* Sun Type 6 Props */ 0x161, /* Sun Type 6 Undo */ 0x162, /* Sun Type 6 Front */ 0x163, /* Sun Type 6 Copy */ 0x164, /* Sun Type 6 Open */ 0x165, /* Sun Type 6 Paste */ 0x166, /* Sun Type 6 Find */ 0x167, /* Sun Type 6 Cut */ 0x125, /* Sun Type 6 Mute */ /* 120 - 130 */ 0x11f, /* Sun Type 6 VolumeDown */ 0x11e, /* Sun Type 6 VolumeUp */ 0x120, /* Sun Type 6 PowerDown */ /* Japanese 106/109 keyboard */ 0x73, /* Keyboard Intl' 1 (backslash / underscore) */ 0x70, /* Keyboard Intl' 2 (Katakana / Hiragana) */ 0x7d, /* Keyboard Intl' 3 (Yen sign) (Not using in jp106/109) */ 0x79, /* Keyboard Intl' 4 (Henkan) */ 0x7b, /* Keyboard Intl' 5 (Muhenkan) */ 0x5c, /* Keyboard Intl' 6 (Keypad ,) (For PC-9821 layout) */ 0x71, /* Apple Keyboard JIS (Kana) */ 0x72, /* Apple Keyboard JIS (Eisu) */ }; if ((code >= 89) && (code < (int)(89 + nitems(scan)))) { code = scan[code - 89]; } /* PrintScreen */ if (code == 0x137 && (!( HKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) || HKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */) || HKBD_KEY_PRESSED(bitmap, 0xe1 /* SHIFT-L */) || HKBD_KEY_PRESSED(bitmap, 0xe5 /* SHIFT-R */)))) { code |= SCAN_PREFIX_SHIFT; } /* Pause/Break */ if ((code == 0x146) && (!( HKBD_KEY_PRESSED(bitmap, 0xe0 /* CTRL-L */) || HKBD_KEY_PRESSED(bitmap, 0xe4 /* CTRL-R */)))) { code = (0x45 | SCAN_PREFIX_E1 | SCAN_PREFIX_CTL); } code |= (up ? SCAN_RELEASE : SCAN_PRESS); if (code & SCAN_PREFIX) { if (code & SCAN_PREFIX_CTL) { /* Ctrl */ sc->sc_buffered_char[0] = (0x1d | (code & SCAN_RELEASE)); sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX); } else if (code & SCAN_PREFIX_SHIFT) { /* Shift */ sc->sc_buffered_char[0] = (0x2a | (code & SCAN_RELEASE)); sc->sc_buffered_char[1] = (code & ~SCAN_PREFIX_SHIFT); } else { sc->sc_buffered_char[0] = (code & ~SCAN_PREFIX); sc->sc_buffered_char[1] = 0; } return ((code & SCAN_PREFIX_E0) ? 0xe0 : 0xe1); } return (code); } #endif /* HKBD_EMULATE_ATSCANCODE */ static keyboard_switch_t hkbdsw = { .probe = &hkbd__probe, .init = &hkbd_init, .term = &hkbd_term, .intr = &hkbd_intr, .test_if = &hkbd_test_if, .enable = &hkbd_enable, .disable = &hkbd_disable, .read = &hkbd_read, .check = &hkbd_check, .read_char = &hkbd_read_char, .check_char = &hkbd_check_char, .ioctl = &hkbd_ioctl, .lock = &hkbd_lock, .clear_state = &hkbd_clear_state, .get_state = &hkbd_get_state, .set_state = &hkbd_set_state, .poll = &hkbd_poll, }; KEYBOARD_DRIVER(hkbd, hkbdsw, hkbd_configure); static int hkbd_driver_load(module_t mod, int what, void *arg) { switch (what) { case MOD_LOAD: kbd_add_driver(&hkbd_kbd_driver); break; case MOD_UNLOAD: kbd_delete_driver(&hkbd_kbd_driver); break; } return (0); } static device_method_t hkbd_methods[] = { DEVMETHOD(device_probe, hkbd_probe), DEVMETHOD(device_attach, hkbd_attach), DEVMETHOD(device_detach, hkbd_detach), DEVMETHOD(device_resume, hkbd_resume), DEVMETHOD_END }; static driver_t hkbd_driver = { .name = "hkbd", .methods = hkbd_methods, .size = sizeof(struct hkbd_softc), }; DRIVER_MODULE(hkbd, hidbus, hkbd_driver, hkbd_driver_load, NULL); MODULE_DEPEND(hkbd, hid, 1, 1, 1); MODULE_DEPEND(hkbd, hidbus, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(hkbd, evdev, 1, 1, 1); #endif MODULE_VERSION(hkbd, 1); HID_PNP_INFO(hkbd_devs); diff --git a/sys/dev/hid/hmt.c b/sys/dev/hid/hmt.c index cb9de6f24f88..b190772e2498 100644 --- a/sys/dev/hid/hmt.c +++ b/sys/dev/hid/hmt.c @@ -1,917 +1,917 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2014-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$"); /* * MS Windows 7/8/10 compatible HID Multi-touch Device driver. * https://msdn.microsoft.com/en-us/library/windows/hardware/jj151569(v=vs.85).aspx * http://download.microsoft.com/download/7/d/d/7dd44bb7-2a7a-4505-ac1c-7227d3d96d5b/hid-over-i2c-protocol-spec-v1-0.docx * https://www.kernel.org/doc/Documentation/input/multi-touch-protocol.txt */ #include "opt_hid.h" #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR hmt_debug #include #include #include #include static SYSCTL_NODE(_hw_hid, OID_AUTO, hmt, CTLFLAG_RW, 0, "MSWindows 7/8/10 compatible HID Multi-touch Device"); #ifdef HID_DEBUG static int hmt_debug = 0; SYSCTL_INT(_hw_hid_hmt, OID_AUTO, debug, CTLFLAG_RWTUN, &hmt_debug, 1, "Debug level"); #endif static bool hmt_timestamps = 0; SYSCTL_BOOL(_hw_hid_hmt, OID_AUTO, timestamps, CTLFLAG_RDTUN, &hmt_timestamps, 1, "Enable hardware timestamp reporting"); #define HMT_BTN_MAX 8 /* Number of buttons supported */ enum hmt_type { HMT_TYPE_UNKNOWN = 0, /* HID report descriptor is not probed */ HMT_TYPE_UNSUPPORTED, /* Repdescr does not belong to MT device */ HMT_TYPE_TOUCHPAD, HMT_TYPE_TOUCHSCREEN, }; enum { HMT_TIP_SWITCH = ABS_MT_INDEX(ABS_MT_TOOL_TYPE), HMT_WIDTH = ABS_MT_INDEX(ABS_MT_TOUCH_MAJOR), HMT_HEIGHT = ABS_MT_INDEX(ABS_MT_TOUCH_MINOR), HMT_ORIENTATION = ABS_MT_INDEX(ABS_MT_ORIENTATION), HMT_X = ABS_MT_INDEX(ABS_MT_POSITION_X), HMT_Y = ABS_MT_INDEX(ABS_MT_POSITION_Y), HMT_CONTACTID = ABS_MT_INDEX(ABS_MT_TRACKING_ID), HMT_PRESSURE = ABS_MT_INDEX(ABS_MT_PRESSURE), HMT_IN_RANGE = ABS_MT_INDEX(ABS_MT_DISTANCE), HMT_CONFIDENCE = ABS_MT_INDEX(ABS_MT_BLOB_ID), HMT_TOOL_X = ABS_MT_INDEX(ABS_MT_TOOL_X), HMT_TOOL_Y = ABS_MT_INDEX(ABS_MT_TOOL_Y), }; #define HMT_N_USAGES MT_CNT #define HMT_NO_USAGE -1 struct hmt_hid_map_item { char name[5]; int32_t usage; /* HID usage */ bool reported; /* Item value is passed to evdev */ bool required; /* Required for MT Digitizers */ }; static const struct hmt_hid_map_item hmt_hid_map[HMT_N_USAGES] = { [HMT_TIP_SWITCH] = { .name = "TIP", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH), .reported = false, .required = true, }, [HMT_WIDTH] = { .name = "WDTH", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH), .reported = true, .required = false, }, [HMT_HEIGHT] = { .name = "HGHT", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT), .reported = true, .required = false, }, [HMT_ORIENTATION] = { .name = "ORIE", .usage = HMT_NO_USAGE, .reported = true, .required = false, }, [HMT_X] = { .name = "X", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), .reported = true, .required = true, }, [HMT_Y] = { .name = "Y", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), .reported = true, .required = true, }, [HMT_CONTACTID] = { .name = "C_ID", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID), .reported = true, .required = true, }, [HMT_PRESSURE] = { .name = "PRES", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE), .reported = true, .required = false, }, [HMT_IN_RANGE] = { .name = "RANG", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE), .reported = true, .required = false, }, [HMT_CONFIDENCE] = { .name = "CONF", .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE), .reported = false, .required = false, }, [HMT_TOOL_X] = { /* Shares HID usage with POS_X */ .name = "TL_X", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), .reported = true, .required = false, }, [HMT_TOOL_Y] = { /* Shares HID usage with POS_Y */ .name = "TL_Y", .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), .reported = true, .required = false, }, }; struct hmt_softc { device_t dev; enum hmt_type type; int32_t cont_count_max; struct hid_absinfo ai[HMT_N_USAGES]; struct hid_location locs[MAX_MT_SLOTS][HMT_N_USAGES]; struct hid_location cont_count_loc; struct hid_location btn_loc[HMT_BTN_MAX]; struct hid_location int_btn_loc; struct hid_location scan_time_loc; int32_t scan_time_max; int32_t scan_time; int32_t timestamp; bool touch; bool prev_touch; struct evdev_dev *evdev; union evdev_mt_slot slot_data; uint8_t caps[howmany(HMT_N_USAGES, 8)]; uint8_t buttons[howmany(HMT_BTN_MAX, 8)]; uint32_t nconts_per_report; uint32_t nconts_todo; uint8_t report_id; uint32_t max_button; bool has_int_button; bool has_cont_count; bool has_scan_time; bool is_clickpad; bool do_timestamps; #ifdef IICHID_SAMPLING bool iichid_sampling; #endif struct hid_location cont_max_loc; uint32_t cont_max_rlen; uint8_t cont_max_rid; struct hid_location btn_type_loc; uint32_t btn_type_rlen; uint8_t btn_type_rid; uint32_t thqa_cert_rlen; uint8_t thqa_cert_rid; }; #define HMT_FOREACH_USAGE(caps, usage) \ for ((usage) = 0; (usage) < HMT_N_USAGES; ++(usage)) \ if (isset((caps), (usage))) static enum hmt_type hmt_hid_parse(struct hmt_softc *, const void *, hid_size_t, uint32_t, uint8_t); static int hmt_set_input_mode(struct hmt_softc *, enum hconf_input_mode); static hid_intr_t hmt_intr; static device_probe_t hmt_probe; static device_attach_t hmt_attach; static device_detach_t hmt_detach; static evdev_open_t hmt_ev_open; static evdev_close_t hmt_ev_close; static const struct evdev_methods hmt_evdev_methods = { .ev_open = &hmt_ev_open, .ev_close = &hmt_ev_close, }; static const struct hid_device_id hmt_devs[] = { { HID_TLC(HUP_DIGITIZERS, HUD_TOUCHSCREEN) }, { HID_TLC(HUP_DIGITIZERS, HUD_TOUCHPAD) }, }; static int hmt_ev_close(struct evdev_dev *evdev) { - return (hidbus_intr_stop(evdev_get_softc(evdev))); + return (hid_intr_stop(evdev_get_softc(evdev))); } static int hmt_ev_open(struct evdev_dev *evdev) { - return (hidbus_intr_start(evdev_get_softc(evdev))); + return (hid_intr_start(evdev_get_softc(evdev))); } static int hmt_probe(device_t dev) { struct hmt_softc *sc = device_get_softc(dev); void *d_ptr; hid_size_t d_len; int err; err = HIDBUS_LOOKUP_DRIVER_INFO(dev, hmt_devs); if (err != 0) return (err); err = hid_get_report_descr(dev, &d_ptr, &d_len); if (err != 0) { device_printf(dev, "could not retrieve report descriptor from " "device: %d\n", err); return (ENXIO); } /* Check if report descriptor belongs to a HID multitouch device */ if (sc->type == HMT_TYPE_UNKNOWN) sc->type = hmt_hid_parse(sc, d_ptr, d_len, hidbus_get_usage(dev), hidbus_get_index(dev)); if (sc->type == HMT_TYPE_UNSUPPORTED) return (ENXIO); hidbus_set_desc(dev, sc->type == HMT_TYPE_TOUCHPAD ? "TouchPad" : "TouchScreen"); return (BUS_PROBE_DEFAULT); } static int hmt_attach(device_t dev) { struct hmt_softc *sc = device_get_softc(dev); const struct hid_device_info *hw = hid_get_device_info(dev); void *d_ptr; uint8_t *fbuf = NULL; hid_size_t d_len, fsize, rsize; uint32_t cont_count_max; int nbuttons, btn; size_t i; int err; err = hid_get_report_descr(dev, &d_ptr, &d_len); if (err != 0) { device_printf(dev, "could not retrieve report descriptor from " "device: %d\n", err); return (ENXIO); } sc->dev = dev; fsize = hid_report_size_max(d_ptr, d_len, hid_feature, NULL); if (fsize != 0) fbuf = malloc(fsize, M_TEMP, M_WAITOK | M_ZERO); /* Fetch and parse "Contact count maximum" feature report */ if (sc->cont_max_rlen > 1) { err = hid_get_report(dev, fbuf, sc->cont_max_rlen, &rsize, HID_FEATURE_REPORT, sc->cont_max_rid); if (err == 0 && (rsize - 1) * 8 >= sc->cont_max_loc.pos + sc->cont_max_loc.size) { cont_count_max = hid_get_udata(fbuf + 1, sc->cont_max_rlen - 1, &sc->cont_max_loc); /* * Feature report is a primary source of * 'Contact Count Maximum' */ if (cont_count_max > 0) sc->cont_count_max = cont_count_max; } else DPRINTF("hid_get_report error=%d\n", err); } if (sc->cont_count_max == 0) sc->cont_count_max = sc->type == HMT_TYPE_TOUCHSCREEN ? 10 : 5; /* Fetch and parse "Button type" feature report */ if (sc->btn_type_rlen > 1 && sc->btn_type_rid != sc->cont_max_rid) { bzero(fbuf, fsize); err = hid_get_report(dev, fbuf, sc->btn_type_rlen, &rsize, HID_FEATURE_REPORT, sc->btn_type_rid); if (err != 0) DPRINTF("hid_get_report error=%d\n", err); } if (sc->btn_type_rlen > 1 && err == 0 && (rsize - 1) * 8 >= sc->btn_type_loc.pos + sc->btn_type_loc.size) sc->is_clickpad = hid_get_udata(fbuf + 1, sc->btn_type_rlen - 1, &sc->btn_type_loc) == 0; else sc->is_clickpad = sc->max_button == 0 && sc->has_int_button; /* Fetch THQA certificate to enable some devices like WaveShare */ if (sc->thqa_cert_rlen > 1 && sc->thqa_cert_rid != sc->cont_max_rid) (void)hid_get_report(dev, fbuf, sc->thqa_cert_rlen, NULL, HID_FEATURE_REPORT, sc->thqa_cert_rid); free(fbuf, M_TEMP); /* Switch touchpad in to absolute multitouch mode */ if (sc->type == HMT_TYPE_TOUCHPAD) { err = hmt_set_input_mode(sc, HCONF_INPUT_MODE_MT_TOUCHPAD); if (err != 0) DPRINTF("Failed to set input mode: %d\n", err); } /* Cap contact count maximum to MAX_MT_SLOTS */ if (sc->cont_count_max > MAX_MT_SLOTS) { DPRINTF("Hardware reported %d contacts while only %d is " "supported\n", sc->cont_count_max, MAX_MT_SLOTS); sc->cont_count_max = MAX_MT_SLOTS; } if (sc->has_scan_time && (hid_test_quirk(hw, HQ_MT_TIMESTAMP) || hmt_timestamps)) sc->do_timestamps = true; #ifdef IICHID_SAMPLING if (hid_test_quirk(hw, HQ_IICHID_SAMPLING)) sc->iichid_sampling = true; #endif hidbus_set_intr(dev, hmt_intr, sc); sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(dev)); evdev_set_phys(sc->evdev, device_get_nameunit(dev)); evdev_set_id(sc->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(sc->evdev, hw->serial); evdev_set_methods(sc->evdev, dev, &hmt_evdev_methods); evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT); evdev_set_flag(sc->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ switch (sc->type) { case HMT_TYPE_TOUCHSCREEN: evdev_support_prop(sc->evdev, INPUT_PROP_DIRECT); break; case HMT_TYPE_TOUCHPAD: evdev_support_prop(sc->evdev, INPUT_PROP_POINTER); if (sc->is_clickpad) evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD); break; default: KASSERT(0, ("hmt_attach: unsupported touch device type")); } evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); if (sc->do_timestamps) { evdev_support_event(sc->evdev, EV_MSC); evdev_support_msc(sc->evdev, MSC_TIMESTAMP); } #ifdef IICHID_SAMPLING if (sc->iichid_sampling) evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_AUTOREL); #endif nbuttons = 0; if (sc->max_button != 0 || sc->has_int_button) { evdev_support_event(sc->evdev, EV_KEY); if (sc->has_int_button) evdev_support_key(sc->evdev, BTN_LEFT); for (btn = 0; btn < sc->max_button; ++btn) { if (isset(sc->buttons, btn)) { evdev_support_key(sc->evdev, BTN_MOUSE + btn); nbuttons++; } } } evdev_support_abs(sc->evdev, ABS_MT_SLOT, 0, sc->cont_count_max - 1, 0, 0, 0); HMT_FOREACH_USAGE(sc->caps, i) { if (hmt_hid_map[i].reported) evdev_support_abs(sc->evdev, ABS_MT_FIRST + i, sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res); } err = evdev_register(sc->evdev); if (err) { hmt_detach(dev); return (ENXIO); } /* Announce information about the touch device */ device_printf(sc->dev, "%s %s with %d external button%s%s\n", sc->cont_count_max > 1 ? "Multitouch" : "Singletouch", sc->type == HMT_TYPE_TOUCHSCREEN ? "touchscreen" : "touchpad", nbuttons, nbuttons != 1 ? "s" : "", sc->is_clickpad ? ", click-pad" : ""); device_printf(sc->dev, "%d contact%s with [%s%s%s%s%s] properties. Report range [%d:%d] - [%d:%d]\n", (int)sc->cont_count_max, sc->cont_count_max != 1 ? "s" : "", isset(sc->caps, HMT_IN_RANGE) ? "R" : "", isset(sc->caps, HMT_CONFIDENCE) ? "C" : "", isset(sc->caps, HMT_WIDTH) ? "W" : "", isset(sc->caps, HMT_HEIGHT) ? "H" : "", isset(sc->caps, HMT_PRESSURE) ? "P" : "", (int)sc->ai[HMT_X].min, (int)sc->ai[HMT_Y].min, (int)sc->ai[HMT_X].max, (int)sc->ai[HMT_Y].max); return (0); } static int hmt_detach(device_t dev) { struct hmt_softc *sc = device_get_softc(dev); evdev_free(sc->evdev); return (0); } static void hmt_intr(void *context, void *buf, hid_size_t len) { struct hmt_softc *sc = context; size_t usage; union evdev_mt_slot *slot_data; uint32_t cont, btn; uint32_t cont_count; uint32_t width; uint32_t height; uint32_t int_btn = 0; uint32_t left_btn = 0; int slot; uint32_t scan_time; int32_t delta; uint8_t id; #ifdef IICHID_SAMPLING /* * Special packet of zero length is generated by iichid driver running * in polling mode at the start of inactivity period to workaround * "stuck touch" problem caused by miss of finger release events. * This snippet is to be removed after GPIO interrupt support is added. */ if (sc->iichid_sampling && len == 0) { sc->prev_touch = false; sc->timestamp = 0; /* EVDEV_FLAG_MT_AUTOREL releases all touches for us */ evdev_sync(sc->evdev); return; } #endif /* Ignore irrelevant reports */ id = sc->report_id != 0 ? *(uint8_t *)buf : 0; if (sc->report_id != id) { DPRINTF("Skip report with unexpected ID: %hhu\n", id); return; } /* Strip leading "report ID" byte */ if (sc->report_id != 0) { len--; buf = (uint8_t *)buf + 1; } /* * "In Serial mode, each packet contains information that describes a * single physical contact point. Multiple contacts are streamed * serially. In this mode, devices report all contact information in a * series of packets. The device sends a separate packet for each * concurrent contact." * * "In Parallel mode, devices report all contact information in a * single packet. Each physical contact is represented by a logical * collection that is embedded in the top-level collection." * * Since additional contacts that were not present will still be in the * report with contactid=0 but contactids are zero-based, find * contactcount first. */ if (sc->has_cont_count) cont_count = hid_get_udata(buf, len, &sc->cont_count_loc); else cont_count = 1; /* * "In Hybrid mode, the number of contacts that can be reported in one * report is less than the maximum number of contacts that the device * supports. For example, a device that supports a maximum of * 4 concurrent physical contacts, can set up its top-level collection * to deliver a maximum of two contacts in one report. If four contact * points are present, the device can break these up into two serial * reports that deliver two contacts each. * * "When a device delivers data in this manner, the Contact Count usage * value in the first report should reflect the total number of * contacts that are being delivered in the hybrid reports. The other * serial reports should have a contact count of zero (0)." */ if (cont_count != 0) sc->nconts_todo = cont_count; #ifdef HID_DEBUG DPRINTFN(6, "cont_count:%2u", (unsigned)cont_count); if (hmt_debug >= 6) { HMT_FOREACH_USAGE(sc->caps, usage) { if (hmt_hid_map[usage].usage != HMT_NO_USAGE) printf(" %-4s", hmt_hid_map[usage].name); } printf("\n"); } #endif /* Find the number of contacts reported in current report */ cont_count = MIN(sc->nconts_todo, sc->nconts_per_report); /* Use protocol Type B for reporting events */ for (cont = 0; cont < cont_count; cont++) { slot_data = &sc->slot_data; bzero(slot_data, sizeof(sc->slot_data)); HMT_FOREACH_USAGE(sc->caps, usage) { if (sc->locs[cont][usage].size > 0) slot_data->val[usage] = hid_get_udata( buf, len, &sc->locs[cont][usage]); } slot = evdev_mt_id_to_slot(sc->evdev, slot_data->id); #ifdef HID_DEBUG DPRINTFN(6, "cont%01x: data = ", cont); if (hmt_debug >= 6) { HMT_FOREACH_USAGE(sc->caps, usage) { if (hmt_hid_map[usage].usage != HMT_NO_USAGE) printf("%04x ", slot_data->val[usage]); } printf("slot = %d\n", slot); } #endif if (slot == -1) { DPRINTF("Slot overflow for contact_id %u\n", (unsigned)slot_data->id); continue; } if (slot_data->val[HMT_TIP_SWITCH] != 0 && !(isset(sc->caps, HMT_CONFIDENCE) && slot_data->val[HMT_CONFIDENCE] == 0)) { /* This finger is in proximity of the sensor */ sc->touch = true; slot_data->dist = !slot_data->val[HMT_IN_RANGE]; /* Divided by two to match visual scale of touch */ width = slot_data->val[HMT_WIDTH] >> 1; height = slot_data->val[HMT_HEIGHT] >> 1; slot_data->ori = width > height; slot_data->maj = MAX(width, height); slot_data->min = MIN(width, height); } else slot_data = NULL; evdev_mt_push_slot(sc->evdev, slot, slot_data); } sc->nconts_todo -= cont_count; if (sc->do_timestamps && sc->nconts_todo == 0) { /* HUD_SCAN_TIME is measured in 100us, convert to us. */ scan_time = hid_get_udata(buf, len, &sc->scan_time_loc); if (sc->prev_touch) { delta = scan_time - sc->scan_time; if (delta < 0) delta += sc->scan_time_max; } else delta = 0; sc->scan_time = scan_time; sc->timestamp += delta * 100; evdev_push_msc(sc->evdev, MSC_TIMESTAMP, sc->timestamp); sc->prev_touch = sc->touch; sc->touch = false; if (!sc->prev_touch) sc->timestamp = 0; } if (sc->nconts_todo == 0) { /* Report both the click and external left btns as BTN_LEFT */ if (sc->has_int_button) int_btn = hid_get_data(buf, len, &sc->int_btn_loc); if (isset(sc->buttons, 0)) left_btn = hid_get_data(buf, len, &sc->btn_loc[0]); if (sc->has_int_button || isset(sc->buttons, 0)) evdev_push_key(sc->evdev, BTN_LEFT, (int_btn != 0) | (left_btn != 0)); for (btn = 1; btn < sc->max_button; ++btn) { if (isset(sc->buttons, btn)) evdev_push_key(sc->evdev, BTN_MOUSE + btn, hid_get_data(buf, len, &sc->btn_loc[btn]) != 0); } evdev_sync(sc->evdev); } } static enum hmt_type hmt_hid_parse(struct hmt_softc *sc, const void *d_ptr, hid_size_t d_len, uint32_t tlc_usage, uint8_t tlc_index) { struct hid_absinfo ai; struct hid_item hi; struct hid_data *hd; uint32_t flags; size_t i; size_t cont = 0; enum hmt_type type; uint32_t left_btn, btn; int32_t cont_count_max = 0; uint8_t report_id = 0; bool finger_coll = false; bool cont_count_found = false; bool scan_time_found = false; bool has_int_button = false; #define HMT_HI_ABSOLUTE(hi) ((hi).nusages != 0 && \ ((hi).flags & (HIO_VARIABLE | HIO_RELATIVE)) == HIO_VARIABLE) #define HUMS_THQA_CERT 0xC5 /* * Get left button usage taking in account MS Precision Touchpad specs. * For Windows PTP report descriptor assigns buttons in following way: * Button 1 - Indicates Button State for touchpad button integrated * with digitizer. * Button 2 - Indicates Button State for external button for primary * (default left) clicking. * Button 3 - Indicates Button State for external button for secondary * (default right) clicking. * If a device only supports external buttons, it must still use * Button 2 and Button 3 to reference the external buttons. */ switch (tlc_usage) { case HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHSCREEN): type = HMT_TYPE_TOUCHSCREEN; left_btn = 1; break; case HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD): type = HMT_TYPE_TOUCHPAD; left_btn = 2; break; default: return (HMT_TYPE_UNSUPPORTED); } /* Parse features for mandatory maximum contact count usage */ if (!hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACT_MAX), hid_feature, tlc_index, 0, &sc->cont_max_loc, &flags, &sc->cont_max_rid, &ai) || (flags & (HIO_VARIABLE | HIO_RELATIVE)) != HIO_VARIABLE) return (HMT_TYPE_UNSUPPORTED); cont_count_max = ai.max; /* Parse features for button type usage */ if (hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_DIGITIZERS, HUD_BUTTON_TYPE), hid_feature, tlc_index, 0, &sc->btn_type_loc, &flags, &sc->btn_type_rid, NULL) && (flags & (HIO_VARIABLE | HIO_RELATIVE)) != HIO_VARIABLE) sc->btn_type_rid = 0; /* Parse features for THQA certificate report ID */ hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_MICROSOFT, HUMS_THQA_CERT), hid_feature, tlc_index, 0, NULL, NULL, &sc->thqa_cert_rid, NULL); /* Parse input for other parameters */ hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { switch (hi.kind) { case hid_collection: if (hi.collevel == 2 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_FINGER)) finger_coll = true; break; case hid_endcollection: if (hi.collevel == 1 && finger_coll) { finger_coll = false; cont++; } break; case hid_input: /* * Ensure that all usages belong to the same report */ if (HMT_HI_ABSOLUTE(hi) && (report_id == 0 || report_id == hi.report_ID)) report_id = hi.report_ID; else break; if (hi.collevel == 1 && left_btn == 2 && hi.usage == HID_USAGE2(HUP_BUTTON, 1)) { has_int_button = true; sc->int_btn_loc = hi.loc; break; } if (hi.collevel == 1 && hi.usage >= HID_USAGE2(HUP_BUTTON, left_btn) && hi.usage <= HID_USAGE2(HUP_BUTTON, HMT_BTN_MAX)) { btn = (hi.usage & 0xFFFF) - left_btn; setbit(sc->buttons, btn); sc->btn_loc[btn] = hi.loc; if (btn >= sc->max_button) sc->max_button = btn + 1; break; } if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTCOUNT)) { cont_count_found = true; sc->cont_count_loc = hi.loc; break; } if (hi.collevel == 1 && hi.usage == HID_USAGE2(HUP_DIGITIZERS, HUD_SCAN_TIME)) { scan_time_found = true; sc->scan_time_loc = hi.loc; sc->scan_time_max = hi.logical_maximum; break; } if (!finger_coll || hi.collevel != 2) break; if (cont >= MAX_MT_SLOTS) { DPRINTF("Finger %zu ignored\n", cont); break; } for (i = 0; i < HMT_N_USAGES; i++) { if (hi.usage == hmt_hid_map[i].usage) { /* * HUG_X usage is an array mapped to * both ABS_MT_POSITION and ABS_MT_TOOL * events. So don`t stop search if we * already have HUG_X mapping done. */ if (sc->locs[cont][i].size) continue; sc->locs[cont][i] = hi.loc; /* * Hid parser returns valid logical and * physical sizes for first finger only * at least on ElanTS 0x04f3:0x0012. */ if (cont > 0) break; setbit(sc->caps, i); sc->ai[i] = (struct hid_absinfo) { .max = hi.logical_maximum, .min = hi.logical_minimum, .res = hid_item_resolution(&hi), }; break; } } break; default: break; } } hid_end_parse(hd); /* Check for required HID Usages */ if ((!cont_count_found && cont != 1) || cont == 0) return (HMT_TYPE_UNSUPPORTED); for (i = 0; i < HMT_N_USAGES; i++) { if (hmt_hid_map[i].required && isclr(sc->caps, i)) return (HMT_TYPE_UNSUPPORTED); } /* Touchpads must have at least one button */ if (type == HMT_TYPE_TOUCHPAD && !sc->max_button && !has_int_button) return (HMT_TYPE_UNSUPPORTED); /* * According to specifications 'Contact Count Maximum' should be read * from Feature Report rather than from HID descriptor. Set sane * default value now to handle the case of 'Get Report' request failure */ if (cont_count_max < 1) cont_count_max = cont; /* Report touch orientation if both width and height are supported */ if (isset(sc->caps, HMT_WIDTH) && isset(sc->caps, HMT_HEIGHT)) { setbit(sc->caps, HMT_ORIENTATION); sc->ai[HMT_ORIENTATION].max = 1; } sc->cont_max_rlen = hid_report_size(d_ptr, d_len, hid_feature, sc->cont_max_rid); if (sc->btn_type_rid > 0) sc->btn_type_rlen = hid_report_size(d_ptr, d_len, hid_feature, sc->btn_type_rid); if (sc->thqa_cert_rid > 0) sc->thqa_cert_rlen = hid_report_size(d_ptr, d_len, hid_feature, sc->thqa_cert_rid); sc->report_id = report_id; sc->cont_count_max = cont_count_max; sc->nconts_per_report = cont; sc->has_int_button = has_int_button; sc->has_cont_count = cont_count_found; sc->has_scan_time = scan_time_found; return (type); } static int hmt_set_input_mode(struct hmt_softc *sc, enum hconf_input_mode mode) { devclass_t hconf_devclass; device_t hconf; int err; bus_topo_assert(); /* Find touchpad's configuration TLC */ hconf = hidbus_find_child(device_get_parent(sc->dev), HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIG)); if (hconf == NULL) return (ENXIO); /* Ensure that hconf driver is attached to configuration TLC */ if (device_is_alive(hconf) == 0) device_probe_and_attach(hconf); if (device_is_attached(hconf) == 0) return (ENXIO); hconf_devclass = devclass_find("hconf"); if (device_get_devclass(hconf) != hconf_devclass) return (ENXIO); /* hconf_set_input_mode can drop the topo lock while sleeping */ device_busy(hconf); err = hconf_set_input_mode(hconf, mode); device_unbusy(hconf); return (err); } static device_method_t hmt_methods[] = { DEVMETHOD(device_probe, hmt_probe), DEVMETHOD(device_attach, hmt_attach), DEVMETHOD(device_detach, hmt_detach), DEVMETHOD_END }; static driver_t hmt_driver = { .name = "hmt", .methods = hmt_methods, .size = sizeof(struct hmt_softc), }; DRIVER_MODULE(hmt, hidbus, hmt_driver, NULL, NULL); MODULE_DEPEND(hmt, hidbus, 1, 1, 1); MODULE_DEPEND(hmt, hid, 1, 1, 1); MODULE_DEPEND(hmt, hconf, 1, 1, 1); MODULE_DEPEND(hmt, evdev, 1, 1, 1); MODULE_VERSION(hmt, 1); HID_PNP_INFO(hmt_devs); diff --git a/sys/dev/hid/ietp.c b/sys/dev/hid/ietp.c index cdc5491b2c6d..1eaf95f17364 100644 --- a/sys/dev/hid/ietp.c +++ b/sys/dev/hid/ietp.c @@ -1,678 +1,678 @@ /*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2020, 2022 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. */ /* * Elan I2C Touchpad driver. Based on Linux driver. * https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/tree/drivers/input/mouse/elan_i2c_core.c */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define HID_DEBUG_VAR ietp_debug #include #include #include #ifdef HID_DEBUG static SYSCTL_NODE(_hw_hid, OID_AUTO, ietp, CTLFLAG_RW, 0, "Elantech Touchpad"); static int ietp_debug = 1; SYSCTL_INT(_hw_hid_ietp, OID_AUTO, debug, CTLFLAG_RWTUN, &ietp_debug, 1, "Debug level"); #endif #define IETP_PATTERN 0x0100 #define IETP_UNIQUEID 0x0101 #define IETP_FW_VERSION 0x0102 #define IETP_IC_TYPE 0x0103 #define IETP_OSM_VERSION 0x0103 #define IETP_NSM_VERSION 0x0104 #define IETP_TRACENUM 0x0105 #define IETP_MAX_X_AXIS 0x0106 #define IETP_MAX_Y_AXIS 0x0107 #define IETP_RESOLUTION 0x0108 #define IETP_PRESSURE 0x010A #define IETP_CONTROL 0x0300 #define IETP_CTRL_ABSOLUTE 0x0001 #define IETP_CTRL_STANDARD 0x0000 #define IETP_REPORT_LEN_LO 32 #define IETP_REPORT_LEN_HI 37 #define IETP_MAX_FINGERS 5 #define IETP_REPORT_ID_LO 0x5D #define IETP_REPORT_ID_HI 0x60 #define IETP_TOUCH_INFO 1 #define IETP_FINGER_DATA 2 #define IETP_FINGER_DATA_LEN 5 #define IETP_HOVER_INFO 28 #define IETP_WH_DATA 31 #define IETP_TOUCH_LMB (1 << 0) #define IETP_TOUCH_RMB (1 << 1) #define IETP_TOUCH_MMB (1 << 2) #define IETP_MAX_PRESSURE 255 #define IETP_FWIDTH_REDUCE 90 #define IETP_FINGER_MAX_WIDTH 15 #define IETP_PRESSURE_BASE 25 struct ietp_softc { device_t dev; struct evdev_dev *evdev; uint8_t report_id; hid_size_t report_len; uint16_t product_id; uint16_t ic_type; int32_t pressure_base; uint16_t max_x; uint16_t max_y; uint16_t trace_x; uint16_t trace_y; uint16_t res_x; /* dots per mm */ uint16_t res_y; bool hi_precision; bool is_clickpad; bool has_3buttons; }; static evdev_open_t ietp_ev_open; static evdev_close_t ietp_ev_close; static hid_intr_t ietp_intr; static int ietp_probe(struct ietp_softc *); static int ietp_attach(struct ietp_softc *); static int ietp_detach(struct ietp_softc *); static int32_t ietp_res2dpmm(uint8_t, bool); static device_identify_t ietp_iic_identify; static device_probe_t ietp_iic_probe; static device_attach_t ietp_iic_attach; static device_detach_t ietp_iic_detach; static device_resume_t ietp_iic_resume; static int ietp_iic_read_reg(device_t, uint16_t, size_t, void *); static int ietp_iic_write_reg(device_t, uint16_t, uint16_t); static int ietp_iic_set_absolute_mode(device_t, bool); #define IETP_IIC_DEV(pnp) \ { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE), HID_BUS(BUS_I2C), HID_PNP(pnp) } static const struct hid_device_id ietp_iic_devs[] = { IETP_IIC_DEV("ELAN0000"), IETP_IIC_DEV("ELAN0100"), IETP_IIC_DEV("ELAN0600"), IETP_IIC_DEV("ELAN0601"), IETP_IIC_DEV("ELAN0602"), IETP_IIC_DEV("ELAN0603"), IETP_IIC_DEV("ELAN0604"), IETP_IIC_DEV("ELAN0605"), IETP_IIC_DEV("ELAN0606"), IETP_IIC_DEV("ELAN0607"), IETP_IIC_DEV("ELAN0608"), IETP_IIC_DEV("ELAN0609"), IETP_IIC_DEV("ELAN060B"), IETP_IIC_DEV("ELAN060C"), IETP_IIC_DEV("ELAN060F"), IETP_IIC_DEV("ELAN0610"), IETP_IIC_DEV("ELAN0611"), IETP_IIC_DEV("ELAN0612"), IETP_IIC_DEV("ELAN0615"), IETP_IIC_DEV("ELAN0616"), IETP_IIC_DEV("ELAN0617"), IETP_IIC_DEV("ELAN0618"), IETP_IIC_DEV("ELAN0619"), IETP_IIC_DEV("ELAN061A"), IETP_IIC_DEV("ELAN061B"), IETP_IIC_DEV("ELAN061C"), IETP_IIC_DEV("ELAN061D"), IETP_IIC_DEV("ELAN061E"), IETP_IIC_DEV("ELAN061F"), IETP_IIC_DEV("ELAN0620"), IETP_IIC_DEV("ELAN0621"), IETP_IIC_DEV("ELAN0622"), IETP_IIC_DEV("ELAN0623"), IETP_IIC_DEV("ELAN0624"), IETP_IIC_DEV("ELAN0625"), IETP_IIC_DEV("ELAN0626"), IETP_IIC_DEV("ELAN0627"), IETP_IIC_DEV("ELAN0628"), IETP_IIC_DEV("ELAN0629"), IETP_IIC_DEV("ELAN062A"), IETP_IIC_DEV("ELAN062B"), IETP_IIC_DEV("ELAN062C"), IETP_IIC_DEV("ELAN062D"), IETP_IIC_DEV("ELAN062E"), /* Lenovo V340 Whiskey Lake U */ IETP_IIC_DEV("ELAN062F"), /* Lenovo V340 Comet Lake U */ IETP_IIC_DEV("ELAN0631"), IETP_IIC_DEV("ELAN0632"), IETP_IIC_DEV("ELAN0633"), /* Lenovo S145 */ IETP_IIC_DEV("ELAN0634"), /* Lenovo V340 Ice lake */ IETP_IIC_DEV("ELAN0635"), /* Lenovo V1415-IIL */ IETP_IIC_DEV("ELAN0636"), /* Lenovo V1415-Dali */ IETP_IIC_DEV("ELAN0637"), /* Lenovo V1415-IGLR */ IETP_IIC_DEV("ELAN1000"), }; static uint8_t const ietp_dummy_rdesc[] = { 0x05, HUP_GENERIC_DESKTOP, /* Usage Page (Generic Desktop Ctrls) */ 0x09, HUG_MOUSE, /* Usage (Mouse) */ 0xA1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (0x01) */ 0x95, IETP_REPORT_LEN_LO, /* Report Count (IETP_REPORT_LEN_LO) */ 0x75, 0x08, /* Report Size (8) */ 0x81, 0x02, /* Input (Data,Var,Abs) */ 0xC0, /* End Collection */ }; static const struct evdev_methods ietp_evdev_methods = { .ev_open = &ietp_ev_open, .ev_close = &ietp_ev_close, }; static int ietp_ev_open(struct evdev_dev *evdev) { - return (hidbus_intr_start(evdev_get_softc(evdev))); + return (hid_intr_start(evdev_get_softc(evdev))); } static int ietp_ev_close(struct evdev_dev *evdev) { - return (hidbus_intr_stop(evdev_get_softc(evdev))); + return (hid_intr_stop(evdev_get_softc(evdev))); } static int ietp_probe(struct ietp_softc *sc) { if (hidbus_find_child(device_get_parent(sc->dev), HID_USAGE2(HUP_DIGITIZERS, HUD_TOUCHPAD)) != NULL) { DPRINTFN(5, "Ignore HID-compatible touchpad on %s\n", device_get_nameunit(device_get_parent(sc->dev))); return (ENXIO); } device_set_desc(sc->dev, "Elan Touchpad"); return (BUS_PROBE_DEFAULT); } static int ietp_attach(struct ietp_softc *sc) { const struct hid_device_info *hw = hid_get_device_info(sc->dev); void *d_ptr; hid_size_t d_len; int32_t minor, major; int error; sc->report_id = sc->hi_precision ? IETP_REPORT_ID_HI : IETP_REPORT_ID_LO; sc->report_len = sc->hi_precision ? IETP_REPORT_LEN_HI : IETP_REPORT_LEN_LO; /* Try to detect 3-rd button by relative mouse TLC */ if (!sc->is_clickpad) { error = hid_get_report_descr(sc->dev, &d_ptr, &d_len); if (error != 0) { device_printf(sc->dev, "could not retrieve report " "descriptor from device: %d\n", error); return (ENXIO); } if (hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_BUTTON, 3), hid_input, hidbus_get_index(sc->dev), 0, NULL, NULL, NULL, NULL)) sc->has_3buttons = true; } sc->evdev = evdev_alloc(); evdev_set_name(sc->evdev, device_get_desc(sc->dev)); evdev_set_phys(sc->evdev, device_get_nameunit(sc->dev)); evdev_set_id(sc->evdev, hw->idBus, hw->idVendor, hw->idProduct, hw->idVersion); evdev_set_serial(sc->evdev, hw->serial); evdev_set_methods(sc->evdev, sc->dev, &ietp_evdev_methods); evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_STCOMPAT); evdev_set_flag(sc->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ evdev_support_event(sc->evdev, EV_SYN); evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_prop(sc->evdev, INPUT_PROP_POINTER); evdev_support_key(sc->evdev, BTN_LEFT); if (sc->is_clickpad) { evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD); } else { evdev_support_key(sc->evdev, BTN_RIGHT); if (sc->has_3buttons) evdev_support_key(sc->evdev, BTN_MIDDLE); } major = IETP_FINGER_MAX_WIDTH * MAX(sc->trace_x, sc->trace_y); minor = IETP_FINGER_MAX_WIDTH * MIN(sc->trace_x, sc->trace_y); evdev_support_abs(sc->evdev, ABS_MT_SLOT, 0, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TRACKING_ID, -1, IETP_MAX_FINGERS - 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_POSITION_X, 0, sc->max_x, 0, 0, sc->res_x); evdev_support_abs(sc->evdev, ABS_MT_POSITION_Y, 0, sc->max_y, 0, 0, sc->res_y); evdev_support_abs(sc->evdev, ABS_MT_PRESSURE, 0, IETP_MAX_PRESSURE, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_ORIENTATION, 0, 1, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MAJOR, 0, major, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_MT_TOUCH_MINOR, 0, minor, 0, 0, 0); evdev_support_abs(sc->evdev, ABS_DISTANCE, 0, 1, 0, 0, 0); error = evdev_register(sc->evdev); if (error != 0) { ietp_detach(sc); return (ENOMEM); } hidbus_set_intr(sc->dev, ietp_intr, sc); device_printf(sc->dev, "[%d:%d], %s\n", sc->max_x, sc->max_y, sc->is_clickpad ? "clickpad" : sc->has_3buttons ? "3 buttons" : "2 buttons"); return (0); } static int ietp_detach(struct ietp_softc *sc) { evdev_free(sc->evdev); return (0); } static void ietp_intr(void *context, void *buf, hid_size_t len) { struct ietp_softc *sc = context; union evdev_mt_slot slot_data; uint8_t *report, *fdata; int32_t finger; int32_t x, y, w, h, wh; /* we seem to get 0 length reports sometimes, ignore them */ if (len == 0) return; if (len != sc->report_len) { DPRINTF("wrong report length (%d vs %d expected)", len, sc->report_len); return; } report = buf; if (*report != sc->report_id) return; evdev_push_key(sc->evdev, BTN_LEFT, report[IETP_TOUCH_INFO] & IETP_TOUCH_LMB); evdev_push_key(sc->evdev, BTN_MIDDLE, report[IETP_TOUCH_INFO] & IETP_TOUCH_MMB); evdev_push_key(sc->evdev, BTN_RIGHT, report[IETP_TOUCH_INFO] & IETP_TOUCH_RMB); evdev_push_abs(sc->evdev, ABS_DISTANCE, (report[IETP_HOVER_INFO] & 0x40) >> 6); for (finger = 0, fdata = report + IETP_FINGER_DATA; finger < IETP_MAX_FINGERS; finger++, fdata += IETP_FINGER_DATA_LEN) { if ((report[IETP_TOUCH_INFO] & (1 << (finger + 3))) != 0) { if (sc->hi_precision) { x = fdata[0] << 8 | fdata[1]; y = fdata[2] << 8 | fdata[3]; wh = report[IETP_WH_DATA + finger]; } else { x = (fdata[0] & 0xf0) << 4 | fdata[1]; y = (fdata[0] & 0x0f) << 8 | fdata[2]; wh = fdata[3]; } if (x > sc->max_x || y > sc->max_y) { DPRINTF("[%d] x=%d y=%d over max (%d, %d)", finger, x, y, sc->max_x, sc->max_y); continue; } /* Reduce trace size to not treat large finger as palm */ w = (wh & 0x0F) * (sc->trace_x - IETP_FWIDTH_REDUCE); h = (wh >> 4) * (sc->trace_y - IETP_FWIDTH_REDUCE); slot_data = (union evdev_mt_slot) { .id = finger, .x = x, .y = sc->max_y - y, .p = MIN((int32_t)fdata[4] + sc->pressure_base, IETP_MAX_PRESSURE), .ori = w > h ? 1 : 0, .maj = MAX(w, h), .min = MIN(w, h), }; evdev_mt_push_slot(sc->evdev, finger, &slot_data); } else { evdev_push_abs(sc->evdev, ABS_MT_SLOT, finger); evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); } } evdev_sync(sc->evdev); } static int32_t ietp_res2dpmm(uint8_t res, bool hi_precision) { int32_t dpi; dpi = hi_precision ? 300 + res * 100 : 790 + res * 10; return (dpi * 10 /254); } static void ietp_iic_identify(driver_t *driver, device_t parent) { void *d_ptr; hid_size_t d_len; int isize; uint8_t iid; if (HIDBUS_LOOKUP_ID(parent, ietp_iic_devs) == NULL) return; if (hid_get_report_descr(parent, &d_ptr, &d_len) != 0) return; /* * Some Elantech trackpads have a mangled HID report descriptor, which * reads as having an incorrect input size (i.e. < IETP_REPORT_LEN_LO). * If the input size is incorrect, load a dummy report descriptor. */ isize = hid_report_size_max(d_ptr, d_len, hid_input, &iid); if (isize >= IETP_REPORT_LEN_LO) return; hid_set_report_descr(parent, ietp_dummy_rdesc, sizeof(ietp_dummy_rdesc)); } static int ietp_iic_probe(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); device_t iichid; int error; error = HIDBUS_LOOKUP_DRIVER_INFO(dev, ietp_iic_devs); if (error != 0) return (error); iichid = device_get_parent(device_get_parent(dev)); if (device_get_devclass(iichid) != devclass_find("iichid")) return (ENXIO); sc->dev = dev; return (ietp_probe(sc)); } static int ietp_iic_attach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); uint16_t buf, reg; uint8_t *buf8; uint8_t pattern; buf8 = (uint8_t *)&buf; if (ietp_iic_read_reg(dev, IETP_UNIQUEID, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading product ID\n"); return (EIO); } sc->product_id = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_PATTERN, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pattern\n"); return (EIO); } pattern = buf == 0xFFFF ? 0 : buf8[1]; sc->hi_precision = pattern >= 0x02; reg = pattern >= 0x01 ? IETP_IC_TYPE : IETP_OSM_VERSION; if (ietp_iic_read_reg(dev, reg, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading IC type\n"); return (EIO); } sc->ic_type = pattern >= 0x01 ? be16toh(buf) : buf8[1]; if (ietp_iic_read_reg(dev, IETP_NSM_VERSION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading SM version\n"); return (EIO); } sc->is_clickpad = (buf8[0] & 0x10) != 0; if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(sc->dev, "failed to set absolute mode\n"); return (EIO); } if (ietp_iic_read_reg(dev, IETP_MAX_X_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max x\n"); return (EIO); } sc->max_x = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_MAX_Y_AXIS, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading max y\n"); return (EIO); } sc->max_y = le16toh(buf); if (ietp_iic_read_reg(dev, IETP_TRACENUM, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading trace info\n"); return (EIO); } sc->trace_x = sc->max_x / buf8[0]; sc->trace_y = sc->max_y / buf8[1]; if (ietp_iic_read_reg(dev, IETP_PRESSURE, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading pressure format\n"); return (EIO); } sc->pressure_base = (buf8[0] & 0x10) ? 0 : IETP_PRESSURE_BASE; if (ietp_iic_read_reg(dev, IETP_RESOLUTION, sizeof(buf), &buf) != 0) { device_printf(sc->dev, "failed reading resolution\n"); return (EIO); } /* Conversion from internal format to dot per mm */ sc->res_x = ietp_res2dpmm(buf8[0], sc->hi_precision); sc->res_y = ietp_res2dpmm(buf8[1], sc->hi_precision); return (ietp_attach(sc)); } static int ietp_iic_detach(device_t dev) { struct ietp_softc *sc = device_get_softc(dev); if (ietp_iic_set_absolute_mode(dev, false) != 0) device_printf(dev, "failed setting standard mode\n"); return (ietp_detach(sc)); } static int ietp_iic_resume(device_t dev) { if (ietp_iic_set_absolute_mode(dev, true) != 0) { device_printf(dev, "reset when resuming failed: \n"); return (EIO); } return (0); } static int ietp_iic_set_absolute_mode(device_t dev, bool enable) { struct ietp_softc *sc = device_get_softc(dev); static const struct { uint16_t ic_type; uint16_t product_id; } special_fw[] = { { 0x0E, 0x05 }, { 0x0E, 0x06 }, { 0x0E, 0x07 }, { 0x0E, 0x09 }, { 0x0E, 0x13 }, { 0x08, 0x26 }, }; uint16_t val; int i, error; bool require_wakeup; error = 0; /* * Some ASUS touchpads need to be powered on to enter absolute mode. */ require_wakeup = false; for (i = 0; i < nitems(special_fw); i++) { if (sc->ic_type == special_fw[i].ic_type && sc->product_id == special_fw[i].product_id) { require_wakeup = true; break; } } - if (require_wakeup && hidbus_intr_start(dev) != 0) { + if (require_wakeup && hid_intr_start(dev) != 0) { device_printf(dev, "failed writing poweron command\n"); return (EIO); } val = enable ? IETP_CTRL_ABSOLUTE : IETP_CTRL_STANDARD; if (ietp_iic_write_reg(dev, IETP_CONTROL, val) != 0) { device_printf(dev, "failed setting absolute mode\n"); error = EIO; } - if (require_wakeup && hidbus_intr_stop(dev) != 0) { + if (require_wakeup && hid_intr_stop(dev) != 0) { device_printf(dev, "failed writing poweroff command\n"); error = EIO; } return (error); } static int ietp_iic_read_reg(device_t dev, uint16_t reg, size_t len, void *val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[2] = { reg & 0xff, (reg >> 8) & 0xff }; struct iic_msg msgs[2] = { { addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd }, { addr, IIC_M_RD, len, val }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; int error; DPRINTF("Read reg 0x%04x with size %zu\n", reg, len); error = hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird); if (error != 0) return (error); DPRINTF("Response: %*D\n", (int)len, val, " "); return (0); } static int ietp_iic_write_reg(device_t dev, uint16_t reg, uint16_t val) { device_t iichid = device_get_parent(device_get_parent(dev)); uint16_t addr = iicbus_get_addr(iichid) << 1; uint8_t cmd[4] = { reg & 0xff, (reg >> 8) & 0xff, val & 0xff, (val >> 8) & 0xff }; struct iic_msg msgs[1] = { { addr, IIC_M_WR, sizeof(cmd), cmd }, }; struct iic_rdwr_data ird = { msgs, nitems(msgs) }; DPRINTF("Write reg 0x%04x with value 0x%04x\n", reg, val); return (hid_ioctl(dev, I2CRDWR, (uintptr_t)&ird)); } static device_method_t ietp_methods[] = { DEVMETHOD(device_identify, ietp_iic_identify), DEVMETHOD(device_probe, ietp_iic_probe), DEVMETHOD(device_attach, ietp_iic_attach), DEVMETHOD(device_detach, ietp_iic_detach), DEVMETHOD(device_resume, ietp_iic_resume), DEVMETHOD_END }; static driver_t ietp_driver = { .name = "ietp", .methods = ietp_methods, .size = sizeof(struct ietp_softc), }; DRIVER_MODULE(ietp, hidbus, ietp_driver, NULL, NULL); MODULE_DEPEND(ietp, hidbus, 1, 1, 1); MODULE_DEPEND(ietp, hid, 1, 1, 1); MODULE_DEPEND(ietp, evdev, 1, 1, 1); MODULE_VERSION(ietp, 1); HID_PNP_INFO(ietp_iic_devs);