Name

Host Device Object — Structure and Interface

Synopsis

#include <cyg/io/usb.h>
    

void usb_device_ref(usb_device *dev);

void usb_device_unref(usb_device *dev);

Description

Whenever a new device or hub is attached to a USB, a device object is created to represent it in the USB stack. Most users of the USB stack do not need to concern themselves with the contents of a device object, so most of the information here is for the use of USB stack developers.

Host Device Object

This structure does double duty for both standard devices and for hubs. The internal union separates out the role specific fields. This structure also has two typedef names, usb_device and usb_hub, which can be used interchangeably, but help keep track of which role the structure is currently being used in. A device object has the following structure:

struct usb_device
{
    usb_node                    node;           // Node in per-hub list
    usb_bus                     *bus;           // Controlling bus
    usb_hub                     *parent;        // Parent hub

    usb_uint32                  flags;          // Flags
    usb_uint8                   refcount;       // Reference counter
    usb_uint8                   id;             // Device ID
    usb_uint8                   port;           // Port on parent hub
    usb_uint8                   state;          // Current device state
    usb_uint8                   state_data;     // Associated data

    usb_device_descriptor       desc;           // Device descriptor
    usb_descriptor              *config;        // Current configuration
    usb_descriptor              *interface;     // Current interface
    usb_descriptor              *desc_chain;    // Chain of all descriptors

    usb_resource_client         res_client;     // Resource client

    void                        *hcd_priv;      // HCD private data

    usb_device_endpoint         *endpoints;     // List of active endpoints

    union
    {
        struct
        {
            union
            {
                // Used only during initialization
                usb_uint8               *buf;          // Descriptor read buffer
                usb_config_descriptor   *config;       // Config descriptor view of buf

                // Used only when state >= RUNNING
                usb_class_driver        *class_driver; // Attached class driver

            };

        } device;
        struct
        {
            int                 port_count;     // Number of downstream ports
            usb_hub_port_status port_status[USB_HUB_PORT_MAX+1];

            usb_uint8           *buf;           // Descriptor read buffer

            usb_list            devices;        // List of attached devices

            // Status tfr state
            usb_tfr             *status_tfr;    // Current status change tfr
            usb_uint8           status_buf[4];  // Status change buffer
            usb_descriptor      *intr_desc;     // Interrupt endpoint descriptor

            // TODO: Power allocation stuff

            // TODO: Transaction translator stuff
        } hub;
    };
};

The fields of the device are as follows:

node
A list node that is used to link this device into a list in the hub object to which this device is attached.
bus
A pointer to the object representing the bus to which this device is attached. This pointer provides access to the HCD that communicates with this device. The bus object also controls the allocation of device IDs.
parent
A pointer to the parent hub, the same hub in whose device list this device must be linked via the node field.
flags
Flag bits controlling aspects of this device. The USB_DEVICE_FLAGS_HUB indicates that this device is a hub, and USB_DEVICE_FLAGS_HUB_ROOT indicates that this is a root hub.
refcount
Device reference count. This is initialized to 1 when the device is first attached, representing a reference held by the physical device, and incremented for each active transfer for this device. Class drivers may also take their own references. When the physical device is detached the refcount is decremented, which should result in the device object being freed.
id
Device ID. Each device starts with an ID of zero while it is being configured. This field will be set to the allocated ID once the physical device has had its address set successfully.
port
This is the port number within the parent hub to which this device is attached.
state
Device state. During their lifetime devices pass through a set of different states. This field described what state the device is currently in.
state_data
Some device states need some additional data in addition to the state. That data is stored here.
desc
During initialization the USB stack will read the device device descriptor and store it here.
config
When the device has been configured, this will point to the descriptor for the configuration that has been set in the physical device.
interface
When the device has been configured, this will point to the descriptor for the interface that has been set in the physical device.
desc_chain
This points to a chain of usb_descriptor objects which contain all the configuration, interface and endpoint descriptors that have been read from the physical device. They are stored in a single linear chain with the descriptors for each configuration chained on to the end of the previous descriptor chain.
res_client
This structure is used internally by the USB stack to enable devices to wait for resources such as memory, or to implement delays.
hcd_priv
This is a pointer to private data defined by the HCD that controls the physical device. It is copied from the hcd_priv field of the usb_bus object from which the device attachment was detected.
endpoints
This points to a chain of usb_device_endpoint objects which associate an endpoint descriptor with an HCD supplied pointer that implements that endpoint. Only the endpoints for the interface currently selected appear in this list, together with endpoint 0 for control packets.
device
This sub-structure is part of an anonymous union that provides fields for either devices or hubs, depending on the flags field. At present this contains an anonymous union that contains either a pointer to a buffer used to read configurations, or during normal running a pointer to the class driver that is using this device.
hub
This is the second element of the anonymous union and contains field used if this device is a hub. This contains a number of fields that are mainly used internally by the USB stack. Included are a count of the number of downstream ports the hub contains together with the most recent reported state of each and a list of the devices attached to this hub. As hub support evolves, this sub-structure will acquire further fields.

Device Lifecycle

From initial attachment through configuration, data transfer and final detachment, a device goes through a lifecycle in the USB stack. This section looks at this lifecycle.

When a physical device is attached to a port on a hub the state change is detected by the hub state machine (see Hub Lifecycle). This results in a device object being created and initialized. The refcount is set to 1, representing a reference held by the physical device.

A device runs through a state machine that is generally run in the callbacks of transfers, delays and resource allocations. Each state assesses the result of the previous operation, issues new transfers/delays/allocations, sets the next state and waits for completion. States are generally named for the operation for which they waiting to complete. The device moves through the following states:

