Tutorial on USB with Linux

Background of Work

This tutorial is a result of an independant study I made with the original intention of porting USB to RTLinux (a real time operating system)

Who should read this?

  • If you have no knowledge of any of the aspects of USB whatsoever, and are curious to know as to what the hype surrounding it is about, this would provide a reasonable starting point.
  • If you have some ideas of working with USB on the application layer (as using a client library), but would like to learn about the internals this is worth reading
  • If you are familiar with the workings of USB, but have not worked with it under Linux, this tutorial would be useful.
  • If you are not too interested in USB, but are interested in virtual filesystems under Linux, this may be worthwhile
  • If you are accomplished in using the USB stack and using it under Linux, then please read on and give me some feedback on what you think of the page!
All in all, if you'd rather not yet read the numerous other voluminous, exhaustive documentation; if you want to read something about USB which is quick, short and relatively straighforward, just to give a feel for all that there is and hopefully get you going, this would be something for you.

Disclaimer

In spite of the fact that I have made every effort to ensure that the content of the tutorial is correct, I make no guarantees regarding the same.

Overview of the tutorial

In this tutorial I present a series of topics of interest related to USB and its implementation in Linux. The topics are dealt with in varying degrees of detail, due to both their relevance to main issue, and due to varying degrees to which I (had to) understand them myself.

I start by giving an brief introduction about USB from the enduser stand point. Then, I delve into some of the technicalities and specifications of the protocol - all of which is operating system independant. I then talk about what it takes to port USB to a particular operating system. I then talk about how Linux goes about things and to some extent why it does so.

Here goes.....

Its quite likely that you have worked with a USB device before, for maybe linking up your camera to download pictures, or maybe to even hook up your mouse/keyboard. But USB for many of us is a fancy new technology, which seems to do provide lot of features - its fast (you pictures download much faster from your camera on the USB link and compared to say a serial link), does not seem to have those annoying driver problems - with minimal fuss - all that there is, is a small connector which fits snugly into the slot at the back of your machine.

To understand USB better it may be instructive to understand the acronym - which stands for Universal Serial Bus. The words Serial and Bus are crucial.

  • Bus reminds you that though we seem to use USB for data transfer between a computer and an external device much like a serial cable, it is infact much more than that. To understand the difference, consider what's required to get a serial connection to work on a computer. All that we need to have is a driver for the serial port installed and that would make our serial port functional. It is not our concern as to what is hooked up at the other end of the serial cable. With the driver installed, the only other utility that maybe required is a serial client application like Hyperterm (if you are using Windows) or Minicom (if you running some flavour of Unix/Linux). The point is that you are only concerned about our end of things. But USB is a bus like say a PCI bus. Since, the bus is part of the computer's datapath, it follows that all devices connected the USB cable, by virtue of being 'on the bus', are part of the computer in some sense. So, apart from being interested in just putting the bits on the wire - as was the case with the serial link - now we are also concerned at some level on what is on the other end wire.
  • Serial tells us that data transmission on the wire is serial (as opposed to parallel)- one bit at a time

In spite of the fact that most USB connections seem to run from computer to device (point to point) this hardly needs to be the case. The layout is really a tree rather than a link. Every node in the tree is represents a hub. The 'root hub' is present inside the computer and the two USB sockets present on the computer may infact be two nodes connected to the root hub. Thus a compound USB device which consists of a keyboard and the mouse with one USB link going to the host may be represented as folows.

                              -------------------------------
                         |                 Keyboard     |
                         |                /             |
                         |               /              |
                         |              /               |
        Computer ----------Internal Hub                 |
                         |              /               |
                         |               /              |
                         |                /             |
                         |                Mouse         |
                         --------------------------------

where the boxed region represents the device. The internal hub is invisible to the user
This raises a couple of points. First, obviously the computer needs to be aware and able to address either of the devices. This is done by way of assigning device addresses to each of the devices on the bus. Also, inspite of the fact that the function and the location of the internal hub is different from the devices, as far as the computer is concerned, it treats the hub in the very same way as it treats the other devices.
USB was designed having in mind devices with multiple capabilitites. With this being the case, the host needs to be aware of these multiple capabilities that a particular device may have. This is acheived by describing a device in terms of heirarchical structure. A device may have multiple configurations, each of which may in turn have multiple interfaces, which in turn may have multiple endpoints. The interfaces also have classes (and sub-classes) associated with them. Common classes include those for human interface devices such as keyboard, mice etc. and those for storage devices such as hard disks. See references for links to more detailed descriptions regarding USB heirarchy structures. This allows a device to present multiple interfaces/configurations for different purposes.

