Add uhidpp(4), a driver for Logitech HID++ devices. Currently limited to
authoranton <anton@openbsd.org>
Thu, 4 Feb 2021 16:25:38 +0000 (16:25 +0000)
committeranton <anton@openbsd.org>
Thu, 4 Feb 2021 16:25:38 +0000 (16:25 +0000)
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 <weezeldinga at gmail dot com> for testing.

ok mglocker@

21 files changed:
share/man/man4/Makefile
share/man/man4/uhidev.4
share/man/man4/uhidpp.4 [new file with mode: 0644]
share/man/man4/usb.4
sys/arch/alpha/conf/GENERIC
sys/arch/amd64/conf/GENERIC
sys/arch/arm64/conf/GENERIC
sys/arch/armv7/conf/GENERIC
sys/arch/hppa/conf/GENERIC
sys/arch/i386/conf/GENERIC
sys/arch/landisk/conf/GENERIC
sys/arch/loongson/conf/GENERIC
sys/arch/macppc/conf/GENERIC
sys/arch/octeon/conf/GENERIC
sys/arch/powerpc64/conf/GENERIC
sys/arch/sgi/conf/GENERIC-IP27
sys/arch/sgi/conf/GENERIC-IP30
sys/arch/sgi/conf/GENERIC-IP32
sys/arch/sparc64/conf/GENERIC
sys/dev/usb/files.usb
sys/dev/usb/uhidpp.c [new file with mode: 0644]

index 70e6213..4cbe824 100644 (file)
@@ -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 \
index 06911dd..0225278 100644 (file)
@@ -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 (file)
index 0000000..5033128
--- /dev/null
@@ -0,0 +1,48 @@
+.\"    $OpenBSD: uhidpp.4,v 1.1 2021/02/04 16:25:38 anton Exp $
+.\"
+.\" Copyright (c) 2021 Anton Lindqvsit <anton@openbsd.org>
+.\"
+.\" 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 .
index 8b9e3ff..5f9d5fd 100644 (file)
@@ -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
index 05953f8..8af652c 100644 (file)
@@ -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
index ffa1b4a..19b2148 100644 (file)
@@ -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
index adefc8f..3b3de9b 100644 (file)
@@ -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
index 1312441..3bf925a 100644 (file)
@@ -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
index d95e5a8..1a27dbb 100644 (file)
@@ -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
index c167a2f..6d725d9 100644 (file)
@@ -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
index 120aa0a..a209141 100644 (file)
@@ -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
index 4e8d826..d213bd9 100644 (file)
@@ -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  
index 46cd739..3401420 100644 (file)
@@ -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
index e5f407d..ec54249 100644 (file)
@@ -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
index cba5f59..c5f8380 100644 (file)
@@ -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
index 889a921..75ce024 100644 (file)
@@ -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
index bd90d34..a9e5b03 100644 (file)
@@ -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
index d4c0d64..6719f88 100644 (file)
@@ -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
index e29faa8..57b893b 100644 (file)
@@ -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
index ff94bf8..4d79c5e 100644 (file)
@@ -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 (file)
index 0000000..b041d86
--- /dev/null
@@ -0,0 +1,1054 @@
+/*     $OpenBSD: uhidpp.c,v 1.1 2021/02/04 16:25:39 anton Exp $        */
+
+/*
+ * Copyright (c) 2021 Anton Lindqvist <anton@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/device.h>
+#include <sys/mutex.h>
+#include <sys/sensors.h>
+
+#include <dev/usb/usb.h>
+#include <dev/usb/usbhid.h>
+#include <dev/usb/usbdi.h>
+#include <dev/usb/usbdevs.h>
+#include <dev/usb/uhidev.h>
+
+/* #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