Skip to content

Commit a251d65

Browse files
JoseExpositoJiri Kosina
authored and
Jiri Kosina
committed
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 <[email protected]> Tested-by: Andreas Grosse <[email protected]> Signed-off-by: José Expósito <[email protected]> Signed-off-by: Jiri Kosina <[email protected]>
1 parent bd85c13 commit a251d65

5 files changed

+271
-0
lines changed

drivers/hid/hid-uclogic-core-test.c

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
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 <[email protected]>
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 <[email protected]>");

drivers/hid/hid-uclogic-core.c

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,34 @@ static int uclogic_resume(struct hid_device *hdev)
249249
}
250250
#endif
251251

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+
252280
/**
253281
* uclogic_raw_event_pen - handle raw pen events (pen HID reports).
254282
*
@@ -407,6 +435,9 @@ static int uclogic_raw_event(struct hid_device *hdev,
407435
if (report->type != HID_INPUT_REPORT)
408436
return 0;
409437

438+
if (uclogic_exec_event_hook(params, data, size))
439+
return 0;
440+
410441
while (true) {
411442
/* Tweak pen reports, if necessary */
412443
if ((report_id == params->pen.id) && (size >= 2)) {
@@ -536,3 +567,7 @@ module_hid_driver(uclogic_driver);
536567
MODULE_AUTHOR("Martin Rusko");
537568
MODULE_AUTHOR("Nikolai Kondrashov");
538569
MODULE_LICENSE("GPL");
570+
571+
#ifdef CONFIG_HID_KUNIT_TEST
572+
#include "hid-uclogic-core-test.c"
573+
#endif

drivers/hid/hid-uclogic-params-test.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,25 @@ static void hid_test_uclogic_parse_ugee_v2_desc(struct kunit *test)
174174
KUNIT_EXPECT_EQ(test, params->frame_type, frame_type);
175175
}
176176

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+
177192
static struct kunit_case hid_uclogic_params_test_cases[] = {
178193
KUNIT_CASE_PARAM(hid_test_uclogic_parse_ugee_v2_desc,
179194
uclogic_parse_ugee_v2_desc_gen_params),
195+
KUNIT_CASE(hid_test_uclogic_params_cleanup_event_hooks),
180196
{}
181197
};
182198