NEW
This is the initial state, the device is further initialized to have an endpoint for device 0 endpoint 0. A request is set up to wait for allocation of the shared configuration buffer.
BUFFER
This state is entered when the shared configuration buffer has been allocated. This buffer allocation has two purposes. First, it provides us with a buffer large enough to read entire configurations into. Second, and more importantly, it serializes all device initializations, which is necessary before setting the device address. The device sends a control packet to the hub to clear the connect change bit. The state_data field is initialized to contain a pair of 4 bit counters which count the number of reset and port status retries that have been tried.
CLEAR_CONNECT
Once the connect change bit has been cleared, the device sends a control command to the hub to reset the physical device. This puts the device into a state where it responds to commands sent to device ID 0.
RESETTING
After the reset command has been sent, the device waits 200ms for the reset to complete.
RESET_DELAY
After the delay, a control request is sent to the hub to get the status of the port.
PORT_STATUS
The result of the port status request is analyzed. If the device appears to have disconnected, then the state machine is terminated, and the detach event will be detected by the hub state machine. If the port status indicates that the device has not been reset, then the port status retry counter is decremented, and after a delay the state machine goes back to the RESETTING state, to re-submit the port status request. If the port status counter is zero, then a clear port enable command is sent, the reset retry counter is decremented, the port status counter reset to its original value and the next state set to CLEAR_CONNECT. This will cause the port to be reset again. If both retry counters are zero, then the device is considered unusable and the device state set to UNDEFINED. If the device has been rest and enabled then the reset is successful. A control command is sent to the hub to clear the reset change bit in the port and the next state set to CLEAR_RESET.
CLEAR_RESET
In this state we are reasonably sure that the device has been reset correctly, it should respond to control commands sent to device ID 0. A new device ID is allocated from the usb_bus object and stored in the state_data field. A control command is now sent to device ID 0 to set the address of the device to the allocated value. The next state is set to ADDRESS.
ADDRESS
If the attempt to set the address failed then the device is disabled, the ID freed and the state set to CLEAR_CONNECT to go through the rest and port status cycle again. If it was successful then the device ID is set to the allocated value and a new control endpoint is attached in the HCD. A control request is sent to the device to read the device descriptor into the buffer and the next state set to DEV_DESC.
DEV_DESC
Once the device descriptor has been successfully read, it is copied into the desc field of the device object. A request is now sent to read the first 9 bytes of the first configuration descriptor into the buffer. The next state is set to CFG_DESC and the state_data set to zero.
CFG_DESC
From the first 9 bytes of the configuration descriptor it is possible to get the whole size of the configuration. This is used to send a request to read the entire configuration into the buffer. The next state is set to CFG_ALL.
CFG_ALL

The read configuration is parsed and converted into a chain of usb_descriptor objects, which are then appended to the desc_chain in the device. If there are more configurations to read, then a new request to read the first 9 bytes of the next descriptor is sent and the state set to CFG_DESC; the state_data field is used to keep track of which descriptor is currently being read.

If all the descriptors have been read then the device configuration is inspected. If the device is a hub, then the hub state machine is started. Otherwise, the shared buffer is released and a class driver is sought to support this device. If no class driver is found, the device state is set to UNSUPPORTED, otherwise it is set to RUNNING.

RUNNING

This is the eventual state for a device supported by a class driver. The device will stay in this state until the physical device detaches.

A device can end up in two other states instead of this one. UNSUPPORTED state is similar to RUNNING except that there is no class driver. UNDEFINED state is reached if the device appears to be attached to the hub port, but does not communicate with the USB stack.

When a device detach is detected by the hub state machine, usb_device_detach() is called. This function puts the device into DETACH state, deallocates the device ID and unlinks the device from the parent hub. If the device is a hub, then it also recursively detaches any devices attached to the ports of this hub. If the device has a class driver attached to it, then the driver's detach routine is called. Finally, usb_device_unref() is called to remove the physical device's reference. This should result in the device being deallocated once any pending transfers have terminated.

Hub Lifecycle

Initially a hub passes through the same state machine as any other device to reset it, allocate an ID and read the descriptors. Once this is done and the device is identified as a hub, control moves to the hub state machine, which is an additional set of states to the device state machine.

RUNNING
The hub starts out in device RUNNING state. The shared buffer is still allocated and a control command is sent to the hub to read its hub descriptor. The next state is set to DESC.
DESC
When the hub descriptor has been read, the number of downstream ports is extracted and saved. A control command is sent to power up port 1 and the state_data is set to 1. The next state is set to PORT_POWER.
PORT_POWER
The state machine loops in this state sending a command to power up each hub port in turn, using state_data to keep track of the current port. Once all ports have been powered up, a command to fetch the port status of port 1 is sent and status_data set to 1. The next state is set to PORT_STATUS.
PORT_STATUS
The port status result is analyzed and if it shows a connection status change then usb_device_attach() or usb_device_detach() are called as appropriate. If there are more ports to poll, then a port status command is sent for the next port, and state_data incremented to track which port is being polled. If all the ports have been polled, then the state is set to READY and a delay set up for some number of milliseconds in the future.
READY
This is the default state of a hub when it is not polling the ports. When the delay set up in PORT_STATUS expires, this state is processed. A new port status request is sent for port 1, state_data set to 1, and the next state set to PORT_STATUS. This re-executes the loop in PORT_STATUS state to poll all the ports and act on any attach/detach events.