Name

Target Object — Structure and Interface

Synopsis

#include <cyg/io/usb.h>
    

void usb_target_ref(usb_target *tgt);

void usb_target_unref(usb_target *tgt);

int usb_target_attach(usb_target *tgt, usb_pcdi *pcdi);

int usb_target_detach(usb_target *tgt);

int usb_target_stall(usb_target *tgt, int ep, int stall);

usb_pcdi *usb_pcdi_find_by_name(char *name);

int usb_string_descriptor_utf8(usb_uint8 *buf, int len, const char *u8);

int usb_string_descriptor_create(usb_uint8 *buf, int len, usb_uint8 index, const char *strings[], int strings_num);

Description

In order to support a target device, application code must create and initialize a usb_target object, which is then passed to the USB stack.

Throughout this section the code that instantiates and uses the target object is referred to an an "application". Normally this will be a device driver or other middleware that translates USB operations into some other interface that eCosPro understands. Examples would be the CDC ACM driver that translates USB traffic into a serial device, or the RNDIS driver that translates into a Ethernet driver interface.

The target mode stack retains the USB terminology for data transfer direction, which can be a little confusing. So a transfer which involves data being passed from the host to be received by the target, is referred to as an OUT transfer. Similarly, transmission from the target to the host is referred to an an IN transfer.

Target Object

A target object has the following structure:

struct usb_target
{
    usb_pcdi                    *pcdi;          // PCD device instance

    usb_uint32                  flags;          // Flags
    usb_uint8                   refcount;       // Reference counter
    usb_uint8                   id;             // Device ID
    usb_uint8                   state;          // Current target state

    const usb_device_descriptor *desc;          // Device descriptor
    const usb_device_qual_descriptor *qdesc;    // Device qualifier descriptor

    const usb_config_descriptor **configs;      // Array of configurations
    int                         config_count;   // Size of array
    const usb_config_descriptor *config_current;// Currently selected config

    const usb_string_descriptor **strings;      // Array of string descriptors
    int                         string_count;   // Size of array

    usb_target_endpoint         ep0;            // Control endpoint

    usb_target_interface        *interfaces;    // List of active interfaces

    void                        *data;          // Client private data pointer


    // Optional callbacks to user code

    // Control message escape
    int (*control)(usb_target *tgt, usb_request *req, void **buf, usb_uint16 *len );

    int (*new_state)(usb_target *tgt);          // Signals state change

    // Dynamic descriptor callback, called if the addressed static
    // descriptor pointer is NULL.
    int (*get_descriptor)(usb_target *tgt, usb_uint8 type, usb_uint8 index,
                          usb_uint8 **buf, usb_uint16 *len );

#if CYGINT_IO_USB_TARGET_INTERFACE_CALLBACK>0
    // Interface change callback
    int (*set_interface)(usb_target *tgt, usb_uint8 intf, usb_uint8 alt);
#endif
};

The fields of the target object are as follows:

pcdi
This is a pointer to the PCD instance to which this target object is attached. This is initialized during the call to usb_target_attach() and cleared by usb_target_detach(). This field should not be initialized or changed by the application.
flags
Various flag bits. At present only two flags are defined, USB_TGT_FLAG_CALLBACK_CTRL and USB_TGT_FLAG_CALLBACK_DESC which control the invocation of the control() and get_descriptor() callbacks.
refcount
Target reference count. Since target objects are allocated by the application, zeroing this count does not cause the target to be zeroed. This count is used to keep track of the number of transfers associated with the target and for consistency checking.
id
The device ID. This is the ID that the host has set via a SET ADDRESS command. Before that happens, and when the target is reset, this will be set to zero.
state
Target state. This moves through states as defined by the USB specification and controls how the target reacts to bus events such as suspend, resume and reset.
desc
A pointer to the device descriptor for this target. If this pointer is NULL, then the get_descriptor() callback is called to supply the descriptor.
qdesc
A pointer to the device qualifier descriptor for this target. If this pointer is NULL, then the get_descriptor() callback is called to supply the descriptor.
configs
A pointer to an array of pointers to configuration descriptors. If this pointer is NULL, or a pointer in the array is NULL, then the get_descriptor() callback is called to supply the descriptor.
config_count
The size of the configs array. If a descriptor index greater than this value is requested, then the get_descriptor() callback is called to supply the descriptor.
config_current
When a target passes into the CONFIGURED state, this field will be set to point to the configuration selected. When the target is reset, this will be set back to NULL.
strings

