eCos USB Slave SupportIntroductionIntroductioneCos support for USB slave devicesIntroduction
The eCos USB slave support allows developers to produce USB
peripherals. It consists of a number of different eCos packages:
Device drivers for specific implementations of USB slave hardware, for
example the on-chip USB Device Controller provided by the Intel SA1110
processor. A typical USB peripheral will only provide one USB slave
port and therefore only one such device driver package will be needed.
Usually the device driver package will be loaded automatically when
you create an eCos configuration for target hardware that has a USB
slave device. If you select a target which does have a USB slave
device but no USB device driver is loaded, this implies that no such
device driver is currently available.
The common USB slave package. This serves two purposes. It defines the
API that specific device drivers should implement. It also provides
various utilities that will be needed by most USB device drivers and
applications, such as handlers for standard control messages.
Usually this package will be loaded automatically at the same time as
the USB device driver.
The common USB package. This merely provides some information common
to both the host and slave sides of USB, such as details of the
control protocol. It is also used to place the other USB-related
packages appropriately in the overall configuration hierarchy. Usually
this package will be loaded at the same time as the USB device driver.
Class-specific USB support packages. These make it easier to develop
specific classes of USB peripheral, such as a USB-ethernet device. If
no suitable package is available for a given class of peripheral then
the USB device driver can instead be accessed directly from
application code. Such packages will never be loaded automatically
since the configuration system has no way of knowing what class of USB
peripheral is being developed. Instead developers have to add the
appropriate package or packages explicitly.
These packages only provide support for developing USB peripherals,
not USB hosts.
USB Concepts
Information about USB can be obtained from a number of sources
including the USB Implementers Forum
web site. Only a brief summary is provided here.
A USB network is asymmetrical: it consists of a single host, one or
more slave devices, and possibly some number of intermediate hubs. The
host side is significantly more complicated than the slave side.
Essentially, all operations are initiated by the host. For example, if
the host needs to receive some data from a particular USB peripheral
then it will send an IN token to that peripheral; the latter should
respond with either a NAK or with appropriate data. Similarly, when
the host wants to transmit data to a peripheral it will send an OUT
token followed by the data; the peripheral will return a NAK if it is
currently unable to receive more data or if there was corruption,
otherwise it will return an ACK. All transfers are check-summed and
there is a clearly-defined error recovery process. USB peripherals can
only interact with the host, not with each other.
USB supports four different types of communication: control messages,
interrupt transfers, isochronous transfers, and bulk transfers.
Control messages are further subdivided into four categories:
standard, class, vendor and a reserved category. All USB peripherals
must respond to certain standard control messages, and usually this
will be handled by the common USB slave package (for complicated
peripherals, application support will be needed). Class and vendor
control messages may be handled by an class-specific USB support
package, for example the USB-ethernet package will handle control
messages such as getting the MAC address or enabling/disabling
promiscuous mode. Alternatively, some or all of these messages will
have to be handled by application code.
Interrupt transfers are used for devices which need to be polled
regularly. For example, a USB keyboard might be polled once every
millisecond. The host will not poll the device more frequently than
this, so interrupt transfers are best suited to peripherals that
involve a relatively small amount of data. Isochronous transfers are
intended for multimedia-related peripherals where typically a large
amount of video or audio data needs to be exchanged continuously.
Given appropriate host support a USB peripheral can reserve some of
the available bandwidth. Isochronous transfers are not reliable; if a
particular packet is corrupted then it will just be discarded and
software is expected to recover from this. Bulk transfers are used for
everything else: after taking care of any pending control, isochronous
and interrupt transfers the host will use whatever bandwidth remains
for bulk transfers. Bulk transfers are reliable.
Transfers are organized into USB packets, with the details depending
on the transfer type. Control messages always involve an initial
8-byte packet from host to peripheral, optionally followed by some
additional packets; in theory these additional packets can be up to 64
bytes, but hardware may limit it to 8 bytes. Interrupt transfers
involve a single packet of up to 64 bytes. Isochronous transfers
involve a single packet of up to 1024 bytes. Bulk transfers involve
multiple packets. There will be some number, possibly zero, of 64-byte
packets. The transfer is terminated by a single packet of less than 64
bytes. If the transfer involves an exact multiple of 64 bytes than the
final packet will be 0 bytes, consisting of just a header and checksum
which typically will be generated by the hardware. There is no
pre-defined limit on the size of a bulk transfer. Instead higher-level
protocols are expected to handle this, so for a USB-ethernet
peripheral the protocol could impose a limit of 1514 bytes of data
plus maybe some additional protocol overhead.
Transfers from the host to a peripheral are addressed not just to that
peripheral but to a specific endpoint within that peripheral.
Similarly, the host requests incoming data from a specific endpoint
rather than from the peripheral as a whole. For example, a combined
keyboard/touchpad device could provide the keyboard events on endpoint
1 and the mouse events on endpoint 2. A given USB peripheral can have
up to 16 endpoints for incoming data and another 16 for outgoing data.
However, given the comparatively high speed of USB I/O this endpoint
addressing is typically implemented in hardware rather than software,
and the hardware will only implement a small number of endpoints.
Endpoint 0 is generally used only for control messages.
In practice, many of these details are irrelevant to application code
or to class packages. Instead, such higher-level code usually just
performs blocking read and
write, or non-blocking USB-specific calls, to
transfer data between host and target via a specific endpoint. Control
messages are more complicated but are usually handled by existing
code.
When a USB peripheral is plugged into the host there is an initial
enumeration and configuration process. The peripheral provides
information such as its class of device (audio, video, etc.), a
vendor id, which endpoints should be used for what kind of data, and
so on. The host OS uses this information to identify a suitable host
device driver. This could be a generic driver for a class of
peripherals, or it could be a vendor-specific driver. Assuming a
suitable driver is installed the host will then activate the USB
peripheral and perform additional application-specific initialisation.
For example for a USB-ethernet device this would involve obtaining an
ethernet MAC address. Most USB peripherals will be fairly simple, but
it is possible to build multifunction peripherals with multiple
configurations, interfaces, and alternate interface settings.
It is not possible for any of the eCos packages to generate all the
enumeration data automatically. Some of the required information such
as the vendor id cannot be supplied by generic packages; only by the
application developer. Class support code such as the USB-ethernet
package could in theory supply some of the information automatically,
but there are also hardware dependencies such as which endpoints get
used for incoming and outgoing ethernet frames. Instead it is the
responsibility of the application developer to provide all the
enumeration data and perform some additional initialisation. In
addition, the common USB slave package can handle all the standard
control messages for a simple USB peripheral, but for something like a
multifunction peripheral additional application support is needed.
The initial implementation of the eCos USB slave packages involved
hardware that only supported control and bulk transfers, not
isochronous or interrupt. There may be future changes to the USB
code and API to allow for isochronous and interrupt transfers,
especially the former. Other changes may be required to support
different USB devices. At present there is no support for USB remote
wakeups, since again it is not supported by the hardware.
eCos USB I/O Facilities
For protocols other than control messages, eCos provides two ways of
performing USB I/O. The first involves device table or devtab entries such
as /dev/usb1r,
with one entry per endpoint per USB device. It is possible to
open these devices and use conventional blocking
I/O functions such as read and
write to exchange data between host and
peripheral.
There is also a lower-level USB-specific API, consisting of functions
such as usbs_start_rx_buffer.
A USB device driver will supply a data structure for each endpoint,
for example a usbs_rx_endpoint
structure for every receive endpoint. The first argument to
usbs_start_rx_buffer should be a pointer to such
a data structure. The USB-specific API is non-blocking: the initial
call merely starts the transfer; some time later, once the transfer
has completed or has been aborted, the device driver will invoke a
completion function.
Control messages are different. With four different categories of
control messages including application and vendor specific ones, the
conventional
open/read/write
model of I/O cannot easily be applied. Instead, a USB device driver
will supply a usbs_control_endpoint
data structure which can be manipulated appropriately. In practice the
standard control messages will usually be handled by the common USB
slave package, and other control messages will be handled by
class-specific code such as the USB-ethernet package. Typically,
application code remains responsible for supplying the enumeration data and for actually starting up the USB device.
Enabling the USB code
If the target hardware contains a USB slave device then the
appropriate USB device driver and the common packages will typically
be loaded into the configuration automatically when that target is
selected (assuming a suitable device driver exists). However, the
driver will not necessarily be active. For example a processor might
have an on-chip USB device, but not all applications using that
processor will want to use USB functionality. Hence by default the USB
device is disabled, ensuring that applications do not suffer any
memory or other penalties for functionality that is not required.
If the application developer explicitly adds a class support package
such as the USB-ethernet one then this implies that the USB device is
actually needed, and the device will be enabled automatically.
However, if no suitable class package is available and the USB device
will instead be accessed by application code, it is necessary to
enable the USB device manually. Usually the easiest way to do this is
to enable the configuration option
CYGGLO_IO_USB_SLAVE_APPLICATION, and the USB device
driver and related packages will adjust accordingly. Alternatively,
the device driver may provide some configuration options to provide
more fine-grained control.
USB Enumeration DataEnumeration DataThe USB enumeration data structures
#include <cyg/io/usb/usb.h>
#include <cyg/io/usb/usbs.h>
typedef struct usb_device_descriptor {
…
} usb_device_descriptor __attribute__((packed));
typedef struct usb_configuration_descriptor {
…
} usb_configuration_descriptor __attribute__((packed));
typedef struct usb_interface_descriptor {
…
} usb_interface_descriptor __attribute__((packed));
typedef struct usb_endpoint_descriptor {
…
} usb_endpoint_descriptor;
typedef struct usbs_enumeration_data {
usb_device_descriptor device;
int total_number_interfaces;
int total_number_endpoints;
int total_number_strings;
const usb_configuration_descriptor* configurations;
const usb_interface_descriptor* interfaces;
const usb_endpoint_descriptor* endpoints;
const unsigned char** strings;
} usbs_enumeration_data;
USB Enumeration Data
When a USB host detects that a peripheral has been plugged in or
powered up, one of the first steps is to ask the peripheral to
describe itself by supplying enumeration data. Some of this data
depends on the class of peripheral. Other fields are vendor-specific.
There is also a dependency on the hardware, specifically which
endpoints are available should be used. In general it is not possible
for generic code to provide this information, so it is the
responsibility of application code to provide a suitable
usbs_enumeration_data data structure and
install it in the endpoint 0 data structure during initialization.
This must happen before the USB device is enabled by a call to
usbs_start, for example:
const usbs_enumeration_data usb_enum_data = {
…
};
int
main(int argc, char** argv)
{
usbs_sa11x0_ep0.enumeration_data = &usb_enum_data;
…
usbs_start(&usbs_sa11x0_ep0);
…
}
For most applications the enumeration data will be static, although
the usbs_enumeration_data structure can be
filled in at run-time if necessary. Full details of the enumeration
data can be found in the Universal Serial Bus specification obtainable
from the USB Implementers Forum web
site, although the meaning of most fields is fairly obvious.
The various data structures and utility macros are defined in the
header files cyg/io/usb/usb.h
and cyg/io/usb/usbs.h. Note
that the example code below makes use of the gcc labelled element
extension.
usb_device_descriptor
The main information about a USB peripheral comes from a single
usb_device_descriptor structure, which is
embedded in the usbs_enumeration_data
structure. A typical example might look like this:
const usbs_enumeration_data usb_enum_data = {
{
length: USB_DEVICE_DESCRIPTOR_LENGTH,
type: USB_DEVICE_DESCRIPTOR_TYPE,
usb_spec_lo: USB_DEVICE_DESCRIPTOR_USB11_LO,
usb_spec_hi: USB_DEVICE_DESCRIPTOR_USB11_HI,
device_class: USB_DEVICE_DESCRIPTOR_CLASS_VENDOR,
device_subclass: USB_DEVICE_DESCRIPTOR_SUBCLASS_VENDOR,
device_protocol: USB_DEVICE_DESCRIPTOR_PROTOCOL_VENDOR,
max_packet_size: 8,
vendor_lo: 0x42,
vendor_hi: 0x42,
product_lo: 0x42,
product_hi: 0x42,
device_lo: 0x00,
device_hi: 0x01,
manufacturer_str: 1,
product_str: 2,
serial_number_str: 0,
number_configurations: 1
},
…
};
The length and type fields are specified by the USB standard. The
usb_spec_lo and
usb_spec_hi fields identify the particular
revision of the standard that the peripheral implements, for example
revision 1.1.
The device class, subclass, and protocol fields are used by generic
host-side USB software to determine which host-side device driver
should be loaded to interact with the peripheral. A number of standard
classes are defined, for example mass-storage devices and
human-interface devices. If a peripheral implements one of the
standard classes then a standard existing host-side device driver may
exist, eliminating the need to write a custom driver. The value
0xFF (VENDOR) is reserved for
peripherals that implement a vendor-specific protocol rather than a
standard one. Such peripherals will require a custom host-side device
driver. The value 0x00
(INTERFACE) is reserved and indicates that the
protocol used by the peripheral is defined at the interface level
rather than for the peripheral as a whole.
The max_package_size field specifies the
maximum length of a control message. There is a lower bound of eight
bytes, and typical hardware will not support anything larger because
control messages are usually small and not performance-critical.
The vendor_lo and
vendor_hi fields specify a vendor id, which
must be obtained from the USB Implementor's Forum. The numbers used in
the code fragment above are examples only and must not be used in real
USB peripherals. The product identifier is determined by the vendor,
and different USB peripherals should use different identifiers. The
device identifier field should indicate a release number in
binary-coded decimal.
The above fields are all numerical in nature. A USB peripheral can
also provide a number of strings as described below, for example the name of the
vendor can be provided. The various _str
fields act as indices into an array of strings, with index 0
indicating that no string is available.
A typical USB peripheral involves just a single configuration. However
more complicated peripherals can support multiple configurations. Only
one configuration will be active at any one time, and the host will
switch between them as appropriate. If a peripheral does involve
multiple configurations then typically it will be the responsibility
of application code to handle the standard
set-configuration control message.
usb_configuration_descriptor
A USB peripheral involves at least one and possible several different
configurations. The usbs_enumeration_data
structure requires a pointer to an array, possibly of length 1, of
usb_configuration_descriptor structures.
Usually a single structure suffices:
const usb_configuration_descriptor usb_configuration = {
length: USB_CONFIGURATION_DESCRIPTOR_LENGTH,
type: USB_CONFIGURATION_DESCRIPTOR_TYPE,
total_length_lo: USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_LO(1, 2),
total_length_hi: USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, 2),
number_interfaces: 1,
configuration_id: 1,
configuration_str: 0,
attributes: USB_CONFIGURATION_DESCRIPTOR_ATTR_REQUIRED |
USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED,
max_power: 50
};
const usbs_enumeration_data usb_enum_data = {
…
configurations: &usb_configuration,
…
};
The values for the length and
type fields are determined by the standard.
The total_length field depends on the
number of interfaces and endpoints used by this configuration, and
convenience macros are provided to calculate this: the first argument
to the macros specify the number of interfaces, the second the number
of endpoints. The number_interfaces field
is self-explanatory. If the peripheral involves multiple
configurations then each one must have a unique id, and this will be
used in the set-configuration control message. The id
0 is reserved, and a set-configuration control
message that uses this id indicates that the peripheral should be
inactive. Configurations can have a string description if required.
The attributes field must have the
REQUIRED bit set; the
SELF_POWERED bit informs the host that the
peripheral has its own power supply and will not draw any power over
the bus, leaving more bus power available to other peripherals; the
REMOTE_WAKEUP bit is used if the peripheral can
interrupt the host when the latter is in power-saving mode. For
peripherals that are not self-powered, the
max_power field specifies the power
requirements in units of 2mA.
usb_interface_descriptor
A USB configuration involves one or more interfaces, typically
corresponding to different streams of data. For example, one interface
might involve video data while another interface is for audio.
Multiple interfaces in a single configuration will be active at the
same time.
const usb_interface_descriptor usb_interface = {
length: USB_INTERFACE_DESCRIPTOR_LENGTH,
type: USB_INTERFACE_DESCRIPTOR_TYPE,
interface_id: 0,
alternate_setting: 0,
number_endpoints: 2,
interface_class: USB_INTERFACE_DESCRIPTOR_CLASS_VENDOR,
interface_subclass: USB_INTERFACE_DESCRIPTOR_SUBCLASS_VENDOR,
interface_protocol: USB_INTERFACE_DESCRIPTOR_PROTOCOL_VENDOR,
interface_str: 0
};
const usbs_enumeration_data usb_enum_data = {
…
total_number_interfaces: 1,
interfaces: &usb_interface,
…
};
Again, the length and
type fields are specified by the standard.
Each interface within a configuration requires its own id. However, a
given interface may have several alternate settings, in other words
entries in the interfaces array with the same id but different
alternate_setting fields. For example,
there might be one setting which requires a bandwidth of 100K/s and
another setting that only needs 50K/s. The host can use the standard
set-interface control message to choose the most appropriate setting.
The handling of this request is the responsibility of higher-level
code, so the application may have to install its own handler.
The number of endpoints used by an interface is specified in the
number_endpoints field. Exact details of
which endpoints are used is held in a separate array of endpoint
descriptors. The class, subclass and protocol fields are used by
host-side code to determine which host-side device driver should
handle this specific interface. Usually this is determined on a
per-peripheral basis in the
usb_device_descriptor structure, but that can
defer the details to individual interfaces. A per-interface string
is allowed as well.
For USB peripherals involving multiple configurations, the array of
usb_interface_descriptor structures should
first contain all the interfaces for the first configuration, then all
the interfaces for the second configuration, and so on.
usb_endpoint_descriptor
The host also needs information about which endpoint should be used
for what. This involves an array of endpoint descriptors:
const usb_endpoint_descriptor usb_endpoints[] = {
{
length: USB_ENDPOINT_DESCRIPTOR_LENGTH,
type: USB_ENDPOINT_DESCRIPTOR_TYPE,
endpoint: USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT | 1,
attributes: USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
max_packet_lo: 64,
max_packet_hi: 0,
interval: 0
},
{
length: USB_ENDPOINT_DESCRIPTOR_LENGTH,
type: USB_ENDPOINT_DESCRIPTOR_TYPE,
endpoint: USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN | 2,
attributes: USB_ENDPOINT_DESCRIPTOR_ATTR_BULK,
max_packet_lo: 64,
max_packet_hi: 0,
interval: 0
}
};
const usbs_enumeration_data usb_enum_data = {
…
total_number_endpoints: 2,
endpoints: usb_endpoints,
…
};
As usual the values for the length and
type fields are specified by the standard.
The endpoint field gives both the endpoint
number and the direction, so in the above example endpoint 1 is used
for OUT (host to peripheral) transfers and endpoint 2 is used for IN
(peripheral to host) transfers. The
attributes field indicates the USB protocol
that should be used on this endpoint: CONTROL,
ISOCHRONOUS, BULK or
INTERRUPT. The
max_packet field specifies the maximum size
of a single USB packet. For bulk transfers this will typically be 64
bytes. For isochronous transfers this can be up to 1023 bytes. For
interrupt transfers it can be up to 64 bytes, although usually a
smaller value will be used. The interval
field is ignored for control and bulk transfers. For isochronous
transfers it should be set to 1. For interrupt transfers it can be a
value between 1 and 255, and indicates the number of milliseconds
between successive polling operations.
For USB peripherals involving multiple configurations or interfaces
the array of endpoint descriptors should be organized sequentially:
first the endpoints corresponding to the first interface of the first
configuration, then the second interface in that configuration, and so
on; then all the endpoints for all the interfaces in the second
configuration; etc.
Strings
The enumeration data can contain a number of strings with additional
information. Unicode encoding is used for the strings, and it is
possible for a peripheral to supply a given string in multiple
languages using the appropriate characters. The first two bytes of
each string give a length and type field. The first string is special;
after the two bytes header it consists of an array of 2-byte language
id codes, indicating the supported languages. The language code
0x0409 corresponds to English (United States).
const unsigned char* usb_strings[] = {
"\004\003\011\004",
"\020\003R\000e\000d\000 \000H\000a\000t\000"
};
const usbs_enumeration_data usb_enum_data = {
…
total_number_strings: 2,
strings: usb_strings,
…
};
The default handler for standard control messages assumes that the
peripheral only uses a single language. If this is not the case then
higher-level code will have to handle the standard get-descriptor
control messages when a string descriptor is requested.
usbs_enumeration_data
The usbs_enumeration_data data structure
collects together all the various descriptors that make up the
enumeration data. It is the responsibility of application code to
supply a suitable data structure and install it in the control
endpoints's enumeration_data field before
the USB device is started.
Starting up a USB Deviceusbs_startStarting up a USB Device
#include <cyg/io/usb/usbs.h>
void usbs_startusbs_control_endpoint* ep0Description
Initializing a USB device requires some support from higher-level
code, typically the application, in the form of enumeration data.
Hence it is not possible for the low-level USB driver to activate a
USB device itself. Instead the higher-level code has to take care of
this by invoking usbs_start. This function takes
a pointer to a USB control endpoint data structure. USB device drivers
should provide exactly one such data structure for every USB device,
so the pointer uniquely identifies the device.
const usbs_enumeration_data usb_enum_data = {
…
};
int
main(int argc, char** argv)
{
usbs_sa11x0_ep0.enumeration_data = &usb_enum_data;
…
usbs_start(&usbs_sa11x0_ep0);
…
}
The exact behaviour of usbs_start depends on the
USB hardware and the device driver. A typical implementation would
change the USB data pins from tristated to active. If the peripheral
is already plugged into a host then the latter should detect this
change and start interacting with the peripheral, including requesting
the enumeration data. Some of this may happen before
usbs_start returns, but given that multiple
interactions between USB host and peripheral are required it is likely
that the function will return before the peripheral is fully
configured. Control endpoints provide a mechanism for informing
higher-level code of USB state changes.
usbs_start will return even if the peripheral is
not currently connected to a host: it will not block until the
connection is established.
usbs_start should only be called once for a given
USB device. There are no defined error conditions. Note that the
function affects the entire USB device and not just the control
endpoint: there is no need to start any data endpoints as well.
Devtab EntriesDevtab EntriesData endpoint data structure
/dev/usb0c
/dev/usb1r
/dev/usb2w
Devtab Entries
USB device drivers provide two ways of transferring data between host
and peripheral. The first involves USB-specific functionality such as
usbs_start_rx_buffer.
This provides non-blocking I/O: a transfer is started, and some time
later the device driver will call a supplied completion function. The
second uses the conventional I/O model: there are entries in the
device table corresponding to the various endpoints. Standard calls
such as open can then be used to get a suitable
handle. Actual I/O happens via blocking read and
write calls. In practice the blocking operations
are simply implemented using the underlying non-blocking
functionality.
Each endpoint will have its own devtab entry. The exact names are
controlled by the device driver package, but typically the root will
be /dev/usb. This is followed by one or more
decimal digits giving the endpoint number, followed by
c for a control endpoint, r for
a receive endpoint (host to peripheral), and w for
a transmit endpoint (peripheral to host). If the target hardware
involves more than one USB device then different roots should be used,
for example /dev/usb0c and
/dev/usb1_0c. This may require explicit
manipulation of device driver configuration options by the application
developer.
At present the devtab entry for a control endpoint does not support
any I/O operations.
write operationscyg_io_write and similar functions in
higher-level packages can be used to perform a transfer from
peripheral to host. Successive write operations will not be coalesced.
For example, when doing a 1000 byte write to an endpoint that uses the
bulk transfer protocol this will involve 15 full-size 64-byte packets
and a terminating 40-byte packet. USB device drivers are not expected
to do any locking, and if higher-level code performs multiple
concurrent write operations on a single endpoint then the resulting
behaviour is undefined.
A USB write operation will never transfer less
data than specified. It is the responsibility of higher-level code to
ensure that the amount of data being transferred is acceptable to the
host-side code. Usually this will be defined by a higher-level
protocol. If an attempt is made to transfer more data than the host
expects then the resulting behaviour is undefined.
There are two likely error conditions. EPIPE
indicates that the connection between host and target has been broken.
EAGAIN indicates that the endpoint has been
stalled, either at the request of the host or by other activity
inside the peripheral.
read operationscyg_io_read and similar functions in higher-level
packages can be used to perform a transfer from host to peripheral.
This should be a complete transfer: higher-level protocols should
define an upper bound on the amount of data being transferred, and the
read operation should involve at least this
amount of data. The return value will indicate the actual transfer
size, which may be less than requested.
Some device drivers may support partial reads, but USB device drivers
are not expected to perform any buffering because that involves both
memory and code overheads. One technique that may work for bulk
transfers is to exploit the fact that such transfers happen in 64-byte
packets. It is possible to read an initial 64
bytes, corresponding to the first packet in the transfer. These 64
bytes can then be examined to determine the total transfer size, and
the remaining data can be transferred in another
read operation. This technique is not guaranteed
to work with all USB hardware. Also, if the delay between accepting
the first packet and the remainder of the transfer is excessive then
this could cause timeout problems for the host-side software. For
these reasons the use of partial reads should be avoided.
There are two likely error conditions. EPIPE
indicates that the connection between host and target has been broken.
EAGAIN indicates that the endpoint has been
stalled, either at the request of the host or by other activity
inside the peripheral.
USB device drivers are not expected to do any locking. If higher-level
code performs multiple concurrent read operations on a single endpoint
then the resulting behaviour is undefined.
select operations
Typical USB device drivers will not provide any support for
select. Consider bulk transfers from the host to
the peripheral. At the USB device driver level there is no way of
knowing in advance how large a transfer will be, so it is not feasible
for the device driver to buffer the entire transfer. It may be
possible to buffer part of the transfer, for example the first 64-byte
packet, and copy this into application space at the start of a
read, but this adds code and memory overheads.
Worse, it means that there is an unknown but potentially long delay
between a peripheral accepting the first packet of a transfer and the
remaining packets, which could confuse or upset the host-side
software.
With some USB hardware it may be possible for the device driver to
detect OUT tokens from the host without actually accepting the data,
and this would indicate that a read is likely to
succeed. However, it would not be reliable since the host-side I/O
operation could time out. A similar mechanism could be used to
implement select for outgoing data, but again
this would not be reliable.
Some device drivers may provide partial support for
select anyway, possibly under the control of a
configuration option. The device driver's documentation should be
consulted for further information. It is also worth noting that the
USB-specific non-blocking API can often be used as an alternative to
select.
get_config and
set_config operations
There are no set_config or
get_config (also known as
ioctl) operations defined for USB devices.
Some device drivers may provide hardware-specific facilities this way.
Currently the USB-specific functions related to halted endpoints cannot be accessed readily
via devtab entries. This functionality should probably be made
available via set_config and
get_config. It may also prove useful to provide
a get_config operation that maps from the
devtab entries to the underlying endpoint data structures.
Presence
The devtab entries are optional. If the USB device is accessed
primarily by class-specific code such as the USB-ethernet package and
that package uses the USB-specific API directly, the devtab entries
are redundant. Even if application code does need to access the USB
device, the non-blocking API may be more convenient than the blocking
I/O provided via the devtab entries. In these cases the devtab entries
serve no useful purpose, but they still impose a memory overhead. It
is possible to suppress the presence of these entries by disabling the
configuration option
CYGGLO_IO_USB_SLAVE_PROVIDE_DEVTAB_ENTRIES.
Receiving Data from the Hostusbs_start_rx_bufferReceiving Data from the Host
#include <cyg/io/usb/usbs.h>
void usbs_start_rx_bufferusbs_rx_endpoint* epunsigned char* bufferint lengthvoid (*)(void*,int) complete_fnvoid * complete_datavoid usbs_start_rxusbs_rx_endpoint* epDescriptionusbs_start_rx_buffer is a USB-specific function
to accept a transfer from host to peripheral. It can be used for bulk,
interrupt or isochronous transfers, but not for control messages.
Instead those involve manipulating the usbs_control_endpoint
data structure directly. The function takes five arguments:
The first argument identifies the specific endpoint that should be
used. Different USB devices will support different sets of endpoints
and the device driver will provide appropriate data structures. The
device driver's documentation should be consulted for details of which
endpoints are available.
The buffer and length
arguments control the actual transfer. USB device drivers are not
expected to perform any buffering or to support partial transfers, so
the length specified should correspond to the maximum transfer that is
currently possible and the buffer should be at least this large. For
isochronous transfers the USB specification imposes an upper bound of
1023 bytes, and a smaller limit may be set in the enumeration data. Interrupt
transfers are similarly straightforward with an upper bound of 64
bytes, or less as per the enumeration data. Bulk transfers are more
complicated because they can involve multiple 64-byte packets plus a
terminating packet of less than 64 bytes, so there is no predefined
limit on the transfer size. Instead it is left to higher-level
protocols to specify an appropriate upper bound.
One technique that may work for bulk transfers is to exploit the fact
that such transfers happen in 64-byte packets: it may be possible to
receive an initial 64 bytes, corresponding to the first packet in the
transfer; these 64 bytes can then be examined to determine the total
transfer size, and the remaining data can be transferred in another
receive operation. This technique is not guaranteed to work with all
USB hardware. Also, if the delay between accepting the first packet and
the remainder of the transfer is excessive then this could cause
timeout problems for the host-side software. For these reasons this
technique should be avoided.
usbs_start_rx_buffer is non-blocking. It merely
starts the receive operation, and does not wait for completion. At
some later point the USB device driver will invoke the completion
function parameter with two arguments: the completion data defined by
the last parameter and a result field. A result >=
0 indicates a successful transfer of that many
bytes, which may be less than the upper bound imposed by the
length argument. A result <
0 indicates an error. The most likely errors are
-EPIPE to indicate that the connection between the
host and the target has been broken, and -EAGAIN
for when the endpoint has been halted. Specific USB device drivers may
specify additional error conditions.
The normal sequence of events is that the USB device driver will
update the appropriate hardware registers. At some point after that
the host will attempt to send data by transmitting an OUT token
followed by a data packet, and since a receive operation is now in
progress the data will be accepted and ACK'd. If there were no receive
operation then the peripheral would instead generate a NAK. The USB
hardware will generate an interrupt once the whole packet has been
received, and the USB device driver will service this interrupt and
arrange for a DSR to be called. Isochronous and interrupt transfers
involve just a single packet. However, bulk transfers may involve
multiple packets so the device driver has to check whether the packet
was a full 64 bytes or whether it was a terminating packet of less
than this. When the device driver DSR detects a complete transfer it
will inform higher-level code by invoking the supplied completion
function.
This means that the completion function will normally be invoked by a
DSR and not in thread context - although some USB device drivers may
have a different implementation. Therefore the completion function is
restricted in what it can do. In particular it must not make any
calls that will or may block such as locking a mutex or allocating
memory. The kernel documentation should be consulted for more details
of DSR's and interrupt handling generally.
It is possible that the completion function will be invoked before
usbs_start_rx_buffer returns. Such an event would
be unusual because the transfer cannot happen until the next time the
host tries to send data to this peripheral, but it may happen if for
example another interrupt happens and a higher priority thread is
scheduled to run. Also, if the endpoint is currently halted then the
completion function will be invoked immediately with
-EAGAIN: typically this will happen in the current
thread rather than in a separate DSR. The completion function is
allowed to start another transfer immediately by calling
usbs_start_rx_buffer again.
USB device drivers are not expected to perform any locking. It is the
responsibility of higher-level code to ensure that there is only one
receive operation for a given endpoint in progress at any one time. If
there are concurrent calls to
usbs_start_rx_buffer then the resulting behaviour
is undefined. For typical USB applications this does not present any
problems, because only one piece of code will access a given endpoint
at any particular time.
The following code fragment illustrates a very simple use of
usbs_start_rx_buffer to implement a blocking
receive, using a semaphore to synchronise between the foreground
thread and the DSR. For a simple example like this no completion data
is needed.
static int error_code = 0;
static cyg_sem_t completion_wait;
static void
completion_fn(void* data, int result)
{
error_code = result;
cyg_semaphore_post(&completion_wait);
}
int
blocking_receive(usbs_rx_endpoint* ep, unsigned char* buf, int len)
{
error_code = 0;
usbs_start_rx_buffer(ep, buf, len, &completion_fn, NULL);
cyg_semaphore_wait(&completion_wait);
return error_code;
}
There is also a utility function usbs_start_rx. This
can be used by code that wants to manipulate data endpoints directly, specifically the
complete_fn,
complete_data,
buffer and
buffer_size fields.
usbs_start_tx just invokes a function
supplied by the device driver.
Sending Data to the Hostusbs_start_tx_bufferSending Data to the Host
#include <cyg/io/usb/usbs.h>
void usbs_start_tx_bufferusbs_tx_endpoint* epconst unsigned char* bufferint lengthvoid (*)(void*,int) complete_fnvoid * complete_datavoid usbs_start_txusbs_tx_endpoint* epDescriptionusbs_start_tx_buffer is a USB-specific function
to transfer data from peripheral to host. It can be used for bulk,
interrupt or isochronous transfers, but not for control messages;
instead those involve manipulating the usbs_control_endpoint
data structure directly. The function takes five arguments:
The first argument identifies the specific endpoint that should be
used. Different USB devices will support different sets of endpoints
and the device driver will provide appropriate data structures. The
device driver's documentation should be consulted for details of which
endpoints are available.
The buffer and length
arguments control the actual transfer. USB device drivers are not
allowed to modify the buffer during the transfer, so the data can
reside in read-only memory. The transfer will be for all the data
specified, and it is the responsibility of higher-level code to make
sure that the host is expecting this amount of data. For isochronous
transfers the USB specification imposes an upper bound of 1023 bytes,
but a smaller limit may be set in the enumeration data. Interrupt
transfers have an upper bound of 64 bytes or less, as per the
enumeration data. Bulk transfers are more complicated because they can
involve multiple 64-byte packets plus a terminating packet of less
than 64 bytes, so the basic USB specification does not impose an upper
limit on the total transfer size. Instead it is left to higher-level
protocols to specify an appropriate upper bound. If the peripheral
attempts to send more data than the host is willing to accept then the
resulting behaviour is undefined and may well depend on the specific
host operating system being used.
For bulk transfers, the USB device driver or the underlying hardware
will automatically split the transfer up into the appropriate number
of full-size 64-byte packets plus a single terminating packet, which
may be 0 bytes.
usbs_start_tx_buffer is non-blocking. It merely
starts the transmit operation, and does not wait for completion. At
some later point the USB device driver will invoke the completion
function parameter with two arguments: the completion data defined by
the last parameter, and a result field. This result will be either an
error code < 0, or the amount of data
transferred which should correspond to the
length argument. The most likely errors are
-EPIPE to indicate that the connection between the
host and the target has been broken, and -EAGAIN
for when the endpoint has been halted. Specific USB device drivers may
define additional error conditions.
The normal sequence of events is that the USB device driver will
update the appropriate hardware registers. At some point after that
the host will attempt to fetch data by transmitting an IN token. Since
a transmit operation is now in progress the peripheral can send a
packet of data, and the host will generate an ACK. At this point the
USB hardware will generate an interrupt, and the device driver will
service this interrupt and arrange for a DSR to be called. Isochronous
and interrupt transfers involve just a single packet. However, bulk
transfers may involve multiple packets so the device driver has to
check whether there is more data to send and set things up for the
next packet. When the device driver DSR detects a complete transfer it
will inform higher-level code by invoking the supplied completion
function.
This means that the completion function will normally be invoked by a
DSR and not in thread context - although some USB device drivers may
have a different implementation. Therefore the completion function is
restricted in what it can do, in particular it must not make any
calls that will or may block such as locking a mutex or allocating
memory. The kernel documentation should be consulted for more details
of DSR's and interrupt handling generally.
It is possible that the completion function will be invoked before
usbs_start_tx_buffer returns. Such an event would
be unusual because the transfer cannot happen until the next time the
host tries to fetch data from this peripheral, but it may happen if,
for example, another interrupt happens and a higher priority thread is
scheduled to run. Also, if the endpoint is currently halted then the
completion function will be invoked immediately with
-EAGAIN: typically this will happen in the current
thread rather than in a separate DSR. The completion function is
allowed to start another transfer immediately by calling
usbs_start_tx_buffer again.
USB device drivers are not expected to perform any locking. It is the
responsibility of higher-level code to ensure that there is only one
transmit operation for a given endpoint in progress at any one time.
If there are concurrent calls to
usbs_start_tx_buffer then the resulting behaviour
is undefined. For typical USB applications this does not present any
problems because only piece of code will access a given endpoint at
any particular time.
The following code fragment illustrates a very simple use of
usbs_start_tx_buffer to implement a blocking
transmit, using a semaphore to synchronise between the foreground
thread and the DSR. For a simple example like this no completion data
is needed.
static int error_code = 0;
static cyg_sem_t completion_wait;
static void
completion_fn(void* data, int result)
{
error_code = result;
cyg_semaphore_post(&completion_wait);
}
int
blocking_transmit(usbs_tx_endpoint* ep, const unsigned char* buf, int len)
{
error_code = 0;
usbs_start_tx_buffer(ep, buf, len, &completion_fn, NULL);
cyg_semaphore_wait(&completion_wait);
return error_code;
}
There is also a utility function usbs_start. This
can be used by code that wants to manipulate data endpoints directly, specifically the
complete_fn,
complete_data,
buffer and
buffer_size fields.
usbs_start_tx just calls a function supplied by
the device driver.
Halted EndpointsHalted EndpointsSupport for Halting and Halted Endpoints
#include <cyg/io/usb/usbs.h>
cyg_bool usbs_rx_endpoint_haltedusbs_rx_endpoint* epvoid usbs_set_rx_endpoint_haltedusbs_rx_endpoint* epcyg_bool new_statevoid usbs_start_rx_endpoint_waitusbs_rx_endpoint* epvoid (*)(void*, int) complete_fnvoid * complete_datacyg_bool
usbs_tx_endpoint_haltedusbs_tx_endpoint* epvoid usbs_set_tx_endpoint_haltedusbs_tx_endpoint* epcyg_bool new_statevoid usbs_start_tx_endpoint_waitusbs_tx_endpoint* epvoid (*)(void*, int) complete_fnvoid * complete_dataDescription
Normal USB traffic involves straightforward handshakes, with either an
ACK to indicate that a packet was transferred
without errors, or a NAK if an error occurred, or
if a peripheral is currently unable to process another packet from the
host, or has no packet to send to the host. There is a third form of
handshake, a STALL, which indicates that the
endpoint is currently halted.
When an endpoint is halted it means that the host-side code needs to
take some sort of recovery action before communication over that
endpoint can resume. The exact circumstances under which this can
happen are not defined by the USB specification, but one example would
be a protocol violation if say the peripheral attempted to transmit
more data to the host than was permitted by the protocol in use. The
host can use the standard control messages get-status, set-feature and
clear-feature to examine and manipulate the halted status of a given
endpoint. There are USB-specific functions which can be used inside
the peripheral to achieve the same effect. Once an endpoint has been
halted the host can then interact with the peripheral using class or
vendor control messages to perform appropriate recovery, and then the
halted condition can be cleared.
Halting an endpoint does not constitute a device state change, and
there is no mechanism by which higher-level code can be informed
immediately. However, any ongoing receive or transmit operations will
be aborted with an -EAGAIN error, and any new
receives or transmits will fail immediately with the same error.
There are six functions to support halted endpoints, one set for
receive endpoints and another for transmit endpoints, with both sets
behaving in essentially the same way. The first,
usbs_rx_endpoint_halted, can be used to determine
whether or not an endpoint is currently halted: it takes a single
argument that identifies the endpoint of interest. The second
function, usbs_set_rx_endpoint_halted, can be
used to change the halted condition of an endpoint: it takes two
arguments; one to identify the endpoint and another to specify the new
state. The last function
usbs_start_rx_endpoint_wait operates in much the
same way as usbs_start_rx_buffer: when the
endpoint is no longer halted the device driver will invoke the
supplied completion function with a status of 0. The completion
function has the same signature as that for a transfer operation.
Often it will be possible to use a single completion function and have
the foreground code invoke either
usbs_start_rx_buffer or
usbs_start_rx_endpoint_wait depending on the
current state of the endpoint.
Control EndpointsControl EndpointsControl endpoint data structure
#include <cyg/io/usb/usbs.h>
typedef struct usbs_control_endpoint {
*hellip;
} usbs_control_endpoint;
usbs_control_endpoint Data Structure
The device driver for a USB slave device should supply one
usbs_control_endpoint data structure per USB
device. This corresponds to endpoint 0 which will be used for all
control message interaction between the host and that device. The data
structure is also used for internal management purposes, for example
to keep track of the current state. In a typical USB peripheral there
will only be one such data structure in the entire system, but if
there are multiple USB slave ports, allowing the peripheral to be
connected to multiple hosts, then there will be a separate data
structure for each one. The name or names of the data structures are
determined by the device drivers. For example, the SA11x0 USB device
driver package provides usbs_sa11x0_ep0.
The operations on a control endpoint do not fit cleanly into a
conventional open/read/write I/O model. For example, when the host
sends a control message to the USB peripheral this may be one of four
types: standard, class, vendor and reserved. Some or all of the
standard control messages will be handled automatically by the common
USB slave package or by the device driver itself. Other standard
control messages and the other types of control messages may be
handled by a class-specific package or by application code. Although
it would be possible to have devtab entries such as
/dev/usbs_ep0/standard and
/dev/usbs_ep0/class, and then support read and
write operations on these devtab entries, this would add significant
overhead and code complexity. Instead, all of the fields in the
control endpoint data structure are public and can be manipulated
directly by higher level code if and when required.
Control endpoints involve a number of callback functions, with
higher-level code installing suitable function pointers in the control
endpoint data structure. For example, if the peripheral involves
vendor-specific control messages then a suitable handler for these
messages should be installed. Although the exact details depend on the
device driver, typically these callback functions will be invoked at
DSR level rather than thread level. Therefore, only certain eCos
functions can be invoked; specifically, those functions that are
guaranteed not to block. If a potentially blocking function such as a
semaphore wait or a mutex lock operation is invoked from inside the
callback then the resulting behaviour is undefined, and the system as
a whole may fail. In addition, if one of the callback functions
involves significant processing effort then this may adversely affect
the system's real time characteristics. The eCos kernel documentation
should be consulted for more details of DSR handling.
Initialization
The usbs_control_endpoint data structure
contains the following fields related to initialization.
typedef struct usbs_control_endpoint {
…
const usbs_enumeration_data* enumeration_data;
void (*start_fn)(usbs_control_endpoint*);
…
};
It is the responsibility of higher-level code, usually the
application, to define the USB enumeration data. This needs to be
installed in the control endpoint data structure early on during
system startup, before the USB device is actually started and any
interaction with the host is possible. Details of the enumeration data
are supplied in the section USB Enumeration
Data. Typically, the enumeration data is constant for a given
peripheral, although it can be constructed dynamically if necessary.
However, the enumeration data cannot change while the peripheral is
connected to a host: the peripheral cannot easily claim to be a
keyboard one second and a printer the next.
The start_fn member is normally accessed
via the utility usbs_start rather
than directly. It is provided by the device driver and should be
invoked once the system is fully initialized and interaction with the
host is possible. A typical implementation would change the USB data
pins from tristated to active. If the peripheral is already plugged
into a host then the latter should detect this change and start
interacting with the peripheral, including requesting the enumeration
data.
State
There are three usbs_control_endpoint fields
related to the current state of a USB slave device, plus some state
constants and an enumeration of the possible state changes:
typedef struct usbs_control_endpoint {
…
int state;
void (*state_change_fn)(struct usbs_control_endpoint*, void*,
usbs_state_change, int);
void* state_change_data;
…
};
#define USBS_STATE_DETACHED 0x01
#define USBS_STATE_ATTACHED 0x02
#define USBS_STATE_POWERED 0x03
#define USBS_STATE_DEFAULT 0x04
#define USBS_STATE_ADDRESSED 0x05
#define USBS_STATE_CONFIGURED 0x06
#define USBS_STATE_MASK 0x7F
#define USBS_STATE_SUSPENDED (1 << 7)
typedef enum {
USBS_STATE_CHANGE_DETACHED = 1,
USBS_STATE_CHANGE_ATTACHED = 2,
USBS_STATE_CHANGE_POWERED = 3,
USBS_STATE_CHANGE_RESET = 4,
USBS_STATE_CHANGE_ADDRESSED = 5,
USBS_STATE_CHANGE_CONFIGURED = 6,
USBS_STATE_CHANGE_DECONFIGURED = 7,
USBS_STATE_CHANGE_SUSPENDED = 8,
USBS_STATE_CHANGE_RESUMED = 9
} usbs_state_change;
The USB standard defines a number of states for a given USB
peripheral. The initial state is detached, where
the peripheral is either not connected to a host at all or, from the
host's perspective, the peripheral has not started up yet because the
relevant pins are tristated. The peripheral then moves via
intermediate attached and
powered states to its default or
reset state, at which point the host and
peripheral can actually start exchanging data. The first message is
from host to peripheral and provides a unique 7-bit address within the
local USB network, resulting in a state change to
addressed. The host then requests enumeration
data and performs other initialization. If everything succeeds the
host sends a standard set-configuration control message, after which
the peripheral is configured and expected to be
up and running. Note that some USB device drivers may be unable to
distinguish between the detached,
attached and powered states
but generally this is not important to higher-level code.
A USB host should generate at least one token every millisecond. If a
peripheral fails to detect any USB traffic for a period of time then
typically this indicates that the host has entered a power-saving
mode, and the peripheral should do the same if possible. This
corresponds to the suspended bit. The actual
state is a combination of suspended and the
previous state, for example configured and
suspended rather than just
suspended. When the peripheral subsequently
detects USB traffic it would switch back to the
configured state.
The USB device driver and the common USB slave package will maintain
the current state in the control endpoint's
state field. There should be no need for
any other code to change this field, but it can be examined whenever
appropriate. In addition whenever a state change occurs the generic
code can invoke a state change callback function. By default, no such
callback function will be installed. Some class-specific packages such
as the USB-ethernet package will install a suitable function to keep
track of whether or not the host-peripheral connection is up, that is
whether or not ethernet packets can be exchanged. Application code can
also update this field. If multiple parties want to be informed of
state changes, for example both a class-specific package and
application code, then typically the application code will install its
state change handler after the class-specific package and is
responsible for chaining into the package's handler.
The state change callback function is invoked with four arguments. The
first identifies the control endpoint. The second is an arbitrary
pointer: higher-level code can fill in the
state_change_data field to set this. The
third argument specifies the state change that has occurred, and the
last argument supplies the previous state (the new state is readily
available from the control endpoint structure).
eCos does not provide any utility functions for updating or examining
the state_change_fn or
state_change_data fields. Instead, it is
expected that the fields in the
usbs_control_endpoint data structure will be
manipulated directly. Any utility functions would do just this, but
at the cost of increased code and cpu overheads.
Standard Control Messages
typedef struct usbs_control_endpoint {
…
unsigned char control_buffer[8];
usbs_control_return (*standard_control_fn)(struct usbs_control_endpoint*, void*);
void* standard_control_data;
…
} usbs_control_endpoint;
typedef enum {
USBS_CONTROL_RETURN_HANDLED = 0,
USBS_CONTROL_RETURN_UNKNOWN = 1,
USBS_CONTROL_RETURN_STALL = 2
} usbs_control_return;
extern usbs_control_return usbs_handle_standard_control(struct usbs_control_endpoint*);
When a USB peripheral is connected to the host it must always respond
to control messages sent to endpoint 0. Control messages always
consist of an initial eight-byte header, containing fields such as a
request type. This may be followed by a further data transfer, either
from host to peripheral or from peripheral to host. The way this is
handled is described in the Buffer Management section below.
The USB device driver will always accept the initial eight-byte
header, storing it in the control_buffer
field. Then it determines the request type: standard, class, vendor,
or reserved. The way in which the last three of these are processed is
described in the section Other
Control Messages. Some
standard control messages will be handled by the device driver itself;
typically the set-address request and the
get-status, set-feature and
clear-feature requests when applied to endpoints.
If a standard control message cannot be handled by the device driver
itself, the driver checks the
standard_control_fn field in the control
endpoint data structure. If higher-level code has installed a suitable
callback function then this will be invoked with two argument, the
control endpoint data structure itself and the
standard_control_data field. The latter
allows the higher level code to associate arbitrary data with the
control endpoint. The callback function can return one of three
values: HANDLED to indicate that the request has
been processed; UNKNOWN if the message should be
handled by the default code; or STALL to indicate
an error condition. If higher level code has not installed a callback
function or if the callback function has returned
UNKNOWN then the device driver will invoke a
default handler, usbs_handle_standard_control
provided by the common USB slave package.
The default handler can cope with all of the standard control messages
for a simple USB peripheral. However, if the peripheral involves
multiple configurations, multiple interfaces in a configuration, or
alternate settings for an interface, then this cannot be handled by
generic code. For example, a multimedia peripheral may support various
alternate settings for a given data source with different bandwidth
requirements, and the host can select a setting that takes into
account the current load. Clearly higher-level code needs to be aware
when the host changes the current setting, so that it can adjust the
rate at which data is fed to or retrieved from the host. Therefore the
higher-level code needs to install its own standard control callback
and process appropriate messages, rather than leaving these to the
default handler.
The default handler will take care of the
get-descriptor request used to obtain the
enumeration data. It has support for string descriptors but ignores
language encoding issues. If language encoding is important for the
peripheral then this will have to be handled by an
application-specific standard control handler.
The header file <cyg/io/usb/usb.h> defines various
constants related to control messages, for example the function codes
corresponding to the standard request types. This header file is
provided by the common USB package, not by the USB slave package,
since the information is also relevant to USB hosts.
Other Control Messages
typedef struct usbs_control_endpoint {
…
usbs_control_return (*class_control_fn)(struct usbs_control_endpoint*, void*);
void* class_control_data;
usbs_control_return (*vendor_control_fn)(struct usbs_control_endpoint*, void*);
void* vendor_control_data;
usbs_control_return (*reserved_control_fn)(struct usbs_control_endpoint*, void*);
void* reserved_control_data;
…
} usbs_control_endpoint;
Non-standard control messages always have to be processed by
higher-level code. This could be class-specific packages. For example,
the USB-ethernet package will handle requests for getting the MAC
address and for enabling or disabling promiscuous mode. In all cases
the device driver will store the initial request in the
control_buffer field, check for an
appropriate handler, and invoke it with details of the control
endpoint and any handler-specific data that has been installed
alongside the handler itself. The handler should return either
USBS_CONTROL_RETURN_HANDLED to report success or
USBS_CONTROL_RETURN_STALL to report failure. The
device driver will report this to the host.
If there are multiple parties interested in a particular type of
control messages, it is the responsibility of application code to
install an appropriate handler and process the requests appropriately.
Buffer Management
typedef struct usbs_control_endpoint {
…
unsigned char* buffer;
int buffer_size;
void (*fill_buffer_fn)(struct usbs_control_endpoint*);
void* fill_data;
int fill_index;
usbs_control_return (*complete_fn)(struct usbs_control_endpoint*, int);
…
} usbs_control_endpoint;
Many USB control messages involve transferring more data than just the
initial eight-byte header. The header indicates the direction of the
transfer, OUT for host to peripheral or IN for peripheral to host.
It also specifies a length field, which is exact for an OUT transfer
or an upper bound for an IN transfer. Control message handlers can
manipulate six fields within the control endpoint data structure to
ensure that the transfer happens correctly.
For an OUT transfer, the handler should examine the length field in
the header and provide a single buffer for all the data. A
class-specific protocol would typically impose an upper bound on the
amount of data, allowing the buffer to be allocated statically.
The handler should update the buffer and
complete_fn fields. When all the data has
been transferred the completion callback will be invoked, and its
return value determines the response sent back to the host. The USB
standard allows for a new control message to be sent before the
current transfer has completed, effectively cancelling the current
operation. When this happens the completion function will also be
invoked. The second argument to the completion function specifies what
has happened, with a value of 0 indicating success and an error code
such as -EPIPE or -EIO
indicating that the current transfer has been cancelled.
IN transfers are a little bit more complicated. The required
information, for example the enumeration data, may not be in a single
contiguous buffer. Instead a mechanism is provided by which the buffer
can be refilled, thus allowing the transfer to move from one record to
the next. Essentially, the transfer operates as follows:
When the host requests another chunk of data (typically eight bytes),
the USB device driver will examine the
buffer_size field. If non-zero then
buffer contains at least one more byte of
data, and then buffer_size is decremented.
When buffer_size has dropped to 0, the
fill_buffer_fn field will be examined. If
non-null it will be invoked to refill the buffer.
The fill_data and
fill_index fields are not used by the
device driver. Instead these fields are available to the refill
function to keep track of the current state of the transfer.
When buffer_size is 0 and
fill_buffer_fn is NULL, no more data is
available and the transfer has completed.
Optionally a completion function can be installed. This will be
invoked with 0 if the transfer completes successfully, or with an
error code if the transfer is cancelled because of another control
messsage.
If the requested data is contiguous then the only fields that need
to be manipulated are buffer and
buffer_size, and optionally
complete_fn. If the requested data is not
contiguous then the initial control message handler should update
fill_buffer_fn and some or all of the other
fields, as required. An example of this is the handling of the
standard get-descriptor control message by
usbs_handle_standard_control.
Polling Support
typedef struct usbs_control_endpoint {
void (*poll_fn)(struct usbs_control_endpoint*);
int interrupt_vector;
…
} usbs_control_endpoint;
In nearly all circumstances USB I/O should be interrupt-driven.
However, there are special environments such as RedBoot where polled
operation may be appropriate. If the device driver can operate in
polled mode then it will provide a suitable function via the
poll_fn field, and higher-level code can
invoke this regularly. This polling function will take care of all
endpoints associated with the device, not just the control endpoint.
If the USB hardware involves a single interrupt vector then this will
be identified in the data structure as well.
Dynamic Data Endpoint Support
typedef struct usbs_control_endpoint {
struct usbs_rx_endpoint* (*get_rxep_fn)(struct usbs_control_endpoint*, cyg_uint8);
struct usbs_tx_endpoint* (*get_txep_fn)(struct usbs_control_endpoint*, cyg_uint8);
…
} usbs_control_endpoint;
USB slave hardware may support multiple USB configurations via
configurable data endpoints. If the device driver can support such
operation, it will provide a pair of functions via the
get_rxep_fn and
get_txep_fn fields which enable retrieval
of the receive and transmit data endpoint structures using the logical
endpoint IDs specified in a USB class descriptor. Access to these
functions from higher-level code is provided by the
usbs_get_rx_endpoint and
usbs_get_tx_endpoint functions.
Data EndpointsData EndpointsData endpoint data structures
#include <cyg/io/usb/usbs.h>
typedef struct usbs_rx_endpoint {
void (*start_rx_fn)(struct usbs_rx_endpoint*);
void (*set_halted_fn)(struct usbs_rx_endpoint*, cyg_bool);
void (*complete_fn)(void*, int);
void* complete_data;
unsigned char* buffer;
int buffer_size;
cyg_bool halted;
} usbs_rx_endpoint;
typedef struct usbs_tx_endpoint {
void (*start_tx_fn)(struct usbs_tx_endpoint*);
void (*set_halted_fn)(struct usbs_tx_endpoint*, cyg_bool);
void (*complete_fn)(void*, int);
void* complete_data;
const unsigned char* buffer;
int buffer_size;
cyg_bool halted;
} usbs_tx_endpoint;
Receive and Transmit Data Structures
In addition to a single usbs_control_endpoint
data structure per USB slave device, the USB device driver should also
provide receive and transmit data structures corresponding to the
other endpoints. The names of these are determined by the device
driver. For example, the SA1110 USB device driver package provides
usbs_sa11x0_ep1 for receives and
usbs_sa11x0_ep2 for transmits.
Unlike control endpoints, the common USB slave package does provide a
number of utility routines to manipulate data endpoints. For example
usbs_start_rx_buffer
can be used to receive data from the host into a buffer. In addition
the USB device driver can provide devtab entries such as
/dev/usbs1r and /dev/usbs2w, so
higher-level code can open these devices and then
perform blocking read and
write operations.
However, the operation of data endpoints and the various
endpoint-related functions is relatively straightforward. First
consider a usbs_rx_endpoint structure. The
device driver will provide the members
start_rx_fn and
set_halted_fn, and it will maintain the
halted field. To receive data, higher-level
code sets the buffer,
buffer_size,
complete_fn and optionally the
complete_data fields. Next the
start_rx_fn member should be called. When
the transfer has finished the device driver will invoke the completion
function, using complete_data as the first
argument and a size field for the second argument. A negative size
indicates an error of some sort: -EGAIN indicates
that the endpoint has been halted, usually at the request of the host;
-EPIPE indicates that the connection between the
host and the peripheral has been broken. Certain device drivers may
generate other error codes.
If higher-level code needs to halt or unhalt an endpoint then it can
invoke the set_halted_fn member. When an
endpoint is halted, invoking start_rx_fn
wit buffer_size set to 0 indicates that
higher-level code wants to block until the endpoint is no longer
halted; at that point the completion function will be invoked.
USB device drivers are allowed to assume that higher-level protocols
ensure that host and peripheral agree on the amount of data that will
be transferred, or at least on an upper bound. Therefore there is no
need for the device driver to maintain its own buffers, and copy
operations are avoided. If the host sends more data than expected then
the resulting behaviour is undefined.
Transmit endpoints work in essentially the same way as receive
endpoints. Higher-level code should set the
buffer and
buffer_size fields to point at the data to
be transferred, then call start_tx_fn, and
the device driver will invoked the completion function when the
transfer has completed.
USB device drivers are not expected to perform any locking. If at any
time there are two concurrent receive operations for a given endpoint,
or two concurrent transmit operations, then the resulting behaviour is
undefined. It is the responsibility of higher-level code to perform
any synchronisation that may be necessary. In practice, conflicts are
unlikely because typically a given endpoint will only be accessed
sequentially by just one part of the overall system.
Writing a USB Device DriverWriting a USB Device DriverUSB Device Driver Porting GuideIntroduction
Often the best way to write a USB device driver will be to start with
an existing one and modify it as necessary. The information given here
is intended primarily as an outline rather than as a complete guide.
At the time of writing only one USB device driver has been
implemented. Hence it is possible, perhaps probable, that some
portability issues have not yet been addressed. One issue
involves the different types of transfer, for example the initial
target hardware had no support for isochronous or interrupt transfers,
so additional functionality may be needed to switch between transfer
types. Another issue would be hardware where a given endpoint number,
say endpoint 1, could be used for either receiving or transmitting
data, but not both because a single fifo is used. Issues like these
will have to be resolved as and when additional USB device drivers are
written.
The Control Endpoint
A USB device driver should provide a single usbs_control_endpoint
data structure for every USB device. Typical peripherals will have
only one USB port so there will be just one such data structure in the
entire system, but theoretically it is possible to have multiple USB
devices. These may all involve the same chip, in which case a single
device driver should support multiple device instances, or they may
involve different chips. The name or names of these data structures
are determined by the device driver, but appropriate care should be
taken to avoid name clashes.
A USB device cannot be used unless the control endpoint data structure
exists. However, the presence of USB hardware in the target processor
or board does not guarantee that the application will necessarily want
to use that hardware. To avoid unwanted code or data overheads, the
device driver can provide a configuration option to determine whether
or not the endpoint 0 data structure is actually provided. A default
value of CYGINT_IO_USB_SLAVE_CLIENTS ensures that
the USB driver will be enabled automatically if higher-level code does
require USB support, while leaving ultimate control to the user.
The USB device driver is responsible for filling in the
start_fn,
poll_fn and
interrupt_vector fields. Usually this can
be achieved by static initialization. The driver is also largely
responsible for maintaining the state
field. The control_buffer array should be
used to hold the first packet of a control message. The
buffer and other fields related to data
transfers will be managed jointly by higher-level code and
the device driver. The remaining fields are generally filled in by
higher-level code, although the driver should initialize them to NULL
values.
Hardware permitting, the USB device should be inactive until the
start_fn is invoked, for example by
tristating the appropriate pins. This prevents the host from
interacting with the peripheral before all other parts of the system
have initialized. It is expected that the
start_fn will only be invoked once, shortly
after power-up.
Where possible the device driver should detect state changes, such as
when the connection between host and peripheral is established, and
report these to higher-level
code via the state_change_fn callback, if
any. The state change to and from configured state cannot easily be
handled by the device driver itself, instead higher-level code such as
the common USB slave package will take care of this.
Once the connection between host and peripheral has been established,
the peripheral must be ready to accept control messages at all times,
and must respond to these within certain time constraints. For
example, the standard set-address control message must be handled
within 50ms. The USB specification provides more information on these
constraints. The device driver is responsible for receiving the
initial packet of a control message. This packet will always be eight
bytes and should be stored in the
control_buffer field. Certain standard
control messages should be detected and handled by the device driver
itself. The most important is set-address, but usually the get-status,
set-feature and clear-feature requests when applied to halted
endpoints should also be handled by the driver. Other standard control
messages should first be passed on to the
standard_control_fn callback (if any), and
finally to the default handler
usbs_handle_standard_control provided by the
common USB slave package. Class, vendor and reserved control messages
should always be dispatched to the appropriate callback and there is
no default handler for these.
Some control messages will involve further data transfer, not just the
initial packet. The device driver must handle this in accordance with
the USB specification and the buffer management strategy. The
driver is also responsible for keeping track of whether or not the
control operation has succeeded and generating an ACK or STALL
handshake.
The polling support is optional and may not be feasible on all
hardware. It is only used in certain specialised environments such as
RedBoot. A typical implementation of the polling function would just
check whether or not an interrupt would have occurred and, if so, call
the same code that the interrupt handler would.
Data Endpoints
In addition to the control endpoint data structure, a USB device
driver should also provide appropriate data
endpoint data structures. Obviously this is only relevant if
the USB support generally is desired, that is if the control endpoint is
provided. In addition, higher-level code may not require all the
endpoints, so it may be useful to provide configuration options that
control the presence of each endpoint. For example, the intended
application might only involve a single transmit endpoint and of
course control messages, so supporting receive endpoints might waste
memory.
Conceptually, data endpoints are much simpler than the control
endpoint. The device driver has to supply two functions, one for
data transfers and another to control the halted condition. These
implement the functionality for
usbs_start_rx_buffer,
usbs_start_tx_buffer,
usbs_set_rx_endpoint_halted and
usbs_set_tx_endpoint_halted.
The device driver is also responsible for maintaining the
halted status.
For data transfers, higher-level code will have filled in the
buffer,
buffer_size,
complete_fn and
complete_data fields. The transfer function
should arrange for the transfer to start, allowing the host to send or
receive packets. Typically this will result in an interrupt at the end
of the transfer or after each packet. Once the entire transfer has
been completed, the driver's interrupt handling code should invoke the
completion function. This can happen either in DSR context or thread
context, depending on the driver's implementation. There are a number
of special cases to consider. If the endpoint is halted when the
transfer is started then the completion function can be invoked
immediately with -EAGAIN. If the transfer cannot be
completed because the connection is broken then the completion
function should be invoked with -EPIPE. If the
endpoint is stalled during the transfer, either because of a standard
control message or because higher-level code calls the appropriate
set_halted_fn, then again the completion
function should be invoked with -EAGAIN. Finally,
the <usbs_start_rx_endpoint_wait and
usbs_start_tx_endpoint_wait functions involve
calling the device driver's data transfer function with a buffer size
of 0 bytes.
Giving a buffer size of 0 bytes a special meaning is problematical
because it prevents transfers of that size. Such transfers are allowed
by the USB protocol, consisting of just headers and acknowledgements
and an empty data phase, although rarely useful. A future modification
of the device driver specification will address this issue, although
care has to be taken that the functionality remains accessible through
devtab entries as well as via low-level accesses.
Devtab Entries
For some applications or higher-level packages it may be more
convenient to use traditional open/read/write I/O calls rather than
the non-blocking USB I/O calls. To support this the device driver can
provide a devtab entry for each endpoint, for example:
#ifdef CYGVAR_DEVS_USB_SA11X0_EP1_DEVTAB_ENTRY
static CHAR_DEVIO_TABLE(usbs_sa11x0_ep1_devtab_functions,
&cyg_devio_cwrite,
&usbs_devtab_cread,
&cyg_devio_bwrite,
&cyg_devio_bread,
&cyg_devio_select,
&cyg_devio_get_config,
&cyg_devio_set_config);
static CHAR_DEVTAB_ENTRY(usbs_sa11x0_ep1_devtab_entry,
CYGDAT_DEVS_USB_SA11X0_DEVTAB_BASENAME "1r",
0,
&usbs_sa11x0_ep1_devtab_functions,
&usbs_sa11x0_devtab_dummy_init,
0,
(void*) &usbs_sa11x0_ep1);
#endif
Again care must be taken to avoid name clashes. This can be achieved
by having a configuration option to control the base name, with a
default value of e.g. /dev/usbs, and appending an
endpoint-specific string. This gives the application developer
sufficient control to eliminate any name clashes. The common USB slave
package provides functions usbs_devtab_cwrite and
usbs_devtab_cread, which can be used in the
function tables for transmit and receive endpoints respectively. The
private field priv of the devtab entry
should be a pointer to the underlying endpoint data structure.
Because devtab entries are never accessed directly, only indirectly,
they would usually be eliminated by the linker. To avoid this the
devtab entries should normally be defined in a separate source file
which ends up the special library libextras.a
rather than in the default library libtarget.a.
Not all applications or higher-level packages will want to use the
devtab entries and the blocking I/O facilities. It may be appropriate
for the device driver to provide additional configuration options that
control whether or not any or all of the devtab entries should be
provided, to avoid unnecessary memory overheads.
Interrupt Handling
A typical USB device driver will need to service interrupts for all of
the endpoints and possibly for additional USB events such as entering
or leaving suspended mode. Usually these interrupts need not be
serviced directly by the ISR. Instead, they can be left to a DSR. If
the peripheral is not able to accept or send another packet just yet,
the hardware will generate a NAK and the host will just retry a little
bit later. If high throughput is required then it may be desirable to
handle the bulk transfer protocol largely at ISR level, that is take
care of each packet in the ISR and only activate the DSR once the
whole transfer has completed.
Control messages may involve invoking arbitrary callback functions in
higher-level code. This should normally happen at DSR level. Doing it
at ISR level could seriously affect the system's interrupt latency and
impose unacceptable constraints on what operations can be performed by
those callbacks. If the device driver requires a thread anyway then it
may be appropriate to use this thread for invoking the callbacks, but
usually it is not worthwhile to add a new thread to the system just
for this; higher-level code is expected to write callbacks that
function sensibly at DSR level. Much the same applies to the
completion functions associated with data transfers. These should also
be invoked at DSR or thread level.
Support for USB Testing
Optionally a USB device driver can provide support for the
USB test software. This requires
defining a number of additional data structures, allowing the
generic test code to work out just what the hardware is capable of and
hence what testing can be performed.
The key data structure is
usbs_testing_endpoint, defined in cyg/io/usb/usbs.h. In addition some
commonly required constants are provided by the common USB package in
cyg/io/usb/usb.h. One
usbs_testing_endpoint structure should be
defined for each supported endpoint. The following fields need to be
filled in:
endpoint_type
This specifies the type of endpoint and should be one of
USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL,
BULK, ISOCHRONOUS or
INTERRUPT.
endpoint_number
This identifies the number that should be used by the host
to address this endpoint. For a control endpoint it should
be 0. For other types of endpoints it should be between
1 and 15.
endpoint_direction
For control endpoints this field is irrelevant. For other
types of endpoint it should be either
USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN or
USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT. If a given
endpoint number can be used for traffic in both directions then
there should be two entries in the array, one for each direction.
endpoint
This should be a pointer to the appropriate
usbs_control_endpoint,
usbs_rx_endpoint or
usbs_tx_endpoint structure, allowing the
generic testing code to perform low-level I/O.
devtab_entry
If the endpoint also has an entry in the system's device table then
this field should give the corresponding string, for example
"/dev/usbs1r". This allows the
generic testing code to access the device via higher-level
calls like open and read.
min_size
This indicates the smallest transfer size that the hardware can
support on this endpoint. Typically this will be one.
Strictly speaking a minimum size of one is not quite right since it
is valid for a USB transfer to involve zero bytes, in other words a
transfer that involves just headers and acknowledgements and an
empty data phase, and that should be tested as well. However current
device drivers interpret a transfer size of 0 as special, so that
would have to be resolved first.
max_size
Similarly, this specifies the largest transfer size. For control
endpoints the USB protocol uses only two bytes to hold the transfer
length, so there is an upper bound of 65535 bytes. In practice
it is very unlikely that any control transfers would ever need to
be this large, and in fact such transfers would take a long time
and probably violate timing constraints. For other types of endpoint
any of the protocol, the hardware, or the device driver may impose
size limits. For example a given device driver might be unable to
cope with transfers larger than 65535 bytes. If it should be
possible to transfer arbitrary amounts of data then a value of
-1 indicates no upper limit, and transfer
sizes will be limited by available memory and by the capabilities
of the host machine.
max_in_padding
This field is needed on some hardware where it is impossible to
send packets of a certain size. For example the hardware may be
incapable of sending an empty bulk packet to terminate a transfer
that is an exact multiple of the 64-byte bulk packet size.
Instead the driver has to do some padding and send an extra byte,
and the host has to be prepared to receive this extra byte. Such a
driver should specify a value of 1 for the
padding field. For most drivers this field should be set to
0.
A better solution would be for the device driver to supply a
fragment of Tcl code that would adjust the receive buffer size
only when necessary, rather than for every transfer. Forcing
receive padding on all transfers when only certain transfers
will actually be padded reduces the accuracy of certain tests.
alignment
On some hardware data transfers may need to be aligned to certain
boundaries, for example a word boundary or a cacheline boundary.
Although in theory device drivers could hide such alignment
restrictions from higher-level code by having their own buffers and
performing appropriate copying, that would be expensive in terms of
both memory and cpu cycles. Instead the generic testing code will
align any buffers passed to the device driver to the specified
boundary. For example, if the driver requires that buffers be
aligned to a word boundary then it should specify an alignment
value of 4.
The device driver should provide an array of these structures
usbs_testing_endpoints[]. The USB testing code
examines this array and uses the information to perform appropriate
tests. Because different USB devices support different numbers of
endpoints the number of entries in the array is not known in advance,
so instead the testing code looks for a special terminator
USBS_TESTING_ENDPOINTS_TERMINATOR. An example
array, showing just the control endpoint and the terminator, might
look like this:
usbs_testing_endpoint usbs_testing_endpoints[] = {
{
endpoint_type : USB_ENDPOINT_DESCRIPTOR_ATTR_CONTROL,
endpoint_number : 0,
endpoint_direction : USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN,
endpoint : (void*) &ep0.common,
devtab_entry : (const char*) 0,
min_size : 1,
max_size : 0x0FFFF,
max_in_padding : 0,
alignment : 0
},
…,
USBS_TESTING_ENDPOINTS_TERMINATOR
};
The use of a single array usbs_testing_endpoints
limits USB testing to platforms with a single USB device: if there
were multiple devices, each defining their own instance of this array,
then there would a collision at link time. In practice this should not
be a major problem since typical USB peripherals only interact with a
single host machine via a single slave port. In addition, even if a
peripheral did have multiple slave ports the current USB testing code
would not support this since it would not know which port to use.
TestingTestingTesting of USB Device DriversIntroduction
The support for USB testing provided by the eCos USB common slave
package is somewhat different in nature from the kind of testing used
in many other packages. One obvious problem is that USB tests cannot
be run on just a bare target platform: instead the target platform
must be connected to a suitable USB host machine, and that host
machine must be running appropriate software for the test code to
interact with. This is very different from say a kernel test which
typically will have no external dependencies. Another important
difference between USB testing and say a C library
strcmp test is sensitivity to timing and to
hardware boundary conditions: although a simple test case that just
performs a small number of USB transfers is better than no testing at
all, it should also be possible to run tests for hours or days on end,
under a variety of loads. In order to provide the required
functionality the basic architecture of the USB testing support is as
follows:
There is a single target-side program
usbtarget. By default when this is run
on a target platform it will appear to do nothing. In fact it is
waiting to be contacted by another program
usbhost which will tell it what test or
tests to run. usbtarget provides
mechanisms for running a wide range of tests.
usbtarget is a generic program, but USB
testing depends to some extent on the functionality provided by the
hardware. For example there is no point in testing bulk transmits
to endpoint 12 if the target hardware does not support an endpoint
12. Therefore each USB device driver should supply information about
what the hardware is actually capable of, in the form of an array of
usbs_testing_endpoint data structures.
There is a single host-side program
usbhost, which acts as a counterpart to
usbtarget. Again
usbhost has no built-in knowledge of
the test or tests that are supposed to run, it only provides
mechanisms for running a wide range of tests. On start-up
usbhost will search the USB bus for
hardware running the target-side program, specifically a USB device
that identifies itself as the product "Red Hat eCos
USB test".
usbhost contains a Tcl interpreter, and
will execute any Tcl scripts specified on the command line
together with appropriate arguments. The Tcl interpreter has been
extended with various commands such as
usbtest::bulktest, so the script can perform
the desired test or tests.
Adding a new test simply involves writing a short Tcl script that
invokes the appropriate USB-specific commands. Running multiple
tests involves passing appropriate arguments to
usbhost, or alternatively writing a
single script that just invokes other scripts.
The current implementation of usbhost
depends heavily on functionality provided by the Linux kernel and in
particular the usbdevfs support. It uses
/proc/bus/usb/devices to find out what devices
are attached to the bus, and will then access the device by opening
/proc/bus/usb/xxx/yyy and performing
ioctl operations. This allows USB testing to take
place without having to write a new host-side device driver, but
getting the code working on host machines not running Linux would
obviously be problematical.
Building and Running the Target-side Code
The target-side component of the USB testing software consists of a
single program usbtarget which contains
support for a range of different tests, under the control of host-side
software. This program is not built by default alongside other eCos
test cases since it will only operate in certain environments,
specifically when the target board's connector is plugged into a Linux
host, and when the appropriate host-side software has been installed
on that host. Instead the user must enable a configuration option
CYGBLD_IO_USB_SLAVE_USBTEST to add the program to
the list of tests for the current configuration.
Starting the usbtarget program does not
require anything unusual, so it can be run in a normal
gdb session just like any eCos application.
After initialization the program will wait for activity from the host.
Depending on the hardware, the Linux host will detect that a new USB
peripheral is present on the bus either when the
usbtarget initialization is complete or
when the cable between target and host is connected. The host will
perform the normal USB enumeration sequence and discover that the
peripheral does not match any known vendor or product id and that
there is no device driver for "Red Hat eCos USB
test", so it will ignore the peripheral. When the
usbhost program is run on the host it will
connect to the target-side software, and testing can now commence.
Building and Running the Host-side Code
In theory the host-side software should be built when the package is
installed in the component repository, and removed when a package
is uninstalled. The current eCos administration tool does not provide
this functionality.
The host-side software should be built via the usual sequence of
"configure/make/make install". It can only be built on a
Linux host and the configure script contains an
explicit test for this. Because the eCos component repository should
generally be treated as a read-only resource the configure script will
also prevent you from trying to build inside the source tree. Instead
a separate build tree is required. Hence a typical sequence for
building the host-side software would be as follows:
$ mkdir usbhost_build
$ cd usbhost_build
$ <repo>packages/io/usb/slave/current/host/configure <args>
$ make
<output from make>
$ su
$ make install
<output from make install>
$
The location of the eCos component repository should be substituted
for <repo>.
If the package has been obtained via CVS or anonymous CVS then the
package version will be current, as per the
example. If instead the package has been obtained as part of a full
eCos release or as a separate .epk file then the
appropriate package version should be used instead of
current.
The configure script takes the usual arguments such
as --prefix= to specify where the executables
and support files should be installed. The only other parameter that
some users may wish to specify is the location of a suitable Tcl
installation. By default usbhost will use
the existing Tcl installation in /usr,
as provided by your Linux distribution. An alternative Tcl
installation can be specified using the parameter
--with-tcl=, or alternatively using some
combination of --with-tcl-include,
--with-tcl-lib and
--with-tcl-version.
One of the host-side executables that gets built,
usbchmod, needs to be installed with suid
root privileges. Although the Linux kernel makes it possible for
applications to perform low-level USB operations such as transmitting
bulk packets, by default access to this functionality is restricted to
programs with superuser privileges. It is undesirable to run a complex
program such as usbhost with such
privileges, especially since the program contains a general-purpose
Tcl interpreter. Therefore when usbhost
starts up and discovers that it does not have sufficient access to the
appropriate entries in /proc/bus/usb,
it spawns an instance of usbchmod to modify
the permissions on these entries. usbchmod
will only do this for a USB device "Red Hat eCos USB
test", so installing this program suid root should not
introduce any security problems.
During make install the following actions will take
place:
usbhost will be installed in /usr/local/bin,
or some other bin directory if
the default location is changed at configure-time using a
--prefix= or similar option. It will be
installed as the executable
usbhost_<version>, for example
usbhost_current, thus allowing several
releases of the USB slave package to co-exist. For convenience a
symbolic link from usbhost to this executable
will be created, so users can just run usbhost to
access the most recently-installed version.
usbchmod will be installed in
/usr/local/libexec/ecos/io_usb_slave_<version>.
This program should only be run by usbhost,
not invoked directly, so it is not placed in the bin
directory. Again the presence of the package version in the directory
name allows multiple releases of the package to co-exist.
A Tcl script usbhost.tcl will get installed in
the same directory as usbchmod. This Tcl
script is loaded automatically by the
usbhost executable.
A number of additional Tcl scripts, for example
list.tcl will get installed alongside
usbhost.tcl. These correspond to various test
cases provided as standard. If a given test case is specified on the
command line and cannot be found relative to the current directory
then usbhost will search the install
directory for these test cases.
Strictly speaking installing the usbhost.tcl and
other Tcl scripts below the libexec
directory deviates from standard practice: they are
architecture-independent data files so should be installed below
the share subdirectory. In
practice the files are sufficiently small that there is no point in
sharing them, and keeping them below libexec
simplifies the host-side software somewhat.
The usbhost should be run only when there is a
suitable target attached to the USB bus and running the
usbtarget program. It will search
/proc/bus/usb/devices for an entry corresponding
to this program, invoke usbchmod if
necessary to change the access rights, and then interact with
usbtarget over the USB bus.
usbhost should be invoked as follows:
$ usbhost [-v|--version] [-h|--help] [-V|--verbose] <test> [<test parameters>]
The -v or --version
option will display version information for
usbhost including the version of the USB
slave package that was used to build the executable.
The -h or --help option
will display usage information.
The -V or --verbose
option can be used to obtain more information at run-time, for example
some output for every USB transfer. This option can be repeated
multiple times to increase the amount of output.
The first argument that does not begin with a hyphen specifies a test
that should be run, in the form of a Tcl script. For example an
argument of list.tcl will cause
usbhost to look for a script with that
name, adding a .tcl suffix if necessarary, and
run that script. usbhost will look in the
current directory first, then in the install tree for standard test
scripts provided by the USB slave package.
Some test scripts may want their own parameters, for example a
duration in seconds. These can be passed on the command line after
the name of the test, for example
usbhost mytest 60.
Writing a Test
Each test is defined by a Tcl script, running inside an interpreter
provided by usbhost. In addition to the
normal Tcl functionality this interpreter provides a number of
variables and functions related to USB testing. For example there is a
variable bulk_in_endpoints that lists all the
endpoints on the target that can perform bulk IN operations, and a
related array bulk_in which contains information
such as the minimum and maximum packets sizes. There is a function
bulktest which can be used to perform bulk tests
on a particular endpoint. A simple test script aimed at specific
hardware could ignore the information variables since it would know
exactly what USB hardware is available on the target, whereas a
general-purpose script would use the information to adapt to the
hardware capabilities.
To avoid namespace pollution all USB-related Tcl variables and
functions live in the usbtest:: namespace.
Therefore accessing requires either explicitly including the
namespace any references, for example
$usbtest::bulk_in_endpoints, or by using Tcl's
namespace import facility.
A very simple test script might look like this:
usbtest::bulktest 1 out 4000
usbtest::bulktest 2 in 4000
if { [usbtest::start 60] } {
puts "Test successful"
} else
puts "Test failed"
foreach result $usbtest::results {
puts $result
}
}
This would perform a test run involving 4000 bulk transfers from the
host to the target's endpoint 1, and concurrently 4000 bulk transfers
from endpoint 2. Default settings for packet sizes, contents, and
delays would be used. The actual test would not start running until
usbtest is invoked, and it is expected that the
test would complete within 60 seconds. If any failures occur then they
are reported.
Available Hardware
Each target-side USB device driver provides information about the
actual capabilities of the hardware, for example which endpoints are
available. Strictly speaking it provides information about what is
actually supported by the device driver, which may be a subset of what
the hardware is capable of. For example, the hardware may support
isochronous transfers on a particular endpoint but if there is no
software support for this in the driver then this endpoint will not be
listed. When usbhost first contacts the
usbtarget program running on the target
platform, it obtains this information and makes it available to test
scripts via Tcl variables:
bulk_in_endpoints
This is a simple list of the endpoints which can support bulk IN
transfers. For example if the target-side hardware supports
these transfers on endpoints 3 and 5 then the value would be
"3 5" Typical test scripts would
iterate over the list using something like:
if { 0 != [llength $usbtest::bulk_in_endpoints] } {
puts"Bulk IN endpoints: $usbtest::bulk_in_endpoints"
foreach endpoint $usbtest:bulk_in_endpoints {
…
}
}
bulk_in()
This array holds additional information about each bulk IN endpoint.
The array is indexed by two fields, the endpoint number and one of
min_size, max_size,
max_in_padding and devtab:
min_size
This field specifies a lower bound on the size of bulk transfers,
and will typically will have a value of 1.
The typical minimum transfer size of a single byte is not strictly
speaking correct, since under some circumstances it can make sense
to have a transfer size of zero bytes. However current target-side
device drivers interpret a request to transfer zero bytes as a way
for higher-level code to determine whether or not an endpoint is
stalled, so it is not actually possible to perform zero-byte
transfers. This issue will be addressed at some future point.
max_size
This field specifies an upper bound on the size of bulk transfers.
Some target-side drivers may be limited to transfers of say
0x0FFFF bytes because of hardware limitations. In practice the
transfer size is likely to be limited primarily to limit memory
consumption of the test code on the target hardware, and to ensure
that tests complete reasonably quickly. At the time of writing
transfers are limited to 4K.
max_in_padding
On some hardware it may be necessary for the target-side device
driver to send more data than is actually intended. For example
the SA11x0 USB hardware cannot perform bulk transfers that are
an exact multiple of 64 bytes, instead it must pad such
transfers with an extra byte and the host must be ready to
accept and discard this byte. The
max_in_padding field indicates the amount of
padding that is required. The low-level code inside
usbhost will use this field
automatically, and there is no need for test scripts to adjust
packet sizes for padding. The field is provided for
informational purposes only.
devtab
This is a string indicating whether or not the
target-side USB device driver supports access to this endpoint
via entries in the device table, in other words through
conventional calls like open and
write. Some device drivers may only
support low-level USB access because typically that is what gets
used by USB class-specific packages such as USB-ethernet.
An empty string indicates that no devtab entry is available,
otherwise it will be something like
"/dev/usbs2w".
Typical test scripts would access this data using something like:
foreach endpoint $usbtest:bulk_in_endpoints {
puts "Endpoint $endpoint: "
puts " minimum transfer size $usbtest::bulk_in($endpoint,min_size)"
puts " maximum transfer size $usbtest::bulk_in($endpoint,max_size)"
if { 0 == $usbtest::bulk_in($endpoint,max_in_padding) } {
puts " no IN padding required"
} else {
puts " $usbtest::bulk_in($endpoint,max_in_padding) bytes of IN padding required"
}
if { "" == $usbtest::bulk_in($endpoint,devtab) } {
puts " no devtab entry provided"
} else {
puts " corresponding devtab entry is $usbtest::bulk_in($endpoint,devtab)"
}
}
bulk_out_endpoint
This is a simple list of the endpoints which can support bulk OUT
transfers. It is analogous to
bulk_in_endpoints.
bulk_out()
This array holds additional information about each bulk OUT
endpoint. It can be accessed in the same way as
bulk_in(), except that there is no
max_in_padding field because that field only
makes sense for IN transfers.
control()
This array holds information about the control endpoint. It contains
two fields, min_size and
max_size. Note that there is no variable
control_endpoints because a USB target always
supports a single control endpoint 0. Similarly
the control array does not use an endpoint number
as the first index because that would be redundant.
isochronous_in_endpoints and
isochronous_in()
These variables provide the same information as
bulk_in_endpoints and bulk_in,
but for endpoints that support isochronous IN transfers.
isochronous_out_endpoints and
isochronous_out()
These variables provide the same information as
bulk_out_endpoints and bulk_out,
but for endpoints that support isochronous OUT transfers.
interrupt_in_endpoints and
interrupt_in()
These variables provide the same information as
bulk_in_endpoints and bulk_in,
but for endpoints that support interrupt IN transfers.
interrupt_out_endpoints and
interrupt_out()
These variables provide the same information as
bulk_out_endpoints and bulk_out,
but for endpoints that support interrupt OUT transfers.
Testing Bulk Transfers
The main function for initiating a bulk test is
usbtest::bulktest. This takes three compulsory
arguments, and can be given a number of additional arguments to
control the exact behaviour. The compulsory arguments are:
endpoint
This specifies the endpoint to use. It should correspond to
one of the entries in
usbtest::bulk_in_endpoints or
usbtest::bulk_out_endpoints, depending on the
transfer direction.
direction
This should be either in or out.
number of transfers
This specifies the number of transfers that should take place. The
testing software does not currently support the concept of performing
transfers for a given period of time because synchronising this on
both the host and a wide range of targets is difficult. However it
is relatively easy to work out the approximate time a number of bulk
transfers should take place, based on a typical bandwidth of
1MB/second and assuming say a 1ms overhead per transfer.
Alternatively a test script could perform a small initial run to
determine what performance can actually be expected from a given
target, and then use this information to run a much longer test.
Additional arguments can be used to control the exact transfer. For
example a txdelay+ argument can be used to
slowly increase the delay between transfers. All such arguments involve
a value which can be passed either as part of the argument itself,
for example txdelay+=5, or as a subsequent
argument, txdelay+ 5. The possible arguments fall
into a number of categories: data, I/O mechanism, transmit size,
receive size, transmit delay, and receive delay.
Data
An obvious parameter to control is the actual data that gets sent.
This can be controlled by the argument data
which can take one of five values: none,
bytefill, intfill,
byteseq and wordseq. The default
value is none.
none
The transmit code will not attempt to fill the buffer in any way,
and the receive code will not check it. The actual data that gets
transferred will be whatever happened to be in the buffer before
the transfer started.
bytefill
The entire buffer will be filled with a single byte, as per
memset.
intfill
The buffer will be treated as an array of 32-bit integers, and will
be filled with the same integer repeated the appropriate number of
times. If the buffer size is not a multiple of four bytes then
the last few bytes will be set to 0.
byteseq
The buffer will be filled with a sequence of bytes, generated by
a linear congruential generator. If the first byte in the buffer is
filled with the value x, the next byte will be
(m*x)+i. For example a sequence of slowly
incrementing bytes can be achieved by setting both the multiplier
and the increment to 1. Alternatively a pseudo-random number
sequence can be achieved using values 1103515245 and 12345, as
per the standard C library rand function.
For convenience these two constants are available as Tcl
variables usbtest::MULTIPLIER and
usbtest::INCREMENT.
wordseq
This acts like byteseq, except that the buffer is
treated as an array of 32-bit integers rather than as an array of
bytes. If the buffer is not a multiple of four bytes then the last
few bytes will be filled with zeroes.
The above requires three additional parameters
data1, data* and
data+. data1 specifies
the value to be used for byte or word fills, or the first number when
calculating a sequence. The default value is 0.
data* and data+ specify
the multiplier and increment for a sequence, and have default values
of 1 and 0 respectively. For
example, to perform a bulk transfer of a pseudo-random sequence of
integers starting with 42 the following code could be used:
bulktest 2 IN 1000 data=wordseq data1=42 \
data* $usbtest::MULTIPLIER data+ $usbtest::INCREMENT
The above parameters define what data gets transferred for the first
transfer, but a test can involve multiple transfers. The data format
will be the same for all transfers, but it is possible to adjust the
current value, the multiplier, and the increment between each
transfer. This is achieved with parameters data1*,
data1+, data**,
data*+, data+*, and
data++, with default values of 1 for each
multiplier and 0 for each increment. For example, if the multiplier
for the first transfer is set to 2 using
data*, and arguments
data** 2 and data*+ -1 are also
supplied, then the multiplier for subsequent transfers will be
3, 5, 9,
….
Currently it is not possible for a test script to send specific data,
for example a specific sequence of bytes captured by a protocol analyser
that caused a problem. If the transfer was from host to target then
the target would have to know the exact sequence of bytes to expect,
which means transferring data over the USB bus when that data is known
to have caused problems in the past. Similarly for target to host
transfers the target would have to know what bytes to send. A possible
future extension of the USB testing support would allow for bounce
operations, where a given message is first sent to the target and then
sent back to the host, with only the host checking that the data was
returned correctly.
I/O Mechanism
On the target side USB transfers can happen using either low-level
USB calls such as usbs_start_rx_buffer, or by
higher-level calls which go through the device table. By default the
target-side code will use the low-level calls. If it is desired to
test the higher-level calls instead, for example because those are
what the application uses, then that can be achieved with an
argument mechanism=devtab.
Transmit Size
The next set of arguments can be used to control the size of the
transmitted buffer: txsize1,
txsize>=, txsize<=txsize*, txsize/,
and txsize+.
txsize1 determines the size of the first
transfer, and has a default value of 32 bytes. The size of the next
transfer is calculated by first multiplying by the
txsize* value, then dividing by the
txsize/ value, and finally adding the
txsize+ value. The defaults for these are
1, 1, and 0
respectively, which means that the transfer size will remain
unchanged. If for example the transfer size should increase by
approximately 50 per cent each time then suitable values might be
txsize* 3, txsize/ 2,
and txsize+ 1.
The txsize>= and
txsize<= arguments can be used to impose
lower and upper bounds on the transfer. By default the
min_size and max_size values
appropriate for the endpoint will be used. If at any time the
current size falls outside the bounds then it will be normalized.
Receive Size
The receive size, in other words the number of bytes that either host
or target will expect to receive as opposed to the number of bytes
that actually get sent, can be adjusted using a similar set of
arguments: rxsize1,
rxsize>=,
rxsize<=,
rxsize*, rxsize/ and
rxsize+. The current receive size will be
adjusted between transfers just like the transmit size. However when
communicating over USB it is not a good idea to attempt to receive
less data than will actually be sent: typically neither the hardware
nor the software will be able to do anything useful with the excess,
so there will be problems. Therefore if at any time the calculated
receive size is less than the transmit size, the actual receive will
be for the exact number of bytes that will get transmitted. However
this will not affect the calculations for the next receive size.
The default values for rxsize1,
rxsize*, rxsize/ and
rxsize+ are 0,
1, 1 and 0
respectively. This means that the calculated receive size will always
be less than the transmit size, so the receive operation will be for
the exact number of bytes transmitted. For some USB protocols this
would not accurately reflect the traffic that will happen. For example
with USB-ethernet transfer sizes will vary between 16 and 1516 bytes,
so the receiver will always expect up to 1516 bytes. This can be
achieved using rxsize1 1516, leaving the
other parameters at their default values.
For target hardware which involves non-zero
max_in_padding, on the host side the padding will
be added automatically to the receive size if necessary.
Transmit and Receive Delays
Typically during the testing there will be some minor delays between
transfers on both host and target. Some of these delays will be caused
by timeslicing, for example another process running on the host, or a
concurrent test thread running inside the target. Other delays will be
caused by the USB bus itself, for example activity from another device
on the bus. However it is desirable that test cases be allowed to
inject additional and somewhat more controlled delays into the system,
for example to make sure that the target behaves correctly even if the
target is not yet ready to receive data from the host.
The transmit delay is controlled by six parameters:
txdelay1, txdelay*,
txdelay/, txdelay+,
txdelay>= and
txdelay<=. The default values for these are
0, 1, 1,
0, 0 and
1000000000 respectively, so that by default
transmits will happen as quickly as possible. Delays are measured in
nanoseconds, so a value of 1000000 would correspond
to a delay of 0.001 seconds or one millisecond. By default delays have
an upper bound of one second. Between transfers the transmit delay is
updated in much the same was as the transfer sizes.
The receive delay is controlled by a similar set of six parameters:
rxdelay1, rxdelay*,
rxdelay/, rxdelay+,
rxdelay>= and
rxdelay<=. The default values for these are
the same as for transmit delays.
The transmit delay is used on the side which sends data over the USB
bus, so for a bulk IN transfer it is the target that sends data and
hence sleeps for the specified transmit delay, while the host receives
data sleeps for the receive delay. For an OUT transfer the positions
are reversed.
It should be noted that although the delays are measured in
nanoseconds, the actual delays will be much less precise and are
likely to be of the order of milliseconds. The exact details will
depend on the kernel clock speed.
Other Types of Transfer
Support for testing other types of USB traffic such as isochronous
transfers is not yet implemented.
Starting a Test and Collecting Results
A USB test script should prepare one or more transfers using
appropriate functions such as usbtest::bulktest.
Once all the individual tests have been prepared they can be started
by a call to usbtest::start. This takes a single
argument, a maximum duration measured in seconds. If all transfers
have not been completed in the specified time then any remaining
transfers will be aborted.
usbtest::start will return 1
if all the tests have succeeded, or 0 if any of
them have failed. More detailed reports will be stored in the
Tcl variable usbtests::results, which will be a
list of string messages.
Existing Test Scripts
A number of test scripts are provided as standard. These are located
in the host subdirectory of the
common USB slave package, and will be installed as part of the process
of building the host-side software. When a script is specified on the
command line usbhost will first search for
it in the current directory, then in the install tree. Standard
test scripts include the following:
list.tcl
This script simply displays information about the capabilities
of the target platform, as provided by the target-side USB
device driver. It can help with tracking down problems, but its
primary purpose is to let users check that everything is working
correctly: if running usbhost list.tcl
outputs sensible information then the user knows that the
target side is running correctly and that communication between
host and target is possible.
verbose.tcl
The target-side code can provide information about what
is happening while tests are prepared and run. This facility
should not normally be used since the extra I/O involved will
significantly affect the behaviour of the system, but in some
circumstances it may prove useful. Since an eCos application
cannot easily be given command-line arguments the target-side
verbosity level cannot be controlled using
-V or --verbose
options. Instead it can be controlled from inside
gdb by changing the integer
variable verbose. Alternatively it can
be manipulated by running the test script
verbose.tcl. This script takes a single
argument, the desired verbosity level, which should be a small
integer. For example, to disable target-side run-time logging
the command usbhost verbose 0 can
be used.
bulk-boundaries.tcl This script performs simple bulk IN and OUT
transfers of different sizes around interesting boundaries. This
test is useful to ensure the driver correctly handles the case
where a transfer is just smaller than, the same size as, and
just bigger than the hardware buffer in the endpoint hardware.
This script takes no parameters. It determines what endpoints
the device has by asking it.
Possible Problems
If all transfers succeed within the specified time then both host and
target remain in synch and further tests can be run without problem.
However, if at any time a failure occurs then things get more
complicated. For example, if the current test involves a series of
bulk OUT transfers and the target detects that for one of these
transfers it received less data than was expected then the test has
failed, and the target will stop accepting data on this endpoint.
However the host-side software may not have detected anything wrong
and is now blocked trying to send the next lot of data.
The test code goes to considerable effort to recover from problems
such as these. On the host-side separate threads are used for
concurrent transfers, and on the target-side appropriate asynchronous
I/O mechanisms are used. In addition there is a control thread on the
host that checks the state of all the main host-side threads, and the
state of the target using private control messages. If it discovers
that one side has stopped sending or receiving data because of an
error and the other side is blocked as a result, it will set certain
flags and then cause one additional transfer to take place. That
additional transfer will have the effect of unblocking the other side,
which then discovers that an error has occurred by checking the
appropriate flags. In this way both host and target should end up back
in synch, and it is possible to move on to the next set of tests.
However, the above assumes that the testing has not triggered any
serious hardware conditions. If instead the target-side hardware has
been left in some strange state so that, for example, it will no
longer raise an interrupt for traffic on a particular endpoint then
recovery is not currently possible, and the testing software will just
hang.
A possible future enhancement to the testing software would allow the
host-side to raise a USB reset signal whenever a failure occurs, in
the hope that this would clear any remaining problems within the
target-side USB hardware.