LRR-TUM-Logo Department of Informatics
Technische Universität München
Informatik X: Rechnertechnik und Rechnerorganisation / Parallelrechnerarchitektur
Prof. Dr. Arndt Bode , Prof. Dr. Hans Michael Gerndt
abakus50x50.gif
 Home  | Addresses  | Staff  | Research  | Lectures 
Search 


Universal Serial Bus Development for Linux
G. Acher & D. Fliegl & T. Sailer

Specification and Internals for the New UHCI Driver (Whitepaper...)

brought to you by

Georg Acher, (executive slave) (base guitar)
Deti Fliegl, (executive slave) (lead voice)
Thomas Sailer, (chief consultant) (cheer leader)

$Id: nuhci_spec.html,v 1.10 2002/03/23 19:04:22 deti Exp $

1. General issues

1.1 Why a new UHCI driver, we already have one?!?

Correct, but its internal structure got more and more mixed up by the (still ongoing) efforts to get isochronous transfers (ISO) to work. Since there is an increasing need for reliable ISO-transfers (especially for USB-audio needed by TS and for a DAB-USB-Receiver build by GA and DF), this state was a bit unsatisfying in our opinion, so we've decided (based on knowledge and experiences with the old UHCI driver) to start from scratch with a new approach, much simpler but at the same time more powerful.

It is inspired by the way Win98/Win2000 handles USB requests via URBs, but it's definitely 100% free of MS-code and doesn't crash while unplugging an used ISO-device like Win98 ;-)

Some code for HW setup and root hub management was taken from the original UHCI driver, but heavily modified to fit into the new code. The invention of the basic concept, and major coding were completed in two days (and nights) on the 16th and 17th of October 1999, now known as the great USB-October-Revolution started by GA, DF, and TS ;-)

Since the concept is in no way UHCI dependant, we hope that it will also be transfered to the OHCI-driver, so both drivers share a common API.

1.2. Advantages and disadvantages

1.3. Is there some compatibility to the old API?

Yes, but only for control, bulk and interrupt transfers. We've implemented some wrapper calls for these transfer types. The usbcore works fine with these wrappers. For ISO there's no compatibility, because the old ISO-API and its semantics were unnecessary complicated in our opinion.

1.4. What's really working?

As said above, CTRL und BULK already work fine even with the wrappers, so legacy code wouldn't notice the change.

Regarding to Thomas, ISO transfers now run stable with USB audio. INT transfers (e.g. mouse driver) work fine, too.

1.5. Are there any bugs?

No ;-)

Hm...

Well, of course this implementation needs extensive testing on all available hardware, but we believe that any fixes shouldn't harm the overall concept.

1.6. What should be done next?

A large part of the request handling seems to be identical for UHCI and OHCI, so it would be a good idea to extract the common parts and have only the HW specific stuff in uhci.c. Furthermore, all other USB device drivers should need URBification, if they use isochronous or interrupt transfers. One thing missing in the current implementation (and the old UHCI driver) is fair queueing for BULK transfers. Since this would need (in principle) the alteration of already constructed TD chains (to switch from depth to breadth execution), another way has to be found. Maybe some simple heuristics work with the same effect.


2. Specification of the API

2.1. Basic concept or 'What is an URB?'

The basic idea of the new driver is message passing, the message itself is called USB Request Block, or URB for short.

2.2. The URB structure

typedef struct urb {
// ignore, for host controller/URB machine internal use
void *hcpriv; // private data for host controller
struct list_head urb_list; // list pointer to all active urbs
// This is used for urb linking
struct urb* next; // pointer to next URB
struct usb_device *dev; // pointer to associated USB device
// pipe is assembled by the various well known pipe-macros in usb.h
unsigned int pipe; // pipe information
// status after each completion
int status; // returned status
unsigned int transfer_flags; // ASAP, SP_OK, EARLY_COMPLETE
// for data stage (CTRL), BULK, INT and ISO
void *transfer_buffer; // associated data buffer
// expected length
int transfer_buffer_length; // data buffer length
int actual_length; // actual data buffer length
// setup stage for CTRL (always 8 bytes!)
unsigned char* setup_packet; // setup packet (control only)
// with ASAP, start_frame is set to the determined frame
int start_frame; // start frame (iso/irq)
int number_of_packets; // # of packets (iso/int)
int interval; // polling interval (irq only)
int error_count; // number of errors (iso only)
//
void *context; // context for completion routine
usb_complete_t complete; // pointer to completion routine
//
// specification of the requested data offsets and length for ISO
iso_packet_descriptor_t iso_frame_desc[0];
} urb_t, *purb_t;

2.3. How to get an URB?

URBs are allocated with the following call

purb_t alloc_urb(int isoframes)

Return value is a pointer to the allocated URB, 0 if allocation failed. The parameter isoframes specifies the number of isochronous transfer frames you want to schedule. For CTRL/BULK/INT, use 0.

To free an URB, use

void free_urb(purb_t purb)

This call also may free internal (host controller specific) memory in the future.

2.4. What has to be filled in?

Depending on the type of transaction, there are some macros (FILL_CONTROL_URB, FILL_BULK_URB, and FILL_INT_URB, defined in uhci.h) that simplify the URB creation. In general, all macros need the usb device pointer, the pipe (usual format), the transfer buffer, the desired transfer length, the completion handler, and its context. Take a look at the uhci_control_msg-function that convert the old API into an URB.

Flags:

For ISO there are two startup behaviors: Specified start_frame or ASAP. For ASAP set USB_ISO_ASAP in transfer_flags.

If short packets should NOT be tolerated, set USB_DISABLE_SPD in transfer_flags.

