1866 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1866 lines
		
	
	
		
			52 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // 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 <linux/string.h>
 | |
| #include <asm/unaligned.h>
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_pen_inrange_to_str() - Convert a pen in-range reporting type
 | |
|  *                                       to a string.
 | |
|  * @inrange:	The in-range reporting type to convert.
 | |
|  *
 | |
|  * Return:
 | |
|  * * The string representing the type, or
 | |
|  * * %NULL if the type is unknown.
 | |
|  */
 | |
| static 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";
 | |
| 	case UCLOGIC_PARAMS_PEN_INRANGE_NONE:
 | |
| 		return "none";
 | |
| 	default:
 | |
| 		return NULL;
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_pen_hid_dbg() - Dump tablet interface pen parameters
 | |
|  * @hdev:	The HID device the pen parameters describe.
 | |
|  * @pen:	The pen parameters to dump.
 | |
|  *
 | |
|  * Dump tablet interface pen parameters with hid_dbg(). The dump is indented
 | |
|  * with a tab.
 | |
|  */
 | |
| static void uclogic_params_pen_hid_dbg(const struct hid_device *hdev,
 | |
| 					const struct uclogic_params_pen *pen)
 | |
| {
 | |
| 	size_t i;
 | |
| 
 | |
| 	hid_dbg(hdev, "\t.usage_invalid = %s\n",
 | |
| 		(pen->usage_invalid ? "true" : "false"));
 | |
| 	hid_dbg(hdev, "\t.desc_ptr = %p\n", pen->desc_ptr);
 | |
| 	hid_dbg(hdev, "\t.desc_size = %u\n", pen->desc_size);
 | |
| 	hid_dbg(hdev, "\t.id = %u\n", pen->id);
 | |
| 	hid_dbg(hdev, "\t.subreport_list = {\n");
 | |
| 	for (i = 0; i < ARRAY_SIZE(pen->subreport_list); i++) {
 | |
| 		hid_dbg(hdev, "\t\t{0x%02hhx, %hhu}%s\n",
 | |
| 			pen->subreport_list[i].value,
 | |
| 			pen->subreport_list[i].id,
 | |
| 			i < (ARRAY_SIZE(pen->subreport_list) - 1) ? "," : "");
 | |
| 	}
 | |
| 	hid_dbg(hdev, "\t}\n");
 | |
| 	hid_dbg(hdev, "\t.inrange = %s\n",
 | |
| 		uclogic_params_pen_inrange_to_str(pen->inrange));
 | |
| 	hid_dbg(hdev, "\t.fragmented_hires = %s\n",
 | |
| 		(pen->fragmented_hires ? "true" : "false"));
 | |
| 	hid_dbg(hdev, "\t.tilt_y_flipped = %s\n",
 | |
| 		(pen->tilt_y_flipped ? "true" : "false"));
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_frame_hid_dbg() - Dump tablet interface frame parameters
 | |
|  * @hdev:	The HID device the pen parameters describe.
 | |
|  * @frame:	The frame parameters to dump.
 | |
|  *
 | |
|  * Dump tablet interface frame parameters with hid_dbg(). The dump is
 | |
|  * indented with two tabs.
 | |
|  */
 | |
| static void uclogic_params_frame_hid_dbg(
 | |
| 				const struct hid_device *hdev,
 | |
| 				const struct uclogic_params_frame *frame)
 | |
| {
 | |
| 	hid_dbg(hdev, "\t\t.desc_ptr = %p\n", frame->desc_ptr);
 | |
| 	hid_dbg(hdev, "\t\t.desc_size = %u\n", frame->desc_size);
 | |
| 	hid_dbg(hdev, "\t\t.id = %u\n", frame->id);
 | |
| 	hid_dbg(hdev, "\t\t.suffix = %s\n", frame->suffix);
 | |
| 	hid_dbg(hdev, "\t\t.re_lsb = %u\n", frame->re_lsb);
 | |
| 	hid_dbg(hdev, "\t\t.dev_id_byte = %u\n", frame->dev_id_byte);
 | |
| 	hid_dbg(hdev, "\t\t.touch_byte = %u\n", frame->touch_byte);
 | |
| 	hid_dbg(hdev, "\t\t.touch_max = %hhd\n", frame->touch_max);
 | |
| 	hid_dbg(hdev, "\t\t.touch_flip_at = %hhd\n",
 | |
| 		frame->touch_flip_at);
 | |
| 	hid_dbg(hdev, "\t\t.bitmap_dial_byte = %u\n",
 | |
| 		frame->bitmap_dial_byte);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_hid_dbg() - Dump tablet interface parameters
 | |
|  * @hdev:	The HID device the parameters describe.
 | |
|  * @params:	The parameters to dump.
 | |
|  *
 | |
|  * Dump tablet interface parameters with hid_dbg().
 | |
|  */
 | |
| void uclogic_params_hid_dbg(const struct hid_device *hdev,
 | |
| 				const struct uclogic_params *params)
 | |
| {
 | |
| 	size_t i;
 | |
| 
 | |
| 	hid_dbg(hdev, ".invalid = %s\n",
 | |
| 		params->invalid ? "true" : "false");
 | |
| 	hid_dbg(hdev, ".desc_ptr = %p\n", params->desc_ptr);
 | |
| 	hid_dbg(hdev, ".desc_size = %u\n", params->desc_size);
 | |
| 	hid_dbg(hdev, ".pen = {\n");
 | |
| 	uclogic_params_pen_hid_dbg(hdev, ¶ms->pen);
 | |
| 	hid_dbg(hdev, "\t}\n");
 | |
| 	hid_dbg(hdev, ".frame_list = {\n");
 | |
| 	for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
 | |
| 		hid_dbg(hdev, "\t{\n");
 | |
| 		uclogic_params_frame_hid_dbg(hdev, ¶ms->frame_list[i]);
 | |
| 		hid_dbg(hdev, "\t}%s\n",
 | |
| 			i < (ARRAY_SIZE(params->frame_list) - 1) ? "," : "");
 | |
| 	}
 | |
| 	hid_dbg(hdev, "}\n");
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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;
 | |
| 	__u8 *buf = NULL;
 | |
| 
 | |
| 	/* Check arguments */
 | |
| 	if (hdev == NULL) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	udev = hid_to_usb_dev(hdev);
 | |
| 
 | |
| 	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 #%u: %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_v1() - initialize tablet interface pen
 | |
|  * input and retrieve its parameters from the device, using v1 protocol.
 | |
|  *
 | |
|  * @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_v1(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_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_v1_pen_template_arr,
 | |
| 				uclogic_rdesc_v1_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_v1_pen_template_size;
 | |
| 	pen->id = UCLOGIC_RDESC_V1_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_get_le24() - get a 24-bit little-endian number from a
 | |
|  * buffer.
 | |
|  *
 | |
|  * @p:	The pointer to the number buffer.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	The retrieved number
 | |
|  */
 | |
| static s32 uclogic_params_get_le24(const void *p)
 | |
| {
 | |
| 	const __u8 *b = p;
 | |
| 	return b[0] | (b[1] << 8UL) | (b[2] << 16UL);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_pen_init_v2() - initialize tablet interface pen
 | |
|  * input and retrieve its parameters from the device, using v2 protocol.
 | |
|  *
 | |
|  * @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.
 | |
|  * @pparams_ptr:	Location for a kmalloc'ed pointer to the retrieved raw
 | |
|  *			parameters, which could be used to identify the tablet
 | |
|  *			to some extent. Should be freed with kfree after use.
 | |
|  *			NULL, if not needed. Not modified in case of error.
 | |
|  *			Only set if *pfound is set to true.
 | |
|  * @pparams_len:	Location for the length of the retrieved raw
 | |
|  *			parameters. NULL, if not needed. Not modified in case
 | |
|  *			of error. Only set if *pfound is set to true.
 | |
|  * @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_v2(struct uclogic_params_pen *pen,
 | |
| 					bool *pfound,
 | |
| 					__u8 **pparams_ptr,
 | |
| 					size_t *pparams_len,
 | |
| 					struct hid_device *hdev)
 | |
| {
 | |
| 	int rc;
 | |
| 	bool found = false;
 | |
| 	/* Buffer for (part of) the parameter string descriptor */
 | |
| 	__u8 *buf = NULL;
 | |
| 	/* Parameter string descriptor required length */
 | |
| 	const int params_len_min = 18;
 | |
| 	/* Parameter string descriptor accepted length */
 | |
| 	const int params_len_max = 32;
 | |
| 	/* Parameter string descriptor received length */
 | |
| 	int params_len;
 | |
| 	size_t i;
 | |
| 	s32 resolution;
 | |
| 	/* Pen report descriptor template parameters */
 | |
| 	s32 desc_params[UCLOGIC_RDESC_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, 200, params_len_max);
 | |
| 	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 < params_len_min) {
 | |
| 		hid_dbg(hdev,
 | |
| 			"string descriptor with pen parameters is too short (got %d, expected at least %d), assuming not compatible\n",
 | |
| 			rc, params_len_min);
 | |
| 		goto finish;
 | |
| 	}
 | |
| 
 | |
| 	params_len = rc;
 | |
| 
 | |
| 	/*
 | |
| 	 * Check it's not just a catch-all UTF-16LE-encoded ASCII
 | |
| 	 * string (such as the model name) some tablets put into all
 | |
| 	 * unknown string descriptors.
 | |
| 	 */
 | |
| 	for (i = 2;
 | |
| 	     i < params_len &&
 | |
| 		(buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0);
 | |
| 	     i += 2);
 | |
| 	if (i >= params_len) {
 | |
| 		hid_dbg(hdev,
 | |
| 			"string descriptor with pen parameters seems to contain only text, assuming not compatible\n");
 | |
| 		goto finish;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Fill report descriptor parameters from the string descriptor
 | |
| 	 */
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] =
 | |
| 		uclogic_params_get_le24(buf + 2);
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] =
 | |
| 		uclogic_params_get_le24(buf + 5);
 | |
| 	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;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Generate pen report descriptor
 | |
| 	 */
 | |
| 	desc_ptr = uclogic_rdesc_template_apply(
 | |
| 				uclogic_rdesc_v2_pen_template_arr,
 | |
| 				uclogic_rdesc_v2_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_v2_pen_template_size;
 | |
| 	pen->id = UCLOGIC_RDESC_V2_PEN_ID;
 | |
| 	pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE;
 | |
| 	pen->fragmented_hires = true;
 | |
| 	pen->tilt_y_flipped = true;
 | |
| 	found = true;
 | |
| 	if (pparams_ptr != NULL) {
 | |
| 		*pparams_ptr = buf;
 | |
| 		buf = NULL;
 | |
| 	}
 | |
| 	if (pparams_len != NULL)
 | |
| 		*pparams_len = params_len;
 | |
| 
 | |
| 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_v1() - initialize v1 tablet interface frame
 | |
|  * controls.
 | |
|  *
 | |
|  * @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_v1(struct uclogic_params_frame *frame,
 | |
| 					bool *pfound,
 | |
| 					struct hid_device *hdev)
 | |
| {
 | |
| 	int rc;
 | |
| 	bool found = false;
 | |
| 	struct usb_device *usb_dev;
 | |
| 	char *str_buf = NULL;
 | |
| 	const size_t str_len = 16;
 | |
| 
 | |
| 	/* Check arguments */
 | |
| 	if (frame == NULL || pfound == NULL || hdev == NULL) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	usb_dev = hid_to_usb_dev(hdev);
 | |
| 
 | |
| 	/*
 | |
| 	 * 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_v1_frame_arr,
 | |
| 				uclogic_rdesc_v1_frame_size,
 | |
| 				UCLOGIC_RDESC_V1_FRAME_ID);
 | |
| 		if (rc != 0)
 | |
| 			goto cleanup;
 | |
| 		found = true;
 | |
| 	}
 | |
| 
 | |
| 	*pfound = found;
 | |
| 	rc = 0;
 | |
| cleanup:
 | |
| 	kfree(str_buf);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_cleanup_event_hooks - free resources used by the list of raw
 | |
|  * event hooks.
 | |
|  * Can be called repeatedly.
 | |
|  *
 | |
|  * @params: Input parameters to cleanup. Cannot be NULL.
 | |
|  */
 | |
| static void uclogic_params_cleanup_event_hooks(struct uclogic_params *params)
 | |
| {
 | |
| 	struct uclogic_raw_event_hook *curr, *n;
 | |
| 
 | |
| 	if (!params || !params->event_hooks)
 | |
| 		return;
 | |
| 
 | |
| 	list_for_each_entry_safe(curr, n, ¶ms->event_hooks->list, list) {
 | |
| 		cancel_work_sync(&curr->work);
 | |
| 		list_del(&curr->list);
 | |
| 		kfree(curr->event);
 | |
| 		kfree(curr);
 | |
| 	}
 | |
| 
 | |
| 	kfree(params->event_hooks);
 | |
| 	params->event_hooks = NULL;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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) {
 | |
| 		size_t i;
 | |
| 		kfree(params->desc_ptr);
 | |
| 		uclogic_params_pen_cleanup(¶ms->pen);
 | |
| 		for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
 | |
| 			uclogic_params_frame_cleanup(¶ms->frame_list[i]);
 | |
| 
 | |
| 		uclogic_params_cleanup_event_hooks(params);
 | |
| 		memset(params, 0, sizeof(*params));
 | |
| 	}
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_get_desc() - 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)
 | |
| {
 | |
| 	int rc = -ENOMEM;
 | |
| 	bool present = false;
 | |
| 	unsigned int size = 0;
 | |
| 	__u8 *desc = NULL;
 | |
| 	size_t i;
 | |
| 
 | |
| 	/* Check arguments */
 | |
| 	if (params == NULL || pdesc == NULL || psize == NULL)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	/* Concatenate descriptors */
 | |
| #define ADD_DESC(_desc_ptr, _desc_size) \
 | |
| 	do {                                                        \
 | |
| 		unsigned int new_size;                              \
 | |
| 		__u8 *new_desc;                                     \
 | |
| 		if ((_desc_ptr) == NULL) {                          \
 | |
| 			break;                                      \
 | |
| 		}                                                   \
 | |
| 		new_size = size + (_desc_size);                     \
 | |
| 		new_desc = krealloc(desc, new_size, GFP_KERNEL);    \
 | |
| 		if (new_desc == NULL) {                             \
 | |
| 			goto cleanup;                               \
 | |
| 		}                                                   \
 | |
| 		memcpy(new_desc + size, (_desc_ptr), (_desc_size)); \
 | |
| 		desc = new_desc;                                    \
 | |
| 		size = new_size;                                    \
 | |
| 		present = true;                                     \
 | |
| 	} while (0)
 | |
| 
 | |
| 	ADD_DESC(params->desc_ptr, params->desc_size);
 | |
| 	ADD_DESC(params->pen.desc_ptr, params->pen.desc_size);
 | |
| 	for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) {
 | |
| 		ADD_DESC(params->frame_list[i].desc_ptr,
 | |
| 				params->frame_list[i].desc_size);
 | |
| 	}
 | |
| 
 | |
| #undef ADD_DESC
 | |
| 
 | |
| 	if (present) {
 | |
| 		*pdesc = desc;
 | |
| 		*psize = size;
 | |
| 		desc = NULL;
 | |
| 	}
 | |
| 	rc = 0;
 | |
| cleanup:
 | |
| 	kfree(desc);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * 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_huion_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_device *udev;
 | |
| 	struct usb_interface *iface;
 | |
| 	__u8 bInterfaceNumber;
 | |
| 	bool found;
 | |
| 	/* The resulting parameters (noop) */
 | |
| 	struct uclogic_params p = {0, };
 | |
| 	static const char transition_ver[] = "HUION_T153_160607";
 | |
| 	char *ver_ptr = NULL;
 | |
| 	const size_t ver_len = sizeof(transition_ver) + 1;
 | |
| 	__u8 *params_ptr = NULL;
 | |
| 	size_t params_len = 0;
 | |
| 	/* Parameters string descriptor of a model with touch ring (HS610) */
 | |
| 	const __u8 touch_ring_model_params_buf[] = {
 | |
| 		0x13, 0x03, 0x70, 0xC6, 0x00, 0x06, 0x7C, 0x00,
 | |
| 		0xFF, 0x1F, 0xD8, 0x13, 0x03, 0x0D, 0x10, 0x01,
 | |
| 		0x04, 0x3C, 0x3E
 | |
| 	};
 | |
| 
 | |
| 	/* Check arguments */
 | |
| 	if (params == NULL || hdev == NULL) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	udev = hid_to_usb_dev(hdev);
 | |
| 	iface = to_usb_interface(hdev->dev.parent);
 | |
| 	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
 | |
| 
 | |
| 	/* If it's a custom keyboard interface */
 | |
| 	if (bInterfaceNumber == 1) {
 | |
| 		/* Keep everything intact, but mark pen usage invalid */
 | |
| 		p.pen.usage_invalid = true;
 | |
| 		goto output;
 | |
| 	/* Else, if it's not a pen interface */
 | |
| 	} else if (bInterfaceNumber != 0) {
 | |
| 		uclogic_params_init_invalid(&p);
 | |
| 		goto output;
 | |
| 	}
 | |
| 
 | |
| 	/* Try to get firmware version */
 | |
| 	ver_ptr = kzalloc(ver_len, GFP_KERNEL);
 | |
| 	if (ver_ptr == NULL) {
 | |
| 		rc = -ENOMEM;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 	rc = usb_string(udev, 201, ver_ptr, ver_len);
 | |
| 	if (rc == -EPIPE) {
 | |
| 		*ver_ptr = '\0';
 | |
| 	} else if (rc < 0) {
 | |
| 		hid_err(hdev,
 | |
| 			"failed retrieving Huion firmware version: %d\n", rc);
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	/* The firmware is used in userspace as unique identifier */
 | |
| 	strscpy(hdev->uniq, ver_ptr, sizeof(hdev->uniq));
 | |
| 
 | |
| 	/* If this is a transition firmware */
 | |
| 	if (strcmp(ver_ptr, transition_ver) == 0) {
 | |
| 		hid_dbg(hdev,
 | |
| 			"transition firmware detected, not probing pen v2 parameters\n");
 | |
| 	} else {
 | |
| 		/* Try to probe v2 pen parameters */
 | |
| 		rc = uclogic_params_pen_init_v2(&p.pen, &found,
 | |
| 						¶ms_ptr, ¶ms_len,
 | |
| 						hdev);
 | |
| 		if (rc != 0) {
 | |
| 			hid_err(hdev,
 | |
| 				"failed probing pen v2 parameters: %d\n", rc);
 | |
| 			goto cleanup;
 | |
| 		} else if (found) {
 | |
| 			hid_dbg(hdev, "pen v2 parameters found\n");
 | |
| 			/* Create v2 frame button parameters */
 | |
| 			rc = uclogic_params_frame_init_with_desc(
 | |
| 					&p.frame_list[0],
 | |
| 					uclogic_rdesc_v2_frame_buttons_arr,
 | |
| 					uclogic_rdesc_v2_frame_buttons_size,
 | |
| 					UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID);
 | |
| 			if (rc != 0) {
 | |
| 				hid_err(hdev,
 | |
| 					"failed creating v2 frame button parameters: %d\n",
 | |
| 					rc);
 | |
| 				goto cleanup;
 | |
| 			}
 | |
| 
 | |
| 			/* Link from pen sub-report */
 | |
| 			p.pen.subreport_list[0].value = 0xe0;
 | |
| 			p.pen.subreport_list[0].id =
 | |
| 				UCLOGIC_RDESC_V2_FRAME_BUTTONS_ID;
 | |
| 
 | |
| 			/* If this is the model with touch ring */
 | |
| 			if (params_ptr != NULL &&
 | |
| 			    params_len == sizeof(touch_ring_model_params_buf) &&
 | |
| 			    memcmp(params_ptr, touch_ring_model_params_buf,
 | |
| 				   params_len) == 0) {
 | |
| 				/* Create touch ring parameters */
 | |
| 				rc = uclogic_params_frame_init_with_desc(
 | |
| 					&p.frame_list[1],
 | |
| 					uclogic_rdesc_v2_frame_touch_ring_arr,
 | |
| 					uclogic_rdesc_v2_frame_touch_ring_size,
 | |
| 					UCLOGIC_RDESC_V2_FRAME_TOUCH_ID);
 | |
| 				if (rc != 0) {
 | |
| 					hid_err(hdev,
 | |
| 						"failed creating v2 frame touch ring parameters: %d\n",
 | |
| 						rc);
 | |
| 					goto cleanup;
 | |
| 				}
 | |
| 				p.frame_list[1].suffix = "Touch Ring";
 | |
| 				p.frame_list[1].dev_id_byte =
 | |
| 					UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE;
 | |
| 				p.frame_list[1].touch_byte = 5;
 | |
| 				p.frame_list[1].touch_max = 12;
 | |
| 				p.frame_list[1].touch_flip_at = 7;
 | |
| 			} else {
 | |
| 				/* Create touch strip parameters */
 | |
| 				rc = uclogic_params_frame_init_with_desc(
 | |
| 					&p.frame_list[1],
 | |
| 					uclogic_rdesc_v2_frame_touch_strip_arr,
 | |
| 					uclogic_rdesc_v2_frame_touch_strip_size,
 | |
| 					UCLOGIC_RDESC_V2_FRAME_TOUCH_ID);
 | |
| 				if (rc != 0) {
 | |
| 					hid_err(hdev,
 | |
| 						"failed creating v2 frame touch strip parameters: %d\n",
 | |
| 						rc);
 | |
| 					goto cleanup;
 | |
| 				}
 | |
| 				p.frame_list[1].suffix = "Touch Strip";
 | |
| 				p.frame_list[1].dev_id_byte =
 | |
| 					UCLOGIC_RDESC_V2_FRAME_TOUCH_DEV_ID_BYTE;
 | |
| 				p.frame_list[1].touch_byte = 5;
 | |
| 				p.frame_list[1].touch_max = 8;
 | |
| 			}
 | |
| 
 | |
| 			/* Link from pen sub-report */
 | |
| 			p.pen.subreport_list[1].value = 0xf0;
 | |
| 			p.pen.subreport_list[1].id =
 | |
| 				UCLOGIC_RDESC_V2_FRAME_TOUCH_ID;
 | |
| 
 | |
| 			/* Create v2 frame dial parameters */
 | |
| 			rc = uclogic_params_frame_init_with_desc(
 | |
| 					&p.frame_list[2],
 | |
| 					uclogic_rdesc_v2_frame_dial_arr,
 | |
| 					uclogic_rdesc_v2_frame_dial_size,
 | |
| 					UCLOGIC_RDESC_V2_FRAME_DIAL_ID);
 | |
| 			if (rc != 0) {
 | |
| 				hid_err(hdev,
 | |
| 					"failed creating v2 frame dial parameters: %d\n",
 | |
| 					rc);
 | |
| 				goto cleanup;
 | |
| 			}
 | |
| 			p.frame_list[2].suffix = "Dial";
 | |
| 			p.frame_list[2].dev_id_byte =
 | |
| 				UCLOGIC_RDESC_V2_FRAME_DIAL_DEV_ID_BYTE;
 | |
| 			p.frame_list[2].bitmap_dial_byte = 5;
 | |
| 
 | |
| 			/* Link from pen sub-report */
 | |
| 			p.pen.subreport_list[2].value = 0xf1;
 | |
| 			p.pen.subreport_list[2].id =
 | |
| 				UCLOGIC_RDESC_V2_FRAME_DIAL_ID;
 | |
| 
 | |
| 			goto output;
 | |
| 		}
 | |
| 		hid_dbg(hdev, "pen v2 parameters not found\n");
 | |
| 	}
 | |
| 
 | |
| 	/* Try to probe v1 pen parameters */
 | |
| 	rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
 | |
| 	if (rc != 0) {
 | |
| 		hid_err(hdev,
 | |
| 			"failed probing pen v1 parameters: %d\n", rc);
 | |
| 		goto cleanup;
 | |
| 	} else if (found) {
 | |
| 		hid_dbg(hdev, "pen v1 parameters found\n");
 | |
| 		/* Try to probe v1 frame */
 | |
| 		rc = uclogic_params_frame_init_v1(&p.frame_list[0],
 | |
| 						  &found, hdev);
 | |
| 		if (rc != 0) {
 | |
| 			hid_err(hdev, "v1 frame probing failed: %d\n", rc);
 | |
| 			goto cleanup;
 | |
| 		}
 | |
| 		hid_dbg(hdev, "frame v1 parameters%s found\n",
 | |
| 			(found ? "" : " not"));
 | |
| 		if (found) {
 | |
| 			/* Link frame button subreports from pen reports */
 | |
| 			p.pen.subreport_list[0].value = 0xe0;
 | |
| 			p.pen.subreport_list[0].id =
 | |
| 				UCLOGIC_RDESC_V1_FRAME_ID;
 | |
| 		}
 | |
| 		goto output;
 | |
| 	}
 | |
| 	hid_dbg(hdev, "pen v1 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:
 | |
| 	kfree(params_ptr);
 | |
| 	kfree(ver_ptr);
 | |
| 	uclogic_params_cleanup(&p);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_probe_interface() - some tablets, like the Parblo A610 PLUS V2 or
 | |
|  * the XP-PEN Deco Mini 7, need to be initialized by sending them magic data.
 | |
|  *
 | |
|  * @hdev:	The HID device of the tablet interface to initialize and get
 | |
|  *		parameters from. Cannot be NULL.
 | |
|  * @magic_arr:	The magic data that should be sent to probe the interface.
 | |
|  *		Cannot be NULL.
 | |
|  * @magic_size:	Size of the magic data.
 | |
|  * @endpoint:	Endpoint where the magic data should be sent.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_probe_interface(struct hid_device *hdev, const u8 *magic_arr,
 | |
| 				   size_t magic_size, int endpoint)
 | |
| {
 | |
| 	struct usb_device *udev;
 | |
| 	unsigned int pipe = 0;
 | |
| 	int sent;
 | |
| 	u8 *buf = NULL;
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	if (!hdev || !magic_arr) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	buf = kmemdup(magic_arr, magic_size, GFP_KERNEL);
 | |
| 	if (!buf) {
 | |
| 		rc = -ENOMEM;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	udev = hid_to_usb_dev(hdev);
 | |
| 	pipe = usb_sndintpipe(udev, endpoint);
 | |
| 
 | |
| 	rc = usb_interrupt_msg(udev, pipe, buf, magic_size, &sent, 1000);
 | |
| 	if (rc || sent != magic_size) {
 | |
| 		hid_err(hdev, "Interface probing failed: %d\n", rc);
 | |
| 		rc = -1;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	rc = 0;
 | |
| cleanup:
 | |
| 	kfree(buf);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_parse_ugee_v2_desc - parse the string descriptor containing
 | |
|  * pen and frame parameters returned by UGEE v2 devices.
 | |
|  *
 | |
|  * @str_desc:		String descriptor, cannot be NULL.
 | |
|  * @str_desc_size:	Size of the string descriptor.
 | |
|  * @desc_params:	Output description params list.
 | |
|  * @desc_params_size:	Size of the output description params list.
 | |
|  * @frame_type:		Output frame type.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_parse_ugee_v2_desc(const __u8 *str_desc,
 | |
| 					     size_t str_desc_size,
 | |
| 					     s32 *desc_params,
 | |
| 					     size_t desc_params_size,
 | |
| 					     enum uclogic_params_frame_type *frame_type)
 | |
| {
 | |
| 	s32 pen_x_lm, pen_y_lm;
 | |
| 	s32 pen_x_pm, pen_y_pm;
 | |
| 	s32 pen_pressure_lm;
 | |
| 	s32 frame_num_buttons;
 | |
| 	s32 resolution;
 | |
| 
 | |
| 	/* Minimum descriptor length required, maximum seen so far is 14 */
 | |
| 	const int min_str_desc_size = 12;
 | |
| 
 | |
| 	if (!str_desc || str_desc_size < min_str_desc_size)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	pen_x_lm = get_unaligned_le16(str_desc + 2);
 | |
| 	pen_y_lm = get_unaligned_le16(str_desc + 4);
 | |
| 	frame_num_buttons = str_desc[6];
 | |
| 	*frame_type = str_desc[7];
 | |
| 	pen_pressure_lm = get_unaligned_le16(str_desc + 8);
 | |
| 
 | |
| 	resolution = get_unaligned_le16(str_desc + 10);
 | |
| 	if (resolution == 0) {
 | |
| 		pen_x_pm = 0;
 | |
| 		pen_y_pm = 0;
 | |
| 	} else {
 | |
| 		pen_x_pm = pen_x_lm * 1000 / resolution;
 | |
| 		pen_y_pm = pen_y_lm * 1000 / resolution;
 | |
| 	}
 | |
| 
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = pen_x_lm;
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = pen_x_pm;
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = pen_y_lm;
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = pen_y_pm;
 | |
| 	desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = pen_pressure_lm;
 | |
| 	desc_params[UCLOGIC_RDESC_FRAME_PH_ID_UM] = frame_num_buttons;
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init_frame_buttons() - initialize a UGEE v2 frame with
 | |
|  * buttons.
 | |
|  * @p:			Parameters to fill in, cannot be NULL.
 | |
|  * @desc_params:	Device description params list.
 | |
|  * @desc_params_size:	Size of the description params list.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_ugee_v2_init_frame_buttons(struct uclogic_params *p,
 | |
| 						     const s32 *desc_params,
 | |
| 						     size_t desc_params_size)
 | |
| {
 | |
| 	__u8 *rdesc_frame = NULL;
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	if (!p || desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	rdesc_frame = uclogic_rdesc_template_apply(
 | |
| 				uclogic_rdesc_ugee_v2_frame_btn_template_arr,
 | |
| 				uclogic_rdesc_ugee_v2_frame_btn_template_size,
 | |
| 				desc_params, UCLOGIC_RDESC_PH_ID_NUM);
 | |
| 	if (!rdesc_frame)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	rc = uclogic_params_frame_init_with_desc(&p->frame_list[0],
 | |
| 						 rdesc_frame,
 | |
| 						 uclogic_rdesc_ugee_v2_frame_btn_template_size,
 | |
| 						 UCLOGIC_RDESC_V1_FRAME_ID);
 | |
| 	kfree(rdesc_frame);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init_frame_dial() - initialize a UGEE v2 frame with a
 | |
|  * bitmap dial.
 | |
|  * @p:			Parameters to fill in, cannot be NULL.
 | |
|  * @desc_params:	Device description params list.
 | |
|  * @desc_params_size:	Size of the description params list.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_ugee_v2_init_frame_dial(struct uclogic_params *p,
 | |
| 						  const s32 *desc_params,
 | |
| 						  size_t desc_params_size)
 | |
| {
 | |
| 	__u8 *rdesc_frame = NULL;
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	if (!p || desc_params_size != UCLOGIC_RDESC_PH_ID_NUM)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	rdesc_frame = uclogic_rdesc_template_apply(
 | |
| 				uclogic_rdesc_ugee_v2_frame_dial_template_arr,
 | |
| 				uclogic_rdesc_ugee_v2_frame_dial_template_size,
 | |
| 				desc_params, UCLOGIC_RDESC_PH_ID_NUM);
 | |
| 	if (!rdesc_frame)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	rc = uclogic_params_frame_init_with_desc(&p->frame_list[0],
 | |
| 						 rdesc_frame,
 | |
| 						 uclogic_rdesc_ugee_v2_frame_dial_template_size,
 | |
| 						 UCLOGIC_RDESC_V1_FRAME_ID);
 | |
| 	kfree(rdesc_frame);
 | |
| 	if (rc)
 | |
| 		return rc;
 | |
| 
 | |
| 	p->frame_list[0].bitmap_dial_byte = 7;
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init_frame_mouse() - initialize a UGEE v2 frame with a
 | |
|  * mouse.
 | |
|  * @p:			Parameters to fill in, cannot be NULL.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_ugee_v2_init_frame_mouse(struct uclogic_params *p)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	if (!p)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	rc = uclogic_params_frame_init_with_desc(&p->frame_list[1],
 | |
| 						 uclogic_rdesc_ugee_v2_frame_mouse_template_arr,
 | |
| 						 uclogic_rdesc_ugee_v2_frame_mouse_template_size,
 | |
| 						 UCLOGIC_RDESC_V1_FRAME_ID);
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_has_battery() - check whether a UGEE v2 device has
 | |
|  * battery or not.
 | |
|  * @hdev:	The HID device of the tablet interface.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	True if the device has battery, false otherwise.
 | |
|  */
 | |
| static bool uclogic_params_ugee_v2_has_battery(struct hid_device *hdev)
 | |
| {
 | |
| 	struct uclogic_drvdata *drvdata = hid_get_drvdata(hdev);
 | |
| 
 | |
| 	if (drvdata->quirks & UCLOGIC_BATTERY_QUIRK)
 | |
| 		return true;
 | |
| 
 | |
| 	/* The XP-PEN Deco LW vendor, product and version are identical to the
 | |
| 	 * Deco L. The only difference reported by their firmware is the product
 | |
| 	 * name. Add a quirk to support battery reporting on the wireless
 | |
| 	 * version.
 | |
| 	 */
 | |
| 	if (hdev->vendor == USB_VENDOR_ID_UGEE &&
 | |
| 	    hdev->product == USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L) {
 | |
| 		struct usb_device *udev = hid_to_usb_dev(hdev);
 | |
| 
 | |
| 		if (strstarts(udev->product, "Deco LW"))
 | |
| 			return true;
 | |
| 	}
 | |
| 
 | |
| 	return false;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init_battery() - initialize UGEE v2 battery reporting.
 | |
|  * @hdev:	The HID device of the tablet interface, cannot be NULL.
 | |
|  * @p:		Parameters to fill in, cannot be NULL.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
 | |
| 					       struct uclogic_params *p)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 
 | |
| 	if (!hdev || !p)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	/* Some tablets contain invalid characters in hdev->uniq, throwing a
 | |
| 	 * "hwmon: '<name>' is not a valid name attribute, please fix" error.
 | |
| 	 * Use the device vendor and product IDs instead.
 | |
| 	 */
 | |
| 	snprintf(hdev->uniq, sizeof(hdev->uniq), "%x-%x", hdev->vendor,
 | |
| 		 hdev->product);
 | |
| 
 | |
| 	rc = uclogic_params_frame_init_with_desc(&p->frame_list[1],
 | |
| 						 uclogic_rdesc_ugee_v2_battery_template_arr,
 | |
| 						 uclogic_rdesc_ugee_v2_battery_template_size,
 | |
| 						 UCLOGIC_RDESC_UGEE_V2_BATTERY_ID);
 | |
| 	if (rc)
 | |
| 		return rc;
 | |
| 
 | |
| 	p->frame_list[1].suffix = "Battery";
 | |
| 	p->pen.subreport_list[1].value = 0xf2;
 | |
| 	p->pen.subreport_list[1].id = UCLOGIC_RDESC_UGEE_V2_BATTERY_ID;
 | |
| 
 | |
| 	return rc;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
 | |
|  * connection to the USB dongle and reconnects, either because of its physical
 | |
|  * distance or because it was switches off and on using the frame's switch,
 | |
|  * uclogic_probe_interface() needs to be called again to enable the tablet.
 | |
|  *
 | |
|  * @work: The work that triggered this function.
 | |
|  */
 | |
| static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
 | |
| {
 | |
| 	struct uclogic_raw_event_hook *event_hook;
 | |
| 
 | |
| 	event_hook = container_of(work, struct uclogic_raw_event_hook, work);
 | |
| 	uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
 | |
| 				uclogic_ugee_v2_probe_size,
 | |
| 				uclogic_ugee_v2_probe_endpoint);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
 | |
|  * to be hooked for UGEE v2 devices.
 | |
|  * @hdev:	The HID device of the tablet interface to initialize and get
 | |
|  *		parameters from.
 | |
|  * @p:		Parameters to fill in, cannot be NULL.
 | |
|  *
 | |
|  * Returns:
 | |
|  *	Zero, if successful. A negative errno code on error.
 | |
|  */
 | |
| static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
 | |
| 						   struct uclogic_params *p)
 | |
| {
 | |
| 	struct uclogic_raw_event_hook *event_hook;
 | |
| 	__u8 reconnect_event[] = {
 | |
| 		/* Event received on wireless tablet reconnection */
 | |
| 		0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
 | |
| 	};
 | |
| 
 | |
| 	if (!p)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	/* The reconnection event is only received if the tablet has battery */
 | |
| 	if (!uclogic_params_ugee_v2_has_battery(hdev))
 | |
| 		return 0;
 | |
| 
 | |
| 	p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
 | |
| 	if (!p->event_hooks)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	INIT_LIST_HEAD(&p->event_hooks->list);
 | |
| 
 | |
| 	event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
 | |
| 	if (!event_hook)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
 | |
| 	event_hook->hdev = hdev;
 | |
| 	event_hook->size = ARRAY_SIZE(reconnect_event);
 | |
| 	event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
 | |
| 	if (!event_hook->event)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	list_add_tail(&event_hook->list, &p->event_hooks->list);
 | |
| 
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
 | |
|  * discovering their parameters.
 | |
|  *
 | |
|  * These tables, internally designed as v2 to differentiate them from older
 | |
|  * models, expect a payload of magic data in orther to be switched to the fully
 | |
|  * functional mode and expose their parameters in a similar way to the
 | |
|  * information present in uclogic_params_pen_init_v1() but with some
 | |
|  * differences.
 | |
|  *
 | |
|  * @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_ugee_v2_init(struct uclogic_params *params,
 | |
| 				       struct hid_device *hdev)
 | |
| {
 | |
| 	int rc = 0;
 | |
| 	struct uclogic_drvdata *drvdata;
 | |
| 	struct usb_interface *iface;
 | |
| 	__u8 bInterfaceNumber;
 | |
| 	const int str_desc_len = 12;
 | |
| 	__u8 *str_desc = NULL;
 | |
| 	__u8 *rdesc_pen = NULL;
 | |
| 	s32 desc_params[UCLOGIC_RDESC_PH_ID_NUM];
 | |
| 	enum uclogic_params_frame_type frame_type;
 | |
| 	/* The resulting parameters (noop) */
 | |
| 	struct uclogic_params p = {0, };
 | |
| 
 | |
| 	if (!params || !hdev) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	drvdata = hid_get_drvdata(hdev);
 | |
| 	iface = to_usb_interface(hdev->dev.parent);
 | |
| 	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
 | |
| 
 | |
| 	if (bInterfaceNumber == 0) {
 | |
| 		rc = uclogic_params_ugee_v2_init_frame_mouse(&p);
 | |
| 		if (rc)
 | |
| 			goto cleanup;
 | |
| 
 | |
| 		goto output;
 | |
| 	}
 | |
| 
 | |
| 	if (bInterfaceNumber != 2) {
 | |
| 		uclogic_params_init_invalid(&p);
 | |
| 		goto output;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Initialize the interface by sending magic data.
 | |
| 	 * The specific data was discovered by sniffing the Windows driver
 | |
| 	 * traffic.
 | |
| 	 */
 | |
| 	rc = uclogic_probe_interface(hdev, uclogic_ugee_v2_probe_arr,
 | |
| 				     uclogic_ugee_v2_probe_size,
 | |
| 				     uclogic_ugee_v2_probe_endpoint);
 | |
| 	if (rc) {
 | |
| 		uclogic_params_init_invalid(&p);
 | |
| 		goto output;
 | |
| 	}
 | |
| 
 | |
| 	/*
 | |
| 	 * Read the string descriptor containing pen and frame parameters.
 | |
| 	 * The specific string descriptor and data were discovered by sniffing
 | |
| 	 * the Windows driver traffic.
 | |
| 	 */
 | |
| 	rc = uclogic_params_get_str_desc(&str_desc, hdev, 100, str_desc_len);
 | |
| 	if (rc != str_desc_len) {
 | |
| 		hid_err(hdev, "failed retrieving pen and frame parameters: %d\n", rc);
 | |
| 		uclogic_params_init_invalid(&p);
 | |
| 		goto output;
 | |
| 	}
 | |
| 
 | |
| 	rc = uclogic_params_parse_ugee_v2_desc(str_desc, str_desc_len,
 | |
| 					       desc_params,
 | |
| 					       ARRAY_SIZE(desc_params),
 | |
| 					       &frame_type);
 | |
| 	if (rc)
 | |
| 		goto cleanup;
 | |
| 
 | |
| 	kfree(str_desc);
 | |
| 	str_desc = NULL;
 | |
| 
 | |
| 	/* Initialize the pen interface */
 | |
| 	rdesc_pen = uclogic_rdesc_template_apply(
 | |
| 				uclogic_rdesc_ugee_v2_pen_template_arr,
 | |
| 				uclogic_rdesc_ugee_v2_pen_template_size,
 | |
| 				desc_params, ARRAY_SIZE(desc_params));
 | |
| 	if (!rdesc_pen) {
 | |
| 		rc = -ENOMEM;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	p.pen.desc_ptr = rdesc_pen;
 | |
| 	p.pen.desc_size = uclogic_rdesc_ugee_v2_pen_template_size;
 | |
| 	p.pen.id = 0x02;
 | |
| 	p.pen.subreport_list[0].value = 0xf0;
 | |
| 	p.pen.subreport_list[0].id = UCLOGIC_RDESC_V1_FRAME_ID;
 | |
| 
 | |
| 	/* Initialize the frame interface */
 | |
| 	if (drvdata->quirks & UCLOGIC_MOUSE_FRAME_QUIRK)
 | |
| 		frame_type = UCLOGIC_PARAMS_FRAME_MOUSE;
 | |
| 
 | |
| 	switch (frame_type) {
 | |
| 	case UCLOGIC_PARAMS_FRAME_DIAL:
 | |
| 	case UCLOGIC_PARAMS_FRAME_MOUSE:
 | |
| 		rc = uclogic_params_ugee_v2_init_frame_dial(&p, desc_params,
 | |
| 							    ARRAY_SIZE(desc_params));
 | |
| 		break;
 | |
| 	case UCLOGIC_PARAMS_FRAME_BUTTONS:
 | |
| 	default:
 | |
| 		rc = uclogic_params_ugee_v2_init_frame_buttons(&p, desc_params,
 | |
| 							       ARRAY_SIZE(desc_params));
 | |
| 		break;
 | |
| 	}
 | |
| 
 | |
| 	if (rc)
 | |
| 		goto cleanup;
 | |
| 
 | |
| 	/* Initialize the battery interface*/
 | |
| 	if (uclogic_params_ugee_v2_has_battery(hdev)) {
 | |
| 		rc = uclogic_params_ugee_v2_init_battery(hdev, &p);
 | |
| 		if (rc) {
 | |
| 			hid_err(hdev, "error initializing battery: %d\n", rc);
 | |
| 			goto cleanup;
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	/* Create a list of raw events to be ignored */
 | |
| 	rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
 | |
| 	if (rc) {
 | |
| 		hid_err(hdev, "error initializing event hook list: %d\n", rc);
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| output:
 | |
| 	/* Output parameters */
 | |
| 	memcpy(params, &p, sizeof(*params));
 | |
| 	memset(&p, 0, sizeof(p));
 | |
| 	rc = 0;
 | |
| cleanup:
 | |
| 	kfree(str_desc);
 | |
| 	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. Must be using the USB low-level
 | |
|  *		driver, i.e. be an actual USB tablet.
 | |
|  *
 | |
|  * 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;
 | |
| 	__u8  bNumInterfaces;
 | |
| 	struct usb_interface *iface;
 | |
| 	__u8 bInterfaceNumber;
 | |
| 	bool found;
 | |
| 	/* The resulting parameters (noop) */
 | |
| 	struct uclogic_params p = {0, };
 | |
| 
 | |
| 	/* Check arguments */
 | |
| 	if (params == NULL || hdev == NULL || !hid_is_usb(hdev)) {
 | |
| 		rc = -EINVAL;
 | |
| 		goto cleanup;
 | |
| 	}
 | |
| 
 | |
| 	udev = hid_to_usb_dev(hdev);
 | |
| 	bNumInterfaces = udev->config->desc.bNumInterfaces;
 | |
| 	iface = to_usb_interface(hdev->dev.parent);
 | |
| 	bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber;
 | |
| 
 | |
| 	/*
 | |
| 	 * 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):
 | |
| 		if (hdev->dev_rsize == UCLOGIC_RDESC_WP5540U_V2_ORIG_SIZE) {
 | |
| 			if (bInterfaceNumber == 0) {
 | |
| 				/* Try to probe v1 pen parameters */
 | |
| 				rc = uclogic_params_pen_init_v1(&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");
 | |
| 				}
 | |
| 			} else {
 | |
| 				uclogic_params_init_invalid(&p);
 | |
| 			}
 | |
| 		} else {
 | |
| 			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;
 | |
| 		}
 | |
| 		fallthrough;
 | |
| 	case VID_PID(USB_VENDOR_ID_HUION,
 | |
| 		     USB_DEVICE_ID_HUION_TABLET):
 | |
| 	case VID_PID(USB_VENDOR_ID_HUION,
 | |
| 		     USB_DEVICE_ID_HUION_TABLET2):
 | |
| 	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):
 | |
| 	case VID_PID(USB_VENDOR_ID_UCLOGIC,
 | |
| 		     USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_47):
 | |
| 		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_UGTIZER,
 | |
| 		     USB_DEVICE_ID_UGTIZER_TABLET_GT5040):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G540):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_G640):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_STAR06):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_TABLET_RAINBOW_CV720):
 | |
| 		/* If this is the pen interface */
 | |
| 		if (bInterfaceNumber == 1) {
 | |
| 			/* Probe v1 pen parameters */
 | |
| 			rc = uclogic_params_pen_init_v1(&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 {
 | |
| 			uclogic_params_init_invalid(&p);
 | |
| 		}
 | |
| 		break;
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01):
 | |
| 		/* If this is the pen and frame interface */
 | |
| 		if (bInterfaceNumber == 1) {
 | |
| 			/* Probe v1 pen parameters */
 | |
| 			rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
 | |
| 			if (rc != 0) {
 | |
| 				hid_err(hdev, "pen probing failed: %d\n", rc);
 | |
| 				goto cleanup;
 | |
| 			}
 | |
| 			/* Initialize frame parameters */
 | |
| 			rc = uclogic_params_frame_init_with_desc(
 | |
| 				&p.frame_list[0],
 | |
| 				uclogic_rdesc_xppen_deco01_frame_arr,
 | |
| 				uclogic_rdesc_xppen_deco01_frame_size,
 | |
| 				0);
 | |
| 			if (rc != 0)
 | |
| 				goto cleanup;
 | |
| 		} else {
 | |
| 			uclogic_params_init_invalid(&p);
 | |
| 		}
 | |
| 		break;
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_PARBLO_A610_PRO):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO01_V2):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_L):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_MW):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_S):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_XPPEN_TABLET_DECO_PRO_SW):
 | |
| 		rc = uclogic_params_ugee_v2_init(&p, hdev);
 | |
| 		if (rc != 0)
 | |
| 			goto cleanup;
 | |
| 		break;
 | |
| 	case VID_PID(USB_VENDOR_ID_TRUST,
 | |
| 		     USB_DEVICE_ID_TRUST_PANORA_TABLET):
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_TABLET_G5):
 | |
| 		/* Ignore non-pen interfaces */
 | |
| 		if (bInterfaceNumber != 1) {
 | |
| 			uclogic_params_init_invalid(&p);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
 | |
| 		if (rc != 0) {
 | |
| 			hid_err(hdev, "pen probing failed: %d\n", rc);
 | |
| 			goto cleanup;
 | |
| 		} else if (found) {
 | |
| 			rc = uclogic_params_frame_init_with_desc(
 | |
| 				&p.frame_list[0],
 | |
| 				uclogic_rdesc_ugee_g5_frame_arr,
 | |
| 				uclogic_rdesc_ugee_g5_frame_size,
 | |
| 				UCLOGIC_RDESC_UGEE_G5_FRAME_ID);
 | |
| 			if (rc != 0) {
 | |
| 				hid_err(hdev,
 | |
| 					"failed creating frame parameters: %d\n",
 | |
| 					rc);
 | |
| 				goto cleanup;
 | |
| 			}
 | |
| 			p.frame_list[0].re_lsb =
 | |
| 				UCLOGIC_RDESC_UGEE_G5_FRAME_RE_LSB;
 | |
| 			p.frame_list[0].dev_id_byte =
 | |
| 				UCLOGIC_RDESC_UGEE_G5_FRAME_DEV_ID_BYTE;
 | |
| 		} else {
 | |
| 			hid_warn(hdev, "pen parameters not found");
 | |
| 			uclogic_params_init_invalid(&p);
 | |
| 		}
 | |
| 
 | |
| 		break;
 | |
| 	case VID_PID(USB_VENDOR_ID_UGEE,
 | |
| 		     USB_DEVICE_ID_UGEE_TABLET_EX07S):
 | |
| 		/* Ignore non-pen interfaces */
 | |
| 		if (bInterfaceNumber != 1) {
 | |
| 			uclogic_params_init_invalid(&p);
 | |
| 			break;
 | |
| 		}
 | |
| 
 | |
| 		rc = uclogic_params_pen_init_v1(&p.pen, &found, hdev);
 | |
| 		if (rc != 0) {
 | |
| 			hid_err(hdev, "pen probing failed: %d\n", rc);
 | |
| 			goto cleanup;
 | |
| 		} else if (found) {
 | |
| 			rc = uclogic_params_frame_init_with_desc(
 | |
| 				&p.frame_list[0],
 | |
| 				uclogic_rdesc_ugee_ex07_frame_arr,
 | |
| 				uclogic_rdesc_ugee_ex07_frame_size,
 | |
| 				0);
 | |
| 			if (rc != 0) {
 | |
| 				hid_err(hdev,
 | |
| 					"failed creating frame parameters: %d\n",
 | |
| 					rc);
 | |
| 				goto cleanup;
 | |
| 			}
 | |
| 		} else {
 | |
| 			hid_warn(hdev, "pen parameters not found");
 | |
| 			uclogic_params_init_invalid(&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;
 | |
| }
 | |
| 
 | |
| #ifdef CONFIG_HID_KUNIT_TEST
 | |
| #include "hid-uclogic-params-test.c"
 | |
| #endif
 |