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.
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.
-------------------------------
| Keyboard |
| / |
| / |
| / |
Computer ----------Internal Hub |
| \ |
| \ |
| \ |
| Mouse |
--------------------------------
where the boxed region represents the device. The internal hub is
invisible to the userAll 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.
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 driversshowing 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 003The 001 device is always present and it represents the root hub.
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.
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:
For more information on the API provided by libusb, lookup references.
Here is an example using libusb