Usually, (to reduce restart time) the completion handler is called AFTER the URB re-submission. You can get the other way by setting USB_URB_EARLY_COMPLETE in transfer_flags. This is implicite for INT transfers.

2.5. How to submit an URB?

Just call

int submit_urb(purb_t purb)

It immediately returns, either with status 0 (request queued) or some error code, usually caused by the following:

After submission, urb->status is USB_ST_URB_PENDING.

For isochronous endpoints, subsequent submitting of URBs to the same endpoint with the ASAP flag result in a seamless ISO streaming. Exception: The execution cannot be scheduled later than 900 frames from the 'now'-time. The same applies to INT transfers, but here the seamless continuation is independent of the transfer flags (implicitely ASAP).

2.6. How to cancel an already running URB?

Call

int unlink_urb(purb_t purb)

It removes the urb from the internal list and frees all allocated HW descriptors. The status is changed to USB_ST_URB_KILLED. After unlink_urb() returns, you can safely free the URB with free_urb(urb) and all other possibly associated data (urb->context etc.)

2.7. What about the completion handler?

The completion handler is optional, but useful for fast data processing or wakeup of a sleeping process (as shown in the compatibility wrapper's completion handler).

The handler is of the following type:

typedef void (*usb_complete_t)(struct urb *)

i.e. it gets just the URB that caused the completion call. In the completion handler, you should have a look at urb->status to detect any USB errors. Since the context parameter is included in the URB, you can pass information to the completion handler.

2.8. How to do isochronous (ISO) transfers?

For ISO transfers you have to append the iso_packet_descriptor_t structure to the URB for each frame you want to schedule. When using alloc_urb(n) (recommended), the isoframe-parameter n can be used to allocate the structures for n frames.

For each entry you have to specify the data offset for this frame (base is transfer_buffer), and the length you want to write/expect to read. After completion, actual_length contains the actual transfered length and status contains the resulting USB-status for the ISO transfer for this frame. It is allowed to specify a varying length from frame to frame (e.g. for audio synchronisation/adaptive transfer rates). You can also use the length 0 to omit one or more frames (striping).

As can be concluded from above, the UHCI-driver does not care for continous data in case of short packet ISO reads! There's no fixup_isoc() like in the old driver. There may be a common routine to do this in the future, but this has nothing to do with the UHCI-driver!

For scheduling you can choose your own start frame or ASAP. As written above, queuing more than one ISO frame with ASAP to the same device&endpoint result in seamless ISO streaming. For continous streaming you have to use URB linking.

2.9. How to start interrupt (INT) transfers?

INT transfers are currently implemented with 8 different queues for intervals for 1, 2, 4,... 128ms. Only one TD is allocated for each interrupt. After calling the completion handler, the TD is recycled. With the submission of one URB, the interrupt is scheduled until it is canceled by unlink_urb.

The submit_urb()-call modifies urb->interval to the rounded value.


3. Internal structure and mechanisms

To get quickly familiar with the internal structures, here's a short description how the new UHCI driver works. However, the ultimate source of truth is only uhci.c!

3.1. Descriptor structure (QHs and TDs)

During initialization, the following skeleton is allocated in init_skel:

For each CTRL or BULK transfer a new QH is allocated and the containing data transfers are appended as (vertical) TDs. After building the whole QH with its dangling TDs, the QH is inserted before the BULK Chain QH (for CTRL) or before the End Chain QH (for BULK). Since only the QH->next pointers are affected, no atomic memory operation is required. The three QHs in the common chain are never equipped with TDs!

For ISO or INT, the TD for each frame is simply inserted into the apropriate ISO/INT-TD-chain for the desired frame. The 7 skeleton INT-TDs are scattered among the 1024 frames similar to the old UHCI driver.

For CTRL/BULK/ISO, the last TD in the transfer has the IOC-bit set. For INT, every TD (there is only one...) has the IOC-bit set.

Besides the data for the UHCI controller (2 or 4 32bit words), the descriptors are double-linked through the .vertical and .horizontal elements in the SW data of the descriptor (using the double-linked list structures and operations), but SW-linking occurs only in closed domains, i.e. for each of the 1024 ISO-chains and the 8 INT-chains there is a closed cycle. This simplifies all insertions and unlinking operations and avoids costly bus_to_virt()-calls.

3.2. URB structure and linking to QH/TDs

During assembly of the QH and TDs of the requested action, these descriptors are stored in urb->urb_list, so the allocated QH/TD descriptors are bound to this URB.

If the assembly was successful and the descriptors were added to the HW chain, the corresponding URB is inserted into a global URB list for this controller. This list stores all pending URBs.

3.3. Interrupt processing

Since UHCI provides no means to directly detect completed transactions, the following is done in each UHCI interrupt (uhci_interrupt()):

For each URB in the pending queue (process_urb()), the ACTIVE-flag of the associated TDs are processed (depending on the transfer type process_{transfer|interrupt|iso}()). If the TDs are not active anymore, they indicate the completion of the transaction and the status is calculated. Inactive QH/TDs are removed from the HW chain (since the host controller already removed the TDs from the QH, no atomic access is needed) and eventually the URB is marked as completed (OK or errors) and removed from the pending queue. Then the next linked URB is submitted. After (or immediately before) that, the completion handler is called.

3.4. Unlinking URBs

First, all QH/TDs stored in the URB are unlinked from the HW chain. To ensure that the host controller really left a vertical TD chain, we wait for one frame. After that, the TDs are physically destroyed.

3.5. URB linking and the consequences

Since URBs can be linked and the corresponding submit_urb is called in the UHCI-interrupt, all work associated with URB/QH/TD assembly has to be interrupt save. This forces kmalloc to use GFP_ATOMIC in the interrupt.


USB-Team
Last modified: Mon Nov 8 18:21:42 CET 1999
(none) Webmaster