hid: Import hidmap and bunch of drivers based on it

hidmap is a kernel module that maps HID input usages to evdev events.

Following dependent drivers is included in the commit:

hms       - HID mouse driver.
hcons     - Consumer page AKA Multimedia keys driver.
hsctrl    - System Controls page (Power/Sleep keys) driver.
ps4dshock - Sony DualShock 4 gamepad driver.

Reviewed by:	hselasky
Differential revision:	https://reviews.freebsd.org/D27993
This commit is contained in:
Vladimir Kondratyev 2020-10-15 01:02:15 +03:00
parent 3b2175fdb6
commit afd590d9e5
18 changed files with 3652 additions and 1 deletions

View File

@ -180,11 +180,13 @@ MAN= aac.4 \
gre.4 \
h_ertt.4 \
hconf.4 \
hcons.4 \
hidbus.4 \
hidquirk.4 \
hidraw.4 \
hifn.4 \
hkbd.4 \
hms.4 \
hmt.4 \
hpet.4 \
${_hpt27xx.4} \
@ -192,6 +194,7 @@ MAN= aac.4 \
${_hptmv.4} \
${_hptnr.4} \
${_hptrr.4} \
hsctrl.4 \
${_hv_kvp.4} \
${_hv_netvsc.4} \
${_hv_storvsc.4} \
@ -432,6 +435,7 @@ MAN= aac.4 \
ppi.4 \
procdesc.4 \
proto.4 \
ps4dshock.4 \
psm.4 \
pst.4 \
pt.4 \

98
share/man/man4/hcons.4 Normal file
View File

@ -0,0 +1,98 @@
.\" Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 14, 2020
.Dt 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 .

115
share/man/man4/hms.4 Normal file
View File

@ -0,0 +1,115 @@
.\" Copyright (c)
.\" 1999 Nick Hibma <n_hibma@FreeBSD.org>. All rights reserved.
.\" 2020 Vladimir Kondratyev <wulf@FreeBSD.org>.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 12, 2020
.Dt HMS 4
.Os
.Sh NAME
.Nm hms
.Nd HID mouse driver
.Sh SYNOPSIS
To compile this driver into the kernel,
place the following lines in your
kernel configuration file:
.Bd -ragged -offset indent
.Cd "device hms"
.Cd "device hidbus"
.Cd "device hid"
.Cd "device evdev"
.Ed
.Pp
Alternatively, to load the driver as a
module at boot time, place the following line in
.Xr loader.conf 5 :
.Bd -literal -offset indent
hms_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides support for HID mice that attach to the HID transport
backend.
See
.Xr iichid 4
or
.Xr usbhid 4 .
Supported are
mice with any number of buttons, mice with a wheel and absolute mice.
.Pp
The
.Pa /dev/input/eventX
device presents the mouse as a
.Ar evdev
type device.
.Sh SYSCTL VARIABLES
The following 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 .

98
share/man/man4/hsctrl.4 Normal file
View File

@ -0,0 +1,98 @@
.\" Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 14, 2020
.Dt 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 .

109
share/man/man4/ps4dshock.4 Normal file
View File

@ -0,0 +1,109 @@
.\" Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" $FreeBSD$
.\"
.Dd September 19, 2020
.Dt PS4DSHOCK 4
.Os
.Sh NAME
.Nm ps4dshock
.Nd Sony PlayStation 4 Dualshock 4 gamepad driver
.Sh SYNOPSIS
To compile this driver into the kernel,
place the following lines in your
kernel configuration file:
.Bd -ragged -offset indent
.Cd "device ps4dshock"
.Cd "device hid"
.Cd "device hidbus"
.Cd "device hidmap"
.Cd "device evdev"
.Ed
.Pp
Alternatively, to load the driver as a
module at boot time, place the following line in
.Xr loader.conf 5 :
.Bd -literal -offset indent
ps4dshock_load="YES"
.Ed
.Sh DESCRIPTION
The
.Nm
driver provides support for Sony PlayStation 4 Dualshock 4 gamepad driver.
.Pp
The
.Pa /dev/input/event*
device presents the game controller as a
.Ar evdev
type device.
.Sh SYSCTL VARIABLES
Next parameters are available as
.Xr sysctl 8
variables.
Debug parameter is available as
.Xr loader 8
tunable as well.
.Bl -tag -width indent
.It Va dev.p4dshock.*.led_state
LED state: 0 - off, 1 - on, 2 - blinking.
.It Va dev.p4dshock.*.led_color_r
LED color.
Red component.
.It Va dev.p4dshock.*.led_color_g
LED color.
Green component.
.It Va dev.p4dshock.*.led_color_b
LED color.
Blue component.
.It Va dev.p4dshock.*.led_delay_on
LED blink.
On delay, msecs.
.It Va dev.p4dshock.*.led_delay_off
LED blink.
Off delay, msecs.
.It Va hw.hid.ps4dshock.debug
Debug output level, where 0 is debugging disabled and larger values increase
debug message verbosity.
Default is 0.
.El
.Sh FILES
.Bl -tag -width /dev/input/event* -compact
.It Pa /dev/input/event*
input event device node.
.El
.Sh BUGS
The
.Nm
does not support force-feedback events.
.Sh HISTORY
The
.Nm
driver first appeared in
.Fx 13.0.
.Sh AUTHORS
.An -nosplit
The
.Nm
driver was written by
.An Vladimir Kondratyev Aq Mt wulf@FreeBSD.org .

View File

@ -1816,13 +1816,18 @@ dev/gpio/gpiobus_if.m optional gpio
dev/gpio/gpiopps.c optional gpiopps fdt
dev/gpio/ofw_gpiobus.c optional fdt gpio
dev/hid/hconf.c optional hconf
dev/hid/hcons.c optional hcons
dev/hid/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/hsctrl.c optional hsctrl
dev/hid/ps4dshock.c optional ps4dshock
dev/hifn/hifn7751.c optional hifn
dev/hptiop/hptiop.c optional hptiop scbus
dev/hwpmc/hwpmc_logging.c optional hwpmc

