diff options
-rw-r--r-- | drivers/hid/Makefile | 3 | ||||
-rw-r--r-- | drivers/hid/hid-uclogic-core.c | 428 | ||||
-rw-r--r-- | drivers/hid/hid-uclogic-params.c | 806 | ||||
-rw-r--r-- | drivers/hid/hid-uclogic-params.h | 180 |
4 files changed, 1090 insertions, 327 deletions
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index fb75366ea776..9b3a747af60d 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -109,7 +109,8 @@ 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 + hid-uclogic-rdesc.o \ + hid-uclogic-params.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 index 4042183ee9a3..72a3a43766cc 100644 --- a/drivers/hid/hid-uclogic-core.c +++ b/drivers/hid/hid-uclogic-core.c @@ -16,126 +16,48 @@ #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 */ @@ -189,160 +111,12 @@ static int uclogic_input_configured(struct hid_device *hdev, 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 @@ -352,104 +126,97 @@ static int uclogic_probe(struct hid_device *hdev, /* 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) }, @@ -465,14 +232,22 @@ static const struct hid_device_id uclogic_devices[] = { 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); @@ -481,6 +256,7 @@ static struct hid_driver uclogic_driver = { .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, 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(¶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; +} diff --git a/drivers/hid/hid-uclogic-params.h b/drivers/hid/hid-uclogic-params.h new file mode 100644 index 000000000000..4c78d9dd0576 --- /dev/null +++ b/drivers/hid/hid-uclogic-params.h @@ -0,0 +1,180 @@ +/* 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 */ |