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, andUSB_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()
orusb_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.
2024-03-18 | eCosPro Non-Commercial Public License |