295
sys/dev/hid/hcons.c Normal file
View File

@ -0,0 +1,295 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* Consumer Controls usage page driver
* https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
*/
#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidmap.h>
static hidmap_cb_t hcons_rel_volume_cb;
#define HCONS_MAP_KEY(usage, code) \
{ HIDMAP_KEY(HUP_CONSUMER, usage, code) }
#define HCONS_MAP_ABS(usage, code) \
{ HIDMAP_ABS(HUP_CONSUMER, usage, code) }
#define HCONS_MAP_REL(usage, code) \
{ HIDMAP_REL(HUP_CONSUMER, usage, code) }
#define HCONS_MAP_REL_CB(usage, callback) \
{ HIDMAP_REL_CB(HUP_CONSUMER, usage, &callback) }
static const struct hidmap_item hcons_map[] = {
HCONS_MAP_KEY(0x030, KEY_POWER),
HCONS_MAP_KEY(0x031, KEY_RESTART),
HCONS_MAP_KEY(0x032, KEY_SLEEP),
HCONS_MAP_KEY(0x034, KEY_SLEEP),
HCONS_MAP_KEY(0x035, KEY_KBDILLUMTOGGLE),
HCONS_MAP_KEY(0x036, BTN_MISC),
HCONS_MAP_KEY(0x040, KEY_MENU), /* Menu */
HCONS_MAP_KEY(0x041, KEY_SELECT), /* Menu Pick */
HCONS_MAP_KEY(0x042, KEY_UP), /* Menu Up */
HCONS_MAP_KEY(0x043, KEY_DOWN), /* Menu Down */
HCONS_MAP_KEY(0x044, KEY_LEFT), /* Menu Left */
HCONS_MAP_KEY(0x045, KEY_RIGHT), /* Menu Right */
HCONS_MAP_KEY(0x046, KEY_ESC), /* Menu Escape */
HCONS_MAP_KEY(0x047, KEY_KPPLUS), /* Menu Value Increase */
HCONS_MAP_KEY(0x048, KEY_KPMINUS), /* Menu Value Decrease */
HCONS_MAP_KEY(0x060, KEY_INFO), /* Data On Screen */
HCONS_MAP_KEY(0x061, KEY_SUBTITLE), /* Closed Caption */
HCONS_MAP_KEY(0x063, KEY_VCR), /* VCR/TV */
HCONS_MAP_KEY(0x065, KEY_CAMERA), /* Snapshot */
HCONS_MAP_KEY(0x069, KEY_RED),
HCONS_MAP_KEY(0x06a, KEY_GREEN),
HCONS_MAP_KEY(0x06b, KEY_BLUE),
HCONS_MAP_KEY(0x06c, KEY_YELLOW),
HCONS_MAP_KEY(0x06d, KEY_ASPECT_RATIO),
HCONS_MAP_KEY(0x06f, KEY_BRIGHTNESSUP),
HCONS_MAP_KEY(0x070, KEY_BRIGHTNESSDOWN),
HCONS_MAP_KEY(0x072, KEY_BRIGHTNESS_TOGGLE),
HCONS_MAP_KEY(0x073, KEY_BRIGHTNESS_MIN),
HCONS_MAP_KEY(0x074, KEY_BRIGHTNESS_MAX),
HCONS_MAP_KEY(0x075, KEY_BRIGHTNESS_AUTO),
HCONS_MAP_KEY(0x079, KEY_KBDILLUMUP),
HCONS_MAP_KEY(0x07a, KEY_KBDILLUMDOWN),
HCONS_MAP_KEY(0x07c, KEY_KBDILLUMTOGGLE),
HCONS_MAP_KEY(0x082, KEY_VIDEO_NEXT),
HCONS_MAP_KEY(0x083, KEY_LAST),
HCONS_MAP_KEY(0x084, KEY_ENTER),
HCONS_MAP_KEY(0x088, KEY_PC),
HCONS_MAP_KEY(0x089, KEY_TV),
HCONS_MAP_KEY(0x08a, KEY_WWW),
HCONS_MAP_KEY(0x08b, KEY_DVD),
HCONS_MAP_KEY(0x08c, KEY_PHONE),
HCONS_MAP_KEY(0x08d, KEY_PROGRAM),
HCONS_MAP_KEY(0x08e, KEY_VIDEOPHONE),
HCONS_MAP_KEY(0x08f, KEY_GAMES),
HCONS_MAP_KEY(0x090, KEY_MEMO),
HCONS_MAP_KEY(0x091, KEY_CD),
HCONS_MAP_KEY(0x092, KEY_VCR),
HCONS_MAP_KEY(0x093, KEY_TUNER),
HCONS_MAP_KEY(0x094, KEY_EXIT),
HCONS_MAP_KEY(0x095, KEY_HELP),
HCONS_MAP_KEY(0x096, KEY_TAPE),
HCONS_MAP_KEY(0x097, KEY_TV2),
HCONS_MAP_KEY(0x098, KEY_SAT),
HCONS_MAP_KEY(0x09a, KEY_PVR),
HCONS_MAP_KEY(0x09c, KEY_CHANNELUP),
HCONS_MAP_KEY(0x09d, KEY_CHANNELDOWN),
HCONS_MAP_KEY(0x0a0, KEY_VCR2),
HCONS_MAP_KEY(0x0b0, KEY_PLAY),
HCONS_MAP_KEY(0x0b1, KEY_PAUSE),
HCONS_MAP_KEY(0x0b2, KEY_RECORD),
HCONS_MAP_KEY(0x0b3, KEY_FASTFORWARD),
HCONS_MAP_KEY(0x0b4, KEY_REWIND),
HCONS_MAP_KEY(0x0b5, KEY_NEXTSONG),
HCONS_MAP_KEY(0x0b6, KEY_PREVIOUSSONG),
HCONS_MAP_KEY(0x0b7, KEY_STOPCD),
HCONS_MAP_KEY(0x0b8, KEY_EJECTCD),
HCONS_MAP_KEY(0x0bc, KEY_MEDIA_REPEAT),
HCONS_MAP_KEY(0x0b9, KEY_SHUFFLE),
HCONS_MAP_KEY(0x0bf, KEY_SLOW),
HCONS_MAP_KEY(0x0cd, KEY_PLAYPAUSE),
HCONS_MAP_KEY(0x0cf, KEY_VOICECOMMAND),
HCONS_MAP_ABS(0x0e0, ABS_VOLUME),
HCONS_MAP_REL_CB(0x0e0, hcons_rel_volume_cb),
HCONS_MAP_KEY(0x0e2, KEY_MUTE),
HCONS_MAP_KEY(0x0e5, KEY_BASSBOOST),
HCONS_MAP_KEY(0x0e9, KEY_VOLUMEUP),
HCONS_MAP_KEY(0x0ea, KEY_VOLUMEDOWN),
HCONS_MAP_KEY(0x0f5, KEY_SLOW),
HCONS_MAP_KEY(0x181, KEY_BUTTONCONFIG),
HCONS_MAP_KEY(0x182, KEY_BOOKMARKS),
HCONS_MAP_KEY(0x183, KEY_CONFIG),
HCONS_MAP_KEY(0x184, KEY_WORDPROCESSOR),
HCONS_MAP_KEY(0x185, KEY_EDITOR),
HCONS_MAP_KEY(0x186, KEY_SPREADSHEET),
HCONS_MAP_KEY(0x187, KEY_GRAPHICSEDITOR),
HCONS_MAP_KEY(0x188, KEY_PRESENTATION),
HCONS_MAP_KEY(0x189, KEY_DATABASE),
HCONS_MAP_KEY(0x18a, KEY_MAIL),
HCONS_MAP_KEY(0x18b, KEY_NEWS),
HCONS_MAP_KEY(0x18c, KEY_VOICEMAIL),
HCONS_MAP_KEY(0x18d, KEY_ADDRESSBOOK),
HCONS_MAP_KEY(0x18e, KEY_CALENDAR),
HCONS_MAP_KEY(0x18f, KEY_TASKMANAGER),
HCONS_MAP_KEY(0x190, KEY_JOURNAL),
HCONS_MAP_KEY(0x191, KEY_FINANCE),
HCONS_MAP_KEY(0x192, KEY_CALC),
HCONS_MAP_KEY(0x193, KEY_PLAYER),
HCONS_MAP_KEY(0x194, KEY_FILE),
HCONS_MAP_KEY(0x196, KEY_WWW),
HCONS_MAP_KEY(0x199, KEY_CHAT),
HCONS_MAP_KEY(0x19c, KEY_LOGOFF),
HCONS_MAP_KEY(0x19e, KEY_COFFEE),
HCONS_MAP_KEY(0x19f, KEY_CONTROLPANEL),
HCONS_MAP_KEY(0x1a2, KEY_APPSELECT),
HCONS_MAP_KEY(0x1a3, KEY_NEXT),
HCONS_MAP_KEY(0x1a4, KEY_PREVIOUS),
HCONS_MAP_KEY(0x1a6, KEY_HELP),
HCONS_MAP_KEY(0x1a7, KEY_DOCUMENTS),
HCONS_MAP_KEY(0x1ab, KEY_SPELLCHECK),
HCONS_MAP_KEY(0x1ae, KEY_KEYBOARD),
HCONS_MAP_KEY(0x1b1, KEY_SCREENSAVER),
HCONS_MAP_KEY(0x1b4, KEY_FILE),
HCONS_MAP_KEY(0x1b6, KEY_IMAGES),
HCONS_MAP_KEY(0x1b7, KEY_AUDIO),
HCONS_MAP_KEY(0x1b8, KEY_VIDEO),
HCONS_MAP_KEY(0x1bc, KEY_MESSENGER),
HCONS_MAP_KEY(0x1bd, KEY_INFO),
HCONS_MAP_KEY(0x1cb, KEY_ASSISTANT),
HCONS_MAP_KEY(0x201, KEY_NEW),
HCONS_MAP_KEY(0x202, KEY_OPEN),
HCONS_MAP_KEY(0x203, KEY_CLOSE),
HCONS_MAP_KEY(0x204, KEY_EXIT),
HCONS_MAP_KEY(0x207, KEY_SAVE),
HCONS_MAP_KEY(0x208, KEY_PRINT),
HCONS_MAP_KEY(0x209, KEY_PROPS),
HCONS_MAP_KEY(0x21a, KEY_UNDO),
HCONS_MAP_KEY(0x21b, KEY_COPY),
HCONS_MAP_KEY(0x21c, KEY_CUT),
HCONS_MAP_KEY(0x21d, KEY_PASTE),
HCONS_MAP_KEY(0x21f, KEY_FIND),
HCONS_MAP_KEY(0x221, KEY_SEARCH),
HCONS_MAP_KEY(0x222, KEY_GOTO),
HCONS_MAP_KEY(0x223, KEY_HOMEPAGE),
HCONS_MAP_KEY(0x224, KEY_BACK),
HCONS_MAP_KEY(0x225, KEY_FORWARD),
HCONS_MAP_KEY(0x226, KEY_STOP),
HCONS_MAP_KEY(0x227, KEY_REFRESH),
HCONS_MAP_KEY(0x22a, KEY_BOOKMARKS),
HCONS_MAP_KEY(0x22d, KEY_ZOOMIN),
HCONS_MAP_KEY(0x22e, KEY_ZOOMOUT),
HCONS_MAP_KEY(0x22f, KEY_ZOOMRESET),
HCONS_MAP_KEY(0x232, KEY_FULL_SCREEN),
HCONS_MAP_KEY(0x233, KEY_SCROLLUP),
HCONS_MAP_KEY(0x234, KEY_SCROLLDOWN),
HCONS_MAP_REL(0x238, REL_HWHEEL), /* AC Pan */
HCONS_MAP_KEY(0x23d, KEY_EDIT),
HCONS_MAP_KEY(0x25f, KEY_CANCEL),
HCONS_MAP_KEY(0x269, KEY_INSERT),
HCONS_MAP_KEY(0x26a, KEY_DELETE),
HCONS_MAP_KEY(0x279, KEY_REDO),
HCONS_MAP_KEY(0x289, KEY_REPLY),
HCONS_MAP_KEY(0x28b, KEY_FORWARDMAIL),
HCONS_MAP_KEY(0x28c, KEY_SEND),
HCONS_MAP_KEY(0x29d, KEY_KBD_LAYOUT_NEXT),
HCONS_MAP_KEY(0x2c7, KEY_KBDINPUTASSIST_PREV),
HCONS_MAP_KEY(0x2c8, KEY_KBDINPUTASSIST_NEXT),
HCONS_MAP_KEY(0x2c9, KEY_KBDINPUTASSIST_PREVGROUP),
HCONS_MAP_KEY(0x2ca, KEY_KBDINPUTASSIST_NEXTGROUP),
HCONS_MAP_KEY(0x2cb, KEY_KBDINPUTASSIST_ACCEPT),
HCONS_MAP_KEY(0x2cc, KEY_KBDINPUTASSIST_CANCEL),
HCONS_MAP_KEY(0x29f, KEY_SCALE),
};
static const struct hid_device_id hcons_devs[] = {
{ HID_TLC(HUP_CONSUMER, HUC_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);

832
sys/dev/hid/hidmap.c Normal file
View File

@ -0,0 +1,832 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* Abstract 1 to 1 HID input usage to evdev event mapper driver.
*/
#include "opt_hid.h"
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <sys/systm.h>
#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidmap.h>
#ifdef HID_DEBUG
#define DPRINTFN(hm, n, fmt, ...) do { \
if ((hm)->debug_var != NULL && *(hm)->debug_var >= (n)) { \
device_printf((hm)->dev, "%s: " fmt, \
__FUNCTION__ ,##__VA_ARGS__); \
} \
} while (0)
#define DPRINTF(hm, ...) DPRINTFN(hm, 1, __VA_ARGS__)
#else
#define DPRINTF(...) do { } while (0)
#define DPRINTFN(...) do { } while (0)
#endif
static evdev_open_t hidmap_ev_open;
static evdev_close_t hidmap_ev_close;
#define HIDMAP_WANT_MERGE_KEYS(hm) ((hm)->key_rel != NULL)
#define HIDMAP_FOREACH_ITEM(hm, mi, uoff) \
for (u_int _map = 0, _item = 0, _uoff_priv = -1; \
((mi) = hidmap_get_next_map_item( \
(hm), &_map, &_item, &_uoff_priv, &(uoff))) != NULL;)
static inline bool
hidmap_get_next_map_index(const struct hidmap_item *map, int nmap_items,
uint32_t *index, uint16_t *usage_offset)
{
++*usage_offset;
if ((*index != 0 || *usage_offset != 0) &&
*usage_offset >= map[*index].nusages) {
++*index;
*usage_offset = 0;
}
return (*index < nmap_items);
}
static inline const struct hidmap_item *
hidmap_get_next_map_item(struct hidmap *hm, u_int *map, u_int *item,
u_int *uoff_priv, uint16_t *uoff)
{
*uoff = *uoff_priv;
while (!hidmap_get_next_map_index(
hm->map[*map], hm->nmap_items[*map], item, uoff)) {
++*map;
*item = 0;
*uoff = -1;
if (*map >= hm->nmaps)
return (NULL);
}
*uoff_priv = *uoff;
return (hm->map[*map] + *item);
}
void
_hidmap_set_debug_var(struct hidmap *hm, int *debug_var)
{
#ifdef HID_DEBUG
hm->debug_var = debug_var;
#endif
}
static int
hidmap_ev_close(struct evdev_dev *evdev)
{
return (hidbus_intr_stop(evdev_get_softc(evdev)));
}
static int
hidmap_ev_open(struct evdev_dev *evdev)
{
return (hidbus_intr_start(evdev_get_softc(evdev)));
}
void
hidmap_support_key(struct hidmap *hm, uint16_t key)
{
if (hm->key_press == NULL) {
hm->key_press = malloc(howmany(KEY_CNT, 8), M_DEVBUF,
M_ZERO | M_WAITOK);
evdev_support_event(hm->evdev, EV_KEY);
hm->key_min = key;
hm->key_max = key;
}
hm->key_min = MIN(hm->key_min, key);
hm->key_max = MAX(hm->key_max, key);
if (isset(hm->key_press, key)) {
if (hm->key_rel == NULL)
hm->key_rel = malloc(howmany(KEY_CNT, 8), M_DEVBUF,
M_ZERO | M_WAITOK);
} else {
setbit(hm->key_press, key);
evdev_support_key(hm->evdev, key);
}
}
void
hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value)
{
if (HIDMAP_WANT_MERGE_KEYS(hm))
setbit(value != 0 ? hm->key_press : hm->key_rel, key);
else
evdev_push_key(hm->evdev, key, value);
}
static void
hidmap_sync_keys(struct hidmap *hm)
{
int i, j;
bool press, rel;
for (j = hm->key_min / 8; j <= hm->key_max / 8; j++) {
if (hm->key_press[j] != hm->key_rel[j]) {
for (i = j * 8; i < j * 8 + 8; i++) {
press = isset(hm->key_press, i);
rel = isset(hm->key_rel, i);
if (press != rel)
evdev_push_key(hm->evdev, i, press);
}
}
}
bzero(hm->key_press, howmany(KEY_CNT, 8));
bzero(hm->key_rel, howmany(KEY_CNT, 8));
}
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 2s 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);

