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