[PATCH v1 4/4] samples/bpf: an example of a raw IR decoder

From: Sean Young
Date: Mon May 14 2018 - 17:11:20 EST


This implements the grundig-16 IR protocol.

Signed-off-by: Sean Young <sean@xxxxxxxx>
---
samples/bpf/Makefile | 4 +
samples/bpf/bpf_load.c | 9 +-
samples/bpf/grundig_decoder_kern.c | 112 ++++++++++++++++++++++
samples/bpf/grundig_decoder_user.c | 54 +++++++++++
tools/bpf/bpftool/prog.c | 1 +
tools/include/uapi/linux/bpf.h | 17 +++-
tools/testing/selftests/bpf/bpf_helpers.h | 6 ++
7 files changed, 200 insertions(+), 3 deletions(-)
create mode 100644 samples/bpf/grundig_decoder_kern.c
create mode 100644 samples/bpf/grundig_decoder_user.c

diff --git a/samples/bpf/Makefile b/samples/bpf/Makefile
index 4d6a6edd4bf6..c6fa111f103a 100644
--- a/samples/bpf/Makefile
+++ b/samples/bpf/Makefile
@@ -44,6 +44,7 @@ hostprogs-y += xdp_monitor
hostprogs-y += xdp_rxq_info
hostprogs-y += syscall_tp
hostprogs-y += cpustat
+hostprogs-y += grundig_decoder

# Libbpf dependencies
LIBBPF := ../../tools/lib/bpf/bpf.o ../../tools/lib/bpf/nlattr.o
@@ -95,6 +96,7 @@ xdp_monitor-objs := bpf_load.o $(LIBBPF) xdp_monitor_user.o
xdp_rxq_info-objs := bpf_load.o $(LIBBPF) xdp_rxq_info_user.o
syscall_tp-objs := bpf_load.o $(LIBBPF) syscall_tp_user.o
cpustat-objs := bpf_load.o $(LIBBPF) cpustat_user.o
+grundig_decoder-objs := bpf_load.o $(LIBBPF) grundig_decoder_user.o

# Tell kbuild to always build the programs
always := $(hostprogs-y)
@@ -148,6 +150,7 @@ always += xdp_rxq_info_kern.o
always += xdp2skb_meta_kern.o
always += syscall_tp_kern.o
always += cpustat_kern.o
+always += grundig_decoder_kern.o

HOSTCFLAGS += -I$(objtree)/usr/include
HOSTCFLAGS += -I$(srctree)/tools/lib/
@@ -193,6 +196,7 @@ HOSTLOADLIBES_xdp_monitor += -lelf
HOSTLOADLIBES_xdp_rxq_info += -lelf
HOSTLOADLIBES_syscall_tp += -lelf
HOSTLOADLIBES_cpustat += -lelf
+HOSTLOADLIBES_grundig_decoder += -lelf

# Allows pointing LLC/CLANG to a LLVM backend with bpf support, redefine on cmdline:
# make samples/bpf/ LLC=~/git/llvm/build/bin/llc CLANG=~/git/llvm/build/bin/clang
diff --git a/samples/bpf/bpf_load.c b/samples/bpf/bpf_load.c
index bebe4188b4b3..0fd389e95bb9 100644
--- a/samples/bpf/bpf_load.c
+++ b/samples/bpf/bpf_load.c
@@ -69,6 +69,7 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size)
bool is_sockops = strncmp(event, "sockops", 7) == 0;
bool is_sk_skb = strncmp(event, "sk_skb", 6) == 0;
bool is_sk_msg = strncmp(event, "sk_msg", 6) == 0;
+ bool is_ir_decoder = strncmp(event, "ir_decoder", 10) == 0;
size_t insns_cnt = size / sizeof(struct bpf_insn);
enum bpf_prog_type prog_type;
char buf[256];
@@ -102,6 +103,8 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size)
prog_type = BPF_PROG_TYPE_SK_SKB;
} else if (is_sk_msg) {
prog_type = BPF_PROG_TYPE_SK_MSG;
+ } else if (is_ir_decoder) {
+ prog_type = BPF_PROG_TYPE_RAWIR_DECODER;
} else {
printf("Unknown event '%s'\n", event);
return -1;
@@ -116,7 +119,8 @@ static int load_and_attach(const char *event, struct bpf_insn *prog, int size)

prog_fd[prog_cnt++] = fd;

- if (is_xdp || is_perf_event || is_cgroup_skb || is_cgroup_sk)
+ if (is_xdp || is_perf_event || is_cgroup_skb || is_cgroup_sk ||
+ is_ir_decoder)
return 0;

if (is_socket || is_sockops || is_sk_skb || is_sk_msg) {
@@ -607,7 +611,8 @@ static int do_load_bpf_file(const char *path, fixup_map_cb fixup_map)
memcmp(shname, "cgroup/", 7) == 0 ||
memcmp(shname, "sockops", 7) == 0 ||
memcmp(shname, "sk_skb", 6) == 0 ||
- memcmp(shname, "sk_msg", 6) == 0) {
+ memcmp(shname, "sk_msg", 6) == 0 ||
+ memcmp(shname, "ir_decoder", 10) == 0) {
ret = load_and_attach(shname, data->d_buf,
data->d_size);
if (ret != 0)
diff --git a/samples/bpf/grundig_decoder_kern.c b/samples/bpf/grundig_decoder_kern.c
new file mode 100644
index 000000000000..c80f2c9cc69a
--- /dev/null
+++ b/samples/bpf/grundig_decoder_kern.c
@@ -0,0 +1,112 @@
+
+#include <uapi/linux/bpf.h>
+#include <uapi/linux/bpf_rcdev.h>
+#include "bpf_helpers.h"
+#include <linux/version.h>
+
+enum grundig_state {
+ STATE_INACTIVE,
+ STATE_HEADER_SPACE,
+ STATE_LEADING_PULSE,
+ STATE_BITS_SPACE,
+ STATE_BITS_PULSE,
+};
+
+struct decoder_state {
+ u32 bits;
+ enum grundig_state state;
+ u32 count;
+ u32 last_space;
+};
+
+struct bpf_map_def SEC("maps") decoder_state_map = {
+ .type = BPF_MAP_TYPE_ARRAY,
+ .key_size = sizeof(u32),
+ .value_size = sizeof(struct decoder_state),
+ .max_entries = 1,
+};
+
+#define US_TO_NS(t) 1000*(t)
+static inline bool eq_margin(unsigned d1, unsigned d2, unsigned margin)
+{
+ return ((d1 > (d2 - margin)) && (d1 < (d2 + margin)));
+}
+
+SEC("ir_decoder/grundig_decoder")
+int bpf_decoder(struct ir_raw_event *raw)
+{
+ u32 key = 0;
+ struct decoder_state init = {};
+
+ struct decoder_state *s = bpf_map_lookup_elem(&decoder_state_map, &key);
+
+ if (!s)
+ s = &init;
+
+ if (raw->carrier_report) {
+ // ignore
+ } else if (raw->reset) {
+ s->state = STATE_INACTIVE;
+ } else if (s->state == STATE_INACTIVE) {
+ if (raw->pulse && eq_margin(US_TO_NS(900), raw->duration, US_TO_NS(100))) {
+ s->bits = 0;
+ s->state = STATE_HEADER_SPACE;
+ s->count = 0;
+ }
+ } else if (s->state == STATE_HEADER_SPACE) {
+ if (!raw->pulse && eq_margin(US_TO_NS(2900), raw->duration, US_TO_NS(200)))
+ s->state = STATE_LEADING_PULSE;
+ else
+ s->state = STATE_INACTIVE;
+ } else if (s->state == STATE_LEADING_PULSE) {
+ if (raw->pulse && eq_margin(US_TO_NS(1300), raw->duration, US_TO_NS(100)))
+ s->state = STATE_BITS_SPACE;
+ else
+ s->state = STATE_INACTIVE;
+ } else if (s->state == STATE_BITS_SPACE) {
+ s->last_space = raw->duration;
+ s->state = STATE_BITS_PULSE;
+ } else if (s->state == STATE_BITS_PULSE) {
+ int t = -1;
+ if (eq_margin(s->last_space, US_TO_NS(472), US_TO_NS(150)) &&
+ eq_margin(raw->duration, US_TO_NS(583), US_TO_NS(150)))
+ t = 0;
+ if (eq_margin(s->last_space, US_TO_NS(1139), US_TO_NS(150)) &&
+ eq_margin(raw->duration, US_TO_NS(583), US_TO_NS(150)))
+ t = 1;
+ if (eq_margin(s->last_space, US_TO_NS(1806), US_TO_NS(150)) &&
+ eq_margin(raw->duration, US_TO_NS(583), US_TO_NS(150)))
+ t = 2;
+ if (eq_margin(s->last_space, US_TO_NS(2200), US_TO_NS(150)) &&
+ eq_margin(raw->duration, US_TO_NS(1139), US_TO_NS(150)))
+ t = 3;
+ if (t < 0) {
+ s->state = STATE_INACTIVE;
+ } else {
+ s->bits <<= 2;
+ switch (t) {
+ case 3: s->bits |= 0; break;
+ case 2: s->bits |= 3; break;
+ case 1: s->bits |= 2; break;
+ case 0: s->bits |= 1; break;
+ }
+ s->count += 2;
+ if (s->count == 16) {
+ bpf_rc_keydown(raw, 0x40, s->bits, 0);
+ s->state = STATE_INACTIVE;
+ } else {
+ s->state = STATE_BITS_SPACE;
+ }
+ }
+ }
+
+ if (s == &init)
+ bpf_map_update_elem(&decoder_state_map, &key, s, BPF_NOEXIST);
+
+ return 0;
+}
+
+char _license[] SEC("license") = "GPL";
+
+u32 _version SEC("version") = LINUX_VERSION_CODE;
+
diff --git a/samples/bpf/grundig_decoder_user.c b/samples/bpf/grundig_decoder_user.c
new file mode 100644
index 000000000000..61e8ee5f73ee
--- /dev/null
+++ b/samples/bpf/grundig_decoder_user.c
@@ -0,0 +1,54 @@
+
+#include <linux/bpf.h>
+#include <assert.h>
+#include <errno.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <libgen.h>
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "bpf_load.h"
+#include "bpf_util.h"
+#include "libbpf.h"
+
+int main(int argc, char **argv)
+{
+ char filename[256];
+ int ret, lircfd;
+
+ if (argc != 2) {
+ printf("Usage: %s /dev/lircN\n", argv[0]);
+ return 2;
+ }
+
+ snprintf(filename, sizeof(filename), "%s_kern.o", argv[0]);
+
+ if (load_bpf_file(filename)) {
+ printf("%s", bpf_log_buf);
+ return 1;
+ }
+
+ lircfd = open(argv[1], O_RDWR);
+ if (lircfd == -1) {
+ printf("failed to open lirc device %s: %m\n", argv[1]);
+ return 1;
+ }
+
+ ret = bpf_prog_attach(prog_fd[0], lircfd, BPF_RAWIR_DECODER, 0);
+ if (ret) {
+ printf("Failed to attach bpf to lirc device: %m\n");
+ return 1;
+ }
+
+ printf("Grundig IR decoder loaded and attached. Hit any key to stop\n");
+ getchar();
+
+ return 0;
+}
diff --git a/tools/bpf/bpftool/prog.c b/tools/bpf/bpftool/prog.c
index f7a810897eac..ae1c26df212d 100644
--- a/tools/bpf/bpftool/prog.c
+++ b/tools/bpf/bpftool/prog.c
@@ -68,6 +68,7 @@ static const char * const prog_type_name[] = {
[BPF_PROG_TYPE_SOCK_OPS] = "sock_ops",
[BPF_PROG_TYPE_SK_SKB] = "sk_skb",
[BPF_PROG_TYPE_CGROUP_DEVICE] = "cgroup_device",
+ [BPF_PROG_TYPE_RAWIR_DECODER] = "ir_decoder",
};

static void print_boot_time(__u64 nsecs, char *buf, unsigned int size)
diff --git a/tools/include/uapi/linux/bpf.h b/tools/include/uapi/linux/bpf.h
index c5ec89732a8d..d9740599adf6 100644
--- a/tools/include/uapi/linux/bpf.h
+++ b/tools/include/uapi/linux/bpf.h
@@ -137,6 +137,7 @@ enum bpf_prog_type {
BPF_PROG_TYPE_SK_MSG,
BPF_PROG_TYPE_RAW_TRACEPOINT,
BPF_PROG_TYPE_CGROUP_SOCK_ADDR,
+ BPF_PROG_TYPE_RAWIR_DECODER,
};

enum bpf_attach_type {
@@ -154,6 +155,7 @@ enum bpf_attach_type {
BPF_CGROUP_INET6_CONNECT,
BPF_CGROUP_INET4_POST_BIND,
BPF_CGROUP_INET6_POST_BIND,
+ BPF_RAWIR_DECODER,
__MAX_BPF_ATTACH_TYPE
};

@@ -755,6 +757,17 @@ union bpf_attr {
* @addr: pointer to struct sockaddr to bind socket to
* @addr_len: length of sockaddr structure
* Return: 0 on success or negative error code
+ *
+ * int bpf_rc_keydown(ctx, protocol, scancode, toggle)
+ * Report decoded scancode with toggle value
+ * @ctx: pointer to ctx
+ * @protocol: decoded protocol
+ * @scancode: decoded scancode
+ * @toggle: set to 1 if button was toggled, else 0
+ *
+ * int bpf_rc_repeat(ctx)
+ * Repeat the last decoded scancode
+ * @ctx: pointer to ctx
*/
#define __BPF_FUNC_MAPPER(FN) \
FN(unspec), \
@@ -821,7 +834,9 @@ union bpf_attr {
FN(msg_apply_bytes), \
FN(msg_cork_bytes), \
FN(msg_pull_data), \
- FN(bind),
+ FN(bind), \
+ FN(rc_repeat), \
+ FN(rc_keydown),

/* integer value in 'imm' field of BPF_CALL instruction selects which helper
* function eBPF program intends to call
diff --git a/tools/testing/selftests/bpf/bpf_helpers.h b/tools/testing/selftests/bpf/bpf_helpers.h
index d8223d99f96d..4bf23d3dfc33 100644
--- a/tools/testing/selftests/bpf/bpf_helpers.h
+++ b/tools/testing/selftests/bpf/bpf_helpers.h
@@ -96,6 +96,12 @@ static int (*bpf_msg_pull_data)(void *ctx, int start, int end, int flags) =
(void *) BPF_FUNC_msg_pull_data;
static int (*bpf_bind)(void *ctx, void *addr, int addr_len) =
(void *) BPF_FUNC_bind;
+static int (*bpf_rc_repeat)(void *ctx) =
+ (void *) BPF_FUNC_rc_repeat;
+static int (*bpf_rc_keydown)(void *ctx, unsigned protocol, unsigned scancode,
+ unsigned toggle) =
+ (void *) BPF_FUNC_rc_keydown;
+

/* llvm builtin functions that eBPF C program may use to
* emit BPF_LD_ABS and BPF_LD_IND instructions
--
2.17.0