drivers/hid/hid-uclogic-params.c

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -615,6 +615,31 @@ static int uclogic_params_frame_init_v1(struct uclogic_params_frame *frame,
615615
return rc;
616616
}
617617

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+
618643
/**
619644
* uclogic_params_cleanup - free resources used by struct uclogic_params
620645
* (tablet interface's parameters).
@@ -631,6 +656,7 @@ void uclogic_params_cleanup(struct uclogic_params *params)
631656
for (i = 0; i < ARRAY_SIZE(params->frame_list); i++)
632657
uclogic_params_frame_cleanup(&params->frame_list[i]);
633658

659+
uclogic_params_cleanup_event_hooks(params);
634660
memset(params, 0, sizeof(*params));
635661
}
636662
}
@@ -1280,6 +1306,72 @@ static int uclogic_params_ugee_v2_init_battery(struct hid_device *hdev,
12801306
return rc;
12811307
}
12821308

1309+
/**
1310+
* uclogic_params_ugee_v2_reconnect_work() - When a wireless tablet looses
1311+
* connection to the USB dongle and reconnects, either because of its physical
1312+
* distance or because it was switches off and on using the frame's switch,
1313+
* uclogic_probe_interface() needs to be called again to enable the tablet.
1314+
*
1315+
* @work: The work that triggered this function.
1316+
*/
1317+
static void uclogic_params_ugee_v2_reconnect_work(struct work_struct *work)
1318+
{
1319+
struct uclogic_raw_event_hook *event_hook;
1320+
1321+
event_hook = container_of(work, struct uclogic_raw_event_hook, work);
1322+
uclogic_probe_interface(event_hook->hdev, uclogic_ugee_v2_probe_arr,
1323+
uclogic_ugee_v2_probe_size,
1324+
uclogic_ugee_v2_probe_endpoint);
1325+
}
1326+
1327+
/**
1328+
* uclogic_params_ugee_v2_init_event_hooks() - initialize the list of events
1329+
* to be hooked for UGEE v2 devices.
1330+
* @hdev: The HID device of the tablet interface to initialize and get
1331+
* parameters from.
1332+
* @p: Parameters to fill in, cannot be NULL.
1333+
*
1334+
* Returns:
1335+
* Zero, if successful. A negative errno code on error.
1336+
*/
1337+
static int uclogic_params_ugee_v2_init_event_hooks(struct hid_device *hdev,
1338+
struct uclogic_params *p)
1339+
{
1340+
struct uclogic_raw_event_hook *event_hook;
1341+
__u8 reconnect_event[] = {
1342+
/* Event received on wireless tablet reconnection */
1343+
0x02, 0xF8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
1344+
};
1345+
1346+
if (!p)
1347+
return -EINVAL;
1348+
1349+
/* The reconnection event is only received if the tablet has battery */
1350+
if (!uclogic_params_ugee_v2_has_battery(hdev))
1351+
return 0;
1352+
1353+
p->event_hooks = kzalloc(sizeof(*p->event_hooks), GFP_KERNEL);
1354+
if (!p->event_hooks)
1355+
return -ENOMEM;
1356+
1357+
INIT_LIST_HEAD(&p->event_hooks->list);
1358+
1359+
event_hook = kzalloc(sizeof(*event_hook), GFP_KERNEL);
1360+
if (!event_hook)
1361+
return -ENOMEM;
1362+
1363+
INIT_WORK(&event_hook->work, uclogic_params_ugee_v2_reconnect_work);
1364+
event_hook->hdev = hdev;
1365+
event_hook->size = ARRAY_SIZE(reconnect_event);
1366+
event_hook->event = kmemdup(reconnect_event, event_hook->size, GFP_KERNEL);
1367+
if (!event_hook->event)
1368+
return -ENOMEM;
1369+
1370+
list_add_tail(&event_hook->list, &p->event_hooks->list);
1371+
1372+
return 0;
1373+
}
1374+
12831375
/**
12841376
* uclogic_params_ugee_v2_init() - initialize a UGEE graphics tablets by
12851377
* discovering their parameters.
@@ -1416,6 +1508,13 @@ static int uclogic_params_ugee_v2_init(struct uclogic_params *params,
14161508
}
14171509
}
14181510

1511+
/* Create a list of raw events to be ignored */
1512+
rc = uclogic_params_ugee_v2_init_event_hooks(hdev, &p);
1513+
if (rc) {
1514+
hid_err(hdev, "error initializing event hook list: %d\n", rc);
1515+
goto cleanup;
1516+
}
1517+
14191518
output:
14201519
/* Output parameters */
14211520
memcpy(params, &p, sizeof(*params));

drivers/hid/hid-uclogic-params.h

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include <linux/usb.h>
2020
#include <linux/hid.h>
21+
#include <linux/list.h>
2122

2223
#define UCLOGIC_MOUSE_FRAME_QUIRK BIT(0)
2324
#define UCLOGIC_BATTERY_QUIRK BIT(1)
@@ -176,6 +177,17 @@ struct uclogic_params_frame {
176177
unsigned int bitmap_dial_byte;
177178
};
178179

180+
/*
181+
* List of works to be performed when a certain raw event is received.
182+
*/
183+
struct uclogic_raw_event_hook {
184+
struct hid_device *hdev;
185+
__u8 *event;
186+
size_t size;
187+
struct work_struct work;
188+
struct list_head list;
189+
};
190+
179191
/*
180192
* Tablet interface report parameters.
181193
*
@@ -216,6 +228,10 @@ struct uclogic_params {
216228
* parts. Only valid, if "invalid" is false.
217229
*/
218230
struct uclogic_params_frame frame_list[3];
231+
/*
232+
* List of event hooks.
233+
*/
234+
struct uclogic_raw_event_hook *event_hooks;
219235
};
220236

221237
/* Driver data */

0 commit comments

Comments
 (0)