#include <sys/param.h>
#include <sys/systm.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>
#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_REPORT_ID_SHORT 0x10
#define HIDPP_REPORT_ID_LONG 0x11
#define HIDPP_REPORT_SHORT_LENGTH (7 - 1)
#define HIDPP_REPORT_LONG_LENGTH (20 - 1)
#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_ID 0x00
#define HIDPP_FEAT_ROOT_PING_FUNC 0x01
#define HIDPP_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_ENABLE_REPORTS_DEVICE_BATTERY_STATUS 0x10
#define HIDPP_ENABLE_REPORTS_RECEIVER_WIRELESS 0x01
#define HIDPP_ENABLE_REPORTS_RECEIVER_SOFTWARE_PRESENT 0x08
#define HIDPP_REG_PAIRING_INFORMATION 0xb5
#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
#define HIDPP_SOFTWARE_ID_MIN 1
#define HIDPP_SOFTWARE_ID_MAX 15
#define HIDPP_SOFTWARE_ID_LEN 4
#define HIDPP20_FEAT_ROOT_ID 0x0000
#define HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC 0x0000
#define HIDPP20_FEAT_FEATURE_ID 0x0001
#define HIDPP20_FEAT_FEATURE_COUNT_FUNC 0x0000
#define HIDPP20_FEAT_FEATURE_ID_FUNC 0x0001
#define HIDPP20_FEAT_BATTERY_ID 0x1000
#define HIDPP20_FEAT_BATTERY_LEVEL_STATUS_FUNC 0x0000
#define HIDPP20_LEVEL_STATUS_CHARGING_DONE 0x0003
#define HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC 0x0001
#define HIDPP20_CAPABILITY_RECHARGEABLE 0x0004
#define HIDPP20_FEAT_UNIFIED_BATTERY_ID 0x1004
#define HIDPP20_FEAT_UNIFIED_BATTERY_CAPABILITIES_FUNC 0x0000
#define HIDPP20_CAPABILITES_RECHARGEABLE 0x0002
#define HIDPP20_FEAT_UNIFIED_BATTERY_STATUS_FUNC 0x0001
#define HIDPP20_BATTERY_STATUS_CRITICAL 0x0001
#define HIDPP20_BATTERY_STATUS_LOW 0x0002
#define HIDPP20_BATTERY_STATUS_GOOD 0x0004
#define HIDPP20_BATTERY_STATUS_FULL 0x0008
#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
#define UHIDPP_RESP_NONE 0
#define UHIDPP_RESP_WAIT 1
#define UHIDPP_RESP_ERROR 2
#define UHIDPP_NDEVICES 6
#define UHIDPP_NNOTIFICATIONS 4
#define UHIDPP_NSENSORS 3
struct fap {
uint8_t feature_idx;
uint8_t funcidx_swid;
uint8_t params[HIDPP_REPORT_LONG_PARAMS_MAX];
};
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;
uint8_t d_major;
uint8_t d_minor;
uint8_t d_features;
#define UHIDPP_DEVICE_FEATURE_ROOT 0x01
#define UHIDPP_DEVICE_FEATURE_BATTERY 0x02
#define UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY 0x04
struct {
struct ksensor sens[UHIDPP_NSENSORS];
uint8_t feature_idx;
uint8_t nlevels;
uint8_t unified_level_mask;
uint8_t rechargeable;
} d_battery;
};
struct uhidpp_softc {
struct uhidev sc_hdev;
struct usbd_device *sc_udev;
struct mutex sc_mtx;
struct uhidpp_device sc_devices[UHIDPP_NDEVICES];
struct uhidpp_notification sc_notifications[UHIDPP_NNOTIFICATIONS];
struct usb_task sc_task;
struct ksensordev sc_sensdev;
struct sensor_task *sc_senstsk;
struct uhidpp_report *sc_req;
struct uhidpp_report *sc_resp;
u_int sc_resp_state;
u_int sc_swid;
enum {
UHIDPP_RECEIVER_UNIFYING,
UHIDPP_RECEIVER_BOLT,
} sc_receiver;
};
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 *);
int uhidpp_device_features(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 *);
static int uhidpp_has_sensors(struct uhidpp_softc *);
int hidpp_get_protocol_version(struct uhidpp_softc *, uint8_t, uint8_t *,
uint8_t *);
int hidpp10_get_name(struct uhidpp_softc *, uint8_t, char *, 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_feature_get_count(struct uhidpp_softc *, uint8_t, uint8_t,
uint8_t *);
int hidpp20_feature_get_id(struct uhidpp_softc *, uint8_t, uint8_t, uint8_t,
uint16_t *, uint8_t *);
int hidpp20_battery_get_level_status(struct uhidpp_softc *,
struct uhidpp_device *);
int hidpp20_battery_get_capability(struct uhidpp_softc *,
struct uhidpp_device *);
int hidpp20_battery_status_is_charging(uint8_t);
int hidpp20_unified_battery_get_capabilities(struct uhidpp_softc *,
struct uhidpp_device *);
int hidpp20_unified_battery_get_status(struct uhidpp_softc *,
struct uhidpp_device *);
int hidpp20_unified_battery_status_is_charging(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 *);
static uint8_t
nlevels(uint8_t mask)
{
uint8_t nbits = 0;
for (; mask > 0; mask >>= 1) {
if (mask & 1)
nbits++;
}
return nbits;
}
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 (!UHIDEV_CLAIM_MULTIPLE_REPORTID(uha))
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;
uha->claimed[HIDPP_REPORT_ID_SHORT] = 1;
uha->claimed[HIDPP_REPORT_ID_LONG] = 1;
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 npaired = 0;
int error, i;
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;
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;
sc->sc_swid = HIDPP_SOFTWARE_ID_MIN;
error = uhidev_open(&sc->sc_hdev);
if (error) {
printf(" open error %d\n", error);
return;
}
usb_init_task(&sc->sc_task, uhidpp_task, sc, USB_TASK_TYPE_GENERIC);
mtx_enter(&sc->sc_mtx);
error = uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev,
HIDPP_REPORT_ID_SHORT);
if (error) {
mtx_leave(&sc->sc_mtx);
printf(" short report error %d\n", error);
return;
}
error = uhidev_set_report_dev(sc->sc_hdev.sc_parent, &sc->sc_hdev,
HIDPP_REPORT_ID_LONG);
if (error) {
mtx_leave(&sc->sc_mtx);
printf(" long report error %d\n", error);
return;
}
if (uaa->product == 0xc548)
sc->sc_receiver = UHIDPP_RECEIVER_BOLT;
else
sc->sc_receiver = UHIDPP_RECEIVER_UNIFYING;
for (i = 0; i < UHIDPP_NDEVICES; i++) {
char name[16];
struct uhidpp_device *dev = &sc->sc_devices[i];
const char *type;
uint8_t device_id = i + 1;
dev->d_id = device_id;
if (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);
npaired++;
}
if (npaired == 0)
goto out;
error = hidpp10_enable_notifications(sc, HIDPP_DEVICE_ID_RECEIVER);
if (error)
printf(" error %d", error);
out:
mtx_leave(&sc->sc_mtx);
printf("\n");
}
int
uhidpp_detach(struct device *self, int flags)
{
struct uhidpp_softc *sc = (struct uhidpp_softc *)self;
int i;
usb_rem_wait_task(sc->sc_udev, &sc->sc_task);
if (uhidpp_has_sensors(sc))
sensor_task_unregister(sc->sc_senstsk);
KASSERT(sc->sc_resp_state == UHIDPP_RESP_NONE);
if (uhidpp_has_sensors(sc))
sensordev_deinstall(&sc->sc_sensdev);
for (i = 0; i < UHIDPP_NDEVICES; i++) {
struct uhidpp_device *dev = &sc->sc_devices[i];
struct ksensor *sens = dev->d_battery.sens;
if (!dev->d_connected)
continue;
sensor_detach(&sc->sc_sensdev, &sens[0]);
sensor_detach(&sc->sc_sensdev, &sens[1]);
if (dev->d_battery.rechargeable)
sensor_detach(&sc->sc_sensdev, &sens[2]);
}
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;
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:
case 0x40:
case 0x4b:
break;
case 0x41:
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;
MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
if (dev->d_connected)
return;
if (dev->d_features)
return;
error = hidpp_get_protocol_version(sc, dev->d_id,
&dev->d_major, &dev->d_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, dev->d_major, dev->d_minor);
if (dev->d_major <= 1)
return;
error = uhidpp_device_features(sc, dev);
if (error) {
DPRINTF("%s: features failure: device_id=%d, "
"error=%d\n",
__func__, dev->d_id, error);
return;
}
if (dev->d_features & UHIDPP_DEVICE_FEATURE_BATTERY)
error = hidpp20_battery_get_capability(sc, dev);
else if (dev->d_features & UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY)
error = hidpp20_unified_battery_get_capabilities(sc, dev);
if (error) {
DPRINTF("%s: battery capability failure: device_id=%d, "
"error=%d\n", __func__, dev->d_id, error);
return;
}
dev->d_connected = 1;
sens = &dev->d_battery.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.sens[1];
strlcpy(sens->desc, "number of battery levels", sizeof(sens->desc));
sens->type = SENSOR_INTEGER;
sens->value = dev->d_battery.nlevels;
sensor_attach(&sc->sc_sensdev, sens);
if (dev->d_battery.rechargeable) {
sens = &dev->d_battery.sens[2];
strlcpy(sens->desc, "charger", sizeof(sens->desc));
sens->type = SENSOR_INDICATOR;
sens->value = 0;
sensor_attach(&sc->sc_sensdev, sens);
}
uhidpp_device_refresh(sc, dev);
if (uhidpp_has_sensors(sc))
return;
strlcpy(sc->sc_sensdev.xname, sc->sc_hdev.sc_dev.dv_xname,
sizeof(sc->sc_sensdev.xname));
sensordev_install(&sc->sc_sensdev);
KASSERT(sc->sc_senstsk == NULL);
mtx_leave(&sc->sc_mtx);
sc->sc_senstsk = sensor_task_register(sc, uhidpp_refresh, 60);
mtx_enter(&sc->sc_mtx);
}
void
uhidpp_device_refresh(struct uhidpp_softc *sc, struct uhidpp_device *dev)
{
int error;
MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
if (dev->d_major <= 1)
return;
if (dev->d_features & UHIDPP_DEVICE_FEATURE_BATTERY)
error = hidpp20_battery_get_level_status(sc, dev);
else if (dev->d_features & UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY)
error = hidpp20_unified_battery_get_status(sc, dev);
else
error = -ENOTSUP;
if (error) {
DPRINTF("%s: battery status failure: device_id=%d, error=%d\n",
__func__, dev->d_id, error);
}
}
int
uhidpp_device_features(struct uhidpp_softc *sc, struct uhidpp_device *dev)
{
int error;
uint8_t count, feature_idx, feature_type, i;
dev->d_features |= UHIDPP_DEVICE_FEATURE_ROOT;
error = hidpp20_root_get_feature(sc, dev->d_id,
HIDPP20_FEAT_FEATURE_ID,
&feature_idx, &feature_type);
if (error) {
DPRINTF("%s: feature index failure: device_id=%d, error=%d\n",
__func__, dev->d_id, error);
return error;
}
error = hidpp20_feature_get_count(sc, dev->d_id, feature_idx, &count);
if (error) {
DPRINTF("%s: feature count failure: device_id=%d, error=%d\n",
__func__, dev->d_id, error);
return error;
}
for (i = 1; i <= count; i++) {
uint16_t id;
uint8_t type;
error = hidpp20_feature_get_id(sc, dev->d_id, feature_idx, i,
&id, &type);
if (error)
continue;
if (id == HIDPP20_FEAT_BATTERY_ID) {
dev->d_features |= UHIDPP_DEVICE_FEATURE_BATTERY;
dev->d_battery.feature_idx = i;
} else if (id == HIDPP20_FEAT_UNIFIED_BATTERY_ID) {
dev->d_features |= UHIDPP_DEVICE_FEATURE_UNIFIED_BATTERY;
dev->d_battery.feature_idx = i;
}
DPRINTF("%s: idx=%d, id=%x, type=%x device_id=%d\n",
__func__, i, id, type, dev->d_id);
}
DPRINTF("%s: device_id=%d, count=%d, features=%x\n",
__func__, dev->d_id, count, dev->d_features);
if ((dev->d_features & ~UHIDPP_DEVICE_FEATURE_ROOT) == 0)
return -ENODEV;
return 0;
}
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;
}
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_softc *sc, struct uhidpp_report *rep)
{
if (sc->sc_req == NULL)
return 1;
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;
if ((rep->rap.sub_id == HIDPP_ERROR ||
rep->fap.feature_idx == HIDPP20_ERROR) &&
rep->fap.funcidx_swid == sc->sc_req->fap.feature_idx &&
rep->fap.params[0] == sc->sc_req->fap.funcidx_swid)
return 0;
return 1;
}
static int
uhidpp_has_sensors(struct uhidpp_softc *sc)
{
return sc->sc_sensdev.xname[0] != '\0';
}
int
hidpp_get_protocol_version(struct uhidpp_softc *sc, uint8_t device_id,
uint8_t *major, uint8_t *minor)
{
struct uhidpp_report resp;
uint8_t params[3] = { 0, 0, HIDPP_PING_DATA };
int error;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_SHORT,
device_id,
HIDPP_FEAT_ROOT_ID,
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_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;
const uint8_t *name;
uint8_t len;
if (sc->sc_receiver == UHIDPP_RECEIVER_BOLT) {
uint8_t params[2] = { 0x60 + device_id, 0x01 };
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[2];
name = &resp.rap.params[3];
} else {
uint8_t params[1] = { 0x40 + (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;
len = resp.rap.params[1];
name = &resp.rap.params[2];
}
if (len > bufsiz - 1)
len = bufsiz - 1;
memcpy(buf, name, len);
buf[len] = '\0';
return 0;
}
int
hidpp10_get_type(struct uhidpp_softc *sc, uint8_t device_id, const char **buf)
{
struct uhidpp_report resp;
int error;
uint8_t type;
if (sc->sc_receiver == UHIDPP_RECEIVER_BOLT) {
uint8_t params[1] = { 0x50 + device_id };
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;
type = resp.rap.params[1] & 0xf;
} else {
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;
type = resp.rap.params[7];
}
switch (type) {
case 0x00:
*buf = "unknown";
return 0;
case 0x01:
*buf = "keyboard";
return 0;
case 0x02:
*buf = "mouse";
return 0;
case 0x03:
*buf = "numpad";
return 0;
case 0x04:
*buf = "presenter";
return 0;
case 0x08:
*buf = "trackball";
return 0;
case 0x09:
*buf = "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];
params[0] = HIDPP_ENABLE_REPORTS_DEVICE_BATTERY_STATUS;
params[1] = HIDPP_ENABLE_REPORTS_RECEIVER_WIRELESS |
HIDPP_ENABLE_REPORTS_RECEIVER_SOFTWARE_PRESENT;
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_idx, 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_ID,
HIDPP20_FEAT_ROOT_GET_FEATURE_FUNC,
params, sizeof(params), &resp);
if (error)
return error;
if (resp.fap.params[0] == 0)
return -ENOENT;
*feature_idx = resp.fap.params[0];
*feature_type = resp.fap.params[1];
return 0;
}
int
hidpp20_feature_get_count(struct uhidpp_softc *sc, uint8_t device_id,
uint8_t feature_idx, uint8_t *count)
{
struct uhidpp_report resp;
int error;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
device_id,
feature_idx,
HIDPP20_FEAT_FEATURE_COUNT_FUNC,
NULL, 0, &resp);
if (error)
return error;
*count = resp.fap.params[0];
return 0;
}
int
hidpp20_feature_get_id(struct uhidpp_softc *sc, uint8_t device_id,
uint8_t feature_idx, uint8_t idx, uint16_t *id, uint8_t *type)
{
struct uhidpp_report resp;
uint8_t params[1] = { idx };
int error;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
device_id,
feature_idx,
HIDPP20_FEAT_FEATURE_ID_FUNC,
params, sizeof(params), &resp);
if (error)
return error;
*id = bemtoh16(resp.fap.params);
*type = resp.fap.params[2];
return 0;
}
int
hidpp20_battery_get_level_status(struct uhidpp_softc *sc,
struct uhidpp_device *dev)
{
struct uhidpp_report resp;
int charging, error;
uint8_t level, status;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
dev->d_id,
dev->d_battery.feature_idx,
HIDPP20_FEAT_BATTERY_LEVEL_STATUS_FUNC,
NULL, 0, &resp);
if (error)
return error;
level = resp.fap.params[0];
status = resp.fap.params[2];
if (status == HIDPP20_LEVEL_STATUS_CHARGING_DONE) {
level = 100;
status = 0;
}
charging = hidpp20_battery_status_is_charging(status);
dev->d_battery.sens[0].value = level * 1000;
dev->d_battery.sens[0].flags &= ~SENSOR_FUNKNOWN;
if (dev->d_battery.nlevels < 10) {
if (charging)
dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
else if (level <= 10)
dev->d_battery.sens[0].status = SENSOR_S_CRIT;
else if (level <= 30)
dev->d_battery.sens[0].status = SENSOR_S_WARN;
else
dev->d_battery.sens[0].status = SENSOR_S_OK;
} else {
dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
}
if (dev->d_battery.rechargeable)
dev->d_battery.sens[2].value = charging;
return 0;
}
int
hidpp20_battery_get_capability(struct uhidpp_softc *sc,
struct uhidpp_device *dev)
{
struct uhidpp_report resp;
int error;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
dev->d_id,
dev->d_battery.feature_idx,
HIDPP20_FEAT_BATTERY_CAPABILITY_FUNC,
NULL, 0, &resp);
if (error)
return error;
dev->d_battery.nlevels = resp.fap.params[0];
dev->d_battery.rechargeable = resp.fap.params[1] &
HIDPP20_CAPABILITY_RECHARGEABLE;
return 0;
}
int
hidpp20_unified_battery_get_capabilities(struct uhidpp_softc *sc,
struct uhidpp_device *dev)
{
struct uhidpp_report resp;
int error;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
dev->d_id,
dev->d_battery.feature_idx,
HIDPP20_FEAT_UNIFIED_BATTERY_CAPABILITIES_FUNC,
NULL, 0, &resp);
if (error)
return error;
dev->d_battery.nlevels = nlevels(resp.fap.params[0]);
dev->d_battery.unified_level_mask = resp.fap.params[0];
dev->d_battery.rechargeable = resp.fap.params[1] &
HIDPP20_CAPABILITES_RECHARGEABLE;
return 0;
}
int
hidpp20_unified_battery_get_status(struct uhidpp_softc *sc,
struct uhidpp_device *dev)
{
struct uhidpp_report resp;
int charging, error;
uint8_t level, percentage, status;
error = hidpp_send_fap_report(sc,
HIDPP_REPORT_ID_LONG,
dev->d_id,
dev->d_battery.feature_idx,
HIDPP20_FEAT_UNIFIED_BATTERY_STATUS_FUNC,
NULL, 0, &resp);
if (error)
return error;
percentage = resp.fap.params[0];
level = resp.fap.params[1] & dev->d_battery.unified_level_mask;
status = resp.fap.params[2];
charging = hidpp20_unified_battery_status_is_charging(status);
dev->d_battery.sens[0].value = percentage * 1000;
dev->d_battery.sens[0].flags &= ~SENSOR_FUNKNOWN;
dev->d_battery.sens[0].status = SENSOR_S_UNKNOWN;
if (!charging) {
if (level & HIDPP20_BATTERY_STATUS_CRITICAL)
dev->d_battery.sens[0].status = SENSOR_S_CRIT;
else if (level & HIDPP20_BATTERY_STATUS_LOW)
dev->d_battery.sens[0].status = SENSOR_S_WARN;
else if (level & HIDPP20_BATTERY_STATUS_GOOD)
dev->d_battery.sens[0].status = SENSOR_S_OK;
else if (level & HIDPP20_BATTERY_STATUS_FULL)
dev->d_battery.sens[0].status = SENSOR_S_OK;
}
if (dev->d_battery.rechargeable)
dev->d_battery.sens[2].value = charging;
return 0;
}
int
hidpp20_unified_battery_status_is_charging(uint8_t status)
{
switch (status) {
case 1:
case 2:
return 1;
default:
return 0;
}
}
int
hidpp20_battery_status_is_charging(uint8_t status)
{
switch (status) {
case 1:
case 2:
case 4:
return 1;
case 3:
return 1;
case 0:
case 5:
case 6:
case 7:
default:
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_idx, uint8_t func_idx,
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_idx = feature_idx;
sc->sc_swid = sc->sc_swid == HIDPP_SOFTWARE_ID_MAX ?
HIDPP_SOFTWARE_ID_MIN : sc->sc_swid + 1;
req.fap.funcidx_swid =
(func_idx << HIDPP_SOFTWARE_ID_LEN) | sc->sc_swid;
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 = 0;
int len, n;
MUTEX_ASSERT_LOCKED(&sc->sc_mtx);
if (report_id == HIDPP_REPORT_ID_SHORT)
len = HIDPP_REPORT_SHORT_LENGTH;
else if (report_id == HIDPP_REPORT_ID_LONG)
len = HIDPP_REPORT_LONG_LENGTH;
else
return -EINVAL;
DREPORT(__func__, report_id, (const unsigned char *)req, len);
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;
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;
}
if (sc->sc_resp_state == UHIDPP_RESP_WAIT) {
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_idx == 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