Name

Overview — eCosPro Support for USB Serial devices

Description

eCosPro USB serial support is divided into a number of packages. The USB serial driver package (CYGPKG_IO_SERIAL_USB) provides the common part of the USB serial device support. It communicates with one or more packages that implement a specific USB serial protocol. Serial support is implemented in both peripheral and host modes. The currently supported USB serial protocol packages include the Target CDC ACM protocol driver, Host CDC ACM protocol driver and Host FTDI protocol driver.

Applications access USB serial devices just as they would a physical UART, using the standard eCos I/O package API to send and receive data, and to set and get configuration and status information.

Example USB serial test applications can be found in the packages/devs/serial/usb/<VER>/tests directory. In particular the usb_echo.c test demonstrates access to all potentially attached USB serial protocol device types. It also includes code that shows the use of callbacks to determine when a host adapter has been attached or detached.

The following sections describe the configuration, internal data structures and workings of the USB serial package. They will generally only be of interest to someone with specific configuration requirements, or to gain a deeper understanding of the interface between the USB serial and protocol packages.

Data Structures

For various practical reasons, the interface between this package and the protocol drivers is defined in the usb_serial.h file in the main USB package.

The interface between the eCos serial driver and the USB protocol driver is defined by a data structure, usb_serial_if. This contains a number of fields that control the transfer of data between the drivers. Many of these are for internal use of the drivers, however, a number must be initialized by the user:

dev
In host mode this should be set to point to the usb_device object for the device being used when it has attached to the bus. The protocol driver must take a reference to this device in order to prevent it from being deallocated.
tgt
In target mode this points to the usb_target object that represents this peripheral. This target should be populated with suitable descriptors for the protocol being implemented.
call
This points to a table of functions that are used to communicate between the two drivers. This is described later.
chan
A pointer to the eCos serial device channel.
rx_buf
A pointer to a buffer used to receive data from the protocol package.
rx_maxpkt
The size of the rx_buf buffer.
target.pcdi_name
In target mode, this points to a string that names the peripheral controller to which the target will be attached.
host.id
In host mode, this should be the channel index number. This effectively defines the order in which host channels are searched for a VID/PID match. See the CDC/ACM host driver for details.
host.vid
In host mode, this controls whether this serial channel matches a particular USB Vendor ID. If zero it will match any ID, otherwise this channel will only match a device with the given Vendor ID. See the CDC/ACM host driver for details.
host.pid
In host mode, this controls whether this serial channel matches a particular USB Product ID. If zero it will match any ID, otherwise this channel will only match a device with the given Product ID. See the CDC/ACM host driver for details.

The call field points to a table of function calls. The functions in this table provide communication between the serial driver and the protocol driver.

