Sunday, March 30, 2008

Writing my first linux driver - Part 1

I've always wanted to write a driver for a piece of hardware, but hadn't the slightest clue how to go about it. It seems like such a complex process...interfacing with the system at the kernel level, dealing with the necessary subsystems (ex: USB), reverse engineering the device's communication protocol. Where does a driver writing noob even begin?

Getting my inspiration

Luckily, I found a very insightful and well written series of articles by Greg Kroah-Hartman, who is the official maintainer of (among other things) the linux USB subsystem code. In his series, he covers all kinds of topics dealing with driver writing. Theres a lot of content, and it's not exactly laid out in step-by-step tutorial form. Still, it is by far the best resource out there that I've seen so far.

A list of the articles is located here. Lots of those articles are useful, but the most enlightening for someone getting started were the articles on writng usb drivers, writing your first kernel space driver, then rewriting the driver as a user space driver. In addition, the article on snooping the usb stream was helpful in figuring out how to reverse engineer a devices protocol from the working Windows driver that almost every usb device has.


Choosing a device for my first driver

So now that the series of articles had inspired me, I needed a piece of hardware to serve as my first driver. I quickly figured out a way to kill 2 birds with one stone.

I've been having some success getting my LCD working, but I've been having some trouble with the IR part of it. It seems that when the IR sensor receives certain signals, it locks up and stops responding. However, it works fine under Windows, so theres some issue in the current linux driver for this device.

The device seemed to be simple enough. I didn't envision a lot of back and forth communication. I envision a one way stream of incoming data for the IR sensor, and another of outgoing data to write to the LCD. The hardware didn't appear to have a lot of conditional states built into it. That meant there probably woulnd't be a lot of tricky reverse engineering such as "this command does X if the device is doing A, but Y if it's doing C, or Z if it's doing C or D" etc. Nope...it appeared it would (hopefully) be fairly straight forward.


Reverse engineering the protocol

If I needed to, I could always try to strip some protocol information out of the existing partially working driver. However, I figured it would be best to start out from scratch (that would be the most rewarding experience for my learning process).

I downloaded a copy the Snoopy program for Windows, hooked the LCD up to my Windows system, installed the drivers, and started monitoring the stream with Snoopy. Snoopy has it's pluses and minuses. It's very simple to use, but it doesn't let you monitor the stream in real time. It records it all to a log file, which you can later look through. The entries are all timestamped (relative to the start of the log), so you can tell what happened and when.

So first thing I did way was start up the log and watch the event counter grow. After a couple seconds, the counter stopped. I waited 10 second and the count remained steady. Great...that means I can look at that bunch and treat it as the initialization code. I then pressed a single button, waited for the event counter to stabilize, and then waited another 10. Then I started doing the same with other things (turn the volume knob, hit the menu key, switch the driver into graphic eq mode, etc).

At the same time I was doing this, I was writing down on paper a list of what actions I took at each step, and what type of feedback I got for each action (text on the LCD changed, an icon blinked, etc). After doing this for about a dozen different actions, I stopped and saved the log file.

Now, all I had to do was go through the list and start picking out some common things. I saw that every event had one pair of command codes in common. Looking at my notes, the one thing that every event had in common was that it blinked a particular icon. I now figured out the first code was to turn the icon on, and the other to turn it off.

Comparing them, they were very similar codes. Almost all zeros, except for the last byte (which was identical), and one of the other bytes had a single bit set in one of the codes but not the other. I quickly figured out that the last byte indicated that we were toggling the icons, and which bits were set in the other bytes indicated which icons to turn on.

I then looked at my written list. One of the other action had toggled a different icon. I suspected that If I looked in the corresponding series of events, I'd find an event where the command code had the same last byte, but a different set of bits turned on in the other bytes. Indeed, there was. I had now figured out how to toggle the icons. All that was left was to determine which bits handle which icons...a trivial task.


Comming Attractions!

Before I got much further in the debugging, I decided I needed to test my theory about the icon command. It was time to start writing a very basic driver. In my next post, I'll cover writing the first bits of driver code and putting my theories about the protocol to the test.



No comments: