From 42bc4f3129e6565567ee63116850c530dc561aa0 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sat, 20 Apr 2019 13:22:17 +0200 Subject: [PATCH] HID: logitech-hidpp: add support for HID++ 1.0 consumer keys reports All Logitech 27 MHz keyboards and also the MX5000 bluetooth keyboard use Logitech custom usages of 0x10xx in the consumer page. The descriptor for the consumer input-report only declares usages up to 652, so we end up dropping all the input-reports reporting 0x10xx usages without reporting events for these to userspace. This commit adds a descriptor_fixup function for this which changes the usage and logical maximum to 0x107f. Mapping these usages to something other then KEY_UNKNOWN is left to userspace (hwdb). Note: 1. The old descriptor_fixup for this in hid-lg.c used a maximimum of 0x104d this is not high enough, the S520 keyboard battery key sends 0x106f. 2. The descriptor_fixup is flexible so that it works with both the kbd- desc. passed by the logitech-dj code and with bluetooth descriptors. The descriptor_fixup makes most keys work on 27 MHz keyboards, but it is not enough to get all keys to work on 27 MHz keyboards and just the fixup is not enough to get the MX5000 to generate 0x10xx events: 1) The LX501 and MX3000 27 MHz kbds both have a button labelled "media" (called "Media Player" by SetPoint) and a button with a remote-control symbol ("Media Life" in SetPoint) which both send an identical consumer usage-page code (0x0183) making the 2 buttons indistinguishable, switching to HID++ 1.0 consumer keys reports makes the remote-control symbol button generate a 0x10xx Logitech specific code instead. 2) The MX5000 Bluetooth keyboard has 11 keys which report 0x10xx consumer page usages, but unlike 27 MHz devices which happily send 0x10xx codes in their normal consumer-page input-report, the MX5000 honors the maximum of 652 from its descriptor and sends a 0x0000 code (so release) whenever these keys are pressed. When switching to HID++ sub-id 0x03 HID++ 1.0 consumer keys reports these 0x10xx codes do get properly reported. This commit adds support for HID++ 1.0 consumer keys reports and enables this for all 27 MHz keyboards and for the MX5000. Signed-off-by: Hans de Goede Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-logitech-hidpp.c | 133 ++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 12 deletions(-) diff --git a/drivers/hid/hid-logitech-hidpp.c b/drivers/hid/hid-logitech-hidpp.c index 215c3a256c27..72fc9c0566db 100644 --- a/drivers/hid/hid-logitech-hidpp.c +++ b/drivers/hid/hid-logitech-hidpp.c @@ -53,6 +53,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_REPORT_LONG_LENGTH 20 #define HIDPP_REPORT_VERY_LONG_MAX_LENGTH 64 +#define HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS 0x03 #define HIDPP_SUB_ID_ROLLER 0x05 #define HIDPP_SUB_ID_MOUSE_EXTRA_BTNS 0x06 @@ -73,6 +74,7 @@ MODULE_PARM_DESC(disable_tap_to_click, #define HIDPP_QUIRK_HI_RES_SCROLL_X2121 BIT(28) #define HIDPP_QUIRK_HIDPP_WHEELS BIT(29) #define HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS BIT(30) +#define HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS BIT(31) /* These are just aliases for now */ #define HIDPP_QUIRK_KBD_SCROLL_WHEEL HIDPP_QUIRK_HIDPP_WHEELS @@ -2851,6 +2853,71 @@ static void hidpp10_extra_mouse_buttons_populate_input( __set_bit(BTN_7, input_dev->keybit); } +/* -------------------------------------------------------------------------- */ +/* HID++1.0 kbds which only report 0x10xx consumer usages through sub-id 0x03 */ +/* -------------------------------------------------------------------------- */ + +/* Find the consumer-page input report desc and change Maximums to 0x107f */ +static u8 *hidpp10_consumer_keys_report_fixup(struct hidpp_device *hidpp, + u8 *_rdesc, unsigned int *rsize) +{ + /* Note 0 terminated so we can use strnstr to search for this. */ + const char consumer_rdesc_start[] = { + 0x05, 0x0C, /* USAGE_PAGE (Consumer Devices) */ + 0x09, 0x01, /* USAGE (Consumer Control) */ + 0xA1, 0x01, /* COLLECTION (Application) */ + 0x85, 0x03, /* REPORT_ID = 3 */ + 0x75, 0x10, /* REPORT_SIZE (16) */ + 0x95, 0x02, /* REPORT_COUNT (2) */ + 0x15, 0x01, /* LOGICAL_MIN (1) */ + 0x26, 0x00 /* LOGICAL_MAX (... */ + }; + char *consumer_rdesc, *rdesc = (char *)_rdesc; + unsigned int size; + + consumer_rdesc = strnstr(rdesc, consumer_rdesc_start, *rsize); + size = *rsize - (consumer_rdesc - rdesc); + if (consumer_rdesc && size >= 25) { + consumer_rdesc[15] = 0x7f; + consumer_rdesc[16] = 0x10; + consumer_rdesc[20] = 0x7f; + consumer_rdesc[21] = 0x10; + } + return _rdesc; +} + +static int hidpp10_consumer_keys_connect(struct hidpp_device *hidpp) +{ + return hidpp10_set_register(hidpp, HIDPP_REG_ENABLE_REPORTS, 0, + HIDPP_ENABLE_CONSUMER_REPORT, + HIDPP_ENABLE_CONSUMER_REPORT); +} + +static int hidpp10_consumer_keys_raw_event(struct hidpp_device *hidpp, + u8 *data, int size) +{ + u8 consumer_report[5]; + + if (size < 7) + return 0; + + if (data[0] != REPORT_ID_HIDPP_SHORT || + data[2] != HIDPP_SUB_ID_CONSUMER_VENDOR_KEYS) + return 0; + + /* + * Build a normal consumer report (3) out of the data, this detour + * is necessary to get some keyboards to report their 0x10xx usages. + */ + consumer_report[0] = 0x03; + memcpy(&consumer_report[1], &data[3], 4); + /* We are called from atomic context */ + hid_report_raw_event(hidpp->hid_dev, HID_INPUT_REPORT, + consumer_report, 5, 1); + + return 1; +} + /* -------------------------------------------------------------------------- */ /* High-resolution scroll wheels */ /* -------------------------------------------------------------------------- */ @@ -2886,6 +2953,22 @@ static int hi_res_scroll_enable(struct hidpp_device *hidpp) /* Generic HID++ devices */ /* -------------------------------------------------------------------------- */ +static u8 *hidpp_report_fixup(struct hid_device *hdev, u8 *rdesc, + unsigned int *rsize) +{ + struct hidpp_device *hidpp = hid_get_drvdata(hdev); + + if (!hidpp) + return rdesc; + + /* For 27 MHz keyboards the quirk gets set after hid_parse. */ + if (hdev->group == HID_GROUP_LOGITECH_27MHZ_DEVICE || + (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS)) + rdesc = hidpp10_consumer_keys_report_fixup(hidpp, rdesc, rsize); + + return rdesc; +} + static int hidpp_input_mapping(struct hid_device *hdev, struct hid_input *hi, struct hid_field *field, struct hid_usage *usage, unsigned long **bit, int *max) @@ -3024,6 +3107,12 @@ static int hidpp_raw_hidpp_event(struct hidpp_device *hidpp, u8 *data, return ret; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_raw_event(hidpp, data, size); + if (ret != 0) + return ret; + } + return 0; } @@ -3283,6 +3372,12 @@ static void hidpp_connect_event(struct hidpp_device *hidpp) return; } + if (hidpp->quirks & HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS) { + ret = hidpp10_consumer_keys_connect(hidpp); + if (ret) + return; + } + /* the device is already connected, we can ask for its name and * protocol */ if (!hidpp->protocol_major) { @@ -3415,6 +3510,16 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) bool connected; unsigned int connect_mask = HID_CONNECT_DEFAULT; + /* report_fixup needs drvdata to be set before we call hid_parse */ + hidpp = devm_kzalloc(&hdev->dev, sizeof(*hidpp), GFP_KERNEL); + if (!hidpp) + return -ENOMEM; + + hidpp->hid_dev = hdev; + hidpp->name = hdev->name; + hidpp->quirks = id->driver_data; + hid_set_drvdata(hdev, hidpp); + ret = hid_parse(hdev); if (ret) { hid_err(hdev, "%s:parse failed\n", __func__); @@ -3424,19 +3529,11 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) /* * Make sure the device is HID++ capable, otherwise treat as generic HID */ - if (!hidpp_validate_device(hdev)) + if (!hidpp_validate_device(hdev)) { + hid_set_drvdata(hdev, NULL); + devm_kfree(&hdev->dev, hidpp); return hid_hw_start(hdev, HID_CONNECT_DEFAULT); - - hidpp = devm_kzalloc(&hdev->dev, sizeof(struct hidpp_device), - GFP_KERNEL); - if (!hidpp) - return -ENOMEM; - - hidpp->hid_dev = hdev; - hidpp->name = hdev->name; - hid_set_drvdata(hdev, hidpp); - - hidpp->quirks = id->driver_data; + } hidpp->very_long_report_length = hidpp_get_report_length(hdev, REPORT_ID_HIDPP_VERY_LONG); @@ -3451,6 +3548,10 @@ static int hidpp_probe(struct hid_device *hdev, const struct hid_device_id *id) hidpp->quirks |= HIDPP_QUIRK_HIDPP_WHEELS | HIDPP_QUIRK_HIDPP_EXTRA_MOUSE_BTNS; + if (id->group == HID_GROUP_LOGITECH_27MHZ_DEVICE && + hidpp_application_equals(hdev, HID_GD_KEYBOARD)) + hidpp->quirks |= HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS; + if (disable_raw_mode) { hidpp->quirks &= ~HIDPP_QUIRK_CLASS_WTP; hidpp->quirks &= ~HIDPP_QUIRK_NO_HIDINPUT; @@ -3628,6 +3729,9 @@ static const struct hid_device_id hidpp_devices[] = { { /* Solar Keyboard Logitech K750 */ LDJ_DEVICE(0x4002), .driver_data = HIDPP_QUIRK_CLASS_K750 }, + { /* Keyboard MX5000 (Bluetooth-receiver in HID proxy mode) */ + LDJ_DEVICE(0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, { LDJ_DEVICE(HID_ANY_ID) }, @@ -3652,6 +3756,10 @@ static const struct hid_device_id hidpp_devices[] = { { /* Logitech G920 Wheel over USB */ HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_LOGITECH_G920_WHEEL), .driver_data = HIDPP_QUIRK_CLASS_G920 | HIDPP_QUIRK_FORCE_OUTPUT_REPORTS}, + + { /* MX5000 keyboard over Bluetooth */ + HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH, 0xb305), + .driver_data = HIDPP_QUIRK_HIDPP_CONSUMER_VENDOR_KEYS }, {} }; @@ -3665,6 +3773,7 @@ static const struct hid_usage_id hidpp_usages[] = { static struct hid_driver hidpp_driver = { .name = "logitech-hidpp-device", .id_table = hidpp_devices, + .report_fixup = hidpp_report_fixup, .probe = hidpp_probe, .remove = hidpp_remove, .raw_event = hidpp_raw_event, -- 2.20.1