diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -180,18 +180,23 @@ 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} \ @@ -432,6 +437,7 @@ ppi.4 \ procdesc.4 \ proto.4 \ + ps4dshock.4 \ psm.4 \ pst.4 \ pt.4 \ @@ -588,6 +594,7 @@ wmt.4 \ ${_wpi.4} \ wsp.4 \ + xb360gp.4 \ ${_xen.4} \ xhci.4 \ xl.4 \ 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,98 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt 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 variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.hcons.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It default value is set with +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.hcons.debug +.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,105 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt 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 variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.hgame.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It's default value is set with +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.hgame.debug +.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/hms.4 b/share/man/man4/hms.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hms.4 @@ -0,0 +1,115 @@ +.\" Copyright (c) +.\" 1999 Nick Hibma . All rights reserved. +.\" 2020 Vladimir Kondratyev . +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 12, 2020 +.Dt HMS 4 +.Os +.Sh NAME +.Nm hms +.Nd HID mouse driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hms" +.Cd "device hidbus" +.Cd "device hid" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hms_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for HID mice that attach to the HID transport +backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +Supported are +mice with any number of buttons, mice with a wheel and absolute mice. +.Pp +The +.Pa /dev/input/eventX +device presents the mouse as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.hms.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It default value is derived from +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.hms.debug +.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/hpen.4 b/share/man/man4/hpen.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/hpen.4 @@ -0,0 +1,112 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt HPEN 4 +.Os +.Sh NAME +.Nm hpen +.Nd MS Windows compatible HID pen tablet driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device hpen" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +hpen_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for generic MS Windows compatible HID pen tablet +and digitizer that attach to the HID transport backend. +See +.Xr iichid 4 +or +.Xr usbhid 4 . +.Pp +The +.Pa /dev/input/event* +device presents the pen as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +The following variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.hpen.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It's default value is set with +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.hpen.debug +.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,98 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 14, 2020 +.Dt 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 variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.hsctrl.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It default value is set with +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.hsctrl.debug +.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/ps4dshock.4 b/share/man/man4/ps4dshock.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/ps4dshock.4 @@ -0,0 +1,109 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 19, 2020 +.Dt PS4DSHOCK 4 +.Os +.Sh NAME +.Nm ps4dshock +.Nd Sony PlayStation 4 Dualshock 4 gamepad driver +.Sh SYNOPSIS +To compile this driver into the kernel, +place the following lines in your +kernel configuration file: +.Bd -ragged -offset indent +.Cd "device ps4dshock" +.Cd "device hid" +.Cd "device hidbus" +.Cd "device hidmap" +.Cd "device evdev" +.Ed +.Pp +Alternatively, to load the driver as a +module at boot time, place the following line in +.Xr loader.conf 5 : +.Bd -literal -offset indent +ps4dshock_load="YES" +.Ed +.Sh DESCRIPTION +The +.Nm +driver provides support for Sony PlayStation 4 Dualshock 4 gamepad driver. +.Pp +The +.Pa /dev/input/event* +device presents the game controller as a +.Ar evdev +type device. +.Sh SYSCTL VARIABLES +Next parameters are available as +.Xr sysctl 8 +variables. +Debug parameter is available as +.Xr loader 8 +tunable as well. +.Bl -tag -width indent +.It Va dev.p4dshock.*.led_state +LED state: 0 - off, 1 - on, 2 - blinking. +.It Va dev.p4dshock.*.led_color_r +LED color. +Red component. +.It Va dev.p4dshock.*.led_color_g +LED color. +Green component. +.It Va dev.p4dshock.*.led_color_b +LED color. +Blue component. +.It Va dev.p4dshock.*.led_delay_on +LED blink. +On delay, msecs. +.It Va dev.p4dshock.*.led_delay_off +LED blink. +Off delay, msecs. +.It Va hw.hid.ps4dshock.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Sh FILES +.Bl -tag -width /dev/input/event* -compact +.It Pa /dev/input/event* +input event device node. +.El +.Sh BUGS +The +.Nm +does not support force-feedback events. +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 13.0. +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org . diff --git a/share/man/man4/xb360gp.4 b/share/man/man4/xb360gp.4 new file mode 100644 --- /dev/null +++ b/share/man/man4/xb360gp.4 @@ -0,0 +1,98 @@ +.\" Copyright (c) 2020 Vladimir Kondratyev +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" $FreeBSD$ +.\" +.Dd September 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 variable is available as both +.Xr sysctl 8 +variable and +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va dev.xb360gp.X.debug +Debug output level, where 0 is debugging disabled and larger values increase +debug message verbosity. +Default is 0. +.El +.Pp +It's default value is set with +.Xr loader 8 +tunable: +.Bl -tag -width indent +.It Va hw.hid.xb360gp.debug +.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/conf/files b/sys/conf/files --- a/sys/conf/files +++ b/sys/conf/files @@ -1816,13 +1816,21 @@ 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 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 @@ -684,6 +684,7 @@ evdev_modify_event(struct evdev_dev *evdev, uint16_t type, uint16_t code, int32_t *value) { + int32_t fuzz, old_value, abs_change; EVDEV_LOCK_ASSERT(evdev); @@ -710,7 +711,24 @@ break; case EV_ABS: - /* TBD: implement fuzz */ + fuzz = evdev->ev_absinfo[code].fuzz; + if (fuzz == 0 || code == ABS_MT_SLOT) + break; + else if (!ABS_IS_MT(code)) + old_value = evdev->ev_absinfo[code].value; + else if (bit_test(evdev->ev_abs_flags, ABS_MT_SLOT)) + old_value = evdev_get_mt_value(evdev, + evdev_get_last_mt_slot(evdev), code); + else /* Pass MT protocol type A events as is */ + break; + + abs_change = abs(*value - old_value); + if (abs_change < fuzz / 2) + *value = old_value; + else if (abs_change < fuzz) + *value = (old_value * 3 + *value) / 4; + else if (abs_change < fuzz * 2) + *value = (old_value + *value) / 2; break; } } diff --git a/sys/dev/hid/hcons.c b/sys/dev/hid/hcons.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hcons.c @@ -0,0 +1,295 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Consumer Controls usage page driver + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +static hidmap_cb_t hcons_rel_volume_cb; + +#define HCONS_MAP_KEY(usage, code) \ + { HIDMAP_KEY(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_REL(usage, code) \ + { HIDMAP_REL(HUP_CONSUMER, usage, code) } +#define HCONS_MAP_REL_CB(usage, callback) \ + { HIDMAP_REL_CB(HUP_CONSUMER, usage, &callback) } + +static const struct hidmap_item hcons_map[] = { + HCONS_MAP_KEY(0x030, KEY_POWER), + HCONS_MAP_KEY(0x031, KEY_RESTART), + HCONS_MAP_KEY(0x032, KEY_SLEEP), + HCONS_MAP_KEY(0x034, KEY_SLEEP), + HCONS_MAP_KEY(0x035, KEY_KBDILLUMTOGGLE), + HCONS_MAP_KEY(0x036, BTN_MISC), + HCONS_MAP_KEY(0x040, KEY_MENU), /* Menu */ + HCONS_MAP_KEY(0x041, KEY_SELECT), /* Menu Pick */ + HCONS_MAP_KEY(0x042, KEY_UP), /* Menu Up */ + HCONS_MAP_KEY(0x043, KEY_DOWN), /* Menu Down */ + HCONS_MAP_KEY(0x044, KEY_LEFT), /* Menu Left */ + HCONS_MAP_KEY(0x045, KEY_RIGHT), /* Menu Right */ + HCONS_MAP_KEY(0x046, KEY_ESC), /* Menu Escape */ + HCONS_MAP_KEY(0x047, KEY_KPPLUS), /* Menu Value Increase */ + HCONS_MAP_KEY(0x048, KEY_KPMINUS), /* Menu Value Decrease */ + HCONS_MAP_KEY(0x060, KEY_INFO), /* Data On Screen */ + HCONS_MAP_KEY(0x061, KEY_SUBTITLE), /* Closed Caption */ + HCONS_MAP_KEY(0x063, KEY_VCR), /* VCR/TV */ + HCONS_MAP_KEY(0x065, KEY_CAMERA), /* Snapshot */ + HCONS_MAP_KEY(0x069, KEY_RED), + HCONS_MAP_KEY(0x06a, KEY_GREEN), + HCONS_MAP_KEY(0x06b, KEY_BLUE), + HCONS_MAP_KEY(0x06c, KEY_YELLOW), + HCONS_MAP_KEY(0x06d, KEY_ASPECT_RATIO), + HCONS_MAP_KEY(0x06f, KEY_BRIGHTNESSUP), + HCONS_MAP_KEY(0x070, KEY_BRIGHTNESSDOWN), + HCONS_MAP_KEY(0x072, KEY_BRIGHTNESS_TOGGLE), + HCONS_MAP_KEY(0x073, KEY_BRIGHTNESS_MIN), + HCONS_MAP_KEY(0x074, KEY_BRIGHTNESS_MAX), + HCONS_MAP_KEY(0x075, KEY_BRIGHTNESS_AUTO), + HCONS_MAP_KEY(0x079, KEY_KBDILLUMUP), + HCONS_MAP_KEY(0x07a, KEY_KBDILLUMDOWN), + HCONS_MAP_KEY(0x07c, KEY_KBDILLUMTOGGLE), + HCONS_MAP_KEY(0x082, KEY_VIDEO_NEXT), + HCONS_MAP_KEY(0x083, KEY_LAST), + HCONS_MAP_KEY(0x084, KEY_ENTER), + HCONS_MAP_KEY(0x088, KEY_PC), + HCONS_MAP_KEY(0x089, KEY_TV), + HCONS_MAP_KEY(0x08a, KEY_WWW), + HCONS_MAP_KEY(0x08b, KEY_DVD), + HCONS_MAP_KEY(0x08c, KEY_PHONE), + HCONS_MAP_KEY(0x08d, KEY_PROGRAM), + HCONS_MAP_KEY(0x08e, KEY_VIDEOPHONE), + HCONS_MAP_KEY(0x08f, KEY_GAMES), + HCONS_MAP_KEY(0x090, KEY_MEMO), + HCONS_MAP_KEY(0x091, KEY_CD), + HCONS_MAP_KEY(0x092, KEY_VCR), + HCONS_MAP_KEY(0x093, KEY_TUNER), + HCONS_MAP_KEY(0x094, KEY_EXIT), + HCONS_MAP_KEY(0x095, KEY_HELP), + HCONS_MAP_KEY(0x096, KEY_TAPE), + HCONS_MAP_KEY(0x097, KEY_TV2), + HCONS_MAP_KEY(0x098, KEY_SAT), + HCONS_MAP_KEY(0x09a, KEY_PVR), + HCONS_MAP_KEY(0x09c, KEY_CHANNELUP), + HCONS_MAP_KEY(0x09d, KEY_CHANNELDOWN), + HCONS_MAP_KEY(0x0a0, KEY_VCR2), + HCONS_MAP_KEY(0x0b0, KEY_PLAY), + HCONS_MAP_KEY(0x0b1, KEY_PAUSE), + HCONS_MAP_KEY(0x0b2, KEY_RECORD), + HCONS_MAP_KEY(0x0b3, KEY_FASTFORWARD), + HCONS_MAP_KEY(0x0b4, KEY_REWIND), + HCONS_MAP_KEY(0x0b5, KEY_NEXTSONG), + HCONS_MAP_KEY(0x0b6, KEY_PREVIOUSSONG), + HCONS_MAP_KEY(0x0b7, KEY_STOPCD), + HCONS_MAP_KEY(0x0b8, KEY_EJECTCD), + HCONS_MAP_KEY(0x0bc, KEY_MEDIA_REPEAT), + HCONS_MAP_KEY(0x0b9, KEY_SHUFFLE), + HCONS_MAP_KEY(0x0bf, KEY_SLOW), + HCONS_MAP_KEY(0x0cd, KEY_PLAYPAUSE), + HCONS_MAP_KEY(0x0cf, KEY_VOICECOMMAND), + HCONS_MAP_ABS(0x0e0, ABS_VOLUME), + HCONS_MAP_REL_CB(0x0e0, hcons_rel_volume_cb), + HCONS_MAP_KEY(0x0e2, KEY_MUTE), + HCONS_MAP_KEY(0x0e5, KEY_BASSBOOST), + HCONS_MAP_KEY(0x0e9, KEY_VOLUMEUP), + HCONS_MAP_KEY(0x0ea, KEY_VOLUMEDOWN), + HCONS_MAP_KEY(0x0f5, KEY_SLOW), + HCONS_MAP_KEY(0x181, KEY_BUTTONCONFIG), + HCONS_MAP_KEY(0x182, KEY_BOOKMARKS), + HCONS_MAP_KEY(0x183, KEY_CONFIG), + HCONS_MAP_KEY(0x184, KEY_WORDPROCESSOR), + HCONS_MAP_KEY(0x185, KEY_EDITOR), + HCONS_MAP_KEY(0x186, KEY_SPREADSHEET), + HCONS_MAP_KEY(0x187, KEY_GRAPHICSEDITOR), + HCONS_MAP_KEY(0x188, KEY_PRESENTATION), + HCONS_MAP_KEY(0x189, KEY_DATABASE), + HCONS_MAP_KEY(0x18a, KEY_MAIL), + HCONS_MAP_KEY(0x18b, KEY_NEWS), + HCONS_MAP_KEY(0x18c, KEY_VOICEMAIL), + HCONS_MAP_KEY(0x18d, KEY_ADDRESSBOOK), + HCONS_MAP_KEY(0x18e, KEY_CALENDAR), + HCONS_MAP_KEY(0x18f, KEY_TASKMANAGER), + HCONS_MAP_KEY(0x190, KEY_JOURNAL), + HCONS_MAP_KEY(0x191, KEY_FINANCE), + HCONS_MAP_KEY(0x192, KEY_CALC), + HCONS_MAP_KEY(0x193, KEY_PLAYER), + HCONS_MAP_KEY(0x194, KEY_FILE), + HCONS_MAP_KEY(0x196, KEY_WWW), + HCONS_MAP_KEY(0x199, KEY_CHAT), + HCONS_MAP_KEY(0x19c, KEY_LOGOFF), + HCONS_MAP_KEY(0x19e, KEY_COFFEE), + HCONS_MAP_KEY(0x19f, KEY_CONTROLPANEL), + HCONS_MAP_KEY(0x1a2, KEY_APPSELECT), + HCONS_MAP_KEY(0x1a3, KEY_NEXT), + HCONS_MAP_KEY(0x1a4, KEY_PREVIOUS), + HCONS_MAP_KEY(0x1a6, KEY_HELP), + HCONS_MAP_KEY(0x1a7, KEY_DOCUMENTS), + HCONS_MAP_KEY(0x1ab, KEY_SPELLCHECK), + HCONS_MAP_KEY(0x1ae, KEY_KEYBOARD), + HCONS_MAP_KEY(0x1b1, KEY_SCREENSAVER), + HCONS_MAP_KEY(0x1b4, KEY_FILE), + HCONS_MAP_KEY(0x1b6, KEY_IMAGES), + HCONS_MAP_KEY(0x1b7, KEY_AUDIO), + HCONS_MAP_KEY(0x1b8, KEY_VIDEO), + HCONS_MAP_KEY(0x1bc, KEY_MESSENGER), + HCONS_MAP_KEY(0x1bd, KEY_INFO), + HCONS_MAP_KEY(0x1cb, KEY_ASSISTANT), + HCONS_MAP_KEY(0x201, KEY_NEW), + HCONS_MAP_KEY(0x202, KEY_OPEN), + HCONS_MAP_KEY(0x203, KEY_CLOSE), + HCONS_MAP_KEY(0x204, KEY_EXIT), + HCONS_MAP_KEY(0x207, KEY_SAVE), + HCONS_MAP_KEY(0x208, KEY_PRINT), + HCONS_MAP_KEY(0x209, KEY_PROPS), + HCONS_MAP_KEY(0x21a, KEY_UNDO), + HCONS_MAP_KEY(0x21b, KEY_COPY), + HCONS_MAP_KEY(0x21c, KEY_CUT), + HCONS_MAP_KEY(0x21d, KEY_PASTE), + HCONS_MAP_KEY(0x21f, KEY_FIND), + HCONS_MAP_KEY(0x221, KEY_SEARCH), + HCONS_MAP_KEY(0x222, KEY_GOTO), + HCONS_MAP_KEY(0x223, KEY_HOMEPAGE), + HCONS_MAP_KEY(0x224, KEY_BACK), + HCONS_MAP_KEY(0x225, KEY_FORWARD), + HCONS_MAP_KEY(0x226, KEY_STOP), + HCONS_MAP_KEY(0x227, KEY_REFRESH), + HCONS_MAP_KEY(0x22a, KEY_BOOKMARKS), + HCONS_MAP_KEY(0x22d, KEY_ZOOMIN), + HCONS_MAP_KEY(0x22e, KEY_ZOOMOUT), + HCONS_MAP_KEY(0x22f, KEY_ZOOMRESET), + HCONS_MAP_KEY(0x232, KEY_FULL_SCREEN), + HCONS_MAP_KEY(0x233, KEY_SCROLLUP), + HCONS_MAP_KEY(0x234, KEY_SCROLLDOWN), + HCONS_MAP_REL(0x238, REL_HWHEEL), /* AC Pan */ + HCONS_MAP_KEY(0x23d, KEY_EDIT), + HCONS_MAP_KEY(0x25f, KEY_CANCEL), + HCONS_MAP_KEY(0x269, KEY_INSERT), + HCONS_MAP_KEY(0x26a, KEY_DELETE), + HCONS_MAP_KEY(0x279, KEY_REDO), + HCONS_MAP_KEY(0x289, KEY_REPLY), + HCONS_MAP_KEY(0x28b, KEY_FORWARDMAIL), + HCONS_MAP_KEY(0x28c, KEY_SEND), + HCONS_MAP_KEY(0x29d, KEY_KBD_LAYOUT_NEXT), + HCONS_MAP_KEY(0x2c7, KEY_KBDINPUTASSIST_PREV), + HCONS_MAP_KEY(0x2c8, KEY_KBDINPUTASSIST_NEXT), + HCONS_MAP_KEY(0x2c9, KEY_KBDINPUTASSIST_PREVGROUP), + HCONS_MAP_KEY(0x2ca, KEY_KBDINPUTASSIST_NEXTGROUP), + HCONS_MAP_KEY(0x2cb, KEY_KBDINPUTASSIST_ACCEPT), + HCONS_MAP_KEY(0x2cc, KEY_KBDINPUTASSIST_CANCEL), + HCONS_MAP_KEY(0x29f, KEY_SCALE), +}; + +static const struct hid_device_id hcons_devs[] = { + { HID_TLC(HUP_CONSUMER, HUC_CONSUMER_CONTROL) }, +}; + +/* + * Emulate relative Consumer volume usage with pressing + * VOLUMEUP and VOLUMEDOWN keys appropriate number of times + */ +static int +hcons_rel_volume_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t code; + int nrepeats; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_KEY); + evdev_support_key(evdev, KEY_VOLUMEUP); + evdev_support_key(evdev, KEY_VOLUMEDOWN); + break; + case HIDMAP_CB_IS_RUNNING: + /* Nothing to report. */ + if (ctx.data == 0) + return (ENOMSG); + code = ctx.data > 0 ? KEY_VOLUMEUP : KEY_VOLUMEDOWN; + for (nrepeats = abs(ctx.data); nrepeats > 0; nrepeats--) { + evdev_push_key(evdev, code, 1); + evdev_push_key(evdev, code, 0); + } + } + + return (0); +} + +static int +hcons_probe(device_t dev) +{ + return (HIDMAP_PROBE(device_get_softc(dev), dev, + hcons_devs, hcons_map, "Consumer Control")); +} + +static int +hcons_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +hcons_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static devclass_t hcons_devclass; +static device_method_t hcons_methods[] = { + DEVMETHOD(device_probe, hcons_probe), + DEVMETHOD(device_attach, hcons_attach), + DEVMETHOD(device_detach, hcons_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hcons, hcons_driver, hcons_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hcons, hidbus, hcons_driver, hcons_devclass, NULL, 0); +MODULE_DEPEND(hcons, hid, 1, 1, 1); +MODULE_DEPEND(hcons, hidbus, 1, 1, 1); +MODULE_DEPEND(hcons, hidmap, 1, 1, 1); +MODULE_DEPEND(hcons, evdev, 1, 1, 1); +MODULE_VERSION(hcons, 1); +HID_PNP_INFO(hcons_devs); diff --git a/sys/dev/hid/hgame.h b/sys/dev/hid/hgame.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hgame.h @@ -0,0 +1,45 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HID_HGAME_H_ +#define _HID_HGAME_H_ + +#include + +hidmap_cb_t hgame_dpad_cb; +hidmap_cb_t hgame_final_cb; + +struct hgame_softc { + struct hidmap hm; + bool dpad_up; + bool dpad_down; + bool dpad_right; + bool dpad_left; +}; + +#endif /* !_HGAME_H_ */ diff --git a/sys/dev/hid/hgame.c b/sys/dev/hid/hgame.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hgame.c @@ -0,0 +1,200 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Generic HID game controller (joystick/gamepad) driver, + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#define HGAME_MAP_BRG(number_from, number_to, code) \ + { HIDMAP_KEY_RANGE(HUP_BUTTON, number_from, number_to, code) } +#define HGAME_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } +#define HGAME_MAP_CRG(usage_from, usage_to, callback) \ + { HIDMAP_ANY_CB_RANGE(HUP_GENERIC_DESKTOP, \ + HUG_##usage_from, HUG_##usage_to, callback) } +#define HGAME_FINALCB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +static const struct hidmap_item hgame_map[] = { + HGAME_MAP_BRG(1, 16, BTN_TRIGGER), + HGAME_MAP_ABS(X, ABS_X), + HGAME_MAP_ABS(Y, ABS_Y), + HGAME_MAP_ABS(Z, ABS_Z), + HGAME_MAP_ABS(RX, ABS_RX), + HGAME_MAP_ABS(RY, ABS_RY), + HGAME_MAP_ABS(RZ, ABS_RZ), + HGAME_MAP_ABS(HAT_SWITCH, ABS_HAT0X), + HGAME_MAP_CRG(D_PAD_UP, D_PAD_LEFT, hgame_dpad_cb), + HGAME_MAP_BRG(17, 57, BTN_TRIGGER_HAPPY), + HGAME_FINALCB( hgame_final_cb), +}; + +static const struct hid_device_id hgame_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_JOYSTICK), + HID_DRIVER_INFO(HUG_JOYSTICK) }, + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_GAME_PAD), + HID_DRIVER_INFO(HUG_GAME_PAD) }, +}; + +/* + * Emulate the hat switch report via the D-pad usages + * found on XInput/XBox style devices + */ +int +hgame_dpad_cb(HIDMAP_CB_ARGS) +{ + struct hgame_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t data; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + HIDMAP_CB_UDATA64 = HID_GET_USAGE(ctx.hi->usage); + evdev_support_event(evdev, EV_ABS); + evdev_support_abs(evdev, ABS_HAT0X, -1, 1, 0, 0, 0); + evdev_support_abs(evdev, ABS_HAT0Y, -1, 1, 0, 0, 0); + break; + + case HIDMAP_CB_IS_RUNNING: + data = ctx.data; + switch (HIDMAP_CB_UDATA64) { + case HUG_D_PAD_UP: + if (sc->dpad_down) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : -1); + sc->dpad_up = (data != 0); + break; + case HUG_D_PAD_DOWN: + if (sc->dpad_up) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0Y, (data == 0) ? 0 : 1); + sc->dpad_down = (data != 0); + break; + case HUG_D_PAD_RIGHT: + if (sc->dpad_left) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : 1); + sc->dpad_right = (data != 0); + break; + case HUG_D_PAD_LEFT: + if (sc->dpad_right) + return (ENOMSG); + evdev_push_abs(evdev, ABS_HAT0X, (data == 0) ? 0 : -1); + sc->dpad_left = (data != 0); + break; + } + } + + return (0); +} + +int +hgame_final_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +hgame_probe(device_t dev) +{ + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hgame_softc *sc = device_get_softc(dev); + int error; + + if (hid_test_quirk(hw, HQ_IS_XBOX360GP)) + return(ENXIO); + + error = HIDMAP_PROBE(&sc->hm, dev, hgame_devs, hgame_map, NULL); + if (error > 0) + return (error); + + hidbus_set_desc(dev, hidbus_get_driver_info(dev) == HUG_GAME_PAD ? + "Gamepad" : "Joystick"); + + return (BUS_PROBE_GENERIC); +} + + + +static int +hgame_attach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_attach(&sc->hm)); +} + +static int +hgame_detach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t hgame_devclass; +static device_method_t hgame_methods[] = { + DEVMETHOD(device_probe, hgame_probe), + DEVMETHOD(device_attach, hgame_attach), + DEVMETHOD(device_detach, hgame_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hgame, hgame_driver, hgame_methods, sizeof(struct hgame_softc)); +DRIVER_MODULE(hgame, hidbus, hgame_driver, hgame_devclass, NULL, 0); +MODULE_DEPEND(hgame, hid, 1, 1, 1); +MODULE_DEPEND(hgame, hidbus, 1, 1, 1); +MODULE_DEPEND(hgame, hidmap, 1, 1, 1); +MODULE_DEPEND(hgame, evdev, 1, 1, 1); +MODULE_VERSION(hgame, 1); +HID_PNP_INFO(hgame_devs); diff --git a/sys/dev/hid/hidmap.h b/sys/dev/hid/hidmap.h new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hidmap.h @@ -0,0 +1,262 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _HIDMAP_H_ +#define _HIDMAP_H_ + +#include + +#include + +#define HIDMAP_MAX_MAPS 4 + +struct hid_device_id; +struct hidmap_hid_item; +struct hidmap_item; +struct hidmap; + +enum hidmap_cb_state { + HIDMAP_CB_IS_PROBING, + HIDMAP_CB_IS_ATTACHING, + HIDMAP_CB_IS_RUNNING, + HIDMAP_CB_IS_DETACHING, +}; + +#define HIDMAP_KEY_NULL 0xFF /* Special event code to discard input */ + +/* Third parameter of hidmap callback has different type depending on state */ +union hidmap_cb_ctx { + struct hid_item *hi; /* Probe- and attach-stage callbacks */ + int32_t data; /* Run-stage callbacks */ + uint8_t rid; /* Run-stage finalizing callbacks */ +}; + +#define HIDMAP_CB_ARGS \ + struct hidmap *hm, struct hidmap_hid_item *hi, union hidmap_cb_ctx ctx +typedef int hidmap_cb_t(HIDMAP_CB_ARGS); + +/* These helpers can be used at any stage of any callbacks */ +#define HIDMAP_CB_GET_STATE(...) \ + ((hm == NULL) ? HIDMAP_CB_IS_PROBING : hm->cb_state) +#define HIDMAP_CB_GET_SOFTC(...) \ + (hm == NULL ? NULL : device_get_softc(hm->dev)) +#define HIDMAP_CB_GET_EVDEV(...) \ + (hm == NULL ? NULL : hm->evdev) +#define HIDMAP_CB_UDATA (hi->udata) +#define HIDMAP_CB_UDATA64 (hi->udata64) +/* Special helpers for run-stage of finalizing callbacks */ +#define HIDMAP_CB_GET_RID(...) (ctx.rid) +#define HIDMAP_CB_GET_DATA(loc) \ + hid_get_data(hm->intr_buf, hm->intr_len, (loc)) +#define HIDMAP_CB_GET_UDATA(loc) \ + hid_get_udata(hm->intr_buf, hm->intr_len, (loc)) + +enum hidmap_relabs { + HIDMAP_RELABS_ANY = 0, + HIDMAP_RELATIVE, + HIDMAP_ABSOLUTE, +}; + +struct hidmap_item { + union { + struct { + uint16_t type; /* Evdev event type */ + uint16_t code; /* Evdev event code */ + uint16_t fuzz; /* Evdev event fuzz */ + uint16_t flat; /* Evdev event flat */ + }; + hidmap_cb_t *cb; /* Reporting callback */ + }; + int32_t usage; /* HID usage (base) */ + uint16_t nusages; /* number of usages */ + bool required:1; /* Required by driver */ + enum hidmap_relabs relabs:2; + bool has_cb:1; + bool final_cb:1; + bool invert_value:1; + u_int reserved:10; +}; + +#define HIDMAP_ANY(_page, _usage, _type, _code) \ + .usage = HID_USAGE2((_page), (_usage)), \ + .nusages = 1, \ + .type = (_type), \ + .code = (_code) +#define HIDMAP_ANY_RANGE(_page, _usage_from, _usage_to, _type, _code) \ + .usage = HID_USAGE2((_page), (_usage_from)), \ + .nusages = (_usage_to) - (_usage_from) + 1, \ + .type = (_type), \ + .code = (_code) +#define HIDMAP_ANY_CB(_page, _usage, _callback) \ + .usage = HID_USAGE2((_page), (_usage)), \ + .nusages = 1, \ + .cb = (_callback), \ + .has_cb = true +#define HIDMAP_ANY_CB_RANGE(_page, _usage_from, _usage_to, _callback) \ + .usage = HID_USAGE2((_page), (_usage_from)), \ + .nusages = (_usage_to) - (_usage_from) + 1, \ + .cb = (_callback), \ + .has_cb = true +#define HIDMAP_KEY(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_KEY, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_KEY_RANGE(_page, _ufrom, _uto, _code) \ + HIDMAP_ANY_RANGE((_page), (_ufrom), (_uto), EV_KEY, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_REL(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_REL, (_code)), \ + .relabs = HIDMAP_RELATIVE +#define HIDMAP_ABS(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_ABS, (_code)), \ + .relabs = HIDMAP_ABSOLUTE +#define HIDMAP_SW(_page, _usage, _code) \ + HIDMAP_ANY((_page), (_usage), EV_SW, (_code)), \ + .relabs = HIDMAP_RELABS_ANY +#define HIDMAP_REL_CB(_page, _usage, _callback) \ + HIDMAP_ANY_CB((_page), (_usage), (_callback)), \ + .relabs = HIDMAP_RELATIVE +#define HIDMAP_ABS_CB(_page, _usage, _callback) \ + HIDMAP_ANY_CB((_page), (_usage), (_callback)), \ + .relabs = HIDMAP_ABSOLUTE +/* + * Special callback function which is not tied to particular HID input usage + * but called at the end evdev properties setting or interrupt handler + * just before evdev_register() or evdev_sync() calls. + */ +#define HIDMAP_FINAL_CB(_callback) \ + HIDMAP_ANY_CB(0, 0, (_callback)), .final_cb = true + +enum hidmap_type { + HIDMAP_TYPE_FINALCB = 0,/* No HID item associated. Runs unconditionally + * at the end of other items processing */ + HIDMAP_TYPE_CALLBACK, /* HID item is reported with user callback */ + HIDMAP_TYPE_VARIABLE, /* HID item is variable (single usage) */ + HIDMAP_TYPE_VAR_NULLST, /* HID item is null state variable */ + HIDMAP_TYPE_ARR_LIST, /* HID item is array with list of usages */ + HIDMAP_TYPE_ARR_RANGE, /* Array with range (min;max) of usages */ +}; + +struct hidmap_hid_item { + union { + hidmap_cb_t *cb; /* Callback */ + struct { /* Variable */ + uint16_t evtype; /* Evdev event type */ + uint16_t code; /* Evdev event code */ + }; + uint16_t *codes; /* Array list map type */ + int32_t umin; /* Array range map type */ + }; + union { + void *udata; /* Callback private context */ + uint64_t udata64; + int32_t last_val; /* Last reported value (var) */ + uint16_t last_key; /* Last reported key (array) */ + }; + struct hid_location loc; /* HID item location */ + int32_t lmin; /* HID item logical minimum */ + int32_t lmax; /* HID item logical maximum */ + enum hidmap_type type:8; + uint8_t id; /* Report ID */ + bool invert_value; +}; + +struct hidmap { + device_t dev; + + struct evdev_dev *evdev; + struct evdev_methods evdev_methods; + + /* Scatter-gather list of maps */ + int nmaps; + uint32_t nmap_items[HIDMAP_MAX_MAPS]; + const struct hidmap_item *map[HIDMAP_MAX_MAPS]; + + /* List of preparsed HID items */ + uint32_t nhid_items; + struct hidmap_hid_item *hid_items; + + /* Key event merging buffers */ + uint8_t *key_press; + uint8_t *key_rel; + uint16_t key_min; + uint16_t key_max; + + int *debug_var; + int debug_level; + enum hidmap_cb_state cb_state; + void * intr_buf; + hid_size_t intr_len; +}; + +typedef uint8_t * hidmap_caps_t; +#define HIDMAP_CAPS_SZ(nitems) howmany((nitems), 8) +#define HIDMAP_CAPS(name, map) uint8_t (name)[HIDMAP_CAPS_SZ(nitems(map))] +static inline bool +hidmap_test_cap(hidmap_caps_t caps, int cap) +{ + return (isset(caps, cap) != 0); +} + +/* + * It is safe to call any of following procedures in device_probe context + * that makes possible to write probe-only drivers with attach/detach handlers + * inherited from hidmap. See hcons and hsctrl drivers for example. + */ +static inline void +hidmap_set_dev(struct hidmap *hm, device_t dev) +{ + hm->dev = dev; +} + +/* Hack to avoid #ifdef-ing of hidmap_set_debug_var in hidmap based drivers */ +#ifdef HID_DEBUG +#define hidmap_set_debug_var(h, d) _hidmap_set_debug_var((h), (d)) +#else +#define hidmap_set_debug_var(...) +#endif +void _hidmap_set_debug_var(struct hidmap *hm, int *debug_var); +#define HIDMAP_ADD_MAP(hm, map, caps) \ + hidmap_add_map((hm), (map), nitems(map), (caps)) +uint32_t hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map, + int nitems_map, hidmap_caps_t caps); + +/* Versions of evdev_* functions capable to merge key events with same codes */ +void hidmap_support_key(struct hidmap *hm, uint16_t key); +void hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value); + +void hidmap_intr(void *context, void *buf, hid_size_t len); +#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,832 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Abstract 1 to 1 HID input usage to evdev event mapper driver. + */ + +#include "opt_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#ifdef HID_DEBUG +#define DPRINTFN(hm, n, fmt, ...) do { \ + if ((hm)->debug_var != NULL && *(hm)->debug_var >= (n)) { \ + device_printf((hm)->dev, "%s: " fmt, \ + __FUNCTION__ ,##__VA_ARGS__); \ + } \ +} while (0) +#define DPRINTF(hm, ...) DPRINTFN(hm, 1, __VA_ARGS__) +#else +#define DPRINTF(...) do { } while (0) +#define DPRINTFN(...) do { } while (0) +#endif + +static evdev_open_t hidmap_ev_open; +static evdev_close_t hidmap_ev_close; + +#define HIDMAP_WANT_MERGE_KEYS(hm) ((hm)->key_rel != NULL) + +#define HIDMAP_FOREACH_ITEM(hm, mi, uoff) \ + for (u_int _map = 0, _item = 0, _uoff_priv = -1; \ + ((mi) = hidmap_get_next_map_item( \ + (hm), &_map, &_item, &_uoff_priv, &(uoff))) != NULL;) + +static inline bool +hidmap_get_next_map_index(const struct hidmap_item *map, int nmap_items, + uint32_t *index, uint16_t *usage_offset) +{ + + ++*usage_offset; + if ((*index != 0 || *usage_offset != 0) && + *usage_offset >= map[*index].nusages) { + ++*index; + *usage_offset = 0; + } + + return (*index < nmap_items); +} + +static inline const struct hidmap_item * +hidmap_get_next_map_item(struct hidmap *hm, u_int *map, u_int *item, + u_int *uoff_priv, uint16_t *uoff) +{ + + *uoff = *uoff_priv; + while (!hidmap_get_next_map_index( + hm->map[*map], hm->nmap_items[*map], item, uoff)) { + ++*map; + *item = 0; + *uoff = -1; + if (*map >= hm->nmaps) + return (NULL); + } + *uoff_priv = *uoff; + + return (hm->map[*map] + *item); +} + +void +_hidmap_set_debug_var(struct hidmap *hm, int *debug_var) +{ +#ifdef HID_DEBUG + hm->debug_var = debug_var; +#endif +} + +static int +hidmap_ev_close(struct evdev_dev *evdev) +{ + return (hidbus_intr_stop(evdev_get_softc(evdev))); +} + +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)); +} + +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_set_flag(hm->evdev, EVDEV_FLAG_EXT_EPOCH); /* hidbus child */ + evdev_support_event(hm->evdev, EV_SYN); + error = hidmap_parse_hid_descr(hm, hidbus_get_index(hm->dev)); + if (error) { + DPRINTF(hm, "error=%d\n", error); + hidmap_detach(hm); + return (ENXIO); + } + + evdev_set_methods(hm->evdev, hm->dev, &hm->evdev_methods); + hm->cb_state = HIDMAP_CB_IS_RUNNING; + + error = evdev_register(hm->evdev); + if (error) { + DPRINTF(hm, "error=%d\n", error); + hidmap_detach(hm); + return (ENXIO); + } + + return (0); +} + +int +hidmap_detach(struct hidmap* hm) +{ + struct hidmap_hid_item *hi; + + DPRINTFN(hm, 11, "\n"); + + hm->cb_state = HIDMAP_CB_IS_DETACHING; + + evdev_free(hm->evdev); + if (hm->hid_items != NULL) { + for (hi = hm->hid_items; + hi < hm->hid_items + hm->nhid_items; + hi++) + if (hi->type == HIDMAP_TYPE_FINALCB || + hi->type == HIDMAP_TYPE_CALLBACK) + hi->cb(hm, hi, (union hidmap_cb_ctx){}); + else if (hi->type == HIDMAP_TYPE_ARR_LIST) + free(hi->codes, M_DEVBUF); + free(hm->hid_items, M_DEVBUF); + } + + free(hm->key_press, M_DEVBUF); + free(hm->key_rel, M_DEVBUF); + + return (0); +} + +MODULE_DEPEND(hidmap, hid, 1, 1, 1); +MODULE_DEPEND(hidmap, hidbus, 1, 1, 1); +MODULE_DEPEND(hidmap, evdev, 1, 1, 1); +MODULE_VERSION(hidmap, 1); diff --git a/sys/dev/hid/hms.c b/sys/dev/hid/hms.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hms.c @@ -0,0 +1,267 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +static const uint8_t hms_boot_desc[] = { HID_MOUSE_BOOTPROTO_DESCR() }; + +enum { + HMS_REL_X, + HMS_REL_Y, + HMS_REL_Z, + HMS_ABS_X, + HMS_ABS_Y, + HMS_ABS_Z, + HMS_HWHEEL, + HMS_BTN, + HMS_BTN_MS1, + HMS_BTN_MS2, + HMS_FINAL_CB, +}; + +static hidmap_cb_t hms_final_cb; + +#define HMS_MAP_BUT_RG(usage_from, usage_to, code) \ + { HIDMAP_KEY_RANGE(HUP_BUTTON, usage_from, usage_to, code) } +#define HMS_MAP_BUT_MS(usage, code) \ + { HIDMAP_KEY(HUP_MICROSOFT, usage, code) } +#define HMS_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, usage, code) } +#define HMS_MAP_REL(usage, code) \ + { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code) } +#define HMS_MAP_REL_REV(usage, code) \ + { HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code), .invert_value = true } +#define HMS_MAP_REL_CN(usage, code) \ + { HIDMAP_REL(HUP_CONSUMER, usage, code) } +#define HMS_FINAL_CB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +static const struct hidmap_item hms_map[] = { + [HMS_REL_X] = HMS_MAP_REL(HUG_X, REL_X), + [HMS_REL_Y] = HMS_MAP_REL(HUG_Y, REL_Y), + [HMS_REL_Z] = HMS_MAP_REL(HUG_Z, REL_Z), + [HMS_ABS_X] = HMS_MAP_ABS(HUG_X, ABS_X), + [HMS_ABS_Y] = HMS_MAP_ABS(HUG_Y, ABS_Y), + [HMS_ABS_Z] = HMS_MAP_ABS(HUG_Z, ABS_Z), + [HMS_HWHEEL] = HMS_MAP_REL_CN(HUC_AC_PAN, REL_HWHEEL), + [HMS_BTN] = HMS_MAP_BUT_RG(1, 16, BTN_MOUSE), + [HMS_BTN_MS1] = HMS_MAP_BUT_MS(1, BTN_RIGHT), + [HMS_BTN_MS2] = HMS_MAP_BUT_MS(2, BTN_MIDDLE), + [HMS_FINAL_CB] = HMS_FINAL_CB(hms_final_cb), +}; + +static const struct hidmap_item hms_map_wheel[] = { + HMS_MAP_REL(HUG_WHEEL, REL_WHEEL), +}; +static const struct hidmap_item hms_map_wheel_rev[] = { + HMS_MAP_REL_REV(HUG_WHEEL, REL_WHEEL), +}; + +/* A match on these entries will load hms */ +static const struct hid_device_id hms_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE) }, +}; + +struct hms_softc { + struct hidmap hm; + HIDMAP_CAPS(caps, hms_map); +}; + +static int +hms_final_cb(HIDMAP_CB_ARGS) +{ + struct hms_softc *sc = HIDMAP_CB_GET_SOFTC(); + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) { + if (hidmap_test_cap(sc->caps, HMS_ABS_X) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + else + evdev_support_prop(evdev, INPUT_PROP_POINTER); + } + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static void +hms_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + void *d_ptr; + hid_size_t d_len; + int error; + + /* + * If device claimed boot protocol support but do not have report + * descriptor, load one defined in "Appendix B.2" of HID1_11.pdf + */ + error = hid_get_report_descr(parent, &d_ptr, &d_len); + if ((error != 0 && hid_test_quirk(hw, HQ_HAS_MS_BOOTPROTO)) || + (error == 0 && hid_test_quirk(hw, HQ_MS_BOOTPROTO) && + hid_is_mouse(d_ptr, d_len))) + (void)hid_set_report_descr(parent, hms_boot_desc, + sizeof(hms_boot_desc)); +} + +static int +hms_probe(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + int error; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hms_devs); + if (error != 0) + return (error); + + hidmap_set_dev(&sc->hm, dev); + + /* Check if report descriptor belongs to mouse */ + error = HIDMAP_ADD_MAP(&sc->hm, hms_map, sc->caps); + if (error != 0) + return (error); + + /* There should be at least one X or Y axis */ + if (!hidmap_test_cap(sc->caps, HMS_REL_X) && + !hidmap_test_cap(sc->caps, HMS_REL_X) && + !hidmap_test_cap(sc->caps, HMS_ABS_X) && + !hidmap_test_cap(sc->caps, HMS_ABS_Y)) + return (ENXIO); + + if (hidmap_test_cap(sc->caps, HMS_ABS_X) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) + hidbus_set_desc(dev, "Tablet"); + else + hidbus_set_desc(dev, "Mouse"); + + return (BUS_PROBE_DEFAULT); +} + +static int +hms_attach(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hidmap_hid_item *hi; + HIDMAP_CAPS(cap_wheel, hms_map_wheel); + void *d_ptr; + hid_size_t d_len; + bool set_report_proto; + int error, nbuttons = 0; + + /* + * Set the report (non-boot) protocol if report descriptor has not been + * overloaded with boot protocol report descriptor. + * + * Mice without boot protocol support may choose not to implement + * Set_Protocol at all; Ignore any error. + */ + error = hid_get_report_descr(dev, &d_ptr, &d_len); + set_report_proto = !(error == 0 && d_len == sizeof(hms_boot_desc) && + memcmp(d_ptr, hms_boot_desc, sizeof(hms_boot_desc)) == 0); + (void)hid_set_protocol(dev, set_report_proto ? 1 : 0); + + if (hid_test_quirk(hw, HQ_MS_REVZ)) + HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel_rev, cap_wheel); + else + HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel, cap_wheel); + + error = hidmap_attach(&sc->hm); + if (error) + return (error); + + /* Count number of input usages of variable type mapped to buttons */ + for (hi = sc->hm.hid_items; + hi < sc->hm.hid_items + sc->hm.nhid_items; + hi++) + if (hi->type == HIDMAP_TYPE_VARIABLE && hi->evtype == EV_KEY) + nbuttons++; + + /* announce information about the mouse */ + device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n", + nbuttons, + (hidmap_test_cap(sc->caps, HMS_REL_X) || + hidmap_test_cap(sc->caps, HMS_ABS_X)) ? "X" : "", + (hidmap_test_cap(sc->caps, HMS_REL_Y) || + hidmap_test_cap(sc->caps, HMS_ABS_Y)) ? "Y" : "", + (hidmap_test_cap(sc->caps, HMS_REL_Z) || + hidmap_test_cap(sc->caps, HMS_ABS_Z)) ? "Z" : "", + hidmap_test_cap(cap_wheel, 0) ? "W" : "", + hidmap_test_cap(sc->caps, HMS_HWHEEL) ? "H" : "", + sc->hm.hid_items[0].id); + + return (0); +} + +static int +hms_detach(device_t dev) +{ + struct hms_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t hms_devclass; +static device_method_t hms_methods[] = { + DEVMETHOD(device_identify, hms_identify), + DEVMETHOD(device_probe, hms_probe), + DEVMETHOD(device_attach, hms_attach), + DEVMETHOD(device_detach, hms_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hms, hms_driver, hms_methods, sizeof(struct hms_softc)); +DRIVER_MODULE(hms, hidbus, hms_driver, hms_devclass, NULL, 0); +MODULE_DEPEND(hms, hid, 1, 1, 1); +MODULE_DEPEND(hms, hidbus, 1, 1, 1); +MODULE_DEPEND(hms, hidmap, 1, 1, 1); +MODULE_DEPEND(hms, evdev, 1, 1, 1); +MODULE_VERSION(hms, 1); +HID_PNP_INFO(hms_devs); diff --git a/sys/dev/hid/hpen.c b/sys/dev/hid/hpen.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hpen.c @@ -0,0 +1,256 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2019 Vladimir Kondratyev + * Copyright (c) 2019 Greg V + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Generic / MS Windows compatible HID pen tablet driver: + * https://docs.microsoft.com/en-us/windows-hardware/design/component-guidelines/required-hid-top-level-collections + * + * Tested on: Wacom WCOM50C1 (Google Pixelbook "eve") + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include "usbdevs.h" + +static const uint8_t hpen_graphire_report_descr[] = + { HID_GRAPHIRE_REPORT_DESCR() }; +static const uint8_t hpen_graphire3_4x5_report_descr[] = + { HID_GRAPHIRE3_4X5_REPORT_DESCR() }; + +static hidmap_cb_t hpen_battery_strenght_cb; +static hidmap_cb_t hpen_final_digi_cb; +static hidmap_cb_t hpen_final_pen_cb; + +#define HPEN_MAP_BUT(usage, code) \ + HIDMAP_KEY(HUP_DIGITIZERS, HUD_##usage, code) +#define HPEN_MAP_ABS(usage, code) \ + HIDMAP_ABS(HUP_DIGITIZERS, HUD_##usage, code) +#define HPEN_MAP_ABS_GD(usage, code) \ + HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) +#define HPEN_MAP_ABS_CB(usage, cb) \ + HIDMAP_ABS_CB(HUP_DIGITIZERS, HUD_##usage, &cb) + +/* Generic map digitizer page map according to hut1_12v2.pdf */ +static const struct hidmap_item hpen_map_digi[] = { + { HPEN_MAP_ABS_GD(X, ABS_X), .required = true }, + { HPEN_MAP_ABS_GD(Y, ABS_Y), .required = true }, + { HPEN_MAP_ABS( TIP_PRESSURE, ABS_PRESSURE) }, + { HPEN_MAP_ABS( X_TILT, ABS_TILT_X) }, + { HPEN_MAP_ABS( Y_TILT, ABS_TILT_Y) }, + { HPEN_MAP_ABS_CB(BATTERY_STRENGTH, hpen_battery_strenght_cb) }, + { HPEN_MAP_BUT( TOUCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( TIP_SWITCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( SEC_TIP_SWITCH, BTN_TOUCH) }, + { HPEN_MAP_BUT( IN_RANGE, BTN_TOOL_PEN) }, + { HPEN_MAP_BUT( BARREL_SWITCH, BTN_STYLUS) }, + { HPEN_MAP_BUT( INVERT, BTN_TOOL_RUBBER) }, + { HPEN_MAP_BUT( ERASER, BTN_TOUCH) }, + { HPEN_MAP_BUT( TABLET_PICK, BTN_STYLUS2) }, + { HPEN_MAP_BUT( SEC_BARREL_SWITCH,BTN_STYLUS2) }, + { HIDMAP_FINAL_CB( &hpen_final_digi_cb) }, +}; + +/* Microsoft-standardized pen support */ +static const struct hidmap_item hpen_map_pen[] = { + { HPEN_MAP_ABS_GD(X, ABS_X), .required = true }, + { HPEN_MAP_ABS_GD(Y, ABS_Y), .required = true }, + { HPEN_MAP_ABS( TIP_PRESSURE, ABS_PRESSURE), .required = true }, + { HPEN_MAP_ABS( X_TILT, ABS_TILT_X) }, + { HPEN_MAP_ABS( Y_TILT, ABS_TILT_Y) }, + { HPEN_MAP_ABS_CB(BATTERY_STRENGTH, hpen_battery_strenght_cb) }, + { HPEN_MAP_BUT( TIP_SWITCH, BTN_TOUCH), .required = true }, + { HPEN_MAP_BUT( IN_RANGE, BTN_TOOL_PEN), .required = true }, + { HPEN_MAP_BUT( BARREL_SWITCH, BTN_STYLUS) }, + { HPEN_MAP_BUT( INVERT, BTN_TOOL_RUBBER), .required = true }, + { HPEN_MAP_BUT( ERASER, BTN_TOUCH), .required = true }, + { HIDMAP_FINAL_CB( &hpen_final_pen_cb) }, +}; + +static const struct hid_device_id hpen_devs[] = { + { HID_TLC(HUP_DIGITIZERS, HUD_DIGITIZER) }, + { HID_TLC(HUP_DIGITIZERS, HUD_PEN) }, +}; + +static int +hpen_battery_strenght_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + int32_t data; + + switch (HIDMAP_CB_GET_STATE()) { + case HIDMAP_CB_IS_ATTACHING: + evdev_support_event(evdev, EV_PWR); + /* TODO */ + break; + case HIDMAP_CB_IS_RUNNING: + data = ctx.data; + /* TODO */ + } + + return (0); +} + +static int +hpen_final_digi_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_POINTER); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static int +hpen_final_pen_cb(HIDMAP_CB_ARGS) +{ + struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV(); + + if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) + evdev_support_prop(evdev, INPUT_PROP_DIRECT); + + /* Do not execute callback at interrupt handler and detach */ + return (ENOSYS); +} + +static void +hpen_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + + /* the report descriptor for the Wacom Graphire is broken */ + if (hw->idBus == BUS_USB && hw->idVendor == USB_VENDOR_WACOM) { + switch (hw->idProduct) { + case USB_PRODUCT_WACOM_GRAPHIRE: + hid_set_report_descr(parent, + hpen_graphire_report_descr, + sizeof(hpen_graphire_report_descr)); + break; + + case USB_PRODUCT_WACOM_GRAPHIRE3_4X5: + hid_set_report_descr(parent, + hpen_graphire3_4x5_report_descr, + sizeof(hpen_graphire3_4x5_report_descr)); + break; + } + } +} + +static int +hpen_probe(device_t dev) +{ + struct hidmap *hm = device_get_softc(dev); + int error; + bool is_pen; + + error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hpen_devs); + if (error != 0) + return (error); + + hidmap_set_dev(hm, dev); + + /* Check if report descriptor belongs to a HID tablet device */ + is_pen = hidbus_get_usage(dev) == HID_USAGE2(HUP_DIGITIZERS, HUD_PEN); + error = is_pen + ? HIDMAP_ADD_MAP(hm, hpen_map_pen, NULL) + : HIDMAP_ADD_MAP(hm, hpen_map_digi, NULL); + if (error != 0) + return (error); + + hidbus_set_desc(dev, is_pen ? "Pen" : "Digitizer"); + + return (BUS_PROBE_DEFAULT); +} + +static int +hpen_attach(device_t dev) +{ + const struct hid_device_info *hw = hid_get_device_info(dev); + struct hidmap *hm = device_get_softc(dev); + int error; + + if (hw->idBus == BUS_USB && hw->idVendor == USB_VENDOR_WACOM && + hw->idProduct == USB_PRODUCT_WACOM_GRAPHIRE3_4X5) { + /* + * The Graphire3 needs 0x0202 to be written to + * feature report ID 2 before it'll start + * returning digitizer data. + */ + static const uint8_t reportbuf[3] = {2, 2, 2}; + error = hid_set_report(dev, reportbuf, sizeof(reportbuf), + HID_FEATURE_REPORT, reportbuf[0]); + if (error) + device_printf(dev, "set feature report failed, " + "error=%d (ignored)\n", error); + } + + return (hidmap_attach(hm)); +} + +static int +hpen_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + + +static devclass_t hpen_devclass; +static device_method_t hpen_methods[] = { + DEVMETHOD(device_identify, hpen_identify), + DEVMETHOD(device_probe, hpen_probe), + DEVMETHOD(device_attach, hpen_attach), + DEVMETHOD(device_detach, hpen_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hpen, hpen_driver, hpen_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hpen, hidbus, hpen_driver, hpen_devclass, NULL, 0); +MODULE_DEPEND(hpen, hid, 1, 1, 1); +MODULE_DEPEND(hpen, hidbus, 1, 1, 1); +MODULE_DEPEND(hpen, hidmap, 1, 1, 1); +MODULE_DEPEND(hpen, evdev, 1, 1, 1); +MODULE_VERSION(hpen, 1); +HID_PNP_INFO(hpen_devs); diff --git a/sys/dev/hid/hsctrl.c b/sys/dev/hid/hsctrl.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/hsctrl.c @@ -0,0 +1,110 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * General Desktop/System Controls usage page driver + * https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf + */ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#define HSCTRL_MAP(usage, code) \ + { HIDMAP_KEY(HUP_GENERIC_DESKTOP, HUG_SYSTEM_##usage, code) } + +static const struct hidmap_item hsctrl_map[] = { + HSCTRL_MAP(POWER_DOWN, KEY_POWER), + HSCTRL_MAP(SLEEP, KEY_SLEEP), + HSCTRL_MAP(WAKEUP, KEY_WAKEUP), + HSCTRL_MAP(CONTEXT_MENU, KEY_CONTEXT_MENU), + HSCTRL_MAP(MAIN_MENU, KEY_MENU), + HSCTRL_MAP(APP_MENU, KEY_PROG1), + HSCTRL_MAP(MENU_HELP, KEY_HELP), + HSCTRL_MAP(MENU_EXIT, KEY_EXIT), + HSCTRL_MAP(MENU_SELECT, KEY_SELECT), + HSCTRL_MAP(MENU_RIGHT, KEY_RIGHT), + HSCTRL_MAP(MENU_LEFT, KEY_LEFT), + HSCTRL_MAP(MENU_UP, KEY_UP), + HSCTRL_MAP(MENU_DOWN, KEY_DOWN), + HSCTRL_MAP(POWER_UP, KEY_POWER2), + HSCTRL_MAP(RESTART, KEY_RESTART), +}; + +static const struct hid_device_id hsctrl_devs[] = { + { HID_TLC(HUP_GENERIC_DESKTOP, HUG_SYSTEM_CONTROL) }, +}; + +static int +hsctrl_probe(device_t dev) +{ + return (HIDMAP_PROBE(device_get_softc(dev), dev, + hsctrl_devs, hsctrl_map, "System Control")); +} + +static int +hsctrl_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +hsctrl_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static devclass_t hsctrl_devclass; +static device_method_t hsctrl_methods[] = { + DEVMETHOD(device_probe, hsctrl_probe), + DEVMETHOD(device_attach, hsctrl_attach), + DEVMETHOD(device_detach, hsctrl_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(hsctrl, hsctrl_driver, hsctrl_methods, sizeof(struct hidmap)); +DRIVER_MODULE(hsctrl, hidbus, hsctrl_driver, hsctrl_devclass, NULL, 0); +MODULE_DEPEND(hsctrl, hid, 1, 1, 1); +MODULE_DEPEND(hsctrl, hidbus, 1, 1, 1); +MODULE_DEPEND(hsctrl, hidmap, 1, 1, 1); +MODULE_DEPEND(hsctrl, evdev, 1, 1, 1); +MODULE_VERSION(hsctrl, 1); +HID_PNP_INFO(hsctrl_devs); diff --git a/sys/dev/hid/ps4dshock.c b/sys/dev/hid/ps4dshock.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/ps4dshock.c @@ -0,0 +1,1406 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * Sony PS4 DualShock 4 driver + * https://eleccelerator.com/wiki/index.php?title=DualShock_4 + * https://gist.github.com/johndrinkwater/7708901 + * https://www.psdevwiki.com/ps4/DS4-USB + */ + +#include "opt_hid.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define HID_DEBUG_VAR ps4dshock_debug +#include +#include +#include +#include +#include "usbdevs.h" + +#ifdef HID_DEBUG +static int ps4dshock_debug = 1; + +static SYSCTL_NODE(_hw_hid, OID_AUTO, ps4dshock, CTLFLAG_RW, 0, + "Sony PS4 DualShock Gamepad"); +SYSCTL_INT(_hw_hid_ps4dshock, OID_AUTO, debug, CTLFLAG_RWTUN, + &ps4dshock_debug, 0, "Debug level"); +#endif + +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, + }; + + return (hid_write(sc->hm.dev, buf, osize)); +} + +/* Synaptics Touchpad */ +static int +ps4dshock_sysctl(SYSCTL_HANDLER_ARGS) +{ + struct ps4dshock_softc *sc; + int error, arg; + + if (oidp->oid_arg1 == NULL || oidp->oid_arg2 < 0 || + oidp->oid_arg2 > PD4DSHOCK_SYSCTL_LAST) + return (EINVAL); + + sc = oidp->oid_arg1; + sx_xlock(&sc->lock); + + /* Read the current value. */ + arg = *(int *)((char *)sc + oidp->oid_arg2); + error = sysctl_handle_int(oidp, &arg, 0, req); + + /* Sanity check. */ + if (error || !req->newptr) + goto unlock; + + /* + * Check that the new value is in the concerned node's range + * of values. + */ + switch (oidp->oid_arg2) { + case PD4DSHOCK_SYSCTL_LED_STATE: + if (arg < 0 || arg >= PD4DS_LED_CNT) + error = EINVAL; + break; + case PD4DSHOCK_SYSCTL_LED_COLOR_R: + case PD4DSHOCK_SYSCTL_LED_COLOR_G: + case PD4DSHOCK_SYSCTL_LED_COLOR_B: + if (arg < 0 || arg > UINT8_MAX) + error = EINVAL; + break; + case PD4DSHOCK_SYSCTL_LED_DELAY_ON: + case PD4DSHOCK_SYSCTL_LED_DELAY_OFF: + if (arg < 0 || arg > UINT8_MAX * 10) + error = EINVAL; + break; + default: + error = EINVAL; + } + + /* Update. */ + if (error == 0) { + *(int *)((char *)sc + oidp->oid_arg2) = arg; + ps4dshock_write(sc); + } +unlock: + sx_unlock(&sc->lock); + + return (error); +} + +static void +ps4dshock_identify(driver_t *driver, device_t parent) +{ + + /* Overload PS4 DualShock gamepad rudimentary report descriptor */ + if (HIDBUS_LOOKUP_ID(parent, ps4dshock_devs) != NULL) + hid_set_report_descr(parent, ps4dshock_rdesc, + sizeof(ps4dshock_rdesc)); +} + +static int +ps4dshock_probe(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dshock_devs, ps4dshock_map, NULL) + ); +} + +static int +ps4dsacc_probe(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dsacc_devs, ps4dsacc_map, "Sensors") + ); +} + +static int +ps4dshead_probe(device_t dev) +{ + struct hidmap *hm = device_get_softc(dev); + + hidmap_set_debug_var(hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(hm, dev, ps4dshead_devs, ps4dshead_map, "Headset") + ); +} + +static int +ps4dsmtp_probe(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_set_debug_var(&sc->hm, &HID_DEBUG_VAR); + return ( + HIDMAP_PROBE(&sc->hm, dev, ps4dsmtp_devs, ps4dsmtp_map, "Touchpad") + ); +} + +static int +ps4dshock_attach(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + struct sysctl_ctx_list *ctx = device_get_sysctl_ctx(dev); + struct sysctl_oid *tree = device_get_sysctl_tree(dev); + + sc->led_state = PS4DS_LED_ON; + sc->led_color = ps4ds_leds[device_get_unit(dev) % nitems(ps4ds_leds)]; + sc->led_delay_on = 500; /* 1 Hz */ + sc->led_delay_off = 500; + ps4dshock_write(sc); + + sx_init(&sc->lock, "ps4dshock"); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_state", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_STATE, ps4dshock_sysctl, "I", + "LED state: 0 - off, 1 - on, 2 - blinking."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_r", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_R, ps4dshock_sysctl, "I", + "LED color. Red component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_g", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_G, ps4dshock_sysctl, "I", + "LED color. Green component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_color_b", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_COLOR_B, ps4dshock_sysctl, "I", + "LED color. Blue component."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_delay_on", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_DELAY_ON, ps4dshock_sysctl, "I", + "LED blink. On delay, msecs."); + + SYSCTL_ADD_PROC(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, + "led_delay_off", CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_ANYBODY, sc, + PD4DSHOCK_SYSCTL_LED_DELAY_OFF, ps4dshock_sysctl, "I", + "LED blink. Off delay, msecs."); + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dsacc_attach(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + uint8_t buf[PS4DS_FEATURE_REPORT2_SIZE]; + int error, speed_2x, range_2g; + + /* Read accelerometers and gyroscopes calibration data */ + error = hid_get_report(dev, buf, sizeof(buf), NULL, + HID_FEATURE_REPORT, 0x02); + if (error) + DPRINTF("get feature report failed, error=%d " + "(ignored)\n", error); + + DPRINTFN(5, "calibration data: %*D\n", (int)sizeof(buf), buf, " "); + + /* + * Set gyroscope calibration and normalization parameters. + * Data values will be normalized to 1/ PS4DS_GYRO_RES_PER_DEG_S + * degree/s. + */ +#define HGETW(w) ((int16_t)((w)[0] | (((uint16_t)((w)[1])) << 8))) + speed_2x = HGETW(&buf[19]) + HGETW(&buf[21]); + sc->calib_data[0].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RX); + sc->calib_data[0].code = ABS_RX; + sc->calib_data[0].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[0].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[0].bias = HGETW(&buf[1]); + sc->calib_data[0].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[0].sens_denom = HGETW(&buf[7]) - HGETW(&buf[9]); + /* BT case */ + /* sc->calib_data[0].sens_denom = HGETW(&buf[7]) - HGETW(&buf[13]); */ + + sc->calib_data[1].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RY); + sc->calib_data[1].code = ABS_RY; + sc->calib_data[1].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[1].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[1].bias = HGETW(&buf[3]); + sc->calib_data[1].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[1].sens_denom = HGETW(&buf[11]) - HGETW(&buf[13]); + /* BT case */ + /* sc->calib_data[1].sens_denom = HGETW(&buf[9]) - HGETW(&buf[15]); */ + + sc->calib_data[2].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_RZ); + sc->calib_data[2].code = ABS_RZ; + sc->calib_data[2].range = PS4DS_GYRO_RES_PER_DEG_S * 2048; + sc->calib_data[2].res = PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[2].bias = HGETW(&buf[5]); + sc->calib_data[2].sens_numer = speed_2x * PS4DS_GYRO_RES_PER_DEG_S; + sc->calib_data[2].sens_denom = HGETW(&buf[15]) - HGETW(&buf[17]); + /* BT case */ + /* sc->calib_data[2].sens_denom = HGETW(&buf[11]) - HGETW(&buf[17]); */ + + /* + * Set accelerometer calibration and normalization parameters. + * Data values will be normalized to 1 / PS4DS_ACC_RES_PER_G G. + */ + range_2g = HGETW(&buf[23]) - HGETW(&buf[25]); + sc->calib_data[3].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_X); + sc->calib_data[3].code = ABS_X; + sc->calib_data[3].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[3].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[3].bias = HGETW(&buf[23]) - range_2g / 2; + sc->calib_data[3].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[3].sens_denom = range_2g; + + range_2g = HGETW(&buf[27]) - HGETW(&buf[29]); + sc->calib_data[4].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Y); + sc->calib_data[4].code = ABS_Y; + sc->calib_data[4].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[4].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[4].bias = HGETW(&buf[27]) - range_2g / 2; + sc->calib_data[4].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[4].sens_denom = range_2g; + + range_2g = HGETW(&buf[31]) - HGETW(&buf[33]); + sc->calib_data[5].usage = HID_USAGE2(HUP_GENERIC_DESKTOP, HUG_Z); + sc->calib_data[5].code = ABS_Z; + sc->calib_data[5].range = PS4DS_ACC_RES_PER_G * 4; + sc->calib_data[5].res = PS4DS_ACC_RES_PER_G; + sc->calib_data[5].bias = HGETW(&buf[31]) - range_2g / 2; + sc->calib_data[5].sens_numer = 2 * PS4DS_ACC_RES_PER_G; + sc->calib_data[5].sens_denom = range_2g; + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dshead_attach(device_t dev) +{ + return (hidmap_attach(device_get_softc(dev))); +} + +static int +ps4dsmtp_attach(device_t dev) +{ + struct ps4dsmtp_softc *sc = device_get_softc(dev); + + return (hidmap_attach(&sc->hm)); +} + +static int +ps4dshock_detach(device_t dev) +{ + struct ps4dshock_softc *sc = device_get_softc(dev); + + hidmap_detach(&sc->hm); + sc->led_state = PS4DS_LED_OFF; + ps4dshock_write(sc); + sx_destroy(&sc->lock); + + return (0); +} + +static int +ps4dsacc_detach(device_t dev) +{ + struct ps4dsacc_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static int +ps4dshead_detach(device_t dev) +{ + return (hidmap_detach(device_get_softc(dev))); +} + +static int +ps4dsmtp_detach(device_t dev) +{ + struct ps4dsmtp_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t ps4dshock_devclass; +static devclass_t ps4dsacc_devclass; +static devclass_t ps4dshead_devclass; +static devclass_t ps4dsmtp_devclass; + +static device_method_t ps4dshock_methods[] = { + DEVMETHOD(device_identify, ps4dshock_identify), + DEVMETHOD(device_probe, ps4dshock_probe), + DEVMETHOD(device_attach, ps4dshock_attach), + DEVMETHOD(device_detach, ps4dshock_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dsacc_methods[] = { + DEVMETHOD(device_probe, ps4dsacc_probe), + DEVMETHOD(device_attach, ps4dsacc_attach), + DEVMETHOD(device_detach, ps4dsacc_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dshead_methods[] = { + DEVMETHOD(device_probe, ps4dshead_probe), + DEVMETHOD(device_attach, ps4dshead_attach), + DEVMETHOD(device_detach, ps4dshead_detach), + + DEVMETHOD_END +}; +static device_method_t ps4dsmtp_methods[] = { + DEVMETHOD(device_probe, ps4dsmtp_probe), + DEVMETHOD(device_attach, ps4dsmtp_attach), + DEVMETHOD(device_detach, ps4dsmtp_detach), + + DEVMETHOD_END +}; + +DEFINE_CLASS_0(ps4dsacc, ps4dsacc_driver, ps4dsacc_methods, + sizeof(struct ps4dsacc_softc)); +DRIVER_MODULE(ps4dsacc, hidbus, ps4dsacc_driver, ps4dsacc_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dshead, ps4dshead_driver, ps4dshead_methods, + sizeof(struct hidmap)); +DRIVER_MODULE(ps4dshead, hidbus, ps4dshead_driver, ps4dshead_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dsmtp, ps4dsmtp_driver, ps4dsmtp_methods, + sizeof(struct ps4dsmtp_softc)); +DRIVER_MODULE(ps4dsmtp, hidbus, ps4dsmtp_driver, ps4dsmtp_devclass, NULL, 0); +DEFINE_CLASS_0(ps4dshock, ps4dshock_driver, ps4dshock_methods, + sizeof(struct ps4dshock_softc)); +DRIVER_MODULE(ps4dshock, hidbus, ps4dshock_driver, ps4dshock_devclass, NULL, 0); + +MODULE_DEPEND(ps4dshock, hid, 1, 1, 1); +MODULE_DEPEND(ps4dshock, hidbus, 1, 1, 1); +MODULE_DEPEND(ps4dshock, hidmap, 1, 1, 1); +MODULE_DEPEND(ps4dshock, evdev, 1, 1, 1); +MODULE_VERSION(ps4dshock, 1); +HID_PNP_INFO(ps4dshock_devs); diff --git a/sys/dev/hid/xb360gp.c b/sys/dev/hid/xb360gp.c new file mode 100644 --- /dev/null +++ b/sys/dev/hid/xb360gp.c @@ -0,0 +1,183 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause-FreeBSD + * + * Copyright (c) 2020 Vladimir Kondratyev + * Copyright (c) 2020 Greg V + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +__FBSDID("$FreeBSD$"); + +/* + * XBox 360 gamepad driver thanks to the custom descriptor in usbhid. + * + * Tested on: SVEN GC-5070 in both XInput (XBox 360) and DirectInput modes + */ + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +static const uint8_t xb360gp_rdesc[] = {HID_XB360GP_REPORT_DESCR()}; + +#define XB360GP_MAP_BUT(number, code) \ + { HIDMAP_KEY(HUP_BUTTON, number, code) } +#define XB360GP_MAP_ABS(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code) } +#define XB360GP_MAP_ABS_FLT(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), \ + .fuzz = 16, .flat = 128 } +#define XB360GP_MAP_ABS_INV(usage, code) \ + { HIDMAP_ABS(HUP_GENERIC_DESKTOP, HUG_##usage, code), \ + .fuzz = 16, .flat = 128, .invert_value = true } +#define XB360GP_MAP_CRG(usage_from, usage_to, callback) \ + { HIDMAP_ANY_CB_RANGE(HUP_GENERIC_DESKTOP, \ + HUG_##usage_from, HUG_##usage_to, callback) } +#define XB360GP_FINALCB(cb) \ + { HIDMAP_FINAL_CB(&cb) } + +/* Customized to match usbhid's XBox 360 descriptor */ +static const struct hidmap_item xb360gp_map[] = { + XB360GP_MAP_BUT(1, BTN_SOUTH), + XB360GP_MAP_BUT(2, BTN_EAST), + XB360GP_MAP_BUT(3, BTN_WEST), + XB360GP_MAP_BUT(4, BTN_NORTH), + XB360GP_MAP_BUT(5, BTN_TL), + XB360GP_MAP_BUT(6, BTN_TR), + XB360GP_MAP_BUT(7, BTN_SELECT), + XB360GP_MAP_BUT(8, BTN_START), + XB360GP_MAP_BUT(9, BTN_THUMBL), + XB360GP_MAP_BUT(10, BTN_THUMBR), + XB360GP_MAP_BUT(11, BTN_MODE), + XB360GP_MAP_CRG(D_PAD_UP, D_PAD_LEFT, hgame_dpad_cb), + XB360GP_MAP_ABS_FLT(X, ABS_X), + XB360GP_MAP_ABS_INV(Y, ABS_Y), + XB360GP_MAP_ABS(Z, ABS_Z), + XB360GP_MAP_ABS_FLT(RX, ABS_RX), + XB360GP_MAP_ABS_INV(RY, ABS_RY), + XB360GP_MAP_ABS(RZ, ABS_RZ), + XB360GP_FINALCB( hgame_final_cb), +}; + +static const STRUCT_USB_HOST_ID xb360gp_devs[] = { + /* the Xbox 360 gamepad doesn't use the HID class */ + {USB_IFACE_CLASS(UICLASS_VENDOR), + USB_IFACE_SUBCLASS(UISUBCLASS_XBOX360_CONTROLLER), + USB_IFACE_PROTOCOL(UIPROTO_XBOX360_GAMEPAD),}, +}; + +static void +xb360gp_identify(driver_t *driver, device_t parent) +{ + const struct hid_device_info *hw = hid_get_device_info(parent); + + /* the Xbox 360 gamepad has no report descriptor */ + if (hid_test_quirk(hw, HQ_IS_XBOX360GP)) + hid_set_report_descr(parent, xb360gp_rdesc, + sizeof(xb360gp_rdesc)); +} + +static int +xb360gp_probe(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + const struct hid_device_info *hw = hid_get_device_info(dev); + int error; + + if (!hid_test_quirk(hw, HQ_IS_XBOX360GP)) + return (ENXIO); + + hidmap_set_dev(&sc->hm, dev); + + error = HIDMAP_ADD_MAP(&sc->hm, xb360gp_map, NULL); + if (error != 0) + return (error); + + device_set_desc(dev, "XBox 360 Gamepad"); + + return (BUS_PROBE_DEFAULT); +} + +static int +xb360gp_attach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + int error; + + /* + * Turn off the four LEDs on the gamepad which + * are blinking by default: + */ + static const uint8_t reportbuf[3] = {1, 3, 0}; + error = hid_set_report(dev, reportbuf, sizeof(reportbuf), + HID_OUTPUT_REPORT, 0); + if (error) + device_printf(dev, "set output report failed, error=%d " + "(ignored)\n", error); + + return (hidmap_attach(&sc->hm)); +} + +static int +xb360gp_detach(device_t dev) +{ + struct hgame_softc *sc = device_get_softc(dev); + + return (hidmap_detach(&sc->hm)); +} + +static devclass_t xb360gp_devclass; +static device_method_t xb360gp_methods[] = { + DEVMETHOD(device_identify, xb360gp_identify), + DEVMETHOD(device_probe, xb360gp_probe), + DEVMETHOD(device_attach, xb360gp_attach), + DEVMETHOD(device_detach, xb360gp_detach), + DEVMETHOD_END +}; + +DEFINE_CLASS_0(xb360gp, xb360gp_driver, xb360gp_methods, + sizeof(struct hgame_softc)); +DRIVER_MODULE(xb360gp, hidbus, xb360gp_driver, xb360gp_devclass, NULL, 0); +MODULE_DEPEND(xb360gp, hid, 1, 1, 1); +MODULE_DEPEND(xb360gp, hidbus, 1, 1, 1); +MODULE_DEPEND(xb360gp, hidmap, 1, 1, 1); +MODULE_DEPEND(xb360gp, hgame, 1, 1, 1); +MODULE_DEPEND(xb360gp, evdev, 1, 1, 1); +MODULE_VERSION(xb360gp, 1); +USB_PNP_HOST_INFO(xb360gp_devs); diff --git a/sys/modules/hid/Makefile b/sys/modules/hid/Makefile --- a/sys/modules/hid/Makefile +++ b/sys/modules/hid/Makefile @@ -3,12 +3,20 @@ SUBDIR = \ hid \ hidbus \ + hidmap \ hidquirk \ hidraw SUBDIR += \ hconf \ + hcons \ + hgame \ hkbd \ - hmt + hms \ + hmt \ + hpen \ + hsctrl \ + ps4dshock \ + xb360gp .include diff --git a/sys/modules/hid/hcons/Makefile b/sys/modules/hid/hcons/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hcons/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hcons +SRCS= hcons.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hgame/Makefile b/sys/modules/hid/hgame/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hgame/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hgame +SRCS= hgame.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hidmap/Makefile b/sys/modules/hid/hidmap/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hidmap/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hidmap +SRCS= hidmap.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hms/Makefile b/sys/modules/hid/hms/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hms/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hms +SRCS= hms.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/hpen/Makefile b/sys/modules/hid/hpen/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hpen/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hpen +SRCS= hpen.c +SRCS+= bus_if.h device_if.h usbdevs.h + +.include diff --git a/sys/modules/hid/hsctrl/Makefile b/sys/modules/hid/hsctrl/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/hsctrl/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= hsctrl +SRCS= hsctrl.c +SRCS+= bus_if.h device_if.h + +.include diff --git a/sys/modules/hid/ps4dshock/Makefile b/sys/modules/hid/ps4dshock/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/ps4dshock/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= ps4dshock +SRCS= ps4dshock.c +SRCS+= bus_if.h device_if.h usbdevs.h + +.include diff --git a/sys/modules/hid/xb360gp/Makefile b/sys/modules/hid/xb360gp/Makefile new file mode 100644 --- /dev/null +++ b/sys/modules/hid/xb360gp/Makefile @@ -0,0 +1,9 @@ +# $FreeBSD$ + +.PATH: ${SRCTOP}/sys/dev/hid + +KMOD= xb360gp +SRCS= xb360gp.c +SRCS+= bus_if.h device_if.h opt_usb.h + +.include