1

USB Host Stack



1.1    Introduction

The Universal Serial Bus (USB) provides hosts and devices with a versatile channel for communication at low to moderate speeds. The USB currently offers the following types of service at low (1.5 Mbps) and medium (12 Mbps) data transfer rates:

  • control transfers
  • bulk transfers
  • interrupt transfers
  • isochronous transfers

The USB also incorporates provisions for power management and for the dynamic attachment and removal of devices.

This flexibility allows the USB to be used--often concurrently--by a number of different kinds of devices, each requiring its own device driver support. It is desirable that these device drivers be written independent of each other and independent of the implementation of the host computer's underlying USB host controller interface. Wind River's USB host driver stack meets these requirements, providing a complete set of services to operate the USB and a number of pre-built USB class drivers that handle specific kinds of USB devices.

The USB Developer's Kit, also referred to as the USB Kit, includes the following software and hardware items:

  • Source and object code for the USB host stack and drivers.

  • The USB Developer's Kit Programmer's Guide (this document).

  • The USB Developer's Kit Release Notes.

The USB Kit shows you how to write your own USB host controller driver and USB device drivers. It is designed to allow the following:

  • Easy portability to RTOSs other than VxWorks.

  • Fast development of new host controller drivers.

  • Easy portability to many BSPs.

1.1.1   System Requirements

Tornado 2.0 runs on PCs and Sun-4 workstations. Host systems must have the following resources:

  • 64 MB RAM.

  • 15 MB disk space for typical installation.

  • A CD-ROM drive for installation.

  • For Solaris, a SPARCstation 5 minimum; Ultra5 is recommended. For Windows, an Intel 80486 or better; Intel Pentium 90 or better is recommended.

1.1.2   Documentation

The USB Developer's Kit Programmer's Guide, 1.1.1 (this manual), describes the architecture and implementation of Wind River's USB host and peripheral stacks. The guide contains the following sections:

  • 1.11 Running the USB Kit, describes how to use the USB Kit with VxWorks and the Tornado Integrated Development Environment (IDE).

  • 1.12 Initialization, contains instructions for initializing the host stack, host controllers, external devices, and sample applications, and for USB-UGL configuration. This section also includes information on the usbTool code exerciser.

This manual assumes that you are already familiar with the USB specification and Wind River's Tornado IDE and VxWorks operating system. The Wind River USB driver stack has been developed in compliance with the Universal Serial Bus Specification, Revision 1.1, generally referred to in this document as the "USB specification." Where possible, this manual uses terminology similar to that used in the USB specification so that the correspondence between USB concepts and actions is readily apparent in the software interfaces described.

Additional Documentation

  • PDIUSBD12 Evaluation Board (PC Kit) User's Manual, Rev. 2.1, which is included on the floppy disk distributed with the Philips evaluation kit.

  • Firmware Programming Guide for PDIUSBD12, Version 1.0, which is included on the floppy disk distributed with the Philips evaluation kit.

Reference Manual Entries

Reference pages for the USB Kit libraries and subroutines can be accessed online in HTML from the Tornado Help>Manual Index menu command. Search the display box for USB-related libraries in the VxWorks Reference Manual, as in Figure 1.

Figure 1:   Tornado Online Manuals Tree

Related Documentation

  • USB Developer's Kit Release Notes, 1.1.1

  • OHCI and UHCI host controller specifications:

  • Tornado User's Guide 2.0

  • VxWorks Programmer's Guide 5.4

  • VxWorks Reference Manual 5.4



1.2    Architecture Overview

Figure 2 presents a simplified overview of the USB host driver stack architecture.

Figure 2:   USB Host Driver Stack

At the bottom of the stack is the USB host controller (USB HC), the piece of hardware in the host system that controls each USB. Currently, there are two major families of USB host controllers on the market, those supporting the Universal Host Controller Interface (UHCI) originally put forth by Intel, and those supporting the Open Host Controller Interface (OHCI) designed by Microsoft, Compaq, and National Semiconductor. A number of hardware manufacturers have built USB HCs around one or the other of these specifications.

For each type of host controller there is a single, hardware-dependent USB host controller driver (HCD). For example, Wind River provides the source for two pre-built drivers:

  • usbHcdUhciLib for UHCI HCs

  • usbHcdOhciLib for OHCI HCs

The interface between the USB host driver (USBD) and the HCD allows for each HCD to control one or more underlying HCs. Also, Wind River's USBD is capable of connecting to multiple USB HCDs simultaneously. These design features allow you to build a range of complex USB systems.

The USBD is the hardware-independent module above the HCD(s). The USBD manages each USB connected to the host and provides the path through which higher layers communicate with the USB. Among its responsibilities, the USBD handles USB power management and USB bandwidth management automatically. Also, unique to the Wind River architecture, the USBD manages USB hubs. Hub functionality is critical to the proper operation of the USB, so the designers of the Wind River USBD concluded that hub functionality should be handled transparently by the USBD. This means that the USBD also handles the dynamic attachment and removal of USB hubs and devices.

Figure 2 shows the USB Client Module at the top of the stack. USB class drivers are typical examples of client modules. USB class drivers are responsible for managing individual types of devices that can be connected to the USB; they rely on the USBD to provide the communication path to the individual devices. Applications, diagnostics, and test programs are other examples of client modules that rely on the USBD to communicate with USB devices. For example, Wind River provides the test application/module usbTool, which gives you interactive control over the USB and USB devices.

Host Module Roadmap

The diagram in Figure 3 illustrates the functional relationships among the modules that comprise Wind River's USB host driver stack. Each module is further described in this section; for additional information on USB-related libraries and subroutines, see the associated reference pages in the online VxWorks Reference Manual: USB Libraries.

Figure 3:   Host Module Roadmap

usbTool


*      
NOTE: The usbTool module relies internally on static data; you should not run multiple instances of usbTool simultaneously.


*      
NOTE: The usbTool utility resides in target/config/comps/src and is called usrUsbTool.c.

cmdParser

usbKeyboardLib

usbMouseLib

usbPrinterLib

usbSpeakerLib

usbBulkDevLib
and usbCbiUfiDevLib

usbPegasusEndLib,
usbKlsiEndLib,
and usbNC1080EndLib

usbAcmLib

usbdLib
and usbdCoreLib

usbHcdLib

usbHcdUhciLib
and usbHcdOhciLib

usbPciLib

ossLib

usbLib

  • The usbLib module contains routines that the USBD and HCD use to determine the amount of bandwidth a specified pipe requires.

  • Other usbLib routines provide the functionality that allows class drivers to issue commands to devices using the device's control pipe.

usbQueueLib

usbHandleLib

usbListLib



1.3    The USB Host Driver (USBD)

This section describes typical use of the USBD: activities such as initialization, client registration, dynamic attachment registration, device configuration, and data transfers. It also discusses key features of the USBD's internal design.

1.3.1   Initializing the USBD

Initializing the USBD is a two-step process. First, the USBD's entry point, usbdInitialize( ), must be called at least once. The usbdInitialize( ) routine initializes internal USBD data structures and, in turn, calls the initialization entry points of other modules in the USB driver stack. In a given system, it is acceptable to call usbdInitialize( ) once (perhaps during the boot sequence) or to call it many times (as during the initialization of each USBD client). The USBD maintains a usage count that is incremented for each successful call to usbdInitialize( ) and decremented for each corresponding call to usbdShutdown( ). The USBD only truly initializes itself when the usage count goes from zero to one, and it only truly "shuts down" when the usage count returns to zero.

The following code fragment illustrates the proper initialization and shutdown of the USBD:

    ... 
    /* Initialize the USBD. */ 
    if (usbdInitialize () != OK) 
        return ERROR; 
    ... 
    ... 
    /* application code. */ 
    ... 
    ... 
    /* Shut down the USBD. Note: we only execute this code if the return 
     * code from usbdInitialize() was OK. Note also that there's really no 
     * reason to check the result of usbdShutdown(). 
     */ 
    usbdShutdown ();

The second step of USBD initialization is to attach at least one HCD to the USBD, using the USBD's usbdHcdAttach( ) routine. Normally, the attachment of HCDs to the USBD happens during the VxWorks boot sequence. However, the USBD is designed to allow HCDs to be attached (and detached) at run-time. This flexibility allows the USBD to support systems in which USB host controllers can be "hotswapped" without restarting the system.

Attaching and Detaching HCDs

You must decide when to attach HCDs to the USBD and when to detach them (if ever). Typically, USBD clients, such as USB class drivers, are not responsible for attaching HCDs to the USBD.

When an HCD is attached to the USBD, the caller passes the HCD's execution entry point (of the form HCD_EXEC_FUNC) and an HCD-defined parameter (the HCD attachment parameter) to the usbdHcdAttach( ) routine. The USBD in turn invokes the HCD's execution entry point with an HCD_FNC_ATTACH service request, passing it the same HCD attachment parameter provided by the caller. Use of this HCD-defined parameter can vary; however, both HCDs provided by Wind River use the parameter in the same way. For the UHCI and OHCI HCDs provided by Wind River, the HCD attachment parameter is a pointer to a structure of type PCI_CFG_HEADER (defined in pciConstants.h). The structure has been initialized with the PCI configuration header for a UHCI or OCHI host controller. The HCD uses the information in this structure to locate and manage the specified host controller.

Typically, the caller obtains the PCI configuration header for the desired host controller by calling the usbPciClassFind( ) and usbPciConfigHeaderGet( ) routines in usbPciLib (that is, in various stubUsbArchPciLib.c files). If more than one UHCI or OHCI host controller is to be attached to the USBD, these routines should be used repeatedly to obtain the PCI_CFG_HEADER for each host controller, and usbdHcdAttach( ) should be invoked once for each host controller so identified.

The following code fragment demonstrates how to attach a UHCI host controller to the USBD:

    UINT8 busNo; 
    UINT8 deviceNo; 
    UINT8 funcNo; 
    PCI_CFG_HEADER pciCfgHdr;         /* PCI_CFG_HEADER defined in 
        pciConstants.h */ 
    GENERIC_HANDLE uhciAttachToken;   /* GENERIC_HANDLE defined in 
        usbHandleLib.h */ 
 
    /* Locate the first (0th) UHCI controller. UHCI_CLASS, etc., are defined 
     * in usbUhci.h. The functions usbPciClassFind() and 
     * usbPciConfigHeaderGet() are exported by usbPciLib. 
     */ 
    if (!usbPciClassFind (UHCI_CLASS, UHCI_SUBCLASS, UHCI_PGMIF, 0,  
        &busNo, &deviceNo, &funcNo)) 
        { 
        /* No UHCI controller was found. */ 
        return ERROR; 
        } 
    usbPciConfigHeaderGet (busNo, deviceNo, funcNo, &pciCfgHdr); 
 
    /* Attach the UHCI HCD to the USBD. The function usbHcdUhciExec() is 
     * exported by usbHcdUhciLib. 
     */ 
    if (usbdHcdAttach (usbHcdUhciExec, &pciCfgHdr, &uhciAttachToken) != OK) 
        { 
        /* USBD failed to attach to UHCI HCD. */ 
        return ERROR; 
        } 
 
    /* Attachment is complete. */

Detaching an HCD from the USBD is even simpler, as demonstrated in the following code fragment:

    /* Detach the UHCI HCD from the HCD. */ 
 
    usbdHcdDetach (uhciAttachToken); 
 
    /* Detach is complete! */


*      
CAUTION: While the USBD and Wind River HCDs are designed to support the dynamic attachment and detachment of HCDs, certain Wind River BSPs may not support the disconnection of the USB HCD from the underlying PCI interrupt vector, which is essential to the detaching process. If the vector is subsequently re-enabled, the system may attempt to execute a stale interrupt service routine and will fault.

Order of Initialization

The usbdInitialize( ) routine must be called before all other USBD functions, including usbdHcdAttach( ). However, it is not necessary that all USBD clients call usbdInitialize( ) before one or more HCDs have been attached to the USBD. The following two scenarios for initialization sequences are both acceptable:

Scenario #1: Traditional "boot time" initialization

  1. Boot code calls usbdInitialize( ).
  1. Boot code calls usbPciClassFind( ) to locate a USB host controller.
  1. Boot code calls usbPciConfigHeaderGet( ) to read the host controller's configuration header.
  1. Boot code calls usbdHcdAttach( ) to attach the HCD for the identified host controller.
  1. Boot code calls the USB class driver initialization entry point.
  1. The USB class driver calls usbdInitialize( ).

Scenario #2: "Hotswap"-driven initialization
  1. Boot code calls the USB class driver initialization entry point.
  1. The USB class driver calls usbdInitialize( ).

  1. Hotswap code identifies the presence or insertion of the USB host controller.
  1. Hotswap code calls usbdInitialize( ).
  1. Hotswap code calls usbPciConfigHeaderGet( ) to read the host controller's configuration header.
  1. Hotswap code calls usbdHcdAttach( ) to attach the HCD for the identified host controller.

Similarly, calls to usbdHcdDetach( ) can be made at any time to detach HCDs from the USBD. Outstanding requests to an HCD that is being detached are canceled, and detachment proceeds.

To understand why these different scenarios work correctly, it is necessary to recognize that hot-plugging of USB devices can occur at any time. So, both the USBD and its clients must be written (using the "dynamic attachment" functions described in Dynamic Attachment Registration) to recognize the appearance and disappearance of USB devices at run-time. It is for this reason that the appearance and disappearance of host controllers can also be handled flexibly. When a new host controller is attached to the system, the USBD automatically identifies the devices connected to it and notifies interested clients. Likewise, when a host controller is removed from the system, the USBD automatically notifies interested clients that the devices attached to that host controller have disappeared. The key point here is that USBD clients, such as USB class drivers, never assume that a particular device is present when the client is first initialized, and these drivers are always ready to accept devices connected to the system at other times.

Bus Tasks

For each host controller attached to the USBD, the USBD spawns a bus task responsible for monitoring bus events, such as the attachment and removal of devices. These tasks are normally "dormant"--that is, consuming no CPU time--and they typically wake up only when a USB hub reports a change on one of its ports.

Each USBD bus task has the VxWorks task name tUsbdBus.

It is also possible, though not mandated by the design of the USBD, for the HCD itself to create tasks that monitor host controller events. For example, both the UHCI and OHCI HCDs provided by Wind River create such tasks. In the case of the Wind River UHCI module, usbHcdUhciLib, the background task wakes up periodically to poll the status of the UHCI root hub and to check for timed-out I/O request packets (IRPs). In the case of the Wind River OHCI module, usbHcdOhciLib, the background task wakes up periodically to check for timed-out IRPs only (OHCI root hub changes are reported by an interrupt).

Registering Client Modules

Client modules that intend to make use of the USBD to communicate with USB devices must, in addition to calling usbdInitialize( ), register with the USBD by calling usbdClientRegister( ). When a client registers with the USBD, the USBD allocates per-client data structures that are later used to track all requests made by that client. During client registration, the USBD also creates a callback task for each registered client (see Client Callback Tasks). After successfully registering a new client, the USBD returns a handle, USBD_CLIENT_HANDLE, that must be used by that client when making subsequent calls to the USBD.

When a client no longer intends to use the USBD, it must call usbdClientUnregister( ) in order to release its per-client data and callback task. Any outstanding USB requests made by the client are canceled at that time.

The following code fragment demonstrates the registration and unregistration of a USBD client:

    USBD_CLIENT_HANDLE usbdClientHandle; 
 
    /* Register a client named "USBD_TEST" with the USBD. */ 
    if (usbdClientRegister ("USBD_TEST", &usbdClientHandle) != OK) 
        { 
        /* Attempt to register a new client failed. */ 
        return ERROR; 
        } 
 
    /* Client is registered...application code follows. */ 
    ... 
    ... 
    /* Unregister the client. */ 
    usbdClientUnregister (usbdClientHandle); 
Client Callback Tasks

USB operations can be time-critical. For example, both USB interrupt and isochronous transfers depend on timely servicing in order to work correctly. In a host system in which several different USBD clients are present, the possibility always exists for one client to interfere with the timely execution of other clients needing to service time-sensitive USB traffic. The Wind River USBD introduces per-client callback tasks to manage this problem.

Many USB events can result in callbacks to a USBD client. For example, whenever the USBD completes the execution of a USB IRP, the client's IRP callback routine is invoked. Similarly, whenever the USBD recognizes a dynamic attachment event, one or more client's dynamic attachment callback routines are invoked. Instead of invoking these callback routines immediately, the USBD schedules the callbacks to be performed by the callback task for the appropriate USBD client(s). Normally, the callback task for each client is "dormant" (in a blocked state). When the USBD schedules a callback for a specified client, the corresponding client callback task "wakes up" (unblocks) and performs the actual callback. This approach allows the USBD to process all outstanding USB events before the clients themselves obtain control of the CPU.

The callback task for each client inherits the VxWorks task priority of the task that originally called usbdClientRegister( ). This ensures that callbacks are processed at the task priority level intended by each client and allows you to write clients to take advantage of task priorities as a means of ensuring proper scheduling of time-sensitive USB traffic.

