// 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";
case UCLOGIC_PARAMS_PEN_INRANGE_NONE:
return "none";
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;
__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 #%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_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_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 =