Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

HID: uclogic: Handle wireless device reconnection

UGEEv2 tablets with battery can be connected using a USB cable or a USB
Bluetooth dongle.

When the Bluetooth dongle is used, the connection to that tablet can be
lost because the tablet is out of the range of the receiver or because
it was switched off using the switch placed in the back of the tablet's
frame.

After losing connection, the tablet is able to reconnect automatically
and its firmware sends a special packet indicating that the device was
reconnected. In response to this packet, the tablet needs to receive the
same array of magic data it expects on probe to enable its interfaces.

This patch implements a generic mechanism to hook raw events and
schedule a work to perform any custom action.

Tested-by: Mia Kanashi <chad@redpilled.dev>
Tested-by: Andreas Grosse <andig.mail@t-online.de>
Signed-off-by: José Expósito <jose.exposito89@gmail.com>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>

authored by

José Expósito and committed by
Jiri Kosina
a251d657 bd85c131

+271
+105
drivers/hid/hid-uclogic-core-test.c
··· 1 + // SPDX-License-Identifier: GPL-2.0+ 2 + 3 + /* 4 + * HID driver for UC-Logic devices not fully compliant with HID standard 5 + * 6 + * Copyright (c) 2022 José Expósito <jose.exposito89@gmail.com> 7 + */ 8 + 9 + #include <kunit/test.h> 10 + #include "./hid-uclogic-params.h" 11 + 12 + #define MAX_EVENT_SIZE 12 13 + 14 + struct uclogic_raw_event_hook_test { 15 + u8 event[MAX_EVENT_SIZE]; 16 + size_t size; 17 + bool expected; 18 + }; 19 + 20 + static struct uclogic_raw_event_hook_test hook_events[] = { 21 + { 22 + .event = { 0xA1, 0xB2, 0xC3, 0xD4 }, 23 + .size = 4, 24 + }, 25 + { 26 + .event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A }, 27 + .size = 6, 28 + }, 29 + }; 30 + 31 + static struct uclogic_raw_event_hook_test test_events[] = { 32 + { 33 + .event = { 0xA1, 0xB2, 0xC3, 0xD4 }, 34 + .size = 4, 35 + .expected = true, 36 + }, 37 + { 38 + .event = { 0x1F, 0x2E, 0x3D, 0x4C, 0x5B, 0x6A }, 39 + .size = 6, 40 + .expected = true, 41 + }, 42 + { 43 + .event = { 0xA1, 0xB2, 0xC3 }, 44 + .size = 3, 45 + .expected = false, 46 + }, 47 + { 48 + .event = { 0xA1, 0xB2, 0xC3, 0xD4, 0x00 }, 49 + .size = 5, 50 + .expected = false, 51 + }, 52 + { 53 + .event = { 0x2E, 0x3D, 0x4C, 0x5B, 0x6A, 0x1F }, 54 + .size = 6, 55 + .expected = false, 56 + }, 57 + }; 58 + 59 + static void hid_test_uclogic_exec_event_hook_test(struct kunit *test) 60 + { 61 + struct uclogic_params p = {0, }; 62 + struct uclogic_raw_event_hook *filter; 63 + bool res; 64 + int n; 65 + 66 + /* Initialize the list of events to hook */ 67 + p.event_hooks = kunit_kzalloc(test, sizeof(*p.event_hooks), GFP_KERNEL); 68 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, p.event_hooks); 69 + INIT_LIST_HEAD(&p.event_hooks->list); 70 + 71 + for (n = 0; n < ARRAY_SIZE(hook_events); n++) { 72 + filter = kunit_kzalloc(test, sizeof(*filter), GFP_KERNEL); 73 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter); 74 + 75 + filter->size = hook_events[n].size; 76 + filter->event = kunit_kzalloc(test, filter->size, GFP_KERNEL); 77 + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, filter->event); 78 + memcpy(filter->event, &hook_events[n].event[0], filter->size); 79 + 80 + list_add_tail(&filter->list, &p.event_hooks->list); 81 + } 82 + 83 + /* Test uclogic_exec_event_hook() */ 84 + for (n = 0; n < ARRAY_SIZE(test_events); n++) { 85 + res = uclogic_exec_event_hook(&p, &test_events[n].event[0], 86 + test_events[n].size); 87 + KUNIT_ASSERT_EQ(test, res, test_events[n].expected); 88 + } 89 + } 90 + 91 + static struct kunit_case hid_uclogic_core_test_cases[] = { 92 + KUNIT_CASE(hid_test_uclogic_exec_event_hook_test), 93 + {} 94 + }; 95 + 96 + static struct kunit_suite hid_uclogic_core_test_suite = { 97 + .name = "hid_uclogic_core_test", 98 + .test_cases = hid_uclogic_core_test_cases, 99 + }; 100 + 101 + kunit_test_suite(hid_uclogic_core_test_suite); 102 + 103 + MODULE_DESCRIPTION("KUnit tests for the UC-Logic driver"); 104 + MODULE_LICENSE("GPL"); 105 + MODULE_AUTHOR("José Expósito <jose.exposito89@gmail.com>");
+35
drivers/hid/hid-uclogic-core.c
··· 250 250 #endif 251 251 252 252 /** 253 + * uclogic_exec_event_hook - if the received event is hooked schedules the 254 + * associated work. 255 + * 256 + * @p: Tablet interface report parameters. 257 + * @event: Raw event. 258 + * @size: The size of event. 259 + * 260 + * Returns: 261 + * Whether the event was hooked or not. 262 + */ 263 + static bool uclogic_exec_event_hook(struct uclogic_params *p, u8 *event, int size) 264 + { 265 + struct uclogic_raw_event_hook *curr; 266 + 267 + if (!p->event_hooks) 268 + return false; 269 + 270 + list_for_each_entry(curr, &p->event_hooks->list, list) { 271 + if (curr->size == size && memcmp(curr->event, event, size) == 0) { 272 + schedule_work(&curr->work); 273 + return true; 274 + } 275 + } 276 + 277 + return false; 278 + } 279 + 280 + /** 253 281 * uclogic_raw_event_pen - handle raw pen events (pen HID reports). 254 282 * 255 283 * @drvdata: Driver data. ··· 435 407 if (report->type != HID_INPUT_REPORT) 436 408 return 0; 437 409 410 + if (uclogic_exec_event_hook(params, data, size)) 411 + return 0; 412 + 438 413 while (true) { 439 414 /* Tweak pen reports, if necessary */ 440 415 if ((report_id == params->pen.id) && (size >= 2)) { ··· 567 536 MODULE_AUTHOR("Martin Rusko"); 568 537 MODULE_AUTHOR("Nikolai Kondrashov"); 569 538 MODULE_LICENSE("GPL"); 539 + 540 + #ifdef CONFIG_HID_KUNIT_TEST 541 + #include "hid-uclogic-core-test.c" 542 + #endif
+16
drivers/hid/hid-uclogic-params-test.c
··· 174 174 KUNIT_EXPECT_EQ(test, params->frame_type, frame_type); 175 175 } 176 176 177 + static void hid_test_uclogic_params_cleanup_event_hooks(struct kunit *test) 178 + { 179 + int res, n; 180 + struct uclogic_params p = {0, }; 181 + 182 + res = uclogic_params_ugee_v2_init_event_hooks(NULL, &p); 183 + KUNIT_ASSERT_EQ(test, res, 0); 184 + 185 + /* Check that the function can be called repeatedly */ 186 + for (n = 0; n < 4; n++) { 187 + uclogic_params_cleanup_event_hooks(&p); 188 + KUNIT_EXPECT_PTR_EQ(test, p.event_hooks, NULL); 189 + } 190 + } 191 + 177 192 static struct kunit_case hid_uclogic_params_test_cases[] = { 178 193 KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc, 179 194 uclogic_parse_ugee_v2_desc_gen_params), 195 + KUNIT_CASE(hid_test_uclogic_params_cleanup_event_hooks), 180 196 {} 181 197 }; 182 198
+99
drivers/hid/hid-uclogic-params.c
··· 616 616 } 617 617 618 618 /** 619 + * uclogic_params_cleanup_event_hooks - free resources used by the list of raw 620 + * event hooks. 621 + * Can be called repeatedly. 622 + * 623 + * @params: Input parameters to cleanup. Cannot be NULL. 624 + */ 625 + static void uclogic_params_cleanup_event_hooks(struct uclogic_params *params) 626 + { 627 + struct uclogic_raw_event_hook *curr, *n; 628 + 629 + if (!params || !params->event_hooks) 630 + return; 631 + 632 + list_for_each_entry_safe(curr, n, &params->event_hooks->list, list) { 633 + cancel_work_sync(&curr->work); 634 + list_del(&curr->list); 635 + kfree(curr->event); 636 + kfree(curr); 637 + } 638 + 639 + kfree(params->event_hooks); 640 + params->event_hooks = NULL; 641 + } 642 + 643 + /** 619 644 * uclogic_params_cleanup - free resources used by struct uclogic_params 620 645 * (tablet interface's parameters). 621 646 * Can be called repeatedly. ··· 656 631 for (i = 0; i < ARRAY_SIZE(params->frame_list); i++) 657 632 uclogic_params_frame_cleanup(&params->frame_list[i]); 658 633 634 + uclogic_params_cleanup_event_hooks(params); 659 635 memset(params, 0, sizeof(*params)); 660 636 } 661 637 } ··· 1307 1281 } 1308 1282 1309 1283 /** 1284 + * uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses 1285 + * connection to the USB dongle and reconnects, either because of its physical 1286 + * distance or because it was switches off and on using the frame's switch, 1287 + * uclogic_probe_interface() needs to be called again to enable the tablet. 1288 + * 1289 + * @work: The work that triggered this function. 1290 + */ 1291 + static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work) 1292 + { 1293 + struct uclogic_raw_event_hook *event_hook; 1294 + 1295 + event_hook = container_of(work, struct uclogic_raw_event_hook, work); 1296 + uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr, 1297 + uclogic_ugee_v2_probe_size, 1298 + uclogic_ugee_v2_probe_endpoint); 1299 + } 1300 + 1301 + /** 1302 + * uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events 1303 + * to be hooked for UGEE v2 devices. 1304 + * @hdev: The HID device of the tablet interface to initialize and get 1305 + * parameters from. 1306 + * @p: Parameters to fill in, cannot be NULL. 1307 + * 1308 + * Returns: 1309 + * Zero, if successful. A negative errno code on error. 1310 + */ 1311 + static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev, 1312 + struct uclogic_params *p) 1313 + { 1314 + struct uclogic_raw_event_hook *event_hook; 1315 + __u8 reconnect_event[] = { 1316 + /* Event received on wireless tablet reconnection */ 1317 + 0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 1318 + }; 1319 + 1320 + if (!p) 1321 + return -EINVAL; 1322 + 1323 + /* The reconnection event is only received if the tablet has battery */ 1324 + if (!uclogic_params_ugee_v2_has_battery(hdev)) 1325 + return 0; 1326 + 1327 + p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL); 1328 + if (!p->event_hooks) 1329 + return -ENOMEM; 1330 + 1331 + INIT_LIST_HEAD(&p->event_hooks->list); 1332 + 1333 + event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL); 1334 + if (!event_hook) 1335 + return -ENOMEM; 1336 + 1337 + INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work); 1338 + event_hook->hdev = hdev; 1339 + event_hook->size = ARRAY_SIZE(reconnect_event); 1340 + event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL); 1341 + if (!event_hook->event) 1342 + return -ENOMEM; 1343 + 1344 + list_add_tail(&event_hook->list, &p->event_hooks->list); 1345 + 1346 + return 0; 1347 + } 1348 + 1349 + /** 1310 1350 * uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by 1311 1351 * discovering their parameters. 1312 1352 * ··· 1506 1414 hid_err(hdev, "error initializing battery: %d\n", rc); 1507 1415 goto cleanup; 1508 1416 } 1417 + } 1418 + 1419 + /* Create a list of raw events to be ignored */ 1420 + rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p); 1421 + if (rc) { 1422 + hid_err(hdev, "error initializing event hook list: %d\n", rc); 1423 + goto cleanup; 1509 1424 } 1510 1425 1511 1426 output:
+16
drivers/hid/hid-uclogic-params.h
··· 18 18 19 19 #include <linux/usb.h> 20 20 #include <linux/hid.h> 21 + #include <linux/list.h> 21 22 22 23 #define UCLOGIC_MOUSE_FRAME_QUIRK BIT(0) 23 24 #define UCLOGIC_BATTERY_QUIRK BIT(1) ··· 178 177 }; 179 178 180 179 /* 180 + * List of works to be performed when a certain raw event is received. 181 + */ 182 + struct uclogic_raw_event_hook { 183 + struct hid_device *hdev; 184 + __u8 *event; 185 + size_t size; 186 + struct work_struct work; 187 + struct list_head list; 188 + }; 189 + 190 + /* 181 191 * Tablet interface report parameters. 182 192 * 183 193 * Must use declarative (descriptive) language, not imperative, to simplify ··· 228 216 * parts. Only valid, if "invalid" is false. 229 217 */ 230 218 struct uclogic_params_frame frame_list[3]; 219 + /* 220 + * List of event hooks. 221 + */ 222 + struct uclogic_raw_event_hook *event_hooks; 231 223 }; 232 224 233 225 /* Driver data */