In the process of building my latest mythbox, I've been doing a lot of adhoc experiments with a various things over the last few weeks. I thought it was time I start putting some of them together to start moving towards a working system. One of those things I needed to do was get all 3 of my lirc devices working together. The process turned out to be a bit trickier than I expected, but I got it done, and thought I'd share the details of a couple of the trickier spots
Why three lirc devices?
I wish I could say I only needs one device, but unfortunately that's not the case. First of all, my new Thermaltake DH-101 case has a set of front panel buttons, and the driver handles those buttons by treating them like IR codes and sending them through LIRC. I certainly would like those buttons to work, so that's one device.
In addition, as I discovered in my driver writing experiment, the iMON (which handles the front panel buttons) will block the input queue if it receives certain IR signals. The only way to unblock it is to remove those signal events from the queue, and the way that gets done is by exposing a second lirc interface. So, I need to have that interface working if I want my front panel buttons to continue working.
Finally, since the iMON can only receive a very limited subset of IR signals, it is pretty much worthless as a general purpose IR receiver. Therefore, I have a separate receiver...the IRA-3 (which appears to lirc as an Irman).
Compiling LIRC to support both drivers
The 2 iMON interfaces use the lirc_imon kernel driver (via the lirc default driver), while the IRA-3 uses the irman driver. Therefor, I need to get both drivers installed. However, thats one are where lirc is a bit troublesome.
The suggested approach is to configure/make/install lirc once for each individual driver, and then configure again with the --with-driver=all flag. This will configure lirc to make and install every supported lirc driver available. That's not exactly ideal, but it works. However, when I tried that, I got errors during the build complaining the the necessary bttv libraries were not installed and it couldn't continue. Now, it seemed silly to have to install dependancies for drivers I never even planned to use, so I was determined to find a better way.
I tried every way I could think of to run configure: with individual --with-driver parameters (one for each driver), as well as using a single flag and specifying both drivers (space separated, comma separated, quoted, etc). Nothing I could come up with would work. After searching and finding no resolution, I started trying to hack the configure script (which got me nowhere). However, I eventually found a trick that worked...hack the generated makefile.
The trick here was to configure it with --with-driver=all, and then edit the drivers/Makefile script. In here, there are 3 parameters to be edited. Each parameter contains a list of all the lirc drivers. You want to remove any drivers from that list you aren't interested in. The three parameters in that file you want to edit are: lirc_driver, DIST_SUBDIRS, and SUBDIRS. Once you've modified those lines to remove the extra drivers, just run the make and make install.
-----------------------------------------------
Edit: Well, a few days later, I get to work trying to setup my lcd display and realize it's not working. It seems that compiling lirc for all drivers makes it pick and choose between some mutually exclusive settings. One of those setting was whether the lirc_imon driver is built for the LCD or the VFD. It picks the VFD. In order to fix this for the iMON LCD, after running configure you need to edit the config.h file (in addition to the drivers/Makefile). Change config.h so that the line:
/* #undef LIRC_IMON_LCD */
becomes:
#define LIRC_IMON_LCD 1
Then continue on with the make and install. You may find a similar issue occurs with some of the other hardware.
-----------------------------------------------
Getting all 3 devices working
Now that I had lirc installed with all the drivers, I needed to get the 3 lirc devices functions. First steps was to test them out individually using irw and then merge them together. Unfortunately, I had an odd bug which I was able to resolve, but can't explain. My lircd.conf file has definitions for 4 remotes (my hauppauge remote, my wife's tivo remote, the iMON Pad's IR codes, and the case's front panel butons). For some reason, the Tivo/Hauppauge remotes would work no matter which order they were in, but the, but the other 2 wouldn't work with the TiVo and hauppage remotes defined first. I had to switch the order to get all 4 to work.
As far as I can tell, they are all valid definitions, and they all work. I was able to pinpoint the problem more specifically. If the earlier definition had the post_data_bits attribute definited, the iMON and front panel button definitions failed to work. Commenting it out fixed the problem with the lower definition (though it broke that one). Curiously enough, each of the 4 definitions has a post_data_bits attribute set, yet they all work fine when in the correct order. I can't seem to make sense of it.
Getting them working together
Now that I had each one working individually, I needed to get them working together through a single interface. I found documentation specifying how to use the listen and connect parameters to connect multiple lircd instances together. I tested, and that worked well enough for 2. However, as soon as I added the third, only one of the 3 would work.
It turns out that lircd has its client/server model a bit different that what I'd expect and would believe is the normal way to do it. Normally, when you have multiple processes connected together by sockets, you have one server that listens on the port and then multiple clients that connect to the one socket. And indeed, if you attempt this with lirc, it works perfectly. The one server will happily establish connections with multiple clients, but it won't actually do anything.
Instead, what you need to do is have all but 1 instance of lircd running as a server (ie: listening), and then have a single client connect to all of the servers, aggregate their data, and output it all on the single interface. Once I figured that out, it worked like a charm.
Just for reference, in case you'd like to see the actual commands I used:
lircd --driver=irman --device=/dev/lirc --pidfile=/var/run/lirc.pid --listen=9988
lircd --driver=default --device=/dev/lirc1 --pidfile=/var/run/lirc1.pid --listen=9987
lircd --driver=default --device=/dev/lirc0 --output=/dev/lircd --pidfile=/var/run/lirc0.pid --connect=localhost:9988 --connect=localhost:9987
...click here to read more!
Wednesday, April 2, 2008
Setting up LIRC with multiple devices
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.
...click here to read more!
