Page MenuHomeFreeBSD

D27777.id81194.diff
No OneTemporary

D27777.id81194.diff

This file is larger than 256 KB, so syntax highlighting was skipped.
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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <n_hibma@FreeBSD.org>. 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 <n_hibma@FreeBSD.org>. All rights reserved.
+.\" 2020 Vladimir Kondratyev <wulf@FreeBSD.org>.
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\" 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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <wulf@FreeBSD.org>
+.\"
+.\" Redistribution and use in source and binary forms, with or without
+.\" modification, are permitted provided that the following conditions
+.\" are met:
+.\" 1. Redistributions of source code must retain the above copyright
+.\" notice, this list of conditions and the following disclaimer.
+.\" 2. Redistributions in binary form must reproduce the above copyright
+.\" notice, this list of conditions and the following disclaimer in the
+.\" documentation and/or other materials provided with the distribution.
+.\"
+.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+.\" SUCH DAMAGE.
+.\"
+.\" $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 <sys/param.h>
#include <sys/bitstring.h>
#include <sys/conf.h>
+#include <sys/epoch.h>
#include <sys/filio.h>
#include <sys/fcntl.h>
#include <sys/kernel.h>
@@ -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 <sys/types.h>
+#include <sys/epoch.h>
#include <sys/kbio.h>
#include <dev/evdev/input.h>
#include <dev/kbd/kbdreg.h>
@@ -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 <sys/param.h>
#include <sys/bitstring.h>
+#include <sys/ck.h>
#include <sys/conf.h>
+#include <sys/epoch.h>
#include <sys/kdb.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/proc.h>
+#include <sys/sx.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
@@ -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 <sys/bitstring.h>
+#include <sys/ck.h>
+#include <sys/epoch.h>
#include <sys/kbio.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/mutex.h>
#include <sys/queue.h>
#include <sys/selinfo.h>
+#include <sys/sx.h>
#include <sys/sysctl.h>
#include <dev/evdev/evdev.h>
@@ -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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/sx.h>
+
+#define HID_DEBUG_VAR hconf_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+
+#include <dev/hid/hconf.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Consumer Controls usage page driver
+ * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/bitstring.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+
+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 <wulf@FreeBSD.org>
+ * Copyright (c) 2020 Greg V <greg@unrelenting.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 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 <dev/hid/hidmap.h>
+
+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 <wulf@FreeBSD.org>
+ * Copyright (c) 2020 Greg V <greg@unrelenting.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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Generic HID game controller (joystick/gamepad) driver,
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hgame.h>
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidmap.h>
+
+#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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kdb.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#define HID_DEBUG_VAR hid_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <dev/hid/hid.h>
+
+# 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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/ck.h>
+#include <sys/epoch.h>
+#include <sys/kdb.h>
+#include <sys/kernel.h>
+#include <sys/libkern.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/sx.h>
+
+#define HID_DEBUG_VAR hid_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#ifndef _HIDMAP_H_
+#define _HIDMAP_H_
+
+#include <sys/param.h>
+
+#include <dev/hid/hid.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * Abstract 1 to 1 HID input usage to evdev event mapper driver.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+
+#ifdef HID_DEBUG
+#define DPRINTFN(hm, n, fmt, ...) do { \
+ if ((hm)->debug_var != NULL && *(hm)->debug_var >= (n)) { \
+ device_printf((hm)->dev, "%s: " fmt, \
+ __FUNCTION__ ,##__VA_ARGS__); \
+ } \
+} while (0)
+#define DPRINTF(hm, ...) DPRINTFN(hm, 1, __VA_ARGS__)
+#else
+#define DPRINTF(...) do { } while (0)
+#define DPRINTFN(...) do { } while (0)
+#endif
+
+static evdev_open_t hidmap_ev_open;
+static evdev_close_t hidmap_ev_close;
+
+#define HIDMAP_WANT_MERGE_KEYS(hm) ((hm)->key_rel != NULL)
+
+#define HIDMAP_FOREACH_ITEM(hm, mi, uoff) \
+ for (u_int _map = 0, _item = 0, _uoff_priv = -1; \
+ ((mi) = hidmap_get_next_map_item( \
+ (hm), &_map, &_item, &_uoff_priv, &(uoff))) != NULL;)
+
+static inline bool
+hidmap_get_next_map_index(const struct hidmap_item *map, int nmap_items,
+ uint32_t *index, uint16_t *usage_offset)
+{
+
+ ++*usage_offset;
+ if ((*index != 0 || *usage_offset != 0) &&
+ *usage_offset >= map[*index].nusages) {
+ ++*index;
+ *usage_offset = 0;
+ }
+
+ return (*index < nmap_items);
+}
+
+static inline const struct hidmap_item *
+hidmap_get_next_map_item(struct hidmap *hm, u_int *map, u_int *item,
+ u_int *uoff_priv, uint16_t *uoff)
+{
+
+ *uoff = *uoff_priv;
+ while (!hidmap_get_next_map_index(
+ hm->map[*map], hm->nmap_items[*map], item, uoff)) {
+ ++*map;
+ *item = 0;
+ *uoff = -1;
+ if (*map >= hm->nmaps)
+ return (NULL);
+ }
+ *uoff_priv = *uoff;
+
+ return (hm->map[*map] + *item);
+}
+
+void
+_hidmap_set_debug_var(struct hidmap *hm, int *debug_var)
+{
+#ifdef HID_DEBUG
+ hm->debug_var = debug_var;
+#endif
+}
+
+static int
+hidmap_ev_close(struct evdev_dev *evdev)
+{
+ return (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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * 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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+
+#include <dev/evdev/input.h>
+
+#define HID_DEBUG_VAR hid_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef _HID_HIDRAW_H
+#define _HID_HIDRAW_H
+
+#include <sys/ioccom.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+#include <sys/filio.h>
+#include <sys/ioccom.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/poll.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/selinfo.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/tty.h>
+#include <sys/uio.h>
+
+#define HID_DEBUG_VAR hidraw_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidraw.h>
+
+#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 <n_hibma@FreeBSD.org>
+ * All rights reserved.
+ *
+ * Copyright (c) 2005 Ed Schouten <ed@FreeBSD.org>
+ * 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 <sys/cdefs.h>
+__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 <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/proc.h>
+#include <sys/kdb.h>
+#include <sys/taskqueue.h>
+
+#include <machine/atomic.h>
+
+#define HID_DEBUG_VAR hkbd_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidrdesc.h>
+
+#ifdef EVDEV_SUPPORT
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+#endif
+
+#include <sys/ioccom.h>
+#include <sys/filio.h>
+#include <sys/kbio.h>
+
+#include <dev/kbd/kbdreg.h>
+
+/* 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 <dev/kbd/kbdtables.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidrdesc.h>
+
+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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+
+#include <dev/evdev/evdev.h>
+#include <dev/evdev/input.h>
+
+#define HID_DEBUG_VAR hmt_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/hid/hconf.h>
+
+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 <wulf@FreeBSD.org>
+ * Copyright (c) 2019 Greg V <greg@unrelenting.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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+#include <dev/hid/hidrdesc.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * General Desktop/System Controls usage page driver
+ * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
+ */
+
+#include <sys/param.h>
+#include <sys/bitstring.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+
+#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 <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/sx.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#define HID_DEBUG_VAR ps4dshock_debug
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidmap.h>
+#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 <wulf@FreeBSD.org>
+ * Copyright (c) 2020 Greg V <greg@unrelenting.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 AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__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 <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/evdev/input.h>
+#include <dev/evdev/evdev.h>
+
+#include <dev/hid/hgame.h>
+#include <dev/hid/hid.h>
+#include <dev/hid/hidbus.h>
+#include <dev/hid/hidmap.h>
+#include <dev/hid/hidquirk.h>
+#include <dev/hid/hidrdesc.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+
+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 <marc.priggemeyer@gmail.com>
+ * Copyright (c) 2019-2020 Vladimir Kondratyev <wulf@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * I2C HID transport backend.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_hid.h"
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/callout.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/taskqueue.h>
+
+#include <machine/resource.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+#include <contrib/dev/acpica/include/accommon.h>
+#include <dev/acpica/acpivar.h>
+
+#include <dev/evdev/input.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/iicbus/iic.h>
+#include <dev/iicbus/iicbus.h>
+#include <dev/iicbus/iiconf.h>
+
+#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.<unit>.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 <sys/stdint.h>
#include <sys/stddef.h>
#include <sys/param.h>
@@ -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 <n_hibma@FreeBSD.org>
- * All rights reserved.
- *
- * Copyright (c) 2005 Ed Schouten <ed@FreeBSD.org>
- * All rights reserved.
+ * Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
@@ -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 <dev/hid/hidrdesc.h>
-/* 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 <wulf@FreeBSD.org>
+ *
+ * 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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+/*
+ * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf
+ */
+
+#include <sys/stdint.h>
+#include <sys/stddef.h>
+#include <sys/param.h>
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/bus.h>
+#include <sys/module.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/condvar.h>
+#include <sys/sysctl.h>
+#include <sys/sx.h>
+#include <sys/unistd.h>
+#include <sys/callout.h>
+#include <sys/malloc.h>
+#include <sys/priv.h>
+#include <sys/conf.h>
+#include <sys/fcntl.h>
+
+#include <dev/evdev/input.h>
+
+#include <dev/hid/hid.h>
+#include <dev/hid/hidquirk.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdi_util.h>
+#include <dev/usb/usbhid.h>
+
+#define USB_DEBUG_VAR usbhid_debug
+#include <dev/usb/usb_debug.h>
+
+#include <dev/usb/quirk/usb_quirk.h>
+
+#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 <dev/usb/usb_request.h>
#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 <dev/hid/hid.h>
+
#ifndef USB_GLOBAL_INCLUDE_FILE
#include <dev/usb/usb_endian.h>
#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 <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/ctype.h>
+#include <sys/libkern.h>
+
+/*
+ * 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 <bsd.subdir.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <wulf@FreeBSD.org>
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+#
+# $FreeBSD$
+#
+
+.PATH: ${SRCTOP}/sys/dev/hid
+
+KMOD= hidquirk
+SRCS= hidquirk.c
+SRCS+= bus_if.h device_if.h usbdevs.h
+
+.include <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.subdir.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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 <bsd.kmod.mk>
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);

File Metadata

Mime Type
text/plain
Expires
Sat, Feb 22, 11:10 AM (1 h, 59 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
16770177
Default Alt Text
D27777.id81194.diff (548 KB)

Event Timeline