262
sys/dev/hid/hidmap.h Normal file
View File

@ -0,0 +1,262 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef _HIDMAP_H_
#define _HIDMAP_H_
#include <sys/param.h>
#include <dev/hid/hid.h>
#define HIDMAP_MAX_MAPS 4
struct hid_device_id;
struct hidmap_hid_item;
struct hidmap_item;
struct hidmap;
enum hidmap_cb_state {
HIDMAP_CB_IS_PROBING,
HIDMAP_CB_IS_ATTACHING,
HIDMAP_CB_IS_RUNNING,
HIDMAP_CB_IS_DETACHING,
};
#define HIDMAP_KEY_NULL 0xFF /* Special event code to discard input */
/* Third parameter of hidmap callback has different type depending on state */
union hidmap_cb_ctx {
struct hid_item *hi; /* Probe- and attach-stage callbacks */
int32_t data; /* Run-stage callbacks */
uint8_t rid; /* Run-stage finalizing callbacks */
};
#define HIDMAP_CB_ARGS \
struct hidmap *hm, struct hidmap_hid_item *hi, union hidmap_cb_ctx ctx
typedef int hidmap_cb_t(HIDMAP_CB_ARGS);
/* These helpers can be used at any stage of any callbacks */
#define HIDMAP_CB_GET_STATE(...) \
((hm == NULL) ? HIDMAP_CB_IS_PROBING : hm->cb_state)
#define HIDMAP_CB_GET_SOFTC(...) \
(hm == NULL ? NULL : device_get_softc(hm->dev))
#define HIDMAP_CB_GET_EVDEV(...) \
(hm == NULL ? NULL : hm->evdev)
#define HIDMAP_CB_UDATA (hi->udata)
#define HIDMAP_CB_UDATA64 (hi->udata64)
/* Special helpers for run-stage of finalizing callbacks */
#define HIDMAP_CB_GET_RID(...) (ctx.rid)
#define HIDMAP_CB_GET_DATA(loc) \
hid_get_data(hm->intr_buf, hm->intr_len, (loc))
#define HIDMAP_CB_GET_UDATA(loc) \
hid_get_udata(hm->intr_buf, hm->intr_len, (loc))
enum hidmap_relabs {
HIDMAP_RELABS_ANY = 0,
HIDMAP_RELATIVE,
HIDMAP_ABSOLUTE,
};
struct hidmap_item {
union {
struct {
uint16_t type; /* Evdev event type */
uint16_t code; /* Evdev event code */
uint16_t fuzz; /* Evdev event fuzz */
uint16_t flat; /* Evdev event flat */
};
hidmap_cb_t *cb; /* Reporting callback */
};
int32_t usage; /* HID usage (base) */
uint16_t nusages; /* number of usages */
bool required:1; /* Required by driver */
enum hidmap_relabs relabs:2;
bool has_cb:1;
bool final_cb:1;
bool invert_value:1;
u_int reserved:10;
};
#define HIDMAP_ANY(_page, _usage, _type, _code) \
.usage = HID_USAGE2((_page), (_usage)), \
.nusages = 1, \
.type = (_type), \
.code = (_code)
#define HIDMAP_ANY_RANGE(_page, _usage_from, _usage_to, _type, _code) \
.usage = HID_USAGE2((_page), (_usage_from)), \
.nusages = (_usage_to) - (_usage_from) + 1, \
.type = (_type), \
.code = (_code)
#define HIDMAP_ANY_CB(_page, _usage, _callback) \
.usage = HID_USAGE2((_page), (_usage)), \
.nusages = 1, \
.cb = (_callback), \
.has_cb = true
#define HIDMAP_ANY_CB_RANGE(_page, _usage_from, _usage_to, _callback) \
.usage = HID_USAGE2((_page), (_usage_from)), \
.nusages = (_usage_to) - (_usage_from) + 1, \
.cb = (_callback), \
.has_cb = true
#define HIDMAP_KEY(_page, _usage, _code) \
HIDMAP_ANY((_page), (_usage), EV_KEY, (_code)), \
.relabs = HIDMAP_RELABS_ANY
#define HIDMAP_KEY_RANGE(_page, _ufrom, _uto, _code) \
HIDMAP_ANY_RANGE((_page), (_ufrom), (_uto), EV_KEY, (_code)), \
.relabs = HIDMAP_RELABS_ANY
#define HIDMAP_REL(_page, _usage, _code) \
HIDMAP_ANY((_page), (_usage), EV_REL, (_code)), \
.relabs = HIDMAP_RELATIVE
#define HIDMAP_ABS(_page, _usage, _code) \
HIDMAP_ANY((_page), (_usage), EV_ABS, (_code)), \
.relabs = HIDMAP_ABSOLUTE
#define HIDMAP_SW(_page, _usage, _code) \
HIDMAP_ANY((_page), (_usage), EV_SW, (_code)), \
.relabs = HIDMAP_RELABS_ANY
#define HIDMAP_REL_CB(_page, _usage, _callback) \
HIDMAP_ANY_CB((_page), (_usage), (_callback)), \
.relabs = HIDMAP_RELATIVE
#define HIDMAP_ABS_CB(_page, _usage, _callback) \
HIDMAP_ANY_CB((_page), (_usage), (_callback)), \
.relabs = HIDMAP_ABSOLUTE
/*
* Special callback function which is not tied to particular HID input usage
* but called at the end evdev properties setting or interrupt handler
* just before evdev_register() or evdev_sync() calls.
*/
#define HIDMAP_FINAL_CB(_callback) \
HIDMAP_ANY_CB(0, 0, (_callback)), .final_cb = true
enum hidmap_type {
HIDMAP_TYPE_FINALCB = 0,/* No HID item associated. Runs unconditionally
* at the end of other items processing */
HIDMAP_TYPE_CALLBACK, /* HID item is reported with user callback */
HIDMAP_TYPE_VARIABLE, /* HID item is variable (single usage) */
HIDMAP_TYPE_VAR_NULLST, /* HID item is null state variable */
HIDMAP_TYPE_ARR_LIST, /* HID item is array with list of usages */
HIDMAP_TYPE_ARR_RANGE, /* Array with range (min;max) of usages */
};
struct hidmap_hid_item {
union {
hidmap_cb_t *cb; /* Callback */
struct { /* Variable */
uint16_t evtype; /* Evdev event type */
uint16_t code; /* Evdev event code */
};
uint16_t *codes; /* Array list map type */
int32_t umin; /* Array range map type */
};
union {
void *udata; /* Callback private context */
uint64_t udata64;
int32_t last_val; /* Last reported value (var) */
uint16_t last_key; /* Last reported key (array) */
};
struct hid_location loc; /* HID item location */
int32_t lmin; /* HID item logical minimum */
int32_t lmax; /* HID item logical maximum */
enum hidmap_type type:8;
uint8_t id; /* Report ID */
bool invert_value;
};
struct hidmap {
device_t dev;
struct evdev_dev *evdev;
struct evdev_methods evdev_methods;
/* Scatter-gather list of maps */
int nmaps;
uint32_t nmap_items[HIDMAP_MAX_MAPS];
const struct hidmap_item *map[HIDMAP_MAX_MAPS];
/* List of preparsed HID items */
uint32_t nhid_items;
struct hidmap_hid_item *hid_items;
/* Key event merging buffers */
uint8_t *key_press;
uint8_t *key_rel;
uint16_t key_min;
uint16_t key_max;
int *debug_var;
int debug_level;
enum hidmap_cb_state cb_state;
void * intr_buf;
hid_size_t intr_len;
};
typedef uint8_t * hidmap_caps_t;
#define HIDMAP_CAPS_SZ(nitems) howmany((nitems), 8)
#define HIDMAP_CAPS(name, map) uint8_t (name)[HIDMAP_CAPS_SZ(nitems(map))]
static inline bool
hidmap_test_cap(hidmap_caps_t caps, int cap)
{
return (isset(caps, cap) != 0);
}
/*
* It is safe to call any of following procedures in device_probe context
* that makes possible to write probe-only drivers with attach/detach handlers
* inherited from hidmap. See hcons and hsctrl drivers for example.
*/
static inline void
hidmap_set_dev(struct hidmap *hm, device_t dev)
{
hm->dev = dev;
}
/* Hack to avoid #ifdef-ing of hidmap_set_debug_var in hidmap based drivers */
#ifdef HID_DEBUG
#define hidmap_set_debug_var(h, d) _hidmap_set_debug_var((h), (d))
#else
#define hidmap_set_debug_var(...)
#endif
void _hidmap_set_debug_var(struct hidmap *hm, int *debug_var);
#define HIDMAP_ADD_MAP(hm, map, caps) \
hidmap_add_map((hm), (map), nitems(map), (caps))
uint32_t hidmap_add_map(struct hidmap *hm, const struct hidmap_item *map,
int nitems_map, hidmap_caps_t caps);
/* Versions of evdev_* functions capable to merge key events with same codes */
void hidmap_support_key(struct hidmap *hm, uint16_t key);
void hidmap_push_key(struct hidmap *hm, uint16_t key, int32_t value);
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_ */

