I’ve written and tested my first non-trivial Linux kernel module, which directly accesses VGA registers to track the beginning of each vertical blanking interval. It works!

Synchronizing with the vertical blanking interval is important in a variety of applications that draw pictures on a computer screen. Unsynchronized drawing results in a “tearing” effect, where the bottom part of an image becomes visible first, followed 15 to 20 milliseconds later by the top portion. Even if you don’t consciously notice the effect, your brain is probably processing it at some level. At work, I need to avoid this tearing effect because we’re making psychological testing software: anything that interferes with how your brain processes our test makes the results less accurate.

Of course, Linux already has support for this kind of synchronization, but it’s limited to hardware from a few vendors. Our software needs to be able to run on as wide a range of hardware as possible, because we sell it to doctors, who not only don’t tend to know anything about their computers, but also don’t want to have to know. We can’t tell them, “You need to buy and install a new video card.” So my module is written to work with IBM PC-compatible video cards from 1987 to today.

If you don’t want to know the really technical details, stop reading now. (I try to write for mixed audiences so my family and friends can all read my blog posts…)

I wrote a prototype implementation of this module a couple of weeks ago that programmed the VGA registers to turn on an interrupt when vblank occurs, but it turns out that many modern video cards don’t implement that particular feature of the original VGA specification. (My laptop’s Radeon 9600 has it, but not two other machines I tested on.) So this new version of the module polls a bit of the “input status 1” register instead. Of course, pure polling would waste a ton of CPU time, electricity, etc., and keep any other work from getting done. So I used the high-resolution timer API introduced in Linux 2.6.16.

First, measure the time from the start of one vertical blanking interval to the start of the next, using pure polling. Then set up a high-resolution timer to fire at the measured rate. If the timer fires in the middle of a blanking interval, make it fire earlier the next time. If it fires before the start of the interval, poll until the interval starts. If you had to poll for a “long” time, consider firing the timer a bit later the next time. If you had to poll for a “really long” time, probably there was heavy system load and you just missed an entire interval.

I’ve GPL-licensed the module and stuck it in a git repository, but haven’t published the repository yet. In the event that I never get it published, I expect others could reconstruct my work from the above description. :-) Hopefully I can figure out where this kind of code belongs in a mainline kernel and get a patch submitted in the near future though.