int (*init)(usb_serial_if *usb_if);
This is the initialization routine for the protocol driver, it is called from the serial driver during it's initialization. This function should perform any USB stack initialization, such as attaching the target object to the PCDI in target mode, or register the class driver in host mode.
int (*attach)(usb_serial_if *usb_if);
This is called from the protocol driver whenever an attach event is detected in both host and target modes. The main side effect of this will be to invoke any registered serial callback.
int (*detach)(usb_serial_if *usb_if);
This is called from the protocol driver whenever an attach event is detected in both host and target modes. As with the attach call, this will cause the serial callback to be invoked.
int (*send_data_start)(usb_serial_if *usb_if);
This is called from the protocol driver to kick the serial driver transmitter into activity. It should be called when the USB device, host or target, moves into a state where data transfers can be started.
int (*send_data)(usb_serial_if *usb_if, usb_uint8 *buf, usb_int16 len);
This is called by the serial driver to transmit data on the USB device. The buf and len arguments describe the raw data to be sent. The protocol driver may need to wrap this data in any protocol headers or trailers and send it via the USB stack.
int (*send_data_done)(usb_serial_if *usb_if, usb_uint8 *buf, usb_int16 len);
This is called by the protocol driver when the transfer requested by send_data has completed. The buf will match the buffer pointer in the send_data call, and len will give the amount of data transmitted. It is likely that this function will call send_data to start another transmission; so the protocol driver must be ready for this.
int (*recv_data)(usb_serial_if *usb_if, usb_uint8 *buf, usb_int32 len);
This is called by the serial driver to supply a buffer for asynchronous data reception. It will use the rx_buf and rx_maxpkt fields from the common data structure. The protocol driver should use this to submit the necessary USB reception transfers to the USB device.
int (*recv_data_done)(usb_serial_if *usb_if, usb_uint8 *buf, usb_int16 len);
The protocol driver calls this when some data has been received. The buf and len describe the data received; while these will describe a portion of the rx_buf buffer, they may not describe all of it since protocol headers and trailers may be skipped. As with send_data_done, the serial driver may call back into the protocol driver during this call.
int (*dev_line_coding)(usb_serial_if *usb_if, usb_line_coding *line_coding);
This is called from the protocol driver when it receives a command from the USB peer to set the line parameters. The parameters passed are encoded in the usb_line_coding as described in the usb_serial.h header.
int (*dev_control_line_state)(usb_serial_if *usb_if, usb_uint16 line_state);
This is called from the protocol driver when it receives a command from the USB peer to set the RTS and DTR control line states. The state is encoded in the line_state argument as described in the usb_serial.h header.
int (*usb_line_coding)(usb_serial_if *usb_if, usb_line_coding *line_coding);
This is called from the serial driver when the eCos client sets any of the serial line parameters. The parameters passed are encoded in the usb_line_coding as described in the usb_serial.h header.
int (*usb_control_line_state)(usb_serial_if *usb_if, usb_uint16 line_state);
This is called from the serial driver when the eCos client sets the state of either the RTS or DTR lines. The state is encoded in the line_state argument as described in the usb_serial.h header.
int (*usb_set_config)(usb_serial_if *usb_if, cyg_uint32 key, const void *xbuf, cyg_uint32 *len);
This may be called from the serial driver if it is passed any set_config() keys that it does not recognize. It allows the protocol driver to handle any config options itself. This entry may be set to NULL, in which case no call will be made.
int (*usb_get_descriptor)(usb_serial_if *usb_if, usb_uint8 type, usb_uint8 index, usb_uint8 **buf, usb_uint16 *len );
This is used only for target mode drivers. The target get_descriptor() callback will be called if a given descriptor is not statically defined in the target object. If that routine cannot supply the descriptor then this callback should be invoked. The arguments follow the pattern of the target get_descriptor() function, except for the first argument, which is a pointer to the serial interface object and not the target object. This entry may be set to NULL if there are no descriptors to be fetched.

The line coding and control line entries in this list provide functionality that in the context of a pseudo-USB-serial connection between two machines have no real purpose. They only make sense if there is a genuine UART being controlled at one end.

Example Target Setup

A USB serial device needs some data structures to be defined and initialized. For a target mode device the following example for a notional target shows what needs to be done. This is usually done in a platform specific USB configuration package to associate a hardware peripheral with the serial protocol driver. This example shows a CDC ACM device, although the same approach should serve for any target protocol.

//=============================================================================
// USB serial device ACM0

#define USB_SUBSYSTEM USB_SUBSYSTEM_PCD

#include <cyg/usb/usb.h>
#include <pkgconf/io_usb_cdc_acm.h>
#include <cyg/usb/usb_serial.h>
#include <cyg/usb/cdc_acm.h>

#include CYGDAT_IO_USB_SERIAL_DEVICE_HEADER

//-----------------------------------------------------------------------------
// Connection calls
//
// Functions that start with usb_serial are supplied by the serial driver.
// Functions that start with cdc_acm are supplied by the CDC ACM protocol
// driver and would be substituted with functions for another protocol
// driver.

static const usb_serial_calls example_serial_calls =
{
    .init                       = cdc_acm_init,
    .attach                     = usb_serial_attach,
    .detach                     = usb_serial_detach,
    .send_data_start            = usb_serial_send_data_start,
    .send_data                  = cdc_acm_send_data,
    .send_data_done             = usb_serial_send_data_done,
    .recv_data                  = cdc_acm_recv_data,
    .recv_data_done             = usb_serial_recv_data_done,
    .dev_line_coding            = usb_serial_line_coding,
    .dev_control_line_state     = usb_serial_control_line_state,
    .usb_line_coding            = cdc_acm_line_coding,
    .usb_control_line_state     = cdc_acm_control_line_state,
    .usb_get_descriptor         = cdc_acm_get_descriptor,
};

//-----------------------------------------------------------------------------
// Interface object
//
// Preceeded by some forward definitions and the declaration of the receive
// buffer.

static usb_target example_acm0_target;
static serial_channel example_acm0;

static usb_uint8 example_acm0_rx_buf[CYGNUM_IO_USB_CDC_ACM_MAXPKT];

static usb_serial_if example_acm0_serial_if =
{
    .tgt                        = &example_acm0_target,
    .call                       = &example_serial_calls,
    .chan                       = &example_acm0,
    .target.pcdi_name           = "usb_fs",

    .rx_buf                     = example_acm0_rx_buf,
    .rx_maxpkt                  = CYGNUM_IO_USB_CDC_ACM_MAXPKT,
};

//-----------------------------------------------------------------------------
// USB target
//
// The CDC ACM protocol driver supplies the device, configuration and string
// descriptors, However, string descriptor 3, the serial number, is not provided
// and must be generated by a call to the target get_descriptor callback. The
// control and new_state callbacks are also supplied by the protocol driver.

static int example_acm0_get_descriptor(usb_target *tgt, usb_uint8 type, usb_uint8 index,
                                       usb_uint8 **buf, usb_uint16 *len );

static usb_target example_acm0_target =
{
    .desc               = &cdc_acm_device_descriptor,

    .configs            = cdc_acm_config_descriptors,
    .config_count       = 1,

    .strings            = cdc_acm_string_descriptors,
    .string_count       = 4,
    .get_descriptor     = example_acm0_get_descriptor,

    .control            = cdc_acm_control,
    .new_state          = cdc_acm_new_state,

    .data               = &example_acm0_serial_if,
};

//-----------------------------------------------------------------------------
// Serial channel
//
// This is the standard serial device channel structure, and needs to be
// initialized in the standard way with default settings and transmit and
// receive buffers.

// The baud rate is irrelevant, but we must choose a default value
#define CYGNUM_DEVS_USB_EXAMPLE_ACM0_BAUD         9600

static unsigned char example_acm_out_buf0[CYGNUM_DEVS_USB_EXAMPLE_ACM0_BUFSIZE];
static unsigned char example_acm_in_buf0[CYGNUM_DEVS_USB_EXAMPLE_ACM0_BUFSIZE];

static SERIAL_CHANNEL_USING_INTERRUPTS(example_acm0,
                                       usb_serial_funs,
                                       example_acm0_serial_if,
                                       CYG_SERIAL_BAUD_RATE(CYGNUM_DEVS_USB_EXAMPLE_ACM0_BAUD),
                                       CYG_SERIAL_STOP_DEFAULT,
                                       CYG_SERIAL_PARITY_DEFAULT,
                                       CYG_SERIAL_WORD_LENGTH_DEFAULT,
                                       CYG_SERIAL_FLAGS_DEFAULT,
                                       &example_acm_out_buf0[0], sizeof(example_acm_out_buf0),
                                       &example_acm_in_buf0[0], sizeof(example_acm_in_buf0)
    );

//-----------------------------------------------------------------------------
// Device table entry
//
// This generates an entry in the device table for the ACM0 device.

DEVTAB_ENTRY(example_serial_io0,
             "/dev/acm0",
             0,                     // Does not depend on a lower level interface
             &cyg_io_serial_devio,
             usb_serial_init,
             usb_serial_lookup,     // Serial driver may need initializing
             &example_acm0
    );

//-----------------------------------------------------------------------------
// Descriptor callback
//
// String descriptor 3 is not defined by the CDC ACM driver. Instead it must
// be supplied by a platform-specific callback. The following example simply
// returns a constant descriptor; in real systems, a descriptor may need to be
// synthesized from a board-specific serial number (fetched from flash or EEPROM).
// Any other descriptors are generated by the CDC ACM driver via the
// usb_get_descriptor() callback.