A pointer to an array of pointers to string descriptors. If this pointer is NULL, or a pointer in the array is NULL, then the get_descriptor() callback is called to supply the descriptor.

There are a number of issues with string descriptors and their encoding which are covered in the section titled String Descriptor Encoding.

string_count
The size of the strings array. If a descriptor index greater than this value is requested, then the get_descriptor() callback is called to supply the descriptor.
ep0

A target endpoint structure. This is initialized and attached to the PCDI when the target is initially connected to the bus. It is used for all control endpoint transfers.

The desc sub-field of this object may be initialized to point to a descriptor for endpoint 0. If this is left NULL, it will be initialized to point to a default descriptor that allows a maximum packet size of 64 bytes. If the client needs to use a different maximum packet size on endpoint zero, it should set this sub-field.

interfaces
A chain of dynamically allocated target interface objects. When the target is configured by the host the selected configuration is scanned and for each endpoint in each active interface an endpoint is created and attached to the PCDI. A usb_target_endpoint object is allocated and attached to this list for each interface, and itself contains a list of usb_target_endpoint objects for each attached endpoint. When the target is reset, this list is scanned, the endpoints detached from the PCDI, and all the objects freed. A switch to a different alternate setting for an interface will result in the interface object in this list being detached, its endpoints detached from the PCDI, and a new interface and endpoints for the selected alternate created.
data
A data pointer that the application may use for its own purposes. Normally this will point to some data structure associated with the application.
control()

Normally, the USB target mode stack will handle all control SETUP messages to read descriptors, set the address, set the configuration and other commands. If a SETUP packet arrives that has a request type or code that is not recognized, then this function will be called. The return code defines what will happen next:

USB_OK
This indicates that the command was recognized and processed and there is no further action required. The USB stack will return a status packet to the host and then return to looking for the next SETUP packet.
USB_TARGET_CONTROL_DATAIN

This indicates that the USB stack should return data to the host. The data to be returned should be described by setting *buf and *len to the address and size of a buffer.

When the data has been successfully sent, this function will be called again, with the same request structure, the buffer pointer as passed, and *len set to the actual quantity of data sent. This is done so that the application can release or reuse the buffer; it can distinguish this call from the first by looking at *buf which will be NULL in the first call and non-NULL in the second. On return from this second call a status packet will be received from the host and the USB stack will return to looking for the next SETUP packet.

USB_TARGET_CONTROL_DATAOUT

This indicates that the USB stack should receive data from the host. A buffer into which the data should be received is described by setting *buf and *len to the address and size of the buffer.

When the data has been successfully received, this function will be called again, with the same request structure, the buffer pointer as passed, and *len set to the actual quantity of data received. The application can distinguish this call from the first by looking at *buf which will be NULL in the first call and non-NULL in the second. On return from this second call a status packet will be return to the host and the USB stack will return to looking for the next SETUP packet.

USB_ERR_COMMAND_INVALID
If the callback returns this error code, then the USB stack will generate a STALL condition on the bus, which will act to abort the control transfer. The stack will then return to looking for the next SETUP packet.

If this callback is NULL, then any unrecognized SETUP packets will cause a STALL. So, unless the target device protocol contains extra control operations, it is not necessary for the application to supply this callback.

new_state()
This callback is called each time the target moves into a new state. The application can perform any processing of its own in response to this call. If the application does no need to process these events, it can set this pointer to NULL.
get_descriptor()

If any of the descriptor pointers in the target object is NULL, or a descriptor outside the supplied set is fetched, this callback will be called. The type and index values identify the descriptor being read. If the application can generate the descriptor itself, it should set *buf and *len to point to the descriptor and return.

When the descriptor has been returned to the host, this callback will be called again with the same type and index values. This is done so that the application can release or reuse the buffer; it can distinguish this call from the first by looking at *buf which will be NULL in the first call and non-NULL in the second.

