Hi,

I have a bit oldish Logitech M705 mouse, bought around 2010-2011.
Regarding the dmesg (on below) I can see it gets attached correctly to
uhiddp0 but doesn't report battery levels. Here's the line from dmesg:
uhidpp0 at uhidev2 device 1 mouse "M705" serial xx-xx-x-xx, device 2 keyboard 
"K750" serial xx-xx-xx-xx.
And corresponding sysctl values:
hw.sensors.uhidpp0.raw0=unknown (battery levels)
hw.sensors.uhidpp0.raw1=unknown (battery levels)
hw.sensors.uhidpp0.percent0=unknown (battery level)
hw.sensors.uhidpp0.percent1=unknown (battery level)

Not sure if censoring of serial has any value, though.

On Ubuntu battery levels are detected correctly so I could probably
take a USB dump with Wireshark and compare the differences.

And here's sysctl.sensors & dmesg differences (before <-> after the
patch):
<sysctl.sensors>
--- sysctl_before.txt   Fri Jan 29 21:31:44 2021
+++ sysctl_after.txt    Fri Jan 29 21:34:50 2021
@@ -5,39 +5,43 @@ hw.byteorder=1234
 hw.pagesize=4096
 hw.disknames=sd0:c01774372c2b714a
 hw.diskcount=1
-hw.sensors.cpu0.temp0=54.00 degC
+hw.sensors.cpu0.temp0=50.00 degC
 hw.sensors.acpibtn0.indicator0=On (lid open)
 hw.sensors.acpibat0.volt0=11.10 VDC (voltage)
 hw.sensors.acpibat0.volt1=12.38 VDC (current voltage)
 hw.sensors.acpibat0.power0=0.00 W (rate)
-hw.sensors.acpibat0.watthour0=15.64 Wh (last full capacity)
+hw.sensors.acpibat0.watthour0=15.62 Wh (last full capacity)
 hw.sensors.acpibat0.watthour1=0.78 Wh (warning capacity)
 hw.sensors.acpibat0.watthour2=0.20 Wh (low capacity)
 hw.sensors.acpibat0.watthour3=15.62 Wh (remaining capacity), OK
 hw.sensors.acpibat0.watthour4=23.20 Wh (design capacity)
-hw.sensors.acpibat0.raw0=0 (battery idle), OK
+hw.sensors.acpibat0.raw0=0 (battery full), OK
 hw.sensors.acpibat1.volt0=11.10 VDC (voltage)
 hw.sensors.acpibat1.volt1=12.32 VDC (current voltage)
 hw.sensors.acpibat1.power0=0.00 W (rate)
-hw.sensors.acpibat1.watthour0=16.53 Wh (last full capacity)
+hw.sensors.acpibat1.watthour0=16.52 Wh (last full capacity)
 hw.sensors.acpibat1.watthour1=0.83 Wh (warning capacity)
 hw.sensors.acpibat1.watthour2=0.20 Wh (low capacity)
 hw.sensors.acpibat1.watthour3=16.52 Wh (remaining capacity), OK
 hw.sensors.acpibat1.watthour4=23.20 Wh (design capacity)
-hw.sensors.acpibat1.raw0=0 (battery idle), OK
+hw.sensors.acpibat1.raw0=0 (battery full), OK
 hw.sensors.acpiac0.indicator0=On (power supply)
-hw.sensors.acpithinkpad0.temp0=55.00 degC
-hw.sensors.acpithinkpad0.temp1=55.00 degC
-hw.sensors.acpithinkpad0.temp2=55.00 degC
-hw.sensors.acpithinkpad0.temp3=55.00 degC
-hw.sensors.acpithinkpad0.temp4=55.00 degC
-hw.sensors.acpithinkpad0.temp5=55.00 degC
-hw.sensors.acpithinkpad0.temp6=55.00 degC
-hw.sensors.acpithinkpad0.temp7=55.00 degC
-hw.sensors.acpithinkpad0.fan0=4112 RPM
+hw.sensors.acpithinkpad0.temp0=51.00 degC
+hw.sensors.acpithinkpad0.temp1=51.00 degC
+hw.sensors.acpithinkpad0.temp2=51.00 degC
+hw.sensors.acpithinkpad0.temp3=51.00 degC
+hw.sensors.acpithinkpad0.temp4=51.00 degC
+hw.sensors.acpithinkpad0.temp5=51.00 degC
+hw.sensors.acpithinkpad0.temp6=51.00 degC
+hw.sensors.acpithinkpad0.temp7=51.00 degC
+hw.sensors.acpithinkpad0.fan0=3659 RPM
 hw.sensors.acpithinkpad0.indicator0=Off (port replicator), UNKNOWN
