mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
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 Link: https://lore.kernel.org/bpf/20260820214332.89030-1-sean@mess.org Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
parent
fbc547151d
commit
d761934c94
1
tools/testing/selftests/bpf/.gitignore
vendored
1
tools/testing/selftests/bpf/.gitignore
vendored
|
|
@ -17,7 +17,6 @@ test_verifier_log
|
|||
feature
|
||||
urandom_read
|
||||
test_sockmap
|
||||
test_lirc_mode2_user
|
||||
flow_dissector_load
|
||||
test_tcpnotify_user
|
||||
test_libbpf
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ TEST_FILES = xsk_prereqs.sh $(wildcard progs/btf_dump_test_case_*.c) \
|
|||
|
||||
# Order correspond to 'make run_tests' order
|
||||
TEST_PROGS := test_kmod.sh \
|
||||
test_lirc_mode2.sh \
|
||||
test_bpftool_build.sh \
|
||||
test_doc_build.sh \
|
||||
test_xsk.sh \
|
||||
|
|
@ -143,7 +142,6 @@ TEST_GEN_PROGS_EXTENDED = \
|
|||
bench \
|
||||
flow_dissector_load \
|
||||
test_cpp \
|
||||
test_lirc_mode2_user \
|
||||
veristat \
|
||||
xdp_features \
|
||||
xdp_hw_metadata \
|
||||
|
|
@ -340,7 +338,6 @@ $(OUTPUT)/test_sockmap: $(CGROUP_HELPERS) $(TESTING_HELPERS)
|
|||
$(OUTPUT)/test_tcpnotify_user: $(CGROUP_HELPERS) $(TESTING_HELPERS) $(TRACE_HELPERS)
|
||||
$(OUTPUT)/test_sock_fields: $(CGROUP_HELPERS) $(TESTING_HELPERS)
|
||||
$(OUTPUT)/test_tag: $(TESTING_HELPERS)
|
||||
$(OUTPUT)/test_lirc_mode2_user: $(TESTING_HELPERS)
|
||||
$(OUTPUT)/flow_dissector_load: $(TESTING_HELPERS)
|
||||
$(OUTPUT)/test_maps: $(TESTING_HELPERS)
|
||||
$(OUTPUT)/test_verifier: $(TESTING_HELPERS) $(CAP_HELPERS) $(UNPRIV_HELPERS)
|
||||
|
|
|
|||
334
tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
Normal file
334
tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
Normal file
|
|
@ -0,0 +1,334 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// test ir decoder
|
||||
//
|
||||
// Copyright (C) 2018 Sean Young <sean@mess.org>
|
||||
|
||||
// A lirc chardev is a device representing a consumer IR (cir) device which
|
||||
// can receive infrared signals from remote control and/or transmit IR.
|
||||
//
|
||||
// IR is sent as a series of pulses and space somewhat like morse code. The
|
||||
// BPF program can decode this into scancodes so that rc-core can translate
|
||||
// this into input key codes using the rc keymap.
|
||||
//
|
||||
// This test works by sending IR over rc-loopback, so the IR is processed by
|
||||
// BPF and then decoded into scancodes. The lirc chardev must be the one
|
||||
// associated with rc-loopback, see the output of ir-keytable(1).
|
||||
//
|
||||
// The following CONFIG options must be enabled for the test to succeed:
|
||||
// CONFIG_RC_CORE=y
|
||||
// CONFIG_BPF_LIRC_MODE2=y
|
||||
// CONFIG_RC_LOOPBACK=y
|
||||
// CONFIG_LIRC=y
|
||||
|
||||
#include <linux/input.h>
|
||||
#include <linux/lirc.h>
|
||||
#include <glob.h>
|
||||
#include <limits.h>
|
||||
#include <poll.h>
|
||||
#include <test_progs.h>
|
||||
#include "lirc_mode2.skel.h"
|
||||
|
||||
/*
|
||||
* Read the DEVNAME= line out of the first uevent file that matches
|
||||
* pattern, and turn it into a /dev/<name> path.
|
||||
*/
|
||||
static bool find_devname(const char *pattern, char *path, size_t path_sz)
|
||||
{
|
||||
glob_t gl = {};
|
||||
bool found = false;
|
||||
FILE *f;
|
||||
|
||||
if (glob(pattern, 0, NULL, &gl) || gl.gl_pathc == 0)
|
||||
goto out;
|
||||
|
||||
f = fopen(gl.gl_pathv[0], "r");
|
||||
if (!f)
|
||||
goto out;
|
||||
|
||||
char line[256];
|
||||
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
char *val;
|
||||
|
||||
if (strncmp(line, "DEVNAME=", 8))
|
||||
continue;
|
||||
|
||||
val = line + 8;
|
||||
val[strcspn(val, "\n")] = '\0';
|
||||
snprintf(path, path_sz, "/dev/%s", val);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
out:
|
||||
globfree(&gl);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* Load rc-loopback and find the lirc and input chardevs it created. */
|
||||
static bool find_loopback_devices(char *lirc_path, char *input_path,
|
||||
size_t path_sz)
|
||||
{
|
||||
glob_t gl = {};
|
||||
bool found = false;
|
||||
|
||||
/* Ignore failure, we check for the resulting devices below. */
|
||||
SYS_NOFAIL("modprobe rc-loopback > /dev/null 2>&1");
|
||||
|
||||
if (glob("/sys/class/rc/rc*", 0, NULL, &gl))
|
||||
goto out;
|
||||
|
||||
for (size_t i = 0; i < gl.gl_pathc; i++) {
|
||||
const char *rcdir = gl.gl_pathv[i];
|
||||
char uevent_path[PATH_MAX];
|
||||
char uevent[4096];
|
||||
char pattern[PATH_MAX];
|
||||
FILE *f;
|
||||
size_t n;
|
||||
|
||||
snprintf(uevent_path, sizeof(uevent_path), "%s/uevent", rcdir);
|
||||
f = fopen(uevent_path, "r");
|
||||
if (!f)
|
||||
continue;
|
||||
n = fread(uevent, 1, sizeof(uevent) - 1, f);
|
||||
fclose(f);
|
||||
uevent[n] = '\0';
|
||||
|
||||
if (!strstr(uevent, "DRV_NAME=rc-loopback"))
|
||||
continue;
|
||||
|
||||
snprintf(pattern, sizeof(pattern), "%s/lirc*/uevent", rcdir);
|
||||
if (!find_devname(pattern, lirc_path, path_sz))
|
||||
continue;
|
||||
|
||||
snprintf(pattern, sizeof(pattern), "%s/input*/event*/uevent", rcdir);
|
||||
if (!find_devname(pattern, input_path, path_sz))
|
||||
continue;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
|
||||
out:
|
||||
if (!found) {
|
||||
fprintf(stderr, "No rc devices found\n");
|
||||
fprintf(stderr, "Enable CONFIG_RC_LOOPBACK and CONFIG_BPF_LIRC_MODE2\n");
|
||||
}
|
||||
|
||||
globfree(&gl);
|
||||
return found;
|
||||
}
|
||||
|
||||
void test_lirc_mode2(void)
|
||||
{
|
||||
char lirc_path[PATH_MAX], input_path[PATH_MAX];
|
||||
int lircfd = -1, inputfd = -1, progfd, progfd2 = -1;
|
||||
struct lirc_mode2 *skel = NULL, *skel2 = NULL;
|
||||
__u32 prog_ids[10], prog_flags[10], prog_cnt;
|
||||
struct bpf_prog_info info;
|
||||
__u32 info_len, prog_id, prog_id2;
|
||||
int testir1 = 0x8ead; /* keydown flag (0x8000) | scancode 0xead */
|
||||
int testir2 = 0x4081; /* pointer_rel flag (0x4000) | rel_x=1 | rel_y=1 */
|
||||
struct input_event event;
|
||||
struct pollfd pfd = {};
|
||||
int ret;
|
||||
|
||||
if (getuid() != 0) {
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!find_loopback_devices(lirc_path, input_path, sizeof(lirc_path))) {
|
||||
test__skip();
|
||||
return;
|
||||
}
|
||||
|
||||
skel = lirc_mode2__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel, "lirc_mode2__open_and_load"))
|
||||
return;
|
||||
|
||||
progfd = bpf_program__fd(skel->progs.bpf_decoder);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info_len = sizeof(info);
|
||||
ret = bpf_prog_get_info_by_fd(progfd, &info, &info_len);
|
||||
if (!ASSERT_OK(ret, "get first program's info"))
|
||||
goto out;
|
||||
prog_id = info.id;
|
||||
|
||||
lircfd = open(lirc_path, O_RDWR | O_NONBLOCK);
|
||||
if (!ASSERT_GE(lircfd, 0, "open lirc device"))
|
||||
goto out;
|
||||
|
||||
/* Try to detach it before it was ever attached, should fail. */
|
||||
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
|
||||
if (!ASSERT_EQ(ret, -ENOENT, "detach unattached program"))
|
||||
goto out;
|
||||
|
||||
inputfd = open(input_path, O_RDONLY | O_NONBLOCK);
|
||||
if (!ASSERT_GE(inputfd, 0, "open input device"))
|
||||
goto out;
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs before attach"))
|
||||
goto out;
|
||||
if (!ASSERT_EQ(prog_cnt, 0, "no programs should be attached yet"))
|
||||
goto out;
|
||||
|
||||
/* Invalid attach flags must be rejected, and must not attach. */
|
||||
ret = bpf_prog_attach(progfd, lircfd, BPF_LIRC_MODE2, 1);
|
||||
if (!ASSERT_EQ(ret, -EINVAL, "attach with invalid flags"))
|
||||
goto out;
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs after rejected attach"))
|
||||
goto out;
|
||||
if (!ASSERT_EQ(prog_cnt, 0, "rejected attach should not attach"))
|
||||
goto out;
|
||||
|
||||
ret = bpf_prog_attach(progfd, lircfd, BPF_LIRC_MODE2, 0);
|
||||
if (!ASSERT_OK(ret, "attach program to lirc device"))
|
||||
goto out;
|
||||
|
||||
/* Invalid query flags must be rejected too, without upsetting state. */
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 1, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
ASSERT_EQ(ret, -EINVAL, "query with invalid flags");
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs after attach"))
|
||||
goto out_detach;
|
||||
if (!ASSERT_EQ(prog_cnt, 1, "one program should be attached"))
|
||||
goto out_detach;
|
||||
ASSERT_EQ(prog_ids[0], prog_id, "queried id should match attached program");
|
||||
|
||||
/* Write raw IR */
|
||||
ret = write(lircfd, &testir1, sizeof(testir1));
|
||||
if (!ASSERT_EQ(ret, sizeof(testir1), "send test IR message 1"))
|
||||
goto out_detach;
|
||||
|
||||
pfd.fd = inputfd;
|
||||
pfd.events = POLLIN;
|
||||
|
||||
for (;;) {
|
||||
poll(&pfd, 1, 100);
|
||||
|
||||
/* Read decoded IR */
|
||||
ret = read(inputfd, &event, sizeof(event));
|
||||
if (!ASSERT_EQ(ret, sizeof(event), "read decoded IR 1"))
|
||||
goto out_detach;
|
||||
|
||||
if (event.type == EV_MSC && event.code == MSC_SCAN &&
|
||||
event.value == 0xead)
|
||||
break;
|
||||
}
|
||||
|
||||
/* Write raw IR */
|
||||
ret = write(lircfd, &testir2, sizeof(testir2));
|
||||
if (!ASSERT_EQ(ret, sizeof(testir2), "send test IR message 2"))
|
||||
goto out_detach;
|
||||
|
||||
for (;;) {
|
||||
poll(&pfd, 1, 100);
|
||||
|
||||
/* Read decoded IR */
|
||||
ret = read(inputfd, &event, sizeof(event));
|
||||
if (!ASSERT_EQ(ret, sizeof(event), "read decoded IR 2"))
|
||||
goto out_detach;
|
||||
|
||||
if (event.type == EV_REL && event.code == REL_Y &&
|
||||
event.value == 1)
|
||||
break;
|
||||
}
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs after IR was decoded"))
|
||||
goto out_detach;
|
||||
if (!ASSERT_EQ(prog_cnt, 1, "one program should still be attached"))
|
||||
goto out_detach;
|
||||
|
||||
/*
|
||||
* The lirc chardev can hold more than one attached program at once.
|
||||
* Load a second, independent instance and check it can be attached
|
||||
* alongside the first, queried, and then detached on its own
|
||||
* without disturbing the first program's attachment.
|
||||
*/
|
||||
skel2 = lirc_mode2__open_and_load();
|
||||
if (!ASSERT_OK_PTR(skel2, "lirc_mode2__open_and_load (2nd)"))
|
||||
goto out_detach;
|
||||
|
||||
progfd2 = bpf_program__fd(skel2->progs.bpf_decoder);
|
||||
|
||||
memset(&info, 0, sizeof(info));
|
||||
info_len = sizeof(info);
|
||||
ret = bpf_prog_get_info_by_fd(progfd2, &info, &info_len);
|
||||
if (!ASSERT_OK(ret, "get second program's info"))
|
||||
goto out_detach;
|
||||
prog_id2 = info.id;
|
||||
|
||||
ret = bpf_prog_attach(progfd2, lircfd, BPF_LIRC_MODE2, 0);
|
||||
if (!ASSERT_OK(ret, "attach second program to lirc device"))
|
||||
goto out_detach;
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs after second attach"))
|
||||
goto out_detach2;
|
||||
if (!ASSERT_EQ(prog_cnt, 2, "two programs should be attached"))
|
||||
goto out_detach2;
|
||||
ASSERT_TRUE((prog_ids[0] == prog_id && prog_ids[1] == prog_id2) ||
|
||||
(prog_ids[0] == prog_id2 && prog_ids[1] == prog_id),
|
||||
"queried ids should be the two attached programs");
|
||||
|
||||
/* Detach the second program; the first should remain attached. */
|
||||
ret = bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
|
||||
if (!ASSERT_OK(ret, "detach second program"))
|
||||
goto out_detach2;
|
||||
|
||||
/* Detaching an already-detached program should now fail. */
|
||||
ret = bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
|
||||
ASSERT_EQ(ret, -ENOENT, "detach second program again");
|
||||
|
||||
prog_cnt = ARRAY_SIZE(prog_ids);
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (!ASSERT_OK(ret, "query programs after second detach"))
|
||||
goto out_detach;
|
||||
if (!ASSERT_EQ(prog_cnt, 1, "one program should remain attached"))
|
||||
goto out_detach;
|
||||
ASSERT_EQ(prog_ids[0], prog_id, "remaining program should be the first one");
|
||||
|
||||
out_detach:
|
||||
/* Let's try detaching it now it is actually attached. */
|
||||
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
|
||||
ASSERT_OK(ret, "detach program from lirc device");
|
||||
|
||||
/* Detaching it again should now fail the same way. */
|
||||
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
|
||||
ASSERT_EQ(ret, -ENOENT, "detach program from lirc device again");
|
||||
goto out;
|
||||
|
||||
out_detach2:
|
||||
/* Best-effort cleanup of the second program before bailing out. */
|
||||
bpf_prog_detach2(progfd2, lircfd, BPF_LIRC_MODE2);
|
||||
goto out_detach;
|
||||
|
||||
out:
|
||||
if (inputfd >= 0)
|
||||
close(inputfd);
|
||||
if (lircfd >= 0)
|
||||
close(lircfd);
|
||||
lirc_mode2__destroy(skel2);
|
||||
lirc_mode2__destroy(skel);
|
||||
}
|
||||
32
tools/testing/selftests/bpf/progs/lirc_mode2.c
Normal file
32
tools/testing/selftests/bpf/progs/lirc_mode2.c
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// test ir decoder
|
||||
//
|
||||
// Copyright (C) 2018 Sean Young <sean@mess.org>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/lirc.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
SEC("lirc_mode2")
|
||||
int bpf_decoder(unsigned int *sample)
|
||||
{
|
||||
if (LIRC_IS_PULSE(*sample)) {
|
||||
unsigned int duration = LIRC_VALUE(*sample);
|
||||
|
||||
/*
|
||||
* Flag bits picked deliberately low: rc-loopback simulates
|
||||
* a receiver overflow for any pulse over MS_TO_US(50) (see
|
||||
* loop_tx_ir() in rc-loopback.c), which would silently
|
||||
* swallow the sample before it ever reaches this decoder.
|
||||
*/
|
||||
if (duration & 0x8000)
|
||||
bpf_rc_keydown(sample, 0x40, duration & 0x3fff, 0);
|
||||
if (duration & 0x4000)
|
||||
bpf_rc_pointer_rel(sample, (duration >> 7) & 0x7f,
|
||||
duration & 0x7f);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
@ -1,26 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// test ir decoder
|
||||
//
|
||||
// Copyright (C) 2018 Sean Young <sean@mess.org>
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/lirc.h>
|
||||
#include <bpf/bpf_helpers.h>
|
||||
|
||||
SEC("lirc_mode2")
|
||||
int bpf_decoder(unsigned int *sample)
|
||||
{
|
||||
if (LIRC_IS_PULSE(*sample)) {
|
||||
unsigned int duration = LIRC_VALUE(*sample);
|
||||
|
||||
if (duration & 0x1000)
|
||||
bpf_rc_keydown(sample, 0x40, duration & 0xffff, 0);
|
||||
if (duration & 0x2000)
|
||||
bpf_rc_pointer_rel(sample, (duration >> 8) & 0xff,
|
||||
duration & 0xff);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char _license[] SEC("license") = "GPL";
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# Kselftest framework requirement - SKIP code is 4.
|
||||
ksft_skip=4
|
||||
ret=$ksft_skip
|
||||
|
||||
msg="skip all tests:"
|
||||
if [ $UID != 0 ]; then
|
||||
echo $msg please run this as root >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
GREEN='\033[0;92m'
|
||||
RED='\033[0;31m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
modprobe rc-loopback
|
||||
|
||||
for i in /sys/class/rc/rc*
|
||||
do
|
||||
if grep -q DRV_NAME=rc-loopback $i/uevent
|
||||
then
|
||||
LIRCDEV=$(grep DEVNAME= $i/lirc*/uevent | sed sQDEVNAME=Q/dev/Q)
|
||||
INPUTDEV=$(grep DEVNAME= $i/input*/event*/uevent | sed sQDEVNAME=Q/dev/Q)
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -n "$LIRCDEV" ];
|
||||
then
|
||||
TYPE=lirc_mode2
|
||||
./test_lirc_mode2_user $LIRCDEV $INPUTDEV
|
||||
ret=$?
|
||||
if [ $ret -ne 0 ]; then
|
||||
echo -e ${RED}"FAIL: $TYPE"${NC}
|
||||
else
|
||||
echo -e ${GREEN}"PASS: $TYPE"${NC}
|
||||
fi
|
||||
fi
|
||||
|
||||
exit $ret
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
// test ir decoder
|
||||
//
|
||||
// Copyright (C) 2018 Sean Young <sean@mess.org>
|
||||
|
||||
// A lirc chardev is a device representing a consumer IR (cir) device which
|
||||
// can receive infrared signals from remote control and/or transmit IR.
|
||||
//
|
||||
// IR is sent as a series of pulses and space somewhat like morse code. The
|
||||
// BPF program can decode this into scancodes so that rc-core can translate
|
||||
// this into input key codes using the rc keymap.
|
||||
//
|
||||
// This test works by sending IR over rc-loopback, so the IR is processed by
|
||||
// BPF and then decoded into scancodes. The lirc chardev must be the one
|
||||
// associated with rc-loopback, see the output of ir-keytable(1).
|
||||
//
|
||||
// The following CONFIG options must be enabled for the test to succeed:
|
||||
// CONFIG_RC_CORE=y
|
||||
// CONFIG_BPF_RAWIR_EVENT=y
|
||||
// CONFIG_RC_LOOPBACK=y
|
||||
|
||||
// Steps:
|
||||
// 1. Open the /dev/lircN device for rc-loopback (given on command line)
|
||||
// 2. Attach bpf_lirc_mode2 program which decodes some IR.
|
||||
// 3. Send some IR to the same IR device; since it is loopback, this will
|
||||
// end up in the bpf program
|
||||
// 4. bpf program should decode IR and report keycode
|
||||
// 5. We can read keycode from same /dev/lirc device
|
||||
|
||||
#include <linux/bpf.h>
|
||||
#include <linux/input.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <poll.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "bpf_util.h"
|
||||
#include <bpf/bpf.h>
|
||||
#include <bpf/libbpf.h>
|
||||
|
||||
#include "testing_helpers.h"
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
struct bpf_object *obj;
|
||||
int ret, lircfd, progfd, inputfd;
|
||||
int testir1 = 0x1ead;
|
||||
int testir2 = 0x2101;
|
||||
u32 prog_ids[10], prog_flags[10], prog_cnt;
|
||||
|
||||
if (argc != 3) {
|
||||
printf("Usage: %s /dev/lircN /dev/input/eventM\n", argv[0]);
|
||||
return 2;
|
||||
}
|
||||
|
||||
ret = bpf_prog_test_load("test_lirc_mode2_kern.bpf.o",
|
||||
BPF_PROG_TYPE_LIRC_MODE2, &obj, &progfd);
|
||||
if (ret) {
|
||||
printf("Failed to load bpf program\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
lircfd = open(argv[1], O_RDWR | O_NONBLOCK);
|
||||
if (lircfd == -1) {
|
||||
printf("failed to open lirc device %s: %m\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Let's try detach it before it was ever attached */
|
||||
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
|
||||
if (ret != -ENOENT) {
|
||||
printf("bpf_prog_detach2 not attached should fail: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
inputfd = open(argv[2], O_RDONLY | O_NONBLOCK);
|
||||
if (inputfd == -1) {
|
||||
printf("failed to open input device %s: %m\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
prog_cnt = 10;
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (ret) {
|
||||
printf("Failed to query bpf programs on lirc device: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (prog_cnt != 0) {
|
||||
printf("Expected nothing to be attached\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
ret = bpf_prog_attach(progfd, lircfd, BPF_LIRC_MODE2, 0);
|
||||
if (ret) {
|
||||
printf("Failed to attach bpf to lirc device: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Write raw IR */
|
||||
ret = write(lircfd, &testir1, sizeof(testir1));
|
||||
if (ret != sizeof(testir1)) {
|
||||
printf("Failed to send test IR message: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct pollfd pfd = { .fd = inputfd, .events = POLLIN };
|
||||
struct input_event event;
|
||||
|
||||
for (;;) {
|
||||
poll(&pfd, 1, 100);
|
||||
|
||||
/* Read decoded IR */
|
||||
ret = read(inputfd, &event, sizeof(event));
|
||||
if (ret != sizeof(event)) {
|
||||
printf("Failed to read decoded IR: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (event.type == EV_MSC && event.code == MSC_SCAN &&
|
||||
event.value == 0x1ead) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Write raw IR */
|
||||
ret = write(lircfd, &testir2, sizeof(testir2));
|
||||
if (ret != sizeof(testir2)) {
|
||||
printf("Failed to send test IR message: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
poll(&pfd, 1, 100);
|
||||
|
||||
/* Read decoded IR */
|
||||
ret = read(inputfd, &event, sizeof(event));
|
||||
if (ret != sizeof(event)) {
|
||||
printf("Failed to read decoded IR: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (event.type == EV_REL && event.code == REL_Y &&
|
||||
event.value == 1 ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
prog_cnt = 10;
|
||||
ret = bpf_prog_query(lircfd, BPF_LIRC_MODE2, 0, prog_flags, prog_ids,
|
||||
&prog_cnt);
|
||||
if (ret) {
|
||||
printf("Failed to query bpf programs on lirc device: %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (prog_cnt != 1) {
|
||||
printf("Expected one program to be attached\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Let's try detaching it now it is actually attached */
|
||||
ret = bpf_prog_detach2(progfd, lircfd, BPF_LIRC_MODE2);
|
||||
if (ret) {
|
||||
printf("bpf_prog_detach2: returned %m\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user