Vlad Lazarenko

... making all this up as I go along

Linux, HID and PyUSB

Today I was going through some stuff in my desk’s locker and ran into this nice toy — a big red electronic panic button. It has been there for years but I never had a chance to use it because a company making those does not have a driver for OS X, and of course I couldn’t find one for Linux either. But pushing a big red button is always fun and this time I have decided to try and make it work. So I brought it home with me into my “lab” to see what I can do.

After plugging it into a USB port, it immediately showed up in a list of USB devices (which you can see with lsusb tool). My Linux box has identified it as a Dream Cheeky Stress/Panic Button:

Bus 007 Device 010: ID 04f3:04a0 Elan Microelectronics Corp. Dream Cheeky Stress/Panic Button

Meanwhile, the kernel has also logged a message saying that it has found a new USB device. So the magic button seemed somewhat operational and I already knew two important things about it — a vendor and a product IDs. They were 0x04f3 and 0x04a0 respectively.

Writing a USB driver

The only little detail missing was actually a driver. I tried to Duck Duck Go it real quick but nothing showed up, and I decided to write it myself. How hard can it be, after all?

Reverse Engineering

In order to write a device driver, one must know what the device is doing. Obviously, I didn’t have any specification, neither I wanted to deal with Elan Microelectronics support department. So the only way to figure it out was reverse engineering. Luckily, Linux has usbmon — a facility in the kernel that is used to collect traces of I/O on the USB bus. After a quick pick at its documentation, I loaded the module using modprobe usbmon command and the device showed up in /sys/kernel/debug/usb/devices. Since it was attached to bus #7, I traced the I/O by reading the /sys/kernel/debug/usb/usbmon/7u file and pushing my panic button a few times to see if anything shows up. And every time I hit the button, the device was sending the following data to the host:

1
2
3
4
5
ffff8801b2efc9c0 1873321198 C Ii:7:010:1 0:8 8 = 02000000 00000000
ffff8801b2efc9c0 1873457204 C Ii:7:010:1 0:8 8 = 06000000 00000000
ffff8801b2efc9c0 1873633203 C Ii:7:010:1 0:8 8 = 06001300 00000000
ffff8801b2efc9c0 1873801206 C Ii:7:010:1 0:8 8 = 06000000 00000000
ffff8801b2efc9c0 1873977211 C Ii:7:010:1 0:8 8 = 02000000 00000000

Not only this proved that button itself works, it also uncovered a pattern — the device was sending 02000000 and 06000000 twice with 06001300 in between. So 06001300 seemed like a good indication of the button being pressed.

Linux Kernel USB Driver

The next step was to write a USB Device Driver for Linux. I dealt with PCI Express, DMA and Ethernet network drivers, but never wrote a single USB driver before. So I pulled my copy of the Linux Device Drivers book off the shelf and opened it on Chapter 13 “USB Drivers”. I have to say I got surprised — that rant is about 100 pages long. I would of course suck it up, enjoy the reading and proceed to hacking. If only it was something serious. And what I had was a simple USB button that did nothing but sending two 32-bit integers, 0x06 and 0x13, every time it gets hit. Going through the hundred pages just to read 64-bit of data off the USB cable, on Monday night, after a hard 10 hours working day… Ain’t nobody got time for that?

PyUSB

A bit disappointed, I tried to find an easier way and once against ducked for a few keywords like “USB”, “driver”, “read”, “HID” and so on…

Ask and it will be given to you; seek and you will find; knock and the door will be opened to you. — Matthew 7:7

Turned out, it is possible to write a USB driver in just a few lines of Python. Sir Micah Carrick have managed to make a driver for his credit card reader and wrote about his experience in this nice article. Following in his footsteps, I quickly glanced at PyUSB that he was using in his work, read a few other examples, and came up with a driver for my magic button. This is truly the shortest and simplest device driver that I have made in my life:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#!/usr/bin/env python

import sys, usb.core

dev = usb.core.find(idVendor=0x04f3, idProduct=0x04a0)
if dev is None:
    sys.exit("No Panic button found in the system");

try:
    if dev.is_kernel_driver_active(0) is True:
        dev.detach_kernel_driver(0)
except usb.core.USBError as e:
    sys.exit("Kernel driver won't give up control over device: %s" % str(e))

try:
    dev.set_configuration()
    dev.reset()
except usb.core.USBError as e:
    sys.exit("Cannot set configuration the device: %s" % str(e))

endpoint = dev[0][(0,0)][0]
while 1:
    try:
        data = dev.read(endpoint.bEndpointAddress, endpoint.wMaxPacketSize,
                        timeout=10000)
        if data is not None and len(data) > 2:
            if data[0] == 6 and data[2] == 19:
                # Panic button was pressed!
                print "OH MY GOD! OH MY GOD! DOUBLE RAINBOW!!!"
    except usb.core.USBError as e:
        if e.errno != 110: # 110 is a timeout.
            sys.exit("Error readin data: %s" % str(e))

So now I have my beautiful toy working!

UPDATE

Drew Fustini have hooked this up to the BeagleBone Next-Gen and made this video: