From 2c3a88c64cb62cc59010359dbfc97f734e98d683 Mon Sep 17 00:00:00 2001 From: Nikolai Kondrashov Date: Sun, 10 Feb 2019 12:13:56 +0200 Subject: [PATCH] HID: uclogic: Support v2 protocol Add support for UC-Logic v2 protocol to hid-uclogic. This adds support for a bunch of new Huion models. Signed-off-by: Nikolai Kondrashov Signed-off-by: Benjamin Tissoires --- drivers/hid/hid-uclogic-params.c | 201 +++++++++++++++++++++++++++++++ drivers/hid/hid-uclogic-rdesc.c | 63 ++++++++++ drivers/hid/hid-uclogic-rdesc.h | 14 +++ 3 files changed, 278 insertions(+) diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c index b5e4d99c6771..132663a87f38 100644 --- a/drivers/hid/hid-uclogic-params.c +++ b/drivers/hid/hid-uclogic-params.c @@ -231,6 +231,151 @@ cleanup: 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. + * @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, + struct hid_device *hdev) +{ + int rc; + bool found = false; + /* Buffer for (part of) the string descriptor */ + __u8 *buf = NULL; + /* Descriptor length required */ + const int len = 18; + 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, 200, 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; + } else { + size_t i; + /* + * 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 < len && + (buf[i] >= 0x20 && buf[i] < 0x7f && buf[i + 1] == 0); + i += 2); + if (i >= 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; + } + kfree(buf); + buf = NULL; + + /* + * Generate pen report descriptor + */ + desc_ptr = uclogic_rdesc_template_apply( + uclogic_rdesc_pen_v2_template_arr, + uclogic_rdesc_pen_v2_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_v2_template_size; + pen->id = UCLOGIC_RDESC_PEN_V2_ID; + pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_NONE; + pen->fragmented_hires = true; + 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). @@ -560,11 +705,15 @@ static int uclogic_params_huion_init(struct uclogic_params *params, struct hid_device *hdev) { int rc; + struct usb_device *udev = hid_to_usb_dev(hdev); 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, }; + static const char transition_ver[] = "HUION_T153_160607"; + char *ver_ptr = NULL; + const size_t ver_len = sizeof(transition_ver) + 1; /* Check arguments */ if (params == NULL || hdev == NULL) { @@ -579,6 +728,57 @@ static int uclogic_params_huion_init(struct uclogic_params *params, 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 (ver_ptr == NULL) { + rc = -ENOMEM; + goto cleanup; + } + if (rc == -EPIPE) { + *ver_ptr = '\0'; + } else if (rc < 0) { + hid_err(hdev, + "failed retrieving Huion firmware version: %d\n", rc); + goto cleanup; + } + + /* 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, 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 buttonpad parameters */ + rc = uclogic_params_frame_init_with_desc( + &p.frame, + uclogic_rdesc_buttonpad_v2_arr, + uclogic_rdesc_buttonpad_v2_size, + UCLOGIC_RDESC_BUTTONPAD_V2_ID); + if (rc != 0) { + hid_err(hdev, + "failed creating v2 buttonpad parameters: %d\n", + rc); + goto cleanup; + } + /* Set bitmask marking frame reports in pen reports */ + p.pen_frame_flag = 0x20; + 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) { @@ -613,6 +813,7 @@ output: memset(&p, 0, sizeof(p)); rc = 0; cleanup: + kfree(ver_ptr); uclogic_params_cleanup(&p); return rc; } diff --git a/drivers/hid/hid-uclogic-rdesc.c b/drivers/hid/hid-uclogic-rdesc.c index 359e72394d83..ef1d3cf918a4 100644 --- a/drivers/hid/hid-uclogic-rdesc.c +++ b/drivers/hid/hid-uclogic-rdesc.c @@ -585,6 +585,62 @@ const __u8 uclogic_rdesc_pen_v1_template_arr[] = { const size_t uclogic_rdesc_pen_v1_template_size = sizeof(uclogic_rdesc_pen_v1_template_arr); +/* Fixed report descriptor template for (tweaked) v2 pen reports */ +const __u8 uclogic_rdesc_pen_v2_template_arr[] = { + 0x05, 0x0D, /* Usage Page (Digitizer), */ + 0x09, 0x02, /* Usage (Pen), */ + 0xA1, 0x01, /* Collection (Application), */ + 0x85, 0x08, /* Report ID (8), */ + 0x09, 0x20, /* Usage (Stylus), */ + 0xA0, /* Collection (Physical), */ + 0x14, /* Logical Minimum (0), */ + 0x25, 0x01, /* Logical Maximum (1), */ + 0x75, 0x01, /* Report Size (1), */ + 0x09, 0x42, /* Usage (Tip Switch), */ + 0x09, 0x44, /* Usage (Barrel Switch), */ + 0x09, 0x46, /* Usage (Tablet Pick), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x03, /* Report Count (3), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x09, 0x32, /* Usage (In Range), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x02, /* Input (Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0x95, 0x01, /* Report Count (1), */ + 0xA4, /* Push, */ + 0x05, 0x01, /* Usage Page (Desktop), */ + 0x65, 0x13, /* Unit (Inch), */ + 0x55, 0xFD, /* Unit Exponent (-3), */ + 0x75, 0x18, /* Report Size (24), */ + 0x34, /* Physical Minimum (0), */ + 0x09, 0x30, /* Usage (X), */ + 0x27, UCLOGIC_RDESC_PEN_PH(X_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(X_PM), + /* Physical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0x09, 0x31, /* Usage (Y), */ + 0x27, UCLOGIC_RDESC_PEN_PH(Y_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x47, UCLOGIC_RDESC_PEN_PH(Y_PM), + /* Physical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0xB4, /* Pop, */ + 0x09, 0x30, /* Usage (Tip Pressure), */ + 0x75, 0x10, /* Report Size (16), */ + 0x27, UCLOGIC_RDESC_PEN_PH(PRESSURE_LM), + /* Logical Maximum (PLACEHOLDER), */ + 0x81, 0x02, /* Input (Variable), */ + 0x81, 0x03, /* Input (Constant, Variable), */ + 0xC0, /* End Collection, */ + 0xC0 /* End Collection */ +}; + +const size_t uclogic_rdesc_pen_v2_template_size = + sizeof(uclogic_rdesc_pen_v2_template_arr); + /** * Expand to the contents of a generic buttonpad report descriptor. * @@ -638,6 +694,13 @@ const __u8 uclogic_rdesc_buttonpad_v1_arr[] = { const size_t uclogic_rdesc_buttonpad_v1_size = sizeof(uclogic_rdesc_buttonpad_v1_arr); +/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ +const __u8 uclogic_rdesc_buttonpad_v2_arr[] = { + UCLOGIC_RDESC_BUTTONPAD_BYTES(52) +}; +const size_t uclogic_rdesc_buttonpad_v2_size = + sizeof(uclogic_rdesc_buttonpad_v2_arr); + /** * uclogic_rdesc_template_apply() - apply report descriptor parameters to a * report descriptor template, creating a report descriptor. Copies the diff --git a/drivers/hid/hid-uclogic-rdesc.h b/drivers/hid/hid-uclogic-rdesc.h index cf03b98ae7cb..f205254a733c 100644 --- a/drivers/hid/hid-uclogic-rdesc.h +++ b/drivers/hid/hid-uclogic-rdesc.h @@ -107,6 +107,13 @@ enum uclogic_rdesc_pen_ph_id { extern const __u8 uclogic_rdesc_pen_v1_template_arr[]; extern const size_t uclogic_rdesc_pen_v1_template_size; +/* Report ID for v2 pen reports */ +#define UCLOGIC_RDESC_PEN_V2_ID 0x08 + +/* Fixed report descriptor template for (tweaked) v2 pen reports */ +extern const __u8 uclogic_rdesc_pen_v2_template_arr[]; +extern const size_t uclogic_rdesc_pen_v2_template_size; + /* Fixed report descriptor for (tweaked) v1 buttonpad reports */ extern const __u8 uclogic_rdesc_buttonpad_v1_arr[]; extern const size_t uclogic_rdesc_buttonpad_v1_size; @@ -114,4 +121,11 @@ extern const size_t uclogic_rdesc_buttonpad_v1_size; /* Report ID for tweaked v1 buttonpad reports */ #define UCLOGIC_RDESC_BUTTONPAD_V1_ID 0xf7 +/* Fixed report descriptor for (tweaked) v2 buttonpad reports */ +extern const __u8 uclogic_rdesc_buttonpad_v2_arr[]; +extern const size_t uclogic_rdesc_buttonpad_v2_size; + +/* Report ID for tweaked v2 buttonpad reports */ +#define UCLOGIC_RDESC_BUTTONPAD_V2_ID 0xf7 + #endif /* _HID_UCLOGIC_RDESC_H */