From 7acea25ddf6a2c2e0c9890ddb6c0a15e0dd82242 Mon Sep 17 00:00:00 2001 From: anton Date: Thu, 4 Feb 2021 16:25:38 +0000 Subject: [PATCH] Add uhidpp(4), a driver for Logitech HID++ devices. Currently limited to exposing battery sensors for HID++ 2.0 devices. Most of the code is derived from the hid-logitech-hidpp Linux driver. Thanks to Ville Valkonen for testing. ok mglocker@ --- share/man/man4/Makefile | 6 +- share/man/man4/uhidev.4 | 6 +- share/man/man4/uhidpp.4 | 48 ++ share/man/man4/usb.4 | 6 +- sys/arch/alpha/conf/GENERIC | 3 +- sys/arch/amd64/conf/GENERIC | 3 +- sys/arch/arm64/conf/GENERIC | 3 +- sys/arch/armv7/conf/GENERIC | 3 +- sys/arch/hppa/conf/GENERIC | 3 +- sys/arch/i386/conf/GENERIC | 3 +- sys/arch/landisk/conf/GENERIC | 3 +- sys/arch/loongson/conf/GENERIC | 3 +- sys/arch/macppc/conf/GENERIC | 3 +- sys/arch/octeon/conf/GENERIC | 3 +- sys/arch/powerpc64/conf/GENERIC | 3 +- sys/arch/sgi/conf/GENERIC-IP27 | 3 +- sys/arch/sgi/conf/GENERIC-IP30 | 3 +- sys/arch/sgi/conf/GENERIC-IP32 | 3 +- sys/arch/sparc64/conf/GENERIC | 3 +- sys/dev/usb/files.usb | 7 +- sys/dev/usb/uhidpp.c | 1054 +++++++++++++++++++++++++++++++ 21 files changed, 1149 insertions(+), 23 deletions(-) create mode 100644 share/man/man4/uhidpp.4 create mode 100644 sys/dev/usb/uhidpp.c diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index 70e62135237..4cbe824766e 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -1,4 +1,4 @@ -# $OpenBSD: Makefile,v 1.791 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: Makefile,v 1.792 2021/02/04 16:25:38 anton Exp $ MAN= aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \ acpi.4 acpiac.4 acpials.4 acpiasus.4 acpibat.4 \ @@ -83,8 +83,8 @@ MAN= aac.4 abcrtc.4 abl.4 ac97.4 acphy.4 acrtc.4 \ txp.4 txphy.4 uaudio.4 uark.4 uath.4 ubcmtp.4 uberry.4 ubsa.4 \ ubsec.4 ucom.4 uchcom.4 ucrcom.4 ucycom.4 ukspan.4 uslhcom.4 \ udav.4 udcf.4 udl.4 udp.4 udsbr.4 \ - uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4 uipaq.4 \ - ujoy.4 uk.4 ukbd.4 \ + uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4 uhidpp.4 \ + uipaq.4 ujoy.4 uk.4 ukbd.4 \ ukphy.4 ulpt.4 umass.4 umb.4 umbg.4 umcs.4 umct.4 umidi.4 umodem.4 \ ums.4 umsm.4 umstc.4 umt.4 unix.4 uonerng.4 uow.4 uoaklux.4 uoakrh.4 \ uoakv.4 upd.4 upgt.4 upl.4 uplcom.4 ural.4 ure.4 url.4 urlphy.4 \ diff --git a/share/man/man4/uhidev.4 b/share/man/man4/uhidev.4 index 06911ddef29..02252789a3f 100644 --- a/share/man/man4/uhidev.4 +++ b/share/man/man4/uhidev.4 @@ -1,4 +1,4 @@ -.\" $OpenBSD: uhidev.4,v 1.13 2021/01/23 05:08:34 thfr Exp $ +.\" $OpenBSD: uhidev.4,v 1.14 2021/02/04 16:25:38 anton Exp $ .\" $NetBSD: uhidev.4,v 1.2 2001/12/29 03:06:41 augustss Exp $ .\" .\" Copyright (c) 2001 The NetBSD Foundation, Inc. @@ -28,7 +28,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: January 23 2021 $ +.Dd $Mdocdate: February 4 2021 $ .Dt UHIDEV 4 .Os .Sh NAME @@ -40,6 +40,7 @@ .Cd "ucycom* at uhidev?" .Cd "ugold* at uhidev?" .Cd "uhid* at uhidev?" +.Cd "uhidpp* at uhidev?" .Cd "ujoy* at uhidev?" .Cd "ukbd* at uhidev?" .Cd "ums* at uhidev?" @@ -74,6 +75,7 @@ only dispatches data to them based on the report id. .Xr ucycom 4 , .Xr ugold 4 , .Xr uhid 4 , +.Xr uhidpp 4 , .Xr ujoy 4 , .Xr ukbd 4 , .Xr ums 4 , diff --git a/share/man/man4/uhidpp.4 b/share/man/man4/uhidpp.4 new file mode 100644 index 00000000000..50331287ba7 --- /dev/null +++ b/share/man/man4/uhidpp.4 @@ -0,0 +1,48 @@ +.\" $OpenBSD: uhidpp.4,v 1.1 2021/02/04 16:25:38 anton Exp $ +.\" +.\" Copyright (c) 2021 Anton Lindqvsit +.\" +.\" Permission to use, copy, modify, and distribute this software for any +.\" purpose with or without fee is hereby granted, provided that the above +.\" copyright notice and this permission notice appear in all copies. +.\" +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +.\" +.Dd $Mdocdate: February 4 2021 $ +.Dt UHIDPP 4 +.Os +.Sh NAME +.Nm uhidpp +.Nd Logitech HID++ devices +.Sh SYNOPSIS +.Cd "uhidpp* at uhidev?" +.Sh DESCRIPTION +The +.Nm +driver provides support for Logitech HID++ devices. +It exposes a collection of battery sensor values which are made available +through the +.Xr sysctl 8 +interface. +.Sh SEE ALSO +.Xr intro 4 , +.Xr uhidev 4 , +.Xr usb 4 , +.Xr sensorsd 8 , +.Xr sysctl 8 +.Sh HISTORY +The +.Nm +driver first appeared in +.Ox 6.9 . +.Sh AUTHORS +The +.Nm +driver was written by +.An Anton Lindqvist Aq Mt anton@opensd.org . diff --git a/share/man/man4/usb.4 b/share/man/man4/usb.4 index 8b9e3ffdc3c..5f9d5fd0c70 100644 --- a/share/man/man4/usb.4 +++ b/share/man/man4/usb.4 @@ -1,4 +1,4 @@ -.\" $OpenBSD: usb.4,v 1.204 2021/01/23 05:08:34 thfr Exp $ +.\" $OpenBSD: usb.4,v 1.205 2021/02/04 16:25:38 anton Exp $ .\" $NetBSD: usb.4,v 1.15 1999/07/29 14:20:32 augustss Exp $ .\" .\" Copyright (c) 1999 The NetBSD Foundation, Inc. @@ -28,7 +28,7 @@ .\" ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE .\" POSSIBILITY OF SUCH DAMAGE. .\" -.Dd $Mdocdate: January 23 2021 $ +.Dd $Mdocdate: February 4 2021 $ .Dt USB 4 .Os .Sh NAME @@ -255,6 +255,8 @@ TEMPer gold HID thermometer and hygrometer Generic driver for Human Interface Devices .It Xr uhidev 4 Base driver for all Human Interface Devices +.It Xr uhidpp 4 +Logitech HID++ devices .It Xr ujoy 4 USB joysticks/gamecontrollers .It Xr ukbd 4 diff --git a/sys/arch/alpha/conf/GENERIC b/sys/arch/alpha/conf/GENERIC index 05953f8e7cb..8af652ce301 100644 --- a/sys/arch/alpha/conf/GENERIC +++ b/sys/arch/alpha/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.267 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: GENERIC,v 1.268 2021/02/04 16:25:38 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -108,6 +108,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet #atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/amd64/conf/GENERIC b/sys/arch/amd64/conf/GENERIC index ffa1b4a497c..19b21486bc7 100644 --- a/sys/arch/amd64/conf/GENERIC +++ b/sys/arch/amd64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.496 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: GENERIC,v 1.497 2021/02/04 16:25:38 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -287,6 +287,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors umstc* at uhidev? # Microsoft Surface Type Cover aue* at uhub? # ADMtek AN986 Pegasus Ethernet diff --git a/sys/arch/arm64/conf/GENERIC b/sys/arch/arm64/conf/GENERIC index adefc8ffea9..3b3de9b42b1 100644 --- a/sys/arch/arm64/conf/GENERIC +++ b/sys/arch/arm64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.184 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: GENERIC,v 1.185 2021/02/04 16:25:38 anton Exp $ # # GENERIC machine description file # @@ -370,6 +370,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/armv7/conf/GENERIC b/sys/arch/armv7/conf/GENERIC index 13124411dc1..3bf925a2be7 100644 --- a/sys/arch/armv7/conf/GENERIC +++ b/sys/arch/armv7/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.133 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: GENERIC,v 1.134 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -322,6 +322,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/hppa/conf/GENERIC b/sys/arch/hppa/conf/GENERIC index d95e5a8977b..1a27dbb18c0 100644 --- a/sys/arch/hppa/conf/GENERIC +++ b/sys/arch/hppa/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.181 2021/01/23 05:08:34 thfr Exp $ +# $OpenBSD: GENERIC,v 1.182 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -112,6 +112,7 @@ wskbd* at ukbd? mux 1 uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet url* at uhub? # Realtek RTL8150L based adapters diff --git a/sys/arch/i386/conf/GENERIC b/sys/arch/i386/conf/GENERIC index c167a2f8011..6d725d99f93 100644 --- a/sys/arch/i386/conf/GENERIC +++ b/sys/arch/i386/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.854 2021/01/23 05:08:35 thfr Exp $ +# $OpenBSD: GENERIC,v 1.855 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -285,6 +285,7 @@ ucom* at uticom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/landisk/conf/GENERIC b/sys/arch/landisk/conf/GENERIC index 120aa0a108d..a2091413514 100644 --- a/sys/arch/landisk/conf/GENERIC +++ b/sys/arch/landisk/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.56 2021/01/23 05:08:35 thfr Exp $ +# $OpenBSD: GENERIC,v 1.57 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -138,6 +138,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/loongson/conf/GENERIC b/sys/arch/loongson/conf/GENERIC index 4e8d826142b..d213bd976f9 100644 --- a/sys/arch/loongson/conf/GENERIC +++ b/sys/arch/loongson/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.63 2021/01/23 05:08:35 thfr Exp $ +# $OpenBSD: GENERIC,v 1.64 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -165,6 +165,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors atu* at uhub? # Atmel AT76c50x based 802.11b aue* at uhub? # ADMtek AN986 Pegasus Ethernet diff --git a/sys/arch/macppc/conf/GENERIC b/sys/arch/macppc/conf/GENERIC index 46cd7397286..34014204a23 100644 --- a/sys/arch/macppc/conf/GENERIC +++ b/sys/arch/macppc/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.272 2021/01/23 05:08:35 thfr Exp $g +# $OpenBSD: GENERIC,v 1.273 2021/02/04 16:25:39 anton Exp $g # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -261,6 +261,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/octeon/conf/GENERIC b/sys/arch/octeon/conf/GENERIC index e5f407d7e9a..ec5424938cb 100644 --- a/sys/arch/octeon/conf/GENERIC +++ b/sys/arch/octeon/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.57 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: GENERIC,v 1.58 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -157,6 +157,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/arch/powerpc64/conf/GENERIC b/sys/arch/powerpc64/conf/GENERIC index cba5f59f2cf..c5f83807504 100644 --- a/sys/arch/powerpc64/conf/GENERIC +++ b/sys/arch/powerpc64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.24 2021/01/23 12:10:08 kettenis Exp $ +# $OpenBSD: GENERIC,v 1.25 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -128,6 +128,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors umstc* at uhidev? # Microsoft Surface Type Cover aue* at uhub? # ADMtek AN986 Pegasus Ethernet diff --git a/sys/arch/sgi/conf/GENERIC-IP27 b/sys/arch/sgi/conf/GENERIC-IP27 index 889a921dd8f..75ce0247c65 100644 --- a/sys/arch/sgi/conf/GENERIC-IP27 +++ b/sys/arch/sgi/conf/GENERIC-IP27 @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC-IP27,v 1.67 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: GENERIC-IP27,v 1.68 2021/02/04 16:25:39 anton Exp $ # # THIS KERNEL IS FOR Origin, Onyx, Fuel, Tezro (IP27, IP35) SYSTEMS ONLY. # @@ -129,6 +129,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices atu* at uhub? # Atmel AT76c50x based 802.11b aue* at uhub? # ADMtek AN986 Pegasus Ethernet axe* at uhub? # ASIX Electronics AX88172 USB Ethernet diff --git a/sys/arch/sgi/conf/GENERIC-IP30 b/sys/arch/sgi/conf/GENERIC-IP30 index bd90d34945b..a9e5b034fca 100644 --- a/sys/arch/sgi/conf/GENERIC-IP30 +++ b/sys/arch/sgi/conf/GENERIC-IP30 @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC-IP30,v 1.60 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: GENERIC-IP30,v 1.61 2021/02/04 16:25:39 anton Exp $ # # THIS KERNEL IS FOR Octane and Octane 2 (IP30) SYSTEMS ONLY. # @@ -120,6 +120,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices atu* at uhub? # Atmel AT76c50x based 802.11b aue* at uhub? # ADMtek AN986 Pegasus Ethernet axe* at uhub? # ASIX Electronics AX88172 USB Ethernet diff --git a/sys/arch/sgi/conf/GENERIC-IP32 b/sys/arch/sgi/conf/GENERIC-IP32 index d4c0d64019a..6719f883044 100644 --- a/sys/arch/sgi/conf/GENERIC-IP32 +++ b/sys/arch/sgi/conf/GENERIC-IP32 @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC-IP32,v 1.51 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: GENERIC-IP32,v 1.52 2021/02/04 16:25:39 anton Exp $ # # THIS KERNEL IS FOR O2 (IP32) SYSTEMS ONLY. # @@ -112,6 +112,7 @@ ucom* at uslhcom? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices atu* at uhub? # Atmel AT76c50x based 802.11b aue* at uhub? # ADMtek AN986 Pegasus Ethernet axe* at uhub? # ASIX Electronics AX88172 USB Ethernet diff --git a/sys/arch/sparc64/conf/GENERIC b/sys/arch/sparc64/conf/GENERIC index e29faa8181a..57b893babad 100644 --- a/sys/arch/sparc64/conf/GENERIC +++ b/sys/arch/sparc64/conf/GENERIC @@ -1,4 +1,4 @@ -# $OpenBSD: GENERIC,v 1.315 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: GENERIC,v 1.316 2021/02/04 16:25:39 anton Exp $ # # For further information on compiling OpenBSD kernels, see the config(8) # man page. @@ -225,6 +225,7 @@ ucom* at umsm? uhid* at uhidev? # USB generic HID support fido* at uhidev? # FIDO/U2F security key support ujoy* at uhidev? # USB joystick/gamecontroller support +uhidpp* at uhidev? # Logitech HID++ Devices upd* at uhidev? # USB Power Devices sensors aue* at uhub? # ADMtek AN986 Pegasus Ethernet atu* at uhub? # Atmel AT76c50x based 802.11b diff --git a/sys/dev/usb/files.usb b/sys/dev/usb/files.usb index ff94bf8765b..4d79c5ea21a 100644 --- a/sys/dev/usb/files.usb +++ b/sys/dev/usb/files.usb @@ -1,4 +1,4 @@ -# $OpenBSD: files.usb,v 1.144 2021/01/23 05:08:36 thfr Exp $ +# $OpenBSD: files.usb,v 1.145 2021/02/04 16:25:39 anton Exp $ # $NetBSD: files.usb,v 1.16 2000/02/14 20:29:54 augustss Exp $ # # Config file and device description for machine-independent USB code. @@ -483,3 +483,8 @@ file dev/usb/if_bwfm_usb.c bwfm_usb device umstc: hid attach umstc at uhidbus file dev/usb/umstc.c umstc + +# Logitech HID++ Devices +device uhidpp: hid +attach uhidpp at uhidbus +file dev/usb/uhidpp.c uhidpp diff --git a/sys/dev/usb/uhidpp.c b/sys/dev/usb/uhidpp.c new file mode 100644 index 00000000000..b041d86fecd --- /dev/null +++ b/sys/dev/usb/uhidpp.c @@ -0,0 +1,1054 @@ +/* $OpenBSD: uhidpp.c,v 1.1 2021/02/04 16:25:39 anton Exp $ */ + +/* + * Copyright (c) 2021 Anton Lindqvist + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* #define UHIDPP_DEBUG */ +#ifdef UHIDPP_DEBUG + +#define DPRINTF(x...) do { \ + if (uhidpp_debug) \ + printf(x); \ +} while (0) + +#define DREPORT(prefix, repid, buf, len) do { \ + if (uhidpp_debug) \ + uhidd_dump_report((prefix), (repid), (buf), (len)); \ +} while (0) + +void uhidd_dump_report(const char *, uint8_t, const unsigned char *, u_int); + +int uhidpp_debug = 1; + +#else + +#define DPRINTF(x...) +#define DREPORT(prefix, repid, buf, len) + +#endif + +#define HIDPP_LINK_STATUS(x) ((x) & (1 << 7)) + +#define HIDPP_REPORT_ID_SHORT 0x10 +#define HIDPP_REPORT_ID_LONG 0x11 + +/* + * Length of reports. Note that the effective length is always +1 as + * uhidev_set_report() prepends the report ID. + */ +#define HIDPP_REPORT_SHORT_LENGTH (7 - 1) +#define HIDPP_REPORT_LONG_LENGTH (20 - 1) + +/* + * Maximum number of allowed parameters for reports. Note, the parameters always + * starts at offset 3 for both RAP and FAP reports. + */ +#define HIDPP_REPORT_SHORT_PARAMS_MAX (HIDPP_REPORT_SHORT_LENGTH - 3) +#define HIDPP_REPORT_LONG_PARAMS_MAX (HIDPP_REPORT_LONG_LENGTH - 3) + +#define HIDPP_DEVICE_ID_RECEIVER 0xff + +#define HIDPP_FEAT_ROOT_IDX 0x00 +#define HIDPP_FEAT_ROOT_PING_FUNC 0x01 +#define HIDPP_FEAT_ROOT_PING_DATA 0x5a + +#define HIDPP_SET_REGISTER 0x80 +#define HIDPP_GET_REGISTER 0x81 +#define HIDPP_SET_LONG_REGISTER 0x82 +#define HIDPP_GET_LONG_REGISTER 0x83 + +#define HIDPP_REG_ENABLE_REPORTS 0x00 +#define HIDPP_REG_PAIRING_INFORMATION 0xb5 + +#define HIDPP_NOTIF_DEVICE_BATTERY_STATUS (1 << 4) +#define HIDPP_NOTIF_RECEIVER_WIRELESS (1 << 0) +#define HIDPP_NOTIF_RECEIVER_SOFTWARE_PRESENT (1 << 3) + +/* HID++ 1.0 error codes. */ +#define HIDPP_ERROR 0x8f +#define HIDPP_ERROR_SUCCESS 0x00 +#define HIDPP_ERROR_INVALID_SUBID 0x01 +#define HIDPP_ERROR_INVALID_ADRESS 0x02 +#define HIDPP_ERROR_INVALID_VALUE 0x03 +#define HIDPP_ERROR_CONNECT_FAIL 0x04 +#define HIDPP_ERROR_TOO_MANY_DEVICES 0x05 +#define HIDPP_ERROR_ALREADY_EXISTS 0x06 +#define HIDPP_ERROR_BUSY 0x07 +#define HIDPP_ERROR_UNKNOWN_DEVICE 0x08 +#define HIDPP_ERROR_RESOURCE_ERROR 0x09 +#define HIDPP_ERROR_REQUEST_UNAVAILABLE 0x0a +#define HIDPP_ERROR_INVALID_PARAM_VALUE 0x0b +#define HIDPP_ERROR_WRONG_PIN_CODE 0x0c + +/* + * The software ID is added to feature access reports (FAP) and used to + * distinguish responses from notifications. Note, the software ID must be + * greater than zero which is reserved for notifications. + */ +#define HIDPP_SOFTWARE_ID 0x01 +#define HIDPP_SOFTWARE_ID_MASK 0x0f +#define HIDPP_SOFTWARE_ID_LEN 4 + +#define HIDPP20_FEAT_ROOT_IDX 0x00 +#define HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC 0x00 + +#define HIDPP20_FEAT_BATTERY_IDX 0x1000 +#define HIDPP20_FEAT_BATTERY_LEVEL_FUNC 0x0000 +#define HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC 0x0001 + +/* HID++ 2.0 error codes. */ +#define HIDPP20_ERROR 0xff +#define HIDPP20_ERROR_NO_ERROR 0x00 +#define HIDPP20_ERROR_UNKNOWN 0x01 +#define HIDPP20_ERROR_INVALID_ARGUMENT 0x02 +#define HIDPP20_ERROR_OUT_OF_RANGE 0x03 +#define HIDPP20_ERROR_HARDWARE_ERROR 0x04 +#define HIDPP20_ERROR_LOGITECH_INTERNAL 0x05 +#define HIDPP20_ERROR_INVALID_FEATURE_INDEX 0x06 +#define HIDPP20_ERROR_INVALID_FUNCTION_ID 0x07 +#define HIDPP20_ERROR_BUSY 0x08 +#define HIDPP20_ERROR_UNSUPPORTED 0x09 + +/* + * Sentinels used for interrupt response synchronization. The values must be + * disjoint from existing report IDs. + */ +#define UHIDPP_RESP_NONE 0 +#define UHIDPP_RESP_WAIT 1 +#define UHIDPP_RESP_ERROR 2 + +/* Maximum number of devices associated with a single receiver. */ +#define UHIDPP_NDEVICES 6 + +/* Maximum number of pending notifications. */ +#define UHIDPP_NNOTIFICATIONS 4 + +/* Number of sensors per paired device. */ +#define UHIDPP_NSENSORS 2 + +/* Feature access report used by the HID++ 2.0 (and greater) protocol. */ +struct fap { + uint8_t feature_index; + uint8_t funcindex_clientid; + uint8_t params[HIDPP_REPORT_LONG_PARAMS_MAX]; +}; + +/* + * Register access report used by the HID++ 1.0 protocol. Receivers always uses + * this type of report. + */ +struct rap { + uint8_t sub_id; + uint8_t reg_address; + uint8_t params[HIDPP_REPORT_LONG_PARAMS_MAX]; +}; + +struct uhidpp_report { + uint8_t device_id; + union { + struct fap fap; + struct rap rap; + }; +} __packed; + +struct uhidpp_notification { + struct uhidpp_report n_rep; + unsigned int n_id; +}; + +struct uhidpp_device { + uint8_t d_id; + uint8_t d_connected; + struct { + struct ksensor b_sens[UHIDPP_NSENSORS]; + uint8_t b_feature_idx; + uint8_t b_level; + uint8_t b_next_level; + uint8_t b_status; + uint8_t b_nlevels; + } d_battery; +}; + +/* + * Locking: + * [m] sc_mtx + */ +struct uhidpp_softc { + struct uhidev sc_hdev; + struct usbd_device *sc_udev; + + struct mutex sc_mtx; + + struct uhidpp_device sc_devices[UHIDPP_NDEVICES]; + /* [m] connected devices */ + + struct uhidpp_notification sc_notifications[UHIDPP_NNOTIFICATIONS]; + /* [m] pending notifications */ + + struct usb_task sc_task; /* [m] notification task */ + + struct ksensordev sc_sensdev; /* [m] */ + struct sensor_task *sc_senstsk; /* [m] */ + + struct uhidpp_report *sc_req; /* [m] synchronous request buffer */ + struct uhidpp_report *sc_resp; /* [m] synchronous response buffer */ + u_int sc_resp_state; /* [m] synchronous response state */ + +}; + +int uhidpp_match(struct device *, void *, void *); +void uhidpp_attach(struct device *, struct device *, void *); +int uhidpp_detach(struct device *, int flags); +void uhidpp_intr(struct uhidev *addr, void *ibuf, u_int len); +void uhidpp_refresh(void *); +void uhidpp_task(void *); +int uhidpp_sleep(struct uhidpp_softc *, uint64_t); + +void uhidpp_device_connect(struct uhidpp_softc *, struct uhidpp_device *); +void uhidpp_device_refresh(struct uhidpp_softc *, struct uhidpp_device *); + +struct uhidpp_notification *uhidpp_claim_notification(struct uhidpp_softc *); +int uhidpp_consume_notification(struct uhidpp_softc *, struct uhidpp_report *); +int uhidpp_is_notification(struct uhidpp_softc *, struct uhidpp_report *); + +int hidpp_get_protocol_version(struct uhidpp_softc *, uint8_t, int *, int *); + +int hidpp10_get_name(struct uhidpp_softc *, uint8_t, char *, size_t); +int hidpp10_get_serial(struct uhidpp_softc *, uint8_t, uint8_t *, size_t); +int hidpp10_get_type(struct uhidpp_softc *, uint8_t, const char **); +int hidpp10_enable_notifications(struct uhidpp_softc *, uint8_t); + +int hidpp20_root_get_feature(struct uhidpp_softc *, uint8_t, uint16_t, + uint8_t *, uint8_t *); +int hidpp20_battery_get_level_status(struct uhidpp_softc *, uint8_t, uint8_t, + uint8_t *, uint8_t *, uint8_t *); +int hidpp20_battery_get_capability(struct uhidpp_softc *, uint8_t, uint8_t, + uint8_t *); + +int hidpp_send_validate(uint8_t, int); +int hidpp_send_rap_report(struct uhidpp_softc *, uint8_t, uint8_t, + uint8_t, uint8_t, uint8_t *, int, struct uhidpp_report *); +int hidpp_send_fap_report(struct uhidpp_softc *, uint8_t, uint8_t, uint8_t, + uint8_t, uint8_t *, int, struct uhidpp_report *); +int hidpp_send_report(struct uhidpp_softc *, uint8_t, struct uhidpp_report *, + struct uhidpp_report *); + +struct cfdriver uhidpp_cd = { + NULL, "uhidpp", DV_DULL +}; + +const struct cfattach uhidpp_ca = { + sizeof(struct uhidpp_softc), + uhidpp_match, + uhidpp_attach, + uhidpp_detach, +}; + +static const struct usb_devno uhidpp_devs[] = { + { USB_VENDOR_LOGITECH, USB_PRODUCT_ANY }, +}; + +int +uhidpp_match(struct device *parent, void *match, void *aux) +{ + struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; + void *desc; + int descsiz, siz; + + if (uha->reportid != UHIDEV_CLAIM_ALLREPORTID) + return UMATCH_NONE; + + if (usb_lookup(uhidpp_devs, + uha->uaa->vendor, uha->uaa->product) == NULL) + return UMATCH_NONE; + + uhidev_get_report_desc(uha->parent, &desc, &descsiz); + siz = hid_report_size(desc, descsiz, hid_output, HIDPP_REPORT_ID_SHORT); + if (siz != HIDPP_REPORT_SHORT_LENGTH) + return UMATCH_NONE; + siz = hid_report_size(desc, descsiz, hid_output, HIDPP_REPORT_ID_LONG); + if (siz != HIDPP_REPORT_LONG_LENGTH) + return UMATCH_NONE; + + return UMATCH_VENDOR_PRODUCT; +} + +void +uhidpp_attach(struct device *parent, struct device *self, void *aux) +{ + struct uhidpp_softc *sc = (struct uhidpp_softc *)self; + struct uhidev_attach_arg *uha = (struct uhidev_attach_arg *)aux; + struct usb_attach_arg *uaa = uha->uaa; + int error, i; + int npaired = 0; + + sc->sc_hdev.sc_intr = uhidpp_intr; + sc->sc_hdev.sc_udev = uaa->device; + sc->sc_hdev.sc_parent = uha->parent; + sc->sc_hdev.sc_report_id = uha->reportid; + /* The largest supported report dictates the sizes. */ + sc->sc_hdev.sc_isize = HIDPP_REPORT_LONG_LENGTH; + sc->sc_hdev.sc_osize = HIDPP_REPORT_LONG_LENGTH; + + sc->sc_udev = uaa->device; + + mtx_init(&sc->sc_mtx, IPL_USB); + + sc->sc_resp = NULL; + sc->sc_resp_state = UHIDPP_RESP_NONE; + + error = uhidev_open(&sc->sc_hdev); + if (error) { + printf(" error %d\n", error); + return; + } + + usb_init_task(&sc->sc_task, uhidpp_task, sc, USB_TASK_TYPE_GENERIC); + + mtx_enter(&sc->sc_mtx); + + /* + * Wire up report device handlers before issuing commands to the device + * in order to receive responses. Necessary as uhidev by default + * performs the wiring after the attach routine has returned. + */ + uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev, + HIDPP_REPORT_ID_SHORT); + uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev, + HIDPP_REPORT_ID_LONG); + + /* Probe paired devices. */ + for (i = 0; i < UHIDPP_NDEVICES; i++) { + char name[16]; + uint8_t serial[4]; + struct uhidpp_device *dev = &sc->sc_devices[i]; + const char *type; + uint8_t device_id = device_id + 1; + + dev->d_id = device_id; + + if (hidpp10_get_serial(sc, device_id, serial, sizeof(serial)) || + hidpp10_get_type(sc, device_id, &type) || + hidpp10_get_name(sc, device_id, name, sizeof(name))) + continue; + + if (npaired > 0) + printf(","); + printf(" device %d", device_id); + printf(" %s", type); + printf(" \"%s\"", name); + printf(" serial %02x-%02x-%02x-%02x", + serial[0], serial[1], serial[2], serial[3]); + npaired++; + } + + /* Enable notifications for the receiver. */ + error = hidpp10_enable_notifications(sc, HIDPP_DEVICE_ID_RECEIVER); + if (error) + printf(" error %d", error); + + printf("\n"); + + strlcpy(sc->sc_sensdev.xname, sc->sc_hdev.sc_dev.dv_xname, + sizeof(sc->sc_sensdev.xname)); + sensordev_install(&sc->sc_sensdev); + sc->sc_senstsk = sensor_task_register(sc, uhidpp_refresh, 6); + + mtx_leave(&sc->sc_mtx); +} + +int +uhidpp_detach(struct device *self, int flags) +{ + struct uhidpp_softc *sc = (struct uhidpp_softc *)self; + int i, j; + + usb_rem_wait_task(sc->sc_udev, &sc->sc_task); + + if (sc->sc_senstsk != NULL) + sensor_task_unregister(sc->sc_senstsk); + + KASSERT(sc->sc_resp_state == UHIDPP_RESP_NONE); + + sensordev_deinstall(&sc->sc_sensdev); + + for (i = 0; i < UHIDPP_NDEVICES; i++) { + struct uhidpp_device *dev = &sc->sc_devices[i]; + + if (!dev->d_connected) + continue; + + for (j = 0; j < UHIDPP_NSENSORS; j++) + sensor_detach(&sc->sc_sensdev, &dev->d_battery.b_sens[j]); + } + + uhidev_close(&sc->sc_hdev); + + return 0; +} + +void +uhidpp_intr(struct uhidev *addr, void *buf, u_int len) +{ + struct uhidpp_softc *sc = (struct uhidpp_softc *)addr; + struct uhidpp_report *rep = buf; + int dowake = 0; + uint8_t repid; + + /* + * Ugliness ahead as the report ID is stripped of by uhidev_intr() but + * needed to determine if an error occurred. + * Note that an error response is always a short report even if the + * command that caused the error is a long report. + */ + repid = ((uint8_t *)buf)[-1]; + + DREPORT(__func__, repid, buf, len); + + mtx_enter(&sc->sc_mtx); + if (uhidpp_is_notification(sc, rep)) { + struct uhidpp_notification *ntf; + + ntf = uhidpp_claim_notification(sc); + if (ntf != NULL) { + memcpy(&ntf->n_rep, buf, len); + usb_add_task(sc->sc_udev, &sc->sc_task); + } else { + DPRINTF("%s: too many notifications", __func__); + } + } else { + KASSERT(sc->sc_resp_state == UHIDPP_RESP_WAIT); + dowake = 1; + sc->sc_resp_state = repid; + memcpy(sc->sc_resp, buf, len); + } + mtx_leave(&sc->sc_mtx); + if (dowake) + wakeup(sc); +} + +void +uhidpp_refresh(void *arg) +{ + struct uhidpp_softc *sc = arg; + int i; + + mtx_enter(&sc->sc_mtx); + for (i = 0; i < UHIDPP_NDEVICES; i++) { + struct uhidpp_device *dev = &sc->sc_devices[i]; + + if (dev->d_connected) + uhidpp_device_refresh(sc, dev); + } + mtx_leave(&sc->sc_mtx); +} + +void +uhidpp_task(void *arg) +{ + struct uhidpp_softc *sc = arg; + + mtx_enter(&sc->sc_mtx); + for (;;) { + struct uhidpp_report rep; + struct uhidpp_device *dev; + + if (uhidpp_consume_notification(sc, &rep)) + break; + + DPRINTF("%s: device_id=%d, sub_id=%02x\n", + __func__, rep.device_id, rep.rap.sub_id); + + if (rep.device_id == 0 || rep.device_id > UHIDPP_NDEVICES) { + DPRINTF("%s: invalid device\n", __func__); + continue; + } + dev = &sc->sc_devices[rep.device_id - 1]; + + switch (rep.rap.sub_id) { + case 0x0e: /* leds */ + case 0x40: /* disconnect */ + case 0x4b: /* pairing accepted */ + break; + case 0x41: /* connect */ + /* + * Do nothing if the link is reported to be out of + * range. This happens when a device has been idle for a + * while. + */ + if (HIDPP_LINK_STATUS(rep.rap.params[0])) + uhidpp_device_connect(sc, dev); + break; + } + } + mtx_leave(&sc->sc_mtx); +} + +int +uhidpp_sleep(struct uhidpp_softc *sc, uint64_t nsecs) +{ + return msleep_nsec(sc, &sc->sc_mtx, PZERO, "uhidpp", nsecs); +} + +void +uhidpp_device_connect(struct uhidpp_softc *sc, struct uhidpp_device *dev) +{ + struct ksensor *sens; + int error, major, minor; + uint8_t feature_type; + + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + /* A connected device will continously send connect events. */ + if (dev->d_connected) + return; + + error = hidpp_get_protocol_version(sc, dev->d_id, &major, &minor); + if (error) { + DPRINTF("%s: protocol version failure: device_id=%d, error=%d\n", + __func__, dev->d_id, error); + return; + } + + DPRINTF("%s: device_id=%d, version=%d.%d\n", + __func__, dev->d_id, major, minor); + + error = hidpp20_root_get_feature(sc, dev->d_id, + HIDPP20_FEAT_BATTERY_IDX, + &dev->d_battery.b_feature_idx, &feature_type); + if (error) { + DPRINTF("%s: battery feature index failure: device_id=%d, " + "error=%d\n", __func__, dev->d_id, error); + return; + } + + error = hidpp20_battery_get_capability(sc, dev->d_id, + dev->d_battery.b_feature_idx, &dev->d_battery.b_nlevels); + if (error) { + DPRINTF("%s: battery capability failure: device_id=%d, " + "error=%d\n", __func__, dev->d_id, error); + return; + } + + sens = &dev->d_battery.b_sens[0]; + strlcpy(sens->desc, "battery level", sizeof(sens->desc)); + sens->type = SENSOR_PERCENT; + sens->flags = SENSOR_FUNKNOWN; + sensor_attach(&sc->sc_sensdev, sens); + + sens = &dev->d_battery.b_sens[1]; + strlcpy(sens->desc, "battery levels", sizeof(sens->desc)); + sens->type = SENSOR_INTEGER; + sens->value = dev->d_battery.b_nlevels; + sensor_attach(&sc->sc_sensdev, sens); + + dev->d_connected = 1; + uhidpp_device_refresh(sc, dev); +} + +void +uhidpp_device_refresh(struct uhidpp_softc *sc, struct uhidpp_device *dev) +{ + int error; + + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + error = hidpp20_battery_get_level_status(sc, dev->d_id, + dev->d_battery.b_feature_idx, + &dev->d_battery.b_level, &dev->d_battery.b_next_level, + &dev->d_battery.b_status); + if (error) { + DPRINTF("%s: battery level status failure: device_id=%d, " + "error=%d\n", __func__, dev->d_id, error); + return; + } + + dev->d_battery.b_sens[0].value = dev->d_battery.b_level * 1000; + dev->d_battery.b_sens[0].flags &= ~SENSOR_FUNKNOWN; + if (dev->d_battery.b_nlevels < 10) { + /* + * According to the HID++ 2.0 specification, less than 10 levels + * should be mapped to the following 4 levels: + * + * [0, 10] critical + * [11, 30] low + * [31, 80] good + * [81, 100] full + * + * Since sensors are limited to 3 valid statuses, clamp it even + * further. + */ + if (dev->d_battery.b_level <= 10) + dev->d_battery.b_sens[0].status = SENSOR_S_CRIT; + else if (dev->d_battery.b_level <= 30) + dev->d_battery.b_sens[0].status = SENSOR_S_WARN; + else + dev->d_battery.b_sens[0].status = SENSOR_S_OK; + } else { + /* + * XXX the device supports battery mileage. The current level + * must be checked against resp.fap.params[3] given by + * hidpp20_battery_get_capability(). + */ + dev->d_battery.b_sens[0].status = SENSOR_S_UNKNOWN; + } +} + +/* + * Returns the next available notification slot, if available. + */ +struct uhidpp_notification * +uhidpp_claim_notification(struct uhidpp_softc *sc) +{ + struct uhidpp_notification *ntf = NULL; + int nclaimed = 0; + int i; + + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + for (i = 0; i < UHIDPP_NNOTIFICATIONS; i++) { + struct uhidpp_notification *tmp = &sc->sc_notifications[i]; + + if (tmp->n_id > 0) + nclaimed++; + else if (ntf == NULL) + ntf = tmp; + } + + if (ntf == NULL) + return NULL; + ntf->n_id = nclaimed + 1; + return ntf; +} + +/* + * Consume the first unhandled notification, if present. + */ +int +uhidpp_consume_notification(struct uhidpp_softc *sc, struct uhidpp_report *rep) +{ + struct uhidpp_notification *ntf = NULL; + int i; + + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + for (i = 0; i < UHIDPP_NNOTIFICATIONS; i++) { + struct uhidpp_notification *tmp = &sc->sc_notifications[i]; + + if (tmp->n_id > 0 && (ntf == NULL || tmp->n_id < ntf->n_id)) + ntf = tmp; + } + if (ntf == NULL) + return 1; + + memcpy(rep, &ntf->n_rep, sizeof(*rep)); + ntf->n_id = 0; + return 0; +} + + +/* + * Returns non-zero if the given report is a notification. Otherwise, it must be + * a response. + */ +int +uhidpp_is_notification(struct uhidpp_softc *sc, struct uhidpp_report *rep) +{ + /* Not waiting for a response. */ + if (sc->sc_req == NULL) + return 1; + + /* Everything except the parameters must be repeated in a response. */ + if (sc->sc_req->device_id == rep->device_id && + sc->sc_req->rap.sub_id == rep->rap.sub_id && + sc->sc_req->rap.reg_address == rep->rap.reg_address) + return 0; + + /* An error must always be a response. */ + if ((rep->rap.sub_id == HIDPP_ERROR || + rep->fap.feature_index == HIDPP20_ERROR) && + rep->fap.funcindex_clientid == sc->sc_req->fap.feature_index && + rep->fap.params[0] == sc->sc_req->fap.funcindex_clientid) + return 0; + + return 1; +} + +int +hidpp_get_protocol_version(struct uhidpp_softc *sc, uint8_t device_id, + int *major, int *minor) +{ + struct uhidpp_report resp; + uint8_t params[3] = { 0, 0, HIDPP_FEAT_ROOT_PING_DATA }; + int error; + + error = hidpp_send_fap_report(sc, + HIDPP_REPORT_ID_SHORT, + device_id, + HIDPP_FEAT_ROOT_IDX, + HIDPP_FEAT_ROOT_PING_FUNC, + params, sizeof(params), &resp); + if (error == HIDPP_ERROR_INVALID_SUBID) { + *major = 1; + *minor = 0; + return 0; + } + if (error) + return error; + if (resp.rap.params[2] != HIDPP_FEAT_ROOT_PING_DATA) + return -EPROTO; + + *major = resp.fap.params[0]; + *minor = resp.fap.params[1]; + return 0; +} + +int +hidpp10_get_name(struct uhidpp_softc *sc, uint8_t device_id, + char *buf, size_t bufsiz) +{ + struct uhidpp_report resp; + int error; + uint8_t params[1] = { 0x40 + (device_id - 1) }; + uint8_t len; + + error = hidpp_send_rap_report(sc, + HIDPP_REPORT_ID_SHORT, + HIDPP_DEVICE_ID_RECEIVER, + HIDPP_GET_LONG_REGISTER, + HIDPP_REG_PAIRING_INFORMATION, + params, sizeof(params), &resp); + if (error) + return error; + + len = resp.rap.params[1]; + if (len + 2 > sizeof(resp.rap.params)) + return -ENAMETOOLONG; + if (len > bufsiz - 1) + len = bufsiz - 1; + memcpy(buf, &resp.rap.params[2], len); + buf[len] = '\0'; + return 0; +} + +int +hidpp10_get_serial(struct uhidpp_softc *sc, uint8_t device_id, + uint8_t *buf, size_t bufsiz) +{ + struct uhidpp_report resp; + int error; + uint8_t params[1] = { 0x30 + (device_id - 1) }; + uint8_t len; + + error = hidpp_send_rap_report(sc, + HIDPP_REPORT_ID_SHORT, + HIDPP_DEVICE_ID_RECEIVER, + HIDPP_GET_LONG_REGISTER, + HIDPP_REG_PAIRING_INFORMATION, + params, sizeof(params), &resp); + if (error) + return error; + + len = 4; + if (bufsiz < len) + len = bufsiz; + memcpy(buf, &resp.rap.params[1], len); + return 0; +} + +int +hidpp10_get_type(struct uhidpp_softc *sc, uint8_t device_id, const char **type) +{ + struct uhidpp_report resp; + int error; + uint8_t params[1] = { 0x20 + (device_id - 1) }; + + error = hidpp_send_rap_report(sc, + HIDPP_REPORT_ID_SHORT, + HIDPP_DEVICE_ID_RECEIVER, + HIDPP_GET_LONG_REGISTER, + HIDPP_REG_PAIRING_INFORMATION, + params, sizeof(params), &resp); + if (error) + return error; + + switch (resp.rap.params[7]) { + case 0x00: + *type = "unknown"; + return 0; + case 0x01: + *type = "keyboard"; + return 0; + case 0x02: + *type = "mouse"; + return 0; + case 0x03: + *type = "numpad"; + return 0; + case 0x04: + *type = "presenter"; + return 0; + case 0x08: + *type = "trackball"; + return 0; + case 0x09: + *type = "touchpad"; + return 0; + } + return -ENOENT; +} + +int +hidpp10_enable_notifications(struct uhidpp_softc *sc, uint8_t device_id) +{ + struct uhidpp_report resp; + uint8_t params[3]; + + /* Device reporting flags. */ + params[0] = HIDPP_NOTIF_DEVICE_BATTERY_STATUS; + /* Receiver reporting flags. */ + params[1] = HIDPP_NOTIF_RECEIVER_WIRELESS | + HIDPP_NOTIF_RECEIVER_SOFTWARE_PRESENT; + /* Device reporting flags (continued). */ + params[2] = 0; + + return hidpp_send_rap_report(sc, + HIDPP_REPORT_ID_SHORT, + device_id, + HIDPP_SET_REGISTER, + HIDPP_REG_ENABLE_REPORTS, + params, sizeof(params), &resp); +} + +int +hidpp20_root_get_feature(struct uhidpp_softc *sc, uint8_t device_id, + uint16_t feature, uint8_t *feature_index, uint8_t *feature_type) +{ + struct uhidpp_report resp; + uint8_t params[2] = { feature >> 8, feature & 0xff }; + int error; + + error = hidpp_send_fap_report(sc, + HIDPP_REPORT_ID_LONG, + device_id, + HIDPP20_FEAT_ROOT_IDX, + HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC, + params, sizeof(params), &resp); + if (error) + return error; + + if (resp.fap.params[0] == 0) + return -ENOENT; + + *feature_index = resp.fap.params[0]; + *feature_type = resp.fap.params[1]; + return 0; +} + +int +hidpp20_battery_get_level_status(struct uhidpp_softc *sc, uint8_t device_id, + uint8_t feature_index, uint8_t *level, uint8_t *next_level, uint8_t *status) +{ + struct uhidpp_report resp; + int error; + + error = hidpp_send_fap_report(sc, + HIDPP_REPORT_ID_LONG, + device_id, + feature_index, + HIDPP20_FEAT_BATTERY_LEVEL_FUNC, + NULL, 0, &resp); + if (error) + return error; + + *level = resp.fap.params[0]; + *next_level = resp.fap.params[1]; + *status = resp.fap.params[2]; + return 0; +} + +int +hidpp20_battery_get_capability(struct uhidpp_softc *sc, uint8_t device_id, + uint8_t feature_index, uint8_t *nlevels) +{ + struct uhidpp_report resp; + int error; + + error = hidpp_send_fap_report(sc, + HIDPP_REPORT_ID_LONG, + device_id, + feature_index, + HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC, + NULL, 0, &resp); + if (error) + return error; + *nlevels = resp.fap.params[0]; + return 0; +} + +int +hidpp_send_validate(uint8_t report_id, int nparams) +{ + if (report_id == HIDPP_REPORT_ID_SHORT) { + if (nparams > HIDPP_REPORT_SHORT_PARAMS_MAX) + return -EMSGSIZE; + } else if (report_id == HIDPP_REPORT_ID_LONG) { + if (nparams > HIDPP_REPORT_LONG_PARAMS_MAX) + return -EMSGSIZE; + } else { + return -EINVAL; + } + return 0; +} + +int +hidpp_send_fap_report(struct uhidpp_softc *sc, uint8_t report_id, + uint8_t device_id, uint8_t feature_index, uint8_t funcindex_clientid, + uint8_t *params, int nparams, struct uhidpp_report *resp) +{ + struct uhidpp_report req; + int error; + + error = hidpp_send_validate(report_id, nparams); + if (error) + return error; + + memset(&req, 0, sizeof(req)); + req.device_id = device_id; + req.fap.feature_index = feature_index; + req.fap.funcindex_clientid = + (funcindex_clientid << HIDPP_SOFTWARE_ID_LEN) | HIDPP_SOFTWARE_ID; + memcpy(req.fap.params, params, nparams); + return hidpp_send_report(sc, report_id, &req, resp); +} + +int +hidpp_send_rap_report(struct uhidpp_softc *sc, uint8_t report_id, + uint8_t device_id, uint8_t sub_id, uint8_t reg_address, + uint8_t *params, int nparams, struct uhidpp_report *resp) +{ + struct uhidpp_report req; + int error; + + error = hidpp_send_validate(report_id, nparams); + if (error) + return error; + + memset(&req, 0, sizeof(req)); + req.device_id = device_id; + req.rap.sub_id = sub_id; + req.rap.reg_address = reg_address; + memcpy(req.rap.params, params, nparams); + return hidpp_send_report(sc, report_id, &req, resp); +} + +int +hidpp_send_report(struct uhidpp_softc *sc, uint8_t report_id, + struct uhidpp_report *req, struct uhidpp_report *resp) +{ + int error, len, n; + + MUTEX_ASSERT_LOCKED(&sc->sc_mtx); + + if (report_id == HIDPP_REPORT_ID_SHORT) + len = HIDPP_REPORT_SHORT_LENGTH; + else if (report_id == HIDPP_REPORT_ID_LONG) + len = HIDPP_REPORT_LONG_LENGTH; + else + return -EINVAL; + + DREPORT(__func__, report_id, (const unsigned char *)req, len); + + /* Wait until any ongoing command has completed. */ + while (sc->sc_resp_state != UHIDPP_RESP_NONE) + uhidpp_sleep(sc, INFSLP); + sc->sc_req = req; + sc->sc_resp = resp; + sc->sc_resp_state = UHIDPP_RESP_WAIT; + /* + * The mutex must be temporarily released while calling + * uhidev_set_report() as it might end up sleeping. + */ + mtx_leave(&sc->sc_mtx); + + n = uhidev_set_report(sc->sc_hdev.sc_parent, UHID_OUTPUT_REPORT, + report_id, req, len); + + mtx_enter(&sc->sc_mtx); + if (len != n) { + error = -EBUSY; + goto out; + } + /* + * The interrupt could already have been received while the mutex was + * released. Otherwise, wait for it. + */ + if (sc->sc_resp_state == UHIDPP_RESP_WAIT) { + /* Timeout taken from the hid-logitech-hidpp Linux driver. */ + error = uhidpp_sleep(sc, SEC_TO_NSEC(5)); + if (error) { + error = -error; + goto out; + } + } + + if (sc->sc_resp_state == UHIDPP_RESP_ERROR) + error = -EIO; + else if (sc->sc_resp_state == HIDPP_REPORT_ID_SHORT && + resp->rap.sub_id == HIDPP_ERROR) + error = resp->rap.params[1]; + else if (sc->sc_resp_state == HIDPP_REPORT_ID_LONG && + resp->fap.feature_index == HIDPP20_ERROR) + error = resp->fap.params[1]; + +out: + sc->sc_req = NULL; + sc->sc_resp = NULL; + sc->sc_resp_state = UHIDPP_RESP_NONE; + wakeup(sc); + return error; +} + +#ifdef UHIDPP_DEBUG + +void +uhidd_dump_report(const char *prefix, uint8_t repid, const unsigned char *buf, + u_int buflen) +{ + u_int i; + + printf("%s: %02x ", prefix, repid); + for (i = 0; i < buflen; i++) { + printf("%02x%s", buf[i], + i == 2 ? " [" : (i + 1 < buflen ? " " : "")); + } + printf("]\n"); +} + +#endif -- 2.20.1