get_interface()

If a target implements interfaces with alternate settings, the CDL interface CYGINT_IO_USB_TARGET_INTERFACE_CALLBACK should be implemented to cause this callback to be present. Subsequently, whenever the host sends a SET_INTERFACE operation to select an alternate setting, this function will be called. This allows the client code to adapt to the potential change in endpoint configuration.

Since relatively few targets implement alternate interface settings, this callback is only present if the CDL interface is implemented.

String Descriptor Encoding

USB string descriptors are in Unicode, encoded in UTF-16LE. Unfortunately, this is not a character encoding that is directly supported by the GCC toolchain. There are a number of ways to work around this. The first, and simplest is to use the -fshort-char compiler option to force wchar_t to be 16 bits rather than the default 32 bits. Strings can then be prefixed by L to ensure 16 bit Unicode encoding. A typical static descriptor can then be defined as follows:

static const usb_string_descriptor mytgt_string_manufacturer =
{
    .bLength                            = 2+2*11,
    .bDescriptorType                    = USB_DESC_STRING,
    .bString                            = L"eCosCentric"
};

However, there are a number of problems with this. The encoding is strictly 16 bits, and any code points that require a surrogate pair cannot be defined. Compiling files with the -fshort-char option will throw up compiler warnings since it differs from the defaults with which the libraries will have been built. But, most importantly, it only works for little endian targets; big endian targets will generate the 16 bit values in big endian byte order.

A more portable approach would be to encode the UTF-16LE directly using optional byte swaps where necessary, as in the following example:

static const usb_string_descriptor mytgt_string_manufacturer =
{
    .bLength                            = 2+2*11,
    .bDescriptorType                    = USB_DESC_STRING,
    .bString                            = { USB_CPU_TO_LE16('e'),
                                            USB_CPU_TO_LE16('C'),
                                            USB_CPU_TO_LE16('o'),
                                            USB_CPU_TO_LE16('s'),
                                            …
                                          }
};

However, this approach is clumsy and does not allow the size or contents of the string to be made a configuration option, or even easy to change in the code.

The preferred approach in the USB stack is to generate and store string descriptors in UTF-8 and to convert them to UTF-16LE at run time, when the descriptor is requested. A UTF-8 string is just a sequence of bytes and can be defined and manipulated like any other byte array. Most text editors will allow a UTF-8 string to be created or pasted in from some other source without any problems. Most UTF-8 strings occupy less space than their UTF-16LE equivalents. A standard ASCII string is just a UTF-8 string that contains no code points beyond the basic ASCII set.

To simplify use of UTF-8 strings, the USB stack exports a couple of helper functions. The function usb_string_descriptor_utf8() takes a pointer and length of a buffer in which a string descriptor is created, and a pointer to a UTF-8 string. It recodes the UTF-8 string into UTF-16LE in the buffer together with setting the descriptor size and type. If successful, the buffer will contain a string descriptor ready to be transmitted. If the buffer is not large enough for the descriptor, an error code will be returned.

The function usb_string_descriptor_create() is passed a buffer pointer and length, the index of the descriptor to be returned and a pointer to an array of UTF-8 strings. It checks that the index is correct and then creates a new string descriptor in the buffer using the indexed string from the array; it calls usb_string_descriptor_utf8() to do this.

To put all this together, the strings for a device can be defined statically as follows:

static const usb_string_descriptor mytgt_string_langid =
{
    .bLength                            = 2+2*1,
    .bDescriptorType                    = USB_DESC_STRING,
    .bString                            = { USB_CPU_TO_LE16(0x0809) },
};

static const usb_string_descriptor *mytgt_string_descriptors[1] =
{
    [0]                 = &mytgt_string_langid,
};

static const char *mytgt_descriptor_strings[] =
{
    [1]                 = "eCosCentric",
    [2]                 = "My Device",
    [3]                 = "01234567890",
};

In the target object, the strings field is set to point to mytgt_string_descriptors and string_count set to 1. This will cause the USB target stack to call the target's get_descriptor function for the remaining string descriptors. This function should look like the following example:

static usb_uint8 mytgt_dynamic_desc[64];       // Dynamic descriptor buffer

static int mytgt_get_descriptor(usb_target *tgt, usb_uint8 type, usb_uint8 index,
                                usb_uint8 **buf, usb_uint16 *len )
{
    int result = USB_OK;

    // A non-NULL buffer pointer is the USB stack returning the buffer
    // to us for reuse.
    if( *buf != NULL )
        return USB_OK;

    if( type == USB_DESC_STRING )
    {
        result = usb_string_descriptor_create( mytgt_dynamic_desc, sizeof(mytgt_dynamic_desc),
                                               index,
                                               mytgt_descriptor_strings,
                                               sizeof(mytgt_descriptor_strings)/sizeof(char *) );
        if( result == USB_OK )
        {
            *buf = mytgt_dynamic_desc;
            *len = mytgt_dynamic_desc[0];
        }
    }
    else
    {
        // Handle any other descriptor types
    }

    return result;
}

Note that in this example the serial number string is a constant. For devices where a unique serial number is required for each unit, a different approach may be needed. First the unit must have a unique identifier that can be used for this purpose. Depending on the platform this could be fetched from EPROM, FLASH, a serial number chip or a built-in chip ID. The simplest approach is to convert this value into an ASCII string. Then in the get_descriptor() function this string can be converted to a string descriptor when the serial number is requested. For an example take a look at the hid_test.c test program where a serial number is manufactured from a checksum of the executable.

Target Object API

The USB stack exports a number of functions that are intended for use by applications using targets.

The function usb_target_attach() must be called to attach a target to a specific peripheral interface. Peripheral interfaces are named and a pointer to a particular interface can be obtained by calling usb_pcdi_find_by_name(). Following this, most target events will be handled by the USB stack with calls to the callbacks as necessary. If the application wants to stop the target, it should call usb_target_detach().

Some USB protocols require an endpoint stall to signal various conditions. The function usb_target_endpoint_stall() allows this to be done. The ep argument contains the endpoint address, and should have USB_ENDPOINT_ADDR_IN set for IN endpoints. The stall argument is 1 to stall the endpoints and zero to clear the stall condition.

An application must instantiate a usb_target object and initialize it before calling usb_target_attach(). Typically this object can be defined statically as in the following example:

static usb_target mytgt_target =
{
    .desc               = &mytgt_device_descriptor,
    .qdesc              = &mytgt_device_qual_descriptor,

    .configs            = mytgt_config_descriptors,
    .config_count       = 1,

    .strings            = mytgt_string_descriptors,
    .string_count       = 1,

    .control            = mytgt_control,
    .new_state          = mytgt_new_state,
    .get_descriptor     = mytgt_get_descriptor,

    .data               = &mytgt_data,
};

This defines the target object, initializes the static descriptors and callbacks. No fields beyond those shown above need to be initialized. While none of the fields is mandatory, if a static descriptor is not present, the get_descriptor() callback will be called, so it is not sensible to have both NULL descriptors and no get_descriptor().

Once the application has started, it should locate the PCDI it wants to attach the target to and call usb_target_attach(), as in the following example:

void mytgt_init( void )
{
    usb_pcdi *pcdi;

    // Find PCDI by name
    pcdi = usb_pcdi_find_by_name( "usb_fs" );

    if( pcdi == NULL )
    {
        // Handle error
    }

    // Attach our target to the PCDI
    result = usb_target_attach( &mytgt_target, pcdi );

    if( result != USB_OK )
    {
        // Handle error
    }
}

Once the target has been attached, all further interaction with the application will be via the callbacks. Most new_state() callbacks can be ignored while the target is going through the initial connect/reset/address sequence. The transition to CONFIGURED state is the most important since this is when the target should become ready to interact with the host. Normally this will be the point at which it submits transfers to the OUT endpoints to receive packets from the host and maybe starts sending data via the IN endpoints.

For a complete example take a look at the acm_example.c test program. This source is annotated with extra comments. The usbms_tgt.c and hid_test.c test programs in packages/io/usb/<version>/tests directory. The CDC/ACM protocol driver can also examined for example code.