Because each client has its own callback task, clients have greater flexibility in the amount of work they can do during the callback. For example, during callback, it is acceptable for executing code to block without hurting the performance of the USBD or other USBD clients.

Client callback tasks have the VxWorks task name tUsbdCln.

USBD Internal Client

When first initialized, the USBD registers an internal "client" that is used to track USB requests generated by the USBD itself. Only one such internal client is created, regardless of the number of calls to usbdInitialize( ).

What types of USB requests can the USBD generate? All USBD communication with USB devices is made using the same calls available to USBD clients. For example, all control pipe requests to a device are made using a control pipe that the USBD creates automatically when a device is first attached to the system. Just as a USBD client would use usbdPipeCreate( ) to create a pipe to talk to a USB device endpoint, the USBD itself creates a control pipe to talk to endpoint 0 (the default control endpoint) on each device. All internal USBD and external USBD client calls to routines like usbdDescriptorGet( ) or usbdFeatureSet( ) are routed through this pipe.

So, the USBD is actually using its own entry points recursively, and the internal client is the USBD's mechanism for tracking these requests.

Dynamic Attachment Registration

A typical USBD client wants to be notified whenever a device of a particular type is attached to or removed from the system. By calling the usbdDynamicAttachRegister( ) routine, a client can specify a callback routine to request such notification.

USB device types are identified by a class, subclass, and protocol. Standard USB classes are defined in usb.h as USB_CLASS_xxxx. Subclass and protocol definitions depend on the class; therefore, these constants are generally defined in the header files associated with a specific class.

Sometimes, a client is interested in a narrow range of devices. In this case, it specifies values for the class, subclass, and protocol when registering through usbdDynamicAttachRegister( ). For example, the USB keyboard class driver, usbKeyboardLib, registers for a Human Interface Device (HID) class of USB_CLASS_HID, a subclass of USB_SUBCLASS_HID_BOOT, and a protocol of USB_PROTOCOL_HID_BOOT_KEYBOARD (both the subclass and protocol are defined in usbHid.h). In response, by means of the callback mechanism, the USBD notifies the keyboard class driver whenever a device matching exactly these criteria is attached to or removed from the system.

In other cases, a client's interest is broader. In this case, the constant USBD_NOTIFY_ALL (defined in usbdLib.h) can be substituted for any or all of the class, subclass, and protocol match criteria. For example, the USB printer class driver, usbPrinterLib, registers for a class of USB_CLASS_PRINTER, subclass of USB_SUBCLASS_PRINTER (defined in usbPrinter.h), and a protocol of USBD_NOTIFY_ALL.

While a typical client makes only a single call to usbdDynamicAttachRegister( ), there is no limit to the number of concurrent notification requests a client can have. A single client can register concurrently for attachment notification of as many device types as desired.

The following code fragments demonstrate the correct use of the dynamic attachment registration functions:

/*************************************************************************** 
* 
* attachCallback - called by USBD when a device is attached/removed 
* 
* The USBD invokes this callback when a USB device is attached to or 
* removed from the system. <nodeId> is the USBD_NODE_ID of the node being 
* attached or removed. <attachAction> is USBD_DYNA_ATTACH or 
* USBD_DYNA_REMOVE. 
* 
* RETURNS: N/A 
*/ 
 
LOCAL VOID attachCallback 
    ( 
    USBD_NODE_ID nodeId,  
    UINT16 attachAction,  
    UINT16 configuration, 
    UINT16 interface, 
    UINT16 deviceClass,  
    UINT16 deviceSubClass,  
    UINT16 deviceProtocol 
    ) 
    { 
    /* Depending on the attachment code, add or remove a device. */ 
    switch (attachAction) 
        { 
        case USBD_DYNA_ATTACH: 
            /* A device is being attached. */ 
            printf ("New device attached to system.\n"); 
            break; 
        case USBD_DYNA_REMOVE: 
            /* A device is being detached. */ 
            printf ("Device removed from system.\n"); 
            break; 
        } 
    }

Somewhere in the initialization of the application, the following code fragment should also appear:

    /* Register for dynamic notification when a USB device is attached to or 
     * removed from the system. For the sake of demonstration, we'll request 
     * notification for USB printers, though this same code could be used for 
     * any other type of device. 
     * 
     * usbdClientHandle is the USBD_CLIENT_HANDLE for the client. 
     * USB_CLASS_PRINTER is defined in usb.h. USB_SUBCLASS_PRINTER is 
     * defined in usbPrinter.h. USBD_NOTIFY_ALL is a wild-card that 
     * matches anything. In this case we use it to match any USB programming 
     * interface. 
     */ 
    if (usbdDynamicAttachRegister (usbdClientHandle, USB_CLASS_PRINTER, 
        USB_SUBCLASS_PRINTER, USBD_NOTIFY_ALL, attachCallback) != OK) 
        { 
        /* Attempt to register for dynamic attachment notification failed. */ 
        return ERROR; 
        } 
 
    /* attachCallback() - above - is now called whenever a USB keyboard 
     * is attached or removed from the system. 
     */ 
    ... 
    ... 
 
    /* Cancel the dynamic attachment registration. Each parameter must match 
     * exactly those found in an earlier call to usbdDynamicAttachRegister(). 
     */ 
    usbdDynamicAttachUnRegister (usbdClientHandle, USB_CLASS_PRINTER, 
        USB_SUBCLASS_PRINTER, USBD_NOTIFY_ALL, attachCallback); 
Node IDs

USB devices are always identified using a USBD_NODE_ID. The USBD_NODE_ID is, in effect, a handle created by the USBD to track a device; it has no relationship to the device's actual USB address. This reflects the fact that clients are usually not interested in knowing to which USB/host controller a device is physically attached. Because each device is referred to using the abstract concept of a node ID, the client can remain unconcerned with the details of physical device attachment and USB address assignment, and the USBD can manage these details internally.

When a client is notified of the attachment or removal of a device, the USBD always identifies the device in question using the USBD_NODE_ID. Likewise, when the client wishes to communicate through the USBD with a particular device, it must pass the USBD_NODE_ID for that device to the USBD.

Bus Enumeration Routines

The usbdLib module exports the routines usbdBusCountGet( ), usbdRootNodeIdGet( ), usbdHubPortCountGet( ), usbdNodeIdGet( ), and usbdNodeInfoGet( ). As a group, these are referred to as bus enumeration routines, and they allow USBD clients to enumerate the network of devices attached to each host controller.

These routines are useful in the authoring of diagnostic tools and test programs such as usbTool. However, when the caller uses these routines, it has no way of knowing if the USB topology changes after enumeration or even mid-enumeration. Therefore, authors of traditional clients, such as USB class drivers, are advised not to use these routines.

Device Configuration

The USB specification defines a set of standard requests, a subset of which must be supported by all USB devices. Typically, a client uses these functions to interrogate a device's "descriptors"--thus determining the device's capabilities--and to set a particular device configuration.

The USBD itself takes care of certain USB configuration issues automatically. Most critically, the USBD internally implements the code necessary to manage USB hubs and to set device addresses when new devices are added to the USB topology. Clients must not attempt to manage these functions themselves, for doing so is likely to cause the USBD to malfunction.

The USBD also monitors so-called configuration events. When a USBD client invokes a USBD function that causes a configuration event, the USBD automatically resets the USB data toggles (DATA0 and DATA1) associated with the device's pipes and endpoints. For example, a call to the USB command usbdConfigurationSet( ) issues the set configuration command to the device, and in the process resets the data toggle to DATA0. Because the USBD handles USB data toggles automatically, you do not typically need to concern yourself with resetting data toggles. For an explanation of pipes and endpoints in the USB environment, see Data Flow. For more information about USB data toggles, see the USB Specification.

Device configuration depends heavily on the type of device being configured. The following code fragment demonstrates how to read and parse a typical device descriptor:

    UINT8 bfr [USB_MAX_DESCR_LEN]; /* USB_MAX_DESCR_LEN defined in usb.h */ 
    UINT16 actLen; 
    pUSB_CONFIG_DESCR pCfgDescr;   /* USB_CONFIG_DESCR defined in usb.h */ 
    pUSB_INTERFACE_DESCR pIfDescr; /* USB_INTERFACE_DESCR is also in usb.h */ 
 
    /* Read the configuration descriptor. In the following fragment it is 
     * assumed that nodeId was initialized, probably in response to an 
     * earlier call to the client's dynamic attachment notification callback  
     * (see above).  
     */ 
    if (usbdDescriptorGet (usbdClientHandle, nodeId,  
        USB_RT_STANDARD | USB_RT_DEVICE, USB_DESCR_CONFIGURATION, 0, 0, 
        sizeof (bfr), bfr, &actLen) != OK) 
        { 
        /* We failed to read the device's configuration descriptor. */ 
        return FALSE; 
        } 
 
    /* Use the function usbDescrParse() - exported by usbLib.h - to extract 
     * the configuration descriptor and the first interface descriptor from 
     * the buffer. 
     */ 
    if ((pCfgDescr = usbDescrParse (bfr, actLen, USB_DESCR_CONFIGURATION)) == NULL) 
        { 
        /* No configuration descriptor was found in the buffer. */ 
        return FALSE; 
        } 
    if ((pIfDescr = usbDescrParse (bfr, actLen, USB_DESCR_INTERFACE)) == NULL) 
        { 
        /* No interface descriptor was found in the buffer. */ 
        return FALSE; 
        } 

Data Flow

Once a client has completely configured a device, it can begin to exchange data with that device using the pipe and transfer functions provided by the USBD. Control, bulk, interrupt, and isochronous transfers are described using a single USB_IRP data structure (defined in usb.h). For more information about the USB_IRP mechanism, see HCD_FNC_IRP_SUBMIT.

USB data transfers are addressed to specific endpoints within each device.1 The channel between a USBD client and a specific device endpoint is called a pipe. Each pipe has a number of characteristics, including:

  • the USBD_NODE_ID of the device
  • the endpoint number on the device
  • the direction of data transfer
  • bandwidth requirements
  • latency requirements

In order to exchange data with a device, a client must first create a pipe. In response to a client request to create a new pipe, the USBD creates a USBD_PIPE_HANDLE, which the client must use for all subsequent operations on the pipe.

When a client attempts to create a pipe, the USBD checks that sufficient bus bandwidth is available. Bandwidth limits are enforced for interrupt and isochronous pipes. The USBD does not allow more than 90% of the available bus bandwidth to be allocated to interrupt and isochronous pipes. For control and bulk pipes, there is no bandwidth limit. At the same time, there is no guarantee that more than 10% of the bus is available for control transfers, and there is no guarantee that any bus bandwidth is available for bulk transfers.

The following code fragment demonstrates the creation of a pipe for sending data to the bulk output endpoint on a printer:

    USBD_PIPE_HANDLE outPipeHandle; 
 
    /* Create a pipe for output to the printer. It is assumed that endpoint, 
     * configValue, interface, and maxPacketSize were determined by reading 
     * the appropriate descriptors from the device. 
     */ 
    if (usbdPipeCreate (usbdClientHandle, nodeId, endpoint, configValue, 
        interface, USB_XFRTYPE_BULK, USB_DIR_OUT, maxPacketSize, 0, 0, 
        &outPipeHandle) != OK) 
        { 
        /* We failed to create the pipe. */ 
        return ERROR; 
        } 

The following fragment demonstrates sending data to the pipe that has just been created:

/*************************************************************************** 
* 
* ourIrpCallback - Invoked upon IRP completion/cancellation 
* 
* RETURNS: N/A 
*/ 
 
LOCAL VOID ourIrpCallback 
    ( 
    pVOID p    /* pointer to the completed IRP */ 
    ) 
 
    { 
    pUSB_IRP pIrp = (pUSB_IRP) p; 
 
    /* Set status based on whether the IRP completed successfully or not. */ 
    if (pIrp->result == OK) 
        { 
        /* IRP was successful. */ 
        } 
    else 
        /* IRP failed. */ 
        } 
    } 

Then, elsewhere in the code, the following should appear:

    UINT8 bfr [4096];    /* a buffer of arbitrary size. */ 
    UINT16 bfrCount;     /* amount of data in the buffer. */ 
    USB_IRP irp; 
    /* The code here would presumably put data into the buffer and initialize  
     * bfrCount with the amount of data in the buffer. 
     */ 
 
    ... 
    ... 
    /* Send bfr to the printer. */ 
    /* Initialize IRP. */ 
    memset (irp, 0, sizeof (irp)); 
    irp.userPtr = ourUserPtr;       /* some value of use to our callback */ 
    irp.irpLen = sizeof (*pIrp);        /* the length of the IRP */ 
    irp.userCallback = ourIrpCallback;  /* our IRP completion callback */ 
    irp.timeout = 30000;                /* 30 second timeout */ 
    irp.transferLen = bfrCount; 
    irp.bfrCount = 1; 
    irp.bfrList [0].pid = USB_PID_OUT; 
    irp.bfrList [0].pBfr = bfr; 
    irp.bfrList [0].bfrLen = count; 
    /* Submit IRP */ 
    if (usbdTransfer (usbdClientHandle, outPipeHandle, &irp) != OK) 
        { 
        /* An error here means that our IRP was malformed. */ 
        return ERROR; 
        } 
    /* The IRP will complete asynchronously. ourIrpCallback() will be invoked 
     * by the USBD when the IRP is complete. 
     */ 

1.3.2   USBD Internals

Hub Management

The management of USB hubs is integral to the proper functioning of the Wind River USBD; therefore, USBD clients should not attempt to modify the configuration of a USB hub attached to the system. Doing so will likely cause the USBD to malfunction.

When the USBD detects a hub, it checks to see whether the hub's power requirements can be met--in other words, can the upstream hub to which the hub is connected provide enough power. If so, the USBD automatically configures the hub and creates a pipe to monitor the hub's status endpoint. If there is insufficient power for the hub or insufficient bus bandwidth to create the pipe, the USBD refuses to recognize the hub.

New Device Connections

When a device is connected to one of a hub's ports, the USBD checks to see whether the attachment of the device exceeds the maximum topology depth allowed by the USB (six levels). Then the USBD tries to read the device's class information, first by reading the device's device descriptor or, alternatively, by reading its configuration and interface descriptors, if the device descriptor does not declare the class information. If either of these steps fails, the USBD refuses to recognize the device.

If the device is recognized successfully, the USBD automatically notifies any clients registered for dynamic attachment notification that the new device has been attached to the system.

If a device is not recognized for any reason, the USBD disables the port to which the device is connected. In this case, the USBD itself is unable to determine the type of device, and no dynamic attachment notification callbacks are invoked. If a USBD client calls usbdNodeIdGet( ) to query the hub port of an unrecognized device, the USBD indicates that no device is attached to the port.



1.4    The USB Host Controller Driver (HCD)

This section describes the interface and requirements for HCDs. This information is essential if you are creating an HCD for a host controller that Wind River does not already support.

1.4.1   HCD Header File

All HCDs implement the interface as defined in usbHcd.h.

1.4.2   HCD Entry Point

Each HCD exports a single entry point, its execution entry point. This function takes the following form:

typedef STATUS (*HCD_EXEC_FUNC) (pVOID pHrb);

1.4.3   Host Request Blocks (HRB)

All requests to the HCD are made by constructing Host Request Blocks (HRBs) and passing them to the HCD's execution entry point for processing. HRBs begin with a common header, followed by parameters unique to the function being executed. The format of the header is as follows:

typedef struct hrb_header 
    { 
    HCD_CLIENT_HANDLE handle;      /* I/O: caller's HCD client handle */ 
    UINT16 function;               /* IN: HCD function code */ 
    UINT16 hrbLength;              /* IN: Length of the total HRB */ 
    } HRB_HEADER, *pHRB_HEADER; 

When an HCD is attached, a handle to the host controller is created. That handle, stored in the handle field, is used by all host controller driver functions; however, each time an HCD is attached, a new handle is created.

The function field in HRB_HEADER defines one of the following functions as the HCD function to be executed: 

HCD_FNC_ATTACH

HCD_FNC_DETACH

HCD_FNC_BUS_RESET

HCD_FNC_SET_BUS_STATE

HCD_FNC_SOF_INTERVAL_GET

HCD_FNC_SOF_INTERVAL_SET

HCD_FNC_PIPE_CREATE

HCD_FNC_PIPE_DESTROY

HCD_FNC_PIPE_MODIFY

HCD_FNC_CURRENT_FRAME_GET

HCD_FNC_IRP_SUBMIT

HCD_FNC_IRP_CANCEL

An HCD must implement all of these functions in order to perform properly.

The hrbLength field is set by the caller to be the total length of the HRB, including the header and any additional parameters that follow.

Each HCD function, including any associated HRB, is described in the following sections.

HCD_FNC_ATTACH

This function is contained in the following HRB:

