Materials from my prototypes have arrived for the most part. I am still waiting for the PCBs, but, since I am not exactly the type of person that would sit idle in the meantime, I decided to torture my mind with a set of optimizations for the kernel driver and other hacks.
The first of these optimizations — and the one I am to discuss in this post, hence its title — is also the only one that I have started implementing so far and relates to serial numbers. To start the discussion, I might have to remind readers that USB, among the other device descriptors (device string, manufacturer string, etc.) supports a “serial number” string descriptor. But wait a moment, why would a serial number be needed in the first place?
The most important use of serial numbers that I was thinking about is called channel number persistence. As discussed in my older post Hello, Asterisk!, the channel number that the dahdi core gives to a board depends on the order in which this board is plugged into the system. Moreover, all other characteristics of a device in Dahdi and Asterisk, like e.g. the local extension number, the echo canceller setup, etc., depend on the channel number. If one plugs two boards in the opposite order, one gets the extensions and all other settings mixed up. Given that I am experimenting with two or more boards which I plug and unplug all the time, this behavior is not exactly what I wanted.
Because of that, I was thinking about this channel number thing. What this would mean is that I could add a module parameter that my kernel driver would understand, something e.g. along this example:
insmod oufxs.ko persist=<serial1>,<serial2>,<serial3>
and this would have the effect that my driver would pre-allocate some dahdi channels (three, in the above example) and map each of them to the appropriate serial number using a field in the respective device structure. The driver could arrange at startup to mark these pre-allocated channels as inoperative and return e.g. -ENXIO or something similar to any caller for all operations – until a board with an expected serial number is plugged in.
Then, at device plug-in time, the driver would look through its internal list of devices and try to match the serial number of the board being plugged in onto an existing idle channel. (I have underlined idle here, because there is no guarantee that two USB devices will not present the same serial number to the host, and if a check for an idle channel is not made, this would result in the newly-plugged board hijacking an existing channel belonging to another board). If a match is found, then the driver would associate the respective idle channel with the new board, otherwise it would allocate a new internal structure as it does now.
To implement that, I first needed to implement serial numbers in firmware. The default USB stack provided by Microchip does not include a serial number string. To begin with, I was not sure whether the standard defines what format a serial number should be in, but after looking around, I found several examples in which the serial was a (text) string of hex digits, so I decided to stick with that.
The next decision had to do with how to program the serial number. All other device descriptors in Michrochip’s stack are stored in program flash memory. By means of a few additional lines of code, I was able to configure the famous “dead beef” hex string as a serial number and get the host to see that. Good, that proved that I could use the program flash as source for the serial number, but was this what I really needed?
The answer was “no”: if I chose this solution, I would have to manually program the serial number along with the firmware. In other words, I would have to produce individualized firmware images for each board. Not only that, but I would have to somehow remember the serial number of a board and restore that after a firmware upgrade (and firmware upgrades in my boards are pretty frequent as you may imagine).
Fortunately, the PIC has a good answer for that problem. It contains a program-controllable EEPROM memory, where data can be stored. After some googling, I found this hack which gave me enough hints on how to use the EEPROM. My first step then was to move the device descriptor into RAM, still initializing it from program flash as “dead beef”, and make sure that this worked OK. Second step, I tested the EEPROM read functions and got back factory-set data (a series of 0xEE’s).
Then, all I had to do was write a USB primitive for “burning” a serial number on the EEPROM and craft a couple of modifications in my driver. The driver would check for a serial number, and if it found the factory-set string 0xEEEEEEEE, it would use the “burn” primitive to write a new, should-be-unique, serial number in the EEPROM. I coded that, using the current value of the jiffies variable as a source for unique serials, plugged a board and — voilà, the driver reported that a new serial was burnt into the board. Re-plugged the board and, that’s it, the new serial was recognized.
I had only forgotten a minor detail, that caused me a kernel ”oops”: old-firmware boards did not report any serials. In this case, the kernel initializes the respective field of the device’s usb structure with a NULL pointer, and I was comparing that NULL pointer against “EEEEEEEE”, resulting in a zero-address dereference. Fixed that, by adding another message informing that this was an old board and the firmware had to be upgraded. Tested again, and everything worked fine.
Here are some dmesg excerpts I know you will like. At first, a new device with old firmware gets plugged:
Jun 2 14:48:09 avarvit-d NetworkManager: <debug> [1275479289.719625] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_4d8_fcf1_noserial_usbraw'). Jun 2 14:48:10 avarvit-d kernel: [ 735.306908] oufxs: oufxs_setup: oufxs1: old-version firmware not reporting serial Jun 2 14:48:10 avarvit-d kernel: [ 735.306921] oufxs: oufxs_setup: please upgrade firmware on board and replug
Then, the firmware is upgraded to report a serial number (and the “burn” operation) and re-plugged. The driver sees that this is a fresh-from-factory EEPROM, and burns a new serial:
'/org/freedesktop/Hal/devices/usb_device_4d8_fcf1_EEEEEEEE_usbraw'). Jun 2 14:50:19 avarvit-d kernel: [ 864.196194] oufxs: oufxs_setup: oufxs1: no serial on device's eeprom, burning one Jun 2 14:50:19 avarvit-d kernel: [ 864.211314] oufxs: oufxs_setup: oufxs1: serial written OK, re-plug to activate new serial
Finally, when the board is removed and plugged back again, the new serial is recognized by the Linux USB core:
Jun 2 14:51:01 avarvit-d NetworkManager: <debug> [1275479461.618913] nm_hal_device_added(): New device added (hal udi is '/org/freedesktop/Hal/devices/usb_device_4d8_fcf1_6100E926_usbraw').
So now, I am ready to do some serial — sorry, serious — programming on the driver code in order to implement channel persistence. I don’t know if I am going to do this right now, because of other priorities (some tests that I will mention in my next post, and prototype assembly). But it is a feature that I think is needed, and I will add it ASAP.