Skip to content

Commit 480e91e

Browse files
seanyoungKernel Patches Daemon
authored andcommitted
selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage
While porting, extend coverage of the BPF_LIRC_MODE2 attach/detach/ query API: - bpf_prog_attach() with invalid flags is rejected with -EINVAL and does not attach the program - bpf_prog_query() with invalid flags is rejected with -EINVAL without disturbing existing attachments - bpf_prog_query() reports the correct program id, not just count, at each step, via bpf_prog_get_info_by_fd() - a lirc chardev can hold more than one attached program: load a second, independent instance, attach it alongside the first, confirm both are reported by bpf_prog_query(), then detach it without disturbing the first program's attachment - detaching an already-detached program consistently fails with -ENOENT, for both the first and second program Signed-off-by: Sean Young <sean@mess.org> Assisted-by: Claude:claude-sonnet-5
1 parent 738fefc commit 480e91e

7 files changed

Lines changed: 366 additions & 248 deletions

File tree

tools/testing/selftests/bpf/.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ test_verifier_log
1717
feature
1818
urandom_read
1919
test_sockmap
20-
test_lirc_mode2_user
2120
flow_dissector_load
2221
test_tcpnotify_user
2322
test_libbpf

tools/testing/selftests/bpf/Makefile

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,6 @@ TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c)
123123

124124
# Order correspond to 'make run_tests' order
125125
TEST_PROGS := test_kmod.sh \
126-
test_lirc_mode2.sh \
127126
test_bpftool_build.sh \
128127
test_doc_build.sh \
129128
test_xsk.sh \
@@ -141,7 +140,6 @@ TEST_GEN_PROGS_EXTENDED = \
141140
bench \
142141
flow_dissector_load \
143142
test_cpp \
144-
test_lirc_mode2_user \
145143
veristat \
146144
xdp_features \
147145
xdp_hw_metadata \
@@ -338,7 +336,6 @@ $(OUTPUT)/test_sockmap: $(CGROUP_HELPERS) $(TESTING_HELPERS)
338336
$(OUTPUT)/test_tcpnotify_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(TRACE_HELPERS)
339337
$(OUTPUT)/test_sock_fields: $(CGROUP_HELPERS) $(TESTING_HELPERS)
340338
$(OUTPUT)/test_tag: $(TESTING_HELPERS)
341-
$(OUTPUT)/test_lirc_mode2_user: $(TESTING_HELPERS)
342339
$(OUTPUT)/flow_dissector_load: $(TESTING_HELPERS)
343340
$(OUTPUT)/test_maps: $(TESTING_HELPERS)
344341
$(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// test ir decoder
3+
//
4+
// Copyright (C) 2018 Sean Young <sean@mess.org>
5+
6+
// A lirc chardev is a device representing a consumer IR (cir) device which
7+
// can receive infrared signals from remote control and/or transmit IR.
8+
//
9+
// IR is sent as a series of pulses and space somewhat like morse code. The
10+
// BPF program can decode this into scancodes so that rc-core can translate
11+
// this into input key codes using the rc keymap.
12+
//
13+
// This test works by sending IR over rc-loopback, so the IR is processed by
14+
// BPF and then decoded into scancodes. The lirc chardev must be the one
15+
// associated with rc-loopback, see the output of ir-keytable(1).
16+
//
17+
// The following CONFIG options must be enabled for the test to succeed:
18+
// CONFIG_RC_CORE=y
19+
// CONFIG_BPF_LIRC_MODE2=y
20+
// CONFIG_RC_LOOPBACK=y
21+
// CONFIG_LIRC=y
22+
23+
#include <linux/input.h>
24+
#include <linux/lirc.h>
25+
#include <glob.h>
26+
#include <limits.h>
27+
#include <poll.h>
28+
#include <test_progs.h>
29+
#include "lirc_mode2.skel.h"
30+
31+
/*
32+
* Read the DEVNAME= line out of the first uevent file that matches
33+
* pattern, and turn it into a /dev/<name> path.
34+
*/
35+
static bool find_devname(const char *pattern, char *path, size_t path_sz)
36+
{
37+
glob_t gl = {};
38+
bool found = false;
39+
FILE *f;
40+
41+
if (glob(pattern, 0, NULL, &gl) || gl.gl_pathc == 0)
42+
goto out;
43+
44+
f = fopen(gl.gl_pathv[0], "r");
45+
if (!f)
46+
goto out;
47+
48+
char line[256];
49+
50+
while (fgets(line, sizeof(line), f)) {
51+
char *val;
52+
53+
if (strncmp(line, "DEVNAME=", 8))
54+
continue;
55+
56+
val = line + 8;
57+
val[strcspn(val, "\n")] = '\0';
58+
snprintf(path, path_sz, "/dev/%s", val);
59+
found = true;
60+
break;
61+
}
62+
63+
fclose(f);
64+
out:
65+
globfree(&gl);
66+
return found;
67+
}
68+
69+
/* Load rc-loopback and find the lirc and input chardevs it created. */
70+
static bool find_loopback_devices(char *lirc_path, char *input_path,
71+
size_t path_sz)
72+
{
73+
glob_t gl = {};
74+
bool found = false;
75+
76+
/* Ignore failure, we check for the resulting devices below. */
77+
SYS_NOFAIL("modprobe rc-loopback > /dev/null 2>&1");
78+
79+
if (glob("/sys/class/rc/rc*", 0, NULL, &gl))
80+
goto out;
81+
82+
for (size_t i = 0; i < gl.gl_pathc; i++) {
83+
const char *rcdir = gl.gl_pathv[i];
84+
char uevent_path[PATH_MAX];
85+
char uevent[4096];
86+
char pattern[PATH_MAX];
87+
FILE *f;
88+
size_t n;
89+
90+
snprintf(uevent_path, sizeof(uevent_path), "%s/uevent", rcdir);
91+
f = fopen(uevent_path, "r");
92+
if (!f)
93+
continue;
94+
n = fread(uevent, 1, sizeof(uevent) - 1, f);
95+
fclose(f);
96+
uevent[n] = '\0';
97+
98+
if (!strstr(uevent, "DRV_NAME=rc-loopback"))
99+
continue;
100+
101+
snprintf(pattern, sizeof(pattern), "%s/lirc*/uevent", rcdir);
102+
if (!find_devname(pattern, lirc_path, path_sz))
103+
continue;
104+
105+
snprintf(pattern, sizeof(pattern), "%s/input*/event*/uevent", rcdir);
106+
if (!find_devname(pattern, input_path, path_sz))
107+
continue;
108+
109+
found = true;
110+
break;
111+
}
112+
113+
out:
114+
if (!found) {
115+
fprintf(stderr, "No rc devices found\n");
116+
fprintf(stderr, "Enable CONFIG_RC_LOOPBACK and CONFIG_BPF_LIRC_MODE2\n");
117+
}
118+
119+
globfree(&gl);
120+
return found;
121+
}
122+
123+
void test_lirc_mode2(void)
124+
{
125+
char lirc_path[PATH_MAX], input_path[PATH_MAX];
126+
int lircfd = -1, inputfd = -1, progfd, progfd2 = -1;
127+
struct lirc_mode2 *skel = NULL, *skel2 = NULL;
128+
__u32 prog_ids[10], prog_flags[10], prog_cnt;
129+
struct bpf_prog_info info;
130+
__u32 info_len, prog_id, prog_id2;
131+
int testir1 = 0x8ead; /* keydown flag (0x8000) | scancode 0xead */
132+
int testir2 = 0x4081; /* pointer_rel flag (0x4000) | rel_x=1 | rel_y=1 */
133+
struct input_event event;
134+
struct pollfd pfd = {};
135+
int ret;
136+
137+
if (getuid() != 0) {
138+
test__skip();
139+
return;
140+
}
141+
142+
if (!find_loopback_devices(lirc_path, input_path, sizeof(lirc_path))) {
143+
test__skip();
144+
return;
145+
}
146+
147+
skel = lirc_mode2__open_and_load();
148+
if (!ASSERT_OK_PTR(skel, "lirc_mode2__open_and_load"))
149+
return;
150+
151+
progfd = bpf_program__fd(skel->progs.bpf_decoder);
152+
153+
memset(&info, 0, sizeof(info));
154+
info_len = sizeof(info);
155+
ret = bpf_prog_get_info_by_fd(progfd, &info, &info_len);
156+
if (!ASSERT_OK(ret, "get first program's info"))
157+
goto out;
158+
prog_id = info.id;
159+
160+
lircfd = open(lirc_path, O_RDWR | O_NONBLOCK);
161+
if (!ASSERT_GE(lircfd, 0, "open lirc device"))
162+
goto out;
163+
164+
/* Try to detach it before it was ever attached, should fail. */
165+
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
166+
if (!ASSERT_EQ(ret, -ENOENT, "detach unattached program"))
167+
goto out;
168+
169+
inputfd = open(input_path, O_RDONLY | O_NONBLOCK);
170+
if (!ASSERT_GE(inputfd, 0, "open input device"))
171+
goto out;
172+
173+
prog_cnt = ARRAY_SIZE(prog_ids);
174+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
175+
&prog_cnt);
176+
if (!ASSERT_OK(ret, "query programs before attach"))
177+
goto out;
178+
if (!ASSERT_EQ(prog_cnt, 0, "no programs should be attached yet"))
179+
goto out;
180+
181+
/* Invalid attach flags must be rejected, and must not attach. */
182+
ret = bpf_prog_attach(progfd, lircfd, BPF_LIRC_MODE2, 1);
183+
if (!ASSERT_EQ(ret, -EINVAL, "attach with invalid flags"))
184+
goto out;
185+
186+
prog_cnt = ARRAY_SIZE(prog_ids);
187+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
188+
&prog_cnt);
189+
if (!ASSERT_OK(ret, "query programs after rejected attach"))
190+
goto out;
191+
if (!ASSERT_EQ(prog_cnt, 0, "rejected attach should not attach"))
192+
goto out;
193+
194+
ret = bpf_prog_attach(progfd, lircfd, BPF_LIRC_MODE2, 0);
195+
if (!ASSERT_OK(ret, "attach program to lirc device"))
196+
goto out;
197+
198+
/* Invalid query flags must be rejected too, without upsetting state. */
199+
prog_cnt = ARRAY_SIZE(prog_ids);
200+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 1, prog_flags, prog_ids,
201+
&prog_cnt);
202+
ASSERT_EQ(ret, -EINVAL, "query with invalid flags");
203+
204+
prog_cnt = ARRAY_SIZE(prog_ids);
205+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
206+
&prog_cnt);
207+
if (!ASSERT_OK(ret, "query programs after attach"))
208+
goto out_detach;
209+
if (!ASSERT_EQ(prog_cnt, 1, "one program should be attached"))
210+
goto out_detach;
211+
ASSERT_EQ(prog_ids[0], prog_id, "queried id should match attached program");
212+
213+
/* Write raw IR */
214+
ret = write(lircfd, &testir1, sizeof(testir1));
215+
if (!ASSERT_EQ(ret, sizeof(testir1), "send test IR message 1"))
216+
goto out_detach;
217+
218+
pfd.fd = inputfd;
219+
pfd.events = POLLIN;
220+
221+
for (;;) {
222+
poll(&pfd, 1, 100);
223+
224+
/* Read decoded IR */
225+
ret = read(inputfd, &event, sizeof(event));
226+
if (!ASSERT_EQ(ret, sizeof(event), "read decoded IR 1"))
227+
goto out_detach;
228+
229+
if (event.type == EV_MSC && event.code == MSC_SCAN &&
230+
event.value == 0xead)
231+
break;
232+
}
233+
234+
/* Write raw IR */
235+
ret = write(lircfd, &testir2, sizeof(testir2));
236+
if (!ASSERT_EQ(ret, sizeof(testir2), "send test IR message 2"))
237+
goto out_detach;
238+
239+
for (;;) {
240+
poll(&pfd, 1, 100);
241+
242+
/* Read decoded IR */
243+
ret = read(inputfd, &event, sizeof(event));
244+
if (!ASSERT_EQ(ret, sizeof(event), "read decoded IR 2"))
245+
goto out_detach;
246+
247+
if (event.type == EV_REL && event.code == REL_Y &&
248+
event.value == 1)
249+
break;
250+
}
251+
252+
prog_cnt = ARRAY_SIZE(prog_ids);
253+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
254+
&prog_cnt);
255+
if (!ASSERT_OK(ret, "query programs after IR was decoded"))
256+
goto out_detach;
257+
if (!ASSERT_EQ(prog_cnt, 1, "one program should still be attached"))
258+
goto out_detach;
259+
260+
/*
261+
* The lirc chardev can hold more than one attached program at once.
262+
* Load a second, independent instance and check it can be attached
263+
* alongside the first, queried, and then detached on its own
264+
* without disturbing the first program's attachment.
265+
*/
266+
skel2 = lirc_mode2__open_and_load();
267+
if (!ASSERT_OK_PTR(skel2, "lirc_mode2__open_and_load (2nd)"))
268+
goto out_detach;
269+
270+
progfd2 = bpf_program__fd(skel2->progs.bpf_decoder);
271+
272+
memset(&info, 0, sizeof(info));
273+
info_len = sizeof(info);
274+
ret = bpf_prog_get_info_by_fd(progfd2, &info, &info_len);
275+
if (!ASSERT_OK(ret, "get second program's info"))
276+
goto out_detach;
277+
prog_id2 = info.id;
278+
279+
ret = bpf_prog_attach(progfd2, lircfd, BPF_LIRC_MODE2, 0);
280+
if (!ASSERT_OK(ret, "attach second program to lirc device"))
281+
goto out_detach;
282+
283+
prog_cnt = ARRAY_SIZE(prog_ids);
284+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
285+
&prog_cnt);
286+
if (!ASSERT_OK(ret, "query programs after second attach"))
287+
goto out_detach2;
288+
if (!ASSERT_EQ(prog_cnt, 2, "two programs should be attached"))
289+
goto out_detach2;
290+
ASSERT_TRUE((prog_ids[0] == prog_id && prog_ids[1] == prog_id2) ||
291+
(prog_ids[0] == prog_id2 && prog_ids[1] == prog_id),
292+
"queried ids should be the two attached programs");
293+
294+
/* Detach the second program; the first should remain attached. */
295+
ret = bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
296+
if (!ASSERT_OK(ret, "detach second program"))
297+
goto out_detach2;
298+
299+
/* Detaching an already-detached program should now fail. */
300+
ret = bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
301+
ASSERT_EQ(ret, -ENOENT, "detach second program again");
302+
303+
prog_cnt = ARRAY_SIZE(prog_ids);
304+
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
305+
&prog_cnt);
306+
if (!ASSERT_OK(ret, "query programs after second detach"))
307+
goto out_detach;
308+
if (!ASSERT_EQ(prog_cnt, 1, "one program should remain attached"))
309+
goto out_detach;
310+
ASSERT_EQ(prog_ids[0], prog_id, "remaining program should be the first one");
311+
312+
out_detach:
313+
/* Let's try detaching it now it is actually attached. */
314+
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
315+
ASSERT_OK(ret, "detach program from lirc device");
316+
317+
/* Detaching it again should now fail the same way. */
318+
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
319+
ASSERT_EQ(ret, -ENOENT, "detach program from lirc device again");
320+
goto out;
321+
322+
out_detach2:
323+
/* Best-effort cleanup of the second program before bailing out. */
324+
bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
325+
goto out_detach;
326+
327+
out:
328+
if (inputfd >= 0)
329+
close(inputfd);
330+
if (lircfd >= 0)
331+
close(lircfd);
332+
lirc_mode2__destroy(skel2);
333+
lirc_mode2__destroy(skel);
334+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
// test ir decoder
3+
//
4+
// Copyright (C) 2018 Sean Young <sean@mess.org>
5+
6+
#include <linux/bpf.h>
7+
#include <linux/lirc.h>
8+
#include <bpf/bpf_helpers.h>
9+
10+
SEC("lirc_mode2")
11+
int bpf_decoder(unsigned int *sample)
12+
{
13+
if (LIRC_IS_PULSE(*sample)) {
14+
unsigned int duration = LIRC_VALUE(*sample);
15+
16+
/*
17+
* Flag bits picked deliberately low: rc-loopback simulates
18+
* a receiver overflow for any pulse over MS_TO_US(50) (see
19+
* loop_tx_ir() in rc-loopback.c), which would silently
20+
* swallow the sample before it ever reaches this decoder.
21+
*/
22+
if (duration & 0x8000)
23+
bpf_rc_keydown(sample, 0x40, duration & 0x3fff, 0);
24+
if (duration & 0x4000)
25+
bpf_rc_pointer_rel(sample, (duration >> 7) & 0x7f,
26+
duration & 0x7f);
27+
}
28+
29+
return 0;
30+
}
31+
32+
char _license[] SEC("license") = "GPL";

0 commit comments

Comments
 (0)