All transfers to USB devices fall into one of four classes, namely: control, isochronous, bulk and interrupt, with the names being fairly descriptive of the purpose of each of the transfers. Control and interrupt transfers are used for sending small messages across , such as for setting a configuration of a device or sending small sets of data across. Bulk and isochronous transfers are for sending larger sets of data across. While isochronous (as the name suggests) gives some time guarantees with regard to the transfers, bulk transfers are highly flexible in their transfer mechanisms. Every endpoint that a device may posses has a specific transfer type associated with itself. Hence a device which wants to make transfers of multiple types, must have one corresponding endpoint for each of the types. A more rigorous discussion on the configurations/iterfaces and on the different types of transfers is beyond the scope of this tutorial. Please lookup the references for more information.

The host learns of the various devices on the bus through a process known as enumeration. All communication between the host and a device takes place through pipes. The device is controlled using pipe 0, which is the standard control pipe for any device. The host can ask the device to enuremate itself using the control pipe, on which device sends back a whole lot of information back to the host including - its product and vendor id, information regarding configurations, interfaces and the different endpoints that it has. Apart from enumeration, the host also has other responsibilites. For example, it may be noted that the USB link is a resource that is shared across all the devices. This means that contention can result in terms of more than one device wanting to send data at the same time (as is the case with ethernet). USB resolves this issue by following a master-slave protocol. The host is the master and controls the bus, and all the devices attached to the bus are slaves. Any device can transfer on the bus only at the request of the master

The host-controller is the component inside the host which is responsible for implementing all host-aspects of USB. In some sense it is analogous to an ethernet NIC card, but much more complicated. The host-controller is protocol-aware and identifies different transfer types (control/bulk etc.) Note that all information described thus far is operating system independant. Any operating system will have to provide some means for the client applications to open pipes to particular endpoints of devices, make transfers of desired types etc. Also, typically on booting up, the operating would enumerate all the devices on the bus to get an idea of the layout of the entire bus.
The actual layout of USB on the host is as follows:

      |-------------------------|

          Client Application

       -------------------------

      USB System software (USB Driver)

      --------------------------

        Host Controller Software

      --------------------------

             Hardware

      |-------------------------|

As the figure shows, USB has a layered structure on the host side. Right at the bottom is the actual hardware which throws signals on the wire and most importantly consists of the host controller.

Rest of the tutorial deals with USB as related to Linux.
First we will look at what it takes to get a USB device to work under Linux Let us consider a typical scenario of a USB device being used in Linux. Whenever a new device is plugged, the host controller detects the new device and conveys the information to the operating system. As soon as the operating system learns of the new device, the device is enumerated. This enumeration takes place by way of the operating system issuing commands to the host controller, more of which is presented later. Note that all devices on the bus are also enumerated at boot time. Based on the information that the host learns at the time of enumeration, the operating system assigns a driver to the device. Of course, it is possible that none of the known drivers match. A device driver for a USB device implements all the functions implemented in a usual device driver, namely open, read, write, ioctl etc. Among these ioctl() performs a specially important role. It is easy to should be noted that the operation of say writing to a USB device has several attributes associated with it such as the type of transfer. It would be convinient to implement a write inside an ioctl so that the necessary attributes may be passed to the ioctl call as paramaters.
An example ioctl call may be as follows:

  ret = ioctl(dev->fd, IOCTL_USB_BULK, &bulk);
This makes a bulk write by calling ioctl function associated with the file of USB device dev. The second parameter IOCTL_USB_BULK identifies the call as a bulk write, with the structure bulk containing the actual data to be written.
In the previous example, it was mentioned that Linux compares the device description that it gets back at the time of enumeration, to check for any suitable drivers. This means that the operating system needs to keep track of among other things - all attached devices and all registered drivers. LInux accomplishes this by way of using the USB filesystem of USBFS (also known as USB Dev FS). USBFS is a virtual filesystem like the /proc filesystem.

