Design of the USB subsystem

The initial plan was to follow linux kernel with this. But it's come out quite differently due to our assumptions which simplify things.

Some important Data Structures

Host Controller

The most important one is the abstraction of the USB host controller itself. It's pretty similar to how the Linux kernel does it, but simpler.

A struct usb_hcd describes a generic host controller device. It looks like this.

struct usb_hcd {
	struct hc_driver	*driver;
 
	unsigned long 		res_addr;
	unsigned long		res_size;
 
	void			*hcpriv;
};

* hcpriv is a host controller private object for example, struct uhci_hcd defined for the UHCI host controller.

* res_addr/res_size is the Memory/IO resource region containing the registers of the device.

USB Device

struct usb_device {
	unsigned int 			devnum;
	enum usb_device_speed		speed;
	struct usb_device_descriptor 	descriptor;
 
	struct usb_host_endpoint 	ep_0_in;
 
	struct usb_hcd			*hcd;		
};

* devnum is an address given to the USB device at configuration time. Its assigned by the usb subsystem.

* The descriptor contains information about the USB device and is obtained by querying the device for it during configuration time. Device Descriptors are one of the standard descriptors defined in chapter 9 of the USB reference manual.

* ep_0_in is the IN endpoint numbered zero which is found in all devices. This is used to obtain information such as device descriptor from the USB device.

* hcd is the host controller object to which the device is attached to. Talking to the device is dont in a host controller driver specific way. To delegate it to the hc_driver, we need to maintain a reference for the 'struct usb_hcd' object in struct usb_device.

Host Controller driver

struct hc_driver {
	int	(*enqueue_urb)(struct usb_hcd *hcd, struct urb *urb);
};

This has only one method at the moment. This is used by the USB subsystem to tell the host controller driver to transfer data in a device specific way.

USB request Block (URB)

Drivers communicate with USB devices using an URB. It represents a single request to transfer data to or from the device. It should be host controller agnostic.

struct urb {
	void 				*hcpriv;
 
	struct usb_device 		*udev;
	struct usb_host_endpoint 	*ep;
 
	void 				*transfer_buffer;
	unsigned long 			transfer_dma;
	unsigned int 			transfer_buffer_length;
	unsigned int 			actual_length;
	unsigned char 			*setup_packet;
	unsigned long 			setup_dma;
 
	int 				type;
};

* hcpriv is needed to maintain host controller specific information related to the hub. For example in case of the UHCI host controller, we need to maintain the list of TDs (Transfer Descriptors) associated with a particular URB and similar other house keeping information which are needed to keep track of the after submission.

* ep is the endpoint to which the communication is targeted to.

* type is either control or bulk (only two are supported at the moment).

* the DMA addresses of the transfer buffers is obtained via a call to virt_to_bus() on a piece of buffer allocated by malloc_dma. The setup packet is used in control transactions to send control commands to the usb device.

Everything begins in the probe method of the Host Controller PCI driver which is a non-generic piece of code. The driver looks out for devices on its root hub and tells the USB subsystem about that by creating struct usb_device objects for every usb device it finds. Then the usb devices and their drivers are matched by calling the probe method.

UHCI Host Controller Driver operation

A descriptor for the UHCI host controller device looks like this,

struct uhci_hcd {
	unsigned int		rh_numports;
	unsigned long		io_addr;
 
	unsigned int		*frame;
	unsigned int		frame_dma_handle;
 
	struct uhci_qh		*fs_control_skelqh;
	struct uhci_qh		*bulk_skelqh;
};

* rh_numports is the number of ports on the root hub. * Once we're in the probe method of our PCI driver, we initialize the Host controller's registers with the FrameList address stored in frame_dma_handle. We create two skeleton QH (queue heads), one for Control transactions and one for Bulk Transactions and store it in fs_control_skelqh and bulk_skelqh and link them onto the first Frame List entry. The remaining 1023 frame list entries are marked invalid as they are unused.

'enqueue_urb' method

This is called by the USB subsystem when it needs to transfer information to a device. Here we create TDs for the transaction and link it into the appropriate skeleton QH. For every URB, we maintain a UHCI specific hcpriv object of type struct uhci_urb_priv.

struct uhci_urb_priv {
	struct list_head td_list;
	struct uhci_td	*first_td;
};

This is used to contain the list of TDs the transaction was broken into. (We need to break transactions into TDs because there's a limitation on the maximum data packet that can be transferred in one go). This also contains a pointer to the last TD. This can be used to check for completion of the whole transaction.

Using this in device drivers

Now let's see how all these fit together. Typically the usb_device will be contained as a reference in a descriptor representing a functional device (most likely a struct net_device). To transfer data, we need to create a URB of the required type and fill its fields with information. It is then submitted to the USB subsystem by calling usb_submit_urb. This is an asynchronous operation.

The USB subsystem looks at the URB and identifies the device to which the transfer is targeted to. Once it has a struct usb_device in hand, it can call the device specific transfer method in this way.

int usb_submit_urb(struct urb *urb)
{
	return urb->udev->hcd->driver->enqueue_urb(urb->udev->hcd, urb);
}

QR Code
QR Code soc:2008:balajirrao:notes:usb_gpxe_design (generated for current page)