IR decoding with BPF
Infrared remote controls emit IR light using a simple LED. The LED is turned on and off for shorter or longer periods, which is interpreted somewhat akin to morse code. When infrared light has been detected for a period, the result is called a "pulse". The time between pulses when no infrared light is detected is called a "space".
Whenever a pulse or space is detected by an IR receiver, a BPF program will be executed (if one is attached). This program consists of a single function entry point that takes a pointer to a context. For IR decoders, this context is an unsigned int value. For a packet filter, the context would instead be a packet. In our case, the lower 24 bits of the int value contain the duration of the pulse or space, in microseconds. The top eight bits define the type of the event, which can either be LIRC_MODE2_PULSE, LIRC_MODE2_SPACE, or LIRC_MODE2_TIMEOUT. The return value of the BPF program is ignored.
If a space between two pulses gets excessively long, it could delay the decoding of a button press. For example, we might want to know that the IR message has really ended by measuring the space after the last pulse has occurred. Since a space is a time between two pulses, we would have to wait for the next pulse from the next IR message to occur before we would get this value. So, for this reason, there is a timeout. If a space lasts longer than the timeout, it is reported as LIRC_MODE2_TIMEOUT. This is typically set at 125ms.
A BPF program can be written in a number of different ways, but the easiest way is to use clang with the target BPF. This allows the BPF program to be written in a sort of restricted C that does not allow the use of C-library functions or loops, for example.
To create an IR decoder in BPF, we start with:
static int eq_margin(int duration, int expected, int margin)
{
return (duration >= (expected - margin))
&& (duration <= (expected + margin));
}
int bpf_decoder(unsigned int *sample)
{
int duration = *sample & LIRC_VALUE_MASK;
bool pulse = (*sample & LIRC_MODE2_MASK) == LIRC_MODE2_PULSE;
if (pulse && eq_margin(duration, 300, 100) {
// seen short pulse of about 300 microseconds
}
}
Typically, IR receivers have a precision of 50µs at most. I would recommend checking for durations of at least 100µs around the value you expect.
Now we can parse a single pulse or space, but every IR message consists of several pulses and spaces in quick succession. In a regular C program, we would use a static variable, a global variable, or some heap memory to maintain our state while waiting for the next event. Unfortunately none of those options are available in BPF. Instead, we use BPF maps, which are a generic key-value store where the key is always an unsigned int and the value is a generic blob; we can store whatever we want. This is how we declare a BPF map to hold the IR-decoding state:
struct decoder_state {
unsigned int bits;
unsigned int count;
};
struct bpf_map_def SEC("maps") decoder_state_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(unsigned int),
.value_size = sizeof(struct decoder_state),
.max_entries = 1,
};
There are a few different types of BPF maps, the main ones being "array" and "hash". Since we are only looking to store one structure, an array is more than sufficient; we thus specify max_entries as one. The key_size has to be the size of an unsigned int, no other key size is supported. The value_size is the size of our blob of data. We've declared a struct for this purpose, and we use sizeof() to ensure we have the right storage for it.
There are a number functions available to use BPF maps from our BPF code. For example, to get an our entry in decoder_state_map BPF map, we can call:
int key = 0;
struct decoder_state *s = bpf_map_lookup_elem(&decoder_state_map, &key);
Unfortunately, if we try to use the pointer to the map, we will get an error when we load our BPF program: "R6 invalid mem access 'map_value_or_null'". This is the kernel's BPF verifier complaining; it checks to ensure that a BPF program does not do anything it should not, like try to access out-of-bounds memory. It also checks for other conditions, like relying on undefined behavior or loops.
The problem here is that bpf_map_lookup_elem(), the function used to obtain a value from a BPF map, might return NULL if the key is beyond the last element. The elements of an array are pre-allocated, and we are looking for element zero out of a total of one, so this lookup should never fail. However, the BPF verifier is not aware of this so, in order to keep the verifier happy, we have to add:
if (!s)
return 0;
The pointer we get from bpf_map_lookup_elem() is a direct pointer to the array, so we do not have to call bpf_map_update_elem() after making changes. The BPF verifier will check that we only use our pointer with the right offsets within our array entry; otherwise our program will not load.
Now that we have memory to store state, we can implement decoding. When we have decoded the IR to a button event, we can submit that event to the input subsystem using the BPF function bpf_rc_keydown(). It takes four arguments, being the BPF context, the protocol, the scancode, and the toggle bit:
- The context for BPF is the pointer that was passed to the main BPF function; so we simply pass sample here.
- The IR protocol can be used by user space to determine which protocol produced any given scancode; at the moment, nothing uses it.
- The scancode is the value that was decoded. IR protocols generally encode some sort of value, and that value does not necessarily represent a key or a button. A particular remote might assign particular values with buttons; so, we need a mapping from scancode to key code. This is done using remote-control keymaps, which usually live in /lib/udev/rc_keymaps/ if the v4l-utils package is installed (or the ir-keytable package on Ubuntu or Debian).
- Some IR protocols include a toggle bit. Since the IR message is repeated every 90ms or so, it is impossible to distinguish a key being held from a key released and pressed again (toggled). In the latter case, the toggle bit will change value, so rc-core knows to generate both key-up and key-down events.
So those are the four arguments to bpf_rc_keydown(). Now, we can show a complete example of a fictional IR decoder.
#include <linux/lirc.h>
#include <linux/bpf.h>
#include "bpf_helpers.h"
enum state {
STATE_INACTIVE,
STATE_FIRST_PULSE,
STATE_SECOND_PULSE
};
struct decoder_state {
enum state state;
unsigned int space;
};
struct bpf_map_def SEC("maps") decoder_state_map = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(unsigned int),
.value_size = sizeof(struct decoder_state),
.max_entries = 1,
};
SEC("fictional_ir")
int decode(unsigned int *sample)
{
int key = 0;
struct decoder_state *s = bpf_map_lookup_elem(&decoder_state_map, &key);
if (!s)
return 0;
int duration = LIRC_VALUE(*sample);
switch (s->state) {
case STATE_INACTIVE:
if (LIRC_IS_PULSE(*sample) && duration == 500) {
s->state = STATE_FIRST_PULSE;
}
break;
case STATE_FIRST_PULSE:
if (LIRC_IS_SPACE(*sample)) {
s->space = duration;
s->state = STATE_SECOND_PULSE;
} else {
s->state = STATE_INACTIVE;
}
break;
case STATE_SECOND_PULSE:
if (LIRC_IS_PULSE(*sample) && duration == 500) {
bpf_rc_keydown(sample, 64, s->space / 100, 0);
}
s->state = STATE_INACTIVE;
break;
}
return 0;
}
char _license[] SEC("license") = "GPL";
Several operations are multiplexed through the bpf() system call for managing BPF programs and BPF maps, and for attaching them to devices. To create a BPF program, the BPF_PROG_LOAD is used. We have to provide a pointer to the BPF instructions, the instruction count, and a program name. If the system call is successful, we will get a file descriptor.
We can create BPF maps with the BPF_MAP_CREATE command, which also returns a file descriptor on success. Once we have the program and maps created, we can attach the program to a LIRC device (e.g. /dev/lirc0) using the BPF_PROG_ATTACH command. We have to provide a file descriptor for the LIRC device and the BPF program file descriptor. Once the file descriptor is attached, we can safely exit our process and the BPF program won't be freed when its file descriptor is closed.
Currently there is a hard-coded limit of 64 BPF programs that may be attached to one LIRC device. Any more, and BPF_PROG_ATTACH will return E2BIG. Every time a new pulse or space occurs, all the BPF programs will be executed. This makes it possible to load multiple BPF decoders, so that different remotes can be used at the same time.
As you might expect there are also commands for querying and detaching BPF programs.
The BPF example above can be compiled it with:
clang --target=bpf -O2 -c foobar.c
You'll need to compile it with kernel headers from 4.18 (or later), and the bpf_helpers.h from the same tree. This produces foobar.o, an ELF object file.
Using ir-keytable, you can load this BPF program. You'll need the BPF patches, which have not been merged yet at the time of writing. In order to simulate this, the rc-loopback pseudo-receiver can be used, so no IR hardware is needed. Here are the steps to make this work:
modprobe rc-loopback
ir-keytable -p ./foobar.o
In order to test this setup, create a file test with the following contents:
pulse 500
space 1500
pulse 500
Now, run:
ir-keytable -k 15:KEY_VOLUMEUP -t
in one terminal, and:
ir-ctl -s test
in another. You should get this output:
855.168999: lirc protocol(64): scancode = 0xf
855.169009: event type EV_MSC(0x04): scancode = 0x0f
855.169009: event type EV_KEY(0x01): key_down: KEY_VOLUMEUP
855.169009: event type EV_SYN(0x00).
The ir-keytable patches above also include a Python script that converts lircd remote configuration so that it can be used with ir-keytable. This should make it possible to do without the lirc daemon. However, some protocol decoders require very basic loops, which currently the BPF verifier does not allow at all.
Even with all lircd remote configurations supported, that would
still not cover all possible remote controls. A possible solution can be
found in IRP
notation, a general form of description for IR protocols; it would be
nice to generate BPF from
that, and have support for a very broad array of remotes, without having to
open-code each one.
Lastly, other things than button presses are encoded in IR, for example
target temperatures in air conditioning remote controls, or some remote
controls include a directional pad. Supporting such devices with BPF
decoders will require some further work.
| Index entries for this article | |
|---|---|
| Kernel | BPF/Device drivers |
| Kernel | Device drivers/Support APIs |
| GuestArticles | Young, Sean |
Posted Jul 9, 2018 16:09 UTC (Mon)
by linuxjacques (subscriber, #45768)
[Link] (6 responses)
Posted Jul 9, 2018 18:01 UTC (Mon)
by jake (editor, #205)
[Link] (5 responses)
I just added a link to hopefully help others who are not up on BPF.
thanks!
jake
Posted Jul 9, 2018 23:34 UTC (Mon)
by atai (subscriber, #10977)
[Link] (4 responses)
Posted Jul 10, 2018 9:06 UTC (Tue)
by k3ninho (subscriber, #50375)
[Link] (1 responses)
K3n.
Posted Jul 10, 2018 16:51 UTC (Tue)
by Cyberax (✭ supporter ✭, #52523)
[Link]
Posted Jul 19, 2018 19:02 UTC (Thu)
by miquels (guest, #59247)
[Link]
Posted Jul 20, 2018 8:06 UTC (Fri)
by rurban (guest, #96594)
[Link]
With arrays in BPF and spectre/meltdown it is not possible to be on the safe side anymore.
Posted Jul 9, 2018 20:45 UTC (Mon)
by meyert (subscriber, #32097)
[Link] (6 responses)
So why is any of this actually done in kernel code?
Why not rip out all in kernel IR decoding and why adding more like this bpf support?
Posted Jul 9, 2018 22:45 UTC (Mon)
by roc (subscriber, #30627)
[Link]
Posted Jul 9, 2018 23:08 UTC (Mon)
by rahvin (guest, #16953)
[Link] (3 responses)
I don't know about you but that's pretty cool that it was designed and implemented generically enough that it's got uses beyond the direct use for which it was developed. I think that's actually a pretty good example of the Linux way in designing these kernel interfaces so they are flexible.
Posted Jul 9, 2018 23:35 UTC (Mon)
by atai (subscriber, #10977)
[Link] (1 responses)
Posted Jul 10, 2018 0:11 UTC (Tue)
by daney (guest, #24551)
[Link]
It seems the main type of device that would use this is probably embedded consumer platforms. Are we going to start seeing evil IR blasters designed to hack into these things via the (now) in-kernel IR decoders? I don't know.
Posted Jul 10, 2018 0:57 UTC (Tue)
by wahern (subscriber, #37304)
[Link]
This would be noteworthy if this were using original BPF, which was explicitly written for stateless filtering of network packets using a very minimal virtual stack machine. The BPF engine in Linux was extended to support more generic constructs as well as loading and storing of state, and has more in common with Google's NaCL project than original BPF. The only remaining limitation in Linux's BPF is, IIUC, the inability to loop--in classic BPF only forward jumps are allowed, whereas in Linux BPF backwards jumps are allowed if the verifier is convinced the program doesn't actually loop.
It's not surprising at all that it can do this.
Posted Jul 16, 2018 16:53 UTC (Mon)
by mchehab (subscriber, #41156)
[Link]
The answer is the same to this question: "why an USB HID scancode is translated to key inside the Kernel?"
Because on an embedded device, the IR may be the only input device. Think for example on a Set Top Box or a TV set: usually, all the user has is the remote controller. It needs to be able to handle key presses since when the Kernel starts.
Posted Jul 10, 2018 0:16 UTC (Tue)
by quotemstr (subscriber, #45331)
[Link] (12 responses)
Posted Jul 10, 2018 7:45 UTC (Tue)
by shiftee (subscriber, #110711)
[Link] (7 responses)
While not a disaster it is preferable to throw out only that packet and try reading the next one.
Posted Jul 10, 2018 8:40 UTC (Tue)
by dgm (subscriber, #49227)
[Link] (5 responses)
But I thought that libmodbus (http://libmodbus.org/) had this already implemented. Is it not the case?
Posted Jul 10, 2018 8:58 UTC (Tue)
by chris.sykes (subscriber, #54374)
[Link] (4 responses)
Nope, libmodbus tries to parse the RTU frames as bytes are received in order to figure out how many are still outstanding; the trouble is, it has to do this before it can check the CRC...
Posted Jul 10, 2018 14:00 UTC (Tue)
by dgm (subscriber, #49227)
[Link] (3 responses)
Posted Jul 10, 2018 15:39 UTC (Tue)
by chris.sykes (subscriber, #54374)
[Link] (2 responses)
It's difficult to do RTU frame timing properly from user-space, without RT scheduling; and you really want control over the RX FIFO interrupt thresholds in the UART as well.
I've often thought making Modbus RTU serial line discipline, that exposed a socket API (like socketCAN), would make an interesting project, but so far, haven't found the time...
Posted Jul 11, 2018 7:22 UTC (Wed)
by shiftee (subscriber, #110711)
[Link] (1 responses)
Posted Jul 11, 2018 9:44 UTC (Wed)
by Darkmere (subscriber, #53695)
[Link]
With rs485 you then want to decode traffic in multiple speeds at once to find out which traffic is modbus RTU and which is some other traffic.
Posted Jul 12, 2018 9:35 UTC (Thu)
by seanyoung (subscriber, #28711)
[Link]
This is an interesting idea. I have a device which produces packets on usb-serial; the process wakes up for nearly every byte that arrives, which causes load on the underpowered arm device it is attached to. It would be nice if the process could be woken up only when a complete packet arrives, and invalid packets get dropped.
This seems similar to the original purpose of BPF. The BPF program would need access to the serial buffer and control read readiness reporting.
Posted Jul 16, 2018 17:10 UTC (Mon)
by mchehab (subscriber, #41156)
[Link] (3 responses)
Handling input events in Kernel has noting to do with latency. It is all about allowing to control the system when it boots. It doesn't really matter if the keyboard is an old PS/2 keyboard, an USB keyboard or a Infrared remote: once keys are pressed, user expects actions to be taken.
The remote controller subsystem was designed to allow:
On systems that have keyboard/mice, all the above options could be used, but there are embedded devices where the only input device is an infrared (like a TV set of a Set Top Box). For those, doing Kernel decoding provide ways to allow passing keycodes very early at boot time, with would help, for example, to start userspace with a different run level, just like if the device had any other type of keyboard.
Posted Jul 17, 2018 9:20 UTC (Tue)
by seanyoung (subscriber, #28711)
[Link]
1. IR receivers that report pulse/space information "raw IR" or "mode 2"
By having pulse/space decoding in kernel space, both types of devices can be used in LIRC_MODE_SCANCODE, which means that decoded scancodes are reported. Userspace does not need to care if the IR decoding happens in the IR receiver or in the kernel; the same interface is exposed to userspace.
Some IR devices (like the winbond-cir SuperI/O) can do both decoding in hardware circuit or report pulse-space to the kernel. We choose to set up the device in the later mode since we can then support many more protocols than the few the hardware supports.
Posted Jul 30, 2022 0:47 UTC (Sat)
by comex (subscriber, #71521)
[Link] (1 responses)
Posted Jul 30, 2022 21:20 UTC (Sat)
by flussence (guest, #85566)
[Link]
Posted Jul 11, 2018 15:37 UTC (Wed)
by gebi (guest, #59940)
[Link] (9 responses)
https://github.com/torvalds/linux/blob/master/drivers/inp...
Posted Jul 12, 2018 5:19 UTC (Thu)
by paulj (subscriber, #341)
[Link] (8 responses)
Posted Jul 12, 2018 9:25 UTC (Thu)
by seanyoung (subscriber, #28711)
[Link] (6 responses)
Posted Jul 13, 2018 0:52 UTC (Fri)
by k8to (guest, #15413)
[Link] (5 responses)
In other words if doing it in user space costs 0.00000000001% of a core and doing it in kernel costs 0.000000000001%, then it's not really a performance concern.
Posted Jul 16, 2018 6:43 UTC (Mon)
by cpitrat (subscriber, #116459)
[Link] (4 responses)
Posted Jul 16, 2018 8:47 UTC (Mon)
by paulj (subscriber, #341)
[Link] (3 responses)
Posted Jul 16, 2018 8:59 UTC (Mon)
by cpitrat (subscriber, #116459)
[Link] (2 responses)
So a bunch of challenges, but I as you see, no major obstacle.
Posted Jul 16, 2018 16:50 UTC (Mon)
by excors (subscriber, #95769)
[Link]
Posted Jul 19, 2018 8:56 UTC (Thu)
by johnny (guest, #10110)
[Link]
Posted Jul 15, 2018 14:51 UTC (Sun)
by rossmohax (guest, #71829)
[Link]
Posted Jul 28, 2018 14:27 UTC (Sat)
by HybridAU (guest, #85157)
[Link] (1 responses)
I know this is slightly off-topic for this article, but does anyone know of a good USB IR Blaster / Receiver? I bought an Irdroid USB IR Transceiver a few years ago and I don't know if it was damaged during postage or something but I could never get it to work. I tried Lirc / irrecord and never got anything. Essentially what I want to do is set up a Raspberry Pi, record the signals from my air conditioner's remote and then use a cron job to playback and switch on / off my air conditioner at certain times.
Posted Oct 11, 2018 11:54 UTC (Thu)
by seanyoung (subscriber, #28711)
[Link]
The raspberrypi this device is very well supported: https://energenie4u.co.uk/catalogue/product/ENER314-IR
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
I'll get onto it once I've finished the Common Lisp interpreter -- unless we can fold in an incomplete and buggy implementation thereof within the eBPF-js engine?
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
For the embedded C version we customised the driver to watch for a gap like above in order to separate the packets.
We then put the data in a struct with an int telling us the number of bytes.
For the Linux version we have not found a way to do this. We can read the bytes sequentially but the timing information is lost.
This means that if we receive a malformed or unsupported packet the only recovery is to flush the buffers and try reading again.
To do so under Linux it seems we need to modify the driver, but BPF looks like a nice solution if it were possible
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
(It’s common enough to have Mbus at 2400 bps and modbus at 9600 and 19200. And no. It ain’t pretty)
IR decoding with BPF
IR decoding with BPF
- disabling it completely;
- enable hardware decoders but require userspace to prepare it;
- do decoding in software (via LIRC);
- support devices with a micro-controller that reports scan codes to the Kernel (instead of PULSE/SPACE timings);
- and now, via BPF decoding (for newer Kernels).
IR decoding with BPF
2. IR receivers that report decoded scancodes
Also both types of devices report input events to the input layer in exactly the same way, so again it looks the same from userspace.
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
First you have to buy them. That's where you discover that few sellers support int64 through their whole toolchain.
Then you have to get them deliver. Even with small packages (20 cm^3) we're talking about 20,000 trucks coming to your home.
Then you need longer cables because even if the receptor is only 1cm^3 you need a 32m radius sphere to fit them all (ignoring the cable).
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
IR decoding with BPF
