1 // SPDX-License-Identifier: GPL-2.0+
2 //
3 // Copyright (C) 2019 Sean Young <sean@mess.org>
4 //
5 // This decoder matches pre-defined pulse-space sequences. It does so by
6 // iterating through the list There are many optimisation possible, if
7 // performance is an issue.
8 //
9 // First of all iterating through the list of patterns is one-by-one, even
10 // though after the first few pulse space sequences, most patterns will be
11 // will not be a match. The bitmap datastructure could use a "next clear bit"
12 // function.
13 //
14 // Secondly this can be transformed into a much more efficient state machine,
15 // where we pre-compile the patterns much like a regex.
16 
17 #include <linux/lirc.h>
18 #include <linux/bpf.h>
19 
20 #include "bpf_helpers.h"
21 #include "bitmap.h"
22 
23 #define MAX_PATTERNS 1024
24 
25 struct decoder_state {
26 	int pos;
27 	DECLARE_BITMAP(nomatch, MAX_PATTERNS);
28 };
29 
30 struct bpf_map_def SEC("maps") decoder_state_map = {
31 	.type = BPF_MAP_TYPE_ARRAY,
32 	.key_size = sizeof(unsigned int),
33 	.value_size = sizeof(struct decoder_state),
34 	.max_entries = 1,
35 };
36 
37 struct raw_pattern {
38 	unsigned int scancode;
39 	unsigned short raw[0];
40 };
41 
42 // ir-keytable will load the raw patterns here
43 struct bpf_map_def SEC("maps") raw_map = {
44 	.type = BPF_MAP_TYPE_ARRAY,
45 	.key_size = sizeof(unsigned int),
46 	.value_size = sizeof(struct raw_pattern), // this is not used
47 	.max_entries = MAX_PATTERNS,
48 };
49 
50 
51 // These values can be overridden in the rc_keymap toml
52 //
53 // We abuse elf relocations. We cast the address of these variables to
54 // an int, so that the compiler emits a mov immediate for the address
55 // but uses it as an int. The bpf loader replaces the relocation with the
56 // actual value (either overridden or taken from the data segment).
57 int margin = 200;
58 int rc_protocol = 68;
59 // The following two values are calculated by ir-keytable
60 int trail_space = 1000;
61 int max_length = 1;
62 
63 #define BPF_PARAM(x) (int)(&(x))
64 
eq_margin(unsigned d1,unsigned d2)65 static inline int eq_margin(unsigned d1, unsigned d2)
66 {
67 	return ((d1 > (d2 - BPF_PARAM(margin))) && (d1 < (d2 + BPF_PARAM(margin))));
68 }
69 
70 SEC("raw")
bpf_decoder(unsigned int * sample)71 int bpf_decoder(unsigned int *sample)
72 {
73 	unsigned int key = 0;
74 	struct decoder_state *s = bpf_map_lookup_elem(&decoder_state_map, &key);
75 	struct raw_pattern *p;
76 	unsigned int i;
77 
78 	// Make verifier happy. Should never come to pass
79 	if (!s)
80 		return 0;
81 
82 	switch (*sample & LIRC_MODE2_MASK) {
83 	case LIRC_MODE2_SPACE:
84 	case LIRC_MODE2_PULSE:
85 	case LIRC_MODE2_TIMEOUT:
86 		break;
87 	default:
88 		// not a timing events
89 		return 0;
90 	}
91 
92 	int duration = LIRC_VALUE(*sample);
93 	int pulse = LIRC_IS_PULSE(*sample);
94 	int pos = s->pos;
95 
96 	if (pos < 0 || pos >= BPF_PARAM(max_length)) {
97 		if (!pulse && duration >= BPF_PARAM(trail_space)) {
98 			bitmap_zero(s->nomatch, MAX_PATTERNS);
99 			s->pos = 0;
100 		}
101 
102 		return 0;
103 	}
104 
105 	if (!pulse && duration >= BPF_PARAM(trail_space)) {
106 		for (i = 0; i < MAX_PATTERNS; i++) {
107 			key = i;
108 			p = bpf_map_lookup_elem(&raw_map, &key);
109 			// Make verifier happy. Should never come to pass
110 			if (!p)
111 				break;
112 
113 			// Has this pattern already mismatched?
114 			if (bitmap_test(s->nomatch, i))
115 				continue;
116 
117 			// Are we at the end of the pattern?
118 			if (p->raw[pos] == 0)
119 				bpf_rc_keydown(sample, BPF_PARAM(rc_protocol),
120 					       p->scancode, 0);
121 		}
122 
123 		bitmap_zero(s->nomatch, MAX_PATTERNS);
124 		s->pos = 0;
125 	} else {
126 		for (i = 0; i < MAX_PATTERNS; i++) {
127 			key = i;
128 			p = bpf_map_lookup_elem(&raw_map, &key);
129 			// Make verifier happy. Should never come to pass
130 			if (!p)
131 				break;
132 
133 			// Has this pattern already mismatched?
134 			if (bitmap_test(s->nomatch, i))
135 				continue;
136 
137 			// If the pattern ended, or does not match
138 			if (p->raw[pos] == 0 ||
139 				!eq_margin(duration,  p->raw[pos]))
140 				bitmap_set(s->nomatch, i);
141 		}
142 
143 		s->pos++;
144 	}
145 
146 	return 0;
147 }
148 
149 char _license[] SEC("license") = "GPL";
150