diff options
Diffstat (limited to 'drivers/hid/hid-uclogic-params.c')
-rw-r--r-- | drivers/hid/hid-uclogic-params.c | 806 |
1 files changed, 806 insertions, 0 deletions
diff --git a/drivers/hid/hid-uclogic-params.c b/drivers/hid/hid-uclogic-params.c new file mode 100644 index 000000000000..2f8870d58f9a --- /dev/null +++ b/drivers/hid/hid-uclogic-params.c | |||
@@ -0,0 +1,806 @@ | |||
1 | // SPDX-License-Identifier: GPL-2.0+ | ||
2 | /* | ||
3 | * HID driver for UC-Logic devices not fully compliant with HID standard | ||
4 | * - tablet initialization and parameter retrieval | ||
5 | * | ||
6 | * Copyright (c) 2018 Nikolai Kondrashov | ||
7 | */ | ||
8 | |||
9 | /* | ||
10 | * This program is free software; you can redistribute it and/or modify it | ||
11 | * under the terms of the GNU General Public License as published by the Free | ||
12 | * Software Foundation; either version 2 of the License, or (at your option) | ||
13 | * any later version. | ||
14 | */ | ||
15 | |||
16 | #include "hid-uclogic-params.h" | ||
17 | #include "hid-uclogic-rdesc.h" | ||
18 | #include "usbhid/usbhid.h" | ||
19 | #include "hid-ids.h" | ||
20 | #include <linux/ctype.h> | ||
21 | #include <asm/unaligned.h> | ||
22 | |||
23 | /** | ||
24 | * Convert a pen in-range reporting type to a string. | ||
25 | * | ||
26 | * @inrange: The in-range reporting type to convert. | ||
27 | * | ||
28 | * Returns: | ||
29 | * The string representing the type, or NULL if the type is unknown. | ||
30 | */ | ||
31 | const char *uclogic_params_pen_inrange_to_str( | ||
32 | enum uclogic_params_pen_inrange inrange) | ||
33 | { | ||
34 | switch (inrange) { | ||
35 | case UCLOGIC_PARAMS_PEN_INRANGE_NORMAL: | ||
36 | return "normal"; | ||
37 | case UCLOGIC_PARAMS_PEN_INRANGE_INVERTED: | ||
38 | return "inverted"; | ||
39 | default: | ||
40 | return NULL; | ||
41 | } | ||
42 | } | ||
43 | |||
44 | /** | ||
45 | * uclogic_params_get_str_desc - retrieve a string descriptor from a HID | ||
46 | * device interface, putting it into a kmalloc-allocated buffer as is, without | ||
47 | * character encoding conversion. | ||
48 | * | ||
49 | * @pbuf: Location for the kmalloc-allocated buffer pointer containing | ||
50 | * the retrieved descriptor. Not modified in case of error. | ||
51 | * Can be NULL to have retrieved descriptor discarded. | ||
52 | * @hdev: The HID device of the tablet interface to retrieve the string | ||
53 | * descriptor from. Cannot be NULL. | ||
54 | * @idx: Index of the string descriptor to request from the device. | ||
55 | * @len: Length of the buffer to allocate and the data to retrieve. | ||
56 | * | ||
57 | * Returns: | ||
58 | * number of bytes retrieved (<= len), | ||
59 | * -EPIPE, if the descriptor was not found, or | ||
60 | * another negative errno code in case of other error. | ||
61 | */ | ||
62 | static int uclogic_params_get_str_desc(__u8 **pbuf, struct hid_device *hdev, | ||
63 | __u8 idx, size_t len) | ||
64 | { | ||
65 | int rc; | ||
66 | struct usb_device *udev = hid_to_usb_dev(hdev); | ||
67 | __u8 *buf = NULL; | ||
68 | |||
69 | /* Check arguments */ | ||
70 | if (hdev == NULL) { | ||
71 | rc = -EINVAL; | ||
72 | goto cleanup; | ||
73 | } | ||
74 | |||
75 | buf = kmalloc(len, GFP_KERNEL); | ||
76 | if (buf == NULL) { | ||
77 | rc = -ENOMEM; | ||
78 | goto cleanup; | ||
79 | } | ||
80 | |||
81 | rc = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), | ||
82 | USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, | ||
83 | (USB_DT_STRING << 8) + idx, | ||
84 | 0x0409, buf, len, | ||
85 | USB_CTRL_GET_TIMEOUT); | ||
86 | if (rc == -EPIPE) { | ||
87 | hid_dbg(hdev, "string descriptor #%hhu not found\n", idx); | ||
88 | goto cleanup; | ||
89 | } else if (rc < 0) { | ||
90 | hid_err(hdev, | ||
91 | "failed retrieving string descriptor #%hhu: %d\n", | ||
92 | idx, rc); | ||
93 | goto cleanup; | ||
94 | } | ||
95 | |||
96 | if (pbuf != NULL) { | ||
97 | *pbuf = buf; | ||
98 | buf = NULL; | ||
99 | } | ||
100 | |||
101 | cleanup: | ||
102 | kfree(buf); | ||
103 | return rc; | ||
104 | } | ||
105 | |||
106 | /** | ||
107 | * uclogic_params_pen_cleanup - free resources used by struct | ||
108 | * uclogic_params_pen (tablet interface's pen input parameters). | ||
109 | * Can be called repeatedly. | ||
110 | * | ||
111 | * @pen: Pen input parameters to cleanup. Cannot be NULL. | ||
112 | */ | ||
113 | static void uclogic_params_pen_cleanup(struct uclogic_params_pen *pen) | ||
114 | { | ||
115 | kfree(pen->desc_ptr); | ||
116 | memset(pen, 0, sizeof(*pen)); | ||
117 | } | ||
118 | |||
119 | /** | ||
120 | * uclogic_params_pen_init() - initialize tablet interface pen | ||
121 | * input and retrieve its parameters from the device. | ||
122 | * | ||
123 | * @pen: Pointer to the pen parameters to initialize (to be | ||
124 | * cleaned up with uclogic_params_pen_cleanup()). Not modified in | ||
125 | * case of error, or if parameters are not found. Cannot be NULL. | ||
126 | * @pfound: Location for a flag which is set to true if the parameters | ||
127 | * were found, and to false if not (e.g. device was | ||
128 | * incompatible). Not modified in case of error. Cannot be NULL. | ||
129 | * @hdev: The HID device of the tablet interface to initialize and get | ||
130 | * parameters from. Cannot be NULL. | ||
131 | * | ||
132 | * Returns: | ||
133 | * Zero, if successful. A negative errno code on error. | ||
134 | */ | ||
135 | static int uclogic_params_pen_init(struct uclogic_params_pen *pen, | ||
136 | bool *pfound, | ||
137 | struct hid_device *hdev) | ||
138 | { | ||
139 | int rc; | ||
140 | bool found = false; | ||
141 | /* Buffer for (part of) the string descriptor */ | ||
142 | __u8 *buf = NULL; | ||
143 | /* Minimum descriptor length required, maximum seen so far is 18 */ | ||
144 | const int len = 12; | ||
145 | s32 resolution; | ||
146 | /* Pen report descriptor template parameters */ | ||
147 | s32 desc_params[UCLOGIC_RDESC_PEN_PH_ID_NUM]; | ||
148 | __u8 *desc_ptr = NULL; | ||
149 | |||
150 | /* Check arguments */ | ||
151 | if (pen == NULL || pfound == NULL || hdev == NULL) { | ||
152 | rc = -EINVAL; | ||
153 | goto cleanup; | ||
154 | } | ||
155 | |||
156 | /* | ||
157 | * Read string descriptor containing pen input parameters. | ||
158 | * The specific string descriptor and data were discovered by sniffing | ||
159 | * the Windows driver traffic. | ||
160 | * NOTE: This enables fully-functional tablet mode. | ||
161 | */ | ||
162 | rc = uclogic_params_get_str_desc(&buf, hdev, 100, len); | ||
163 | if (rc == -EPIPE) { | ||
164 | hid_dbg(hdev, | ||
165 | "string descriptor with pen parameters not found, assuming not compatible\n"); | ||
166 | goto finish; | ||
167 | } else if (rc < 0) { | ||
168 | hid_err(hdev, "failed retrieving pen parameters: %d\n", rc); | ||
169 | goto cleanup; | ||
170 | } else if (rc != len) { | ||
171 | hid_dbg(hdev, | ||
172 | "string descriptor with pen parameters has invalid length (got %d, expected %d), assuming not compatible\n", | ||
173 | rc, len); | ||
174 | goto finish; | ||
175 | } | ||
176 | |||
177 | /* | ||
178 | * Fill report descriptor parameters from the string descriptor | ||
179 | */ | ||
180 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] = | ||
181 | get_unaligned_le16(buf + 2); | ||
182 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] = | ||
183 | get_unaligned_le16(buf + 4); | ||
184 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_PRESSURE_LM] = | ||
185 | get_unaligned_le16(buf + 8); | ||
186 | resolution = get_unaligned_le16(buf + 10); | ||
187 | if (resolution == 0) { | ||
188 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = 0; | ||
189 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = 0; | ||
190 | } else { | ||
191 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_PM] = | ||
192 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_X_LM] * 1000 / | ||
193 | resolution; | ||
194 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_PM] = | ||
195 | desc_params[UCLOGIC_RDESC_PEN_PH_ID_Y_LM] * 1000 / | ||
196 | resolution; | ||
197 | } | ||
198 | kfree(buf); | ||
199 | buf = NULL; | ||
200 | |||
201 | /* | ||
202 | * Generate pen report descriptor | ||
203 | */ | ||
204 | desc_ptr = uclogic_rdesc_template_apply( | ||
205 | uclogic_rdesc_pen_template_arr, | ||
206 | uclogic_rdesc_pen_template_size, | ||
207 | desc_params, ARRAY_SIZE(desc_params)); | ||
208 | if (desc_ptr == NULL) { | ||
209 | rc = -ENOMEM; | ||
210 | goto cleanup; | ||
211 | } | ||
212 | |||
213 | /* | ||
214 | * Fill-in the parameters | ||
215 | */ | ||
216 | memset(pen, 0, sizeof(*pen)); | ||
217 | pen->desc_ptr = desc_ptr; | ||
218 | desc_ptr = NULL; | ||
219 | pen->desc_size = uclogic_rdesc_pen_template_size; | ||
220 | pen->id = UCLOGIC_RDESC_PEN_ID; | ||
221 | pen->inrange = UCLOGIC_PARAMS_PEN_INRANGE_INVERTED; | ||
222 | found = true; | ||
223 | finish: | ||
224 | *pfound = found; | ||
225 | rc = 0; | ||
226 | cleanup: | ||
227 | kfree(desc_ptr); | ||
228 | kfree(buf); | ||
229 | return rc; | ||
230 | } | ||
231 | |||
232 | /** | ||
233 | * uclogic_params_frame_cleanup - free resources used by struct | ||
234 | * uclogic_params_frame (tablet interface's frame controls input parameters). | ||
235 | * Can be called repeatedly. | ||
236 | * | ||
237 | * @frame: Frame controls input parameters to cleanup. Cannot be NULL. | ||
238 | */ | ||
239 | static void uclogic_params_frame_cleanup(struct uclogic_params_frame *frame) | ||
240 | { | ||
241 | kfree(frame->desc_ptr); | ||
242 | memset(frame, 0, sizeof(*frame)); | ||
243 | } | ||
244 | |||
245 | /** | ||
246 | * uclogic_params_frame_init_with_desc() - initialize tablet's frame control | ||
247 | * parameters with a static report descriptor. | ||
248 | * | ||
249 | * @frame: Pointer to the frame parameters to initialize (to be cleaned | ||
250 | * up with uclogic_params_frame_cleanup()). Not modified in case | ||
251 | * of error. Cannot be NULL. | ||
252 | * @desc_ptr: Report descriptor pointer. Can be NULL, if desc_size is zero. | ||
253 | * @desc_size: Report descriptor size. | ||
254 | * @id: Report ID used for frame reports, if they should be tweaked, | ||
255 | * zero if not. | ||
256 | * | ||
257 | * Returns: | ||
258 | * Zero, if successful. A negative errno code on error. | ||
259 | */ | ||
260 | static int uclogic_params_frame_init_with_desc( | ||
261 | struct uclogic_params_frame *frame, | ||
262 | const __u8 *desc_ptr, | ||
263 | size_t desc_size, | ||
264 | unsigned int id) | ||
265 | { | ||
266 | __u8 *copy_desc_ptr; | ||
267 | |||
268 | if (frame == NULL || (desc_ptr == NULL && desc_size != 0)) | ||
269 | return -EINVAL; | ||
270 | |||
271 | copy_desc_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL); | ||
272 | if (copy_desc_ptr == NULL) | ||
273 | return -ENOMEM; | ||
274 | |||
275 | memset(frame, 0, sizeof(*frame)); | ||
276 | frame->desc_ptr = copy_desc_ptr; | ||
277 | frame->desc_size = desc_size; | ||
278 | frame->id = id; | ||
279 | return 0; | ||
280 | } | ||
281 | |||
282 | /** | ||
283 | * uclogic_params_frame_init_buttonpad() - initialize abstract buttonpad | ||
284 | * on a tablet interface. | ||
285 | * | ||
286 | * @frame: Pointer to the frame parameters to initialize (to be cleaned | ||
287 | * up with uclogic_params_frame_cleanup()). Not modified in case | ||
288 | * of error, or if parameters are not found. Cannot be NULL. | ||
289 | * @pfound: Location for a flag which is set to true if the parameters | ||
290 | * were found, and to false if not (e.g. device was | ||
291 | * incompatible). Not modified in case of error. Cannot be NULL. | ||
292 | * @hdev: The HID device of the tablet interface to initialize and get | ||
293 | * parameters from. Cannot be NULL. | ||
294 | * | ||
295 | * Returns: | ||
296 | * Zero, if successful. A negative errno code on error. | ||
297 | */ | ||
298 | static int uclogic_params_frame_init_buttonpad( | ||
299 | struct uclogic_params_frame *frame, | ||
300 | bool *pfound, | ||
301 | struct hid_device *hdev) | ||
302 | { | ||
303 | int rc; | ||
304 | bool found = false; | ||
305 | struct usb_device *usb_dev = hid_to_usb_dev(hdev); | ||
306 | char *str_buf = NULL; | ||
307 | const size_t str_len = 16; | ||
308 | |||
309 | /* Check arguments */ | ||
310 | if (frame == NULL || pfound == NULL || hdev == NULL) { | ||
311 | rc = -EINVAL; | ||
312 | goto cleanup; | ||
313 | } | ||
314 | |||
315 | /* | ||
316 | * Enable generic button mode | ||
317 | */ | ||
318 | str_buf = kzalloc(str_len, GFP_KERNEL); | ||
319 | if (str_buf == NULL) { | ||
320 | rc = -ENOMEM; | ||
321 | goto cleanup; | ||
322 | } | ||
323 | |||
324 | rc = usb_string(usb_dev, 123, str_buf, str_len); | ||
325 | if (rc == -EPIPE) { | ||
326 | hid_dbg(hdev, | ||
327 | "generic button -enabling string descriptor not found\n"); | ||
328 | } else if (rc < 0) { | ||
329 | goto cleanup; | ||
330 | } else if (strncmp(str_buf, "HK On", rc) != 0) { | ||
331 | hid_dbg(hdev, | ||
332 | "invalid response to enabling generic buttons: \"%s\"\n", | ||
333 | str_buf); | ||
334 | } else { | ||
335 | hid_dbg(hdev, "generic buttons enabled\n"); | ||
336 | rc = uclogic_params_frame_init_with_desc( | ||
337 | frame, | ||
338 | uclogic_rdesc_buttonpad_arr, | ||
339 | uclogic_rdesc_buttonpad_size, | ||
340 | UCLOGIC_RDESC_BUTTONPAD_ID); | ||
341 | if (rc != 0) | ||
342 | goto cleanup; | ||
343 | found = true; | ||
344 | } | ||
345 | |||
346 | *pfound = found; | ||
347 | rc = 0; | ||
348 | cleanup: | ||
349 | kfree(str_buf); | ||
350 | return rc; | ||
351 | } | ||
352 | |||
353 | /** | ||
354 | * uclogic_params_cleanup - free resources used by struct uclogic_params | ||
355 | * (tablet interface's parameters). | ||
356 | * Can be called repeatedly. | ||
357 | * | ||
358 | * @params: Input parameters to cleanup. Cannot be NULL. | ||
359 | */ | ||
360 | void uclogic_params_cleanup(struct uclogic_params *params) | ||
361 | { | ||
362 | if (!params->invalid) { | ||
363 | kfree(params->desc_ptr); | ||
364 | if (!params->pen_unused) | ||
365 | uclogic_params_pen_cleanup(¶ms->pen); | ||
366 | uclogic_params_frame_cleanup(¶ms->frame); | ||
367 | memset(params, 0, sizeof(*params)); | ||
368 | } | ||
369 | } | ||
370 | |||
371 | /** | ||
372 | * Get a replacement report descriptor for a tablet's interface. | ||
373 | * | ||
374 | * @params: The parameters of a tablet interface to get report | ||
375 | * descriptor for. Cannot be NULL. | ||
376 | * @pdesc: Location for the resulting, kmalloc-allocated report | ||
377 | * descriptor pointer, or for NULL, if there's no replacement | ||
378 | * report descriptor. Not modified in case of error. Cannot be | ||
379 | * NULL. | ||
380 | * @psize: Location for the resulting report descriptor size, not set if | ||
381 | * there's no replacement report descriptor. Not modified in case | ||
382 | * of error. Cannot be NULL. | ||
383 | * | ||
384 | * Returns: | ||
385 | * Zero, if successful. | ||
386 | * -EINVAL, if invalid arguments are supplied. | ||
387 | * -ENOMEM, if failed to allocate memory. | ||
388 | */ | ||
389 | int uclogic_params_get_desc(const struct uclogic_params *params, | ||
390 | __u8 **pdesc, | ||
391 | unsigned int *psize) | ||
392 | { | ||
393 | bool common_present; | ||
394 | bool pen_present; | ||
395 | bool frame_present; | ||
396 | unsigned int size; | ||
397 | __u8 *desc = NULL; | ||
398 | |||
399 | /* Check arguments */ | ||
400 | if (params == NULL || pdesc == NULL || psize == NULL) | ||
401 | return -EINVAL; | ||
402 | |||
403 | size = 0; | ||
404 | |||
405 | common_present = (params->desc_ptr != NULL); | ||
406 | pen_present = (!params->pen_unused && params->pen.desc_ptr != NULL); | ||
407 | frame_present = (params->frame.desc_ptr != NULL); | ||
408 | |||
409 | if (common_present) | ||
410 | size += params->desc_size; | ||
411 | if (pen_present) | ||
412 | size += params->pen.desc_size; | ||
413 | if (frame_present) | ||
414 | size += params->frame.desc_size; | ||
415 | |||
416 | if (common_present || pen_present || frame_present) { | ||
417 | __u8 *p; | ||
418 | |||
419 | desc = kmalloc(size, GFP_KERNEL); | ||
420 | if (desc == NULL) | ||
421 | return -ENOMEM; | ||
422 | p = desc; | ||
423 | |||
424 | if (common_present) { | ||
425 | memcpy(p, params->desc_ptr, | ||
426 | params->desc_size); | ||
427 | p += params->desc_size; | ||
428 | } | ||
429 | if (pen_present) { | ||
430 | memcpy(p, params->pen.desc_ptr, | ||
431 | params->pen.desc_size); | ||
432 | p += params->pen.desc_size; | ||
433 | } | ||
434 | if (frame_present) { | ||
435 | memcpy(p, params->frame.desc_ptr, | ||
436 | params->frame.desc_size); | ||
437 | p += params->frame.desc_size; | ||
438 | } | ||
439 | |||
440 | WARN_ON(p != desc + size); | ||
441 | |||
442 | *psize = size; | ||
443 | } | ||
444 | |||
445 | *pdesc = desc; | ||
446 | return 0; | ||
447 | } | ||
448 | |||
449 | /** | ||
450 | * uclogic_params_init_invalid() - initialize tablet interface parameters, | ||
451 | * specifying the interface is invalid. | ||
452 | * | ||
453 | * @params: Parameters to initialize (to be cleaned with | ||
454 | * uclogic_params_cleanup()). Cannot be NULL. | ||
455 | */ | ||
456 | static void uclogic_params_init_invalid(struct uclogic_params *params) | ||
457 | { | ||
458 | params->invalid = true; | ||
459 | } | ||
460 | |||
461 | /** | ||
462 | * uclogic_params_init_with_opt_desc() - initialize tablet interface | ||
463 | * parameters with an optional replacement report descriptor. Only modify | ||
464 | * report descriptor, if the original report descriptor matches the expected | ||
465 | * size. | ||
466 | * | ||
467 | * @params: Parameters to initialize (to be cleaned with | ||
468 | * uclogic_params_cleanup()). Not modified in case of | ||
469 | * error. Cannot be NULL. | ||
470 | * @hdev: The HID device of the tablet interface create the | ||
471 | * parameters for. Cannot be NULL. | ||
472 | * @orig_desc_size: Expected size of the original report descriptor to | ||
473 | * be replaced. | ||
474 | * @desc_ptr: Pointer to the replacement report descriptor. | ||
475 | * Can be NULL, if desc_size is zero. | ||
476 | * @desc_size: Size of the replacement report descriptor. | ||
477 | * | ||
478 | * Returns: | ||
479 | * Zero, if successful. -EINVAL if an invalid argument was passed. | ||
480 | * -ENOMEM, if failed to allocate memory. | ||
481 | */ | ||
482 | static int uclogic_params_init_with_opt_desc(struct uclogic_params *params, | ||
483 | struct hid_device *hdev, | ||
484 | unsigned int orig_desc_size, | ||
485 | __u8 *desc_ptr, | ||
486 | unsigned int desc_size) | ||
487 | { | ||
488 | __u8 *desc_copy_ptr = NULL; | ||
489 | unsigned int desc_copy_size; | ||
490 | int rc; | ||
491 | |||
492 | /* Check arguments */ | ||
493 | if (params == NULL || hdev == NULL || | ||
494 | (desc_ptr == NULL && desc_size != 0)) { | ||
495 | rc = -EINVAL; | ||
496 | goto cleanup; | ||
497 | } | ||
498 | |||
499 | /* Replace report descriptor, if it matches */ | ||
500 | if (hdev->dev_rsize == orig_desc_size) { | ||
501 | hid_dbg(hdev, | ||
502 | "device report descriptor matches the expected size, replacing\n"); | ||
503 | desc_copy_ptr = kmemdup(desc_ptr, desc_size, GFP_KERNEL); | ||
504 | if (desc_copy_ptr == NULL) { | ||
505 | rc = -ENOMEM; | ||
506 | goto cleanup; | ||
507 | } | ||
508 | desc_copy_size = desc_size; | ||
509 | } else { | ||
510 | hid_dbg(hdev, | ||
511 | "device report descriptor doesn't match the expected size (%u != %u), preserving\n", | ||
512 | hdev->dev_rsize, orig_desc_size); | ||
513 | desc_copy_ptr = NULL; | ||
514 | desc_copy_size = 0; | ||
515 | } | ||
516 | |||
517 | /* Output parameters */ | ||
518 | memset(params, 0, sizeof(*params)); | ||
519 | params->desc_ptr = desc_copy_ptr; | ||
520 | desc_copy_ptr = NULL; | ||
521 | params->desc_size = desc_copy_size; | ||
522 | |||
523 | rc = 0; | ||
524 | cleanup: | ||
525 | kfree(desc_copy_ptr); | ||
526 | return rc; | ||
527 | } | ||
528 | |||
529 | /** | ||
530 | * uclogic_params_init_with_pen_unused() - initialize tablet interface | ||
531 | * parameters preserving original reports and generic HID processing, but | ||
532 | * disabling pen usage. | ||
533 | * | ||
534 | * @params: Parameters to initialize (to be cleaned with | ||
535 | * uclogic_params_cleanup()). Not modified in case of | ||
536 | * error. Cannot be NULL. | ||
537 | */ | ||
538 | static void uclogic_params_init_with_pen_unused(struct uclogic_params *params) | ||
539 | { | ||
540 | memset(params, 0, sizeof(*params)); | ||
541 | params->pen_unused = true; | ||
542 | } | ||
543 | |||
544 | /** | ||
545 | * uclogic_params_init() - initialize a Huion tablet interface and discover | ||
546 | * its parameters. | ||
547 | * | ||
548 | * @params: Parameters to fill in (to be cleaned with | ||
549 | * uclogic_params_cleanup()). Not modified in case of error. | ||
550 | * Cannot be NULL. | ||
551 | * @hdev: The HID device of the tablet interface to initialize and get | ||
552 | * parameters from. Cannot be NULL. | ||
553 | * | ||
554 | * Returns: | ||
555 | * Zero, if successful. A negative errno code on error. | ||
556 | */ | ||
557 | static int uclogic_params_huion_init(struct uclogic_params *params, | ||
558 | struct hid_device *hdev) | ||
559 | { | ||
560 | int rc; | ||
561 | struct usb_interface *iface = to_usb_interface(hdev->dev.parent); | ||
562 | __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber; | ||
563 | bool found; | ||
564 | /* The resulting parameters (noop) */ | ||
565 | struct uclogic_params p = {0, }; | ||
566 | |||
567 | /* Check arguments */ | ||
568 | if (params == NULL || hdev == NULL) { | ||
569 | rc = -EINVAL; | ||
570 | goto cleanup; | ||
571 | } | ||
572 | |||
573 | /* If it's not a pen interface */ | ||
574 | if (bInterfaceNumber != 0) { | ||
575 | /* TODO: Consider marking the interface invalid */ | ||
576 | uclogic_params_init_with_pen_unused(&p); | ||
577 | goto output; | ||
578 | } | ||
579 | |||
580 | /* Try to probe pen parameters */ | ||
581 | rc = uclogic_params_pen_init(&p.pen, &found, hdev); | ||
582 | if (rc != 0) { | ||
583 | hid_err(hdev, | ||
584 | "failed probing pen parameters: %d\n", rc); | ||
585 | goto cleanup; | ||
586 | } else if (found) { | ||
587 | hid_dbg(hdev, "pen parameters found\n"); | ||
588 | /* Try to probe buttonpad */ | ||
589 | rc = uclogic_params_frame_init_buttonpad( | ||
590 | &p.frame, | ||
591 | &found, hdev); | ||
592 | if (rc != 0) { | ||
593 | hid_err(hdev, "v1 buttonpad probing failed: %d\n", rc); | ||
594 | goto cleanup; | ||
595 | } | ||
596 | hid_dbg(hdev, "buttonpad parameters%s found\n", | ||
597 | (found ? "" : " not")); | ||
598 | if (found) { | ||
599 | /* Set bitmask marking frame reports */ | ||
600 | p.pen_frame_flag = 0x20; | ||
601 | } | ||
602 | goto output; | ||
603 | } | ||
604 | hid_dbg(hdev, "pen parameters not found\n"); | ||
605 | |||
606 | uclogic_params_init_invalid(&p); | ||
607 | |||
608 | output: | ||
609 | /* Output parameters */ | ||
610 | memcpy(params, &p, sizeof(*params)); | ||
611 | memset(&p, 0, sizeof(p)); | ||
612 | rc = 0; | ||
613 | cleanup: | ||
614 | uclogic_params_cleanup(&p); | ||
615 | return rc; | ||
616 | } | ||
617 | |||
618 | /** | ||
619 | * uclogic_params_init() - initialize a tablet interface and discover its | ||
620 | * parameters. | ||
621 | * | ||
622 | * @params: Parameters to fill in (to be cleaned with | ||
623 | * uclogic_params_cleanup()). Not modified in case of error. | ||
624 | * Cannot be NULL. | ||
625 | * @hdev: The HID device of the tablet interface to initialize and get | ||
626 | * parameters from. Cannot be NULL. | ||
627 | * | ||
628 | * Returns: | ||
629 | * Zero, if successful. A negative errno code on error. | ||
630 | */ | ||
631 | int uclogic_params_init(struct uclogic_params *params, | ||
632 | struct hid_device *hdev) | ||
633 | { | ||
634 | int rc; | ||
635 | struct usb_device *udev = hid_to_usb_dev(hdev); | ||
636 | __u8 bNumInterfaces = udev->config->desc.bNumInterfaces; | ||
637 | struct usb_interface *iface = to_usb_interface(hdev->dev.parent); | ||
638 | __u8 bInterfaceNumber = iface->cur_altsetting->desc.bInterfaceNumber; | ||
639 | bool found; | ||
640 | /* The resulting parameters (noop) */ | ||
641 | struct uclogic_params p = {0, }; | ||
642 | |||
643 | /* Check arguments */ | ||
644 | if (params == NULL || hdev == NULL) { | ||
645 | rc = -EINVAL; | ||
646 | goto cleanup; | ||
647 | } | ||
648 | |||
649 | /* | ||
650 | * Set replacement report descriptor if the original matches the | ||
651 | * specified size. Otherwise keep interface unchanged. | ||
652 | */ | ||
653 | #define WITH_OPT_DESC(_orig_desc_token, _new_desc_token) \ | ||
654 | uclogic_params_init_with_opt_desc( \ | ||
655 | &p, hdev, \ | ||
656 | UCLOGIC_RDESC_##_orig_desc_token##_SIZE, \ | ||
657 | uclogic_rdesc_##_new_desc_token##_arr, \ | ||
658 | uclogic_rdesc_##_new_desc_token##_size) | ||
659 | |||
660 | #define VID_PID(_vid, _pid) \ | ||
661 | (((__u32)(_vid) << 16) | ((__u32)(_pid) & U16_MAX)) | ||
662 | |||
663 | /* | ||
664 | * Handle specific interfaces for specific tablets. | ||
665 | * | ||
666 | * Observe the following logic: | ||
667 | * | ||
668 | * If the interface is recognized as producing certain useful input: | ||
669 | * Mark interface as valid. | ||
670 | * Output interface parameters. | ||
671 | * Else, if the interface is recognized as *not* producing any useful | ||
672 | * input: | ||
673 | * Mark interface as invalid. | ||
674 | * Else: | ||
675 | * Mark interface as valid. | ||
676 | * Output noop parameters. | ||
677 | * | ||
678 | * Rule of thumb: it is better to disable a broken interface than let | ||
679 | * it spew garbage input. | ||
680 | */ | ||
681 | |||
682 | switch (VID_PID(hdev->vendor, hdev->product)) { | ||
683 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
684 | USB_DEVICE_ID_UCLOGIC_TABLET_PF1209): | ||
685 | rc = WITH_OPT_DESC(PF1209_ORIG, pf1209_fixed); | ||
686 | if (rc != 0) | ||
687 | goto cleanup; | ||
688 | break; | ||
689 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
690 | USB_DEVICE_ID_UCLOGIC_TABLET_WP4030U): | ||
691 | rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp4030u_fixed); | ||
692 | if (rc != 0) | ||
693 | goto cleanup; | ||
694 | break; | ||
695 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
696 | USB_DEVICE_ID_UCLOGIC_TABLET_WP5540U): | ||
697 | rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp5540u_fixed); | ||
698 | if (rc != 0) | ||
699 | goto cleanup; | ||
700 | break; | ||
701 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
702 | USB_DEVICE_ID_UCLOGIC_TABLET_WP8060U): | ||
703 | rc = WITH_OPT_DESC(WPXXXXU_ORIG, wp8060u_fixed); | ||
704 | if (rc != 0) | ||
705 | goto cleanup; | ||
706 | break; | ||
707 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
708 | USB_DEVICE_ID_UCLOGIC_TABLET_WP1062): | ||
709 | rc = WITH_OPT_DESC(WP1062_ORIG, wp1062_fixed); | ||
710 | if (rc != 0) | ||
711 | goto cleanup; | ||
712 | break; | ||
713 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
714 | USB_DEVICE_ID_UCLOGIC_WIRELESS_TABLET_TWHL850): | ||
715 | switch (bInterfaceNumber) { | ||
716 | case 0: | ||
717 | rc = WITH_OPT_DESC(TWHL850_ORIG0, twhl850_fixed0); | ||
718 | if (rc != 0) | ||
719 | goto cleanup; | ||
720 | break; | ||
721 | case 1: | ||
722 | rc = WITH_OPT_DESC(TWHL850_ORIG1, twhl850_fixed1); | ||
723 | if (rc != 0) | ||
724 | goto cleanup; | ||
725 | break; | ||
726 | case 2: | ||
727 | rc = WITH_OPT_DESC(TWHL850_ORIG2, twhl850_fixed2); | ||
728 | if (rc != 0) | ||
729 | goto cleanup; | ||
730 | break; | ||
731 | } | ||
732 | break; | ||
733 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
734 | USB_DEVICE_ID_UCLOGIC_TABLET_TWHA60): | ||
735 | /* | ||
736 | * If it is not a three-interface version, which is known to | ||
737 | * respond to initialization. | ||
738 | */ | ||
739 | if (bNumInterfaces != 3) { | ||
740 | switch (bInterfaceNumber) { | ||
741 | case 0: | ||
742 | rc = WITH_OPT_DESC(TWHA60_ORIG0, | ||
743 | twha60_fixed0); | ||
744 | if (rc != 0) | ||
745 | goto cleanup; | ||
746 | break; | ||
747 | case 1: | ||
748 | rc = WITH_OPT_DESC(TWHA60_ORIG1, | ||
749 | twha60_fixed1); | ||
750 | if (rc != 0) | ||
751 | goto cleanup; | ||
752 | break; | ||
753 | } | ||
754 | break; | ||
755 | } | ||
756 | /* FALL THROUGH */ | ||
757 | case VID_PID(USB_VENDOR_ID_HUION, | ||
758 | USB_DEVICE_ID_HUION_TABLET): | ||
759 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
760 | USB_DEVICE_ID_HUION_TABLET): | ||
761 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
762 | USB_DEVICE_ID_YIYNOVA_TABLET): | ||
763 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
764 | USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_81): | ||
765 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
766 | USB_DEVICE_ID_UCLOGIC_DRAWIMAGE_G3): | ||
767 | case VID_PID(USB_VENDOR_ID_UCLOGIC, | ||
768 | USB_DEVICE_ID_UCLOGIC_UGEE_TABLET_45): | ||
769 | rc = uclogic_params_huion_init(&p, hdev); | ||
770 | if (rc != 0) | ||
771 | goto cleanup; | ||
772 | break; | ||
773 | case VID_PID(USB_VENDOR_ID_UGTIZER, | ||
774 | USB_DEVICE_ID_UGTIZER_TABLET_GP0610): | ||
775 | case VID_PID(USB_VENDOR_ID_UGEE, | ||
776 | USB_DEVICE_ID_UGEE_TABLET_EX07S): | ||
777 | /* If this is the pen interface */ | ||
778 | if (bInterfaceNumber == 1) { | ||
779 | /* Probe pen parameters */ | ||
780 | rc = uclogic_params_pen_init(&p.pen, &found, hdev); | ||
781 | if (rc != 0) { | ||
782 | hid_err(hdev, "pen probing failed: %d\n", rc); | ||
783 | goto cleanup; | ||
784 | } | ||
785 | if (!found) { | ||
786 | hid_warn(hdev, "pen parameters not found"); | ||
787 | uclogic_params_init_invalid(&p); | ||
788 | } | ||
789 | } else { | ||
790 | /* TODO: Consider marking the interface invalid */ | ||
791 | uclogic_params_init_with_pen_unused(&p); | ||
792 | } | ||
793 | break; | ||
794 | } | ||
795 | |||
796 | #undef VID_PID | ||
797 | #undef WITH_OPT_DESC | ||
798 | |||
799 | /* Output parameters */ | ||
800 | memcpy(params, &p, sizeof(*params)); | ||
801 | memset(&p, 0, sizeof(p)); | ||
802 | rc = 0; | ||
803 | cleanup: | ||
804 | uclogic_params_cleanup(&p); | ||
805 | return rc; | ||
806 | } | ||