Files
libratbag/src/hidpp20.c
Peter Hutterer a962082173 hidpp20: constify a helper function
Just because we can

Signed-off-by: Peter Hutterer <peter.hutterer@who-t.net>
2017-05-02 09:52:59 +02:00

2120 lines
53 KiB
C

/*
* HID++ 2.0 library.
*
* Copyright 2015 Benjamin Tissoires <benjamin.tissoires@gmail.com>
* 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 <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include "hidpp20.h"
#include "libratbag.h"
#include "libratbag-hidraw.h"
#include "libratbag-util.h"
#include "libratbag-private.h"
const char*
hidpp20_feature_get_name(uint16_t feature)
{
static char numeric[8];
const char *str;
#define CASE_RETURN_STRING(a) case a: return #a; break
switch(feature)
{
CASE_RETURN_STRING(HIDPP_PAGE_ROOT);
CASE_RETURN_STRING(HIDPP_PAGE_FEATURE_SET);
CASE_RETURN_STRING(HIDPP_PAGE_DEVICE_INFO);
CASE_RETURN_STRING(HIDPP_PAGE_BATTERY_LEVEL_STATUS);
CASE_RETURN_STRING(HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS);
CASE_RETURN_STRING(HIDPP_PAGE_SPECIAL_KEYS_BUTTONS);
CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_POINTER_BASIC);
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI);
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE);
CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS);
CASE_RETURN_STRING(HIDPP_PAGE_ONBOARD_PROFILES);
CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_BUTTON_SPY);
default:
sprintf_safe(numeric, "%#4x", feature);
str = numeric;
break;
}
#undef CASE_RETURN_STRING
return str;
}
static int
hidpp20_request_command_allow_error(struct hidpp20_device *device, union hidpp20_message *msg,
bool allow_error)
{
union hidpp20_message read_buffer;
int ret;
uint8_t hidpp_err = 0;
size_t msg_len;
/* msg->address is 4 MSB: subcommand, 4 LSB: 4-bit SW identifier so
* the device knows who to respond to. kernel uses 0x1 */
const int DEVICE_SW_ID = 0x8;
if (msg->msg.address & 0xf) {
hidpp_log_raw(&device->base, "hidpp20 error: sw address is already set\n");
return -EINVAL;
}
msg->msg.address |= DEVICE_SW_ID;
msg_len = msg->msg.report_id == REPORT_ID_SHORT ? SHORT_MESSAGE_LENGTH : LONG_MESSAGE_LENGTH;
/* Send the message to the Device */
ret = hidpp_write_command(&device->base, 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 = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH);
/* Wait and retry if the USB timed out */
if (ret == -ETIMEDOUT) {
msleep(10);
ret = hidpp_read_response(&device->base, read_buffer.data, LONG_MESSAGE_LENGTH);
}
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.sub_id == 0xff) &&
read_buffer.msg.address == msg->msg.sub_id &&
read_buffer.msg.parameters[0] == msg->msg.address) {
hidpp_err = read_buffer.msg.parameters[1];
if (allow_error)
hidpp_log_debug(&device->base,
" 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);
else
hidpp_log_error(&device->base,
" 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) {
hidpp_log_error(&device->base, " USB error: %s (%d)\n", strerror(-ret), -ret);
perror("write");
goto out_err;
}
if (!hidpp_err) {
/* copy the answer for the caller */
*msg = read_buffer;
}
ret = hidpp_err;
out_err:
return ret;
}
int
hidpp20_request_command(struct hidpp20_device *device, union hidpp20_message *msg)
{
return hidpp20_request_command_allow_error(device, msg, false);
}
/* -------------------------------------------------------------------------- */
/* 0x0000: Root */
/* -------------------------------------------------------------------------- */
#define HIDPP_PAGE_ROOT_IDX 0x00
#define CMD_ROOT_GET_FEATURE 0x00
#define CMD_ROOT_GET_PROTOCOL_VERSION 0x10
/**
* Returns the feature index or 0x00 if it is not found.
*/
static uint8_t
hidpp_root_get_feature_idx(struct hidpp20_device *device,
uint16_t feature)
{
unsigned i;
/* error or not, we should not ask for feature 0 */
if (feature == 0x0000)
return 0;
/* feature 0x0000 is always at 0 */
for (i = 1; i < device->feature_count; i++) {
if (device->feature_list[i].feature == feature)
return i;
}
return 0;
}
int
hidpp_root_get_feature(struct hidpp20_device *device,
uint16_t feature,
uint8_t *feature_index,
uint8_t *feature_type,
uint8_t *feature_version)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = HIDPP_PAGE_ROOT_IDX,
.msg.address = CMD_ROOT_GET_FEATURE,
};
hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], feature);
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
*feature_index = msg.msg.parameters[0];
*feature_type = msg.msg.parameters[1];
*feature_version = msg.msg.parameters[2];
hidpp_log_raw(&device->base, "feature 0x%04x is at 0x%02x\n", feature, *feature_index);
return 0;
}
int
hidpp20_root_get_protocol_version(struct hidpp20_device *device,
unsigned *major,
unsigned *minor)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = HIDPP_PAGE_ROOT_IDX,
.msg.address = CMD_ROOT_GET_PROTOCOL_VERSION,
};
rc = hidpp20_request_command_allow_error(device, &msg, true);
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;
}
/* -------------------------------------------------------------------------- */
/* 0x0001: Feature Set */
/* -------------------------------------------------------------------------- */
#define CMD_FEATURE_SET_GET_COUNT 0x00
#define CMD_FEATURE_SET_GET_FEATURE_ID 0x10
static int
hidpp20_feature_set_get_count(struct hidpp20_device *device, uint8_t reg)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_FEATURE_SET_GET_COUNT,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[0];
}
static int
hidpp20_feature_set_get_feature_id(struct hidpp20_device *device,
uint8_t reg,
uint8_t feature_index,
uint16_t *feature,
uint8_t *type)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_FEATURE_SET_GET_FEATURE_ID,
.msg.parameters[0] = feature_index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
*feature = hidpp_get_unaligned_be_u16(msg.msg.parameters);
*type = msg.msg.parameters[2];
return 0;
}
/**
* allocates the list of features.
*
* returns 0 or a negative error
*/
static int
hidpp20_feature_set_get(struct hidpp20_device *device)
{
uint8_t feature_index, feature_type, feature_version;
struct hidpp20_feature *flist;
int rc;
uint8_t feature_count;
unsigned int i;
rc = hidpp_root_get_feature(device,
HIDPP_PAGE_FEATURE_SET,
&feature_index,
&feature_type,
&feature_version);
if (rc)
return rc;
rc = hidpp20_feature_set_get_count(device, feature_index);
if (rc < 0)
return rc;
feature_count = (uint8_t)rc;
if (!feature_count)
return -ENOTSUP;
flist = zalloc(feature_count * sizeof(struct hidpp20_feature));
for (i = 0; i < feature_count; i++) {
rc = hidpp20_feature_set_get_feature_id(device,
feature_index,
i,
&flist[i].feature,
&flist[i].type);
if (rc)
goto err;
}
device->feature_list = flist;
device->feature_count = feature_count;
return 0;
err:
free(flist);
return rc;
}
/* -------------------------------------------------------------------------- */
/* 0x1000: Battery level status */
/* -------------------------------------------------------------------------- */
#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS 0x00
#define CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_CAPABILITY 0x10
int
hidpp20_batterylevel_get_battery_level(struct hidpp20_device *device,
uint16_t *level,
uint16_t *next_level)
{
uint8_t feature_index;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_BATTERY_LEVEL_STATUS_GET_BATTERY_LEVEL_STATUS,
};
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_BATTERY_LEVEL_STATUS);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
*level = msg.msg.parameters[0];
*next_level = msg.msg.parameters[1];
return msg.msg.parameters[2];
}
/* -------------------------------------------------------------------------- */
/* 0x1b00: KBD reprogrammable keys and mouse buttons */
/* -------------------------------------------------------------------------- */
#define CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT 0x00
#define CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO 0x10
static int
hidpp20_kbd_reprogrammable_keys_get_count(struct hidpp20_device *device, uint8_t reg)
{
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_COUNT,
};
int rc;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[0];
}
static int
hidpp20_kbd_reprogrammable_keys_get_info(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_control_id *control)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_KBD_REPROGRAMMABLE_KEYS_GET_CTRL_ID_INFO,
.msg.parameters[0] = control->index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
control->control_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[0]);
control->task_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[2]);
control->flags = msg.msg.parameters[4];
return 0;
}
int
hidpp20_kbd_reprogrammable_keys_get_controls(struct hidpp20_device *device,
struct hidpp20_control_id **controls_list)
{
uint8_t feature_index;
struct hidpp20_control_id *c_list, *control;
uint8_t num_controls;
unsigned i;
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_KBD_REPROGRAMMABLE_KEYS);
if (feature_index == 0)
return -ENOTSUP;
rc = hidpp20_kbd_reprogrammable_keys_get_count(device, feature_index);
if (rc < 0)
return rc;
num_controls = rc;
if (num_controls == 0) {
*controls_list = NULL;
return 0;
}
c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id));
for (i = 0; i < num_controls; i++) {
control = &c_list[i];
control->index = i;
rc = hidpp20_kbd_reprogrammable_keys_get_info(device,
feature_index,
control);
if (rc)
goto err;
/* 0x1b00 and 0x1b04 have the same control/task id mappings.
* I hope */
hidpp_log_raw(&device->base,
"control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x\n",
control->index,
hidpp20_1b04_get_logical_mapping_name(control->control_id),
control->control_id,
hidpp20_1b04_get_physical_mapping_name(control->task_id),
control->task_id,
control->flags);
}
*controls_list = c_list;
return num_controls;
err:
free(c_list);
return rc;
}
/* -------------------------------------------------------------------------- */
/* 0x8070 - Color LED effects */
/* -------------------------------------------------------------------------- */
#define CMD_COLOR_LED_EFFECTS_GET_INFO 0x00
#define CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO 0x10
static int
hidpp20_color_led_zones_get_count(struct hidpp20_device *device, uint8_t reg)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_COLOR_LED_EFFECTS_GET_INFO,
};
struct hidpp20_color_led_info *info;
uint8_t feature_index;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_COLOR_LED_EFFECTS);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
info = (struct hidpp20_color_led_info *)msg.msg.parameters;
return info->zone_count;
}
int
hidpp20_color_led_effects_get_zone_info(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_color_led_zone_info *info)
{
int rc;
struct hidpp20_color_led_zone_info *msg_info;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_COLOR_LED_EFFECTS_GET_ZONE_INFO,
.msg.parameters[0] = info->index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
msg_info = (struct hidpp20_color_led_zone_info *)msg.msg.parameters;
info->location = msg_info->location;
info->num_effects = msg_info->num_effects;
info->persistency_caps = msg_info->persistency_caps;
return 0;
}
int
hidpp20_color_led_effects_get_zone_infos(struct hidpp20_device *device,
struct hidpp20_color_led_zone_info **infos_list)
{
uint8_t feature_index;
struct hidpp20_color_led_zone_info *i_list, *info;
uint8_t num_infos;
unsigned i;
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_COLOR_LED_EFFECTS);
if (feature_index == 0)
return -ENOTSUP;
rc = hidpp20_color_led_zones_get_count(device, feature_index);
if (rc < 0)
return rc;
num_infos = rc;
if (num_infos == 0) {
*infos_list = NULL;
return 0;
}
i_list = zalloc(num_infos * sizeof(struct hidpp20_color_led_zone_info));
for (i = 0; i < num_infos; i++) {
info = &i_list[i];
info->index = i;
rc = hidpp20_color_led_effects_get_zone_info(device, feature_index, info);
if (rc)
goto err;
hidpp_log_raw(&device->base,
"led_info %d: location: %d type %s num_effects: %d persistency_caps: 0x%02x\n",
info->index,
info->location,
hidpp20_8070_get_location_mapping_name(info->location),
info->num_effects,
info->persistency_caps);
}
*infos_list = i_list;
return num_infos;
err:
free(i_list);
return rc;
}
/* -------------------------------------------------------------------------- */
/* 0x1b04: Special keys and mouse buttons */
/* -------------------------------------------------------------------------- */
#define CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT 0x00
#define CMD_SPECIAL_KEYS_BUTTONS_GET_INFO 0x10
#define CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING 0x20
#define CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING 0x30
static int
hidpp20_special_keys_buttons_get_count(struct hidpp20_device *device, uint8_t reg)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_COUNT,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[0];
}
static int
hidpp20_special_keys_buttons_get_info(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_control_id *control)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_INFO,
.msg.parameters[0] = control->index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
control->control_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[0]);
control->task_id = hidpp_get_unaligned_be_u16(&msg.msg.parameters[2]);
control->flags = msg.msg.parameters[4];
control->position = msg.msg.parameters[5];
control->group = msg.msg.parameters[6];
control->group_mask = msg.msg.parameters[7];
control->raw_XY = msg.msg.parameters[8] & 0x01;
return 0;
}
static int
hidpp20_special_keys_buttons_get_reporting(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_control_id *control)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_SPECIAL_KEYS_BUTTONS_GET_REPORTING,
};
hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id);
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
control->reporting.remapped = hidpp_get_unaligned_be_u16(&msg.msg.parameters[3]);
control->reporting.raw_XY = !!(msg.msg.parameters[2] & 0x10);
control->reporting.persist = !!(msg.msg.parameters[2] & 0x04);
control->reporting.divert = !!(msg.msg.parameters[2] & 0x01);
return 0;
}
int hidpp20_special_key_mouse_get_controls(struct hidpp20_device *device,
struct hidpp20_control_id **controls_list)
{
uint8_t feature_index;
struct hidpp20_control_id *c_list, *control;
uint8_t num_controls;
unsigned i;
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_SPECIAL_KEYS_BUTTONS);
if (feature_index == 0)
return -ENOTSUP;
rc = hidpp20_special_keys_buttons_get_count(device, feature_index);
if (rc < 0)
return rc;
num_controls = rc;
if (num_controls == 0) {
*controls_list = NULL;
return 0;
}
c_list = zalloc(num_controls * sizeof(struct hidpp20_control_id));
for (i = 0; i < num_controls; i++) {
control = &c_list[i];
control->index = i;
rc = hidpp20_special_keys_buttons_get_info(device,
feature_index,
control);
if (rc)
goto err;
rc = hidpp20_special_keys_buttons_get_reporting(device,
feature_index,
control);
if (rc)
goto err;
hidpp_log_raw(&device->base,
"control %d: cid: '%s' (%d) tid: '%s' (%d) flags: 0x%02x pos: %d group: %d gmask: 0x%02x raw_XY: %s\n"
" reporting: raw_xy: %s persist: %s divert: %s remapped: '%s' (%d)\n",
control->index,
hidpp20_1b04_get_logical_mapping_name(control->control_id),
control->control_id,
hidpp20_1b04_get_physical_mapping_name(control->task_id),
control->task_id,
control->flags,
control->position,
control->group,
control->group_mask,
control->raw_XY ? "yes" : "no",
control->reporting.raw_XY ? "yes" : "no",
control->reporting.persist ? "yes" : "no",
control->reporting.divert ? "yes" : "no",
hidpp20_1b04_get_logical_mapping_name(control->reporting.remapped),
control->reporting.remapped);
}
*controls_list = c_list;
return num_controls;
err:
free(c_list);
return rc;
}
int
hidpp20_special_key_mouse_set_control(struct hidpp20_device *device,
struct hidpp20_control_id *control)
{
uint8_t feature_index;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_SPECIAL_KEYS_BUTTONS_SET_REPORTING,
};
hidpp_set_unaligned_be_u16(&msg.msg.parameters[0], control->control_id);
hidpp_set_unaligned_be_u16(&msg.msg.parameters[3], control->reporting.remapped);
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_SPECIAL_KEYS_BUTTONS);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
if (control->reporting.divert)
msg.msg.parameters[2] |= 0x03;
if (control->reporting.persist)
msg.msg.parameters[2] |= 0x0c;
if (control->reporting.raw_XY)
msg.msg.parameters[2] |= 0x20;
return hidpp20_request_command(device, &msg);
}
/* -------------------------------------------------------------------------- */
/* 0x2200: Mouse Pointer Basic Optical Sensors */
/* -------------------------------------------------------------------------- */
#define CMD_MOUSE_POINTER_BASIC_GET_INFO 0x00
int
hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device,
uint16_t *resolution,
uint8_t *flags)
{
uint8_t feature_index;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_MOUSE_POINTER_BASIC_GET_INFO,
};
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_MOUSE_POINTER_BASIC);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
*resolution = hidpp_get_unaligned_be_u16(msg.msg.parameters);
*flags = msg.msg.parameters[2];
return 0;
}
/* -------------------------------------------------------------------------- */
/* 0x2201: Adjustable DPI */
/* -------------------------------------------------------------------------- */
#define CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT 0x00
#define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST 0x10
#define CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI 0x20
#define CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI 0x30
static int
hidpp20_adjustable_dpi_get_count(struct hidpp20_device *device, uint8_t reg)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_COUNT,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[0];
}
static int
hidpp20_adjustable_dpi_get_dpi_list(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_sensor *sensor)
{
int rc;
unsigned i = 1, dpi_index = 0;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST,
.msg.parameters[0] = sensor->index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
sensor->dpi_min = 0xffff;
sensor->index = msg.msg.parameters[0];
while (i < LONG_MESSAGE_LENGTH - 4U &&
hidpp_get_unaligned_be_u16(&msg.msg.parameters[i]) != 0) {
uint16_t value = hidpp_get_unaligned_be_u16(&msg.msg.parameters[i]);
if (value > 0xe000) {
sensor->dpi_steps = value - 0xe000;
} else {
sensor->dpi_min = value < sensor->dpi_min ? value : sensor->dpi_min;
sensor->dpi_max = value > sensor->dpi_max ? value : sensor->dpi_max;
sensor->dpi_list[dpi_index++] = value;
}
assert(sensor->dpi_list[dpi_index] == 0x0000);
i += 2;
}
return 0;
}
static int
hidpp20_adjustable_dpi_get_dpi(struct hidpp20_device *device,
uint8_t reg,
struct hidpp20_sensor *sensor)
{
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.sub_id = reg,
.msg.address = CMD_ADJUSTABLE_DPI_GET_SENSOR_DPI,
.msg.parameters[0] = sensor->index,
};
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
sensor->dpi = hidpp_get_unaligned_be_u16(&msg.msg.parameters[1]);
sensor->default_dpi = hidpp_get_unaligned_be_u16(&msg.msg.parameters[3]);
return 0;
}
int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device,
struct hidpp20_sensor **sensors_list)
{
uint8_t feature_index;
struct hidpp20_sensor *s_list, *sensor;
uint8_t num_sensors;
unsigned i;
int rc;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ADJUSTABLE_DPI);
if (feature_index == 0)
return -ENOTSUP;
rc = hidpp20_adjustable_dpi_get_count(device, feature_index);
if (rc < 0)
return rc;
num_sensors = rc;
if (num_sensors == 0) {
*sensors_list = NULL;
return 0;
}
s_list = zalloc(num_sensors * sizeof(struct hidpp20_sensor));
for (i = 0; i < num_sensors; i++) {
sensor = &s_list[i];
sensor->index = i;
rc = hidpp20_adjustable_dpi_get_dpi_list(device,
feature_index,
sensor);
if (rc)
goto err;
rc = hidpp20_adjustable_dpi_get_dpi(device, feature_index, sensor);
if (rc)
goto err;
hidpp_log_raw(&device->base,
"sensor %d: current dpi: %d (default: %d) min: %d max: %d steps: %d\n",
sensor->index,
sensor->dpi,
sensor->default_dpi,
sensor->dpi_min,
sensor->dpi_max,
sensor->dpi_steps);
}
*sensors_list = s_list;
return num_sensors;
err:
num_sensors = 0;
free(s_list);
return rc > 0 ? -EPROTO : rc;
}
int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device,
struct hidpp20_sensor *sensor, uint16_t dpi)
{
uint8_t feature_index;
uint16_t returned_parameters;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_ADJUSTABLE_DPI_SET_SENSOR_DPI,
.msg.parameters[0] = sensor->index,
};
hidpp_set_unaligned_be_u16(&msg.msg.parameters[1], dpi);
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ADJUSTABLE_DPI);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
returned_parameters = hidpp_get_unaligned_be_u16(&msg.msg.parameters[1]);
/* version 0 of the protocol does not echo the parameters */
if (returned_parameters != dpi && returned_parameters)
return -EIO;
return 0;
}
/* -------------------------------------------------------------------------- */
/* 0x8100 - Onboard Profiles */
/* -------------------------------------------------------------------------- */
#define CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR 0x00
#define CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE 0x10
#define CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE 0x20
#define CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE 0x30
#define CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE 0x40
#define CMD_ONBOARD_PROFILES_MEMORY_READ 0x50
#define CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE 0x60
#define CMD_ONBOARD_PROFILES_MEMORY_WRITE 0x70
#define CMD_ONBOARD_PROFILES_MEMORY_WRITE_END 0x80
#define CMD_ONBOARD_PROFILES_GET_CURRENT_DPI_INDEX 0xb0
#define CMD_ONBOARD_PROFILES_SET_CURRENT_DPI_INDEX 0xc0
#define HIDPP20_PAGE_SIZE (16*16)
#define HIDPP20_PROFILE_SIZE HIDPP20_PAGE_SIZE
#define HIDPP20_BUTTON_HID 0x80
#define HIDPP20_MODE_NO_CHANGE 0x00
#define HIDPP20_ONBOARD_MODE 0x01
#define HIDPP20_HOST_MODE 0x02
#define HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402 0x01
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402 0x01
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303 0x02
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900 0x03
#define HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402 0x01
struct hidpp20_onboard_profiles_info {
uint8_t memory_model_id;
uint8_t profile_format_id;
uint8_t macro_format_id;
uint8_t profile_count;
uint8_t profile_count_oob;
uint8_t button_count;
uint8_t sector_count;
uint16_t sector_size;
uint8_t mechanical_layout;
uint8_t various_info;
uint8_t reserved[5];
} __attribute__((packed));
_Static_assert(sizeof(struct hidpp20_onboard_profiles_info) == 16, "Invalid size");
int
hidpp20_onboard_profiles_read_memory(struct hidpp20_device *device,
uint8_t read_rom,
uint8_t page,
uint8_t section,
uint8_t result[16])
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_MEMORY_READ,
.msg.parameters[0] = read_rom,
.msg.parameters[1] = page,
.msg.parameters[2] = 0,
.msg.parameters[3] = section,
};
if (read_rom > 1)
return -EINVAL;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
/* msg.msg.parameters is guaranteed to have a size >= 16 */
memcpy(result, msg.msg.parameters, 16);
return 0;
}
static int
hidpp20_onboard_profiles_write_start(struct hidpp20_device *device,
uint8_t page,
uint8_t section)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_MEMORY_ADDR_WRITE,
.msg.parameters[0] = 0x00,
.msg.parameters[1] = page,
.msg.parameters[2] = 0,
.msg.parameters[3] = section,
.msg.parameters[4] = 0x01,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return 0;
}
static int
hidpp20_onboard_profiles_write_end(struct hidpp20_device *device)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE_END,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return 0;
}
static int
hidpp20_onboard_profiles_write_data(struct hidpp20_device *device,
uint8_t page,
uint8_t section,
uint8_t *data,
size_t len)
{
uint8_t feature_index;
int transfered = 0;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_LONG,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_onboard_profiles_write_start(device, page, section);
if (rc)
return rc;
while (len >= 16) {
msg.msg.address = CMD_ONBOARD_PROFILES_MEMORY_WRITE;
memcpy(msg.msg.parameters, data, 16);
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
len -= 16;
data += 16;
transfered += 16;
}
rc = hidpp20_onboard_profiles_write_end(device);
if (rc)
return rc;
return transfered;
}
static int
hidpp20_onboard_profiles_write_page(struct hidpp20_device *device,
uint8_t page,
uint8_t data[HIDPP20_PAGE_SIZE])
{
uint16_t crc;
crc = hidpp_crc_ccitt(data, HIDPP20_PAGE_SIZE - 2);
hidpp_set_unaligned_be_u16(&data[HIDPP20_PROFILE_SIZE - 2], crc);
return hidpp20_onboard_profiles_write_data(device, page, 0x00, data, HIDPP20_PAGE_SIZE);
}
static int
hidpp20_onboard_profiles_get_onboard_mode(struct hidpp20_device *device)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_GET_ONBOARD_MODE,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[0];
}
static int
hidpp20_onboard_profiles_set_onboard_mode(struct hidpp20_device *device,
uint8_t onboard_mode)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_SET_ONBOARD_MODE,
.msg.parameters[1] = onboard_mode,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return 0;
}
int
hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_GET_CURRENT_PROFILE,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return msg.msg.parameters[1];
}
int
hidpp20_onboard_profiles_set_current_profile(struct hidpp20_device *device,
uint8_t index)
{
uint8_t feature_index;
int rc;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_SET_CURRENT_PROFILE,
.msg.parameters[1] = index + 1,
};
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
return 0;
}
static int
hidpp20_onboard_profiles_initialize(struct hidpp20_device *device,
struct hidpp20_profiles **profiles_list)
{
uint8_t feature_index;
int rc;
struct hidpp20_profiles *profiles;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
.msg.address = CMD_ONBOARD_PROFILES_GET_PROFILES_DESCR,
};
struct hidpp20_onboard_profiles_info *info;
int onboard_mode;
feature_index = hidpp_root_get_feature_idx(device,
HIDPP_PAGE_ONBOARD_PROFILES);
if (feature_index == 0)
return -ENOTSUP;
msg.msg.sub_id = feature_index;
rc = hidpp20_request_command(device, &msg);
if (rc)
return rc;
info = (struct hidpp20_onboard_profiles_info *)msg.msg.parameters;
if (info->memory_model_id != HIDPP20_ONBOARD_PROFILES_MEMORY_TYPE_G402) {
hidpp_log_error(&device->base,
"Memory layout not supported: 0x%02x.\n",
info->memory_model_id);
return -ENOTSUP;
}
if ((info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G402) &&
(info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303) &&
(info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900)) {
hidpp_log_error(&device->base,
"Profile layout not supported: 0x%02x.\n",
info->profile_format_id);
return -ENOTSUP;
}
if (info->macro_format_id != HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402) {
hidpp_log_error(&device->base,
"Macro format not supported: 0x%02x.\n",
info->macro_format_id);
return -ENOTSUP;
}
info->sector_size = hidpp_be_u16_to_cpu(info->sector_size);
if (info->sector_size != HIDPP20_PAGE_SIZE) {
hidpp_log_error(&device->base,
"Unsupported sector size: %d.\n",
info->sector_size);
return -ENOTSUP;
}
rc = hidpp20_onboard_profiles_get_onboard_mode(device);
if (rc < 0)
return rc;
onboard_mode = rc;
if (onboard_mode != HIDPP20_ONBOARD_MODE) {
hidpp_log_raw(&device->base,
"not on the correct mode: %d.\n",
onboard_mode);
rc = hidpp20_onboard_profiles_set_onboard_mode(device,
HIDPP20_ONBOARD_MODE);
if (rc < 0)
return rc;
}
profiles = zalloc(sizeof(struct hidpp20_profiles) +
info->profile_count * sizeof(struct hidpp20_profile));
profiles->num_profiles = info->profile_count;
profiles->num_rom_profiles = info->profile_count_oob;
profiles->num_buttons = msg.msg.parameters[5] <= 16 ? msg.msg.parameters[5] : 16;
profiles->num_modes = HIDPP20_DPI_COUNT;
profiles->num_leds = HIDPP20_LED_COUNT;
profiles->has_g_shift = (info->mechanical_layout & 0x03) == 2;
profiles->has_dpi_shift = ((info->mechanical_layout & 0x0c) >> 2) == 2;
switch(info->various_info & 0x07) {
case 1:
profiles->corded = 1;
break;
case 2:
profiles->wireless = 1;
break;
case 4:
profiles->corded = 1;
profiles->wireless = 1;
break;
}
*profiles_list = profiles;
return 0;
}
static int
hidpp20_onboard_profiles_macro_next(struct hidpp20_device *device,
uint8_t memory[32],
uint16_t *index,
union hidpp20_macro_data *macro)
{
int rc = 0;
unsigned int step = 1;
if (*index >= 32 - sizeof(union hidpp20_macro_data)) {
hidpp_log_error(&device->base, "error while parsing macro.\n");
return -EFAULT;
}
memcpy(macro, &memory[*index], sizeof(union hidpp20_macro_data));
switch (macro->any.type) {
case HIDPP20_MACRO_DELAY:
/* fallthrough */
case HIDPP20_MACRO_KEY_PRESS:
/* fallthrough */
case HIDPP20_MACRO_KEY_RELEASE:
/* fallthrough */
case HIDPP20_MACRO_JUMP:
step = 3;
rc = -EAGAIN;
break;
case HIDPP20_MACRO_NOOP:
step = 1;
rc = -EAGAIN;
break;
case HIDPP20_MACRO_END:
step = 1;
return 0;
default:
hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", macro->any.type);
rc = -EFAULT;
}
if ((*index + step) & 0xF0)
/* the next item will be on the following chunk */
return -ENOMEM;
*index += step;
return rc;
}
static int
hidpp20_onboard_profiles_read_macro(struct hidpp20_device *device,
uint8_t page, uint8_t offset,
union hidpp20_macro_data **return_macro)
{
uint8_t memory[HIDPP20_PAGE_SIZE];
union hidpp20_macro_data *macro = NULL;
unsigned count = 0;
unsigned index = 0;
uint16_t mem_index = 0;
int rc = -ENOMEM;
do {
if (count == index) {
union hidpp20_macro_data *tmp;
count += 32;
/* manual realloc to have the memory zero-initialized */
tmp = zalloc(count * sizeof(union hidpp20_macro_data));
if (macro) {
memcpy(tmp, macro, (count - 32) * sizeof(union hidpp20_macro_data));
free(macro);
}
macro = tmp;
}
if (rc == -ENOMEM) {
offset += mem_index;
rc = hidpp20_onboard_profiles_read_memory(device,
0,
page,
offset,
memory);
if (rc)
goto out_err;
mem_index = 0;
}
rc = hidpp20_onboard_profiles_macro_next(device,
memory,
&mem_index,
&macro[index]);
if (rc == -EFAULT)
goto out_err;
if (rc != -ENOMEM) {
if (macro[index].any.type == HIDPP20_MACRO_JUMP) {
page = macro[index].jump.page;
offset = macro[index].jump.offset;
mem_index = 0;
/* no need to store the jump in memory */
index--;
/* force memory fetching */
rc = -ENOMEM;
}
index++;
}
} while (rc);
*return_macro = macro;
return index;
out_err:
if (macro)
free(macro);
return rc;
}
static int
hidpp20_onboard_profiles_parse_macro(struct hidpp20_device *device,
uint8_t page, uint8_t offset,
union hidpp20_macro_data **return_macro)
{
union hidpp20_macro_data *m, *macro = NULL;
unsigned i, count = 0;
int rc;
rc = hidpp20_onboard_profiles_read_macro(device, page, offset, &macro);
if (rc < 0)
return rc;
count = rc;
for (i = 0; i < count; i++) {
m = &macro[i];
switch (m->any.type) {
case HIDPP20_MACRO_DELAY:
m->delay.time = hidpp_be_u16_to_cpu(m->delay.time);
break;
case HIDPP20_MACRO_KEY_PRESS:
break;
case HIDPP20_MACRO_KEY_RELEASE:
break;
case HIDPP20_MACRO_JUMP:
break;
case HIDPP20_MACRO_END:
break;
case HIDPP20_MACRO_NOOP:
break;
default:
hidpp_log_error(&device->base, "unknown tag: 0x%02x\n", m->any.type);
}
}
*return_macro = macro;
return 0;
}
static unsigned int
hidpp20_onboard_profiles_compute_dict_size(const struct hidpp20_device *device,
const struct hidpp20_profiles *profiles)
{
unsigned p, num_offset;
num_offset = 0;
p = profiles->num_profiles;
while (p) {
p >>= 2;
num_offset += 16;
}
return num_offset;
}
int
hidpp20_onboard_profiles_allocate(struct hidpp20_device *device,
struct hidpp20_profiles **profiles_list)
{
unsigned i, offset, num_offset;
int rc;
uint8_t data[HIDPP20_PAGE_SIZE] = {0};
struct hidpp20_profiles *profiles = NULL;
rc = hidpp20_onboard_profiles_initialize(device,
&profiles);
if (rc < 0)
return rc;
assert(profiles);
num_offset = hidpp20_onboard_profiles_compute_dict_size(device,
profiles);
for (offset = 0; offset < num_offset; offset += 16) {
rc = hidpp20_onboard_profiles_read_memory(device,
0x00,
0x00,
offset,
data + offset);
if (rc < 0)
return rc;
}
for (i = 0; i < profiles->num_profiles; i++) {
uint8_t *d = data + offset + 4 * i;
if (d[0] == 0xFF && d[1] == 0xFF)
break;
profiles->profiles[i].index = d[1];
profiles->profiles[i].enabled = d[2];
}
*profiles_list = profiles;
return profiles->num_profiles;
}
void
hidpp20_onboard_profiles_destroy(struct hidpp20_device *device,
struct hidpp20_profiles *profiles_list)
{
struct hidpp20_profile *profile;
union hidpp20_macro_data **macro;
unsigned i;
if (!profiles_list)
return;
for (i = 0; i < profiles_list->num_profiles; i++) {
profile = &profiles_list->profiles[i];
ARRAY_FOR_EACH(profile->macros, macro) {
if (*macro)
free(*macro);
}
}
free(profiles_list);
}
static int
hidpp20_onboard_profiles_find_and_read_profile(struct hidpp20_device *device,
unsigned int index,
uint8_t *data,
unsigned int num_rom_profiles)
{
int rc;
unsigned i;
uint16_t crc, read_crc;
rc = hidpp20_onboard_profiles_read_memory(device,
0,
index + 1,
0x00,
data);
if (rc < 0)
return rc;
if (data[0] > 0) {
for (i = 1; i < HIDPP20_PROFILE_SIZE / 0x10; i++) {
rc = hidpp20_onboard_profiles_read_memory(device,
0,
index + 1,
i * 0x10,
data + i * 0x10);
if (rc < 0)
return rc;
}
crc = hidpp_crc_ccitt(data, HIDPP20_PROFILE_SIZE - 2);
read_crc = hidpp_get_unaligned_be_u16(&data[HIDPP20_PROFILE_SIZE - 2]);
if (crc == read_crc)
return 0;
}
/* something went wrong, the mouse is using the factory profile in ROM */
if (num_rom_profiles == 0)
return -EINVAL;
if (index >= num_rom_profiles)
index = num_rom_profiles - 1;
for (i = 0; i < HIDPP20_PROFILE_SIZE / 0x10; i++) {
rc = hidpp20_onboard_profiles_read_memory(device,
1,
index + 1,
i * 0x10,
data + i * 0x10);
if (rc != 0)
return rc;
}
return 0;
}
static int
hidpp20_onboard_profiles_enable_profile(struct hidpp20_device *device,
unsigned int index,
struct hidpp20_profiles *profiles_list)
{
unsigned int i, buffer_index = 0;
uint8_t data[HIDPP20_PROFILE_SIZE] = {0};
if (profiles_list->profiles[index].enabled)
return 0;
profiles_list->profiles[index].index = index + 1;
profiles_list->profiles[index].enabled = 0x01;
for (i = 0; i < profiles_list->num_profiles; i++) {
data[buffer_index++] = 0x00;
data[buffer_index++] = i + 1;
data[buffer_index++] = profiles_list->profiles[i].enabled;
data[buffer_index++] = 0x00;
}
data[buffer_index++] = 0xFF;
data[buffer_index++] = 0xFF;
data[buffer_index++] = 0x00;
data[buffer_index++] = 0x00;
memset(data + buffer_index, 0xff, sizeof(data) - buffer_index);
hidpp_log_buf_debug(&device->base,
"dictionary: ",
data,
hidpp20_onboard_profiles_compute_dict_size(device,
profiles_list));
return hidpp20_onboard_profiles_write_page(device, 0x00, data);
}
union hidpp20_internal_profile {
uint8_t data[HIDPP20_PROFILE_SIZE];
struct {
uint8_t report_rate;
uint8_t default_dpi;
uint8_t switched_dpi;
uint16_t dpi[5];
struct hidpp20_color profile_color;
uint8_t power_mode;
uint8_t angle_snapping;
uint8_t reserved[14];
union hidpp20_button_binding buttons[16];
union hidpp20_button_binding alternate_buttons[16];
union {
char txt[16 * 3];
uint8_t raw[16 * 3];
} name;
struct hidpp20_internal_led leds[2]; /* G303, g502, g900 only */
uint8_t free[24];
uint16_t crc;
} __attribute__((packed)) profile;
};
_Static_assert(sizeof(union hidpp20_internal_profile) == HIDPP20_PROFILE_SIZE, "Invalid size");
static void
hidpp20_buttons_to_cpu(struct hidpp20_device *device,
struct hidpp20_profile *profile,
union hidpp20_button_binding *buttons,
unsigned int count)
{
unsigned int i;
for (i = 0; i < count; i++) {
union hidpp20_button_binding *b = &buttons[i];
union hidpp20_button_binding *button = &profile->buttons[i];
button->any.type = b->any.type;
switch (b->any.type) {
case HIDPP20_BUTTON_HID_TYPE:
button->subany.subtype = b->subany.subtype;
switch (b->subany.subtype) {
case HIDPP20_BUTTON_HID_TYPE_MOUSE:
button->button.buttons = ffs(hidpp_be_u16_to_cpu(b->button.buttons));
break;
case HIDPP20_BUTTON_HID_TYPE_KEYBOARD:
button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags;
button->keyboard_keys.key = b->keyboard_keys.key;
break;
case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL:
button->consumer_control.consumer_control =
hidpp_be_u16_to_cpu(b->consumer_control.consumer_control);
break;
}
break;
case HIDPP20_BUTTON_SPECIAL:
button->special.special = b->special.special;
break;
case HIDPP20_BUTTON_MACRO:
if (profile->macros[i]) {
free(profile->macros[i]);
profile->macros[i] = NULL;
}
hidpp20_onboard_profiles_parse_macro(device,
b->macro.page,
b->macro.offset,
&profile->macros[i]);
/* the actual page is stored in the 'zero' field */
button->macro.page = i;
button->macro.offset = b->macro.offset;
button->macro.zero = b->macro.page;
break;
case HIDPP20_BUTTON_DISABLED:
break;
default:
break;
}
}
}
static void
hidpp20_buttons_from_cpu(struct hidpp20_profile *profile,
union hidpp20_button_binding *buttons,
unsigned int count)
{
unsigned int i;
for (i = 0; i < count; i++) {
union hidpp20_button_binding *button = &buttons[i];
union hidpp20_button_binding *b = &profile->buttons[i];
button->any.type = b->any.type;
switch (b->any.type) {
case HIDPP20_BUTTON_HID_TYPE:
button->subany.subtype = b->subany.subtype;
switch (b->subany.subtype) {
case HIDPP20_BUTTON_HID_TYPE_MOUSE:
button->button.buttons = hidpp_cpu_to_be_u16(1U << (b->button.buttons - 1));
break;
case HIDPP20_BUTTON_HID_TYPE_KEYBOARD:
button->keyboard_keys.modifier_flags = b->keyboard_keys.modifier_flags;
button->keyboard_keys.key = b->keyboard_keys.key;
break;
case HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL:
button->consumer_control.type = HIDPP20_BUTTON_HID_TYPE;
button->consumer_control.subtype = HIDPP20_BUTTON_HID_TYPE_CONSUMER_CONTROL;
button->consumer_control.consumer_control =
hidpp_cpu_to_be_u16(b->consumer_control.consumer_control);
break;
}
break;
case HIDPP20_BUTTON_SPECIAL:
button->special.special = b->special.special;
break;
case HIDPP20_BUTTON_DISABLED:
break;
case HIDPP20_BUTTON_MACRO:
/* the actual page is stored in the 'zero' field */
button->macro.page = b->macro.zero;
button->macro.offset = b->macro.offset;
button->macro.zero = 0;
break;
default:
break;
}
}
}
static void
hidpp20_onboard_profile_read_led(struct hidpp20_led *led,
struct hidpp20_internal_led internal_led)
{
uint16_t rate = 0;
uint8_t brightness = 0;
switch (internal_led.mode) {
case HIDPP20_LED_CYCLE:
rate = internal_led.effect.cycle.rate;
brightness = internal_led.effect.cycle.brightness;
break;
case HIDPP20_LED_BREATHING:
rate = internal_led.effect.breath.rate;
brightness = internal_led.effect.breath.brightness;
break;
}
led->mode = (enum hidpp20_led_mode)internal_led.mode;
led->color = internal_led.color;
led->rate = rate;
led->brightness = brightness;
}
int hidpp20_onboard_profiles_read(struct hidpp20_device *device,
unsigned int index,
struct hidpp20_profiles *profiles_list)
{
union hidpp20_internal_profile pdata = {0};
uint8_t *data = pdata.data;
struct hidpp20_profile *profile = &profiles_list->profiles[index];
unsigned i;
int rc;
if (index >= profiles_list->num_profiles)
return -EINVAL;
rc = hidpp20_onboard_profiles_find_and_read_profile(device,
index,
data,
profiles_list->num_rom_profiles);
if (rc != 0)
return rc;
profile->report_rate = 1000 / max(1, pdata.profile.report_rate);
profile->default_dpi = pdata.profile.default_dpi;
profile->switched_dpi = pdata.profile.switched_dpi;
for (i = 0; i < 5; i++) {
profile->dpi[i] = hidpp_get_unaligned_le_u16(&data[2 * i + 3]);
}
for (i = 0; i < profiles_list->num_leds; i++)
hidpp20_onboard_profile_read_led(&profile->leds[i], pdata.profile.leds[i]);
hidpp20_buttons_to_cpu(device, profile, pdata.profile.buttons, profiles_list->num_buttons);
memcpy(profile->name, pdata.profile.name.txt, sizeof(profile->name));
/* force terminating '\0' */
profile->name[sizeof(profile->name) - 1] = '\0';
/* check if we are using the default name or not */
for (i = 0; i < sizeof(profile->name); i++) {
if (pdata.profile.name.raw[i] != 0xff)
break;
}
if (i == sizeof(profile->name))
memset(profile->name, 0, sizeof(profile->name));
return 0;
}
static void
hidpp20_onboard_write_profile_led(struct hidpp20_internal_led *internal_led,
struct hidpp20_led *led)
{
uint16_t rate = led->rate;
uint8_t brightness = led->brightness;
internal_led->mode = (uint8_t)led->mode;
internal_led->color.red = led->color.red;
internal_led->color.blue = led->color.blue;
internal_led->color.green = led->color.green;
switch (led->mode) {
case HIDPP20_LED_CYCLE:
internal_led->effect.cycle.rate = rate;
internal_led->effect.cycle.brightness = brightness;
break;
case HIDPP20_LED_BREATHING:
internal_led->effect.breath.rate = rate;
internal_led->effect.breath.brightness = brightness;
break;
case HIDPP20_LED_ON:
case HIDPP20_LED_OFF:
break;
}
}
int hidpp20_onboard_profiles_write(struct hidpp20_device *device,
unsigned int index,
struct hidpp20_profiles *profiles_list)
{
union hidpp20_internal_profile pdata = {0};
uint8_t *data = pdata.data;
struct hidpp20_profile *profile = &profiles_list->profiles[index];
unsigned i;
int rc;
if (index >= profiles_list->num_profiles)
return -EINVAL;
rc = hidpp20_onboard_profiles_find_and_read_profile(device,
index,
data,
profiles_list->num_rom_profiles);
if (rc < 0)
return rc;
rc = hidpp20_onboard_profiles_enable_profile(device, index, profiles_list);
if (rc < 0)
return rc;
pdata.profile.report_rate = 1000 / profile->report_rate;
pdata.profile.default_dpi = profile->default_dpi;
pdata.profile.switched_dpi = profile->switched_dpi;
for (i = 0; i < 5; i++) {
pdata.profile.dpi[i] = hidpp_cpu_to_le_u16(profile->dpi[i]);
}
for (i = 0; i < profiles_list->num_leds; i++)
hidpp20_onboard_write_profile_led(&pdata.profile.leds[i], &profile->leds[i]);
hidpp20_buttons_from_cpu(profile, pdata.profile.buttons, profiles_list->num_buttons);
memcpy(pdata.profile.name.txt, profile->name, sizeof(profile->name));
rc = hidpp20_onboard_profiles_write_page(device, index + 1, data);
if (rc < 0)
return rc;
if (rc != sizeof(pdata.data))
return -EIO;
return 0;
}
static const enum ratbag_button_action_special hidpp20_profiles_specials[] = {
[0x00] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID,
[0x01] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_LEFT,
[0x02] = RATBAG_BUTTON_ACTION_SPECIAL_WHEEL_RIGHT,
[0x03] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_UP,
[0x04] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DOWN,
[0x05] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_DEFAULT,
[0x06] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_CYCLE_UP,
[0x07] = RATBAG_BUTTON_ACTION_SPECIAL_RESOLUTION_ALTERNATE,
[0x08] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID,
[0x09] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID,
[0x0a] = RATBAG_BUTTON_ACTION_SPECIAL_PROFILE_CYCLE_UP,
[0x0b] = RATBAG_BUTTON_ACTION_SPECIAL_SECOND_MODE,
[0x0c] = RATBAG_BUTTON_ACTION_SPECIAL_BATTERY_LEVEL,
[0x0d ... 0xff] = RATBAG_BUTTON_ACTION_SPECIAL_INVALID,
};
enum ratbag_button_action_special
hidpp20_onboard_profiles_get_special(uint8_t code)
{
return hidpp20_profiles_specials[code];
}
uint8_t
hidpp20_onboard_profiles_get_code_from_special(enum ratbag_button_action_special special)
{
uint8_t i = 0;
while (++i) {
if (hidpp20_profiles_specials[i] == special)
return i;
}
return RATBAG_BUTTON_ACTION_SPECIAL_INVALID;
}
/* -------------------------------------------------------------------------- */
/* generic hidpp20 device operations */
/* -------------------------------------------------------------------------- */
struct hidpp20_device *
hidpp20_device_new(const struct hidpp_device *base, unsigned int idx)
{
struct hidpp20_device *dev;
int rc;
dev = zalloc(sizeof(*dev));
dev->index = idx;
dev->base = *base;
dev->proto_major = 1;
dev->proto_minor = 0;
rc = hidpp20_root_get_protocol_version(dev, &dev->proto_major, &dev->proto_minor);
if (rc) {
/* communication error, best to ignore the device */
goto err;
}
if (dev->proto_major < 2)
goto err;
rc = hidpp20_feature_set_get(dev);
if (rc < 0)
goto err;
return dev;
err:
free(dev);
return NULL;
}
void
hidpp20_device_destroy(struct hidpp20_device *device)
{
free(device->feature_list);
free(device);
}