aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/hid/hid-uclogic-params.c
diff options
context:
space:
mode:
authorNikolai Kondrashov <spbnick@gmail.com>2019-02-10 12:13:51 +0200
committerBenjamin Tissoires <benjamin.tissoires@redhat.com>2019-02-21 12:00:53 +0100
commit9614219e9310ef19e66719bf37f9f68919bac08e (patch)
treee658e86c165670b6dbc7e49f44ef460415721d3e /drivers/hid/hid-uclogic-params.c
parentHID: uclogic: Extract report descriptors to a module (diff)
downloadlinux-dev-9614219e9310ef19e66719bf37f9f68919bac08e.tar.xz
linux-dev-9614219e9310ef19e66719bf37f9f68919bac08e.zip
HID: uclogic: Extract tablet parameter discovery into a module
Refactor and extract UC-Logic tablet initialization and parameter discovery into a module. For these tablets, the major part of parameter discovery cannot be separated from initialization so they have to be in the same module. Define explicitly and clearly what possible quirks the tablets may have to make the driver implementation easier and simpler. Signed-off-by: Nikolai Kondrashov <spbnick@gmail.com> Signed-off-by: Benjamin Tissoires <benjamin.tissoires@redhat.com>
Diffstat (limited to 'drivers/hid/hid-uclogic-params.c')
-rw-r--r--drivers/hid/hid-uclogic-params.c806
1 files changed, 806 insertions, 0 deletions
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c
new file mode 100644
index 000000000000..2f8870d58f9a
--- /dev/null
+++ b/drivers/hid/hid-uclogic-params.c
@@ -0,0 +1,806 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * HID driver for UC-Logic devices not fully compliant with HID standard
+ * - tablet initialization and parameter retrieval
+ *
+ * Copyright (c) 2018 Nikolai Kondrashov
+ */
+
+/*
+ * 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 "hid-uclogic-params.h"
+#include "hid-uclogic-rdesc.h"
+#include "usbhid/usbhid.h"
+#include "hid-ids.h"
+#include <linux/ctype.h>
+#include <asm/unaligned.h>
+
+/**
+ * Convert a pen in-range reporting type to a string.
+ *
+ * @inrange: The in-range reporting type to convert.
+ *
+ * Returns:
+ * The string representing the type, or NULL if the type is unknown.
+ */
+const char *uclogic_params_pen_inrange_to_str(
+ enum uclogic_params_pen_inrange inrange)
+{
+ switch (inrange) {
+ case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL:
+ return "normal";
+ case UCLOGIC_PARAMS_PEN_INRANGE_INVERTED:
+ return "inverted";
+ default:
+ return NULL;
+ }
+}
+
+/**
+ * uclogic_params_get_str_desc - retrieve a string descriptor from a HID
+ * device interface, putting it into a kmalloc-allocated buffer as is, without
+ * character encoding conversion.
+ *
+ * @pbuf: Location for the kmalloc-allocated buffer pointer containing
+ * the retrieved descriptor. Not modified in case of error.
+ * Can be NULL to have retrieved descriptor discarded.
+ * @hdev: The HID device of the tablet interface to retrieve the string
+ * descriptor from. Cannot be NULL.
+ * @idx: Index of the string descriptor to request from the device.
+ * @len: Length of the buffer to allocate and the data to retrieve.
+ *
+ * Returns:
+ * number of bytes retrieved (<= len),
+ * -EPIPE, if the descriptor was not found, or
+ * another negative errno code in case of other error.
+ */
+static int uclogic_params_get_str_desc(__u8 **pbuf, struct hid_device *hdev,
+ __u8 idx, size_t len)
+{
+ int rc;
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ __u8 *buf = NULL;
+
+ /* Check arguments */
+ if (hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ buf = kmalloc(len, GFP_KERNEL);
+ if (buf == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0),
+ USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+ (USB_DT_STRING << 8) + idx,
+ 0x0409, buf, len,
+ USB_CTRL_GET_TIMEOUT);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev, "string descriptor #%hhu not found\n", idx);
+ goto cleanup;
+ } else if (rc < 0) {
+ hid_err(hdev,
+ "failed retrieving string descriptor #%hhu: %d\n",
+ idx, rc);
+ goto cleanup;
+ }
+
+ if (pbuf != NULL) {
+ *pbuf = buf;
+ buf = NULL;
+ }
+
+cleanup:
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_pen_cleanup - free resources used by struct
+ * uclogic_params_pen (tablet interface's pen input parameters).
+ * Can be called repeatedly.
+ *
+ * @pen: Pen input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen)
+{
+ kfree(pen->desc_ptr);
+ memset(pen, 0, sizeof(*pen));
+}
+
+/**
+ * uclogic_params_pen_init() - initialize tablet interface pen
+ * input and retrieve its parameters from the device.
+ *
+ * @pen: Pointer to the pen parameters to initialize (to be
+ * cleaned up with uclogic_params_pen_cleanup()). Not modified in
+ * case of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_pen_init(struct uclogic_params_pen *pen,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ int rc;
+ bool found = false;
+ /* Buffer for (part of) the string descriptor */
+ __u8 *buf = NULL;
+ /* Minimum descriptor length required, maximum seen so far is 18 */
+ const int len = 12;
+ s32 resolution;
+ /* Pen report descriptor template parameters */
+ s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM];
+ __u8 *desc_ptr = NULL;
+
+ /* Check arguments */
+ if (pen == NULL || pfound == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Read string descriptor containing pen input parameters.
+ * The specific string descriptor and data were discovered by sniffing
+ * the Windows driver traffic.
+ * NOTE: This enables fully-functional tablet mode.
+ */
+ rc = uclogic_params_get_str_desc(&buf, hdev, 100, len);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters not found, assuming not compatible\n");
+ goto finish;
+ } else if (rc < 0) {
+ hid_err(hdev, "failed retrieving pen parameters: %d\n", rc);
+ goto cleanup;
+ } else if (rc != len) {
+ hid_dbg(hdev,
+ "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n",
+ rc, len);
+ goto finish;
+ }
+
+ /*
+ * Fill report descriptor parameters from the string descriptor
+ */
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
+ get_unaligned_le16(buf + 2);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
+ get_unaligned_le16(buf + 4);
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] =
+ get_unaligned_le16(buf + 8);
+ resolution = get_unaligned_le16(buf + 10);
+ if (resolution == 0) {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0;
+ } else {
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 /
+ resolution;
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] =
+ desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 /
+ resolution;
+ }
+ kfree(buf);
+ buf = NULL;
+
+ /*
+ * Generate pen report descriptor
+ */
+ desc_ptr = uclogic_rdesc_template_apply(
+ uclogic_rdesc_pen_template_arr,
+ uclogic_rdesc_pen_template_size,
+ desc_params, ARRAY_SIZE(desc_params));
+ if (desc_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ /*
+ * Fill-in the parameters
+ */
+ memset(pen, 0, sizeof(*pen));
+ pen->desc_ptr = desc_ptr;
+ desc_ptr = NULL;
+ pen->desc_size = uclogic_rdesc_pen_template_size;
+ pen->id = UCLOGIC_RDESC_PEN_ID;
+ pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED;
+ found = true;
+finish:
+ *pfound = found;
+ rc = 0;
+cleanup:
+ kfree(desc_ptr);
+ kfree(buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_frame_cleanup - free resources used by struct
+ * uclogic_params_frame (tablet interface's frame controls input parameters).
+ * Can be called repeatedly.
+ *
+ * @frame: Frame controls input parameters to cleanup. Cannot be NULL.
+ */
+static void uclogic_params_frame_cleanup(struct uclogic_params_frame *frame)
+{
+ kfree(frame->desc_ptr);
+ memset(frame, 0, sizeof(*frame));
+}
+
+/**
+ * uclogic_params_frame_init_with_desc() - initialize tablet's frame control
+ * parameters with a static report descriptor.
+ *
+ * @frame: Pointer to the frame parameters to initialize (to be cleaned
+ * up with uclogic_params_frame_cleanup()). Not modified in case
+ * of error. Cannot be NULL.
+ * @desc_ptr: Report descriptor pointer. Can be NULL, if desc_size is zero.
+ * @desc_size: Report descriptor size.
+ * @id: Report ID used for frame reports, if they should be tweaked,
+ * zero if not.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_frame_init_with_desc(
+ struct uclogic_params_frame *frame,
+ const __u8 *desc_ptr,
+ size_t desc_size,
+ unsigned int id)
+{
+ __u8 *copy_desc_ptr;
+
+ if (frame == NULL || (desc_ptr == NULL && desc_size != 0))
+ return -EINVAL;
+
+ copy_desc_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
+ if (copy_desc_ptr == NULL)
+ return -ENOMEM;
+
+ memset(frame, 0, sizeof(*frame));
+ frame->desc_ptr = copy_desc_ptr;
+ frame->desc_size = desc_size;
+ frame->id = id;
+ return 0;
+}
+
+/**
+ * uclogic_params_frame_init_buttonpad() - initialize abstract buttonpad
+ * on a tablet interface.
+ *
+ * @frame: Pointer to the frame parameters to initialize (to be cleaned
+ * up with uclogic_params_frame_cleanup()). Not modified in case
+ * of error, or if parameters are not found. Cannot be NULL.
+ * @pfound: Location for a flag which is set to true if the parameters
+ * were found, and to false if not (e.g. device was
+ * incompatible). Not modified in case of error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_frame_init_buttonpad(
+ struct uclogic_params_frame *frame,
+ bool *pfound,
+ struct hid_device *hdev)
+{
+ int rc;
+ bool found = false;
+ struct usb_device *usb_dev = hid_to_usb_dev(hdev);
+ char *str_buf = NULL;
+ const size_t str_len = 16;
+
+ /* Check arguments */
+ if (frame == NULL || pfound == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Enable generic button mode
+ */
+ str_buf = kzalloc(str_len, GFP_KERNEL);
+ if (str_buf == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+
+ rc = usb_string(usb_dev, 123, str_buf, str_len);
+ if (rc == -EPIPE) {
+ hid_dbg(hdev,
+ "generic button -enabling string descriptor not found\n");
+ } else if (rc < 0) {
+ goto cleanup;
+ } else if (strncmp(str_buf, "HK On", rc) != 0) {
+ hid_dbg(hdev,
+ "invalid response to enabling generic buttons: \"%s\"\n",
+ str_buf);
+ } else {
+ hid_dbg(hdev, "generic buttons enabled\n");
+ rc = uclogic_params_frame_init_with_desc(
+ frame,
+ uclogic_rdesc_buttonpad_arr,
+ uclogic_rdesc_buttonpad_size,
+ UCLOGIC_RDESC_BUTTONPAD_ID);
+ if (rc != 0)
+ goto cleanup;
+ found = true;
+ }
+
+ *pfound = found;
+ rc = 0;
+cleanup:
+ kfree(str_buf);
+ return rc;
+}
+
+/**
+ * uclogic_params_cleanup - free resources used by struct uclogic_params
+ * (tablet interface's parameters).
+ * Can be called repeatedly.
+ *
+ * @params: Input parameters to cleanup. Cannot be NULL.
+ */
+void uclogic_params_cleanup(struct uclogic_params *params)
+{
+ if (!params->invalid) {
+ kfree(params->desc_ptr);
+ if (!params->pen_unused)
+ uclogic_params_pen_cleanup(&params->pen);
+ uclogic_params_frame_cleanup(&params->frame);
+ memset(params, 0, sizeof(*params));
+ }
+}
+
+/**
+ * Get a replacement report descriptor for a tablet's interface.
+ *
+ * @params: The parameters of a tablet interface to get report
+ * descriptor for. Cannot be NULL.
+ * @pdesc: Location for the resulting, kmalloc-allocated report
+ * descriptor pointer, or for NULL, if there's no replacement
+ * report descriptor. Not modified in case of error. Cannot be
+ * NULL.
+ * @psize: Location for the resulting report descriptor size, not set if
+ * there's no replacement report descriptor. Not modified in case
+ * of error. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful.
+ * -EINVAL, if invalid arguments are supplied.
+ * -ENOMEM, if failed to allocate memory.
+ */
+int uclogic_params_get_desc(const struct uclogic_params *params,
+ __u8 **pdesc,
+ unsigned int *psize)
+{
+ bool common_present;
+ bool pen_present;
+ bool frame_present;
+ unsigned int size;
+ __u8 *desc = NULL;
+
+ /* Check arguments */
+ if (params == NULL || pdesc == NULL || psize == NULL)
+ return -EINVAL;
+
+ size = 0;
+
+ common_present = (params->desc_ptr != NULL);
+ pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL);
+ frame_present = (params->frame.desc_ptr != NULL);
+
+ if (common_present)
+ size += params->desc_size;
+ if (pen_present)
+ size += params->pen.desc_size;
+ if (frame_present)
+ size += params->frame.desc_size;
+
+ if (common_present || pen_present || frame_present) {
+ __u8 *p;
+
+ desc = kmalloc(size, GFP_KERNEL);
+ if (desc == NULL)
+ return -ENOMEM;
+ p = desc;
+
+ if (common_present) {
+ memcpy(p, params->desc_ptr,
+ params->desc_size);
+ p += params->desc_size;
+ }
+ if (pen_present) {
+ memcpy(p, params->pen.desc_ptr,
+ params->pen.desc_size);
+ p += params->pen.desc_size;
+ }
+ if (frame_present) {
+ memcpy(p, params->frame.desc_ptr,
+ params->frame.desc_size);
+ p += params->frame.desc_size;
+ }
+
+ WARN_ON(p != desc + size);
+
+ *psize = size;
+ }
+
+ *pdesc = desc;
+ return 0;
+}
+
+/**
+ * uclogic_params_init_invalid() - initialize tablet interface parameters,
+ * specifying the interface is invalid.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Cannot be NULL.
+ */
+static void uclogic_params_init_invalid(struct uclogic_params *params)
+{
+ params->invalid = true;
+}
+
+/**
+ * uclogic_params_init_with_opt_desc() - initialize tablet interface
+ * parameters with an optional replacement report descriptor. Only modify
+ * report descriptor, if the original report descriptor matches the expected
+ * size.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of
+ * error. Cannot be NULL.
+ * @hdev: The HID device of the tablet interface create the
+ * parameters for. Cannot be NULL.
+ * @orig_desc_size: Expected size of the original report descriptor to
+ * be replaced.
+ * @desc_ptr: Pointer to the replacement report descriptor.
+ * Can be NULL, if desc_size is zero.
+ * @desc_size: Size of the replacement report descriptor.
+ *
+ * Returns:
+ * Zero, if successful. -EINVAL if an invalid argument was passed.
+ * -ENOMEM, if failed to allocate memory.
+ */
+static int uclogic_params_init_with_opt_desc(struct uclogic_params *params,
+ struct hid_device *hdev,
+ unsigned int orig_desc_size,
+ __u8 *desc_ptr,
+ unsigned int desc_size)
+{
+ __u8 *desc_copy_ptr = NULL;
+ unsigned int desc_copy_size;
+ int rc;
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL ||
+ (desc_ptr == NULL && desc_size != 0)) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /* Replace report descriptor, if it matches */
+ if (hdev->dev_rsize == orig_desc_size) {
+ hid_dbg(hdev,
+ "device report descriptor matches the expected size, replacing\n");
+ desc_copy_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL);
+ if (desc_copy_ptr == NULL) {
+ rc = -ENOMEM;
+ goto cleanup;
+ }
+ desc_copy_size = desc_size;
+ } else {
+ hid_dbg(hdev,
+ "device report descriptor doesn't match the expected size (%u != %u), preserving\n",
+ hdev->dev_rsize, orig_desc_size);
+ desc_copy_ptr = NULL;
+ desc_copy_size = 0;
+ }
+
+ /* Output parameters */
+ memset(params, 0, sizeof(*params));
+ params->desc_ptr = desc_copy_ptr;
+ desc_copy_ptr = NULL;
+ params->desc_size = desc_copy_size;
+
+ rc = 0;
+cleanup:
+ kfree(desc_copy_ptr);
+ return rc;
+}
+
+/**
+ * uclogic_params_init_with_pen_unused() - initialize tablet interface
+ * parameters preserving original reports and generic HID processing, but
+ * disabling pen usage.
+ *
+ * @params: Parameters to initialize (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of
+ * error. Cannot be NULL.
+ */
+static void uclogic_params_init_with_pen_unused(struct uclogic_params *params)
+{
+ memset(params, 0, sizeof(*params));
+ params->pen_unused = true;
+}
+
+/**
+ * uclogic_params_init() - initialize a Huion tablet interface and discover
+ * its parameters.
+ *
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+static int uclogic_params_huion_init(struct uclogic_params *params,
+ struct hid_device *hdev)
+{
+ int rc;
+ struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+ __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+ bool found;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /* If it's not a pen interface */
+ if (bInterfaceNumber != 0) {
+ /* TODO: Consider marking the interface invalid */
+ uclogic_params_init_with_pen_unused(&p);
+ goto output;
+ }
+
+ /* Try to probe pen parameters */
+ rc = uclogic_params_pen_init(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev,
+ "failed probing pen parameters: %d\n", rc);
+ goto cleanup;
+ } else if (found) {
+ hid_dbg(hdev, "pen parameters found\n");
+ /* Try to probe buttonpad */
+ rc = uclogic_params_frame_init_buttonpad(
+ &p.frame,
+ &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc);
+ goto cleanup;
+ }
+ hid_dbg(hdev, "buttonpad parameters%s found\n",
+ (found ? "" : " not"));
+ if (found) {
+ /* Set bitmask marking frame reports */
+ p.pen_frame_flag = 0x20;
+ }
+ goto output;
+ }
+ hid_dbg(hdev, "pen parameters not found\n");
+
+ uclogic_params_init_invalid(&p);
+
+output:
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+ rc = 0;
+cleanup:
+ uclogic_params_cleanup(&p);
+ return rc;
+}
+
+/**
+ * uclogic_params_init() - initialize a tablet interface and discover its
+ * parameters.
+ *
+ * @params: Parameters to fill in (to be cleaned with
+ * uclogic_params_cleanup()). Not modified in case of error.
+ * Cannot be NULL.
+ * @hdev: The HID device of the tablet interface to initialize and get
+ * parameters from. Cannot be NULL.
+ *
+ * Returns:
+ * Zero, if successful. A negative errno code on error.
+ */
+int uclogic_params_init(struct uclogic_params *params,
+ struct hid_device *hdev)
+{
+ int rc;
+ struct usb_device *udev = hid_to_usb_dev(hdev);
+ __u8 bNumInterfaces = udev->config->desc.bNumInterfaces;
+ struct usb_interface *iface = to_usb_interface(hdev->dev.parent);
+ __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
+ bool found;
+ /* The resulting parameters (noop) */
+ struct uclogic_params p = {0, };
+
+ /* Check arguments */
+ if (params == NULL || hdev == NULL) {
+ rc = -EINVAL;
+ goto cleanup;
+ }
+
+ /*
+ * Set replacement report descriptor if the original matches the
+ * specified size. Otherwise keep interface unchanged.
+ */
+#define WITH_OPT_DESC(_orig_desc_token, _new_desc_token) \
+ uclogic_params_init_with_opt_desc( \
+ &p, hdev, \
+ UCLOGIC_RDESC_##_orig_desc_token##_SIZE, \
+ uclogic_rdesc_##_new_desc_token##_arr, \
+ uclogic_rdesc_##_new_desc_token##_size)
+
+#define VID_PID(_vid, _pid) \
+ (((__u32)(_vid) << 16) | ((__u32)(_pid) & U16_MAX))
+
+ /*
+ * Handle specific interfaces for specific tablets.
+ *
+ * Observe the following logic:
+ *
+ * If the interface is recognized as producing certain useful input:
+ * Mark interface as valid.
+ * Output interface parameters.
+ * Else, if the interface is recognized as *not* producing any useful
+ * input:
+ * Mark interface as invalid.
+ * Else:
+ * Mark interface as valid.
+ * Output noop parameters.
+ *
+ * Rule of thumb: it is better to disable a broken interface than let
+ * it spew garbage input.
+ */
+
+ switch (VID_PID(hdev->vendor, hdev->product)) {
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_PF1209):
+ rc = WITH_OPT_DESC(PF1209_ORIG, pf1209_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U):
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp4030u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U):
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp5540u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U):
+ rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp8060u_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_WP1062):
+ rc = WITH_OPT_DESC(WP1062_ORIG, wp1062_fixed);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850):
+ switch (bInterfaceNumber) {
+ case 0:
+ rc = WITH_OPT_DESC(TWHL850_ORIG0, twhl850_fixed0);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 1:
+ rc = WITH_OPT_DESC(TWHL850_ORIG1, twhl850_fixed1);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 2:
+ rc = WITH_OPT_DESC(TWHL850_ORIG2, twhl850_fixed2);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ }
+ break;
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60):
+ /*
+ * If it is not a three-interface version, which is known to
+ * respond to initialization.
+ */
+ if (bNumInterfaces != 3) {
+ switch (bInterfaceNumber) {
+ case 0:
+ rc = WITH_OPT_DESC(TWHA60_ORIG0,
+ twha60_fixed0);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case 1:
+ rc = WITH_OPT_DESC(TWHA60_ORIG1,
+ twha60_fixed1);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ }
+ break;
+ }
+ /* FALL THROUGH */
+ case VID_PID(USB_VENDOR_ID_HUION,
+ USB_DEVICE_ID_HUION_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_HUION_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_YIYNOVA_TABLET):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3):
+ case VID_PID(USB_VENDOR_ID_UCLOGIC,
+ USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45):
+ rc = uclogic_params_huion_init(&p, hdev);
+ if (rc != 0)
+ goto cleanup;
+ break;
+ case VID_PID(USB_VENDOR_ID_UGTIZER,
+ USB_DEVICE_ID_UGTIZER_TABLET_GP0610):
+ case VID_PID(USB_VENDOR_ID_UGEE,
+ USB_DEVICE_ID_UGEE_TABLET_EX07S):
+ /* If this is the pen interface */
+ if (bInterfaceNumber == 1) {
+ /* Probe pen parameters */
+ rc = uclogic_params_pen_init(&p.pen, &found, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "pen probing failed: %d\n", rc);
+ goto cleanup;
+ }
+ if (!found) {
+ hid_warn(hdev, "pen parameters not found");
+ uclogic_params_init_invalid(&p);
+ }
+ } else {
+ /* TODO: Consider marking the interface invalid */
+ uclogic_params_init_with_pen_unused(&p);
+ }
+ break;
+ }
+
+#undef VID_PID
+#undef WITH_OPT_DESC
+
+ /* Output parameters */
+ memcpy(params, &p, sizeof(*params));
+ memset(&p, 0, sizeof(p));
+ rc = 0;
+cleanup:
+ uclogic_params_cleanup(&p);
+ return rc;
+}