* [PATCH v2] selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage
@ 2026-08-20 21:01 Sean Young
2026-08-20 21:11 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Sean Young @ 2026-08-20 21:01 UTC (permalink / raw)
To: linux-kernel, Andrii Nakryiko, Eduard Zingerman, Ihor Solodrai,
Alexei Starovoitov, Daniel Borkmann, Kumar Kartikeya Dwivedi,
Martin KaFai Lau, Song Liu, Yonghong Song, Jiri Olsa,
Emil Tsalapatis, Shuah Khan
Cc: Sean Young, bpf, linux-kselftest
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
---
Since v1:
- Remove obsolete test_lirc_mode2_user.c file
- Use SYS_NOFAIL() rather than system()
- Fix comment formatting
- Fix leaks in error paths
tools/testing/selftests/bpf/.gitignore | 1 -
tools/testing/selftests/bpf/Makefile | 3 -
.../selftests/bpf/prog_tests/lirc_mode2.c | 334 ++++++++++++++++++
.../testing/selftests/bpf/progs/lirc_mode2.c | 32 ++
.../bpf/progs/test_lirc_mode2_kern.c | 26 --
.../testing/selftests/bpf/test_lirc_mode2.sh | 41 ---
.../selftests/bpf/test_lirc_mode2_user.c | 177 ----------
7 files changed, 366 insertions(+), 248 deletions(-)
create mode 100644 tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
create mode 100644 tools/testing/selftests/bpf/progs/lirc_mode2.c
delete mode 100644 tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
delete mode 100755 tools/testing/selftests/bpf/test_lirc_mode2.sh
delete mode 100644 tools/testing/selftests/bpf/test_lirc_mode2_user.c
diff --git a/tools/testing/selftests/bpf/.gitignore b/tools/testing/selftests/bpf/.gitignore
index 986a6389186b..b815bf0d8877 100644
--- a/tools/testing/selftests/bpf/.gitignore
+++ b/tools/testing/selftests/bpf/.gitignore
@@ -17,7 +17,6 @@ test_verifier_log
feature
urandom_read
test_sockmap
-test_lirc_mode2_user
flow_dissector_load
test_tcpnotify_user
test_libbpf
diff --git a/tools/testing/selftests/bpf/Makefile b/tools/testing/selftests/bpf/Makefile
index 2b2f93dec474..f8ec0f574559 100644
--- a/tools/testing/selftests/bpf/Makefile
+++ b/tools/testing/selftests/bpf/Makefile
@@ -123,7 +123,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 \
@@ -141,7 +140,6 @@ TEST_GEN_PROGS_EXTENDED = \
bench \
flow_dissector_load \
test_cpp \
- test_lirc_mode2_user \
veristat \
xdp_features \
xdp_hw_metadata \
@@ -338,7 +336,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)
diff --git a/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c b/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
new file mode 100644
index 000000000000..8e485db70b78
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
@@ -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_RAWIR_EVENT=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, CONFIG_RC_LOOPBACK, and CONFIG_LIRC\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);
+}
diff --git a/tools/testing/selftests/bpf/progs/lirc_mode2.c b/tools/testing/selftests/bpf/progs/lirc_mode2.c
new file mode 100644
index 000000000000..98137f3c5c03
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/lirc_mode2.c
@@ -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";
diff --git a/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c b/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
deleted file mode 100644
index cbe4284c032f..000000000000
--- a/tools/testing/selftests/bpf/progs/test_lirc_mode2_kern.c
+++ /dev/null
@@ -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";
diff --git a/tools/testing/selftests/bpf/test_lirc_mode2.sh b/tools/testing/selftests/bpf/test_lirc_mode2.sh
deleted file mode 100755
index 5252b91f48a1..000000000000
--- a/tools/testing/selftests/bpf/test_lirc_mode2.sh
+++ /dev/null
@@ -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
diff --git a/tools/testing/selftests/bpf/test_lirc_mode2_user.c b/tools/testing/selftests/bpf/test_lirc_mode2_user.c
deleted file mode 100644
index cd191da20d14..000000000000
--- a/tools/testing/selftests/bpf/test_lirc_mode2_user.c
+++ /dev/null
@@ -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;
-}
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH v2] selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage
2026-08-20 21:01 [PATCH v2] selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage Sean Young
@ 2026-08-20 21:11 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-08-20 21:11 UTC (permalink / raw)
To: Sean Young; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Low] A stray `?` character before a closing brace causes a compilation error.
- [Low] Multi-line block comments use C++ style `//` instead of the required kernel `/* ... */` style.
- [Low] The printed error message for missing rc devices incorrectly repeats `CONFIG_RC_LOOPBACK`.
--
--- Patch [1]: [PATCH v2] selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage ---
commit b838818703c3bdf7a467c66079361ca289b5a98e
Author: Sean Young <sean@mess.org>
selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage
This patch ports the lirc_mode2 test to the prog_tests framework and extends
its coverage. It tests attaching, querying, and detaching BPF programs to
and from a lirc chardev.
> diff --git a/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c b/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
> new file mode 100644
> index 0000000000000..8e485db70b78a
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/lirc_mode2.c
> @@ -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_RAWIR_EVENT=y
> +// CONFIG_RC_LOOPBACK=y
> +// CONFIG_LIRC=y
[Severity: Low]
Is it possible to use standard C-style multi-line comments (/* ... */) here
instead of C++ style comments? The BPF subsystem guidelines prefer the standard
kernel multi-line comment style.
[ ... ]
> +out:
> + if (!found) {
> + fprintf(stderr, "No rc devices found\n");
> + fprintf(stderr, "Enable CONFIG_RC_LOOPBACK, CONFIG_RC_LOOPBACK, and CONFIG_LIRC\n");
[Severity: Low]
This isn't a bug, but there's a typo here where CONFIG_RC_LOOPBACK is listed
twice in the error message.
> +? }
[Severity: Low]
Is this stray '?' character intended? It appears this will cause a compilation
error when building the selftests.
> +
> + globfree(&gl);
> + return found;
> +}
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/lirc_mode2.c b/tools/testing/selftests/bpf/progs/lirc_mode2.c
> new file mode 100644
> index 0000000000000..98137f3c5c03d
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/lirc_mode2.c
> @@ -0,0 +1,32 @@
> +// SPDX-License-Identifier: GPL-2.0
> +// test ir decoder
> +//
> +// Copyright (C) 2018 Sean Young <sean@mess.org>
[Severity: Low]
Similarly here, should this be converted to a standard C-style multi-line
comment block?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260820210156.50603-1-sean@mess.org?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-20 21:11 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 21:01 [PATCH v2] selftests/bpf: convert lirc_mode2 to prog_tests and extend coverage Sean Young
2026-08-20 21:11 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox