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 byusb_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
andUSB_TGT_FLAG_CALLBACK_DESC
which control the invocation of thecontrol()
andget_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 theget_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 theget_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
andindex
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.
2024-03-18 | eCosPro Non-Commercial Public License |