diff mbox

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

Message ID CAABJi+M-_hWdc6L-W3k8kA06Zd3tjAEb06LhsHOcwtzyjRRYbA@mail.gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel M. Lambea July 9, 2018, 6:05 p.m. UTC
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@gmail.com>
---

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

--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Comments

Benjamin Tissoires July 10, 2018, 4:16 p.m. UTC | #1
Hi Daniel,

On Mon, Jul 9, 2018 at 8:05 PM Daniel M. Lambea <dmlambea@gmail.com> wrote:
>
> 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@gmail.com>
> ---
>
> 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

Thanks for your contribution. I have a few comments I'll inline below.

>
> 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@gmail.com>
> + */
> +
> +/*
> + * 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.
> + */

The new and preferred way of adding the GPLv2+ license is by using SPDX:
// SPDX-License-Identifier: GPL-2.0+

> +
> +#include <linux/hid.h>
> +#include <linux/module.h>
> +#include <linux/usb.h>

Generally, I really dislike when a HID driver needs to access usb.h.
This basically kills replaying the devices through uhid, and is
usually not future-proof. I usually recommend to detect which
interface we are based on the report descriptors.

> +
> +#include "hid-ids.h"
> +
> +MODULE_AUTHOR("Daniel M. Lambea <dmlambea@gmail.com>");
> +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);

As mentioned, I'd rather see the fixup decided with the actual content
of the rdesc, not the USB interface.

> +
> +    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)

I am not a big fan of the siblings here. I think you want it for
rerouting the events from the proprietary HID node to the keyboard
one.

Also, the data you are sharing is not 'correct'. You should not share
a data bound to a specific hid device, but a shared one that is
refcounted and that will have its own life span.

For an example of that, see wacom_sys.c in drivers/hid/.

> +{
> +    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);

Not sure we need these debug messages.

> +        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 you are using the devm API, using 'goto' is usually a hint that
something went wrong...

> +
> +    if (intfno == COUGAR_RESERVED_INTFNO) {
> +        ret = cougar_set_drvdata_from_keyb_interface(hdev);

This should probably happen before hid_hw_start(), as you might have a
race between an incoming report and the fetching of the sibling device
input node.

> +        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);

this one is fine though (but unusual)

> +err_cleanup:
> +    hid_set_drvdata(hdev, NULL);

This is nice to do, but not really important. A driver that has its
.probe() called should reset it if it needs it.

> +    if (kbd != NULL && kbd->owner == hdev)
> +        devm_kfree(&hdev->dev, kbd);

The point of using the devm (dev managed) API is to not have to call
kfree. This can be removed without a blink.

> +    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;

This should probably be set in .probe()

> +    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;

I'd rather have you store a flag in kbd to know which interface you
are in instead of relying on USB each time.

> +
> +    // Enable this to see a dump of the data received from reserved interface
> +    //print_hex_dump(KERN_ERR, DRIVER_NAME " data : ",

No C++ style comments, please.

> DUMP_PREFIX_OFFSET, 8, 1, data, size, 0);

Your email client seems to have introduce line breaks here, you need
to fix it so you don't have to do manual processing of the patches.

> +
> +    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");

What is the point of all of these hid_dbg messages?

> +        break;
> +    default:
> +        /* Try normal key mappings */
> +        for (i = 0; cougar_mapping[i][0]; i++) {

Here you have a mapping table, so I wonder if you better not have a
IGNORE flag in your mapping table and include the previous checks in
the mapping table so you only have one loop instead of a switch +
loop.

> +            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)

If you have an explicit test for it that will be run for all of the
inpus, I wonder if you should not put the test at the beginning.

> +                    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);

Again, I'd rather have this info stored in the driverdata instead of
relying on the usb stack.

> +
> +    hid_hw_stop(hdev);
> +    hid_set_drvdata(hdev, NULL);
> +    if (kbd != NULL && kbd->owner == hdev)
> +        devm_kfree(&hdev->dev, kbd);

Same comments than in probe().

One more applies here:
you now have a race between the removal of the keyboard device and the
incoming events from the proprietary collection:
- you call remove of the HID devices, only the keyboard one is being processed
- an IRQ comes in, .raw_event is called for the proprietary interface
- .raw_events tries to access the drvdata from the keyboard interface
that has been removed -> kernel oops

I am not entirely sure if USB will allow that or not, but I *think*
this can be triggered by rmmod-ing your driver.

> +}
> +
> +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

This can be dropped as of v4.16, and I strongly encourage you to drop
it. This way, hid-generic can bind to your keyboard during the
initramfs phase, and when teh full system will be set up, hid-cougar
will take over. This is useful for LUKS passwords.

>  #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

Cheers,
Benjamin
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Daniel M. Lambea July 11, 2018, 5:05 p.m. UTC | #2
Hi, Benjamin

Thank you very much for your detailed review. I appreciate it very
much since this is my very first attempt of writing something related
to the Linux kernel and your comments and guidelines are very valuable
to me.

I will address all the issues you've spotted and send you back a new
patch. Regarding the sibling interface, I find the examples at
wacom_sys.c very illustrative.

Regards,
Daniel M. Lambea

