On Tue, Feb 02, 2021 at 07:55:17PM +0100, Anton Lindqvist wrote: [...] > > > However, it would ease development by getting this in and continue > > > development in tree. Anyone willing to ok? > > > > The reason why I'm currently reluctant to ok this is because of the > > uhidev_set_intr() part which you introduce with this diff, and I don't > > fully understand. I can understand that for this device you seem to > > have the need to state commands already in the attach code. > > > > But is sc_subdevs[repid] really only used for the interrupt handler? > > Shouldn't it be more generically called something like > > uhidev_set_repid, or uhidev_set_subdev or something like that? > > That's a fair point. How about uhidev_set_report_dev?
Yes, I think that's less confusing. I gave it a spin here as well and couldn't face any regression so far. ok mglocker@ > diff --git share/man/man4/Makefile share/man/man4/Makefile > index 70e62135237..22db50677db 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 \ > - ujoy.4 uk.4 ukbd.4 \ > + uftdi.4 ugen.4 ugl.4 ugold.4 uguru.4 uhci.4 uhid.4 uhidev.4 uhidpp.4 \ > + uipaq.4 ujoy.4 uk.4 ukbd.4 \ > ukphy.4 ulpt.4 umass.4 umb.4 umbg.4 umcs.4 umct.4 umidi.4 umodem.4 \ > ums.4 umsm.4 umstc.4 umt.4 unix.4 uonerng.4 uow.4 uoaklux.4 uoakrh.4 \ > uoakv.4 upd.4 upgt.4 upl.4 uplcom.4 ural.4 ure.4 url.4 urlphy.4 \ > diff --git share/man/man4/uhidev.4 share/man/man4/uhidev.4 > index 06911ddef29..aa1efbea710 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 "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 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 8b9e3ffdc3c..245d01cdef4 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 ujoy 4 > USB joysticks/gamecontrollers > .It Xr ukbd 4 > diff --git sys/arch/alpha/conf/GENERIC sys/arch/alpha/conf/GENERIC > index 05953f8e7cb..c789e5dacad 100644 > --- sys/arch/alpha/conf/GENERIC > +++ sys/arch/alpha/conf/GENERIC > @@ -108,6 +108,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller > support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > #atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/amd64/conf/GENERIC sys/arch/amd64/conf/GENERIC > index ffa1b4a497c..f6c62eee450 100644 > --- sys/arch/amd64/conf/GENERIC > +++ sys/arch/amd64/conf/GENERIC > @@ -287,6 +287,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > umstc* at uhidev? # Microsoft Surface Type Cover > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > diff --git sys/arch/arm64/conf/GENERIC sys/arch/arm64/conf/GENERIC > index adefc8ffea9..ba8efb20919 100644 > --- sys/arch/arm64/conf/GENERIC > +++ sys/arch/arm64/conf/GENERIC > @@ -370,6 +370,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller > support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/armv7/conf/GENERIC sys/arch/armv7/conf/GENERIC > index 13124411dc1..b3c146ef498 100644 > --- sys/arch/armv7/conf/GENERIC > +++ sys/arch/armv7/conf/GENERIC > @@ -322,6 +322,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/hppa/conf/GENERIC sys/arch/hppa/conf/GENERIC > index d95e5a8977b..f27cb2a477e 100644 > --- sys/arch/hppa/conf/GENERIC > +++ sys/arch/hppa/conf/GENERIC > @@ -112,6 +112,7 @@ wskbd* at ukbd? mux 1 > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > url* at uhub? # Realtek RTL8150L based adapters > diff --git sys/arch/i386/conf/GENERIC sys/arch/i386/conf/GENERIC > index c167a2f8011..b1900cffd11 100644 > --- sys/arch/i386/conf/GENERIC > +++ sys/arch/i386/conf/GENERIC > @@ -285,6 +285,7 @@ ucom* at uticom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/landisk/conf/GENERIC sys/arch/landisk/conf/GENERIC > index 120aa0a108d..53b42e1cdf5 100644 > --- sys/arch/landisk/conf/GENERIC > +++ sys/arch/landisk/conf/GENERIC > @@ -138,6 +138,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/loongson/conf/GENERIC sys/arch/loongson/conf/GENERIC > index 4e8d826142b..36d779b724a 100644 > --- sys/arch/loongson/conf/GENERIC > +++ sys/arch/loongson/conf/GENERIC > @@ -165,6 +165,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > atu* at uhub? # Atmel AT76c50x based 802.11b > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > diff --git sys/arch/macppc/conf/GENERIC sys/arch/macppc/conf/GENERIC > index 46cd7397286..e7ee6cc0429 100644 > --- sys/arch/macppc/conf/GENERIC > +++ sys/arch/macppc/conf/GENERIC > @@ -261,6 +261,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/octeon/conf/GENERIC sys/arch/octeon/conf/GENERIC > index e5f407d7e9a..a3aa720b4f2 100644 > --- sys/arch/octeon/conf/GENERIC > +++ sys/arch/octeon/conf/GENERIC > @@ -157,6 +157,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/arch/powerpc64/conf/GENERIC sys/arch/powerpc64/conf/GENERIC > index cba5f59f2cf..144cc41dbeb 100644 > --- sys/arch/powerpc64/conf/GENERIC > +++ sys/arch/powerpc64/conf/GENERIC > @@ -128,6 +128,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > umstc* at uhidev? # Microsoft Surface Type Cover > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > diff --git sys/arch/sgi/conf/GENERIC-IP27 sys/arch/sgi/conf/GENERIC-IP27 > index 889a921dd8f..1d47d94cf0d 100644 > --- sys/arch/sgi/conf/GENERIC-IP27 > +++ sys/arch/sgi/conf/GENERIC-IP27 > @@ -129,6 +129,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > atu* at uhub? # Atmel AT76c50x based 802.11b > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > axe* at uhub? # ASIX Electronics AX88172 USB Ethernet > diff --git sys/arch/sgi/conf/GENERIC-IP30 sys/arch/sgi/conf/GENERIC-IP30 > index bd90d34945b..f09f274918e 100644 > --- sys/arch/sgi/conf/GENERIC-IP30 > +++ sys/arch/sgi/conf/GENERIC-IP30 > @@ -120,6 +120,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > atu* at uhub? # Atmel AT76c50x based 802.11b > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > axe* at uhub? # ASIX Electronics AX88172 USB Ethernet > diff --git sys/arch/sgi/conf/GENERIC-IP32 sys/arch/sgi/conf/GENERIC-IP32 > index d4c0d64019a..4ccab12f171 100644 > --- sys/arch/sgi/conf/GENERIC-IP32 > +++ sys/arch/sgi/conf/GENERIC-IP32 > @@ -112,6 +112,7 @@ ucom* at uslhcom? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > atu* at uhub? # Atmel AT76c50x based 802.11b > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > axe* at uhub? # ASIX Electronics AX88172 USB Ethernet > diff --git sys/arch/sparc64/conf/GENERIC sys/arch/sparc64/conf/GENERIC > index e29faa8181a..416cad7bd4b 100644 > --- sys/arch/sparc64/conf/GENERIC > +++ sys/arch/sparc64/conf/GENERIC > @@ -225,6 +225,7 @@ ucom* at umsm? > uhid* at uhidev? # USB generic HID support > fido* at uhidev? # FIDO/U2F security key support > ujoy* at uhidev? # USB joystick/gamecontroller support > +uhidpp* at uhidev? # Logitech HID++ Devices > upd* at uhidev? # USB Power Devices sensors > aue* at uhub? # ADMtek AN986 Pegasus Ethernet > atu* at uhub? # Atmel AT76c50x based 802.11b > diff --git sys/dev/usb/files.usb sys/dev/usb/files.usb > index ff94bf8765b..35e533ab290 100644 > --- sys/dev/usb/files.usb > +++ sys/dev/usb/files.usb > @@ -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 sys/dev/usb/uhidev.c sys/dev/usb/uhidev.c > index 16f440714c1..ab403483182 100644 > --- sys/dev/usb/uhidev.c > +++ sys/dev/usb/uhidev.c > @@ -256,8 +256,13 @@ 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_report_dev(). > + */ > + if (sc->sc_subdevs[repid] == NULL) > + sc->sc_subdevs[repid] = (struct uhidev *)dev; > + } > return; > } > > @@ -270,7 +275,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_report_dev(). */ > + if (sc->sc_subdevs[repid] == NULL) > + sc->sc_subdevs[repid] = (struct uhidev *)dev; > } > } > > @@ -992,3 +999,15 @@ uhidev_clear_iface_eps(struct uhidev_softc *sc, struct > usbd_interface *iface) > bad: > printf("%s: clear endpoints failed!\n", __func__); > } > + > +int > +uhidev_set_report_dev(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..bb6f64c65f0 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_report_dev(struct uhidev_softc *, struct uhidev *, int); > diff --git sys/dev/usb/uhidpp.c sys/dev/usb/uhidpp.c > new file mode 100644 > index 00000000000..942b4d45d4f > --- /dev/null > +++ sys/dev/usb/uhidpp.c > @@ -0,0 +1,1054 @@ > +/* $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_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, data, 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 >