#include <linux/device.h>
#include <linux/hid.h>
#include <linux/module.h>
-#include <linux/usb.h>
#include "usbhid/usbhid.h"
-#include "hid-uclogic-rdesc.h"
+#include "hid-uclogic-params.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;
+ /* Interface parameters */
+ struct uclogic_params params;
+ /* Pointer to the replacement report descriptor. NULL if none. */
+ __u8 *desc_ptr;
+ /*
+ * Size of the replacement report descriptor.
+ * Only valid if desc_ptr is not NULL
+ */
+ unsigned int desc_size;
};
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;
+ if (drvdata->desc_ptr != NULL) {
+ rdesc = drvdata->desc_ptr;
+ *rsize = drvdata->desc_size;
}
-
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)
+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);
+ struct uclogic_params *params = &drvdata->params;
/* discard the unused pen interface */
- if ((drvdata->ignore_pen_usage) &&
- (field->application == HID_DG_PEN))
+ if (params->pen_unused && (field->application == HID_DG_PEN))
return -1;
/* let hid-core decide what to do */
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;
+ struct uclogic_drvdata *drvdata = NULL;
+ bool params_initialized = false;
/*
* libinput requires the pad interface to be on a different node
/* Allocate and assign driver data */
drvdata = devm_kzalloc(&hdev->dev, sizeof(*drvdata), GFP_KERNEL);
- if (drvdata == NULL)
- return -ENOMEM;
-
+ if (drvdata == NULL) {
+ rc = -ENOMEM;
+ goto failure;
+ }
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;
+ /* Initialize the device and retrieve interface parameters */
+ rc = uclogic_params_init(&drvdata->params, hdev);
+ if (rc != 0) {
+ hid_err(hdev, "failed probing parameters: %d\n", rc);
+ goto failure;
+ }
+ params_initialized = true;
+ hid_dbg(hdev, "parameters:\n" UCLOGIC_PARAMS_FMT_STR,
+ UCLOGIC_PARAMS_FMT_ARGS(&drvdata->params));
+ if (drvdata->params.invalid) {
+ hid_info(hdev, "interface is invalid, ignoring\n");
+ rc = -ENODEV;
+ goto failure;
+ }
- rc = uclogic_button_enable(hdev);
- drvdata->has_virtual_pad_interface = !rc;
- } else {
- drvdata->ignore_pen_usage = true;
- }
- }
- break;
+ /* Generate replacement report descriptor */
+ rc = uclogic_params_get_desc(&drvdata->params,
+ &drvdata->desc_ptr,
+ &drvdata->desc_size);
+ if (rc) {
+ hid_err(hdev,
+ "failed generating replacement report descriptor: %d\n",
+ rc);
+ goto failure;
}
rc = hid_parse(hdev);
if (rc) {
hid_err(hdev, "parse failed\n");
- return rc;
+ goto failure;
}
rc = hid_hw_start(hdev, HID_CONNECT_DEFAULT);
if (rc) {
hid_err(hdev, "hw start failed\n");
- return rc;
+ goto failure;
}
return 0;
+failure:
+ /* Assume "remove" might not be called if "probe" failed */
+ if (params_initialized)
+ uclogic_params_cleanup(&drvdata->params);
+ return rc;
}
-static int uclogic_raw_event(struct hid_device *hdev, struct hid_report *report,
- u8 *data, int size)
+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);
+ struct uclogic_params *params = &drvdata->params;
- if ((report->type == HID_INPUT_REPORT) &&
- (report->id == UCLOGIC_RDESC_PEN_ID) &&
+ /* Tweak pen reports, if necessary */
+ if (!params->pen_unused &&
+ (report->type == HID_INPUT_REPORT) &&
+ (report->id == params->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)
+ /* If it's the "virtual" frame controls report */
+ if (params->frame.id != 0 &&
+ data[1] & params->pen_frame_flag) {
+ /* Change to virtual frame controls report ID */
+ data[0] = params->frame.id;
+ return 0;
+ }
+ /* If in-range reports are inverted */
+ if (params->pen.inrange ==
+ UCLOGIC_PARAMS_PEN_INRANGE_INVERTED) {
/* Invert the in-range bit */
data[1] ^= 0x40;
+ }
}
return 0;
}
+static void uclogic_remove(struct hid_device *hdev)
+{
+ struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
+
+ hid_hw_stop(hdev);
+ kfree(drvdata->desc_ptr);
+ uclogic_params_cleanup(&drvdata->params);
+}
+
static const struct hid_device_id uclogic_devices[] = {
{ HID_USB_DEVICE(USB_VENDOR_ID_UCLOGIC,
USB_DEVICE_ID_UCLOGIC_TABLET_PF1209) },
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) },
+ { 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);
.name = "uclogic",
.id_table = uclogic_devices,
.probe = uclogic_probe,
+ .remove = uclogic_remove,
.report_fixup = uclogic_report_fixup,
.raw_event = uclogic_raw_event,
.input_mapping = uclogic_input_mapping,
--- /dev/null
+// 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(¶ms->pen);
+ uclogic_params_frame_cleanup(¶ms->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;
+}
--- /dev/null
+/* 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.
+ */
+
+#ifndef _HID_UCLOGIC_PARAMS_H
+#define _HID_UCLOGIC_PARAMS_H
+
+#include <linux/usb.h>
+#include <linux/hid.h>
+
+/* Types of pen in-range reporting */
+enum uclogic_params_pen_inrange {
+ /* Normal reports: zero - out of proximity, one - in proximity */
+ UCLOGIC_PARAMS_PEN_INRANGE_NORMAL = 0,
+ /* Inverted reports: zero - in proximity, one - out of proximity */
+ UCLOGIC_PARAMS_PEN_INRANGE_INVERTED,
+};
+
+/* Convert a pen in-range reporting type to a string */
+extern const char *uclogic_params_pen_inrange_to_str(
+ enum uclogic_params_pen_inrange inrange);
+
+/*
+ * Tablet interface's pen input parameters.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * Noop (preserving functionality) when filled with zeroes.
+ */
+struct uclogic_params_pen {
+ /*
+ * Pointer to report descriptor describing the inputs.
+ * Allocated with kmalloc.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /* Report ID, if reports should be tweaked, zero if not */
+ unsigned int id;
+ /* Type of in-range reporting, only valid if "id" is not zero */
+ enum uclogic_params_pen_inrange inrange;
+};
+
+/*
+ * Parameters of frame control inputs of a tablet interface.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * Noop (preserving functionality) when filled with zeroes.
+ */
+struct uclogic_params_frame {
+ /*
+ * Pointer to report descriptor describing the inputs.
+ * Allocated with kmalloc.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /*
+ * Report ID, if reports should be tweaked, zero if not.
+ */
+ unsigned int id;
+};
+
+/*
+ * Tablet interface report parameters.
+ *
+ * Must use declarative (descriptive) language, not imperative, to simplify
+ * understanding and maintain consistency.
+ *
+ * When filled with zeros represents a "noop" configuration - passes all
+ * reports unchanged and lets the generic HID driver handle everything.
+ *
+ * The resulting device report descriptor is assembled from all the report
+ * descriptor parts referenced by the structure. No order of assembly should
+ * be assumed. The structure represents original device report descriptor if
+ * all the parts are NULL.
+ */
+struct uclogic_params {
+ /*
+ * True if the whole interface is invalid, false otherwise.
+ */
+ bool invalid;
+ /*
+ * Pointer to the common part of the replacement report descriptor,
+ * allocated with kmalloc. NULL if no common part is needed.
+ * Only valid, if "invalid" is false.
+ */
+ __u8 *desc_ptr;
+ /*
+ * Size of the common part of the replacement report descriptor.
+ * Only valid, if "desc_ptr" is not NULL.
+ */
+ unsigned int desc_size;
+ /*
+ * True, if pen usage in report descriptor is invalid, when present.
+ * Only valid, if "invalid" is false.
+ */
+ bool pen_unused;
+ /*
+ * Pen parameters and optional report descriptor part.
+ * Only valid if "pen_unused" is valid and false.
+ */
+ struct uclogic_params_pen pen;
+ /*
+ * Frame control parameters and optional report descriptor part.
+ * Only valid, if "invalid" is false.
+ */
+ struct uclogic_params_frame frame;
+ /*
+ * Bitmask matching frame controls "sub-report" flag in the second
+ * byte of the pen report, or zero if it's not expected.
+ * Only valid if both "pen" and "frame" are valid, and "frame.id" is
+ * not zero.
+ */
+ __u8 pen_frame_flag;
+};
+
+/* Initialize a tablet interface and discover its parameters */
+extern int uclogic_params_init(struct uclogic_params *params,
+ struct hid_device *hdev);
+
+/* Tablet interface parameters *printf format string */
+#define UCLOGIC_PARAMS_FMT_STR \
+ ".invalid = %s\n" \
+ ".desc_ptr = %p\n" \
+ ".desc_size = %u\n" \
+ ".pen_unused = %s\n" \
+ ".pen.desc_ptr = %p\n" \
+ ".pen.desc_size = %u\n" \
+ ".pen.id = %u\n" \
+ ".pen.inrange = %s\n" \
+ ".frame.desc_ptr = %p\n" \
+ ".frame.desc_size = %u\n" \
+ ".frame.id = %u\n" \
+ ".pen_frame_flag = 0x%02x\n"
+
+/* Tablet interface parameters *printf format arguments */
+#define UCLOGIC_PARAMS_FMT_ARGS(_params) \
+ ((_params)->invalid ? "true" : "false"), \
+ (_params)->desc_ptr, \
+ (_params)->desc_size, \
+ ((_params)->pen_unused ? "true" : "false"), \
+ (_params)->pen.desc_ptr, \
+ (_params)->pen.desc_size, \
+ (_params)->pen.id, \
+ uclogic_params_pen_inrange_to_str((_params)->pen.inrange), \
+ (_params)->frame.desc_ptr, \
+ (_params)->frame.desc_size, \
+ (_params)->frame.id, \
+ (_params)->pen_frame_flag
+
+/* Get a replacement report descriptor for a tablet's interface. */
+extern int uclogic_params_get_desc(const struct uclogic_params *params,
+ __u8 **pdesc,
+ unsigned int *psize);
+
+/* Free resources used by tablet interface's parameters */
+extern void uclogic_params_cleanup(struct uclogic_params *params);
+
+#endif /* _HID_UCLOGIC_PARAMS_H */