On Tue, Jul 10, 2018 at 5:16 PM, Benjamin Tissoires
<benjamin.tissoires@redhat.com> wrote:
> Hi Daniel,
>
> On Mon, Jul 9, 2018 at 8:05 PM Daniel M. Lambea <dmlambea@gmail.com> wrote:
>>
>> 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@gmail.com>
>> ---
>>
>> 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
>
> Thanks for your contribution. I have a few comments I'll inline below.
>
>>
>> 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@gmail.com>
>> + */
>> +
>> +/*
>> + * 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.
>> + */
>
> The new and preferred way of adding the GPLv2+ license is by using SPDX:
> // SPDX-License-Identifier: GPL-2.0+
>
>> +
>> +#include <linux/hid.h>
>> +#include <linux/module.h>
>> +#include <linux/usb.h>
>
> Generally, I really dislike when a HID driver needs to access usb.h.
> This basically kills replaying the devices through uhid, and is
> usually not future-proof. I usually recommend to detect which
> interface we are based on the report descriptors.
>> +
>> +#include "hid-ids.h"
>> +
>> +MODULE_AUTHOR("Daniel M. Lambea <dmlambea@gmail.com>");
>> +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);
>
> As mentioned, I'd rather see the fixup decided with the actual content
> of the rdesc, not the USB interface.
>
>> +
>> +    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)
>
> I am not a big fan of the siblings here. I think you want it for
> rerouting the events from the proprietary HID node to the keyboard
> one.
>
> Also, the data you are sharing is not 'correct'. You should not share
> a data bound to a specific hid device, but a shared one that is
> refcounted and that will have its own life span.
>
> For an example of that, see wacom_sys.c in drivers/hid/.
>
>> +{
>> +    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);
>
> Not sure we need these debug messages.
>
>> +        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 you are using the devm API, using 'goto' is usually a hint that
> something went wrong...
>
>> +
>> +    if (intfno == COUGAR_RESERVED_INTFNO) {
>> +        ret = cougar_set_drvdata_from_keyb_interface(hdev);
>
> This should probably happen before hid_hw_start(), as you might have a
> race between an incoming report and the fetching of the sibling device
> input node.
>
>> +        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);
>
> this one is fine though (but unusual)
>
>> +err_cleanup:
>> +    hid_set_drvdata(hdev, NULL);
>
> This is nice to do, but not really important. A driver that has its
> .probe() called should reset it if it needs it.
>
>> +    if (kbd != NULL && kbd->owner == hdev)
>> +        devm_kfree(&hdev->dev, kbd);
>
> The point of using the devm (dev managed) API is to not have to call
> kfree. This can be removed without a blink.
>
>> +    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;
>
> This should probably be set in .probe()
>
>> +    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;
>
> I'd rather have you store a flag in kbd to know which interface you
> are in instead of relying on USB each time.
>
>> +
>> +    // Enable this to see a dump of the data received from reserved interface
>> +    //print_hex_dump(KERN_ERR, DRIVER_NAME " data : ",
>
> No C++ style comments, please.
>
>> DUMP_PREFIX_OFFSET, 8, 1, data, size, 0);
>
> Your email client seems to have introduce line breaks here, you need
> to fix it so you don't have to do manual processing of the patches.
>
>> +
>> +    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");
>
> What is the point of all of these hid_dbg messages?
>
>> +        break;
>> +    default:
>> +        /* Try normal key mappings */
>> +        for (i = 0; cougar_mapping[i][0]; i++) {
>
> Here you have a mapping table, so I wonder if you better not have a
> IGNORE flag in your mapping table and include the previous checks in
> the mapping table so you only have one loop instead of a switch +
> loop.
>
>> +            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)
>
> If you have an explicit test for it that will be run for all of the
> inpus, I wonder if you should not put the test at the beginning.
>
>> +                    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);
>
> Again, I'd rather have this info stored in the driverdata instead of
> relying on the usb stack.
>
>> +
>> +    hid_hw_stop(hdev);
>> +    hid_set_drvdata(hdev, NULL);
>> +    if (kbd != NULL && kbd->owner == hdev)
>> +        devm_kfree(&hdev->dev, kbd);
>
> Same comments than in probe().
>
> One more applies here:
> you now have a race between the removal of the keyboard device and the
> incoming events from the proprietary collection:
> - you call remove of the HID devices, only the keyboard one is being processed
> - an IRQ comes in, .raw_event is called for the proprietary interface
> - .raw_events tries to access the drvdata from the keyboard interface
> that has been removed -> kernel oops
>
> I am not entirely sure if USB will allow that or not, but I *think*
> this can be triggered by rmmod-ing your driver.
>
>> +}
>> +
>> +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
>
> This can be dropped as of v4.16, and I strongly encourage you to drop
> it. This way, hid-generic can bind to your keyboard during the
> initramfs phase, and when teh full system will be set up, hid-cougar
> will take over. This is useful for LUKS passwords.
>
>>  #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
>
> Cheers,
> Benjamin
--
To unsubscribe from this list: send the line "unsubscribe linux-input" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

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@gmail.com>
+ */
+
+/*
+ * 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@gmail.com>");
+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