typedef struct hrb_attach 
    { 
    HRB_HEADER header;          /* HRB header */ 
    pVOID param;                /* IN: HCD-specific parameter */ 
    USB_HCD_MNGMT_CALLBACK mngmtCallback; 
                                /* IN: USBD's callback for mngmt events */ 
    pVOID mngmtCallbackParam;   /* IN: USBD-defined parameter to callback */ 
    UINT16 busCount;            /* OUT: number of buses managed by HCD */ 
    } HRB_ATTACH, *pHRB_ATTACH; 

In response to an HCD_FNC_ATTACH request, an HCD initializes one or more USB host controllers, thereby enabling the USB(s) attached to those host controller(s).

The USBD invokes the HCD's HCD_FNC_ATTACH function when usbdHcdAttach( ) is called. Unlike other HCD functions, the USBD does not store an existing HCD_CLIENT_HANDLE in the HRB_HEADER.handle field. Instead, upon successful completion of this routine, the HCD is expected to store a newly created HCD_CLIENT_HANDLE in the HRB_HEADER.handle field so that the USBD can retrieve it. The USBD uses the newly created HCD_CLIENT_HANDLE to identify the indicated USB host controller in subsequent calls to the HCD. Typically, the HCD casts the newly created HCD_CLIENT_HANDLE as a pointer to an internal HCD data structure used to manage a specific host controller.

The HCD-defined parameter passed to the usbdHcdAttach( ) routine is in turn passed in the param field. The USBD makes no attempt to interpret the meaning of this parameter. The HCD can use the param field as needed; for example, the UHCI and OHCI HCDs provided by Wind River cast this parameter to a pointer to a PCI_CFG_HEADER structure (defined in pciConstants.h). The UHCI and OHCI HCDs assume that this structure has already been initialized with the PCI configuration header of a USB host controller.

The mngmtCallback parameter is a pointer to a function within the USBD that the HCD should call when it detects certain USB management events. This function takes the following form:

typedef VOID (*USB_HCD_MNGMT_CALLBACK) 
    ( 
    pVOID mngmtCallbackParam,    /* caller-defined parameter */ 
    HCD_CLIENT_HANDLE handle,    /* handle to host controller */ 
    UINT16 busNo,                /* bus number */ 
    UINT16 mngmtCode             /* management code */ 
    ); 

Management events are described in 1.4.4 Management Events. The mngmtCallbackParam is a USBD-defined parameter that the HCD must pass to the USBD whenever the HCD calls the mngmtCallback function.

Finally, the HCD must return the number of host controllers initialized in the busCount field. The UHCI and OCHI HCDs provided by Wind River always return 1 in this field. However, other HCDs can initialize multiple host controllers in response to a single HCD_FNC_ATTACH request. When a value greater than one is returned in busCount, the USBD identifies individual host controllers using a combination of the HCD_CLIENT_HANDLE and a host controller index in the range 0..busCount - 1. When busCount is returned as 1, the USBD always passes a host controller index of 0 to the HCD.

HCD_FNC_DETACH

This function is contained in the following HRB:

typedef struct hrb_detach 
    { 
    HRB_HEADER header;            /* HRB header */ 
    } HRB_DETACH, *pHRB_DETACH; 

The USBD invokes the HCD_FNC_DETACH function when the HCD must shut down the host controller(s) specified by HCD_CLIENT_HANDLE. Two conditions prompt the USBD to do this:

  • A call to the usbdHcdDetach( ) routine.

  • Automatically, when the USBD's usage count goes to zero as a result of a call to usbdShutdown( ).

In response to this function, the HCD disables the specified host controller(s) and releases any memory and other resources allocated on behalf of the specified host controller(s).

HCD_FNC_BUS_RESET

This function is contained in the following HRB:

typedef struct hrb_bus_reset 
    { 
    HRB_HEADER header;            /* HRB header */ 
    UINT16 busNo;                 /* IN: bus number to reset */ 
    } HRB_BUS_RESET, *pHRB_BUS_RESET; 

The USBD invokes the HCD_FNC_BUS_RESET function to reset a USB. The HCD_CLIENT_HANDLE passed in HRB.HRB_HEADER.handle specifies the USB to be reset, and the host controller index passed in busNo specifies the host controller to be reset.

When driving the signaling of a USB reset, the HCD must ensure that USB reset timing requirements are met. That is, the reset width must be at least USB_TIME_RESET and the HCD should wait USB_TIME_RESET_RECOVERY after resetting the bus. Both of these time management constants are defined in usb.h.

HCD_FNC_SET_BUS_STATE

This function is contained in the following HRB:

typedef struct hrb_set_bus_state 
    { 
    HRB_HEADER header;            /* HRB header */ 
    UINT16 busNo;                 /* IN: bus number */ 
    UINT16 busState;              /* IN: new bus state, HCD_BUS_xxxx */ 
    } HRB_SET_BUS_STATE, *pHRB_SET_BUS_STATE; 

The USBD controls global SUSPEND and global RESUME states with the HCD_FNC_SET_BUS_STATE function. To place a host controller in the global SUSPEND state, the USBD invokes this function with the field busState set to HCD_BUS_SUSPEND. To bring the host controller out of the SUSPEND state, the USBD invokes this function with busState set to HCD_BUS_RESUME. The HCD responds by placing the specified host controller, busNo, in the specified state.

HCD_FNC_SOF_INTERVAL_GET and HCD_FNC_SOF_INTERVAL_SET

These functions share the following HRB:

typedef struct hrb_sof_interval_get_set 
    { 
    HRB_HEADER header;        /* HRB header */ 
    UINT16 busNo;             /* IN: bus index */ 
    UINT16 sofInterval;       /* I/O: SOF interval */ 
    } HRB_SOF_INTERVAL_GET_SET, *pHRB_SOF_INTERVAL_GET_SET; 

Most USB host controllers can adjust start-of-frame (SOF) timing within a narrow range (± 1 bit-time). This adjustment allows time-sensitive applications to tune the real-time behavior of the USB to meet strict isochronous timing requirements. The entire frame is called an interval.

The HCD responds to these functions by setting or requesting the current SOF interval for a specified host controller, busNo. It returns the current SOF interval in the sofInterval field. The value returned is expressed as the current number of bit-times in each frame. The default value should always be 12,000.

HCD_FNC_PIPE_CREATE

This function is contained in the following HRB:

typedef struct hrb_pipe_create 
    { 
    HRB_HEADER header;            /* HRB header */ 
    UINT16 busNo;                 /* IN: bus index: 0, 1, ... */ 
    UINT16 busAddress;            /* IN: bus address of USB device */ 
    UINT16 endpoint;              /* IN: endpoint on device */ 
    UINT16 transferType;          /* IN: transfer type */ 
    UINT16 direction;             /* IN: transfer/pipe direction */ 
    UINT16 speed;                 /* IN: transfer speed */ 
    UINT16 maxPacketSize;         /* IN: packet size */ 
    UINT16 bytesPerFrame;         /* IN: number of bytes per frame */ 
    UINT16 interval;              /* IN: service interval */ 
    UINT32 time;                  /* OUT: calc'd transfer time in nanosecs */ 
    HCD_PIPE_HANDLE pipeHandle;   /* OUT: pipe handle */ 
    } HRB_PIPE_CREATE, *pHRB_PIPE_CREATE; 

The USBD invokes the HCD_FNC_PIPE_CREATE function when creating a pipe. The structure's elements specify a new pipe's attributes as follows:

busAddress

endpoint

transferType

direction (control pipes only)

speed

maxPacketSize

bytesPerFrame

interval

time

The HCD keeps track of the total amount of bandwidth allocated for each host controller it manages. If the amount of bandwidth requested exceeds the maximum amount of bus bandwidth available for interrupt and isochronous transfers (90% of total bus bandwidth), the HCD returns the error S_usbHcdLib_BANDWIDTH_FAULT.

Finally, the HCD must return a non-NULL HCD_PIPE_HANDLE in pipeHandle. The USBD records this value and passes it to the HCD_FNC_IRP_SUBMIT function when subsequently submitting IRPs for execution on the pipe.

HCD_FNC_PIPE_DESTROY

This function is contained in the following HRB:

typedef struct hrb_pipe_destroy 
    { 
    HRB_HEADER header;            /* HRB header */ 
    HCD_PIPE_HANDLE pipeHandle;   /* IN: pipe to be destroyed */ 
    } HRB_PIPE_DESTROY, *pHRB_PIPE_DESTROY; 

The USBD invokes the HCD_FNC_PIPE_DESTROY function to notify the HCD that a specified pipe is being torn down and that the bandwidth associated with that pipe should be released. The HCD responds by releasing the amount of bandwidth originally allocated to the pipe and releasing any data structures and other resources allocated on behalf of the pipe.

Before invoking this function, the USBD cancels any outstanding IRPs on the pipe.

HCD_FNC_PIPE_MODIFY

This function is contained in the following HRB:

typedef struct hrb_pipe_modify 
    { 
    HRB_HEADER header;          /* HRB header */ 
    HCD_PIPE_HANDLE pipeHandle; /* IN: pipe to modify */ 
    UINT16 busAddress;          /* IN: new bus address, or 0 if unchanged */ 
    UINT16 maxPacketSize;       /* IN: new max pkt size, or 0 if unchanged */ 
    } HRB_PIPE_MODIFY, *pHRB_PIPE_MODIFY; 

The HCD_FNC_PIPE_MODIFY function allows the USBD to modify pipe attributes after creating the default control pipe.

When the USBD creates the default control pipe for a device, it must read the device's device descriptor before it can know the maximum packet size that the packet permits. Also, the USBD generally assigns a new USB address to a device after establishing communication with the device's default control pipe.

By convention, when creating the default control pipe, the USBD specifies a device address of 0 and a maximum packet size of 8 (the minimum required by the USB Specification). Typically, after the host obtains the device descriptor, it issues the command to set a new, non-zero address for the device specified by pipeHandle. Then, after the device address has been changed, the USBD invokes HCD_FNC_PIPE_MODIFY to change the busAddress and maxPacketSize attributes.

HCD_FNC_CURRENT_FRAME_GET

This function is contained in the following HRB:

typedef struct hrb_current_frame_get 
    { 
    HRB_HEADER header;            /* HRB header */ 
    UINT16 busNo;                 /* IN: bus index: 0, 1, ... */ 
    UINT16 frameNo;               /* OUT: current frame number */ 
    UINT16 frameWindow;           /* OUT: frame window size */ 
    } HRB_CURRENT_FRAME_GET, *pHRB_CURRENT_FRAME_GET; 

USBD clients use this function to retrieve the current USB frame number for a specified host controller, busNo. In response to this function, the HCD stores the current USB frame number in the frameNo field and the maximum number of unique frame numbers tracked by the host controller in the frameWindow field.

For example, most USB host controllers are capable of counting frame numbers from 0 to 1023 or from 0 to 2047. In such cases, frameWindow should be returned as 1024 or 2048, respectively.

HCD_FNC_IRP_SUBMIT

This function is contained in the following HRB:

typedef struct hrb_irp_submit 
    { 
    HRB_HEADER header;            /* HRB header */ 
    HCD_PIPE_HANDLE pipeHandle;   /* IN: pipe to which IRP is directed */ 
    pUSB_IRP pIrp;                /* IN: pointer to IRP */ 
    } HRB_IRP_SUBMIT, *pHRB_IRP_SUBMIT; 

The USBD uses the HCD_FNC_IRP_SUBMIT function to pass a USB_IRP data structure to the HCD for execution on a specified pipe. IRPs are a mechanism for scheduling data transfers across the USB. See below for additional information about the USB_IRP structure.

Use the pipeHandle parameter to specify the HCD_PIPE_HANDLE for a pipe previously created through the HCD_FNC_PIPE_CREATE function.

In response, the HCD queues the IRP and returns immediately. The HCD notifies the USBD when the IRP is complete by invoking the USBD's IRP callback function (identified in the usbdCallback field of the IRP). After accepting an IRP for execution, an HCD must guarantee that it will invoke the USBD's IRP callback function when the IRP completes for any reason, including being canceled (see HCD_FNC_IRP_CANCEL).

The HCD can omit the call to the USBD's IRP callback function if it detects a malformed HRB or IRP when the IRP is initially passed to the HCD_FNC_IRP_SUBMIT function. If this is the case, the HCD returns an error, indicating to the USBD that the HCD refused to accept the IRP.

USB_IRP Data Structure.

The structure of a USB_IRP takes the following form:

typedef struct usb_irp 
    { 
    LINK usbdLink;              /* link field used internally by USBD */ 
    pVOID usbdPtr;              /* pointer field for use by USBD */ 
    LINK hcdLink;               /* link field used internally by USB HCD */ 
    pVOID hcdPtr;               /* pointer field for use by USB HCD */ 
    pVOID userPtr;              /* pointer field for use by client */ 
    UINT16 irpLen;              /* total length of IRP structure */ 
    int result;                 /* IRP completion result: S_usbHcdLib_xxxx */ 
    IRP_CALLBACK usbdCallback;  /* USBD completion callback routine */ 
    IRP_CALLBACK userCallback;  /* client's completion callback routine */ 
    UINT16 dataToggle;          /* IRP should start with DATA0/DATA1. */ 
    UINT16 flags;               /* defines other IRP processing options */ 
    UINT32 timeout;             /* IRP timeout in milliseconds */ 
    UINT16 startFrame;          /* start frame for isochronous transfer */ 
    UINT16 dataBlockSize;       /* granularity of data for isoch. transfer */ 
    UINT32 transferLen;         /* total length of data to be transferred */ 
    UINT16 bfrCount;            /* indicates count of buffers in BfrList */ 
    USB_BFR_LIST bfrList [1]; 
    } USB_IRP, *pUSB_IRP; 

The following fields comprise the IRP data structure:

usbdLink

usbdPtr

hcdLink

hcdPtr

userPtr

irpLen

irpLen = sizeof (USB_IRP) + (sizeof (USB_BFR_LIST) x (bfrCount -1))
result

usbdCallback

userCallback

dataToggle

flags

timeout

startFrame

dataBlockSize

transferLen

bfrCount

USB_BFR_LIST Structure and Conventions.

The structure of USB_BFR_LIST is as follows:

typedef struct usb_bfr_list 
    { 
    UINT16 pid;            /* specifies packet type as USB_PID_xxxx */ 
    pUINT8 pBfr;           /* pointer to buffer */ 
    UINT32 bfrLen;         /* length of buffer */ 
    UINT32 actLen;         /* actual length transferred */ 
    } USB_BFR_LIST, *pUSB_BFR_LIST; 

The following fields comprise the USB buffer list structure:

pid

pBfr

bfrLen

actLen

By convention, there are several additional limitations on the structure of USB_IRPs passed to the HCD:

  • USB_IRPs that describe control transfers must always have two or three bfrList entries (bfrCount equals 2 or 3), depending on whether the setup transfer involves a data phase.

The first bfrList entry must have a pid value of USB_PID_SETUP and must describe the SETUP transaction. If the SETUP transfer involves a data phase, the second bfrList entry must have a pid value of USB_PID_IN or USB_PID_OUT and must describe the data phase. If there is a data phase, the third bfrList entry must have a pid value opposite to that of the data phase and must describe a 0-byte packet (pBfr equals NULL and bfrLen equals 0). If there is no data phase, the second bfrList entry must have a pid value of USB_PID_IN and must describe a 0-byte packet. Following these rules, a single USB_IRP always describes a complete SETUP transfer.

  • USB_IRPs describing interrupt transfers generally have only a single bfrList entry with a pid value of USB_PID_IN or USB_PID_OUT; however, this is essentially a practical consideration because most interrupt transfers are smaller than the maximum size of the packet for a specified interrupt pipe. This is not a mandatory requirement.

  • USB_IRPs describing bulk transfers can have an unlimited number of bfrList entries. Because USB bulk and interrupt pipes are uni-directional (one-way), the pid value of all bfrList entries must be the same and must correspond to the direction of the pipe.

  • USB_IRPs that describe isochronous transfers must have only one USB_BFR_LIST entry. In general, the buffer that the USB_BFR_LIST entry describes should be large enough to span n frames, where n is comfortably larger than the interrupt latency of the target system (measured in milliseconds). The HCDs provided by Wind River automatically split the buffer into frames of the appropriate size to maintain the average transfer rate specified for the corresponding pipe.

USB_IRP Queuing Conventions

The USBD never submits more than one SETUP transfer for a particular device at one time. (The USBD internally serializes any attempts to schedule simultaneous control transfers to a single device.)

By convention, when using the Wind River-provided UHCI and OHCI HCDs, USBD clients should never attempt to queue more than one bulk or interrupt transfer for a particular pipe at the same time. The Wind River HCDs make no attempt to serialize multiple USB_IRPs. Therefore, queuing multiple bulk transfers at the same time likely causes data to be exchanged with the device in an unpredictable order. (The USBD does not enforce this convention, so it is possible for third parties to create HCDs and USBD clients that do not adhere to this rule.)

