eCos USB Slave Support Introduction Introduction eCos support for USB slave devices Introduction 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 Data Enumeration Data The 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. <structname>usb_device_descriptor</structname> 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. <structname>usb_configuration_descriptor</structname> 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. <structname>usb_interface_descriptor</structname> 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. <structname>usb_endpoint_descriptor</structname> 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. <structname>usbs_enumeration_data</structname> 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 Device usbs_start Starting up a USB Device #include <cyg/io/usb/usbs.h> void usbs_start usbs_control_endpoint* ep0 Description 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 Entries Devtab Entries Data 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. <function>write</function> operations cyg_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. <function>read</function> operations cyg_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. <function>select</function> 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. <function>get_config</function> and <function>set_config</function> 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 Host usbs_start_rx_buffer Receiving Data from the Host #include <cyg/io/usb/usbs.h> void usbs_start_rx_buffer usbs_rx_endpoint* ep unsigned char* buffer int length void (*)(void*,int) complete_fn void * complete_data void usbs_start_rx usbs_rx_endpoint* ep <function>Description</function> usbs_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 Host usbs_start_tx_buffer Sending Data to the Host #include <cyg/io/usb/usbs.h> void usbs_start_tx_buffer usbs_tx_endpoint* ep const unsigned char* buffer int length void (*)(void*,int) complete_fn void * complete_data void usbs_start_tx usbs_tx_endpoint* ep <function>Description</function> usbs_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 Endpoints Halted Endpoints Support for Halting and Halted Endpoints #include <cyg/io/usb/usbs.h> cyg_bool usbs_rx_endpoint_halted usbs_rx_endpoint* ep void usbs_set_rx_endpoint_halted usbs_rx_endpoint* ep cyg_bool new_state void usbs_start_rx_endpoint_wait usbs_rx_endpoint* ep void (*)(void*, int) complete_fn void * complete_data cyg_bool usbs_tx_endpoint_halted usbs_tx_endpoint* ep void usbs_set_tx_endpoint_halted usbs_tx_endpoint* ep cyg_bool new_state void usbs_start_tx_endpoint_wait usbs_tx_endpoint* ep void (*)(void*, int) complete_fn void * complete_data <function>Description</function> 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 Endpoints Control Endpoints Control endpoint data structure #include <cyg/io/usb/usbs.h> typedef struct usbs_control_endpoint { *hellip; } usbs_control_endpoint; <literal>usbs_control_endpoint</literal> 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 Endpoints Data Endpoints Data 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 Driver Writing a USB Device Driver USB Device Driver Porting Guide Introduction 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. Testing Testing Testing of USB Device Drivers Introduction 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.