Last week, I found myself having to dive into the udev code to figure out how it determines whether what is a mouse or a keyboard. To solve the problem I was working on, I ended up having to replicate some of that logic in python, which is posted at the bottom. Let me explain how it works.
Device Info in /sys
To start, let’s look in /sys and see what input devices we have. If you look in /sys/class/input on your system, you’ll see many symlinks to devices. I’ll pick /sys/class/input/event12 to look at in more detail, so cd to event12/device
mfisch@caprica:/sys/class/input/event12/device$ ls -al
total 0
drwxr-xr-x 7 root root 0 Jul 8 16:05 .
drwxr-xr-x 3 root root 0 Jul 8 16:05 ..
drwxr-xr-x 2 root root 0 Jul 8 16:05 capabilities
lrwxrwxrwx 1 root root 0 Jul 8 20:50 device -> ../../../serio1
drwxr-xr-x 3 root root 0 Jul 8 16:05 event12
drwxr-xr-x 2 root root 0 Jul 8 16:05 id
-r--r--r-- 1 root root 4096 Jul 8 20:50 modalias
drwxr-xr-x 3 root root 0 Jul 8 16:05 mouse0
-r--r--r-- 1 root root 4096 Jul 8 16:05 name
-r--r--r-- 1 root root 4096 Jul 8 20:50 phys
drwxr-xr-x 2 root root 0 Jul 8 20:50 power
-r--r--r-- 1 root root 4096 Jul 8 20:50 properties
lrwxrwxrwx 1 root root 0 Jul 8 16:05 subsystem -> ../../../../../../class/input
-rw-r--r-- 1 root root 4096 Jul 8 16:05 uevent
-r--r--r-- 1 root root 4096 Jul 8 20:50 uniq
There are some interesting things in here, but the two I’m interested in are “name” and the “capabilities” directory. Let’s look at name first.
mfisch@caprica:/sys/class/input/event12/device$ cat name
PS/2 Generic Mouse
Using Capabilities
Okay, so this looks like a mouse, but udev doesn’t use the name to figure this out, it uses the capabilities directory. Let’s look there:
mfisch@caprica:/sys/class/input/event12/device/capabilities$ ls -al
total 0
drwxr-xr-x 2 root root 0 Jul 8 16:05 .
drwxr-xr-x 6 root root 0 Jul 8 16:05 ..
-r--r--r-- 1 root root 4096 Jul 8 16:05 abs
-r--r--r-- 1 root root 4096 Jul 8 16:05 ev
-r--r--r-- 1 root root 4096 Jul 8 20:43 ff
-r--r--r-- 1 root root 4096 Jul 8 16:05 key
-r--r--r-- 1 root root 4096 Jul 8 20:39 led
-r--r--r-- 1 root root 4096 Jul 8 20:43 msc
-r--r--r-- 1 root root 4096 Jul 8 16:05 rel
-r--r--r-- 1 root root 4096 Jul 8 20:43 snd
-r--r--r-- 1 root root 4096 Jul 8 16:05 sw
The aptly named capabilities provide information to udev on what “capabilities” the device has. udev is specifically interested in the following ones: abs (Absolute axes), ev (Event types), key (Keys and Buttons), and rel (Relative axes). Let’s examine one of these to determine what data it contains:
mfisch@caprica:/sys/class/input/event12/device/capabilities$ cat ev
7
So what does 7 mean? This is a bitmask (111) who’s bits are defined in /usr/include/linux/input.h. That file has bits defined for each of the types listed in the capabilities directory, the #define prefix matches the name in the capabilities directory, so we’re looking at EV_ (Note: key is represented by defines KEY_ and BTN_, since it’s for keys and buttons).
Since this mask is 0x7 or 111, we have bits 0, 1, and 2 set and the rest are false. Judging from the #defines, the following bits are set for this device:
#define EV_SYN 0x00
#define EV_KEY 0x01
#define EV_REL 0x02
This tells us that our device gets key events, relative movement event, and synchronization events. (Figuring this out required poking around the header a bit more).
A Quick Note About the ‘key’ Capability
The entry ‘key’ is much longer than ev and requires more explanation. Let’s look at what the ‘key’ capabilities shows for my keyboard:
20000 20000000020 0 0 500f02100002 3803078f900d401 feffffdfffefffff fffffffffffffffe
So that’s much larger than ev was. It’s still a bitmask, but it’s been split into words, using the word-size sense, so 8 bytes on my system. Since this mask is little endian, the ‘20000’ represents the highest set bits. This makes processing need to take place in word-size chunks, starting at the right. Another way to think of the output above is to think about it as one large mask, as long as you pad each “word” to be 16 digits, for example, what you really see above is this:
00000000000200000000020000000020000000000000000000000000000000000000500f02100002 03803078f900d401feffffdfffeffffffffffffffffffffe
To solve my problem, I split the output up on the spaces and set 64-bits at a time in my mask starting from the right-most word.
Now that we’ve read all these bitmasks, we need to do something with it.
What udev Does with this Info
If you look at the udev source code, specifically udev/udev-builtin-input_id.c, you can see how this data is used.
For mice, the code is fairly straightforward, look at the code for the test_pointers function. We really want to know where they set “is_mouse”. The code is also looking for other stuff like touchpads, so let’s ignore that. It basically boils down to this:
if EV_REL and REL_X and REL_Y and BTN_MOUSE:
you have a mouse
There’s also a similar block where you can replace REL above with ABS, which the comment claims is for VMWare’s USB mouse, but I kept it in my solution because I didn’t quite trust the comment and it seemed harmless otherwise.
For keyboards, the code is a bit more complex because it’s basically checking to see if you have a bunch of keys defined, this code is in the function test_key. then test_key looks for a keyboard it boils down to this:
If any bits are set in 'ev' and
if bits 1-31 (but not 0) are set:
you have a keyboard
Note: bits 1-31 represent the Escape key, numbers, and Q through D.
Putting it All Together
So now that we know how it works, let’s see it in action! Here it is run on my dev box:
mfisch@caprica:~/tmp/find_input$ ./find_input.py
INFO:root:/dev/input/event3 is a keyboard (AT Translated Set 2 keyboard)
INFO:root:/dev/input/event12 is a mouse (PS/2 Generic Mouse)
Here it is run on another system with more devices attached:
INFO:root: /dev/input/event3 is a keyboard (AT Translated Set 2 keyboard)
INFO:root: /dev/input/event13 is a mouse (PS/2 Generic Mouse)
INFO:root: /dev/input/event15 is a keyboard (BTC USB Keyboard)
INFO:root: /dev/input/event16 is a mouse (Primax HP Wireless Laser Mini Mouse)
My python code including unit tests! is avaialble in bzr at lp:~mfisch/+junk/find_input
Comments, bugs, and fixes all gladly accepted.