Similarly, Wind River HCDs do not serialize concurrent interrupt USB_IRPs directed at the same pipe; therefore, USBD clients should avoid queuing more than a single interrupt USB_IRP for a particular interrupt pipe at one time. (Again, the USBD does not enforce this convention, so third-party HCD/client combinations may be coded to behave differently.)

On the contrary, USBD clients should maintain a queue of at least two USB_IRPs for each active isochronous pipe, if the isochronous data flow is to continue uninterrupted. Because isochronous USB_IRPs generally define the startFrame field for each isochronous transfer, the HCD has enough information to schedule each USB_IRP for execution at the correct time. Therefore, "double-buffering" USB_IRPs for isochronous transfers allows the HCD to eliminate dead-time, ensuring a continuous flow of time-sensitive data. The Wind River UHCI and OHCI HCDs are implemented to ensure continuous isochronous data transfer, provided the USBD client uses at least two USB_IRPs to maintain double-buffering.

HCD_FNC_IRP_CANCEL

This function is contained in the following HRB:

typedef struct hrb_irp_cancel 
    { 
    HRB_HEADER header;            /* HRB header */ 
    pUSB_IRP pIrp;                /* IN: pointer to IPR to be canceled */ 
    } HRB_IRP_CANCEL, *pHRB_IRP_CANCEL; 

The USBD uses the HCD_FNC_IRP_CANCEL function to cancel an outstanding IRP previously passed to the HCD through the HCD_FNC_IRP_SUBMIT function. If the specified IRP is still outstanding (that is, it has not yet completed, either successfully or with an error), the HCD must cancel the IRP and set the USB_IRP.result field to S_usbHcdLib_IRP_CANCELED. If the IRP has already completed, the HCD returns the error S_usbHcdLib_CANNOT_CANCEL in response to the HCD_FNC_IRP_CANCEL request.

1.4.4   Management Events

The management callback provided by the USBD to the HCD as part of the HCD_FNC_ATTACH request gives the HCD a way to notify the USBD when it detects asynchronous management events. The format of the callback is as follows:

typedef VOID (*USB_HCD_MNGMT_CALLBACK) 
    ( 
    pVOID mngmtCallbackParam,        /* caller-defined parameter */ 
    HCD_CLIENT_HANDLE handle,        /* handle to host controller */ 
    UINT16 busNo,                    /* bus number */ 
    UINT16 mngmtCode                 /* management code */ 
    ); 

The mngmtCallbackParam parameter passed to the callback is the HRB.mngmtCallbackParam value originally passed to the HCD during the HCD_FNC_ATTACH request. The handle parameter is the HCD-assigned HCD_CLIENT_HANDLE for the corresponding host controller(s), and the busNo parameter is the index of the specific host controller reporting the management event.

At the present time, the USBD/HCD interface defines only one type of management event, HCD_MNGMT_RESUME.

The management code is passed to the USBD's callback in the mngmtCode parameter.

HCD_MNGMT_RESUME

Most available host controllers in the SUSPEND state can detect a request by a USB device to resume. This feature, called remote wakeup, can be used to bring a suspended bus back to normal operation.

If the HCD detects that a USB device is driving RESUME signaling on the USB while the host controller is in the SUSPEND state, the HCD should invoke the USBD's management callback, passing the value HCD_MNGMT_RESUME in the mngmtCode parameter.

1.4.5   HCD Error Reporting Conventions

A number of standard HCD error codes are defined in usbHcd.h. You are encouraged to use the existing HCD error codes when creating new HCDs. These error codes are returned both in response to HCD function requests through the HCDs execution entry point, as well as in the result field of each USB_IRP processed by the HCD. (For more information about the USB_IRP result field, see HCD_FNC_IRP_SUBMIT.) To conform to Wind River conventions, the HCD should also set the system errno whenever returning an error.

HCDs return the standard VxWorks constant OK when a function or IRP completes successfully. HCD error codes are defined as follows:

S_usbHcdLib_BAD_CLIENT

S_usbHcdLib_BAD_PARAM

S_usbHcdLib_BAD_HANDLE

S_usbHcdLib_OUT_OF_MEMORY

S_usbHcdLib_OUT_OF_RESOURCES

S_usbHcdLib_NOT_IMPLEMENTED

S_usbHcdLib_GENERAL_FAULT

S_usbHcdLib_NOT_INITIALIZED

S_usbHcdLib_INT_HOOK_FAILED

S_usbHcdLib_STRUCT_SIZE_FAULT

S_usbHcdLib_HW_NOT_READY

S_usbHcdLib_NOT_SUPPORTED

S_usbHcdLib_SHUTDOWN

S_usbHcdLib_IRP_CANCELED

S_usbHcdLib_STALLED

S_usbHcdLib_DATA_BFR_FAULT

S_usbHcdLib_BABBLE

S_usbHcdLib_CRC_TIMEOUT

S_usbHcdLib_TIMEOUT

S_usbHcdLib_BITSTUFF_FAULT

S_usbHcdLib_SHORT_PACKET

S_usbHcdLib_CANNOT_CANCEL

S_usbHcdLib_BANDWIDTH_FAULT

S_usbHcdLib_SOF_INTERVAL_FAULT

S_usbHcdLib_DATA_TOGGLE_FAULT

S_usbHcdLib_PID_FAULT

S_usbHcdLib_ISOCH_FAULT

In particular, HCDs must store the error code S_usbHcdLib_IRP_CANCELED in the result field of USB_IRPs that have been canceled for any reason. Higher layers rely on this error code to recognize situations in which IRPs should not be retried.

For their own convenience, HCDs can temporarily store other values in the USB_IRP result field while processing IRPs. For example, the UHCI and OHCI HCDs provided by Wind River use this field temporarily while executing the IRP, to store a value indicating that the IRP is "pending." However, before invoking an IRP's completion callback routine, the HCD is required to store either the constant OK or one of the above error codes in the USB_IRP result field.

1.4.6   Root Emulation

HCDs are required to emulate the behavior of the root hub. That is, HCDs must intercept transfer requests intended for the root hub and must synthesize standard USB responses to these requests.

For example, when a host controller is first initialized by the HCD_FNC_ATTACH request, the root hub must respond at the default USB address 0, and its downstream ports must be disabled. The USBD interrogates the root hub, just as it would other hubs, by issuing USB GET_DESCRIPTOR requests. The USBD then configures the root hub for operation by issuing a series of SET_ADDRESS, SET_CONFIGURATION, SET_FEATURE, and CLEAR_FEATURE requests. The HCD is responsible for recognizing which of these requests are intended for the root hub and must respond to them appropriately.

After configuration, the USBD begins polling the root hub's interrupt status pipe to monitor changes on the root hub's downstream ports. The HCD must intercept IRPs directed to the root hub's interrupt endpoint and synthesize appropriate replies. Typically, the HCD queues USB_IRPs directed to the root hub separately from those that actually result in bus operations. For example, the UHCI and OHCI HCDs provided by Wind River queue the USB_IRPs sent to the root hub and spawn a background task to synthesize responses to these IRPs. The background task typically polls the host controller hardware several times per second to determine whether there has been a change in state on one of the root hub's downstream ports.

The source files for the HCDs provided by Wind River include complete examples of the root hub emulation required of each. See the following source files:

  • for UHCI HCD: usbHcdUhciLib.c

  • for OHCI HCD: usbHcdOhciLib.c

1.4.7   IRP Timeouts

Each HCD is responsible for determining whether a particular IRP has timed out, and when. The allowable execution time for each IRP is stored in the USB_IRP.timeout field. Typically, IRPs have a default timeout of 5 seconds (defined as USB_TIMEOUT_DEFAULT in usb.h). However, certain IRPs, such as those used to monitor interrupt status input from hubs, can have no timeout (defined as USB_TIMEOUT_NONE in usb.h).

Typically, a background task spawned by the HCD during HCD_FNC_ATTACH processing is the mechanism used to detect when IRPs reach their timeouts. In the case of the Wind River UHCI HCD, this same background task periodically polls the root hub for port status changes (as described in 1.4.6 Root Emulation). This background task is named tUhciInt, whereas the background task spawned by the Wind River OHCI HCD is named tOhciInt.



1.5    Keyboard Driver

USB keyboards are described in Human Interface Device (HID) related specifications. The Wind River implementation of the USB keyboard driver, usbKeyboardLib, is concerned only with USB devices claiming to be keyboards as set forth in the USB Specification; the driver ignores other types of human interface devices, such as printers.


*      
NOTE: USB keyboards can operate according to either a boot protocol or a report protocol; however, the usbKeyboardLib driver enables keyboards for operation using the boot protocol only.

1.5.1   SIO Driver Model

The USB keyboard class driver, usbKeyboardLib, follows the VxWorks serial I/O (SIO) driver model, with certain exceptions and extensions. As the SIO driver model presents a fairly limited, byte-stream-oriented view of a serial device, the keyboard driver maps USB keyboard scan codes into appropriate ASCII codes. Scan codes and combinations of scan codes that do not map to the ASCII character set are suppressed.

1.5.2   Dynamic Attachment

Unlike most SIO drivers, the usbKeyboardLib driver's number of supported channels is not fixed. Rather, USB keyboards can be added or removed from the system at any time. Thus, the number of channels is dynamic, and clients of usbKeyboardLib must be made aware of the appearance and disappearance of channels. Therefore, this driver includes a set of routines that allows clients to register for notification upon the attachment and removal of USB keyboards, and the corresponding creation and deletion of channels.

In order to be notified of the attachment and removal of USB keyboards, clients should register with usbKeyboardLib by calling usbKeyboardDynamicAttachRegister( ). Clients provide a callback function of the following form:

typedef VOID (*USB_KBD_ATTACH_CALLBACK)  
    ( 
    pVOID arg,                /* caller-defined argument */ 
    SIO_CHAN *pChan,          /* pointer to the affected SIO_CHAN */ 
    UINT16 attachCode         /* defined as USB_KBD_xxxx */ 
    ); 

When usbKeyboardLib detects a new USB keyboard, each registered callback is invoked with pChan pointing to a new SIO_CHAN structure and with attachCode set to the value USB_KBD_ATTACH. When keyboards are removed from the system, each registered callback is invoked with attachCode set to USB_KBD_REMOVE and with pChan pointing to the SIO_CHAN structure of the keyboard that has been removed.

The usbKeyboardLib driver maintains a usage count for each SIO_CHAN structure. Callers can increase the usage count by calling usbKeyboardSioChanLock( ), or they can decrease it by calling usbKeyboardSioChanUnlock( ). Normally, if a keyboard is removed from the system when the usage count is zero (0), usbKeyboardLib automatically releases the SIO_CHAN structure formerly associated with the keyboard. However, clients that rely on this structure can use the locking mechanism to force the driver to retain the structure until it is no longer needed.

Example 1:   SIO_CHAN Locking

Consider an application that periodically polls a keyboard by calling the pollInput( ) callback identified in a particular SIO_CHAN structure. This is an example where SIO_CHAN locking could be required.

The task responsible for polling runs in the background and operates asynchronously with respect to the code that receives attachment or detachment notification from usbKeyboardLib. If usbKeyboardLib frees the memory associated with the SIO_CHAN structure as soon as the keyboard is unplugged, it is possible that the pollInput( ) function pointer may be corrupted. If that occurs, the application's asynchronous polling task would fail upon calling the--now corrupt--pollInput( ) function pointer, probably taking down the system.

The application can use the SIO_CHAN locking mechanism to force the usbKeyboardLib to delay the release of the SIO_CHAN structure until after the application has canceled the background polling operation.

The following code fragments demonstrate the typical use of the dynamic attachment and SIO_CHAN locking functions:

    /* First, initialize the usbKeyboardLib. */ 
    if (usbKeyboardDevInit () != OK) 
        { 
        /* We failed to initialize usbKeyboardLib. */ 
        return ERROR; 
        } 
 
    /* Register for keyboard attachment/detachment notification. */ 
    if (usbKeyboardDynamicAttachRegister (kbdAttachCallback, (pVOID) 1234) 
        != OK) 
        { 
        /* We failed to register for attachment notification. */ 
        return ERROR; 
        } 
 
    /* The kbdAttachCallback() function will now be called asynchronously 
     * whenever a keyboard is attached to or detached from the system. 
     */ 
    ... 
    ... 
    /* Unregister for keyboard notification and shut down usbKeyboardLib. */ 
    usbKeyboardDynamicAttachUnRegister (kbdAttachCallback, (pVOID) 1234); 
    usbKeyboardDevShutdown (); 

The keyboard attachment callback might resemble the following:

/************************************************************************* 
* 
* kbdAttachCallback - receives callbacks from USB keyboard SIO driver 
* 
* RETURNS: N/A 
*/ 
 
LOCAL SIO_CHAN *pOurChan = NULL; 
 
LOCAL VOID kbdAttachCallback 
    ( 
    pVOID arg,            /* caller-defined argument */ 
    SIO_CHAN *pChan,      /* pointer to the affected SIO_CHAN */ 
    UINT16 attachCode     /* defined as USB_KBD_xxxx */ 
    ) 
 
    { 
    UINT32 ourArg = (UINT32) arg; 
 
    /* The argument is any arbitrary value that may be of use to this 
     * callback. In this example, we just demonstrate that the value 
     * originally passed to usbKeyboardDynamicAttachRegister() shows up here 
     * as our argument. 
     */ 
 
    if (ourArg != 1234) 
        { 
        /* The argument never made it. */ 
        ... 
        } 
 
    switch (attachCode) 
        { 
        case USB_KBD_ATTACH: 
 
            /* Lock the SIO_CHAN structure so it doesn't disappear on us. */ 
            if (usbKeyboardSioChanLock (pChan) != OK) 
                { 
                /* This really shouldn't be able to fail. */ 
                ... 
                } 
 
            /* Do other initialization stuff. */ 
            pOurChan = pChan; 
            ... 
            ... 
 
            break; 
 
        case USB_KBD_DETACH: 
 
            /* Tear down any data structures we may have created. */ 
            ... 
            ... 
            pOurChan = NULL; 
 
            /* Allow usbKeyboardLib to release the SIO_CHAN structure. */ 
            usbKeyboardSioChanUnlock (pChan); 
            break; 
        } 
    } 

1.5.3   Initialization

As with standard SIO drivers, usbKeyboardLib must be initialized by calling usbKeyboardDevInit( ), which in turn initializes its connection to the USBD and other internal resources needed for operation. All interaction with the USB host controllers and devices is handled through the USBD.

Unlike some SIO drivers, usbKeyboardLib does not include data structures that require initialization prior to calling usbKeyboardDevInit( ).

However, before a call to usbKeyboardDevInit( ), the caller must ensure that the USBD has been properly initialized by calling, at a minimum, usbdInitialize( ). The caller must also confirm that at least one USB HCD is attached to the USBD, using usbdHcdAttach( ), before keyboard operation can begin; however, it is not necessary to call usbdHcdAttach( ) prior to initializing usbKeyboardLib. The usbKeyboardLib driver uses the USBD dynamic attachment services and recognizes USB keyboard attachment and removal on the fly. Therefore, it is possible for USB HCDs to be attached to or detached from the USBD at run-time, as may be required (for example, in systems supporting hardware hotswap).

Unlike traditional SIO drivers, the usbKeyboardLib driver does not export entry points for send, receive, and error interrupts. All interrupt-driven behavior is managed by the underlying USBD and USB HCD(s), so there is no need for a caller (or BSP) to connect interrupts on behalf of usbKeyboardLib. For the same reason, there is no post-interrupt-connect initialization code, and usbKeyboardLib therefore omits the devInit2 entry point.

1.5.4   ioctl Functions

The usbKeyboardLib driver supports the SIO ioctl interface. However, attempts to set parameters, such as baud rates and start or stop bits, have no meaning in the USB environment and return ENOSYS.

1.5.5   Data Flow

For each USB keyboard connected to the system, usbKeyboardLib sets up a USB pipe to monitor keyboard input. Input, in the form of scan codes, is translated to ASCII codes and placed in an input queue. If SIO callbacks have been installed and usbKeyboardLib has been placed in the SIO interrupt mode of operation, usbKeyboardLib invokes the character received callback for each character in the queue. When usbKeyboardLib has been placed in polled mode, callbacks are not invoked and the caller must fetch keyboard input using the driver's pollInput( ) routine.

The usbKeyboardLib driver does not support output to the keyboard; therefore, calls to the txStartup( )) and pollOutput( ) routines return errors. The only output supported is the control of the keyboard LEDs, which usbKeyboardLib handles internally.

The caller should be aware that usbKeyboardLib is not capable of operating in a true polled mode, because the underlying USBD and USB HCD always operate in interrupt mode.

The following code fragment demonstrates using usbKeyboardLib to display keystrokes typed by the user:

    int i; 
    char inChar; 
 
    /* Display the next ten keystrokes typed by the user. This code  
     * assumes that pOurChan was initialized as shown earlier and is  
     * currently not NULL. */ 
    for (i = 0; i < 10; i++) 
        { 
        /* Wait for a keystroke */ 
        while ((*pOurChan->pDrvFuncs->pollInput) (pOurChan, &inChar) != OK) 
            ; 
 
        /* Display the keystroke. */ 
        printf ("The user pressed `%c'.\n", inChar); 
        } 