-hw.sensors.acpitz0.temp0=55.00 degC (zone temperature)
-hw.sensors.pchtemp0.temp0=62.50 degC
+hw.sensors.acpitz0.temp0=51.00 degC (zone temperature)
+hw.sensors.pchtemp0.temp0=58.00 degC
+hw.sensors.uhidpp0.raw0=unknown (battery levels)
+hw.sensors.uhidpp0.raw1=unknown (battery levels)
+hw.sensors.uhidpp0.percent0=unknown (battery level)
+hw.sensors.uhidpp0.percent1=unknown (battery level)
 hw.cpuspeed=500
 hw.setperf=0
 hw.vendor=LENOVO
@@ -46,7 +50,7 @@ hw.version=ThinkPad X250
 hw.serialno=PC031KCD
 hw.uuid=816b2e2f-d253-cb11-9791-8821c15b5a68
 hw.physmem=8277995520
-hw.usermem=8233095168
+hw.usermem=8221360128
 hw.ncpufound=2
 hw.allowpowerdown=1
 hw.perfpolicy=auto
</sysctl.sensors>

And:
<dmesg>
--- dmesg_before.txt    2021-01-29 21:38:10.000000000 +0200
+++ dmesg_after.txt     2021-01-29 22:03:30.341216215 +0200
@@ -1,7 +1,7 @@
-OpenBSD 6.8-current (GENERIC.MP) #301: Fri Jan 29 02:04:27 MST 2021
-   
dera...@amd64.openbsd.org:/usr/src/sys/arch/amd64/compile/GENERIC.MP
+OpenBSD 6.8-current (GENERIC.MP) #0: Fri Jan 29 21:30:07 EET 2021
+    wee...@weezel.lan:/obsd_src/sys/arch/amd64/compile/GENERIC.MP
 real mem = 8277995520 (7894MB)
-avail mem = 8011780096 (7640MB)
+avail mem = 8011751424 (7640MB)
 random: good seed from bootblocks
 mpath0 at root
 scsibus0 at mpath0: 256 targets
@@ -18,7 +18,7 @@ acpihpet0 at acpi0: 14318179 Hz
 acpiec0 at acpi0
 acpimadt0 at acpi0 addr 0xfee00000: PC-AT compat
 cpu0 at mainbus0: apid 0 (boot processor)
-cpu0: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz, 2494.55 MHz, 06-3d-04
+cpu0: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz, 2494.63 MHz, 06-3d-04
 cpu0:
FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36
,CFLUSH,DS,ACPI,MMX,FXSR,SSE,SSE2,SS,HTT,TM,PBE,SSE3,PCLMUL,DTES64,MWAI
T,DS-
CPL,VMX,SMX,EST,TM2,SSSE3,SDBG,FMA3,CX16,xTPR,PDCM,PCID,SSE4.1,SSE4.2,x
2APIC,MOVBE,POPCNT,DEADLINE,AES,XSAVE,AVX,F16C,RDRAND,NXE,PAGE1GB,RDTSC
P,LONG,LAHF,ABM,3DNOWP,PERF,ITSC,FSGSBASE,TSC_ADJUST,BMI1,HLE,AVX2,SMEP
,BMI2,ERMS,INVPCID,RTM,RDSEED,ADX,SMAP,PT,SRBDS_CTRL,MD_CLEAR,IBRS,IBPB
,STIBP,L1DF,SSBD,SENSOR,ARAT,XSAVEOPT,MELTDOWN
 cpu0: 256KB 64b/line 8-way L2 cache
 cpu0: smt 0, core 0, package 0
@@ -26,7 +26,7 @@ mtrr: Pentium Pro MTRR support, 10 var r
 cpu0: apic clock running at 99MHz
 cpu0: mwait min=64, max=64, C-substates=0.2.1.2.4.1.1.1, IBE
 cpu1 at mainbus0: apid 2 (application processor)
