[PATCH] HID: cougar: Add support for the Cougar 500k Gaming Keyboard

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Cougar 500k Gaming Keyboard have some special function keys that
make the keyboard stop responding when pressed. Implement the custom
vendor interface that deals with the extended keypresses to fix.

The bug can be reproduced by plugging in the keyboard, then pressing the
rightmost part of the spacebar.

Signed-off-by: Daniel M. Lambea <dmlambea@xxxxxxxxx>
---

One of those special function keys is just the right-half part of the
spacebar, so the chance of hitting it is very high. This is very annoying to the
user, since the only way of recovering the device back is to unplug it
and to plug
it back.

The code, built as a DKMS module, has been released and tested by
others. For more
information, please refer to the bug:
https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1511511

diff -uprN -X hid-vanilla/Documentation/dontdiff
hid-vanilla/drivers/hid/hid-cougar.c hid/drivers/hid/hid-cougar.c
--- hid-vanilla/drivers/hid/hid-cougar.c    1970-01-01 00:00:00.000000000 +0000
+++ hid/drivers/hid/hid-cougar.c    2018-07-09 18:42:42.187292906 +0100
@@ -0,0 +1,313 @@
+/*
+ *  HID driver for Cougar 500k Gaming Keyboard
+ *
+ *  Copyright (c) 2018 Daniel M. Lambea <dmlambea@xxxxxxxxx>
+ */
+
+/*
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ */
+
+#include <linux/hid.h>
+#include <linux/module.h>
+#include <linux/usb.h>
+
+#include "hid-ids.h"
+
+MODULE_AUTHOR("Daniel M. Lambea <dmlambea@xxxxxxxxx>");
+MODULE_DESCRIPTION("Cougar 500k Gaming Keyboard");
+MODULE_LICENSE("GPL");
+MODULE_INFO(key_mappings, "G1-G6 are mapped to F13-F18");
+
+static int cougar_g6_is_space = 1;
+module_param_named(g6_is_space, cougar_g6_is_space, int, 0600);
+MODULE_PARM_DESC(g6_is_space,
+    "If set, G6 programmable key sends SPACE instead of F18 (0=off,
1=on) (default=1)");
+
+
+#define COUGAR_KEYB_INTFNO        0
+#define COUGAR_MOUSE_INTFNO        1
+#define COUGAR_RESERVED_INTFNO        2
+
+#define COUGAR_RESERVED_FLD_CODE    1
+#define COUGAR_RESERVED_FLD_ACTION    2
+
+#define COUGAR_KEY_G1            0x83
+#define COUGAR_KEY_G2            0x84
+#define COUGAR_KEY_G3            0x85
+#define COUGAR_KEY_G4            0x86
+#define COUGAR_KEY_G5            0x87
+#define COUGAR_KEY_G6            0x78
+#define COUGAR_KEY_FN            0x0d
+#define COUGAR_KEY_MR            0x6f
+#define COUGAR_KEY_M1            0x80
+#define COUGAR_KEY_M2            0x81
+#define COUGAR_KEY_M3            0x82
+#define COUGAR_KEY_LEDS            0x67
+#define COUGAR_KEY_LOCK            0x6e
+
+
+/* Default key mappings */
+static unsigned char cougar_mapping[][2] = {
+    { COUGAR_KEY_G1,   KEY_F13 },
+    { COUGAR_KEY_G2,   KEY_F14 },
+    { COUGAR_KEY_G3,   KEY_F15 },
+    { COUGAR_KEY_G4,   KEY_F16 },
+    { COUGAR_KEY_G5,   KEY_F17 },
+    { COUGAR_KEY_G6,   KEY_F18 },    // This is handled individually
+    { COUGAR_KEY_LOCK, KEY_SCREENLOCK },
+    { 0, 0 },
+};
+
+struct cougar_kbd_data {
+    struct hid_device *owner;
+    struct input_dev  *input;
+};
+
+/*
+ * Constant-friendly rdesc fixup for mouse interface
+ */
+static __u8 *cougar_report_fixup(struct hid_device *hdev, __u8 *rdesc,
+                 unsigned int *rsize)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+
+    if (intf->cur_altsetting->desc.bInterfaceNumber == COUGAR_MOUSE_INTFNO &&
+        (rdesc[0x73] | rdesc[0x74] << 8) >= HID_MAX_USAGES) {
+        hid_info(hdev, "usage count exceeds max: fixing up report descriptor");
+        rdesc[0x73] = ((HID_MAX_USAGES-1) & 0xff);
+        rdesc[0x74] = ((HID_MAX_USAGES-1) >> 8);
+    }
+    return rdesc;
+}
+
+/*
+ * Returns a sibling hid_device with the given intf number
+ */
+static struct hid_device *cougar_get_sibling_hid_device(struct
hid_device *hdev,
+                            const __u8 intfno)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct usb_device *device;
+    struct usb_host_config *hostcfg;
+    struct hid_device *siblingHdev;
+    int i;
+
+    if (intf->cur_altsetting->desc.bInterfaceNumber == intfno)
+        hid_err(hdev, "returning hid device's data from myself?");
+
+    device = interface_to_usbdev(intf);
+    hostcfg = device->config;
+    siblingHdev = NULL;
+    for (i = 0; i < hostcfg->desc.bNumInterfaces; i++) {
+        if (i == intfno)
+            return usb_get_intfdata(hostcfg->interface[i]);
+    }
+    return NULL;
+}
+
+static int cougar_set_drvdata_from_keyb_interface(struct hid_device *hdev)
+{
+    struct hid_device *sibling;
+    struct cougar_kbd_data *kbd;
+
+    /* Search for the keyboard */
+    sibling = cougar_get_sibling_hid_device(hdev, COUGAR_KEYB_INTFNO);
+    if (sibling == NULL) {
+        hid_err(hdev,
+            "no sibling hid device found for intf %d", COUGAR_KEYB_INTFNO);
+        return -ENODEV;
+    }
+
+    kbd = hid_get_drvdata(sibling);
+    if (kbd == NULL || kbd->input == NULL) {
+        hid_err(hdev,
+            "keyboard descriptor not found in intf %d", COUGAR_KEYB_INTFNO);
+        return -ENODATA;
+    }
+
+    /* And save its data on reserved, custom vendor intf. device */
+    hid_set_drvdata(hdev, kbd);
+    return 0;
+}
+
+/*
+ * The probe will save the keyb's input device, so that the
+ * vendor intf will be able to send keys as if it were the
+ * keyboard itself.
+ */
+static int cougar_probe(struct hid_device *hdev,
+            const struct hid_device_id *id)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct cougar_kbd_data *kbd = NULL;
+    unsigned int mask;
+    int ret;
+    __u8 intfno;
+
+    intfno = intf->cur_altsetting->desc.bInterfaceNumber;
+    hid_dbg(hdev, "about to probe intf %d", intfno);
+
+    if (intfno == COUGAR_KEYB_INTFNO) {
+        kbd = devm_kzalloc(&hdev->dev, sizeof(*kbd), GFP_KERNEL);
+        if (kbd == NULL)
+            return -ENOMEM;
+
+        hid_dbg(hdev, "attaching kbd data to intf %d", intfno);
+        hid_set_drvdata(hdev, kbd);
+    }
+
+    /* Parse and start hw */
+    ret = hid_parse(hdev);
+    if (ret)
+        goto err_cleanup;
+
+    /* Reserved, custom vendor interface connects hidraw only */
+    mask = intfno == COUGAR_RESERVED_INTFNO ?
+                HID_CONNECT_HIDRAW : HID_CONNECT_DEFAULT;
+    ret = hid_hw_start(hdev, mask);
+    if (ret)
+        goto err_cleanup;
+
+    if (intfno == COUGAR_RESERVED_INTFNO) {
+        ret = cougar_set_drvdata_from_keyb_interface(hdev);
+        if (ret)
+            goto err_stop_and_cleanup;
+
+        hid_dbg(hdev, "keyboard descriptor attached to intf %d", intfno);
+
+        ret = hid_hw_open(hdev);
+        if (ret)
+            goto err_stop_and_cleanup;
+    }
+    hid_dbg(hdev, "intf %d probed successfully", intfno);
+
+    return 0;
+
+err_stop_and_cleanup:
+    hid_hw_stop(hdev);
+err_cleanup:
+    hid_set_drvdata(hdev, NULL);
+    if (kbd != NULL && kbd->owner == hdev)
+        devm_kfree(&hdev->dev, kbd);
+    return ret;
+}
+
+/*
+ * Keeps track of the keyboard's hid_input
+ */
+static int cougar_input_configured(struct hid_device *hdev, struct
hid_input *hidinput)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    __u8 intfno = intf->cur_altsetting->desc.bInterfaceNumber;
+    struct cougar_kbd_data *kbd;
+
+    if (intfno != COUGAR_KEYB_INTFNO) {
+        /* Skip interfaces other than COUGAR_KEYB_INTFNO,
+         * which is the one containing the configured hidinput
+         */
+        hid_dbg(hdev, "input_configured: skipping intf %d", intfno);
+        return 0;
+    }
+    kbd = hid_get_drvdata(hdev);
+    kbd->owner = hdev;
+    kbd->input = hidinput->input;
+    hid_dbg(hdev, "input_configured: intf %d configured", intfno);
+    return 0;
+}
+
+/*
+ * Converts events from vendor intf to input key events
+ */
+static int cougar_raw_event(struct hid_device *hdev, struct hid_report *report,
+                u8 *data, int size)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct cougar_kbd_data *kbd;
+    unsigned char action, code, transcode;
+    int i;
+
+    if (intf->cur_altsetting->desc.bInterfaceNumber != COUGAR_RESERVED_INTFNO)
+        return 0;
+
+    // Enable this to see a dump of the data received from reserved interface
+    //print_hex_dump(KERN_ERR, DRIVER_NAME " data : ",
DUMP_PREFIX_OFFSET, 8, 1, data, size, 0);
+
+    code = data[COUGAR_RESERVED_FLD_CODE];
+    switch (code) {
+    case COUGAR_KEY_FN:
+        hid_dbg(hdev, "FN (special function) key is handled by the
hardware itself");
+        break;
+    case COUGAR_KEY_MR:
+        hid_dbg(hdev, "MR (macro recording) key is handled by the
hardware itself");
+        break;
+    case COUGAR_KEY_M1:
+        hid_dbg(hdev, "M1 (macro set 1) key is handled by the
hardware itself");
+        break;
+    case COUGAR_KEY_M2:
+        hid_dbg(hdev, "M2 (macro set 2) key is handled by the
hardware itself");
+        break;
+    case COUGAR_KEY_M3:
+        hid_dbg(hdev, "M3 (macro set 3) key is handled by the
hardware itself");
+        break;
+    case COUGAR_KEY_LEDS:
+        hid_dbg(hdev, "LED (led set) key is handled by the hardware itself");
+        break;
+    default:
+        /* Try normal key mappings */
+        for (i = 0; cougar_mapping[i][0]; i++) {
+            if (cougar_mapping[i][0] == code) {
+                action = data[COUGAR_RESERVED_FLD_ACTION];
+                hid_dbg(hdev, "found mapping for code %x", code);
+                if (code == COUGAR_KEY_G6 && cougar_g6_is_space)
+                    transcode = KEY_SPACE;
+                else
+                    transcode = cougar_mapping[i][1];
+
+                kbd = hid_get_drvdata(hdev);
+                input_event(kbd->input, EV_KEY, transcode, action);
+                input_sync(kbd->input);
+                hid_dbg(hdev, "translated to %x", transcode);
+                return 0;
+            }
+        }
+        hid_warn(hdev, "unmapped key code %d: ignoring", code);
+    }
+    return 0;
+}
+
+static void cougar_remove(struct hid_device *hdev)
+{
+    struct usb_interface *intf = to_usb_interface(hdev->dev.parent);
+    struct cougar_kbd_data *kbd = hid_get_drvdata(hdev);
+
+    hid_dbg(hdev, "removing %d", intf->cur_altsetting->desc.bInterfaceNumber);
+    if (intf->cur_altsetting->desc.bInterfaceNumber == COUGAR_RESERVED_INTFNO)
+        hid_hw_close(hdev);
+
+    hid_hw_stop(hdev);
+    hid_set_drvdata(hdev, NULL);
+    if (kbd != NULL && kbd->owner == hdev)
+        devm_kfree(&hdev->dev, kbd);
+}
+
+static struct hid_device_id cougar_id_table[] = {
+    { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) },
+    {}
+};
+MODULE_DEVICE_TABLE(hid, cougar_id_table);
+
+static struct hid_driver cougar_driver = {
+    .name            = "cougar",
+    .id_table        = cougar_id_table,
+    .report_fixup        = cougar_report_fixup,
+    .probe            = cougar_probe,
+    .input_configured    = cougar_input_configured,
+    .remove            = cougar_remove,
+    .raw_event        = cougar_raw_event,
+};
+
+module_hid_driver(cougar_driver);
diff -uprN -X hid-vanilla/Documentation/dontdiff
hid-vanilla/drivers/hid/hid-ids.h hid/drivers/hid/hid-ids.h
--- hid-vanilla/drivers/hid/hid-ids.h    2018-07-09 17:48:30.189316299 +0100
+++ hid/drivers/hid/hid-ids.h    2018-07-09 17:54:29.332832182 +0100
@@ -1001,6 +1001,9 @@
 #define USB_VENDOR_ID_SINO_LITE            0x1345
 #define USB_DEVICE_ID_SINO_LITE_CONTROLLER    0x3008

+#define USB_VENDOR_ID_SOLID_YEAR            0x060b
+#define USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD    0x500a
+
 #define USB_VENDOR_ID_SOUNDGRAPH    0x15c2
 #define USB_DEVICE_ID_SOUNDGRAPH_IMON_FIRST    0x0034
 #define USB_DEVICE_ID_SOUNDGRAPH_IMON_LAST    0x0046
diff -uprN -X hid-vanilla/Documentation/dontdiff
hid-vanilla/drivers/hid/hid-quirks.c hid/drivers/hid/hid-quirks.c
--- hid-vanilla/drivers/hid/hid-quirks.c    2018-07-09 17:48:30.193316294 +0100
+++ hid/drivers/hid/hid-quirks.c    2018-07-09 17:54:42.708814231 +0100
@@ -610,6 +610,9 @@ static const struct hid_device_id hid_ha
     { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD,
USB_DEVICE_ID_SUPER_DUAL_BOX_PRO) },
     { HID_USB_DEVICE(USB_VENDOR_ID_WISEGROUP_LTD,
USB_DEVICE_ID_SUPER_JOY_BOX_5_PRO) },
 #endif
+#if IS_ENABLED(CONFIG_HID_COUGAR)
+    { HID_USB_DEVICE(USB_VENDOR_ID_SOLID_YEAR,
USB_DEVICE_ID_COUGAR_500K_GAMING_KEYBOARD) },
+#endif
 #if IS_ENABLED(CONFIG_HID_SONY)
     { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_LOGITECH,
USB_DEVICE_ID_LOGITECH_HARMONY_PS3) },
     { HID_BLUETOOTH_DEVICE(USB_VENDOR_ID_SMK,
USB_DEVICE_ID_SMK_PS3_BDREMOTE) },
diff -uprN -X hid-vanilla/Documentation/dontdiff
hid-vanilla/drivers/hid/Kconfig hid/drivers/hid/Kconfig
--- hid-vanilla/drivers/hid/Kconfig    2018-07-09 17:48:30.185316305 +0100
+++ hid/drivers/hid/Kconfig    2018-07-09 18:46:10.075657150 +0100
@@ -207,6 +207,16 @@ config HID_CORSAIR
     - Vengeance K90
     - Scimitar PRO RGB

+config HID_COUGAR
+    tristate "Cougar devices"
+    depends on HID
+    help
+    Support for Cougar devices that are not fully compliant with the
+    HID standard.
+
+    Supported devices:
+    - Cougar 500k Gaming Keyboard
+
 config HID_PRODIKEYS
     tristate "Prodikeys PC-MIDI Keyboard support"
     depends on HID && SND
diff -uprN -X hid-vanilla/Documentation/dontdiff
hid-vanilla/drivers/hid/Makefile hid/drivers/hid/Makefile
--- hid-vanilla/drivers/hid/Makefile    2018-07-09 17:48:30.185316305 +0100
+++ hid/drivers/hid/Makefile    2018-07-09 17:54:15.140851231 +0100
@@ -35,6 +35,7 @@ obj-$(CONFIG_HID_CHERRY)    += hid-cherry.o
 obj-$(CONFIG_HID_CHICONY)    += hid-chicony.o
 obj-$(CONFIG_HID_CMEDIA)    += hid-cmedia.o
 obj-$(CONFIG_HID_CORSAIR)    += hid-corsair.o
+obj-$(CONFIG_HID_COUGAR)    += hid-cougar.o
 obj-$(CONFIG_HID_CP2112)    += hid-cp2112.o
 obj-$(CONFIG_HID_CYPRESS)    += hid-cypress.o
 obj-$(CONFIG_HID_DRAGONRISE)    += hid-dr.o
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html



[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux