File 1676.patch of Package libratbag
From 17a96bcb34cdb6d63733649b8b6770598036d861 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Sat, 11 Jan 2025 22:49:33 +1000
Subject: [PATCH 1/8] hidpp20: Correct start index of onboard active profile
getter
When loading the current active profile from the device, it would
incorrectly be read one profile ahead of the actual selected active
profile. This issue only presented as a inconsistency within ratbag, as
setting the active profile would still account for the correct start
index being 1 within hidpp20_onboard_profiles_set_current_profile.
This change reduces the value returned from the command by one to align
with the "Profile 0" = 0 indexing, however limiting the offset such that
it does not integer underflow.
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/hidpp20.c | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/src/hidpp20.c b/src/hidpp20.c
index 83531c0f8..c7ee6b849 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -2115,6 +2115,10 @@ hidpp20_onboard_profiles_get_current_profile(struct hidpp20_device *device)
unknown_0 = msg.msg.parameters[0];
active_profile_index = msg.msg.parameters[1];
+ /* Profile index offset stored in the paramter starts at 1, refer to
+ * set_current_profile. Prevent integer underflow. */
+ if (active_profile_index > 0)
+ active_profile_index = active_profile_index - 1;
hidpp_log_raw(
&device->base,
From 1055dca137e816505d1a5dc63aac868421838ab3 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Sun, 12 Jan 2025 00:47:44 +1000
Subject: [PATCH 2/8] driver-hidpp20: handle decoding/encoding onboard profile
name
Devices that support onboard profiles also allow for the name of the
profile to be configured. With recent devices and with the G-Hub
software these devices have the profile name encoded in UTF16-LE. This
change parses the raw data contained in the internal profile structure
from UTF16-LE in order to provide the profile name in UTF8. The reverse
is also handled to encode the UTF8 from the profile name into the
internal profile. This allows for displaying and updating the profile
name for onboard profiles.
Depending on the device, default (ROM) profiles may have no name stored
in the profile field. The G-Hub software will treat this as a default
name (e.g. "Profile 1"). However it is not clear if all profile formats
for all devices support the name field and if under all cases this is
encoded in UTF16-LE. As such if no name is encoded in the profile it is
treated as not having profile name support and will not allow it to be
updated when committing the onboard profile. The profile name can
initially be set in G-Hub, after which they can be displayed and or
modified within ratbag.
This has been tested with the following devices:
- Logitech G203 - profile format id = 0x4
- Logitech G900 - profile format id = 0x3
- Logitech G-Pro X SL2 - profile format id = 0x6
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 75 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 75 insertions(+)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index 7c4cfb003..fd4b21b70 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -1058,6 +1058,71 @@ hidpp20drv_update_report_rate(struct ratbag_profile *profile, int hz)
return -ENOTSUP;
}
+static int
+hidpp20drv_read_profile_name_8100(struct ratbag_profile *profile)
+{
+ struct ratbag_device *device = profile->device;
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+ struct hidpp20_profile *h_profile;
+ int rc;
+
+ h_profile = &drv_data->profiles->profiles[profile->index];
+
+ if (profile->name) {
+ free(profile->name);
+ profile->name = NULL;
+ }
+
+ /* Convert UTF-16LE onboard profile name to UTF8 */
+ rc = ratbag_utf8_from_enc(h_profile->name,
+ sizeof(h_profile->name), "UTF-16LE",
+ &profile->name);
+ if (rc < 0)
+ return -EINVAL;
+
+ return RATBAG_SUCCESS;
+}
+
+static int
+hidpp20drv_update_profile_name_8100(struct ratbag_profile *profile)
+{
+ struct ratbag_device *device = profile->device;
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+ struct hidpp20_profile *h_profile;
+ int rc;
+
+ if (!profile->name)
+ return -ENOTSUP;
+
+ h_profile = &drv_data->profiles->profiles[profile->index];
+
+ if (strlen(profile->name) == 0) {
+ /* Empty string */
+ memset(h_profile->name, 0, sizeof(h_profile->name));
+ } else {
+ /* Encode string as UTF-16 */
+ rc = ratbag_utf8_to_enc(h_profile->name,
+ sizeof(h_profile->name), "UTF-16LE",
+ "%s", profile->name);
+ if (rc < 0)
+ return -EINVAL;
+ }
+
+ return RATBAG_SUCCESS;
+}
+
+static int
+hidpp20drv_update_profile_name(struct ratbag_profile *profile)
+{
+ struct ratbag_device *device = profile->device;
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+
+ if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)
+ return hidpp20drv_update_profile_name_8100(profile);
+
+ return -ENOTSUP;
+}
+
static int
hidpp20drv_read_special_key_mouse(struct ratbag_device *device)
{
@@ -1195,6 +1260,8 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
p = &drv_data->profiles->profiles[profile->index];
+ hidpp20drv_read_profile_name_8100(profile);
+
ratbag_profile_for_each_resolution(profile, res) {
struct hidpp20_sensor *sensor;
@@ -1473,6 +1540,14 @@ hidpp20drv_commit(struct ratbag_device *device)
if (!profile->dirty)
continue;
+ if (profile->name != NULL) {
+ rc = hidpp20drv_update_profile_name(profile);
+ if (rc) {
+ log_error(device->ratbag, "hidpp20: failed to update profile name (%d)\n", rc);
+ return RATBAG_ERROR_DEVICE;
+ }
+ }
+
if (profile->rate_dirty) {
rc = hidpp20drv_update_report_rate(profile, profile->hz);
if (rc) {
From 38755b58553a3aa79a2fa4d3e9d361292532e55a Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Mon, 13 Jan 2025 16:12:31 +1000
Subject: [PATCH 3/8] data: Add device data for Logitech G Pro X Wireless
Superlight 2
The Logitech G Pro X Wireless Superlight 2 has a similar device data
configuration to the previous generation device (Logitech G Pro X
Wireless Superlight), using the hidpp20 driver.
Support for the wireless receiver relies on the kernel supporting the
0xc54d USB device within the hid-logitech-dj driver to expose the hidraw
device specific to the mouse (HID device identifies as 0x40a9). This
kernel support has not yet been merged as of kernel 6.13.
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
data/devices/logitech-g-pro-x-wireless-superlight-2.device | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 data/devices/logitech-g-pro-x-wireless-superlight-2.device
diff --git a/data/devices/logitech-g-pro-x-wireless-superlight-2.device b/data/devices/logitech-g-pro-x-wireless-superlight-2.device
new file mode 100644
index 000000000..eeca4bb76
--- /dev/null
+++ b/data/devices/logitech-g-pro-x-wireless-superlight-2.device
@@ -0,0 +1,5 @@
+[Device]
+Name=Logitech G Pro X Wireless Superlight 2
+DeviceMatch=usb:046d:40a9;usb:046d:c09b
+DeviceType=mouse
+Driver=hidpp20
From f209e6b585b230e3539250e84b0bceda0f80769b Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Sun, 12 Jan 2025 00:08:34 +1000
Subject: [PATCH 4/8] hidpp20: Extend sensor information to handle X/Y/LOD
The existing hidpp20 code base handles only a singular DPI value to
support the 2201 Adjustable DPI feature alongside onboard profiles which
match. Newer devices (e.g. Logitech G Pro X Superlight 2) add support
for per axis dpi configuration as well as Lift off distance.
This change reworks the existing hidpp20/driver-hidpp20 to describe
sensors that are capable of dual axis DPI and lift off distance. Changes
to the hidpp20_sensor structure provide per axis information in new
hidpp20_sensor_axis structures along with fixed details for the lift off
distance. For devices that only have one axis of configuration, the X
axis is used to represent the singular value/capability (which matches
other drivers in ratbag).
Additionally the newer devices are able to provide multiple DPI ranges
with different step increments per range e.g. 100-200 in steps 1
followed by 200-400 in steps of 2. The hidpp20_sensor_axis structure
includes the DPI ranges with an array of a hidpp20_sensor_dpi_range
structures. This allows for capturing the >1000 distinct dpi values
without storing >1000 values. The hidpp20drv_validate_dpi_ranges
function is added in order to handle the validation of DPI values used
in various setters. The DPI list values provided to the upper ratbag API
is still generated by standard values in the dpi_min/dpi_max range, this
is because the ratbag API does not allow for the >1000 values to be
stored (and the resolution of those rates are not particularly useful).
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 127 +++++++++++++++++++++++++++++++------------
src/hidpp20.c | 74 +++++++++++++++++++------
src/hidpp20.h | 36 +++++++++---
3 files changed, 178 insertions(+), 59 deletions(-)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index fd4b21b70..9ef8f2fb7 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -788,9 +788,9 @@ hidpp20drv_read_resolution_dpi_2201(struct ratbag_device *device)
}
log_debug(ratbag,
"device is at %d dpi (variable between %d and %d).\n",
- drv_data->sensors[0].dpi,
- drv_data->sensors[0].dpi_min,
- drv_data->sensors[0].dpi_max);
+ drv_data->sensors[0].x.dpi,
+ drv_data->sensors[0].x.dpi_min,
+ drv_data->sensors[0].x.dpi_max);
drv_data->num_sensors = rc;
@@ -901,17 +901,27 @@ hidpp20drv_read_resolution_dpi(struct ratbag_profile *profile)
return rc;
ratbag_profile_for_each_resolution(profile, res) {
- struct hidpp20_sensor *sensor;
-
/* We only look at the first sensor. Multiple
* sensors is too niche to care about right now */
- sensor = &drv_data->sensors[0];
+ struct hidpp20_sensor *sensor = &drv_data->sensors[0];
+
+ uint16_t dpi_min = sensor->x.dpi_min;
+ uint16_t dpi_max = sensor->x.dpi_max;
+ if (sensor->has_y) {
+ /* Limit min/max to the worse case values, as
+ * the ratbag resolution API only allows a
+ * common range for each axis. */
+ dpi_min = max(dpi_min, sensor->y.dpi_min);
+ dpi_max = max(dpi_max, sensor->y.dpi_max);
+
+ ratbag_resolution_set_resolution(res, sensor->x.dpi, sensor->y.dpi);
+ ratbag_resolution_set_cap(res,
+ RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
+ } else {
+ ratbag_resolution_set_resolution(res, sensor->x.dpi, sensor->x.dpi);
+ }
+ ratbag_resolution_set_dpi_list_from_range(res, dpi_min, dpi_max);
- /* FIXME: retrieve the refresh rate */
- ratbag_resolution_set_resolution(res, sensor->dpi, sensor->dpi);
- ratbag_resolution_set_dpi_list_from_range(res,
- sensor->dpi_min,
- sensor->dpi_max);
/* FIXME: we mark all resolutions as active because
* they are from different sensors */
res->is_active = true;
@@ -955,6 +965,56 @@ hidpp20drv_update_resolution_dpi_8100(struct ratbag_resolution *resolution,
return RATBAG_SUCCESS;
}
+static bool
+hidpp20drv_validate_axis_dpi_ranges(struct hidpp20_sensor *sensor, struct hidpp20_sensor_axis *axis, uint16_t dpi)
+{
+ unsigned int i;
+ struct hidpp20_sensor_dpi_range *range;
+
+ if (dpi < axis->dpi_min)
+ return false;
+ if (dpi > axis->dpi_max)
+ return false;
+
+ for (i = 0; i < axis->num_dpi_ranges; i++) {
+ range = &axis->dpi_ranges[i];
+
+ if (range->step != 0) {
+ /* Expand the range */
+ unsigned int count = (range->end - range->start) / range->step;
+
+ uint16_t value = range->start;
+ while (count) {
+ if (dpi == value)
+ return true;
+
+ value += range->step;
+ count--;
+ }
+
+ /* Include the end of the range */
+ if (dpi == value)
+ return true;
+ } else {
+ if (dpi == range->start)
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool
+hidpp20drv_validate_dpi_ranges(struct hidpp20_sensor *sensor, uint16_t dpi_x, uint16_t dpi_y)
+{
+ /* validate that the sensor accepts the given DPI values */
+ if (!hidpp20drv_validate_axis_dpi_ranges(sensor, &sensor->x, dpi_x))
+ return false;
+
+ if (sensor->has_y)
+ return hidpp20drv_validate_axis_dpi_ranges(sensor, &sensor->y, dpi_y);
+ return (dpi_x == dpi_y);
+}
+
static int
hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution,
int dpi_x, int dpi_y)
@@ -963,7 +1023,6 @@ hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution,
struct ratbag_device *device = profile->device;
struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
struct hidpp20_sensor *sensor;
- int i;
int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */
if (resolution->is_disabled) {
@@ -987,22 +1046,8 @@ hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution,
if (!resolution->is_disabled) {
/* validate that the sensor accepts the given DPI */
- if (dpi < sensor->dpi_min || dpi > sensor->dpi_max)
+ if (!hidpp20drv_validate_dpi_ranges(sensor, dpi, dpi))
return -EINVAL;
- if (sensor->dpi_steps) {
- for (i = sensor->dpi_min; i < dpi; i += sensor->dpi_steps) {
- }
- if (i != dpi)
- return -EINVAL;
- } else {
- i = 0;
- while (sensor->dpi_list[i]) {
- if (sensor->dpi_list[i] == dpi)
- break;
- }
- if (sensor->dpi_list[i] != dpi)
- return -EINVAL;
- }
}
return hidpp20_adjustable_dpi_set_sensor_dpi(drv_data->dev, sensor, dpi);
@@ -1246,7 +1291,7 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
struct ratbag_resolution *res;
struct hidpp20_profile *p;
int dpi_index = 0xff;
- int dpi;
+ int dpi_x, dpi_y;
profile->is_enabled = drv_data->profiles->profiles[profile->index].enabled;
@@ -1269,15 +1314,17 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
* sensors is too niche to care about right now */
sensor = &drv_data->sensors[0];
- dpi = p->dpi[res->index];
+ dpi_x = p->dpi[res->index];
+ dpi_y = p->dpi[res->index];
/* If the resolution is zero dpi it is disabled,
* but internally we set the minimum value */
- if (dpi == 0) {
+ if (dpi_x == 0 || dpi_y == 0) {
res->is_disabled = true;
- dpi = sensor->dpi_min;
+ dpi_x = sensor->x.dpi_min;
+ dpi_y = (sensor->has_y) ? sensor->y.dpi_min : dpi_x;
}
- ratbag_resolution_set_resolution(res, dpi, dpi);
+ ratbag_resolution_set_resolution(res, dpi_x, dpi_y);
if (profile->is_active &&
res->index == (unsigned int)dpi_index)
@@ -1288,9 +1335,19 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
res->is_active = true;
}
- ratbag_resolution_set_dpi_list_from_range(res,
- sensor->dpi_min,
- sensor->dpi_max);
+ uint16_t dpi_min = sensor->x.dpi_min;
+ uint16_t dpi_max = sensor->x.dpi_max;
+ if (sensor->has_y) {
+ /* Limit min/max to the worse case values, as the
+ * ratbag resolution API only allows a common range
+ * between both axis. */
+ dpi_min = max(dpi_min, sensor->y.dpi_min);
+ dpi_max = max(dpi_max, sensor->y.dpi_max);
+
+ ratbag_resolution_set_cap(res,
+ RATBAG_RESOLUTION_CAP_SEPARATE_XY_RESOLUTION);
+ }
+ ratbag_resolution_set_dpi_list_from_range(res, dpi_min, dpi_max);
}
ratbag_profile_set_report_rate_list(profile,
diff --git a/src/hidpp20.c b/src/hidpp20.c
index c7ee6b849..75d05dcd3 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -1521,7 +1521,9 @@ hidpp20_adjustable_dpi_get_dpi_list(struct hidpp20_device *device,
struct hidpp20_sensor *sensor)
{
int rc;
- unsigned i = 1, dpi_index = 0;
+ unsigned i = 1;
+ struct hidpp20_sensor_axis *axis = &sensor->x;
+ struct hidpp20_sensor_dpi_range *range;
union hidpp20_message msg = {
.msg.report_id = REPORT_ID_SHORT,
.msg.device_idx = device->index,
@@ -1539,24 +1541,63 @@ hidpp20_adjustable_dpi_get_dpi_list(struct hidpp20_device *device,
if (rc)
return rc;
- sensor->dpi_min = 0xffff;
+ sensor->has_y = false;
+ sensor->has_lod = false;
+ axis->dpi_min = 0xffff;
sensor->index = msg.msg.parameters[0];
- while (i < LONG_MESSAGE_LENGTH - 4U &&
- get_unaligned_be_u16(&msg.msg.parameters[i]) != 0) {
+
+ while (1) {
+ if ((LONG_MESSAGE_LENGTH - 4U - i) < 2)
+ break; /* no enough data for 16-bit value */
+
uint16_t value = get_unaligned_be_u16(&msg.msg.parameters[i]);
+ if (value == 0)
+ break; /* break on end of list terminator */
if (device->quirk == HIDPP20_QUIRK_G602 && i == 2)
value += 0xe000;
if (value > 0xe000) {
- sensor->dpi_steps = value - 0xe000;
+ if ((i + 4) >= (LONG_MESSAGE_LENGTH - 4U)) {
+ /* Missing the end extent of the range */
+ hidpp_log_error(&device->base,
+ "DPI list information contains invalid step range, not enough data\n");
+ return -EINVAL;
+ }
+ if (axis->num_dpi_ranges < 1) {
+ /* Missing the start of the steps */
+ hidpp_log_error(&device->base,
+ "Invalid DPI list step range, missing initial start value\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Range starting at last dpi + step, and going to the nearest
+ * integer step preceeding the end value.
+ */
+ uint16_t step = value - 0xe000;
+ uint16_t start = axis->dpi_ranges[axis->num_dpi_ranges - 1].end;
+ uint16_t end = get_unaligned_be_u16(&msg.msg.parameters[i + 2]);
+ int count = (end - start) / step;
+
+ assert(axis->num_dpi_ranges < HIDPP20_DPI_RANGES_MAX);
+ range = &axis->dpi_ranges[axis->num_dpi_ranges++];
+ range->start = start + step;
+ range->end = start + (step * count);
+ range->step = step;
+ axis->dpi_min = min(range->end, axis->dpi_min);
+ axis->dpi_max = max(range->end, axis->dpi_max);
+
+ i += 2; /* consume the range end value */
} else {
- sensor->dpi_min = min(value, sensor->dpi_min);
- sensor->dpi_max = max(value, sensor->dpi_max);
- sensor->dpi_list[dpi_index++] = value;
+ axis->dpi_min = min(value, axis->dpi_min);
+ axis->dpi_max = max(value, axis->dpi_max);
+ assert(axis->num_dpi_ranges < HIDPP20_DPI_RANGES_MAX);
+ range = &axis->dpi_ranges[axis->num_dpi_ranges++];
+ range->start = range->end = value;
+ range->step = 0;
}
- assert(sensor->dpi_list[dpi_index] == 0x0000);
i += 2;
}
@@ -1585,8 +1626,8 @@ hidpp20_adjustable_dpi_get_dpi(struct hidpp20_device *device,
if (rc)
return rc;
- sensor->dpi = get_unaligned_be_u16(&msg.msg.parameters[1]);
- sensor->default_dpi = get_unaligned_be_u16(&msg.msg.parameters[3]);
+ sensor->x.dpi = get_unaligned_be_u16(&msg.msg.parameters[1]);
+ sensor->x.default_dpi = get_unaligned_be_u16(&msg.msg.parameters[3]);
return 0;
}
@@ -1631,13 +1672,12 @@ int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device,
goto err;
hidpp_log_raw(&device->base,
- "sensor %d: current dpi: %d (default: %d) min: %d max: %d steps: %d\n",
+ "sensor %d: current dpi: %d (default: %d) min: %d max: %d\n",
sensor->index,
- sensor->dpi,
- sensor->default_dpi,
- sensor->dpi_min,
- sensor->dpi_max,
- sensor->dpi_steps);
+ sensor->x.dpi,
+ sensor->x.default_dpi,
+ sensor->x.dpi_min,
+ sensor->x.dpi_max);
}
*sensors_list = s_list;
diff --git a/src/hidpp20.h b/src/hidpp20.h
index cddf3866e..f8619da08 100644
--- a/src/hidpp20.h
+++ b/src/hidpp20.h
@@ -391,18 +391,40 @@ int hidpp20_mousepointer_get_mousepointer_info(struct hidpp20_device *device,
#define HIDPP_PAGE_ADJUSTABLE_DPI 0x2201
-/**
- * either dpi_steps is not null or the values are stored in the null terminated
- * array dpi_list.
+/*
+ * The HID++20 protocol provides this data via a paged request mechanism, each
+ * page can store ~7 absolute values or ~3 ranges (start/end/step). The page
+ * index is only 8-bits, which could allow for at most 1500 entries however
+ * most devices only provide a smaller number of pages (e.g. 3). As such allow
+ * for 32 entries (approx. 5 pages).
*/
-struct hidpp20_sensor {
- uint8_t index;
+#define HIDPP20_DPI_RANGES_MAX 32
+
+struct hidpp20_sensor_dpi_range {
+ uint16_t start;
+ uint16_t end;
+ uint16_t step;
+};
+
+struct hidpp20_sensor_axis {
uint16_t dpi;
uint16_t dpi_min;
uint16_t dpi_max;
- uint16_t dpi_steps;
uint16_t default_dpi;
- uint16_t dpi_list[LONG_MESSAGE_LENGTH / 2 + 1];
+ struct hidpp20_sensor_dpi_range dpi_ranges[HIDPP20_DPI_RANGES_MAX];
+ unsigned int num_dpi_ranges;
+};
+
+struct hidpp20_sensor {
+ uint8_t index;
+ bool has_y;
+ struct hidpp20_sensor_axis x;
+ struct hidpp20_sensor_axis y;
+
+ /* Lift Off Distance (LOD) has no valid ranges only fixed enum values */
+ bool has_lod;
+ uint8_t lod;
+ uint8_t default_lod;
};
/**
From 7ffb0c516698c17cac6061166c92ba394031f641 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Tue, 14 Jan 2025 00:40:23 +1000
Subject: [PATCH 5/8] hidpp20: Add support for Extended Adjustable DPI (0x2202)
Newer Logitech devices such as the G Pro X Wireless Superlight 2 have
extended DPI adjustable support to provide access to per axis (X/Y) and
lift off distance configuration. This support is enabled via the
Extended Adjustable DPI feature (0x2202).
This change implements the necessary support to read the capabilities of
the sensor (dual axis/lift off distance) as well as the
functions/commands associated with getting the current DPI and setting
the current DPI.
The ratbag API does not currently provide a mechanism for lift off
distance configuration, because of this it defaults to configuring the
value as 2 (Medium) for all cases.
These changes were validated with the G Pro X Wireless Superlight 2.
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 91 ++++++++++--
src/hidpp20.c | 338 +++++++++++++++++++++++++++++++++++++++++++
src/hidpp20.h | 23 +++
3 files changed, 442 insertions(+), 10 deletions(-)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index 9ef8f2fb7..a09b4b08c 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -58,6 +58,7 @@
#define HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8060 (1 << 8)
#define HIDPP_CAP_BATTERY_VOLTAGE_1001 (1 << 9)
#define HIDPP_CAP_RGB_EFFECTS_8071 (1 << 10)
+#define HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202 (1 << 11)
#define HIDPP_HIDDEN_FEATURE (1 << 6)
@@ -803,6 +804,50 @@ hidpp20drv_read_resolution_dpi_2201(struct ratbag_device *device)
return 0;
}
+static int
+hidpp20drv_read_resolution_dpi_2202(struct ratbag_device *device)
+{
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+ struct ratbag *ratbag = device->ratbag;
+ int rc;
+
+ free(drv_data->sensors);
+ drv_data->sensors = NULL;
+ drv_data->num_sensors = 0;
+ rc = hidpp20_ext_adjustable_dpi_get_sensors(drv_data->dev, &drv_data->sensors);
+ if (rc < 0) {
+ log_error(ratbag,
+ "Error while requesting resolution: %s (%d)\n",
+ strerror(-rc), rc);
+ return rc;
+ } else if (rc == 0) {
+ log_error(ratbag, "Error, no compatible sensors found.\n");
+ return -ENODEV;
+ }
+ log_debug(ratbag,
+ "device is at %d x dpi (variable between %d and %d).\n",
+ drv_data->sensors[0].x.dpi,
+ drv_data->sensors[0].x.dpi_min,
+ drv_data->sensors[0].x.dpi_max);
+ if (drv_data->sensors[0].has_y) {
+ log_debug(ratbag,
+ "device is at %d y dpi (variable between %d and %d).\n",
+ drv_data->sensors[0].y.dpi,
+ drv_data->sensors[0].y.dpi_min,
+ drv_data->sensors[0].y.dpi_max);
+ }
+
+ drv_data->num_sensors = rc;
+
+ /* if 0x8100 has already been enumerated we already have the supported
+ * number of resolutions and shouldn't overwrite it
+ */
+ if (!(drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100))
+ drv_data->num_resolutions = drv_data->num_sensors;
+
+ return 0;
+}
+
static int
hidpp20drv_read_report_rate_8060(struct ratbag_device *device)
{
@@ -899,7 +944,14 @@ hidpp20drv_read_resolution_dpi(struct ratbag_profile *profile)
rc = hidpp20drv_read_resolution_dpi_2201(device);
if (rc < 0)
return rc;
+ } else if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202) {
+ rc = hidpp20drv_read_resolution_dpi_2202(device);
+ if (rc < 0)
+ return rc;
+ }
+ if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201 ||
+ drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202) {
ratbag_profile_for_each_resolution(profile, res) {
/* We only look at the first sensor. Multiple
* sensors is too niche to care about right now */
@@ -1023,21 +1075,14 @@ hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution,
struct ratbag_device *device = profile->device;
struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
struct hidpp20_sensor *sensor;
- int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */
if (resolution->is_disabled) {
if (!ratbag_resolution_has_capability(resolution, RATBAG_RESOLUTION_CAP_DISABLE))
return -ENOTSUP;
- dpi = dpi_x = dpi_y = 0;
+ dpi_x = dpi_y = 0;
}
- if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)
- return hidpp20drv_update_resolution_dpi_8100(resolution, dpi_x, dpi_y);
-
- if (!(drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201))
- return -ENOTSUP;
-
if (!drv_data->num_sensors)
return -ENOTSUP;
@@ -1045,12 +1090,28 @@ hidpp20drv_update_resolution_dpi(struct ratbag_resolution *resolution,
sensor = &drv_data->sensors[0];
if (!resolution->is_disabled) {
+ if (!sensor->has_y)
+ dpi_y = dpi_x;
+
/* validate that the sensor accepts the given DPI */
- if (!hidpp20drv_validate_dpi_ranges(sensor, dpi, dpi))
+ if (!hidpp20drv_validate_dpi_ranges(sensor, dpi_x, dpi_y))
return -EINVAL;
}
- return hidpp20_adjustable_dpi_set_sensor_dpi(drv_data->dev, sensor, dpi);
+ if (drv_data->capabilities & HIDPP_CAP_ONBOARD_PROFILES_8100)
+ return hidpp20drv_update_resolution_dpi_8100(resolution, dpi_x, dpi_y);
+
+ if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201)
+ return hidpp20_adjustable_dpi_set_sensor_dpi(drv_data->dev, sensor, dpi_x);
+
+ if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202)
+ return hidpp20_ext_adjustable_dpi_set_sensor_dpi(drv_data->dev,
+ sensor,
+ dpi_x, dpi_y,
+ sensor->default_lod);
+
+ return -ENOTSUP;
+
}
static int
@@ -1479,6 +1540,16 @@ hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature)
drv_data->capabilities |= HIDPP_CAP_SWITCHABLE_RESOLUTION_2201;
break;
}
+ case HIDPP_PAGE_EXTENDED_ADJUSTABLE_DPI: {
+ log_debug(ratbag, "device has extended adjustable dpi\n");
+ /* we read the profile once to get the correct number of
+ * supported resolutions. */
+ rc = hidpp20drv_read_resolution_dpi_2202(device);
+ if (rc < 0)
+ return 0; /* this is not a hard failure */
+ drv_data->capabilities |= HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202;
+ break;
+ }
case HIDPP_PAGE_SPECIAL_KEYS_BUTTONS: {
log_debug(ratbag, "device has programmable keys/buttons\n");
drv_data->capabilities |= HIDPP_CAP_BUTTON_KEY_1b04;
diff --git a/src/hidpp20.c b/src/hidpp20.c
index 75d05dcd3..c0e645b57 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -64,6 +64,7 @@ hidpp20_feature_get_name(uint16_t feature)
CASE_RETURN_STRING(HIDPP_PAGE_WIRELESS_DEVICE_STATUS);
CASE_RETURN_STRING(HIDPP_PAGE_MOUSE_POINTER_BASIC);
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI);
+ CASE_RETURN_STRING(HIDPP_PAGE_EXTENDED_ADJUSTABLE_DPI);
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE);
CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS);
CASE_RETURN_STRING(HIDPP_PAGE_RGB_EFFECTS);
@@ -1725,6 +1726,343 @@ int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device,
return 0;
}
+/* -------------------------------------------------------------------------- */
+/* 0x2202: Extended Adjustable DPI */
+/* -------------------------------------------------------------------------- */
+
+#define CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_COUNT 0x00
+#define CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI_AXES 0x10
+#define CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST 0x20
+#define CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI 0x50
+#define CMD_EXTENDED_ADJUSTABLE_DPI_SET_SENSOR_DPI 0x60
+
+static int
+hidpp20_ext_adjustable_dpi_get_dpi_axes(struct hidpp20_device *device,
+ uint8_t reg,
+ struct hidpp20_sensor *sensor)
+{
+ int rc;
+
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_SHORT,
+ .msg.device_idx = device->index,
+ .msg.sub_id = reg,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI_AXES,
+ .msg.parameters[0] = sensor->index,
+ };
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ if (msg.msg.parameters[0] != sensor->index)
+ return -EIO;
+
+ return msg.msg.parameters[2];
+}
+
+static int
+hidpp20_ext_adjustable_dpi_get_dpi_list_page(struct hidpp20_device *device,
+ uint8_t reg,
+ struct hidpp20_sensor *sensor,
+ unsigned int axis_index, unsigned int page,
+ uint8_t *buffer)
+{
+ int rc;
+
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_SHORT,
+ .msg.device_idx = device->index,
+ .msg.sub_id = reg,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI_LIST,
+ .msg.parameters[0] = sensor->index,
+ .msg.parameters[1] = axis_index, /* X/Y */
+ .msg.parameters[2] = page, /* page offset in the list */
+ };
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ if (msg.msg.parameters[0] != sensor->index)
+ return -EIO;
+ if (msg.msg.parameters[1] != axis_index)
+ return -EIO;
+ if (msg.msg.parameters[2] != page)
+ return -EIO;
+
+ memcpy(buffer, &msg.msg.parameters[3], LONG_MESSAGE_LENGTH - 4 - 3);
+ return LONG_MESSAGE_LENGTH - 4 - 3;
+}
+
+static int
+hidpp20_ext_adjustable_dpi_get_dpi_list(struct hidpp20_device *device,
+ uint8_t reg,
+ struct hidpp20_sensor *sensor,
+ unsigned int axis_index)
+{
+ int rc;
+ unsigned int page = 0;
+ /* buffer the messages, as fields occur across page boundaries */
+ uint8_t buffer[LONG_MESSAGE_LENGTH - 4 + 1];
+ unsigned int buf_remaining = 0;
+ unsigned int buf_index = 0;
+ struct hidpp20_sensor_axis *axis;
+ struct hidpp20_sensor_dpi_range *range;
+ uint16_t last_value;
+
+ if (axis_index > 2 || (!sensor->has_y && axis_index > 1))
+ return -ENOENT;
+
+ axis = (axis_index == 1) ? &sensor->y : &sensor->x;
+ axis->dpi_min = 0xffff;
+ axis->dpi_max = 0x0000;
+
+ while (1) {
+ if (buf_index != 0) {
+ /* shift remaining data to start of buffer */
+ memcpy(buffer, &buffer[buf_index], buf_remaining);
+ buf_index = 0;
+ }
+
+ rc = hidpp20_ext_adjustable_dpi_get_dpi_list_page(device, reg, sensor, axis_index, page, &buffer[buf_index + buf_remaining]);
+ if (rc < 0)
+ return rc;
+
+ buf_remaining += rc;
+
+ /* Process 2-byte fields */
+ while (buf_remaining >= 2) {
+ uint16_t value = get_unaligned_be_u16(&buffer[buf_index]);
+
+ if (value == 0) {
+ return 0; /* End of listing */
+ }
+
+ /* Step Range */
+ if (value >> 13 == 0x7) {
+ if (buf_remaining < 4) {
+ /* Not enough data, this field is likely across page
+ * boundaries */
+ break;
+ }
+ if (axis->num_dpi_ranges < 1) {
+ hidpp_log_error(&device->base,
+ "Invalid DPI list step range, missing initial start value\n");
+ return -EINVAL;
+ }
+
+ /*
+ * Range starting at last dpi + step, and going to the nearest
+ * integer step preceeding the end value.
+ */
+ uint16_t step = value & 0x1fff;
+ uint16_t start = last_value;
+ uint16_t end = get_unaligned_be_u16(&buffer[buf_index + 2]);
+ int count = (end - start) / step;
+
+ assert(axis->num_dpi_ranges < HIDPP20_DPI_RANGES_MAX);
+ range = &axis->dpi_ranges[axis->num_dpi_ranges++];
+ range->start = start + step;
+ range->end = start + (step * count);
+ range->step = step;
+ last_value = range->end;
+
+ buf_remaining -= 4;
+ buf_index += 4;
+ } else {
+ /* Singular value */
+ assert(axis->num_dpi_ranges < HIDPP20_DPI_RANGES_MAX);
+ range = &axis->dpi_ranges[axis->num_dpi_ranges++];
+ range->start = range->end = value;
+ range->step = 0;
+ last_value = value;
+
+ buf_remaining -= 2;
+ buf_index += 2;
+ }
+
+ axis->dpi_min = min(axis->dpi_min, last_value);
+ axis->dpi_max = max(axis->dpi_max, last_value);
+ }
+
+ /* More data on next page */
+ page += 1;
+ }
+}
+
+static int
+hidpp20_ext_adjustable_dpi_get_dpi_lists(struct hidpp20_device *device,
+ uint8_t reg,
+ struct hidpp20_sensor *sensor)
+{
+ int rc;
+
+ rc = hidpp20_ext_adjustable_dpi_get_dpi_axes(device, reg, sensor);
+ if (rc < 0)
+ return rc;
+
+ sensor->has_y = !!(rc & 0x1); /* Y axis is present */
+ sensor->has_lod = !!(rc & 0x2); /* LOD axis is present */
+
+ /* X */
+ rc = hidpp20_ext_adjustable_dpi_get_dpi_list(device, reg, sensor, 0);
+ if (rc)
+ return rc;
+
+ if (sensor->has_y) {
+ /* Y */
+ return hidpp20_ext_adjustable_dpi_get_dpi_list(device, reg, sensor, 1);
+ }
+ return 0;
+}
+
+static int
+hidpp20_ext_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_SHORT,
+ .msg.device_idx = device->index,
+ .msg.sub_id = reg,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_DPI_GET_SENSOR_DPI,
+ .msg.parameters[0] = sensor->index,
+ };
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ /* X */
+ sensor->x.dpi = get_unaligned_be_u16(&msg.msg.parameters[1]);
+ sensor->x.default_dpi = get_unaligned_be_u16(&msg.msg.parameters[3]);
+
+ /* Y */
+ if (sensor->has_y) {
+ sensor->y.dpi = get_unaligned_be_u16(&msg.msg.parameters[5]);
+ sensor->y.default_dpi = get_unaligned_be_u16(&msg.msg.parameters[7]);
+ }
+
+ /* LOD */
+ if (sensor->has_lod) {
+ sensor->lod = msg.msg.parameters[9];
+ sensor->default_lod = 2;
+ }
+
+ return 0;
+}
+
+
+int hidpp20_ext_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_EXTENDED_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_ext_adjustable_dpi_get_dpi_lists(device,
+ feature_index,
+ sensor);
+ if (rc)
+ goto err;
+
+ rc = hidpp20_ext_adjustable_dpi_get_dpi(device, feature_index, sensor);
+ if (rc)
+ goto err;
+
+ hidpp_log_raw(&device->base,
+ "sensor %d: current x dpi: %d (default: %d) min: %d max: %d ranges: %d\n",
+ sensor->index,
+ sensor->x.dpi,
+ sensor->x.default_dpi,
+ sensor->x.dpi_min,
+ sensor->x.dpi_max,
+ sensor->x.num_dpi_ranges);
+
+ if (sensor->has_y) {
+ hidpp_log_raw(&device->base,
+ "sensor %d: current y dpi: %d (default: %d) min: %d max: %d ranges: %d\n",
+ sensor->index,
+ sensor->y.dpi,
+ sensor->y.default_dpi,
+ sensor->y.dpi_min,
+ sensor->y.dpi_max,
+ sensor->y.num_dpi_ranges);
+ }
+
+ if (sensor->has_lod) {
+ hidpp_log_raw(&device->base,
+ "sensor %d: current lod: %d (default: %d)\n",
+ sensor->index,
+ sensor->lod,
+ sensor->default_lod);
+ }
+ }
+
+ *sensors_list = s_list;
+ return num_sensors;
+err:
+ free(s_list);
+ return rc > 0 ? -EPROTO : rc;
+}
+
+int hidpp20_ext_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device,
+ struct hidpp20_sensor *sensor,
+ uint16_t dpi_x, uint16_t dpi_y,
+ uint8_t lod)
+
+{
+ uint8_t feature_index;
+ int rc;
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_LONG,
+ .msg.device_idx = device->index,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_DPI_SET_SENSOR_DPI,
+ .msg.parameters[0] = sensor->index,
+ };
+
+ set_unaligned_be_u16(&msg.msg.parameters[1], dpi_x);
+ set_unaligned_be_u16(&msg.msg.parameters[3], dpi_y);
+ msg.msg.parameters[5] = lod;
+
+ feature_index = hidpp_root_get_feature_idx(device,
+ HIDPP_PAGE_EXTENDED_ADJUSTABLE_DPI);
+ if (feature_index == 0)
+ return -ENOTSUP;
+
+ msg.msg.sub_id = feature_index;
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
/* -------------------------------------------------------------------------- */
/* 0x8060 - Adjustable Report Rate */
/* -------------------------------------------------------------------------- */
diff --git a/src/hidpp20.h b/src/hidpp20.h
index f8619da08..29d17bdd6 100644
--- a/src/hidpp20.h
+++ b/src/hidpp20.h
@@ -442,6 +442,29 @@ int hidpp20_adjustable_dpi_get_sensors(struct hidpp20_device *device,
int hidpp20_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device,
struct hidpp20_sensor *sensor, uint16_t dpi);
+/* -------------------------------------------------------------------------- */
+/* 0x2202: Extended Adjustable DPI */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_EXTENDED_ADJUSTABLE_DPI 0x2202
+
+/**
+ * allocates a list of sensors that has to be freed by the caller.
+ *
+ * returns the elements in the list or a negative error
+ */
+int hidpp20_ext_adjustable_dpi_get_sensors(struct hidpp20_device *device,
+ struct hidpp20_sensor **sensors_list);
+
+/**
+ * set the current dpi of the provided sensor. sensor must have been
+ * allocated by hidpp20_ext_adjustable_dpi_get_sensors()
+ */
+int hidpp20_ext_adjustable_dpi_set_sensor_dpi(struct hidpp20_device *device,
+ struct hidpp20_sensor *sensor,
+ uint16_t dpi_x, uint16_t dpi_y,
+ uint8_t lod);
+
/* -------------------------------------------------------------------------- */
/* 0x8060 - Adjustable Report Rate */
/* -------------------------------------------------------------------------- */
From f9e8a69e05935b6bdbd0d67dc8ef7b5dd56b0fb5 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Tue, 14 Jan 2025 00:49:40 +1000
Subject: [PATCH 6/8] hidpp20: Add support for Extended Adjustable Report Rates
(0x8601)
Newer Logitech devices such as the G Pro X Wireless Superlight 2 have
extended report rate capabilities to provide access support for rates
exceeding 1000Hz. This support is enabled via the Extended Adjustable
Report Rate feature (0x8601).
This change implements the necessary support to read the capabilities of
the device as well as the functions/commands associated with getting the
current rate and setting the current rate.
The new feature provides per-connection rate capabilities and
configuration, this is currently limited to wired and wireless
connection modes. These changes capture the capabilities for both
separately. The hidpp20 driver does not currently know whether it is
being accessed via a specific connection type, due to this only the
wired case of get/set is handled. This feature is not used directly when
onboard profiles are supported.
These changes were validated with the G Pro X Wireless Superlight 2.
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 156 ++++++++++++++++++++++++++++++++++++++++++-
src/hidpp20.c | 139 ++++++++++++++++++++++++++++++++++++++
src/hidpp20.h | 29 ++++++++
3 files changed, 323 insertions(+), 1 deletion(-)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index a09b4b08c..ea25fd5fa 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -59,6 +59,7 @@
#define HIDPP_CAP_BATTERY_VOLTAGE_1001 (1 << 9)
#define HIDPP_CAP_RGB_EFFECTS_8071 (1 << 10)
#define HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202 (1 << 11)
+#define HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061 (1 << 12)
#define HIDPP_HIDDEN_FEATURE (1 << 6)
@@ -73,8 +74,14 @@ struct hidpp20drv_data {
struct hidpp20_led *leds;
union hidpp20_generic_led_zone_info led_infos;
- unsigned int report_rates[4];
+ unsigned int report_rates[7];
+ unsigned int report_rates_min;
+ unsigned int report_rates_max;
unsigned int num_report_rates;
+ unsigned int report_rates_wireless[7];
+ unsigned int report_rates_wireless_min;
+ unsigned int report_rates_wireless_max;
+ unsigned int num_report_rates_wireless;
unsigned int num_profiles;
unsigned int num_resolutions;
@@ -913,6 +920,108 @@ hidpp20drv_read_report_rate_8060(struct ratbag_device *device)
return 0;
}
+static int
+hidpp20drv_parse_report_rates_bitflags_8061(uint16_t bitflags,
+ unsigned int *rates,
+ unsigned int *rate_min,
+ unsigned int *rate_max)
+{
+ int nrates = 0;
+ int i;
+
+ if (bitflags & (1 << 0))
+ rates[nrates++] = 125;
+ if (bitflags & (1 << 1))
+ rates[nrates++] = 250;
+ if (bitflags & (1 << 2))
+ rates[nrates++] = 500;
+ if (bitflags & (1 << 3))
+ rates[nrates++] = 1000;
+ if (bitflags & (1 << 4))
+ rates[nrates++] = 2000;
+ if (bitflags & (1 << 5))
+ rates[nrates++] = 4000;
+ if (bitflags & (1 << 6))
+ rates[nrates++] = 8000;
+
+ if (nrates > 0) {
+ if (rate_min)
+ *rate_min = rates[0];
+ if (rate_max)
+ *rate_max = rates[0];
+ for (i = 1; i < nrates; i++) {
+ if (rate_min)
+ *rate_min = min(*rate_min, rates[i]);
+ if (rate_max)
+ *rate_max = max(*rate_max, rates[i]);
+ }
+ }
+
+ return nrates;
+}
+
+static int
+hidpp20drv_read_report_rate_8061(struct ratbag_device *device)
+{
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+ struct ratbag *ratbag = device->ratbag;
+ struct ratbag_profile *profile;
+ uint16_t bitflags;
+ int rc;
+ uint8_t rate;
+ unsigned rate_hz;
+
+ /* Wired */
+ rc = hidpp20_ext_adjustable_report_rate_get_report_rate_list(drv_data->dev,
+ HIDPP20_EXT_RATE_CONN_TYPE_WIRED,
+ &bitflags);
+ if (rc < 0)
+ return rc;
+
+ drv_data->num_report_rates =
+ hidpp20drv_parse_report_rates_bitflags_8061(bitflags,
+ drv_data->report_rates,
+ &drv_data->report_rates_min,
+ &drv_data->report_rates_max);
+
+ /* Wireless */
+ rc = hidpp20_ext_adjustable_report_rate_get_report_rate_list(drv_data->dev,
+ HIDPP20_EXT_RATE_CONN_TYPE_LIGHTSPEED,
+ &bitflags);
+ if (rc < 0)
+ return rc;
+
+ drv_data->num_report_rates_wireless =
+ hidpp20drv_parse_report_rates_bitflags_8061(bitflags,
+ drv_data->report_rates_wireless,
+ &drv_data->report_rates_wireless_min,
+ &drv_data->report_rates_wireless_max);
+
+ /*
+ * FIXME: The hidpp20 driver does not currently know if a device is via
+ * a wireless recevier so assume the device is a wired connection. This
+ * report rate read mechanism is only used when profiles are not used
+ * or are unsupported.
+ */
+ rc = hidpp20_ext_adjustable_report_rate_get_report_rate(drv_data->dev,
+ HIDPP20_EXT_RATE_CONN_TYPE_WIRED,
+ &rate);
+ if (rc)
+ return rc;
+
+ rate_hz = hidpp20_ext_adjustable_report_rate_to_hz(rate);
+ if (rate_hz) {
+ log_debug(ratbag, "report rate is %u\n", rate_hz);
+ ratbag_device_for_each_profile(device, profile)
+ profile->hz = rate_hz;
+ }
+
+ log_debug(ratbag, "device has %d wired report rates, %d wireless report rates\n",
+ drv_data->num_report_rates, drv_data->num_report_rates_wireless);
+
+ return 0;
+}
+
static int
hidpp20drv_read_resolution_dpi(struct ratbag_profile *profile)
{
@@ -991,6 +1100,16 @@ hidpp20drv_read_resolution_dpi(struct ratbag_profile *profile)
ratbag_profile_set_report_rate_list(profile,
drv_data->report_rates,
drv_data->num_report_rates);
+ } else if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061) {
+ rc = hidpp20drv_read_report_rate_8061(device);
+ if (rc < 0 && drv_data->report_rates[0] == 0)
+ return rc;
+
+ /* FIXME: Handle wired/wireless rate differences. See
+ * hidpp20drv_read_report_rate_8061. */
+ ratbag_profile_set_report_rate_list(profile,
+ drv_data->report_rates,
+ drv_data->num_report_rates);
} else {
ratbag_profile_set_report_rate_list(profile, &default_rate, 1);
}
@@ -1128,6 +1247,21 @@ hidpp20drv_update_report_rate_8060(struct ratbag_profile *profile, int hz)
return RATBAG_SUCCESS;
}
+static int
+hidpp20drv_update_report_rate_8061(struct ratbag_profile *profile, int hz)
+{
+ struct ratbag_device *device = profile->device;
+ struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
+ int rc;
+ uint8_t value = hidpp20_ext_adjustable_report_rate_from_hz(hz);
+
+ rc = hidpp20_ext_adjustable_report_rate_set_report_rate(drv_data->dev, value);
+ if (rc)
+ return rc;
+
+ return RATBAG_SUCCESS;
+}
+
static int
hidpp20drv_update_report_rate_8100(struct ratbag_profile *profile, int hz)
{
@@ -1158,6 +1292,14 @@ hidpp20drv_update_report_rate(struct ratbag_profile *profile, int hz)
if (rc)
hidpp20drv_read_report_rate_8060(profile->device);
+ return rc;
+ } else if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061) {
+ rc = hidpp20drv_update_report_rate_8061(profile, hz);
+
+ /* re-populate the profile with the correct value if we fail */
+ if (rc)
+ hidpp20drv_read_report_rate_8061(profile->device);
+
return rc;
}
@@ -1612,6 +1754,18 @@ hidpp20drv_init_feature(struct ratbag_device *device, uint16_t feature)
drv_data->capabilities |= HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8060;
break;
}
+ case HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE: {
+ log_debug(ratbag, "device has extended adjustable report rate\n");
+
+ /* we read the profile once to get the correct number of
+ * supported report rates. */
+ rc = hidpp20drv_read_report_rate_8061(device);
+ if (rc < 0)
+ return 0; /* this is not a hard failure */
+
+ drv_data->capabilities |= HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061;
+ break;
+ }
case HIDPP_PAGE_COLOR_LED_EFFECTS: {
/* The 8070 feature implemented in the G602 doesn't follow the spec,
* so we ignore it */
diff --git a/src/hidpp20.c b/src/hidpp20.c
index c0e645b57..1a7a4c97b 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -66,6 +66,7 @@ hidpp20_feature_get_name(uint16_t feature)
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_DPI);
CASE_RETURN_STRING(HIDPP_PAGE_EXTENDED_ADJUSTABLE_DPI);
CASE_RETURN_STRING(HIDPP_PAGE_ADJUSTABLE_REPORT_RATE);
+ CASE_RETURN_STRING(HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE);
CASE_RETURN_STRING(HIDPP_PAGE_COLOR_LED_EFFECTS);
CASE_RETURN_STRING(HIDPP_PAGE_RGB_EFFECTS);
CASE_RETURN_STRING(HIDPP_PAGE_ONBOARD_PROFILES);
@@ -2152,6 +2153,144 @@ int hidpp20_adjustable_report_rate_set_report_rate(struct hidpp20_device *device
return 0;
}
+/* -------------------------------------------------------------------------- */
+/* 0x8061 - Extended Adjustable Report Rate */
+/* -------------------------------------------------------------------------- */
+
+unsigned hidpp20_ext_adjustable_report_rate_to_hz(uint8_t rate_ms)
+{
+ switch (rate_ms)
+ {
+ case 0: /* 8ms */
+ return 125;
+ case 1: /* 4ms */
+ return 250;
+ case 2: /* 2ms */
+ return 500;
+ case 3: /* 1ms */
+ return 1000;
+ case 4: /* 500us */
+ return 2000;
+ case 5: /* 250us */
+ return 4000;
+ case 6: /* 125us */
+ return 8000;
+ default:
+ return 0;
+ }
+}
+
+uint8_t hidpp20_ext_adjustable_report_rate_from_hz(unsigned rate_hz)
+{
+ switch (rate_hz)
+ {
+ case 125: /* 8ms */
+ return 0;
+ case 250: /* 4ms */
+ return 1;
+ case 500: /* 2ms */
+ return 2;
+ case 1000: /* 1ms */
+ return 3;
+ case 2000: /* 500us */
+ return 4;
+ case 4000: /* 250us */
+ return 5;
+ case 8000: /* 125us */
+ return 6;
+ default:
+ return -1;
+ }
+}
+
+#define CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE_LIST 0x00
+#define CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE 0x20
+#define CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_SET_REPORT_RATE 0x30
+
+int hidpp20_ext_adjustable_report_rate_get_report_rate_list(struct hidpp20_device *device,
+ enum hidpp20_ext_rate_conn_type conn,
+ uint16_t *bitflags)
+{
+ uint8_t feature_index;
+ int rc;
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_SHORT,
+ .msg.device_idx = device->index,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE_LIST,
+ .msg.parameters[0] = conn,
+ };
+
+ feature_index = hidpp_root_get_feature_idx(device,
+ HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE);
+ if (feature_index == 0)
+ return -ENOTSUP;
+
+ msg.msg.sub_id = feature_index;
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ *bitflags = get_unaligned_be_u16(&msg.msg.parameters[0]);
+
+ return 0;
+}
+
+int hidpp20_ext_adjustable_report_rate_get_report_rate(struct hidpp20_device *device,
+ enum hidpp20_ext_rate_conn_type conn,
+ uint8_t *rate)
+{
+ uint8_t feature_index;
+ int rc;
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_SHORT,
+ .msg.device_idx = device->index,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_GET_REPORT_RATE,
+ .msg.parameters[0] = conn,
+ };
+
+ feature_index = hidpp_root_get_feature_idx(device,
+ HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE);
+ if (feature_index == 0)
+ return -ENOTSUP;
+
+ msg.msg.sub_id = feature_index;
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ *rate = msg.msg.parameters[0];
+
+ return 0;
+}
+
+int hidpp20_ext_adjustable_report_rate_set_report_rate(struct hidpp20_device *device,
+ uint8_t rate)
+{
+ uint8_t feature_index;
+ int rc;
+ union hidpp20_message msg = {
+ .msg.report_id = REPORT_ID_SHORT,
+ .msg.device_idx = device->index,
+ .msg.address = CMD_EXTENDED_ADJUSTABLE_REPORT_RATE_SET_REPORT_RATE,
+ .msg.parameters[0] = rate,
+ };
+
+ feature_index = hidpp_root_get_feature_idx(device,
+ HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE);
+ if (feature_index == 0)
+ return -ENOTSUP;
+
+ msg.msg.sub_id = feature_index;
+
+ rc = hidpp20_request_command(device, &msg);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
/* -------------------------------------------------------------------------- */
/* 0x8100 - Onboard Profiles */
/* -------------------------------------------------------------------------- */
diff --git a/src/hidpp20.h b/src/hidpp20.h
index 29d17bdd6..7d1b7839e 100644
--- a/src/hidpp20.h
+++ b/src/hidpp20.h
@@ -484,6 +484,35 @@ int hidpp20_adjustable_report_rate_get_report_rate(struct hidpp20_device *device
int hidpp20_adjustable_report_rate_set_report_rate(struct hidpp20_device *device,
uint8_t rate_ms);
+/* -------------------------------------------------------------------------- */
+/* 0x8061 - Extended Adjustable Report Rate */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_EXTENDED_ADJUSTABLE_REPORT_RATE 0x8061
+
+unsigned hidpp20_ext_adjustable_report_rate_to_hz(uint8_t rate_ms);
+uint8_t hidpp20_ext_adjustable_report_rate_from_hz(unsigned rate_hz);
+
+enum hidpp20_ext_rate_conn_type {
+ HIDPP20_EXT_RATE_CONN_TYPE_WIRED = 0x00,
+ HIDPP20_EXT_RATE_CONN_TYPE_LIGHTSPEED = 0x01,
+};
+
+/**
+ * set the bitmap_ms to the supported report rates. Bits enabled reflect a
+ * supported report rate (non-linearly).
+ */
+int hidpp20_ext_adjustable_report_rate_get_report_rate_list(struct hidpp20_device *device,
+ enum hidpp20_ext_rate_conn_type conn,
+ uint16_t *bitflags);
+
+int hidpp20_ext_adjustable_report_rate_get_report_rate(struct hidpp20_device *device,
+ enum hidpp20_ext_rate_conn_type conn,
+ uint8_t *rate);
+
+int hidpp20_ext_adjustable_report_rate_set_report_rate(struct hidpp20_device *device,
+ uint8_t rate);
+
/* -------------------------------------------------------------------------- */
/* 0x8070v4 - Color LED effects */
/* -------------------------------------------------------------------------- */
From 8fbde89b2b80fb76c5ec79eb787d9fa20a1885d8 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Tue, 14 Jan 2025 00:50:05 +1000
Subject: [PATCH 7/8] hidpp20: Move profile report rate decoding/encoding
This change moves where the value of the profiles report rate is
decoded/encoded from the parse_profile/write_profile functions to the
read_profile_8100/update_report_rate_8100 functions.
This makes report rate consistent with other internal profile values,
such that the raw value is stored in the internal profile structure.
Additionally this refactors the internal profile and decoding/encoding
handling to integrate better with additional profile formats or report
rate feature capabilities (e.g. 0x8061).
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 4 ++--
src/hidpp20.c | 4 ++--
src/hidpp20.h | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index ea25fd5fa..a59fe1a25 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -1270,7 +1270,7 @@ hidpp20drv_update_report_rate_8100(struct ratbag_profile *profile, int hz)
struct hidpp20_profile *h_profile;
h_profile = &drv_data->profiles->profiles[profile->index];
- h_profile->report_rate = hz;
+ h_profile->report_rate = 1000 / hz;
return RATBAG_SUCCESS;
}
@@ -1556,7 +1556,7 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
ratbag_profile_set_report_rate_list(profile,
drv_data->report_rates,
drv_data->num_report_rates);
- profile->hz = p->report_rate;
+ profile->hz = 1000 / max(1, p->report_rate);
}
static int
diff --git a/src/hidpp20.c b/src/hidpp20.c
index 1a7a4c97b..f42c3b010 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -3255,7 +3255,7 @@ hidpp20_onboard_profiles_parse_profile(struct hidpp20_device *device,
}
}
- profile->report_rate = 1000 / max(1, pdata->profile.report_rate);
+ profile->report_rate = pdata->profile.report_rate;
profile->default_dpi = pdata->profile.default_dpi;
profile->switched_dpi = pdata->profile.switched_dpi;
@@ -3458,7 +3458,7 @@ hidpp20_onboard_profiles_write_profile(struct hidpp20_device *device,
memset(data, 0xff, profiles_list->sector_size);
- pdata->profile.report_rate = 1000 / profile->report_rate;
+ pdata->profile.report_rate = profile->report_rate;
pdata->profile.default_dpi = profile->default_dpi;
pdata->profile.switched_dpi = profile->switched_dpi;
diff --git a/src/hidpp20.h b/src/hidpp20.h
index 7d1b7839e..957edf109 100644
--- a/src/hidpp20.h
+++ b/src/hidpp20.h
@@ -951,7 +951,7 @@ struct hidpp20_profile {
char name[16 * 3];
uint16_t powersave_timeout;
uint16_t poweroff_timeout;
- unsigned report_rate;
+ uint8_t report_rate;
unsigned default_dpi;
unsigned switched_dpi;
unsigned current_dpi;
From 410246fa5df533d0bc3850a1e0d4b8c379abbfe4 Mon Sep 17 00:00:00 2001
From: Nathan Rossi <nathan@nathanrossi.com>
Date: Sun, 12 Jan 2025 00:44:26 +1000
Subject: [PATCH 8/8] hidpp20: Add support for onboard profile format id 0x06
Newer devices including the Logitech G Pro X Wireless Superlight 2
implement onboard profiles with a new format id of 0x06. This format is
significantly different from the existing formats due to the support for
features 0x2202 and 0x8061 which includes support for independent axis
DPI values, Lift Off Distance (LOD) and extended report rates including
wired/wireless specific values.
This change adds support for the basic functionality that is present in
this profile format, as the G Pro X Wireless Superlight 2 does not
provide some of the extra functions that are present in the format
profile structure (e.g. LEDs). This profile format also provide
configuration specifics that are not currently possible with the ratbag
API.
The X and Y axis DPI configuration is implemented to match the ratbag
API, however LOD is not. Whilst the internal profile structure for LOD
is handled it is not presented to any higher level interfaces. It will
however preserve the existing values configured, such that if the
Logitech software is used to configured a specific value it will be kept
when DPI values are modified by ratbag.
Button mapping and remapping is implemented based on the existing
functionality already present in the hidpp20 driver.
Report rate configuration is present on a per profile basis, however the
profile allows for wired and wireless rates to be configured
independently. The ratbag API does not have any mechanism to handle
this, so this change allows for modifying the profile report rate based
on the maximum capabilities of the device. Such that when setting the
report rate it will set both wired and wireless to the desired value
however if that value is above capability of that connection mode it
will be capped. This is useful to be able to configure the wireless
report rate (e.g. 8000Hz) of the device which can be higher than the
wired rate (e.g. 1000Hz)
In order to handle this new profile, the format_id of the profile is
persisted to the hidpp20_profile structure. This allows for the
conditional handling of the parse/write behaviour due to the significant
differences in profile binary structure. Other values are also added to
the hidpp20_profile structure to handle the additional configuration
available in this profile format.
These changes were validated with the G Pro X Wireless Superlight 2,
G203 and G900 due to changes to the common internal profile structure.
Signed-off-by: Nathan Rossi <nathan@nathanrossi.com>
---
src/driver-hidpp20.c | 72 +++++++++++++++---
src/hidpp20.c | 177 ++++++++++++++++++++++++++++++++++++++++++-
src/hidpp20.h | 8 +-
3 files changed, 243 insertions(+), 14 deletions(-)
diff --git a/src/driver-hidpp20.c b/src/driver-hidpp20.c
index a59fe1a25..10466dc79 100644
--- a/src/driver-hidpp20.c
+++ b/src/driver-hidpp20.c
@@ -1125,10 +1125,10 @@ hidpp20drv_update_resolution_dpi_8100(struct ratbag_resolution *resolution,
struct ratbag_device *device = profile->device;
struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
struct hidpp20_profile *h_profile;
- int dpi = dpi_x; /* dpi_x == dpi_y if we don't have the individual resolution cap */
h_profile = &drv_data->profiles->profiles[profile->index];
- h_profile->dpi[resolution->index] = dpi;
+ h_profile->dpi[resolution->index].x = dpi_x;
+ h_profile->dpi[resolution->index].y = dpi_y;
if (resolution->is_default)
h_profile->default_dpi = resolution->index;
@@ -1268,9 +1268,38 @@ hidpp20drv_update_report_rate_8100(struct ratbag_profile *profile, int hz)
struct ratbag_device *device = profile->device;
struct hidpp20drv_data *drv_data = ratbag_get_drv_data(device);
struct hidpp20_profile *h_profile;
+ unsigned value;
h_profile = &drv_data->profiles->profiles[profile->index];
- h_profile->report_rate = 1000 / hz;
+
+ /*
+ * FIXME: Handle wireless report rate independently of wired, for now
+ * allow the report rate to be configured for the mode that supports
+ * the highest rate (e.g. wireless) and restrict the alternate mode
+ * reported rate to its maximum. This allows for configuring e.g. the
+ * wireless rate as 2000hz when the wired rate is limited to 1000hz
+ * (e.g. G Pro X Superlight 2)
+ */
+ if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061) {
+ /* Restrict wired report rate */
+ h_profile->report_rate = 0;
+ if (drv_data->num_report_rates > 0) {
+ value = min(max((unsigned)hz, drv_data->report_rates_min),
+ drv_data->report_rates_max);
+ h_profile->report_rate = hidpp20_ext_adjustable_report_rate_from_hz(value);
+ }
+
+ /* Restrict wireless report rate */
+ h_profile->report_rate_wireless = 0;
+ if (drv_data->num_report_rates_wireless > 0) {
+ value = min(max((unsigned)hz, drv_data->report_rates_wireless_min),
+ drv_data->report_rates_wireless_max);
+ h_profile->report_rate_wireless = hidpp20_ext_adjustable_report_rate_from_hz(value);
+ }
+ } else {
+ h_profile->report_rate = 1000 / hz;
+ h_profile->report_rate_wireless = 0; /* not supported */
+ }
return RATBAG_SUCCESS;
}
@@ -1517,8 +1546,8 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
* sensors is too niche to care about right now */
sensor = &drv_data->sensors[0];
- dpi_x = p->dpi[res->index];
- dpi_y = p->dpi[res->index];
+ dpi_x = p->dpi[res->index].x;
+ dpi_y = (sensor->has_y) ? p->dpi[res->index].y : dpi_x;
/* If the resolution is zero dpi it is disabled,
* but internally we set the minimum value */
@@ -1553,10 +1582,32 @@ hidpp20drv_read_profile_8100(struct ratbag_profile *profile)
ratbag_resolution_set_dpi_list_from_range(res, dpi_min, dpi_max);
}
- ratbag_profile_set_report_rate_list(profile,
- drv_data->report_rates,
- drv_data->num_report_rates);
- profile->hz = 1000 / max(1, p->report_rate);
+ /*
+ * FIXME: Handle wired/wrieless independently, for now parse the
+ * highest rate, and allow the available rates to be the highest of
+ * wired/wrieless where present. See hidpp20drv_update_report_rate_8100
+ * for additional details.
+ */
+ if (drv_data->num_report_rates_wireless > 0 &&
+ drv_data->report_rates_wireless_max > drv_data->report_rates_max) {
+ ratbag_profile_set_report_rate_list(profile,
+ drv_data->report_rates_wireless,
+ drv_data->num_report_rates_wireless);
+ } else {
+ ratbag_profile_set_report_rate_list(profile,
+ drv_data->report_rates,
+ drv_data->num_report_rates);
+ }
+
+ if (drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_REPORT_RATE_8061) {
+ /* FIXME: Handle wired/wrieless independently, for now use the
+ * highest rate */
+ unsigned wired = hidpp20_ext_adjustable_report_rate_to_hz(p->report_rate);
+ unsigned wireless = hidpp20_ext_adjustable_report_rate_to_hz(p->report_rate_wireless);
+ profile->hz = max(wired, wireless);
+ } else {
+ profile->hz = 1000 / max(1, p->report_rate);
+ }
}
static int
@@ -1604,7 +1655,8 @@ hidpp20drv_init_profile_8100(struct ratbag_device *device)
drv_data->num_profiles = drv_data->profiles->num_profiles;
drv_data->num_buttons = drv_data->profiles->num_buttons;
- if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201)
+ if (drv_data->capabilities & HIDPP_CAP_SWITCHABLE_RESOLUTION_2201 ||
+ drv_data->capabilities & HIDPP_CAP_ADJUSTABLE_RESOLUTION_2202)
drv_data->num_resolutions = drv_data->profiles->num_modes;
/* We ignore the profile's num_leds and require
* HIDPP_PAGE_COLOR_LED_EFFECTS to succeed instead
diff --git a/src/hidpp20.c b/src/hidpp20.c
index f42c3b010..c9905eec2 100644
--- a/src/hidpp20.c
+++ b/src/hidpp20.c
@@ -2319,6 +2319,7 @@ int hidpp20_ext_adjustable_report_rate_set_report_rate(struct hidpp20_device *de
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G303 0x02
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G900 0x03
#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G915 0x04
+#define HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_GPXSL2 0x06
#define HIDPP20_ONBOARD_PROFILES_MACRO_TYPE_G402 0x01
#define HIDPP20_USER_PROFILES_G402 0x0000
@@ -2354,6 +2355,36 @@ union hidpp20_internal_profile {
};
_Static_assert(sizeof(union hidpp20_internal_profile) == HIDPP20_PROFILE_SIZE, "Invalid size");
+union hidpp20_internal_profile_ext {
+ uint8_t data[HIDPP20_PROFILE_SIZE];
+ struct {
+ uint8_t report_rate_wireless;
+ uint8_t report_rate;
+ uint8_t default_dpi;
+ uint8_t switched_dpi;
+ struct {
+ uint16_t x;
+ uint16_t y;
+ uint8_t lod; /* 0x1 = low, 0x2 = medium, 0x3 = high */
+ } __attribute__((packed)) dpi[5];
+ uint8_t reserved[15];
+ uint16_t powersave_timeout;
+ uint16_t poweroff_timeout;
+ union hidpp20_button_binding buttons[16];
+ union hidpp20_button_binding alternate_buttons[12];
+ /* UTF16-LE encoded profile name */
+ union {
+ char txt[48];
+ uint8_t raw[48];
+ } name;
+ struct hidpp20_internal_led leds[2];
+ struct hidpp20_internal_led alt_leds[2];
+ uint8_t reserved_2; /* default value of 0x03 */
+ uint16_t crc;
+ } __attribute__((packed)) profile;
+};
+_Static_assert(sizeof(union hidpp20_internal_profile_ext) == HIDPP20_PROFILE_SIZE, "Invalid size");
+
int
hidpp20_onboard_profiles_get_profiles_desc(struct hidpp20_device *device,
struct hidpp20_onboard_profiles_info *info)
@@ -2741,7 +2772,8 @@ hidpp20_onboard_profiles_validate(struct hidpp20_device *device,
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) &&
- (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G915)) {
+ (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_G915) &&
+ (info->profile_format_id != HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_GPXSL2)) {
hidpp_log_error(&device->base,
"Profile layout not supported: 0x%02x.\n",
info->profile_format_id);
@@ -2798,6 +2830,7 @@ hidpp20_onboard_profiles_allocate(struct hidpp20_device *device,
profiles->profiles = zalloc(info.profile_count * sizeof(struct hidpp20_profile));
profiles->sector_size = info.sector_size;
profiles->sector_count = info.sector_count;
+ profiles->format_id = info.profile_format_id;
profiles->num_profiles = info.profile_count;
profiles->num_rom_profiles = info.profile_count_oob;
profiles->num_buttons = min(info.button_count, 16);
@@ -3221,6 +3254,81 @@ hidpp20_onboard_profiles_read_led(struct hidpp20_led *led,
led->brightness = brightness;
}
+static int
+hidpp20_onboard_profiles_parse_profile_ext(struct hidpp20_device *device,
+ struct hidpp20_profiles *profiles_list,
+ unsigned index,
+ bool check_crc)
+{
+ union hidpp20_internal_profile_ext *pdata;
+ struct hidpp20_profile *profile = &profiles_list->profiles[index];
+ uint16_t sector = profile->address;
+ _cleanup_free_ uint8_t *data = NULL;
+ unsigned i;
+ int rc;
+
+ if (index >= profiles_list->num_profiles)
+ return -EINVAL;
+
+ data = hidpp20_onboard_profiles_allocate_sector(profiles_list);
+ pdata = (union hidpp20_internal_profile_ext *)data;
+
+ rc = hidpp20_onboard_profiles_read_sector(device,
+ sector,
+ profiles_list->sector_size,
+ data);
+ if (rc < 0)
+ return rc;
+
+ if (check_crc) {
+ if (!hidpp20_onboard_profiles_is_sector_valid(device,
+ profiles_list->sector_size,
+ data)) {
+ return -EAGAIN;
+ }
+ }
+
+ profile->report_rate = pdata->profile.report_rate;
+ profile->report_rate_wireless = pdata->profile.report_rate_wireless;
+ profile->default_dpi = pdata->profile.default_dpi;
+ profile->switched_dpi = pdata->profile.switched_dpi;
+
+ profile->powersave_timeout = pdata->profile.powersave_timeout;
+ profile->poweroff_timeout = pdata->profile.poweroff_timeout;
+
+ for (i = 0; i < 5; i++) {
+ unsigned int offset = 4 + (5 * i);
+ profile->dpi[i].x = get_unaligned_le_u16(&data[offset]);
+ profile->dpi[i].y = get_unaligned_le_u16(&data[offset + 2]);
+ profile->dpi[i].lod = data[offset + 4];
+ }
+
+ for (i = 0; i < profiles_list->num_leds; i++) {
+ hidpp20_onboard_profiles_read_led(&profile->leds[i], pdata->profile.leds[i]);
+ hidpp20_onboard_profiles_read_led(&profile->alt_leds[i], pdata->profile.alt_leds[i]);
+ }
+
+ hidpp20_buttons_to_cpu(device, profiles_list, profile, pdata->profile.buttons, profiles_list->num_buttons);
+
+ /* check if we are using the default name or not */
+ for (i = 0; i < sizeof(pdata->profile.name.raw); i++) {
+ if (pdata->profile.name.raw[i] != 0xff)
+ break;
+ }
+
+ /* clear profile name */
+ if (i == sizeof(pdata->profile.name.raw)) {
+ memset(profile->name, 0, sizeof(profile->name));
+ return 0;
+ }
+
+ memcpy(profile->name, pdata->profile.name.txt, sizeof(profile->name));
+ /* force terminating '\0' */
+ profile->name[sizeof(profile->name) - 1] = '\0';
+
+ return 0;
+}
+
static int
hidpp20_onboard_profiles_parse_profile(struct hidpp20_device *device,
struct hidpp20_profiles *profiles_list,
@@ -3234,6 +3342,11 @@ hidpp20_onboard_profiles_parse_profile(struct hidpp20_device *device,
unsigned i;
int rc;
+ if (profiles_list->format_id == HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_GPXSL2) {
+ /* Handle extended format profiles */
+ return hidpp20_onboard_profiles_parse_profile_ext(device, profiles_list, index, check_crc);
+ }
+
if (index >= profiles_list->num_profiles)
return -EINVAL;
@@ -3263,7 +3376,7 @@ hidpp20_onboard_profiles_parse_profile(struct hidpp20_device *device,
profile->poweroff_timeout = pdata->profile.poweroff_timeout;
for (i = 0; i < 5; i++) {
- profile->dpi[i] = get_unaligned_le_u16(&data[2 * i + 3]);
+ profile->dpi[i].x = get_unaligned_le_u16(&data[2 * i + 3]);
}
for (i = 0; i < profiles_list->num_leds; i++)
@@ -3437,6 +3550,59 @@ hidpp20_onboard_profiles_write_led(struct hidpp20_internal_led *internal_led,
}
}
+static int
+hidpp20_onboard_profiles_write_profile_ext(struct hidpp20_device *device,
+ struct hidpp20_profiles *profiles_list,
+ unsigned int index)
+{
+ union hidpp20_internal_profile_ext *pdata;
+ _cleanup_free_ uint8_t *data = NULL;
+ uint16_t sector_size = profiles_list->sector_size;
+ uint16_t sector = index + 1;
+ struct hidpp20_profile *profile = &profiles_list->profiles[index];
+ unsigned i;
+ int rc;
+
+ if (index >= profiles_list->num_profiles)
+ return -EINVAL;
+
+ data = hidpp20_onboard_profiles_allocate_sector(profiles_list);
+ pdata = (union hidpp20_internal_profile_ext *)data;
+
+ memset(data, 0xff, profiles_list->sector_size);
+
+ pdata->profile.report_rate = profile->report_rate;
+ pdata->profile.report_rate_wireless = profile->report_rate_wireless;
+ pdata->profile.default_dpi = profile->default_dpi;
+ pdata->profile.switched_dpi = profile->switched_dpi;
+
+ pdata->profile.powersave_timeout = profile->powersave_timeout;
+ pdata->profile.poweroff_timeout = profile->poweroff_timeout;
+
+ for (i = 0; i < 5; i++) {
+ pdata->profile.dpi[i].x = hidpp_cpu_to_le_u16(profile->dpi[i].x);
+ pdata->profile.dpi[i].y = hidpp_cpu_to_le_u16(profile->dpi[i].y);
+ pdata->profile.dpi[i].lod = profile->dpi[i].lod;
+ }
+
+ for (i = 0; i < profiles_list->num_leds; i++) {
+ hidpp20_onboard_profiles_write_led(&pdata->profile.leds[i], &profile->leds[i]);
+ hidpp20_onboard_profiles_write_led(&pdata->profile.alt_leds[i], &profile->alt_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_sector(device, sector, sector_size, data, true);
+ if (rc < 0) {
+ hidpp_log_error(&device->base, "failed to write profile\n");
+ return rc;
+ }
+
+ return 0;
+}
+
static int
hidpp20_onboard_profiles_write_profile(struct hidpp20_device *device,
struct hidpp20_profiles *profiles_list,
@@ -3450,6 +3616,11 @@ hidpp20_onboard_profiles_write_profile(struct hidpp20_device *device,
unsigned i;
int rc;
+ if (profiles_list->format_id == HIDPP20_ONBOARD_PROFILES_PROFILE_TYPE_GPXSL2) {
+ /* Handle extended format profiles */
+ return hidpp20_onboard_profiles_write_profile_ext(device, profiles_list, index);
+ }
+
if (index >= profiles_list->num_profiles)
return -EINVAL;
@@ -3466,7 +3637,7 @@ hidpp20_onboard_profiles_write_profile(struct hidpp20_device *device,
pdata->profile.poweroff_timeout = profile->poweroff_timeout;
for (i = 0; i < 5; i++) {
- pdata->profile.dpi[i] = hidpp_cpu_to_le_u16(profile->dpi[i]);
+ pdata->profile.dpi[i] = hidpp_cpu_to_le_u16(profile->dpi[i].x);
}
for (i = 0; i < profiles_list->num_leds; i++)
diff --git a/src/hidpp20.h b/src/hidpp20.h
index 957edf109..3d2d5e42f 100644
--- a/src/hidpp20.h
+++ b/src/hidpp20.h
@@ -952,10 +952,15 @@ struct hidpp20_profile {
uint16_t powersave_timeout;
uint16_t poweroff_timeout;
uint8_t report_rate;
+ uint8_t report_rate_wireless;
unsigned default_dpi;
unsigned switched_dpi;
unsigned current_dpi;
- uint16_t dpi[HIDPP20_DPI_COUNT];
+ struct {
+ uint16_t x;
+ uint16_t y;
+ uint8_t lod;
+ } dpi[HIDPP20_DPI_COUNT];
union hidpp20_button_binding buttons[32];
union hidpp20_macro_data *macros[32];
struct hidpp20_led leds[HIDPP20_LED_COUNT];
@@ -990,6 +995,7 @@ struct hidpp20_profiles {
uint8_t sector_count;
uint16_t sector_size;
uint8_t active_profile_index;
+ uint8_t format_id;
struct hidpp20_profile *profiles;
};