267
sys/dev/hid/hms.c Normal file
View File

@ -0,0 +1,267 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* HID spec: https://www.usb.org/sites/default/files/documents/hid1_11.pdf
*/
#include <sys/param.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidmap.h>
#include <dev/hid/hidquirk.h>
#include <dev/hid/hidrdesc.h>
static const uint8_t hms_boot_desc[] = { HID_MOUSE_BOOTPROTO_DESCR() };
enum {
HMS_REL_X,
HMS_REL_Y,
HMS_REL_Z,
HMS_ABS_X,
HMS_ABS_Y,
HMS_ABS_Z,
HMS_HWHEEL,
HMS_BTN,
HMS_BTN_MS1,
HMS_BTN_MS2,
HMS_FINAL_CB,
};
static hidmap_cb_t hms_final_cb;
#define HMS_MAP_BUT_RG(usage_from, usage_to, code) \
{ HIDMAP_KEY_RANGE(HUP_BUTTON, usage_from, usage_to, code) }
#define HMS_MAP_BUT_MS(usage, code) \
{ HIDMAP_KEY(HUP_MICROSOFT, usage, code) }
#define HMS_MAP_ABS(usage, code) \
{ HIDMAP_ABS(HUP_GENERIC_DESKTOP, usage, code) }
#define HMS_MAP_REL(usage, code) \
{ HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code) }
#define HMS_MAP_REL_REV(usage, code) \
{ HIDMAP_REL(HUP_GENERIC_DESKTOP, usage, code), .invert_value = true }
#define HMS_MAP_REL_CN(usage, code) \
{ HIDMAP_REL(HUP_CONSUMER, usage, code) }
#define HMS_FINAL_CB(cb) \
{ HIDMAP_FINAL_CB(&cb) }
static const struct hidmap_item hms_map[] = {
[HMS_REL_X] = HMS_MAP_REL(HUG_X, REL_X),
[HMS_REL_Y] = HMS_MAP_REL(HUG_Y, REL_Y),
[HMS_REL_Z] = HMS_MAP_REL(HUG_Z, REL_Z),
[HMS_ABS_X] = HMS_MAP_ABS(HUG_X, ABS_X),
[HMS_ABS_Y] = HMS_MAP_ABS(HUG_Y, ABS_Y),
[HMS_ABS_Z] = HMS_MAP_ABS(HUG_Z, ABS_Z),
[HMS_HWHEEL] = HMS_MAP_REL_CN(HUC_AC_PAN, REL_HWHEEL),
[HMS_BTN] = HMS_MAP_BUT_RG(1, 16, BTN_MOUSE),
[HMS_BTN_MS1] = HMS_MAP_BUT_MS(1, BTN_RIGHT),
[HMS_BTN_MS2] = HMS_MAP_BUT_MS(2, BTN_MIDDLE),
[HMS_FINAL_CB] = HMS_FINAL_CB(hms_final_cb),
};
static const struct hidmap_item hms_map_wheel[] = {
HMS_MAP_REL(HUG_WHEEL, REL_WHEEL),
};
static const struct hidmap_item hms_map_wheel_rev[] = {
HMS_MAP_REL_REV(HUG_WHEEL, REL_WHEEL),
};
/* A match on these entries will load hms */
static const struct hid_device_id hms_devs[] = {
{ HID_TLC(HUP_GENERIC_DESKTOP, HUG_MOUSE) },
};
struct hms_softc {
struct hidmap hm;
HIDMAP_CAPS(caps, hms_map);
};
static int
hms_final_cb(HIDMAP_CB_ARGS)
{
struct hms_softc *sc = HIDMAP_CB_GET_SOFTC();
struct evdev_dev *evdev = HIDMAP_CB_GET_EVDEV();
if (HIDMAP_CB_GET_STATE() == HIDMAP_CB_IS_ATTACHING) {
if (hidmap_test_cap(sc->caps, HMS_ABS_X) ||
hidmap_test_cap(sc->caps, HMS_ABS_Y))
evdev_support_prop(evdev, INPUT_PROP_DIRECT);
else
evdev_support_prop(evdev, INPUT_PROP_POINTER);
}
/* Do not execute callback at interrupt handler and detach */
return (ENOSYS);
}
static void
hms_identify(driver_t *driver, device_t parent)
{
const struct hid_device_info *hw = hid_get_device_info(parent);
void *d_ptr;
hid_size_t d_len;
int error;
/*
* If device claimed boot protocol support but do not have report
* descriptor, load one defined in "Appendix B.2" of HID1_11.pdf
*/
error = hid_get_report_descr(parent, &d_ptr, &d_len);
if ((error != 0 && hid_test_quirk(hw, HQ_HAS_MS_BOOTPROTO)) ||
(error == 0 && hid_test_quirk(hw, HQ_MS_BOOTPROTO) &&
hid_is_mouse(d_ptr, d_len)))
(void)hid_set_report_descr(parent, hms_boot_desc,
sizeof(hms_boot_desc));
}
static int
hms_probe(device_t dev)
{
struct hms_softc *sc = device_get_softc(dev);
int error;
error = HIDBUS_LOOKUP_DRIVER_INFO(dev, hms_devs);
if (error != 0)
return (error);
hidmap_set_dev(&sc->hm, dev);
/* Check if report descriptor belongs to mouse */
error = HIDMAP_ADD_MAP(&sc->hm, hms_map, sc->caps);
if (error != 0)
return (error);
/* There should be at least one X or Y axis */
if (!hidmap_test_cap(sc->caps, HMS_REL_X) &&
!hidmap_test_cap(sc->caps, HMS_REL_X) &&
!hidmap_test_cap(sc->caps, HMS_ABS_X) &&
!hidmap_test_cap(sc->caps, HMS_ABS_Y))
return (ENXIO);
if (hidmap_test_cap(sc->caps, HMS_ABS_X) ||
hidmap_test_cap(sc->caps, HMS_ABS_Y))
hidbus_set_desc(dev, "Tablet");
else
hidbus_set_desc(dev, "Mouse");
return (BUS_PROBE_DEFAULT);
}
static int
hms_attach(device_t dev)
{
struct hms_softc *sc = device_get_softc(dev);
const struct hid_device_info *hw = hid_get_device_info(dev);
struct hidmap_hid_item *hi;
HIDMAP_CAPS(cap_wheel, hms_map_wheel);
void *d_ptr;
hid_size_t d_len;
bool set_report_proto;
int error, nbuttons = 0;
/*
* Set the report (non-boot) protocol if report descriptor has not been
* overloaded with boot protocol report descriptor.
*
* Mice without boot protocol support may choose not to implement
* Set_Protocol at all; Ignore any error.
*/
error = hid_get_report_descr(dev, &d_ptr, &d_len);
set_report_proto = !(error == 0 && d_len == sizeof(hms_boot_desc) &&
memcmp(d_ptr, hms_boot_desc, sizeof(hms_boot_desc)) == 0);
(void)hid_set_protocol(dev, set_report_proto ? 1 : 0);
if (hid_test_quirk(hw, HQ_MS_REVZ))
HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel_rev, cap_wheel);
else
HIDMAP_ADD_MAP(&sc->hm, hms_map_wheel, cap_wheel);
error = hidmap_attach(&sc->hm);
if (error)
return (error);
/* Count number of input usages of variable type mapped to buttons */
for (hi = sc->hm.hid_items;
hi < sc->hm.hid_items + sc->hm.nhid_items;
hi++)
if (hi->type == HIDMAP_TYPE_VARIABLE && hi->evtype == EV_KEY)
nbuttons++;
/* announce information about the mouse */
device_printf(dev, "%d buttons and [%s%s%s%s%s] coordinates ID=%u\n",
nbuttons,
(hidmap_test_cap(sc->caps, HMS_REL_X) ||
hidmap_test_cap(sc->caps, HMS_ABS_X)) ? "X" : "",
(hidmap_test_cap(sc->caps, HMS_REL_Y) ||
hidmap_test_cap(sc->caps, HMS_ABS_Y)) ? "Y" : "",
(hidmap_test_cap(sc->caps, HMS_REL_Z) ||
hidmap_test_cap(sc->caps, HMS_ABS_Z)) ? "Z" : "",
hidmap_test_cap(cap_wheel, 0) ? "W" : "",
hidmap_test_cap(sc->caps, HMS_HWHEEL) ? "H" : "",
sc->hm.hid_items[0].id);
return (0);
}
static int
hms_detach(device_t dev)
{
struct hms_softc *sc = device_get_softc(dev);
return (hidmap_detach(&sc->hm));
}
static devclass_t hms_devclass;
static device_method_t hms_methods[] = {
DEVMETHOD(device_identify, hms_identify),
DEVMETHOD(device_probe, hms_probe),
DEVMETHOD(device_attach, hms_attach),
DEVMETHOD(device_detach, hms_detach),
DEVMETHOD_END
};
DEFINE_CLASS_0(hms, hms_driver, hms_methods, sizeof(struct hms_softc));
DRIVER_MODULE(hms, hidbus, hms_driver, hms_devclass, NULL, 0);
MODULE_DEPEND(hms, hid, 1, 1, 1);
MODULE_DEPEND(hms, hidbus, 1, 1, 1);
MODULE_DEPEND(hms, hidmap, 1, 1, 1);
MODULE_DEPEND(hms, evdev, 1, 1, 1);
MODULE_VERSION(hms, 1);
HID_PNP_INFO(hms_devs);

