• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0+
2 //
3 // Copyright (C) 2018 Sean Young <sean@mess.org>
4 
5 #include <linux/lirc.h>
6 #include <linux/bpf.h>
7 
8 #include "bpf_helpers.h"
9 
10 enum state {
11 	STATE_INACTIVE,
12 	STATE_HEADER_SPACE,
13 	STATE_BITS_SPACE,
14 	STATE_BITS_PULSE,
15 	STATE_TRAILER,
16 };
17 
18 struct decoder_state {
19 	unsigned long bits;
20 	enum state state;
21 	unsigned int count;
22 };
23 
24 struct bpf_map_def SEC("lirc_mode2/maps") decoder_state_map = {
25 	.type = BPF_MAP_TYPE_ARRAY,
26 	.key_size = sizeof(unsigned int),
27 	.value_size = sizeof(struct decoder_state),
28 	.max_entries = 1,
29 };
30 
31 // These values can be overridden in the rc_keymap toml
32 //
33 // We abuse elf relocations. We cast the address of these variables to
34 // an int, so that the compiler emits a mov immediate for the address
35 // but uses it as an int. The bpf loader replaces the relocation with the
36 // actual value (either overridden or taken from the data segment).
37 int margin = 200;
38 int header_pulse = 4000;
39 int header_space = 3900;
40 int bit_pulse = 550;
41 int bit_0_space = 900;
42 int bit_1_space = 1900;
43 int trailer_pulse = 550;
44 int bits = 24;
45 int rc_protocol = 68;
46 
47 #define BPF_PARAM(x) (int)(long)(&(x))
48 
eq_margin(unsigned d1,unsigned d2)49 static inline int eq_margin(unsigned d1, unsigned d2)
50 {
51 	return ((d1 > (d2 - BPF_PARAM(margin))) && (d1 < (d2 + BPF_PARAM(margin))));
52 }
53 
54 SEC("lirc_mode2/xbox_dvd")
bpf_decoder(unsigned int * sample)55 int bpf_decoder(unsigned int *sample)
56 {
57 	unsigned int key = 0;
58 	struct decoder_state *s = bpf_map_lookup_elem(&decoder_state_map, &key);
59 
60 	if (!s)
61 		return 0;
62 
63 	switch (*sample & LIRC_MODE2_MASK) {
64 	case LIRC_MODE2_SPACE:
65 	case LIRC_MODE2_PULSE:
66 	case LIRC_MODE2_TIMEOUT:
67 		break;
68 	default:
69 		// not a timing events
70 		return 0;
71 	}
72 
73 	int duration = LIRC_VALUE(*sample);
74 	int pulse = LIRC_IS_PULSE(*sample);
75 
76 	switch (s->state) {
77 	case STATE_HEADER_SPACE:
78 		if (!pulse && eq_margin(BPF_PARAM(header_space), duration))
79 			s->state = STATE_BITS_PULSE;
80 		else
81 			s->state = STATE_INACTIVE;
82 		break;
83 	case STATE_INACTIVE:
84 		if (pulse && eq_margin(BPF_PARAM(header_pulse), duration)) {
85 			s->bits = 0;
86 			s->state = STATE_HEADER_SPACE;
87 			s->count = 0;
88 		}
89 		break;
90 	case STATE_BITS_PULSE:
91 		if (pulse && eq_margin(BPF_PARAM(bit_pulse), duration))
92 			s->state = STATE_BITS_SPACE;
93 		else
94 			s->state = STATE_INACTIVE;
95 		break;
96 	case STATE_BITS_SPACE:
97 		if (pulse) {
98 			s->state = STATE_INACTIVE;
99 			break;
100 		}
101 
102 		s->bits <<= 1;
103 
104 		if (eq_margin(BPF_PARAM(bit_1_space), duration))
105 			s->bits |= 1;
106 		else if (!eq_margin(BPF_PARAM(bit_0_space), duration)) {
107 			s->state = STATE_INACTIVE;
108 			break;
109 		}
110 
111 		s->count++;
112 		if (s->count == BPF_PARAM(bits))
113 			s->state = STATE_TRAILER;
114 		else
115 			s->state = STATE_BITS_PULSE;
116 		break;
117 	case STATE_TRAILER:
118 		if (pulse && eq_margin(BPF_PARAM(trailer_pulse), duration)) {
119 			if (((s->bits >> 12) ^ (s->bits & 0xfff)) == 0xfff)
120 				bpf_rc_keydown(sample, BPF_PARAM(rc_protocol), s->bits & 0xfff, 0);
121 		}
122 
123 		s->state = STATE_INACTIVE;
124 	}
125 
126 	return 0;
127 }
128 
129 char _license[] SEC("license") = "GPL";
130