OpenBSD Journal

Developer Blog: niallo: USB device driver hacking

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)


Comments
  1. By Anonymous Coward (87.78.89.160) on

    nice! thx! more!

    Comments
    1. By Anonymous Coward (70.31.84.121) on

      Ditto. I love reading this stuff, it's fantastically informative to those who otherwise have no clue what's going on down in kernel land.

      Comments
      1. By Anonymous Coward (128.171.90.200) on

        It's especially nice to know whats happening currently with OpenBSD without having to check the CVS commits and the changelog

  2. By Noah (12.168.235.2) on

    Word to yo mama. These blogs are great. Keep em coming. Please!

    Comments
    1. By Anonymous Coward (69.70.207.240) on

      What'chu talkin'bout willis!

      Comments
      1. By Anonymous Coward (24.84.108.32) on

        Pardon me, male sibling, but would you mind slipping me some epidermis?

  3. By Mike (71.240.76.4) on

    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);


    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
    1. By Anonymous Coward (62.252.32.11) on

      I might be wrong here -- and I probably am -- but the "w" part makes me think this call prepares the commands to be put into the chip's working (w) register when the struct is shoved into it?

    2. By David Gwynne (130.102.78.195) loki@animata.net on

      USETW is a macro. It's name is short for USB Set Word and it is used to do the right byteswapping of host values to something USB devices like.

      Comments
      1. By Miod Vallat (82.195.186.220) miod@ on

        Don't forget the UFIXIT macro which is used everywhere to make rogue USB devices behave.

  4. By Dunceor (192.16.134.66) on

    This is great information for people that want to start hack on the kernel but don't really know where to start.
    I highly doubt that there is a operating system that keep such close contact and openly information about the development such as OpenBSD.

  5. By Anonymous Coward (24.34.57.27) on

    I know I'm not the only one that read that as a urinal(4) device the first time.

  6. 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.

  7. By Fábio Olivé Leite (15.227.249.72) on

    This is Insanely Great! Thanks so much for all these blogs. Posting introductory blogs like this one surely helps bridge the gap between an "experienced user" and a "lightweight contributor".

    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.

  8. By Roman (169.200.215.36) on

    "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!"


    LOL What's wrong with those people?

    Comments
    1. By Anonymous Coward (128.171.90.200) on

      They like to re-invent the wheel

    2. By tedu (69.12.168.114) on

      iirc, there's several 802.11 stacks for linux, but instead of picking one to be the winner, they decided all of them get to be the loser.

    3. By Anonymous Coward (213.224.7.60) on

      Djeez, get your facts right instead of trolling, people.

      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.

Credits

Copyright © - Daniel Hartmeier. All rights reserved. Articles and comments are copyright their respective authors, submission implies license to publish on this web site. Contents of the archive prior to as well as images and HTML templates were copied from the fabulous original deadly.org with Jose's and Jim's kind permission. This journal runs as CGI with httpd(8) on OpenBSD, the source code is BSD licensed. undeadly \Un*dead"ly\, a. Not subject to death; immortal. [Obs.]