From patchwork Mon Jul 9 18:05:20 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Daniel M. Lambea" X-Patchwork-Id: 10515481 Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 21D826032A for ; Mon, 9 Jul 2018 18:05:46 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 12DD628D80 for ; Mon, 9 Jul 2018 18:05:46 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 0692228DCC; Mon, 9 Jul 2018 18:05:46 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.8 required=2.0 tests=BAYES_00, DKIM_ADSP_CUSTOM_MED, DKIM_SIGNED, FREEMAIL_FROM, MAILING_LIST_MULTI, RCVD_IN_DNSWL_HI, T_DKIM_INVALID autolearn=ham version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1021128D80 for ; Mon, 9 Jul 2018 18:05:45 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933798AbeGISFo (ORCPT ); Mon, 9 Jul 2018 14:05:44 -0400 Received: from mail-vk0-f53.google.com ([209.85.213.53]:35641 "EHLO mail-vk0-f53.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933703AbeGISFl (ORCPT ); Mon, 9 Jul 2018 14:05:41 -0400 Received: by mail-vk0-f53.google.com with SMTP id o202-v6so10945888vko.2 for ; Mon, 09 Jul 2018 11:05:41 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:from:date:message-id:subject:to:cc; bh=pckhRfZ8G2l9HVoqTUKx6cYI58hndIGYIoLwNnq8mVY=; b=HFgKf4uySlejWYb2pivu9j4MUUJVUNjGLxfOPonnZ5uFfti6OlKv9F4KReld1Ov0BK ezrYXRkdI7JwPrNOT/6TzLOAMvcV6XEs3hK2KwLj/XqbxXb7lOFb/MnpA4pYrrh5Ewh7 MLoXC+MiJ158eX2MzLbx2/YF537Hli6IqqMmUScUsRnmA3q3jmXM147CamXcAfKf8gGy 6yWb7zWPjhuraicnOUxFBMZfnFtKkYuKXM8088ZXunHAF5iMAesUjVfgP9gAWjqiUbS7 jGORDqNWWslScCZGclavy1ruok0YBUkwiJICxaYAI8Wyd/X4LUgydtogdxHbYLn+ImXv F9Fg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:from:date:message-id:subject:to:cc; bh=pckhRfZ8G2l9HVoqTUKx6cYI58hndIGYIoLwNnq8mVY=; b=dajduFmZdOKQiqThymMk9iS6fDVg/aW1bnZv7Y4+6oaElYP4xg+iGa6Ea890JJ6qSP IWLG+I/v6FhQkCNJAZx2X9OjYeB5QXUHwyx6BzFK7DfZVQBW7LbYKehuBxMAsEcUzyrq DLIP9WJ0HQo8bICVB/8Lvj+RJa0PWyWWTzUQCl6j+i8KLkaRKflmYCduVPizbpyIpMF8 +ZsqCvj7F/dJkXj+gHjXOnIGdRgnDrhujhPnqg0O2eyyEgiUmEoT+rvtxsosDeZfxUJZ rRBfJSK7tK6rlx6AgnqUKFPb7uTLaiLdN/TUm1hAlhVlrPQ0MYfebjD5IDRae+MU1YtU 765w== X-Gm-Message-State: APt69E2xT5Ca/MaRODL5GzXj3eJjYE6KmsZpcbCueUMvsAexgNNpt1c8 wfIX3RFUqf+ed4YCw7qjBa5vXhZ2ZiSx19C82hM= X-Google-Smtp-Source: AAOMgpf5tSFvK05YOj8JNIcDMT+VJPBxEzE+txYeXqpD59lqTH1i7fhFV7l4UzYqOt3kS7zY5JWmRTHYGftzuR21GS8= X-Received: by 2002:a1f:3db:: with SMTP id f88-v6mr12150408vki.14.1531159540589; Mon, 09 Jul 2018 11:05:40 -0700 (PDT) MIME-Version: 1.0 Received: by 2002:a67:cb14:0:0:0:0:0 with HTTP; Mon, 9 Jul 2018 11:05:20 -0700 (PDT) From: "Daniel M. Lambea" Date: Mon, 9 Jul 2018 19:05:20 +0100 Message-ID: Subject: [PATCH] HID: cougar: Add support for the Cougar 500k Gaming Keyboard To: Jiri Kosina Cc: Benjamin Tissoires , linux-input@vger.kernel.org Sender: linux-input-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-input@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP 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 --- 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 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 + */ + +/* + * 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 +#include +#include + +#include "hid-ids.h" + +MODULE_AUTHOR("Daniel M. Lambea "); +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