110
sys/dev/hid/hsctrl.c Normal file
View File

@ -0,0 +1,110 @@
/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2020 Vladimir Kondratyev <wulf@FreeBSD.org>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
/*
* General Desktop/System Controls usage page driver
* https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf
*/
#include <sys/param.h>
#include <sys/bitstring.h>
#include <sys/bus.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/sysctl.h>
#include <dev/evdev/input.h>
#include <dev/evdev/evdev.h>
#include <dev/hid/hid.h>
#include <dev/hid/hidbus.h>
#include <dev/hid/hidmap.h>
#define HSCTRL_MAP(usage, code) \
{ HIDMAP_KEY(HUP_GENERIC_DESKTOP, HUG_SYSTEM_##usage, code) }
static const struct hidmap_item hsctrl_map[] = {
HSCTRL_MAP(POWER_DOWN, KEY_POWER),
HSCTRL_MAP(SLEEP, KEY_SLEEP),
HSCTRL_MAP(WAKEUP, KEY_WAKEUP),
HSCTRL_MAP(CONTEXT_MENU, KEY_CONTEXT_MENU),
HSCTRL_MAP(MAIN_MENU, KEY_MENU),
HSCTRL_MAP(APP_MENU, KEY_PROG1),
HSCTRL_MAP(MENU_HELP, KEY_HELP),
HSCTRL_MAP(MENU_EXIT, KEY_EXIT),
HSCTRL_MAP(MENU_SELECT, KEY_SELECT),
HSCTRL_MAP(MENU_RIGHT, KEY_RIGHT),
HSCTRL_MAP(MENU_LEFT, KEY_LEFT),
HSCTRL_MAP(MENU_UP, KEY_UP),
HSCTRL_MAP(MENU_DOWN, KEY_DOWN),
HSCTRL_MAP(POWER_UP, KEY_POWER2),
HSCTRL_MAP(RESTART, KEY_RESTART),
};
static const struct hid_device_id hsctrl_devs[] = {
{ HID_TLC(HUP_GENERIC_DESKTOP, HUG_SYSTEM_CONTROL) },
};
static int
hsctrl_probe(device_t dev)
{
return (HIDMAP_PROBE(device_get_softc(dev), dev,
hsctrl_devs, hsctrl_map, "System Control"));
}
static int
hsctrl_attach(device_t dev)
{
return (hidmap_attach(device_get_softc(dev)));
}
static int
hsctrl_detach(device_t dev)
{
return (hidmap_detach(device_get_softc(dev)));
}
static devclass_t hsctrl_devclass;
static device_method_t hsctrl_methods[] = {
DEVMETHOD(device_probe, hsctrl_probe),
DEVMETHOD(device_attach, hsctrl_attach),
DEVMETHOD(device_detach, hsctrl_detach),
DEVMETHOD_END
};
DEFINE_CLASS_0(hsctrl, hsctrl_driver, hsctrl_methods, sizeof(struct hidmap));
DRIVER_MODULE(hsctrl, hidbus, hsctrl_driver, hsctrl_devclass, NULL, 0);
MODULE_DEPEND(hsctrl, hid, 1, 1, 1);
MODULE_DEPEND(hsctrl, hidbus, 1, 1, 1);
MODULE_DEPEND(hsctrl, hidmap, 1, 1, 1);
MODULE_DEPEND(hsctrl, evdev, 1, 1, 1);
MODULE_VERSION(hsctrl, 1);
HID_PNP_INFO(hsctrl_devs);

1406
sys/dev/hid/ps4dshock.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,12 +3,17 @@
SUBDIR = \
hid \
hidbus \
hidmap \
hidquirk \
hidraw
SUBDIR += \
hconf \
hcons \
hkbd \
hmt
hms \
hmt \
hsctrl \
ps4dshock
.include <bsd.subdir.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= hcons
SRCS= hcons.c
SRCS+= bus_if.h device_if.h
.include <bsd.kmod.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= hidmap
SRCS= hidmap.c
SRCS+= bus_if.h device_if.h
.include <bsd.kmod.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= hms
SRCS= hms.c
SRCS+= bus_if.h device_if.h
.include <bsd.kmod.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= hsctrl
SRCS= hsctrl.c
SRCS+= bus_if.h device_if.h
.include <bsd.kmod.mk>

View File

@ -0,0 +1,9 @@
# $FreeBSD$
.PATH: ${SRCTOP}/sys/dev/hid
KMOD= ps4dshock
SRCS= ps4dshock.c
SRCS+= bus_if.h device_if.h usbdevs.h
.include <bsd.kmod.mk>