static const usb_string_serial cdc_acm_string_product =
{
    .bLength                            = 2+2*10,
    .bDescriptorType                    = USB_DESC_STRING,
    .bString                            = L"0123456789",
};

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

    if( (type == USB_DESC_STRING) && (index == tgt->desc->iSerialNumber) )
    {
        *buf = (usb_uint8 *)&usb_string_serial;
        *len = usb_string_serial->bLength;
    }
    else
    {
        usb_serial_if *usb_if = tgt->data;
        if( usb_if->call->usb_get_descriptor )
            result = usb_if->call->usb_get_descriptor( usb_if, type, index, buf, len );
    }

    return result;
}

Example Host Setup

Host mode devices need largely the same set of data structures as for target mode, but initialized in a slightly different way. This is usually done in the USB serial protocol driver where a number of channels will be instantiated. The following shows the data structures for CDC ACM channel 0.

//-----------------------------------------------------------------------------
// Connection calls
//
// Functions that start with usb_serial are supplied by the serial driver.
// Functions that start with cdc_acm_host are supplied by the CDC ACM host protocol
// driver and would be substituted with functions for another protocol
// driver.

static const usb_serial_calls cdc_acm_host_serial_calls =
{
    .init                       = cdc_acm_host_init,
    .attach                     = usb_serial_attach,
    .detach                     = usb_serial_detach,
    .send_data_start            = usb_serial_send_data_start,
    .send_data                  = cdc_acm_host_send_data,
    .send_data_done             = usb_serial_send_data_done,
    .recv_data                  = cdc_acm_host_recv_data,
    .recv_data_done             = usb_serial_recv_data_done,
    .dev_line_coding            = usb_serial_line_coding,
    .dev_control_line_state     = usb_serial_control_line_state,
    .usb_line_coding            = cdc_acm_host_line_coding,
    .usb_control_line_state     = cdc_acm_host_control_line_state,
};

//-----------------------------------------------------------------------------
// Interface object
//
// Preceeded by some forward definitions and the declaration of the receive
// buffer.

static serial_channel cdc_acm0_host;

static usb_uint8 cdc_acm0_host_rx_buf[CDC_ACM_HOST_MAXPKT];

static usb_serial_if cdc_acm0_host_serial_if =
{
    .call                       = &cdc_acm0_host_serial_calls,
    .chan                       = &cdc_acm0_host,

    .rx_buf                     = cdc_acm0_host_rx_buf,
    .rx_maxpkt                  = CDC_ACM_HOST_MAXPKT,

    .host.id                    = __n,
    .host.vid                   = CYGNUM_IO_USB_CDC_ACM_HOST_SERIAL0_VID,
    .host.pid                   = CYGNUM_IO_USB_CDC_ACM_HOST_SERIAL0_PID,
};

//-----------------------------------------------------------------------------
// Serial channel
//
// This is the standard serial device channel structure, and needs to be
// initialized in the standard way with default settings and transmit and
// receive buffers.

static unsigned char cdc_acm_out_buf0_host[CYGNUM_IO_USB_CDC_ACM_HOST_SERIAL0_BUFSIZE];
static unsigned char cdc_acm_in_buf0_host[CYGNUM_IO_USB_CDC_ACM_HOST_SERIAL0_BUFSIZE];

static SERIAL_CHANNEL_USING_INTERRUPTS(cdc_acm0_host,
                                       usb_serial_funs,
                                       cdc_acm0_host_serial_if,
                                       CYG_SERIAL_BAUD_RATE(CYGNUM_IO_USB_CDC_ACM_HOST_SERIAL0_BAUD),
                                       CYG_SERIAL_STOP_DEFAULT,
                                       CYG_SERIAL_PARITY_DEFAULT,
                                       CYG_SERIAL_WORD_LENGTH_DEFAULT,
                                       CYG_SERIAL_FLAGS_DEFAULT,
                                       &cdc_acm_out_buf0_host[0], sizeof(cdc_acm_out_buf0_host),
                                       &cdc_acm_in_buf0_host[0], sizeof(cdc_acm_in_buf0_host)
    );

//-----------------------------------------------------------------------------
// Device table entry
//
// This generates an entry in the device table for the ACM0 device.

DEVTAB_ENTRY(cdc_acm_host_serial_io0,
             CYGPKG_IO_USB_CDC_ACM_HOST_SERIAL0_NAME,
             0,
             &cyg_io_serial_devio,
             usb_serial_init,
             usb_serial_lookup,
             &cdc_acm0_host
    );