diff --git a/etc/mtree/BSD.include.dist b/etc/mtree/BSD.include.dist --- a/etc/mtree/BSD.include.dist +++ b/etc/mtree/BSD.include.dist @@ -54,6 +54,8 @@ .. firewire .. + hid + .. hwpmc .. hyperv diff --git a/include/Makefile b/include/Makefile --- a/include/Makefile +++ b/include/Makefile @@ -159,7 +159,7 @@ done; \ fi .endfor -.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hyperv:Ndev/pci:Ndev/veriexec} ${LSUBSUBDIRS} +.for i in ${LDIRS} ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec} ${LSUBSUBDIRS} cd ${SRCTOP}/sys; \ ${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 $i/*.h \ ${SDESTDIR}${INCLUDEDIR}/$i @@ -179,6 +179,11 @@ ${SDESTDIR}${INCLUDEDIR}/dev/evdev; \ ${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 uinput.h \ ${SDESTDIR}${INCLUDEDIR}/dev/evdev + cd ${SRCTOP}/sys/dev/hid; \ + ${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 hid.h \ + ${SDESTDIR}${INCLUDEDIR}/dev/hid; \ + ${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 hidraw.h \ + ${SDESTDIR}${INCLUDEDIR}/dev/hid cd ${SRCTOP}/sys/dev/hyperv/include; \ ${INSTALL} -C ${TAG_ARGS} -o ${BINOWN} -g ${BINGRP} -m 444 hyperv.h \ ${SDESTDIR}${INCLUDEDIR}/dev/hyperv @@ -262,7 +267,7 @@ ${INSTALL_SYMLINK} ${TAG_ARGS} ../../../sys/$i/$$h ${SDESTDIR}${INCLUDEDIR}/$i; \ done .endfor -.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hyperv:Ndev/pci:Ndev/veriexec} +.for i in ${LSUBDIRS:Ndev/agp:Ndev/acpica:Ndev/evdev:Ndev/hid:Ndev/hyperv:Ndev/pci:Ndev/veriexec} cd ${SRCTOP}/sys/$i; \ for h in *.h; do \ ${INSTALL_SYMLINK} ${TAG_ARGS} ../../../../sys/$i/$$h ${SDESTDIR}${INCLUDEDIR}/$i; \ @@ -283,6 +288,11 @@ ln -fs ../../../../sys/dev/evdev/$$h \ ${SDESTDIR}${INCLUDEDIR}/dev/evdev; \ done + cd ${SRCTOP}/sys/dev/hid; \ + for h in hid.h hidraw.h; do \ + ln -fs ../../../../sys/dev/hid/$$h \ + ${SDESTDIR}${INCLUDEDIR}/dev/hid; \ + done cd ${SRCTOP}/sys/dev/hyperv/include; \ for h in hyperv.h; do \ ${INSTALL_SYMLINK} ${TAG_ARGS} ../../../../sys/dev/hyperv/include/$$h \ diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -181,13 +181,24 @@ gpioths.4 \ gre.4 \ h_ertt.4 \ + hconf.4 \ + hcons.4 \ + hgame.4 \ + hidbus.4 \ + hidquirk.4 \ + hidraw.4 \ hifn.4 \ + hkbd.4 \ + hms.4 \ + hmt.4 \ + hpen.4 \ hpet.4 \ ${_hpt27xx.4} \ ${_hptiop.4} \ ${_hptmv.4} \ ${_hptnr.4} \ ${_hptrr.4} \ + hsctrl.4 \ ${_hv_kvp.4} \ ${_hv_netvsc.4} \ ${_hv_storvsc.4} \ @@ -211,6 +222,7 @@ iic_gpiomux.4 \ iicbb.4 \ iicbus.4 \ + iichid.4 \ iicmux.4 \ iicsmb.4 \ iir.4 \ @@ -428,6 +440,7 @@ ppi.4 \ procdesc.4 \ proto.4 \ + ps4dshock.4 \ psm.4 \ pst.4 \ pt.4 \ @@ -584,6 +597,7 @@ wmt.4 \ ${_wpi.4} \ wsp.4 \ + xb360gp.4 \ ${_xen.4} \ xhci.4 \ xl.4 \ @@ -1019,6 +1033,7 @@ usb.4 \ usb_quirk.4 \ usb_template.4 \ + usbhid.4 \ usfs.4 \ uslcom.4 \ uvisor.4 \ diff --git a/share/man/man4/atp.4 b/share/man/man4/atp.4 --- a/share/man/man4/atp.4 +++ b/share/man/man4/atp.4 @@ -38,6 +38,7 @@ your kernel configuration file: .Bd -ragged -offset indent .Cd "device atp" +.Cd "device hid" .Cd "device usb" .Ed .Pp diff --git a/share/man/man4/hconf.4 b/share/man/man4/hconf.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hconf.4 @@ -0,0 +1,90 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HCONF 4 +.Os +.Sh NAME +.Nm hconf +.Nd MS Windows Precision Touchpad configuration driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hconf" +.Cd "device hid" +.Cd "device hidbus" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hconf_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for generic MS Windows Precision Touchpad +configuration collection. +It enables the host to configure two different aspects of the device. +One allows the host to select input mode, and the other allows the host to be +selective in what is reported. +.Sh SYSCTL VARIABLES +Next parameters are available as +.Xr sysctl 8 +variables. +Debug parameter is available as +.Xr loader 8 +tunable as well. +.Bl -tag -width indent +.It Va dev.hmt.*.input_mode +HID device input mode: 0 = mouse, 3 = touchpad. +.It Va dev.hmt.*.surface_switch +Enable / disable switch for surface: 1 = on, 0 = off. +.It Va dev.hmt.*.buttons_switch +Enable / disable switch for buttons: 1 = on, 0 = off. +.It Va hw.hid.hconf.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh SEE ALSO +.Xr hms 4 , +.Xr hmt 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +Switch parameter support was added by +.An Andriy Gapon Aq Mt avg@FreeBSD.org . diff --git a/share/man/man4/hcons.4 b/share/man/man4/hcons.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hcons.4 @@ -0,0 +1,91 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HCONS 4 +.Os +.Sh NAME +.Nm hcons +.Nd HID consumer page controls driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hcons" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hgame_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for HID consumer page controls most often used as +"Multimedia keys" found on many keyboards. +.Pp +The +.Pa /dev/input/event* +device presents the consumer page controls as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hcons.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh SEE ALSO +.Xr iichid 4 , +.Xr usbhid 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/hgame.4 b/share/man/man4/hgame.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hgame.4 @@ -0,0 +1,98 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HGAME 4 +.Os +.Sh NAME +.Nm hgame +.Nd Generic HID game controller (joystick/gamepad) driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hgame" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hgame_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for generic game controllers (joysticks/gamepads) +that attach to the HID transport backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +.Pp +The +.Pa /dev/input/event* +device presents the game controller as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hgame.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh SEE ALSO +.Xr iichid 4 , +.Xr usbhid 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Greg V Aq Mt greg@unrelenting.technology . +.Pp +This manual page was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/hidbus.4 b/share/man/man4/hidbus.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hidbus.4 @@ -0,0 +1,102 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HIDBUS 4 +.Os +.Sh NAME +.Nm hidbus +.Nd generic HID bus driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hidbus" +.Cd "device hid" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hidbus_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for multiple HID driver attachments to single HID +transport backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +.Pp +Each HID device can have several components, e.g., a keyboard and +a mouse. +These components use different report identifiers (a byte) combined into +groups called collections to distinguish which one data is coming from. +The +.Nm +driver has other drivers attached that handle particular +kinds of devices and +.Nm +broadcasts data to all of them. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hidbus.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh SEE ALSO +.Xr hconf 4 , +.Xr hcons 4 , +.Xr hgame 4 , +.Xr hidraw 4 , +.Xr hkbd 4 , +.Xr hms 4 , +.Xr hmt 4 , +.Xr hpen 4 , +.Xr hsctrl 4 , +.Xr hskbd 4 , +.Xr iichid 4 , +.Xr usbhid 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/hidquirk.4 b/share/man/man4/hidquirk.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hidquirk.4 @@ -0,0 +1,143 @@ +.\" +.\" Copyright (c) 2010 AnyWi Technologies +.\" All rights reserved. +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 16, 2020 +.Dt HIDQUIRK 4 +.Os +.Sh NAME +.Nm hidquirk +.Nd HID quirks module +.Sh SYNOPSIS +To compile this module into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hid" +.Ed +.Pp +Alternatively, to load the module at boot +time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hidquirk_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +module provides support for adding quirks for HID devices +.Bl -tag -width Ds +.It HQ_HID_IGNORE +device should be ignored by hid class +.It HQ_KBD_BOOTPROTO +device should set the boot protocol +.It HQ_MS_BOOTPROTO +device should set the boot protocol +.It HQ_MS_BAD_CLASS +doesn't identify properly +.It HQ_MS_LEADING_BYTE +mouse sends an unknown leading byte +.It HQ_MS_REVZ +mouse has Z-axis reversed +.It HQ_SPUR_BUT_UP +spurious mouse button up events +.It HQ_MT_TIMESTAMP +Multitouch device exports HW timestamps +.Dv 0x1b5a01 +.El +.Pp +See +.Pa /sys/dev/hid/hidquirk.h +for the complete list of supported quirks. +.Sh LOADER TUNABLE +The following tunable can be set at the +.Xr loader 8 +prompt before booting the kernel, or stored in +.Xr loader.conf 5 . +.Bl -tag -width indent +.It Va hw.hid.quirk.%d +The value is a string whose format is: +.Bd -literal -offset indent +.Qo BusId VendorId ProductId LowRevision HighRevision HQ_QUIRK,... Qc +.Ed +.Pp +Installs the quirks +.Ic HQ_QUIRK,... +for all HID devices matching +.Ic BusId +and +.Ic VendorId +and +.Ic ProductId +which have a hardware revision between and including +.Ic LowRevision +and +.Ic HighRevision . +.Pp +.Ic BusId , +.Ic VendorId , +.Ic ProductId , +.Ic LowRevision +and +.Ic HighRevision +are all 16 bits numbers which can be decimal or hexadecimal based. +.Pp +A maximum of 100 variables +.Ic hw.hid.quirk.0, .1, ..., .99 +can be defined. +.Pp +If a matching entry is found in the kernel's internal quirks table, it +is replaced by the new definition. +.Pp +Else a new entry is created given that the quirk table is not full. +.Pp +The kernel iterates over the +.Ic hw.hid.quirk.N +variables starting at +.Ic N = 0 +and stops at +.Ic N = 99 +or the first non-existing one. +.El +.Sh EXAMPLES +To install a quirk at boot time, place one or several lines like the +following in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hw.hid.quirk.0="0x18 0x6cb 0x1941 0 0xffff HQ_MT_TIMESTAMP" +.Ed +.Sh HISTORY +The +.Nm +module appeared in +.Fx 13.0 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Hans Petter Selasky Aq Mt hselasky@FreeBSD.org +for +.Xr usb 4 +subsystem and adopted to +.Xr hid 4 +by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +This manual page is based on +.Xr usb_quirk 4 +manual page written by +.An Nick Hibma Aq Mt n_hibma@FreeBSD.org . diff --git a/share/man/man4/hidraw.4 b/share/man/man4/hidraw.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hidraw.4 @@ -0,0 +1,308 @@ +.\" $NetBSD: uhid.4,v 1.13 2001/12/29 14:41:59 augustss Exp $ +.\" +.\" Copyright (c) 1999, 2001 The NetBSD Foundation, Inc. +.\" All rights reserved. +.\" +.\" This code is derived from software contributed to The NetBSD Foundation +.\" by Lennart Augustsson. +.\" +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd July 1, 2018 +.Dt HIDRAW 4 +.Os +.Sh NAME +.Nm hidraw +.Nd Raw Access to HID devices +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hidraw" +.Cd "device hid" +.Cd "device hidbus" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hidraw_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides a raw interface to Human Interface Devices (HIDs). +The reports are sent to and received from the device unmodified. +.Pp +The device handles 2 sets of +.Xr ioctl 2 +calls: +.Pp +.Fx +.Xr uhid 4 +\-compatible calls: +.Bl -tag -width indent +.It Dv HID_GET_REPORT_ID Pq Vt int +Get the report identifier used by this HID report. +.It Dv HID_GET_REPORT_DESC Pq Vt "struct hidraw_gen_descriptor" +Get the HID report descriptor. +Copies a maximum of +.Va hgd_maxlen +bytes of the report descriptor data into the memory +specified by +.Va hgd_data . +Upon return +.Va hgd_actlen +is set to the number of bytes copied. +Using +this descriptor the exact layout and meaning of data to/from +the device can be found. +The report descriptor is delivered +without any processing. +.Bd -literal +struct hidraw_gen_descriptor { + void *hgd_data; + uint16_t hgd_maxlen; + uint16_t hgd_actlen; + uint8_t hgd_report_type; + ... +}; +.Ed +.It Dv HID_SET_IMMED Pq Vt int +Sets the device in a mode where each +.Xr read 2 +will return the current value of the input report. +Normally +a +.Xr read 2 +will only return the data that the device reports on its +interrupt pipe. +This call may fail if the device does not support +this feature. +.It Dv HID_GET_REPORT Pq Vt "struct hidraw_gen_descriptor" +Get a report from the device without waiting for data on +the interrupt pipe. +Copies a maximum of +.Va hgd_maxlen +bytes of the report data into the memory specified by +.Va hgd_data . +Upon return +.Va hgd_actlen +is set to the number of bytes copied. +The +.Va hgd_report_type +field indicates which report is requested. +It should be +.Dv HID_INPUT_REPORT , +.Dv HID_OUTPUT_REPORT , +or +.Dv HID_FEATURE_REPORT . +On a device which uses numbered reports, the first byte of the returned data +is the report number. +The report data begins from the second byte. +For devices which do not use numbered reports, the report data begins at the +first byte. +This call may fail if the device does not support this feature. +.It Dv HID_SET_REPORT Pq Vt "struct hidraw_gen_descriptor" +Set a report in the device. +The +.Va hgd_report_type +field indicates which report is to be set. +It should be +.Dv HID_INPUT_REPORT , +.Dv HID_OUTPUT_REPORT , +or +.Dv HID_FEATURE_REPORT . +The value of the report is specified by the +.Va hgd_data +and the +.Va hgd_maxlen +fields. +On a device which uses numbered reports, the first byte of data to be sent is +the report number. +The report data begins from the second byte. +For devices which do not use numbered reports, the report data begins at the +first byte. +This call may fail if the device does not support this feature. +.El +.Pp +Linux +.Nm +\-compatible calls: +.Bl -tag -width indent +.It Dv HIDIOCGRDESCSIZE Pq Vt int +Get the HID report descriptor size. +Returns the size of the device report descriptor to use with +.Dv HIDIOCGRDESC +.Xr ioctl 2 . +.It Dv HIDIOCGRDESC Pq Vt "struct hidraw_report_descriptor" +Get the HID report descriptor. +Copies a maximum of +.Va size +bytes of the report descriptor data into the memory +specified by +.Va value . +.Bd -literal +struct hidraw_report_descriptor { + uint32_t size; + uint8_t value[HID_MAX_DESCRIPTOR_SIZE]; +}; +.Ed +.It Dv HIDIOCGRAWINFO Pq Vt "struct hidraw_devinfo" +Get structure containing the bus type, the vendor ID (VID), and product ID +(PID) of the device. The bus type can be one of: +.Dv BUS_USB +or +.Dv BUS_I2C +which are defined in dev/evdev/input.h. +.Bd -literal +struct hidraw_devinfo { + uint32_t bustype; + int16_t vendor; + int16_t product; +}; +.Ed +.It Dv HIDIOCGRAWNAME(len) Pq Vt "char[] buf" +Get device description. +Copies a maximum of +.Va len +bytes of the device description previously set with +.Xr device_set_desc 9 +procedure into the memory +specified by +.Va buf . +.It Dv HIDIOCGRAWPHYS(len) Pq Vt "char[] buf" +Get the newbus path to the device. +.\For Bluetooth devices, it returns the hardware (MAC) address of the device. +Copies a maximum of +.Va len +bytes of the newbus device path +into the memory +specified by +.Va buf . +.It Dv HIDIOCGFEATURE(len) Pq Vt "void[] buf" +Get a feature report from the device. +Copies a maximum of +.Va len +bytes of the feature report data into the memory specified by +.Va buf . +The first byte of the supplied buffer should be set to the report +number of the requested report. +For devices which do not use numbered reports, set the first byte to 0. +The report will be returned starting at the first byte of the buffer +(ie: the report number is not returned). +This call may fail if the device does not support this feature. +.It Dv HIDIOCSFEATURE(len) Pq Vt "void[] buf" +Set a feature Report in the device. +The value of the report is specified by the +.Va buf +and the +.Va len +parameters. +Set the first byte of the supplied buffer to the report number. +For devices which do not use numbered reports, set the first byte to 0. +The report data begins in the second byte. +Make sure to set len accordingly, to one more than the length of the report +(to account for the report number). +This call may fail if the device does not support this feature. +.El +.Pp +Use +.Xr read 2 +to get data from the device. +Data should be read in chunks of the +size prescribed by the report descriptor. +On a device which uses numbered reports, the first byte of the returned data +is the report number. +The report data begins from the second byte. +For devices which do not use numbered reports, the report data begins at the +first byte. +.Pp +Use +.Xr write 2 +to send data to the device. +Data should be written in chunks of the +size prescribed by the report descriptor. +The first byte of the buffer passed to +.Xr write 2 +should be set to the report number. +If the device does not use numbered reports, there are 2 operation modes: +.Nm +mode and +.Xr uhid 4 +mode. +In the +.Nm +mode, the first byte should be set to 0 and the report data itself should +begin at the second byte. +In the +.Xr uhid 4 +mode, the report data should begin at the first byte. +The modes can be switched with issuing one of +.Dv HIDIOCGRDESC +or +.Dv HID_GET_REPORT_DESC +.Xr ioctl 2 +accordingly. +Default mode is +.Nm . +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hidraw.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width ".Pa /dev/hidraw?" +.It Pa /dev/hidraw? +.El +.Sh SEE ALSO +.Xr usbhidctl 1 , +.Xr hid 4 , +.Xr hidbus 4 , +.Xr uhid 4 +.Sh HISTORY +The +.Xr uhid 4 +driver +appeared in +.Nx 1.4 . +.Nm +protocol support was added in +.Fx 13 +by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +This manual page was adopted from +.Nx +by +.An Tom Rhodes Aq Mt trhodes@FreeBSD.org +in April 2002. diff --git a/share/man/man4/hkbd.4 b/share/man/man4/hkbd.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hkbd.4 @@ -0,0 +1,195 @@ +.\" Copyright (c) 1997, 1998 +.\" Nick Hibma . 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 12, 2020 +.Dt HKBD 4 +.Os +.Sh NAME +.Nm hkbd +.Nd HID keyboard driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following line in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hkbd" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device evdev" +.Cd "options EVDEV_SUPPORT" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hkbd_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for keyboards that attach to the HID transport +backend. +.Xr hid 4 , +.Xr hidbus 4 , +and one of +.Xr iichid 4 +or +.Xr usbhid 4 +must be configured in the kernel as well. +.Sh CONFIGURATION +By default, the keyboard subsystem does not create the appropriate devices yet. +Make sure you reconfigure your kernel with the following option in the kernel +config file: +.Pp +.Dl "options KBD_INSTALL_CDEV" +.Pp +If both an AT keyboard USB keyboards are used at the same time, the +AT keyboard will appear as +.Pa kbd0 +in +.Pa /dev . +The USB keyboards will be +.Pa kbd1 , kbd2 , +etc. +You can see some information about the keyboard with the following command: +.Pp +.Dl "kbdcontrol -i < /dev/kbd1" +.Pp +or load a keymap with +.Pp +.Dl "kbdcontrol -l keymaps/pt.iso < /dev/kbd1" +.Pp +See +.Xr kbdcontrol 1 +for more possible options. +.Pp +You can swap console keyboards by using the command +.Pp +.Dl "kbdcontrol -k /dev/kbd1" +.Pp +From this point on, the first USB keyboard will be the keyboard +to be used by the console. +.Pp +If you want to use a USB keyboard as your default and not use an AT keyboard at +all, you will have to remove the +.Cd "device atkbd" +line from the kernel configuration file. +Because of the device initialization order, +the USB keyboard will be detected +.Em after +the console driver +initializes itself and you have to explicitly tell the console +driver to use the existence of the USB keyboard. +This can be done in +one of the following two ways. +.Pp +Run the following command as a part of system initialization: +.Pp +.Dl "kbdcontrol -k /dev/kbd0 < /dev/ttyv0 > /dev/null" +.Pp +(Note that as the USB keyboard is the only keyboard, it is accessed as +.Pa /dev/kbd0 ) +or otherwise tell the console driver to periodically look for a +keyboard by setting a flag in the kernel configuration file: +.Pp +.Dl "device sc0 at isa? flags 0x100" +.Pp +With the above flag, the console driver will try to detect any +keyboard in the system if it did not detect one while it was +initialized at boot time. +.Sh DRIVER CONFIGURATION +.D1 Cd "options KBD_INSTALL_CDEV" +.Pp +Make the keyboards available through a character device in +.Pa /dev . +.Pp +.D1 Cd options HKBD_DFLT_KEYMAP +.D1 Cd makeoptions HKBD_DFLT_KEYMAP=fr.iso +.Pp +The above lines will put the French ISO keymap in the ukbd driver. +You can specify any keymap in +.Pa /usr/share/syscons/keymaps +or +.Pa /usr/share/vt/keymaps +(depending on the console driver being used) with this option. +.Pp +.D1 Cd "options KBD_DISABLE_KEYMAP_LOADING" +.Pp +Do not allow the user to change the keymap. +Note that these options also affect the AT keyboard driver, +.Xr atkbd 4 . +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hkbd.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width ".Pa /dev/input/event*" -compact +.It Pa /dev/kbd* +blocking device nodes +.It Pa /dev/input/event* +input event device nodes. +.El +.Sh EXAMPLES +.D1 Cd "device hkbd" +.Pp +Add the +.Nm +driver to the kernel. +.Sh SEE ALSO +.Xr kbdcontrol 1 , +.Xr hid 4 , +.Xr hidbus 4 , +.Xr iichid 4 , +.Xr syscons 4 , +.Xr usbhid 4 , +.Xr vt 4 , +.Xr config 8 +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Lennart Augustsson Aq Mt augustss@cs.chalmers.se +for +.Nx +and was substantially rewritten for +.Fx +by +.An Kazutaka YOKOTA Aq Mt yokota@zodiac.mech.utsunomiya-u.ac.jp . +.Pp +This manual page was written by +.An Nick Hibma Aq Mt n_hibma@FreeBSD.org +with a large amount of input from +.An Kazutaka YOKOTA Aq Mt yokota@zodiac.mech.utsunomiya-u.ac.jp . diff --git a/share/man/man4/hms.4 b/share/man/man4/hms.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hms.4 @@ -0,0 +1,108 @@ +.\" Copyright (c) +.\" 1999 Nick Hibma . All rights reserved. +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 12, 2020 +.Dt HMS 4 +.Os +.Sh NAME +.Nm hms +.Nd HID mouse driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hms" +.Cd "device hidbus" +.Cd "device hid" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hms_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for HID mice that attach to the HID transport +backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +Supported are +mice with any number of buttons, mice with a wheel and absolute mice. +.Pp +The +.Pa /dev/input/eventX +device presents the mouse as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hms.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/eventX -compact +.It Pa /dev/input/eventX +input event device node. +.El +.Sh SEE ALSO +.Xr iichid 4 , +.Xr usbhid 4 , +.Xr xorg.conf 5 Pq Pa ports/x11/xorg +.\.Xr moused 8 +.Sh BUGS +.Nm +cannot act like +.Xr sysmouse 4 +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +.Pp +This manual page was originally written by +.An Nick Hibma Aq Mt n_hibma@FreeBSD.org +for +.Xr umt 4 +driver and was adopted for +.Nm +by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/hmt.4 b/share/man/man4/hmt.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hmt.4 @@ -0,0 +1,80 @@ +.\" Copyright (c) 2014-2020 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. +.\" +.\" $FreeBSD$ +.\" +.Dd August 11, 2020 +.Dt HMT 4 +.Os +.Sh NAME +.Nm hmt +.Nd MS Windows 7/8/10 - compatible HID multi-touch device driver +.Sh SYNOPSIS +To compile this driver into the kernel, place the following lines into +your kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hmt" +.Cd "device hidbus" +.Cd "device hid" +.Cd "device hconf" +.Cd "device evdev" + +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hmt_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for the MS Windows 7/8/10 - compatible HID +multi-touch devices found in many laptops. +.Pp +To get multi-touch device working in +.Xr X 7 , +install +.Pa ports/x11-drivers/xf86-input-evdev . +.Sh FILES +.Nm +creates a pseudo-device file, +.Pa /dev/input/eventX +which presents the multi-touch device as an input event device. +.Sh SEE ALSO +.Xr hid 4 , +.Xr loader.conf 5 , +.Xr xorg.conf 5 Pq Pa ports/x11/xorg , +.Xr evdev 4 Pq Pa ports/x11-drivers/xf86-input-evdev . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +.Sh BUGS +.Nm +cannot act like +.Xr sysmouse 4 diff --git a/share/man/man4/hpen.4 b/share/man/man4/hpen.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hpen.4 @@ -0,0 +1,105 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HPEN 4 +.Os +.Sh NAME +.Nm hpen +.Nd MS Windows compatible HID pen tablet driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hpen" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hpen_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for generic MS Windows compatible HID pen tablet +and digitizer that attach to the HID transport backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +.Pp +The +.Pa /dev/input/event* +device presents the pen as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hpen.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh SEE ALSO +.Xr iichid 4 , +.Xr usbhid 4 , +.Xr xorg.conf 5 Pq Pa ports/x11/xorg +.Sh BUGS +.Nm +cannot act like +.Xr sysmouse 4 . +.Pp +Pen battery charge level reporting is not supported. +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Greg V Aq Mt greg@unrelenting.technology . +.Pp +This manual page was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/hsctrl.4 b/share/man/man4/hsctrl.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hsctrl.4 @@ -0,0 +1,91 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HSCTRL 4 +.Os +.Sh NAME +.Nm hsctrl +.Nd HID system controls driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hsctrl" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hgame_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for HID system controls most often used as +"Power off/Sleep keys" found on many keyboards. +.Pp +The +.Pa /dev/input/event* +device presents the consumer page controls as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.hsctrl.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh SEE ALSO +.Xr iichid 4 , +.Xr usbhid 4 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/iichid.4 b/share/man/man4/iichid.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/iichid.4 @@ -0,0 +1,96 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 21, 2020 +.Dt IICHID 4 +.Os +.Sh NAME +.Nm iichid +.Nd I2C HID transport driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device iichid" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +iichid_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides a interface to I2C Human Interface Devices (HIDs). +.Sh SYSCTL VARIABLES +Next parameters are available as +.Xr sysctl 8 +variables. +Debug parameter is available as +.Xr loader 8 +tunable as well. +.Bl -tag -width indent +.It Va dev.iichid.*.sampling_rate_fast +Active sampling rate in num/second (for sampling mode). +.It Va dev.iichid.*.sampling_rate_slow +Idle sampling rate in num/second (for sampling mode). +.It Va dev.iichid.*.sampling_hysteresis +Number of missing samples before enabling of slow mode (for sampling mode). +.It Va hw.iichid.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh SEE ALSO +.Xr ig4 4 +.Sh BUGS +The +.Nm +does not support GPIO interrupts yet. +In that case +.Nm +enables sampling mode with periodic polling of hardware by driver means. +See dev.iichid.*.sampling_* +.Xr sysctl 4 +variables for tuning of sampling parameters. +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Marc Priggemeyer Aq Mt marc.priggemeyer@gmail.com +and +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . +.Pp +This manual page was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/ps4dshock.4 b/share/man/man4/ps4dshock.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/ps4dshock.4 @@ -0,0 +1,109 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 19, 2020 +.Dt PS4DSHOCK 4 +.Os +.Sh NAME +.Nm ps4dshock +.Nd Sony PlayStation 4 Dualshock 4 gamepad driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device ps4dshock" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +ps4dshock_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for Sony PlayStation 4 Dualshock 4 gamepad driver. +.Pp +The +.Pa /dev/input/event* +device presents the game controller as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +Next parameters are available as +.Xr sysctl 8 +variables. +Debug parameter is available as +.Xr loader 8 +tunable as well. +.Bl -tag -width indent +.It Va dev.p4dshock.*.led_state +LED state: 0 - off, 1 - on, 2 - blinking. +.It Va dev.p4dshock.*.led_color_r +LED color. +Red component. +.It Va dev.p4dshock.*.led_color_g +LED color. +Green component. +.It Va dev.p4dshock.*.led_color_b +LED color. +Blue component. +.It Va dev.p4dshock.*.led_delay_on +LED blink. +On delay, msecs. +.It Va dev.p4dshock.*.led_delay_off +LED blink. +Off delay, msecs. +.It Va hw.hid.ps4dshock.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh BUGS +The +.Nm +does not support force-feedback events. +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/ucycom.4 b/share/man/man4/ucycom.4 --- a/share/man/man4/ucycom.4 +++ b/share/man/man4/ucycom.4 @@ -39,6 +39,7 @@ kernel configuration file: .Bd -ragged -offset indent .Cd "device usb" +.Cd "device hid" .Cd "device ucom" .Cd "device ucycom" .Ed diff --git a/share/man/man4/ugold.4 b/share/man/man4/ugold.4 --- a/share/man/man4/ugold.4 +++ b/share/man/man4/ugold.4 @@ -28,6 +28,7 @@ your kernel configuration file: .Bd -ragged -offset indent .Cd "device usb" +.Cd "device hid" .Cd "device ugold" .Ed .Pp diff --git a/share/man/man4/uhid.4 b/share/man/man4/uhid.4 --- a/share/man/man4/uhid.4 +++ b/share/man/man4/uhid.4 @@ -41,6 +41,8 @@ kernel configuration file: .Bd -ragged -offset indent .Cd "device uhid" +.Cd "device hid" +.Cd "device usb" .Ed .Pp Alternatively, to load the driver as a diff --git a/share/man/man4/ukbd.4 b/share/man/man4/ukbd.4 --- a/share/man/man4/ukbd.4 +++ b/share/man/man4/ukbd.4 @@ -36,6 +36,8 @@ kernel configuration file: .Bd -ragged -offset indent .Cd "device ukbd" +.Cd "device hid" +.Cd "device usb" .Ed .Pp Alternatively, to load the driver as a diff --git a/share/man/man4/ums.4 b/share/man/man4/ums.4 --- a/share/man/man4/ums.4 +++ b/share/man/man4/ums.4 @@ -36,6 +36,7 @@ kernel configuration file: .Bd -ragged -offset indent .Cd "device ums" +.Cd "device hid" .Cd "device uhci" .Cd "device ohci" .Cd "device usb" diff --git a/share/man/man4/usbhid.4 b/share/man/man4/usbhid.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/usbhid.4 @@ -0,0 +1,79 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 21, 2020 +.Dt USBHID 4 +.Os +.Sh NAME +.Nm usbhid +.Nd USB HID transport driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device usbhid" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +usbhid_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides a interface to USB Human Interface Devices (HIDs). +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.usb.usbhid.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh SEE ALSO +.Xr ehci 4 , +.Xr ohci 4 , +.Xr uhci 4 , +.Xr usb 4 , +.Xr xhci 4 , +.Xr usbconfig 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/wmt.4 b/share/man/man4/wmt.4 --- a/share/man/man4/wmt.4 +++ b/share/man/man4/wmt.4 @@ -36,6 +36,7 @@ .Bd -ragged -offset indent .Cd "device wmt" .Cd "device usb" +.Cd "device hid" .Cd "device evdev" .Ed .Pp diff --git a/share/man/man4/wsp.4 b/share/man/man4/wsp.4 --- a/share/man/man4/wsp.4 +++ b/share/man/man4/wsp.4 @@ -35,6 +35,7 @@ your kernel configuration file: .Bd -ragged -offset indent .Cd "device wsp" +.Cd "device hid" .Cd "device usb" .Ed .Pp diff --git a/share/man/man4/xb360gp.4 b/share/man/man4/xb360gp.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/xb360gp.4 @@ -0,0 +1,91 @@ +.\" 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. +.\" +.\" $FreeBSD$ +.\" +.Dd September 16, 2020 +.Dt XB360GP 4 +.Os +.Sh NAME +.Nm xb360gp +.Nd XBox 360 gamepad driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device xb360gp" +.Cd "device hgame" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +xb360gp_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for XBox 360 gamepad driver. +.Pp +The +.Pa /dev/input/event* +device presents the game controller as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variables are available as both +.Xr sysctl 8 +variables and +.Xr loader 8 +tunables: +.Bl -tag -width indent +.It Va hw.hid.xb360gp.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Greg V Aq Mt greg@unrelenting.technology . +.Pp +This manual page was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/sys/amd64/conf/GENERIC b/sys/amd64/conf/GENERIC --- a/sys/amd64/conf/GENERIC +++ b/sys/amd64/conf/GENERIC @@ -380,3 +380,12 @@ options EVDEV_SUPPORT # evdev support in legacy drivers device evdev # input event device support device uinput # install /dev/uinput cdev + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support +options IICHID_DEBUG # enable debug msgs for I2C transport +options IICHID_SAMPLING # Workaround missing GPIO INTR support +#device usbhid # USB transport support. +#device hidbus # HID bus (required by usbhid/iichid) +#options USBHID_ENABLED # Prefer usbhid to other USB drivers diff --git a/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c b/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c --- a/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c +++ b/sys/arm/broadcom/bcm2835/bcm2835_ft5406.c @@ -246,13 +246,13 @@ evdev_support_event(sc->sc_evdev, EV_SYN); evdev_support_event(sc->sc_evdev, EV_ABS); - evdev_support_abs(sc->sc_evdev, ABS_MT_SLOT, 0, 0, + evdev_support_abs(sc->sc_evdev, ABS_MT_SLOT, 0, MAX_TOUCH_ID, 0, 0, 0); - evdev_support_abs(sc->sc_evdev, ABS_MT_TRACKING_ID, 0, -1, + evdev_support_abs(sc->sc_evdev, ABS_MT_TRACKING_ID, -1, MAX_TOUCH_ID, 0, 0, 0); - evdev_support_abs(sc->sc_evdev, ABS_MT_POSITION_X, 0, 0, + evdev_support_abs(sc->sc_evdev, ABS_MT_POSITION_X, 0, SCREEN_WIDTH, 0, 0, SCREEN_RES_X); - evdev_support_abs(sc->sc_evdev, ABS_MT_POSITION_Y, 0, 0, + evdev_support_abs(sc->sc_evdev, ABS_MT_POSITION_Y, 0, SCREEN_HEIGHT, 0, 0, SCREEN_RES_Y); err = evdev_register_mtx(sc->sc_evdev, &sc->sc_mtx); diff --git a/sys/arm/conf/EFIKA_MX b/sys/arm/conf/EFIKA_MX --- a/sys/arm/conf/EFIKA_MX +++ b/sys/arm/conf/EFIKA_MX @@ -116,6 +116,10 @@ device wlan_tkip # 802.11 TKIP support device wlan_amrr # AMRR transmit rate control algorithm +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data options FDT_DTB_STATIC diff --git a/sys/arm/conf/GENERIC b/sys/arm/conf/GENERIC --- a/sys/arm/conf/GENERIC +++ b/sys/arm/conf/GENERIC @@ -280,6 +280,10 @@ # Thermal sensors device aw_thermal # Allwinner Thermal Sensor Controller +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data makeoptions MODULES_EXTRA+="dtb/allwinner" diff --git a/sys/arm/conf/IMX53 b/sys/arm/conf/IMX53 --- a/sys/arm/conf/IMX53 +++ b/sys/arm/conf/IMX53 @@ -113,6 +113,10 @@ #device mmc # SD/MMC protocol #device mmcsd # SDCard disk device +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data diff --git a/sys/arm/conf/IMX6 b/sys/arm/conf/IMX6 --- a/sys/arm/conf/IMX6 +++ b/sys/arm/conf/IMX6 @@ -106,6 +106,10 @@ #device wlan_tkip # 802.11 TKIP support #device wlan_amrr # AMRR transmit rate control algorithm +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + device vt device kbdmux device ukbd diff --git a/sys/arm/conf/RPI-B b/sys/arm/conf/RPI-B --- a/sys/arm/conf/RPI-B +++ b/sys/arm/conf/RPI-B @@ -89,6 +89,10 @@ device fdt_pinctrl +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data # Note: DTB is normally loaded and modified by RPi boot loader, then diff --git a/sys/arm/conf/TEGRA124 b/sys/arm/conf/TEGRA124 --- a/sys/arm/conf/TEGRA124 +++ b/sys/arm/conf/TEGRA124 @@ -130,6 +130,10 @@ #device sound #device snd_hda +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data device fdt_pinctrl diff --git a/sys/arm/conf/VYBRID b/sys/arm/conf/VYBRID --- a/sys/arm/conf/VYBRID +++ b/sys/arm/conf/VYBRID @@ -103,5 +103,9 @@ device kbdmux device ukbd +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # Flattened Device Tree options FDT # Configure using FDT/DTB data diff --git a/sys/arm/ti/ti_adc.c b/sys/arm/ti/ti_adc.c --- a/sys/arm/ti/ti_adc.c +++ b/sys/arm/ti/ti_adc.c @@ -890,9 +890,9 @@ evdev_support_event(sc->sc_evdev, EV_ABS); evdev_support_event(sc->sc_evdev, EV_KEY); - evdev_support_abs(sc->sc_evdev, ABS_X, 0, 0, + evdev_support_abs(sc->sc_evdev, ABS_X, 0, ADC_MAX_VALUE, 0, 0, 0); - evdev_support_abs(sc->sc_evdev, ABS_Y, 0, 0, + evdev_support_abs(sc->sc_evdev, ABS_Y, 0, ADC_MAX_VALUE, 0, 0, 0); evdev_support_key(sc->sc_evdev, BTN_TOUCH); diff --git a/sys/arm64/conf/GENERIC b/sys/arm64/conf/GENERIC --- a/sys/arm64/conf/GENERIC +++ b/sys/arm64/conf/GENERIC @@ -369,3 +369,7 @@ # DTBs makeoptions MODULES_EXTRA="dtb/allwinner dtb/freescale dtb/imx8 dtb/mv dtb/rockchip dtb/rpi" + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -1817,6 +1817,22 @@ dev/gpio/gpiobus_if.m optional gpio dev/gpio/gpiopps.c optional gpiopps fdt dev/gpio/ofw_gpiobus.c optional fdt gpio +dev/hid/hconf.c optional hconf +dev/hid/hcons.c optional hcons +dev/hid/hgame.c optional hgame +dev/hid/hid.c optional hid +dev/hid/hid_if.m optional hid +dev/hid/hidbus.c optional hidbus +dev/hid/hidmap.c optional hidmap +dev/hid/hidquirk.c optional hid +dev/hid/hidraw.c optional hidraw +dev/hid/hkbd.c optional hkbd +dev/hid/hms.c optional hms +dev/hid/hmt.c optional hmt hconf +dev/hid/hpen.c optional hpen +dev/hid/hsctrl.c optional hsctrl +dev/hid/ps4dshock.c optional ps4dshock +dev/hid/xb360gp.c optional xb360gp dev/hifn/hifn7751.c optional hifn dev/hptiop/hptiop.c optional hptiop scbus dev/hwpmc/hwpmc_logging.c optional hwpmc @@ -1846,6 +1862,7 @@ dev/iicbus/iicbb_if.m optional iicbb dev/iicbus/iicbus.c optional iicbus dev/iicbus/iicbus_if.m optional iicbus +dev/iicbus/iichid.c optional iichid acpi hid iicbus dev/iicbus/iiconf.c optional iicbus dev/iicbus/iicsmb.c optional iicsmb \ dependency "iicbus_if.h" @@ -2322,7 +2339,7 @@ compile-with "${NORMAL_C} -I$S/dev/ixgbe" dev/jedec_dimm/jedec_dimm.c optional jedec_dimm smbus dev/jme/if_jme.c optional jme pci -dev/kbd/kbd.c optional atkbd | pckbd | sc | ukbd | vt +dev/kbd/kbd.c optional atkbd | pckbd | sc | ukbd | vt | hkbd dev/kbdmux/kbdmux.c optional kbdmux dev/ksyms/ksyms.c optional ksyms dev/le/am7990.c optional le @@ -3409,6 +3426,7 @@ dev/usb/input/uhid_snes.c optional uhid_snes dev/usb/input/ukbd.c optional ukbd dev/usb/input/ums.c optional ums +dev/usb/input/usbhid.c optional usbhid dev/usb/input/wmt.c optional wmt dev/usb/input/wsp.c optional wsp # @@ -4065,6 +4083,7 @@ libkern/random.c standard libkern/scanc.c standard libkern/strcasecmp.c standard +libkern/strcasestr.c standard libkern/strcat.c standard libkern/strchr.c standard libkern/strchrnul.c optional gdb diff --git a/sys/conf/options b/sys/conf/options --- a/sys/conf/options +++ b/sys/conf/options @@ -668,6 +668,7 @@ UPLCOM_INTR_INTERVAL opt_uplcom.h UVSCOM_DEFAULT_OPKTSIZE opt_uvscom.h UVSCOM_INTR_INTERVAL opt_uvscom.h +USBHID_ENABLED opt_usb.h # options for the Realtek rtwn driver RTWN_DEBUG opt_rtwn.h @@ -1012,3 +1013,10 @@ # gcov support GCOV opt_global.h LINDEBUGFS + +# options for HID support +HID_DEBUG opt_hid.h +IICHID_DEBUG opt_hid.h +IICHID_SAMPLING opt_hid.h +HKBD_DFLT_KEYMAP opt_hkbd.h +HIDRAW_MAKE_UHID_ALIAS opt_hid.h diff --git a/sys/dev/atkbdc/psm.c b/sys/dev/atkbdc/psm.c --- a/sys/dev/atkbdc/psm.c +++ b/sys/dev/atkbdc/psm.c @@ -1705,7 +1705,7 @@ size_t i; for (i = 0; info[i][0] != ABS_CNT; i++) - evdev_support_abs(evdev, info[i][0], 0, info[i][1], info[i][2], + evdev_support_abs(evdev, info[i][0], info[i][1], info[i][2], 0, 0, info[i][3]); } @@ -1865,7 +1865,7 @@ if (sc->synhw.capAdvancedGestures || sc->synhw.capReportsV) psm_support_abs_bulk(evdev_a, synaptics_absinfo_mt); if (sc->synhw.capPalmDetect) - evdev_support_abs(evdev_a, ABS_TOOL_WIDTH, 0, 0, 15, 0, 0, 0); + evdev_support_abs(evdev_a, ABS_TOOL_WIDTH, 0, 15, 0, 0, 0); evdev_support_key(evdev_a, BTN_LEFT); if (!sc->synhw.capClickPad) { evdev_support_key(evdev_a, BTN_RIGHT); diff --git a/sys/dev/cyapa/cyapa.c b/sys/dev/cyapa/cyapa.c --- a/sys/dev/cyapa/cyapa.c +++ b/sys/dev/cyapa/cyapa.c @@ -594,13 +594,13 @@ evdev_support_prop(sc->evdev, INPUT_PROP_BUTTONPAD); evdev_support_abs(sc->evdev, ABS_MT_SLOT, - 0, 0, CYAPA_MAX_MT - 1, 0, 0, 0); - evdev_support_abs(sc->evdev, ABS_MT_TRACKING_ID, 0, -1, 15, 0, 0, 0); - evdev_support_abs(sc->evdev, ABS_MT_POSITION_X, 0, 0, sc->cap_resx, - 0, 0, sc->cap_phyx != 0 ? sc->cap_resx / sc->cap_phyx : 0); - evdev_support_abs(sc->evdev, ABS_MT_POSITION_Y, 0, 0, sc->cap_resy, - 0, 0, sc->cap_phyy != 0 ? sc->cap_resy / sc->cap_phyy : 0); - evdev_support_abs(sc->evdev, ABS_MT_PRESSURE, 0, 0, 255, 0, 0, 0); + 0, CYAPA_MAX_MT - 1, 0, 0, 0); + evdev_support_abs(sc->evdev, ABS_MT_TRACKING_ID, -1, 15, 0, 0, 0); + evdev_support_abs(sc->evdev, ABS_MT_POSITION_X, 0, sc->cap_resx, 0, 0, + sc->cap_phyx != 0 ? sc->cap_resx / sc->cap_phyx : 0); + evdev_support_abs(sc->evdev, ABS_MT_POSITION_Y, 0, sc->cap_resy, 0, 0, + sc->cap_phyy != 0 ? sc->cap_resy / sc->cap_phyy : 0); + evdev_support_abs(sc->evdev, ABS_MT_PRESSURE, 0, 255, 0, 0, 0); if (evdev_register(sc->evdev) != 0) { mtx_destroy(&sc->mutex); diff --git a/sys/dev/evdev/cdev.c b/sys/dev/evdev/cdev.c --- a/sys/dev/evdev/cdev.c +++ b/sys/dev/evdev/cdev.c @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include @@ -125,23 +126,20 @@ mtx_init(&client->ec_buffer_mtx, "evclient", "evdev", MTX_DEF); knlist_init_mtx(&client->ec_selp.si_note, &client->ec_buffer_mtx); + ret = EVDEV_LIST_LOCK_SIG(evdev); + if (ret != 0) + goto out; /* Avoid race with evdev_unregister */ - EVDEV_LOCK(evdev); if (dev->si_drv1 == NULL) ret = ENODEV; else ret = evdev_register_client(evdev, client); - - if (ret != 0) - evdev_revoke_client(client); - /* - * Unlock evdev here because non-sleepable lock held - * while calling devfs_set_cdevpriv upsets WITNESS - */ - EVDEV_UNLOCK(evdev); - - if (!ret) + EVDEV_LIST_UNLOCK(evdev); +out: + if (ret == 0) ret = devfs_set_cdevpriv(client, evdev_dtor); + else + client->ec_revoked = true; if (ret != 0) { debugf(client, "cannot register evdev client"); @@ -156,11 +154,13 @@ { struct evdev_client *client = (struct evdev_client *)data; - EVDEV_LOCK(client->ec_evdev); + EVDEV_LIST_LOCK(client->ec_evdev); if (!client->ec_revoked) evdev_dispose_client(client->ec_evdev, client); - EVDEV_UNLOCK(client->ec_evdev); + EVDEV_LIST_UNLOCK(client->ec_evdev); + if (client->ec_evdev->ev_lock_type != EV_LOCK_MTX) + epoch_wait_preempt(client->ec_evdev->ev_epoch); knlist_clear(&client->ec_selp.si_note, 0); seldrain(&client->ec_selp); knlist_destroy(&client->ec_selp.si_note); @@ -547,12 +547,12 @@ if (*(int *)data != 0) return (EINVAL); - EVDEV_LOCK(evdev); + EVDEV_LIST_LOCK(evdev); if (dev->si_drv1 != NULL && !client->ec_revoked) { evdev_dispose_client(evdev, client); evdev_revoke_client(client); } - EVDEV_UNLOCK(evdev); + EVDEV_LIST_UNLOCK(evdev); return (0); case EVIOCSCLOCKID: @@ -717,7 +717,7 @@ evdev_revoke_client(struct evdev_client *client) { - EVDEV_LOCK_ASSERT(client->ec_evdev); + EVDEV_LIST_LOCK_ASSERT(client->ec_evdev); client->ec_revoked = true; } diff --git a/sys/dev/evdev/evdev.h b/sys/dev/evdev/evdev.h --- a/sys/dev/evdev/evdev.h +++ b/sys/dev/evdev/evdev.h @@ -30,6 +30,7 @@ #define _DEV_EVDEV_EVDEV_H #include +#include #include #include #include @@ -110,6 +111,7 @@ const struct evdev_methods *); int evdev_register(struct evdev_dev *); int evdev_register_mtx(struct evdev_dev *, struct mtx *); +int evdev_register_epoch(struct evdev_dev *, epoch_t); int evdev_unregister(struct evdev_dev *); int evdev_push_event(struct evdev_dev *, uint16_t, uint16_t, int32_t); void evdev_support_prop(struct evdev_dev *, uint16_t); @@ -117,7 +119,7 @@ void evdev_support_key(struct evdev_dev *, uint16_t); void evdev_support_rel(struct evdev_dev *, uint16_t); void evdev_support_abs(struct evdev_dev *, uint16_t, int32_t, int32_t, int32_t, - int32_t, int32_t, int32_t); + int32_t, int32_t); void evdev_support_msc(struct evdev_dev *, uint16_t); void evdev_support_led(struct evdev_dev *, uint16_t); void evdev_support_snd(struct evdev_dev *, uint16_t); diff --git a/sys/dev/evdev/evdev.c b/sys/dev/evdev/evdev.c --- a/sys/dev/evdev/evdev.c +++ b/sys/dev/evdev/evdev.c @@ -31,12 +31,15 @@ #include #include +#include #include +#include #include #include #include #include #include +#include #include #include @@ -295,12 +298,14 @@ evdev->ev_shortname, evdev->ev_name, evdev->ev_serial); /* Initialize internal structures */ - LIST_INIT(&evdev->ev_clients); + CK_SLIST_INIT(&evdev->ev_clients); + sx_init(&evdev->ev_list_lock, "evsx"); if (evdev_event_supported(evdev, EV_REP) && bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) { /* Initialize callout */ - callout_init_mtx(&evdev->ev_rep_callout, evdev->ev_lock, 0); + callout_init_mtx(&evdev->ev_rep_callout, + evdev->ev_state_lock, 0); if (evdev->ev_rep[REP_DELAY] == 0 && evdev->ev_rep[REP_PERIOD] == 0) { @@ -332,6 +337,8 @@ evdev_sysctl_create(evdev); bail_out: + if (ret != 0) + sx_destroy(&evdev->ev_list_lock); return (ret); } @@ -341,8 +348,9 @@ int ret; evdev->ev_lock_type = EV_LOCK_INTERNAL; - evdev->ev_lock = &evdev->ev_mtx; + evdev->ev_state_lock = &evdev->ev_mtx; mtx_init(&evdev->ev_mtx, "evmtx", NULL, MTX_DEF); + evdev->ev_epoch = global_epoch_preempt; ret = evdev_register_common(evdev); if (ret != 0) @@ -356,10 +364,27 @@ { evdev->ev_lock_type = EV_LOCK_MTX; - evdev->ev_lock = mtx; + evdev->ev_state_lock = mtx; return (evdev_register_common(evdev)); } +int +evdev_register_epoch(struct evdev_dev *evdev, epoch_t epoch) +{ + int ret; + + evdev->ev_lock_type = EV_LOCK_EPOCH; + evdev->ev_state_lock = &evdev->ev_mtx; + mtx_init(&evdev->ev_mtx, "evmtx", NULL, MTX_DEF); + evdev->ev_epoch = epoch; + + ret = evdev_register_common(evdev); + if (ret != 0) + mtx_destroy(&evdev->ev_mtx); + + return (ret); +} + int evdev_unregister(struct evdev_dev *evdev) { @@ -370,22 +395,23 @@ sysctl_ctx_free(&evdev->ev_sysctl_ctx); - EVDEV_LOCK(evdev); + EVDEV_LIST_LOCK(evdev); evdev->ev_cdev->si_drv1 = NULL; /* Wake up sleepers */ - LIST_FOREACH_SAFE(client, &evdev->ev_clients, ec_link, tmp) { + CK_SLIST_FOREACH_SAFE(client, &evdev->ev_clients, ec_link, tmp) { evdev_revoke_client(client); evdev_dispose_client(evdev, client); EVDEV_CLIENT_LOCKQ(client); evdev_notify_event(client); EVDEV_CLIENT_UNLOCKQ(client); } - EVDEV_UNLOCK(evdev); + EVDEV_LIST_UNLOCK(evdev); - /* destroy_dev can sleep so release lock */ + /* release lock to avoid deadlock with evdev_dtor */ ret = evdev_cdev_destroy(evdev); evdev->ev_cdev = NULL; - if (ret == 0 && evdev->ev_lock_type == EV_LOCK_INTERNAL) + sx_destroy(&evdev->ev_list_lock); + if (ret == 0 && evdev->ev_lock_type != EV_LOCK_MTX) mtx_destroy(&evdev->ev_mtx); evdev_free_absinfo(evdev->ev_absinfo); @@ -477,16 +503,15 @@ } inline void -evdev_support_abs(struct evdev_dev *evdev, uint16_t code, int32_t value, - int32_t minimum, int32_t maximum, int32_t fuzz, int32_t flat, - int32_t resolution) +evdev_support_abs(struct evdev_dev *evdev, uint16_t code, int32_t minimum, + int32_t maximum, int32_t fuzz, int32_t flat, int32_t resolution) { struct input_absinfo absinfo; KASSERT(code < ABS_CNT, ("invalid evdev abs property")); absinfo = (struct input_absinfo) { - .value = value, + .value = 0, .minimum = minimum, .maximum = maximum, .fuzz = fuzz, @@ -692,7 +717,7 @@ } else { /* Start/stop callout for evdev repeats */ if (bit_test(evdev->ev_key_states, code) == !*value && - !LIST_EMPTY(&evdev->ev_clients)) { + !CK_SLIST_EMPTY(&evdev->ev_clients)) { if (*value == KEY_EVENT_DOWN) evdev_start_repeat(evdev, code); else { @@ -845,6 +870,7 @@ evdev_propagate_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, int32_t value) { + struct epoch_tracker et; struct evdev_client *client; debugf(evdev, "%s pushed event %d/%d/%d", @@ -853,7 +879,9 @@ EVDEV_LOCK_ASSERT(evdev); /* Propagate event through all clients */ - LIST_FOREACH(client, &evdev->ev_clients, ec_link) { + if (evdev->ev_lock_type != EV_LOCK_MTX) + epoch_enter_preempt(evdev->ev_epoch, &et); + CK_SLIST_FOREACH(client, &evdev->ev_clients, ec_link) { if (evdev->ev_grabber != NULL && evdev->ev_grabber != client) continue; @@ -863,6 +891,8 @@ evdev_notify_event(client); EVDEV_CLIENT_UNLOCKQ(client); } + if (evdev->ev_lock_type != EV_LOCK_MTX) + epoch_exit_preempt(evdev->ev_epoch, &et); evdev->ev_event_count++; } @@ -990,10 +1020,10 @@ case EV_ABS: case EV_SW: push: - if (evdev->ev_lock_type != EV_LOCK_INTERNAL) + if (evdev->ev_lock_type == EV_LOCK_MTX) EVDEV_LOCK(evdev); ret = evdev_push_event(evdev, type, code, value); - if (evdev->ev_lock_type != EV_LOCK_INTERNAL) + if (evdev->ev_lock_type == EV_LOCK_MTX) EVDEV_UNLOCK(evdev); break; @@ -1011,16 +1041,16 @@ debugf(evdev, "adding new client for device %s", evdev->ev_shortname); - EVDEV_LOCK_ASSERT(evdev); + EVDEV_LIST_LOCK_ASSERT(evdev); - if (LIST_EMPTY(&evdev->ev_clients) && evdev->ev_methods != NULL && + if (CK_SLIST_EMPTY(&evdev->ev_clients) && evdev->ev_methods != NULL && evdev->ev_methods->ev_open != NULL) { debugf(evdev, "calling ev_open() on device %s", evdev->ev_shortname); ret = evdev->ev_methods->ev_open(evdev); } if (ret == 0) - LIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link); + CK_SLIST_INSERT_HEAD(&evdev->ev_clients, client, ec_link); return (ret); } @@ -1029,18 +1059,27 @@ { debugf(evdev, "removing client for device %s", evdev->ev_shortname); - EVDEV_LOCK_ASSERT(evdev); + EVDEV_LIST_LOCK_ASSERT(evdev); - LIST_REMOVE(client, ec_link); - if (LIST_EMPTY(&evdev->ev_clients)) { + CK_SLIST_REMOVE(&evdev->ev_clients, client, evdev_client, ec_link); + if (CK_SLIST_EMPTY(&evdev->ev_clients)) { if (evdev->ev_methods != NULL && evdev->ev_methods->ev_close != NULL) (void)evdev->ev_methods->ev_close(evdev); if (evdev_event_supported(evdev, EV_REP) && - bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) + bit_test(evdev->ev_flags, EVDEV_FLAG_SOFTREPEAT)) { + if (evdev->ev_lock_type != EV_LOCK_MTX) + EVDEV_LOCK(evdev); evdev_stop_repeat(evdev); + if (evdev->ev_lock_type != EV_LOCK_MTX) + EVDEV_UNLOCK(evdev); + } } + if (evdev->ev_lock_type != EV_LOCK_MTX) + EVDEV_LOCK(evdev); evdev_release_client(evdev, client); + if (evdev->ev_lock_type != EV_LOCK_MTX) + EVDEV_UNLOCK(evdev); } int diff --git a/sys/dev/evdev/evdev_mt.c b/sys/dev/evdev/evdev_mt.c --- a/sys/dev/evdev/evdev_mt.c +++ b/sys/dev/evdev/evdev_mt.c @@ -185,7 +185,6 @@ for (i = 0; i < nitems(evdev_mtstmap); i++) if (bit_test(evdev->ev_abs_flags, evdev_mtstmap[i][0])) evdev_support_abs(evdev, evdev_mtstmap[i][1], - evdev->ev_absinfo[evdev_mtstmap[i][0]].value, evdev->ev_absinfo[evdev_mtstmap[i][0]].minimum, evdev->ev_absinfo[evdev_mtstmap[i][0]].maximum, evdev->ev_absinfo[evdev_mtstmap[i][0]].fuzz, diff --git a/sys/dev/evdev/evdev_private.h b/sys/dev/evdev/evdev_private.h --- a/sys/dev/evdev/evdev_private.h +++ b/sys/dev/evdev/evdev_private.h @@ -31,12 +31,15 @@ #define _DEV_EVDEV_EVDEV_PRIVATE_H #include +#include +#include #include #include #include #include #include #include +#include #include #include @@ -77,8 +80,9 @@ enum evdev_lock_type { - EV_LOCK_INTERNAL = 0, /* Internal evdev mutex */ + EV_LOCK_INTERNAL = 0, /* Internal epoch */ EV_LOCK_MTX, /* Driver`s mutex */ + EV_LOCK_EPOCH, /* External epoch */ }; struct evdev_dev @@ -89,8 +93,10 @@ struct cdev * ev_cdev; int ev_unit; enum evdev_lock_type ev_lock_type; - struct mtx * ev_lock; - struct mtx ev_mtx; + struct mtx * ev_state_lock; /* Evdev state lock */ + struct mtx ev_mtx; /* Evdev internal state lock */ + epoch_t ev_epoch; + struct sx ev_list_lock; /* Evdev client list lock */ struct input_id ev_id; struct evdev_client * ev_grabber; size_t ev_report_size; @@ -139,28 +145,56 @@ struct sysctl_ctx_list ev_sysctl_ctx; LIST_ENTRY(evdev_dev) ev_link; - LIST_HEAD(, evdev_client) ev_clients; + CK_SLIST_HEAD(, evdev_client) ev_clients; }; #define SYSTEM_CONSOLE_LOCK &Giant -#define EVDEV_LOCK(evdev) mtx_lock((evdev)->ev_lock) -#define EVDEV_UNLOCK(evdev) mtx_unlock((evdev)->ev_lock) +#define EVDEV_LOCK(evdev) mtx_lock((evdev)->ev_state_lock) +#define EVDEV_UNLOCK(evdev) mtx_unlock((evdev)->ev_state_lock) #define EVDEV_LOCK_ASSERT(evdev) do { \ - if ((evdev)->ev_lock != SYSTEM_CONSOLE_LOCK) \ - mtx_assert((evdev)->ev_lock, MA_OWNED); \ + if ((evdev)->ev_state_lock != SYSTEM_CONSOLE_LOCK) \ + mtx_assert((evdev)->ev_state_lock, MA_OWNED); \ } while (0) #define EVDEV_ENTER(evdev) do { \ - if ((evdev)->ev_lock_type == EV_LOCK_INTERNAL) \ + if ((evdev)->ev_lock_type != EV_LOCK_MTX) \ EVDEV_LOCK(evdev); \ else \ EVDEV_LOCK_ASSERT(evdev); \ } while (0) #define EVDEV_EXIT(evdev) do { \ - if ((evdev)->ev_lock_type == EV_LOCK_INTERNAL) \ + if ((evdev)->ev_lock_type != EV_LOCK_MTX) \ EVDEV_UNLOCK(evdev); \ } while (0) +#define EVDEV_LIST_LOCK(evdev) do { \ + if ((evdev)->ev_lock_type == EV_LOCK_MTX) \ + EVDEV_LOCK(evdev); \ + else \ + sx_xlock(&(evdev)->ev_list_lock); \ +} while (0) +#define EVDEV_LIST_UNLOCK(evdev) do { \ + if ((evdev)->ev_lock_type == EV_LOCK_MTX) \ + EVDEV_UNLOCK(evdev); \ + else \ + sx_unlock(&(evdev)->ev_list_lock); \ +} while (0) +#define EVDEV_LIST_LOCK_ASSERT(evdev) do { \ + if ((evdev)->ev_lock_type == EV_LOCK_MTX) \ + EVDEV_LOCK_ASSERT(evdev); \ + else \ + sx_assert(&(evdev)->ev_list_lock, MA_OWNED); \ +} while (0) +static inline int +EVDEV_LIST_LOCK_SIG(struct evdev_dev *evdev) +{ + if (evdev->ev_lock_type == EV_LOCK_MTX) { + EVDEV_LOCK(evdev); + return (0); + } + return (sx_xlock_sig(&evdev->ev_list_lock)); +} + struct evdev_client { struct evdev_dev * ec_evdev; @@ -177,7 +211,7 @@ bool ec_blocked; bool ec_selected; - LIST_ENTRY(evdev_client) ec_link; + CK_SLIST_ENTRY(evdev_client) ec_link; struct input_event ec_buffer[]; }; diff --git a/sys/dev/evdev/uinput.c b/sys/dev/evdev/uinput.c --- a/sys/dev/evdev/uinput.c +++ b/sys/dev/evdev/uinput.c @@ -525,10 +525,9 @@ if (uabs->code > ABS_MAX) return (EINVAL); - evdev_support_abs(state->ucs_evdev, uabs->code, - uabs->absinfo.value, uabs->absinfo.minimum, - uabs->absinfo.maximum, uabs->absinfo.fuzz, - uabs->absinfo.flat, uabs->absinfo.resolution); + evdev_set_abs_bit(state->ucs_evdev, uabs->code); + evdev_set_absinfo(state->ucs_evdev, uabs->code, + &uabs->absinfo); return (0); case UI_SET_EVBIT: diff --git a/sys/dev/hid/hconf.h b/sys/dev/hid/hconf.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hconf.h @@ -0,0 +1,41 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _HCONF_H_ +#define _HCONF_H_ + +enum hconf_input_mode { + HCONF_INPUT_MODE_MOUSE = 0x0, + HCONF_INPUT_MODE_MT_TOUCHSCREEN = 0x2, + HCONF_INPUT_MODE_MT_TOUCHPAD = 0x3, +}; + +int hconf_set_input_mode(device_t, enum hconf_input_mode); + +#endif /* _HCONF_H_ */ diff --git a/sys/dev/hid/hconf.c b/sys/dev/hid/hconf.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hconf.c @@ -0,0 +1,331 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Digitizer configuration top-level collection support. + * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/windows-precision-touchpad-required-hid-top-level-collections + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HID_DEBUG_VAR hconf_debug +#include +#include + +#include + +#ifdef HID_DEBUG +static int hconf_debug = 0; + +static SYSCTL_NODE(_hw_hid, OID_AUTO, hconf, CTLFLAG_RW, 0, + "Digitizer configuration top-level collection"); +SYSCTL_INT(_hw_hid_hconf, OID_AUTO, debug, CTLFLAG_RWTUN, + &hconf_debug, 1, "Debug level"); +#endif + +enum feature_control_type { + INPUT_MODE = 0, + SURFACE_SWITCH, + BUTTONS_SWITCH, + CONTROLS_COUNT +}; + +struct feature_control_descr { + const char *name; + const char *descr; + uint16_t usage; +} feature_control_descrs[] = { + [INPUT_MODE] = { + .name = "input_mode", + .descr = "HID device input mode: 0 = mouse, 3 = touchpad", + .usage = HUD_INPUT_MODE + }, + [SURFACE_SWITCH] = { + .name = "surface_switch", + .descr = "Enable / disable switch for surface: 1 = on, 0 = off", + .usage = HUD_SURFACE_SWITCH + }, + [BUTTONS_SWITCH] = { + .name = "buttons_switch", + .descr = "Enable / disable switch for buttons: 1 = on, 0 = off", + .usage = HUD_BUTTONS_SWITCH + }, +}; + +struct feature_control { + u_int val; + struct hid_location loc; + hid_size_t rlen; + uint8_t rid; +}; + +struct hconf_softc { + device_t dev; + struct sx lock; + + struct feature_control feature_controls[CONTROLS_COUNT]; +}; + +static device_probe_t hconf_probe; +static device_attach_t hconf_attach; +static device_detach_t hconf_detach; +static device_resume_t hconf_resume; + +static devclass_t hconf_devclass; + +static device_method_t hconf_methods[] = { + + DEVMETHOD(device_probe, hconf_probe), + DEVMETHOD(device_attach, hconf_attach), + DEVMETHOD(device_detach, hconf_detach), + DEVMETHOD(device_resume, hconf_resume), + + DEVMETHOD_END +}; + +static driver_t hconf_driver = { + .name = "hconf", + .methods = hconf_methods, + .size = sizeof(struct hconf_softc), +}; + +static const struct hid_device_id hconf_devs[] = { + { HID_TLC(HUP_DIGITIZERS, HUD_CONFIG) }, +}; + +static int +hconf_set_feature_control(struct hconf_softc *sc, int ctrl_id, u_int val) +{ + struct feature_control *fc; + uint8_t *fbuf; + int error; + int i; + + KASSERT(ctrl_id >= 0 && ctrl_id < CONTROLS_COUNT, + ("impossible ctrl id %d", ctrl_id)); + fc = &sc->feature_controls[ctrl_id]; + if (fc->rlen <= 1) + return (ENXIO); + + fbuf = malloc(fc->rlen, M_TEMP, M_WAITOK | M_ZERO); + sx_xlock(&sc->lock); + + /* Reports are not strictly required to be readable */ + error = hid_get_report(sc->dev, fbuf, fc->rlen, NULL, + HID_FEATURE_REPORT, fc->rid); + + /* + * If the report is write-only, then we have to check for other controls + * that may share the same report and set their bits as well. + */ + if (error != 0) { + bzero(fbuf + 1, fc->rlen - 1); + for (i = 0; i < nitems(sc->feature_controls); i++) { + struct feature_control *ofc = &sc->feature_controls[i]; + + /* Skip unrelated report IDs. */ + if (ofc->rid != fc->rid) + continue; + /* Skip self. */ + if (ofc == fc) + continue; + KASSERT(fc->rlen == ofc->rlen, + ("different lengths for report %d: %d vs %d\n", + fc->rid, fc->rlen, ofc->rlen)); + hid_put_data_unsigned(fbuf + 1, ofc->rlen - 1, + &ofc->loc, ofc->val); + } + } + + fbuf[0] = fc->rid; + hid_put_data_unsigned(fbuf + 1, fc->rlen - 1, &fc->loc, val); + + error = hid_set_report(sc->dev, fbuf, fc->rlen, + HID_FEATURE_REPORT, fc->rid); + if (error == 0) + fc->val = val; + + sx_unlock(&sc->lock); + free(fbuf, M_TEMP); + + return (error); +} + +static int +hconf_feature_control_handler(SYSCTL_HANDLER_ARGS) +{ + struct feature_control *fc; + struct hconf_softc *sc = arg1; + int ctrl_id = arg2; + u_int value; + int error; + + if (ctrl_id < 0 || ctrl_id >= CONTROLS_COUNT) + return (ENXIO); + + fc = &sc->feature_controls[ctrl_id]; + value = fc->val; + error = sysctl_handle_int(oidp, &value, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + error = hconf_set_feature_control(sc, ctrl_id, value); + if (error != 0) { + DPRINTF("Failed to set %s: %d\n", + feature_control_descrs[ctrl_id].name, error); + } + return (0); +} + + +static int +hconf_parse_feature(struct feature_control *fc, uint8_t tlc_index, + uint16_t usage, void *d_ptr, hid_size_t d_len) +{ + uint32_t flags; + + if (!hidbus_locate(d_ptr, d_len, HID_USAGE2(HUP_DIGITIZERS, usage), + hid_feature, tlc_index, 0, &fc->loc, &flags, &fc->rid, NULL)) + return (ENOENT); + + if ((flags & (HIO_VARIABLE | HIO_RELATIVE)) != HIO_VARIABLE) + return (EINVAL); + + fc->rlen = hid_report_size(d_ptr, d_len, hid_feature, fc->rid); + return (0); +} + +static int +hconf_probe(device_t dev) +{ + int error; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hconf_devs); + if (error != 0) + return (error); + + hidbus_set_desc(dev, "Configuration"); + + return (BUS_PROBE_DEFAULT); +} + +static int +hconf_attach(device_t dev) +{ + struct hconf_softc *sc = device_get_softc(dev); + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); + struct sysctl_oid *tree = device_get_sysctl_tree(dev); + void *d_ptr; + hid_size_t d_len; + uint8_t tlc_index; + int error; + int i; + + error = hid_get_report_descr(dev, &d_ptr, &d_len); + if (error) { + device_printf(dev, "could not retrieve report descriptor from " + "device: %d\n", error); + return (ENXIO); + } + + sc->dev = dev; + sx_init(&sc->lock, device_get_nameunit(dev)); + + tlc_index = hidbus_get_index(dev); + for (i = 0; i < nitems(sc->feature_controls); i++) { + (void)hconf_parse_feature(&sc->feature_controls[i], tlc_index, + feature_control_descrs[i].usage, d_ptr, d_len); + if (sc->feature_controls[i].rlen > 1) { + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + feature_control_descrs[i].name, + CTLTYPE_UINT | CTLFLAG_RW, + sc, i, hconf_feature_control_handler, "I", + feature_control_descrs[i].descr); + } + } + + /* Fully enable (at least, try to). */ + (void)hconf_set_feature_control(sc, SURFACE_SWITCH, 1); + (void)hconf_set_feature_control(sc, BUTTONS_SWITCH, 1); + return (0); +} + +static int +hconf_detach(device_t dev) +{ + struct hconf_softc *sc = device_get_softc(dev); + + sx_destroy(&sc->lock); + + return (0); +} + +static int +hconf_resume(device_t dev) +{ + struct hconf_softc *sc = device_get_softc(dev); + int error; + int i; + + for (i = 0; i < nitems(sc->feature_controls); i++) { + if (sc->feature_controls[i].rlen < 2) + continue; + error = hconf_set_feature_control(sc, i, + sc->feature_controls[i].val); + if (error != 0) { + DPRINTF("Failed to restore %s: %d\n", + feature_control_descrs[i].name, error); + } + } + + return (0); +} + +int +hconf_set_input_mode(device_t dev, enum hconf_input_mode mode) +{ + struct hconf_softc *sc = device_get_softc(dev); + + return (hconf_set_feature_control(sc, INPUT_MODE, mode)); +} + +DRIVER_MODULE(hconf, hidbus, hconf_driver, hconf_devclass, NULL, 0); +MODULE_DEPEND(hconf, hidbus, 1, 1, 1); +MODULE_DEPEND(hconf, hid, 1, 1, 1); +MODULE_VERSION(hconf, 1); +HID_PNP_INFO(hconf_devs); diff --git a/sys/dev/hid/hcons.c b/sys/dev/hid/hcons.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hcons.c @@ -0,0 +1,295 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Consumer Controls usage page driver + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static hidmap_cb_t hcons_rel_volume_cb; + +#define HCONS_MAP_KEY(usage, code) \ + { HIDMAP_KEY(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_REL(usage, code) \ + { HIDMAP_REL(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_REL_CB(usage, callback) \ + { HIDMAP_REL_CB(HUP_CONSUMER, usage, &callback) } + +static const struct hidmap_item hcons_map[] = { + HCONS_MAP_KEY(0x030, KEY_POWER), + HCONS_MAP_KEY(0x031, KEY_RESTART), + HCONS_MAP_KEY(0x032, KEY_SLEEP), + HCONS_MAP_KEY(0x034, KEY_SLEEP), + HCONS_MAP_KEY(0x035, KEY_KBDILLUMTOGGLE), + HCONS_MAP_KEY(0x036, BTN_MISC), + HCONS_MAP_KEY(0x040, KEY_MENU), /* Menu */ + HCONS_MAP_KEY(0x041, KEY_SELECT), /* Menu Pick */ + HCONS_MAP_KEY(0x042, KEY_UP), /* Menu Up */ + HCONS_MAP_KEY(0x043, KEY_DOWN), /* Menu Down */ + HCONS_MAP_KEY(0x044, KEY_LEFT), /* Menu Left */ + HCONS_MAP_KEY(0x045, KEY_RIGHT), /* Menu Right */ + HCONS_MAP_KEY(0x046, KEY_ESC), /* Menu Escape */ + HCONS_MAP_KEY(0x047, KEY_KPPLUS), /* Menu Value Increase */ + HCONS_MAP_KEY(0x048, KEY_KPMINUS), /* Menu Value Decrease */ + HCONS_MAP_KEY(0x060, KEY_INFO), /* Data On Screen */ + HCONS_MAP_KEY(0x061, KEY_SUBTITLE), /* Closed Caption */ + HCONS_MAP_KEY(0x063, KEY_VCR), /* VCR/TV */ + HCONS_MAP_KEY(0x065, KEY_CAMERA), /* Snapshot */ + HCONS_MAP_KEY(0x069, KEY_RED), + HCONS_MAP_KEY(0x06a, KEY_GREEN), + HCONS_MAP_KEY(0x06b, KEY_BLUE), + HCONS_MAP_KEY(0x06c, KEY_YELLOW), + HCONS_MAP_KEY(0x06d, KEY_ASPECT_RATIO), + HCONS_MAP_KEY(0x06f, KEY_BRIGHTNESSUP), + HCONS_MAP_KEY(0x070, KEY_BRIGHTNESSDOWN), + HCONS_MAP_KEY(0x072, KEY_BRIGHTNESS_TOGGLE), + HCONS_MAP_KEY(0x073, KEY_BRIGHTNESS_MIN), + HCONS_MAP_KEY(0x074, KEY_BRIGHTNESS_MAX), + HCONS_MAP_KEY(0x075, KEY_BRIGHTNESS_AUTO), + HCONS_MAP_KEY(0x079, KEY_KBDILLUMUP), + HCONS_MAP_KEY(0x07a, KEY_KBDILLUMDOWN), + HCONS_MAP_KEY(0x07c, KEY_KBDILLUMTOGGLE), + HCONS_MAP_KEY(0x082, KEY_VIDEO_NEXT), + HCONS_MAP_KEY(0x083, KEY_LAST), + HCONS_MAP_KEY(0x084, KEY_ENTER), + HCONS_MAP_KEY(0x088, KEY_PC), + HCONS_MAP_KEY(0x089, KEY_TV), + HCONS_MAP_KEY(0x08a, KEY_WWW), + HCONS_MAP_KEY(0x08b, KEY_DVD), + HCONS_MAP_KEY(0x08c, KEY_PHONE), + HCONS_MAP_KEY(0x08d, KEY_PROGRAM), + HCONS_MAP_KEY(0x08e, KEY_VIDEOPHONE), + HCONS_MAP_KEY(0x08f, KEY_GAMES), + HCONS_MAP_KEY(0x090, KEY_MEMO), + HCONS_MAP_KEY(0x091, KEY_CD), + HCONS_MAP_KEY(0x092, KEY_VCR), + HCONS_MAP_KEY(0x093, KEY_TUNER), + HCONS_MAP_KEY(0x094, KEY_EXIT), + HCONS_MAP_KEY(0x095, KEY_HELP), + HCONS_MAP_KEY(0x096, KEY_TAPE), + HCONS_MAP_KEY(0x097, KEY_TV2), + HCONS_MAP_KEY(0x098, KEY_SAT), + HCONS_MAP_KEY(0x09a, KEY_PVR), + HCONS_MAP_KEY(0x09c, KEY_CHANNELUP), + HCONS_MAP_KEY(0x09d, KEY_CHANNELDOWN), + HCONS_MAP_KEY(0x0a0, KEY_VCR2), + HCONS_MAP_KEY(0x0b0, KEY_PLAY), + HCONS_MAP_KEY(0x0b1, KEY_PAUSE), + HCONS_MAP_KEY(0x0b2, KEY_RECORD), + HCONS_MAP_KEY(0x0b3, KEY_FASTFORWARD), + HCONS_MAP_KEY(0x0b4, KEY_REWIND), + HCONS_MAP_KEY(0x0b5, KEY_NEXTSONG), + HCONS_MAP_KEY(0x0b6, KEY_PREVIOUSSONG), + HCONS_MAP_KEY(0x0b7, KEY_STOPCD), + HCONS_MAP_KEY(0x0b8, KEY_EJECTCD), + HCONS_MAP_KEY(0x0bc, KEY_MEDIA_REPEAT), + HCONS_MAP_KEY(0x0b9, KEY_SHUFFLE), + HCONS_MAP_KEY(0x0bf, KEY_SLOW), + HCONS_MAP_KEY(0x0cd, KEY_PLAYPAUSE), + HCONS_MAP_KEY(0x0cf, KEY_VOICECOMMAND), + HCONS_MAP_ABS(0x0e0, ABS_VOLUME), + HCONS_MAP_REL_CB(0x0e0, hcons_rel_volume_cb), + HCONS_MAP_KEY(0x0e2, KEY_MUTE), + HCONS_MAP_KEY(0x0e5, KEY_BASSBOOST), + HCONS_MAP_KEY(0x0e9, KEY_VOLUMEUP), + HCONS_MAP_KEY(0x0ea, KEY_VOLUMEDOWN), + HCONS_MAP_KEY(0x0f5, KEY_SLOW), + HCONS_MAP_KEY(0x181, KEY_BUTTONCONFIG), + HCONS_MAP_KEY(0x182, KEY_BOOKMARKS), + HCONS_MAP_KEY(0x183, KEY_CONFIG), + HCONS_MAP_KEY(0x184, KEY_WORDPROCESSOR), + HCONS_MAP_KEY(0x185, KEY_EDITOR), + HCONS_MAP_KEY(0x186, KEY_SPREADSHEET), + HCONS_MAP_KEY(0x187, KEY_GRAPHICSEDITOR), + HCONS_MAP_KEY(0x188, KEY_PRESENTATION), + HCONS_MAP_KEY(0x189, KEY_DATABASE), + HCONS_MAP_KEY(0x18a, KEY_MAIL), + HCONS_MAP_KEY(0x18b, KEY_NEWS), + HCONS_MAP_KEY(0x18c, KEY_VOICEMAIL), + HCONS_MAP_KEY(0x18d, KEY_ADDRESSBOOK), + HCONS_MAP_KEY(0x18e, KEY_CALENDAR), + HCONS_MAP_KEY(0x18f, KEY_TASKMANAGER), + HCONS_MAP_KEY(0x190, KEY_JOURNAL), + HCONS_MAP_KEY(0x191, KEY_FINANCE), + HCONS_MAP_KEY(0x192, KEY_CALC), + HCONS_MAP_KEY(0x193, KEY_PLAYER), + HCONS_MAP_KEY(0x194, KEY_FILE), + HCONS_MAP_KEY(0x196, KEY_WWW), + HCONS_MAP_KEY(0x199, KEY_CHAT), + HCONS_MAP_KEY(0x19c, KEY_LOGOFF), + HCONS_MAP_KEY(0x19e, KEY_COFFEE), + HCONS_MAP_KEY(0x19f, KEY_CONTROLPANEL), + HCONS_MAP_KEY(0x1a2, KEY_APPSELECT), + HCONS_MAP_KEY(0x1a3, KEY_NEXT), + HCONS_MAP_KEY(0x1a4, KEY_PREVIOUS), + HCONS_MAP_KEY(0x1a6, KEY_HELP), + HCONS_MAP_KEY(0x1a7, KEY_DOCUMENTS), + HCONS_MAP_KEY(0x1ab, KEY_SPELLCHECK), + HCONS_MAP_KEY(0x1ae, KEY_KEYBOARD), + HCONS_MAP_KEY(0x1b1, KEY_SCREENSAVER), + HCONS_MAP_KEY(0x1b4, KEY_FILE), + HCONS_MAP_KEY(0x1b6, KEY_IMAGES), + HCONS_MAP_KEY(0x1b7, KEY_AUDIO), + HCONS_MAP_KEY(0x1b8, KEY_VIDEO), + HCONS_MAP_KEY(0x1bc, KEY_MESSENGER), + HCONS_MAP_KEY(0x1bd, KEY_INFO), + HCONS_MAP_KEY(0x1cb, KEY_ASSISTANT), + HCONS_MAP_KEY(0x201, KEY_NEW), + HCONS_MAP_KEY(0x202, KEY_OPEN), + HCONS_MAP_KEY(0x203, KEY_CLOSE), + HCONS_MAP_KEY(0x204, KEY_EXIT), + HCONS_MAP_KEY(0x207, KEY_SAVE), + HCONS_MAP_KEY(0x208, KEY_PRINT), + HCONS_MAP_KEY(0x209, KEY_PROPS), + HCONS_MAP_KEY(0x21a, KEY_UNDO), + HCONS_MAP_KEY(0x21b, KEY_COPY), + HCONS_MAP_KEY(0x21c, KEY_CUT), + HCONS_MAP_KEY(0x21d, KEY_PASTE), + HCONS_MAP_KEY(0x21f, KEY_FIND), + HCONS_MAP_KEY(0x221, KEY_SEARCH), + HCONS_MAP_KEY(0x222, KEY_GOTO), + HCONS_MAP_KEY(0x223, KEY_HOMEPAGE), + HCONS_MAP_KEY(0x224, KEY_BACK), + HCONS_MAP_KEY(0x225, KEY_FORWARD), + HCONS_MAP_KEY(0x226, KEY_STOP), + HCONS_MAP_KEY(0x227, KEY_REFRESH), + HCONS_MAP_KEY(0x22a, KEY_BOOKMARKS), + HCONS_MAP_KEY(0x22d, KEY_ZOOMIN), + HCONS_MAP_KEY(0x22e, KEY_ZOOMOUT), + HCONS_MAP_KEY(0x22f, KEY_ZOOMRESET), + HCONS_MAP_KEY(0x232, KEY_FULL_SCREEN), + HCONS_MAP_KEY(0x233, KEY_SCROLLUP), + HCONS_MAP_KEY(0x234, KEY_SCROLLDOWN), + HCONS_MAP_REL(0x238, REL_HWHEEL), /* AC Pan */ + HCONS_MAP_KEY(0x23d, KEY_EDIT), + HCONS_MAP_KEY(0x25f, KEY_CANCEL), + HCONS_MAP_KEY(0x269, KEY_INSERT), + HCONS_MAP_KEY(0x26a, KEY_DELETE), + HCONS_MAP_KEY(0x279, KEY_REDO), + HCONS_MAP_KEY(0x289, KEY_REPLY), + HCONS_MAP_KEY(0x28b, KEY_FORWARDMAIL), + HCONS_MAP_KEY(0x28c, KEY_SEND), + HCONS_MAP_KEY(0x29d, KEY_KBD_LAYOUT_NEXT), + HCONS_MAP_KEY(0x2c7, KEY_KBDINPUTASSIST_PREV), + HCONS_MAP_KEY(0x2c8, KEY_KBDINPUTASSIST_NEXT), + HCONS_MAP_KEY(0x2c9, KEY_KBDINPUTASSIST_PREVGROUP), + HCONS_MAP_KEY(0x2ca, KEY_KBDINPUTASSIST_NEXTGROUP), + HCONS_MAP_KEY(0x2cb, KEY_KBDINPUTASSIST_ACCEPT), + HCONS_MAP_KEY(0x2cc, KEY_KBDINPUTASSIST_CANCEL), + HCONS_MAP_KEY(0x29f, KEY_SCALE), +}; + +static const struct hid_device_id hcons_devs[] = { + { HID_TLC(HUP_CONSUMER, HUC_CONSUMER_CONTROL) }, +}; + +/* + * Emulate relative Consumer volume usage with pressing + * VOLUMEUP and VOLUMEDOWN keys appropriate number of times + */ +static int +hcons_rel_volume_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t code; + int nrepeats; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_KEY); + evdev_support_key(evdev, KEY_VOLUMEUP); + evdev_support_key(evdev, KEY_VOLUMEDOWN); + break; + case HIDMAP_CB_IS_RUNNING: + /* Nothing to report. */ + if (ctx.data == 0) + return (ENOMSG); + code = ctx.data > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + for (nrepeats = abs(ctx.data); nrepeats > 0; nrepeats--) { + evdev_push_key(evdev, code, 1); + evdev_push_key(evdev, code, 0); + } + } + + return (0); +} + +static int +hcons_probe(device_t dev) +{ + return (HIDMAP_PROBE(device_get_softc(dev), dev, + hcons_devs, hcons_map, "Consumer Control")); +} + +static int +hcons_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +hcons_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static devclass_t hcons_devclass; +static device_method_t hcons_methods[] = { + DEVMETHOD(device_probe, hcons_probe), + DEVMETHOD(device_attach, hcons_attach), + DEVMETHOD(device_detach, hcons_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hcons, hcons_driver, hcons_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hcons, hidbus, hcons_driver, hcons_devclass, NULL, 0); +MODULE_DEPEND(hcons, hid, 1, 1, 1); +MODULE_DEPEND(hcons, hidbus, 1, 1, 1); +MODULE_DEPEND(hcons, hidmap, 1, 1, 1); +MODULE_DEPEND(hcons, evdev, 1, 1, 1); +MODULE_VERSION(hcons, 1); +HID_PNP_INFO(hcons_devs); diff --git a/sys/dev/hid/hgame.h b/sys/dev/hid/hgame.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hgame.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * 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_HGAME_H_ +#define _HID_HGAME_H_ + +#include + +hidmap_cb_t hgame_dpad_cb; +hidmap_cb_t hgame_final_cb; + +struct hgame_softc { + struct hidmap hm; + bool dpad_up; + bool dpad_down; + bool dpad_right; + bool dpad_left; +}; + +#endif /* !_HGAME_H_ */ diff --git a/sys/dev/hid/hgame.c b/sys/dev/hid/hgame.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hgame.c @@ -0,0 +1,200 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * 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$"); + +/* + * Generic HID game controller (joystick/gamepad) driver, + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define HGAME_MAP_BRG(number_from, number_to, code) \ + { HIDMAP_KEY_RANGE(HUP_BUTTON, number_from, number_to, code) } +#define HGAME_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } +#define HGAME_MAP_CRG(usage_from, usage_to, callback) \ + { HIDMAP_ANY_CB_RANGE(HUP_GENERIC_DESKTOP, \ + HUG_##usage_from, HUG_##usage_to, callback) } +#define HGAME_FINALCB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +static const struct hidmap_item hgame_map[] = { + HGAME_MAP_BRG(1, 16, BTN_TRIGGER), + HGAME_MAP_ABS(X, ABS_X), + HGAME_MAP_ABS(Y, ABS_Y), + HGAME_MAP_ABS(Z, ABS_Z), + HGAME_MAP_ABS(RX, ABS_RX), + HGAME_MAP_ABS(RY, ABS_RY), + HGAME_MAP_ABS(RZ, ABS_RZ), + HGAME_MAP_ABS(HAT_SWITCH, ABS_HAT0X), + HGAME_MAP_CRG(D_PAD_UP, D_PAD_LEFT, hgame_dpad_cb), + HGAME_MAP_BRG(17, 57, BTN_TRIGGER_HAPPY), + HGAME_FINALCB( hgame_final_cb), +}; + +static const struct hid_device_id hgame_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_JOYSTICK), + HID_DRIVER_INFO(HUG_JOYSTICK) }, + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD), + HID_DRIVER_INFO(HUG_GAME_PAD) }, +}; + +/* + * Emulate the hat switch report via the D-pad usages + * found on XInput/XBox style devices + */ +int +hgame_dpad_cb(HIDMAP_CB_ARGS) +{ + struct hgame_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t data; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + HIDMAP_CB_UDATA64 = HID_GET_USAGE(ctx.hi->usage); + evdev_support_event(evdev, EV_ABS); + evdev_support_abs(evdev, ABS_HAT0X, -1, 1, 0, 0, 0); + evdev_support_abs(evdev, ABS_HAT0Y, -1, 1, 0, 0, 0); + break; + + case HIDMAP_CB_IS_RUNNING: + data = ctx.data; + switch (HIDMAP_CB_UDATA64) { + case HUG_D_PAD_UP: + if (sc->dpad_down) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : -1); + sc->dpad_up = (data != 0); + break; + case HUG_D_PAD_DOWN: + if (sc->dpad_up) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : 1); + sc->dpad_down = (data != 0); + break; + case HUG_D_PAD_RIGHT: + if (sc->dpad_left) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : 1); + sc->dpad_right = (data != 0); + break; + case HUG_D_PAD_LEFT: + if (sc->dpad_right) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : -1); + sc->dpad_left = (data != 0); + break; + } + } + + return (0); +} + +int +hgame_final_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +hgame_probe(device_t dev) +{ + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hgame_softc *sc = device_get_softc(dev); + int error; + + if (hid_test_quirk(hw, HQ_IS_XBOX360GP)) + return(ENXIO); + + error = HIDMAP_PROBE(&sc->hm, dev, hgame_devs, hgame_map, NULL); + if (error > 0) + return (error); + + hidbus_set_desc(dev, hidbus_get_driver_info(dev) == HUG_GAME_PAD ? + "Gamepad" : "Joystick"); + + return (BUS_PROBE_GENERIC); +} + + + +static int +hgame_attach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_attach(&sc->hm)); +} + +static int +hgame_detach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t hgame_devclass; +static device_method_t hgame_methods[] = { + DEVMETHOD(device_probe, hgame_probe), + DEVMETHOD(device_attach, hgame_attach), + DEVMETHOD(device_detach, hgame_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hgame, hgame_driver, hgame_methods, sizeof(struct hgame_softc)); +DRIVER_MODULE(hgame, hidbus, hgame_driver, hgame_devclass, NULL, 0); +MODULE_DEPEND(hgame, hid, 1, 1, 1); +MODULE_DEPEND(hgame, hidbus, 1, 1, 1); +MODULE_DEPEND(hgame, hidmap, 1, 1, 1); +MODULE_DEPEND(hgame, evdev, 1, 1, 1); +MODULE_VERSION(hgame, 1); +HID_PNP_INFO(hgame_devs); diff --git a/sys/dev/hid/hid.h b/sys/dev/hid/hid.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hid.h @@ -0,0 +1,347 @@ +/* $FreeBSD$ */ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * 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_CONSUMER_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_DEGREE 0x14 + +#if defined(_KERNEL) || defined(_STANDALONE) + +#define HID_ITEM_MAXUSAGE 4 +#define HID_MAX_AUTO_QUIRK 8 /* maximum number of dynamic quirks */ +#define HID_PNP_ID_SIZE 20 /* includes null terminator */ + +/* Share unit number pool between uhid and hidraw */ +extern devclass_t hidraw_devclass; + +/* 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_data_unsigned(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_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); +#endif /* _KERNEL || _STANDALONE */ +#endif /* _HID_HID_H_ */ diff --git a/sys/dev/hid/hid.c b/sys/dev/hid/hid.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hid.c @@ -0,0 +1,1080 @@ +/* $FreeBSD$ */ +/* $NetBSD: hid.c,v 1.17 2001/11/13 06:24:53 lukem Exp $ */ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * 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"); + +#ifdef HIDRAW_MAKE_UHID_ALIAS +devclass_t hidraw_devclass; +#endif + +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]) { + if (s->iusage < MAXUSAGE - 1) + 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_data_unsigned(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, hid_input); + 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: + multiplier = 10; + divisor = 254; + break; + case HUM_RADIAN: + multiplier = 1; + divisor = 1; + break; + case HUM_DEGREE: + multiplier = 573; + divisor = 10; + break; + default: + return (0); + } + + if ((hi->logical_maximum <= hi->logical_minimum) || + (hi->physical_maximum <= hi->physical_minimum) || + (hi->unit_exponent < 0) || (hi->unit_exponent >= nitems(scale))) + return (0); + + logical_size = (int64_t)hi->logical_maximum - + (int64_t)hi->logical_minimum; + physical_size = (int64_t)hi->physical_maximum - + (int64_t)hi->physical_minimum; + /* Round to ceiling */ + resolution = logical_size * multiplier * scale[hi->unit_exponent][0] / + (physical_size * divisor * scale[hi->unit_exponent][1]); + + if (resolution > INT32_MAX) + return (0); + + return (resolution); +} + +/*------------------------------------------------------------------------* + * hid_is_mouse + * + * This function will decide if a USB descriptor belongs to a USB mouse. + * + * Return values: + * Zero: Not a USB mouse. + * Else: Is a USB mouse. + *------------------------------------------------------------------------*/ +int +hid_is_mouse(const void *d_ptr, uint16_t d_len) +{ + struct hid_data *hd; + struct hid_item hi; + int mdepth; + int found; + + hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); + if (hd == NULL) + return (0); + + mdepth = 0; + found = 0; + + while (hid_get_item(hd, &hi)) { + switch (hi.kind) { + case hid_collection: + if (mdepth != 0) + mdepth++; + else if (hi.collection == 1 && + hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_MOUSE)) + mdepth++; + break; + case hid_endcollection: + if (mdepth != 0) + mdepth--; + break; + case hid_input: + if (mdepth == 0) + break; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + found++; + if (hi.usage == + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y) && + (hi.flags & (HIO_CONST|HIO_RELATIVE)) == HIO_RELATIVE) + found++; + break; + default: + break; + } + } + hid_end_parse(hd); + return (found); +} + +/*------------------------------------------------------------------------* + * hid_is_keyboard + * + * This function will decide if a USB descriptor belongs to a USB keyboard. + * + * Return values: + * Zero: Not a USB keyboard. + * Else: Is a USB keyboard. + *------------------------------------------------------------------------*/ +int +hid_is_keyboard(const void *d_ptr, uint16_t d_len) +{ + if (hid_is_collection(d_ptr, d_len, + HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_KEYBOARD))) + return (1); + return (0); +} + +/*------------------------------------------------------------------------* + * hid_test_quirk - test a device for a given quirk + * + * Return values: + * false: The HID device does not have the given quirk. + * true: The HID device has the given quirk. + *------------------------------------------------------------------------*/ +bool +hid_test_quirk(const struct hid_device_info *dev_info, uint16_t quirk) +{ + bool found; + uint8_t x; + + if (quirk == HQ_NONE) + return (false); + + /* search the automatic per device quirks first */ + for (x = 0; x != HID_MAX_AUTO_QUIRK; x++) { + if (dev_info->autoQuirk[x] == quirk) + return (true); + } + + /* search global quirk table, if any */ + found = (hid_test_quirk_p) (dev_info, quirk); + + return (found); +} + +static bool +hid_test_quirk_w(const struct hid_device_info *dev_info, uint16_t quirk) +{ + return (false); /* no match */ +} + +int +hid_add_dynamic_quirk(struct hid_device_info *dev_info, uint16_t quirk) +{ + uint8_t x; + + for (x = 0; x != HID_MAX_AUTO_QUIRK; x++) { + if (dev_info->autoQuirk[x] == 0 || + dev_info->autoQuirk[x] == quirk) { + dev_info->autoQuirk[x] = quirk; + return (0); /* success */ + } + } + return (ENOSPC); +} + +void +hid_quirk_unload(void *arg) +{ + /* reset function pointer */ + hid_test_quirk_p = &hid_test_quirk_w; +#ifdef NOT_YET + hidquirk_ioctl_p = &hidquirk_ioctl_w; +#endif + + /* wait for CPU to exit the loaded functions, if any */ + + /* XXX this is a tradeoff */ + + pause("WAIT", hz); +} + +int +hid_get_rdesc(device_t dev, void *data, hid_size_t len) +{ + return (HID_GET_RDESC(device_get_parent(dev), data, len)); +} + +int +hid_read(device_t dev, void *data, hid_size_t maxlen, hid_size_t *actlen) +{ + return (HID_READ(device_get_parent(dev), data, maxlen, actlen)); +} + +int +hid_write(device_t dev, const void *data, hid_size_t len) +{ + return (HID_WRITE(device_get_parent(dev), data, len)); +} + +int +hid_get_report(device_t dev, void *data, hid_size_t maxlen, hid_size_t *actlen, + uint8_t type, uint8_t id) +{ + return (HID_GET_REPORT(device_get_parent(dev), data, maxlen, actlen, + type, id)); +} + +int +hid_set_report(device_t dev, const void *data, hid_size_t len, uint8_t type, + uint8_t id) +{ + return (HID_SET_REPORT(device_get_parent(dev), data, len, type, id)); +} + +int +hid_set_idle(device_t dev, uint16_t duration, uint8_t id) +{ + return (HID_SET_IDLE(device_get_parent(dev), duration, id)); +} + +int +hid_set_protocol(device_t dev, uint16_t protocol) +{ + return (HID_SET_PROTOCOL(device_get_parent(dev), protocol)); +} + +MODULE_VERSION(hid, 1); diff --git a/sys/dev/hid/hid_if.m b/sys/dev/hid/hid_if.m new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hid_if.m @@ -0,0 +1,164 @@ +#- +# Copyright (c) 2019 Vladimir Kondratyev +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# $FreeBSD$ +# + +#include +#include +#include +#include +#include + +# Any function listed here can do unbound sleeps waiting for IO to complete. + +INTERFACE hid; + +# Interrupts interface + +# +# Allocate memory and initialise interrupt transfers. +# intr callback function which is called if input data is available. +# context is the private softc pointer, which will be used to callback. +# isize, osize and fsize are requested maximal sizes of input, output and +# feature reports and used to determine sizes of driver internal buffers. +# This function returns zero upon success. A non-zero return value indicates +# failure. +# +METHOD void intr_setup { + device_t dev; + hid_intr_t intr; + void *context; + struct hid_rdesc_info *rdesc; +}; + +# +# Release all allocated resources associated with interrupt transfers. +# +METHOD void intr_unsetup { + device_t dev; +}; + +# +# Start the interrupt transfers if not already started. +# +METHOD int intr_start { + device_t dev; +}; + +# +# Stop the interrupt transfers if not already stopped. +# +METHOD int intr_stop { + device_t dev; +}; + +# +# The following function gets called from the HID keyboard driver +# when the system has paniced. +# +METHOD void intr_poll { + device_t dev; +}; + +# HID interface + +# +# Read out an report descriptor from the HID device. +# +METHOD int get_rdesc { + device_t dev; + void *data; + hid_size_t len; +}; + +# +# Get input data from the device. Data should be read in chunks +# of the size prescribed by the report descriptor. +# This function interferes with interrupt transfers and should not be used. +# +METHOD int read { + device_t dev; + void *data; + hid_size_t maxlen; + hid_size_t *actlen; +}; + +# +# Send data to the device. Data should be written in +# chunks of the size prescribed by the report descriptor. +# +METHOD int write { + device_t dev; + const void *data; + hid_size_t len; +}; + +# +# Get a report from the device without waiting for data on the interrupt. +# Copies a maximum of len bytes of the report data into the memory specified +# by data. Upon return actlen is set to the number of bytes copied. The type +# field indicates which report is requested. It should be HID_INPUT_REPORT, +# HID_OUTPUT_REPORT, or HID_FEATURE_REPORT. This call may fail if the device +# does not support this feature. +# +METHOD int get_report { + device_t dev; + void *data; + hid_size_t maxlen; + hid_size_t *actlen; + uint8_t type; + uint8_t id; +}; + +# +# Set a report in the device. The type field indicates which report is to be +# set. It should be HID_INPUT_REPORT, HID_OUTPUT_REPORT, or HID_FEATURE_REPORT. +# The value of the report is specified by the data and the len fields. +# This call may fail if the device does not support this feature. +# +METHOD int set_report { + device_t dev; + const void *data; + hid_size_t len; + uint8_t type; + uint8_t id; +}; + +# +# Set duration between input reports (in mSec). +# +METHOD int set_idle { + device_t dev; + uint16_t duration; + uint8_t id; +}; + +# +# Switch between the boot protocol and the report protocol (or vice versa). +# +METHOD int set_protocol { + device_t dev; + uint16_t protocol; +}; diff --git a/sys/dev/hid/hidbus.h b/sys/dev/hid/hidbus.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidbus.h @@ -0,0 +1,178 @@ +/*- + * 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_ + +#define HIDBUS_EPOCH global_epoch_preempt + +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); + +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); + +extern devclass_t hidbus_devclass; + +#endif /* _HID_HIDBUS_H_ */ diff --git a/sys/dev/hid/hidbus.c b/sys/dev/hid/hidbus.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidbus.c @@ -0,0 +1,905 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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 + +#define HID_DEBUG_VAR hid_debug +#include +#include +#include + +#include "hid_if.h" + +#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); +} + +static device_t +hidbus_add_child(device_t dev, u_int order, const char *name, int unit) +{ + struct hidbus_softc *sc = device_get_softc(dev); + struct hidbus_ivars *tlc; + device_t child; + + child = device_add_child_ordered(dev, order, name, unit); + if (child == NULL) + return (child); + + tlc = malloc(sizeof(struct hidbus_ivars), M_DEVBUF, M_WAITOK | M_ZERO); + tlc->mtx = &sc->mtx; + device_set_ivars(child, tlc); + sx_xlock(&sc->sx); + CK_STAILQ_INSERT_TAIL(&sc->tlcs, tlc, link); + sx_unlock(&sc->sx); + + return (child); +} + +static int +hidbus_enumerate_children(device_t dev, const void* data, hid_size_t len) +{ + struct hidbus_softc *sc = device_get_softc(dev); + struct hid_data *hd; + struct hid_item hi; + device_t child; + uint8_t index = 0; + + if (data == NULL || len == 0) + return (ENXIO); + + /* Add a child for each top level collection */ + hd = hid_start_parse(data, len, 1 << hid_input); + while (hid_get_item(hd, &hi)) { + if (hi.kind != hid_collection || hi.collevel != 1) + continue; + child = BUS_ADD_CHILD(dev, 0, NULL, -1); + if (child == NULL) { + device_printf(dev, "Could not add HID device\n"); + continue; + } + hidbus_set_index(child, index); + hidbus_set_usage(child, hi.usage); + hidbus_set_flags(child, HIDBUS_FLAG_AUTOCHILD); + index++; + DPRINTF("Add child TLC: 0x%04x:0x%04x\n", + HID_GET_USAGE_PAGE(hi.usage), HID_GET_USAGE(hi.usage)); + } + hid_end_parse(hd); + + if (index == 0) + return (ENXIO); + + sc->nauto = index; + + return (0); +} + +static int +hidbus_attach_children(device_t dev) +{ + struct hidbus_softc *sc = device_get_softc(dev); + int error; + + HID_INTR_SETUP(device_get_parent(dev), hidbus_intr, sc, &sc->rdesc); + + 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) == hidbus_devclass; + bus = is_bus ? dev : device_get_parent(dev); + + KASSERT(device_get_devclass(bus) == hidbus_devclass, + ("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)); + + 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(HIDBUS_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; + 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_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + struct hidbus_ivars *tlc = device_get_ivars(child); + + snprintf(buf, buflen, "index=%hhu", tlc->index); + return (0); +} + +/* PnP information for devctl(8) */ +static int +hidbus_child_pnpinfo_str(device_t bus, device_t child, char *buf, + size_t buflen) +{ + struct hidbus_ivars *tlc = device_get_ivars(child); + struct hid_device_info *devinfo = device_get_ivars(bus); + + snprintf(buf, buflen, "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); + char buf[80]; + + /* Do not add NULL suffix or if device name already contains it. */ + if (suffix != NULL && sc->nauto > 1 && + strcasestr(devinfo->name, suffix) == NULL) { + 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; + + GIANT_REQUIRED; + + /* 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(HIDBUS_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(HIDBUS_EPOCH, &et); +} + +void +hidbus_set_intr(device_t child, hid_intr_t *handler, void *context) +{ + struct hidbus_ivars *tlc = device_get_ivars(child); + + tlc->intr_handler = handler; + tlc->intr_ctx = context; +} + +int +hidbus_intr_start(device_t child) +{ + device_t bus = device_get_parent(child); + struct hidbus_softc *sc = device_get_softc(bus); + struct hidbus_ivars *ivar = device_get_ivars(child); + struct hidbus_ivars *tlc; + int refcnt = 0; + int error; + + if (sx_xlock_sig(&sc->sx) != 0) + return (EINTR); + CK_STAILQ_FOREACH(tlc, &sc->tlcs, link) { + refcnt += tlc->refcnt; + if (tlc == ivar) { + mtx_lock(tlc->mtx); + ++tlc->refcnt; + mtx_unlock(tlc->mtx); + } + } + error = refcnt != 0 ? 0 : HID_INTR_START(device_get_parent(bus)); + sx_unlock(&sc->sx); + + return (error); +} + +int +hidbus_intr_stop(device_t child) +{ + device_t bus = device_get_parent(child); + struct hidbus_softc *sc = device_get_softc(bus); + struct hidbus_ivars *ivar = device_get_ivars(child); + struct hidbus_ivars *tlc; + bool refcnt = 0; + 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); + } + refcnt += tlc->refcnt; + } + error = refcnt != 0 ? 0 : HID_INTR_STOP(device_get_parent(bus)); + sx_unlock(&sc->sx); + + return (error); +} + +void +hidbus_intr_poll(device_t child) +{ + device_t bus = device_get_parent(child); + + HID_INTR_POLL(device_get_parent(bus)); +} + +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) == hidbus_devclass ? + 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; + + GIANT_REQUIRED; + + is_bus = device_get_devclass(dev) == hidbus_devclass; + bus = is_bus ? dev : device_get_parent(dev); + sc = device_get_softc(bus); + + /* + * Do not overload already overloaded report descriptor in + * device_identify handler. It causes infinite recursion loop. + */ + if (is_bus && sc->overloaded) + return(0); + + DPRINTFN(5, "len=%d\n", len); + DPRINTFN(5, "data = %*D\n", len, data, " "); + + error = hidbus_fill_rdesc_info(&rdesc, data, len); + if (error != 0) + return (error); + + error = hidbus_detach_children(dev); + if (error != 0) + return(error); + + /* Make private copy to handle a case of dynamicaly allocated data. */ + rdesc.data = malloc(len, M_DEVBUF, M_ZERO | M_WAITOK); + bcopy(data, rdesc.data, len); + sc->overloaded = true; + free(sc->rdesc.data, M_DEVBUF); + bcopy(&rdesc, &sc->rdesc, sizeof(struct hid_rdesc_info)); + + error = hidbus_attach_children(bus); + + return (error); +} + +static int +hidbus_write(device_t dev, const void *data, hid_size_t len) +{ + 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)); +} + +/*------------------------------------------------------------------------* + * 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) != hidbus_devclass; + 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) == hidbus_devclass ? + 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_str,hidbus_child_pnpinfo_str), + DEVMETHOD(bus_child_location_str,hidbus_child_location_str), + + /* hid interface */ + DEVMETHOD(hid_get_rdesc, hid_get_rdesc), + DEVMETHOD(hid_read, hid_read), + DEVMETHOD(hid_write, hidbus_write), + DEVMETHOD(hid_get_report, hid_get_report), + DEVMETHOD(hid_set_report, hid_set_report), + DEVMETHOD(hid_set_idle, hid_set_idle), + DEVMETHOD(hid_set_protocol, hid_set_protocol), + + DEVMETHOD_END +}; + +devclass_t hidbus_devclass; +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, usbhid, hidbus_driver, hidbus_devclass, 0, 0); +DRIVER_MODULE(hidbus, iichid, hidbus_driver, hidbus_devclass, 0, 0); diff --git a/sys/dev/hid/hidmap.h b/sys/dev/hid/hidmap.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidmap.h @@ -0,0 +1,261 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HIDMAP_H_ +#define _HIDMAP_H_ + +#include + +#include + +#define HIDMAP_MAX_MAPS 4 + +struct hid_device_id; +struct hidmap_hid_item; +struct hidmap_item; +struct hidmap; + +enum hidmap_cb_state { + HIDMAP_CB_IS_PROBING, + HIDMAP_CB_IS_ATTACHING, + HIDMAP_CB_IS_RUNNING, + HIDMAP_CB_IS_DETACHING, +}; + +#define HIDMAP_KEY_NULL 0xFF /* Special event code to discard input */ + +/* Third parameter of hidmap callback has different type depending on state */ +union hidmap_cb_ctx { + struct hid_item *hi; /* Probe- and attach-stage callbacks */ + int32_t data; /* Run-stage callbacks */ + uint8_t rid; /* Run-stage finalizing callbacks */ +}; + +#define HIDMAP_CB_ARGS \ + struct hidmap *hm, struct hidmap_hid_item *hi, union hidmap_cb_ctx ctx +typedef int hidmap_cb_t(HIDMAP_CB_ARGS); + +/* These helpers can be used at any stage of any callbacks */ +#define HIDMAP_CB_GET_STATE(...) \ + ((hm == NULL) ? HIDMAP_CB_IS_PROBING : hm->cb_state) +#define HIDMAP_CB_GET_SOFTC(...) \ + (hm == NULL ? NULL : device_get_softc(hm->dev)) +#define HIDMAP_CB_GET_EVDEV(...) \ + (hm == NULL ? NULL : hm->evdev) +#define HIDMAP_CB_UDATA (hi->udata) +#define HIDMAP_CB_UDATA64 (hi->udata64) +/* Special helpers for run-stage of finalizing callbacks */ +#define HIDMAP_CB_GET_RID(...) (ctx.rid) +#define HIDMAP_CB_GET_DATA(loc) \ + hid_get_data(hm->intr_buf, hm->intr_len, (loc)) +#define HIDMAP_CB_GET_UDATA(loc) \ + hid_get_udata(hm->intr_buf, hm->intr_len, (loc)) + +enum hidmap_relabs { + HIDMAP_RELABS_ANY = 0, + HIDMAP_RELATIVE, + HIDMAP_ABSOLUTE, +}; + +struct hidmap_item { + union { + struct { + uint16_t type; /* Evdev event type */ + uint16_t code; /* Evdev event code */ + uint16_t fuzz; /* Evdev event fuzz */ + uint16_t flat; /* Evdev event flat */ + }; + hidmap_cb_t *cb; /* Reporting callback */ + }; + int32_t usage; /* HID usage (base) */ + uint16_t nusages; /* number of usages */ + bool required:1; /* Required by driver */ + enum hidmap_relabs relabs:2; + bool has_cb:1; + bool final_cb:1; + bool invert_value:1; + u_int reserved:10; +}; + +#define HIDMAP_ANY(_page, _usage, _type, _code) \ + .usage = HID_USAGE2((_page), (_usage)), \ + .nusages = 1, \ + .type = (_type), \ + .code = (_code) +#define HIDMAP_ANY_RANGE(_page, _usage_from, _usage_to, _type, _code) \ + .usage = HID_USAGE2((_page), (_usage_from)), \ + .nusages = (_usage_to) - (_usage_from) + 1, \ + .type = (_type), \ + .code = (_code) +#define HIDMAP_ANY_CB(_page, _usage, _callback) \ + .usage = HID_USAGE2((_page), (_usage)), \ + .nusages = 1, \ + .cb = (_callback), \ + .has_cb = true +#define HIDMAP_ANY_CB_RANGE(_page, _usage_from, _usage_to, _callback) \ + .usage = HID_USAGE2((_page), (_usage_from)), \ + .nusages = (_usage_to) - (_usage_from) + 1, \ + .cb = (_callback), \ + .has_cb = true +#define HIDMAP_KEY(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_KEY, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_KEY_RANGE(_page, _ufrom, _uto, _code) \ + HIDMAP_ANY_RANGE((_page), (_ufrom), (_uto), EV_KEY, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_REL(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_REL, (_code)), \ + .relabs = HIDMAP_RELATIVE +#define HIDMAP_ABS(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_ABS, (_code)), \ + .relabs = HIDMAP_ABSOLUTE +#define HIDMAP_SW(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_SW, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_REL_CB(_page, _usage, _callback) \ + HIDMAP_ANY_CB((_page), (_usage), (_callback)), \ + .relabs = HIDMAP_RELATIVE +#define HIDMAP_ABS_CB(_page, _usage, _callback) \ + HIDMAP_ANY_CB((_page), (_usage), (_callback)), \ + .relabs = HIDMAP_ABSOLUTE +/* + * Special callback function which is not tied to particular HID input usage + * but called at the end evdev properties setting or interrupt handler + * just before evdev_register() or evdev_sync() calls. + */ +#define HIDMAP_FINAL_CB(_callback) \ + HIDMAP_ANY_CB(0, 0, (_callback)), .final_cb = true + +enum hidmap_type { + HIDMAP_TYPE_FINALCB = 0,/* No HID item associated. Runs unconditionally + * at the end of other items processing */ + HIDMAP_TYPE_CALLBACK, /* HID item is reported with user callback */ + HIDMAP_TYPE_VARIABLE, /* HID item is variable (single usage) */ + HIDMAP_TYPE_VAR_NULLST, /* HID item is null state variable */ + HIDMAP_TYPE_ARR_LIST, /* HID item is array with list of usages */ + HIDMAP_TYPE_ARR_RANGE, /* Array with range (min;max) of usages */ +}; + +struct hidmap_hid_item { + union { + hidmap_cb_t *cb; /* Callback */ + struct { /* Variable */ + uint16_t evtype; /* Evdev event type */ + uint16_t code; /* Evdev event code */ + }; + uint16_t *codes; /* Array list map type */ + int32_t umin; /* Array range map type */ + }; + union { + void *udata; /* Callback private context */ + uint64_t udata64; + int32_t last_val; /* Last reported value (var) */ + uint16_t last_key; /* Last reported key (array) */ + }; + struct hid_location loc; /* HID item location */ + int32_t lmin; /* HID item logical minimum */ + int32_t lmax; /* HID item logical maximum */ + enum hidmap_type type:8; + uint8_t id; /* Report ID */ + bool invert_value; +}; + +struct hidmap { + device_t dev; + + struct evdev_dev *evdev; + struct evdev_methods evdev_methods; + + /* Scatter-gather list of maps */ + int nmaps; + uint32_t nmap_items[HIDMAP_MAX_MAPS]; + const struct hidmap_item *map[HIDMAP_MAX_MAPS]; + + /* List of preparsed HID items */ + uint32_t nhid_items; + struct hidmap_hid_item *hid_items; + + /* Key event merging buffers */ + uint8_t *key_press; + uint8_t *key_rel; + uint16_t key_min; + uint16_t key_max; + + int *debug_var; + int debug_level; + enum hidmap_cb_state cb_state; + void * intr_buf; + hid_size_t intr_len; +}; + +typedef uint8_t * hidmap_caps_t; +#define HIDMAP_CAPS_SZ(nitems) howmany((nitems), 8) +#define HIDMAP_CAPS(name, map) uint8_t (name)[HIDMAP_CAPS_SZ(nitems(map))] +static inline bool +hidmap_test_cap(hidmap_caps_t caps, int cap) +{ + return (isset(caps, cap) != 0); +} + +/* + * It is safe to call any of following procedures in device_probe context + * that makes possible to write probe-only drivers with attach/detach handlers + * inherited from hidmap. See hcons and hsctrl drivers for example. + */ +static inline void +hidmap_set_dev(struct hidmap *hm, device_t dev) +{ + hm->dev = dev; +} + +/* Hack to avoid #ifdef-ing of hidmap_set_debug_var in hidmap based drivers */ +#ifdef HID_DEBUG +#define hidmap_set_debug_var(h, d) _hidmap_set_debug_var((h), (d)) +#else +#define hidmap_set_debug_var(...) +#endif +void _hidmap_set_debug_var(struct hidmap *hm, int *debug_var); +#define HIDMAP_ADD_MAP(hm, map, caps) \ + hidmap_add_map((hm), (map), nitems(map), (caps)) +uint32_t hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map, + int nitems_map, hidmap_caps_t caps); + +/* Versions of evdev_* functions capable to merge key events with same codes */ +void hidmap_support_key(struct hidmap *hm, uint16_t key); +void hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value); + +#define HIDMAP_PROBE(hm, dev, id, map, suffix) \ + hidmap_probe((hm), (dev), (id), nitems(id), (map), nitems(map), \ + (suffix), NULL) +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 hidmap_attach(struct hidmap *hm); +int hidmap_detach(struct hidmap *hm); + +#endif /* _HIDMAP_H_ */ diff --git a/sys/dev/hid/hidmap.c b/sys/dev/hid/hidmap.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidmap.c @@ -0,0 +1,829 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Abstract 1 to 1 HID input usage to evdev event mapper driver. + */ + +#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))); +} + +static int +hidmap_ev_open(struct evdev_dev *evdev) +{ + return (hidbus_intr_start(evdev_get_softc(evdev))); +} + +void +hidmap_support_key(struct hidmap *hm, uint16_t key) +{ + if (hm->key_press == NULL) { + hm->key_press = malloc(howmany(KEY_CNT, 8), M_DEVBUF, + M_ZERO | M_WAITOK); + evdev_support_event(hm->evdev, EV_KEY); + hm->key_min = key; + hm->key_max = key; + } + hm->key_min = MIN(hm->key_min, key); + hm->key_max = MAX(hm->key_max, key); + if (isset(hm->key_press, key)) { + if (hm->key_rel == NULL) + hm->key_rel = malloc(howmany(KEY_CNT, 8), M_DEVBUF, + M_ZERO | M_WAITOK); + } else { + setbit(hm->key_press, key); + evdev_support_key(hm->evdev, key); + } +} + +void +hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value) +{ + if (HIDMAP_WANT_MERGE_KEYS(hm)) + setbit(value != 0 ? hm->key_press : hm->key_rel, key); + else + evdev_push_key(hm->evdev, key, value); +} + +static void +hidmap_sync_keys(struct hidmap *hm) +{ + int i, j; + bool press, rel; + + for (j = hm->key_min / 8; j <= hm->key_max / 8; j++) { + if (hm->key_press[j] != hm->key_rel[j]) { + for (i = j * 8; i < j * 8 + 8; i++) { + press = isset(hm->key_press, i); + rel = isset(hm->key_rel, i); + if (press != rel) + evdev_push_key(hm->evdev, i, press); + } + } + } + bzero(hm->key_press, howmany(KEY_CNT, 8)); + bzero(hm->key_rel, howmany(KEY_CNT, 8)); +} + +static void +hidmap_intr(void *context, void *buf, hid_size_t len) +{ + struct hidmap *hm = context; + struct hidmap_hid_item *hi; + const struct hidmap_item *mi; + int32_t usage; + int32_t data; + uint16_t key, uoff; + uint8_t id = 0; + bool found, do_sync = false; + + DPRINTFN(hm, 6, "hm=%p len=%d\n", hm, len); + DPRINTFN(hm, 6, "data = %*D\n", len, buf, " "); + + /* Strip leading "report ID" byte */ + if (hm->hid_items[0].id) { + id = *(uint8_t *)buf; + len--; + buf = (uint8_t *)buf + 1; + } + + hm->intr_buf = buf; + hm->intr_len = len; + + for (hi = hm->hid_items; hi < hm->hid_items + hm->nhid_items; hi++) { + /* At first run callbacks that not tied to HID items */ + if (hi->type == HIDMAP_TYPE_FINALCB) { + DPRINTFN(hm, 6, "type=%d item=%*D\n", hi->type, + (int)sizeof(hi->cb), &hi->cb, " "); + if (hi->cb(hm, hi, (union hidmap_cb_ctx){.rid = id}) + == 0) + do_sync = true; + continue; + } + + /* Ignore irrelevant reports */ + if (id != hi->id) + continue; + + /* + * 5.8. If Logical Minimum and Logical Maximum are both + * positive values then the contents of a field can be assumed + * to be an unsigned value. Otherwise, all integer values are + * signed values represented in 2’s complement format. + */ + data = hi->lmin < 0 || hi->lmax < 0 + ? hid_get_data(buf, len, &hi->loc) + : hid_get_udata(buf, len, &hi->loc); + + DPRINTFN(hm, 6, "type=%d data=%d item=%*D\n", hi->type, data, + (int)sizeof(hi->cb), &hi->cb, " "); + + if (hi->invert_value && hi->type < HIDMAP_TYPE_ARR_LIST) + data = hi->evtype == EV_REL + ? -data + : hi->lmin + hi->lmax - data; + + switch (hi->type) { + case HIDMAP_TYPE_CALLBACK: + if (hi->cb(hm, hi, (union hidmap_cb_ctx){.data = data}) + != 0) + continue; + break; + + case HIDMAP_TYPE_VAR_NULLST: + /* + * 5.10. If the host or the device receives an + * out-of-range value then the current value for the + * respective control will not be modified. + */ + if (data < hi->lmin || data > hi->lmax) + continue; + /* FALLTHROUGH */ + case HIDMAP_TYPE_VARIABLE: + /* + * Ignore reports for absolute data if the data did not + * change and for relative data if data is 0. + * Evdev layer filters out them anyway. + */ + if (data == (hi->evtype == EV_REL ? 0 : hi->last_val)) + continue; + if (hi->evtype == EV_KEY) + hidmap_push_key(hm, hi->code, data); + else + evdev_push_event(hm->evdev, hi->evtype, + hi->code, data); + hi->last_val = data; + break; + + case HIDMAP_TYPE_ARR_LIST: + key = KEY_RESERVED; + /* + * 6.2.2.5. An out-of range value in an array field + * is considered no controls asserted. + */ + if (data < hi->lmin || data > hi->lmax) + goto report_key; + /* + * 6.2.2.5. Rather than returning a single bit for each + * button in the group, an array returns an index in + * each field that corresponds to the pressed button. + */ + key = hi->codes[data - hi->lmin]; + if (key == KEY_RESERVED) + DPRINTF(hm, "Can not map unknown HID " + "array index: %08x\n", data); + goto report_key; + + case HIDMAP_TYPE_ARR_RANGE: + key = KEY_RESERVED; + /* + * 6.2.2.5. An out-of range value in an array field + * is considered no controls asserted. + */ + if (data < hi->lmin || data > hi->lmax) + goto report_key; + /* + * When the input field is an array and the usage is + * specified with a range instead of an ID, we have to + * derive the actual usage by using the item value as + * an index in the usage range list. + */ + usage = data - hi->lmin + hi->umin; + found = false; + HIDMAP_FOREACH_ITEM(hm, mi, uoff) { + if (usage == mi->usage + uoff && + mi->type == EV_KEY && !mi->has_cb) { + key = mi->code; + found = true; + break; + } + } + if (!found) + DPRINTF(hm, "Can not map unknown HID " + "usage: %08x\n", usage); +report_key: + if (key == HIDMAP_KEY_NULL || key == hi->last_key) + continue; + if (hi->last_key != KEY_RESERVED) + hidmap_push_key(hm, hi->last_key, 0); + if (key != KEY_RESERVED) + hidmap_push_key(hm, key, 1); + hi->last_key = key; + break; + + default: + KASSERT(0, ("Unknown map type (%d)", hi->type)); + } + do_sync = true; + } + + if (do_sync) { + if (HIDMAP_WANT_MERGE_KEYS(hm)) + hidmap_sync_keys(hm); + evdev_sync(hm->evdev); + } +} + +static inline bool +can_map_callback(struct hid_item *hi, const struct hidmap_item *mi, + uint16_t usage_offset) +{ + + return (mi->has_cb && !mi->final_cb && + hi->usage == mi->usage + usage_offset && + (mi->relabs == HIDMAP_RELABS_ANY || + !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); +} + +static inline bool +can_map_variable(struct hid_item *hi, const struct hidmap_item *mi, + uint16_t usage_offset) +{ + + return ((hi->flags & HIO_VARIABLE) != 0 && !mi->has_cb && + hi->usage == mi->usage + usage_offset && + (mi->relabs == HIDMAP_RELABS_ANY || + !(hi->flags & HIO_RELATIVE) == !(mi->relabs == HIDMAP_RELATIVE))); +} + +static inline bool +can_map_arr_range(struct hid_item *hi, const struct hidmap_item *mi, + uint16_t usage_offset) +{ + + return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && + hi->usage_minimum <= mi->usage + usage_offset && + hi->usage_maximum >= mi->usage + usage_offset && + mi->type == EV_KEY && + (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); +} + +static inline bool +can_map_arr_list(struct hid_item *hi, const struct hidmap_item *mi, + uint32_t usage, uint16_t usage_offset) +{ + + return ((hi->flags & HIO_VARIABLE) == 0 && !mi->has_cb && + usage == mi->usage + usage_offset && + mi->type == EV_KEY && + (mi->code != KEY_RESERVED && mi->code != HIDMAP_KEY_NULL)); +} + +static bool +hidmap_probe_hid_item(struct hid_item *hi, const struct hidmap_item *map, + int nitems_map, hidmap_caps_t caps) +{ + u_int i, j; + uint16_t uoff; + bool found = false; + +#define HIDMAP_FOREACH_INDEX(map, nitems, idx, uoff) \ + for ((idx) = 0, (uoff) = -1; \ + hidmap_get_next_map_index((map), (nitems), &(idx), &(uoff));) + + HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { + if (can_map_callback(hi, map + i, uoff)) { + if (map[i].cb(NULL, NULL, + (union hidmap_cb_ctx){.hi = hi}) != 0) + break; + setbit(caps, i); + return (true); + } + } + + if (hi->flags & HIO_VARIABLE) { + HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { + if (can_map_variable(hi, map + i, uoff)) { + KASSERT(map[i].type == EV_KEY || + map[i].type == EV_REL || + map[i].type == EV_ABS || + map[i].type == EV_SW, + ("Unsupported event type")); + setbit(caps, i); + return (true); + } + } + return (false); + } + + if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { + HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { + if (can_map_arr_range(hi, map + i, uoff)) { + setbit(caps, i); + found = true; + } + } + return (found); + } + + for (j = 0; j < hi->nusages; j++) { + HIDMAP_FOREACH_INDEX(map, nitems_map, i, uoff) { + if (can_map_arr_list(hi, map+i, hi->usages[j], uoff)) { + setbit(caps, i); + found = true; + } + } + } + + return (found); +} + +static uint32_t +hidmap_probe_hid_descr(void *d_ptr, hid_size_t d_len, uint8_t tlc_index, + const struct hidmap_item *map, int nitems_map, hidmap_caps_t caps) +{ + struct hid_data *hd; + struct hid_item hi; + uint32_t i, items = 0; + bool do_free = false; + + if (caps == NULL) { + caps = malloc(HIDMAP_CAPS_SZ(nitems_map), M_DEVBUF, M_WAITOK); + do_free = true; + } else + bzero (caps, HIDMAP_CAPS_SZ(nitems_map)); + + /* Parse inputs */ + hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); + HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { + if (hi.kind != hid_input) + continue; + if (hi.flags & HIO_CONST) + continue; + for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) + if (hidmap_probe_hid_item(&hi, map, nitems_map, caps)) + items++; + } + hid_end_parse(hd); + + /* Take finalizing callbacks in to account */ + for (i = 0; i < nitems_map; i++) { + if (map[i].has_cb && map[i].final_cb && + map[i].cb(NULL, NULL, (union hidmap_cb_ctx){}) == 0) { + setbit(caps, i); + items++; + } + } + + /* Check that all mandatory usages are present in report descriptor */ + if (items != 0) { + for (i = 0; i < nitems_map; i++) { + if (map[i].required && isclr(caps, i)) { + items = 0; + break; + } + } + } + + if (do_free) + free(caps, M_DEVBUF); + + return (items); +} + +uint32_t +hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map, + int nitems_map, hidmap_caps_t caps) +{ + void *d_ptr; + uint32_t items; + int i, error; + hid_size_t d_len; + uint8_t tlc_index = hidbus_get_index(hm->dev); + + /* Avoid double-adding of map in probe() handler */ + for (i = 0; i < hm->nmaps; i++) + if (hm->map[i] == map) + return (0); + + error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); + if (error != 0) { + device_printf(hm->dev, "could not retrieve report descriptor " + "from device: %d\n", error); + return (error); + } + + hm->cb_state = HIDMAP_CB_IS_PROBING; + items = hidmap_probe_hid_descr(d_ptr, d_len, tlc_index, map, + nitems_map, caps); + if (items == 0) + return (ENXIO); + + KASSERT(hm->nmaps < HIDMAP_MAX_MAPS, + ("Not more than %d maps is supported", HIDMAP_MAX_MAPS)); + hm->nhid_items += items; + hm->map[hm->nmaps] = map; + hm->nmap_items[hm->nmaps] = nitems_map; + hm->nmaps++; + + return (0); +} + +static bool +hidmap_parse_hid_item(struct hidmap *hm, struct hid_item *hi, + struct hidmap_hid_item *item) +{ + const struct hidmap_item *mi; + struct hidmap_hid_item hi_temp; + uint32_t i; + uint16_t uoff; + bool found = false; + + HIDMAP_FOREACH_ITEM(hm, mi, uoff) { + if (can_map_callback(hi, mi, uoff)) { + bzero(&hi_temp, sizeof(hi_temp)); + hi_temp.cb = mi->cb; + hi_temp.type = HIDMAP_TYPE_CALLBACK; + /* + * Values returned by probe- and attach-stage + * callbacks MUST be identical. + */ + if (mi->cb(hm, &hi_temp, + (union hidmap_cb_ctx){.hi = hi}) != 0) + break; + bcopy(&hi_temp, item, sizeof(hi_temp)); + goto mapped; + } + } + + if (hi->flags & HIO_VARIABLE) { + HIDMAP_FOREACH_ITEM(hm, mi, uoff) { + if (can_map_variable(hi, mi, uoff)) { + item->evtype = mi->type; + item->code = mi->code + uoff; + item->type = hi->flags & HIO_NULLSTATE + ? HIDMAP_TYPE_VAR_NULLST + : HIDMAP_TYPE_VARIABLE; + item->last_val = 0; + item->invert_value = mi->invert_value; + switch (mi->type) { + case EV_KEY: + hidmap_support_key(hm, item->code); + break; + case EV_REL: + evdev_support_event(hm->evdev, EV_REL); + evdev_support_rel(hm->evdev, + item->code); + break; + case EV_ABS: + evdev_support_event(hm->evdev, EV_ABS); + evdev_support_abs(hm->evdev, + item->code, + hi->logical_minimum, + hi->logical_maximum, + mi->fuzz, + mi->flat, + hid_item_resolution(hi)); + break; + case EV_SW: + evdev_support_event(hm->evdev, EV_SW); + evdev_support_sw(hm->evdev, + item->code); + break; + default: + KASSERT(0, ("Unsupported event type")); + } + goto mapped; + } + } + return (false); + } + + if (hi->usage_minimum != 0 || hi->usage_maximum != 0) { + HIDMAP_FOREACH_ITEM(hm, mi, uoff) { + if (can_map_arr_range(hi, mi, uoff)) { + hidmap_support_key(hm, mi->code + uoff); + found = true; + } + } + if (!found) + return (false); + item->umin = hi->usage_minimum; + item->type = HIDMAP_TYPE_ARR_RANGE; + item->last_key = KEY_RESERVED; + goto mapped; + } + + for (i = 0; i < hi->nusages; i++) { + HIDMAP_FOREACH_ITEM(hm, mi, uoff) { + if (can_map_arr_list(hi, mi, hi->usages[i], uoff)) { + hidmap_support_key(hm, mi->code + uoff); + if (item->codes == NULL) + item->codes = malloc( + hi->nusages * sizeof(uint16_t), + M_DEVBUF, M_WAITOK | M_ZERO); + item->codes[i] = mi->code + uoff; + found = true; + break; + } + } + } + if (!found) + return (false); + item->type = HIDMAP_TYPE_ARR_LIST; + item->last_key = KEY_RESERVED; + +mapped: + item->id = hi->report_ID; + item->loc = hi->loc; + item->loc.count = 1; + item->lmin = hi->logical_minimum; + item->lmax = hi->logical_maximum; + + DPRINTFN(hm, 6, "usage=%04x id=%d loc=%u/%u type=%d item=%*D\n", + hi->usage, hi->report_ID, hi->loc.pos, hi->loc.size, item->type, + (int)sizeof(item->cb), &item->cb, " "); + + return (true); +} + +static int +hidmap_parse_hid_descr(struct hidmap *hm, uint8_t tlc_index) +{ + const struct hidmap_item *map; + struct hidmap_hid_item *item = hm->hid_items; + void *d_ptr; + struct hid_data *hd; + struct hid_item hi; + int i, error; + hid_size_t d_len; + + error = hid_get_report_descr(hm->dev, &d_ptr, &d_len); + if (error != 0) { + DPRINTF(hm, "could not retrieve report descriptor from " + "device: %d\n", error); + return (error); + } + + /* Parse inputs */ + hd = hid_start_parse(d_ptr, d_len, 1 << hid_input); + HIDBUS_FOREACH_ITEM(hd, &hi, tlc_index) { + if (hi.kind != hid_input) + continue; + if (hi.flags & HIO_CONST) + continue; + for (i = 0; i < hi.loc.count; i++, hi.loc.pos += hi.loc.size) + if (hidmap_parse_hid_item(hm, &hi, item)) + item++; + KASSERT(item <= hm->hid_items + hm->nhid_items, + ("Parsed HID item array overflow")); + } + hid_end_parse(hd); + + /* Add finalizing callbacks to the end of list */ + for (i = 0; i < hm->nmaps; i++) { + for (map = hm->map[i]; + map < hm->map[i] + hm->nmap_items[i]; + map++) { + if (map->has_cb && map->final_cb && + map->cb(hm, item, (union hidmap_cb_ctx){}) == 0) { + item->cb = map->cb; + item->type = HIDMAP_TYPE_FINALCB; + item++; + } + } + } + + /* + * Resulting number of parsed HID items can be less than expected as + * map items might be duplicated in different maps. Save real number. + */ + if (hm->nhid_items != item - hm->hid_items) + DPRINTF(hm, "Parsed HID item number mismatch: expected=%u " + "result=%td\n", hm->nhid_items, item - hm->hid_items); + hm->nhid_items = item - hm->hid_items; + + if (HIDMAP_WANT_MERGE_KEYS(hm)) + bzero(hm->key_press, howmany(KEY_CNT, 8)); + + return (0); +} + +int +hidmap_probe(struct hidmap* hm, device_t dev, + const struct hid_device_id *id, int nitems_id, + const struct hidmap_item *map, int nitems_map, + const char *suffix, hidmap_caps_t caps) +{ + int error; + + error = hidbus_lookup_driver_info(dev, id, nitems_id); + if (error != 0) + return (error); + + hidmap_set_dev(hm, dev); + + error = hidmap_add_map(hm, map, nitems_map, caps); + if (error != 0) + return (error); + + hidbus_set_desc(dev, suffix); + + return (BUS_PROBE_DEFAULT); +} + +int +hidmap_attach(struct hidmap* hm) +{ + const struct hid_device_info *hw = hid_get_device_info(hm->dev); +#ifdef HID_DEBUG + char tunable[40]; +#endif + int error; + +#ifdef HID_DEBUG + if (hm->debug_var == NULL) { + hm->debug_var = &hm->debug_level; + snprintf(tunable, sizeof(tunable), "hw.hid.%s.debug", + device_get_name(hm->dev)); + TUNABLE_INT_FETCH(tunable, &hm->debug_level); + SYSCTL_ADD_INT(device_get_sysctl_ctx(hm->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(hm->dev)), + OID_AUTO, "debug", CTLTYPE_INT | CTLFLAG_RWTUN, + &hm->debug_level, 0, "Verbosity level"); + } +#endif + + DPRINTFN(hm, 11, "hm=%p\n", hm); + + hm->cb_state = HIDMAP_CB_IS_ATTACHING; + + hm->hid_items = malloc(hm->nhid_items * sizeof(struct hid_item), + M_DEVBUF, M_WAITOK | M_ZERO); + + hidbus_set_intr(hm->dev, hidmap_intr, hm); + hm->evdev_methods = (struct evdev_methods) { + .ev_open = &hidmap_ev_open, + .ev_close = &hidmap_ev_close, + }; + + hm->evdev = evdev_alloc(); + evdev_set_name(hm->evdev, device_get_desc(hm->dev)); + evdev_set_phys(hm->evdev, device_get_nameunit(hm->dev)); + evdev_set_id(hm->evdev, hw->idBus, hw->idVendor, hw->idProduct, + hw->idVersion); + evdev_set_serial(hm->evdev, hw->serial); + evdev_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_epoch(hm->evdev, HIDBUS_EPOCH); + 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/hidquirk.h b/sys/dev/hid/hidquirk.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidquirk.h @@ -0,0 +1,74 @@ +/* $FreeBSD$ */ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Screening of all content of this file except HID_QUIRK_LIST is a kind of + * hack that allows multiple HID_QUIRK_LIST inclusion with different HQ() + * wrappers. That save us splitting hidquirk.h on two header files. + */ +#ifndef HQ +#ifndef _HID_QUIRK_H_ +#define _HID_QUIRK_H_ +#endif + +/* + * Keep in sync with share/man/man4/hidquirk.4 + */ +#define HID_QUIRK_LIST(...) \ + HQ(NONE), /* not a valid quirk */ \ + \ + HQ(MATCH_VENDOR_ONLY), /* match quirk on vendor only */ \ + \ + /* Autoquirks */ \ + HQ(HAS_KBD_BOOTPROTO), /* device supports keyboard boot protocol */ \ + HQ(HAS_MS_BOOTPROTO), /* device supports mouse boot protocol */ \ + HQ(IS_XBOX360GP), /* device is XBox 360 GamePad */ \ + HQ(NOWRITE), /* device does not support writes */ \ + HQ(IICHID_SAMPLING), /* IIC backend runs in sampling mode */ \ + \ + /* Various quirks */ \ + HQ(HID_IGNORE), /* device should be ignored by hid class */ \ + HQ(KBD_BOOTPROTO), /* device should set the boot protocol */ \ + HQ(MS_BOOTPROTO), /* device should set the boot protocol */ \ + HQ(MS_BAD_CLASS), /* doesn't identify properly */ \ + HQ(MS_LEADING_BYTE), /* mouse sends an unknown leading byte */ \ + HQ(MS_REVZ), /* mouse has Z-axis reversed */ \ + HQ(SPUR_BUT_UP), /* spurious mouse button up events */ \ + HQ(MT_TIMESTAMP) /* Multitouch device exports HW timestamps */ + +#ifndef HQ +#define HQ(x) HQ_##x +enum { + HID_QUIRK_LIST(), + HID_QUIRK_MAX +}; +#undef HQ + +#endif /* _HID_QUIRK_H_ */ +#endif /* HQ */ diff --git a/sys/dev/hid/hidquirk.c b/sys/dev/hid/hidquirk.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidquirk.c @@ -0,0 +1,437 @@ +/* $FreeBSD$ */ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c) 1998 The NetBSD Foundation, Inc. All rights reserved. + * Copyright (c) 1998 Lennart Augustsson. All rights reserved. + * Copyright (c) 2008 Hans Petter Selasky. All rights reserved. + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define HID_DEBUG_VAR hid_debug +#include +#include +#include "usbdevs.h" + + +MODULE_DEPEND(hidquirk, hid, 1, 1, 1); +MODULE_VERSION(hidquirk, 1); + +#define HID_DEV_QUIRKS_MAX 384 +#define HID_SUB_QUIRKS_MAX 8 +#define HID_QUIRK_ENVROOT "hw.hid.quirk." + +struct hidquirk_entry { + uint16_t bus; + uint16_t vid; + uint16_t pid; + uint16_t lo_rev; + uint16_t hi_rev; + uint16_t quirks[HID_SUB_QUIRKS_MAX]; +}; + +static struct mtx hidquirk_mtx; + +#define HID_QUIRK_VP(b,v,p,l,h,...) \ + { .bus = (b), .vid = (v), .pid = (p), .lo_rev = (l), .hi_rev = (h), \ + .quirks = { __VA_ARGS__ } } +#define USB_QUIRK(v,p,l,h,...) \ + HID_QUIRK_VP(BUS_USB, USB_VENDOR_##v, USB_PRODUCT_##v##_##p, l, h, __VA_ARGS__) + +static struct hidquirk_entry hidquirks[HID_DEV_QUIRKS_MAX] = { + USB_QUIRK(ASUS, LCM, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(QTRONIX, 980N, 0x110, 0x110, HQ_SPUR_BUT_UP), + USB_QUIRK(ALCOR2, KBD_HUB, 0x001, 0x001, HQ_SPUR_BUT_UP), + USB_QUIRK(LOGITECH, G510S, 0x0000, 0xFFFF, HQ_KBD_BOOTPROTO), + /* Devices which should be ignored by usbhid */ + USB_QUIRK(APC, UPS, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6H375USB, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C550AVR, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C1250TWRK, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C1500TWRK, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C900UNV, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C100UNV, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C120UNV, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C800UNV, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(BELKIN, F6C1100UNV, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(CYBERPOWER, BC900D, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(CYBERPOWER, 1500CAVRLCD, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(CYBERPOWER, OR2200LCDRM2U, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(DELL2, VARIOUS_UPS, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(CYPRESS, SILVERSHIELD, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(DELORME, EARTHMATE, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(DREAMLINK, DL100B, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(MICROCHIP, PICOLCD20X2, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(MICROCHIP, PICOLCD4X20, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(LIEBERT, POWERSURE_PXT, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(LIEBERT2, PSI1000, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(LIEBERT2, POWERSURE_PSA, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(MGE, UPS1, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(MGE, UPS2, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(POWERCOM, IMPERIAL_SERIES, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(POWERCOM, SMART_KING_PRO, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(POWERCOM, WOW, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(POWERCOM, VANGUARD, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(POWERCOM, BLACK_KNIGHT_PRO, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, AVR550U, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, AVR750U, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, ECO550UPS, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, T750_INTL, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, RT_2200_INTL, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, OMNI1000LCD, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, OMNI900LCD, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, SMART_2200RMXL2U, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, UPS_3014, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, SU6000RT4U, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(TRIPPLITE2, SU1500RTXL2UA_2, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(APPLE, IPHONE_3G, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(MEGATEC, UPS, 0x0000, 0xffff, HQ_HID_IGNORE), + /* Devices which should be ignored by both ukbd and uhid */ + USB_QUIRK(CYPRESS, WISPY1A, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY1B, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(METAGEEK, WISPY24X, 0x0000, 0xffff, HQ_HID_IGNORE), + USB_QUIRK(METAGEEK2, WISPYDBX, 0x0000, 0xffff, HQ_HID_IGNORE), + /* MS keyboards do weird things */ + USB_QUIRK(MICROSOFT, NATURAL4000, 0x0000, 0xFFFF, HQ_KBD_BOOTPROTO), + USB_QUIRK(MICROSOFT, WLINTELLIMOUSE, 0x0000, 0xffff, HQ_MS_LEADING_BYTE), + /* Quirk for Corsair Vengeance K60 keyboard */ + USB_QUIRK(CORSAIR, K60, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Quirk for Corsair Gaming K68 keyboard */ + USB_QUIRK(CORSAIR, K68, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Quirk for Corsair Vengeance K70 keyboard */ + USB_QUIRK(CORSAIR, K70, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Quirk for Corsair K70 RGB keyboard */ + USB_QUIRK(CORSAIR, K70_RGB, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Quirk for Corsair STRAFE Gaming keyboard */ + USB_QUIRK(CORSAIR, STRAFE, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + USB_QUIRK(CORSAIR, STRAFE2, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), + /* Holtek USB gaming keyboard */ + USB_QUIRK(HOLTEK, F85, 0x0000, 0xffff, HQ_KBD_BOOTPROTO), +}; +#undef HID_QUIRK_VP +#undef USB_QUIRK + +/* hidquirk.h exposes only HID_QUIRK_LIST macro when HQ() is defined */ +#define HQ(x) [HQ_##x] = "HQ_"#x +#include "hidquirk.h" +static const char *hidquirk_str[HID_QUIRK_MAX] = { HID_QUIRK_LIST() }; +#undef HQ + +static hid_test_quirk_t hid_test_quirk_by_info; + +/*------------------------------------------------------------------------* + * hidquirkstr + * + * This function converts an USB quirk code into a string. + *------------------------------------------------------------------------*/ +static const char * +hidquirkstr(uint16_t quirk) +{ + return ((quirk < HID_QUIRK_MAX && hidquirk_str[quirk] != NULL) ? + hidquirk_str[quirk] : "HQ_UNKNOWN"); +} + +/*------------------------------------------------------------------------* + * hid_strquirk + * + * This function converts a string into a HID quirk code. + * + * Returns: + * Less than HID_QUIRK_MAX: Quirk code + * Else: Quirk code not found + *------------------------------------------------------------------------*/ +static uint16_t +hid_strquirk(const char *str, size_t len) +{ + const char *quirk; + uint16_t x; + + for (x = 0; x != HID_QUIRK_MAX; x++) { + quirk = hidquirkstr(x); + if (strncmp(str, quirk, len) == 0 && + quirk[len] == 0) + break; + } + return (x); +} + +/*------------------------------------------------------------------------* + * hid_test_quirk_by_info + * + * Returns: + * false: Quirk not found + * true: Quirk found + *------------------------------------------------------------------------*/ +bool +hid_test_quirk_by_info(const struct hid_device_info *info, uint16_t quirk) +{ + uint16_t x; + uint16_t y; + + if (quirk == HQ_NONE) + goto done; + + mtx_lock(&hidquirk_mtx); + + for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((hidquirks[x].bus != info->idBus) || + (hidquirks[x].vid != info->idVendor) || + (hidquirks[x].lo_rev > info->idVersion) || + (hidquirks[x].hi_rev < info->idVersion)) { + continue; + } + /* see if quirk only should match vendor ID */ + if (hidquirks[x].pid != info->idProduct) { + if (hidquirks[x].pid != 0) + continue; + + for (y = 0; y != HID_SUB_QUIRKS_MAX; y++) { + if (hidquirks[x].quirks[y] == HQ_MATCH_VENDOR_ONLY) + break; + } + if (y == HID_SUB_QUIRKS_MAX) + continue; + } + /* lookup quirk */ + for (y = 0; y != HID_SUB_QUIRKS_MAX; y++) { + if (hidquirks[x].quirks[y] == quirk) { + mtx_unlock(&hidquirk_mtx); + DPRINTF("Found quirk '%s'.\n", hidquirkstr(quirk)); + return (true); + } + } + } + mtx_unlock(&hidquirk_mtx); +done: + return (false); /* no quirk match */ +} + +static struct hidquirk_entry * +hidquirk_get_entry(uint16_t bus, uint16_t vid, uint16_t pid, + uint16_t lo_rev, uint16_t hi_rev, uint8_t do_alloc) +{ + uint16_t x; + + mtx_assert(&hidquirk_mtx, MA_OWNED); + + if ((bus | vid | pid | lo_rev | hi_rev) == 0) { + /* all zero - special case */ + return (hidquirks + HID_DEV_QUIRKS_MAX - 1); + } + /* search for an existing entry */ + for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((hidquirks[x].bus != bus) || + (hidquirks[x].vid != vid) || + (hidquirks[x].pid != pid) || + (hidquirks[x].lo_rev != lo_rev) || + (hidquirks[x].hi_rev != hi_rev)) { + continue; + } + return (hidquirks + x); + } + + if (do_alloc == 0) { + /* no match */ + return (NULL); + } + /* search for a free entry */ + for (x = 0; x != HID_DEV_QUIRKS_MAX; x++) { + /* see if quirk information does not match */ + if ((hidquirks[x].bus | + hidquirks[x].vid | + hidquirks[x].pid | + hidquirks[x].lo_rev | + hidquirks[x].hi_rev) != 0) { + continue; + } + hidquirks[x].bus = bus; + hidquirks[x].vid = vid; + hidquirks[x].pid = pid; + hidquirks[x].lo_rev = lo_rev; + hidquirks[x].hi_rev = hi_rev; + + return (hidquirks + x); + } + + /* no entry found */ + return (NULL); +} + +/*------------------------------------------------------------------------* + * usb_quirk_strtou16 + * + * Helper function to scan a 16-bit integer. + *------------------------------------------------------------------------*/ +static uint16_t +hidquirk_strtou16(const char **pptr, const char *name, const char *what) +{ + unsigned long value; + char *end; + + value = strtoul(*pptr, &end, 0); + if (value > 65535 || *pptr == end || (*end != ' ' && *end != '\t')) { + printf("%s: %s 16-bit %s value set to zero\n", + name, what, *end == 0 ? "incomplete" : "invalid"); + return (0); + } + *pptr = end + 1; + return ((uint16_t)value); +} + +/*------------------------------------------------------------------------* + * usb_quirk_add_entry_from_str + * + * Add a USB quirk entry from string. + * "VENDOR PRODUCT LO_REV HI_REV QUIRK[,QUIRK[,...]]" + *------------------------------------------------------------------------*/ +static void +hidquirk_add_entry_from_str(const char *name, const char *env) +{ + struct hidquirk_entry entry = { }; + struct hidquirk_entry *new; + uint16_t quirk_idx; + uint16_t quirk; + const char *end; + + /* check for invalid environment variable */ + if (name == NULL || env == NULL) + return; + + if (bootverbose) + printf("Adding HID QUIRK '%s' = '%s'\n", name, env); + + /* parse device information */ + entry.bus = hidquirk_strtou16(&env, name, "Bus ID"); + entry.vid = hidquirk_strtou16(&env, name, "Vendor ID"); + entry.pid = hidquirk_strtou16(&env, name, "Product ID"); + entry.lo_rev = hidquirk_strtou16(&env, name, "Low revision"); + entry.hi_rev = hidquirk_strtou16(&env, name, "High revision"); + + /* parse quirk information */ + quirk_idx = 0; + while (*env != 0 && quirk_idx != HID_SUB_QUIRKS_MAX) { + /* skip whitespace before quirks */ + while (*env == ' ' || *env == '\t') + env++; + + /* look for quirk separation character */ + end = strchr(env, ','); + if (end == NULL) + end = env + strlen(env); + + /* lookup quirk in string table */ + quirk = hid_strquirk(env, end - env); + if (quirk < HID_QUIRK_MAX) { + entry.quirks[quirk_idx++] = quirk; + } else { + printf("%s: unknown HID quirk '%.*s' (skipped)\n", + name, (int)(end - env), env); + } + env = end; + + /* skip quirk delimiter, if any */ + if (*env != 0) + env++; + } + + /* register quirk */ + if (quirk_idx != 0) { + if (*env != 0) { + printf("%s: Too many HID quirks, only %d allowed!\n", + name, HID_SUB_QUIRKS_MAX); + } + mtx_lock(&hidquirk_mtx); + new = hidquirk_get_entry(entry.bus, entry.vid, entry.pid, + entry.lo_rev, entry.hi_rev, 1); + if (new == NULL) + printf("%s: HID quirks table is full!\n", name); + else + memcpy(new->quirks, entry.quirks, sizeof(entry.quirks)); + mtx_unlock(&hidquirk_mtx); + } else { + printf("%s: No USB quirks found!\n", name); + } +} + +static void +hidquirk_init(void *arg) +{ + char envkey[sizeof(HID_QUIRK_ENVROOT) + 2]; /* 2 digits max, 0 to 99 */ + int i; + + /* initialize mutex */ + mtx_init(&hidquirk_mtx, "HID quirk", NULL, MTX_DEF); + + /* look for quirks defined by the environment variable */ + for (i = 0; i != 100; i++) { + snprintf(envkey, sizeof(envkey), HID_QUIRK_ENVROOT "%d", i); + + /* Stop at first undefined var */ + if (!testenv(envkey)) + break; + + /* parse environment variable */ + hidquirk_add_entry_from_str(envkey, kern_getenv(envkey)); + } + + /* register our function */ + hid_test_quirk_p = &hid_test_quirk_by_info; +} + +static void +hidquirk_uninit(void *arg) +{ + hid_quirk_unload(arg); + + /* destroy mutex */ + mtx_destroy(&hidquirk_mtx); +} + +SYSINIT(hidquirk_init, SI_SUB_LOCK, SI_ORDER_FIRST, hidquirk_init, NULL); +SYSUNINIT(hidquirk_uninit, SI_SUB_LOCK, SI_ORDER_ANY, hidquirk_uninit, NULL); diff --git a/sys/dev/hid/hidraw.h b/sys/dev/hid/hidraw.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidraw.h @@ -0,0 +1,97 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $FreeBSD$ + */ + +#ifndef _HID_HIDRAW_H +#define _HID_HIDRAW_H + +#include + +#define HIDRAW_BUFFER_SIZE 64 /* number of input reports buffered */ +#define HID_MAX_DESCRIPTOR_SIZE 4096 /* artificial limit taken from Linux */ + +/* + * Align IOCTL structures to hide differences when running 32-bit + * programs under 64-bit kernels: + */ +#ifdef COMPAT_32BIT +#define HIDRAW_IOCTL_STRUCT_ALIGN(n) __aligned(n) +#else +#define HIDRAW_IOCTL_STRUCT_ALIGN(n) +#endif + +/* Compatible with usb_gen_descriptor structure */ +struct hidraw_gen_descriptor { +#ifdef COMPAT_32BIT + uint64_t hgd_data; +#else + void *hgd_data; +#endif + 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]; +} HIDRAW_IOCTL_STRUCT_ALIGN(8); + +struct hidraw_report_descriptor { + uint32_t size; + uint8_t value[HID_MAX_DESCRIPTOR_SIZE]; +} HIDRAW_IOCTL_STRUCT_ALIGN(4); + +struct hidraw_devinfo { + uint32_t bustype; + int16_t vendor; + int16_t product; +} HIDRAW_IOCTL_STRUCT_ALIGN(4); + +/* FreeBSD uhid(4)-compatible ioctl interface */ +#define HID_GET_REPORT_DESC _IOWR('U', 21, struct hidraw_gen_descriptor) +#define HID_SET_IMMED _IOW ('U', 22, int) +#define HID_GET_REPORT _IOWR('U', 23, struct hidraw_gen_descriptor) +#define HID_SET_REPORT _IOW ('U', 24, struct hidraw_gen_descriptor) +#define HID_GET_REPORT_ID _IOR ('U', 25, int) +#define HID_SET_REPORT_DESC _IOW ('U', 26, struct hidraw_gen_descriptor) + +/* Linux hidraw-compatible ioctl interface */ +#define HIDIOCGRDESCSIZE _IOR('U', 30, int) +#define HIDIOCGRDESC _IO ('U', 31) +#define HIDIOCGRAWINFO _IOR('U', 32, struct hidraw_devinfo) +#define HIDIOCGRAWNAME(len) _IOC(IOC_OUT, 'U', 33, len) +#define HIDIOCGRAWPHYS(len) _IOC(IOC_OUT, 'U', 34, len) +#define HIDIOCSFEATURE(len) _IOC(IOC_IN, 'U', 35, len) +#define HIDIOCGFEATURE(len) _IOC(IOC_INOUT, 'U', 36, len) +#define HIDIOCGRAWUNIQ(len) _IOC(IOC_OUT, 'U', 37, len) + +#endif /* _HID_HIDRAW_H */ diff --git a/sys/dev/hid/hidraw.c b/sys/dev/hid/hidraw.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidraw.c @@ -0,0 +1,909 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * 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 +#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; +}; + +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); + + 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); + + 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); +} + +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]; + void *buf; + struct hidraw_softc *sc; + struct hidraw_gen_descriptor *hgd; + struct hidraw_report_descriptor *hrd; + struct hidraw_devinfo *hdi; + uint32_t size; + int id, len; + int error = 0; + + DPRINTFN(2, "cmd=%lx\n", cmd); + + sc = dev->si_drv1; + if (sc == NULL) + return (EIO); + + /* 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 HID_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); + hgd = (struct hidraw_gen_descriptor *)addr; + if (sc->sc_rdesc->len > hgd->hgd_maxlen) { + size = hgd->hgd_maxlen; + } else { + size = sc->sc_rdesc->len; + } + hgd->hgd_actlen = size; + if (hgd->hgd_data == NULL) + return (0); /* descriptor length only */ + return (copyout(sc->sc_rdesc->data, hgd->hgd_data, size)); + + + case HID_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); + + hgd = (struct hidraw_gen_descriptor *)addr; + buf = HIDRAW_LOCAL_ALLOC(local_buf, hgd->hgd_maxlen); + copyin(hgd->hgd_data, buf, hgd->hgd_maxlen); + /* Lock newbus around set_report_descr call */ + mtx_lock(&Giant); + error = hid_set_report_descr(sc->sc_dev, buf, hgd->hgd_maxlen); + mtx_unlock(&Giant); + 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 HID_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 HID_GET_REPORT: + if (!(sc->sc_fflags & FREAD)) + return (EPERM); + hgd = (struct hidraw_gen_descriptor *)addr; + 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); + return (error); + + case HID_SET_REPORT: + if (!(sc->sc_fflags & FWRITE)) + return (EPERM); + hgd = (struct hidraw_gen_descriptor *)addr; + 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 HID_GET_REPORT_ID: + *(int *)addr = 0; /* XXX: we only support reportid 0? */ + return (0); + + case HIDIOCGRDESCSIZE: + *(int *)addr = sc->sc_rdesc->len; + 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); + return (0); + + case HIDIOCGRAWPHYS(0): + strlcpy(addr, device_get_nameunit(sc->sc_dev), 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); + 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) +}; + +#ifndef HIDRAW_MAKE_UHID_ALIAS +devclass_t hidraw_devclass; +#endif + +DRIVER_MODULE(hidraw, hidbus, hidraw_driver, hidraw_devclass, NULL, 0); +MODULE_DEPEND(hidraw, hidbus, 1, 1, 1); +MODULE_DEPEND(hidraw, hid, 1, 1, 1); +MODULE_DEPEND(hidraw, usb, 1, 1, 1); +MODULE_VERSION(hidraw, 1); diff --git a/sys/dev/hid/hidrdesc.h b/sys/dev/hid/hidrdesc.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidrdesc.h @@ -0,0 +1,370 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2000 Nick Hibma + * All rights reserved. + * + * Copyright (c) 2005 Ed Schouten + * 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. + * + * $FreeBSD$ + * + * This file contains replacements for broken HID report descriptors. + */ + +#define HID_GRAPHIRE_REPORT_DESCR(...) \ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + +#define HID_GRAPHIRE3_4X5_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x02, /* USAGE (Mouse) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + 0x85, 0x01, /* REPORT_ID (1) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x05, 0x09, /* USAGE_PAGE (Button) */\ + 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */\ + 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x75, 0x05, /* REPORT_SIZE (5) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x38, /* USAGE (Wheel) */\ + 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */\ + 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */\ + 0x75, 0x08, /* REPORT_SIZE (8) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x06, /* INPUT (Data,Var,Rel) */\ + 0xc0, /* END_COLLECTION */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x01, /* COLLECTION (Applicaption) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x01, /* USAGE (Digitizer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x09, 0x33, /* USAGE (Touch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x09, 0x44, /* USAGE (Barrel Switch) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x02, /* REPORT_COUNT (2) */\ + 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ + 0x09, 0x3c, /* USAGE (Invert) */\ + 0x09, 0x38, /* USAGE (Transducer Index) */\ + 0x09, 0x32, /* USAGE (In Range) */\ + 0x75, 0x01, /* REPORT_SIZE (1) */\ + 0x95, 0x03, /* REPORT_COUNT (3) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ + 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x30, /* USAGE (Tip Pressure) */\ + 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ + 0x75, 0x10, /* REPORT_SIZE (16) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ + 0xc0, /* END_COLLECTION */\ + 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x02, /* REPORT_ID (2) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0x09, 0x00, /* USAGE (Undefined) */\ + 0x85, 0x03, /* REPORT_ID (3) */\ + 0x95, 0x01, /* REPORT_COUNT (1) */\ + 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ + 0xc0 /* END_COLLECTION */\ + +/* + * The descriptor has no output report format, thus preventing you from + * controlling the LEDs and the built-in rumblers. + */ +#define HID_XB360GP_REPORT_DESCR(...) \ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x05, /* USAGE (Gamepad) */\ + 0xa1, 0x01, /* COLLECTION (Application) */\ + /* Unused */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Byte count */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x3b, /* USAGE (Byte Count) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* D-Pad */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x01, /* USAGE (Pointer) */\ + 0xa1, 0x00, /* COLLECTION (Physical) */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x90, /* USAGE (D-Pad Up) */\ + 0x09, 0x91, /* USAGE (D-Pad Down) */\ + 0x09, 0x93, /* USAGE (D-Pad Left) */\ + 0x09, 0x92, /* USAGE (D-Pad Right) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + 0xc0, /* END COLLECTION */\ + /* Buttons 5-11 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x07, /* REPORT COUNT (7) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x09, 0x08, /* USAGE (Button 8) */\ + 0x09, 0x07, /* USAGE (Button 7) */\ + 0x09, 0x09, /* USAGE (Button 9) */\ + 0x09, 0x0a, /* USAGE (Button 10) */\ + 0x09, 0x05, /* USAGE (Button 5) */\ + 0x09, 0x06, /* USAGE (Button 6) */\ + 0x09, 0x0b, /* USAGE (Button 11) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + /* Buttons 1-4 */\ + 0x75, 0x01, /* REPORT SIZE (1) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x09, /* USAGE PAGE (Button) */\ + 0x19, 0x01, /* USAGE MINIMUM (Button 1) */\ + 0x29, 0x04, /* USAGE MAXIMUM (Button 4) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Triggers */\ + 0x75, 0x08, /* REPORT SIZE (8) */\ + 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ + 0x26, 0xff, 0x00, /* LOGICAL MAXIMUM (255) */\ + 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ + 0x46, 0xff, 0x00, /* PHYSICAL MAXIMUM (255) */\ + 0x95, 0x02, /* REPORT SIZE (2) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x32, /* USAGE (Z) */\ + 0x09, 0x35, /* USAGE (Rz) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Sticks */\ + 0x75, 0x10, /* REPORT SIZE (16) */\ + 0x16, 0x00, 0x80, /* LOGICAL MINIMUM (-32768) */\ + 0x26, 0xff, 0x7f, /* LOGICAL MAXIMUM (32767) */\ + 0x36, 0x00, 0x80, /* PHYSICAL MINIMUM (-32768) */\ + 0x46, 0xff, 0x7f, /* PHYSICAL MAXIMUM (32767) */\ + 0x95, 0x04, /* REPORT COUNT (4) */\ + 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ + 0x09, 0x30, /* USAGE (X) */\ + 0x09, 0x31, /* USAGE (Y) */\ + 0x09, 0x33, /* USAGE (Rx) */\ + 0x09, 0x34, /* USAGE (Ry) */\ + 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ + /* Unused */\ + 0x75, 0x30, /* REPORT SIZE (48) */\ + 0x95, 0x01, /* REPORT COUNT (1) */\ + 0x81, 0x01, /* INPUT (Constant) */\ + 0xc0 /* END COLLECTION */\ + +/* Fixed report descriptor for Super Nintendo gamepads */ +#define HID_SNES_REPORT_DESCR(...) \ + 0x05, 0x01, /* Usage Page (Desktop), */\ + 0x09, 0x04, /* Usage (Joystik), */\ + 0xA1, 0x01, /* Collection (Application), */\ + 0xA1, 0x02, /* Collection (Logical), */\ + 0x14, /* Logical Minimum (0), */\ + 0x75, 0x08, /* Report Size (8), */\ + 0x95, 0x03, /* Report Count (3), */\ + 0x81, 0x01, /* Input (Constant), */\ + 0x26, 0xFF, 0x00, /* Logical Maximum (255), */\ + 0x95, 0x02, /* Report Count (2), */\ + 0x09, 0x30, /* Usage (X), */\ + 0x09, 0x31, /* Usage (Y), */\ + 0x81, 0x02, /* Input (Variable), */\ + 0x75, 0x01, /* Report Size (1), */\ + 0x95, 0x04, /* Report Count (4), */\ + 0x81, 0x01, /* Input (Constant), */\ + 0x25, 0x01, /* Logical Maximum (1), */\ + 0x95, 0x0A, /* Report Count (10), */\ + 0x05, 0x09, /* Usage Page (Button), */\ + 0x19, 0x01, /* Usage Minimum (01h), */\ + 0x29, 0x0A, /* Usage Maximum (0Ah), */\ + 0x81, 0x02, /* Input (Variable), */\ + 0x95, 0x0A, /* Report Count (10), */\ + 0x81, 0x01, /* Input (Constant), */\ + 0xC0, /* End Collection, */\ + 0xC0 /* End Collection */ + +/* HID mouse boot protocol descriptor */ +#define HID_MOUSE_BOOTPROTO_DESCR(...) \ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */\ + 0x09, 0x02, /* Usage (Mouse) */\ + 0xA1, 0x01, /* Collection (Application) */\ + 0x09, 0x01, /* Usage (Pointer) */\ + 0xA1, 0x00, /* Collection (Physical) */\ + 0x95, 0x03, /* Report Count (3) */\ + 0x75, 0x01, /* Report Size (1) */\ + 0x05, 0x09, /* Usage Page (Button) */\ + 0x19, 0x01, /* Usage Minimum (0x01) */\ + 0x29, 0x03, /* Usage Maximum (0x03) */\ + 0x15, 0x00, /* Logical Minimum (0) */\ + 0x25, 0x01, /* Logical Maximum (1) */\ + 0x81, 0x02, /* Input (Data,Var,Abs) */\ + 0x95, 0x01, /* Report Count (1) */\ + 0x75, 0x05, /* Report Size (5) */\ + 0x81, 0x03, /* Input (Const) */\ + 0x75, 0x08, /* Report Size (8) */\ + 0x95, 0x02, /* Report Count (2) */\ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */\ + 0x09, 0x30, /* Usage (X) */\ + 0x09, 0x31, /* Usage (Y) */\ + 0x15, 0x81, /* Logical Minimum (-127) */\ + 0x25, 0x7F, /* Logical Maximum (127) */\ + 0x81, 0x06, /* Input (Data,Var,Rel) */\ + 0xC0, /* End Collection */\ + 0xC0, /* End Collection */ + +/* HID keyboard boot protocol descriptor */ +#define HID_KBD_BOOTPROTO_DESCR(...) \ + 0x05, 0x01, /* Usage Page (Generic Desktop Ctrls) */\ + 0x09, 0x06, /* Usage (Keyboard) */\ + 0xA1, 0x01, /* Collection (Application) */\ + 0x05, 0x07, /* Usage Page (Kbrd/Keypad) */\ + 0x19, 0xE0, /* Usage Minimum (0xE0) */\ + 0x29, 0xE7, /* Usage Maximum (0xE7) */\ + 0x15, 0x00, /* Logical Minimum (0) */\ + 0x25, 0x01, /* Logical Maximum (1) */\ + 0x75, 0x01, /* Report Size (1) */\ + 0x95, 0x08, /* Report Count (8) */\ + 0x81, 0x02, /* Input (Data,Var,Abs) */\ + 0x95, 0x01, /* Report Count (1) */\ + 0x75, 0x08, /* Report Size (8) */\ + 0x81, 0x01, /* Input (Const,Array,Abs) */\ + 0x95, 0x03, /* Report Count (3) */\ + 0x75, 0x01, /* Report Size (1) */\ + 0x05, 0x08, /* Usage Page (LEDs) */\ + 0x19, 0x01, /* Usage Minimum (Num Lock) */\ + 0x29, 0x03, /* Usage Maximum (Scroll Lock) */\ + 0x91, 0x02, /* Output (Data,Var,Abs) */\ + 0x95, 0x05, /* Report Count (5) */\ + 0x75, 0x01, /* Report Size (1) */\ + 0x91, 0x01, /* Output (Const,Array,Abs) */\ + 0x95, 0x06, /* Report Count (6) */\ + 0x75, 0x08, /* Report Size (8) */\ + 0x15, 0x00, /* Logical Minimum (0) */\ + 0x26, 0xFF, 0x00, /* Logical Maximum (255) */\ + 0x05, 0x07, /* Usage Page (Kbrd/Keypad) */\ + 0x19, 0x00, /* Usage Minimum (0x00) */\ + 0x2A, 0xFF, 0x00, /* Usage Maximum (0xFF) */\ + 0x81, 0x00, /* Input (Data,Array,Abs) */\ + 0xC0, /* End Collection */ diff --git a/sys/dev/hid/hkbd.c b/sys/dev/hid/hkbd.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hkbd.c @@ -0,0 +1,2009 @@ +#include +__FBSDID("$FreeBSD$"); + +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * 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_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 + +#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 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); \ + ((map)[(key) / 64] & (1ULL << ((key) % 64))); \ +}) + +#define MOD_EJECT 0x01 +#define MOD_FN 0x02 + +#define MOD_MIN 0xe0 +#define MOD_MAX 0xe7 + +struct hkbd_data { + uint64_t bitmap[howmany(HKBD_NKEYCODE, 64)]; +}; + +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]; + uint64_t sc_loc_key_valid[howmany(HKBD_NKEYCODE, 64)]; + 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; + struct hkbd_data sc_ndata; + struct hkbd_data sc_odata; + + 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 uint64_t *); +static int hkbd_key2scan(struct hkbd_softc *, int, const uint64_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) +{ + bool ret = false; + unsigned i; + + for (i = 0; i != howmany(HKBD_NKEYCODE, 64); i++) + ret |= (sc->sc_odata.bitmap[i] != 0); + return (ret); +} + +static bool +hkbd_any_key_valid(struct hkbd_softc *sc) +{ + bool ret = false; + unsigned i; + + for (i = 0; i != howmany(HKBD_NKEYCODE, 64); i++) + ret |= (sc->sc_loc_key_valid[i] != 0); + return (ret); +} + +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)); +#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); + + /* 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. Modifier keys down + * 2. Regular keys up/down + * 3. Modifier keys up + * + * This allows devices which send events changing the state of + * both a modifier key and a regular key, to be correctly + * translated. */ + for (key = MOD_MIN; key <= MOD_MAX; key++) { + const uint64_t mask = 1ULL << (key % 64); + + if (!(sc->sc_odata.bitmap[key / 64] & mask) && + (sc->sc_ndata.bitmap[key / 64] & mask)) { + hkbd_put_key(sc, key | KEY_PRESS); + } + } + for (key = 0; key != HKBD_NKEYCODE; key++) { + const uint64_t mask = 1ULL << (key % 64); + const uint64_t delta = + sc->sc_odata.bitmap[key / 64] ^ + sc->sc_ndata.bitmap[key / 64]; + + if (hkbd_is_modifier_key(key)) + continue; + + if (mask == 1 && delta == 0) { + key += 63; + continue; /* skip empty areas */ + } else if (delta & mask) { + if (sc->sc_odata.bitmap[key / 64] & mask) { + hkbd_put_key(sc, key | KEY_RELEASE); + + /* clear repeating key, if any */ + if (sc->sc_repeat_key == key) + sc->sc_repeat_key = 0; + } else { + 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; + } + } + } + for (key = MOD_MIN; key <= MOD_MAX; key++) { + const uint64_t mask = 1ULL << (key % 64); + + if ((sc->sc_odata.bitmap[key / 64] & mask) && + !(sc->sc_ndata.bitmap[key / 64] & mask)) { + hkbd_put_key(sc, key | KEY_RELEASE); + } + } + + /* synchronize old data with new data */ + sc->sc_odata = sc->sc_ndata; + + /* 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); +#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; + + HKBD_LOCK_ASSERT(sc); + + sc->sc_time_ms += sc->sc_delay; + sc->sc_delay = 0; + + hkbd_interrupt(sc); + + /* 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; + int offset; + + 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 */ + memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); + + /* 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; + } + + for (i = 0; i != HKBD_NKEYCODE; i++) { + const uint64_t valid = sc->sc_loc_key_valid[i / 64]; + const uint64_t mask = 1ULL << (i % 64); + + if (mask == 1 && valid == 0) { + i += 63; + continue; /* skip empty areas */ + } else if (~valid & mask) { + continue; /* location is not valid */ + } else if (id != sc->sc_id_loc_key[i]) { + continue; /* invalid HID ID */ + } else if (i == 0) { + offset = sc->sc_loc_key[0].count; + if (offset < 0 || offset > len) + offset = len; + while (offset--) { + uint32_t key = + hid_get_data(buf + offset, len - offset, + &sc->sc_loc_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 */ + sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64); + } + } 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 */ + sc->sc_ndata.bitmap[key / 64] |= 1ULL << (key % 64); + } + } +#ifdef HID_DEBUG + DPRINTF("modifiers = 0x%04x\n", modifiers); + for (i = 0; i != HKBD_NKEYCODE; i++) { + const uint64_t valid = sc->sc_ndata.bitmap[i / 64]; + const uint64_t mask = 1ULL << (i % 64); + + if (valid & mask) + 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, sizeof(sc->sc_loc_key_valid)); + + /* 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 { + sc->sc_loc_key_valid[0] |= 1; + 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) { + sc->sc_loc_key_valid[key / 64] |= + 1ULL << (key % 64); + 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_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_epoch(evdev, HIDBUS_EPOCH)) + 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); + + 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); + 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); + + /* release all leftover keys, if any */ + memset(&sc->sc_ndata, 0, sizeof(sc->sc_ndata)); + + /* process releasing of all keys */ + HKBD_LOCK(sc); + hkbd_interrupt(sc); + 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.bitmap); + if (keycode == NN) { + return -1; + } + return (hkbd_key2scan(sc, keycode, sc->sc_ndata.bitmap, + (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.bitmap); + 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.bitmap, + (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; + 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; + } + KBD_LED_VAL(kbd) = *(int *)arg; + if (KBD_HAS_DEVICE(kbd)) + return (hkbd_set_leds(sc, i)); + 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(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) + evdev_push_repeats(sc->sc_evdev, kbd); +#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 OPIO_KEYMAP: /* set keyboard translation table + * (compat) */ + case PIO_KEYMAPENT: /* set keyboard translation table + * entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + 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, sizeof(sc->sc_ndata)); + memset(&sc->sc_odata, 0, sizeof(sc->sc_odata)); + 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_data_unsigned(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_data_unsigned(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_data_unsigned(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); + +#ifdef EVDEV_SUPPORT + if (sc->sc_evdev != NULL) + evdev_push_leds(sc->sc_evdev, leds); +#endif + + /* 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 + static const int delays[] = {250, 500, 750, 1000}; + static const int rates[] = {34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504}; + + if (code & ~0x7f) { + return (EINVAL); + } + kbd->kb_delay1 = delays[(code >> 5) & 3]; + kbd->kb_delay2 = rates[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 uint64_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 uint64_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 devclass_t hkbd_devclass; + +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_devclass, hkbd_driver_load, 0); +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/hms.c b/sys/dev/hid/hms.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hms.c @@ -0,0 +1,267 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +static const uint8_t hms_boot_desc[] = { HID_MOUSE_BOOTPROTO_DESCR() }; + +enum { + HMS_REL_X, + HMS_REL_Y, + HMS_REL_Z, + HMS_ABS_X, + HMS_ABS_Y, + HMS_ABS_Z, + HMS_HWHEEL, + HMS_BTN, + HMS_BTN_MS1, + HMS_BTN_MS2, + HMS_FINAL_CB, +}; + +static hidmap_cb_t hms_final_cb; + +#define HMS_MAP_BUT_RG(usage_from, usage_to, code) \ + { HIDMAP_KEY_RANGE(HUP_BUTTON, usage_from, usage_to, code) } +#define HMS_MAP_BUT_MS(usage, code) \ + { HIDMAP_KEY(HUP_MICROSOFT, usage, code) } +#define HMS_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, usage, code) } +#define HMS_MAP_REL(usage, code) \ + { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code) } +#define HMS_MAP_REL_REV(usage, code) \ + { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code), .invert_value = true } +#define HMS_MAP_REL_CN(usage, code) \ + { HIDMAP_REL(HUP_CONSUMER, usage, code) } +#define HMS_FINAL_CB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +static const struct hidmap_item hms_map[] = { + [HMS_REL_X] = HMS_MAP_REL(HUG_X, REL_X), + [HMS_REL_Y] = HMS_MAP_REL(HUG_Y, REL_Y), + [HMS_REL_Z] = HMS_MAP_REL(HUG_Z, REL_Z), + [HMS_ABS_X] = HMS_MAP_ABS(HUG_X, ABS_X), + [HMS_ABS_Y] = HMS_MAP_ABS(HUG_Y, ABS_Y), + [HMS_ABS_Z] = HMS_MAP_ABS(HUG_Z, ABS_Z), + [HMS_HWHEEL] = HMS_MAP_REL_CN(HUC_AC_PAN, REL_HWHEEL), + [HMS_BTN] = HMS_MAP_BUT_RG(1, 16, BTN_MOUSE), + [HMS_BTN_MS1] = HMS_MAP_BUT_MS(1, BTN_RIGHT), + [HMS_BTN_MS2] = HMS_MAP_BUT_MS(2, BTN_MIDDLE), + [HMS_FINAL_CB] = HMS_FINAL_CB(hms_final_cb), +}; + +static const struct hidmap_item hms_map_wheel[] = { + HMS_MAP_REL(HUG_WHEEL, REL_WHEEL), +}; +static const struct hidmap_item hms_map_wheel_rev[] = { + HMS_MAP_REL_REV(HUG_WHEEL, REL_WHEEL), +}; + +/* A match on these entries will load hms */ +static const struct hid_device_id hms_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE) }, +}; + +struct hms_softc { + struct hidmap hm; + HIDMAP_CAPS(caps, hms_map); +}; + +static int +hms_final_cb(HIDMAP_CB_ARGS) +{ + struct hms_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) { + if (hidmap_test_cap(sc->caps, HMS_ABS_X) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + else + evdev_support_prop(evdev, INPUT_PROP_POINTER); + } + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static void +hms_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + void *d_ptr; + hid_size_t d_len; + int error; + + /* + * If device claimed boot protocol support but do not have report + * descriptor, load one defined in "Appendix B.2" of HID1_11.pdf + */ + error = hid_get_report_descr(parent, &d_ptr, &d_len); + if ((error != 0 && hid_test_quirk(hw, HQ_HAS_MS_BOOTPROTO)) || + (error == 0 && hid_test_quirk(hw, HQ_MS_BOOTPROTO) && + hid_is_mouse(d_ptr, d_len))) + (void)hid_set_report_descr(parent, hms_boot_desc, + sizeof(hms_boot_desc)); +} + +static int +hms_probe(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + int error; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hms_devs); + if (error != 0) + return (error); + + hidmap_set_dev(&sc->hm, dev); + + /* Check if report descriptor belongs to mouse */ + error = HIDMAP_ADD_MAP(&sc->hm, hms_map, sc->caps); + if (error != 0) + return (error); + + /* There should be at least one X or Y axis */ + if (!hidmap_test_cap(sc->caps, HMS_REL_X) && + !hidmap_test_cap(sc->caps, HMS_REL_X) && + !hidmap_test_cap(sc->caps, HMS_ABS_X) && + !hidmap_test_cap(sc->caps, HMS_ABS_Y)) + return (ENXIO); + + if (hidmap_test_cap(sc->caps, HMS_ABS_X) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) + hidbus_set_desc(dev, "Tablet"); + else + hidbus_set_desc(dev, "Mouse"); + + return (BUS_PROBE_DEFAULT); +} + +static int +hms_attach(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hidmap_hid_item *hi; + HIDMAP_CAPS(cap_wheel, hms_map_wheel); + void *d_ptr; + hid_size_t d_len; + bool set_report_proto; + int error, nbuttons = 0; + + /* + * Set the report (non-boot) protocol if report descriptor has not been + * overloaded with boot protocol report descriptor. + * + * Mice without boot protocol support may choose not to implement + * Set_Protocol at all; Ignore any error. + */ + error = hid_get_report_descr(dev, &d_ptr, &d_len); + set_report_proto = !(error == 0 && d_len == sizeof(hms_boot_desc) && + memcmp(d_ptr, hms_boot_desc, sizeof(hms_boot_desc)) == 0); + (void)hid_set_protocol(dev, set_report_proto ? 1 : 0); + + if (hid_test_quirk(hw, HQ_MS_REVZ)) + HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel_rev, cap_wheel); + else + HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel, cap_wheel); + + error = hidmap_attach(&sc->hm); + if (error) + return (error); + + /* Count number of input usages of variable type mapped to buttons */ + for (hi = sc->hm.hid_items; + hi < sc->hm.hid_items + sc->hm.nhid_items; + hi++) + if (hi->type == HIDMAP_TYPE_VARIABLE && hi->evtype == EV_KEY) + nbuttons++; + + /* announce information about the mouse */ + device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n", + nbuttons, + (hidmap_test_cap(sc->caps, HMS_REL_X) || + hidmap_test_cap(sc->caps, HMS_ABS_X)) ? "X" : "", + (hidmap_test_cap(sc->caps, HMS_REL_Y) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) ? "Y" : "", + (hidmap_test_cap(sc->caps, HMS_REL_Z) || + hidmap_test_cap(sc->caps, HMS_ABS_Z)) ? "Z" : "", + hidmap_test_cap(cap_wheel, 0) ? "W" : "", + hidmap_test_cap(sc->caps, HMS_HWHEEL) ? "H" : "", + sc->hm.hid_items[0].id); + + return (0); +} + +static int +hms_detach(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t hms_devclass; +static device_method_t hms_methods[] = { + DEVMETHOD(device_identify, hms_identify), + DEVMETHOD(device_probe, hms_probe), + DEVMETHOD(device_attach, hms_attach), + DEVMETHOD(device_detach, hms_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hms, hms_driver, hms_methods, sizeof(struct hms_softc)); +DRIVER_MODULE(hms, hidbus, hms_driver, hms_devclass, NULL, 0); +MODULE_DEPEND(hms, hid, 1, 1, 1); +MODULE_DEPEND(hms, hidbus, 1, 1, 1); +MODULE_DEPEND(hms, hidmap, 1, 1, 1); +MODULE_DEPEND(hms, evdev, 1, 1, 1); +MODULE_VERSION(hms, 1); +HID_PNP_INFO(hms_devs); diff --git a/sys/dev/hid/hmt.c b/sys/dev/hid/hmt.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hmt.c @@ -0,0 +1,911 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * 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 +#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, +#define HMT_SLOT HMT_TIP_SWITCH + HMT_WIDTH, +#define HMT_MAJOR HMT_WIDTH + HMT_HEIGHT, +#define HMT_MINOR HMT_HEIGHT + HMT_ORIENTATION, + HMT_X, + HMT_Y, + HMT_CONTACTID, + HMT_PRESSURE, + HMT_IN_RANGE, + HMT_CONFIDENCE, + HMT_TOOL_X, + HMT_TOOL_Y, + HMT_N_USAGES, +}; + +#define HMT_NO_CODE (ABS_MAX + 10) +#define HMT_NO_USAGE -1 + +struct hmt_hid_map_item { + char name[5]; + int32_t usage; /* HID usage */ + uint32_t code; /* Evdev event code */ + bool required; /* Required for MT Digitizers */ +}; + +static const struct hmt_hid_map_item hmt_hid_map[HMT_N_USAGES] = { + [HMT_TIP_SWITCH] = { /* HMT_SLOT */ + .name = "TIP", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_SWITCH), + .code = ABS_MT_SLOT, + .required = true, + }, + [HMT_WIDTH] = { /* HMT_MAJOR */ + .name = "WDTH", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_WIDTH), + .code = ABS_MT_TOUCH_MAJOR, + .required = false, + }, + [HMT_HEIGHT] = { /* HMT_MINOR */ + .name = "HGHT", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_HEIGHT), + .code = ABS_MT_TOUCH_MINOR, + .required = false, + }, + [HMT_ORIENTATION] = { + .name = "ORIE", + .usage = HMT_NO_USAGE, + .code = ABS_MT_ORIENTATION, + .required = false, + }, + [HMT_X] = { + .name = "X", + .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + .code = ABS_MT_POSITION_X, + .required = true, + }, + [HMT_Y] = { + .name = "Y", + .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + .code = ABS_MT_POSITION_Y, + .required = true, + }, + [HMT_CONTACTID] = { + .name = "C_ID", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONTACTID), + .code = ABS_MT_TRACKING_ID, + .required = true, + }, + [HMT_PRESSURE] = { + .name = "PRES", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_TIP_PRESSURE), + .code = ABS_MT_PRESSURE, + .required = false, + }, + [HMT_IN_RANGE] = { + .name = "RANG", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_IN_RANGE), + .code = ABS_MT_DISTANCE, + .required = false, + }, + [HMT_CONFIDENCE] = { + .name = "CONF", + .usage = HID_USAGE2(HUP_DIGITIZERS, HUD_CONFIDENCE), + .code = HMT_NO_CODE, + .required = false, + }, + [HMT_TOOL_X] = { /* Shares HID usage with HMT_X */ + .name = "TL_X", + .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X), + .code = ABS_MT_TOOL_X, + .required = false, + }, + [HMT_TOOL_Y] = { /* Shares HID usage with HMT_Y */ + .name = "TL_Y", + .usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y), + .code = ABS_MT_TOOL_Y, + .required = false, + }, +}; + +struct hmt_softc { + device_t dev; + enum hmt_type type; + + 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; + + uint32_t slot_data[HMT_N_USAGES]; + 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 is_clickpad; + bool do_timestamps; + bool iichid_sampling; + + 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))); +} + +static int +hmt_ev_open(struct evdev_dev *evdev) +{ + return (hidbus_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; + 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, NULL, + HID_FEATURE_REPORT, sc->cont_max_rid); + if (err == 0) { + 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->ai[HMT_SLOT].max = cont_count_max - 1; + } else + DPRINTF("hid_get_report error=%d\n", err); + } else + DPRINTF("Feature report %hhu size invalid: %u\n", + sc->cont_max_rid, sc->cont_max_rlen); + + /* 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, NULL, + HID_FEATURE_REPORT, sc->btn_type_rid); + } + if (sc->btn_type_rlen > 1) { + if (err == 0) + sc->is_clickpad = hid_get_udata(fbuf + 1, + sc->btn_type_rlen - 1, &sc->btn_type_loc) == 0; + else + DPRINTF("hid_get_report error=%d\n", err); + } + + /* 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->ai[HMT_SLOT].max >= MAX_MT_SLOTS) { + DPRINTF("Hardware reported %d contacts while only %d is " + "supported\n", (int)sc->ai[HMT_SLOT].max+1, MAX_MT_SLOTS); + sc->ai[HMT_SLOT].max = MAX_MT_SLOTS - 1; + } + + if (hid_test_quirk(hw, HQ_MT_TIMESTAMP) || hmt_timestamps) + sc->do_timestamps = true; + if (hid_test_quirk(hw, HQ_IICHID_SAMPLING)) + sc->iichid_sampling = true; + + 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); + 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); + } + if (sc->iichid_sampling) + evdev_set_flag(sc->evdev, EVDEV_FLAG_MT_AUTOREL); + 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++; + } + } + } + HMT_FOREACH_USAGE(sc->caps, i) { + if (hmt_hid_map[i].code != HMT_NO_CODE) + evdev_support_abs(sc->evdev, hmt_hid_map[i].code, + sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res); + } + + err = evdev_register_epoch(sc->evdev, HIDBUS_EPOCH); + if (err) { + hmt_detach(dev); + return (ENXIO); + } + + /* Announce information about the touch device */ + device_printf(sc->dev, "Multitouch %s with %d external button%s%s\n", + sc->type == HMT_TYPE_TOUCHSCREEN ? "touchscreen" : "touchpad", + nbuttons, nbuttons != 1 ? "s" : "", + sc->is_clickpad ? ", click-pad" : ""); + device_printf(sc->dev, + "%d contacts with [%s%s%s%s%s] properties. Report range [%d:%d] - [%d:%d]\n", + (int)sc->ai[HMT_SLOT].max + 1, + 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; + uint32_t *slot_data = sc->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; + int32_t slot; + uint32_t scan_time; + int32_t delta; + uint8_t id; + + MPASS(in_epoch(HIDBUS_EPOCH) == 1); + + /* + * 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; + for (slot = 0; slot <= sc->ai[HMT_SLOT].max; slot++) { + evdev_push_abs(sc->evdev, ABS_MT_SLOT, slot); + evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); + } + evdev_sync(sc->evdev); + return; + } + + /* 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 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. + */ + cont_count = hid_get_udata(buf, len, &sc->cont_count_loc); + /* + * "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++) { + bzero(slot_data, sizeof(sc->slot_data)); + HMT_FOREACH_USAGE(sc->caps, usage) { + if (sc->locs[cont][usage].size > 0) + slot_data[usage] = hid_get_udata( + buf, len, &sc->locs[cont][usage]); + } + + slot = evdev_get_mt_slot_by_tracking_id(sc->evdev, + slot_data[HMT_CONTACTID]); + +#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[usage]); + } + printf("slot = %d\n", (int)slot); + } +#endif + + if (slot == -1) { + DPRINTF("Slot overflow for contact_id %u\n", + (unsigned)slot_data[HMT_CONTACTID]); + continue; + } + + if (slot_data[HMT_TIP_SWITCH] != 0 && + !(isset(sc->caps, HMT_CONFIDENCE) && + slot_data[HMT_CONFIDENCE] == 0)) { + /* This finger is in proximity of the sensor */ + sc->touch = true; + slot_data[HMT_SLOT] = slot; + slot_data[HMT_IN_RANGE] = !slot_data[HMT_IN_RANGE]; + /* Divided by two to match visual scale of touch */ + width = slot_data[HMT_WIDTH] >> 1; + height = slot_data[HMT_HEIGHT] >> 1; + slot_data[HMT_ORIENTATION] = width > height; + slot_data[HMT_MAJOR] = MAX(width, height); + slot_data[HMT_MINOR] = MIN(width, height); + + HMT_FOREACH_USAGE(sc->caps, usage) + if (hmt_hid_map[usage].code != HMT_NO_CODE) + evdev_push_abs(sc->evdev, + hmt_hid_map[usage].code, + slot_data[usage]); + } else { + evdev_push_abs(sc->evdev, ABS_MT_SLOT, slot); + evdev_push_abs(sc->evdev, ABS_MT_TRACKING_ID, -1); + } + } + + 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).flags & (HIO_CONST|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; + } + /* Scan time is required but clobbered by evdev */ + 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 || !scan_time_found || 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; + + /* Set number of MT protocol type B slots */ + sc->ai[HMT_SLOT] = (struct hid_absinfo) { + .min = 0, + .max = cont_count_max - 1, + .res = 0, + }; + + /* 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->nconts_per_report = cont; + sc->has_int_button = has_int_button; + + 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; + + GIANT_REQUIRED; + + /* 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 Giant while sleeping */ + device_busy(hconf); + err = hconf_set_input_mode(hconf, mode); + device_unbusy(hconf); + + return (err); +} + +static devclass_t hmt_devclass; + +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, hmt_devclass, NULL, 0); +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/hpen.c b/sys/dev/hid/hpen.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hpen.c @@ -0,0 +1,256 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Vladimir Kondratyev + * Copyright (c) 2019 Greg V + * + * 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$"); + +/* + * Generic / MS Windows compatible HID pen tablet driver: + * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/required-hid-top-level-collections + * + * Tested on: Wacom WCOM50C1 (Google Pixelbook "eve") + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "usbdevs.h" + +static const uint8_t hpen_graphire_report_descr[] = + { HID_GRAPHIRE_REPORT_DESCR() }; +static const uint8_t hpen_graphire3_4x5_report_descr[] = + { HID_GRAPHIRE3_4X5_REPORT_DESCR() }; + +static hidmap_cb_t hpen_battery_strenght_cb; +static hidmap_cb_t hpen_final_digi_cb; +static hidmap_cb_t hpen_final_pen_cb; + +#define HPEN_MAP_BUT(usage, code) \ + HIDMAP_KEY(HUP_DIGITIZERS, HUD_##usage, code) +#define HPEN_MAP_ABS(usage, code) \ + HIDMAP_ABS(HUP_DIGITIZERS, HUD_##usage, code) +#define HPEN_MAP_ABS_GD(usage, code) \ + HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) +#define HPEN_MAP_ABS_CB(usage, cb) \ + HIDMAP_ABS_CB(HUP_DIGITIZERS, HUD_##usage, &cb) + +/* Generic map digitizer page map according to hut1_12v2.pdf */ +static const struct hidmap_item hpen_map_digi[] = { + { HPEN_MAP_ABS_GD(X, ABS_X), .required = true }, + { HPEN_MAP_ABS_GD(Y, ABS_Y), .required = true }, + { HPEN_MAP_ABS( TIP_PRESSURE, ABS_PRESSURE) }, + { HPEN_MAP_ABS( X_TILT, ABS_TILT_X) }, + { HPEN_MAP_ABS( Y_TILT, ABS_TILT_Y) }, + { HPEN_MAP_ABS_CB(BATTERY_STRENGTH, hpen_battery_strenght_cb) }, + { HPEN_MAP_BUT( TOUCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( TIP_SWITCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( SEC_TIP_SWITCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( IN_RANGE, BTN_TOOL_PEN) }, + { HPEN_MAP_BUT( BARREL_SWITCH, BTN_STYLUS) }, + { HPEN_MAP_BUT( INVERT, BTN_TOOL_RUBBER) }, + { HPEN_MAP_BUT( ERASER, BTN_TOUCH) }, + { HPEN_MAP_BUT( TABLET_PICK, BTN_STYLUS2) }, + { HPEN_MAP_BUT( SEC_BARREL_SWITCH,BTN_STYLUS2) }, + { HIDMAP_FINAL_CB( &hpen_final_digi_cb) }, +}; + +/* Microsoft-standardized pen support */ +static const struct hidmap_item hpen_map_pen[] = { + { HPEN_MAP_ABS_GD(X, ABS_X), .required = true }, + { HPEN_MAP_ABS_GD(Y, ABS_Y), .required = true }, + { HPEN_MAP_ABS( TIP_PRESSURE, ABS_PRESSURE), .required = true }, + { HPEN_MAP_ABS( X_TILT, ABS_TILT_X) }, + { HPEN_MAP_ABS( Y_TILT, ABS_TILT_Y) }, + { HPEN_MAP_ABS_CB(BATTERY_STRENGTH, hpen_battery_strenght_cb) }, + { HPEN_MAP_BUT( TIP_SWITCH, BTN_TOUCH), .required = true }, + { HPEN_MAP_BUT( IN_RANGE, BTN_TOOL_PEN), .required = true }, + { HPEN_MAP_BUT( BARREL_SWITCH, BTN_STYLUS) }, + { HPEN_MAP_BUT( INVERT, BTN_TOOL_RUBBER), .required = true }, + { HPEN_MAP_BUT( ERASER, BTN_TOUCH), .required = true }, + { HIDMAP_FINAL_CB( &hpen_final_pen_cb) }, +}; + +static const struct hid_device_id hpen_devs[] = { + { HID_TLC(HUP_DIGITIZERS, HUD_DIGITIZER) }, + { HID_TLC(HUP_DIGITIZERS, HUD_PEN) }, +}; + +static int +hpen_battery_strenght_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t data; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_PWR); + /* TODO */ + break; + case HIDMAP_CB_IS_RUNNING: + data = ctx.data; + /* TODO */ + } + + return (0); +} + +static int +hpen_final_digi_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_POINTER); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +hpen_final_pen_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static void +hpen_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + + /* the report descriptor for the Wacom Graphire is broken */ + if (hw->idBus == BUS_USB && hw->idVendor == USB_VENDOR_WACOM) { + switch (hw->idProduct) { + case USB_PRODUCT_WACOM_GRAPHIRE: + hid_set_report_descr(parent, + hpen_graphire_report_descr, + sizeof(hpen_graphire_report_descr)); + break; + + case USB_PRODUCT_WACOM_GRAPHIRE3_4X5: + hid_set_report_descr(parent, + hpen_graphire3_4x5_report_descr, + sizeof(hpen_graphire3_4x5_report_descr)); + break; + } + } +} + +static int +hpen_probe(device_t dev) +{ + struct hidmap *hm = device_get_softc(dev); + int error; + bool is_pen; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hpen_devs); + if (error != 0) + return (error); + + hidmap_set_dev(hm, dev); + + /* Check if report descriptor belongs to a HID tablet device */ + is_pen = hidbus_get_usage(dev) == HID_USAGE2(HUP_DIGITIZERS, HUD_PEN); + error = is_pen + ? HIDMAP_ADD_MAP(hm, hpen_map_pen, NULL) + : HIDMAP_ADD_MAP(hm, hpen_map_digi, NULL); + if (error != 0) + return (error); + + hidbus_set_desc(dev, is_pen ? "Pen" : "Digitizer"); + + return (BUS_PROBE_DEFAULT); +} + +static int +hpen_attach(device_t dev) +{ + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hidmap *hm = device_get_softc(dev); + int error; + + if (hw->idBus == BUS_USB && hw->idVendor == USB_VENDOR_WACOM && + hw->idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { + /* + * The Graphire3 needs 0x0202 to be written to + * feature report ID 2 before it'll start + * returning digitizer data. + */ + static const uint8_t reportbuf[3] = {2, 2, 2}; + error = hid_set_report(dev, reportbuf, sizeof(reportbuf), + HID_FEATURE_REPORT, reportbuf[0]); + if (error) + device_printf(dev, "set feature report failed, " + "error=%d (ignored)\n", error); + } + + return (hidmap_attach(hm)); +} + +static int +hpen_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + + +static devclass_t hpen_devclass; +static device_method_t hpen_methods[] = { + DEVMETHOD(device_identify, hpen_identify), + DEVMETHOD(device_probe, hpen_probe), + DEVMETHOD(device_attach, hpen_attach), + DEVMETHOD(device_detach, hpen_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hpen, hpen_driver, hpen_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hpen, hidbus, hpen_driver, hpen_devclass, NULL, 0); +MODULE_DEPEND(hpen, hid, 1, 1, 1); +MODULE_DEPEND(hpen, hidbus, 1, 1, 1); +MODULE_DEPEND(hpen, hidmap, 1, 1, 1); +MODULE_DEPEND(hpen, evdev, 1, 1, 1); +MODULE_VERSION(hpen, 1); +HID_PNP_INFO(hpen_devs); diff --git a/sys/dev/hid/hsctrl.c b/sys/dev/hid/hsctrl.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hsctrl.c @@ -0,0 +1,110 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * General Desktop/System Controls usage page driver + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define HSCTRL_MAP(usage, code) \ + { HIDMAP_KEY(HUP_GENERIC_DESKTOP, HUG_SYSTEM_##usage, code) } + +static const struct hidmap_item hsctrl_map[] = { + HSCTRL_MAP(POWER_DOWN, KEY_POWER), + HSCTRL_MAP(SLEEP, KEY_SLEEP), + HSCTRL_MAP(WAKEUP, KEY_WAKEUP), + HSCTRL_MAP(CONTEXT_MENU, KEY_CONTEXT_MENU), + HSCTRL_MAP(MAIN_MENU, KEY_MENU), + HSCTRL_MAP(APP_MENU, KEY_PROG1), + HSCTRL_MAP(MENU_HELP, KEY_HELP), + HSCTRL_MAP(MENU_EXIT, KEY_EXIT), + HSCTRL_MAP(MENU_SELECT, KEY_SELECT), + HSCTRL_MAP(MENU_RIGHT, KEY_RIGHT), + HSCTRL_MAP(MENU_LEFT, KEY_LEFT), + HSCTRL_MAP(MENU_UP, KEY_UP), + HSCTRL_MAP(MENU_DOWN, KEY_DOWN), + HSCTRL_MAP(POWER_UP, KEY_POWER2), + HSCTRL_MAP(RESTART, KEY_RESTART), +}; + +static const struct hid_device_id hsctrl_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_SYSTEM_CONTROL) }, +}; + +static int +hsctrl_probe(device_t dev) +{ + return (HIDMAP_PROBE(device_get_softc(dev), dev, + hsctrl_devs, hsctrl_map, "System Control")); +} + +static int +hsctrl_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +hsctrl_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static devclass_t hsctrl_devclass; +static device_method_t hsctrl_methods[] = { + DEVMETHOD(device_probe, hsctrl_probe), + DEVMETHOD(device_attach, hsctrl_attach), + DEVMETHOD(device_detach, hsctrl_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hsctrl, hsctrl_driver, hsctrl_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hsctrl, hidbus, hsctrl_driver, hsctrl_devclass, NULL, 0); +MODULE_DEPEND(hsctrl, hid, 1, 1, 1); +MODULE_DEPEND(hsctrl, hidbus, 1, 1, 1); +MODULE_DEPEND(hsctrl, hidmap, 1, 1, 1); +MODULE_DEPEND(hsctrl, evdev, 1, 1, 1); +MODULE_VERSION(hsctrl, 1); +HID_PNP_INFO(hsctrl_devs); diff --git a/sys/dev/hid/ps4dshock.c b/sys/dev/hid/ps4dshock.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/ps4dshock.c @@ -0,0 +1,1437 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Sony PS4 DualShock 4 driver + * https://eleccelerator.com/wiki/index.php?title=DualShock_4 + * https://gist.github.com/johndrinkwater/7708901 + * https://www.psdevwiki.com/ps4/DS4-USB + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define HID_DEBUG_VAR ps4dshock_debug +#include +#include +#include +#include +#include "usbdevs.h" + +#ifdef HID_DEBUG +static int ps4dshock_debug = 1; + +static SYSCTL_NODE(_hw_hid, OID_AUTO, ps4dshock, CTLFLAG_RW, 0, + "Sony PS4 DualShock Gamepad"); +SYSCTL_INT(_hw_hid_ps4dshock, OID_AUTO, debug, CTLFLAG_RWTUN, + &ps4dshock_debug, 0, "Debug level"); +#endif + +#define PS4DS_NAME "Sony PS4 Dualshock 4" + +/* + * Hardware timestamp export is functional but as of May 2020 it does not + * fully supported by libinput. Disable it for now as it results in extra + * userland wakeups when touch state does not change between consecutive + * reports. Evdev tries to filter out such an events but ever changing + * timestamp interferes with that. + */ +/* #define PS4DSMTP_ENABLE_HW_TIMESTAMPS 1 */ + +static const uint8_t ps4dshock_rdesc[] = { + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x05, // Usage (Game Pad) + 0xA1, 0x01, // Collection (Application) + 0x85, 0x01, // Report ID (1) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x09, 0x33, // Usage (Rx) + 0x09, 0x34, // Usage (Ry) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x04, // Report Count (4) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x39, // Usage (Hat switch) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x07, // Logical Maximum (7) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x3B, 0x01, // Physical Maximum (315) + 0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x42, // Input (Data,Var,Abs,Null State) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0x05, 0x09, // Usage Page (Button) + 0x19, 0x01, // Usage Minimum (0x01) + 0x29, 0x0E, // Usage Maximum (0x0E) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x0E, // Report Count (14) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x20, // Usage (0x20) + 0x75, 0x06, // Report Size (6) + 0x95, 0x01, // Report Count (1) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x3F, // Logical Maximum (63) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x32, // Usage (Z) + 0x09, 0x35, // Usage (Rz) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs) + 0xC0, // End Collection + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x08, // Usage (Multi-axis Controller) + 0xA1, 0x01, // Collection (Application) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x21, // Usage (0x21) + 0x27, 0xFF, 0xFF, 0x00, 0x00, // Logical Maximum (65534) + 0x75, 0x10, // Report Size (16) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x06, // Usage Page (Generic Dev Ctrls) + 0x09, 0x20, // Usage (Battery Strength) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x19, 0x33, // Usage Minimum (RX) + 0x29, 0x35, // Usage Maximum (RZ) + 0x16, 0x00, 0x80, // Logical Minimum (-32768) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + 0x75, 0x10, // Report Size (16) + 0x95, 0x03, // Report Count (3) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x19, 0x30, // Usage Minimum (X) + 0x29, 0x32, // Usage Maximum (Z) + 0x16, 0x00, 0x80, // Logical Minimum (-32768) + 0x26, 0xFF, 0x7F, // Logical Maximum (32767) + 0x95, 0x03, // Report Count (3) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x21, // Usage (0x21) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x05, // Report Count (5) + 0x81, 0x03, // Input (Const) + 0xC0, // End Collection + 0x05, 0x0C, // Usage Page (Consumer) + 0x09, 0x05, // Usage (Headphone) + 0xA1, 0x01, // Collection (Application) + 0x75, 0x05, // Report Size (5) + 0x95, 0x01, // Report Count (1) + 0x81, 0x03, // Input (Const) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x20, // Usage (0x20) + 0x09, 0x21, // Usage (0x21) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x02, // Report Count (2) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x03, // Input (Const) + 0x75, 0x08, // Report Size (8) + 0x95, 0x02, // Report Count (2) + 0x81, 0x03, // Input (Const) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x05, // Usage (Touch Pad) + 0xA1, 0x01, // Collection (Application) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x21, // Usage (0x21) + 0x15, 0x00, // Logical Minimum (0) + 0x25, 0x03, // Logical Maximum (3) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x75, 0x04, // Report Size (4) + 0x95, 0x01, // Report Count (1) + 0x81, 0x03, // Input (Data,Var,Abs) + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x56, // Usage (0x56) + 0x55, 0x0C, // Unit Exponent (-4) + 0x66, 0x01, 0x10, // Unit (System: SI Linear, Time: Seconds) + 0x46, 0xCC, 0x06, // Physical Maximum (1740) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x56, // Usage (0x56) + 0x55, 0x0C, // Unit Exponent (-4) + 0x66, 0x01, 0x10, // Unit (System: SI Linear, Time: Seconds) + 0x46, 0xCC, 0x06, // Physical Maximum (1740) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x56, // Usage (0x56) + 0x55, 0x0C, // Unit Exponent (-4) + 0x66, 0x01, 0x10, // Unit (System: SI Linear, Time: Seconds) + 0x46, 0xCC, 0x06, // Physical Maximum (1740) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x75, 0x08, // Report Size (8) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x05, 0x0D, // Usage Page (Digitizer) + 0x09, 0x22, // Usage (Finger) + 0xA1, 0x02, // Collection (Logical) + 0x09, 0x51, // Usage (0x51) + 0x25, 0x7F, // Logical Maximum (127) + 0x75, 0x07, // Report Size (7) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x42, // Usage (Tip Switch) + 0x25, 0x01, // Logical Maximum (1) + 0x75, 0x01, // Report Size (1) + 0x95, 0x01, // Report Count (1) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x05, 0x01, // Usage Page (Generic Desktop Ctrls) + 0x09, 0x30, // Usage (X) + 0x55, 0x0E, // Unit Exponent (-2) + 0x65, 0x11, // Unit (System: SI Linear, Length: Centimeter) + 0x35, 0x00, // Physical Minimum (0) + 0x46, 0x80, 0x02, // Physical Maximum (640) + 0x26, 0x80, 0x07, // Logical Maximum (1920) + 0x75, 0x0C, // Report Size (12) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x09, 0x31, // Usage (Y) + 0x46, 0xC0, 0x00, // Physical Maximum (192) + 0x26, 0xAE, 0x03, // Logical Maximum (942) + 0x81, 0x02, // Input (Data,Var,Abs) + 0x65, 0x00, // Unit (None) + 0x45, 0x00, // Physical Maximum (0) + 0xC0, // End Collection + 0x75, 0x08, // Report Size (8) + 0x95, 0x03, // Report Count (3) + 0x81, 0x03, // Input (Const) + /* Output and feature reports */ + 0x85, 0x05, // Report ID (5) + 0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00) + 0x09, 0x22, // Usage (0x22) + 0x15, 0x00, // Logical Minimum (0) + 0x26, 0xFF, 0x00, // Logical Maximum (255) + 0x95, 0x1F, // Report Count (31) + 0x91, 0x02, // Output (Data,Var,Abs) + 0x85, 0x04, // Report ID (4) + 0x09, 0x23, // Usage (0x23) + 0x95, 0x24, // Report Count (36) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x02, // Report ID (2) + 0x09, 0x24, // Usage (0x24) + 0x95, 0x24, // Report Count (36) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x08, // Report ID (8) + 0x09, 0x25, // Usage (0x25) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x10, // Report ID (16) + 0x09, 0x26, // Usage (0x26) + 0x95, 0x04, // Report Count (4) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x11, // Report ID (17) + 0x09, 0x27, // Usage (0x27) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x12, // Report ID (18) + 0x06, 0x02, 0xFF, // Usage Page (Vendor Defined 0xFF02) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x0F, // Report Count (15) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x13, // Report ID (19) + 0x09, 0x22, // Usage (0x22) + 0x95, 0x16, // Report Count (22) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x14, // Report ID (20) + 0x06, 0x05, 0xFF, // Usage Page (Vendor Defined 0xFF05) + 0x09, 0x20, // Usage (0x20) + 0x95, 0x10, // Report Count (16) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x15, // Report ID (21) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x2C, // Report Count (44) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x06, 0x80, 0xFF, // Usage Page (Vendor Defined 0xFF80) + 0x85, 0x80, // Report ID (-128) + 0x09, 0x20, // Usage (0x20) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x81, // Report ID (-127) + 0x09, 0x21, // Usage (0x21) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x82, // Report ID (-126) + 0x09, 0x22, // Usage (0x22) + 0x95, 0x05, // Report Count (5) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x83, // Report ID (-125) + 0x09, 0x23, // Usage (0x23) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x84, // Report ID (-124) + 0x09, 0x24, // Usage (0x24) + 0x95, 0x04, // Report Count (4) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x85, // Report ID (-123) + 0x09, 0x25, // Usage (0x25) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x86, // Report ID (-122) + 0x09, 0x26, // Usage (0x26) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x87, // Report ID (-121) + 0x09, 0x27, // Usage (0x27) + 0x95, 0x23, // Report Count (35) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x88, // Report ID (-120) + 0x09, 0x28, // Usage (0x28) + 0x95, 0x22, // Report Count (34) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x89, // Report ID (-119) + 0x09, 0x29, // Usage (0x29) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x90, // Report ID (-112) + 0x09, 0x30, // Usage (0x30) + 0x95, 0x05, // Report Count (5) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x91, // Report ID (-111) + 0x09, 0x31, // Usage (0x31) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x92, // Report ID (-110) + 0x09, 0x32, // Usage (0x32) + 0x95, 0x03, // Report Count (3) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0x93, // Report ID (-109) + 0x09, 0x33, // Usage (0x33) + 0x95, 0x0C, // Report Count (12) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA0, // Report ID (-96) + 0x09, 0x40, // Usage (0x40) + 0x95, 0x06, // Report Count (6) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA1, // Report ID (-95) + 0x09, 0x41, // Usage (0x41) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA2, // Report ID (-94) + 0x09, 0x42, // Usage (0x42) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA3, // Report ID (-93) + 0x09, 0x43, // Usage (0x43) + 0x95, 0x30, // Report Count (48) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA4, // Report ID (-92) + 0x09, 0x44, // Usage (0x44) + 0x95, 0x0D, // Report Count (13) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA5, // Report ID (-91) + 0x09, 0x45, // Usage (0x45) + 0x95, 0x15, // Report Count (21) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA6, // Report ID (-90) + 0x09, 0x46, // Usage (0x46) + 0x95, 0x15, // Report Count (21) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xF0, // Report ID (-16) + 0x09, 0x47, // Usage (0x47) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xF1, // Report ID (-15) + 0x09, 0x48, // Usage (0x48) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xF2, // Report ID (-14) + 0x09, 0x49, // Usage (0x49) + 0x95, 0x0F, // Report Count (15) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA7, // Report ID (-89) + 0x09, 0x4A, // Usage (0x4A) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA8, // Report ID (-88) + 0x09, 0x4B, // Usage (0x4B) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xA9, // Report ID (-87) + 0x09, 0x4C, // Usage (0x4C) + 0x95, 0x08, // Report Count (8) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAA, // Report ID (-86) + 0x09, 0x4E, // Usage (0x4E) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAB, // Report ID (-85) + 0x09, 0x4F, // Usage (0x4F) + 0x95, 0x39, // Report Count (57) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAC, // Report ID (-84) + 0x09, 0x50, // Usage (0x50) + 0x95, 0x39, // Report Count (57) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAD, // Report ID (-83) + 0x09, 0x51, // Usage (0x51) + 0x95, 0x0B, // Report Count (11) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAE, // Report ID (-82) + 0x09, 0x52, // Usage (0x52) + 0x95, 0x01, // Report Count (1) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xAF, // Report ID (-81) + 0x09, 0x53, // Usage (0x53) + 0x95, 0x02, // Report Count (2) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0x85, 0xB0, // Report ID (-80) + 0x09, 0x54, // Usage (0x54) + 0x95, 0x3F, // Report Count (63) + 0xB1, 0x02, // Feature (Data,Var,Abs) + 0xC0, // End Collection +}; + +#define PS4DS_GYRO_RES_PER_DEG_S 1024 +#define PS4DS_ACC_RES_PER_G 8192 +#define PS4DS_MAX_TOUCHPAD_PACKETS 4 +#define PS4DS_FEATURE_REPORT2_SIZE 37 +#define PS4DS_OUTPUT_REPORT5_SIZE 32 +#define PS4DS_OUTPUT_REPORT11_SIZE 78 + +static hidmap_cb_t ps4dshock_hat_switch_cb; +static hidmap_cb_t ps4dshock_final_cb; +static hidmap_cb_t ps4dsacc_data_cb; +static hidmap_cb_t ps4dsacc_tstamp_cb; +static hidmap_cb_t ps4dsacc_final_cb; +static hidmap_cb_t ps4dsmtp_data_cb; +static hidmap_cb_t ps4dsmtp_npackets_cb; +static hidmap_cb_t ps4dsmtp_final_cb; + +struct ps4ds_out5 { + uint8_t features; + uint8_t reserved1; + uint8_t reserved2; + uint8_t rumble_right; + uint8_t rumble_left; + uint8_t led_color_r; + uint8_t led_color_g; + uint8_t led_color_b; + uint8_t led_delay_on; /* centiseconds */ + uint8_t led_delay_off; +} __attribute__((packed)); + +static const struct ps4ds_led { + int r; + int g; + int b; +} ps4ds_leds[] = { + /* The first 4 entries match the PS4, other from Linux driver */ + { 0x00, 0x00, 0x40 }, /* Blue */ + { 0x40, 0x00, 0x00 }, /* Red */ + { 0x00, 0x40, 0x00 }, /* Green */ + { 0x20, 0x00, 0x20 }, /* Pink */ + { 0x02, 0x01, 0x00 }, /* Orange */ + { 0x00, 0x01, 0x01 }, /* Teal */ + { 0x01, 0x01, 0x01 } /* White */ +}; + +enum ps4ds_led_state { + PS4DS_LED_OFF, + PS4DS_LED_ON, + PS4DS_LED_BLINKING, + PD4DS_LED_CNT, +}; + +/* Map structure for accelerometer and gyro. */ +struct ps4ds_calib_data { + int32_t usage; + int32_t code; + int32_t res; + int32_t range; + /* Calibration data for accelerometer and gyro. */ + int16_t bias; + int32_t sens_numer; + int32_t sens_denom; +}; + +enum { + PS4DS_TSTAMP, + PS4DS_CID1, + PS4DS_TIP1, + PS4DS_X1, + PS4DS_Y1, + PS4DS_CID2, + PS4DS_TIP2, + PS4DS_X2, + PS4DS_Y2, + PS4DS_NTPUSAGES, +}; + +struct ps4dshock_softc { + struct hidmap hm; + + bool is_bluetooth; + + struct sx lock; + enum ps4ds_led_state led_state; + struct ps4ds_led led_color; + int led_delay_on; /* msecs */ + int led_delay_off; + + int rumble_right; + int rumble_left; +}; + +struct ps4dsacc_softc { + struct hidmap hm; + + uint16_t hw_tstamp; + int32_t ev_tstamp; + + struct ps4ds_calib_data calib_data[6]; +}; + +struct ps4dsmtp_softc { + struct hidmap hm; + + struct hid_location btn_loc; + u_int npackets; + int32_t *data_ptr; + int32_t data[PS4DS_MAX_TOUCHPAD_PACKETS * PS4DS_NTPUSAGES]; + + bool do_tstamps; + uint8_t hw_tstamp; + int32_t ev_tstamp; + bool touch; +}; + +#define PD4DSHOCK_OFFSET(field) offsetof(struct ps4dshock_softc, field) +enum { + PD4DSHOCK_SYSCTL_LED_STATE = PD4DSHOCK_OFFSET(led_state), + PD4DSHOCK_SYSCTL_LED_COLOR_R = PD4DSHOCK_OFFSET(led_color.r), + PD4DSHOCK_SYSCTL_LED_COLOR_G = PD4DSHOCK_OFFSET(led_color.g), + PD4DSHOCK_SYSCTL_LED_COLOR_B = PD4DSHOCK_OFFSET(led_color.b), + PD4DSHOCK_SYSCTL_LED_DELAY_ON = PD4DSHOCK_OFFSET(led_delay_on), + PD4DSHOCK_SYSCTL_LED_DELAY_OFF= PD4DSHOCK_OFFSET(led_delay_off), +#define PD4DSHOCK_SYSCTL_LAST PD4DSHOCK_SYSCTL_LED_DELAY_OFF +}; + +#define PS4DS_MAP_BTN(number, code) \ + { HIDMAP_KEY(HUP_BUTTON, number, code) } +#define PS4DS_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } +#define PS4DS_MAP_FLT(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), .flat = 15 } +#define PS4DS_MAP_VSW(usage, code) \ + { HIDMAP_SW(HUP_MICROSOFT, usage, code) } +#define PS4DS_MAP_GCB(usage, callback) \ + { HIDMAP_ANY_CB(HUP_GENERIC_DESKTOP, HUG_##usage, callback) } +#define PS4DS_MAP_VCB(usage, callback) \ + { HIDMAP_ANY_CB(HUP_MICROSOFT, usage, callback) } +#define PS4DS_FINALCB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +static const struct hidmap_item ps4dshock_map[] = { + PS4DS_MAP_FLT(X, ABS_X), + PS4DS_MAP_FLT(Y, ABS_Y), + PS4DS_MAP_ABS(Z, ABS_Z), + PS4DS_MAP_FLT(RX, ABS_RX), + PS4DS_MAP_FLT(RY, ABS_RY), + PS4DS_MAP_ABS(RZ, ABS_RZ), + PS4DS_MAP_BTN(1, BTN_WEST), + PS4DS_MAP_BTN(2, BTN_SOUTH), + PS4DS_MAP_BTN(3, BTN_EAST), + PS4DS_MAP_BTN(4, BTN_NORTH), + PS4DS_MAP_BTN(5, BTN_TL), + PS4DS_MAP_BTN(6, BTN_TR), + PS4DS_MAP_BTN(7, BTN_TL2), + PS4DS_MAP_BTN(8, BTN_TR2), + PS4DS_MAP_BTN(9, BTN_SELECT), + PS4DS_MAP_BTN(10, BTN_START), + PS4DS_MAP_BTN(11, BTN_THUMBL), + PS4DS_MAP_BTN(12, BTN_THUMBR), + PS4DS_MAP_BTN(13, BTN_MODE), + /* Click button is handled by touchpad driver */ + /* PS4DS_MAP_BTN(14, BTN_LEFT), */ + PS4DS_MAP_GCB(HAT_SWITCH, ps4dshock_hat_switch_cb), + PS4DS_FINALCB( ps4dshock_final_cb), +}; +static const struct hidmap_item ps4dsacc_map[] = { + PS4DS_MAP_GCB(X, ps4dsacc_data_cb), + PS4DS_MAP_GCB(Y, ps4dsacc_data_cb), + PS4DS_MAP_GCB(Z, ps4dsacc_data_cb), + PS4DS_MAP_GCB(RX, ps4dsacc_data_cb), + PS4DS_MAP_GCB(RY, ps4dsacc_data_cb), + PS4DS_MAP_GCB(RZ, ps4dsacc_data_cb), + PS4DS_MAP_VCB(0x0021, ps4dsacc_tstamp_cb), + PS4DS_FINALCB( ps4dsacc_final_cb), +}; +static const struct hidmap_item ps4dshead_map[] = { + PS4DS_MAP_VSW(0x0020, SW_MICROPHONE_INSERT), + PS4DS_MAP_VSW(0x0021, SW_HEADPHONE_INSERT), +}; +static const struct hidmap_item ps4dsmtp_map[] = { + { HIDMAP_ABS_CB(HUP_MICROSOFT, 0x0021, ps4dsmtp_npackets_cb)}, + { HIDMAP_ABS_CB(HUP_DIGITIZERS, HUD_SCAN_TIME, ps4dsmtp_data_cb) }, + { HIDMAP_ABS_CB(HUP_DIGITIZERS, HUD_CONTACTID, ps4dsmtp_data_cb) }, + { HIDMAP_ABS_CB(HUP_DIGITIZERS, HUD_TIP_SWITCH, ps4dsmtp_data_cb) }, + { HIDMAP_ABS_CB(HUP_GENERIC_DESKTOP, HUG_X, ps4dsmtp_data_cb) }, + { HIDMAP_ABS_CB(HUP_GENERIC_DESKTOP, HUG_Y, ps4dsmtp_data_cb) }, + { HIDMAP_FINAL_CB( ps4dsmtp_final_cb) }, +}; + +static const struct hid_device_id ps4dshock_devs[] = { + { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x9cc), + HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD) }, +}; +static const struct hid_device_id ps4dsacc_devs[] = { + { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x9cc), + HID_TLC(HUP_GENERIC_DESKTOP, HUG_MULTIAXIS_CNTROLLER) }, +}; +static const struct hid_device_id ps4dshead_devs[] = { + { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x9cc), + HID_TLC(HUP_CONSUMER, HUC_HEADPHONE) }, +}; +static const struct hid_device_id ps4dsmtp_devs[] = { + { HID_BVP(BUS_USB, USB_VENDOR_SONY, 0x9cc), + HID_TLC(HUP_DIGITIZERS, HUD_TOUCHPAD) }, +}; + +static int +ps4dshock_hat_switch_cb(HIDMAP_CB_ARGS) +{ + static const struct { int32_t x; int32_t y; } hat_switch_map[] = { + {0, -1}, {1, -1}, {1, 0}, {1, 1}, {0, 1}, {-1, 1}, {-1, 0}, + {-1, -1},{0, 0} + }; + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + u_int idx; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_ABS); + evdev_support_abs(evdev, ABS_HAT0X, -1, 1, 0, 0, 0); + evdev_support_abs(evdev, ABS_HAT0Y, -1, 1, 0, 0, 0); + break; + + case HIDMAP_CB_IS_RUNNING: + idx = MIN(nitems(hat_switch_map) - 1, (u_int)ctx.data); + evdev_push_abs(evdev, ABS_HAT0X, hat_switch_map[idx].x); + evdev_push_abs(evdev, ABS_HAT0Y, hat_switch_map[idx].y); + } + + return (0); +} + +static int +ps4dshock_final_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +ps4dsacc_data_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + struct ps4dsacc_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct ps4ds_calib_data *calib; + u_int i; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + for (i = 0; i < nitems(sc->calib_data); i++) { + if (sc->calib_data[i].usage == ctx.hi->usage) { + evdev_support_abs(evdev, + sc->calib_data[i].code, + -sc->calib_data[i].range, + sc->calib_data[i].range, 16, 0, + sc->calib_data[i].res); + HIDMAP_CB_UDATA = &sc->calib_data[i]; + break; + } + } + break; + + case HIDMAP_CB_IS_RUNNING: + calib = HIDMAP_CB_UDATA; + evdev_push_abs(evdev, calib->code, + ((int64_t)ctx.data - calib->bias) * calib->sens_numer / + calib->sens_denom); + break; + } + + return (0); +} + +static int +ps4dsacc_tstamp_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + struct ps4dsacc_softc *sc = HIDMAP_CB_GET_SOFTC(); + uint16_t tstamp; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_MSC); + evdev_support_msc(evdev, MSC_TIMESTAMP); + break; + + case HIDMAP_CB_IS_RUNNING: + /* Convert timestamp (in 5.33us unit) to timestamp_us */ + tstamp = (uint16_t)ctx.data; + sc->ev_tstamp += (uint16_t)(tstamp - sc->hw_tstamp) * 16 / 3; + sc->hw_tstamp = tstamp; + evdev_push_msc(evdev, MSC_TIMESTAMP, sc->ev_tstamp); + break; + } + + return (0); +} + +static int +ps4dsacc_final_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) { + evdev_support_event(evdev, EV_ABS); + evdev_support_prop(evdev, INPUT_PROP_ACCELEROMETER); + } + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +ps4dsmtp_npackets_cb(HIDMAP_CB_ARGS) +{ + struct ps4dsmtp_softc *sc = HIDMAP_CB_GET_SOFTC(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_RUNNING) { + sc->npackets = MIN(PS4DS_MAX_TOUCHPAD_PACKETS,(u_int)ctx.data); + /* Reset pointer here as it is first usage in touchpad TLC */ + sc->data_ptr = sc->data; + } + + return (0); +} + +static int +ps4dsmtp_data_cb(HIDMAP_CB_ARGS) +{ + struct ps4dsmtp_softc *sc = HIDMAP_CB_GET_SOFTC(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_RUNNING) { + *sc->data_ptr = ctx.data; + ++sc->data_ptr; + } + + return (0); +} + +static void +ps4dsmtp_push_packet(struct ps4dsmtp_softc *sc, struct evdev_dev *evdev, + int32_t *data) +{ + uint8_t hw_tstamp, delta; + bool touch; + + evdev_push_abs(evdev, ABS_MT_SLOT, 0); + if (data[PS4DS_TIP1] == 0) { + evdev_push_abs(evdev, ABS_MT_TRACKING_ID, data[PS4DS_CID1]); + evdev_push_abs(evdev, ABS_MT_POSITION_X, data[PS4DS_X1]); + evdev_push_abs(evdev, ABS_MT_POSITION_Y, data[PS4DS_Y1]); + } else + evdev_push_abs(evdev, ABS_MT_TRACKING_ID, -1); + evdev_push_abs(evdev, ABS_MT_SLOT, 1); + if (data[PS4DS_TIP2] == 0) { + evdev_push_abs(evdev, ABS_MT_TRACKING_ID, data[PS4DS_CID2]); + evdev_push_abs(evdev, ABS_MT_POSITION_X, data[PS4DS_X2]); + evdev_push_abs(evdev, ABS_MT_POSITION_Y, data[PS4DS_Y2]); + } else + evdev_push_abs(evdev, ABS_MT_TRACKING_ID, -1); + + if (sc->do_tstamps) { + /* + * Export hardware timestamps in libinput-friendly way. + * Make timestamp counter 32-bit, scale up hardware + * timestamps to be on per 1usec basis and reset + * counter at the start of each touch. + */ + hw_tstamp = (uint8_t)data[PS4DS_TSTAMP]; + delta = hw_tstamp - sc->hw_tstamp; + sc->hw_tstamp = hw_tstamp; + touch = data[PS4DS_TIP1] == 0 || data[PS4DS_TIP2] == 0; + /* Hardware timestamp counter ticks in 682 usec interval. */ + if ((touch || sc->touch) && delta != 0) { + if (sc->touch) + sc->ev_tstamp += delta * 682; + evdev_push_msc(evdev, MSC_TIMESTAMP, sc->ev_tstamp); + } + if (!touch) + sc->ev_tstamp = 0; + sc->touch = touch; + } +} + +static int +ps4dsmtp_final_cb(HIDMAP_CB_ARGS) +{ + struct ps4dsmtp_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t *data; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + if (hid_test_quirk(hid_get_device_info(sc->hm.dev), + HQ_MT_TIMESTAMP)) + sc->do_tstamps = true; + /* + * Dualshock 4 touchpad TLC contained in fixed report + * descriptor is almost compatible with MS precission touchpad + * specs and hmt(4) driver. But... for some reasons "Click" + * button location was grouped with other GamePad buttons by + * touchpad designers so it belongs to GamePad TLC. Fix it with + * direct reading of "Click" button value from interrupt frame. + */ + sc->btn_loc = (struct hid_location) { 1, 0, 49 }; + evdev_support_event(evdev, EV_SYN); + evdev_support_event(evdev, EV_KEY); + evdev_support_event(evdev, EV_ABS); + if (sc->do_tstamps) { + evdev_support_event(evdev, EV_MSC); + evdev_support_msc(evdev, MSC_TIMESTAMP); + } + evdev_support_key(evdev, BTN_LEFT); + evdev_support_abs(evdev, ABS_MT_SLOT, 0, 1, 0, 0, 0); + evdev_support_abs(evdev, ABS_MT_TRACKING_ID, -1, 127, 0, 0, 0); + evdev_support_abs(evdev, ABS_MT_POSITION_X, 0, 1920, 0, 0, 30); + evdev_support_abs(evdev, ABS_MT_POSITION_Y, 0, 942, 0, 0, 49); + evdev_support_prop(evdev, INPUT_PROP_POINTER); + evdev_support_prop(evdev, INPUT_PROP_BUTTONPAD); + evdev_set_flag(evdev, EVDEV_FLAG_MT_STCOMPAT); + break; + + case HIDMAP_CB_IS_RUNNING: + /* Only packets with ReportID=1 are accepted */ + if (HIDMAP_CB_GET_RID() != 1) + return (ENOTSUP); + evdev_push_key(evdev, BTN_LEFT, + HIDMAP_CB_GET_UDATA(&sc->btn_loc)); + for (data = sc->data; + data < sc->data + PS4DS_NTPUSAGES * sc->npackets; + data += PS4DS_NTPUSAGES) { + ps4dsmtp_push_packet(sc, evdev, data); + evdev_sync(evdev); + } + break; + } + + /* Do execute callback at interrupt handler and detach */ + return (0); +} + +static int +ps4dshock_write(struct ps4dshock_softc *sc) +{ + hid_size_t osize = sc->is_bluetooth ? + PS4DS_OUTPUT_REPORT11_SIZE : PS4DS_OUTPUT_REPORT5_SIZE; + uint8_t buf[osize]; + int offset; + bool led_on, led_blinks; + + memset(buf, 0, osize); + buf[0] = sc->is_bluetooth ? 0x11 : 0x05; + offset = sc->is_bluetooth ? 3 : 1; + led_on = sc->led_state != PS4DS_LED_OFF; + led_blinks = sc->led_state == PS4DS_LED_BLINKING; + *(struct ps4ds_out5 *)(buf + offset) = (struct ps4ds_out5) { + .features = 0x07, /* blink + LEDs + motor */ + .rumble_right = sc->rumble_right, + .rumble_left = sc->rumble_left, + .led_color_r = led_on ? sc->led_color.r : 0, + .led_color_g = led_on ? sc->led_color.g : 0, + .led_color_b = led_on ? sc->led_color.b : 0, + /* convert milliseconds to centiseconds */ + .led_delay_on = led_blinks ? sc->led_delay_on / 10 : 0, + .led_delay_off = led_blinks ? sc->led_delay_off / 10 : 0, + }; + + /* + * The lower 6 bits of buf[1] field of the Bluetooth report + * control the interval at which Dualshock 4 reports data: + * 0x00 - 1ms + * 0x01 - 1ms + * 0x02 - 2ms + * 0x3E - 62ms + * 0x3F - disabled + */ +#if 0 + if (sc->sc->is_bluetooth) { + buf[1] = 0xC0 /* HID + CRC */ | sc->bt_poll_interval; + /* CRC generation */ + uint8_t bthdr = 0xA2; + uint32_t crc; + + crc = crc32_le(0xFFFFFFFF, &bthdr, 1); + crc = ~crc32_le(crc, buf, osize - 4); + put_unaligned_le32(crc, &buf[74]); + } +#endif + + return (hid_write(sc->hm.dev, buf, osize)); +} + +/* Synaptics Touchpad */ +static int +ps4dshock_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct ps4dshock_softc *sc; + int error, arg; + + if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 || + oidp->oid_arg2 > PD4DSHOCK_SYSCTL_LAST) + return (EINVAL); + + sc = oidp->oid_arg1; + sx_xlock(&sc->lock); + + /* Read the current value. */ + arg = *(int *)((char *)sc + oidp->oid_arg2); + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* Sanity check. */ + if (error || !req->newptr) + goto unlock; + + /* + * Check that the new value is in the concerned node's range + * of values. + */ + switch (oidp->oid_arg2) { + case PD4DSHOCK_SYSCTL_LED_STATE: + if (arg < 0 || arg >= PD4DS_LED_CNT) + error = EINVAL; + break; + case PD4DSHOCK_SYSCTL_LED_COLOR_R: + case PD4DSHOCK_SYSCTL_LED_COLOR_G: + case PD4DSHOCK_SYSCTL_LED_COLOR_B: + if (arg < 0 || arg > UINT8_MAX) + error = EINVAL; + break; + case PD4DSHOCK_SYSCTL_LED_DELAY_ON: + case PD4DSHOCK_SYSCTL_LED_DELAY_OFF: + if (arg < 0 || arg > UINT8_MAX * 10) + error = EINVAL; + break; + default: + error = EINVAL; + } + + /* Update. */ + if (error == 0) { + *(int *)((char *)sc + oidp->oid_arg2) = arg; + ps4dshock_write(sc); + } +unlock: + sx_unlock(&sc->lock); + + return (error); +} + +static void +ps4dshock_identify(driver_t *driver, device_t parent) +{ + + /* Overload PS4 DualShock gamepad rudimentary report descriptor */ + if (HIDBUS_LOOKUP_ID(parent, ps4dshock_devs) != NULL) + hid_set_report_descr(parent, ps4dshock_rdesc, + sizeof(ps4dshock_rdesc)); +} + +static int +ps4dshock_probe(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dshock_devs, ps4dshock_map, NULL) + ); +} + +static int +ps4dsacc_probe(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dsacc_devs, ps4dsacc_map, "Sensors") + ); +} + +static int +ps4dshead_probe(device_t dev) +{ + struct hidmap *hm = device_get_softc(dev); + + hidmap_set_debug_var(hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(hm, dev, ps4dshead_devs, ps4dshead_map, "Headset") + ); +} + +static int +ps4dsmtp_probe(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dsmtp_devs, ps4dsmtp_map, "Touchpad") + ); +} + +static int +ps4dshock_attach(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); + struct sysctl_oid *tree = device_get_sysctl_tree(dev); + + sc->led_state = PS4DS_LED_ON; + sc->led_color = ps4ds_leds[device_get_unit(dev) % nitems(ps4ds_leds)]; + sc->led_delay_on = 500; /* 1 Hz */ + sc->led_delay_off = 500; + ps4dshock_write(sc); + + sx_init(&sc->lock, "ps4dshock"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_state", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_STATE, ps4dshock_sysctl, "I", + "LED state: 0 - off, 1 - on, 2 - blinking."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_r", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_R, ps4dshock_sysctl, "I", + "LED color. Red component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_g", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_G, ps4dshock_sysctl, "I", + "LED color. Green component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_b", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_B, ps4dshock_sysctl, "I", + "LED color. Blue component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_delay_on", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_DELAY_ON, ps4dshock_sysctl, "I", + "LED blink. On delay, msecs."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_delay_off", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_DELAY_OFF, ps4dshock_sysctl, "I", + "LED blink. Off delay, msecs."); + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dsacc_attach(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + uint8_t buf[PS4DS_FEATURE_REPORT2_SIZE]; + int error, speed_2x, range_2g; + + /* Read accelerometers and gyroscopes calibration data */ + error = hid_get_report(dev, buf, sizeof(buf), NULL, + HID_FEATURE_REPORT, 0x02); + if (error) + DPRINTF("get feature report failed, error=%d " + "(ignored)\n", error); + + DPRINTFN(5, "calibration data: %*D\n", (int)sizeof(buf), buf, " "); + + /* + * Set gyroscope calibration and normalization parameters. + * Data values will be normalized to 1/ PS4DS_GYRO_RES_PER_DEG_S + * degree/s. + */ +#define HGETW(w) ((int16_t)((w)[0] | (((uint16_t)((w)[1])) << 8))) + speed_2x = HGETW(&buf[19]) + HGETW(&buf[21]); + sc->calib_data[0].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RX); + sc->calib_data[0].code = ABS_RX; + sc->calib_data[0].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[0].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[0].bias = HGETW(&buf[1]); + sc->calib_data[0].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[0].sens_denom = HGETW(&buf[7]) - HGETW(&buf[9]); + /* BT case */ + /* sc->calib_data[0].sens_denom = HGETW(&buf[7]) - HGETW(&buf[13]); */ + + sc->calib_data[1].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RY); + sc->calib_data[1].code = ABS_RY; + sc->calib_data[1].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[1].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[1].bias = HGETW(&buf[3]); + sc->calib_data[1].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[1].sens_denom = HGETW(&buf[11]) - HGETW(&buf[13]); + /* BT case */ + /* sc->calib_data[1].sens_denom = HGETW(&buf[9]) - HGETW(&buf[15]); */ + + sc->calib_data[2].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RZ); + sc->calib_data[2].code = ABS_RZ; + sc->calib_data[2].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[2].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[2].bias = HGETW(&buf[5]); + sc->calib_data[2].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[2].sens_denom = HGETW(&buf[15]) - HGETW(&buf[17]); + /* BT case */ + /* sc->calib_data[2].sens_denom = HGETW(&buf[11]) - HGETW(&buf[17]); */ + + /* + * Set accelerometer calibration and normalization parameters. + * Data values will be normalized to 1 / PS4DS_ACC_RES_PER_G G. + */ + range_2g = HGETW(&buf[23]) - HGETW(&buf[25]); + sc->calib_data[3].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X); + sc->calib_data[3].code = ABS_X; + sc->calib_data[3].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[3].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[3].bias = HGETW(&buf[23]) - range_2g / 2; + sc->calib_data[3].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[3].sens_denom = range_2g; + + range_2g = HGETW(&buf[27]) - HGETW(&buf[29]); + sc->calib_data[4].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y); + sc->calib_data[4].code = ABS_Y; + sc->calib_data[4].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[4].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[4].bias = HGETW(&buf[27]) - range_2g / 2; + sc->calib_data[4].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[4].sens_denom = range_2g; + + range_2g = HGETW(&buf[31]) - HGETW(&buf[33]); + sc->calib_data[5].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z); + sc->calib_data[5].code = ABS_Z; + sc->calib_data[5].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[5].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[5].bias = HGETW(&buf[31]) - range_2g / 2; + sc->calib_data[5].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[5].sens_denom = range_2g; + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dshead_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +ps4dsmtp_attach(device_t dev) +{ + struct ps4dsmtp_softc *sc = device_get_softc(dev); + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dshock_detach(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_detach(&sc->hm); + sc->led_state = PS4DS_LED_OFF; + ps4dshock_write(sc); + sx_destroy(&sc->lock); + + return (0); +} + +static int +ps4dsacc_detach(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static int +ps4dshead_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static int +ps4dsmtp_detach(device_t dev) +{ + struct ps4dsmtp_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t ps4dshock_devclass; +static devclass_t ps4dsacc_devclass; +static devclass_t ps4dshead_devclass; +static devclass_t ps4dsmtp_devclass; + +static device_method_t ps4dshock_methods[] = { + DEVMETHOD(device_identify, ps4dshock_identify), + DEVMETHOD(device_probe, ps4dshock_probe), + DEVMETHOD(device_attach, ps4dshock_attach), + DEVMETHOD(device_detach, ps4dshock_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dsacc_methods[] = { + DEVMETHOD(device_probe, ps4dsacc_probe), + DEVMETHOD(device_attach, ps4dsacc_attach), + DEVMETHOD(device_detach, ps4dsacc_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dshead_methods[] = { + DEVMETHOD(device_probe, ps4dshead_probe), + DEVMETHOD(device_attach, ps4dshead_attach), + DEVMETHOD(device_detach, ps4dshead_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dsmtp_methods[] = { + DEVMETHOD(device_probe, ps4dsmtp_probe), + DEVMETHOD(device_attach, ps4dsmtp_attach), + DEVMETHOD(device_detach, ps4dsmtp_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(ps4dsacc, ps4dsacc_driver, ps4dsacc_methods, + sizeof(struct ps4dsacc_softc)); +DRIVER_MODULE(ps4dsacc, hidbus, ps4dsacc_driver, ps4dsacc_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dshead, ps4dshead_driver, ps4dshead_methods, + sizeof(struct hidmap)); +DRIVER_MODULE(ps4dshead, hidbus, ps4dshead_driver, ps4dshead_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dsmtp, ps4dsmtp_driver, ps4dsmtp_methods, + sizeof(struct ps4dsmtp_softc)); +DRIVER_MODULE(ps4dsmtp, hidbus, ps4dsmtp_driver, ps4dsmtp_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dshock, ps4dshock_driver, ps4dshock_methods, + sizeof(struct ps4dshock_softc)); +DRIVER_MODULE(ps4dshock, hidbus, ps4dshock_driver, ps4dshock_devclass, NULL, 0); + +MODULE_DEPEND(ps4dshock, hid, 1, 1, 1); +MODULE_DEPEND(ps4dshock, hidbus, 1, 1, 1); +MODULE_DEPEND(ps4dshock, hidmap, 1, 1, 1); +MODULE_DEPEND(ps4dshock, evdev, 1, 1, 1); +MODULE_VERSION(ps4dshock, 1); +HID_PNP_INFO(ps4dshock_devs); diff --git a/sys/dev/hid/xb360gp.c b/sys/dev/hid/xb360gp.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/xb360gp.c @@ -0,0 +1,183 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * 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$"); + +/* + * XBox 360 gamepad driver thanks to the custom descriptor in usbhid. + * + * Tested on: SVEN GC-5070 in both XInput (XBox 360) and DirectInput modes + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static const uint8_t xb360gp_rdesc[] = {HID_XB360GP_REPORT_DESCR()}; + +#define XB360GP_MAP_BUT(number, code) \ + { HIDMAP_KEY(HUP_BUTTON, number, code) } +#define XB360GP_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } +#define XB360GP_MAP_ABS_FLT(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), \ + .fuzz = 16, .flat = 128 } +#define XB360GP_MAP_ABS_INV(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), \ + .fuzz = 16, .flat = 128, .invert_value = true } +#define XB360GP_MAP_CRG(usage_from, usage_to, callback) \ + { HIDMAP_ANY_CB_RANGE(HUP_GENERIC_DESKTOP, \ + HUG_##usage_from, HUG_##usage_to, callback) } +#define XB360GP_FINALCB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +/* Customized to match usbhid's XBox 360 descriptor */ +static const struct hidmap_item xb360gp_map[] = { + XB360GP_MAP_BUT(1, BTN_SOUTH), + XB360GP_MAP_BUT(2, BTN_EAST), + XB360GP_MAP_BUT(3, BTN_WEST), + XB360GP_MAP_BUT(4, BTN_NORTH), + XB360GP_MAP_BUT(5, BTN_TL), + XB360GP_MAP_BUT(6, BTN_TR), + XB360GP_MAP_BUT(7, BTN_SELECT), + XB360GP_MAP_BUT(8, BTN_START), + XB360GP_MAP_BUT(9, BTN_THUMBL), + XB360GP_MAP_BUT(10, BTN_THUMBR), + XB360GP_MAP_BUT(11, BTN_MODE), + XB360GP_MAP_CRG(D_PAD_UP, D_PAD_LEFT, hgame_dpad_cb), + XB360GP_MAP_ABS_FLT(X, ABS_X), + XB360GP_MAP_ABS_INV(Y, ABS_Y), + XB360GP_MAP_ABS(Z, ABS_Z), + XB360GP_MAP_ABS_FLT(RX, ABS_RX), + XB360GP_MAP_ABS_INV(RY, ABS_RY), + XB360GP_MAP_ABS(RZ, ABS_RZ), + XB360GP_FINALCB( hgame_final_cb), +}; + +static const STRUCT_USB_HOST_ID xb360gp_devs[] = { + /* the Xbox 360 gamepad doesn't use the HID class */ + {USB_IFACE_CLASS(UICLASS_VENDOR), + USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), + USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),}, +}; + +static void +xb360gp_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + + /* the Xbox 360 gamepad has no report descriptor */ + if (hid_test_quirk(hw, HQ_IS_XBOX360GP)) + hid_set_report_descr(parent, xb360gp_rdesc, + sizeof(xb360gp_rdesc)); +} + +static int +xb360gp_probe(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + const struct hid_device_info *hw = hid_get_device_info(dev); + int error; + + if (!hid_test_quirk(hw, HQ_IS_XBOX360GP)) + return (ENXIO); + + hidmap_set_dev(&sc->hm, dev); + + error = HIDMAP_ADD_MAP(&sc->hm, xb360gp_map, NULL); + if (error != 0) + return (error); + + device_set_desc(dev, "XBox 360 Gamepad"); + + return (BUS_PROBE_DEFAULT); +} + +static int +xb360gp_attach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + int error; + + /* + * Turn off the four LEDs on the gamepad which + * are blinking by default: + */ + static const uint8_t reportbuf[3] = {1, 3, 0}; + error = hid_set_report(dev, reportbuf, sizeof(reportbuf), + HID_OUTPUT_REPORT, 0); + if (error) + device_printf(dev, "set output report failed, error=%d " + "(ignored)\n", error); + + return (hidmap_attach(&sc->hm)); +} + +static int +xb360gp_detach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t xb360gp_devclass; +static device_method_t xb360gp_methods[] = { + DEVMETHOD(device_identify, xb360gp_identify), + DEVMETHOD(device_probe, xb360gp_probe), + DEVMETHOD(device_attach, xb360gp_attach), + DEVMETHOD(device_detach, xb360gp_detach), + DEVMETHOD_END +}; + +DEFINE_CLASS_0(xb360gp, xb360gp_driver, xb360gp_methods, + sizeof(struct hgame_softc)); +DRIVER_MODULE(xb360gp, hidbus, xb360gp_driver, xb360gp_devclass, NULL, 0); +MODULE_DEPEND(xb360gp, hid, 1, 1, 1); +MODULE_DEPEND(xb360gp, hidbus, 1, 1, 1); +MODULE_DEPEND(xb360gp, hidmap, 1, 1, 1); +MODULE_DEPEND(xb360gp, hgame, 1, 1, 1); +MODULE_DEPEND(xb360gp, evdev, 1, 1, 1); +MODULE_VERSION(xb360gp, 1); +USB_PNP_HOST_INFO(xb360gp_devs); diff --git a/sys/dev/iicbus/iichid.c b/sys/dev/iicbus/iichid.c new file mode 100644 --- /dev/null +++ b/sys/dev/iicbus/iichid.c @@ -0,0 +1,1252 @@ +/*- + * Copyright (c) 2018-2019 Marc Priggemeyer + * Copyright (c) 2019-2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * I2C HID transport backend. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include "opt_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include + +#include "hid_if.h" + +#ifdef IICHID_DEBUG +static int iichid_debug = 1; + +static SYSCTL_NODE(_hw, OID_AUTO, iichid, CTLFLAG_RW, 0, "I2C HID"); +SYSCTL_INT(_hw_iichid, OID_AUTO, debug, CTLFLAG_RWTUN, + &iichid_debug, 1, "Debug level"); + +#define DPRINTFN(sc, n, ...) do { \ + if (iichid_debug >= (n)) \ + device_printf((sc)->dev, __VA_ARGS__); \ +} while (0) +#define DPRINTF(sc, ...) DPRINTFN((sc), 1, __VA_ARGS__) +#else +#define DPRINTFN(...) +#define DPRINTF(...) +#endif + +typedef hid_size_t iichid_size_t; +#define IICHID_SIZE_MAX (UINT16_MAX - 2) + +/* 7.2 */ +enum { + I2C_HID_CMD_DESCR = 0x0, + I2C_HID_CMD_RESET = 0x1, + I2C_HID_CMD_GET_REPORT = 0x2, + I2C_HID_CMD_SET_REPORT = 0x3, + I2C_HID_CMD_GET_IDLE = 0x4, + I2C_HID_CMD_SET_IDLE = 0x5, + I2C_HID_CMD_GET_PROTO = 0x6, + I2C_HID_CMD_SET_PROTO = 0x7, + I2C_HID_CMD_SET_POWER = 0x8, +}; + +#define I2C_HID_POWER_ON 0x0 +#define I2C_HID_POWER_OFF 0x1 + +/* + * Since interrupt resource acquisition is not always possible (in case of GPIO + * interrupts) iichid now supports a sampling_mode. + * Set dev.iichid..sampling_rate_slow to a value greater then 0 + * to activate sampling. A value of 0 is possible but will not reset the + * callout and, thereby, disable further report requests. Do not set the + * sampling_rate_fast value too high as it may result in periodical lags of + * cursor motion. + */ +#define IICHID_SAMPLING_RATE_FAST 60 +#define IICHID_SAMPLING_RATE_SLOW 10 +#define IICHID_SAMPLING_HYSTERESIS 1 + +/* 5.1.1 - HID Descriptor Format */ +struct i2c_hid_desc { + uint16_t wHIDDescLength; + uint16_t bcdVersion; + uint16_t wReportDescLength; + uint16_t wReportDescRegister; + uint16_t wInputRegister; + uint16_t wMaxInputLength; + uint16_t wOutputRegister; + uint16_t wMaxOutputLength; + uint16_t wCommandRegister; + uint16_t wDataRegister; + uint16_t wVendorID; + uint16_t wProductID; + uint16_t wVersionID; + uint32_t reserved; +} __packed; + +static char *iichid_ids[] = { + "PNP0C50", + "ACPI0C50", + NULL +}; + +enum iichid_powerstate_how { + IICHID_PS_NULL, + IICHID_PS_ON, + IICHID_PS_OFF, +}; + +/* + * Locking: no internal locks are used. To serialize access to shared members, + * external iicbus lock should be taken. That allows to make locking greatly + * simple at the cost of running front interrupt handlers with locked bus. + */ +struct iichid_softc { + device_t dev; + + bool probe_done; + int probe_result; + + struct hid_device_info hw; + uint16_t addr; /* Shifted left by 1 */ + struct i2c_hid_desc desc; + + hid_intr_t *intr_handler; + void *intr_ctx; + uint8_t *intr_buf; + iichid_size_t intr_bufsize; + + int irq_rid; + struct resource *irq_res; + void *irq_cookie; + +#ifdef IICHID_SAMPLING + int sampling_rate_slow; /* iicbus lock */ + int sampling_rate_fast; + int sampling_hysteresis; + int missing_samples; /* iicbus lock */ + struct timeout_task periodic_task; /* iicbus lock */ + bool callout_setup; /* iicbus lock */ + struct taskqueue *taskqueue; + struct task event_task; +#endif + + bool open; /* iicbus lock */ + bool suspend; /* iicbus lock */ + bool power_on; /* iicbus lock */ +}; + +static device_probe_t iichid_probe; +static device_attach_t iichid_attach; +static device_detach_t iichid_detach; +static device_resume_t iichid_resume; +static device_suspend_t iichid_suspend; + +#ifdef IICHID_SAMPLING +static int iichid_setup_callout(struct iichid_softc *); +static int iichid_reset_callout(struct iichid_softc *); +static void iichid_teardown_callout(struct iichid_softc *); +#endif + +static __inline bool +acpi_is_iichid(ACPI_HANDLE handle) +{ + char **ids; + UINT32 sta; + + for (ids = iichid_ids; *ids != NULL; ids++) { + if (acpi_MatchHid(handle, *ids)) + break; + } + if (*ids == NULL) + return (false); + + /* + * If no _STA method or if it failed, then assume that + * the device is present. + */ + if (ACPI_FAILURE(acpi_GetInteger(handle, "_STA", &sta)) || + ACPI_DEVICE_PRESENT(sta)) + return (true); + + return (false); +} + +static ACPI_STATUS +iichid_get_config_reg(ACPI_HANDLE handle, uint16_t *config_reg) +{ + ACPI_OBJECT *result; + ACPI_BUFFER acpi_buf; + ACPI_STATUS status; + + /* + * function (_DSM) to be evaluated to retrieve the address of + * the configuration register of the HID device. + */ + /* 3cdff6f7-4267-4555-ad05-b30a3d8938de */ + static uint8_t dsm_guid[ACPI_UUID_LENGTH] = { + 0xF7, 0xF6, 0xDF, 0x3C, 0x67, 0x42, 0x55, 0x45, + 0xAD, 0x05, 0xB3, 0x0A, 0x3D, 0x89, 0x38, 0xDE, + }; + + status = acpi_EvaluateDSMTyped(handle, dsm_guid, 1, 1, NULL, &acpi_buf, + ACPI_TYPE_INTEGER); + if (ACPI_FAILURE(status)) { + printf("%s: error evaluating _DSM\n", __func__); + return (status); + } + result = (ACPI_OBJECT *) acpi_buf.Pointer; + *config_reg = result->Integer.Value & 0xFFFF; + + AcpiOsFree(result); + return (status); +} + +static int +iichid_cmd_read(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, + iichid_size_t *actual_len) +{ + /* + * 6.1.3 - Retrieval of Input Reports + * DEVICE returns the length (2 Bytes) and the entire Input Report. + */ + uint8_t actbuf[2] = { 0, 0 }; + /* Read actual input report length. */ + struct iic_msg msgs[] = { + { sc->addr, IIC_M_RD | IIC_M_NOSTOP, sizeof(actbuf), actbuf }, + }; + uint16_t actlen; + int error; + + error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); + if (error != 0) + return (error); + + actlen = actbuf[0] | actbuf[1] << 8; + if (actlen <= 2 || actlen == 0xFFFF || maxlen == 0) { + /* Read and discard 1 byte to send I2C STOP condition. */ + msgs[0] = (struct iic_msg) + { sc->addr, IIC_M_RD | IIC_M_NOSTART, 1, actbuf }; + actlen = 0; + } else { + actlen -= 2; + if (actlen > maxlen) { + DPRINTF(sc, "input report too big. requested=%d " + "received=%d\n", maxlen, actlen); + actlen = maxlen; + } + /* Read input report itself. */ + msgs[0] = (struct iic_msg) + { sc->addr, IIC_M_RD | IIC_M_NOSTART, actlen, buf }; + } + + error = iicbus_transfer(sc->dev, msgs, 1); + if (error == 0 && actual_len != NULL) + *actual_len = actlen; + + DPRINTFN(sc, 5, + "%*D - %*D\n", 2, actbuf, " ", msgs[0].len, msgs[0].buf, " "); + + return (error); +} + +static int +iichid_cmd_write(struct iichid_softc *sc, const void *buf, iichid_size_t len) +{ + /* 6.2.3 - Sending Output Reports. */ + uint8_t *cmdreg = (uint8_t *)&sc->desc.wOutputRegister; + uint16_t replen = 2 + len; + uint8_t cmd[4] = { cmdreg[0], cmdreg[1], replen & 0xFF, replen >> 8 }; + struct iic_msg msgs[] = { + {sc->addr, IIC_M_WR | IIC_M_NOSTOP, sizeof(cmd), cmd}, + {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, + }; + + if (le16toh(sc->desc.wMaxOutputLength) == 0) + return (IIC_ENOTSUPP); + if (len < 2) + return (IIC_ENOTSUPP); + + DPRINTF(sc, "HID command I2C_HID_CMD_WRITE (len %d): " + "%*D\n", len, len, buf, " "); + + return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); +} + +static int +iichid_cmd_get_hid_desc(struct iichid_softc *sc, uint16_t config_reg, + struct i2c_hid_desc *hid_desc) +{ + /* + * 5.2.2 - HID Descriptor Retrieval + * register is passed from the controller. + */ + uint16_t cmd = htole16(config_reg); + struct iic_msg msgs[] = { + { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, + { sc->addr, IIC_M_RD, sizeof(*hid_desc), (uint8_t *)hid_desc }, + }; + int error; + + DPRINTF(sc, "HID command I2C_HID_CMD_DESCR at 0x%x\n", config_reg); + + error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); + if (error != 0) + return (error); + + DPRINTF(sc, "HID descriptor: %*D\n", + (int)sizeof(struct i2c_hid_desc), hid_desc, " "); + + return (0); +} + +static int +iichid_set_power(struct iichid_softc *sc, uint8_t param) +{ + uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; + uint8_t cmd[] = { cmdreg[0], cmdreg[1], param, I2C_HID_CMD_SET_POWER }; + struct iic_msg msgs[] = { + { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, + }; + + DPRINTF(sc, "HID command I2C_HID_CMD_SET_POWER(%d)\n", param); + + return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); +} + +static int +iichid_reset(struct iichid_softc *sc) +{ + uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; + uint8_t cmd[] = { cmdreg[0], cmdreg[1], 0, I2C_HID_CMD_RESET }; + struct iic_msg msgs[] = { + { sc->addr, IIC_M_WR, sizeof(cmd), cmd }, + }; + + DPRINTF(sc, "HID command I2C_HID_CMD_RESET\n"); + + return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); +} + +static int +iichid_cmd_get_report_desc(struct iichid_softc* sc, void *buf, + iichid_size_t len) +{ + uint16_t cmd = sc->desc.wReportDescRegister; + struct iic_msg msgs[] = { + { sc->addr, IIC_M_WR | IIC_M_NOSTOP, 2, (uint8_t *)&cmd }, + { sc->addr, IIC_M_RD, len, buf }, + }; + int error; + + DPRINTF(sc, "HID command I2C_HID_REPORT_DESCR at 0x%x with size %d\n", + le16toh(cmd), len); + + error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); + if (error != 0) + return (error); + + DPRINTF(sc, "HID report descriptor: %*D\n", len, buf, " "); + + return (0); +} + +static int +iichid_cmd_get_report(struct iichid_softc* sc, void *buf, iichid_size_t maxlen, + iichid_size_t *actual_len, uint8_t type, uint8_t id) +{ + /* + * 7.2.2.4 - "The protocol is optimized for Report < 15. If a + * report ID >= 15 is necessary, then the Report ID in the Low Byte + * must be set to 1111 and a Third Byte is appended to the protocol. + * This Third Byte contains the entire/actual report ID." + */ + uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; + uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; + uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ + cmdreg[0] , + cmdreg[1] , + (id >= 15 ? 15 | (type << 4): id | (type << 4)), + I2C_HID_CMD_GET_REPORT , + (id >= 15 ? id : dtareg[0] ), + (id >= 15 ? dtareg[0] : dtareg[1] ), + (id >= 15 ? dtareg[1] : 0 ), + }; + int cmdlen = (id >= 15 ? 7 : 6 ); + uint8_t actbuf[2] = { 0, 0 }; + uint16_t actlen; + int d, error; + struct iic_msg msgs[] = { + { sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd }, + { sc->addr, IIC_M_RD | IIC_M_NOSTOP, 2, actbuf }, + { sc->addr, IIC_M_RD | IIC_M_NOSTART, maxlen, buf }, + }; + + if (maxlen == 0) + return (EINVAL); + + DPRINTF(sc, "HID command I2C_HID_CMD_GET_REPORT %d " + "(type %d, len %d)\n", id, type, maxlen); + + /* + * 7.2.2.2 - Response will be a 2-byte length value, the report + * id (1 byte, if defined in Report Descriptor), and then the report. + */ + error = iicbus_transfer(sc->dev, msgs, nitems(msgs)); + if (error != 0) + return (error); + + actlen = actbuf[0] | actbuf[1] << 8; + if (actlen != maxlen + 2) + DPRINTF(sc, "response size %d != expected length %d\n", + actlen, maxlen + 2); + + if (actlen <= 2 || actlen == 0xFFFF) + return (ENOMSG); + + d = id != 0 ? *(uint8_t *)buf : 0; + if (d != id) { + DPRINTF(sc, "response report id %d != %d\n", d, id); + return (EBADMSG); + } + + actlen -= 2; + if (actlen > maxlen) + actlen = maxlen; + if (actual_len != NULL) + *actual_len = actlen; + + DPRINTF(sc, "response: %*D %*D\n", 2, actbuf, " ", actlen, buf, " "); + + return (0); +} + +static int +iichid_cmd_set_report(struct iichid_softc* sc, const void *buf, + iichid_size_t len, uint8_t type, uint8_t id) +{ + /* + * 7.2.2.4 - "The protocol is optimized for Report < 15. If a + * report ID >= 15 is necessary, then the Report ID in the Low Byte + * must be set to 1111 and a Third Byte is appended to the protocol. + * This Third Byte contains the entire/actual report ID." + */ + uint8_t *dtareg = (uint8_t *)&sc->desc.wDataRegister; + uint8_t *cmdreg = (uint8_t *)&sc->desc.wCommandRegister; + uint16_t replen = 2 + len; + uint8_t cmd[] = { /*________|______id>=15_____|______id<15______*/ + cmdreg[0] , + cmdreg[1] , + (id >= 15 ? 15 | (type << 4): id | (type << 4)), + I2C_HID_CMD_SET_REPORT , + (id >= 15 ? id : dtareg[0] ), + (id >= 15 ? dtareg[0] : dtareg[1] ), + (id >= 15 ? dtareg[1] : replen & 0xff ), + (id >= 15 ? replen & 0xff : replen >> 8 ), + (id >= 15 ? replen >> 8 : 0 ), + }; + int cmdlen = (id >= 15 ? 9 : 8 ); + struct iic_msg msgs[] = { + {sc->addr, IIC_M_WR | IIC_M_NOSTOP, cmdlen, cmd}, + {sc->addr, IIC_M_WR | IIC_M_NOSTART, len, __DECONST(void *, buf)}, + }; + + DPRINTF(sc, "HID command I2C_HID_CMD_SET_REPORT %d (type %d, len %d): " + "%*D\n", id, type, len, len, buf, " "); + + return (iicbus_transfer(sc->dev, msgs, nitems(msgs))); +} + +#ifdef IICHID_SAMPLING +static void +iichid_event_task(void *context, int pending) +{ + struct iichid_softc *sc; + device_t parent; + iichid_size_t actual; + bool bus_requested; + int error; + + sc = context; + parent = device_get_parent(sc->dev); + + bus_requested = false; + if (iicbus_request_bus(parent, sc->dev, IIC_WAIT) != 0) + goto rearm; + bus_requested = true; + + if (!sc->power_on) + goto out; + + error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); + if (error == 0) { + if (actual > 0) { + sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); + sc->missing_samples = 0; + } else + ++sc->missing_samples; + } else + DPRINTF(sc, "read error occured: %d\n", error); + +rearm: + if (sc->callout_setup && sc->sampling_rate_slow > 0) { + if (sc->missing_samples == sc->sampling_hysteresis) + sc->intr_handler(sc->intr_ctx, sc->intr_buf, 0); + taskqueue_enqueue_timeout(sc->taskqueue, &sc->periodic_task, + hz / MAX(sc->missing_samples >= sc->sampling_hysteresis ? + sc->sampling_rate_slow : sc->sampling_rate_fast, 1)); + } +out: + if (bus_requested) + iicbus_release_bus(parent, sc->dev); +} +#endif /* IICHID_SAMPLING */ + +static void +iichid_intr(void *context) +{ + struct iichid_softc *sc; + device_t parent; + iichid_size_t maxlen, actual; + int error; + + sc = context; + parent = device_get_parent(sc->dev); + + /* + * Designware(IG4) driver-specific hack. + * Requesting of an I2C bus with IIC_DONTWAIT parameter enables polled + * mode in the driver, making possible iicbus_transfer execution from + * interrupt handlers and callouts. + */ + if (iicbus_request_bus(parent, sc->dev, IIC_DONTWAIT) != 0) + return; + + /* + * Reading of input reports of I2C devices residing in SLEEP state is + * not allowed and often returns a garbage. If a HOST needs to + * communicate with the DEVICE it MUST issue a SET POWER command + * (to ON) before any other command. As some hardware requires reads to + * acknoledge interrupts we fetch only length header and discard it. + */ + maxlen = sc->power_on ? sc->intr_bufsize : 0; + error = iichid_cmd_read(sc, sc->intr_buf, maxlen, &actual); + if (error == 0) { + if (sc->power_on) { + if (actual != 0) + sc->intr_handler(sc->intr_ctx, sc->intr_buf, + actual); + else + DPRINTF(sc, "no data received\n"); + } + } else + DPRINTF(sc, "read error occured: %d\n", error); + + iicbus_release_bus(parent, sc->dev); +} + +static int +iichid_set_power_state(struct iichid_softc *sc, + enum iichid_powerstate_how how_open, + enum iichid_powerstate_how how_suspend) +{ + device_t parent; + int error; + int how_request; + bool power_on; + + /* + * Request iicbus early as sc->suspend and sc->power_on + * are protected by iicbus internal lock. + */ + parent = device_get_parent(sc->dev); + /* Allow to interrupt open()/close() handlers by SIGINT */ + how_request = how_open == IICHID_PS_NULL ? IIC_WAIT : IIC_INTRWAIT; + error = iicbus_request_bus(parent, sc->dev, how_request); + if (error != 0) + return (error); + + switch (how_open) { + case IICHID_PS_ON: + sc->open = true; + break; + case IICHID_PS_OFF: + sc->open = false; + break; + case IICHID_PS_NULL: + default: + break; + } + + switch (how_suspend) { + case IICHID_PS_ON: + sc->suspend = false; + break; + case IICHID_PS_OFF: + sc->suspend = true; + break; + case IICHID_PS_NULL: + default: + break; + } + + power_on = sc->open & !sc->suspend; + + if (power_on != sc->power_on) { + error = iichid_set_power(sc, + power_on ? I2C_HID_POWER_ON : I2C_HID_POWER_OFF); + + sc->power_on = power_on; +#ifdef IICHID_SAMPLING + if (sc->sampling_rate_slow >= 0 && sc->intr_handler != NULL) { + if (power_on) { + iichid_setup_callout(sc); + iichid_reset_callout(sc); + } else + iichid_teardown_callout(sc); + } +#endif + } + + iicbus_release_bus(parent, sc->dev); + + return (error); +} + +static int +iichid_setup_interrupt(struct iichid_softc *sc) +{ + sc->irq_cookie = 0; + + int error = bus_setup_intr(sc->dev, sc->irq_res, + INTR_TYPE_TTY|INTR_MPSAFE, NULL, iichid_intr, sc, &sc->irq_cookie); + if (error != 0) + DPRINTF(sc, "Could not setup interrupt handler\n"); + else + DPRINTF(sc, "successfully setup interrupt\n"); + + return (error); +} + +static void +iichid_teardown_interrupt(struct iichid_softc *sc) +{ + if (sc->irq_cookie) + bus_teardown_intr(sc->dev, sc->irq_res, sc->irq_cookie); + + sc->irq_cookie = 0; +} + +#ifdef IICHID_SAMPLING +static int +iichid_setup_callout(struct iichid_softc *sc) +{ + + if (sc->sampling_rate_slow < 0) { + DPRINTF(sc, "sampling_rate is below 0, can't setup callout\n"); + return (EINVAL); + } + + sc->callout_setup = true; + DPRINTF(sc, "successfully setup callout\n"); + return (0); +} + +static int +iichid_reset_callout(struct iichid_softc *sc) +{ + + if (sc->sampling_rate_slow <= 0) { + DPRINTF(sc, "sampling_rate is below or equal to 0, " + "can't reset callout\n"); + return (EINVAL); + } + + if (!sc->callout_setup) + return (EINVAL); + + /* Start with slow sampling. */ + sc->missing_samples = sc->sampling_hysteresis; + taskqueue_enqueue(sc->taskqueue, &sc->event_task); + + return (0); +} + +static void +iichid_teardown_callout(struct iichid_softc *sc) +{ + + sc->callout_setup = false; + taskqueue_cancel_timeout(sc->taskqueue, &sc->periodic_task, NULL); + DPRINTF(sc, "tore callout down\n"); +} + +static int +iichid_sysctl_sampling_rate_handler(SYSCTL_HANDLER_ARGS) +{ + struct iichid_softc *sc; + device_t parent; + int error, oldval, value; + + sc = arg1; + + value = sc->sampling_rate_slow; + error = sysctl_handle_int(oidp, &value, 0, req); + + if (error != 0 || req->newptr == NULL || + value == sc->sampling_rate_slow) + return (error); + + /* Can't switch to interrupt mode if it is not supported. */ + if (sc->irq_res == NULL && value < 0) + return (EINVAL); + + parent = device_get_parent(sc->dev); + error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); + if (error != 0) + return (iic2errno(error)); + + oldval = sc->sampling_rate_slow; + sc->sampling_rate_slow = value; + + if (oldval < 0 && value >= 0) { + iichid_teardown_interrupt(sc); + if (sc->power_on) + iichid_setup_callout(sc); + } else if (oldval >= 0 && value < 0) { + if (sc->power_on) + iichid_teardown_callout(sc); + iichid_setup_interrupt(sc); + } + + if (sc->power_on && value > 0) + iichid_reset_callout(sc); + + iicbus_release_bus(parent, sc->dev); + + DPRINTF(sc, "new sampling_rate value: %d\n", value); + + return (0); +} +#endif /* IICHID_SAMPLING */ + +static void +iichid_intr_setup(device_t dev, hid_intr_t intr, void *context, + struct hid_rdesc_info *rdesc) +{ + struct iichid_softc *sc; + + sc = device_get_softc(dev); + /* + * Do not rely on wMaxInputLength, as some devices may set it to + * a wrong length. Find the longest input report in report descriptor. + */ + rdesc->rdsize = rdesc->isize; + /* Write and get/set_report sizes are limited by I2C-HID protocol. */ + rdesc->grsize = rdesc->srsize = IICHID_SIZE_MAX; + rdesc->wrsize = IICHID_SIZE_MAX; + + sc->intr_handler = intr; + sc->intr_ctx = context; + sc->intr_buf = malloc(rdesc->rdsize, M_DEVBUF, M_WAITOK | M_ZERO); + sc->intr_bufsize = rdesc->rdsize; +#ifdef IICHID_SAMPLING + taskqueue_start_threads(&sc->taskqueue, 1, PI_TTY, + "%s taskq", device_get_nameunit(sc->dev)); +#endif +} + +static void +iichid_intr_unsetup(device_t dev) +{ + struct iichid_softc *sc; + + sc = device_get_softc(dev); +#ifdef IICHID_SAMPLING + taskqueue_drain_all(sc->taskqueue); +#endif + free(sc->intr_buf, M_DEVBUF); +} + +static int +iichid_intr_start(device_t dev) +{ + struct iichid_softc *sc; + + sc = device_get_softc(dev); + DPRINTF(sc, "iichid device open\n"); + iichid_set_power_state(sc, IICHID_PS_ON, IICHID_PS_NULL); + + return (0); +} + +static int +iichid_intr_stop(device_t dev) +{ + struct iichid_softc *sc; + + sc = device_get_softc(dev); + DPRINTF(sc, "iichid device close\n"); + /* + * 8.2 - The HOST determines that there are no active applications + * that are currently using the specific HID DEVICE. The HOST + * is recommended to issue a HIPO command to the DEVICE to force + * the DEVICE in to a lower power state. + */ + iichid_set_power_state(sc, IICHID_PS_OFF, IICHID_PS_NULL); + + return (0); +} + +static void +iichid_intr_poll(device_t dev) +{ + struct iichid_softc *sc; + iichid_size_t actual; + int error; + + sc = device_get_softc(dev); + error = iichid_cmd_read(sc, sc->intr_buf, sc->intr_bufsize, &actual); + if (error == 0 && actual != 0) + sc->intr_handler(sc->intr_ctx, sc->intr_buf, actual); +} + +/* + * HID interface + */ +static int +iichid_get_rdesc(device_t dev, void *buf, hid_size_t len) +{ + struct iichid_softc *sc; + int error; + + sc = device_get_softc(dev); + error = iichid_cmd_get_report_desc(sc, buf, len); + if (error) + DPRINTF(sc, "failed to fetch report descriptor: %d\n", error); + + return (iic2errno(error)); +} + +static int +iichid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen) +{ + struct iichid_softc *sc; + device_t parent; + int error; + + if (maxlen > IICHID_SIZE_MAX) + return (EMSGSIZE); + sc = device_get_softc(dev); + parent = device_get_parent(sc->dev); + error = iicbus_request_bus(parent, sc->dev, IIC_WAIT); + if (error == 0) { + error = iichid_cmd_read(sc, buf, maxlen, actlen); + iicbus_release_bus(parent, sc->dev); + } + return (iic2errno(error)); +} + +static int +iichid_write(device_t dev, const void *buf, hid_size_t len) +{ + struct iichid_softc *sc; + + if (len > IICHID_SIZE_MAX) + return (EMSGSIZE); + sc = device_get_softc(dev); + return (iic2errno(iichid_cmd_write(sc, buf, len))); +} + +static int +iichid_get_report(device_t dev, void *buf, hid_size_t maxlen, + hid_size_t *actlen, uint8_t type, uint8_t id) +{ + struct iichid_softc *sc; + + if (maxlen > IICHID_SIZE_MAX) + return (EMSGSIZE); + sc = device_get_softc(dev); + return (iic2errno( + iichid_cmd_get_report(sc, buf, maxlen, actlen, type, id))); +} + +static int +iichid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type, + uint8_t id) +{ + struct iichid_softc *sc; + + if (len > IICHID_SIZE_MAX) + return (EMSGSIZE); + sc = device_get_softc(dev); + return (iic2errno(iichid_cmd_set_report(sc, buf, len, type, id))); +} + +static int +iichid_set_idle(device_t dev, uint16_t duration, uint8_t id) +{ + return (ENOTSUP); +} + +static int +iichid_set_protocol(device_t dev, uint16_t protocol) +{ + return (ENOTSUP); +} + +static int +iichid_fill_device_info(struct i2c_hid_desc *desc, ACPI_HANDLE handle, + struct hid_device_info *hw) +{ + ACPI_DEVICE_INFO *device_info; + + hw->idBus = BUS_I2C; + hw->idVendor = le16toh(desc->wVendorID); + hw->idProduct = le16toh(desc->wProductID); + hw->idVersion = le16toh(desc->wVersionID); + + /* get ACPI HID. It is a base part of the device name. */ + if (ACPI_FAILURE(AcpiGetObjectInfo(handle, &device_info))) + return (ENXIO); + + if (device_info->Valid & ACPI_VALID_HID) + strlcpy(hw->idPnP, device_info->HardwareId.String, + HID_PNP_ID_SIZE); + snprintf(hw->name, sizeof(hw->name), "%s:%02lX %04X:%04X", + (device_info->Valid & ACPI_VALID_HID) ? + device_info->HardwareId.String : "Unknown", + (device_info->Valid & ACPI_VALID_UID) ? + strtoul(device_info->UniqueId.String, NULL, 10) : 0UL, + le16toh(desc->wVendorID), le16toh(desc->wProductID)); + + AcpiOsFree(device_info); + + strlcpy(hw->serial, "", sizeof(hw->serial)); + hw->rdescsize = le16toh(desc->wReportDescLength); + if (desc->wOutputRegister == 0 || desc->wMaxOutputLength == 0) + hid_add_dynamic_quirk(hw, HQ_NOWRITE); + + return (0); +} + +static int +iichid_probe(device_t dev) +{ + struct iichid_softc *sc; + ACPI_HANDLE handle; + char buf[80]; + uint16_t config_reg; + int error; + + sc = device_get_softc(dev); + sc->dev = dev; + if (sc->probe_done) + goto done; + + sc->probe_done = true; + sc->probe_result = ENXIO; + + if (acpi_disabled("iichid")) + return (ENXIO); + + sc->addr = iicbus_get_addr(dev) << 1; + if (sc->addr == 0) + return (ENXIO); + + handle = acpi_get_handle(dev); + if (handle == NULL) + return (ENXIO); + + if (!acpi_is_iichid(handle)) + return (ENXIO); + + if (ACPI_FAILURE(iichid_get_config_reg(handle, &config_reg))) + return (ENXIO); + + DPRINTF(sc, " IICbus addr : 0x%02X\n", sc->addr >> 1); + DPRINTF(sc, " HID descriptor reg: 0x%02X\n", config_reg); + + error = iichid_cmd_get_hid_desc(sc, config_reg, &sc->desc); + if (error) { + DPRINTF(sc, "could not retrieve HID descriptor from the " + "device: %d\n", error); + return (ENXIO); + } + + if (le16toh(sc->desc.wHIDDescLength) != 30 || + le16toh(sc->desc.bcdVersion) != 0x100) { + DPRINTF(sc, "HID descriptor is broken\n"); + return (ENXIO); + } + + /* Setup hid_device_info so we can figure out quirks for the device. */ + if (iichid_fill_device_info(&sc->desc, handle, &sc->hw) != 0) { + DPRINTF(sc, "error evaluating AcpiGetObjectInfo\n"); + return (ENXIO); + } + + if (hid_test_quirk(&sc->hw, HQ_HID_IGNORE)) + return (ENXIO); + + sc->probe_result = BUS_PROBE_DEFAULT; +done: + if (sc->probe_result <= BUS_PROBE_SPECIFIC) { + snprintf(buf, sizeof(buf), "%s I2C HID device", sc->hw.name); + device_set_desc_copy(dev, buf); + } + return (sc->probe_result); +} + +static int +iichid_attach(device_t dev) +{ + struct iichid_softc *sc; + device_t child; + int error; + + sc = device_get_softc(dev); + error = iichid_set_power(sc, I2C_HID_POWER_ON); + if (error) { + device_printf(dev, "failed to power on: %d\n", error); + return (ENXIO); + } + /* + * Windows driver sleeps for 1ms between the SET_POWER and RESET + * commands. So we too as some devices may depend on this. + */ + pause("iichid", (hz + 999) / 1000); + + error = iichid_reset(sc); + if (error) { + device_printf(dev, "failed to reset hardware: %d\n", error); + return (ENXIO); + } + + sc->power_on = false; +#ifdef IICHID_SAMPLING + TASK_INIT(&sc->event_task, 0, iichid_event_task, sc); + /* taskqueue_create can't fail with M_WAITOK mflag passed. */ + sc->taskqueue = taskqueue_create("hmt_tq", M_WAITOK | M_ZERO, + taskqueue_thread_enqueue, &sc->taskqueue); + TIMEOUT_TASK_INIT(sc->taskqueue, &sc->periodic_task, 0, + iichid_event_task, sc); + + sc->sampling_rate_slow = -1; + sc->sampling_rate_fast = IICHID_SAMPLING_RATE_FAST; + sc->sampling_hysteresis = IICHID_SAMPLING_HYSTERESIS; +#endif + + sc->irq_rid = 0; + sc->irq_res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, + &sc->irq_rid, RF_ACTIVE); + + if (sc->irq_res != NULL) { + DPRINTF(sc, "allocated irq at %p and rid %d\n", + sc->irq_res, sc->irq_rid); + error = iichid_setup_interrupt(sc); + } + + if (sc->irq_res == NULL || error != 0) { +#ifdef IICHID_SAMPLING + device_printf(sc->dev, + "Interrupt setup failed. Fallback to sampling\n"); + sc->sampling_rate_slow = IICHID_SAMPLING_RATE_SLOW; +#else + device_printf(sc->dev, "Interrupt setup failed\n"); + if (sc->irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, + sc->irq_res); + error = ENXIO; + goto done; +#endif + } + +#ifdef IICHID_SAMPLING + SYSCTL_ADD_PROC(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), + OID_AUTO, "sampling_rate_slow", CTLTYPE_INT | CTLFLAG_RWTUN, + sc, 0, iichid_sysctl_sampling_rate_handler, "I", + "idle sampling rate in num/second"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), + OID_AUTO, "sampling_rate_fast", CTLTYPE_INT | CTLFLAG_RWTUN, + &sc->sampling_rate_fast, 0, + "active sampling rate in num/second"); + SYSCTL_ADD_INT(device_get_sysctl_ctx(sc->dev), + SYSCTL_CHILDREN(device_get_sysctl_tree(sc->dev)), + OID_AUTO, "sampling_hysteresis", CTLTYPE_INT | CTLFLAG_RWTUN, + &sc->sampling_hysteresis, 0, + "number of missing samples before enabling of slow mode"); + hid_add_dynamic_quirk(&sc->hw, HQ_IICHID_SAMPLING); +#endif /* IICHID_SAMPLING */ + + child = device_add_child(dev, "hidbus", -1); + if (child == NULL) { + device_printf(sc->dev, "Could not add I2C device\n"); + iichid_detach(dev); + error = ENOMEM; + goto done; + } + + device_set_ivars(child, &sc->hw); + error = bus_generic_attach(dev); + if (error) { + device_printf(dev, "failed to attach child: error %d\n", error); + iichid_detach(dev); + } +done: + (void)iichid_set_power(sc, I2C_HID_POWER_OFF); + return (error); +} + +static int +iichid_detach(device_t dev) +{ + struct iichid_softc *sc; + int error; + + sc = device_get_softc(dev); + error = device_delete_children(dev); + if (error) + return (error); + iichid_teardown_interrupt(sc); + if (sc->irq_res != NULL) + bus_release_resource(dev, SYS_RES_IRQ, sc->irq_rid, + sc->irq_res); +#ifdef IICHID_SAMPLING + if (sc->taskqueue != NULL) + taskqueue_free(sc->taskqueue); + sc->taskqueue = NULL; +#endif + return (0); +} + +static int +iichid_suspend(device_t dev) +{ + struct iichid_softc *sc; + int error; + + sc = device_get_softc(dev); + DPRINTF(sc, "Suspend called, setting device to power_state 1\n"); + (void)bus_generic_suspend(dev); + /* + * 8.2 - The HOST is going into a deep power optimized state and wishes + * to put all the devices into a low power state also. The HOST + * is recommended to issue a HIPO command to the DEVICE to force + * the DEVICE in to a lower power state. + */ + error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_OFF); + if (error != 0) + DPRINTF(sc, "Could not set power_state, error: %d\n", error); + else + DPRINTF(sc, "Successfully set power_state\n"); + + return (0); +} + +static int +iichid_resume(device_t dev) +{ + struct iichid_softc *sc; + int error; + + sc = device_get_softc(dev); + DPRINTF(sc, "Resume called, setting device to power_state 0\n"); + error = iichid_set_power_state(sc, IICHID_PS_NULL, IICHID_PS_ON); + if (error != 0) + DPRINTF(sc, "Could not set power_state, error: %d\n", error); + else + DPRINTF(sc, "Successfully set power_state\n"); + (void)bus_generic_resume(dev); + + return (0); +} + +static devclass_t iichid_devclass; + +static device_method_t iichid_methods[] = { + DEVMETHOD(device_probe, iichid_probe), + DEVMETHOD(device_attach, iichid_attach), + DEVMETHOD(device_detach, iichid_detach), + DEVMETHOD(device_suspend, iichid_suspend), + DEVMETHOD(device_resume, iichid_resume), + + DEVMETHOD(hid_intr_setup, iichid_intr_setup), + DEVMETHOD(hid_intr_unsetup, iichid_intr_unsetup), + DEVMETHOD(hid_intr_start, iichid_intr_start), + DEVMETHOD(hid_intr_stop, iichid_intr_stop), + DEVMETHOD(hid_intr_poll, iichid_intr_poll), + + /* HID interface */ + DEVMETHOD(hid_get_rdesc, iichid_get_rdesc), + DEVMETHOD(hid_read, iichid_read), + DEVMETHOD(hid_write, iichid_write), + DEVMETHOD(hid_get_report, iichid_get_report), + DEVMETHOD(hid_set_report, iichid_set_report), + DEVMETHOD(hid_set_idle, iichid_set_idle), + DEVMETHOD(hid_set_protocol, iichid_set_protocol), + + DEVMETHOD_END +}; + +static driver_t iichid_driver = { + .name = "iichid", + .methods = iichid_methods, + .size = sizeof(struct iichid_softc), +}; + +DRIVER_MODULE(iichid, iicbus, iichid_driver, iichid_devclass, NULL, 0); +MODULE_DEPEND(iichid, iicbus, IICBUS_MINVER, IICBUS_PREFVER, IICBUS_MAXVER); +MODULE_DEPEND(iichid, acpi, 1, 1, 1); +MODULE_DEPEND(iichid, hid, 1, 1, 1); +MODULE_DEPEND(iichid, hidbus, 1, 1, 1); +MODULE_VERSION(iichid, 1); +IICBUS_ACPI_PNP_INFO(iichid_ids); diff --git a/sys/dev/sound/usb/uaudio.c b/sys/dev/sound/usb/uaudio.c --- a/sys/dev/sound/usb/uaudio.c +++ b/sys/dev/sound/usb/uaudio.c @@ -6210,6 +6210,7 @@ DRIVER_MODULE_ORDERED(uaudio, uhub, uaudio_driver, uaudio_devclass, NULL, 0, SI_ORDER_ANY); MODULE_DEPEND(uaudio, usb, 1, 1, 1); MODULE_DEPEND(uaudio, sound, SOUND_MINVER, SOUND_PREFVER, SOUND_MAXVER); +MODULE_DEPEND(uaudio, hid, 1, 1, 1); MODULE_VERSION(uaudio, 1); USB_PNP_HOST_INFO(uaudio_devs); USB_PNP_HOST_INFO(uaudio_vendor_midi); diff --git a/sys/dev/usb/input/atp.c b/sys/dev/usb/input/atp.c --- a/sys/dev/usb/input/atp.c +++ b/sys/dev/usb/input/atp.c @@ -2633,6 +2633,7 @@ DRIVER_MODULE(atp, uhub, atp_driver, atp_devclass, NULL, 0); MODULE_DEPEND(atp, usb, 1, 1, 1); +MODULE_DEPEND(atp, hid, 1, 1, 1); MODULE_VERSION(atp, 1); USB_PNP_HOST_INFO(fg_devs); USB_PNP_HOST_INFO(wsp_devs); diff --git a/sys/dev/usb/input/uep.c b/sys/dev/usb/input/uep.c --- a/sys/dev/usb/input/uep.c +++ b/sys/dev/usb/input/uep.c @@ -381,8 +381,8 @@ evdev_support_event(sc->evdev, EV_ABS); evdev_support_event(sc->evdev, EV_KEY); evdev_support_key(sc->evdev, BTN_TOUCH); - evdev_support_abs(sc->evdev, ABS_X, 0, 0, UEP_MAX_X, 0, 0, 0); - evdev_support_abs(sc->evdev, ABS_Y, 0, 0, UEP_MAX_Y, 0, 0, 0); + evdev_support_abs(sc->evdev, ABS_X, 0, UEP_MAX_X, 0, 0, 0); + evdev_support_abs(sc->evdev, ABS_Y, 0, UEP_MAX_Y, 0, 0, 0); error = evdev_register_mtx(sc->evdev, &sc->mtx); if (error) { diff --git a/sys/dev/usb/input/uhid.c b/sys/dev/usb/input/uhid.c --- a/sys/dev/usb/input/uhid.c +++ b/sys/dev/usb/input/uhid.c @@ -43,6 +43,8 @@ * HID spec: http://www.usb.org/developers/devclass_docs/HID1_11.pdf */ +#include "opt_hid.h" + #include #include #include @@ -889,7 +891,9 @@ return (0); } +#ifndef HIDRAW_MAKE_UHID_ALIAS static devclass_t uhid_devclass; +#endif static device_method_t uhid_methods[] = { DEVMETHOD(device_probe, uhid_probe), @@ -900,12 +904,23 @@ }; static driver_t uhid_driver = { +#ifdef HIDRAW_MAKE_UHID_ALIAS + .name = "hidraw", +#else .name = "uhid", +#endif .methods = uhid_methods, .size = sizeof(struct uhid_softc), }; +#ifdef HIDRAW_MAKE_UHID_ALIAS +DRIVER_MODULE(uhid, uhub, uhid_driver, hidraw_devclass, NULL, 0); +#else DRIVER_MODULE(uhid, uhub, uhid_driver, uhid_devclass, NULL, 0); +#endif MODULE_DEPEND(uhid, usb, 1, 1, 1); +MODULE_DEPEND(uhid, hid, 1, 1, 1); MODULE_VERSION(uhid, 1); +#ifndef USBHID_ENABLED USB_PNP_HOST_INFO(uhid_devs); +#endif diff --git a/sys/dev/usb/input/ukbd.c b/sys/dev/usb/input/ukbd.c --- a/sys/dev/usb/input/ukbd.c +++ b/sys/dev/usb/input/ukbd.c @@ -2185,8 +2185,11 @@ DRIVER_MODULE(ukbd, uhub, ukbd_driver, ukbd_devclass, ukbd_driver_load, 0); MODULE_DEPEND(ukbd, usb, 1, 1, 1); +MODULE_DEPEND(ukbd, hid, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(ukbd, evdev, 1, 1, 1); #endif MODULE_VERSION(ukbd, 1); +#ifndef USBHID_ENABLED USB_PNP_HOST_INFO(ukbd_devs); +#endif diff --git a/sys/dev/usb/input/ums.c b/sys/dev/usb/input/ums.c --- a/sys/dev/usb/input/ums.c +++ b/sys/dev/usb/input/ums.c @@ -1213,8 +1213,11 @@ DRIVER_MODULE(ums, uhub, ums_driver, ums_devclass, NULL, 0); MODULE_DEPEND(ums, usb, 1, 1, 1); +MODULE_DEPEND(ums, hid, 1, 1, 1); #ifdef EVDEV_SUPPORT MODULE_DEPEND(ums, evdev, 1, 1, 1); #endif MODULE_VERSION(ums, 1); +#ifndef USBHID_ENABLED USB_PNP_HOST_INFO(ums_devs); +#endif diff --git a/sys/dev/usb/input/usb_rdesc.h b/sys/dev/usb/input/usb_rdesc.h --- a/sys/dev/usb/input/usb_rdesc.h +++ b/sys/dev/usb/input/usb_rdesc.h @@ -1,11 +1,7 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD * - * Copyright (c) 2000 Nick Hibma - * All rights reserved. - * - * Copyright (c) 2005 Ed Schouten - * All rights reserved. + * Copyright (c) 2020 Vladimir Kondratyev * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,277 +26,14 @@ * * $FreeBSD$ * - * This file contains replacements for broken HID report descriptors. + * This a proxy file for replacements for broken HID report descriptors. */ -#define UHID_GRAPHIRE_REPORT_DESCR(...) \ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x01, /* USAGE (Digitizer) */\ - 0xa1, 0x01, /* COLLECTION (Application) */\ - 0x85, 0x02, /* REPORT_ID (2) */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x01, /* USAGE (Digitizer) */\ - 0xa1, 0x00, /* COLLECTION (Physical) */\ - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ - 0x09, 0x33, /* USAGE (Touch) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x44, /* USAGE (Barrel Switch) */\ - 0x95, 0x02, /* REPORT_COUNT (2) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x00, /* USAGE (Undefined) */\ - 0x95, 0x02, /* REPORT_COUNT (2) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x03, /* INPUT (Cnst,Var,Abs) */\ - 0x09, 0x3c, /* USAGE (Invert) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x38, /* USAGE (Transducer Index) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x32, /* USAGE (In Range) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ - 0x09, 0x30, /* USAGE (X) */\ - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ - 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x31, /* USAGE (Y) */\ - 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x30, /* USAGE (Tip Pressure) */\ - 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0xc0, /* END_COLLECTION */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x00, /* USAGE (Undefined) */\ - 0x85, 0x02, /* REPORT_ID (2) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ - 0x09, 0x00, /* USAGE (Undefined) */\ - 0x85, 0x03, /* REPORT_ID (3) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ - 0xc0, /* END_COLLECTION */\ - -#define UHID_GRAPHIRE3_4X5_REPORT_DESCR(...) \ - 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ - 0x09, 0x02, /* USAGE (Mouse) */\ - 0xa1, 0x01, /* COLLECTION (Application) */\ - 0x85, 0x01, /* REPORT_ID (1) */\ - 0x09, 0x01, /* USAGE (Pointer) */\ - 0xa1, 0x00, /* COLLECTION (Physical) */\ - 0x05, 0x09, /* USAGE_PAGE (Button) */\ - 0x19, 0x01, /* USAGE_MINIMUM (Button 1) */\ - 0x29, 0x03, /* USAGE_MAXIMUM (Button 3) */\ - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ - 0x95, 0x03, /* REPORT_COUNT (3) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x75, 0x05, /* REPORT_SIZE (5) */\ - 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ - 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ - 0x09, 0x30, /* USAGE (X) */\ - 0x09, 0x31, /* USAGE (Y) */\ - 0x09, 0x38, /* USAGE (Wheel) */\ - 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */\ - 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */\ - 0x75, 0x08, /* REPORT_SIZE (8) */\ - 0x95, 0x03, /* REPORT_COUNT (3) */\ - 0x81, 0x06, /* INPUT (Data,Var,Rel) */\ - 0xc0, /* END_COLLECTION */\ - 0xc0, /* END_COLLECTION */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x01, /* USAGE (Pointer) */\ - 0xa1, 0x01, /* COLLECTION (Applicaption) */\ - 0x85, 0x02, /* REPORT_ID (2) */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x01, /* USAGE (Digitizer) */\ - 0xa1, 0x00, /* COLLECTION (Physical) */\ - 0x09, 0x33, /* USAGE (Touch) */\ - 0x09, 0x44, /* USAGE (Barrel Switch) */\ - 0x09, 0x44, /* USAGE (Barrel Switch) */\ - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x95, 0x03, /* REPORT_COUNT (3) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x95, 0x02, /* REPORT_COUNT (2) */\ - 0x81, 0x01, /* INPUT (Cnst,Ary,Abs) */\ - 0x09, 0x3c, /* USAGE (Invert) */\ - 0x09, 0x38, /* USAGE (Transducer Index) */\ - 0x09, 0x32, /* USAGE (In Range) */\ - 0x75, 0x01, /* REPORT_SIZE (1) */\ - 0x95, 0x03, /* REPORT_COUNT (3) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */\ - 0x09, 0x30, /* USAGE (X) */\ - 0x15, 0x00, /* LOGICAL_MINIMUM (0) */\ - 0x26, 0xde, 0x27, /* LOGICAL_MAXIMUM (10206) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x09, 0x31, /* USAGE (Y) */\ - 0x26, 0xfe, 0x1c, /* LOGICAL_MAXIMUM (7422) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x30, /* USAGE (Tip Pressure) */\ - 0x26, 0xff, 0x01, /* LOGICAL_MAXIMUM (511) */\ - 0x75, 0x10, /* REPORT_SIZE (16) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0x81, 0x02, /* INPUT (Data,Var,Abs) */\ - 0xc0, /* END_COLLECTION */\ - 0x05, 0x0d, /* USAGE_PAGE (Digitizers) */\ - 0x09, 0x00, /* USAGE (Undefined) */\ - 0x85, 0x02, /* REPORT_ID (2) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ - 0x09, 0x00, /* USAGE (Undefined) */\ - 0x85, 0x03, /* REPORT_ID (3) */\ - 0x95, 0x01, /* REPORT_COUNT (1) */\ - 0xb1, 0x02, /* FEATURE (Data,Var,Abs) */\ - 0xc0 /* END_COLLECTION */\ - -/* - * The descriptor has no output report format, thus preventing you from - * controlling the LEDs and the built-in rumblers. - */ -#define UHID_XB360GP_REPORT_DESCR(...) \ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x05, /* USAGE (Gamepad) */\ - 0xa1, 0x01, /* COLLECTION (Application) */\ - /* Unused */\ - 0x75, 0x08, /* REPORT SIZE (8) */\ - 0x95, 0x01, /* REPORT COUNT (1) */\ - 0x81, 0x01, /* INPUT (Constant) */\ - /* Byte count */\ - 0x75, 0x08, /* REPORT SIZE (8) */\ - 0x95, 0x01, /* REPORT COUNT (1) */\ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x3b, /* USAGE (Byte Count) */\ - 0x81, 0x01, /* INPUT (Constant) */\ - /* D-Pad */\ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x01, /* USAGE (Pointer) */\ - 0xa1, 0x00, /* COLLECTION (Physical) */\ - 0x75, 0x01, /* REPORT SIZE (1) */\ - 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ - 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ - 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ - 0x95, 0x04, /* REPORT COUNT (4) */\ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x90, /* USAGE (D-Pad Up) */\ - 0x09, 0x91, /* USAGE (D-Pad Down) */\ - 0x09, 0x93, /* USAGE (D-Pad Left) */\ - 0x09, 0x92, /* USAGE (D-Pad Right) */\ - 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ - 0xc0, /* END COLLECTION */\ - /* Buttons 5-11 */\ - 0x75, 0x01, /* REPORT SIZE (1) */\ - 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ - 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ - 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ - 0x95, 0x07, /* REPORT COUNT (7) */\ - 0x05, 0x09, /* USAGE PAGE (Button) */\ - 0x09, 0x08, /* USAGE (Button 8) */\ - 0x09, 0x07, /* USAGE (Button 7) */\ - 0x09, 0x09, /* USAGE (Button 9) */\ - 0x09, 0x0a, /* USAGE (Button 10) */\ - 0x09, 0x05, /* USAGE (Button 5) */\ - 0x09, 0x06, /* USAGE (Button 6) */\ - 0x09, 0x0b, /* USAGE (Button 11) */\ - 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ - /* Unused */\ - 0x75, 0x01, /* REPORT SIZE (1) */\ - 0x95, 0x01, /* REPORT COUNT (1) */\ - 0x81, 0x01, /* INPUT (Constant) */\ - /* Buttons 1-4 */\ - 0x75, 0x01, /* REPORT SIZE (1) */\ - 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ - 0x25, 0x01, /* LOGICAL MAXIMUM (1) */\ - 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ - 0x45, 0x01, /* PHYSICAL MAXIMUM (1) */\ - 0x95, 0x04, /* REPORT COUNT (4) */\ - 0x05, 0x09, /* USAGE PAGE (Button) */\ - 0x19, 0x01, /* USAGE MINIMUM (Button 1) */\ - 0x29, 0x04, /* USAGE MAXIMUM (Button 4) */\ - 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ - /* Triggers */\ - 0x75, 0x08, /* REPORT SIZE (8) */\ - 0x15, 0x00, /* LOGICAL MINIMUM (0) */\ - 0x26, 0xff, 0x00, /* LOGICAL MAXIMUM (255) */\ - 0x35, 0x00, /* PHYSICAL MINIMUM (0) */\ - 0x46, 0xff, 0x00, /* PHYSICAL MAXIMUM (255) */\ - 0x95, 0x02, /* REPORT SIZE (2) */\ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x32, /* USAGE (Z) */\ - 0x09, 0x35, /* USAGE (Rz) */\ - 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ - /* Sticks */\ - 0x75, 0x10, /* REPORT SIZE (16) */\ - 0x16, 0x00, 0x80, /* LOGICAL MINIMUM (-32768) */\ - 0x26, 0xff, 0x7f, /* LOGICAL MAXIMUM (32767) */\ - 0x36, 0x00, 0x80, /* PHYSICAL MINIMUM (-32768) */\ - 0x46, 0xff, 0x7f, /* PHYSICAL MAXIMUM (32767) */\ - 0x95, 0x04, /* REPORT COUNT (4) */\ - 0x05, 0x01, /* USAGE PAGE (Generic Desktop) */\ - 0x09, 0x30, /* USAGE (X) */\ - 0x09, 0x31, /* USAGE (Y) */\ - 0x09, 0x33, /* USAGE (Rx) */\ - 0x09, 0x34, /* USAGE (Ry) */\ - 0x81, 0x02, /* INPUT (Data, Variable, Absolute) */\ - /* Unused */\ - 0x75, 0x30, /* REPORT SIZE (48) */\ - 0x95, 0x01, /* REPORT COUNT (1) */\ - 0x81, 0x01, /* INPUT (Constant) */\ - 0xc0 /* END COLLECTION */\ +#include -/* Fixed report descriptor for Super Nintendo gamepads */ -#define UHID_SNES_REPORT_DESCR(...) \ - 0x05, 0x01, /* Usage Page (Desktop), */\ - 0x09, 0x04, /* Usage (Joystik), */\ - 0xA1, 0x01, /* Collection (Application), */\ - 0xA1, 0x02, /* Collection (Logical), */\ - 0x14, /* Logical Minimum (0), */\ - 0x75, 0x08, /* Report Size (8), */\ - 0x95, 0x03, /* Report Count (3), */\ - 0x81, 0x01, /* Input (Constant), */\ - 0x26, 0xFF, 0x00, /* Logical Maximum (255), */\ - 0x95, 0x02, /* Report Count (2), */\ - 0x09, 0x30, /* Usage (X), */\ - 0x09, 0x31, /* Usage (Y), */\ - 0x81, 0x02, /* Input (Variable), */\ - 0x75, 0x01, /* Report Size (1), */\ - 0x95, 0x04, /* Report Count (4), */\ - 0x81, 0x01, /* Input (Constant), */\ - 0x25, 0x01, /* Logical Maximum (1), */\ - 0x95, 0x0A, /* Report Count (10), */\ - 0x05, 0x09, /* Usage Page (Button), */\ - 0x19, 0x01, /* Usage Minimum (01h), */\ - 0x29, 0x0A, /* Usage Maximum (0Ah), */\ - 0x81, 0x02, /* Input (Variable), */\ - 0x95, 0x0A, /* Report Count (10), */\ - 0x81, 0x01, /* Input (Constant), */\ - 0xC0, /* End Collection, */\ - 0xC0 /* End Collection */ +#define UHID_GRAPHIRE_REPORT_DESCR HID_GRAPHIRE_REPORT_DESCR +#define UHID_GRAPHIRE3_4X5_REPORT_DESCR HID_GRAPHIRE3_4X5_REPORT_DESCR +#define UHID_XB360GP_REPORT_DESCR HID_XB360GP_REPORT_DESCR +#define UHID_SNES_REPORT_DESCR HID_SNES_REPORT_DESCR +#define UHID_MOUSE_BOOTPROTO_DESCR HID_MOUSE_BOOTPROTO_DESCR +#define UHID_KBD_BOOTPROTO_DESCR HID_KBD_BOOTPROTO_DESCR diff --git a/sys/dev/usb/input/usbhid.c b/sys/dev/usb/input/usbhid.c new file mode 100644 --- /dev/null +++ b/sys/dev/usb/input/usbhid.c @@ -0,0 +1,789 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c) 1998 The NetBSD Foundation, Inc. + * Copyright (c) 2019 Vladimir Kondratyev + * + * This code is derived from software contributed to The NetBSD Foundation + * by Lennart Augustsson (lennart@augustsson.net) at + * Carlstedt Research & Technology. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +#define USB_DEBUG_VAR usbhid_debug +#include + +#include + +#include "hid_if.h" + +#ifdef USB_DEBUG +static int usbhid_debug = 0; + +static SYSCTL_NODE(_hw_usb, OID_AUTO, usbhid, CTLFLAG_RW, 0, "USB usbhid"); +SYSCTL_INT(_hw_usb_usbhid, OID_AUTO, debug, CTLFLAG_RWTUN, + &usbhid_debug, 0, "Debug level"); +#endif + +enum { + USBHID_INTR_OUT_DT, + USBHID_INTR_IN_DT, + USBHID_CTRL_DT, + USBHID_N_TRANSFER, +}; + +struct usbhid_xfer_ctx; +typedef int usbhid_callback_t(struct usbhid_xfer_ctx *xfer_ctx); + +union usbhid_device_request { + struct { /* INTR xfers */ + uint16_t maxlen; + uint16_t actlen; + } intr; + struct usb_device_request ctrl; /* CTRL xfers */ +}; + +/* Syncronous USB transfer context */ +struct usbhid_xfer_ctx { + union usbhid_device_request req; + uint8_t *buf; + int error; + usbhid_callback_t *cb; + void *cb_ctx; + int waiters; + bool influx; +}; + +struct usbhid_softc { + hid_intr_t *sc_intr_handler; + void *sc_intr_ctx; + void *sc_intr_buf; + + struct hid_device_info sc_hw; + + struct mtx sc_mtx; + struct usb_config sc_config[USBHID_N_TRANSFER]; + struct usb_xfer *sc_xfer[USBHID_N_TRANSFER]; + struct usbhid_xfer_ctx sc_xfer_ctx[USBHID_N_TRANSFER]; + + struct usb_device *sc_udev; + uint8_t sc_iface_no; + uint8_t sc_iface_index; +}; + +/* prototypes */ + +static device_probe_t usbhid_probe; +static device_attach_t usbhid_attach; +static device_detach_t usbhid_detach; + +static usb_callback_t usbhid_intr_out_callback; +static usb_callback_t usbhid_intr_in_callback; +static usb_callback_t usbhid_ctrl_callback; + +static usbhid_callback_t usbhid_intr_handler_cb; +static usbhid_callback_t usbhid_sync_wakeup_cb; + +static void +usbhid_intr_out_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int len; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + case USB_ST_SETUP: +tr_setup: + len = xfer_ctx->req.intr.maxlen; + if (len == 0) { + if (USB_IN_POLLING_MODE_FUNC()) + xfer_ctx->error = 0; + else + usbd_transfer_stop(xfer); + return; + } + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, xfer_ctx->buf, len); + usbd_xfer_set_frame_len(xfer, 0, len); + usbd_transfer_submit(xfer); + xfer_ctx->req.intr.maxlen = 0; + if (USB_IN_POLLING_MODE_FUNC()) + return; + xfer_ctx->error = 0; + goto tr_exit; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto tr_setup; + } + xfer_ctx->error = EIO; +tr_exit: + (void)xfer_ctx->cb(xfer_ctx); + return; + } +} + +static void +usbhid_intr_in_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); + struct usb_page_cache *pc; + int actlen; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_TRANSFERRED: + DPRINTF("transferred!\n"); + + usbd_xfer_status(xfer, &actlen, NULL, NULL, NULL); + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, 0, xfer_ctx->buf, actlen); + xfer_ctx->req.intr.actlen = actlen; + if (xfer_ctx->cb(xfer_ctx) != 0) + return; + + case USB_ST_SETUP: +re_submit: + usbd_xfer_set_frame_len(xfer, 0, xfer_ctx->req.intr.maxlen); + usbd_transfer_submit(xfer); + return; + + default: /* Error */ + if (error != USB_ERR_CANCELLED) { + /* try to clear stall first */ + usbd_xfer_set_stall(xfer); + goto re_submit; + } + return; + } +} + +static void +usbhid_ctrl_callback(struct usb_xfer *xfer, usb_error_t error) +{ + struct usbhid_xfer_ctx *xfer_ctx = usbd_xfer_softc(xfer); + struct usb_device_request *req = &xfer_ctx->req.ctrl; + struct usb_page_cache *pc; + int len = UGETW(req->wLength); + bool is_rd = (req->bmRequestType & UT_READ) != 0; + + switch (USB_GET_STATE(xfer)) { + case USB_ST_SETUP: + if (!is_rd && len != 0) { + pc = usbd_xfer_get_frame(xfer, 1); + usbd_copy_in(pc, 0, xfer_ctx->buf, len); + } + + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_in(pc, 0, req, sizeof(*req)); + usbd_xfer_set_frame_len(xfer, 0, sizeof(*req)); + if (len != 0) + usbd_xfer_set_frame_len(xfer, 1, len); + usbd_xfer_set_frames(xfer, len != 0 ? 2 : 1); + usbd_transfer_submit(xfer); + return; + + case USB_ST_TRANSFERRED: + if (is_rd && len != 0) { + pc = usbd_xfer_get_frame(xfer, 0); + usbd_copy_out(pc, sizeof(*req), xfer_ctx->buf, len); + } + xfer_ctx->error = 0; + goto tr_exit; + + default: /* Error */ + /* bomb out */ + DPRINTFN(1, "error=%s\n", usbd_errstr(error)); + xfer_ctx->error = EIO; +tr_exit: + (void)xfer_ctx->cb(xfer_ctx); + return; + } +} + +static int +usbhid_intr_handler_cb(struct usbhid_xfer_ctx *xfer_ctx) +{ + struct usbhid_softc *sc = xfer_ctx->cb_ctx; + + sc->sc_intr_handler(sc->sc_intr_ctx, xfer_ctx->buf, + xfer_ctx->req.intr.actlen); + + return (0); +} + +static int +usbhid_sync_wakeup_cb(struct usbhid_xfer_ctx *xfer_ctx) +{ + + if (!USB_IN_POLLING_MODE_FUNC()) + wakeup(xfer_ctx->cb_ctx); + + return (ECANCELED); +} + +static const struct usb_config usbhid_config[USBHID_N_TRANSFER] = { + + [USBHID_INTR_OUT_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_OUT, + .flags = {.pipe_bof = 1,.proxy_buffer = 1}, + .callback = &usbhid_intr_out_callback, + }, + [USBHID_INTR_IN_DT] = { + .type = UE_INTERRUPT, + .endpoint = UE_ADDR_ANY, + .direction = UE_DIR_IN, + .flags = {.pipe_bof = 1,.short_xfer_ok = 1,.proxy_buffer = 1}, + .callback = &usbhid_intr_in_callback, + }, + [USBHID_CTRL_DT] = { + .type = UE_CONTROL, + .endpoint = 0x00, /* Control pipe */ + .direction = UE_DIR_ANY, + .flags = {.proxy_buffer = 1}, + .callback = &usbhid_ctrl_callback, + .timeout = 1000, /* 1 second */ + }, +}; + +static void +usbhid_intr_setup(device_t dev, hid_intr_t intr, void *context, + struct hid_rdesc_info *rdesc) +{ + struct usbhid_softc* sc = device_get_softc(dev); + uint16_t n; + bool nowrite; + int error; + + sc->sc_intr_handler = intr; + sc->sc_intr_ctx = context; + bcopy(usbhid_config, sc->sc_config, sizeof(usbhid_config)); + + /* Set buffer sizes to match HID report sizes */ + sc->sc_config[USBHID_INTR_OUT_DT].bufsize = rdesc->osize; + sc->sc_config[USBHID_INTR_IN_DT].bufsize = rdesc->isize; + sc->sc_config[USBHID_CTRL_DT].bufsize = + MAX(rdesc->isize, MAX(rdesc->osize, rdesc->fsize)); + + nowrite = hid_test_quirk(&sc->sc_hw, HQ_NOWRITE); + + /* + * Setup the USB transfers one by one, so they are memory independent + * which allows for handling panics triggered by the HID drivers + * itself, typically by hkbd via CTRL+ALT+ESC sequences. Or if the HID + * keyboard driver was processing a key at the moment of panic. + */ + for (n = 0; n != USBHID_N_TRANSFER; n++) { + if (nowrite && n == USBHID_INTR_OUT_DT) + continue; + error = usbd_transfer_setup(sc->sc_udev, &sc->sc_iface_index, + sc->sc_xfer + n, sc->sc_config + n, 1, + (void *)(sc->sc_xfer_ctx + n), &sc->sc_mtx); + if (error) + break; + } + + if (error) + DPRINTF("error=%s\n", usbd_errstr(error)); + + rdesc->rdsize = usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]); + rdesc->grsize = usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT]); + rdesc->srsize = rdesc->grsize; + rdesc->wrsize = nowrite ? rdesc->srsize : + usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT]); + + sc->sc_intr_buf = malloc(rdesc->rdsize, M_USBDEV, M_ZERO | M_WAITOK); +} + +static void +usbhid_intr_unsetup(device_t dev) +{ + struct usbhid_softc* sc = device_get_softc(dev); + + usbd_transfer_unsetup(sc->sc_xfer, USBHID_N_TRANSFER); + free(sc->sc_intr_buf, M_USBDEV); +} + +static int +usbhid_intr_start(device_t dev) +{ + struct usbhid_softc* sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + sc->sc_xfer_ctx[USBHID_INTR_IN_DT] = (struct usbhid_xfer_ctx) { + .req.intr.maxlen = + usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT]), + .cb = usbhid_intr_handler_cb, + .cb_ctx = sc, + .buf = sc->sc_intr_buf, + }; + usbd_transfer_start(sc->sc_xfer[USBHID_INTR_IN_DT]); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static int +usbhid_intr_stop(device_t dev) +{ + struct usbhid_softc* sc = device_get_softc(dev); + + mtx_lock(&sc->sc_mtx); + usbd_transfer_stop(sc->sc_xfer[USBHID_INTR_IN_DT]); + mtx_unlock(&sc->sc_mtx); + + return (0); +} + +static void +usbhid_intr_poll(device_t dev) +{ + struct usbhid_softc* sc = device_get_softc(dev); + + usbd_transfer_poll(sc->sc_xfer + USBHID_INTR_IN_DT, 1); +} + +/* + * HID interface + */ +static int +usbhid_sync_xfer(struct usbhid_softc* sc, int xfer_idx, + union usbhid_device_request *req, void *buf) +{ + int error, timeout; + struct usbhid_xfer_ctx *xfer_ctx, save; + + xfer_ctx = sc->sc_xfer_ctx + xfer_idx; + + if (USB_IN_POLLING_MODE_FUNC()) { + save = *xfer_ctx; + } else { + mtx_lock(&sc->sc_mtx); + ++xfer_ctx->waiters; + while (xfer_ctx->influx) + mtx_sleep(&xfer_ctx->waiters, &sc->sc_mtx, 0, + "usbhid wt", 0); + --xfer_ctx->waiters; + xfer_ctx->influx = true; + } + + xfer_ctx->buf = buf; + xfer_ctx->req = *req; + xfer_ctx->error = ETIMEDOUT; + xfer_ctx->cb = &usbhid_sync_wakeup_cb; + xfer_ctx->cb_ctx = xfer_ctx; + timeout = USB_DEFAULT_TIMEOUT; + usbd_transfer_start(sc->sc_xfer[xfer_idx]); + + if (USB_IN_POLLING_MODE_FUNC()) + while (timeout > 0 && xfer_ctx->error == ETIMEDOUT) { + usbd_transfer_poll(sc->sc_xfer + xfer_idx, 1); + DELAY(1000); + timeout--; + } + else + msleep_sbt(xfer_ctx, &sc->sc_mtx, 0, "usbhid io", + SBT_1MS * timeout, 0, C_HARDCLOCK); + + /* Perform usbhid_write() asyncronously to improve queueing */ + if (USB_IN_POLLING_MODE_FUNC() || + sc->sc_config[xfer_idx].type != UE_INTERRUPT || + sc->sc_config[xfer_idx].direction != UE_DIR_OUT) + usbd_transfer_stop(sc->sc_xfer[xfer_idx]); + error = xfer_ctx->error; + if (error == 0) + *req = xfer_ctx->req; + + if (USB_IN_POLLING_MODE_FUNC()) { + *xfer_ctx = save; + } else { + xfer_ctx->influx = false; + if (xfer_ctx->waiters != 0) + wakeup_one(&xfer_ctx->waiters); + mtx_unlock(&sc->sc_mtx); + } + + if (error) + DPRINTF("USB IO error:%d\n", error); + + return (error); +} + +static int +usbhid_get_rdesc(device_t dev, void *buf, hid_size_t len) +{ + struct usbhid_softc* sc = device_get_softc(dev); + int error; + + error = usbd_req_get_report_descriptor(sc->sc_udev, NULL, + buf, len, sc->sc_iface_index); + + if (error) + DPRINTF("no report descriptor: %s\n", usbd_errstr(error)); + + return (error == 0 ? 0 : ENXIO); +} + +static int +usbhid_get_report(device_t dev, void *buf, hid_size_t maxlen, + hid_size_t *actlen, uint8_t type, uint8_t id) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + int error; + + if (maxlen > usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT])) + return (ENOBUFS); + + req.ctrl.bmRequestType = UT_READ_CLASS_INTERFACE; + req.ctrl.bRequest = UR_GET_REPORT; + USETW2(req.ctrl.wValue, type, id); + req.ctrl.wIndex[0] = sc->sc_iface_no; + req.ctrl.wIndex[1] = 0; + USETW(req.ctrl.wLength, maxlen); + + error = usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, buf); + if (!error && actlen != NULL) + *actlen = maxlen; + + return (error); +} + +static int +usbhid_set_report(device_t dev, const void *buf, hid_size_t len, uint8_t type, + uint8_t id) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + + if (len > usbd_xfer_max_len(sc->sc_xfer[USBHID_CTRL_DT])) + return (ENOBUFS); + + req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.ctrl.bRequest = UR_SET_REPORT; + USETW2(req.ctrl.wValue, type, id); + req.ctrl.wIndex[0] = sc->sc_iface_no; + req.ctrl.wIndex[1] = 0; + USETW(req.ctrl.wLength, len); + + return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, + __DECONST(void *, buf))); +} + +static int +usbhid_read(device_t dev, void *buf, hid_size_t maxlen, hid_size_t *actlen) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + int error; + + if (maxlen > usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_IN_DT])) + return (ENOBUFS); + + req.intr.maxlen = maxlen; + error = usbhid_sync_xfer(sc, USBHID_INTR_IN_DT, &req, buf); + if (error == 0 && actlen != NULL) + *actlen = req.intr.actlen; + + return (error); +} + +static int +usbhid_write(device_t dev, const void *buf, hid_size_t len) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + + if (len > usbd_xfer_max_len(sc->sc_xfer[USBHID_INTR_OUT_DT])) + return (ENOBUFS); + + req.intr.maxlen = len; + return (usbhid_sync_xfer(sc, USBHID_INTR_OUT_DT, &req, + __DECONST(void *, buf))); +} + +static int +usbhid_set_idle(device_t dev, uint16_t duration, uint8_t id) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + + /* Duration is measured in 4 milliseconds per unit. */ + req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.ctrl.bRequest = UR_SET_IDLE; + USETW2(req.ctrl.wValue, (duration + 3) / 4, id); + req.ctrl.wIndex[0] = sc->sc_iface_no; + req.ctrl.wIndex[1] = 0; + USETW(req.ctrl.wLength, 0); + + return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); +} + +static int +usbhid_set_protocol(device_t dev, uint16_t protocol) +{ + struct usbhid_softc* sc = device_get_softc(dev); + union usbhid_device_request req; + + req.ctrl.bmRequestType = UT_WRITE_CLASS_INTERFACE; + req.ctrl.bRequest = UR_SET_PROTOCOL; + USETW(req.ctrl.wValue, protocol); + req.ctrl.wIndex[0] = sc->sc_iface_no; + req.ctrl.wIndex[1] = 0; + USETW(req.ctrl.wLength, 0); + + return (usbhid_sync_xfer(sc, USBHID_CTRL_DT, &req, NULL)); +} + +static void +usbhid_init_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) +{ + + hw->idBus = BUS_USB; + hw->idVendor = uaa->info.idVendor; + hw->idProduct = uaa->info.idProduct; + hw->idVersion = uaa->info.bcdDevice; + + /* Set various quirks based on usb_attach_arg */ + hid_add_dynamic_quirk(hw, USB_GET_DRIVER_INFO(uaa)); +} + +static void +usbhid_fill_device_info(struct usb_attach_arg *uaa, struct hid_device_info *hw) +{ + struct usb_device *udev = uaa->device; + struct usb_interface *iface = uaa->iface; + struct usb_hid_descriptor *hid; + struct usb_endpoint *ep; + + snprintf(hw->name, sizeof(hw->name), "%s %s", + usb_get_manufacturer(udev), usb_get_product(udev)); + strlcpy(hw->serial, usb_get_serial(udev), sizeof(hw->serial)); + + if (uaa->info.bInterfaceClass == UICLASS_HID && + iface != NULL && iface->idesc != NULL) { + hid = hid_get_descriptor_from_usb( + usbd_get_config_descriptor(udev), iface->idesc); + if (hid != NULL) + hw->rdescsize = + UGETW(hid->descrs[0].wDescriptorLength); + } + + /* See if there is a interrupt out endpoint. */ + ep = usbd_get_endpoint(udev, uaa->info.bIfaceIndex, + usbhid_config + USBHID_INTR_OUT_DT); + if (ep == NULL || ep->methods == NULL) + hid_add_dynamic_quirk(hw, HQ_NOWRITE); +} + +static const STRUCT_USB_HOST_ID usbhid_devs[] = { + /* the Xbox 360 gamepad doesn't use the HID class */ + {USB_IFACE_CLASS(UICLASS_VENDOR), + USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), + USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD), + USB_DRIVER_INFO(HQ_IS_XBOX360GP)}, + /* HID keyboard with boot protocol support */ + {USB_IFACE_CLASS(UICLASS_HID), + USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), + USB_IFACE_PROTOCOL(UIPROTO_BOOT_KEYBOARD), + USB_DRIVER_INFO(HQ_HAS_KBD_BOOTPROTO)}, + /* HID mouse with boot protocol support */ + {USB_IFACE_CLASS(UICLASS_HID), + USB_IFACE_SUBCLASS(UISUBCLASS_BOOT), + USB_IFACE_PROTOCOL(UIPROTO_MOUSE), + USB_DRIVER_INFO(HQ_HAS_MS_BOOTPROTO)}, + /* generic HID class */ + {USB_IFACE_CLASS(UICLASS_HID), USB_DRIVER_INFO(HQ_NONE)}, +}; + +static int +usbhid_probe(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usbhid_softc *sc = device_get_softc(dev); + int error; + + DPRINTFN(11, "\n"); + + if (uaa->usb_mode != USB_MODE_HOST) + return (ENXIO); + + error = usbd_lookup_id_by_uaa(usbhid_devs, sizeof(usbhid_devs), uaa); + if (error) + return (error); + + if (usb_test_quirk(uaa, UQ_HID_IGNORE)) + return (ENXIO); + + /* + * Setup temporary hid_device_info so that we can figure out some + * basic quirks for this device. + */ + usbhid_init_device_info(uaa, &sc->sc_hw); + + if (hid_test_quirk(&sc->sc_hw, HQ_HID_IGNORE)) + return (ENXIO); + +#ifdef USBHID_ENABLED + return (BUS_PROBE_GENERIC + 1); +#else + return (BUS_PROBE_GENERIC - 1); +#endif +} + +static int +usbhid_attach(device_t dev) +{ + struct usb_attach_arg *uaa = device_get_ivars(dev); + struct usbhid_softc *sc = device_get_softc(dev); + device_t child; + int error = 0; + + DPRINTFN(10, "sc=%p\n", sc); + + device_set_usb_desc(dev); + + sc->sc_udev = uaa->device; + sc->sc_iface_no = uaa->info.bIfaceNum; + sc->sc_iface_index = uaa->info.bIfaceIndex; + + usbhid_fill_device_info(uaa, &sc->sc_hw); + + error = usbd_req_set_idle(uaa->device, NULL, + uaa->info.bIfaceIndex, 0, 0); + if (error) + DPRINTF("set idle failed, error=%s (ignored)\n", + usbd_errstr(error)); + + mtx_init(&sc->sc_mtx, "usbhid lock", NULL, MTX_DEF); + + child = device_add_child(dev, "hidbus", -1); + if (child == NULL) { + device_printf(dev, "Could not add hidbus device\n"); + usbhid_detach(dev); + return (ENOMEM); + } + + device_set_ivars(child, &sc->sc_hw); + error = bus_generic_attach(dev); + if (error) { + device_printf(dev, "failed to attach child: %d\n", error); + usbhid_detach(dev); + return (error); + } + + return (0); /* success */ +} + +static int +usbhid_detach(device_t dev) +{ + struct usbhid_softc *sc = device_get_softc(dev); + + device_delete_children(dev); + mtx_destroy(&sc->sc_mtx); + + return (0); +} + +static devclass_t usbhid_devclass; + +static device_method_t usbhid_methods[] = { + DEVMETHOD(device_probe, usbhid_probe), + DEVMETHOD(device_attach, usbhid_attach), + DEVMETHOD(device_detach, usbhid_detach), + + DEVMETHOD(hid_intr_setup, usbhid_intr_setup), + DEVMETHOD(hid_intr_unsetup, usbhid_intr_unsetup), + DEVMETHOD(hid_intr_start, usbhid_intr_start), + DEVMETHOD(hid_intr_stop, usbhid_intr_stop), + DEVMETHOD(hid_intr_poll, usbhid_intr_poll), + + /* HID interface */ + DEVMETHOD(hid_get_rdesc, usbhid_get_rdesc), + DEVMETHOD(hid_read, usbhid_read), + DEVMETHOD(hid_write, usbhid_write), + DEVMETHOD(hid_get_report, usbhid_get_report), + DEVMETHOD(hid_set_report, usbhid_set_report), + DEVMETHOD(hid_set_idle, usbhid_set_idle), + DEVMETHOD(hid_set_protocol, usbhid_set_protocol), + + DEVMETHOD_END +}; + +static driver_t usbhid_driver = { + .name = "usbhid", + .methods = usbhid_methods, + .size = sizeof(struct usbhid_softc), +}; + +DRIVER_MODULE(usbhid, uhub, usbhid_driver, usbhid_devclass, NULL, 0); +MODULE_DEPEND(usbhid, usb, 1, 1, 1); +MODULE_DEPEND(usbhid, hid, 1, 1, 1); +MODULE_DEPEND(usbhid, hidbus, 1, 1, 1); +MODULE_VERSION(usbhid, 1); +#ifdef USBHID_ENABLED +USB_PNP_HOST_INFO(usbhid_devs); +#endif diff --git a/sys/dev/usb/input/wmt.c b/sys/dev/usb/input/wmt.c --- a/sys/dev/usb/input/wmt.c +++ b/sys/dev/usb/input/wmt.c @@ -456,7 +456,7 @@ } WMT_FOREACH_USAGE(sc->caps, i) { if (wmt_hid_map[i].code != WMT_NO_CODE) - evdev_support_abs(sc->evdev, wmt_hid_map[i].code, 0, + evdev_support_abs(sc->evdev, wmt_hid_map[i].code, sc->ai[i].min, sc->ai[i].max, 0, 0, sc->ai[i].res); } @@ -1048,11 +1048,13 @@ return (err); } +#ifndef USBHID_ENABLED static const STRUCT_USB_HOST_ID wmt_devs[] = { /* generic HID class w/o boot interface */ {USB_IFACE_CLASS(UICLASS_HID), USB_IFACE_SUBCLASS(0),}, }; +#endif static devclass_t wmt_devclass; @@ -1072,6 +1074,9 @@ DRIVER_MODULE(wmt, uhub, wmt_driver, wmt_devclass, NULL, 0); MODULE_DEPEND(wmt, usb, 1, 1, 1); +MODULE_DEPEND(wmt, hid, 1, 1, 1); MODULE_DEPEND(wmt, evdev, 1, 1, 1); MODULE_VERSION(wmt, 1); +#ifndef USBHID_ENABLED USB_PNP_HOST_INFO(wmt_devs); +#endif diff --git a/sys/dev/usb/input/wsp.c b/sys/dev/usb/input/wsp.c --- a/sys/dev/usb/input/wsp.c +++ b/sys/dev/usb/input/wsp.c @@ -1403,5 +1403,6 @@ DRIVER_MODULE(wsp, uhub, wsp_driver, wsp_devclass, NULL, 0); MODULE_DEPEND(wsp, usb, 1, 1, 1); +MODULE_DEPEND(wsp, hid, 1, 1, 1); MODULE_VERSION(wsp, 1); USB_PNP_HOST_INFO(wsp_devs); diff --git a/sys/dev/usb/misc/ugold.c b/sys/dev/usb/misc/ugold.c --- a/sys/dev/usb/misc/ugold.c +++ b/sys/dev/usb/misc/ugold.c @@ -142,6 +142,7 @@ DRIVER_MODULE(ugold, uhub, ugold_driver, ugold_devclass, NULL, NULL); MODULE_DEPEND(ugold, usb, 1, 1, 1); +MODULE_DEPEND(ugold, hid, 1, 1, 1); MODULE_VERSION(ugold, 1); USB_PNP_HOST_INFO(ugold_devs); diff --git a/sys/dev/usb/serial/ucycom.c b/sys/dev/usb/serial/ucycom.c --- a/sys/dev/usb/serial/ucycom.c +++ b/sys/dev/usb/serial/ucycom.c @@ -185,6 +185,7 @@ DRIVER_MODULE(ucycom, uhub, ucycom_driver, ucycom_devclass, NULL, 0); MODULE_DEPEND(ucycom, ucom, 1, 1, 1); MODULE_DEPEND(ucycom, usb, 1, 1, 1); +MODULE_DEPEND(ucycom, hid, 1, 1, 1); MODULE_VERSION(ucycom, 1); USB_PNP_HOST_INFO(ucycom_devs); diff --git a/sys/dev/usb/usb_hid.c b/sys/dev/usb/usb_hid.c --- a/sys/dev/usb/usb_hid.c +++ b/sys/dev/usb/usb_hid.c @@ -68,703 +68,6 @@ #include #endif /* USB_GLOBAL_INCLUDE_FILE */ -static void hid_clear_local(struct hid_item *); -static uint8_t hid_get_byte(struct hid_data *s, const uint16_t wSize); - -#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->usage = 0; - 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, usb_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]) { - if (s->iusage < MAXUSAGE - 1) - s->iusage ++; - s->ousage = 0; - } else { - s->ousage ++; - } - } else { - DPRINTFN(1, "Using last usage\n"); - dval = s->usage_last; - } - 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; - *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, usb_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, usb_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; - - for (d = hid_start_parse(desc, size, 1 << k); hid_get_item(d, &h);) { - if (h.kind == k && !(h.flags & HIO_CONST) && h.usage == u) { - if (index--) - continue; - 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, usb_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, usb_size_t len, struct hid_location *loc) -{ - return (hid_get_data_sub(buf, len, loc, 1)); -} - -uint32_t -hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, struct hid_location *loc) -{ - return (hid_get_data_sub(buf, len, loc, 0)); -} - -/*------------------------------------------------------------------------* - * hid_put_data - *------------------------------------------------------------------------*/ -void -hid_put_data_unsigned(uint8_t *buf, usb_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, usb_size_t size, int32_t usage) -{ - struct hid_data *hd; - struct hid_item hi; - int err; - - hd = hid_start_parse(desc, size, hid_input); - 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); -} - /*------------------------------------------------------------------------* * hid_get_descriptor_from_usb * @@ -849,153 +152,3 @@ } return (USB_ERR_NORMAL_COMPLETION); } - -/*------------------------------------------------------------------------* - * 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: - 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); -} diff --git a/sys/dev/usb/usb_ioctl.h b/sys/dev/usb/usb_ioctl.h --- a/sys/dev/usb/usb_ioctl.h +++ b/sys/dev/usb/usb_ioctl.h @@ -270,7 +270,7 @@ #define USB_DEVICESTATS _IOR ('U', 5, struct usb_device_stats) #define USB_DEVICEENUMERATE _IOW ('U', 6, int) -/* Generic HID device */ +/* Generic HID device. Numbers 26 and 30-39 are occupied by hidraw. */ #define USB_GET_REPORT_DESC _IOWR('U', 21, struct usb_gen_descriptor) #define USB_SET_IMMED _IOW ('U', 22, int) #define USB_GET_REPORT _IOWR('U', 23, struct usb_gen_descriptor) diff --git a/sys/dev/usb/usbhid.h b/sys/dev/usb/usbhid.h --- a/sys/dev/usb/usbhid.h +++ b/sys/dev/usb/usbhid.h @@ -31,6 +31,8 @@ #ifndef _USB_HID_H_ #define _USB_HID_H_ +#include + #ifndef USB_GLOBAL_INCLUDE_FILE #include #endif @@ -61,218 +63,28 @@ #define USB_HID_DESCRIPTOR_SIZE(n) (9+((n)*3)) -/* 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_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_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_LATENCY_MODE 0x0060 - -/* Usages, Consumer */ -#define HUC_AC_PAN 0x0238 - -#define HID_USAGE2(p,u) (((p) << 16) | (u)) - -#define UHID_INPUT_REPORT 0x01 -#define UHID_OUTPUT_REPORT 0x02 -#define UHID_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_DEGREE 0x14 +#define UHID_INPUT_REPORT HID_INPUT_REPORT +#define UHID_OUTPUT_REPORT HID_OUTPUT_REPORT +#define UHID_FEATURE_REPORT HID_FEATURE_REPORT #if defined(_KERNEL) || defined(_STANDALONE) struct usb_config_descriptor; -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 */ - int32_t usage; - 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; -}; - -/* prototypes from "usb_hid.c" */ +/* FreeBSD <= 12 compat shims */ +#define hid_report_size(buf, len, kind, id) \ + hid_report_size_max(buf, len, kind, id) +static __inline uint32_t +hid_get_data_unsigned(const uint8_t *buf, hid_size_t len, + struct hid_location *loc) +{ + return (hid_get_udata(buf, len, loc)); +} -struct hid_data *hid_start_parse(const void *d, usb_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, usb_size_t len, enum hid_kind k, - uint8_t *id); -int hid_locate(const void *desc, usb_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, usb_size_t len, - struct hid_location *loc); -uint32_t hid_get_data_unsigned(const uint8_t *buf, usb_size_t len, - struct hid_location *loc); -void hid_put_data_unsigned(uint8_t *buf, usb_size_t len, - struct hid_location *loc, unsigned int value); -int hid_is_collection(const void *desc, usb_size_t size, int32_t usage); struct usb_hid_descriptor *hid_get_descriptor_from_usb( struct usb_config_descriptor *cd, struct usb_interface_descriptor *id); usb_error_t usbd_req_get_hid_desc(struct usb_device *udev, struct mtx *mtx, void **descp, uint16_t *sizep, struct malloc_type *mem, uint8_t iface_index); -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); #endif /* _KERNEL || _STANDALONE */ #endif /* _USB_HID_H_ */ diff --git a/sys/i386/conf/GENERIC b/sys/i386/conf/GENERIC --- a/sys/i386/conf/GENERIC +++ b/sys/i386/conf/GENERIC @@ -348,3 +348,12 @@ options EVDEV_SUPPORT # evdev support in legacy drivers device evdev # input event device support device uinput # install /dev/uinput cdev + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support +options IICHID_DEBUG # enable debug msgs for I2C transport +options IICHID_SAMPLING # Workaround missing GPIO INTR support +#device usbhid # USB transport support. +#device hidbus # HID bus (required by usbhid/iichid) +#options USBHID_ENABLED # Prefer usbhid to other USB drivers diff --git a/sys/libkern/strcasestr.c b/sys/libkern/strcasestr.c new file mode 100644 --- /dev/null +++ b/sys/libkern/strcasestr.c @@ -0,0 +1,68 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * Copyright (c) 1990, 1993 + * The Regents of the University of California. All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Chris Torek. + * + * Copyright (c) 2011 The FreeBSD Foundation + * All rights reserved. + * Portions of this software were developed by David Chisnall + * under sponsorship from the FreeBSD Foundation. + * + * 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. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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 + +/* + * Find the first occurrence of find in s, ignore case. + */ +char * +strcasestr(const char *s, const char *find) +{ + char c, sc; + size_t len; + + if ((c = *find++) != 0) { + c = tolower((unsigned char)c); + len = strlen(find); + do { + do { + if ((sc = *s++) == 0) + return (NULL); + } while ((char)tolower((unsigned char)sc) != c); + } while (strncasecmp(s, find, len) != 0); + s--; + } + return (__DECONST(char *, s)); +} diff --git a/sys/mips/conf/ERL b/sys/mips/conf/ERL --- a/sys/mips/conf/ERL +++ b/sys/mips/conf/ERL @@ -212,3 +212,7 @@ # PMC support #device hwpmc + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/mips/conf/JZ4780 b/sys/mips/conf/JZ4780 --- a/sys/mips/conf/JZ4780 +++ b/sys/mips/conf/JZ4780 @@ -109,5 +109,9 @@ device umass # Disks/Mass storage - Requires scbus and da device ums # Mouse +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support + # FDT support options FDT diff --git a/sys/mips/conf/OCTEON1 b/sys/mips/conf/OCTEON1 --- a/sys/mips/conf/OCTEON1 +++ b/sys/mips/conf/OCTEON1 @@ -237,3 +237,7 @@ # PMC support #device hwpmc + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/modules/Makefile b/sys/modules/Makefile --- a/sys/modules/Makefile +++ b/sys/modules/Makefile @@ -129,6 +129,7 @@ ${_glxiic} \ ${_glxsb} \ gpio \ + hid \ hifn \ ${_hpt27xx} \ ${_hptiop} \ diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/Makefile @@ -0,0 +1,22 @@ +# $FreeBSD$ + +SUBDIR = \ + hid \ + hidbus \ + hidmap \ + hidquirk \ + hidraw + +SUBDIR += \ + hconf \ + hcons \ + hgame \ + hkbd \ + hms \ + hmt \ + hpen \ + hsctrl \ + ps4dshock \ + xb360gp + +.include diff --git a/sys/modules/hid/hconf/Makefile b/sys/modules/hid/hconf/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hconf/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hconf +SRCS= hconf.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hcons/Makefile b/sys/modules/hid/hcons/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hcons/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hcons +SRCS= hcons.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hgame/Makefile b/sys/modules/hid/hgame/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hgame/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hgame +SRCS= hgame.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hid/Makefile b/sys/modules/hid/hid/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hid/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hid +SRCS= hid.c hid_if.c +SRCS+= opt_hid.h +SRCS+= bus_if.h device_if.h hid_if.h + +.include diff --git a/sys/modules/hid/hidbus/Makefile b/sys/modules/hid/hidbus/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hidbus/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidbus +SRCS= hidbus.c +SRCS+= bus_if.h device_if.h hid_if.h + +.include diff --git a/sys/modules/hid/hidmap/Makefile b/sys/modules/hid/hidmap/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hidmap/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidmap +SRCS= hidmap.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hidquirk/Makefile b/sys/modules/hid/hidquirk/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hidquirk/Makefile @@ -0,0 +1,34 @@ +# +# 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. +# +# $FreeBSD$ +# + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidquirk +SRCS= hidquirk.c +SRCS+= bus_if.h device_if.h usbdevs.h + +.include diff --git a/sys/modules/hid/hidraw/Makefile b/sys/modules/hid/hidraw/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hidraw/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidraw +SRCS= hidraw.c +SRCS+= opt_hid.h bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hkbd/Makefile b/sys/modules/hid/hkbd/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hkbd/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hkbd +SRCS= hkbd.c +SRCS+= opt_evdev.h opt_kbd.h opt_hkbd.h +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hms/Makefile b/sys/modules/hid/hms/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hms/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hms +SRCS= hms.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hmt/Makefile b/sys/modules/hid/hmt/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hmt/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hmt +SRCS= hmt.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hpen/Makefile b/sys/modules/hid/hpen/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hpen/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hpen +SRCS= hpen.c +SRCS+= bus_if.h device_if.h usbdevs.h + +.include diff --git a/sys/modules/hid/hsctrl/Makefile b/sys/modules/hid/hsctrl/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hsctrl/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hsctrl +SRCS= hsctrl.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/ps4dshock/Makefile b/sys/modules/hid/ps4dshock/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/ps4dshock/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= ps4dshock +SRCS= ps4dshock.c +SRCS+= bus_if.h device_if.h usbdevs.h + +.include diff --git a/sys/modules/hid/xb360gp/Makefile b/sys/modules/hid/xb360gp/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/xb360gp/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= xb360gp +SRCS= xb360gp.c +SRCS+= bus_if.h device_if.h opt_usb.h + +.include diff --git a/sys/modules/i2c/Makefile b/sys/modules/i2c/Makefile --- a/sys/modules/i2c/Makefile +++ b/sys/modules/i2c/Makefile @@ -29,4 +29,9 @@ rx8803 .endif +.if ${MACHINE_CPUARCH} == "aarch64" || ${MACHINE_CPUARCH} == "amd64" || \ + ${MACHINE_CPUARCH} == "i386" +SUBDIR += iichid +.endif + .include diff --git a/sys/modules/i2c/iichid/Makefile b/sys/modules/i2c/iichid/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/i2c/iichid/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/iicbus +KMOD = iichid +SRCS = iichid.c +SRCS += acpi_if.h bus_if.h device_if.h hid_if.h iicbus_if.h opt_hid.h + +.include diff --git a/sys/modules/usb/Makefile b/sys/modules/usb/Makefile --- a/sys/modules/usb/Makefile +++ b/sys/modules/usb/Makefile @@ -47,7 +47,8 @@ SUBDIR += ${_dwc_otg} ehci ${_musb} ohci uhci xhci ${_uss820dci} \ ${_atmegadci} ${_avr32dci} ${_rsu} ${_rsufw} ${_saf1761otg} SUBDIR += ${_rum} ${_run} ${_runfw} ${_uath} upgt usie ural ${_zyd} ${_urtw} -SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled +SUBDIR += atp cfumass uhid uhid_snes ukbd ums udbp uep wmt wsp ugold uled \ + usbhid SUBDIR += ucom u3g uark ubsa ubser uchcom ucycom ufoma uftdi ugensa uipaq ulpt \ umct umcs umodem umoscom uplcom uslcom uvisor uvscom SUBDIR += cp2112 diff --git a/sys/modules/usb/uhid/Makefile b/sys/modules/usb/uhid/Makefile --- a/sys/modules/usb/uhid/Makefile +++ b/sys/modules/usb/uhid/Makefile @@ -30,7 +30,7 @@ .PATH: $S/dev/usb/input KMOD= uhid -SRCS= opt_bus.h opt_usb.h device_if.h bus_if.h usb_if.h vnode_if.h usbdevs.h \ - uhid.c +SRCS= opt_bus.h opt_hid.h opt_usb.h device_if.h bus_if.h usb_if.h \ + vnode_if.h usbdevs.h uhid.c .include diff --git a/sys/modules/usb/usbhid/Makefile b/sys/modules/usb/usbhid/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/usb/usbhid/Makefile @@ -0,0 +1,10 @@ +# $FreeBSD$ + +S= ${SRCTOP}/sys + +.PATH: $S/dev/usb/input + +KMOD= usbhid +SRCS= opt_usb.h bus_if.h device_if.h hid_if.h usb_if.h usbhid.c + +.include diff --git a/sys/powerpc/conf/GENERIC b/sys/powerpc/conf/GENERIC --- a/sys/powerpc/conf/GENERIC +++ b/sys/powerpc/conf/GENERIC @@ -240,3 +240,6 @@ device virtio_scsi # VirtIO SCSI device device virtio_balloon # VirtIO Memory Balloon device +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/powerpc/conf/GENERIC64 b/sys/powerpc/conf/GENERIC64 --- a/sys/powerpc/conf/GENERIC64 +++ b/sys/powerpc/conf/GENERIC64 @@ -270,3 +270,6 @@ device virtio_scsi # VirtIO SCSI device device virtio_balloon # VirtIO Memory Balloon device +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/powerpc/conf/GENERIC64LE b/sys/powerpc/conf/GENERIC64LE --- a/sys/powerpc/conf/GENERIC64LE +++ b/sys/powerpc/conf/GENERIC64LE @@ -251,3 +251,6 @@ device virtio_scsi # VirtIO SCSI device device virtio_balloon # VirtIO Memory Balloon device +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/powerpc/conf/MPC85XX b/sys/powerpc/conf/MPC85XX --- a/sys/powerpc/conf/MPC85XX +++ b/sys/powerpc/conf/MPC85XX @@ -117,3 +117,7 @@ device videomode device vt device fbd + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/powerpc/conf/MPC85XXSPE b/sys/powerpc/conf/MPC85XXSPE --- a/sys/powerpc/conf/MPC85XXSPE +++ b/sys/powerpc/conf/MPC85XXSPE @@ -118,3 +118,7 @@ device videomode device vt device fbd + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/powerpc/conf/QORIQ64 b/sys/powerpc/conf/QORIQ64 --- a/sys/powerpc/conf/QORIQ64 +++ b/sys/powerpc/conf/QORIQ64 @@ -120,3 +120,7 @@ options KBD_INSTALL_CDEV device ukbd device ums + +# HID support +options HID_DEBUG # enable debug msgs +device hid # Generic HID support diff --git a/sys/sys/libkern.h b/sys/sys/libkern.h --- a/sys/sys/libkern.h +++ b/sys/sys/libkern.h @@ -168,6 +168,7 @@ u_long random(void); int scanc(u_int, const u_char *, const u_char *, int); int strcasecmp(const char *, const char *); +char *strcasestr(const char *, const char *); char *strcat(char * __restrict, const char * __restrict); char *strchr(const char *, int); char *strchrnul(const char *, int);