Subversion Repositories openrisc
Compare Revisions
- This comparison shows the changes necessary to convert path
/openrisc/trunk/rtos/ecos-2.0/packages/io/usb
- from Rev 27 to Rev 174
- ↔ Reverse comparison
Rev 27 → Rev 174
# ==================================================================== |
# |
# usbs_eth.cdl |
# |
# USB slave-side ethernet package. |
# |
# ==================================================================== |
#####ECOSGPLCOPYRIGHTBEGIN#### |
## ------------------------------------------- |
## This file is part of eCos, the Embedded Configurable Operating System. |
## Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
## |
## eCos is free software; you can redistribute it and/or modify it under |
## the terms of the GNU General Public License as published by the Free |
## Software Foundation; either version 2 or (at your option) any later version. |
## |
## eCos is distributed in the hope that it will be useful, but WITHOUT ANY |
## WARRANTY; without even the implied warranty of MERCHANTABILITY or |
## FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
## for more details. |
## |
## You should have received a copy of the GNU General Public License along |
## with eCos; if not, write to the Free Software Foundation, Inc., |
## 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
## |
## As a special exception, if other files instantiate templates or use macros |
## or inline functions from this file, or you compile this file and link it |
## with other works to produce a work based on this file, this file does not |
## by itself cause the resulting work to be covered by the GNU General Public |
## License. However the source code for this file must still be made available |
## in accordance with section (3) of the GNU General Public License. |
## |
## This exception does not invalidate any other reasons why a work based on |
## this file might be covered by the GNU General Public License. |
## |
## Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
## at http://sources.redhat.com/ecos/ecos-license/ |
## ------------------------------------------- |
#####ECOSGPLCOPYRIGHTEND#### |
# ==================================================================== |
######DESCRIPTIONBEGIN#### |
# |
# Author(s): bartv |
# Original data: bartv |
# Contributors: |
# Date: 2000-10-04 |
# |
#####DESCRIPTIONEND#### |
# ==================================================================== |
|
cdl_package CYGPKG_IO_USB_SLAVE_ETH { |
display "USB slave ethernet support" |
include_dir "cyg/io/usb" |
parent CYGPKG_IO_USB_SLAVE |
requires { CYGHWR_IO_USB_SLAVE_OUT_ENDPOINTS >= 1 } |
requires { CYGHWR_IO_USB_SLAVE_IN_ENDPOINTS >= 1 } |
compile usbseth.c |
implements CYGINT_IO_USB_SLAVE_CLIENTS |
doc ref/io-usb-slave-eth.html |
|
description " |
The USB slave ethernet package supports the development |
of USB peripherals which provide an ethernet service to |
the host machine. Such a peripheral could be a simple |
USB-ethernet converter, or it could be rather more |
complicated internally." |
|
cdl_component CYGPKG_USBS_ETHDRV { |
display "Provide a driver for a TCP/IP stack." |
requires CYGPKG_IO_ETH_DRIVERS |
implements CYGHWR_NET_DRIVERS |
default_value CYGPKG_NET |
compile -library=libextras.a usbsethdrv.c |
|
description " |
The primary purpose of USB slave ethernet support is to provide |
an ethernet service to the USB host. This is very different |
from a conventional network driver which provides a service |
to a TCP/IP stack running inside the peripheral. If this |
component is enabled then the USB-ethernet code will implement |
an eCos network driver, thus supporting both a host-side TCP/IP |
stack and an eCos stack. This raises issues such as enabling |
the bridge code in the stack, and the package documentation |
should be consulted for further information." |
|
cdl_option CYGFUN_USBS_ETHDRV_STATISTICS { |
display "Maintain traffic statistics" |
flavor bool |
default_value CYGPKG_SNMPAGENT |
description " |
The USB network device driver can maintain some statistics |
about traffic, for example the number of incoming and |
outgoing packets. These statistics are intended mainly |
for SNMP agent software." |
} |
|
cdl_option CYGDAT_USBS_ETHDRV_NAME { |
display "Name to use for this network device" |
flavor data |
default_value { (1 == CYGHWR_NET_DRIVERS) ? "\"eth0\"" : "\"eth1\"" } |
description " |
The name of this network device for control purposes. |
" |
} |
|
cdl_option CYGPRI_USBS_ETHDRV_ETH0 { |
display "Enable/disable generic eth0 configury" |
flavor bool |
calculated { "\"eth0\"" == CYGDAT_USBS_ETHDRV_NAME } |
implements CYGHWR_NET_DRIVER_ETH0 |
requires !CYGHWR_NET_DRIVER_ETH0_BOOTP |
} |
|
cdl_option CYGPRI_USBS_ETHDRV_ETH1 { |
display "Enable/disable generic eth1 configury" |
flavor bool |
calculated { "\"eth1\"" == CYGDAT_USBS_ETHDRV_NAME } |
implements CYGHWR_NET_DRIVER_ETH1 |
requires !CYGHWR_NET_DRIVER_ETH1_BOOTP |
} |
} |
} |
#ifndef CYGONCE_USBS_ETH_H |
#define CYGONCE_USBS_ETH_H_ |
//========================================================================== |
// |
// include/usbs_eth.h |
// |
// Description of the USB slave-side ethernet support |
// |
//========================================================================== |
//####ECOSGPLCOPYRIGHTBEGIN#### |
// ------------------------------------------- |
// This file is part of eCos, the Embedded Configurable Operating System. |
// Copyright (C) 1998, 1999, 2000, 2001, 2002 Red Hat, Inc. |
// |
// eCos is free software; you can redistribute it and/or modify it under |
// the terms of the GNU General Public License as published by the Free |
// Software Foundation; either version 2 or (at your option) any later version. |
// |
// eCos is distributed in the hope that it will be useful, but WITHOUT ANY |
// WARRANTY; without even the implied warranty of MERCHANTABILITY or |
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
// for more details. |
// |
// You should have received a copy of the GNU General Public License along |
// with eCos; if not, write to the Free Software Foundation, Inc., |
// 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. |
// |
// As a special exception, if other files instantiate templates or use macros |
// or inline functions from this file, or you compile this file and link it |
// with other works to produce a work based on this file, this file does not |
// by itself cause the resulting work to be covered by the GNU General Public |
// License. However the source code for this file must still be made available |
// in accordance with section (3) of the GNU General Public License. |
// |
// This exception does not invalidate any other reasons why a work based on |
// this file might be covered by the GNU General Public License. |
// |
// Alternative licenses for eCos may be arranged by contacting Red Hat, Inc. |
// at http://sources.redhat.com/ecos/ecos-license/ |
// ------------------------------------------- |
//####ECOSGPLCOPYRIGHTEND#### |
//========================================================================== |
//#####DESCRIPTIONBEGIN#### |
// |
// Author(s): bartv |
// Contributors: bartv |
// Date: 2000-10-04 |
// Purpose: |
// Description: USB slave-side ethernet support |
// |
// |
//####DESCRIPTIONEND#### |
//========================================================================== |
|
#ifdef __cplusplus |
extern "C" { |
#endif |
|
// |
// The primary purpose of the USB slave-side ethernet code is to |
// provide an ethernet service for the host. Essentially this means |
// the following: |
// |
// 1) the host can transmit an ethernet frame to the USB peripheral. |
// This frame is received by the code in this package and then |
// passed up to higher-level code for processing. Typically the |
// frame will originate from a TCP/IP stack running inside the |
// host, and the higher-level code will forward the frame via a |
// real ethernet chip or some other ethernet-style device. |
// |
// 2) higher-level code will provide ethernet frames to be sent to |
// the host, usually to a TCP/IP stack running on the host. The |
// exact source of the ethernet frame is not known. |
// |
// 3) the host may initiate a number of control operations, for |
// example it may request the MAC address or it may want to |
// control the filtering mode (e.g. enable promiscuous mode). |
// |
// 4) there are USB control-related operations, for example actions |
// to be taken when the peripheral is disconnected from the |
// bus or when the host wants to disable the ethernet interface. |
// |
// It is possible to develop a USB ethernet peripheral that does not |
// involve a TCP/IP stack inside the peripheral, in fact that is the |
// most common implementation. Instead a typical peripheral would |
// involve a USB port, an ethernet port, and a cheap microcontroller |
// just powerful enough to forward packets between the two. The eCos |
// USB code can be used in this way, and the primary external |
// interface provides enough functionality for this to work. |
// |
// +---------------+ ethernet |
// +----+ | | | |
// | | USB | app | | |
// |host|---------| / \ |-----o |
// | | | / \ | | |
// +----+ | USB-eth eth | | |
// +---------------+ | |
// USB peripheral |
// |
// Note that the USB-ethernet code does not know anything about the |
// real ethernet device or what the application gets up to, it just |
// provides an interface to the app. The above represents just one |
// possible use for a USB-ethernet device. |
// |
// Also worth mentioning: when the host TCP/IP stack requests the MAC |
// address USB-eth would normally respond with the MAC address for the |
// real ethernet device. That way things like host-side DHCP should |
// just work. |
// |
// Alternatively for some applications it is desirable to run a TCP/IP |
// stack inside the peripheral as well as on the host. This makes |
// things a fair bit more complicated, something like this. |
// |
// +---------------+ |
// | app | |
// | | | ethernet |
// +----+ | | | | |
// | | USB | TCP/IP | | |
// |host|---------| / \ |-----o |
// | | | / \ | | |
// +----+ | USB-eth eth | | |
// +---------------+ | |
// USB peripheral |
// |
// |
// Usually this will involve enabling the bridge code in the TCP/IP |
// stack, or possibly performing some sort of bridging below the |
// TCP/IP stack. One way of getting things to work is to view the |
// USB connection as a small ethernet segment with just two |
// attached machines, the host and the peripheral. The two will |
// need separate MAC addresses, in addition to the MAC address |
// for the real ethernet device. This way the bridge code |
// sees things the way it expects. |
// |
// There will still be some subtle differences between a setup like |
// this and a conventional ethernet bridge, mainly because there |
// is a host-side TCP/IP stack which can perform control operations. |
// For example the host stack may request that USB-eth go into |
// promiscuous mode. A conventional ethernet bridge just deals |
// with ethernet segments and does not need to worry about |
// control requests coming in from one of the segments. |
// |
// It is not absolutely essential that there is another network. |
// However without another network this setup would look to the host |
// like an ethernet segment with just two machines attached to it, the |
// host itself and the USB peripheral, yet it still involves all the |
// complexities of ethernet such as broadcast masks and IP subnets. |
// Anything along these lines is likely to prove somewhat confusing, |
// and the USB peripheral should probably act like some other class |
// of USB device instead. |
// |
// One special setup has the host acting as a bridge to another |
// network, rather than the peripheral. This might make sense for |
// mobile peripherals such as PDA's, as a way of connecting the |
// peripheral to an existing LAN without needing a LAN adapter. |
// Enabling bridging in the host may be a complex operation, limiting |
// the applicability of such a setup. |
// |
// This package will only implement the eCos network driver interface |
// if explicitly enabled. The package-specific interface is always |
// provided, although trying to mix and match the two may lead to |
// terrible confusion: once the network driver is active nothing else |
// should use the lower-level USB ethernet code. However application |
// code is responsible for initializing the package, and specifically |
// for providing details of the USB endpoints that should be used. |
// |
// The package assumes that it needs to provide just one |
// instantiation. Conceivably there may be applications where it makes |
// sense for a USB peripheral to supply two separate ethernet devices |
// to the host, but that would be an unusual setup. Also a peripheral |
// might provide two or more USB slave ports to allow multiple hosts |
// to be connected, with a separate USB-ethernet instantiation for |
// each port, but again that would be an unusual setup. Applications |
// which do require more than one instantiation are responsible |
// for doing this inside the application code. |
|
// The public interface depends on configuration options. |
#include <pkgconf/io_usb_slave_eth.h> |
|
// Define the interface in terms of eCos data types. |
#include <cyg/infra/cyg_type.h> |
|
// The generic USB support |
#include <cyg/io/usb/usbs.h> |
|
// Network driver definition, to support cloning of usbs_eth_netdev0 |
#ifdef CYGPKG_USBS_ETHDRV |
# include <cyg/io/eth/netdev.h> |
#endif |
|
// Cache details, to allow alignment to cache line boundaries etc. |
#include <cyg/hal/hal_cache.h> |
|
// ---------------------------------------------------------------------------- |
// Maximum transfer size. This is not specified by io/eth. It can be |
// determined from <netinet/if_ether.h> but the TCP/IP stack may not |
// be loaded so that header file cannot be used. |
// |
// Some (most?) USB implementations have implementation problems. For |
// example the SA11x0 family cannot support transfers that are exact |
// multiples of the 64-byte USB bulk packet size, instead it is |
// necessary to add explicit size information. This can be encoded |
// conveniently at the start of the buffer. |
// |
// So the actual MTU consists of: |
// 1) a 1500 byte payload |
// 2) the usual ethernet header with a six-byte source MAC |
// address, a six-byte destination MAC address, and a |
// two-byte protocol or length field, for a total header |
// size of 14 bytes. |
// 3) an extra two bytes of size info. |
// |
// For a total of 1516 bytes. |
#define CYGNUM_USBS_ETH_MAX_FRAME_SIZE 1514 |
#define CYGNUM_USBS_ETH_MAXTU (CYGNUM_USBS_ETH_MAX_FRAME_SIZE + 2) |
|
// Although the minimum ethernet frame size is 60 bytes, this includes |
// padding which is not needed when transferring over USB. Hence the |
// actual minimum is just the 14 byte ethernet header plus two bytes |
// for the length. |
#define CYGNUM_USBS_ETH_MIN_FRAME_SIZE 14 |
#define CYGNUM_USBS_ETH_MINTU (CYGNUM_USBS_ETH_MIN_FRAME_SIZE + 2) |
|
// Typical USB devices involve DMA operations and hence confusion |
// between cached and uncached memory. To make life easier for |
// the underlying USB device drivers, this package ensures that |
// receive operations always involve buffers that are aligned to |
// a cache-line boundary and that are a multiple of the cacheline |
// size. |
#ifndef HAL_DCACHE_LINE_SIZE |
# define CYGNUM_USBS_ETH_RXBUFSIZE CYGNUM_USBS_ETH_MAXTU |
# define CYGNUM_USBS_ETH_RXSIZE CYGNUM_USBS_ETH_MAXTU |
#else |
# define CYGNUM_USBS_ETH_RXBUFSIZE ((CYGNUM_USBS_ETH_MAXTU + HAL_DCACHE_LINE_SIZE + HAL_DCACHE_LINE_SIZE - 1) \ |
& ~(HAL_DCACHE_LINE_SIZE - 1)) |
# define CYGNUM_USBS_ETH_RXSIZE ((CYGNUM_USBS_ETH_MAXTU + HAL_DCACHE_LINE_SIZE - 1) & ~(HAL_DCACHE_LINE_SIZE - 1)) |
#endif |
|
// ---------------------------------------------------------------------------- |
// This data structure serves two purposes. First, it keeps track of |
// the information needed by the low-level USB ethernet code, for |
// example which endpoints should be used for incoming and outgoing |
// packets. Second, if the support for the TCP/IP stack is enabled |
// then there are additional fields to support that (e.g. for keeping |
// track of statistics). |
// |
// Arguably the two uses should be separated into distinct data |
// structures. That would make it possible to instantiate multiple |
// low-level USB-ethernet devices but only have a network driver for |
// one of them. Achieving that flexibility would require some extra |
// indirection, affecting performance and code-size, and it is not |
// clear that that flexibility would ever prove useful. For now having |
// a single data structure seems more appropriate. |
|
typedef struct usbs_eth { |
|
// What endpoints should be used for communication? |
usbs_control_endpoint* control_endpoint; |
usbs_rx_endpoint* rx_endpoint; |
usbs_tx_endpoint* tx_endpoint; |
|
// Is the host ready to receive packets? This state is determined |
// largely by control packets sent from the host. It can change at |
// DSR level. |
volatile cyg_bool host_up; |
|
// Has the host-side set promiscuous mode? This is relevant to the |
// network driver which may need to do filtering based on the MAC |
// address and host-side promiscuity. |
volatile cyg_bool host_promiscuous; |
|
// The host MAC address. This is the address supplied to the |
// host's TCP/IP stack and filled in by the init function. There |
// is no real hardware to extract the address from. |
unsigned char host_MAC[6]; |
|
// Needed for callback operations. |
void (*tx_callback_fn)(struct usbs_eth*, void*, int); |
void* tx_callback_arg; |
|
void (*rx_callback_fn)(struct usbs_eth*, void*, int); |
void* rx_callback_arg; |
|
// RX operations just block if the host is not connected, resuming |
// when a connection is established. This means saving the buffer |
// pointer so that when the host comes back up the rx operation |
// proper can start. This is not quite consistent because if the |
// connection breaks while an RX is in progress there will be a |
// callback with an error code whereas an RX on a broken |
// connection just blocks, but this does fit neatly into an |
// event-driven I/O model. |
unsigned char* rx_pending_buf; |
|
#ifdef CYGPKG_USBS_ETHDRV |
// Has the TCP/IP stack brought up this interface yet? |
cyg_bool ecos_up; |
|
// Is there an ongoing receive? Cancelling a receive operation |
// during a stop() may be difficult, and a stop() may be followed |
// immediately by a restart. |
cyg_bool rx_active; |
|
// The eCos-side MAC. If the host and the eCos stack are to |
// communicate then they must be able to address each other, i.e. |
// they need separate addresses. Again there is no real hardware |
// to extract the address from so it has to be supplied by higher |
// level code via e.g. an ioctl(). |
unsigned char ecos_MAC[6]; |
|
// SNMP statistics |
# ifdef CYGFUN_USBS_ETHDRV_STATISTICS |
unsigned int interrupts; |
unsigned int tx_count; |
unsigned int rx_count; |
unsigned int rx_short_frames; |
unsigned int rx_too_long_frames; |
# endif |
|
// The need for a receive buffer is unavoidable for now because |
// the network driver interface does not support pre-allocating an |
// mbuf and then passing it back to the stack later. Ideally the |
// rx operation would read a single USB packet, determine the |
// required mbuf size from the 2-byte header, copy the initial |
// data, and then read more USB packets. Alternatively, a |
// 1516 byte mbuf could be pre-allocated and then the whole |
// transfer could go there, potentially wasting some mbuf space. |
// None of this is possible at present. |
// |
// Also, typically there will be complications because of |
// dependencies on DMA, cached vs. uncached memory, etc. |
unsigned char rx_buffer[CYGNUM_USBS_ETH_RXBUFSIZE]; |
unsigned char* rx_bufptr; |
cyg_bool rx_buffer_full; |
|
// It should be possible to eliminate the tx buffer. The problem |
// is that the protocol requires 2 bytes to be prepended, and that |
// may not be possible with the buffer supplied by higher-level |
// code. Eliminating this buffer would either require USB |
// device drivers to implement gather functionality on transmits, |
// or it would impose a dependency on higher-level code. |
unsigned char tx_buffer[CYGNUM_USBS_ETH_MAXTU]; |
cyg_bool tx_buffer_full; |
cyg_bool tx_done; |
unsigned long tx_key; |
|
// Prevent recursion send()->tx_done()->can_send()/send() |
cyg_bool tx_in_send; |
#endif |
|
} usbs_eth; |
|
// The package automatically instantiates one USB ethernet device. |
extern usbs_eth usbs_eth0; |
|
// ---------------------------------------------------------------------------- |
// If the network driver option is enabled then the package also |
// provides a single cyg_netdevtab_entry. This is exported so that |
// application code can clone the entry. |
#ifdef CYGPKG_USBS_ETHDRV |
extern cyg_netdevtab_entry_t usbs_eth_netdev0; |
#endif |
|
// ---------------------------------------------------------------------------- |
// A C interface to the low-level USB code. |
|
// Initialize the USBS-eth support for a particular usbs_eth device. |
// This associates a usbs_eth structure with specific endpoints. |
extern void usbs_eth_init(usbs_eth*, usbs_control_endpoint*, usbs_rx_endpoint*, usbs_tx_endpoint*, unsigned char*); |
|
// Start an asynchronous transmit of a single buffer of up to |
// CYGNUM_USBS_ETH_MAXTU bytes. This buffer should contain a 2-byte |
// size field, a 14-byte ethernet header, and upto 1500 bytes of |
// payload. When the transmit has completed the callback function (if |
// any) will be invoked with the specified pointer. NOTE: figure out |
// what to do about error reporting |
extern void usbs_eth_start_tx(usbs_eth*, unsigned char*, void (*)(usbs_eth*, void*, int), void*); |
|
// Start an asynchronous receive of an ethernet packet. The supplied |
// buffer should be at least CYGNUM_USBS_ETH_MAXTU bytes. When a |
// complete ethernet frame has been received or when some sort of |
// error occurs the callback function will be invoked. The third |
// argument |
extern void usbs_eth_start_rx(usbs_eth*, unsigned char*, void (*)(usbs_eth*, void*, int), void*); |
|
// The handler for application class control messages. The init call |
// will install this in the control endpoint by default. However the |
// handler is fairly dumb: it assumes that all application control |
// messages are for the ethernet interface and does not bother to |
// check the control message's destination. This is fine for simple |
// USB ethernet devices, but for any kind of multi-function peripheral |
// higher-level code will have to perform multiplexing and invoke this |
// handler only when appropriate. |
extern usbs_control_return usbs_eth_class_control_handler(usbs_control_endpoint*, void*); |
|
// Similarly a handler for state change messages. Installing this |
// means that the ethernet code will have sufficient knowledge about |
// the state of the USB connection for simple ethernet-only |
// peripherals, but not for anything more complicated. In the latter |
// case higher-level code will need to keep track of which |
// configuration, interfaces, etc. are currently active and explicitly |
// enable or disable the ethernet device using the functions below. |
extern void usbs_eth_state_change_handler(usbs_control_endpoint*, void*, usbs_state_change, int); |
extern void usbs_eth_disable(usbs_eth*); |
extern void usbs_eth_enable(usbs_eth*); |
|
#ifdef __cplusplus |
} // extern "C" |
#endif |
|
#endif // CYGONCE_USBS_ETH_H_ |
<!-- Copyright (C) 2001 Red Hat, Inc. --> |
<!-- This material may be distributed only subject to the terms --> |
<!-- and conditions set forth in the Open Publication License, v1.0 --> |
<!-- or later (the latest version is presently available at --> |
<!-- http://www.opencontent.org/openpub/). --> |
<!-- Distribution of substantively modified versions of this --> |
<!-- document is prohibited without the explicit permission of the --> |
<!-- copyright holder. --> |
<!-- Distribution of the work or derivative of the work in any --> |
<!-- standard (paper) book form is prohibited unless prior --> |
<!-- permission is obtained from the copyright holder. --> |
<HTML |
><HEAD |
><TITLE |
>Communication Protocol</TITLE |
><META |
NAME="GENERATOR" |
CONTENT="Modular DocBook HTML Stylesheet Version 1.64 |
"><LINK |
REL="HOME" |
TITLE="eCos Support for Developing USB-ethernet Peripherals" |
HREF="io-usb-slave-eth.html"><LINK |
REL="PREVIOUS" |
TITLE="Example Host-side Device Driver" |
HREF="usbseth-host.html"></HEAD |
><BODY |
CLASS="REFENTRY" |
BGCOLOR="#FFFFFF" |
TEXT="#000000" |
LINK="#0000FF" |
VLINK="#840084" |
ALINK="#0000FF" |
><DIV |
CLASS="NAVHEADER" |
><TABLE |
WIDTH="100%" |
BORDER="0" |
CELLPADDING="0" |
CELLSPACING="0" |
><TR |
><TH |
COLSPAN="3" |
ALIGN="center" |
>eCos Support for Developing USB-ethernet Peripherals</TH |
></TR |
><TR |
><TD |
WIDTH="10%" |
ALIGN="left" |
VALIGN="bottom" |
><A |
HREF="usbseth-host.html" |
>Prev</A |
></TD |
><TD |
WIDTH="80%" |
ALIGN="center" |
VALIGN="bottom" |
></TD |
><TD |
WIDTH="10%" |
ALIGN="right" |
VALIGN="bottom" |
> </TD |
></TR |
></TABLE |
><HR |
ALIGN="LEFT" |
WIDTH="100%"></DIV |
><H1 |
><A |
NAME="USBSETH-PROTOCOL" |
>Communication Protocol</A |
></H1 |
><DIV |
CLASS="REFNAMEDIV" |
><A |
NAME="AEN281" |
></A |
><H2 |
>Name</H2 |
>Communication Protocol -- Protocol used between the host-side device driver and the eCos |
USB-ethernet package </DIV |
><DIV |
CLASS="REFSECT1" |
><A |
NAME="AEN284" |
></A |
><H2 |
>Description</H2 |
><P |
>There is a USB standard for the protocol to be used between the host |
and a class of communication devices, including ethernet. However, the |
eCos USB-ethernet package does not implement this protocol: the target |
hardware for which the package was first developed had certain |
limitations, and could not implement the standard. Instead, the package |
implements a simple new protocol.</P |
><P |
>A USB-ethernet peripheral involves bulk transfers on two endpoints: |
one endpoint will be used for packets from host to peripheral and the |
other will be used for the opposite direction. Transfers in both |
directions are variable length, with a lower limit of 16 bytes and an |
upper limit of 1516 bytes. The first two bytes of each transfer |
constitute a header specific to USB-ethernet. The next 14 bytes form |
the normal header for an ethernet frame: destination MAC address, |
source MAC address, and a protocol field. The remaining data, up to |
1500 bytes, are the payload. The first two bytes give the size of the |
ethernet frame, least significant byte first, with a value between 14 |
and 1514.</P |
><P |
>For example an ARP request from host to peripheral involves an |
ethernet frame of 42 bytes (0x002A), with the usual 14-byte header and |
a 28-byte payload. The destination is the broadcast address |
0xFFFFFFFFFFFF. The source depends on the MAC address specified for |
the host in the call to <A |
HREF="usbseth-init.html" |
><TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
></A |
>, e.g. |
0x405D90A9BC02. The remaining data is as specified by the appropriate |
<A |
HREF="http://www.ietf.org" |
TARGET="_top" |
>IETF RFC's</A |
>. The actual bulk |
USB transfer involves the following sequence of 44 bytes:</P |
><TABLE |
BORDER="0" |
BGCOLOR="#E0E0E0" |
WIDTH="100%" |
><TR |
><TD |
><PRE |
CLASS="SCREEN" |
>2a 00 ff ff ff ff ff ff 40 5d 90 a9 bc 02 08 06 |
00 01 08 00 06 04 00 01 40 5d 90 a9 bc 02 0a 00 |
00 01 00 00 00 00 00 00 0a 00 00 02</PRE |
></TD |
></TR |
></TABLE |
><P |
>In addition there are two control messages. These will be sent by the |
host to endpoint 0, the control endpoint, and by default they will |
be handled by <A |
HREF="usbseth-control.html" |
><TT |
CLASS="FUNCTION" |
>usbs_eth_class_control_handler</TT |
></A |
>. If class-specific |
control messages are intercepted by other code then it is the |
responsibility of that code to invoke the USB-ethernet handler when |
appropriate.</P |
><P |
>The first control message can be used by the host to obtain a MAC |
address:</P |
><TABLE |
BORDER="0" |
BGCOLOR="#E0E0E0" |
WIDTH="100%" |
><TR |
><TD |
><PRE |
CLASS="PROGRAMLISTING" |
>#define ECOS_USBETH_CONTROL_GET_MAC_ADDRESS 0x01</PRE |
></TD |
></TR |
></TABLE |
><P |
>The control message's type field should specify IN as the direction. |
The request field should be <TT |
CLASS="LITERAL" |
>0x01</TT |
>. The length fields |
should specify a size of 6 bytes. The remaining fields of the control |
message will be ignored by the USB-ethernet package. The response |
consists of the 6-byte MAC address supplied by the initialization call |
<A |
HREF="usbseth-init.html" |
><TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
></A |
>.</P |
><P |
>The second control message can be used by the host to enable or |
disable promiscuous mode.</P |
><TABLE |
BORDER="0" |
BGCOLOR="#E0E0E0" |
WIDTH="100%" |
><TR |
><TD |
><PRE |
CLASS="PROGRAMLISTING" |
>#define ECOS_USBETH_CONTROL_SET_PROMISCUOUS_MODE 0x02</PRE |
></TD |
></TR |
></TABLE |
><P |
>This control message involves no further data so the length field |
should be set to 0. The value field should be non-zero to enable |
promiscuous mode, zero to disable it. The request field should be |
<TT |
CLASS="LITERAL" |
>0x02</TT |
>. The remaining fields in the control message |
will be ignored. It is the responsibility of the host-side device |
driver to keep track of whether or not promiscuous mode is currently |
enabled. It will be disabled when the peripheral changes to |
Configured state, typically at the point where the host-side device |
driver has been activated.</P |
></DIV |
><DIV |
CLASS="NAVFOOTER" |
><HR |
ALIGN="LEFT" |
WIDTH="100%"><TABLE |
WIDTH="100%" |
BORDER="0" |
CELLPADDING="0" |
CELLSPACING="0" |
><TR |
><TD |
WIDTH="33%" |
ALIGN="left" |
VALIGN="top" |
><A |
HREF="usbseth-host.html" |
>Prev</A |
></TD |
><TD |
WIDTH="34%" |
ALIGN="center" |
VALIGN="top" |
><A |
HREF="io-usb-slave-eth.html" |
>Home</A |
></TD |
><TD |
WIDTH="33%" |
ALIGN="right" |
VALIGN="top" |
> </TD |
></TR |
><TR |
><TD |
WIDTH="33%" |
ALIGN="left" |
VALIGN="top" |
>Example Host-side Device Driver</TD |
><TD |
WIDTH="34%" |
ALIGN="center" |
VALIGN="top" |
> </TD |
><TD |
WIDTH="33%" |
ALIGN="right" |
VALIGN="top" |
> </TD |
></TR |
></TABLE |
></DIV |
></BODY |
></HTML |
> |
<!-- Copyright (C) 2001 Red Hat, Inc. --> |
<!-- This material may be distributed only subject to the terms --> |
<!-- and conditions set forth in the Open Publication License, v1.0 --> |
<!-- or later (the latest version is presently available at --> |
<!-- http://www.opencontent.org/openpub/). --> |
<!-- Distribution of substantively modified versions of this --> |
<!-- document is prohibited without the explicit permission of the --> |
<!-- copyright holder. --> |
<!-- Distribution of the work or derivative of the work in any --> |
<!-- standard (paper) book form is prohibited unless prior --> |
<!-- permission is obtained from the copyright holder. --> |
<HTML |
><HEAD |
><TITLE |
>Initializing the USB-ethernet Package</TITLE |
><META |
NAME="GENERATOR" |
CONTENT="Modular DocBook HTML Stylesheet Version 1.64 |
"><LINK |
REL="HOME" |
TITLE="eCos Support for Developing USB-ethernet Peripherals" |
HREF="io-usb-slave-eth.html"><LINK |
REL="PREVIOUS" |
TITLE="Introduction" |
HREF="usbseth-intro.html"><LINK |
REL="NEXT" |
TITLE="USB-ethernet Data Transfers" |
HREF="usbseth-data.html"></HEAD |
><BODY |
CLASS="REFENTRY" |
BGCOLOR="#FFFFFF" |
TEXT="#000000" |
LINK="#0000FF" |
VLINK="#840084" |
ALINK="#0000FF" |
><DIV |
CLASS="NAVHEADER" |
><TABLE |
WIDTH="100%" |
BORDER="0" |
CELLPADDING="0" |
CELLSPACING="0" |
><TR |
><TH |
COLSPAN="3" |
ALIGN="center" |
>eCos Support for Developing USB-ethernet Peripherals</TH |
></TR |
><TR |
><TD |
WIDTH="10%" |
ALIGN="left" |
VALIGN="bottom" |
><A |
HREF="usbseth-intro.html" |
>Prev</A |
></TD |
><TD |
WIDTH="80%" |
ALIGN="center" |
VALIGN="bottom" |
></TD |
><TD |
WIDTH="10%" |
ALIGN="right" |
VALIGN="bottom" |
><A |
HREF="usbseth-data.html" |
>Next</A |
></TD |
></TR |
></TABLE |
><HR |
ALIGN="LEFT" |
WIDTH="100%"></DIV |
><H1 |
><A |
NAME="USBSETH-INIT" |
>Initializing the USB-ethernet Package</A |
></H1 |
><DIV |
CLASS="REFNAMEDIV" |
><A |
NAME="AEN47" |
></A |
><H2 |
>Name</H2 |
><TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
> -- Initializing the USB-ethernet Package</DIV |
><DIV |
CLASS="REFSYNOPSISDIV" |
><A |
NAME="AEN51" |
></A |
><H2 |
>Synopsis</H2 |
><DIV |
CLASS="FUNCSYNOPSIS" |
><A |
NAME="AEN52" |
></A |
><P |
></P |
><TABLE |
BORDER="0" |
BGCOLOR="#E0E0E0" |
WIDTH="100%" |
><TR |
><TD |
><PRE |
CLASS="FUNCSYNOPSISINFO" |
>#include <cyg/io/usb/usbs_eth.h></PRE |
></TD |
></TR |
></TABLE |
><P |
><CODE |
><CODE |
CLASS="FUNCDEF" |
>void usbs_eth_init</CODE |
>(usbs_eth* usbeth, usbs_control_endpoint* ep0, usbs_rx_endpoint* ep1, usbs_tx_endpoint* ep2, unsigned char* mac_address);</CODE |
></P |
><P |
></P |
></DIV |
></DIV |
><DIV |
CLASS="REFSECT1" |
><A |
NAME="AEN67" |
></A |
><H2 |
>Description</H2 |
><P |
>The USB-ethernet package is not tied to any specific hardware. It |
requires certain functionality: there must be USB-slave hardware |
supported by a device driver; there must also be two endpoints for |
bulk transfers between host and peripheral, one for each direction; |
there must also be a control endpoint, although of course that is |
implicit with any USB hardware.</P |
><P |
>However, USB-slave hardware may well provide more endpoints than the |
minimum required for ethernet support. Some of those endpoints might |
be used by other packages, while other endpoints might be used |
directly by the application, or might not be needed for the peripheral |
being built. There is also the possibility of a USB peripheral that |
supports multiple configurations, with the ethernet support active in |
only some of those configurations. The USB-ethernet package has no |
knowledge about any of this, so it relies on higher-level code to tell |
it which endpoints should be used and other information. This is the |
purpose of the <TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
> function.</P |
><P |
>The first argument identifies the specific |
<SPAN |
CLASS="STRUCTNAME" |
>usbs_eth</SPAN |
> data structure that is affected. It |
is expected that the vast majority of affected applications will only |
provide a single USB-ethernet device to a single host, and the package |
automatically provides a suitable data structure |
<TT |
CLASS="LITERAL" |
>usbs_eth0</TT |
> to support this. If multiple |
<SPAN |
CLASS="STRUCTNAME" |
>usbs_eth</SPAN |
> structures are needed for some |
reason then these need to be instantiated by other code, and each one |
needs to be initialised by a call to |
<TT |
CLASS="FUNCTION" |
>usbs_eth_init()</TT |
>. </P |
><P |
>The next three arguments identify the endpoints that should be used |
for USB communications: a control endpoint, a receive endpoint for |
ethernet packets coming from the host to the peripheral, and a |
transmit endpoint for ethernet packets going in the other direction. |
Obviously all three endpoints should be provided by the same USB |
hardware. The USB-ethernet package assumes that it has sole access to |
the receive and transmit endpoints, subject to the use of |
<TT |
CLASS="FUNCTION" |
>usbs_eth_disable</TT |
> and |
<TT |
CLASS="FUNCTION" |
>usbs_eth_enable</TT |
> control functions. The package |
also assumes that no other code is interested in USB state changes or |
class control messages: it installs handlers |
<A |
HREF="usbseth-control.html" |
><TT |
CLASS="FUNCTION" |
>usbs_eth_state_change_handler</TT |
></A |
> |
and |
<A |
HREF="usbseth-control.html" |
><TT |
CLASS="FUNCTION" |
>usbs_eth_class_control_handler</TT |
></A |
> |
in the control endpoint. If any other code does need to handle USB |
state changes or class control messages then replacement handlers |
should be installed after the call to |
<TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
>, and those replacements should |
invoke the USB-ethernet ones when appropriate.</P |
><P |
>The final argument to <TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
> specifies |
the MAC address (or Ethernet Station Address) that should be provided |
to the host-side device driver. Since the USB-ethernet package does not |
interact directly with a real ethernet device it cannot obtain the MAC |
address from any hardware. Instead, it must be supplied by higher-level |
code. The details depend on the <A |
HREF="usbseth-intro.html#AEN22" |
>scenario</A |
> in which the |
USB-ethernet package is being used.</P |
><P |
>The call to <TT |
CLASS="FUNCTION" |
>usbs_eth_init</TT |
> should normally happen |
after the enumeration data has been provided but before the underlying |
USB device driver has been started. If the USB device were to be |
started first then a connection between host and peripheral could be |
established immediately, and the host-side device driver would attempt |
to contact the USB-ethernet package for information such as the MAC |
address. </P |
><TABLE |
BORDER="0" |
BGCOLOR="#E0E0E0" |
WIDTH="100%" |
><TR |
><TD |
><PRE |
CLASS="PROGRAMLISTING" |
>int |
main(int argc, char** argv) |
{ |
unsigned char host_MAC[6] = { 0x40, 0x5d, 0x90, 0xa9, 0xbc, 0x02 }; |
|
usbs_sa11x0_ep0.enumeration_data = &usb_enum_data; |
… |
usbs_eth_init(&usbs_eth0, &usbs_sa11x0_ep0, &usbs_sa11x0_ep1, &usbs_sa11x0_ep2, host_MAC); |
… |
usbs_start(&usbs_sa11x0_ep0); |
… |
}</PRE |
></TD |
></TR |
></TABLE |
></DIV |
><DIV |
CLASS="NAVFOOTER" |
><HR |
ALIGN="LEFT" |
WIDTH="100%"><TABLE |
WIDTH="100%" |
BORDER="0" |
CELLPADDING="0" |
CELLSPACING="0" |
><TR |
><TD |
WIDTH="33%" |
ALIGN="left" |
VALIGN="top" |
><A |
HREF="usbseth-intro.html" |
>Prev</A |
></TD |
><TD |
WIDTH="34%" |
ALIGN="center" |
VALIGN="top" |
><A |
HREF="io-usb-slave-eth.html" |
>Home</A |
></TD |
><TD |
WIDTH="33%" |
ALIGN="right" |
VALIGN="top" |
><A |
HREF="usbseth-data.html" |
>Next</A |
></TD |
></TR |
><TR |
><TD |
WIDTH="33%" |
ALIGN="left" |
VALIGN="top" |
>Introduction</TD |
><TD |
WIDTH="34%" |
ALIGN="center" |
VALIGN="top" |
> </TD |
><TD |
WIDTH="33%" |
ALIGN="right" |
VALIGN="top" |
>USB-ethernet Data Transfers</TD |
></TR |
></TABLE |
></DIV |
></BODY |
></HTML |
> |
Network Device for the eCos TCP/IP Stack
Description
If the USB peripheral involves running the eCos TCP/IP stack and that +stack needs to use USB-ethernet as a transport layer (or as one of the +transports), then the USB-ethernet package can provide a suitable +network device driver. It is still necessary for higher-level code to +perform appropriate initialization by calling usbs_eth_init, but +after that it will be the TCP/IP stack rather than application code +that transmits or receives ethernet frames.
Not all peripherals involving the USB-ethernet package will require a +TCP/IP stack. Hence the provision of the network device is controlled +by a configuration option CYGPKG_USBS_ETHDRV. By +default this will be enabled if the TCP/IP package +CYGPKG_NET is loaded, and disabled otherwise.
There are a number of other configuration options related to the +network device. CYGFUN_USBS_ETHDRV_STATISTICS +determines whether or not the package will maintain statistics, mainly +intended for SNMP: by default this will be enabled if the SNMP support +package CYGPKG_SNMPAGENT is loaded, and disabled +otherwise. The name of the ethernet device is controlled by +CYGDATA_USBS_ETHDRV_NAME, and has a default value +of either eth0 or eth1 +depending on whether or not there is another network device driver +present in the configuration.
Usually eCos network device drivers default to using DHCP for +obtaining necessary information such as IP addresses. This is not +appropriate for USB-ethernet devices. On the host-side the +USB-ethernet network device will not exist until the USB peripheral +has been plugged in and communication has been established. Therefore +any DHCP daemon on the host would not be listening on that network +device at the point that eCos requests its IP and other information. A +related issue is that the use of DHCP would imply the presence of a +DHCP daemon on every affected host machine, as opposed to a single +daemon (plus backups) for the network as a whole. For these reasons +the USB-ethernet package precludes the use of DHCP as a way of setting +the IP address, instead requiring alternatives such as manual +configuration.
I. eCos Support for Developing USB-ethernet Peripherals
- Table of Contents
- Introduction — eCos support for developing USB ethernet peripherals
- Initializing the USB-ethernet Package — Initializing the USB-ethernet Package
- USB-ethernet Data Transfers — Exchanging ethernet packets with the USB host
- USB-ethernet State Handling — Maintaining the USB-ethernet connection with the host
- Network Device for the eCos TCP/IP Stack — USB-ethernet support for the eCos TCP/IP Stack
- Example Host-side Device Driver — Provide host-side support for the eCos USB-ethernet package
- Communication Protocol — Protocol used between the host-side device driver and the eCos +USB-ethernet package
Next | ||
Introduction |
USB-ethernet Data Transfers
Description
The USB-ethernet package provides two main modes of operation. In the +first mode it provides a network device +driver for use by a TCP/IP stack running inside the USB +peripheral. All incoming ethernet packages should be passed up the +TCP/IP stack, and only the stack will generate outgoing packets. Apart +from initialization and possibly +certain control operations, +higher-level code will not interact with the USB-ethernet package +directly.
In the second mode there is no TCP/IP stack running inside the USB +peripheral. For example, a simple USB-ethernet converter has an +ethernet chip and a USB port: ethernet packets received by the +ethernet chip need to be forwarded to the USB host, and ethernet +packets sent by the USB host need to be sent out of the ethernet chip. +usbs_eth_start_rx and +usbs_eth_start_tx allow for this lower-level +access to the USB-ethernet package.
The two modes of operation are mutually exclusive. If the network +device driver mode is enabled then application code should communicate +at the TCP/IP level, and not by using the lower-level functions. +Instead, it is the network device driver that will make use of these +functions, and it assumes that it has exclusive access. The package +does not perform any locking.
The transmit and receive functions work in much the same way. The +first argument identifies the usbs_eth +structure that should be used. For the majority of applications this +will be usbs_eth0. The second argument specifies +the location of the ethernet packet; outgoing for +usbs_eth_start_tx and incoming for +usbs_eth_start_rx. This buffer should correspond +to the protocol:
Outgoing packets can consist of up to 1516 bytes, consisting of a +two-byte header specific to USB-ethernet followed by a standard +ethernet frame (a header with 6-byte destination address, 6-byte +source address and a further two bytes, followed by a payload of +up to 1500 bytes). The two-byte USB-ethernet header consists simply of +the size of the ethernet frame, i.e. the size of the rest of the +packet not including the USB-ethernet header, with the least +significant byte first.
For incoming packets the supplied buffer should usually be at least +1516 bytes. There may be special circumstances in which a smaller +buffer might be safe; for example, if the host-side device driver is +modified to support only smaller packets. Once the packet has been +received the buffer will contain a two-byte header specific to +USB-ethernet, followed by a normal ethernet frame. The header +gives the size of the ethernet frame, excluding the header, with the +least significant byte first.
Both usbs_eth_start_tx and +usbs_eth_start_rx are asynchronous: the transfer +is started and, some time later, a completion function will be invoked. +The third and fourth arguments to both +usbs_eth_start_tx and +usbs_eth_start_rx supply the completion function +and an argument to that function respectively. The completion function +will be invoked with three arguments: a pointer to the +usbs_eth data structure, usually +usbs_eth0; the supplied completion data ; and a +return code field. A negative value indicates that an error occurred, +for example -EPIPE if the connection between USB +host and peripheral has been broken, or -EAGAIN if +an endpoint has been halted. A positive value indicates the total size +of the transfer, which should correspond to the size in the +USB-ethernet header plus an additional two bytes for the header +itself.
If the data transfer is succesful then the completion function will +typically be invoked in DSR context rather than in thread context, +although this depends on the implementation of the underlying USB +device driver. 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. Note that if the transfer finishes +quickly then the completion function may be invoked before +usbs_eth_start_rx or +usbs_eth_start_tx returns. This is especially +likely to happen if the current thread is descheduled after starting +the data transfer but before returning from these functions.
For transmit operations, it is possible for +usbs_eth_start_tx to invoke the completion +function immediately. If there is no current connection between host +and target then the transmit will fail immediately with +-EPIPE. In addition the USB-ethernet package will +check the destination MAC address and make sure that the ethernet +frame really is intended for the host: either it must be for the +address specified in the initialization call usbs_eth_init, or +it must be a broadcast packet, or the host must have enabled +promiscuous mode.
Introduction
Introduction
The eCos USB-ethernet package provides additional support for USB +peripherals that involve some sort of ethernet-style network. This can +be a traditional ethernet, or it can involve some other networking +technology that uses ethernet frames as a unit of transfer. It +provides functions to transfer ethernet frames over the USB bus, +handles certain control messages from the host, and optionally it can +provide a network device driver for use by the eCos TCP/IP stack. +The package comes with an example host-side device driver.
The USB-ethernet package is not tied to any specific hardware. It +requires the presence of USB hardware and a suitable device driver, +but not all USB peripherals involve ethernet communications. Hence the +configuration system cannot load the package automatically for +specific targets, in the way that a USB device driver or an ethernet +driver can be loaded automatically. Instead, the package has to be +added explicitly. When using the command line tools this will involve +an operation like the following:
$ ecosconfig add usbs_eth |
Typically, this will automatically cause the USB device driver to +become active. Loading the USB-ethernet package automatically provides +functionality for initialization, +data transfer, and the handling of +control messages and state +changes. If the current configuration includes the eCos TCP/IP stack +then the network device driver +support will be enabled as well by default, allowing the stack to +exchange ethernet frames over the USB bus.
There is a USB standard for a class of communication devices including +ethernet. The package does not implement this standard, due to +limitations in the hardware for which the package was first developed. +Instead, the package uses its own protocol between USB +host device driver and the +peripheral.
Usage Scenarios
The USB-ethernet package can be used several different scenarios. In +a simple scenario, the peripheral serves only to connect the USB host +to a suitable network:
After initialization, and once the USB connection between host and +peripheral has been established, higher-level code needs to detect +packets that are intended for the host, and to forward these. This can +be achieved by the low-level usbs_eth_start_tx +function. Similarly, higher-level code needs to detect packets coming +from the host, using usbs_eth_start_rx, and to +forward these using the real network. As far as the host is concerned +it is connected directly to the network. In this scenario there is no +confusion about addresses: there is a single MAC address for the +host/peripheral combination, corresponding to the connection to the +real network, and it is this address which should be supplied during +initialization.
In a more complicated scenario, there is a TCP/IP stack running inside +the peripheral.
This involves the USB-ethernet package providing a service both to the +host and to the eCos TCP/IP stack. It achieves the latter by acting as +an eCos network device. Typically, the TCP/IP stack will be configured +to act as a network bridge. The USB peripheral needs to examine the +packets arriving over the real network. Some of these packets will be +intended for the host, while others will be intended for the +peripheral itself. To distinguish between these two scenarios, two +distinct MAC addresses are needed: one for the host, and one for the +peripheral. Similarly, packets sent by the host may have to be +forwarded via the real network, or they may be intended for the TCP/IP +stack inside the peripheral. Packets generated inside the peripheral's +TCP/IP stack may need to be sent via the real network or over the USB +bus. The network bridge software will have to take care of all these +possibilities. Unusually for a network bridge, one of the network +segments being bridged will only ever have one machine attached.
There are other possible usage scenarios. For example, the peripheral +might not be attached to a real network at all. Instead it could be +the USB host that acts as a network bridge, allowing a TCP/IP stack +inside the peripheral to communicate with the outside world. The +various details will depend on the exact type of peripheral being +developed.
Prev | Home | Next |
eCos Support for Developing USB-ethernet Peripherals | Initializing the USB-ethernet Package |
Example Host-side Device Driver
Description
The USB-ethernet package is supplied with a single host-side device +driver. This driver has been developed against the Linux kernel +2.2.16-22, as shipped with Red Hat 7. The driver is provided as is and +should not be considered production quality: for example it only +checks for a bogus vendor id 0x4242 rather than an +official vendor id supplied by the USB Implementers Forum. Also, if the +peripheral involves multiple configurations or multiple interfaces, it +will fail to detect this. However, the driver can be used for simple +testing and as the basis of a full device driver. Details of the +protocol used between host and peripheral can be found in the Communication Protocol section.
The host-side device driver can be found in the host subdirectory of the USB-ethernet +package, specifically the file ecos_usbeth.c, and +comes with a Makefile. Both files may need +to be modified for specific applications. For example, the vendor id +table ecos_usbeth_implementations may need to be +updated for the specific USB peripheral being built. The +Makefile assumes that the Linux kernel sources +reside in /usr/src/linux, and +that the kernel has already been configured and built. Assuming this +is the case, the device driver can be built simply by invoking +make with no additional arguments. This will result +in a dynamically loadable kernel module, +ecos_usbeth.o, in the current directory.
Note: As normal for Linux kernel builds, the generated files such as +ecos_usbeth.o live in the same directory as the +source tree. This is very different from eCos where the source tree +(or component repository) is kept separate from any builds. There may +be problems if the component repository is kept read-only or if it is +put under source code control. Any such problems can be avoided by +making a copy of the host +subdirectory and building that copy.
Loading the kernel module into the current system requires root +privileges. If the generic USB support is also a loadable module and +has not been loaded already, this must happen first:
# insmod usb-uhci +Using /lib/modules/2.2.16-22/usb/usb-uhci.o |
Depending on the host hardware, the uhci or +usb-ohci modules may be more appropriate. Loading +the generic USB module will typically result in a number of messages +to the logfile /var/log/messages, giving details +of the specific host-side hardware that has been detected plus any +hubs. The next step is to load the USB-ethernet module:
# insmod ecos_usbeth.o |
This should result in a number of additional diagnostics in the +logfile:
Apr 1 18:01:08 grumpy kernel: eCos USB-ethernet device driver +Apr 1 18:01:08 grumpy kernel: usb.c: registered new driver ecos_usbeth |
If a suitable USB peripheral is now connected the host will detect +this, assign an address in the local USB network, obtain enumeration +data, and find a suitable device driver. Assuming the peripheral and +device driver agree on the supported vendor ids, the +ecos_usbeth.o module will be selected and this +will be reported in the system log:
Apr 1 18:04:12 grumpy kernel: usb.c: USB new device connect, assigned device number 3 +Apr 1 18:04:12 grumpy kernel: eCos-based USB ethernet peripheral active at eth1 |
What can happen next depends very much on the software that is running +on top of the USB-ethernet package inside the peripheral. For example, +if there is a TCP/IP stack then it should be possible to bring up a +network connection between host and peripheral using +ifconfig.
USB-ethernet State Handling
Synopsis
#include <cyg/io/usb/usbs_eth.h> |
usbs_control_return usbs_eth_class_control_handler
(usbs_control_endpoint* ep0, void* callback_data);
void usbs_eth_state_change_handler
(usbs_control_endpoint* ep0, void* callback_data, usbs_state_change change, int old_state);
void usbs_eth_disable
(usbs_eth* usbseth>);
void usbs_eth_enable
(usbs_eth* usbseth>);
Description
When the USB-ethernet package is initialized by a call to usbs_eth_init it +installs usbs_eth_state_change_handler to handle +USB state changes. This allows the package to detect when the +connection between the host and the peripheral is established or +broken, resulting in internal calls to +usbs_eth_enable and +usbs_eth_disable respectively. This is +appropriate if no other code needs to access the USB device. However, +if there is other code, either other USB-related packages or the +application itself, that needs to perform I/O over the USB bus, then +typically the USB-ethernet package should not have exclusive access to +state change events. Instead, the assumption is that higher-level +code, typically provided by the application, will install an +alternative state change handler in the control endpoint data +structure after the call to usbs_eth_init. This +alternative handler will either chain into +usbs_eth_state_change_handler when appropriate, +or else it will invoke usbs_eth_enable and +usbs_eth_disable directly. For further details of +state change handlers and control endpoints generally, see the +documentation for the common USB-slave package.
Similarly, usbs_eth_init will install +usbs_eth_class_control_handler in the control +endpoint data structure as the appropriate handler for class-specific +USB control messages. This code will handle the ethernet-specific +control messages , for example +requests by the host to enable or disable promiscuous mode or to +obtain the MAC address. If the USB device is not shared with any other +code then this is both necessary and sufficient. However, if other code +is involved and if that code also needs to process certain control +messages, higher-level code should install its own handler and chain +to the USB-ethernet one when appropriate. It should be noted that the +request code is encoded in just a single byte, so there is a real +possibility that exactly the same number will be used by different +protocols for different requests. Any such problems will have to be +identified and resolved by application developers, and may involve +modifying the source code for the USB-ethernet package.
As an alternative to chaining the state change handler, higher-level +code can instead call usbs_eth_disable and +usbs_eth_enable directly. These functions may +also be called if the USB-ethernet package should become inactive for +reasons not related directly to events on the USB bus. The main effect +of usbs_eth_enable is to restart receive +operations and to allow transmits. The main effect of +usbs_eth_disable is to block further transmits: +any current receive operations need to be aborted at the USB level, +for example by halting the appropriate endpoint.
Writing a USB Device Driver
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.
Note: 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.
Note: 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. +
Note: 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 +}; |
Note: 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.
USB Enumeration Data
Synopsis
#include <cyg/io/usb/usb.h> +#include <cyg/io/usb/usbs.h> + +typedef struct usb_device_descriptor { + … +} usb_device_descriptor __attribute__((packed)); + +typedef struct usb_configuration_descriptor { + … +} usb_configuration_descriptor __attribute__((packed)); + +typedef struct usb_interface_descriptor { + … +} usb_interface_descriptor __attribute__((packed)); + +typedef struct usb_endpoint_descriptor { + … +} usb_endpoint_descriptor; + +typedef struct usbs_enumeration_data { + usb_device_descriptor device; + int total_number_interfaces; + int total_number_endpoints; + int total_number_strings; + const usb_configuration_descriptor* configurations; + const usb_interface_descriptor* interfaces; + const usb_endpoint_descriptor* endpoints; + const unsigned char** strings; +} usbs_enumeration_data; |
USB Enumeration Data
When a USB host detects that a peripheral has been plugged in or +powered up, one of the first steps is to ask the peripheral to +describe itself by supplying enumeration data. Some of this data +depends on the class of peripheral. Other fields are vendor-specific. +There is also a dependency on the hardware, specifically which +endpoints are available should be used. In general it is not possible +for generic code to provide this information, so it is the +responsibility of application code to provide a suitable +usbs_enumeration_data data structure and +install it in the endpoint 0 data structure during initialization. +This must happen before the USB device is enabled by a call to +usbs_start, for example:
const usbs_enumeration_data usb_enum_data = { + … +}; + +int +main(int argc, char** argv) +{ + usbs_sa11x0_ep0.enumeration_data = &usb_enum_data; + … + usbs_start(&usbs_sa11x0_ep0); + … +} |
For most applications the enumeration data will be static, although +the usbs_enumeration_data structure can be +filled in at run-time if necessary. Full details of the enumeration +data can be found in the Universal Serial Bus specification obtainable +from the USB Implementers Forum web +site, although the meaning of most fields is fairly obvious. +The various data structures and utility macros are defined in the +header files cyg/io/usb/usb.h +and cyg/io/usb/usbs.h. Note +that the example code below makes use of the gcc labelled element +extension.
usb_device_descriptor
The main information about a USB peripheral comes from a single +usb_device_descriptor structure, which is +embedded in the usbs_enumeration_data +structure. A typical example might look like this:
const usbs_enumeration_data usb_enum_data = { + { + length: USB_DEVICE_DESCRIPTOR_LENGTH, + type: USB_DEVICE_DESCRIPTOR_TYPE, + usb_spec_lo: USB_DEVICE_DESCRIPTOR_USB11_LO, + usb_spec_hi: USB_DEVICE_DESCRIPTOR_USB11_HI, + device_class: USB_DEVICE_DESCRIPTOR_CLASS_VENDOR, + device_subclass: USB_DEVICE_DESCRIPTOR_SUBCLASS_VENDOR, + device_protocol: USB_DEVICE_DESCRIPTOR_PROTOCOL_VENDOR, + max_packet_size: 8, + vendor_lo: 0x42, + vendor_hi: 0x42, + product_lo: 0x42, + product_hi: 0x42, + device_lo: 0x00, + device_hi: 0x01, + manufacturer_str: 1, + product_str: 2, + serial_number_str: 0, + number_configurations: 1 + }, + … +}; |
The length and type fields are specified by the USB standard. The +usb_spec_lo and +usb_spec_hi fields identify the particular +revision of the standard that the peripheral implements, for example +revision 1.1.
The device class, subclass, and protocol fields are used by generic +host-side USB software to determine which host-side device driver +should be loaded to interact with the peripheral. A number of standard +classes are defined, for example mass-storage devices and +human-interface devices. If a peripheral implements one of the +standard classes then a standard existing host-side device driver may +exist, eliminating the need to write a custom driver. The value +0xFF (VENDOR) is reserved for +peripherals that implement a vendor-specific protocol rather than a +standard one. Such peripherals will require a custom host-side device +driver. The value 0x00 +(INTERFACE) is reserved and indicates that the +protocol used by the peripheral is defined at the interface level +rather than for the peripheral as a whole.
The max_package_size field specifies the +maximum length of a control message. There is a lower bound of eight +bytes, and typical hardware will not support anything larger because +control messages are usually small and not performance-critical.
The vendor_lo and +vendor_hi fields specify a vendor id, which +must be obtained from the USB Implementor's Forum. The numbers used in +the code fragment above are examples only and must not be used in real +USB peripherals. The product identifier is determined by the vendor, +and different USB peripherals should use different identifiers. The +device identifier field should indicate a release number in +binary-coded decimal.
The above fields are all numerical in nature. A USB peripheral can +also provide a number of strings as described below, for example the name of the +vendor can be provided. The various _str +fields act as indices into an array of strings, with index 0 +indicating that no string is available.
A typical USB peripheral involves just a single configuration. However +more complicated peripherals can support multiple configurations. Only +one configuration will be active at any one time, and the host will +switch between them as appropriate. If a peripheral does involve +multiple configurations then typically it will be the responsibility +of application code to handle the standard +set-configuration control message.
usb_configuration_descriptor
A USB peripheral involves at least one and possible several different +configurations. The usbs_enumeration_data +structure requires a pointer to an array, possibly of length 1, of +usb_configuration_descriptor structures. +Usually a single structure suffices:
const usb_configuration_descriptor usb_configuration = { + length: USB_CONFIGURATION_DESCRIPTOR_LENGTH, + type: USB_CONFIGURATION_DESCRIPTOR_TYPE, + total_length_lo: USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_LO(1, 2), + total_length_hi: USB_CONFIGURATION_DESCRIPTOR_TOTAL_LENGTH_HI(1, 2), + number_interfaces: 1, + configuration_id: 1, + configuration_str: 0, + attributes: USB_CONFIGURATION_DESCRIPTOR_ATTR_REQUIRED | + USB_CONFIGURATION_DESCRIPTOR_ATTR_SELF_POWERED, + max_power: 50 +}; + +const usbs_enumeration_data usb_enum_data = { + … + configurations: &usb_configuration, + … +}; |
The values for the length and +type fields are determined by the standard. +The total_length field depends on the +number of interfaces and endpoints used by this configuration, and +convenience macros are provided to calculate this: the first argument +to the macros specify the number of interfaces, the second the number +of endpoints. The number_interfaces field +is self-explanatory. If the peripheral involves multiple +configurations then each one must have a unique id, and this will be +used in the set-configuration control message. The id +0 is reserved, and a set-configuration control +message that uses this id indicates that the peripheral should be +inactive. Configurations can have a string description if required. +The attributes field must have the +REQUIRED bit set; the +SELF_POWERED bit informs the host that the +peripheral has its own power supply and will not draw any power over +the bus, leaving more bus power available to other peripherals; the +REMOTE_WAKEUP bit is used if the peripheral can +interrupt the host when the latter is in power-saving mode. For +peripherals that are not self-powered, the +max_power field specifies the power +requirements in units of 2mA.
usb_interface_descriptor
A USB configuration involves one or more interfaces, typically +corresponding to different streams of data. For example, one interface +might involve video data while another interface is for audio. +Multiple interfaces in a single configuration will be active at the +same time.
const usb_interface_descriptor usb_interface = { + length: USB_INTERFACE_DESCRIPTOR_LENGTH, + type: USB_INTERFACE_DESCRIPTOR_TYPE, + interface_id: 0, + alternate_setting: 0, + number_endpoints: 2, + interface_class: USB_INTERFACE_DESCRIPTOR_CLASS_VENDOR, + interface_subclass: USB_INTERFACE_DESCRIPTOR_SUBCLASS_VENDOR, + interface_protocol: USB_INTERFACE_DESCRIPTOR_PROTOCOL_VENDOR, + interface_str: 0 +}; + +const usbs_enumeration_data usb_enum_data = { + … + total_number_interfaces: 1, + interfaces: &usb_interface, + … +}; |
Again, the length and +type fields are specified by the standard. +Each interface within a configuration requires its own id. However, a +given interface may have several alternate settings, in other words +entries in the interfaces array with the same id but different +alternate_setting fields. For example, +there might be one setting which requires a bandwidth of 100K/s and +another setting that only needs 50K/s. The host can use the standard +set-interface control message to choose the most appropriate setting. +The handling of this request is the responsibility of higher-level +code, so the application may have to install its own handler.
The number of endpoints used by an interface is specified in the +number_endpoints field. Exact details of +which endpoints are used is held in a separate array of endpoint +descriptors. The class, subclass and protocol fields are used by +host-side code to determine which host-side device driver should +handle this specific interface. Usually this is determined on a +per-peripheral basis in the +usb_device_descriptor structure, but that can +defer the details to individual interfaces. A per-interface string +is allowed as well.
For USB peripherals involving multiple configurations, the array of +usb_interface_descriptor structures should +first contain all the interfaces for the first configuration, then all +the interfaces for the second configuration, and so on.
usb_endpoint_descriptor
The host also needs information about which endpoint should be used +for what. This involves an array of endpoint descriptors:
const usb_endpoint_descriptor usb_endpoints[] = { + { + length: USB_ENDPOINT_DESCRIPTOR_LENGTH, + type: USB_ENDPOINT_DESCRIPTOR_TYPE, + endpoint: USB_ENDPOINT_DESCRIPTOR_ENDPOINT_OUT | 1, + attributes: USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + max_packet_lo: 64, + max_packet_hi: 0, + interval: 0 + }, + { + length: USB_ENDPOINT_DESCRIPTOR_LENGTH, + type: USB_ENDPOINT_DESCRIPTOR_TYPE, + endpoint: USB_ENDPOINT_DESCRIPTOR_ENDPOINT_IN | 2, + attributes: USB_ENDPOINT_DESCRIPTOR_ATTR_BULK, + max_packet_lo: 64, + max_packet_hi: 0, + interval: 0 + } +}; + +const usbs_enumeration_data usb_enum_data = { + … + total_number_endpoints: 2, + endpoints: usb_endpoints, + … +}; |
As usual the values for the length and +type fields are specified by the standard. +The endpoint field gives both the endpoint +number and the direction, so in the above example endpoint 1 is used +for OUT (host to peripheral) transfers and endpoint 2 is used for IN +(peripheral to host) transfers. The +attributes field indicates the USB protocol +that should be used on this endpoint: CONTROL, +ISOCHRONOUS, BULK or +INTERRUPT. The +max_packet field specifies the maximum size +of a single USB packet. For bulk transfers this will typically be 64 +bytes. For isochronous transfers this can be up to 1023 bytes. For +interrupt transfers it can be up to 64 bytes, although usually a +smaller value will be used. The interval +field is ignored for control and bulk transfers. For isochronous +transfers it should be set to 1. For interrupt transfers it can be a +value between 1 and 255, and indicates the number of milliseconds +between successive polling operations.
For USB peripherals involving multiple configurations or interfaces +the array of endpoint descriptors should be organized sequentially: +first the endpoints corresponding to the first interface of the first +configuration, then the second interface in that configuration, and so +on; then all the endpoints for all the interfaces in the second +configuration; etc.
Strings
The enumeration data can contain a number of strings with additional +information. Unicode encoding is used for the strings, and it is +possible for a peripheral to supply a given string in multiple +languages using the appropriate characters. The first two bytes of +each string give a length and type field. The first string is special; +after the two bytes header it consists of an array of 2-byte language +id codes, indicating the supported languages. The language code +0x0409 corresponds to English (United States).
const unsigned char* usb_strings[] = { + "\004\003\011\004", + "\020\003R\000e\000d\000 \000H\000a\000t\000" +}; + +const usbs_enumeration_data usb_enum_data = { + … + total_number_strings: 2, + strings: usb_strings, + … +}; |
The default handler for standard control messages assumes that the +peripheral only uses a single language. If this is not the case then +higher-level code will have to handle the standard get-descriptor +control messages when a string descriptor is requested.
usbs_enumeration_data
The usbs_enumeration_data data structure +collects together all the various descriptors that make up the +enumeration data. It is the responsibility of application code to +supply a suitable data structure and install it in the control +endpoints's enumeration_data field before +the USB device is started.
Receiving Data from the Host
Description
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.
Devtab Entries
Devtab Entries
USB device drivers provide two ways of transferring data between host +and peripheral. The first involves USB-specific functionality such as +usbs_start_rx_buffer. +This provides non-blocking I/O: a transfer is started, and some time +later the device driver will call a supplied completion function. The +second uses the conventional I/O model: there are entries in the +device table corresponding to the various endpoints. Standard calls +such as open can then be used to get a suitable +handle. Actual I/O happens via blocking read and +write calls. In practice the blocking operations +are simply implemented using the underlying non-blocking +functionality.
Each endpoint will have its own devtab entry. The exact names are +controlled by the device driver package, but typically the root will +be /dev/usb. This is followed by one or more +decimal digits giving the endpoint number, followed by +c for a control endpoint, r for +a receive endpoint (host to peripheral), and w for +a transmit endpoint (peripheral to host). If the target hardware +involves more than one USB device then different roots should be used, +for example /dev/usb0c and +/dev/usb1_0c. This may require explicit +manipulation of device driver configuration options by the application +developer.
At present the devtab entry for a control endpoint does not support +any I/O operations.
write 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.
read 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.
select operations
Typical USB device drivers will not provide any support for +select. Consider bulk transfers from the host to +the peripheral. At the USB device driver level there is no way of +knowing in advance how large a transfer will be, so it is not feasible +for the device driver to buffer the entire transfer. It may be +possible to buffer part of the transfer, for example the first 64-byte +packet, and copy this into application space at the start of a +read, but this adds code and memory overheads. +Worse, it means that there is an unknown but potentially long delay +between a peripheral accepting the first packet of a transfer and the +remaining packets, which could confuse or upset the host-side +software.
With some USB hardware it may be possible for the device driver to +detect OUT tokens from the host without actually accepting the data, +and this would indicate that a read is likely to +succeed. However, it would not be reliable since the host-side I/O +operation could time out. A similar mechanism could be used to +implement select for outgoing data, but again +this would not be reliable.
Some device drivers may provide partial support for +select anyway, possibly under the control of a +configuration option. The device driver's documentation should be +consulted for further information. It is also worth noting that the +USB-specific non-blocking API can often be used as an alternative to +select.
get_config and +set_config operations
There are no set_config or +get_config (also known as +ioctl) operations defined for USB devices. +Some device drivers may provide hardware-specific facilities this way.
Note: 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.
Sending Data to the Host
Description
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
Synopsis
#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);
Description
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.
Data Endpoints
Synopsis
#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.
Introduction
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.
Note: 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.
I. eCos USB Slave Support
- Table of Contents
- Introduction — eCos support for USB slave devices
- USB Enumeration Data — The USB enumeration data structures
- Starting up a USB Device — Starting up a USB Device
- Devtab Entries — Data endpoint data structure
- Receiving Data from the Host — Receiving Data from the Host
- Sending Data to the Host — Sending Data to the Host
- Halted Endpoints — Support for Halting and Halted Endpoints
- Control Endpoints — Control endpoint data structure
- Data Endpoints — Data endpoint data structures
- Writing a USB Device Driver — USB Device Driver Porting Guide
- Testing — Testing of USB Device Drivers
Next | ||
Introduction |
Starting up a USB Device
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.
eCos USB Slave Support | ||
---|---|---|
Prev |
Testing
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
Note: 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 |
- 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.
Note: 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. +
Note: 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, +….
Note: 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. +
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.
Control Endpoints
Synopsis
#include <cyg/io/usb/usbs.h> + +typedef struct usbs_control_endpoint { + *hellip; +} usbs_control_endpoint; |
usbs_control_endpoint Data Structure
The device driver for a USB slave device should supply one +usbs_control_endpoint data structure per USB +device. This corresponds to endpoint 0 which will be used for all +control message interaction between the host and that device. The data +structure is also used for internal management purposes, for example +to keep track of the current state. In a typical USB peripheral there +will only be one such data structure in the entire system, but if +there are multiple USB slave ports, allowing the peripheral to be +connected to multiple hosts, then there will be a separate data +structure for each one. The name or names of the data structures are +determined by the device drivers. For example, the SA11x0 USB device +driver package provides usbs_sa11x0_ep0.
The operations on a control endpoint do not fit cleanly into a +conventional open/read/write I/O model. For example, when the host +sends a control message to the USB peripheral this may be one of four +types: standard, class, vendor and reserved. Some or all of the +standard control messages will be handled automatically by the common +USB slave package or by the device driver itself. Other standard +control messages and the other types of control messages may be +handled by a class-specific package or by application code. Although +it would be possible to have devtab entries such as +/dev/usbs_ep0/standard and +/dev/usbs_ep0/class, and then support read and +write operations on these devtab entries, this would add significant +overhead and code complexity. Instead, all of the fields in the +control endpoint data structure are public and can be manipulated +directly by higher level code if and when required.
Control endpoints involve a number of callback functions, with +higher-level code installing suitable function pointers in the control +endpoint data structure. For example, if the peripheral involves +vendor-specific control messages then a suitable handler for these +messages should be installed. Although the exact details depend on the +device driver, typically these callback functions will be invoked at +DSR level rather than thread level. Therefore, only certain eCos +functions can be invoked; specifically, those functions that are +guaranteed not to block. If a potentially blocking function such as a +semaphore wait or a mutex lock operation is invoked from inside the +callback then the resulting behaviour is undefined, and the system as +a whole may fail. In addition, if one of the callback functions +involves significant processing effort then this may adversely affect +the system's real time characteristics. The eCos kernel documentation +should be consulted for more details of DSR handling.
Initialization
The usbs_control_endpoint data structure +contains the following fields related to initialization.
typedef struct usbs_control_endpoint { + … + const usbs_enumeration_data* enumeration_data; + void (*start_fn)(usbs_control_endpoint*); + … +}; |
It is the responsibility of higher-level code, usually the +application, to define the USB enumeration data. This needs to be +installed in the control endpoint data structure early on during +system startup, before the USB device is actually started and any +interaction with the host is possible. Details of the enumeration data +are supplied in the section USB Enumeration +Data. Typically, the enumeration data is constant for a given +peripheral, although it can be constructed dynamically if necessary. +However, the enumeration data cannot change while the peripheral is +connected to a host: the peripheral cannot easily claim to be a +keyboard one second and a printer the next.
The start_fn member is normally accessed +via the utility usbs_start rather +than directly. It is provided by the device driver and should be +invoked once the system is fully initialized and interaction with the +host is possible. A typical implementation would change the USB data +pins from tristated to active. If the peripheral is already plugged +into a host then the latter should detect this change and start +interacting with the peripheral, including requesting the enumeration +data.
State
There are three usbs_control_endpoint fields +related to the current state of a USB slave device, plus some state +constants and an enumeration of the possible state changes:
typedef struct usbs_control_endpoint { + … + int state; + void (*state_change_fn)(struct usbs_control_endpoint*, void*, + usbs_state_change, int); + void* state_change_data; + … +}; + +#define USBS_STATE_DETACHED 0x01 +#define USBS_STATE_ATTACHED 0x02 +#define USBS_STATE_POWERED 0x03 +#define USBS_STATE_DEFAULT 0x04 +#define USBS_STATE_ADDRESSED 0x05 +#define USBS_STATE_CONFIGURED 0x06 +#define USBS_STATE_MASK 0x7F +#define USBS_STATE_SUSPENDED (1 << 7) + +typedef enum { + USBS_STATE_CHANGE_DETACHED = 1, + USBS_STATE_CHANGE_ATTACHED = 2, + USBS_STATE_CHANGE_POWERED = 3, + USBS_STATE_CHANGE_RESET = 4, + USBS_STATE_CHANGE_ADDRESSED = 5, + USBS_STATE_CHANGE_CONFIGURED = 6, + USBS_STATE_CHANGE_DECONFIGURED = 7, + USBS_STATE_CHANGE_SUSPENDED = 8, + USBS_STATE_CHANGE_RESUMED = 9 +} usbs_state_change; |
The USB standard defines a number of states for a given USB +peripheral. The initial state is detached, where +the peripheral is either not connected to a host at all or, from the +host's perspective, the peripheral has not started up yet because the +relevant pins are tristated. The peripheral then moves via +intermediate attached and +powered states to its default or +reset state, at which point the host and +peripheral can actually start exchanging data. The first message is +from host to peripheral and provides a unique 7-bit address within the +local USB network, resulting in a state change to +addressed. The host then requests enumeration +data and performs other initialization. If everything succeeds the +host sends a standard set-configuration control message, after which +the peripheral is configured and expected to be +up and running. Note that some USB device drivers may be unable to +distinguish between the detached, +attached and powered states +but generally this is not important to higher-level code.
A USB host should generate at least one token every millisecond. If a +peripheral fails to detect any USB traffic for a period of time then +typically this indicates that the host has entered a power-saving +mode, and the peripheral should do the same if possible. This +corresponds to the suspended bit. The actual +state is a combination of suspended and the +previous state, for example configured and +suspended rather than just +suspended. When the peripheral subsequently +detects USB traffic it would switch back to the +configured state.
The USB device driver and the common USB slave package will maintain +the current state in the control endpoint's +state field. There should be no need for +any other code to change this field, but it can be examined whenever +appropriate. In addition whenever a state change occurs the generic +code can invoke a state change callback function. By default, no such +callback function will be installed. Some class-specific packages such +as the USB-ethernet package will install a suitable function to keep +track of whether or not the host-peripheral connection is up, that is +whether or not ethernet packets can be exchanged. Application code can +also update this field. If multiple parties want to be informed of +state changes, for example both a class-specific package and +application code, then typically the application code will install its +state change handler after the class-specific package and is +responsible for chaining into the package's handler.
The state change callback function is invoked with four arguments. The +first identifies the control endpoint. The second is an arbitrary +pointer: higher-level code can fill in the +state_change_data field to set this. The +third argument specifies the state change that has occurred, and the +last argument supplies the previous state (the new state is readily +available from the control endpoint structure).
eCos does not provide any utility functions for updating or examining +the state_change_fn or +state_change_data fields. Instead, it is +expected that the fields in the +usbs_control_endpoint data structure will be +manipulated directly. Any utility functions would do just this, but +at the cost of increased code and cpu overheads.
Standard Control Messages
typedef struct usbs_control_endpoint { + … + unsigned char control_buffer[8]; + usbs_control_return (*standard_control_fn)(struct usbs_control_endpoint*, void*); + void* standard_control_data; + … +} usbs_control_endpoint; + +typedef enum { + USBS_CONTROL_RETURN_HANDLED = 0, + USBS_CONTROL_RETURN_UNKNOWN = 1, + USBS_CONTROL_RETURN_STALL = 2 +} usbs_control_return; + +extern usbs_control_return usbs_handle_standard_control(struct usbs_control_endpoint*); |
When a USB peripheral is connected to the host it must always respond +to control messages sent to endpoint 0. Control messages always +consist of an initial eight-byte header, containing fields such as a +request type. This may be followed by a further data transfer, either +from host to peripheral or from peripheral to host. The way this is +handled is described in the Buffer Management section below.
The USB device driver will always accept the initial eight-byte +header, storing it in the control_buffer +field. Then it determines the request type: standard, class, vendor, +or reserved. The way in which the last three of these are processed is +described in the section Other +Control Messages. Some +standard control messages will be handled by the device driver itself; +typically the set-address request and the +get-status, set-feature and +clear-feature requests when applied to endpoints.
If a standard control message cannot be handled by the device driver +itself, the driver checks the +standard_control_fn field in the control +endpoint data structure. If higher-level code has installed a suitable +callback function then this will be invoked with two argument, the +control endpoint data structure itself and the +standard_control_data field. The latter +allows the higher level code to associate arbitrary data with the +control endpoint. The callback function can return one of three +values: HANDLED to indicate that the request has +been processed; UNKNOWN if the message should be +handled by the default code; or STALL to indicate +an error condition. If higher level code has not installed a callback +function or if the callback function has returned +UNKNOWN then the device driver will invoke a +default handler, usbs_handle_standard_control +provided by the common USB slave package.
The default handler can cope with all of the standard control messages +for a simple USB peripheral. However, if the peripheral involves +multiple configurations, multiple interfaces in a configuration, or +alternate settings for an interface, then this cannot be handled by +generic code. For example, a multimedia peripheral may support various +alternate settings for a given data source with different bandwidth +requirements, and the host can select a setting that takes into +account the current load. Clearly higher-level code needs to be aware +when the host changes the current setting, so that it can adjust the +rate at which data is fed to or retrieved from the host. Therefore the +higher-level code needs to install its own standard control callback +and process appropriate messages, rather than leaving these to the +default handler.
The default handler will take care of the +get-descriptor request used to obtain the +enumeration data. It has support for string descriptors but ignores +language encoding issues. If language encoding is important for the +peripheral then this will have to be handled by an +application-specific standard control handler.
The header file <cyg/io/usb/usb.h> defines various +constants related to control messages, for example the function codes +corresponding to the standard request types. This header file is +provided by the common USB package, not by the USB slave package, +since the information is also relevant to USB hosts.
Other Control Messages
typedef struct usbs_control_endpoint { + … + usbs_control_return (*class_control_fn)(struct usbs_control_endpoint*, void*); + void* class_control_data; + usbs_control_return (*vendor_control_fn)(struct usbs_control_endpoint*, void*); + void* vendor_control_data; + usbs_control_return (*reserved_control_fn)(struct usbs_control_endpoint*, void*); + void* reserved_control_data; + … +} usbs_control_endpoint; |
Non-standard control messages always have to be processed by +higher-level code. This could be class-specific packages. For example, +the USB-ethernet package will handle requests for getting the MAC +address and for enabling or disabling promiscuous mode. In all cases +the device driver will store the initial request in the +control_buffer field, check for an +appropriate handler, and invoke it with details of the control +endpoint and any handler-specific data that has been installed +alongside the handler itself. The handler should return either +USBS_CONTROL_RETURN_HANDLED to report success or +USBS_CONTROL_RETURN_STALL to report failure. The +device driver will report this to the host.
If there are multiple parties interested in a particular type of +control messages, it is the responsibility of application code to +install an appropriate handler and process the requests appropriately.
Buffer Management
typedef struct usbs_control_endpoint { + … + unsigned char* buffer; + int buffer_size; + void (*fill_buffer_fn)(struct usbs_control_endpoint*); + void* fill_data; + int fill_index; + usbs_control_return (*complete_fn)(struct usbs_control_endpoint*, int); + … +} usbs_control_endpoint; |
Many USB control messages involve transferring more data than just the +initial eight-byte header. The header indicates the direction of the +transfer, OUT for host to peripheral or IN for peripheral to host. +It also specifies a length field, which is exact for an OUT transfer +or an upper bound for an IN transfer. Control message handlers can +manipulate six fields within the control endpoint data structure to +ensure that the transfer happens correctly.
For an OUT transfer, the handler should examine the length field in +the header and provide a single buffer for all the data. A +class-specific protocol would typically impose an upper bound on the +amount of data, allowing the buffer to be allocated statically. +The handler should update the buffer and +complete_fn fields. When all the data has +been transferred the completion callback will be invoked, and its +return value determines the response sent back to the host. The USB +standard allows for a new control message to be sent before the +current transfer has completed, effectively cancelling the current +operation. When this happens the completion function will also be +invoked. The second argument to the completion function specifies what +has happened, with a value of 0 indicating success and an error code +such as -EPIPE or -EIO +indicating that the current transfer has been cancelled.
IN transfers are a little bit more complicated. The required +information, for example the enumeration data, may not be in a single +contiguous buffer. Instead a mechanism is provided by which the buffer +can be refilled, thus allowing the transfer to move from one record to +the next. Essentially, the transfer operates as follows:
When the host requests another chunk of data (typically eight bytes), +the USB device driver will examine the +buffer_size field. If non-zero then +buffer contains at least one more byte of +data, and then buffer_size is decremented.
When buffer_size has dropped to 0, the +fill_buffer_fn field will be examined. If +non-null it will be invoked to refill the buffer.
The fill_data and +fill_index fields are not used by the +device driver. Instead these fields are available to the refill +function to keep track of the current state of the transfer.
When buffer_size is 0 and +fill_buffer_fn is NULL, no more data is +available and the transfer has completed.
Optionally a completion function can be installed. This will be +invoked with 0 if the transfer completes successfully, or with an +error code if the transfer is cancelled because of another control +messsage.
If the requested data is contiguous then the only fields that need +to be manipulated are buffer and +buffer_size, and optionally +complete_fn. If the requested data is not +contiguous then the initial control message handler should update +fill_buffer_fn and some or all of the other +fields, as required. An example of this is the handling of the +standard get-descriptor control message by +usbs_handle_standard_control.
Polling Support
typedef struct usbs_control_endpoint { + void (*poll_fn)(struct usbs_control_endpoint*); + int interrupt_vector; + … +} usbs_control_endpoint; |
In nearly all circumstances USB I/O should be interrupt-driven. +However, there are special environments such as RedBoot where polled +operation may be appropriate. If the device driver can operate in +polled mode then it will provide a suitable function via the +poll_fn field, and higher-level code can +invoke this regularly. This polling function will take care of all +endpoints associated with the device, not just the control endpoint. +If the USB hardware involves a single interrupt vector then this will +be identified in the data structure as well.