From patchwork Sun Feb 10 10:13:50 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Nikolai Kondrashov X-Patchwork-Id: 10804675 X-Patchwork-Delegate: jikos@jikos.cz Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 81D8113B4 for ; Sun, 10 Feb 2019 10:14:58 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 6B2F229F4B for ; Sun, 10 Feb 2019 10:14:58 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 5FA4929F51; Sun, 10 Feb 2019 10:14:58 +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=-8.0 required=2.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,FREEMAIL_FROM,MAILING_LIST_MULTI,RCVD_IN_DNSWL_HI 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 95F2729F4F for ; Sun, 10 Feb 2019 10:14:55 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1726002AbfBJKOy (ORCPT ); Sun, 10 Feb 2019 05:14:54 -0500 Received: from mail-lf1-f67.google.com ([209.85.167.67]:41532 "EHLO mail-lf1-f67.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1725994AbfBJKOy (ORCPT ); Sun, 10 Feb 2019 05:14:54 -0500 Received: by mail-lf1-f67.google.com with SMTP id e27so5564653lfj.8 for ; Sun, 10 Feb 2019 02:14:49 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; bh=MfgzRySszATdedDxKzIR0eF4Jc+/b4snrbynKtmrvsA=; b=EVVbQnfvhg1R+SEWudqeEvaERR0rd6md4eYUhlJugG6Epm/qHlTkemMFZc6940zhm6 seMrbraFZ+882uZnvoJHUZ3wULGnYApYEH6EZStcwwxjv5KaU54OzanCDC5isAlKF11A cZWL+hgJGi7rXHq9k7Vnblk6O0wQ8hKDOog0OqsSD1/ieUtr3mdRav6lIGs3yinRd2cs ZWZRQCJet2U3u7A2fNmK8S+GD/iQvbfG9GriC2U1I9JOaprl+VQB1sRVsKWAJ0YceNjF JOuXFlGi/iQYZwVc2Y9uqYOw8Dx+iNlwGQlKODnyzci6pd37pXZVSd9/sE3RPFYtLo0Y X3ig== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:cc:subject:date:message-id:in-reply-to :references:mime-version:content-transfer-encoding; bh=MfgzRySszATdedDxKzIR0eF4Jc+/b4snrbynKtmrvsA=; b=HOHV17RcxwHg/SYDoiRP/x5vQBetBuz8+QwVPTX6uTDg0BBDgXN+RvQGBylVYgjSij D5WXuCVEhi7up31V8xNzgwcpfp1lFdm+Gv20gAKTJ8YzHwQiXTDcIHpTlL7/7UwBoQJz 8P1C4qnWSDwp+ONA1duSHcbG8ZtGbB8WpCuMqUU5MGTH4JtXqv5L0vrGt1gMOj2VXABP o1BkpOqigtyhB8+VA7FaOPxmIZfgSxb+mEHZ0YLzWIvDQI7oojC9I6joQX4xoSO4Cc+Y Gj3kvE9ju76wGU8xi7egfDjctFMqhHolvAAn+CXRS0RqOpz5nwd4hDXS9OjxfsxDzAh6 dt/Q== X-Gm-Message-State: AHQUAuYUgrqC7C4/xjCoOdf16EYV6sUxNiMUHk94iVpQCaPFs4+69ZA0 7QgYhnY78s68f95PD0D7fUw= X-Google-Smtp-Source: AHgI3IYBDwoNzMjr7q7D1doZY34W/bOV5qXlnqFiEswirwv6MqMmkmGlJc2OBZ5roJB07yAbLGDIvw== X-Received: by 2002:a19:c949:: with SMTP id z70mr8416222lff.9.1549793688033; Sun, 10 Feb 2019 02:14:48 -0800 (PST) Received: from bard.ponomarevs.fi (88-112-122-53.elisa-laajakaista.fi. [88.112.122.53]) by smtp.gmail.com with ESMTPSA id l21-v6sm1472010ljj.48.2019.02.10.02.14.46 (version=TLS1_2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Sun, 10 Feb 2019 02:14:47 -0800 (PST) From: Nikolai Kondrashov To: Benjamin Tissoires Cc: Jiri Kosina , linux-input@vger.kernel.org, Nikolai Kondrashov Subject: [PATCH v2 04/23] HID: uclogic: Extract report descriptors to a module Date: Sun, 10 Feb 2019 12:13:50 +0200 Message-Id: <20190210101409.3511-5-spbnick@gmail.com> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20190210101409.3511-1-spbnick@gmail.com> References: <20181227171625.11164-1-spbnick@gmail.com> <20190210101409.3511-1-spbnick@gmail.com> MIME-Version: 1.0 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 As hid-uclogic has a lot of report descriptors already and there's going to be more, move them out of the driver code and into a separate module. Signed-off-by: Nikolai Kondrashov --- drivers/hid/Makefile | 2 + drivers/hid/hid-uclogic-core.c | 493 +++++++++++++ .../{hid-uclogic.c => hid-uclogic-rdesc.c} | 673 ++++-------------- drivers/hid/hid-uclogic-rdesc.h | 117 +++ 4 files changed, 745 insertions(+), 540 deletions(-) create mode 100644 drivers/hid/hid-uclogic-core.c rename drivers/hid/{hid-uclogic.c => hid-uclogic-rdesc.c} (66%) create mode 100644 drivers/hid/hid-uclogic-rdesc.h diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index a57e1088133a..fb75366ea776 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -108,6 +108,8 @@ obj-$(CONFIG_HID_THRUSTMASTER) += hid-tmff.o obj-$(CONFIG_HID_TIVO) += hid-tivo.o obj-$(CONFIG_HID_TOPSEED) += hid-topseed.o obj-$(CONFIG_HID_TWINHAN) += hid-twinhan.o +hid-uclogic-objs := hid-uclogic-core.o \ + hid-uclogic-rdesc.o obj-$(CONFIG_HID_UCLOGIC) += hid-uclogic.o obj-$(CONFIG_HID_UDRAW_PS3) += hid-udraw-ps3.o obj-$(CONFIG_HID_LED) += hid-led.o diff --git a/drivers/hid/hid-uclogic-core.c b/drivers/hid/hid-uclogic-core.c new file mode 100644 index 000000000000..4042183ee9a3 --- /dev/null +++ b/drivers/hid/hid-uclogic-core.c @@ -0,0 +1,493 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * HID driver for UC-Logic devices not fully compliant with HID standard + * + * Copyright (c) 2010-2014 Nikolai Kondrashov + * Copyright (c) 2013 Martin Rusko + */ + +/* + * 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 +#include "usbhid/usbhid.h" +#include "hid-uclogic-rdesc.h" + +#include "hid-ids.h" + +/* Parameter indices */ +enum uclogic_prm { + UCLOGIC_PRM_X_LM = 1, + UCLOGIC_PRM_Y_LM = 2, + UCLOGIC_PRM_PRESSURE_LM = 4, + UCLOGIC_PRM_RESOLUTION = 5, + UCLOGIC_PRM_NUM +}; + +/* Driver data */ +struct uclogic_drvdata { + __u8 *rdesc; + unsigned int rsize; + bool invert_pen_inrange; + bool ignore_pen_usage; + bool has_virtual_pad_interface; +}; + +static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + struct usb_interface *iface = to_usb_interface(hdev->dev.parent); + __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + + if (drvdata->rdesc != NULL) { + rdesc = drvdata->rdesc; + *rsize = drvdata->rsize; + return rdesc; + } + + switch (hdev->product) { + case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: + if (*rsize == UCLOGIC_RDESC_PF1209_ORIG_SIZE) { + rdesc = uclogic_rdesc_pf1209_fixed_arr; + *rsize = uclogic_rdesc_pf1209_fixed_size; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U: + if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { + rdesc = uclogic_rdesc_wp4030u_fixed_arr; + *rsize = uclogic_rdesc_wp4030u_fixed_size; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U: + if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { + rdesc = uclogic_rdesc_wp5540u_fixed_arr; + *rsize = uclogic_rdesc_wp5540u_fixed_size; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U: + if (*rsize == UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE) { + rdesc = uclogic_rdesc_wp8060u_fixed_arr; + *rsize = uclogic_rdesc_wp8060u_fixed_size; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062: + if (*rsize == UCLOGIC_RDESC_WP1062_ORIG_SIZE) { + rdesc = uclogic_rdesc_wp1062_fixed_arr; + *rsize = uclogic_rdesc_wp1062_fixed_size; + } + break; + case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850: + switch (iface_num) { + case 0: + if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG0_SIZE) { + rdesc = uclogic_rdesc_twhl850_fixed0_arr; + *rsize = uclogic_rdesc_twhl850_fixed0_size; + } + break; + case 1: + if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG1_SIZE) { + rdesc = uclogic_rdesc_twhl850_fixed1_arr; + *rsize = uclogic_rdesc_twhl850_fixed1_size; + } + break; + case 2: + if (*rsize == UCLOGIC_RDESC_TWHL850_ORIG2_SIZE) { + rdesc = uclogic_rdesc_twhl850_fixed2_arr; + *rsize = uclogic_rdesc_twhl850_fixed2_size; + } + break; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: + switch (iface_num) { + case 0: + if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG0_SIZE) { + rdesc = uclogic_rdesc_twha60_fixed0_arr; + *rsize = uclogic_rdesc_twha60_fixed0_size; + } + break; + case 1: + if (*rsize == UCLOGIC_RDESC_TWHA60_ORIG1_SIZE) { + rdesc = uclogic_rdesc_twha60_fixed1_arr; + *rsize = uclogic_rdesc_twha60_fixed1_size; + } + break; + } + break; + } + + return rdesc; +} + +static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + + /* discard the unused pen interface */ + if ((drvdata->ignore_pen_usage) && + (field->application == HID_DG_PEN)) + return -1; + + /* let hid-core decide what to do */ + return 0; +} + +static int uclogic_input_configured(struct hid_device *hdev, + struct hid_input *hi) +{ + char *name; + const char *suffix = NULL; + struct hid_field *field; + size_t len; + + /* no report associated (HID_QUIRK_MULTI_INPUT not set) */ + if (!hi->report) + return 0; + + field = hi->report->field[0]; + + switch (field->application) { + case HID_GD_KEYBOARD: + suffix = "Keyboard"; + break; + case HID_GD_MOUSE: + suffix = "Mouse"; + break; + case HID_GD_KEYPAD: + suffix = "Pad"; + break; + case HID_DG_PEN: + suffix = "Pen"; + break; + case HID_CP_CONSUMER_CONTROL: + suffix = "Consumer Control"; + break; + case HID_GD_SYSTEM_CONTROL: + suffix = "System Control"; + break; + } + + if (suffix) { + len = strlen(hdev->name) + 2 + strlen(suffix); + name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL); + if (name) { + snprintf(name, len, "%s %s", hdev->name, suffix); + hi->input->name = name; + } + } + + return 0; +} + +/** + * Enable fully-functional tablet mode and determine device parameters. + * + * @hdev: HID device + */ +static int uclogic_tablet_enable(struct hid_device *hdev) +{ + int rc; + struct usb_device *usb_dev = hid_to_usb_dev(hdev); + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + __le16 *buf = NULL; + size_t len; + s32 params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; + s32 resolution; + + /* + * Read string descriptor containing tablet parameters. The specific + * string descriptor and data were discovered by sniffing the Windows + * driver traffic. + * NOTE: This enables fully-functional tablet mode. + */ + len = UCLOGIC_PRM_NUM * sizeof(*buf); + buf = kmalloc(len, GFP_KERNEL); + if (buf == NULL) { + rc = -ENOMEM; + goto cleanup; + } + rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (USB_DT_STRING << 8) + 0x64, + 0x0409, buf, len, + USB_CTRL_GET_TIMEOUT); + if (rc == -EPIPE) { + hid_err(hdev, "device parameters not found\n"); + rc = -ENODEV; + goto cleanup; + } else if (rc < 0) { + hid_err(hdev, "failed to get device parameters: %d\n", rc); + rc = -ENODEV; + goto cleanup; + } else if (rc != len) { + hid_err(hdev, "invalid device parameters\n"); + rc = -ENODEV; + goto cleanup; + } + + /* Extract device parameters */ + params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = + le16_to_cpu(buf[UCLOGIC_PRM_X_LM]); + params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = + le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]); + params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = + le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]); + resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]); + if (resolution == 0) { + params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0; + params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0; + } else { + params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = + params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * + 1000 / resolution; + params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = + params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * + 1000 / resolution; + } + + /* Format fixed report descriptor */ + drvdata->rdesc = uclogic_rdesc_template_apply( + uclogic_rdesc_pen_template_arr, + uclogic_rdesc_pen_template_size, + params, ARRAY_SIZE(params)); + if (drvdata->rdesc == NULL) { + rc = -ENOMEM; + goto cleanup; + } + drvdata->rsize = uclogic_rdesc_pen_template_size; + + rc = 0; + +cleanup: + kfree(buf); + return rc; +} + +/** + * Enable actual button mode. + * + * @hdev: HID device + */ +static int uclogic_button_enable(struct hid_device *hdev) +{ + int rc; + struct usb_device *usb_dev = hid_to_usb_dev(hdev); + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + char *str_buf; + size_t str_len = 16; + unsigned char *rdesc; + size_t rdesc_len; + + str_buf = kzalloc(str_len, GFP_KERNEL); + if (str_buf == NULL) { + rc = -ENOMEM; + goto cleanup; + } + + /* Enable abstract keyboard mode */ + rc = usb_string(usb_dev, 0x7b, str_buf, str_len); + if (rc == -EPIPE) { + hid_info(hdev, "button mode setting not found\n"); + rc = 0; + goto cleanup; + } else if (rc < 0) { + hid_err(hdev, "failed to enable abstract keyboard\n"); + goto cleanup; + } else if (strncmp(str_buf, "HK On", rc)) { + hid_info(hdev, "invalid answer when requesting buttons: '%s'\n", + str_buf); + rc = -EINVAL; + goto cleanup; + } + + /* Re-allocate fixed report descriptor */ + rdesc_len = drvdata->rsize + uclogic_rdesc_buttonpad_size; + rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL); + if (!rdesc) { + rc = -ENOMEM; + goto cleanup; + } + + memcpy(rdesc, drvdata->rdesc, drvdata->rsize); + + /* Append the buttonpad descriptor */ + memcpy(rdesc + drvdata->rsize, uclogic_rdesc_buttonpad_arr, + uclogic_rdesc_buttonpad_size); + + /* clean up old rdesc and use the new one */ + drvdata->rsize = rdesc_len; + devm_kfree(&hdev->dev, drvdata->rdesc); + drvdata->rdesc = rdesc; + + rc = 0; + +cleanup: + kfree(str_buf); + return rc; +} + +static int uclogic_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int rc; + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *udev = hid_to_usb_dev(hdev); + struct uclogic_drvdata *drvdata; + + /* + * libinput requires the pad interface to be on a different node + * than the pen, so use QUIRK_MULTI_INPUT for all tablets. + */ + hdev->quirks |= HID_QUIRK_MULTI_INPUT; + + /* Allocate and assign driver data */ + drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); + if (drvdata == NULL) + return -ENOMEM; + + hid_set_drvdata(hdev, drvdata); + + switch (id->product) { + case USB_DEVICE_ID_HUION_TABLET: + case USB_DEVICE_ID_YIYNOVA_TABLET: + case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81: + case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3: + case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45: + /* If this is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + + rc = uclogic_button_enable(hdev); + drvdata->has_virtual_pad_interface = !rc; + } else { + drvdata->ignore_pen_usage = true; + } + break; + case USB_DEVICE_ID_UGTIZER_TABLET_GP0610: + case USB_DEVICE_ID_UGEE_TABLET_EX07S: + /* If this is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + } else { + drvdata->ignore_pen_usage = true; + } + break; + case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: + /* + * If it is the three-interface version, which is known to + * respond to initialization. + */ + if (udev->config->desc.bNumInterfaces == 3) { + /* If it is the pen interface */ + if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { + rc = uclogic_tablet_enable(hdev); + if (rc) { + hid_err(hdev, "tablet enabling failed\n"); + return rc; + } + drvdata->invert_pen_inrange = true; + + rc = uclogic_button_enable(hdev); + drvdata->has_virtual_pad_interface = !rc; + } else { + drvdata->ignore_pen_usage = true; + } + } + break; + } + + rc = hid_parse(hdev); + if (rc) { + hid_err(hdev, "parse failed\n"); + return rc; + } + + rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (rc) { + hid_err(hdev, "hw start failed\n"); + return rc; + } + + return 0; +} + +static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, + u8 *data, int size) +{ + struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); + + if ((report->type == HID_INPUT_REPORT) && + (report->id == UCLOGIC_RDESC_PEN_ID) && + (size >= 2)) { + if (drvdata->has_virtual_pad_interface && (data[1] & 0x20)) + /* Change to virtual frame button report ID */ + data[0] = 0xf7; + else if (drvdata->invert_pen_inrange) + /* Invert the in-range bit */ + data[1] ^= 0x40; + } + + return 0; +} + +static const struct hid_device_id uclogic_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, + USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, + { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, + { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) }, + { } +}; +MODULE_DEVICE_TABLE(hid, uclogic_devices); + +static struct hid_driver uclogic_driver = { + .name = "uclogic", + .id_table = uclogic_devices, + .probe = uclogic_probe, + .report_fixup = uclogic_report_fixup, + .raw_event = uclogic_raw_event, + .input_mapping = uclogic_input_mapping, + .input_configured = uclogic_input_configured, +}; +module_hid_driver(uclogic_driver); + +MODULE_AUTHOR("Martin Rusko"); +MODULE_AUTHOR("Nikolai Kondrashov"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-uclogic.c b/drivers/hid/hid-uclogic-rdesc.c similarity index 66% rename from drivers/hid/hid-uclogic.c rename to drivers/hid/hid-uclogic-rdesc.c index fdc4d1ea7049..e9053d28f4e0 100644 --- a/drivers/hid/hid-uclogic.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -1,7 +1,9 @@ +// SPDX-License-Identifier: GPL-2.0+ /* * HID driver for UC-Logic devices not fully compliant with HID standard + * - original and fixed report descriptors * - * Copyright (c) 2010-2014 Nikolai Kondrashov + * Copyright (c) 2010-2017 Nikolai Kondrashov * Copyright (c) 2013 Martin Rusko */ @@ -12,20 +14,12 @@ * any later version. */ -#include -#include -#include -#include +#include "hid-uclogic-rdesc.h" +#include #include -#include "usbhid/usbhid.h" - -#include "hid-ids.h" - -/* Size of the original descriptor of WPXXXXU tablets */ -#define WPXXXXU_RDESC_ORIG_SIZE 212 /* Fixed WP4030U report descriptor */ -static __u8 wp4030u_rdesc_fixed[] = { +__u8 uclogic_rdesc_wp4030u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -66,8 +60,11 @@ static __u8 wp4030u_rdesc_fixed[] = { 0xC0 /* End Collection */ }; +const size_t uclogic_rdesc_wp4030u_fixed_size = + sizeof(uclogic_rdesc_wp4030u_fixed_arr); + /* Fixed WP5540U report descriptor */ -static __u8 wp5540u_rdesc_fixed[] = { +__u8 uclogic_rdesc_wp5540u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -140,8 +137,11 @@ static __u8 wp5540u_rdesc_fixed[] = { 0xC0 /* End Collection */ }; +const size_t uclogic_rdesc_wp5540u_fixed_size = + sizeof(uclogic_rdesc_wp5540u_fixed_arr); + /* Fixed WP8060U report descriptor */ -static __u8 wp8060u_rdesc_fixed[] = { +__u8 uclogic_rdesc_wp8060u_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -214,11 +214,11 @@ static __u8 wp8060u_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* Size of the original descriptor of WP1062 tablet */ -#define WP1062_RDESC_ORIG_SIZE 254 +const size_t uclogic_rdesc_wp8060u_fixed_size = + sizeof(uclogic_rdesc_wp8060u_fixed_arr); /* Fixed WP1062 report descriptor */ -static __u8 wp1062_rdesc_fixed[] = { +__u8 uclogic_rdesc_wp1062_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -262,11 +262,11 @@ static __u8 wp1062_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* Size of the original descriptor of PF1209 tablet */ -#define PF1209_RDESC_ORIG_SIZE 234 +const size_t uclogic_rdesc_wp1062_fixed_size = + sizeof(uclogic_rdesc_wp1062_fixed_arr); /* Fixed PF1209 report descriptor */ -static __u8 pf1209_rdesc_fixed[] = { +__u8 uclogic_rdesc_pf1209_fixed_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -339,13 +339,11 @@ static __u8 pf1209_rdesc_fixed[] = { 0xC0 /* End Collection */ }; -/* Size of the original descriptors of TWHL850 tablet */ -#define TWHL850_RDESC_ORIG_SIZE0 182 -#define TWHL850_RDESC_ORIG_SIZE1 161 -#define TWHL850_RDESC_ORIG_SIZE2 92 +const size_t uclogic_rdesc_pf1209_fixed_size = + sizeof(uclogic_rdesc_pf1209_fixed_arr); /* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */ -static __u8 twhl850_rdesc_fixed0[] = { +__u8 uclogic_rdesc_twhl850_fixed0_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -387,8 +385,11 @@ static __u8 twhl850_rdesc_fixed0[] = { 0xC0 /* End Collection */ }; +const size_t uclogic_rdesc_twhl850_fixed0_size = + sizeof(uclogic_rdesc_twhl850_fixed0_arr); + /* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */ -static __u8 twhl850_rdesc_fixed1[] = { +__u8 uclogic_rdesc_twhl850_fixed1_arr[] = { 0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x02, /* Usage (Mouse), */ 0xA1, 0x01, /* Collection (Application), */ @@ -424,8 +425,11 @@ static __u8 twhl850_rdesc_fixed1[] = { 0xC0 /* End Collection */ }; +const size_t uclogic_rdesc_twhl850_fixed1_size = + sizeof(uclogic_rdesc_twhl850_fixed1_arr); + /* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */ -static __u8 twhl850_rdesc_fixed2[] = { +__u8 uclogic_rdesc_twhl850_fixed2_arr[] = { 0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x06, /* Usage (Keyboard), */ 0xA1, 0x01, /* Collection (Application), */ @@ -447,12 +451,11 @@ static __u8 twhl850_rdesc_fixed2[] = { 0xC0 /* End Collection */ }; -/* Size of the original descriptors of TWHA60 tablet */ -#define TWHA60_RDESC_ORIG_SIZE0 254 -#define TWHA60_RDESC_ORIG_SIZE1 139 +const size_t uclogic_rdesc_twhl850_fixed2_size = + sizeof(uclogic_rdesc_twhl850_fixed2_arr); /* Fixed TWHA60 report descriptor, interface 0 (stylus) */ -static __u8 twha60_rdesc_fixed0[] = { +__u8 uclogic_rdesc_twha60_fixed0_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -497,8 +500,11 @@ static __u8 twha60_rdesc_fixed0[] = { 0xC0 /* End Collection */ }; +const size_t uclogic_rdesc_twha60_fixed0_size = + sizeof(uclogic_rdesc_twha60_fixed0_arr); + /* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */ -static __u8 twha60_rdesc_fixed1[] = { +__u8 uclogic_rdesc_twha60_fixed1_arr[] = { 0x05, 0x01, /* Usage Page (Desktop), */ 0x09, 0x06, /* Usage (Keyboard), */ 0xA1, 0x01, /* Collection (Application), */ @@ -522,25 +528,11 @@ static __u8 twha60_rdesc_fixed1[] = { 0xC0 /* End Collection */ }; -/* Report descriptor template placeholder head */ -#define UCLOGIC_PH_HEAD 0xFE, 0xED, 0x1D +const size_t uclogic_rdesc_twha60_fixed1_size = + sizeof(uclogic_rdesc_twha60_fixed1_arr); -/* Report descriptor template placeholder IDs */ -enum uclogic_ph_id { - UCLOGIC_PH_ID_X_LM, - UCLOGIC_PH_ID_X_PM, - UCLOGIC_PH_ID_Y_LM, - UCLOGIC_PH_ID_Y_PM, - UCLOGIC_PH_ID_PRESSURE_LM, - UCLOGIC_PH_ID_NUM -}; - -/* Report descriptor template placeholder */ -#define UCLOGIC_PH(_ID) UCLOGIC_PH_HEAD, UCLOGIC_PH_ID_##_ID -#define UCLOGIC_PEN_REPORT_ID 0x07 - -/* Fixed report descriptor template */ -static const __u8 uclogic_tablet_rdesc_template[] = { +/* Fixed report descriptor template for (tweaked) pen reports */ +const __u8 uclogic_rdesc_pen_template_arr[] = { 0x05, 0x0D, /* Usage Page (Digitizer), */ 0x09, 0x02, /* Usage (Pen), */ 0xA1, 0x01, /* Collection (Application), */ @@ -570,521 +562,122 @@ static const __u8 uclogic_tablet_rdesc_template[] = { 0x55, 0xFD, /* Unit Exponent (-3), */ 0x34, /* Physical Minimum (0), */ 0x09, 0x30, /* Usage (X), */ - 0x27, UCLOGIC_PH(X_LM), /* Logical Maximum (PLACEHOLDER), */ - 0x47, UCLOGIC_PH(X_PM), /* Physical Maximum (PLACEHOLDER), */ + 0x27, UCLOGIC_RDESC_PEN_PH(X_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(X_PM), + /* Physical Maximum (PLACEHOLDER), */ 0x81, 0x02, /* Input (Variable), */ 0x09, 0x31, /* Usage (Y), */ - 0x27, UCLOGIC_PH(Y_LM), /* Logical Maximum (PLACEHOLDER), */ - 0x47, UCLOGIC_PH(Y_PM), /* Physical Maximum (PLACEHOLDER), */ + 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM), + /* Physical Maximum (PLACEHOLDER), */ 0x81, 0x02, /* Input (Variable), */ 0xB4, /* Pop, */ 0x09, 0x30, /* Usage (Tip Pressure), */ - 0x27, - UCLOGIC_PH(PRESSURE_LM),/* Logical Maximum (PLACEHOLDER), */ + 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM), + /* Logical Maximum (PLACEHOLDER), */ 0x81, 0x02, /* Input (Variable), */ 0xC0, /* End Collection, */ 0xC0 /* End Collection */ }; -/* Fixed virtual pad report descriptor */ -static const __u8 uclogic_buttonpad_rdesc[] = { - 0x05, 0x01, /* Usage Page (Desktop), */ - 0x09, 0x07, /* Usage (Keypad), */ - 0xA1, 0x01, /* Collection (Application), */ - 0x85, 0xF7, /* Report ID (247), */ - 0x05, 0x0D, /* Usage Page (Digitizers), */ - 0x09, 0x39, /* Usage (Tablet Function Keys), */ - 0xA0, /* Collection (Physical), */ - 0x05, 0x09, /* Usage Page (Button), */ - 0x75, 0x01, /* Report Size (1), */ - 0x95, 0x18, /* Report Count (24), */ - 0x81, 0x03, /* Input (Constant, Variable), */ - 0x19, 0x01, /* Usage Minimum (01h), */ - 0x29, 0x08, /* Usage Maximum (08h), */ - 0x95, 0x08, /* Report Count (8), */ - 0x81, 0x02, /* Input (Variable), */ - 0xC0, /* End Collection */ - 0xC0 /* End Collection */ -}; - -/* Parameter indices */ -enum uclogic_prm { - UCLOGIC_PRM_X_LM = 1, - UCLOGIC_PRM_Y_LM = 2, - UCLOGIC_PRM_PRESSURE_LM = 4, - UCLOGIC_PRM_RESOLUTION = 5, - UCLOGIC_PRM_NUM -}; +const size_t uclogic_rdesc_pen_template_size = + sizeof(uclogic_rdesc_pen_template_arr); -/* Driver data */ -struct uclogic_drvdata { - __u8 *rdesc; - unsigned int rsize; - bool invert_pen_inrange; - bool ignore_pen_usage; - bool has_virtual_pad_interface; +/** + * Expand to the contents of a generic buttonpad report descriptor. + * + * @_padding: Padding from the end of button bits at bit 44, until + * the end of the report, in bits. + */ +#define UCLOGIC_RDESC_BUTTONPAD_BYTES(_padding) \ + 0x05, 0x01, /* Usage Page (Desktop), */ \ + 0x09, 0x07, /* Usage (Keypad), */ \ + 0xA1, 0x01, /* Collection (Application), */ \ + 0x85, 0xF7, /* Report ID (247), */ \ + 0x14, /* Logical Minimum (0), */ \ + 0x25, 0x01, /* Logical Maximum (1), */ \ + 0x75, 0x01, /* Report Size (1), */ \ + 0x05, 0x0D, /* Usage Page (Digitizer), */ \ + 0x09, 0x39, /* Usage (Tablet Function Keys), */ \ + 0xA0, /* Collection (Physical), */ \ + 0x09, 0x44, /* Usage (Barrel Switch), */ \ + 0x95, 0x01, /* Report Count (1), */ \ + 0x81, 0x02, /* Input (Variable), */ \ + 0x05, 0x01, /* Usage Page (Desktop), */ \ + 0x09, 0x30, /* Usage (X), */ \ + 0x09, 0x31, /* Usage (Y), */ \ + 0x95, 0x02, /* Report Count (2), */ \ + 0x81, 0x02, /* Input (Variable), */ \ + 0x95, 0x15, /* Report Count (21), */ \ + 0x81, 0x01, /* Input (Constant), */ \ + 0x05, 0x09, /* Usage Page (Button), */ \ + 0x19, 0x01, /* Usage Minimum (01h), */ \ + 0x29, 0x0A, /* Usage Maximum (0Ah), */ \ + 0x95, 0x0A, /* Report Count (10), */ \ + 0x81, 0x02, /* Input (Variable), */ \ + 0xC0, /* End Collection, */ \ + 0x05, 0x01, /* Usage Page (Desktop), */ \ + 0x09, 0x05, /* Usage (Gamepad), */ \ + 0xA0, /* Collection (Physical), */ \ + 0x05, 0x09, /* Usage Page (Button), */ \ + 0x19, 0x01, /* Usage Minimum (01h), */ \ + 0x29, 0x02, /* Usage Maximum (02h), */ \ + 0x95, 0x02, /* Report Count (2), */ \ + 0x81, 0x02, /* Input (Variable), */ \ + 0x95, _padding, /* Report Count (_padding), */ \ + 0x81, 0x01, /* Input (Constant), */ \ + 0xC0, /* End Collection, */ \ + 0xC0 /* End Collection */ + +/* Fixed report descriptor for (tweaked) buttonpad reports */ +const __u8 uclogic_rdesc_buttonpad_arr[] = { + UCLOGIC_RDESC_BUTTONPAD_BYTES(20) }; - -static __u8 *uclogic_report_fixup(struct hid_device *hdev, __u8 *rdesc, - unsigned int *rsize) -{ - struct usb_interface *iface = to_usb_interface(hdev->dev.parent); - __u8 iface_num = iface->cur_altsetting->desc.bInterfaceNumber; - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - - if (drvdata->rdesc != NULL) { - rdesc = drvdata->rdesc; - *rsize = drvdata->rsize; - return rdesc; - } - - switch (hdev->product) { - case USB_DEVICE_ID_UCLOGIC_TABLET_PF1209: - if (*rsize == PF1209_RDESC_ORIG_SIZE) { - rdesc = pf1209_rdesc_fixed; - *rsize = sizeof(pf1209_rdesc_fixed); - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U: - if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) { - rdesc = wp4030u_rdesc_fixed; - *rsize = sizeof(wp4030u_rdesc_fixed); - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U: - if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) { - rdesc = wp5540u_rdesc_fixed; - *rsize = sizeof(wp5540u_rdesc_fixed); - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U: - if (*rsize == WPXXXXU_RDESC_ORIG_SIZE) { - rdesc = wp8060u_rdesc_fixed; - *rsize = sizeof(wp8060u_rdesc_fixed); - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_WP1062: - if (*rsize == WP1062_RDESC_ORIG_SIZE) { - rdesc = wp1062_rdesc_fixed; - *rsize = sizeof(wp1062_rdesc_fixed); - } - break; - case USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850: - switch (iface_num) { - case 0: - if (*rsize == TWHL850_RDESC_ORIG_SIZE0) { - rdesc = twhl850_rdesc_fixed0; - *rsize = sizeof(twhl850_rdesc_fixed0); - } - break; - case 1: - if (*rsize == TWHL850_RDESC_ORIG_SIZE1) { - rdesc = twhl850_rdesc_fixed1; - *rsize = sizeof(twhl850_rdesc_fixed1); - } - break; - case 2: - if (*rsize == TWHL850_RDESC_ORIG_SIZE2) { - rdesc = twhl850_rdesc_fixed2; - *rsize = sizeof(twhl850_rdesc_fixed2); - } - break; - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: - switch (iface_num) { - case 0: - if (*rsize == TWHA60_RDESC_ORIG_SIZE0) { - rdesc = twha60_rdesc_fixed0; - *rsize = sizeof(twha60_rdesc_fixed0); - } - break; - case 1: - if (*rsize == TWHA60_RDESC_ORIG_SIZE1) { - rdesc = twha60_rdesc_fixed1; - *rsize = sizeof(twha60_rdesc_fixed1); - } - break; - } - break; - } - - return rdesc; -} - -static int uclogic_input_mapping(struct hid_device *hdev, struct hid_input *hi, - struct hid_field *field, struct hid_usage *usage, - unsigned long **bit, int *max) -{ - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - - /* discard the unused pen interface */ - if ((drvdata->ignore_pen_usage) && - (field->application == HID_DG_PEN)) - return -1; - - /* let hid-core decide what to do */ - return 0; -} - -static int uclogic_input_configured(struct hid_device *hdev, - struct hid_input *hi) -{ - char *name; - const char *suffix = NULL; - struct hid_field *field; - size_t len; - - /* no report associated (HID_QUIRK_MULTI_INPUT not set) */ - if (!hi->report) - return 0; - - field = hi->report->field[0]; - - switch (field->application) { - case HID_GD_KEYBOARD: - suffix = "Keyboard"; - break; - case HID_GD_MOUSE: - suffix = "Mouse"; - break; - case HID_GD_KEYPAD: - suffix = "Pad"; - break; - case HID_DG_PEN: - suffix = "Pen"; - break; - case HID_CP_CONSUMER_CONTROL: - suffix = "Consumer Control"; - break; - case HID_GD_SYSTEM_CONTROL: - suffix = "System Control"; - break; - } - - if (suffix) { - len = strlen(hdev->name) + 2 + strlen(suffix); - name = devm_kzalloc(&hi->input->dev, len, GFP_KERNEL); - if (name) { - snprintf(name, len, "%s %s", hdev->name, suffix); - hi->input->name = name; - } - } - - return 0; -} +const size_t uclogic_rdesc_buttonpad_size = + sizeof(uclogic_rdesc_buttonpad_arr); /** - * Enable fully-functional tablet mode and determine device parameters. + * uclogic_rdesc_template_apply() - apply report descriptor parameters to a + * report descriptor template, creating a report descriptor. Copies the + * template over to the new report descriptor and replaces every occurrence of + * UCLOGIC_RDESC_PH_HEAD, followed by an index byte, with the value from the + * parameter list at that index. + * + * @template_ptr: Pointer to the template buffer. + * @template_size: Size of the template buffer. + * @param_list: List of template parameters. + * @param_num: Number of parameters in the list. * - * @hdev: HID device + * Returns: + * Kmalloc-allocated pointer to the created report descriptor, + * or NULL if allocation failed. */ -static int uclogic_tablet_enable(struct hid_device *hdev) +__u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr, + size_t template_size, + const s32 *param_list, + size_t param_num) { - int rc; - struct usb_device *usb_dev = hid_to_usb_dev(hdev); - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - __le16 *buf = NULL; - size_t len; - s32 params[UCLOGIC_PH_ID_NUM]; - s32 resolution; + static const __u8 head[] = {UCLOGIC_RDESC_PH_HEAD}; + __u8 *rdesc_ptr; __u8 *p; s32 v; - /* - * Read string descriptor containing tablet parameters. The specific - * string descriptor and data were discovered by sniffing the Windows - * driver traffic. - * NOTE: This enables fully-functional tablet mode. - */ - len = UCLOGIC_PRM_NUM * sizeof(*buf); - buf = kmalloc(len, GFP_KERNEL); - if (buf == NULL) { - rc = -ENOMEM; - goto cleanup; - } - rc = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, - (USB_DT_STRING << 8) + 0x64, - 0x0409, buf, len, - USB_CTRL_GET_TIMEOUT); - if (rc == -EPIPE) { - hid_err(hdev, "device parameters not found\n"); - rc = -ENODEV; - goto cleanup; - } else if (rc < 0) { - hid_err(hdev, "failed to get device parameters: %d\n", rc); - rc = -ENODEV; - goto cleanup; - } else if (rc != len) { - hid_err(hdev, "invalid device parameters\n"); - rc = -ENODEV; - goto cleanup; - } - - /* Extract device parameters */ - params[UCLOGIC_PH_ID_X_LM] = le16_to_cpu(buf[UCLOGIC_PRM_X_LM]); - params[UCLOGIC_PH_ID_Y_LM] = le16_to_cpu(buf[UCLOGIC_PRM_Y_LM]); - params[UCLOGIC_PH_ID_PRESSURE_LM] = - le16_to_cpu(buf[UCLOGIC_PRM_PRESSURE_LM]); - resolution = le16_to_cpu(buf[UCLOGIC_PRM_RESOLUTION]); - if (resolution == 0) { - params[UCLOGIC_PH_ID_X_PM] = 0; - params[UCLOGIC_PH_ID_Y_PM] = 0; - } else { - params[UCLOGIC_PH_ID_X_PM] = params[UCLOGIC_PH_ID_X_LM] * - 1000 / resolution; - params[UCLOGIC_PH_ID_Y_PM] = params[UCLOGIC_PH_ID_Y_LM] * - 1000 / resolution; - } + rdesc_ptr = kmemdup(template_ptr, template_size, GFP_KERNEL); + if (rdesc_ptr == NULL) + return NULL; - /* Allocate fixed report descriptor */ - drvdata->rdesc = devm_kzalloc(&hdev->dev, - sizeof(uclogic_tablet_rdesc_template), - GFP_KERNEL); - if (drvdata->rdesc == NULL) { - rc = -ENOMEM; - goto cleanup; - } - drvdata->rsize = sizeof(uclogic_tablet_rdesc_template); - - /* Format fixed report descriptor */ - memcpy(drvdata->rdesc, uclogic_tablet_rdesc_template, - drvdata->rsize); - for (p = drvdata->rdesc; - p <= drvdata->rdesc + drvdata->rsize - 4;) { - if (p[0] == 0xFE && p[1] == 0xED && p[2] == 0x1D && - p[3] < ARRAY_SIZE(params)) { - v = params[p[3]]; + for (p = rdesc_ptr; p + sizeof(head) < rdesc_ptr + template_size;) { + if (memcmp(p, head, sizeof(head)) == 0 && + p[sizeof(head)] < param_num) { + v = param_list[p[sizeof(head)]]; put_unaligned(cpu_to_le32(v), (s32 *)p); - p += 4; + p += sizeof(head) + 1; } else { p++; } } - rc = 0; - -cleanup: - kfree(buf); - return rc; -} - -/** - * Enable actual button mode. - * - * @hdev: HID device - */ -static int uclogic_button_enable(struct hid_device *hdev) -{ - int rc; - struct usb_device *usb_dev = hid_to_usb_dev(hdev); - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - char *str_buf; - size_t str_len = 16; - unsigned char *rdesc; - size_t rdesc_len; - - str_buf = kzalloc(str_len, GFP_KERNEL); - if (str_buf == NULL) { - rc = -ENOMEM; - goto cleanup; - } - - /* Enable abstract keyboard mode */ - rc = usb_string(usb_dev, 0x7b, str_buf, str_len); - if (rc == -EPIPE) { - hid_info(hdev, "button mode setting not found\n"); - rc = 0; - goto cleanup; - } else if (rc < 0) { - hid_err(hdev, "failed to enable abstract keyboard\n"); - goto cleanup; - } else if (strncmp(str_buf, "HK On", rc)) { - hid_info(hdev, "invalid answer when requesting buttons: '%s'\n", - str_buf); - rc = -EINVAL; - goto cleanup; - } - - /* Re-allocate fixed report descriptor */ - rdesc_len = drvdata->rsize + sizeof(uclogic_buttonpad_rdesc); - rdesc = devm_kzalloc(&hdev->dev, rdesc_len, GFP_KERNEL); - if (!rdesc) { - rc = -ENOMEM; - goto cleanup; - } - - memcpy(rdesc, drvdata->rdesc, drvdata->rsize); - - /* Append the buttonpad descriptor */ - memcpy(rdesc + drvdata->rsize, uclogic_buttonpad_rdesc, - sizeof(uclogic_buttonpad_rdesc)); - - /* clean up old rdesc and use the new one */ - drvdata->rsize = rdesc_len; - devm_kfree(&hdev->dev, drvdata->rdesc); - drvdata->rdesc = rdesc; - - rc = 0; - -cleanup: - kfree(str_buf); - return rc; -} - -static int uclogic_probe(struct hid_device *hdev, - const struct hid_device_id *id) -{ - int rc; - struct usb_interface *intf = to_usb_interface(hdev->dev.parent); - struct usb_device *udev = hid_to_usb_dev(hdev); - struct uclogic_drvdata *drvdata; - - /* - * libinput requires the pad interface to be on a different node - * than the pen, so use QUIRK_MULTI_INPUT for all tablets. - */ - hdev->quirks |= HID_QUIRK_MULTI_INPUT; - - /* Allocate and assign driver data */ - drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL); - if (drvdata == NULL) - return -ENOMEM; - - hid_set_drvdata(hdev, drvdata); - - switch (id->product) { - case USB_DEVICE_ID_HUION_TABLET: - case USB_DEVICE_ID_YIYNOVA_TABLET: - case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81: - case USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3: - case USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45: - /* If this is the pen interface */ - if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { - rc = uclogic_tablet_enable(hdev); - if (rc) { - hid_err(hdev, "tablet enabling failed\n"); - return rc; - } - drvdata->invert_pen_inrange = true; - - rc = uclogic_button_enable(hdev); - drvdata->has_virtual_pad_interface = !rc; - } else { - drvdata->ignore_pen_usage = true; - } - break; - case USB_DEVICE_ID_UGTIZER_TABLET_GP0610: - case USB_DEVICE_ID_UGEE_TABLET_EX07S: - /* If this is the pen interface */ - if (intf->cur_altsetting->desc.bInterfaceNumber == 1) { - rc = uclogic_tablet_enable(hdev); - if (rc) { - hid_err(hdev, "tablet enabling failed\n"); - return rc; - } - drvdata->invert_pen_inrange = true; - } else { - drvdata->ignore_pen_usage = true; - } - break; - case USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60: - /* - * If it is the three-interface version, which is known to - * respond to initialization. - */ - if (udev->config->desc.bNumInterfaces == 3) { - /* If it is the pen interface */ - if (intf->cur_altsetting->desc.bInterfaceNumber == 0) { - rc = uclogic_tablet_enable(hdev); - if (rc) { - hid_err(hdev, "tablet enabling failed\n"); - return rc; - } - drvdata->invert_pen_inrange = true; - - rc = uclogic_button_enable(hdev); - drvdata->has_virtual_pad_interface = !rc; - } else { - drvdata->ignore_pen_usage = true; - } - } - break; - } - - rc = hid_parse(hdev); - if (rc) { - hid_err(hdev, "parse failed\n"); - return rc; - } - - rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT); - if (rc) { - hid_err(hdev, "hw start failed\n"); - return rc; - } - - return 0; + return rdesc_ptr; } - -static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report, - u8 *data, int size) -{ - struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev); - - if ((report->type == HID_INPUT_REPORT) && - (report->id == UCLOGIC_PEN_REPORT_ID) && - (size >= 2)) { - if (drvdata->has_virtual_pad_interface && (data[1] & 0x20)) - /* Change to virtual frame button report ID */ - data[0] = 0xf7; - else if (drvdata->invert_pen_inrange) - /* Invert the in-range bit */ - data[1] ^= 0x40; - } - - return 0; -} - -static const struct hid_device_id uclogic_devices[] = { - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_WP1062) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, - USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60) }, - { HID_USB_DEVICE(USB_VENDOR_ID_HUION, USB_DEVICE_ID_HUION_TABLET) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_HUION_TABLET) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_YIYNOVA_TABLET) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC, USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UGTIZER, USB_DEVICE_ID_UGTIZER_TABLET_GP0610) }, - { HID_USB_DEVICE(USB_VENDOR_ID_UGEE, USB_DEVICE_ID_UGEE_TABLET_EX07S) }, - { } -}; -MODULE_DEVICE_TABLE(hid, uclogic_devices); - -static struct hid_driver uclogic_driver = { - .name = "uclogic", - .id_table = uclogic_devices, - .probe = uclogic_probe, - .report_fixup = uclogic_report_fixup, - .raw_event = uclogic_raw_event, - .input_mapping = uclogic_input_mapping, - .input_configured = uclogic_input_configured, -}; -module_hid_driver(uclogic_driver); - -MODULE_AUTHOR("Martin Rusko"); -MODULE_AUTHOR("Nikolai Kondrashov"); -MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h new file mode 100644 index 000000000000..610575879522 --- /dev/null +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -0,0 +1,117 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * HID driver for UC-Logic devices not fully compliant with HID standard + * - original and fixed report descriptors + * + * Copyright (c) 2010-2018 Nikolai Kondrashov + * Copyright (c) 2013 Martin Rusko + */ + +/* + * 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. + */ + +#ifndef _HID_UCLOGIC_RDESC_H +#define _HID_UCLOGIC_RDESC_H + +#include + +/* Size of the original descriptor of WPXXXXU tablets */ +#define UCLOGIC_RDESC_WPXXXXU_ORIG_SIZE 212 + +/* Fixed WP4030U report descriptor */ +extern __u8 uclogic_rdesc_wp4030u_fixed_arr[]; +extern const size_t uclogic_rdesc_wp4030u_fixed_size; + +/* Fixed WP5540U report descriptor */ +extern __u8 uclogic_rdesc_wp5540u_fixed_arr[]; +extern const size_t uclogic_rdesc_wp5540u_fixed_size; + +/* Fixed WP8060U report descriptor */ +extern __u8 uclogic_rdesc_wp8060u_fixed_arr[]; +extern const size_t uclogic_rdesc_wp8060u_fixed_size; + +/* Size of the original descriptor of WP1062 tablet */ +#define UCLOGIC_RDESC_WP1062_ORIG_SIZE 254 + +/* Fixed WP1062 report descriptor */ +extern __u8 uclogic_rdesc_wp1062_fixed_arr[]; +extern const size_t uclogic_rdesc_wp1062_fixed_size; + +/* Size of the original descriptor of PF1209 tablet */ +#define UCLOGIC_RDESC_PF1209_ORIG_SIZE 234 + +/* Fixed PF1209 report descriptor */ +extern __u8 uclogic_rdesc_pf1209_fixed_arr[]; +extern const size_t uclogic_rdesc_pf1209_fixed_size; + +/* Size of the original descriptors of TWHL850 tablet */ +#define UCLOGIC_RDESC_TWHL850_ORIG0_SIZE 182 +#define UCLOGIC_RDESC_TWHL850_ORIG1_SIZE 161 +#define UCLOGIC_RDESC_TWHL850_ORIG2_SIZE 92 + +/* Fixed PID 0522 tablet report descriptor, interface 0 (stylus) */ +extern __u8 uclogic_rdesc_twhl850_fixed0_arr[]; +extern const size_t uclogic_rdesc_twhl850_fixed0_size; + +/* Fixed PID 0522 tablet report descriptor, interface 1 (mouse) */ +extern __u8 uclogic_rdesc_twhl850_fixed1_arr[]; +extern const size_t uclogic_rdesc_twhl850_fixed1_size; + +/* Fixed PID 0522 tablet report descriptor, interface 2 (frame buttons) */ +extern __u8 uclogic_rdesc_twhl850_fixed2_arr[]; +extern const size_t uclogic_rdesc_twhl850_fixed2_size; + +/* Size of the original descriptors of TWHA60 tablet */ +#define UCLOGIC_RDESC_TWHA60_ORIG0_SIZE 254 +#define UCLOGIC_RDESC_TWHA60_ORIG1_SIZE 139 + +/* Fixed TWHA60 report descriptor, interface 0 (stylus) */ +extern __u8 uclogic_rdesc_twha60_fixed0_arr[]; +extern const size_t uclogic_rdesc_twha60_fixed0_size; + +/* Fixed TWHA60 report descriptor, interface 1 (frame buttons) */ +extern __u8 uclogic_rdesc_twha60_fixed1_arr[]; +extern const size_t uclogic_rdesc_twha60_fixed1_size; + +/* Report descriptor template placeholder head */ +#define UCLOGIC_RDESC_PH_HEAD 0xFE, 0xED, 0x1D + +/* Apply report descriptor parameters to a report descriptor template */ +extern __u8 *uclogic_rdesc_template_apply(const __u8 *template_ptr, + size_t template_size, + const s32 *param_list, + size_t param_num); + +/* Pen report descriptor template placeholder IDs */ +enum uclogic_rdesc_pen_ph_id { + UCLOGIC_RDESC_PEN_PH_ID_X_LM, + UCLOGIC_RDESC_PEN_PH_ID_X_PM, + UCLOGIC_RDESC_PEN_PH_ID_Y_LM, + UCLOGIC_RDESC_PEN_PH_ID_Y_PM, + UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM, + UCLOGIC_RDESC_PEN_PH_ID_NUM +}; + +/* Report descriptor pen template placeholder */ +#define UCLOGIC_RDESC_PEN_PH(_ID) \ + UCLOGIC_RDESC_PH_HEAD, UCLOGIC_RDESC_PEN_PH_ID_##_ID + +/* Report ID for pen reports */ +#define UCLOGIC_RDESC_PEN_ID 0x07 + +/* Fixed report descriptor template for (tweaked) pen reports */ +extern const __u8 uclogic_rdesc_pen_template_arr[]; +extern const size_t uclogic_rdesc_pen_template_size; + +/* Fixed report descriptor for (tweaked) buttonpad reports */ +extern const __u8 uclogic_rdesc_buttonpad_arr[]; +extern const size_t uclogic_rdesc_buttonpad_size; + +/* Report ID for tweaked buttonpad reports */ +#define UCLOGIC_RDESC_BUTTONPAD_ID 0xf7 + +#endif /* _HID_UCLOGIC_RDESC_H */