-cpu1: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz, 2494.24 MHz, 06-3d-04
+cpu1: Intel(R) Core(TM) i7-5600U CPU @ 2.60GHz, 2494.23 MHz, 06-3d-04
 cpu1:
FPU,VME,DE,PSE,TSC,MSR,PAE,MCE,CX8,APIC,SEP,MTRR,PGE,MCA,CMOV,PAT,PSE36
,CFLUSH,DS,ACPI,MMX,FXSR,SSE,SSE2,SS,HTT,TM,PBE,SSE3,PCLMUL,DTES64,MWAI
T,DS-
CPL,VMX,SMX,EST,TM2,SSSE3,SDBG,FMA3,CX16,xTPR,PDCM,PCID,SSE4.1,SSE4.2,x
2APIC,MOVBE,POPCNT,DEADLINE,AES,XSAVE,AVX,F16C,RDRAND,NXE,PAGE1GB,RDTSC
P,LONG,LAHF,ABM,3DNOWP,PERF,ITSC,FSGSBASE,TSC_ADJUST,BMI1,HLE,AVX2,SMEP
,BMI2,ERMS,INVPCID,RTM,RDSEED,ADX,SMAP,PT,SRBDS_CTRL,MD_CLEAR,IBRS,IBPB
,STIBP,L1DF,SSBD,SENSOR,ARAT,XSAVEOPT,MELTDOWN
 cpu1: 256KB 64b/line 8-way L2 cache
 cpu1: smt 0, core 1, package 0
@@ -107,32 +107,29 @@ pms0: Synaptics clickpad, firmware 8.1,
 pcppi0 at isa0 port 0x61
 spkr0 at pcppi0
 vmm0 at mainbus0: VMX/EPT
-ugen0 at uhub0 port 7 "Intel Bluetooth" rev 2.01/0.01 addr 2
-uhub2 at uhub1 port 1 configuration 1 interface 0 "Intel Rate Matching
Hub" rev 2.00/0.03 addr 2
-vscsi0 at root
-scsibus2 at vscsi0: 256 targets
-softraid0 at root
-scsibus3 at softraid0: 256 targets
-root on sd0a (c01774372c2b714a.a) swap on sd0b dump on sd0b
-inteldrm0: 1920x1080, 32bpp
-wsdisplay0 at inteldrm0 mux 1: console (std, vt100 emulation), using
wskbd0
-wsdisplay0: screen 1-5 added (std, vt100 emulation)
-iwm0: hw rev 0x210, fw ver 17.3216344376.0, address 60:57:18:6a:df:8d
-uhidev0 at uhub0 port 1 configuration 1 interface 0 "Logitech USB
Receiver" rev 2.00/12.10 addr 3
+uhidev0 at uhub0 port 1 configuration 1 interface 0 "Logitech USB
Receiver" rev 2.00/12.10 addr 2
 uhidev0: iclass 3/1
 ukbd0 at uhidev0: 8 variable keys, 6 key codes
 wskbd1 at ukbd0 mux 1
-wskbd1: connecting to wsdisplay0
-uhidev1 at uhub0 port 1 configuration 1 interface 1 "Logitech USB
Receiver" rev 2.00/12.10 addr 3
+uhidev1 at uhub0 port 1 configuration 1 interface 1 "Logitech USB
Receiver" rev 2.00/12.10 addr 2
 uhidev1: iclass 3/1, 8 report ids
 ums0 at uhidev1 reportid 2: 16 buttons, Z and W dir
 wsmouse2 at ums0 mux 0
 uhid0 at uhidev1 reportid 3: input=4, output=0, feature=0
 uhid1 at uhidev1 reportid 4: input=1, output=0, feature=0
 uhid2 at uhidev1 reportid 8: input=1, output=0, feature=0
-uhidev2 at uhub0 port 1 configuration 1 interface 2 "Logitech USB
Receiver" rev 2.00/12.10 addr 3
+uhidev2 at uhub0 port 1 configuration 1 interface 2 "Logitech USB
Receiver" rev 2.00/12.10 addr 2
 uhidev2: iclass 3/0, 33 report ids
