diff --git a/src/Makefile.am b/src/Makefile.am index aad4f3d5..81748172 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -8,6 +8,8 @@ libratbag_la_SOURCES = \ driver-logitech-unifying.c \ hidpp10.c \ hidpp10.h \ + hidpp20.c \ + hidpp20.h \ libratbag.c \ libratbag.h \ libratbag-hidraw.c \ diff --git a/src/driver-logitech-unifying.c b/src/driver-logitech-unifying.c index 988d33f8..6d284e33 100644 --- a/src/driver-logitech-unifying.c +++ b/src/driver-logitech-unifying.c @@ -29,6 +29,12 @@ * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing */ +/* + * for this driver to work, you need a kernel >= v3.19 or one which contains + * 925f0f3ed24f98b40c28627e74ff3e7f9d1e28bc ("HID: logitech-dj: allow transfer + * of HID++ reports from/to the correct dj device") + */ + #include "config.h" #include @@ -37,6 +43,7 @@ #include #include +#include "hidpp20.h" #include "libratbag-private.h" #include "libratbag-hidraw.h" @@ -108,6 +115,15 @@ unifying_probe(struct ratbag_device *device, const struct ratbag_id id) drv_data->proto_major = 1; drv_data->proto_minor = 0; + rc = hidpp20_root_get_protocol_version(device, &drv_data->proto_major, &drv_data->proto_minor); + if (rc) { + /* communication error, best to ignore the device */ + rc = -EINVAL; + goto err; + } + + log_info(device->ratbag, "'%s' is using protocol v%d.%d\n", ratbag_device_get_name(device), drv_data->proto_major, drv_data->proto_minor); + ratbag_set_drv_data(device, drv_data); device->num_profiles = 1; diff --git a/src/hidpp20.c b/src/hidpp20.c new file mode 100644 index 00000000..6d23e5de --- /dev/null +++ b/src/hidpp20.c @@ -0,0 +1,160 @@ +/* + * HID++ 2.0 library. + * + * Copyright 2015 Benjamin Tissoires + * Copyright 2015 Red Hat, Inc + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: + * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing + */ + +#include +#include +#include + +#include "hidpp20.h" +#include "libratbag.h" +#include "libratbag-hidraw.h" +#include "libratbag-util.h" +#include "libratbag-private.h" + +static int +hidpp20_write_command(struct ratbag_device *device, uint8_t *cmd, int size) +{ + int res = ratbag_hidraw_output_report(device, cmd, size); + + if (res == 0) + return 0; + + if (res < 0) + log_error(device->ratbag, "Error: %d\n", errno); + + return res; +} + +int +hidpp20_request_command(struct ratbag_device *device, union hidpp20_message *msg) +{ + struct ratbag *ratbag = device->ratbag; + union hidpp20_message read_buffer; + int ret; + uint8_t hidpp_err = 0; + size_t msg_len; + + msg_len = msg->msg.report_id == REPORT_ID_SHORT ? SHORT_MESSAGE_LENGTH : LONG_MESSAGE_LENGTH; + + log_buf_debug(ratbag, "sending: ", msg->data, msg_len); + + /* Send the message to the Device */ + ret = hidpp20_write_command(device, msg->data, msg_len); + if (ret) + goto out_err; + + /* + * Now read the answers from the device: + * loop until we get the actual answer or an error code. + */ + do { + ret = ratbag_hidraw_read_input_report(device, read_buffer.data, LONG_MESSAGE_LENGTH); + log_buf_debug(ratbag, " *** received: ", read_buffer.data, ret); + + if (read_buffer.msg.report_id != REPORT_ID_SHORT && + read_buffer.msg.report_id != REPORT_ID_LONG) + continue; + + /* actual answer */ + if (read_buffer.msg.sub_id == msg->msg.sub_id && + read_buffer.msg.address == msg->msg.address) + break; + + /* error */ + if (read_buffer.msg.sub_id == __ERROR_MSG && + (read_buffer.msg.parameters[0] & 0x0f) == (read_buffer.msg.address & 0x0f)) { + hidpp_err = read_buffer.msg.parameters[1]; + log_error(ratbag, + " HID++ error from the device (%d): %s (%02x)\n", + read_buffer.msg.device_idx, + hidpp_errors[hidpp_err] ? hidpp_errors[hidpp_err] : "Undocumented error code", + hidpp_err); + break; + } + } while (ret > 0); + + if (ret < 0) { + log_error(ratbag, " USB error: %d\n", errno); + perror("write"); + goto out_err; + } + + if (!hidpp_err) { + log_buf_debug(ratbag, " received: ", read_buffer.data, ret); + /* copy the answer for the caller */ + *msg = read_buffer; + } + + ret = hidpp_err; + +out_err: + return ret; +} + +/* -------------------------------------------------------------------------- */ +/* 0x0000: Root */ +/* -------------------------------------------------------------------------- */ + +#define HIDPP_PAGE_ROOT 0x0000 +#define HIDPP_PAGE_ROOT_IDX 0x00 + +#define CMD_ROOT_GET_FEATURE 0x08 +#define CMD_ROOT_GET_PROTOCOL_VERSION 0x18 + +int +hidpp20_root_get_protocol_version(struct ratbag_device *device, + unsigned *major, + unsigned *minor) +{ + int rc; + union hidpp20_message msg = { + .msg.report_id = REPORT_ID_SHORT, + .msg.device_idx = 0xff, + .msg.sub_id = HIDPP_PAGE_ROOT_IDX, + .msg.address = CMD_ROOT_GET_PROTOCOL_VERSION, + }; + + rc = hidpp20_request_command(device, &msg); + + if (rc == ERR_INVALID_SUBID) { + *major = 1; + *minor = 0; + return 0; + } + + if (rc == 0) { + *major = msg.msg.parameters[0]; + *minor = msg.msg.parameters[1]; + } + + return rc; +} + diff --git a/src/hidpp20.h b/src/hidpp20.h new file mode 100644 index 00000000..c4e834ef --- /dev/null +++ b/src/hidpp20.h @@ -0,0 +1,60 @@ +/* + * HID++ 2.0 library - headers file. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +/* + * Based on the HID++ 2.0 documentation provided by Nestor Lopez Casado at: + * https://drive.google.com/folderview?id=0BxbRzx7vEV7eWmgwazJ3NUFfQ28&usp=sharing + */ + +#ifndef HIDPP_20_H +#define HIDPP_20_H + +#include "hidpp10.h" + +extern const char *hidpp_errors[0xFF]; + +struct _hidpp20_message { + uint8_t report_id; + uint8_t device_idx; + uint8_t sub_id; + uint8_t address; + uint8_t parameters[LONG_MESSAGE_LENGTH - 4U]; +} __attribute__((packed)); + +union hidpp20_message { + struct _hidpp20_message msg; + uint8_t data[LONG_MESSAGE_LENGTH]; +}; + +int hidpp20_request_command(struct ratbag_device *device, union hidpp20_message *msg); + +/* -------------------------------------------------------------------------- */ +/* 0x0000: Root */ +/* -------------------------------------------------------------------------- */ + +int hidpp20_root_get_protocol_version(struct ratbag_device *device, + unsigned *major, + unsigned *minor); + + +#endif /* HIDPP_20_H */