On Sun, Feb 10, 2019 at 11:15 AM Nikolai Kondrashov <spbnick@xxxxxxxxx> wrote: > > 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 <spbnick@xxxxxxxxx> > --- > 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 @@ static int uclogic_params_pen_init_v1(struct uclogic_params_pen *pen, > 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); Nitpick, I am pretty sure we already have the bits in the kernel for that. But OTOH, not sure I'll request a v3 just for that. Cheers, Benjamin > +} > + > +/** > + * 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 @@ static int uclogic_params_huion_init(struct uclogic_params *params, > 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 */ > -- > 2.20.1 >