MagTek USB Card Scanner Driver

Authors: Patrick Tully and Steven Bronson

Downloads


Source Code
Presentation

Technical Documentation


Setup

Run "sudo ./install.sh". This will compile the module, create a file on modprobe.d so that usbhid ignores the card swipe device, reloads usbhid, and inserts cardscanner. To remove run "sudo ./uninstall.sh"

Functionality

This device driver manages a MagTek USB card reader and allows userspace program to easily get swiped card info. It creates a file in /dev called cardscanner which can be read to get this information. The file is always nonblocking and so a read called on the file when there is no information available will return 0. Each card swipe is separated by a newline character when read in from the cardscanner file.

Motivation


In the Majors Lab, we scan people's FSU cards to track lab usage. The card scanner presents itself as USB keyboard, so each card swipe generates a sequence of characters corresponding to pressing those keys on a keyboard. Unfortunately, this means that to capture those characters, there must be a program with keyboard focus at all times to receive them. Ideally, the program could receive those characters without keyboard focus, and the lab monitor could use the regular keyboard to do other work without his keystrokes being logged as card swipes. Therefore, we chose to write a keyboard driver that would create an entry in /dev that a program can open and read from. It doesn't support anything except open, read, and close, so even cat can drive the interface.

Related Work


There are two very closely related USB card scanners produced by MagTek. The Systems Group purchased the one that acts as a USB keyboard, but there is another that acts as just a USB HID. Some Internet guides show how to use /dev/usb/hiddev to read from the USB HID version, but because ours is a keyboard that file isn't created. (Even if it were, it still sprays output all over the window with focus, and it's not clear that the output would be duplicated, one copy to each.) Other Internet guides show how to write a userspace program that replaces the usbhid driver with their custom driver for the specific device at run time. This is nice, except that the replacement process requires root privileges, so the userspace program must run as root (or at least you must run that preface as root each time). It also depends on a deprecated kernel interface, usbfs.

Challenges


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.