1.5.6   Typematic Repeat

USB keyboards do not implement typematic repeat, a feature that causes a key to repeat if it is held down, typically for more than one-fourth or one-half second. If you want this feature, implement it with the host software. For this purpose, usbKeyboardLib creates a task, tUsbKbd, that monitors all open channels and injects characters into input queues at an appropriate repeat rate.

For example, if a user presses and holds a key on a USB keyboard, a single report is sent from the keyboard to the host indicating the keypress. If no report is received within a pre-set interval indicating that the key has been released, the tUsbKbd thread automatically injects additional copies of the same key into the input queue at a pre-set rate. In the current implementation, the pre-set interval (delay) is one-half (1/2) second, and the repeat rate is 15 characters per second.



1.6    Mouse Driver

USB mice are described in Human Interface Device (HID) related specifications. The Wind River implementation of the USB mouse driver, usbMouseLib, concerns itself only with USB devices claiming to be mice as set forth in the USB Specification; the driver ignores other types of human interface devices, such as keyboards.


*      
NOTE: USB mice operate according to either a boot protocol or a report protocol; however, the usbMouseLib driver enables mice for operation using the boot protocol only.

1.6.1   SIO Driver Model

The USB mouse class driver, usbMouseLib, follows the VxWorks serial I/O (SIO) driver model, with certain exceptions and extensions. For example, ioctl functions have no effect.

1.6.2   Dynamic Attachment

As with the USB keyboard class driver, the number of channels supported by this driver is not fixed. Rather, USB mice can be added or removed from the system at any time. Thus, the number of channels is dynamic, and clients of usbMouseLib must be made aware of the appearance and disappearance of channels. Therefore, this driver includes a set of routines that allows clients to register for notification upon the attachment and removal of USB mice, and the corresponding creation and deletion of channels.

In order to be notified of the attachment and removal of USB mice, clients should register with usbMouseLib by calling usbMouseDynamicAttachRegister( ). Clients provide a callback function of the following form:

typedef VOID (*USB_MSE_ATTACH_CALLBACK)  
    ( 
    pVOID arg,            /* caller-defined argument */ 
    SIO_CHAN *pChan,      /* pointer to the affected SIO_CHAN */ 
    UINT16 attachCode     /* defined as USB_MSE_xxxx */ 
    ); 

When usbMouseLib detects a new USB mouse, each registered callback is invoked with pChan pointing to a new SIO_CHAN structure and with attachCode set to the value USB_MSE_ATTACH. When a mouse is removed from the system, each registered callback is invoked with attachCode set to USB_MSE_REMOVE and with pChan pointing to the SIO_CHAN structure of the mouse that has been removed.

As with usbKeyboardLib, usbMouseLib maintains a usage count for each SIO_CHAN structure. Callers can increment the usage count by calling usbMouseSioChanLock( ) and can decrement it by calling usbMouseSioChanUnlock( ). For more information on using the SIO_CHAN structure, see Example 1.

1.6.3   Initialization

As with standard SIO drivers, usbMouseLib must be initialized by calling usbMouseDevInit( ), which in turn initializes its connection to the USBD and other internal resources needed for operation. All interaction with the USB host controllers and devices is handled through the USBD.

Unlike some SIO drivers, usbMouseLib does not include data structures that require initialization prior to calling usbMouseDevInit( ).

However, before a call to usbMouseDevInit( ), the caller must ensure that the USBD has been properly initialized by calling, at a minimum, usbdInitialize( ). The caller must also confirm that at least one USB HCD is attached to the USBD, using usbdHcdAttach( ), before mouse operation can begin; however, it is not necessary to call usbdHcdAttach( ) prior to initializing usbMouseLib. The usbMouseLib driver uses the USBD dynamic attachment services and recognizes USB mouse attachment and removal on the fly. Therefore, it is possible for USB HCDs to be attached to or detached from the USBD at run-time, as may be required (for example, in systems supporting hardware hotswap).

Unlike traditional SIO drivers, the usbMouseLib driver does not export entry points for send, receive, and error interrupts. All interrupt-driven behavior is managed by the underlying USBD and USB HCD(s), so there is no need for a caller (or BSP) to connect interrupts on behalf of usbMouseLib. For the same reason, there is no post-interrupt-connect initialization code, and usbMouseLib therefore omits the devInit2 entry point.

1.6.4   ioctl Functions

The usbMouseLib driver supports the SIO ioctl interface. However, attempts to set parameters, such as baud rates and start or stop bits, have no meaning in the USB environment and return ENOSYS.

1.6.5   Data Flow

For each USB mouse connected to the system, usbMouseLib sets up a USB pipe to monitor input from the mouse, in the form of HID boot reports. These mouse boot reports are of the following form (as defined in usbHid.h):

typedef struct hid_mse_boot_report 
    { 
    UINT8 buttonState;        /* buttons */ 
    char xDisplacement;       /* signed x-displacement */ 
    char yDisplacement;       /* signed y-displacement */ 
    } HID_MSE_BOOT_REPORT, *pHID_MSE_BOOT_REPORT; 

In order to receive these reports, a client of usbMouseLib must install a special callback using the driver's callbackInstall( ) routine. The callback type is SIO_CALLBACK_PUT_MOUSE_REPORT, and the callback itself takes the following form:

VOID (*MSE_REPORT_CALLBACK) 
    ( 
    void *callbackArg,           /* callback parameter specified by caller */ 
    pHID_MSE_BOOT_REPORT pReport /* pointer to mouse boot report */ 
    ); 

The client callback should interpret the report according to its needs, saving any data that might be required from the HID_MSE_BOOT_REPORT structure.

The usbMouseLib driver does not support polled modes of operation; neither does it support the traditional SIO_CALLBACK_PUT_RCV_CHAR callback. Given the structured nature of the boot reports received from USB mice, character-by-character input of boot reports would be inefficient and could lead to report framing problems in the input stream.



1.7    Printer Driver

USB printers are described in Human Interface Device (HID) related specifications. This class driver specification presents two kinds of printer: uni-directional printers (output only) and bi-directional printers (capable of both output and input). The usbPrinterLib driver is capable of handling both kinds of printers. If a printer is uni-directional, the driver only allows characters to be written to the printer; if the printer is bi-directional, it allows both output and input streams to be written and read.

1.7.1   SIO Driver Model

The USB printer class driver, usbPrinterLib, follows the VxWorks serial I/O (SIO) driver model, with certain exceptions and extensions. This driver provides the external APIs expected of a standard multi-mode serial (SIO) driver and adds extensions that support the hot-plugging USB environment.

1.7.2   Dynamic Attachment

As with usbKeyboardLib, usbMouseLib maintains a usage count for each SIO_CHAN structure. Callers can increment the usage count by calling usbMouseSioChanLock( ) and can decrement it by calling usbMouseSioChanUnlock( ). These mechanisms are identical to those described in 1.5 Keyboard Driver.

As with the USB keyboard class driver, the number of channels supported by this driver is not fixed. Rather, USB printers can be added or removed from the system at any time. Thus, the number of channels is dynamic, and clients of usbPrinterLib must be made aware of the appearance and disappearance of channels. Therefore, this driver includes a set of routines that allows clients to register for notification upon the attachment and removal of USB printers, and the corresponding creation and deletion of channels.

In order to be notified of the attachment and removal of USB printers, clients should register with usbPrinterLib through the routine usbPrinterDynamicAttachRegister( ). Clients provide a callback function of the following form:

typedef VOID (*USB_PRN_ATTACH_CALLBACK)  
    ( 
    pVOID arg,                /* caller-defined argument */ 
    SIO_CHAN *pChan,          /* pointer to the affected SIO_CHAN */ 
    UINT16 attachCode         /* defined as USB_PRN_xxxx */ 
    ); 

When usbPrinterLib detects a new USB printer, each registered callback is invoked with pChan pointing to a new SIO_CHAN structure and with attachCode set to the value USB_PRN_ATTACH. When printers are removed from the system, each registered callback is invoked with attachCode set to USB_PRN_REMOVE and with pChan pointing to the SIO_CHAN structure of the printer that has been removed.

As with usbKeyboardLib, usbPrinterLib maintains a usage count for each SIO_CHAN structure. Callers can increment the usage count by calling usbPrinterSioChanLock( ) and can decrement it by calling usbPrinterSioChanUnlock( ). Normally, if a printer is removed from the system when the usage count is zero (0), usbPrinterLib automatically releases the SIO_CHAN structure formerly associated with the printer. However, clients that rely on this structure can use this locking mechanism to force the driver to retain the structure until it is no longer needed.

For more information about using the SIO_CHAN structure, see Example 1.

1.7.3   Initialization

As with standard SIO drivers, usbPrinterLib must be initialized by calling usbPrinterDevInit( ), which in turn initializes its connection to the USBD and other internal resources needed for operation. All interaction with the USB host controllers and devices is handled through the USBD.

Unlike some SIO drivers, usbPrinterLib does not include data structures that require initialization prior to calling usbPrinterDevInit( ).

However, before a call to usbPrinterDevInit( ), the caller must ensure that the USBD has been properly initialized by calling, at a minimum, usbdInitialize( ). The caller must also confirm that at least one USB HCD is attached to the USBD, using usbdHcdAttach( ), before printer operation can begin; however, it is not necessary to call usbdHcdAttach( ) prior to initializing usbPrinterLib. The usbPrinterLib driver uses the USBD dynamic attachment services and recognizes USB printer attachment and removal on the fly. Therefore, it is possible for USB HCDs to be attached to or detached from the USBD at run-time, as may be required (for example, in systems supporting hardware hotswap).

Unlike traditional SIO drivers, the usbPrinterLib driver does not export entry points for send, receive, and error interrupts. All interrupt-driven behavior is managed by the underlying USBD and USB HCD(s), so there is no need for a caller (or BSP) to connect interrupts on behalf of usbPrinterLib. For the same reason, there is no post-interrupt-connect initialization code, and usbPrinterLib therefore omits the devInit2 entry point.

1.7.4   ioctl Functions

The usbPrinterLib driver supports the SIO ioctl interface. However, attempts to set parameters, such as baud rates and start or stop bits, have no meaning in the USB environment and are treated as no-ops.

Additional ioctl functions have been added to allow the caller to retrieve the USB printer's device ID string, the type of printer (uni- or bi-directional), and the current printer status. The device ID string is discussed in more detail in the USB Specification and is based on the IEEE-1284 device ID string used by most 1284-compliant printers. The printer status function can be used to determine whether the printer has been selected, is out of paper, or has an error condition.

1.7.5   Data Flow

For each USB printer connected to the system, usbPrinterLib sets up a USB pipe to output bulk data to the printer. This is the pipe through which printer control and page description data are sent to the printer. Additionally, if the printer is bi-directional, usbPrinterLib sets up a USB pipe to receive bulk input data from the printer. The meaning of data received from a bi-directional printer depends on the particular printer make and model.

The USB printer driver supports only SIO_MODE_INT, the SIO interrupt mode of operation. Any attempt to place the driver in polled mode returns an error.



1.8    Speaker Driver

USB speakers are described in the USB Device Class Definition for Audio Devices. The Wind River implementation of the USB speaker driver, usbSpeakerLib, supports only USB speakers as defined by this specification and ignores other types of USB audio devices, such as MPEG and MIDI devices.


*      
NOTE: Some models of USB speakers are implemented as compound devices, and these compound devices often integrate a small number of physical audio controls, such as those for volume, bass, treble, and balance. These physical controls are presented as separate USB interfaces within the compound device and are implemented according to the HID specification. The usbSpeakerLib library ignores these non-audio interfaces. If the target application requires these HID controls to be enabled, you must implement additional logic to recognize the HID interface and map the HID functions to appropriate usbSpeakerLib ioctl functions (see 1.8.5 ioctl Functions).

1.8.1   SEQ_DEV Driver Model

The usbSpeakerLib driver provides a modified VxWorks SEQ_DEV interface to its callers. Among existing VxWorks driver models, the SEQ_DEV interface best supports the streaming data transfer model required by isochronous devices such as USB speakers. As with other VxWorks USB class drivers, the standard driver interface has been expanded to support features unique to the USB and to speakers in general. Functions have been added to allow callers to recognize the dynamic attachment and removal of speaker devices. ioctl functions have been added to retrieve and control additional settings related to speaker operation.

1.8.2   Dynamic Attachment

Like other USB devices, USB speakers can be attached to or detached from the system dynamically. The usbSpeakerLib driver uses the USBD's dynamic attachment services to recognize these events, and callers to usbSpeakerLib can use the usbSpeakerDynamicAttachRegister( ) routine to register with the driver to be notified when USB speakers are attached or removed. The caller must provide usbSpeakerDynamicAttachRegister( ) with a pointer to a callback routine of the following form:

typedef VOID (*USB_SPKR_ATTACH_CALLBACK)  
    ( 
    pVOID arg,           /* caller-defined argument */ 
    SEQ_DEV *pSeqDev,    /* pointer to the affected SEQ_DEV */ 
        UINT16 attachCode    /* defined as USB_SPKR_xxxx */ 
    ); 

When a USB speaker is attached or removed, usbSpeakerLib invokes each registered notification callback. The callback is passed a pointer to the affected SEQ_DEV structure, pSeqDev, and an attachment code, attachCode, indicating whether the speaker is being attached or removed.

The usbSpeakerLib driver maintains a usage count for each SEQ_DEV structure. Callers can increment the usage count by calling usbSpeakerSeqDevLock( ) or can decrement the count by calling usbSpeakerSeqDevUnlock( ). If a USB speaker is removed from the system when its usage count is 0, usbSpeakerLib automatically removes all data structures, including the SEQ_DEV structure itself, that have been allocated on behalf of the device. Sometimes, however, callers rely on these data structures and must properly recognize the removal of the device before it is safe to destroy the underlying data structures. The lock and unlock routines provide a mechanism for callers to protect these data structures, as needed.

1.8.3   Initialization

As with standard SEQ_DEV drivers, this driver must be initialized by calling usbSpeakerDevInit( ). The usbSpeakerDevInit( ) routine, in turn, initializes its connection to the USBD and other internal resources needed for operation.

Unlike some SEQ_DEV drivers, there are no usbSpeakerLib data structures that must be initialized before usbSpeakerDevInit( ) is called.

Prior to calling usbSpeakerDevInit( ), the caller must ensure that the USBD has been properly initialized by, at a minimum, a call to usbdInitialize( ). It is also the caller's responsibility to ensure that at least one USB HCD is attached to the USBD--using the USBD function usbdHcdAttach( )-- before speaker operation can begin. However, it is not necessary to call usbdHcdAttach( ) prior to initializing usbSpeakerLib. The usbSpeakerLib driver uses USBD's dynamic attachment services and can recognize USB speaker attachment and removal on the fly. Therefore, it is possible for USB HCDs to be attached to or detached from the USBD at run-time, as may be required (for example, in systems supporting hardware hotswap).

1.8.4   Recognizing and Handling USB Speakers

Speakers, loosely defined, are USB audio devices that provide an output terminal. For each USB audio device, usbSpeakerLib examines the descriptors that define both the units and terminals contained within the device and how they are connected.

If an output terminal is found, usbSpeakerLib traces the device's internal connections to determine which input terminal provides the audio stream for that output terminal, and which feature unit, if any, is responsible for controlling audio stream attributes such as volume. After building an internal map of the device, usbSpeakerLib configures the device and waits for a caller to provide a stream of audio data. If no output terminal is found, usbSpeakerLib ignores the audio device.

After determining that the audio device contains an output terminal, usbSpeakerLib builds a list of the audio formats that the device supports. The usbSpeakerLib driver supports only AudioStreaming interfaces (no MIDIStreaming is supported).

For each USB speaker attached to the system and properly recognized by usbSpeakerLib, usbSpeakerLib creates a SEQ_DEV structure to control the speaker. Each speaker is uniquely identified by the pointer to its corresponding SEQ_DEV structure.

1.8.5   ioctl Functions

The usbSpeakerLib driver implements a number of ioctl functions unique to the handling of audio data and devices. The driver uses ioctl functions to set the mute, volume, bass, mid-range, and treble controls. The driver provides ioctl functions for use by callers to interrogate a speaker's audio format capabilities or to specify the audio format for a subsequent data stream. The driver also provides ioctl functions to mark the beginning and end of audio data streams (see 1.8.6 Data Flow).

1.8.6   Data Flow

Before sending audio data to a speaker device, the caller must specify the data format (for example, PCM or MPEG) using an ioctl function (see 1.8.5 ioctl Functions). The USB speaker itself must support the specified data format or a similar one.