-uhid3 at uhidev2 reportid 16: input=6, output=6, feature=0
-uhid4 at uhidev2 reportid 17: input=19, output=19, feature=0
-uhid5 at uhidev2 reportid 32: input=14, output=14, feature=0
-uhid6 at uhidev2 reportid 33: input=31, output=31, feature=0
+uhidpp0 at uhidev2 device 1 mouse "M705" serial xx-xx-xx-xx, device 2
keyboard "K750" serial xx-xx-xx-xx
+ugen0 at uhub0 port 7 "Intel Bluetooth" rev 2.01/0.01 addr 3
+uhub2 at uhub1 port 1 configuration 1 interface 0 "Intel Rate Matching
Hub" rev 2.00/0.03 addr 2
+vscsi0 at root
+scsibus2 at vscsi0: 256 targets
+softraid0 at root
+scsibus3 at softraid0: 256 targets
+root on sd0a (c01774372c2b714a.a) swap on sd0b dump on sd0b
+inteldrm0: 1920x1080, 32bpp
+wsdisplay0 at inteldrm0 mux 1: console (std, vt100 emulation), using
wskbd0
+wskbd1: connecting to wsdisplay0
+wsdisplay0: screen 1-5 added (std, vt100 emulation)
+iwm0: hw rev 0x210, fw ver 17.3216344376.0, address 60:57:18:6a:df:8d
</dmesg>

--
Kind regards,
Ville

On Fri, 2021-01-29 at 19:23 +0200, Ville Valkonen wrote:
Hello Anton,

either I failed to use git or then files have changed since the first
patch:
$ git apply -p0 --check hid_plusplus.patch
error: patch failed: share/man/man4/Makefile:83
error: share/man/man4/Makefile: patch does not apply
error: patch failed: share/man/man4/uhidev.4:40
error: share/man/man4/uhidev.4: patch does not apply
error: patch failed: share/man/man4/usb.4:255
error: share/man/man4/usb.4: patch does not apply
error: patch failed: sys/arch/amd64/conf/GENERIC:288
error: sys/arch/amd64/conf/GENERIC: patch does not apply
error: patch failed: sys/dev/usb/uhidev.c:950
error: sys/dev/usb/uhidev.c: patch does not apply
error: sys/dev/usb/uhidpp.c: already exists in working directory

Running that on root of src. A quick peek on sys/dev/usb/uhidev.c file
shows that it has been modified on 25th of Jan so I'd guess the patch
needs to be updated.

Thanks in advance! Been thinking to have a look on that protocol but
since I am no HW hacker I've postponed that for years :)

--
Kind regards,
Ville