Modern versions of the Linux kernel use one central filesystem, known as the Virtual Filesystem. All 'real' filesystems register themselves with the VFS, where the filesystem registering itself specifies the 'driver' that the VFS can use to talk to itself. This requires the filesystem to implement a particular interface that the VFS can use to communicate with it. Once a particular filesystem has been registered, directories can be mounted of the different filesystem types in various places inside the VFS. For example, ext2 and dos may register themselves and have respective directories mounted at / and /dos.
One of the motivations for using the VFS is to accomodate the inter-operability of different types of filesystems. The other advantage of using the VFS is being able to accomodate virtual filesystem i.e filesystems which have no physical existence. As we saw earlier, the only requirement on part of a filesystem to register itself with the VFS is for it to implement the necessary interface with which the VFS can interact with it. The proc filesystem for example - mounted at /proc - presents a directory structure containing a whole lot of system information concerning memory, system buses, processor etc. None of this information is physically stored in any particular file on disk. When the VFS acesses any of the files in the /proc directory, the /proc filesystem generates all this information realtime. This makes the implementation of the virtual filesystem completely transparent to the end user and indeed even to the VFS. Another example of a virtual filesystem is devfs.
Having seen the idea behind a virtual filesystem, it is straightforward to see the motivation behind using USBFS and making it a virtual filesystem. USBFS tries to make all attached USB devices seem like traditional files (in line with the UNIX philosophy). It dynamically keeps track of devices which are attached to or removed from the bus .Compare this to the old unix philosophy of using nodes inside /dev. This suffered from the fact that there never had to be any correspondance between an entry in /dev and a physical device on the machine. Often huge number of nodes are present in /dev with node entries being created for every conveivable scenario, but rarely ever used. USBFS being dynamically updated, does not have this problem.
Apart from the aspect of providing a cleaner filesystem, USBFS also makes drivers aspect of things much more transparent. all USB devices have the same major number, making the use of them to determine the drivers impractical. Instead, the filesystem decides the association based on the description the device returns on enumeration and the list of registered drivers. Apart from this aspect, USB drivers work in the same way as conventional drivers, with functions being defined for open,read,write ,ioctl etc.

The usb filesystem is mounted traditionally at /proc/bus/usb. Inside this directory there is one entry for each bus. On my machine, the contents of the directory reads as:

/proc/bus/usb$ ls -l

total 0

dr-xr-xr-x 1 root root 0 Oct 18 16:30 001

dr-xr-xr-x 1 root root 0 Oct 18 16:30 002

-r--r--r-- 1 root root 0 Oct 29 12:10 devices

-r--r--r-- 1 root root 0 Oct 29 12:10 drivers

showing that there are two busses (designated by 001 and 002). The directories corresponding to the hubs have one file per device connected directly or indirectly to the hub. My first hub has one device connected to it. So its directory contents read:

/proc/bus/usb/001$ ls -l

total 1

-rw-r--r-- 1 root root 18 Oct 29 12:11 001

-rw-r--r-- 1 root root 18 Oct 29 12:11 003

The 001 device is always present and it represents the root hub.

Note that 003 is not characterized as a hardware device file(such as a char or a block) as is the case with say the serial port. But the file is opened, the VFS forwards this request to USBFS. The USBFS then refers to its list of registered drivers and picks the appropriate driver to complete the open operation.

Apart from the usual functions defined in a driver, those for USB need to have another function named probe defined. Once a device is connected to the USB bus, USBFS executes this probe method in each of the drivers, to decide on the driver corresponding to the device. The signature of the probe method is as follows:

static void *probe(struct usb_device *dev, unsigned int i,

                   const struct usb_device_id *id)

Based on the information retreived from the device, the driver determines as to whether it is compatible with the device,and accordingly returns a pointer to a driver-specific value or NULL. For example, a driver for a hub may check to see whether the interface subclass has the value 0, and also whether the device has one single interrupt endpoint. Devices typically use the product id, vendor id and class/sub-class values while probing the device.

