Contributed by dlg on from the when-donations-go-wrong dept.
The other week, David Cathcart gave me a supposed ural(4) device (CNet CWD-854) he had bought. While listed as supported in the manual page, this device would not attach properly. Let me explain a little more how USB devices are detected and the correct driver attached.
Of course, we all know how manufacturers love to change the chipsets internally without changing the product name - which leads to a ton of confusion for consumers who are trying to buy something that is going to work. At first I thought it might be a simple matter of adding some new device IDs to make the driver attach to it.
As defined by the USB specification, every USB device must respond to a basic set of generic operations. Among these generic operations are methods to get the vendor ID and the device ID. These IDs are just 16 bit values. You can find them easily for the USB devices plugged into your system by executing usbdevs -v, e.g.:
port 2 addr 2: full speed, power 100 mA, config 1, Biometric Coprocessor(0x2016), STMicroelectronics(0x0483), rev 0.01
If you take a peek at src/sys/dev/usb/usbdevs you will see a list which associates vendor IDs with a C-like constant and a humanly readable vendor name, for example:
vendor ACERSA 0x066e Acer Semiconductor America
Also in this file is another list which associates device IDs with a C-like constant, a humanly readable product name and a vendor, for example:
product RALINK RT2570 0x1706 RT2570
Actual C headers with these constants and values are generated automatically from the usbdevs file when you execute make in the src/sys/dev/usb/ directory. USB device drivers include these auto-generated headers and use the defined constants to determine whether they should attach to a given device. Typically you will see a structure named '<dev>_devs' where <dev> is the name of the device. This structure lists the products supported by the driver and sometimes some extra information such as quirks necessary for the particular device. For example, from the atu(4) USB wifi driver:
{ USB_VENDOR_OQO, USB_PRODUCT_OQO_WIFI01, RadioRFMD2958_SMC, ATU_QUIRK_NO_REMAP | ATU_QUIRK_FW_DELAY },
The driver can loop through this list in its match function - USB_MATCH() for USB devices - and check if it should attach to the given device.
Now, returning to the mysterious ural(4) device. So, I added its IDs to the various files and told the ural(4) driver that it should attach to it, built a new kernel and rebooted. It attached all right but didn't work! There was some error about the radio transceiver being unknown, so - knowing absolutely nothing about the Ralink chipsets - I attempted to hack the driver to support the radio transceiver. However, I realised quickly enough that it wasn't just a matter of a new radio transceiver. In fact this device has a completely different chipset, the RT2501USB. Damien Bergamini (damien@) - who wrote the ral(4) and ural(4) drivers - confirmed this.
At the time of this writing, there is no documentation available for this chipset, however there is a reference driver for Linux. Furthermore, the chip is somewhat similar to the PCI RT2661 - many register addresses are identical for example. A funny aside - these new Ralink chipsets actually have little i8051 micro controllers in them! My first step was taking a look at the reference driver to extract some values such as register addresses etc. Simultaneously, I started to try to figure out exactly how USB drivers worked. From reading the USB specification and existing driver source code (mostly ural(4) and atu(4)) I began to develop an understanding. Communication with a USB device is conducted over a number of "pipes". There are four types of transfers which can be sent over pipes:
- Control Transfers, used for command and status operations.
- Isochronous Transfers, used for periodic, time-relevant information.
- Interrupt Transfers, used for bounded-latency communication.
- Bulk Transfers, used for large data transfers.
In the case of a wifi device, you'd likely have a pair of bulk pipes - one for for sending data and one for receiving data. It's in fact pretty easy to send commands to a USB device. You set a bunch of fields in a usb_device_request_t structure and push it out to the device using 'usbd_do_request()'. So to read or write to a register, you can wrap these requests, for example (writing to the LED control registers of the RT2501USB):
req.bmRequestType = UT_WRITE_VENDOR_DEVICE; req.bRequest = RAL_WRITE_LED; USETW(req.wValue, reg); USETW(req.wIndex, strength); USETW(req.wLength, 0); error = usbd_do_request(sc->sc_udev, &req, NULL);
Thats all there is to it.
At some point, I talked briefly to Jonathan Gray (jsg@) since I was told he was working on a driver for this device. He had also noticed the similarities to the RT2661 in terms of register layout, and sent me a very preliminary diff. His diff was pretty much identical to what I already had, but he'd added some different register values which I was able to use. He also sugested the driver should be separate to the existing ural(4) driver since its a completely different chipset. So right now, I have made some headway with the device driver - it loads and executes the firmware, I can blink the LED and initialise a few other parts like the BBP. I'm taking it piece by piece, reworking code from the existing OpenBSD RT2500USB and RT2661 drivers and filling in the gaps with information gleaned from the reference driver.
Fortunately, OpenBSD has the net80211 framework which implements many tricky parts of the 802.11 protocols in a device-independent way. Hence, it is not necessary for each driver to have its own 802.11 stack. This means our wireless drivers are much smaller and simpler than, say, the corresponding Linux drivers. This is because each Linux wifi driver has to implement its own complete 802.11 stack - the vendor RT2500USB Linux driver is 36,453 lines, OpenBSD's ural(4) is about 10% the size at 3,387 lines!
(Comments are closed)
By Anonymous Coward (87.78.89.160) on
Comments
By Anonymous Coward (70.31.84.121) on
Comments
By Anonymous Coward (128.171.90.200) on
By Noah (12.168.235.2) on
Comments
By Anonymous Coward (69.70.207.240) on
Comments
By Anonymous Coward (24.84.108.32) on
By Mike (71.240.76.4) on
req.bRequest = RAL_WRITE_LED;
USETW(req.wValue, reg);
USETW(req.wIndex, strength);
USETW(req.wLength, 0);
error = usbd_do_request(sc->sc_udev, &req, NULL);
One thing I don't fully understand is what USETW() does. Can anybody offer a brief explanation of what that function call does?
Thanks
Comments
By Anonymous Coward (62.252.32.11) on
By David Gwynne (130.102.78.195) loki@animata.net on
Comments
By Miod Vallat (82.195.186.220) miod@ on
By Dunceor (192.16.134.66) on
I highly doubt that there is a operating system that keep such close contact and openly information about the development such as OpenBSD.
By Anonymous Coward (24.34.57.27) on
By Chas (147.154.235.52) on
You went from zero to USB guru in how long?
If I tried that, my head would explode.
By Fábio Olivé Leite (15.227.249.72) on
Now if someone would post some material about pci drivers, I might start sooner that pet project to make the BCM4318 WiFi in my notebook work on OpenBSD... there's some info available on http://bcm-specs.sipsolutions.net/ and some prototype linux drivers as a last resource. :)
Again, thanks to all devels that are blogging.
By Roman (169.200.215.36) on
LOL What's wrong with those people?
Comments
By Anonymous Coward (128.171.90.200) on
By tedu (69.12.168.114) on
By Anonymous Coward (213.224.7.60) on
The rt2500 driver is not integrated in the kernel.org sources. One of the reasons, is exactly that the rt2500 driver does not use the standard 80211 stack included in the kernel, but uses its own implementation. So, Linux developers are not happy with this situation either. The Linux kernel itself is not to blame that some external driver developers decided to reinvent the wheel. Nevertheless, it's indeed a sad situation. Thanks to OpenBSD's wireless efforts, the situation in OpenBSD is a lot better.