One design issue we encountered was whether to produce the swipes as a byte stream separated by newlines (they cannot appear in the data) or as a records, forcing each read to return a full line of input. While the record interface is a superior interface in isolation, since it makes the common case of reading a record at a time simple and the less common case of reading across records no more difficult than separating records in a byte stream API, it is not the better interface on a Unix system, where most tools and libraries are designed to work with byte streams instead of records. In addition, it's unclear how to make reading records amenable to the read(2) API specified by POSIX. We could make all reads less than LINE_LENGTH return 0, but that would be at best disingenuous or even flatly disallowed by POSIX. We could allow reads less than LINE_LENGTH, but then what happens if they try to read across a record separator? If we don't permit that, we should return 0 or signal an error, but 0 seems wrong for the same POSIXly reasons as before, and no error seems to fit. If we do permit that, then this is indistinguishable from a byte stream API. A final alternative is to use system call besides read to obtain the data, e.g. an ioctl. It would be unexpected to use an ioctl for something clearly related to reading, and we felt that writing a driver that exposed socket operations like recv would be strange, so we did neither. We went with the byte stream API with usual semantics for read.
To implement a byte stream API, we chose to use a circular buffer to hold the characters read from card swipes. If the buffer fills, newly read characters are dropped (as opposed to overwriting the oldest characters). Supposedly the kernel has an implementation of circular buffers, but, in characteristic kernel style, it doesn't provide most of the useful operations one would expect from an implementation of a circular buffer. For example, it doesn't provide insert (enqueue), remove (dequeue), or clear, and while it provides a struct declaration to represent a circular buffer, none of the few macros it provides accept that struct as an argument. Instead, you must manually destructure it into the input arguments. This is a good example of where the kernel and its developers would benefit from having complete implementations of data structures; inserting into the circular buffer will always take time negligible compared to the time it takes for the kernel to allocate and prepare the URBs and communicate with the hardware, so it just wastes development time, run time (our implementation is mostly unoptimized because it's not worth it for us), and kernel stability having us write our own data structures.
In order to develop a driver for the card scanner, we obviously needed to make our driver responsible for the device. Because the card scanner presents itself as a USB keyboard, and usbhid was already loaded because we had a real USB keyboard and mouse attached, usbhid would always grab the device first whether or not our new driver was loaded. The USB probe order is based on device insertion order instead of establishing a partial order on the specificity of probe pattern (usb_device_id), which is silly. At first we worked around this by removing usbhid, inserting our module, and then inserting usbhid so that our module would get a chance to accept the probe to the device. Though this worked, it sometimes caused trouble because removing usbhid disables the keyboard and mouse if they are USB, so if reinserting usbhid fails for some reason (e.g. mistyped shell command) you have to ssh in from somewhere else to fix it. Later, we found on the kernel mailing list that usbhid has a static array of product ID and vendor ID pairs that it will ignore. Unfortunately, the only way to modify that is to change the source and recompile the module (or the kernel!). Shortly before the deadline, we discovered that we could hack this with USB quirks. USB quirks is an interface for informing USB drivers about specific ways in which certain USB devices deviate from the standard or are outright broken. Conveniently, one of the quirks you can specify flatly tells the driver to ignore the device completely, which is what we needed. If usbhid is compiled as module, these quirks can be put in a file in modprobe.d/ ; otherwise they can be specified on the kernel boot line.