Next, we look at how data can be transferred on the bus. To transfer data to or from a device, first a handle needs to be obtained to the device. libusb is a user-level library that may be used to implement client-side functionality for USB on Linux. libusb requires that the function namely usb_init() be called before any other function is called. To get to access the various devices on the bus, two functions may be called in sequence: usb_find_busses(), usb_find_devices() . Calling these functions leads to initialization of a global link list names bus (of type usb_bus). The number of elements in this link link is equal to the number of busses on the system. The structure usb_type has a field named devices , which in itself is a linked list of all devices (of type usb_device)on the bus. A brief description on the internals of the functions follows. A more comprehensive explanation is beyond the scope of the tutorial. As we saw earlier, each device on the bus is represented as a file in the usb filesystem (at /proc/bus/usb/001 directory). This file really contains the device descriptor for the associated device. For example, the following line of code initializes the descriptor for the pointer to structure dev, by reading the data off the file.

ret = read(fd, (void *)&dev->descriptor, sizeof(dev->descriptor));

where fd is a file descriptor corresponding to the device file inside the usb filesystem. All that the two functions do, is to iterate through the two directories corresponding to the two busses on the system, namely /proc/bus/usb/001 and /proc/bus/usb/002. For each file inside the directory, which is representative of a device on the bus, the file descriptor is read in the manner mentioned above.
After calling the two functions (usb_find_busses() and usb_find_devices()), to get a handle to the required device, all that needs to be done is iteration along the linked list of the devices on the file, to get access to each of the devices. On reaching the desired device, a handle to the device may be obtained by calling the usb_open() method, passing a pointer to the usb_device structure as a parameter.
Once the handle has been obtained, one may use the whole lot of libusb functions on the handle. The range of functions broadly classify into two classes:
  • those for configuring the device: usb_set_configuration, usb_claim_interface
  • those for doing data transfer: usb_bulk_write,usb_bulk_read,usb_control_msg

For more information on the API provided by libusb, lookup references.
Here is an example using libusb

 

/* Function to read and write the XSV300 board USB port */

#include <stdio.h>
#include <usb.h>

usb_dev_handle *locate_xsv(void);

int main (int argc,char **argv)
{
struct usb_dev_handle *xsv_handle;
struct usb_device *xsv_device;
int send_status;
int open_status;
unsigned char send_data=0xff;
unsigned char receive_data=0;

usb_init();
usb_set_debug(2);
if ((xsv_handle = locate_xsv())==0)
{
printf("Could not open the XSV device/n");
return (-1);


open_status = usb_set_configuration(xsv_handle,1);
printf("conf_stat=%d/n",open_status);

open_status = usb_claim_interface(xsv_handle,0);
printf("claim_stat=%d/n",open_status);

open_status = usb_set_altinterface(xsv_handle,0);
printf("alt_stat=%d/n",open_status);

send_status=usb_bulk_write(xsv_handle,4,&send_data,1,500);
printf("TX stat=%d/n",send_status);

usb_bulk_read(xsv_handle,3,&receive_data,1,500);
printf("RX stat=%d -> RX char=%d/n",send_status,receive_data);

/* Write the data2send bytes to the 7-segs */
  /*  send_status = usb_control_msg(xsv_handle,0x20,0x09,0x0200,0x0001,send_data,2,500);
printf("TX stat=%d/n",send_status);
usleep(10000);
  */
/* Read the bytes that were just sent to the 7-segs */
  /*
    send_status = usb_control_msg(xsv_handle,0xA0,0x01,0x0100,0x0001,receive_data,2,500);
printf("RX stat=%d data=0x%x,0x%x/n",send_status,receive_data[0],receive_data[1]);
usleep(10000);
  */
usb_close(xsv_handle);
}

usb_dev_handle *locate_xsv(void)
{
unsigned char located = 0;
struct usb_bus *bus;
struct usb_device *dev;
usb_dev_handle *device_handle = 0;

usb_find_busses();
usb_find_devices();

for (bus = usb_busses; bus; bus = bus->next)
{
for (dev = bus->devices; dev; dev = dev->next)
{
if (dev->descriptor.idVendor == 0x1234)
{
located++;
device_handle = usb_open(dev);
printf("XSV Device Found @ Address %s /n", dev->filename);
printf("XSV Vendor ID 0x0%x/n",dev->descriptor.idVendor);
printf("XSV Product ID 0x0%x/n",dev->descriptor.idProduct);
}
else printf("** usb device %s found **/n", dev->filename);
}
  }

  if (device_handle==0) return (0);
  else return (device_handle); 
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章