blob: 7b7352dbcd8265783feed80a7642af99918735e8 [file] [edit]
// 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);
}