@@ -58,6 +58,7 @@ MODULE_PARM_DESC(disable_tap_to_click,
#define HIDPP_QUIRK_CLASS_K400 BIT(2)
#define HIDPP_QUIRK_CLASS_G920 BIT(3)
#define HIDPP_QUIRK_CLASS_K750 BIT(4)
+#define HIDPP_QUIRK_CLASS_BLUETOOTH BIT(5)
/* bits 2..20 are reserved for classes */
/* #define HIDPP_QUIRK_CONNECT_EVENTS BIT(21) disabled */
@@ -81,6 +82,10 @@ MODULE_PARM_DESC(disable_tap_to_click,
HIDPP_QUIRK_HI_RES_SCROLL_X2120 | \
HIDPP_QUIRK_HI_RES_SCROLL_X2121)
+/* Just an alias for now, may possibly be a catch-all in the future */
+#define HIDPP_QUIRK_MISSING_SHORT_REPORTS HIDPP_QUIRK_CLASS_BLUETOOTH
+#define HIDPP_QUIRK_WIRELESS_DEVICE_STATUS HIDPP_QUIRK_CLASS_BLUETOOTH
+
#define HIDPP_QUIRK_DELAYED_INIT HIDPP_QUIRK_NO_HIDINPUT
#define HIDPP_CAPABILITY_HIDPP10_BATTERY BIT(0)
@@ -186,6 +191,9 @@ struct hidpp_device {
struct hidpp_battery battery;
struct hidpp_scroll_counter vertical_wheel_counter;
+
+ u16 *features;
+ u8 feature_count;
};
/* HID++ 1.0 error codes */
@@ -340,6 +348,11 @@ static int hidpp_send_rap_command_sync(struct hidpp_device *hidpp_dev,
struct hidpp_report *message;
int ret, max_count;
+ /* Force long reports on devices that do not support short reports */
+ if (hidpp_dev->quirks & HIDPP_QUIRK_MISSING_SHORT_REPORTS &&
+ report_id == REPORT_ID_HIDPP_SHORT)
+ report_id = REPORT_ID_HIDPP_LONG;
+
switch (report_id) {
case REPORT_ID_HIDPP_SHORT:
max_count = HIDPP_REPORT_SHORT_LENGTH - 4;
@@ -393,9 +406,22 @@ static inline bool hidpp_match_error(struct hidpp_report *question,
(answer->fap.params[0] == question->fap.funcindex_clientid);
}
-static inline bool hidpp_report_is_connect_event(struct hidpp_report *report)
+#define HIDPP_PAGE_WIRELESS_DEVICE_STATUS 0x1d4b
+
+static inline bool hidpp_report_is_connect_event(struct hidpp_device *hidpp,
+ struct hidpp_report *report)
{
- return (report->report_id == REPORT_ID_HIDPP_SHORT) &&
+ if (hidpp->quirks & HIDPP_QUIRK_WIRELESS_DEVICE_STATUS) {
+ /* If feature is invalid, skip array check */
+ if (report->fap.feature_index > hidpp->feature_count)
+ return false;
+
+ return (hidpp->features[report->fap.feature_index] ==
+ HIDPP_PAGE_WIRELESS_DEVICE_STATUS);
+ }
+
+ return ((report->report_id == REPORT_ID_HIDPP_SHORT) ||
+ (hidpp->quirks & HIDPP_QUIRK_MISSING_SHORT_REPORTS)) &&
(report->rap.sub_id == 0x41);
}
@@ -901,6 +927,84 @@ static int hidpp_root_get_protocol_version(struct hidpp_device *hidpp)
return 0;
}
+/* -------------------------------------------------------------------------- */
+/* 0x0001: FeatureSet */
+/* -------------------------------------------------------------------------- */
+
+#define HIDPP_PAGE_FEATURESET 0x0001
+
+#define CMD_FEATURESET_GET_COUNT 0x00
+#define CMD_FEATURESET_GET_FEATURE 0x11
+
+static int hidpp20_featureset_get_feature(struct hidpp_device *hidpp,
+ u8 featureset_index, u8 feature_index, u16 *feature_id)
+{
+ struct hidpp_report response;
+ int ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, featureset_index,
+ CMD_FEATURESET_GET_FEATURE, &feature_index, 1, &response);
+
+ if (ret)
+ return ret;
+
+ *feature_id = (response.fap.params[0] << 8) | response.fap.params[1];
+
+ return ret;
+}
+
+static int hidpp20_featureset_get_count(struct hidpp_device *hidpp,
+ u8 feature_index, u8 *count)
+{
+ struct hidpp_report response;
+ int ret;
+
+ ret = hidpp_send_fap_command_sync(hidpp, feature_index,
+ CMD_FEATURESET_GET_COUNT, NULL, 0, &response);
+
+ if (ret)
+ return ret;
+
+ *count = response.fap.params[0];
+
+ return ret;
+}
+
+static int hidpp20_get_features(struct hidpp_device *hidpp)
+{
+ int ret;
+ u8 featureset_index, featureset_type;
+ u8 i;
+
+ hidpp->feature_count = 0;
+
+ ret = hidpp_root_get_feature(hidpp, HIDPP_PAGE_FEATURESET,
+ &featureset_index, &featureset_type);
+
+ if (ret == -ENOENT) {
+ hid_warn(hidpp->hid_dev, "Unable to retrieve feature set.");
+ return 0;
+ }
+
+ if (ret)
+ return ret;
+
+ ret = hidpp20_featureset_get_count(hidpp, featureset_index,
+ &hidpp->feature_count);
+
+ if (ret)
+ return ret;
+
+ hidpp->features = devm_kzalloc(&hidpp->hid_dev->dev,
+ hidpp->feature_count * sizeof(u16), GFP_KERNEL);
+
+ for (i = 0; i < hidpp->feature_count && !ret; i++)
+ ret = hidpp20_featureset_get_feature(hidpp, featureset_index,
+ i, &(hidpp->features[i]));
+
+ return ret;
+}
+
/* -------------------------------------------------------------------------- */
/* 0x0005: GetDeviceNameType */
/* -------------------------------------------------------------------------- */
@@ -3068,7 +3172,7 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data,
}
}
- if (unlikely(hidpp_report_is_connect_event(report))) {
+ if (unlikely(hidpp_report_is_connect_event(hidpp, report))) {
atomic_set(&hidpp->connected,
!(report->rap.params[0] & (1 << 6)));
if (schedule_work(&hidpp->work) == 0)
@@ -3482,6 +3586,12 @@ static bool hidpp_validate_report(struct hid_device *hdev, int id,
static bool hidpp_validate_device(struct hid_device *hdev)
{
+ struct hidpp_device *hidpp = hid_get_drvdata(hdev);
+ /* Skip the short report check if the device does not support it */
+ if (hidpp->quirks & HIDPP_QUIRK_MISSING_SHORT_REPORTS)
+ return hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG,
+ HIDPP_REPORT_LONG_LENGTH, false);
+
return hidpp_validate_report(hdev, REPORT_ID_HIDPP_SHORT,
HIDPP_REPORT_SHORT_LENGTH, false) &&
hidpp_validate_report(hdev, REPORT_ID_HIDPP_LONG,
@@ -3619,6 +3729,17 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id)
goto hid_hw_init_fail;
}
+ /* Cache feature indexes and IDs to check reports faster */
+ if (hidpp->protocol_major >= 2) {
+ if (hidpp20_get_features(hidpp)) {
+ hid_err(hdev, "%s:hidpp20_get_features returned error\n",
+ __func__);
+ goto hid_hw_init_fail;
+ }
+ } else {
+ hidpp->feature_count = 0;
+ }
+
hidpp_connect_event(hidpp);
/* Reset the HID node state */
@@ -3773,6 +3894,31 @@ static const struct hid_device_id hidpp_devices[] = {
{ /* MX5500 keyboard over Bluetooth */
HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb30b),
.driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS },
+ { /* MX Anywhere 2 mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb013),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb018),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { /* MX Anywhere 2S mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01a),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { /* MX Master mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb012),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb017),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb01e),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
+ { /* MX Master 2S mouse over Bluetooth */
+ HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb019),
+ .driver_data = HIDPP_QUIRK_HI_RES_SCROLL_X2121 |
+ HIDPP_QUIRK_CLASS_BLUETOOTH },
{}
};