1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* Copyright (c) 2022 Red Hat */ |
3 | #include "hid.skel.h" |
4 | |
5 | #include "../kselftest_harness.h" |
6 | |
7 | #include <bpf/bpf.h> |
8 | #include <fcntl.h> |
9 | #include <fnmatch.h> |
10 | #include <dirent.h> |
11 | #include <poll.h> |
12 | #include <pthread.h> |
13 | #include <stdbool.h> |
14 | #include <linux/hidraw.h> |
15 | #include <linux/uhid.h> |
16 | |
17 | #define SHOW_UHID_DEBUG 0 |
18 | |
19 | static unsigned char rdesc[] = { |
20 | 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ |
21 | 0x09, 0x21, /* Usage (Vendor Usage 0x21) */ |
22 | 0xa1, 0x01, /* COLLECTION (Application) */ |
23 | 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ |
24 | 0xa1, 0x00, /* COLLECTION (Physical) */ |
25 | 0x85, 0x02, /* REPORT_ID (2) */ |
26 | 0x19, 0x01, /* USAGE_MINIMUM (1) */ |
27 | 0x29, 0x08, /* USAGE_MAXIMUM (3) */ |
28 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ |
29 | 0x25, 0xff, /* LOGICAL_MAXIMUM (255) */ |
30 | 0x95, 0x08, /* REPORT_COUNT (8) */ |
31 | 0x75, 0x08, /* REPORT_SIZE (8) */ |
32 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ |
33 | 0xc0, /* END_COLLECTION */ |
34 | 0x09, 0x01, /* Usage (Vendor Usage 0x01) */ |
35 | 0xa1, 0x00, /* COLLECTION (Physical) */ |
36 | 0x85, 0x01, /* REPORT_ID (1) */ |
37 | 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ |
38 | 0x19, 0x01, /* USAGE_MINIMUM (1) */ |
39 | 0x29, 0x03, /* USAGE_MAXIMUM (3) */ |
40 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ |
41 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ |
42 | 0x95, 0x03, /* REPORT_COUNT (3) */ |
43 | 0x75, 0x01, /* REPORT_SIZE (1) */ |
44 | 0x81, 0x02, /* INPUT (Data,Var,Abs) */ |
45 | 0x95, 0x01, /* REPORT_COUNT (1) */ |
46 | 0x75, 0x05, /* REPORT_SIZE (5) */ |
47 | 0x81, 0x01, /* INPUT (Cnst,Var,Abs) */ |
48 | 0x05, 0x01, /* USAGE_PAGE (Generic Desktop) */ |
49 | 0x09, 0x30, /* USAGE (X) */ |
50 | 0x09, 0x31, /* USAGE (Y) */ |
51 | 0x15, 0x81, /* LOGICAL_MINIMUM (-127) */ |
52 | 0x25, 0x7f, /* LOGICAL_MAXIMUM (127) */ |
53 | 0x75, 0x10, /* REPORT_SIZE (16) */ |
54 | 0x95, 0x02, /* REPORT_COUNT (2) */ |
55 | 0x81, 0x06, /* INPUT (Data,Var,Rel) */ |
56 | |
57 | 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ |
58 | 0x19, 0x01, /* USAGE_MINIMUM (1) */ |
59 | 0x29, 0x03, /* USAGE_MAXIMUM (3) */ |
60 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ |
61 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ |
62 | 0x95, 0x03, /* REPORT_COUNT (3) */ |
63 | 0x75, 0x01, /* REPORT_SIZE (1) */ |
64 | 0x91, 0x02, /* Output (Data,Var,Abs) */ |
65 | 0x95, 0x01, /* REPORT_COUNT (1) */ |
66 | 0x75, 0x05, /* REPORT_SIZE (5) */ |
67 | 0x91, 0x01, /* Output (Cnst,Var,Abs) */ |
68 | |
69 | 0x06, 0x00, 0xff, /* Usage Page (Vendor Defined Page 1) */ |
70 | 0x19, 0x06, /* USAGE_MINIMUM (6) */ |
71 | 0x29, 0x08, /* USAGE_MAXIMUM (8) */ |
72 | 0x15, 0x00, /* LOGICAL_MINIMUM (0) */ |
73 | 0x25, 0x01, /* LOGICAL_MAXIMUM (1) */ |
74 | 0x95, 0x03, /* REPORT_COUNT (3) */ |
75 | 0x75, 0x01, /* REPORT_SIZE (1) */ |
76 | 0xb1, 0x02, /* Feature (Data,Var,Abs) */ |
77 | 0x95, 0x01, /* REPORT_COUNT (1) */ |
78 | 0x75, 0x05, /* REPORT_SIZE (5) */ |
79 | 0x91, 0x01, /* Output (Cnst,Var,Abs) */ |
80 | |
81 | 0xc0, /* END_COLLECTION */ |
82 | 0xc0, /* END_COLLECTION */ |
83 | }; |
84 | |
85 | static __u8 feature_data[] = { 1, 2 }; |
86 | |
87 | struct attach_prog_args { |
88 | int prog_fd; |
89 | unsigned int hid; |
90 | int retval; |
91 | int insert_head; |
92 | }; |
93 | |
94 | struct hid_hw_request_syscall_args { |
95 | __u8 data[10]; |
96 | unsigned int hid; |
97 | int retval; |
98 | size_t size; |
99 | enum hid_report_type type; |
100 | __u8 request_type; |
101 | }; |
102 | |
103 | #define ASSERT_OK(data) ASSERT_FALSE(data) |
104 | #define ASSERT_OK_PTR(ptr) ASSERT_NE(NULL, ptr) |
105 | |
106 | #define UHID_LOG(fmt, ...) do { \ |
107 | if (SHOW_UHID_DEBUG) \ |
108 | TH_LOG(fmt, ##__VA_ARGS__); \ |
109 | } while (0) |
110 | |
111 | static pthread_mutex_t uhid_started_mtx = PTHREAD_MUTEX_INITIALIZER; |
112 | static pthread_cond_t uhid_started = PTHREAD_COND_INITIALIZER; |
113 | |
114 | /* no need to protect uhid_stopped, only one thread accesses it */ |
115 | static bool uhid_stopped; |
116 | |
117 | static int uhid_write(struct __test_metadata *_metadata, int fd, const struct uhid_event *ev) |
118 | { |
119 | ssize_t ret; |
120 | |
121 | ret = write(fd, ev, sizeof(*ev)); |
122 | if (ret < 0) { |
123 | TH_LOG("Cannot write to uhid: %m" ); |
124 | return -errno; |
125 | } else if (ret != sizeof(*ev)) { |
126 | TH_LOG("Wrong size written to uhid: %zd != %zu" , |
127 | ret, sizeof(ev)); |
128 | return -EFAULT; |
129 | } else { |
130 | return 0; |
131 | } |
132 | } |
133 | |
134 | static int uhid_create(struct __test_metadata *_metadata, int fd, int rand_nb) |
135 | { |
136 | struct uhid_event ev; |
137 | char buf[25]; |
138 | |
139 | sprintf(buf, fmt: "test-uhid-device-%d" , rand_nb); |
140 | |
141 | memset(&ev, 0, sizeof(ev)); |
142 | ev.type = UHID_CREATE; |
143 | strcpy(p: (char *)ev.u.create.name, q: buf); |
144 | ev.u.create.rd_data = rdesc; |
145 | ev.u.create.rd_size = sizeof(rdesc); |
146 | ev.u.create.bus = BUS_USB; |
147 | ev.u.create.vendor = 0x0001; |
148 | ev.u.create.product = 0x0a37; |
149 | ev.u.create.version = 0; |
150 | ev.u.create.country = 0; |
151 | |
152 | sprintf(buf, fmt: "%d" , rand_nb); |
153 | strcpy(p: (char *)ev.u.create.phys, q: buf); |
154 | |
155 | return uhid_write(_metadata, fd, ev: &ev); |
156 | } |
157 | |
158 | static void uhid_destroy(struct __test_metadata *_metadata, int fd) |
159 | { |
160 | struct uhid_event ev; |
161 | |
162 | memset(&ev, 0, sizeof(ev)); |
163 | ev.type = UHID_DESTROY; |
164 | |
165 | uhid_write(_metadata, fd, ev: &ev); |
166 | } |
167 | |
168 | static int uhid_event(struct __test_metadata *_metadata, int fd) |
169 | { |
170 | struct uhid_event ev, answer; |
171 | ssize_t ret; |
172 | |
173 | memset(&ev, 0, sizeof(ev)); |
174 | ret = read(fd, &ev, sizeof(ev)); |
175 | if (ret == 0) { |
176 | UHID_LOG("Read HUP on uhid-cdev" ); |
177 | return -EFAULT; |
178 | } else if (ret < 0) { |
179 | UHID_LOG("Cannot read uhid-cdev: %m" ); |
180 | return -errno; |
181 | } else if (ret != sizeof(ev)) { |
182 | UHID_LOG("Invalid size read from uhid-dev: %zd != %zu" , |
183 | ret, sizeof(ev)); |
184 | return -EFAULT; |
185 | } |
186 | |
187 | switch (ev.type) { |
188 | case UHID_START: |
189 | pthread_mutex_lock(&uhid_started_mtx); |
190 | pthread_cond_signal(&uhid_started); |
191 | pthread_mutex_unlock(&uhid_started_mtx); |
192 | |
193 | UHID_LOG("UHID_START from uhid-dev" ); |
194 | break; |
195 | case UHID_STOP: |
196 | uhid_stopped = true; |
197 | |
198 | UHID_LOG("UHID_STOP from uhid-dev" ); |
199 | break; |
200 | case UHID_OPEN: |
201 | UHID_LOG("UHID_OPEN from uhid-dev" ); |
202 | break; |
203 | case UHID_CLOSE: |
204 | UHID_LOG("UHID_CLOSE from uhid-dev" ); |
205 | break; |
206 | case UHID_OUTPUT: |
207 | UHID_LOG("UHID_OUTPUT from uhid-dev" ); |
208 | break; |
209 | case UHID_GET_REPORT: |
210 | UHID_LOG("UHID_GET_REPORT from uhid-dev" ); |
211 | |
212 | answer.type = UHID_GET_REPORT_REPLY; |
213 | answer.u.get_report_reply.id = ev.u.get_report.id; |
214 | answer.u.get_report_reply.err = ev.u.get_report.rnum == 1 ? 0 : -EIO; |
215 | answer.u.get_report_reply.size = sizeof(feature_data); |
216 | memcpy(answer.u.get_report_reply.data, feature_data, sizeof(feature_data)); |
217 | |
218 | uhid_write(_metadata, fd, ev: &answer); |
219 | |
220 | break; |
221 | case UHID_SET_REPORT: |
222 | UHID_LOG("UHID_SET_REPORT from uhid-dev" ); |
223 | break; |
224 | default: |
225 | TH_LOG("Invalid event from uhid-dev: %u" , ev.type); |
226 | } |
227 | |
228 | return 0; |
229 | } |
230 | |
231 | struct uhid_thread_args { |
232 | int fd; |
233 | struct __test_metadata *_metadata; |
234 | }; |
235 | static void *uhid_read_events_thread(void *arg) |
236 | { |
237 | struct uhid_thread_args *args = (struct uhid_thread_args *)arg; |
238 | struct __test_metadata *_metadata = args->_metadata; |
239 | struct pollfd pfds[1]; |
240 | int fd = args->fd; |
241 | int ret = 0; |
242 | |
243 | pfds[0].fd = fd; |
244 | pfds[0].events = POLLIN; |
245 | |
246 | uhid_stopped = false; |
247 | |
248 | while (!uhid_stopped) { |
249 | ret = poll(pfds, 1, 100); |
250 | if (ret < 0) { |
251 | TH_LOG("Cannot poll for fds: %m" ); |
252 | break; |
253 | } |
254 | if (pfds[0].revents & POLLIN) { |
255 | ret = uhid_event(_metadata, fd); |
256 | if (ret) |
257 | break; |
258 | } |
259 | } |
260 | |
261 | return (void *)(long)ret; |
262 | } |
263 | |
264 | static int uhid_start_listener(struct __test_metadata *_metadata, pthread_t *tid, int uhid_fd) |
265 | { |
266 | struct uhid_thread_args args = { |
267 | .fd = uhid_fd, |
268 | ._metadata = _metadata, |
269 | }; |
270 | int err; |
271 | |
272 | pthread_mutex_lock(&uhid_started_mtx); |
273 | err = pthread_create(tid, NULL, uhid_read_events_thread, (void *)&args); |
274 | ASSERT_EQ(0, err) { |
275 | TH_LOG("Could not start the uhid thread: %d" , err); |
276 | pthread_mutex_unlock(&uhid_started_mtx); |
277 | close(uhid_fd); |
278 | return -EIO; |
279 | } |
280 | pthread_cond_wait(&uhid_started, &uhid_started_mtx); |
281 | pthread_mutex_unlock(&uhid_started_mtx); |
282 | |
283 | return 0; |
284 | } |
285 | |
286 | static int uhid_send_event(struct __test_metadata *_metadata, int fd, __u8 *buf, size_t size) |
287 | { |
288 | struct uhid_event ev; |
289 | |
290 | if (size > sizeof(ev.u.input.data)) |
291 | return -E2BIG; |
292 | |
293 | memset(&ev, 0, sizeof(ev)); |
294 | ev.type = UHID_INPUT2; |
295 | ev.u.input2.size = size; |
296 | |
297 | memcpy(ev.u.input2.data, buf, size); |
298 | |
299 | return uhid_write(_metadata, fd, ev: &ev); |
300 | } |
301 | |
302 | static int setup_uhid(struct __test_metadata *_metadata, int rand_nb) |
303 | { |
304 | int fd; |
305 | const char *path = "/dev/uhid" ; |
306 | int ret; |
307 | |
308 | fd = open(path, O_RDWR | O_CLOEXEC); |
309 | ASSERT_GE(fd, 0) TH_LOG("open uhid-cdev failed; %d" , fd); |
310 | |
311 | ret = uhid_create(_metadata, fd, rand_nb); |
312 | ASSERT_EQ(0, ret) { |
313 | TH_LOG("create uhid device failed: %d" , ret); |
314 | close(fd); |
315 | } |
316 | |
317 | return fd; |
318 | } |
319 | |
320 | static bool match_sysfs_device(int dev_id, const char *workdir, struct dirent *dir) |
321 | { |
322 | const char *target = "0003:0001:0A37.*" ; |
323 | char phys[512]; |
324 | char uevent[1024]; |
325 | char temp[512]; |
326 | int fd, nread; |
327 | bool found = false; |
328 | |
329 | if (fnmatch(target, dir->d_name, 0)) |
330 | return false; |
331 | |
332 | /* we found the correct VID/PID, now check for phys */ |
333 | sprintf(buf: uevent, fmt: "%s/%s/uevent" , workdir, dir->d_name); |
334 | |
335 | fd = open(uevent, O_RDONLY | O_NONBLOCK); |
336 | if (fd < 0) |
337 | return false; |
338 | |
339 | sprintf(buf: phys, fmt: "PHYS=%d" , dev_id); |
340 | |
341 | nread = read(fd, temp, ARRAY_SIZE(temp)); |
342 | if (nread > 0 && (strstr(temp, phys)) != NULL) |
343 | found = true; |
344 | |
345 | close(fd); |
346 | |
347 | return found; |
348 | } |
349 | |
350 | static int get_hid_id(int dev_id) |
351 | { |
352 | const char *workdir = "/sys/devices/virtual/misc/uhid" ; |
353 | const char *str_id; |
354 | DIR *d; |
355 | struct dirent *dir; |
356 | int found = -1, attempts = 3; |
357 | |
358 | /* it would be nice to be able to use nftw, but the no_alu32 target doesn't support it */ |
359 | |
360 | while (found < 0 && attempts > 0) { |
361 | attempts--; |
362 | d = opendir(workdir); |
363 | if (d) { |
364 | while ((dir = readdir(d)) != NULL) { |
365 | if (!match_sysfs_device(dev_id, workdir, dir)) |
366 | continue; |
367 | |
368 | str_id = dir->d_name + sizeof("0003:0001:0A37." ); |
369 | found = (int)strtol(str_id, NULL, 16); |
370 | |
371 | break; |
372 | } |
373 | closedir(d); |
374 | } |
375 | if (found < 0) |
376 | usleep(100000); |
377 | } |
378 | |
379 | return found; |
380 | } |
381 | |
382 | static int get_hidraw(int dev_id) |
383 | { |
384 | const char *workdir = "/sys/devices/virtual/misc/uhid" ; |
385 | char sysfs[1024]; |
386 | DIR *d, *subd; |
387 | struct dirent *dir, *subdir; |
388 | int i, found = -1; |
389 | |
390 | /* retry 5 times in case the system is loaded */ |
391 | for (i = 5; i > 0; i--) { |
392 | usleep(10); |
393 | d = opendir(workdir); |
394 | |
395 | if (!d) |
396 | continue; |
397 | |
398 | while ((dir = readdir(d)) != NULL) { |
399 | if (!match_sysfs_device(dev_id, workdir, dir)) |
400 | continue; |
401 | |
402 | sprintf(buf: sysfs, fmt: "%s/%s/hidraw" , workdir, dir->d_name); |
403 | |
404 | subd = opendir(sysfs); |
405 | if (!subd) |
406 | continue; |
407 | |
408 | while ((subdir = readdir(subd)) != NULL) { |
409 | if (fnmatch("hidraw*" , subdir->d_name, 0)) |
410 | continue; |
411 | |
412 | found = atoi(subdir->d_name + strlen("hidraw" )); |
413 | } |
414 | |
415 | closedir(subd); |
416 | |
417 | if (found > 0) |
418 | break; |
419 | } |
420 | closedir(d); |
421 | } |
422 | |
423 | return found; |
424 | } |
425 | |
426 | static int open_hidraw(int dev_id) |
427 | { |
428 | int hidraw_number; |
429 | char hidraw_path[64] = { 0 }; |
430 | |
431 | hidraw_number = get_hidraw(dev_id); |
432 | if (hidraw_number < 0) |
433 | return hidraw_number; |
434 | |
435 | /* open hidraw node to check the other side of the pipe */ |
436 | sprintf(buf: hidraw_path, fmt: "/dev/hidraw%d" , hidraw_number); |
437 | return open(hidraw_path, O_RDWR | O_NONBLOCK); |
438 | } |
439 | |
440 | FIXTURE(hid_bpf) { |
441 | int dev_id; |
442 | int uhid_fd; |
443 | int hidraw_fd; |
444 | int hid_id; |
445 | pthread_t tid; |
446 | struct hid *skel; |
447 | int hid_links[3]; /* max number of programs loaded in a single test */ |
448 | }; |
449 | static void detach_bpf(FIXTURE_DATA(hid_bpf) * self) |
450 | { |
451 | int i; |
452 | |
453 | if (self->hidraw_fd) |
454 | close(self->hidraw_fd); |
455 | self->hidraw_fd = 0; |
456 | |
457 | for (i = 0; i < ARRAY_SIZE(self->hid_links); i++) { |
458 | if (self->hid_links[i]) |
459 | close(self->hid_links[i]); |
460 | } |
461 | |
462 | hid__destroy(self->skel); |
463 | self->skel = NULL; |
464 | } |
465 | |
466 | FIXTURE_TEARDOWN(hid_bpf) { |
467 | void *uhid_err; |
468 | |
469 | uhid_destroy(_metadata, fd: self->uhid_fd); |
470 | |
471 | detach_bpf(self); |
472 | pthread_join(self->tid, &uhid_err); |
473 | } |
474 | #define TEARDOWN_LOG(fmt, ...) do { \ |
475 | TH_LOG(fmt, ##__VA_ARGS__); \ |
476 | hid_bpf_teardown(_metadata, self, variant); \ |
477 | } while (0) |
478 | |
479 | FIXTURE_SETUP(hid_bpf) |
480 | { |
481 | time_t t; |
482 | int err; |
483 | |
484 | /* initialize random number generator */ |
485 | srand((unsigned int)time(&t)); |
486 | |
487 | self->dev_id = rand() % 1024; |
488 | |
489 | self->uhid_fd = setup_uhid(_metadata, rand_nb: self->dev_id); |
490 | |
491 | /* locate the uev, self, variant);ent file of the created device */ |
492 | self->hid_id = get_hid_id(dev_id: self->dev_id); |
493 | ASSERT_GT(self->hid_id, 0) |
494 | TEARDOWN_LOG("Could not locate uhid device id: %d" , self->hid_id); |
495 | |
496 | err = uhid_start_listener(_metadata, &self->tid, self->uhid_fd); |
497 | ASSERT_EQ(0, err) TEARDOWN_LOG("could not start udev listener: %d" , err); |
498 | } |
499 | |
500 | struct test_program { |
501 | const char *name; |
502 | int insert_head; |
503 | }; |
504 | #define LOAD_PROGRAMS(progs) \ |
505 | load_programs(progs, ARRAY_SIZE(progs), _metadata, self, variant) |
506 | #define LOAD_BPF \ |
507 | load_programs(NULL, 0, _metadata, self, variant) |
508 | static void load_programs(const struct test_program programs[], |
509 | const size_t progs_count, |
510 | struct __test_metadata *_metadata, |
511 | FIXTURE_DATA(hid_bpf) * self, |
512 | const FIXTURE_VARIANT(hid_bpf) * variant) |
513 | { |
514 | int attach_fd, err = -EINVAL; |
515 | struct attach_prog_args args = { |
516 | .retval = -1, |
517 | }; |
518 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattr, |
519 | .ctx_in = &args, |
520 | .ctx_size_in = sizeof(args), |
521 | ); |
522 | |
523 | ASSERT_LE(progs_count, ARRAY_SIZE(self->hid_links)) |
524 | TH_LOG("too many programs are to be loaded" ); |
525 | |
526 | /* open the bpf file */ |
527 | self->skel = hid__open(); |
528 | ASSERT_OK_PTR(self->skel) TEARDOWN_LOG("Error while calling hid__open" ); |
529 | |
530 | for (int i = 0; i < progs_count; i++) { |
531 | struct bpf_program *prog; |
532 | |
533 | prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj, |
534 | programs[i].name); |
535 | ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'" , programs[i].name); |
536 | |
537 | bpf_program__set_autoload(prog, true); |
538 | } |
539 | |
540 | err = hid__load(self->skel); |
541 | ASSERT_OK(err) TH_LOG("hid_skel_load failed: %d" , err); |
542 | |
543 | attach_fd = bpf_program__fd(self->skel->progs.attach_prog); |
544 | ASSERT_GE(attach_fd, 0) TH_LOG("locate attach_prog: %d" , attach_fd); |
545 | |
546 | for (int i = 0; i < progs_count; i++) { |
547 | struct bpf_program *prog; |
548 | |
549 | prog = bpf_object__find_program_by_name(*self->skel->skeleton->obj, |
550 | programs[i].name); |
551 | ASSERT_OK_PTR(prog) TH_LOG("can not find program by name '%s'" , programs[i].name); |
552 | |
553 | args.prog_fd = bpf_program__fd(prog); |
554 | args.hid = self->hid_id; |
555 | args.insert_head = programs[i].insert_head; |
556 | err = bpf_prog_test_run_opts(prog_fd: attach_fd, opts: &tattr); |
557 | ASSERT_GE(args.retval, 0) |
558 | TH_LOG("attach_hid(%s): %d" , programs[i].name, args.retval); |
559 | |
560 | self->hid_links[i] = args.retval; |
561 | } |
562 | |
563 | self->hidraw_fd = open_hidraw(dev_id: self->dev_id); |
564 | ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw" ); |
565 | } |
566 | |
567 | /* |
568 | * A simple test to see if the fixture is working fine. |
569 | * If this fails, none of the other tests will pass. |
570 | */ |
571 | TEST_F(hid_bpf, test_create_uhid) |
572 | { |
573 | } |
574 | |
575 | /* |
576 | * Attach hid_first_event to the given uhid device, |
577 | * retrieve and open the matching hidraw node, |
578 | * inject one event in the uhid device, |
579 | * check that the program sees it and can change the data |
580 | */ |
581 | TEST_F(hid_bpf, raw_event) |
582 | { |
583 | const struct test_program progs[] = { |
584 | { .name = "hid_first_event" }, |
585 | }; |
586 | __u8 buf[10] = {0}; |
587 | int err; |
588 | |
589 | LOAD_PROGRAMS(progs); |
590 | |
591 | /* check that the program is correctly loaded */ |
592 | ASSERT_EQ(self->skel->data->callback_check, 52) TH_LOG("callback_check1" ); |
593 | ASSERT_EQ(self->skel->data->callback2_check, 52) TH_LOG("callback2_check1" ); |
594 | |
595 | /* inject one event */ |
596 | buf[0] = 1; |
597 | buf[1] = 42; |
598 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
599 | |
600 | /* check that hid_first_event() was executed */ |
601 | ASSERT_EQ(self->skel->data->callback_check, 42) TH_LOG("callback_check1" ); |
602 | |
603 | /* read the data from hidraw */ |
604 | memset(buf, 0, sizeof(buf)); |
605 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
606 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw" ); |
607 | ASSERT_EQ(buf[0], 1); |
608 | ASSERT_EQ(buf[2], 47); |
609 | |
610 | /* inject another event */ |
611 | memset(buf, 0, sizeof(buf)); |
612 | buf[0] = 1; |
613 | buf[1] = 47; |
614 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
615 | |
616 | /* check that hid_first_event() was executed */ |
617 | ASSERT_EQ(self->skel->data->callback_check, 47) TH_LOG("callback_check1" ); |
618 | |
619 | /* read the data from hidraw */ |
620 | memset(buf, 0, sizeof(buf)); |
621 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
622 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw" ); |
623 | ASSERT_EQ(buf[2], 52); |
624 | } |
625 | |
626 | /* |
627 | * Ensures that we can attach/detach programs |
628 | */ |
629 | TEST_F(hid_bpf, test_attach_detach) |
630 | { |
631 | const struct test_program progs[] = { |
632 | { .name = "hid_first_event" }, |
633 | { .name = "hid_second_event" }, |
634 | }; |
635 | __u8 buf[10] = {0}; |
636 | int err, link; |
637 | |
638 | LOAD_PROGRAMS(progs); |
639 | |
640 | link = self->hid_links[0]; |
641 | ASSERT_GT(link, 0) TH_LOG("HID-BPF link not created" ); |
642 | |
643 | /* inject one event */ |
644 | buf[0] = 1; |
645 | buf[1] = 42; |
646 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
647 | |
648 | /* read the data from hidraw */ |
649 | memset(buf, 0, sizeof(buf)); |
650 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
651 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw" ); |
652 | ASSERT_EQ(buf[0], 1); |
653 | ASSERT_EQ(buf[2], 47); |
654 | |
655 | /* make sure both programs are run */ |
656 | ASSERT_EQ(buf[3], 52); |
657 | |
658 | /* pin the first program and immediately unpin it */ |
659 | #define PIN_PATH "/sys/fs/bpf/hid_first_event" |
660 | err = bpf_obj_pin(fd: link, PIN_PATH); |
661 | ASSERT_OK(err) TH_LOG("error while calling bpf_obj_pin" ); |
662 | remove(PIN_PATH); |
663 | #undef PIN_PATH |
664 | usleep(100000); |
665 | |
666 | /* detach the program */ |
667 | detach_bpf(self); |
668 | |
669 | self->hidraw_fd = open_hidraw(dev_id: self->dev_id); |
670 | ASSERT_GE(self->hidraw_fd, 0) TH_LOG("open_hidraw" ); |
671 | |
672 | /* inject another event */ |
673 | memset(buf, 0, sizeof(buf)); |
674 | buf[0] = 1; |
675 | buf[1] = 47; |
676 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
677 | |
678 | /* read the data from hidraw */ |
679 | memset(buf, 0, sizeof(buf)); |
680 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
681 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw_no_bpf" ); |
682 | ASSERT_EQ(buf[0], 1); |
683 | ASSERT_EQ(buf[1], 47); |
684 | ASSERT_EQ(buf[2], 0); |
685 | ASSERT_EQ(buf[3], 0); |
686 | |
687 | /* re-attach our program */ |
688 | |
689 | LOAD_PROGRAMS(progs); |
690 | |
691 | /* inject one event */ |
692 | memset(buf, 0, sizeof(buf)); |
693 | buf[0] = 1; |
694 | buf[1] = 42; |
695 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
696 | |
697 | /* read the data from hidraw */ |
698 | memset(buf, 0, sizeof(buf)); |
699 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
700 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw" ); |
701 | ASSERT_EQ(buf[0], 1); |
702 | ASSERT_EQ(buf[2], 47); |
703 | ASSERT_EQ(buf[3], 52); |
704 | } |
705 | |
706 | /* |
707 | * Attach hid_change_report_id to the given uhid device, |
708 | * retrieve and open the matching hidraw node, |
709 | * inject one event in the uhid device, |
710 | * check that the program sees it and can change the data |
711 | */ |
712 | TEST_F(hid_bpf, test_hid_change_report) |
713 | { |
714 | const struct test_program progs[] = { |
715 | { .name = "hid_change_report_id" }, |
716 | }; |
717 | __u8 buf[10] = {0}; |
718 | int err; |
719 | |
720 | LOAD_PROGRAMS(progs); |
721 | |
722 | /* inject one event */ |
723 | buf[0] = 1; |
724 | buf[1] = 42; |
725 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
726 | |
727 | /* read the data from hidraw */ |
728 | memset(buf, 0, sizeof(buf)); |
729 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
730 | ASSERT_EQ(err, 9) TH_LOG("read_hidraw" ); |
731 | ASSERT_EQ(buf[0], 2); |
732 | ASSERT_EQ(buf[1], 42); |
733 | ASSERT_EQ(buf[2], 0) TH_LOG("leftovers_from_previous_test" ); |
734 | } |
735 | |
736 | /* |
737 | * Attach hid_user_raw_request to the given uhid device, |
738 | * call the bpf program from userspace |
739 | * check that the program is called and does the expected. |
740 | */ |
741 | TEST_F(hid_bpf, test_hid_user_raw_request_call) |
742 | { |
743 | struct hid_hw_request_syscall_args args = { |
744 | .retval = -1, |
745 | .type = HID_FEATURE_REPORT, |
746 | .request_type = HID_REQ_GET_REPORT, |
747 | .size = 10, |
748 | }; |
749 | DECLARE_LIBBPF_OPTS(bpf_test_run_opts, tattrs, |
750 | .ctx_in = &args, |
751 | .ctx_size_in = sizeof(args), |
752 | ); |
753 | int err, prog_fd; |
754 | |
755 | LOAD_BPF; |
756 | |
757 | args.hid = self->hid_id; |
758 | args.data[0] = 1; /* report ID */ |
759 | |
760 | prog_fd = bpf_program__fd(self->skel->progs.hid_user_raw_request); |
761 | |
762 | err = bpf_prog_test_run_opts(prog_fd, opts: &tattrs); |
763 | ASSERT_OK(err) TH_LOG("error while calling bpf_prog_test_run_opts" ); |
764 | |
765 | ASSERT_EQ(args.retval, 2); |
766 | |
767 | ASSERT_EQ(args.data[1], 2); |
768 | } |
769 | |
770 | /* |
771 | * Attach hid_insert{0,1,2} to the given uhid device, |
772 | * retrieve and open the matching hidraw node, |
773 | * inject one event in the uhid device, |
774 | * check that the programs have been inserted in the correct order. |
775 | */ |
776 | TEST_F(hid_bpf, test_hid_attach_flags) |
777 | { |
778 | const struct test_program progs[] = { |
779 | { |
780 | .name = "hid_test_insert2" , |
781 | .insert_head = 0, |
782 | }, |
783 | { |
784 | .name = "hid_test_insert1" , |
785 | .insert_head = 1, |
786 | }, |
787 | { |
788 | .name = "hid_test_insert3" , |
789 | .insert_head = 0, |
790 | }, |
791 | }; |
792 | __u8 buf[10] = {0}; |
793 | int err; |
794 | |
795 | LOAD_PROGRAMS(progs); |
796 | |
797 | /* inject one event */ |
798 | buf[0] = 1; |
799 | uhid_send_event(_metadata, fd: self->uhid_fd, buf, size: 6); |
800 | |
801 | /* read the data from hidraw */ |
802 | memset(buf, 0, sizeof(buf)); |
803 | err = read(self->hidraw_fd, buf, sizeof(buf)); |
804 | ASSERT_EQ(err, 6) TH_LOG("read_hidraw" ); |
805 | ASSERT_EQ(buf[1], 1); |
806 | ASSERT_EQ(buf[2], 2); |
807 | ASSERT_EQ(buf[3], 3); |
808 | } |
809 | |
810 | /* |
811 | * Attach hid_rdesc_fixup to the given uhid device, |
812 | * retrieve and open the matching hidraw node, |
813 | * check that the hidraw report descriptor has been updated. |
814 | */ |
815 | TEST_F(hid_bpf, test_rdesc_fixup) |
816 | { |
817 | struct hidraw_report_descriptor rpt_desc = {0}; |
818 | const struct test_program progs[] = { |
819 | { .name = "hid_rdesc_fixup" }, |
820 | }; |
821 | int err, desc_size; |
822 | |
823 | LOAD_PROGRAMS(progs); |
824 | |
825 | /* check that hid_rdesc_fixup() was executed */ |
826 | ASSERT_EQ(self->skel->data->callback2_check, 0x21); |
827 | |
828 | /* read the exposed report descriptor from hidraw */ |
829 | err = ioctl(self->hidraw_fd, HIDIOCGRDESCSIZE, &desc_size); |
830 | ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESCSIZE: %d" , err); |
831 | |
832 | /* ensure the new size of the rdesc is bigger than the old one */ |
833 | ASSERT_GT(desc_size, sizeof(rdesc)); |
834 | |
835 | rpt_desc.size = desc_size; |
836 | err = ioctl(self->hidraw_fd, HIDIOCGRDESC, &rpt_desc); |
837 | ASSERT_GE(err, 0) TH_LOG("error while reading HIDIOCGRDESC: %d" , err); |
838 | |
839 | ASSERT_EQ(rpt_desc.value[4], 0x42); |
840 | } |
841 | |
842 | static int libbpf_print_fn(enum libbpf_print_level level, |
843 | const char *format, va_list args) |
844 | { |
845 | char buf[1024]; |
846 | |
847 | if (level == LIBBPF_DEBUG) |
848 | return 0; |
849 | |
850 | snprintf(buf, size: sizeof(buf), fmt: "# %s" , format); |
851 | |
852 | vfprintf(stdout, buf, args); |
853 | return 0; |
854 | } |
855 | |
856 | static void __attribute__((constructor)) __constructor_order_last(void) |
857 | { |
858 | if (!__constructor_order) |
859 | __constructor_order = _CONSTRUCTOR_ORDER_BACKWARD; |
860 | } |
861 | |
862 | int main(int argc, char **argv) |
863 | { |
864 | /* Use libbpf 1.0 API mode */ |
865 | libbpf_set_strict_mode(mode: LIBBPF_STRICT_ALL); |
866 | libbpf_set_print(libbpf_print_fn); |
867 | |
868 | return test_harness_run(argc, argv); |
869 | } |
870 | |