USB speakers rely on an uninterrupted, time-critical stream of audio data. The data is sent to the speaker through an isochronous pipe. In order for the data flow to continue uninterrupted, usbSpeakerLib uses an internal double-buffering scheme. When the caller presents data to usbSpeakerLib's sd_seqWrt( ) routine, usbSpeakerLib copies the data into an internal buffer and immediately releases the caller's buffer. The caller should immediately try to pass the next buffer to usbSpeakerLib. When usbSpeakerLib's internal buffer is filled, it blocks the caller until it can accept new data. In this manner, the caller and usbSpeakerLib work together to ensure that an adequate supply of audio data is always available to continue isochronous transmission uninterrupted.

Audio play begins after usbSpeakerLib has accepted a half-second of audio data or when the caller closes the audio stream, whichever happens first. The caller must use ioctl functions to open and close each audio stream. The usbSpeakerLib driver relies on these open and close ioctl functions to manage its internal buffers correctly.



1.9    Mass Storage Class Driver

USB Mass Storage Class devices are described in the Mass Storage Class specification and can behave according to several different implementations. Wind River supplies drivers that adhere to the Bulk-Only and Control/Bulk/Interrupt (CBI) implementation methods. Each of these two drivers uses a command set from an existing protocol. The Wind River CBI driver wraps USB protocol around the commands documented in SCSI Primary Commands: 2 (SPC-2), Revision 3 or later.2 The Wind River Bulk-Only driver wraps USB protocol around the commands documented in Advanced Technology Attachment Packet Interface (ATAPI) for Floppies, SFF-8070i.3

A device's Subclass code, presented in its interface descriptor, indicates which of these command sets the device understands. Table 1, adapted from the Universal Serial Bus Mass Storage Class Specification Overview, shows the command set that corresponds to each Subclass code.

Table 1:   Device Subclass Codes and Corresponding Command Sets


Subclass Code
Command Block Specification
Comments

01h
Reduced Block Commands (RBC) T10 Project 1240-D
Typically, a flash device uses RBCs. However, any Mass Storage device can use RBCs.
02h
SFF-8020i
    or
MMC-2 (ATAPI)
Typically, a CD/DVD device uses SFF-8020i or MMC-2 command blocks for its Mass Storage interface.
03h
QIC-157
Typically, a tape device uses QIC-157 command blocks.
04h
UFI
Typically, a floppy disk drive (FDD) device uses UFI command blocks.
05h
SFF-8070i
Typically, a floppy disk drive (FDD) device uses SFF-8070i command blocks. However, an FDD device can belong to another Subclass (for example, RBC); likewise, other types of storage device can belong to the SFF-8070i Subclass.
06h
SCSI transparent command set
07h-FFh
Reserved for future use

Wind River's CBI driver responds to devices with Subclass code 05h. Wind River's Bulk-Only driver responds to devices with Subclass code 06h.

All references to the Mass Storage Class driver library take the form usbMSCxxx( ). References to Wind River's CBI driver take the form usbCbiUfixxx( ); references to Wind River's Bulk-Only driver take the form usbBulkxxx( ).

1.9.1   Block Device Driver Model

A Mass Storage Class driver is a type of block device driver that provides generic direct access to a block device through VxWorks. Mass Storage Class drivers interact with the file system. The file system, in turn, interacts with the I/O system.

A device driver for a block device must provide a means of creating a logical block device structure called BLK_DEV. This structure describes the USB Mass Storage device in a generic fashion, specifying only those common characteristics that must be known to the file system being used with the device (for example, read, write, and ioctl routines). After the device has been initialized with a particular file system, all I/O operations for the device are routed through that file system. The file system, in turn, calls the routines in the specified BLK_DEV structure. The BLK_DEV structure is initialized to point to the read/write routines described in this section.

When the class driver creates the block device using usbMSCBlkDevCreate( ), the USB device does not have a name or a file system associated with it. In most cases, a file system is assigned during device initialization in the user code (through a routine such as dosFsDevInit( ) or rt11FsDevInit( )). File system initialization routines assign a specified name to the USB device and enter the device into the I/O system device table. The following code fragment shows a typical USB Mass Storage driver initialization sequence:

pBlkDev = usbMSCDevInit( ); 
dosFsMkfs("dev1:", pBlkDev)

The hierarchy diagram in Figure 4 illustrates where the USB Mass Storage Class driver fits into a VxWorks system.   

Figure 4:   USB Block Driver Hierarchy in a VxWorks System

  

The USB Mass Storage Class device driver provides the following API functions to the file system:

usbMSCDevInit( )

usbMSCDevCreate( )


usbMSCBlkWrt( )


usbMSCBlkRd( )


usbMSCDevIoctl( )


usbMSCStatusChk( )


usbMSCDevReset( )


1.9.2   Dynamic Attachment

A USB device driver must support the most important feature of a USB device: dynamic insertion and removal of the device. A USB Mass Storage Class device can be plugged into and out of the system at any time. This "hotswap" feature is supported by a callback mechanism.

The USB Mass Storage Class driver provides the registration function usbMSCDynamicAttachRegister( ), which registers the client with the driver. When a USB Mass Storage Class device is attached to or removed from the system, all clients are notified through the USBD's call to a user-provided callback function. The callback function receives the USB_NODE_ID of the attached device and a flag that is set to either USB_MSC_ATTACH or USB_MSC_DETACH, indicating the attachment or removal of the device. The driver also provides the usbMSCDynamicAttachUnregister( ) function for deregistering a block device driver.

The Mass Storage Class driver maintains a usage count of BLK_DEV structures. When a client uses the BLK_DEV structure, it informs the driver by calling usbMSCDevLock( ). For each usbMSCDevLock( ) call, the driver increments the usage count. When a client is finished with the BLK_DEV structure, it must notify the driver by calling usbMSCDevUnlock( ). The driver then decrements the usage count. Normally, if a Mass Storage Class device is removed from the system when the usage count is zero (0), the driver releases the corresponding BLK_DEV structure. However, clients that rely on this structure can use this locking mechanism to force the driver to retain the structure until it is no longer needed.

1.9.3   Initialization

The Mass Storage Class driver is initialized through the usbMSCDevInit( ) routine. This API call, in turn, initializes internal resources needed for its operation and registers a callback routine with the USBD. The callback routine is then invoked whenever a USB Mass Storage Class device is attached to or removed from the system.

All interactions between the USB host controller and the Mass Storage Class device are handled through the USBD. Therefore, before calling usbMSCDevInit( ), the user must ensure that the USBD has been properly initialized with usbdInitialize( ). Also, before any operation with a block device driver, the caller must ensure that at least one host controller is attached to the USBD.

1.9.4   Data Flow

The Mass Storage Class driver's data read and write mechanism behaves like that of a standard block device driver. It uses the data read and write function pointers that are installed through the usbMSCBlkDevCreate( ) routine. Because most USB Mass Storage Class devices can implement 64-byte endpoints only, the driver must manage the transfer of the larger chunks (that is, 512-byte blocks) of data that are understood by the file system. To facilitate the multiple read/write transactions that are necessary to complete the block access, the USBD uses its IRP mechanism. This allows the user to specify a function, usbMSCIrpCallback( ), that is called when the block transaction is complete.



1.10    Communication Class Drivers

USB Communication Class devices can behave according to several different implementations. Wind River supplies drivers for Ethernet Networking Control Model devices, and for Abstract Control Model devices.

1.10.1   Ethernet Networking Control Model Driver

This section describes Wind River's USB Networking Control Model driver. The driver supports USB network adapter devices with Subclass code 06h (see Table 1), with certain exceptions and extensions.

The Ethernet device presents two interfaces for transferring information to the device: a Communication Class interface and a Data Class interface. The Communication Class interface is a management interface and is required of all communication devices. The Data Class interface can be used to transport data across the USB wire. Ethernet data frames are encapsulated into USB packets and are then transferred using this Data Class interface. These Ethernet packets include an Ethernet destination address (DA), which is appended to the data field. Ethernet packets in either direction over the USB do not include a CRC (cyclic redundancy check); error-checking is instead performed on the surrounding USB packet.

The hierarchy diagram in Figure 5 illustrates where the USB Communication Class driver fits into a VxWorks system.   

Figure 5:   USB Communication Class Driver Hierarchy in a VxWorks System

  

Enhanced Network Driver Model

Wind River's USB Networking Control Model driver conforms to the MUX Enhanced Network Driver (END) model, with certain variations. These differences are designed to accommodate the following features:

  • Hotswap of USB devices.
  • Attaching multiple identical devices to one host.
  • USB network devices with vendor-specific initialization requirements. For example, one of the supported devices, the KSLI adapter, requires new firmware to be downloaded before normal operation can begin.
  • A dynamic insertion and removal callback mechanism.

In order to meet these requirements, Wind River's drivers include additional APIs, beyond those defined in the standard END specification. For detailed information on the END model, see the Tornado BSP Developer's Kit for VxWorks User's Guide: Implementing a MUX-Based Network Interface Driver.

Dynamic Attachment

Because USB network adapters can be hotswapped to and from the system at any time, the number of devices is dynamic. Clients of usbXXXEndLib can be made aware of the attachment and removal of devices. The driver includes a set of API calls that allows clients to register for notification upon attachment or removal of a USB network adapter device. The attachment and removal of USB network adapters correspond, respectively, to the creation and deletion of USB_XXX_DEV structures.

In order to be notified of the attachment or removal of USB network adapters, clients should register with usbXXXEndLib by calling usbXXXDynamicAttachRegister( ), providing a callback function.

When usbXXXEndLib detects a new USB network adapter, each registered client callback function is invoked with callbackType set to USB_XXX_ATTACH. Similarly, when a USB network adapter is removed from the system, each registered client callback function is invoked with callbackType set to USB_XXX_DETACH.

The usbXXXEndLib driver maintains a usage count for each USB_XXX_DEV structure. When a client uses the USB_XXX_DEV structure, it informs the driver by calling usbXXXDevLock( ). For each usbXXXDevLock( ) call, the driver increments the usage count. When a client is finished with the USB_XXX_DEV structure, it must notify the driver by calling usbXXXDevUnlock( ). The driver then decrements the usage count. Normally, if an adapter is removed from the system when the usage count is zero (0), the driver releases the corresponding USB_XXX_DEV structure. However, clients that rely on this structure can use this locking mechanism to force the driver to retain the structure until it is no longer needed.

Initialization

The usbXXXEndLib driver must be initialized through the usbXXXEndInit( ) routine, which in turn initializes its connection to the USBD and other internal resources needed for its operation. This API call also registers a callback routine with the USBD. The callback routine is then invoked whenever a USB networking device is attached to or removed from the system.

All interactions between the USB host controller and the networking device are handled through the USBD. Therefore, before calling usbXXXEndInit( ), the user must ensure that the USBD has been properly initialized with usbdInitialize( ). Also, before any operation with a networking device driver, the caller must ensure that at least one host controller has been attached to the USBD through hcdAttach( ).

Interrupt Behavior

The usbXXXEndLib driver relies on the underlying USBD and HCD layers to communicate with the USB Ethernet Networking Control Model devices (network adapters). The USBD and HCD layers, in turn, use the host controller interrupt for this communication. In this way, all interrupt-driven behavior is managed by the underlying USBD and HCD layers. Therefore, there is no need for the caller (or BSP) to connect interrupts on behalf of usbXXXEndLib. For the same reason, there is no post-interrupt-connect initialization code and usbXXXEndLib omits the devInit2 entry point.

The usbXXXEndLib driver inherently depends on the host controller interrupt for its communication with USB network adapters. Therefore, the driver supports only the interrupt mode of operation. Any attempt to place the driver in the polled mode returns an error.

ioctl Functions

The usbXXXEndLib driver supports the END ioctl interface. However, any attempt to place the driver in the polled mode returns an error.

Data Flow

The usbXXXEndLib driver's data transmission mechanism deviates slightly from the END standard in that all data is routed through the USBD. The XXXEndSend( ) routine fills in the pUSB_IRP structure and exports the structure to the USBD to send or receive the data.

IRPs are a mechanism for scheduling data transfers across the USB. For example, for the host to receive data from a network device, an IRP using the bulk input pipe is formatted and submitted to the USBD by the driver. When data becomes available, XXXEndRecv( ) is invoked by the IRP's callback to process the incoming packet.

Whenever data is transferred through the USBD using the pUSB_IRP structure, callback functions are passed to the USBD. These callback functions acknowledge the transmission of each packet of data. The execution of each callback function indicates that the corresponding data packet has been successfully transmitted.

1.10.2   Abstract Control Model Driver

All USB Abstract Control Model (ACM) devices (serial emulation devices) are modems. These devices are described in the USB Communication Class device specification document. These devices use the common AT commands ("attention" commands; also known as "Hayes-compatible" commands) to communicate with the devices that conform to the specification. In this section, Wind River uses the term "USB modem driver" interchangeably with the term "USB ACM class driver."

SIO Driver Model

The USB ACM class driver follows the VxWorks serial I/O (SIO) driver model, with certain exceptions and extensions. The reason for these differences is that the modems, traditionally, interface with the host by means of an RS232 serial line and are controlled as serial devices. The USB ACM class driver, therefore, provides the external APIs expected of a standard multimode serial I/O driver and adds extensions that support USB hotswap. The driver fills in an SIO_CHAN structure with the additional API routines and extensions, and exports the structure to the upper layers of the USB stack for control of the modem devices.

Dynamic Attachment

Because USB modems can be added to or deleted from the system at any time, the number of devices is dynamic. Clients of usbAcmLib can be made aware of the attachment and removal of devices. The driver includes a set of API calls that allows clients to register for notification upon the attachment or removal of USB modems. The attachment and removal of USB modems correspond, respectively, to the creation and deletion of SIO_CHAN structures.

In order to be notified of the attachment or removal of USB modems, clients should register with usbAcmLib by calling usbAcmCallbackRegister( ), with a callback code of USB_ACM_CALLBACK_ATTACH. While registering, clients must provide a callback function of the following type:

typedef STATUS (*USB_ACM_CALLBACK) 
    ( 
    pVOID arg,            /* caller-defined argument */ 
    SIO_CHAN * pChan,     /* pointer to the affected SIO_CHAN */ 
    UINT16 callbackType,  /* defined as USB_ACM_CALLBACK_XXXX */ 
    UINT8 * pBuf,         /* pointer to data buffer, if any data*/ 
                          /* transfer is involved. Otherwise NULL */ 
    UINT16 count          /* No. of bytes of data transferred if data */ 
                          /* transfer is involved. 0 otherwise. */ 
    ) 

When usbAcmLib detects a new USB modem, each registered callback is invoked with pChan pointing to a new SIO_CHAN structure and with callbackType set to USB_ACM_CALLBACK_ATTACH. Similarly, when a USB modem is removed from the system, each registered callback is invoked with pChan pointing to the SIO_CHAN structure of the removed device, and callbackType set to USB_ACM_CALLBACK_DETACH.

The usbAcmLib driver maintains a usage count for each SIO_CHAN structure. When a client uses the SIO_CHAN structure, it informs the driver by calling usbAcmLockSioChan( ). For each usbAcmLockSioChan( ) call, the driver increments the usage count. When a client is finished with the SIO_CHAN structure, it must notify the driver by calling usbAcmUnlockSioChan( ). The driver then decrements the usage count. Normally, if a modem is removed from the system when the usage count is zero (0), the driver releases the corresponding SIO_CHAN structure. However, clients that rely on this structure can use this locking mechanism to force the driver to retain the structure until it is no longer needed.

Initialization

The usbAcmLib driver is initialized through the usbAcmLibInit( ) routine. This API call, in turn, initializes internal resources needed for its operation and establishes its connection to the USBD.

All interactions with the USB host controller and devices are handled through the USBD. Therefore, before calling usbAcmLibInit( ), the user must ensure that the USBD has been properly initialized with usbdInitialize( ). Also, before modem operation can begin, the caller must ensure that the host controller has been attached to the USBD.

Interrupt Behavior

The usbAcmLib driver relies on the underlying USBD and HCD layers to communicate with the USB ACM devices (modems). The USBD and HCD layers, in turn, use the host controller interrupt for this communication. In this way, all interrupt-driven behavior is managed by the underlying USBD and HCD layers. Therefore, there is no need for the caller (or BSP) to connect interrupts on behalf of usbAcmLib. For the same reason, there is no post-interrupt-connect initialization code and usbAcmLib omits the devInit2 entry point.

The usbAcmLib driver inherently depends on the host controller interrupt for its communication with modems. Therefore, the driver supports only SIO_MODE_INT, the SIO interrupt mode of operation. Any attempt to place the driver in the polled mode returns an error.

ioctl Functions

The usbAcmLib driver supports the SIO ioctl interface. However, any attempt to place the driver in the polled mode returns an error. The driver supports additional ioctl functions in order to provide the ACM behavior detailed by the USB Communication Class devices specification.

Data Flow

The usbAcmLib exports the callback functions for the sending and receiving of data as required by the VxWorks SIO model. These callback functions provide data for transmission and accept any data received, character by character. This means that these callback functions are executed once for each character transmitted.