On Fri, 29 Jan 2021 at 09:21, Anton Lindqvist <an...@openbsd.org>
wrote:
> 
> Ping
> 
> On Fri, Jan 22, 2021 at 08:18:51AM +0100, Anton Lindqvist wrote:
> > Hi,
> > Here's a new driver for Logitech HID++ devices, currently limited
> > to
> > exposing battery sensors. Here's an example using a Logitech M330
> > mouse:
> > 
> >       $ dmesg | grep uhidpp
> >       uhidpp0 at uhidev1 device 1 mouse "B330/M330/M3" serial c7-
> > 2f-a8-33
> >       $ sysctl hw.sensors.uhidpp0
> >       hw.sensors.uhidpp0.raw0=2 (battery levels)
> >       hw.sensors.uhidpp0.percent0=70.00% (battery level), OK
> > 
> > The raw0 sensor reflects number of available battery levels, the
> > resolution on this device is not great...
> > 
> > Most of the code is derived from the hid-logitech-hidpp Linux
> > driver.
> > Some assorted notes:
> > 
> > * In order to communicate with the device inside the attach
> > routine, I
> >   had to wire up the interrupt handler as this by default is done
> > first
> >   once the same attach routine has returned. Hence the introduction
> > of
> >   uhidev_set_intr(). If this is an acceptable approach, this can go
> > in
> >   as a separate commit.
> > 
> > * I kept using the `return -errno' convention from the Linux driver
> > in
> >   order to distingush errors from the hardware, which are always
> >   positive.
> > 
> > I you happen to have a Logitech HID++ device and run into any
> > problem(s), please enable UHIDPP_DEBUG and send me the output.
> > 
> > Comments? OK?
> > 
> > diff --git share/man/man4/Makefile share/man/man4/Makefile
> > index 02af7a47a44..74a4e17d7dc 100644
> > --- share/man/man4/Makefile
> > +++ share/man/man4/Makefile
> > @@ -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 \
> > -     uk.4 ukbd.4 \
> > +     uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4
> > uhidpp.4 \
> > +     uipaq.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 share/man/man4/uhidev.4 share/man/man4/uhidev.4
> > index f0a6776a27b..d264935a65c 100644
> > --- share/man/man4/uhidev.4
> > +++ share/man/man4/uhidev.4
> > @@ -40,6 +40,7 @@
> >  .Cd "ucycom*  at uhidev?"
> >  .Cd "ugold*   at uhidev?"
> >  .Cd "uhid*    at uhidev?"
> > +.Cd "uhidpp*  at uhidev?"
> >  .Cd "ukbd*    at uhidev?"
> >  .Cd "ums*     at uhidev?"
> >  .Cd "umstc*   at uhidev?"
> > @@ -73,6 +74,7 @@ only dispatches data to them based on the report
> > id.
> >  .Xr ucycom 4 ,
> >  .Xr ugold 4 ,
> >  .Xr uhid 4 ,
> > +.Xr uhidpp 4 ,
> >  .Xr ukbd 4 ,
> >  .Xr ums 4 ,
> >  .Xr umstc 4 ,
> > diff --git share/man/man4/uhidpp.4 share/man/man4/uhidpp.4
> > new file mode 100644
> > index 00000000000..4c78380c35b
> > --- /dev/null
> > +++ share/man/man4/uhidpp.4
> > @@ -0,0 +1,48 @@
> > +.\"  $OpenBSD$
> > +.\"
> > +.\" Copyright (c) 2021 Anton Lindqvsit <an...@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$
> > +.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 an...@opensd.org .
> > diff --git share/man/man4/usb.4 share/man/man4/usb.4
> > index 520f46265e0..b58190539e3 100644
> > --- share/man/man4/usb.4
> > +++ share/man/man4/usb.4
> > @@ -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 ukbd 4
> >  USB keyboards that follow the boot protocol
> >  .It Xr ums 4
> > diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC
> > index 45b3a9b6e66..00ac52adcd6 100644
> > --- sys/arch/amd64/conf/GENERIC
> > +++ sys/arch/amd64/conf/GENERIC
> > @@ -288,6 +288,7 @@ uhid*     at uhidev?              # USB generic
> > HID support
> >  fido*        at uhidev?              # FIDO/U2F security key
> > support
> >  upd* at uhidev?              # USB Power Devices sensors
> >  umstc*       at uhidev?              # Microsoft Surface Type
> > Cover
> > +uhidpp*      at uhidev?              # Logitech HID++ Devices
> >  aue* at uhub?                # ADMtek AN986 Pegasus Ethernet
> >  atu* at uhub?                # Atmel AT76c50x based 802.11b
> >  axe* at uhub?                # ASIX Electronics AX88172 USB
> > Ethernet
> > diff --git sys/dev/usb/files.usb sys/dev/usb/files.usb
> > index 1d673cf635d..5c95d1c0ac5 100644
> > --- sys/dev/usb/files.usb
> > +++ sys/dev/usb/files.usb
> > @@ -478,3 +478,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 sys/dev/usb/uhidev.c sys/dev/usb/uhidev.c
> > index 5f2bc07cc96..68212d50471 100644
> > --- sys/dev/usb/uhidev.c
> > +++ sys/dev/usb/uhidev.c
> > @@ -255,8 +255,11 @@ uhidev_attach(struct device *parent, struct
> > device *self, void *aux)
> >       /* Look for a driver claiming all report IDs first. */
> >       dev = config_found_sm(self, &uha, NULL, uhidevsubmatch);
> >       if (dev != NULL) {
> > -             for (repid = 0; repid < nrepid; repid++)
> > -                     sc->sc_subdevs[repid] = (struct uhidev *)dev;
> > +             for (repid = 0; repid < nrepid; repid++) {
> > +                     /* Could already be assigned by
> > uhidev_set_intr(). */
> > +                     if (sc->sc_subdevs[repid] == NULL)
> > +                             sc->sc_subdevs[repid] = (struct
> > uhidev *)dev;
> > +             }
> >               return;
> >       }
> > 
> > @@ -269,7 +272,9 @@ uhidev_attach(struct device *parent, struct
> > device *self, void *aux)
> > 
> >               uha.reportid = repid;
> >               dev = config_found_sm(self, &uha, uhidevprint,
> > uhidevsubmatch);
> > -             sc->sc_subdevs[repid] = (struct uhidev *)dev;
> > +             /* Could already be assigned by uhidev_set_intr(). */
> > +             if (sc->sc_subdevs[repid] == NULL)
> > +                     sc->sc_subdevs[repid] = (struct uhidev *)dev;
> >       }
> >  }
> > 
> > @@ -950,3 +955,16 @@ uhidev_ioctl(struct uhidev *sc, u_long cmd,
> > caddr_t addr, int flag,
> >       }
> >       return 0;
> >  }
> > +
> > +int
> > +uhidev_set_intr(struct uhidev_softc *sc, struct uhidev *dev, int
> > repid)
> > +{
> > +
> > +     if ((dev->sc_state & UHIDEV_OPEN) == 0)
> > +             return ENODEV;
> > +     if (repid >= sc->sc_nrepid)
> > +             return EINVAL;
> > +
> > +     sc->sc_subdevs[repid] = dev;
> > +     return 0;
> > +}
> > diff --git sys/dev/usb/uhidev.h sys/dev/usb/uhidev.h
> > index 16657f1e712..791c8882769 100644
> > --- sys/dev/usb/uhidev.h
> > +++ sys/dev/usb/uhidev.h
> > @@ -95,3 +95,4 @@ int uhidev_get_report(struct uhidev_softc *, int,
> > int, void *, int);
> >  int uhidev_get_report_async(struct uhidev_softc *, int, int, void
> > *, int,
> >      void *, void (*)(void *, int, void *, int));
> >  usbd_status uhidev_write(struct uhidev_softc *, void *, int);
> > +int uhidev_set_intr(struct uhidev_softc *, struct uhidev *, int);
> > diff --git sys/dev/usb/uhidpp.c sys/dev/usb/uhidpp.c
> > new file mode 100644
> > index 00000000000..aee989e2de5
> > --- /dev/null
> > +++ sys/dev/usb/uhidpp.c
> > @@ -0,0 +1,1055 @@
> > +/*   $OpenBSD$       */
> > +
> > +/*
> > + * Copyright (c) 2021 Anton Lindqvist <an...@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_paired;
> > +     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_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_pair(struct uhidpp_softc *, struct
> > uhidpp_device *);
> > +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_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, void *,
> > +    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 interrupt 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_intr(sc->sc_hdev.sc_parent, &sc->sc_hdev,
> > +         HIDPP_REPORT_ID_SHORT);
> > +     uhidev_set_intr(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;
> > +
> > +             uhidpp_device_pair(sc, dev);
> > +
> > +             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_paired)
> > +                     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(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 */
> > +                     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_pair(struct uhidpp_softc *sc, struct uhidpp_device
> > *dev)
> > +{
> > +     struct ksensor *sens;
> > +
> > +     MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
> > +
> > +     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->flags = SENSOR_FUNKNOWN;
> > +     sensor_attach(&sc->sc_sensdev, sens);
> > +
> > +     dev->d_paired = 1;
> > +}
> > +
> > +void
> > +uhidpp_device_connect(struct uhidpp_softc *sc, struct
> > uhidpp_device *dev)
> > +{
> > +     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;
> > +     }
> > +     dev->d_battery.b_sens[1].value = dev->d_battery.b_nlevels;
> > +     dev->d_battery.b_sens[1].flags &= ~SENSOR_FUNKNOWN;
> > +
> > +     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;
> > +}
> > +
> > +
> > +int
> > +uhidpp_is_notification(struct uhidpp_report *rep)
> > +{
> > +     uint8_t swid;
> > +
> > +     /* HID++ 1.0 response. */
> > +     if (rep->rap.sub_id > 0x7f)
> > +             return 0;
> > +
> > +     /* HID++ 2.0 response if the software ID is ours. */
> > +     swid = rep->fap.funcindex_clientid & HIDPP_SOFTWARE_ID_MASK;
> > +     if (swid == HIDPP_SOFTWARE_ID)
> > +             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, void
> > *data,
> > +    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, data, len);
> > +
> > +     /* Wait until any ongoing command has completed. */
> > +     while (sc->sc_resp_state != UHIDPP_RESP_NONE)
> > +             uhidpp_sleep(sc, INFSLP);
> > +     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, data, 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_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
> 


Reply via email to