The practice of sending or receiving a single character at a time is typically intended for a traditional serial I/O device that has no buffer or very little buffer. For USB ACM devices, data is transmitted over bulk I/O pipes, with a packet size in multiples of 8. For example, a typical value is 64 bytes per packet. In this case, it makes sense for the API to allow transmission of data in packets, leaving the packet size to be determined by the maximum packet size of the bulk I/O pipe. Therefore, the driver exports another set of routines for sending and receiving data in bulk quantities. The following ioctl command retrieves the maximum packet size of the I/O pipe (that is, the buffer size):

UINT size; 
usbAcmIoctl (ptr, USB_ACM_SIO_GET_MAX_BUF_SIZE, &size) ;

Modem Control: Hayes Commands

The USB ACM specification indicates that modems must support the common AT command set for controlling modem behavior. The usbAcmLib driver exports two functions to support this feature:

  • a transmit function

  • a callback registration function. The user registers this callback function in order to be notified of the modem's response to the AT command.



1.11    Running the USB Kit

The Tornado project facility is a key element of the Tornado Integrated Development Environment (IDE). It provides graphical and automated mechanisms for creating applications that can be downloaded to VxWorks, for configuring VxWorks with selected features, and for creating applications that can be linked with a VxWorks image and started when the target system boots. The project facility provides mechanisms for the following actions:

  • Organizing the files that make up a project.

  • Grouping related projects into a workspace.

  • Customizing and scaling VxWorks.

  • Adding application initialization routines to VxWorks.

  • Defining varied sets of build options.

  • Building applications and VxWorks images.

  • Downloading application objects to the target.

For a tutorial introduction to the project facility, see the Tornado Getting Started Guide.

Creating a Downloadable VxWorks Image Including USB

From the Tornado project facility, start a downloadable VxWorks project based on your BSP. (For a list of BSPs supported with this release of the USB Kit, see the Release Notes.) The USB Kit software provides components that support USB functionality.

USB components are located in the component tree under the hardware folder. The following components can be found in hardware>buses>USB Hosts:

  • OHCI
  • OHCI Pci Init
  • UHCI
  • USB Host Stack
  • usbTool

The following components can be found in hardware>peripherals>USB Devices:

  • End - Pegasus
  • Keyboard
  • Mass Storage - Bulk
  • Mass Storage - CBI
  • Mouse
  • Printer
  • Speaker

Under each of these folders there is also a sub-folder that contains the initialization components for the USB modules.

For general instructions on using the project facility and creating projects, see the Tornado User's Guide: Projects.

For a basic USB setup, you must include the USB host stack component and at least one of the two host controller components. If you intend to test a device, you must include one or more of the device components.

Step 1:   Including the Host Stack

Selecting the USB host stack component includes support for the USBD.

Step 2:   Including a Host Controller

Selecting either the UHCI or OHCI components includes modules for those types of host controllers, respectively. Either host controller component requires that the USB host stack component also be selected. Both host controllers can be present on the image at once.


*      
NOTE: When you include the OHCI component, the OHCI Init component is required. If your BSP does not have pciAutoConfig( ), use OHCI Pci Init as an example of how the OHCI controller should be configured on the PCI bus. (For information about configuring the OHCI host controller on the PCI bus, see 1.15 BSP Porting Issues.) The OHCI Init component searches the PCI bus for an OHCI controller. The component searches through the list of supported host controllers. If it finds a match, it configures it and continues looking until it finds no more host controllers. If you are using a different OHCI controller, you must modify the configlette for the OHCI Init component. Add your Vendor ID (VID) and Product ID (PID) to the list of supported host controllers in the usrUsbPciOhciInit.c file. This configlette is located in target/config/comps/src.

Step 3:   Including Devices

Selecting any of the following USB device components includes the corresponding driver module. These components require that the USB stack be present on the image:

  • Keyboard
  • Mouse
  • Printer
  • Speaker
  • Mass Storage - Bulk
  • Mass Storage - CBI
  • END - Pegasus


1.12    Initialization

1.12.1   Host Stack and Host Controller Initialization

The USB Kit includes initialization configlettes for the USBD (host stack) and for both OHCI- and UHCI-type host controllers. In the initialization process, the USB initialization component makes a call to usbdInitialize( ). The host controller components then attach any host controllers existing in the system to the initialized USB stack.

1.12.2   Device Initialization

The USB Kit includes initialization components for all of the USB modules.


*      
CAUTION: When you use the initialization components, usbTool may not be used. The components were derived from usbTool itself, and cause compile-time errors when used concurrently with usbTool.

Keyboard, Mouse, Printer, and Speaker Initialization

The keyboard, mouse, printer, and speaker drivers contain initialization routines that install standard open, close, read, write, and ioctl functions into the I/O system driver table. This allows an application to call these drivers using standard VxWorks system calls.

For example, an application might want to monitor a keyboard's input. First, the application opens the keyboard with the system call to open( ):

fileDescr = open ("/usbkb/0", 2, 0);

The application can now call the system's read( ) routine in a loop:

read (fileDescr, &inChar, 1);

These operations can be used for the mouse, printer, and speaker drivers as well.

Mass Storage Class Device Initialization

The Bulk-Only and CBI Mass Storage Class driver configlettes install standard functions into a file system. As with the mouse, keyboard, speaker, and printer drivers, these functions allow an application to make standard VxWorks system calls such as copy, rm, and format (depending on which file system is attached) to access the Mass Storage Class device.

After a USB block device has been created by means of usbMSCBlkDevCreate( ) (see 1.9 Mass Storage Class Driver, for the possible values of MSC in this API name), a file system can be attached to the device as follows:

/* attach DOS file system to the USB drive */ 
pBulkDosVol = dosFsDevCreate ("/usbDr0", pBulkBlkDev, MAX_NO_FILES, 0);

Now calls such as copy( ) can refer to the device as usbDr0. In the following code fragment, the file readme.txt is copied from a host to the USB drive usbDr0.

copy ("host:/readme.txt", "/usbDr0/readme.txt");
SCSI6 Commands

In internal testing, Wind River has found that one supported drive, the M-Systems FlashOnKey device, does not support SCSI6 commands. This may also be the case for some non-supported drives. Therefore, the Bulk-Only driver supports both SCSI-6 and SCSI-10 read/write commands. The user must configure the driver to use the appropriate command for communicating with the device.

The fourth parameter of usbMSCBlkDevCreate( ) sets the SCSI transfer mode. It can take either of the following values: 

USB_SCSI_FLAG_READ_WRITE10

Use SCSI read/write 10.

USB_SCSI_FLAG_READ_WRITE6

Use SCSI read/write 6.

 

Communication Class Device Initialization

The USB Kit includes a configlette, called usrUsbPegasusEndInit.c, to initialize the Pegasus Communication Class driver. Upon device insertion, these routines connect a Pegasus device to the network stack, attaching an IP address to the device through a specified gateway address.

The IP address, gateway, host name, and a net mask are all user-definable parameters of the component and must be set before communication with the Pegasus device can occur.

When a Communication Class device has been successfully connected, it can be used like any other network device.

Like all USB devices, a Communication Class device can be inserted or removed at any time. If the device is unplugged, any resources devoted to it are freed, and the system node that was created for it is removed.


*      
NOTE: In most cases, when support for a USB Communication Class device is added, two network devices exist on the system as a result. To make this possible, you must increase the value of IP_MAX_UNITS to 2. This value is a parameter to the network buffer initialization component. This component is found under network components>basic network initialization components>network buffer initialization.

1.12.3   usbAudio Initialization

The USB Kit includes a sample application called usbAudio, which demonstrates how to use the USB stack and the speaker class driver. This application was designed to run on a pcPentium machine that contains both a host controller (OHCI or UHCI) and an ATA hard drive containing .wav files.

The usbAudio program operates with any of the listed supported speakers.

1.12.4   usbTool Code Exerciser

The USB Kit includes a utility called usbTool which you can use to exercise the modules that comprise the USB stack. For example, if you were implementing a different device driver, you can use usbTool to exercise and debug your code.

The usbTool utility provides a basic code skeleton that you can customize to suit your test needs.


*      
NOTE: The usbTool module relies internally on static data; you should not run multiple instances of usbTool simultaneously.

Running usbTool from the Shell

  1. To use the tool from the shell, type the entry point of the tool at the shell prompt, as follows:
->usbTool

This produces a new prompt where commands can be entered:

usb>
  1. To get a list of all usbTool commands and their definitions, type the following:
usb>?


*      
CAUTION: When running usbTool on a host shell, you may encounter a redirection problem that prevents entered commands from being echoed back to the shell. You can avoid this issue by running usbTool on a target shell.

The usbTool Execution Sequence

The usbTool utility allows the user to see a typical execution sequence needed to get the USB up and running.

When an image that includes USB components boots, it automatically executes a sequence of events similar to using the usbTool utility:

  1. To initialize the USB host stack, enter the usbInit command at the usbTool prompt.
  1. To initialize an OHCI or UHCI host controller, enter the attach uhci or attach ohci commands at the usbTool prompt.
  1. If you plan to use a device driver, after initializing the host stack and host controller, enter the initialization command for that driver at the usbTool prompt.

To test any included devices, enter the appropriate test command, for example:

usb>mouseTest


*      
NOTE: Selecting any of the device driver components (keyboard, mouse, printer, speaker) includes the device test commands into usbTool. If the components are not included, the commands are not available.

The following commands are available when their associated device components are included in your VxWorks image:

mouse

keyboard


printer

speaker

For descriptions of these commands, invoke the help list from usbTool.


*      
NOTE: Command names do not have to be fully entered to be executed. For example, usbInit can be entered as usbi.



1.13    Booting VxWorks Through a Communication Class Driver

You can use the Pegasus USB Ethernet driver to build a bootable VxWorks image. At the time of this release, only the Pegasus driver supported booting.

In order to make a USB boot ROM, you must modify several BSP files. However, it is not recommended that you alter the original BSP system files. Instead, make a copy of the files so that you can revert to the original BSP configuration if needed.

The following configuration files must be altered in order to build a bootable VxWorks image:

  • config.h
  • configNet.h
  • sysLib.c
  • bootConfig.c

The sections below provide details on how to alter these files.

In config.h

In this file, you must define the USB parts that are required.

Because this method of booting VxWorks uses a USB networking device for booting, you must undefine the default boot device. The following line undefines the default boot device for a pcPentium system:

#undef    INCLUDE_FEI

The native network support component (in this case, FEI) conditionally includes the network stack type (in this case, END) as well as the network device connection type (in this case, PCI). Therefore, in addition to including the USB components, you must also re-include the components for network stack type and network device connection type.

The following code fragment demonstrates how to include in config.h the components necessary for booting:

#define INCLUDE_USB_PEGASUS_END 
    #if defined(INCLUDE_USB_PEGASUS_END) 
 
    #ifndef INCLUDE_USB 
    #define INCLUDE_USB 
    #endif 
 
    #ifndef INCLUDE_OHCI 
    #   define INCLUDE_OHCI_PCI_INIT 
    #   define INCLUDE_OHCI 
    #endif 
 
    #ifndef INCLUDE_PCI 
    #   define INCLUDE_PCI 
    #endif 
 
    #ifndef INCLUDE_END 
    #   define INCLUDE_END 
    #endif 
 
    #endif 
 
#endif
In configNet.h

This file contains the definitions of the network load routine and other parameters that are to be included in the endDrvTbl[ ]. This table tells muxDevLoad( ) which network method should be called when the network stack is bringing up the network device.

  1. First, define the load routine for the driver. The load routine for the Pegasus driver is contained in usrUsbPegasusInit.c under config/comps/src. For example:
/* Define the Pegasus load routine */ 
#ifdef INCLUDE_USB_PEGASUS_END 
#define END_PEGASUS_LOAD_FUNC     sysUsbPegasusEndLoad 
#define END_PEGASUS_BUFF_LOAN     1 
#define END_PEGASUS_LOAD_STRING   "" 
 
IMPORT END_OBJ * END_PEGASUS_LOAD_FUNC (char *, void*);
  1. Then add these definitions to the endDevTbl[ ] array. For example:
END_TBL_ENTRY endDevTbl [] = 
{ 
/* Include the Pegasus load routine in the table */ 
#ifdef INCLUDE_USB_PEGASUS_END 
    {0, END_PEGASUS_LOAD_FUNC, END_PEGASUS_LOAD_STRING,  \ 
    END_PEGASUS_BUFF_LOAN, NULL}, 
#endif /* INCLUDE_USB_PEGASUS_END */ 
... 
... 
... 
    { 0, END_TBL_END, NULL, 0, NULL, FALSE}, 
    }; 
In sysLib.c

Add a section to this file to include the configlette files for the initialization of the USB stack and the network driver. For example, the following code includes the USB stack, the OHCI driver, and the Pegasus network driver initialization code:

/* Include USB stack initialization */ 
#ifdef INCLUDE_USB 
#include usrUsbInit.c 
#endif 
 
/* Include PCI enumeration of OHCI card */ 
#ifdef INCLUDE_OHCI_PCI_INIT 
#include usrUsbPciOhciInit.c 
#endif 
 
/* Include the OHCI driver initialization */ 
#ifdef INCLUDE_OHCI 
#include usrUsbHcdOhciInit.c 
#endif 
 
/* Include the Pegasus network driver initialization */ 
#ifdef INCLUDE_PEGASUS 
#include usrUsbPegasusInit.c 
#endif

For more information on the files named in this example, see 1.12 Initialization.

In target/config/all/bootConfig.c

This file contains the calls to the USB routines.

The boot ROM you make for booting VxWorks must do the following:

  1. Initialize the USB host stack through a call to usbdInitialize( ).
  1. Attach a host controller to the USBD. In the example below, an OHCI controller is attached.
  1. Initialize the USB network driver. The example below uses the Pegasus driver, as it is the only device that supports USB booting.

Create a routine like the one below. It should be called from usrRoot( ) after sysClkEnable( ).

/**************************************************************************** 
* 
* usbOhciInit - initialize the USB stack w/ OHCI controller 
* 
* RETURNS: Nothing. 
*/ 
 
LOCAL void usbOhciInit 
    ( 
    ) 
    { 
    usbInit (); 
    sysUsbPciOhciInit (); 
    usrUsbHcdOhciAttach (); 
    taskDelay (sysClkRateGet()*3); 
    usbPegasusEndInit (); 
    }

A call to your function should appear as follows:

sysClkEnable ();                /* start it */ 
 
/* Begin USB initialization */ 
sysUsbPciOhciInit();            /* map OHCI card into PCI space */ 
usbOhciInit();                  /* initialize stack, attach HC, initialize */ 
                                /* network driver */ 
taskDelay (sysClkRateGet()*5);  /* gratuitous delay to allow bus traffic */ 
                                /* to settle */ 
/* End USB initialization */ 
 
/* initialize I/O and file system */ 
iosInit (NUM_DRIVERS, NUM_FILES, "/null");

With these modifications, you can build a boot ROM in the BSP with the following command:

make bootrom_uncmp

Now USB can be used as a boot parameter:

           VxWorks System Boot 
 
Copyright 1984-1998 Wind River Systems, Inc. 
CPU: PCPENTIUM 
Version: 5.4.2 
BSP version: 1.2/0 
Creation date: Jun 14 2001, 10:03:12 
Press any key to stop auto-boot... 
@2
boot device                    usb 
unit number                    0 
processor number                    0 
host name                    host 
file name                    path/vxWorks 
inet on ethernet (e)        90.0.0.50 
host inet (h)                    90.0.0.3 
user (u)                    target 
flags (f)                    0x0


1.14    Benchmarking Information

This section provides information to help you benchmark your test results. The metrics shown in this section are derived using spyLib, a VxWorks library that displays information on task execution and CPU utilization.

The sample test described in this section was performed on a project created with the following components included:

  • USB stack and its associated initialization components
  • OHCI driver and its associated initialization components
  • Speaker driver and its associated initialization components
  • Mass Storage Class driver and its associated initialization components
  • Speaker demo program
  • spyLib
  • Target shell

This test was performed on a 400 MHz Pentium II machine with the following features:

  • 128 MB RAM
  • Lucent Quadrabus 4-port OHCI card
  • Philips USB speakers
  • ORB 2.2 GB removable media USB drive

This test used the usbAudio demo program's play command to play a .wav file through the speakers. The Mass Storage Class driver initialization code was allowed use of the standard calls to the ORB drive, such as copy, rm, and format.

To perform the test, a VxWorks image was built with all of the components listed above. Two host shells and a target shell were used for entering commands. Using one host shell, a large (~8 MB) file was copied from the host machine to the ORB drive. Immediately following, on the other host shell, a .wav file was played using the usbAudio tool's play command. The .wav file was approximately 2 MB and had the following format: 

Size of Format:

16

Format:

Pulse Code Modulation (PCM)

Channels:

1

Sample Rate:

22050

Bytes per Second:

44100

 

While the .wav file was being transferred and sound was playing, spyLib was called, to display all tasks' CPU usage. To invoke spyLib, give the following command through the target shell:

->spy 10, 200

This command to spyLib produces a report every 10 seconds, displaying data that was gathered at the rate of 200 times per second. The following 3 sample reports, Example 2, Example 3, and Example 4, were produced during the time the data transfers were in progress.

Example 2:   spyLib Results Before USB Transfers

NAME       ENTRY       TID         PRI   total % (ticks) delta % (ticks) 
---------- --------    -------     ---   --------------- ---------------- 
tUsbdClnt              3fd0604     0     0% ( 0)         0% ( 0) 
tOhciInt               3fb9da4     0     0% ( 0)         0% ( 0) 
tOhciInt               3f9fdb4     0     0% ( 0)         0% ( 0) 
tOhciInt               3f85da4     0     0% ( 0)         0% ( 0) 
tOhciInt               3f6bdf0     0     0% ( 0)         0% ( 0) 
tExcTask    excTask    3f632d0     0     0% ( 0)         0% ( 0) 
tLogTask    logTask    3f609cc     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f5cb38     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f36e54     0     0% ( 0)         0% ( 0) 
tUsbdBus               3fb595c     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f9b9a8     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f81998     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f679fc     1     0% ( 0)         0% ( 0) 
tShel       shell      3ea3c80           0% ( 0)         0% ( 0) 
tWdbTask               3ea4e14     3     0% ( 0)         0% ( 0) 
tPlay                  36f4d88     4     0% ( 0)         0% ( 0) 
tBulkClnt              3f327fc     5     0% ( 0)         0% ( 0) 
tSpyTask    spyComTask 35a37f4     5     0% ( 0)         0% ( 0) 
tNetTask    netTask    3ef0ed0     50    0% ( 0)         0% ( 0) 
KERNEL                                   0% ( 0)         0% ( 0) 
INTERRUPT                                0% ( 0)         0% ( 0) 
IDLE                                     0% ( 0)         0% ( 0) 
TOTAL                                    0% ( 1)         0% ( 1) 

Example 3 records when the data transfers begin to be logged.

Example 3:   spyLib Results During USB Transfers

NAME       ENTRY       TID         PRI   total % (ticks) delta % (ticks) 
---------- --------    -------     ---   --------------- ---------------- 
tUsbdClnt              3fd0604     0     0% ( 0)         0% ( 0) 
tOhciInt               3fb9da4     0     1% ( 24)        1% ( 12) 
tOhciInt               3f9fdb4     0     0% ( 2)         0% ( 0) 
tOhciInt               3f85da4     0     0% ( 0)         0% ( 0) 
tOhciInt               3f6bdf0     0     0% ( 0)         0% ( 0) 
tExcTask    excTask    3f632d0     0     0% ( 0)         0% ( 0) 
tLogTask    logTask    3f609cc     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f5cb38     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f36e54     0     0% ( 0)         0% ( 0) 
tUsbdBus               3fb595c     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f9b9a8     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f81998     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f679fc     1     0% ( 0)         0% ( 0) 
tShell      shell      3ea3c80     1     3% ( 52)        3% ( 25) 
tWdbTask               3ea4e14     3     0% ( 0)         0% ( 0) 
tPlay                  36f4d88     4     0% ( 0)         0% ( 0) 
tBulkClnt              3f327fc     5     0% ( 0)         0% ( 0) 
tSpyTask    spyComTask 35a37f4     5     0% ( 1)         0% ( 1) 
tNetTask    netTask    3ef0ed0     50    0% ( 6)         0% ( 0) 
KERNEL                                   0% ( 3)         0% ( 1) 
INTERRUPT                                0% ( 9)         0% ( 3) 
IDLE                                     93%( 1363)      94% ( 687) 
TOTAL                                    97%( 1461)      98% ( 729) 

Example 4:   spyLib Results During USB Transfers

NAME       ENTRY       TID         PRI   total % (ticks) delta % (ticks) 
---------- --------    -------     ---   --------------- ---------------- 
tUsbdClnt              3fd0604     0     0% ( 0)         0% ( 0) 
tOhciInt               3fb9da4     0     1% ( 30)        0% ( 6) 
tOhciInt               3f9fdb4     0     0% ( 3)         0% ( 1) 
tOhciInt               3f85da4     0     0% ( 0)         0% ( 0) 
tOhciInt               3f6bdf0     0     0% ( 0)         0% ( 0) 
tExcTask   excTask     3f632d0     0     0% ( 0)         0% ( 0) 
tLogTask   logTask     3f609cc     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f5cb38     0     0% ( 0)         0% ( 0) 
tUsbdClnt              3f36e54     0     0% ( 0)         0% ( 0) 
tUsbdBus               3fb595c     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f9b9a8     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f81998     1     0% ( 0)         0% ( 0) 
tUsbdBus               3f679fc     1     0% ( 0)         0% ( 0) 
tShell     shell       3ea3c80     1     3% ( 78)        3% ( 26) 
tWdbTask               3ea4e14     3     0% ( 0)         0% ( 0) 
tPlay                  36f4d88     4     0% ( 0)         0% ( 0) 
tBulkClnt              3f327fc     5     0% ( 0)         0% ( 0) 
tSpyTask   spyComTask  35a37f4     5     0% ( 2)         0% ( 1) 
tNetTask   netTask     3ef0ed0     50    0% ( 7)         0% ( 1) 
KERNEL                                   0% ( 3)         0% ( 0) 
INTERRUPT                                0% ( 17)        1% ( 8) 
IDLE                                     93%( 2048)      94% ( 685) 
TOTAL                                    97%( 2189)      98% ( 728) 

The OHCI task, which is responsible for handling the traffic, occupies a minimum amount of CPU time. (Four such tasks are shown in the reports above: one for each port on the Lucent card.)

In addition to using spyLib, data throughput rates were determined using a CATC USB bus analyzer. The CATC is a bus sniffer that allows USB bus traffic to be monitored. The traces obtained from this hardware were used to calculate the bandwidth of this VxWorks host. These bandwidth calculations were obtained from the traffic during a file transfer to the ORB drive only.

The ORB drive implements a 64-byte output endpoint for sending data to a host. It sends 512-byte chunks per transfer. The CATC trace showed transfers over a 499-millisecond period of time. Using the amount of raw data transferred in this interval, a throughput rate of 6 Mbps was calculated.

This number most likely points to the device, not the host, as the bottleneck. Identical rates were also found using a Windows 98 Second Edition host.



1.15    BSP Porting Issues

The Wind River USB driver stack has been designed to ease porting across Wind River BSP platforms. At the source code level, the stack has been tested successfully on both big and little-endian platforms and across several Wind River BSPs. (See USB Developer's Kit Release Notes: Tested Devices.)

You must address two key issues when porting the USB stack to a new BSP:

  • The BSP startup code must configure the USB hardware prior to the initialization of the USB stack.

  • For UHCI and OHCI host stacks, the usbPciLib module (that is, the various usbPciStub.c files) must be modified to use the correct PCI functions in the underlying BSP.

The following sections discuss each of these issues in detail.

1.15.1   Configuring USB Hardware

As shipped, the Wind River USB stack supports both UHCI and OHCI USB host controllers. In all systems tested to date, both the UHCI and OHCI host controllers are implemented as PCI devices. As with any PCI device, the UHCI and OHCI host controllers need to be configured to use specific PCI resources.

A PCI UHCI host controller, for example, uses a single I/O address range and one interrupt channel. A PCI OHCI controller, on the other hand, uses a single memory address range and one interrupt channel. Both types of controllers need to be assigned a unique address range and an interrupt channel, as well as enabled for bus master operation on the PCI bus; these activities are referred to in this manual as configuring the USB hardware.

It is important to note that the Wind River USB stack itself makes no attempt to assign such PCI resources. Instead, the stack expects that the USB host controller(s) are already assigned resources and enabled before an attempt is made to attach the specific host controller to the USBD.

BSPs with pciAutoConfigLib

Certain Wind River BSPs include the module pciAutoConfigLib; in these BSPs, the configuration of each PCI device is handled automatically. The only PCI devices not configured automatically are those listed in the PCI auto-config exclude list which is unique for each BSP. So, in order to configure a UHCI or OHCI host controller in these systems, you should ensure that the USB host controller is not in the exclude list.

For more information about pciAutoConfigLib, see your BSP documentation.

BSPs Without pciAutoConfigLib

In BSPs that do not include pciAutoConfigLib, the assignment of PCI resources and enabling of each PCI device is generally handled in sysLib.c. You must write code that explicitly assigns certain PCI resources to each PCI device to be configured. That can be done in a configlette or by modifying sysLib.c directly.


*      
NOTE: For this release, this functionality is provided by a configlette; see the component description file (CDF) in target/config/comps/vxWorks/src.

The following example was written for the mtx604 BSP to enable an OPTi FireLink 82C861 OHCI host controller. Use this code in a configlette or add it directly to sysLib.c:

/**************************************************************************** 
* 
* sysUsbPciOhciInit - configures OHCI adapter 
* 
* This routine attempts to locate an OHCI controller. If found, it sets up 
* the controller's PCI configuration. 
* 
* RETURNS: N/A 
*/ 
 
void sysUsbPciOhciInit (void) 
    { 
    int pciBus; 
    int pciDevice; 
    int pciFunc; 
 
/* Attempt to locate the OHCI controller. */ 
if (pciFindDevice (0x1045 /* OPTi */, 0xc861 /* 82c861 */, 0,  
        &pciBus, &pciDevice, &pciFunc) != OK) 
        return; 
 
/* Set up the base memory address for the controller. */ 
pciDevConfig (pciBus, pciDevice, pciFunc, NULL, PCI_MEM_ADRS,  
        PCI_CMD_MASTER_ENABLE | PCI_CMD_MEM_ENABLE); 
 
/* Set up the controller's interrupt assignment. */ 
pciConfigOutByte (pciBus, pciDevice, pciFunc, PCI_CFG_DEV_INT_LINE,  
        0xb /* based on PCI "slot" number */); 
    } 

The sysUsbPciOhciInit( ) routine is called at the end of sysHwInit( ) in sysLib.c.

In this example, the OHCI controller needs a memory address range through which usbHcdOhciLib reads and writes the controller's memory-mapped registers. The choice of the address, PCI_MEM_ADRS-- the fifth parameter to pciDevConfig( )--was appropriate for temporary testing on the mtx604 BSP. You should make a permanent selection for your particular BSP and create constants that document this assignment.

Similarly, in this example, the interrupt channel 0xb is assigned to the OHCI controller. The mtx604 motherboard routes the physical PCI slot to interrupt channel 0xb. The interrupt channel number in your BSP or hardware environment is likely to be different, and your code should reflect this.

The choice of a specific address range or interrupt channel depends entirely on the underlying hardware configuration.

1.15.2   Configuring Memory Address Windows

When using PCI devices that require memory address windows, such as OHCI host controllers, you must make sure that a memory mapping has been created, allowing the processor to "see" the memory window assigned to the PCI device. In the sysUsbPciOhciInit( ) example in BSPs with pciAutoConfigLib, the memory window beginning at PCI_MEM_ADRS already has a mapping on the mtx604 platform, and no code modifications are required.

However, other BSP platforms might require alterations, for example, the pcPentium platform. The following code fragment creates a CPU memory mapping entry for the pcPentium. Add this code at the end of sysHwInit( ) in sysLib.c, either through a configlette or by manually inserting it:

    int pciBus; 
    int pciDevice; 
    int pciFunc; 
    UINT32 membaseCsr; 
 
    /* Find the OPTi controller. */ 
    if (pciFindDevice (0x1045 /* OPTi */, 0xc861 /* 82C861 */, 0,  
        &pciBus, &pciDevice, &pciFunc) != OK) 
        return; 
 
    /* get memory base address. 
     * 
     * NOTE: We're assuming here that the memory address range was assigned 
     * elsewhere, perhaps by pciAutoConfigLib. 
     */ 
    pciConfigInLong (pciBus, pciDevice, pciFunc, PCI_CFG_BASE_ADDRESS_0, 
                        &membaseCsr); membaseCsr &= PCI_MEMBASE_MASK; 
 
    /* add the entry to sysPhysMemDesc[]. 
     * 
     * Note: This code must be executed before usrMmuInit(). 
     */ 
    sysMmuMapAdd ((void *) membaseCsr, 0x1000 /* 4k window */,  
        0x3f /* memory attribute mask */, 0x5 /* memory attributes */); 

The final function call in the above code, sysMmuMapAdd( ), creates a memory mapping on the pcPentium platform. The mechanism for creating such a mapping will vary from BSP to BSP. For more information, check the relevant BSP documentation.

1.15.3   Creating BSP-Specific Stub usbPciLib Files

As noted earlier, the stubUsbArchPciLib.c files shield the UHCI and OHCI HCDs from variations among underlying BSPs. These files provide the following routines, however the underlying PCI functionality may vary from one BSP to the next.

8-bit, 16-bit, and 32-bit data I/O:

usbPciByteIn( )

usbPciWordIn( )

usbPciDwordIn( )

usbPciByteOut( )

usbPciWordOut( )

usbPciDwordOut( )

Address range translation functions:

usbPciMemOffset( )

usbMemToPci( )

usbPciToMem( )

Interrupt functions:

usbPciIntConnect( )

usbPciIntRestore( )


*      
NOTE: Certain Wind River BSPs do not support disconnection from the underlying PCI interrupt vector. See USB Developer's Kit Release Notes: BSP-Specific Issues.

Internally, the stubUsbArchPciLib.c files implement the above routines by defining a series of macros. Definitions may vary from BSP to BSP.

The following code excerpt from stubUsbPpcPciLib.c shows how the necessary macros are defined for the mcp750 and mtx604. Creating your own stub file for a new platform generally involves creating a comparable set of definitions customized for that platform.

/* Power PC 604 (e.g., mcp750) */ 
 
#include "mv2600.h" 
 
/* Mappings for I/O and memory address translation (values from mv2600.h) */ 
#define PCI_IO_OFFSET       CPU_PCI_ISA_IO_ADRS 
#define PCI_MEM_OFFSET      PCI2DRAM_BASE_ADRS 
#define PCI_MEMIO_OFFSET    (CPU_PCI_MEM_ADRS - PCI_MEM_ADRS) 
 
/* The mcp750 doesn't define sysInWord, sysInLong, sysOutWord, and 
 * sysOutLong. It includes the following entry points instead for which 
 * there is no vxWorks function prototype. We include the following 
 * definitions in order to suppress compiler warnings. 
 */ 
extern USHORT sysIn16 (int port); 
extern ULONG sysIn32 (int port); 
extern void      sysOut16 (int port, short data); 
extern void      sysOut32 (int port, long data); 
 
/* Map the "standard" vxworks I/O functions to the mcp750 equivalents */ 
#define sysInWord(p)     sysIn16(p) 
#define sysInLong(p)     sysIn32(p) 
#define sysOutWord(p,d)  sysOut16(p,d) 
#define sysOutLong(p,d)  sysOut32(p,d) 
 
/* Interrupt enable/disable */ 
#ifdef  MCP750 
#define INTERRUPT_BASE  ISA_INTERRUPT_BASE 
#else 
#define INTERRUPT_BASE  EXT_INTERRUPT_BASE 
#endif 
 
#define IVEC(i)         INUM_TO_IVEC ((int) ((i) + INTERRUPT_BASE)) 
 
#define INT_CONNECT(intNo, func, param) \ 
    intConnect (IVEC (intNo), (VOIDFUNCPTR) func, (int) param) 
 
#define INT_DISCONNECT(intNo, func, param) 
 
#define INT_ENABLE(i)   intEnable ((i) + INTERRUPT_BASE) 
#define INT_DISABLE(i)  intDisable ((i) + INTERRUPT_BASE) 
 
/* Map I/O functions to underlying system functions. */ 
#define OUR_PCI_IN_BYTE(a)        sysInByte ((a) + PCI_IO_OFFSET) 
#define OUR_PCI_IN_WORD(a)        sysInWord ((a) + PCI_IO_OFFSET) 
#define OUR_PCI_IN_DWORD(a)       sysInLong ((a) + PCI_IO_OFFSET) 
#define OUR_PCI_OUT_BYTE(a,v)     sysOutByte ((a) + PCI_IO_OFFSET, (v)) 
#define OUR_PCI_OUT_WORD(a,v)     sysOutWord ((a) + PCI_IO_OFFSET, (v)) 
#define OUR_PCI_OUT_DWORD(a,v)    sysOutLong ((a) + PCI_IO_OFFSET, (v)) 


1:  Unlike IEEE-1394, the USB treats an isochronous transfer as an exchange between the USB host and a specific USB device. In contrast, 1394 treats isochronous transfers as a broadcast to which any number of 1394 devices can listen simultaneously.

2:  Available from Global Engineering, 800-854-7179.

3:  Also available from Global Engineering.